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

> Discover how Clash Nyanpasu ensures single-instance operation with file-based locking and seamlessly handles deep links and custom schemes using Tauri.

- Repository: [Nyanpasu/clash-nyanpasu](https://github.com/libnyanpasu/clash-nyanpasu)
- Tags: internals
- Published: 2026-03-06

---

**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`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/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.

```rust
// 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
}

```

## Deep Link and Custom Scheme Handling

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`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/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.

```rust
// 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,
};

```

### Registering the Deep-Link Handler

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.

```rust
// 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.

```javascript
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`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/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`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/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`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/backend/tauri/src/lib.rs) using the `tauri-plugin-deep-link` plugin.

### How are deep links handled differently on macOS versus Windows and Linux?

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.

### What happens when a deep link is received while the application is already running?

When a deep link is received, the registered handler in [`backend/tauri/src/lib.rs`](https://github.com/libnyanpasu/clash-nyanpasu/blob/main/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.