From 0ed61cea4c096ca05dffed3edbc4f6598afdbcc2 Mon Sep 17 00:00:00 2001 From: nmemmert <67961292+nmemmert@users.noreply.github.com> Date: Thu, 23 Jul 2026 14:39:00 -0400 Subject: [PATCH] Add orionpkg (package manager GUI) + installer locale step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit orionpkg/orionpkg.c (~500 lines, native Xlib/Xft): - Launches with checkupdates (pacman-contrib) to list pending upgrades - ST_CHECKING: animated spinner while querying - ST_UPDATES: scrollable package list with name / old ver → new ver columns - ST_UPDATING: pkexec pacman -Syu in background, live log output, progress bar estimated from pacman's (N/M) counters - ST_DONE / ST_ERROR: success or failure screen - Refresh (r key), scroll (mouse wheel / arrow keys), Close (Esc/q) - pacman-contrib added to ISO packages Installer: add STEP_LOCALE (timezone + keyboard layout): - Inserted between STEP_DISK and STEP_USER - Timezone pane: searchable filter input + scrollable list of 37 zones - Keyboard pane: 20 common layouts with code + description - Timezone and keyboard passed as args 6/7 to orioninstall - orioninstall: sets /etc/vconsole.conf and X11 xorg.conf.d keyboard config from args; uses selected timezone for localtime symlink Also: - Right-click menu: "Software Updates" → orionpkg - Root Makefile: orionpkg target + install rule - ISO: orionpkg binary installed, permissions map, gitignore Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 1 + Makefile | 9 +- installer/orioninstall | 21 +- installer/orioninstall-gui.c | 240 +++++++++++- iso/docker/entrypoint.sh | 3 + orionpkg/Makefile | 15 + orionpkg/orionpkg.c | 716 +++++++++++++++++++++++++++++++++++ wm/src/menu.c | 1 + 8 files changed, 1000 insertions(+), 6 deletions(-) create mode 100644 orionpkg/Makefile create mode 100644 orionpkg/orionpkg.c diff --git a/.gitignore b/.gitignore index b116f12..e09d489 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.o deskbar/oriondeskbar nebula/nebula +orionpkg/orionpkg installer/orioninstall installer/orioninstall-gui launcher/orionlauncher diff --git a/Makefile b/Makefile index 55898c0..ead1c80 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,9 @@ # make clean — remove build artifacts # make iso — build the bootable ISO (requires Arch Linux + archiso) -.PHONY: all wm deskbar launcher installer nebula clean install iso +.PHONY: all wm deskbar launcher installer nebula orionpkg clean install iso -all: wm deskbar launcher installer nebula +all: wm deskbar launcher installer nebula orionpkg wm: $(MAKE) -C wm @@ -24,12 +24,16 @@ installer: nebula: $(MAKE) -C nebula +orionpkg: + $(MAKE) -C orionpkg + clean: $(MAKE) -C wm clean $(MAKE) -C deskbar clean $(MAKE) -C launcher clean $(MAKE) -C installer clean $(MAKE) -C nebula clean + $(MAKE) -C orionpkg clean install: all # binaries @@ -38,6 +42,7 @@ install: all install -Dm755 launcher/orionlauncher /usr/local/bin/orionlauncher install -Dm755 themes/orionwallpaper /usr/local/bin/orionwallpaper install -Dm755 nebula/nebula /usr/local/bin/nebula + install -Dm755 orionpkg/orionpkg /usr/local/bin/orionpkg # GTK theme mkdir -p /usr/share/themes cp -r themes/OrionOS /usr/share/themes/ diff --git a/installer/orioninstall b/installer/orioninstall index 26124eb..27d6c97 100644 --- a/installer/orioninstall +++ b/installer/orioninstall @@ -18,7 +18,9 @@ if [[ "${1:-}" == "--gui" ]]; then HOSTNAME="${3:?missing hostname}" USERNAME="${4:?missing username}" PASSWORD="${5:?missing password}" - shift 5 2>/dev/null || true + TIMEZONE="${6:-UTC}" + KEYBOARD="${7:-us}" + shift 7 2>/dev/null || true fi # ── output helpers ──────────────────────────────────────────────────── @@ -193,8 +195,23 @@ sed -i 's/#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen 2>/dev/null || locale-gen echo "LANG=en_US.UTF-8" > /etc/locale.conf +# Console keyboard layout +echo "KEYMAP=${KEYBOARD:-us}" > /etc/vconsole.conf + +# X11 keyboard layout +mkdir -p /etc/X11/xorg.conf.d +cat > /etc/X11/xorg.conf.d/10-keyboard.conf </dev/null || true # Remove live-only archiso package to prevent mkinitcpio confusion diff --git a/installer/orioninstall-gui.c b/installer/orioninstall-gui.c index 49bb94d..a9ebf66 100644 --- a/installer/orioninstall-gui.c +++ b/installer/orioninstall-gui.c @@ -84,6 +84,7 @@ typedef enum { STEP_WELCOME = 0, STEP_DISK, + STEP_LOCALE, /* timezone + keyboard layout */ STEP_USER, STEP_REVIEW, STEP_INSTALL, @@ -140,6 +141,15 @@ typedef struct { int ndisks; int sel_disk; + /* locale selection */ + int tz_sel; /* index into TZ_LIST */ + int tz_scroll; /* first visible row in timezone pane */ + char tz_filter[48]; /* live filter text */ + int tz_filter_len; + int tz_filter_focus; + int kb_sel; /* index into KB_LIST */ + int kb_scroll; + /* user fields */ TF tf_host, tf_user, tf_pass, tf_pass2; TF *focused_tf; @@ -422,7 +432,7 @@ static void draw_footer(Ins *s, XftDraw *xd, draw_line(s, 0, fy, WIN_W, fy, C_BORDER); /* step dots */ - int ndots = 5; /* steps 0-4 shown as dots (5 is done) */ + int ndots = 6; /* steps 0-5 shown as dots (6 is done) */ int dot_step = (int)s->step < ndots ? (int)s->step : ndots-1; for (int i = 0; i < ndots; i++) { int dx = WIN_W/2 - (ndots*14)/2 + i*14 + 7; @@ -494,6 +504,164 @@ static int validate_step(Ins *s) { /* Step rendering */ /* ══════════════════════════════════════════════════════════════════ */ +/* ── locale data ─────────────────────────────────────────────────── */ +static const char *TZ_LIST[] = { + "UTC", + "America/New_York", + "America/Chicago", + "America/Denver", + "America/Los_Angeles", + "America/Anchorage", + "America/Honolulu", + "America/Toronto", + "America/Vancouver", + "America/Mexico_City", + "America/Sao_Paulo", + "America/Buenos_Aires", + "Europe/London", + "Europe/Paris", + "Europe/Berlin", + "Europe/Rome", + "Europe/Madrid", + "Europe/Amsterdam", + "Europe/Stockholm", + "Europe/Warsaw", + "Europe/Moscow", + "Europe/Istanbul", + "Asia/Dubai", + "Asia/Kolkata", + "Asia/Dhaka", + "Asia/Bangkok", + "Asia/Shanghai", + "Asia/Tokyo", + "Asia/Seoul", + "Asia/Singapore", + "Australia/Sydney", + "Australia/Perth", + "Pacific/Auckland", + "Pacific/Honolulu", + "Africa/Cairo", + "Africa/Johannesburg", + "Africa/Lagos", +}; +#define NTZ ((int)(sizeof(TZ_LIST)/sizeof(TZ_LIST[0]))) + +typedef struct { const char *code; const char *label; } KBLayout; +static const KBLayout KB_LIST[] = { + { "us", "US English" }, + { "gb", "UK English" }, + { "de", "German" }, + { "fr", "French" }, + { "es", "Spanish" }, + { "it", "Italian" }, + { "pt", "Portuguese" }, + { "br", "Portuguese (BR)" }, + { "ru", "Russian" }, + { "pl", "Polish" }, + { "nl", "Dutch" }, + { "sv", "Swedish" }, + { "no", "Norwegian" }, + { "da", "Danish" }, + { "fi", "Finnish" }, + { "tr", "Turkish" }, + { "ar", "Arabic" }, + { "jp", "Japanese" }, + { "kr", "Korean" }, + { "cn", "Chinese (Pinyin)" }, +}; +#define NKB ((int)(sizeof(KB_LIST)/sizeof(KB_LIST[0]))) + +#define LC_LIST_H 220 /* pixel height of each list pane */ +#define LC_ROW_H 22 /* row height in list */ +#define LC_ROWS_VIS (LC_LIST_H / LC_ROW_H) +#define LC_PANE_W ((WIN_W - 60) / 2 - 6) + +static void draw_locale(Ins *s, XftDraw *xd) { + draw_header(s, xd, "Timezone & Keyboard"); + fill_rect(s, 0, BODY_Y, WIN_W, BODY_H, C_BODY); + + int lx = 30, rx = WIN_W/2 + 4; + int ty = BODY_Y + 14; + + /* ── Timezone pane ── */ + xft_text(s, xd, s->f_head, C_TXT, lx, ty, "Timezone"); + + /* filter input */ + int fy = ty + 22; + int factive = s->tz_filter_focus; + unsigned long fbd = factive ? C_INPUT_ACT : C_INPUT_BD; + fill_rect(s, lx, fy, LC_PANE_W, 26, C_INPUT_BG); + XSetForeground(s->dpy, s->gc, mkcolor(s, fbd)); + XDrawRectangle(s->dpy, s->win, s->gc, lx, fy, LC_PANE_W-1, 25); + const char *fdisp = s->tz_filter_len ? s->tz_filter : ""; + xft_text(s, xd, s->f_body, s->tz_filter_len ? C_TXT_INP : C_TXT_DIM, + lx + 6, fy + 17, s->tz_filter_len ? fdisp : "Filter…"); + if (factive) { + int cx = lx + 6 + text_w(s, s->f_body, fdisp); + XSetForeground(s->dpy, s->gc, mkcolor(s, C_TXT_INP)); + XDrawLine(s->dpy, s->win, s->gc, cx, fy + 4, cx, fy + 21); + } + + /* timezone list */ + int ly = fy + 32; + fill_rect(s, lx, ly, LC_PANE_W, LC_LIST_H, C_PANEL); + XSetForeground(s->dpy, s->gc, mkcolor(s, C_SEP)); + XDrawRectangle(s->dpy, s->win, s->gc, lx, ly, LC_PANE_W-1, LC_LIST_H-1); + + int vis = 0, row = 0; + for (int i = 0; i < NTZ && vis < LC_ROWS_VIS + s->tz_scroll; i++) { + if (s->tz_filter_len && + !strstr(TZ_LIST[i], s->tz_filter)) continue; + if (row++ < s->tz_scroll) continue; + int ry2 = ly + vis * LC_ROW_H; + int actual_i = i; /* track real index */ + if (actual_i == s->tz_sel) + fill_rect(s, lx+1, ry2, LC_PANE_W-2, LC_ROW_H, C_RADIO_SEL); + else if (vis % 2 == 1) + fill_rect(s, lx+1, ry2, LC_PANE_W-2, LC_ROW_H, 0x141C2C); + xft_text(s, xd, s->f_small, + actual_i == s->tz_sel ? C_TXT_HDR : C_TXT, + lx + 8, ry2 + LC_ROW_H - 5, TZ_LIST[i]); + vis++; + } + + /* ── Keyboard pane ── */ + xft_text(s, xd, s->f_head, C_TXT, rx, ty, "Keyboard Layout"); + + int kly = ty + 22 + 32; /* align with timezone list (below filter) */ + fill_rect(s, rx, kly, LC_PANE_W, LC_LIST_H + 26, C_PANEL); + XSetForeground(s->dpy, s->gc, mkcolor(s, C_SEP)); + XDrawRectangle(s->dpy, s->win, s->gc, rx, kly, LC_PANE_W-1, LC_LIST_H+25); + + int kvis = NKB < LC_ROWS_VIS + 1 ? NKB : LC_ROWS_VIS + 1; + for (int i = s->kb_scroll; i < NKB && (i - s->kb_scroll) < kvis; i++) { + int ky = kly + (i - s->kb_scroll) * LC_ROW_H; + if (i == s->kb_sel) + fill_rect(s, rx+1, ky, LC_PANE_W-2, LC_ROW_H, C_RADIO_SEL); + else if ((i - s->kb_scroll) % 2 == 1) + fill_rect(s, rx+1, ky, LC_PANE_W-2, LC_ROW_H, 0x141C2C); + char kbuf[48]; + snprintf(kbuf, sizeof(kbuf), "[%s] %s", + KB_LIST[i].code, KB_LIST[i].label); + xft_text(s, xd, s->f_small, + i == s->kb_sel ? C_TXT_HDR : C_TXT, + rx + 8, ky + LC_ROW_H - 5, kbuf); + } + + /* selected values footer */ + int sy2 = BODY_Y + BODY_H - FOOT_H - 4; + char sel_tz[80], sel_kb[80]; + snprintf(sel_tz, sizeof(sel_tz), "Timezone: %s", + s->tz_sel >= 0 ? TZ_LIST[s->tz_sel] : "UTC"); + snprintf(sel_kb, sizeof(sel_kb), " | Layout: %s", + KB_LIST[s->kb_sel].code); + xft_text(s, xd, s->f_small, C_TXT_DIM, lx, sy2, sel_tz); + xft_text(s, xd, s->f_small, C_TXT_DIM, + lx + text_w(s, s->f_small, sel_tz), sy2, sel_kb); + + draw_footer(s, xd, "← Back", "Next →", C_BTN_PRI); +} + static void draw_welcome(Ins *s, XftDraw *xd) { draw_header(s, xd, "Welcome"); fill_rect(s, 0, BODY_Y, WIN_W, BODY_H, C_BODY); @@ -746,6 +914,7 @@ static void redraw(Ins *s) { switch (s->step) { case STEP_WELCOME: draw_welcome(s, xd); break; case STEP_DISK: draw_disk(s, xd); break; + case STEP_LOCALE: draw_locale(s, xd); break; case STEP_USER: draw_user(s, xd); break; case STEP_REVIEW: draw_review(s, xd); break; case STEP_INSTALL: draw_install(s, xd); break; @@ -793,6 +962,8 @@ static void start_install(Ins *s) { s->tf_host.buf, s->tf_user.buf, s->tf_pass.buf, + s->tz_sel >= 0 ? TZ_LIST[s->tz_sel] : "UTC", + KB_LIST[s->kb_sel].code, NULL); fprintf(stderr, "exec orioninstall failed: %s\n", strerror(errno)); _exit(1); @@ -899,7 +1070,8 @@ static void handle_click(Ins *s, int x, int y) { s->step = STEP_DISK; if (s->ndisks == 0) detect_disks(s); break; - case STEP_DISK: s->step = STEP_USER; break; + case STEP_DISK: s->step = STEP_LOCALE; break; + case STEP_LOCALE: s->step = STEP_USER; break; case STEP_USER: s->step = STEP_REVIEW; break; case STEP_REVIEW: s->step = STEP_INSTALL; @@ -942,6 +1114,41 @@ static void handle_click(Ins *s, int x, int y) { } } + /* locale pane clicks */ + if (s->step == STEP_LOCALE) { + int lx = 30, rx = WIN_W/2 + 4; + int ty = BODY_Y + 14; + int fy = ty + 22; + /* filter input focus */ + if (in_rect(x, y, lx, fy, LC_PANE_W, 26)) { + s->tz_filter_focus = 1; + redraw(s); return; + } else { + s->tz_filter_focus = 0; + } + /* timezone list */ + int ly = fy + 32; + if (in_rect(x, y, lx, ly, LC_PANE_W, LC_LIST_H)) { + int vis = 0, row = 0; + int clicked_row = (y - ly) / LC_ROW_H; + for (int i = 0; i < NTZ; i++) { + if (s->tz_filter_len && !strstr(TZ_LIST[i], s->tz_filter)) continue; + if (row++ < s->tz_scroll) continue; + if (vis == clicked_row) { s->tz_sel = i; break; } + vis++; + } + redraw(s); return; + } + /* keyboard list */ + int kly = ty + 22 + 32; + if (in_rect(x, y, rx, kly, LC_PANE_W, LC_LIST_H + 26)) { + int row2 = (y - kly) / LC_ROW_H + s->kb_scroll; + if (row2 >= 0 && row2 < NKB) { s->kb_sel = row2; } + redraw(s); return; + } + redraw(s); return; + } + /* text fields */ if (s->step == STEP_USER) { TF *hit = tf_at(s, x, y); @@ -955,6 +1162,33 @@ static void handle_click(Ins *s, int x, int y) { static void handle_key(Ins *s, XKeyEvent *e) { KeySym sym = XLookupKeysym(e, 0); + /* Locale step: typing into the timezone filter */ + if (s->step == STEP_LOCALE && s->tz_filter_focus) { + if (sym == XK_BackSpace && s->tz_filter_len > 0) { + s->tz_filter[--s->tz_filter_len] = '\0'; + s->tz_scroll = 0; + redraw(s); return; + } + if (sym == XK_Escape) { + s->tz_filter_len = 0; s->tz_filter[0] = '\0'; + s->tz_scroll = 0; + redraw(s); return; + } + /* scroll list with arrow keys */ + if (sym == XK_Down) { s->tz_scroll++; redraw(s); return; } + if (sym == XK_Up && s->tz_scroll > 0) { s->tz_scroll--; redraw(s); return; } + char buf[8]; int len = XLookupString(e, buf, sizeof(buf)-1, NULL, NULL); + if (len > 0 && (unsigned char)buf[0] >= 32 + && s->tz_filter_len < (int)sizeof(s->tz_filter) - 2) { + buf[len] = '\0'; + strcat(s->tz_filter, buf); + s->tz_filter_len += len; + s->tz_scroll = 0; + redraw(s); return; + } + return; + } + /* Tab cycles text fields */ if (sym == XK_Tab && s->step == STEP_USER) { TF *order[] = { &s->tf_host, &s->tf_user, &s->tf_pass, &s->tf_pass2 }; @@ -1054,6 +1288,8 @@ static void init(Ins *s) { /* defaults */ s->step = STEP_WELCOME; s->sel_disk = -1; + s->tz_sel = 0; /* UTC */ + s->kb_sel = 0; /* us */ s->install_fd = -1; s->progress = 0; snprintf(s->prog_msg, sizeof(s->prog_msg), "Starting…"); diff --git a/iso/docker/entrypoint.sh b/iso/docker/entrypoint.sh index b24ddcf..2a3798a 100755 --- a/iso/docker/entrypoint.sh +++ b/iso/docker/entrypoint.sh @@ -87,6 +87,7 @@ xclip scrot parcellite papirus-icon-theme +pacman-contrib zip unzip p7zip @@ -231,6 +232,7 @@ install -m755 "$SRC/deskbar/oriondeskbar" "$AIROOTFS/usr/local/bin/oriondeskb install -m755 "$SRC/launcher/orionlauncher" "$AIROOTFS/usr/local/bin/orionlauncher" install -m755 "$SRC/themes/orionwallpaper" "$AIROOTFS/usr/local/bin/orionwallpaper" install -m755 "$SRC/nebula/nebula" "$AIROOTFS/usr/local/bin/nebula" +install -m755 "$SRC/orionpkg/orionpkg" "$AIROOTFS/usr/local/bin/orionpkg" install -m755 "$SRC/installer/orioninstall" "$AIROOTFS/usr/local/bin/orioninstall" # GUI installer (compiled binary; falls back gracefully if not built) if [[ -f "$SRC/installer/orioninstall-gui" ]]; then @@ -690,6 +692,7 @@ file_permissions=( ["/usr/local/bin/orionlauncher"]="0:0:755" ["/usr/local/bin/orionwallpaper"]="0:0:755" ["/usr/local/bin/nebula"]="0:0:755" + ["/usr/local/bin/orionpkg"]="0:0:755" ["/usr/local/bin/orioninstall"]="0:0:755" ) PROFILEDEF diff --git a/orionpkg/Makefile b/orionpkg/Makefile new file mode 100644 index 0000000..042cefe --- /dev/null +++ b/orionpkg/Makefile @@ -0,0 +1,15 @@ +BIN = orionpkg + +CC = gcc +CFLAGS = -Wall -Wextra -O2 \ + $(shell pkg-config --cflags x11 xft fontconfig 2>/dev/null) +LIBS = $(shell pkg-config --libs x11 xft fontconfig 2>/dev/null) \ + -lX11 -lXft -lfontconfig + +$(BIN): orionpkg.c + $(CC) $(CFLAGS) -o $@ $< $(LIBS) + +clean: + rm -f $(BIN) + +.PHONY: clean diff --git a/orionpkg/orionpkg.c b/orionpkg/orionpkg.c new file mode 100644 index 0000000..9305fe0 --- /dev/null +++ b/orionpkg/orionpkg.c @@ -0,0 +1,716 @@ +/* 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; +} diff --git a/wm/src/menu.c b/wm/src/menu.c index f474c08..c91a3d2 100644 --- a/wm/src/menu.c +++ b/wm/src/menu.c @@ -14,6 +14,7 @@ static const MenuItem MENU_ITEMS[] = { { "System Monitor", "alacritty -T 'System Monitor' -e htop &" }, { "Volume Control", "pavucontrol &" }, { "System Settings", "nebula &" }, + { "Software Updates", "orionpkg &" }, { "Display Settings", "arandr &" }, { "──────────────────", NULL }, { "Change Wallpaper", "orionwallpaper --reset &" },