Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The App Struct

The App struct in src/app.rs is the central state container for the entire application. It owns all module instances, the general configuration, and the output/surface management.

Fields

#![allow(unused)]
fn main() {
pub struct App {
    config_path: PathBuf,               // Path to the TOML config file
    logger: LoggerHandle,               // flexi_logger handle for runtime log level changes
    pub general_config: GeneralConfig,  // Extracted config subset (outputs, modules, layer)
    pub outputs: Outputs,               // Multi-monitor surface management

    // Module instances
    pub custom: HashMap<String, Custom>,     // User-defined custom modules
    pub updates: Option<Updates>,            // Package update checker (optional)
    pub workspaces: Workspaces,              // Workspace indicators
    pub window_title: WindowTitle,           // Active window display
    pub system_info: SystemInfo,             // CPU/RAM/disk/network stats
    pub keyboard_layout: KeyboardLayout,     // Keyboard layout indicator
    pub keyboard_submap: KeyboardSubmap,     // Hyprland submap display
    pub tray: TrayModule,                    // System tray
    pub tempo: Tempo,                        // Clock/calendar/weather
    pub privacy: Privacy,                    // Mic/camera/screenshare indicators
    pub settings: Settings,                  // Settings panel
    pub media_player: MediaPlayer,           // MPRIS media control
    pub notifications: Notifications,        // Notification center and toasts
    pub osd: Osd,                            // On-screen display overlay
}
}

The theme is not stored on App: it lives in a thread-local in src/theme.rs, set with init_theme() and read with use_theme(). Bar visibility (toggled via SIGUSR1 or IPC) is tracked by Outputs, not by App.

GeneralConfig

A subset of the config used at the App level:

#![allow(unused)]
fn main() {
pub struct GeneralConfig {
    outputs: config::Outputs,     // Which monitors to show the bar on
    pub modules: Modules,         // Left/center/right module layout
    pub layer: config::Layer,     // Wayland layer (Top/Bottom/Overlay)
    enable_esc_key: bool,         // Whether ESC closes menus
}
}

Initialization

App::new() returns a closure that produces the initial state and a startup task:

#![allow(unused)]
fn main() {
pub fn new(
    (logger, config, config_path): (LoggerHandle, Config, PathBuf),
) -> impl FnOnce() -> (Self, Task<Message>) {
    move || {
        let mut outputs = Outputs::new(/* bar layout, position, layer, scale_factor */);

        // Initialize all modules from config
        let custom = config.custom_modules.clone().into_iter()
            .map(|o| (o.name.clone(), Custom::new(o)))
            .collect();

        init_theme(AshellTheme::new(config.position, &config.appearance, &config.animations));
        init_localizer(resolve_localizer(&config));

        // ...
        (App { /* all fields */ }, warm_icons)
    }
}
}

The startup task warms the XDG icon cache when the tray or icon-based workspace indicators are in use.

Config Hot-Reload

When the config file changes, the Message::ConfigChanged handler in App::update() re-syncs outputs if needed, updates the logger level, and calls App::refresh_config() to propagate changes to all modules:

#![allow(unused)]
fn main() {
// In App::update(), Message::ConfigChanged(config):
if /* outputs, position, bar layout, scale factor or layer changed */ {
    tasks.push(self.outputs.sync(/* ... */)); // may create/destroy surfaces
}
self.logger.set_new_spec(get_log_spec(&config.logging.level));
tasks.push(self.refresh_config(config));

fn refresh_config(&mut self, config: Box<Config>) -> Task<Message> {
    // Update theme and localizer
    init_theme(AshellTheme::new(config.position, &config.appearance, &config.animations));
    init_localizer(resolve_localizer(&config));

    // Update general config
    self.general_config = GeneralConfig { /* ... */ };

    // Propagate to each module via ConfigReloaded messages
    self.workspaces.update(modules::workspaces::Message::ConfigReloaded(config.workspaces));
    self.settings.update(modules::settings::Message::ConfigReloaded(Box::new(config.settings)));
    // ... and so on for each module
}
}

This enables live editing of the config file without restarting ashell. Note that only logging.level is re-applied on reload: logging.target and logging.directory are read once at startup by config::read_logging_config() and need a restart.