Make launcher runnable

This commit is contained in:
Albert Armea 2025-12-27 10:50:54 -05:00
parent e2013eb694
commit 2965afacae
15 changed files with 98 additions and 30 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target
/dev-runtime

12
Cargo.lock generated
View file

@ -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]]

View file

@ -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"

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -105,7 +105,7 @@ impl LauncherApp {
fn build_ui(app: &gtk4::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(
&gtk4::gdk::Display::default().expect("Could not get default display"),
&provider,

View file

@ -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::<Vec<_>>()
.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",
}
}

View file

@ -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<Vec<LauncherTile>>,
pub on_launch: RefCell<Option<Box<dyn Fn(EntryId) + 'static>>>,
pub on_launch: Rc<RefCell<Option<Box<dyn Fn(EntryId) + 'static>>>>,
}
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);
}
}

View file

@ -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

View file

@ -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 }

View file

@ -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<PathBuf>,
/// 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<PathBuf>,
/// Log level

46
run-dev
View file

@ -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

View file

@ -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