OFFSET偏移
← Back to the constellation

PROJECT 01 / Desktop utility

shell-switcher

Handover first.
Then switch.

A sequenced handover for stopping, starting, and checking the state of desktop shells.

THE QUESTION

Multiple desktop shells can draw panels at the same time and compete for the notification service. When stopping the old service and starting the new one by hand, you also need to check their states. A command returning is not enough.

One switch. Three paths.

Source-based flow diagram · No system commands are run
Choose a switching scenario

Success: hand over, then save

01Stop registered services
02Start the target service
03Confirm active · Attempt to save

Stop all registered services and confirm that they no longer report active. Start the target, poll until it reports active, then attempt to save the selection. If the target is already the only active service, only attempt to update the saved selection.

View this branch of the flow

Checks depend on whether systemd reports active. Saving the selection and restoring the default service can both fail.

01 / PROBLEM

Problem#

Multiple desktop shells can draw panels at the same time and compete for the notification service. When stopping the old service and starting the new one by hand, you also need to check their states. A command returning is not enough.

02 / STRUCTURE

Structure#

The program reads a mapping of names to systemd user services from TOML and identifies the session through environment variables. If the target is already the only active service, it only attempts to update the saved selection. Otherwise, it stops all registered services, checks that none still reports active, starts the target, waits for active, and finally attempts to save the selection. The boot entry point chooses a valid saved selection or the default service and issues a start command, without the same stop-and-poll sequence.

src/main.rs · RUSTL283–L296
    // 确认真的起来了(systemd 可能还在 activating)
    let deadline = Instant::now() + START_TIMEOUT;
    loop {
        if is_active(&target_entry.service) {
            println!("{}", t!("switched", target = target));
            write_current(target);
            return ExitCode::SUCCESS;
        }
        if Instant::now() >= deadline {
            eprintln!("{}", t!("start_timeout", target = target));
            return ExitCode::from(1);
        }
        sleep(Duration::from_millis(200));
    }

After the command returns, keep checking for active. The timeout branch only returns an error.

03 / TRADEOFF

Trade-offs#

systemd manages the processes; the Rust program handles sequencing and state checks. Stopping before starting reduces deliberate overlap, at the cost of a brief gap in the interface. Only Hyprland and niri are currently recognized, and session checks and fixed timeouts further limit where the tool applies.

04 / RESULT

Outcome#

The source provides entry points for state queries, idempotent switching, and startup from a saved selection. If stop confirmation times out or the command to start the target fails, the program attempts to start the default service and returns an error, without checking whether recovery succeeded. If waiting for the target to become active times out, it returns an error directly, without that recovery attempt.

05 / SOURCE

Source & evidence#

These notes refer to revision 1668dbdc42. They do not establish results for every runtime environment.

NEXT ORBIT / NEXT PROJECTMellow × Matugen

Beyond color,there is structure.