跳转至

每用户一个 Hex 进程,承载 Rune 窗口

状态: 已在 dev 上为每个操作系统实现;GPUI 已删除(参见 rune-ui.md)。Linux 以每用户和数据根目录一个进程的方式运行,支持启动转发、单实例套接字、Type=notify systemd 用户单元,以及 Rune 窗口和 HUD 覆盖层(参见 ../linux.md);macOS 是一个菜单栏进程(macos_rune.rs);Windows 是一个托盘进程(windows_rune.rs)。

架构

Hex 在每个操作系统上都是每用户和数据根目录一个长期运行的进程:

hex (no instance)    -> become the instance (or the supervisor starts it) -> open window unless hidden
hex (instance)       -> forward Activate{activation token} -> exit 0
hex dictation start  -> forward Shortcut -> exit 0 | non-zero without an instance (never starts one)
close window         -> Rune window closed or hidden; runtime, listener, HUD, and models stay
quit                 -> explicit: tray / menu-bar Quit, "Quit Hex" in the window, `hex quit` / `hex stop`

运行时线程 拥有所有 Hex 状态和非 UI 工作(LinuxDesktopHost、WindowsDesktopHost 或 macOS 运行时),每次更改后发布 Arc<Published> 快照,并唤醒订阅的 UI 句柄。将运行时置于 UI 线程之外,可确保听写、模型准备和快捷键命令在没有任何窗口打开时也能继续运行,并且永远不会让渲染停滞。

主线程 运行 rune::App(windowless、keep_running、on_close、AppHandle::{activate, open, update, quit} 和 App::overlay)。关闭窗口会在 macOS、Windows 和 X11 上隐藏窗口,并在 Wayland 上销毁 surface,同时保留进程内的 Runtime<Ui> 状态。

Linux

决策:单实例套接字 + 每数据根目录的锁

排他性和转发都以数据根目录(settings.json、历史记录、监听器锁、模型,以及测试、Nix 和开发使用的隔离 HEX_APPLICATION_SUPPORT_DIR 根目录)为键。linux_service.rs 使用 desktop::instance::acquire("service") 保护该根目录中的 service.sock,并通过仅所有者权限、对端 UID 检查、有界版本 2 帧、截止期限以及陈旧套接字恢复(scripts/test-linux-service.py)进行防护:

enum Request {
    Activate(Activation),        // activation_token, startup_id, session, show
    Shortcut(ShortcutCommand),   // straight from socket thread to listener queue
    Status,                      // Published snapshot for `hex status`
    Quit,                        // drain accepted work and exit
}

已拒绝的备选方案:

  • D-Bus 知名名称(org.freedesktop.Application): 按会话总线而不是数据根目录划分作用域(会破坏隔离的测试/开发实例),需要重命名 hex.desktop(使 linux_portal::APP_ID "hex" 和窗口规则失效),并且需要在 UI 线程上运行 glib 主循环。
  • 抽象命名空间套接字: 按网络命名空间而不是数据根目录划分作用域,并且该命名空间中的任何本地进程都可以访问。
  • 锁文件 + 信号: 无法携带激活令牌、状态回复或结构化错误。

决策:Systemd 继续作为登录监督者

托管安装和 Home Manager 保留 hex.service 用户单元,使用 Type=notify(在 service.sock 绑定后 READY=1)、PartOf=graphical-session.target 和 Restart=on-failure:

connect(socket) ok        -> Activate{token from XDG_ACTIVATION_TOKEN / DESKTOP_STARTUP_ID, session, show} -> exit 0
no instance, managed unit -> write session.env; systemctl --user start hex.service (Type=notify) -> Activate{token, show}
no instance, otherwise    -> become the instance in this process (custom data root, cargo run, non-systemd)
  • 崩溃重启会运行 ExecStart=… service,但不会打开窗口;只有显式的 Activate 且 show: true 才会打开窗口。
  • session.env 将 display、session、cursor-theme、IME、XDG_DATA_DIRS 和 locale 变量导入被监督的进程,使 Rune 窗口以桌面的主题和输入法渲染,同时在切换会话类型时清除过期的 X11/Wayland 变量。
  • service、app、app --hidden、start、stop 和 quit 仍是有效的入口点,因此自我更新只需替换可执行文件。
  • 在 Linux 启动时,transcription::local_transcriber::initialize_backends() 会在任何工作线程或 Rune 窗口启动之前,于主线程上创建 transcribe.cpp 的 Vulkan 实例,从而避免在加载器中并发创建 Vulkan 实例。
  • 录制/处理胶囊是一个 Rune App::overlay(linux_rune_hud.rs):在 Wayland 上是 layer-shell,在 X11 上是 override-redirect。

Rune 下的 Windows

windows/rune.rs、 windows/app.rs 和 rune_ui/windows_host.rs:

  • 单实例使用命名自动重置事件 Local\anomaly.hex.app。第二次启动会授予 AllowSetForegroundWindow、发出事件信号并退出(--hidden 不发出信号直接退出)。
  • 主线程创建 tray-icon(由 winit 的 Win32 消息循环驱动),并运行带有 keep_running() 的 rune::App(对于 --hidden 则使用 windowless())。托盘的 Show 和重新启动等待者调用 AppHandle::open;托盘的 Quit、侧边栏的 Quit Hex、Ctrl-C 以及更新重启都会调用 AppHandle::quit。如果没有托盘,关闭窗口将退出 Hex。
  • 运行时线程拥有 WindowsDesktopHost(监听器、TranscriptionPreparation、OpenCode 目录、个人转换和 WindowsUpdater),发布 Arc<Published>,并唤醒窗口。
  • 快捷键捕获在窗口内轮询键盘状态,同时抑制低级钩子,因为仅修饰键组合不会作为窗口按键事件到达。
  • HUD(windows_rune_hud.rs)在置顶、点击穿透、不激活的 Rune 覆盖层中更新 desktop_indicator_model,并将 desktop_indicator_shader 着色到 rune::Pixels。

macOS 下的 Rune

macos_rune.rs 和 rune_ui/macos_host.rs:

  • LaunchServices 强制单实例运行。主线程执行 rune_ui::app().accessory(),当设置完成且“显示 Dock 图标”选项关闭时以无窗口方式启动,否则打开窗口。
  • 由于 winit 拥有 NSApplicationDelegate,Hex 为 kAEReopenApplication 注册了一个 NSAppleEventManager 处理器,以响应 Dock 和 Finder 的重新打开事件。
  • 主线程上的一个初始 AppHandle::update 闭包启动更新器、NSStatusItem(macos_rune/status_item.rs)、Hex/File/Window 主菜单(省略 Edit 菜单,以便 ⌘C/⌘V 到达 Rune 文本字段)、重新打开处理器以及原生 Metal HUD(dictation_indicator.rs)。一个 16 ms 的 NSTimer 在常见 run-loop 模式下持续排空 HUD、状态项、会议和开发者控制通道,即使没有窗口打开。
  • 打开窗口会显示 Dock 图标并激活 Hex;关闭窗口则隐藏窗口并恢复已保存的 Dock 策略。
  • 运行时线程拥有 TranscriptionPreparation(与菜单栏中仅显示已安装模型的模型选择器共享),一旦设置就绪便启动监听器,并将状态发布到窗口和菜单栏。监听器、会议控制器(meeting_watcher::controller_loop)和 local_api 各自在独立线程上运行。
  • macos_host::MacHost 实现 NativeHost,用于权限与指南、系统音频、登录项 worker、Hex Microphone、会议操作和 1 Hz meeting::query 重新扫描、命令目录与工作区,以及导览练习事件。快捷键捕获使用 AppKit 本地事件监视器,并暂停监听器 tap。
  • 会议胶囊(rune_ui/meeting_capsules.rs)是交互式、非激活的 Rune 覆盖层,由主循环上的控制器事件驱动。听写 HUD 仍然是 dictation_indicator.rs 中的原生 Metal NSPanel。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 hex19/hex