oxwm

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