Skip to content

Instantly share code, notes, and snippets.

@HalanoSiblee
Last active September 3, 2026 10:25
Show Gist options
  • Select an option

  • Save HalanoSiblee/7b205fa34add4cdefb8a4eb19cbf815f to your computer and use it in GitHub Desktop.

Select an option

Save HalanoSiblee/7b205fa34add4cdefb8a4eb19cbf815f to your computer and use it in GitHub Desktop.
X11/xcb volume bar osd helper for window managers
/*
g++ -std=c++20 -O3 -march=native -flto \
-fomit-frame-pointer -fno-plt \
-ffunction-sections -fdata-sections \
-Wl,--gc-sections -Wl,--as-needed -s \
-DNDEBUG \
mothxcbvol.cpp -lxcb -o mothxcbvol
*/
/*
MY i3 binds
bindsym XF86AudioRaiseVolume exec --no-startup-id amixer set Master 10%+ && mothxcbvol
bindsym XF86AudioLowerVolume exec --no-startup-id amixer set Master 10%- && mothxcbvol
*/
#include <xcb/xcb.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <sys/file.h>
#include <unistd.h>
#include <algorithm>
#include <array>
#include <charconv>
#include <chrono>
#include <cstring>
#include <memory>
#include <string>
#include <string_view>
using namespace std::chrono_literals;
using Clock = std::chrono::steady_clock;
constexpr int BAR_HEIGHT = 28;
constexpr int MARGIN_BOT = 80;
constexpr int SNAP_RADIUS = 2;
constexpr auto TIMEOUT_DUR = 3s;
constexpr auto DEBOUNCE_DUR = 500ms;
enum class Backend { Wpctl, Pactl, Amixer };
struct FileLock {
int fd = -1;
FileLock() {
std::string path;
if (const char* xdg = std::getenv("XDG_RUNTIME_DIR"); xdg && *xdg)
path = std::string(xdg) + "/mothxcbvol.lock";
else
path = "/tmp/mothxcbvol" + std::to_string(::getuid()) + ".lock";
fd = ::open(path.c_str(), O_CREAT | O_RDWR, 0600);
if (fd >= 0) {
::fcntl(fd, F_SETFD, FD_CLOEXEC);
if (::flock(fd, LOCK_EX | LOCK_NB) < 0) {
::close(fd);
fd = -1;
}
}
}
~FileLock() { if (fd >= 0) ::close(fd); }
explicit operator bool() const noexcept { return fd >= 0; }
};
struct XcbContext {
xcb_connection_t* conn = nullptr;
const xcb_setup_t* setup = nullptr;
xcb_screen_t* screen = nullptr;
xcb_get_keyboard_mapping_reply_t* km_reply = nullptr;
xcb_font_t font = 0;
xcb_window_t win = 0;
XcbContext() {
conn = xcb_connect(nullptr, nullptr);
if (!xcb_connection_has_error(conn)) {
setup = xcb_get_setup(conn);
screen = xcb_setup_roots_iterator(setup).data;
auto cookie = xcb_get_keyboard_mapping(conn, setup->min_keycode,
setup->max_keycode - setup->min_keycode + 1);
km_reply = xcb_get_keyboard_mapping_reply(conn, cookie, nullptr);
}
}
~XcbContext() {
if (!conn) return;
xcb_ungrab_keyboard(conn, XCB_CURRENT_TIME);
if (font) xcb_close_font(conn, font);
if (win) xcb_destroy_window(conn, win);
if (km_reply) std::free(km_reply);
xcb_disconnect(conn);
}
bool valid() const noexcept { return conn && !xcb_connection_has_error(conn); }
};
static Backend detect_backend() noexcept {
if (::system("command -v wpctl >/dev/null 2>&1 && wpctl get-volume @DEFAULT_AUDIO_SINK@ >/dev/null 2>&1") == 0)
return Backend::Wpctl;
if (::system("command -v pactl >/dev/null 2>&1 && pactl get-sink-volume @DEFAULT_SINK@ >/dev/null 2>&1") == 0)
return Backend::Pactl;
return Backend::Amixer;
}
static int get_volume(Backend b) {
const char* cmd = nullptr;
switch (b) {
case Backend::Wpctl: cmd = "wpctl get-volume @DEFAULT_AUDIO_SINK@ 2>/dev/null | awk '{print int($2*100)}'"; break;
case Backend::Pactl: cmd = "pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | grep -o '[0-9]\\+%' | head -n1 | tr -d '%'"; break;
case Backend::Amixer: cmd = "amixer get Master 2>/dev/null | grep -o '[0-9]\\+%' | head -n1 | tr -d '%'"; break;
}
std::unique_ptr<FILE, decltype(&::pclose)> pipe(::popen(cmd, "r"), &::pclose);
int v = 50;
if (pipe && std::fscanf(pipe.get(), "%d", &v) != 1) v = 50;
return std::clamp(v, 0, 100);
}
static void set_volume_async(Backend b, int v) {
if (pid_t pid = ::fork(); pid == 0) {
::close(0); ::close(1); ::close(2);
char buf[32];
if (b == Backend::Wpctl) {
std::snprintf(buf, sizeof(buf), "%.2f", v / 100.0f);
::execlp("wpctl", "wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", buf, nullptr);
} else if (b == Backend::Pactl) {
std::snprintf(buf, sizeof(buf), "%d%%", v);
::execlp("pactl", "pactl", "set-sink-volume", "@DEFAULT_SINK@", buf, nullptr);
} else {
std::snprintf(buf, sizeof(buf), "%d%%", v);
::execlp("amixer", "amixer", "set", "Master", buf, nullptr);
}
::_exit(0);
}
}
static uint32_t parse_color(std::string_view sv, uint32_t fallback) noexcept {
if (sv.starts_with('#')) sv.remove_prefix(1);
else if (sv.starts_with("0x") || sv.starts_with("0X")) sv.remove_prefix(2);
uint32_t val = 0;
auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), val, 16);
return (ec == std::errc{}) ? val : fallback;
}
static constexpr int apply_magnet(int raw) noexcept {
int nearest = ((raw + 5) / 10) * 10;
return (std::abs(raw - nearest) <= SNAP_RADIUS) ? nearest : raw;
}
static void draw_text(xcb_connection_t* c, xcb_drawable_t d, xcb_gcontext_t gc,
int16_t x, int16_t y, std::string_view str) {
std::array<uint8_t, 258> buf;
buf[0] = static_cast<uint8_t>(str.size());
buf[1] = 0;
std::memcpy(buf.data() + 2, str.data(), str.size());
xcb_poly_text_8(c, d, gc, x, y, static_cast<uint32_t>(str.size() + 2), buf.data());
}
static void draw(xcb_connection_t* c, xcb_window_t win,
xcb_gcontext_t gc_bg, xcb_gcontext_t gc_fg,
xcb_gcontext_t gc_txt_fill, xcb_gcontext_t gc_txt_empty,
int w, int vol) {
xcb_rectangle_t bg = {0, 0, static_cast<uint16_t>(w), BAR_HEIGHT};
xcb_poly_fill_rectangle(c, win, gc_bg, 1, &bg);
int fill_w = (vol * w) / 100;
if (fill_w > 0) {
xcb_rectangle_t fg = {0, 0, static_cast<uint16_t>(fill_w), BAR_HEIGHT};
xcb_poly_fill_rectangle(c, win, gc_fg, 1, &fg);
}
xcb_rectangle_t clip_fill = { 0, 0, static_cast<uint16_t>(fill_w), BAR_HEIGHT };
xcb_rectangle_t clip_empty = { static_cast<int16_t>(fill_w), 0, static_cast<uint16_t>(w - fill_w), BAR_HEIGHT };
xcb_set_clip_rectangles(c, XCB_CLIP_ORDERING_UNSORTED, gc_txt_fill, 0, 0, 1, &clip_fill);
xcb_set_clip_rectangles(c, XCB_CLIP_ORDERING_UNSORTED, gc_txt_empty, 0, 0, 1, &clip_empty);
for (int i = 0; i <= 100; i += 10) {
int tx = (i * w) / 100;
if (i > 0 && i < 100) {
int th = (i == 50) ? 7 : 4;
xcb_segment_t seg = { static_cast<int16_t>(tx), 0, static_cast<int16_t>(tx), static_cast<int16_t>(th) };
xcb_poly_segment(c, win, gc_txt_fill, 1, &seg);
xcb_poly_segment(c, win, gc_txt_empty, 1, &seg);
}
std::array<char, 8> num;
auto [ptr, _] = std::to_chars(num.data(), num.data() + num.size(), i);
std::string_view sv(num.data(), ptr - num.data());
int tw = static_cast<int>(sv.size()) * 6;
int text_x = tx - tw / 2;
text_x = std::clamp(text_x, 4, w - 4 - tw);
draw_text(c, win, gc_txt_fill, text_x, 19, sv);
draw_text(c, win, gc_txt_empty, text_x, 19, sv);
}
xcb_flush(c);
}
int main(int argc, char* argv[]) {
FileLock lock;
if (!lock) return 0;
::signal(SIGCHLD, SIG_IGN);
std::string_view color_arg;
for (int i = 1; i < argc; ++i) {
if (std::string_view(argv[i]) == "-c" && i + 1 < argc)
color_arg = argv[++i];
}
if (color_arg.empty()) {
if (const char* env = std::getenv("ACCENT_COLOR"); env)
color_arg = env;
}
const uint32_t fg_col = parse_color(color_arg, 0x89b4fa);
const Backend backend = detect_backend();
XcbContext ctx;
if (!ctx.valid()) return 1;
const int bar_width = ctx.screen->width_in_pixels / 4;
const int x = (ctx.screen->width_in_pixels - bar_width) / 2;
const int y = ctx.screen->height_in_pixels - BAR_HEIGHT - MARGIN_BOT;
ctx.win = xcb_generate_id(ctx.conn);
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
uint32_t values[] = {
0x181825,
fg_col,
1,
XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION |
XCB_EVENT_MASK_KEY_PRESS
};
xcb_create_window(ctx.conn, XCB_COPY_FROM_PARENT, ctx.win, ctx.screen->root,
x, y, bar_width, BAR_HEIGHT, 2,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
ctx.screen->root_visual, mask, values);
ctx.font = xcb_generate_id(ctx.conn);
xcb_open_font(ctx.conn, ctx.font, 5, "fixed");
int r = (fg_col >> 16) & 0xff, g = (fg_col >> 8) & 0xff, b = fg_col & 0xff;
int lum = (r * 299 + g * 587 + b * 114) / 1000;
uint32_t txt_fill_col = (lum > 135) ? 0x11111b : 0xffffff;
xcb_gcontext_t gc_bg = xcb_generate_id(ctx.conn);
xcb_gcontext_t gc_fg = xcb_generate_id(ctx.conn);
xcb_gcontext_t gc_txt_fill = xcb_generate_id(ctx.conn);
xcb_gcontext_t gc_txt_empty = xcb_generate_id(ctx.conn);
uint32_t bg_col = 0x181825;
xcb_create_gc(ctx.conn, gc_bg, ctx.win, XCB_GC_FOREGROUND, &bg_col);
xcb_create_gc(ctx.conn, gc_fg, ctx.win, XCB_GC_FOREGROUND, &fg_col);
uint32_t txt_mask = XCB_GC_FOREGROUND | XCB_GC_FONT;
uint32_t v_fill[] = { txt_fill_col, ctx.font };
uint32_t v_empt[] = { 0x6c7086, ctx.font };
xcb_create_gc(ctx.conn, gc_txt_fill, ctx.win, txt_mask, v_fill);
xcb_create_gc(ctx.conn, gc_txt_empty, ctx.win, txt_mask, v_empt);
xcb_map_window(ctx.conn, ctx.win);
xcb_grab_keyboard(ctx.conn, 1, ctx.win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
xcb_flush(ctx.conn);
int vol = get_volume(backend);
draw(ctx.conn, ctx.win, gc_bg, gc_fg, gc_txt_fill, gc_txt_empty, bar_width, vol);
struct pollfd pfd = { .fd = xcb_get_file_descriptor(ctx.conn), .events = POLLIN, .revents = 0 };
auto last_interaction = Clock::now();
int pending_vol = -1;
auto commit_time = Clock::time_point::min();
bool dragging = false;
while (true) {
auto now = Clock::now();
if (pending_vol != -1 && now >= commit_time) {
set_volume_async(backend, pending_vol);
pending_vol = -1;
}
auto time_left_exit = std::chrono::duration_cast<std::chrono::milliseconds>((last_interaction + TIMEOUT_DUR) - now);
if (time_left_exit <= 0ms) break;
auto poll_timeout = time_left_exit;
if (pending_vol != -1) {
auto time_left_commit = std::chrono::duration_cast<std::chrono::milliseconds>(commit_time - now);
if (time_left_commit > 0ms && time_left_commit < poll_timeout)
poll_timeout = time_left_commit;
}
if (::poll(&pfd, 1, static_cast<int>(poll_timeout.count())) < 0) break;
xcb_generic_event_t* ev = nullptr;
while ((ev = xcb_poll_for_event(ctx.conn))) {
std::unique_ptr<xcb_generic_event_t, decltype(&std::free)> ev_owner(ev, &std::free);
uint8_t type = ev->response_type & ~0x80;
if (type == XCB_EXPOSE) {
draw(ctx.conn, ctx.win, gc_bg, gc_fg, gc_txt_fill, gc_txt_empty, bar_width, vol);
} else if (type == XCB_BUTTON_PRESS) {
auto* bp = reinterpret_cast<xcb_button_press_event_t*>(ev);
if (bp->detail == 1) {
dragging = true;
vol = apply_magnet(std::clamp((bp->event_x * 100) / bar_width, 0, 100));
pending_vol = vol;
commit_time = Clock::now() + DEBOUNCE_DUR;
last_interaction = Clock::now();
draw(ctx.conn, ctx.win, gc_bg, gc_fg, gc_txt_fill, gc_txt_empty, bar_width, vol);
}
} else if (type == XCB_BUTTON_RELEASE) {
dragging = false;
last_interaction = Clock::now();
} else if (type == XCB_MOTION_NOTIFY && dragging) {
auto* mot = reinterpret_cast<xcb_motion_notify_event_t*>(ev);
vol = apply_magnet(std::clamp((mot->event_x * 100) / bar_width, 0, 100));
pending_vol = vol;
commit_time = Clock::now() + DEBOUNCE_DUR;
last_interaction = Clock::now();
draw(ctx.conn, ctx.win, gc_bg, gc_fg, gc_txt_fill, gc_txt_empty, bar_width, vol);
} else if (type == XCB_KEY_PRESS) {
auto* kp = reinterpret_cast<xcb_key_press_event_t*>(ev);
xcb_keysym_t sym = 0;
if (ctx.km_reply && kp->detail >= ctx.setup->min_keycode) {
int idx = (kp->detail - ctx.setup->min_keycode) * ctx.km_reply->keysyms_per_keycode;
sym = (reinterpret_cast<xcb_keysym_t*>(ctx.km_reply + 1))[idx];
}
bool changed = true;
if (sym == 0xff51 || sym == 0xff54) vol = std::max(0, vol - 5);
else if (sym == 0xff53 || sym == 0xff52) vol = std::min(100, vol + 5);
else if (sym >= '1' && sym <= '9') vol = (sym - '0') * 10;
else if (sym == '0') vol = 100;
else if (sym >= 0xffb1 && sym <= 0xffb9) vol = (sym - 0xffb0) * 10;
else if (sym == 0xffb0) vol = 100;
else if (sym == 0xff1b) break;
else changed = false;
if (changed) {
vol = apply_magnet(vol);
pending_vol = vol;
commit_time = Clock::now() + DEBOUNCE_DUR;
last_interaction = Clock::now();
draw(ctx.conn, ctx.win, gc_bg, gc_fg, gc_txt_fill, gc_txt_empty, bar_width, vol);
}
}
}
}
if (pending_vol != -1)
set_volume_async(backend, pending_vol);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment