2022년 12월 17일 토요일

Linux 키보드 매핑 변경

알리에서 VR-PARK 라는 BT 콘트롤러를 샀다.
(그리 필요하진 않지만) 이북 리더에서 페이지 넘기는건 괜찮은것 같은데
그걸 제외하면 볼륨조절 이외에는 별로 쓸모가 없어 보인다.
그래서 PC 의 미디어 리모콘용으로 키를 변경해 봤다.

기본적으로는 디바이스별 키맵 변경을 참고 했다
테스트 환경 - ubuntu 22.04

* 우선 모드별 키 매핑은 다음과 같다. (기본은 모드 D)

모드 A
A  - 마우스 버튼 1
B  - 마우스 버튼 8
C  - 키코드 123 XF86AudioRaiseVolume
D  - 키코드 122 XF86AudioLowerVolume
상 - 여러 번의 마우스 커서 이동
하 - 여러 번의 마우스 커서 이동
좌 - 여러 번의 마우스 커서 이동
우 - 여러 번의 마우스 커서 이동
전면 위, 아래 - 각각 A, B 와 동일
모드 B
A  - 키코드172 XF86AudioPlay
B  - 키코드180 XF86HomePage
C  - 키코드123 XF86AudioRaiseVolume
D  - 키코드122 XF86AudioLowerVolume
상 - 키코드173 XF86AudioPrev
하 - 키코드171 XF86AudioNext
좌 - 키코드173 XF86AudioPrev
우 - 키코드171 XF86AudioNext
전면 위, 아래 - 각각 A, B 와 동일
모드 C
A  - 마우스 버튼 41
B  - 마우스 버튼 37
C  - 마우스 버튼 40
D  - 마우스 버튼 38
상 - 없음
하 - 없음
좌 - 없음
우 - 없음
전면 위, 아래 - 각각 B, A 와 동일
모드 D
A  - 마우스 버튼 1
B  - 마우스 버튼 8
C  - 키코드 123 XF86AudioRaiseVolume
D  - 키코드 122 XF86AudioLowerVolume
상 - 마우스 커서
하 - 마우스 커서
좌 - 마우스 커서
우 - 마우스 커서
전면 위, 아래 - 각각 A, B 와 동일매

* 키 매핑 조사

xev 를 띄우고, 창에 마우스 커서를 놓고, 버튼 및 조이스틱을 동작 시켜 터미널 출력을 확인

일부 버튼(볼륨)은 키코드를 얻을 수 없고 KeymapNotify 등이 발생했는데 xev 로 Fn+F1-F12 키코드 조사에 따른면 다른 키 바인딩 앱이 동작 중이라 그렇다고 한다.
xlsclients 를 실행하니 gsd-media-keys 라는 이름이 보였다.
killall gsd-media-keys 로 삭제 후 모든 매핑을 조사할 수 있었다.

변경할 키의  심볼은 xkbcomp.dump 에서 또는 다음 명령어로 확인한다.

xkbcomp $DISPLAY -

최종적으로는 키보드로만 구성된 모드 B 에서 다음과 같이 동작하게 하기로 했다. 
$ cat vrpark.sh
#! /bin/bash

XKB_ROOT="/tmp/$USER/xkb"

# 매핑 생성
# @B 모드
# 위/왼쪽     - 소리 크게
# 아래/오른쪽 - 소리 작게
# C           - 재생/일시정지
# D           - 전체화면 (f)
# A           - 오른쪽
# B           - 왼쪽
mkdir -p $XKB_ROOT/symbols
cat << EOF > $XKB_ROOT/symbols/custom
xkb_symbols "media" {
  key <I171> { [ XF86AudioLowerVolume ] };
  key <I172> { [ Right ] };
  key <I173> { [ XF86AudioRaiseVolume ] };
  key <I180> { [ Left ] };
  key <VOL+> { [ XF86AudioPlay, XF86AudioPause ] };
  key <VOL-> { type="ALPHABETIC", symbols[Group1]=[ f, F ] };
};
EOF

kbd_id=$(xinput list | sed -n 's/.*VR-PARK.*id=\([0-9]*\).*keyboard.*/\1/p')
[ "$kbd_id" ] &&
  setxkbmap -device $kbd_id -print |
  sed 's/\(xkb_symbols.*\)"/\1+custom(youtube)"/' |
  xkbcomp -I$XKB_ROOT -i $kbd_id -synch - $DISPLAY 2>/dev/null

연결되면 기본으로 모드 D 가 되어 모드 B 로 변경해야 하지만, 꽤 만족스럽다.


다만 몇 가지 문제는
1. 유튜브의 경우 오른쪽 왼쪽을 누르면 5초씩 이동하는데 VLC player의 경우 10초씩 이동한다.

2. 미디어 콘트롤러로는 키가 부족하다. 최소 이전파일/다음파일의 두 개가 더 필요하다.

3. 유튜브 중간 중간 나오는 광고 건너뛰기는 처리가 안된다. 배너 광고 삭제도 안된다.
모드D 로 바꿔서 할 수 있기는 있는데 정확히 맞추기가 너무 힘들다

4. 디바이스가 연결될 때마다 이 스크립트를 실행해야 한다. (절전을 위해 자주 꺼진다)


1 에 대해서는 VLC player 의 설정을 변경한다.
도구 > 환경설정 > (왼쪽 아래의) 설정보기에서 전체 선택 > 인터페이스 > 단축키 설정 > 이동 시간: 짧게 에서 5로 변경 > 저장 

2 는 디바이스 쪽에서 제어가 되어야 할 것 같다. 전면 버튼 두 개와 조이스틱의 방향 2개가 남지만 보내는 곳에서 같은 값으로 보내면 받는 곳에서는 할 수 있는게 없다.

3 은 이 방법에 익숙해 지거나, 마우스를 사용하거나, 디바이스 쪽에서 제어가 되어야 할 것 같다.

4 는 udev 를 설정해서 해결하기로 한다. 

* udev 설정
기본적으론 Linux에서 장치 검색 및 관리를 위해 Udev 사용 방법 에 따라 기본 룰을 작성하고 테스트를 했다.

udevadm 을 실행하고 디바이스를 연결하여 표시되는 메시지를 확인한다.
$ udevadm monitor --udev
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
UDEV  [9025.790074] add      /devices/pci0000:00/0000:00:14.0/usb1/1-10/1-10:1.0/bluetooth/hci0/hci0:3585 (bluetooth)
UDEV  [9025.800014] add      /devices/virtual/misc/uhid/0005:05AC:022C.0014 (hid)
UDEV  [9025.806226] add      /devices/virtual/misc/uhid/0005:05AC:022C.0014/input/input40 (input)
UDEV  [9025.810143] add      /devices/virtual/misc/uhid/0005:05AC:022C.0014/hidraw/hidraw3 (hidraw)
UDEV  [9025.811948] add      /devices/virtual/misc/uhid/0005:05AC:022C.0014/input/input40/mouse3 (input)
UDEV  [9025.814807] add      /devices/virtual/misc/uhid/0005:05AC:022C.0014/input/input40/js0 (input)
UDEV  [9025.926914] add      /devices/virtual/misc/uhid/0005:05AC:022C.0014/input/input40/event22 (input)
UDEV  [9026.992700] bind     /devices/virtual/misc/uhid/0005:05AC:022C.0014 (hid)
$ udevadm monitor --environment --udev
. . .
UDEV  [8397.519158] add      /devices/virtual/misc/uhid/0005:05AC:022C.0012/input/input38/event22 (input)
ACTION=add
DEVPATH=/devices/virtual/misc/uhid/0005:05AC:022C.0012/input/input38/event22
SUBSYSTEM=input
DEVNAME=/dev/input/event22
. . .
$ udevadm info -a /dev/input/event22

몇 가지 테스트를 해보니

1. udevadm info -a <디바이스> 의 결과에 나오는 ATTR 또는 ATTRS 등을 그대로 써야한다.
예로 많은 문서에서 vender id 를 참조할 때 ATTRS{idVendor} 를 사용하지만 이 디바이스는 ATTRS{id/vendor} 를 사용해야 동작했다.

2. 스크립트를 한 번만 실행되게 하려면 룰이 정확히 한 번만 일치되게 작성해야 한다.
예로 SUBSYSTEM=="input" ACTION="add" ATTRS{id/vendor}=="05ac" ATTRS{id/product}=="022c" 는 mouse3, js0, event22 에 모두 일치해서 세번 실행된다.

 간단히 하기 위해 한 번만 발생하는 bind 이벤트를 사용하기로 했다.

$ cat /etc/udev/rules.d/80-vrpark.rules 
ACTION=="bind", SUBSYSTEM=="hid", ENV{HID_NAME}=="VR-PARK", RUN+="/tmp/vrpark.sh"

* 합체 후 문제

위 룰로 vrpark.sh 가 시작하기는 하는데 매핑 변경은 이루어지지 않았다.

udev 가 스크립트의 xinput 을 실행하지 않음 에 따르면 xinput 이 동작하려면 DISPLAY와 XAUTHORITY 환경변수가 필요하다고 한다.

파일 하나로 관리하고 싶은 마음도 있고 해서 최종 파일은 다음과 같다.

vrpark.sh


참고
* xkb 로 리눅스 키보드 해킹
디바이스별 키맵 변경
xev 로 Fn+F1-F12 키코드 조사
xkbcomp.dump
Linux에서 장치 검색 및 관리를 위해 Udev 사용 방법