oxwm

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