├── README ├── tinywm-xcb.c └── tinywm_annotated.c /README: -------------------------------------------------------------------------------- 1 | A few years ago I was interested in learning XCB, and this project is my 2 | attempt at porting TinyWM to XCB. It works about as well as you could expect 3 | from a Window Manager this simple. I include the original source because in is 4 | well annotated and a great way to learn Xlib programming. 5 | 6 | If you are aware of TinyWM, you will know that it is the simplest of all X 7 | Window Managers out there. My XCB port is no different. 8 | 9 | Requirements (on Debian): 10 | * libxcb1-dev 11 | * libxcb-util0-dev 12 | 13 | To build: 14 | gcc -o tinywm tinywm-xcb.c -lxcb -lxcb-util 15 | -------------------------------------------------------------------------------- /tinywm-xcb.c: -------------------------------------------------------------------------------- 1 | /* TinyWM is written by Nick Welch in 2005 & 2011. 2 | * TinyWM_xcb is a port of Nick Welch's TinyWM to the XCB library written 3 | * in 2015. 4 | * 5 | * This software is in the public domain 6 | * and is provided AS IS, with NO WARRANTY. */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | xcb_connection_t *connection; 15 | xcb_screen_t *screen; 16 | xcb_window_t rootWindow; 17 | 18 | void wmInitialize (void) 19 | { 20 | int defaultScreen; 21 | 22 | /* Connect to the X server */ 23 | connection = xcb_connect (NULL, &defaultScreen); 24 | if (xcb_connection_has_error (connection) > 0) { 25 | fprintf (stderr, "Could not open connection to X Server!\n"); 26 | exit (EXIT_FAILURE); 27 | } 28 | 29 | printf ("Default screen # = %d\n", defaultScreen); 30 | screen = xcb_aux_get_screen (connection, defaultScreen);; 31 | rootWindow = screen->root; 32 | printf ("Screen size = %dx%d\n", screen->width_in_pixels, screen->height_in_pixels); 33 | printf ("rootWindow = %d\n", rootWindow); 34 | 35 | xcb_grab_server (connection); 36 | 37 | /* This causes an error in another window manager is running */ 38 | { 39 | xcb_void_cookie_t cookie; 40 | const uint32_t value = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; 41 | 42 | cookie = xcb_change_window_attributes_checked (connection, rootWindow, XCB_CW_EVENT_MASK, &value); 43 | if (xcb_request_check (connection, cookie)) { 44 | fprintf (stderr, "It seems that another window manager is already running!\n"); 45 | xcb_disconnect (connection); 46 | exit (EXIT_FAILURE); 47 | } 48 | } 49 | 50 | xcb_ungrab_server (connection); 51 | 52 | /* Mouse button bindings */ 53 | xcb_grab_button (connection, 0, rootWindow, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE, XCB_GRAB_MODE_ASYNC, 54 | XCB_GRAB_MODE_ASYNC, rootWindow, XCB_NONE, 1, XCB_MOD_MASK_1); 55 | xcb_grab_button (connection, 0, rootWindow, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE, XCB_GRAB_MODE_ASYNC, 56 | XCB_GRAB_MODE_ASYNC, rootWindow, XCB_NONE, 3, XCB_MOD_MASK_1); 57 | xcb_grab_button (connection, 0, rootWindow, XCB_EVENT_MASK_BUTTON_PRESS, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, rootWindow, 58 | XCB_NONE, 1, XCB_NONE); 59 | 60 | xcb_flush (connection); 61 | } 62 | 63 | int main (void) 64 | { 65 | xcb_window_t focused_window; 66 | xcb_window_t window; 67 | xcb_generic_event_t *event; 68 | uint32_t attr[5]; 69 | 70 | xcb_get_geometry_reply_t *geometry; 71 | 72 | wmInitialize (); 73 | 74 | focused_window = rootWindow; 75 | while (1) { 76 | event = xcb_wait_for_event (connection); 77 | 78 | switch (event->response_type & ~0x80) { 79 | case XCB_BUTTON_PRESS: 80 | { 81 | xcb_button_press_event_t *e; 82 | 83 | e = (xcb_button_press_event_t *) event; 84 | printf ("Button press = %d, Modifier = %d\n", e->detail, e->state); 85 | window = e->child; 86 | attr[0] = XCB_STACK_MODE_ABOVE; 87 | xcb_configure_window (connection, window, XCB_CONFIG_WINDOW_STACK_MODE, attr); 88 | geometry = xcb_get_geometry_reply (connection, xcb_get_geometry (connection, window), NULL); 89 | printf ("e->response_type = %d, e->sequence = %d, e->detail = %d, e->state = %d\n", e->response_type, e->sequence, 90 | e->detail, e->state); 91 | if (e->detail == 1) { 92 | if (e->state == XCB_MOD_MASK_1) { 93 | attr[4] = 1; 94 | xcb_warp_pointer (connection, XCB_NONE, window, 0, 0, 0, 0, 1, 1); 95 | xcb_grab_pointer (connection, 0, rootWindow, 96 | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION_HINT, 97 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, rootWindow, XCB_NONE, XCB_CURRENT_TIME); 98 | } else { 99 | if (focused_window != window) { 100 | printf ("focusing window %d\n", window); 101 | focused_window = window; 102 | /* xcb_ungrab_button (connection, 1, rootWindow, XCB_NONE); 103 | xcb_grab_button (connection, 0, window, XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, rootWindow, XCB_NONE, XCB_BUTTON_INDEX_ANY, XCB_MOD_MASK_ANY); 104 | */ xcb_set_input_focus (connection, XCB_INPUT_FOCUS_POINTER_ROOT, window, XCB_CURRENT_TIME); 105 | } 106 | } 107 | } else { 108 | attr[4] = 3; 109 | xcb_warp_pointer (connection, XCB_NONE, window, 0, 0, 0, 0, geometry->width, geometry->height); 110 | xcb_grab_pointer (connection, 0, rootWindow, 111 | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION_HINT, 112 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, rootWindow, XCB_NONE, XCB_CURRENT_TIME); 113 | } 114 | xcb_flush (connection); 115 | } 116 | break; 117 | 118 | case XCB_MOTION_NOTIFY: 119 | { 120 | xcb_query_pointer_reply_t *pointer; 121 | 122 | pointer = xcb_query_pointer_reply (connection, xcb_query_pointer (connection, rootWindow), 0); 123 | geometry = xcb_get_geometry_reply (connection, xcb_get_geometry (connection, window), NULL); 124 | if (attr[4] == 1) { /* move */ 125 | if (pointer->root_x > screen->width_in_pixels - 5) { 126 | attr[0] = screen->width_in_pixels / 2; 127 | attr[1] = 0; 128 | attr[2] = screen->width_in_pixels / 2; 129 | attr[3] = screen->height_in_pixels; 130 | xcb_configure_window (connection, window, 131 | XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | 132 | XCB_CONFIG_WINDOW_HEIGHT, attr); 133 | } else { 134 | attr[0] = (pointer->root_x + geometry->width > screen->width_in_pixels) 135 | ? (screen->width_in_pixels - geometry->width) : pointer->root_x; 136 | attr[1] = (pointer->root_y + geometry->height > screen->height_in_pixels) 137 | ? (screen->height_in_pixels - geometry->height) : pointer->root_y; 138 | xcb_configure_window (connection, window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, attr); 139 | } 140 | xcb_flush (connection); 141 | } else if (attr[4] == 3) { /* resize */ 142 | attr[0] = pointer->root_x - geometry->x; 143 | attr[1] = pointer->root_y - geometry->y; 144 | xcb_configure_window (connection, window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, attr); 145 | xcb_flush (connection); 146 | } 147 | } 148 | break; 149 | 150 | case XCB_BUTTON_RELEASE: 151 | xcb_ungrab_pointer (connection, XCB_CURRENT_TIME); 152 | xcb_flush (connection); 153 | } 154 | 155 | free (event); 156 | } 157 | 158 | xcb_disconnect (connection); 159 | } 160 | -------------------------------------------------------------------------------- /tinywm_annotated.c: -------------------------------------------------------------------------------- 1 | /* TinyWM is written by Nick Welch in 2005 & 2011. 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(void) 26 | { 27 | Display * dpy; 28 | XWindowAttributes attr; 29 | 30 | /* we use this to save the pointer's state at the beginning of the 31 | * move/resize. 32 | */ 33 | XButtonEvent start; 34 | 35 | XEvent ev; 36 | 37 | /* return failure status if we can't connect */ 38 | if(!(dpy = XOpenDisplay(0x0))) return 1; 39 | 40 | /* we use DefaultRootWindow to get the root window, which is a somewhat 41 | * naive approach that will only work on the default screen. most people 42 | * only have one screen, but not everyone. if you run multi-head without 43 | * xinerama then you quite possibly have multiple screens. (i'm not sure 44 | * about vendor-specific implementations, like nvidia's) 45 | * 46 | * many, probably most window managers only handle one screen, so in 47 | * reality this isn't really *that* naive. 48 | * 49 | * if you wanted to get the root window of a specific screen you'd use 50 | * RootWindow(), but the user can also control which screen is our default: 51 | * if they set $DISPLAY to ":0.foo", then our default screen number is 52 | * whatever they specify "foo" as. 53 | */ 54 | 55 | /* you could also include keysym.h and use the XK_F1 constant instead of 56 | * the call to XStringToKeysym, but this method is more "dynamic." imagine 57 | * you have config files which specify key bindings. instead of parsing 58 | * the key names and having a huge table or whatever to map strings to XK_* 59 | * constants, you can just take the user-specified string and hand it off 60 | * to XStringToKeysym. XStringToKeysym will give you back the appropriate 61 | * keysym or tell you if it's an invalid key name. 62 | * 63 | * a keysym is basically a platform-independent numeric representation of a 64 | * key, like "F1", "a", "b", "L", "5", "Shift", etc. a keycode is a 65 | * numeric representation of a key on the keyboard sent by the keyboard 66 | * driver (or something along those lines -- i'm no hardware/driver expert) 67 | * to X. so we never want to hard-code keycodes, because they can and will 68 | * differ between systems. 69 | */ 70 | XGrabKey(dpy, XKeysymToKeycode(dpy, XStringToKeysym("F1")), Mod1Mask, 71 | DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync); 72 | 73 | /* XGrabKey and XGrabButton are basically ways of saying "when this 74 | * combination of modifiers and key/button is pressed, send me the events." 75 | * so we can safely assume that we'll receive Alt+F1 events, Alt+Button1 76 | * events, and Alt+Button3 events, but no others. You can either do 77 | * individual grabs like these for key/mouse combinations, or you can use 78 | * XSelectInput with KeyPressMask/ButtonPressMask/etc to catch all events 79 | * of those types and filter them as you receive them. 80 | */ 81 | XGrabButton(dpy, 1, Mod1Mask, DefaultRootWindow(dpy), True, 82 | ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None); 83 | XGrabButton(dpy, 3, Mod1Mask, DefaultRootWindow(dpy), True, 84 | ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None); 85 | 86 | start.subwindow = None; 87 | for(;;) 88 | { 89 | /* this is the most basic way of looping through X events; you can be 90 | * more flexible by using XPending(), or ConnectionNumber() along with 91 | * select() (or poll() or whatever floats your boat). 92 | */ 93 | XNextEvent(dpy, &ev); 94 | 95 | /* this is our keybinding for raising windows. as i saw someone 96 | * mention on the ratpoison wiki, it is pretty stupid; however, i 97 | * wanted to fit some sort of keyboard binding in here somewhere, and 98 | * this was the best fit for it. 99 | * 100 | * i was a little confused about .window vs. .subwindow for a while, 101 | * but a little RTFMing took care of that. our passive grabs above 102 | * grabbed on the root window, so since we're only interested in events 103 | * for its child windows, we look at .subwindow. when subwindow == 104 | * None, that means that the window the event happened in was the same 105 | * window that was grabbed on -- in this case, the root window. 106 | */ 107 | if(ev.type == KeyPress && ev.xkey.subwindow != None) 108 | XRaiseWindow(dpy, ev.xkey.subwindow); 109 | else if(ev.type == ButtonPress && ev.xbutton.subwindow != None) 110 | { 111 | /* we "remember" the position of the pointer at the beginning of 112 | * our move/resize, and the size/position of the window. that way, 113 | * when the pointer moves, we can compare it to our initial data 114 | * and move/resize accordingly. 115 | */ 116 | XGetWindowAttributes(dpy, ev.xbutton.subwindow, &attr); 117 | start = ev.xbutton; 118 | } 119 | /* we only get motion events when a button is being pressed, 120 | * but we still have to check that the drag started on a window */ 121 | else if(ev.type == MotionNotify && start.subwindow != None) 122 | { 123 | /* here we could "compress" motion notify events by doing: 124 | * 125 | * while(XCheckTypedEvent(dpy, MotionNotify, &ev)); 126 | * 127 | * if there are 10 of them waiting, it makes no sense to look at 128 | * any of them but the most recent. in some cases -- if the window 129 | * is really big or things are just acting slowly in general -- 130 | * failing to do this can result in a lot of "drag lag," especially 131 | * if your wm does a lot of drawing and whatnot that causes it to 132 | * lag. 133 | * 134 | * for window managers with things like desktop switching, it can 135 | * also be useful to compress EnterNotify events, so that you don't 136 | * get "focus flicker" as windows shuffle around underneath the 137 | * pointer. 138 | */ 139 | 140 | /* now we use the stuff we saved at the beginning of the 141 | * move/resize and compare it to the pointer's current position to 142 | * determine what the window's new size or position should be. 143 | * 144 | * if the initial button press was button 1, then we're moving. 145 | * otherwise it was 3 and we're resizing. 146 | * 147 | * we also make sure not to go negative with the window's 148 | * dimensions, resulting in "wrapping" which will make our window 149 | * something ridiculous like 65000 pixels wide (often accompanied 150 | * by lots of swapping and slowdown). 151 | * 152 | * even worse is if we get "lucky" and hit a width or height of 153 | * exactly zero, triggering an X error. so we specify a minimum 154 | * width/height of 1 pixel. 155 | */ 156 | int xdiff = ev.xbutton.x_root - start.x_root; 157 | int ydiff = ev.xbutton.y_root - start.y_root; 158 | XMoveResizeWindow(dpy, start.subwindow, 159 | attr.x + (start.button==1 ? xdiff : 0), 160 | attr.y + (start.button==1 ? ydiff : 0), 161 | MAX(1, attr.width + (start.button==3 ? xdiff : 0)), 162 | MAX(1, attr.height + (start.button==3 ? ydiff : 0))); 163 | } 164 | else if(ev.type == ButtonRelease) 165 | { 166 | start.subwindow = None; 167 | } 168 | } 169 | } 170 | 171 | --------------------------------------------------------------------------------