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