incise.org: TinyWM

[ Edit ] [ Info ] [ RecentChanges ]

What it is

TinyWM is a tiny window manager that I created as an exercise in minimalism. It is also maybe helpful in learning some of the very basics of creating a window manager. It is only around 50 lines of C. There is also a Python version using python-xlib.

It lets you do 4 basic things:

  1. Move windows interactively with Alt+Button1 drag (left mouse button)

  2. Resize windows interactively with Alt+Button3 drag (right mouse button)

  3. Raise windows with Alt+F1 (not high on usability I know, but I needed a keybinding in there somewhere)

  4. Focus windows with the mouse pointer (X does this on its own)

Download

Known to be packaged in

  • Debian

  • Ubuntu

  • FreeBSD

  • CRUX

TinyWM around the web

See Also

The source

Here is tinywm.c from the most recent release, 1.3:

   1 /* TinyWM is written by Nick Welch <mack@incise.org>, 2005.
   2  *
   3  * This software is in the public domain
   4  * and is provided AS IS, with NO WARRANTY. */
   5 
   6 #include <X11/Xlib.h>
   7 
   8 #define MAX(a, b) ((a) > (b) ? (a) : (b))
   9 
  10 int main()
  11 {
  12     Display * dpy;
  13     Window root;
  14     XWindowAttributes attr;
  15     XButtonEvent start;
  16     XEvent ev;
  17 
  18     if(!(dpy = XOpenDisplay(0x0))) return 1;
  19 
  20     root = DefaultRootWindow(dpy);
  21 
  22     XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("F1")), Mod1Mask, root,
  23             True, GrabModeAsync, GrabModeAsync);
  24     XGrabButton(dpy, 1, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync,
  25             GrabModeAsync, None, None);
  26     XGrabButton(dpy, 3, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync,
  27             GrabModeAsync, None, None);
  28 
  29     for(;;)
  30     {
  31         XNextEvent(dpy, &ev);
  32         if(ev.type == KeyPress && ev.xkey.subwindow != None)
  33             XRaiseWindow(dpy, ev.xkey.subwindow);
  34         else if(ev.type == ButtonPress && ev.xbutton.subwindow != None)
  35         {
  36             XGrabPointer(dpy, ev.xbutton.subwindow, True,
  37                     PointerMotionMask|ButtonReleaseMask, GrabModeAsync,
  38                     GrabModeAsync, None, None, CurrentTime);
  39             XGetWindowAttributes(dpy, ev.xbutton.subwindow, &attr);
  40             start = ev.xbutton;
  41         }
  42         else if(ev.type == MotionNotify)
  43         {
  44             int xdiff, ydiff;
  45             while(XCheckTypedEvent(dpy, MotionNotify, &ev));
  46             xdiff = ev.xbutton.x_root - start.x_root;
  47             ydiff = ev.xbutton.y_root - start.y_root;
  48             XMoveResizeWindow(dpy, ev.xmotion.window,
  49                 attr.x + (start.button==1 ? xdiff : 0),
  50                 attr.y + (start.button==1 ? ydiff : 0),
  51                 MAX(1, attr.width + (start.button==3 ? xdiff : 0)),
  52                 MAX(1, attr.height + (start.button==3 ? ydiff : 0)));
  53         }
  54         else if(ev.type == ButtonRelease)
  55             XUngrabPointer(dpy, CurrentTime);
  56     }
  57 }
  58 

Here is annotated.c, which is just tinywm.c with a lot of comments explaining what is going on. This should give you a reasonable idea of how everything works.

   1 /* TinyWM is written by Nick Welch <mack@incise.org>, 2005.
   2  *
   3  * This software is in the public domain
   4  * and is provided AS IS, with NO WARRANTY. */
   5 
   6 /* much of tinywm's purpose is to serve as a very basic example of how to do X
   7  * stuff and/or understand window managers, so i wanted to put comments in the
   8  * code explaining things, but i really hate wading through code that is
   9  * over-commented -- and for that matter, tinywm is supposed to be as concise
  10  * as possible, so having lots of comments just wasn't really fitting for it.
  11  * i want tinywm.c to be something you can just look at and go "wow, that's
  12  * it?  cool!"  so what i did was just copy it over to annotated.c and comment
  13  * the hell out of it.  ahh, but now i have to make every code change twice!
  14  * oh well.  i could always use some sort of script to process the comments out
  15  * of this and write it to tinywm.c ... nah.
  16  */
  17 
  18 /* most X stuff will be included with Xlib.h, but a few things require other
  19  * headers, like Xmd.h, keysym.h, etc.
  20  */
  21 #include <X11/Xlib.h>
  22 
  23 #define MAX(a, b) ((a) > (b) ? (a) : (b))
  24 
  25 int main()
  26 {
  27     Display * dpy;
  28     Window root;
  29     XWindowAttributes attr;
  30 
  31     /* we use this to save the pointer's state at the beginning of the
  32      * move/resize.
  33      */
  34     XButtonEvent start;
  35 
  36     XEvent ev;
  37 
  38 
  39     /* return failure status if we can't connect */
  40     if(!(dpy = XOpenDisplay(0x0))) return 1;
  41 
  42     /* you'll usually be referencing the root window a lot.  this is a somewhat
  43      * naive approach that will only work on the default screen.  most people
  44      * only have one screen, but not everyone.  if you run multi-head without
  45      * xinerama then you quite possibly have multiple screens. (i'm not sure
  46      * about vendor-specific implementations, like nvidia's)
  47      *
  48      * many, probably most window managers only handle one screen, so in
  49      * reality this isn't really *that* naive.
  50      *
  51      * if you wanted to get the root window of a specific screen you'd use
  52      * RootWindow(), but the user can also control which screen is our default:
  53      * if they set $DISPLAY to ":0.foo", then our default screen number is
  54      * whatever they specify "foo" as.
  55      */
  56     root = DefaultRootWindow(dpy);
  57 
  58     /* you could also include keysym.h and use the XK_F1 constant instead of
  59      * the call to XStringToKeysym, but this method is more "dynamic."  imagine
  60      * you have config files which specify key bindings.  instead of parsing
  61      * the key names and having a huge table or whatever to map strings to XK_*
  62      * constants, you can just take the user-specified string and hand it off
  63      * to XStringToKeysym.  XStringToKeysym will give you back the appropriate
  64      * keysym or tell you if it's an invalid key name.
  65      *
  66      * a keysym is basically a platform-independent numeric representation of a
  67      * key, like "F1", "a", "b", "L", "5", "Shift", etc.  a keycode is a
  68      * numeric representation of a key on the keyboard sent by the keyboard
  69      * driver (or something along those lines -- i'm no hardware/driver expert)
  70      * to X.  so we never want to hard-code keycodes, because they can and will
  71      * differ between systems.
  72      */
  73     XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("F1")), Mod1Mask, root,
  74             True, GrabModeAsync, GrabModeAsync);
  75 
  76     /* XGrabKey and XGrabButton are basically ways of saying "when this
  77      * combination of modifiers and key/button is pressed, send me the events."
  78      * so we can safely assume that we'll receive Alt+F1 events, Alt+Button1
  79      * events, and Alt+Button3 events, but no others.  You can either do
  80      * individual grabs like these for key/mouse combinations, or you can use
  81      * XSelectInput with KeyPressMask/ButtonPressMask/etc to catch all events
  82      * of those types and filter them as you receive them.
  83      */
  84     XGrabButton(dpy, 1, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync,
  85             GrabModeAsync, None, None);
  86     XGrabButton(dpy, 3, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync,
  87             GrabModeAsync, None, None);
  88 
  89     for(;;)
  90     {
  91         /* this is the most basic way of looping through X events; you can be
  92          * more flexible by using XPending(), or ConnectionNumber() along with
  93          * select() (or poll() or whatever floats your boat).
  94          */
  95         XNextEvent(dpy, &ev);
  96 
  97         /* this is our keybinding for raising windows.  as i saw someone
  98          * mention on the ratpoison wiki, it is pretty stupid; however, i
  99          * wanted to fit some sort of keyboard binding in here somewhere, and
 100          * this was the best fit for it.
 101          *
 102          * i was a little confused about .window vs. .subwindow for a while,
 103          * but a little RTFMing took care of that.  our passive grabs above
 104          * grabbed on the root window, so since we're only interested in events
 105          * for its child windows, we look at .subwindow.  when subwindow ==
 106          * None, that means that the window the event happened in was the same
 107          * window that was grabbed on -- in this case, the root window.
 108          */
 109         if(ev.type == KeyPress && ev.xkey.subwindow != None)
 110             XRaiseWindow(dpy, ev.xkey.subwindow);
 111         else if(ev.type == ButtonPress && ev.xbutton.subwindow != None)
 112         {
 113             /* now we take command of the pointer, looking for motion and
 114              * button release events.
 115              */
 116             XGrabPointer(dpy, ev.xbutton.subwindow, True,
 117                     PointerMotionMask|ButtonReleaseMask, GrabModeAsync,
 118                     GrabModeAsync, None, None, CurrentTime);
 119 
 120             /* we "remember" the position of the pointer at the beginning of
 121              * our move/resize, and the size/position of the window.  that way,
 122              * when the pointer moves, we can compare it to our initial data
 123              * and move/resize accordingly.
 124              */
 125             XGetWindowAttributes(dpy, ev.xbutton.subwindow, &attr);
 126             start = ev.xbutton;
 127         }
 128         /* the only way we'd receive a motion notify event is if we already did
 129          * a pointer grab and we're in move/resize mode, so we assume that. */
 130         else if(ev.type == MotionNotify)
 131         {
 132             int xdiff, ydiff;
 133 
 134             /* here we "compress" motion notify events.  if there are 10 of
 135              * them waiting, it makes no sense to look at any of them but the
 136              * most recent.  in some cases -- if the window is really big or
 137              * things are just acting slowly in general -- failing to do this
 138              * can result in a lot of "drag lag."
 139              *
 140              * for window managers with things like desktop switching, it can
 141              * also be useful to compress EnterNotify events, so that you don't
 142              * get "focus flicker" as windows shuffle around underneath the
 143              * pointer.
 144              */
 145             while(XCheckTypedEvent(dpy, MotionNotify, &ev));
 146 
 147             /* now we use the stuff we saved at the beginning of the
 148              * move/resize and compare it to the pointer's current position to
 149              * determine what the window's new size or position should be.
 150              *
 151              * if the initial button press was button 1, then we're moving.
 152              * otherwise it was 3 and we're resizing.
 153              *
 154              * we also make sure not to go negative with the window's
 155              * dimensions, resulting in "wrapping" which will make our window
 156              * something ridiculous like 65000 pixels wide (often accompanied
 157              * by lots of swapping and slowdown).
 158              *
 159              * even worse is if we get "lucky" and hit a width or height of
 160              * exactly zero, triggering an X error.  so we specify a minimum
 161              * width/height of 1 pixel.
 162              */
 163             xdiff = ev.xbutton.x_root - start.x_root;
 164             ydiff = ev.xbutton.y_root - start.y_root;
 165             XMoveResizeWindow(dpy, ev.xmotion.window,
 166                 attr.x + (start.button==1 ? xdiff : 0),
 167                 attr.y + (start.button==1 ? ydiff : 0),
 168                 MAX(1, attr.width + (start.button==3 ? xdiff : 0)),
 169                 MAX(1, attr.height + (start.button==3 ? ydiff : 0)));
 170         }
 171         /* like motion notifies, the only way we'll receive a button release is
 172          * during a move/resize, due to our pointer grab.  this ends the
 173          * move/resize.
 174          */
 175         else if(ev.type == ButtonRelease)
 176             XUngrabPointer(dpy, CurrentTime);
 177     }
 178 }
 179 

And here's tinywm.py. XCheckTypedEvent has no equivalent in python-xlib, so it is commented out. It doesn't affect functionality, except that responsiveness is worse when you are moving/resizing (especially resizing a large window).

   1 # TinyWM is written by Nick Welch <mack@incise.org>, 2005.
   2 #
   3 # This software is in the public domain
   4 # and is provided AS IS, with NO WARRANTY.
   5 
   6 from Xlib.display import Display
   7 from Xlib import X, XK
   8 
   9 dpy = Display()
  10 root = dpy.screen().root
  11 
  12 root.grab_key(XK.string_to_keysym("F1"), X.Mod1Mask, 1,
  13         X.GrabModeAsync, X.GrabModeAsync)
  14 root.grab_button(1, X.Mod1Mask, 1, X.ButtonPressMask,
  15         X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE)
  16 root.grab_button(3, X.Mod1Mask, 1, X.ButtonPressMask,
  17         X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE)
  18 
  19 while 1:
  20     ev = root.display.next_event()
  21 
  22     if ev.type == X.KeyPress and ev.child != X.NONE:
  23         ev.window.circulate(X.RaiseLowest)
  24     elif ev.type == X.ButtonPress and ev.child != X.NONE:
  25         ev.child.grab_pointer(1, X.PointerMotionMask|X.ButtonReleaseMask,
  26                 X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE, X.CurrentTime)
  27         attr = ev.child.get_geometry()
  28         start = ev
  29     elif ev.type == X.MotionNotify:
  30         #while(XCheckTypedEvent(dpy, MotionNotify, &ev));
  31         xdiff = ev.root_x - start.root_x
  32         ydiff = ev.root_y - start.root_y
  33         ev.window.configure(
  34             x = attr.x + (start.detail == 1 and xdiff or 0),
  35             y = attr.y + (start.detail == 1 and ydiff or 0),
  36             width = max(1, attr.width + (start.detail == 3 and xdiff or 0)),
  37             height = max(1, attr.height + (start.detail == 3 and ydiff or 0)))
  38     elif ev.type == X.ButtonRelease:
  39         dpy.ungrab_pointer(X.CurrentTime)