Skip to content

Instantly share code, notes, and snippets.

@s1037989
Created March 30, 2025 15:59
Show Gist options
  • Save s1037989/81c651b31ef753737a3114f7f25bae3b to your computer and use it in GitHub Desktop.
Save s1037989/81c651b31ef753737a3114f7f25bae3b to your computer and use it in GitHub Desktop.
multiple progress bars for x11
// $ rm -f xprogress.c ; vi xprogress.c ; gcc -o xprogress xprogress.c -lX11
// $ mkdir -p media ; echo 10 > media/media1.txt ; echo 20 > media/media2.txt ; ./xprogress media
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#define PADDING 5
#define BAR_WIDTH 300
#define BAR_HEIGHT 20
#define TEXT_HEIGHT 20
#define LINE_SPACING 10
#define SECTION_SPACING 20
#define MAX_FILES 100
#define LABEL_LEN 256
typedef struct {
char label[LABEL_LEN];
char path[1024];
} ProgressFile;
int read_progress_from_file(const char *filename, int *is_empty, int *is_missing) {
struct stat st;
*is_missing = 0;
*is_empty = 0;
if (stat(filename, &st) != 0) {
*is_missing = 1;
return -1;
}
if (st.st_size == 0) {
*is_empty = 1;
return 0;
}
FILE *file = fopen(filename, "r");
if (!file) return -1;
int value = -1;
fscanf(file, "%d", &value);
fclose(file);
return value;
}
int load_progress_files_from_dir(const char *dirname, ProgressFile *files, int max_files) {
DIR *dir = opendir(dirname);
if (!dir) return -1;
struct dirent *entry;
int count = 0;
while ((entry = readdir(dir)) && count < max_files) {
if (entry->d_type != DT_REG) continue;
snprintf(files[count].label, LABEL_LEN, "%s", entry->d_name);
snprintf(files[count].path, sizeof(files[count].path), "%s/%s", dirname, entry->d_name);
count++;
}
closedir(dir);
return count;
}
void set_window_above(Display *display, Window window, int above) {
Atom wmStateAbove = XInternAtom(display, "_NET_WM_STATE_ABOVE", False);
Atom wmState = XInternAtom(display, "_NET_WM_STATE", False);
XEvent xev;
memset(&xev, 0, sizeof(xev));
xev.xclient.type = ClientMessage;
xev.xclient.window = window;
xev.xclient.message_type = wmState;
xev.xclient.format = 32;
xev.xclient.data.l[0] = above ? 1 : 0;
xev.xclient.data.l[1] = wmStateAbove;
xev.xclient.data.l[2] = 0;
xev.xclient.data.l[3] = 0;
xev.xclient.data.l[4] = 0;
XSendEvent(display, DefaultRootWindow(display), False,
SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <directory>\n", argv[0]);
return 1;
}
const char *progress_dir = argv[1];
ProgressFile files[MAX_FILES];
int num_files = 0;
Display *display;
int screen;
int window_width = BAR_WIDTH + 2 * PADDING;
Window window = 0;
GC gc;
XFontStruct *font;
int y = 0;
while (1) {
num_files = load_progress_files_from_dir(progress_dir, files, MAX_FILES);
if (num_files < 0) {
fprintf(stderr, "Unable to read progress directory. Exiting.\n");
break;
}
y = PADDING + TEXT_HEIGHT;
int visible_items = 0;
int any_exist = 0;
for (int i = 0; i < num_files; i++) {
struct stat st;
if (stat(files[i].path, &st) == 0) {
any_exist = 1;
break;
}
}
if (!any_exist) {
if (window) {
XClearWindow(display, window);
XDestroyWindow(display, window);
XCloseDisplay(display);
window = 0;
}
usleep(100000);
continue;
}
if (!window) {
display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "Cannot open display\n");
return 1;
}
screen = DefaultScreen(display);
window = XCreateSimpleWindow(display, RootWindow(display, screen), 100, 100, window_width, 1, 1,
BlackPixel(display, screen), WhitePixel(display, screen));
XStoreName(display, window, "Sync Media");
XClassHint class_hint = {"xprogresswin", "XProgressWin"};
XSetClassHint(display, window, &class_hint);
XSelectInput(display, window, ExposureMask | KeyPressMask);
XMapWindow(display, window);
set_window_above(display, window, 1);
gc = XCreateGC(display, window, 0, NULL);
font = XLoadQueryFont(display, "fixed");
if (font) XSetFont(display, gc, font->fid);
XSetForeground(display, gc, BlackPixel(display, screen));
}
XClearWindow(display, window);
for (int i = 0; i < num_files; i++) {
int is_empty = 0, is_missing = 0;
int progress = read_progress_from_file(files[i].path, &is_empty, &is_missing);
if (is_missing) continue;
if (visible_items) {
XDrawLine(display, window, gc, PADDING, y, window_width - PADDING, y);
y += SECTION_SPACING;
}
char line[256];
if (is_empty) {
snprintf(line, sizeof(line), "%.240s: Busy...", files[i].label);
XDrawString(display, window, gc, PADDING, y, line, strlen(line));
y += TEXT_HEIGHT + SECTION_SPACING;
visible_items++;
} else {
snprintf(line, sizeof(line), "%.240s: %d%%", files[i].label, progress);
XDrawString(display, window, gc, PADDING, y, line, strlen(line));
y += LINE_SPACING;
XDrawRectangle(display, window, gc, PADDING, y, BAR_WIDTH, BAR_HEIGHT);
XFillRectangle(display, window, gc, PADDING, y, progress * BAR_WIDTH / 100, BAR_HEIGHT);
y += BAR_HEIGHT + LINE_SPACING + TEXT_HEIGHT;
visible_items++;
}
}
XResizeWindow(display, window, window_width, y + PADDING);
XFlush(display);
while (window && XPending(display)) {
XEvent event;
XNextEvent(display, &event);
if (event.type == KeyPress) {
char buf[16];
KeySym keysym;
XLookupString(&event.xkey, buf, sizeof(buf), &keysym, NULL);
if (keysym == XK_Escape || keysym == XK_q || keysym == XK_x) {
if (window) XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
}
}
usleep(100000);
}
if (window) XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment