def get_primary_selection(): """Get the word selected in the primary selection.""" display = Display() xsel_data_atom = display.intern_atom("XSEL_DATA") UTF8_STRING = display.intern_atom("UTF8_STRING") screen = display.screen() w = screen.root.create_window(0, 0, 2, 2, 0, screen.root_depth) w.convert_selection( Xlib.Xatom.PRIMARY, # selection UTF8_STRING, # target xsel_data_atom, # property Xlib.X.CurrentTime) # time while True: e = display.next_event() if e.type == X.SelectionNotify: break if e.property != xsel_data_atom or \ e.target != UTF8_STRING: return '' reply = w.get_full_property(xsel_data_atom, X.AnyPropertyType) reply = reply.value.strip() return reply
class Window(object): """ Abstract object representing the X Window of an application obtained with the window ID. """ def __init__(self, windowID): self._display = Display() self._root = self._display.screen().root self._window = self._display.create_resource_object('window', windowID) def reserve_space(self, left=0, right=0, top=0, bottom=0): """ Reserves screen-space for toplevel window. """ LEFT = left RIGHT = right TOP = top BOTTOM = bottom self._window.change_property(self._display.intern_atom('_NET_WM_STRUT'), self._display.intern_atom('CARDINAL'), 32, [LEFT, RIGHT, TOP, BOTTOM]) self._display.sync() def set_wm_state_skip_taskbar(self): """ Change state of the window. """ self._window.set_wm_state(Display().intern_atom('_NET_WM_STATE_SKIP_TASKBAR'))
def start(self): mod = self.MOD_MASK dpy = Display() keys = {} for key, name in self.KEY_MAP.items(): keys[key] = dpy.keysym_to_keycode(XK.string_to_keysym(key)) root = dpy.screen().root for key, code in keys.items(): root.grab_key(code, mod, 1, X.GrabModeAsync, X.GrabModeAsync) root.grab_button( 1, mod, 1, X.ButtonPressMask | X.ButtonReleaseMask | X.PointerMotionMask, X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE) root.grab_button( 3, mod, 1, X.ButtonPressMask | X.ButtonReleaseMask | X.PointerMotionMask, X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE) self.dpy = dpy self.keys = keys self.root = root self.WM_PROTOCOLS = dpy.intern_atom("WM_PROTOCOLS") self.WM_DELETE_WINDOW = dpy.intern_atom("WM_DELETE_WINDOW") self._loop()
class BarWindow: def __init__(self, window_id): self._display = Display() self._window = self._display.create_resource_object('window', window_id) def reserve_space(self, left=0, right=0, top=0, bottom=0): self._window.change_property( self._display.intern_atom('_NET_WM_STRUT'), self._display.intern_atom('CARDINAL'), 32, [left, right, top, bottom]) self._display.sync()
def init(): global display display = Display() global root root = display.screen().root global NET_WM_NAME NET_WM_NAME = display.intern_atom('_NET_WM_NAME') # UTF-8 global WM_NAME WM_NAME = display.intern_atom('WM_NAME') global NET_ACTIVE_WINDOW NET_ACTIVE_WINDOW = display.intern_atom('_NET_ACTIVE_WINDOW') root.change_attributes(event_mask=Xlib.X.PropertyChangeMask)
def repositionize(title=str, x=int, y=int, width=int, height=int): HEIGHT = height WIDTH = width y=1080-y-height display = Display() root = display.screen().root windowIDs = root.get_full_property(display.intern_atom('_NET_CLIENT_LIST'),X.AnyPropertyType).value for windowID in windowIDs: window = display.create_resource_object('window', windowID) titles = window.get_wm_name() pid = window.get_full_property(display.intern_atom('_NET_WM_PID'), X.AnyPropertyType) if title in titles: window.configure(x = x, y = y, width=WIDTH, height=HEIGHT) display.sync()
def from_python_xlib(): """Get the I3 socket path using python-xlib. """ try: from Xlib.display import Display except ImportError: return None display = Display(os.environ.get('DISPLAY', ':0.0')) screen = display.screen() atom = display.intern_atom(I3_SOCK_X_ATOM, True) utf8_string = display.intern_atom('UTF8_STRING', True) response = screen.root.get_full_property(atom, utf8_string) return response.value
def run(self): self.disable_keyboard_interrupt() display = Display() while True: try: time.sleep(self._interval) window = display.get_input_focus().focus if window.get_wm_class() is None and window.get_wm_name() is None: window = window.query_tree().parent if window: pid_value = window.get_full_property(display.intern_atom('_NET_WM_PID'),0) if pid_value: try: pid = int(pid_value.value[0]) process = psutil.Process(pid) name,exe,title = process.name,process.exe,window.get_wm_name() value = {'exe':exe,'title':title.decode('latin-1'),'time':time.time()-self._last_time} self.send_event(value) self._last_time = time.time() except: pass except AttributeError: pass
def resize(title=str, height=int, width=int): TITLE = title HEIGHT = height WIDTH = width display = Display() root = display.screen().root windowIDs = root.get_full_property(display.intern_atom('_NET_CLIENT_LIST'), X.AnyPropertyType).value for windowID in windowIDs: window = display.create_resource_object('window', windowID) title = window.get_wm_name() pid = window.get_full_property(display.intern_atom('_NET_WM_PID'), X.AnyPropertyType) if TITLE in title: window.configure(width=WIDTH, height=HEIGHT) display.sync()
def run(self, window, screen): opacity = max(0.0, min(1.0, self.arg)) xdisplay = XDisplay() xwindow = xdisplay.create_resource_object("window", window.get_xid()) atom = xdisplay.intern_atom("_NET_WM_WINDOW_OPACITY") data = struct.pack("L", int(4294967295 * opacity)) xwindow.change_property(atom, Xatom.CARDINAL, 32, data) xdisplay.sync()
class Window(object): def __init__(self, window_ID): self._display = Display() self._window = self._display.create_resource_object( 'window', window_ID) def reserve_space(self, left=0, right=0, top=0, bottom=0): LEFT = int(left) RIGHT = int(right) TOP = int(top) BOTTOM = int(bottom) print([LEFT, RIGHT, TOP, BOTTOM]) self._window.change_property( self._display.intern_atom('_NET_WM_STRUT'), self._display.intern_atom('CARDINAL'), 32, [LEFT, RIGHT, TOP, BOTTOM]) self._display.sync()
def resize(title=str, height=int, width=int): TITLE = title HEIGHT = height WIDTH = width display = Display() root = display.screen().root windowIDs = root.get_full_property(display.intern_atom('_NET_CLIENT_LIST'), X.AnyPropertyType).value for windowID in windowIDs: window = display.create_resource_object('window', windowID) title = window.get_wm_name() pid = window.get_full_property(display.intern_atom('_NET_WM_PID'), X.AnyPropertyType) if TITLE in title: window.configure(width = WIDTH, height = HEIGHT) display.sync()
def main(argv): display = Display() root = display.screen().root root.change_attributes(event_mask=X.PropertyChangeMask | X.SubstructureNotifyMask) skip_windows = [ 'tk.TkN/Awindowswitcher', 'xfce4-appfinder.Xfce4-appfinderxavierApplicationFinder' ] NET_ACTIVE_WINDOW = display.intern_atom('_NET_ACTIVE_WINDOW') try: while True: event = display.next_event() if event.type != X.PropertyNotify: continue response = root.get_full_property(NET_ACTIVE_WINDOW, X.AnyPropertyType) win_id = hex(response.value[0]).rstrip('L').lstrip('0x') if len(win_id) == 0: continue wmctrl_out = os.popen( 'sleep 0.1; wmctrl -dliGux | grep -i {win_id}'.format( win_id=win_id)).read() wmctrl_out = wmctrl_out.split('\n') wmctrl_out.pop() if len(wmctrl_out) != 1: continue window = WmctrlWindow(wmctrl_out[0]) # ignore window switcher if window.name in skip_windows: continue str_json = open("/home/danilo/scripts/flip360_wids.json", "r").read() jjson = json.loads(str_json) key_json = 'm{m}{w}'.format(m=window.monitor, w=window.workspace) if jjson.get(key_json, None) == None: continue window_previous = WmctrlWindow(jjson[key_json]) if window.id == window_previous.id: continue jjson[key_json] = window.str_win flip360_wids = open("/home/danilo/scripts/flip360_wids.json", "w") flip360_wids.write(json.dumps(jjson)) flip360_wids.close() finally: display.close()
class Window(object): def __init__(self): self.display = Display() self.root = self.display.screen().root self.title = 'Pointer active' def active_window(self): window_id = self.root.get_full_property(self.display.intern_atom('_NET_ACTIVE_WINDOW'), X.AnyPropertyType).value[0] window = self.display.create_resource_object('window', window_id) return window def find_window(self, title): window_ids = self.root.get_full_property(self.display.intern_atom('_NET_CLIENT_LIST'), X.AnyPropertyType).value for window_id in window_ids: window = self.display.create_resource_object('window', window_id) if title == window.get_wm_name(): return window return None def resize(self, window, dim): window.configure(width = dim[0], height = dim[1]) self.display.sync() def move(self, window, pos): window.configure(x = pos[0], y = pos[1]) self.display.sync() def destroy(self, window): window.destroy() self.display.sync() def shape(self, window): geo = window.get_geometry() return (geo.width, geo.height) def position(self, window): p = window.query_pointer() return (p.root_x - p.win_x, p.root_y - p.win_y - 28) # The 28 seems to be the taskbar def rename(self, window): print 'renaming', window.get_wm_name() window.set_wm_icon_name('poo') window.set_wm_name('poo') self.display.sync()
def generate_message(args): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('localhost', args.port)) flag = False disp = Display() root = disp.screen().root NET_ACTIVE_WINDOW = disp.intern_atom('_NET_ACTIVE_WINDOW') if args.add == 0: response = root.get_full_property(NET_ACTIVE_WINDOW, X.AnyPropertyType) if not response: print("Couldn't find the active window", file=sys.stderr) send(s, "add " + str(response.value[0])) elif args.add is None: pass else: send(s, "add " + str(args.add)) flag = True if args.rm == 0: response = root.get_full_property(NET_ACTIVE_WINDOW, X.AnyPropertyType) if not response: print("Couldn't find the active window", file=sys.stderr) send(s, "rm " + str(response.value[0])) if args.rm is None: pass else: send(s, "rm " + str(args.rm)) flag = True if args.clear is True: send(s, "clear") flag = True if args.active is True: send(s, "active") flag = True if args.deactive is True: send(s, "deactive") flag = True if args.toggle is True: send(s, "toggle") flag = True if args.kill is True: send(s, "stop") flag = True return flag
def x11_setup(): global _x11, xdisplay, modifier_keycodes, NET_ACTIVE_WINDOW, NET_WM_PID, WM_CLASS, xtest_available if _x11 is not None: return _x11 try: from Xlib.display import Display xdisplay = Display() modifier_keycodes = xdisplay.get_modifier_mapping( ) # there should be a way to do this in Gdk NET_ACTIVE_WINDOW = xdisplay.intern_atom('_NET_ACTIVE_WINDOW') NET_WM_PID = xdisplay.intern_atom('_NET_WM_PID') WM_CLASS = xdisplay.intern_atom('WM_CLASS') _x11 = True # X11 available if _log.isEnabledFor(_INFO): _log.info('X11 library loaded and display set up') except Exception: _log.warn('X11 not available - some rule capabilities inoperable: %s', exc_info=_sys.exc_info()) _x11 = False xtest_available = False return _x11
def get_primary_selection(): """Get the word selected in the primary selection.""" display = Display() xsel_data_atom = display.intern_atom("XSEL_DATA") UTF8_STRING = display.intern_atom("UTF8_STRING") screen = display.screen() w = screen.root.create_window(0, 0, 2, 2, 0, screen.root_depth) w.convert_selection(Xlib.Xatom.PRIMARY, # selection UTF8_STRING, # target xsel_data_atom, # property Xlib.X.CurrentTime) # time while True: e = display.next_event() if e.type == X.SelectionNotify: break if e.property != xsel_data_atom or \ e.target != UTF8_STRING: return '' reply = w.get_full_property(xsel_data_atom, X.AnyPropertyType) reply = reply.value.strip() return reply
def xstuff(self): screen = self.window.get_screen() s = Gdk.Screen.get_default() disp = screen.get_display() width = s.get_width() height = s.get_height() bar_size = 40 x,y = 0,0 self.window.move(x,y) self.window.resize(width,bar_size) self.window.show_all() display = Display() topw = display.create_resource_object('window', self.window.get_toplevel().get_window().get_xid()) topw.change_property(display.intern_atom('_NET_WM_STRUT'), display.intern_atom('CARDINAL'), 32, [0, 0, bar_size, 0 ], X.PropModeReplace) topw.change_property(display.intern_atom('_NET_WM_STRUT_PARTIAL'), display.intern_atom('CARDINAL'), 32, [0, 0, bar_size, 0, 0, 0, 0, 0, x, x+width-1, 0, 0], X.PropModeReplace)
def main(argv): display = Display() thread = EventThread(display) thread.start() time.sleep(1) screen = display.screen() # The get_property call should not deadlock, despite the blocking next_event # call in the thread. atom = display.intern_atom('_XROOTPMAP_ID', True) response = screen.root.get_property(atom, Xatom.PIXMAP, 0, 1) print('get_property response: %r' % response) display.close()
def set_wm_class(self): """ Set the X11 WM_CLASS. This is used to link the window to the X11 application (menu entry from the .desktop file). Gnome-shell won't display the application icon correctly in the dash with the default value of `python3, python3`. """ display = Display() root = display.screen().root windowIDs = root.get_full_property( display.intern_atom('_NET_CLIENT_LIST'), X.AnyPropertyType).value for windowID in windowIDs: window = display.create_resource_object('window', windowID) title = window.get_wm_name() if title == self.title: window.set_wm_class("MyKivyTestApp", "python3") display.sync()
def __init__(self, notification: int, summary: str, body: str, application: str, icon_path: Optional[str] = None, timeout: Optional[int] = -1, actions: Optional[List[List]] = None, dismiss: bool = False): # Set up empty window and add style Gtk.Window.__init__(self) # No need to provide a mainloop, as we are only sending # Version information print("Gtk %d.%d.%d" % (Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version())) self.notification = notification self.body = body self.summary = summary self.application = application self.icon_path = icon_path self.timeout = timeout self.actions = actions self.dismiss = dismiss self.set_name("bar") self.set_type_hint(Gdk.WindowTypeHint.DOCK) self.set_decorated(False) self.connect("delete-event", Gtk.main_quit) # style_provider = Gtk.CssProvider() # style_provider.from_data(stylesheet) # Gtk.StyleContext.add_provider_for_screen( # Gdk.Screen.get_default(), # style_provider, # Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) # Layout container self.hbox = Gtk.Box(spacing=5) self.hbox.set_homogeneous(False) message_str = "<big>{}</big>".format(self.application) if icon_path is not None: if self._try_init_icon(): message_str = "" # Application displayed with icon self.message = Gtk.Label() if self.body is None: message_str += "<b>{}</b>".format(self.summary) else: message_str += "<b>{}</b>\n{}".format(self.summary, self.body) self.message.set_markup(message_str) self.hbox.pack_start(self.message, False, True, 0) self._init_action_buttons() button = Gtk.Button.new_with_mnemonic("_OK") button.connect("clicked", self.done) self.hbox.pack_end(button, False, False, 0) self.add(self.hbox) # the screen contains all monitors screen = self.get_screen() width = screen.width() # width = Gdk.Screen.width() print("width: %d" % width) # (c) collect data about each monitor monitors = [] nmons = screen.get_display().get_n_monitors() print("there are %d monitors" % nmons) for m in range(nmons): mg = screen.get_monitor_geometry(m) print("monitor %d: %d x %d" % (m, mg.width, mg.height)) monitors.append(mg) # current monitor curmon = screen.get_monitor_at_window(screen.get_active_window()) x = monitors[curmon].x y = monitors[curmon].y width = monitors[curmon].width height = monitors[curmon].height print("monitor %d: %d x %d (current, offset %d)" % (curmon, width, height, x)) print("bar: start=%d end=%d" % (x, x + width - 1)) # display bar along the top of the current monitor self.move(x, y) self.resize(width, bar_size) # it must be shown before changing properties self.show_all() print(f"Window shown. Size {self.get_size()}") print(f"Window shown. Size {self.get_size()}") # (d) reserve space (a "strut") for the bar so it does not become obscured # when other windows are maximized, etc # http://stackoverflow.com/questions/33719686 property_change not in gtk3.0 # https://sourceforge.net/p/python-xlib/mailman/message/27574603 display = Display() topw = display.create_resource_object( 'window', self.get_toplevel().get_window().get_xid()) # http://python-xlib.sourceforge.net/doc/html/python-xlib_21.html#SEC20 topw.change_property(display.intern_atom('_NET_WM_STRUT'), display.intern_atom('CARDINAL'), 32, [0, 0, bar_size, 0], X.PropModeReplace) topw.change_property( display.intern_atom('_NET_WM_STRUT_PARTIAL'), display.intern_atom('CARDINAL'), 32, [0, 0, bar_size, 0, 0, 0, 0, 0, x, x + width - 1, 0, 0], X.PropModeReplace) # we set _NET_WM_STRUT, the older mechanism as well as _NET_WM_STRUT_PARTIAL # but window managers ignore the former if they support the latter. # # the numbers in the array are as follows: # # 0, 0, bar_size, 0 are the number of pixels to reserve along each edge of the # screen given in the order left, right, top, bottom. Here the size of the bar # is reserved at the top of the screen and the other edges are left alone. # # _NET_WM_STRUT_PARTIAL also supplies a further four pairs, each being a # start and end position for the strut (they don't need to occupy the entire # edge). # # In the example, we set the top start to the current monitor's x co-ordinate # and the top-end to the same value plus that monitor's width, deducting one. # because the co-ordinate system starts at zero rather than 1. The net result # is that space is reserved only on the current monitor. # # co-ordinates are specified relative to the screen (i.e. all monitors together). # # main event loop # Gtk.main() # Control-C termination broken in GTK3 http://stackoverflow.com/a/33834721 # https://bugzilla.gnome.org/show_bug.cgi?id=622084 from gi.repository import GLib self.invoker = ActionInvoker() print(f"Timeout {self.timeout}") self.timer = Event() if self.timeout > 0: seconds = timeout / 1000 def sleep_then_quit(): self.timer.wait(seconds) self.quit() Thread(target=sleep_then_quit).start() print("Running main loop") Gtk.main()
class WindowManager(AbstractWindowManager): def __init__(self): super(WindowManager, self).__init__() self.disp = Display() self.root = self.disp.screen().root self._listen_for_window_property_changes() self.NET_ACTIVE_WINDOW = self.disp.intern_atom('_NET_ACTIVE_WINDOW') self._active_window = None self._update_active_window() def run(self): self.notify_all() while True: # next_event() sleeps until we get an event self._handle_xevent(self.disp.next_event()) def get_active_program_name(self): try: return self._active_window.get_wm_class()[1] except: logger.exception("_xorg.WindowManager.get_active_program_name failed") return 'FAILED' def _handle_xevent(self, event): if event.type != Xlib.X.PropertyNotify: return if event.atom == self.NET_ACTIVE_WINDOW: if self._update_active_window(): self.notify_all() def _update_active_window(self): active_win_id = self.root.get_full_property(self.NET_ACTIVE_WINDOW, Xlib.X.AnyPropertyType).value[0] if self._has_focus_changed(active_win_id): new_win = self._window_obj(active_win_id) if new_win: if self._active_window: self._active_window.change_attributes(event_mask=Xlib.X.NoEventMask) self._active_window = new_win self._active_window.change_attributes(event_mask=Xlib.X.PropertyChangeMask) return True return False def _listen_for_window_property_changes(self): self.root.change_attributes(event_mask=Xlib.X.PropertyChangeMask) def _window_obj(self, xid): window_obj = None if xid: try: window_obj = self.disp.create_resource_object('window', xid) except Xlib.error.XError: pass return window_obj def _has_focus_changed(self, active_win_xid): if self._active_window: return active_win_xid != self._active_window.id return True
#window.add(area) #area.show() label = Gtk.Label('<span size="38000" color="red">gmail.com</span>') label.set_use_markup(True) window.add(label) window.connect("delete-event", Gtk.main_quit) window.show_all() # Reserve space on the top display = Display() topw = display.create_resource_object( 'window', window.get_toplevel().get_window().get_xid()) topw.change_property(display.intern_atom('_NET_WM_STRUT'), display.intern_atom('CARDINAL'), 32, [0, 0, 100, 0], X.PropModeReplace) topw.change_property(display.intern_atom('_NET_WM_STRUCT_PARTIAL'), display.intern_atom('CARDINAL'), 32, [0, 0, 100, 0, 0, 0, 0, 0, width - 1, 0, 0], X.PropModeReplace) from threading import Thread class KeyboardReaderThread(Thread): def run(self): from evdev import InputDevice, categorize, ecodes dev = InputDevice( '/dev/input/by-id/usb-Apple__Inc_Apple_Keyboard-event-kbd')
class ActiveWindowManager(): # Based on code by Stephan Sokolow # Source: https://gist.github.com/ssokolow/e7c9aae63fb7973e4d64cff969a78ae8 # Modified by hezral to add _get_window_class_name function """python-xlib example which reacts to changing the active window/title. Requires: - Python - python-xlib Tested with Python 2.x because my Kubuntu 14.04 doesn't come with python-xlib for Python 3.x. Design: ------- Any modern window manager that isn't horrendously broken maintains an X11 property on the root window named _NET_ACTIVE_WINDOW. Any modern application toolkit presents the window title via a property named _NET_WM_NAME. This listens for changes to both of them and then hides duplicate events so it only reacts to title changes once. Known Bugs: ----------- - Under some circumstances, I observed that the first window creation and last window deletion on on an empty desktop (ie. not even a taskbar/panel) would go ignored when using this test setup: Xephyr :3 & DISPLAY=:3 openbox & DISPLAY=:3 python3 x11_watch_active_window.py # ...and then launch one or more of these in other terminals DISPLAY=:3 leafpad """ stop_thread = False id_thread = None callback = None def __init__(self, gtk_application=None): super().__init__() self.app = gtk_application # Connect to the X server and get the root window self.disp = Display() self.root = self.disp.screen().root # Prepare the property names we use so they can be fed into X11 APIs self.NET_ACTIVE_WINDOW = self.disp.intern_atom('_NET_ACTIVE_WINDOW') self.NET_WM_NAME = self.disp.intern_atom('_NET_WM_NAME') # UTF-8 self.WM_NAME = self.disp.intern_atom('WM_NAME') # Legacy encoding self.WM_CLASS = self.disp.intern_atom('WM_CLASS') self.last_seen = {'xid': None, 'title': None} # type: Dict[str, Any] def _run(self, callback): self.callback = callback self.stop_thread = False def init_manager(): # Listen for _NET_ACTIVE_WINDOW changes self.root.change_attributes(event_mask=X.PropertyChangeMask) # Prime last_seen with whatever window was active when we started this self.get_window_name(self.get_active_window()[0]) self.handle_change(self.last_seen) while True: # next_event() sleeps until we get an event self.handle_xevent(self.disp.next_event()) if self.stop_thread: print(datetime.now(), "active_window_manager stopped") break self.thread = threading.Thread(target=init_manager) self.thread.daemon = True self.thread.start() print(datetime.now(), "active_window_manager started") def _stop(self): self.stop_thread = True @contextmanager def window_obj(self, win_id: Optional[int]) -> Window: """Simplify dealing with BadWindow (make it either valid or None)""" window_obj = None if win_id: try: window_obj = self.disp.create_resource_object('window', win_id) except XError: pass yield window_obj def get_active_window(self) -> Tuple[Optional[int], bool]: """Return a (window_obj, focus_has_changed) tuple for the active window.""" response = self.root.get_full_property(self.NET_ACTIVE_WINDOW, X.AnyPropertyType) if not response: return None, False win_id = response.value[0] focus_changed = (win_id != self.last_seen['xid']) if focus_changed: with self.window_obj(self.last_seen['xid']) as old_win: if old_win: old_win.change_attributes(event_mask=X.NoEventMask) self.last_seen['xid'] = win_id with self.window_obj(win_id) as new_win: if new_win: new_win.change_attributes(event_mask=X.PropertyChangeMask) return win_id, focus_changed def _get_window_name_inner(self, win_obj: Window) -> str: """Simplify dealing with _NET_WM_NAME (UTF-8) vs. WM_NAME (legacy)""" for atom in (self.NET_WM_NAME, self.WM_NAME): try: window_name = win_obj.get_full_property(atom, 0) except UnicodeDecodeError: # Apparently a Debian distro package bug title = "<could not decode characters>" else: if window_name: win_name = window_name.value # type: Union[str, bytes] if isinstance(win_name, bytes): # Apparently COMPOUND_TEXT is so arcane that this is how # tools like xprop deal with receiving it these days win_name = win_name.decode('latin1', 'replace') return win_name else: title = "<unnamed window>" return "{} (XID: {})".format(title, win_obj.id) def _get_window_class_name(self, win_obj: Window) -> str: """SReturn window class name""" try: window_name = win_obj.get_full_property(self.WM_CLASS, 0) except UnicodeDecodeError: # Apparently a Debian distro package bug title = "<could not decode characters>" else: if window_name: win_class_name = window_name.value # type: Union[str, bytes] if isinstance(win_class_name, bytes): # Apparently COMPOUND_TEXT is so arcane that this is how # tools like xprop deal with receiving it these days win_class_name = win_class_name.replace(b'\x00',b' ').decode("utf-8").lower() return win_class_name else: title = "<undefined wm_class_name>" return "{} (XID: {})".format(title, win_obj.id) def get_window_name(self, win_id: Optional[int]) -> Tuple[Optional[str], bool]: """ Look up the window class name for a given X11 window ID retrofitted to provide window class name instead of window title """ if not win_id: self.last_seen['title'] = None return self.last_seen['title'], True title_changed = False with self.window_obj(win_id) as wobj: if wobj: try: win_title = self._get_window_class_name(wobj) except XError: pass else: title_changed = (win_title != self.last_seen['title']) self.last_seen['title'] = win_title return self.last_seen['title'], title_changed def handle_xevent(self, event: Event): """Handler for X events which ignores anything but focus/title change""" if event.type != X.PropertyNotify: return changed = False if event.atom == self.NET_ACTIVE_WINDOW: if self.get_active_window()[1]: self.get_window_name(self.last_seen['xid']) # Rely on the side-effects changed = True elif event.atom in (self.NET_WM_NAME, self.WM_NAME): changed = changed or self.get_window_name(self.last_seen['xid'])[1] if changed: self.handle_change(self.last_seen) def handle_change(self, new_state: dict): """Replace this with whatever you want to actually do""" GLib.idle_add(self.callback, new_state['title'])
def handleQuery(query): if not query.isTriggered: return [] try: items = [] # Prepare query string input = query.string needles = cgi.escape(input.lower()).strip().split(' ') # Create display handle display = Display() # Intern a few atoms we will use later NET_CLIENT_LIST = display.intern_atom('_NET_CLIENT_LIST') NET_WM_WINDOW_TYPE = display.intern_atom('_NET_WM_WINDOW_TYPE') NET_WM_WINDOW_TYPE_NORMAL = display.intern_atom( '_NET_WM_WINDOW_TYPE_NORMAL') NET_WM_NAME = display.intern_atom('_NET_WM_NAME') # Get the IDs of all windows on the display root = display.screen().root win_list = root.get_full_property(NET_CLIENT_LIST, X.AnyPropertyType).value for id in win_list: # If the user already typed something new, the query is cancelled # and we should just stop wasting resources if not query.isValid: return [] # Get the window associated with that ID win = display.create_resource_object('window', id) # Check if the window type is "normal window" win_type = win.get_full_property(NET_WM_WINDOW_TYPE, X.AnyPropertyType).value[0] if win_type == NET_WM_WINDOW_TYPE_NORMAL: # Obtain the name of the window. Windows without the `_NET_WM_NAME` # property will be ignored name = win.get_full_property(NET_WM_NAME, X.AnyPropertyType) if not name: continue name = name.value.decode('utf-8') # Apply filter to the name lowercase_name = name.lower() is_ok = True for needle in needles: if needle not in lowercase_name: is_ok = False break if not is_ok: continue for needle in needles: start = lowercase_name.find(needle) end = start + len(needle) name = name[:start] + "<b>" + name[ start:end] + "</b>" + name[end:] lowercase_name = name.lower() # Add the item def activate_win(id): call(['xdotool', 'windowactivate', str(id)]) # Generate icon icon_path = gen_icon(display, id, win) item = Item(id=__prettyname__, completion=query.rawString) item.text = name #cgi.escape(str(type(name))) item.subtext = "Switch to" item.icon = icon_path item.addAction( FuncAction("Switch to", lambda id=id: activate_win(id))) items.append(item) return items # We never know what could go wrong... except Exception as e: item = Item(id=__prettyname__, completion=query.rawString) item.text = e.__class__.__name__ item.subtext = str(e) return item
# Rule GUI keyname determination uses a local file generated # from http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h # and http://cgit.freedesktop.org/xorg/proto/x11proto/plain/XF86keysym.h # because there does not seem to be a non-X11 file for this set of key names XK_KEYS = _keysymdef.keysymdef try: import Xlib from Xlib.display import Display xdisplay = Display() modifier_keycodes = xdisplay.get_modifier_mapping( ) # there should be a way to do this in Gdk x11 = True NET_ACTIVE_WINDOW = xdisplay.intern_atom('_NET_ACTIVE_WINDOW') NET_WM_PID = xdisplay.intern_atom('_NET_WM_PID') WM_CLASS = xdisplay.intern_atom('WM_CLASS') # set up to get keyboard state using ctypes interface to libx11 import ctypes class XkbDisplay(ctypes.Structure): """ opaque struct """ class XkbStateRec(ctypes.Structure): _fields_ = [ ('group', ctypes.c_ubyte), ('locked_group', ctypes.c_ubyte), ('base_group', ctypes.c_ushort), ('latched_group', ctypes.c_ushort), ('mods', ctypes.c_ubyte), ('base_mods', ctypes.c_ubyte),
def __init__(self, discover, piggyback=None): Gtk.Window.__init__(self, type=self.detect_type()) self.discover = discover screen = self.get_screen() self.compositing = False self.text_font = None self.text_size = None self.pos_x = None self.pos_y = None self.width = None self.height = None self.needsredraw = True self.hidden = False self.enabled = False self.set_size_request(50, 50) self.connect('draw', self.overlay_draw) # Set RGBA screen = self.get_screen() visual = screen.get_rgba_visual() if not self.get_display().supports_input_shapes(): log.info("Input shapes not available. Quitting") sys.exit(1) if visual: # Set the visual even if we can't use it right now self.set_visual(visual) if screen.is_composited(): self.compositing = True self.set_app_paintable(True) self.set_untouchable() self.set_skip_pager_hint(True) self.set_skip_taskbar_hint(True) self.set_keep_above(True) self.set_decorated(True) self.set_accept_focus(False) self.set_wayland_state() if not piggyback: self.show_all() if discover.steamos: display = Display() atom = display.intern_atom("GAMESCOPE_EXTERNAL_OVERLAY") opaq = display.intern_atom("_NET_WM_WINDOW_OPACITY") topw = display.create_resource_object( "window", self.get_toplevel().get_window().get_xid()) topw.change_property(atom, Xatom.CARDINAL, 32, [1], X.PropModeReplace) # Keep for reference, but appears to be unnecessary # topw.change_property(opaq, # Xatom.CARDINAL,16, # [0xffff], X.PropModeReplace) log.info("Setting STEAM_EXTERNAL_OVERLAY") display.sync() self.monitor = 0 self.align_right = True self.align_vert = 1 self.floating = False self.force_xshape = False self.context = None self.autohide = False self.piggyback = None self.piggyback_parent = None if piggyback: self.set_piggyback(piggyback)
active_device_time = np.array(object=[], dtype=np.float32) prev_device_time = None prev_device_counter = None start_device_time = None prev_title = None current_title = None window_actives = {} main_process_pid = None # Connect to the X server and get the root window display_focus = Display() root = display_focus.screen().root # Prepare the property names we use so they can be fed into X11 APIs NET_ACTIVE_WINDOW = display_focus.intern_atom('_NET_ACTIVE_WINDOW') NET_WM_NAME = display_focus.intern_atom('_NET_WM_NAME') # UTF-8 WM_NAME = display_focus.intern_atom('WM_NAME') # Legacy encoding last_seen = {'xid': None, 'title': None} # type: Dict[str, Any] def print_window_actives(actives: dict): for title, list_times in actives.items(): print(title) for (start_time, end_time) in list_times: print("{} - {}".format(start_time, end_time)) print("---------------------------------------") @contextmanager
class Environment(): def __init__(self): self.display = Display() self.screen = self.display.screen() self.root = self.screen.root self.region = Region(x=0, y=0, width=self.screen.width_in_pixels, height=self.screen.height_in_pixels) self.desktops = [] self.windows = self.get_window_set() self.window_desktop_map = {} self.hidden_windows = set() self.visible_windows = set() for i in range(self.number_of_desktops()): LOGGER.debug("\n\n\nCreating Desktop %d" % (i)) d = Desktop(i, self) self.desktops.append(d) #d.print_windows() d.arrange() self.update_desktop_map() self.setup_listeners() #self.print_hierarchy(self.root, " - ") def setup_listeners(self): self.root.change_attributes(event_mask=X.SubstructureNotifyMask | X.PropertyChangeMask) anchor = self.root.create_window(0, 0, 1, 1, 1, self.screen.root_depth) anchor.xrandr_select_input(randr.RRScreenChangeNotifyMask | randr.RRCrtcChangeNotifyMask | randr.RROutputChangeNotifyMask | randr.RROutputPropertyNotifyMask) def update_all_the_things(self): self.display = Display() self.screen = self.display.screen() self.root = self.screen.root self.region = Region(x=0, y=0, width=self.screen.width_in_pixels, height=self.screen.height_in_pixels - 32) LOGGER.debug("NEW REGION: %s" % (self.region)) for d in self.desktops: d.resize() self.setup_listeners() def print_hierarchy(self, window, indent): children = window.query_tree().children for w in children: LOGGER.debug(indent, window.get_wm_class()) self.print_hierarchy(w, indent + '-') def interesting_properties(self): #_NET_WM_DESKTOP # root: _NET_CURRENT_DESKTOP LOGGER.debug("Current desktop") LOGGER.debug( self.root.get_full_property( self.display.intern_atom('_NET_CURRENT_DESKTOP'), Xatom.CARDINAL).value[0]) def current_desktop(self): return self.root.get_full_property( self.display.intern_atom('_NET_CURRENT_DESKTOP'), Xatom.CARDINAL).value[0] def number_of_desktops(self): return self.root.get_full_property( self.display.intern_atom('_NET_NUMBER_OF_DESKTOPS'), Xatom.CARDINAL).value[0] def update_window_states(self): window_ids = self.get_window_set(include_hidden=True) for window_id in window_ids: if self.is_window_hidden(window_id): self.hidden_windows.add(window_id) else: self.visible_windows.add(window_id) def update_desktop_map(self): self.window_desktop_map = {} for d in self.desktops: for window_id in d.get_window_set(include_hidden=True): self.window_desktop_map[window_id] = d def get_window_desktop(self, window): if type(window) is long: w = self.display.create_resource_object('window', window) else: w = window try: value = w.get_full_property( self.display.intern_atom('_NET_WM_DESKTOP'), Xatom.CARDINAL).value[0] if value > self.number_of_desktops(): return None else: return value except AttributeError: return None except Xlib.error.BadWindow: return None def get_window_states(self, window_id): w = self.display.create_resource_object('window', window_id) #return w.get_full_property(self.display.intern_atom('_NET_WM_STATE'), Xatom.ATOM).value try: states = w.get_full_property( self.display.get_atom('_NET_WM_STATE'), Xatom.WINDOW) except Xlib.error.BadWindow: LOGGER.warn("Bad window fetching states...") states = None if states == None: return [] else: res = w.get_full_property(self.display.get_atom('_NET_WM_STATE'), 0).value.tolist() return res def is_window_hidden(self, window_id): hidden_state_atom = self.display.get_atom("_NET_WM_STATE_HIDDEN") states = self.get_window_states(window_id) return hidden_state_atom in states def is_window_visible(self, window_id): return not self.is_window_hidden(window_id) def listen_for_events(self): LOGGER.debug("Listening for change events!") while True: ev = self.display.next_event() self.handle_event(ev) def handle_event(self, event): old_hidden = set(self.hidden_windows) old_visible = set(self.visible_windows) self.update_window_states() changed_ids = set() changed_ids.update(old_hidden.symmetric_difference( self.hidden_windows)) changed_ids.update( old_visible.symmetric_difference(self.visible_windows)) if len(changed_ids) > 0: LOGGER.debug("Changed IDs: %s" % (changed_ids)) needs_update = False wm_active_window = self.display.get_atom('_NET_ACTIVE_WINDOW') wm_move_window = self.display.get_atom('_NET_MOVERESIZE_WINDOW') wm_hidden_window = self.display.get_atom('_NET_WM_STATE_HIDDEN') wm_state = self.display.get_atom('_NET_WM_STATE') try: window_props = event.window.get_full_property(event.atom, 0) window_id = int(window_props.value.tolist()[0]) LOGGER.debug("Window ID: %s" % (window_id)) except AttributeError: pass #LOGGER.debug("Not a window-level event") for window_id in changed_ids: desktop = self.window_desktop_map.get(window_id, None) LOGGER.debug("Window changed: %s on desktop: %s" % (window_id, desktop)) if desktop is not None: desktop.arrange() needs_update = True if event.type == X.PropertyNotify: LOGGER.debug("Property changed...") if event.atom == wm_active_window: LOGGER.debug("Property changed on an active window....") elif event.type == X.CreateNotify or event.type == X.DestroyNotify: needs_update = True window_set = self.get_window_set() if event.type == X.CreateNotify: LOGGER.debug("Handling creation!") new_windows = window_set.difference(self.windows) for window in new_windows: window_resource = self.display.create_resource_object( 'window', window) window_desktop = self.get_window_desktop(window_resource) if window_desktop is not None: self.desktops[window_desktop].layout.add_window( window_resource) if event.type == X.DestroyNotify: LOGGER.debug("Handling destruction!") missing_windows = self.windows.difference(window_set) for window in missing_windows: window_resource = self.display.create_resource_object( 'window', window) # TODO: optimize lookup by keeping old map? for desktop in self.desktops: LOGGER.debug("Trying to remove from desktop %s" % (desktop)) desktop.layout.remove_window(window_resource) self.windows = window_set for desktop in self.desktops: desktop.layout.redraw() elif event.__class__.__name__ == randr.ScreenChangeNotify.__name__: LOGGER.debug('Screen change') self.update_all_the_things() else: #LOGGER.debug("Unhandled event: %d" % (event.type)) pass if needs_update: self.update_desktop_map() def get_window_set(self, include_hidden=False, desktop_number=None): windows = set([ x for x in self.root.get_full_property( self.display.intern_atom('_NET_CLIENT_LIST'), Xatom.WINDOW).value ]) if desktop_number is not None: #LOGGER.debug("Filtering windows not on: %d" % (desktop_number)) windows = filter( lambda w: self.get_window_desktop(w) == desktop_number, windows) if include_hidden is False: #LOGGER.debug("Filtering hidden windows...") windows = filter(lambda w: self.is_window_visible(w) == True, windows) return set(windows)
from Xlib.display import Display from Xlib.ext import record from Xlib.protocol import rq from Xlib import XK as _XK _XK.load_keysym_group('xf86') XK_KEYS = vars(_XK) disp_prog = Display() x11 = True except Exception: _log.warn('X11 not available - rules will not be activated') XK_KEYS = {} x11 = False if x11: # determine name of active process NET_ACTIVE_WINDOW = disp_prog.intern_atom('_NET_ACTIVE_WINDOW') NET_WM_PID = disp_prog.intern_atom('_NET_WM_PID') root2 = disp_prog.screen().root root2.change_attributes(event_mask=Xlib.X.PropertyChangeMask) active_process_name = None def active_program(): try: window_id = root2.get_full_property(NET_ACTIVE_WINDOW, Xlib.X.AnyPropertyType).value[0] window = disp_prog.create_resource_object('window', window_id) window_pid = window.get_full_property(NET_WM_PID, 0).value[0] return psutil.Process(window_pid).name() except (Xlib.error.XError,
class Prenestr(object): def __init__(self): self.ratio = 0.5 self.wborder = 10 self.hborder = 30 self.disp = Display() self.root = self.disp.screen().root # we tell the X server we want to catch keyPress event self.root.change_attributes(event_mask=X.KeyPressMask) self.grab_key(K_L) self.grab_key(K_H) self.grab_key(K_L, X.Mod4Mask | X.ShiftMask) self.grab_key(K_H, X.Mod4Mask | X.ShiftMask) self.grab_key(K_T) self.grab_key(K_ENTER) while True: event = self.root.display.next_event() if event.type == X.KeyPress: self.keypress(event) def get_active(self): id = self.root.get_full_property( self.disp.intern_atom("_NET_ACTIVE_WINDOW"), 0).value[0] obj = self.disp.create_resource_object('window', id) return (id, obj) def _send_event(self, win, ctype, data, mask=None): data = (data + ([0] * (5 - len(data))))[:5] ev = protocol.event.ClientMessage(window=win, client_type=ctype, data=(32, (data))) self.root.send_event(ev, event_mask=X.SubstructureRedirectMask) def workarea(self): v = self.root.get_full_property( self.disp.intern_atom("_NET_WORKAREA"), 0).value return v[0], v[1], v[2], v[3] def move(self, win, to='left', y_pos=0, y_nbwin=1): id, obj = win rx, ry, rw, rh = self.workarea() if to == 'left': x = rx y = ry w = rw * self.ratio - self.wborder h = rh elif to == 'right': x = rx + rw * self.ratio + self.wborder y = ry + y_pos * (rh / y_nbwin) w = rw * (1 - self.ratio) - self.wborder h = rh / y_nbwin if y_nbwin > 1: h = h - self.hborder # Reset state self._send_event(id, self.disp.intern_atom("_NET_WM_STATE"), [0, self.disp.intern_atom("_NET_WM_STATE_MAXIMIZED_VERT"), self.disp.intern_atom("_NET_WM_STATE_MAXIMIZED_HORZ")]) obj.configure(x=x, y=y, width=w, height=h, stack_mode=X.Above) self._send_event(id, self.disp.intern_atom("_NET_ACTIVE_WINDOW"), []) self.disp.flush() def grab_key(self, key, mask=X.Mod4Mask, ungrab=False): self.root.grab_key(key, mask, 1, X.GrabModeAsync, X.GrabModeAsync) if ungrab: self.ungrab_list.append(key) def ungrab_key(self, key, mask=X.Mod4Mask): self.root.ungrab_key(key, mask, 1) def tile(self, master='position'): current_window = self.get_active() win_list = self.root.get_full_property( self.disp.intern_atom("_NET_CLIENT_LIST"), Xatom.WINDOW).value current_desktop = self.root.get_full_property( self.disp.intern_atom("_NET_CURRENT_DESKTOP"), 0).value[0] desk_list = [] for win_id in win_list: obj = self.disp.create_resource_object('window', win_id) windesk = obj.get_full_property( self.disp.intern_atom("_NET_WM_DESKTOP"), 0).value[0] if windesk == current_desktop: # This window is on the current desktop # Skip if transient transient = obj.get_wm_transient_for() if transient and transient != self.root: continue # Skip if hidden state = obj.get_full_property( self.disp.intern_atom("_NET_WM_STATE"), Xatom.ATOM) dock = obj.get_full_property( self.disp.intern_atom("_NET_WM_WINDOW_TYPE"), Xatom.ATOM) if (state and self.disp.intern_atom("_NET_WM_STATE_HIDDEN") in state.value or self.disp.intern_atom("_NET_WM_STATE_SKIP_TASKBAR") in state.value or self.disp.intern_atom("_NET_WM_STATE_SKIP_PAGER") in state.value): # hidden continue if (dock and self.disp.intern_atom("_NET_WM_WINDOW_TYPE_DOCK") in dock.value and self.disp.intern_atom("_NET_WM_WINDOW_TYPE_TOOLBAR") in dock.value and self.disp.intern_atom("_NET_WM_WINDOW_TYPE_MENU") in dock.value and self.disp.intern_atom("_NET_ACTIVE_WINDOW_TYPE_SPLASH") in dock.value and self.disp.intern_atom("_NET_ACTIVE_WINDOW_TYPE_DIALOG") in dock.value): # hidden continue desk_list.append((win_id, obj)) if not desk_list: return def get_geom(window): wg = window.get_geometry() tl = window.translate_coords(self.root, wg.x, wg.y) return (-tl.x, -tl.y, wg.width, wg.height) geom = [(w, get_geom(w[1])) for w in desk_list] if master == 'position': left = min(geom, key=lambda l: l[1][0])[0] else: left = current_window self.move(left, to="left") others = [w for w in geom if w[0][0] != left[0]] others.sort(key=lambda l: l[1][1]) for pos, win in enumerate(others): self.move(win[0], to="right", y_pos=pos, y_nbwin=len(others)) # Reactivate window self._send_event(current_window[0], self.disp.intern_atom("_NET_ACTIVE_WINDOW"), []) return def keypress(self, event): if event.detail == K_H: if event.state & X.ShiftMask: self.ratio -= 0.1 if self.ratio < 0: self.ratio = 0 self.tile() else: self.move(self.get_active()) elif event.detail == K_L: if event.state & X.ShiftMask: self.ratio += 0.1 if self.ratio > 1: self.ratio = 1 self.tile() else: self.move(self.get_active(), to='right') elif event.detail == K_T: self.tile() elif event.detail == K_ENTER: self.tile(master='active')
def get_pid_by_window_id(display: Xdisplay.Display, window_id: int): window = display.create_resource_object('window', window_id) prop = window.get_full_property(display.intern_atom('_NET_WM_PID'), Xlib.X.AnyPropertyType) return (prop.value[0] if prop else None)
class XWindowFocusTracker: def __init__(self): self._callbacks: MutableSequence[Callback] = [] self._disp = Display() self._current_window_id: Optional[XWindowId] = None self.NET_ACTIVE_WINDOW = self._disp.intern_atom('_NET_ACTIVE_WINDOW') self.NET_WM_NAME = self._disp.intern_atom('_NET_WM_NAME') self._screen_locked = False self._lock = threading.Lock() def register(self, callback: Callback) -> None: with self._lock: self._callbacks.append(callback) def run(self) -> None: screen_lock_tracker = ScreenLockTracker(self) screen_lock_tracker_thread = threading.Thread( target=screen_lock_tracker.run, daemon=True) screen_lock_tracker_thread.start() root = self._disp.screen().root root.change_attributes(event_mask=X.PropertyChangeMask) while True: self._handle_xevent(self._disp.next_event()) def set_screen_locked(self, locked: bool) -> None: logging.info(f'XWindowFocusTracker.set_screen_locked(locked={locked})') with self._lock: self._screen_locked = locked if locked: window_id = -1 window_name = 'locked' else: window_id = self._current_window_id window_name = self._get_window_name(window_id) for callback in self._callbacks: callback(window_id, window_name) def _handle_xevent(self, event: Event) -> None: """Handler for X events which ignores anything but focus/title change""" if event.type != X.PropertyNotify: return if event.atom != self.NET_ACTIVE_WINDOW: return window_id = event.window.get_full_property(self.NET_ACTIVE_WINDOW, X.AnyPropertyType).value[0] with self._lock: if self._current_window_id == window_id: return self._current_window_id = window_id window_name = self._get_window_name(window_id) for callback in self._callbacks: callback(window_id, window_name) def _get_window_name(self, window_id: XWindowId) -> str: window_obj = self._disp.create_resource_object('window', window_id) try: window_name_property = window_obj.get_full_property( self.NET_WM_NAME, 0) except Xlib.error.BadWindow: return '' else: return window_name_property.value.decode('utf-8')