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