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