Diff
diff --git a/src/bar/bar.zig b/src/bar/bar.zig
index a4b1366..bd69d7e 100644
--- a/src/bar/bar.zig
+++ b/src/bar/bar.zig
@@ -1,7 +1,6 @@
const std = @import("std");
const xlib = @import("../x11/xlib.zig");
const monitor_mod = @import("../monitor.zig");
-const client_mod = @import("../client.zig");
const blocks_mod = @import("blocks/blocks.zig");
const config_mod = @import("../config/config.zig");
const ColorScheme = config_mod.ColorScheme;
@@ -9,22 +8,12 @@ const ColorScheme = config_mod.ColorScheme;
const Monitor = monitor_mod.Monitor;
const Block = blocks_mod.Block;
-fn get_layout_symbol(layout_index: u32, config: ?config_mod.Config) []const u8 {
- if (config) |conf| {
- return switch (layout_index) {
- 0 => conf.layout_tile_symbol,
- 1 => conf.layout_monocle_symbol,
- 2 => conf.layout_floating_symbol,
- 3 => "[S]",
- 4 => "[#]",
- else => "[?]",
- };
- }
+fn get_layout_symbol(layout_index: u32, config: config_mod.Config) []const u8 {
return switch (layout_index) {
- 0 => "[]=",
- 1 => "[M]",
- 2 => "><>",
- 3 => "[S]",
+ 0 => config.layout_tile_symbol,
+ 1 => config.layout_monocle_symbol,
+ 2 => config.layout_floating_symbol,
+ 3 => config.layout_scrolling_symbol,
4 => "[#]",
else => "[?]",
};
@@ -53,6 +42,8 @@ pub const Bar = struct {
needs_redraw: bool,
next: ?*Bar,
+ /// Creates a bar window for `monitor` using the given config.
+ /// Returns null on allocation failure or if the font cannot be loaded.
pub fn create(
allocator: std.mem.Allocator,
display: *xlib.Display,
@@ -67,7 +58,10 @@ pub const Bar = struct {
const depth = xlib.XDefaultDepth(display, screen);
const root = xlib.XRootWindow(display, screen);
- const font_name_z = allocator.dupeZ(u8, config.font) catch return null;
+ const font_name_z = allocator.dupeZ(u8, config.font) catch {
+ allocator.destroy(bar);
+ return null;
+ };
defer allocator.free(font_name_z);
const font = xlib.XftFontOpenName(display, screen, font_name_z);
@@ -91,7 +85,6 @@ pub const Bar = struct {
0x1a1b26,
);
- _ = xlib.c.XSetWindowAttributes{};
var attributes: xlib.c.XSetWindowAttributes = undefined;
attributes.override_redirect = xlib.True;
attributes.event_mask = xlib.c.ExposureMask | xlib.c.ButtonPressMask;
@@ -106,7 +99,6 @@ pub const Bar = struct {
);
const graphics_context = xlib.XCreateGC(display, pixmap, 0, null);
-
const xft_draw = xlib.XftDrawCreate(display, pixmap, visual, colormap);
_ = xlib.XMapWindow(display, window);
@@ -127,7 +119,7 @@ pub const Bar = struct {
.scheme_urgent = config.scheme_urgent,
.hide_vacant_tags = config.hide_vacant_tags,
.allocator = allocator,
- .blocks = .{},
+ .blocks = .empty,
.needs_redraw = true,
.next = null,
};
@@ -139,13 +131,12 @@ pub const Bar = struct {
return bar;
}
+ /// Destroys the bar's X resources and frees the allocation.
+ // TODO: Remove `allocator` param as its already stored in `Bar`.
pub fn destroy(self: *Bar, allocator: std.mem.Allocator, display: *xlib.Display) void {
- if (self.xft_draw) |xft_draw| {
- xlib.XftDrawDestroy(xft_draw);
- }
- if (self.font) |font| {
- xlib.XftFontClose(display, font);
- }
+ if (self.xft_draw) |xft_draw| xlib.XftDrawDestroy(xft_draw);
+ if (self.font) |font| xlib.XftFontClose(display, font);
+
_ = xlib.XFreeGC(display, self.graphics_context);
_ = xlib.XFreePixmap(display, self.pixmap);
_ = xlib.c.XDestroyWindow(display, self.window);
@@ -157,11 +148,16 @@ pub const Bar = struct {
self.blocks.append(self.allocator, block) catch {};
}
+ pub fn clear_blocks(self: *Bar) void {
+ self.blocks.clearRetainingCapacity();
+ }
+
pub fn invalidate(self: *Bar) void {
self.needs_redraw = true;
}
- pub fn draw(self: *Bar, display: *xlib.Display, tags: []const []const u8, config: ?config_mod.Config) void {
+ /// Redraws the bar if marked dirty. Tags are taken from `config.tags`
+ pub fn draw(self: *Bar, display: *xlib.Display, config: config_mod.Config) void {
if (!self.needs_redraw) return;
self.fill_rect(display, 0, 0, self.width, self.height, self.scheme_normal.background);
@@ -171,14 +167,19 @@ pub const Bar = struct {
const monitor = self.monitor;
const current_tags = monitor.tagset[monitor.sel_tags];
- for (tags, 0..) |tag, index| {
+ for (config.tags, 0..) |tag, index| {
const tag_mask: u32 = @as(u32, 1) << @intCast(index);
const is_selected = (current_tags & tag_mask) != 0;
const is_occupied = has_clients_on_tag(monitor, tag_mask);
if (self.hide_vacant_tags and !is_occupied and !is_selected) continue;
- const scheme = if (is_selected) self.scheme_selected else if (is_occupied) self.scheme_occupied else self.scheme_normal;
+ const scheme = if (is_selected)
+ self.scheme_selected
+ else if (is_occupied)
+ self.scheme_occupied
+ else
+ self.scheme_normal;
const tag_text_width = self.text_width(display, tag);
const tag_width = tag_text_width + padding * 2;
@@ -189,7 +190,6 @@ pub const Bar = struct {
const text_y = @divTrunc(self.height + self.font_height, 2) - 4;
self.draw_text(display, x_position + padding, text_y, tag, scheme.foreground);
-
x_position += tag_width;
}
@@ -220,48 +220,15 @@ pub const Bar = struct {
self.needs_redraw = false;
}
- fn fill_rect(self: *Bar, display: *xlib.Display, x: i32, y: i32, width: i32, height: i32, color: c_ulong) void {
- _ = xlib.XSetForeground(display, self.graphics_context, color);
- _ = xlib.XFillRectangle(display, self.pixmap, self.graphics_context, x, y, @intCast(width), @intCast(height));
- }
-
- fn draw_text(self: *Bar, display: *xlib.Display, x: i32, y: i32, text: []const u8, color: c_ulong) void {
- if (self.xft_draw == null or self.font == null) return;
-
- var xft_color: xlib.XftColor = undefined;
- var render_color: xlib.XRenderColor = undefined;
- render_color.red = @intCast((color >> 16 & 0xff) * 257);
- render_color.green = @intCast((color >> 8 & 0xff) * 257);
- render_color.blue = @intCast((color & 0xff) * 257);
- render_color.alpha = 0xffff;
-
- const visual = xlib.XDefaultVisual(display, 0);
- const colormap = xlib.XDefaultColormap(display, 0);
-
- _ = xlib.XftColorAllocValue(display, visual, colormap, &render_color, &xft_color);
-
- xlib.XftDrawStringUtf8(self.xft_draw, &xft_color, self.font, x, y, text.ptr, @intCast(text.len));
-
- xlib.XftColorFree(display, visual, colormap, &xft_color);
- }
-
- fn text_width(self: *Bar, display: *xlib.Display, text: []const u8) i32 {
- if (self.font == null) return 0;
-
- var extents: xlib.XGlyphInfo = undefined;
- xlib.XftTextExtentsUtf8(display, self.font, text.ptr, @intCast(text.len), &extents);
- return extents.xOff;
- }
-
- pub fn handle_click(self: *Bar, click_x: i32, tags: []const []const u8) ?usize {
+ /// Returns the index of the tag the user clicked on, or null if the
+ /// click was outside the tag area.
+ pub fn handle_click(self: *Bar, display: *xlib.Display, click_x: i32, config: config_mod.Config) ?usize {
var x_position: i32 = 0;
const padding: i32 = 8;
- const display = xlib.c.XOpenDisplay(null) orelse return null;
- defer _ = xlib.XCloseDisplay(display);
-
const monitor = self.monitor;
const current_tags = monitor.tagset[monitor.sel_tags];
- for (tags, 0..) |tag, index| {
+
+ for (config.tags, 0..) |tag, index| {
const tag_mask = @as(u32, 1) << @intCast(index);
const is_selected = (current_tags & tag_mask) != 0;
const is_occupied = has_clients_on_tag(monitor, tag_mask);
@@ -279,59 +246,51 @@ pub const Bar = struct {
return null;
}
+ /// Updates all blocks and marks the bar dirty if any block changed.
pub fn update_blocks(self: *Bar) void {
var changed = false;
for (self.blocks.items) |*block| {
- if (block.update()) {
- changed = true;
- }
- }
- if (changed) {
- self.needs_redraw = true;
+ if (block.update()) changed = true;
}
+ if (changed) self.needs_redraw = true;
}
- pub fn clear_blocks(self: *Bar) void {
- self.blocks.clearRetainingCapacity();
+ fn fill_rect(self: *Bar, display: *xlib.Display, x: i32, y: i32, width: i32, height: i32, color: c_ulong) void {
+ _ = xlib.XSetForeground(display, self.graphics_context, color);
+ _ = xlib.XFillRectangle(display, self.pixmap, self.graphics_context, x, y, @intCast(width), @intCast(height));
}
-};
-fn has_clients_on_tag(monitor: *Monitor, tag_mask: u32) bool {
- var current = monitor.clients;
- while (current) |client| {
- if ((client.tags & tag_mask) != 0) {
- return true;
- }
- current = client.next;
- }
- return false;
-}
+ fn draw_text(self: *Bar, display: *xlib.Display, x: i32, y: i32, text: []const u8, color: c_ulong) void {
+ if (self.xft_draw == null or self.font == null) return;
-pub var bars: ?*Bar = null;
+ var xft_color: xlib.XftColor = undefined;
+ var render_color: xlib.XRenderColor = undefined;
+ render_color.red = @intCast((color >> 16 & 0xff) * 257);
+ render_color.green = @intCast((color >> 8 & 0xff) * 257);
+ render_color.blue = @intCast((color & 0xff) * 257);
+ render_color.alpha = 0xffff;
-pub fn create_bars(allocator: std.mem.Allocator, display: *xlib.Display, screen: c_int) void {
- var current_monitor = monitor_mod.monitors;
- while (current_monitor) |monitor| {
- const bar = Bar.create(allocator, display, screen, monitor, "monospace:size=10");
- if (bar) |created_bar| {
- bars = created_bar;
- }
- current_monitor = monitor.next;
+ const visual = xlib.XDefaultVisual(display, 0);
+ const colormap = xlib.XDefaultColormap(display, 0);
+
+ _ = xlib.XftColorAllocValue(display, visual, colormap, &render_color, &xft_color);
+ xlib.XftDrawStringUtf8(self.xft_draw, &xft_color, self.font, x, y, text.ptr, @intCast(text.len));
+ xlib.XftColorFree(display, visual, colormap, &xft_color);
}
-}
-pub fn draw_bars(display: *xlib.Display, tags: []const []const u8) void {
- var current_monitor = monitor_mod.monitors;
- while (current_monitor) |monitor| {
- _ = monitor;
- if (bars) |bar| {
- bar.draw(display, tags, null);
- }
- current_monitor = if (current_monitor) |m| m.next else null;
+ fn text_width(self: *Bar, display: *xlib.Display, text: []const u8) i32 {
+ if (self.font == null) return 0;
+
+ var extents: xlib.XGlyphInfo = undefined;
+ xlib.XftTextExtentsUtf8(display, self.font, text.ptr, @intCast(text.len), &extents);
+ return extents.xOff;
}
-}
+};
+
+// Bar list helpers >.<
-pub fn invalidate_bars() void {
+/// Marks all bars in the list as needing a redraw.
+pub fn invalidate_bars(bars: ?*Bar) void {
var current = bars;
while (current) |bar| {
bar.invalidate();
@@ -339,23 +298,31 @@ pub fn invalidate_bars() void {
}
}
-pub fn destroy_bars(allocator: std.mem.Allocator, display: *xlib.Display) void {
+/// Destroys all bars in the list and frees their resources.
+pub fn destroy_bars(bars: ?*Bar, allocator: std.mem.Allocator, display: *xlib.Display) void {
var current = bars;
while (current) |bar| {
const next = bar.next;
bar.destroy(allocator, display);
current = next;
}
- bars = null;
}
-pub fn window_to_bar(win: xlib.Window) ?*Bar {
+/// Returns the bar whose window matches `win`, or null.
+pub fn window_to_bar(bars: ?*Bar, win: xlib.Window) ?*Bar {
var current = bars;
while (current) |bar| {
- if (bar.window == win) {
- return bar;
- }
+ if (bar.window == win) return bar;
current = bar.next;
}
return null;
}
+
+fn has_clients_on_tag(monitor: *Monitor, tag_mask: u32) bool {
+ var current = monitor.clients;
+ while (current) |client| {
+ if ((client.tags & tag_mask) != 0) return true;
+ current = client.next;
+ }
+ return false;
+}
diff --git a/src/main.zig b/src/main.zig
index faf0b6d..e9160ce 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,20 +1,14 @@
const std = @import("std");
const VERSION = "v0.11.2";
-const Atoms = @import("x11/atoms.zig").Atoms;
-const kchord = @import("keyboard/chord.zig");
+const wm_mod = @import("wm.zig");
const display_mod = @import("x11/display.zig");
const events = @import("x11/events.zig");
const xlib = @import("x11/xlib.zig");
const client_mod = @import("client.zig");
const monitor_mod = @import("monitor.zig");
const tiling = @import("layouts/tiling.zig");
-const monocle = @import("layouts/monocle.zig");
-const floating = @import("layouts/floating.zig");
const scrolling = @import("layouts/scrolling.zig");
-const grid = @import("layouts/grid.zig");
-const animations = @import("animations.zig");
const bar_mod = @import("bar/bar.zig");
-const blocks_mod = @import("bar/blocks/blocks.zig");
const config_mod = @import("config/config.zig");
const lua = @import("config/lua.zig");
const overlay_mod = @import("overlay.zig");
@@ -22,50 +16,47 @@ const overlay_mod = @import("overlay.zig");
const Display = display_mod.Display;
const Client = client_mod.Client;
const Monitor = monitor_mod.Monitor;
+const WindowManager = wm_mod.WindowManager;
-var running: bool = true;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
-var atoms: Atoms = undefined;
-var wm_check_window: xlib.Window = undefined;
-
-const Cursors = struct {
- normal: xlib.Cursor,
- resize: xlib.Cursor,
- move: xlib.Cursor,
-
- fn init(display: *Display) Cursors {
- return .{
- .normal = xlib.XCreateFontCursor(display.handle, xlib.XC_left_ptr),
- .resize = xlib.XCreateFontCursor(display.handle, xlib.XC_sizing),
- .move = xlib.XCreateFontCursor(display.handle, xlib.XC_fleur),
- };
- }
-};
-
-var cursors: Cursors = undefined;
-
const NormalState: c_long = 1;
const WithdrawnState: c_long = 0;
const IconicState: c_long = 3;
const IsViewable: c_int = 2;
const snap_distance: i32 = 32;
-var numlock_mask: c_uint = 0;
+// Remaining module-level state
+/// Config built from Lua, passed into WindowManager.init.
var config: config_mod.Config = undefined;
+/// Path to the loaded config file, kept for hot-reload.
var config_path_global: ?[]const u8 = null;
-/// Interim pointer to the X11 display, used only by `arrange` until the
-/// WindowManager struct refactor moves display ownership there properly.
+/// TODO(wm-refactor): move into WindowManager, waiting on handle_key_press
+/// and execute_action being converted to methods.
+var chord = @import("keyboard/chord.zig").ChordState{};
+
+/// TODO(wm-refactor): remove once all callers are WindowManager methods.
+/// Aliases wm.atoms so existing functions that take display but not wm
+/// can still reach atom values without requiring a full signature change.
+var atoms: @import("x11/atoms.zig").Atoms = undefined;
+
+/// TODO(wm-refactor): move into WindowManager, waiting on arrange() becoming
+/// a method so it can access self.display directly.
var wm_display: ?*Display = null;
-var scroll_animation: animations.Scroll_Animation = .{};
-var animation_config: animations.Animation_Config = .{ .duration_ms = 150, .easing = .ease_out };
+/// TODO(wm-refactor): remove once all callers are WindowManager methods.
+/// Used only by functions that need to invalidate bars but don't yet receive
+/// wm as a parameter.
+var wm_ptr: ?*WindowManager = null;
-var chord = kchord.ChordState{};
+/// TODO(wm-refactor): move into WindowManager.
+var numlock_mask: c_uint = 0;
-var keybind_overlay: ?*overlay_mod.Keybind_Overlay = null;
+/// File descriptor of the X11 connection, used in spawn_child_setup.
+/// Set from wm.x11_fd after init.
+var x11_fd: c_int = -1;
fn print_help() void {
std.debug.print(
@@ -98,6 +89,7 @@ fn get_config_path(allocator: std.mem.Allocator) ![]u8 {
const home = std.posix.getenv("HOME") orelse return error.CouldNotGetHomeDir;
break :blk try std.fs.path.join(allocator, &.{ home, ".config" });
};
+ // TODO: wtf is this shit
defer if (std.posix.getenv("XDG_CONFIG_HOME") == null) allocator.free(config_home);
const config_path = try std.fs.path.join(allocator, &.{ config_home, "oxwm", "config.lua" });
@@ -201,7 +193,6 @@ pub fn main() !void {
std.debug.print("oxwm starting\n", .{});
config = config_mod.Config.init(allocator);
- defer config.deinit();
if (lua.init(&config)) {
const loaded = if (std.fs.cwd().statFile(config_path)) |_|
@@ -223,229 +214,34 @@ pub fn main() !void {
initialize_default_config();
}
- var display = Display.open() catch |err| {
- std.debug.print("failed to open display: {}\n", .{err});
+ var wm = WindowManager.init(allocator, config, config_path_global) catch |err| {
+ std.debug.print("failed to start window manager: {}\n", .{err});
return;
};
- defer display.close();
-
- x11_fd = xlib.XConnectionNumber(display.handle);
- wm_display = &display;
+ defer wm.deinit();
- std.debug.print("display opened: screen={d} root=0x{x}\n", .{ display.screen, display.root });
- std.debug.print("screen size: {d}x{d}\n", .{ display.screen_width(), display.screen_height() });
-
- display.become_window_manager() catch |err| {
- std.debug.print("failed to become window manager: {}\n", .{err});
- return;
- };
+ // Propagate values that transitional code in this file still reads
+ // from module-level vars. These will be removed soon.
+ x11_fd = wm.x11_fd;
+ wm_display = &wm.display;
+ wm_ptr = &wm;
+ atoms = wm.atoms;
+ std.debug.print("display opened: screen={d} root=0x{x}\n", .{ wm.display.screen, wm.display.root });
std.debug.print("successfully became window manager\n", .{});
-
- const atoms_result = Atoms.init(display.handle, display.root);
- atoms = atoms_result.atoms;
- wm_check_window = atoms_result.check_window;
std.debug.print("atoms initialized with EWMH support\n", .{});
- cursors = Cursors.init(&display);
- _ = xlib.XDefineCursor(display.handle, display.root, cursors.normal);
- monitor_mod.init(allocator);
- tiling.set_display(display.handle);
- tiling.set_screen_size(display.screen_width(), display.screen_height());
-
- setup_monitors(&display);
- setup_bars(allocator, &display);
- setup_overlay(allocator, &display);
- grab_keybinds(&display);
- scan_existing_windows(&display);
+ grab_keybinds(&wm.display);
+ scan_existing_windows(&wm.display);
try run_autostart_commands(allocator, config.autostart.items);
std.debug.print("entering event loop\n", .{});
- run_event_loop(&display);
-
- bar_mod.destroy_bars(allocator, display.handle);
-
- var mon = monitor_mod.monitors;
- while (mon) |m| {
- const next = m.next;
- monitor_mod.destroy(m);
- mon = next;
- }
-
- if (keybind_overlay) |overlay| {
- overlay.deinit(allocator);
- }
+ run_event_loop(&wm);
lua.deinit();
std.debug.print("oxwm exiting\n", .{});
}
-fn setup_bars(allocator: std.mem.Allocator, display: *Display) void {
- var current_monitor = monitor_mod.monitors;
- var last_bar: ?*bar_mod.Bar = null;
-
- while (current_monitor) |monitor| {
- const bar = bar_mod.Bar.create(allocator, display.handle, display.screen, monitor, config);
- if (bar) |created_bar| {
- if (tiling.bar_height == 0) {
- tiling.set_bar_height(created_bar.height);
- }
-
- if (config.blocks.items.len > 0) {
- for (config.blocks.items) |cfg_block| {
- const block = config_block_to_bar_block(cfg_block);
- created_bar.add_block(block);
- }
- } else {
- created_bar.add_block(blocks_mod.Block.init_ram("", 5, 0x7aa2f7, true));
- created_bar.add_block(blocks_mod.Block.init_static(" | ", 0x666666, false));
- created_bar.add_block(blocks_mod.Block.init_datetime("", "%H:%M", 1, 0x0db9d7, true));
- }
-
- if (last_bar) |prev| {
- prev.next = created_bar;
- } else {
- bar_mod.bars = created_bar;
- }
- last_bar = created_bar;
- std.debug.print("bar created for monitor {d}\n", .{monitor.num});
- }
- current_monitor = monitor.next;
- }
-}
-
-fn setup_overlay(allocator: std.mem.Allocator, display: *Display) void {
- keybind_overlay = overlay_mod.Keybind_Overlay.init(display.handle, display.screen, display.root, config.font, allocator);
-}
-
-fn config_block_to_bar_block(cfg: config_mod.Block) blocks_mod.Block {
- return switch (cfg.block_type) {
- .static => blocks_mod.Block.init_static(cfg.format, cfg.color, cfg.underline),
- .datetime => blocks_mod.Block.init_datetime(cfg.format, cfg.datetime_format orelse "%H:%M", cfg.interval, cfg.color, cfg.underline),
- .ram => blocks_mod.Block.init_ram(cfg.format, cfg.interval, cfg.color, cfg.underline),
- .shell => blocks_mod.Block.init_shell(cfg.format, cfg.command orelse "", cfg.interval, cfg.color, cfg.underline),
- .battery => blocks_mod.Block.init_battery(
- cfg.format_charging orelse "",
- cfg.format_discharging orelse "",
- cfg.format_full orelse "",
- cfg.battery_name orelse "BAT0",
- cfg.interval,
- cfg.color,
- cfg.underline,
- ),
- .cpu_temp => blocks_mod.Block.init_cpu_temp(
- cfg.format,
- cfg.thermal_zone orelse "thermal_zone0",
- cfg.interval,
- cfg.color,
- cfg.underline,
- ),
- };
-}
-
-fn setup_monitors(display: *Display) void {
- std.debug.print("checking xinerama...\n", .{});
- if (xlib.XineramaIsActive(display.handle) != 0) {
- std.debug.print("xinerama is active!\n", .{});
- var screen_count: c_int = 0;
- const screens = xlib.XineramaQueryScreens(display.handle, &screen_count);
-
- if (screen_count > 0 and screens != null) {
- var prev_monitor: ?*Monitor = null;
- var index: usize = 0;
-
- while (index < @as(usize, @intCast(screen_count))) : (index += 1) {
- const screen = screens[index];
- const mon = monitor_mod.create() orelse continue;
-
- mon.num = @intCast(index);
- mon.mon_x = screen.x_org;
- mon.mon_y = screen.y_org;
- mon.mon_w = screen.width;
- mon.mon_h = screen.height;
- mon.win_x = screen.x_org;
- mon.win_y = screen.y_org;
- mon.win_w = screen.width;
- mon.win_h = screen.height;
- mon.lt[0] = &tiling.layout;
- mon.lt[1] = &monocle.layout;
- mon.lt[2] = &floating.layout;
- mon.lt[3] = &scrolling.layout;
- mon.lt[4] = &grid.layout;
- for (0..10) |i| {
- mon.pertag.ltidxs[i][0] = mon.lt[0];
- mon.pertag.ltidxs[i][1] = mon.lt[1];
- mon.pertag.ltidxs[i][2] = mon.lt[2];
- mon.pertag.ltidxs[i][3] = mon.lt[3];
- mon.pertag.ltidxs[i][4] = mon.lt[4];
- }
-
- init_monitor_gaps(mon);
-
- if (prev_monitor) |prev| {
- prev.next = mon;
- } else {
- monitor_mod.monitors = mon;
- }
- prev_monitor = mon;
-
- std.debug.print("monitor {d}: {d}x{d} at ({d},{d})\n", .{ index, mon.mon_w, mon.mon_h, mon.mon_x, mon.mon_y });
- }
-
- monitor_mod.selected_monitor = monitor_mod.monitors;
- _ = xlib.XFree(@ptrCast(screens));
- return;
- }
- } else {
- std.debug.print("xinerama not active, using single monitor\n", .{});
- }
-
- const mon = monitor_mod.create() orelse return;
- mon.mon_x = 0;
- mon.mon_y = 0;
- mon.mon_w = display.screen_width();
- mon.mon_h = display.screen_height();
- mon.win_x = 0;
- mon.win_y = 0;
- mon.win_w = display.screen_width();
- mon.win_h = display.screen_height();
- mon.lt[0] = &tiling.layout;
- mon.lt[1] = &monocle.layout;
- mon.lt[2] = &floating.layout;
- mon.lt[3] = &scrolling.layout;
- mon.lt[4] = &grid.layout;
- for (0..10) |i| {
- mon.pertag.ltidxs[i][0] = mon.lt[0];
- mon.pertag.ltidxs[i][1] = mon.lt[1];
- mon.pertag.ltidxs[i][2] = mon.lt[2];
- mon.pertag.ltidxs[i][3] = mon.lt[3];
- mon.pertag.ltidxs[i][4] = mon.lt[4];
- }
-
- init_monitor_gaps(mon);
-
- monitor_mod.monitors = mon;
- monitor_mod.selected_monitor = mon;
- std.debug.print("monitor created: {d}x{d}\n", .{ mon.mon_w, mon.mon_h });
-}
-
-fn init_monitor_gaps(mon: *Monitor) void {
- const any_gap_nonzero = config.gap_inner_h != 0 or config.gap_inner_v != 0 or
- config.gap_outer_h != 0 or config.gap_outer_v != 0;
-
- if (config.gaps_enabled and any_gap_nonzero) {
- mon.gap_inner_h = config.gap_inner_h;
- mon.gap_inner_v = config.gap_inner_v;
- mon.gap_outer_h = config.gap_outer_h;
- mon.gap_outer_v = config.gap_outer_v;
- } else {
- mon.gap_inner_h = 0;
- mon.gap_inner_v = 0;
- mon.gap_outer_h = 0;
- mon.gap_outer_v = 0;
- }
-}
-
fn make_keybind(mod: u32, key: u64, action: config_mod.Action) config_mod.Keybind {
var kb: config_mod.Keybind = .{ .action = action };
kb.keys[0] = .{ .mod_mask = mod, .keysym = key };
@@ -634,35 +430,34 @@ fn scan_existing_windows(display: *Display) void {
}
}
-fn run_event_loop(display: *Display) void {
- const fd = xlib.XConnectionNumber(display.handle);
+fn run_event_loop(wm: *WindowManager) void {
var fds = [_]std.posix.pollfd{
- .{ .fd = fd, .events = std.posix.POLL.IN, .revents = 0 },
+ .{ .fd = wm.x11_fd, .events = std.posix.POLL.IN, .revents = 0 },
};
- _ = xlib.XSync(display.handle, xlib.False);
+ _ = xlib.XSync(wm.display.handle, xlib.False);
- while (running) {
- while (xlib.XPending(display.handle) > 0) {
- var event = display.next_event();
- handle_event(display, &event);
+ while (wm.running) {
+ while (xlib.XPending(wm.display.handle) > 0) {
+ var event = wm.display.next_event();
+ handle_event(&wm.display, &event, wm);
}
- tick_animations();
+ tick_animations(wm);
- var current_bar = bar_mod.bars;
+ var current_bar = wm.bars;
while (current_bar) |bar| {
bar.update_blocks();
- bar.draw(display.handle, &config.tags, config);
+ bar.draw(wm.display.handle, wm.config);
current_bar = bar.next;
}
- const poll_timeout: i32 = if (scroll_animation.is_active()) 16 else 1000;
+ const poll_timeout: i32 = if (wm.scroll_animation.is_active()) 16 else 1000;
_ = std.posix.poll(&fds, poll_timeout) catch 0;
}
}
-fn handle_event(display: *Display, event: *xlib.XEvent) void {
+fn handle_event(display: *Display, event: *xlib.XEvent, wm: *WindowManager) void {
const event_type = events.get_event_type(event);
if (event_type == .button_press) {
@@ -672,15 +467,15 @@ fn handle_event(display: *Display, event: *xlib.XEvent) void {
switch (event_type) {
.map_request => handle_map_request(display, &event.xmaprequest),
.configure_request => handle_configure_request(display, &event.xconfigurerequest),
- .key_press => handle_key_press(display, &event.xkey),
+ .key_press => handle_key_press(display, &event.xkey, wm),
.destroy_notify => handle_destroy_notify(display, &event.xdestroywindow),
.unmap_notify => handle_unmap_notify(display, &event.xunmap),
- .enter_notify => handle_enter_notify(display, &event.xcrossing),
+ .enter_notify => handle_enter_notify(display, &event.xcrossing, wm),
.focus_in => handle_focus_in(display, &event.xfocus),
- .motion_notify => handle_motion_notify(display, &event.xmotion),
+ .motion_notify => handle_motion_notify(display, &event.xmotion, wm),
.client_message => handle_client_message(display, &event.xclient),
- .button_press => handle_button_press(display, &event.xbutton),
- .expose => handle_expose(display, &event.xexpose),
+ .button_press => handle_button_press(display, &event.xbutton, wm),
+ .expose => handle_expose(display, &event.xexpose, wm),
.property_notify => handle_property_notify(display, &event.xproperty),
else => {},
}
@@ -843,42 +638,37 @@ fn handle_configure_request(display: *Display, event: *xlib.XConfigureRequestEve
_ = xlib.XSync(display.handle, xlib.False);
}
-fn reset_chord_state(display_handle: *xlib.Display) void {
- chord.reset(display_handle);
-}
-
-fn handle_key_press(display: *Display, event: *xlib.XKeyEvent) void {
+fn handle_key_press(display: *Display, event: *xlib.XKeyEvent, wm: *WindowManager) void {
const keysym = xlib.XKeycodeToKeysym(display.handle, @intCast(event.keycode), 0);
- if (keybind_overlay) |overlay| {
- if (overlay.handle_key(keysym)) {
- return;
- }
+ if (wm.overlay) |overlay| {
+ if (overlay.handle_key(keysym)) return;
}
const clean_state = event.state & ~@as(c_uint, xlib.LockMask | xlib.Mod2Mask);
- const current_time = std.time.milliTimestamp();
- if (chord.index > 0 and (current_time - chord.last_timestamp) > kchord.timeout_ms) {
- reset_chord_state(display.handle);
+ if (wm.chord.is_timed_out()) {
+ wm.chord.reset(display.handle);
}
- _ = chord.push(.{ .mod_mask = clean_state, .keysym = keysym });
+ _ = wm.chord.push(.{ .mod_mask = clean_state, .keysym = keysym });
for (config.keybinds.items) |keybind| {
if (keybind.key_count == 0) continue;
- if (keybind.key_count == chord.index) {
+ if (keybind.key_count == wm.chord.index) {
var matches = true;
for (0..keybind.key_count) |i| {
- if (chord.keys[i].keysym != keybind.keys[i].keysym or chord.keys[i].mod_mask != keybind.keys[i].mod_mask) {
+ if (wm.chord.keys[i].keysym != keybind.keys[i].keysym or
+ wm.chord.keys[i].mod_mask != keybind.keys[i].mod_mask)
+ {
matches = false;
break;
}
}
if (matches) {
- execute_action(display, keybind.action, keybind.int_arg, keybind.str_arg);
- reset_chord_state(display.handle);
+ execute_action(display, keybind.action, keybind.int_arg, keybind.str_arg, wm);
+ wm.chord.reset(display.handle);
return;
}
}
@@ -886,10 +676,12 @@ fn handle_key_press(display: *Display, event: *xlib.XKeyEvent) void {
var has_partial_match = false;
for (config.keybinds.items) |keybind| {
- if (keybind.key_count > chord.index) {
+ if (keybind.key_count > wm.chord.index) {
var matches = true;
- for (0..chord.index) |i| {
- if (chord.keys[i].keysym != keybind.keys[i].keysym or chord.keys[i].mod_mask != keybind.keys[i].mod_mask) {
+ for (0..wm.chord.index) |i| {
+ if (wm.chord.keys[i].keysym != keybind.keys[i].keysym or
+ wm.chord.keys[i].mod_mask != keybind.keys[i].mod_mask)
+ {
matches = false;
break;
}
@@ -902,32 +694,30 @@ fn handle_key_press(display: *Display, event: *xlib.XKeyEvent) void {
}
if (has_partial_match) {
- chord.grab_keyboard(display.handle, display.root);
- } else if (!has_partial_match) {
- reset_chord_state(display.handle);
+ wm.chord.grab_keyboard(display.handle, display.root);
+ } else {
+ wm.chord.reset(display.handle);
}
}
-fn execute_action(display: *Display, action: config_mod.Action, int_arg: i32, str_arg: ?[]const u8) void {
+fn execute_action(display: *Display, action: config_mod.Action, int_arg: i32, str_arg: ?[]const u8, wm: *WindowManager) void {
switch (action) {
.spawn_terminal => spawn_terminal(),
.spawn => {
- if (str_arg) |cmd| {
- spawn_command(cmd);
- }
+ if (str_arg) |cmd| spawn_command(cmd);
},
.kill_client => kill_focused(display),
.quit => {
std.debug.print("quit keybind pressed\n", .{});
- running = false;
+ wm.running = false;
},
- .reload_config => reload_config(display),
- .restart => reload_config(display),
+ .reload_config => reload_config(display, wm),
+ .restart => reload_config(display, wm),
.show_keybinds => {
- if (keybind_overlay) |overlay| {
- const mon = monitor_mod.selected_monitor orelse monitor_mod.monitors;
+ if (wm.overlay) |overlay| {
+ const mon = wm.selected_monitor orelse wm.monitors;
if (mon) |m| {
- overlay.toggle(m.mon_x, m.mon_y, m.mon_w, m.mon_h, &config);
+ overlay.toggle(m.mon_x, m.mon_y, m.mon_w, m.mon_h, &wm.config);
}
}
},
@@ -967,16 +757,12 @@ fn execute_action(display: *Display, action: config_mod.Action, int_arg: i32, st
},
.focus_monitor => focusmon(display, int_arg),
.send_to_monitor => sendmon(display, int_arg),
- .scroll_left => {
- scroll_layout(-1);
- },
- .scroll_right => {
- scroll_layout(1);
- },
+ .scroll_left => scroll_layout(-1, wm),
+ .scroll_right => scroll_layout(1, wm),
}
}
-fn reload_config(display: *Display) void {
+fn reload_config(display: *Display, wm: *WindowManager) void {
std.debug.print("reloading config...\n", .{});
ungrab_keybinds(display);
@@ -1005,27 +791,19 @@ fn reload_config(display: *Display) void {
initialize_default_config();
}
- bar_mod.destroy_bars(gpa.allocator(), display.handle);
- setup_bars(gpa.allocator(), display);
- rebuild_bar_blocks();
+ bar_mod.destroy_bars(wm.bars, gpa.allocator(), display.handle);
+ wm.bars = null;
+ wm.setup_bars();
+ rebuild_bar_blocks(wm);
grab_keybinds(display);
}
-fn rebuild_bar_blocks() void {
- var current_bar = bar_mod.bars;
+fn rebuild_bar_blocks(wm: *WindowManager) void {
+ var current_bar = wm.bars;
while (current_bar) |bar| {
bar.clear_blocks();
- if (config.blocks.items.len > 0) {
- for (config.blocks.items) |cfg_block| {
- const block = config_block_to_bar_block(cfg_block);
- bar.add_block(block);
- }
- } else {
- bar.add_block(blocks_mod.Block.init_ram("", 5, 0x7aa2f7, true));
- bar.add_block(blocks_mod.Block.init_static(" | ", 0x666666, false));
- bar.add_block(blocks_mod.Block.init_datetime("", "%H:%M", 1, 0x0db9d7, true));
- }
+ wm.populate_bar_blocks(bar);
current_bar = bar.next;
}
}
@@ -1034,10 +812,6 @@ fn ungrab_keybinds(display: *Display) void {
_ = xlib.XUngrabKey(display.handle, xlib.AnyKey, xlib.AnyModifier, display.root);
}
-/// File descriptor for the X11 connection. Set once after the display is
-/// opened and used only in post-fork child setup to close the inherited fd.
-var x11_fd: c_int = -1;
-
fn spawn_child_setup() void {
_ = std.c.setsid();
if (x11_fd >= 0) std.posix.close(@intCast(x11_fd));
@@ -1173,7 +947,7 @@ fn toggle_view(display: *Display, tag_mask: u32) void {
focus_top_client(display, monitor);
arrange(monitor);
- bar_mod.invalidate_bars();
+ if (wm_ptr) |wm| wm.invalidate_bars();
}
}
@@ -1185,7 +959,7 @@ fn toggle_client_tag(display: *Display, tag_mask: u32) void {
client.tags = new_tags;
focus_top_client(display, monitor);
arrange(monitor);
- bar_mod.invalidate_bars();
+ if (wm_ptr) |wm| wm.invalidate_bars();
}
}
@@ -1309,7 +1083,7 @@ fn view(display: *Display, tag_mask: u32) void {
focus_top_client(display, monitor);
arrange(monitor);
- bar_mod.invalidate_bars();
+ if (wm_ptr) |wm| wm.invalidate_bars();
std.debug.print("view: tag_mask={d}\n", .{monitor.tagset[monitor.sel_tags]});
}
@@ -1365,7 +1139,7 @@ fn tag_client(display: *Display, tag_mask: u32) void {
client.tags = tag_mask;
focus_top_client(display, monitor);
arrange(monitor);
- bar_mod.invalidate_bars();
+ if (wm_ptr) |wm| wm.invalidate_bars();
std.debug.print("tag_client: window=0x{x} tag_mask={d}\n", .{ client.window, tag_mask });
}
@@ -1485,7 +1259,7 @@ fn cycle_layout() void {
monitor.scroll_offset = 0;
}
arrange(monitor);
- bar_mod.invalidate_bars();
+ if (wm_ptr) |wm| wm.invalidate_bars();
if (monitor.lt[monitor.sel_lt]) |layout| {
std.debug.print("cycle_layout: {s}\n", .{layout.symbol});
}
@@ -1516,7 +1290,7 @@ fn set_layout(layout_name: ?[]const u8) void {
monitor.scroll_offset = 0;
}
arrange(monitor);
- bar_mod.invalidate_bars();
+ if (wm_ptr) |wm| wm.invalidate_bars();
if (monitor.lt[monitor.sel_lt]) |layout| {
std.debug.print("set_layout: {s}\n", .{layout.symbol});
}
@@ -1530,7 +1304,7 @@ fn set_layout_index(index: u32) void {
monitor.scroll_offset = 0;
}
arrange(monitor);
- bar_mod.invalidate_bars();
+ if (wm_ptr) |wm| wm.invalidate_bars();
if (monitor.lt[monitor.sel_lt]) |layout| {
std.debug.print("set_layout_index: {s}\n", .{layout.symbol});
}
@@ -1591,7 +1365,7 @@ fn snap_y(client: *Client, new_y: i32, monitor: *Monitor) i32 {
return new_y;
}
-fn movemouse(display: *Display) void {
+fn movemouse(display: *Display, wm: *WindowManager) void {
const monitor = monitor_mod.selected_monitor orelse return;
const client = monitor.sel orelse return;
@@ -1622,7 +1396,7 @@ fn movemouse(display: *Display) void {
xlib.GrabModeAsync,
xlib.GrabModeAsync,
xlib.None,
- cursors.move,
+ wm.cursors.move,
xlib.CurrentTime,
);
@@ -1698,7 +1472,7 @@ fn movemouse(display: *Display) void {
}
}
-fn resizemouse(display: *Display) void {
+fn resizemouse(display: *Display, wm: *WindowManager) void {
const monitor = monitor_mod.selected_monitor orelse return;
const client = monitor.sel orelse return;
@@ -1720,7 +1494,7 @@ fn resizemouse(display: *Display) void {
xlib.GrabModeAsync,
xlib.GrabModeAsync,
xlib.None,
- cursors.resize,
+ wm.cursors.resize,
xlib.CurrentTime,
);
@@ -1771,12 +1545,11 @@ fn resizemouse(display: *Display) void {
arrange(monitor);
}
-fn handle_expose(display: *Display, event: *xlib.XExposeEvent) void {
+fn handle_expose(display: *Display, event: *xlib.XExposeEvent, wm: *WindowManager) void {
if (event.count != 0) return;
-
- if (bar_mod.window_to_bar(event.window)) |bar| {
+ if (bar_mod.window_to_bar(wm.bars, event.window)) |bar| {
bar.invalidate();
- bar.draw(display.handle, &config.tags, config);
+ bar.draw(display.handle, wm.config);
}
}
@@ -1792,7 +1565,7 @@ fn clean_mask(mask: c_uint) c_uint {
return mask & ~(lock | numlock_mask) & (shift | ctrl | mod1 | mod2 | mod3 | mod4 | mod5);
}
-fn handle_button_press(display: *Display, event: *xlib.XButtonEvent) void {
+fn handle_button_press(display: *Display, event: *xlib.XButtonEvent, wm: *WindowManager) void {
std.debug.print("button_press: window=0x{x} subwindow=0x{x}\n", .{ event.window, event.subwindow });
const clicked_monitor = monitor_mod.window_to_monitor(display.handle, display.root, event.window);
@@ -1806,8 +1579,8 @@ fn handle_button_press(display: *Display, event: *xlib.XButtonEvent) void {
}
}
- if (bar_mod.window_to_bar(event.window)) |bar| {
- const clicked_tag = bar.handle_click(event.x, &config.tags);
+ if (bar_mod.window_to_bar(wm.bars, event.window)) |bar| {
+ const clicked_tag = bar.handle_click(display.handle, event.x, wm.config);
if (clicked_tag) |tag_index| {
const tag_mask: u32 = @as(u32, 1) << @intCast(tag_index);
view(display, tag_mask);
@@ -1830,8 +1603,8 @@ fn handle_button_press(display: *Display, event: *xlib.XButtonEvent) void {
const button_clean_mask = clean_mask(button.mod_mask);
if (clean_state == button_clean_mask and event.button == button.button) {
switch (button.action) {
- .move_mouse => movemouse(display),
- .resize_mouse => resizemouse(display),
+ .move_mouse => movemouse(display, wm),
+ .resize_mouse => resizemouse(display, wm),
.toggle_floating => {
if (click_client) |found_client| {
found_client.is_floating = !found_client.is_floating;
@@ -1933,10 +1706,11 @@ fn unmanage(display: *Display, client: *Client) void {
client_mod.destroy(gpa.allocator(), client);
update_client_list(display);
- bar_mod.invalidate_bars();
+ if (wm_ptr) |wm| wm.invalidate_bars();
}
-fn handle_enter_notify(display: *Display, event: *xlib.XCrossingEvent) void {
+fn handle_enter_notify(display: *Display, event: *xlib.XCrossingEvent, wm: *WindowManager) void {
+ _ = wm;
if ((event.mode != xlib.NotifyNormal or event.detail == xlib.NotifyInferior) and event.window != display.root) {
return;
}
@@ -1977,22 +1751,18 @@ fn set_focus(display: *Display, client: *Client) void {
_ = send_event(display, client, atoms.wm_take_focus);
}
-var last_motion_monitor: ?*Monitor = null;
-
-fn handle_motion_notify(display: *Display, event: *xlib.XMotionEvent) void {
- if (event.window != display.root) {
- return;
- }
+fn handle_motion_notify(display: *Display, event: *xlib.XMotionEvent, wm: *WindowManager) void {
+ if (event.window != display.root) return;
const target_mon = monitor_mod.rect_to_monitor(event.x_root, event.y_root, 1, 1);
- if (target_mon != last_motion_monitor and last_motion_monitor != null) {
+ if (target_mon != wm.last_motion_monitor and wm.last_motion_monitor != null) {
if (monitor_mod.selected_monitor) |selmon| {
unfocus_client(display, selmon.sel, true);
}
monitor_mod.selected_monitor = target_mon;
focus(display, null);
}
- last_motion_monitor = target_mon;
+ wm.last_motion_monitor = target_mon;
}
fn handle_property_notify(display: *Display, event: *xlib.XPropertyEvent) void {
@@ -2016,7 +1786,7 @@ fn handle_property_notify(display: *Display, event: *xlib.XPropertyEvent) void {
client.hints_valid = false;
} else if (event.atom == xlib.XA_WM_HINTS) {
update_wm_hints(display, client);
- bar_mod.invalidate_bars();
+ if (wm_ptr) |wm| wm.invalidate_bars();
} else if (event.atom == xlib.XA_WM_NAME or event.atom == atoms.net_wm_name) {
update_title(display, client);
} else if (event.atom == atoms.net_wm_window_type) {
@@ -2140,15 +1910,15 @@ fn focus(display: *Display, target_client: ?*Client) void {
if (focus_client) |client| {
if (is_scrolling_layout(current_selmon)) {
- scroll_to_window(client, true);
+ scroll_to_window(client, true, wm_ptr orelse return);
}
}
- bar_mod.invalidate_bars();
+ if (wm_ptr) |wm| wm.invalidate_bars();
}
fn restack(display: *Display, monitor: *Monitor) void {
- bar_mod.invalidate_bars();
+ if (wm_ptr) |wm| wm.invalidate_bars();
const selected_client = monitor.sel orelse return;
if (selected_client.is_floating or monitor.lt[monitor.sel_lt] == null) {
@@ -2177,7 +1947,7 @@ fn restack(display: *Display, monitor: *Monitor) void {
}
fn arrange(monitor: *Monitor) void {
- // TODO: display will become a WindowManager field; for now
+ // TODO(wm-refactor): display will become a WindowManager field; for now
// we use a module-level pointer set once after the display is opened.
if (wm_display) |display| {
showhide(display, monitor);
@@ -2192,11 +1962,11 @@ fn arrange(monitor: *Monitor) void {
}
}
-fn tick_animations() void {
- if (!scroll_animation.is_active()) return;
+fn tick_animations(wm: *WindowManager) void {
+ if (!wm.scroll_animation.is_active()) return;
- const monitor = monitor_mod.selected_monitor orelse return;
- if (scroll_animation.update()) |new_offset| {
+ const monitor = wm.selected_monitor orelse return;
+ if (wm.scroll_animation.update()) |new_offset| {
monitor.scroll_offset = new_offset;
arrange(monitor);
}
@@ -2209,32 +1979,32 @@ fn is_scrolling_layout(monitor: *Monitor) bool {
return false;
}
-fn scroll_layout(direction: i32) void {
- const monitor = monitor_mod.selected_monitor orelse return;
+fn scroll_layout(direction: i32, wm: *WindowManager) void {
+ const monitor = wm.selected_monitor orelse return;
if (!is_scrolling_layout(monitor)) return;
const scroll_step = scrolling.get_scroll_step(monitor);
const max_scroll = scrolling.get_max_scroll(monitor);
- const current = if (scroll_animation.is_active())
- scroll_animation.target()
+ const current = if (wm.scroll_animation.is_active())
+ wm.scroll_animation.target()
else
monitor.scroll_offset;
var target = current + direction * scroll_step;
target = @max(0, @min(target, max_scroll));
- scroll_animation.start(monitor.scroll_offset, target, animation_config);
+ wm.scroll_animation.start(monitor.scroll_offset, target, wm.animation_config);
}
-fn scroll_to_window(client: *Client, animate: bool) void {
+fn scroll_to_window(client: *Client, animate: bool, wm: *WindowManager) void {
const monitor = client.monitor orelse return;
if (!is_scrolling_layout(monitor)) return;
const target = scrolling.get_target_scroll_for_window(monitor, client);
if (animate) {
- scroll_animation.start(monitor.scroll_offset, target, animation_config);
+ wm.scroll_animation.start(monitor.scroll_offset, target, wm.animation_config);
} else {
monitor.scroll_offset = target;
arrange(monitor);
diff --git a/src/monitor.zig b/src/monitor.zig
index 173e1e7..4cfae13 100644
--- a/src/monitor.zig
+++ b/src/monitor.zig
@@ -7,6 +7,7 @@ pub const Layout = struct {
arrange_fn: ?*const fn (*Monitor) void,
};
+// TODO: Make clearer, document? refactor?
pub const Pertag = struct {
curtag: u32 = 1,
prevtag: u32 = 1,
@@ -17,6 +18,7 @@ pub const Pertag = struct {
showbars: [10]bool = [_]bool{true} ** 10,
};
+// TODO: Make clearer, document? refactor?
pub const Monitor = struct {
lt_symbol: [16]u8 = std.mem.zeroes([16]u8),
mfact: f32 = 0.55,
@@ -46,58 +48,61 @@ pub const Monitor = struct {
stack: ?*Client = null,
next: ?*Monitor = null,
bar_win: xlib.Window = 0,
- lt: [5]?*const Layout = .{ null, null, null, null, null },
- pertag: Pertag = Pertag{},
+ lt: [5]?*const Layout = .{null} ** 5,
+ pertag: Pertag = .{},
};
+// NOTE: `monitors` and `selected_monitor` will soon be removed, they will
+// move to `WindowManager` fields in a future refactor step. All new
+// code should prefer receiving a `*Monitor` or `?*Monitor` as a parameter
+// rather than reaching into these globals directly.
+
pub var monitors: ?*Monitor = null;
pub var selected_monitor: ?*Monitor = null;
var allocator: std.mem.Allocator = undefined;
+/// Must be called once before any other function in this module.
pub fn init(alloc: std.mem.Allocator) void {
allocator = alloc;
}
+/// Allocates and zero-initialises a new `Monitor`.
pub fn create() ?*Monitor {
const mon = allocator.create(Monitor) catch return null;
mon.* = Monitor{};
return mon;
}
+/// Frees a monitor previously returned by `create`.
pub fn destroy(mon: *Monitor) void {
allocator.destroy(mon);
}
-var root_window: xlib.Window = 0;
-var display_handle: ?*xlib.Display = null;
-
-pub fn set_root_window(root: xlib.Window, display: *xlib.Display) void {
- root_window = root;
- display_handle = display;
-}
-
-pub fn window_to_monitor(win: xlib.Window) ?*Monitor {
- if (win == root_window and display_handle != null) {
+/// Returns the monitor whose bar window or client matches `win`, falling
+/// back to a pointer-position query when `win` is the root window.
+///
+/// `display` and `root` are passed explicitly rather than cached in module
+/// state, this keeps ownership clear and avoids a stale-pointer hazard.
+pub fn window_to_monitor(display: *xlib.Display, root: xlib.Window, win: xlib.Window) ?*Monitor {
+ if (win == root) {
var root_x: c_int = undefined;
var root_y: c_int = undefined;
var dummy_win: xlib.Window = undefined;
var dummy_int: c_int = undefined;
var dummy_uint: c_uint = undefined;
- if (xlib.XQueryPointer(display_handle.?, root_window, &dummy_win, &dummy_win, &root_x, &root_y, &dummy_int, &dummy_int, &dummy_uint) != 0) {
+ if (xlib.XQueryPointer(display, root, &dummy_win, &dummy_win, &root_x, &root_y, &dummy_int, &dummy_int, &dummy_uint) != 0) {
return rect_to_monitor(root_x, root_y, 1, 1);
}
}
var current = monitors;
while (current) |monitor| {
- if (monitor.bar_win == win) {
- return monitor;
- }
+ if (monitor.bar_win == win) return monitor;
current = monitor.next;
}
- const client = @import("client.zig").window_to_client(win);
+ const client = @import("client.zig").window_to_client(monitors, win);
if (client) |found_client| {
return found_client.monitor;
}
@@ -105,6 +110,8 @@ pub fn window_to_monitor(win: xlib.Window) ?*Monitor {
return selected_monitor;
}
+/// Returns the monitor with the greatest intersection area with the given
+/// rectangle, or `selected_monitor` if no intersection is found.
pub fn rect_to_monitor(x: i32, y: i32, width: i32, height: i32) ?*Monitor {
var result = selected_monitor;
var max_area: i32 = 0;
@@ -123,6 +130,14 @@ pub fn rect_to_monitor(x: i32, y: i32, width: i32, height: i32) ?*Monitor {
return result;
}
+/// Returns the next or previous monitor relative to `selected_monitor`.
+///
+/// Positive `direction` moves forward through the linked list (wrapping to
+/// the head); negative moves backward (wrapping to the tail).
+///
+// TODO:
+// - Change direction to an enum/enum_literal
+// - Rename function
pub fn dir_to_monitor(direction: i32) ?*Monitor {
var target: ?*Monitor = null;
@@ -132,6 +147,7 @@ pub fn dir_to_monitor(direction: i32) ?*Monitor {
target = monitors;
}
} else if (selected_monitor == monitors) {
+ // Already at head, walk to tail.
var last = monitors;
while (last) |iter| {
if (iter.next == null) {
@@ -141,6 +157,7 @@ pub fn dir_to_monitor(direction: i32) ?*Monitor {
last = iter.next;
}
} else {
+ // Walk until we find the node just before selected_monitor.
var previous = monitors;
while (previous) |iter| {
if (iter.next == selected_monitor) {
diff --git a/src/wm.zig b/src/wm.zig
new file mode 100644
index 0000000..c5d4c4a
--- /dev/null
+++ b/src/wm.zig
@@ -0,0 +1,366 @@
+const std = @import("std");
+const mem = std.mem;
+
+const display_mod = @import("x11/display.zig");
+const xlib = @import("x11/xlib.zig");
+const events = @import("x11/events.zig");
+const atoms_mod = @import("x11/atoms.zig");
+const chord_mod = @import("keyboard/chord.zig");
+const client_mod = @import("client.zig");
+const monitor_mod = @import("monitor.zig");
+const bar_mod = @import("bar/bar.zig");
+const blocks_mod = @import("bar/blocks/blocks.zig");
+const config_mod = @import("config/config.zig");
+const overlay_mod = @import("overlay.zig");
+const animations = @import("animations.zig");
+const tiling = @import("layouts/tiling.zig");
+const monocle = @import("layouts/monocle.zig");
+const floating = @import("layouts/floating.zig");
+const scrolling = @import("layouts/scrolling.zig");
+const grid = @import("layouts/grid.zig");
+
+const Display = display_mod.Display;
+const Atoms = atoms_mod.Atoms;
+const ChordState = chord_mod.ChordState;
+const Client = client_mod.Client;
+const Monitor = monitor_mod.Monitor;
+const Bar = bar_mod.Bar;
+const Config = config_mod.Config;
+
+pub const Cursors = struct {
+ normal: xlib.Cursor,
+ resize: xlib.Cursor,
+ move: xlib.Cursor,
+
+ pub fn init(display: *Display) Cursors {
+ return .{
+ .normal = xlib.XCreateFontCursor(display.handle, xlib.XC_left_ptr),
+ .resize = xlib.XCreateFontCursor(display.handle, xlib.XC_sizing),
+ .move = xlib.XCreateFontCursor(display.handle, xlib.XC_fleur),
+ };
+ }
+};
+
+pub const WindowManager = struct {
+ allocator: mem.Allocator,
+
+ /// The connection to the X server.
+ display: Display,
+ x11_fd: c_int,
+ /// Invisible 1×1 window used to satisfy the _NET_SUPPORTING_WM_CHECK
+ /// EWMH convention.
+ wm_check_window: xlib.Window,
+
+ atoms: Atoms,
+ cursors: Cursors,
+ numlock_mask: c_uint,
+
+ config: Config,
+ /// Path to the config file that was loaded.
+ /// Null if using default config.
+ config_path: ?[]const u8,
+
+ /// Head of the linked list of all managed monitors.
+ monitors: ?*Monitor,
+ /// The monitor that currently has input focus.
+ selected_monitor: ?*Monitor,
+
+ /// Head of the linked list of status bars (one per monitor).
+ bars: ?*Bar,
+
+ chord: ChordState,
+
+ overlay: ?*overlay_mod.Keybind_Overlay,
+
+ scroll_animation: animations.Scroll_Animation,
+ animation_config: animations.Animation_Config,
+
+ running: bool,
+ last_motion_monitor: ?*Monitor,
+
+ /// Initialises the window manager
+ ///
+ /// Returns an error if the display cannot be opened or another WM is
+ /// already running.
+ pub fn init(allocator: mem.Allocator, config: Config, config_path: ?[]const u8) !WindowManager {
+ var display = try Display.open();
+ errdefer display.close();
+
+ try display.become_window_manager();
+
+ const x11_fd = xlib.XConnectionNumber(display.handle);
+
+ const atoms_result = Atoms.init(display.handle, display.root);
+ const cursors = Cursors.init(&display);
+ _ = xlib.XDefineCursor(display.handle, display.root, cursors.normal);
+
+ tiling.set_display(display.handle);
+ tiling.set_screen_size(display.screen_width(), display.screen_height());
+
+ monitor_mod.init(allocator);
+ // client_mod.init(allocator);
+
+ var wm = WindowManager{
+ .allocator = allocator,
+ .display = display,
+ .x11_fd = x11_fd,
+ .wm_check_window = atoms_result.check_window,
+ .atoms = atoms_result.atoms,
+ .cursors = cursors,
+ .numlock_mask = 0,
+ .config = config,
+ .config_path = config_path,
+ .monitors = null,
+ .selected_monitor = null,
+ .bars = null,
+ .chord = .{},
+ .overlay = null,
+ .scroll_animation = .{},
+ .animation_config = .{ .duration_ms = 150, .easing = .ease_out },
+ .running = true,
+ .last_motion_monitor = null,
+ };
+
+ wm.setup_monitors();
+ wm.setup_bars();
+ wm.setup_overlay();
+
+ return wm;
+ }
+
+ /// Release all allocated memory owned by the WM.
+ pub fn deinit(self: *WindowManager) void {
+ bar_mod.destroy_bars(self.bars, self.allocator, self.display.handle);
+ self.bars = null;
+
+ if (self.overlay) |o| {
+ o.deinit(self.allocator);
+ self.overlay = null;
+ }
+
+ var mon = self.monitors;
+ while (mon) |m| {
+ const next = m.next;
+ monitor_mod.destroy(m);
+ mon = next;
+ }
+ self.monitors = null;
+ self.selected_monitor = null;
+
+ _ = xlib.XDestroyWindow(self.display.handle, self.wm_check_window);
+
+ self.display.close();
+ self.config.deinit();
+ }
+
+ fn setup_monitors(self: *WindowManager) void {
+ if (xlib.XineramaIsActive(self.display.handle) != 0) {
+ var screen_count: c_int = 0;
+ const screens = xlib.XineramaQueryScreens(self.display.handle, &screen_count);
+
+ if (screen_count > 0 and screens != null) {
+ var prev_monitor: ?*Monitor = null;
+ var index: usize = 0;
+
+ while (index < @as(usize, @intCast(screen_count))) : (index += 1) {
+ const screen = screens[index];
+ const mon = monitor_mod.create() orelse continue;
+
+ mon.num = @intCast(index);
+ mon.mon_x = screen.x_org;
+ mon.mon_y = screen.y_org;
+ mon.mon_w = screen.width;
+ mon.mon_h = screen.height;
+ mon.win_x = screen.x_org;
+ mon.win_y = screen.y_org;
+ mon.win_w = screen.width;
+ mon.win_h = screen.height;
+ mon.lt[0] = &tiling.layout;
+ mon.lt[1] = &monocle.layout;
+ mon.lt[2] = &floating.layout;
+ mon.lt[3] = &scrolling.layout;
+ mon.lt[4] = &grid.layout;
+
+ for (0..10) |i| {
+ mon.pertag.ltidxs[i][0] = mon.lt[0];
+ mon.pertag.ltidxs[i][1] = mon.lt[1];
+ mon.pertag.ltidxs[i][2] = mon.lt[2];
+ mon.pertag.ltidxs[i][3] = mon.lt[3];
+ mon.pertag.ltidxs[i][4] = mon.lt[4];
+ }
+
+ self.init_monitor_gaps(mon);
+
+ if (prev_monitor) |prev| {
+ prev.next = mon;
+ } else {
+ self.monitors = mon;
+ self.selected_monitor = mon;
+ }
+ prev_monitor = mon;
+ }
+
+ _ = xlib.XFree(@ptrCast(screens));
+ }
+ }
+
+ // Fallback: single monitor covering the full screen.
+ if (self.monitors == null) {
+ const mon = monitor_mod.create() orelse return;
+ mon.num = 0;
+ mon.mon_x = 0;
+ mon.mon_y = 0;
+ mon.mon_w = self.display.screen_width();
+ mon.mon_h = self.display.screen_height();
+ mon.win_x = 0;
+ mon.win_y = 0;
+ mon.win_w = mon.mon_w;
+ mon.win_h = mon.mon_h;
+ mon.lt[0] = &tiling.layout;
+ mon.lt[1] = &monocle.layout;
+ mon.lt[2] = &floating.layout;
+ mon.lt[3] = &scrolling.layout;
+ mon.lt[4] = &grid.layout;
+
+ for (0..10) |i| {
+ mon.pertag.ltidxs[i][0] = mon.lt[0];
+ mon.pertag.ltidxs[i][1] = mon.lt[1];
+ mon.pertag.ltidxs[i][2] = mon.lt[2];
+ mon.pertag.ltidxs[i][3] = mon.lt[3];
+ mon.pertag.ltidxs[i][4] = mon.lt[4];
+ }
+
+ self.init_monitor_gaps(mon);
+ self.monitors = mon;
+ self.selected_monitor = mon;
+ }
+
+ // Mirror into monitor_mod so legacy code that still reads the
+ // module-level vars continues to work during the transition.
+ // TODO(wm-refactor): remove once all callers use self.monitors.
+ monitor_mod.monitors = self.monitors;
+ monitor_mod.selected_monitor = self.selected_monitor;
+ }
+
+ fn init_monitor_gaps(self: *WindowManager, mon: *Monitor) void {
+ const cfg = &self.config;
+ const any_gap_nonzero = cfg.gap_inner_h != 0 or cfg.gap_inner_v != 0 or
+ cfg.gap_outer_h != 0 or cfg.gap_outer_v != 0;
+
+ if (cfg.gaps_enabled and any_gap_nonzero) {
+ mon.gap_inner_h = cfg.gap_inner_h;
+ mon.gap_inner_v = cfg.gap_inner_v;
+ mon.gap_outer_h = cfg.gap_outer_h;
+ mon.gap_outer_v = cfg.gap_outer_v;
+ } else {
+ mon.gap_inner_h = 0;
+ mon.gap_inner_v = 0;
+ mon.gap_outer_h = 0;
+ mon.gap_outer_v = 0;
+ }
+ }
+
+ pub fn setup_bars(self: *WindowManager) void {
+ var current_monitor = self.monitors;
+ var last_bar: ?*Bar = null;
+
+ while (current_monitor) |monitor| {
+ const bar = Bar.create(
+ self.allocator,
+ self.display.handle,
+ self.display.screen,
+ monitor,
+ self.config,
+ ) orelse {
+ current_monitor = monitor.next;
+ continue;
+ };
+
+ if (tiling.bar_height == 0) {
+ tiling.set_bar_height(bar.height);
+ }
+
+ self.populate_bar_blocks(bar);
+
+ if (last_bar) |prev| {
+ prev.next = bar;
+ } else {
+ self.bars = bar;
+ }
+ last_bar = bar;
+
+ std.debug.print("bar created for monitor {d}\n", .{monitor.num});
+ current_monitor = monitor.next;
+ }
+ }
+
+ pub fn populate_bar_blocks(self: *WindowManager, bar: *Bar) void {
+ if (self.config.blocks.items.len > 0) {
+ for (self.config.blocks.items) |cfg_block| {
+ bar.add_block(config_block_to_bar_block(cfg_block));
+ }
+ } else {
+ bar.add_block(blocks_mod.Block.init_ram("", 5, 0x7aa2f7, true));
+ bar.add_block(blocks_mod.Block.init_static(" | ", 0x666666, false));
+ bar.add_block(blocks_mod.Block.init_datetime("", "%H:%M", 1, 0x0db9d7, true));
+ }
+ }
+
+ fn setup_overlay(self: *WindowManager) void {
+ self.overlay = overlay_mod.Keybind_Overlay.init(
+ self.display.handle,
+ self.display.screen,
+ self.display.root,
+ self.config.font,
+ self.allocator,
+ );
+ }
+
+ // Wrap free functions in `bar_mod` with `self.bars` passed in.
+
+ pub fn invalidate_bars(self: *WindowManager) void {
+ bar_mod.invalidate_bars(self.bars);
+ }
+
+ pub fn window_to_bar(self: *WindowManager, win: xlib.Window) ?*Bar {
+ return bar_mod.window_to_bar(self.bars, win);
+ }
+};
+
+/// Converts a config block description into a live status bar block.
+pub fn config_block_to_bar_block(cfg: config_mod.Block) blocks_mod.Block {
+ return switch (cfg.block_type) {
+ .static => blocks_mod.Block.init_static(cfg.format, cfg.color, cfg.underline),
+ .datetime => blocks_mod.Block.init_datetime(
+ cfg.format,
+ cfg.datetime_format orelse "%H:%M",
+ cfg.interval,
+ cfg.color,
+ cfg.underline,
+ ),
+ .ram => blocks_mod.Block.init_ram(cfg.format, cfg.interval, cfg.color, cfg.underline),
+ .shell => blocks_mod.Block.init_shell(
+ cfg.format,
+ cfg.command orelse "",
+ cfg.interval,
+ cfg.color,
+ cfg.underline,
+ ),
+ .battery => blocks_mod.Block.init_battery(
+ cfg.format_charging orelse "",
+ cfg.format_discharging orelse "",
+ cfg.format_full orelse "",
+ cfg.battery_name orelse "BAT0",
+ cfg.interval,
+ cfg.color,
+ cfg.underline,
+ ),
+ .cpu_temp => blocks_mod.Block.init_cpu_temp(
+ cfg.format,
+ cfg.thermal_zone orelse "thermal_zone0",
+ cfg.interval,
+ cfg.color,
+ cfg.underline,
+ ),
+ };
+}