patch: wmdock script - manage dockable windows using dmenu
Hello, I'm really enyoing the Xmonad experience so far, but inability to use applications that insist on docking was a drawback to me. So I did my firsts steps in the Xlib programming and here are the results. The hack is really crude; script identifies windows that look like they should be docked and optionally sends mouse events to them (most of such apps will raise the main window after such treatment). I hope someone will find it useful. Cheers, -- Krzysztof Kościuszkiewicz, Gadu: 111851, Jabber: kokr@jabberpl.org "Simplicity is the ultimate sophistication" -- Leonardo da Vinci New patches: [Added wmdock application Krzysztof Kosciuszkiewicz <k.kosciuszkiewicz@gmail.com>**20070529011349] { adddir ./examples/dmenu-wmdock addfile ./examples/dmenu-wmdock/README hunk ./examples/dmenu-wmdock/README 1 +Use dmenu to "manage" windows that belong to WM-style docks. + +wmdock + A Python script using Xlib bindings to query and send mouse button events + to windows that seem to be dockable. Probably should be rewritten in + haskell ;) + +Config.hs + Add something similar to setup keybindings for wmdock and dmenu: + + , ((modMask, xK_t ), spawn "wmdock -n | dmenu | wmdock send") + + mod-t pops up a menu with all dockable windows. Selecting one will send a + double-button1 click to it (hopefully raising/lowering main app window). + Modify wmdock to suit your taste. + +dmenu is available from + http://www.suckless.org/wiki/tools/xlib + +python Xlib bindings are available from + http://python-xlib.sf.net/ addfile ./examples/dmenu-wmdock/wmdock hunk ./examples/dmenu-wmdock/wmdock 1 +#! /usr/bin/env python + +__version__ = "0.2" +__author__ = "Krzysztof Kosciuszkiewicz <k.kosciuszkiewicz@gmail.com>" +__program__ = "wmdock" +__license__ = "BSD" + +import Xlib +import Xlib.X +import Xlib.Xatom +import Xlib.Xutil +import Xlib.display +import Xlib.protocol +import sys +import time + +# globals +disp = Xlib.display.Display() +scrn = disp.screen() +root = scrn.root + +# the atoms that we will need to query for +atom_wm_type = disp.intern_atom("_NET_WM_WINDOW_TYPE") +atom_wm_type_dock = disp.intern_atom("_NET_WM_WINDOW_TYPE_DOCK") +atom_wm_kde_dock = disp.intern_atom("_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR") + +def get_property(win, prop, prop_type=Xlib.X.AnyPropertyType): + return win.get_full_property(prop, prop_type) + +def is_dock(win): + """Check if the X window is a dock app""" + # everything in this function is based on code from Ion3 WM + + # test window manager hints + hints = win.get_wm_hints() + if hints: + init_state = Xlib.Xutil.NormalState + if hints.flags & Xlib.Xutil.StateHint: + init_state = hints.initial_state + if init_state == Xlib.Xutil.WithdrawnState and \ + hints.flags & Xlib.Xutil.IconWindowHint and hints.icon_window: + return True + + # check if window has WM_WINDOW_TYPE_DOCK as WM_WINDOW_TYPE property + prop = get_property(win, atom_wm_type) + if prop: + # type should be atom, and value should contain the atom type_dock + if prop.format == Xlib.Xatom.ATOM and \ + atom_wm_type_dock in prop.value: + return True + + # test window manager class + # also all Qt dockable windows I've seen satisfy cls[0].endswith('dock') + cls = win.get_wm_class() + if cls and cls[1] == "DockApp": + # or cls[0].endswith('dock'): + return True + + # test if it's kde-style dockapp + if get_property(win, atom_wm_kde_dock): + return True + + return False + + +def send_mouse_click(window, button=Xlib.X.Button1, double=False, delay=500): + """Send button press event followed by button release event to client""" + # I hope that translation of coordinates is ok here. + coords = root.translate_coords(window, 4, 4) + ev1_dict = dict( + time=0, + root=root, + window=window, + same_screen=1, + child=Xlib.X.NONE, + root_x=coords.x, + root_y=coords.y, + event_x=4, + event_y=4, + state=Xlib.X.NONE, + detail=button) + ev2_dict = ev1_dict.copy() + ev2_dict['state'] = button << 8 # that is ButtonXMask + ev1 = Xlib.protocol.event.ButtonPress(**ev1_dict) + ev2 = Xlib.protocol.event.ButtonRelease(**ev2_dict) + window.send_event(ev1) + disp.sync() + time.sleep(delay/1000) + window.send_event(ev2) + disp.sync() + if double: + time.sleep(delay/1000) + window.send_event(ev1) + disp.sync() + time.sleep(delay/1000) + window.send_event(ev2) + disp.sync() + + +def walk_tree(win=root, func=None): + """Walk the X window hierarchy and call func on each window""" + if callable(func) and win: + func(win) + if win: + res = win.query_tree() + if res: + for chld in res.children: + walk_tree(chld, func=func) + return func + + +class WindowVisitor(object): + """Visits windows and collects ones that match predicate""" + def __init__(self, condition=None): + self.winlist = [] + self.condition = condition + + def __call__(self, window): + if callable(self.condition) and self.condition(window): + self.winlist.append(window) + + def collected(self): + return self.winlist + + +if __name__ == "__main__": + import getopt + import string + import os + + usage = """Usage: %s [options...] [list|send {ids|names...}] + """ % __program__ + helpmsg = """Options: + -n list only window names (default: False) + -i list only window IDs (default: False) + -b button mouse button number in event to be sent (default: 1) + -d delay delay between mouse press and release events (default: 500ms) + -s send single-click events (default: False) + """ + behaviour = """Operation: + If you tell me to "list", I will find and print IDs and names of dockable + X11 windows on your display. If you tell me to "send", I will send button + click events to each of specified windows (use names or IDs). If you + don't specify any after "send" I will try to read them from standard + input. + + You can combine "list" and "send". By default I just "list". + """ + try: + opts, args = getopt.gnu_getopt(sys.argv[1:], + "b:d:sinh", ["help", "version"]) + except getopt.GetoptError, e: + print >>sys.stderr, e + print >>sys.stderr, usage + sys.exit(0) + + button = Xlib.X.Button1 + delay = 500 + double = True + window_repr = lambda win: "%-10s %s" % (win.get_wm_name(), hex(int(win.id))) + for o, val in opts: + if o == '-b': + button = Xlib.X.Button1 + int(val) - 1 + elif o == '-d': + delay = int(val) + elif o == '-s': + double = False + elif o == '-i': + window_repr = lambda win: hex(int(win.id)) + elif o == '-n': + window_repr = lambda win: win.get_wm_name() + elif o == '--help': + print usage + print helpmsg + print behaviour + sys.exit(0) + elif o == '--version': + print "%s %s" % (__program__, __version__) + sys.exit(0) + + do_print = True + do_send = False + get_ids = lambda: [] + + if not args or args[0] == "list": + args = args[1:] + else: + do_print = False + + if args and args[0] == "send": + args = args[1:] + do_send = True + if args: + get_ids, args = lambda: args, [] + else: + get_ids = lambda: map(string.strip, sys.stdin.read().split()) + + if args: + print >>sys.stderr, usage + sys.exit(0) + + windows = walk_tree(func=WindowVisitor(is_dock)).collected() + + if do_print: + for win in windows: + print window_repr(win) + sys.stdout.flush() + + if do_send: + for word in get_ids(): + try: + winid = int(word, 16) + finder = lambda w: w.id == winid + except: + finder = lambda w: w.get_wm_name() == word + + for win in filter(finder, windows): + send_mouse_click(win, button=button, delay=delay, double=double) + + sys.exit(0) } Context: [Rescreen is in main xmonad now Spencer Janssen <sjanssen@cse.unl.edu>**20070528050656] [replace "name" in NamedWindow with a Show instance. David Roundy <droundy@darcs.net>**20070526185114] [[Spiral] blend in the scale factor so it doesn't have any effect on the smallest windows joe.thornber@gmail.com**20070525032732] [[Spiral] last rect takes all available space joe.thornber@gmail.com**20070524120239] [[Spiral] Introduce a simpler Rect data type to remove a lot of the fromIntegrals joe.thornber@gmail.com**20070524100423] [[Spiral] divideRects now takes a list of directions to split in joe.thornber@gmail.com**20070524090211] [[Spiral] misc tidying joe.thornber@gmail.com**20070524085537] [[Spiral] remove old spiral code joe.thornber@gmail.com**20070524084805] [[Spiral] add fibonacci spiral joe.thornber@gmail.com**20070524084423] [Allow clients of NamedWindows to get at the name. glasser@mit.edu**20070523184251] [dzen module (with xinerama support, which requires glasser's Xinerama patch to dzen) glasser@mit.edu**20070523184315] [Extract NamedWindow support from Mosaic into its own module glasser@mit.edu**20070523155855] [remove SwapFocus (which is no longer possible) David Roundy <droundy@darcs.net>**20070523153841 This module depended on the focus stack. ] [Fix Spiral's module name Spencer Janssen <sjanssen@cse.unl.edu>**20070522170909] [[SPIRAL] add spiral tiling layout joe.thornber@gmail.com**20070522062537] [Make RotView compile. Miikka Koskinen <arcatan@kapsi.fi>**20070522075338 As I'm not a Xinerama user, I'm not sure if rotView should consider only hidden workspaces or also visible but not focused workspaces. I thought hidden workspaces only would be more logical. ] [bug fix in DwmPromote. whoops. Miikka Koskinen <arcatan@kapsi.fi>**20070522062118] [make FindEmptyWorkspace compile Miikka Koskinen <arcatan@kapsi.fi>**20070521123239] [make DwmPromote compile Miikka Koskinen <arcatan@kapsi.fi>**20070521123140] [updated Dmenu.hs to work with zipper StackSet Jason Creighton <jcreigh@gmail.com>**20070521233947] [Add GreedyView Spencer Janssen <sjanssen@cse.unl.edu>**20070521220048] [Rescreen: collects new screen information Spencer Janssen <sjanssen@cse.unl.edu>**20070521164808] [Fixes for windowset -> workspace rename Spencer Janssen <sjanssen@cse.unl.edu>**20070521042118] [TwoPane: hide windows that aren't in view Spencer Janssen <sjanssen@cse.unl.edu>**20070518224240] [make Mosaic even less picky by default. David Roundy <droundy@darcs.net>**20070516175554] [add clear window message in Mosaic. David Roundy <droundy@darcs.net>**20070516175518] [Comment only Spencer Janssen <sjanssen@cse.unl.edu>**20070517211003] [Add instructions for TwoPane Spencer Janssen <sjanssen@cse.unl.edu>**20070517210206] [Add TwoPane Spencer Janssen <sjanssen@cse.unl.edu>**20070517195618] [throttle the exponential expense when many windows are present. David Roundy <droundy@darcs.net>**20070516022123] [make mosaic configure windows by name rather than by Window. David Roundy <droundy@darcs.net>**20070512215644 Note that this is still pretty flawed. Often window names change, and the layout then stagnates a bit. Gimp, for example, opens most its windows with the same name before renaming them, so you have to hit mod-return or something to force a doLayout. Also, gimp still overrides xmonad regarding the size of its main window. :( ] [XMonadContrib.FindEmptyWorkspace Miikka Koskinen <arcatan@kapsi.fi>**20070513184338 With this module you can find empty workspaces, view them and tag windows to them. ] [make DwmPromote compile Miikka Koskinen <arcatan@kapsi.fi>**20070513184254] [make DwmPromote compile again Miikka Koskinen <arcatan@kapsi.fi>**20070510154158] [make DwmPromote compile Miikka Koskinen <arcatan@kapsi.fi>**20070503105236] [add SwapFocus. David Roundy <droundy@darcs.net>**20070512191315] [make rotView only consider non-visible workspaces (Xinerama) Jason Creighton <jcreigh@gmail.com>**20070510012059] [fix commend in RotView. David Roundy <droundy@darcs.net>**20070505185654] [switch to Message type for layout messages Don Stewart <dons@cse.unsw.edu.au>**20070505014332] [Fix instructions in Mosaic. Chris Mears <chris@cmears.id.au>**20070503222345] [add Mosaic layout. David Roundy <droundy@darcs.net>**20070503151024] [-Wall police Spencer Janssen <sjanssen@cse.unl.edu>**20070503211700] [Make RotView build, and add a brief description. Chris Mears <chris@cmears.id.au>**20070503104234] [comment: Gave URL to xinerama-enabled dmenu patch Jason Creighton <jcreigh@gmail.com>**20070503053133] [Put dmenu in X too Spencer Janssen <sjanssen@cse.unl.edu>**20070503053727] [Add dmenu (thanks jcreigh) Spencer Janssen <sjanssen@cse.unl.edu>**20070503052225] [add RotView module. David Roundy <droundy@darcs.net>**20070421233838] [XMonadContrib.DwmPromote: dwm-like promote Miikka Koskinen <arcatan@kapsi.fi>**20070501082031 I like the way dwm's equivalent to xmonad's promote works, so I implemented dwmpromote. ] [add simple date example Don Stewart <dons@cse.unsw.edu.au>**20070429064013] [more details Don Stewart <dons@cse.unsw.edu.au>**20070429061426] [add readme Don Stewart <dons@cse.unsw.edu.au>**20070429061329] [Initial import of xmonad contributions Don Stewart <dons@cse.unsw.edu.au>**20070429061150] Patch bundle hash: 8c693213500612db37f8411c2498d77d9268dcd9
k.kosciuszkiewicz:
Hello,
I'm really enyoing the Xmonad experience so far, but inability to use applications that insist on docking was a drawback to me. So I did my firsts steps in the Xlib programming and here are the results. The hack is really crude; script identifies windows that look like they should be docked and optionally sends mouse events to them (most of such apps will raise the main window after such treatment).
This is very timely. We added support for docking/status bar clients just a couple of days ago. It should be possible to set an appropriate gap width in your Config.hs, to reserve space for a dock on the edge of the screen. Did you try this? Or does this patch solve a different problem? -- Don
Donald Bruce Stewart wrote:
This is very timely. We added support for docking/status bar clients just a couple of days ago. It should be possible to set an appropriate gap width in your Config.hs, to reserve space for a dock on the edge of the screen. Did you try this?
Or does this patch solve a different problem?
It is not a dock for applications. It just finds dockable apps, reports their names/window IDs and allows you to send X events programmaticaly to them. I've seen the "gaps" support and I've tested it with a lightweight dock named trayer. It is in Debian repositories and also lives on berlios.de: http://developer.berlios.de/project/showfiles.php?group_id=1595 It works fine with xmonad (I had to unmanage the window before I got it working properly). So if you need a dock you can try that one. Cheers, -- Krzysztof Kościuszkiewicz, Gadu: 111851, Jabber: kokr@jabberpl.org "Simplicity is the ultimate sophistication" -- Leonardo da Vinci
k.kosciuszkiewicz:
Donald Bruce Stewart wrote:
This is very timely. We added support for docking/status bar clients just a couple of days ago. It should be possible to set an appropriate gap width in your Config.hs, to reserve space for a dock on the edge of the screen. Did you try this?
Or does this patch solve a different problem?
It is not a dock for applications. It just finds dockable apps, reports their names/window IDs and allows you to send X events programmaticaly to them.
Ah, ok. Very good.
I've seen the "gaps" support and I've tested it with a lightweight dock named trayer. It is in Debian repositories and also lives on berlios.de:
Got a screenshot?
http://developer.berlios.de/project/showfiles.php?group_id=1595 It works fine with xmonad (I had to unmanage the window before I got it working properly). So if you need a dock you can try that one.
Yes, you'd need the trayer to have the override bit set. -- Don
participants (2)
-
dons@cse.unsw.edu.au -
Krzysztof Kościuszkiewicz