oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
13,385 bytes raw
1
const std = @import("std");
2
const xlib = @import("x11/xlib.zig");
3
const config_mod = @import("config/config.zig");
4
5
const padding: i32 = 32;
6
const line_spacing: i32 = 12;
7
const key_action_spacing: i32 = 32;
8
const border_width: i32 = 4;
9
const border_color: c_ulong = 0x7fccff;
10
const bg_color: c_ulong = 0x1a1a1a;
11
const fg_color: c_ulong = 0xffffff;
12
const key_bg_color: c_ulong = 0x2a2a2a;
13
14
const max_lines: usize = 12;
15
16
pub const Keybind_Overlay = struct {
17
    window: xlib.Window = 0,
18
    pixmap: xlib.Pixmap = 0,
19
    gc: xlib.GC = null,
20
    xft_draw: ?*xlib.XftDraw = null,
21
    font: ?*xlib.XftFont = null,
22
    font_height: i32 = 0,
23
    width: i32 = 0,
24
    height: i32 = 0,
25
    visible: bool = false,
26
    display: ?*xlib.Display = null,
27
    root: xlib.Window = 0,
28
    screen: c_int = 0,
29
30
    key_bufs: [max_lines][64]u8 = undefined,
31
    key_lens: [max_lines]usize = undefined,
32
    descs: [max_lines][]const u8 = undefined,
33
    line_count: usize = 0,
34
35
    pub fn init(display: *xlib.Display, screen: c_int, root: xlib.Window, font_name: []const u8, allocator: std.mem.Allocator) ?*Keybind_Overlay {
36
        const overlay = allocator.create(Keybind_Overlay) catch return null;
37
38
        const font_name_z = allocator.dupeZ(u8, font_name) catch {
39
            allocator.destroy(overlay);
40
            return null;
41
        };
42
        defer allocator.free(font_name_z);
43
44
        const font = xlib.XftFontOpenName(display, screen, font_name_z);
45
        if (font == null) {
46
            allocator.destroy(overlay);
47
            return null;
48
        }
49
50
        const font_height = font.*.ascent + font.*.descent;
51
52
        overlay.* = .{
53
            .display = display,
54
            .root = root,
55
            .font = font,
56
            .font_height = font_height,
57
            .screen = screen,
58
        };
59
60
        return overlay;
61
    }
62
63
    pub fn deinit(self: *Keybind_Overlay, allocator: std.mem.Allocator) void {
64
        if (self.display) |display| {
65
            self.destroy_window(display);
66
            if (self.font) |font| {
67
                xlib.XftFontClose(display, font);
68
            }
69
        }
70
        allocator.destroy(self);
71
    }
72
73
    fn destroy_window(self: *Keybind_Overlay, display: *xlib.Display) void {
74
        if (self.xft_draw) |xft_draw| {
75
            xlib.XftDrawDestroy(xft_draw);
76
            self.xft_draw = null;
77
        }
78
        if (self.gc) |gc| {
79
            _ = xlib.XFreeGC(display, gc);
80
            self.gc = null;
81
        }
82
        if (self.pixmap != 0) {
83
            _ = xlib.XFreePixmap(display, self.pixmap);
84
            self.pixmap = 0;
85
        }
86
        if (self.window != 0) {
87
            _ = xlib.c.XDestroyWindow(display, self.window);
88
            self.window = 0;
89
        }
90
    }
91
92
    pub fn toggle(self: *Keybind_Overlay, mon_x: i32, mon_y: i32, mon_w: i32, mon_h: i32) void {
93
        if (self.visible) {
94
            self.hide();
95
        } else {
96
            self.show(mon_x, mon_y, mon_w, mon_h);
97
        }
98
    }
99
100
    pub fn show(self: *Keybind_Overlay, mon_x: i32, mon_y: i32, mon_w: i32, mon_h: i32) void {
101
        const display = self.display orelse return;
102
        const cfg = config_mod.get_config() orelse return;
103
104
        self.collect_keybinds(cfg);
105
        if (self.line_count == 0) return;
106
107
        var max_key_width: i32 = 0;
108
        var max_desc_width: i32 = 0;
109
110
        for (0..self.line_count) |i| {
111
            const key_slice = self.key_bufs[i][0..self.key_lens[i]];
112
            const key_w = self.text_width(display, key_slice);
113
            const desc_w = self.text_width(display, self.descs[i]);
114
            if (key_w > max_key_width) max_key_width = key_w;
115
            if (desc_w > max_desc_width) max_desc_width = desc_w;
116
        }
117
118
        const title = "Keybindings";
119
        const title_width = self.text_width(display, title);
120
121
        const content_width = max_key_width + key_action_spacing + max_desc_width;
122
        const min_width = @max(title_width, content_width);
123
124
        self.width = min_width + padding * 2;
125
        const line_height = self.font_height + line_spacing;
126
        const title_height = self.font_height + 20;
127
        self.height = title_height + @as(i32, @intCast(self.line_count)) * line_height + padding * 2;
128
129
        self.destroy_window(display);
130
131
        const x: i32 = mon_x + @divTrunc(mon_w - self.width, 2);
132
        const y: i32 = mon_y + @divTrunc(mon_h - self.height, 2);
133
134
        const visual = xlib.XDefaultVisual(display, self.screen);
135
        const colormap = xlib.XDefaultColormap(display, self.screen);
136
        const depth = xlib.XDefaultDepth(display, self.screen);
137
138
        self.window = xlib.c.XCreateSimpleWindow(
139
            display,
140
            self.root,
141
            x,
142
            y,
143
            @intCast(self.width),
144
            @intCast(self.height),
145
            @intCast(border_width),
146
            border_color,
147
            bg_color,
148
        );
149
150
        var attrs: xlib.c.XSetWindowAttributes = undefined;
151
        attrs.override_redirect = xlib.True;
152
        attrs.event_mask = xlib.c.ExposureMask | xlib.c.KeyPressMask | xlib.c.ButtonPressMask;
153
        _ = xlib.c.XChangeWindowAttributes(display, self.window, xlib.c.CWOverrideRedirect | xlib.c.CWEventMask, &attrs);
154
155
        self.pixmap = xlib.XCreatePixmap(display, self.window, @intCast(self.width), @intCast(self.height), @intCast(depth));
156
        self.gc = xlib.XCreateGC(display, self.pixmap, 0, null);
157
        self.xft_draw = xlib.XftDrawCreate(display, self.pixmap, visual, colormap);
158
159
        _ = xlib.XMapWindow(display, self.window);
160
        _ = xlib.XRaiseWindow(display, self.window);
161
162
        self.draw(display, max_key_width, title);
163
164
        _ = xlib.XGrabKeyboard(display, self.window, xlib.True, xlib.GrabModeAsync, xlib.GrabModeAsync, xlib.CurrentTime);
165
166
        _ = xlib.XSync(display, xlib.False);
167
168
        self.visible = true;
169
    }
170
171
    pub fn hide(self: *Keybind_Overlay) void {
172
        if (!self.visible) return;
173
        if (self.display) |display| {
174
            _ = xlib.XUngrabKeyboard(display, xlib.CurrentTime);
175
            if (self.window != 0) {
176
                _ = xlib.c.XUnmapWindow(display, self.window);
177
            }
178
        }
179
        self.visible = false;
180
    }
181
182
    pub fn handle_key(self: *Keybind_Overlay, keysym: u64) bool {
183
        if (!self.visible) return false;
184
        if (keysym == 0xff1b or keysym == 'q' or keysym == 'Q') {
185
            self.hide();
186
            return true;
187
        }
188
        return false;
189
    }
190
191
    pub fn is_overlay_window(self: *Keybind_Overlay, win: xlib.Window) bool {
192
        return self.visible and self.window != 0 and self.window == win;
193
    }
194
195
    fn draw(self: *Keybind_Overlay, display: *xlib.Display, max_key_width: i32, title: []const u8) void {
196
        self.fill_rect(display, 0, 0, self.width, self.height, bg_color);
197
198
        const title_x = @divTrunc(self.width - self.text_width(display, title), 2);
199
        const title_y = padding + self.font.?.*.ascent;
200
        self.draw_text(display, title_x, title_y, title, fg_color);
201
202
        const line_height = self.font_height + line_spacing;
203
        var y = padding + self.font_height + 20 + self.font.?.*.ascent;
204
205
        for (0..self.line_count) |i| {
206
            const key_slice = self.key_bufs[i][0..self.key_lens[i]];
207
            const key_w = self.text_width(display, key_slice);
208
            self.fill_rect(display, padding - 4, y - self.font.?.*.ascent - 2, key_w + 8, self.font_height + 4, key_bg_color);
209
            self.draw_text(display, padding, y, key_slice, fg_color);
210
211
            const desc_x = padding + max_key_width + key_action_spacing;
212
            self.draw_text(display, desc_x, y, self.descs[i], fg_color);
213
214
            y += line_height;
215
        }
216
217
        _ = xlib.c.XCopyArea(display, self.pixmap, self.window, self.gc, 0, 0, @intCast(self.width), @intCast(self.height), 0, 0);
218
        _ = xlib.c.XFlush(display);
219
    }
220
221
    fn fill_rect(self: *Keybind_Overlay, display: *xlib.Display, x: i32, y: i32, w: i32, h: i32, color: c_ulong) void {
222
        _ = xlib.XSetForeground(display, self.gc, color);
223
        _ = xlib.XFillRectangle(display, self.pixmap, self.gc, x, y, @intCast(w), @intCast(h));
224
    }
225
226
    fn draw_text(self: *Keybind_Overlay, display: *xlib.Display, x: i32, y: i32, text: []const u8, color: c_ulong) void {
227
        if (self.xft_draw == null or self.font == null) return;
228
        if (text.len == 0) return;
229
230
        var xft_color: xlib.XftColor = undefined;
231
        var render_color: xlib.XRenderColor = undefined;
232
        render_color.red = @intCast((color >> 16 & 0xff) * 257);
233
        render_color.green = @intCast((color >> 8 & 0xff) * 257);
234
        render_color.blue = @intCast((color & 0xff) * 257);
235
        render_color.alpha = 0xffff;
236
237
        const visual = xlib.XDefaultVisual(display, self.screen);
238
        const colormap = xlib.XDefaultColormap(display, self.screen);
239
240
        _ = xlib.XftColorAllocValue(display, visual, colormap, &render_color, &xft_color);
241
        xlib.XftDrawStringUtf8(self.xft_draw, &xft_color, self.font, x, y, text.ptr, @intCast(text.len));
242
        xlib.XftColorFree(display, visual, colormap, &xft_color);
243
    }
244
245
    fn text_width(self: *Keybind_Overlay, display: *xlib.Display, text: []const u8) i32 {
246
        if (self.font == null or text.len == 0) return 0;
247
        var extents: xlib.XGlyphInfo = undefined;
248
        xlib.XftTextExtentsUtf8(display, self.font, text.ptr, @intCast(text.len), &extents);
249
        return extents.xOff;
250
    }
251
252
    fn collect_keybinds(self: *Keybind_Overlay, cfg: *config_mod.Config) void {
253
        const priority_actions = [_]config_mod.Action{
254
            .show_keybinds,
255
            .quit,
256
            .reload_config,
257
            .kill_client,
258
            .spawn_terminal,
259
            .toggle_fullscreen,
260
            .toggle_floating,
261
            .cycle_layout,
262
            .focus_next,
263
            .focus_prev,
264
            .view_tag,
265
            .move_to_tag,
266
        };
267
268
        self.line_count = 0;
269
270
        for (priority_actions) |action| {
271
            if (self.line_count >= max_lines) break;
272
273
            for (cfg.keybinds.items) |kb| {
274
                if (kb.action == action and kb.key_count > 0) {
275
                    self.format_key_to_buf(self.line_count, &kb.keys[0]);
276
                    self.descs[self.line_count] = action_desc(action);
277
                    self.line_count += 1;
278
                    break;
279
                }
280
            }
281
        }
282
    }
283
284
    fn format_key_to_buf(self: *Keybind_Overlay, idx: usize, key: *const config_mod.Key_Press) void {
285
        var len: usize = 0;
286
        var buf = &self.key_bufs[idx];
287
288
        if (key.mod_mask & (1 << 6) != 0) {
289
            const s = "Mod + ";
290
            @memcpy(buf[len .. len + s.len], s);
291
            len += s.len;
292
        }
293
        if (key.mod_mask & (1 << 0) != 0) {
294
            const s = "Shift + ";
295
            @memcpy(buf[len .. len + s.len], s);
296
            len += s.len;
297
        }
298
        if (key.mod_mask & (1 << 2) != 0) {
299
            const s = "Ctrl + ";
300
            @memcpy(buf[len .. len + s.len], s);
301
            len += s.len;
302
        }
303
304
        const key_name = keysym_to_name(key.keysym);
305
        if (len + key_name.len < buf.len) {
306
            @memcpy(buf[len .. len + key_name.len], key_name);
307
            len += key_name.len;
308
        }
309
310
        self.key_lens[idx] = len;
311
    }
312
313
    fn keysym_to_name(keysym: u64) []const u8 {
314
        const upper_letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
315
        const digits = "0123456789";
316
317
        return switch (keysym) {
318
            0xff0d => "Return",
319
            0x0020 => "Space",
320
            0xff1b => "Escape",
321
            0xff08 => "BackSpace",
322
            0xff09 => "Tab",
323
            0xffbe => "F1",
324
            0xffbf => "F2",
325
            0xffc0 => "F3",
326
            0xffc1 => "F4",
327
            0xffc2 => "F5",
328
            0xffc3 => "F6",
329
            0xffc4 => "F7",
330
            0xffc5 => "F8",
331
            0xffc6 => "F9",
332
            0xffc7 => "F10",
333
            0xffc8 => "F11",
334
            0xffc9 => "F12",
335
            0xff51 => "Left",
336
            0xff52 => "Up",
337
            0xff53 => "Right",
338
            0xff54 => "Down",
339
            0x002c => ",",
340
            0x002e => ".",
341
            0x002f => "/",
342
            'a'...'z' => |c| upper_letters[c - 'a' ..][0..1],
343
            'A'...'Z' => |c| upper_letters[c - 'A' ..][0..1],
344
            '0'...'9' => |c| digits[c - '0' ..][0..1],
345
            else => "?",
346
        };
347
    }
348
349
    fn action_desc(action: config_mod.Action) []const u8 {
350
        return switch (action) {
351
            .show_keybinds => "Show Keybinds",
352
            .quit => "Quit WM",
353
            .reload_config => "Reload Config",
354
            .restart => "Restart WM",
355
            .kill_client => "Close Window",
356
            .spawn_terminal => "Open Terminal",
357
            .spawn => "Launch Program",
358
            .toggle_fullscreen => "Toggle Fullscreen",
359
            .toggle_floating => "Toggle Floating",
360
            .toggle_gaps => "Toggle Gaps",
361
            .cycle_layout => "Cycle Layout",
362
            .set_layout => "Set Layout",
363
            .focus_next => "Focus Next",
364
            .focus_prev => "Focus Previous",
365
            .move_next => "Move Next",
366
            .move_prev => "Move Previous",
367
            .view_tag => "View Tag",
368
            .move_to_tag => "Move to Tag",
369
            .focus_monitor => "Focus Monitor",
370
            .send_to_monitor => "Send to Monitor",
371
            else => "Action",
372
        };
373
    }
374
};