Repository navigation
session management and hyprland integration
Related Files:
README.mdsrc/session.rssrc/wall/mod.rssrc/config.rssrc/cmd.rs
Related Pages:
- Installation, Configuration, and Usage
- Architecture and Data Flow
- Wallpaper, Theme, and Bar Workflow
Relevant source files
The following files were used as context for generating this wiki page:
The rice project is a small Rust CLI designed to orchestrate wallpapers, theming, bar restarts, and session startup for a Hyprland desktop environment. It exposes subcommands that can be bound directly to Hyprland keybinds, enabling a single binary to handle wallpaper changes, wallust theme application, waybar restarts, and application session startup.
Sources: README.md:1-80
Session management and Hyprland integration in rice center around the session command (rice session start), the wall commands (rice wall next|prev|start), and their use inside a Hyprland configuration or scripting environment. These pieces work together through shared configuration, persistent state, and small command invocations to provide a consistent, theme-aware desktop session.
Sources: README.md:80-160, src/session.rs:1-34, src/wall/mod.rs:1-113, src/main.rs:1-61
Sources: src/main.rs:1-61, src/cli.rs:1-43, README.md:80-160
At a high level, Hyprland keybinds (or hooks) invoke rice subcommands. These subcommands load configuration and state, modify wallpapers or start applications, optionally apply themes and restart the bar, and finally persist new state and emit notifications.
| Component | Responsibility |
|---|---|
CLI (Cli, Command) |
Parse rice command line into wall, bar, and session subcommands and actions. |
session module |
Start a session by spawning configured applications and then restoring the last wallpaper. |
wall module |
List images, compute next/previous/current wallpaper, call backend to set it, apply theme, and persist state. |
wall::backend |
Abstract the wallpaper backend (hyprpaper, swww, none) via command invocations. |
config module |
Load and parse TOML config, provide runtime configuration (including session, bar, theme, notifications). |
state module |
Load and save last wallpaper and backend in JSON under ~/.cache/rice/state.json. |
theme module |
Run theme command (e.g. wallust), then optionally restart the bar. |
bar module |
Restart bar process (waybar by default) safely. |
paths module |
Resolve and create config/cache paths and expand ~. |
cmd module |
Run external commands with error handling. |
notifications module |
Show desktop notifications for success/failure using notify-rust. |
Sources: src/cli.rs:1-43, src/session.rs:1-34, src/wall/mod.rs:1-113, src/wall/backend.rs:1-30, src/config.rs:1-53, src/state.rs:1-63, src/theme.rs:1-44, src/bar.rs:1-27, src/paths.rs:1-63, src/cmd.rs:1-17, src/notifications.rs:1-54
The typical Hyprland integration is illustrated in the README, where hooks and keybinds are wired to rice commands.
Sources: README.md:120-160
graph TD
HStart["Hyprland<br/>startup"] --> HExec1["Start<br/>hyprpaper"]
HStart --> HExec2["Run<br/>rice session start"]
HBindNext["Key SUPER+W"] --> RWallNext["Command<br/>rice wall next"]
HBindPrev["Key SUPER+SHIFT+W"] --> RWallPrev["Command<br/>rice wall prev"]
HBindBar["Key SUPER+R"] --> RBarRestart["Command<br/>rice bar restart"]
Sources: README.md:120-160
Sources: src/cli.rs:1-43, src/main.rs:1-61
The CLI is defined via clap with three primary subcommands: Wall, Bar, and Session. Each has its own set of actions.
Sources: src/cli.rs:1-43
#[derive(Parser)]
#[command(name = "rice", about = "Hyprland helpers")]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand)]
pub enum Command {
Wall {
#[command(subcommand)]
action: WallAction,
},
Bar {
#[command(subcommand)]
action: BarAction,
},
Session {
#[command(subcommand)]
action: SessionAction,
},
}Sources: src/cli.rs:1-23
Supported actions:
| Command | Action | Behavior (high-level) |
|---|---|---|
wall |
Next |
Set next wallpaper, apply theme, update state. |
wall |
Prev |
Set previous wallpaper, apply theme, update state. |
wall |
Start |
Restore last wallpaper (or first) and apply theme. |
bar |
Restart |
Kill and restart configured bar. |
session |
Start |
Spawn configured apps, then run wall start. |
Sources: src/cli.rs:24-43, src/wall/mod.rs:49-113, src/bar.rs:18-27, src/session.rs:1-34
main dispatches on the parsed command and centralizes notification behavior for success and error.
Sources: src/main.rs:1-61
fn main() -> Result<()> {
let result = match Cli::parse().command {
Command::Wall { action } => match action {
WallAction::Next => wall::next(),
WallAction::Prev => wall::prev(),
WallAction::Start => wall::start(),
},
Command::Bar { action } => match action {
BarAction::Restart => bar::restart(&Config::load()?),
},
Command::Session { action } => match action {
SessionAction::Start => session::start(),
}
};
match result {
Ok(()) => {
if let Ok(cfg) = config::Config::load() {
notifications::success(&cfg, "Wallpaper updated");
}
Ok(())
},
Err(e) => {
if let Ok(cfg) = config::Config::load() {
notifications::error(&cfg, &format!("Update failed: {e:#}"));
}
eprintln!("{e:#}");
std::process::exit(1);
}
}
}Sources: src/main.rs:9-61
This behavior ensures:
- Every subcommand returns a
Result<()>. - On
Ok(()), a success notification is shown if enabled. - On
Err(e), an error notification is shown and process exits with status1.
Sources: src/session.rs:1-34, src/config.rs:1-53, src/wall/mod.rs:84-113
The Session configuration is part of the global Config and contains a list of applications to spawn when starting a session.
Sources: src/session.rs:1-18, src/config.rs:15-29
#[derive(Deserialize, Debug)]
#[serde(default)]
pub struct Session {
pub exec: Vec<String>,
}
impl Default for Session {
fn default() -> Self {
Self {
exec: vec!["waybar".into(), "swaync".into(), "discord".into()],
}
}
}Sources: src/session.rs:1-18
In the Config struct:
#[derive(Deserialize, Debug)]
pub struct Config {
pub wallpaper_dir: PathBuf,
pub backend: String,
pub sort: String,
pub extensions: Vec<String>,
pub theme: Theme,
pub bar: Bar,
pub session: Session,
pub notifications: Notifications,
}Sources: src/config.rs:15-26
session::start is the central function that implements session startup.
Sources: src/session.rs:20-34
pub fn start() -> Result<()> {
let cfg = Config::load()?;
for exec in cfg.session.exec {
std::process::Command::new(&exec)
.spawn()
.with_context(|| format!("failed to start {exec}"))?;
};
crate::wall::start()?;
Ok(())
}Sources: src/session.rs:20-34
Key behaviors:
- Load configuration via
Config::load(). - Iterate over
cfg.session.execandspawneach program. - After spawning all applications, call
wall::start()to restore the last wallpaper (and theme/bar as configured). - Return success or propagate errors with context.
This creates a tight coupling between the session initialization and wallpaper restoration, ensuring a consistent look whenever a session starts.
sequenceDiagram
autonumber
participant H as actor Hyprland
participant R as control rice(main)
participant C as control Config
participant S as control Session
participant W as control Wall
participant B as control Backend
H->>+R: rice session start
R->>+S: session::start()
S->>+C: Config::load()
C-->>-S: Config
loop for each exec in cfg.session.exec
S->>+S: spawn(exec)
S-->>-S: child process
end
S->>+W: wall::start()
W->>+C: Config::load()
C-->>-W: Config
W->>+W: State::load()
W-->>-W: State
W->>+B: backend::set(path, backend)
B-->>-W: Result
W-->>-S: Result
S-->>-R: Result
R->>+C: Config::load()
C-->>-R: Config
R->>R: notifications::success(...)
R-->>-H: exit 0
Sources: src/session.rs:20-34, src/wall/mod.rs:84-113, src/config.rs:29-53, src/wall/backend.rs:1-22, src/main.rs:9-61
Sources: src/wall/mod.rs:1-113, src/state.rs:1-63, src/config.rs:29-53
wall::list_images collects valid wallpapers from the configured directory and applies simple name-based sorting when requested.
Sources: src/wall/mod.rs:7-33
fn list_images(cfg: &Config) -> Result<Vec<PathBuf>> {
let dir = &cfg.wallpaper_dir;
let mut images: Vec<PathBuf> = fs::read_dir(dir)?
.filter_map(|res| res.ok())
.map(|entry| entry.path())
.filter(|path| {
path.is_file() && path.extension().map_or(false, |ext| cfg.is_valid_img(&ext.to_string_lossy().to_lowercase()))
})
.collect();
if images.is_empty() {
bail!("no wallpapers in {}", dir.display());
}
if cfg.sort == "name" {
images.sort();
}
Ok(images)
}Sources: src/wall/mod.rs:7-33, src/config.rs:55-60
The is_valid_img helper on Config checks extensions against the configured whitelist:
Sources: src/config.rs:55-60
pub fn is_valid_img(&self, img_ext: &str) -> bool {
self.extensions.contains(&img_ext.to_string())
}wall::next and wall::prev delegate to step(delta) which computes the next index using modular arithmetic and updates state and theme.
Sources: src/wall/mod.rs:35-65
fn step(delta: isize) -> Result<()> {
let cfg = Config::load()?;
let mut state = State::load()?;
let images = list_images(&cfg)?;
let n = images.len() as isize;
let idx = state.wallpaper
.as_ref()
.and_then(|cur| images.iter().position(|p| p == cur))
.unwrap_or(0) as isize;
let next = (idx + delta).rem_euclid(n) as usize;
let backend = state.backend
.as_deref()
.unwrap_or(cfg.backend.as_str());
backend::set(&images[next], backend)?;
theme::apply(&images[next], &cfg)?;
state.wallpaper = Some(images[next].clone());
state.backend = Some(backend.to_string());
state.save()?;
println!("{}", images[next].display());
Ok(())
}Sources: src/wall/mod.rs:35-65
Public functions:
pub fn next() -> Result<()> {
step(1)
}
pub fn prev() -> Result<()> {
step(-1)
}Sources: src/wall/mod.rs:67-73
Tests verify forward and backward wrapping behavior for a 3-element list using .rem_euclid.
Sources: src/wall/mod.rs:115-127
#[test]
fn wrap_next_from_last() {
let n: isize = 3;
let idx: isize = 2;
assert_eq!((idx + 1).rem_euclid(n) as usize, 0);
}
#[test]
fn wrap_prev_from_first() {
let n: isize = 3;
let idx: isize = 0;
assert_eq!((idx + -1).rem_euclid(n) as usize, 2);
}wall::start is used both independently (rice wall start) and from session::start. It picks a wallpaper based on saved state or falls back to the first available image.
Sources: src/wall/mod.rs:75-113
pub fn start() -> Result<()> {
let cfg = Config::load()?;
let mut state = State::load()?;
let images = list_images(&cfg)?;
let path = state.wallpaper
.as_ref()
.and_then(|cur| images.iter().find(|p| *p == cur))
.unwrap_or(&images[0])
.clone();
let backend = state.backend
.as_deref()
.unwrap_or(cfg.backend.as_str())
.to_string();
backend::set(&path, &backend)?;
theme::apply(&path, &cfg)?;
state.wallpaper = Some(path.clone());
state.backend = Some(backend);
state.save()?;
println!("{}", path.display());
Ok(())
}Sources: src/wall/mod.rs:75-113
The state is a simple JSON file containing the last wallpaper path and backend name.
Sources: src/state.rs:1-63
#[derive(Serialize, Deserialize, Debug)]
#[serde(default)]
pub struct State {
pub wallpaper: Option<PathBuf>,
pub backend: Option<String>,
}Sources: src/state.rs:7-15
Location is resolved via paths::state_file (e.g. ~/.cache/rice/state.json on Linux).
Sources: src/paths.rs:31-40
pub fn state_file() -> Result<PathBuf> {
Ok(cache_dir()?.join("state.json"))
}State load and save logic:
pub fn load() -> Result<State> {
let path = paths::state_file()?;
if !path.exists() {
return Ok(State::default());
}
let raw = std::fs::read_to_string(&path)
.with_context(|| format!("failed to read {}", path.display()))?;
let state = serde_json::from_str(&raw)
.with_context(|| format!("failed for parse {}", path.display()))?;
Ok(state)
}
pub fn save(&self) -> Result<()> {
let path = paths::state_file()?;
let parent = path.parent().context("Path does not have a parent")?;
paths::ensure_dir(parent)?;
let json = serde_json::to_string_pretty(self).context("failed to serialize state")?;
std::fs::write(&path, json)
.with_context(|| format!("failed to write {}", path.display()))?;
Ok(())
}Sources: src/state.rs:19-49
graph TD
Cmd["wall command"] --> LoadCfg["Config::load()"]
Cmd --> LoadState["State::load()"]
LoadCfg --> ListImg["list_images(cfg)"]
ListImg --> SelectImg["Select<br/>current/next/prev"]
LoadState --> SelectImg
SelectImg --> BackendSet["backend::set(path, backend)"]
BackendSet --> ThemeApply["theme::apply(path, cfg)"]
ThemeApply --> UpdateState["Update<br/>State fields"]
UpdateState --> SaveState["State::save()"]
SaveState --> Done["Print path<br/>and return"]
Sources: src/wall/mod.rs:7-65, src/wall/mod.rs:75-113, src/state.rs:19-49, src/theme.rs:23-44, src/wall/backend.rs:1-22
Sources: src/wall/backend.rs:1-30, src/cmd.rs:1-17, src/config.rs:15-26
The wall::backend module abstracts over the actual wallpaper setting mechanism by mapping a backend string to a specific external command invocation.
pub fn set(path: &Path, backend: &str) -> Result<()> {
let img_path = path.to_str().context("wallpaper path is not valid utf-8")?;
match backend {
"hyprpaper" => cmd::run("hyprctl", &["hyprpaper", "wallpaper", &format!("eDP-1, {img_path}")]),
"swww" => cmd::run("swww", &["img", img_path]),
"none" => Ok(()),
_ => bail!("unkown backend {backend}"),
}
}Sources: src/wall/backend.rs:1-22
Backends:
| Backend string | Behavior |
|---|---|
"hyprpaper" |
Run hyprctl hyprpaper wallpaper "eDP-1, {img_path}". |
"swww" |
Run swww img {img_path}. |
"none" |
Do nothing; skip wallpaper command. |
| other | Error: unknown backend {backend}. |
Sources: src/wall/backend.rs:1-22
cmd::run encapsulates spawning an external binary and checking its exit status.
Sources: src/cmd.rs:1-17
pub fn run(bin: &str, args: &[&str]) -> Result<()> {
let status = Command::new(bin)
.args(args)
.status()
.with_context(|| format!("failed to start {bin}"))?;
if !status.success() {
bail!("{bin} exited with {status}");
}
Ok(())
}Sources: src/cmd.rs:1-17
This is used by backend::set, theme::apply, and bar::restart.
The backend used is tracked both in configuration and state:
- Config default (from
examples/config.tomlviaConfig::load) providesbackendfor first use. - State persists the last used backend.
Sources: src/config.rs:15-26, src/state.rs:7-15, src/wall/mod.rs:35-65, src/wall/mod.rs:75-113
In step (next/prev):
let backend = state.backend
.as_deref()
.unwrap_or(cfg.backend.as_str());Sources: src/wall/mod.rs:47-52
In start:
let backend = state.backend
.as_deref()
.unwrap_or(cfg.backend.as_str())
.to_string();Sources: src/wall/mod.rs:95-99
Sources: src/config.rs:1-60, src/paths.rs:1-63, README.md:80-120
On first run, Config::load() will create ~/.config/rice/config.toml from the embedded default if it does not exist.
Sources: src/config.rs:29-53
pub fn load() -> Result<Config> {
let path = paths::config_file()?;
if !path.exists() {
let parent = path.parent().context("Config path has no parent")?;
paths::ensure_dir(parent)?;
std::fs::write(&path, DEFAULT)
.with_context(|| format!("failed to write {}", path.display()))?;
}
let raw = std::fs::read_to_string(&path)
.with_context(|| format!("failed to read {}", path.display()))?;
let mut cfg: Config = toml::from_str::<Config>(&raw)
.with_context(|| format!("failed to parse {}", path.display()))?;
cfg.wallpaper_dir = paths::expand_tilde(&cfg.wallpaper_dir);
Ok(cfg)
}Sources: src/config.rs:29-53
paths uses directories::ProjectDirs to resolve config and cache directories for the rice application.
Sources: src/paths.rs:1-63
Key helpers:
| Function | Description |
|---|---|
config_dir() |
Returns ~/.config/rice. |
cache_dir() |
Returns ~/.cache/rice. |
config_file() |
Returns ~/.config/rice/config.toml. |
icon_file() |
Returns ~/.config/rice/icon.png. |
state_file() |
Returns ~/.cache/rice/state.json. |
ensure_dir(p) |
mkdir -p equivalent on the given directory path. |
expand_tilde(p) |
Expands ~ or ~/... using $HOME when available. |
Sources: src/paths.rs:13-63
Example:
pub fn expand_tilde(path: &Path) -> PathBuf {
let raw = path.to_string_lossy();
if raw == "~" {
return home_dir().unwrap_or_else(|| PathBuf::from(raw.as_ref()));
}
if let Some(rest) = raw.strip_prefix("~/") {
let mut home = home_dir().unwrap_or_else(|| PathBuf::from("~"));
home.push(rest);
return home;
}
path.to_path_buf()
}Sources: src/paths.rs:45-63
Sources: src/theme.rs:1-44, src/bar.rs:1-27, src/config.rs:15-26, README.md:40-70
The Theme struct defines how theming is applied after a wallpaper change.
Sources: src/theme.rs:1-21, src/config.rs:15-26
#[derive(Deserialize, Debug)]
#[serde(default)]
pub struct Theme {
enable: bool,
command: String,
args: Option<Vec<String>>
}
impl Default for Theme {
fn default() -> Self {
Self {
enable: true,
command: "wallust".into(),
args: None,
}
}
}Sources: src/theme.rs:1-21
README example configuration:
[theme]
enable = true
command = "wallust"
args = [] # apply() should call: wallust run <image>Sources: README.md:40-70
theme::apply is called after successfully setting the wallpaper.
Sources: src/theme.rs:23-44, src/wall/mod.rs:35-65, src/wall/mod.rs:75-113
pub fn apply(path: &Path, cfg: &Config) -> Result<()> {
if !cfg.theme.enable {
return Ok(());
}
let p = paths::expand_tilde(path);
let img = p
.to_str()
.context("wallpaper path is not valid utf-8")?;
let mut args: Vec<String> = vec!["run".into(), img.to_string()];
if let Some(extra) = &cfg.theme.args {
args.extend(extra.iter().cloned());
}
let args_ref: Vec<&str> = args.iter().map(String::as_str).collect();
cmd::run(&cfg.theme.command, &args_ref)?;
if cfg.bar.restart_on_theme {
crate::bar::restart(cfg)?;
}
Ok(())
}Sources: src/theme.rs:23-44
This function:
- Honors
cfg.theme.enable. - Builds command arguments as
["run", <image_path>, ...extra_args]. - Calls the configured theme command via
cmd::run. - If
cfg.bar.restart_on_themeis true, callsbar::restart(cfg).
bar::restart uses pkill on the configured bar command, then spawns a new instance.
Sources: src/bar.rs:1-27
#[derive(Deserialize, Debug)]
#[serde(default)]
pub struct Bar {
pub restart_on_theme: bool,
pub command: String,
}
impl Default for Bar {
fn default() -> Self {
Self {
restart_on_theme: true,
command: "waybar".into(),
}
}
}
pub fn restart(cfg: &Config) -> Result<()> {
cmd::run("pkill", &[&cfg.bar.command])?;
std::process::Command::new(&cfg.bar.command)
.spawn()
.with_context(|| format!("failed to start {}", cfg.bar.command))?;
Ok(())
} Sources: src/bar.rs:1-27
README example configuration:
[bar]
restart_on_theme = true
command = "waybar"Sources: README.md:40-70
Sources: src/notifications.rs:1-54, src/main.rs:9-61, src/paths.rs:13-40
Notification preferences are part of the global Config:
Sources: src/notifications.rs:7-21, src/config.rs:15-26
#[derive(Serialize, Deserialize, Debug)]
#[serde(default)]
pub struct Notifications {
pub on_success: bool,
pub on_error: bool,
}
impl Default for Notifications {
fn default() -> Self {
Self {
on_success: false,
on_error: true,
}
}
}Sources: src/notifications.rs:7-21
main calls notifications::success or notifications::error based on subcommand results.
Sources: src/main.rs:26-61
notifications ensures an icon file exists and then displays a desktop notification via notify-rust.
Sources: src/notifications.rs:23-54
fn ensure_icon() -> Result<PathBuf> {
let path = crate::paths::icon_file()?;
if !path.exists() {
if let Some(dir) = path.parent() {
crate::paths::ensure_dir(dir)?;
}
std::fs::write(&path, ICON)
.with_context(|| format!("failed to write {}", path.display()))?;
}
Ok(path)
}
fn show(body: &str, urgency: Urgency) {
let Ok(icon) = ensure_icon() else { return };
let Some(icon) = icon.to_str() else { return };
let _ = Notification::new()
.appname("rice")
.summary("rice")
.body(body)
.icon(icon)
.urgency(urgency)
.show();
}Sources: src/notifications.rs:23-44
Public helpers:
pub fn success(cfg: &Config, body: &str) {
if cfg.notifications.on_success {
show(body, Urgency::Low);
}
}
pub fn error(cfg: &Config, body: &str) {
if cfg.notifications.on_error {
show(body, Urgency::Normal);
}
}Sources: src/notifications.rs:46-54
Sources: README.md:120-160, src/cli.rs:1-43, src/session.rs:20-34, src/wall/mod.rs:67-113, src/bar.rs:18-27
The README shows how rice is intended to be called from Hyprland, particularly the session and wall commands.
Sources: README.md:120-160
Example Hyprland integration:
hl.on("hyprland.start", function()
hl.exec_cmd("hyprpaper")
hl.exec_cmd("rice session start")
end)
hl.bind("SUPER + W", hl.dsp.exec_cmd("rice wall next"))
hl.bind("SUPER + SHIFT + W", hl.dsp.exec_cmd("rice wall prev"))
hl.bind("SUPER + R", hl.dsp.exec_cmd("rice bar restart"))Sources: README.md:120-160
Key patterns:
- On Hyprland start:
- Start wallpaper backend (
hyprpaper) separately. - Call
rice session startto spawn the configured session apps and restore wallpaper.
- Start wallpaper backend (
- Bind keys to
rice wall next/prevto cycle wallpapers. - Bind a key to
rice bar restartto manually restart bar if needed (in addition to automatic restarts triggered by theme changes).
graph TD
HyprStart["Hyprland<br/>start"] --> StartBackend["exec hyprpaper"]
HyprStart --> StartSession["exec rice session start"]
KeyNext["SUPER+W"] --> CmdNext["rice wall next"]
KeyPrev["SUPER+SHIFT+W"] --> CmdPrev["rice wall prev"]
KeyBar["SUPER+R"] --> CmdBar["rice bar restart"]
Sources: README.md:120-160
Based on README example and module definitions, relevant configuration sections:
| Section | Key | Type | Default (if any) | Role in Session/Integration |
|---|---|---|---|---|
| root | wallpaper_dir |
PathBuf |
from example config | Directory for wallpapers. |
| root | backend |
String |
from example config | Default wallpaper backend (hyprpaper/swww). |
| root | sort |
String |
from example config |
"name" triggers filename sorting. |
| root | extensions |
Vec<String> |
from example config | Valid wallpaper file extensions. |
[theme] |
enable |
bool |
true |
Toggle theme application on wallpaper change. |
[theme] |
command |
String |
"wallust" |
Theme command binary. |
[theme] |
args |
Option<Vec<String>> |
None |
Extra args appended after run <image>. |
[bar] |
restart_on_theme |
bool |
true |
Whether to restart bar after theme apply. |
[bar] |
command |
String |
"waybar" |
Bar command to restart. |
[session] |
exec |
Vec<String> |
["waybar","swaync","discord"] |
Apps started by rice session start. |
[notifications] |
on_success |
bool |
false |
Show notification on success. |
[notifications] |
on_error |
bool |
true |
Show notification on error. |
Sources: src/config.rs:15-26, src/session.rs:1-18, src/theme.rs:1-21, src/bar.rs:1-16, src/notifications.rs:7-21, README.md:40-70
Session management and Hyprland integration in rice are built around a small set of cohesive modules and commands:
-
rice session startorchestrates starting user applications and restoring wallpapers. -
rice wall next|prev|startmanage wallpaper selection, backend invocation, theme application, bar restarts, and persistent state updates. - Configuration and state are clearly separated via TOML and JSON files in XDG directories, resolved by the
pathsmodule. - External components (Hyprland, wallpaper backends, theming tools, bar, desktop notifications) are integrated via small, well-defined command calls encapsulated in helper modules.
Together, these pieces provide a straightforward yet flexible way to manage a Hyprland-based desktop session through a single CLI entry point that can be easily wired to Hyprland startup hooks and keybindings.
Sources: README.md:1-80, src/session.rs:1-34, src/wall/mod.rs:1-113, src/theme.rs:23-44, src/bar.rs:1-27, src/notifications.rs:23-54, src/main.rs:9-61