Package pywingo
[frames] | no frames]

Source Code for Package pywingo

  1  import json 
  2  import os 
  3  import os.path 
  4  import socket 
  5  import sys 
  6  import time 
  7   
  8  from pywingo.commands import WingoCommands 
  9  import pywingo.events as events 
 10   
 11  _bool_cmds = ['True', 'False', 'Not', 'And', 'Or'] 
 12  _string_cmds = ['GetWorkspace', 'GetWorkspaceList', 
 13                  'GetWorkspaceNext', 'GetWorkspacePrev', 'GetWorkspacePrefix', 
 14                  'GetHeadWorkspace'] 
 15   
 16   
17 -class WingoError(Exception):
18 - def __init__(self, message):
19 self.message = message
20
21 - def __str__(self):
22 return self.message
23 24
25 -class Disconnected(Exception):
26 - def __init__(self):
27 pass
28
29 - def __str__(self):
30 return 'socket disconnected'
31 32
33 -class WingoUtil(WingoCommands):
34 ''' 35 Provides a set of utility functions on top of the base commands 36 defined by Wingo. These are special to the Python Wingo bindings. 37 '''
38 - def __init__(self):
39 assert False, 'cannot create WingoCommands directly'
40
41 - def GetAllNormalClients(self):
42 ''' 43 Exactly the same as GetAllClients, except only clients with 44 type "normal" are returned. (i.e., excludes "desktop" and 45 "dock" clients.) 46 ''' 47 cids = [] 48 for cid in self.GetAllClients(): 49 if self.GetClientType(cid) == 'normal': 50 cids.append(cid) 51 return cids
52
53 - def IsEmpty(self, Workspace):
54 ''' 55 Returns true if the given Workspace has no clients. (Including 56 iconified clients.) 57 58 Workspace may be a workspace index (integer) starting at 0, or a 59 workspace name. 60 ''' 61 return len(self.GetClientList(Workspace)) == 0
62
63 - def GetVisibleWorkspaceList(self):
64 ''' 65 Returns a list of all visible workspaces in order of their 66 physical position: left to right and then top to bottom. 67 ''' 68 spaces = [] 69 for i in xrange(self.GetNumHeads()): 70 spaces.append(self.GetHeadWorkspace(i)) 71 return spaces
72
73 - def GetHiddenWorkspaceList(self):
74 ''' 75 Returns a list of all hidden workspaces. 76 ''' 77 spaces = [] 78 visibles = set(self.GetVisibleWorkspaceList()) 79 for space in self.GetWorkspaceList(): 80 if space not in visibles: 81 spaces.append(space) 82 return spaces
83 84
85 -class Wingo(WingoUtil):
86 - def __init__(self):
87 self.__path = os.path.join(os.getenv('XDG_RUNTIME_DIR'), 'wingo', 88 os.getenv('DISPLAY')) 89 self.__buf = '' 90 self.__evbuf = '' 91 self.__callbacks = {} 92 93 # Not opened until the first command is issued. 94 self.__sock = None 95 96 # Not opened until the event loop is started. 97 self.__evsock = None
98
99 - def __del__(self):
100 self.__sock.close()
101
102 - def __reconnect(self):
103 self.__sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 104 self.__sock.connect(self.__path)
105
106 - def __recv(self):
107 while chr(0) not in self.__buf: 108 data = self.__sock.recv(4096) 109 if not data: 110 raise Disconnected 111 self.__buf += data 112 113 sentinel = self.__buf.index(chr(0)) 114 payload = self.__buf[0:sentinel][:] 115 self.__buf = self.__buf[sentinel+1:] 116 return payload
117
118 - def __recv_event(self):
119 assert self.__evsock is not None 120 121 # So far this is the same as `__recv`, but they use different 122 # buffers and could potentially use different protocols. 123 while chr(0) not in self.__evbuf: 124 data = self.__evsock.recv(4096) 125 if not data: 126 raise Disconnected 127 self.__evbuf += data 128 129 sentinel = self.__evbuf.index(chr(0)) 130 payload = self.__evbuf[0:sentinel][:] 131 self.__evbuf = self.__evbuf[sentinel+1:] 132 return payload
133
134 - def gribble(self, cmd):
135 ''' 136 Executes a raw Gribble commands and always returns the result 137 as a string. This should only be used if you know it's necessary. 138 Otherwise, use the API to run commands. 139 ''' 140 if self.__sock is None: 141 self.__reconnect() 142 self.__sock.send("%s%s" % (cmd, chr(0))) 143 return self.__recv()
144
145 - def _assert_arg_type(self, name, val, types):
146 for t in types: 147 if isinstance(val, t): 148 return 149 assert False, '%s has invalid type %s' % (name, type(val))
150
151 - def _gribble_arg_str(self, vals):
152 args = [] 153 for v in vals: 154 if isinstance(v, int) or isinstance(v, float): 155 args.append(repr(v)) 156 elif isinstance(v, basestring): 157 args.append('"%s"' % self._escape_str(v)) 158 else: 159 assert False, 'bug' 160 return ' '.join(args)
161
162 - def _escape_str(self, s):
163 return s.replace('"', '\\"')
164
165 - def _from_str(self, cmd_name, s):
166 if cmd_name in _bool_cmds or cmd_name.startswith('Match'): 167 return bool(int(s)) 168 169 if 'List' in cmd_name or '\n' in s: 170 trimmed = s.strip() 171 if len(trimmed) == 0: 172 return [] 173 return map(lambda item: self._primitive_from_str(cmd_name, item), 174 trimmed.split('\n')) 175 176 if cmd_name in _string_cmds: 177 return s 178 179 try: 180 return int(s) 181 except ValueError: 182 try: 183 return float(s) 184 except ValueError: 185 if s.startswith('ERROR:'): 186 raise WingoError(s) 187 else: 188 return s 189 190 assert False, 'bug'
191
192 - def _primitive_from_str(self, cmd_name, s):
193 if cmd_name in _bool_cmds or cmd_name.startswith('Match'): 194 return bool(int(s)) 195 196 if cmd_name in _string_cmds: 197 return s 198 199 try: 200 return int(s) 201 except ValueError: 202 try: 203 return float(s) 204 except ValueError: 205 return s 206 207 assert False, 'bug'
208
209 - def __wingo_restarting(self, ev):
210 self.__sock = None
211
212 - def loop(self, restart=True):
213 ''' 214 Listens for event notifications and executes callbacks when 215 corresponding events are received. 216 217 When `restart` is enabled, the event loop will be restarted if there 218 was an error reading from the socket. This is intended to keep the 219 program alive if Wingo restarts. (If Wingo really does die, the 220 reconnection will fail and a regular socket error will be raised.) 221 ''' 222 223 # Invalidate the Gribble socket once Wingo starts restarting. 224 self.bind('Restarting', self.__wingo_restarting) 225 226 try: 227 self.__evsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 228 f = os.path.join(self.__path + '-notify') 229 self.__evsock.connect(f) 230 231 while True: 232 evJson = self.__recv_event() 233 j = json.loads(evJson) 234 ev_name = str(j['EventName']) 235 key = '_new_%s' % ev_name 236 if key not in events.__dict__: 237 print >> sys.stderr, \ 238 'Event "%s" is not defined in pywingo. ' \ 239 'Time to update!' % ev_name 240 continue 241 ev = events.__dict__[key](j) 242 243 for cb in self.__callbacks.get(ev_name, []): 244 cb(ev) 245 except Disconnected: 246 if not restart: 247 raise Disconnected 248 time.sleep(1) 249 self.loop(restart)
250
251 - def bind(self, event_name, f=None):
252 ''' 253 Binds an event named `event_name` to a callback function `f`. 254 `f` should be a function that takes a single argument `event`, 255 which will correspond to a namedtuple of the event with any 256 relevant data as properties. 257 258 If `f` is None, then a partially applied function is returned. 259 (For decorator support.) 260 ''' 261 def doit(fun): 262 if event_name not in self.__callbacks: 263 self.__callbacks[event_name] = [] 264 self.__callbacks[event_name].append(fun) 265 return fun
266 267 if f is None: 268 return doit 269 return doit(f)
270