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
24 '''Represents the Carbon.framework.
25 '''
26
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
37
40
41
43 '''Represents an EventLoop from Carbon.framework.
44
45 http://developer.apple.com/documentation/Carbon/Reference/Carbon_Event_Manager_Ref/
46 '''
47
49 self.carb = carb
50 self.handle = handle
51
53 self.carb.lib.QuitEventLoop(self.handle)
54
55
57 '''Represents the CoreFoundation.framework.
58 '''
59
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
68 return CFType(self, handle=handle)
69
72
75
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
86
89
92
95
98
99
101 '''Fundamental type for all CoreFoundation types.
102
103 http://developer.apple.com/documentation/CoreFoundation/Reference/CFTypeRef/
104 '''
105
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
121 if not self.owner:
122 self.cf.lib.CFRetain(self)
123 self.owner = True
124
127
130
132 if self.owner:
133 self.cf.lib.CFRelease(self)
134 self.handle = None
135 self.cf = None
136
138 return '%s(handle=%s)' % (self.__class__.__name__, repr(self.handle))
139
140
141 _as_parameter_ = property(get_handle)
142
143
145 '''CoreFoundation string type.
146
147 Supports Python unicode type only.
148
149 http://developer.apple.com/documentation/CoreFoundation/Reference/CFStringRef/
150 '''
151
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
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
171 return self.__str__().decode('utf8')
172
174 return self.cf.lib.CFStringGetLength(self)
175
177 return 'CFString(%s)' % repr(unicode(self))
178
179
181 '''CoreFoundation number type.
182
183 Supports Python int type only.
184
185 http://developer.apple.com/documentation/CoreFoundation/Reference/CFNumberRef/
186 '''
187
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
196 n = c_int()
197 if self.cf.lib.CFNumberGetValue(self, 3, byref(n)):
198 return n.value
199 return 0
200
202 return 'CFNumber(%s)' % repr(int(self))
203
204
206 '''CoreFoundation immutable dictionary type.
207
208 http://developer.apple.com/documentation/CoreFoundation/Reference/CFDictionaryRef/
209 '''
210
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
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
241 return self.cf.CFType(c_void_p(self.cf.lib.CFDictionaryGetValue(self, key)))
242
244 return self.cf.lib.CFDictionaryGetCount(self)
245
246
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
256 CFType.__init__(self, *args, **kwargs)
257 self.handle = c_void_p(self.cf.lib.CFNotificationCenterGetDistributedCenter())
258
259 self.owner = False
260 self.callbacks = {}
261 self._callback = self.CFNotificationCallback(self._notification_callback)
262
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
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
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
334
340
342 if hasattr(self, 'loop'):
343 self.loop.quit()
344 self.client_id = -1
345 self.DebugPrint('closed')
346
353
356
357 - def Attach(self, Timeout=30000, Wait=True):
381
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
421
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
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
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
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
477
479 self.DebugPrint('<-', 'SKSkypeBecameAvailable')
480 self.SetAttachmentStatus(apiAttachAvailable)
481
483 self.DebugPrint('<-', 'SKAvailabilityUpdate')
484 self.is_available = bool(int(CFNumber(userInfo[self.coref.CFSTR('SKYPE_API_AVAILABILITY')])))
485
487 self.DebugPrint('<-', 'SKSkypeAttachResponse')
488
489
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