Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tokcat",
"version": "0.1.27",
"version": "0.1.28",
"private": true,
"type": "module",
"engines": {
Expand Down
71 changes: 69 additions & 2 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "tokcat"
version = "0.1.27"
version = "0.1.28"
edition = "2021"
description = "Native macOS menubar dashboard for local AI token usage"
authors = ["handlecusion"]
Expand Down Expand Up @@ -32,6 +32,7 @@ env_logger = "0.11"
tauri-plugin-updater = "2"
tauri-plugin-dialog = "2"
tauri-plugin-process = "2"
tauri-plugin-global-shortcut = "2"
# Bundled SQLite so we don't depend on the system libsqlite3; needed for
# the Hermes Agent SQLite tail in usage_tail.rs (no schema migrations —
# read-only queries only).
Expand Down
30 changes: 30 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ fn quit_app(app: tauri::AppHandle) {
app.exit(0);
}

/// Hide the popover from the frontend (⌘W / Esc) and return focus to the
/// previously-frontmost app. Routed through the same helper as the tray-click
/// and Ctrl+Cmd+T toggle so every explicit dismiss behaves identically.
#[tauri::command]
fn hide_popover(app: tauri::AppHandle) {
tray::hide_popover(&app);
}

#[tauri::command]
fn push_dialog_shield(state: tauri::State<'_, Arc<AppState>>) {
state.push_suppress_blur_hide();
Expand Down Expand Up @@ -299,6 +307,12 @@ pub fn run() {
let state = AppState::new();
let state_clone = state.clone();

// Ctrl+Cmd+T toggles the popover from anywhere — registered in setup, fired
// by the plugin handler below. Shortcut is Copy, so the same value is reused
// for both the handler match and the setup-time registration.
use tauri_plugin_global_shortcut::{Code, Modifiers, Shortcut, ShortcutState};
let toggle_shortcut = Shortcut::new(Some(Modifiers::CONTROL | Modifiers::SUPER), Code::KeyT);

let mut builder = tauri::Builder::default()
.plugin(tauri_plugin_autostart::init(
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
Expand All @@ -307,11 +321,21 @@ pub fn run() {
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_process::init())
.plugin(
tauri_plugin_global_shortcut::Builder::new()
.with_handler(move |app, shortcut, event| {
if shortcut == &toggle_shortcut && event.state() == ShortcutState::Pressed {
tray::toggle_popover(app);
}
})
.build(),
)
.manage(state.clone())
.invoke_handler(tauri::generate_handler![
get_graph,
refresh_graph,
quit_app,
hide_popover,
push_dialog_shield,
pop_dialog_shield,
set_animate_tray,
Expand All @@ -331,6 +355,12 @@ pub fn run() {
}
let handle = app.handle().clone();
tray::setup(&handle)?;
{
use tauri_plugin_global_shortcut::GlobalShortcutExt;
if let Err(e) = app.global_shortcut().register(toggle_shortcut) {
log::warn!("global shortcut register failed: {}", e);
}
}
#[cfg(target_os = "macos")]
if let Err(e) = native_tray::init() {
log::warn!(
Expand Down
35 changes: 34 additions & 1 deletion src-tauri/src/tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub fn setup<R: Runtime>(app: &AppHandle<R>) -> tauri::Result<()> {
if let Some(w) = app.get_webview_window("main") {
let visible = w.is_visible().unwrap_or(false);
if visible {
let _ = w.hide();
hide_popover(app);
} else {
let _ = position_window_under_tray(tray, &w);
let _ = w.show();
Expand All @@ -95,6 +95,39 @@ pub fn setup<R: Runtime>(app: &AppHandle<R>) -> tauri::Result<()> {
Ok(())
}

/// Hide the popover and hand keyboard focus back to the app that was in front
/// before it opened. Plain `w.hide()` (orderOut) leaves Tokcat the active
/// accessory app with no window, so focus lands nowhere; `app.hide()` (NSApp
/// hide) deactivates Tokcat and reactivates the previously-frontmost app. The
/// `w.hide()` runs first so the toggle's `is_visible()` check is reliable
/// regardless of how NSApp hide reports window visibility. Used by every
/// explicit dismiss (Ctrl+Cmd+T, tray-click toggle, ⌘W, Esc) but not the
/// blur-hide, where focus has already moved to whatever stole it.
pub fn hide_popover<R: Runtime>(app: &AppHandle<R>) {
if let Some(w) = app.get_webview_window("main") {
let _ = w.hide();
}
#[cfg(target_os = "macos")]
let _ = app.hide();
}

/// Show the popover under the tray if hidden, hide it if visible. Mirrors the
/// left-click tray toggle so the global shortcut (Ctrl+Cmd+T) behaves the same.
pub fn toggle_popover<R: Runtime>(app: &AppHandle<R>) {
if let Some(w) = app.get_webview_window("main") {
if w.is_visible().unwrap_or(false) {
hide_popover(app);
} else {
if let Some(tray) = app.tray_by_id("main-tray") {
let _ = position_window_under_tray(&tray, &w);
}
let _ = w.show();
let _ = w.set_focus();
let _ = app.emit("popover-shown", ());
}
}
}

fn show_popover<R: Runtime>(app: &AppHandle<R>) {
if let Some(w) = app.get_webview_window("main") {
if let Some(tray) = app.tray_by_id("main-tray") {
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Tokcat",
"version": "0.1.27",
"version": "0.1.28",
"identifier": "com.handlecusion.tokcat",
"build": {
"beforeDevCommand": "npm run dev:vite",
Expand Down
Loading
Loading