2zw - X11 Windowmanager
Files | Log | Commits | Refs | README
Size: 34136 bytes
const std = @import("std");
const C = @import("c.zig");
const Client = struct {
full: bool,
wx: c_int,
wy: c_int,
ww: c_int,
wh: c_int,
w: C.Window,
is_floating: bool = false,
};
const L = std.DoublyLinkedList(Client);
var hl = L{};
var list = L{};
var cursor: ?*L.Node = null;
var previously_focused: ?*L.Node = undefined;
const FOCUS_BORDER_COLOR = 0x6699cc;
const NORMAL_BORDER_COLOR = 0x333333;
const BORDER_WIDTH = 1;
const GAP_SIZE = 0;
const MASTER_FACTOR = 0.6;
var master_factor: f32 = MASTER_FACTOR;
var n_master: usize = 1;
const terminal = "st";
const launcher = "zmen";
const autostart_path = "/home/smi/.scripts/ewm.sh";
var resize_cursor: C.Cursor = undefined;
var move_cursor: C.Cursor = undefined;
var normal_cursor: C.Cursor = undefined;
const Key = struct {
keysym: C.KeySym,
action: *const fn () void,
};
var keymap: std.AutoHashMap(c_uint, *const fn () void) = undefined;
const keys = [_]Key{
.{ .keysym = C.XK_q, .action = &killClient },
.{ .keysym = C.XK_comma, .action = &winPrev },
.{ .keysym = C.XK_period, .action = &winNext },
.{ .keysym = C.XK_a, .action = &attachWindow },
.{ .keysym = C.XK_d, .action = &detachWindow },
.{ .keysym = C.XK_h, .action = struct {
fn action() void {
master_factor = @max(0.1, master_factor - 0.05);
applyLayout();
}
}.action },
.{
.keysym = C.XK_l,
.action = struct {
fn action() void {
master_factor = @min(0.9, master_factor + 0.05);
applyLayout();
}
}.action,
},
.{ .keysym = C.XK_Return, .action = struct {
fn action() void {
spawn(terminal);
}
}.action },
.{ .keysym = C.XK_p, .action = struct {
fn action() void {
spawn(launcher);
}
}.action },
.{ .keysym = C.XK_s, .action = struct {
fn action() void {
spawn("slock");
}
}.action },
// add more in the format:
// .{ .keysym = C.XK_b, .action = struct {
// fn action() void { spawn("firefox"); }
// }.action },
};
fn logError(msg: []const u8) void {
const stderr = std.io.getStdErr().writer();
stderr.print("Error: {s}\n", .{msg}) catch return;
}
fn logInfo(msg: []const u8) void {
const stdInfo = std.io.getStdOut().writer();
stdInfo.print("INFO: {s}\n", .{msg}) catch return;
}
fn initKeyMap(allocator: std.mem.Allocator) !std.AutoHashMap(c_uint, *const fn () void) {
var map = std.AutoHashMap(c_uint, *const fn () void).init(allocator);
errdefer map.deinit();
for (keys) |key| {
try map.put(C.XKeysymToKeycode(display, key.keysym), key.action);
}
return map;
}
fn grabInput(window: C.Window) void {
_ = C.XUngrabKey(display, C.AnyKey, C.AnyModifier, root);
for (keys) |key| {
_ = C.XGrabKey(display, C.XKeysymToKeycode(display, key.keysym), C.Mod4Mask, window, 0, C.GrabModeAsync, C.GrabModeAsync);
}
for ([_]u8{ 1, 3 }) |btn| {
_ = C.XGrabButton(display, btn, C.Mod4Mask, root, 0, C.ButtonPressMask | C.ButtonReleaseMask | C.PointerMotionMask, C.GrabModeAsync, C.GrabModeAsync, 0, 0);
}
}
fn attachWindow() void {
if (hl.len == 0) return;
const node_to_attach = hl.first.?;
hl.remove(node_to_attach);
_ = C.XMapWindow(display, node_to_attach.data.w);
_ = C.XSync(display, 0);
list.append(node_to_attach);
focus(node_to_attach);
applyLayout();
}
fn detachWindow() void {
if (cursor == null) return;
const node_to_detach = cursor.?;
if (cursor.?.next) |next| {
focus(next);
} else if (cursor.?.prev) |prev| {
focus(prev);
} else {
cursor = null;
}
list.remove(node_to_detach);
_ = C.XUnmapWindow(display, node_to_detach.data.w);
_ = C.XSync(display, 0);
hl.append(node_to_detach);
applyLayout();
}
fn initialWin(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) {
if (num_children > 0) {
for (0..num_children) |i| {
const win = children[i];
var wa: C.XWindowAttributes = undefined;
_ = C.XSetErrorHandler(ignoreError);
const status = C.XGetWindowAttributes(display, win, &wa);
_ = C.XSetErrorHandler(handleError);
if (status != 0 and wa.map_state == C.IsViewable and wa.override_redirect == 0) {
_ = C.XSelectInput(display, win, C.StructureNotifyMask | C.EnterWindowMask);
_ = C.XSetWindowBorderWidth(display, win, BORDER_WIDTH);
if (addClient(allocator, win)) |node| {
focus(node);
} else |err| {
const err_msg = std.fmt.allocPrint(allocator, "Error adding existing window: {any}", .{err}) catch "Error adding window";
defer if (@TypeOf(err_msg) == []const u8) allocator.free(err_msg);
logError(err_msg);
}
}
}
_ = C.XFree(children);
if (list.len > 0) {
applyLayout();
}
}
}
}
fn handleFloatingWindows() void {
var node = list.first;
while (node) |n| : (node = n.next) {
if (n.data.is_floating) {
_ = C.XRaiseWindow(display, n.data.w);
if (cursor) |c| {
_ = C.XSetWindowBorder(display, c.data.w, NORMAL_BORDER_COLOR);
}
_ = C.XSetWindowBorder(display, n.data.w, FOCUS_BORDER_COLOR);
_ = C.XSetInputFocus(display, n.data.w, C.RevertToParent, C.CurrentTime);
cursor = n;
const net_active_window = C.XInternAtom(display, "_NET_ACTIVE_WINDOW", 0);
_ = C.XChangeProperty(display, root, net_active_window, C.XA_WINDOW, 32, C.PropModeReplace, @ptrCast(&n.data.w), 1);
_ = C.XFlush(display);
}
}
}
fn applyLayout() void {
if (list.len == 0) return;
handleFloatingWindows();
var tiled_count: usize = 0;
var next = list.first;
while (next) |node| : (next = node.next) {
if (!node.data.is_floating) {
tiled_count += 1;
}
}
if (tiled_count == 0) return;
const sw: c_int = @intCast(screen_w);
const sh: c_int = @intCast(screen_h);
if (tiled_count == 1) {
next = list.first;
while (next) |node| : (next = node.next) {
if (!node.data.is_floating) {
_ = C.XMoveResizeWindow(display, node.data.w, GAP_SIZE, GAP_SIZE, @as(c_uint, @intCast(@max(0, screen_w - (2 * GAP_SIZE) - (2 * BORDER_WIDTH)))), @as(c_uint, @intCast(@max(0, screen_h - (2 * GAP_SIZE) - (2 * BORDER_WIDTH)))));
break;
}
}
return;
}
const master_width = @as(c_uint, @intCast(@as(c_int, @intFromFloat(master_factor * @as(f32, @floatFromInt(sw))))));
var actual_masters: usize = 0;
var masters_found: usize = 0;
next = list.first;
while (next) |node| : (next = node.next) {
if (!node.data.is_floating) {
if (masters_found < n_master) {
if (actual_masters == 0) {
actual_masters = 1;
var temp = next;
while (temp) |t| : (temp = t.next) {
if (!t.data.is_floating and masters_found < n_master) {
masters_found += 1;
}
}
}
if (masters_found == 1) {
_ = C.XMoveResizeWindow(display, node.data.w, GAP_SIZE, GAP_SIZE, @as(c_uint, @intCast(@max(0, master_width - (2 * GAP_SIZE) - (2 * BORDER_WIDTH)))), @as(c_uint, @intCast(@max(0, sh - (2 * GAP_SIZE) - (2 * BORDER_WIDTH)))));
} else {
const gap_total = @as(c_int, @intCast((masters_found + 1) * GAP_SIZE));
const master_height = @divTrunc(sh - gap_total, @as(c_int, @intCast(masters_found)));
const master_index = actual_masters - 1; // 0-based index
const y_position = GAP_SIZE + (@as(c_int, @intCast(master_index)) * (master_height + GAP_SIZE));
_ = C.XMoveResizeWindow(display, node.data.w, GAP_SIZE, y_position, @as(c_uint, @intCast(@max(0, master_width - (2 * GAP_SIZE) - (2 * BORDER_WIDTH)))), @as(c_uint, @intCast(@max(0, master_height - (2 * BORDER_WIDTH)))));
}
actual_masters += 1;
}
}
}
const stack_windows = tiled_count - masters_found;
if (stack_windows > 0) {
const stack_width = sw - @as(c_int, @intCast(master_width)) - GAP_SIZE;
var stack_i: usize = 0;
next = list.first;
while (next) |node| : (next = node.next) {
if (!node.data.is_floating) {
var is_master = false;
var master_idx: usize = 0;
var check = list.first;
while (check != null and master_idx < masters_found) {
if (check.? == node and !check.?.data.is_floating) {
is_master = true;
break;
}
if (!check.?.data.is_floating) {
master_idx += 1;
}
check = check.?.next;
}
if (!is_master) {
if (stack_windows == 1) {
_ = C.XMoveResizeWindow(display, node.data.w, @as(c_int, @intCast(master_width)) + GAP_SIZE, GAP_SIZE, @as(c_uint, @intCast(@max(0, stack_width - GAP_SIZE - (2 * BORDER_WIDTH)))), @as(c_uint, @intCast(@max(0, sh - (2 * GAP_SIZE) - (2 * BORDER_WIDTH)))));
} else {
const gap_total_stack = @as(c_int, @intCast((stack_windows + 1) * GAP_SIZE));
const stack_height = @divTrunc(sh - gap_total_stack, @as(c_int, @intCast(stack_windows)));
const y_position = GAP_SIZE + (@as(c_int, @intCast(stack_i)) * (stack_height + GAP_SIZE));
_ = C.XMoveResizeWindow(display, node.data.w, @as(c_int, @intCast(master_width)) + GAP_SIZE, y_position, @as(c_uint, @intCast(@max(0, stack_width - GAP_SIZE - (2 * BORDER_WIDTH)))), @as(c_uint, @intCast(@max(0, stack_height - (2 * BORDER_WIDTH)))));
stack_i += 1;
}
}
}
}
}
}
fn addClient(allocator: std.mem.Allocator, window: C.Window) !*L.Node {
var attributes: C.XWindowAttributes = undefined;
_ = C.XGetWindowAttributes(display, window, &attributes);
const m_float = shouldFloat(window);
const client = Client{
.full = false,
.wx = attributes.x,
.wy = attributes.y,
.ww = attributes.width,
.wh = attributes.height,
.w = window,
.is_floating = m_float,
};
var node = try allocator.create(L.Node);
node.data = client;
list.append(node);
return node;
}
fn shouldFloat(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(ignoreError);
const net_wm_window_type = C.XInternAtom(display, "_NET_WM_WINDOW_TYPE", 0);
if (net_wm_window_type != 0) {
const status = C.XGetWindowProperty(display, window, net_wm_window_type, 0, 32, 0, C.XA_ATOM, &actual_type, &actual_format, &nitems, &bytes_after, &prop_return);
if (status == 0 and prop_return != null and actual_type == C.XA_ATOM and actual_format == 32 and nitems > 0) {
const atoms = @as([*]C.Atom, @alignCast(@ptrCast(prop_return)));
for (0..nitems) |i| {
const atom = atoms[i];
const dialog_atom = C.XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", 0);
const utility_atom = C.XInternAtom(display, "_NET_WM_WINDOW_TYPE_UTILITY", 0);
const popup_atom = C.XInternAtom(display, "_NET_WM_WINDOW_TYPE_POPUP_MENU", 0);
if (atom == dialog_atom or atom == utility_atom or atom == popup_atom) {
_ = C.XFree(prop_return);
_ = C.XSetErrorHandler(handleError);
return true;
}
}
_ = C.XFree(prop_return);
}
}
_ = C.XSetErrorHandler(handleError);
return false;
}
fn focus(node: ?*L.Node) void {
if (list.len == 0) return;
if (cursor) |c| _ = C.XSetWindowBorder(display, c.data.w, NORMAL_BORDER_COLOR);
const target = node orelse previously_focused orelse list.first.?;
previously_focused = cursor;
_ = C.XSetInputFocus(
display,
target.data.w,
C.RevertToParent,
C.CurrentTime,
);
_ = C.XRaiseWindow(display, target.data.w);
_ = C.XSetWindowBorder(display, target.data.w, FOCUS_BORDER_COLOR);
cursor = target;
}
fn winToNode(w: C.Window) ?*L.Node {
var next = list.first;
while (next) |node| : (next = node.next) {
if (node.data.w == w) return node;
}
return null;
}
fn unmanage(allocator: std.mem.Allocator, node: *L.Node, destroyed: bool) void {
if (!destroyed) {
_ = C.XGrabServer(display);
_ = C.XSetErrorHandler(ignoreError);
_ = C.XSelectInput(display, node.data.w, C.NoEventMask);
_ = C.XUngrabButton(display, C.AnyButton, C.AnyModifier, node.data.w);
_ = C.XSync(display, 0);
_ = C.XSetErrorHandler(handleError);
_ = C.XUngrabServer(display);
}
var next_focus: ?*L.Node = null;
if (node == cursor) {
if (node.next) |next| {
next_focus = next;
} else if (node.prev) |prev| {
next_focus = prev;
} else if (list.len > 1) {
var temp = list.first;
while (temp) |t| : (temp = t.next) {
if (t != node) {
next_focus = t;
break;
}
}
}
cursor = null;
}
if (previously_focused) |pf| {
if (node.data.w == pf.data.w) previously_focused = null;
}
list.remove(node);
allocator.destroy(node);
if (list.len > 0) {
applyLayout();
if (next_focus) |focus_target| {
focus(focus_target);
} else {
focus(list.first);
}
} else {
cursor = null;
previously_focused = null;
}
}
var screen_w: c_uint = 0;
var screen_h: c_uint = 0;
var center_w: c_uint = 0;
var center_h: c_uint = 0;
var randr_event_base: c_int = 0;
var randr_error_base: c_int = 0;
fn initRandR(allocator: std.mem.Allocator) !void {
if (C.XRRQueryExtension(display, &randr_event_base, &randr_error_base) == 0) {
logError("RandR extension not available");
return;
}
const base_msg = try std.fmt.allocPrint(allocator, "RandR extension initialized with event base: {d}, error base: {d}", .{ randr_event_base, randr_error_base });
defer allocator.free(base_msg);
logInfo(base_msg);
var major: c_int = 0;
var minor: c_int = 0;
if (C.XRRQueryVersion(display, &major, &minor) != 0) {
const ver_msg = try std.fmt.allocPrint(allocator, "RandR version: {d}.{d}", .{ major, minor });
defer allocator.free(ver_msg);
logInfo(ver_msg);
}
try updateScreenDimensions(allocator);
_ = C.XRRSelectInput(display, root, C.RROutputChangeNotifyMask | C.RRCrtcChangeNotifyMask | C.RRScreenChangeNotifyMask);
}
fn updateScreenDimensions(allocator: std.mem.Allocator) !void {
const res = C.XRRGetScreenResources(display, root);
if (res == null) {
logError("Failed to get screen resources");
const x_screen = C.DefaultScreen(display);
screen_w = @intCast(C.XDisplayWidth(display, x_screen));
screen_h = @intCast(C.XDisplayHeight(display, x_screen));
center_w = @divTrunc((3 * screen_w), 5);
center_h = screen_h - 20;
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 tryUseMonitor(allocator, res, primary_output, "primary");
}
if (!found_active_monitor) {
found_active_monitor = try findLargestMonitor(allocator, res);
}
if (!found_active_monitor) {
const x_screen = C.DefaultScreen(display);
const old_w = screen_w;
const old_h = screen_h;
screen_w = @intCast(C.XDisplayWidth(display, x_screen));
screen_h = @intCast(C.XDisplayHeight(display, x_screen));
center_w = @divTrunc((3 * screen_w), 5);
center_h = screen_h - 20;
if (old_w != screen_w or old_h != screen_h) {
const log_msg = try std.fmt.allocPrint(allocator, "Using X screen dimensions: {d}x{d}", .{ screen_w, screen_h });
defer allocator.free(log_msg);
logInfo(log_msg);
}
}
if (list.len > 0 and screen_w > 0 and screen_h > 0) {
applyLayout();
}
}
fn tryUseMonitor(allocator: std.mem.Allocator, res: *C.XRRScreenResources, output_id: C.RROutput, monitor_type: []const u8) !bool {
for (0..@intCast(res.*.noutput)) |i| {
if (res.*.outputs[i] != output_id) continue;
const output_info = C.XRRGetOutputInfo(display, res, res.*.outputs[i]);
if (output_info == null) continue;
defer C.XRRFreeOutputInfo(output_info);
const output_name = std.mem.span(@as([*:0]const u8, @ptrCast(output_info.*.name)));
if (output_info.*.connection != C.RR_Connected or output_info.*.crtc == 0) {
const skip_msg = try std.fmt.allocPrint(allocator, "{s} monitor {s} is not active, skipping", .{ monitor_type, output_name });
defer allocator.free(skip_msg);
logInfo(skip_msg);
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) {
const zero_dim_msg = try std.fmt.allocPrint(allocator, "{s} monitor {s} has zero dimensions, skipping", .{ monitor_type, output_name });
defer allocator.free(zero_dim_msg);
logInfo(zero_dim_msg);
continue;
}
screen_w = @intCast(crtc_info.*.width);
screen_h = @intCast(crtc_info.*.height);
center_w = @divTrunc((3 * screen_w), 5);
center_h = screen_h - 20;
const log_msg = try std.fmt.allocPrint(allocator, "Using {s} monitor: {s} ({d}x{d})", .{ monitor_type, output_name, screen_w, screen_h });
defer allocator.free(log_msg);
logInfo(log_msg);
return true;
}
return false;
}
fn findLargestMonitor(allocator: 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;
for (0..@intCast(res.*.noutput)) |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 = @as(c_uint, @intCast(crtc_info.*.width));
const output_height = @as(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;
}
}
if (largest_area > 0) {
const old_w = screen_w;
const old_h = screen_h;
screen_w = largest_width;
screen_h = largest_height;
center_w = @divTrunc((3 * screen_w), 5);
center_h = screen_h - 20;
if (old_w != screen_w or old_h != screen_h) {
const log_msg = try std.fmt.allocPrint(allocator, "Using largest monitor: {d}x{d}", .{ screen_w, screen_h });
defer allocator.free(log_msg);
logInfo(log_msg);
}
return true;
}
return false;
}
fn forceSyncMonitors(allocator: std.mem.Allocator) void {
_ = C.XSync(display, 0);
updateScreenDimensions(allocator) catch |err| {
const err_msg = std.fmt.allocPrint(allocator, "Error updating screen dimensions: {any}", .{err}) catch "Error updating dimensions";
defer if (@TypeOf(err_msg) == []const u8) allocator.free(err_msg);
logError(err_msg);
};
}
fn onRRNotify(allocator: std.mem.Allocator, e: *C.XEvent) !void {
const rrev = @as(*C.XRRNotifyEvent, @ptrCast(e));
switch (rrev.subtype) {
C.RRNotify_OutputChange, C.RRNotify_CrtcChange => {
_ = C.XSync(display, 0);
const old_w = screen_w;
const old_h = screen_h;
try updateScreenDimensions(allocator);
if (old_w != screen_w or old_h != screen_h) {
const change_msg = try std.fmt.allocPrint(allocator, "Screen dimensions changed: {d}x{d} -> {d}x{d}", .{ old_w, old_h, screen_w, screen_h });
defer allocator.free(change_msg);
logInfo(change_msg);
if (list.len > 0) {
applyLayout();
}
}
},
else => {
// left blank
},
}
}
fn run() void {
const script_path = autostart_path;
const pid = std.posix.fork() catch |err| {
std.debug.print("fork failed: {}\n", .{err});
return;
};
if (pid == 0) {
_ = std.os.linux.setsid();
const args = [_]?[*:0]const u8{ "/bin/sh", script_path, null };
const rc = execvp("/bin/sh", &args);
if (rc == -1) {
std.debug.print("failed to execute autostart script: errno={}\n", .{errno});
std.posix.exit(1);
} else {
std.debug.print("Autostart launched with PID {}\n", .{pid});
}
}
}
fn killClient() void {
if (cursor == null) return;
_ = C.XGrabServer(display);
_ = C.XSetErrorHandler(ignoreError);
_ = C.XSetCloseDownMode(display, C.DestroyAll);
_ = C.XKillClient(display, cursor.?.data.w);
_ = C.XSync(display, 0);
_ = C.XSetErrorHandler(handleError);
_ = C.XUngrabServer(display);
}
fn winNext() void {
if (cursor) |c| {
if (c.next) |next|
focus(next)
else if (list.first) |first|
focus(first);
}
}
fn winPrev() void {
if (cursor) |c| {
if (c.prev) |prev|
focus(prev)
else if (list.last) |last|
focus(last);
}
}
extern fn execvp(prog: [*:0]const u8, argv: [*]const ?[*:0]const u8) c_int;
extern var errno: c_int;
fn spawn(cmd: [*:0]const u8) void {
const log = std.log.scoped(.launcher);
const pid = std.posix.fork() catch {
log.err("fork failed", .{});
return;
};
if (pid == 0) {
_ = std.os.linux.setsid();
const second_pid = std.posix.fork() catch {
log.err("second fork failed", .{});
std.posix.exit(1);
};
if (second_pid > 0) {
std.posix.exit(0);
}
var args = [_:null]?[*:0]const u8{
cmd,
null,
};
const rc = execvp(cmd, &args);
if (rc == -1) {
log.err("execvp failed with errno={}", .{errno});
std.posix.exit(1);
}
} else {
_ = std.posix.waitpid(pid, 0);
}
}
fn handleError(_: ?*C.Display, event: [*c]C.XErrorEvent) callconv(.C) c_int {
const evt: *C.XErrorEvent = @ptrCast(event);
switch (evt.error_code) {
C.BadMatch => logError("BadMatch"),
C.BadWindow => logError("BadWindow"),
C.BadDrawable => logError("BadDrawable"),
else => logError("Unknown X error"),
}
return 0;
}
fn ignoreError(_: ?*C.Display, _: [*c]C.XErrorEvent) callconv(.C) c_int {
return 0;
}
fn onConfigureRequest(e: *C.XConfigureRequestEvent) void {
window_changes.x = e.x;
window_changes.y = e.y;
window_changes.width = e.width;
window_changes.height = e.height;
window_changes.border_width = e.border_width;
window_changes.sibling = e.above;
window_changes.stack_mode = e.detail;
_ = C.XConfigureWindow(display, e.window, @intCast(e.value_mask), &window_changes);
}
// TODO: Windows show for a brief of a second on the left, and then tiling layout gets applied.
// We want to directly have it on the stack.
// the C.XMapWindow position is determining. However, we have a problem then with our app launcher.
fn onMapRequest(allocator: std.mem.Allocator, event: *C.XEvent) !void {
const window: C.Window = event.xmaprequest.window;
_ = C.XSelectInput(display, window, C.StructureNotifyMask | C.EnterWindowMask);
_ = C.XMapWindow(display, window);
_ = C.XSetWindowBorderWidth(display, window, BORDER_WIDTH);
const node = try addClient(allocator, window);
focus(node);
applyLayout();
if (node.data.is_floating) {
_ = C.XRaiseWindow(display, node.data.w);
focus(node);
}
}
fn onUnmapNotify(allocator: std.mem.Allocator, e: *C.XEvent) void {
const ev = &e.xunmap;
if (winToNode(ev.window)) |node| {
if (ev.send_event == 1) {
// INVESTIGATE: Is this what we want to do?
const data = [_]c_long{ C.WithdrawnState, C.None };
// Data Format: Specifies whether the data should be viewed as a list
// of 8-bit, 16-bit, or 32-bit quantities.
const data_format = 32;
_ = C.XChangeProperty(
display,
node.data.w,
C.XInternAtom(display, "WM_STATE", 0),
C.XInternAtom(display, "WM_STATE", 0),
data_format,
C.PropModeReplace,
@ptrCast(&data),
data.len,
);
} else {
unmanage(allocator, node, false);
}
}
}
fn onKeyPress(e: *C.XEvent) void {
if (keymap.get(e.xkey.keycode)) |action| action();
}
fn onEnterNotify(e: *C.XEvent) void {
var current_node = list.first;
while (current_node) |n| : (current_node = n.next) {
if (n.data.is_floating) {
return; // Skip focus changes while dialogs are present
}
}
if (e.xcrossing.mode != C.NotifyNormal or e.xcrossing.detail == C.NotifyInferior) {
return;
}
_ = C.XSetErrorHandler(ignoreError);
if (winToNode(e.xcrossing.window)) |window_node| {
if (cursor != null and window_node.data.w == cursor.?.data.w) {
_ = C.XSetErrorHandler(handleError);
return;
}
if (!window_node.data.is_floating) {
focus(window_node);
}
}
_ = C.XSetErrorHandler(handleError);
}
fn onButtonPress(e: *C.XEvent) void {
if (e.xbutton.subwindow == 0) return;
var attributes: C.XWindowAttributes = undefined;
_ = C.XGetWindowAttributes(display, e.xbutton.subwindow, &attributes);
win_w = attributes.width;
win_h = attributes.height;
win_x = attributes.x;
win_y = attributes.y;
if (e.xbutton.button == 3 and (e.xbutton.state & C.Mod4Mask) != 0) {
_ = C.XGrabPointer(display, e.xbutton.subwindow, 1, C.ButtonReleaseMask | C.PointerMotionMask, C.GrabModeAsync, C.GrabModeAsync, C.None, resize_cursor, C.CurrentTime);
_ = 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) {
_ = C.XGrabPointer(display, e.xbutton.subwindow, 1, C.ButtonReleaseMask | C.PointerMotionMask, C.GrabModeAsync, C.GrabModeAsync, C.None, move_cursor, C.CurrentTime);
mouse = e.xbutton;
}
if (winToNode(e.xbutton.subwindow)) |node| if (node != cursor) {
focus(node);
};
}
fn onNotifyMotion(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) {
_ = C.XMoveResizeWindow(display, mouse.subwindow, win_x, win_y, @as(c_uint, @intCast(@max(10, win_w + dx))), @as(c_uint, @intCast(@max(10, win_h + dy))));
}
}
fn onNotifyDestroy(allocator: std.mem.Allocator, e: *C.XEvent) void {
const ev = &e.xdestroywindow;
if (winToNode(ev.window)) |node| {
unmanage(allocator, node, true);
}
}
fn onButtonRelease(_: *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 window_changes: C.XWindowChanges = undefined;
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;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var event: C.XEvent = undefined;
display = C.XOpenDisplay(0) orelse {
logError("Could not open display");
std.c._exit(1);
};
resize_cursor = C.XCreateFontCursor(display, 120);
move_cursor = C.XCreateFontCursor(display, 52);
normal_cursor = C.XCreateFontCursor(display, 68);
const screen = C.DefaultScreen(display);
root = C.RootWindow(display, screen);
screen_w = @intCast(C.XDisplayWidth(display, screen));
screen_h = @intCast(C.XDisplayHeight(display, screen));
center_w = @divTrunc((3 * screen_w), 5);
center_h = screen_h - 20;
try initRandR(allocator);
_ = C.XSetErrorHandler(handleError);
_ = C.XSelectInput(display, root, C.SubstructureRedirectMask | C.EnterWindowMask);
_ = C.XDefineCursor(display, root, C.XCreateFontCursor(display, 68));
grabInput(root);
keymap = try initKeyMap(allocator);
run();
forceSyncMonitors(allocator);
_ = C.XSync(display, 0);
initialWin(allocator);
while (!shouldQuit and C.XNextEvent(display, &event) == 0) {
switch (event.type) {
C.MapRequest => try onMapRequest(allocator, &event),
C.UnmapNotify => onUnmapNotify(allocator, &event),
C.KeyPress => onKeyPress(&event),
C.ButtonPress => onButtonPress(&event),
C.ButtonRelease => onButtonRelease(&event),
C.MotionNotify => onNotifyMotion(&event),
C.DestroyNotify => onNotifyDestroy(allocator, &event),
C.ConfigureRequest => onConfigureRequest(@ptrCast(&event)),
C.EnterNotify => onEnterNotify(&event),
else => {
if (randr_event_base != 0) {
if (event.type == randr_event_base + C.RRScreenChangeNotify) {
logInfo("RandR: Screen change event detected");
try onRRNotify(allocator, &event);
} else if (event.type == randr_event_base + C.RRNotify) {
logInfo("RandR: Output change event detected");
try onRRNotify(allocator, &event);
}
}
},
}
}
keymap.deinit();
_ = C.XFreeCursor(display, resize_cursor);
_ = C.XFreeCursor(display, move_cursor);
_ = C.XFreeCursor(display, normal_cursor);
_ = C.XCloseDisplay(display);
std.c.exit(0);
}