@neuralmindlabs/tokn 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/.github/workflows/ci.yml +22 -0
  2. package/.oxlintrc.json +8 -0
  3. package/LICENSE +21 -0
  4. package/README.md +71 -0
  5. package/dist/assets/index-Dq74RfU-.css +1 -0
  6. package/dist/assets/index-_9e8Z58Z.js +66 -0
  7. package/dist/favicon.svg +1 -0
  8. package/dist/icons.svg +24 -0
  9. package/dist/index.html +14 -0
  10. package/index.html +13 -0
  11. package/package.json +45 -0
  12. package/packages/tokn/LICENSE +21 -0
  13. package/packages/tokn/README.md +85 -0
  14. package/packages/tokn/package.json +56 -0
  15. package/packages/tokn/src/aggregate.ts +83 -0
  16. package/packages/tokn/src/cli.ts +181 -0
  17. package/packages/tokn/src/cursor.ts +85 -0
  18. package/packages/tokn/src/index.ts +6 -0
  19. package/packages/tokn/src/node.ts +50 -0
  20. package/packages/tokn/src/parsers/claude.ts +53 -0
  21. package/packages/tokn/src/parsers/codex.ts +74 -0
  22. package/packages/tokn/src/pricing/index.ts +106 -0
  23. package/packages/tokn/src/pricing/prices.json +948 -0
  24. package/packages/tokn/src/pricing/prices.ts +947 -0
  25. package/packages/tokn/src/types.ts +51 -0
  26. package/packages/tokn/tests/__snapshots__/eval.test.ts.snap +12 -0
  27. package/packages/tokn/tests/aggregate.test.ts +73 -0
  28. package/packages/tokn/tests/claude.test.ts +87 -0
  29. package/packages/tokn/tests/codex.test.ts +54 -0
  30. package/packages/tokn/tests/cursor.test.ts +126 -0
  31. package/packages/tokn/tests/eval.test.ts +69 -0
  32. package/packages/tokn/tests/fixtures/claude-session.jsonl +3 -0
  33. package/packages/tokn/tests/fixtures/codex-session.jsonl +5 -0
  34. package/packages/tokn/tests/pricing.test.ts +137 -0
  35. package/packages/tokn/tsconfig.json +21 -0
  36. package/public/favicon.svg +1 -0
  37. package/public/icons.svg +24 -0
  38. package/scripts/validate.ts +122 -0
  39. package/src/App.tsx +12 -0
  40. package/src/assets/hero.png +0 -0
  41. package/src/assets/vite.svg +1 -0
  42. package/src/index.css +250 -0
  43. package/src/lib/refresh.ts +134 -0
  44. package/src/lib/scanner.ts +108 -0
  45. package/src/main.tsx +7 -0
  46. package/src/ui/Dashboard.tsx +203 -0
  47. package/src/ui/Popover.tsx +91 -0
  48. package/src/ui/Settings.tsx +114 -0
  49. package/src/ui/useAppData.ts +71 -0
  50. package/src-tauri/Cargo.lock +5972 -0
  51. package/src-tauri/Cargo.toml +35 -0
  52. package/src-tauri/build.rs +3 -0
  53. package/src-tauri/capabilities/default.json +114 -0
  54. package/src-tauri/icons/128x128.png +0 -0
  55. package/src-tauri/icons/128x128@2x.png +0 -0
  56. package/src-tauri/icons/32x32.png +0 -0
  57. package/src-tauri/icons/64x64.png +0 -0
  58. package/src-tauri/icons/Square107x107Logo.png +0 -0
  59. package/src-tauri/icons/Square142x142Logo.png +0 -0
  60. package/src-tauri/icons/Square150x150Logo.png +0 -0
  61. package/src-tauri/icons/Square284x284Logo.png +0 -0
  62. package/src-tauri/icons/Square30x30Logo.png +0 -0
  63. package/src-tauri/icons/Square310x310Logo.png +0 -0
  64. package/src-tauri/icons/Square44x44Logo.png +0 -0
  65. package/src-tauri/icons/Square71x71Logo.png +0 -0
  66. package/src-tauri/icons/Square89x89Logo.png +0 -0
  67. package/src-tauri/icons/StoreLogo.png +0 -0
  68. package/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  69. package/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png +0 -0
  70. package/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  71. package/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png +0 -0
  72. package/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png +0 -0
  73. package/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  74. package/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png +0 -0
  75. package/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png +0 -0
  76. package/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  77. package/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png +0 -0
  78. package/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png +0 -0
  79. package/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  80. package/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  81. package/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png +0 -0
  82. package/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  83. package/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  84. package/src-tauri/icons/android/values/ic_launcher_background.xml +4 -0
  85. package/src-tauri/icons/icon.icns +0 -0
  86. package/src-tauri/icons/icon.ico +0 -0
  87. package/src-tauri/icons/icon.png +0 -0
  88. package/src-tauri/icons/ios/AppIcon-20x20@1x.png +0 -0
  89. package/src-tauri/icons/ios/AppIcon-20x20@2x-1.png +0 -0
  90. package/src-tauri/icons/ios/AppIcon-20x20@2x.png +0 -0
  91. package/src-tauri/icons/ios/AppIcon-20x20@3x.png +0 -0
  92. package/src-tauri/icons/ios/AppIcon-29x29@1x.png +0 -0
  93. package/src-tauri/icons/ios/AppIcon-29x29@2x-1.png +0 -0
  94. package/src-tauri/icons/ios/AppIcon-29x29@2x.png +0 -0
  95. package/src-tauri/icons/ios/AppIcon-29x29@3x.png +0 -0
  96. package/src-tauri/icons/ios/AppIcon-40x40@1x.png +0 -0
  97. package/src-tauri/icons/ios/AppIcon-40x40@2x-1.png +0 -0
  98. package/src-tauri/icons/ios/AppIcon-40x40@2x.png +0 -0
  99. package/src-tauri/icons/ios/AppIcon-40x40@3x.png +0 -0
  100. package/src-tauri/icons/ios/AppIcon-512@2x.png +0 -0
  101. package/src-tauri/icons/ios/AppIcon-60x60@2x.png +0 -0
  102. package/src-tauri/icons/ios/AppIcon-60x60@3x.png +0 -0
  103. package/src-tauri/icons/ios/AppIcon-76x76@1x.png +0 -0
  104. package/src-tauri/icons/ios/AppIcon-76x76@2x.png +0 -0
  105. package/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png +0 -0
  106. package/src-tauri/icons/source/app-icon-1024.png +0 -0
  107. package/src-tauri/icons/source/app-icon.svg +74 -0
  108. package/src-tauri/icons/source/tray-22.png +0 -0
  109. package/src-tauri/icons/source/tray-44.png +0 -0
  110. package/src-tauri/icons/source/tray-icon.svg +14 -0
  111. package/src-tauri/permissions/allow-custom-commands.toml +11 -0
  112. package/src-tauri/src/lib.rs +238 -0
  113. package/src-tauri/src/main.rs +6 -0
  114. package/src-tauri/tauri.conf.json +57 -0
  115. package/tsconfig.app.json +27 -0
  116. package/tsconfig.json +7 -0
  117. package/tsconfig.node.json +23 -0
  118. package/vite.config.ts +7 -0
  119. package/vitest.config.ts +28 -0
@@ -0,0 +1,35 @@
1
+ [package]
2
+ name = "app"
3
+ version = "0.1.0"
4
+ description = "A Tauri App"
5
+ authors = ["you"]
6
+ license = ""
7
+ repository = ""
8
+ edition = "2021"
9
+ rust-version = "1.77.2"
10
+
11
+ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12
+
13
+ [lib]
14
+ name = "app_lib"
15
+ crate-type = ["staticlib", "cdylib", "rlib"]
16
+
17
+ [build-dependencies]
18
+ tauri-build = { version = "2.6.3", features = [] }
19
+
20
+ [dependencies]
21
+ serde_json = "1.0"
22
+ serde = { version = "1.0", features = ["derive"] }
23
+ log = "0.4"
24
+ tauri = { version = "2.11.3", features = ["tray-icon", "image-png", "macos-private-api"] }
25
+ tauri-plugin-log = "2"
26
+ tauri-plugin-fs = "2.5.1"
27
+ tauri-plugin-http = "2.5.9"
28
+ tauri-plugin-store = "2.4.3"
29
+ tauri-plugin-autostart = "2.5.1"
30
+ tauri-plugin-opener = "2.5.4"
31
+
32
+ [target.'cfg(target_os = "macos")'.dependencies]
33
+ cocoa = "0.25"
34
+ objc = "0.2"
35
+ tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2.1" }
@@ -0,0 +1,3 @@
1
+ fn main() {
2
+ tauri_build::build()
3
+ }
@@ -0,0 +1,114 @@
1
+ {
2
+ "$schema": "../gen/schemas/desktop-schema.json",
3
+ "identifier": "default",
4
+ "description": "Popover and dashboard windows: read AI tool logs, call Cursor API, persist settings",
5
+ "windows": [
6
+ "popover",
7
+ "dashboard"
8
+ ],
9
+ "permissions": [
10
+ "core:default",
11
+ "core:window:allow-show",
12
+ "core:window:allow-hide",
13
+ "core:window:allow-set-focus",
14
+ "core:window:allow-set-position",
15
+ "core:window:allow-outer-size",
16
+ {
17
+ "identifier": "fs:allow-read-text-file",
18
+ "allow": [
19
+ {
20
+ "path": "$HOME/.claude/**"
21
+ },
22
+ {
23
+ "path": "$HOME/.codex/**"
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "identifier": "fs:allow-read-text-file-lines",
29
+ "allow": [
30
+ {
31
+ "path": "$HOME/.claude/**"
32
+ },
33
+ {
34
+ "path": "$HOME/.codex/**"
35
+ }
36
+ ]
37
+ },
38
+ {
39
+ "identifier": "fs:allow-read-dir",
40
+ "allow": [
41
+ {
42
+ "path": "$HOME/.claude"
43
+ },
44
+ {
45
+ "path": "$HOME/.claude/**"
46
+ },
47
+ {
48
+ "path": "$HOME/.codex"
49
+ },
50
+ {
51
+ "path": "$HOME/.codex/**"
52
+ }
53
+ ]
54
+ },
55
+ {
56
+ "identifier": "fs:allow-stat",
57
+ "allow": [
58
+ {
59
+ "path": "$HOME/.claude/**"
60
+ },
61
+ {
62
+ "path": "$HOME/.codex/**"
63
+ }
64
+ ]
65
+ },
66
+ {
67
+ "identifier": "fs:allow-exists",
68
+ "allow": [
69
+ {
70
+ "path": "$HOME/.claude"
71
+ },
72
+ {
73
+ "path": "$HOME/.claude/**"
74
+ },
75
+ {
76
+ "path": "$HOME/.codex"
77
+ },
78
+ {
79
+ "path": "$HOME/.codex/**"
80
+ }
81
+ ]
82
+ },
83
+ {
84
+ "identifier": "http:default",
85
+ "allow": [
86
+ {
87
+ "url": "https://cursor.com/*"
88
+ },
89
+ {
90
+ "url": "https://*.cursor.com/*"
91
+ },
92
+ {
93
+ "url": "https://raw.githubusercontent.com/*"
94
+ }
95
+ ]
96
+ },
97
+ "store:default",
98
+ "autostart:allow-enable",
99
+ "autostart:allow-disable",
100
+ "autostart:allow-is-enabled",
101
+ "allow-custom-commands",
102
+ {
103
+ "identifier": "opener:allow-open-url",
104
+ "allow": [
105
+ {
106
+ "url": "https://cursor.com/*"
107
+ },
108
+ {
109
+ "url": "https://www.cursor.com/*"
110
+ }
111
+ ]
112
+ }
113
+ ]
114
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
4
+ <background android:drawable="@color/ic_launcher_background"/>
5
+ </adaptive-icon>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <resources>
3
+ <color name="ic_launcher_background">#fff</color>
4
+ </resources>
Binary file
Binary file
Binary file
@@ -0,0 +1,74 @@
1
+ <svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="face" x1="0.15" y1="0.05" x2="0.85" y2="0.95">
4
+ <stop offset="0" stop-color="#9a6bff"/>
5
+ <stop offset="0.45" stop-color="#6f6cfb"/>
6
+ <stop offset="1" stop-color="#3c7cff"/>
7
+ </linearGradient>
8
+ <linearGradient id="edge" x1="0" y1="0" x2="0" y2="1">
9
+ <stop offset="0" stop-color="#4a58c8"/>
10
+ <stop offset="1" stop-color="#2e4bd6"/>
11
+ </linearGradient>
12
+ <radialGradient id="glow" cx="0.5" cy="0.42" r="0.65">
13
+ <stop offset="0" stop-color="#3c7cff" stop-opacity="0.35"/>
14
+ <stop offset="0.6" stop-color="#3c7cff" stop-opacity="0.08"/>
15
+ <stop offset="1" stop-color="#3c7cff" stop-opacity="0"/>
16
+ </radialGradient>
17
+ <linearGradient id="rim" x1="0" y1="0" x2="1" y2="1">
18
+ <stop offset="0" stop-color="#ffffff" stop-opacity="0.5"/>
19
+ <stop offset="1" stop-color="#ffffff" stop-opacity="0.06"/>
20
+ </linearGradient>
21
+ </defs>
22
+
23
+ <!-- squircle canvas -->
24
+ <rect x="64" y="64" width="896" height="896" rx="200" fill="#0f1821"/>
25
+ <rect x="64" y="64" width="896" height="896" rx="200" fill="url(#glow)"/>
26
+
27
+ <!-- coin, tilted -->
28
+ <g transform="rotate(-14 512 512)">
29
+ <!-- edge (thickness) -->
30
+ <ellipse cx="500" cy="548" rx="308" ry="300" fill="url(#edge)"/>
31
+ <!-- ridges hint on the lower edge -->
32
+ <g stroke="#20347f" stroke-width="10" opacity="0.55">
33
+ <line x1="268" y1="700" x2="252" y2="672"/>
34
+ <line x1="330" y1="756" x2="318" y2="726"/>
35
+ <line x1="402" y1="796" x2="394" y2="764"/>
36
+ <line x1="482" y1="816" x2="478" y2="782"/>
37
+ <line x1="562" y1="812" x2="562" y2="778"/>
38
+ <line x1="640" y1="786" x2="644" y2="754"/>
39
+ <line x1="708" y1="740" x2="716" y2="712"/>
40
+ </g>
41
+ <!-- face -->
42
+ <circle cx="512" cy="500" r="304" fill="url(#face)"/>
43
+ <!-- inner ring -->
44
+ <circle cx="512" cy="500" r="304" fill="none" stroke="url(#rim)" stroke-width="10"/>
45
+ <circle cx="512" cy="500" r="252" fill="none" stroke="#ffffff" stroke-opacity="0.16" stroke-width="6"/>
46
+
47
+ <!-- T glyph: slanted top bar + stem -->
48
+ <g fill="#ffffff">
49
+ <path d="M 360 388
50
+ L 664 360
51
+ Q 686 358 682 380
52
+ L 674 424
53
+ Q 671 442 652 444
54
+ L 380 468
55
+ Q 356 470 358 448
56
+ Z"/>
57
+ <path d="M 490 486
58
+ L 586 478
59
+ L 548 664
60
+ Q 540 700 504 704
61
+ L 462 708
62
+ Q 442 710 446 690
63
+ Z"/>
64
+ </g>
65
+ <!-- pixel dots trailing off the bar -->
66
+ <g fill="#ffffff">
67
+ <rect x="596" y="500" width="34" height="34" rx="6" opacity="0.9"/>
68
+ <rect x="646" y="500" width="34" height="34" rx="6" opacity="0.45"/>
69
+ <rect x="596" y="550" width="34" height="34" rx="6" opacity="0.6"/>
70
+ <rect x="646" y="550" width="34" height="34" rx="6" opacity="0.25"/>
71
+ <rect x="596" y="600" width="34" height="34" rx="6" opacity="0.3"/>
72
+ </g>
73
+ </g>
74
+ </svg>
@@ -0,0 +1,14 @@
1
+ <svg width="44" height="44" viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg">
2
+ <!-- menu bar template icon: pure black + alpha, macOS recolors it -->
3
+ <g transform="rotate(-12 22 22)">
4
+ <circle cx="22" cy="22" r="18" fill="none" stroke="#000000" stroke-width="3.4"/>
5
+ <!-- slanted T bar -->
6
+ <path fill="#000000" d="M 13.2 15.6 L 30.6 14.0 Q 31.9 13.9 31.6 15.2 L 31.2 17.4 Q 31.0 18.4 29.9 18.5 L 14.4 19.9 Q 13.0 20.0 13.1 18.8 Z"/>
7
+ <!-- stem -->
8
+ <path fill="#000000" d="M 20.2 21.4 L 25.4 21.0 L 23.4 30.4 Q 23.0 32.2 21.2 32.4 L 19.4 32.6 Q 18.3 32.7 18.5 31.6 Z"/>
9
+ <!-- dots -->
10
+ <rect x="26.4" y="21.8" width="2.6" height="2.6" rx="0.6" fill="#000000"/>
11
+ <rect x="29.8" y="21.8" width="2.6" height="2.6" rx="0.6" fill="#000000" opacity="0.55"/>
12
+ <rect x="26.4" y="25.2" width="2.6" height="2.6" rx="0.6" fill="#000000" opacity="0.55"/>
13
+ </g>
14
+ </svg>
@@ -0,0 +1,11 @@
1
+ [[permission]]
2
+ identifier = "allow-custom-commands"
3
+ description = "Allow custom commands"
4
+
5
+ [permission.commands]
6
+ allow = [
7
+ "show_dashboard",
8
+ "set_tray_title",
9
+ "hide_popover",
10
+ ]
11
+ deny = []
@@ -0,0 +1,238 @@
1
+ use tauri::{
2
+ tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
3
+ Manager, WindowEvent,
4
+ };
5
+ use std::sync::Mutex;
6
+ use std::time::Instant;
7
+
8
+ #[cfg(target_os = "macos")]
9
+ use tauri_nspanel::tauri_panel;
10
+
11
+ #[cfg(target_os = "macos")]
12
+ tauri_panel! {
13
+ panel!(Panel {
14
+ config: {
15
+ can_become_key_window: true,
16
+ can_become_main_window: false
17
+ }
18
+ })
19
+
20
+ panel_event!(PanelEventHandler {
21
+ window_did_resign_key(notification: &NSNotification) -> (),
22
+ })
23
+ }
24
+
25
+ #[allow(dead_code)]
26
+ struct PopoverState {
27
+ last_hidden: Mutex<Option<Instant>>,
28
+ }
29
+
30
+ impl Default for PopoverState {
31
+ fn default() -> Self {
32
+ Self {
33
+ last_hidden: Mutex::new(None),
34
+ }
35
+ }
36
+ }
37
+
38
+ #[tauri::command]
39
+ fn set_tray_title(app: tauri::AppHandle, title: String) {
40
+ if let Some(tray) = app.tray_by_id("main") {
41
+ let _ = tray.set_title(Some(title));
42
+ }
43
+ }
44
+
45
+ #[tauri::command]
46
+ fn show_dashboard(app: tauri::AppHandle) {
47
+ // Menu bar apps run as Accessory (no Dock icon); give the dashboard a
48
+ // Dock icon and normal cmd-tab presence while it's open.
49
+ #[cfg(target_os = "macos")]
50
+ let _ = app.set_activation_policy(tauri::ActivationPolicy::Regular);
51
+ if let Some(win) = app.get_webview_window("dashboard") {
52
+ let _ = win.show();
53
+ let _ = win.set_focus();
54
+ }
55
+ }
56
+
57
+ #[tauri::command]
58
+ fn hide_popover(app: tauri::AppHandle) {
59
+ #[cfg(target_os = "macos")]
60
+ {
61
+ use tauri_nspanel::ManagerExt;
62
+ if let Ok(panel) = app.get_webview_panel("popover") {
63
+ panel.hide();
64
+ return;
65
+ }
66
+ }
67
+ if let Some(win) = app.get_webview_window("popover") {
68
+ let _ = win.hide();
69
+ }
70
+ }
71
+
72
+ pub fn run() {
73
+ let mut builder = tauri::Builder::default();
74
+
75
+ #[cfg(target_os = "macos")]
76
+ {
77
+ builder = builder.plugin(tauri_nspanel::init());
78
+ }
79
+
80
+ builder
81
+ .plugin(tauri_plugin_fs::init())
82
+ .plugin(tauri_plugin_http::init())
83
+ .plugin(tauri_plugin_store::Builder::default().build())
84
+ .plugin(tauri_plugin_opener::init())
85
+ .plugin(tauri_plugin_autostart::init(
86
+ tauri_plugin_autostart::MacosLauncher::LaunchAgent,
87
+ None,
88
+ ))
89
+ .manage(PopoverState::default())
90
+ .invoke_handler(tauri::generate_handler![set_tray_title, show_dashboard, hide_popover])
91
+ .setup(|app| {
92
+ #[cfg(target_os = "macos")]
93
+ {
94
+ app.set_activation_policy(tauri::ActivationPolicy::Accessory);
95
+
96
+ if let Some(win) = app.get_webview_window("popover") {
97
+ use tauri_nspanel::{CollectionBehavior, StyleMask, WebviewWindowExt};
98
+
99
+ let panel = win.to_panel::<Panel>().unwrap();
100
+ let handler = PanelEventHandler::new();
101
+ let handle = app.handle().clone();
102
+ handler.window_did_resign_key(move |_notification| {
103
+ use tauri_nspanel::ManagerExt;
104
+ if let Ok(panel) = handle.get_webview_panel("popover") {
105
+ panel.hide();
106
+ }
107
+ // Also update our state so next click toggles correctly
108
+ if let Some(state) = handle.try_state::<PopoverState>() {
109
+ if let Ok(mut last) = state.last_hidden.lock() {
110
+ *last = Some(std::time::Instant::now());
111
+ }
112
+ }
113
+ });
114
+ panel.set_event_handler(Some(handler.as_ref()));
115
+
116
+ // Non-activating: the panel can become key without activating the
117
+ // app. Activating would make macOS switch away from a fullscreen
118
+ // Space instead of showing the panel on top of it.
119
+ panel.set_style_mask(
120
+ StyleMask::empty().nonactivating_panel().value(),
121
+ );
122
+ panel.set_level(101); // NSPopUpMenuWindowLevel
123
+ // CanJoinAllSpaces (not MoveToActiveSpace, which doesn't work with
124
+ // fullscreen Spaces) + FullScreenAuxiliary, set once — toggling
125
+ // behaviors around show() races the window server's Space migration.
126
+ panel.set_collection_behavior(
127
+ CollectionBehavior::new()
128
+ .can_join_all_spaces()
129
+ .full_screen_auxiliary()
130
+ .ignores_cycle()
131
+ .value(),
132
+ );
133
+ panel.set_hides_on_deactivate(false);
134
+ }
135
+ }
136
+
137
+ let tray_icon = tauri::image::Image::from_bytes(include_bytes!("../icons/source/tray-44.png"))
138
+ .unwrap_or_else(|_| app.default_window_icon().unwrap().clone());
139
+ TrayIconBuilder::with_id("main")
140
+ .icon(tray_icon)
141
+ .icon_as_template(true)
142
+ .title("$–.––")
143
+ .on_tray_icon_event(|tray, event| {
144
+ if let TrayIconEvent::Click {
145
+ button: MouseButton::Left,
146
+ button_state: MouseButtonState::Up,
147
+ rect,
148
+ ..
149
+ } = event
150
+ {
151
+ let app = tray.app_handle();
152
+ let state = app.state::<PopoverState>();
153
+ let now = Instant::now();
154
+
155
+ let recently_hidden = if let Some(last_hidden) = *state.last_hidden.lock().unwrap() {
156
+ now.duration_since(last_hidden).as_millis() < 200
157
+ } else {
158
+ false
159
+ };
160
+
161
+ if recently_hidden {
162
+ return;
163
+ }
164
+
165
+ if let Some(win) = app.get_webview_window("popover") {
166
+ #[cfg(target_os = "macos")]
167
+ {
168
+ use tauri_nspanel::ManagerExt;
169
+ if let Ok(panel) = app.get_webview_panel("popover") {
170
+ if panel.is_visible() {
171
+ panel.hide();
172
+ *state.last_hidden.lock().unwrap() = Some(Instant::now());
173
+ } else {
174
+ let icon_pos = rect.position.to_physical::<f64>(1.0);
175
+ let icon_size = rect.size.to_physical::<f64>(1.0);
176
+ let width = win
177
+ .outer_size()
178
+ .map(|s| s.width as f64)
179
+ .unwrap_or(380.0);
180
+ let x = icon_pos.x + icon_size.width / 2.0 - width / 2.0;
181
+ let y = icon_pos.y + icon_size.height + 4.0;
182
+ let _ = win.set_position(tauri::PhysicalPosition::new(x, y));
183
+
184
+ // Order front + make key without activating the app
185
+ // (the panel is non-activating), so it appears on the
186
+ // current Space even when a fullscreen app is active.
187
+ panel.show_and_make_key();
188
+ }
189
+ }
190
+ }
191
+
192
+ #[cfg(not(target_os = "macos"))]
193
+ {
194
+ let is_visible = win.is_visible().unwrap_or(false);
195
+ if is_visible {
196
+ let _ = win.hide();
197
+ *state.last_hidden.lock().unwrap() = Some(Instant::now());
198
+ } else {
199
+ let icon_pos = rect.position.to_physical::<f64>(1.0);
200
+ let icon_size = rect.size.to_physical::<f64>(1.0);
201
+ let width = win
202
+ .outer_size()
203
+ .map(|s| s.width as f64)
204
+ .unwrap_or(380.0);
205
+ let x = icon_pos.x + icon_size.width / 2.0 - width / 2.0;
206
+ let y = icon_pos.y + icon_size.height + 4.0;
207
+ let _ = win.set_position(tauri::PhysicalPosition::new(x, y));
208
+ let _ = win.show();
209
+ let _ = win.set_focus();
210
+ }
211
+ }
212
+ }
213
+ }
214
+ })
215
+ .build(app)?;
216
+ Ok(())
217
+ })
218
+ .on_window_event(|window, event| {
219
+ if window.label() == "popover" {
220
+ if let WindowEvent::Focused(false) = event {
221
+ let _ = window.hide();
222
+ let state = window.state::<PopoverState>();
223
+ *state.last_hidden.lock().unwrap() = Some(Instant::now());
224
+ }
225
+ }
226
+ if window.label() == "dashboard" {
227
+ if let WindowEvent::CloseRequested { api, .. } = event {
228
+ api.prevent_close();
229
+ let _ = window.hide();
230
+ // Back to menu-bar-only: drop the Dock icon
231
+ #[cfg(target_os = "macos")]
232
+ let _ = window.app_handle().set_activation_policy(tauri::ActivationPolicy::Accessory);
233
+ }
234
+ }
235
+ })
236
+ .run(tauri::generate_context!())
237
+ .expect("error while running tauri application");
238
+ }
@@ -0,0 +1,6 @@
1
+ // Prevents additional console window on Windows in release, DO NOT REMOVE!!
2
+ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3
+
4
+ fn main() {
5
+ app_lib::run();
6
+ }