From 803ffe991f718746d168a78af933da89cb937ef8 Mon Sep 17 00:00:00 2001 From: nmemmert <67961292+nmemmert@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:32:27 -0400 Subject: [PATCH] Phase 1/2 complete: system tray (XEMBED) + Plymouth boot splash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit System tray in skydeskbar (Phase 1 #4): - Claims _NET_SYSTEM_TRAY_S selection on startup and broadcasts MANAGER so nm-applet and other tray clients connect - Handles _NET_SYSTEM_TRAY_OPCODE dock requests: XReparentWindow icon into the deskbar, sends XEMBED_EMBEDDED_NOTIFY - Tray section is zero-height when empty; expands to DB_TRAY_H=26px when first icon embeds, shrinking back when last icon exits - DestroyNotify / ReparentNotify clean up the icon list and reposition remaining icons; SelectionClear hands off gracefully - Icon positions are updated (db_tray_reposition) whenever the app list changes and shifts the tray section vertically Plymouth boot splash (Phase 2 #10): - 'skyos' script-module theme: dark navy (#111520) background, "SkyOS" logo in accent blue (#90BBEE), thin progress bar - 1×1 pixel bar_bg.png / bar_fg.png scaled at runtime by the Plymouth script — no large PNGs committed to the repo - logo.png generated at build time by ImageMagick (same tool used for the wallpaper); build continues if ImageMagick is absent - mkinitcpio HOOKS: added 'plymouth' after 'udev' - Kernel cmdline: added 'splash' for UEFI systemd-boot entry - customize_airootfs.sh: plymouth-set-default-theme skyos Co-Authored-By: Claude Sonnet 4.6 --- deskbar/include/deskbar.h | 15 +++- deskbar/src/deskbar.c | 139 +++++++++++++++++++++++++++++++++++--- iso/docker/entrypoint.sh | 85 ++++++++++++++++++++++- 3 files changed, 227 insertions(+), 12 deletions(-) diff --git a/deskbar/include/deskbar.h b/deskbar/include/deskbar.h index 100dba6..2dffa4d 100644 --- a/deskbar/include/deskbar.h +++ b/deskbar/include/deskbar.h @@ -25,7 +25,9 @@ * ├──────────────────────────┤ * │ [1] [2] [3] [4] │ <- workspace strip * ├──────────────────────────┤ - * │ CPU 12% MEM 45% │ <- status bar + * │ [⊙][📶][🔋] │ <- system tray (XEMBED; hidden when empty) + * ├──────────────────────────┤ + * │ VOL 50% CPU 12% MEM 45%│ <- status bar * └──────────────────────────┘ */ @@ -37,6 +39,9 @@ #define DB_ICON_SZ 18 /* app icon square size */ #define DB_WS_H 28 /* workspace strip height */ #define DB_STATUS_H 22 /* status bar height */ +#define DB_TRAY_H 26 /* tray section height when icons are present */ +#define DB_TRAY_ICON_SZ 20 /* embedded icon window size */ +#define DB_TRAY_MAX 16 /* maximum simultaneous tray icons */ #define DB_MAX_APPS 32 /* ── fly-out menu ────────────────────────────────────────────────────── */ @@ -133,6 +138,14 @@ typedef struct Deskbar { int sub_active; /* which sub-menu is open: 0=Apps, 1=Settings, -1=none */ int sub_hov; /* hovered item in sub-menu */ int sw_cache; /* cached screen width for menu positioning */ + + /* system tray (freedesktop.org XEmbed tray protocol) */ + Window tray_icons[DB_TRAY_MAX]; + int n_tray_icons; + Atom atom_tray_s0; /* _NET_SYSTEM_TRAY_S */ + Atom atom_tray_opcode; /* _NET_SYSTEM_TRAY_OPCODE */ + Atom atom_xembed; /* _XEMBED */ + Atom atom_manager; /* MANAGER */ } Deskbar; void db_init(Deskbar *db); diff --git a/deskbar/src/deskbar.c b/deskbar/src/deskbar.c index bb0f721..653cc46 100644 --- a/deskbar/src/deskbar.c +++ b/deskbar/src/deskbar.c @@ -1,5 +1,9 @@ #include "deskbar.h" +#define TRAY_REQUEST_DOCK 0 +#define XEMBED_EMBEDDED_NOTIFY 0 +#define XEMBED_VERSION 0 + /* ── layout ───────────────────────────────────────────────────────────── */ int db_apps_y(void) { return DB_TOP_H + 1; } @@ -8,8 +12,11 @@ int db_ws_y(Deskbar *db) { return db_apps_y() + db->napps * DB_ITEM_H + 1; } +static int db_tray_h(Deskbar *db) { return db->n_tray_icons > 0 ? DB_TRAY_H : 0; } +static int db_tray_y(Deskbar *db) { return db_ws_y(db) + DB_WS_H + 1; } static int db_status_y(Deskbar *db) { - return db_ws_y(db) + DB_WS_H + 1; + int th = db_tray_h(db); + return db_tray_y(db) + th + (th > 0 ? 1 : 0); } void db_resize(Deskbar *db) { @@ -141,6 +148,65 @@ void db_update_workspace(Deskbar *db) { db->num_ws = (nws > 0) ? (int)nws : 4; } +/* ── system tray (XEmbed) ─────────────────────────────────────────────── */ + +static void db_tray_reposition(Deskbar *db) { + int ty = db_tray_y(db); + int iy = (DB_TRAY_H - DB_TRAY_ICON_SZ) / 2; + for (int i = 0; i < db->n_tray_icons; i++) { + int ix = 4 + i * (DB_TRAY_ICON_SZ + 4); + XMoveWindow(db->dpy, db->tray_icons[i], ix, ty + iy); + } +} + +static void db_tray_dock(Deskbar *db, Window icon_win) { + if (db->n_tray_icons >= DB_TRAY_MAX) return; + for (int i = 0; i < db->n_tray_icons; i++) + if (db->tray_icons[i] == icon_win) return; + + int ty = db_tray_y(db); + int iy = (DB_TRAY_H - DB_TRAY_ICON_SZ) / 2; + int ix = 4 + db->n_tray_icons * (DB_TRAY_ICON_SZ + 4); + int was_empty = (db->n_tray_icons == 0); + + XSelectInput(db->dpy, icon_win, StructureNotifyMask); + XReparentWindow(db->dpy, icon_win, db->win, ix, ty + iy); + XResizeWindow(db->dpy, icon_win, DB_TRAY_ICON_SZ, DB_TRAY_ICON_SZ); + XMapWindow(db->dpy, icon_win); + + /* XEMBED_EMBEDDED_NOTIFY message to the icon */ + XEvent ev = {0}; + ev.xclient.type = ClientMessage; + ev.xclient.window = icon_win; + ev.xclient.message_type = db->atom_xembed; + ev.xclient.format = 32; + ev.xclient.data.l[0] = CurrentTime; + ev.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY; + ev.xclient.data.l[2] = 0; + ev.xclient.data.l[3] = db->win; + ev.xclient.data.l[4] = XEMBED_VERSION; + XSendEvent(db->dpy, icon_win, False, NoEventMask, &ev); + + db->tray_icons[db->n_tray_icons++] = icon_win; + if (was_empty) db_resize(db); + db_draw(db); +} + +static void db_tray_remove(Deskbar *db, Window icon_win) { + int pos = -1; + for (int i = 0; i < db->n_tray_icons; i++) + if (db->tray_icons[i] == icon_win) { pos = i; break; } + if (pos < 0) return; + + memmove(&db->tray_icons[pos], &db->tray_icons[pos + 1], + (db->n_tray_icons - pos - 1) * sizeof(Window)); + db->n_tray_icons--; + + db_tray_reposition(db); + if (db->n_tray_icons == 0) db_resize(db); + db_draw(db); +} + /* ── system status ────────────────────────────────────────────────────── */ static int read_cpu_percent(void) { @@ -429,6 +495,17 @@ void db_draw(Deskbar *db) { XDrawLine(db->dpy, db->win, db->gc, 0, wy + DB_WS_H, DB_W, wy + DB_WS_H); + /* ── SYSTEM TRAY ── */ + if (db->n_tray_icons > 0) { + int ty = db_tray_y(db); + XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_BG)); + XFillRectangle(db->dpy, db->win, db->gc, 0, ty, DB_W, DB_TRAY_H); + + /* separator below tray */ + XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_BORDER_D)); + XDrawLine(db->dpy, db->win, db->gc, 0, ty + DB_TRAY_H, DB_W, ty + DB_TRAY_H); + } + /* ── STATUS BAR ── */ int sy = db_status_y(db); XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_BG)); @@ -800,13 +877,14 @@ void db_init(Deskbar *db) { db->cmap = DefaultColormap(db->dpy, db->screen); db->visual = DefaultVisual(db->dpy, db->screen); db->depth = DefaultDepth(db->dpy, db->screen); - db->hovered = -1; - db->menu_win = None; - db->sub_win = None; - db->menu_hov = -1; - db->sub_active = -1; - db->sub_hov = -1; - db->sw_cache = 0; + db->hovered = -1; + db->menu_win = None; + db->sub_win = None; + db->menu_hov = -1; + db->sub_active = -1; + db->sub_hov = -1; + db->sw_cache = 0; + db->n_tray_icons = 0; db->net_client_list = XInternAtom(db->dpy, "_NET_CLIENT_LIST", False); db->net_active_window = XInternAtom(db->dpy, "_NET_ACTIVE_WINDOW", False); @@ -815,6 +893,15 @@ void db_init(Deskbar *db) { db->net_wm_name = XInternAtom(db->dpy, "_NET_WM_NAME", False); db->utf8_string = XInternAtom(db->dpy, "UTF8_STRING", False); + { + char sel_name[32]; + snprintf(sel_name, sizeof(sel_name), "_NET_SYSTEM_TRAY_S%d", db->screen); + db->atom_tray_s0 = XInternAtom(db->dpy, sel_name, False); + } + db->atom_tray_opcode = XInternAtom(db->dpy, "_NET_SYSTEM_TRAY_OPCODE", False); + db->atom_xembed = XInternAtom(db->dpy, "_XEMBED", False); + db->atom_manager = XInternAtom(db->dpy, "MANAGER", False); + db->font_ui = XftFontOpenName(db->dpy, db->screen, DB_FONT_UI); db->font_menu = XftFontOpenName(db->dpy, db->screen, DB_FONT_MENU); db->font_clock = XftFontOpenName(db->dpy, db->screen, DB_FONT_CLOCK); @@ -829,7 +916,7 @@ void db_init(Deskbar *db) { db_update_apps(db); db_update_workspace(db); - db->bar_h = DB_TOP_H + 1 + db->napps * DB_ITEM_H + 1 + DB_WS_H + 1 + DB_STATUS_H + 2; + db->bar_h = db_status_y(db) + DB_STATUS_H + 2; int sw = DisplayWidth(db->dpy, db->screen); @@ -847,6 +934,20 @@ void db_init(Deskbar *db) { CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); db->gc = XCreateGC(db->dpy, db->win, 0, NULL); + /* Claim _NET_SYSTEM_TRAY_S selection and announce ourselves */ + XSetSelectionOwner(db->dpy, db->atom_tray_s0, db->win, CurrentTime); + if (XGetSelectionOwner(db->dpy, db->atom_tray_s0) == db->win) { + XEvent sev = {0}; + sev.xclient.type = ClientMessage; + sev.xclient.window = db->root; + sev.xclient.message_type = db->atom_manager; + sev.xclient.format = 32; + sev.xclient.data.l[0] = CurrentTime; + sev.xclient.data.l[1] = db->atom_tray_s0; + sev.xclient.data.l[2] = db->win; + XSendEvent(db->dpy, db->root, False, StructureNotifyMask, &sev); + } + Atom net_wm_window_type = XInternAtom(db->dpy, "_NET_WM_WINDOW_TYPE", False); Atom net_wm_type_dock = XInternAtom(db->dpy, "_NET_WM_WINDOW_TYPE_DOCK", False); XChangeProperty(db->dpy, db->win, net_wm_window_type, XA_ATOM, 32, @@ -943,10 +1044,30 @@ void db_run(Deskbar *db) { e.xvisibility.state != VisibilityUnobscured) XRaiseWindow(db->dpy, db->win); break; + case ClientMessage: + if (e.xclient.message_type == db->atom_tray_opcode && + e.xclient.data.l[1] == TRAY_REQUEST_DOCK) + db_tray_dock(db, (Window)e.xclient.data.l[2]); + break; + case DestroyNotify: + db_tray_remove(db, e.xdestroywindow.window); + break; + case ReparentNotify: + if (e.xreparent.parent != db->win) + db_tray_remove(db, e.xreparent.window); + break; + case SelectionClear: + if (e.xselectionclear.selection == db->atom_tray_s0) { + db->n_tray_icons = 0; + db_resize(db); + db_draw(db); + } + break; case PropertyNotify: if (e.xproperty.atom == db->net_client_list) { /* Window list changed — full refresh + resize */ db_update_apps(db); + db_tray_reposition(db); db_resize(db); db_draw(db); } else if (e.xproperty.atom == db->net_active_window) { diff --git a/iso/docker/entrypoint.sh b/iso/docker/entrypoint.sh index 8aba51d..2269715 100755 --- a/iso/docker/entrypoint.sh +++ b/iso/docker/entrypoint.sh @@ -101,6 +101,7 @@ git sudo bash-completion fastfetch +plymouth PKGS # ── 3. airootfs overlay — files that land in the live system ───────── @@ -188,7 +189,7 @@ cat > "$AIROOTFS/etc/mkinitcpio.conf.d/archiso.conf" <<'MKCONF' MODULES=() BINARIES=() FILES=() -HOOKS=(base udev microcode archiso block filesystems keyboard) +HOOKS=(base udev plymouth microcode archiso block filesystems keyboard) MKCONF echo ">>> Live initramfs HOOKS: base udev microcode archiso block filesystems keyboard" @@ -531,6 +532,83 @@ gtk-icon-theme-name=Adwaita gtk-cursor-theme-name=DMZ-White GTK3 +# ── Plymouth dark navy boot splash ─────────────────────────────────── +echo ">>> Creating Plymouth theme..." +PLYM="$AIROOTFS/usr/share/plymouth/themes/skyos" +mkdir -p "$PLYM" + +cat > "$PLYM/skyos.plymouth" <<'PLYMDEF' +[Plymouth Theme] +Name=SkyOS +Description=SkyOS dark navy boot splash +ModuleName=script + +[script] +ImageDir=/usr/share/plymouth/themes/skyos +ScriptFile=/usr/share/plymouth/themes/skyos/skyos.script +PLYMDEF + +cat > "$PLYM/skyos.script" <<'PLYMSCRIPT' +# SkyOS Plymouth boot splash — dark navy + accent blue progress bar + +W = Window.GetWidth(); +H = Window.GetHeight(); + +# Solid dark navy background +Window.SetBackgroundTopColor(0.067, 0.082, 0.125); +Window.SetBackgroundBottomColor(0.040, 0.055, 0.090); + +# Logo (centered, upper third of screen) +logo = Image("logo.png"); +logo_sprite = Sprite(logo); +logo_sprite.SetPosition( + Math.Int((W - logo.GetWidth()) / 2), + Math.Int(H * 0.40 - logo.GetHeight() / 2), + 1 +); + +# Progress bar +BAR_W = Math.Int(Math.Min(W * 0.42, 440)); +BAR_H = 4; +bar_x = Math.Int((W - BAR_W) / 2); +bar_y = Math.Int(H * 0.72); + +# Bar background track (dark blue-grey) +bar_bg = Image("bar_bg.png").Scale(BAR_W, BAR_H); +bar_bg_sprite = Sprite(bar_bg); +bar_bg_sprite.SetPosition(bar_x, bar_y, 2); + +# Bar fill (accent blue, grows with progress) +bar_fill_sprite = Sprite(); +bar_fill_sprite.SetPosition(bar_x, bar_y, 3); + +fun boot_progress_callback(time, progress) { + fill_w = Math.Int(BAR_W * progress); + if (fill_w < 2) fill_w = 2; + bar_fill = Image("bar_fg.png").Scale(fill_w, BAR_H); + bar_fill_sprite.SetImage(bar_fill); +} + +Plymouth.SetBootProgressFunction(boot_progress_callback); +PLYMSCRIPT + +# Generate PNG assets (ImageMagick is used above for the wallpaper) +if command -v convert &>/dev/null; then + # Logo: "SkyOS" in accent blue on transparent background + convert -size 340x90 xc:none \ + -font DejaVu-Sans-Bold -pointsize 62 \ + -fill "#90BBEE" -gravity center \ + -annotate 0 "SkyOS" \ + "$PLYM/logo.png" 2>/dev/null \ + && echo " Plymouth logo generated." \ + || echo " Plymouth logo generation failed (non-fatal)." + # 1×1 pixel images — scaled at runtime by the script + convert -size 1x1 xc:"#1C2840" "$PLYM/bar_bg.png" 2>/dev/null || true + convert -size 1x1 xc:"#1E58A8" "$PLYM/bar_fg.png" 2>/dev/null || true +else + echo " ImageMagick not available — Plymouth PNGs skipped." +fi + # ── Mac hardware: libinput touchpad config ──────────────────────────── mkdir -p "$AIROOTFS/etc/X11/xorg.conf.d" cat > "$AIROOTFS/etc/X11/xorg.conf.d/40-libinput.conf" <<'TOUCHPAD' @@ -626,7 +704,7 @@ cat > "$PROFILE/efiboot/loader/entries/skyos.conf" <<'ENTRY' title SkyOS linux /arch/boot/x86_64/vmlinuz-linux initrd /arch/boot/x86_64/initramfs-linux.img -options archisobasedir=arch archisolabel=%ARCHISO_LABEL% quiet loglevel=3 +options archisobasedir=arch archisolabel=%ARCHISO_LABEL% quiet splash loglevel=3 ENTRY @@ -694,6 +772,9 @@ update-desktop-database 2>/dev/null || true # Fontconfig cache fc-cache -f 2>/dev/null || true +# Plymouth: set SkyOS as the default theme (archiso runs mkinitcpio after this) +plymouth-set-default-theme skyos 2>/dev/null || true + # xscreensaver config (screen lock) mkdir -p /etc/skel cat > /etc/skel/.xscreensaver <<'SS'