1 """
2 Low level *Skype for Linux* interface implemented using *XWindows messaging*.
3 Uses direct *Xlib* calls through *ctypes* module.
4
5 This module handles the options that you can pass to `Skype.__init__`
6 for Linux machines when the transport is set to *X11*.
7
8 No further options are currently supported.
9
10 Warning PyGTK framework users
11 =============================
12
13 The multithreaded architecture of Skype4Py requires a special treatment
14 if the Xlib transport is combined with PyGTK GUI framework.
15
16 The following code has to be called at the top of your script, before
17 PyGTK is even imported.
18
19 .. python::
20
21 from Skype4Py.api.posix_x11 import threads_init
22 threads_init()
23
24 This function enables multithreading support in Xlib and GDK. If not done
25 here, this is enabled for Xlib library when the `Skype` object is instantiated.
26 If your script imports the PyGTK module, doing this so late may lead to a
27 segmentation fault when the GUI is shown on the screen.
28
29 A remedy is to enable the multithreading support before PyGTK is imported
30 by calling the ``threads_init`` function.
31 """
32 __docformat__ = 'restructuredtext en'
33
34
35 import sys
36 import threading
37 import os
38 from ctypes import *
39
40 from ctypes.util import find_library
41 import time
42 import logging
43
44 from Skype4Py.api import Command, SkypeAPIBase, \
45 timeout2float, finalize_opts
46 from Skype4Py.enums import *
47 from Skype4Py.errors import SkypeAPIError
48
49
50 __all__ = ['SkypeAPI', 'threads_init']
51
52
53
54
55
56
57
58
59 PropertyChangeMask = 0x400000
60 PropertyNotify = 28
61 ClientMessage = 33
62 PropertyNewValue = 0
63 PropertyDelete = 1
64
65
66
67 c_ulong_p = POINTER(c_ulong)
68 DisplayP = c_void_p
69 Atom = c_ulong
70 AtomP = c_ulong_p
71 XID = c_ulong
72 Window = XID
73 Bool = c_int
74 Status = c_int
75 Time = c_ulong
76 c_int_p = POINTER(c_int)
77
78
79
80 align = (sizeof(c_long) == 8 and sizeof(c_int) == 4)
81
82
83
85 if align:
86 _fields_ = [('type', c_int),
87 ('pad0', c_int),
88 ('serial', c_ulong),
89 ('send_event', Bool),
90 ('pad1', c_int),
91 ('display', DisplayP),
92 ('window', Window),
93 ('message_type', Atom),
94 ('format', c_int),
95 ('pad2', c_int),
96 ('data', c_char * 20)]
97 else:
98 _fields_ = [('type', c_int),
99 ('serial', c_ulong),
100 ('send_event', Bool),
101 ('display', DisplayP),
102 ('window', Window),
103 ('message_type', Atom),
104 ('format', c_int),
105 ('data', c_char * 20)]
106
108 if align:
109 _fields_ = [('type', c_int),
110 ('pad0', c_int),
111 ('serial', c_ulong),
112 ('send_event', Bool),
113 ('pad1', c_int),
114 ('display', DisplayP),
115 ('window', Window),
116 ('atom', Atom),
117 ('time', Time),
118 ('state', c_int),
119 ('pad2', c_int)]
120 else:
121 _fields_ = [('type', c_int),
122 ('serial', c_ulong),
123 ('send_event', Bool),
124 ('display', DisplayP),
125 ('window', Window),
126 ('atom', Atom),
127 ('time', Time),
128 ('state', c_int)]
129
131 if align:
132 _fields_ = [('type', c_int),
133 ('pad0', c_int),
134 ('display', DisplayP),
135 ('resourceid', XID),
136 ('serial', c_ulong),
137 ('error_code', c_ubyte),
138 ('request_code', c_ubyte),
139 ('minor_code', c_ubyte)]
140 else:
141 _fields_ = [('type', c_int),
142 ('display', DisplayP),
143 ('resourceid', XID),
144 ('serial', c_ulong),
145 ('error_code', c_ubyte),
146 ('request_code', c_ubyte),
147 ('minor_code', c_ubyte)]
148
150 if align:
151 _fields_ = [('type', c_int),
152 ('xclient', XClientMessageEvent),
153 ('xproperty', XPropertyEvent),
154 ('xerror', XErrorEvent),
155 ('pad', c_long * 24)]
156 else:
157 _fields_ = [('type', c_int),
158 ('xclient', XClientMessageEvent),
159 ('xproperty', XPropertyEvent),
160 ('xerror', XErrorEvent),
161 ('pad', c_long * 24)]
162
163 XEventP = POINTER(XEvent)
164
165
166 if getattr(sys, 'skype4py_setup', False):
167
168
176 x11 = X()
177 else:
178
179 libpath = find_library('X11')
180 if not libpath:
181 raise ImportError('Could not find X11 library')
182 x11 = cdll.LoadLibrary(libpath)
183 del libpath
184
185
186
187 x11.XCloseDisplay.argtypes = (DisplayP,)
188 x11.XCloseDisplay.restype = None
189 x11.XCreateSimpleWindow.argtypes = (DisplayP, Window, c_int, c_int, c_uint,
190 c_uint, c_uint, c_ulong, c_ulong)
191 x11.XCreateSimpleWindow.restype = Window
192 x11.XDefaultRootWindow.argtypes = (DisplayP,)
193 x11.XDefaultRootWindow.restype = Window
194 x11.XDeleteProperty.argtypes = (DisplayP, Window, Atom)
195 x11.XDeleteProperty.restype = None
196 x11.XDestroyWindow.argtypes = (DisplayP, Window)
197 x11.XDestroyWindow.restype = None
198 x11.XFree.argtypes = (c_void_p,)
199 x11.XFree.restype = None
200 x11.XGetAtomName.argtypes = (DisplayP, Atom)
201 x11.XGetAtomName.restype = c_void_p
202 x11.XGetErrorText.argtypes = (DisplayP, c_int, c_char_p, c_int)
203 x11.XGetErrorText.restype = None
204 x11.XGetWindowProperty.argtypes = (DisplayP, Window, Atom, c_long, c_long, Bool,
205 Atom, AtomP, c_int_p, c_ulong_p, c_ulong_p, POINTER(POINTER(Window)))
206 x11.XGetWindowProperty.restype = c_int
207 x11.XInitThreads.argtypes = ()
208 x11.XInitThreads.restype = Status
209 x11.XInternAtom.argtypes = (DisplayP, c_char_p, Bool)
210 x11.XInternAtom.restype = Atom
211 x11.XNextEvent.argtypes = (DisplayP, XEventP)
212 x11.XNextEvent.restype = None
213 x11.XOpenDisplay.argtypes = (c_char_p,)
214 x11.XOpenDisplay.restype = DisplayP
215 x11.XPending.argtypes = (DisplayP,)
216 x11.XPending.restype = c_int
217 x11.XSelectInput.argtypes = (DisplayP, Window, c_long)
218 x11.XSelectInput.restype = None
219 x11.XSendEvent.argtypes = (DisplayP, Window, Bool, c_long, XEventP)
220 x11.XSendEvent.restype = Status
221 x11.XLockDisplay.argtypes = (DisplayP,)
222 x11.XLockDisplay.restype = None
223 x11.XUnlockDisplay.argtypes = (DisplayP,)
224 x11.XUnlockDisplay.restype = None
225
226
228 """Enables multithreading support in Xlib and PyGTK.
229 See the module docstring for more info.
230
231 :Parameters:
232 gtk : bool
233 May be set to False to skip the PyGTK module.
234 """
235
236 x11.XInitThreads()
237 if gtk:
238 from gtk.gdk import threads_init
239 threads_init()
240
241
244 self.logger = logging.getLogger('Skype4Py.api.posix_x11.SkypeAPI')
245 SkypeAPIBase.__init__(self)
246 finalize_opts(opts)
247
248
249 threads_init(gtk=False)
250
251
252 self.disp = x11.XOpenDisplay(None)
253 if not self.disp:
254 raise SkypeAPIError('Could not open XDisplay')
255 self.win_root = x11.XDefaultRootWindow(self.disp)
256 self.win_self = x11.XCreateSimpleWindow(self.disp, self.win_root,
257 100, 100, 100, 100, 1, 0, 0)
258 x11.XSelectInput(self.disp, self.win_root, PropertyChangeMask)
259 self.win_skype = self.get_skype()
260 ctrl = 'SKYPECONTROLAPI_MESSAGE'
261 self.atom_msg = x11.XInternAtom(self.disp, ctrl, False)
262 self.atom_msg_begin = x11.XInternAtom(self.disp, ctrl + '_BEGIN', False)
263
264 self.loop_event = threading.Event()
265 self.loop_timeout = 0.0001
266 self.loop_break = False
267
269 if x11:
270 if hasattr(self, 'disp'):
271 if hasattr(self, 'win_self'):
272 x11.XDestroyWindow(self.disp, self.win_self)
273 x11.XCloseDisplay(self.disp)
274
276 self.logger.info('thread started')
277
278 event = XEvent()
279 data = ''
280 while not self.loop_break and x11:
281 while x11.XPending(self.disp):
282 self.loop_timeout = 0.0001
283 x11.XNextEvent(self.disp, byref(event))
284
285 if event.type == ClientMessage:
286 if event.xclient.format == 8:
287 if event.xclient.message_type == self.atom_msg_begin:
288 data = str(event.xclient.data)
289 elif event.xclient.message_type == self.atom_msg:
290 if data != '':
291 data += str(event.xclient.data)
292 else:
293 self.logger.warning('Middle of Skype X11 message received with no beginning!')
294 else:
295 continue
296 if len(event.xclient.data) != 20 and data:
297 self.notify(data.decode('utf-8'))
298 data = ''
299 elif event.type == PropertyNotify:
300 namep = x11.XGetAtomName(self.disp, event.xproperty.atom)
301 is_inst = (c_char_p(namep).value == '_SKYPE_INSTANCE')
302 x11.XFree(namep)
303 if is_inst:
304 if event.xproperty.state == PropertyNewValue:
305 self.win_skype = self.get_skype()
306
307
308
309
310 time.sleep(1.0)
311 self.set_attachment_status(apiAttachAvailable)
312 elif event.xproperty.state == PropertyDelete:
313 self.win_skype = None
314 self.set_attachment_status(apiAttachNotAvailable)
315 self.loop_event.wait(self.loop_timeout)
316 if self.loop_event.isSet():
317 self.loop_timeout = 0.0001
318 elif self.loop_timeout < 1.0:
319 self.loop_timeout *= 2
320 self.loop_event.clear()
321 self.logger.info('thread finished')
322
324 """Returns Skype window ID or None if Skype not running."""
325 skype_inst = x11.XInternAtom(self.disp, '_SKYPE_INSTANCE', True)
326 if not skype_inst:
327 return
328 type_ret = Atom()
329 format_ret = c_int()
330 nitems_ret = c_ulong()
331 bytes_after_ret = c_ulong()
332 winp = pointer(Window())
333 fail = x11.XGetWindowProperty(self.disp, self.win_root, skype_inst,
334 0, 1, False, 33, byref(type_ret), byref(format_ret),
335 byref(nitems_ret), byref(bytes_after_ret), byref(winp))
336 if not fail and format_ret.value == 32 and nitems_ret.value == 1:
337 return winp.contents.value
338
340 self.loop_break = True
341 self.loop_event.set()
342 while self.isAlive():
343 time.sleep(0.01)
344 SkypeAPIBase.close(self)
345
352
353 - def attach(self, timeout, wait=True):
354 if self.attachment_status == apiAttachSuccess:
355 return
356 self.acquire()
357 try:
358 if not self.isAlive():
359 try:
360 self.start()
361 except AssertionError:
362 raise SkypeAPIError('Skype API closed')
363 try:
364 self.wait = True
365 t = threading.Timer(timeout2float(timeout), lambda: setattr(self, 'wait', False))
366 if wait:
367 t.start()
368 while self.wait:
369 self.win_skype = self.get_skype()
370 if self.win_skype is not None:
371 break
372 else:
373 time.sleep(1.0)
374 else:
375 raise SkypeAPIError('Skype attach timeout')
376 finally:
377 t.cancel()
378 command = Command('NAME %s' % self.friendly_name, '', True, timeout)
379 self.release()
380 try:
381 self.send_command(command, True)
382 finally:
383 self.acquire()
384 if command.Reply != 'OK':
385 self.win_skype = None
386 self.set_attachment_status(apiAttachRefused)
387 return
388 self.set_attachment_status(apiAttachSuccess)
389 finally:
390 self.release()
391 command = Command('PROTOCOL %s' % self.protocol, Blocking=True)
392 self.send_command(command, True)
393 self.protocol = int(command.Reply.rsplit(None, 1)[-1])
394
397
398 - def startup(self, minimized, nosplash):
399
400 if not self.is_running():
401 if os.fork() == 0:
402 os.setsid()
403 os.execlp('skype')
404
406 from signal import SIGINT
407 fh = os.popen('ps -o %p --no-heading -C skype')
408 pid = fh.readline().strip()
409 fh.close()
410 if pid:
411 os.kill(int(pid), SIGINT)
412
413 skype_inst = x11.XInternAtom(self.disp, '_SKYPE_INSTANCE', True)
414 if skype_inst:
415 x11.XDeleteProperty(self.disp, self.win_root, skype_inst)
416 self.win_skype = None
417 self.set_attachment_status(apiAttachNotAvailable)
418
420 if self.attachment_status != apiAttachSuccess and not force:
421 self.attach(command.Timeout)
422 self.push_command(command)
423 self.notifier.sending_command(command)
424 cmd = u'#%d %s' % (command.Id, command.Command)
425 self.logger.debug('sending %s', repr(cmd))
426 if command.Blocking:
427 command._event = bevent = threading.Event()
428 else:
429 command._timer = timer = threading.Timer(command.timeout2float(), self.pop_command, (command.Id,))
430 event = XEvent()
431 event.xclient.type = ClientMessage
432 event.xclient.display = self.disp
433 event.xclient.window = self.win_self
434 event.xclient.message_type = self.atom_msg_begin
435 event.xclient.format = 8
436 cmd = cmd.encode('utf-8') + '\x00'
437 for i in xrange(0, len(cmd), 20):
438 event.xclient.data = cmd[i:i + 20]
439 x11.XSendEvent(self.disp, self.win_skype, False, 0, byref(event))
440 event.xclient.message_type = self.atom_msg
441 self.loop_event.set()
442 if command.Blocking:
443 bevent.wait(command.timeout2float())
444 if not bevent.isSet():
445 raise SkypeAPIError('Skype command timeout')
446 else:
447 timer.start()
448
450 self.logger.debug('received %s', repr(cmd))
451
452 if cmd.startswith(u'#'):
453 p = cmd.find(u' ')
454 command = self.pop_command(int(cmd[1:p]))
455 if command is not None:
456 command.Reply = cmd[p + 1:]
457 if command.Blocking:
458 command._event.set()
459 else:
460 command._timer.cancel()
461 self.notifier.reply_received(command)
462 else:
463 self.notifier.notification_received(cmd[p + 1:])
464 else:
465 self.notifier.notification_received(cmd)
466