[Xmonad] patch: wmdock script - manage dockable windows using dmenu

Krzysztof Kościuszkiewicz k.kosciuszkiewicz at gmail.com
Mon May 28 21:30:47 EDT 2007


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 at jabberpl.org
"Simplicity is the ultimate sophistication"
                        -- Leonardo da Vinci
-------------- next part --------------

New patches:

[Added wmdock application
Krzysztof Kosciuszkiewicz <k.kosciuszkiewicz at 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 at 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 at cse.unl.edu>**20070528050656] 
[replace "name" in NamedWindow with a Show instance.
David Roundy <droundy at darcs.net>**20070526185114] 
[[Spiral] blend in the scale factor so it doesn't have any effect on the smallest windows
joe.thornber at gmail.com**20070525032732] 
[[Spiral] last rect takes all available space
joe.thornber at gmail.com**20070524120239] 
[[Spiral] Introduce a simpler Rect data type to remove a lot of the fromIntegrals
joe.thornber at gmail.com**20070524100423] 
[[Spiral] divideRects now takes a list of directions to split in
joe.thornber at gmail.com**20070524090211] 
[[Spiral] misc tidying
joe.thornber at gmail.com**20070524085537] 
[[Spiral] remove old spiral code
joe.thornber at gmail.com**20070524084805] 
[[Spiral] add fibonacci spiral
joe.thornber at gmail.com**20070524084423] 
[Allow clients of NamedWindows to get at the name.
glasser at mit.edu**20070523184251] 
[dzen module (with xinerama support, which requires glasser's Xinerama patch to dzen)
glasser at mit.edu**20070523184315] 
[Extract NamedWindow support from Mosaic into its own module
glasser at mit.edu**20070523155855] 
[remove SwapFocus (which is no longer possible)
David Roundy <droundy at darcs.net>**20070523153841
 This module depended on the focus stack.
] 
[Fix Spiral's module name
Spencer Janssen <sjanssen at cse.unl.edu>**20070522170909] 
[[SPIRAL] add spiral tiling layout
joe.thornber at gmail.com**20070522062537] 
[Make RotView compile.
Miikka Koskinen <arcatan at 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 at kapsi.fi>**20070522062118] 
[make FindEmptyWorkspace compile
Miikka Koskinen <arcatan at kapsi.fi>**20070521123239] 
[make DwmPromote compile
Miikka Koskinen <arcatan at kapsi.fi>**20070521123140] 
[updated Dmenu.hs to work with zipper StackSet
Jason Creighton <jcreigh at gmail.com>**20070521233947] 
[Add GreedyView
Spencer Janssen <sjanssen at cse.unl.edu>**20070521220048] 
[Rescreen: collects new screen information
Spencer Janssen <sjanssen at cse.unl.edu>**20070521164808] 
[Fixes for windowset -> workspace rename
Spencer Janssen <sjanssen at cse.unl.edu>**20070521042118] 
[TwoPane: hide windows that aren't in view
Spencer Janssen <sjanssen at cse.unl.edu>**20070518224240] 
[make Mosaic even less picky by default.
David Roundy <droundy at darcs.net>**20070516175554] 
[add clear window message in Mosaic.
David Roundy <droundy at darcs.net>**20070516175518] 
[Comment only
Spencer Janssen <sjanssen at cse.unl.edu>**20070517211003] 
[Add instructions for TwoPane
Spencer Janssen <sjanssen at cse.unl.edu>**20070517210206] 
[Add TwoPane
Spencer Janssen <sjanssen at cse.unl.edu>**20070517195618] 
[throttle the exponential expense when many windows are present.
David Roundy <droundy at darcs.net>**20070516022123] 
[make mosaic configure windows by name rather than by Window.
David Roundy <droundy at 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 at kapsi.fi>**20070513184338
 
 With this module you can find empty workspaces, view them and tag windows to
 them.
] 
[make DwmPromote compile
Miikka Koskinen <arcatan at kapsi.fi>**20070513184254] 
[make DwmPromote compile again
Miikka Koskinen <arcatan at kapsi.fi>**20070510154158] 
[make DwmPromote compile
Miikka Koskinen <arcatan at kapsi.fi>**20070503105236] 
[add SwapFocus.
David Roundy <droundy at darcs.net>**20070512191315] 
[make rotView only consider non-visible workspaces (Xinerama)
Jason Creighton <jcreigh at gmail.com>**20070510012059] 
[fix commend in RotView.
David Roundy <droundy at darcs.net>**20070505185654] 
[switch to Message type for layout messages
Don Stewart <dons at cse.unsw.edu.au>**20070505014332] 
[Fix instructions in Mosaic.
Chris Mears <chris at cmears.id.au>**20070503222345] 
[add Mosaic layout.
David Roundy <droundy at darcs.net>**20070503151024] 
[-Wall police
Spencer Janssen <sjanssen at cse.unl.edu>**20070503211700] 
[Make RotView build, and add a brief description.
Chris Mears <chris at cmears.id.au>**20070503104234] 
[comment: Gave URL to xinerama-enabled dmenu patch
Jason Creighton <jcreigh at gmail.com>**20070503053133] 
[Put dmenu in X too
Spencer Janssen <sjanssen at cse.unl.edu>**20070503053727] 
[Add dmenu (thanks jcreigh)
Spencer Janssen <sjanssen at cse.unl.edu>**20070503052225] 
[add RotView module.
David Roundy <droundy at darcs.net>**20070421233838] 
[XMonadContrib.DwmPromote: dwm-like promote
Miikka Koskinen <arcatan at 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 at cse.unsw.edu.au>**20070429064013] 
[more details
Don Stewart <dons at cse.unsw.edu.au>**20070429061426] 
[add readme
Don Stewart <dons at cse.unsw.edu.au>**20070429061329] 
[Initial import of xmonad contributions
Don Stewart <dons at cse.unsw.edu.au>**20070429061150] 
Patch bundle hash:
8c693213500612db37f8411c2498d77d9268dcd9


More information about the Xmonad mailing list