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
22 _PropertyChangeMask = 0x400000
23 _PropertyNotify = 28
24 _ClientMessage = 33
25 _PropertyNewValue = 0
26 _PropertyDelete = 1
27
28
29
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
43 _align = (sizeof(c_long) == 8 and sizeof(c_int) == 4)
44
45
46
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
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
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
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
130 XErrorHandlerP = CFUNCTYPE(c_int, DisplayP, POINTER(_XErrorEvent))
131
132
135 _ISkypeAPIBase.__init__(self, opts)
136 self.RegisterHandler(handler)
137
138
139 if opts:
140 raise TypeError('Unexpected parameter(s): %s' % ', '.join(opts.keys()))
141
142
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
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
189 self.x11.XInitThreads()
190 self.error = None
191
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
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
218 self.DebugPrint('thread started')
219
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
256
257
258
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
267
268 self.error = error.contents.error_code
269 self.DebugPrint('Xlib error', self.error)
270
271 for command in self.Commands.values():
272 if hasattr(command, '_event'):
273 command._event.set()
274 return 0
275
277 '''Checks last Xlib error and raises an exception if needed.'''
278 if self.error != None:
279 if self.error == 3:
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
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
303 self.loop_break = True
304 self.loop_event.set()
305 while self.isAlive():
306 time.sleep(0.01)
307 self.DebugPrint('closed')
308
315
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
352 return self.get_skype() != None
353
354 - def Start(self, Minimized=False, Nosplash=False):
355
356 if not self.IsRunning():
357 import os
358 if os.fork() == 0:
359 os.setsid()
360 os.execlp('skype')
361
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
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
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
410 self.DebugPrint('<-', repr(com))
411
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