/* orionpkg.c — OrionOS Package Manager * * States: CHECKING → UPTODATE | UPDATES → UPDATING → DONE/ERROR * Uses checkupdates (pacman-contrib) to list pending upgrades. * Runs pkexec pacman -Syu for the actual update. * Native C/Xlib + Xft, same dark-navy palette as OrionOS. */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* ── layout ─────────────────────────────────────────────────────────── */ #define WIN_W 620 #define WIN_H 500 #define HDR_H 60 #define FOOT_H 52 #define BODY_Y HDR_H #define BODY_H (WIN_H - HDR_H - FOOT_H) #define PAD 20 #define ROW_H 28 #define BTN_W 120 #define BTN_H 30 /* ── palette ─────────────────────────────────────────────────────────── */ #define C_BG 0x0E1220 #define C_HDR 0x090C18 #define C_BODY 0x0D1624 #define C_FOOT 0x090C18 #define C_PANEL 0x111828 #define C_BORDER 0x1A2840 #define C_SEP 0x162030 #define C_ACCENT 0x1E58A8 #define C_ACCENT_H 0x2A70CC #define C_BTN 0x182848 #define C_BTN_HOV 0x1E3860 #define C_BTN_UPD 0x1A4888 #define C_BTN_UPD_H 0x2060AA #define C_BTN_DNG 0x5A1414 #define C_BTN_DNG_H 0x882020 #define C_TEXT 0xA8C4DC #define C_TEXT_DIM 0x3E5870 #define C_TEXT_HOV 0xE0EEFF #define C_TEXT_HDR 0xE8F2FF #define C_TEXT_PKG 0xC0D8F0 #define C_TEXT_VER 0x4A7090 #define C_TEXT_OK 0x44BB77 #define C_TEXT_ERR 0xFF5555 #define C_TEXT_WARN 0xFFCC44 #define C_ROW_ALT 0x0C1520 #define C_ROW_HOV 0x132030 #define C_BAR_BG 0x141E30 #define C_BAR_FG 0x1E58A8 #define C_MONO 0x7AAAC8 #define FONT_UI "Sans:size=9" #define FONT_HDR "Sans:size=10:weight=bold" #define FONT_MONO "DejaVu Sans Mono:size=8" /* ── states ─────────────────────────────────────────────────────────── */ typedef enum { ST_CHECKING, /* running checkupdates */ ST_UPTODATE, /* no updates found */ ST_UPDATES, /* update list ready */ ST_UPDATING, /* pacman -Syu running */ ST_DONE, /* success */ ST_ERROR, /* failed */ } State; /* ── package entry ───────────────────────────────────────────────────── */ #define MAX_PKGS 256 typedef struct { char name[48]; char old_ver[24]; char new_ver[24]; } Pkg; /* ── log buffer ──────────────────────────────────────────────────────── */ #define MAX_LOG 300 #define LOG_COLS 90 typedef struct { char lines[MAX_LOG][LOG_COLS+1]; int head, count; } LogBuf; static void log_push(LogBuf *lb, const char *line) { snprintf(lb->lines[lb->head], LOG_COLS, "%s", line); lb->head = (lb->head + 1) % MAX_LOG; if (lb->count < MAX_LOG) lb->count++; } /* ── app struct ──────────────────────────────────────────────────────── */ typedef struct { Display *dpy; int screen; Window root, win; GC gc; Colormap cmap; Visual *visual; int depth; XftFont *font_ui, *font_hdr, *font_mono; State state; Pkg pkgs[MAX_PKGS]; int npkgs; int scroll; /* list scroll offset */ int hover_pkg; /* hovered list row */ int hover_btn; /* 0=none,1=update,2=refresh,3=close */ LogBuf log; int update_fd; /* pipe fd for pacman output */ int progress; /* 0-100 estimated */ int spin_frame; /* animation tick */ char status_msg[256]; } Pkg_App; /* ── X helpers ───────────────────────────────────────────────────────── */ static unsigned long xcol(Pkg_App *a, unsigned long rgb) { XColor c = { .red = ((rgb>>16)&0xFF)*257, .green = ((rgb>> 8)&0xFF)*257, .blue = ( rgb &0xFF)*257, .flags = DoRed|DoGreen|DoBlue }; XAllocColor(a->dpy, a->cmap, &c); return c.pixel; } static void xftcol(Pkg_App *a, unsigned long rgb, XftColor *out) { XRenderColor rc = { .red = ((rgb>>16)&0xFF)*257, .green = ((rgb>> 8)&0xFF)*257, .blue = ( rgb &0xFF)*257, .alpha = 0xFFFF }; XftColorAllocValue(a->dpy, a->visual, a->cmap, &rc, out); } static void fill(Pkg_App *a, unsigned long rgb, int x, int y, int w, int h) { XSetForeground(a->dpy, a->gc, xcol(a, rgb)); XFillRectangle(a->dpy, a->win, a->gc, x, y, w, h); } static void outline(Pkg_App *a, unsigned long rgb, int x, int y, int w, int h) { XSetForeground(a->dpy, a->gc, xcol(a, rgb)); XDrawRectangle(a->dpy, a->win, a->gc, x, y, w-1, h-1); } static void text(Pkg_App *a, XftFont *f, unsigned long rgb, int x, int y, const char *s) { if (!s || !s[0]) return; XftDraw *xd = XftDrawCreate(a->dpy, a->win, a->visual, a->cmap); XftColor c; xftcol(a, rgb, &c); XftDrawStringUtf8(xd, &c, f, x, y, (const FcChar8 *)s, strlen(s)); XftColorFree(a->dpy, a->visual, a->cmap, &c); XftDrawDestroy(xd); } static int textw(Pkg_App *a, XftFont *f, const char *s) { XGlyphInfo ext; XftTextExtentsUtf8(a->dpy, f, (const FcChar8 *)s, strlen(s), &ext); return ext.xOff; } static int baseline(XftFont *f, int y, int h) { return y + (h + f->ascent - f->descent) / 2; } static int in_rect(int mx, int my, int x, int y, int w, int h) { return mx>=x && mx=y && myhover_btn == btn_id) ? bghov : bg; fill(a, b, x, y, w, h); outline(a, C_BORDER, x, y, w, h); int tx = x + (w - textw(a, a->font_ui, label)) / 2; text(a, a->font_ui, C_TEXT_HDR, tx, baseline(a->font_ui, y, h), label); } /* ── header ──────────────────────────────────────────────────────────── */ static void draw_header(Pkg_App *a) { fill(a, C_HDR, 0, 0, WIN_W, HDR_H); fill(a, C_BORDER, 0, HDR_H-1, WIN_W, 1); /* accent stripe */ fill(a, C_ACCENT, 0, 0, 3, HDR_H); text(a, a->font_hdr, C_TEXT_HDR, PAD + 8, baseline(a->font_hdr, 0, HDR_H) - 8, "OrionOS Package Manager"); /* subtitle */ const char *sub = (a->state == ST_CHECKING) ? "Checking for updates…" : (a->state == ST_UPTODATE) ? "System is up to date" : (a->state == ST_UPDATING) ? "Updating system…" : (a->state == ST_DONE) ? "Update complete" : (a->state == ST_ERROR) ? "Update failed" : /* ST_UPDATES */ ""; char sub_buf[64]; if (a->state == ST_UPDATES) { snprintf(sub_buf, sizeof(sub_buf), "%d package%s available", a->npkgs, a->npkgs == 1 ? "" : "s"); sub = sub_buf; } unsigned long sc = (a->state == ST_DONE) ? C_TEXT_OK : (a->state == ST_ERROR) ? C_TEXT_ERR : C_TEXT_DIM; text(a, a->font_ui, sc, PAD + 8, baseline(a->font_ui, 0, HDR_H) + 10, sub); } /* ── footer ──────────────────────────────────────────────────────────── */ static void draw_footer(Pkg_App *a) { int fy = WIN_H - FOOT_H; fill(a, C_FOOT, 0, fy, WIN_W, FOOT_H); fill(a, C_BORDER, 0, fy, WIN_W, 1); int by = fy + (FOOT_H - BTN_H) / 2; /* Close always available */ draw_btn(a, 3, WIN_W - BTN_W - PAD, by, BTN_W, BTN_H, "Close", C_BTN, C_BTN_HOV); if (a->state == ST_UPDATES) { draw_btn(a, 1, WIN_W - BTN_W*2 - PAD - 8, by, BTN_W, BTN_H, "Update All", C_BTN_UPD, C_BTN_UPD_H); draw_btn(a, 2, PAD, by, BTN_W - 20, BTN_H, "Refresh", C_BTN, C_BTN_HOV); } else if (a->state == ST_UPTODATE || a->state == ST_ERROR) { draw_btn(a, 2, PAD, by, BTN_W - 20, BTN_H, "Refresh", C_BTN, C_BTN_HOV); } } /* ── spinning indicator ──────────────────────────────────────────────── */ static const char *SPIN[] = { "⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏" }; #define NSPIN 10 /* ── package list ────────────────────────────────────────────────────── */ #define LIST_ROWS_VIS ((BODY_H - 4) / ROW_H) #define VER_X (WIN_W / 2 - 40) #define ARROW_X (WIN_W / 2 + 80) #define NEWVER_X (WIN_W / 2 + 100) static void draw_list_header(Pkg_App *a) { int hy = BODY_Y + 2; fill(a, 0x0A1220, 0, hy, WIN_W, ROW_H - 2); fill(a, C_SEP, 0, hy + ROW_H - 3, WIN_W, 1); text(a, a->font_ui, C_TEXT_DIM, PAD, baseline(a->font_ui, hy, ROW_H-2), "Package"); text(a, a->font_ui, C_TEXT_DIM, VER_X, baseline(a->font_ui, hy, ROW_H-2), "Current"); text(a, a->font_ui, C_TEXT_DIM, NEWVER_X, baseline(a->font_ui, hy, ROW_H-2), "New"); } static void draw_updates(Pkg_App *a) { fill(a, C_BODY, 0, BODY_Y, WIN_W, BODY_H); draw_list_header(a); int list_y = BODY_Y + ROW_H; int vis = LIST_ROWS_VIS - 1; for (int i = a->scroll; i < a->npkgs && (i - a->scroll) < vis; i++) { int ry = list_y + (i - a->scroll) * ROW_H; int is_hov = (a->hover_pkg == i); if (is_hov) fill(a, C_ROW_HOV, 0, ry, WIN_W, ROW_H); else if ((i - a->scroll) % 2 == 1) fill(a, C_ROW_ALT, 0, ry, WIN_W, ROW_H); fill(a, C_SEP, 0, ry + ROW_H - 1, WIN_W, 1); int ty = baseline(a->font_ui, ry, ROW_H); text(a, a->font_ui, is_hov ? C_TEXT_HOV : C_TEXT_PKG, PAD, ty, a->pkgs[i].name); text(a, a->font_ui, C_TEXT_VER, VER_X, ty, a->pkgs[i].old_ver); /* arrow */ text(a, a->font_ui, C_TEXT_DIM, ARROW_X - 16, ty, "→"); text(a, a->font_ui, C_TEXT_OK, NEWVER_X, ty, a->pkgs[i].new_ver); } /* scrollbar */ if (a->npkgs > vis) { int sb_x = WIN_W - 6; int sb_h = BODY_H - ROW_H; int th_h = sb_h * vis / a->npkgs; if (th_h < 20) th_h = 20; int th_y = list_y + (sb_h - th_h) * a->scroll / (a->npkgs - vis); fill(a, C_SEP, sb_x, list_y, 4, sb_h); fill(a, C_ACCENT, sb_x, th_y, 4, th_h); } } /* ── log view (used during update) ──────────────────────────────────── */ #define LOG_ROWS_VIS ((BODY_H - 40) / 16) #define LOG_ROW_H 16 static void draw_log(Pkg_App *a) { fill(a, C_BODY, 0, BODY_Y, WIN_W, BODY_H); /* progress bar */ int bx = PAD, bw = WIN_W - PAD*2, bh = 8; int by = BODY_Y + 10; fill(a, C_BAR_BG, bx, by, bw, bh); int fw = bw * a->progress / 100; if (fw > 0) fill(a, C_BAR_FG, bx, by, fw, bh); outline(a, C_BORDER, bx, by, bw, bh); char pct[16]; snprintf(pct, sizeof(pct), "%d%%", a->progress); text(a, a->font_ui, C_TEXT, bx + bw + 6, baseline(a->font_ui, by, bh), pct); /* log panel */ int ly = by + bh + 10; fill(a, C_PANEL, PAD, ly, WIN_W - PAD*2, BODY_H - (ly - BODY_Y) - 4); outline(a, C_BORDER, PAD, ly, WIN_W - PAD*2, BODY_H - (ly - BODY_Y) - 4); int start = (a->log.count > LOG_ROWS_VIS) ? (a->log.head - LOG_ROWS_VIS + MAX_LOG) % MAX_LOG : (a->log.head - a->log.count + MAX_LOG) % MAX_LOG; int show = a->log.count < LOG_ROWS_VIS ? a->log.count : LOG_ROWS_VIS; for (int i = 0; i < show; i++) { int idx = (start + i) % MAX_LOG; const char *line = a->log.lines[idx]; int logy = ly + 4 + i * LOG_ROW_H; unsigned long c = C_MONO; if (strstr(line, "error:") || strstr(line, "Error")) c = C_TEXT_ERR; else if (strstr(line, "warning:")) c = C_TEXT_WARN; else if (line[0] == '(') c = C_TEXT_OK; XRectangle clip = { (short)(PAD+2), (short)logy, (unsigned short)(WIN_W - PAD*2 - 4), (unsigned short)LOG_ROW_H }; XftDraw *xd = XftDrawCreate(a->dpy, a->win, a->visual, a->cmap); XftDrawSetClipRectangles(xd, 0, 0, &clip, 1); XftColor col; xftcol(a, c, &col); XftDrawStringUtf8(xd, &col, a->font_mono, PAD + 4, logy + LOG_ROW_H - 3, (const FcChar8 *)line, strlen(line)); XftColorFree(a->dpy, a->visual, a->cmap, &col); XftDrawSetClip(xd, None); XftDrawDestroy(xd); } } /* ── simple status screens ───────────────────────────────────────────── */ static void draw_centered(Pkg_App *a, unsigned long col, const char *line1, const char *line2) { fill(a, C_BODY, 0, BODY_Y, WIN_W, BODY_H); int cy = BODY_Y + BODY_H / 2 - 20; text(a, a->font_hdr, col, (WIN_W - textw(a, a->font_hdr, line1)) / 2, cy, line1); if (line2 && line2[0]) { cy += 28; text(a, a->font_ui, C_TEXT_DIM, (WIN_W - textw(a, a->font_ui, line2)) / 2, cy, line2); } } /* ── full redraw ─────────────────────────────────────────────────────── */ static void draw(Pkg_App *a) { fill(a, C_BG, 0, 0, WIN_W, WIN_H); draw_header(a); switch (a->state) { case ST_CHECKING: { fill(a, C_BODY, 0, BODY_Y, WIN_W, BODY_H); char buf[32]; snprintf(buf, sizeof(buf), "%s Checking for updates…", SPIN[a->spin_frame % NSPIN]); text(a, a->font_hdr, C_TEXT_DIM, (WIN_W - textw(a, a->font_hdr, buf)) / 2, BODY_Y + BODY_H/2 - 8, buf); break; } case ST_UPTODATE: draw_centered(a, C_TEXT_OK, "✓ System is up to date", "No packages require updating."); break; case ST_UPDATES: draw_updates(a); break; case ST_UPDATING: draw_log(a); break; case ST_DONE: draw_centered(a, C_TEXT_OK, "✓ Update complete", "All packages have been updated successfully."); break; case ST_ERROR: draw_centered(a, C_TEXT_ERR, "✗ Update failed", a->status_msg[0] ? a->status_msg : "Check the log for details."); break; } draw_footer(a); XFlush(a->dpy); } /* ── checkupdates ────────────────────────────────────────────────────── */ static void run_check(Pkg_App *a) { a->state = ST_CHECKING; a->npkgs = 0; a->scroll = 0; draw(a); FILE *f = popen("checkupdates 2>/dev/null || true", "r"); if (!f) { a->state = ST_ERROR; snprintf(a->status_msg, sizeof(a->status_msg), "checkupdates not found — install pacman-contrib"); draw(a); return; } char line[256]; while (fgets(line, sizeof(line), f) && a->npkgs < MAX_PKGS) { /* format: name old_ver -> new_ver */ char name[48], old[24], arrow[4], newv[24]; if (sscanf(line, "%47s %23s %3s %23s", name, old, arrow, newv) == 4) { strncpy(a->pkgs[a->npkgs].name, name, 47); strncpy(a->pkgs[a->npkgs].old_ver, old, 23); strncpy(a->pkgs[a->npkgs].new_ver, newv, 23); a->npkgs++; } } pclose(f); a->state = (a->npkgs > 0) ? ST_UPDATES : ST_UPTODATE; draw(a); } /* ── update ──────────────────────────────────────────────────────────── */ static void start_update(Pkg_App *a) { int pipefd[2]; if (pipe(pipefd) < 0) { a->state = ST_ERROR; snprintf(a->status_msg, sizeof(a->status_msg), "pipe() failed: %s", strerror(errno)); draw(a); return; } pid_t pid = fork(); if (pid < 0) { a->state = ST_ERROR; snprintf(a->status_msg, sizeof(a->status_msg), "fork() failed: %s", strerror(errno)); close(pipefd[0]); close(pipefd[1]); draw(a); return; } if (pid == 0) { close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); dup2(pipefd[1], STDERR_FILENO); close(pipefd[1]); execlp("pkexec", "pkexec", "pacman", "-Syu", "--noconfirm", NULL); fprintf(stderr, "exec failed: %s\n", strerror(errno)); _exit(1); } close(pipefd[1]); a->update_fd = pipefd[0]; fcntl(a->update_fd, F_SETFL, O_NONBLOCK); a->state = ST_UPDATING; a->progress = 0; a->log.head = a->log.count = 0; draw(a); } static void read_update_output(Pkg_App *a) { char buf[4096]; ssize_t n = read(a->update_fd, buf, sizeof(buf)-1); if (n <= 0) { close(a->update_fd); a->update_fd = -1; /* check exit */ int status = 0; waitpid(-1, &status, WNOHANG); int ok = (WIFEXITED(status) && WEXITSTATUS(status) == 0); a->state = ok ? ST_DONE : ST_ERROR; a->progress = ok ? 100 : a->progress; if (!ok) snprintf(a->status_msg, sizeof(a->status_msg), "pacman exited with code %d", WEXITSTATUS(status)); draw(a); return; } buf[n] = '\0'; /* split on newlines, push to log */ static char carry[256]; static int clen = 0; for (int i = 0; i < (int)n; i++) { if (buf[i] == '\n' || buf[i] == '\r') { carry[clen] = '\0'; if (clen > 0) { log_push(&a->log, carry); /* estimate progress from ":: Downloading" lines */ if (strncmp(carry, "(", 1) == 0) { int cur, tot; if (sscanf(carry, "(%d/%d)", &cur, &tot) == 2 && tot > 0) a->progress = 10 + cur * 80 / tot; } if (strstr(carry, "Running post-transaction")) a->progress = 90; if (strstr(carry, "Upgrade complete")) a->progress = 100; } clen = 0; } else if (clen < (int)sizeof(carry)-2) { carry[clen++] = buf[i]; } } draw(a); } /* ── hover / click ───────────────────────────────────────────────────── */ static int btn_at(Pkg_App *a, int x, int y) { int fy = WIN_H - FOOT_H; int by = fy + (FOOT_H - BTN_H) / 2; /* close */ int cx = WIN_W - BTN_W - PAD; if (in_rect(x, y, cx, by, BTN_W, BTN_H)) return 3; if (a->state == ST_UPDATES) { /* update all */ int ux = WIN_W - BTN_W*2 - PAD - 8; if (in_rect(x, y, ux, by, BTN_W, BTN_H)) return 1; /* refresh */ if (in_rect(x, y, PAD, by, BTN_W-20, BTN_H)) return 2; } else if (a->state == ST_UPTODATE || a->state == ST_ERROR) { if (in_rect(x, y, PAD, by, BTN_W-20, BTN_H)) return 2; } return 0; } static void on_click(Pkg_App *a, int x, int y) { int b = btn_at(a, x, y); if (b == 3) { XDestroyWindow(a->dpy, a->win); return; } if (b == 2) { run_check(a); return; } if (b == 1 && a->state == ST_UPDATES) { start_update(a); return; } /* package list click */ if (a->state == ST_UPDATES) { int list_y = BODY_Y + ROW_H; int vis = LIST_ROWS_VIS - 1; if (in_rect(x, y, 0, list_y, WIN_W, vis * ROW_H)) { int row = (y - list_y) / ROW_H + a->scroll; if (row >= 0 && row < a->npkgs) { /* selection, no action yet */ } } } } static void on_motion(Pkg_App *a, int x, int y) { int nb = btn_at(a, x, y); int np = -1; if (a->state == ST_UPDATES) { int list_y = BODY_Y + ROW_H; int vis = LIST_ROWS_VIS - 1; if (in_rect(x, y, 0, list_y, WIN_W, vis * ROW_H)) np = (y - list_y) / ROW_H + a->scroll; } if (nb != a->hover_btn || np != a->hover_pkg) { a->hover_btn = nb; a->hover_pkg = np; draw(a); } } static void on_scroll(Pkg_App *a, int dir) { if (a->state != ST_UPDATES) return; int vis = LIST_ROWS_VIS - 1; int max_scroll = a->npkgs - vis; a->scroll += dir; if (a->scroll < 0) a->scroll = 0; if (a->scroll > max_scroll) a->scroll = max_scroll > 0 ? max_scroll : 0; draw(a); } /* ── init ────────────────────────────────────────────────────────────── */ static void init(Pkg_App *a) { a->dpy = XOpenDisplay(NULL); if (!a->dpy) { fputs("orionpkg: cannot open display\n", stderr); exit(1); } a->screen = DefaultScreen(a->dpy); a->root = RootWindow(a->dpy, a->screen); a->cmap = DefaultColormap(a->dpy, a->screen); a->visual = DefaultVisual(a->dpy, a->screen); a->depth = DefaultDepth(a->dpy, a->screen); a->font_ui = XftFontOpenName(a->dpy, a->screen, FONT_UI); a->font_hdr = XftFontOpenName(a->dpy, a->screen, FONT_HDR); a->font_mono = XftFontOpenName(a->dpy, a->screen, FONT_MONO); if (!a->font_ui || !a->font_hdr) { fputs("orionpkg: cannot load fonts\n", stderr); exit(1); } a->state = ST_CHECKING; a->update_fd = -1; a->hover_btn = 0; a->hover_pkg = -1; XSetWindowAttributes swa = { .background_pixel = xcol(a, C_BG), .event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | KeyPressMask | StructureNotifyMask, }; a->win = XCreateWindow(a->dpy, a->root, 120, 120, WIN_W, WIN_H, 0, a->depth, InputOutput, a->visual, CWBackPixel | CWEventMask, &swa); a->gc = XCreateGC(a->dpy, a->win, 0, NULL); XStoreName(a->dpy, a->win, "OrionOS Package Manager"); XClassHint *ch = XAllocClassHint(); if (ch) { ch->res_name = (char *)"orionpkg"; ch->res_class = (char *)"OrionPkg"; XSetClassHint(a->dpy, a->win, ch); XFree(ch); } XSizeHints *sh = XAllocSizeHints(); if (sh) { sh->flags = PMinSize; sh->min_width = WIN_W; sh->min_height = 300; XSetWMNormalHints(a->dpy, a->win, sh); XFree(sh); } Atom wm_delete = XInternAtom(a->dpy, "WM_DELETE_WINDOW", False); Atom wm_protos = XInternAtom(a->dpy, "WM_PROTOCOLS", False); XChangeProperty(a->dpy, a->win, wm_protos, XA_ATOM, 32, PropModeReplace, (unsigned char *)&wm_delete, 1); XMapWindow(a->dpy, a->win); XFlush(a->dpy); } /* ── event loop ──────────────────────────────────────────────────────── */ static void run(Pkg_App *a) { Atom wm_delete = XInternAtom(a->dpy, "WM_DELETE_WINDOW", False); /* kick off initial check */ run_check(a); struct pollfd pfds[2]; pfds[0].fd = ConnectionNumber(a->dpy); pfds[0].events = POLLIN; for (;;) { pfds[1].fd = a->update_fd; pfds[1].events = (a->update_fd >= 0) ? POLLIN : 0; int nfds = (a->update_fd >= 0) ? 2 : 1; /* 200ms tick for spinner animation */ int r = poll(pfds, nfds, 200); if (a->update_fd >= 0 && pfds[1].revents & (POLLIN|POLLHUP)) read_update_output(a); /* spinner animation when checking */ if (a->state == ST_CHECKING || a->state == ST_UPDATING) { a->spin_frame++; if (a->state == ST_CHECKING) draw(a); } if (r <= 0 && a->update_fd < 0) { /* idle */ } while (XPending(a->dpy)) { XEvent ev; XNextEvent(a->dpy, &ev); switch (ev.type) { case Expose: if (ev.xexpose.count == 0) draw(a); break; case ButtonPress: if (ev.xbutton.button == Button1) on_click(a, ev.xbutton.x, ev.xbutton.y); else if (ev.xbutton.button == Button4) on_scroll(a, -3); else if (ev.xbutton.button == Button5) on_scroll(a, 3); break; case MotionNotify: on_motion(a, ev.xmotion.x, ev.xmotion.y); break; case KeyPress: { KeySym sym = XLookupKeysym(&ev.xkey, 0); if (sym == XK_Escape || sym == XK_q) return; if (sym == XK_r) run_check(a); if (sym == XK_Up) on_scroll(a, -3); if (sym == XK_Down) on_scroll(a, 3); break; } case ClientMessage: if ((Atom)ev.xclient.data.l[0] == wm_delete) return; break; case DestroyNotify: return; } } } } int main(void) { Pkg_App a = {0}; init(&a); run(&a); return 0; }