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
13 changes: 13 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ fn initialize_core_logic(app_handle: &AppHandle) {
// Initialize tray menu with idle state
utils::update_tray_menu(app_handle, &utils::TrayIconState::Idle, None);

// Apply show_tray_icon setting
let settings = settings::get_settings(app_handle);
if !settings.show_tray_icon {
tray::set_tray_visibility(app_handle, false);
}

// Refresh tray menu when model state changes
let app_handle_for_listener = app_handle.clone();
app_handle.listen("model-state-changed", move |_| {
Expand Down Expand Up @@ -289,6 +295,7 @@ pub fn run() {
shortcut::change_update_checks_setting,
shortcut::change_keyboard_implementation_setting,
shortcut::get_keyboard_implementation,
shortcut::change_show_tray_icon_setting,
shortcut::handy_keys::start_handy_keys_recording,
shortcut::handy_keys::stop_handy_keys_recording,
trigger_update_check,
Expand Down Expand Up @@ -419,6 +426,12 @@ pub fn run() {
})
.on_window_event(|window, event| match event {
tauri::WindowEvent::CloseRequested { api, .. } => {
let settings = get_settings(&window.app_handle());
// If tray icon is hidden, quit the app
if !settings.show_tray_icon {
window.app_handle().exit(0);
return;
}
api.prevent_close();
let _res = window.hide();
#[cfg(target_os = "macos")]
Expand Down
7 changes: 7 additions & 0 deletions src-tauri/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ pub struct AppSettings {
pub experimental_enabled: bool,
#[serde(default)]
pub keyboard_implementation: KeyboardImplementation,
#[serde(default = "default_show_tray_icon")]
pub show_tray_icon: bool,
#[serde(default = "default_paste_delay_ms")]
pub paste_delay_ms: u64,
}
Expand Down Expand Up @@ -396,6 +398,10 @@ fn default_app_language() -> String {
.unwrap_or_else(|| "en".to_string())
}

fn default_show_tray_icon() -> bool {
true
}

fn default_post_process_provider_id() -> String {
"openai".to_string()
}
Expand Down Expand Up @@ -631,6 +637,7 @@ pub fn get_default_settings() -> AppSettings {
app_language: default_app_language(),
experimental_enabled: false,
keyboard_implementation: KeyboardImplementation::default(),
show_tray_icon: default_show_tray_icon(),
paste_delay_ms: default_paste_delay_ms(),
}
}
Expand Down
13 changes: 13 additions & 0 deletions src-tauri/src/shortcut/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -978,3 +978,16 @@ pub fn change_app_language_setting(app: AppHandle, language: String) -> Result<(

Ok(())
}

#[tauri::command]
#[specta::specta]
pub fn change_show_tray_icon_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
let mut settings = settings::get_settings(&app);
settings.show_tray_icon = enabled;
settings::write_settings(&app, settings);

// Apply change immediately
tray::set_tray_visibility(&app, enabled);

Ok(())
}
9 changes: 9 additions & 0 deletions src-tauri/src/tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,15 @@ fn last_transcript_text(entry: &HistoryEntry) -> &str {
.unwrap_or(&entry.transcription_text)
}

pub fn set_tray_visibility(app: &AppHandle, visible: bool) {
let tray = app.state::<TrayIcon>();
if let Err(e) = tray.set_visible(visible) {
error!("Failed to set tray visibility: {}", e);
} else {
info!("Tray visibility set to: {}", visible);
}
}

pub fn copy_last_transcript(app: &AppHandle) {
let history_manager = app.state::<Arc<HistoryManager>>();
let entry = match history_manager.get_latest_entry() {
Expand Down
10 changes: 9 additions & 1 deletion src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,14 @@ async changeKeyboardImplementationSetting(implementation: string) : Promise<Resu
async getKeyboardImplementation() : Promise<string> {
return await TAURI_INVOKE("get_keyboard_implementation");
},
async changeShowTrayIconSetting(enabled: boolean) : Promise<Result<null, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("change_show_tray_icon_setting", { enabled }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
/**
* Start key recording mode
*/
Expand Down Expand Up @@ -695,7 +703,7 @@ async isLaptop() : Promise<Result<boolean, string>> {

/** user-defined types **/

export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; update_checks_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: number; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: Partial<{ [key in string]: string }>; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean; append_trailing_space?: boolean; app_language?: string; experimental_enabled?: boolean; keyboard_implementation?: KeyboardImplementation; paste_delay_ms?: number }
export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; update_checks_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: number; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: Partial<{ [key in string]: string }>; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean; append_trailing_space?: boolean; app_language?: string; experimental_enabled?: boolean; keyboard_implementation?: KeyboardImplementation; show_tray_icon?: boolean; paste_delay_ms?: number }
export type AudioDevice = { index: string; name: string; is_default: boolean }
export type BindingResponse = { success: boolean; binding: ShortcutBinding | null; error: string | null }
export type ClipboardHandling = "dont_modify" | "copy_to_clipboard"
Expand Down
31 changes: 31 additions & 0 deletions src/components/settings/ShowTrayIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { ToggleSwitch } from "../ui/ToggleSwitch";
import { useSettings } from "../../hooks/useSettings";

interface ShowTrayIconProps {
descriptionMode?: "inline" | "tooltip";
grouped?: boolean;
}

export const ShowTrayIcon: React.FC<ShowTrayIconProps> = React.memo(
({ descriptionMode = "tooltip", grouped = false }) => {
const { t } = useTranslation();
const { getSetting, updateSetting, isUpdating } = useSettings();

const showTrayIcon = getSetting("show_tray_icon") ?? true;

return (
<ToggleSwitch
checked={showTrayIcon}
onChange={(enabled) => updateSetting("show_tray_icon", enabled)}
isUpdating={isUpdating("show_tray_icon")}
label={t("settings.advanced.showTrayIcon.label")}
description={t("settings.advanced.showTrayIcon.description")}
descriptionMode={descriptionMode}
grouped={grouped}
tooltipPosition="bottom"
/>
);
},
);
2 changes: 2 additions & 0 deletions src/components/settings/advanced/AdvancedSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { CustomWords } from "../CustomWords";
import { SettingsGroup } from "../../ui/SettingsGroup";
import { StartHidden } from "../StartHidden";
import { AutostartToggle } from "../AutostartToggle";
import { ShowTrayIcon } from "../ShowTrayIcon";
import { PasteMethodSetting } from "../PasteMethod";
import { ClipboardHandlingSetting } from "../ClipboardHandling";
import { PostProcessingToggle } from "../PostProcessingToggle";
Expand All @@ -26,6 +27,7 @@ export const AdvancedSettings: React.FC = () => {
<SettingsGroup title={t("settings.advanced.groups.app")}>
<StartHidden descriptionMode="tooltip" grouped={true} />
<AutostartToggle descriptionMode="tooltip" grouped={true} />
<ShowTrayIcon descriptionMode="tooltip" grouped={true} />
<ShowOverlay descriptionMode="tooltip" grouped={true} />
<ModelUnloadTimeoutSetting descriptionMode="tooltip" grouped={true} />
<ExperimentalToggle descriptionMode="tooltip" grouped={true} />
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/ar/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@
"label": "التشغيل عند بدء التشغيل",
"description": ".بدء تشغيل Handy تلقائياً عند تسجيل الدخول إلى جهاز الكمبيوتر الخاص بك"
},
"showTrayIcon": {
"label": "إظهار أيقونة شريط النظام",
"description": ".عرض أيقونة Handy في شريط النظام"
},
"overlay": {
"title": "موقع التراكب",
"description": "عرض تراكب الملاحظات المرئية أثناء التسجيل والتفريغ. على نظام Linux يوصى بـ 'بلا'.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/cs/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "Spouštět při startu",
"description": "Automaticky spustit Handy po přihlášení do počítače."
},
"showTrayIcon": {
"label": "Zobrazit ikonu v systémové liště",
"description": "Zobrazit ikonu Handy v systémové liště."
},
"overlay": {
"title": "Pozice překryvu",
"description": "Zobrazovat vizuální překryv během nahrávání a přepisu. Na Linuxu je doporučeno 'Žádné'.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "Beim Start ausführen",
"description": "Handy automatisch beim Anmelden starten."
},
"showTrayIcon": {
"label": "Taskleistensymbol anzeigen",
"description": "Das Handy-Symbol in der Taskleiste anzeigen."
},
"overlay": {
"title": "Overlay-Position",
"description": "Visuelles Feedback-Overlay während Aufnahme und Transkription anzeigen. Unter Linux wird 'Keine' empfohlen.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "Launch on Startup",
"description": "Automatically start Handy when you log in to your computer."
},
"showTrayIcon": {
"label": "Show Tray Icon",
"description": "Display the Handy icon in the system tray."
},
"overlay": {
"title": "Overlay Position",
"description": "Display visual feedback overlay during recording and transcription. On Linux 'None' is recommended.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "Iniciar al Arranque",
"description": "Iniciar Handy automáticamente cuando inicies sesión en tu computadora."
},
"showTrayIcon": {
"label": "Mostrar Icono de Bandeja",
"description": "Mostrar el icono de Handy en la bandeja del sistema."
},
"overlay": {
"title": "Posición de Superposición",
"description": "Mostrar superposición de retroalimentación visual durante la grabación y transcripción. En Linux se recomienda 'Ninguna'.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "Lancer au démarrage",
"description": "Démarrer automatiquement Handy lorsque vous vous connectez à votre ordinateur."
},
"showTrayIcon": {
"label": "Afficher l'icône de la barre",
"description": "Afficher l'icône de Handy dans la barre système."
},
"overlay": {
"title": "Position de la fenêtre d'enregistrement",
"description": "Afficher un retour visuel pendant l'enregistrement et la transcription. Sur Linux, 'Aucune' est recommandé.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/it/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "Avvia all'Accensione",
"description": "Avvia Handy automaticamente quando accedi al computer."
},
"showTrayIcon": {
"label": "Mostra icona nella barra di sistema",
"description": "Mostra l'icona di Handy nella barra di sistema."
},
"overlay": {
"title": "Posizione della Sovrimpressione",
"description": "Mostra un feedback visivo in sovrimpressione durante la registrazione e la trascrizione. Su Linux si raccomanda 'Nessuna'.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/ja/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "起動時に実行",
"description": "コンピューターにログインしたときにHandyを自動的に起動。"
},
"showTrayIcon": {
"label": "トレイアイコンを表示",
"description": "システムトレイにHandyのアイコンを表示します。"
},
"overlay": {
"title": "オーバーレイ位置",
"description": "録音と文字起こし中に視覚的なフィードバックオーバーレイを表示。Linuxでは「なし」を推奨。",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/ko/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "시작 시 실행",
"description": "컴퓨터 로그인 시 Handy를 자동으로 시작합니다."
},
"showTrayIcon": {
"label": "트레이 아이콘 표시",
"description": "시스템 트레이에 Handy 아이콘을 표시합니다."
},
"overlay": {
"title": "오버레이 위치",
"description": "녹음 및 전사 중 시각적 피드백 오버레이를 표시합니다. Linux에서는 '없음'을 권장합니다.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/pl/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "Uruchamiaj przy starcie",
"description": "Automatycznie uruchamiaj Handy po zalogowaniu."
},
"showTrayIcon": {
"label": "Pokaż ikonę w zasobniku",
"description": "Wyświetlaj ikonę Handy w zasobniku systemowym."
},
"overlay": {
"title": "Pozycja nakładki",
"description": "Wyświetlaj wizualną nakładkę podczas nagrywania i transkrypcji. Na Linuxie zalecane 'Brak'.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/pt/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "Iniciar na Inicialização",
"description": "Iniciar automaticamente o Handy quando você fizer login no seu computador."
},
"showTrayIcon": {
"label": "Mostrar ícone na bandeja",
"description": "Exibir o ícone do Handy na bandeja do sistema."
},
"overlay": {
"title": "Posição da Sobreposição",
"description": "Exibir sobreposição de feedback visual durante gravação e transcrição. No Linux, 'Nenhum' é recomendado.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/ru/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "Запуск при запуске",
"description": "Автоматически запускать Handy при входе в систему."
},
"showTrayIcon": {
"label": "Показать значок в трее",
"description": "Отображать значок Handy в системном трее."
},
"overlay": {
"title": "Позиция наложения",
"description": "Отображение наложения визуальной обратной связи во время записи и транскрипции. В Linux рекомендуется выбрать «Нет».",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/tr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "Başlangıçta Çalıştır",
"description": "Bilgisayara giriş yaptığınızda Handy otomatik olarak başlatılır."
},
"showTrayIcon": {
"label": "Tepsi simgesini göster",
"description": "Handy simgesini sistem tepsisinde göster."
},
"overlay": {
"title": "Overlay Konumu",
"description": "Kayıt ve transkripsiyon sırasında görsel geri bildirim kaplamasını gösterir. Linux'ta 'Yok' önerilir.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/uk/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "Запуск при старті системи",
"description": "Автоматично запускати Handy при вході в систему"
},
"showTrayIcon": {
"label": "Показати значок у треї",
"description": "Відображати значок Handy у системному треї."
},
"overlay": {
"title": "Позиція оверлею",
"description": "Показувати візуальний оверлей під час запису та транскрипції. На Linux рекомендовано «Немає»",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/vi/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "Khởi động cùng hệ thống",
"description": "Tự động khởi động Handy khi bạn đăng nhập vào máy tính."
},
"showTrayIcon": {
"label": "Hiển thị biểu tượng khay",
"description": "Hiển thị biểu tượng Handy trong khay hệ thống."
},
"overlay": {
"title": "Vị trí lớp phủ",
"description": "Hiển thị lớp phủ phản hồi trực quan trong quá trình ghi âm và chuyển đổi. Trên Linux, 'Không có' được khuyến nghị.",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/zh/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@
"label": "开机启动",
"description": "登录计算机时自动启动 Handy。"
},
"showTrayIcon": {
"label": "显示托盘图标",
"description": "在系统托盘中显示 Handy 图标。"
},
"overlay": {
"title": "悬浮窗位置",
"description": "在录制和转录期间显示可视反馈悬浮窗。在 Linux 上建议选择「无」。",
Expand Down
2 changes: 2 additions & 0 deletions src/stores/settingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ const settingUpdaters: {
app_language: (value) => commands.changeAppLanguageSetting(value as string),
experimental_enabled: (value) =>
commands.changeExperimentalEnabledSetting(value as boolean),
show_tray_icon: (value) =>
commands.changeShowTrayIconSetting(value as boolean),
};

export const useSettingsStore = create<SettingsStore>()(
Expand Down