Package Skype4Py :: Package api :: Module posix_x11
[frames] | no frames]

Source Code for Module Skype4Py.api.posix_x11

  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  # The Xlib Programming Manual: 
 54  # ============================ 
 55  # http://tronche.com/gui/x/xlib/ 
 56   
 57   
 58  # some Xlib constants 
 59  PropertyChangeMask = 0x400000 
 60  PropertyNotify = 28 
 61  ClientMessage = 33 
 62  PropertyNewValue = 0 
 63  PropertyDelete = 1 
 64   
 65   
 66  # some Xlib types 
 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  # should the structures be aligned to 8 bytes? 
 80  align = (sizeof(c_long) == 8 and sizeof(c_int) == 4) 
 81   
 82   
 83  # some Xlib structures 
84 -class XClientMessageEvent(Structure):
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
107 -class XPropertyEvent(Structure):
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
130 -class XErrorEvent(Structure):
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
149 -class XEvent(Union):
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 # we get here if we're building docs; to let the module import without 168 # exceptions, we emulate the X11 library using a class:
169 - class X(object):
170 - def __getattr__(self, name):
171 return self
172 - def __setattr__(self, name, value):
173 pass
174 - def __call__(self, *args, **kwargs):
175 pass
176 x11 = X() 177 else: 178 # load X11 library (Xlib) 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 # setup Xlib function prototypes 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
227 -def threads_init(gtk=True):
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 # enable X11 multithreading 236 x11.XInitThreads() 237 if gtk: 238 from gtk.gdk import threads_init 239 threads_init()
240 241
242 -class SkypeAPI(SkypeAPIBase):
243 - def __init__(self, opts):
244 self.logger = logging.getLogger('Skype4Py.api.posix_x11.SkypeAPI') 245 SkypeAPIBase.__init__(self) 246 finalize_opts(opts) 247 248 # initialize threads if not done already by the user 249 threads_init(gtk=False) 250 251 # init Xlib display 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
268 - def __del__(self):
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
275 - def run(self):
276 self.logger.info('thread started') 277 # main loop 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 # events we get here are already prefiltered by the predicate function 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 # changing attachment status can cause an event handler to be fired, in 307 # turn it could try to call Attach() and doing this immediately seems to 308 # confuse Skype (command '#0 NAME xxx' returns '#0 CONNSTATUS OFFLINE' :D); 309 # to fix this, we give Skype some time to initialize itself 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
323 - def get_skype(self):
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
339 - def close(self):
340 self.loop_break = True 341 self.loop_event.set() 342 while self.isAlive(): 343 time.sleep(0.01) 344 SkypeAPIBase.close(self)
345
346 - def set_friendly_name(self, friendly_name):
347 SkypeAPIBase.set_friendly_name(self, friendly_name) 348 if self.attachment_status == apiAttachSuccess: 349 # reattach with the new name 350 self.set_attachment_status(apiAttachUnknown) 351 self.attach()
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
395 - def is_running(self):
396 return (self.get_skype() is not None)
397
398 - def startup(self, minimized, nosplash):
399 # options are not supported as of Skype 1.4 Beta for Linux 400 if not self.is_running(): 401 if os.fork() == 0: # we're the child 402 os.setsid() 403 os.execlp('skype')
404
405 - def shutdown(self):
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 # Skype sometimes doesn't delete the '_SKYPE_INSTANCE' property 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
419 - def send_command(self, command, force=False):
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
449 - def notify(self, cmd):
450 self.logger.debug('received %s', repr(cmd)) 451 # Called by main loop for all received Skype commands. 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