⚡️A native, local-first alternative to Logitech Options+, written in Rust 🦀 — remap buttons, DPI, and SmartShift over HID++. No account, no telemetry.
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
style(infra): format orb setup Co-authored-by: Xuan Zhang <xuan@arcbox.dev> | 13 天前 | |
ci: automate typo checks and consolidate configs (#906) * docs: fix spelling errors * ci: automate source typo checks * ci: pin typos action revision --------- Co-authored-by: luojiyin <luojiyin@hotmail.com> | 13 天前 | |
fix(gui): survive and complete the live language switch (#1033) * fix(gui): defer the menu rebuild out of the language-switch state update AppState::set_language runs inside AppState::update, and since the entity conversion (a3bf5b29) the Device menu's device listing reads that same entity — rebuilding the menu synchronously re-enters the leased entity and panics, which aborted the app on every language switch in 0.8.0. Defer the rebuild until the update returns the lease. Under the old Global model the same read raced the lease too, but try_global returned None during it, so the menu merely rendered its empty state instead of crashing. * fix(gui): carry a live language switch through to titles and cached text The window refresh a language switch triggers only re-renders strings that are re-derived at render time. Everything localized once and then stored kept its old locale: the native window titles stamped at open, InputState placeholders stamped at construction, and the cached asset-cache-size line. - windows::retitle_open restamps the Settings / Add Device native titles from the same window_title() definitions their open sites use, deferred from set_language beside the menu rebuild. - ui::components::localize_placeholder re-derives a placeholder in the owning render, guarded so the unconditional notify in set_placeholder cannot re-render forever; all five construction-stamped placeholders now route through it. - StateEvent::LanguageChanged lets views recompute localized text cached in state; the Settings view rebuilds its cache-size description on it. - set_language takes Context<AppState> so the event is emitted where the invariant lives, and .claude/rules/gui.md documents both this pattern and the update-lease re-entrancy rule behind the 0.8.0 crash. Known residual: gpui-component's Settings sidebar search placeholder is stamped inside the component's private keyed state and needs an upstream change. | 11 天前 | |
feat(i18n): add Turkish (tr) interface locale (#835) Ships `locales/tr.yml` with every key `en.yml` defines, registers `tr` ("Türkçe") in `SUPPORTED` between Svenska and Ελληνικά — native-name alphabetical within the Latin block — and adds it to the parity test's `include_str!` table so the catalog is actually checked rather than silently drifting. `crowdin.yml` gains the language and its `tr -> tr` mapping so Crowdin round-trips the new catalog like every other one. | 13 天前 | |
perf(release): accelerate macOS packaging (#1023) * perf(release): accelerate macos packaging * fix(release): preserve app bundle metadata * fix(release): resolve bundle resource paths | 11 天前 | |
feat(gui): refine device gallery worktree changes | 3 个月前 | |
chore: release v0.8.1 (#1025) Co-authored-by: aprilnea[bot] <263112868+aprilnea[bot]@users.noreply.github.com> | 11 天前 | |
fix(linux): install the app icon at every indexed hicolor size (#837) * fix(linux): install the app icon at every indexed hicolor size The packages installed `openlogi.png` only under `/usr/share/icons/hicolor/1024x1024/apps`. A stock `hicolor/index.theme` stops at 512x512, so launchers that resolve icons through the theme index — KDE's kbuildsycoca, and the GTK icon cache — found nothing and showed the generic placeholder. Pre-render 512 down to 16 alongside the 1024 master and install all of them from nfpm, the Nix derivation, and `install.sh`; `uninstall.sh` and the Nix install check cover the same set. The derivation's fileset takes `design/icon` as a directory so a future size needs no second edit. Fixes #797 Fixes #766 * ci(nix): widen the icon path filter to the whole directory The derivation's fileset now takes `design/icon` as a directory, so the package build consumes every file in it. The Nix workflow still triggered on `design/icon/openlogi.png` alone, so a change touching only one of the size-specific icons skipped the build — and with it the install check that asserts all eight sizes land. Filter on `design/icon/**` instead, which also covers whatever size is added next without a second edit, matching the fileset it guards. Found by the Greptile review on this PR. | 13 天前 | |
perf(release): accelerate macOS packaging (#1023) * perf(release): accelerate macos packaging * fix(release): preserve app bundle metadata * fix(release): resolve bundle resource paths | 11 天前 | |
fix: show Windows MSI completion and failure state (#819) Fixes #666 Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> | 13 天前 | |
chore: release v0.8.1 (#1025) Co-authored-by: aprilnea[bot] <263112868+aprilnea[bot]@users.noreply.github.com> | 11 天前 | |
ci(shell): gate every shell script with shellcheck and shfmt (#745) * style(shell): adopt shfmt formatting across every script shfmt reads its formatting options from EditorConfig, and discards that file the moment a printer flag appears on the command line — so the settings live in .editorconfig and every call site invokes shfmt bare. The `[[shell]]` section is shfmt's own extension: it matches by shebang as well as by extension, which is how the extensionless .agents/* scripts get the same rules as *.sh. indent_size = 2 costs 393 diff lines against the tree; four spaces costs 972, and shfmt's own default (tabs) another 1150 on top. No behaviour change — shfmt reprints a parsed AST, and every script still passes its shell's -n syntax check plus shellcheck. * ci(shell): gate every shell script with shellcheck and shfmt Nothing checked the repo's shell until now — no linter, no formatter, no CI job. All nine tracked scripts already pass shellcheck at its default severity, so this lands as a gate rather than a cleanup. The file set is whatever `shfmt -f` recognises among tracked files: extension or shebang, so a new script is covered the day it lands and `.devenv/`'s thousands of generated scripts stay out. prek runs the same two tools at commit time (excluding `.envrc`, which direnv sources and shfmt does not consider a script), and devenv now ships both binaries. | 16 天前 | |
fix: allow local dev without devenv (#486) | 1 个月前 | |
docs(agents): add a macOS permissions task skill The path-scoped rules in `.claude/rules/` load from the file being edited. Triaging a TCC report has no file to key on — you are reading an issue, not a crate — so the knowledge that decides "is this a permission problem at all" was never reachable at the moment it was needed. That question has been answered wrong repeatedly: #336, #704, #707 and #87 are TCC, while #520, #780, #781 and #798 open the channel fine and fail later, and all of them reach the user as an empty device list. The skill leads with that discriminator (did `opened HID++ channel` appear?), then the identity map — one install ships four code-signing identities and only the agent may hold Input Monitoring — the responsible process rules the helper launch already implements, what `IOHIDCheckAccess` / `IOHIDRequestAccess` / `IOHIDDeviceOpen` actually do, the invariants a change in this area must not break, and an explicit list of what TCC will never let us fix. `scripts/diagnose.sh` is read-only and safe to hand to a reporter. It resolves the *running* agent rather than assuming `/Applications`, because a dev bundle is its own TCC identity and a grant to the installed copy says nothing about it — a mis-diagnosis this repo can produce on the maintainer's own machine. Tracked, not personal: `.gitignore` gains a named carve-out so project skills are versioned while `.claude/skills/`'s per-developer symlinks into `.agents/skills/` stay ignored. AGENTS.md indexes it in a new section, so agents that do not read skills by description still find it. | 15 天前 | |
ci: automate typo checks and consolidate configs (#906) * docs: fix spelling errors * ci: automate source typo checks * ci: pin typos action revision --------- Co-authored-by: luojiyin <luojiyin@hotmail.com> | 13 天前 | |
chore: release v0.8.0 (#991) * chore: release v0.7.11 * chore(release): write whole-repo changelog * chore: release v0.8.0 --------- Co-authored-by: aprilnea[bot] <263112868+aprilnea[bot]@users.noreply.github.com> Co-authored-by: aprilnea[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: AprilNEA <github@sku.moe> | 12 天前 | |
chore(infra): make CLAUDE.md an @import instead of a symlink | 17 天前 | |
chore: release v0.8.1 (#1025) Co-authored-by: aprilnea[bot] <263112868+aprilnea[bot]@users.noreply.github.com> | 11 天前 | |
chore: release v0.8.1 (#1025) Co-authored-by: aprilnea[bot] <263112868+aprilnea[bot]@users.noreply.github.com> | 11 天前 | |
feat: scaffold optminus workspace and v0.0.1 list command Three-crate workspace (optminus-core, optminus-hid, optminus-cli) wired to the published hidpp 0.2 + async-hid 0.4. The CLI ships one subcommand — `optminus list` — that enumerates Logi Bolt receivers and any online paired devices, printing slot, codename, wpid, kind, and battery level. Inject + app-detect + per-app profiles are deferred until there is real code to put behind their traits; the boundary lives in commit history and in the README, not in empty crates. Dual MIT/Apache-2.0 licensed. CI runs fmt + clippy + check on Ubuntu and tests on macOS. | 3 个月前 | |
feat: scaffold optminus workspace and v0.0.1 list command Three-crate workspace (optminus-core, optminus-hid, optminus-cli) wired to the published hidpp 0.2 + async-hid 0.4. The CLI ships one subcommand — `optminus list` — that enumerates Logi Bolt receivers and any online paired devices, printing slot, codename, wpid, kind, and battery level. Inject + app-detect + per-app profiles are deferred until there is real code to put behind their traits; the boundary lives in commit history and in the README, not in empty crates. Dual MIT/Apache-2.0 licensed. CI runs fmt + clippy + check on Ubuntu and tests on macOS. | 3 个月前 | |
feat(agent): dispatch independent long-press actions (#961) | 12 天前 | |
chore(infra): prune the tree's lint suppressions and make the survivors expect (#720) * chore(infra): let clippy.toml silence unwrap/expect in tests The workspace table keeps `unwrap_used`/`expect_used` at warn so production code has to state its panics, but every test module then re-stated the same exemption by hand — 78 copies of `#[allow(…, reason = "idiomatic in tests")]`, roughly 40% of every lint suppression in the tree. Clippy has a config for exactly this; a two-line `clippy.toml` replaces all of them. Three build scripts and the `openlogi-ipc` wire-format test keep a suppression, because neither is a test in clippy's sense: a build script fails by panicking, and the golden-bytes fixtures sit in free helpers outside any `#[test]` fn — the one test shape `allow-expect-in-tests` cannot see. Those four are now `expect`, so they warn if they ever stop being needed. * chore(infra): expect, not allow, for lint suppressions that must stay `#[allow]` is silent when it stops suppressing anything, so suppressions rot in place: nothing tells you the cast it covered was deleted three refactors ago. `#[expect]` warns instead. Converting the 77 non-`cfg_attr` suppressions surfaced 20 that no longer suppress anything, all removed here: - `openlogi-assets` {index,manifest,metadata}: three module-wide `dead_code` blankets that have been inert since the modules became `pub mod` — public items are reachable, so the lint could never fire. The dangerous kind: had those modules gone private again, the blanket would have hidden real dead code forever. - `LightCapabilities` is down to three bools, under `struct_excessive_bools`. - `InstanceGuard._handle` and `CGEventTapInformation` are exempt from `dead_code` on their own (leading underscore / all fields now read). - Nine GUI `dead_code` and cast suppressions, and the surplus lint names in six multi-lint blankets that only ever needed one or two of them. Four suppressions stay `allow` on purpose, each with a comment saying why `expect` is wrong there: - `platform::os_version` — the lint only fires on the macOS arm; off macOS the expectation would go unfulfilled and redden the Linux/Windows lanes. - `function_row`'s `float_cmp` — rustc does not credit an expectation with a lint raised inside a macro expansion (`assert_eq!` here), so `expect` would suppress the warning and then report itself unfulfilled: unfixable under `-D warnings`. - The four `cfg_attr`-wrapped ones, whose fulfilment differs between the lib and test targets of the same crate. Also drops `_silence_pickfn`, a dead function that existed only to keep an unused `PickFn` import "used" — a hand-rolled lint workaround; the import goes with it. And gives the tree's one reason-less suppression a reason. Verified on the macOS and windows-gnu lanes; the Linux lane is CI's. * refactor(infra): narrow the file-wide cast blankets to their real sites Seven files silenced `cast_possible_truncation` / `cast_sign_loss` / `cast_possible_wrap` (and in the hook's Win32 backend `borrow_as_ptr` and `needless_pass_by_value`) for their entire contents. Between them they covered 50 real sites — and every future cast anyone adds to those files, unchecked. Most of the sites did not need a suppression at all: - `capture_windows` and the hook's Win32 backend now use `cast_signed()` / `cast_unsigned()`, which say "reinterpret the bits" instead of "trust me". That is the whole file-level blanket gone from `capture_windows`. - The hook's six `borrow_as_ptr` sites become `&raw mut` / `&raw const`, the edition-2024 spelling — and the better one at an FFI boundary, since it never materializes the intermediate reference. - `uvc`'s descriptor fixture builds its two bytes with `to_le_bytes`. - `camera/controls` funnelled eleven ad-hoc `i32 as f32` / `f32 as i32` conversions through `to_slider`/`from_slider`, so the UVC-range argument is made once where it belongs rather than eleven times by omission. What genuinely needs a cast keeps one, scoped to the function that does it and with a reason naming the bound that makes it safe. `capture_linux` keeps its file-level blanket: `v4l2-sys-mit` runs bindgen, so that backend cannot be cross-linted from macOS and narrowing it would be blind work. Tracked separately. Verified on all three lint lanes: macOS native, x86_64-pc-windows-gnu, and aarch64-unknown-linux-musl (everything but the GPUI crates and the v4l2 backend, neither of which cross-compiles). * docs(infra): write down the allow-vs-expect rule and the test exemption `.claude/rules/rust.md` still told agents to hand-write an `#[allow(clippy::expect_used)]` on every test module — now `clippy.toml`'s job — and still described `openlogi-hidpp` as sitting outside the workspace lint table, which its own AGENTS.md retired some time ago. Replaces both, and adds the rule the sweep produced: `expect` by default, with the three cases where it provably cannot work (cfg-conditional lints, fulfilment differing between a crate's lib and test targets, and lints raised inside a macro expansion), plus the scoping rule for cast suppressions and the mechanical recipe for re-running the sweep across all three lint lanes. Logged in `docs/DECISIONS.md` with the numbers behind it. * style(infra): trim the comments this sweep added Several of the new suppression rationales argued the case instead of stating it. A `reason` earns its keep by naming the bound; the surrounding prose was restating what the attribute already says. * chore(infra): make the suppression policy machine-checked `allow_attributes` and `allow_attributes_without_reason` turn the two rules the sweep just wrote down into build failures instead of review notes: reach for `expect`, and say why. Adopting them cost three annotated exceptions and nothing else — the sweep had already left the tree compliant, and `allow_attributes_without_reason` found zero violations on any lane. The three sites where `expect` provably cannot work keep their `allow` behind an `#[expect(clippy::allow_attributes)]`, which is a better home for the "why not expect" note than a bare comment: it fails the build if the exception ever stops being one. Two useful things fell out: - The first pass ruled that a `cfg_attr`-wrapped suppression always needs `allow`. Two of the four turned out to work fine as `expect` — the rule was too coarse, and the lint is what surfaced it. `state.rs` and the permissions page are now `expect`. - `allow_attributes` only sees outer `#[allow]`. A module-wide `#![allow(…)]` is invisible to it — precisely the shape that had rotted in `openlogi-assets`. Recorded in the lint table and in the rules so nobody reads a green build as full coverage. * fix(camera): drop unfulfilled linux cast expects * build(nix): overlay current stable rustc * fix(nix): include nfpm.yaml in the package fileset --------- Co-authored-by: David Budnick <david@budnick.ca> | 16 天前 | |
chore: track current stable instead of trailing it by two releases The 1.96 floor protected nobody. Every crate published from this workspace has zero external reverse dependencies, and OpenLogi reaches users as prebuilt installers, so the only thing `rust-version` buys is a clear error for `cargo install` on an old toolchain — worth keeping, worth keeping current. Trailing stable cost us the opposite: 1.98's `as_chunks` was only usable because it happened to predate 1.96, and every other newly stabilized API needed a probe before it could even be considered. The floor and the toolchain move together. `rust-version` is checked by cargo, so bumping it without `devenv update rust-overlay` would break every local build; doing both means the local gate and the pre-push hooks finally compile with the same rustc CI does. Today's breakage — 1.98 red-lining untouched master while the local gate sat on 1.96, blind to it — is exactly what that gap produces. The msrv CI matrix stays, at 1.98: with the other jobs floating on stable it is the only thing that can catch the declared floor drifting into a lie. | 17 天前 | |
ci: automate typo checks and consolidate configs (#906) * docs: fix spelling errors * ci: automate source typo checks * ci: pin typos action revision --------- Co-authored-by: luojiyin <luojiyin@hotmail.com> | 13 天前 | |
chore: init project with nix | 3 个月前 | |
chore(infra): prune the tree's lint suppressions and make the survivors expect (#720) * chore(infra): let clippy.toml silence unwrap/expect in tests The workspace table keeps `unwrap_used`/`expect_used` at warn so production code has to state its panics, but every test module then re-stated the same exemption by hand — 78 copies of `#[allow(…, reason = "idiomatic in tests")]`, roughly 40% of every lint suppression in the tree. Clippy has a config for exactly this; a two-line `clippy.toml` replaces all of them. Three build scripts and the `openlogi-ipc` wire-format test keep a suppression, because neither is a test in clippy's sense: a build script fails by panicking, and the golden-bytes fixtures sit in free helpers outside any `#[test]` fn — the one test shape `allow-expect-in-tests` cannot see. Those four are now `expect`, so they warn if they ever stop being needed. * chore(infra): expect, not allow, for lint suppressions that must stay `#[allow]` is silent when it stops suppressing anything, so suppressions rot in place: nothing tells you the cast it covered was deleted three refactors ago. `#[expect]` warns instead. Converting the 77 non-`cfg_attr` suppressions surfaced 20 that no longer suppress anything, all removed here: - `openlogi-assets` {index,manifest,metadata}: three module-wide `dead_code` blankets that have been inert since the modules became `pub mod` — public items are reachable, so the lint could never fire. The dangerous kind: had those modules gone private again, the blanket would have hidden real dead code forever. - `LightCapabilities` is down to three bools, under `struct_excessive_bools`. - `InstanceGuard._handle` and `CGEventTapInformation` are exempt from `dead_code` on their own (leading underscore / all fields now read). - Nine GUI `dead_code` and cast suppressions, and the surplus lint names in six multi-lint blankets that only ever needed one or two of them. Four suppressions stay `allow` on purpose, each with a comment saying why `expect` is wrong there: - `platform::os_version` — the lint only fires on the macOS arm; off macOS the expectation would go unfulfilled and redden the Linux/Windows lanes. - `function_row`'s `float_cmp` — rustc does not credit an expectation with a lint raised inside a macro expansion (`assert_eq!` here), so `expect` would suppress the warning and then report itself unfulfilled: unfixable under `-D warnings`. - The four `cfg_attr`-wrapped ones, whose fulfilment differs between the lib and test targets of the same crate. Also drops `_silence_pickfn`, a dead function that existed only to keep an unused `PickFn` import "used" — a hand-rolled lint workaround; the import goes with it. And gives the tree's one reason-less suppression a reason. Verified on the macOS and windows-gnu lanes; the Linux lane is CI's. * refactor(infra): narrow the file-wide cast blankets to their real sites Seven files silenced `cast_possible_truncation` / `cast_sign_loss` / `cast_possible_wrap` (and in the hook's Win32 backend `borrow_as_ptr` and `needless_pass_by_value`) for their entire contents. Between them they covered 50 real sites — and every future cast anyone adds to those files, unchecked. Most of the sites did not need a suppression at all: - `capture_windows` and the hook's Win32 backend now use `cast_signed()` / `cast_unsigned()`, which say "reinterpret the bits" instead of "trust me". That is the whole file-level blanket gone from `capture_windows`. - The hook's six `borrow_as_ptr` sites become `&raw mut` / `&raw const`, the edition-2024 spelling — and the better one at an FFI boundary, since it never materializes the intermediate reference. - `uvc`'s descriptor fixture builds its two bytes with `to_le_bytes`. - `camera/controls` funnelled eleven ad-hoc `i32 as f32` / `f32 as i32` conversions through `to_slider`/`from_slider`, so the UVC-range argument is made once where it belongs rather than eleven times by omission. What genuinely needs a cast keeps one, scoped to the function that does it and with a reason naming the bound that makes it safe. `capture_linux` keeps its file-level blanket: `v4l2-sys-mit` runs bindgen, so that backend cannot be cross-linted from macOS and narrowing it would be blind work. Tracked separately. Verified on all three lint lanes: macOS native, x86_64-pc-windows-gnu, and aarch64-unknown-linux-musl (everything but the GPUI crates and the v4l2 backend, neither of which cross-compiles). * docs(infra): write down the allow-vs-expect rule and the test exemption `.claude/rules/rust.md` still told agents to hand-write an `#[allow(clippy::expect_used)]` on every test module — now `clippy.toml`'s job — and still described `openlogi-hidpp` as sitting outside the workspace lint table, which its own AGENTS.md retired some time ago. Replaces both, and adds the rule the sweep produced: `expect` by default, with the three cases where it provably cannot work (cfg-conditional lints, fulfilment differing between a crate's lib and test targets, and lints raised inside a macro expansion), plus the scoping rule for cast suppressions and the mechanical recipe for re-running the sweep across all three lint lanes. Logged in `docs/DECISIONS.md` with the numbers behind it. * style(infra): trim the comments this sweep added Several of the new suppression rationales argued the case instead of stating it. A `reason` earns its keep by naming the bound; the surrounding prose was restating what the attribute already says. * chore(infra): make the suppression policy machine-checked `allow_attributes` and `allow_attributes_without_reason` turn the two rules the sweep just wrote down into build failures instead of review notes: reach for `expect`, and say why. Adopting them cost three annotated exceptions and nothing else — the sweep had already left the tree compliant, and `allow_attributes_without_reason` found zero violations on any lane. The three sites where `expect` provably cannot work keep their `allow` behind an `#[expect(clippy::allow_attributes)]`, which is a better home for the "why not expect" note than a bare comment: it fails the build if the exception ever stops being one. Two useful things fell out: - The first pass ruled that a `cfg_attr`-wrapped suppression always needs `allow`. Two of the four turned out to work fine as `expect` — the rule was too coarse, and the lint is what surfaced it. `state.rs` and the permissions page are now `expect`. - `allow_attributes` only sees outer `#[allow]`. A module-wide `#![allow(…)]` is invisible to it — precisely the shape that had rotted in `openlogi-assets`. Recorded in the lint table and in the rules so nobody reads a green build as full coverage. * fix(camera): drop unfulfilled linux cast expects * build(nix): overlay current stable rustc * fix(nix): include nfpm.yaml in the package fileset --------- Co-authored-by: David Budnick <david@budnick.ca> | 16 天前 | |
chore(infra): prune the tree's lint suppressions and make the survivors expect (#720) * chore(infra): let clippy.toml silence unwrap/expect in tests The workspace table keeps `unwrap_used`/`expect_used` at warn so production code has to state its panics, but every test module then re-stated the same exemption by hand — 78 copies of `#[allow(…, reason = "idiomatic in tests")]`, roughly 40% of every lint suppression in the tree. Clippy has a config for exactly this; a two-line `clippy.toml` replaces all of them. Three build scripts and the `openlogi-ipc` wire-format test keep a suppression, because neither is a test in clippy's sense: a build script fails by panicking, and the golden-bytes fixtures sit in free helpers outside any `#[test]` fn — the one test shape `allow-expect-in-tests` cannot see. Those four are now `expect`, so they warn if they ever stop being needed. * chore(infra): expect, not allow, for lint suppressions that must stay `#[allow]` is silent when it stops suppressing anything, so suppressions rot in place: nothing tells you the cast it covered was deleted three refactors ago. `#[expect]` warns instead. Converting the 77 non-`cfg_attr` suppressions surfaced 20 that no longer suppress anything, all removed here: - `openlogi-assets` {index,manifest,metadata}: three module-wide `dead_code` blankets that have been inert since the modules became `pub mod` — public items are reachable, so the lint could never fire. The dangerous kind: had those modules gone private again, the blanket would have hidden real dead code forever. - `LightCapabilities` is down to three bools, under `struct_excessive_bools`. - `InstanceGuard._handle` and `CGEventTapInformation` are exempt from `dead_code` on their own (leading underscore / all fields now read). - Nine GUI `dead_code` and cast suppressions, and the surplus lint names in six multi-lint blankets that only ever needed one or two of them. Four suppressions stay `allow` on purpose, each with a comment saying why `expect` is wrong there: - `platform::os_version` — the lint only fires on the macOS arm; off macOS the expectation would go unfulfilled and redden the Linux/Windows lanes. - `function_row`'s `float_cmp` — rustc does not credit an expectation with a lint raised inside a macro expansion (`assert_eq!` here), so `expect` would suppress the warning and then report itself unfulfilled: unfixable under `-D warnings`. - The four `cfg_attr`-wrapped ones, whose fulfilment differs between the lib and test targets of the same crate. Also drops `_silence_pickfn`, a dead function that existed only to keep an unused `PickFn` import "used" — a hand-rolled lint workaround; the import goes with it. And gives the tree's one reason-less suppression a reason. Verified on the macOS and windows-gnu lanes; the Linux lane is CI's. * refactor(infra): narrow the file-wide cast blankets to their real sites Seven files silenced `cast_possible_truncation` / `cast_sign_loss` / `cast_possible_wrap` (and in the hook's Win32 backend `borrow_as_ptr` and `needless_pass_by_value`) for their entire contents. Between them they covered 50 real sites — and every future cast anyone adds to those files, unchecked. Most of the sites did not need a suppression at all: - `capture_windows` and the hook's Win32 backend now use `cast_signed()` / `cast_unsigned()`, which say "reinterpret the bits" instead of "trust me". That is the whole file-level blanket gone from `capture_windows`. - The hook's six `borrow_as_ptr` sites become `&raw mut` / `&raw const`, the edition-2024 spelling — and the better one at an FFI boundary, since it never materializes the intermediate reference. - `uvc`'s descriptor fixture builds its two bytes with `to_le_bytes`. - `camera/controls` funnelled eleven ad-hoc `i32 as f32` / `f32 as i32` conversions through `to_slider`/`from_slider`, so the UVC-range argument is made once where it belongs rather than eleven times by omission. What genuinely needs a cast keeps one, scoped to the function that does it and with a reason naming the bound that makes it safe. `capture_linux` keeps its file-level blanket: `v4l2-sys-mit` runs bindgen, so that backend cannot be cross-linted from macOS and narrowing it would be blind work. Tracked separately. Verified on all three lint lanes: macOS native, x86_64-pc-windows-gnu, and aarch64-unknown-linux-musl (everything but the GPUI crates and the v4l2 backend, neither of which cross-compiles). * docs(infra): write down the allow-vs-expect rule and the test exemption `.claude/rules/rust.md` still told agents to hand-write an `#[allow(clippy::expect_used)]` on every test module — now `clippy.toml`'s job — and still described `openlogi-hidpp` as sitting outside the workspace lint table, which its own AGENTS.md retired some time ago. Replaces both, and adds the rule the sweep produced: `expect` by default, with the three cases where it provably cannot work (cfg-conditional lints, fulfilment differing between a crate's lib and test targets, and lints raised inside a macro expansion), plus the scoping rule for cast suppressions and the mechanical recipe for re-running the sweep across all three lint lanes. Logged in `docs/DECISIONS.md` with the numbers behind it. * style(infra): trim the comments this sweep added Several of the new suppression rationales argued the case instead of stating it. A `reason` earns its keep by naming the bound; the surrounding prose was restating what the attribute already says. * chore(infra): make the suppression policy machine-checked `allow_attributes` and `allow_attributes_without_reason` turn the two rules the sweep just wrote down into build failures instead of review notes: reach for `expect`, and say why. Adopting them cost three annotated exceptions and nothing else — the sweep had already left the tree compliant, and `allow_attributes_without_reason` found zero violations on any lane. The three sites where `expect` provably cannot work keep their `allow` behind an `#[expect(clippy::allow_attributes)]`, which is a better home for the "why not expect" note than a bare comment: it fails the build if the exception ever stops being one. Two useful things fell out: - The first pass ruled that a `cfg_attr`-wrapped suppression always needs `allow`. Two of the four turned out to work fine as `expect` — the rule was too coarse, and the lint is what surfaced it. `state.rs` and the permissions page are now `expect`. - `allow_attributes` only sees outer `#[allow]`. A module-wide `#![allow(…)]` is invisible to it — precisely the shape that had rotted in `openlogi-assets`. Recorded in the lint table and in the rules so nobody reads a green build as full coverage. * fix(camera): drop unfulfilled linux cast expects * build(nix): overlay current stable rustc * fix(nix): include nfpm.yaml in the package fileset --------- Co-authored-by: David Budnick <david@budnick.ca> | 16 天前 | |
ci: automate typo checks and consolidate configs (#906) * docs: fix spelling errors * ci: automate source typo checks * ci: pin typos action revision --------- Co-authored-by: luojiyin <luojiyin@hotmail.com> | 13 天前 | |
ci: automate typo checks and consolidate configs (#906) * docs: fix spelling errors * ci: automate source typo checks * ci: pin typos action revision --------- Co-authored-by: luojiyin <luojiyin@hotmail.com> | 13 天前 | |
feat: scaffold optminus workspace and v0.0.1 list command Three-crate workspace (optminus-core, optminus-hid, optminus-cli) wired to the published hidpp 0.2 + async-hid 0.4. The CLI ships one subcommand — `optminus list` — that enumerates Logi Bolt receivers and any online paired devices, printing slot, codename, wpid, kind, and battery level. Inject + app-detect + per-app profiles are deferred until there is real code to put behind their traits; the boundary lives in commit history and in the README, not in empty crates. Dual MIT/Apache-2.0 licensed. CI runs fmt + clippy + check on Ubuntu and tests on macOS. | 3 个月前 | |
feat: scaffold optminus workspace and v0.0.1 list command Three-crate workspace (optminus-core, optminus-hid, optminus-cli) wired to the published hidpp 0.2 + async-hid 0.4. The CLI ships one subcommand — `optminus list` — that enumerates Logi Bolt receivers and any online paired devices, printing slot, codename, wpid, kind, and battery level. Inject + app-detect + per-app profiles are deferred until there is real code to put behind their traits; the boundary lives in commit history and in the README, not in empty crates. Dual MIT/Apache-2.0 licensed. CI runs fmt + clippy + check on Ubuntu and tests on macOS. | 3 个月前 |
Warning
OpenLogi 正在积极开发中,目前尚不稳定——功能和配置仍可能发生变化。请为仓库点个 Star ⭐,并 Watch 👀,以便在新版本发布时收到通知。
English | 简体中文 | 日本語 | Deutsch | Français | 한국어
OpenLogi
⚡️ 一个原生、本地优先的罗技 Options+ 替代方案,使用 Rust 🦀 编写
通过 HID++ 和 UVC 全面解锁罗技鼠标、键盘和摄像头的完整功能
受够了 Options+?来试试 OpenLogi。
支持 macOS、Linux 和 Windows。
超越 Options+
OpenLogi 可以,而 Options+ 做不到:
- 保持轻量。 原生 Rust + GPUI。
- 在 Linux 上运行。 在 OpenLogi 中,Linux 是一等公民平台。
- 任意物理按键手势。 可将手势角色赋予任意物理按键——或完全关闭手势。
- 纯文本配置。 所有内容都由一个 TOML 文件承载,可按任意方式在多台机器间同步。
- 可脚本化。 GUI 之外,还有真正的 CLI。
功能
- 支持通过 Logi Bolt 接收器、Unifying 接收器、蓝牙或有线连接接入的设备,并显示电池百分比与充电状态
- 通过系统输入钩子实现按键重映射:内置动作目录,外加在 TOML 配置中编写的自定义键盘快捷键;支持独立的短按/长按动作,以及用于按下说话的按住至释放组合键¹
- 按应用配置叠加层,在应用获得焦点时自动切换(macOS + Windows;Linux 仅支持 X11 / XWayland)
- Litra 灯光:电源、亮度与色温,支持可选的自动开关,并跟随摄像头活动
鼠标
- 捕获并重映射中键、模式切换键与拇指滚轮键(中键普遍可用,其余按键仅在设备支持时可用)
- 支持按方向绑定手势,可在任意支持手势的按键上实时捕获
- Actions Ring:以光标为中心的八格动作叠加层(
ShowActionsRing),支持按应用设置布局 - DPI 控制:支持预设以及 Cycle / Set-preset 动作(
0x2201) - SmartShift 滚轮:模式切换、灵敏度与永久棘轮面板(
0x2111) - 按设备设置原生滚动方向反转(
0x2121,适用于支持的设备)
键盘
- 全局 F 键重映射:与鼠标相同的动作目录,外加高级用户动作——键入文本、组合键、多步工作流(macOS + Windows)
- 静态 RGB 灯光(
0x8070/0x8080,适用于支持的设备)
摄像头
- 任意 Logitech UVC 摄像头(Brio、StreamCam、C920 系列……),即插即用
- 实时预览仅在你查看时打开摄像头——离开后会完全释放摄像头,LED 熄灭
- 图像控制直接写入 UVC 硬件——变焦、对焦、曝光、亮度、对比度、饱和度、锐度、白平衡、色调、防闪烁与低光补偿,并支持对焦 / 曝光 / 白平衡的自动模式切换——因此更改会在 Meet / Zoom / OBS 以及所有使用摄像头的应用中生效
- 一键配置:内置 Default / Streaming / Video call 以及自定义快照;设置按摄像头持久保存,并在下次查看时写回硬件
¹ 媒体键动作在 Linux 上使用 D-Bus MPRIS;少量 macOS 专属动作没有通用的 Linux 等效实现,因此不会执行。Windows 会在可用时将平台动作映射到原生等效动作。
安装
Important
请先退出 Logi Options+:这两个应用会争抢 HID++ 访问权限,同一时间只能有一个应用占用某个接收器。
macOS
需要 macOS 13 或更高版本。
从最新版本下载已签名并完成公证的 .dmg 文件,然后将 OpenLogi.app 拖到 /Applications。
或者通过 Homebrew 安装:
brew install --cask openlogi
官方 Homebrew cask 是默认安装路径。若要显式
跟踪来自 aprilnea/tap 的最新 GitHub release:
brew tap aprilnea/tap
brew install --cask aprilnea/tap/openlogi@latest
openlogi@latest 由 OpenLogi 的发布工作流维护,可能早于官方 cask 自动版本更新合并。请安装 openlogi 或 openlogi@latest 其中之一,不要同时安装。
Linux
从最新版本下载适用于你发行版的软件包:
# Debian / Ubuntu
sudo dpkg -i openlogi_*.deb
# Fedora / RHEL
sudo rpm -i openlogi-*.rpm
# Arch Linux
sudo pacman -U openlogi-*.pkg.tar.zst
x86_64/amd64 与 arm64/aarch64 均已发布软件包。
预构建软件包需要 GLIBC 2.35 或更高版本(Ubuntu 22.04 基线)。
NixOS 用户也可以导入仓库中的模块,该模块会安装软件包和 udev 规则,并随图形会话启动服务进程:
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.openlogi = {
url = "github:AprilNEA/OpenLogi";
inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { nixpkgs, openlogi, ... }: {
nixosConfigurations.my-host = nixpkgs.lib.nixosSystem {
system = "x86_64-linux"; # or aarch64-linux
modules = [
openlogi.nixosModules.default
{ programs.openlogi.enable = true; }
];
};
};
}
所有 Linux 软件包都会安装 udev 规则,在无需 sudo 的情况下,授予当前用户对
/dev/hidraw*、/dev/uinput 以及你的 Logitech 鼠标的
/dev/input/event* 设备节点的访问权限。NixOS 模块会自动启动 agent;安装
.deb、.rpm 或 .pkg.tar.zst 包后,请为当前用户启用它:
systemctl --user enable --now openlogi-agent.service
完整的 NixOS 选项、手动 / 源码安装方式,以及没有 systemd 的发行版,请参见 docs/INSTALL-linux.md。
Windows
每个发布版本都会附带已签名的可移植 .zip 压缩包,以及按用户安装的 .msi 安装程序(支持 x86_64 和 arm64)。二者均包含图形界面(OpenLogi.exe)和后台代理(openlogi-agent.exe),后者负责所有设备 I/O。使用可移植压缩包时,请将这两个文件放在同一目录下,否则图形界面将没有可连接的对象。
Windows 支持已在 Windows 11 上通过真实硬件(有线键盘和使用 Unifying 接收器的鼠标)完成端到端验证,涵盖 MSI 的安装、就地升级和卸载。Windows 构建比 macOS 构建更新,因此如遇到任何不完善之处,请报告该问题。代理会在系统托盘中显示一个图标(显示主窗口 / 退出),以便在主窗口关闭后仍能访问应用。若要在 Windows 上禁用该托盘图标,请在 TOML 的 [app_settings] 配置块中设置 show_in_menu_bar = false,然后重新启动代理;图形界面中的切换开关目前仅限 macOS。
如要从源码构建,请参见 DEVELOPMENT.md。
使用(CLI)
参见 USAGE.md
配置
开发
致谢
- Windows、摄像头和 i18n 由 @davidbudnick 贡献 — 键盘 RGB、Windows 支持、Logitech 网络摄像头支持
- Linux 移植 由 @cserby 贡献 — Linux 支持
- Solaar 由 @pwr 开发 — 开源 HID++ 实现
- Mouser 由 @TomBadash 开发 — 一款本地、无需账号的 Options+ 替代方案
许可证
本仓库中的代码采用双重许可授权,您可任选以下任一许可证作为适用许可证:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
第三方代码
crates/openlogi-hidpp 是 hidpp
的内置 fork,由 @lus 提供,采用 0BSD 许可。
标志与品牌资产
感谢 @kubai087 设计了 OpenLogi
标志。OpenLogi 标志和应用图标(design/ 目录下的品牌资产)© 2026 AprilNEA,保留所有权利,且不在上述 MIT/Apache
许可证的覆盖范围内;详见 design/LICENSE。Fork 代码并不会授予对 OpenLogi 名称、标志或图标的任何权利;未经事先书面许可,请勿使用它们来代表
你自己的项目、fork 或发行版。
与 Logitech 无隶属关系。 “Logitech”、“MX Master” 和 “Options+” 均为 Logitech International S.A. 的商标。
仓库动态
项目介绍
⚡️Logitech Options+ 的原生、本地优先替代方案,采用 Rust 🦀 编写 — 通过 HID++ 实现按钮重映射、DPI 调节及智能切换功能。无需账户,无遥测数据。【此简介由AI生成】