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
19 self.message = message
20
23
24
28
30 return 'socket disconnected'
31
32
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 '''
39 assert False, 'cannot create WingoCommands directly'
40
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
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
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
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
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
94 self.__sock = None
95
96
97 self.__evsock = None
98
101
103 self.__sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
104 self.__sock.connect(self.__path)
105
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
119 assert self.__evsock is not None
120
121
122
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
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
146 for t in types:
147 if isinstance(val, t):
148 return
149 assert False, '%s has invalid type %s' % (name, type(val))
150
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
163 return s.replace('"', '\\"')
164
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
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
211
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
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