oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
90,636 bytes raw
1
const std = @import("std");
2
const display_mod = @import("x11/display.zig");
3
const events = @import("x11/events.zig");
4
const xlib = @import("x11/xlib.zig");
5
const client_mod = @import("client.zig");
6
const monitor_mod = @import("monitor.zig");
7
const tiling = @import("layouts/tiling.zig");
8
const monocle = @import("layouts/monocle.zig");
9
const floating = @import("layouts/floating.zig");
10
const scrolling = @import("layouts/scrolling.zig");
11
const animations = @import("animations.zig");
12
const bar_mod = @import("bar/bar.zig");
13
const blocks_mod = @import("bar/blocks/blocks.zig");
14
const config_mod = @import("config/config.zig");
15
const lua = @import("config/lua.zig");
16
const overlay_mod = @import("overlay.zig");
17
18
const Display = display_mod.Display;
19
const Client = client_mod.Client;
20
const Monitor = monitor_mod.Monitor;
21
22
var running: bool = true;
23
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
24
25
var wm_protocols: xlib.Atom = 0;
26
var wm_delete: xlib.Atom = 0;
27
var wm_state: xlib.Atom = 0;
28
var wm_take_focus: xlib.Atom = 0;
29
30
var net_supported: xlib.Atom = 0;
31
var net_wm_name: xlib.Atom = 0;
32
var net_wm_state: xlib.Atom = 0;
33
var net_wm_check: xlib.Atom = 0;
34
var net_wm_state_fullscreen: xlib.Atom = 0;
35
var net_active_window: xlib.Atom = 0;
36
var net_wm_window_type: xlib.Atom = 0;
37
var net_wm_window_type_dialog: xlib.Atom = 0;
38
var net_client_list: xlib.Atom = 0;
39
40
var wm_check_window: xlib.Window = 0;
41
42
var border_color_focused: c_ulong = 0x6dade3;
43
var border_color_unfocused: c_ulong = 0x444444;
44
var border_width: i32 = 2;
45
var gap_outer_v: i32 = 5;
46
var gap_outer_h: i32 = 5;
47
var gap_inner_h: i32 = 5;
48
var gap_inner_v: i32 = 5;
49
50
var tags: [9][]const u8 = .{ "1", "2", "3", "4", "5", "6", "7", "8", "9" };
51
52
var cursor_normal: xlib.Cursor = 0;
53
var cursor_resize: xlib.Cursor = 0;
54
var cursor_move: xlib.Cursor = 0;
55
56
const NormalState: c_long = 1;
57
const WithdrawnState: c_long = 0;
58
const IconicState: c_long = 3;
59
const IsViewable: c_int = 2;
60
const snap_distance: i32 = 32;
61
62
var numlock_mask: c_uint = 0;
63
64
var config: config_mod.Config = undefined;
65
var display_global: ?*Display = null;
66
var config_path_global: ?[]const u8 = null;
67
68
var scroll_animation: animations.Scroll_Animation = .{};
69
var animation_config: animations.Animation_Config = .{ .duration_ms = 150, .easing = .ease_out };
70
71
var chord_keys: [4]config_mod.Key_Press = [_]config_mod.Key_Press{.{}} ** 4;
72
var chord_index: u8 = 0;
73
var chord_timestamp: i64 = 0;
74
const chord_timeout_ms: i64 = 1000;
75
var keyboard_grabbed: bool = false;
76
77
var keybind_overlay: ?*overlay_mod.Keybind_Overlay = null;
78
79
fn print_help() void {
80
    std.debug.print(
81
        \\oxwm - A window manager
82
        \\
83
        \\USAGE:
84
        \\    oxwm [OPTIONS]
85
        \\
86
        \\OPTIONS:
87
        \\    --init              Create default config in ~/.config/oxwm/config.lua
88
        \\    --config <PATH>     Use custom config file
89
        \\    --version           Print version information
90
        \\    --help              Print this help message
91
        \\
92
        \\CONFIG:
93
        \\    Location: ~/.config/oxwm/config.lua
94
        \\    Edit the config file and use Mod+Shift+R to reload
95
        \\    No compilation needed - instant hot-reload!
96
        \\
97
        \\FIRST RUN:
98
        \\    Run 'oxwm --init' to create a config file
99
        \\    Or just start oxwm and it will create one automatically
100
        \\
101
    , .{});
102
}
103
104
fn init_config(allocator: std.mem.Allocator) void {
105
    const home = std.posix.getenv("HOME") orelse {
106
        std.debug.print("error: HOME environment variable not set\n", .{});
107
        return;
108
    };
109
110
    var path_buf: [512]u8 = undefined;
111
    const config_dir = std.fmt.bufPrint(&path_buf, "{s}/.config/oxwm", .{home}) catch {
112
        std.debug.print("error: path too long\n", .{});
113
        return;
114
    };
115
116
    std.fs.cwd().makePath(config_dir) catch |err| {
117
        std.debug.print("error: could not create config directory: {}\n", .{err});
118
        return;
119
    };
120
121
    var config_path_buf: [512]u8 = undefined;
122
    const config_path = std.fmt.bufPrint(&config_path_buf, "{s}/config.lua", .{config_dir}) catch {
123
        std.debug.print("error: path too long\n", .{});
124
        return;
125
    };
126
127
    const template = std.fs.cwd().readFileAlloc(allocator, "templates/config.lua", 64 * 1024) catch |err| {
128
        std.debug.print("error: could not read template (templates/config.lua): {}\n", .{err});
129
        std.debug.print("hint: run from the oxwm source directory, or copy templates/config.lua manually\n", .{});
130
        return;
131
    };
132
    defer allocator.free(template);
133
134
    const file = std.fs.createFileAbsolute(config_path, .{}) catch |err| {
135
        std.debug.print("error: could not create config file: {}\n", .{err});
136
        return;
137
    };
138
    defer file.close();
139
140
    _ = file.writeAll(template) catch |err| {
141
        std.debug.print("error: could not write config file: {}\n", .{err});
142
        return;
143
    };
144
145
    std.debug.print("Config created at {s}\n", .{config_path});
146
    std.debug.print("Edit the file and reload with Mod+Shift+R\n", .{});
147
    std.debug.print("No compilation needed - changes take effect immediately!\n", .{});
148
}
149
150
pub fn main() !void {
151
    const allocator = gpa.allocator();
152
    defer _ = gpa.deinit();
153
154
    std.debug.print("oxwm starting\n", .{});
155
156
    var config_path: ?[]const u8 = null;
157
    var args = std.process.args();
158
    _ = args.skip();
159
    while (args.next()) |arg| {
160
        if (std.mem.eql(u8, arg, "-c") or std.mem.eql(u8, arg, "--config")) {
161
            config_path = args.next();
162
        } else if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) {
163
            print_help();
164
            return;
165
        } else if (std.mem.eql(u8, arg, "-v") or std.mem.eql(u8, arg, "--version")) {
166
            std.debug.print("oxwm 0.1.0\n", .{});
167
            return;
168
        } else if (std.mem.eql(u8, arg, "--init")) {
169
            init_config(allocator);
170
            return;
171
        }
172
    }
173
174
    config = config_mod.Config.init(allocator);
175
    defer config.deinit();
176
    config_mod.set_config(&config);
177
178
    if (lua.init(&config)) {
179
        const loaded = if (config_path) |path|
180
            lua.load_file(path)
181
        else
182
            lua.load_config();
183
184
        if (loaded) {
185
            config_path_global = config_path;
186
            if (config_path) |path| {
187
                std.debug.print("loaded config from {s}\n", .{path});
188
            } else {
189
                std.debug.print("loaded config from ~/.config/oxwm/config.lua\n", .{});
190
            }
191
            apply_config_values();
192
        } else {
193
            std.debug.print("no config found, using defaults\n", .{});
194
            initialize_default_config();
195
        }
196
    } else {
197
        std.debug.print("failed to init lua, using defaults\n", .{});
198
        initialize_default_config();
199
    }
200
201
    var display = Display.open() catch |err| {
202
        std.debug.print("failed to open display: {}\n", .{err});
203
        return;
204
    };
205
    defer display.close();
206
207
    display_global = &display;
208
209
    std.debug.print("display opened: screen={d} root=0x{x}\n", .{ display.screen, display.root });
210
    std.debug.print("screen size: {d}x{d}\n", .{ display.screen_width(), display.screen_height() });
211
212
    display.become_window_manager() catch |err| {
213
        std.debug.print("failed to become window manager: {}\n", .{err});
214
        return;
215
    };
216
217
    std.debug.print("successfully became window manager\n", .{});
218
219
    setup_atoms(&display);
220
    setup_cursors(&display);
221
    client_mod.init(allocator);
222
    monitor_mod.init(allocator);
223
    monitor_mod.set_root_window(display.root, display.handle);
224
    tiling.set_display(display.handle);
225
    tiling.set_screen_size(display.screen_width(), display.screen_height());
226
227
    setup_monitors(&display);
228
    setup_bars(allocator, &display);
229
    setup_overlay(allocator, &display);
230
    grab_keybinds(&display);
231
    scan_existing_windows(&display);
232
233
    std.debug.print("entering event loop\n", .{});
234
    run_event_loop(&display);
235
236
    lua.deinit();
237
    std.debug.print("oxwm exiting\n", .{});
238
}
239
240
fn setup_atoms(display: *Display) void {
241
    wm_protocols = xlib.XInternAtom(display.handle, "WM_PROTOCOLS", xlib.False);
242
    wm_delete = xlib.XInternAtom(display.handle, "WM_DELETE_WINDOW", xlib.False);
243
    wm_state = xlib.XInternAtom(display.handle, "WM_STATE", xlib.False);
244
    wm_take_focus = xlib.XInternAtom(display.handle, "WM_TAKE_FOCUS", xlib.False);
245
246
    net_active_window = xlib.XInternAtom(display.handle, "_NET_ACTIVE_WINDOW", xlib.False);
247
    net_supported = xlib.XInternAtom(display.handle, "_NET_SUPPORTED", xlib.False);
248
    net_wm_name = xlib.XInternAtom(display.handle, "_NET_WM_NAME", xlib.False);
249
    net_wm_state = xlib.XInternAtom(display.handle, "_NET_WM_STATE", xlib.False);
250
    net_wm_check = xlib.XInternAtom(display.handle, "_NET_SUPPORTING_WM_CHECK", xlib.False);
251
    net_wm_state_fullscreen = xlib.XInternAtom(display.handle, "_NET_WM_STATE_FULLSCREEN", xlib.False);
252
    net_wm_window_type = xlib.XInternAtom(display.handle, "_NET_WM_WINDOW_TYPE", xlib.False);
253
    net_wm_window_type_dialog = xlib.XInternAtom(display.handle, "_NET_WM_WINDOW_TYPE_DIALOG", xlib.False);
254
    net_client_list = xlib.XInternAtom(display.handle, "_NET_CLIENT_LIST", xlib.False);
255
256
    const utf8_string = xlib.XInternAtom(display.handle, "UTF8_STRING", xlib.False);
257
258
    wm_check_window = xlib.XCreateSimpleWindow(display.handle, display.root, 0, 0, 1, 1, 0, 0, 0);
259
    _ = xlib.XChangeProperty(display.handle, wm_check_window, net_wm_check, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&wm_check_window), 1);
260
    _ = xlib.XChangeProperty(display.handle, wm_check_window, net_wm_name, utf8_string, 8, xlib.PropModeReplace, "oxwm", 6);
261
    _ = xlib.XChangeProperty(display.handle, display.root, net_wm_check, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&wm_check_window), 1);
262
263
    var net_atoms = [_]xlib.Atom{ net_supported, net_wm_name, net_wm_state, net_wm_check, net_wm_state_fullscreen, net_active_window, net_wm_window_type, net_wm_window_type_dialog, net_client_list };
264
    _ = xlib.XChangeProperty(display.handle, display.root, net_supported, xlib.XA_ATOM, 32, xlib.PropModeReplace, @ptrCast(&net_atoms), net_atoms.len);
265
266
    _ = xlib.XDeleteProperty(display.handle, display.root, net_client_list);
267
268
    std.debug.print("atoms initialized with EWMH support\n", .{});
269
}
270
271
fn setup_cursors(display: *Display) void {
272
    cursor_normal = xlib.XCreateFontCursor(display.handle, xlib.XC_left_ptr);
273
    cursor_resize = xlib.XCreateFontCursor(display.handle, xlib.XC_sizing);
274
    cursor_move = xlib.XCreateFontCursor(display.handle, xlib.XC_fleur);
275
    _ = xlib.XDefineCursor(display.handle, display.root, cursor_normal);
276
}
277
278
fn setup_bars(allocator: std.mem.Allocator, display: *Display) void {
279
    var current_monitor = monitor_mod.monitors;
280
    var last_bar: ?*bar_mod.Bar = null;
281
282
    while (current_monitor) |monitor| {
283
        const bar = bar_mod.Bar.create(allocator, display.handle, display.screen, monitor, config.font);
284
        if (bar) |created_bar| {
285
            if (tiling.bar_height == 0) {
286
                tiling.set_bar_height(created_bar.height);
287
            }
288
289
            if (config.blocks.items.len > 0) {
290
                for (config.blocks.items) |cfg_block| {
291
                    const block = config_block_to_bar_block(cfg_block);
292
                    created_bar.add_block(block);
293
                }
294
            } else {
295
                created_bar.add_block(blocks_mod.Block.init_ram("", 5, 0x7aa2f7, true));
296
                created_bar.add_block(blocks_mod.Block.init_static(" | ", 0x666666, false));
297
                created_bar.add_block(blocks_mod.Block.init_datetime("", "%H:%M", 1, 0x0db9d7, true));
298
            }
299
300
            if (last_bar) |prev| {
301
                prev.next = created_bar;
302
            } else {
303
                bar_mod.bars = created_bar;
304
            }
305
            last_bar = created_bar;
306
            std.debug.print("bar created for monitor {d}\n", .{monitor.num});
307
        }
308
        current_monitor = monitor.next;
309
    }
310
}
311
312
fn setup_overlay(allocator: std.mem.Allocator, display: *Display) void {
313
    keybind_overlay = overlay_mod.Keybind_Overlay.init(display.handle, display.screen, display.root, config.font, allocator);
314
}
315
316
fn config_block_to_bar_block(cfg: config_mod.Block) blocks_mod.Block {
317
    return switch (cfg.block_type) {
318
        .static => blocks_mod.Block.init_static(cfg.format, cfg.color, cfg.underline),
319
        .datetime => blocks_mod.Block.init_datetime(cfg.format, cfg.datetime_format orelse "%H:%M", cfg.interval, cfg.color, cfg.underline),
320
        .ram => blocks_mod.Block.init_ram(cfg.format, cfg.interval, cfg.color, cfg.underline),
321
        .shell => blocks_mod.Block.init_shell(cfg.format, cfg.command orelse "", cfg.interval, cfg.color, cfg.underline),
322
        .battery => blocks_mod.Block.init_battery(
323
            cfg.format_charging orelse "",
324
            cfg.format_discharging orelse "",
325
            cfg.format_full orelse "",
326
            cfg.battery_name orelse "BAT0",
327
            cfg.interval,
328
            cfg.color,
329
            cfg.underline,
330
        ),
331
        .cpu_temp => blocks_mod.Block.init_cpu_temp(
332
            cfg.format,
333
            cfg.thermal_zone orelse "thermal_zone0",
334
            cfg.interval,
335
            cfg.color,
336
            cfg.underline,
337
        ),
338
    };
339
}
340
341
fn setup_monitors(display: *Display) void {
342
    std.debug.print("checking xinerama...\n", .{});
343
    if (xlib.XineramaIsActive(display.handle) != 0) {
344
        std.debug.print("xinerama is active!\n", .{});
345
        var screen_count: c_int = 0;
346
        const screens = xlib.XineramaQueryScreens(display.handle, &screen_count);
347
348
        if (screen_count > 0 and screens != null) {
349
            var prev_monitor: ?*Monitor = null;
350
            var index: usize = 0;
351
352
            while (index < @as(usize, @intCast(screen_count))) : (index += 1) {
353
                const screen = screens[index];
354
                const mon = monitor_mod.create() orelse continue;
355
356
                mon.num = @intCast(index);
357
                mon.mon_x = screen.x_org;
358
                mon.mon_y = screen.y_org;
359
                mon.mon_w = screen.width;
360
                mon.mon_h = screen.height;
361
                mon.win_x = screen.x_org;
362
                mon.win_y = screen.y_org;
363
                mon.win_w = screen.width;
364
                mon.win_h = screen.height;
365
                mon.lt[0] = &tiling.layout;
366
                mon.lt[1] = &monocle.layout;
367
                mon.lt[2] = &floating.layout;
368
                mon.lt[3] = &scrolling.layout;
369
                for (0..10) |i| {
370
                    mon.pertag.ltidxs[i][0] = mon.lt[0];
371
                    mon.pertag.ltidxs[i][1] = mon.lt[1];
372
                    mon.pertag.ltidxs[i][2] = mon.lt[2];
373
                    mon.pertag.ltidxs[i][3] = mon.lt[3];
374
                }
375
376
                if (prev_monitor) |prev| {
377
                    prev.next = mon;
378
                } else {
379
                    monitor_mod.monitors = mon;
380
                }
381
                prev_monitor = mon;
382
383
                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 });
384
            }
385
386
            monitor_mod.selected_monitor = monitor_mod.monitors;
387
            _ = xlib.XFree(@ptrCast(screens));
388
            return;
389
        }
390
    } else {
391
        std.debug.print("xinerama not active, using single monitor\n", .{});
392
    }
393
394
    const mon = monitor_mod.create() orelse return;
395
    mon.mon_x = 0;
396
    mon.mon_y = 0;
397
    mon.mon_w = display.screen_width();
398
    mon.mon_h = display.screen_height();
399
    mon.win_x = 0;
400
    mon.win_y = 0;
401
    mon.win_w = display.screen_width();
402
    mon.win_h = display.screen_height();
403
    mon.lt[0] = &tiling.layout;
404
    mon.lt[1] = &monocle.layout;
405
    mon.lt[2] = &floating.layout;
406
    mon.lt[3] = &scrolling.layout;
407
    for (0..10) |i| {
408
        mon.pertag.ltidxs[i][0] = mon.lt[0];
409
        mon.pertag.ltidxs[i][1] = mon.lt[1];
410
        mon.pertag.ltidxs[i][2] = mon.lt[2];
411
        mon.pertag.ltidxs[i][3] = mon.lt[3];
412
    }
413
    monitor_mod.monitors = mon;
414
    monitor_mod.selected_monitor = mon;
415
    std.debug.print("monitor created: {d}x{d}\n", .{ mon.mon_w, mon.mon_h });
416
}
417
418
fn apply_config_values() void {
419
    border_color_focused = config.border_focused;
420
    border_color_unfocused = config.border_unfocused;
421
    border_width = config.border_width;
422
    gap_inner_h = config.gap_inner_h;
423
    gap_inner_v = config.gap_inner_v;
424
    gap_outer_h = config.gap_outer_h;
425
    gap_outer_v = config.gap_outer_v;
426
    tags = config.tags;
427
}
428
429
fn make_keybind(mod: u32, key: u64, action: config_mod.Action) config_mod.Keybind {
430
    var kb: config_mod.Keybind = .{ .action = action };
431
    kb.keys[0] = .{ .mod_mask = mod, .keysym = key };
432
    kb.key_count = 1;
433
    return kb;
434
}
435
436
fn make_keybind_int(mod: u32, key: u64, action: config_mod.Action, int_arg: i32) config_mod.Keybind {
437
    var kb = make_keybind(mod, key, action);
438
    kb.int_arg = int_arg;
439
    return kb;
440
}
441
442
fn make_keybind_str(mod: u32, key: u64, action: config_mod.Action, str_arg: []const u8) config_mod.Keybind {
443
    var kb = make_keybind(mod, key, action);
444
    kb.str_arg = str_arg;
445
    return kb;
446
}
447
448
fn initialize_default_config() void {
449
    const mod_key: u32 = 1 << 6;
450
    const shift_key: u32 = 1 << 0;
451
    const control_key: u32 = 1 << 2;
452
453
    config.add_keybind(make_keybind(mod_key, 0xff0d, .spawn_terminal)) catch {};
454
    config.add_keybind(make_keybind_str(mod_key, 'd', .spawn, "rofi -show drun")) catch {};
455
    config.add_keybind(make_keybind_str(mod_key, 's', .spawn, "maim -s | xclip -selection clipboard -t image/png")) catch {};
456
    config.add_keybind(make_keybind(mod_key, 'q', .kill_client)) catch {};
457
    config.add_keybind(make_keybind(mod_key | shift_key, 'q', .quit)) catch {};
458
    config.add_keybind(make_keybind(mod_key | shift_key, 'r', .reload_config)) catch {};
459
    config.add_keybind(make_keybind(mod_key, 'j', .focus_next)) catch {};
460
    config.add_keybind(make_keybind(mod_key, 'k', .focus_prev)) catch {};
461
    config.add_keybind(make_keybind(mod_key | shift_key, 'j', .move_next)) catch {};
462
    config.add_keybind(make_keybind(mod_key | shift_key, 'k', .move_prev)) catch {};
463
    config.add_keybind(make_keybind_int(mod_key, 'h', .resize_master, -50)) catch {};
464
    config.add_keybind(make_keybind_int(mod_key, 'l', .resize_master, 50)) catch {};
465
    config.add_keybind(make_keybind(mod_key, 'i', .inc_master)) catch {};
466
    config.add_keybind(make_keybind(mod_key, 'p', .dec_master)) catch {};
467
    config.add_keybind(make_keybind(mod_key, 'a', .toggle_gaps)) catch {};
468
    config.add_keybind(make_keybind(mod_key, 'f', .toggle_fullscreen)) catch {};
469
    config.add_keybind(make_keybind(mod_key, 0x0020, .toggle_floating)) catch {};
470
    config.add_keybind(make_keybind(mod_key, 'n', .cycle_layout)) catch {};
471
    config.add_keybind(make_keybind_int(mod_key, 0x002c, .focus_monitor, -1)) catch {};
472
    config.add_keybind(make_keybind_int(mod_key, 0x002e, .focus_monitor, 1)) catch {};
473
    config.add_keybind(make_keybind_int(mod_key | shift_key, 0x002c, .send_to_monitor, -1)) catch {};
474
    config.add_keybind(make_keybind_int(mod_key | shift_key, 0x002e, .send_to_monitor, 1)) catch {};
475
476
    var tag_index: i32 = 0;
477
    while (tag_index < 9) : (tag_index += 1) {
478
        const keysym: u64 = @as(u64, '1') + @as(u64, @intCast(tag_index));
479
        config.add_keybind(make_keybind_int(mod_key, keysym, .view_tag, tag_index)) catch {};
480
        config.add_keybind(make_keybind_int(mod_key | shift_key, keysym, .move_to_tag, tag_index)) catch {};
481
        config.add_keybind(make_keybind_int(mod_key | control_key, keysym, .toggle_view_tag, tag_index)) catch {};
482
        config.add_keybind(make_keybind_int(mod_key | control_key | shift_key, keysym, .toggle_tag, tag_index)) catch {};
483
    }
484
485
    config.add_button(.{ .click = .client_win, .mod_mask = mod_key, .button = 1, .action = .move_mouse }) catch {};
486
    config.add_button(.{ .click = .client_win, .mod_mask = mod_key, .button = 3, .action = .resize_mouse }) catch {};
487
}
488
489
fn grab_keybinds(display: *Display) void {
490
    update_numlock_mask(display);
491
    const modifiers = [_]c_uint{ 0, xlib.LockMask, numlock_mask, numlock_mask | xlib.LockMask };
492
493
    _ = xlib.XUngrabKey(display.handle, xlib.AnyKey, xlib.AnyModifier, display.root);
494
495
    for (config.keybinds.items) |keybind| {
496
        if (keybind.key_count == 0) continue;
497
        const first_key = keybind.keys[0];
498
        const keycode = xlib.XKeysymToKeycode(display.handle, @intCast(first_key.keysym));
499
        if (keycode != 0) {
500
            for (modifiers) |modifier| {
501
                _ = xlib.XGrabKey(
502
                    display.handle,
503
                    keycode,
504
                    first_key.mod_mask | modifier,
505
                    display.root,
506
                    xlib.True,
507
                    xlib.GrabModeAsync,
508
                    xlib.GrabModeAsync,
509
                );
510
            }
511
        }
512
    }
513
514
    for (config.buttons.items) |button| {
515
        if (button.click == .client_win) {
516
            for (modifiers) |modifier| {
517
                _ = xlib.XGrabButton(
518
                    display.handle,
519
                    @intCast(button.button),
520
                    button.mod_mask | modifier,
521
                    display.root,
522
                    xlib.True,
523
                    xlib.ButtonPressMask | xlib.ButtonReleaseMask | xlib.PointerMotionMask,
524
                    xlib.GrabModeAsync,
525
                    xlib.GrabModeAsync,
526
                    xlib.None,
527
                    xlib.None,
528
                );
529
            }
530
        }
531
    }
532
533
    std.debug.print("grabbed {d} keybinds from config\n", .{config.keybinds.items.len});
534
}
535
536
fn get_state(display: *Display, window: xlib.Window) c_long {
537
    var actual_type: xlib.Atom = 0;
538
    var actual_format: c_int = 0;
539
    var num_items: c_ulong = 0;
540
    var bytes_after: c_ulong = 0;
541
    var prop: [*c]u8 = null;
542
543
    const result = xlib.XGetWindowProperty(
544
        display.handle,
545
        window,
546
        wm_state,
547
        0,
548
        2,
549
        xlib.False,
550
        wm_state,
551
        &actual_type,
552
        &actual_format,
553
        &num_items,
554
        &bytes_after,
555
        &prop,
556
    );
557
558
    if (result != 0 or actual_type != wm_state or num_items < 1) {
559
        if (prop != null) {
560
            _ = xlib.XFree(prop);
561
        }
562
        return WithdrawnState;
563
    }
564
565
    const state: c_long = @as(*c_long, @alignCast(@ptrCast(prop))).*;
566
    _ = xlib.XFree(prop);
567
    return state;
568
}
569
570
fn scan_existing_windows(display: *Display) void {
571
    var root_return: xlib.Window = undefined;
572
    var parent_return: xlib.Window = undefined;
573
    var children: [*c]xlib.Window = undefined;
574
    var num_children: c_uint = undefined;
575
576
    if (xlib.XQueryTree(display.handle, display.root, &root_return, &parent_return, &children, &num_children) == 0) {
577
        return;
578
    }
579
580
    var index: c_uint = 0;
581
    while (index < num_children) : (index += 1) {
582
        var window_attrs: xlib.XWindowAttributes = undefined;
583
        if (xlib.XGetWindowAttributes(display.handle, children[index], &window_attrs) == 0) {
584
            continue;
585
        }
586
        if (window_attrs.override_redirect != 0) {
587
            continue;
588
        }
589
        var trans: xlib.Window = 0;
590
        if (xlib.XGetTransientForHint(display.handle, children[index], &trans) != 0) {
591
            continue;
592
        }
593
        if (window_attrs.map_state == IsViewable or get_state(display, children[index]) == IconicState) {
594
            manage(display, children[index], &window_attrs);
595
        }
596
    }
597
598
    index = 0;
599
    while (index < num_children) : (index += 1) {
600
        var window_attrs: xlib.XWindowAttributes = undefined;
601
        if (xlib.XGetWindowAttributes(display.handle, children[index], &window_attrs) == 0) {
602
            continue;
603
        }
604
        var trans: xlib.Window = 0;
605
        if (xlib.XGetTransientForHint(display.handle, children[index], &trans) != 0) {
606
            if (window_attrs.map_state == IsViewable or get_state(display, children[index]) == IconicState) {
607
                manage(display, children[index], &window_attrs);
608
            }
609
        }
610
    }
611
612
    if (children != null) {
613
        _ = xlib.XFree(@ptrCast(children));
614
    }
615
}
616
617
fn run_event_loop(display: *Display) void {
618
    const x11_fd = xlib.XConnectionNumber(display.handle);
619
    var fds = [_]std.posix.pollfd{
620
        .{ .fd = x11_fd, .events = std.posix.POLL.IN, .revents = 0 },
621
    };
622
623
    _ = xlib.XSync(display.handle, xlib.False);
624
625
    while (running) {
626
        while (xlib.XPending(display.handle) > 0) {
627
            var event = display.next_event();
628
            handle_event(display, &event);
629
        }
630
631
        tick_animations();
632
633
        var current_bar = bar_mod.bars;
634
        while (current_bar) |bar| {
635
            bar.update_blocks();
636
            bar.draw(display.handle, &tags);
637
            current_bar = bar.next;
638
        }
639
640
        const poll_timeout: i32 = if (scroll_animation.is_active()) 16 else 1000;
641
        _ = std.posix.poll(&fds, poll_timeout) catch 0;
642
    }
643
}
644
645
fn handle_event(display: *Display, event: *xlib.XEvent) void {
646
    const event_type = events.get_event_type(event);
647
648
    if (event_type == .button_press) {
649
        std.debug.print("EVENT: button_press received type={d}\n", .{event.type});
650
    }
651
652
    switch (event_type) {
653
        .map_request => handle_map_request(display, &event.xmaprequest),
654
        .configure_request => handle_configure_request(display, &event.xconfigurerequest),
655
        .key_press => handle_key_press(display, &event.xkey),
656
        .destroy_notify => handle_destroy_notify(display, &event.xdestroywindow),
657
        .unmap_notify => handle_unmap_notify(display, &event.xunmap),
658
        .enter_notify => handle_enter_notify(display, &event.xcrossing),
659
        .focus_in => handle_focus_in(display, &event.xfocus),
660
        .motion_notify => handle_motion_notify(display, &event.xmotion),
661
        .client_message => handle_client_message(display, &event.xclient),
662
        .button_press => handle_button_press(display, &event.xbutton),
663
        .expose => handle_expose(display, &event.xexpose),
664
        .property_notify => handle_property_notify(display, &event.xproperty),
665
        else => {},
666
    }
667
}
668
669
fn handle_map_request(display: *Display, event: *xlib.XMapRequestEvent) void {
670
    std.debug.print("map_request: window=0x{x}\n", .{event.window});
671
672
    var window_attributes: xlib.XWindowAttributes = undefined;
673
    if (xlib.XGetWindowAttributes(display.handle, event.window, &window_attributes) == 0) {
674
        return;
675
    }
676
    if (window_attributes.override_redirect != 0) {
677
        return;
678
    }
679
    if (client_mod.window_to_client(event.window) != null) {
680
        return;
681
    }
682
683
    manage(display, event.window, &window_attributes);
684
}
685
686
fn manage(display: *Display, win: xlib.Window, window_attrs: *xlib.XWindowAttributes) void {
687
    const client = client_mod.create(win) orelse return;
688
    var trans: xlib.Window = 0;
689
690
    client.x = window_attrs.x;
691
    client.y = window_attrs.y;
692
    client.width = window_attrs.width;
693
    client.height = window_attrs.height;
694
    client.old_x = window_attrs.x;
695
    client.old_y = window_attrs.y;
696
    client.old_width = window_attrs.width;
697
    client.old_height = window_attrs.height;
698
    client.old_border_width = window_attrs.border_width;
699
    client.border_width = border_width;
700
701
    update_title(display, client);
702
703
    if (xlib.XGetTransientForHint(display.handle, win, &trans) != 0) {
704
        if (client_mod.window_to_client(trans)) |transient_client| {
705
            client.monitor = transient_client.monitor;
706
            client.tags = transient_client.tags;
707
        }
708
    }
709
710
    if (client.monitor == null) {
711
        client.monitor = monitor_mod.selected_monitor;
712
        apply_rules(display, client);
713
    }
714
715
    const monitor = client.monitor orelse return;
716
717
    if (client.x + client.width > monitor.win_x + monitor.win_w) {
718
        client.x = monitor.win_x + monitor.win_w - client.width - 2 * client.border_width;
719
    }
720
    if (client.y + client.height > monitor.win_y + monitor.win_h) {
721
        client.y = monitor.win_y + monitor.win_h - client.height - 2 * client.border_width;
722
    }
723
    client.x = @max(client.x, monitor.win_x);
724
    client.y = @max(client.y, monitor.win_y);
725
726
    _ = xlib.XSetWindowBorderWidth(display.handle, win, @intCast(client.border_width));
727
    _ = xlib.XSetWindowBorder(display.handle, win, border_color_unfocused);
728
    tiling.send_configure(client);
729
730
    update_window_type(display, client);
731
    update_size_hints(display, client);
732
    update_wm_hints(display, client);
733
734
    _ = xlib.XSelectInput(
735
        display.handle,
736
        win,
737
        xlib.EnterWindowMask | xlib.FocusChangeMask | xlib.PropertyChangeMask | xlib.StructureNotifyMask,
738
    );
739
    grabbuttons(display, client, false);
740
741
    if (!client.is_floating) {
742
        client.is_floating = trans != 0 or client.is_fixed;
743
        client.old_state = client.is_floating;
744
    }
745
    if (client.is_floating) {
746
        _ = xlib.XRaiseWindow(display.handle, client.window);
747
    }
748
749
    client_mod.attach_aside(client);
750
    client_mod.attach_stack(client);
751
752
    _ = xlib.XChangeProperty(display.handle, display.root, net_client_list, xlib.XA_WINDOW, 32, xlib.PropModeAppend, @ptrCast(&client.window), 1);
753
    _ = xlib.XMoveResizeWindow(display.handle, client.window, client.x + 2 * display.screen_width(), client.y, @intCast(client.width), @intCast(client.height));
754
    set_client_state(display, client, NormalState);
755
756
    if (client.monitor == monitor_mod.selected_monitor) {
757
        const selmon = monitor_mod.selected_monitor orelse return;
758
        unfocus_client(display, selmon.sel, false);
759
    }
760
    monitor.sel = client;
761
762
    if (is_scrolling_layout(monitor)) {
763
        monitor.scroll_offset = 0;
764
    }
765
766
    arrange(monitor);
767
    _ = xlib.XMapWindow(display.handle, win);
768
    focus(display, null);
769
}
770
771
fn handle_configure_request(display: *Display, event: *xlib.XConfigureRequestEvent) void {
772
    const client = client_mod.window_to_client(event.window);
773
774
    if (client) |managed_client| {
775
        if ((event.value_mask & xlib.c.CWBorderWidth) != 0) {
776
            managed_client.border_width = event.border_width;
777
        } else if (managed_client.is_floating or (managed_client.monitor != null and managed_client.monitor.?.lt[managed_client.monitor.?.sel_lt] == null)) {
778
            const monitor = managed_client.monitor orelse return;
779
            if ((event.value_mask & xlib.c.CWX) != 0) {
780
                managed_client.old_x = managed_client.x;
781
                managed_client.x = monitor.mon_x + event.x;
782
            }
783
            if ((event.value_mask & xlib.c.CWY) != 0) {
784
                managed_client.old_y = managed_client.y;
785
                managed_client.y = monitor.mon_y + event.y;
786
            }
787
            if ((event.value_mask & xlib.c.CWWidth) != 0) {
788
                managed_client.old_width = managed_client.width;
789
                managed_client.width = event.width;
790
            }
791
            if ((event.value_mask & xlib.c.CWHeight) != 0) {
792
                managed_client.old_height = managed_client.height;
793
                managed_client.height = event.height;
794
            }
795
            const client_full_width = managed_client.width + managed_client.border_width * 2;
796
            const client_full_height = managed_client.height + managed_client.border_width * 2;
797
            if ((managed_client.x + managed_client.width) > monitor.mon_x + monitor.mon_w and managed_client.is_floating) {
798
                managed_client.x = monitor.mon_x + @divTrunc(monitor.mon_w, 2) - @divTrunc(client_full_width, 2);
799
            }
800
            if ((managed_client.y + managed_client.height) > monitor.mon_y + monitor.mon_h and managed_client.is_floating) {
801
                managed_client.y = monitor.mon_y + @divTrunc(monitor.mon_h, 2) - @divTrunc(client_full_height, 2);
802
            }
803
            if (((event.value_mask & (xlib.c.CWX | xlib.c.CWY)) != 0) and ((event.value_mask & (xlib.c.CWWidth | xlib.c.CWHeight)) == 0)) {
804
                tiling.send_configure(managed_client);
805
            }
806
            if (client_mod.is_visible(managed_client)) {
807
                _ = xlib.XMoveResizeWindow(display.handle, managed_client.window, managed_client.x, managed_client.y, @intCast(managed_client.width), @intCast(managed_client.height));
808
            }
809
        } else {
810
            tiling.send_configure(managed_client);
811
        }
812
    } else {
813
        var changes: xlib.XWindowChanges = undefined;
814
        changes.x = event.x;
815
        changes.y = event.y;
816
        changes.width = event.width;
817
        changes.height = event.height;
818
        changes.border_width = event.border_width;
819
        changes.sibling = event.above;
820
        changes.stack_mode = event.detail;
821
        _ = xlib.XConfigureWindow(display.handle, event.window, @intCast(event.value_mask), &changes);
822
    }
823
    _ = xlib.XSync(display.handle, xlib.False);
824
}
825
826
fn reset_chord_state() void {
827
    chord_index = 0;
828
    chord_keys = [_]config_mod.Key_Press{.{}} ** 4;
829
    chord_timestamp = 0;
830
    if (keyboard_grabbed) {
831
        if (display_global) |dsp| {
832
            _ = xlib.XUngrabKeyboard(dsp.handle, xlib.CurrentTime);
833
        }
834
        keyboard_grabbed = false;
835
    }
836
}
837
838
fn handle_key_press(display: *Display, event: *xlib.XKeyEvent) void {
839
    const keysym = xlib.XKeycodeToKeysym(display.handle, @intCast(event.keycode), 0);
840
841
    if (keybind_overlay) |overlay| {
842
        if (overlay.handle_key(keysym)) {
843
            return;
844
        }
845
    }
846
847
    const clean_state = event.state & ~@as(c_uint, xlib.LockMask | xlib.Mod2Mask);
848
    const current_time = std.time.milliTimestamp();
849
850
    if (chord_index > 0 and (current_time - chord_timestamp) > chord_timeout_ms) {
851
        reset_chord_state();
852
    }
853
854
    chord_keys[chord_index] = .{ .mod_mask = clean_state, .keysym = keysym };
855
    chord_index += 1;
856
    chord_timestamp = current_time;
857
858
    for (config.keybinds.items) |keybind| {
859
        if (keybind.key_count == 0) continue;
860
861
        if (keybind.key_count == chord_index) {
862
            var matches = true;
863
            var i: u8 = 0;
864
            while (i < keybind.key_count) : (i += 1) {
865
                if (chord_keys[i].keysym != keybind.keys[i].keysym or
866
                    chord_keys[i].mod_mask != keybind.keys[i].mod_mask)
867
                {
868
                    matches = false;
869
                    break;
870
                }
871
            }
872
            if (matches) {
873
                execute_action(display, keybind.action, keybind.int_arg, keybind.str_arg);
874
                reset_chord_state();
875
                return;
876
            }
877
        }
878
    }
879
880
    var has_partial_match = false;
881
    for (config.keybinds.items) |keybind| {
882
        if (keybind.key_count > chord_index) {
883
            var matches = true;
884
            var i: u8 = 0;
885
            while (i < chord_index) : (i += 1) {
886
                if (chord_keys[i].keysym != keybind.keys[i].keysym or
887
                    chord_keys[i].mod_mask != keybind.keys[i].mod_mask)
888
                {
889
                    matches = false;
890
                    break;
891
                }
892
            }
893
            if (matches) {
894
                has_partial_match = true;
895
                break;
896
            }
897
        }
898
    }
899
900
    if (has_partial_match and !keyboard_grabbed) {
901
        const grab_result = xlib.XGrabKeyboard(
902
            display.handle,
903
            display.root,
904
            xlib.True,
905
            xlib.GrabModeAsync,
906
            xlib.GrabModeAsync,
907
            xlib.CurrentTime,
908
        );
909
        if (grab_result == xlib.GrabSuccess) {
910
            keyboard_grabbed = true;
911
        }
912
    } else if (!has_partial_match) {
913
        reset_chord_state();
914
    }
915
}
916
917
fn execute_action(display: *Display, action: config_mod.Action, int_arg: i32, str_arg: ?[]const u8) void {
918
    switch (action) {
919
        .spawn_terminal => spawn_terminal(),
920
        .spawn => {
921
            if (str_arg) |cmd| {
922
                spawn_command(cmd);
923
            }
924
        },
925
        .kill_client => kill_focused(display),
926
        .quit => {
927
            std.debug.print("quit keybind pressed\n", .{});
928
            running = false;
929
        },
930
        .reload_config => reload_config(display),
931
        .restart => reload_config(display),
932
        .show_keybinds => {
933
            if (keybind_overlay) |overlay| {
934
                const mon = monitor_mod.selected_monitor orelse monitor_mod.monitors;
935
                if (mon) |m| {
936
                    overlay.toggle(m.mon_x, m.mon_y, m.mon_w, m.mon_h);
937
                }
938
            }
939
        },
940
        .focus_next => focusstack(display, 1),
941
        .focus_prev => focusstack(display, -1),
942
        .move_next => movestack(display, 1),
943
        .move_prev => movestack(display, -1),
944
        .resize_master => setmfact(@as(f32, @floatFromInt(int_arg)) / 1000.0),
945
        .inc_master => incnmaster(1),
946
        .dec_master => incnmaster(-1),
947
        .toggle_floating => toggle_floating(display),
948
        .toggle_fullscreen => toggle_fullscreen(display),
949
        .toggle_gaps => toggle_gaps(),
950
        .cycle_layout => cycle_layout(),
951
        .set_layout => set_layout(str_arg),
952
        .set_layout_tiling => set_layout_index(0),
953
        .set_layout_floating => set_layout_index(2),
954
        .view_tag => {
955
            const tag_mask: u32 = @as(u32, 1) << @intCast(int_arg);
956
            view(display, tag_mask);
957
        },
958
        .view_next_tag => view_adjacent_tag(display, 1),
959
        .view_prev_tag => view_adjacent_tag(display, -1),
960
        .view_next_nonempty_tag => view_adjacent_nonempty_tag(display, 1),
961
        .view_prev_nonempty_tag => view_adjacent_nonempty_tag(display, -1),
962
        .move_to_tag => {
963
            const tag_mask: u32 = @as(u32, 1) << @intCast(int_arg);
964
            tag_client(display, tag_mask);
965
        },
966
        .toggle_view_tag => {
967
            const tag_mask: u32 = @as(u32, 1) << @intCast(int_arg);
968
            toggle_view(display, tag_mask);
969
        },
970
        .toggle_tag => {
971
            const tag_mask: u32 = @as(u32, 1) << @intCast(int_arg);
972
            toggle_client_tag(display, tag_mask);
973
        },
974
        .focus_monitor => focusmon(display, int_arg),
975
        .send_to_monitor => sendmon(display, int_arg),
976
        .scroll_left => {
977
            scroll_layout(-1);
978
        },
979
        .scroll_right => {
980
            scroll_layout(1);
981
        },
982
    }
983
}
984
985
fn reload_config(display: *Display) void {
986
    std.debug.print("reloading config...\n", .{});
987
988
    ungrab_keybinds(display);
989
990
    config.keybinds.clearRetainingCapacity();
991
    config.buttons.clearRetainingCapacity();
992
    config.rules.clearRetainingCapacity();
993
    config.blocks.clearRetainingCapacity();
994
995
    lua.deinit();
996
    _ = lua.init(&config);
997
998
    const loaded = if (config_path_global) |path|
999
        lua.load_file(path)
1000
    else
1001
        lua.load_config();
1002
1003
    if (loaded) {
1004
        if (config_path_global) |path| {
1005
            std.debug.print("reloaded config from {s}\n", .{path});
1006
        } else {
1007
            std.debug.print("reloaded config from ~/.config/oxwm/config.lua\n", .{});
1008
        }
1009
        apply_config_values();
1010
    } else {
1011
        std.debug.print("reload failed, restoring defaults\n", .{});
1012
        initialize_default_config();
1013
    }
1014
1015
    bar_mod.destroy_bars(gpa.allocator(), display.handle);
1016
    setup_bars(gpa.allocator(), display);
1017
    rebuild_bar_blocks();
1018
1019
    grab_keybinds(display);
1020
}
1021
1022
fn rebuild_bar_blocks() void {
1023
    var current_bar = bar_mod.bars;
1024
    while (current_bar) |bar| {
1025
        bar.clear_blocks();
1026
        if (config.blocks.items.len > 0) {
1027
            for (config.blocks.items) |cfg_block| {
1028
                const block = config_block_to_bar_block(cfg_block);
1029
                bar.add_block(block);
1030
            }
1031
        } else {
1032
            bar.add_block(blocks_mod.Block.init_ram("", 5, 0x7aa2f7, true));
1033
            bar.add_block(blocks_mod.Block.init_static(" | ", 0x666666, false));
1034
            bar.add_block(blocks_mod.Block.init_datetime("", "%H:%M", 1, 0x0db9d7, true));
1035
        }
1036
        current_bar = bar.next;
1037
    }
1038
}
1039
1040
fn ungrab_keybinds(display: *Display) void {
1041
    _ = xlib.XUngrabKey(display.handle, xlib.AnyKey, xlib.AnyModifier, display.root);
1042
}
1043
1044
fn spawn_child_setup() void {
1045
    _ = std.c.setsid();
1046
    if (display_global) |display| {
1047
        const display_fd = xlib.XConnectionNumber(display.handle);
1048
        std.posix.close(@intCast(display_fd));
1049
    }
1050
    const sigchld_handler = std.posix.Sigaction{
1051
        .handler = .{ .handler = std.posix.SIG.DFL },
1052
        .mask = std.mem.zeroes(std.posix.sigset_t),
1053
        .flags = 0,
1054
    };
1055
    std.posix.sigaction(std.posix.SIG.CHLD, &sigchld_handler, null);
1056
}
1057
1058
fn spawn_command(cmd: []const u8) void {
1059
    const pid = std.posix.fork() catch return;
1060
    if (pid == 0) {
1061
        spawn_child_setup();
1062
        var cmd_buf: [1024]u8 = undefined;
1063
        if (cmd.len >= cmd_buf.len) {
1064
            std.posix.exit(1);
1065
        }
1066
        @memcpy(cmd_buf[0..cmd.len], cmd);
1067
        cmd_buf[cmd.len] = 0;
1068
        const argv = [_:null]?[*:0]const u8{ "sh", "-c", @ptrCast(&cmd_buf) };
1069
        _ = std.posix.execvpeZ("sh", &argv, std.c.environ) catch {};
1070
        std.posix.exit(1);
1071
    }
1072
}
1073
1074
fn spawn_terminal() void {
1075
    const pid = std.posix.fork() catch return;
1076
    if (pid == 0) {
1077
        spawn_child_setup();
1078
        var term_buf: [256]u8 = undefined;
1079
        const terminal = config.terminal;
1080
        if (terminal.len >= term_buf.len) {
1081
            std.posix.exit(1);
1082
        }
1083
        @memcpy(term_buf[0..terminal.len], terminal);
1084
        term_buf[terminal.len] = 0;
1085
        const term_ptr: [*:0]const u8 = @ptrCast(&term_buf);
1086
        const argv = [_:null]?[*:0]const u8{term_ptr};
1087
        _ = std.posix.execvpeZ(term_ptr, &argv, std.c.environ) catch {};
1088
        std.posix.exit(1);
1089
    }
1090
}
1091
1092
fn movestack(display: *Display, direction: i32) void {
1093
    const monitor = monitor_mod.selected_monitor orelse return;
1094
    const current = monitor.sel orelse return;
1095
1096
    if (current.is_floating) {
1097
        return;
1098
    }
1099
1100
    var target: ?*Client = null;
1101
1102
    if (direction > 0) {
1103
        target = current.next;
1104
        while (target) |client| {
1105
            if (client_mod.is_visible(client) and !client.is_floating) {
1106
                break;
1107
            }
1108
            target = client.next;
1109
        }
1110
        if (target == null) {
1111
            target = monitor.clients;
1112
            while (target) |client| {
1113
                if (client == current) {
1114
                    break;
1115
                }
1116
                if (client_mod.is_visible(client) and !client.is_floating) {
1117
                    break;
1118
                }
1119
                target = client.next;
1120
            }
1121
        }
1122
    } else {
1123
        var prev: ?*Client = null;
1124
        var iter = monitor.clients;
1125
        while (iter) |client| {
1126
            if (client == current) {
1127
                break;
1128
            }
1129
            if (client_mod.is_visible(client) and !client.is_floating) {
1130
                prev = client;
1131
            }
1132
            iter = client.next;
1133
        }
1134
        if (prev == null) {
1135
            iter = current.next;
1136
            while (iter) |client| {
1137
                if (client_mod.is_visible(client) and !client.is_floating) {
1138
                    prev = client;
1139
                }
1140
                iter = client.next;
1141
            }
1142
        }
1143
        target = prev;
1144
    }
1145
1146
    if (target) |swap_client| {
1147
        if (swap_client != current) {
1148
            client_mod.swap_clients(current, swap_client);
1149
            arrange(monitor);
1150
            focus(display, current);
1151
        }
1152
    }
1153
}
1154
1155
fn toggle_view(display: *Display, tag_mask: u32) void {
1156
    const monitor = monitor_mod.selected_monitor orelse return;
1157
    const new_tags = monitor.tagset[monitor.sel_tags] ^ tag_mask;
1158
    if (new_tags != 0) {
1159
        monitor.tagset[monitor.sel_tags] = new_tags;
1160
1161
        if (new_tags == ~@as(u32, 0)) {
1162
            monitor.pertag.prevtag = monitor.pertag.curtag;
1163
            monitor.pertag.curtag = 0;
1164
        }
1165
1166
        if ((new_tags & (@as(u32, 1) << @intCast(monitor.pertag.curtag -| 1))) == 0) {
1167
            monitor.pertag.prevtag = monitor.pertag.curtag;
1168
            var i: u32 = 0;
1169
            while (i < 9) : (i += 1) {
1170
                if ((new_tags & (@as(u32, 1) << @intCast(i))) != 0) break;
1171
            }
1172
            monitor.pertag.curtag = i + 1;
1173
        }
1174
1175
        monitor.nmaster = monitor.pertag.nmasters[monitor.pertag.curtag];
1176
        monitor.mfact = monitor.pertag.mfacts[monitor.pertag.curtag];
1177
        monitor.sel_lt = monitor.pertag.sellts[monitor.pertag.curtag];
1178
1179
        focus_top_client(display, monitor);
1180
        arrange(monitor);
1181
        bar_mod.invalidate_bars();
1182
    }
1183
}
1184
1185
fn toggle_client_tag(display: *Display, tag_mask: u32) void {
1186
    const monitor = monitor_mod.selected_monitor orelse return;
1187
    const client = monitor.sel orelse return;
1188
    const new_tags = client.tags ^ tag_mask;
1189
    if (new_tags != 0) {
1190
        client.tags = new_tags;
1191
        focus_top_client(display, monitor);
1192
        arrange(monitor);
1193
        bar_mod.invalidate_bars();
1194
    }
1195
}
1196
1197
fn toggle_gaps() void {
1198
    const monitor = monitor_mod.selected_monitor orelse return;
1199
    if (monitor.gap_inner_h == 0) {
1200
        monitor.gap_inner_h = gap_inner_v;
1201
        monitor.gap_inner_v = gap_inner_v;
1202
        monitor.gap_outer_h = gap_outer_h;
1203
        monitor.gap_outer_v = gap_outer_v;
1204
    } else {
1205
        monitor.gap_inner_h = 0;
1206
        monitor.gap_inner_v = 0;
1207
        monitor.gap_outer_h = 0;
1208
        monitor.gap_outer_v = 0;
1209
    }
1210
    arrange(monitor);
1211
}
1212
1213
fn kill_focused(display: *Display) void {
1214
    const selected = monitor_mod.selected_monitor orelse return;
1215
    const client = selected.sel orelse return;
1216
    std.debug.print("killing window: 0x{x}\n", .{client.window});
1217
1218
    if (!send_event(display, client, wm_delete)) {
1219
        _ = xlib.XGrabServer(display.handle);
1220
        _ = xlib.XKillClient(display.handle, client.window);
1221
        _ = xlib.XSync(display.handle, xlib.False);
1222
        _ = xlib.XUngrabServer(display.handle);
1223
    }
1224
}
1225
1226
fn toggle_fullscreen(display: *Display) void {
1227
    const selected = monitor_mod.selected_monitor orelse return;
1228
    const client = selected.sel orelse return;
1229
    set_fullscreen(display, client, !client.is_fullscreen);
1230
}
1231
1232
fn set_fullscreen(display: *Display, client: *Client, fullscreen: bool) void {
1233
    const monitor = client.monitor orelse return;
1234
1235
    if (fullscreen and !client.is_fullscreen) {
1236
        var fullscreen_atom = net_wm_state_fullscreen;
1237
        _ = xlib.XChangeProperty(
1238
            display.handle,
1239
            client.window,
1240
            net_wm_state,
1241
            xlib.XA_ATOM,
1242
            32,
1243
            xlib.PropModeReplace,
1244
            @ptrCast(&fullscreen_atom),
1245
            1,
1246
        );
1247
        client.is_fullscreen = true;
1248
        client.old_state = client.is_floating;
1249
        client.old_border_width = client.border_width;
1250
        client.border_width = 0;
1251
        client.is_floating = true;
1252
1253
        _ = xlib.XSetWindowBorderWidth(display.handle, client.window, 0);
1254
        tiling.resize_client(client, monitor.mon_x, monitor.mon_y, monitor.mon_w, monitor.mon_h);
1255
        _ = xlib.XRaiseWindow(display.handle, client.window);
1256
1257
        std.debug.print("fullscreen enabled: window=0x{x}\n", .{client.window});
1258
    } else if (!fullscreen and client.is_fullscreen) {
1259
        var no_atom: xlib.Atom = 0;
1260
        _ = xlib.XChangeProperty(
1261
            display.handle,
1262
            client.window,
1263
            net_wm_state,
1264
            xlib.XA_ATOM,
1265
            32,
1266
            xlib.PropModeReplace,
1267
            @ptrCast(&no_atom),
1268
            0,
1269
        );
1270
        client.is_fullscreen = false;
1271
        client.is_floating = client.old_state;
1272
        client.border_width = client.old_border_width;
1273
1274
        client.x = client.old_x;
1275
        client.y = client.old_y;
1276
        client.width = client.old_width;
1277
        client.height = client.old_height;
1278
1279
        tiling.resize_client(client, client.x, client.y, client.width, client.height);
1280
        arrange(monitor);
1281
1282
        std.debug.print("fullscreen disabled: window=0x{x}\n", .{client.window});
1283
    }
1284
}
1285
1286
fn view(display: *Display, tag_mask: u32) void {
1287
    const monitor = monitor_mod.selected_monitor orelse return;
1288
    if (tag_mask == monitor.tagset[monitor.sel_tags]) {
1289
        return;
1290
    }
1291
    monitor.sel_tags ^= 1;
1292
    if (tag_mask != 0) {
1293
        monitor.tagset[monitor.sel_tags] = tag_mask;
1294
        monitor.pertag.prevtag = monitor.pertag.curtag;
1295
1296
        if (tag_mask == ~@as(u32, 0)) {
1297
            monitor.pertag.curtag = 0;
1298
        } else {
1299
            var i: u32 = 0;
1300
            while (i < 9) : (i += 1) {
1301
                if ((tag_mask & (@as(u32, 1) << @intCast(i))) != 0) break;
1302
            }
1303
            monitor.pertag.curtag = i + 1;
1304
        }
1305
    } else {
1306
        const tmp = monitor.pertag.prevtag;
1307
        monitor.pertag.prevtag = monitor.pertag.curtag;
1308
        monitor.pertag.curtag = tmp;
1309
    }
1310
1311
    monitor.nmaster = monitor.pertag.nmasters[monitor.pertag.curtag];
1312
    monitor.mfact = monitor.pertag.mfacts[monitor.pertag.curtag];
1313
    monitor.sel_lt = monitor.pertag.sellts[monitor.pertag.curtag];
1314
1315
    focus_top_client(display, monitor);
1316
    arrange(monitor);
1317
    bar_mod.invalidate_bars();
1318
    std.debug.print("view: tag_mask={d}\n", .{monitor.tagset[monitor.sel_tags]});
1319
}
1320
1321
fn view_adjacent_tag(display: *Display, direction: i32) void {
1322
    const monitor = monitor_mod.selected_monitor orelse return;
1323
    const current_tag = monitor.pertag.curtag;
1324
    var new_tag: i32 = @intCast(current_tag);
1325
1326
    new_tag += direction;
1327
    if (new_tag < 1) new_tag = 9;
1328
    if (new_tag > 9) new_tag = 1;
1329
1330
    const tag_mask: u32 = @as(u32, 1) << @intCast(new_tag - 1);
1331
    view(display, tag_mask);
1332
}
1333
1334
fn view_adjacent_nonempty_tag(display: *Display, direction: i32) void {
1335
    const monitor = monitor_mod.selected_monitor orelse return;
1336
    const current_tag = monitor.pertag.curtag;
1337
    var new_tag: i32 = @intCast(current_tag);
1338
1339
    var attempts: i32 = 0;
1340
    while (attempts < 9) : (attempts += 1) {
1341
        new_tag += direction;
1342
        if (new_tag < 1) new_tag = 9;
1343
        if (new_tag > 9) new_tag = 1;
1344
1345
        const tag_mask: u32 = @as(u32, 1) << @intCast(new_tag - 1);
1346
        if (has_clients_on_tag(monitor, tag_mask)) {
1347
            view(display, tag_mask);
1348
            return;
1349
        }
1350
    }
1351
}
1352
1353
fn has_clients_on_tag(monitor: *monitor_mod.Monitor, tag_mask: u32) bool {
1354
    var client = monitor.clients;
1355
    while (client) |c| {
1356
        if ((c.tags & tag_mask) != 0) {
1357
            return true;
1358
        }
1359
        client = c.next;
1360
    }
1361
    return false;
1362
}
1363
1364
fn tag_client(display: *Display, tag_mask: u32) void {
1365
    const monitor = monitor_mod.selected_monitor orelse return;
1366
    const client = monitor.sel orelse return;
1367
    if (tag_mask == 0) {
1368
        return;
1369
    }
1370
    client.tags = tag_mask;
1371
    focus_top_client(display, monitor);
1372
    arrange(monitor);
1373
    bar_mod.invalidate_bars();
1374
    std.debug.print("tag_client: window=0x{x} tag_mask={d}\n", .{ client.window, tag_mask });
1375
}
1376
1377
fn focus_top_client(display: *Display, monitor: *Monitor) void {
1378
    var visible_client = monitor.stack;
1379
    while (visible_client) |client| {
1380
        if (client_mod.is_visible(client)) {
1381
            focus(display, client);
1382
            return;
1383
        }
1384
        visible_client = client.stack_next;
1385
    }
1386
    monitor.sel = null;
1387
    _ = xlib.XSetInputFocus(display.handle, display.root, xlib.RevertToPointerRoot, xlib.CurrentTime);
1388
}
1389
1390
fn focusstack(display: *Display, direction: i32) void {
1391
    const monitor = monitor_mod.selected_monitor orelse return;
1392
    const current = monitor.sel orelse return;
1393
1394
    var next_client: ?*Client = null;
1395
1396
    if (direction > 0) {
1397
        next_client = current.next;
1398
        while (next_client) |client| {
1399
            if (client_mod.is_visible(client)) {
1400
                break;
1401
            }
1402
            next_client = client.next;
1403
        }
1404
        if (next_client == null) {
1405
            next_client = monitor.clients;
1406
            while (next_client) |client| {
1407
                if (client_mod.is_visible(client)) {
1408
                    break;
1409
                }
1410
                next_client = client.next;
1411
            }
1412
        }
1413
    } else {
1414
        var prev: ?*Client = null;
1415
        var iter = monitor.clients;
1416
        while (iter) |client| {
1417
            if (client == current) {
1418
                break;
1419
            }
1420
            if (client_mod.is_visible(client)) {
1421
                prev = client;
1422
            }
1423
            iter = client.next;
1424
        }
1425
        if (prev == null) {
1426
            iter = current.next;
1427
            while (iter) |client| {
1428
                if (client_mod.is_visible(client)) {
1429
                    prev = client;
1430
                }
1431
                iter = client.next;
1432
            }
1433
        }
1434
        next_client = prev;
1435
    }
1436
1437
    if (next_client) |client| {
1438
        focus(display, client);
1439
        if (client.monitor) |client_monitor| {
1440
            restack(display, client_monitor);
1441
        }
1442
    }
1443
}
1444
1445
fn toggle_floating(_: *Display) void {
1446
    const monitor = monitor_mod.selected_monitor orelse return;
1447
    const client = monitor.sel orelse return;
1448
1449
    if (client.is_fullscreen) {
1450
        return;
1451
    }
1452
1453
    client.is_floating = !client.is_floating;
1454
1455
    if (client.is_floating) {
1456
        tiling.resize(client, client.x, client.y, client.width, client.height, false);
1457
    }
1458
1459
    arrange(monitor);
1460
    std.debug.print("toggle_floating: window=0x{x} floating={}\n", .{ client.window, client.is_floating });
1461
}
1462
1463
fn incnmaster(delta: i32) void {
1464
    const monitor = monitor_mod.selected_monitor orelse return;
1465
    const new_val = @max(0, monitor.nmaster + delta);
1466
    monitor.nmaster = new_val;
1467
    monitor.pertag.nmasters[monitor.pertag.curtag] = new_val;
1468
    arrange(monitor);
1469
    std.debug.print("incnmaster: nmaster={d}\n", .{monitor.nmaster});
1470
}
1471
1472
fn setmfact(delta: f32) void {
1473
    const monitor = monitor_mod.selected_monitor orelse return;
1474
    const new_mfact = monitor.mfact + delta;
1475
    if (new_mfact < 0.05 or new_mfact > 0.95) {
1476
        return;
1477
    }
1478
    monitor.mfact = new_mfact;
1479
    monitor.pertag.mfacts[monitor.pertag.curtag] = new_mfact;
1480
    arrange(monitor);
1481
    std.debug.print("setmfact: mfact={d:.2}\n", .{monitor.mfact});
1482
}
1483
1484
fn cycle_layout() void {
1485
    const monitor = monitor_mod.selected_monitor orelse return;
1486
    const new_lt = (monitor.sel_lt + 1) % 4;
1487
    monitor.sel_lt = new_lt;
1488
    monitor.pertag.sellts[monitor.pertag.curtag] = new_lt;
1489
    if (new_lt != 3) {
1490
        monitor.scroll_offset = 0;
1491
    }
1492
    arrange(monitor);
1493
    bar_mod.invalidate_bars();
1494
    if (monitor.lt[monitor.sel_lt]) |layout| {
1495
        std.debug.print("cycle_layout: {s}\n", .{layout.symbol});
1496
    }
1497
}
1498
1499
fn set_layout(layout_name: ?[]const u8) void {
1500
    const monitor = monitor_mod.selected_monitor orelse return;
1501
    const name = layout_name orelse return;
1502
1503
    const new_lt: u32 = if (std.mem.eql(u8, name, "tiling") or std.mem.eql(u8, name, "[]="))
1504
        0
1505
    else if (std.mem.eql(u8, name, "monocle") or std.mem.eql(u8, name, "[M]"))
1506
        1
1507
    else if (std.mem.eql(u8, name, "floating") or std.mem.eql(u8, name, "><>"))
1508
        2
1509
    else if (std.mem.eql(u8, name, "scrolling") or std.mem.eql(u8, name, "[S]"))
1510
        3
1511
    else {
1512
        std.debug.print("set_layout: unknown layout '{s}'\n", .{name});
1513
        return;
1514
    };
1515
1516
    monitor.sel_lt = new_lt;
1517
    monitor.pertag.sellts[monitor.pertag.curtag] = new_lt;
1518
    if (new_lt != 3) {
1519
        monitor.scroll_offset = 0;
1520
    }
1521
    arrange(monitor);
1522
    bar_mod.invalidate_bars();
1523
    if (monitor.lt[monitor.sel_lt]) |layout| {
1524
        std.debug.print("set_layout: {s}\n", .{layout.symbol});
1525
    }
1526
}
1527
1528
fn set_layout_index(index: u32) void {
1529
    const monitor = monitor_mod.selected_monitor orelse return;
1530
    monitor.sel_lt = index;
1531
    monitor.pertag.sellts[monitor.pertag.curtag] = index;
1532
    if (index != 3) {
1533
        monitor.scroll_offset = 0;
1534
    }
1535
    arrange(monitor);
1536
    bar_mod.invalidate_bars();
1537
    if (monitor.lt[monitor.sel_lt]) |layout| {
1538
        std.debug.print("set_layout_index: {s}\n", .{layout.symbol});
1539
    }
1540
}
1541
1542
fn focusmon(display: *Display, direction: i32) void {
1543
    const selmon = monitor_mod.selected_monitor orelse return;
1544
    const target = monitor_mod.dir_to_monitor(direction) orelse return;
1545
    if (target == selmon) {
1546
        return;
1547
    }
1548
    unfocus_client(display, selmon.sel, false);
1549
    monitor_mod.selected_monitor = target;
1550
    focus(display, null);
1551
    std.debug.print("focusmon: monitor {d}\n", .{target.num});
1552
}
1553
1554
fn sendmon(display: *Display, direction: i32) void {
1555
    const source_monitor = monitor_mod.selected_monitor orelse return;
1556
    const client = source_monitor.sel orelse return;
1557
    const target = monitor_mod.dir_to_monitor(direction) orelse return;
1558
1559
    if (target == source_monitor) {
1560
        return;
1561
    }
1562
1563
    client_mod.detach(client);
1564
    client_mod.detach_stack(client);
1565
    client.monitor = target;
1566
    client.tags = target.tagset[target.sel_tags];
1567
    client_mod.attach_aside(client);
1568
    client_mod.attach_stack(client);
1569
1570
    focus_top_client(display, source_monitor);
1571
    arrange(source_monitor);
1572
    arrange(target);
1573
1574
    std.debug.print("sendmon: window=0x{x} to monitor {d}\n", .{ client.window, target.num });
1575
}
1576
1577
fn snap_x(client: *Client, new_x: i32, monitor: *Monitor) i32 {
1578
    const client_width = client.width + 2 * client.border_width;
1579
    if (@abs(monitor.win_x - new_x) < snap_distance) {
1580
        return monitor.win_x;
1581
    } else if (@abs((monitor.win_x + monitor.win_w) - (new_x + client_width)) < snap_distance) {
1582
        return monitor.win_x + monitor.win_w - client_width;
1583
    }
1584
    return new_x;
1585
}
1586
1587
fn snap_y(client: *Client, new_y: i32, monitor: *Monitor) i32 {
1588
    const client_height = client.height + 2 * client.border_width;
1589
    if (@abs(monitor.win_y - new_y) < snap_distance) {
1590
        return monitor.win_y;
1591
    } else if (@abs((monitor.win_y + monitor.win_h) - (new_y + client_height)) < snap_distance) {
1592
        return monitor.win_y + monitor.win_h - client_height;
1593
    }
1594
    return new_y;
1595
}
1596
1597
fn movemouse(display: *Display) void {
1598
    const monitor = monitor_mod.selected_monitor orelse return;
1599
    const client = monitor.sel orelse return;
1600
1601
    if (client.is_fullscreen) {
1602
        return;
1603
    }
1604
1605
    restack(display, monitor);
1606
1607
    const was_floating = client.is_floating;
1608
    if (!client.is_floating) {
1609
        client.is_floating = true;
1610
    }
1611
1612
    var root_x: c_int = undefined;
1613
    var root_y: c_int = undefined;
1614
    var dummy_win: xlib.Window = undefined;
1615
    var dummy_int: c_int = undefined;
1616
    var dummy_uint: c_uint = undefined;
1617
1618
    _ = xlib.XQueryPointer(display.handle, display.root, &dummy_win, &dummy_win, &root_x, &root_y, &dummy_int, &dummy_int, &dummy_uint);
1619
1620
    const grab_result = xlib.XGrabPointer(
1621
        display.handle,
1622
        display.root,
1623
        xlib.False,
1624
        xlib.ButtonPressMask | xlib.ButtonReleaseMask | xlib.PointerMotionMask,
1625
        xlib.GrabModeAsync,
1626
        xlib.GrabModeAsync,
1627
        xlib.None,
1628
        cursor_move,
1629
        xlib.CurrentTime,
1630
    );
1631
1632
    if (grab_result != xlib.GrabSuccess) {
1633
        return;
1634
    }
1635
1636
    const start_x = client.x;
1637
    const start_y = client.y;
1638
    const pointer_start_x = root_x;
1639
    const pointer_start_y = root_y;
1640
    var last_time: c_ulong = 0;
1641
1642
    var event: xlib.XEvent = undefined;
1643
    var done = false;
1644
1645
    while (!done) {
1646
        _ = xlib.XNextEvent(display.handle, &event);
1647
1648
        switch (event.type) {
1649
            xlib.MotionNotify => {
1650
                const motion = &event.xmotion;
1651
                if ((motion.time - last_time) < (1000 / 60)) {
1652
                    continue;
1653
                }
1654
                last_time = motion.time;
1655
                const delta_x = motion.x_root - pointer_start_x;
1656
                const delta_y = motion.y_root - pointer_start_y;
1657
                var new_x = start_x + delta_x;
1658
                var new_y = start_y + delta_y;
1659
                if (client.monitor) |client_monitor| {
1660
                    new_x = snap_x(client, new_x, client_monitor);
1661
                    new_y = snap_y(client, new_y, client_monitor);
1662
                }
1663
                tiling.resize(client, new_x, new_y, client.width, client.height, true);
1664
            },
1665
            xlib.ButtonRelease => {
1666
                done = true;
1667
            },
1668
            else => {},
1669
        }
1670
    }
1671
1672
    _ = xlib.XUngrabPointer(display.handle, xlib.CurrentTime);
1673
1674
    const new_mon = monitor_mod.rect_to_monitor(client.x, client.y, client.width, client.height);
1675
    if (new_mon != null and new_mon != monitor) {
1676
        client_mod.detach(client);
1677
        client_mod.detach_stack(client);
1678
        client.monitor = new_mon;
1679
        client.tags = new_mon.?.tagset[new_mon.?.sel_tags];
1680
        client_mod.attach_aside(client);
1681
        client_mod.attach_stack(client);
1682
        monitor_mod.selected_monitor = new_mon;
1683
        focus(display, client);
1684
        arrange(monitor);
1685
        arrange(new_mon.?);
1686
    } else {
1687
        arrange(monitor);
1688
    }
1689
1690
    if (config.auto_tile and !was_floating) {
1691
        const drop_monitor = client.monitor orelse return;
1692
        const center_x = client.x + @divTrunc(client.width, 2);
1693
        const center_y = client.y + @divTrunc(client.height, 2);
1694
1695
        if (client_mod.tiled_window_at(client, drop_monitor, center_x, center_y)) |target| {
1696
            client_mod.insert_before(client, target);
1697
        }
1698
1699
        client.is_floating = false;
1700
        arrange(drop_monitor);
1701
    }
1702
}
1703
1704
fn resizemouse(display: *Display) void {
1705
    const monitor = monitor_mod.selected_monitor orelse return;
1706
    const client = monitor.sel orelse return;
1707
1708
    if (client.is_fullscreen) {
1709
        return;
1710
    }
1711
1712
    restack(display, monitor);
1713
1714
    if (!client.is_floating) {
1715
        client.is_floating = true;
1716
    }
1717
1718
    const grab_result = xlib.XGrabPointer(
1719
        display.handle,
1720
        display.root,
1721
        xlib.False,
1722
        xlib.ButtonPressMask | xlib.ButtonReleaseMask | xlib.PointerMotionMask,
1723
        xlib.GrabModeAsync,
1724
        xlib.GrabModeAsync,
1725
        xlib.None,
1726
        cursor_resize,
1727
        xlib.CurrentTime,
1728
    );
1729
1730
    if (grab_result != xlib.GrabSuccess) {
1731
        return;
1732
    }
1733
1734
    _ = xlib.XWarpPointer(display.handle, xlib.None, client.window, 0, 0, 0, 0, client.width + client.border_width - 1, client.height + client.border_width - 1);
1735
1736
    var event: xlib.XEvent = undefined;
1737
    var done = false;
1738
    var last_time: c_ulong = 0;
1739
1740
    while (!done) {
1741
        _ = xlib.XNextEvent(display.handle, &event);
1742
1743
        switch (event.type) {
1744
            xlib.MotionNotify => {
1745
                const motion = &event.xmotion;
1746
                if ((motion.time - last_time) < (1000 / 60)) {
1747
                    continue;
1748
                }
1749
                last_time = motion.time;
1750
                var new_width = @max(1, motion.x_root - client.x - 2 * client.border_width + 1);
1751
                var new_height = @max(1, motion.y_root - client.y - 2 * client.border_width + 1);
1752
                if (client.monitor) |client_monitor| {
1753
                    const client_right = client.x + new_width + 2 * client.border_width;
1754
                    const client_bottom = client.y + new_height + 2 * client.border_width;
1755
                    const mon_right = client_monitor.win_x + client_monitor.win_w;
1756
                    const mon_bottom = client_monitor.win_y + client_monitor.win_h;
1757
                    if (@abs(mon_right - client_right) < snap_distance) {
1758
                        new_width = @max(1, mon_right - client.x - 2 * client.border_width);
1759
                    }
1760
                    if (@abs(mon_bottom - client_bottom) < snap_distance) {
1761
                        new_height = @max(1, mon_bottom - client.y - 2 * client.border_width);
1762
                    }
1763
                }
1764
                tiling.resize(client, client.x, client.y, new_width, new_height, true);
1765
            },
1766
            xlib.ButtonRelease => {
1767
                done = true;
1768
            },
1769
            else => {},
1770
        }
1771
    }
1772
1773
    _ = xlib.XUngrabPointer(display.handle, xlib.CurrentTime);
1774
    arrange(monitor);
1775
}
1776
1777
fn handle_expose(display: *Display, event: *xlib.XExposeEvent) void {
1778
    if (event.count != 0) return;
1779
1780
    if (bar_mod.window_to_bar(event.window)) |bar| {
1781
        bar.invalidate();
1782
        bar.draw(display.handle, &tags);
1783
    }
1784
}
1785
1786
fn clean_mask(mask: c_uint) c_uint {
1787
    const lock: c_uint = @intCast(xlib.LockMask);
1788
    const shift: c_uint = @intCast(xlib.ShiftMask);
1789
    const ctrl: c_uint = @intCast(xlib.ControlMask);
1790
    const mod1: c_uint = @intCast(xlib.Mod1Mask);
1791
    const mod2: c_uint = @intCast(xlib.Mod2Mask);
1792
    const mod3: c_uint = @intCast(xlib.Mod3Mask);
1793
    const mod4: c_uint = @intCast(xlib.Mod4Mask);
1794
    const mod5: c_uint = @intCast(xlib.Mod5Mask);
1795
    return mask & ~(lock | numlock_mask) & (shift | ctrl | mod1 | mod2 | mod3 | mod4 | mod5);
1796
}
1797
1798
fn handle_button_press(display: *Display, event: *xlib.XButtonEvent) void {
1799
    std.debug.print("button_press: window=0x{x} subwindow=0x{x}\n", .{ event.window, event.subwindow });
1800
1801
    const clicked_monitor = monitor_mod.window_to_monitor(event.window);
1802
    if (clicked_monitor) |monitor| {
1803
        if (monitor != monitor_mod.selected_monitor) {
1804
            if (monitor_mod.selected_monitor) |selmon| {
1805
                unfocus_client(display, selmon.sel, true);
1806
            }
1807
            monitor_mod.selected_monitor = monitor;
1808
            focus(display, null);
1809
        }
1810
    }
1811
1812
    if (bar_mod.window_to_bar(event.window)) |bar| {
1813
        const clicked_tag = bar.handle_click(event.x, &tags);
1814
        if (clicked_tag) |tag_index| {
1815
            const tag_mask: u32 = @as(u32, 1) << @intCast(tag_index);
1816
            view(display, tag_mask);
1817
        }
1818
        return;
1819
    }
1820
1821
    const click_client = client_mod.window_to_client(event.window);
1822
    if (click_client) |found_client| {
1823
        focus(display, found_client);
1824
        if (monitor_mod.selected_monitor) |selmon| {
1825
            restack(display, selmon);
1826
        }
1827
        _ = xlib.XAllowEvents(display.handle, xlib.ReplayPointer, xlib.CurrentTime);
1828
    }
1829
1830
    const clean_state = clean_mask(event.state);
1831
    for (config.buttons.items) |button| {
1832
        if (button.click != .client_win) continue;
1833
        const button_clean_mask = clean_mask(button.mod_mask);
1834
        if (clean_state == button_clean_mask and event.button == button.button) {
1835
            switch (button.action) {
1836
                .move_mouse => movemouse(display),
1837
                .resize_mouse => resizemouse(display),
1838
                .toggle_floating => {
1839
                    if (click_client) |found_client| {
1840
                        found_client.is_floating = !found_client.is_floating;
1841
                        if (monitor_mod.selected_monitor) |monitor| {
1842
                            arrange(monitor);
1843
                        }
1844
                    }
1845
                },
1846
            }
1847
            return;
1848
        }
1849
    }
1850
}
1851
1852
fn handle_client_message(display: *Display, event: *xlib.XClientMessageEvent) void {
1853
    const client = client_mod.window_to_client(event.window) orelse return;
1854
1855
    if (event.message_type == net_wm_state) {
1856
        const action = event.data.l[0];
1857
        const first_property = @as(xlib.Atom, @intCast(event.data.l[1]));
1858
        const second_property = @as(xlib.Atom, @intCast(event.data.l[2]));
1859
1860
        if (first_property == net_wm_state_fullscreen or second_property == net_wm_state_fullscreen) {
1861
            const net_wm_state_remove = 0;
1862
            const net_wm_state_add = 1;
1863
            const net_wm_state_toggle = 2;
1864
1865
            if (action == net_wm_state_add) {
1866
                set_fullscreen(display, client, true);
1867
            } else if (action == net_wm_state_remove) {
1868
                set_fullscreen(display, client, false);
1869
            } else if (action == net_wm_state_toggle) {
1870
                set_fullscreen(display, client, !client.is_fullscreen);
1871
            }
1872
        }
1873
    } else if (event.message_type == net_active_window) {
1874
        const selected = monitor_mod.selected_monitor orelse return;
1875
        if (client != selected.sel and !client.is_urgent) {
1876
            set_urgent(display, client, true);
1877
        }
1878
    }
1879
}
1880
1881
fn handle_destroy_notify(display: *Display, event: *xlib.XDestroyWindowEvent) void {
1882
    const client = client_mod.window_to_client(event.window) orelse return;
1883
    std.debug.print("destroy_notify: window=0x{x}\n", .{event.window});
1884
    unmanage(display, client);
1885
}
1886
1887
fn handle_unmap_notify(display: *Display, event: *xlib.XUnmapEvent) void {
1888
    const client = client_mod.window_to_client(event.window) orelse return;
1889
    std.debug.print("unmap_notify: window=0x{x}\n", .{event.window});
1890
    unmanage(display, client);
1891
}
1892
1893
fn unmanage(display: *Display, client: *Client) void {
1894
    const client_monitor = client.monitor;
1895
1896
    var next_focus: ?*Client = null;
1897
    if (client_monitor) |monitor| {
1898
        if (monitor.sel == client and is_scrolling_layout(monitor)) {
1899
            next_focus = client.next;
1900
            if (next_focus == null) {
1901
                var prev: ?*Client = null;
1902
                var iter = monitor.clients;
1903
                while (iter) |c| {
1904
                    if (c == client) break;
1905
                    prev = c;
1906
                    iter = c.next;
1907
                }
1908
                next_focus = prev;
1909
            }
1910
        }
1911
    }
1912
1913
    client_mod.detach(client);
1914
    client_mod.detach_stack(client);
1915
1916
    if (client_monitor) |monitor| {
1917
        if (monitor.sel == client) {
1918
            monitor.sel = if (next_focus) |nf| nf else monitor.stack;
1919
        }
1920
        if (is_scrolling_layout(monitor)) {
1921
            const target = if (monitor.sel) |sel| scrolling.get_target_scroll_for_window(monitor, sel) else 0;
1922
            if (target == 0) {
1923
                monitor.scroll_offset = scrolling.get_scroll_step(monitor);
1924
            } else {
1925
                monitor.scroll_offset = 0;
1926
            }
1927
        }
1928
        arrange(monitor);
1929
    }
1930
1931
    if (client_monitor) |monitor| {
1932
        if (monitor.sel) |selected| {
1933
            focus(display, selected);
1934
        }
1935
    }
1936
1937
    client_mod.destroy(client);
1938
    update_client_list(display);
1939
    bar_mod.invalidate_bars();
1940
}
1941
1942
fn handle_enter_notify(display: *Display, event: *xlib.XCrossingEvent) void {
1943
    if ((event.mode != xlib.NotifyNormal or event.detail == xlib.NotifyInferior) and event.window != display.root) {
1944
        return;
1945
    }
1946
1947
    const client = client_mod.window_to_client(event.window);
1948
    const target_mon = if (client) |c| c.monitor else monitor_mod.window_to_monitor(event.window);
1949
    const selmon = monitor_mod.selected_monitor;
1950
1951
    if (target_mon != selmon) {
1952
        if (selmon) |sel| {
1953
            unfocus_client(display, sel.sel, true);
1954
        }
1955
        monitor_mod.selected_monitor = target_mon;
1956
    } else if (client == null) {
1957
        return;
1958
    } else if (selmon) |sel| {
1959
        if (client.? == sel.sel) {
1960
            return;
1961
        }
1962
    }
1963
1964
    focus(display, client);
1965
}
1966
1967
fn handle_focus_in(display: *Display, event: *xlib.XFocusChangeEvent) void {
1968
    const selmon = monitor_mod.selected_monitor orelse return;
1969
    const selected = selmon.sel orelse return;
1970
    if (event.window != selected.window) {
1971
        set_focus(display, selected);
1972
    }
1973
}
1974
1975
fn set_focus(display: *Display, client: *Client) void {
1976
    if (!client.never_focus) {
1977
        _ = xlib.XSetInputFocus(display.handle, client.window, xlib.RevertToPointerRoot, xlib.CurrentTime);
1978
        _ = xlib.XChangeProperty(display.handle, display.root, net_active_window, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&client.window), 1);
1979
    }
1980
    _ = send_event(display, client, wm_take_focus);
1981
}
1982
1983
var last_motion_monitor: ?*Monitor = null;
1984
1985
fn handle_motion_notify(display: *Display, event: *xlib.XMotionEvent) void {
1986
    if (event.window != display.root) {
1987
        return;
1988
    }
1989
1990
    const target_mon = monitor_mod.rect_to_monitor(event.x_root, event.y_root, 1, 1);
1991
    if (target_mon != last_motion_monitor and last_motion_monitor != null) {
1992
        if (monitor_mod.selected_monitor) |selmon| {
1993
            unfocus_client(display, selmon.sel, true);
1994
        }
1995
        monitor_mod.selected_monitor = target_mon;
1996
        focus(display, null);
1997
    }
1998
    last_motion_monitor = target_mon;
1999
}
2000
2001
fn handle_property_notify(display: *Display, event: *xlib.XPropertyEvent) void {
2002
    if (event.state == xlib.PropertyDelete) {
2003
        return;
2004
    }
2005
2006
    const client = client_mod.window_to_client(event.window) orelse return;
2007
2008
    if (event.atom == xlib.XA_WM_TRANSIENT_FOR) {
2009
        var trans: xlib.Window = 0;
2010
        if (!client.is_floating and xlib.XGetTransientForHint(display.handle, client.window, &trans) != 0) {
2011
            client.is_floating = client_mod.window_to_client(trans) != null;
2012
            if (client.is_floating) {
2013
                if (client.monitor) |monitor| {
2014
                    arrange(monitor);
2015
                }
2016
            }
2017
        }
2018
    } else if (event.atom == xlib.XA_WM_NORMAL_HINTS) {
2019
        client.hints_valid = false;
2020
    } else if (event.atom == xlib.XA_WM_HINTS) {
2021
        update_wm_hints(display, client);
2022
        bar_mod.invalidate_bars();
2023
    } else if (event.atom == xlib.XA_WM_NAME or event.atom == net_wm_name) {
2024
        update_title(display, client);
2025
    } else if (event.atom == net_wm_window_type) {
2026
        update_window_type(display, client);
2027
    }
2028
}
2029
2030
fn unfocus_client(display: *Display, client: ?*Client, reset_input_focus: bool) void {
2031
    const unfocus_target = client orelse return;
2032
    grabbuttons(display, unfocus_target, false);
2033
    _ = xlib.XSetWindowBorder(display.handle, unfocus_target.window, border_color_unfocused);
2034
    if (reset_input_focus) {
2035
        _ = xlib.XSetInputFocus(display.handle, display.root, xlib.RevertToPointerRoot, xlib.CurrentTime);
2036
        _ = xlib.XDeleteProperty(display.handle, display.root, net_active_window);
2037
    }
2038
}
2039
2040
fn set_client_state(display: *Display, client: *Client, state: c_long) void {
2041
    var data: [2]c_long = .{ state, xlib.None };
2042
    _ = xlib.c.XChangeProperty(display.handle, client.window, wm_state, wm_state, 32, xlib.PropModeReplace, @ptrCast(&data), 2);
2043
}
2044
2045
fn update_numlock_mask(display: *Display) void {
2046
    numlock_mask = 0;
2047
    const modmap = xlib.XGetModifierMapping(display.handle);
2048
    if (modmap == null) return;
2049
    defer _ = xlib.XFreeModifiermap(modmap);
2050
2051
    const numlock_keycode = xlib.XKeysymToKeycode(display.handle, xlib.XK_Num_Lock);
2052
2053
    var modifier_index: usize = 0;
2054
    while (modifier_index < 8) : (modifier_index += 1) {
2055
        var key_index: usize = 0;
2056
        while (key_index < @as(usize, @intCast(modmap.*.max_keypermod))) : (key_index += 1) {
2057
            const keycode = modmap.*.modifiermap[modifier_index * @as(usize, @intCast(modmap.*.max_keypermod)) + key_index];
2058
            if (keycode == numlock_keycode) {
2059
                numlock_mask = @as(c_uint, 1) << @intCast(modifier_index);
2060
            }
2061
        }
2062
    }
2063
}
2064
2065
fn grabbuttons(display: *Display, client: *Client, focused: bool) void {
2066
    update_numlock_mask(display);
2067
    const modifiers = [_]c_uint{ 0, xlib.LockMask, numlock_mask, numlock_mask | xlib.LockMask };
2068
2069
    _ = xlib.XUngrabButton(display.handle, xlib.AnyButton, xlib.AnyModifier, client.window);
2070
    if (!focused) {
2071
        _ = xlib.XGrabButton(
2072
            display.handle,
2073
            xlib.AnyButton,
2074
            xlib.AnyModifier,
2075
            client.window,
2076
            xlib.False,
2077
            xlib.ButtonPressMask | xlib.ButtonReleaseMask,
2078
            xlib.GrabModeSync,
2079
            xlib.GrabModeSync,
2080
            xlib.None,
2081
            xlib.None,
2082
        );
2083
    }
2084
    for (config.buttons.items) |button| {
2085
        if (button.click == .client_win) {
2086
            for (modifiers) |modifier| {
2087
                _ = xlib.XGrabButton(
2088
                    display.handle,
2089
                    @intCast(button.button),
2090
                    button.mod_mask | modifier,
2091
                    client.window,
2092
                    xlib.False,
2093
                    xlib.ButtonPressMask | xlib.ButtonReleaseMask,
2094
                    xlib.GrabModeAsync,
2095
                    xlib.GrabModeSync,
2096
                    xlib.None,
2097
                    xlib.None,
2098
                );
2099
            }
2100
        }
2101
    }
2102
}
2103
2104
fn focus(display: *Display, target_client: ?*Client) void {
2105
    const selmon = monitor_mod.selected_monitor orelse return;
2106
2107
    var focus_client = target_client;
2108
    if (focus_client == null or !client_mod.is_visible(focus_client.?)) {
2109
        focus_client = selmon.stack;
2110
        while (focus_client) |iter| {
2111
            if (client_mod.is_visible(iter)) break;
2112
            focus_client = iter.stack_next;
2113
        }
2114
    }
2115
2116
    if (selmon.sel != null and selmon.sel != focus_client) {
2117
        unfocus_client(display, selmon.sel, false);
2118
    }
2119
2120
    if (focus_client) |client| {
2121
        if (client.monitor != selmon) {
2122
            monitor_mod.selected_monitor = client.monitor;
2123
        }
2124
        if (client.is_urgent) {
2125
            set_urgent(display, client, false);
2126
        }
2127
        client_mod.detach_stack(client);
2128
        client_mod.attach_stack(client);
2129
        grabbuttons(display, client, true);
2130
        _ = xlib.XSetWindowBorder(display.handle, client.window, border_color_focused);
2131
        if (!client.never_focus) {
2132
            _ = xlib.XSetInputFocus(display.handle, client.window, xlib.RevertToPointerRoot, xlib.CurrentTime);
2133
            _ = xlib.XChangeProperty(display.handle, display.root, net_active_window, xlib.XA_WINDOW, 32, xlib.PropModeReplace, @ptrCast(&client.window), 1);
2134
        }
2135
        _ = send_event(display, client, wm_take_focus);
2136
    } else {
2137
        _ = xlib.XSetInputFocus(display.handle, display.root, xlib.RevertToPointerRoot, xlib.CurrentTime);
2138
        _ = xlib.XDeleteProperty(display.handle, display.root, net_active_window);
2139
    }
2140
2141
    const current_selmon = monitor_mod.selected_monitor orelse return;
2142
    current_selmon.sel = focus_client;
2143
2144
    if (focus_client) |client| {
2145
        if (is_scrolling_layout(current_selmon)) {
2146
            scroll_to_window(client, true);
2147
        }
2148
    }
2149
2150
    bar_mod.invalidate_bars();
2151
}
2152
2153
fn restack(display: *Display, monitor: *Monitor) void {
2154
    bar_mod.invalidate_bars();
2155
    const selected_client = monitor.sel orelse return;
2156
2157
    if (selected_client.is_floating or monitor.lt[monitor.sel_lt] == null) {
2158
        _ = xlib.XRaiseWindow(display.handle, selected_client.window);
2159
    }
2160
2161
    if (monitor.lt[monitor.sel_lt] != null) {
2162
        var window_changes: xlib.c.XWindowChanges = undefined;
2163
        window_changes.stack_mode = xlib.c.Below;
2164
        window_changes.sibling = monitor.bar_win;
2165
2166
        var current = monitor.stack;
2167
        while (current) |client| {
2168
            if (!client.is_floating and client_mod.is_visible(client)) {
2169
                _ = xlib.c.XConfigureWindow(display.handle, client.window, xlib.c.CWSibling | xlib.c.CWStackMode, &window_changes);
2170
                window_changes.sibling = client.window;
2171
            }
2172
            current = client.stack_next;
2173
        }
2174
    }
2175
2176
    _ = xlib.XSync(display.handle, xlib.False);
2177
2178
    var discard_event: xlib.XEvent = undefined;
2179
    while (xlib.c.XCheckMaskEvent(display.handle, xlib.EnterWindowMask, &discard_event) != 0) {}
2180
}
2181
2182
fn arrange(monitor: *Monitor) void {
2183
    if (display_global) |display| {
2184
        showhide(display, monitor);
2185
    }
2186
    if (monitor.lt[monitor.sel_lt]) |layout| {
2187
        if (layout.arrange_fn) |arrange_fn| {
2188
            arrange_fn(monitor);
2189
        }
2190
    }
2191
    if (display_global) |display| {
2192
        restack(display, monitor);
2193
    }
2194
}
2195
2196
fn tick_animations() void {
2197
    if (!scroll_animation.is_active()) return;
2198
2199
    const monitor = monitor_mod.selected_monitor orelse return;
2200
    if (scroll_animation.update()) |new_offset| {
2201
        monitor.scroll_offset = new_offset;
2202
        arrange(monitor);
2203
    }
2204
}
2205
2206
fn is_scrolling_layout(monitor: *Monitor) bool {
2207
    if (monitor.lt[monitor.sel_lt]) |layout| {
2208
        return layout.arrange_fn == scrolling.layout.arrange_fn;
2209
    }
2210
    return false;
2211
}
2212
2213
fn scroll_layout(direction: i32) void {
2214
    const monitor = monitor_mod.selected_monitor orelse return;
2215
    if (!is_scrolling_layout(monitor)) return;
2216
2217
    const scroll_step = scrolling.get_scroll_step(monitor);
2218
    const max_scroll = scrolling.get_max_scroll(monitor);
2219
2220
    const current = if (scroll_animation.is_active())
2221
        scroll_animation.target()
2222
    else
2223
        monitor.scroll_offset;
2224
2225
    var target = current + direction * scroll_step;
2226
    target = @max(0, @min(target, max_scroll));
2227
2228
    scroll_animation.start(monitor.scroll_offset, target, animation_config);
2229
}
2230
2231
fn scroll_to_window(client: *Client, animate: bool) void {
2232
    const monitor = client.monitor orelse return;
2233
    if (!is_scrolling_layout(monitor)) return;
2234
2235
    const target = scrolling.get_target_scroll_for_window(monitor, client);
2236
2237
    if (animate) {
2238
        scroll_animation.start(monitor.scroll_offset, target, animation_config);
2239
    } else {
2240
        monitor.scroll_offset = target;
2241
        arrange(monitor);
2242
    }
2243
}
2244
2245
fn showhide_client(display: *Display, client: ?*Client) void {
2246
    const target = client orelse return;
2247
    if (client_mod.is_visible(target)) {
2248
        _ = xlib.XMoveWindow(display.handle, target.window, target.x, target.y);
2249
        const monitor = target.monitor orelse return;
2250
        if ((monitor.lt[monitor.sel_lt] == null or target.is_floating) and !target.is_fullscreen) {
2251
            tiling.resize(target, target.x, target.y, target.width, target.height, false);
2252
        }
2253
        showhide_client(display, target.stack_next);
2254
    } else {
2255
        showhide_client(display, target.stack_next);
2256
        const client_width = target.width + 2 * target.border_width;
2257
        _ = xlib.XMoveWindow(display.handle, target.window, -2 * client_width, target.y);
2258
    }
2259
}
2260
2261
fn showhide(display: *Display, monitor: *Monitor) void {
2262
    showhide_client(display, monitor.stack);
2263
}
2264
2265
fn update_size_hints(display: *Display, client: *Client) void {
2266
    var size_hints: xlib.XSizeHints = undefined;
2267
    var msize: c_long = 0;
2268
2269
    if (xlib.XGetWMNormalHints(display.handle, client.window, &size_hints, &msize) == 0) {
2270
        size_hints.flags = xlib.PSize;
2271
    }
2272
2273
    if ((size_hints.flags & xlib.PBaseSize) != 0) {
2274
        client.base_width = size_hints.base_width;
2275
        client.base_height = size_hints.base_height;
2276
    } else if ((size_hints.flags & xlib.PMinSize) != 0) {
2277
        client.base_width = size_hints.min_width;
2278
        client.base_height = size_hints.min_height;
2279
    } else {
2280
        client.base_width = 0;
2281
        client.base_height = 0;
2282
    }
2283
2284
    if ((size_hints.flags & xlib.PResizeInc) != 0) {
2285
        client.increment_width = size_hints.width_inc;
2286
        client.increment_height = size_hints.height_inc;
2287
    } else {
2288
        client.increment_width = 0;
2289
        client.increment_height = 0;
2290
    }
2291
2292
    if ((size_hints.flags & xlib.PMaxSize) != 0) {
2293
        client.max_width = size_hints.max_width;
2294
        client.max_height = size_hints.max_height;
2295
    } else {
2296
        client.max_width = 0;
2297
        client.max_height = 0;
2298
    }
2299
2300
    if ((size_hints.flags & xlib.PMinSize) != 0) {
2301
        client.min_width = size_hints.min_width;
2302
        client.min_height = size_hints.min_height;
2303
    } else if ((size_hints.flags & xlib.PBaseSize) != 0) {
2304
        client.min_width = size_hints.base_width;
2305
        client.min_height = size_hints.base_height;
2306
    } else {
2307
        client.min_width = 0;
2308
        client.min_height = 0;
2309
    }
2310
2311
    if ((size_hints.flags & xlib.PAspect) != 0) {
2312
        client.min_aspect = @as(f32, @floatFromInt(size_hints.min_aspect.y)) / @as(f32, @floatFromInt(size_hints.min_aspect.x));
2313
        client.max_aspect = @as(f32, @floatFromInt(size_hints.max_aspect.x)) / @as(f32, @floatFromInt(size_hints.max_aspect.y));
2314
    } else {
2315
        client.min_aspect = 0.0;
2316
        client.max_aspect = 0.0;
2317
    }
2318
2319
    client.is_fixed = (client.max_width != 0 and client.max_height != 0 and client.max_width == client.min_width and client.max_height == client.min_height);
2320
    client.hints_valid = true;
2321
}
2322
2323
fn update_wm_hints(display: *Display, client: *Client) void {
2324
    const wmh = xlib.XGetWMHints(display.handle, client.window);
2325
    if (wmh) |hints| {
2326
        defer _ = xlib.XFree(@ptrCast(hints));
2327
2328
        if (client == (monitor_mod.selected_monitor orelse return).sel and (hints.*.flags & xlib.XUrgencyHint) != 0) {
2329
            hints.*.flags = hints.*.flags & ~@as(c_long, xlib.XUrgencyHint);
2330
            _ = xlib.XSetWMHints(display.handle, client.window, hints);
2331
        } else {
2332
            client.is_urgent = (hints.*.flags & xlib.XUrgencyHint) != 0;
2333
        }
2334
2335
        if ((hints.*.flags & xlib.InputHint) != 0) {
2336
            client.never_focus = hints.*.input == 0;
2337
        } else {
2338
            client.never_focus = false;
2339
        }
2340
    }
2341
}
2342
2343
fn update_window_type(display: *Display, client: *Client) void {
2344
    const state = get_atom_prop(display, client, net_wm_state);
2345
    const window_type = get_atom_prop(display, client, net_wm_window_type);
2346
2347
    if (state == net_wm_state_fullscreen) {
2348
        set_fullscreen(display, client, true);
2349
    }
2350
    if (window_type == net_wm_window_type_dialog) {
2351
        client.is_floating = true;
2352
    }
2353
}
2354
2355
fn update_title(display: *Display, client: *Client) void {
2356
    if (!get_text_prop(display, client.window, net_wm_name, &client.name)) {
2357
        _ = get_text_prop(display, client.window, xlib.XA_WM_NAME, &client.name);
2358
    }
2359
    if (client.name[0] == 0) {
2360
        @memcpy(client.name[0..6], "broken");
2361
    }
2362
}
2363
2364
fn get_atom_prop(display: *Display, client: *Client, prop: xlib.Atom) xlib.Atom {
2365
    var actual_type: xlib.Atom = undefined;
2366
    var actual_format: c_int = undefined;
2367
    var num_items: c_ulong = undefined;
2368
    var bytes_after: c_ulong = undefined;
2369
    var prop_data: [*c]u8 = undefined;
2370
2371
    if (xlib.XGetWindowProperty(display.handle, client.window, prop, 0, @sizeOf(xlib.Atom), xlib.False, xlib.XA_ATOM, &actual_type, &actual_format, &num_items, &bytes_after, &prop_data) == 0 and prop_data != null) {
2372
        const atom: xlib.Atom = @as(*xlib.Atom, @ptrCast(@alignCast(prop_data))).*;
2373
        _ = xlib.XFree(@ptrCast(prop_data));
2374
        return atom;
2375
    }
2376
    return 0;
2377
}
2378
2379
fn get_text_prop(display: *Display, window: xlib.Window, atom: xlib.Atom, text: *[256]u8) bool {
2380
    var name: xlib.XTextProperty = undefined;
2381
    text[0] = 0;
2382
2383
    if (xlib.XGetTextProperty(display.handle, window, &name, atom) == 0 or name.nitems == 0) {
2384
        return false;
2385
    }
2386
2387
    if (name.encoding == xlib.XA_STRING) {
2388
        const len = @min(name.nitems, 255);
2389
        @memcpy(text[0..len], name.value[0..len]);
2390
        text[len] = 0;
2391
    } else {
2392
        var list: [*c][*c]u8 = undefined;
2393
        var count: c_int = undefined;
2394
        if (xlib.XmbTextPropertyToTextList(display.handle, &name, &list, &count) >= xlib.Success and count > 0 and list[0] != null) {
2395
            const str = std.mem.sliceTo(list[0], 0);
2396
            const copy_len = @min(str.len, 255);
2397
            @memcpy(text[0..copy_len], str[0..copy_len]);
2398
            text[copy_len] = 0;
2399
            xlib.XFreeStringList(list);
2400
        }
2401
    }
2402
    text[255] = 0;
2403
    _ = xlib.XFree(@ptrCast(name.value));
2404
    return true;
2405
}
2406
2407
fn apply_rules(display: *Display, client: *Client) void {
2408
    var class_hint: xlib.XClassHint = .{ .res_name = null, .res_class = null };
2409
    _ = xlib.XGetClassHint(display.handle, client.window, &class_hint);
2410
2411
    const class_str: []const u8 = if (class_hint.res_class) |ptr| std.mem.sliceTo(ptr, 0) else "";
2412
    const instance_str: []const u8 = if (class_hint.res_name) |ptr| std.mem.sliceTo(ptr, 0) else "";
2413
2414
    client.is_floating = false;
2415
    client.tags = 0;
2416
2417
    for (config.rules.items) |rule| {
2418
        const class_matches = if (rule.class) |rc| std.mem.indexOf(u8, class_str, rc) != null else true;
2419
        const instance_matches = if (rule.instance) |ri| std.mem.indexOf(u8, instance_str, ri) != null else true;
2420
        const title_matches = if (rule.title) |rt| std.mem.indexOf(u8, std.mem.sliceTo(&client.name, 0), rt) != null else true;
2421
2422
        if (class_matches and instance_matches and title_matches) {
2423
            client.is_floating = rule.is_floating;
2424
            client.tags |= rule.tags;
2425
            if (rule.monitor >= 0) {
2426
                var target = monitor_mod.monitors;
2427
                var index: i32 = 0;
2428
                while (target) |mon| {
2429
                    if (index == rule.monitor) {
2430
                        client.monitor = mon;
2431
                        break;
2432
                    }
2433
                    index += 1;
2434
                    target = mon.next;
2435
                }
2436
            }
2437
        }
2438
    }
2439
2440
    if (class_hint.res_class) |ptr| {
2441
        _ = xlib.XFree(@ptrCast(ptr));
2442
    }
2443
    if (class_hint.res_name) |ptr| {
2444
        _ = xlib.XFree(@ptrCast(ptr));
2445
    }
2446
2447
    const monitor = client.monitor orelse return;
2448
    if (client.tags == 0) {
2449
        client.tags = monitor.tagset[monitor.sel_tags];
2450
    }
2451
}
2452
2453
fn update_client_list(display: *Display) void {
2454
    _ = xlib.XDeleteProperty(display.handle, display.root, net_client_list);
2455
2456
    var current_monitor = monitor_mod.monitors;
2457
    while (current_monitor) |monitor| {
2458
        var current_client = monitor.clients;
2459
        while (current_client) |client| {
2460
            _ = xlib.XChangeProperty(display.handle, display.root, net_client_list, xlib.XA_WINDOW, 32, xlib.PropModeAppend, @ptrCast(&client.window), 1);
2461
            current_client = client.next;
2462
        }
2463
        current_monitor = monitor.next;
2464
    }
2465
}
2466
2467
fn send_event(display: *Display, client: *Client, protocol: xlib.Atom) bool {
2468
    var protocols: [*c]xlib.Atom = undefined;
2469
    var num_protocols: c_int = 0;
2470
    var exists = false;
2471
2472
    if (xlib.XGetWMProtocols(display.handle, client.window, &protocols, &num_protocols) != 0) {
2473
        var index: usize = 0;
2474
        while (index < @as(usize, @intCast(num_protocols))) : (index += 1) {
2475
            if (protocols[index] == protocol) {
2476
                exists = true;
2477
                break;
2478
            }
2479
        }
2480
        _ = xlib.XFree(@ptrCast(protocols));
2481
    }
2482
2483
    if (exists) {
2484
        var event: xlib.XEvent = undefined;
2485
        event.type = xlib.ClientMessage;
2486
        event.xclient.window = client.window;
2487
        event.xclient.message_type = wm_protocols;
2488
        event.xclient.format = 32;
2489
        event.xclient.data.l[0] = @intCast(protocol);
2490
        event.xclient.data.l[1] = xlib.CurrentTime;
2491
        _ = xlib.XSendEvent(display.handle, client.window, xlib.False, xlib.NoEventMask, &event);
2492
    }
2493
    return exists;
2494
}
2495
2496
fn set_urgent(display: *Display, client: *Client, urgent: bool) void {
2497
    client.is_urgent = urgent;
2498
    const wmh = xlib.XGetWMHints(display.handle, client.window);
2499
    if (wmh) |hints| {
2500
        if (urgent) {
2501
            hints.*.flags = hints.*.flags | xlib.XUrgencyHint;
2502
        } else {
2503
            hints.*.flags = hints.*.flags & ~@as(c_long, xlib.XUrgencyHint);
2504
        }
2505
        _ = xlib.XSetWMHints(display.handle, client.window, hints);
2506
        _ = xlib.XFree(@ptrCast(hints));
2507
    }
2508
}