oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git

refactor(atoms): create `Atoms` type to store intern atoms and remove globals from main

Commit
8a8d303f4b07a5a62c83a89cfff69038e3158099
Parent
78f29dc
Author
emzywastaken <amiamemetoo@gmail.com>
Date
2026-02-20 23:19:19

Diff

diff --git a/src/main.zig b/src/main.zig
index c61accd..35addb0 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,5 +1,6 @@
 const std = @import("std");
 const VERSION = "v0.11.2";
+const Atoms = @import("x11/atoms.zig").Atoms;
 const kchord = @import("keyboard/chord.zig");
 const display_mod = @import("x11/display.zig");
 const events = @import("x11/events.zig");
@@ -25,22 +26,8 @@ const Monitor = monitor_mod.Monitor;
 var running: bool = true;
 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
 
-var wm_protocols: xlib.Atom = 0;
-var wm_delete: xlib.Atom = 0;
-var wm_state: xlib.Atom = 0;
-var wm_take_focus: xlib.Atom = 0;
-
-var net_supported: xlib.Atom = 0;
-var net_wm_name: xlib.Atom = 0;
-var net_wm_state: xlib.Atom = 0;
-var net_wm_check: xlib.Atom = 0;
-var net_wm_state_fullscreen: xlib.Atom = 0;
-var net_active_window: xlib.Atom = 0;
-var net_wm_window_type: xlib.Atom = 0;
-var net_wm_window_type_dialog: xlib.Atom = 0;
-var net_client_list: xlib.Atom = 0;
-
-var wm_check_window: xlib.Window = 0;
+var atoms: Atoms = undefined;
+var wm_check_window: xlib.Window = undefined;
 
 var border_color_focused: c_ulong = 0x6dade3;
 var border_color_unfocused: c_ulong = 0x444444;
@@ -250,7 +237,11 @@ pub fn main() !void {
 
     std.debug.print("successfully became window manager\n", .{});
 
-    setup_atoms(&display);
+    const atoms_result = Atoms.init(display_global.?.handle, display_global.?.root);
+    atoms = atoms_result.atoms;
+    wm_check_window = atoms_result.check_window;
+    std.debug.print("atoms initialized with EWMH support\n", .{});
+
     setup_cursors(&display);
     client_mod.init(allocator);
     monitor_mod.init(allocator);
@@ -285,37 +276,6 @@ pub fn main() !void {
     std.debug.print("oxwm exiting\n", .{});
 }
 
-fn setup_atoms(display: *Display) void {
-    wm_protocols = xlib.XInternAtom(display.handle, "WM_PROTOCOLS", xlib.False);
-    wm_delete = xlib.XInternAtom(display.handle, "WM_DELETE_WINDOW", xlib.False);
-    wm_state = xlib.XInternAtom(display.handle, "WM_STATE", xlib.False);
-    wm_take_focus = xlib.XInternAtom(display.handle, "WM_TAKE_FOCUS", xlib.False);
-
-    net_active_window = xlib.XInternAtom(display.handle, "_NET_ACTIVE_WINDOW", xlib.False);
-    net_supported = xlib.XInternAtom(display.handle, "_NET_SUPPORTED", xlib.False);
-    net_wm_name = xlib.XInternAtom(display.handle, "_NET_WM_NAME", xlib.False);
-    net_wm_state = xlib.XInternAtom(display.handle, "_NET_WM_STATE", xlib.False);
-    net_wm_check = xlib.XInternAtom(display.handle, "_NET_SUPPORTING_WM_CHECK", xlib.False);
-    net_wm_state_fullscreen = xlib.XInternAtom(display.handle, "_NET_WM_STATE_FULLSCREEN", xlib.False);
-    net_wm_window_type = xlib.XInternAtom(display.handle, "_NET_WM_WINDOW_TYPE", xlib.False);
-    net_wm_window_type_dialog = xlib.XInternAtom(display.handle, "_NET_WM_WINDOW_TYPE_DIALOG", xlib.False);
-    net_client_list = xlib.XInternAtom(display.handle, "_NET_CLIENT_LIST", xlib.False);
-
-    const utf8_string = xlib.XInternAtom(display.handle, "UTF8_STRING", xlib.False);
-
-    wm_check_window = xlib.XCreateSimpleWindow(display.handle, display.root, 0, 0, 1, 1, 0, 0, 0);
-    _ = xlib.XChangeProperty(display.handle, wm_check_window, net_wm_check, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&wm_check_window), 1);
-    _ = xlib.XChangeProperty(display.handle, wm_check_window, net_wm_name, utf8_string, 8, xlib.PropModeReplace, "oxwm", 6);
-    _ = xlib.XChangeProperty(display.handle, display.root, net_wm_check, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&wm_check_window), 1);
-
-    var net_atoms = [_]xlib.Atom{ net_supported, net_wm_name, net_wm_state, net_wm_check, net_wm_state_fullscreen, net_active_window, net_wm_window_type, net_wm_window_type_dialog, net_client_list };
-    _ = xlib.XChangeProperty(display.handle, display.root, net_supported, xlib.XA_ATOM, 32, xlib.PropModeReplace, @ptrCast(&net_atoms), net_atoms.len);
-
-    _ = xlib.XDeleteProperty(display.handle, display.root, net_client_list);
-
-    std.debug.print("atoms initialized with EWMH support\n", .{});
-}
-
 fn setup_cursors(display: *Display) void {
     cursor_normal = xlib.XCreateFontCursor(display.handle, xlib.XC_left_ptr);
     cursor_resize = xlib.XCreateFontCursor(display.handle, xlib.XC_sizing);
@@ -617,11 +577,11 @@ fn get_state(display: *Display, window: xlib.Window) c_long {
     const result = xlib.XGetWindowProperty(
         display.handle,
         window,
-        wm_state,
+        atoms.wm_state,
         0,
         2,
         xlib.False,
-        wm_state,
+        atoms.wm_state,
         &actual_type,
         &actual_format,
         &num_items,
@@ -629,7 +589,7 @@ fn get_state(display: *Display, window: xlib.Window) c_long {
         &prop,
     );
 
-    if (result != 0 or actual_type != wm_state or num_items < 1) {
+    if (result != 0 or actual_type != atoms.wm_state or num_items < 1) {
         if (prop != null) {
             _ = xlib.XFree(prop);
         }
@@ -823,7 +783,7 @@ fn manage(display: *Display, win: xlib.Window, window_attrs: *xlib.XWindowAttrib
     client_mod.attach_aside(client);
     client_mod.attach_stack(client);
 
-    _ = xlib.XChangeProperty(display.handle, display.root, net_client_list, xlib.XA_WINDOW, 32, xlib.PropModeAppend, @ptrCast(&client.window), 1);
+    _ = xlib.XChangeProperty(display.handle, display.root, atoms.net_client_list, xlib.XA_WINDOW, 32, xlib.PropModeAppend, @ptrCast(&client.window), 1);
     _ = xlib.XMoveResizeWindow(display.handle, client.window, client.x + 2 * display.screen_width(), client.y, @intCast(client.width), @intCast(client.height));
     set_client_state(display, client, NormalState);
 
@@ -1264,7 +1224,7 @@ fn kill_focused(display: *Display) void {
     const client = selected.sel orelse return;
     std.debug.print("killing window: 0x{x}\n", .{client.window});
 
-    if (!send_event(display, client, wm_delete)) {
+    if (!send_event(display, client, atoms.wm_delete)) {
         _ = xlib.XGrabServer(display.handle);
         _ = xlib.XKillClient(display.handle, client.window);
         _ = xlib.XSync(display.handle, xlib.False);
@@ -1282,11 +1242,11 @@ fn set_fullscreen(display: *Display, client: *Client, fullscreen: bool) void {
     const monitor = client.monitor orelse return;
 
     if (fullscreen and !client.is_fullscreen) {
-        var fullscreen_atom = net_wm_state_fullscreen;
+        var fullscreen_atom = atoms.net_wm_state_fullscreen;
         _ = xlib.XChangeProperty(
             display.handle,
             client.window,
-            net_wm_state,
+            atoms.net_wm_state,
             xlib.XA_ATOM,
             32,
             xlib.PropModeReplace,
@@ -1309,7 +1269,7 @@ fn set_fullscreen(display: *Display, client: *Client, fullscreen: bool) void {
         _ = xlib.XChangeProperty(
             display.handle,
             client.window,
-            net_wm_state,
+            atoms.net_wm_state,
             xlib.XA_ATOM,
             32,
             xlib.PropModeReplace,
@@ -1903,12 +1863,12 @@ fn handle_button_press(display: *Display, event: *xlib.XButtonEvent) void {
 fn handle_client_message(display: *Display, event: *xlib.XClientMessageEvent) void {
     const client = client_mod.window_to_client(event.window) orelse return;
 
-    if (event.message_type == net_wm_state) {
+    if (event.message_type == atoms.net_wm_state) {
         const action = event.data.l[0];
         const first_property = @as(xlib.Atom, @intCast(event.data.l[1]));
         const second_property = @as(xlib.Atom, @intCast(event.data.l[2]));
 
-        if (first_property == net_wm_state_fullscreen or second_property == net_wm_state_fullscreen) {
+        if (first_property == atoms.net_wm_state_fullscreen or second_property == atoms.net_wm_state_fullscreen) {
             const net_wm_state_remove = 0;
             const net_wm_state_add = 1;
             const net_wm_state_toggle = 2;
@@ -1921,7 +1881,7 @@ fn handle_client_message(display: *Display, event: *xlib.XClientMessageEvent) vo
                 set_fullscreen(display, client, !client.is_fullscreen);
             }
         }
-    } else if (event.message_type == net_active_window) {
+    } else if (event.message_type == atoms.net_active_window) {
         const selected = monitor_mod.selected_monitor orelse return;
         if (client != selected.sel and !client.is_urgent) {
             set_urgent(display, client, true);
@@ -2026,9 +1986,9 @@ fn handle_focus_in(display: *Display, event: *xlib.XFocusChangeEvent) void {
 fn set_focus(display: *Display, client: *Client) void {
     if (!client.never_focus) {
         _ = xlib.XSetInputFocus(display.handle, client.window, xlib.RevertToPointerRoot, xlib.CurrentTime);
-        _ = xlib.XChangeProperty(display.handle, display.root, net_active_window, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&client.window), 1);
+        _ = xlib.XChangeProperty(display.handle, display.root, atoms.net_active_window, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&client.window), 1);
     }
-    _ = send_event(display, client, wm_take_focus);
+    _ = send_event(display, client, atoms.wm_take_focus);
 }
 
 var last_motion_monitor: ?*Monitor = null;
@@ -2071,9 +2031,9 @@ fn handle_property_notify(display: *Display, event: *xlib.XPropertyEvent) void {
     } else if (event.atom == xlib.XA_WM_HINTS) {
         update_wm_hints(display, client);
         bar_mod.invalidate_bars();
-    } else if (event.atom == xlib.XA_WM_NAME or event.atom == net_wm_name) {
+    } else if (event.atom == xlib.XA_WM_NAME or event.atom == atoms.net_wm_name) {
         update_title(display, client);
-    } else if (event.atom == net_wm_window_type) {
+    } else if (event.atom == atoms.net_wm_window_type) {
         update_window_type(display, client);
     }
 }
@@ -2084,13 +2044,13 @@ fn unfocus_client(display: *Display, client: ?*Client, reset_input_focus: bool)
     _ = xlib.XSetWindowBorder(display.handle, unfocus_target.window, border_color_unfocused);
     if (reset_input_focus) {
         _ = xlib.XSetInputFocus(display.handle, display.root, xlib.RevertToPointerRoot, xlib.CurrentTime);
-        _ = xlib.XDeleteProperty(display.handle, display.root, net_active_window);
+        _ = xlib.XDeleteProperty(display.handle, display.root, atoms.net_active_window);
     }
 }
 
 fn set_client_state(display: *Display, client: *Client, state: c_long) void {
     var data: [2]c_long = .{ state, xlib.None };
-    _ = xlib.c.XChangeProperty(display.handle, client.window, wm_state, wm_state, 32, xlib.PropModeReplace, @ptrCast(&data), 2);
+    _ = xlib.c.XChangeProperty(display.handle, client.window, atoms.wm_state, xlib.XA_ATOM, 32, xlib.PropModeReplace, @ptrCast(&data), 2);
 }
 
 fn update_numlock_mask(display: *Display) void {
@@ -2181,12 +2141,12 @@ fn focus(display: *Display, target_client: ?*Client) void {
         _ = xlib.XSetWindowBorder(display.handle, client.window, border_color_focused);
         if (!client.never_focus) {
             _ = xlib.XSetInputFocus(display.handle, client.window, xlib.RevertToPointerRoot, xlib.CurrentTime);
-            _ = xlib.XChangeProperty(display.handle, display.root, net_active_window, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&client.window), 1);
+            _ = xlib.XChangeProperty(display.handle, display.root, atoms.net_active_window, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&client.window), 1);
         }
-        _ = send_event(display, client, wm_take_focus);
+        _ = send_event(display, client, atoms.wm_take_focus);
     } else {
         _ = xlib.XSetInputFocus(display.handle, display.root, xlib.RevertToPointerRoot, xlib.CurrentTime);
-        _ = xlib.XDeleteProperty(display.handle, display.root, net_active_window);
+        _ = xlib.XDeleteProperty(display.handle, display.root, atoms.net_active_window);
     }
 
     const current_selmon = monitor_mod.selected_monitor orelse return;
@@ -2392,19 +2352,19 @@ fn update_wm_hints(display: *Display, client: *Client) void {
 }
 
 fn update_window_type(display: *Display, client: *Client) void {
-    const state = get_atom_prop(display, client, net_wm_state);
-    const window_type = get_atom_prop(display, client, net_wm_window_type);
+    const state = get_atom_prop(display, client, atoms.net_wm_state);
+    const window_type = get_atom_prop(display, client, atoms.net_wm_window_type);
 
-    if (state == net_wm_state_fullscreen) {
+    if (state == atoms.net_wm_state_fullscreen) {
         set_fullscreen(display, client, true);
     }
-    if (window_type == net_wm_window_type_dialog) {
+    if (window_type == atoms.net_wm_window_type_dialog) {
         client.is_floating = true;
     }
 }
 
 fn update_title(display: *Display, client: *Client) void {
-    if (!get_text_prop(display, client.window, net_wm_name, &client.name)) {
+    if (!get_text_prop(display, client.window, atoms.net_wm_name, &client.name)) {
         _ = get_text_prop(display, client.window, xlib.XA_WM_NAME, &client.name);
     }
     if (client.name[0] == 0) {
@@ -2514,13 +2474,13 @@ fn apply_rules(display: *Display, client: *Client) void {
 }
 
 fn update_client_list(display: *Display) void {
-    _ = xlib.XDeleteProperty(display.handle, display.root, net_client_list);
+    _ = xlib.XDeleteProperty(display.handle, display.root, atoms.net_client_list);
 
     var current_monitor = monitor_mod.monitors;
     while (current_monitor) |monitor| {
         var current_client = monitor.clients;
         while (current_client) |client| {
-            _ = xlib.XChangeProperty(display.handle, display.root, net_client_list, xlib.XA_WINDOW, 32, xlib.PropModeAppend, @ptrCast(&client.window), 1);
+            _ = xlib.XChangeProperty(display.handle, display.root, atoms.net_client_list, xlib.XA_WINDOW, 32, xlib.PropModeAppend, @ptrCast(&client.window), 1);
             current_client = client.next;
         }
         current_monitor = monitor.next;
@@ -2547,7 +2507,7 @@ fn send_event(display: *Display, client: *Client, protocol: xlib.Atom) bool {
         var event: xlib.XEvent = undefined;
         event.type = xlib.ClientMessage;
         event.xclient.window = client.window;
-        event.xclient.message_type = wm_protocols;
+        event.xclient.message_type = atoms.wm_protocols;
         event.xclient.format = 32;
         event.xclient.data.l[0] = @intCast(protocol);
         event.xclient.data.l[1] = xlib.CurrentTime;
diff --git a/src/x11/atoms.zig b/src/x11/atoms.zig
new file mode 100644
index 0000000..cbf0226
--- /dev/null
+++ b/src/x11/atoms.zig
@@ -0,0 +1,81 @@
+const std = @import("std");
+const xlib = @import("xlib.zig");
+
+pub const Atoms = struct {
+    // ICCCM atoms
+    wm_protocols: xlib.Atom,
+    wm_delete: xlib.Atom,
+    wm_state: xlib.Atom,
+    wm_take_focus: xlib.Atom,
+
+    // EWHM atoms
+    net_supported: xlib.Atom,
+    net_wm_name: xlib.Atom,
+    net_wm_state: xlib.Atom,
+    net_wm_check: xlib.Atom,
+    net_wm_state_fullscreen: xlib.Atom,
+    net_active_window: xlib.Atom,
+    net_wm_window_type: xlib.Atom,
+    net_wm_window_type_dialog: xlib.Atom,
+    net_client_list: xlib.Atom,
+
+    /// Intern all atoms and set up the EWMH check window on `root`.
+    ///
+    /// `check_window` is a 1×1 invisible window created solely to satisfy
+    /// the _NET_SUPPORTING_WM_CHECK convention. The caller owns it and
+    /// should destroy it on shutdown via `XDestroyWindow`.
+    pub fn init(display: *xlib.Display, root: xlib.Window) struct {
+        atoms: Atoms,
+        check_window: xlib.Window,
+    } {
+        const intern = struct {
+            fn call(dpy: *xlib.Display, name: [*:0]const u8) xlib.Atom {
+                return xlib.XInternAtom(dpy, name, xlib.False);
+            }
+        }.call;
+
+        const atoms = Atoms{
+            .wm_protocols = intern(display, "WM_PROTOCOLS"),
+            .wm_delete = intern(display, "WM_DELETE_WINDOW"),
+            .wm_state = intern(display, "WM_STATE"),
+            .wm_take_focus = intern(display, "WM_TAKE_FOCUS"),
+
+            .net_supported = intern(display, "_NET_SUPPORTED"),
+            .net_wm_name = intern(display, "_NET_WM_NAME"),
+            .net_wm_state = intern(display, "_NET_WM_STATE"),
+            .net_wm_check = intern(display, "_NET_SUPPORTING_WM_CHECK"),
+            .net_wm_state_fullscreen = intern(display, "_NET_WM_STATE_FULLSCREEN"),
+            .net_active_window = intern(display, "_NET_ACTIVE_WINDOW"),
+            .net_wm_window_type = intern(display, "_NET_WM_WINDOW_TYPE"),
+            .net_wm_window_type_dialog = intern(display, "_NET_WM_WINDOW_TYPE_DIALOG"),
+            .net_client_list = intern(display, "_NET_CLIENT_LIST"),
+        };
+
+        const utf8_string = intern(display, "UTF8_STRING");
+
+        // Create the EWMH check window.
+        var check_win = xlib.XCreateSimpleWindow(display, root, 0, 0, 1, 1, 0, 0, 0);
+
+        _ = xlib.XChangeProperty(display, check_win, atoms.net_wm_check, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&check_win), 1);
+        _ = xlib.XChangeProperty(display, check_win, atoms.net_wm_name, utf8_string, 8, xlib.PropModeReplace, "oxwm", 4);
+        _ = xlib.XChangeProperty(display, root, atoms.net_wm_check, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&check_win), 1);
+
+        // Advertise supported EWMH hints on the root window.
+        var net_atoms = [_]xlib.Atom{
+            atoms.net_supported,
+            atoms.net_wm_name,
+            atoms.net_wm_state,
+            atoms.net_wm_check,
+            atoms.net_wm_state_fullscreen,
+            atoms.net_active_window,
+            atoms.net_wm_window_type,
+            atoms.net_wm_window_type_dialog,
+            atoms.net_client_list,
+        };
+        _ = xlib.XChangeProperty(display, root, atoms.net_supported, xlib.XA_ATOM, 32, xlib.PropModeReplace, @ptrCast(&net_atoms), net_atoms.len);
+
+        _ = xlib.XDeleteProperty(display, root, atoms.net_client_list);
+
+        return .{ .atoms = atoms, .check_window = check_win };
+    }
+};