Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/content/en/api-reference/keys/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Subscribes to all raw input events (including unregistered keys).
```typescript
interface RawInputEvent {
device: 'keyboard' | 'mouse' | 'gamepad' | 'unknown';
label: string; // e.g., 'A', 'MOUSE1', 'HIDB:1ccf:101c:9:3'
label: string; // e.g., 'A', 'MOUSE1', 'WHEELUP', 'HIDB:1ccf:101c:9:3'
labels: string[]; // all candidate labels for this event
state: 'DOWN' | 'UP';
}
Expand All @@ -95,6 +95,13 @@ HID device buttons (Windows) are delivered with `device: 'gamepad'` and
labels in the form `HIDB:vid:pid:usagePage:usage`. See the
[Knobs API](/docs/api-reference/knobs) for HID axis (knob) elements.

The mouse wheel is delivered with `device: 'mouse'` and the labels
`WHEELUP`, `WHEELDOWN`, `WHEELLEFT`, and `WHEELRIGHT`. A wheel has no release,
so each notch is synthesized as a short tap: DOWN, then UP 50 ms later. When
the next notch arrives within 50 ms, UP and DOWN are sent back to back, so the
number of DOWN events always equals the number of notches. The wheel can also
drive a knob element as an axis.

---

## Counter Events
Expand Down
24 changes: 17 additions & 7 deletions docs/content/en/api-reference/knobs/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,45 @@ description: dmn.knobItems API reference for HID knob elements
</Callout>

Manage knob elements: rotary visualization elements bound to HID device axes
(e.g., rhythm game controller knobs). While HID device **buttons** are mapped
(e.g., rhythm game controller knobs) or the mouse wheel. While HID device **buttons** are mapped
and visualized exactly like keyboard keys, **axes** are visualized with
dedicated knob elements that rotate along with the physical knob.

<Callout type="info">
HID input recognition is currently supported on **Windows** only.
HID input recognition is currently supported on **Windows** only. The mouse
wheel axis works on both **Windows** and **macOS**.
</Callout>

## Identifiers

DmNote assigns stable string identifiers to HID inputs:

| Kind | Format | Example |
| ------ | ------------------------------ | --------------------- |
| Button | `HIDB:vid:pid:usagePage:usage` | `HIDB:1ccf:101c:9:3` |
| Axis | `HIDA:vid:pid:usagePage:usage` | `HIDA:1ccf:101c:1:48` |
| Kind | Format | Example |
| ---------------- | ------------------------------------- | --------------------- |
| Button | `HIDB:vid:pid:usagePage:usage` | `HIDB:1ccf:101c:9:3` |
| Axis | `HIDA:vid:pid:usagePage:usage` | `HIDA:1ccf:101c:1:48` |
| Mouse wheel axis | `WHEEL:VERTICAL` / `WHEEL:HORIZONTAL` | `WHEEL:VERTICAL` |

Button identifiers appear as key labels (mappable like any keyboard key and
delivered through `dmn.keys.onRawInput` with `device: 'gamepad'`). Axis
identifiers are stored in a knob element's `axisId`.

The mouse wheel axis is a virtual axis where 24 wheel notches make one
revolution, so with `sensitivity` 1 a knob turns 360° every 24 notches. Wheel
up (away from you) and tilt right are the positive direction. The horizontal
axis uses the same unit but has no physical revolution behind it. On macOS the
wheel is read from the mouse's HID reports, so trackpad and touch-surface
scrolling is not recognized, and a mouse utility that takes over the wheel can
prevent it from being recognized.

## Types

```typescript
// KeyPosition styling fields are inherited (position, size, colors, images...)
// including the stable element `id` (UUID): it keeps identifying the same knob
// across reorders and mode switches, and full preset loads reissue new IDs (tab presets only for collections they include)
type KnobItemPosition = KeyPosition & {
axisId: string; // bound HID axis ("HIDA:..."), empty if unbound
axisId: string; // bound axis ("HIDA:..." or "WHEEL:..."), empty if unbound
sensitivity: number; // rotation multiplier (default 1)
reverse: boolean; // invert rotation direction
};
Expand Down
8 changes: 7 additions & 1 deletion docs/content/ko/api-reference/keys/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ dmn.keys.onKeysReset(({ reason }) => {
```typescript
interface RawInputPayload {
device: 'keyboard' | 'mouse' | 'gamepad' | 'unknown';
label: string; // "D", "MOUSE1", "HIDB:1ccf:101c:9:3" 등
label: string; // "D", "MOUSE1", "WHEELUP", "HIDB:1ccf:101c:9:3" 등
labels: string[]; // 모든 레이블 목록
state: string; // "DOWN" | "UP"
}
Expand All @@ -448,6 +448,12 @@ HID 기기 버튼(Windows)은 `device: 'gamepad'`와
`HIDB:vid:pid:usagePage:usage` 형식의 레이블로 전달됩니다. HID 축(노브)
요소는 [노브 API](/docs/api-reference/knobs)를 참고하세요.

마우스 휠은 `device: 'mouse'`와 `WHEELUP`·`WHEELDOWN`·`WHEELLEFT`·`WHEELRIGHT`
레이블로 전달됩니다. 휠에는 떼는 동작이 없어서 한 칸마다 DOWN을 보내고 50ms 뒤에
UP을 보내는 짧은 탭으로 합성합니다. 50ms 안에 다음 칸이 오면 UP과 DOWN을 바로
이어 보내므로 DOWN 수는 언제나 칸 수와 같습니다. 휠은 노브 요소의 축으로도 쓸 수
있습니다.

<Callout type="info">
구독자가 없으면 백엔드에서 이벤트를 emit하지 않아 성능 오버헤드가 없습니다.
</Callout>
Expand Down
25 changes: 17 additions & 8 deletions docs/content/ko/api-reference/knobs/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,45 @@ description: HID 노브 요소를 위한 dmn.knobItems API 레퍼런스
coordinator를 거쳐 노브 레이아웃을 쓸 수 있습니다.
</Callout>

노브 요소를 관리합니다. HID 기기 축(예: 리듬게임 컨트롤러의 노브)에 바인딩되어
물리 노브와 함께 회전하는 시각화 요소입니다. HID 기기의 **버튼**은
노브 요소를 관리합니다. HID 기기 축(예: 리듬게임 컨트롤러의 노브)이나 마우스 휠에
바인딩되어 물리 노브와 함께 회전하는 시각화 요소입니다. HID 기기의 **버튼**은
키보드 키와 완전히 동일하게 매핑·시각화되고, **축**은 전용 노브 요소로
시각화됩니다.

<Callout type="info">
HID 입력 인식은 현재 **Windows**에서만 지원됩니다.
HID 입력 인식은 현재 **Windows**에서만 지원됩니다. 마우스 휠 축은
**Windows**와 **macOS** 모두에서 동작합니다.
</Callout>

## 식별자

DmNote는 HID 입력에 고정 문자열 식별자를 부여합니다:

| 종류 | 형식 | 예시 |
| ---- | ------------------------------ | --------------------- |
| 버튼 | `HIDB:vid:pid:usagePage:usage` | `HIDB:1ccf:101c:9:3` |
| 축 | `HIDA:vid:pid:usagePage:usage` | `HIDA:1ccf:101c:1:48` |
| 종류 | 형식 | 예시 |
| ------------ | ------------------------------------- | --------------------- |
| 버튼 | `HIDB:vid:pid:usagePage:usage` | `HIDB:1ccf:101c:9:3` |
| 축 | `HIDA:vid:pid:usagePage:usage` | `HIDA:1ccf:101c:1:48` |
| 마우스 휠 축 | `WHEEL:VERTICAL` / `WHEEL:HORIZONTAL` | `WHEEL:VERTICAL` |

버튼 식별자는 키 라벨로 사용되며(일반 키보드 키처럼 매핑 가능,
`dmn.keys.onRawInput`에서 `device: 'gamepad'`로 전달) 축 식별자는 노브
요소의 `axisId`에 저장됩니다.

마우스 휠 축은 휠 24칸을 1회전으로 보는 가상 축입니다. `sensitivity`가 1이면
24칸마다 노브가 360° 돕니다. 휠을 앞쪽(사용자 반대쪽)으로 굴리거나 오른쪽으로
기울이는 방향이 양수입니다. 가로 축도 같은 단위를 쓰지만 실제 회전량과는
관계가 없습니다. macOS에서는 마우스의 HID 보고를 직접 읽기 때문에 트랙패드나
터치 표면 스크롤은 인식하지 않습니다. 휠을 가로채는 마우스 유틸리티를 쓰면
인식되지 않을 수 있습니다.

## 타입

```typescript
// KeyPosition의 스타일 필드를 상속 (위치, 크기, 색상, 이미지 등)
// 요소 안정 id(UUID)도 포함: 재정렬·모드 전환에도 같은 노브를 가리키며,
// 전체 프리셋 로드 시에는 새 ID가 발급됩니다 (탭 프리셋은 담긴 컬렉션만)
type KnobItemPosition = KeyPosition & {
axisId: string; // 바인딩된 HID 축("HIDA:..."), 미바인딩이면 빈 문자열
axisId: string; // 바인딩된 축("HIDA:..." 또는 "WHEEL:..."), 미바인딩이면 빈 문자열
sensitivity: number; // 회전 배율 (기본 1)
reverse: boolean; // 회전 방향 반전
};
Expand Down
1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ self-replace = "1.5"

[target."cfg(target_os = \"macos\")".dependencies]
libc = "0.2"
core-foundation = "0.10"
rdev = "0.5.3"
objc = "0.2"
objc2 = "0.6"
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/keyboard/daemon/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ pub(super) fn run_macos() -> Result<()> {
}

let output = super::start_output_writer(Box::new(std::io::stdout()))?;
let wheel = super::wheel::spawn_wheel_engine(output.clone())?;
super::macos_wheel::start_hid_wheel_capture(wheel);

// rdev::listen — 접근성 + 입력 모니터링 권한 필수
// 권한 부여 직후 CGEventTap 생성 실패 가능 — 재시도 처리
Expand Down
Loading
Loading