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
5 changes: 5 additions & 0 deletions apps/config/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,16 @@ struct CoreStatus {
elevated: bool,
/// 核心是否正在监听热键与鼠标(由核心回报)。
monitoring: bool,
/// 自动隐藏当前是否启用(托盘菜单也可切换,须回读对齐界面)。
auto_hide_enabled: bool,
}

const CORE_OFFLINE: CoreStatus = CoreStatus {
running: false,
hidden: false,
elevated: false,
monitoring: false,
auto_hide_enabled: false,
};

/// 核心状态:单次管道往返 + 快速失败(核心未运行时立即返回,不重试)。
Expand All @@ -282,11 +285,13 @@ async fn core_status() -> CoreStatus {
hidden,
elevated,
monitoring,
auto_hide_enabled,
}) => CoreStatus {
running: true,
hidden,
elevated,
monitoring,
auto_hide_enabled,
},
_ => CORE_OFFLINE,
}
Expand Down
78 changes: 78 additions & 0 deletions apps/config/ui/src/components/NotificationsPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,30 @@
import SettingRow from "./SettingRow.svelte";
import Toggle from "./Toggle.svelte";
import IconBell from "~icons/lucide/bell";
import IconCircleDot from "~icons/lucide/circle-dot";
import { app } from "../lib/state.svelte.js";

const n = $derived(app.config.notifications);
const s = $derived(app.config.setting);

// 四种角标颜色,顺序即优先级(红最高),与核心 tray_badge.rs 保持一致。
const BADGE_COLORS = [
{ key: "red", labelKey: "notify.badgeRed", css: "#f44336" },
{ key: "green", labelKey: "notify.badgeGreen", css: "#4caf50" },
{ key: "yellow", labelKey: "notify.badgeYellow", css: "#ffc107" },
{ key: "blue", labelKey: "notify.badgeBlue", css: "#2196f3" },
];

// 可绑定的状态源;"" 表示不显示该颜色。取值与 crates/common 的 TRAY_STATUSES 一致。
const BADGE_STATUSES = [
{ labelKey: "notify.statusNone", value: "" },
{ value: "hidden", labelKey: "notify.statusHidden" },
{ value: "auto_hide", labelKey: "notify.statusAutoHide" },
{ value: "hide_current", labelKey: "notify.statusHideCurrent" },
{ value: "freeze", labelKey: "notify.statusFreeze" },
{ value: "elevated", labelKey: "notify.statusElevated" },
{ value: "monitor_paused", labelKey: "notify.statusMonitorPaused" },
];
</script>

<div class="panel-stack">
Expand All @@ -27,6 +48,28 @@
{#snippet control()}<Toggle bind:checked={n.on_show} />{/snippet}
</SettingRow>
</section>

<section class="fcard">
<h3><IconCircleDot width="16" height="16" /> {t("notify.trayCard")}</h3>
{#each BADGE_COLORS as color (color.key)}
<SettingRow label={t(color.labelKey)}>
{#snippet control()}
<div class="badge-ctl">
<span class="dot" style:background={color.css} class:off={!s.tray_badges[color.key]}></span>
<select class="sel" bind:value={s.tray_badges[color.key]}>
{#each BADGE_STATUSES as st (st.value)}
<option value={st.value}>{t(st.labelKey)}</option>
{/each}
</select>
</div>
{/snippet}
</SettingRow>
{/each}
<div class="note">{t("notify.trayPriorityNote")}</div>
<SettingRow label={t("notify.trayTooltip")} description={t("notify.trayTooltipDesc")}>
{#snippet control()}<Toggle bind:checked={s.tray_show_tooltip} />{/snippet}
</SettingRow>
</section>
</div>

<style>
Expand All @@ -35,4 +78,39 @@
align-items: center;
gap: 6px;
}
.badge-ctl {
display: flex;
align-items: center;
gap: 10px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid var(--surface-2);
box-shadow: 0 0 0 1px var(--border);
flex-shrink: 0;
}
.dot.off {
opacity: 0.25;
}
.sel {
padding: 5px 10px;
border-radius: 7px;
border: 1px solid var(--border);
background: var(--surface-2);
color: var(--text);
font-size: 13px;
}
.sel:focus {
outline: none;
border-color: var(--accent);
}
.note {
padding: 10px 14px;
font-size: 12px;
color: var(--muted);
line-height: 1.6;
border-top: 1px solid var(--border);
}
</style>
15 changes: 14 additions & 1 deletion apps/config/ui/src/lib/ipc.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const mockConfig = {
hide_current: true,
click_to_hide: true,
hide_icon_after_hide: false,
tray_badges: { red: "hidden", green: "auto_hide", yellow: "hide_current", blue: "freeze" },
tray_show_tooltip: true,
freeze_after_hide: false,
enhanced_freeze: false,
freeze_whole_tree: false,
Expand Down Expand Up @@ -96,6 +98,8 @@ const mockWindows = [

/** mock 下的核心监控状态。 */
let mockMonitoring = true;
/** mock 下的自动隐藏开关(随 save_config 更新,供状态轮询回读联动)。 */
let mockAutoHide = mockConfig.setting.auto_hide_enabled;
/** mock 下的开机自启状态(有状态,供预览联动 UI)。 */
let mockAutostart = false;

Expand All @@ -110,7 +114,16 @@ function mockInvoke(cmd, args) {
case "autostart_status":
return { enabled: mockAutostart, method: mockAutostart ? "task" : null };
case "core_status":
return { running: true, hidden: false, elevated: false, monitoring: mockMonitoring };
return {
running: true,
hidden: false,
elevated: false,
monitoring: mockMonitoring,
auto_hide_enabled: mockAutoHide,
};
case "save_config":
mockAutoHide = !!args?.config?.setting?.auto_hide_enabled;
return null;
case "set_hotkeys_enabled":
mockMonitoring = !!args?.enabled;
return true;
Expand Down
18 changes: 16 additions & 2 deletions apps/config/ui/src/lib/state.svelte.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const app = $state({
/** exe 路径 → PNG data URI;null 表示查询过但无图标(负缓存)。 */
icons: {},
/** 核心状态;running 为 null 表示首次检测尚未返回。monitoring 由核心回报。 */
status: { running: null, hidden: false, elevated: false, monitoring: true },
status: { running: null, hidden: false, elevated: false, monitoring: true, auto_hide_enabled: false },
autostart: false,
/** 当前自启注册方式:"task"|"registry"|null(未注册)。 */
autostartMethod: null,
Expand Down Expand Up @@ -173,11 +173,15 @@ async function loadIcons(windows) {
// 自动保存:改动即存,带 debounce。

let saveTimer = null;
/** 有改动排队待存(debounce 期间为 true);状态回读不得覆盖未保存的改动。 */
let savePending = false;

/** 安排一次自动保存;连续改动只在停顿后写一次盘。 */
export function scheduleSave(delayMs = 600) {
savePending = true;
clearTimeout(saveTimer);
saveTimer = setTimeout(() => {
savePending = false;
saveConfig();
}, delayMs);
}
Expand Down Expand Up @@ -287,13 +291,23 @@ export function reportError(message, detail = "") {
app.errorReport = { message, detail: String(detail) };
}

/** 托盘菜单也能切换自动隐藏;以核心回报为准回读界面,但不覆盖用户尚未保存的改动。 */
function syncAutoHideFromCore() {
if (!app.config || !app.status.running) return;
if (savePending || app.saving) return;
if (app.config.setting.auto_hide_enabled !== app.status.auto_hide_enabled) {
app.config.setting.auto_hide_enabled = app.status.auto_hide_enabled;
}
}

/** 刷新核心状态;失败时视为核心离线。 */
export async function refreshStatus() {
try {
app.status = await invoke("core_status");
} catch {
app.status = { running: false, hidden: false, elevated: false, monitoring: false };
app.status = { running: false, hidden: false, elevated: false, monitoring: false, auto_hide_enabled: false };
}
syncAutoHideFromCore();
// 核心在停用期间重启过(新实例默认监听),重新按下停用。
if (suspenders.size > 0 && app.status.running && app.status.monitoring) {
invoke("set_hotkeys_enabled", { enabled: false }).catch(() => {});
Expand Down
20 changes: 19 additions & 1 deletion apps/config/ui/src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default {

"tab.binding": "Windows",
"tab.hotkeys": "Hotkeys & Mouse",
"tab.notify": "Notifications",
"tab.notify": "Alerts",
"tab.options": "Options",
"tab.about": "About & Feedback",

Expand Down Expand Up @@ -175,6 +175,24 @@ export default {
"notify.onShow": "Notify when showing windows",
"notify.onShowDesc": "Show a notification each time windows are restored",

"notify.trayCard": "Tray icon status",
"notify.badgeRed": "Red",
"notify.badgeGreen": "Green",
"notify.badgeYellow": "Yellow",
"notify.badgeBlue": "Blue",
"notify.statusNone": "Do not show",
"notify.statusHidden": "Windows are hidden",
"notify.statusAutoHide": "Auto hide is enabled",
"notify.statusHideCurrent": "Also-hide-active-window is enabled",
"notify.statusFreeze": "Process freezing is enabled",
"notify.statusElevated": "Running as administrator",
"notify.statusMonitorPaused": "Hotkey monitoring is paused",
"notify.trayPriorityNote":
"Bind a state to each colored dot badge; the badge appears in the bottom-right corner of the tray icon. When several bound states are active at once, only one dot is shown, in red > green > yellow > blue priority order. Choose “Do not show” to disable a color.",
"notify.trayTooltip": "Show the tray icon tooltip",
"notify.trayTooltipDesc":
"Show “Boss Key” when hovering over the tray icon; turn this off to show no text for extra discretion.",

"options.generalCard": "General",
"options.muteAfterHide": "Mute after hiding",
"options.muteAfterHideDesc":
Expand Down
19 changes: 18 additions & 1 deletion apps/config/ui/src/locales/zh-CN.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default {

"tab.binding": "窗口绑定",
"tab.hotkeys": "热键与鼠标",
"tab.notify": "通知设置",
"tab.notify": "提示设置",
"tab.options": "其他选项",
"tab.about": "关于与反馈",

Expand Down Expand Up @@ -171,6 +171,23 @@ export default {
"notify.onShow": "显示窗口时通知",
"notify.onShowDesc": "每次恢复显示时弹出通知",

"notify.trayCard": "图标状态提示",
"notify.badgeRed": "红色",
"notify.badgeGreen": "绿色",
"notify.badgeYellow": "黄色",
"notify.badgeBlue": "蓝色",
"notify.statusNone": "不显示",
"notify.statusHidden": "存在隐藏中的窗口",
"notify.statusAutoHide": "启用了自动隐藏",
"notify.statusHideCurrent": "启用了同时隐藏当前窗口",
"notify.statusFreeze": "启用了进程冻结",
"notify.statusElevated": "以管理员身份运行",
"notify.statusMonitorPaused": "热键监控已暂停",
"notify.trayPriorityNote":
"为每种颜色的圆点角标绑定一个状态,角标显示在托盘图标右下角;多个状态同时满足时,按红 > 绿 > 黄 > 蓝的优先级只显示一个圆点。选择「不显示」可置空该颜色。",
"notify.trayTooltip": "显示图标悬浮名称",
"notify.trayTooltipDesc": "鼠标悬停在托盘图标上时显示「Boss Key」;关闭后不显示任何文字,更隐蔽。",

"options.generalCard": "常规",
"options.muteAfterHide": "隐藏窗口后静音",
"options.muteAfterHideDesc": "隐藏时静音目标进程的音频,恢复显示时自动取消静音。",
Expand Down
19 changes: 18 additions & 1 deletion apps/config/ui/src/locales/zh-TW.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default {

"tab.binding": "視窗綁定",
"tab.hotkeys": "快速鍵與滑鼠",
"tab.notify": "通知設定",
"tab.notify": "提示設定",
"tab.options": "其他選項",
"tab.about": "關於與意見回饋",

Expand Down Expand Up @@ -170,6 +170,23 @@ export default {
"notify.onShow": "顯示視窗時通知",
"notify.onShowDesc": "每次復原顯示時顯示通知",

"notify.trayCard": "圖示狀態提示",
"notify.badgeRed": "紅色",
"notify.badgeGreen": "綠色",
"notify.badgeYellow": "黃色",
"notify.badgeBlue": "藍色",
"notify.statusNone": "不顯示",
"notify.statusHidden": "存在隱藏中的視窗",
"notify.statusAutoHide": "已啟用自動隱藏",
"notify.statusHideCurrent": "已啟用同時隱藏目前視窗",
"notify.statusFreeze": "已啟用程序凍結",
"notify.statusElevated": "以系統管理員身分執行",
"notify.statusMonitorPaused": "快速鍵監控已暫停",
"notify.trayPriorityNote":
"為每種顏色的圓點角標繫結一個狀態,角標顯示在通知區域圖示右下角;多個狀態同時滿足時,依紅 > 綠 > 黃 > 藍的優先順序僅顯示一個圓點。選擇「不顯示」可停用該顏色。",
"notify.trayTooltip": "顯示圖示懸浮名稱",
"notify.trayTooltipDesc": "滑鼠停留在通知區域圖示上時顯示「Boss Key」;關閉後不顯示任何文字,更為隱密。",

"options.generalCard": "一般",
"options.muteAfterHide": "隱藏視窗後靜音",
"options.muteAfterHideDesc": "隱藏時將目標程序靜音,復原顯示時自動取消靜音。",
Expand Down
Loading
Loading