1 """
2 Low level *Skype for Windows* interface implemented using *Windows messaging*.
3 Uses direct *WinAPI* calls through *ctypes* module.
4
5 This module handles the options that you can pass to `Skype.__init__`
6 for Windows machines.
7
8 No options are currently supported.
9 """
10 __docformat__ = 'restructuredtext en'
11
12
13 import sys
14 import threading
15 import time
16 from ctypes import *
17 import logging
18
19 from Skype4Py.api import Command, SkypeAPIBase, \
20 timeout2float, finalize_opts, \
21 DEFAULT_TIMEOUT
22 from Skype4Py.enums import *
23 from Skype4Py.errors import SkypeAPIError
24
25
26 __all__ = ['SkypeAPI']
27
28
29 try:
30 WNDPROC = WINFUNCTYPE(c_long, c_int, c_uint, c_int, c_int)
31 except NameError:
32
33 if not getattr(sys, 'skype4py_setup', False):
34 raise
35
36
37 WNDPROC = c_void_p
38
39
41 _fields_ = [('style', c_uint),
42 ('lpfnWndProc', WNDPROC),
43 ('cbClsExtra', c_int),
44 ('cbWndExtra', c_int),
45 ('hInstance', c_int),
46 ('hIcon', c_int),
47 ('hCursor', c_int),
48 ('hbrBackground', c_int),
49 ('lpszMenuName', c_char_p),
50 ('lpszClassName', c_char_p)]
51
52
53 -class MSG(Structure):
54 _fields_ = [('hwnd', c_int),
55 ('message', c_uint),
56 ('wParam', c_int),
57 ('lParam', c_int),
58 ('time', c_int),
59 ('pointX', c_long),
60 ('pointY', c_long)]
61
62
64 _fields_ = [('dwData', POINTER(c_uint)),
65 ('cbData', c_uint),
66 ('lpData', c_char_p)]
67
68
69 PCOPYDATASTRUCT = POINTER(COPYDATASTRUCT)
70
71 WM_QUIT = 0x12
72 WM_COPYDATA = 0x4A
73
74 HWND_BROADCAST = 0xFFFF
75
76
79 self.logger = logging.getLogger('Skype4Py.api.windows.SkypeAPI')
80 SkypeAPIBase.__init__(self)
81 finalize_opts(opts)
82 self.window_class = None
83 self.hwnd = None
84 self.skype = None
85 self.wait = False
86 self.SkypeControlAPIDiscover = windll.user32.RegisterWindowMessageA('SkypeControlAPIDiscover')
87 self.SkypeControlAPIAttach = windll.user32.RegisterWindowMessageA('SkypeControlAPIAttach')
88 windll.user32.GetWindowLongA.restype = c_ulong
89
91 self.logger.info('thread started')
92 if not self.create_window():
93 self.hwnd = None
94 return
95
96 msg = MSG()
97 pmsg = pointer(msg)
98 while self.hwnd and windll.user32.GetMessageA(pmsg, self.hwnd, 0, 0):
99 windll.user32.TranslateMessage(pmsg)
100 windll.user32.DispatchMessageA(pmsg)
101
102 self.destroy_window()
103 self.hwnd = None
104 self.logger.info('thread finished')
105
107 if self.hwnd:
108 windll.user32.PostMessageA(self.hwnd, WM_QUIT, 0, 0)
109 while self.hwnd:
110 time.sleep(0.01)
111 self.skype = None
112 SkypeAPIBase.close(self)
113
118
120 fhwnd = windll.user32.GetForegroundWindow()
121 if fhwnd:
122
123
124
125
126
127
128
129 if windll.user32.GetWindowLongA(fhwnd, -16) & 8 == 0:
130 fhwnd = None
131 return fhwnd
132
133 - def attach(self, timeout, wait=True):
134 if self.skype is not None and windll.user32.IsWindow(self.skype):
135 return
136 self.acquire()
137 self.skype = None
138 try:
139 if not self.isAlive():
140 try:
141 self.start()
142 except AssertionError:
143 raise SkypeAPIError('Skype API closed')
144
145 while not self.hwnd:
146 time.sleep(0.01)
147 self.logger.debug('broadcasting SkypeControlAPIDiscover')
148 fhwnd = self.get_foreground_window()
149 try:
150 if fhwnd:
151 windll.user32.SetForegroundWindow(self.hwnd)
152 if not windll.user32.SendMessageTimeoutA(HWND_BROADCAST, self.SkypeControlAPIDiscover,
153 self.hwnd, None, 2, 5000, None):
154 raise SkypeAPIError('Could not broadcast Skype discover message')
155
156 self.wait = True
157 t = threading.Timer(timeout2float(timeout), lambda: setattr(self, 'wait', False))
158 if wait:
159 t.start()
160 while self.wait and self.attachment_status not in (apiAttachSuccess, apiAttachRefused):
161 if self.attachment_status == apiAttachPendingAuthorization:
162
163 t.cancel()
164 elif self.attachment_status == apiAttachAvailable:
165
166 self.logger.debug('broadcasting SkypeControlAPIDiscover')
167 windll.user32.SetForegroundWindow(self.hwnd)
168 if not windll.user32.SendMessageTimeoutA(HWND_BROADCAST, self.SkypeControlAPIDiscover,
169 self.hwnd, None, 2, 5000, None):
170 raise SkypeAPIError('Could not broadcast Skype discover message')
171 time.sleep(0.01)
172 t.cancel()
173 finally:
174 if fhwnd:
175 windll.user32.SetForegroundWindow(fhwnd)
176 finally:
177 self.release()
178
179 if self.skype is not None:
180 command = Command('PROTOCOL %s' % self.protocol, Blocking=True)
181 self.send_command(command)
182 self.protocol = int(command.Reply.rsplit(None, 1)[-1])
183 elif not self.wait:
184 raise SkypeAPIError('Skype attach timeout')
185
187
188 return bool(windll.user32.FindWindowA('TZapMainForm.UnicodeClass', None) or \
189 windll.user32.FindWindowA('tSkMainForm.UnicodeClass', None))
190
192 key = c_long()
193
194 if windll.advapi32.RegOpenKeyA(0x80000001, 'Software\\Skype\\Phone', byref(key)) != 0:
195
196 if windll.advapi32.RegOpenKeyA(0x80000002, 'Software\\Skype\\Phone', byref(key)) != 0:
197 raise SkypeAPIError('Skype not installed')
198 pathlen = c_long(512)
199 path = create_string_buffer(pathlen.value)
200 if windll.advapi32.RegQueryValueExA(key, 'SkypePath', None, None, path, byref(pathlen)) != 0:
201 windll.advapi32.RegCloseKey(key)
202 raise SkypeAPIError('Cannot find Skype path')
203 windll.advapi32.RegCloseKey(key)
204 return path.value
205
206 - def startup(self, minimized, nosplash):
207 args = []
208 if minimized:
209 args.append('/MINIMIZED')
210 if nosplash:
211 args.append('/NOSPLASH')
212 try:
213 if self.hwnd:
214 fhwnd = self.get_foreground_window()
215 if fhwnd:
216 windll.user32.SetForegroundWindow(self.hwnd)
217 if windll.shell32.ShellExecuteA(None, 'open', self.get_skype_path(), ' '.join(args), None, 0) <= 32:
218 raise SkypeAPIError('Could not start Skype')
219 finally:
220 if self.hwnd and fhwnd:
221 windll.user32.SetForegroundWindow(fhwnd)
222
224 try:
225 if self.hwnd:
226 fhwnd = self.get_foreground_window()
227 if fhwnd:
228 windll.user32.SetForegroundWindow(self.hwnd)
229 if windll.shell32.ShellExecuteA(None, 'open', self.get_skype_path(), '/SHUTDOWN', None, 0) <= 32:
230 raise SkypeAPIError('Could not shutdown Skype')
231 finally:
232 if self.hwnd and fhwnd:
233 windll.user32.SetForegroundWindow(fhwnd)
234
236
237 self.window_class = WNDCLASS(3, WNDPROC(self.window_proc), 0, 0,
238 windll.kernel32.GetModuleHandleA(None),
239 0, 0, 0, None, 'Skype4Py.%d' % id(self))
240
241 wclass = windll.user32.RegisterClassA(byref(self.window_class))
242 if wclass == 0:
243 return False
244
245 self.hwnd = windll.user32.CreateWindowExA(0, 'Skype4Py.%d' % id(self), 'Skype4Py',
246 0xCF0000, 0x80000000, 0x80000000,
247 0x80000000, 0x80000000, None, None,
248 self.window_class.hInstance, 0)
249 if self.hwnd == 0:
250 windll.user32.UnregisterClassA('Skype4Py.%d' % id(self), None)
251 return False
252
253 return True
254
256 if not windll.user32.DestroyWindow(self.hwnd):
257 return False
258 self.hwnd = None
259
260 if not windll.user32.UnregisterClassA('Skype4Py.%d' % id(self), None):
261 return False
262 self.window_class = None
263
264 return True
265
267 if umsg == self.SkypeControlAPIAttach:
268 self.logger.debug('received SkypeControlAPIAttach %s', lparam)
269 if lparam == apiAttachSuccess:
270 if self.skype is None or self.skype == wparam:
271 self.skype = wparam
272 else:
273 self.logger.warning('second successful attach received for different API window')
274 elif lparam in (apiAttachRefused, apiAttachNotAvailable, apiAttachAvailable):
275 self.skype = None
276 elif lparam == apiAttachPendingAuthorization:
277 if self.attachment_status == apiAttachSuccess:
278 self.logger.warning('received pending attach after successful attach')
279 return 0
280 self.set_attachment_status(lparam)
281 return 1
282 elif umsg == WM_COPYDATA and wparam == self.skype and lparam:
283 copydata = cast(lparam, PCOPYDATASTRUCT).contents
284 cmd8 = copydata.lpData[:copydata.cbData - 1]
285 cmd = cmd8.decode('utf-8')
286 self.logger.debug('received %s', repr(cmd))
287 if cmd.startswith(u'#'):
288 p = cmd.find(u' ')
289 command = self.pop_command(int(cmd[1:p]))
290 if command is not None:
291 command.Reply = cmd[p + 1:]
292 if command.Blocking:
293 command._event.set()
294 else:
295 command._timer.cancel()
296 self.notifier.reply_received(command)
297 else:
298 self.notifier.notification_received(cmd[p + 1:])
299 else:
300 self.notifier.notification_received(cmd)
301 return 1
302 elif umsg == apiAttachAvailable:
303 self.logger.debug('received apiAttachAvailable')
304 self.skype = None
305 self.set_attachment_status(umsg)
306 return 1
307 return windll.user32.DefWindowProcA(c_int(hwnd), c_int(umsg), c_int(wparam), c_int(lparam))
308
310 for retry in xrange(2):
311 if self.skype is None:
312 self.attach(command.Timeout)
313 self.push_command(command)
314 self.notifier.sending_command(command)
315 cmd = u'#%d %s' % (command.Id, command.Command)
316 cmd8 = cmd.encode('utf-8') + '\0'
317 copydata = COPYDATASTRUCT(None, len(cmd8), cmd8)
318 if command.Blocking:
319 command._event = event = threading.Event()
320 else:
321 command._timer = timer = threading.Timer(command.timeout2float(), self.pop_command, (command.Id,))
322 self.logger.debug('sending %s', repr(cmd))
323 fhwnd = self.get_foreground_window()
324 try:
325 if fhwnd:
326 windll.user32.SetForegroundWindow(self.hwnd)
327 if windll.user32.SendMessageA(self.skype, WM_COPYDATA, self.hwnd, byref(copydata)):
328 if command.Blocking:
329 event.wait(command.timeout2float())
330 if not event.isSet():
331 raise SkypeAPIError('Skype command timeout')
332 else:
333 timer.start()
334 break
335 else:
336
337 self.pop_command(command.Id)
338 self.skype = None
339
340 finally:
341 if fhwnd:
342 windll.user32.SetForegroundWindow(fhwnd)
343 else:
344 raise SkypeAPIError('Skype API error, check if Skype wasn\'t closed')
345
347 if self.skype is None:
348 self.attach(timeout)
349 process_id = c_ulong()
350 windll.user32.GetWindowThreadProcessId(self.skype, byref(process_id))
351 if process_id:
352 windll.user32.AllowSetForegroundWindow(process_id)
353