Skip to content
Open
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
82 changes: 74 additions & 8 deletions src-tauri/Cargo.lock

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

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ enigo = "0.3"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-global-shortcut = "2"
tauri-plugin-dialog = "2"
tauri-plugin-autostart = "2"
44 changes: 44 additions & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::time::{Duration, Instant};
use tauri::menu::{Menu, MenuItem};
use tauri::tray::TrayIconBuilder;
use tauri::{async_runtime::spawn, Emitter, Manager, State};
use tauri_plugin_autostart::ManagerExt;
use tokio::sync::Mutex as TokioMutex;

mod audio;
Expand Down Expand Up @@ -66,6 +67,9 @@ pub struct AppState {
pub region: &'static str,
}

/// Passed to the app by the autostart entry so it can stay in the tray.
const HIDDEN_LAUNCH_FLAG: &str = "--hidden";

const TRAY_ID: &str = "main-tray";
const TRAY_ICON_IDLE: &[u8] = include_bytes!("../icons/32x32.png");
const TRAY_ICON_DICTATING: &[u8] = include_bytes!("../icons/tray-microphone.png");
Expand Down Expand Up @@ -874,6 +878,26 @@ async fn minimize_window(window: tauri::Window) -> Result<(), String> {
window.minimize().map_err(|e| e.to_string())
}

/// Whether the app is registered to start on login (registry Run key on
/// Windows, LaunchAgent on macOS).
#[tauri::command]
fn get_autostart(app: tauri::AppHandle) -> Result<bool, String> {
app.autolaunch().is_enabled().map_err(|e| e.to_string())
}

/// Register or remove the login entry. The entry carries `--hidden`, so an
/// automatic launch goes straight to the tray without showing the window.
#[tauri::command]
fn set_autostart(app: tauri::AppHandle, enabled: bool) -> Result<(), String> {
let manager = app.autolaunch();
if enabled {
manager.enable()
} else {
manager.disable()
}
.map_err(|e| e.to_string())
}

#[tauri::command]
async fn register_dictation_hotkey(
hotkey: String,
Expand Down Expand Up @@ -1112,6 +1136,10 @@ fn main() {
.build(),
)
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.plugin(tauri_plugin_autostart::init(
tauri_plugin_autostart::MacosLauncher::LaunchAgent,
Some(vec![HIDDEN_LAUNCH_FLAG]),
))
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_notification::init())
.setup(|app| {
Expand Down Expand Up @@ -1211,8 +1239,22 @@ fn main() {
})
.build(app)?;

// The window starts hidden (see tauri.conf.json) so an autostart
// launch never flashes a window; a manual launch shows it here.
let hidden_launch = std::env::args().any(|arg| arg == HIDDEN_LAUNCH_FLAG);

// Intercept window close: hide instead of destroying
if let Some(window) = app.get_webview_window("main") {
if hidden_launch {
#[cfg(target_os = "macos")]
let _ = window
.app_handle()
.set_activation_policy(tauri::ActivationPolicy::Accessory);
} else {
let _ = window.show();
let _ = window.set_focus();
}

let w = window.clone();
window.on_window_event(move |event| {
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
Expand Down Expand Up @@ -1256,6 +1298,8 @@ fn main() {
show_window,
hide_window,
minimize_window,
get_autostart,
set_autostart,
config::save_hotkey,
config::get_hotkey,
config::save_language_settings,
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"height": 600,
"resizable": false,
"fullscreen": false,
"visible": true
"visible": false
}
]
},
Expand Down
80 changes: 58 additions & 22 deletions src/components/AppSettingsView.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useEffect, useState } from "react";
import { invoke } from "@tauri-apps/api/core";
import type { AppSettings, AudioDeviceInfo } from "../types";
import {
formatKeySymbol,
Expand Down Expand Up @@ -67,6 +69,19 @@ export function AppSettingsView({
onDone: () => void;
onOpenLogs: () => void;
}) {
const [autostart, setAutostart] = useState(false);

useEffect(() => {
invoke<boolean>("get_autostart")
.then(setAutostart)
.catch(() => {});
}, []);

const toggleAutostart = async (enabled: boolean) => {
await invoke("set_autostart", { enabled });
setAutostart(enabled);
};

const renderKbdKeys = (keys: string[]) =>
sortKeys(keys).map((key, i) => (
<kbd key={i} className="shortcut-kbd">
Expand Down Expand Up @@ -214,28 +229,49 @@ export function AppSettingsView({
</div>
</div>

<div className="form-group">
<label className="form-label">
Copy to clipboard
<InfoTooltip label="About copy to clipboard">
<strong>Copy to clipboard</strong>
When on, the full transcription is left on your clipboard after each
dictation.
</InfoTooltip>
</label>
<label className="toggle-switch" title="Copy to clipboard">
<input
type="checkbox"
checked={settings.copyToClipboard}
onChange={(e) =>
setSettings({
...settings,
copyToClipboard: e.target.checked,
})
}
/>
<span className="toggle-slider" />
</label>
<div className="settings-row">
<div className="form-group">
<label className="form-label">
Copy to clipboard
<InfoTooltip label="About copy to clipboard">
<strong>Copy to clipboard</strong>
When on, the full transcription is left on your clipboard after
each dictation.
</InfoTooltip>
</label>
<label className="toggle-switch" title="Copy to clipboard">
<input
type="checkbox"
checked={settings.copyToClipboard}
onChange={(e) =>
setSettings({
...settings,
copyToClipboard: e.target.checked,
})
}
/>
<span className="toggle-slider" />
</label>
</div>

<div className="form-group">
<label className="form-label">
Launch at login
<InfoTooltip label="About launch at login">
<strong>Launch at login</strong>
When on, GladiaFlow starts with your session and stays in the tray
instead of opening its window.
</InfoTooltip>
</label>
<label className="toggle-switch" title="Launch at login">
<input
type="checkbox"
checked={autostart}
onChange={(e) => void toggleAutostart(e.target.checked)}
/>
<span className="toggle-slider" />
</label>
</div>
</div>

<div className="form-group">
Expand Down