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

IPC

IPC lets other programs talk to your running shell. Its main use is keybinds: your compositor runs amane ipc call toggle-launcher, and your shell opens the launcher.

Toggling a window from the command line

use std::time::Duration;

use amane::{App, LayerWindow, Parent, Rectangle, Service};

struct Launcher {
    open: bool,
}

impl Service for Launcher {
    fn new() -> Self {
        Self { open: false }
    }

    fn interval() -> Duration {
        Duration::from_secs(3600)
    }
}

fn main() {
    App::new().ipc("toggle-launcher", toggle).window(view).run();
}

fn view() -> LayerWindow {
    let launcher = Launcher::read();

    LayerWindow::new()
        .width(400.0)
        .height(300.0)
        .visible(launcher.open)
        .child(Rectangle::new().width(Parent).height(Parent).fill("#1e1e2e"))
}

fn toggle(_: &[String]) -> String {
    let mut launcher = Launcher::write();

    launcher.open = !launcher.open;

    if launcher.open {
        String::from("opened")
    } else {
        String::from("closed")
    }
}

From a terminal:

amane ipc call toggle-launcher

It prints opened, and the launcher appears. Run it again to close it.

How a call reaches your shell

  • App::ipc(name, handler) registers a handler under a name. Call it once per name.
  • Your shell listens on a socket, $XDG_RUNTIME_DIR/amane.sock.
  • amane ipc call <name> [arguments...] connects to that socket, sends the name and arguments, and prints the reply.
  • The handler gets the arguments and returns a String. Whatever it returns is printed by amane ipc call.
  • The handler writes to a Service, so every window that reads it redraws (State and Services).

The handler runs on the same thread that draws, so keep it quick. For slow work, start a thread or use amane::spawn.

Passing arguments

Everything after the name is passed to the handler as a list of strings:

fn main() {
    App::new().ipc("say", say).window(view).run();
}

fn say(arguments: &[String]) -> String {
    let words = arguments.join(" ");

    Message::write().text = words.clone();

    format!("showing: {words}")
}
amane ipc call say hello world

The handler gets ["hello", "world"]. Arguments can’t contain newlines.

Calling a name that has no handler answers no handler named <name>.

Binding calls to keys

niri:

binds {
    Mod+Space { spawn "amane" "ipc" "call" "toggle-launcher"; }
}

Hyprland:

bind = SUPER, SPACE, exec, amane ipc call toggle-launcher

Sway:

bindsym $mod+space exec amane ipc call toggle-launcher

One shell at a time

There’s one socket per session, so only one Amane shell can run at a time. Starting a second one stops it right away with failed to listen: amane is already running. amane dev restarts its own shell without hitting this, but it can’t stop a shell started some other way. Stop your everyday shell (the one from amane run) before running amane dev.

If amane ipc call says amane is not running, no shell is running in this session.