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

Source Code for Module Skype4Py.api.darwin

  1  """ 
  2  Low level *Skype for Mac OS X* interface implemented using *Carbon 
  3  distributed notifications*. Uses direct *Carbon*/*CoreFoundation* 
  4  calls through the *ctypes* module. 
  5   
  6  This module handles the options that you can pass to 
  7  `Skype.__init__` for *Mac OS X* machines. 
  8   
  9  - ``RunMainLoop`` (bool) - If set to False, Skype4Py won't start the Carbon event 
 10    loop. Otherwise it is started in a separate thread. The loop must be running for 
 11    Skype4Py events to work properly. Set this option to False if you plan to run the 
 12    loop yourself or if, for example, your GUI framework does it for you. 
 13   
 14  Thanks to **Eion Robb** for reversing *Skype for Mac* API protocol. 
 15  """ 
 16  __docformat__ = 'restructuredtext en' 
 17   
 18   
 19  import sys 
 20  from ctypes import * 
 21  from ctypes.util import find_library 
 22  import threading 
 23  import time 
 24  import logging 
 25   
 26  from Skype4Py.api import Command, SkypeAPIBase, \ 
 27                           timeout2float, finalize_opts 
 28  from Skype4Py.errors import SkypeAPIError 
 29  from Skype4Py.enums import * 
 30   
 31   
 32  __all__ = ['SkypeAPI'] 
33 34 35 -class CFType(object):
36 """Fundamental type for all CoreFoundation types. 37 38 :see: http://developer.apple.com/documentation/CoreFoundation/Reference/CFTypeRef/ 39 """ 40
41 - def __init__(self, init):
42 self.owner = True 43 if isinstance(init, CFType): 44 # copy the handle and increase the use count 45 self.handle = init.get_handle() 46 coref.CFRetain(self) 47 elif isinstance(init, c_void_p): 48 self.handle = init 49 else: 50 raise TypeError('illegal init type: %s' % type(init))
51 52 @classmethod
53 - def from_handle(cls, handle):
54 if isinstance(handle, (int, long)): 55 handle = c_void_p(handle) 56 elif not isinstance(handle, c_void_p): 57 raise TypeError('illegal handle type: %s' % type(handle)) 58 obj = cls(handle) 59 obj.owner = False 60 return obj
61
62 - def __del__(self):
63 if not coref: 64 return 65 if self.owner: 66 coref.CFRelease(self)
67
68 - def __repr__(self):
69 return '%s(handle=%s)' % (self.__class__.__name__, repr(self.handle))
70
71 - def retain(self):
72 if not self.owner: 73 coref.CFRetain(self) 74 self.owner = True
75
76 - def get_retain_count(self):
77 return coref.CFGetRetainCount(self)
78
79 - def get_handle(self):
80 return self.handle
81 82 # allows passing CF types as ctypes function parameters 83 _as_parameter_ = property(get_handle)
84
85 86 -class CFString(CFType):
87 """CoreFoundation string type. 88 89 Supports Python unicode type only. String is immutable. 90 91 :see: http://developer.apple.com/documentation/CoreFoundation/Reference/CFStringRef/ 92 """ 93
94 - def __init__(self, init=u''):
95 if isinstance(init, (str, unicode)): 96 s = unicode(init).encode('utf-8') 97 init = c_void_p(coref.CFStringCreateWithBytes(None, 98 s, len(s), 0x08000100, False)) 99 CFType.__init__(self, init)
100
101 - def __str__(self):
102 i = coref.CFStringGetLength(self) 103 size = c_long() 104 if coref.CFStringGetBytes(self, 0, i, 0x08000100, 0, False, None, 0, byref(size)) > 0: 105 buf = create_string_buffer(size.value) 106 coref.CFStringGetBytes(self, 0, i, 0x08000100, 0, False, buf, size, None) 107 return buf.value 108 else: 109 raise UnicodeError('CFStringGetBytes() failed')
110
111 - def __unicode__(self):
112 return self.__str__().decode('utf-8')
113
114 - def __len__(self):
115 return coref.CFStringGetLength(self)
116
117 - def __repr__(self):
118 return 'CFString(%s)' % repr(unicode(self))
119
120 121 -class CFNumber(CFType):
122 """CoreFoundation number type. 123 124 Supports Python int type only. Number is immutable. 125 126 :see: http://developer.apple.com/documentation/CoreFoundation/Reference/CFNumberRef/ 127 """ 128
129 - def __init__(self, init=0):
130 if isinstance(init, (int, long)): 131 init = c_void_p(coref.CFNumberCreate(None, 3, byref(c_int(int(init))))) 132 CFType.__init__(self, init)
133
134 - def __int__(self):
135 n = c_int() 136 if coref.CFNumberGetValue(self, 3, byref(n)): 137 return n.value 138 return 0
139
140 - def __repr__(self):
141 return 'CFNumber(%s)' % repr(int(self))
142
143 144 -class CFDictionary(CFType):
145 """CoreFoundation immutable dictionary type. 146 147 :see: http://developer.apple.com/documentation/CoreFoundation/Reference/CFDictionaryRef/ 148 """ 149
150 - def __init__(self, init={}):
151 if isinstance(init, dict): 152 d = dict(init) 153 keys = (c_void_p * len(d))() 154 values = (c_void_p * len(d))() 155 for i, (k, v) in enumerate(d.items()): 156 keys[i] = k.get_handle() 157 values[i] = v.get_handle() 158 init = c_void_p(coref.CFDictionaryCreate(None, keys, values, len(d), 159 coref.kCFTypeDictionaryKeyCallBacks, coref.kCFTypeDictionaryValueCallBacks)) 160 CFType.__init__(self, init)
161
162 - def get_dict(self):
163 n = len(self) 164 keys = (c_void_p * n)() 165 values = (c_void_p * n)() 166 coref.CFDictionaryGetKeysAndValues(self, keys, values) 167 d = dict() 168 for i in xrange(n): 169 d[CFType.from_handle(keys[i])] = CFType.from_handle(values[i]) 170 return d
171
172 - def __getitem__(self, key):
173 return CFType.from_handle(coref.CFDictionaryGetValue(self, key))
174
175 - def __len__(self):
176 return coref.CFDictionaryGetCount(self)
177
178 179 -class CFDistributedNotificationCenter(CFType):
180 """CoreFoundation distributed notification center type. 181 182 :see: http://developer.apple.com/documentation/CoreFoundation/Reference/CFNotificationCenterRef/ 183 """ 184 185 CFNOTIFICATIONCALLBACK = CFUNCTYPE(None, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p) 186
187 - def __init__(self):
188 CFType.__init__(self, c_void_p(coref.CFNotificationCenterGetDistributedCenter())) 189 # there is only one distributed notification center per application 190 self.owner = False 191 self.callbacks = {} 192 self._c_callback = self.CFNOTIFICATIONCALLBACK(self._callback)
193
194 - def _callback(self, center, observer, name, obj, userInfo):
195 observer = CFString.from_handle(observer) 196 name = CFString.from_handle(name) 197 if obj: 198 obj = CFString.from_handle(obj) 199 userInfo = CFDictionary.from_handle(userInfo) 200 callback = self.callbacks[(unicode(observer), unicode(name))] 201 callback(self, observer, name, obj, userInfo)
202
203 - def add_observer(self, observer, callback, name=None, obj=None, 204 drop=False, coalesce=False, hold=False, immediate=False):
205 if not callable(callback): 206 raise TypeError('callback must be callable') 207 observer = CFString(observer) 208 self.callbacks[(unicode(observer), unicode(name))] = callback 209 if name is not None: 210 name = CFString(name) 211 if obj is not None: 212 obj = CFString(obj) 213 if drop: 214 behaviour = 1 215 elif coalesce: 216 behaviour = 2 217 elif hold: 218 behaviour = 3 219 elif immediate: 220 behaviour = 4 221 else: 222 behaviour = 0 223 coref.CFNotificationCenterAddObserver(self, observer, 224 self._c_callback, name, obj, behaviour)
225
226 - def remove_observer(self, observer, name=None, obj=None):
227 observer = CFString(observer) 228 if name is not None: 229 name = CFString(name) 230 if obj is not None: 231 obj = CFString(obj) 232 coref.CFNotificationCenterRemoveObserver(self, observer, name, obj) 233 try: 234 del self.callbacks[(unicode(observer), unicode(name))] 235 except KeyError: 236 pass
237
238 - def post_notification(self, name, obj=None, userInfo=None, immediate=False):
239 name = CFString(name) 240 if obj is not None: 241 obj = CFString(obj) 242 if userInfo is not None: 243 userInfo = CFDictionary(userInfo) 244 coref.CFNotificationCenterPostNotification(self, name, obj, userInfo, immediate)
245
246 247 -class EventLoop(object):
248 """Carbon event loop object for the current thread. 249 250 The Carbon reference documentation seems to be gone from developer.apple.com, the following 251 link points to a mirror I found. I don't know how long until this one is gone too. 252 253 :see: http://www.monen.nl/DevDoc/documentation/Carbon/Reference/Carbon_Event_Manager_Ref/index.html 254 """ 255
256 - def __init__(self):
257 self.handle = c_void_p(carbon.GetCurrentEventLoop())
258 259 @staticmethod
260 - def run(timeout=-1):
261 # Timeout is expressed in seconds (float), -1 means forever. 262 # Returns True if aborted (eventLoopQuitErr). 263 return (carbon.RunCurrentEventLoop(timeout) == -9876)
264
265 - def stop(self):
266 carbon.QuitEventLoop(self.handle)
267 268 269 # load the Carbon and CoreFoundation frameworks 270 # (only if not building the docs) 271 if not getattr(sys, 'skype4py_setup', False): 272 273 path = find_library('Carbon') 274 if path is None: 275 raise ImportError('Could not find Carbon.framework') 276 carbon = cdll.LoadLibrary(path) 277 carbon.RunCurrentEventLoop.argtypes = (c_double,) 278 279 path = find_library('CoreFoundation') 280 if path is None: 281 raise ImportError('Could not find CoreFoundation.framework') 282 coref = cdll.LoadLibrary(path)
283 284 285 -class SkypeAPI(SkypeAPIBase):
286 """ 287 :note: Code based on Pidgin Skype Plugin source 288 (http://code.google.com/p/skype4pidgin/). 289 Permission to use granted by the author. 290 """ 291
292 - def __init__(self, opts):
293 self.logger = logging.getLogger('Skype4Py.api.darwin.SkypeAPI') 294 SkypeAPIBase.__init__(self) 295 self.run_main_loop = opts.pop('RunMainLoop', True) 296 finalize_opts(opts) 297 self.center = CFDistributedNotificationCenter() 298 self.is_available = False 299 self.client_id = -1
300
301 - def run(self):
302 self.logger.info('thread started') 303 if self.run_main_loop: 304 self.loop = EventLoop() 305 EventLoop.run() 306 self.logger.info('thread finished')
307
308 - def close(self):
309 if hasattr(self, 'loop'): 310 self.loop.stop() 311 self.client_id = -1 312 SkypeAPIBase.close(self)
313
314 - def set_friendly_name(self, friendly_name):
315 SkypeAPIBase.set_friendly_name(self, friendly_name) 316 if self.attachment_status == apiAttachSuccess: 317 # reattach with the new name 318 self.set_attachment_status(apiAttachUnknown) 319 self.attach()
320
321 - def attach(self, timeout, wait=True):
322 if self.attachment_status in (apiAttachPendingAuthorization, apiAttachSuccess): 323 return 324 self.acquire() 325 try: 326 try: 327 self.start() 328 except AssertionError: 329 pass 330 t = threading.Timer(timeout2float(timeout), lambda: setattr(self, 'wait', False)) 331 try: 332 self.init_observer() 333 self.client_id = -1 334 self.set_attachment_status(apiAttachPendingAuthorization) 335 self.post('SKSkypeAPIAttachRequest') 336 self.wait = True 337 if wait: 338 t.start() 339 while self.wait and self.attachment_status == apiAttachPendingAuthorization: 340 if self.run_main_loop: 341 time.sleep(1.0) 342 else: 343 EventLoop.run(1.0) 344 finally: 345 t.cancel() 346 if not self.wait: 347 self.set_attachment_status(apiAttachUnknown) 348 raise SkypeAPIError('Skype attach timeout') 349 finally: 350 self.release() 351 command = Command('PROTOCOL %s' % self.protocol, Blocking=True) 352 self.send_command(command) 353 self.protocol = int(command.Reply.rsplit(None, 1)[-1])
354
355 - def is_running(self):
356 try: 357 self.start() 358 except AssertionError: 359 pass 360 self.init_observer() 361 self.is_available = False 362 self.post('SKSkypeAPIAvailabilityRequest') 363 time.sleep(1.0) 364 return self.is_available
365
366 - def startup(self, minimized, nosplash):
367 if not self.is_running(): 368 from subprocess import Popen 369 nul = file('/dev/null') 370 Popen(['/Applications/Skype.app/Contents/MacOS/Skype'], stdin=nul, stdout=nul, stderr=nul)
371
372 - def send_command(self, command):
373 if not self.attachment_status == apiAttachSuccess: 374 self.attach(command.Timeout) 375 self.push_command(command) 376 self.notifier.sending_command(command) 377 cmd = u'#%d %s' % (command.Id, command.Command) 378 if command.Blocking: 379 if self.run_main_loop: 380 command._event = event = threading.Event() 381 else: 382 command._loop = EventLoop() 383 else: 384 command._timer = timer = threading.Timer(command.timeout2float(), self.pop_command, (command.Id,)) 385 386 self.logger.debug('sending %s', repr(cmd)) 387 userInfo = CFDictionary({CFString('SKYPE_API_COMMAND'): CFString(cmd), 388 CFString('SKYPE_API_CLIENT_ID'): CFNumber(self.client_id)}) 389 self.post('SKSkypeAPICommand', userInfo) 390 391 if command.Blocking: 392 if self.run_main_loop: 393 event.wait(command.timeout2float()) 394 if not event.isSet(): 395 raise SkypeAPIError('Skype command timeout') 396 else: 397 if not EventLoop.run(command.timeout2float()): 398 raise SkypeAPIError('Skype command timeout') 399 else: 400 timer.start()
401
402 - def init_observer(self):
403 if self.has_observer(): 404 self.delete_observer() 405 self.observer = CFString(self.friendly_name) 406 self.center.add_observer(self.observer, self.SKSkypeAPINotification, 'SKSkypeAPINotification', immediate=True) 407 self.center.add_observer(self.observer, self.SKSkypeWillQuit, 'SKSkypeWillQuit', immediate=True) 408 self.center.add_observer(self.observer, self.SKSkypeBecameAvailable, 'SKSkypeBecameAvailable', immediate=True) 409 self.center.add_observer(self.observer, self.SKAvailabilityUpdate, 'SKAvailabilityUpdate', immediate=True) 410 self.center.add_observer(self.observer, self.SKSkypeAttachResponse, 'SKSkypeAttachResponse', immediate=True)
411
412 - def delete_observer(self):
413 if not self.has_observer(): 414 return 415 self.center.remove_observer(self.observer, 'SKSkypeAPINotification') 416 self.center.remove_observer(self.observer, 'SKSkypeWillQuit') 417 self.center.remove_observer(self.observer, 'SKSkypeBecameAvailable') 418 self.center.remove_observer(self.observer, 'SKAvailabilityUpdate') 419 self.center.remove_observer(self.observer, 'SKSkypeAttachResponse') 420 del self.observer
421
422 - def has_observer(self):
423 return hasattr(self, 'observer')
424
425 - def post(self, name, userInfo=None):
426 if not self.has_observer(): 427 self.init_observer() 428 self.center.post_notification(name, self.observer, userInfo, immediate=True)
429
430 - def SKSkypeAPINotification(self, center, observer, name, obj, userInfo):
431 client_id = int(CFNumber(userInfo[CFString('SKYPE_API_CLIENT_ID')])) 432 if client_id != 999 and (client_id == 0 or client_id != self.client_id): 433 return 434 cmd = unicode(CFString(userInfo[CFString('SKYPE_API_NOTIFICATION_STRING')])) 435 self.logger.debug('received %s', repr(cmd)) 436 437 if cmd.startswith(u'#'): 438 p = cmd.find(u' ') 439 command = self.pop_command(int(cmd[1:p])) 440 if command is not None: 441 command.Reply = cmd[p + 1:] 442 if command.Blocking: 443 if self.run_main_loop: 444 command._event.set() 445 else: 446 command._loop.stop() 447 else: 448 command._timer.cancel() 449 self.notifier.reply_received(command) 450 else: 451 self.notifier.notification_received(cmd[p + 1:]) 452 else: 453 self.notifier.notification_received(cmd)
454
455 - def SKSkypeWillQuit(self, center, observer, name, obj, userInfo):
456 self.logger.debug('received SKSkypeWillQuit') 457 self.set_attachment_status(apiAttachNotAvailable)
458
459 - def SKSkypeBecameAvailable(self, center, observer, name, obj, userInfo):
460 self.logger.debug('received SKSkypeBecameAvailable') 461 self.set_attachment_status(apiAttachAvailable)
462
463 - def SKAvailabilityUpdate(self, center, observer, name, obj, userInfo):
464 self.logger.debug('received SKAvailabilityUpdate') 465 self.is_available = not not int(CFNumber(userInfo[CFString('SKYPE_API_AVAILABILITY')]))
466
467 - def SKSkypeAttachResponse(self, center, observer, name, obj, userInfo):
468 self.logger.debug('received SKSkypeAttachResponse') 469 # It seems that this notification is not called if the access is refused. Therefore we can't 470 # distinguish between attach timeout and access refuse. 471 if unicode(CFString(userInfo[CFString('SKYPE_API_CLIENT_NAME')])) == self.friendly_name: 472 response = int(CFNumber(userInfo[CFString('SKYPE_API_ATTACH_RESPONSE')])) 473 if response and self.client_id == -1: 474 self.client_id = response 475 self.set_attachment_status(apiAttachSuccess)
476