oxwm

https://git.tonybtw.com/oxwm.git git://git.tonybtw.com/oxwm.git
12,159 bytes raw
1
const std = @import("std");
2
const xlib = @import("../x11/xlib.zig");
3
const monitor_mod = @import("../monitor.zig");
4
const client_mod = @import("../client.zig");
5
const blocks_mod = @import("blocks/blocks.zig");
6
const config_mod = @import("../config/config.zig");
7
const ColorScheme = config_mod.ColorScheme;
8
9
const Monitor = monitor_mod.Monitor;
10
const Block = blocks_mod.Block;
11
12
fn get_layout_symbol(layout_index: u32) []const u8 {
13
    const cfg = config_mod.get_config();
14
    if (cfg) |conf| {
15
        return switch (layout_index) {
16
            0 => conf.layout_tile_symbol,
17
            1 => conf.layout_monocle_symbol,
18
            2 => conf.layout_floating_symbol,
19
            3 => "[S]",
20
            4 => "[#]",
21
            else => "[?]",
22
        };
23
    }
24
    return switch (layout_index) {
25
        0 => "[]=",
26
        1 => "[M]",
27
        2 => "><>",
28
        3 => "[S]",
29
        4 => "[#]",
30
        else => "[?]",
31
    };
32
}
33
34
pub const Bar = struct {
35
    window: xlib.Window,
36
    pixmap: xlib.Pixmap,
37
    graphics_context: xlib.GC,
38
    xft_draw: ?*xlib.XftDraw,
39
    width: i32,
40
    height: i32,
41
    monitor: *Monitor,
42
43
    font: ?*xlib.XftFont,
44
    font_height: i32,
45
46
    scheme_normal: ColorScheme,
47
    scheme_selected: ColorScheme,
48
    scheme_occupied: ColorScheme,
49
    scheme_urgent: ColorScheme,
50
    hide_vacant_tags: bool,
51
52
    allocator: std.mem.Allocator,
53
    blocks: std.ArrayList(Block),
54
    needs_redraw: bool,
55
    next: ?*Bar,
56
57
    pub fn create(
58
        allocator: std.mem.Allocator,
59
        display: *xlib.Display,
60
        screen: c_int,
61
        monitor: *Monitor,
62
        config: config_mod.Config,
63
    ) ?*Bar {
64
        const bar = allocator.create(Bar) catch return null;
65
66
        const visual = xlib.XDefaultVisual(display, screen);
67
        const colormap = xlib.XDefaultColormap(display, screen);
68
        const depth = xlib.XDefaultDepth(display, screen);
69
        const root = xlib.XRootWindow(display, screen);
70
71
        const font_name_z = allocator.dupeZ(u8, config.font) catch return null;
72
        defer allocator.free(font_name_z);
73
74
        const font = xlib.XftFontOpenName(display, screen, font_name_z);
75
        if (font == null) {
76
            allocator.destroy(bar);
77
            return null;
78
        }
79
80
        const font_height = font.*.ascent + font.*.descent;
81
        const bar_height: i32 = @intCast(@as(i32, font_height) + 8);
82
83
        const window = xlib.c.XCreateSimpleWindow(
84
            display,
85
            root,
86
            monitor.mon_x,
87
            monitor.mon_y,
88
            @intCast(monitor.mon_w),
89
            @intCast(bar_height),
90
            0,
91
            0,
92
            0x1a1b26,
93
        );
94
95
        _ = xlib.c.XSetWindowAttributes{};
96
        var attributes: xlib.c.XSetWindowAttributes = undefined;
97
        attributes.override_redirect = xlib.True;
98
        attributes.event_mask = xlib.c.ExposureMask | xlib.c.ButtonPressMask;
99
        _ = xlib.c.XChangeWindowAttributes(display, window, xlib.c.CWOverrideRedirect | xlib.c.CWEventMask, &attributes);
100
101
        const pixmap = xlib.XCreatePixmap(
102
            display,
103
            window,
104
            @intCast(monitor.mon_w),
105
            @intCast(bar_height),
106
            @intCast(depth),
107
        );
108
109
        const graphics_context = xlib.XCreateGC(display, pixmap, 0, null);
110
111
        const xft_draw = xlib.XftDrawCreate(display, pixmap, visual, colormap);
112
113
        _ = xlib.XMapWindow(display, window);
114
115
        bar.* = Bar{
116
            .window = window,
117
            .pixmap = pixmap,
118
            .graphics_context = graphics_context,
119
            .xft_draw = xft_draw,
120
            .width = monitor.mon_w,
121
            .height = bar_height,
122
            .monitor = monitor,
123
            .font = font,
124
            .font_height = font_height,
125
            .scheme_normal = config.scheme_normal,
126
            .scheme_selected = config.scheme_selected,
127
            .scheme_occupied = config.scheme_occupied,
128
            .scheme_urgent = config.scheme_urgent,
129
            .hide_vacant_tags = config.hide_vacant_tags,
130
            .allocator = allocator,
131
            .blocks = .{},
132
            .needs_redraw = true,
133
            .next = null,
134
        };
135
136
        monitor.bar_win = window;
137
        monitor.win_y = monitor.mon_y + bar_height;
138
        monitor.win_h = monitor.mon_h - bar_height;
139
140
        return bar;
141
    }
142
143
    pub fn destroy(self: *Bar, allocator: std.mem.Allocator, display: *xlib.Display) void {
144
        if (self.xft_draw) |xft_draw| {
145
            xlib.XftDrawDestroy(xft_draw);
146
        }
147
        if (self.font) |font| {
148
            xlib.XftFontClose(display, font);
149
        }
150
        _ = xlib.XFreeGC(display, self.graphics_context);
151
        _ = xlib.XFreePixmap(display, self.pixmap);
152
        _ = xlib.c.XDestroyWindow(display, self.window);
153
        self.blocks.deinit(self.allocator);
154
        allocator.destroy(self);
155
    }
156
157
    pub fn add_block(self: *Bar, block: Block) void {
158
        self.blocks.append(self.allocator, block) catch {};
159
    }
160
161
    pub fn invalidate(self: *Bar) void {
162
        self.needs_redraw = true;
163
    }
164
165
    pub fn draw(self: *Bar, display: *xlib.Display, tags: []const []const u8) void {
166
        if (!self.needs_redraw) return;
167
168
        self.fill_rect(display, 0, 0, self.width, self.height, self.scheme_normal.background);
169
170
        var x_position: i32 = 0;
171
        const padding: i32 = 8;
172
        const monitor = self.monitor;
173
        const current_tags = monitor.tagset[monitor.sel_tags];
174
175
        for (tags, 0..) |tag, index| {
176
            const tag_mask: u32 = @as(u32, 1) << @intCast(index);
177
            const is_selected = (current_tags & tag_mask) != 0;
178
            const is_occupied = has_clients_on_tag(monitor, tag_mask);
179
180
            if (self.hide_vacant_tags and !is_occupied and !is_selected) continue;
181
182
            const scheme = if (is_selected) self.scheme_selected else if (is_occupied) self.scheme_occupied else self.scheme_normal;
183
184
            const tag_text_width = self.text_width(display, tag);
185
            const tag_width = tag_text_width + padding * 2;
186
187
            if (is_selected) {
188
                self.fill_rect(display, x_position, self.height - 3, tag_width, 3, scheme.border);
189
            }
190
191
            const text_y = @divTrunc(self.height + self.font_height, 2) - 4;
192
            self.draw_text(display, x_position + padding, text_y, tag, scheme.foreground);
193
194
            x_position += tag_width;
195
        }
196
197
        x_position += padding;
198
199
        const layout_symbol = get_layout_symbol(monitor.sel_lt);
200
        self.draw_text(display, x_position, @divTrunc(self.height + self.font_height, 2) - 4, layout_symbol, self.scheme_normal.foreground);
201
        x_position += self.text_width(display, layout_symbol) + padding;
202
203
        var block_x: i32 = self.width - padding;
204
        var block_index: usize = self.blocks.items.len;
205
        while (block_index > 0) {
206
            block_index -= 1;
207
            const block = &self.blocks.items[block_index];
208
            const content = block.get_content();
209
            const content_width = self.text_width(display, content);
210
            block_x -= content_width;
211
            self.draw_text(display, block_x, @divTrunc(self.height + self.font_height, 2) - 4, content, block.color());
212
            if (block.underline) {
213
                self.fill_rect(display, block_x, self.height - 2, content_width, 2, block.color());
214
            }
215
            block_x -= padding;
216
        }
217
218
        _ = xlib.XCopyArea(display, self.pixmap, self.window, self.graphics_context, 0, 0, @intCast(self.width), @intCast(self.height), 0, 0);
219
        _ = xlib.XSync(display, xlib.False);
220
221
        self.needs_redraw = false;
222
    }
223
224
    fn fill_rect(self: *Bar, display: *xlib.Display, x: i32, y: i32, width: i32, height: i32, color: c_ulong) void {
225
        _ = xlib.XSetForeground(display, self.graphics_context, color);
226
        _ = xlib.XFillRectangle(display, self.pixmap, self.graphics_context, x, y, @intCast(width), @intCast(height));
227
    }
228
229
    fn draw_text(self: *Bar, display: *xlib.Display, x: i32, y: i32, text: []const u8, color: c_ulong) void {
230
        if (self.xft_draw == null or self.font == null) return;
231
232
        var xft_color: xlib.XftColor = undefined;
233
        var render_color: xlib.XRenderColor = undefined;
234
        render_color.red = @intCast((color >> 16 & 0xff) * 257);
235
        render_color.green = @intCast((color >> 8 & 0xff) * 257);
236
        render_color.blue = @intCast((color & 0xff) * 257);
237
        render_color.alpha = 0xffff;
238
239
        const visual = xlib.XDefaultVisual(display, 0);
240
        const colormap = xlib.XDefaultColormap(display, 0);
241
242
        _ = xlib.XftColorAllocValue(display, visual, colormap, &render_color, &xft_color);
243
244
        xlib.XftDrawStringUtf8(self.xft_draw, &xft_color, self.font, x, y, text.ptr, @intCast(text.len));
245
246
        xlib.XftColorFree(display, visual, colormap, &xft_color);
247
    }
248
249
    fn text_width(self: *Bar, display: *xlib.Display, text: []const u8) i32 {
250
        if (self.font == null) return 0;
251
252
        var extents: xlib.XGlyphInfo = undefined;
253
        xlib.XftTextExtentsUtf8(display, self.font, text.ptr, @intCast(text.len), &extents);
254
        return extents.xOff;
255
    }
256
257
    pub fn handle_click(self: *Bar, click_x: i32, tags: []const []const u8) ?usize {
258
        var x_position: i32 = 0;
259
        const padding: i32 = 8;
260
        const display = xlib.c.XOpenDisplay(null) orelse return null;
261
        defer _ = xlib.XCloseDisplay(display);
262
263
        const monitor = self.monitor;
264
        const current_tags = monitor.tagset[monitor.sel_tags];
265
        for (tags, 0..) |tag, index| {
266
            const tag_mask = @as(u32, 1) << @intCast(index);
267
            const is_selected = (current_tags & tag_mask) != 0;
268
            const is_occupied = has_clients_on_tag(monitor, tag_mask);
269
270
            if (self.hide_vacant_tags and !is_occupied and !is_selected) continue;
271
272
            const tag_text_width = self.text_width(display, tag);
273
            const tag_width = tag_text_width + padding * 2;
274
275
            if (click_x >= x_position and click_x < x_position + tag_width) {
276
                return index;
277
            }
278
            x_position += tag_width;
279
        }
280
        return null;
281
    }
282
283
    pub fn update_blocks(self: *Bar) void {
284
        var changed = false;
285
        for (self.blocks.items) |*block| {
286
            if (block.update()) {
287
                changed = true;
288
            }
289
        }
290
        if (changed) {
291
            self.needs_redraw = true;
292
        }
293
    }
294
295
    pub fn clear_blocks(self: *Bar) void {
296
        self.blocks.clearRetainingCapacity();
297
    }
298
};
299
300
fn has_clients_on_tag(monitor: *Monitor, tag_mask: u32) bool {
301
    var current = monitor.clients;
302
    while (current) |client| {
303
        if ((client.tags & tag_mask) != 0) {
304
            return true;
305
        }
306
        current = client.next;
307
    }
308
    return false;
309
}
310
311
pub var bars: ?*Bar = null;
312
313
pub fn create_bars(allocator: std.mem.Allocator, display: *xlib.Display, screen: c_int) void {
314
    var current_monitor = monitor_mod.monitors;
315
    while (current_monitor) |monitor| {
316
        const bar = Bar.create(allocator, display, screen, monitor, "monospace:size=10");
317
        if (bar) |created_bar| {
318
            bars = created_bar;
319
        }
320
        current_monitor = monitor.next;
321
    }
322
}
323
324
pub fn draw_bars(display: *xlib.Display, tags: []const []const u8) void {
325
    var current_monitor = monitor_mod.monitors;
326
    while (current_monitor) |monitor| {
327
        _ = monitor;
328
        if (bars) |bar| {
329
            bar.draw(display, tags);
330
        }
331
        current_monitor = if (current_monitor) |m| m.next else null;
332
    }
333
}
334
335
pub fn invalidate_bars() void {
336
    var current = bars;
337
    while (current) |bar| {
338
        bar.invalidate();
339
        current = bar.next;
340
    }
341
}
342
343
pub fn destroy_bars(allocator: std.mem.Allocator, display: *xlib.Display) void {
344
    var current = bars;
345
    while (current) |bar| {
346
        const next = bar.next;
347
        bar.destroy(allocator, display);
348
        current = next;
349
    }
350
    bars = null;
351
}
352
353
pub fn window_to_bar(win: xlib.Window) ?*Bar {
354
    var current = bars;
355
    while (current) |bar| {
356
        if (bar.window == win) {
357
            return bar;
358
        }
359
        current = bar.next;
360
    }
361
    return null;
362
}