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 getWindows(): """Return dict: {'window title' : window id} for all visible windows""" titles = {} display = Display() root = display.screen().root win_list = root.get_full_property(NET_CLIENT_LIST, X.AnyPropertyType).value for win_id in win_list: window = display.create_resource_object('window', win_id) class_list = window.get_wm_class() """ get_wm_name() method unable to handle utf-8 characters, thats why below window title gets from property and decoding instead of using one short Window method that provided by python xlib """ try: win_name = window.get_full_property(NET_WM_NAME, X.AnyPropertyType).value except: pass if win_name: try: win_name = win_name.decode("utf-8") except: pass titles[win_name] = win_id else: titles[class_list[1]] = win_id return titles
class PointerMonitor(GObject.GObject, threading.Thread): __gsignals__ = { 'activate': (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self): GObject.GObject.__init__ (self) threading.Thread.__init__ (self) self.setDaemon (True) self.display = Display() self.root = self.display.screen().root self.windows = [] # Receives GDK windows def addWindowToMonitor(self, window): gdk.gdk_x11_drawable_get_xid.argtypes = [c_void_p] xWindow = self.display.create_resource_object("window", gdk.gdk_x11_drawable_get_xid(hash(window))) self.windows.append(xWindow) def grabPointer(self): self.root.grab_button(X.AnyButton, X.AnyModifier, True, X.ButtonPressMask, X.GrabModeSync, X.GrabModeAsync, 0, 0) self.display.flush() def ungrabPointer(self): self.root.ungrab_button(X.AnyButton, X.AnyModifier) self.display.flush() def idle(self): self.emit("activate") return False def activate(self): GLib.idle_add(self.run) def run(self): self.running = True while self.running: event = self.display.next_event() try: if event.type == X.ButtonPress: # Check if pointer is inside monitored windows for w in self.windows: p = w.query_pointer() g = w.get_geometry() if p.win_x >= 0 and p.win_y >= 0 and p.win_x <= g.width and p.win_y <= g.height: break else: # Is outside, so activate GLib.idle_add(self.idle) self.display.allow_events(X.ReplayPointer, event.time) else: self.display.allow_events(X.ReplayPointer, X.CurrentTime) except Exception as e: print "Unexpected error: " + str(e) def stop(self): self.running = False self.root.ungrab_button(X.AnyButton, X.AnyModifier) self.display.close()
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 PointerMonitor(GObject.GObject, threading.Thread): __gsignals__ = { 'activate': (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self): GObject.GObject.__init__ (self) threading.Thread.__init__ (self) self.setDaemon (True) self.display = Display() self.root = self.display.screen().root self.windows = [] # Receives GDK windows def addWindowToMonitor(self, window): xWindow = self.display.create_resource_object("window", gdk.gdk_x11_drawable_get_xid(hash(window))) self.windows.append(xWindow) def grabPointer(self): self.root.grab_button(X.AnyButton, X.AnyModifier, True, X.ButtonPressMask, X.GrabModeSync, X.GrabModeAsync, 0, 0) self.display.flush() def ungrabPointer(self): self.root.ungrab_button(X.AnyButton, X.AnyModifier) self.display.flush() def idle(self): self.emit("activate") return False def activate(self): GLib.idle_add(self.run) def run(self): self.running = True while self.running: event = self.display.next_event() try: if event.type == X.ButtonPress: # Check if pointer is inside monitored windows for w in self.windows: p = w.query_pointer() g = w.get_geometry() if p.win_x >= 0 and p.win_y >= 0 and p.win_x <= g.width and p.win_y <= g.height: break else: # Is outside, so activate GLib.idle_add(self.idle) self.display.allow_events(X.ReplayPointer, event.time) else: self.display.allow_events(X.ReplayPointer, X.CurrentTime) except Exception as e: print "Unexpected error: " + str(e) def stop(self): self.running = False self.root.ungrab_button(X.AnyButton, X.AnyModifier) self.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()
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()
class XlibHelper: """Utility class for some X11-specific logic""" def __init__(self): self.display = Display() self.use_xres = self._try_init_xres() def _try_init_xres(self) -> bool: if XRes is None or self.display.query_extension(XRes.extname) is None: LOG.warning( "X-Resource extension is not supported. " "Process identification for X11 applications will be less reliable." ) return False ver = self.display.res_query_version() LOG.info( "X-Resource version %d.%d", ver.server_major, ver.server_minor, ) return (ver.server_major, ver.server_minor) >= (1, 2) def get_net_wm_pid(self, wid: int) -> int: """Get PID from _NET_WM_PID property of X11 window""" window = self.display.create_resource_object("window", wid) net_wm_pid = self.display.get_atom("_NET_WM_PID") pid = window.get_full_property(net_wm_pid, X.AnyPropertyType) if pid is None: raise Exception("Failed to get PID from _NET_WM_PID") return int(pid.value.tolist()[0]) def get_xres_client_id(self, wid: int) -> int: """Get PID from X server via X-Resource extension""" res = self.display.res_query_client_ids([{ "client": wid, "mask": XRes.LocalClientPIDMask }]) for cid in res.ids: if cid.spec.client > 0 and cid.spec.mask == XRes.LocalClientPIDMask: for value in cid.value: return value raise Exception("Failed to get PID via X-Resource extension") def get_window_pid(self, wid: int) -> Optional[int]: """Get PID of X11 window""" if self.use_xres: return self.get_xres_client_id(wid) return self.get_net_wm_pid(wid)
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 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 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 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()
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 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)
# print(str(event)) #area.connect("motion_notify_event", mouse_move_callback); #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
class GlobalKeyBinding(GObject.GObject, threading.Thread): __gsignals__ = { 'activate': (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self): GObject.GObject.__init__ (self) threading.Thread.__init__ (self) self.setDaemon (True) gdk.gdk_keymap_get_default.restype = c_void_p self.keymap = capi.get_widget (gdk.gdk_keymap_get_default()) self.display = Display() self.screen = self.display.screen() self.window = self.screen.root self.ignored_masks = self.get_mask_combinations(X.LockMask | X.Mod2Mask | X.Mod5Mask) self.map_modifiers() self.raw_keyval = None self.keytext = "" def is_hotkey(self, key, modifier): keymatch = False modmatch = False modifier = modifier & ~Gdk.ModifierType.SUPER_MASK modint = int(modifier) if self.get_keycode(key) == self.keycode: keymatch = True for ignored_mask in self.ignored_masks: if self.modifiers | ignored_mask == modint | ignored_mask: modmatch = True break return keymatch and modmatch def map_modifiers(self): gdk_modifiers =(Gdk.ModifierType.CONTROL_MASK, Gdk.ModifierType.SHIFT_MASK, Gdk.ModifierType.MOD1_MASK, Gdk.ModifierType.MOD2_MASK, Gdk.ModifierType.MOD3_MASK, Gdk.ModifierType.MOD4_MASK, Gdk.ModifierType.MOD5_MASK, Gdk.ModifierType.SUPER_MASK, Gdk.ModifierType.HYPER_MASK) self.known_modifiers_mask = 0 for modifier in gdk_modifiers: if "Mod" not in Gtk.accelerator_name(0, modifier) or "Mod4" in Gtk.accelerator_name(0, modifier): self.known_modifiers_mask |= modifier def get_keycode(self, keyval): count = c_int() array = (KeymapKey * 10)() keys = cast(array, POINTER(KeymapKey)) gdk.gdk_keymap_get_entries_for_keyval.argtypes = [c_void_p, c_uint, c_void_p, c_void_p] gdk.gdk_keymap_get_entries_for_keyval(hash(self.keymap), keyval, byref(keys), byref(count)) return keys[0].keycode def grab(self, key): accelerator = key accelerator = accelerator.replace("<Super>", "<Mod4>") keyval, modifiers = Gtk.accelerator_parse(accelerator) if not accelerator or (not keyval and not modifiers): self.keycode = None self.modifiers = None return False self.keytext = key self.keycode = self.get_keycode(keyval) self.modifiers = int(modifiers) catch = error.CatchError(error.BadAccess) for ignored_mask in self.ignored_masks: mod = modifiers | ignored_mask result = self.window.grab_key(self.keycode, mod, True, X.GrabModeAsync, X.GrabModeSync, onerror=catch) self.display.flush() # sync has been blocking. Don't know why. #self.display.sync() if catch.get_error(): return False return True def ungrab(self): if self.keycode: self.window.ungrab_key(self.keycode, X.AnyModifier, self.window) def rebind(self, key): self.ungrab() if key != "": self.grab(key) else: self.keytext = "" def set_focus_window(self, window = None): self.ungrab() if window is None: self.window = self.screen.root else: gdk.gdk_x11_drawable_get_xid.argtypes = [c_void_p] self.window = self.display.create_resource_object("window", gdk.gdk_x11_drawable_get_xid(hash(window))) self.grab(self.keytext) def get_mask_combinations(self, mask): return [x for x in xrange(mask+1) if not (x & ~mask)] def idle(self): self.emit("activate") return False def activate(self): GLib.idle_add(self.run) def run(self): self.running = True wait_for_release = False while self.running: event = self.display.next_event() try: self.current_event_time = event.time if event.detail == self.keycode and event.type == X.KeyPress and not wait_for_release: modifiers = event.state & self.known_modifiers_mask if modifiers == self.modifiers: wait_for_release = True self.display.allow_events(X.AsyncKeyboard, event.time) else: self.display.allow_events(X.ReplayKeyboard, event.time) elif event.detail == self.keycode and wait_for_release: if event.type == X.KeyRelease: wait_for_release = False GLib.idle_add(self.idle) self.display.allow_events(X.AsyncKeyboard, event.time) else: self.display.allow_events(X.ReplayKeyboard, event.time) except AttributeError: continue def stop(self): self.running = False self.ungrab() self.display.close()
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')
class GlobalKeyBinding(GObject.GObject, threading.Thread): __gsignals__ = { 'activate': (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self): GObject.GObject.__init__(self) threading.Thread.__init__(self) self.setDaemon(True) self.keymap = Gdk.Keymap().get_default() self.display = Display() self.screen = self.display.screen() self.window = self.screen.root self.ignored_masks = self.get_mask_combinations(X.LockMask | X.Mod2Mask | X.Mod5Mask) self.map_modifiers() self.raw_keyval = None self.keytext = "" def map_modifiers(self): gdk_modifiers = (Gdk.ModifierType.CONTROL_MASK, Gdk.ModifierType.SHIFT_MASK, Gdk.ModifierType.MOD1_MASK, Gdk.ModifierType.MOD2_MASK, Gdk.ModifierType.MOD3_MASK, Gdk.ModifierType.MOD4_MASK, Gdk.ModifierType.MOD5_MASK, Gdk.ModifierType.SUPER_MASK, Gdk.ModifierType.HYPER_MASK) self.known_modifiers_mask = 0 for modifier in gdk_modifiers: if "Mod" not in Gtk.accelerator_name( 0, modifier) or "Mod4" in Gtk.accelerator_name( 0, modifier): self.known_modifiers_mask |= modifier def grab(self, key): accelerator = key accelerator = accelerator.replace("<Super>", "<Mod4>") keyval, modifiers = Gtk.accelerator_parse(accelerator) if not accelerator or (not keyval and not modifiers): self.keycode = None self.modifiers = None return False self.keytext = key try: self.keycode = self.keymap.get_entries_for_keyval( keyval).keys[0].keycode except AttributeError: # In older Gtk3 the get_entries_for_keyval() returns an unnamed tuple... self.keycode = self.keymap.get_entries_for_keyval( keyval)[1][0].keycode self.modifiers = int(modifiers) # Request to receive key press/release reports from other windows that may not be using modifiers catch = error.CatchError(error.BadWindow) if self.modifiers: self.window.change_attributes(onerror=catch, event_mask=X.KeyPressMask | X.KeyReleaseMask) else: self.window.change_attributes(onerror=catch, event_mask=X.NoEventMask) if catch.get_error(): return False catch = error.CatchError(error.BadAccess) for ignored_mask in self.ignored_masks: mod = modifiers | ignored_mask result = self.window.grab_key(self.keycode, mod, True, X.GrabModeAsync, X.GrabModeAsync, onerror=catch) self.display.flush() # sync has been blocking. Don't know why. #self.display.sync() if catch.get_error(): return False return True def ungrab(self): if self.keycode: self.window.ungrab_key(self.keycode, X.AnyModifier, self.window) def rebind(self, key): self.ungrab() if key != "": self.grab(key) else: self.keytext = "" def set_focus_window(self, window=None): self.ungrab() if window is None: self.window = self.screen.root else: self.window = self.display.create_resource_object( "window", window.get_xid()) self.grab(self.keytext) def get_mask_combinations(self, mask): return [x for x in xrange(mask + 1) if not (x & ~mask)] def idle(self): self.emit("activate") return False def activate(self): GLib.idle_add(self.run) def run(self): self.running = True wait_for_release = False while self.running: event = self.display.next_event() if self.modifiers: # Use simpler logic when using traditional combined keybindings modifiers = event.state & self.known_modifiers_mask if event.type == X.KeyPress and event.detail == self.keycode and modifiers == self.modifiers: GLib.idle_add(self.idle) else: try: if event.type == X.KeyPress and event.detail == self.keycode and not wait_for_release: modifiers = event.state & self.known_modifiers_mask if modifiers == self.modifiers: wait_for_release = True elif event.type == X.KeyRelease and event.detail == self.keycode and wait_for_release: GLib.idle_add(self.idle) wait_for_release = False else: self.display.ungrab_keyboard(X.CurrentTime) # Send the event up in case another window is listening to it self.display.send_event( event.window, event, X.KeyPressMask | X.KeyReleaseMask, True) wait_for_release = False except AttributeError: continue def stop(self): self.running = False self.ungrab() self.display.close()
def getBox(winID): dpy = Display() data = dpy.create_resource_object('window', winID).get_geometry() boxWin = (data.x, data.y, data.x + data.width, data.y + data.height) return boxWin
def run(self, window, screen): xid = window.get_xid() xdisplay = XDisplay() xwindow = xdisplay.create_resource_object("window", xid) xwindow.configure(x=self.arg[0], y=self.arg[1]) xdisplay.sync()
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'])
win_class = sys.argv[1] fps = int(sys.argv[2]) width = int(sys.argv[3]) height = int(sys.argv[4]) name = len(sys.argv) >= 6 and sys.argv[5] or "A Stream" streamer_name = len(sys.argv) >= 7 and sys.argv[6] or "Anonymous" url = "https://sevii.dev/api/streamer" # "http://localhost/api/streamer" dis = Display() root = dis.screen().root win = None # ------------------------------ # if win_class[0] == "0": win = dis.create_resource_object("window", int(win_class, 16)) else: for w in root.query_tree().children: wclass = w.get_wm_class() if wclass != None and wclass[1].lower() == win_class: try: dis.create_resource_object("window", w.id + 2).get_image(0, 0, 1, 1, X.ZPixmap, 0xffffffff) except: pass else: win = dis.create_resource_object("window", w.id + 2) break if not win:
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 KnoX: Geometry = namedtuple("Geometry", "x y width height") FrameExtents = namedtuple("FrameExtents", "left right top bottom") def __init__(self): #self.display = Display(os.environ.get("DISPLAY", ":0.0")) self.display = Display() print("Connected to X DISPLAY %r" % self.display.get_display_name()) self.display.set_error_handler(self.knox_error_handler) self.screen = self.display.screen() self.root = self.screen.root self.atoms = dict() self.atom_names = dict() self.keysyms = Keysyms() self.modifiers = Modifiers(self) self._supported_properties = None self._acceptable_error_sequence = 0 self._acceptable_errors = dict() self._silenced_errors = set() def fileno(self): """This function is here to make select work with this object""" return self.display.fileno() @contextmanager def silenced_error(self, error): silencer = self.silence_error(error) try: yield silencer finally: self.remove_silencer(silencer) def silence_error(self, error): k = self._acceptable_error_sequence self._acceptable_errors[k] = error self._acceptable_error_sequence += 1 self._silenced_errors = set(self._acceptable_errors.values()) return k def remove_silencer(self, key): if key in self._acceptable_errors: del self._acceptable_errors[key] self._silenced_errors = set(self._acceptable_errors.values()) def knox_error_handler(self, err, *args): if type(err) not in self._silenced_errors: print("X protocol error: %s" % err) traceback.print_stack() # def wait_for_event(self, timeout_seconds): # """ Wait up to `timeout_seconds` seconds for an event to be queued. # Return True, if a xevent is available. # Return False, if the timeout was reached. # from https://gist.github.com/fphammerle/d81ca3ff0a169f062a9f28e57b18f04d""" # rlist = select.select( # [self.display], # rlist # [], # wlist # [], # xlist # timeout_seconds, # timeout [seconds] # )[0] # return len(rlist) > 0 def next_event(self, wait=True): if (wait or self.display.pending_events()): return self.display.next_event() else: return None # def next_event(self, event_loop): # event_loop.register_reader(self.display, def atom(self, name, only_if_exists=False): if isinstance(name, int): a = name elif name not in self.atoms: a = self.display.get_atom(name, only_if_exists=only_if_exists) self.atoms[name] = a else: a = self.atoms[name] return a def atom_name(self, atom): if atom in self.atom_names: return self.atom_names[atom] name = self.display.get_atom_name(atom) if name: self.atom_names[atom] = name if name not in self.atoms: self.atoms[name] = atom return name def get_prop(self, window, name): prop_name = self.atom(name, only_if_exists=True) if not prop_name: return None if isinstance(window, int): window = self.get_window(window) p = window.get_full_property(prop_name, X.AnyPropertyType) if p: return p.value def get_text_prop(self, window, name): prop_name = self.atom(name, only_if_exists=True) if not prop_name: return None s = window.get_full_text_property(prop_name, Xatom.STRING) if not s: t = self.atom("UTF8_STRING", only_if_exists=True) if t: s = window.get_full_text_property(prop_name, t) return s def onerror(self, *args, **kwargs): print("ERROR: something bad happened about %r and %r" % (args, kwargs)) raise Exception("Error is bad...") def set_prop(self, window, name, type_name, value): if isinstance(window, int): window = self.get_window(window) if isinstance(type_name, int): prop_type_name = type_name #type_name = self.atom_name(prop_type_name) else: prop_type_name = self.atom(type_name, only_if_exists=False) prop_name = self.atom(name, only_if_exists=False) if value is None: window.delete_property(prop_name) else: window.change_property(prop_name, prop_type_name, 32, value, mode=X.PropModeReplace, onerror=self.onerror) def send_prop_change_event( self, property_name, data, target=None, window=None, ): if target is None: target = self.root if window is None: window = target ev = protocol.event.ClientMessage(window=window, client_type=self.atom(property_name), data=data) target.send_event(ev, event_mask=X.SubstructureNotifyMask | X.SubstructureRedirectMask, propagate=False, onerror=self.onerror) def current_desktop(self, desktop=None, wait=True): prop_name = "_NET_CURRENT_DESKTOP" if desktop is None: pv = self.get_prop(self.root, prop_name) if pv: return pv[0] else: v = array('I', [desktop]) #self.set_prop(self.root, prop_name, Xatom.CARDINAL, v) self.send_prop_change_event( prop_name, (32, [desktop, X.CurrentTime, 0, 0, 0])) self.flush() w = Waiter(wait) while w.wait(): print("DESKTOPCHECK", hex(desktop)) if self.current_desktop() == desktop: print("DESKTOP OK") break def get_wm_pid(self, window): pid_prop = self.get_prop(window, "_NET_WM_PID") if pid_prop: return pid_prop[0] return None def get_wm_name(self, window): if isinstance(window, int): window = self.get_window(window) # window.get_wm_name gets only STRING property and returns nothing # if it's UTF8_STRING return self.get_text_prop(window, Xatom.WM_NAME) def active_window(self, window=None, wait=3, id_only=False): prop_name = "_NET_ACTIVE_WINDOW" if window is None: pv = self.get_prop(self.root, prop_name) if pv and pv[0]: window = self.get_window(pv[0]) if window and window.get_wm_name() != 'Desktop': if id_only: return window.id else: return window else: if isinstance(window, int): window = self.get_window(window) desktop = self.get_desktop_for_window(window) self.current_desktop(desktop) #v = array('I', [ window.id, 0 ]) #self.set_prop(self.root, prop_name, Xatom.WINDOW, v) # data[0]: source indication # 1: when the request comes from an application # 2: from a pager # 0: no spec. self.send_prop_change_event(prop_name, (32, [2, X.CurrentTime, 0, 0, 0]), window=window) self.flush() #self.raise_window(window) # it won't become active until it's focused focused = self.set_focused_window(window, wait=1) w = Waiter(wait) while w.wait(): a = self.active_window() self.flush() if not focused: focused = self.set_focused_window(window, wait=1) self.flush() if a and a.id == window.id: print("Activated %r!" % window.id) return True self.send_prop_change_event(prop_name, (32, [2, X.CurrentTime, 0, 0, 0]), window=window) self.flush() print("Can't activate %d" % window.id) return False def get_focused_window(self, toplevel=True): f = self.display.get_input_focus() #f = protocol.request.GetInputFocus(display=self.display.display) if f.focus in [X.NONE, X.PointerRoot]: return None if toplevel: w = self.get_client_window(f.focus) if w is not None: return w.id return f.focus.id def raise_window(self, window): if isinstance(window, int): window = self.get_window(window) elif window is None: return window.raise_window() def focus_error(self, *args, **kwargs): print("Cannot set_input_focus: %r %r" % (args, kwargs)) def set_focused_window(self, window, wait=3): if window is None: self.display.set_input_focus(X.NONE, X.RevertToParent, X.CurrentTime, onerror=self.focus_error) return True elif not wait: self.display.set_input_focus(window, X.RevertToParent, X.CurrentTime) return True else: with self.silenced_error(error.BadMatch): if isinstance(window, int): window = self.get_window(window) self.display.set_input_focus(window, X.RevertToParent, X.CurrentTime) self.flush() w = Waiter(wait) while w.wait(): if w.timeout: if w.progressed: print("WAITING %.3f seconds more for focus on %r" % (w.remaining, window.id)) else: print( "READY TO WAIT %.3f seconds for focus on %r" % (w.remaining, window.id)) focused_win_id = self.get_focused_window() if focused_win_id == window.id: print("FOCUSED %r" % window.id) return True # many times it's needed to repeat the command, esp. when mouse is # not inside the target window self.display.set_input_focus(window, X.RevertToParent, X.CurrentTime) self.flush() #self.display.set_input_focus(window, X.RevertToParent, X.CurrentTime) #self.display.flush() return False def get_desktop_for_window(self, window): pv = self.get_prop(window, "_NET_WM_DESKTOP") if pv: return pv[0] def set_desktop_for_window(self, window, desktop): if desktop is None: return name = self.atom("_NET_WM_DESKTOP", only_if_exists=True) if name in self.supported_properties: pv = self.set_prop(window, name, Xatom.CARDINAL, array('I', [desktop])) def save_state(self): state = { "Current Desktop": self.current_desktop(), "Active Window": self.active_window(id_only=True), "Focused Window": self.get_focused_window() } return state def restore_state(self, state): a = self.supported_properties self.current_desktop(state["Current Desktop"]) self.flush() try: self.set_focused_window(state["Focused Window"]) except error.BadWindow: print("Sorry, the old focused window went away...") # self.active_window(state["Active Window"]) def keysym_to_string(self, keysym, friendly=False, very_friendly=False): if keysym not in self.keysyms.keysyms: return chr(keysym) if very_friendly: return self.keysyms.friendly_name(keysym, simplest=True) if friendly: return self.keysyms.friendly_name(keysym, simplest=False) else: return self.keysyms[keysym] def keycode_to_keysym(self, keycode, idx=None): if idx is None: syms = set() for i in range(4): keysym = self.display.keycode_to_keysym(keycode, i) if keysym: syms.add(keysym) return syms else: return self.display.keycode_to_keysym(event.detail, i) def keysym_to_keycode(self, keysym): return self.display.keysym_to_keycode(keysym) def string_to_keysym(self, s): k = self.keysyms[s] if not k: k = self.keysyms["XK_" + s] if k: return k k = XK.string_to_keysym(s) return k # allow simpler names, like AudioRaiseVolume? # if s.startswith("XF86_"): # s = "XF86" + s[5:] # return XK.string_to_keysym(s) def error_handler(self, fn, *args, **kwargs): return functools.partial(fn, *args, **kwargs) def toggle_frame(self, window, frame=None, wait=1): """Set window frame. Value should be True or False for on and off, or None for toggle.""" # flags - set bit for every iteresting value # 0 functions => integer bits # 1 decorations => integer bits # 2 input_mode => enum string or integer # 3 status => integer bits # # functions: # bit actions offered # --- --------------- # 1 all functions # 2 resize window # 4 move window # 8 minimize, to iconify # 16 maximize, to full-screen (with a frame still) # 32 close window # # decorations: # bit decorations displayed # --- --------------------- # 1 all decorations # 2 border around the window # 4 resizeh, handles to resize by dragging # 8 title bar, showing WM_NAME # 16 menu, drop-down menu of the "functions" above # 32 minimize button, to iconify # 64 maximize button, to full-screen # # input mode: # string integer # "modeless" 0 not modal (the default) # "primary_application_modal" 1 modal to its "transient for" # "system_modal" 2 modal to the whole display # "full_application_modal" 3 modal to the current client # # status: # # bit # 1 tearoff menu window name = self.atom("_MOTIF_WM_HINTS", only_if_exists=True) # If does not exist, probably not supported, though should check # root for _NET_SUPPORTED list return assert prop != 0 pv = pv = self.get_prop(window, name) fe = self.get_frame_extents(window) if pv and len(pv) == 5: hints = array(pv.typecode, pv) if frame is None: hints[2] = 0 if hints[2] else 1 elif frame: hints[2] = 1 else: hints[2] = 0 else: # reasonable default hints = array('I', [2, 0, 0, 0, 0]) self.set_prop(window, name, name, hints) w = Waiter(wait) while w.wait(): pv = self.get_prop(window, name) if pv and array(pv.typecode, pv) == hints: new_fe = self.get_frame_extents(window) # make sure frame extents changed # this seems to take a while once the hints change if new_fe != fe: break def set_opacity(self, window, value): """value is a number between 0 and 1""" v = int(((1 << 32) - 1) * value) self.set_prop(window, "_NET_WM_WINDOW_OPACITY", Xatom.CARDINAL, array('I', [v])) def get_opacity(self, window): pv = self.get_prop(window, "_NET_WM_WINDOW_OPACITY") if pv: value = int(pv[0] / ((1 << 32) - 1)) return value return 1 @property def supported_properties(self): if self._supported_properties is None: self._supported_properties = self.get_prop(self.root, "_NET_SUPPORTED") or [] return self._supported_properties def get_window(self, win_id): if isinstance(win_id, int): return self.display.create_resource_object('window', win_id) else: return win_id def get_client_window(self, window): win_id = window.id for tlw in self.toplevel_windows(): for (_, parent, _) in self.window_tree( tlw, filter=lambda w, parent, level: w.id == win_id): return tlw return None def toplevel_windows(self, id_only=False): name = self.atom("_NET_CLIENT_LIST", only_if_exists=True) if name in self.supported_properties: lst = self.get_prop(self.root, name) if id_only: return lst else: return list(map(lambda win_id: self.get_window(win_id), lst)) else: print("BELGENGOC") if id_only: return list( map(lambda w: w.id, self.root.query_tree().children)) else: return list(self.root.query_tree().children) def window_tree(self, parent=None, level=1, filter=None): if parent is None: parent = self.root if filter is None or filter(parent, None, 0): yield (parent, None, 0) for w in parent.query_tree().children: if filter is None or filter(w, parent, level): yield (w, parent, level) yield from self.window_tree(parent=w, level=level + 1, filter=filter) def close_window(self, window): self.send_prop_change_event("_NET_CLOSE_WINDOW", (32, [0, 0, 0, 0, 0]), window=self.get_window(window)) # https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html # window = the respective client window # message_type = _NET_WM_STATE # format = 32 # data.l[0] = the action, as listed below # data.l[1] = first property to alter # data.l[2] = second property to alter # data.l[3] = source indication # other data.l[] elements = 0 # This message allows two prop # _NET_WM_STATE_REMOVE = 0 # remove/unset property _NET_WM_STATE_ADD = 1 #add/set property _NET_WM_STATE_TOGGLE = 2 # toggle property def set_wm_states(self, window, names, action=None): if action is None: action = self._NET_WM_STATE_TOGGLE elif action is True: action = self._NET_WM_STATE_ADD elif action is False: action = self._NET_WM_STATE_REMOVE window = self.get_window(window) values = list() for name in names: value = self.atom("_NET_WM_STATE_%s" % name.upper()) values.append(value) data = [action, *values] while len(data) < 5: data.append(0) self.send_prop_change_event("_NET_WM_STATE", (32, data), window=self.get_window(window)) def set_wm_state(self, window, name, action=None): if action is None: action = self._NET_WM_STATE_TOGGLE elif action is True: action = self._NET_WM_STATE_ADD elif action is False: action = self._NET_WM_STATE_REMOVE window = self.get_window(window) value = self.atom("_NET_WM_STATE_%s" % name.upper()) self.send_prop_change_event("_NET_WM_STATE", (32, [action, value, 0, 0, 0]), window=self.get_window(window)) def below_window(self, window, action=None): self.set_wm_state(window, name="below", action=action) def fullscreen_window(self, window, action=None): self.set_wm_state(window, name="fullscreen", action=action) def above_window(self, window, action=None): self.set_wm_state(window, name="above", action=action) def sticky_window(self, window, action=None): self.set_wm_state(window, name="sticky", action=action) def skip_pager(self, window, action=None): self.set_wm_state(window, name="skip_pager", action=action) def skip_taskbar(self, window, action=None): self.set_wm_state(window, name="skip_taskbar", action=action) def maximize_window(self, window, horizontal=True, vertical=True, action=None): if horizontal: self.set_wm_state(window, name="maximized_horz", action=action) if vertical: self.set_wm_state(window, name="maximized_vert", action=action) def minimize_window(self, window): if isinstance(window, int): window = self.get_window(window) self.send_prop_change_event("WM_CHANGE_STATE", (32, [Xutil.IconicState, 0, 0, 0, 0]), window=self.get_window(window)) def get_attributes(self, window): if isinstance(window, int): window = self.get_window(window) return window.get_attributes() def get_window_type(self, window): e = self.get_prop(window, "_NET_WM_WINDOW_TYPE") if e is None: return None type_details = set() prefix = "_NET_WM_WINDOW_TYPE_" for t in e: if not t: continue s = self.atom_name(t) if s.startswith(prefix): s = s[len(prefix):] type_details.add(s) return type_details def get_frame_extents(self, window): # x, y, width, height if isinstance(window, int): window = self.get_window(window) e = self.get_prop(window, "_NET_FRAME_EXTENTS") if e: return self.FrameExtents(*e) else: return self.FrameExtents(0, 0, 0, 0) def get_geometry(self, window): # x, y, width, height if isinstance(window, int): window = self.get_window(window) return window.get_geometry() def set_geometry(self, window, **data): # x, y, width, height if isinstance(window, int): window = self.get_window(window) if any(map(lambda v: v < 0, data.values())): gw = self.get_geometry(window) f = self.get_frame_extents(window) wa = self.usable_workarea() if 'x' in data and data['x'] < 0: data['x'] = wa.width - gw.width - (f.left + f.right) + data['x'] + 1 else: data['x'] += wa.x if 'y' in data and data['y'] < 0: data['y'] = wa.height - gw.height - (f.top + f.bottom) + data['y'] + 1 else: data['y'] += wa.y window.configure(**data) def usable_workarea(self): a = self.get_prop(self.root, "_NET_WORKAREA") if a: p = self.current_desktop() * 4 #return (x, y, width, height) return self.Geometry(*a[p:p + 4]) else: r = self.get_geometry(self.root) return self.Geometry(0, 0, r.width, r.height) def send_key(self, window, keysym, modifiers): if isinstance(window, int): window = self.get_window(window) keycode = self.display.keysym_to_keycode(keysym) event = protocol.event.KeyPress(time=X.CurrentTime, root=self.root, window=window, child=X.NONE, same_screen=True, root_x=0, root_y=0, event_x=0, event_y=0, state=modifiers.bitmap, detail=keycode) window.send_event(event, propagate=False) event = protocol.event.KeyRelease( time=X.CurrentTime, root=self.root, window=window, child=X.NONE, same_screen=True, # same screen as the root window root_x=0, root_y=0, event_x=0, event_y=0, state=modifiers.bitmap, detail=keycode) window.send_event(event, propagate=False) def show_desktop(self, action=None): prop_name = self.atom("_NET_SHOWING_DESKTOP") if action is True: self.send_prop_change_event(prop_name, (32, [1, X.CurrentTime, 0, 0, 0])) elif action is False: self.send_prop_change_event(prop_name, (32, [0, X.CurrentTime, 0, 0, 0])) else: pv = self.get_prop(self.root, prop_name) new_val = 0 if pv and pv[0] else 1 self.send_prop_change_event( prop_name, (32, [new_val, X.CurrentTime, 0, 0, 0])) def flush(self): # send all pending events self.display.flush() def sync(self): # flush and make sure everything is handled and processed or rejected by the server self.display.sync() @property def display_count(self): res = randr.get_screen_resources(self.root) n = 0 for i in res.outputs: o = randr.get_output_info(self.root, i, config_timestamp=0) if o.modes: # has modes, empty if there's no monitor connected here n += 1 return n
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
class mwsd: def __init__(self, args): self.verbose = args.verbose self.ip = "localhost" self.port = args.port self.ignore = args.ignore self.disp = Display() self.screen = self.disp.screen() self.root = self.screen.root self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.setup_socket() self.ids = [] self.windows = [] self.status = False self.running = True self.lock = threading.Lock() # Communication to daemon def setup_socket(self): # self.socket.setblocking(False) self.socket.bind((self.ip, self.port)) self.socket.listen(1) # Running the server in a other thread self.running = True self.thread = threading.Thread(target=self.server_listen) self.thread.daemon = True self.thread.start() def server_listen(self): while self.running is True: connection, client_address = self.socket.accept() try: msg = "" while True: data = connection.recv(16) if data: msg += data.decode() else: break if msg != "": msg_list = msg.split() if self.verbose is True: print(msg) if msg_list[0] == "add": for id in msg_list[1:]: self.add(int(id)) connection.sendall("done".encode()) elif msg_list[0] == "rm": for id in msg_list[1:]: self.rm(int(id)) connection.sendall("done".encode()) elif msg_list[0] == "clear": self.clear() elif msg_list[0] == "active": self.active() elif msg_list[0] == "deactive": self.deactive() elif msg_list[0] == "toggle": if self.status is True: self.active() else: self.deactive() elif msg_list[0] == "stop": self.terminate() else: print("Unknown command") connection.sendall("UnknownCommand".encode()) finally: connection.close() # Actions def add(self, id: int): self.lock.acquire(blocking=True) self.ids.append(id) self.windows.append(self.disp.create_resource_object('window', id)) self.grab(self.windows[-1]) self.lock.release() def rm(self, id: int): self.lock.acquire(blocking=True) index = self.ids.index(id) self.ids.remove(id) self.windows.remove(self.windows[index]) self.lock.release() def clear(self): self.lock.acquire(blocking=True) self.windows.clear() self.ids.clear() self.lock.release() def active(self): self.status = True def deactive(self): self.status = False def terminate(self): self.running = False # Daemon main function def listen(self): while self.running: # X11 if len(self.windows) != 0 and len(self.ids) != 0: evt = self.disp.next_event() if evt.type in [X.KeyPress]: keycode = evt.detail if self.verbose is True: print("Keycode:", keycode) self.disp.allow_events(X.ReplayKeyboard, X.CurrentTime) if self.status is True: for window in self.windows: self.press(window, keycode, evt.state) else: index = self.ids.index(evt.window.id) self.press(self.windows[index], keycode, evt.state) if evt.type == X.DestroyNotify: try: self.rm(evt.window.id) except ValueError: pass # X11 def event(self, name, window, detail, state): return name(time=X.CurrentTime, root=self.root, window=window, same_screen=0, child=Xlib.X.NONE, root_x=0, root_y=0, event_x=0, event_y=0, state=state, detail=detail) def press(self, window, keycode, mask=X.NONE): window.send_event(self.event(event.KeyPress, window, keycode, mask), propagate=True) window.send_event(self.event(event.KeyRelease, window, keycode, mask), propagate=True) self.disp.flush() self.disp.sync() def grab(self, window): window.grab_key(X.AnyKey, X.AnyModifier, True, X.GrabModeAsync, X.GrabModeAsync) # Ungrab window manager shortcuts (Super + ...) for key in self.ignore: window.ungrab_key(key, X.AnyModifier, True) window.change_attributes(event_mask=X.KeyReleaseMask | X.KeyPressMask | X.StructureNotifyMask) def ungrab(self, window): window.ungrab_key(X.AnyKey, X.AnyModifier, True) # Cleanup def cleanup(self): self.running = False
class GlobalKeyBinding(GObject.GObject, threading.Thread): __gsignals__ = { 'activate': (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self): try: GObject.GObject.__init__(self) threading.Thread.__init__(self) self.setDaemon(True) self.keymap = Gdk.Keymap().get_default() self.display = Display() self.screen = self.display.screen() self.window = self.screen.root self.showscreen = Wnck.Screen.get_default() self.ignored_masks = self.get_mask_combinations(X.LockMask | X.Mod2Mask | X.Mod5Mask) self.map_modifiers() self.raw_keyval = None self.keytext = "" except Exception as cause: print(("init keybinding error: \n", str(cause))) self.display = None return None def is_hotkey(self, key, modifier): keymatch = False modmatch = False modifier = modifier & ~Gdk.ModifierType.SUPER_MASK modint = int(modifier) if self.get_keycode(key) == self.keycode or self.get_keycode( key) == 134: keymatch = True for ignored_mask in self.ignored_masks: if self.modifiers | ignored_mask == modint | ignored_mask: modmatch = True break return keymatch and modmatch def map_modifiers(self): gdk_modifiers = (Gdk.ModifierType.CONTROL_MASK, Gdk.ModifierType.SHIFT_MASK, Gdk.ModifierType.MOD1_MASK, Gdk.ModifierType.MOD2_MASK, Gdk.ModifierType.MOD3_MASK, Gdk.ModifierType.MOD4_MASK, Gdk.ModifierType.MOD5_MASK, Gdk.ModifierType.SUPER_MASK, Gdk.ModifierType.HYPER_MASK) self.known_modifiers_mask = 0 for modifier in gdk_modifiers: if "Mod" not in Gtk.accelerator_name( 0, modifier) or "Mod4" in Gtk.accelerator_name( 0, modifier): self.known_modifiers_mask |= modifier def get_keycode(self, keyval): return self.keymap.get_entries_for_keyval(keyval).keys[0].keycode def grab(self, key): if self.display == None: return False accelerator = key accelerator = accelerator.replace("<Super>", "<Mod4>") keyval, modifiers = Gtk.accelerator_parse(accelerator) if not accelerator or (not keyval and not modifiers): self.keycode = None self.modifiers = None return False self.keytext = key self.keycode = self.get_keycode(keyval) self.modifiers = int(modifiers) catch = error.CatchError(error.BadAccess) for ignored_mask in self.ignored_masks: mod = modifiers | ignored_mask result = self.window.grab_key(self.keycode, mod, True, X.GrabModeAsync, X.GrabModeSync, onerror=catch) result = self.window.grab_key(134, mod, True, X.GrabModeAsync, X.GrabModeSync, onerror=catch) self.display.flush() # sync has been blocking. Don't know why. #self.display.sync() if catch.get_error(): return False return True def ungrab(self): if self.display == None: return if self.keycode: self.window.ungrab_key(self.keycode, X.AnyModifier, self.window) self.window.ungrab_key(134, X.AnyModifier, self.window) def rebind(self, key): self.ungrab() if key != "": self.grab(key) else: self.keytext = "" def set_focus_window(self, window=None): if self.display == None: return self.ungrab() if window is None: self.window = self.screen.root else: self.window = self.display.create_resource_object( "window", window.get_xid()) self.grab(self.keytext) def get_mask_combinations(self, mask): return [x for x in range(mask + 1) if not (x & ~mask)] def idle(self): self.emit("activate") return False def activate(self): GLib.idle_add(self.run) def run(self): if self.display == None: return self.running = True wait_for_release = False showdesktop = True while self.running: event = self.display.next_event() try: self.current_event_time = event.time if (event.detail == self.keycode and event.type == X.KeyPress and not wait_for_release) or ( event.detail == 134 and event.type == X.KeyPress and not wait_for_release): modifiers = event.state & self.known_modifiers_mask if modifiers == self.modifiers: wait_for_release = True self.display.allow_events(X.SyncKeyboard, event.time) else: self.display.allow_events(X.ReplayKeyboard, event.time) elif (event.detail == self.keycode and wait_for_release) or (event.detail == 134 and wait_for_release): if event.type == X.KeyRelease: wait_for_release = False GLib.idle_add(self.idle) self.display.allow_events(X.SyncKeyboard, event.time) elif event.detail == 40 and event.type == X.KeyPress: #super+d self.display.allow_events(X.SyncKeyboard, event.time) elif event.detail == 40 and event.type == X.KeyRelease: #super+d if showdesktop: self.showscreen.toggle_showing_desktop(True) showdesktop = False else: self.showscreen.toggle_showing_desktop(False) showdesktop = True self.display.allow_events(X.ReplayKeyboard, event.time) elif event.detail == 33 and event.type == X.KeyPress: #super+p self.display.allow_events(X.SyncKeyboard, event.time) elif event.detail == 33 and event.type == X.KeyRelease: #super+p self.display.allow_events(X.ReplayKeyboard, event.time) elif event.detail == 26 and event.type == X.KeyPress: #super+e self.display.allow_events(X.SyncKeyboard, event.time) elif event.detail == 26 and event.type == X.KeyRelease: #super+e os.system("peony &") self.display.allow_events(X.ReplayKeyboard, event.time) else: self.display.allow_events(X.ReplayKeyboard, event.time) except AttributeError: continue def stop(self): self.running = False self.ungrab() self.display.close()
class GlobalKeyBinding(GObject.GObject, threading.Thread): __gsignals__ = { 'activate': (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self): GObject.GObject.__init__(self) threading.Thread.__init__(self) self.setDaemon(True) self.keymap = capi.get_widget(gdk.gdk_keymap_get_default()) self.display = Display() self.screen = self.display.screen() self.window = self.screen.root self.ignored_masks = self.get_mask_combinations(X.LockMask | X.Mod2Mask | X.Mod5Mask) self.map_modifiers() self.raw_keyval = None self.keytext = "" def is_hotkey(self, key, modifier): keymatch = False modmatch = False modifier = modifier & ~Gdk.ModifierType.SUPER_MASK modint = int(modifier) if self.get_keycode(key) == self.keycode: keymatch = True for ignored_mask in self.ignored_masks: if self.modifiers | ignored_mask == modint | ignored_mask: modmatch = True break return keymatch and modmatch def map_modifiers(self): gdk_modifiers = (Gdk.ModifierType.CONTROL_MASK, Gdk.ModifierType.SHIFT_MASK, Gdk.ModifierType.MOD1_MASK, Gdk.ModifierType.MOD2_MASK, Gdk.ModifierType.MOD3_MASK, Gdk.ModifierType.MOD4_MASK, Gdk.ModifierType.MOD5_MASK, Gdk.ModifierType.SUPER_MASK, Gdk.ModifierType.HYPER_MASK) self.known_modifiers_mask = 0 for modifier in gdk_modifiers: if "Mod" not in Gtk.accelerator_name( 0, modifier) or "Mod4" in Gtk.accelerator_name( 0, modifier): self.known_modifiers_mask |= modifier def get_keycode(self, keyval): count = c_int() array = (KeymapKey * 10)() keys = cast(array, POINTER(KeymapKey)) gdk.gdk_keymap_get_entries_for_keyval(hash(self.keymap), keyval, byref(keys), byref(count)) return keys[0].keycode def grab(self, key): accelerator = key accelerator = accelerator.replace("<Super>", "<Mod4>") keyval, modifiers = Gtk.accelerator_parse(accelerator) if not accelerator or (not keyval and not modifiers): self.keycode = None self.modifiers = None return False self.keytext = key self.keycode = self.get_keycode(keyval) self.modifiers = int(modifiers) catch = error.CatchError(error.BadAccess) for ignored_mask in self.ignored_masks: mod = modifiers | ignored_mask result = self.window.grab_key(self.keycode, mod, True, X.GrabModeAsync, X.GrabModeSync, onerror=catch) self.display.flush() # sync has been blocking. Don't know why. #self.display.sync() if catch.get_error(): return False return True def ungrab(self): if self.keycode: self.window.ungrab_key(self.keycode, X.AnyModifier, self.window) def rebind(self, key): self.ungrab() if key != "": self.grab(key) else: self.keytext = "" def set_focus_window(self, window=None): self.ungrab() if window is None: self.window = self.screen.root else: self.window = self.display.create_resource_object( "window", gdk.gdk_x11_drawable_get_xid(hash(window))) self.grab(self.keytext) def get_mask_combinations(self, mask): return [x for x in xrange(mask + 1) if not (x & ~mask)] def idle(self): self.emit("activate") return False def activate(self): GLib.idle_add(self.run) def run(self): self.running = True wait_for_release = False while self.running: event = self.display.next_event() try: self.current_event_time = event.time if event.detail == self.keycode and event.type == X.KeyPress and not wait_for_release: modifiers = event.state & self.known_modifiers_mask if modifiers == self.modifiers: wait_for_release = True self.display.allow_events(X.AsyncKeyboard, event.time) else: self.display.allow_events(X.ReplayKeyboard, event.time) elif event.detail == self.keycode and wait_for_release: if event.type == X.KeyRelease: wait_for_release = False GLib.idle_add(self.idle) self.display.allow_events(X.AsyncKeyboard, event.time) else: self.display.allow_events(X.ReplayKeyboard, event.time) except AttributeError: continue def stop(self): self.running = False self.ungrab() self.display.close()
class GlobalKeyBinding(GObject.GObject, threading.Thread): __gsignals__ = { 'activate': (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self): GObject.GObject.__init__ (self) threading.Thread.__init__ (self) self.setDaemon (True) self.keymap = Gdk.Keymap().get_default() self.display = Display() self.screen = self.display.screen() self.window = self.screen.root self.ignored_masks = self.get_mask_combinations(X.LockMask | X.Mod2Mask | X.Mod5Mask) self.map_modifiers() self.raw_keyval = None self.keytext = "" def map_modifiers(self): gdk_modifiers =(Gdk.ModifierType.CONTROL_MASK, Gdk.ModifierType.SHIFT_MASK, Gdk.ModifierType.MOD1_MASK, Gdk.ModifierType.MOD2_MASK, Gdk.ModifierType.MOD3_MASK, Gdk.ModifierType.MOD4_MASK, Gdk.ModifierType.MOD5_MASK, Gdk.ModifierType.SUPER_MASK, Gdk.ModifierType.HYPER_MASK) self.known_modifiers_mask = 0 for modifier in gdk_modifiers: if "Mod" not in Gtk.accelerator_name(0, modifier) or "Mod4" in Gtk.accelerator_name(0, modifier): self.known_modifiers_mask |= modifier def grab(self, key): accelerator = key accelerator = accelerator.replace("<Super>", "<Mod4>") keyval, modifiers = Gtk.accelerator_parse(accelerator) if not accelerator or (not keyval and not modifiers): self.keycode = None self.modifiers = None return False self.keytext = key try: self.keycode = self.keymap.get_entries_for_keyval(keyval).keys[0].keycode except: # In Betsy, the get_entries_for_keyval() returns an unamed tuple... self.keycode = self.keymap.get_entries_for_keyval(keyval)[1][0].keycode self.modifiers = int(modifiers) catch = error.CatchError(error.BadAccess) for ignored_mask in self.ignored_masks: mod = modifiers | ignored_mask result = self.window.grab_key(self.keycode, mod, True, X.GrabModeAsync, X.GrabModeAsync, onerror=catch) self.display.flush() # sync has been blocking. Don't know why. #self.display.sync() if catch.get_error(): return False return True def ungrab(self): if self.keycode: self.window.ungrab_key(self.keycode, X.AnyModifier, self.window) def rebind(self, key): self.ungrab() if key != "": self.grab(key) else: self.keytext = "" def set_focus_window(self, window = None): self.ungrab() if window is None: self.window = self.screen.root else: self.window = self.display.create_resource_object("window", window.get_xid()) self.grab(self.keytext) def get_mask_combinations(self, mask): return [x for x in xrange(mask+1) if not (x & ~mask)] def idle(self): self.emit("activate") return False def activate(self): GLib.idle_add(self.run) def run(self): self.running = True wait_for_release = False while self.running: event = self.display.next_event() try: self.current_event_time = event.time if event.detail == self.keycode and event.type == X.KeyPress and not wait_for_release: modifiers = event.state & self.known_modifiers_mask if modifiers == self.modifiers: wait_for_release = True self.display.allow_events(X.AsyncKeyboard, event.time) else: self.display.allow_events(X.ReplayKeyboard, event.time) elif event.detail == self.keycode and wait_for_release: if event.type == X.KeyRelease: wait_for_release = False GLib.idle_add(self.idle) self.display.allow_events(X.AsyncKeyboard, event.time) else: self.display.send_event(self.window, event, X.KeyPressMask | X.KeyReleaseMask, True) self.display.allow_events(X.ReplayKeyboard, event.time) wait_for_release = False except AttributeError: continue def stop(self): self.running = False self.ungrab() self.display.close()
class Window(object): def __init__(self, win_id): self.dpy = Display() self.screen = self.dpy.screen() self.root = self.screen.root self._hwnd = self.dpy.create_resource_object('window', win_id) def set_position(self, x, y, width, height): """Set window top-left corner position and size""" self._hwnd.configure(x=x, y=y, width=width, height=height) self.dpy.sync() def move(self, x, y): """Move window top-left corner to position""" self._hwnd.configure(x=x, y=y) self.dpy.sync() def resize(self, width, height): """Change window size""" self._hwnd.configure(width=width, height=height) self.dpy.sync() def maximize(self): win_send_event(self.root, self._hwnd, NET_WM_STATE, [ NET_WM_STATE_TOGGLE, NET_WM_STATE_MAXIMIZED_HORZ, NET_WM_STATE_MAXIMIZED_VERT ]) self.dpy.sync() def set_foreground(self): self._hwnd.set_input_focus(X.RevertToParent, X.CurrentTime) win_send_event(self.root, self._hwnd, NET_WM_STATE, [NET_WM_STATE_ADD, NET_WM_STATE_ABOVE]) self.dpy.sync() def minimize(self): win_send_event(self.root, self._hwnd, WM_CHANGE_STATE, [Xutil.IconicState]) self.dpy.sync() def restore(self): self._hwnd.map() self.dpy.sync() def close(self): win_send_event(self.root, self._hwnd, NET_CLOSE_WINDOW, []) self.dpy.sync() def get_position(self): # get_geometry() in KDE5 always returns zero x and y coords = self._hwnd.translate_coords(self.root, 0, 0) # translate_coords always returns inverted coordinates if coords.x < 0: x = abs(coords.x) else: x = coords.x * -1 if coords.y < 0: y = abs(coords.y) else: y = coords.y * -1 return x, y
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 __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)
class Manager(): def __init__(self, inkscape_id): self.id = inkscape_id self.disp = Display() self.screen = self.disp.screen() self.root = self.screen.root self.inkscape = self.disp.create_resource_object('window', inkscape_id) self.mode = normal_mode def event(self, name, detail, state): return name(time=X.CurrentTime, root=self.root, window=self.inkscape, same_screen=0, child=Xlib.X.NONE, root_x=0, root_y=0, event_x=0, event_y=0, state=state, detail=detail) def string_to_keycode(self, key): keysym = XK.string_to_keysym(key) keycode = self.disp.keysym_to_keycode(keysym) return keycode def press(self, key, mask=X.NONE): keycode = self.string_to_keycode(key) self.inkscape.send_event(self.event(event.KeyPress, keycode, mask), propagate=True) self.inkscape.send_event(self.event(event.KeyRelease, keycode, mask), propagate=True) self.disp.flush() self.disp.sync() def grab(self): self.inkscape.grab_key(X.AnyKey, X.AnyModifier, True, X.GrabModeAsync, X.GrabModeAsync) # Ungrab window manager shortcuts (Super + ...) self.inkscape.ungrab_key(self.string_to_keycode('Super'), X.AnyModifier, True) self.inkscape.ungrab_key(self.string_to_keycode('Alt_L'), X.AnyModifier, True) self.inkscape.ungrab_key(self.string_to_keycode('Shift_R'), X.AnyModifier, True) self.inkscape.change_attributes(event_mask=X.KeyReleaseMask | X.KeyPressMask | X.StructureNotifyMask) def ungrab(self): self.inkscape.ungrab_key(X.AnyKey, X.AnyModifier, True) def listen(self): self.grab() while True: evt = self.disp.next_event() if evt.type in [X.KeyPress, X.KeyRelease]: keycode = evt.detail keysym = self.disp.keycode_to_keysym(keycode, 0) char = XK.keysym_to_string(keysym) self.disp.allow_events(X.ReplayKeyboard, X.CurrentTime) self.mode(self, evt, char) if evt.type == X.DestroyNotify: if evt.window.id == self.id: self.ungrab() return
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)
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
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 GlobalKeyBinding(GObject.GObject, threading.Thread): __gsignals__ = { 'activate': (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self): GObject.GObject.__init__(self) threading.Thread.__init__(self) self.setDaemon(True) self.keymap = Gdk.Keymap().get_default() self.display = Display() self.screen = self.display.screen() self.window = self.screen.root self.ignored_masks = self.get_mask_combinations(X.LockMask | X.Mod2Mask | X.Mod5Mask) self.map_modifiers() self.raw_keyval = None self.keytext = "" def map_modifiers(self): gdk_modifiers = (Gdk.ModifierType.CONTROL_MASK, Gdk.ModifierType.SHIFT_MASK, Gdk.ModifierType.MOD1_MASK, Gdk.ModifierType.MOD2_MASK, Gdk.ModifierType.MOD3_MASK, Gdk.ModifierType.MOD4_MASK, Gdk.ModifierType.MOD5_MASK, Gdk.ModifierType.SUPER_MASK, Gdk.ModifierType.HYPER_MASK) self.known_modifiers_mask = 0 for modifier in gdk_modifiers: if "Mod" not in Gtk.accelerator_name( 0, modifier) or "Mod4" in Gtk.accelerator_name( 0, modifier): self.known_modifiers_mask |= modifier def grab(self, key): accelerator = key accelerator = accelerator.replace("<Super>", "<Mod4>") keyval, modifiers = Gtk.accelerator_parse(accelerator) if not accelerator or (not keyval and not modifiers): self.keycode = None self.modifiers = None return False self.keytext = key try: self.keycode = self.keymap.get_entries_for_keyval( keyval).keys[0].keycode except AttributeError: # In older Gtk3 the get_entries_for_keyval() returns an unnamed tuple... self.keycode = self.keymap.get_entries_for_keyval( keyval)[1][0].keycode self.modifiers = int(modifiers) # Request to receive key press/release reports from other windows that may not be using modifiers catch = error.CatchError(error.BadWindow) if self.modifiers: self.window.change_attributes(onerror=catch, event_mask=X.KeyPressMask | X.KeyReleaseMask) else: self.window.change_attributes(onerror=catch, event_mask=X.NoEventMask) if catch.get_error(): return False catch = error.CatchError(error.BadAccess) for ignored_mask in self.ignored_masks: mod = modifiers | ignored_mask result = self.window.grab_key(self.keycode, mod, True, X.GrabModeAsync, X.GrabModeAsync, onerror=catch) self.display.flush() if catch.get_error(): return False catch = error.CatchError(error.BadCursor) if not self.modifiers: # We grab Super+click so that we can forward it to the window manager and allow Super+click bindings (window move, resize, etc.) self.window.grab_button(X.AnyButton, X.Mod4Mask, True, X.ButtonPressMask, X.GrabModeSync, X.GrabModeAsync, X.NONE, X.NONE) self.display.flush() if catch.get_error(): return False return True def ungrab(self): if self.keycode: self.window.ungrab_key(self.keycode, X.AnyModifier, self.window) def rebind(self, key): self.ungrab() if key != "": self.grab(key) else: self.keytext = "" def set_focus_window(self, window=None): self.ungrab() if window is None: self.window = self.screen.root else: self.window = self.display.create_resource_object( "window", window.get_xid()) self.grab(self.keytext) def get_mask_combinations(self, mask): return [x for x in range(mask + 1) if not (x & ~mask)] def idle(self): self.emit("activate") return False def activate(self): GLib.idle_add(self.run) # Get which window manager we're currently using (Marco, Compiz, Metacity, etc...) def get_wm(self): name = '' wm_check = self.display.get_atom('_NET_SUPPORTING_WM_CHECK') win_id = self.window.get_full_property(wm_check, X.AnyPropertyType) if win_id: w = self.display.create_resource_object("window", win_id.value[0]) wm_name = self.display.get_atom('_NET_WM_NAME') prop = w.get_full_property(wm_name, X.AnyPropertyType) if prop: name = prop.value return name.lower() def run(self): self.running = True wait_for_release = False while self.running: event = self.display.next_event() if self.modifiers: # Use simpler logic when using traditional combined keybindings modifiers = event.state & self.known_modifiers_mask if event.type == X.KeyPress and event.detail == self.keycode and modifiers == self.modifiers: GLib.idle_add(self.idle) else: try: # KeyPress if event.type == X.KeyPress and event.detail == self.keycode and not wait_for_release: modifiers = event.state & self.known_modifiers_mask if modifiers == self.modifiers: wait_for_release = True # KeyRelease elif event.type == X.KeyRelease and event.detail == self.keycode and wait_for_release: GLib.idle_add(self.idle) wait_for_release = False # Modifiers are often used with mouse events - don't let the system swallow those elif event.type == X.ButtonPress: self.display.allow_events(X.ReplayPointer, X.CurrentTime) # Compiz would rather not have the event sent to it and just read it from the replayed queue wm = self.get_wm() if wm != "compiz": self.display.ungrab_keyboard(X.CurrentTime) self.display.ungrab_pointer(X.CurrentTime) query_pointer = self.window.query_pointer() self.display.send_event(query_pointer.child, event, X.ButtonPressMask, True) wait_for_release = False # If the user presses another key in between the KeyPress and the KeyRelease, they # meant to use a different shortcut else: self.display.ungrab_keyboard(X.CurrentTime) # Send the event up in case another window is listening to it self.display.send_event( event.window, event, X.KeyPressMask | X.KeyReleaseMask, True) wait_for_release = False except AttributeError: continue def stop(self): self.running = False self.ungrab() self.display.close()