Commit Diff


commit - a413da8b884f2883828d64a68564305f33065b77
commit + c2143b5760024c3e3855f08abcd00c6279d482b5
blob - ecfbc8c0d146d4a015d938ed753bcc5da73671d6
blob + f9f45a5721bc59e0f599af975047dd0ab1bc1571
--- bxbar.c
+++ bxbar.c
@@ -1,7 +1,6 @@
 /* 
  * bxbar
- *
- * A very basic X status bar, a.k.a. Brett's X bar.
+ * A very basic X status bar.
  * See LICENSE.md and README.md for details.
  */
 
blob - /dev/null
blob + 49a72351d78295e0ec728fad37516674622b94c6 (mode 644)
--- /dev/null
+++ bxbar-ctl.lua
@@ -0,0 +1,55 @@
+#!/usr/bin/env lua5.4
+-- bxbar-ctl
+
+-- Enforce Lua 5.4+
+if not _VERSION:match("5.4") then
+    io.stderr:write("Error: Lua 5.4 required (got " .. _VERSION .. ").\n")
+    os.exit(1)
+end
+
+local function get_x11_info()
+    local h = io.popen("bxinfo", "r")
+    if not h then return "?", "" end
+    local total_s   = h:read("*l")
+    local current_s = h:read("*l")
+    local occ_str   = h:read("*l")
+    local win_title = h:read("*l") or ""
+    h:close()
+
+    if not total_s or not current_s then return "?", "" end
+
+    local total   = tonumber(total_s)
+    local current = tonumber(current_s)
+    local occupied = {}
+    if occ_str then
+        for n in occ_str:gmatch("%d+") do
+            occupied[tonumber(n)] = true
+        end
+    end
+
+    local ws_str = ""
+    for i = 0, total - 1 do
+        if i == current then
+            ws_str = ws_str .. "[" .. (i + 1) .. "*]"
+        elseif occupied[i] then
+            ws_str = ws_str .. "[" .. (i + 1) .. "]"
+        end
+    end
+
+    ws_str = ws_str ~= "" and ws_str or "?"
+    return ws_str, win_title
+end
+
+while true do
+    local ws, title = get_x11_info()
+    local dt = os.date("%Y-%m-%d %a %H:%M")
+
+    -- Left: workspaces + active title | Right: datetime (battery later)
+    local left  = ws .. " " .. title
+    local right = dt
+
+    io.write(left .. " | " .. right .. "\n")
+    io.flush()
+    os.execute("sleep 2")
+end
+
blob - /dev/null
blob + 371bbfcc955ef87b04a186086283a40ff5c57f02 (mode 644)
--- /dev/null
+++ bxinfo.c
@@ -0,0 +1,218 @@
+/*
+ * bxinfo - A basic X info helper
+ * Queries EWMH atoms and prints workspace state to stdout.
+ * Designed to be called from bxbar-ctl or other scripts.
+ * See LICENSE.md and README.md for details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+static long get_cardinal(Display *dpy, Window win, Atom prop);
+static Window *get_window_list(Display *dpy, Window win, Atom prop,
+                               unsigned long *count);
+static char *get_window_name(Display *dpy, Window win);
+
+/* Read a single CARDINAL property from a window.
+ * Returns the value, or -1 on failure. */
+static long
+get_cardinal(Display *dpy, Window win, Atom prop)
+{
+    Atom type;
+    int format;
+    unsigned long nitems, bytes_after;
+    unsigned char *data = NULL;
+    long val = -1;
+
+    if (XGetWindowProperty(dpy, win, prop, 0, 1, False, XA_CARDINAL,
+            &type, &format, &nitems, &bytes_after, &data) == Success && data) {
+        if (type == XA_CARDINAL && nitems >= 1)
+            val = *(long *)data;
+        XFree(data);
+    }
+    return val;
+}
+
+/* Read a WINDOW list property from a window.
+ * Returns dynamically allocated array; caller must free.
+ * Sets *count to number of items. Returns NULL on failure. */
+static Window *
+get_window_list(Display *dpy, Window win, Atom prop, unsigned long *count)
+{
+    Atom type;
+    int format;
+    unsigned long nitems, bytes_after;
+    unsigned char *data = NULL;
+    Window *wins = NULL;
+
+    *count = 0;
+
+    if (XGetWindowProperty(dpy, win, prop, 0, 1024, False, XA_WINDOW,
+            &type, &format, &nitems, &bytes_after, &data) == Success && data) {
+        if (type == XA_WINDOW && nitems > 0) {
+            wins = malloc(nitems * sizeof(Window));
+            if (wins) {
+                memcpy(wins, data, nitems * sizeof(Window));
+                *count = nitems;
+            }
+        }
+        XFree(data);
+        return wins;
+    }
+    return NULL;
+}
+
+/* Read the name of a window.
+ * Tries _NET_WM_NAME (UTF8_STRING), falls back to WM_NAME (XA_STRING).
+ * Returns malloc'd string or NULL. Caller must free(). */
+static char *
+get_window_name(Display *dpy, Window win)
+{
+    Atom net_wm_name, utf8_string, type;
+    int format;
+    unsigned long nitems, bytes_after;
+    unsigned char *data = NULL;
+    char *name = NULL;
+
+    net_wm_name = XInternAtom(dpy, "_NET_WM_NAME", False);
+    utf8_string = XInternAtom(dpy, "UTF8_STRING", False);
+
+    /* Try _NET_WM_NAME first — covers UTF-8 titles like web pages */
+    if (XGetWindowProperty(dpy, win, net_wm_name, 0, 1024, False,
+            utf8_string, &type, &format, &nitems, &bytes_after,
+            &data) == Success && data) {
+        if (type == utf8_string && nitems > 0) {
+            name = malloc(nitems + 1);
+            if (name) {
+                memcpy(name, data, nitems);
+                name[nitems] = '\0';
+            }
+            XFree(data);
+            return name;
+        }
+        XFree(data);
+    }
+
+    /* Fallback to WM_NAME — ICCCM legacy, Latin-1 */
+    char *wm_name = NULL;
+    if (XFetchName(dpy, win, &wm_name) && wm_name) {
+        name = malloc(strlen(wm_name) + 1);
+        if (name)
+            strcpy(name, wm_name);
+        XFree(wm_name);
+        return name;
+    }
+
+    return NULL;
+}
+
+int
+main(void)
+{
+    Display *dpy;
+    Window root;
+    Atom net_current_desktop, net_number_of_desktops;
+    Atom net_client_list, net_wm_desktop;
+    Atom net_active_window;
+    Atom type;
+    int format;
+    unsigned long nitems, bytes_after;
+    unsigned char *data = NULL;
+    long ndesktops, current;
+    Window *clients;
+    unsigned long nclients, i;
+    int *occupied;
+    int d, first;
+
+    dpy = XOpenDisplay(NULL);
+    if (!dpy) {
+        fprintf(stderr, "bxinfo: cannot open display\n");
+        return 1;
+    }
+
+    root = DefaultRootWindow(dpy);
+
+    /* Intern atoms */
+    net_number_of_desktops = XInternAtom(dpy, "_NET_NUMBER_OF_DESKTOPS", False);
+    net_current_desktop    = XInternAtom(dpy, "_NET_CURRENT_DESKTOP", False);
+    net_client_list        = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
+    net_wm_desktop         = XInternAtom(dpy, "_NET_WM_DESKTOP", False);
+    net_active_window     = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
+
+    /* Read total and current desktop */
+    ndesktops = get_cardinal(dpy, root, net_number_of_desktops);
+    current   = get_cardinal(dpy, root, net_current_desktop);
+
+    if (ndesktops < 1 || current < 0) {
+        fprintf(stderr, "bxinfo: cannot read desktop atoms\n");
+        XCloseDisplay(dpy);
+        return 1;
+    }
+
+    /* Track which desktops have at least one client */
+    occupied = calloc(ndesktops, sizeof(int));
+    if (!occupied) {
+        XCloseDisplay(dpy);
+        return 1;
+    }
+
+    clients = get_window_list(dpy, root, net_client_list, &nclients);
+    if (clients) {
+        for (i = 0; i < nclients; i++) {
+            long ws = get_cardinal(dpy, clients[i], net_wm_desktop);
+            if (ws >= 0 && ws < ndesktops)
+                occupied[ws] = 1;
+        }
+        free(clients);
+    }
+
+    /* Output three lines:
+     *   line 1: total number of desktops
+     *   line 2: current desktop (0-indexed)
+     *   line 3: occupied desktops, comma-separated (0-indexed)
+     */
+    printf("%ld\n", ndesktops);
+    printf("%ld\n", current);
+
+    first = 1;
+    for (d = 0; d < ndesktops; d++) {
+        if (occupied[d]) {
+            if (!first) putchar(',');
+            printf("%d", d);
+            first = 0;
+        }
+    }
+    putchar('\n');
+
+    /* Output active window title (line 4) */
+    if (XGetWindowProperty(dpy, root, net_active_window, 0, 1, False,
+            XA_WINDOW, &type, &format, &nitems, &bytes_after,
+            &data) == Success && data) {
+        Window active = None;
+        if (type == XA_WINDOW && nitems >= 1)
+            active = *(Window *)data;
+        XFree(data);
+
+        if (active != None) {
+            char *name = get_window_name(dpy, active);
+            if (name) {
+                printf("%s\n", name);
+                free(name);
+            } else {
+                printf("\n");
+            }
+        } else {
+            printf("\n");
+        }
+    } else {
+        printf("\n");
+    }
+
+    free(occupied);
+    XCloseDisplay(dpy);
+    return 0;
+}
+