libevent를 사용하여 그래픽 태블릿 에뮬레이션

libevent를 사용하여 그래픽 태블릿 에뮬레이션

스크립트에서 그래픽 태블릿(예: wacom의 그래픽 태블릿)을 시뮬레이션하고 싶습니다. 진행하는 "좋은" 방법은 uinput 위에 있는 추상화 계층인 libevent를 사용하는 것 같습니다. 그래서 Python 라이브러리를 사용하여 or와 같은 일부 이벤트를 보내는 스크립트를 작성하려고 합니다 EV_ABS.ABS_X. 불행하게도 Krita/Gimp/...를 사용하여 테스트할 때 그리는 선은 압력에 따라 모양이 바뀌지 않으며 이벤트도 표시되지 않습니다. 이유를 아시나요?EV_ABS.ABS_PRESSURElibeventxinput test-xi2pressure

감사해요!

재현 단계

다음 코드를 실행합니다.

sudo pip3 install libevdev
chmod +x ./simulate_graphics_tablet.py
sudo ./simulate_graphics_tablet.py

그러면 30초가 주어집니다:

  • xinput list그런 다음 실행하십시오 .xinput test-xi2 <number of Tablet alone>
  • 또는 Gimp를 열고 "편집/입력 장치"로 이동하여 "태블릿 전용" 장치를 "화면"으로 구성하고 팝업을 저장하고 닫은 다음 새 파일을 만들고(Ctrl-N) 확대/축소한 다음 "Tab"을 눌러 덮어씁니다. 대부분의 화면에 대한 그리기 표면. "p" 키를 사용하여 브러시로 전환하고 브러시가 로 설정되어 있는지 확인하세요 Pressure size.

내가 얻는 것은 다음과 같습니다: 균일한 선, xinput에서 압력에 대한 참조가 없습니다. 다음과 같은 것이 있습니다.

EVENT type 17 (RawMotion)
    device: 11 (11)
    detail: 0
    flags: 
    valuators:
          0: 29897.54 (29897.54)
          1: 29897.54 (29897.54)

내가 기대하는 것은 일정하지 않은 크기의 줄(스크립트가 선형적으로 압력을 증가시킴) 또는 xinput에서 일부 압력 관련 이벤트를 보는 것입니다.

#!/usr/bin/env python3
import sys
import libevdev
import time

def main(args):
    dev = libevdev.Device()
    dev.name = "Tablet alone"
    dev.enable(libevdev.EV_ABS.ABS_X,
               libevdev.InputAbsInfo(minimum=0, maximum=32767))
    dev.enable(libevdev.EV_ABS.ABS_Y,
               libevdev.InputAbsInfo(minimum=0, maximum=32767))
    dev.enable(libevdev.EV_ABS.ABS_Z,
               libevdev.InputAbsInfo(minimum=0, maximum=8191))
    # dev.enable(libevdev.EV_ABS.ABS_0B,
    #            libevdev.InputAbsInfo(minimum=0, maximum=8191))
    # dev.enable(libevdev.EV_ABS.ABS_DISTANCE,
    #            libevdev.InputAbsInfo(minimum=0, maximum=8191))
    dev.enable(libevdev.EV_ABS.ABS_PRESSURE,
               libevdev.InputAbsInfo(minimum=0, maximum=8191))
    dev.enable(libevdev.EV_MSC.MSC_SCAN)
    dev.enable(libevdev.EV_KEY.KEY_P)
    dev.enable(libevdev.EV_KEY.BTN_LEFT)
    dev.enable(libevdev.EV_KEY.BTN_RIGHT)
    dev.enable(libevdev.EV_KEY.BTN_MIDDLE)
    dev.enable(libevdev.EV_KEY.BTN_TOUCH)
    dev.enable(libevdev.EV_SYN.SYN_REPORT)
    dev.enable(libevdev.EV_SYN.SYN_CONFIG)
    dev.enable(libevdev.EV_SYN.SYN_MT_REPORT)
    dev.enable(libevdev.EV_SYN.SYN_DROPPED)
    dev.enable(libevdev.EV_SYN.SYN_04)
    dev.enable(libevdev.EV_SYN.SYN_05)
    dev.enable(libevdev.EV_SYN.SYN_06)
    dev.enable(libevdev.EV_SYN.SYN_07)
    dev.enable(libevdev.EV_SYN.SYN_08)
    dev.enable(libevdev.EV_SYN.SYN_09)
    dev.enable(libevdev.EV_SYN.SYN_0A)
    dev.enable(libevdev.EV_SYN.SYN_0B)
    dev.enable(libevdev.EV_SYN.SYN_0C)
    dev.enable(libevdev.EV_SYN.SYN_0D)
    dev.enable(libevdev.EV_SYN.SYN_0E)
    dev.enable(libevdev.EV_SYN.SYN_MAX)
    try:
        uinput = dev.create_uinput_device()
        print("New device at {} ({})".format(uinput.devnode, uinput.syspath))
        # Sleep for a bit so udev, libinput, Xorg, Wayland, ...
        # all have had a chance to see the device and initialize
        # it. Otherwise the event will be sent by the kernel but
        # nothing is ready to listen to the device yet.
        print("Waiting 30s to let you:")
        print("1) open Gimp")
        print("2) Go to 'Edit/Input device' and configure the device 'Tablet alone' to 'Screen'.")
        print("3) Save and close the pop up")
        print("4) Create a new file (Ctrl-N)")
        print("5) Zoom and press 'tab' to have a drawing surface coverint most of the screen.")
        print("6) Switch to brush using 'p' key.")
        time.sleep(30)

        pc = 0
        direc = +1
        already_pressed_one = False
        # uinput.send_events([
        #     libevdev.InputEvent(libevdev.EV_KEY.KEY_P, 1),
        #     libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0),
        # ])
        # time.sleep(0.1)
        # uinput.send_events([
        #     libevdev.InputEvent(libevdev.EV_KEY.KEY_P, 0),
        #     libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0),
        # ])
        for i in range(250):
            pc_ = pc/100
            val_x = int(pc_*10000 + (1-pc_)*17767)
            val_y = int(pc_*5000 + (1-pc_)*22767)
            val_pres = int(pc_*10 + (1-pc_)*6000)
            print("Will send: x={}, y={}, press={} (pc={})".format(
                val_x,
                val_y,
                val_pres,
                pc))
            uinput.send_events([
                libevdev.InputEvent(libevdev.EV_ABS.ABS_PRESSURE, val_pres),
                libevdev.InputEvent(libevdev.EV_ABS.ABS_X, val_y),
                libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, val_y),
                libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0),
            ])
            pc += direc
            if not already_pressed_one:
                print("Press!")
                uinput.send_events([
                    libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1),
                    libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0),
                ])                
                already_pressed_one = True
            if pc >= 100 or pc <=0 :
                print("Release click.")
                uinput.send_events([
                    libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0),
                    libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0),
                ])
                if pc >= 100:
                    pc = 100
                    direc = -1
                if pc <= 0:
                    pc = 0
                    direc = +1
                time.sleep(10)
                print("Press!")
                uinput.send_events([
                    libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1),
                    libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, 0),
                ])
                already_pressed_one = True
            time.sleep(0.1)

    except KeyboardInterrupt:
        pass
    except OSError as e:
        print(e)


if __name__ == "__main__":
    if len(sys.argv) > 2:
        print("Usage: {}")
        sys.exit(1)
    main(sys.argv)

편집: 설명대로 ABS_KEY.BTN_TOOL_PEN을 사용해 보았습니다.여기, 그러나 활성화하면 더 이상 감지되지 않는 이유를 모르겠습니다 xinput list.

dev.enable(libevdev.EV_KEY.BTN_TOOL_PEN)

답변1

일정 시간이 지난 후 그리고문서/암호읽고 또 읽은 끝에 마침내 해결책을 찾았습니다.

(이 답변의 끝 부분에 있는) 스크립트를 테스트하고 싶다면 루트로 실행하세요.

$ chmod +x completely_fake_tablet.py
$ sudo pip3 install libevdev
$ sudo ./completely_fake_tablet.py

그런 다음 김프를 열고 장치를 입력 장치로 추가하고 브러시 역학을 선택 Pencil Generic하고 즐기십시오. 더 자세한 내용을 알고 싶다면 다음을 읽어보세요.

전체적으로 시스템은 매우 까다로우므로 다음 사항을 확인해야 합니다.

  • libevdev.INPUT_PROP_DIRECT태블릿과 유사한 장치가 있다고 말할 수 있습니다(위의 문서 링크 참조).
  • 다음과 같은 태블릿과 유사한 모든 도구를 활성화할 수 있습니다.

    • libevdev.EV_KEY.BTN_TOOL_PEN이는 펜이 타블렛에 가까이 있을 때, libevdev.EV_KEY.BTN_TOUCH클릭할 때를 나타내는 데 사용됩니다.
    • libevdev.EV_KEY.BTN_STYLUS/는 libevdev.EV_KEY.BTN_STYLUS2펜의 버튼에 해당합니다.
    • libevdev.EV_ABS.ABS_{X,Y}위치의 경우(최소, 최대,그리고 해상도:해상도가 없으면 장치가 감지되지 않습니다! )
    • libevdev.EV_ABS.ABS_PRESSURE압력을 위해
    • libevdev.EV_SYN.SYN_REPORT이 정보는 정보 블록이 전송될 때마다 전송되어야 합니다. 이를 보내지 않으면 커널이 이벤트를 처리하지 않거나 매우 느린 속도(예: 1/s)로 처리합니다.

당신은 또한 확인하고 싶습니다장치를 활성화한 후 1초 이상 기다립니다.이벤트를 보내기 전에 그렇지 않으면 앞으로 태블릿이 인식되지 않습니다. 또한 xinput첫 번째 이벤트를 보낼 때까지 마우스의 펜 부분이 나열되지 않는다는 점도 확인했습니다 . 두 개의 장치가 나열 됩니다 xinput. 하나는 키보드인 것으로 추측되는 버튼용이고 다른 하나는 펜용입니다(이름 Tablet alone Pen (0)Tablet alone:

$ xinput list
⎡ Virtual core pointer                          id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ ETPS/2 Elantech Touchpad                  id=17   [slave  pointer  (2)]
⎜   ↳ lircd-uinput                              id=18   [slave  pointer  (2)]
⎜   ↳ Tablet alone Pen (0)                      id=12   [slave  pointer  (2)]
⎣ Virtual core keyboard                         id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
    ↳ Power Button                              id=6    [slave  keyboard (3)]
    ↳ Asus Wireless Radio Control               id=7    [slave  keyboard (3)]
    ↳ Video Bus                                 id=8    [slave  keyboard (3)]
    ↳ Video Bus                                 id=9    [slave  keyboard (3)]
    ↳ Sleep Button                              id=10   [slave  keyboard (3)]
    ↳ USB2.0 HD UVC WebCam: USB2.0 HD           id=14   [slave  keyboard (3)]
    ↳ Asus WMI hotkeys                          id=15   [slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard              id=16   [slave  keyboard (3)]
    ↳ lircd-uinput                              id=19   [slave  keyboard (3)]
    ↳ Tablet alone                              id=11   [slave  keyboard (3)]

테스트를 위해 Gimp를 사용하는 경우 소프트웨어를 열어야 합니다.뒤쪽에 xinput나열할 수 있어야 합니다. 그렇지 않으면 입력 장치에 나열되지 않으며 김프를 다시 시작해야 합니다(김프를 다시 시작하지 않고도 스크립트를 다시 시작할 수 있다는 점에 유의하세요). 또한 Edit/input device장치에서 설정하고 Tablet alone스크린 샷에 있는 것과 Screen유사한 동적 설정이 있는 브러시를 선택 해야 합니다. Pencil Generic스크립트가 제대로 작동하려면 를 눌러 Tab더 넓은 그리기 영역을 확보하고( Tab다시 일반 창으로 돌아가기) 모든 영역이 포함될 때까지 확대/축소해야 할 수도 있습니다.

여기에 이미지 설명을 입력하세요.

스크립트:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import libevdev
import time
## Some doc needed for this project
# http://www.infradead.org/~mchehab/kernel_docs_pdf/linux-input.pdf

## Some code to get inspiration from
# https://github.com/linuxwacom/input-wacom/blob/master/4.5/wacom_w8001.c

## Some doc to read at some point in my life:
# https://lwn.net/Kernel/LDD3/
# https://www.kernel.org/doc/html/v4.11/driver-api/index.html

def main(args):
    dev = libevdev.Device()
    dev.name = "Tablet alone"
    ### NB: all the following information needs to be enabled
    ### in order to recognize the device as a tablet.
    # Say that the device will send "absolute" values
    dev.enable(libevdev.INPUT_PROP_DIRECT)
    # Say that we are using the pen (not the erasor), and should be set to 1 when we are at proximity to the device.
    # See http://www.infradead.org/~mchehab/kernel_docs_pdf/linux-input.pdf page 9 (=13) and guidelines page 12 (=16), or the https://github.com/linuxwacom/input-wacom/blob/master/4.5/wacom_w8001.c (rdy=proximity)
    dev.enable(libevdev.EV_KEY.BTN_TOOL_PEN)
    dev.enable(libevdev.EV_KEY.BTN_TOOL_RUBBER)
    # Click
    dev.enable(libevdev.EV_KEY.BTN_TOUCH)
    # Press button 1 on pen
    dev.enable(libevdev.EV_KEY.BTN_STYLUS)
    # Press button 2 on pen, see great doc
    dev.enable(libevdev.EV_KEY.BTN_STYLUS2)
    # Send absolute X coordinate
    dev.enable(libevdev.EV_ABS.ABS_X,
               libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100))
    # Send absolute Y coordinate
    dev.enable(libevdev.EV_ABS.ABS_Y,
               libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100))
    # Send absolute pressure
    dev.enable(libevdev.EV_ABS.ABS_PRESSURE,
               libevdev.InputAbsInfo(minimum=0, maximum=8191))
    # Use to confirm that we finished to send the informations
    # (to be sent after every burst of information, otherwise
    # the kernel does not proceed the information)
    dev.enable(libevdev.EV_SYN.SYN_REPORT)
    # Report buffer overflow
    dev.enable(libevdev.EV_SYN.SYN_DROPPED)
    try:
        uinput = dev.create_uinput_device()
        print("New device at {} ({})".format(uinput.devnode, uinput.syspath))
        # Sleep for a bit so udev, libinput, Xorg, Wayland, ...
        # all have had a chance to see the device and initialize
        # it. Otherwise the event will be sent by the kernel but
        # nothing is ready to listen to the device yet. And it
        # will never be detected in the futur ;-)
        time.sleep(1) 
        # Reports that the PEN is close to the surface
        # Important to make sure xinput can detect (and list)
        # the pen. Otherwise, it won't write anything in gimp.
        uinput.send_events([
            libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
                                value=0),
            libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
                                value=1),
            libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
                                value=0),
        ])
        # Says that the pen it out of range of the tablet. Useful
        # to make sure you can move your mouse, and to avoid
        # strange things during the first draw.
        uinput.send_events([
            libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
                                value=0),
            libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
                                value=0),
            libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
                                value=0),
        ])
        print("Waiting 30s to let you:")
        print("1) open Gimp")
        print("2) Go to 'Edit/Input device' and configure the device 'Tablet alone' to 'Screen'.")
        print("3) Save and close the pop up")
        print("4) Create a new file (Ctrl-N)")
        print("5) Zoom and press 'tab' to have a drawing surface coverint most of the screen.")
        print("6) Switch to brush using 'p' key.")
        time.sleep(25)

        pc = 0
        direc = +1
        already_pressed_one = False
        for i in range(250):
            pc_ = pc/100
            val_x = int(pc_*10000 + (1-pc_)*17767)
            val_y = int(pc_*5000 + (1-pc_)*22767)
            val_pres = int(pc_*10 + (1-pc_)*6000)
            print("Will send: x={}, y={}, press={} (pc={})".format(
                val_x,
                val_y,
                val_pres,
                pc))
            uinput.send_events([
                libevdev.InputEvent(libevdev.EV_ABS.ABS_X,
                                    value=val_y),
                libevdev.InputEvent(libevdev.EV_ABS.ABS_Y,
                                    value=val_y),
                libevdev.InputEvent(libevdev.EV_ABS.ABS_PRESSURE,
                                    value=val_pres),
                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
                                    value=1),
                libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS,
                                    value=0),
                libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2,
                                    value=0),
                libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
                                    value=1),
                libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
                                    value=0),
            ])
            pc += direc
            if not already_pressed_one:
                print("Press!")
                uinput.send_events([
                    # Pen close to device
                    libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
                                        value=1),
                    libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
                                        value=1),
                    libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
                                        value=0),
                ])                
                already_pressed_one = True
            if pc >= 100 or pc <=0 :
                print("Release click.")
                uinput.send_events([
                    libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
                                        value=0),
                    # Pen outside of the position
                    libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
                                        value=0),
                    libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
                                        value=0),
                ])
                if pc >= 100:
                    pc = 100
                    direc = -1
                if pc <= 0:
                    pc = 0
                    direc = +1
                time.sleep(5)
                print("Press!")
                uinput.send_events([
                    # Pen close to device
                    libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN,
                                        value=1),
                    libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH,
                                        value=1),
                    libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT,
                                        value=0),
                ])
                already_pressed_one = True
            time.sleep(0.1)

    except KeyboardInterrupt:
        pass
    except OSError as e:
        print(e)


if __name__ == "__main__":
    if len(sys.argv) > 2:
        print("Usage: {}")
        sys.exit(1)
    main(sys.argv)

이제 압력 xinput test <id you get with xinput list>도 표시됩니다.

$ xinput test 12
motion a[0]=4151295 a[1]=4151295 a[2]=241 
motion a[0]=4060671 a[1]=4060671 a[2]=226 
motion a[0]=3969535 a[1]=3969535 a[2]=211 
motion a[0]=3878399 a[1]=3878399 a[2]=196 
motion a[0]=3787775 a[1]=3787775 a[2]=181 
motion a[0]=3696639 a[1]=3696639 a[2]=166 
motion a[0]=3605503 a[1]=3605503 a[2]=151 
motion a[0]=3514879 a[1]=3514879 a[2]=137 
motion a[0]=3423743 a[1]=3423743 a[2]=122 
motion a[0]=3332607 a[1]=3332607 a[2]=107 
motion a[0]=3241983 a[1]=3241983 a[2]=92 
motion a[0]=3150847 a[1]=3150847 a[2]=77 
motion a[0]=3059711 a[1]=3059711 a[2]=62 
motion a[0]=2969087 a[1]=2969087 a[2]=47 
motion a[0]=2877951 a[1]=2877951 a[2]=32 
motion a[0]=2650623 a[1]=2650623 a[2]=17 
button release 1

관련 정보