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 
  3  using Carbon distributed notifications. Uses direct 
  4  Carbon/CoreFoundation calls through ctypes module. 
  5   
  6  This module handles the options that you can pass to 
  7  L{ISkype.__init__<skype.ISkype.__init__>} for Mac OS X 
  8  machines. 
  9   
 10  No further options are currently supported. 
 11   
 12  Thanks to Eion Robb for reversing Skype for Mac API protocol. 
 13  ''' 
 14   
 15  from Skype4Py.API import ICommand, _ISkypeAPIBase 
 16  from ctypes import * 
 17  from ctypes.util import find_library 
 18  from Skype4Py.errors import ISkypeAPIError 
 19  from Skype4Py.enums import * 
 20  import threading, time 
 21   
 22   
23 -class Carbon(object):
24 '''Represents the Carbon.framework. 25 ''' 26
27 - def __init__(self):
28 path = find_library('Carbon') 29 if path == None: 30 raise ImportError('Could not find Carbon.framework') 31 self.lib = cdll.LoadLibrary(path) 32 self.lib.RunCurrentEventLoop.argtypes = (c_double,)
33
34 - def RunCurrentEventLoop(self, timeout=-1):
35 # timeout=-1 means forever 36 return self.lib.RunCurrentEventLoop(timeout)
37
38 - def GetCurrentEventLoop(self):
39 return EventLoop(self, c_void_p(self.lib.GetCurrentEventLoop()))
40 41
42 -class EventLoop(object):
43 '''Represents an EventLoop from Carbon.framework. 44 45 http://developer.apple.com/documentation/Carbon/Reference/Carbon_Event_Manager_Ref/ 46 ''' 47
48 - def __init__(self, carb, handle):
49 self.carb = carb 50 self.handle = handle
51
52 - def quit(self):
53 self.carb.lib.QuitEventLoop(self.handle)
54 55
56 -class CoreFoundation(object):
57 '''Represents the CoreFoundation.framework. 58 ''' 59
60 - def __init__(self):
61 path = find_library('CoreFoundation') 62 if path == None: 63 raise ImportError('Could not find CoreFoundation.framework') 64 self.lib = cdll.LoadLibrary(path) 65 self.cfstrs = []
66
67 - def CFType(self, handle=0):
68 return CFType(self, handle=handle)
69
70 - def CFStringFromHandle(self, handle=0):
71 return CFString(self, handle=handle)
72
73 - def CFString(self, s=u''):
74 return CFString(self, string=s)
75
76 - def CFSTR(self, s):
77 for cfs in self.cfstrs: 78 if unicode(cfs) == s: 79 return cfs 80 cfs = self.CFString(s) 81 self.cfstrs.append(cfs) 82 return cfs
83
84 - def CFNumberFromHandle(self, handle=0):
85 return CFNumber(self, handle=handle)
86
87 - def CFNumber(self, n=0):
88 return CFNumber(self, number=n)
89
90 - def CFDictionaryFromHandle(self, handle=0):
91 return CFDictionary(self, handle=handle)
92
93 - def CFDictionary(self, d={}):
94 return CFDictionary(self, dictionary=d)
95
98 99
100 -class CFType(object):
101 '''Fundamental type for all CoreFoundation types. 102 103 http://developer.apple.com/documentation/CoreFoundation/Reference/CFTypeRef/ 104 ''' 105
106 - def __init__(self, cf, **kwargs):
107 if isinstance(cf, CFType): 108 self.cf = cf.cf 109 self.handle = cf.get_handle() 110 if len(kwargs): 111 raise TypeError('unexpected additional arguments') 112 else: 113 self.cf = cf 114 self.handle = None 115 self.owner = False 116 self.__dict__.update(kwargs) 117 if self.handle != None and not isinstance(self.handle, c_void_p): 118 self.handle = c_void_p(self.handle)
119
120 - def retain(self):
121 if not self.owner: 122 self.cf.lib.CFRetain(self) 123 self.owner = True
124
125 - def is_owner(self):
126 return self.owner
127
128 - def get_handle(self):
129 return self.handle
130
131 - def __del__(self):
132 if self.owner: 133 self.cf.lib.CFRelease(self) 134 self.handle = None 135 self.cf = None
136
137 - def __repr__(self):
138 return '%s(handle=%s)' % (self.__class__.__name__, repr(self.handle))
139 140 # allows passing CF types as ctypes function parameters 141 _as_parameter_ = property(get_handle)
142 143
144 -class CFString(CFType):
145 '''CoreFoundation string type. 146 147 Supports Python unicode type only. 148 149 http://developer.apple.com/documentation/CoreFoundation/Reference/CFStringRef/ 150 ''' 151
152 - def __init__(self, *args, **kwargs):
153 CFType.__init__(self, *args, **kwargs) 154 if 'string' in kwargs: 155 s = unicode(kwargs['string']).encode('utf8') 156 self.handle = c_void_p(self.cf.lib.CFStringCreateWithBytes(None, 157 s, len(s), 0x08000100, False)) 158 self.owner = True
159
160 - def __str__(self):
161 i = self.cf.lib.CFStringGetLength(self) 162 size = c_long() 163 if self.cf.lib.CFStringGetBytes(self, 0, i, 0x08000100, 0, False, None, 0, byref(size)) > 0: 164 buf = create_string_buffer(size.value) 165 self.cf.lib.CFStringGetBytes(self, 0, i, 0x08000100, 0, False, buf, size, None) 166 return buf.value 167 else: 168 raise UnicodeError('CFStringGetBytes() failed')
169
170 - def __unicode__(self):
171 return self.__str__().decode('utf8')
172
173 - def __len__(self):
174 return self.cf.lib.CFStringGetLength(self)
175
176 - def __repr__(self):
177 return 'CFString(%s)' % repr(unicode(self))
178 179
180 -class CFNumber(CFType):
181 '''CoreFoundation number type. 182 183 Supports Python int type only. 184 185 http://developer.apple.com/documentation/CoreFoundation/Reference/CFNumberRef/ 186 ''' 187
188 - def __init__(self, *args, **kwargs):
189 CFType.__init__(self, *args, **kwargs) 190 if 'number' in kwargs: 191 n = int(kwargs['number']) 192 self.handle = c_void_p(self.cf.lib.CFNumberCreate(None, 3, byref(c_int(n)))) 193 self.owner = True
194
195 - def __int__(self):
196 n = c_int() 197 if self.cf.lib.CFNumberGetValue(self, 3, byref(n)): 198 return n.value 199 return 0
200
201 - def __repr__(self):
202 return 'CFNumber(%s)' % repr(int(self))
203 204
205 -class CFDictionary(CFType):
206 '''CoreFoundation immutable dictionary type. 207 208 http://developer.apple.com/documentation/CoreFoundation/Reference/CFDictionaryRef/ 209 ''' 210
211 - def __init__(self, *args, **kwargs):
212 CFType.__init__(self, *args, **kwargs) 213 if 'dictionary' in kwargs: 214 d = dict(kwargs['dictionary']) 215 keys = (c_void_p * len(d))() 216 values = (c_void_p * len(d))() 217 i = 0 218 for k, v in d.items(): 219 if not isinstance(k, CFType): 220 raise TypeError('CFDictionary: key is not a CFType') 221 if not isinstance(v, CFType): 222 raise TypeError('CFDictionary: value is not a CFType') 223 keys[i] = k.get_handle() 224 values[i] = v.get_handle() 225 i += 1 226 self.handle = c_void_p(self.cf.lib.CFDictionaryCreate(None, keys, values, len(d), 227 self.cf.lib.kCFTypeDictionaryKeyCallBacks, self.cf.lib.kCFTypeDictionaryValueCallBacks)) 228 self.owner = True
229
230 - def get_dict(self):
231 n = len(self) 232 keys = (c_void_p * n)() 233 values = (c_void_p * n)() 234 self.cf.lib.CFDictionaryGetKeysAndValues(self, keys, values) 235 d = dict() 236 for i in xrange(n): 237 d[self.cf.CFType(keys[i])] = self.cf.CFType(values[i]) 238 return d
239
240 - def __getitem__(self, key):
241 return self.cf.CFType(c_void_p(self.cf.lib.CFDictionaryGetValue(self, key)))
242
243 - def __len__(self):
244 return self.cf.lib.CFDictionaryGetCount(self)
245 246
247 -class CFDistributedNotificationCenter(CFType):
248 '''CoreFoundation distributed notification center type. 249 250 http://developer.apple.com/documentation/CoreFoundation/Reference/CFNotificationCenterRef/ 251 ''' 252 253 CFNotificationCallback = CFUNCTYPE(None, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p) 254
255 - def __init__(self, *args, **kwargs):
256 CFType.__init__(self, *args, **kwargs) 257 self.handle = c_void_p(self.cf.lib.CFNotificationCenterGetDistributedCenter()) 258 # there is only one distributed notification center per application 259 self.owner = False 260 self.callbacks = {} 261 self._callback = self.CFNotificationCallback(self._notification_callback)
262
263 - def _notification_callback(self, center, observer, name, obj, userInfo):
264 observer = self.cf.CFStringFromHandle(observer) 265 name = self.cf.CFStringFromHandle(name) 266 if obj: 267 obj = self.cf.CFStringFromHandle(obj) 268 callback = self.callbacks[(unicode(observer), unicode(name))] 269 callback(self, observer, name, obj, 270 self.cf.CFDictionaryFromHandle(userInfo))
271
272 - def add_observer(self, observer, callback, name=None, obj=None, 273 drop=False, coalesce=False, hold=False, immediate=False):
274 if not isinstance(observer, CFString): 275 observer = self.cf.CFString(observer) 276 if not callable(callback): 277 raise TypeError('callback must be callable') 278 self.callbacks[(unicode(observer), unicode(name))] = callback 279 if name != None and not isinstance(name, CFString): 280 name = self.cf.CFSTR(name) 281 if obj != None and not isinstance(obj, CFString): 282 obj = self.cf.CFSTR(obj) 283 if drop: 284 behaviour = 1 285 elif coalesce: 286 behaviour = 2 287 elif hold: 288 behaviour = 3 289 elif immediate: 290 behaviour = 4 291 else: 292 behaviour = 0 293 self.cf.lib.CFNotificationCenterAddObserver(self, observer, self._callback, name, obj, behaviour)
294
295 - def remove_observer(self, observer, name=None, obj=None):
296 if not isinstance(observer, CFString): 297 observer = self.cf.CFString(observer) 298 if name != None and not isinstance(name, CFString): 299 name = self.cf.CFSTR(name) 300 if obj != None and not isinstance(obj, CFString): 301 obj = self.cf.CFSTR(obj) 302 self.cf.lib.CFNotificationCenterRemoveObserver(self, observer, name, obj) 303 try: 304 del self.callbacks[(unicode(observer), unicode(name))] 305 except KeyError: 306 pass
307
308 - def post_notification(self, name, obj=None, userInfo=None, immediate=False):
309 if not isinstance(name, CFString): 310 name = self.cf.CFSTR(name) 311 if obj != None and not isinstance(obj, CFString): 312 obj = self.cf.CFSTR(obj) 313 if userInfo != None and not isinstance(userInfo, CFDictionary): 314 userInfo = self.cf.CFDictionary(userInfo) 315 self.cf.lib.CFNotificationCenterPostNotification(self, name, obj, userInfo, immediate)
316 317
318 -class _ISkypeAPI(_ISkypeAPIBase):
319 '''Skype for Mac API wrapper. 320 321 Code based on Pidgin Skype Plugin source. 322 http://code.google.com/p/skype4pidgin/ 323 Permission was granted by the author. 324 ''' 325
326 - def __init__(self, handler, opts):
327 _ISkypeAPIBase.__init__(self, opts) 328 self.RegisterHandler(handler) 329 self.carbon = Carbon() 330 self.coref = CoreFoundation() 331 self.center = self.coref.CFDistributedNotificationCenter() 332 self.is_available = False 333 self.client_id = -1
334
335 - def run(self):
336 self.DebugPrint('thread started') 337 self.loop = self.carbon.GetCurrentEventLoop() 338 self.carbon.RunCurrentEventLoop() 339 self.DebugPrint('thread finished')
340
341 - def Close(self):
342 if hasattr(self, 'loop'): 343 self.loop.quit() 344 self.client_id = -1 345 self.DebugPrint('closed')
346
347 - def SetFriendlyName(self, FriendlyName):
348 self.FriendlyName = FriendlyName 349 if self.AttachmentStatus == apiAttachSuccess: 350 # reattach with the new name 351 self.SetAttachmentStatus(apiAttachUnknown) 352 self.Attach()
353
354 - def __Attach_ftimeout(self):
355 self.wait = False
356
357 - def Attach(self, Timeout=30000, Wait=True):
358 if self.AttachmentStatus in (apiAttachPendingAuthorization, apiAttachSuccess): 359 return 360 try: 361 self.start() 362 except AssertionError: 363 pass 364 t = threading.Timer(Timeout / 1000.0, self.__Attach_ftimeout) 365 try: 366 self.init_observer() 367 self.client_id = -1 368 self.SetAttachmentStatus(apiAttachPendingAuthorization) 369 self.post('SKSkypeAPIAttachRequest') 370 self.wait = True 371 if Wait: 372 t.start() 373 while self.wait and self.AttachmentStatus == apiAttachPendingAuthorization: 374 time.sleep(1.0) 375 finally: 376 t.cancel() 377 if not self.wait: 378 self.SetAttachmentStatus(apiAttachUnknown) 379 raise ISkypeAPIError('Skype attach timeout') 380 self.SendCommand(ICommand(-1, 'PROTOCOL %s' % self.Protocol))
381
382 - def IsRunning(self):
383 try: 384 self.start() 385 except AssertionError: 386 pass 387 self.init_observer() 388 self.is_available = False 389 self.post('SKSkypeAPIAvailabilityRequest') 390 time.sleep(1.0) 391 return self.is_available
392
393 - def Start(self, Minimized=False, Nosplash=False):
394 if not self.IsRunning(): 395 from subprocess import Popen 396 nul = file('/dev/null') 397 Popen(['/Applications/Skype.app/Contents/MacOS/Skype'], stdin=nul, stdout=nul, stderr=nul)
398
399 - def SendCommand(self, Command):
400 if not self.AttachmentStatus == apiAttachSuccess: 401 self.Attach(Command.Timeout) 402 self.CommandsStackPush(Command) 403 self.CallHandler('send', Command) 404 com = u'#%d %s' % (Command.Id, Command.Command) 405 if Command.Blocking: 406 Command._event = event = threading.Event() 407 else: 408 Command._timer = timer = threading.Timer(Command.Timeout / 1000.0, self.CommandsStackPop, (Command.Id,)) 409 410 self.DebugPrint('->', repr(com)) 411 userInfo = self.coref.CFDictionary({self.coref.CFSTR('SKYPE_API_COMMAND'): self.coref.CFString(com), 412 self.coref.CFSTR('SKYPE_API_CLIENT_ID'): self.coref.CFNumber(self.client_id)}) 413 self.post('SKSkypeAPICommand', userInfo) 414 415 if Command.Blocking: 416 event.wait(Command.Timeout / 1000.0) 417 if not event.isSet(): 418 raise ISkypeAPIError('Skype command timeout') 419 else: 420 timer.start()
421
422 - def init_observer(self):
423 if self.has_observer(): 424 self.delete_observer() 425 self.observer = self.coref.CFString(self.FriendlyName) 426 self.center.add_observer(self.observer, self.SKSkypeAPINotification, 'SKSkypeAPINotification', immediate=True) 427 self.center.add_observer(self.observer, self.SKSkypeWillQuit, 'SKSkypeWillQuit', immediate=True) 428 self.center.add_observer(self.observer, self.SKSkypeBecameAvailable, 'SKSkypeBecameAvailable', immediate=True) 429 self.center.add_observer(self.observer, self.SKAvailabilityUpdate, 'SKAvailabilityUpdate', immediate=True) 430 self.center.add_observer(self.observer, self.SKSkypeAttachResponse, 'SKSkypeAttachResponse', immediate=True)
431
432 - def delete_observer(self):
433 if not self.has_observer(): 434 return 435 self.center.remove_observer(self.observer, 'SKSkypeAPINotification') 436 self.center.remove_observer(self.observer, 'SKSkypeWillQuit') 437 self.center.remove_observer(self.observer, 'SKSkypeBecameAvailable') 438 self.center.remove_observer(self.observer, 'SKAvailabilityUpdate') 439 self.center.remove_observer(self.observer, 'SKSkypeAttachResponse') 440 del self.observer
441
442 - def has_observer(self):
443 return hasattr(self, 'observer')
444
445 - def post(self, name, userInfo=None):
446 if not self.has_observer(): 447 self.init_observer() 448 self.center.post_notification(name, self.observer, userInfo, immediate=True)
449
450 - def SKSkypeAPINotification(self, center, observer, name, obj, userInfo):
451 client_id = int(CFNumber(userInfo[self.coref.CFSTR('SKYPE_API_CLIENT_ID')])) 452 if client_id != 999 and (client_id == 0 or client_id != self.client_id): 453 return 454 com = unicode(CFString(userInfo[self.coref.CFSTR('SKYPE_API_NOTIFICATION_STRING')])) 455 self.DebugPrint('<-', repr(com)) 456 457 if com.startswith(u'#'): 458 p = com.find(u' ') 459 Command = self.CommandsStackPop(int(com[1:p])) 460 if Command: 461 Command.Reply = com[p + 1:] 462 if Command.Blocking: 463 Command._event.set() 464 del Command._event 465 else: 466 Command._timer.cancel() 467 del Command._timer 468 self.CallHandler('rece', Command) 469 else: 470 self.CallHandler('rece_api', com[p + 1:]) 471 else: 472 self.CallHandler('rece_api', com)
473
474 - def SKSkypeWillQuit(self, center, observer, name, obj, userInfo):
475 self.DebugPrint('<-', 'SKSkypeWillQuit') 476 self.SetAttachmentStatus(apiAttachNotAvailable)
477
478 - def SKSkypeBecameAvailable(self, center, observer, name, obj, userInfo):
479 self.DebugPrint('<-', 'SKSkypeBecameAvailable') 480 self.SetAttachmentStatus(apiAttachAvailable)
481
482 - def SKAvailabilityUpdate(self, center, observer, name, obj, userInfo):
483 self.DebugPrint('<-', 'SKAvailabilityUpdate') 484 self.is_available = bool(int(CFNumber(userInfo[self.coref.CFSTR('SKYPE_API_AVAILABILITY')])))
485
486 - def SKSkypeAttachResponse(self, center, observer, name, obj, userInfo):
487 self.DebugPrint('<-', 'SKSkypeAttachResponse') 488 # It seems that this notification is not called if the access is refused. Therefore we can't 489 # distinguish between attach timeout and access refuse. 490 if unicode(CFString(userInfo[self.coref.CFSTR('SKYPE_API_CLIENT_NAME')])) == self.FriendlyName: 491 response = int(CFNumber(userInfo[self.coref.CFSTR('SKYPE_API_ATTACH_RESPONSE')])) 492 if response and self.client_id == -1: 493 self.client_id = response 494 self.SetAttachmentStatus(apiAttachSuccess)
495