From 0b4e0dc67443b31bfb77c370ff03c9b4d9c72aeb Mon Sep 17 00:00:00 2001 From: Phantop Date: Thu, 16 Aug 2018 00:07:32 -0400 Subject: [PATCH] http://incise.org/tinywm.html --- reads/tinywm.md | 317 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 reads/tinywm.md diff --git a/reads/tinywm.md b/reads/tinywm.md new file mode 100644 index 0000000..4dc85d0 --- /dev/null +++ b/reads/tinywm.md @@ -0,0 +1,317 @@ + +[Source](http://incise.org/tinywm.html "Permalink to tinywm") + +# tinywm + +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 four 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: + + + /* TinyWM is written by Nick Welch , 2005. + * + * This software is in the public domain + * and is provided AS IS, with NO WARRANTY. */ + + #include + + #define MAX(a, b) ((a) > (b) ? (a) : (b)) + + int main() + { + Display * dpy; + Window root; + XWindowAttributes attr; + XButtonEvent start; + XEvent ev; + + if(!(dpy = XOpenDisplay(0x0))) return 1; + + root = DefaultRootWindow(dpy); + + XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("F1")), Mod1Mask, root, + True, GrabModeAsync, GrabModeAsync); + XGrabButton(dpy, 1, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync, + GrabModeAsync, None, None); + XGrabButton(dpy, 3, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync, + GrabModeAsync, None, None); + + for(;;) + { + XNextEvent(dpy, &ev); + if(ev.type == KeyPress && ev.xkey.subwindow != None) + XRaiseWindow(dpy, ev.xkey.subwindow); + else if(ev.type == ButtonPress && ev.xbutton.subwindow != None) + { + XGrabPointer(dpy, ev.xbutton.subwindow, True, + PointerMotionMask|ButtonReleaseMask, GrabModeAsync, + GrabModeAsync, None, None, CurrentTime); + XGetWindowAttributes(dpy, ev.xbutton.subwindow, &attr); + start = ev.xbutton; + } + else if(ev.type == MotionNotify) + { + int xdiff, ydiff; + while(XCheckTypedEvent(dpy, MotionNotify, &ev)); + xdiff = ev.xbutton.x_root - start.x_root; + ydiff = ev.xbutton.y_root - start.y_root; + XMoveResizeWindow(dpy, ev.xmotion.window, + attr.x + (start.button==1 ? xdiff : 0), + attr.y + (start.button==1 ? ydiff : 0), + MAX(1, attr.width + (start.button==3 ? xdiff : 0)), + MAX(1, attr.height + (start.button==3 ? ydiff : 0))); + } + else if(ev.type == ButtonRelease) + XUngrabPointer(dpy, CurrentTime); + } + } + + +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. + + + /* TinyWM is written by Nick Welch , 2005. + * + * This software is in the public domain + * and is provided AS IS, with NO WARRANTY. */ + + /* much of tinywm's purpose is to serve as a very basic example of how to do X + * stuff and/or understand window managers, so i wanted to put comments in the + * code explaining things, but i really hate wading through code that is + * over-commented -- and for that matter, tinywm is supposed to be as concise + * as possible, so having lots of comments just wasn't really fitting for it. + * i want tinywm.c to be something you can just look at and go "wow, that's + * it? cool!" so what i did was just copy it over to annotated.c and comment + * the hell out of it. ahh, but now i have to make every code change twice! + * oh well. i could always use some sort of script to process the comments out + * of this and write it to tinywm.c ... nah. + */ + + /* most X stuff will be included with Xlib.h, but a few things require other + * headers, like Xmd.h, keysym.h, etc. + */ + #include + + #define MAX(a, b) ((a) > (b) ? (a) : (b)) + + int main() + { + Display * dpy; + Window root; + XWindowAttributes attr; + + /* we use this to save the pointer's state at the beginning of the + * move/resize. + */ + XButtonEvent start; + + XEvent ev; + + + /* return failure status if we can't connect */ + if(!(dpy = XOpenDisplay(0x0))) return 1; + + /* you'll usually be referencing the root window a lot. this is a somewhat + * naive approach that will only work on the default screen. most people + * only have one screen, but not everyone. if you run multi-head without + * xinerama then you quite possibly have multiple screens. (i'm not sure + * about vendor-specific implementations, like nvidia's) + * + * many, probably most window managers only handle one screen, so in + * reality this isn't really *that* naive. + * + * if you wanted to get the root window of a specific screen you'd use + * RootWindow(), but the user can also control which screen is our default: + * if they set $DISPLAY to ":0.foo", then our default screen number is + * whatever they specify "foo" as. + */ + root = DefaultRootWindow(dpy); + + /* you could also include keysym.h and use the XK_F1 constant instead of + * the call to XStringToKeysym, but this method is more "dynamic." imagine + * you have config files which specify key bindings. instead of parsing + * the key names and having a huge table or whatever to map strings to XK_* + * constants, you can just take the user-specified string and hand it off + * to XStringToKeysym. XStringToKeysym will give you back the appropriate + * keysym or tell you if it's an invalid key name. + * + * a keysym is basically a platform-independent numeric representation of a + * key, like "F1", "a", "b", "L", "5", "Shift", etc. a keycode is a + * numeric representation of a key on the keyboard sent by the keyboard + * driver (or something along those lines -- i'm no hardware/driver expert) + * to X. so we never want to hard-code keycodes, because they can and will + * differ between systems. + */ + XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("F1")), Mod1Mask, root, + True, GrabModeAsync, GrabModeAsync); + + /* XGrabKey and XGrabButton are basically ways of saying "when this + * combination of modifiers and key/button is pressed, send me the events." + * so we can safely assume that we'll receive Alt+F1 events, Alt+Button1 + * events, and Alt+Button3 events, but no others. You can either do + * individual grabs like these for key/mouse combinations, or you can use + * XSelectInput with KeyPressMask/ButtonPressMask/etc to catch all events + * of those types and filter them as you receive them. + */ + XGrabButton(dpy, 1, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync, + GrabModeAsync, None, None); + XGrabButton(dpy, 3, Mod1Mask, root, True, ButtonPressMask, GrabModeAsync, + GrabModeAsync, None, None); + + for(;;) + { + /* this is the most basic way of looping through X events; you can be + * more flexible by using XPending(), or ConnectionNumber() along with + * select() (or poll() or whatever floats your boat). + */ + XNextEvent(dpy, &ev); + + /* this is our keybinding for raising windows. as i saw someone + * mention on the ratpoison wiki, it is pretty stupid; however, i + * wanted to fit some sort of keyboard binding in here somewhere, and + * this was the best fit for it. + * + * i was a little confused about .window vs. .subwindow for a while, + * but a little RTFMing took care of that. our passive grabs above + * grabbed on the root window, so since we're only interested in events + * for its child windows, we look at .subwindow. when subwindow + * None, that means that the window the event happened in was the same + * window that was grabbed on -- in this case, the root window. + */ + if(ev.type == KeyPress && ev.xkey.subwindow != None) + XRaiseWindow(dpy, ev.xkey.subwindow); + else if(ev.type == ButtonPress && ev.xbutton.subwindow != None) + { + /* now we take command of the pointer, looking for motion and + * button release events. + */ + XGrabPointer(dpy, ev.xbutton.subwindow, True, + PointerMotionMask|ButtonReleaseMask, GrabModeAsync, + GrabModeAsync, None, None, CurrentTime); + + /* we "remember" the position of the pointer at the beginning of + * our move/resize, and the size/position of the window. that way, + * when the pointer moves, we can compare it to our initial data + * and move/resize accordingly. + */ + XGetWindowAttributes(dpy, ev.xbutton.subwindow, &attr); + start = ev.xbutton; + } + /* the only way we'd receive a motion notify event is if we already did + * a pointer grab and we're in move/resize mode, so we assume that. */ + else if(ev.type == MotionNotify) + { + int xdiff, ydiff; + + /* here we "compress" motion notify events. if there are 10 of + * them waiting, it makes no sense to look at any of them but the + * most recent. in some cases -- if the window is really big or + * things are just acting slowly in general -- failing to do this + * can result in a lot of "drag lag." + * + * for window managers with things like desktop switching, it can + * also be useful to compress EnterNotify events, so that you don't + * get "focus flicker" as windows shuffle around underneath the + * pointer. + */ + while(XCheckTypedEvent(dpy, MotionNotify, &ev)); + + /* now we use the stuff we saved at the beginning of the + * move/resize and compare it to the pointer's current position to + * determine what the window's new size or position should be. + * + * if the initial button press was button 1, then we're moving. + * otherwise it was 3 and we're resizing. + * + * we also make sure not to go negative with the window's + * dimensions, resulting in "wrapping" which will make our window + * something ridiculous like 65000 pixels wide (often accompanied + * by lots of swapping and slowdown). + * + * even worse is if we get "lucky" and hit a width or height of + * exactly zero, triggering an X error. so we specify a minimum + * width/height of 1 pixel. + */ + xdiff = ev.xbutton.x_root - start.x_root; + ydiff = ev.xbutton.y_root - start.y_root; + XMoveResizeWindow(dpy, ev.xmotion.window, + attr.x + (start.button==1 ? xdiff : 0), + attr.y + (start.button==1 ? ydiff : 0), + MAX(1, attr.width + (start.button==3 ? xdiff : 0)), + MAX(1, attr.height + (start.button==3 ? ydiff : 0))); + } + /* like motion notifies, the only way we'll receive a button release is + * during a move/resize, due to our pointer grab. this ends the + * move/resize. + */ + else if(ev.type == ButtonRelease) + XUngrabPointer(dpy, CurrentTime); + } + } + +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). + + + # TinyWM is written by Nick Welch , 2005. + # + # This software is in the public domain + # and is provided AS IS, with NO WARRANTY. + + from Xlib.display import Display + from Xlib import X, XK + + dpy = Display() + root = dpy.screen().root + + root.grab_key(XK.string_to_keysym("F1"), X.Mod1Mask, 1, + X.GrabModeAsync, X.GrabModeAsync) + root.grab_button(1, X.Mod1Mask, 1, X.ButtonPressMask, + X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE) + root.grab_button(3, X.Mod1Mask, 1, X.ButtonPressMask, + X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE) + + while 1: + ev = root.display.next_event() + + if ev.type == X.KeyPress and ev.child != X.NONE: + ev.window.circulate(X.RaiseLowest) + elif ev.type == X.ButtonPress and ev.child != X.NONE: + ev.child.grab_pointer(1, X.PointerMotionMask|X.ButtonReleaseMask, + X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE, X.CurrentTime) + attr = ev.child.get_geometry() + start = ev + elif ev.type == X.MotionNotify: + #while(XCheckTypedEvent(dpy, MotionNotify, &ev)); + xdiff = ev.root_x - start.root_x + ydiff = ev.root_y - start.root_y + ev.window.configure( + x = attr.x + (start.detail == 1 and xdiff or 0), + y = attr.y + (start.detail == 1 and ydiff or 0), + width = max(1, attr.width + (start.detail == 3 and xdiff or 0)), + height = max(1, attr.height + (start.detail == 3 and ydiff or 0))) + elif ev.type == X.ButtonRelease: + dpy.ungrab_pointer(X.CurrentTime) + +