diff --git a/crates/openlogi-core/src/config.rs b/crates/openlogi-core/src/config.rs
index 72c50aa..640ae18 100644
--- a/crates/openlogi-core/src/config.rs
+++ b/crates/openlogi-core/src/config.rs
@@ -55,7 +55,11 @@ impl Default for Config {
///
/// All fields are `#[serde(default)]` so adding a new one is backward
/// compatible — old config files just keep the default for the new field.
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[allow(
+ clippy::struct_excessive_bools,
+ reason = "independent on/off user preferences, not a state machine"
+)]
pub struct AppSettings {
/// When true, a macOS `LaunchAgent` plist at
/// `~/Library/LaunchAgents/org.openlogi.openlogi.plist` is installed
@@ -77,6 +81,12 @@ pub struct AppSettings {
/// user opt in on first launch.
#[serde(default)]
pub update_prompt_seen: bool,
+ /// Whether OpenLogi shows a macOS menu-bar (status item) icon. `true`
+ /// (default) → it lives in the menu bar, dropping the Dock icon while no
+ /// window is open; `false` → it stays an ordinary Dock app with no status
+ /// item. macOS-only; ignored on other platforms.
+ #[serde(default = "default_true")]
+ pub show_in_menu_bar: bool,
/// UI language as a BCP-47-ish locale code matching the GUI's bundled
/// locales (`"en"`, `"zh-CN"`, `"zh-HK"`). `None` means "follow the
/// system locale", which the GUI resolves at startup. Stored here so a
@@ -94,6 +104,24 @@ impl AppSettings {
}
}
+impl Default for AppSettings {
+ fn default() -> Self {
+ Self {
+ launch_at_login: false,
+ check_for_updates: false,
+ update_prompt_seen: false,
+ show_in_menu_bar: true,
+ language: None,
+ }
+ }
+}
+
+/// serde default for [`AppSettings::show_in_menu_bar`]: `true`, so the menu-bar
+/// icon is on out of the box and configs predating the field keep that behavior.
+fn default_true() -> bool {
+ true
+}
+
/// Settings scoped to a single physical device (keyed by HID++ model+ext).
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DeviceConfig {
diff --git a/crates/openlogi-gui/locales/app.yml b/crates/openlogi-gui/locales/app.yml
index 997f5f3..9837047 100644
--- a/crates/openlogi-gui/locales/app.yml
+++ b/crates/openlogi-gui/locales/app.yml
@@ -92,10 +92,6 @@ _version: 2
ja: すべてを表示
zh-CN: 全部显示
zh-HK: 全部顯示
-"Quit OpenLogi":
- ja: OpenLogi を終了
- zh-CN: 退出 OpenLogi
- zh-HK: 結束 OpenLogi
"Window":
ja: ウインドウ
zh-CN: 窗口
@@ -108,6 +104,10 @@ _version: 2
ja: ズーム
zh-CN: 缩放
zh-HK: 縮放
+"Close Window":
+ ja: ウインドウを閉じる
+ zh-CN: 关闭窗口
+ zh-HK: 關閉視窗
# ── About window (about.rs) ─────────────────────────────────────────────────
"Open-source Logitech mouse configuration — DPI, SmartShift, button bindings, and gestures.":
@@ -136,6 +136,14 @@ _version: 2
ja: 起動ごとに新しいバージョンを一度だけ確認します(確認のみ — 自動ダウンロードはしません)。
zh-CN: 每次启动检查一次新版本(仅查询,不自动下载)。
zh-HK: 每次啟動檢查一次新版本(僅查詢,不自動下載)。
+"Show in menu bar":
+ ja: メニューバーに表示
+ zh-CN: 显示在菜单栏
+ zh-HK: 顯示在選單列
+"Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.":
+ ja: OpenLogi のアイコンをメニューバーに表示します。オフにすると Dock に残ります。
+ zh-CN: 在菜单栏保留 OpenLogi 图标;关闭后改为停留在程序坞中。
+ zh-HK: 在選單列保留 OpenLogi 圖示;關閉後改為停留在 Dock 中。
"Language":
ja: 言語
zh-CN: 语言
diff --git a/crates/openlogi-gui/src/app_menu.rs b/crates/openlogi-gui/src/app_menu.rs
index 0717c2d..669c354 100644
--- a/crates/openlogi-gui/src/app_menu.rs
+++ b/crates/openlogi-gui/src/app_menu.rs
@@ -14,6 +14,8 @@ use gpui::{App, KeyBinding, Menu, MenuItem, actions};
actions!(
openlogi,
[
+ /// Close the focused window.
+ CloseWindow,
/// Hide the OpenLogi window (macOS).
Hide,
/// Hide every other application (macOS).
@@ -44,6 +46,14 @@ pub fn install(cx: &mut App) {
cx.on_action(|_: &ShowAll, cx| cx.unhide_other_apps());
}
cx.on_action(|_: &Quit, cx| cx.quit());
+ // App-level so it closes whichever window is focused. Settings / About /
+ // Add Device each have their own view root, so a view-level handler (like
+ // Minimize / Zoom) would only fire for the main window.
+ cx.on_action(|_: &CloseWindow, cx| {
+ if let Some(handle) = cx.active_window() {
+ let _ = handle.update(cx, |_, window, _| window.remove_window());
+ }
+ });
cx.on_action(|_: &OpenSettings, cx| crate::windows::settings::open(cx));
cx.on_action(|_: &OpenAbout, cx| crate::windows::about::open(cx));
cx.on_action(|_: &OpenAddDevice, cx| crate::windows::add_device::open(cx));
@@ -55,6 +65,7 @@ pub fn install(cx: &mut App) {
#[cfg(target_os = "macos")]
KeyBinding::new("cmd-alt-h", HideOthers, None),
KeyBinding::new("cmd-m", Minimize, None),
+ KeyBinding::new("cmd-w", CloseWindow, None),
KeyBinding::new("cmd-,", OpenSettings, None),
]);
@@ -100,6 +111,8 @@ fn menus() -> Vec