Skip to content

Commit 210ef93

Browse files
synleclaude
andcommitted
feat: enforce singleton via tauri-plugin-single-instance (v6.4.3)
Add `tauri-plugin-single-instance` to prevent multiple copies of Display DJ from running at the same time. Without this, autostart racing a manual launch (or double-clicking the .app) could result in two tray icons and two competing sidecar processes. The plugin is registered first in the builder so the lock check happens before any sidecar spawn or tray setup; the duplicate process exits immediately and the running instance just logs the duplicate launch (the app is tray-only, so there is no main window to refocus). Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
1 parent 7baa345 commit 210ef93

5 files changed

Lines changed: 52 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ A cross-platform desktop system tray app for controlling monitor brightness, con
5050
- **Settings panel** -- tabbed UI (General + Tiling) with auto-save. Configure brightness, contrast, monitors, night mode, snap zones, exposé grid size, layout strategy, and launch at login
5151
- **About / Update check** -- "About Display DJ" in the tray menu shows current version, latest GitHub release, engine, platform, build date, and homepage. Green "Up to date" or orange "Update available" badge with download link. macOS section includes quarantine fix and Accessibility settings commands
5252
- **System tray app** -- lives in your menu bar / system tray with no dock or taskbar clutter
53+
- **Single-instance** -- only one copy of Display DJ runs at a time. If you accidentally launch it twice (e.g. autostart racing a manual launch), the duplicate exits silently so you never end up with multiple tray icons or competing sidecars
5354

5455
## Download & Install
5556

src-tauri/Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ tauri = { version = "2", features = ["tray-icon"] }
1818
tauri-plugin-autostart = "2"
1919
tauri-plugin-global-shortcut = "2"
2020
tauri-plugin-shell = "2"
21+
tauri-plugin-single-instance = "2"
2122
serde = { version = "1", features = ["derive"] }
2223
serde_json = "1"
2324
dirs = "6"

src-tauri/src/lib.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,24 @@ mod tests {
417417
assert!(!is_night_time(420, 420, 420));
418418
assert!(!is_night_time(420, 420, 1000));
419419
}
420+
421+
// -- single-instance plugin --
422+
423+
/// Verifies that `tauri_plugin_single_instance::init` is callable with the
424+
/// callback signature we use in `run()`. This guards against accidental
425+
/// removal of the dependency or signature drift in plugin upgrades. We
426+
/// build the plugin without registering it with a Tauri builder, which is
427+
/// safe because `init` itself doesn't spawn anything — the singleton lock
428+
/// is acquired only when the plugin is registered with `Builder::plugin`.
429+
#[test]
430+
fn test_single_instance_plugin_callback_signature() {
431+
let _plugin = tauri_plugin_single_instance::init(
432+
|_app: &tauri::AppHandle, _args: Vec<String>, _cwd: String| {
433+
// Callback body intentionally empty — we only verify the
434+
// signature compiles and the plugin can be constructed.
435+
},
436+
);
437+
}
420438
}
421439

422440
/// Main entry point: builds the Tauri app, spawns the display-dj sidecar,
@@ -427,6 +445,21 @@ pub fn run() {
427445
let preferences = config::load_preferences();
428446

429447
tauri::Builder::default()
448+
// Singleton enforcement: if a second copy of Display DJ is launched, the
449+
// plugin notifies this (the first) instance via the callback below and
450+
// the second process exits immediately. This prevents duplicate tray
451+
// icons and conflicting sidecar processes when the app is launched
452+
// multiple times (e.g. user double-clicks .app, autostart races a
453+
// manual launch, etc.). The callback receives the second instance's
454+
// CLI args + cwd; we only log them since the app is tray-only and has
455+
// no main window to refocus.
456+
.plugin(tauri_plugin_single_instance::init(|_app, args, cwd| {
457+
log::info!(
458+
"single-instance: ignored duplicate launch (args={:?}, cwd={:?})",
459+
args,
460+
cwd,
461+
);
462+
}))
430463
.plugin(tauri_plugin_shell::init())
431464
.plugin(tauri_plugin_dialog::init())
432465
.plugin(tauri_plugin_global_shortcut::Builder::new().build())

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/nicoverbruggen/tauri-v2-docs/refs/heads/main/schemas/config.schema.json",
33
"productName": "Display DJ",
4-
"version": "6.4.2",
4+
"version": "6.4.3",
55
"identifier": "com.synle.display-dj",
66
"build": {
77
"beforeDevCommand": "npm run dev",

0 commit comments

Comments
 (0)