/* 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);
}

