wm

[root]/ src / bar.zig

7.9KB

raw
const std = @import("std");
const c = @import("c.zig").c;
const config = @import("config.zig");
const client_mod = @import("client.zig");
const WM = client_mod.WM;
const Client = client_mod.Client;
const util = @import("util.zig");

fn allocColor(display: ?*c.Display, visual: ?*c.Visual, cmap: c.Colormap, name: [:0]const u8) !c.XftColor {
    var color: c.XftColor = undefined;
    if (c.XftColorAllocName(display, visual, cmap, name, &color) == 0)
        return error.ColorAllocFailed;
    return color;
}

fn fontHeight(fonts: []?*c.XftFont) i32 {
    for (fonts) |f| {
        if (f) |font| return font.ascent + font.descent;
    }
    return config.bar_height;
}

fn fontAscent(fonts: []?*c.XftFont) i32 {
    for (fonts) |f| {
        if (f) |font| return font.ascent;
    }
    return 0;
}

fn textWidth(dpy: ?*c.Display, fonts: []?*c.XftFont, text: []const u8) i32 {
    for (fonts) |f| {
        if (f == null) continue;
        var ext: c.XGlyphInfo = undefined;
        c.XftTextExtents8(dpy, f.?, text.ptr, @intCast(text.len), &ext);
        return @intCast(ext.xOff);
    }
    return @intCast(text.len * 8);
}

fn drawChar(dpy: ?*c.Display, xft_draw: ?*c.XftDraw, fonts: []?*c.XftFont, color: *c.XftColor, x: i32, y: i32, ch: u8) i32 {
    const rune: c.FcChar32 = ch;
    for (fonts) |f| {
        if (f == null) continue;
        const glyph = c.XftCharIndex(dpy, f.?, rune);
        if (glyph == 0) continue;
        var ext: c.XGlyphInfo = undefined;
        c.XftGlyphExtents(dpy, f.?, &glyph, 1, &ext);
        c.XftDrawGlyphs(xft_draw, color, f.?, x, y, &glyph, 1);
        return @intCast(ext.xOff);
    }
    return 8;
}

fn drawText(dpy: ?*c.Display, xft_draw: ?*c.XftDraw, fonts: []?*c.XftFont, color: *c.XftColor, x: i32, y: i32, text: []const u8) i32 {
    var cx = x;
    for (text) |ch| {
        cx += drawChar(dpy, xft_draw, fonts, color, cx, y, ch);
    }
    return cx;
}

pub fn create(wm: *WM) !void {
    const d = wm.display;
    const s = wm.screen;
    const root = wm.root;
    const visual = wm.visual;
    const cmap = wm.colormap;

    for (&config.font_names, 0..) |name, i| {
        wm.bar.fonts[i] = c.XftFontOpenName(d, s, name);
        if (wm.bar.fonts[i] == null)
            util.die("cannot open font: {s}\n", .{name});
    }
    wm.bar.colors.bg = allocColor(d, visual, cmap, config.col.bar_bg) catch
        util.die("cannot allocate color: {s}\n", .{config.col.bar_bg});
    wm.bar.colors.fg = allocColor(d, visual, cmap, config.col.bar_fg) catch
        util.die("cannot allocate color: {s}\n", .{config.col.bar_fg});
    wm.bar.colors.sel_bg = allocColor(d, visual, cmap, config.col.bar_sel_bg) catch
        util.die("cannot allocate color: {s}\n", .{config.col.bar_sel_bg});
    wm.bar.colors.min_fg = allocColor(d, visual, cmap, config.col.bar_min_fg) catch
        util.die("cannot allocate color: {s}\n", .{config.col.bar_min_fg});
    wm.bar.colors.urg_fg = allocColor(d, visual, cmap, config.col.bar_urg_fg) catch
        util.die("cannot allocate color: {s}\n", .{config.col.bar_urg_fg});
    wm.bar.colors.border_focused = allocColor(d, visual, cmap, config.col.border_focused) catch
        util.die("cannot allocate color: {s}\n", .{config.col.border_focused});
    wm.bar.colors.border_unfocused = allocColor(d, visual, cmap, config.col.border_unfocused) catch
        util.die("cannot allocate color: {s}\n", .{config.col.border_unfocused});

    var wa = std.mem.zeroes(c.XSetWindowAttributes);
    wa.override_redirect = 1;
    wa.background_pixmap = c.ParentRelative;
    wa.event_mask = c.ExposureMask | c.ButtonPressMask;

    wm.bar.window = c.XCreateWindow(d, root, 0, 0, wm.sw, config.bar_height, 0,
        c.CopyFromParent, c.InputOutput, visual,
        c.CWOverrideRedirect | c.CWBackPixmap | c.CWEventMask, &wa);

    _ = c.XChangeProperty(d, wm.bar.window, wm.atoms.net_wm_window_type,
        c.XA_ATOM, 32, c.PropModeReplace,
        @ptrCast(@constCast(&wm.atoms.net_wm_window_type_dock)), 1);
    _ = c.XMapWindow(d, wm.bar.window);

    wm.bar.pixmap = c.XCreatePixmap(d, wm.bar.window, wm.sw, config.bar_height,
        @intCast(c.DefaultDepth(d, s)));
    wm.bar.draw = c.XftDrawCreate(d, wm.bar.pixmap, visual, cmap);
    wm.bar.button_count = 0;
}

pub fn draw(wm: *WM) void {
    const bar = &wm.bar;
    const d = wm.display;
    const fonts = &bar.fonts;
    const bh: i32 = @intCast(config.bar_height);
    const asc = fontAscent(fonts) + @divTrunc(bh - fontHeight(fonts), 2);

    _ = c.XftDrawRect(bar.draw, &bar.colors.bg, 0, 0, @intCast(wm.sw), bh);

    var x: i32 = 0;
    var btn_idx: usize = 0;
    var it = wm.clients;
    while (it) |cl| : (it = cl.next) {
        if (btn_idx >= bar.buttons.len) break;

        const title = if (cl.title.len > 0) cl.title else "???";
        var tw = textWidth(d, fonts, title) + 12;
        if (x + tw > @as(i32, @intCast(wm.sw))) tw = @as(i32, @intCast(wm.sw)) - x;

        if (wm.focused == cl) {
            _ = c.XftDrawRect(bar.draw, &bar.colors.sel_bg, x, 0, @intCast(tw), @intCast(bh));
        }

        const fg_color: *c.XftColor = if (cl.flags.minimized)
            &bar.colors.min_fg
        else if (cl.flags.urgent)
            &bar.colors.urg_fg
        else
            &bar.colors.fg;

        const draw_w = @max(tw - 12, 0);
        if (draw_w > 0) {
            var end: usize = 0;
            while (end < title.len) : (end += 1) {
                if (textWidth(d, fonts, title[0 .. end + 1]) > draw_w) break;
            }
            if (end > 0)
                _ = drawText(d, bar.draw, fonts, fg_color, x + 6, asc, title[0..end]);
        }

        bar.buttons[btn_idx] = .{ .x = x, .w = @intCast(tw), .win = cl.window };
        btn_idx += 1;
        x += tw;
    }
    bar.button_count = btn_idx;

    var status_buf: [256]u8 = undefined;
    const status = readStatus(wm, &status_buf);
    if (status.len > 0) {
        const sw = textWidth(d, fonts, status);
        const sx = @as(i32, @intCast(wm.sw)) - sw - 6;
        if (sx > x) {
            _ = drawText(d, bar.draw, fonts, &bar.colors.fg, sx, asc, status);
        }
    }

    _ = c.XCopyArea(d, bar.pixmap, bar.window,
        c.DefaultGC(d, wm.screen), 0, 0, @intCast(wm.sw), bh, 0, 0);
    _ = c.XRaiseWindow(d, wm.bar.window);
}

fn readPropText(wm: *WM, prop_atom: c.Atom, buf: *[256]u8) []u8 {
    var actual_type: c.Atom = 0;
    var actual_format: c_int = 0;
    var nitems: c_ulong = 0;
    var bytes_after: c_ulong = 0;
    var prop: [*c]u8 = null;
    if (c.XGetWindowProperty(wm.display, wm.root, prop_atom,
        0, 1024, 0, c.AnyPropertyType, &actual_type, &actual_format,
        &nitems, &bytes_after, &prop) == c.Success and prop != null)
    {
        defer _ = c.XFree(prop);
        if (actual_format != 8 or nitems == 0) return &.{};
        const len = @min(nitems, buf.len - 1);
        @memcpy(buf[0..len], prop[0..len]);
        buf[len] = 0;
        return buf[0..len];
    }
    return &.{};
}

fn readStatus(wm: *WM, buf: *[256]u8) []u8 {
    const utf8 = readPropText(wm, wm.atoms.net_wm_name, buf);
    if (utf8.len > 0) return utf8;
    return readPropText(wm, c.XA_WM_NAME, buf);
}

pub fn destroy(wm: *WM) void {
    if (wm.bar.draw) |d| c.XftDrawDestroy(d);
    if (wm.bar.pixmap != 0) {
        _ = c.XFreePixmap(wm.display, wm.bar.pixmap);
    }
    if (wm.bar.window != 0) {
        _ = c.XDestroyWindow(wm.display, wm.bar.window);
    }
    for (&wm.bar.fonts) |f| {
        if (f) |font| c.XftFontClose(wm.display, font);
    }
    c.XftColorFree(wm.display, wm.visual, wm.colormap, &wm.bar.colors.bg);
    c.XftColorFree(wm.display, wm.visual, wm.colormap, &wm.bar.colors.fg);
    c.XftColorFree(wm.display, wm.visual, wm.colormap, &wm.bar.colors.sel_bg);
    c.XftColorFree(wm.display, wm.visual, wm.colormap, &wm.bar.colors.min_fg);
    c.XftColorFree(wm.display, wm.visual, wm.colormap, &wm.bar.colors.urg_fg);
    c.XftColorFree(wm.display, wm.visual, wm.colormap, &wm.bar.colors.border_focused);
    c.XftColorFree(wm.display, wm.visual, wm.colormap, &wm.bar.colors.border_unfocused);
}