How Clash Nyanpasu Implements Single-Instance Locking and Deep Link Handling

Clash Nyanpasu uses a file-based singleton lock with retry logic to ensure only one instance runs, while routing custom scheme URLs like clash:// or clash-nyanpasu:// to the primary instance via Tauri's deep-link plugin.

The libnyanpasu/clash-nyanpasu repository implements a robust cross-platform solution for single-instance enforcement and deep link activation. This architecture prevents multiple application windows from competing for system resources while ensuring that custom protocol invocations—such as importing configuration files via browser links—are always handled by the running primary instance.

Single-Instance Locking Mechanism

The application guarantees singleton behavior through a combination of platform-specific placeholder files and the single_instance crate. This mechanism resides primarily in backend/tauri/src/utils/init/mod.rs.

Platform-Specific Placeholder File

Before attempting to acquire the lock, the application generates a unique placeholder path via dirs::get_single_instance_placeholder(). This path identifies the current installation and serves as the mutex identifier for the SingleInstance struct.

Retry Logic and Primary Instance Detection

The check_singleton() function implements a resilient acquisition strategy with five retry attempts and one-second intervals between failures. If the lock cannot be obtained after the maximum retries, the function returns None, signaling that another instance is already running.

// backend/tauri/src/utils/init/mod.rs
pub fn check_singleton() -> Result<Option<single_instance::SingleInstance>> {
    let placeholder = super::dirs::get_single_instance_placeholder()?;
    for i in 0..5 {
        let instance = single_instance::SingleInstance::new(&placeholder)
            .context("failed to create single instance")?;
        if instance.is_single() {
            return Ok(Some(instance));   // This is the primary process
        }
        if i != 4 {
            std::thread::sleep(std::time::Duration::from_secs(1));
        }
    }
    Ok(None)   // Another instance is already running
}

Clash Nyanpasu registers the custom schemes clash-nyanpasu and clash to handle external activation requests. The implementation differs between macOS and other platforms, orchestrated in backend/tauri/src/lib.rs.

macOS Implementation

On macOS, the tauri-plugin-deep-link plugin automatically intercepts URL invocations at the system level. The application does not need to parse command-line arguments, as the operating system routes clash-nyanpasu:// URLs directly to the running instance through the plugin's native integration.

Windows and Linux Implementation

On non-macOS platforms, the application inspects the first command-line argument (std::env::args().nth(1)) to detect custom scheme URLs. If a valid URL is detected, it is parsed into an Option<url::Url> and stored as custom_scheme. When a custom scheme is present, the standard CLI parsing via cmds::parse() is bypassed entirely.

// backend/tauri/src/lib.rs
#[cfg(not(target_os = "macos"))]
let custom_scheme = match std::env::args().nth(1) {
    Some(url) => url::Url::parse(&url).ok(),
    None => None,
};

After acquiring the singleton lock, the application prepares and registers the deep-link plugin. The preparation step uses different bundle identifiers for development and release builds to prevent conflicts. The registration specifies the supported schemes (clash-nyanpasu and clash) and defines a callback that ensures the main window exists before emitting a Tauri event.

// backend/tauri/src/lib.rs
// Preparation with environment-specific identifier
#[cfg(feature = "verge-dev")]
tauri_plugin_deep_link::prepare("moe.elaina.clash.nyanpasu.dev");

#[cfg(not(feature = "verge-dev"))]
tauri_plugin_deep_link::prepare("moe.elaina.clash.nyanpasu");

// Registration after singleton lock acquisition
log_err!(tauri_plugin_deep_link::register(
    &["clash-nyanpasu", "clash"],
    move |request| {
        log::info!(target: "app", "scheme request received: {:?}", &request);
        // Ensure window exists
        resolve::create_window(&handle.clone());
        while !is_window_opened() {
            std::thread::sleep(std::time::Duration::from_millis(100));
        }
        // Forward to the frontend
        handle.emit("scheme-request-received", request).unwrap();
    }
));

Frontend Integration

The frontend subscribes to the scheme-request-received event using Tauri's event API. When the backend emits this event—triggered by either a macOS system invocation or a Windows/Linux deep-link activation—the frontend receives the URL payload and can execute application-specific logic, such as importing a remote configuration file.

import { listen } from '@tauri-apps/api/event';

listen('scheme-request-received', event => {
  const url = event.payload; // e.g. "clash://import?url=https://example.com/config.yaml"
  // Parse and act on the URL (e.g., import a config file)
});

Summary

  • Single-instance enforcement uses a file-based lock with retry logic in backend/tauri/src/utils/init/mod.rs, ensuring only one primary process runs while secondary invocations exit cleanly.
  • Deep-link activation supports the custom schemes clash-nyanpasu and clash, with macOS using native plugin integration and Windows/Linux parsing CLI arguments.
  • Cross-platform routing ensures that custom scheme requests always reach the primary instance, which emits a scheme-request-received event for frontend handling.
  • Development vs. release builds use distinct bundle identifiers (moe.elaina.clash.nyanpasu.dev vs. moe.elaina.clash.nyanpasu) to prevent conflicts during testing.

Frequently Asked Questions

How does Clash Nyanpasu prevent multiple windows from opening?

The application implements a singleton pattern using the single_instance crate in backend/tauri/src/utils/init/mod.rs. It attempts to acquire a platform-specific file lock up to five times with one-second intervals. If another instance holds the lock, the new process exits immediately, ensuring only one primary instance manages the application window.

What custom URL schemes does Clash Nyanpasu support?

Clash Nyanpasu registers two custom schemes: clash-nyanpasu and clash. These schemes allow external applications or browser links to trigger actions within the running instance, such as importing configuration files. The schemes are registered in backend/tauri/src/lib.rs using the tauri-plugin-deep-link plugin.

On macOS, the tauri-plugin-deep-link plugin automatically intercepts custom scheme URLs at the system level without requiring command-line parsing. On Windows and Linux, the application checks the first command-line argument (std::env::args().nth(1)) for a URL pattern. If detected, the URL is parsed and routed to the primary instance, bypassing standard CLI argument processing.

When a deep link is received, the registered handler in backend/tauri/src/lib.rs ensures the main window exists (creating it if necessary), waits for the window to fully open, and then emits a Tauri event named scheme-request-received containing the URL payload. The frontend listens for this event and processes the request accordingly, such as importing a remote configuration file.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →