oxwm

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