commit 4b25403b117b17598441e5cef0992c3cbf6902f7
Author: Abhinav <abhinav.prsai@gmail.com>
AuthorDate: Wed Jul 2 23:49:04 2025 +0100
Commit: Abhinav <abhinav.prsai@gmail.com>
CommitDate: Wed Jul 2 23:49:04 2025 +0100
add modules support
---
src/defs.h | 30 +++--
src/sxbar.c | 411 ++++++++++++++++++++++++++++++++++++++++--------------------
2 files changed, 297 insertions(+), 144 deletions(-)
diff --git a/src/defs.h b/src/defs.h
index e2e3a75..804c845 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -1,6 +1,7 @@
#pragma once
#include <X11/Xlib.h>
+#include <time.h>
#define SXBAR_VERSION "sxbar ver. 1.0"
#define SXBAR_AUTHOR "(C) Abhinav Prasai 2025"
@@ -8,21 +9,30 @@
#define MAX_MONITORS 32
-typedef struct {
- Bool bottom_bar;
- Bool border;
- unsigned long border_colour;
- unsigned long background_colour;
- unsigned long foreground_colour;
+typedef struct Module {
+ char *name;
+ char *command;
+ int enabled;
+ int refresh_interval;
+ time_t last_update;
+ char *cached_output;
+} Module;
+
+typedef struct Config {
+ int bottom_bar;
int height;
int vertical_padding;
int horizontal_padding;
- int border_width;
int text_padding;
- int workspace_spacing;
+ int border;
+ int border_width;
+ unsigned long background_colour;
+ unsigned long foreground_colour;
+ unsigned long border_colour;
char *font;
- char *workspace_highlight_left;
- char *workspace_highlight_right;
+ Module *modules;
+ int module_count;
+ int max_modules;
} Config;
typedef void (*EventHandler)(XEvent *);
diff --git a/src/sxbar.c b/src/sxbar.c
index 16168cd..9f84b57 100644
--- a/src/sxbar.c
+++ b/src/sxbar.c
@@ -3,6 +3,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <time.h>
+#include <unistd.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
@@ -11,8 +13,11 @@
#include "defs.h"
+void cleanup_modules(void);
+void cleanup_resources(void);
void create_bars(void);
-void draw_bar_content(Window win, int monitor_index);
+static void draw_bar_into(Drawable draw, int monitor_index);
+static void redraw_monitor(int monitor_index);
int find_window_monitor(Window win);
int get_current_workspace(void);
char **get_workspace_name(int *count);
@@ -20,34 +25,73 @@ void hdl_dummy(XEvent *xev);
void hdl_expose(XEvent *xev);
void hdl_property(XEvent *xev);
void init_defaults(void);
+void init_modules(void);
unsigned long parse_col(const char *hex);
void run(void);
+char *run_command(const char *cmd);
void setup(void);
+void update_modules(void);
EventHandler evtable[LASTEvent];
XFontStruct *font;
Display *dpy;
Window root;
Window *wins = NULL;
-int nmonitors = 0;
XineramaScreenInfo *monitors = NULL;
GC gc;
-int scr;
Config config;
+Pixmap *buffers = NULL;
+int nmonitors = 0;
+int scr;
-void create_bars(void)
+void cleanup_modules(void)
{
- int xin_active = 0;
+ for (int i = 0; i < config.module_count; i++) {
+ free(config.modules[i].name);
+ free(config.modules[i].command);
+ free(config.modules[i].cached_output);
+ }
+ free(config.modules);
+}
+void cleanup_resources(void)
+{
+ if (buffers) {
+ for (int i = 0; i < nmonitors; i++) {
+ XFreePixmap(dpy, buffers[i]);
+ }
+ free(buffers);
+ }
+ if (wins) {
+ for (int i = 0; i < nmonitors; i++) {
+ XDestroyWindow(dpy, wins[i]);
+ }
+ free(wins);
+ }
+ if (monitors) {
+ XFree(monitors);
+ }
+ if (font) {
+ XFreeFont(dpy, font);
+ }
+ if (gc) {
+ XFreeGC(dpy, gc);
+ }
+ if (dpy) {
+ XCloseDisplay(dpy);
+ }
+}
+
+void create_bars(void)
+{
+ int xin = 0;
if (XineramaIsActive(dpy)) {
monitors = XineramaQueryScreens(dpy, &nmonitors);
- xin_active = 1;
+ xin = 1;
}
-
- /* fallback to single monitor if Xinerama is not available */
- if (!xin_active || nmonitors <= 0) {
+ if (!xin || nmonitors <= 0) {
nmonitors = 1;
- monitors = malloc(sizeof(XineramaScreenInfo));
+ monitors = malloc(sizeof *monitors);
monitors[0].screen_number = 0;
monitors[0].x_org = 0;
monitors[0].y_org = 0;
@@ -55,24 +99,17 @@ void create_bars(void)
monitors[0].height = DisplayHeight(dpy, scr);
}
- /* TODO: ADD SUPPORT FOR SELECTING MONITORS */
- wins = malloc(nmonitors * sizeof(Window));
+ wins = malloc(nmonitors * sizeof *wins);
+ buffers = malloc(nmonitors * sizeof *buffers);
for (int i = 0; i < nmonitors; i++) {
int bw = config.border ? config.border_width : 0;
- /* window content width (excluding border) */
- int w = monitors[i].width - (2 * config.horizontal_padding);
- /* padding from screen edge */
- int x = monitors[i].x_org + config.horizontal_padding;
+ int w = monitors[i].width - 2 * config.horizontal_padding;
int h = config.height;
- int y;
-
- if (config.bottom_bar) {
- y = monitors[i].y_org + monitors[i].height - h - config.vertical_padding - bw;
- }
- else {
- y = monitors[i].y_org + config.vertical_padding;
- }
+ int x = monitors[i].x_org + config.horizontal_padding;
+ int y = config.bottom_bar
+ ? monitors[i].y_org + monitors[i].height - h - config.vertical_padding - bw
+ : monitors[i].y_org + config.vertical_padding;
XSetWindowAttributes wa = {.background_pixel = config.background_colour,
.border_pixel = config.border_colour,
@@ -83,11 +120,8 @@ void create_bars(void)
CWBackPixel | CWBorderPixel | CWEventMask, &wa);
XStoreName(dpy, wins[i], "sxbar");
-
- XClassHint class_hint;
- class_hint.res_name = "sxbar";
- class_hint.res_class = "sxbar";
- XSetClassHint(dpy, wins[i], &class_hint);
+ XClassHint ch = {"sxbar", "sxbar"};
+ XSetClassHint(dpy, wins[i], &ch);
Atom A_WM_TYPE = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
Atom A_WM_TYPE_DOCK = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
@@ -96,21 +130,20 @@ void create_bars(void)
Atom A_STRUT = XInternAtom(dpy, "_NET_WM_STRUT_PARTIAL", False);
long strut[12] = {0};
-
if (config.bottom_bar) {
strut[3] = h + bw;
strut[10] = x;
- strut[11] = x + w + (2 * bw) - 1;
+ strut[11] = x + w + 2 * bw - 1;
}
else {
strut[2] = y + h + bw;
strut[8] = x;
- strut[9] = x + w + (2 * bw) - 1;
+ strut[9] = x + w + 2 * bw - 1;
}
-
XChangeProperty(dpy, wins[i], A_STRUT, XA_CARDINAL, 32, PropModeReplace,
(unsigned char *)strut, 12);
+ buffers[i] = XCreatePixmap(dpy, wins[i], w, h, DefaultDepth(dpy, scr));
XMapRaised(dpy, wins[i]);
}
@@ -118,91 +151,105 @@ void create_bars(void)
XSetForeground(dpy, gc, config.foreground_colour);
font = XLoadQueryFont(dpy, config.font);
if (!font) {
- errx(1, "could not load font");
+ errx(1, "could not load font %s", config.font);
}
XSetFont(dpy, gc, font->fid);
}
-void draw_bar_content(Window win, int monitor_index)
+static void draw_bar_into(Drawable draw, int monitor_index)
{
- XClearWindow(dpy, win);
+ int w = monitors[monitor_index].width - 2 * config.horizontal_padding;
+ int h = config.height;
+ /* clear */
+ XSetForeground(dpy, gc, config.background_colour);
+ XFillRectangle(dpy, draw, gc, 0, 0, w, h);
int current_ws = get_current_workspace();
int name_count = 0;
char **names = get_workspace_name(&name_count);
- unsigned int text_y = (config.height + font->ascent - font->descent) / 2;
- const int bg_padding = 5;
- const int workspace_spacing = 10;
+ unsigned text_y = (h + font->ascent - font->descent) / 2;
+ const int pad = 5, ws_sp = 10, mod_sp = 20;
+ unsigned cur_x = config.text_padding + pad;
+ /* workspaces */
if (names) {
- unsigned int *positions = malloc(name_count * sizeof(unsigned int));
- unsigned int *widths = malloc(name_count * sizeof(unsigned int));
- unsigned int text_x = config.text_padding + bg_padding;
-
- /* calculate consistent positions and widths */
- for (int i = 0; i < name_count; ++i) {
- char padded_label[64];
- snprintf(padded_label, sizeof(padded_label), " %s ", names[i]);
-
- int label_width = XTextWidth(font, padded_label, strlen(padded_label));
-
- positions[i] = text_x;
- widths[i] = label_width;
-
- text_x += label_width + workspace_spacing;
+ unsigned *pos = malloc(name_count * sizeof *pos);
+ unsigned *wd = malloc(name_count * sizeof *wd);
+ for (int i = 0; i < name_count; i++) {
+ char tmp[64];
+ snprintf(tmp, sizeof tmp, " %s ", names[i]);
+ wd[i] = XTextWidth(font, tmp, strlen(tmp));
+ pos[i] = cur_x;
+ cur_x += wd[i] + ws_sp;
}
-
- /* draw backgrounds and text */
- for (int i = 0; i < name_count; ++i) {
- char label[64];
- /* TODO: ADD ABILITY TO CHANGE WORKSPACE STRING */
- snprintf(label, sizeof(label), " %s ", names[i]);
-
+ for (int i = 0; i < name_count; i++) {
+ char tmp[64];
+ snprintf(tmp, sizeof tmp, " %s ", names[i]);
if (i == current_ws) {
- /* draw highlight background for active workspace */
- /* TODO: ADD OPTION TO CHANGE SPECIFIC HILIGHT COLOUR */
XSetForeground(dpy, gc, config.foreground_colour);
- XFillRectangle(dpy, win, gc, positions[i] - bg_padding,
- text_y - font->ascent - bg_padding, widths[i] + (2 * bg_padding),
- font->ascent + font->descent + (2 * bg_padding));
-
- /* TODO: ADD CUSTOM TEXT COLOURS */
+ XFillRectangle(dpy, draw, gc, pos[i] - pad, text_y - font->ascent - pad,
+ wd[i] + 2 * pad, font->ascent + font->descent + 2 * pad);
XSetForeground(dpy, gc, config.background_colour);
}
else {
XSetForeground(dpy, gc, config.foreground_colour);
}
-
- XDrawString(dpy, win, gc, positions[i], text_y, label, strlen(label));
+ XDrawString(dpy, draw, gc, pos[i], text_y, tmp, strlen(tmp));
free(names[i]);
}
-
- free(positions);
- free(widths);
free(names);
+ free(pos);
+ free(wd);
}
XSetForeground(dpy, gc, config.foreground_colour);
- /* use window content width for version string positioning */
- int version_width = XTextWidth(font, SXBAR_VERSION, strlen(SXBAR_VERSION));
- int window_width = monitors[monitor_index].width - (2 * config.horizontal_padding);
- int version_x = window_width - version_width - config.text_padding - bg_padding;
+ /* modules */
+ int total_mw = 0;
+ for (int i = 0; i < config.module_count; i++) {
+ if (!config.modules[i].enabled || !config.modules[i].cached_output) {
+ continue;
+ }
+ total_mw += XTextWidth(font, config.modules[i].cached_output,
+ strlen(config.modules[i].cached_output)) +
+ mod_sp;
+ }
+ int ver_w = XTextWidth(font, SXBAR_VERSION, strlen(SXBAR_VERSION));
+ int mx = w - total_mw - ver_w - 2 * config.text_padding - 2 * pad;
- XDrawString(dpy, win, gc, version_x, text_y, SXBAR_VERSION, strlen(SXBAR_VERSION));
+ for (int i = 0; i < config.module_count; i++) {
+ if (!config.modules[i].enabled || !config.modules[i].cached_output) {
+ continue;
+ }
+ char *out = config.modules[i].cached_output;
+ int tw = XTextWidth(font, out, strlen(out));
+ XDrawString(dpy, draw, gc, mx, text_y, out, strlen(out));
+ mx += tw + mod_sp;
+ }
+
+ /* version */
+ int vx = w - ver_w - config.text_padding - pad;
+ XDrawString(dpy, draw, gc, vx, text_y, SXBAR_VERSION, strlen(SXBAR_VERSION));
+}
+
+static void redraw_monitor(int i)
+{
+ int w = monitors[i].width - 2 * config.horizontal_padding;
+ int h = config.height;
+ draw_bar_into(buffers[i], i);
+ XCopyArea(dpy, buffers[i], wins[i], gc, 0, 0, w, h, 0, 0);
}
int get_current_workspace(void)
{
- Atom actual_type;
- int actual_format;
- unsigned long nitems, bytes_after;
+ Atom at = XInternAtom(dpy, "_NET_CURRENT_DESKTOP", False);
+ Atom ret_type;
+ int fmt;
+ unsigned long n, after;
unsigned char *data = NULL;
- Atom prop = XInternAtom(dpy, "_NET_CURRENT_DESKTOP", False);
-
- if (XGetWindowProperty(dpy, root, prop, 0, 1, False, XA_CARDINAL, &actual_type,
- &actual_format, &nitems, &bytes_after, &data) == Success &&
+ if (XGetWindowProperty(dpy, root, at, 0, 1, False, XA_CARDINAL, &ret_type, &fmt, &n, &after,
+ &data) == Success &&
data) {
int ws = *(unsigned long *)data;
XFree(data);
@@ -213,28 +260,25 @@ int get_current_workspace(void)
char **get_workspace_name(int *count)
{
- Atom actual_type;
- int actual_format;
- unsigned long nitems, bytes_after;
- unsigned char *data = NULL;
-
- Atom prop = XInternAtom(dpy, "_NET_DESKTOP_NAMES", False);
+ Atom at = XInternAtom(dpy, "_NET_DESKTOP_NAMES", False);
Atom utf8 = XInternAtom(dpy, "UTF8_STRING", False);
-
- if (XGetWindowProperty(dpy, root, prop, 0, (~0L), False, utf8, &actual_type, &actual_format,
- &nitems, &bytes_after, &data) == Success &&
+ Atom ret_type;
+ int fmt;
+ unsigned long n, after;
+ unsigned char *data = NULL;
+ if (XGetWindowProperty(dpy, root, at, 0, (~0L), False, utf8, &ret_type, &fmt, &n, &after,
+ &data) == Success &&
data) {
-
char **names = NULL;
- int n = 0;
+ int idx = 0;
char *p = (char *)data;
- while (n < MAX_MONITORS && p < (char *)data + nitems) {
- names = realloc(names, (n + 1) * sizeof(char *));
- names[n++] = strdup(p);
+ while (p < (char *)data + n && idx < MAX_MONITORS) {
+ names = realloc(names, (idx + 1) * sizeof *names);
+ names[idx++] = strdup(p);
p += strlen(p) + 1;
}
XFree(data);
- *count = n;
+ *count = idx;
return names;
}
*count = 0;
@@ -248,22 +292,19 @@ void hdl_dummy(XEvent *xev)
void hdl_expose(XEvent *xev)
{
- Window win = xev->xexpose.window;
- int monitor_index = find_window_monitor(win);
- draw_bar_content(win, monitor_index);
+ int idx = find_window_monitor(xev->xexpose.window);
+ redraw_monitor(idx);
}
void hdl_property(XEvent *xev)
{
if (xev->xproperty.atom == XInternAtom(dpy, "_NET_CURRENT_DESKTOP", False)) {
- /* redraw all bars */
for (int i = 0; i < nmonitors; i++) {
- draw_bar_content(wins[i], i);
+ redraw_monitor(i);
}
}
}
-
void init_defaults(void)
{
config.bottom_bar = True;
@@ -276,7 +317,8 @@ void init_defaults(void)
config.background_colour = parse_col("#000000");
config.foreground_colour = parse_col("#7abccd");
config.border_colour = parse_col("#005577");
- config.font = strdup("fixed");
+ config.font = strdup("Bm437 IBM VGA 8x16");
+ init_modules();
}
int find_window_monitor(Window win)
@@ -286,53 +328,152 @@ int find_window_monitor(Window win)
return i;
}
}
- return 0; /* fallback to first monitor */
+ return 0;
+}
+
+void init_modules(void)
+{
+ config.max_modules = 10;
+ config.modules = malloc(config.max_modules * sizeof(Module));
+ config.module_count = 0;
+
+ /* clock */
+ config.modules[config.module_count++] = (Module){.name = strdup("clock"),
+ .command = "date '+%H:%M:%S'",
+ .enabled = True,
+ .refresh_interval = 1,
+ .last_update = 0,
+ .cached_output = NULL};
+ /* date */
+ config.modules[config.module_count++] = (Module){.name = strdup("date"),
+ .command = "date '+%Y-%m-%d'",
+ .enabled = True,
+ .refresh_interval = 60,
+ .last_update = 0,
+ .cached_output = NULL};
+ /* battery */
+ config.modules[config.module_count++] =
+ (Module){.name = strdup("battery"),
+ .command = "cat /sys/class/power_supply/BAT0/capacity 2>/dev/null | sed "
+ "'s/$/%/' || echo 'N/A'",
+ .enabled = False,
+ .refresh_interval = 30,
+ .last_update = 0,
+ .cached_output = NULL};
+ /* volume */
+ config.modules[config.module_count++] =
+ (Module){.name = strdup("volume"),
+ .command = "amixer get Master | grep -o '[0-9]*%' | head -1 || echo 'N/A'",
+ .enabled = True,
+ .refresh_interval = 5,
+ .last_update = 0,
+ .cached_output = NULL};
+ /* cpu */
+ config.modules[config.module_count++] =
+ (Module){.name = strdup("cpu"),
+ .command = "top -bn1 | grep 'Cpu(s)' | sed 's/.*, *\\([0-9.]*\\)%* id.*/\\1/' "
+ "| awk '{print 100-$1\"%\"}'",
+ .enabled = True,
+ .refresh_interval = 3,
+ .last_update = 0,
+ .cached_output = NULL};
}
unsigned long parse_col(const char *hex)
{
XColor col;
- Colormap cmap = DefaultColormap(dpy, DefaultScreen(dpy));
-
- if (!XParseColor(dpy, cmap, hex, &col)) {
- fprintf(stderr, "sxwm: cannot parse color %s\n", hex);
- return WhitePixel(dpy, DefaultScreen(dpy));
- }
-
- if (!XAllocColor(dpy, cmap, &col)) {
- fprintf(stderr, "sxwm: cannot allocate color %s\n", hex);
- return WhitePixel(dpy, DefaultScreen(dpy));
+ Colormap cmap = DefaultColormap(dpy, scr);
+ if (!XParseColor(dpy, cmap, hex, &col) || !XAllocColor(dpy, cmap, &col)) {
+ fprintf(stderr, "sxbar: cannot parse/color %s\n", hex);
+ return WhitePixel(dpy, scr);
}
-
return col.pixel;
}
void run(void)
{
XEvent xev;
+ time_t last = 0;
- for (;;) {
- XNextEvent(dpy, &xev);
- evtable[xev.type](&xev);
+ while (True) {
+ while (XPending(dpy)) {
+ XNextEvent(dpy, &xev);
+ evtable[xev.type](&xev);
+ }
+ time_t now = time(NULL);
+ if (now - last >= 1) {
+ update_modules();
+ for (int i = 0; i < nmonitors; i++) {
+ redraw_monitor(i);
+ }
+ last = now;
+ }
+ struct timespec ts = {0, 100000000};
+ nanosleep(&ts, NULL);
+ }
+}
+
+char *run_command(const char *cmd)
+{
+ FILE *fp = popen(cmd, "r");
+ if (!fp) {
+ return strdup("N/A");
+ }
+
+ char buffer[1024];
+ char *res = NULL;
+ size_t len = 0;
+
+ while (fgets(buffer, sizeof buffer, fp)) {
+ size_t l = strlen(buffer);
+ if (buffer[l - 1] == '\n') {
+ buffer[--l] = '\0';
+ }
+ if (!res) {
+ res = malloc(l + 1);
+ strcpy(res, buffer);
+ len = l;
+ }
+ else {
+ res = realloc(res, len + l + 2);
+ strcat(res, " ");
+ strcat(res, buffer);
+ len += l + 1;
+ }
+ }
+ pclose(fp);
+ return res ? res : strdup("");
+}
+
+void update_modules(void)
+{
+ time_t now = time(NULL);
+ for (int i = 0; i < config.module_count; i++) {
+ Module *m = &config.modules[i];
+ if (!m->enabled) {
+ continue;
+ }
+ if (now - m->last_update >= m->refresh_interval) {
+ free(m->cached_output);
+ m->cached_output = run_command(m->command);
+ m->last_update = now;
+ }
}
}
void setup(void)
{
- if ((dpy = XOpenDisplay(NULL)) == 0) {
- errx(0, "can't open display. quitting...");
+ if (!(dpy = XOpenDisplay(NULL))) {
+ errx(1, "can't open display");
}
root = XDefaultRootWindow(dpy);
scr = DefaultScreen(dpy);
- /* init all events with a dummy */
- for (int i = 0; i < LASTEvent; ++i) {
+ for (int i = 0; i < LASTEvent; i++) {
evtable[i] = hdl_dummy;
}
-
evtable[Expose] = hdl_expose;
evtable[PropertyNotify] = hdl_property;
-
XSelectInput(dpy, root, PropertyChangeMask);
init_defaults();
@@ -342,15 +483,17 @@ void setup(void)
int main(int ac, char **av)
{
if (ac > 1) {
- if (strcmp(av[1], "-v") == 0 || strcmp(av[1], "--version") == 0) {
- errx(0, "%s\n%s\n%s", SXBAR_VERSION, SXBAR_AUTHOR, SXBAR_LICINFO);
- }
- else {
- errx(0, "usage:\n[-v || --version]: See the version of sxbar");
+ if (!strcmp(av[1], "-v") || !strcmp(av[1], "--version")) {
+ printf("%s\n%s\n%s\n", SXBAR_VERSION, SXBAR_AUTHOR, SXBAR_LICINFO);
+ return 0;
}
+ errx(1, "usage: sxbar [-v|--version]");
}
-
setup();
run();
+
+ /* TODO?: never reached */
+ cleanup_modules();
+ cleanup_resources();
return 0;
}