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

Source Code for Module Skype4Py.api.windows

  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      # Proceed only if our setup.py is not running. 
 33      if not getattr(sys, 'skype4py_setup', False): 
 34          raise 
 35      # This will allow importing of this module on non-Windows machines. It won't work 
 36      # of course but this will allow building documentation on any platform. 
 37      WNDPROC = c_void_p 
 38   
 39   
40 -class WNDCLASS(Structure):
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
63 -class COPYDATASTRUCT(Structure):
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
77 -class SkypeAPI(SkypeAPIBase):
78 - def __init__(self, opts):
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
90 - def run(self):
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
106 - def close(self):
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
114 - def set_friendly_name(self, friendly_name):
115 SkypeAPIBase.set_friendly_name(self, friendly_name) 116 if self.skype: 117 self.send_command(Command('NAME %s' % friendly_name))
118
119 - def get_foreground_window(self):
120 fhwnd = windll.user32.GetForegroundWindow() 121 if fhwnd: 122 # awahlig (7.05.2008): 123 # I've found at least one app (RocketDock) that had window style 8 set. 124 # This is odd since windows header files do not contain such a style. 125 # Doing message exchange while this window is a foreground one, causes 126 # lockups if some operations on client UI are involved (for example 127 # sending a 'FOCUS' command). Therefore, we will set our window as 128 # the foreground one for the transmission time. 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 # wait till the thread initializes 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 # wait (with timeout) till the WindProc() attaches 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 # disable the timeout 163 t.cancel() 164 elif self.attachment_status == apiAttachAvailable: 165 # rebroadcast 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 # check if we got the Skype window's hwnd 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
186 - def is_running(self):
187 # TZap is for Skype 4.0, tSk for 3.8 series 188 return bool(windll.user32.FindWindowA('TZapMainForm.UnicodeClass', None) or \ 189 windll.user32.FindWindowA('tSkMainForm.UnicodeClass', None))
190
191 - def get_skype_path(self):
192 key = c_long() 193 # try to find Skype in HKEY_CURRENT_USER registry tree 194 if windll.advapi32.RegOpenKeyA(0x80000001, 'Software\\Skype\\Phone', byref(key)) != 0: 195 # try to find Skype in HKEY_LOCAL_MACHINE registry tree 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
223 - def shutdown(self):
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
235 - def create_window(self):
236 # window class has to be saved as property to keep reference to self.WinProc 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
255 - def destroy_window(self):
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
266 - def window_proc(self, hwnd, umsg, wparam, lparam):
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
309 - def send_command(self, command):
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 # SendMessage failed 337 self.pop_command(command.Id) 338 self.skype = None 339 # let the loop go back and try to reattach but only once 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
346 - def allow_focus(self, timeout):
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