import Xlib import Xlib.display from zope import interface from interfaces import desktopEvent from threading import Thread from interfaces.message import IMessage, Debug from zope import component @interface.implementer(desktopEvent.IDesktop) class Listener(Thread): def __init__(self, mapping, queue): Thread.__init__(self) self.queue = queue self.mapping = mapping self.active_window = None self.last_code = None self.running = False def getForegroundWindowTitle(self): """ Return the name of the selected window """ return self.active_window def run(self): self.disp = Xlib.display.Display() root = self.disp.screen().root root.change_attributes(event_mask=Xlib.X.PropertyChangeMask) self.NET_WM_NAME = self.disp.intern_atom('_NET_WM_NAME') self.WM_CLASS = self.disp.intern_atom('WM_CLASS') self.NET_ACTIVE_WINDOW = self.disp.intern_atom('_NET_ACTIVE_WINDOW') component.handle(Debug("Waiting for xlib event")) self.running = True while self.running: try: self.handle_event(root, self.disp.next_event()) except Exception as e: component.handle(Debug( str(e) )) print(e) def handle_event(self, root, event): try: window_id_property = root.get_full_property(self.NET_ACTIVE_WINDOW, Xlib.X.AnyPropertyType) if window_id_property is None: return window_id = window_id_property.value[0] window = self.disp.create_resource_object('window', window_id) window_prop = window.get_full_property(self.NET_WM_NAME, 0) if window_prop is None: return window_name = str(window_prop.value).lower() class_name = str(window.get_full_property(self.WM_CLASS, 0).value).lower() except Xlib.error.XError: return if window_name is None or window_name == self.active_window: return self.active_window = window_name found = False # Create a reveverse dictionnary for getting the users added first # In python, the elements in a dictionnary are sorted by insertion # order, so we get the user added first here for pattern in reversed(self.mapping.keys()): # Ignore the default layout if pattern == "default": continue if not (pattern.lstrip() in window_name or pattern.lstrip() in class_name): continue code = self.mapping[pattern] # We found something. At this point, even if we do not update # the layer (because it is the same as the previous) we do not # switch back to the default layer. found = True if code != self.last_code: component.handle(Debug("Switching to '%s' for '%s'" % (pattern, window_name))) self.queue.put ( (code, None) ) self.last_code = code # We found a matching configuration. Even if the match is the # same as the current one, we break the loop in order to # prevent another layer to update. break if not found and self.last_code != "default": default = self.mapping.get("default", None) if default is None: return self.queue.put ( (default, None) ) self.last_code = "default" def stop(self): self.running = False