oxwm

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