Welcome to hypricer. Unlike traditional "dotfile managers" that just copy files, hypricer allows you to build Reactive Themes that change instantly based on system events (Music, Battery, Time, Weather, etc.).
As a "Ricer" (Theme Developer), you are not just writing config files; you are effectively building a small, specialized Rust program that manages the desktop state.
A hypricer theme consists of three parts:
- The Manifest (
theme.toml): The wiring. It tells the system "I need to listen to Music and Battery." - The Brain (
logic/*.rs): The logic. "If Music is Metal AND Battery > 20%, use Neon borders." - The Body (
template.conf): The skeleton. Standard Hyprland config with{{ placeholders }}.
Create a new directory in themes/. The folder name (e.g., cyber_punk) will be your theme ID.
mkdir -p themes/cyber_punk/logic
touch themes/cyber_punk/theme.toml
touch themes/cyber_punk/template.confThis file defines what your theme needs to "see" (inputs) and how it decides "what to show" (dynamic parts).
[meta]
name = "Cyber Punk 2077"
version = "1.0"
author = "YourName"
template = "themes/cyber_punk/template.conf"
# 1. INPUTS: What data does your logic need?
# These keys must exist in 'catalog/registry/*.toml'
inputs = ["music_genre", "battery_level", "active_window"]
# 2. DYNAMIC: Which Rust function resolves this tag?
[dynamic]
# When template asks for {{ window_style }}, run logic/window.rs
window_style = "themes/cyber_punk/logic/window.rs"
# 3. STATIC: Simple re-usable components
[static]
apps = "apps_standard"This is where the magic happens. You write standard Rust code to decide which components to load.
File: themes/cyber_punk/logic/window.rs
// The function signature must always be:
// pub fn resolve(ctx: &Context) -> Vec<String>
pub fn resolve(ctx: &Context) -> Vec<String> {
// 1. Access Data safely
// (Keys match the 'inputs' list in theme.toml)
let genre = ctx.data.get("music_genre").unwrap_or("unknown");
let battery = ctx.data.get("battery_level").unwrap_or("100");
let batt_int: i32 = battery.parse().unwrap_or(100);
// 2. The Decision Logic
if batt_int < 20 {
// Low power mode: Use minimal borders
return vec!["win_minimal".to_string()];
}
if genre.contains("Synthwave") {
// High energy mode: Use Neon borders
return vec!["win_neon".to_string()];
}
// Default
return vec!["win_glass".to_string()];
}This is your Hyprland configuration file. Instead of hardcoding values, use the tags you defined.
# Auto-generated by hypricer
# Load Static Apps
{{ apps }}
# Load Dynamic Window Style (Changes instantly!)
{{ window_style }}
# Regular Hyprland Config
monitor=,preferred,auto,1If you need a Watcher or Component that doesn't exist yet, you must add it to the Registry.
Create or edit a file in catalog/registry/.
Example: You want to change themes based on Wi-Fi Status.
Create catalog/registry/network.toml:
[watcher.wifi_ssid]
provider = "poll_cmd"
cmd = "iwgetid -r"
interval = 5000 # Check every 5 seconds
output = "string"Now you can use inputs = ["wifi_ssid"] in your theme!
If you created a new Window Style (e.g., "Glassy"), add it to the registry so it can be resolved.
Edit catalog/registry/styles.toml:
[tunable.win_glass]
path = "catalog/tunable/window_styles/glass.conf"Sometimes raw data is annoying to parse in every file. You can create a Global Setup Hook to clean up data before your components see it.
File: themes/cyber_punk/logic/derived.rs
use std::collections::HashMap;
pub fn calculate(raw_data: &HashMap<String, String>) -> HashMap<String, String> {
let mut derived = raw_data.clone();
// Simplify the time
if let Some(time) = raw_data.get("time_hour") {
let hour: i32 = time.parse().unwrap_or(12);
if hour > 20 || hour < 6 {
derived.insert("is_night".to_string(), "true".to_string());
} else {
derived.insert("is_night".to_string(), "false".to_string());
}
}
return derived;
}To compile your theme into a running daemon:
# 1. Select your profile (which points to your theme)
hypricer build --profile my_dev_profile
# 2. Watch the logs (Located in the 'live' folder)
tail -f ~/.config/hypr/hypricer/live/daemon.log