[root]/ src
/ event.zig
12.2KB
const std = @import("std");
const c = @import("c.zig").c;
const config = @import("config.zig");
const client_mod = @import("client.zig");
const WM = client_mod.WM;
const Client = client_mod.Client;
const bar_mod = @import("bar.zig");
const util = @import("util.zig");
pub fn dispatch(wm: *WM, ev: *c.XEvent) void {
switch (ev.type) {
c.MapRequest => mapRequest(wm, &ev.xmaprequest),
c.UnmapNotify => unmapNotify(wm, &ev.xunmap),
c.DestroyNotify => destroyNotify(wm, &ev.xdestroywindow),
c.ConfigureRequest => configureRequest(wm, &ev.xconfigurerequest),
c.ClientMessage => clientMessage(wm, &ev.xclient),
c.PropertyNotify => propertyNotify(wm, &ev.xproperty),
c.ButtonPress => buttonPress(wm, &ev.xbutton),
c.MotionNotify => motionNotify(wm, &ev.xmotion),
c.ButtonRelease => buttonRelease(wm, &ev.xbutton),
c.KeyPress => keyPress(wm, &ev.xkey),
c.Expose => expose(wm, &ev.xexpose),
c.FocusIn => focusIn(wm, &ev.xfocus),
c.EnterNotify => enterNotify(wm, &ev.xcrossing),
c.MappingNotify => mappingNotify(wm, &ev.xmapping),
c.SelectionClear => selectionClear(wm),
else => {},
}
}
fn mapRequest(wm: *WM, ev: *c.XMapRequestEvent) void {
client_mod.manage(wm, ev.window) catch |err| {
util.log("manage: {}\n", .{err});
};
bar_mod.draw(wm);
}
fn unmapNotify(wm: *WM, ev: *c.XUnmapEvent) void {
if (ev.event != wm.root) return;
const cl = client_mod.listFind(wm.clients, ev.window) orelse return;
if (cl.unmap_count > 0) {
cl.unmap_count -= 1;
return;
}
client_mod.unmanage(wm, cl);
bar_mod.draw(wm);
}
fn destroyNotify(wm: *WM, ev: *c.XDestroyWindowEvent) void {
const cl = client_mod.listFind(wm.clients, ev.window) orelse return;
client_mod.unmanage(wm, cl);
bar_mod.draw(wm);
}
fn configureRequest(wm: *WM, ev: *c.XConfigureRequestEvent) void {
const cl = client_mod.listFind(wm.clients, ev.window);
if (cl) |client| {
if (client.flags.fullscreen) {
client_mod.configure(wm, client);
return;
}
if (ev.value_mask & c.CWX != 0) client.x = ev.x;
if (ev.value_mask & c.CWY != 0) client.y = ev.y;
if (ev.value_mask & c.CWWidth != 0) client.w = @intCast(ev.width);
if (ev.value_mask & c.CWHeight != 0) client.h = @intCast(ev.height);
if (ev.value_mask & c.CWBorderWidth != 0)
client.bw = @intCast(ev.border_width);
_ = c.XMoveResizeWindow(wm.display, client.window,
client.x, client.y, client.w, client.h);
client_mod.configure(wm, client);
client_mod.updateFrameExtents(wm, client);
} else {
var changes: c.XWindowChanges = undefined;
changes.x = ev.x;
changes.y = ev.y;
changes.width = ev.width;
changes.height = ev.height;
changes.border_width = ev.border_width;
changes.sibling = ev.above;
changes.stack_mode = ev.detail;
_ = c.XConfigureWindow(wm.display, ev.window,
@intCast(ev.value_mask), &changes);
}
}
fn clientMessage(wm: *WM, ev: *c.XClientMessageEvent) void {
const cl = client_mod.listFind(wm.clients, ev.window) orelse return;
const a = wm.atoms;
if (ev.message_type == a.net_wm_state) {
const action = ev.data.l[0];
const p1: c.Atom = @intCast(ev.data.l[1]);
const p2: c.Atom = @intCast(ev.data.l[2]);
const toggle = action == 2;
const add = action == 1;
const remove = action == 0;
if (p1 == a.net_wm_state_fullscreen or p2 == a.net_wm_state_fullscreen) {
const want = if (toggle) !cl.flags.fullscreen else add;
client_mod.setFullscreen(wm, cl, want);
}
if (p1 == a.net_wm_state_hidden or p2 == a.net_wm_state_hidden) {
if (add or (toggle and !cl.flags.minimized)) {
client_mod.setMinimized(wm, cl, true);
if (wm.focused == cl) client_mod.focusVisible(wm);
} else if (remove or (toggle and cl.flags.minimized)) {
client_mod.setMinimized(wm, cl, false);
client_mod.focus(wm, cl);
}
}
if (p1 == a.net_wm_state_demands_attention or p2 == a.net_wm_state_demands_attention) {
if (remove or toggle) {
cl.flags.urgent = if (toggle) !cl.flags.urgent else false;
} else if (add) {
cl.flags.urgent = true;
}
client_mod.setNetState(wm, cl);
}
if (p1 == a.net_wm_state_above or p2 == a.net_wm_state_above) {
if (add) client_mod.raise(wm, cl);
}
bar_mod.draw(wm);
} else if (ev.message_type == a.net_active_window) {
if (cl.flags.minimized)
client_mod.setMinimized(wm, cl, false);
client_mod.focus(wm, cl);
client_mod.raise(wm, cl);
bar_mod.draw(wm);
} else if (ev.message_type == a.net_close_window) {
client_mod.closeClient(wm, cl);
}
}
fn propertyNotify(wm: *WM, ev: *c.XPropertyEvent) void {
if (ev.window == wm.root) {
if (ev.atom == wm.atoms.net_wm_name or ev.atom == c.XA_WM_NAME) {
bar_mod.draw(wm);
}
return;
}
const cl = client_mod.listFind(wm.clients, ev.window) orelse return;
if (ev.state == c.PropertyDelete) return;
if (ev.atom == c.XA_WM_NAME or ev.atom == wm.atoms.net_wm_name) {
client_mod.readTitle(wm, cl);
bar_mod.draw(wm);
} else if (ev.atom == c.XA_WM_CLASS) {
client_mod.readClass(wm, cl);
} else if (ev.atom == c.XA_WM_HINTS) {
const wm_hints: [*c]c.XWMHints = c.XGetWMHints(wm.display, cl.window);
if (wm_hints != null) {
const old_urgent = cl.flags.urgent;
cl.flags.urgent = (wm_hints.*.flags & c.XUrgencyHint) != 0;
if (wm_hints.*.flags & c.InputHint != 0)
cl.input_hint = wm_hints.*.input != 0;
cl.flags.never_focus = !cl.input_hint and !cl.supports_take_focus;
client_mod.setNetState(wm, cl);
if (old_urgent != cl.flags.urgent)
bar_mod.draw(wm);
_ = c.XFree(wm_hints);
}
}
}
fn buttonPress(wm: *WM, ev: *c.XButtonEvent) void {
if (ev.window == wm.bar.window) {
barClick(wm, ev);
return;
}
const cl = client_mod.listFind(wm.clients, ev.window) orelse return;
if (ev.state & c.Mod1Mask != 0 and !cl.flags.fullscreen) {
if (wm.focused != cl) {
client_mod.focus(wm, cl);
client_mod.raise(wm, cl);
bar_mod.draw(wm);
}
const resizing = ev.button == 3;
wm.grab = .{
.client = cl,
.orig_x = cl.x,
.orig_y = cl.y,
.orig_w = cl.w,
.orig_h = cl.h,
.start_x = ev.x_root,
.start_y = ev.y_root,
.resizing = resizing,
};
_ = c.XGrabPointer(wm.display, wm.root, 0,
c.ButtonPressMask | c.ButtonReleaseMask | c.PointerMotionMask,
c.GrabModeAsync, c.GrabModeAsync, 0, 0, c.CurrentTime);
return;
}
if (wm.focused != cl) {
client_mod.focus(wm, cl);
client_mod.raise(wm, cl);
bar_mod.draw(wm);
}
_ = c.XAllowEvents(wm.display, c.ReplayPointer, c.CurrentTime);
}
fn barClick(wm: *WM, ev: *c.XButtonEvent) void {
const cl = client_mod.findClientByBarClick(wm, ev.x) orelse return;
switch (ev.button) {
c.Button1 => {
if (cl.flags.minimized)
client_mod.setMinimized(wm, cl, false);
client_mod.focus(wm, cl);
client_mod.raise(wm, cl);
bar_mod.draw(wm);
},
c.Button2 => {
client_mod.closeClient(wm, cl);
},
c.Button3 => {
client_mod.setMinimized(wm, cl, !cl.flags.minimized);
if (cl.flags.minimized and wm.focused == cl) {
client_mod.focusVisible(wm);
}
bar_mod.draw(wm);
},
else => {},
}
}
fn motionNotify(wm: *WM, ev: *c.XMotionEvent) void {
const grab = wm.grab orelse return;
const cl = grab.client;
var dummy: c.XEvent = undefined;
while (c.XCheckMaskEvent(wm.display, c.PointerMotionMask, &dummy) != 0) {
ev.x_root = dummy.xmotion.x_root;
ev.y_root = dummy.xmotion.y_root;
}
const dx = ev.x_root - grab.start_x;
const dy = ev.y_root - grab.start_y;
if (grab.resizing) {
var nw: i32 = @as(i32, @intCast(grab.orig_w)) + dx;
var nh: i32 = @as(i32, @intCast(grab.orig_h)) + dy;
if (nw < 1) nw = 1;
if (nh < 1) nh = 1;
const s = client_mod.applySizeHints(cl, nw, nh);
nw = @max(s.w, 1);
nh = @max(s.h, 1);
client_mod.resize(wm, cl, cl.x, cl.y, @intCast(nw), @intCast(nh));
} else {
var nx = grab.orig_x + dx;
var ny = grab.orig_y + dy;
nx = applySnap(nx, @intCast(cl.w), @intCast(wm.sw));
ny = applySnap(ny, @intCast(cl.h), @intCast(wm.sh));
cl.x = nx;
cl.y = ny;
_ = c.XMoveWindow(wm.display, cl.window, nx, ny);
client_mod.configure(wm, cl);
}
}
fn applySnap(pos: i32, size: i32, limit: i32) i32 {
if (@abs(pos) < config.snap_distance) return 0;
if (@abs(pos + size - limit) < config.snap_distance) return limit - size;
return pos;
}
fn buttonRelease(wm: *WM, ev: *c.XButtonEvent) void {
_ = ev;
if (wm.grab != null) {
wm.grab = null;
_ = c.XUngrabPointer(wm.display, c.CurrentTime);
}
}
fn keyPress(wm: *WM, ev: *c.XKeyEvent) void {
const keysym = c.XkbKeycodeToKeysym(wm.display, @intCast(ev.keycode), 0, 0);
for (&config.keybinds) |kb| {
if (kb.keysym != keysym) continue;
if (kb.mod != (ev.state & ~@as(c_uint, c.LockMask))) continue;
switch (kb.action) {
.quit => wm.running = false,
.close_focused => {
if (wm.focused) |cl| client_mod.closeClient(wm, cl);
},
.toggle_fullscreen => {
if (wm.focused) |cl| {
client_mod.setFullscreen(wm, cl, !cl.flags.fullscreen);
bar_mod.draw(wm);
}
},
.focus_next => client_mod.focusNext(wm),
.minimize => {
if (wm.focused) |cl| {
client_mod.setMinimized(wm, cl, true);
client_mod.focusVisible(wm);
bar_mod.draw(wm);
}
},
.spawn => |cmd| util.spawn(wm.allocator, cmd),
}
return;
}
}
fn expose(wm: *WM, ev: *c.XExposeEvent) void {
if (ev.window == wm.bar.window and ev.count == 0)
bar_mod.draw(wm);
}
fn focusIn(wm: *WM, ev: *c.XFocusChangeEvent) void {
if (ev.detail == c.NotifyInferior or ev.detail == c.NotifyPointer) return;
if (ev.window == wm.root) return;
const cl = client_mod.listFind(wm.clients, ev.window) orelse return;
if (wm.focused != cl) {
client_mod.focus(wm, cl);
bar_mod.draw(wm);
}
}
fn enterNotify(wm: *WM, ev: *c.XCrossingEvent) void {
if (ev.detail == c.NotifyInferior or ev.mode == c.NotifyGrab or ev.mode == c.NotifyUngrab) return;
if (!config.focus_follows_mouse) return;
const cl = client_mod.listFind(wm.clients, ev.window) orelse return;
if (cl.flags.never_focus) return;
client_mod.focus(wm, cl);
bar_mod.draw(wm);
}
fn mappingNotify(wm: *WM, ev: *c.XMappingEvent) void {
_ = c.XRefreshKeyboardMapping(ev);
if (ev.request == c.MappingKeyboard)
grabKeys(wm);
}
fn selectionClear(wm: *WM) void {
wm.running = false;
}
pub fn grabKeys(wm: *WM) void {
_ = c.XUngrabKey(wm.display, c.AnyKey, c.AnyModifier, wm.root);
for (&config.keybinds) |kb| {
const code = c.XKeysymToKeycode(wm.display, kb.keysym);
_ = c.XGrabKey(wm.display, code, kb.mod, wm.root, 1, c.GrabModeAsync, c.GrabModeAsync);
_ = c.XGrabKey(wm.display, code, kb.mod | c.LockMask, wm.root, 1, c.GrabModeAsync, c.GrabModeAsync);
_ = c.XGrabKey(wm.display, code, kb.mod | c.Mod2Mask, wm.root, 1, c.GrabModeAsync, c.GrabModeAsync);
_ = c.XGrabKey(wm.display, code, kb.mod | c.LockMask | c.Mod2Mask, wm.root, 1, c.GrabModeAsync, c.GrabModeAsync);
}
}