OpenLogi 配置指南:深入解析 config.toml 的 schema、字段与 Actions 绑定
2026/9/13 7:59:53 网站建设 项目流程

OpenLogi 配置指南:深入解析 config.toml 的 schema、字段与 Actions 绑定

【免费下载链接】OpenLogi⚡️A native, local-first alternative to Logitech Options+, written in Rust 🦀 — remap buttons, DPI, and SmartShift over HID++. No account, no telemetry.项目地址: https://gitcode.com/GitHub_Trending/op/OpenLogi

OpenLogi 是一款使用 Rust 编写的本地优先(local-first)鼠标/键盘/摄像头配置工具,全部设置以纯 TOML 形式存放在一个config.toml中,GUI 与后台 Agent 共享同一份文件。本文以 docs/CONFIGURATION.md 为骨架,结合 docs/config.example.toml 完整示例与openlogi-core的源码实现,系统讲解配置文件的位置与读写机制、schema 结构、每个字段的取值范围与默认值、Actions 绑定语法,以及 v1–v7 的迁移规则。读完本文,你将能安全地手工编辑配置文件、理解 GUI 每次保存时发生了什么,并正确写出可被加载的单键、长按对、手势方向表与 Actions Ring 布局。

配置文件位置:三平台统一遵循 XDG 规范

OpenLogi 在所有平台(包括 macOS 与 Windows)上都遵循 XDG Base Directory 规范:

  • macOS 与 Linux:$XDG_CONFIG_HOME/openlogi/config.toml(通常是~/.config/openlogi/config.toml
  • Windows:%USERPROFILE%\.config\openlogi\config.toml

注意 macOS 刻意不使用其原生的~/Library/Application Support。这一设计在 crates/openlogi-core/src/paths.rs 中有明确注释说明("Decision (#347)"),是刻意的最终决定而非权宜之计——因为一旦 Agent 已随 Windows 产物发布,再迁移位置会让所有存量用户的config.toml与首启状态"搁浅"。

源码层面,paths::config_path()xdg_config_home()拼上openlogi(或开发构建的openlogi-dev)子目录得到;配置、数据、状态与运行时目录分别对应:

用途环境变量覆盖默认位置
配置(config.toml$XDG_CONFIG_HOME~/.config/openlogi
数据(设备渲染资源缓存等)$XDG_DATA_HOME~/.local/share/openlogi
状态(Agent 轮转日志等)$XDG_STATE_HOME~/.local/state/openlogi
运行时(Agent IPC socket)$XDG_RUNTIME_DIR/openlogi

在 Windows 上$HOME会回退到%USERPROFILE%,因此路径解析为%USERPROFILE%\.config\openlogi等。开发构建(OPENLOGI_PROFILE=dev或 macOS dev bundle 标识)会使用独立的openlogi-dev目录,避免开发 Agent 抢占已安装应用的 socket、锁、配置与资源缓存。

编辑与恢复:原子写入、轮转备份与冲突保护

原子写入与 5 代备份

GUI 保存配置时采用原子写入(atomic write),并保留config.toml.backup.1config.toml.backup.5五份轮转备份。相关逻辑位于 crates/openlogi-core/src/config/file.rs:backup_config_once只会在每个进程生命周期内备份一次(BACKED_UP_CONFIGS集合去重),backup_existing_config先把旧备份向后顺延一代,再把当前文件写入.backup.1。写入通过atomic_write_file完成,Unix 下还显式设置0o600权限并关闭preserve_mode

更关键的是:备份链不会把旧的.backup.1顺延覆盖.backup.5(循环从CONFIG_BACKUP_GENERATIONS-1即 4 开始倒序),保证第 1~5 代各有内容。

保留注释与格式

GUI 更新已知字段时不会把整个文件重写为机器生成的 TOML。render_config会把新配置序列化为字符串后,与磁盘上的原文件做一次toml_edit文档合并(reconcile_table/reconcile_item):不存在的键被移除,值被替换,但原有注释、装饰与排版会被保留reconcile_item会从旧值上取下 decor 装饰重新贴到新值上)。所以你可以放心在文件里写注释,GUI 保存不会抹掉它们。

严格 schema:错误立即失败,而不是悄悄吞掉

配置文件的 schema 是严格的:

  • 拼写错误的字段、已废弃的字段(如 v2 之前的button_bindingsgesture_bindings,v3 之前的gesture_owner)、超出取值范围的字段都会阻止配置加载,而不是静默采用默认值或在下次保存时消失;
  • 加载失败时 GUI 会以只读模式打开,并显示精确的 TOML 错误信息(ConfigError::Parse携带行列信息)。修复文件后重新启动 OpenLogi 即可。

顶层ConfigAppSettingsDeviceConfig等均标注了#[serde(deny_unknown_fields)],这正是"未知字段=硬错误"的机制来源。亮度、DPI、SmartShift 阈值等还带有自定义反序列化校验(例如亮度超过 100、SmartShiftauto_disengage低于 8 都会被拒绝并给出具体错误消息)。

外部编辑的冲突保护

如果在 GUI 打开期间你在编辑器中修改了文件,下一次 GUI 保存会被拒绝ConfigError::Conflict),而不会覆盖你的外部修改——因为ConfigFile::save会先比较磁盘当前内容与加载时记录的内容(source),不一致即拒绝。此时重启 OpenLogi 以加载新版本。

另外,打开 GUI 会通知常驻 Agent 重新加载当前文件(ReloadConfig),因此手工编辑与运行时行为能立即收敛,无需重启 Agent。

文件结构(Shape)

顶层字段

  • schema_version必填,当前为7。加载时先读这个头部(ConfigHeader),再决定后续解析与迁移策略。
  • selected_device:可选,当前选中设备的物理键(physical device key),持久化后重启会恢复上次浏览的设备视图;缺失时回退到第一个设备。
  • [app_settings]:应用级全局偏好。
  • [devices."<physical-key>"]:每个物理设备的配置。
  • [keyboard.bindings]:与设备无关的全局键盘触发绑定。

[app_settings]:应用级偏好

AppSettings在 crates/openlogi-core/src/config/settings.rs 中定义,字段全部带#[serde(default)]以保持向后兼容(旧配置缺失新字段时沿用默认值)。核心字段如下:

字段类型/取值范围默认值说明
launch_at_loginbooltrue登录时启动后台 Agent。默认开启:Agent 是保持按键映射生效的进程,默认关闭等于"重启即失效"。macOS 上是"沉没开关"(SMAppService 登录项始终注册,可在系统设置 › 登录项撤销);Linux/Windows 上 Agent 会同步 autostart 单元 / Run 键
check_for_updatesboolfalse更新检查,默认关闭以兑现"无遥测、无自动更新轮询"承诺。开启后每次启动只发一次HEAD请求并记录是否有新版本,不自动下载
auto_install_updatesboolfalsecheck_for_updates为 true 且发现新版本时后台下载并暂存,下次重启时应用(绝不在会话中途应用、绝不自动重启)。未签名开发构建中验证失败即关闭
show_in_menu_barbooltruemacOS 菜单栏图标 / Windows 通知区(托盘)图标;false时 Agent 无可见存在。Linux 忽略此字段
capture_mouse_eventsbooltrue是否安装 OS 级鼠标钩子(CGEventTap / 排他evdev抓取 /WH_MOUSE_LL)拦截鼠标事件用于按键重映射。false是逃生舱:完全不动输入设备(Linux 不做排他抓取,macOS 跳过启动时的辅助功能授权弹窗)。HID++ 侧功能(DPI、SmartShift、手势键、拇指滚轮)不受影响。Agent 重启后生效
smooth_scrollboolfalse是否把传统鼠标滚轮输入替换为有限平滑滚动动画。默认关闭;开启后 OS 钩子仅在其非阻塞滚动 worker 接受事件后抑制物理滚轮事件。触控板等连续像素输入始终保持原生。Windows 低级钩子无法把滚轮消息归属到设备,因此该偏好作用于所有传统滚轮消息
vertical_scroll_sensitivity整数110014(=1×)传统纵向滚轮的距离倍率;14为 1×。触控板等连续像素输入永不缩放。scroll_multiplier()计算为value / 14
thumbwheel_sensitivity整数110014(=1×)拇指滚轮响应度:同时影响连续滚动速度与自定义滚轮动作触发的旋转增量阈值(action_threshold = (2*14 - value).max(1))。仅在偏离默认值时才会把滚轮从原生滚动中"接管"
auto_download_assetsbooltrue设备出现时是否自动下载设备图片资源;false时完全不发起资源网络请求(回退到内置美术与合成剪影),Settings 中手动"刷新资源"仍可按需拉取
asset_sourceautomatic/openlogi/cloudflare/fastlyautomatic资源下载镜像偏好:automatic并发竞争所有内置镜像取首个健康源,其余为固定来源。进程级环境变量OPENLOGI_ASSETS仍是开发/诊断用覆盖项
language可选字符串(如"en""de""pt-BR""zh-CN"None(跟随系统)UI 语言,BCP-47 风格 locale 码,须匹配 GUI 内置 locales。存于此处使显式选择在重启后仍生效
appearancesystem/light/darksystem跟随系统 / 强制浅色 / 强制深色
ui_scalesmall/normal/large/extra_largenormal文本与 rem 间距比例:90% / 100% / 110% / 125%
device_view_modegrid/list/carouselgrid首页设备画廊布局
app_iconopenlogi/prismopenlogi应用图标(仅 macOS 生效:Windows 图标编译时嵌入可执行文件,Linux 由包安装固定图标)
theme_light/theme_dark可选主题名字符串None(品牌默认主题)浅/深色模式使用的主题名
ui_radius可选0/6/12(像素)None(主题自带圆角)UI 圆角覆盖

值得注意的细节:smooth_scroll与两种 sensitivity 的取舍在源码中有明确意图——平滑滚动只处理传统滚轮输入,触控板原生;vertical_scroll_sensitivity只改滚动距离、永不变更自定义动作的触发阈值,而thumbwheel_sensitivity两者都管。AppSettings::is_default会在全默认时把整个[app_settings]表从序列化结果中省略。

[devices."<physical-key>"]:设备级配置

设备表的键是物理设备键(physical device key),例如接收器设备形如receiver:<receiver-id>:slot:<number>;直连(direct)、raw-HID 与摄像头设备使用其他生成的键。不要用模型 ID(如2b042)代替——schema 5 起设备按"它是什么"(unit:<hex>serial:<s>)而不是"通过哪条路由到达"来定键,设置会跟着设备在接收器与线缆之间移动,而不会分裂成两条记录。

一个没有 USB 序列号的摄像头没有唯一的端口稳定身份,因此其custom_name键跟随 OS 捕获 ID,使两台同型号摄像头可区分;把它换到另一个 USB 端口可能需要重新命名。

常用设备字段(DeviceConfig,见 crates/openlogi-core/src/config/device.rs):

  • custom_nameenabled(默认truefalse时设备完全原生:无捕获会话、不重放易失设置)、dpidpi_presets
  • thumbwheel_sensitivity(可选,缺省回退到应用级值)、invert_scroll(仅纵向、仅当设备支持 HID++ 原生滚轮反转时)、scroll_resolutionlow/high,对应 HID++0x2121 HiResWheel的逐棘轮报告 vs 棘轮间细粒度报告);
  • bindings:按键映射,见下文 Actions 一节;
  • per_app_bindings:按应用覆盖的稀疏动作表,键为 macOS bundle id、Linux application id、精确小写的 Windows 可执行路径或exe:<filename>.exe
  • action_ring:默认与完整的按应用八槽布局;
  • lighting(静态 RGB 颜色 + 亮度 + 开关;颜色为 6 位十六进制"RRGGBB"#前缀,可容忍旧版带#前缀)、smartshift、独立light(如 Logitech Litra,亮度存为归一化百分比)以及摄像头控制/配置文件;
  • host_switch_targetsfn_lock(兼容键盘):前者是跟随该键盘切换主机的指向设备物理键列表(键盘发起、先切目标再让键盘离开当前主机);后者为 HID++ fn 反转状态(0x40a2/0x40a3),true表示无需按住 Fn 即可输出 F1–F12,状态存设备 RAM 每主机一份,Agent 重连时重放;
  • identitydisabled_gestures:应用托管的元数据。identity是设备在线时捕获的静态模型快照(名称/种类/能力),使设备在休眠或冷启动未探测完成前仍能渲染卡片与正确面板;disabled_gestures是关闭手势模式时暂存的定制方向表,重新开启时原样恢复。
SmartShift 的取值约束

schema 中 smartshift段包含三个字段(定义见settings.rsSmartShift):

  • modefree(自由旋转)或ratchet(棘轮/有刻度滚动);
  • auto_disengage:智能释放阈值,合法范围0x080xFE(步进 0.25 转/秒),0xFF表示永久啮合棘轮。低于 8 的值会被拒绝加载SMARTSHIFT_MIN_AUTO_DISENGAGE):阈值过低会导致棘轮在日常滚动速度下就释放成自由旋转,滚轮"卡不住";0同时是固件"不修改"哨兵值,绝不能作为真实值存储。默认16(≈4 转/秒);
  • tunable_torque:固件可调扭矩等级(1255),设备不支持时存0(解析为None)。

这些值是易失的——写入设备 RAM 后掉电即失(issue #189),所以必须持久化以便重连时重放。settings.rs中的测试smartshift_rejects_values_outside_the_persisted_contract验证了边界:低于最小值的拒绝、最小值与最大值(0xff/0xff)合法、扭矩 0 解析为不支持。

[keyboard.bindings]:全局键盘触发

[keyboard.bindings]是独立于设备的全局按键触发映射,例如f1shift+command+f5。支持的修饰键为shiftcontroloptioncommand,并接受别名ctrlaltcmd。解析器(crates/openlogi-core/src/config/key_trigger.rs)按[mod+]+key格式解析:所有段除最后一个是修饰键外,最后一个必须是键名,当前支持escf1f19(映射到 macOS 虚拟键码kVK_*)。键名与修饰键都不支持未知值——未知修饰键/键会返回ParseTriggerErrorFn不出现在触发器中(固件内部机制,不能作为触发器)。

完整配置示例

仓库中的 docs/config.example.toml 是经过测试的完整示例。官方建议只复制你需要的段落,并把其中示例性的物理设备键替换为 OpenLogi 已为你的设备写入的真实键。下面完整呈现并分段注释:

# OpenLogi configuration example. Copy only the sections you need. schema_version = 7 selected_device = "receiver:aabbccdd:slot:1" [app_settings] launch_at_login = true check_for_updates = false auto_install_updates = false show_in_menu_bar = true capture_mouse_events = true smooth_scroll = false vertical_scroll_sensitivity = 14 auto_download_assets = true asset_source = "automatic" language = "en" thumbwheel_sensitivity = 14 appearance = "system" ui_scale = "normal" device_view_mode = "grid" # macOS only: "openlogi" (the signed icon) or "prism". app_icon = "openlogi" [devices."receiver:aabbccdd:slot:1"] custom_name = "Office mouse" dpi = 1600 dpi_presets = [800, 1600, 3200] thumbwheel_sensitivity = 20 invert_scroll = false scroll_resolution = "high" [devices."receiver:aabbccdd:slot:1".bindings] Back = "BrowserBack" Forward = "BrowserForward" # Hold the chord for exactly as long as the physical button is held. MiddleClick = { HoldShortcut = "Ctrl+Space" } # Release before 500 ms runs `short`; reaching 500 ms runs `long` once. DpiToggle = { short = "ShowDesktop", long = "MissionControl" } # The thumb wheel's capacitive tap. Inert unless set here; the wheel also # reports taps from incidental thumb contact. Thumbwheel = "AppExpose" [devices."receiver:aabbccdd:slot:1".bindings.GestureButton] Click = "MissionControl" Up = "MissionControl" Down = "AppExpose" Left = "PreviousDesktop" Right = "NextDesktop" [devices."receiver:aabbccdd:slot:1".per_app_bindings."com.microsoft.VSCode"] Back = "Undo" [devices."receiver:aabbccdd:slot:1".per_app_bindings."exe:sharex.exe"] MiddleClick = { CustomShortcut = "F1" } [devices."receiver:aabbccdd:slot:1".action_ring] enabled = true haptics = true [devices."receiver:aabbccdd:slot:1".action_ring.default.slots] Top = { action = "Copy", icon = "Keyboard" } Right = { action = { OpenApplication = { path = "/Applications/Safari.app", display_name = "Safari" } }, icon = "Applications" } Bottom = { action = "ShowDesktop", label = "Desktop" } [devices."receiver:aabbccdd:slot:1".lighting] enabled = true color = "ff0000" brightness = 80 [devices."receiver:aabbccdd:slot:1".smartshift] mode = "ratchet" auto_disengage = 16 tunable_torque = 50 # Put host-switch links on the keyboard's physical entry. [devices."receiver:aabbccdd:slot:2"] host_switch_targets = ["receiver:aabbccdd:slot:1"] fn_lock = false [devices."receiver:aabbccdd:slot:2".bindings] KeySearch = "MissionControl" KeyScreenCapture = "Sleep" # Since schema 5 a device is keyed by what it *is* — `unit:<hex>` or # `serial:<s>` — rather than by the route it was reached on, so its settings # follow it between its receiver and a cable instead of splitting into two # entries. `receiver:…` entries above are the pre-5 shape; they are folded onto # an identity key the first time the device is seen online with the GUI running. [devices."unit:6be9d300"] dpi = 1600 # Every route this device has been seen on. The set of keys is also the index # that identifies the device while it is asleep and only the route is known, # which is why a route with nothing special about it is still written out as an # empty table. [devices."unit:6be9d300".links."receiver:aabbccdd:slot:3"] # Capabilities as measured on one link. A device can genuinely expose different # features per transport — a G502 LIGHTSPEED publishes hi-res wheel over its # receiver and not over USB — so this is recorded per link, not per device. [devices."unit:6be9d300".links."direct:046d:c08d".capabilities] buttons = true pointer = true lighting = false scroll_inversion = true hires_wheel = false # Settings deliberately made different on this link. Anything not overridden # here falls through to the device-level value above. [devices."unit:6be9d300".links."direct:046d:c08d".overrides] dpi = 800 # Global function-key remapping, independent of a device. [keyboard.bindings] f1 = "MissionControl" "shift+command+f5" = "ShowDesktop"

示例中值得注意的要点:

  • per_app_bindings的键是应用标识符,来自 Agent 前台应用的 Profile 选择器——它是唯一保证能匹配的标识符集合,因为四个平台对应用的命名各不相同,在一个命名空间下编写的 profile 不会在另一个命名空间匹配;
  • links下的capabilities按链路记录的(同一设备在不同传输上可能暴露不同特性),overrides则是用户刻意在该链路上做差异化的设置,未覆盖的项回落到设备级值(effective_*系列方法先查链路覆盖再回退设备值);
  • 一个空links."receiver:…"表也是必要的——链路键集合本身就是设备在休眠且仅知路由时用来识别设备的索引。

Actions:动作绑定详解

动作名称是序列化后的 Rust 枚举变体名(Action,定义于 crates/openlogi-core/src/binding/action.rs),例如CopyBrowserBackPlayPauseCycleDpiPresetsShowActionsRing变体名是稳定的磁盘 schema:已有名字冻结,新增变体只能追加在末尾,删除或重命名必须伴随schema_version提升与迁移。带载荷的动作使用单键内联表:

Back = { CustomShortcut = "Cmd+Shift+P" } Forward = { HoldShortcut = "Ctrl+Space" } MiddleClick = { OpenApplication = { path = "~/Downloads", display_name = "Downloads" } } DpiToggle = { short = "ShowDesktop", long = "MissionControl" }

动作的序列化采用 serde 外部标签(external tagging):无载荷变体序列化为裸字符串("BrowserBack"),元组变体序列化为单键表({ CustomShortcut = "my chord" })。

完整动作词汇表(无载荷变体)

动作按类别组织,常用无载荷动作包括:

  • 鼠标LeftClickRightClickMiddleClickMouseBack(真实鼠标第 4 键,浏览器原生解释为"后退",优于发送 ⌘[ 的BrowserBack)、MouseForward
  • 编辑CopyPasteCutUndoRedo(⌘⇧Z / Ctrl+Shift+Z,如需 Ctrl+Y 用CustomShortcut兜底)、SelectAllFindSave
  • 浏览器/导航BrowserBackBrowserForwardNewTabCloseTabReopenTabNextTabPrevTabReloadPage
  • 窗口/系统MissionControlAppExposePreviousDesktopNextDesktopShowDesktopLaunchpadShowLockScreenScreenshotCaptureRegionSleepShowActionsRing(在当前指针位置打开 Actions Ring,由 Agent 处理会话而非 OS 注入器);
  • 媒体PlayPauseNextTrackPrevTrackVolumeUpVolumeDownMuteVolume
  • DPI/滚动CycleDpiPresets(按序步进dpi_presets)、ToggleSmartShiftScrollUpScrollDownHorizontalScrollLeftHorizontalScrollRight
  • 无操作None(完全抑制输入,被捕获但不合成任何 OS 事件)。

带载荷变体:SetDpiPreset(u8)(跳到 DPI 预设列表中的第 N 档,越界时触发时钳制)、CustomShortcut(KeyCombo)HoldShortcut(KeyCombo)TypeText(String)(逐字符键入 Unicode 字符串)、RunAppleScript(String)osascript -e)、RunShellCommand(String)/bin/sh -c)、Workflow(Vec<WorkflowStep>)(按序执行带Delay的步骤序列)、OpenApplication(ApplicationTarget)。其中TypeTextRunAppleScriptRunShellCommandWorkflow是面向高级用户的逃生舱动作,不出现在默认目录中。

CustomShortcut 与 HoldShortcut 的区别

  • CustomShortcut立即发出按下/抬起(key-down/key-up)键对;
  • HoldShortcut保持和弦按下,直到触发它的物理按键释放,并且在捕获被中断、绑定失效或 Agent 关闭时也会释放。它适合**按住说话(push-to-talk)**以及其他"按住激活"型控制。OS 事件合成实际由openlogi-injectcrate 的execute完成(如 macOSCGEventPost),openlogi-core保持平台无关。

短按/长按对:{ short = ..., long = ... }

{ short = ..., long = ... }绑定会等待按键的结果而不是按下即触发:

  • 500ms 之前释放 → 触发short
  • 按住达到 500ms → 恰好触发一次long,随后的释放不再触发short
  • 若在任一结果产生前捕获被中断、绑定改变或 Agent 关闭,则两个动作都不触发;
  • 只能报告瞬时按键脉冲的输入源回退到short
  • long本身可以是HoldShortcut,此时它的和弦从 500ms 阈值起保持按下直到物理释放。

阈值常量LONG_PRESS_THRESHOLD = Duration::from_millis(500)定义于 crates/openlogi-core/src/binding/value.rs。在 crates/openlogi-core/src/binding/value.rs 中,Binding#[serde(untagged)]枚举:Single(Action)序列化为裸动作、Gesture(BTreeMap<GestureDirection, Action>)序列化为方向名键表、LongPress(LongPressBinding)序列化为结构可区分的{ short = ..., long = ... }表——三个分支靠结构区分(动作变体名与手势方向名零重叠,长按表要求short/long两个小写字段并拒绝未知字段),并有binding_untagged_*测试守卫这些路由不变量。

重要限制:长按对目前仅适用于设备级bindings,且只能通过 TOML 编写。GUI 只呈现其中的short动作;在 GUI 中更改该按钮会用所选单动作替换整个长按对。per_app_bindingskeyboard.bindings始终是单动作映射。

手势方向表

GestureButton等可手势按钮的绑定是一个方向键表,方向包括ClickUpDownLeftRight。手势模式是每个按钮独立的事实(自 v4 起不再有"每设备一个手势拥有者"的锁):任何数量的按钮可同时处于手势模式,各自拥有方向表。Binding::GestureClick条目持有普通点击(不滑动)的动作;方向表支持fill_gesture_defaults用规范默认值填充未绑定的方向。

Actions Ring 条目

Actions Ring 条目包装一个动作并可选添加图标或字面标签:

Top = { action = { CustomShortcut = "Cmd+Shift+P" }, icon = "Keyboard", label = "Command Palette" }

Ring 有八个固定槽位,顺时针自顶部起为TopTopRightRightBottomRightBottomBottomLeftLeftTopLeft(crates/openlogi-core/src/binding/action_ring.rs 的ActionRingSlot,槽位名是 TOML schema 的一部分,必须保持稳定)。RingAction包装动作并可携带iconActionRingIcon,按动作推导或显式指定)或labelShowActionsRing被禁止放入 ring 槽位,以防止递归 Ring。

Ring 布局分"默认布局"与"完整按应用布局"两级:[devices.…​.action_ring]enabled/haptics控制开关与悬停/激活触感,default.slots为默认八槽布局,同时支持每个应用各自的完整布局。Config::set_action_ring_slotset_action_ring_icon等 API 在 crates/openlogi-core/src/config.rs 中提供了编辑入口。

Schema 迁移:v1–v7

加载器(parse_config,见 crates/openlogi-core/src/config/file.rs)先读schema_version头:

  • schema_version为 0 或**大于当前版本(7)**的文件在字段解析前就被拒绝(UnsupportedSchemaVersion),绝不静默丢失设置;
  • 已废弃字段在对应版本上出现时被拒绝(reject_obsolete_fields):v2+ 出现button_bindings/gesture_bindings、v4+ 出现gesture_owner都会报ObsoleteField错误;
  • 支持版本17的旧文件在加载时逐级迁移,迁移前的原文会在首次成功保存时以config.toml.v<N>.bak形式原子地复制旁置(migration_backup_path追加而非替换扩展名),确保键重写式迁移可恢复;保存失败仍保留该副本,只有首次成功保存后才清除债务。

各版本迁移要点(完整注释见 crates/openlogi-core/src/config.rs 的SCHEMA_VERSION文档):

  • v2:合并每设备的button_bindings+gesture_bindings为统一bindings映射。v1 文件仍可加载(RawDeviceConfig垫片把旧字段折叠进来)并在下次保存时自愈为 v2 形状;
  • v3:设备映射从模型键改为物理设备键。不迁移任何 v2 设备条目——模型作用域设置无法在存在两台相同设备时安全分配,v2 模型键条目必须手工复制到生成的物理键上;
  • v4:移除每设备一个手势按钮的 owner 锁。手势模式成为读自绑定形状的每按钮事实;加载 v3 及更早文件时,migrate_owner_locked_gestures解析旧 owner 并把形状重写为等价分派(HID++ owner 缺省/单形状时物化种子方向表;非 owner 的手势形状暂存进disabled_gestures并降级为Click单动作;被消费的gesture_owner不再序列化);
  • v5:从direct:键中去除传输前缀——direct:046d:c08d:unit:6be9d300同时命名了鼠标与它插着的线缆,换到另一条路由就会静默孤立设置。migrate_transport_scoped_keys把这种键重写为裸身份片段(unit:6be9d300),包括selected_device与每个host_switch_targets条目,并把被丢弃的路由保留为links条目;两条 v4 直连条目重命名到同一身份键时执行折叠(fold)而非插入覆盖,保证无损。receiver:键保持不动——磁盘上无从得知配对槽位上是什么设备,因此它们在运行时折叠(adopt_route),即下一次设备在线且 GUI 运行时首次看见时折叠。同版本还加入应用级ui_scale偏好(旧文件默认 100%)。设备自定义名与 Home 画廊视图偏好是可选字段,未触发版本号提升;
  • v6:加入阈值式{ short = ..., long = ... }按键绑定;
  • v7:使拇指滚轮滚动默认值与归一化物理方向对齐。pre-v7 在旧默认值上的显式拇指滚轮滚动对会在设备与按应用 profile 中被迁移(migrate_thumbwheel_native_direction),使它们保持原生方向而非变成反转;旧默认对是 上→右/下→左,v7 归一化极性后交换为 上→左/下→右,仅重写"有效对恰好等于旧默认"的 profile,混合/自定义对保持字面语义不动。

加载迁移完成后还会执行repair_duplicate_routes,并把内存中schema_version置为当前值。迁移的同时,ConfigFile记录migrated_from,首次保存时把迁移前原文原子复制为config.toml.v<N>.bak

实用技巧与常见陷阱

  • 设备键必须用真实键:从~/.config/openlogi/config.toml(或对应平台路径)复制 OpenLogi 已生成的设备键,不要手工编造或沿用模型 ID;schema_version以外的任何未知字段都会导致整个文件无法加载。
  • 改完重启:GUI 打开期间外部编辑会触发保存冲突(Conflict错误),重启以加载新版本;重启 GUI 同时会让 Agent 立即重载当前文件,手工改动与运行时行为即刻收敛。
  • SmartShift 与 DPI 是易失设置:写入设备 RAM 后掉电即失,OpenLogi 在重连时按配置重放(issue #189);同理fn_lock状态每主机一份,重连时重放。
  • 长按对目前仅 TOML 可写:GUI 修改该按钮会整体替换为单动作;per_app_bindingskeyboard.bindings不支持长按对。
  • 备份可救急:GUI 每次会话首次保存前保留.backup.1.backup.5轮转备份,迁移另有config.toml.v<N>.bak;出问题时可以从这些文件恢复。
  • 动作名称即 schema:动作变体名冻结在磁盘格式中,升级后旧动作名仍然有效;同理 ring 槽位名与手势方向名也是 schema 的一部分。

如需进一步验证字段行为,可阅读 crates/openlogi-core/src/config/tests.rs(文件级迁移与往返测试)以及settings.rs/device.rs内置的边界测试(SmartShift 契约、敏感性浮点钳制、host_switch_targets物理键往返等)。

【免费下载链接】OpenLogi⚡️A native, local-first alternative to Logitech Options+, written in Rust 🦀 — remap buttons, DPI, and SmartShift over HID++. No account, no telemetry.项目地址: https://gitcode.com/GitHub_Trending/op/OpenLogi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询