Repository navigation
installation configuration and usage
Related Files:
README.mdexamples/config.tomlsrc/config.rssrc/cli.rssrc/main.rs
Related Pages:
- Overview and Key Features
- Architecture and Data Flow
- Session Management and Hyprland Integration
Relevant source files
The following files were used as context for generating this wiki page:
rice is a small Rust CLI designed to orchestrate wallpapers, theme generation, and session helpers for a Hyprland desktop. It centralizes common desktop actions (wallpaper cycling, wallust theming, waybar restart, and session startup) behind a single binary and a simple CLI interface. README.md:1-7, README.md:23-31
This page describes, in detail, how rice is installed, how it loads and validates configuration, where it stores state, and how the main CLI commands behave at runtime. It ties together the top-level binary (main.rs), command routing (cli.rs), configuration (config.rs, examples/config.toml), filesystem paths (paths.rs), and feature modules for wallpaper, bar, session, theme, and notifications. src/main.rs:1-18, src/cli.rs:1-34, src/config.rs:1-36
Sources: README.md:33-89, README.md:115-143
rice assumes a Linux desktop environment using Hyprland and one or more supported wallpaper backends. The following prerequisites are required or conditionally used:
| Component | Required | Purpose / Notes |
|---|---|---|
| Rust toolchain with Cargo | Yes | Used to build and install the rice binary. |
| Hyprland | Yes | Target compositor for which rice is designed. |
hyprpaper or swww
|
At least one | Wallpaper backends that rice controls. |
wallust |
Optional | Theme generator run after wallpaper changes when enabled. |
waybar |
Optional | Status bar that can be restarted by rice bar restart or session startup. |
rice itself is a single binary installed into ~/.cargo/bin by Cargo. That directory must be on the user’s PATH for global invocation. README.md:63-71
There are two supported installation workflows: installing directly from Git or from a local clone. README.md:63-85
cargo install --git https://github.com/Cody-will/rice.gitThis compiles and installs the rice binary into ~/.cargo/bin. README.md:63-69
git clone https://github.com/Cody-will/rice.git
cd rice
cargo install --path .This uses the checked-out source as the install target. README.md:71-77
After installation:
which rice
rice --helpIf which rice is empty, the user must add ~/.cargo/bin to PATH, for example in ~/.bashrc or ~/.zshrc. README.md:79-89
Reinstallation after pulling changes is done via:
cd rice
git pull
cargo install --path .Sources: README.md:99-151, examples/config.toml, src/config.rs:1-40, src/paths.rs:1-63, src/notifications.rs:1-37, src/bar.rs:1-23, src/session.rs:1-19, src/theme.rs:1-27
Configuration is central to how rice behaves: it controls wallpaper selection, backend choice, bar behavior, theming commands, notification toggles, and which applications are started in a session.
rice uses XDG project directories via directories::ProjectDirs with the application name rice. On Linux this resolves to:
- Config directory:
~/.config/rice - Cache directory:
~/.cache/rice
Key path helpers:
| Function | Description | Example path |
|---|---|---|
config_dir() |
Returns the config directory for rice. |
~/.config/rice |
cache_dir() |
Returns the cache directory for rice. |
~/.cache/rice |
config_file() |
Returns the path to the TOML config file. | ~/.config/rice/config.toml |
state_file() |
Returns the path to the JSON state file. | ~/.cache/rice/state.json |
icon_file() |
Returns the path to the notification icon. | ~/.config/rice/icon.png |
ensure_dir(path) |
Recursively creates a directory (mkdir -p semantics). | Any parent directory for the above |
On the first Config::load() call, if config.toml does not exist, rice will:
- Compute the config file path via
paths::config_file(). - Create the parent directory with
paths::ensure_dir. - Write the embedded default config (
examples/config.tomlincluded at compile time) intoconfig.toml.
Subsequent loads read the file and parse it as TOML into a Config struct. src/config.rs:27-36
The Config struct defines the runtime configuration schema and is deserialized from TOML: src/config.rs:9-23
#[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,
}Theme, Bar, Session, and Notifications all have Default implementations leveraged when fields are missing from the TOML. src/theme.rs:9-24, src/bar.rs:9-21, src/session.rs:7-18, src/notifications.rs:11-24
The example (and default) configuration is stored at examples/config.toml and embedded via include_str!. A representative snippet from the README (which mirrors the example) is: src/config.rs:3-7, README.md:99-135
wallpaper_dir = "~/Pictures/walls"
backend = "hyprpaper" # hyprpaper | swww | none
sort = "name"
extensions = ["jpg", "jpeg", "png", "webp"]
[theme]
enable = true
command = "wallust"
args = [] # apply() should call: wallust run <image>
[bar]
restart_on_theme = true
command = "waybar"
[session]
exec = ["waybar", "swaync"]In addition, notifications configuration is provided via the Notifications struct with default values: src/notifications.rs:11-24
#[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,
}
}
}After parsing, Config::load() post-processes the wallpaper_dir field by expanding a leading ~ using the user’s home directory: src/config.rs:29-36, src/paths.rs:47-63
cfg.wallpaper_dir = paths::expand_tilde(&cfg.wallpaper_dir);The expand_tilde helper:
-
~→$HOME -
~/something→$HOME/something - Any other path is returned unchanged.
This ensures that wallpaper directories configured with ~ are resolved to absolute paths before use.
Below is a consolidated table of configuration fields, types, and default behavior:
| Section | Field | Type | Default value / behavior | Notes |
|---|---|---|---|---|
| root | wallpaper_dir |
PathBuf |
"~/Pictures/walls" (from example) |
Tilde expanded. |
| root | backend |
String |
"hyprpaper" (from example) |
Must be "hyprpaper", "swww", or "none" for built-in backends. src/wall/backend.rs:7-19
|
| root | sort |
String |
"name" (from example) |
Only "name" is treated specially (lexicographic sort). src/wall/mod.rs:15-26
|
| root | extensions |
Vec<String> |
["jpg","jpeg","png","webp"] (from example) |
Used to filter images. src/config.rs:38-40, src/wall/mod.rs:17-23 |
theme |
enable |
bool |
true (Default impl) |
Disables theme/apply if false. src/theme.rs:9-24, src/theme.rs:28-34
|
theme |
command |
String |
"wallust" (Default impl) |
Executable used for theming. |
theme |
args |
Option<Vec<String>> |
None (Default impl) |
Extra args appended after run <image>. src/theme.rs:9-24, src/theme.rs:36-47
|
bar |
restart_on_theme |
bool |
true (Default impl) |
If true, bar::restart is called after theme apply. src/bar.rs:9-21, src/theme.rs:49-55
|
bar |
command |
String |
"waybar" (Default impl) |
Process name used by pkill and for relaunch. src/bar.rs:9-21
|
session |
exec |
Vec<String> |
["waybar","swaync","discord"] (Default impl) |
Commands spawned on rice session start. src/session.rs:7-18, src/session.rs:20-31
|
notifications |
on_success |
bool |
false (Default impl) |
Controls success notification emission. src/notifications.rs:11-24, src/notifications.rs:46-53 |
notifications |
on_error |
bool |
true (Default impl) |
Controls error notification emission. src/notifications.rs:11-24, src/notifications.rs:55-62 |
Sources: src/main.rs:1-58, src/cli.rs:1-34, README.md:23-31
The Cli struct and its nested subcommands define the CLI surface: src/cli.rs:1-34
#[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,
},
}Available actions:
rice wall nextrice wall prevrice wall startrice bar restartrice session start
README.md:23-31, src/cli.rs:18-34
wall actions are dispatched to functions in wall::mod, bar actions to bar::restart, and session actions to session::start via the top-level main function: src/main.rs:7-18, src/main.rs:20-30
The main function parses CLI arguments, runs the selected command, and then handles notifications and exit status: src/main.rs:7-18, src/main.rs:20-58
High-level logic:
- Parse CLI:
Cli::parse(). - Match
commandand dispatch to the appropriate module function. - After the function returns, if
Ok(()):- Try to load
Config. - If successful, send a success notification
"Wallpaper updated". - Return
Ok(()).
- Try to load
- If the function returns
Err(e):- Try to load
Config. - If successful, send an error notification
"Update failed: {e:#}". - Print the error to stderr.
- Exit with code 1.
- Try to load
src/main.rs:7-18, src/main.rs:20-58
The simplified conceptual flow is:
let result = match Cli::parse().command { /* dispatch */ };
match result {
Ok(()) => {
if let Ok(cfg) = Config::load() {
notifications::success(&cfg, "Wallpaper updated");
}
Ok(())
},
Err(e) => {
if let Ok(cfg) = Config::load() {
notifications::error(&cfg, &format!("Update failed: {e:#}"));
}
eprintln!("{e:#}");
std::process::exit(1);
}
}src/main.rs:7-18, src/main.rs:20-58
The following Mermaid diagram visualizes the main command flow from CLI invocation to module dispatch and notification handling:
graph TD
CLI["User runs rice"] --> Parse["Cli::parse()"]
Parse --> MatchCmd["Match Command"]
MatchCmd --> WallCmd["wall::*"]
MatchCmd --> BarCmd["bar::restart"]
MatchCmd --> SessionCmd["session::start"]
WallCmd --> Result["Result<()>"]
BarCmd --> Result
SessionCmd --> Result
Result --> OkBranch["Ok(())"]
Result --> ErrBranch["Err(e)"]
OkBranch --> LoadCfgOk["Config::load()"]
ErrBranch --> LoadCfgErr["Config::load()"]
LoadCfgOk --> SuccNotif["notifications::success()"]
LoadCfgErr --> ErrNotif["notifications::error()"]
ErrNotif --> Stderr["Print error"]
Stderr --> Exit["Exit(1)"]
Sources: src/main.rs:7-18, src/cli.rs:1-34, src/notifications.rs:39-62
Sources: src/wall/mod.rs:1-104, src/wall/backend.rs:1-24, src/state.rs:1-55, src/config.rs:36-40, src/theme.rs:26-55, examples/config.toml
The wall module encapsulates image discovery, selection, and backend-specific wallpaper application. It also coordinates theme application and state persistence.
list_images(cfg: &Config) reads the configured wallpaper_dir and filters files by extension: src/wall/mod.rs:7-26
Key behaviors:
- Uses
fs::read_dironcfg.wallpaper_dir. - Keeps only paths that are files (
path.is_file()). - Keeps only files whose lowercase extension appears in
cfg.extensions. - If no images are found, it errors with
bail!("no wallpapers in {}", dir.display()). - If
cfg.sort == "name", it sorts the images lexicographically.
src/wall/mod.rs:7-26, src/config.rs:38-40
Config::is_valid_img helper:
pub fn is_valid_img(&self, img_ext: &str) -> bool {
self.extensions.contains(&img_ext.to_string())
}The State struct tracks the last used wallpaper and backend across runs: src/state.rs:7-21
#[derive(Serialize, Deserialize, Debug)]
#[serde(default)]
pub struct State {
pub wallpaper: Option<PathBuf>,
pub backend: Option<String>,
}-
State::load():- If
state.jsondoes not exist, returnsState::default()(both fieldsNone). - Otherwise, reads and deserializes JSON from
~/.cache/rice/state.json.
- If
-
State::save():- Ensures the parent directory exists via
paths::ensure_dir. - Serializes the struct to pretty JSON and writes it to
state.json.
- Ensures the parent directory exists via
The step(delta: isize) function is used by both next and prev: src/wall/mod.rs:28-50
Processing steps:
- Load global config:
Config::load(). - Load state:
State::load(). - List available images via
list_images. - Compute current index:
- If
state.wallpaperisSomeand present inimages, use its index. - Otherwise, fall back to index
0.
- If
- Compute next index using wrapping arithmetic:
-
(idx + delta).rem_euclid(n)wherenis the number of images.
-
- Derive backend:
- Use
state.backendif set, elsecfg.backend.as_str().
- Use
- Set wallpaper via backend:
backend::set(&images[next], backend). - Apply theme via
theme::apply. - Update state with chosen wallpaper and backend, then save.
- Print the chosen image path to stdout.
next() and prev() are thin wrappers:
pub fn next() -> Result<()> {
step(1)
}
pub fn prev() -> Result<()> {
step(-1)
}Wrap-around behavior is verified by unit tests: src/wall/mod.rs:76-88
#[test]
fn wrap_next_from_last() {
let n: isize = 3;
let idx: isize = 2;
assert_eq!((idx + 1).rem_euclid(n) as usize, 0);
}start() restores the last used wallpaper or falls back to the first image: src/wall/mod.rs:62-75
Flow:
- Load
ConfigandState. - Get image list.
- Determine target wallpaper:
- If
state.wallpaperis set and present in the image list, use that. - Otherwise, use
images[0].
- If
- Determine backend:
- Use
state.backendif set, elsecfg.backend.
- Use
- Call
backend::set(&path, &backend). - Call
theme::apply(&path, &cfg). - Update
state.wallpaperandstate.backend, save state. - Print chosen path.
The wall::backend module abstracts backend-specific commands: src/wall/backend.rs:1-24
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}"),
}
}- For
"hyprpaper", it useshyprctl hyprpaper wallpaper eDP-1, <img_path>. - For
"swww", it usesswww img <img_path>. - For
"none", it is a no-op. - Any other backend string results in an error.
graph TD
Start["Command: wall next/prev/start"] --> LoadCfgW["Config::load()"]
LoadCfgW --> LoadStateW["State::load()"]
LoadCfgW --> ListImgs["list_images(cfg)"]
ListImgs --> ChooseIdx["Compute index / path"]
ChooseIdx --> ChooseBackend["Resolve backend"]
ChooseBackend --> BackendSet["backend::set(path, backend)"]
BackendSet --> ThemeApply["theme::apply(path, cfg)"]
ThemeApply --> UpdateState["Update State"]
UpdateState --> SaveState["State::save()"]
SaveState --> PrintPath["Print wallpaper path"]
Sources: src/wall/mod.rs:7-26, src/wall/mod.rs:28-50, src/wall/mod.rs:62-75, src/wall/backend.rs:7-19, src/theme.rs:26-55, src/state.rs:23-55
Sources: src/theme.rs:1-55, src/bar.rs:1-29, src/cmd.rs:1-18, examples/config.toml
The Theme struct controls whether theming is performed and how the theme command is executed: src/theme.rs:9-24
#[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,
}
}
}When theme.enable is false, apply returns immediately without invoking any external commands. src/theme.rs:26-34
theme::apply is invoked after wallpaper changes and during wall start: src/theme.rs:26-55, src/wall/mod.rs:44-50, src/wall/mod.rs:68-75
Steps:
- Check
cfg.theme.enable; iffalse, returnOk(()). - Expand tilde in
pathviapaths::expand_tilde. - Convert path to string, erroring if invalid UTF-8.
- Build arguments: start with
["run", <image>], then append any extracfg.theme.argsif present. - Convert to
&strslice and callcmd::run(&cfg.theme.command, &args_ref). - If
cfg.bar.restart_on_themeistrue, callbar::restart(cfg).
cmd::run executes a child command and checks its exit status: src/cmd.rs:1-18
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(())
}The Bar struct and restart function manage restarting the configured bar command: src/bar.rs:9-23
#[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(())
} - First,
pkill <bar.command>is called viacmd::run. - Then a new process for
cfg.bar.commandis spawned.
graph TD
WallChange["Wallpaper updated"] --> ThemeApplyCall["theme::apply(path, cfg)"]
ThemeApplyCall --> CheckEnable["theme.enable?"]
CheckEnable -->|false| ThemeReturn["Return Ok(())"]
CheckEnable -->|true| BuildArgs["Build args: run <image> + extra"]
BuildArgs --> RunCmd["cmd::run(theme.command, args)"]
RunCmd --> CheckBar["bar.restart_on_theme?"]
CheckBar -->|false| EndTheme["Done"]
CheckBar -->|true| BarRestart["bar::restart(cfg)"]
BarRestart --> EndTheme
Sources: src/theme.rs:26-55, src/bar.rs:9-23, src/cmd.rs:1-18
Sources: src/session.rs:1-31, examples/config.toml, src/wall/mod.rs:62-75, README.md:137-151
The session module provides a single action: rice session start. It spawns a list of configured processes and then restores the last wallpaper (with associated theming and bar restart behavior).
Session struct and Default: src/session.rs:7-18
#[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()],
}
}
}The example config in README uses a shorter list: README.md:127-135
[session]
exec = ["waybar", "swaync"]session::start() is invoked from main when the user runs rice session start. src/main.rs:15-18, src/cli.rs:30-34
Implementation: src/session.rs:20-31
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(())
}Sequence:
- Load
Config. - Iterate over
cfg.session.exec, spawning each command as a child process. - Call
wall::start()to restore the last or first wallpaper (including theme and bar restart). - Return
Ok(()).
src/session.rs:20-31, src/wall/mod.rs:62-75
graph TD
SessionCmd["Command: session start"] --> LoadCfgS["Config::load()"]
LoadCfgS --> LoopExec["For each exec in session.exec"]
LoopExec --> SpawnProc["spawn(exec)"]
SpawnProc --> LoopExec
LoopExec --> WallStart["wall::start()"]
WallStart --> DoneSession["Return Ok(())"]
Sources: src/session.rs:20-31, src/wall/mod.rs:62-75
Sources: src/notifications.rs:1-62, src/paths.rs:37-45, src/main.rs:20-58
Notifications are optional desktop notifications triggered after command execution, depending on the success or failure of the operation and per-user configuration.
Notifications struct with defaults: src/notifications.rs:11-24
-
on_success: defaultfalse. -
on_error: defaulttrue.
These flags determine if success and error functions emit a notification.
The notification module embeds an icon and ensures it is written to disk: src/notifications.rs:5-9, src/notifications.rs:26-38
const ICON: &[u8] = include_bytes!("../assets/rice-notify.png");
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)
}The file path is ~/.config/rice/icon.png as provided by paths::icon_file(). src/paths.rs:37-45
show constructs a notification via notify_rust if ensure_icon and path conversion succeed: src/notifications.rs:40-48
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();
}High-level entry points: src/notifications.rs:50-62
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);
}
}These functions are called from main after command execution: src/main.rs:20-58
graph TD
CmdResult["Command result"] --> OkRes["Ok(())"]
CmdResult --> ErrRes["Err(e)"]
OkRes --> LoadCfgN1["Config::load()"]
ErrRes --> LoadCfgN2["Config::load()"]
LoadCfgN1 --> CheckSuccess["cfg.notifications.on_success?"]
LoadCfgN2 --> CheckError["cfg.notifications.on_error?"]
CheckSuccess -->|true| ShowSucc["show(body, Low)"]
CheckSuccess -->|false| EndN1["No notification"]
CheckError -->|true| ShowErr["show(body, Normal)"]
CheckError -->|false| EndN2["No notification"]
ShowSucc --> EndN1
ShowErr --> EndN2
Sources: src/main.rs:20-58, src/notifications.rs:11-24, src/notifications.rs:40-62
Sources: README.md:33-49, src/paths.rs:13-45, src/state.rs:23-55, src/notifications.rs:26-38, src/config.rs:15-27
From README: README.md:33-49
rice/
├── Cargo.toml
├── examples/
│ └── config.toml
├── rustfmt.toml
└── src/
├── main.rs
├── cli.rs
├── paths.rs
├── config.rs
├── state.rs
├── cmd.rs
├── theme.rs
├── bar.rs
├── session.rs
└── wall/
├── mod.rs
└── backend.rs
Runtime files are created in XDG directories:
| File | Purpose | Creator |
|---|---|---|
~/.config/rice/config.toml |
Main configuration TOML |
Config::load() on first run. |
~/.cache/rice/state.json |
Wallpaper/backend state |
State::save(). |
~/.config/rice/icon.png |
Notification icon |
notifications::ensure_icon(). |
src/paths.rs:17-45, src/config.rs:15-27, src/state.rs:23-55, src/notifications.rs:26-38
Sources: README.md:23-31, README.md:137-151, src/cli.rs:18-34, src/main.rs:7-18
rice wall next
rice wall prev
rice wall start
-
nextandprevcycle through available images with wrap-around. -
startrestores the last-used wallpaper (or first available) and applies theme/bar restart. src/wall/mod.rs:28-50, src/wall/mod.rs:62-75
rice bar restart
- Terminates the configured bar command via
pkill. - Spawns a new bar process with the same command name. src/bar.rs:9-23
rice session start
- Spawns each program in
session.exec. - Restores wallpaper (and theme/bar behavior) via
wall::start().
README.md:137-151, src/session.rs:20-31
rice provides a cohesive command-line interface for managing wallpapers, themes, bar restarts, and session startup in a Hyprland environment. Installation is performed via Cargo into ~/.cargo/bin, and configuration is stored in ~/.config/rice/config.toml, with sensible defaults derived from examples/config.toml. README.md:51-85, src/config.rs:3-27
At runtime, the main entrypoint routes CLI subcommands to wall, bar, and session modules, each of which uses structured configuration (Config), state (State), and helper utilities (cmd, paths, notifications) to execute external commands in a predictable way. Wallpaper operations integrate backend-specific logic, theming, and state persistence, while notifications optionally report success or failure. src/main.rs:7-18, src/wall/mod.rs:7-50, src/theme.rs:26-55, src/state.rs:23-55, src/notifications.rs:40-62
Developers extending or integrating rice should rely on the documented configuration schema, path helpers, and command flows described above to maintain consistent behavior across installation, configuration, and usage scenarios.