swim.git

evt.c

espurr
/* swim - window manager
 * Copyright (C) 2022-2023 ArcNyxx
 * see LICENCE file for licensing information */

#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/extensions/Xinerama.h>

#include "bar.h"
#include "conv.h"
#include "func.h"
#include "grab.h"
#include "tile.h"
#include "swim.h"
#include "user.h"
#include "util.h"

static void setfsc(cli_t *cli, bool fsc);
static bool tprop(Window win, Atom prop, void *text, int len);
static void unmanage(cli_t *cli, bool dest);
static void whint(cli_t *cli);

static void clientmessage    (XEvent *evt);
static void configurerequest (XEvent *evt);
static void destroynotify    (XEvent *evt);
static void enternotify      (XEvent *evt);
static void expose           (XEvent *evt);
static void focusin          (XEvent *evt);
static void mappingnotify    (XEvent *evt);
static void maprequest       (XEvent *evt);
static void motionnotify     (XEvent *evt);
static void propertynotify   (XEvent *evt);
static void unmapnotify      (XEvent *evt);

mon_t *sel, *mons;
void (*evts[LASTEvent])(XEvent *) = {
	[ButtonPress]      = buttonpress,
	[ClientMessage]    = clientmessage,
	[ConfigureRequest] = configurerequest,
	[DestroyNotify]    = destroynotify,
	[EnterNotify]      = enternotify,
	[Expose]           = expose,
	[FocusIn]          = focusin,
	[KeyPress]         = keypress,
	[MappingNotify]    = mappingnotify,
	[MapRequest]       = maprequest,
	[MotionNotify]     = motionnotify,
	[PropertyNotify]   = propertynotify,
	[UnmapNotify]      = unmapnotify
};

static void
setfsc(cli_t *cli, bool fsc)
{
	if (cli->fsc == fsc)
		return;
	uint16_t *cr = fsc ? &cli->mon->x : &cli->x;
	SIZE(cli->win, cr[0], cr[1], cr[2], cr[3], BW * !fsc);
	if ((cli->fsc = fsc))
		RAISE(cli);
	else if (!cli->flt)
		LOWER(cli), tile(cli->mon), focus(NULL);
	for (mon_t *mon = mons; mon != NULL; mon = mon->next)
		xcb_configure_window(c, mon->win, GCWM3,
				(uint32_t []){ XCB_STACK_MODE_BELOW });

	XChangeProperty(dpy, cli->win, atom[NET_STATE], XA_ATOM, 32,
			PropModeReplace, (void *)&atom[NET_FULL], fsc);
}

static bool
tprop(Window win, Atom prop, void *text, int len)
{
	unsigned char *store;
	if (XGetWindowProperty(dpy, win, prop, 0, len, false, AnyPropertyType,
			&(Atom){ 0 }, &(int){ 0 }, &(unsigned long)
			{ 0 }, &(unsigned long){ 0 }, &store) || store == NULL)
		return false;
	strcpy(text, (char *)store); XFree(store);
	return true;
}

static void
unmanage(cli_t *cli, bool dest)
{
	if (dest && !sendevent(cli, atom[CC_DELETE])) {
		XGrabServer(dpy);
		XKillClient(dpy, cli->win);
		XSync(dpy, false);
		XUngrabServer(dpy);
	}
	mon_t *mon = cli->mon;
	detach(cli); free(cli); tile(mon); focus(NULL);

	XDeleteProperty(dpy, ROOT, atom[NET_CLIENT_LIST]);
	for (mon = mons; mon != NULL; mon = mon->next)
		for (cli = mon->cli; cli != NULL; cli = cli->next)
			XChangeProperty(dpy, ROOT, atom[NET_CLIENT_LIST],
					XA_WINDOW, 32, PropModeAppend,
					(void *)&cli->win, 1);
}

static void
whint(cli_t *cli)
{
	XWMHints *wmh;
	if ((wmh = XGetWMHints(dpy, cli->win)) != NULL) {
		if (cli == sel->sel && wmh->flags & XUrgencyHint) {
			wmh->flags ^= XUrgencyHint; /* clear flag and reset */
			XSetWMHints(dpy, cli->win, wmh);
		} else {
			cli->urg = wmh->flags & XUrgencyHint;
		}
		cli->nfc = wmh->flags & InputHint ? !wmh->input : false;
		XFree(wmh);
	}
}

static void
clientmessage(XEvent *ev)
{
	cli_t *cli;
	if ((cli = wintocli(ev->xclient.window)) == NULL)
		return;
	if (ev->xclient.message_type == atom[NET_STATE] &&
			(ev->xclient.data.l[1] == (long)atom[NET_FULL] ||
			 ev->xclient.data.l[2] == (long)atom[NET_FULL]))
		setfsc(cli, (!cli->fsc && ev->xclient.data.l[0] == 2) ||
				ev->xclient.data.l[0] == 1);
	else if (ev->xclient.message_type == atom[NET_ACTIVE] &&
			cli != sel->sel && !cli->urg)
		seturg(cli, true);
}

static void
configurerequest(XEvent *evt)
{
	XConfigureRequestEvent *ev = &evt->xconfigurerequest;
	cli_t *cli;
	if ((cli = wintocli(ev->window)) == NULL || cli->flt) {
		XConfigureWindow(dpy, ev->window, ev->value_mask,
				&(XWindowChanges){
			.x = ev->x, .width = ev->width, .sibling = ev->above,
			.y = ev->y, .height = ev->height, .stack_mode =
			ev->detail, .border_width = ev->border_width
	 	});
		XSync(dpy, false);
	}
}

static void
destroynotify(XEvent *ev)
{
	cli_t *cli;
	if ((cli = wintocli(ev->xdestroywindow.window)) != NULL)
		unmanage(cli, true);
}

static void
enternotify(XEvent *ev)
{
	if ((ev->xcrossing.mode != NotifyNormal || ev->xcrossing.detail ==
			NotifyInferior) && ev->xcrossing.window != ROOT)
		return;

	mon_t *mon; cli_t *cli = wintocli(ev->xcrossing.window);
	if ((mon = cli != NULL ? cli->mon :
			wintomon(ev->xcrossing.window)) != sel)
		unfocus(sel->sel, true), sel = mon, focus(cli);
	else if (cli != NULL && cli != sel->sel)
		focus(cli);
}

static void
expose(XEvent *ev)
{
	mon_t *mon;
	if (ev->xexpose.count == 0 &&
			(mon = wintomon(ev->xexpose.window)) != NULL)
		drawbar(mon);
}

static void
focusin(XEvent *ev)
{
	if (sel->sel == NULL || ev->xfocus.window == sel->sel->win)
		return;
	if (!sel->sel->nfc) {
		XSetInputFocus(dpy, sel->sel->win, RevertToPointerRoot,
				CurrentTime);
		XChangeProperty(dpy, ROOT, atom[NET_ACTIVE], XA_WINDOW, 32,
				PropModeReplace, (void *)&(sel->sel->win), 1);
	}
	sendevent(sel->sel, atom[CC_TAKE_FOCUS]);
}

static void
mappingnotify(XEvent *ev)
{
	XRefreshKeyboardMapping(&ev->xmapping);
	if (ev->xmapping.request == MappingKeyboard)
		grabkeys();
}

static void
maprequest(XEvent *ev)
{
	XWindowAttributes attrs;
	if (!XGetWindowAttributes(dpy, ev->xmaprequest.window, &attrs) ||
			wintocli(ev->xmaprequest.window) != NULL ||
			attrs.override_redirect)
		return;

	cli_t *cli = srealloc(NULL, sizeof(cli_t));
	cli->name[0] = '\0'; cli->win = ev->xmaprequest.window;
	cli->flt = cli->fsc = cli->urg = cli->nfc = false;

	Window tr; cli_t *trc;
	cli->mon = XGetTransientForHint(dpy, cli->win, &tr) &&
			(trc = wintocli(tr)) != NULL ? trc->mon : sel;
	cli->tags = cli->mon->tags; attach(cli);

	Atom at;
	if (!tprop(cli->win, atom[NET_NAME], cli->name, 255))
		tprop(cli->win, XA_WM_NAME, cli->name, 255);
	if (tprop(cli->win, atom[NET_TYPE], &at, 4) && at == atom[NET_DIALOG])
		cli->flt = true;
	if (tprop(cli->win, atom[NET_STATE], &at, 4) && at == atom[NET_FULL])
		setfsc(cli, true);
	whint(cli);

	(cli->flt |= tr != 0) ? RAISE(cli) : LOWER(cli);
	cli->x = attrs.x, cli->y = attrs.y,
			cli->w = attrs.width, cli->h = attrs.height;
	CSIZE(cli);

	XSelectInput(dpy, cli->win, EnterWindowMask | FocusChangeMask |
			PropertyChangeMask | StructureNotifyMask);
	XChangeProperty(dpy, ROOT, atom[NET_CLIENT_LIST], XA_WINDOW, 32,
			PropModeAppend, (void *)&cli->win, 1);
	XChangeProperty(dpy, cli->win, atom[CC_STATE], atom[CC_STATE], 32,
			PropModeReplace, (void *)(long [2]){ NormalState }, 2);
	grabbuttons(cli, false);
	tile(cli->mon); XMapWindow(dpy, cli->win); focus(NULL);
}

static void
motionnotify(XEvent *ev)
{
	mon_t *newmon;
	static mon_t *mon = NULL;
	if (ev->xmotion.window == ROOT) {
		if ((newmon = rectomon(ev->xmotion.x_root, ev->xmotion.y_root,
				1, 1)) != mon && mon != NULL)
			unfocus(sel->sel, false), sel = newmon, focus(NULL);
		mon = newmon;
	}
}

static void
propertynotify(XEvent *evt)
{
	extern char stext[256];

	cli_t *cli;
	XPropertyEvent *ev = &evt->xproperty;
	if (ev->atom == XA_WM_NAME && ev->window == ROOT) {
		tprop(ROOT, XA_WM_NAME, stext, 255);
		drawbars();
	} else if (ev->state != PropertyDelete &&
			(cli = wintocli(ev->window)) != NULL) {
		Window tr;
		if (ev->atom == XA_WM_NAME || ev->atom == atom[NET_NAME]) {
			if (!tprop(cli->win, atom[NET_NAME], cli->name, 255))
				tprop(cli->win, XA_WM_NAME, cli->name, 255);
			if (cli == cli->mon->sel)
				drawbar(cli->mon);
		} else if (ev->atom == XA_WM_HINTS) {
			whint(cli);
		} else if (ev->atom == XA_WM_TRANSIENT_FOR && !cli->flt &&
				XGetTransientForHint(dpy, cli->win, &tr) &&
				(cli->flt = wintocli(tr) != NULL)) {
			RAISE(cli);
			tile(cli->mon);
		}
	}
}

static void
unmapnotify(XEvent *ev)
{
	cli_t *cli;
	if ((cli = wintocli(ev->xunmap.window)) != NULL)
		ev->xunmap.send_event ? (void)XDeleteProperty(dpy, cli->win,
				atom[CC_STATE]) : unmanage(cli, false);
}