oxwm

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