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 }