2zw
Owner: IIIlllIIIllI URL: git@git.0x00nyx.xyz:seb/2zw.git
src/main.zig
// MIT License
// Copyright (c) 2025 Sebastian <sebastian.michalk@pm.me>
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
const std = @import("std");
const C = @import("c.zig");
const bar_mod = @import("bar.zig");
const posix = std.posix;
const Client = struct {
name: [256]u8 = std.mem.zeroes([256]u8),
x: c_int,
y: c_int,
w: c_int,
h: c_int,
rx: c_int = 0,
ry: c_int = 0,
rw: c_int = 0,
rh: c_int = 0,
basew: c_int = 0,
baseh: c_int = 0,
incw: c_int = 0,
inch: c_int = 0,
maxw: c_int = 0,
maxh: c_int = 0,
minw: c_int = 0,
minh: c_int = 0,
minax: c_int = 0,
minay: c_int = 0,
maxax: c_int = 0,
maxay: c_int = 0,
flags: c_long = 0,
border: c_uint = BORDER,
isfixed: bool = false,
isfloat: bool = false,
ismax: bool = false,
win: C.Window,
};
var cl: std.ArrayList(Client) = undefined;
var hidden: std.ArrayList(Client) = undefined;
var stack: std.ArrayList(C.Window) = undefined;
var sel: ?usize = null;
const FOCUS_COL = 0x6699cc;
const NORMAL_COL = 0x333333;
const BORDER = 2;
const GAP: c_int = 3;
const MFACT = 0.6;
var mfact: f32 = MFACT;
var nmaster: usize = 1;
var keyacts: [256]?*const fn () void = .{null} ** 256;
const user = "seb";
const terminal = "st";
const launcher = "zmen";
const autostart = std.fmt.comptimePrint("/home/{s}/.scripts/2zw.sh", .{user});
const bar_font: [*:0]const u8 = "monospace:size=15";
const delim = "::";
const Bind = struct { sym: C.KeySym, action: *const fn () void };
const binds = [_]Bind{
.{ .sym = C.XK_q, .action = &killclient },
.{ .sym = C.XK_comma, .action = &winprev },
.{ .sym = C.XK_period, .action = &winnext },
.{ .sym = C.XK_a, .action = &act_show },
.{ .sym = C.XK_d, .action = &act_hide },
.{ .sym = C.XK_h, .action = &act_mfact_dec },
.{ .sym = C.XK_l, .action = &act_mfact_inc },
.{ .sym = C.XK_Return, .action = spawnfn(terminal) },
.{ .sym = C.XK_p, .action = spawnfn(launcher) },
.{ .sym = C.XK_s, .action = spawnfn("slock") },
};
var cur_resize: C.Cursor = undefined;
var cur_move: C.Cursor = undefined;
var cur_normal: C.Cursor = undefined;
var alloc: std.mem.Allocator = undefined;
var atom_proto: C.Atom = undefined;
var atom_del: C.Atom = undefined;
var atom_state: C.Atom = undefined;
var atom_net: C.Atom = undefined;
var atom_name: C.Atom = undefined;
var atom_active: C.Atom = undefined;
fn spawnfn(comptime cmd: [*:0]const u8) *const fn () void {
return struct {
fn action() void {
spawn(cmd);
}
}.action;
}
fn act_show() void {
show();
}
fn act_hide() void {
hide();
}
fn act_mfact_dec() void {
mfact = @max(0.1, mfact - 0.05);
tile();
}
fn act_mfact_inc() void {
mfact = @min(0.9, mfact + 0.05);
tile();
}
fn sigchldignore() void {
var sa: std.c.Sigaction = .{
.handler = .{ .handler = std.c.SIG.IGN },
.mask = std.c.empty_sigset,
.flags = 0,
};
_ = std.c.sigaction(std.c.SIG.CHLD, &sa, null);
}
fn detachstack(win: C.Window) void {
var i: usize = 0;
while (i < stack.items.len) : (i += 1) {
if (stack.items[i] == win) {
_ = stack.orderedRemove(i);
return;
}
}
}
fn attachstack(win: C.Window) !void {
detachstack(win);
try stack.insert(0, win);
}
fn restack() void {
const selected_idx = sel orelse return;
const selected = &cl.items[selected_idx];
if (selected.isfloat) {
_ = C.XRaiseWindow(display, selected.win);
} else {
_ = C.XLowerWindow(display, selected.win);
}
var to_lower = std.ArrayList(C.Window).init(alloc);
defer to_lower.deinit();
for (cl.items, 0..) |client, idx| {
if (idx == selected_idx or client.isfloat) continue;
to_lower.append(client.win) catch continue;
}
for (to_lower.items) |win| {
_ = C.XLowerWindow(display, win);
}
_ = C.XSync(display, 0);
}
fn grabinputs(window: C.Window) void {
_ = C.XUngrabKey(display, C.AnyKey, C.AnyModifier, root);
for (binds) |b| {
_ = C.XGrabKey(
display,
C.XKeysymToKeycode(display, b.sym),
C.Mod4Mask,
window,
0,
C.GrabModeAsync,
C.GrabModeAsync,
);
}
for ([_]u8{ 1, 3 }) |btn| {
const mask = C.ButtonPressMask |
C.ButtonReleaseMask |
C.PointerMotionMask;
_ = C.XGrabButton(
display,
btn,
C.Mod4Mask,
root,
0,
mask,
C.GrabModeAsync,
C.GrabModeAsync,
0,
0,
);
}
}
fn setkeys() void {
for (&keyacts) |*slot| slot.* = null;
for (binds) |b| {
const kc = C.XKeysymToKeycode(display, b.sym);
if (kc < keyacts.len) {
keyacts[kc] = b.action;
}
}
}
fn show() void {
if (hidden.items.len == 0) return;
const client = hidden.orderedRemove(0);
_ = C.XMapWindow(display, client.win);
cl.append(client) catch return;
tile();
focus(cl.items.len - 1);
}
fn hide() void {
const idx = sel orelse return;
const client = cl.orderedRemove(idx);
detachstack(client.win);
_ = C.XUnmapWindow(display, client.win);
hidden.append(client) catch return;
sel = if (cl.items.len > 0) @min(idx, cl.items.len - 1) else null;
focus(sel);
tile();
}
fn scan(allocator: std.mem.Allocator) void {
var root_return: C.Window = undefined;
var parent_return: C.Window = undefined;
var children: [*c]C.Window = undefined;
var num_children: c_uint = 0;
if (C.XQueryTree(
display,
root,
&root_return,
&parent_return,
&children,
&num_children,
) == 0) return;
for (0..num_children) |i| {
var wa: C.XWindowAttributes = undefined;
if (C.XGetWindowAttributes(display, children[i], &wa) == 0) continue;
if (wa.override_redirect != 0 or wa.map_state != C.IsViewable)
continue;
manage(allocator, children[i]);
}
if (children != null) _ = C.XFree(children);
if (cl.items.len > 0) tile();
}
fn floatwins() void {
var last: ?usize = null;
for (cl.items, 0..) |*c, i| {
if (!c.isfloat) continue;
_ = C.XMoveWindow(display, c.win, c.x, c.y);
_ = C.XRaiseWindow(display, c.win);
last = i;
}
if (last) |idx| {
focus(idx);
}
}
inline fn todim(v: c_int) c_uint {
return @intCast(@max(0, v));
}
fn tile() void {
const n = counttilable();
if (n == 0) {
floatwins();
return;
}
const bar_h: c_int = if (bar) |b| b.barH() else 0;
const my: c_int = sy_offset + if (bar) |b| if (b.pos == .top) bar_h else 0 else 0;
const mh: c_int = @intCast(sh - @as(c_uint, @intCast(bar_h)));
const mw: c_int = @intCast(sw);
const n_c: c_int = @intCast(n);
const nmaster_c: c_int = @min(@as(c_int, @intCast(nmaster)), n_c);
const stack_n: c_int = n_c - nmaster_c;
const master_w: c_int = if (stack_n == 0)
mw - 2 * GAP
else
@as(c_int, @intFromFloat(@as(f32, @floatFromInt(mw)) * mfact)) - GAP - GAP / 2;
const stack_w: c_int = mw - master_w - 3 * GAP;
var mi: c_int = 0;
var si: c_int = 0;
for (cl.items) |*c| {
if (c.isfloat) continue;
if (mi < nmaster_c) {
const h = @divTrunc(mh - GAP * (nmaster_c + 1), nmaster_c);
resize(c, sx_offset + GAP, my + GAP + mi * (h + GAP), master_w, h);
mi += 1;
} else {
const h = @divTrunc(mh - GAP * (stack_n + 1), stack_n);
resize(c, sx_offset + master_w + 2 * GAP, my + GAP + si * (h + GAP), stack_w, h);
si += 1;
}
}
floatwins();
}
fn counttilable() usize {
var n: usize = 0;
for (cl.items) |c| {
if (!c.isfloat) n += 1;
}
return n;
}
fn resize(c: *Client, x: c_int, y: c_int, w: c_int, h: c_int) void {
c.x = x;
c.y = y;
c.w = w;
c.h = h;
_ = C.XMoveResizeWindow(display, c.win, x, y, todim(w - 2 * BORDER), todim(h - 2 * BORDER));
}
fn addclient(allocator: std.mem.Allocator, window: C.Window) !usize {
var attributes: C.XWindowAttributes = undefined;
_ = C.XGetWindowAttributes(display, window, &attributes);
const m_float = isfloat(window);
const client = Client{
.x = attributes.x,
.y = attributes.y,
.w = attributes.width,
.h = attributes.height,
.win = window,
.isfloat = m_float,
};
try cl.insert(0, client);
if (sel) |sel_idx| {
sel = sel_idx + 1;
}
sizehints(&cl.items[0]);
title(allocator, &cl.items[0]);
return 0;
}
// refactor
fn isfloat(window: C.Window) bool {
var transient_for: C.Window = undefined;
if (C.XGetTransientForHint(display, window, &transient_for) != 0) {
return true;
}
var class_hint: C.XClassHint = undefined;
if (C.XGetClassHint(display, window, &class_hint) != 0) {
var m_float = false;
if (class_hint.res_class != null) {
const class_name = std.mem.span(
@as([*:0]const u8, @ptrCast(class_hint.res_class)),
);
m_float = (std.mem.indexOf(u8, class_name, "Dialog") != null);
_ = C.XFree(class_hint.res_class);
}
if (class_hint.res_name != null) {
const res_name = std.mem.span(
@as([*:0]const u8, @ptrCast(class_hint.res_name)),
);
m_float = m_float or
(std.mem.indexOf(u8, res_name, "dialog") != null);
_ = C.XFree(class_hint.res_name);
}
if (m_float) {
return true;
}
}
var actual_type: C.Atom = undefined;
var actual_format: c_int = undefined;
var nitems: c_ulong = undefined;
var bytes_after: c_ulong = undefined;
var prop_return: [*c]u8 = undefined;
_ = C.XSetErrorHandler(ignoreerr);
const atom_type = C.XInternAtom(display, "_NET_WM_WINDOW_TYPE", 0);
if (atom_type != 0) {
const status = C.XGetWindowProperty(
display,
window,
atom_type,
0,
32,
0,
C.XA_ATOM,
&actual_type,
&actual_format,
&nitems,
&bytes_after,
&prop_return,
);
const got_atoms = status == 0 and prop_return != null;
const good_format =
actual_type == C.XA_ATOM and actual_format == 32 and nitems > 0;
if (got_atoms and good_format) {
const atoms = @as([*]C.Atom, @ptrCast(@alignCast(prop_return)));
const atom_dialog = C.XInternAtom(
display,
"_NET_WM_WINDOW_TYPE_DIALOG",
0,
);
const atom_utility = C.XInternAtom(
display,
"_NET_WM_WINDOW_TYPE_UTILITY",
0,
);
const atom_popup = C.XInternAtom(
display,
"_NET_WM_WINDOW_TYPE_POPUP_MENU",
0,
);
for (0..nitems) |i| {
const atom = atoms[i];
if (atom == atom_dialog or
atom == atom_utility or
atom == atom_popup)
{
_ = C.XFree(prop_return);
_ = C.XSetErrorHandler(xerr);
return true;
}
}
_ = C.XFree(prop_return);
}
}
_ = C.XSetErrorHandler(xerr);
return false;
}
fn focus(target_index_opt: ?usize) void {
if (cl.items.len == 0) {
sel = null;
if (bar) |b_ptr| b_ptr.setSel(null);
_ = C.XSetInputFocus(
display,
root,
C.RevertToPointerRoot,
C.CurrentTime,
);
_ = C.XFlush(display);
return;
}
const target_index = target_index_opt orelse {
if (sel) |sel_idx| {
if (sel_idx < cl.items.len) {
_ = C.XSetErrorHandler(ignoreerr);
_ = C.XSetWindowBorder(
display,
cl.items[sel_idx].win,
NORMAL_COL,
);
_ = C.XSetErrorHandler(xerr);
}
}
sel = null;
if (bar) |b_ptr| b_ptr.setSel(null);
_ = C.XSetInputFocus(
display,
root,
C.RevertToPointerRoot,
C.CurrentTime,
);
_ = C.XFlush(display);
return;
};
var index = target_index;
if (index >= cl.items.len) {
index = 0;
}
if (sel) |sel_idx| {
if (sel_idx < cl.items.len and sel_idx != index) {
_ = C.XSetErrorHandler(ignoreerr);
_ = C.XSetWindowBorder(display, cl.items[sel_idx].win, NORMAL_COL);
_ = C.XSetErrorHandler(xerr);
}
}
const client = &cl.items[index];
detachstack(client.win);
attachstack(client.win) catch {};
_ = C.XSetErrorHandler(ignoreerr);
_ = C.XSetInputFocus(display, client.win, C.RevertToParent, C.CurrentTime);
_ = C.XRaiseWindow(display, client.win);
_ = C.XSetWindowBorder(display, client.win, FOCUS_COL);
_ = C.XChangeProperty(
display,
root,
atom_active,
C.XA_WINDOW,
32,
C.PropModeReplace,
@ptrCast(&client.win),
1,
);
_ = C.XSetErrorHandler(xerr);
sel = index;
if (bar) |b_ptr| b_ptr.setSel(client.win);
_ = C.XFlush(display);
}
fn getclient(w: C.Window) ?usize {
for (cl.items, 0..) |client, idx| {
if (client.win == w) return idx;
}
return null;
}
fn hiddenidx(w: C.Window) ?usize {
for (hidden.items, 0..) |client, idx| {
if (client.win == w) return idx;
}
return null;
}
fn purgehidden(w: C.Window) void {
if (hiddenidx(w)) |idx| {
_ = hidden.orderedRemove(idx);
detachstack(w);
}
}
fn nextfocus(exclude_win: C.Window) ?usize {
for (stack.items) |win| {
if (win == exclude_win) continue;
if (getclient(win)) |idx| {
return idx;
}
}
return null;
}
fn unmanage(index: usize, destroyed: bool) void {
if (index >= cl.items.len) return;
_ = C.XGrabServer(display);
_ = C.XSetErrorHandler(ignoreerr);
const client = cl.items[index];
if (!destroyed) {
_ = C.XSelectInput(display, client.win, C.NoEventMask);
_ = C.XUngrabButton(display, C.AnyButton, C.AnyModifier, client.win);
const data = [_]c_long{ C.WithdrawnState, C.None };
_ = C.XChangeProperty(
display,
client.win,
atom_state,
atom_state,
32,
C.PropModeReplace,
@ptrCast(&data),
data.len,
);
}
var removed_was_selected = false;
if (sel) |sel_idx| {
if (sel_idx == index) {
removed_was_selected = true;
} else if (sel_idx > index) {
sel = sel_idx - 1;
}
}
detachstack(client.win);
_ = cl.orderedRemove(index);
var next_focus_index: ?usize = null;
if (removed_was_selected) {
sel = null;
next_focus_index = nextfocus(client.win);
}
_ = C.XSync(display, 0);
_ = C.XSetErrorHandler(xerr);
_ = C.XUngrabServer(display);
if (cl.items.len > 0) {
tile();
const fallback = next_focus_index orelse blk: {
if (index < cl.items.len) break :blk index;
break :blk cl.items.len - 1;
};
focus(fallback);
} else {
focus(null);
_ = C.XSync(display, 0);
}
}
var sw: c_uint = 0;
var sh: c_uint = 0;
var sx_offset: c_int = 0;
var sy_offset: c_int = 0;
var srotation: c_uint = 0;
var rr_event: c_int = 0;
var rr_error: c_int = 0;
var bar: ?*bar_mod.Bar = null;
const NS_PER_MS: u64 = 1_000_000;
fn initrandr(allocator: std.mem.Allocator) !void {
if (C.XRRQueryExtension(display, &rr_event, &rr_error) == 0) {
return;
}
try geom(allocator);
const rr_mask =
C.RROutputChangeNotifyMask |
C.RRCrtcChangeNotifyMask |
C.RRScreenChangeNotifyMask;
_ = C.XRRSelectInput(display, root, rr_mask);
}
fn geom(allocator: std.mem.Allocator) !void {
var res = C.XRRGetScreenResourcesCurrent(display, root);
if (res == null) {
res = C.XRRGetScreenResources(display, root);
}
if (res == null) {
const x_screen = C.DefaultScreen(display);
sw = @intCast(C.XDisplayWidth(display, x_screen));
sh = @intCast(C.XDisplayHeight(display, x_screen));
sx_offset = 0;
sy_offset = 0;
srotation = 0;
return;
}
defer C.XRRFreeScreenResources(res);
var found_active_monitor = false;
const primary_output = C.XRRGetOutputPrimary(display, root);
if (primary_output != 0) {
found_active_monitor = try usemon(
allocator,
res,
primary_output,
"primary",
);
}
if (!found_active_monitor) {
found_active_monitor = try lmon(allocator, res);
}
if (!found_active_monitor) {
const x_screen = C.DefaultScreen(display);
sw = @intCast(C.XDisplayWidth(display, x_screen));
sh = @intCast(C.XDisplayHeight(display, x_screen));
sx_offset = 0;
sy_offset = 0;
srotation = 0;
}
}
fn usemon(
allocator: std.mem.Allocator,
res: *C.XRRScreenResources,
output_id: C.RROutput,
monitor_type: []const u8,
) !bool {
_ = allocator;
const output_info = C.XRRGetOutputInfo(display, res, output_id);
if (output_info == null) return false;
defer C.XRRFreeOutputInfo(output_info);
if (output_info.*.connection != C.RR_Connected or output_info.*.crtc == 0) {
return false;
}
const crtc_info = C.XRRGetCrtcInfo(display, res, output_info.*.crtc);
if (crtc_info == null) return false;
defer C.XRRFreeCrtcInfo(crtc_info);
if (crtc_info.*.width == 0 or crtc_info.*.height == 0) {
return false;
}
sw = @intCast(crtc_info.*.width);
sh = @intCast(crtc_info.*.height);
sx_offset = crtc_info.*.x;
sy_offset = crtc_info.*.y;
srotation = @intCast(crtc_info.*.rotation);
const output_name = std.mem.span(
@as([*:0]const u8, @ptrCast(output_info.*.name)),
);
std.log.info("Using {s} monitor: {s} at ({d},{d}) size {d}x{d}", .{
monitor_type,
output_name,
sx_offset,
sy_offset,
sw,
sh,
});
return true;
}
fn lmon(_: std.mem.Allocator, res: *C.XRRScreenResources) !bool {
var largest_width: c_uint = 0;
var largest_height: c_uint = 0;
var largest_area: c_uint = 0;
var largest_x: c_int = 0;
var largest_y: c_int = 0;
var largest_rotation: c_uint = 0;
const output_count: usize = @intCast(res.*.noutput);
for (0..output_count) |i| {
const output_info = C.XRRGetOutputInfo(display, res, res.*.outputs[i]);
if (output_info == null) continue;
defer C.XRRFreeOutputInfo(output_info);
if (output_info.*.connection != C.RR_Connected or
output_info.*.crtc == 0)
{
continue;
}
const crtc_info = C.XRRGetCrtcInfo(display, res, output_info.*.crtc);
if (crtc_info == null) continue;
defer C.XRRFreeCrtcInfo(crtc_info);
if (crtc_info.*.width == 0 or crtc_info.*.height == 0) continue;
const output_width: c_uint = @intCast(crtc_info.*.width);
const output_height: c_uint = @intCast(crtc_info.*.height);
const output_area = output_width * output_height;
if (output_area > largest_area) {
largest_area = output_area;
largest_width = output_width;
largest_height = output_height;
largest_x = crtc_info.*.x;
largest_y = crtc_info.*.y;
largest_rotation = @intCast(crtc_info.*.rotation);
}
}
if (largest_area > 0) {
sw = largest_width;
sh = largest_height;
sx_offset = largest_x;
sy_offset = largest_y;
srotation = largest_rotation;
return true;
}
return false;
}
fn syncmon(allocator: std.mem.Allocator) void {
geom(allocator) catch {};
}
fn onrr(allocator: std.mem.Allocator, e: *C.XEvent) !void {
const rrev = @as(*C.XRRNotifyEvent, @ptrCast(e));
switch (rrev.subtype) {
C.RRNotify_OutputChange,
C.RRNotify_CrtcChange,
=> try refreshmon(allocator),
else => {},
}
}
fn onrrscreen(allocator: std.mem.Allocator, e: *C.XEvent) !void {
_ = C.XRRUpdateConfiguration(e);
try refreshmon(allocator);
}
fn refreshmon(allocator: std.mem.Allocator) !void {
try geom(allocator);
if (bar) |b_ptr| {
b_ptr.resize(sw, sx_offset, sy_offset, sh);
b_ptr.mark();
}
if (cl.items.len > 0) {
tile();
}
}
fn runautostart() void {
const pid = posix.fork() catch return;
if (pid == 0) {
_ = setsid();
const args = [_:null]?[*:0]const u8{ "/bin/sh", autostart, null };
_ = execvp("/bin/sh", &args);
posix.exit(1);
}
}
fn killclient() void {
const index = sel orelse return;
if (index >= cl.items.len) return;
const client_win = cl.items[index].win;
if (protodel(index)) {
_ = C.XSetErrorHandler(ignoreerr);
sendev(client_win, atom_proto, @as(c_long, @bitCast(atom_del)));
_ = C.XSetErrorHandler(xerr);
} else {
_ = C.XGrabServer(display);
_ = C.XSetErrorHandler(ignoreerr);
_ = C.XKillClient(display, client_win);
_ = C.XSync(display, 0);
_ = C.XSetErrorHandler(xerr);
_ = C.XUngrabServer(display);
}
_ = C.XSync(display, 0);
}
fn protodel(client_index: usize) bool {
if (client_index >= cl.items.len) return false;
const client = cl.items[client_index];
var protocols: [*c]C.Atom = undefined;
var n: c_int = 0;
var ret = false;
if (C.XGetWMProtocols(display, client.win, &protocols, &n) != 0) {
const hint_count: usize = @intCast(n);
for (0..hint_count) |i| {
if (protocols[i] == atom_del) {
ret = true;
break;
}
}
_ = C.XFree(protocols);
}
return ret;
}
fn sendev(win: C.Window, atom: C.Atom, value: c_long) void {
var event: C.XEvent = undefined;
event.type = C.ClientMessage;
event.xclient.window = win;
event.xclient.message_type = atom;
event.xclient.format = 32;
event.xclient.data.l[0] = value;
event.xclient.data.l[1] = C.CurrentTime;
_ = C.XSendEvent(display, win, 0, C.NoEventMask, &event);
_ = C.XSync(display, 0);
}
fn sizehints(client: *Client) void {
var size: C.XSizeHints = undefined;
var msize: c_long = 0;
if (C.XGetWMNormalHints(display, client.win, &size, &msize) == 0 or
size.flags == 0)
{
size.flags = C.PSize;
}
client.flags = size.flags;
if ((client.flags & C.PBaseSize) != 0) {
client.basew = size.base_width;
client.baseh = size.base_height;
} else {
client.basew = 0;
client.baseh = 0;
}
if ((client.flags & C.PResizeInc) != 0) {
client.incw = size.width_inc;
client.inch = size.height_inc;
} else {
client.incw = 0;
client.inch = 0;
}
if ((client.flags & C.PMaxSize) != 0) {
client.maxw = size.max_width;
client.maxh = size.max_height;
} else {
client.maxw = 0;
client.maxh = 0;
}
if ((client.flags & C.PMinSize) != 0) {
client.minw = size.min_width;
client.minh = size.min_height;
} else {
client.minw = 0;
client.minh = 0;
}
if ((client.flags & C.PAspect) != 0) {
client.minax = size.min_aspect.x;
client.minay = size.min_aspect.y;
client.maxax = size.max_aspect.x;
client.maxay = size.max_aspect.y;
} else {
client.minax = 0;
client.minay = 0;
client.maxax = 0;
client.maxay = 0;
}
client.isfixed = (client.maxw != 0 and client.minw != 0 and
client.maxh != 0 and client.minh != 0 and
client.maxw == client.minw and
client.maxh == client.minh);
}
fn title(_: std.mem.Allocator, client: *Client) void {
var name: C.XTextProperty = undefined;
client.name[0] = 0;
_ = C.XGetTextProperty(display, client.win, &name, atom_name);
if (name.nitems == 0) {
_ = C.XGetWMName(display, client.win, &name);
}
if (name.nitems == 0) return;
if (name.encoding == C.XA_STRING) {
const title_len = @min(name.nitems, client.name.len - 1);
@memcpy(client.name[0..title_len], name.value[0..title_len]);
client.name[title_len] = 0;
} else {
var list: [*c][*c]u8 = undefined;
var count: c_int = 0;
if (C.XmbTextPropertyToTextList(display, &name, &list, &count) >= 0 and
count > 0)
{
const src_title = std.mem.span(
@as([*:0]const u8, @ptrCast(list[0])),
);
const title_len = @min(src_title.len, client.name.len - 1);
@memcpy(client.name[0..title_len], src_title[0..title_len]);
client.name[title_len] = 0;
_ = C.XFreeStringList(list);
}
}
_ = C.XFree(name.value);
}
fn winnext() void {
if (cl.items.len == 0) return;
if (sel) |idx| {
if (idx + 1 < cl.items.len) {
focus(idx + 1);
} else {
focus(0);
}
} else {
focus(0);
}
}
fn winprev() void {
if (cl.items.len == 0) return;
if (sel) |idx| {
if (idx > 0) {
focus(idx - 1);
} else {
focus(cl.items.len - 1);
}
} else {
focus(0);
}
}
extern fn execvp(prog: [*:0]const u8, argv: [*]const ?[*:0]const u8) c_int;
extern fn setsid() c_int;
fn spawn(cmd: [*:0]const u8) void {
const pid = posix.fork() catch return;
if (pid == 0) {
_ = std.c.close(C.ConnectionNumber(display));
_ = setsid();
var args = [_:null]?[*:0]const u8{ cmd, null };
_ = execvp(cmd, &args);
posix.exit(1);
}
}
fn xerr(_: ?*C.Display, event: [*c]C.XErrorEvent) callconv(.C) c_int {
if (@as(*C.XErrorEvent, @ptrCast(event)).error_code == C.BadAccess)
std.log.err("another wm running", .{});
return 0;
}
fn ignoreerr(_: ?*C.Display, _: [*c]C.XErrorEvent) callconv(.C) c_int {
return 0;
}
fn onconfig(e: *C.XConfigureRequestEvent) void {
wc.x = e.x;
wc.y = e.y;
wc.width = e.width;
wc.height = e.height;
wc.border_width = e.border_width;
wc.sibling = e.above;
wc.stack_mode = e.detail;
_ = C.XSetErrorHandler(ignoreerr);
_ = C.XConfigureWindow(display, e.window, @intCast(e.value_mask), &wc);
_ = C.XSync(display, 0);
_ = C.XSetErrorHandler(xerr);
}
fn onmap(allocator: std.mem.Allocator, event: *C.XEvent) !void {
const window: C.Window = event.xmaprequest.window;
var wa: C.XWindowAttributes = undefined;
if (C.XGetWindowAttributes(display, window, &wa) == 0) {
return;
}
if (wa.override_redirect != 0) {
return;
}
if (getclient(window) != null) {
return;
}
_ = C.XSetErrorHandler(ignoreerr);
_ = C.XSelectInput(
display,
window,
C.StructureNotifyMask | C.EnterWindowMask,
);
_ = C.XSetWindowBorderWidth(display, window, BORDER);
_ = C.XSetWindowBorder(display, window, NORMAL_COL);
_ = C.XSetErrorHandler(xerr);
const index = addclient(allocator, window) catch |err| {
_ = C.XSetErrorHandler(ignoreerr);
_ = C.XUnmapWindow(display, window);
_ = C.XSync(display, 0);
_ = C.XSetErrorHandler(xerr);
return err;
};
const client = &cl.items[index];
_ = C.XSetErrorHandler(ignoreerr);
const screen_width_i: c_int = @intCast(sw);
_ = C.XMoveWindow(display, window, client.x + 2 * screen_width_i, client.y);
_ = C.XMapWindow(display, window);
_ = C.XSetErrorHandler(xerr);
const data = [_]c_long{ C.NormalState, C.None };
_ = C.XChangeProperty(
display,
window,
atom_state,
atom_state,
32,
C.PropModeReplace,
@ptrCast(&data),
data.len,
);
tile();
focus(index);
if (client.isfloat) {
_ = C.XSetErrorHandler(ignoreerr);
_ = C.XRaiseWindow(display, client.win);
_ = C.XSetErrorHandler(xerr);
focus(index);
}
_ = C.XSync(display, 0);
}
fn manage(allocator: std.mem.Allocator, win: C.Window) void {
_ = C.XSelectInput(display, win, C.StructureNotifyMask | C.EnterWindowMask);
_ = C.XSetWindowBorderWidth(display, win, BORDER);
_ = C.XSetWindowBorder(display, win, NORMAL_COL);
if (addclient(allocator, win)) |idx| {
focus(idx);
} else |_| {}
}
fn onunmap(_: std.mem.Allocator, e: *C.XEvent) void {
const ev = &e.xunmap;
if (getclient(ev.window)) |index| {
if (ev.send_event == 1) {
const data = [_]c_long{ C.WithdrawnState, C.None };
_ = C.XChangeProperty(
display,
ev.window,
atom_state,
atom_state,
32,
C.PropModeReplace,
@ptrCast(&data),
data.len,
);
} else {
unmanage(index, false);
}
}
}
fn onkey(e: *C.XEvent) void {
if (e.xkey.keycode < keyacts.len) {
if (keyacts[e.xkey.keycode]) |action| action();
}
}
fn onenter(e: *C.XEvent) void {
for (cl.items) |client| {
if (client.isfloat) return;
}
if (e.xcrossing.mode != C.NotifyNormal or
e.xcrossing.detail == C.NotifyInferior)
{
return;
}
_ = C.XSetErrorHandler(ignoreerr);
defer _ = C.XSetErrorHandler(xerr);
if (getclient(e.xcrossing.window)) |index| {
if (sel) |sel_idx| {
if (sel_idx < cl.items.len and
cl.items[sel_idx].win == e.xcrossing.window)
{
return;
}
}
if (!cl.items[index].isfloat) {
focus(index);
}
}
}
fn onbtn(e: *C.XEvent) void {
if (e.xbutton.subwindow == 0) return;
_ = C.XSetErrorHandler(ignoreerr);
defer _ = C.XSetErrorHandler(xerr);
var attributes: C.XWindowAttributes = undefined;
if (C.XGetWindowAttributes(
display,
e.xbutton.subwindow,
&attributes,
) == 0) {
return;
}
win_w = attributes.width;
win_h = attributes.height;
win_x = attributes.x;
win_y = attributes.y;
const grab_mask = C.ButtonReleaseMask | C.PointerMotionMask;
if (e.xbutton.button == 3 and (e.xbutton.state & C.Mod4Mask) != 0) {
const grab = C.XGrabPointer(
display,
e.xbutton.subwindow,
1,
grab_mask,
C.GrabModeAsync,
C.GrabModeAsync,
C.None,
cur_resize,
C.CurrentTime,
);
if (grab == C.GrabSuccess) {
_ = C.XWarpPointer(
display,
C.None,
e.xbutton.subwindow,
0,
0,
0,
0,
@intCast(attributes.width),
@intCast(attributes.height),
);
mouse = e.xbutton;
var dummy_root: C.Window = undefined;
var dummy_child: C.Window = undefined;
var new_root_x: c_int = undefined;
var new_root_y: c_int = undefined;
var win_x_pos: c_int = undefined;
var win_y_pos: c_int = undefined;
var mask: c_uint = undefined;
_ = C.XQueryPointer(
display,
e.xbutton.subwindow,
&dummy_root,
&dummy_child,
&new_root_x,
&new_root_y,
&win_x_pos,
&win_y_pos,
&mask,
);
mouse.x_root = @intCast(new_root_x);
mouse.y_root = @intCast(new_root_y);
}
} else if (e.xbutton.button == 1 and (e.xbutton.state & C.Mod4Mask) != 0) {
const grab = C.XGrabPointer(
display,
e.xbutton.subwindow,
1,
grab_mask,
C.GrabModeAsync,
C.GrabModeAsync,
C.None,
cur_move,
C.CurrentTime,
);
if (grab == C.GrabSuccess) {
mouse = e.xbutton;
}
}
if (getclient(e.xbutton.subwindow)) |index| {
if (sel == null or sel.? != index) {
focus(index);
}
}
_ = C.XSync(display, 0);
}
fn onmotion(e: *C.XEvent) void {
if (mouse.subwindow == 0) return;
const dx: i32 = @intCast(e.xbutton.x_root - mouse.x_root);
const dy: i32 = @intCast(e.xbutton.y_root - mouse.y_root);
const button: i32 = @intCast(mouse.button);
if (button == 1) {
_ = C.XMoveWindow(display, mouse.subwindow, win_x + dx, win_y + dy);
} else if (button == 3) {
const new_w: c_int = @intCast(@max(10, win_w + dx));
const new_h: c_int = @intCast(@max(10, win_h + dy));
_ = C.XMoveResizeWindow(
display,
mouse.subwindow,
win_x,
win_y,
todim(new_w),
todim(new_h),
);
}
_ = C.XSync(display, 0);
}
fn ondestroy(_: std.mem.Allocator, e: *C.XEvent) void {
const ev = &e.xdestroywindow;
if (getclient(ev.window)) |index| {
unmanage(index, true);
} else {
purgehidden(ev.window);
}
}
fn onbtnup(_: *C.XEvent) void {
if (mouse.subwindow != 0) {
_ = C.XUngrabPointer(display, C.CurrentTime);
}
mouse.subwindow = 0;
}
var shouldquit = false;
var display: *C.Display = undefined;
var root: C.Window = undefined;
var wc: C.XWindowChanges = undefined;
var lists = false;
var cur = false;
var disp = false;
var barset = false;
var win_x: i32 = 0;
var win_y: i32 = 0;
var win_w: i32 = 0;
var win_h: i32 = 0;
var mouse: C.XButtonEvent = undefined;
fn setbar(allocator: std.mem.Allocator, screen: c_int) !void {
if (bar != null) return;
const bar_ptr = allocator.create(bar_mod.Bar) catch |err| return err;
bar_ptr.* = bar_mod.Bar.init(
allocator,
display,
root,
screen,
sw,
sx_offset,
sy_offset,
sh,
.top,
bar_font,
delim,
) catch |err| {
allocator.destroy(bar_ptr);
return err;
};
bar_mod.setGlobalBar(bar_ptr);
bar = bar_ptr;
barset = true;
try bar_ptr.addBat(30000);
try bar_ptr.addWin(100);
try bar_ptr.addClock(1000);
}
fn setup(allocator: std.mem.Allocator) !void {
errdefer cleanup();
cl = std.ArrayList(Client).init(allocator);
hidden = std.ArrayList(Client).init(allocator);
stack = std.ArrayList(C.Window).init(allocator);
lists = true;
display = C.XOpenDisplay(0) orelse {
std.c._exit(1);
};
disp = true;
cur_resize = C.XCreateFontCursor(display, 120);
cur_move = C.XCreateFontCursor(display, 52);
cur_normal = C.XCreateFontCursor(display, 68);
cur = true;
const screen = C.DefaultScreen(display);
root = C.RootWindow(display, screen);
sw = @intCast(C.XDisplayWidth(display, screen));
sh = @intCast(C.XDisplayHeight(display, screen));
sx_offset = 0;
sy_offset = 0;
srotation = 0;
// setup atoms
atom_proto = C.XInternAtom(display, "WM_PROTOCOLS", 0);
atom_del = C.XInternAtom(display, "WM_DELETE_WINDOW", 0);
atom_state = C.XInternAtom(display, "WM_STATE", 0);
atom_net = C.XInternAtom(display, "_NET_SUPPORTED", 0);
atom_name = C.XInternAtom(display, "_NET_WM_NAME", 0);
atom_active = C.XInternAtom(display, "_NET_ACTIVE_WINDOW", 0);
const supported_atoms = [_]C.Atom{ atom_net, atom_name, atom_active };
_ = C.XChangeProperty(
display,
root,
atom_net,
C.XA_ATOM,
32,
C.PropModeReplace,
@ptrCast(&supported_atoms),
supported_atoms.len,
);
try initrandr(allocator);
try setbar(allocator, screen);
_ = C.XSetErrorHandler(xerr);
_ = C.XSelectInput(
display,
root,
C.SubstructureRedirectMask | C.EnterWindowMask,
);
_ = C.XDefineCursor(display, root, C.XCreateFontCursor(display, 68));
grabinputs(root);
setkeys();
sigchldignore();
runautostart();
syncmon(allocator);
if (bar) |b_ptr| {
b_ptr.resize(sw, sx_offset, sy_offset, sh);
b_ptr.mark();
}
_ = C.XSync(display, 0);
scan(allocator);
if (bar) |b_ptr| {
const now_ms = std.time.milliTimestamp();
_ = b_ptr.tick(now_ms) catch {};
b_ptr.draw();
}
}
fn cleanup() void {
if (barset) {
if (bar) |b_ptr| {
b_ptr.deinit();
alloc.destroy(b_ptr);
}
bar = null;
barset = false;
}
if (cur and disp) {
_ = C.XFreeCursor(display, cur_resize);
_ = C.XFreeCursor(display, cur_move);
_ = C.XFreeCursor(display, cur_normal);
cur = false;
}
if (disp) {
_ = C.XCloseDisplay(display);
disp = false;
}
if (lists) {
stack.deinit();
hidden.deinit();
cl.deinit();
lists = false;
}
}
fn run(allocator: std.mem.Allocator) !void {
var event: C.XEvent = undefined;
while (!shouldquit) {
const pending = C.XPending(display);
if (pending == 0) {
var sleep_ns: u64 = 50 * NS_PER_MS;
if (bar) |b_ptr| {
const now_ms = std.time.milliTimestamp();
_ = b_ptr.tick(now_ms) catch {};
if (b_ptr.dirty) b_ptr.draw();
const wait_ms = b_ptr.nextDelay(now_ms);
const desired_ns = wait_ms * NS_PER_MS;
if (desired_ns < sleep_ns) {
sleep_ns = desired_ns;
}
}
if (sleep_ns > 0) {
std.time.sleep(sleep_ns);
}
continue;
}
_ = C.XNextEvent(display, &event);
switch (event.type) {
C.MapRequest => try onmap(allocator, &event),
C.UnmapNotify => onunmap(allocator, &event),
C.KeyPress => onkey(&event),
C.ButtonPress => onbtn(&event),
C.ButtonRelease => onbtnup(&event),
C.MotionNotify => onmotion(&event),
C.DestroyNotify => ondestroy(allocator, &event),
C.ConfigureRequest => onconfig(@ptrCast(&event)),
C.EnterNotify => onenter(&event),
C.Expose => {
if (bar) |b_ptr| {
if (event.xexpose.window == b_ptr.win and
event.xexpose.count == 0)
{
b_ptr.mark();
b_ptr.draw();
}
}
},
else => {
if (rr_event != 0) {
if (event.type == rr_event + C.RRScreenChangeNotify) {
try onrrscreen(allocator, &event);
} else if (event.type == rr_event + C.RRNotify) {
try onrr(allocator, &event);
}
}
},
}
if (bar) |b_ptr| {
const now_ms = std.time.milliTimestamp();
_ = b_ptr.tick(now_ms) catch {};
if (b_ptr.dirty) b_ptr.draw();
}
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer _ = gpa.deinit();
alloc = allocator;
try setup(allocator);
defer cleanup();
try run(allocator);
}