This post comes from the old blog.
A few weeks ago, I have to write a program in PyGTK that was supposed to be all the time in the background. This application needs to run over Microsoft Windows, and hide in the notification area, wich in Windows is near to the clock.
One of challenges for me in this application is that as it must run in the background there must be a way to raise it, the most easy way to do it is by force the user to click on the small icon in the notification area, but in this case, that was impossible because the computer don’t have any mouse, everything is done with the keyboard.
So, I start googling for some way to use hot keys with GTK (PyGtk) in this case, I found that Gtk does have a way to represent the Display wich a way to get to your computer display resources. I just want to grab the keyboard presses and if the right key with the right modifier where pressed then the application arises.
Unfortunely for me, Gtk+ does have a way to do this, but the implementation in PyGTK has a bug where all the display events are ignored, I mean, every time you check of a pending event for the display pygtk will return None, so, no event, no catch, no working code.
Well, in linux you can do this by using the xlib bindings for python, but I have to do this on Windows, so a workmate point me to This url. Wich shows how to register a hot key on windows.
That was easier than I thought!. But, there is a trick… As you can see in this piece of code:
try: msg = wintypes.MSG () while user32.GetMessageA (byref (msg), None, 0, 0) != 0: if msg.message == win32con.WM_HOTKEY: action_to_take = HOTKEY_ACTIONS.get (msg.wParam) if action_to_take: action_to_take () user32.TranslateMessage (byref (msg)) user32.DispatchMessageA (byref (msg))
The method GetMessageA is the one that is listening to the messages in the system, and returns when there is a message. If you use a single threaded program, like… some text bassed application or just a “show something” application, then it will be nice, but, if you use Gtk, where the application’s events are handled in the gtk main loop this is useless, why? because this method locks until it receives something, and then let your application work, but, if you have it in a while loop you will never leave that loop and then, your signals will never been emited and your handler will never do their job. Is something similar to the use of sockets. Ha… I forgot, you can’t use the while gtk.events_pending(): gtk.main_iteration_do() stuff.. it doesn’t work, keep reading if you wanna know why.
So, what can I do for this to work?, well, I thought: A thread, but, there is a problem. Windows documentation says that the hotkey registerd with the method RegisterHotKey will only be catched with GetMessageA if the “register” method and the “get message” are in the same thread.
So, what can I do? Put this on the same thread, and then use a flag to let the rest of your application know that the keys have been pressed, then, raise your application. But, why I can’t make a gobject based class that emits a signal when this keys are pressed? Well, you should know that Gtk+ is thread aware, but not thread safe, this means, you cannot use Gtk accross the threads, and this is something inherited from gobject (AFAIK).
Here’s the code that will let you register hot keys on windows and make your applications raise when you need them.
#!/usr/bin/env python import ctypes import thread import win32con import gobject from ctypes import wintypes class keyhandler: def __init__(self): self.__emit = False thread.start_new_thread(self.__catchMsgs, tuple()) gobject.timeout_add(5,self.checkEmit) def checkEmit(self): if self.__emit: self.emit('keyhandler-keypress',self,self.__emit) print 'Emitiendo senial..' self.__emit = False return True def __catchMsgs(self, *args): byref = ctypes.byref user32 = ctypes.windll.user32 self.HOTKEYS[1] = {'hotkey' : win32con.VK_F3,'modifiers': win32con.MOD_WIN} if not user32.RegisterHotKey (None, 1, win32con.MOD_WIN, win32con.VK_F3): print "Unable to register id", 1 while 1: try: msg = wintypes.MSG() if user32.GetMessageA (byref (msg), None, 0, 0): if msg.message == win32con.WM_HOTKEY: if self.HOTKEYS.has_key(msg.wParam): self.__emit = self.HOTKEYS[msg.wParam] user32.TranslateMessage (byref (msg)) user32.DispatchMessageA (byref (msg)) except: pass