├── .gitignore ├── .gitmodules ├── Makefile ├── README.adoc ├── src └── tinywm.adb ├── tinywm-xcb.c ├── tinywm.c └── tinywm.gpr /.gitignore: -------------------------------------------------------------------------------- 1 | obj 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "xlib-ada"] 2 | path = xlib-ada 3 | url = git://github.com/rtyler/xlib-ada.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | GPRBUILD=gprbuild 3 | GPRCLEAN=gprclean 4 | 5 | all: pre 6 | $(GPRBUILD) -P tinywm.gpr 7 | 8 | clean: pre 9 | $(GPRCLEAN) tinywm.gpr 10 | rm -rf build 11 | 12 | pre: 13 | mkdir -p obj/debug obj/release 14 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = TinyWM in Ada 2 | 3 | This project is an evening experiment to port 4 | Nick Welch's http://incise.org/tinywm.html[tinywm] from C to Ada. Like the original, it 5 | uses Xlib as its underlying library. 6 | 7 | Also like the original, this code can be considered public domain. 8 | 9 | Most of the time spent was creating the proper bindings to Xlib in Ada, which 10 | was no fun. 11 | 12 | image::http://strongspace.com/rtyler/public/tinywm_ada_vimprobable.png[A useless screenshot] 13 | 14 | 15 | == Building 16 | 17 | . Get a GNAT toolchain 18 | . `git submodule update --init` 19 | . `make` 20 | . Run link:https://en.wikipedia.org/wiki/Xephyr[Xephyr]: `Xephyr :1` 21 | . Execute the tinywm: `DISPLAY=:1 ./obj/debug/tinywm` 22 | . Run something in that window manager: `DISPLAY=:1 xeyes` 23 | -------------------------------------------------------------------------------- /src/tinywm.adb: -------------------------------------------------------------------------------- 1 | -- Main driver for the tinywm example 2 | -- 3 | 4 | 5 | with Ada.Command_Line, 6 | Ada.Text_IO, 7 | Interfaces.C, 8 | Interfaces.C.Strings, 9 | System, 10 | Xlib, 11 | Xlib.Event, 12 | Xlib.Masks, 13 | Xlib.Keys; 14 | 15 | procedure TinyWM is 16 | package CLI renames Ada.Command_Line; 17 | use Ada.Text_IO, 18 | Interfaces.C, 19 | Interfaces.C.Strings, 20 | System; 21 | 22 | Display : Xlib.Display_Type; 23 | Root : Xlib.Window_Type; 24 | -- We'll use this to hold onto the pointer's state at the beginning of a move/resize 25 | Start : Xlib.Event.XButtonEvent; 26 | Attributes : aliased Xlib.Window_Attributes_Type; 27 | Event : aliased Xlib.Event.XEvent; 28 | Code : Xlib.Return_Code_Type; 29 | 30 | function Max (A : in Int; B : in Int) return Int is 31 | begin 32 | if A > B then 33 | return A; 34 | end if; 35 | return B; 36 | end Max; 37 | begin 38 | Put_Line ("Starting TinyWM"); 39 | 40 | Display := Xlib.Open_Display (Xlib.Null_Display); 41 | 42 | if Display = Null_Address then 43 | Put_Line ("Failed to properly open the display!"); 44 | CLI.Set_Exit_Status (1); 45 | return; 46 | else 47 | Put_Line ("Opened a connection to the display"); 48 | end if; 49 | 50 | 51 | Root := Xlib.Default_Root_Window (Display); 52 | 53 | -- Set up the raise key combo (Mod4 + F1) 54 | Code := Xlib.Grab_Key (Display => Display, 55 | Key_Code => Xlib.Keys.Symbol_To_Code (Display, 56 | Xlib.Keys.String_To_Symbol (New_String ("F1"))), 57 | Modifiers => Xlib.Keys.Mod4mask, 58 | Window => Root, 59 | Owner_Events => Xlib.True, 60 | Pointer_Mode => Xlib.GrabModeAsync, 61 | Keyboard_Mode => Xlib.GrabModeAsync); 62 | 63 | 64 | 65 | -- Set up the move button key combo (Mod4 + LeftMouseClick) 66 | Code := Xlib.Grab_Button (Display => Display, 67 | Button => 1, 68 | Modifiers => Xlib.Keys.Mod4Mask, 69 | Grab_Window => Root, 70 | Owner_Events => Xlib.True, 71 | Event_Mask => Xlib.Masks.ButtonPressMask, 72 | Pointer_Mode => Xlib.GrabModeAsync, 73 | Keyboard_Mode => Xlib.GrabModeAsync, 74 | Confine_To => Xlib.Window_Type (Xlib.Null_Atom), 75 | Cursor => Xlib.Cursor_Type (Xlib.Null_Atom)); 76 | 77 | -- Set up the resize key combo (Mod4 + RightMouseClick) 78 | Code := Xlib.Grab_Button (Display => Display, 79 | Button => 3, 80 | Modifiers => Xlib.Keys.Mod4Mask, 81 | Grab_Window => Root, 82 | Owner_Events => Xlib.True, 83 | Event_Mask => Xlib.Masks.ButtonPressMask, 84 | Pointer_Mode => Xlib.GrabModeAsync, 85 | Keyboard_Mode => Xlib.GrabModeAsync, 86 | Confine_To => Xlib.Window_Type (Xlib.Null_Atom), 87 | Cursor => Xlib.Cursor_Type (Xlib.Null_Atom)); 88 | 89 | while true loop 90 | Code := Xlib.Event.Next_Event (Display, Event'Unchecked_Access); 91 | 92 | case Event.C_Type is 93 | when Xlib.Event.KeyPress => 94 | Put_Line ("Key pressed!"); 95 | if Event.XKey.Subwindow /= Xlib.Window_Type (Xlib.Null_Atom) then 96 | Code := Xlib.Raise_Window (Display, Event.XKey.Subwindow); 97 | end if; 98 | 99 | when Xlib.Event.ButtonPress => 100 | Put_Line ("Button Pressed!"); 101 | Code := Xlib.Grab_Pointer (Display => Display, 102 | Grab_Window => Event.XButton.Subwindow, 103 | Owner_Events => Xlib.True, 104 | Event_Mask => Xlib.Masks.PointerMotionMask or Xlib.Masks.ButtonReleaseMask, 105 | Pointer_Mode => Xlib.GrabModeAsync, 106 | Keyboard_Mode => Xlib.GrabModeAsync, 107 | Confine_To => Xlib.Window_Type (Xlib.Null_Atom), 108 | Cursor => Xlib.Cursor_Type (Xlib.Null_Atom), 109 | Time => Xlib.CurrentTime); 110 | 111 | -- Remember the position of the pointer at the beginning of the 112 | -- move/resize and the size/position of the window. That means we 113 | -- can properly move/resize as the pointer moves 114 | Code := Xlib.Get_Window_Attributes (Display => Display, 115 | Window => Event.XButton.Subwindow, 116 | Attributes => Attributes'Access); 117 | Start := Event.XButton; 118 | 119 | when Xlib.Event.ButtonRelease => 120 | Put_Line ("Button released"); 121 | Code := Xlib.Ungrab_Pointer (Display => Display, 122 | Time => Xlib.CurrentTime); 123 | 124 | when Xlib.Event.MotionNotify => 125 | Code := 1; 126 | while Code > 0 loop 127 | Code := Xlib.Event.Check_Typed_Event (Display => Display, 128 | Event_Mask => Xlib.Event.MotionNotify, 129 | Event => Event'Unchecked_Access); 130 | declare 131 | X_Diff : Int := Event.XButton.X_Root - Start.X_Root; 132 | Y_Diff : Int := Event.XButton.Y_Root - Start.Y_Root; 133 | X : Int := Attributes.X; 134 | Y : Int := Attributes.Y; 135 | Width : Int := Attributes.Width; 136 | Height : Int := Attributes.Height; 137 | Move_Code : Xlib.Return_Code_Type; 138 | begin 139 | if Start.Button = 1 then 140 | X := Attributes.X + X_Diff; 141 | Y := Attributes.Y + Y_Diff; 142 | else 143 | Width := Max (1, Attributes.Width + X_Diff); 144 | Height := Max (1, Attributes.Height + Y_Diff); 145 | end if; 146 | 147 | Move_Code := Xlib.Move_Resize_Window (Display => Display, 148 | Window => Event.XMotion.The_Window, 149 | X => X, 150 | Y => Y, 151 | Width => Unsigned (Width), 152 | Height => Unsigned (Height)); 153 | end; 154 | end loop; 155 | 156 | when others => 157 | Put_Line ("Received an unhandled event (code: " & 158 | Xlib.Return_Code_Type'Image (Code) & 159 | ")"); 160 | end case; 161 | end loop; 162 | 163 | end TinyWM; 164 | -------------------------------------------------------------------------------- /tinywm-xcb.c: -------------------------------------------------------------------------------- 1 | /* TinyWM is written by Nick Welch , 2005. 2 | * TinyWM-XCB is rewritten by Ping-Hsun Chen , 2010 3 | * 4 | * This software is in the public domain 5 | * and is provided AS IS, with NO WARRANTY. */ 6 | 7 | #include 8 | 9 | int main (int argc, char **argv) 10 | { 11 | 12 | uint32_t values[3]; 13 | 14 | xcb_connection_t *dpy; 15 | xcb_screen_t *screen; 16 | xcb_drawable_t win; 17 | xcb_drawable_t root; 18 | 19 | xcb_generic_event_t *ev; 20 | xcb_get_geometry_reply_t *geom; 21 | 22 | dpy = xcb_connect(NULL, NULL); 23 | if (xcb_connection_has_error(dpy)) return 1; 24 | 25 | screen = xcb_setup_roots_iterator(xcb_get_setup(dpy)).data; 26 | root = screen->root; 27 | 28 | xcb_grab_key(dpy, 1, root, XCB_MOD_MASK_2, XCB_NO_SYMBOL, 29 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC); 30 | 31 | xcb_grab_button(dpy, 0, root, XCB_EVENT_MASK_BUTTON_PRESS | 32 | XCB_EVENT_MASK_BUTTON_RELEASE, XCB_GRAB_MODE_ASYNC, 33 | XCB_GRAB_MODE_ASYNC, root, XCB_NONE, 1, XCB_MOD_MASK_1); 34 | 35 | xcb_grab_button(dpy, 0, root, XCB_EVENT_MASK_BUTTON_PRESS | 36 | XCB_EVENT_MASK_BUTTON_RELEASE, XCB_GRAB_MODE_ASYNC, 37 | XCB_GRAB_MODE_ASYNC, root, XCB_NONE, 3, XCB_MOD_MASK_1); 38 | xcb_flush(dpy); 39 | 40 | for (;;) 41 | { 42 | ev = xcb_wait_for_event(dpy); 43 | switch (ev->response_type & ~0x80) { 44 | 45 | case XCB_BUTTON_PRESS: 46 | { 47 | xcb_button_press_event_t *e; 48 | e = ( xcb_button_press_event_t *) ev; 49 | win = e->child; 50 | values[0] = XCB_STACK_MODE_ABOVE; 51 | xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_STACK_MODE, values); 52 | geom = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, win), NULL); 53 | if (1 == e->detail) { 54 | values[2] = 1; 55 | xcb_warp_pointer(dpy, XCB_NONE, win, 0, 0, 0, 0, 1, 1); 56 | } else { 57 | values[2] = 3; 58 | xcb_warp_pointer(dpy, XCB_NONE, win, 0, 0, 0, 0, geom->width, geom->height); 59 | } 60 | xcb_grab_pointer(dpy, 0, root, XCB_EVENT_MASK_BUTTON_RELEASE 61 | | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION_HINT, 62 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, XCB_CURRENT_TIME); 63 | xcb_flush(dpy); 64 | } 65 | break; 66 | 67 | case XCB_MOTION_NOTIFY: 68 | { 69 | xcb_query_pointer_reply_t *pointer; 70 | pointer = xcb_query_pointer_reply(dpy, xcb_query_pointer(dpy, root), 0); 71 | if (values[2] == 1) {/* move */ 72 | geom = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, win), NULL); 73 | values[0] = (pointer->root_x + geom->width > screen->width_in_pixels)? 74 | (screen->width_in_pixels - geom->width):pointer->root_x; 75 | values[1] = (pointer->root_y + geom->height > screen->height_in_pixels)? 76 | (screen->height_in_pixels - geom->height):pointer->root_y; 77 | xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); 78 | xcb_flush(dpy); 79 | } else if (values[2] == 3) { /* resize */ 80 | geom = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, win), NULL); 81 | values[0] = pointer->root_x - geom->x; 82 | values[1] = pointer->root_y - geom->y; 83 | xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); 84 | xcb_flush(dpy); 85 | } 86 | } 87 | break; 88 | 89 | case XCB_BUTTON_RELEASE: 90 | xcb_ungrab_pointer(dpy, XCB_CURRENT_TIME); 91 | xcb_flush(dpy); 92 | break; 93 | } 94 | } 95 | 96 | return 0; 97 | } 98 | -------------------------------------------------------------------------------- /tinywm.c: -------------------------------------------------------------------------------- 1 | /* TinyWM is written by Nick Welch , 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 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 | -------------------------------------------------------------------------------- /tinywm.gpr: -------------------------------------------------------------------------------- 1 | 2 | project tinywm is 3 | Version := "0.0.1"; 4 | 5 | type Mode_Type is ("debug", "release"); 6 | Mode : Mode_Type := external ("mode", "debug"); 7 | 8 | for Source_Dirs use ("src", "xlib-ada/src"); 9 | for Languages use ("Ada"); 10 | for Object_Dir use "obj/" & external ("mode", "debug"); 11 | for Main use ("src/tinywm.adb"); 12 | 13 | package Compiler is 14 | case Mode is 15 | when "debug" => 16 | for Switches ("Ada") use ("-g", "-gnat12"); 17 | when "release" => 18 | for Switches ("Ada") use ("-O2", "-gnat12"); 19 | end case; 20 | end Compiler; 21 | end tinywm; 22 | --------------------------------------------------------------------------------