tmuxwm.c - tmuxwm - A window manager utilising only tmux.
 (HTM) git clone git://jay.scot/tmuxwm
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       tmuxwm.c (6524B)
       ---
            1 /* TMUXWM.C
            2  * Copyright Jesse McClure 2015
            3  * License: GPLv3
            4  * Compile: gcc -o tmuxwm tmuxwm.c -lX11
            5  */
            6 
            7 #include <stdlib.h>
            8 #include <stdio.h>
            9 #include <signal.h>
           10 #include <unistd.h>
           11 #include <string.h>
           12 #include <X11/Xlib.h>
           13 #include <X11/XKBlib.h>
           14 #include <X11/keysym.h>
           15 #include <X11/Xutil.h>
           16 #include <X11/XF86keysym.h>
           17 
           18 typedef struct Bind { KeySym sym; int mod; const char *const *args; }   Bind;
           19 typedef const char *const Arg[];
           20 
           21 Arg tmux_client = (Arg) {"st", "-e", "tmux", "new-session", NULL };
           22 
           23 static Bind bind[] =
           24 {
           25         { XK_Return, Mod4Mask, (Arg) {"tmux",                         "rename-window", NULL } },
           26         { XK_Tab,    Mod4Mask, (Arg) {"tmux",                         "last-pane",          NULL } },
           27         { XK_space,  Mod4Mask, (Arg) {"tmux",                         "new-window",          NULL } },
           28         { XK_1,             Mod4Mask, (Arg) {"tmux",                         "select-window", "-t", "1", NULL} },
           29         { XK_2,             Mod4Mask, (Arg) {"tmux",                         "select-window", "-t", "2", NULL} },
           30         { XK_3,             Mod4Mask, (Arg) {"tmux",                         "select-window", "-t", "3", NULL} },
           31         { XK_4,             Mod4Mask, (Arg) {"tmux",                         "select-window", "-t", "4", NULL} },
           32         { XK_5,             Mod4Mask, (Arg) {"tmux",                         "select-window", "-t", "5", NULL} },
           33         { XK_6,             Mod4Mask, (Arg) {"tmux",                         "select-window", "-t", "6", NULL} },
           34         { XK_7,             Mod4Mask, (Arg) {"tmux",                         "select-window", "-t", "7", NULL} },
           35         { XK_8,             Mod4Mask, (Arg) {"tmux",                         "select-window", "-t", "8", NULL} },
           36         { XK_9,             Mod4Mask, (Arg) {"tmux",                         "select-window", "-t", "9", NULL} },
           37         { XK_h,             Mod4Mask, (Arg) {"tmux",                         "split-window",  "-h", NULL} },
           38         { XK_v,             Mod4Mask, (Arg) {"tmux",                         "split-window",  "-v", NULL} },
           39         { XK_f,             Mod4Mask, (Arg) {"firefox",                 NULL } },
           40         { XK_p,             Mod4Mask, (Arg) {"rofi",                         "-show",          "run", NULL} },
           41         { XK_z,             Mod4Mask, (Arg) {"/home/jay/.bin/battery",         NULL } },
           42         { XK_x,             Mod4Mask, (Arg) {"/home/jay/.bin/all-time", NULL } },
           43 };
           44 
           45 
           46 static void buttonpress(XEvent *);
           47 static void configurerequest(XEvent *);
           48 static void enternotify(XEvent *);
           49 static void keypress(XEvent *);
           50 static void maprequest(XEvent *);
           51 static void unmapnotify(XEvent *);
           52 
           53 static Display *dpy;
           54 static Window root;
           55 static int sw, sh;
           56 static void (*handler[LASTEvent])(XEvent *) =
           57 {
           58         [ButtonPress] = buttonpress,
           59         [ConfigureRequest] = configurerequest,
           60         [EnterNotify] = enternotify,
           61         [KeyPress] = keypress,
           62         [MapRequest] = maprequest,
           63 };
           64 
           65 static int xerror(Display *d, XErrorEvent *e)
           66 {
           67         char msg[256];
           68 
           69         XGetErrorText(d, e->error_code, msg, sizeof(msg));
           70         fprintf(stderr, "[X11 %d:%d] %s\n", e->request_code, e->error_code, msg);
           71 }
           72 
           73 static inline int spawn(Arg args)
           74 {
           75         if (fork() != 0)
           76                 return 1;
           77         close(ConnectionNumber(dpy));
           78         setsid();
           79         execvp(args[0], args);
           80 }
           81 
           82 int main(int argc, const char **argv)
           83 {
           84         signal(SIGCHLD, SIG_IGN);
           85         if (!(dpy = XOpenDisplay(0x0)))
           86                 return 1;
           87         XSetErrorHandler(xerror);
           88         root = DefaultRootWindow(dpy);
           89         sw = DisplayWidth(dpy, DefaultScreen(dpy));
           90         sh = DisplayHeight(dpy, DefaultScreen(dpy));
           91         XGrabKey(dpy, XKeysymToKeycode(dpy, XK_Tab), Mod1Mask, root, True, GrabModeAsync, GrabModeAsync);
           92         XGrabKey(dpy, XKeysymToKeycode(dpy, XK_Return), Mod4Mask | ShiftMask, root, True, GrabModeAsync, GrabModeAsync);
           93         int i;
           94         for (i = 0; i < sizeof(bind) / sizeof(bind[0]); ++i)
           95                 XGrabKey(dpy, XKeysymToKeycode(dpy, bind[i].sym), bind[i].mod, root, True, GrabModeAsync, GrabModeAsync);
           96         XGrabButton(dpy, AnyButton, Mod4Mask, root, True, ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None);
           97         XSelectInput(dpy, root, SubstructureNotifyMask | SubstructureRedirectMask);
           98         XEvent ev;
           99         spawn(tmux_client);
          100         while (!XNextEvent(dpy, &ev))
          101                 if (handler[ev.type])
          102                         handler[ev.type](&ev);
          103         return 0;
          104 }
          105 
          106 void buttonpress(XEvent *ev)
          107 {
          108         XButtonEvent *e = &ev->xbutton;
          109 
          110         XRaiseWindow(dpy, e->subwindow);
          111         if (e->button == 2)
          112                 XMoveResizeWindow(dpy, e->subwindow, 0, 0, sw, sh);
          113         if (e->button != 1 && e->button != 3)
          114                 return;
          115         XGrabPointer(dpy, root, True, PointerMotionMask | ButtonReleaseMask,
          116                      GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
          117         XWindowAttributes attr;
          118         XGetWindowAttributes(dpy, e->subwindow, &attr);
          119         int xx = e->x_root, yy = e->y_root, px, py;
          120         XEvent ee;
          121         while (!XMaskEvent(dpy, PointerMotionMask | ButtonReleaseMask, &ee)) {
          122                 if (ee.type == ButtonRelease)
          123                         break;
          124                 px = xx; py = yy; xx = ee.xbutton.x_root; yy = ee.xbutton.y_root;
          125                 if (e->button == 1) {
          126                         attr.x += xx - px; attr.y += yy - py;
          127                 } else if (e->button == 3) {
          128                         attr.width += xx - px; attr.height += yy - py;
          129                         if (attr.width < 12)
          130                                 attr.width = 12;
          131                         else if (attr.height < 12)
          132                                 attr.height = 12;
          133                 }
          134                 XMoveResizeWindow(dpy, e->subwindow, attr.x, attr.y, attr.width, attr.height);
          135         }
          136         XUngrabPointer(dpy, CurrentTime);
          137 }
          138 
          139 void configurerequest(XEvent *ev)
          140 {
          141         XConfigureRequestEvent *e = &ev->xconfigurerequest;
          142         XWindowChanges wc;
          143 
          144         wc.x = e->x; wc.y = e->y; wc.width = e->width; wc.height = e->height;
          145         wc.sibling = Above; wc.stack_mode = e->detail; wc.border_width = e->border_width;
          146         XConfigureWindow(dpy, e->window, e->value_mask, &wc);
          147 }
          148 
          149 void enternotify(XEvent *ev)
          150 {
          151         XSetInputFocus(dpy, ev->xcrossing.window, RevertToPointerRoot, CurrentTime);
          152 }
          153 
          154 void keypress(XEvent *ev)
          155 {
          156         XKeyEvent *e = &ev->xkey;
          157         KeySym sym = XkbKeycodeToKeysym(dpy, e->keycode, 0, 0);
          158         int i;
          159 
          160         if (e->state == Mod1Mask && sym == XK_Tab) {
          161                 XCirculateSubwindowsUp(dpy, root);
          162         } else if (e->state == (Mod4Mask | ShiftMask) && sym == XK_Return) {
          163                 spawn(tmux_client);
          164         } else {
          165                 for (i = 0; i < sizeof(bind) / sizeof(bind[0]); ++i)
          166                         if (bind[i].sym == sym && bind[i].mod == e->state && bind[i].args)
          167                                 spawn(bind[i].args);
          168         }
          169 }
          170 
          171 #define MapFocus    0x01
          172 #define MapFull     0x02
          173 
          174 void maprequest(XEvent *ev)
          175 {
          176         XMapRequestEvent *e = &ev->xmaprequest;
          177         XWindowAttributes wa;
          178 
          179         if (!XGetWindowAttributes(dpy, e->window, &wa) || wa.override_redirect)
          180                 return;
          181         int flags = MapFocus | MapFull;
          182         XWMHints *wmHint;
          183         if ((wmHint = XGetWMHints(dpy, e->window))) {
          184                 if (wmHint->flags & InputHint && !wmHint->input)
          185                         flags &= ~MapFocus;
          186                 XFree(wmHint);
          187         }
          188         XClassHint csHint;
          189         XGetClassHint(dpy, e->window, &csHint);
          190         if (strncmp(csHint.res_name, "Float", 5) == 0)
          191                 flags &= ~MapFull;
          192         if (strncmp(csHint.res_class, "Float", 5) == 0)
          193                 flags &= ~MapFull;
          194         Window parent;
          195         if (XGetTransientForHint(dpy, e->window, &parent))
          196                 flags &= ~MapFull;
          197         if (flags & MapFocus)
          198                 XSelectInput(dpy, e->window, EnterWindowMask);
          199         if (flags & MapFull)
          200                 XMoveResizeWindow(dpy, e->window, 0, 0, sw, sh);
          201         else
          202                 XMoveResizeWindow(dpy, e->window, wa.x, wa.y, wa.width, wa.height);
          203         XMapWindow(dpy, e->window);
          204 }