commit - a413da8b884f2883828d64a68564305f33065b77
commit + c2143b5760024c3e3855f08abcd00c6279d482b5
blob - ecfbc8c0d146d4a015d938ed753bcc5da73671d6
blob + f9f45a5721bc59e0f599af975047dd0ab1bc1571
--- bxbar.c
+++ bxbar.c
/*
* 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
+#!/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
+/*
+ * 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;
+}
+