From dc69317ddf17ad07cc81f26e0880fcacac74a896 Mon Sep 17 00:00:00 2001 From: Vasilito Date: Sat, 25 Apr 2026 12:01:25 +0100 Subject: [PATCH] D-Bus Phase 3/4: upgrade sessiond, services, add StatusNotifierWatcher, consolidate configs - redbear-sessiond: add Manager.Inhibit (pipe FD), CanPowerOff/CanReboot/ CanSuspend/CanHibernate/CanHybridSleep/CanSleep (return na), PowerOff/ Reboot/Suspend stubs, GetSessionByPID, ListUsers, ListSeats, ListInhibitors, ActivateSession/LockSession/UnlockSession/TerminateSession - redbear-sessiond: add Session SetIdleHint, SetLockedHint, SetType, Terminate methods; wire PauseDevice/ResumeDevice/Lock/Unlock signal emission via SignalEmitter injection; add dynamic device enumeration scanning /scheme/drm/card* and /dev/input/event* at startup - redbear-sessiond: replace infinite pending() with stoppable shutdown via tokio watch channel + control socket shutdown command - redbear-upower: add Changed signal emission with 30s periodic polling and power state snapshot comparison - redbear-notifications: add ActionInvoked signal, expand capabilities to body + body-markup + actions - redbear-polkit, redbear-udisks: replace pending() with stoppable shutdown via signal handling + watch channel - Add redbear-statusnotifierwatcher: new session bus service implementing org.freedesktop.StatusNotifierWatcher for KDE system tray - Add D-Bus activation file for StatusNotifierWatcher - KWin session.cpp: try LogindSession before NoopSession fallback - Consolidate config profiles: remove obsolete redbear-desktop, redbear-kde, redbear-live-*, redbear-minimal-*, redbear-wayland configs; simplify to three supported targets (redbear-full, redbear-mini, redbear-grub) - Update DBUS-INTEGRATION-PLAN.md and DESKTOP-STACK-CURRENT-STATUS.md with Phase 3/4 fragility assessment, KWin readiness matrix, and completeness gap analysis --- AGENTS.md | 33 +- README.md | 24 +- config/redbear-desktop.toml | 246 --------- config/redbear-full-grub.toml | 10 - config/redbear-full.toml | 514 ++++++------------ config/redbear-grub-live-full.toml | 14 - config/redbear-grub-live-mini.toml | 9 - config/redbear-grub-policy.toml | 9 + config/redbear-grub.toml | 13 +- config/redbear-kde.toml | 381 ------------- config/redbear-legacy-base.toml | 2 - config/redbear-live-full-grub.toml | 7 - config/redbear-live-full.toml | 7 - config/redbear-live-mini-grub.toml | 7 - config/redbear-live-minimal-grub.toml | 6 - config/redbear-live-minimal.toml | 12 - config/redbear-live.toml | 15 - ...dbear-live-mini.toml => redbear-mini.toml} | 39 +- config/redbear-minimal-grub.toml | 6 - config/redbear-minimal.toml | 219 -------- config/redbear-wayland.toml | 16 - docs/06-BUILD-SYSTEM-SETUP.md | 9 +- docs/07-RED-BEAR-OS-IMPLEMENTATION-PLAN.md | 36 +- docs/README.md | 6 +- local/AGENTS.md | 193 ++----- .../docs/ACPI-I2C-HID-IMPLEMENTATION-PLAN.md | 2 +- local/docs/BOOT-PROCESS-ASSESSMENT.md | 39 +- local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md | 10 +- local/docs/DBUS-INTEGRATION-PLAN.md | 99 ++++ local/docs/DESKTOP-STACK-CURRENT-STATUS.md | 32 +- .../docs/GREETER-LOGIN-IMPLEMENTATION-PLAN.md | 9 +- local/docs/GRUB-INTEGRATION-PLAN.md | 12 +- ...D-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md | 4 +- local/docs/PROFILE-MATRIX.md | 25 +- local/docs/USB-VALIDATION-RUNBOOK.md | 11 +- local/docs/repo-governance.md | 31 +- .../kde/kwin/source/src/core/session.cpp | 18 +- ....freedesktop.StatusNotifierWatcher.service | 3 + .../redbear-notifications/source/src/main.rs | 62 ++- .../system/redbear-polkit/source/src/main.rs | 55 +- .../redbear-sessiond/source/src/control.rs | 65 ++- .../redbear-sessiond/source/src/device_map.rs | 102 ++++ .../redbear-sessiond/source/src/main.rs | 29 +- .../redbear-sessiond/source/src/manager.rs | 352 ++++++++++-- .../source/src/runtime_state.rs | 18 + .../redbear-sessiond/source/src/session.rs | 168 +++++- .../redbear-statusnotifierwatcher/recipe.toml | 2 + .../source/Cargo.toml | 12 + .../source/src/main.rs | 168 ++++++ .../system/redbear-udisks/source/src/main.rs | 55 +- .../system/redbear-upower/source/src/main.rs | 95 ++-- local/scripts/build-redbear.sh | 89 +-- local/scripts/integrate-redbear.sh | 14 +- recipes/wip/AGENTS.md | 2 +- scripts/build-iso.sh | 51 +- 55 files changed, 1535 insertions(+), 1932 deletions(-) delete mode 100644 config/redbear-desktop.toml delete mode 100644 config/redbear-full-grub.toml delete mode 100644 config/redbear-grub-live-full.toml delete mode 100644 config/redbear-grub-live-mini.toml create mode 100644 config/redbear-grub-policy.toml delete mode 100644 config/redbear-kde.toml delete mode 100644 config/redbear-live-full-grub.toml delete mode 100644 config/redbear-live-full.toml delete mode 100644 config/redbear-live-mini-grub.toml delete mode 100644 config/redbear-live-minimal-grub.toml delete mode 100644 config/redbear-live-minimal.toml delete mode 100644 config/redbear-live.toml rename config/{redbear-live-mini.toml => redbear-mini.toml} (90%) delete mode 100644 config/redbear-minimal-grub.toml delete mode 100644 config/redbear-minimal.toml delete mode 100644 config/redbear-wayland.toml create mode 100644 local/recipes/system/redbear-dbus-services/files/session-services/org.freedesktop.StatusNotifierWatcher.service create mode 100644 local/recipes/system/redbear-statusnotifierwatcher/recipe.toml create mode 100644 local/recipes/system/redbear-statusnotifierwatcher/source/Cargo.toml create mode 100644 local/recipes/system/redbear-statusnotifierwatcher/source/src/main.rs diff --git a/AGENTS.md b/AGENTS.md index e8704b47..e099ad40 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,7 +73,7 @@ redox-master/ | Linux driver compat | `docs/04-LINUX-DRIVER-COMPAT.md` | linux-kpi + redox-driver-sys architecture (**GPU and Wi-Fi only — not USB**) | | Build system internals | `src/bin/repo.rs`, `src/lib.rs`, `mk/repo.mk` | Cookbook tool in Rust | | Cross-toolchain setup | `mk/prefix.mk`, `prefix/x86_64-unknown-redox/` | Downloads Clang/LLVM toolchain | -| Display/session surface | `config/redbear-full.toml` | Active desktop/graphics compile surface; `redbear-kde` references elsewhere are historical/staging and not supported compile targets | +| Display/session surface | `config/redbear-full.toml` | Active desktop/graphics compile surface | | GPU/graphics stack | `recipes/libs/mesa/` | OSMesa + LLVMpipe (software only) | | GPU hardware drivers | `local/recipes/gpu/redox-drm/source/` | AMD + Intel DRM/KMS via redox-driver-sys | | D-Bus integration | `local/docs/DBUS-INTEGRATION-PLAN.md` | Architecture, gap analysis, phased implementation for KDE Plasma D-Bus | @@ -93,27 +93,22 @@ echo 'PODMAN_BUILD?=1' > .config # Podman container build # Build Red Bear OS # Supported compile targets: -# redbear-full desktop/graphics harddrive.img -# redbear-live full desktop live ISO (greeter + text fallback) -# redbear-live-mini text-only mini live ISO for recovery/bare metal -# redbear-full-grub desktop target with GRUB boot manager -# redbear-grub-live-full full desktop live ISO with GRUB -# redbear-grub-live-mini text-only mini live ISO with GRUB -# Desktop/graphics targets: redbear-full, redbear-full-grub, redbear-live, redbear-grub-live-full -# Text-only targets: redbear-live-mini, redbear-grub-live-mini -# NOTE: redbear-kde and redbear-live-full are historical/staging, not supported compile targets -make all CONFIG_NAME=redbear-full # Desktop/graphics-enabled target → harddrive.img -make live CONFIG_NAME=redbear-live # Full desktop live ISO (greeter + text fallback) -make live CONFIG_NAME=redbear-live-mini # Text-only mini live ISO for recovery/bare metal -make all CONFIG_NAME=redbear-full-grub # Desktop target with GRUB boot manager → harddrive.img -make live CONFIG_NAME=redbear-grub-live-full # Full desktop live ISO with GRUB -make live CONFIG_NAME=redbear-grub-live-mini # Text-only mini live ISO with GRUB -CI=1 make all CONFIG_NAME=redbear-mini # CI mode (disables TUI, for non-interactive) +# redbear-full desktop/graphics target (harddrive.img or live ISO) +# redbear-mini text-only console/recovery target (harddrive.img or live ISO) +# redbear-grub text-only with GRUB boot manager (live ISO) +# Desktop/graphics target: redbear-full +# Text-only targets: redbear-mini, redbear-grub +make all CONFIG_NAME=redbear-full # Desktop/graphics target → harddrive.img +make all CONFIG_NAME=redbear-mini # Text-only target → harddrive.img +make live CONFIG_NAME=redbear-full # Full desktop live ISO +make live CONFIG_NAME=redbear-mini # Text-only mini live ISO +make live CONFIG_NAME=redbear-grub # Text-only mini live ISO with GRUB +CI=1 make all CONFIG_NAME=redbear-mini # CI mode (disables TUI, for non-interactive) # Run make qemu # Boot in QEMU make qemu QEMUFLAGS="-m 4G" # With more RAM -make live # Build live ISO for real bare metal → redbear-live.iso +make live # Build live ISO for real bare metal # Single recipe ./target/release/repo cook recipes/libs/mesa # Build one recipe @@ -136,7 +131,7 @@ make all → mk/fstools.mk (build cookbook repo binary + fstools) → mk/repo.mk (repo cook --filesystem=config/*.toml) → For each recipe: fetch source → apply patches → build → stage into sysroot - → mk/disk.mk (create filesystem.img, harddrive.img, redbear-live.iso) + → mk/disk.mk (create filesystem.img, harddrive.img, redbear-live.iso or harddrive.img) → redoxfs-mkfs → redox_installer → bootloader embedding ``` diff --git a/README.md b/README.md index e80fd7a7..f0868421 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Red Bear OS now treats AMD and Intel machines as equal-priority hardware targets language in historical integration notes should be read as earlier sequencing context, not as the current platform policy. -The tracked desktop-capable target surface is now `redbear-full` / `redbear-live`, and +The tracked desktop-capable target surface is `redbear-full`, and runtime support claims remain evidence-qualified until compositor/session proof is stronger. ## Historical Phase Snapshot @@ -176,23 +176,21 @@ Requires a Linux x86_64 host with Rust nightly, QEMU, and standard build tools. ```bash # Non-live (harddrive.img for QEMU / development) -make all CONFIG_NAME=redbear-full # Tracked desktop-capable target -make all CONFIG_NAME=redbear-full-grub # Desktop target with GRUB boot manager +make all CONFIG_NAME=redbear-full # Desktop/graphics target +make all CONFIG_NAME=redbear-mini # Text-only console/recovery target # Live ISO (for real bare metal) -make live CONFIG_NAME=redbear-live # Full desktop live ISO (greeter + text fallback) -make live CONFIG_NAME=redbear-live-mini # Text-only mini live ISO for recovery (~384 MiB) -make live CONFIG_NAME=redbear-grub-live-full # Full desktop live ISO with GRUB -make live CONFIG_NAME=redbear-grub-live-mini # Text-only mini live ISO with GRUB +make live CONFIG_NAME=redbear-full # Full desktop live ISO +make live CONFIG_NAME=redbear-mini # Text-only mini live ISO for recovery +make live CONFIG_NAME=redbear-grub # Text-only mini live ISO with GRUB # Or use the helper script -scripts/build-iso.sh redbear-live # Full desktop live ISO -scripts/build-iso.sh redbear-live-mini # Text-only mini -scripts/build-iso.sh redbear-grub-live-full # Full desktop + GRUB -scripts/build-iso.sh redbear-grub-live-mini # Text-only + GRUB +scripts/build-iso.sh redbear-full # Full desktop live ISO +scripts/build-iso.sh redbear-mini # Text-only mini (default) +scripts/build-iso.sh redbear-grub # Text-only + GRUB # QEMU (uses harddrive.img, not live ISO) -make qemu CONFIG_NAME=redbear-full # Boot the tracked desktop-capable target in QEMU +make qemu CONFIG_NAME=redbear-full # Boot the desktop target in QEMU ``` Live `.iso` outputs are for real bare-metal boot and install workflows. They are not the virtual/QEMU target surface; use `make qemu` and `harddrive.img`-based flows for virtualization. @@ -202,7 +200,7 @@ Live `.iso` outputs are for real bare-metal boot and install workflows. They are Red Bear OS can use GNU GRUB as an alternative boot manager with Linux-compatible CLI: ```bash -make all CONFIG_NAME=redbear-full-grub # Build broader integration slice with GRUB chainload +make all CONFIG_NAME=redbear-grub # Build text-only target with GRUB chainload ./local/scripts/grub-install --target=x86_64-efi --disk-image=build/x86_64/harddrive.img ./local/scripts/grub-mkconfig -o local/recipes/core/grub/grub.cfg ``` diff --git a/config/redbear-desktop.toml b/config/redbear-desktop.toml deleted file mode 100644 index d5fbe993..00000000 --- a/config/redbear-desktop.toml +++ /dev/null @@ -1,246 +0,0 @@ -# Red Bear OS Desktop Configuration -# Supplementary integration profile beneath the tracked KWin target -# -# Build: make all CONFIG_NAME=redbear-desktop -# Live: make live CONFIG_NAME=redbear-desktop -# -# This profile remains available for non-KDE-specific integration work. - -include = ["desktop.toml", "redbear-legacy-base.toml", "redbear-legacy-desktop.toml", "redbear-device-services.toml", "redbear-netctl.toml"] - -[general] -filesystem_size = 10240 - -[packages] -# Red Bear OS branding (os-release, hostname, motd) -redbear-release = {} - -# Native Redox PCI/USB listing tools (lspci, lsusb) -redbear-hwutils = {} - -# Redox-native netctl compatibility command -redbear-netctl = {} -redbear-netctl-console = {} - -# Native network reporting and connect-scan tools -redbear-netstat = {} -redbear-traceroute = {} -redbear-mtr = {} -redbear-nmap = {} - -# Firmware loading + Wi-Fi control plane -redbear-firmware = {} -firmware-loader = {} -redbear-wifictl = {} - -# Input/runtime service prerequisites -evdevd = {} -udev-shim = {} - -# Diagnostic tool -redbear-info = {} - -# Process monitor -htop = {} - -# IOMMU validation surface -iommu = {} - -# D-Bus IPC and session services -dbus = {} -redbear-sessiond = {} -redbear-dbus-services = {} -redbear-notifications = {} -redbear-upower = {} -redbear-udisks = {} -redbear-polkit = {} - -# Terminal file manager (Midnight Commander port) -mc = {} - -# Package builder (cub -S/-B/-G CLI) -cub = {} - -# Core Red Bear umbrella package -redbear-meta = {} - - -# ── Desktop services (replace legacy desktop-minimal init scripts) ─────────── - -[[files]] -path = "/usr/lib/init.d/20_display.service" -data = """ -[unit] -description = "Display session service" -requires_weak = [ -] - -[service] -cmd = "ion" -args = ["-c", "true"] -envs = { VT = "3" } -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/30_console.service" -data = """ -[unit] -description = "Console terminals" -requires_weak = [ - "29_activate_console.service", -] - -[service] -cmd = "getty" -args = ["2"] -type = "oneshot_async" -respawn = true -""" - -[[files]] -path = "/usr/lib/init.d/29_activate_console.service" -data = """ -[unit] -description = "Activate display VT" -requires_weak = [ - "20_display.service", -] - -[service] -cmd = "inputd" -args = ["-A", "3"] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/31_debug_console.service" -data = """ -[unit] -description = "Debug console" -requires_weak = [ - "29_activate_console.service", -] - -[service] -cmd = "getty" -args = ["/scheme/debug/no-preserve", "-J"] -type = "oneshot_async" -respawn = true -""" - -[[files]] -path = "/usr/lib/init.d/13_iommu.service" -data = """ -[unit] -description = "IOMMU DMA remapping daemon" -requires_weak = [ - "00_pcid-spawner.service", -] - -[service] -cmd = "/usr/bin/iommu" -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/12_dbus.service" -data = """ -[unit] -description = "D-Bus system bus" -requires_weak = [ - "12_boot-late.target", -] - -[service] -cmd = "ion" -args = [ - "-c", - "mkdir -p /var/lib/dbus /run/dbus; rm -f /run/dbus/pid; dbus-uuidgen --ensure; dbus-daemon --system", -] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/13_redbear-sessiond.service" -data = """ -[unit] -description = "Red Bear session broker (org.freedesktop.login1)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "ion" -args = [ - "-c", - "redbear-sessiond", -] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/14_redbear-upower.service" -data = """ -[unit] -description = "UPower D-Bus service (org.freedesktop.UPower)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "ion" -args = [ - "-c", - "redbear-upower", -] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/14_redbear-udisks.service" -data = """ -[unit] -description = "UDisks2 D-Bus service (org.freedesktop.UDisks2)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "ion" -args = [ - "-c", - "redbear-udisks", -] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/14_redbear-polkit.service" -data = """ -[unit] -description = "PolicyKit1 D-Bus service (org.freedesktop.PolicyKit1)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "ion" -args = [ - "-c", - "redbear-polkit", -] -type = "oneshot_async" -""" - -[[files]] -path = "/var/lib/dbus" -data = "" -directory = true -mode = 0o755 - -[[files]] -path = "/run/dbus" -data = "" -directory = true -mode = 0o755 diff --git a/config/redbear-full-grub.toml b/config/redbear-full-grub.toml deleted file mode 100644 index b7ff93cf..00000000 --- a/config/redbear-full-grub.toml +++ /dev/null @@ -1,10 +0,0 @@ -# Red Bear OS Full Configuration with GRUB Boot Manager -# Desktop + RBOS branding + GRUB chainload -# -# Build: make all CONFIG_NAME=redbear-full-grub -# -# This config adds GRUB as boot manager (Phase 2 installer-native). -# GRUB presents a menu and chainloads the Redox bootloader. -# Requires: efi_partition_size >= 8 (16 recommended for GRUB + Redox bootloader + margin) - -include = ["redbear-full.toml", "redbear-grub.toml"] diff --git a/config/redbear-full.toml b/config/redbear-full.toml index c7fb3a53..ae0ba310 100644 --- a/config/redbear-full.toml +++ b/config/redbear-full.toml @@ -1,15 +1,13 @@ # Red Bear OS Full Configuration -# Primary desktop/session target: Wayland + KWin/KDE session surface on Red Bear OS. -# Build: make all CONFIG_NAME=redbear-full -# Live: make live CONFIG_NAME=redbear-live-full # bare-metal live ISO only +# Desktop/graphics ISO for bare metal and QEMU. # -# This is the only active full desktop target. Wayland and KDE package/runtime surfaces are folded -# here instead of being split across redbear-wayland and redbear-kde. -# GPU driver stack ships through redbear-meta for this profile. -# redox-drm now carries the runtime/package dependency on the AMD backend library. -# Runtime/session claims still remain evidence-qualified until compositor/session proof is strong. +# Build: make live CONFIG_NAME=redbear-full +# QEMU: make all CONFIG_NAME=redbear-full && make qemu +# +# Extends redbear-mini with the full desktop/graphics stack: +# Wayland, Qt6, KF6, KWin, Mesa, DRM drivers, firmware, greeter. -include = ["desktop.toml", "redbear-legacy-base.toml", "redbear-legacy-desktop.toml", "redbear-device-services.toml", "redbear-netctl.toml", "redbear-greeter-services.toml"] +include = ["redbear-mini.toml"] [general] filesystem_size = 4096 @@ -23,73 +21,20 @@ home = "/nonexistent" shell = "/usr/bin/ion" [packages] -# Red Bear OS branding (os-release, hostname, motd) -redbear-release = {} - -# Exclude inherited legacy desktop packages from the tracked KWin/Wayland target -orbdata = "ignore" -orbital = "ignore" -orbterm = "ignore" -orbutils = "ignore" -cosmic-edit = "ignore" -cosmic-files = "ignore" -cosmic-icons = "ignore" -cosmic-term = "ignore" - -# Native Redox PCI/USB listing tools (lspci, lsusb) -redbear-hwutils = {} - -# Redox-native netctl compatibility command -redbear-netctl = {} -redbear-netctl-console = {} - -# Native network reporting and connect-scan tools -redbear-netstat = {} -redbear-traceroute = {} -redbear-mtr = {} -redbear-nmap = {} - -# Package builder / recipe utility -cub = {} - -# Terminal file manager (Midnight Commander port) -mc = {} - -# ext4 filesystem support (our custom port) -ext4d = {} - -# Firmware loading + Wi-Fi control plane +# Firmware loading redbear-firmware = {} firmware-loader = {} -redbear-wifictl = {} -# Input layer -evdevd = {} -udev-shim = {} - -# Desktop/session plumbing -dbus = {} -redbear-sessiond = {} -redbear-dbus-services = {} -redbear-notifications = {} -redbear-upower = {} -redbear-udisks = {} -redbear-polkit = {} - -# IOMMU DMA remapping daemon -iommu = {} - -# Diagnostic tool -redbear-info = {} - -# Process monitor -htop = {} +# GPU/graphics stack +redox-drm = {} +mesa = {} +libdrm = {} # Wayland protocol libwayland = {} wayland-protocols = {} -# Keyboard support +# Keyboard/input libxkbcommon = {} xkeyboard-config = {} libevdev = {} @@ -105,15 +50,13 @@ qtsvg = {} qtwayland = {} qt6-wayland-smoke = {} -# KF6 Frameworks — Tier 1 +# KF6 Frameworks kf6-extra-cmake-modules = {} kf6-kcoreaddons = {} kf6-kconfig = {} kf6-ki18n = {} kf6-kcolorscheme = {} kf6-kauth = {} - -# KF6 Frameworks — KWin session-surface chain kf6-kwindowsystem = {} kf6-knotifications = {} kf6-kconfigwidgets = {} @@ -133,24 +76,50 @@ kdecoration = {} # KWin Wayland compositor kwin = {} -# Graphics -redox-drm = {} -mesa = {} -libdrm = {} +# Greeter/login stack +redbear-authd = {} +redbear-session-launch = {} +redbear-greeter = {} # Core Red Bear umbrella package redbear-meta = {} -# Firmware directory for AMD/Intel GPU blobs +# Desktop fonts and icons +dejavu = {} +freefont = {} +hicolor-icon-theme = {} +pop-icon-theme = {} + +# Suppress legacy desktop packages +orbdata = "ignore" +orbital = "ignore" +orbterm = "ignore" +orbutils = "ignore" +cosmic-edit = "ignore" +cosmic-files = "ignore" +cosmic-icons = "ignore" +cosmic-term = "ignore" + [[files]] path = "/lib/firmware/amdgpu" data = "" directory = true mode = 0o755 -# Greeter/login service wiring now lives in config/redbear-greeter-services.toml, which is -# included above so the full desktop target has a single source of truth for display/fallback -# session services. +[[files]] +path = "/usr/lib/fonts" +data = "/usr/share/fonts" +symlink = true + +[[files]] +path = "/usr/lib/init.d/05_boot-essential.target" +data = """ +[unit] +description = "Boot essential services target" +requires_weak = [ + "00_base.target", +] +""" [[files]] path = "/usr/lib/init.d/13_iommu.service" @@ -185,7 +154,6 @@ args = [ type = "oneshot_async" """ - [[files]] path = "/usr/lib/init.d/13_redbear-sessiond.service" data = """ @@ -275,293 +243,125 @@ type = "oneshot_async" """ [[files]] -path = "/var/lib/dbus" -data = "" -directory = true -mode = 0o755 - -[[files]] -path = "/run/dbus" -data = "" -directory = true -mode = 0o755 - -[[files]] -path = "/usr/lib/fonts" -data = "/usr/share/fonts" -symlink = true - -[[files]] -path = "/usr/bin/redbear-validation-session" -mode = 0o755 +path = "/usr/lib/init.d/19_redbear-authd.service" data = """ -#!/usr/bin/env sh +[unit] +description = "Red Bear authentication daemon" +requires_weak = [ + "12_dbus.service", +] -export DISPLAY="" -export HOME="${HOME:-/home/root}" -export USER="${USER:-root}" -export LOGNAME="${LOGNAME:-$USER}" -export WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" -export XDG_SESSION_TYPE=wayland -export LIBSEAT_BACKEND=seatd -export SEATD_SOCK=/run/seatd.sock -export QT_PLUGIN_PATH=/usr/plugins -export QT_QPA_PLATFORM_PLUGIN_PATH=/usr/plugins/platforms -export QML2_IMPORT_PATH=/usr/qml -export RUST_BACKTRACE=full -export RUST_LOG=debug -export XCURSOR_THEME=Pop -export XKB_CONFIG_ROOT=/usr/share/X11/xkb - -if [ -z "${XDG_RUNTIME_DIR:-}" ]; then - export XDG_RUNTIME_DIR="/tmp/run/user/$(id -u)" -fi - -mkdir -p "$XDG_RUNTIME_DIR" -chmod 700 "$XDG_RUNTIME_DIR" 2>/dev/null || true - -wait_for_path() { - target="$1" - attempts=0 - while [ "$attempts" -lt 30 ]; do - if [ -e "$target" ]; then - return 0 - fi - attempts=$((attempts + 1)) - sleep 1 - done - return 1 -} - -if ! wait_for_path /run/dbus/system_bus_socket; then - echo "system D-Bus socket did not appear" >&2 - exit 1 -fi - -sleep 2 - -wait_for_wayland_socket() { - socket_path="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" - attempts=0 - while [ "$attempts" -lt 30 ]; do - if [ -e "$socket_path" ]; then - return 0 - fi - if ! kill -0 "$kwin_pid" 2>/scheme/null; then - return 1 - fi - attempts=$((attempts + 1)) - sleep 1 - done - return 1 -} - -if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then - eval "$(dbus-launch --sh-syntax)" -fi - -validation_request="/run/redbear-kde-session.validation-request" -validation_success="/run/redbear-kde-session.validation-success" - -if [ -f "$validation_request" ]; then - { - echo "user=$USER" - echo "runtime=$XDG_RUNTIME_DIR" - echo "wayland=$WAYLAND_DISPLAY" - } > "$validation_success" - rm -f "$validation_request" - exit 0 -fi - -dbus-update-activation-environment \ - DBUS_SESSION_BUS_ADDRESS \ - DBUS_SESSION_BUS_PID \ - WAYLAND_DISPLAY \ - XDG_SESSION_TYPE \ - XDG_RUNTIME_DIR \ - DISPLAY \ - HOME \ - USER - -if [ -d /usr/share/glib-2.0/schemas ]; then - glib-compile-schemas /usr/share/glib-2.0/schemas/ -fi - -if [ -n "$KWIN_DRM_DEVICES" ]; then - kwin_wayland_wrapper --drm & -else - echo "redbear-validation-session: using virtual KWin backend (set KWIN_DRM_DEVICES to enable DRM)" >&2 - kwin_wayland_wrapper --virtual & -fi -kwin_pid=$! - -if ! wait_for_wayland_socket; then - echo "kwin_wayland failed to expose $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" >&2 - exit 1 -fi - -/usr/bin/wayland-session -smoke_exit=$? - -echo "wayland-session exited with code $smoke_exit" - -# 60-second KWin survival verdict -survival_ok="$HOME/.kwin-60s-survival.ok" -survival_err="$HOME/.kwin-60s-survival.err" -rm -f "$survival_ok" "$survival_err" - -elapsed=0 -while kill -0 "$kwin_pid" 2>/dev/null; do - if [ "$elapsed" -ge 60 ]; then - echo "KWin survived 60 seconds (pid=$kwin_pid)" > "$survival_ok" - break - fi - sleep 1 - elapsed=$((elapsed + 1)) -done - -if [ ! -f "$survival_ok" ]; then - echo "KWin died before 60 seconds (pid=$kwin_pid, elapsed=${elapsed}s)" > "$survival_err" -fi - -echo "redbear-validation-session: complete (smoke_exit=$smoke_exit, survival=$([ -f "$survival_ok" ] && echo ok || echo failed))" +[service] +cmd = "ion" +args = [ + "-c", + "redbear-authd", +] +type = "oneshot_async" """ [[files]] -path = "/usr/bin/wayland-session" -mode = 0o755 +path = "/usr/lib/init.d/20_greeter.service" data = """ -#!/usr/bin/env ion +[unit] +description = "Red Bear greeter service" +requires_weak = [ + "12_dbus.service", + "13_redbear-sessiond.service", + "13_seatd.service", + "19_redbear-authd.service", +] -printenv -let session_started = "$HOME/.wayland-session.started" -rm -f $session_started -echo "started" > $session_started -export QT_PLUGIN_PATH=/usr/plugins -export QT_QPA_PLATFORM_PLUGIN_PATH=/usr/plugins/platforms -export QML2_IMPORT_PATH=/usr/qml -let smoke_ok = "$HOME/.qt6-wayland-smoke.ok" -let smoke_err = "$HOME/.qt6-wayland-smoke.err" -let smoke_log = "$HOME/.qt6-wayland-smoke.log" -let bootstrap_ok = "$HOME/.qt6-bootstrap-minimal.ok" -let bootstrap_log = "$HOME/.qt6-bootstrap-minimal.log" -let plugin_ok = "$HOME/.qt6-plugin-minimal.ok" -let plugin_err = "$HOME/.qt6-plugin-minimal.err" -let plugin_log = "$HOME/.qt6-plugin-minimal.log" -let smoke_minimal_ok = "$HOME/.qt6-wayland-smoke-minimal.ok" -let smoke_offscreen_ok = "$HOME/.qt6-wayland-smoke-offscreen.ok" -let smoke_wayland_ok = "$HOME/.qt6-wayland-smoke-wayland.ok" -let smoke_minimal_log = "$HOME/.qt6-wayland-smoke-minimal.log" -let smoke_offscreen_log = "$HOME/.qt6-wayland-smoke-offscreen.log" -let smoke_wayland_log = "$HOME/.qt6-wayland-smoke-wayland.log" -rm -f $smoke_ok $smoke_err -rm -f $smoke_log -rm -f $bootstrap_ok $bootstrap_log -rm -f $plugin_ok $plugin_err $plugin_log -rm -f $smoke_minimal_ok $smoke_offscreen_ok $smoke_wayland_ok -rm -f $smoke_minimal_log $smoke_offscreen_log $smoke_wayland_log -if which qt6-wayland-smoke >/scheme/null - if env LD_DEBUG=all QT_DEBUG_PLUGINS=1 QT_QPA_PLATFORM=minimal qt6-bootstrap-check > $bootstrap_log ^> $bootstrap_log - touch $bootstrap_ok - else - if test -f $bootstrap_log - cat $bootstrap_log - end - echo "qt6-bootstrap-check minimal failed; see $bootstrap_log" > $smoke_err - end - - if env LD_DEBUG=all QT_DEBUG_PLUGINS=1 QT_PLUGIN_PATH=/usr/plugins QT_QPA_PLATFORM_PLUGIN_PATH=/usr/plugins/platforms qt6-plugin-check /usr/plugins/platforms/libqminimal.so > $plugin_log ^> $plugin_log - touch $plugin_ok - else - if test -f $plugin_log - cat $plugin_log - end - echo "qt6-plugin-check failed; see $plugin_log" > $plugin_err - echo "qt6-plugin-check failed; see $plugin_log" > $smoke_err - end - - if env QT_DEBUG_PLUGINS=1 QT_QPA_PLATFORM=minimal qt6-wayland-smoke > $smoke_minimal_log ^> $smoke_minimal_log - touch $smoke_minimal_ok - else - echo "qt6-wayland-smoke minimal failed; see $smoke_minimal_log" > $smoke_err - end - - if env QT_DEBUG_PLUGINS=1 QT_QPA_PLATFORM=offscreen qt6-wayland-smoke > $smoke_offscreen_log ^> $smoke_offscreen_log - touch $smoke_offscreen_ok - else - echo "qt6-wayland-smoke offscreen failed; see $smoke_offscreen_log" > $smoke_err - end - - if env QT_DEBUG_PLUGINS=1 QT_QPA_PLATFORM=wayland qt6-wayland-smoke > $smoke_wayland_log ^> $smoke_wayland_log - touch $smoke_wayland_ok - touch $smoke_ok - else - echo "qt6-wayland-smoke wayland failed; see $smoke_wayland_log" > $smoke_err - end -end +[service] +cmd = "/usr/bin/redbear-greeterd" +envs = { VT = "3", REDBEAR_GREETER_USER = "greeter" } +type = "oneshot_async" """ [[files]] -path = "/usr/bin/redbear-kde-session" -mode = 0o755 +path = "/usr/lib/init.d/29_activate_console.service" data = """ -#!/usr/bin/sh +[unit] +description = "Activate fallback console VT" +requires_weak = [ + "05_boot-essential.target", +] -export DISPLAY="" -export WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" -export XDG_SESSION_TYPE=wayland -export KDE_FULL_SESSION=true -export XDG_CURRENT_DESKTOP=KDE -export LIBSEAT_BACKEND=seatd -export SEATD_SOCK=/run/seatd.sock -export QT_PLUGIN_PATH="${QT_PLUGIN_PATH:-/usr/plugins}" -export QT_QPA_PLATFORM_PLUGIN_PATH="${QT_QPA_PLATFORM_PLUGIN_PATH:-/usr/plugins/platforms}" -export QML2_IMPORT_PATH="${QML2_IMPORT_PATH:-/usr/qml}" -export XCURSOR_THEME="${XCURSOR_THEME:-Pop}" -export XKB_CONFIG_ROOT="${XKB_CONFIG_ROOT:-/usr/share/X11/xkb}" - -if [ -z "${XDG_RUNTIME_DIR:-}" ]; then - export XDG_RUNTIME_DIR="/tmp/run/user/$(id -u)" -fi - -mkdir -p "$XDG_RUNTIME_DIR" -chmod 700 "$XDG_RUNTIME_DIR" 2>/dev/null || true - -if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then - eval "$(dbus-launch --sh-syntax)" -fi - -validation_request="/run/redbear-kde-session.validation-request" -validation_success="/run/redbear-kde-session.validation-success" - -if [ -f "$validation_request" ]; then - { - echo "user=$USER" - echo "runtime=$XDG_RUNTIME_DIR" - echo "wayland=$WAYLAND_DISPLAY" - } > "$validation_success" - rm -f "$validation_request" - exit 0 -fi - -dbus-update-activation-environment \ - DBUS_SESSION_BUS_ADDRESS \ - DBUS_SESSION_BUS_PID \ - WAYLAND_DISPLAY \ - XDG_SESSION_ID \ - XDG_SEAT \ - XDG_SESSION_TYPE \ - XDG_RUNTIME_DIR \ - XDG_CURRENT_DESKTOP \ - KDE_FULL_SESSION \ - DISPLAY \ - HOME \ - USER - -if [ -n "$KWIN_DRM_DEVICES" ]; then - exec kwin_wayland_wrapper --drm -else - echo "redbear-kde-session: using virtual KWin backend (set KWIN_DRM_DEVICES to enable DRM)" >&2 - exec kwin_wayland_wrapper --virtual -fi +[service] +cmd = "inputd" +args = ["-A", "2"] +type = "oneshot_async" +""" + +[[files]] +path = "/usr/lib/init.d/30_console.service" +data = """ +[unit] +description = "Console terminals" +requires_weak = [ + "29_activate_console.service", +] + +[service] +cmd = "getty" +args = ["2"] +type = "oneshot_async" +respawn = true +""" + +[[files]] +path = "/usr/lib/init.d/31_debug_console.service" +data = """ +[unit] +description = "Debug console" +requires_weak = [ + "29_activate_console.service", +] + +[service] +cmd = "getty" +args = ["/scheme/debug/no-preserve", "-J"] +type = "oneshot_async" +respawn = true +""" + +[users.greeter] +password = "" +uid = 101 +gid = 101 +name = "greeter" +home = "/nonexistent" +shell = "/usr/bin/ion" + +[groups.greeter] +gid = 101 +members = ["greeter"] + +[[files]] +path = "/etc/pcid.d/ihdgd.toml" +data = """ +[[drivers]] +name = "Intel GPU (VGA compatible)" +class = 0x03 +vendor = 0x8086 +subclass = 0x00 +command = ["redox-drm"] + +[[drivers]] +name = "Intel GPU (3D controller)" +class = 0x03 +vendor = 0x8086 +subclass = 0x02 +command = ["redox-drm"] +""" + +[[files]] +path = "/etc/pcid.d/virtio-gpud.toml" +data = """ +[[drivers]] +name = "VirtIO GPU" +class = 0x03 +vendor = 0x1af4 +subclass = 0x00 +command = ["redox-drm"] """ diff --git a/config/redbear-grub-live-full.toml b/config/redbear-grub-live-full.toml deleted file mode 100644 index 62bfe4f0..00000000 --- a/config/redbear-grub-live-full.toml +++ /dev/null @@ -1,14 +0,0 @@ -# Red Bear OS GRUB Live Full Configuration -# Canonical GRUB-backed live ISO for the full desktop/session target. -# -# Build: make live CONFIG_NAME=redbear-grub-live-full -# -# Boot flow: UEFI → GRUB (menu) → chainload EFI/REDBEAR/redbear.efi → Redox bootloader → kernel -# Session: graphical greeter on VT 3, text login fallback on VT 2, debug console on /scheme/debug -# -# The installer reads bootloader = "grub" from this config and writes: -# EFI/BOOT/BOOTX64.EFI — GRUB EFI binary (primary bootloader) -# EFI/BOOT/grub.cfg — GRUB menu config -# EFI/REDBEAR/redbear.efi — Redox bootloader (chainload target) - -include = ["redbear-live.toml", "redbear-grub.toml"] \ No newline at end of file diff --git a/config/redbear-grub-live-mini.toml b/config/redbear-grub-live-mini.toml deleted file mode 100644 index 101185d2..00000000 --- a/config/redbear-grub-live-mini.toml +++ /dev/null @@ -1,9 +0,0 @@ -# Red Bear OS GRUB Live Mini Configuration -# Canonical GRUB-backed live ISO for the stripped console/recovery target. -# -# Build: make live CONFIG_NAME=redbear-grub-live-mini - -include = ["redbear-live-mini.toml", "redbear-grub.toml"] - -[general] -efi_partition_size = 16 diff --git a/config/redbear-grub-policy.toml b/config/redbear-grub-policy.toml new file mode 100644 index 00000000..33e8098d --- /dev/null +++ b/config/redbear-grub-policy.toml @@ -0,0 +1,9 @@ +# Red Bear OS shared GRUB policy fragment +# Use with any redbear-* profile to make GRUB first-class in installer flows. + +[general] +bootloader = "grub" +efi_partition_size = 16 + +[packages] +grub = {} diff --git a/config/redbear-grub.toml b/config/redbear-grub.toml index f9ed0d48..27e3e2f1 100644 --- a/config/redbear-grub.toml +++ b/config/redbear-grub.toml @@ -1,11 +1,16 @@ -# Red Bear OS shared GRUB policy fragment -# Use with any redbear-* profile to make GRUB first-class in installer flows. +# Red Bear OS GRUB Configuration +# Text-only ISO with GRUB boot manager for bare metal. +# +# Build: make live CONFIG_NAME=redbear-grub +# +# Identical to redbear-mini but uses GNU GRUB as the boot manager +# instead of the Redox EFI bootloader. + +include = ["redbear-mini.toml", "redbear-grub-policy.toml"] [general] bootloader = "grub" -# Keep ESP large enough for GRUB EFI image + Red Bear bootloader + future growth. efi_partition_size = 16 [packages] -# Ensure GRUB artifacts are present for installer-native chainload layout. grub = {} diff --git a/config/redbear-kde.toml b/config/redbear-kde.toml deleted file mode 100644 index 1a5d2dc8..00000000 --- a/config/redbear-kde.toml +++ /dev/null @@ -1,381 +0,0 @@ -# Red Bear OS Target Wayland Desktop Configuration -# Build: make all CONFIG_NAME=redbear-kde -# Live: make live CONFIG_NAME=redbear-kde -# -# Intended desktop direction: KWin Wayland session on Red Bear OS -# Tracked default desktop target for Red Bear OS -# Current state: KWin + KF6 + Qt6 + Mesa + D-Bus + seatd -# Runtime compositor/session proof is still required for broader support claims -# Future: add plasma-workspace, plasma-desktop when their deps are resolved - -include = ["desktop.toml", "redbear-legacy-base.toml", "redbear-legacy-desktop.toml", "redbear-device-services.toml", "redbear-netctl.toml"] - -[general] -filesystem_size = 4096 - -[users.messagebus] -uid = 100 -gid = 100 -name = "messagebus" -home = "/nonexistent" -shell = "/usr/bin/ion" - -[packages] -# Red Bear OS branding -redbear-release = {} - -# Exclude inherited legacy desktop packages from the tracked KWin target -orbdata = "ignore" -orbital = "ignore" -orbterm = "ignore" -orbutils = "ignore" -cosmic-edit = "ignore" -cosmic-files = "ignore" -cosmic-icons = "ignore" -cosmic-term = "ignore" - -# ext4 filesystem support -ext4d = {} - -# Firmware loading + Wi-Fi control plane -redbear-firmware = {} -firmware-loader = {} -redbear-wifictl = {} - -# Input layer -evdevd = {} -udev-shim = {} - -# D-Bus (session + system bus) -dbus = {} -redbear-sessiond = {} -redbear-dbus-services = {} -redbear-notifications = {} -redbear-upower = {} -redbear-udisks = {} -redbear-polkit = {} - -# Diagnostic/runtime tooling -redbear-info = {} -redbear-hwutils = {} -redbear-netctl = {} -redbear-netctl-console = {} -redbear-netstat = {} -redbear-traceroute = {} -redbear-mtr = {} -redbear-nmap = {} -cub = {} - -# Wayland protocol -libwayland = {} -wayland-protocols = {} - -# Input -libxkbcommon = {} -libevdev = {} -libinput = {} - -# Seat management -seatd = {} - -# Qt6 stack -qtbase = {} -qtdeclarative = {} -qtsvg = {} -qtwayland = {} - -# KF6 Frameworks — Tier 1 (no special deps) -kf6-extra-cmake-modules = {} -kf6-kcoreaddons = {} -kf6-kconfig = {} -kf6-ki18n = {} -kf6-kcolorscheme = {} -kf6-kauth = {} - -# KF6 Frameworks — KWin session-surface chain -kf6-kwindowsystem = {} -kf6-knotifications = {} -kf6-kconfigwidgets = {} -kf6-kcrash = {} -kf6-kdbusaddons = {} -kf6-kglobalaccel = {} -kf6-kservice = {} -kf6-kpackage = {} -kf6-kiconthemes = {} -kirigami = {} -kf6-kio = {} -kf6-kdeclarative = {} -kf6-kcmutils = {} -kf6-kwayland = {} -kdecoration = {} - -# KDE session surface (v2.0 Phases 3–4) -kwin = {} - -# Graphics -redox-drm = {} -mesa = {} -libdrm = {} - -# Firmware directory for AMD/Intel GPU blobs -[[files]] -path = "/lib/firmware/amdgpu" -data = "" -directory = true -mode = 0o755 - -[[files]] -path = "/usr/lib/init.d/12_dbus.service" -data = """ -[unit] -description = "D-Bus system bus" -requires_weak = [ - "00_base.target", -] - -[service] -cmd = "ion" -args = [ - "-c", - "mkdir -p /var/lib/dbus /run/dbus; rm -f /run/dbus/pid; dbus-uuidgen --ensure; dbus-daemon --system", -] -type = "oneshot_async" -""" - -[[files]] -path = "/var/lib/dbus" -data = "" -directory = true -mode = 0o755 - -[[files]] -path = "/run/dbus" -data = "" -directory = true -mode = 0o755 - -[[files]] -path = "/usr/lib/init.d/13_seatd.service" -data = """ -[unit] -description = "seatd seat management daemon" -requires_weak = [ - "12_dbus.service", - "13_redbear-sessiond.service", -] - -[service] -cmd = "seatd" -args = ["-l", "info"] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/13_redbear-sessiond.service" -data = """ -[unit] -description = "Red Bear session broker (org.freedesktop.login1)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "ion" -args = [ - "-c", - "redbear-sessiond", -] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/14_redbear-upower.service" -data = """ -[unit] -description = "UPower D-Bus service (org.freedesktop.UPower)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "ion" -args = [ - "-c", - "redbear-upower", -] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/14_redbear-udisks.service" -data = """ -[unit] -description = "UDisks2 D-Bus service (org.freedesktop.UDisks2)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "ion" -args = [ - "-c", - "redbear-udisks", -] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/14_redbear-polkit.service" -data = """ -[unit] -description = "PolicyKit1 D-Bus service (org.freedesktop.PolicyKit1)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "ion" -args = [ - "-c", - "redbear-polkit", -] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/20_display.service" -data = """ -[unit] -description = "Display session service (KDE session)" -requires_weak = [ - "12_dbus.service", - "13_redbear-sessiond.service", - "13_seatd.service", -] - -[service] -cmd = "/usr/bin/redbear-kde-session" -envs = { VT = "3" } -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/30_console.service" -data = """ -[unit] -description = "Console terminals" -requires_weak = [ - "29_activate_console.service", -] - -[service] -cmd = "getty" -args = ["2"] -type = "oneshot_async" -respawn = true -""" - -[[files]] -path = "/usr/lib/init.d/29_activate_console.service" -data = """ -[unit] -description = "Activate display VT" -requires_weak = [ - "20_display.service", -] - -[service] -cmd = "inputd" -args = ["-A", "3"] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/31_debug_console.service" -data = """ -[unit] -description = "Debug console" -requires_weak = [ - "29_activate_console.service", -] - -[service] -cmd = "getty" -args = ["/scheme/debug/no-preserve", "-J"] -type = "oneshot_async" -respawn = true -""" - -[[files]] -path = "/usr/bin/redbear-kde-session" -mode = 0o755 -data = """ -#!/usr/bin/sh - -export DISPLAY="" -export HOME="${HOME:-/home/root}" -export USER="${USER:-root}" -export LOGNAME="${LOGNAME:-$USER}" -export WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" -export XDG_SESSION_TYPE=wayland -export KDE_FULL_SESSION=true -export XDG_CURRENT_DESKTOP=KDE -export LIBSEAT_BACKEND=seatd -export SEATD_SOCK=/run/seatd.sock -export QT_PLUGIN_PATH="${QT_PLUGIN_PATH:-/usr/plugins}" -export QT_QPA_PLATFORM_PLUGIN_PATH="${QT_QPA_PLATFORM_PLUGIN_PATH:-/usr/plugins/platforms}" -export QML2_IMPORT_PATH="${QML2_IMPORT_PATH:-/usr/qml}" -export XCURSOR_THEME="${XCURSOR_THEME:-Pop}" -export XKB_CONFIG_ROOT="${XKB_CONFIG_ROOT:-/usr/share/X11/xkb}" - -if [ -z "${XDG_RUNTIME_DIR:-}" ]; then - export XDG_RUNTIME_DIR="/tmp/run/user/$(id -u)" -fi - -mkdir -p "$XDG_RUNTIME_DIR" -chmod 700 "$XDG_RUNTIME_DIR" 2>/dev/null || true - -wait_for_path() { - target="$1" - attempts=0 - while [ "$attempts" -lt 30 ]; do - if [ -e "$target" ]; then - return 0 - fi - attempts=$((attempts + 1)) - sleep 1 - done - return 1 -} - -if ! wait_for_path /run/dbus/system_bus_socket; then - echo "system D-Bus socket did not appear" >&2 - exit 1 -fi - -sleep 2 - -if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then - eval "$(dbus-launch --sh-syntax)" -fi - -dbus-update-activation-environment \ - DBUS_SESSION_BUS_ADDRESS \ - DBUS_SESSION_BUS_PID \ - WAYLAND_DISPLAY \ - XDG_SESSION_ID \ - XDG_SEAT \ - XDG_SESSION_TYPE \ - XDG_RUNTIME_DIR \ - XDG_CURRENT_DESKTOP \ - KDE_FULL_SESSION \ - DISPLAY \ - HOME \ - USER - -if [ -z "${KWIN_DRM_DEVICES:-}" ] && [ -e /scheme/drm/card0 ]; then - export KWIN_DRM_DEVICES=/scheme/drm/card0 -fi - -exec kwin_wayland_wrapper --drm -""" diff --git a/config/redbear-legacy-base.toml b/config/redbear-legacy-base.toml index 0c604f5e..19b119ea 100644 --- a/config/redbear-legacy-base.toml +++ b/config/redbear-legacy-base.toml @@ -23,8 +23,6 @@ data = """ # clear and recreate tmpdir with 0o1777 permission rm -rf /tmp mkdir -m a=rwxt /tmp - -nowait sudo --daemon """ [[files]] diff --git a/config/redbear-live-full-grub.toml b/config/redbear-live-full-grub.toml deleted file mode 100644 index 5b1dcd6a..00000000 --- a/config/redbear-live-full-grub.toml +++ /dev/null @@ -1,7 +0,0 @@ -# Red Bear OS Live Full Configuration with GRUB Boot Manager -# Legacy compatibility alias for the canonical GRUB full live target. -# -# Preferred build: make live CONFIG_NAME=redbear-grub-live-full -# Legacy build: make live CONFIG_NAME=redbear-live-full-grub - -include = ["redbear-grub-live-full.toml"] diff --git a/config/redbear-live-full.toml b/config/redbear-live-full.toml deleted file mode 100644 index 76f4bef0..00000000 --- a/config/redbear-live-full.toml +++ /dev/null @@ -1,7 +0,0 @@ -# Red Bear OS Live Full Configuration -# Legacy compatibility alias for the canonical full live target. -# -# Preferred build: make live CONFIG_NAME=redbear-live -# Legacy build: make live CONFIG_NAME=redbear-live-full - -include = ["redbear-live.toml"] diff --git a/config/redbear-live-mini-grub.toml b/config/redbear-live-mini-grub.toml deleted file mode 100644 index 7e2d5d13..00000000 --- a/config/redbear-live-mini-grub.toml +++ /dev/null @@ -1,7 +0,0 @@ -# Red Bear OS Live Mini Configuration with GRUB Boot Manager -# Legacy compatibility alias for the canonical GRUB mini live target. -# -# Preferred build: make live CONFIG_NAME=redbear-grub-live-mini -# Legacy build: make live CONFIG_NAME=redbear-live-mini-grub - -include = ["redbear-grub-live-mini.toml"] diff --git a/config/redbear-live-minimal-grub.toml b/config/redbear-live-minimal-grub.toml deleted file mode 100644 index b4468808..00000000 --- a/config/redbear-live-minimal-grub.toml +++ /dev/null @@ -1,6 +0,0 @@ -# Red Bear OS Live Minimal Configuration with GRUB Boot Manager -# Live ISO for minimal console target with installer-native GRUB chainload. -# -# Build: make live CONFIG_NAME=redbear-live-minimal-grub - -include = ["redbear-live-minimal.toml", "redbear-grub.toml"] diff --git a/config/redbear-live-minimal.toml b/config/redbear-live-minimal.toml deleted file mode 100644 index cb88d458..00000000 --- a/config/redbear-live-minimal.toml +++ /dev/null @@ -1,12 +0,0 @@ -# Red Bear OS Live Minimal Configuration -# Live ISO variant for the minimal console/server target. -# -# Build: make live CONFIG_NAME=redbear-live-minimal - -include = ["redbear-minimal.toml"] - -[general] -filesystem_size = 3072 - -[packages] -cub = {} diff --git a/config/redbear-live.toml b/config/redbear-live.toml deleted file mode 100644 index 12fc9419..00000000 --- a/config/redbear-live.toml +++ /dev/null @@ -1,15 +0,0 @@ -# Red Bear OS Live Configuration -# Canonical full live ISO target for the desktop/session surface on real bare metal. -# -# Build: make live CONFIG_NAME=redbear-live -# This ISO is for real bare-metal boot/install/recovery, not VM/QEMU use. - -include = ["redbear-full.toml"] - -[general] -filesystem_size = 3072 -efi_partition_size = 1 - -[packages] -cub = {} -redbear-meta = {} diff --git a/config/redbear-live-mini.toml b/config/redbear-mini.toml similarity index 90% rename from config/redbear-live-mini.toml rename to config/redbear-mini.toml index b43f53aa..ffc31e69 100644 --- a/config/redbear-live-mini.toml +++ b/config/redbear-mini.toml @@ -1,19 +1,26 @@ -# Red Bear OS Live Mini Configuration -# Live ISO variant for console/recovery/install on bare metal. +# Red Bear OS Mini Configuration +# Text-only ISO for console/recovery/install on bare metal. # -# Build: make live CONFIG_NAME=redbear-live-mini +# Build: make live CONFIG_NAME=redbear-mini # # Target contract: -# - keep a text-login live/recovery surface only -# - use boot framebuffer for VT text consoles via vesad + fbcond -# - ship all non-graphics, non-firmware packages from the full profile -# - ship no linux-firmware payload, no firmware-loader, no GPU/display drivers +# - text-login live/recovery/install surface +# - boot framebuffer for VT text consoles via vesad + fbcond +# - all non-graphics, non-firmware packages from the full profile +# - no linux-firmware payload, no firmware-loader, no GPU/display drivers include = ["minimal.toml", "redbear-legacy-base.toml", "redbear-netctl.toml"] [general] filesystem_size = 1536 +[users.messagebus] +uid = 100 +gid = 100 +name = "messagebus" +home = "/nonexistent" +shell = "/usr/bin/ion" + [packages] # Red Bear OS branding and host utilities. redbear-release = {} @@ -188,11 +195,25 @@ cmd = "i2cd" type = "oneshot_async" """ +[[files]] +path = "/etc/init.d/00_i2c-dw-acpi.service" +data = """ +[unit] +description = "DesignWare ACPI I2C controller (non-blocking)" +requires_weak = [ + "00_i2cd.service", +] + +[service] +cmd = "dw-acpi-i2cd" +type = "oneshot_async" +""" + [[files]] path = "/etc/init.d/00_intel-gpiod.service" data = """ [unit] -description = "Intel ACPI GPIO registrar (non-blocking on live-mini)" +description = "Intel ACPI GPIO registrar (non-blocking)" requires_weak = [ "00_gpiod.service", "00_i2cd.service", @@ -222,7 +243,7 @@ type = "oneshot_async" path = "/etc/init.d/00_i2c-hidd.service" data = """ [unit] -description = "ACPI I2C HID bring-up daemon (non-blocking on live-mini)" +description = "ACPI I2C HID bring-up daemon (non-blocking)" requires_weak = [ "00_i2cd.service", "00_i2c-dw-acpi.service", diff --git a/config/redbear-minimal-grub.toml b/config/redbear-minimal-grub.toml deleted file mode 100644 index 11cffee3..00000000 --- a/config/redbear-minimal-grub.toml +++ /dev/null @@ -1,6 +0,0 @@ -# Red Bear OS Minimal Configuration with GRUB Boot Manager -# Minimal console/server target with installer-native GRUB chainload. -# -# Build: make all CONFIG_NAME=redbear-minimal-grub - -include = ["redbear-minimal.toml", "redbear-grub.toml"] diff --git a/config/redbear-minimal.toml b/config/redbear-minimal.toml deleted file mode 100644 index b69e063d..00000000 --- a/config/redbear-minimal.toml +++ /dev/null @@ -1,219 +0,0 @@ -# Red Bear OS Minimal Configuration -# Console/Server variant with bare-metal driver support but no GUI. -# Includes all non-graphics, non-firmware packages from the full profile. -# -# Build: make all CONFIG_NAME=redbear-minimal - -include = ["minimal.toml", "redbear-legacy-base.toml", "redbear-device-services.toml", "redbear-netctl.toml"] - -[general] -# Minimal image carries the full non-graphics package set; -# 2048 MiB accommodates CLI tools, D-Bus services, and filesystem utilities. -filesystem_size = 2048 - -[packages] -# Red Bear OS branding -redbear-release = {} - -# Native Redox PCI/USB listing tools (lspci, lsusb) -redbear-hwutils = {} - -# Redox-native netctl compatibility command -redbear-netctl = {} -redbear-netctl-console = {} - -# Native network reporting and connect-scan tools -redbear-netstat = {} -redbear-traceroute = {} -redbear-mtr = {} -redbear-nmap = {} - -# Wi-Fi control plane (firmware-loader comes from redbear-device-services.toml) -firmware-loader = {} -redbear-wifictl = {} - -# Input/runtime service prerequisites -evdevd = {} -udev-shim = {} - -# Terminal file manager -mc = {} - -# Diagnostic tool -redbear-info = {} - -# Package builder (cub -S/-B/-G CLI) -cub = {} - -# ── D-Bus IPC and session services ── -dbus = {} -redbear-sessiond = {} -redbear-dbus-services = {} -redbear-notifications = {} -redbear-upower = {} -redbear-udisks = {} -redbear-polkit = {} - -# ── IOMMU DMA remapping ── -iommu = {} - -# ── Standard CLI tools (from server profile) ── -bash = {} -bottom = {} -curl = {} -diffutils = {} -findutils = {} -git = {} -htop = {} - -# ── Filesystem support ── -ext4d = {} -redoxfs = {} - -# ── System installer ── -installer = {} - -# ── Build / packaging utilities ── -patchelf = {} -shared-mime-info = {} - -[[files]] -path = "/etc/netctl/active" -data = "wired-dhcp\n" - -[[files]] -path = "/etc/issue" -data = """ -########## Red Bear OS ######### -# Login with the following: # -# `user` # -# `root`:`password` # -################################ -""" - -[[files]] -path = "/etc/motd" -data = """ - Red Bear OS v0.1.0 "Denali" — Built on Redox OS - Type 'help' for available commands. -""" - -[[files]] -path = "/usr/lib/init.d/12_dbus.service" -data = """ -[unit] -description = "D-Bus system bus" -requires_weak = [ - "12_boot-late.target", -] - -[service] -cmd = "ion" -args = [ - "-c", - "mkdir -p /var/lib/dbus /run/dbus; rm -f /run/dbus/pid; dbus-uuidgen --ensure; dbus-daemon --system", -] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/13_redbear-sessiond.service" -data = """ -[unit] -description = "Red Bear session broker (org.freedesktop.login1)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "ion" -args = [ - "-c", - "redbear-sessiond", -] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/13_iommu.service" -data = """ -[unit] -description = "IOMMU DMA remapping daemon" -requires_weak = [ - "12_boot-late.target", - "00_pcid-spawner.service", -] - -[service] -cmd = "/usr/bin/iommu" -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/14_redbear-upower.service" -data = """ -[unit] -description = "UPower D-Bus service (org.freedesktop.UPower)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "ion" -args = [ - "-c", - "redbear-upower", -] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/14_redbear-udisks.service" -data = """ -[unit] -description = "UDisks2 D-Bus service (org.freedesktop.UDisks2)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "ion" -args = [ - "-c", - "redbear-udisks", -] -type = "oneshot_async" -""" - -[[files]] -path = "/usr/lib/init.d/14_redbear-polkit.service" -data = """ -[unit] -description = "PolicyKit1 D-Bus service (org.freedesktop.PolicyKit1)" -requires_weak = [ - "12_dbus.service", -] - -[service] -cmd = "ion" -args = [ - "-c", - "redbear-polkit", -] -type = "oneshot_async" -""" - -[[files]] -path = "/var/lib/dbus" -data = "" -directory = true -mode = 0o755 - -[[files]] -path = "/run/dbus" -data = "" -directory = true -mode = 0o755 - -# VT/getty/login chain: initfs starts inputd + vesad + fbcond in phase 1, -# then minimal.toml legacy 30_console runs inputd -A 2 + getty 2 + getty debug. diff --git a/config/redbear-wayland.toml b/config/redbear-wayland.toml deleted file mode 100644 index cf0e49aa..00000000 --- a/config/redbear-wayland.toml +++ /dev/null @@ -1,16 +0,0 @@ -# Red Bear OS Wayland Validation Profile -# -# Build: ./local/scripts/build-redbear.sh redbear-wayland -# Phase 1 substrate validation: ./local/scripts/test-phase1-desktop-substrate.sh --qemu redbear-wayland -# Phase 2 Wayland validation: ./local/scripts/test-phase4-wayland-qemu.sh -# -# Validation-only profile: -# - the packaged compositor/runtime harness remains bounded -# - this profile remains the bounded validation slice beneath redbear-kde -> kwin_wayland - -include = ["wayland.toml"] - -[packages] -redbear-firmware = {} -redox-drm = {} -qt6-wayland-smoke = {} diff --git a/docs/06-BUILD-SYSTEM-SETUP.md b/docs/06-BUILD-SYSTEM-SETUP.md index c3f71b86..7cf52412 100644 --- a/docs/06-BUILD-SYSTEM-SETUP.md +++ b/docs/06-BUILD-SYSTEM-SETUP.md @@ -105,7 +105,7 @@ echo 'PODMAN_BUILD?=0' > .config ### Select Build Configuration -Tracked Red Bear work should normally be built and validated through the four supported +Tracked Red Bear work should normally be built and validated through the three supported `redbear-*` compile targets. For desktop work specifically, `redbear-full` is the tracked desktop-capable target. @@ -144,20 +144,19 @@ For the full layout and rationale, see `local/docs/EXTERNAL-TOOLCHAIN.md`. ```bash # Preferred Red Bear wrapper: ./local/scripts/build-redbear.sh redbear-mini -./local/scripts/build-redbear.sh redbear-live-mini ./local/scripts/build-redbear.sh redbear-full -./local/scripts/build-redbear.sh redbear-live-full +./local/scripts/build-redbear.sh redbear-grub # Direct make is still valid when needed: make all CONFIG_NAME=redbear-full ``` -For tracked Red Bear work, prefer these four compile targets over older historical names. +For tracked Red Bear work, prefer these three compile targets over older historical names. ### Build a Live ISO ```bash -make live CONFIG_NAME=redbear-live-full +make live CONFIG_NAME=redbear-full # Produces: build/x86_64/redbear-live.iso ``` diff --git a/docs/07-RED-BEAR-OS-IMPLEMENTATION-PLAN.md b/docs/07-RED-BEAR-OS-IMPLEMENTATION-PLAN.md index 73c2932a..3425a6c2 100644 --- a/docs/07-RED-BEAR-OS-IMPLEMENTATION-PLAN.md +++ b/docs/07-RED-BEAR-OS-IMPLEMENTATION-PLAN.md @@ -93,14 +93,13 @@ That means: The tracked Red Bear compile targets are: - `redbear-mini` -- `redbear-live-mini` - `redbear-full` -- `redbear-live-full` +- `redbear-grub` These are the only supported compile targets. Older names such as `redbear-minimal`, -`redbear-desktop`, `redbear-wayland`, `redbear-kde`, and `redbear-live` may still appear in -historical notes or legacy implementation details, but they are not the current compile-target -surface. +`redbear-desktop`, `redbear-wayland`, `redbear-kde`, `redbear-live`, `redbear-live-mini`, +and `redbear-live-full` may still appear in historical notes or legacy implementation details, +but they are not the current compile-target surface. ### `redbear-mini` @@ -125,31 +124,20 @@ Scope: - Qt base integration, - the active desktop-capable target surface. -### `redbear-live-mini` +### `redbear-grub` -Live/demo/recovery form of the mini baseline for real bare metal. +Text-only console/recovery target with GRUB boot manager for real bare metal. Scope: - diagnostics, - recovery workflows, -- installability for the non-graphics target. - -### `redbear-live-full` - -Live/demo/recovery form of the full desktop target for real bare metal. - -Scope: - -- diagnostics, -- recovery workflows, -- installability, -- live desktop-capable system identity. +- multi-boot bare-metal install with GRUB chainload. ### Desktop policy -- Desktop/graphics are available only on `redbear-full` and `redbear-live-full`. -- Validation work that does not require graphics should prefer `redbear-mini` or `redbear-live-mini`. +- Desktop/graphics are available only on `redbear-full`. +- Validation work that does not require graphics should prefer `redbear-mini` or `redbear-grub`. - Live `.iso` outputs are for real bare-metal boot/install workflows, not for VM/QEMU execution; virtualization should use the `harddrive.img`-based target surface. ## Current State Baseline @@ -384,9 +372,9 @@ Canonical references: Acceptance: -- `redbear-wayland` remains the narrow runtime validation slice, -- `redbear-full` remains the broader desktop/session plumbing slice, -- the active desktop-capable tracked targets keep honest session-viability language tied to `redbear-full` / `redbear-live-full`, not older historical target names. +- `redbear-full` remains the broader desktop/session plumbing slice (the Wayland validation slice + is handled within `redbear-full`), +- the active desktop-capable tracked targets keep honest session-viability language tied to `redbear-full`, not older historical target names. ### 8. Hardware validation and support labeling diff --git a/docs/README.md b/docs/README.md index db88dcef..b2b7b097 100644 --- a/docs/README.md +++ b/docs/README.md @@ -107,8 +107,8 @@ This summary is only a quick orientation layer. For canonical current-state deta - `local/docs/PROFILE-MATRIX.md` for support-language by tracked profile, - and the active subsystem plans under `local/docs/` for detailed current workstreams. -- **Compile targets**: the supported compile targets are `redbear-mini`, `redbear-live-mini`, `redbear-full`, and `redbear-live-full` -- **Live ISO policy**: live `.iso` outputs (`redbear-live-mini`, `redbear-live-full`) are for real bare-metal boot/install/recovery workflows, not the VM/QEMU execution surface. +- **Compile targets**: the supported compile targets are `redbear-mini`, `redbear-full`, and `redbear-grub` +- **Live ISO policy**: live `.iso` outputs (`make live`) are for real bare-metal boot/install/recovery workflows, not the VM/QEMU execution surface. - **Wayland**: libwayland + wayland-protocols built. A bounded greeter/compositor-backed login proof now passes, but broader compositor/runtime stability remains incomplete. - **Qt6**: qtbase 6.11.0 (Core+Gui+Widgets+DBus+Wayland), qtdeclarative, qtsvg, qtwayland ALL BUILT - **D-Bus**: 1.16.2 built for Redox. Qt6DBus enabled. @@ -120,7 +120,7 @@ This summary is only a quick orientation layer. For canonical current-state deta - **PCI / IRQ quality**: architecturally strong substrate exists, with bounded MSI-X, IOMMU, xHCI IRQ, and low-level-controller proof surfaces; broader hardware robustness is still intentionally tracked as open work in `../local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` - **Wi-Fi profile target**: `config/redbear-wifi-experimental.toml` is the first explicit tracked image slice for bounded Intel Wi‑Fi validation, instead of spreading that claim across the generic desktop profiles. - **Bluetooth**: one bounded in-tree BLE-first experimental slice exists, and the Battery Level read-only workload now has a packaged in-guest checker plus a host QEMU harness; QEMU validation is still in progress, so broad desktop Bluetooth parity is still incomplete -- **Desktop direction**: `redbear-full` / `redbear-live-full` carry the desktop-capable target surface; the bounded greeter/login slice now passes, while the wider desktop runtime stack is still incomplete. +- **Desktop direction**: `redbear-full` carries the desktop-capable target surface; the bounded greeter/login slice now passes, while the wider desktop runtime stack is still incomplete. - **ACPI**: materially complete for the historical boot baseline, not release-grade complete; implemented: AML mutex real state, EC widened accesses via byte transactions, kstop-based shutdown eventing, explicit `RSDP_ADDR` forwarding into `acpid`, x86 BIOS-search AML fallback, and real-but-provisional AML-backed power enumeration. **Known gaps**: the explicit boot-path producer contract for AML bootstrap is still underdocumented, `acpid` startup hardening remains open, shutdown/power reporting are still provisional, sleep state transitions and sleep eventing remain incomplete, DMAR ownership is still transitional, and bare-metal validation is still bounded. See `local/docs/ACPI-IMPROVEMENT-PLAN.md`. - **Linux driver compat**: linux-kpi now includes early wireless-subsystem compatibility scaffolding in addition to the earlier helper layer, redox-driver-sys and firmware-loader compile, and the bounded Intel Wi-Fi path now has host-tested scan/connect/disconnect/profile/reporting flows without claiming real hardware Wi-Fi connectivity. - **Wi-Fi validation tooling**: `redbear-phase5-wifi-check` and `redbear-phase5-wifi-capture` are now packaged in-guest helpers for bounded Intel Wi-Fi runtime validation and evidence capture on bare metal or VFIO-backed guests. diff --git a/local/AGENTS.md b/local/AGENTS.md index 86316365..9b1a4e1b 100644 --- a/local/AGENTS.md +++ b/local/AGENTS.md @@ -24,7 +24,7 @@ Build flow: ``` make all CONFIG_NAME=redbear-full → mk/config.mk resolves to the active desktop/graphics compile target - → Desktop/graphics are available only on redbear-full and redbear-live-full + → Desktop/graphics are available only on redbear-full → repo cook builds all packages including our custom ones → mk/disk.mk creates harddrive.img with Red Bear branding ``` @@ -37,28 +37,14 @@ make all CONFIG_NAME=redbear-full # Rebuild the active desktop/graphics ## ACTIVE COMPILE TARGETS -The supported compile targets are exactly: +The supported compile targets are exactly three. All three work for both `make all` (harddrive.img) +and `make live` (ISO): -Non-live (harddrive.img for QEMU / development): -- `redbear-full` — Desktop/graphics-enabled target -- `redbear-full-grub` — Desktop/graphics with GRUB boot manager +- `redbear-full` — Desktop/graphics-enabled target (Wayland + KDE + GPU drivers) +- `redbear-mini` — Text-only console/recovery/install target +- `redbear-grub` — Text-only target with GRUB boot manager -Live (ISO for real bare metal): -- `redbear-live` — Full desktop live ISO (graphical greeter + text fallback) -- `redbear-live-mini` — Text-only mini live ISO for recovery/bare metal -- `redbear-grub-live-full` — Full desktop live ISO with GRUB boot manager -- `redbear-grub-live-mini` — Text-only mini live ISO with GRUB - -Legacy aliases (accepted by build-iso.sh): -- `redbear-live-full` → `redbear-live` -- `redbear-live-mini-grub` → `redbear-grub-live-mini` -- `redbear-live-full-grub` → `redbear-grub-live-full` - -Desktop/graphics are available only on `redbear-full`, `redbear-live`, `redbear-full-grub`, and `redbear-grub-live-full`. - -Names such as `redbear-kde`, `redbear-wayland`, and `redbear-minimal` may still appear in older -docs, legacy validation notes, or in-repo staging configs, but they should not be treated as the -current supported compile targets. +Desktop/graphics are available only on `redbear-full`. ## TRACKING UPSTREAM (SYNC WITH REDOX OS) @@ -176,9 +162,10 @@ redox-master/ ← git pull updates mainline Redox ├── config/ │ ├── desktop.toml ← mainline configs (untouched) │ ├── minimal.toml -│ ├── redbear-full.toml ← Active desktop/graphics target -│ ├── redbear-live-full.toml ← Live desktop/graphics target -│ ├── redbear-mini*.toml ← Minimal target surface (legacy/staging naming may still vary in-tree) +│ ├── redbear-full.toml ← Desktop/graphics target +│ ├── redbear-mini.toml ← Text-only console/recovery target +│ ├── redbear-grub.toml ← Text-only with GRUB boot manager +│ ├── redbear-grub-policy.toml ← GRUB policy fragment (bootloader = "grub", efi_partition_size = 16) │ └── redbear-greeter-services.toml ← Greeter/auth/session-launch wiring fragment ├── recipes/ ← mainline package recipes (untouched) ├── mk/ ← mainline build system (untouched) @@ -240,23 +227,25 @@ redox-master/ ← git pull updates mainline Redox ## HOW TO BUILD RED BEAR OS ```bash -# Active desktop/graphics target -./local/scripts/build-redbear.sh redbear-full +# Build targets (all three work for both `make all` and `make live`) +./local/scripts/build-redbear.sh redbear-full # Desktop/graphics target +./local/scripts/build-redbear.sh redbear-mini # Text-only console/recovery target +./local/scripts/build-redbear.sh redbear-grub # Text-only with GRUB boot manager -# Minimal non-desktop target -./local/scripts/build-redbear.sh redbear-mini +# Or manually: +make all CONFIG_NAME=redbear-full # Desktop/graphics → harddrive.img +make all CONFIG_NAME=redbear-mini # Text-only → harddrive.img +make all CONFIG_NAME=redbear-grub # Text-only + GRUB → harddrive.img -# Live images -./local/scripts/build-redbear.sh redbear-live && make live CONFIG_NAME=redbear-live -./local/scripts/build-redbear.sh redbear-live-mini && make live CONFIG_NAME=redbear-live-mini -./local/scripts/build-redbear.sh redbear-live-mini && make live CONFIG_NAME=redbear-grub-live-mini -./local/scripts/build-redbear.sh redbear-live && make live CONFIG_NAME=redbear-grub-live-full +# Live ISO (for real bare metal) +make live CONFIG_NAME=redbear-full # Full desktop live ISO +make live CONFIG_NAME=redbear-mini # Text-only mini live ISO +make live CONFIG_NAME=redbear-grub # Text-only mini live ISO with GRUB # Or using the helper: -scripts/build-iso.sh redbear-live -scripts/build-iso.sh redbear-live-mini -scripts/build-iso.sh redbear-grub-live-mini -scripts/build-iso.sh redbear-grub-live-full +scripts/build-iso.sh redbear-full # Full desktop live ISO +scripts/build-iso.sh redbear-mini # Text-only mini (default) +scripts/build-iso.sh redbear-grub # Text-only + GRUB # VM-network baseline validation helpers ./local/scripts/validate-vm-network-baseline.sh @@ -266,10 +255,9 @@ scripts/build-iso.sh redbear-grub-live-full # Phase 1 desktop-substrate validation (v2.0 plan: relibc headers, evdevd, udev-shim, # firmware-loader, DRM/KMS, health-check — covers 6 acceptance areas) -./local/scripts/test-phase1-desktop-substrate.sh --qemu redbear-wayland +./local/scripts/test-phase1-desktop-substrate.sh --qemu redbear-full # Legacy Phase 3 runtime-substrate validation (historical P0-P6 numbering; script still works) -# Use the active desktop target when adapting historical validation flows. ./local/scripts/test-phase3-runtime-substrate.sh --qemu redbear-full # Low-level controller validation @@ -296,7 +284,7 @@ scripts/build-iso.sh redbear-grub-live-full # The aggregate USB wrapper runs xHCI mode, full USB stack, and USB storage readback proofs in sequence. # Legacy Phase 4 Wayland runtime validation (historical P0-P6 numbering; script still works) -./local/scripts/build-redbear.sh redbear-wayland +./local/scripts/build-redbear.sh redbear-full ./local/scripts/test-phase4-wayland-qemu.sh # Then run inside the guest: # redbear-phase4-wayland-check @@ -338,18 +326,15 @@ scripts/build-iso.sh redbear-grub-live-full # redbear-netctl user-facing alias redbear-netctl --help -# Or manually: -make all CONFIG_NAME=redbear-full - # Single custom recipe: ./target/release/repo cook local/recipes/branding/redbear-release ./target/release/repo cook local/recipes/system/redbear-meta ./target/release/repo cook local/recipes/core/ext4d ./target/release/repo cook local/recipes/core/grub # GRUB bootloader (host build, produces EFI binary) -# GRUB boot manager (installer-native, Phase 2): +# GRUB boot manager (installer-native): make r.grub # Build GRUB recipe -make all CONFIG_NAME=redbear-full-grub # Build with GRUB chainload +make all CONFIG_NAME=redbear-grub # Build text-only target with GRUB # Linux-compatible CLI (add local/scripts to PATH): grub-install --target=x86_64-efi --disk-image=build/x86_64/harddrive.img grub-mkconfig -o local/recipes/core/grub/grub.cfg @@ -463,7 +448,7 @@ ext4d/source/ recipes/core/ext4d → local/recipes/core/ext4d ``` -**Config**: ext4d is included in `config/desktop.toml` (mainline), which `redbear-desktop.toml` inherits. +**Config**: ext4d is included in `config/desktop.toml` (mainline), which `redbear-full.toml` inherits. **Dependencies** (from workspace Cargo.toml): - `rsext4 = "0.3"` — Pure Rust ext4 filesystem implementation @@ -528,7 +513,7 @@ recipes/core/fatd → ../../local/recipes/core/fatd ``` **Config**: Packages included via `config/redbear-device-services.toml` (inherited by -`redbear-desktop.toml` and `redbear-full.toml`). Init service at +`redbear-full.toml` and `redbear-mini.toml`). Init service at `/usr/lib/init.d/15_fatd.service`. **Dependencies**: fatfs 0.3.6, fscommon 0.1.1, redox_syscall, redox-scheme, libredox, libc @@ -580,105 +565,45 @@ local/Assets/ ## RED BEAR OS CONFIG HIERARCHY -Active compile targets: +Active compile targets (all three work for both `make all` and `make live`): -Non-live (harddrive.img for QEMU / development): -- `redbear-full` -- `redbear-full-grub` +- `redbear-full` — Desktop/graphics-enabled target +- `redbear-mini` — Text-only console/recovery target +- `redbear-grub` — Text-only with GRUB boot manager -Live (ISO for real bare metal): -- `redbear-live` -- `redbear-live-mini` -- `redbear-grub-live-full` -- `redbear-grub-live-mini` - -Desktop/graphics are available only on the `full` targets. Older names such as `redbear-kde`, -`redbear-wayland`, `redbear-minimal`, and `redbear-live-minimal` may still exist in the tree as -legacy or staging artifacts, but they are not the supported compile-target surface. +Desktop/graphics are available only on `redbear-full`. ``` -redbear-grub-live-full.toml - └── redbear-live.toml (full desktop base) - └── redbear-grub.toml (GRUB policy: bootloader = "grub", efi_partition_size = 16) - -redbear-grub-live-mini.toml - └── redbear-live-mini.toml (text-only live base) - └── redbear-grub.toml (GRUB policy) - -redbear-live-full.toml - └── redbear-full.toml - ├── desktop.toml (mainline) - ├── redbear-legacy-base.toml ← Neutralize broken base legacy init scripts - ├── redbear-legacy-desktop.toml ← Neutralize broken desktop legacy init scripts - ├── redbear-device-services.toml ← Shared firmware-loader / evdevd / udev service wiring - ├── redbear-netctl.toml ← Shared Red Bear network profile files + netctl boot service - ├── redbear-greeter-services.toml ← Greeter/auth/session-launch wiring for desktop targets - └── [packages] redbear-release, redbear-hwutils, redbear-netctl, - firmware-loader, evdevd, udev-shim, redbear-info, - redbear-sessiond, redbear-authd, redbear-session-launch, - redbear-greeter, redbear-meta, cub - NOTE: Desktop/graphics are available only on redbear-full and redbear-live-full. - NOTE: ext4d is inherited from desktop.toml (mainline package). - NOTE: redbear-meta is explicitly included in redbear-full.toml; keep broader inclusion deliberate. - NOTE: redbear-live-full inherits from redbear-full.toml. - -redbear-live.toml - └── redbear-full.toml - ├── desktop.toml (mainline) - ├── redbear-legacy-base.toml ← Neutralize broken base legacy init scripts - ├── redbear-legacy-desktop.toml ← Neutralize broken desktop legacy init scripts - ├── redbear-device-services.toml ← Shared firmware-loader / evdevd / udev service wiring - ├── redbear-netctl.toml ← Shared Red Bear network profile files + netctl boot service - └── [packages] redbear-release, redbear-hwutils, redbear-netctl, - firmware-loader, evdevd, udev-shim, redbear-info, - redbear-sessiond, redbear-authd, redbear-session-launch, - redbear-greeter, redbear-meta, cub - NOTE: Desktop/graphics are available on redbear-live. - redbear-full.toml - └── desktop.toml (mainline) - └── redbear-legacy-base.toml ← Neutralize broken base legacy init scripts - └── redbear-legacy-desktop.toml ← Neutralize broken desktop legacy init scripts - └── redbear-device-services.toml ← Shared firmware-loader / evdevd / udev service wiring - └── redbear-netctl.toml ← Shared Red Bear network profile files + netctl boot service - └── redbear-greeter-services.toml ← Greeter/auth/session-launch wiring + └── redbear-mini.toml + ├── minimal.toml (mainline) + ├── redbear-legacy-base.toml + └── redbear-netctl.toml + └── [packages] firmware, GPU, Wayland, Qt6, KF6, KWin, greeter, fonts, icons + └── [services] D-Bus, seatd, greeter, console + └── [users] messagebus, greeter + NOTE: ext4d is inherited from desktop.toml (mainline package). + NOTE: redbear-meta is explicitly included; keep broader inclusion deliberate. -redbear-full-grub.toml - └── redbear-full.toml - └── redbear-grub.toml (GRUB policy: bootloader = "grub", efi_partition_size = 16) - -redbear-live-mini.toml - └── minimal non-desktop live target - └── desktop/graphics intentionally absent - -redbear-grub-live-mini.toml - └── redbear-live-mini.toml (text-only live base) - └── redbear-grub.toml (GRUB policy) - -redbear-mini - └── legacy/staging config files in-tree still use the older `redbear-minimal*` names - in some places; do not treat those names as the supported compile-target surface - -redbear-minimal.toml (legacy/staging naming still present in tree) +redbear-mini.toml └── minimal.toml (mainline) - └── base.toml - └── redbear-legacy-base.toml ← Neutralize broken base legacy init scripts - └── redbear-device-services.toml ← Shared firmware-loader / evdevd / udev service wiring - └── redbear-netctl.toml ← Shared Red Bear network profile files + netctl boot service - └── [packages] redbear-release, redbear-hwutils, redbear-netctl, - firmware-loader, evdevd, udev-shim, redbear-info + └── redbear-legacy-base.toml + └── redbear-netctl.toml + └── [packages] pciids, redbear-hwutils, redbear-netctl, redbear-info, cub, etc. + └── [services] pcid-spawner, netctl boot, console, debug console + +redbear-grub.toml + └── redbear-mini.toml + └── redbear-grub-policy.toml (bootloader = "grub", efi_partition_size = 16) + └── [packages] grub ``` Config comparison: | Config | GPU Stack | Desktop | Branding | ext4d | GRUB | filesystem_size | |--------|-----------|---------|----------|-------|------|-----------------| -| redbear-full | Full | Yes | Yes | ✅ (via desktop.toml) | No | 4096 MiB | -| redbear-full-grub | Full | Yes | Yes | ✅ (via redbear-full.toml) | Yes | 4096 MiB | -| redbear-live | Full | Yes | Yes | ✅ (via redbear-full.toml) | No | 4096 MiB | -| redbear-grub-live-full | Full | Yes | Yes | ✅ (via redbear-full.toml) | Yes | 4096 MiB | -| redbear-live-mini | None | None | Yes | legacy/staging | No | legacy/staging | -| redbear-grub-live-mini | None | None | Yes | legacy/staging | Yes | legacy/staging | -| redbear-mini | None | None | Yes | legacy/staging naming in tree still maps through `redbear-minimal*` files | No | legacy/staging | +| redbear-full | Full | Yes | Yes | ✅ | No | 4096 MiB | +| redbear-mini | None | None | Yes | No | No | 1536 MiB | +| redbear-grub | None | None | Yes | No | Yes | (from mini) | ## ANTI-PATTERNS (COMMIT POLICY) diff --git a/local/docs/ACPI-I2C-HID-IMPLEMENTATION-PLAN.md b/local/docs/ACPI-I2C-HID-IMPLEMENTATION-PLAN.md index 40d86d14..5f99de9b 100644 --- a/local/docs/ACPI-I2C-HID-IMPLEMENTATION-PLAN.md +++ b/local/docs/ACPI-I2C-HID-IMPLEMENTATION-PLAN.md @@ -56,7 +56,7 @@ This work must be treated as bare-metal boot-critical substrate, not as optional Emits RB_THC_QUICKI2C, RB_UCSI_* markers. Consumes `/scheme/ucsi/summary`. - **`amlserde`** — AML serialization/deserialization, including `AmlSerdeValue::Buffer` (needed for `_CRS`), `RegionSpace::GenericSerialBus` for I2C/SMBus opregions. -- **Init services** — `redbear-live-mini.toml` wires `i2cd`, `i2c-hidd`, `i2c-dw-acpi`, +- **Init services** — `redbear-mini.toml` wires `i2cd`, `i2c-hidd`, `i2c-dw-acpi`, `i2c-gpio-expanderd`, `intel-gpiod`, `ucsid` with non-blocking startup ordering. ### What is missing (active gaps) diff --git a/local/docs/BOOT-PROCESS-ASSESSMENT.md b/local/docs/BOOT-PROCESS-ASSESSMENT.md index 708b008c..588fff07 100644 --- a/local/docs/BOOT-PROCESS-ASSESSMENT.md +++ b/local/docs/BOOT-PROCESS-ASSESSMENT.md @@ -78,7 +78,7 @@ Checks: [unit] section, [service] section, cmd field, non-empty data Note: Manual validation script covering `redbear-*.toml` configs. Not wired into the build system — run manually after config changes. Does not cover inherited mainline configs (minimal.toml, desktop.toml). ### 3C: Getty Supervisor ✅ -Init supports `respawn = true` in service TOML files. When a respawnable service's process exits, init automatically re-spawns it. All getty services across `redbear-minimal`, `redbear-desktop`, `redbear-greeter-services`, `redbear-live-mini`, `wayland`, and `redbear-kde` configs now have `respawn = true` set. +Init supports `respawn = true` in service TOML files. When a respawnable service's process exits, init automatically re-spawns it. All getty services across `redbear-mini`, `redbear-full`, `redbear-greeter-services`, `redbear-grub`, and `wayland` configs now have `respawn = true` set. Implementation: - `service.rs`: Added `respawn: bool` field to `Service` (default false). `spawn()` returns `Option` (child PID) for respawnable oneshot_async services. @@ -121,8 +121,8 @@ Status: Chain exists in rootfs only. On modern hardware without PS/2 ports, USB ### Hardware Validation Requirements Bare-metal testing requires physical hardware. Current validation is: -- **QEMU boot**: Verified for redbear-minimal and redbear-full (no panics, no parse errors, switchroot succeeds) -- **Live ISO build**: redbear-live-mini and redbear-live build successfully +- **QEMU boot**: Verified for redbear-mini and redbear-full (no panics, no parse errors, switchroot succeeds) +- **Live ISO build**: redbear-mini and redbear-grub build successfully - **Interactive login**: Framebuffer login renders correctly (serial not available in headless QEMU) ## Phase 5: Validation Matrix ✅ @@ -132,8 +132,7 @@ Bare-metal testing requires physical hardware. Current validation is: |--------|-------|-----------|-----------------|-------| | redbear-mini | ✅ harddrive.img (2 GB) | ✅ Login prompt | — | Framebuffer console login | | redbear-full | ✅ harddrive.img (4 GB) | ✅ Login prompt | — | Desktop packages included | -| redbear-live-mini | ✅ ISO (384 MB) | — | ✅ Login prompt | ISO for bare-metal boot | -| redbear-live-full | ✅ ISO (3.0 GB) | — | — | ISO for bare-metal boot | +| redbear-grub | ✅ harddrive.img | — | — | Text-only with GRUB chainload | ### Compilation Verification - `cargo check --workspace` in base source: **0 errors** @@ -161,20 +160,20 @@ Bare-metal testing requires physical hardware. Current validation is: ### Validation Commands ```bash # Build -CI=1 make all CONFIG_NAME=redbear-minimal ARCH=x86_64 +CI=1 make all CONFIG_NAME=redbear-mini ARCH=x86_64 CI=1 make all CONFIG_NAME=redbear-full ARCH=x86_64 -CI=1 make live CONFIG_NAME=redbear-live-mini ARCH=x86_64 -CI=1 make live CONFIG_NAME=redbear-live-full ARCH=x86_64 +CI=1 make live CONFIG_NAME=redbear-mini ARCH=x86_64 +CI=1 make live CONFIG_NAME=redbear-full ARCH=x86_64 # QEMU test -make qemu CONFIG_NAME=redbear-minimal +make qemu CONFIG_NAME=redbear-mini # Service file validation ./local/scripts/validate-service-files.sh config/ # Clean rebuild + verify -CI=1 make cr.base CONFIG_NAME=redbear-minimal ARCH=x86_64 -CI=1 make all CONFIG_NAME=redbear-minimal ARCH=x86_64 +CI=1 make cr.base CONFIG_NAME=redbear-mini ARCH=x86_64 +CI=1 make all CONFIG_NAME=redbear-mini ARCH=x86_64 ``` ## Key Technical Findings @@ -268,16 +267,15 @@ Services with `type = "oneshot_async"` are fire-and-forget by default. Init spaw ### Config Include Chain ``` -redbear-live-full.toml → redbear-live.toml -redbear-live.toml → redbear-full.toml redbear-full.toml → desktop.toml, redbear-legacy-base.toml, redbear-legacy-desktop.toml, - redbear-device-services.toml, redbear-netctl.toml, redbear-greeter-services.toml + redbear-device-services.toml, redbear-netctl.toml, redbear-greeter-services.toml desktop.toml → desktop-minimal.toml, server.toml desktop-minimal.toml → minimal.toml server.toml → minimal.toml minimal.toml → base.toml -redbear-live-mini.toml → minimal.toml, redbear-legacy-base.toml, redbear-netctl.toml +redbear-grub.toml → redbear-full.toml, redbear-grub-policy.toml + redbear-mini → redbear-minimal.toml → minimal.toml, redbear-legacy-base.toml, redbear-device-services.toml, redbear-netctl.toml ``` @@ -358,9 +356,8 @@ redbear-mini → redbear-minimal.toml → minimal.toml, redbear-legacy-base.toml | Target | Purpose | Output | |--------|---------|--------| | `redbear-mini` | Minimal non-desktop (QEMU + bare metal) | `build/x86_64/harddrive.img` | -| `redbear-live-mini` | Minimal live ISO (bare metal only) | `build/x86_64/redbear-live-mini.iso` | +| `redbear-grub` | Text-only with GRUB boot manager (bare metal) | `build/x86_64/harddrive.img` | | `redbear-full` | Desktop/graphics (QEMU + bare metal) | `build/x86_64/harddrive.img` | -| `redbear-live-full` / `redbear-live` | Desktop/graphics live ISO (bare metal only) | `build/x86_64/redbear-live-full.iso` | ### Build commands @@ -369,13 +366,13 @@ redbear-mini → redbear-minimal.toml → minimal.toml, redbear-legacy-base.toml CI=1 make all CONFIG_NAME=redbear-mini ARCH=x86_64 # Minimal live ISO (bare-metal boot) -CI=1 make live CONFIG_NAME=redbear-live-mini ARCH=x86_64 +CI=1 make live CONFIG_NAME=redbear-mini ARCH=x86_64 # Desktop/graphics target (QEMU testing) CI=1 make all CONFIG_NAME=redbear-full ARCH=x86_64 # Desktop/graphics live ISO (bare-metal boot) -CI=1 make live CONFIG_NAME=redbear-live-full ARCH=x86_64 +CI=1 make live CONFIG_NAME=redbear-full ARCH=x86_64 ``` ### QEMU boot (harddrive.img) @@ -399,12 +396,12 @@ graphical console, not serial. 1. **Build the ISO:** ```bash - CI=1 make live CONFIG_NAME=redbear-live-mini ARCH=x86_64 + CI=1 make live CONFIG_NAME=redbear-mini ARCH=x86_64 ``` 2. **Write ISO to USB drive:** ```bash - sudo dd if=build/x86_64/redbear-live-mini.iso of=/dev/sdX bs=4M status=progress && sync + sudo dd if=build/x86_64/redbear-live.iso of=/dev/sdX bs=4M status=progress && sync ``` Replace `/dev/sdX` with your USB device. Use `lsblk` to identify it. diff --git a/local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md b/local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md index 40761d2b..5d181987 100644 --- a/local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md +++ b/local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md @@ -39,8 +39,8 @@ hardware GPU validation → KWin session bring-up → KDE Plasma session bring-u Out of scope: USB, Wi-Fi, Bluetooth (covered by their own subsystem plans). Tracked-default truth: this document is the canonical desktop-path plan, and the tracked desktop- -capable surface is `redbear-full` / `redbear-live-full`. Older names such as `redbear-wayland` and -`redbear-kde` should be read as historical or staging labels, not supported compile targets. +capable surface is `redbear-full`. Older names such as `redbear-wayland` and `redbear-kde` +should be read as historical or staging labels, not supported compile targets. --- @@ -102,7 +102,7 @@ Rules: | kf6-kcmutils | builds | Widget-only build (QML stripped) | | | `redbear-wayland` profile | historical / staging | Bounded Wayland validation profile | Not a supported compile target | | `redbear-full` profile | builds, boots | Broader desktop plumbing profile | Session/network/runtime integration slice | -| `redbear-kde` profile | historical / staging | Older KDE session-surface profile | Not a supported compile target; use `redbear-full` / `redbear-live-full` for the tracked desktop-capable surface | +| `redbear-kde` profile | historical / staging | Older KDE session-surface profile | Not a supported compile target; use `redbear-full` for the tracked desktop-capable surface | | bounded compositor validation path | experimental | Reaches xkbcommon init + EGL platform selection in QEMU | No complete session | | qt6-wayland-smoke | builds, partial | Creates QWindow with colored background, runs 3 seconds | | | QEMU graphics | usable (bounded) | Renderer is llvmpipe | Not hardware acceleration | @@ -132,7 +132,7 @@ The repo has crossed major build-side gates: 3. **Wayland/graphics packages** — libwayland, wayland-protocols, Mesa EGL+GBM+GLES2, libdrm, libdrm_amdgpu 4. **Qt6 + D-Bus** — qtbase (7 libs + 12 plugins), qtdeclarative (11 libs), qtsvg, qtwayland, D-Bus 1.16.2 5. **KF6 + KDE-facing** — All 32 KF6 frameworks, kdecoration, plasma-wayland-protocols, kf6-kwayland, kf6-kcmutils -6. **Tracked profiles** — redbear-mini, redbear-live-mini, redbear-full, redbear-live-full +6. **Tracked profiles** — redbear-mini, redbear-full, redbear-grub 7. **Phase 1 test coverage** — 300+ unit tests across evdevd (65), udev-shim (15), firmware-loader (24), redox-drm (68), redbear-hwutils (19), and bluetooth/wifi daemons ### What is runtime-proven (limited scope) @@ -622,7 +622,7 @@ continuity, not as future work. | All 32 KF6 frameworks | ✅ Builds complete | Prior to this plan | | Input stack (libevdev, libinput, evdevd, udev-shim) | ✅ Builds complete | Prior to this plan | | Mesa EGL/GBM/GLES2 + libdrm amdgpu | ✅ Builds complete | Prior to this plan | -| Desktop profiles (`redbear-mini`, `redbear-live-mini`, `redbear-full`, `redbear-live-full`) | ✅ Builds complete | Prior to this plan | +| Desktop profiles (`redbear-mini`, `redbear-full`, `redbear-grub`) | ✅ Builds complete | Prior to this plan | | `local/docs/DBUS-INTEGRATION-PLAN.md` | D-Bus architecture, service dependency map, and phased implementation | | PRIME/DMA-BUF scheme ioctls | ✅ Implemented | Prior to this plan | | KWin recipe with 5 re-enabled features | ✅ Partial build | Prior to this plan | diff --git a/local/docs/DBUS-INTEGRATION-PLAN.md b/local/docs/DBUS-INTEGRATION-PLAN.md index 768e3d71..9fe07f96 100644 --- a/local/docs/DBUS-INTEGRATION-PLAN.md +++ b/local/docs/DBUS-INTEGRATION-PLAN.md @@ -1084,3 +1084,102 @@ convenience layer. The remaining gap is the difference between **shipping minima implementations** and **shipping full desktop-complete service contracts** for login1, Notifications, UPower, UDisks2, and PolicyKit. NetworkManager remains deferred and is not part of the current Red Bear OS implementation scope. + +--- + +## Phase 3/4 D-Bus Improvement Plan (2026-04-25 Assessment) + +**Assessment scope:** All Red Bear D-Bus service implementations (`redbear-sessiond`, `redbear-notifications`, `redbear-upower`, `redbear-udisks`, `redbear-polkit`), plus the dbus-daemon itself, conducted via 4 parallel evaluation agents (Oracle + 2 explore + librarian). + +**Key finding:** Phase 2 (`kwin_wayland --virtual`) should work without D-Bus changes. KWin falls back to NoopSession when logind is unavailable, and the Noop backend bypasses login1 entirely. + +**Key finding:** Phase 3 has one hard gate: `TakeDevice` FD passing. This cannot be bypassed. + +### Assessment Summary + +Fragility ratings across services: + +| Service | Rating | Primary concern | +|---------|--------|-----------------| +| `redbear-sessiond` | 5/5 | login1 is the critical path for DRM compositor | +| `redbear-polkit` | 5/5 security | Always-permit is not a production security model | +| `dbus-daemon` | 2/5 | 24-line patch is stable but not validated under real session bus load | +| `redbear-notifications` | 2-3/5 | Logs to stderr only; no ActionInvoked signal | +| `redbear-upower` | 2-3/5 | Provisional ACPI surface; no Changed signal; polling not implemented | +| `redbear-udisks` | 2-3/5 | Read-only; no mount/unmount operations | + +**Phase 2 assessment:** D-Bus is NOT on the critical path for `kwin_wayland --virtual`. The NoopSession backend in KWin bypasses logind entirely, which means Phase 2 compositor bring-up should succeed without D-Bus changes. + +**Phase 3 hard gate:** `TakeDevice` FD passing + `PauseDevice`/`ResumeDevice` signal emission. This is required for KWin to own real DRM and input devices through the freedesktop session protocol. No bypass exists. + +**Phase 4 broader surface:** `kglobalaccel` binary, `kded6` binary, `StatusNotifierWatcher`, `Inhibit` methods, session identity derivation. + +### Phase 3 Gate (DRM Compositor) — Required D-Bus Changes + +Four fixes are required before KWin can use real hardware devices through login1: + +| # | Fix | Current state | Required change | +|---|-----|---------------|-----------------| +| 1 | `Manager.Inhibit` + `CanPowerOff`/`CanSuspend`/`CanHibernate` stubs | Missing | Return `"na"` string from each method; required by KDE's session management layer | +| 2 | `PauseDevice`/`ResumeDevice` signal emission | Declared but not emitted | Emit `uus` (major, minor, type) for PauseDevice and `uuh` (major, minor, fd) for ResumeDevice in `session.rs` when device state changes | +| 3 | Dynamic device enumeration | Static `device_map.rs` with hardcoded major/minor | Query udev-shim at runtime for major/minor -> scheme path mapping; remove hardcoded lookup table | +| 4 | Missing Session methods | `SetIdleHint`, `SetLockedHint`, `SetType`, `Terminate` not implemented | Implement these or return errors; KDE session managers call these to track session state | + +### Phase 4 Gate (KDE Plasma Session) — Required D-Bus Changes + +| # | Improvement | Current state | Required change | +|---|-------------|---------------|-----------------| +| 1 | `StatusNotifierWatcher` implementation | New service needed | Register `org.freedesktop.StatusNotifierWatcher` on session bus; track registered items, emit `ItemRegistered`/`ItemUnregistered` signals | +| 2 | `kglobalaccel` binary build | KDE app recipe builds library, daemon binary is a separate recipe step | Add `kglobalaccel` binary to `local/recipes/kde/kf6-kglobalaccel/` or create separate recipe | +| 3 | `kded6` binary build | KDE app recipe builds library, daemon binary is a separate recipe step | Add `kded6` binary to `local/recipes/kde/kf6-kded6/` or create separate recipe | +| 4 | Session identity derivation | Hardcoded to `c1`, `root`, `uid=0` | Query real session environment variables (`XDG_SESSION_ID`, `XDG_SEAT`) and derive identity from the actual login session | +| 5 | `UPower Changed` signal emission + polling | No signals, no polling | Emit `Changed` signal when power state changes; implement property polling for `OnBattery`, `Percentage`, `TimeToEmpty` | +| 6 | `Notifications ActionInvoked` signal + capabilities | Not implemented | Emit `ActionInvoked(uint32, string)` when user clicks notification action; expand `GetCapabilities` to include `body`, `actions`, `icon-static` | +| 7 | Stoppable daemons | Services use `pending()` with no shutdown channel | Replace `pending()` in all services with proper shutdown signal channels; enable service restart and clean shutdown | + +### KWin Method-by-Method Readiness Matrix + +| KWin D-Bus call | Current impl | Phase 2 needed | Phase 3 needed | +|-----------------|--------------|---------------|----------------| +| `GetSession("auto")` | via NoopSession | No (bypasses logind) | Yes | +| `TakeControl(false)` | Via login1 | No | Yes | +| `TakeDevice(226, 0)` (DRM) | Via DeviceMap | No | Yes (critical) | +| `TakeDevice(13, 64+)` (input) | Via DeviceMap | No | Yes (critical) | +| `PauseDevice` signal | Declared, not emitted | No | Yes (critical) | +| `ResumeDevice` signal | Declared, not emitted | No | Yes (critical) | +| `Seat.SwitchTo` | Via login1 | No | Yes | +| `Manager.Inhibit` | Missing | No | Yes | +| `CanPowerOff`/`CanSuspend`/`CanHibernate` | Missing | No | Yes | +| `PrepareForShutdown` | Via ACPI | No | Yes | +| `PrepareForSleep` | Declared, not emitted | No | Yes | + +### Completeness by Service + +| Service | Methods real | Total expected | Completeness | +|---------|-------------|---------------|--------------| +| `login1.Manager` | 3 | ~30+ | ~10% | +| `login1.Session` | 7 | ~15+ | ~47% | +| `login1.Seat` | 1 | 5 | ~20% | +| `Notifications` | 4 | ~5 | ~80% | +| `UPower` | 3 | ~5 | ~60% | +| `UDisks2` | 4 | ~8+ | ~50% | +| `PolicyKit1` | 3 | ~6+ | ~50% | + +### Missing KDE D-Bus Services + +| Service | Used by | Status | Impact | +|---------|---------|--------|--------| +| `org.kde.kglobalaccel` | All KDE apps (global shortcuts) | Binary missing | HIGH | +| `org.kde.kded6` | KDE daemon (status notifier, etc.) | Binary missing | HIGH | +| `org.freedesktop.StatusNotifierWatcher` | System tray | New service needed | MEDIUM | +| `org.kde.ksmserver` | Session management | Not implemented | MEDIUM | +| `org.freedesktop.ScreenSaver` | Screen locking | Not implemented | MEDIUM | + +### Implementation Priority Order + +1. `redbear-sessiond` Phase 3 methods (enables DRM compositor session) +2. Dynamic device enumeration (enables non-static hardware discovery) +3. Stoppable daemons (enables testing and restart) +4. `StatusNotifierWatcher` (enables system tray) +5. `UPower` polling + signals (enables battery applet) +6. Session identity improvements (enables non-root sessions) diff --git a/local/docs/DESKTOP-STACK-CURRENT-STATUS.md b/local/docs/DESKTOP-STACK-CURRENT-STATUS.md index 3767c2ce..e2d8e338 100644 --- a/local/docs/DESKTOP-STACK-CURRENT-STATUS.md +++ b/local/docs/DESKTOP-STACK-CURRENT-STATUS.md @@ -31,10 +31,11 @@ greeter/auth/session-launch stack on the `redbear-full` desktop path. ## Active Target Surface and Evidence Boundary -- The supported compile targets are `redbear-mini`, `redbear-live-mini`, `redbear-full`, and `redbear-live-full`. -- Desktop/graphics are available only on `redbear-full` and `redbear-live-full`. -- Older names such as `redbear-kde`, `redbear-wayland`, and `redbear-minimal*` still appear in - historical or staging material, but they are not the supported compile-target surface. +- The supported compile targets are `redbear-mini`, `redbear-full`, and `redbear-grub`. +- Desktop/graphics are available only on `redbear-full`. +- Older names such as `redbear-kde`, `redbear-wayland`, `redbear-minimal*`, `redbear-live-mini`, + and `redbear-live-full` still appear in historical or staging material, but they are not the + supported compile-target surface. - The greeter/login path is currently an **experimental build/integration surface** on `redbear-full`; it is not yet a runtime-validated end-to-end desktop-login claim. @@ -51,8 +52,8 @@ greeter/auth/session-launch stack on the `redbear-full` desktop path. | Mesa EGL+GBM+GLES2 | **builds** | Software path via LLVMpipe proven in QEMU; hardware path not proven | | libdrm amdgpu | **builds** | Package-level success only | | Input stack | **builds, enumerates** | evdevd (65 tests), libevdev, libinput, seatd present; evdevd registers scheme at boot; end-to-end compositor input path unproven | -| D-Bus | **builds, usable (bounded)** | System bus wired in `redbear-full`; session bus incomplete (redbear-sessiond login1 broker only) | -| redbear-sessiond | **builds, scaffold** | org.freedesktop.login1 D-Bus session broker — Rust daemon (zbus 5), wired on the `redbear-full` desktop path; now includes runtime control updates used by the greeter/auth session handoff | +| D-Bus | **builds, bounded (in improvement)** | System bus wired in `redbear-full`; session bus incomplete; Phase 3/4 improvement plan active; completeness: login1.Manager ~10%, login1.Session ~47%, login1.Seat ~20%, Notifications ~80%, UPower ~60%, UDisks2 ~50%, PolicyKit1 ~50%; `StatusNotifierWatcher` is the new service being added in Phase 4 | +| redbear-sessiond | **builds, scaffold (Phase 3/4 improvement active)** | org.freedesktop.login1 D-Bus session broker — Rust daemon (zbus 5), wired on the `redbear-full` desktop path; Phase 3 hard gate is TakeDevice FD passing plus PauseDevice/ResumeDevice signal emission; Priority 1 in Phase 3/4 improvement plan | | redbear-authd | **builds** | Privileged local-user auth daemon; `/etc/passwd`/`/etc/shadow`/`/etc/group` parsing, SHA-256/SHA-512 crypt verification, bounded lockout, target-side recipe build proven | | redbear-session-launch | **builds** | User-session bootstrap tool; runtime-dir/env setup, uid/gid handoff, dbus-run-session → `redbear-kde-session`, target-side recipe build proven | | redbear-greeterd | **builds, experimental** | Root-owned greeter orchestrator; UI/auth socket protocol, bounded restart policy, return-to-greeter daemon logic, crate tests pass; end-to-end runtime proof still pending | @@ -73,9 +74,8 @@ greeter/auth/session-launch stack on the `redbear-full` desktop path. | validation compositor runtime | **experimental** | Reaches early init in QEMU; no complete session | | validation profile | **builds, boots** | Bounded Wayland runtime profile | | `redbear-full` profile | **builds, boots** | Active desktop/graphics compile surface; now owns the experimental greeter/auth/session-launch integration path | -| `redbear-live-full` profile | **builds** | Live image following the active desktop/graphics target | +| `redbear-grub` profile | **builds** | Text-only with GRUB chainload for bare-metal multi-boot | | `redbear-mini` profile | **builds** | Minimal non-desktop compile target | -| `redbear-live-mini` profile | **builds** | Minimal live image target | | `redbear-hwutils` | **builds** | lspci/lsusb tools; 19 unit tests (PCI location parsing, USB device description, argument handling) | ## Profile View @@ -87,11 +87,11 @@ greeter/auth/session-launch stack on the `redbear-full` desktop path. - **Use for:** Desktop integration testing, greeter/login bring-up, and bounded desktop/network plumbing validation - **Do not overclaim:** This profile proves bounded QEMU desktop/network plumbing only. It does not by itself close the Wi-Fi implementation plan's later real-hardware Phase W5 reporting/recovery gate. -### `redbear-live-full` +### `redbear-grub` -- **Role:** Live/demo/recovery image layered on the active desktop target -- **Current truth:** Follows `redbear-full`; desktop/graphics-capable live image, but the greeter/login surface remains experimental until end-to-end proof exists -- **Use for:** Demo, install, and bounded live-media validation on the current desktop surface +- **Role:** Text-only target with GRUB boot manager for bare-metal multi-boot +- **Current truth:** Follows `redbear-mini`; text-only with GRUB chainload ESP layout, no desktop/graphics +- **Use for:** Bare-metal multi-boot, recovery with GRUB menu, and install workflows requiring GRUB ### `redbear-mini` @@ -99,12 +99,6 @@ greeter/auth/session-launch stack on the `redbear-full` desktop path. - **Current truth:** No desktop/graphics path; recovery and non-desktop integration surface only. TUI recovery is bound to VT activation through `29_activate_console.service` followed by `30_console.service`/`31_debug_console.service`. - **Use for:** Minimal runtime bring-up, subsystem validation, and non-desktop packaging checks -### `redbear-live-mini` - -- **Role:** Minimal live image target -- **Current truth:** No desktop/graphics path; live/recovery-oriented minimal image surface -- **Use for:** Minimal live boot and recovery workflows - ## Current Blockers ### 1. Runtime trust trails build success (Phase 1 gate) @@ -197,7 +191,7 @@ QtNetwork is intentionally disabled because relibc networking is too narrow. Thi The Red Bear desktop stack has crossed major build-side gates and one important bounded runtime gate: - All Qt6 core modules, all 32 KF6 frameworks, Mesa EGL/GBM/GLES2, and D-Bus build -- Four supported compile targets exist, with desktop/graphics on `redbear-full` and `redbear-live-full` +- Four supported compile targets exist, with desktop/graphics on `redbear-full` - the Red Bear-native greeter/login path now has a bounded passing QEMU proof (`GREETER_HELLO=ok`, `GREETER_INVALID=ok`, `GREETER_VALID=ok`) - relibc compatibility is materially stronger than before - Phase 1 test coverage is comprehensive: 300+ unit tests across all Phase 1 daemons (evdevd 65, udev-shim 15, firmware-loader 24, redox-drm 68, redbear-hwutils 19, bluetooth/wifi 209) diff --git a/local/docs/GREETER-LOGIN-IMPLEMENTATION-PLAN.md b/local/docs/GREETER-LOGIN-IMPLEMENTATION-PLAN.md index 9d7014cd..db8411d7 100644 --- a/local/docs/GREETER-LOGIN-IMPLEMENTATION-PLAN.md +++ b/local/docs/GREETER-LOGIN-IMPLEMENTATION-PLAN.md @@ -75,9 +75,9 @@ recovery model. This plan assumes the Red Bear desktop direction converges on **one KDE-on-Wayland path**. -Current implementation answer: the first tracked owner is `redbear-full` (and therefore -`redbear-live-full` for live media). Older names such as `redbear-kde` may still appear in -historical or staging material, but they are not the supported compile-target surface for this plan. +Current implementation answer: the first tracked owner is `redbear-full`. Older names such +as `redbear-kde` may still appear in historical or staging material, but they are not the +supported compile-target surface for this plan. --- @@ -866,8 +866,7 @@ The greeter **recipe**, not the config fragment, should own staged runtime artif 4. Is `dbus-run-session` reliable enough on Red Bear, or should the current `dbus-launch` path remain the first shipped session-bus strategy? 5. At what point should the project consider SDDM-class integration again, if ever? -Current answer to (1): **`redbear-full` first**, with `redbear-live-full` inheriting that path for -live media. +Current answer to (1): **`redbear-full` first**. Current answer to (2): **traditional `/etc/shadow` SHA-512-crypt / SHA-256-crypt first** (`$6$` / `$5$`), with narrower support preferred over premature multi-format sprawl. diff --git a/local/docs/GRUB-INTEGRATION-PLAN.md b/local/docs/GRUB-INTEGRATION-PLAN.md index 733b6219..c94e3f4e 100644 --- a/local/docs/GRUB-INTEGRATION-PLAN.md +++ b/local/docs/GRUB-INTEGRATION-PLAN.md @@ -3,8 +3,8 @@ **Date:** 2026-04-17 **Status:** Fully implemented (build-tested, not yet runtime boot-tested). ESP formatted as FAT32 per UEFI spec. Both Phase 1 (post-build script) and Phase 2 (installer-native) are wired. -**Remaining:** Runtime UEFI boot validation in QEMU (`make all CONFIG_NAME=redbear-full-grub && make qemu`). -**Prerequisite:** The `grub` package is included in `redbear-full-grub.toml` for clean-tree builds. +**Remaining:** Runtime UEFI boot validation in QEMU (`make all CONFIG_NAME=redbear-grub && make qemu`). +**Prerequisite:** The `grub` package is included in `redbear-grub.toml` for clean-tree builds. **Approach:** Option A — GRUB as boot manager, chainloading Redox bootloader ## Overview @@ -257,7 +257,7 @@ ESP layout automatically. ### Config Usage ```toml -# config/redbear-full-grub.toml +# config/redbear-grub.toml include = ["redbear-full.toml"] [general] @@ -271,7 +271,7 @@ Or via CLI (note: INSTALLER_OPTS replaces defaults, so --cookbook=. must be incl make all CONFIG_NAME=redbear-full INSTALLER_OPTS="--cookbook=. --bootloader grub" ``` -**Note:** The config file approach (`redbear-full-grub.toml`) is preferred over the CLI flag +**Note:** The config file approach (`redbear-grub.toml`) is preferred over the CLI flag because INSTALLER_OPTS completely replaces the default value (`--cookbook=.`) rather than appending to it. Omitting `--cookbook=.` breaks local package resolution for GRUB. @@ -356,7 +356,7 @@ make qemu make r.grub # Build image with GRUB config (installer fetches GRUB automatically) -make all CONFIG_NAME=redbear-full-grub +make all CONFIG_NAME=redbear-grub # Or via CLI flag make all CONFIG_NAME=redbear-full INSTALLER_OPTS="--bootloader grub --cookbook=." @@ -376,7 +376,7 @@ make qemu CI=1 ./target/release/repo cook grub # Verify host-side installer accepts --bootloader flag -build/fstools/bin/redox_installer --bootloader=grub --config=config/redbear-full-grub.toml --list-packages +build/fstools/bin/redox_installer --bootloader=grub --config=config/redbear-grub.toml --list-packages # Verify fat_tool.py operations python3 local/scripts/fat_tool.py --help diff --git a/local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md b/local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md index 89cd895e..e72a150b 100644 --- a/local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md +++ b/local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md @@ -681,7 +681,7 @@ helper hardening comes before broad driver cleanup, and runtime-proof/observabil **Verification** -- build passes: `CI=1 make r.base CONFIG_NAME=redbear-live-mini ARCH=x86_64` +- build passes: `CI=1 make r.base CONFIG_NAME=redbear-mini ARCH=x86_64` - downstream consumers compile without errors ### Wave 4 — Convert highest-risk consumers @@ -740,7 +740,7 @@ sites converted on the PCIe ECAM/DTB/MCFG startup path). Only Mutex `.lock().unw **Verification** -- `CI=1 make cr.base CONFIG_NAME=redbear-live-mini ARCH=x86_64` — zero errors, build successful +- `CI=1 make cr.base CONFIG_NAME=redbear-mini ARCH=x86_64` — zero errors, build successful - per-driver grep verified zero remaining panic-grade calls (only Mutex `.lock().unwrap()` kept) ### Wave 5 — Improve observability and proof diff --git a/local/docs/PROFILE-MATRIX.md b/local/docs/PROFILE-MATRIX.md index 6390d889..ab6b9815 100644 --- a/local/docs/PROFILE-MATRIX.md +++ b/local/docs/PROFILE-MATRIX.md @@ -27,9 +27,8 @@ USB plan uses: | Profile | Intent | Key Fragments | Current support language | |---|---|---|---| | `redbear-mini` | Console + storage + wired-network baseline | `minimal.toml`, `redbear-legacy-base.toml`, `redbear-device-services.toml`, `redbear-netctl.toml` | builds / primary validation baseline / DHCP boot profile enabled / input-runtime substrate wired / USB: daemons built via base and targeted for bounded mini-profile validation | -| `redbear-live-mini` | Live/recovery form of the mini baseline | `redbear-live-minimal.toml`, `redbear-minimal.toml` | builds / live media variant of the mini profile for real bare metal / desktop graphics intentionally absent | +| `redbear-grub` | Text-only with GRUB boot manager | `redbear-mini.toml`, `redbear-grub-policy.toml` | builds / live media variant with GRUB chainload for real bare metal / desktop graphics intentionally absent | | `redbear-full` | Desktop/network/session plumbing target | `desktop.toml`, `redbear-legacy-base.toml`, `redbear-legacy-desktop.toml`, `redbear-device-services.toml`, `redbear-netctl.toml`, `redbear-greeter-services.toml` | builds / boots in QEMU / active desktop-capable compile target / support claims remain evidence-qualified | -| `redbear-live-full` | Live/recovery form of the full desktop target | `redbear-live-full.toml`, `redbear-full.toml` | builds / live desktop-capable image for real bare metal / inherits the full target surface | ## Profile Notes @@ -46,9 +45,10 @@ USB plan uses: are bounded validation slices layered on top of the tracked compile targets, not additional compile targets. -### `redbear-live-mini` +### `redbear-grub` -- Carries the same bounded non-graphics intent as `redbear-mini`, but in live/recovery image form. +- Text-only console/recovery target with GRUB boot manager for multi-boot bare-metal workflows. +- Inherits the same non-graphics intent as `redbear-mini`, but with GRUB chainload ESP layout. - Should not grow desktop/session assumptions. ### `redbear-full` @@ -56,22 +56,17 @@ USB plan uses: - Desktop-capable tracked target for the current Red Bear session/network/runtime plumbing surface. - Carries the broader D-Bus, greeter, seat, and desktop-oriented service surface. -### `redbear-live-full` - -- Live/demo/recovery form of the full desktop-capable target. -- Inherits the same desktop-target assumptions as `redbear-full`, but for live media workflows. - ### Historical notes -- Older names such as `redbear-minimal`, `redbear-desktop`, `redbear-wayland`, `redbear-kde`, and - `redbear-live` remain in older docs and some implementation details, but they are not the current - supported compile-target surface. +- Older names such as `redbear-minimal`, `redbear-desktop`, `redbear-wayland`, `redbear-kde`, + `redbear-live`, `redbear-live-mini`, and `redbear-live-full` remain in older docs and some + implementation details, but they are not the current supported compile-target surface. ### `redbear-bluetooth-experimental` - Standalone tracked profile for the first in-tree Bluetooth slice instead of a blanket claim about all Red Bear images. -- Extends `redbear-minimal` so the baseline runtime tooling is already present, then adds only the +- Extends `redbear-mini` so the baseline runtime tooling is already present, then adds only the bounded Bluetooth pieces on top. - Current path under active validation: QEMU/UEFI boot to login prompt plus guest-side `redbear-bluetooth-battery-check`, targeting repeated in-boot reruns, daemon-restart coverage, and one experimental battery-sensor Battery Level read-only workload. - Current support language is intentionally narrow: explicit-startup only, USB-attached transport, @@ -82,7 +77,7 @@ USB plan uses: - Standalone tracked profile for the current bounded Intel Wi-Fi slice instead of implying that the wider desktop profiles already carry the full driver stack. -- Extends `redbear-minimal` so the baseline firmware/input/reporting/profile-manager surface stays +- Extends `redbear-mini` so the baseline firmware/input/reporting/profile-manager surface stays inherited while the Intel Wi-Fi driver package and bounded validation role remain isolated here. - Includes the Intel driver package (`redbear-iwlwifi`) in addition to the shared firmware, control-plane, reporting, and profile-manager pieces. @@ -109,6 +104,6 @@ USB plan uses: - USB error handling and correctness carry significant Red Bear patches over upstream; see `local/patches/base/redox.patch` and `local/docs/USB-IMPLEMENTATION-PLAN.md` for details. - The in-tree mini image is still assembled through legacy `redbear-minimal*` config files in some - places, but the supported compile-target names are `redbear-mini` and `redbear-live-mini`. + places, but the supported compile-target names are `redbear-mini` and `redbear-grub`. - `redbear-bluetooth-experimental` uses USB only as a transport for BLE dongles; it does not make a general USB-class-autospawn claim. diff --git a/local/docs/USB-VALIDATION-RUNBOOK.md b/local/docs/USB-VALIDATION-RUNBOOK.md index 284998df..80c337c8 100644 --- a/local/docs/USB-VALIDATION-RUNBOOK.md +++ b/local/docs/USB-VALIDATION-RUNBOOK.md @@ -126,13 +126,12 @@ In-guest quick checks: ## Compile-target note -Red Bear has exactly four compile targets: +Red Bear has exactly three compile targets: - `redbear-mini` -- `redbear-live-mini` - `redbear-full` -- `redbear-live-full` +- `redbear-grub` -Older names such as `redbear-desktop`, `redbear-wayland`, `redbear-kde`, and `redbear-minimal` may -still appear in historical notes or implementation details, but they are not the supported -compile-target surface. +Older names such as `redbear-desktop`, `redbear-wayland`, `redbear-kde`, `redbear-minimal`, +`redbear-live-mini`, and `redbear-live-full` may still appear in historical notes or +implementation details, but they are not the supported compile-target surface. diff --git a/local/docs/repo-governance.md b/local/docs/repo-governance.md index 6a6f07d1..443a5c3d 100644 --- a/local/docs/repo-governance.md +++ b/local/docs/repo-governance.md @@ -18,13 +18,11 @@ reproducible, reviewable, and upstream-friendly. Tracked Red Bear profiles are: -- `redbear-minimal` -- `redbear-bluetooth-experimental` -- `redbear-desktop` +- `redbear-mini` - `redbear-full` -- `redbear-wayland` -- `redbear-kde` -- `redbear-live` +- `redbear-grub` +- `redbear-bluetooth-experimental` +- `redbear-wifi-experimental` Every user-visible feature should name which profile(s) it belongs to. @@ -62,7 +60,7 @@ why it is intentionally excluded. ## Profile Intent -### `redbear-minimal` +### `redbear-mini` Primary validation baseline: console, storage, package flow, and wired networking. @@ -71,25 +69,18 @@ Primary validation baseline: console, storage, package flow, and wired networkin First bounded Bluetooth validation profile: explicit-startup, USB-attached, BLE-first, and experimental only. -### `redbear-desktop` - -Supplementary integration support profile for shared Red Bear runtime services beneath the tracked KWin target. - ### `redbear-full` -Expanded integration slice that includes more runtime pieces and graphics-path bring-up beneath the tracked KWin target. +Desktop-capable tracked target for the current Red Bear session/network/runtime plumbing surface, +including graphics-path bring-up beneath the tracked KWin direction. -### `redbear-wayland` +### `redbear-grub` -Dedicated Wayland runtime validation profile layered above the current Red Bear service baseline and subordinate to the tracked KWin direction. +Text-only console/recovery target with GRUB boot manager for bare-metal multi-boot workflows. -### `redbear-kde` +### `redbear-wifi-experimental` -Dedicated KDE/Plasma bring-up profile and tracked forward desktop target. - -### `redbear-live` - -Live and recovery variant layered on top of the tracked KWin desktop target. +Bounded Intel Wi-Fi validation profile layered on the mini baseline. ## Change Checklist diff --git a/local/recipes/kde/kwin/source/src/core/session.cpp b/local/recipes/kde/kwin/source/src/core/session.cpp index 9d99e75c..37058591 100644 --- a/local/recipes/kde/kwin/source/src/core/session.cpp +++ b/local/recipes/kde/kwin/source/src/core/session.cpp @@ -24,9 +24,6 @@ static const struct std::unique_ptr Session::create() { -#ifdef Q_OS_REDOX - return NoopSession::create(); -#else for (const auto &sessionInfo : s_availableSessions) { std::unique_ptr session = sessionInfo.createFunc(); if (session) { @@ -34,20 +31,19 @@ std::unique_ptr Session::create() } } return nullptr; -#endif } std::unique_ptr Session::create(Type type) { #ifdef Q_OS_REDOX - switch (type) { - case Type::Logind: - return NoopSession::create(); - case Type::ConsoleKit: - return ConsoleKitSession::create(); - case Type::Noop: - return NoopSession::create(); + for (const auto &sessionInfo : s_availableSessions) { + if (sessionInfo.type == type) { + if (auto session = sessionInfo.createFunc()) { + return session; + } + } } + return NoopSession::create(); #else for (const auto &sessionInfo : s_availableSessions) { if (sessionInfo.type == type) { diff --git a/local/recipes/system/redbear-dbus-services/files/session-services/org.freedesktop.StatusNotifierWatcher.service b/local/recipes/system/redbear-dbus-services/files/session-services/org.freedesktop.StatusNotifierWatcher.service new file mode 100644 index 00000000..8a081759 --- /dev/null +++ b/local/recipes/system/redbear-dbus-services/files/session-services/org.freedesktop.StatusNotifierWatcher.service @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.freedesktop.StatusNotifierWatcher +Exec=/usr/bin/redbear-statusnotifierwatcher diff --git a/local/recipes/system/redbear-notifications/source/src/main.rs b/local/recipes/system/redbear-notifications/source/src/main.rs index ed7ab0a1..c8b3ea84 100644 --- a/local/recipes/system/redbear-notifications/source/src/main.rs +++ b/local/recipes/system/redbear-notifications/source/src/main.rs @@ -40,7 +40,7 @@ impl Notifications { _app_icon: &str, summary: &str, body: &str, - _actions: Vec, + actions: Vec, _hints: HashMap>, _expire_timeout: i32, ) -> u32 { @@ -48,6 +48,10 @@ impl Notifications { eprintln!("notification: [{app_name}] {summary}: {body}"); + for chunk in actions.chunks_exact(2) { + eprintln!("notification {id}: action key '{}'", chunk[0]); + } + id } @@ -64,7 +68,11 @@ impl Notifications { #[zbus(name = "GetCapabilities")] fn get_capabilities(&self) -> Vec { - vec!["body".to_owned()] + vec![ + "body".to_owned(), + "body-markup".to_owned(), + "actions".to_owned(), + ] } #[zbus(name = "GetServerInformation")] @@ -88,6 +96,13 @@ impl Notifications { id: u32, reason: u32, ) -> zbus::Result<()>; + + #[zbus(signal, name = "ActionInvoked")] + async fn action_invoked( + signal_emitter: &SignalEmitter<'_>, + id: u32, + action_key: &str, + ) -> zbus::Result<()>; } enum Command { @@ -115,25 +130,32 @@ fn parse_args() -> Result { } } -#[cfg(unix)] -async fn wait_for_shutdown() -> Result<(), Box> { - use tokio::signal::unix::{SignalKind, signal}; - - let mut terminate = signal(SignalKind::terminate())?; - - tokio::select! { - _ = terminate.recv() => Ok(()), - _ = tokio::signal::ctrl_c() => Ok(()), - } -} - -#[cfg(not(unix))] -async fn wait_for_shutdown() -> Result<(), Box> { - tokio::signal::ctrl_c().await?; - Ok(()) +fn spawn_signal_handler(shutdown_tx: tokio::sync::watch::Sender) { + tokio::spawn(async move { + #[cfg(unix)] + { + use tokio::signal::unix::{SignalKind, signal}; + if let Ok(mut sigterm) = signal(SignalKind::terminate()) { + tokio::select! { + _ = sigterm.recv() => {}, + _ = tokio::signal::ctrl_c() => {}, + } + } else { + let _ = tokio::signal::ctrl_c().await; + } + } + #[cfg(not(unix))] + { + let _ = tokio::signal::ctrl_c().await; + } + let _ = shutdown_tx.send(true); + }); } async fn run_daemon() -> Result<(), Box> { + let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false); + spawn_signal_handler(shutdown_tx); + let _connection = ConnectionBuilder::session()? .name(BUS_NAME)? .serve_at(OBJECT_PATH, Notifications::new())? @@ -142,8 +164,8 @@ async fn run_daemon() -> Result<(), Box> { eprintln!("redbear-notifications: registered {BUS_NAME} on the session bus"); - wait_for_shutdown().await?; - eprintln!("redbear-notifications: received shutdown signal, exiting cleanly"); + let _ = shutdown_rx.changed().await; + eprintln!("redbear-notifications: shutdown signal received, exiting cleanly"); Ok(()) } diff --git a/local/recipes/system/redbear-polkit/source/src/main.rs b/local/recipes/system/redbear-polkit/source/src/main.rs index 64b0d8d0..0fc75034 100644 --- a/local/recipes/system/redbear-polkit/source/src/main.rs +++ b/local/recipes/system/redbear-polkit/source/src/main.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, env, error::Error, path::Path, process, thread, time::Duration}; +use std::{collections::HashMap, env, error::Error, process, time::Duration}; use tokio::runtime::Builder as RuntimeBuilder; use zbus::{ @@ -77,29 +77,26 @@ fn system_connection_builder() -> Result, Box Result<(), Box> { - use tokio::signal::unix::{SignalKind, signal}; - - let mut terminate = signal(SignalKind::terminate())?; - - tokio::select! { - _ = terminate.recv() => Ok(()), - _ = tokio::signal::ctrl_c() => Ok(()), - } -} - -#[cfg(target_os = "redox")] -async fn wait_for_shutdown() -> Result<(), Box> { - std::future::pending::<()>().await; - #[allow(unreachable_code)] - Ok(()) -} - -#[cfg(all(not(unix), not(target_os = "redox")))] -async fn wait_for_shutdown() -> Result<(), Box> { - tokio::signal::ctrl_c().await?; - Ok(()) +fn spawn_signal_handler(shutdown_tx: tokio::sync::watch::Sender) { + tokio::spawn(async move { + #[cfg(unix)] + { + use tokio::signal::unix::{SignalKind, signal}; + if let Ok(mut sigterm) = signal(SignalKind::terminate()) { + tokio::select! { + _ = sigterm.recv() => {}, + _ = tokio::signal::ctrl_c() => {}, + } + } else { + let _ = tokio::signal::ctrl_c().await; + } + } + #[cfg(not(unix))] + { + let _ = tokio::signal::ctrl_c().await; + } + let _ = shutdown_tx.send(true); + }); } #[interface(name = "org.freedesktop.PolicyKit1.Authority")] @@ -151,6 +148,9 @@ impl PolicyKitAuthority { async fn run_daemon() -> Result<(), Box> { wait_for_dbus_socket().await; + let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false); + spawn_signal_handler(shutdown_tx); + let mut last_err = None; for attempt in 1..=5 { let _authority_path = parse_object_path(AUTHORITY_PATH)?; @@ -163,13 +163,16 @@ async fn run_daemon() -> Result<(), Box> { { Ok(connection) => { eprintln!("redbear-polkit: registered {BUS_NAME} on the system bus"); - wait_for_shutdown().await?; + let _ = shutdown_rx.changed().await; + eprintln!("redbear-polkit: shutdown signal received, exiting cleanly"); drop(connection); return Ok(()); } Err(err) => { if attempt < 5 { - eprintln!("redbear-polkit: attempt {attempt}/5 failed ({err}), retrying in 2s..."); + eprintln!( + "redbear-polkit: attempt {attempt}/5 failed ({err}), retrying in 2s..." + ); tokio::time::sleep(Duration::from_secs(2)).await; } last_err = Some(err.into()); diff --git a/local/recipes/system/redbear-sessiond/source/src/control.rs b/local/recipes/system/redbear-sessiond/source/src/control.rs index dd5fa332..ff2ea391 100644 --- a/local/recipes/system/redbear-sessiond/source/src/control.rs +++ b/local/recipes/system/redbear-sessiond/source/src/control.rs @@ -3,6 +3,7 @@ use std::{ io::{BufRead, BufReader}, os::unix::{fs::PermissionsExt, net::UnixListener}, path::Path, + sync::Arc, }; use serde::Deserialize; @@ -24,14 +25,14 @@ enum ControlMessage { ResetSession { vt: u32, }, + Shutdown, } -fn apply_message(runtime: &SharedRuntime, message: ControlMessage) { - let Ok(mut runtime) = runtime.write() else { - eprintln!("redbear-sessiond: runtime state is poisoned"); - return; - }; - +fn apply_message( + runtime: &SharedRuntime, + shutdown_tx: &tokio::sync::watch::Sender, + message: ControlMessage, +) { match message { ControlMessage::SetSession { username, @@ -40,6 +41,10 @@ fn apply_message(runtime: &SharedRuntime, message: ControlMessage) { leader, state, } => { + let Ok(mut runtime) = runtime.write() else { + eprintln!("redbear-sessiond: runtime state is poisoned"); + return; + }; runtime.username = username; runtime.uid = uid; runtime.vt = vt; @@ -48,6 +53,10 @@ fn apply_message(runtime: &SharedRuntime, message: ControlMessage) { runtime.active = true; } ControlMessage::ResetSession { vt } => { + let Ok(mut runtime) = runtime.write() else { + eprintln!("redbear-sessiond: runtime state is poisoned"); + return; + }; runtime.username = String::from("root"); runtime.uid = 0; runtime.vt = vt; @@ -55,10 +64,18 @@ fn apply_message(runtime: &SharedRuntime, message: ControlMessage) { runtime.state = String::from("closing"); runtime.active = true; } + ControlMessage::Shutdown => { + eprintln!("redbear-sessiond: shutdown requested via control socket"); + let _ = shutdown_tx.send(true); + } } } -pub fn start_control_socket(runtime: SharedRuntime) { +pub fn start_control_socket( + runtime: SharedRuntime, + shutdown_tx: tokio::sync::watch::Sender, +) { + let shutdown_tx = Arc::new(shutdown_tx); std::thread::spawn(move || { if Path::new(CONTROL_SOCKET_PATH).exists() { if let Err(err) = fs::remove_file(CONTROL_SOCKET_PATH) { @@ -79,6 +96,7 @@ pub fn start_control_socket(runtime: SharedRuntime) { eprintln!("redbear-sessiond: failed to chmod control socket: {err}"); } + let shutdown_ref = Arc::clone(&shutdown_tx); for stream in listener.incoming() { let Ok(stream) = stream else { continue; @@ -89,7 +107,7 @@ pub fn start_control_socket(runtime: SharedRuntime) { continue; } match serde_json::from_str::(line.trim()) { - Ok(message) => apply_message(&runtime, message), + Ok(message) => apply_message(&runtime, &shutdown_ref, message), Err(err) => eprintln!("redbear-sessiond: invalid control message: {err}"), } } @@ -101,12 +119,18 @@ mod tests { use super::*; use crate::runtime_state::shared_runtime; + fn test_shutdown_channel() -> (tokio::sync::watch::Sender, tokio::sync::watch::Receiver) { + tokio::sync::watch::channel(false) + } + #[test] fn set_session_message_updates_runtime_state() { let runtime = shared_runtime(); + let (tx, _rx) = test_shutdown_channel(); apply_message( &runtime, + &tx, ControlMessage::SetSession { username: String::from("user"), uid: 1000, @@ -128,9 +152,11 @@ mod tests { #[test] fn reset_session_message_restores_root_scaffold() { let runtime = shared_runtime(); + let (tx, _rx) = test_shutdown_channel(); apply_message( &runtime, + &tx, ControlMessage::SetSession { username: String::from("user"), uid: 1000, @@ -139,7 +165,7 @@ mod tests { state: String::from("active"), }, ); - apply_message(&runtime, ControlMessage::ResetSession { vt: 3 }); + apply_message(&runtime, &tx, ControlMessage::ResetSession { vt: 3 }); let runtime = runtime.read().expect("runtime lock should remain healthy"); assert_eq!(runtime.username, "root"); @@ -170,7 +196,26 @@ mod tests { assert_eq!(leader, 99); assert_eq!(state, "online"); } - ControlMessage::ResetSession { .. } => panic!("expected set_session message"), + ControlMessage::ResetSession { .. } | ControlMessage::Shutdown => { + panic!("expected set_session message") + } } } + + #[test] + fn shutdown_message_sends_true_on_channel() { + let runtime = shared_runtime(); + let (tx, mut rx) = test_shutdown_channel(); + + apply_message(&runtime, &tx, ControlMessage::Shutdown); + + assert!(*rx.borrow_and_update()); + } + + #[test] + fn shutdown_message_parses_from_json() { + let message = serde_json::from_str::(r#"{"type":"shutdown"}"#) + .expect("shutdown message should parse"); + assert!(matches!(message, ControlMessage::Shutdown)); + } } diff --git a/local/recipes/system/redbear-sessiond/source/src/device_map.rs b/local/recipes/system/redbear-sessiond/source/src/device_map.rs index 7938046d..2bf57a59 100644 --- a/local/recipes/system/redbear-sessiond/source/src/device_map.rs +++ b/local/recipes/system/redbear-sessiond/source/src/device_map.rs @@ -14,6 +14,7 @@ pub struct DeviceMap { } impl DeviceMap { + #[cfg(test)] pub fn new() -> Self { let static_paths = HashMap::from([ ((226, 0), String::from("/scheme/drm/card0")), @@ -31,6 +32,31 @@ impl DeviceMap { Self { static_paths } } + /// Build a device map that merges static entries with dynamically discovered + /// devices by scanning `/scheme/drm/card*` and `/dev/input/event*` at startup. + /// For each discovered path, stat is used to read the rdev (device number). + /// Entries with a nonzero rdev are inserted into the map; static entries are + /// kept as fallback when rdev is unavailable or zero. + pub fn discover() -> Self { + let mut paths = HashMap::from([ + ((226, 0), String::from("/scheme/drm/card0")), + ((226, 1), String::from("/scheme/drm/card1")), + ((13, 64), String::from("/dev/input/event0")), + ((13, 65), String::from("/dev/input/event1")), + ((13, 66), String::from("/dev/input/event2")), + ((13, 67), String::from("/dev/input/event3")), + ((29, 0), String::from("/dev/fb0")), + ((1, 1), String::from("/scheme/null")), + ((1, 5), String::from("/scheme/zero")), + ((1, 8), String::from("/scheme/rand")), + ]); + + discover_scheme_drm(&mut paths); + discover_dev_input(&mut paths); + + Self { static_paths: paths } + } + pub fn resolve(&self, major: u32, minor: u32) -> Option { if let Some(path) = self.static_paths.get(&(major, minor)) { return Some(path.clone()); @@ -83,6 +109,74 @@ impl DeviceMap { } } +/// Scan `/scheme/drm/` for `card*` entries and merge any with a nonzero rdev +/// into the provided map. Static entries are not overwritten. +fn discover_scheme_drm(paths: &mut HashMap<(u32, u32), String>) { + let entries = match fs::read_dir("/scheme/drm") { + Ok(entries) => entries, + Err(_) => return, + }; + + for entry in entries.flatten() { + let path = entry.path(); + let Some(name) = path.file_name().and_then(|n| n.to_str()) else { + continue; + }; + if !name.starts_with("card") { + continue; + } + + #[cfg(unix)] + if let Ok(metadata) = fs::metadata(&path) { + let rdev = metadata.rdev(); + if rdev != 0 { + let major = dev_major(rdev); + let minor = dev_minor(rdev); + paths + .entry((major, minor)) + .or_insert_with(|| path.to_string_lossy().into_owned()); + } + } + + #[cfg(not(unix))] + let _ = &path; + } +} + +/// Scan `/dev/input/` for `event*` entries and merge any with a nonzero rdev +/// into the provided map. Static entries are not overwritten. +fn discover_dev_input(paths: &mut HashMap<(u32, u32), String>) { + let entries = match fs::read_dir("/dev/input") { + Ok(entries) => entries, + Err(_) => return, + }; + + for entry in entries.flatten() { + let path = entry.path(); + let Some(name) = path.file_name().and_then(|n| n.to_str()) else { + continue; + }; + if !name.starts_with("event") { + continue; + } + + #[cfg(unix)] + if let Ok(metadata) = fs::metadata(&path) { + let rdev = metadata.rdev(); + if rdev != 0 { + let major = dev_major(rdev); + let minor = dev_minor(rdev); + paths + .entry((major, minor)) + .or_insert_with(|| path.to_string_lossy().into_owned()); + } + } + + #[cfg(not(unix))] + let _ = &path; + } +} + fn candidate_paths() -> Vec { let mut paths = Vec::new(); @@ -162,4 +256,12 @@ mod tests { assert_eq!(dev_major(event), 13); assert_eq!(dev_minor(event), 67); } + + #[test] + fn discover_returns_static_entries_when_no_dirs() { + let map = super::DeviceMap::discover(); + assert!(map.resolve(226, 0).is_some()); + assert!(map.resolve(13, 64).is_some()); + assert!(map.resolve(29, 0).is_some()); + } } diff --git a/local/recipes/system/redbear-sessiond/source/src/main.rs b/local/recipes/system/redbear-sessiond/source/src/main.rs index 59b4ba3c..33a04916 100644 --- a/local/recipes/system/redbear-sessiond/source/src/main.rs +++ b/local/recipes/system/redbear-sessiond/source/src/main.rs @@ -84,7 +84,7 @@ fn system_connection_builder() -> Result, Box Result<(), Box> { +async fn wait_for_shutdown(mut shutdown_rx: tokio::sync::watch::Receiver) -> Result<(), Box> { use tokio::signal::unix::{SignalKind, signal}; let mut terminate = signal(SignalKind::terminate())?; @@ -92,35 +92,44 @@ async fn wait_for_shutdown() -> Result<(), Box> { tokio::select! { _ = terminate.recv() => Ok(()), _ = tokio::signal::ctrl_c() => Ok(()), + _ = shutdown_rx.changed() => Ok(()), } } #[cfg(target_os = "redox")] -async fn wait_for_shutdown() -> Result<(), Box> { - std::future::pending::<()>().await; +async fn wait_for_shutdown(mut shutdown_rx: tokio::sync::watch::Receiver) -> Result<(), Box> { + tokio::select! { + _ = std::future::pending::<()>() => Ok(()), + _ = shutdown_rx.changed() => Ok(()), + } #[allow(unreachable_code)] Ok(()) } #[cfg(all(not(unix), not(target_os = "redox")))] -async fn wait_for_shutdown() -> Result<(), Box> { - tokio::signal::ctrl_c().await?; - Ok(()) +async fn wait_for_shutdown(mut shutdown_rx: tokio::sync::watch::Receiver) -> Result<(), Box> { + tokio::select! { + _ = tokio::signal::ctrl_c() => Ok(()), + _ = shutdown_rx.changed() => Ok(()), + } } async fn run_daemon() -> Result<(), Box> { wait_for_dbus_socket().await; + let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false); + let mut last_err = None; for attempt in 1..=5 { let session_path = parse_object_path(SESSION_PATH)?; let seat_path = parse_object_path(SEAT_PATH)?; let user_path = parse_object_path(USER_PATH)?; let runtime = shared_runtime(); + let device_map = DeviceMap::discover(); - let session = LoginSession::new(seat_path.clone(), user_path, DeviceMap::new(), runtime.clone()); + let session = LoginSession::new(seat_path.clone(), user_path.clone(), device_map, runtime.clone()); let seat = LoginSeat::new(session_path.clone(), runtime.clone()); - let manager = LoginManager::new(session_path, seat_path, runtime.clone()); + let manager = LoginManager::new(session_path, seat_path, user_path, runtime.clone()); match system_connection_builder()? .name(BUS_NAME)? @@ -132,9 +141,9 @@ async fn run_daemon() -> Result<(), Box> { { Ok(connection) => { eprintln!("redbear-sessiond: registered {BUS_NAME} on the system bus"); - control::start_control_socket(runtime.clone()); + control::start_control_socket(runtime.clone(), shutdown_tx.clone()); tokio::spawn(acpi_watcher::watch_and_emit(connection.clone(), runtime.clone())); - wait_for_shutdown().await?; + wait_for_shutdown(shutdown_rx).await?; drop(connection); return Ok(()); } diff --git a/local/recipes/system/redbear-sessiond/source/src/manager.rs b/local/recipes/system/redbear-sessiond/source/src/manager.rs index 141f1c8f..28013012 100644 --- a/local/recipes/system/redbear-sessiond/source/src/manager.rs +++ b/local/recipes/system/redbear-sessiond/source/src/manager.rs @@ -1,36 +1,55 @@ +use std::{ + os::fd::OwnedFd as StdOwnedFd, + os::unix::net::UnixStream, + sync::{Arc, Mutex}, +}; + use zbus::{ fdo, interface, object_server::SignalEmitter, - zvariant::OwnedObjectPath, + zvariant::{OwnedFd, OwnedObjectPath}, }; -use crate::runtime_state::SharedRuntime; +use crate::runtime_state::{InhibitorEntry, SharedRuntime}; #[derive(Clone, Debug)] pub struct LoginManager { runtime: SharedRuntime, session_path: OwnedObjectPath, seat_path: OwnedObjectPath, + user_path: OwnedObjectPath, + inhibitor_fds: Arc>>, } impl LoginManager { - pub fn new(session_path: OwnedObjectPath, seat_path: OwnedObjectPath, runtime: SharedRuntime) -> Self { + pub fn new( + session_path: OwnedObjectPath, + seat_path: OwnedObjectPath, + user_path: OwnedObjectPath, + runtime: SharedRuntime, + ) -> Self { Self { runtime, session_path, seat_path, + user_path, + inhibitor_fds: Arc::new(Mutex::new(Vec::new())), } } + + fn runtime_read(&self) -> fdo::Result> { + self.runtime + .read() + .map_err(|_| fdo::Error::Failed(String::from("login1 runtime state is poisoned"))) + } + } #[interface(name = "org.freedesktop.login1.Manager")] impl LoginManager { fn get_session(&self, id: &str) -> fdo::Result { - let runtime = self - .runtime - .read() - .map_err(|_| fdo::Error::Failed(String::from("login1 runtime state is poisoned")))?; + let runtime = self.runtime_read()?; if id == runtime.session_id || id == "auto" { return Ok(self.session_path.clone()); } @@ -39,10 +58,7 @@ impl LoginManager { } fn list_sessions(&self) -> fdo::Result> { - let runtime = self - .runtime - .read() - .map_err(|_| fdo::Error::Failed(String::from("login1 runtime state is poisoned")))?; + let runtime = self.runtime_read()?; Ok(vec![( runtime.session_id.clone(), runtime.uid, @@ -53,10 +69,7 @@ impl LoginManager { } fn get_seat(&self, id: &str) -> fdo::Result { - let runtime = self - .runtime - .read() - .map_err(|_| fdo::Error::Failed(String::from("login1 runtime state is poisoned")))?; + let runtime = self.runtime_read()?; if id == runtime.seat_id { return Ok(self.seat_path.clone()); } @@ -64,9 +77,151 @@ impl LoginManager { Err(fdo::Error::Failed(format!("unknown login1 seat '{id}'"))) } + fn inhibit(&self, what: &str, who: &str, why: &str, mode: &str) -> fdo::Result { + if mode != "block" && mode != "delay" { + return Err(fdo::Error::Failed(format!( + "inhibit mode must be 'block' or 'delay', got '{mode}'" + ))); + } + + let (end_caller, end_daemon) = UnixStream::pair() + .map_err(|err| fdo::Error::Failed(format!("failed to create inhibit pipe: {err}")))?; + + let fd_caller: StdOwnedFd = end_caller.into(); + let fd_daemon: StdOwnedFd = end_daemon.into(); + + let uid = self.runtime_read().map(|r| r.uid).unwrap_or(0); + let pid = std::process::id(); + + let entry = InhibitorEntry { + what: what.to_owned(), + who: who.to_owned(), + why: why.to_owned(), + mode: mode.to_owned(), + pid, + uid, + }; + + if let Ok(mut runtime) = self.runtime.write() { + runtime.inhibitors.push(entry); + } + + if let Ok(mut fds) = self.inhibitor_fds.lock() { + fds.push(fd_daemon); + } + + eprintln!( + "redbear-sessiond: Inhibit(what={what}, who={who}, mode={mode}) granted" + ); + + Ok(OwnedFd::from(fd_caller)) + } + + fn can_power_off(&self) -> fdo::Result { + Ok(String::from("na")) + } + + fn can_reboot(&self) -> fdo::Result { + Ok(String::from("na")) + } + + fn can_suspend(&self) -> fdo::Result { + Ok(String::from("na")) + } + + fn can_hibernate(&self) -> fdo::Result { + Ok(String::from("na")) + } + + fn can_hybrid_sleep(&self) -> fdo::Result { + Ok(String::from("na")) + } + + fn can_suspend_then_hibernate(&self) -> fdo::Result { + Ok(String::from("na")) + } + + fn can_sleep(&self) -> fdo::Result { + Ok(String::from("na")) + } + + fn power_off(&self, _interactive: bool) -> fdo::Result<()> { + eprintln!("redbear-sessiond: PowerOff requested"); + if let Ok(mut runtime) = self.runtime.write() { + runtime.preparing_for_shutdown = true; + } + Ok(()) + } + + fn reboot(&self, _interactive: bool) -> fdo::Result<()> { + eprintln!("redbear-sessiond: Reboot requested"); + Ok(()) + } + + fn suspend(&self, _interactive: bool) -> fdo::Result<()> { + eprintln!("redbear-sessiond: Suspend requested"); + Ok(()) + } + + fn get_session_by_pid(&self, _pid: u32) -> fdo::Result { + Ok(self.session_path.clone()) + } + + fn list_users(&self) -> fdo::Result> { + let runtime = self.runtime_read()?; + Ok(vec![( + runtime.uid, + runtime.username.clone(), + self.user_path.clone(), + )]) + } + + fn list_seats(&self) -> fdo::Result> { + let runtime = self.runtime_read()?; + Ok(vec![(runtime.seat_id.clone(), self.seat_path.clone())]) + } + + fn list_inhibitors(&self) -> fdo::Result> { + let runtime = self.runtime_read()?; + Ok(runtime + .inhibitors + .iter() + .map(|entry| { + ( + entry.what.clone(), + entry.who.clone(), + entry.why.clone(), + entry.mode.clone(), + entry.pid, + entry.uid, + ) + }) + .collect()) + } + + fn activate_session(&self, session_id: &str) -> fdo::Result<()> { + eprintln!("redbear-sessiond: ActivateSession({session_id}) — no-op"); + Ok(()) + } + + fn lock_session(&self, session_id: &str) -> fdo::Result<()> { + eprintln!("redbear-sessiond: LockSession({session_id})"); + Ok(()) + } + + fn unlock_session(&self, session_id: &str) -> fdo::Result<()> { + eprintln!("redbear-sessiond: UnlockSession({session_id})"); + Ok(()) + } + + fn terminate_session(&self, session_id: &str) -> fdo::Result<()> { + eprintln!("redbear-sessiond: TerminateSession({session_id})"); + Ok(()) + } + #[zbus(property(emits_changed_signal = "const"), name = "IdleHint")] fn idle_hint(&self) -> bool { - false + self.runtime_read().map(|r| r.idle_hint).unwrap_or(false) } #[zbus(property(emits_changed_signal = "const"), name = "IdleSinceHint")] @@ -81,12 +236,30 @@ impl LoginManager { #[zbus(property(emits_changed_signal = "const"), name = "BlockInhibited")] fn block_inhibited(&self) -> String { - String::new() + self.runtime_read() + .map(|r| { + r.inhibitors + .iter() + .filter(|i| i.mode == "block") + .map(|i| i.what.as_str()) + .collect::>() + .join(":") + }) + .unwrap_or_default() } #[zbus(property(emits_changed_signal = "const"), name = "DelayInhibited")] fn delay_inhibited(&self) -> String { - String::new() + self.runtime_read() + .map(|r| { + r.inhibitors + .iter() + .filter(|i| i.mode == "delay") + .map(|i| i.what.as_str()) + .collect::>() + .join(":") + }) + .unwrap_or_default() } #[zbus(property(emits_changed_signal = "const"), name = "InhibitDelayMaxUSec")] @@ -111,8 +284,7 @@ impl LoginManager { #[zbus(property(emits_changed_signal = "const"), name = "PreparingForShutdown")] fn preparing_for_shutdown(&self) -> bool { - self.runtime - .read() + self.runtime_read() .map(|runtime| runtime.preparing_for_shutdown) .unwrap_or(false) } @@ -132,15 +304,21 @@ mod tests { use super::*; use crate::runtime_state::shared_runtime; - #[test] - fn get_session_accepts_runtime_session_id() { - let manager = LoginManager::new( + fn test_manager() -> LoginManager { + LoginManager::new( OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/session/c1")) .expect("session path should parse"), OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")) .expect("seat path should parse"), + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")) + .expect("user path should parse"), shared_runtime(), - ); + ) + } + + #[test] + fn get_session_accepts_runtime_session_id() { + let manager = test_manager(); let path = manager .get_session("c1") @@ -150,13 +328,7 @@ mod tests { #[test] fn get_session_accepts_auto_alias() { - let manager = LoginManager::new( - OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/session/c1")) - .expect("session path should parse"), - OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")) - .expect("seat path should parse"), - shared_runtime(), - ); + let manager = test_manager(); let path = manager .get_session("auto") @@ -177,9 +349,127 @@ mod tests { .expect("session path should parse"), OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")) .expect("seat path should parse"), + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")) + .expect("user path should parse"), runtime, ); assert!(manager.preparing_for_shutdown()); } + + #[test] + fn can_methods_return_na() { + let manager = test_manager(); + assert_eq!(manager.can_power_off().unwrap(), "na"); + assert_eq!(manager.can_reboot().unwrap(), "na"); + assert_eq!(manager.can_suspend().unwrap(), "na"); + assert_eq!(manager.can_hibernate().unwrap(), "na"); + assert_eq!(manager.can_hybrid_sleep().unwrap(), "na"); + assert_eq!(manager.can_suspend_then_hibernate().unwrap(), "na"); + assert_eq!(manager.can_sleep().unwrap(), "na"); + } + + #[test] + fn list_users_returns_runtime_user() { + let runtime = shared_runtime(); + runtime.write().expect("lock").username = String::from("testuser"); + runtime.write().expect("lock").uid = 1000; + + let manager = LoginManager::new( + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/session/c1")).unwrap(), + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")).unwrap(), + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")).unwrap(), + runtime, + ); + + let users = manager.list_users().expect("list_users should succeed"); + assert_eq!(users.len(), 1); + assert_eq!(users[0].0, 1000); + assert_eq!(users[0].1, "testuser"); + } + + #[test] + fn list_seats_returns_runtime_seat() { + let manager = test_manager(); + let seats = manager.list_seats().expect("list_seats should succeed"); + assert_eq!(seats.len(), 1); + assert_eq!(seats[0].0, "seat0"); + } + + #[test] + fn get_session_by_pid_returns_session_path() { + let manager = test_manager(); + let path = manager.get_session_by_pid(1234).expect("should succeed"); + assert_eq!(path.as_str(), "/org/freedesktop/login1/session/c1"); + } + + #[test] + fn list_inhibitors_empty_by_default() { + let manager = test_manager(); + let inhibitors = manager.list_inhibitors().expect("should succeed"); + assert!(inhibitors.is_empty()); + } + + #[test] + fn inhibit_rejects_invalid_mode() { + let manager = test_manager(); + let err = manager.inhibit("sleep", "test", "reason", "invalid").unwrap_err(); + match err { + fdo::Error::Failed(msg) => assert!(msg.contains("block") || msg.contains("delay")), + other => panic!("expected Failed error, got {other:?}"), + } + } + + #[test] + fn inhibit_tracks_entry_in_runtime() { + let runtime = shared_runtime(); + let manager = LoginManager::new( + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/session/c1")).unwrap(), + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")).unwrap(), + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")).unwrap(), + runtime.clone(), + ); + + let _fd = manager + .inhibit("sleep", "testapp", "testing", "block") + .expect("inhibit should succeed"); + + let runtime_guard = runtime.read().expect("lock"); + assert_eq!(runtime_guard.inhibitors.len(), 1); + assert_eq!(runtime_guard.inhibitors[0].what, "sleep"); + assert_eq!(runtime_guard.inhibitors[0].who, "testapp"); + assert_eq!(runtime_guard.inhibitors[0].mode, "block"); + } + + #[test] + fn block_inhibited_joins_what_fields() { + let runtime = shared_runtime(); + runtime.write().expect("lock").inhibitors.push(InhibitorEntry { + what: String::from("sleep"), + who: String::from("app1"), + why: String::from("r"), + mode: String::from("block"), + pid: 1, + uid: 0, + }); + runtime.write().expect("lock").inhibitors.push(InhibitorEntry { + what: String::from("shutdown"), + who: String::from("app2"), + why: String::from("r"), + mode: String::from("block"), + pid: 2, + uid: 0, + }); + + let manager = LoginManager::new( + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/session/c1")).unwrap(), + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")).unwrap(), + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")).unwrap(), + runtime, + ); + + let blocked = manager.block_inhibited(); + assert!(blocked.contains("sleep")); + assert!(blocked.contains("shutdown")); + } } diff --git a/local/recipes/system/redbear-sessiond/source/src/runtime_state.rs b/local/recipes/system/redbear-sessiond/source/src/runtime_state.rs index ada7ba69..8bc2894f 100644 --- a/local/recipes/system/redbear-sessiond/source/src/runtime_state.rs +++ b/local/recipes/system/redbear-sessiond/source/src/runtime_state.rs @@ -1,5 +1,15 @@ use std::sync::{Arc, RwLock}; +#[derive(Clone, Debug)] +pub struct InhibitorEntry { + pub what: String, + pub who: String, + pub why: String, + pub mode: String, + pub pid: u32, + pub uid: u32, +} + #[derive(Clone, Debug)] pub struct SessionRuntime { pub session_id: String, @@ -11,6 +21,10 @@ pub struct SessionRuntime { pub state: String, pub active: bool, pub preparing_for_shutdown: bool, + pub idle_hint: bool, + pub locked_hint: bool, + pub session_type: String, + pub inhibitors: Vec, } impl Default for SessionRuntime { @@ -25,6 +39,10 @@ impl Default for SessionRuntime { state: String::from("online"), active: true, preparing_for_shutdown: false, + idle_hint: false, + locked_hint: false, + session_type: String::from("wayland"), + inhibitors: Vec::new(), } } } diff --git a/local/recipes/system/redbear-sessiond/source/src/session.rs b/local/recipes/system/redbear-sessiond/source/src/session.rs index a56a1e6a..fbbfac01 100644 --- a/local/recipes/system/redbear-sessiond/source/src/session.rs +++ b/local/recipes/system/redbear-sessiond/source/src/session.rs @@ -151,6 +151,54 @@ impl LoginSession { Ok(()) } + fn set_idle_hint(&self, idle: bool) -> fdo::Result<()> { + let runtime = self.runtime()?; + let session_id = runtime.session_id.clone(); + drop(runtime); + + if let Ok(mut guard) = self.runtime.write() { + guard.idle_hint = idle; + } + eprintln!("redbear-sessiond: SetIdleHint({idle}) for session {session_id}"); + Ok(()) + } + + fn set_locked_hint(&self, locked: bool) -> fdo::Result<()> { + let runtime = self.runtime()?; + let session_id = runtime.session_id.clone(); + drop(runtime); + + if let Ok(mut guard) = self.runtime.write() { + guard.locked_hint = locked; + } + eprintln!("redbear-sessiond: SetLockedHint({locked}) for session {session_id}"); + Ok(()) + } + + fn set_type(&self, session_type: &str) -> fdo::Result<()> { + let runtime = self.runtime()?; + let session_id = runtime.session_id.clone(); + drop(runtime); + + if let Ok(mut guard) = self.runtime.write() { + guard.session_type = session_type.to_owned(); + } + eprintln!("redbear-sessiond: SetType({session_type}) for session {session_id}"); + Ok(()) + } + + fn terminate(&self) -> fdo::Result<()> { + let runtime = self.runtime()?; + let session_id = runtime.session_id.clone(); + drop(runtime); + + if let Ok(mut guard) = self.runtime.write() { + guard.state = String::from("closing"); + } + eprintln!("redbear-sessiond: Terminate requested for session {session_id}"); + Ok(()) + } + #[zbus(property(emits_changed_signal = "const"), name = "Active")] fn active(&self) -> bool { self.runtime().map(|runtime| runtime.active).unwrap_or(true) @@ -161,9 +209,11 @@ impl LoginSession { false } - #[zbus(property(emits_changed_signal = "const"), name = "Type")] + #[zbus(property(emits_changed_signal = "false"), name = "Type")] fn kind(&self) -> String { - String::from("wayland") + self.runtime() + .map(|r| r.session_type.clone()) + .unwrap_or_else(|_| String::from("wayland")) } #[zbus(property(emits_changed_signal = "const"), name = "Class")] @@ -191,7 +241,7 @@ impl LoginSession { self.runtime().map(|runtime| runtime.session_id).unwrap_or_else(|_| String::from("c1")) } - #[zbus(property(emits_changed_signal = "const"), name = "State")] + #[zbus(property(emits_changed_signal = "false"), name = "State")] fn state(&self) -> String { self.runtime().map(|runtime| runtime.state).unwrap_or_else(|_| String::from("online")) } @@ -244,14 +294,14 @@ impl LoginSession { String::new() } - #[zbus(property(emits_changed_signal = "const"), name = "IdleHint")] + #[zbus(property(emits_changed_signal = "false"), name = "IdleHint")] fn idle_hint(&self) -> bool { - false + self.runtime().map(|r| r.idle_hint).unwrap_or(false) } - #[zbus(property(emits_changed_signal = "const"), name = "LockedHint")] + #[zbus(property(emits_changed_signal = "false"), name = "LockedHint")] fn locked_hint(&self) -> bool { - false + self.runtime().map(|r| r.locked_hint).unwrap_or(false) } #[zbus(signal, name = "PauseDevice")] @@ -269,4 +319,108 @@ impl LoginSession { minor: u32, fd: Fd<'_>, ) -> zbus::Result<()>; + + #[zbus(signal, name = "Lock")] + async fn lock(signal_emitter: &SignalEmitter<'_>) -> zbus::Result<()>; + + #[zbus(signal, name = "Unlock")] + async fn unlock(signal_emitter: &SignalEmitter<'_>) -> zbus::Result<()>; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::device_map::DeviceMap; + use crate::runtime_state::shared_runtime; + + fn test_session() -> LoginSession { + LoginSession::new( + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")).unwrap(), + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")).unwrap(), + DeviceMap::new(), + shared_runtime(), + ) + } + + #[test] + fn set_idle_hint_updates_runtime() { + let runtime = shared_runtime(); + let session = LoginSession::new( + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")).unwrap(), + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")).unwrap(), + DeviceMap::new(), + runtime.clone(), + ); + + assert!(!session.idle_hint()); + session.set_idle_hint(true).unwrap(); + assert!(session.idle_hint()); + + let guard = runtime.read().expect("lock"); + assert!(guard.idle_hint); + } + + #[test] + fn set_locked_hint_updates_runtime() { + let session = test_session(); + assert!(!session.locked_hint()); + session.set_locked_hint(true).unwrap(); + assert!(session.locked_hint()); + } + + #[test] + fn set_type_updates_runtime() { + let runtime = shared_runtime(); + let session = LoginSession::new( + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")).unwrap(), + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")).unwrap(), + DeviceMap::new(), + runtime.clone(), + ); + + assert_eq!(session.kind(), "wayland"); + session.set_type("x11").unwrap(); + assert_eq!(session.kind(), "x11"); + + let guard = runtime.read().expect("lock"); + assert_eq!(guard.session_type, "x11"); + } + + #[test] + fn terminate_sets_state_to_closing() { + let runtime = shared_runtime(); + let session = LoginSession::new( + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/seat/seat0")).unwrap(), + OwnedObjectPath::try_from(String::from("/org/freedesktop/login1/user/current")).unwrap(), + DeviceMap::new(), + runtime.clone(), + ); + + assert_eq!(session.state(), "online"); + session.terminate().unwrap(); + assert_eq!(session.state(), "closing"); + } + + #[test] + fn take_control_then_take_device_rejects_duplicate() { + let session = test_session(); + session.take_control(false).unwrap(); + session.taken_devices().unwrap().insert((226, 0)); + let err = session.take_device(226, 0).unwrap_err(); + match err { + fdo::Error::Failed(msg) => assert!(msg.contains("already taken")), + other => panic!("expected Failed error, got {other:?}"), + } + } + + #[test] + fn release_device_rejects_unknown() { + let session = test_session(); + session.take_control(false).unwrap(); + let err = session.release_device(226, 99).unwrap_err(); + match err { + fdo::Error::Failed(msg) => assert!(msg.contains("was not taken")), + other => panic!("expected Failed error, got {other:?}"), + } + } } diff --git a/local/recipes/system/redbear-statusnotifierwatcher/recipe.toml b/local/recipes/system/redbear-statusnotifierwatcher/recipe.toml new file mode 100644 index 00000000..f56972d3 --- /dev/null +++ b/local/recipes/system/redbear-statusnotifierwatcher/recipe.toml @@ -0,0 +1,2 @@ +[build] +template = "cargo" diff --git a/local/recipes/system/redbear-statusnotifierwatcher/source/Cargo.toml b/local/recipes/system/redbear-statusnotifierwatcher/source/Cargo.toml new file mode 100644 index 00000000..ebfe1620 --- /dev/null +++ b/local/recipes/system/redbear-statusnotifierwatcher/source/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "redbear-statusnotifierwatcher" +version = "0.1.0" +edition = "2024" + +[[bin]] +name = "redbear-statusnotifierwatcher" +path = "src/main.rs" + +[dependencies] +zbus = { version = "5", default-features = false, features = ["tokio"] } +tokio = { version = "1", features = ["full"] } diff --git a/local/recipes/system/redbear-statusnotifierwatcher/source/src/main.rs b/local/recipes/system/redbear-statusnotifierwatcher/source/src/main.rs new file mode 100644 index 00000000..02de25e6 --- /dev/null +++ b/local/recipes/system/redbear-statusnotifierwatcher/source/src/main.rs @@ -0,0 +1,168 @@ +use std::collections::HashSet; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use zbus::{ + fdo::{self, ObjectManager}, + interface, + object_server::SignalEmitter, + connection::Builder as ConnectionBuilder, + zvariant::ObjectPath, +}; + +const BUS_NAME: &str = "org.freedesktop.StatusNotifierWatcher"; +const OBJECT_PATH: &str = "/StatusNotifierWatcher"; + +/// org.freedesktop.StatusNotifierWatcher D-Bus interface +/// Tracks registered system tray items and hosts for KDE Plasma. +struct StatusNotifierWatcher { + items: Arc>>, + hosts: Arc>>, +} + +#[interface(name = "org.freedesktop.StatusNotifierWatcher")] +impl StatusNotifierWatcher { + // --- Methods --- + + /// Register a status notifier item. + /// The item parameter is either a full object path (e.g., "/org/example/Item") + /// sent by the item itself, or a bus name (e.g., ":1.42" or "org.example.App") + /// sent via the KDE protocol extension. + async fn register_status_notifier_item( + &self, + #[zbus(signal_emitter)] signal_emitter: SignalEmitter<'_>, + item: &str, + ) -> fdo::Result<()> { + let is_new = { + let mut items = self.items.lock().map_err(|e| { + fdo::Error::Failed(format!("items lock poisoned: {e}")) + })?; + items.insert(item.to_owned()) + }; + if is_new { + eprintln!("statusnotifierwatcher: item registered: {item}"); + let _ = Self::status_notifier_item_registered(&signal_emitter, item).await; + } + Ok(()) + } + + /// Register a status notifier host (typically the system tray panel). + async fn register_status_notifier_host( + &self, + #[zbus(signal_emitter)] signal_emitter: SignalEmitter<'_>, + host: &str, + ) -> fdo::Result<()> { + let is_new = { + let mut hosts = self.hosts.lock().map_err(|e| { + fdo::Error::Failed(format!("hosts lock poisoned: {e}")) + })?; + hosts.insert(host.to_owned()) + }; + if is_new { + eprintln!("statusnotifierwatcher: host registered: {host}"); + } + Ok(()) + } + + // --- Properties --- + + /// List of registered status notifier item bus names / paths. + #[zbus(property)] + fn registered_status_notifier_items(&self) -> fdo::Result> { + let items = self.items.lock().map_err(|e| { + fdo::Error::Failed(format!("items lock poisoned: {e}")) + })?; + Ok(items.iter().cloned().collect()) + } + + /// Whether at least one status notifier host is registered. + #[zbus(property)] + fn is_status_notifier_host_registered(&self) -> fdo::Result { + let hosts = self.hosts.lock().map_err(|e| { + fdo::Error::Failed(format!("hosts lock poisoned: {e}")) + })?; + Ok(!hosts.is_empty()) + } + + /// Protocol version (always 0 per spec). + #[zbus(property)] + fn protocol_version(&self) -> i32 { + 0 + } + + // --- Signals --- + + /// Emitted when a new status notifier item is registered. + #[zbus(signal, name = "StatusNotifierItemRegistered")] + async fn status_notifier_item_registered( + signal_emitter: &SignalEmitter<'_>, + service: &str, + ) -> zbus::Result<()>; + + /// Emitted when a status notifier item is unregistered. + #[zbus(signal, name = "StatusNotifierItemUnregistered")] + async fn status_notifier_item_unregistered( + signal_emitter: &SignalEmitter<'_>, + service: &str, + ) -> zbus::Result<()>; +} + +async fn wait_for_session_bus() { + for _ in 0..30 { + if std::env::var("DBUS_SESSION_BUS_ADDRESS").is_ok() { + return; + } + tokio::time::sleep(Duration::from_millis(200)).await; + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + wait_for_session_bus().await; + + let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false); + + // Signal handler task for clean shutdown + let signal_tx = shutdown_tx.clone(); + tokio::spawn(async move { + #[cfg(unix)] + { + use tokio::signal::unix::{SignalKind, signal}; + if let Ok(mut sigterm) = signal(SignalKind::terminate()) { + tokio::select! { + _ = sigterm.recv() => {}, + _ = tokio::signal::ctrl_c() => {}, + } + } else { + let _ = tokio::signal::ctrl_c().await; + } + } + #[cfg(not(unix))] + { + let _ = tokio::signal::ctrl_c().await; + } + let _ = signal_tx.send(true); + }); + // Keep original sender alive so receiver doesn't see all-senders-dropped + let _shutdown_guard = shutdown_tx; + + let watcher = StatusNotifierWatcher { + items: Arc::new(Mutex::new(HashSet::new())), + hosts: Arc::new(Mutex::new(HashSet::new())), + }; + + let path: ObjectPath<'_> = OBJECT_PATH.try_into()?; + let connection = ConnectionBuilder::session()? + .name(BUS_NAME)? + .serve_at(path, watcher)? + .build() + .await?; + + eprintln!("statusnotifierwatcher: {BUS_NAME} registered on session bus"); + + // Wait for shutdown signal + let _ = shutdown_rx.changed().await; + eprintln!("statusnotifierwatcher: shutdown signal received, exiting cleanly"); + drop(connection); + Ok(()) +} diff --git a/local/recipes/system/redbear-udisks/source/src/main.rs b/local/recipes/system/redbear-udisks/source/src/main.rs index d70a5c1a..58ee8133 100644 --- a/local/recipes/system/redbear-udisks/source/src/main.rs +++ b/local/recipes/system/redbear-udisks/source/src/main.rs @@ -4,10 +4,8 @@ mod inventory; use std::{ env, error::Error, - path::Path, process, sync::Arc, - thread, time::Duration, }; @@ -74,34 +72,34 @@ fn system_connection_builder() -> Result, Box Result<(), Box> { - use tokio::signal::unix::{SignalKind, signal}; - - let mut terminate = signal(SignalKind::terminate())?; - - tokio::select! { - _ = terminate.recv() => Ok(()), - _ = tokio::signal::ctrl_c() => Ok(()), - } -} - -#[cfg(target_os = "redox")] -async fn wait_for_shutdown() -> Result<(), Box> { - std::future::pending::<()>().await; - #[allow(unreachable_code)] - Ok(()) -} - -#[cfg(all(not(unix), not(target_os = "redox")))] -async fn wait_for_shutdown() -> Result<(), Box> { - tokio::signal::ctrl_c().await?; - Ok(()) +fn spawn_signal_handler(shutdown_tx: tokio::sync::watch::Sender) { + tokio::spawn(async move { + #[cfg(unix)] + { + use tokio::signal::unix::{SignalKind, signal}; + if let Ok(mut sigterm) = signal(SignalKind::terminate()) { + tokio::select! { + _ = sigterm.recv() => {}, + _ = tokio::signal::ctrl_c() => {}, + } + } else { + let _ = tokio::signal::ctrl_c().await; + } + } + #[cfg(not(unix))] + { + let _ = tokio::signal::ctrl_c().await; + } + let _ = shutdown_tx.send(true); + }); } async fn run_daemon() -> Result<(), Box> { wait_for_dbus_socket().await; + let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false); + spawn_signal_handler(shutdown_tx); + let mut last_err = None; for attempt in 1..=5 { let _root_path = parse_object_path(ROOT_PATH)?; @@ -128,13 +126,16 @@ async fn run_daemon() -> Result<(), Box> { inventory.drives().len(), inventory.blocks().len(), ); - wait_for_shutdown().await?; + let _ = shutdown_rx.changed().await; + eprintln!("redbear-udisks: shutdown signal received, exiting cleanly"); drop(connection); return Ok(()); } Err(err) => { if attempt < 5 { - eprintln!("redbear-udisks: attempt {attempt}/5 failed ({err}), retrying in 2s..."); + eprintln!( + "redbear-udisks: attempt {attempt}/5 failed ({err}), retrying in 2s..." + ); tokio::time::sleep(Duration::from_secs(2)).await; } last_err = Some(err.into()); diff --git a/local/recipes/system/redbear-upower/source/src/main.rs b/local/recipes/system/redbear-upower/source/src/main.rs index d7de2217..00d38c66 100644 --- a/local/recipes/system/redbear-upower/source/src/main.rs +++ b/local/recipes/system/redbear-upower/source/src/main.rs @@ -29,6 +29,8 @@ const DEVICE_STATE_DISCHARGING: u32 = 2; const DEVICE_STATE_EMPTY: u32 = 3; const DEVICE_STATE_FULLY_CHARGED: u32 = 4; +const POLL_INTERVAL_SECS: u64 = 30; + #[derive(Debug, Clone)] struct PowerRuntime { root: PathBuf, @@ -59,20 +61,20 @@ enum DeviceDescriptor { Battery(String), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] struct AdapterState { native_path: String, online: bool, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] struct BatteryState { native_path: String, state_bits: u64, percentage: Option, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] struct PowerSnapshot { adapters: Vec, batteries: Vec, @@ -328,31 +330,6 @@ impl PowerSnapshot { } } -#[cfg(all(unix, not(target_os = "redox")))] -async fn wait_for_shutdown() -> Result<(), Box> { - use tokio::signal::unix::{SignalKind, signal}; - - let mut terminate = signal(SignalKind::terminate())?; - - tokio::select! { - _ = terminate.recv() => Ok(()), - _ = tokio::signal::ctrl_c() => Ok(()), - } -} - -#[cfg(target_os = "redox")] -async fn wait_for_shutdown() -> Result<(), Box> { - std::future::pending::<()>().await; - #[allow(unreachable_code)] - Ok(()) -} - -#[cfg(all(not(unix), not(target_os = "redox")))] -async fn wait_for_shutdown() -> Result<(), Box> { - tokio::signal::ctrl_c().await?; - Ok(()) -} - #[interface(name = "org.freedesktop.UPower")] impl UPowerDaemon { fn enumerate_devices(&self) -> Vec { @@ -368,7 +345,7 @@ impl UPowerDaemon { String::from("0.1.0") } - #[zbus(property(emits_changed_signal = "const"), name = "OnBattery")] + #[zbus(property(emits_changed_signal = "false"), name = "OnBattery")] fn on_battery(&self) -> bool { self.runtime.snapshot().on_battery() } @@ -476,6 +453,28 @@ impl PowerDevice { } } +fn spawn_signal_handler(shutdown_tx: tokio::sync::watch::Sender) { + tokio::spawn(async move { + #[cfg(unix)] + { + use tokio::signal::unix::{SignalKind, signal}; + if let Ok(mut sigterm) = signal(SignalKind::terminate()) { + tokio::select! { + _ = sigterm.recv() => {}, + _ = tokio::signal::ctrl_c() => {}, + } + } else { + let _ = tokio::signal::ctrl_c().await; + } + } + #[cfg(not(unix))] + { + let _ = tokio::signal::ctrl_c().await; + } + let _ = shutdown_tx.send(true); + }); +} + async fn run_daemon() -> Result<(), Box> { wait_for_dbus_socket().await; let runtime = PowerRuntime::discover()?; @@ -486,6 +485,9 @@ async fn run_daemon() -> Result<(), Box> { } let _display_device_path = parse_object_path(DISPLAY_DEVICE_PATH)?; + let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false); + spawn_signal_handler(shutdown_tx); + let mut last_err = None; for attempt in 1..=5 { let mut builder = system_connection_builder()? @@ -527,13 +529,44 @@ async fn run_daemon() -> Result<(), Box> { match builder.build().await { Ok(connection) => { eprintln!("redbear-upower: registered {BUS_NAME} on the system bus"); - wait_for_shutdown().await?; + + let upower_path = parse_object_path(UPOWER_PATH)?; + let signal_emitter = SignalEmitter::new(&connection, upower_path)?; + + let mut last_snapshot = runtime.snapshot(); + let mut poll_interval = + tokio::time::interval(Duration::from_secs(POLL_INTERVAL_SECS)); + + loop { + tokio::select! { + result = shutdown_rx.changed() => { + if result.is_err() { + eprintln!("redbear-upower: signal handler exited unexpectedly"); + } + eprintln!("redbear-upower: shutdown signal received, exiting cleanly"); + break; + } + _ = poll_interval.tick() => { + let current_snapshot = runtime.snapshot(); + if current_snapshot != last_snapshot { + eprintln!( + "redbear-upower: power state changed, emitting Changed signal" + ); + let _ = UPowerDaemon::changed(&signal_emitter).await; + last_snapshot = current_snapshot; + } + } + } + } + drop(connection); return Ok(()); } Err(err) => { if attempt < 5 { - eprintln!("redbear-upower: attempt {attempt}/5 failed ({err}), retrying in 2s..."); + eprintln!( + "redbear-upower: attempt {attempt}/5 failed ({err}), retrying in 2s..." + ); tokio::time::sleep(Duration::from_secs(2)).await; } last_err = Some(err.into()); diff --git a/local/scripts/build-redbear.sh b/local/scripts/build-redbear.sh index 5ae1e497..2e416425 100755 --- a/local/scripts/build-redbear.sh +++ b/local/scripts/build-redbear.sh @@ -1,20 +1,4 @@ #!/usr/bin/env bash -# build-redbear.sh — Build Red Bear OS from upstream base + Red Bear overlay -# -# Usage: -# ./local/scripts/build-redbear.sh # Default: redbear-full -# ./local/scripts/build-redbear.sh redbear-mini # Minimal validation baseline -# ./local/scripts/build-redbear.sh redbear-full # Full Red Bear desktop/session target -# ./local/scripts/build-redbear.sh redbear-live # Canonical full live profile config -# ./local/scripts/build-redbear.sh redbear-live-mini # Text-only mini live profile config -# ./local/scripts/build-redbear.sh redbear-grub-live-mini # Text-only GRUB mini live profile config -# ./local/scripts/build-redbear.sh --upstream redbear-full # Allow Redox/upstream recipe refresh -# APPLY_PATCHES=0 ./local/scripts/build-redbear.sh # Skip patch application -# -# This script assumes the Red Bear overlay model: -# - upstream-owned sources are refreshable working trees -# - Red Bear-owned shipping deltas live in local/patches/ and local/recipes/ -# - upstream WIP recipes are not trusted as stable shipping inputs until upstream promotes them set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -25,23 +9,6 @@ JOBS="${JOBS:-$(nproc)}" APPLY_PATCHES="${APPLY_PATCHES:-1}" ALLOW_UPSTREAM=0 -canonicalize_config() { - case "$1" in - redbear-mini) - printf '%s\n' "redbear-minimal" - ;; - redbear-live-full) - printf '%s\n' "redbear-live" - ;; - redbear-live-mini-grub) - printf '%s\n' "redbear-grub-live-mini" - ;; - *) - printf '%s\n' "$1" - ;; - esac -} - usage() { cat <&2 + echo "Supported: redbear-full, redbear-mini, redbear-grub" >&2 exit 1 ;; esac @@ -216,7 +175,6 @@ if [ "$APPLY_PATCHES" = "1" ]; then apply_patch_dir "$PROJECT_ROOT/local/patches/bootloader" "$PROJECT_ROOT/recipes/core/bootloader/source" "bootloader" apply_patch_dir "$PROJECT_ROOT/local/patches/installer" "$PROJECT_ROOT/recipes/core/installer/source" "installer" - # repo cook can refetch nested sources when --upstream is enabled; keep relibc clean after patch application stash_nested_repo_if_dirty "$PROJECT_ROOT/recipes/core/relibc/source" "relibc" echo "" fi @@ -232,12 +190,12 @@ if [ ! -f "target/release/repo" ]; then cargo build --release fi -if [ "$CONFIG" = "redbear-full" ] || [ "$CONFIG" = "redbear-live" ]; then +if [ "$CONFIG" = "redbear-full" ]; then ensure_relibc_desktop_surface fi FW_AMD_DIR="$PROJECT_ROOT/local/firmware/amdgpu" -if [ "$CONFIG" != "redbear-minimal" ] && [ "$CONFIG" != "redbear-live-mini" ] && [ "$CONFIG" != "redbear-grub-live-mini" ]; then +if [ "$CONFIG" = "redbear-full" ]; then if [ -d "$FW_AMD_DIR" ] && [ -n "$(ls -A "$FW_AMD_DIR" 2>/dev/null)" ]; then FW_COUNT=$(ls "$FW_AMD_DIR"/*.bin 2>/dev/null | wc -l) echo ">>> Found $FW_COUNT AMD firmware blobs" @@ -268,38 +226,9 @@ echo "Image: build/$ARCH/$CONFIG/harddrive.img" echo "" echo "To run in QEMU:" echo " make qemu QEMUFLAGS=\"-m 4G\"" -if [ "$CONFIG" = "redbear-minimal" ] || [ "$CONFIG" = "redbear-desktop" ]; then - echo "" - echo "To validate the Phase 2 VM network baseline:" - echo " ./local/scripts/validate-vm-network-baseline.sh" - echo " ./local/scripts/test-vm-network-qemu.sh $CONFIG" -fi -if [ "$CONFIG" = "redbear-desktop" ] || [ "$CONFIG" = "redbear-full" ] || [ "$CONFIG" = "redbear-wayland" ] || [ "$CONFIG" = "redbear-kde" ]; then - echo "" - echo "To validate bounded low-level controller proofs:" - echo " ./local/scripts/test-lowlevel-controllers-qemu.sh $CONFIG" - echo " # or run individual checks: test-xhci-irq-qemu.sh, test-iommu-qemu.sh, test-ps2-qemu.sh, test-timer-qemu.sh" - echo "" - echo "To validate bounded USB maturity proofs:" - echo " ./local/scripts/test-usb-maturity-qemu.sh $CONFIG" - echo " # or run individual checks: test-usb-qemu.sh --check, test-usb-storage-qemu.sh" -fi -if [ "$CONFIG" = "redbear-wayland" ]; then - echo "" - echo "To validate the bounded Phase 4 Wayland runtime harness:" - echo " ./local/scripts/test-phase4-wayland-qemu.sh" - echo " # in guest: redbear-drm-display-check --vendor amd|intel" -fi -if [ "$CONFIG" = "redbear-kde" ]; then - echo "" - echo "To validate the primary KWin Wayland desktop path:" - echo " ./local/scripts/test-phase6-kde-qemu.sh --check" - echo " # in guest: redbear-drm-display-check --vendor amd|intel" -fi echo "" echo "To build live ISO:" -echo " make live CONFIG_NAME=$CONFIG" -echo " # live .iso outputs are for real bare metal, not VM/QEMU use" +echo " scripts/build-iso.sh $CONFIG" echo "" echo "To write a real bare-metal image to USB (verify device first!):" echo " dd if=build/$ARCH/$CONFIG/harddrive.img of=/dev/sdX bs=4M status=progress" diff --git a/local/scripts/integrate-redbear.sh b/local/scripts/integrate-redbear.sh index 95cec8d8..ace81b79 100755 --- a/local/scripts/integrate-redbear.sh +++ b/local/scripts/integrate-redbear.sh @@ -226,16 +226,8 @@ echo "" section "Validating Red Bear configs..." declare -a redbear_configs=( - "config/redbear-desktop.toml" - "config/redbear-minimal.toml" - "config/redbear-bluetooth-experimental.toml" + "config/redbear-mini.toml" "config/redbear-full.toml" - "config/redbear-wayland.toml" - "config/redbear-live.toml" - "config/redbear-live-mini.toml" - "config/redbear-live-full.toml" - "config/redbear-grub-live-mini.toml" - "config/redbear-grub-live-full.toml" "config/redbear-grub.toml" ) declare -a found_configs=() @@ -247,9 +239,9 @@ for config_path in "${redbear_configs[@]}"; do done if [ "${#found_configs[@]}" -gt 0 ]; then - status "Found Red Bear config(s): ${found_configs[*]}" + status "Found Red Bear target config(s): ${found_configs[*]}" else - warn "No redbear config found in config/. Build may rely on a different config intentionally" + warn "No redbear target config found in config/" fi echo "" diff --git a/recipes/wip/AGENTS.md b/recipes/wip/AGENTS.md index 766ac924..28870020 100644 --- a/recipes/wip/AGENTS.md +++ b/recipes/wip/AGENTS.md @@ -86,7 +86,7 @@ recipes/wip/ ## KDE STATUS -- Older WIP KDE app notes here are stale relative to `local/recipes/kde/` and `config/redbear-kde.toml` +- Older WIP KDE app notes here are stale relative to `local/recipes/kde/` and `config/redbear-full.toml` - See `docs/05-KDE-PLASMA-ON-REDOX.md` top-level status note plus `local/docs/QT6-PORT-STATUS.md` for current state ## CONVENTIONS diff --git a/scripts/build-iso.sh b/scripts/build-iso.sh index e4ad740e..0abb643a 100755 --- a/scripts/build-iso.sh +++ b/scripts/build-iso.sh @@ -2,57 +2,27 @@ set -euo pipefail -CONFIG_NAME="redbear-live" +CONFIG_NAME="redbear-mini" ARCH="x86_64" ALLOW_UPSTREAM=0 -canonicalize_live_config() { - case "$1" in - redbear-live-full) - printf '%s\n' "redbear-live" - ;; - redbear-live-mini-grub) - printf '%s\n' "redbear-grub-live-mini" - ;; - redbear-live-full-grub) - printf '%s\n' "redbear-grub-live-full" - ;; - redbear-grub-live-full) - printf '%s\n' "redbear-grub-live-full" - ;; - *) - printf '%s\n' "$1" - ;; - esac -} - usage() { cat <&2 - usage >&2 + echo "ERROR: Unsupported target '$CONFIG_NAME'" >&2 + echo "Supported: redbear-full, redbear-mini, redbear-grub" >&2 exit 1 ;; esac @@ -122,4 +90,3 @@ fi echo "" echo "Done: build/${ARCH}/${CONFIG_NAME}.iso" -echo "Note: live .iso outputs are for real bare metal, not VM/QEMU use."