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 
  3  using XWindows messaging. Uses direct Xlib calls 
  4  through ctypes module. 
  5   
  6  This module handles the options that you can pass to L{ISkype.__init__<skype.ISkype.__init__>} 
  7  for Linux machines when the transport is set to X11. 
  8   
  9  No further options are currently supported. 
 10  ''' 
 11   
 12  import threading 
 13  from ctypes import * 
 14  from ctypes.util import find_library 
 15  import time 
 16  from Skype4Py.API import ICommand, _ISkypeAPIBase 
 17  from Skype4Py.enums import * 
 18  from Skype4Py.errors import ISkypeAPIError 
 19   
 20   
 21  # some Xlib constants 
 22  _PropertyChangeMask = 0x400000 
 23  _PropertyNotify = 28 
 24  _ClientMessage = 33 
 25  _PropertyNewValue = 0 
 26  _PropertyDelete = 1 
 27   
 28   
 29  # some Xlib types 
 30  c_ulong_p = POINTER(c_ulong) 
 31  DisplayP = c_void_p 
 32  Atom = c_ulong 
 33  AtomP = c_ulong_p 
 34  XID = c_ulong 
 35  Window = XID 
 36  Bool = c_int 
 37  Status = c_int 
 38  Time = c_ulong 
 39  c_int_p = POINTER(c_int) 
 40   
 41   
 42  # should the structures be aligned to 8 bytes? 
 43  _align = (sizeof(c_long) == 8 and sizeof(c_int) == 4) 
 44   
 45   
 46  # some Xlib structures 
47 -class _XClientMessageEvent(Structure):
48 if _align: 49 _fields_ = [('type', c_int), 50 ('pad0', c_int), 51 ('serial', c_ulong), 52 ('send_event', Bool), 53 ('pad1', c_int), 54 ('display', DisplayP), 55 ('window', Window), 56 ('message_type', Atom), 57 ('format', c_int), 58 ('pad2', c_int), 59 ('data', c_char * 20)] 60 else: 61 _fields_ = [('type', c_int), 62 ('serial', c_ulong), 63 ('send_event', Bool), 64 ('display', DisplayP), 65 ('window', Window), 66 ('message_type', Atom), 67 ('format', c_int), 68 ('data', c_char * 20)]
69
70 -class _XPropertyEvent(Structure):
71 if _align: 72 _fields_ = [('type', c_int), 73 ('pad0', c_int), 74 ('serial', c_ulong), 75 ('send_event', Bool), 76 ('pad1', c_int), 77 ('display', DisplayP), 78 ('window', Window), 79 ('atom', Atom), 80 ('time', Time), 81 ('state', c_int), 82 ('pad2', c_int)] 83 else: 84 _fields_ = [('type', c_int), 85 ('serial', c_ulong), 86 ('send_event', Bool), 87 ('display', DisplayP), 88 ('window', Window), 89 ('atom', Atom), 90 ('time', Time), 91 ('state', c_int)]
92
93 -class _XErrorEvent(Structure):
94 if _align: 95 _fields_ = [('type', c_int), 96 ('pad0', c_int), 97 ('display', DisplayP), 98 ('resourceid', XID), 99 ('serial', c_ulong), 100 ('error_code', c_ubyte), 101 ('request_code', c_ubyte), 102 ('minor_code', c_ubyte)] 103 else: 104 _fields_ = [('type', c_int), 105 ('display', DisplayP), 106 ('resourceid', XID), 107 ('serial', c_ulong), 108 ('error_code', c_ubyte), 109 ('request_code', c_ubyte), 110 ('minor_code', c_ubyte)]
111
112 -class _XEvent(Union):
113 if _align: 114 _fields_ = [('type', c_int), 115 ('xclient', _XClientMessageEvent), 116 ('xproperty', _XPropertyEvent), 117 ('xerror', _XErrorEvent), 118 ('pad', c_long * 24)] 119 else: 120 _fields_ = [('type', c_int), 121 ('xclient', _XClientMessageEvent), 122 ('xproperty', _XPropertyEvent), 123 ('xerror', _XErrorEvent), 124 ('pad', c_long * 24)]
125 126 XEventP = POINTER(_XEvent) 127 128 129 # Xlib error handler type 130 XErrorHandlerP = CFUNCTYPE(c_int, DisplayP, POINTER(_XErrorEvent)) 131 132
133 -class _ISkypeAPI(_ISkypeAPIBase):
134 - def __init__(self, handler, opts):
135 _ISkypeAPIBase.__init__(self, opts) 136 self.RegisterHandler(handler) 137 138 # check options 139 if opts: 140 raise TypeError('Unexpected parameter(s): %s' % ', '.join(opts.keys())) 141 142 # setup Xlib 143 libpath = find_library('X11') 144 if not libpath: 145 raise ImportError('Could not find X11 library') 146 self.x11 = cdll.LoadLibrary(libpath) 147 148 # setup Xlib function prototypes 149 self.x11.XCloseDisplay.argtypes = (DisplayP,) 150 self.x11.XCloseDisplay.restype = None 151 self.x11.XCreateSimpleWindow.argtypes = (DisplayP, Window, c_int, c_int, c_uint, 152 c_uint, c_uint, c_ulong, c_ulong) 153 self.x11.XCreateSimpleWindow.restype = Window 154 self.x11.XDefaultRootWindow.argtypes = (DisplayP,) 155 self.x11.XDefaultRootWindow.restype = Window 156 self.x11.XDeleteProperty.argtypes = (DisplayP, Window, Atom) 157 self.x11.XDeleteProperty.restype = None 158 self.x11.XDestroyWindow.argtypes = (DisplayP, Window) 159 self.x11.XDestroyWindow.restype = None 160 self.x11.XPending.argtypes = (DisplayP,) 161 self.x11.XPending.restype = c_int 162 self.x11.XGetAtomName.argtypes = (DisplayP, Atom) 163 self.x11.XGetAtomName.restype = c_char_p 164 self.x11.XGetErrorText.argtypes = (DisplayP, c_int, c_char_p, c_int) 165 self.x11.XGetErrorText.restype = None 166 self.x11.XGetWindowProperty.argtypes = (DisplayP, Window, Atom, c_long, c_long, Bool, 167 Atom, AtomP, c_int_p, c_ulong_p, c_ulong_p, POINTER(POINTER(Window))) 168 self.x11.XGetWindowProperty.restype = c_int 169 self.x11.XInitThreads.argtypes = () 170 self.x11.XInitThreads.restype = Status 171 self.x11.XInternAtom.argtypes = (DisplayP, c_char_p, Bool) 172 self.x11.XInternAtom.restype = Atom 173 self.x11.XNextEvent.argtypes = (DisplayP, XEventP) 174 self.x11.XNextEvent.restype = None 175 self.x11.XOpenDisplay.argtypes = (c_char_p,) 176 self.x11.XOpenDisplay.restype = DisplayP 177 self.x11.XSelectInput.argtypes = (DisplayP, Window, c_long) 178 self.x11.XSelectInput.restype = None 179 self.x11.XSendEvent.argtypes = (DisplayP, Window, Bool, c_long, XEventP) 180 self.x11.XSendEvent.restype = Status 181 self.x11.XSetErrorHandler.argtypes = (XErrorHandlerP,) 182 self.x11.XSetErrorHandler.restype = None 183 self.x11.XLockDisplay.argtypes = (DisplayP,) 184 self.x11.XLockDisplay.restype = None 185 self.x11.XUnlockDisplay.argtypes = (DisplayP,) 186 self.x11.XUnlockDisplay.restype = None 187 188 # init Xlib 189 self.x11.XInitThreads() 190 self.error = None 191 # callback has to be saved to keep reference to bound method 192 self._error_handler_callback = XErrorHandlerP(self._error_handler) 193 self.x11.XSetErrorHandler(self._error_handler_callback) 194 self.disp = self.x11.XOpenDisplay(None) 195 if not self.disp: 196 raise ISkypeAPIError('Could not open XDisplay') 197 self.win_root = self.x11.XDefaultRootWindow(self.disp) 198 self.win_self = self.x11.XCreateSimpleWindow(self.disp, self.win_root, 199 100, 100, 100, 100, 1, 0, 0) 200 self.x11.XSelectInput(self.disp, self.win_root, _PropertyChangeMask) 201 self.win_skype = self.get_skype() 202 ctrl = 'SKYPECONTROLAPI_MESSAGE' 203 self.atom_msg = self.x11.XInternAtom(self.disp, ctrl, False) 204 self.atom_msg_begin = self.x11.XInternAtom(self.disp, ctrl + '_BEGIN', False) 205 206 self.loop_event = threading.Event() 207 self.loop_timeout = 0.0001 208 self.loop_break = False
209
210 - def __del__(self):
211 if hasattr(self, 'x11'): 212 if hasattr(self, 'disp'): 213 if hasattr(self, 'win_self'): 214 self.x11.XDestroyWindow(self.disp, self.win_self) 215 self.x11.XCloseDisplay(self.disp)
216
217 - def run(self):
218 self.DebugPrint('thread started') 219 # main loop 220 event = _XEvent() 221 data = '' 222 while not self.loop_break: 223 pending = self.x11.XPending(self.disp) 224 if not pending: 225 self.loop_event.wait(self.loop_timeout) 226 if self.loop_event.isSet(): 227 self.loop_timeout = 0.0001 228 elif self.loop_timeout < 1.0: 229 self.loop_timeout *= 2 230 self.loop_event.clear() 231 continue 232 self.loop_timeout = 0.0001 233 for i in xrange(pending): 234 self.x11.XLockDisplay(self.disp) 235 self.x11.XNextEvent(self.disp, byref(event)) 236 self.x11.XUnlockDisplay(self.disp) 237 if event.type == _ClientMessage: 238 if event.xclient.format == 8: 239 if event.xclient.message_type == self.atom_msg_begin: 240 data = str(event.xclient.data) 241 elif event.xclient.message_type == self.atom_msg: 242 if data != '': 243 data += str(event.xclient.data) 244 else: 245 print 'Warning! Middle of message received with no beginning!' 246 else: 247 continue 248 if len(event.xclient.data) != 20 and data: 249 self.notify(data.decode('utf-8')) 250 data = '' 251 elif event.type == _PropertyNotify: 252 if self.x11.XGetAtomName(self.disp, event.xproperty.atom) == '_SKYPE_INSTANCE': 253 if event.xproperty.state == _PropertyNewValue: 254 self.win_skype = self.get_skype() 255 # changing attachment status can cause an event handler to be fired, in 256 # turn it could try to call Attach() and doing this immediately seems to 257 # confuse Skype (command '#0 NAME xxx' returns '#0 CONNSTATUS OFFLINE' :D); 258 # to fix this, we give Skype some time to initialize itself 259 time.sleep(1.0) 260 self.SetAttachmentStatus(apiAttachAvailable) 261 elif event.xproperty.state == _PropertyDelete: 262 self.win_skype = None 263 self.SetAttachmentStatus(apiAttachNotAvailable) 264 self.DebugPrint('thread finished')
265
266 - def _error_handler(self, disp, error):
267 # called from within Xlib when error occures 268 self.error = error.contents.error_code 269 self.DebugPrint('Xlib error', self.error) 270 # stop all pending commands 271 for command in self.Commands.values(): 272 if hasattr(command, '_event'): 273 command._event.set() 274 return 0
275
276 - def error_check(self):
277 '''Checks last Xlib error and raises an exception if needed.''' 278 if self.error != None: 279 if self.error == 3: # BadWindow 280 self.win_skype = None 281 self.SetAttachmentStatus(apiAttachNotAvailable) 282 buf = create_string_buffer(256) 283 self.x11.XGetErrorText(self.disp, self.error, buf, 256) 284 error = ISkypeAPIError('X11 error: %s' % buf.value) 285 self.error = None 286 raise error
287
288 - def get_skype(self):
289 '''Returns Skype window ID or None if Skype not running.''' 290 skype_inst = self.x11.XInternAtom(self.disp, '_SKYPE_INSTANCE', True) 291 type_ret = Atom() 292 format_ret = c_int() 293 nitems_ret = c_ulong() 294 bytes_after_ret = c_ulong() 295 winp = pointer(Window()) 296 fail = self.x11.XGetWindowProperty(self.disp, self.win_root, skype_inst, 297 0, 1, False, 33, byref(type_ret), byref(format_ret), 298 byref(nitems_ret), byref(bytes_after_ret), byref(winp)) 299 if not fail and self.error == None and format_ret.value == 32 and nitems_ret.value == 1: 300 return winp.contents.value
301
302 - def Close(self):
303 self.loop_break = True 304 self.loop_event.set() 305 while self.isAlive(): 306 time.sleep(0.01) 307 self.DebugPrint('closed')
308
309 - def SetFriendlyName(self, FriendlyName):
310 self.FriendlyName = FriendlyName 311 if self.AttachmentStatus == apiAttachSuccess: 312 # reattach with the new name 313 self.SetAttachmentStatus(apiAttachUnknown) 314 self.Attach()
315
316 - def __Attach_ftimeout(self):
317 self.wait = False
318
319 - def Attach(self, Timeout=30000, Wait=True):
320 if self.AttachmentStatus == apiAttachSuccess: 321 return 322 if not self.isAlive(): 323 try: 324 self.start() 325 except AssertionError: 326 raise ISkypeAPIError('Skype API closed') 327 try: 328 self.wait = True 329 t = threading.Timer(Timeout / 1000.0, self.__Attach_ftimeout) 330 if Wait: 331 t.start() 332 while self.wait: 333 self.win_skype = self.get_skype() 334 if self.win_skype != None: 335 break 336 else: 337 time.sleep(1.0) 338 else: 339 raise ISkypeAPIError('Skype attach timeout') 340 finally: 341 t.cancel() 342 c = ICommand(-1, 'NAME %s' % self.FriendlyName, '', True, Timeout) 343 self.SendCommand(c, True) 344 if c.Reply != 'OK': 345 self.win_skype = None 346 self.SetAttachmentStatus(apiAttachRefused) 347 return 348 self.SendCommand(ICommand(-1, 'PROTOCOL %s' % self.Protocol), True) 349 self.SetAttachmentStatus(apiAttachSuccess)
350
351 - def IsRunning(self):
352 return self.get_skype() != None
353
354 - def Start(self, Minimized=False, Nosplash=False):
355 # options are not supported as of Skype 1.4 Beta for Linux 356 if not self.IsRunning(): 357 import os 358 if os.fork() == 0: # we're the child 359 os.setsid() 360 os.execlp('skype')
361
362 - def Shutdown(self):
363 import os 364 from signal import SIGINT 365 fh = os.popen('ps -o %p --no-heading -C skype') 366 pid = fh.readline().strip() 367 fh.close() 368 if pid: 369 os.kill(int(pid), SIGINT) 370 # Skype sometimes doesn't delete the '_SKYPE_INSTANCE' property 371 skype_inst = self.x11.XInternAtom(self.disp, '_SKYPE_INSTANCE', True) 372 self.x11.XDeleteProperty(self.disp, self.win_root, skype_inst) 373 self.win_skype = None 374 self.SetAttachmentStatus(apiAttachNotAvailable)
375
376 - def SendCommand(self, Command, Force=False):
377 if self.AttachmentStatus != apiAttachSuccess and not Force: 378 self.Attach(Command.Timeout) 379 self.CommandsStackPush(Command) 380 self.CallHandler('send', Command) 381 com = u'#%d %s' % (Command.Id, Command.Command) 382 self.DebugPrint('->', repr(com)) 383 if Command.Blocking: 384 Command._event = bevent = threading.Event() 385 else: 386 Command._timer = timer = threading.Timer(Command.Timeout / 1000.0, self.CommandsStackPop, (Command.Id,)) 387 event = _XEvent() 388 event.xclient.type = _ClientMessage 389 event.xclient.display = self.disp 390 event.xclient.window = self.win_self 391 event.xclient.message_type = self.atom_msg_begin 392 event.xclient.format = 8 393 com = unicode(com).encode('utf-8') + '\x00' 394 for i in xrange(0, len(com), 20): 395 event.xclient.data = com[i:i+20] 396 if self.x11.XSendEvent(self.disp, self.win_skype, False, 0, byref(event)) == 0: 397 self.error_check() 398 event.xclient.message_type = self.atom_msg 399 self.loop_event.set() 400 self.error_check() 401 if Command.Blocking: 402 bevent.wait(Command.Timeout / 1000.0) 403 self.error_check() 404 if not bevent.isSet(): 405 raise ISkypeAPIError('Skype command timeout') 406 else: 407 timer.start()
408
409 - def notify(self, com):
410 self.DebugPrint('<-', repr(com)) 411 # Called by main loop for all received Skype commands. 412 if com.startswith(u'#'): 413 p = com.find(u' ') 414 Command = self.CommandsStackPop(int(com[1:p])) 415 if Command: 416 Command.Reply = com[p + 1:] 417 if Command.Blocking: 418 Command._event.set() 419 del Command._event 420 else: 421 Command._timer.cancel() 422 del Command._timer 423 self.CallHandler('rece', Command) 424 else: 425 self.CallHandler('rece_api', com[p + 1:]) 426 else: 427 self.CallHandler('rece_api', com)
428