2zw

2zw - X11 Windowmanager

Files | Log | Commits | Refs | README


a385dd1

Author: SeMi

Date: 2025-04-23

Subject: rewrite codebase, polish some stuff

Diff

commit a385dd13cf1bac1fac98665bbdc3c879628563fb
Author: SeMi <sebastian.michalk@protonmail.com>
Date:   Wed Apr 23 21:03:44 2025 +0200

    rewrite codebase, polish some stuff

diff --git a/README.md b/README.md
index 6514824..1befa9b 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,67 @@
-# EWM
-
-A Window Manager designed specifically for my workflow on an ultrawide monitor.
-
-![Alt Text](gif.gif)
-
-## Why
-I find that tiling window managers are terrible on ultrawide monitors,
-the main issue being that if you only have a single window on the
-screen, say a text editor, then you end up having the bulk of the text
-off to the far left. Floating window managers don't have this issue
-but most of them fall short (for me) in other aspects.
-
-Instead of writing hundereds of lines in some bespoke configuration
-language trying to add missing functionality I figured it would be
-easier to just write a window manager that just does the thing.
-
-## Features
-- It does what I want
-- No configuration
-- Floating
-- Pseudo Tiling
-
-## Keybinds
-
-| Key         | Action             |
-| ----------- | ------------------ |
-| Mod4+q 	  | quit               |
-| Mod4+f 	  | fullscreen         |
-| Mod4+m 	  | center             |
-| Mod4+comma  | previous window    |
-| Mod4+period | next window        |
-| Mod4+h 	  | tile left          |
-| Mod4+l 	  | tile right         |
-| Mod4+t 	  | tile all           |
-| Mod4+s 	  | stack (center) all |
-
-## Building
-Requires zig version 0.12.0 or later.
+zw - minimal window manager
+===========================
+zw is an extremely fast and lean dynamic window manager for X written in Zig.
+
+Features
+--------
+- Small hackable Codebase (<900 LOC)
+- No configuration files, configured via source
+- Floating window management
+- Pseudo window tiling
+- Window focusing with colored borders
+- Minimal approach to UI (no status bar)
+- Zero dependencies beyond X11 and Zig standard library
+
+Requirements
+------------
+In order to build zw you need:
+- Zig compiler (0.11.0 or newer)
+- Xlib header files
+- libX11-dev
+- libXrandr-dev
+
+Installation
+------------
+Edit build.zig to match your local setup (mwm is installed into
+the /usr/local namespace by default).
+
+Afterwards enter the following command to build and install mwm:
+
+   zig build install
+
+Running mwm
+-----------
+Add the following line to your .xinitrc to start mwm using startx:
+
+   exec mwm
+
+In order to connect mwm to a specific display, make sure that
+the DISPLAY environment variable is set correctly, e.g.:
+
+   DISPLAY=foo.bar:1 exec mwm
+
+Configuration
+-------------
+All configuration is done directly in main.zig and rebuilding.
+
+The keybindings and configuration constants are at the top of the file
+for easy modification:
+
+   FOCUS_BORDER_COLOR = 0xffd787;   // Focused window border color
+   NORMAL_BORDER_COLOR = 0x333333;  // Normal window border color
+   BORDER_WIDTH = 2;                // Border width in pixels
+   terminal = "st";                 // Default terminal
+   launcher = "dmenu_run";          // Default application launcher
+
+Keybindings (with MOD4/Super key):
+- MOD+q      Kill focused window
+- MOD+f      Toggle fullscreen
+- MOD+m      Center current window
+- MOD+,      Focus previous window
+- MOD+.      Focus next window
+- MOD+h      Tile current window to left half
+- MOD+l      Tile current window to right half
+- MOD+t      Tile all windows
+- MOD+s      Stack all windows
+- MOD+Return Launch terminal (default st)
+- MOD+p      Launch application launcher (default dmenu)
\ No newline at end of file
diff --git a/build.zig b/build.zig
index 3ab3c77..164ac08 100644
--- a/build.zig
+++ b/build.zig
@@ -16,7 +16,7 @@ pub fn build(b: *std.Build) void {
     const optimize = b.standardOptimizeOption(.{});
 
     const exe = b.addExecutable(.{
-        .name = "ewm",
+        .name = "zw",
         .root_source_file = b.path("src/main.zig"),
         .target = target,
         .optimize = optimize,
@@ -24,6 +24,7 @@ pub fn build(b: *std.Build) void {
 
     exe.linkLibC();
     exe.linkSystemLibrary("X11");
+    exe.linkSystemLibrary("Xrandr");
     // This declares intent for the executable to be installed into the
     // standard location when the user invokes the "install" step (the default
     // step when running `zig build`).
diff --git a/gif.gif b/gif.gif
deleted file mode 100644
index 214b0b1..0000000
Binary files a/gif.gif and /dev/null differ
diff --git a/src/main.zig b/src/main.zig
index 8ec5f68..de046e7 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,18 +1,39 @@
 const std = @import("std");
-const C = @import("c.zig");
 
-const xlib = @cImport({
+//------------------------------------------------------------------------------
+// X11 Imports and C Bindings
+//------------------------------------------------------------------------------
+
+const C = @cImport({
     @cInclude("X11/Xlib.h");
+    @cInclude("X11/XF86keysym.h");
+    @cInclude("X11/keysym.h");
+    @cInclude("X11/XKBlib.h");
+    @cInclude("X11/Xatom.h");
+    @cInclude("X11/Xutil.h");
+    @cInclude("X11/extensions/Xrandr.h");
 });
 
+//------------------------------------------------------------------------------
+// Configuration and Constants
+//------------------------------------------------------------------------------
+
+// Appearance
 const FOCUS_BORDER_COLOR = 0xffd787;
 const NORMAL_BORDER_COLOR = 0x333333;
 const BORDER_WIDTH = 2;
 
-// Keybinds, currently every key is directly under Mod4Mask but I will probably add
-// the ability to specify modifiers.
+// Application settings
+const terminal = "st";
+const launcher = "dmenu_run";
+const autostart_path = "/home/smi/.scritps/ewm.sh";
+
+//------------------------------------------------------------------------------
+// Keybindings Configuration
+//------------------------------------------------------------------------------
+
+// Define keybindings with actions
 const keys = [_]Key{
-    //  .{ .keysym = C.XK_q, .action = &quit },
     .{ .keysym = C.XK_q, .action = &killClient },
     .{ .keysym = C.XK_f, .action = &winFullscreen },
     .{ .keysym = C.XK_m, .action = &centerCurrent },
@@ -22,37 +43,54 @@ const keys = [_]Key{
     .{ .keysym = C.XK_l, .action = &tileCurrentRight },
     .{ .keysym = C.XK_t, .action = &tileAll },
     .{ .keysym = C.XK_s, .action = &stackAll },
-    .{ .keysym = C.XK_Return, .action = &spawnTerminal }, // conf for terminal
-    .{ .keysym = C.XK_p, .action = &spawnDmenu },
+
+    .{ .keysym = C.XK_Return, .action = struct {
+        fn action() void {
+            spawn(terminal);
+        }
+    }.action },
+    .{ .keysym = C.XK_p, .action = struct {
+        fn action() void {
+            spawn(launcher);
+        }
+    }.action },
+
+    // add more in the format:
+    // .{ .keysym = C.XK_b, .action = struct {
+    //     fn action() void { spawn("firefox"); }
+    // }.action },
 };
 
-fn killClient() void {
-    // If no window is selected, return
-    if (cursor == null) return;
+//------------------------------------------------------------------------------
+// Logging Utilities
+//------------------------------------------------------------------------------
 
-    _ = 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 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;
+}
+
+//------------------------------------------------------------------------------
+// Keybindings and Input Handling
+//------------------------------------------------------------------------------
+
 const Key = struct {
     keysym: C.KeySym,
     action: *const fn () void,
 };
 
-// Generate a keymap with key: keysym and value: function pointer,
-// this is to avoid having to define keys to grab and then having to add same
-// keys to be handled in keypress handling code.
+// Generate a keymap with key: keysym and value: function pointer
 var keymap: std.AutoHashMap(c_uint, *const fn () void) = undefined;
 
 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();
-    inline for (keys) |key| {
+    for (keys) |key| {
         try map.put(C.XKeysymToKeycode(display, key.keysym), key.action);
     }
     return map;
@@ -69,7 +107,11 @@ fn grabInput(window: C.Window) void {
     }
 }
 
-// Application state
+//------------------------------------------------------------------------------
+// Client/Window Management
+//------------------------------------------------------------------------------
+
+// Client represents a managed window
 const Client = struct {
     full: bool,
     wx: c_int,
@@ -79,38 +121,10 @@ const Client = struct {
     w: C.Window,
 };
 
-var shouldQuit = false;
-
-// Primarly used to store window attributes when a window is being
-// clicked on before we start potentially moving/resizing it.
-var win_x: i32 = 0;
-var win_y: i32 = 0;
-var win_w: i32 = 0;
-var win_h: i32 = 0;
-
-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 display: *C.Display = undefined;
-var root: C.Window = undefined;
-var mouse: C.XButtonEvent = undefined;
-var window_changes: C.XWindowChanges = undefined;
-
-const ClientNode = struct {
-    prev: ?*ClientNode = null,
-    next: ?*ClientNode = null,
-    data: Client,
-};
-
 // Use the node type with DoublyLinkedList
 const L = std.DoublyLinkedList(Client);
 var list = L{};
-var cursor: ?*L.Node = null; // having the cursor be nullable is annoying..
-
-// IMPROVE: Keeping a pointer to previously_focused window as the previs node in the window list
-// may or may not be the previously focused one -- because a circular dl list is used.
+var cursor: ?*L.Node = null;
 var previously_focused: ?*L.Node = undefined;
 
 fn addClient(allocator: std.mem.Allocator, window: C.Window) !*L.Node {
@@ -127,7 +141,6 @@ fn addClient(allocator: std.mem.Allocator, window: C.Window) !*L.Node {
     };
 
     var node = try allocator.create(L.Node);
-
     node.data = client;
     list.append(node);
 
@@ -155,12 +168,11 @@ fn center(c: *L.Node) void {
     );
 }
 
-// IMPROVE: node is optional so that we don't have to do focusing logic in other places.
 fn focus(node: ?*L.Node) void {
     if (list.len == 0) return;
     if (cursor) |c| _ = C.XSetWindowBorder(display, c.data.w, NORMAL_BORDER_COLOR);
 
-    // IMPROVE: trying to do the most sensible thing here
+    // Most sensible target to focus
     const target = node orelse previously_focused orelse list.first.?;
     previously_focused = cursor;
 
@@ -176,7 +188,6 @@ fn focus(node: ?*L.Node) void {
     cursor = target;
 }
 
-// Utils
 fn winToNode(w: C.Window) ?*L.Node {
     var next = list.first;
     while (next) |node| : (next = node.next) {
@@ -196,9 +207,8 @@ fn unmanage(allocator: std.mem.Allocator, node: *L.Node, destroyed: bool) void {
         _ = C.XUngrabServer(display);
     }
     if (node == cursor) cursor = node.prev;
-    // IMPROVE: There is no way of determining if a window is still alive so we have to make sure we set
-    // previously_focused to null if we destroy it. Another way is to set an error handler to handle
-    // BadWindow errors if we ever try to access it.
+
+    // Update previously_focused if needed
     if (previously_focused) |pf| {
         if (node.data.w == pf.data.w) previously_focused = null;
     }
@@ -216,150 +226,320 @@ fn unmanage(allocator: std.mem.Allocator, node: *L.Node, destroyed: bool) void {
     focus(null);
 }
 
-// Event handlers
-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;
+//------------------------------------------------------------------------------
+// Monitor/Display Management with RandR
+//------------------------------------------------------------------------------
 
-    _ = C.XConfigureWindow(display, e.window, @intCast(e.value_mask), &window_changes);
-}
+// Screen dimensions
+var screen_w: c_uint = 0;
+var screen_h: c_uint = 0;
+var center_w: c_uint = 0;
+var center_h: c_uint = 0;
 
-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);
+// RandR extension data
+var randr_event_base: c_int = 0;
+var randr_error_base: c_int = 0;
 
-    _ = C.XMapWindow(display, window);
-    _ = C.XSetWindowBorderWidth(display, window, BORDER_WIDTH);
+fn initRandR(allocator: std.mem.Allocator) !void {
+    // Check if RandR extension is available
+    if (C.XRRQueryExtension(display, &randr_event_base, &randr_error_base) == 0) {
+        logError("RandR extension not available");
+        return;
+    }
 
-    const node = try addClient(allocator, window);
-    focus(node);
+    // Log RandR base event
+    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);
+
+    // Get RandR version
+    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);
+    }
+
+    // Update screen dimensions based on active monitor
+    try updateScreenDimensions(allocator);
+
+    // Select RandR events to listen for monitor changes
+    _ = C.XRRSelectInput(display, root, C.RROutputChangeNotifyMask | C.RRCrtcChangeNotifyMask | C.RRScreenChangeNotifyMask);
 }
 
-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 updateScreenDimensions(allocator: std.mem.Allocator) !void {
+    // Get screen resources
+    const res = C.XRRGetScreenResources(display, root);
+    if (res == null) {
+        logError("Failed to get screen resources");
+        return;
+    }
+    defer C.XRRFreeScreenResources(res);
+
+    // Log all available outputs for debugging
+    logInfo("Scanning all available outputs:");
+    for (0..@intCast(res.*.noutput)) |i| {
+        const output_info = C.XRRGetOutputInfo(display, res, res.*.outputs[i]);
+        if (output_info == null) continue;
+
+        const output_name = std.mem.span(@as([*:0]const u8, @ptrCast(output_info.*.name)));
+        const connection_status = switch (output_info.*.connection) {
+            C.RR_Connected => "connected",
+            C.RR_Disconnected => "disconnected",
+            C.RR_UnknownConnection => "unknown",
+            else => "invalid",
+        };
+
+        const has_crtc = output_info.*.crtc != 0;
+
+        const log_msg = try std.fmt.allocPrint(allocator, "Output {d}: {s} - {s}, has_crtc: {}", .{ i, output_name, connection_status, has_crtc });
+        defer allocator.free(log_msg);
+        logInfo(log_msg);
+
+        // If it has a CRTC, print its dimensions
+        if (has_crtc) {
+            const crtc_info = C.XRRGetCrtcInfo(display, res, output_info.*.crtc);
+            if (crtc_info != null) {
+                const crtc_msg = try std.fmt.allocPrint(allocator, "  CRTC dimensions: {d}x{d} at {d},{d}", .{ crtc_info.*.width, crtc_info.*.height, crtc_info.*.x, crtc_info.*.y });
+                defer allocator.free(crtc_msg);
+                logInfo(crtc_msg);
+                C.XRRFreeCrtcInfo(crtc_info);
+            }
         }
+
+        C.XRRFreeOutputInfo(output_info);
     }
-}
 
-fn onKeyPress(e: *C.XEvent) void {
-    if (keymap.get(e.xkey.keycode)) |action| action();
-}
+    var found_active_monitor = false;
 
-fn onNotifyEnter(e: *C.XEvent) void {
-    while (C.XCheckTypedEvent(display, C.EnterNotify, e)) {}
-}
+    // First try to find and use the primary monitor
+    const primary_output = C.XRRGetOutputPrimary(display, root);
+    const primary_msg = try std.fmt.allocPrint(allocator, "Primary output ID: {d}", .{primary_output});
+    defer allocator.free(primary_msg);
+    logInfo(primary_msg);
 
-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;
-    mouse = e.xbutton;
+    if (primary_output != 0) {
+        found_active_monitor = try tryUseMonitor(allocator, res, primary_output, "primary");
+    }
 
-    if (winToNode(e.xbutton.subwindow)) |node| if (node != cursor) {
-        focus(node);
-    };
+    // If no primary monitor is active, find any connected monitor with largest dimensions
+    if (!found_active_monitor) {
+        found_active_monitor = try findLargestMonitor(allocator, res);
+    }
+
+    // If still no monitor found, use default X screen dimensions
+    if (!found_active_monitor) {
+        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;
+
+        const log_msg = try std.fmt.allocPrint(allocator, "No active monitors found, using X screen dimensions: {d}x{d}", .{ screen_w, screen_h });
+        defer allocator.free(log_msg);
+        logInfo(log_msg);
+    }
+
+    // Log final selected dimensions
+    const final_dim_msg = try std.fmt.allocPrint(allocator, "Final selected dimensions: {d}x{d}, center window: {d}x{d}", .{ screen_w, screen_h, center_w, center_h });
+    defer allocator.free(final_dim_msg);
+    logInfo(final_dim_msg);
 }
 
-fn onNotifyMotion(e: *C.XEvent) void {
-    if (mouse.subwindow == 0) return;
+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 dx: i32 = @intCast(e.xbutton.x_root - mouse.x_root);
-    const dy: i32 = @intCast(e.xbutton.y_root - mouse.y_root);
+        const output_info = C.XRRGetOutputInfo(display, res, res.*.outputs[i]);
+        if (output_info == null) continue;
+        defer C.XRRFreeOutputInfo(output_info);
 
-    const button: i32 = @intCast(mouse.button);
+        const output_name = std.mem.span(@as([*:0]const u8, @ptrCast(output_info.*.name)));
 
-    _ = C.XMoveResizeWindow(
-        display,
-        mouse.subwindow,
-        win_x + if (button == 1) dx else 0,
-        win_y + if (button == 1) dy else 0,
-        @max(10, win_w + if (button == 3) dx else 0),
-        @max(10, win_h + if (button == 3) dy else 0),
-    );
-}
+        // Only use if connected and has a CRTC (active)
+        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;
+        }
 
-fn onNotifyDestroy(allocator: std.mem.Allocator, e: *C.XEvent) void {
-    const ev = &e.xdestroywindow;
-    if (winToNode(ev.window)) |node| {
-        unmanage(allocator, node, true);
+        const crtc_info = C.XRRGetCrtcInfo(display, res, output_info.*.crtc);
+        if (crtc_info == null) continue;
+        defer C.XRRFreeCrtcInfo(crtc_info);
+
+        // Check if this monitor is actually enabled (non-zero dimensions)
+        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;
+        }
+
+        // Update screen dimensions from primary monitor
+        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;
     }
-}
 
-fn onButtonRelease(_: *C.XEvent) void {
-    mouse.subwindow = 0;
+    return false;
 }
 
-// Error handlers
-fn handleError(_: ?*C.Display, event: [*c]C.XErrorEvent) callconv(.C) c_int {
-    const evt: *C.XErrorEvent = @ptrCast(event);
-    // TODO:
-    switch (evt.error_code) {
-        C.BadMatch => logError("BadMatch"),
-        C.BadWindow => logError("BadWindow"),
-        C.BadDrawable => logError("BadDrawable"),
-        else => logError("TODO: I should handle this error"),
+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;
+    var largest_output_name: []const u8 = "none";
+
+    logInfo("No active primary monitor, searching for largest connected monitor");
+
+    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);
+
+        // Skip if not connected or no CRTC
+        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);
+
+        // Skip if dimensions are zero
+        if (crtc_info.*.width == 0 or crtc_info.*.height == 0) continue;
+
+        const output_name = std.mem.span(@as([*:0]const u8, @ptrCast(output_info.*.name)));
+        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;
+
+        const monitor_msg = try std.fmt.allocPrint(allocator, "Found connected monitor: {s} ({d}x{d}, area: {d})", .{ output_name, output_width, output_height, output_area });
+        defer allocator.free(monitor_msg);
+        logInfo(monitor_msg);
+
+        // Keep track of the largest monitor (by area)
+        if (output_area > largest_area) {
+            largest_area = output_area;
+            largest_width = output_width;
+            largest_height = output_height;
+            largest_output_name = output_name;
+        }
     }
-    return 0;
+
+    // Use the largest monitor if found
+    if (largest_area > 0) {
+        screen_w = largest_width;
+        screen_h = largest_height;
+        center_w = @divTrunc((3 * screen_w), 5);
+        center_h = screen_h - 20;
+
+        const log_msg = try std.fmt.allocPrint(allocator, "Using largest monitor: {s} ({d}x{d})", .{ largest_output_name, screen_w, screen_h });
+        defer allocator.free(log_msg);
+        logInfo(log_msg);
+
+        return true;
+    }
+
+    return false;
 }
 
-fn ignoreError(_: ?*C.Display, _: [*c]C.XErrorEvent) callconv(.C) c_int {
-    return 0;
+fn forceSyncMonitors(allocator: std.mem.Allocator) void {
+    // Force an XSync to make sure we have the latest monitor info
+    _ = C.XSync(display, 0);
+
+    // Directly update screen dimensions
+    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);
+    };
 }
 
-// Logging
-fn logError(msg: []const u8) void {
-    const stderr = std.io.getStdErr().writer();
-    stderr.print("Error: {s}\n", .{msg}) catch return;
+fn onRRNotify(allocator: std.mem.Allocator, e: *C.XEvent) !void {
+    // Access the event data properly
+    const rrev = @as(*C.XRRNotifyEvent, @ptrCast(e));
+
+    // Log the event subtype for debugging
+    const subtype_msg = try std.fmt.allocPrint(allocator, "Processing RandR notification, subtype: {d}", .{rrev.subtype});
+    defer allocator.free(subtype_msg);
+    logInfo(subtype_msg);
+
+    // Force a sync to make sure we have the latest monitor info
+    _ = C.XSync(display, 0);
+
+    // Wait a bit for xrandr changes to complete
+    std.time.sleep(100 * std.time.ns_per_ms); // 100ms delay
+
+    // Update screen dimensions
+    try updateScreenDimensions(allocator);
+
+    // Restack all windows with new dimensions
+    stackAll();
 }
 
-fn logInfo(msg: []const u8) void {
-    const stdInfo = std.io.getStdOut().writer();
-    stdInfo.print("INFO: {s}\n", .{msg}) catch return;
+//------------------------------------------------------------------------------
+// Actions for Keybindings
+//------------------------------------------------------------------------------
+
+// Handle autostart script
+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});
+        }
+    }
 }
 
-// Actions. None of these take any arguments and only work on global state and are
-// meant to be mapped to keys.
-fn quit() void {
-    shouldQuit = true;
+fn killClient() void {
+    // If no window is selected, return
+    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);
+        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);
+        if (c.prev) |prev|
+            focus(prev)
+        else if (list.last) |last|
+            focus(last);
     }
 }
 
@@ -440,6 +620,7 @@ fn winFullscreen() void {
     }
 }
 
+// Process handling (for application launching)
 extern fn execvp(prog: [*:0]const u8, argv: [*]const ?[*:0]const u8) c_int;
 extern var errno: c_int;
 
@@ -469,39 +650,210 @@ fn spawn(cmd: [*:0]const u8) void {
     }
 }
 
-fn spawnTerminal() void {
-    spawn("st");
+//------------------------------------------------------------------------------
+// X11 Event Handlers
+//------------------------------------------------------------------------------
+
+// Error handlers
+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);
+}
+
+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);
+}
+
+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 onNotifyEnter(e: *C.XEvent) void {
+    while (C.XCheckTypedEvent(display, C.EnterNotify, e)) {}
+}
+
+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;
+    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);
+
+    _ = C.XMoveResizeWindow(
+        display,
+        mouse.subwindow,
+        win_x + if (button == 1) dx else 0,
+        win_y + if (button == 1) dy else 0,
+        @max(10, win_w + if (button == 3) dx else 0),
+        @max(10, win_h + if (button == 3) dy else 0),
+    );
+}
+
+fn onNotifyDestroy(allocator: std.mem.Allocator, e: *C.XEvent) void {
+    const ev = &e.xdestroywindow;
+    if (winToNode(ev.window)) |node| {
+        unmanage(allocator, node, true);
+    }
 }
 
-fn spawnDmenu() void {
-    spawn("dmenu_run");
+fn onButtonRelease(_: *C.XEvent) void {
+    mouse.subwindow = 0;
 }
 
-// Main loop
+//------------------------------------------------------------------------------
+// Global Application State
+//------------------------------------------------------------------------------
+
+// Application state variables
+var shouldQuit = false;
+var display: *C.Display = undefined;
+var root: C.Window = undefined;
+var window_changes: C.XWindowChanges = undefined;
+
+// Primarly used to store window attributes when a window is being
+// clicked on before we start potentially moving/resizing it.
+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;
+
+//------------------------------------------------------------------------------
+// Main Application Entry Point
+//------------------------------------------------------------------------------
+
 pub fn main() !void {
+    // Setup memory allocator
     var gpa = std.heap.GeneralPurposeAllocator(.{}){};
     const allocator = gpa.allocator();
 
     var event: C.XEvent = undefined;
 
-    display = C.XOpenDisplay(0) orelse std.c._exit(1);
+    // Initialize X11 connection
+    display = C.XOpenDisplay(0) orelse {
+        logError("Could not open display");
+        std.c._exit(1);
+    };
 
+    // Setup X11 display and root window
     const screen = C.DefaultScreen(display);
     root = C.RootWindow(display, screen);
+
+    // Initialize screen dimensions (will be updated by initRandR)
     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;
 
+    logInfo("Initializing window manager");
+
+    // Initialize RandR for multi-monitor support
+    try initRandR(allocator);
+
+    // Set error handler and input masks
     _ = C.XSetErrorHandler(handleError);
     _ = C.XSelectInput(display, root, C.SubstructureRedirectMask);
     _ = C.XDefineCursor(display, root, C.XCreateFontCursor(display, 68));
 
+    // Setup input grabbing
     grabInput(root);
-    keymap = initKeyMap(allocator) catch @panic("failed to init keymap");
+    keymap = try initKeyMap(allocator);
+
+    // Run autostart script if configured
+    run();
 
+    // Force initial monitor detection
+    forceSyncMonitors(allocator);
+
+    // Sync X11 state before event loop
     _ = C.XSync(display, 0);
+
+    // Log RandR event base for debugging
+    logInfo("Waiting for events. RandR event base is:");
+    const event_base_msg = try std.fmt.allocPrint(allocator, "{d}", .{randr_event_base});
+    defer allocator.free(event_base_msg);
+    logInfo(event_base_msg);
+
+    // Main event loop
     while (!shouldQuit and C.XNextEvent(display, &event) == 0) {
+        // Debug event logging for RandR events
+        if (event.type > 64 and event.type < 128) {
+            const event_msg = try std.fmt.allocPrint(allocator, "Received event type: {d}", .{event.type});
+            defer allocator.free(event_msg);
+            logInfo(event_msg);
+        }
+
         switch (event.type) {
             C.MapRequest => try onMapRequest(allocator, &event),
             C.UnmapNotify => onUnmapNotify(allocator, &event),
@@ -511,10 +863,22 @@ pub fn main() !void {
             C.MotionNotify => onNotifyMotion(&event),
             C.DestroyNotify => onNotifyDestroy(allocator, &event),
             C.ConfigureRequest => onConfigureRequest(@ptrCast(&event)),
-            else => continue,
+            else => {
+                // Handle RandR events
+                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.XCloseDisplay(display);
     std.c.exit(0);
 }