Make launcher runnable
This commit is contained in:
parent
e2013eb694
commit
2965afacae
15 changed files with 98 additions and 30 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
/dev-runtime
|
||||||
|
|
|
||||||
12
Cargo.lock
generated
12
Cargo.lock
generated
|
|
@ -68,7 +68,7 @@ version = "1.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -79,7 +79,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"once_cell_polyfill",
|
"once_cell_polyfill",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -257,7 +257,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -856,7 +856,7 @@ version = "0.50.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1037,7 +1037,7 @@ dependencies = [
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1390,7 +1390,7 @@ dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ bitflags = "2.4"
|
||||||
nix = { version = "0.29", features = ["signal", "process", "user", "socket"] }
|
nix = { version = "0.29", features = ["signal", "process", "user", "socket"] }
|
||||||
|
|
||||||
# CLI
|
# CLI
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive", "env"] }
|
||||||
|
|
||||||
# GTK4 UI
|
# GTK4 UI
|
||||||
gtk4 = "0.9"
|
gtk4 = "0.9"
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ TODO:
|
||||||
tl;dr:
|
tl;dr:
|
||||||
|
|
||||||
1. any Linux with Wayland (optional: TPM-based FDE plus BIOS password to prevent tampering)
|
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`)
|
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)
|
4. binaries (TODO: deployable package that depends on Sway and installs the config)
|
||||||
5. test session on login
|
5. test session on login
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use crate::volume::VolumeStatus;
|
||||||
use gtk4::glib;
|
use gtk4::glib;
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4_layer_shell::{Edge, Layer, LayerShell};
|
use gtk4_layer_shell::{Edge, Layer, LayerShell};
|
||||||
use shepherd_api::commands::Command;
|
use shepherd_api::Command;
|
||||||
use shepherd_ipc::IpcClient;
|
use shepherd_ipc::IpcClient;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ use tracing_subscriber::EnvFilter;
|
||||||
#[command(name = "shepherd-hud")]
|
#[command(name = "shepherd-hud")]
|
||||||
#[command(about = "GTK4 layer-shell HUD for shepherdd", long_about = None)]
|
#[command(about = "GTK4 layer-shell HUD for shepherdd", long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Socket path for shepherdd connection
|
/// Socket path for shepherdd connection (or set SHEPHERD_SOCKET env var)
|
||||||
#[arg(short, long, default_value = "/run/shepherdd/shepherdd.sock")]
|
#[arg(short, long, env = "SHEPHERD_SOCKET", default_value = "/run/shepherdd/shepherdd.sock")]
|
||||||
socket: PathBuf,
|
socket: PathBuf,
|
||||||
|
|
||||||
/// Log level
|
/// Log level
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@
|
||||||
//! The HUD subscribes to events from shepherdd and tracks session state.
|
//! The HUD subscribes to events from shepherdd and tracks session state.
|
||||||
|
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use shepherd_api::events::{Event, EventPayload};
|
use shepherd_api::{Event, EventPayload, SessionEndReason};
|
||||||
use shepherd_api::types::SessionEndReason;
|
|
||||||
use shepherd_util::{EntryId, SessionId};
|
use shepherd_util::{EntryId, SessionId};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ impl LauncherApp {
|
||||||
fn build_ui(app: >k4::Application, socket_path: PathBuf) {
|
fn build_ui(app: >k4::Application, socket_path: PathBuf) {
|
||||||
// Load CSS
|
// Load CSS
|
||||||
let provider = gtk4::CssProvider::new();
|
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::style_context_add_provider_for_display(
|
||||||
>k4::gdk::Display::default().expect("Could not get default display"),
|
>k4::gdk::Display::default().expect("Could not get default display"),
|
||||||
&provider,
|
&provider,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! IPC client wrapper for the launcher UI
|
//! IPC client wrapper for the launcher UI
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
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_ipc::IpcClient;
|
||||||
use shepherd_util::EntryId;
|
use shepherd_util::EntryId;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
@ -182,7 +182,7 @@ impl DaemonClient {
|
||||||
ResponsePayload::LaunchDenied { reasons } => {
|
ResponsePayload::LaunchDenied { reasons } => {
|
||||||
let message = reasons
|
let message = reasons
|
||||||
.iter()
|
.iter()
|
||||||
.map(|r| r.message.as_deref().unwrap_or("Denied"))
|
.map(|r| reason_to_message(r))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ");
|
.join(", ");
|
||||||
self.state.set(LauncherState::Error { message });
|
self.state.set(LauncherState::Error { message });
|
||||||
|
|
@ -237,3 +237,15 @@ impl CommandClient {
|
||||||
client.send(Command::ListEntries { at_time: None }).await.map_err(Into::into)
|
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",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use gtk4::subclass::prelude::*;
|
||||||
use shepherd_api::EntryView;
|
use shepherd_api::EntryView;
|
||||||
use shepherd_util::EntryId;
|
use shepherd_util::EntryId;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::tile::LauncherTile;
|
use crate::tile::LauncherTile;
|
||||||
|
|
||||||
|
|
@ -15,7 +16,7 @@ mod imp {
|
||||||
pub struct LauncherGrid {
|
pub struct LauncherGrid {
|
||||||
pub flow_box: gtk4::FlowBox,
|
pub flow_box: gtk4::FlowBox,
|
||||||
pub tiles: RefCell<Vec<LauncherTile>>,
|
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 {
|
impl Default for LauncherGrid {
|
||||||
|
|
@ -23,7 +24,7 @@ mod imp {
|
||||||
Self {
|
Self {
|
||||||
flow_box: gtk4::FlowBox::new(),
|
flow_box: gtk4::FlowBox::new(),
|
||||||
tiles: RefCell::new(Vec::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);
|
imp.tiles.borrow_mut().push(tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ use tracing_subscriber::EnvFilter;
|
||||||
#[command(name = "shepherd-launcher")]
|
#[command(name = "shepherd-launcher")]
|
||||||
#[command(about = "GTK4 launcher UI for shepherdd", long_about = None)]
|
#[command(about = "GTK4 launcher UI for shepherdd", long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Socket path for shepherdd connection
|
/// Socket path for shepherdd connection (or set SHEPHERD_SOCKET env var)
|
||||||
#[arg(short, long, default_value = "/run/shepherdd/shepherdd.sock")]
|
#[arg(short, long, env = "SHEPHERD_SOCKET", default_value = "/run/shepherdd/shepherdd.sock")]
|
||||||
socket: PathBuf,
|
socket: PathBuf,
|
||||||
|
|
||||||
/// Log level
|
/// Log level
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
clap = { version = "4.4", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive", "env"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,12 @@ struct Args {
|
||||||
#[arg(short, long, default_value = "/etc/shepherdd/config.toml")]
|
#[arg(short, long, default_value = "/etc/shepherdd/config.toml")]
|
||||||
config: PathBuf,
|
config: PathBuf,
|
||||||
|
|
||||||
/// Socket path override
|
/// Socket path override (or set SHEPHERD_SOCKET env var)
|
||||||
#[arg(short, long)]
|
#[arg(short, long, env = "SHEPHERD_SOCKET")]
|
||||||
socket: Option<PathBuf>,
|
socket: Option<PathBuf>,
|
||||||
|
|
||||||
/// Data directory override
|
/// Data directory override (or set SHEPHERD_DATA_DIR env var)
|
||||||
#[arg(short, long)]
|
#[arg(short, long, env = "SHEPHERD_DATA_DIR")]
|
||||||
data_dir: Option<PathBuf>,
|
data_dir: Option<PathBuf>,
|
||||||
|
|
||||||
/// Log level
|
/// Log level
|
||||||
|
|
|
||||||
46
run-dev
46
run-dev
|
|
@ -1,3 +1,47 @@
|
||||||
#!/usr/bin/env bash
|
#!/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
|
||||||
|
|
|
||||||
16
sway.conf
16
sway.conf
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
### Variables
|
### Variables
|
||||||
set $launcher ./target/debug/shepherd-launcher
|
set $launcher ./target/debug/shepherd-launcher
|
||||||
|
set $hud ./target/debug/shepherd-hud
|
||||||
|
|
||||||
### Output configuration
|
### Output configuration
|
||||||
# Set up displays (adjust as needed for your hardware)
|
# Set up displays (adjust as needed for your hardware)
|
||||||
|
|
@ -35,7 +36,13 @@ bindsym Mod4+Shift+Escape exit
|
||||||
|
|
||||||
### Window rules for kiosk behavior
|
### 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 [class=".*"] fullscreen enable
|
||||||
for_window [app_id=".*"] fullscreen enable
|
for_window [app_id=".*"] fullscreen enable
|
||||||
|
|
||||||
|
|
@ -62,9 +69,12 @@ seat * hide_cursor 5000
|
||||||
# Use only one workspace for true kiosk mode
|
# Use only one workspace for true kiosk mode
|
||||||
workspace 1 output *
|
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
|
exec_always $launcher
|
||||||
|
|
||||||
### Disable workspace switching
|
### Disable workspace switching
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue