oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
12,321 bytes raw
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
}