Orisumi

桌面客户端

菜单栏客户端托管本机的桌面 Observer,负责启停、状态和设置,只读展示它们当天的数据;陈述由各 Observer 负责

背景

前台状态 Observer 是一个命令行程序:在终端里启动,终端关闭后采集就停了;发送状态写在标准错误中,用户平时看不到。桌面端之后还有输入事件和设备状态两个 Observer,它们需要系统权限,要有界面引导用户授权。用户需要一个常驻的程序来运行这些 Observer,并能看到它们是否正常工作。

决定

  • 每个桌面平台有一个客户端,常驻在菜单栏或系统托盘。macOS 客户端用 SwiftUI 编写。
  • 客户端托管本机的桌面 Observer:启动、停止和暂停采集,显示发送状态,管理设置和登录时启动,引导用户授予系统权限。
  • 陈述由各 Observer 负责,客户端只托管它们,所以客户端没有自己的实体。
  • Observer 以 helper 进程的形式打包进客户端,继续使用平台 SDK,沿用命令行程序默认的身份文件。命令行入口保留,用于开发和测试。
  • 客户端逐行读取 helper 标准输出中的 JSON 事件,用信号停止 helper。暂停就是停止 helper,设置变化后重启 helper。
  • 暂停和停机期间没有读数,这段时间在记录中是空白。
  • 客户端只读展示它托管的 Observer 当天的数据,数据从 API 读取,再补上本地还没发送的部分。浏览更早的数据和追溯用 Web 控制台,客户端提供入口。

理由

SwiftUI 直接提供菜单栏、登录项和权限请求,这些在 .NET 中都要靠 AppKit 互操作实现。Observer 和 SDK 留在 .NET 中,可靠提交只需维护一份实现,见 ADR · 提交与 SDK;Windows 客户端也能托管同一批 Observer。

客户端只启动、展示和停止,陈述由形成它的 Observer 负责,见 OBSERVER-6。所以客户端不需要身份,记录中也不出现它。

helper 沿用命令行程序的身份文件,已有的程序实例在客户端中继续运行,见 OBSERVER-4。身份文件的锁保证同一时刻只有一个进程使用它。

运行、停止和报告状态,用标准输出和信号就能表达。helper 不监听端口,单独运行时,它的输出也可以直接阅读。暂停沿用停止的规则:当前时段结束于最后一次确认,恢复后的读数开始新的时段。

用户打开客户端,最常问的是“今天用了什么”。这些数据从 API 读取,客户端重启后记录仍然完整,看到的和 Web 控制台是同一份数据。尚未发送的时段只在本地,所以客户端把它们补上。其他日期、其他 Observer 和追溯需要完整的读取界面,留给 Web 控制台。

macOS 按应用授予权限。helper 由客户端启动,系统把它的权限算在客户端名下,用户只需授权一个应用。

放弃的方案:

  • 客户端和 Observer 都用 Swift。 要再实现一份 SDK,前台状态 Observer 也要重写。
  • 客户端用 .NET 编写。 菜单栏、登录项和权限引导都要靠 AppKit 互操作。
  • 客户端和 helper 通过本地 socket 双向通信。 可以查询状态、下发命令,但暂停和修改设置用重启 helper 已经够用,socket 还要多维护一套协议。

代价

  • 客户端由 Swift 和 .NET 两部分组成,构建时要先发布 helper,再打包进应用。
  • 暂停期间 helper 不运行,待发送的版本留在本地,恢复后才继续发送。
  • 客户端运行时,命令行程序要使用另一个身份文件才能同时运行,那是另一个程序实例。
  • 每个桌面平台各写一个客户端外壳。
  • 客户端依赖 API 的读取接口。每个时段的内容要单独读取一次,读过的版本在客户端缓存。

规则位置

  • Desktop · 客户端
  • 运行与构建:src/Clients/Orisumi.Mac/README.md
  • helper 的参数和输出:src/Observers/Orisumi.Observer.ForegroundState/README.md

贡献者

页面历史