#include #include #include #include #include #include #include #ifdef USE_XFT #include #endif #include #include #include #include #include #define MAX_TEXT 512 struct config { char font[256]; char geometry[128]; char info_left[256]; char info_middle[256]; char info_right[256]; int padding_bottom; int padding_left; int padding_right; int padding_top; long rerun_ms; }; struct fontset { XFontStruct *core; #ifdef USE_XFT XftColor color; XftDraw *draw; XftFont *xft; int color_ready; #endif int use_xft; }; static long now_ms(void); static int get_resource_path(char *, size_t); static int file_mtime(const char *, long *); static int parse_int(const char *, int); static long parse_interval_ms(const char *, long); static int text_width(Display *, struct fontset *, const char *); static int font_ascent(struct fontset *); static int font_descent(struct fontset *); static int load_font(Display *, int, struct fontset *, const char *); static void bind_font(Display *, int, Window, GC, struct fontset *); static void free_font(Display *, struct fontset *); static void copystr(char *, size_t, const char *); static void trim(char *); static void resource_get_string(XrmDatabase, const char *, char *, size_t, const char *); static int resource_get_int(XrmDatabase, const char *, int); static long resource_get_interval(XrmDatabase, const char *, long); static void load_config(Display *, struct config *, const char *); static void apply_geometry(Display *, int, const char *, int *, int *, int *, int *); static void run_cmd(const char *, char *, size_t); static void refresh_texts(struct config *, char *, char *, char *); static void draw_text(Display *, Window, GC, int, int, const char *, struct fontset *); static void draw_bar(Display *, Window, GC, struct fontset *, struct config *, const char *, const char *, const char *, int); static long now_ms(void) { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000L + tv.tv_usec / 1000L; } static int get_resource_path(char *out, size_t out_sz) { const char *env; const char *home; out[0] = '\0'; env = getenv("XENVIRONMENT"); if (env != NULL && *env != '\0') { copystr(out, out_sz, env); return 1; } home = getenv("HOME"); if (home == NULL || *home == '\0') return 0; copystr(out, out_sz, home); if (strlen(out) + sizeof("/.Xresources") > out_sz) { out[0] = '\0'; return 0; } strcat(out, "/.Xresources"); return 1; } static int file_mtime(const char *path, long *mtime) { struct stat st; if (path == NULL || *path == '\0') return 0; if (stat(path, &st) != 0) return 0; *mtime = (long)st.st_mtime; return 1; } static int parse_int(const char *s, int fallback) { char *end; long v; errno = 0; v = strtol(s, &end, 10); if (errno != 0 || end == s) return fallback; while (*end != '\0') { if (!isspace((unsigned char)*end)) return fallback; end++; } return (int)v; } static long parse_interval_ms(const char *s, long fallback) { char *end; double v; while (*s != '\0' && isspace((unsigned char)*s)) s++; errno = 0; v = strtod(s, &end); if (errno != 0 || end == s || v < 0.0) return fallback; while (*end != '\0' && isspace((unsigned char)*end)) end++; if (*end == '\0') return (long)(v * 1000.0); if (strcmp(end, "ms") == 0) return (long)v; if (strcmp(end, "s") == 0) return (long)(v * 1000.0); if (strcmp(end, "m") == 0) return (long)(v * 60000.0); return fallback; } static int text_width(Display *dpy, struct fontset *fs, const char *text) { #ifdef USE_XFT if (fs->use_xft) { XGlyphInfo ext; XftTextExtentsUtf8(dpy, fs->xft, (FcChar8 *)text, (int)strlen(text), &ext); return ext.xOff; } #endif return XTextWidth(fs->core, text, (int)strlen(text)); } static int font_ascent(struct fontset *fs) { #ifdef USE_XFT if (fs->use_xft) return fs->xft->ascent; #endif return fs->core->ascent; } static int font_descent(struct fontset *fs) { #ifdef USE_XFT if (fs->use_xft) return fs->xft->descent; #endif return fs->core->descent; } static int load_font(Display *dpy, int screen, struct fontset *fs, const char *name) { memset(fs, 0, sizeof(*fs)); fs->core = XLoadQueryFont(dpy, name); if (fs->core != NULL) return 1; #ifdef USE_XFT fs->xft = XftFontOpenName(dpy, screen, name); if (fs->xft == NULL) return 0; fs->use_xft = 1; return 1; #else (void)screen; return 0; #endif } static void bind_font(Display *dpy, int screen, Window win, GC gc, struct fontset *fs) { #ifdef USE_XFT if (fs->use_xft) { XRenderColor rc; if (fs->draw == NULL) { fs->draw = XftDrawCreate(dpy, win, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen)); } if (!fs->color_ready) { rc.red = 0; rc.green = 0; rc.blue = 0; rc.alpha = 65535; if (XftColorAllocValue(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), &rc, &fs->color)) fs->color_ready = 1; } return; } #endif if (fs->core != NULL) XSetFont(dpy, gc, fs->core->fid); } static void free_font(Display *dpy, struct fontset *fs) { if (fs->core != NULL) XFreeFont(dpy, fs->core); #ifdef USE_XFT if (fs->xft != NULL) XftFontClose(dpy, fs->xft); if (fs->draw != NULL) XftDrawDestroy(fs->draw); if (fs->color_ready) XftColorFree(dpy, DefaultVisual(dpy, DefaultScreen(dpy)), DefaultColormap(dpy, DefaultScreen(dpy)), &fs->color); #endif memset(fs, 0, sizeof(*fs)); } static void copystr(char *dst, size_t dst_sz, const char *src) { if (dst_sz == 0) return; strncpy(dst, src, dst_sz - 1); dst[dst_sz - 1] = '\0'; } static void trim(char *s) { size_t len; char *start; start = s; while (*start != '\0' && isspace((unsigned char)*start)) start++; if (start != s) memmove(s, start, strlen(start) + 1); len = strlen(s); while (len > 0 && isspace((unsigned char)s[len - 1])) { s[len - 1] = '\0'; len--; } len = strlen(s); if (len >= 2 && s[0] == '"' && s[len - 1] == '"') { memmove(s, s + 1, len - 2); s[len - 2] = '\0'; } } static void resource_get_string(XrmDatabase db, const char *name, char *out, size_t out_sz, const char *fallback) { char key[128]; char cls[128]; char *type; XrmValue val; size_t n; strlcpy(key, "bar.", sizeof(key)); strlcat(key, name, sizeof(key)); strlcpy(cls, "Bar.", sizeof(cls)); strlcat(cls, name, sizeof(cls)); if (XrmGetResource(db, key, cls, &type, &val) && val.addr != NULL && val.size > 0) { n = (size_t)val.size; if (n >= out_sz) n = out_sz - 1; memcpy(out, val.addr, n); out[n] = '\0'; trim(out); return; } strlcpy(key, "*.", sizeof(key)); strlcat(key, name, sizeof(key)); strlcpy(cls, "*.", sizeof(cls)); strlcat(cls, name, sizeof(cls)); if (XrmGetResource(db, key, cls, &type, &val) && val.addr != NULL && val.size > 0) { n = (size_t)val.size; if (n >= out_sz) n = out_sz - 1; memcpy(out, val.addr, n); out[n] = '\0'; trim(out); return; } copystr(out, out_sz, fallback); } static int resource_get_int(XrmDatabase db, const char *name, int fallback) { char buf[64]; resource_get_string(db, name, buf, sizeof(buf), ""); if (buf[0] == '\0') return fallback; return parse_int(buf, fallback); } static long resource_get_interval(XrmDatabase db, const char *name, long fallback) { char buf[64]; resource_get_string(db, name, buf, sizeof(buf), ""); if (buf[0] == '\0') return fallback; return parse_interval_ms(buf, fallback); } static void load_config(Display *dpy, struct config *cfg, const char *resource_path) { char *rm; XrmDatabase db; copystr(cfg->font, sizeof(cfg->font), "fixed"); cfg->geometry[0] = '\0'; cfg->info_left[0] = '\0'; cfg->info_middle[0] = '\0'; cfg->info_right[0] = '\0'; cfg->padding_bottom = 2; cfg->padding_left = 8; cfg->padding_right = 8; cfg->padding_top = 2; cfg->rerun_ms = 1000; XrmInitialize(); db = NULL; if (resource_path != NULL && *resource_path != '\0') db = XrmGetFileDatabase(resource_path); if (db == NULL) { rm = XResourceManagerString(dpy); if (rm != NULL) db = XrmGetStringDatabase(rm); } if (db == NULL) return; resource_get_string(db, "font", cfg->font, sizeof(cfg->font), cfg->font); resource_get_string(db, "geometry", cfg->geometry, sizeof(cfg->geometry), ""); resource_get_string(db, "info_left", cfg->info_left, sizeof(cfg->info_left), ""); resource_get_string(db, "info_middle", cfg->info_middle, sizeof(cfg->info_middle), ""); resource_get_string(db, "info_right", cfg->info_right, sizeof(cfg->info_right), ""); cfg->padding_bottom = resource_get_int(db, "padding_bottom", cfg->padding_bottom); cfg->padding_left = resource_get_int(db, "padding_left", cfg->padding_left); cfg->padding_right = resource_get_int(db, "padding_right", cfg->padding_right); cfg->padding_top = resource_get_int(db, "padding_top", cfg->padding_top); cfg->rerun_ms = resource_get_interval(db, "rerun_interval", cfg->rerun_ms); if (cfg->rerun_ms < 50) cfg->rerun_ms = 50; XrmDestroyDatabase(db); } static void apply_geometry(Display *dpy, int screen, const char *geom, int *x, int *y, int *w, int *h) { int gx, gy, mask, nx, ny, nw, nh, sw, sh; unsigned int gw, gh; if (geom == NULL || *geom == '\0') return; gx = 0; gy = 0; gw = 0; gh = 0; mask = XParseGeometry(geom, &gx, &gy, &gw, &gh); if (mask == 0) return; nw = *w; nh = *h; if ((mask & WidthValue) && gw > 0) nw = (int)gw; if ((mask & HeightValue) && gh > 0) nh = (int)gh; sw = DisplayWidth(dpy, screen); sh = DisplayHeight(dpy, screen); if (nw < 1) nw = 1; if (nh < 1) nh = 1; if (nw > sw) nw = sw; if (nh > sh) nh = sh; nx = *x; ny = *y; if (mask & XValue) { if (mask & XNegative) nx = sw - nw + gx; else nx = gx; } if (mask & YValue) { if (mask & YNegative) ny = sh - nh + gy; else ny = gy; } if (nx < 0) nx = 0; if (ny < 0) ny = 0; if (nx + nw > sw) nx = sw - nw; if (ny + nh > sh) ny = sh - nh; *x = nx; *y = ny; *w = nw; *h = nh; } static void run_cmd(const char *cmd, char *out, size_t out_sz) { FILE *fp; size_t n; out[0] = '\0'; if (cmd == NULL || *cmd == '\0') return; fp = popen(cmd, "r"); if (fp == NULL) return; n = fread(out, 1, out_sz - 1, fp); out[n] = '\0'; pclose(fp); while (n > 0 && (out[n - 1] == '\n' || out[n - 1] == '\r' || out[n - 1] == ' ' || out[n - 1] == '\t')) { out[n - 1] = '\0'; n--; } } static void refresh_texts(struct config *cfg, char *left, char *middle, char *right) { run_cmd(cfg->info_left, left, MAX_TEXT); run_cmd(cfg->info_middle, middle, MAX_TEXT); run_cmd(cfg->info_right, right, MAX_TEXT); } static void draw_text(Display *dpy, Window win, GC gc, int x, int y, const char *text, struct fontset *fs) { #ifdef USE_XFT if (fs->use_xft && fs->draw != NULL && fs->color_ready) { XftDrawStringUtf8(fs->draw, &fs->color, fs->xft, x, y, (FcChar8 *)text, (int)strlen(text)); return; } #endif XDrawString(dpy, win, gc, x, y, text, (int)strlen(text)); } static void draw_bar(Display *dpy, Window win, GC gc, struct fontset *fs, struct config *cfg, const char *left, const char *middle, const char *right, int width) { int y, lw, mw, rw, lx, mx, rx; y = cfg->padding_top + font_ascent(fs); lw = text_width(dpy, fs, left); mw = text_width(dpy, fs, middle); rw = text_width(dpy, fs, right); lx = cfg->padding_left; mx = (width - mw) / 2; rx = width - cfg->padding_right - rw; if (mx < lx + lw + 2) mx = lx + lw + 2; if (rx < mx + mw + 2) rx = mx + mw + 2; XClearWindow(dpy, win); if (*right != '\0') draw_text(dpy, win, gc, rx, y, right, fs); if (*middle != '\0') draw_text(dpy, win, gc, mx, y, middle, fs); if (*left != '\0') draw_text(dpy, win, gc, lx, y, left, fs); XFlush(dpy); } int main(void) { Display *dpy; XEvent ev; XGCValues gcv; XSetWindowAttributes wa; char left[MAX_TEXT], middle[MAX_TEXT], resource_path[512], right[MAX_TEXT]; struct config cfg; struct fontset fs, newfs; struct timeval tv; fd_set fds; GC gc; Window root, win; int have_mtime, height, pos_x, pos_y, screen, sel, watch_resources, width; long last_mtime, mt, next_tick, next_watch, now, wait_ms, watch_interval; dpy = XOpenDisplay(NULL); if (dpy == NULL) { fprintf(stderr, "bar: cannot open X display\n"); return 1; } screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); width = DisplayWidth(dpy, screen); watch_resources = get_resource_path(resource_path, sizeof(resource_path)); have_mtime = 0; if (watch_resources && file_mtime(resource_path, &mt)) { last_mtime = mt; have_mtime = 1; } load_config(dpy, &cfg, watch_resources ? resource_path : NULL); if (!load_font(dpy, screen, &fs, cfg.font) && !load_font(dpy, screen, &fs, "fixed")) { fprintf(stderr, "bar: cannot load font\n"); XCloseDisplay(dpy); return 1; } height = font_ascent(&fs) + font_descent(&fs) + cfg.padding_top + cfg.padding_bottom; if (height < 1) height = 1; pos_x = 0; pos_y = 0; apply_geometry(dpy, screen, cfg.geometry, &pos_x, &pos_y, &width, &height); wa.override_redirect = True; wa.background_pixel = WhitePixel(dpy, screen); wa.border_pixel = BlackPixel(dpy, screen); wa.event_mask = ExposureMask | StructureNotifyMask; win = XCreateWindow(dpy, root, pos_x, pos_y, (unsigned int)width, (unsigned int)height, 0, CopyFromParent, InputOutput, CopyFromParent, CWOverrideRedirect | CWBackPixel | CWBorderPixel | CWEventMask, &wa); XStoreName(dpy, win, "bar"); XMapRaised(dpy, win); gcv.foreground = BlackPixel(dpy, screen); gcv.background = WhitePixel(dpy, screen); gc = XCreateGC(dpy, win, GCForeground | GCBackground, &gcv); bind_font(dpy, screen, win, gc, &fs); refresh_texts(&cfg, left, middle, right); draw_bar(dpy, win, gc, &fs, &cfg, left, middle, right, width); next_tick = now_ms() + cfg.rerun_ms; watch_interval = 500; next_watch = now_ms() + watch_interval; for (;;) { now = now_ms(); wait_ms = next_tick - now; if (next_watch - now < wait_ms) wait_ms = next_watch - now; if (wait_ms < 0) wait_ms = 0; FD_ZERO(&fds); FD_SET(ConnectionNumber(dpy), &fds); tv.tv_sec = wait_ms / 1000; tv.tv_usec = (wait_ms % 1000) * 1000; sel = select(ConnectionNumber(dpy) + 1, &fds, NULL, NULL, &tv); if (sel > 0 && FD_ISSET(ConnectionNumber(dpy), &fds)) { while (XPending(dpy)) { XNextEvent(dpy, &ev); if (ev.type == ConfigureNotify) width = ev.xconfigure.width; if (ev.type == Expose || ev.type == ConfigureNotify) draw_bar(dpy, win, gc, &fs, &cfg, left, middle, right, width); } } now = now_ms(); if (watch_resources && now >= next_watch) { if (file_mtime(resource_path, &mt) && (!have_mtime || mt != last_mtime)) { have_mtime = 1; last_mtime = mt; load_config(dpy, &cfg, resource_path); if (load_font(dpy, screen, &newfs, cfg.font) || load_font(dpy, screen, &newfs, "fixed")) { free_font(dpy, &fs); fs = newfs; } bind_font(dpy, screen, win, gc, &fs); width = DisplayWidth(dpy, screen); height = font_ascent(&fs) + font_descent(&fs) + cfg.padding_top + cfg.padding_bottom; if (height < 1) height = 1; pos_x = 0; pos_y = 0; apply_geometry(dpy, screen, cfg.geometry, &pos_x, &pos_y, &width, &height); XMoveResizeWindow(dpy, win, pos_x, pos_y, (unsigned int)width, (unsigned int)height); refresh_texts(&cfg, left, middle, right); draw_bar(dpy, win, gc, &fs, &cfg, left, middle, right, width); next_tick = now + cfg.rerun_ms; } else if (!file_mtime(resource_path, &mt)) have_mtime = 0; next_watch = now + watch_interval; } now = now_ms(); if (now >= next_tick) { refresh_texts(&cfg, left, middle, right); draw_bar(dpy, win, gc, &fs, &cfg, left, middle, right, width); next_tick = now + cfg.rerun_ms; } } }