Created
March 30, 2025 15:59
-
-
Save s1037989/81c651b31ef753737a3114f7f25bae3b to your computer and use it in GitHub Desktop.
multiple progress bars for x11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// $ 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