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

Common Development Tasks

Adding a New Icon

  1. Find the Unicode codepoint from the Nerd Fonts cheat sheet.
  2. Add a variant to the StaticIcon enum in src/components/icons.rs and map it to its codepoint in StaticIcon::get_str():
    #![allow(unused)]
    fn main() {
    StaticIcon::MyNewIcon => "\u{f0001}",
    }
  3. Build — build.rs automatically subsets the font to include the new glyph.

Adding a New Config Option

  1. Add the field to the relevant config struct in src/config.rs:

    #![allow(unused)]
    fn main() {
    #[derive(Deserialize, Clone, Debug)]
    #[serde(default)]
    pub struct MyModuleConfig {
        pub new_option: bool,  // Add this
    }
    }
  2. Set a default value in the Default impl:

    #![allow(unused)]
    fn main() {
    impl Default for MyModuleConfig {
        fn default() -> Self {
            Self {
                new_option: false,
            }
        }
    }
    }
  3. Use the option in your module.

  4. If the option should be hot-reloadable, handle it in the module’s ConfigReloaded message.

Adding a D-Bus Integration

  1. Create proxy definitions in a dbus.rs file:

    #![allow(unused)]
    fn main() {
    #[zbus::proxy(
        interface = "org.example.Service1",
        default_service = "org.example.Service",
    )]
    trait Service1 {
        #[zbus(property)]
        fn my_property(&self) -> zbus::Result<String>;
    }
    }
  2. Implement the ReadOnlyService or Service trait.

  3. Subscribe from a module. See Writing a New Service.

Working with the Theme

To add a new style or modify existing styles, edit src/theme.rs:

#![allow(unused)]
fn main() {
// Add a new button style method
pub fn my_button_style(&self) -> impl Fn(&Theme, Status) -> button::Style {
    let opacity = self.opacity;
    move |theme, status| {
        // Return button::Style based on status and theme
    }
}
}

Adding a Translation

ashell uses Fluent via the i18n-embed crate. Catalogs live in i18n/<lang-tag>/ashell.ftl and are baked into the binary at compile time with include_str!.

  1. Copy the seed catalog to a new BCP-47 directory (e.g. fr-FR, de-DE, ru-RU, uk-UA, it-IT):

    mkdir -p i18n/<lang-tag>
    cp i18n/en-US/ashell.ftl i18n/<lang-tag>/ashell.ftl
    
  2. Translate the right-hand side of every entry. Preserve:

    • the message keys and ## Section comments,
    • every { $variable } placeholder (e.g. { $count }, { $ssid }, { $duration }),
    • Fluent selector keys: state selectors [on]/*[off] and plural selectors like [one]/*[other]. The bracketed identifiers are Fluent literals — only the messages on the right-hand side get translated. Use the CLDR plural categories that apply to your language; *[other] is the required default.
    • A message that takes { $count } may need a plural selector in your language even when the en-US entry has none (e.g. notifications-group-count: French needs “1 nouvelle” / “2 nouvelles”).
  3. Check the wording against established translations instead of translating each string in isolation. Users of a status bar already know the terms from their desktop, so match them:

    • Desktop terms (power actions, power profiles, battery states, Bluetooth pairing, idle inhibition, system monitor labels): look up the same English string in the GNOME and KDE Plasma translations for your language. GNOME .po files are on l10n.gnome.org and in each project’s po/ directory (gnome-shell, gnome-control-center, gnome-bluetooth, gnome-system-monitor). KDE catalogs are on l10n.kde.org, or in your distribution’s packages (powerdevil, plasma-workspace, bluedevil, libksysguard) as .mo files you can turn back into text with msgunfmt. Pay attention to term pairs: Suspend and Hibernate must not end up with each other’s usual names.
    • Weather conditions (the weather-* keys follow the WMO codes used by Open-Meteo): compare with libgweather and with the wording of a major weather service or site in your language. Prefer how they phrase intensities (“light”, “moderate”, “heavy”) and “feels like”.
    • If GNOME and KDE disagree, either is fine; say which one you followed in the PR description.
  4. Register the catalog in the CATALOGS slice in src/i18n.rs:

    #![allow(unused)]
    fn main() {
    const CATALOGS: &[(&str, &str)] = &[
        ("en-US", include_str!("../i18n/en-US/ashell.ftl")),
        ("<lang-tag>", include_str!("../i18n/<lang-tag>/ashell.ftl")),
    ];
    }
  5. Test by setting language = "<lang-tag>" in ~/.config/ashell/config.toml, or by launching with LC_MESSAGES=<lang-tag> ./target/release/ashell. POSIX locale names are normalized to BCP-47 automatically.

  6. Run make check before pushing.

Running Checks Before Committing

Always run the full check before pushing:

make check

This runs cargo fmt, cargo check --workspace, and cargo clippy --workspace -- -D warnings.

Debugging a Specific Module

To see debug output for a specific module:

# In config.toml
[logging]
level = "warn,ashell::modules::my_module=debug"

Testing Config Hot-Reload

  1. Start ashell: make start
  2. Edit ~/.config/ashell/config.toml in another terminal
  3. Save — changes should appear immediately
  4. Check logs if changes don’t apply: tail -f $XDG_RUNTIME_DIR/ashell_rCURRENT.log