swim.git

act.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/Xlib.h>

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

#define MASK ButtonPressMask | ButtonReleaseMask | PointerMotionMask
#define MODKEY Mod4Mask
#define TAGKEY(keysym, shift)                                                 \
	{ view,       1 << shift, MODKEY,               keysym },             \
	{ toggleview, 1 << shift, MODKEY | ControlMask, keysym },             \
	{ tag,        1 << shift, MODKEY | ShiftMask,   keysym },             \
	{ toggletag,  1 << shift, MODKEY | ControlMask | ShiftMask, keysym }

static mon_t *dirtomon(int dir);

static void focusmon       (long arg);
static void focusstack     (long arg);
static void incnmaster     (long arg);
static void killclient     (long arg);
static void mouseevent     (long arg);
static void quit           (long arg);
static void setmfact       (long arg);
static void startexec      (long arg);
static void startnirv      (long arg);
static void tag            (long arg);
static void tagmon         (long arg);
static void togglebar      (long arg);
static void togglefloating (long arg);
static void togglegaps     (long arg);
static void toggletag      (long arg);
static void toggletop      (long arg);
static void toggleview     (long arg);
static void view           (long arg);
static void zoom           (long arg);

extern mon_t *sel;

const keyp_t keys[63] = {
	{ spawn, (long)(const void *[]){ "amixer", "set", "Master",
			"3%+", NULL }, MODKEY, XK_a },
	{ spawn, (long)(const void *[]){ "amixer", "set", "Master",
			"3%-", NULL }, MODKEY, XK_z },
	{ spawn, (long)(const void *[]){ "amixer", "set", "Master",
			"toggle", NULL }, MODKEY, XK_m },
	{ spawn, (long)(const void *[]){ "firefox", NULL }, MODKEY, XK_w },
	{ spawn, (long)(const void *[]){ "st", NULL }, MODKEY, XK_Return },
	{ spawn, (long)(const void *[]){ "bscr", NULL }, MODKEY, XK_s },
	{ startexec,      0, MODKEY, XK_e },
	{ startnirv,      0, MODKEY, XK_n },
	{ killclient,     0, MODKEY | ShiftMask, XK_q },

	{ zoom,           0, MODKEY, XK_semicolon },
	{ focusstack,     1, MODKEY, XK_j },
	{ focusstack,    -1, MODKEY, XK_k },
	{ setmfact,       5, MODKEY, XK_l },
	{ setmfact,      -5, MODKEY, XK_h },
	{ incnmaster,     1, MODKEY, XK_i },
	{ incnmaster,    -1, MODKEY, XK_o },

	{ focusmon,      -1, MODKEY,             XK_comma },
	{ focusmon,       1, MODKEY,             XK_period },
	{ tagmon,        -1, MODKEY | ShiftMask, XK_comma },
	{ tagmon,         1, MODKEY | ShiftMask, XK_period },

	{ togglebar,      0, MODKEY, XK_b },
	{ togglegaps,     0, MODKEY, XK_g },
	{ toggletop,      0, MODKEY, XK_t },
	{ togglefloating, 0, MODKEY, XK_f },

	{ view,          ~0, MODKEY, XK_0 },
	{ tag,           ~0, MODKEY | ShiftMask, XK_0 },
	{ quit,           0, MODKEY | ShiftMask, XK_e },

	TAGKEY(XK_1, 0),
	TAGKEY(XK_2, 1),
	TAGKEY(XK_3, 2),
	TAGKEY(XK_4, 3),
	TAGKEY(XK_5, 4),
	TAGKEY(XK_6, 5),
	TAGKEY(XK_7, 6),
	TAGKEY(XK_8, 7),
	TAGKEY(XK_9, 8)
};
const but_t buts[6] = {
	{ view,        0,     CLK_TAG, 0,      Button1 },
	{ toggleview,  0,     CLK_TAG, 0,      Button3 },
	{ tag,         0,     CLK_TAG, MODKEY, Button1 },
	{ toggletag,   0,     CLK_TAG, MODKEY, Button3 },

	{ mouseevent,  true,  CLK_CLI, MODKEY, Button1 },
	{ mouseevent,  false, CLK_CLI, MODKEY, Button3 }
};
const char *tags[9] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" };

static mon_t *
dirtomon(int dir)
{
	extern mon_t *mons;

	if (mons->next == NULL)
		return NULL;

	mon_t *mon = NULL;
	if (dir > 0) /* just take next monitor or wrap to start */
		return sel->next != NULL ? sel->next : mons;
	else if (sel != mons) /* take mon before sel */
		for (mon = mons; mon->next != sel; mon = mon->next);
	else /* no monitor before sel, take last monitor to wrap around */
		for (mon = mons; mon->next != NULL; mon = mon->next);
	return mon;
}

static void
focusmon(long arg)
{
	mon_t *mon; /* more than one mon */
	if ((mon = dirtomon(arg)) != NULL)
		unfocus(sel->sel, false), sel = mon, focus(NULL);
}

static void
focusstack(long arg)
{
	if (sel->sel == NULL || sel->sel->fsc)
		return;

	cli_t *cli, *cnext;
	if (arg > 0) {
		for (cli = sel->sel->next; cli != NULL && !VIS(cli);
				cli = cli->next);
		if (cli == NULL)
			for (cli = sel->cli; cli != NULL && !VIS(cli);
					cli = cli->next);
	} else {
		for (cli = NULL, cnext = sel->cli; cnext != sel->sel;
				cnext = cnext->next)
			if (VIS(cnext))
				cli = cnext; /* first before client */
		if (cli == NULL)
			for (; cnext != NULL; cnext = cnext->next)
				if (VIS(cnext))
					cli = cnext; /* last client */
	}
	if (cli != NULL)
		focus(cli);
}

static void
incnmaster(long arg)
{
	sel->nmaster = MAX(sel->nmaster + arg, 1);
	tile(sel);
}

static void
killclient(long arg)
{
	if (sel->sel != NULL && !sendevent(sel->sel, atom[CC_DELETE])) {
		XGrabServer(dpy);
		XKillClient(dpy, sel->sel->win);
		XSync(dpy, false);
		XUngrabServer(dpy);
	}
}

static void
mouseevent(long arg)
{
	extern void (*evts[LASTEvent])(XEvent *);

	cli_t *cli;
	if ((cli = sel->sel) == NULL || !cli->flt || cli->fsc ||
			XGrabPointer(dpy, ROOT, false, MASK, GrabModeAsync,
			GrabModeAsync, 0, 0, CurrentTime) != GrabSuccess)
		return;
	RAISE(cli);

	int x, y;
	XQueryPointer(dpy, ROOT, &(Window){ 0 }, &(Window){ 0 }, &x, &y,
			&(int){ 0 }, &(int){ 0 }, &(unsigned int){ 0 });
	int oldx = arg ? cli->x : cli->w;
	int oldy = arg ? cli->y : cli->h;

	XEvent evt;
	do {
		XMaskEvent(dpy, MASK | ExposureMask |
				SubstructureRedirectMask, &evt);
		switch (evt.type) {
		default: break;
		case ConfigureRequest:
		case Expose:
		case MapRequest:
		case PropertyNotify: ; /* ensure bar runs smoothly */
			evts[evt.type](&evt);
			break;
		case MotionNotify:
			if (arg) {
				cli->x = oldx + evt.xmotion.x - x;
				cli->y = oldy + evt.xmotion.y - y;
			} else {
				cli->w = MAX(oldx + evt.xmotion.x - x, H);
				cli->h = MAX(oldy + evt.xmotion.y - y, H);
			}
			CSIZE(cli);
		}
	} while (evt.type != ButtonRelease);
	XUngrabPointer(dpy, CurrentTime);

	mon_t *mon;
	if ((mon = rectomon(cli->x, cli->y, cli->w, cli->h)) != sel)
		unfocus(cli, true), detach(cli), cli->mon = mon, cli->tags =
				mon->tags, attach(cli), focus(NULL);
}

static void
quit(long arg)
{
	XCloseDisplay(dpy);
	exit(0);
}

static void
setmfact(long arg)
{
	sel->mfact = MIN(MAX(arg, 5), 95);
	tile(sel);
}

static void
startexec(long arg)
{
	extern int exec;

	exec = 0;
	XGrabKey(dpy, AnyKey, AnyModifier, ROOT, true,
			GrabModeAsync, GrabModeAsync);
	drawbars();
}

static void
startnirv(long arg)
{
	extern int exec;
	extern char etext[256];

	exec = 6;
	memcpy(etext, "nctl -", 6);
	XGrabKey(dpy, AnyKey, AnyModifier, ROOT, true,
			GrabModeAsync, GrabModeAsync);
	drawbars();
}

static void
tag(long arg)
{
	if (sel->sel != NULL && arg != 0)
		sel->sel->tags = arg, tile(sel), focus(NULL);
}

static void
tagmon(long arg)
{
	mon_t *mon, *old; /* has sel window and more than one mon */
	if (sel->sel != NULL && !sel->sel->flt && !sel->sel->fsc &&
			(mon = dirtomon(arg)) != NULL)
		unfocus(sel->sel, true), detach(sel->sel),
				old = sel->sel->mon, sel->sel->mon = mon,
				sel->sel->tags = mon->tags, attach(sel->sel),
				tile(old), tile(mon), focus(NULL), drawbars();
}

static void
togglebar(long arg)
{
	sel->show = !sel->show;
	MOVE(sel->win, sel->x,
			sel->show ? sel->y + !sel->top * (sel->h - H) : -H);
	tile(sel);
}

static void
togglefloating(long arg)
{
	if (sel->sel != NULL && !sel->sel->fsc)
		(sel->sel->flt = !sel->sel->flt) ? RAISE(sel->sel) :
				LOWER(sel->sel), tile(sel);
}

static void
togglegaps(long arg)
{
	sel->gap = !sel->gap; tile(sel);
}

static void
toggletag(long arg)
{
	if (sel->sel != NULL && (sel->sel->tags ^ arg) != 0)
		sel->sel->tags ^= arg, tile(sel), focus(NULL);
}

static void
toggletop(long arg)
{
	sel->top = !sel->top;
	MOVE(sel->win, sel->x,
			sel->show ? sel->y + !sel->top * (sel->h - H) : -H);
	tile(sel);
}

static void
toggleview(long arg)
{
	if ((sel->tags ^ arg) != 0)
		sel->tags ^= arg, tile(sel), focus(NULL);
}

static void
view(long arg)
{
	if (arg != 0)
		sel->tags = arg, tile(sel), focus(NULL);
}

static void
zoom(long arg)
{
	cli_t *cli = sel->sel;
	if (cli == NULL || cli->flt || cli->fsc)
		return;
	if (cli != next(sel->cli) || ((cli = next(cli->next)) != NULL))
		detach(cli), attach(cli), tile(cli->mon), focus(cli);
}