swim.git
evt.c
/* 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);
}