Extensible modules
The parts of slowshell extensible with plugins.
Surfaces are the obvious extensible parts, but they aren't the only ones. Slowshell has a handful of deeper sockets you can plug into... its command bus, compositor backends, the launcher, etc etc.
Payloads and commands
Payloads are slowshell's command bus: a named action, optionally carrying a typed value.
A single fixed command is just a type:
struct Greet;
impl Payload for Greet {
fn new(_ctx: &Context<'_>) -> Self { Self }
fn invoke(&mut self, ctx: &Context<'_>, args: &Args, _payload: ROption<&PayloadBox>) {
let name = args.get("name").unwrap_or("world");
ctx.notify(&Notification::new("Hello", format!("hello, {name}!")));
}
}
fn register(host: HostRef<'_>) {
host.payload::<Greet>("example.greet");
}
Which you can then fire from IPC(or dispatch) as such:
slowshell ipc exec example.greet name=you
Noe let's say you need a dynamic command such as a.b where .c is handled
within the same handler, you could do this:
host.payload_parser(&["demo.deploy."], |command, args| {
Some(DeployArgs {
kind: command.trim_start_matches("demo.deploy.").to_owned(),
id: args.get("id").unwrap_or("default").to_owned(),
})
});
And ofcourse, if that command should be triggerable from the spotlight command palette, You can register it as such:
host.command_with("example.greet", "Greet Someone", "Send a friendly notification");Compositors
The Compositor state is basically integration of slowshell with the window compositor that it's running in. This is one of the integral slowshell states.
impl Compositor for MyCompositor {
fn new(_ctx: &Context<'_>) -> Self { Self { state: CompositorState::default() } }
fn is_active(&self) -> bool {
// Pick me only when my session is around.
std::env::var("MY_COMPOSITOR_SOCKET").is_ok()
}
fn initialize(&mut self, ctx: &Context<'_>) {
let fd = connect_to_my_compositor();
ctx.host().compositor_register_fd(fd, 0);
}
fn update_state(&mut self, ctx: &Context<'_>) {
drain_socket(&mut self.state);
ctx.set_compositor_state(self.state.clone());
}
fn send_command(&mut self, _ctx: &Context<'_>, command: &CompositorCommand) {
match command {
CompositorCommand::FocusWorkspace(idx) => focus_workspace(*idx as u8),
CompositorCommand::FocusWindow(id) => focus_window(*id),
CompositorCommand::FocusMonitor(name) => focus_monitor(name.as_str()),
CompositorCommand::Custom(payload) => custom(payload),
}
}
}
fn register(host: HostRef<'_>) {
host.compositor::<MyCompositor>("my-compositor");
}Spotlight modes
impl Spotlight for Notes {
fn new(_ctx: &Context<'_>) -> Self { Self }
fn generate(&mut self, _ctx: &Context<'_>, query: &str) -> Vec<SpotlightResult> {
find_notes(query)
.map(|note| {
SpotlightResult::default()
.icon("note-symbolic")
.title(note.title)
.action_label("Open")
})
.collect()
}
fn activate(&mut self, _ctx: &Context<'_>, index: usize) { open_note(index); }
fn display_styles() -> &'static [DisplayStyle] {
&[DisplayStyle::List, DisplayStyle::Grid]
}
}
fn register(host: HostRef<'_>) {
host.spotlight::<Notes>("notes");
}slowshell ipc spotlight.toggle notes
slowshell list spotlightsQuick settings
impl QuickSettings for Vpn {
fn new(_ctx: &Context<'_>) -> Self { Self }
fn tiles(&self, _ctx: &Context<'_>) -> Vec<QuickSettingsTile> {
let connected = vpn_is_up();
vec![
QuickSettingsTile::new("example-vpn", "network-vpn-symbolic", "VPN")
.status(if connected { "Connected" } else { "Off" })
.active(connected)
.order(40)
.action(if connected { "example.vpn.disconnect" } else { "example.vpn.connect" }),
]
}
// tiles can expand into more tiles.
fn children(&self, _ctx: &Context<'_>, id: &str) -> Vec<QuickSettingsTile> {
if id == "example-vpn" {
vec![QuickSettingsTile::new("example-vpn-server", "network-server-symbolic", "Server")
.action("example.vpn.pick")]
} else {
Vec::new()
}
}
}
fn register(host: HostRef<'_>) {
host.quick_settings::<Vpn>("example-vpn");
}