/* TMUXWM.C
* Copyright (c) 2015-2018 Jesse McClure <code@jessemcclure.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/Xutil.h>
#include <X11/XKBlib.h>
#include <X11/XF86keysym.h>
static struct { KeySym sym; char **args; } bind[] = {
/* No keysym = start with WM. First is Mod+Shift+Enter bound */
{ 0, (char *[]) { "st", "-e", "tmux", "new-session", "-AD", "-s", "TmuxWM", NULL } },
{ 0, (char *[]) { "setxkbmap", "-option", "caps:escape", NULL } },
{ XF86XK_AudioRaiseVolume, (char *[]) { "amixer", "set", "Master", "2+", NULL} },
{ XF86XK_AudioLowerVolume, (char *[]) { "amixer", "set", "Master", "2-", NULL} },
{ XF86XK_AudioMute, (char *[]) { "amixer", "set", "Master", "toggle", NULL} },
{ XF86XK_ScreenSaver, (char *[]) { "slock", NULL} },
};
static void configurerequest(XEvent *);
static void enternotify(XEvent *);
static void keypress(XEvent *);
static void maprequest(XEvent *);
static void unmapnotify(XEvent *);
static Display *dpy;
static Window root;
static int sw, sh;
static void (*handler[LASTEvent])(XEvent *) = {
[ConfigureRequest] = configurerequest,
[EnterNotify] = enternotify,
[KeyPress] = keypress,
[MapRequest] = maprequest,
};
static int spawn(char *const *args) {
if (fork() != 0) return 1;
close(ConnectionNumber(dpy));
setsid();
if (fork() != 0) exit(0);
return execvp(args[0], args);
}
static int init() {
if(!(dpy = XOpenDisplay(0x0))) return 1;
signal(SIGCHLD, SIG_IGN);
root = DefaultRootWindow(dpy);
XSetWindowBackground(dpy, root, 0x060812);
XDefineCursor(dpy, root, XCreateFontCursor(dpy, 68));
sw = DisplayWidth(dpy, DefaultScreen(dpy));
sh = DisplayHeight(dpy, DefaultScreen(dpy));
XGrabKey(dpy, XKeysymToKeycode(dpy, XK_Tab), Mod1Mask, root, True, GrabModeAsync, GrabModeAsync);
XGrabKey(dpy, XKeysymToKeycode(dpy, XK_Return), Mod4Mask | ShiftMask, root, True, GrabModeAsync, GrabModeAsync);
XGrabKey(dpy, AnyKey, Mod4Mask, root, True, GrabModeAsync, GrabModeAsync);
XSelectInput(dpy, root, SubstructureNotifyMask | SubstructureRedirectMask);
int i;
for (i = 0; i < sizeof(bind) / sizeof(bind[0]); i++) {
if (bind[i].sym == 0) spawn(bind[i].args);
else XGrabKey(dpy, XKeysymToKeycode(dpy, bind[i].sym), 0, root, True, GrabModeAsync, GrabModeAsync);
}
return 0;
}
int main() {
if (init() != 0) return 1;
XEvent ev;
while (!XNextEvent(dpy,&ev))
if (handler[ev.type]) handler[ev.type](&ev);
return 0;
}
void configurerequest(XEvent *ev) {
XConfigureRequestEvent *e = &ev->xconfigurerequest;
int bpx = (e->x == 0 && e->y == 0 && e->width == sw && e->height == sh ? 0 : 2);
XWindowChanges wc = { e->x, e->y, e->width, e->height, bpx, e->detail, Above };
XConfigureWindow(dpy, e->window, e->value_mask | CWBorderWidth, &wc);
}
void enternotify(XEvent *ev) {
XSetInputFocus(dpy, ev->xcrossing.window, RevertToPointerRoot, CurrentTime);
}
void keypress(XEvent *ev) {
XKeyEvent *e = &ev->xkey;
KeySym sym = XkbKeycodeToKeysym(dpy, e->keycode, 0, 0);
int i;
if (e->state == Mod1Mask && sym == XK_Tab) XCirculateSubwindowsUp(dpy, root);
else if (e->state == (Mod4Mask|ShiftMask) && sym == XK_Return) spawn(bind[0].args);
else if (e->state == 0) {
for (i = 0; i < sizeof(bind) / sizeof(bind[0]); i++)
if (bind[i].sym == sym) spawn(bind[i].args);
}
else if (e->state == Mod4Mask) {
XEvent xev;
xev.type = KeyPress;
xev.xkey.window = e->subwindow;
xev.xkey.time = CurrentTime;
xev.xkey.state = ControlMask;
xev.xkey.keycode = XKeysymToKeycode(dpy, XK_space);
XSendEvent(dpy, e->subwindow, True, KeyPressMask, &xev);
xev.xkey.state = 0;
xev.xkey.keycode = e->keycode;
XSendEvent(dpy, e->subwindow, True, KeyPressMask, &xev);
XFlush(dpy);
}
}
void maprequest(XEvent *ev) {
XMapRequestEvent *e = &ev->xmaprequest;
XWindowAttributes wa;
if (!XGetWindowAttributes(dpy, e->window, &wa) || wa.override_redirect) return;
XWMHints *wmHint = XGetWMHints(dpy,e->window);
if (!(wmHint && wmHint->flags & InputHint && !wmHint->input)) XSelectInput(dpy, e->window, EnterWindowMask);
if (wmHint) XFree(wmHint);
Window parent;
if (XGetTransientForHint(dpy, e->window, &parent)) XMoveResizeWindow(dpy, e->window, wa.x, wa.y, wa.width, wa.height);
else XMoveResizeWindow(dpy, e->window, 0, 0, sw, sh);
XSetWindowBorder(dpy, e->window, 0x000000);
XMapWindow(dpy, e->window);
}