From 2965afacae142d1b734bec21c153d4c90d60ddc5 Mon Sep 17 00:00:00 2001 From: Albert Armea Date: Sat, 27 Dec 2025 10:50:54 -0500 Subject: [PATCH] Make launcher runnable --- .gitignore | 1 + Cargo.lock | 12 +++--- Cargo.toml | 2 +- README.md | 3 +- crates/shepherd-hud/src/app.rs | 2 +- crates/shepherd-hud/src/main.rs | 4 +- crates/shepherd-hud/src/state.rs | 3 +- crates/shepherd-launcher-ui/src/app.rs | 2 +- crates/shepherd-launcher-ui/src/client.rs | 16 +++++++- crates/shepherd-launcher-ui/src/grid.rs | 7 ++-- crates/shepherd-launcher-ui/src/main.rs | 4 +- crates/shepherdd/Cargo.toml | 2 +- crates/shepherdd/src/main.rs | 8 ++-- run-dev | 46 ++++++++++++++++++++++- sway.conf | 16 ++++++-- 15 files changed, 98 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..c00ce12 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/dev-runtime diff --git a/Cargo.lock b/Cargo.lock index f4a4597..276e3dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,7 +68,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -79,7 +79,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -257,7 +257,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -856,7 +856,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1037,7 +1037,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1390,7 +1390,7 @@ dependencies = [ "getrandom", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0980fc2..acea57a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ bitflags = "2.4" nix = { version = "0.29", features = ["signal", "process", "user", "socket"] } # CLI -clap = { version = "4.5", features = ["derive"] } +clap = { version = "4.5", features = ["derive", "env"] } # GTK4 UI gtk4 = "0.9" diff --git a/README.md b/README.md index d162e45..b9e437e 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,8 @@ TODO: tl;dr: 1. any Linux with Wayland (optional: TPM-based FDE plus BIOS password to prevent tampering) -2. System dependencies (Ubuntu: `apt install curl sway swayidle pkg-config libcairo2-dev libxkbcommon-dev`) +2. System dependencies: + - **Ubuntu/Debian**: `apt install build-essential pkg-config libglib2.0-dev libgtk-4-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf-xlib-2.0-dev libwayland-dev libx11-dev libxkbcommon-dev libgirepository1.0-dev libgtk4-layer-shell-dev librust-gtk4-layer-shell-sys-dev sway swayidle` 3. Rust (`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`) 4. binaries (TODO: deployable package that depends on Sway and installs the config) 5. test session on login diff --git a/crates/shepherd-hud/src/app.rs b/crates/shepherd-hud/src/app.rs index 7d34205..0b93cd3 100644 --- a/crates/shepherd-hud/src/app.rs +++ b/crates/shepherd-hud/src/app.rs @@ -10,7 +10,7 @@ use crate::volume::VolumeStatus; use gtk4::glib; use gtk4::prelude::*; use gtk4_layer_shell::{Edge, Layer, LayerShell}; -use shepherd_api::commands::Command; +use shepherd_api::Command; use shepherd_ipc::IpcClient; use std::cell::RefCell; use std::path::PathBuf; diff --git a/crates/shepherd-hud/src/main.rs b/crates/shepherd-hud/src/main.rs index 4a85fe7..2668807 100644 --- a/crates/shepherd-hud/src/main.rs +++ b/crates/shepherd-hud/src/main.rs @@ -20,8 +20,8 @@ use tracing_subscriber::EnvFilter; #[command(name = "shepherd-hud")] #[command(about = "GTK4 layer-shell HUD for shepherdd", long_about = None)] struct Args { - /// Socket path for shepherdd connection - #[arg(short, long, default_value = "/run/shepherdd/shepherdd.sock")] + /// Socket path for shepherdd connection (or set SHEPHERD_SOCKET env var) + #[arg(short, long, env = "SHEPHERD_SOCKET", default_value = "/run/shepherdd/shepherdd.sock")] socket: PathBuf, /// Log level diff --git a/crates/shepherd-hud/src/state.rs b/crates/shepherd-hud/src/state.rs index c55dd11..84f2ef9 100644 --- a/crates/shepherd-hud/src/state.rs +++ b/crates/shepherd-hud/src/state.rs @@ -3,8 +3,7 @@ //! The HUD subscribes to events from shepherdd and tracks session state. use chrono::Local; -use shepherd_api::events::{Event, EventPayload}; -use shepherd_api::types::SessionEndReason; +use shepherd_api::{Event, EventPayload, SessionEndReason}; use shepherd_util::{EntryId, SessionId}; use std::sync::Arc; use tokio::sync::watch; diff --git a/crates/shepherd-launcher-ui/src/app.rs b/crates/shepherd-launcher-ui/src/app.rs index ee99ee3..aae4839 100644 --- a/crates/shepherd-launcher-ui/src/app.rs +++ b/crates/shepherd-launcher-ui/src/app.rs @@ -105,7 +105,7 @@ impl LauncherApp { fn build_ui(app: >k4::Application, socket_path: PathBuf) { // Load CSS let provider = gtk4::CssProvider::new(); - provider.load_from_string(LAUNCHER_CSS); + provider.load_from_data(LAUNCHER_CSS); gtk4::style_context_add_provider_for_display( >k4::gdk::Display::default().expect("Could not get default display"), &provider, diff --git a/crates/shepherd-launcher-ui/src/client.rs b/crates/shepherd-launcher-ui/src/client.rs index 22acbd4..edc9fe5 100644 --- a/crates/shepherd-launcher-ui/src/client.rs +++ b/crates/shepherd-launcher-ui/src/client.rs @@ -1,7 +1,7 @@ //! IPC client wrapper for the launcher UI use anyhow::{Context, Result}; -use shepherd_api::{Command, Event, Response, ResponsePayload, ResponseResult}; +use shepherd_api::{Command, Event, ReasonCode, Response, ResponsePayload, ResponseResult}; use shepherd_ipc::IpcClient; use shepherd_util::EntryId; use std::path::Path; @@ -182,7 +182,7 @@ impl DaemonClient { ResponsePayload::LaunchDenied { reasons } => { let message = reasons .iter() - .map(|r| r.message.as_deref().unwrap_or("Denied")) + .map(|r| reason_to_message(r)) .collect::>() .join(", "); self.state.set(LauncherState::Error { message }); @@ -237,3 +237,15 @@ impl CommandClient { client.send(Command::ListEntries { at_time: None }).await.map_err(Into::into) } } + +/// Convert a ReasonCode enum variant to a human-readable message +fn reason_to_message(reason: &ReasonCode) -> &'static str { + match reason { + ReasonCode::OutsideTimeWindow { .. } => "Outside allowed time window", + ReasonCode::QuotaExhausted { .. } => "Daily quota exhausted", + ReasonCode::CooldownActive { .. } => "Cooldown period active", + ReasonCode::SessionActive { .. } => "Another session is active", + ReasonCode::UnsupportedKind { .. } => "Entry type not supported", + ReasonCode::Disabled { .. } => "Entry disabled", + } +} diff --git a/crates/shepherd-launcher-ui/src/grid.rs b/crates/shepherd-launcher-ui/src/grid.rs index 8f9f2be..d3c5161 100644 --- a/crates/shepherd-launcher-ui/src/grid.rs +++ b/crates/shepherd-launcher-ui/src/grid.rs @@ -6,6 +6,7 @@ use gtk4::subclass::prelude::*; use shepherd_api::EntryView; use shepherd_util::EntryId; use std::cell::RefCell; +use std::rc::Rc; use crate::tile::LauncherTile; @@ -15,7 +16,7 @@ mod imp { pub struct LauncherGrid { pub flow_box: gtk4::FlowBox, pub tiles: RefCell>, - pub on_launch: RefCell>>, + pub on_launch: Rc>>>, } impl Default for LauncherGrid { @@ -23,7 +24,7 @@ mod imp { Self { flow_box: gtk4::FlowBox::new(), tiles: RefCell::new(Vec::new()), - on_launch: RefCell::new(None), + on_launch: Rc::new(RefCell::new(None)), } } } @@ -120,7 +121,7 @@ impl LauncherGrid { } }); - imp.flow_box.append(&tile); + imp.flow_box.insert(&tile, -1); imp.tiles.borrow_mut().push(tile); } } diff --git a/crates/shepherd-launcher-ui/src/main.rs b/crates/shepherd-launcher-ui/src/main.rs index 555c6a2..55d4973 100644 --- a/crates/shepherd-launcher-ui/src/main.rs +++ b/crates/shepherd-launcher-ui/src/main.rs @@ -20,8 +20,8 @@ use tracing_subscriber::EnvFilter; #[command(name = "shepherd-launcher")] #[command(about = "GTK4 launcher UI for shepherdd", long_about = None)] struct Args { - /// Socket path for shepherdd connection - #[arg(short, long, default_value = "/run/shepherdd/shepherdd.sock")] + /// Socket path for shepherdd connection (or set SHEPHERD_SOCKET env var) + #[arg(short, long, env = "SHEPHERD_SOCKET", default_value = "/run/shepherdd/shepherdd.sock")] socket: PathBuf, /// Log level diff --git a/crates/shepherdd/Cargo.toml b/crates/shepherdd/Cargo.toml index 91a6537..851e70a 100644 --- a/crates/shepherdd/Cargo.toml +++ b/crates/shepherdd/Cargo.toml @@ -26,7 +26,7 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true } tokio = { workspace = true } anyhow = { workspace = true } -clap = { version = "4.4", features = ["derive"] } +clap = { version = "4.5", features = ["derive", "env"] } [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/shepherdd/src/main.rs b/crates/shepherdd/src/main.rs index 71f0dd4..f016753 100644 --- a/crates/shepherdd/src/main.rs +++ b/crates/shepherdd/src/main.rs @@ -38,12 +38,12 @@ struct Args { #[arg(short, long, default_value = "/etc/shepherdd/config.toml")] config: PathBuf, - /// Socket path override - #[arg(short, long)] + /// Socket path override (or set SHEPHERD_SOCKET env var) + #[arg(short, long, env = "SHEPHERD_SOCKET")] socket: Option, - /// Data directory override - #[arg(short, long)] + /// Data directory override (or set SHEPHERD_DATA_DIR env var) + #[arg(short, long, env = "SHEPHERD_DATA_DIR")] data_dir: Option, /// Log level diff --git a/run-dev b/run-dev index c07dfe5..da39689 100755 --- a/run-dev +++ b/run-dev @@ -1,3 +1,47 @@ #!/usr/bin/env bash -cargo build && WLR_BACKENDS=wayland WLR_LIBINPUT_NO_DEVICES=1 sway -c ./sway.conf +set -e + +# Set up dev runtime directory +DEV_RUNTIME="./dev-runtime" +DATA_DIR="$DEV_RUNTIME/data" +SOCKET_PATH="$DEV_RUNTIME/shepherd.sock" + +mkdir -p "$DATA_DIR" + +# Export environment variables for shepherd binaries +export SHEPHERD_SOCKET="$SOCKET_PATH" +export SHEPHERD_DATA_DIR="$DATA_DIR" + +# Build all binaries +echo "Building shepherd binaries..." +cargo build + +# Function to cleanup background processes on exit +cleanup() { + echo "Cleaning up..." + if [ ! -z "$SHEPHERDD_PID" ]; then + kill $SHEPHERDD_PID 2>/dev/null || true + fi + if [ ! -z "$HUD_PID" ]; then + kill $HUD_PID 2>/dev/null || true + fi +} +trap cleanup EXIT + +# Start the background daemon +echo "Starting shepherdd..." +./target/debug/shepherdd -c ./config.example.toml & +SHEPHERDD_PID=$! + +# Give the daemon a moment to initialize +sleep 1 + +# Start the HUD in the background +echo "Starting shepherd-hud..." +./target/debug/shepherd-hud & +HUD_PID=$! + +# Start sway with the launcher +echo "Starting sway with shepherd-launcher..." +WLR_BACKENDS=wayland WLR_LIBINPUT_NO_DEVICES=1 sway -c ./sway.conf diff --git a/sway.conf b/sway.conf index a343fb5..b91856f 100644 --- a/sway.conf +++ b/sway.conf @@ -3,6 +3,7 @@ ### Variables set $launcher ./target/debug/shepherd-launcher +set $hud ./target/debug/shepherd-hud ### Output configuration # Set up displays (adjust as needed for your hardware) @@ -35,7 +36,13 @@ bindsym Mod4+Shift+Escape exit ### Window rules for kiosk behavior -# Make all windows fullscreen by default +# Shepherd HUD should always be visible on top (likely uses layer-shell protocol) +# Layer shell surfaces are automatically handled by sway and don't need window rules + +# Make launcher windows fullscreen +for_window [app_id="shepherd-launcher"] fullscreen enable + +# Make other windows fullscreen by default for_window [class=".*"] fullscreen enable for_window [app_id=".*"] fullscreen enable @@ -62,9 +69,12 @@ seat * hide_cursor 5000 # Use only one workspace for true kiosk mode workspace 1 output * -### Application launcher +### Application startup -# Start the shepherd-launcher on startup +# Note: shepherdd (daemon) and shepherd-hud are started by run-dev script +# before sway launches, so they're already running at this point + +# Start the shepherd-launcher on startup (the main "home" screen) exec_always $launcher ### Disable workspace switching