TOML 파일에 남는다. 10개 크레이트로 잘게 나뉜 Rust 워크스페이스 구조 자체가 좋은 교재다. (저장소: AprilNEA/OpenLogi · ★ 117 · TrendShift 데일리 7위 · 2026 신규 · MIT/Apache-2.0 듀얼 라이선스)OpenLogi는 로지텍 마우스(MX Master 계열 등)를 "공식 앱 없이" 길들이는 오픈소스 도구다. 옆 버튼(뒤로/앞으로)을 원하는 동작으로 바꾸고, DPI(마우스 민감도)를 프리셋으로 전환하고, 스크롤 휠의 SmartShift(걸쇠식 ↔ 자유회전) 모드를 토글한다. 앱이 켜진 동안 앞에 떠 있는 프로그램에 따라 버튼 의미를 자동으로 바꿔주는 "앱별 프로필"까지 지원한다.
핵심 차별점은 local-first(로컬 우선)다. 로지텍 계정 로그인도, 사용 데이터 수집(텔레메트리)도, 무거운 백그라운드 서비스도 없다. 설정은 ~/.config/openlogi/config.toml이라는 사람이 읽을 수 있는 텍스트 파일 하나에 저장된다. 현재 macOS·Linux를 정식 지원하고, Windows는 미테스트 preview 상태다(버전 0.x, 활발히 개발 중).
새 마우스를 사면 제조사 앱(Logi Options+)을 깔라고 한다. 편하지만 계정을 요구하고, 백그라운드에서 늘 돌고, 가끔 클라우드와 통신한다. 마치 자동차의 순정 내비게이션 같다 — 잘 되지만 내 통제 밖이다.
OpenLogi는 같은 마우스에 직접 만든 가벼운 펌웨어 콘솔을 물리는 것과 같다. 버튼·DPI·휠 설정을 마우스 칩에 직접 써넣되, 그 과정과 데이터가 전부 내 손 안에 있다. 비결은 로지텍이 쓰는 통신 규약 HID++를 OpenLogi가 그대로 구사한다는 점이다.
Logi Options+는 기능은 많지만 무겁고, 계정을 요구하고, 백그라운드 상주에 가끔 클라우드와 통신한다. "마우스 버튼 하나 바꾸려고 이걸 다 깔아야 하나"라는 불만은 오래된 것이다. OpenLogi는 그 불만에 "계정 없음 · 텔레메트리 없음 · TOML 한 파일"로 답한다. 프라이버시·경량·통제권을 한꺼번에 파는 셈이라, local-first 흐름을 타고 빠르게 입소문이 났다.
설정을 바꾸려면 로그인하고, 앱은 항상 떠 있어야 하며, 내 설정이 어디에 어떻게 저장되는지 사용자가 알기 어렵다. "내 마우스인데 내가 통제하지 못한다"는 감각.
설정은 디스크의 config.toml에 원자적으로 저장된다. 네트워크를 타는 건 (1) 기기 이미지를 받아오는 것과 (2) 기본 꺼져 있는 업데이트 확인뿐. 버튼 리매핑은 OS 이벤트를 가로채서, DPI/SmartShift는 HID++로 기기에 직접 쓴다.
리눅스에는 Solaar라는 훌륭한 HID++ 도구가 오래 있었지만, Rust 기반 크로스플랫폼 로컬 우선 도구는 사각지대였다. OpenLogi는 macOS의 CGEventTap·접근성 권한·앞 앱 감지와 Linux의 evdev+uinput·udev 후킹을 모두 정면으로 구현해, "옆버튼을 앱별로 다르게 쓰고 싶다"는 구체적 갈증을 양 플랫폼에서 푼다. README도 Solaar와 Mouser에게 감사를 표한다.
입력 후킹·USB 통신처럼 보통 C/C++로 짜던 저수준 영역을 전부 Rust로, 그것도 10개 크레이트로 깔끔하게 분리해 구현했다. 게다가 UI는 Electron이 아니라 Zed의 GPUI로 그려서 "네이티브·가벼움"을 끝까지 밀어붙였다. Rust·시스템 프로그래밍·GPUI에 관심 있는 개발자들에게 "읽고 배울 가치가 있는 레퍼런스"로 회자되는 것이 트렌딩의 두 번째 축이다.
대부분의 마우스 유틸리티는 블랙박스다. OpenLogi는 "HID++ 패킷을 어떻게 만들고, OS 이벤트를 어떻게 가로채고, 그걸 어떻게 크레이트로 나누는가"를 통째로 공개한다. 즉 결과물(앱)뿐 아니라 제작 과정 전체가 교재다. 별 117개의 작은 신생 저장소가 TrendShift 상위에 오른 이유가 여기 있다 — 스타 수보다 "관심의 질"이 높다.
버전은 아직 0.x(저장소 릴리스 v0.0.1, 워크스페이스 내부 버전 0.3.4)이고 README가 "활발히 개발 중, 기능·설정이 바뀔 수 있음"을 명시한다. 일부 액션(미디어 키 등)은 "실제 이벤트 대신 의도만 로그로 남기는" 상태이고, 중간/모드시프트/썸휠 버튼 캡처는 "설정은 되지만 하드웨어 캡처는 미구현"이다. 완성품이 아니라 "성장하는 프로젝트"라는 전제를 깔고 보는 게 정확하다.
OpenLogi는 Cargo 워크스페이스 하나에 10개 크레이트(+ xtask)를 담은 모노레포다. Edition은 최신 2024, 최소 지원 Rust 버전(MSRV)은 1.88, 의존성 해석기는 resolver = "3". "프런트엔드/백엔드"가 아니라 "GUI 계층 · 프로토콜 계층 · OS 후킹 계층"으로 나눠 보는 게 이 프로젝트엔 맞다.
| 분류 | 크레이트 | 역할 |
|---|---|---|
| UI 프레임워크 | gpui · gpui_platform (zed-industries/zed) | GPU 가속 네이티브 렌더링. Electron 없이 데스크톱 UI를 그린다 |
| 컴포넌트 라이브러리 | gpui-component (longbridge) | 버튼·리스트·캐러셀 등 재사용 UI 부품. 아이콘은 lucide SVG |
| 아이콘 에셋 | gpui-component-assets | IconName enum이 매크로로 생성되는 SVG 묶음(컴포넌트와 같은 rev로 고정) |
gpui는 Zed 저장소의 기본 브랜치를 따라가고, gpui-component는 특정 커밋(rev)으로 못 박혀 있다. 매크로로 생성되는 아이콘 enum과 SVG 에셋이 "정확히 같은 버전"이어야 깨지지 않기 때문. 정확한 zed 커밋은 Cargo.lock에 핀으로 박고, 올릴 때는 cargo update로 의도적으로 보조를 맞춘다. 외부 git 의존성을 안전하게 다루는 실전 패턴이다.| 분류 | 크레이트/라이브러리 | 역할 |
|---|---|---|
| HID++ 프로토콜 | openlogi-hidpp (패키지명 hidpp) | 로지텍 HID++ 패킷 조립/파싱. 프로젝트의 심장 |
| 크로스플랫폼 HID | async-hid 0.4 | OS별 HID 장치 열기/읽기/쓰기를 async로 추상화 |
| 비동기 런타임 | tokio (rt·macros·sync·time) · futures-lite | 장치 I/O를 논블로킹으로 처리 |
| 직렬화 | serde · serde_json · toml 0.8 | 설정(TOML)·에셋 레지스트리(JSON) 읽고 쓰기 |
Cargo.toml을 보면 hidpp = { package = "openlogi-hidpp", path = "crates/openlogi-hidpp" } — 즉 crates.io의 hidpp를 그대로 쓰지 않고 워크스페이스 안으로 들여와 직접 관리한다. 프로토콜을 자기 요구에 맞게 고치고, 버전을 워크스페이스와 묶어 통제하려는 선택. README는 원작자 @lus의 hidpp 크레이트에 감사를 표한다.| 분류 | 크레이트/라이브러리 | 역할 |
|---|---|---|
| 이벤트 후킹 | openlogi-hook + core-graphics 0.25 | macOS CGEventTap으로 마우스 옆버튼을 가로채 재매핑 |
| 플랫폼 FFI | objc2 · objc2-app-kit (NSWorkspace) | 앞 앱(bundle id) 감지. core-foundation·core-graphics는 CGEventTap 이벤트 탭 자체에 사용 |
| HTTP 클라이언트 | ureq 3 (rustls·json) | 기기 이미지 fetch · 업데이트 매니페스트 확인 |
| 해시 | sha2 0.10 | 다운로드 무결성 검증(SHA256SUMS) |
| CLI | clap 4 (derive·env) | openlogi list/diag/assets 명령 트리 |
| 에러·로깅 | anyhow · thiserror 2 · tracing · tracing-subscriber | 에러 처리 + 구조적 로그(OPENLOGI_LOG=debug) |
devenv.nix로 재현 가능한 개발 셸 제공(sccache·툴체인·GPUI가 요구하는 환경변수까지). devenv tasks run openlogi:check 한 줄로 fmt+clippy+test.xtask라는 워크스페이스 멤버 바이너리로 둔다. cargo run -p xtask -- package-macos로 DMG 생성.release-plz.toml)와 pre-commit 훅(prek.toml).channels/stable/latest.json으로 자체 업데이트 채널을 운영(앱이 빌드 시점에 매니페스트 URL을 임베드).설계의 큰 원칙은 "순수 로직과 부수효과(HID·OS·네트워크)의 분리"다. 한가운데 openlogi-core가 있는데, 이 크레이트는 HID도 async도 모른다 — 오직 타입·설정·경로·버튼/액션 카탈로그 같은 "순수한 규칙"만 담는다. 부수효과는 바깥쪽 크레이트(hid·hook·assets)가 떠맡는다. 덕분에 핵심 로직은 하드웨어 없이도 테스트할 수 있다.
openlogi-core가 "HID 없음 · async 없음"이라는 건 의도적인 경계 설정이다. 버튼이 어떤 액션에 묶이는지, 앱별 오버레이가 어떻게 합쳐지는지 같은 의사결정 규칙은 코어에 두고, 실제로 기기에 패킷을 쏘거나 OS 이벤트를 잡는 I/O는 어댑터 크레이트로 민다. 이렇게 하면 코어는 단위 테스트가 쉽고, 나중에 Linux/Windows 후킹을 추가해도 코어를 안 건드린다.
HID++ 2.0에서 기기 기능은 feature ID로 식별된다. OpenLogi가 쓰는 두 개가 README·USAGE에 그대로 노출된다.
| Feature ID | 이름 | OpenLogi에서의 용도 |
|---|---|---|
0x2201 | Adjustable DPI | DPI 읽기/쓰기, 프리셋 순환(Cycle)·지정(Set) |
0x2111 | Smart Shift | 스크롤 휠 걸쇠식 ↔ 자유회전 토글 |
흐름은 항상 "기기 열기 → 어떤 feature가 있는지 조회 → 해당 feature page에 요청 패킷 전송 → 응답 파싱"이다. CLI의 openlogi diag features는 활성 기기가 보고하는 모든 feature를 덤프하고, diag dpi는 "읽기→쓰기→되읽기→복원"으로 안전하게 동작을 점검한다.
마우스를 "기능별 창구가 늘어선 관공서"라고 보자. 0x2201 창구는 DPI 담당, 0x2111 창구는 휠 모드 담당이다. OpenLogi는 먼저 "오늘 어떤 창구가 열려 있나(feature 조회)"를 묻고, 열린 창구에만 정해진 양식(패킷)으로 민원을 넣는다. 기기마다 열린 창구가 다르므로, 무턱대고 명령하지 않고 항상 먼저 물어보는 게 안전장치다.
DPI·SmartShift는 기기에 직접 쓰지만, 버튼 재매핑은 결이 다르다. 옆버튼을 "Undo"로 바꾸려면 OS 차원에서 그 클릭을 가로채 다른 입력으로 바꿔치기해야 한다. macOS에서는 CGEventTap(Core Graphics 이벤트 탭)이 그 역할을 하고, 이를 쓰려면 접근성(Accessibility) 권한이 필요하다. 동시에 objc2 / NSWorkspace로 "지금 앞에 있는 앱의 bundle id"를 감지해, 앱별 프로필(per-app overlay)을 실시간으로 적용한다.
macOS는 CGEventTap, Linux는 evdev+uinput으로 각각 구현돼 있다. Windows 후킹은 현재 미테스트 preview 상태다. DPI/SmartShift(HID++) 쪽은 크로스플랫폼에 가깝고, "버튼 재매핑"도 macOS·Linux는 정식 지원한다.
설정 파일에는 schema_version = 1이 박혀 있다. 나중에 구조가 바뀌어도 마이그레이션 기준점을 갖기 위함이다. 저장은 원자적(atomic)으로 — 임시 파일에 다 쓴 뒤 한 번에 교체 — 처리해, 쓰는 도중 앱이 죽어도 설정이 깨지지 않게 한다. 기기별 설정은 HID++ 식별자(예: MX Master 4 = 2b042)를 키로 묶는다.
| 크레이트 | 성격 | 핵심 책임 |
|---|---|---|
openlogi-core | 순수 로직 | HID·async·OS를 전혀 모름. 설정 스키마, 버튼→액션 매핑 규칙, 경로 계산. 테스트하기 가장 쉬운 중심 |
openlogi-hidpp | 프로토콜 | HID++ 메시지 바이트 레이아웃. feature page 요청/응답 직렬화 |
openlogi-hid | 장치 I/O | enumerate()로 기기 탐색, DPI/SmartShift 쓰기. async-hid 어댑터 |
openlogi-assets | 네트워크 | 기기 렌더 이미지의 스키마 + assets.openlogi.org 캐시 fetch |
openlogi-cli | 진입점(CLI) | list·diag·assets sync 명령 구현 |
openlogi-gui | 진입점(GUI) | 마우스 다이어그램·액션 피커·DPI 프리셋·기기 캐러셀 |
openlogi-hook | OS 후킹 | macOS 이벤트 탭으로 옆버튼 가로채기 + 앱별 프로필 전환 |
xtask | 빌드 도구 | DMG 패키징, .icns 생성, 업데이트 매니페스트 생성 |
한 덩어리(monolithic crate)로 짜도 동작은 한다. 하지만 크레이트 경계 = 컴파일 단위 = 의존성 방향의 강제선이다. "GUI가 HID를 직접 만지지 못하게", "코어가 네트워크를 모르게" 같은 규칙을 폴더 구조가 아니라 컴파일러가 강제하도록 만든 것이다. 컴파일 캐시도 크레이트 단위라 빌드도 빨라진다. 큰 Rust 프로젝트의 정석 설계를 작은 앱에서 깔끔하게 보여주는 사례다.
Cargo.toml의 [workspace.dependencies]로 버전을 한 곳에서 관리하고, 각 크레이트는 { workspace = true }로 끌어다 쓰는 패턴을 그대로 따라 해보자. [workspace.lints]로 unsafe_code = "deny", clippy pedantic, unwrap_used/expect_used = "warn"을 전 크레이트에 일괄 적용하는 것도 실무에서 바로 쓰는 기법이다.
빈 워크스페이스를 만들고 core(순수)·io(부수효과)·cli(진입점) 3개 크레이트로 나눠, "cli → io → core" 한 방향 의존만 허용되도록 짜보기. core에서 io를 import하면 컴파일 에러가 나는지 확인.
async-hid로 HID 장치를 열고 리포트를 읽는 최소 예제부터 시작하자. 그다음 HID++의 "feature 조회 → feature page 호출" 구조를 이해하면, OpenLogi가 왜 diag features로 먼저 능력을 묻고 diag dpi로 "읽기→쓰기→되읽기→복원"을 하는지 자연스럽게 읽힌다.
가진 HID 기기(아무 키보드/마우스)를 async-hid로 enumerate해서 vendor/product id와 리포트를 콘솔에 찍어보기. 로지텍 기기가 있다면 OpenLogi의 diag features 출력과 대조.
Zed가 만든 GPUI는 "Rust로 GPU 가속 UI를 그린다"는 비교적 새 패러다임이다. OpenLogi는 거기에 gpui-component(longbridge)를 얹어 실제 컴포넌트(캐러셀·리스트·피커)를 구성한다. git rev 고정으로 외부 UI 라이브러리를 안전하게 묶는 법도 함께 배울 수 있다.
GPUI "hello world" 창을 띄우고 버튼 클릭 → 상태 변경 → 리렌더의 흐름을 익히기. 그다음 OpenLogi의 openlogi-gui에서 "기기 캐러셀"이 상태를 어떻게 들고 있는지 따라 읽기.
macOS는 core-graphics(CGEventTap)와 objc2/objc2-app-kit(NSWorkspace로 앞 앱 감지), Linux는 evdev+uinput으로 OS 이벤트를 가로채는 코드가 모두 공개돼 있어 플랫폼 FFI의 살아있는 예제다. 워크스페이스가 unsafe_code = "deny"이면서도 FFI가 필요한 크레이트는 #[expect(unsafe_code, reason="...")]로 국소 허용하는 패턴도 눈여겨보자.
macOS에서 CGEventTap으로 특정 키 입력을 로그로 찍는 최소 프로그램 만들기(접근성 권한 요청 포함). "이벤트를 보기만 할지, 변형/소비할지"의 차이를 체험. Linux라면 evdev로 같은 실습 가능.
xtask로 빌드 자동화를 코드화하고, create-dmg로 macOS DMG를 만들고, Cloudflare R2에 올린 latest.json 매니페스트로 자체 업데이트 채널을 운영하는 전 과정이 공개돼 있다. "내 데스크톱 앱을 어떻게 서명·배포·업데이트하나"의 실전 청사진.
| 항목 | 요구사항 |
|---|---|
| 운영체제(실행) | macOS · Linux 정식 지원. Windows는 미테스트 preview(evdev+uinput·udev·systemd·deb/rpm 패키지까지 Linux 인프라 완비) |
| 대상 기기 | 로지텍 HID++ 마우스(MX Master 계열 등). Logi Bolt 리시버 · 블루투스 직결 · 유선 · Unifying 리시버 모두 지원 |
| 권한 | 버튼 재매핑에 접근성(Accessibility) 권한 필요 |
| 충돌 주의 | 설치 전 Logi Options+를 반드시 종료 — 둘이 같은 리시버의 HID++ 접근권을 두고 다툰다(하나만 점유 가능) |
| 설치(사용자) | 서명·공증된 .dmg를 릴리스에서 받거나 brew install --cask aprilnea/tap/openlogi |
| 빌드(개발자) | Stable Rust(Edition 2024, MSRV 1.88), macOS는 Xcode 16+ & Metal Toolchain(GPUI 셰이더 컴파일), 패키징엔 create-dmg |
GPUI의 gpui_macos 빌드 스크립트가 Apple의 metal 셰이더 컴파일러를 요구한다. Nix(devenv) 환경에서는 DEVELOPER_DIR·SDKROOT 등 환경변수가 맞지 않으면 _write/_sysconf 같은 심볼 링크 에러가 난다. devenv.nix를 바꾼 뒤엔 direnv reload로 셸을 다시 로드해야 한다 — 이 디테일이 DEVELOPMENT.md에 길게 적혀 있을 만큼 흔한 함정이다.
저장소를 클론하고 cargo run -p openlogi --release -- list로 페어링된 기기(슬롯·코드명·온라인·배터리)를 출력해본다. 이어 openlogi diag features로 내 마우스가 보고하는 HID++ feature 목록을 덤프해, 0x2201/0x2111이 실제로 보이는지 확인.
~/.config/openlogi/config.toml을 직접 편집해, VS Code가 앞에 있을 때만 Back 버튼이 Undo가 되도록 per_app_bindings."com.microsoft.VSCode" 블록을 추가한다. 앱 포커스를 바꿔가며 동작이 실시간으로 전환되는지 관찰.
OPENLOGI_LOG=debug openlogi diag dpi를 돌리며 "읽기→쓰기→되읽기→복원" 시퀀스의 로그를 따라간다. openlogi-hidpp 소스에서 0x2201 요청 패킷이 어떤 바이트로 조립되는지 코드와 로그를 1:1로 맞춰보기.
openlogi-core의 버튼/액션 카탈로그(41개 내장 액션)에 새 액션을 정의하고, openlogi-hook이 그 액션을 실제 OS 이벤트로 변환하도록 연결한다. "순수 코어에 규칙 추가 → 부수효과 어댑터에서 실행"이라는 경계가 왜 편한지 체감.
현재 Unsupported로 비어 있는 Linux 이벤트 훅을 evdev/libinput 기반으로 일부라도 구현해본다. 코어·HID 계층은 건드리지 않고 openlogi-hook만 확장하면 된다는 설계의 장점을 직접 확인. (가장 임팩트 큰 기여 지점이기도 하다.)
| 주차 | 주제 | 할 일 |
|---|---|---|
| 1주차 | Rust 워크스페이스 & 모듈 경계 | 다중 크레이트 워크스페이스 구성, [workspace.dependencies]·[workspace.lints] 적용, "순수 코어 + 어댑터" 분리 직접 구현 |
| 2주차 | HID & 비동기 I/O | async-hid로 기기 열기/리포트 읽기, tokio 기초, HID++ feature page 개념과 0x2201·0x2111 분석 |
| 3주차 | GPUI 네이티브 UI | GPUI 창·상태·이벤트 루프, gpui-component로 리스트/캐러셀 만들기, git rev 고정으로 외부 UI 의존성 관리 |
| 4주차 | OS 후킹 & 배포 | macOS CGEventTap·접근성·앞 앱 감지, xtask+create-dmg 패키징, R2 정적 매니페스트로 자체 업데이트 채널 |
일부러 "안에서 밖으로" 순서다. 1주차에 경계(코어/어댑터)를 먼저 몸에 익혀야, 이후 HID·UI·OS 후킹이라는 서로 다른 "부수효과"들이 왜 각자의 크레이트로 격리되는지가 자연스럽게 이해된다. 마지막 주차의 배포까지 마치면, "기능 → 패키징 → 업데이트"의 데스크톱 앱 전체 수명주기를 한 바퀴 돌게 된다.
latest.json(업데이트 매니페스트)을 올려 자체 업데이트 채널을 운영한다.