| 1 |
const std = @import("std");
|
| 2 |
const mem = std.mem;
|
| 3 |
|
| 4 |
const display_mod = @import("x11/display.zig");
|
| 5 |
const xlib = @import("x11/xlib.zig");
|
| 6 |
const events = @import("x11/events.zig");
|
| 7 |
const atoms_mod = @import("x11/atoms.zig");
|
| 8 |
const chord_mod = @import("keyboard/chord.zig");
|
| 9 |
const client_mod = @import("client.zig");
|
| 10 |
const monitor_mod = @import("monitor.zig");
|
| 11 |
const bar_mod = @import("bar/bar.zig");
|
| 12 |
const blocks_mod = @import("bar/blocks/blocks.zig");
|
| 13 |
const config_mod = @import("config/config.zig");
|
| 14 |
const overlay_mod = @import("overlay.zig");
|
| 15 |
const animations = @import("animations.zig");
|
| 16 |
const tiling = @import("layouts/tiling.zig");
|
| 17 |
const monocle = @import("layouts/monocle.zig");
|
| 18 |
const floating = @import("layouts/floating.zig");
|
| 19 |
const scrolling = @import("layouts/scrolling.zig");
|
| 20 |
const grid = @import("layouts/grid.zig");
|
| 21 |
|
| 22 |
const Display = display_mod.Display;
|
| 23 |
const Atoms = atoms_mod.Atoms;
|
| 24 |
const ChordState = chord_mod.ChordState;
|
| 25 |
const Client = client_mod.Client;
|
| 26 |
const Monitor = monitor_mod.Monitor;
|
| 27 |
const Bar = bar_mod.Bar;
|
| 28 |
const Config = config_mod.Config;
|
| 29 |
|
| 30 |
pub const Cursors = struct {
|
| 31 |
normal: xlib.Cursor,
|
| 32 |
resize: xlib.Cursor,
|
| 33 |
move: xlib.Cursor,
|
| 34 |
|
| 35 |
pub fn init(display: *Display) Cursors {
|
| 36 |
return .{
|
| 37 |
.normal = xlib.XCreateFontCursor(display.handle, xlib.XC_left_ptr),
|
| 38 |
.resize = xlib.XCreateFontCursor(display.handle, xlib.XC_sizing),
|
| 39 |
.move = xlib.XCreateFontCursor(display.handle, xlib.XC_fleur),
|
| 40 |
};
|
| 41 |
}
|
| 42 |
};
|
| 43 |
|
| 44 |
pub const WindowManager = struct {
|
| 45 |
allocator: mem.Allocator,
|
| 46 |
|
| 47 |
/// The connection to the X server.
|
| 48 |
display: Display,
|
| 49 |
x11_fd: c_int,
|
| 50 |
/// Invisible 1×1 window used to satisfy the _NET_SUPPORTING_WM_CHECK
|
| 51 |
/// EWMH convention.
|
| 52 |
wm_check_window: xlib.Window,
|
| 53 |
|
| 54 |
atoms: Atoms,
|
| 55 |
cursors: Cursors,
|
| 56 |
numlock_mask: c_uint,
|
| 57 |
|
| 58 |
config: Config,
|
| 59 |
/// Path to the config file that was loaded.
|
| 60 |
/// Null if using default config.
|
| 61 |
config_path: ?[]const u8,
|
| 62 |
|
| 63 |
/// Head of the linked list of all managed monitors.
|
| 64 |
monitors: ?*Monitor,
|
| 65 |
/// The monitor that currently has input focus.
|
| 66 |
selected_monitor: ?*Monitor,
|
| 67 |
|
| 68 |
/// Head of the linked list of status bars (one per monitor).
|
| 69 |
bars: ?*Bar,
|
| 70 |
|
| 71 |
chord: ChordState,
|
| 72 |
|
| 73 |
overlay: ?*overlay_mod.Keybind_Overlay,
|
| 74 |
|
| 75 |
scroll_animation: animations.Scroll_Animation,
|
| 76 |
animation_config: animations.Animation_Config,
|
| 77 |
|
| 78 |
running: bool,
|
| 79 |
last_motion_monitor: ?*Monitor,
|
| 80 |
|
| 81 |
/// Initialises the window manager
|
| 82 |
///
|
| 83 |
/// Returns an error if the display cannot be opened or another WM is
|
| 84 |
/// already running.
|
| 85 |
pub fn init(allocator: mem.Allocator, config: Config, config_path: ?[]const u8) !WindowManager {
|
| 86 |
var display = try Display.open();
|
| 87 |
errdefer display.close();
|
| 88 |
|
| 89 |
try display.become_window_manager();
|
| 90 |
|
| 91 |
const x11_fd = xlib.XConnectionNumber(display.handle);
|
| 92 |
|
| 93 |
const atoms_result = Atoms.init(display.handle, display.root);
|
| 94 |
const cursors = Cursors.init(&display);
|
| 95 |
_ = xlib.XDefineCursor(display.handle, display.root, cursors.normal);
|
| 96 |
|
| 97 |
tiling.set_display(display.handle);
|
| 98 |
tiling.set_screen_size(display.screen_width(), display.screen_height());
|
| 99 |
|
| 100 |
monitor_mod.init(allocator);
|
| 101 |
// client_mod.init(allocator);
|
| 102 |
|
| 103 |
var wm = WindowManager{
|
| 104 |
.allocator = allocator,
|
| 105 |
.display = display,
|
| 106 |
.x11_fd = x11_fd,
|
| 107 |
.wm_check_window = atoms_result.check_window,
|
| 108 |
.atoms = atoms_result.atoms,
|
| 109 |
.cursors = cursors,
|
| 110 |
.numlock_mask = 0,
|
| 111 |
.config = config,
|
| 112 |
.config_path = config_path,
|
| 113 |
.monitors = null,
|
| 114 |
.selected_monitor = null,
|
| 115 |
.bars = null,
|
| 116 |
.chord = .{},
|
| 117 |
.overlay = null,
|
| 118 |
.scroll_animation = .{},
|
| 119 |
.animation_config = .{ .duration_ms = 150, .easing = .ease_out },
|
| 120 |
.running = true,
|
| 121 |
.last_motion_monitor = null,
|
| 122 |
};
|
| 123 |
|
| 124 |
wm.setup_monitors();
|
| 125 |
wm.setup_bars();
|
| 126 |
wm.setup_overlay();
|
| 127 |
|
| 128 |
return wm;
|
| 129 |
}
|
| 130 |
|
| 131 |
/// Release all allocated memory owned by the WM.
|
| 132 |
pub fn deinit(self: *WindowManager) void {
|
| 133 |
bar_mod.destroy_bars(self.bars, self.allocator, self.display.handle);
|
| 134 |
self.bars = null;
|
| 135 |
|
| 136 |
if (self.overlay) |o| {
|
| 137 |
o.deinit(self.allocator);
|
| 138 |
self.overlay = null;
|
| 139 |
}
|
| 140 |
|
| 141 |
var mon = self.monitors;
|
| 142 |
while (mon) |m| {
|
| 143 |
const next = m.next;
|
| 144 |
monitor_mod.destroy(m);
|
| 145 |
mon = next;
|
| 146 |
}
|
| 147 |
self.monitors = null;
|
| 148 |
self.selected_monitor = null;
|
| 149 |
|
| 150 |
_ = xlib.XDestroyWindow(self.display.handle, self.wm_check_window);
|
| 151 |
|
| 152 |
self.display.close();
|
| 153 |
self.config.deinit();
|
| 154 |
}
|
| 155 |
|
| 156 |
fn setup_monitors(self: *WindowManager) void {
|
| 157 |
if (xlib.XineramaIsActive(self.display.handle) != 0) {
|
| 158 |
var screen_count: c_int = 0;
|
| 159 |
const screens = xlib.XineramaQueryScreens(self.display.handle, &screen_count);
|
| 160 |
|
| 161 |
if (screen_count > 0 and screens != null) {
|
| 162 |
var prev_monitor: ?*Monitor = null;
|
| 163 |
var index: usize = 0;
|
| 164 |
|
| 165 |
while (index < @as(usize, @intCast(screen_count))) : (index += 1) {
|
| 166 |
const screen = screens[index];
|
| 167 |
const mon = monitor_mod.create() orelse continue;
|
| 168 |
|
| 169 |
mon.num = @intCast(index);
|
| 170 |
mon.mon_x = screen.x_org;
|
| 171 |
mon.mon_y = screen.y_org;
|
| 172 |
mon.mon_w = screen.width;
|
| 173 |
mon.mon_h = screen.height;
|
| 174 |
mon.win_x = screen.x_org;
|
| 175 |
mon.win_y = screen.y_org;
|
| 176 |
mon.win_w = screen.width;
|
| 177 |
mon.win_h = screen.height;
|
| 178 |
mon.lt[0] = &tiling.layout;
|
| 179 |
mon.lt[1] = &monocle.layout;
|
| 180 |
mon.lt[2] = &floating.layout;
|
| 181 |
mon.lt[3] = &scrolling.layout;
|
| 182 |
mon.lt[4] = &grid.layout;
|
| 183 |
|
| 184 |
for (0..10) |i| {
|
| 185 |
mon.pertag.ltidxs[i][0] = mon.lt[0];
|
| 186 |
mon.pertag.ltidxs[i][1] = mon.lt[1];
|
| 187 |
mon.pertag.ltidxs[i][2] = mon.lt[2];
|
| 188 |
mon.pertag.ltidxs[i][3] = mon.lt[3];
|
| 189 |
mon.pertag.ltidxs[i][4] = mon.lt[4];
|
| 190 |
}
|
| 191 |
|
| 192 |
self.init_monitor_gaps(mon);
|
| 193 |
|
| 194 |
if (prev_monitor) |prev| {
|
| 195 |
prev.next = mon;
|
| 196 |
} else {
|
| 197 |
self.monitors = mon;
|
| 198 |
self.selected_monitor = mon;
|
| 199 |
}
|
| 200 |
prev_monitor = mon;
|
| 201 |
}
|
| 202 |
|
| 203 |
_ = xlib.XFree(@ptrCast(screens));
|
| 204 |
}
|
| 205 |
}
|
| 206 |
|
| 207 |
// Fallback: single monitor covering the full screen.
|
| 208 |
if (self.monitors == null) {
|
| 209 |
const mon = monitor_mod.create() orelse return;
|
| 210 |
mon.num = 0;
|
| 211 |
mon.mon_x = 0;
|
| 212 |
mon.mon_y = 0;
|
| 213 |
mon.mon_w = self.display.screen_width();
|
| 214 |
mon.mon_h = self.display.screen_height();
|
| 215 |
mon.win_x = 0;
|
| 216 |
mon.win_y = 0;
|
| 217 |
mon.win_w = mon.mon_w;
|
| 218 |
mon.win_h = mon.mon_h;
|
| 219 |
mon.lt[0] = &tiling.layout;
|
| 220 |
mon.lt[1] = &monocle.layout;
|
| 221 |
mon.lt[2] = &floating.layout;
|
| 222 |
mon.lt[3] = &scrolling.layout;
|
| 223 |
mon.lt[4] = &grid.layout;
|
| 224 |
|
| 225 |
for (0..10) |i| {
|
| 226 |
mon.pertag.ltidxs[i][0] = mon.lt[0];
|
| 227 |
mon.pertag.ltidxs[i][1] = mon.lt[1];
|
| 228 |
mon.pertag.ltidxs[i][2] = mon.lt[2];
|
| 229 |
mon.pertag.ltidxs[i][3] = mon.lt[3];
|
| 230 |
mon.pertag.ltidxs[i][4] = mon.lt[4];
|
| 231 |
}
|
| 232 |
|
| 233 |
self.init_monitor_gaps(mon);
|
| 234 |
self.monitors = mon;
|
| 235 |
self.selected_monitor = mon;
|
| 236 |
}
|
| 237 |
|
| 238 |
// Mirror into monitor_mod so legacy code that still reads the
|
| 239 |
// module-level vars continues to work during the transition.
|
| 240 |
// TODO(wm-refactor): remove once all callers use self.monitors.
|
| 241 |
monitor_mod.monitors = self.monitors;
|
| 242 |
monitor_mod.selected_monitor = self.selected_monitor;
|
| 243 |
}
|
| 244 |
|
| 245 |
fn init_monitor_gaps(self: *WindowManager, mon: *Monitor) void {
|
| 246 |
const cfg = &self.config;
|
| 247 |
const any_gap_nonzero = cfg.gap_inner_h != 0 or cfg.gap_inner_v != 0 or
|
| 248 |
cfg.gap_outer_h != 0 or cfg.gap_outer_v != 0;
|
| 249 |
|
| 250 |
if (cfg.gaps_enabled and any_gap_nonzero) {
|
| 251 |
mon.gap_inner_h = cfg.gap_inner_h;
|
| 252 |
mon.gap_inner_v = cfg.gap_inner_v;
|
| 253 |
mon.gap_outer_h = cfg.gap_outer_h;
|
| 254 |
mon.gap_outer_v = cfg.gap_outer_v;
|
| 255 |
} else {
|
| 256 |
mon.gap_inner_h = 0;
|
| 257 |
mon.gap_inner_v = 0;
|
| 258 |
mon.gap_outer_h = 0;
|
| 259 |
mon.gap_outer_v = 0;
|
| 260 |
}
|
| 261 |
}
|
| 262 |
|
| 263 |
pub fn setup_bars(self: *WindowManager) void {
|
| 264 |
var current_monitor = self.monitors;
|
| 265 |
var last_bar: ?*Bar = null;
|
| 266 |
|
| 267 |
while (current_monitor) |monitor| {
|
| 268 |
const bar = Bar.create(
|
| 269 |
self.allocator,
|
| 270 |
self.display.handle,
|
| 271 |
self.display.screen,
|
| 272 |
monitor,
|
| 273 |
self.config,
|
| 274 |
) orelse {
|
| 275 |
current_monitor = monitor.next;
|
| 276 |
continue;
|
| 277 |
};
|
| 278 |
|
| 279 |
if (tiling.bar_height == 0) {
|
| 280 |
tiling.set_bar_height(bar.height);
|
| 281 |
}
|
| 282 |
|
| 283 |
self.populate_bar_blocks(bar);
|
| 284 |
|
| 285 |
if (last_bar) |prev| {
|
| 286 |
prev.next = bar;
|
| 287 |
} else {
|
| 288 |
self.bars = bar;
|
| 289 |
}
|
| 290 |
last_bar = bar;
|
| 291 |
|
| 292 |
std.debug.print("bar created for monitor {d}\n", .{monitor.num});
|
| 293 |
current_monitor = monitor.next;
|
| 294 |
}
|
| 295 |
}
|
| 296 |
|
| 297 |
pub fn populate_bar_blocks(self: *WindowManager, bar: *Bar) void {
|
| 298 |
if (self.config.blocks.items.len > 0) {
|
| 299 |
for (self.config.blocks.items) |cfg_block| {
|
| 300 |
bar.add_block(config_block_to_bar_block(cfg_block));
|
| 301 |
}
|
| 302 |
} else {
|
| 303 |
bar.add_block(blocks_mod.Block.init_ram("", 5, 0x7aa2f7, true));
|
| 304 |
bar.add_block(blocks_mod.Block.init_static(" | ", 0x666666, false));
|
| 305 |
bar.add_block(blocks_mod.Block.init_datetime("", "%H:%M", 1, 0x0db9d7, true));
|
| 306 |
}
|
| 307 |
}
|
| 308 |
|
| 309 |
fn setup_overlay(self: *WindowManager) void {
|
| 310 |
self.overlay = overlay_mod.Keybind_Overlay.init(
|
| 311 |
self.display.handle,
|
| 312 |
self.display.screen,
|
| 313 |
self.display.root,
|
| 314 |
self.config.font,
|
| 315 |
self.allocator,
|
| 316 |
);
|
| 317 |
}
|
| 318 |
|
| 319 |
// Wrap free functions in `bar_mod` with `self.bars` passed in.
|
| 320 |
|
| 321 |
pub fn invalidate_bars(self: *WindowManager) void {
|
| 322 |
bar_mod.invalidate_bars(self.bars);
|
| 323 |
}
|
| 324 |
|
| 325 |
pub fn window_to_bar(self: *WindowManager, win: xlib.Window) ?*Bar {
|
| 326 |
return bar_mod.window_to_bar(self.bars, win);
|
| 327 |
}
|
| 328 |
};
|
| 329 |
|
| 330 |
/// Converts a config block description into a live status bar block.
|
| 331 |
pub fn config_block_to_bar_block(cfg: config_mod.Block) blocks_mod.Block {
|
| 332 |
return switch (cfg.block_type) {
|
| 333 |
.static => blocks_mod.Block.init_static(cfg.format, cfg.color, cfg.underline),
|
| 334 |
.datetime => blocks_mod.Block.init_datetime(
|
| 335 |
cfg.format,
|
| 336 |
cfg.datetime_format orelse "%H:%M",
|
| 337 |
cfg.interval,
|
| 338 |
cfg.color,
|
| 339 |
cfg.underline,
|
| 340 |
),
|
| 341 |
.ram => blocks_mod.Block.init_ram(cfg.format, cfg.interval, cfg.color, cfg.underline),
|
| 342 |
.shell => blocks_mod.Block.init_shell(
|
| 343 |
cfg.format,
|
| 344 |
cfg.command orelse "",
|
| 345 |
cfg.interval,
|
| 346 |
cfg.color,
|
| 347 |
cfg.underline,
|
| 348 |
),
|
| 349 |
.battery => blocks_mod.Block.init_battery(
|
| 350 |
cfg.format_charging orelse "",
|
| 351 |
cfg.format_discharging orelse "",
|
| 352 |
cfg.format_full orelse "",
|
| 353 |
cfg.battery_name orelse "BAT0",
|
| 354 |
cfg.interval,
|
| 355 |
cfg.color,
|
| 356 |
cfg.underline,
|
| 357 |
),
|
| 358 |
.cpu_temp => blocks_mod.Block.init_cpu_temp(
|
| 359 |
cfg.format,
|
| 360 |
cfg.thermal_zone orelse "thermal_zone0",
|
| 361 |
cfg.interval,
|
| 362 |
cfg.color,
|
| 363 |
cfg.underline,
|
| 364 |
),
|
| 365 |
};
|
| 366 |
}
|