| 항목 | 내용 |
|---|---|
| 제품명 | 한번 (HanBeon) |
| 한 줄 | 한 번의 누름으로 PC를 제어하는 상황적응형 싱글스위치 소프트웨어 |
| 행사 | 제7회 국립재활원 보조기기 해커톤 — '컴퓨터 및 전자기기 접근성 보조기기' |
| 팀 | 데브파이브 |
| 문서 상태 | 초안 v0.1 (2026-08-12) |
| 범위 | 소프트웨어. 하드웨어(대형 스위치·하우징·펌웨어)는 별도 트랙 |
손·손가락의 정교한 움직임이 어려운 사용자는 키보드와 마우스의 여러 입력을 동시에 다룰 수 없다. 대안으로 쓰이는 싱글스위치 자동주사(auto-scanning) 는 다음과 같이 동작한다.
- 커서가 화면의 요소를 일정 간격(보통 1초)으로 순서대로 훑는다.
- 사용자는 원하는 요소에 커서가 왔을 때 스위치를 누른다.
이 방식의 구조적 결함은 되돌아갈 수 없다는 것이다.
- 목표 요소를 지나치면 목록 전체를 한 바퀴 돌 때까지 기다려야 한다. 요소가 40개인 웹페이지에서 한 칸을 놓치면 39틱, 약 40초를 기다린다.
- 오후 피로로 반응이 0.3초 늦어지면 놓침이 급증하는데, 간격은 고정이다.
- 놓칠까 봐 서두르면 오선택이 늘고, 오선택을 되돌릴 방법도 마땅치 않다.
결과적으로 사용자는 실수 비용이 두려워 익숙한 과업만 반복하게 되고, 새 기능을 시도하지 않는다. 신청서 페르소나(김도윤)가 보호자 도움을 받는 지점도 대부분 '놓침 이후'와 '오선택 이후'다.
스캔 대상을 화면의 요소가 아니라 4개의 '동작'으로 바꾼다.
| 칸 | 주입하는 키 | 의미 |
|---|---|---|
> |
Tab |
다음 요소로 |
< |
Shift+Tab |
이전 요소로 |
Enter |
Enter |
선택 |
설정 |
— | 설정 화면 진입 |
세 가지가 동시에 해결된다.
- 최악 대기가 3틱으로 고정된다. 화면에 요소가 몇 개든 순환 길이는 4다.
- 지나쳐도 돌아올 수 있다.
<가 스캔 목록 안에 상시 존재한다. 한 칸을 놓쳤을 때의 비용이 '한 바퀴'에서 '한 번 더 누르기'로 줄어든다. - 포커스 이동을 OS와 브라우저에 위임한다. 우리가 요소를 탐지·나열할 필요가 없으므로 임의의 앱·웹사이트에서 그대로 동작한다.
여기에 신청서의 두 축을 얹는다.
- 상황적응 주사: 최근 반응 기록으로 주사 간격을 사용자가 정한 범위 안에서만 조정
- 오류 복구: 되돌리기 창과 길게 누르기 취소
4칸 순환만으로는 부족하다. 웹 탐색은 Tab을 연속으로 여러 번 누르는 일이 대부분인데,
매번 순환을 기다리면 Tab 한 번에 최대 4틱이 든다.
그래서 선택 직후에는 커서를 그 칸에 머물게 하고 순환을 멈춘다.
Tab을 한 번 선택하면 커서는>에 머문다. 다시 누르면 곧바로Tab이 한 번 더 나간다. 즉 연속 이동의 비용이 누름 1회 = 이동 1칸이 된다.- 다른 동작을 하려면 누르지 않고 기다린다. 머무름 시간이 지나면 순환이 자동 재개된다.
이 설계로 가장 빈번한 조작(연속 Tab)의 대기가 0이 되고, 드문 조작만 순환 비용을 낸다. 기존 방식 대비 실질 개선의 대부분이 여기서 나온다.
신청서 페르소나를 그대로 따른다.
김도윤, 34세. 재택으로 행정자료를 정리하고 온라인 회의에 참여한다. 하루 56시간
PC·태블릿 사용. 경수 손상으로 손가락 미세 조작은 어렵지만 왼쪽 팔꿈치와 손등으로
지름 80100mm 저압 스위치를 안정적으로 누를 수 있다. 짧게/길게 누르기는 구분할 수
있으나 피로가 쌓이면 누르는 시간이 길어지고 입력 간격이 불규칙해진다.
- 오전에는 반응이 빠르고 오후에는 근긴장·피로로 선택 시간이 늘어난다.
- 휠체어 책상, 침상, 재활실을 오가며 스위치 위치와 각도가 달라진다.
- 기술 수용도는 중간 이상. 큰 글자·고대비·단계별 미리보기가 있으면 새 기능을 시도한다.
- 초기 설정은 3단계 이내, 일상 조정은 한 화면 안에서 끝나기를 기대한다.
핵심 과업 4가지: 이메일 확인, 문서 열기, 화상회의 참여, 메시지 응답. 성공 기준은 이 네 가지를 보호자 개입 없이 스스로 끝내는 것이다.
이 원칙과 충돌하는 기능은 넣지 않는다.
- 수동이 자동을 이긴다. 적응 로직은 사용자가 정한 최소·최대 간격을 벗어나지 않는다. 수동 고정 중에는 어떤 근거로도 간격을 바꾸지 않는다.
- 자동 변경은 항상 보인다. 간격이 바뀌면 현재 값과 변경 이유를 화면에 표시한다.
- 모든 선택은 되돌릴 수 있다. 되돌리기가 불가능한 동작은 스캔 대상에 넣지 않는다.
- 상태는 두 감각으로 알린다. 화면 강조 + 소리(또는 LED).
- 한 번의 실수가 과업 포기로 이어지지 않는다. 반복 오선택이 감지되면 속도를 안전값으로 낮추고 다시 선택할 시간을 준다.
- 예측 가능성이 최적화보다 우선한다. 사용자가 이유를 모르는 변화는 성능이 좋아져도 하지 않는다.
- macOS·Windows·Linux Tauri floating 컨트롤러 (기본 4칸 스캔)
- 앱별 확장 칸 (PDF 뷰어·음악 앱, 앞 4칸 뒤에 최대 3칸)
- 스위치 입력 전역 수신, 짧게/길게 누름 판정
Tab/Shift+Tab/Enter전역 주입- 머무름·순환 상태기계
- 되돌리기 창과 길게 누르기 취소
- 상황적응 주사 간격 (수동 고정·범위 제한 포함)
- 설정 화면과 사용자 프로필 로컬 저장
- 시각·청각 피드백
- 실증용 이벤트 로그 기록
- 앱 전환 — 스위치만으로 다른 앱으로 넘어가기. MVP에서는 보호자가 대신 띄워 준다고 전제한다(아래 5.4).
- 사용자가 직접 만드는 앱별 칸(Hana Cloud의 검토된 프로필만 제공) — M4 이후
- 마우스 포인터를 쓰는 명령 세트(위·아래·좌·우) — M4 이후
- Android(AccessibilityService + 오버레이)
- 프로필 서버 동기화 — MVP는 로컬 저장만 제공
- 문자 입력(온스크린 키보드), 마우스 포인터 제어
- 예측 입력·단어 완성
| 플랫폼 | floating 오버레이 | 전역 키 주입 | MVP | 앞에 있는 앱 식별 / 비고 |
|---|---|---|---|---|
| macOS | 가능 (NSPanel) | 가능 (접근성 권한) | ○ | bundle ID; 손쉬운 사용 권한 필수 |
| Windows | 가능 (WS_EX_NOACTIVATE) | 가능 (SendInput) | ○ | 실행 파일 basename; 관리자 권한 앱 제어는 제한 |
| Linux (X11) | 가능 | 가능 | ○ | desktop ID와 WM_CLASS |
| iOS / iPadOS | 불가 | 불가 | × | OS 정책. 아래 참조 |
| Android | 가능 (오버레이 권한) | 가능 (AccessibilityService) | × | M5 이후 |
Linux Wayland에서 안전한 compositor/desktop 어댑터가 없으면 활성 앱 식별을 추측하지 않는다. 이 경우 앱별 프리셋은 선택하지 않고 기본 4칸을 유지한다.
iOS·iPadOS는 구조적으로 불가능하다. 서드파티 앱은 시스템 전역 오버레이를 띄울 수 없고, 다른 앱에 키 입력을 주입할 수도 없다. 우회 방법을 찾지 않는다.
대신 태블릿은 하드웨어 스위치를 BT HID 키보드로 직접 연결해 iPadOS 내장 '스위치 제어'와 연동하고, 우리 앱은 설정·프로필 안내에 한정한다. 신청서의 '기기 전환 1분 이내' 지표는 이 구성으로 측정한다.
스위치만으로는 앱을 바꿀 수 없다. 우리가 보내는 것은 Tab·Shift+Tab·
Enter와 앱별 단축키뿐이고, 이것으로는 다른 앱으로 넘어갈 수 없다. Cmd+Tab은
누른 채 유지해야 목록이 떠서 한 번 누르고 마는 우리 구조로는 다룰 수 없다.
그래서 MVP는 앱을 띄우고 바꾸는 일은 보호자가 한다고 전제한다. 사용자는 띄워진 앱 안에서 스스로 조작한다.
이 전제는 실증 설계를 바꾼다.
- 과업은 한 앱 안에서 완결되도록 설계한다. 앱을 오가야 끝나는 과업을 재면 보호자 개입이 과업 설계 때문에 생기고, 그 숫자는 제품의 성능이 아니다.
- 아래 10절의 '보호자 개입' 지표는 앱 전환을 빼고 센다. 빼지 않으면 처음부터 달성할 수 없는 목표가 되고, 억지로 달성했다고 적으면 실측을 왜곡하게 된다.
M4 이후에 자주 쓰는 앱을 바로 여는 칸을 검토한다. 지금 넣지 않는 이유는, 그것이 '앱 목록을 스캔한다'는 또 하나의 순환을 만들어 4칸 구조와 부딪히기 때문이다.
[대형 스위치] --USB/BT HID--> [OS] --전역 키 이벤트--> ┌──────────────────┐
│ Rust 코어 │
│ input 판정 │
│ scan 상태기계 │
│ adapt 간격 조정 │
│ emit 키 주입 │
└────┬────────┬─────┘
상태 emit│ │키 주입
▼ ▼
[floating 창] [대상 앱]
(Next.js 정적) (브라우저 등)
스캔 타이머·입력 판정·키 주입은 모두 Rust 코어에 둔다. 프론트는 코어가 보낸 상태를 그리기만 한다. 근거는 두 가지다.
- 주사 간격이 웹뷰 렌더링 지연에 흔들리면 사용자의 타이밍 학습이 무너진다.
- 눌림 시간 판정과 키 주입이 같은 프로세스 경계 안에 있어야 지연을 예측·계측할 수 있다.
하드웨어는 별도 팀이 제작 중이며, 소프트웨어는 아래를 가정한다.
이 가정이 바뀌면 이 절을 먼저 갱신하고 input 모듈을 수정한다.
- 스위치는 USB HID 또는 Bluetooth HID 키보드로 인식된다. 전용 드라이버가 없다.
- 스위치를 누르는 동안 지정된 키 하나가 눌림 상태로 유지되고, 떼면 해제된다. (눌림 시간을 측정해야 하므로 단발 이벤트만 오면 길게 누르기를 판정할 수 없다)
- 기본 키는 일반 사용에서 충돌이 적은
F13을 쓰고, 설정에서 변경할 수 있다. - 접점 떨림(chattering) 필터는 하드웨어와 소프트웨어 양쪽에서 건다.
확인 필요 (하드웨어 팀): 키 리피트가 꺼져 있는지, BT 연결 끊김 시 어떤 이벤트가 오는지, 배터리 부족 신호를 소프트웨어가 알 수 있는지.
-
화면 위에 항상 떠 있고, 모든 워크스페이스/데스크톱에서 보인다.
-
배치는 갈래로 정한다. 창 기본 크기 360×280, 기본 위치는 주 모니터 우하단. (칸 위에 드래그 손잡이와 남은 시간 막대가, 아래에 상태 줄이 한 줄씩 붙는다)
갈래 자리 이동 ><왼쪽에 세로로 선택 Enter오른쪽에 이동 칸 전체 높이로 앱별 칸 구분선 아래에 2열로 설정 맨 아래, 다른 칸보다 낮게 이동과 선택을 붙여 두는 이유는 옮긴 뒤에 고르는 흐름이 가장 잦기 때문이다. 설정을 맨 아래로 내린 이유는 가장 드물게 쓰기 때문이다.
-
창 높이는 앱별 칸 수에 따라 280 / 376 / 444px로 달라진다(F11). 창은 좌상단을 고정하고 아래로 자란다 — 그래야 이동·선택 칸의 화면상 자리가 유지된다. 화면 밖으로 나가면 그만큼만 위로 올린다.
-
창은 절대 포커스를 가져가지 않는다. (macOS: nonactivating NSPanel + Accessory activation policy, Windows:
WS_EX_NOACTIVATE) 이것이 깨지면 주입한 키가 대상 앱이 아니라 우리 창으로 들어가 제품이 동작하지 않는다. -
사용자가 창을 드래그해 옮길 수 있고, 위치는 프로필에 저장된다.
- 창 전체가 아니라 상단 손잡이만 잡힌다. 4칸 어디서나 끌리면 화면을 확인하려던 손짓 한 번에 사용자가 익힌 위치가 달라진다.
- 드래그가 끝나면 활성 상태를 직전 앱에 돌려준다. 창을 끄는 순간 우리 앱이 활성 앱이 되는데, 그대로 두면 이후 주입한 키가 대상 앱으로 가지 않는다. 스위치만 쓰는 사용자는 다른 앱을 다시 클릭할 수단이 없어 조작이 막힌다.
- 저장된 위치가 화면 밖이면(모니터 분리·해상도 변경) 기본 위치로 되돌린다. 보이지 않는 창은 되찾을 방법이 없다.
-
조작할 요소를 가리면 창을 반투명하게 만든다. 컨트롤러는 항상 맨 앞에 뜨므로 지금 고르고 있는 요소가 그 아래에 들어갈 수 있다. 커서는 정상적으로 돌고 있어서 화면만 봐서는 무엇이 잘못됐는지도 알 수 없다.
- 판정 근거는 지금 포커스를 가진 요소의 화면 좌표다. 좌표를 읽지 못하면 아무것도 하지 않는다 — 근거 없이 흐리게 만들면 저시력 사용자는 이유 없이 화면을 잃는다.
- 되돌리기 창에서는 흐리게 하지 않는다. 3초 안에 눌러야 하는 안전 장치라, 가려지는 손해보다 되돌릴 기회를 놓치는 손해가 크다.
- 불투명도는 설정에서 25~100%로 조절하고, 통째로 끌 수 있다. 기본 40%.
-
작업 표시줄·Dock에 노출되지 않는다.
전역 키 후킹으로 스위치 키의 눌림/뗌 시각을 받아 아래로 판정한다.
| 제스처 | 조건 | 기본 동작 |
|---|---|---|
| 짧게 누름 | 눌림 유지 < 600ms | 현재 칸 실행 |
| 길게 누름 | 눌림 유지 ≥ 600ms | 취소 / 일시정지 토글 |
| 접점 떨림 | 직전 입력에서 50ms 이내 | 무시 |
- 길게 누르기 임계값은 설정에서 300~1500ms로 조정할 수 있다. 피로가 쌓이면 짧게 누르려던 입력이 길어지므로 고정값으로 두면 안 된다.
- 길게 누름은 임계값에 도달하는 순간 피드백을 준다. 손을 떼기 전에 무엇이 일어날지 알 수 있어야 한다.
┌──────────────────────────────────────────┐
│ │
┌────▼─────┐ 짧게 누름 ┌──────────┐ 머무름 만료 │
│ SCANNING ├─────────────►│ DWELLING ├────────────┘
│ (순환) │ │ (머무름) │
└────┬─────┘ └────┬─────┘
│ │ 짧게 누름 = 같은 동작 반복
│ 길게 누름 │ (DWELLING 유지, 타이머 초기화)
▼ │
┌──────────┐ │ Enter 실행 시
│ PAUSED │ ▼
│ (정지) │ ┌──────────┐
└──────────┘ │ CONFIRM │ 짧게 누름 = 되돌리기
│ (되돌리기│ 3초 경과 = DWELLING
│ 창 3초) │
└──────────┘
- SCANNING: 커서가
주사 간격마다>→<→Enter→설정순으로 이동. - DWELLING: 실행한 칸에 커서 고정. 다시 누르면 같은 동작 반복.
주사 간격 × 1.5동안 입력이 없으면 SCANNING으로 복귀(다음 칸부터). - CONFIRM:
Enter실행 직후 3초. 이 동안 누르면 되돌리기가 실행된다.><는 반대 방향 칸이 곧 되돌리기이므로 CONFIRM을 거치지 않는다. (매 이동마다 3초를 대기시키면 연속 탐색이 불가능해진다) - PAUSED: 커서 정지, 키 주입 없음. 길게 누르면 해제. 자세를 바꾸거나 스위치 위치를 옮길 때, 그리고 다른 사람이 잠시 화면을 볼 때 쓴다.
각 상태는 색·라벨·소리로 구분되며, 현재 상태를 화면에서 항상 읽을 수 있어야 한다.
순환 순서
칸과 순서는 같지 않다. Enter는 칸이 하나지만 이동 칸마다 그 뒤에 끼어들어
순환에는 두 번 나온다.
> → Enter → < → Enter → [앱별 칸…] → 설정 → 다시 >
옮긴 뒤에 고르는 것이 가장 잦은 흐름인데, 이동과 선택이 멀리 떨어져 있으면 그 흐름이 매번 한 바퀴를 돈다. 이렇게 두면 어느 쪽으로 옮겼든 한 틱만 기다리면 고를 수 있다.
선택하면 첫 이동 칸부터 다시 시작한다. 고르고 나서 다시 옮기는 것이 다음으로 잦은 흐름이라, 이어서 순환하면 그 흐름도 한 바퀴를 돈다.
값은 순환에 한 자리를 더 쓰는 것이다. 기본 4칸이 5자리가 되어 최악 대기가 3틱에서 4틱으로 는다. 자주 밟는 길을 싸게 만들고 드문 길을 비싸게 만드는 교환이다.
- 대상은 현재 포커스를 가진 앱이다. 우리가 대상을 고르지 않는다.
Tab,Shift+Tab,Enter를 OS 레벨로 주입한다.- 주입 실패(권한 없음 등)는 조용히 넘어가지 않는다. 화면에 원인과 해결 방법을 띄운다. macOS 접근성 권한 미허용이 가장 흔한 원인이다.
- 주입 지연 목표: 스위치 뗌 → 키 이벤트 도달 100ms 이내.
관측값 (최근 10회 이동 윈도우)
t_react: 커서가 칸에 진입한 시점 → 스위치 눌림까지의 시간miss: 순환을 한 바퀴 이상 돌고 나서야 선택한 경우undo: 되돌리기가 발생한 경우
조정 규칙
목표 = clamp(avg(t_react) + 300ms, 사용자 최소, 사용자 최대)
간격 = 간격 + clamp(목표 - 간격, -간격 × 0.10, +간격 × 0.10)
-
한 번에 ±10% 이상 바뀌지 않는다. 급변은 사용자의 타이밍 학습을 무너뜨린다.
-
undo나 연속 오선택이 감지되면 즉시 안전값(간격 × 1.25, 최대 이내)으로 늘리고 다음 3회 동안은 줄이는 방향으로 조정하지 않는다. -
최소·최대는 사용자가 설정에서 직접 정한다. 기본 0.8초~4.0초, 초기값 1.8초.
-
수동 고정이 켜져 있으면 관측만 하고 간격은 바꾸지 않는다.
-
간격이 바뀌면 설정 화면과 floating 창에 현재 값과 이유 ("최근 반응이 빨라져 1.8초 → 1.7초")를 표시한다. 이유는 세 가지뿐이다.
이유 문구 조건 빨라짐 최근 반응이 빨라져 A초 → B초반응 표본 3회 이상, 목표가 지금보다 짧음 느려짐 최근 반응이 느려져 A초 → B초같은 계산에서 목표가 지금보다 김 안전값 실수가 감지되어 A초 → B초undo또는miss발생- floating 창은 맨 아래 한 줄에 띄우고 6초 뒤 현재 속도 표시로 돌아간다. 그 줄은 알릴 것이 없을 때도 비우지 않고 현재 주사 간격("2.5초마다")을 보여준다. 나타났다 사라지는 줄은 그때마다 4칸을 밀어 올려, 사용자가 커서 위치를 다시 찾게 만든다.
- 일시정지 안내가 조정 이유보다 앞선다. 멈춰 있다는 사실을 놓치면 사용자는 스위치가 고장 난 줄 안다.
| 상황 | 조작 | 결과 |
|---|---|---|
방금 Enter를 잘못 눌렀다 |
CONFIRM 창(3초) 안에 짧게 누름 | 되돌리기 실행 |
| 이동을 지나쳤다 | < 칸 선택 |
Shift+Tab으로 복귀 |
| 진행 중인 동작을 멈추고 싶다 | 길게 누름 | 취소 후 PAUSED |
| 반복 오선택 | (자동) | 간격을 안전값으로 늘리고 알림 |
되돌리기 동작 매핑은 프로필 설정 항목이다. 기본값은 뒤로가기
(macOS Cmd+[, Windows Alt+←)이며, Cmd/Ctrl+Z로 바꿀 수 있다.
한계: 되돌리기는 대상 앱이 해당 단축키를 지원할 때만 실제로 되돌아간다. 모든 앱에서 되돌릴 수 있다고 사용자에게 약속하지 않는다. 되돌리기를 보낸 뒤 "되돌리기를 보냈습니다"라고 표시하되, 결과를 확인해야 함을 함께 안내한다.
- 시각: 현재 칸을 색 + 테두리 두께(2px → 5px)로 표시. 색만으로 구분하지 않는다. 커서가 있는 칸에만 무슨 동작인지 이름을 붙인다. 네 칸 모두에 설명을 달면 글자가 늘어 정작 커서 위치가 묻힌다.
- 남은 시간: 4칸 위에 막대로 지금 칸이 끝나기까지 남은 시간을 보여준다.
스위치 하나로 조작하는 사용자는 '언제 누를지'를 커서가 도착하기 전에 정해야
하고, 남은 시간이 보이지 않으면 매번 도착 순간에 반응해야 한다.
- 모드마다 길이가 다르다. 순환은 주사 간격, 머무름은 그 1.5배가 분모다.
- 되돌리기 창(3초)에서는 줄어들지 않는다. 막대를 가득 채운 채로 두어 '지금 되돌릴 수 있다'만 알린다. 카운트다운은 사용자를 재촉하고, 이 제품의 사용자는 재촉당하면 실수한다.
- 눈금은 60ms 단위로만 다시 그린다. 쉬지 않고 흐르는 화면은 시선을 막대에 붙잡아 정작 커서를 놓치게 만든다.
- 청각: 커서 이동 시 짧은 틱, 선택 시 확인음, 되돌리기 창 진입 시 구분되는 음. 각각 켜고 끌 수 있고 음량을 조절할 수 있다.
- LED: 하드웨어 연동 시 스캔 위치·상태를 색으로 표시. (하드웨어 인터페이스 확정 후)
- 세 가지는 서로 독립적으로 켜고 끈다. 소음이 큰 환경과 저시력 환경 중 하나만 가정하지 않는다.
한 화면에 모두 펼친다. 계층 메뉴를 만들지 않는다. (초기 설정 10분 이내가 지표)
- 주사 속도: 슬라이더 + 현재 값(초)
- 적응 모드 켜기/끄기, 최소·최대 간격
- 수동 고정
- 길게 누르기 임계값
- 스위치 테스트: 실제로 눌러 보며 눌림 시간과 판정 결과를 즉시 확인
- 되돌리기 동작 매핑
- 소리·LED 켜기/끄기
- 테마: 밝게 / 어둡게 / 고대비
- 현재 상태: 최근 선택 성공 n/10, 현재 간격, 되돌리기 가능 시간, 스위치 연결 상태
초기 설정 3단계: ① 스위치 연결·테스트 → ② 속도 범위 지정 → ③ 프로필 저장.
- 저장 항목: 주사 간격과 범위, 적응 모드 on/off, 길게 누르기 임계값, 스위치 키, 소리·LED, 테마, floating 창 위치, 되돌리기 매핑
- 로컬 파일로 저장하며 앱 시작 시 자동 복원한다.
- 여러 프로필을 만들고 전환할 수 있다. (오전/오후, 책상/침상)
- 연결이 끊기거나 앱이 비정상 종료돼도 마지막 저장 설정으로 복구한다.
- 스위치 연결/해제를 감지해 floating 창에 표시한다.
- 연결이 끊기면 스캔을 PAUSED로 전환한다. 커서만 계속 도는 상태로 두지 않는다.
- 재연결 시 이전 상태로 복귀한다.
- 배터리 정보를 얻을 수 있으면 부족 시 미리 알린다.
4칸만으로는 아예 닿을 수 없는 동작이 있다. PDF 뷰어의 페이지 넘김은 PageDown,
음악 앱의 재생 제어는 미디어 키다. 둘 다 Tab·Enter로는 도달할 수 없어,
그 앱에서 컨트롤러는 사실상 쓸모가 없었다.
활성 앱 조회는 macOS bundle ID, Windows 실행 파일 basename, Linux X11 desktop ID와
WM_CLASS를 원시 값으로 읽는다. Hana Cloud의 registry.json이 이 식별자를 앱
프로필과 연결하고, HanBeon은 HTTPS host·크기·schema·SHA-256을 검증한 마지막 정상본을
캐시한다. 포커스 확인 루프에서는 네트워크나 디스크를 기다리지 않는다. Linux
Wayland에서 안전한 compositor/desktop 어댑터가 없으면 앱별 프로필을 선택하지 않고
기본 4칸을 유지한다.
- 지금 앞에 있는 앱에 맞춰 기본 칸과 설정 사이에 최대 3칸을 더 붙인다.
- 기본 칸의 순서와 자리는 어떤 앱에서도 바뀌지 않는다. 사용자는 자리로 동작을 기억한다.
- 앱별 칸은 기본 칸과 다르게 그린다. 구분선과 프리셋 이름으로 묶고, 배경 색조·테두리 색·모서리 둥글기를 달리한다. 지금 앱에서만 쓸 수 있고 앱이 바뀌면 사라지는 칸이라, 같아 보이면 사용자는 언제나 있는 칸으로 여기고 자리를 외운다.
- 세트는 앱이 바뀔 때만 교체한다. 커서 위치나 화면 요소에 따라 실행 중에 칸이 바뀌지 않는다 — 다음에 무엇이 올지 예측할 수 없게 되면, 이 제품이 기존 자동주사보다 나은 이유가 사라진다.
- 교체 시 커서를 첫 칸으로 되돌리고 소리와 화면으로 알린다.
- 되돌릴 수 없는 동작은 넣지 않는다(원칙 3). PDF의 '파일 열기'는 파일 선택 대화상자를 띄우는데 그 대화상자를 4칸으로 빠져나올 수 없어 넣지 않았다.
- 설정에서 통째로 끌 수 있다(원칙 1).
Hana Cloud 기본 프로필
| 앱 | 붙는 칸 | 보내는 키 |
|---|---|---|
| PDF 뷰어 (미리보기, Adobe Reader) | 다음 장 / 이전 장 | PageDown / PageUp |
| 음악 앱 (Apple Music, Spotify) | 재생·멈춤 / 다음 곡 / 이전 곡 | 미디어 키 |
미디어 키는 앱 전용 단축키가 아니라 시스템이 처리하므로, 어떤 음악 앱이 앞에 있든 같은 키로 동작한다.
비용
칸이 늘면 한 바퀴가 길어져 최악 대기가 3틱에서 6틱으로 는다. 다만 머무름 덕분에 같은 칸을 반복해 쓰는 값은 그대로다 — 다음 장을 한 번 누르면 그 칸에 머물러, 그 뒤로는 한 번 누를 때마다 한 장씩 넘어간다. 6틱은 처음 그 칸에 닿을 때 한 번 치르는 값이다.
한 바퀴가 길어지면 적응 로직의 '놓침' 판정도 덜 발생한다. 판정 기준은 고정 4칸이 아니라 현재 칸 수를 쓴다.
사용자가 키 조합을 직접 편집하는 화면은 만들지 않는다. 스위치 하나로 조작하기에 너무 복잡하다. 프리셋을 늘리는 것은 코드 변경으로 한다.
| 항목 | 기준 |
|---|---|
| 주입 지연 | 스위치 뗌 → 대상 앱 키 도달 100ms 이내 |
| 타이머 정확도 | 주사 간격 오차 ±30ms 이내 |
| 메모리 | floating 상주 시 200MB 이내 |
| 기동 | 앱 실행 → 컨트롤러 표시 3초 이내 |
| 상주 안정성 | 8시간 연속 사용에서 크래시·타이머 드리프트 없음 |
| 오프라인 | 네트워크 없이 모든 기능이 동작 |
| 대비 | 모든 텍스트·경계 WCAG AA 이상, 고대비 테마는 AAA 지향 |
| 자동 실행 | 로그인 시 자동 시작 옵션 (기본 켜짐) |
신청서에 제출한 기준을 그대로 따른다. 실측 없이 달성을 주장하지 않는다. 셈에서 무엇을 뺐는지도 함께 적는다 — 뺀 것을 밝히지 않은 숫자는 실측이 아니다.
| 검증 항목 | 측정 방법 | 목표 |
|---|---|---|
| 명령 선택 성공률 | 기본 4칸과 앱별 칸을 각 10회 반복 | 90% 이상 |
| 오선택 감소 | 고정 속도와 적응 속도 비교 | 30% 이상 감소 |
| 되돌리기 성공률 | 의도적 오선택 20회 후 복구 | 95% 이상 |
| 최초 설정 시간 | 신규 사용자 3단계 설정 과업 | 10분 이내 |
| 기기 전환 시간 | PC와 태블릿 상호 전환 | 1분 이내 |
| 보호자 개입 | 30분 종합 일상 과업 (앱 전환 제외, 5.4) | 0회 |
측정을 위해 코어는 사건을 JSON Lines로 남긴다. 한 줄이 사건 하나이고 줄마다 독립이라, 도중에 앱이 죽어도 그때까지 적힌 것은 그대로 읽을 수 있다.
- 위치: OS 로그 폴더의
events-YYYY-MM-DD.jsonl(날짜별 한 파일) - 모든 줄에
at(현지 시각)과ms(epoch 밀리초)가 붙는다 - 쓰기는 별도 스레드에서 한다. 커서 이동은 주사 간격마다 일어나므로, 그 자리에서 파일에 쓰면 간격 오차 예산(±30ms)을 넘길 수 있다
| 사건 | 남기는 값 | 무엇을 재는가 |
|---|---|---|
session |
phase, version |
실증 구간을 자른다 |
switch |
state, key |
스위치 연결 상태 |
cursor |
cursor, cell, mode, intervalMs |
커서가 어디에 얼마나 머물렀는지 |
input |
gesture, heldMs |
짧게/길게 판정과 실제 누름 시간 |
action |
cell, action, reactionMs, steps, ok, error |
명령 선택 성공률, 반응속도 |
pause |
paused |
정지 구간. 없으면 기록이 끊긴 자리와 구분되지 않는다 |
undo |
mapping, ok |
되돌리기 성공률 |
interval |
fromMs, toMs, reason |
오선택 감소(고정 속도 대비) |
preset |
preset, cells |
앱별 칸이 붙은 구간 |
action의 steps는 지난 선택 이후 커서가 지나온 자리 수다. 한 바퀴를 넘겼다면
원하는 칸을 지나쳐서 다시 기다린 것이므로, 오선택과 구분해서 세야 한다.
보는 방법 두 가지 — 현장에서는 bun run summary가 기록을 세션별 지표로
접어 주고, 대시보드(apps/admin의 '실증 기록')는 같은 계산을 화면으로 보여준다.
대시보드는 파일을 브라우저 안에서만 읽는다. 서버로 올리지 않는다 — 기록을
밖으로 내보내지 않는다는 약속에 대시보드라고 예외를 두지 않는다.
bun run summary가 기록을 세션별 지표로 접어 준다. 선택 실행
횟수, 되돌리기율, 놓침, 반응시간 분포, 간격 조정 이력, 정지 시간, 앱별 칸 구간을
낸다. 계산은 apps/admin/src/lib/metrics.ts에 있고 대시보드가 같은 함수를
쓴다 — 계산이 두 벌이 되면 화면의 숫자와 보고서의 숫자가 갈린다.
의도는 기록에 없다. 사용자가 무엇을 누르려 했는지는 코어가 알 수 없으므로, 성공률은 실증 진행자가 과업 대본과 대조해 센다. 기록은 그 대조의 근거다.
한계 — 강제 종료(SIGTERM 포함)에서는 session stop이 남지 않는다. 줄마다
즉시 흘려보내므로 잃는 줄은 없고, 세션 경계는 session start로 자른다.
로그는 이 기기 안에만 저장한다. 실증 참여자의 동의 없이 외부로 전송하지 않으며, 그래서 코어에 전송 코드를 두지 않는다. 설정에서 기록을 끌 수 있고, 저장 위치를 화면에 그대로 보여준다 — 어디 있는지 모르는 기록은 지울 수도, 진행자에게 건넬 수도 없다.
| 단계 | 내용 | 완료 판정 |
|---|---|---|
| M0 | 레포·문서·스캐폴딩 | bun run desktop으로 컨트롤러 창이 뜬다 ✅ |
| M1 | non-activating 창, 전역 스위치 입력 수신, Tab/Shift+Tab/Enter 주입 | 대상 앱에 포커스를 둔 채 스위치로 Tab·Enter가 주입된다 ✅ (macOS 검증 완료, Windows 미검증) |
| M2 | 스캔 상태기계(SCANNING·DWELLING·CONFIRM·PAUSED), 시각·청각 피드백 | 지나친 요소로 <를 눌러 되돌아갈 수 있다 ✅ (macOS 검증 완료) |
| M3 | 설정 화면, 프로필 저장, 적응 로직, 스위치 테스트 | 신규 사용자가 3단계로 설정을 마친다 ✅ (적응·수동고정·복원 실측 완료, 설정 UI 조작은 육안 확인 필요) |
| M4 | 실증 계측·로그, 앱별 확장 칸, 대시보드 | 정량 검증 표의 6개 항목을 실측한다 (기록·앱별 칸·대시보드 완료, 실측 남음) |
M1이 가장 위험도가 높다. 다른 마일스톤보다 먼저, 가장 작은 형태로 검증한다. 포커스를 뺏지 않는 창에서 키를 주입하는 것이 되지 않으면 나머지가 전부 무의미하다.
| 리스크 | 영향 | 대응 |
|---|---|---|
| floating 창이 포커스를 뺏는다 | 치명 | M1에서 최우선 검증. macOS/Windows 각각 확인 |
| macOS 접근성 권한을 사용자가 못 찾는다 | 높음 | 최초 실행 시 안내 화면과 설정 앱 딥링크 제공 |
| 하드웨어 인터페이스 가정이 틀리다 | 높음 | HID 키보드 가정을 조기에 하드웨어 팀과 확인 (7절) |
| 웹사이트의 Tab 순서가 엉망이다 | 중간 | 제품 범위 밖임을 인정하고, <로 복구 가능함을 강조 |
| 적응 로직이 오히려 예측을 어렵게 한다 | 중간 | 기본은 수동 고정 우선, 변경 이유 상시 표시, 언제든 끄기 |
| Windows 관리자 권한 앱에 주입이 막힌다 | 낮음 | 알려진 제약으로 문서화. 시연 환경에서 회피 |
- 스위치가 보내는 키를 무엇으로 할 것인가? (기본
F13제안, 하드웨어 팀 확인 필요) - 되돌리기 기본 매핑을 뒤로가기로 할 것인가,
Cmd/Ctrl+Z로 할 것인가? 실증에서 결정 - 확장 8칸 세트로의 전환을 사용자가 어떻게 하는가? (설정에서 세트 선택 vs
설정칸 길게)