Issue #102

Tana 全局快捷键自定义失效:schema 键名与实际读取键名不一致AI 主写

近 30 天6 次浏览5 位访客统计截至 2026-10-19 · GA4

背景

Tana 桌面端(Electron 应用)有一个全局语音捕获功能,默认快捷键 Cmd+Shift+E,通过 Electron 的 globalShortcut 注册——全局生效,Tana 在后台也会被触发。这个组合键容易和滴答清单的主窗口唤起(Cmd+Control+E)误触,所以想把它禁用掉。

思路是:把快捷键改成一个物理上不存在的键(如 F24),等效于禁用,又比塞非法字符串干净(注册能成功,不产生错误日志)。

踩过的坑

直接改配置文件 ~/Library/Application Support/Tana Outliner/keyBindings.json:

{
	"toggleVoiceCapture": "CommandOrControl+Shift+F24"
}

改完重启,Cmd+Shift+E 依然生效。看起来像是「设置了 custom 不会禁用 default,两者并存」——但这是错觉。

真正的原因:键名不一致

读 Tana 的 main.js(版本 1.523.0)取证,发现两处键名对不上:

位置键名
store schema 声明(KRe)toggleVoiceCapture(遗留名,全代码只出现一次,没有任何地方读它)
命令列表(dre)里的 commandvoiceCapture
实际读取:store.get(e.command)voiceCapture

快捷键管理器的注册逻辑本身是替换,不是并存:

bindAll() {
  this.globalShortcut.unregisterAll();            // 先全部注销
  for (let r of this.getKeyBindings()) {
    let n = r.customShortcut && typeof r.customShortcut == "string"
            ? r.customShortcut : r.defaultShortcut;  // 有 custom 就用 custom
    this.globalShortcut.register(n, ...);          // 只注册一个
  }
}

但 getKeyBindings() 读的是 store.get("voiceCapture"),而我们写的是 toggleVoiceCapture → 读到 undefined → falsy → 静默回退到默认值 Cmd+Shift+E。自定义配置被无声地忽略了。

本质上是 Tana 自己的一个 bug:schema 里的遗留键名和实际读取的 command 名不一致。

正确的写法

{
	"voiceCapture": "CommandOrControl+Shift+F24",
	"quickCapture": "CommandOrControl+Shift+Space"
}

Tana 的 GUI 入口(Options → Customize Keybindings)内部也是用 command 名写入的,所以这和 GUI 设置完全等效。

另外两个操作要点

  1. 改配置前必须先退出 Tana。Tana 用 electron-store 管理配置,App 退出时会把内存中的值写回文件;在它运行时改 JSON 会被静默覆盖。流程:备份 → 优雅退出 → 改配置 → 重启 → 读回验证。
  2. 不要试图用空字符串「禁用」。取值逻辑是 customShortcut || defaultShortcut,空串是 falsy,会静默回退默认值,你以为禁了其实没禁。要用合法但按不到的键(F24 在任何 Mac 键盘上都不存在,Electron accelerator 支持 F1–F24)。

替代方案:不一定要禁用全局捕获

Tana 还有个非全局的语音入口:Cmd+Shift+L(Quick chat with dictation),只在 Tana 前台生效。如果需要的只是「在 Tana 里语音输入」,禁用全局捕获后改用它就够了。代价是失去 Tana 语音捕获最值钱的能力——在别的 App 里(比如浏览器看资料时)不用切窗口直接说话记进 Tana。

一键禁用脚本

以下脚本可直接分享使用:自动退出 Tana → 备份 → 写入正确的键名(保留其他配置、清理无效的遗留键名)。已在 macOS 上验证。

#!/bin/bash
# 禁用 Tana 全局语音捕获快捷键(Cmd+Shift+E → 永不触发的 F24)
# 原理:Tana 读取 keyBindings.json 里的 "voiceCapture" 键作为自定义快捷键,
#       自定义存在时完全替换默认值。F24 在 Mac 键盘上不存在,等效禁用。
# 注意:schema 里的 "toggleVoiceCapture" 是无人读取的遗留键名,写了无效。
set -euo pipefail

KB="$HOME/Library/Application Support/Tana Outliner/keyBindings.json"

# 1. 退出 Tana(electron-store 会在退出时把内存值写回文件,运行中修改会被覆盖)
if pgrep -f "Tana Outliner" > /dev/null; then
  echo "正在退出 Tana..."
  osascript -e 'quit app "Tana Outliner"' || true
  for _ in $(seq 1 10); do
    pgrep -f "Tana Outliner" > /dev/null || break
    sleep 1
  done
  if pgrep -f "Tana Outliner" > /dev/null; then
    echo "错误:Tana 未能退出,请手动退出后重试" >&2
    exit 1
  fi
fi

# 2. 备份现有配置
if [ -f "$KB" ]; then
  cp "$KB" "$KB.bak.$(date +%Y%m%d-%H%M%S)"
  echo "已备份原配置"
fi

# 3. 合并写入(保留其他键,清理无效的遗留键名)
python3 - "$KB" << 'EOF'
import json, os, sys
path = sys.argv[1]
data = {}
if os.path.exists(path):
    with open(path) as f:
        data = json.load(f)
removed = data.pop("toggleVoiceCapture", None)
data["voiceCapture"] = "CommandOrControl+Shift+F24"
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as f:
    json.dump(data, f, indent="\t", ensure_ascii=False)
    f.write("\n")
if removed:
    print(f"已清理无效键 toggleVoiceCapture(原值: {removed})")
print("已设置 voiceCapture = CommandOrControl+Shift+F24")
EOF

echo "完成。启动 Tana 后,Cmd+Shift+E 将不再触发全局语音捕获。"
echo "回滚:Tana 菜单 → Options → Customize Keybindings → Voice capture 行点 × 清除自定义。"

如果只想粘贴一行(需先手动退出 Tana):

python3 -c "import json,os; p=os.path.expanduser('~/Library/Application Support/Tana Outliner/keyBindings.json'); d=json.load(open(p)) if os.path.exists(p) else {}; d.pop('toggleVoiceCapture',None); d['voiceCapture']='CommandOrControl+Shift+F24'; os.makedirs(os.path.dirname(p),exist_ok=True); json.dump(d,open(p,'w'),indent='\t',ensure_ascii=False); print('已禁用,重启 Tana 生效')"

经验总结

逆向修改 Electron 应用的配置文件前,先确认代码实际 get() 的键名,而不是看 schema 声明。 schema 可能留有历史遗留键名,写错了不会报错,只会静默失效——排查时容易误判成「配置机制和预期不同」(这次就误判成了 custom/default 并存)。

讨论

0 条留言

去 GitHub 留言 ↗

还没有留言,欢迎来打个招呼。

回到顶部