commit 7a9b8269022161cd688dd3b1ed5e7a641575af7f
Author: MrJensK <jens.se@icloud.com>
AuthorDate: Thu May 14 12:40:42 2026 +0200
Commit: MrJensK <jens.se@icloud.com>
CommitDate: Thu May 14 12:40:42 2026 +0200
Enhance module color support and update font handling in configuration
---
Makefile | 4 +-
default_sxbarc | 15 +++-
src/defs.h | 4 ++
src/parser.c | 18 +++++
src/sxbar.c | 218 ++++++++++++++++++++++++++++++++++++---------------------
5 files changed, 175 insertions(+), 84 deletions(-)
diff --git a/Makefile b/Makefile
index 2de826c..a44d8fc 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
CC ?= gcc
-CFLAGS ?= -std=c99 -Wall -Wextra -O3 -Isrc
-LDFLAGS ?= -lX11 -lXinerama
+CFLAGS ?= -std=c99 -Wall -Wextra -O3 -Isrc $(shell pkg-config --cflags xft 2>/dev/null || echo -I/usr/include/freetype2)
+LDFLAGS ?= -lX11 -lXinerama -lXft
PREFIX ?= /usr/local
BIN := sxbar
diff --git a/default_sxbarc b/default_sxbarc
index 4036cef..2a459ed 100644
--- a/default_sxbarc
+++ b/default_sxbarc
@@ -17,7 +17,11 @@ border_width : 1
background_colour : #000000
foreground_colour : #7abccd
border_colour : #005577
-font : fixed
+# Xft font name format: "FamilyName:size=N[:style=Bold]"
+# For Nerd Font symbols use your Nerd Font family name, e.g.:
+# font : JetBrainsMonoNerdFont:size=10
+# font : Hack Nerd Font:size=10
+font : monospace:size=8
# Built-in modules
# module : name : enabled : refresh_interval_seconds
@@ -38,6 +42,15 @@ module : cpu : false : 3
# custom : network : "~/.config/sxbar/scripts/network.sh" : 5
# custom : updates : "checkupdates | wc -l | tr -d ' '" : 300
+# Per-module text colour (overrides global foreground_colour for that module)
+# colour : module_name : #rrggbb
+#
+# Examples:
+# colour : clock : #50fa7b
+# colour : volume : #ff79c6
+# colour : battery : #ffb86c
+# colour : date : #8be9fd
+
# Click commands — run a command when a module is left-clicked
# Works for both built-in modules and custom modules
# click : module_name : "command"
diff --git a/src/defs.h b/src/defs.h
index 01d4168..b8e7947 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -1,6 +1,7 @@
#pragma once
#include <X11/Xlib.h>
+#include <X11/Xft/Xft.h>
#include <time.h>
#define SXBAR_VERSION "sxbar ver. 1.1"
@@ -15,6 +16,9 @@ typedef struct Module {
char *click_command;
char *scroll_up_command;
char *scroll_down_command;
+ char *colour;
+ XftColor xft_colour;
+ int has_colour;
int enabled;
int refresh_interval;
time_t last_update;
diff --git a/src/parser.c b/src/parser.c
index 0c56eea..ea25126 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -258,6 +258,24 @@ int parse_config(Config *cfg)
m->refresh_interval = interval;
m->last_update = 0;
m->cached_output = NULL;
+ } else if (!strcmp(key, "colour") || !strcmp(key, "color")) {
+ /* colour : module_name : #hex */
+ char *p1 = strchr(rest, ':');
+ if (!p1) {
+ fprintf(stderr, "sxbarc:%d: colour missing name and value\n", lineno);
+ continue;
+ }
+ *p1 = '\0';
+ char *name = strip(rest);
+ char *val = strip(p1 + 1);
+ strip_comment(val);
+ Module *m = find_module(cfg, name);
+ if (!m) {
+ fprintf(stderr, "sxbarc:%d: colour: unknown module '%s'\n", lineno, name);
+ continue;
+ }
+ free(m->colour);
+ m->colour = strdup(val);
} else if (!strcmp(key, "click") || !strcmp(key, "scroll_up") || !strcmp(key, "scroll_down")) {
/* click/scroll_up/scroll_down : module_name : "command" */
char *p1 = strchr(rest, ':');
diff --git a/src/sxbar.c b/src/sxbar.c
index 8831f00..248b369 100644
--- a/src/sxbar.c
+++ b/src/sxbar.c
@@ -37,7 +37,10 @@ void setup(void);
void update_modules(void);
EventHandler evtable[LASTEvent];
-XFontStruct *font;
+XftFont *font;
+XftDraw **xft_draws;
+XftColor xft_fg;
+XftColor xft_bg;
Display *dpy;
Window root;
Window *wins = NULL;
@@ -83,14 +86,52 @@ int workspace_has_window(int ws) {
return 0;
}
+static int text_width(const char *str)
+{
+ XGlyphInfo ext;
+ XftTextExtentsUtf8(dpy, font, (const FcChar8 *)str, strlen(str), &ext);
+ return ext.xOff;
+}
+
+static void pixel_to_xftcolor(unsigned long pixel, XftColor *out)
+{
+ XColor xc = {0};
+ xc.pixel = pixel;
+ XQueryColor(dpy, DefaultColormap(dpy, scr), &xc);
+ out->color.red = xc.red;
+ out->color.green = xc.green;
+ out->color.blue = xc.blue;
+ out->color.alpha = 0xffff;
+ out->pixel = pixel;
+}
+
+static void resolve_module_colours(void)
+{
+ Visual *vis = DefaultVisual(dpy, scr);
+ Colormap cmap = DefaultColormap(dpy, scr);
+ for (int i = 0; i < config.module_count; i++) {
+ if (config.modules[i].colour) {
+ XftColorAllocName(dpy, vis, cmap,
+ config.modules[i].colour,
+ &config.modules[i].xft_colour);
+ config.modules[i].has_colour = 1;
+ }
+ }
+}
+
void cleanup_modules(void)
{
+ Visual *vis = DefaultVisual(dpy, scr);
+ Colormap cmap = DefaultColormap(dpy, scr);
for (int i = 0; i < config.module_count; i++) {
free(config.modules[i].name);
free(config.modules[i].command);
free(config.modules[i].click_command);
free(config.modules[i].scroll_up_command);
free(config.modules[i].scroll_down_command);
+ free(config.modules[i].colour);
+ if (config.modules[i].has_colour)
+ XftColorFree(dpy, vis, cmap, &config.modules[i].xft_colour);
free(config.modules[i].cached_output);
}
free(config.modules);
@@ -113,13 +154,20 @@ void cleanup_resources(void)
if (monitors) {
XFree(monitors);
}
- if (font) {
- XFreeFont(dpy, font);
+ if (xft_draws) {
+ for (int i = 0; i < nmonitors; i++)
+ XftDrawDestroy(xft_draws[i]);
+ free(xft_draws);
}
- if (gc) {
+ if (font)
+ XftFontClose(dpy, font);
+ if (gc)
XFreeGC(dpy, gc);
- }
if (dpy) {
+ Visual *vis = DefaultVisual(dpy, scr);
+ Colormap cmap = DefaultColormap(dpy, scr);
+ XftColorFree(dpy, vis, cmap, &xft_fg);
+ XftColorFree(dpy, vis, cmap, &xft_bg);
XCloseDisplay(dpy);
}
}
@@ -189,19 +237,27 @@ void create_bars(void)
XMapRaised(dpy, wins[i]);
}
- gc = XCreateGC(dpy, wins[0], 0, NULL);
- XSetForeground(dpy, gc, config.foreground_colour);
- font = XLoadQueryFont(dpy, config.font);
- if (!font) {
+ gc = XCreateGC(dpy, wins[0], 0, NULL);
+ font = XftFontOpenName(dpy, scr, config.font);
+ if (!font)
errx(1, "could not load font %s", config.font);
- }
- XSetFont(dpy, gc, font->fid);
+
+ Visual *vis = DefaultVisual(dpy, scr);
+ Colormap cmap = DefaultColormap(dpy, scr);
+ pixel_to_xftcolor(config.foreground_colour, &xft_fg);
+ pixel_to_xftcolor(config.background_colour, &xft_bg);
+
+ xft_draws = malloc(nmonitors * sizeof *xft_draws);
+ for (int i = 0; i < nmonitors; i++)
+ xft_draws[i] = XftDrawCreate(dpy, buffers[i], vis, cmap);
}
static void draw_bar_into(Drawable draw, int monitor_index)
{
+ XftDraw *d = xft_draws[monitor_index];
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);
@@ -210,18 +266,18 @@ static void draw_bar_into(Drawable draw, int monitor_index)
int name_count = 0;
char **names = get_workspace_name(&name_count);
- unsigned text_y = (h + font->ascent - font->descent) / 2;
+ int 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;
+ int cur_x = config.text_padding + pad;
/* workspaces */
if (names) {
- unsigned *pos = malloc(name_count * sizeof *pos);
- unsigned *wd = malloc(name_count * sizeof *wd);
+ int *pos = malloc(name_count * sizeof *pos);
+ int *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));
+ wd[i] = text_width(tmp);
pos[i] = cur_x;
cur_x += wd[i] + ws_sp;
}
@@ -230,54 +286,57 @@ static void draw_bar_into(Drawable draw, int monitor_index)
snprintf(tmp, sizeof tmp, " %s ", names[i]);
if (i == current_ws) {
XSetForeground(dpy, gc, config.foreground_colour);
- 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);
+ XFillRectangle(dpy, draw, gc, pos[i] - pad,
+ text_y - font->ascent - pad,
+ wd[i] + 2 * pad,
+ font->ascent + font->descent + 2 * pad);
+ XftDrawStringUtf8(d, &xft_bg, font, pos[i], text_y,
+ (const FcChar8 *)tmp, strlen(tmp));
+ } else {
+ XftDrawStringUtf8(d, &xft_fg, font, pos[i], text_y,
+ (const FcChar8 *)tmp, strlen(tmp));
}
- else {
- XSetForeground(dpy, gc, config.foreground_colour);
- }
- XDrawString(dpy, draw, gc, pos[i], text_y, tmp, strlen(tmp));
-
- int max_boxes = 4;
- int box_size = 5;
- int box_spacing = 2;
- int win_count = 0;
+
+ int max_boxes = 4, box_size = 5, box_spacing = 2, win_count = 0;
{
- Atom at = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
- Atom ret_type;
- int fmt;
- unsigned long nclients, afterclients;
- unsigned char *clients_data = NULL;
- if (XGetWindowProperty(dpy, root, at, 0, (~0L), False, XA_WINDOW, &ret_type, &fmt, &nclients, &afterclients, &clients_data) == Success && clients_data) {
- Atom ws_atom = XInternAtom(dpy, "_NET_WM_DESKTOP", False);
- for (unsigned long j = 0; j < nclients; j++) {
- Window win = ((Window *)clients_data)[j];
- unsigned long ndesk, afterdesk;
- unsigned char *ws_data = NULL;
- if (XGetWindowProperty(dpy, win, ws_atom, 0, 1, False, XA_CARDINAL, &ret_type, &fmt, &ndesk, &afterdesk, &ws_data) == Success && ws_data) {
- unsigned long win_ws = *(unsigned long *)ws_data;
- XFree(ws_data);
- if ((int)win_ws == i && window_on_monitor(win, monitor_index))
- win_count++;
- }
- }
- XFree(clients_data);
- }
- }
- if (win_count > 0) {
- if (win_count > max_boxes) win_count = max_boxes;
- // Färg: svart om markerad, annars foreground
- unsigned long box_col = (i == current_ws) ? parse_col("#000000") : config.foreground_colour;
- XSetForeground(dpy, gc, box_col);
- for (int b = 0; b < win_count; b++) {
- int box_x = pos[i] - pad + 1 + b * (box_size + box_spacing); // 1 pixel från vänster kant
- int box_y = text_y - font->ascent - pad + 1; // 1 pixel från toppen
- XFillRectangle(dpy, draw, gc, box_x, box_y, box_size, box_size);
- }
- // Återställ foreground
- XSetForeground(dpy, gc, (i == current_ws) ? config.background_colour : config.foreground_colour);
- }
+ Atom at = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
+ Atom ret_type;
+ int fmt;
+ unsigned long nclients, afterclients;
+ unsigned char *clients_data = NULL;
+ if (XGetWindowProperty(dpy, root, at, 0, (~0L), False,
+ XA_WINDOW, &ret_type, &fmt,
+ &nclients, &afterclients,
+ &clients_data) == Success && clients_data) {
+ Atom ws_atom = XInternAtom(dpy, "_NET_WM_DESKTOP", False);
+ for (unsigned long j = 0; j < nclients; j++) {
+ Window win = ((Window *)clients_data)[j];
+ unsigned long ndesk, afterdesk;
+ unsigned char *ws_data = NULL;
+ if (XGetWindowProperty(dpy, win, ws_atom, 0, 1, False,
+ XA_CARDINAL, &ret_type, &fmt,
+ &ndesk, &afterdesk,
+ &ws_data) == Success && ws_data) {
+ unsigned long win_ws = *(unsigned long *)ws_data;
+ XFree(ws_data);
+ if ((int)win_ws == i && window_on_monitor(win, monitor_index))
+ win_count++;
+ }
+ }
+ XFree(clients_data);
+ }
+ }
+ if (win_count > 0) {
+ if (win_count > max_boxes) win_count = max_boxes;
+ unsigned long box_col = (i == current_ws)
+ ? parse_col("#000000") : config.foreground_colour;
+ XSetForeground(dpy, gc, box_col);
+ for (int b = 0; b < win_count; b++) {
+ int box_x = pos[i] - pad + 1 + b * (box_size + box_spacing);
+ int box_y = text_y - font->ascent - pad + 1;
+ XFillRectangle(dpy, draw, gc, box_x, box_y, box_size, box_size);
+ }
+ }
free(names[i]);
}
free(names);
@@ -285,35 +344,33 @@ static void draw_bar_into(Drawable draw, int monitor_index)
free(wd);
}
- XSetForeground(dpy, gc, config.foreground_colour);
-
/* modules */
int total_mw = 0;
for (int i = 0; i < config.module_count; i++) {
- if (!config.modules[i].enabled || !config.modules[i].cached_output) {
+ 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;
+ total_mw += text_width(config.modules[i].cached_output) + mod_sp;
}
- int ver_w = config.show_version ? XTextWidth(font, config.version_text, strlen(config.version_text)) : 0;
+ int ver_w = config.show_version ? text_width(config.version_text) : 0;
int mx = w - total_mw - ver_w - 2 * config.text_padding - 2 * pad;
for (int i = 0; i < config.module_count; i++) {
- if (!config.modules[i].enabled || !config.modules[i].cached_output) {
+ 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));
+ int tw = text_width(out);
+ XftColor *col = config.modules[i].has_colour
+ ? &config.modules[i].xft_colour : &xft_fg;
+ XftDrawStringUtf8(d, col, font, mx, text_y, (const FcChar8 *)out, strlen(out));
mx += tw + mod_sp;
}
/* version */
if (config.show_version) {
int vx = w - ver_w - config.text_padding - pad;
- XDrawString(dpy, draw, gc, vx, text_y, config.version_text, strlen(config.version_text));
+ XftDrawStringUtf8(d, &xft_fg, font, vx, text_y,
+ (const FcChar8 *)config.version_text,
+ strlen(config.version_text));
}
}
@@ -399,18 +456,16 @@ void hdl_button(XEvent *xev)
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;
+ total_mw += text_width(config.modules[i].cached_output) + mod_sp;
}
- int ver_w = config.show_version
- ? XTextWidth(font, config.version_text, strlen(config.version_text)) : 0;
+ int ver_w = config.show_version ? text_width(config.version_text) : 0;
int mx = w - total_mw - ver_w - 2 * config.text_padding - 2 * pad;
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));
+ int tw = text_width(out);
if (x_click >= mx && x_click < mx + tw + mod_sp) {
if (btn == Button1 && config.modules[i].click_command)
spawn(config.modules[i].click_command);
@@ -456,7 +511,7 @@ 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("monospace:size=10");
config.show_version = True;
config.version_text = strdup(SXBAR_VERSION);
init_modules();
@@ -621,6 +676,7 @@ void setup(void)
init_defaults();
parse_config(&config);
create_bars();
+ resolve_module_colours();
update_modules();
}