圖形平板 VEIKK:Linux 上的壓力敏感度
我剛買了圖形平板電腦 VEIKK A30。滑鼠直接被辨識。不幸的是,我找不到如何在 Linux 上啟用壓力敏感度。我看到一個人報告說,通過一些調整可以使其工作,但找不到任何參考。
編輯:這是 dmesg 的輸出
[mars19 01:15] usb 2-1: new full-speed USB device number 10 using xhci_hcd [ +0,153026] usb 2-1: New USB device found, idVendor=2feb, idProduct=0002, bcdDevice= 0.00 [ +0,000006] usb 2-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [ +0,000004] usb 2-1: Product: A30 [ +0,000003] usb 2-1: Manufacturer: VEIKK.INC [ +0,000004] usb 2-1: SerialNumber: 0000001 [ +0,003052] input: VEIKK.INC A30 Mouse as /devices/pci0000:00/0000:00:14.0/usb2/2-1/2-1:1.0/0003:2FEB:0002.000C/input/input64 [ +0,064250] input: VEIKK.INC A30 as /devices/pci0000:00/0000:00:14.0/usb2/2-1/2-1:1.0/0003:2FEB:0002.000C/input/input65 [ +0,000429] hid-generic 0003:2FEB:0002.000C: input,hidraw0: USB HID v1.00 Mouse [VEIKK.INC A30] on usb-0000:00:14.0-1/input0 [ +0,001079] input: VEIKK.INC A30 as /devices/pci0000:00/0000:00:14.0/usb2/2-1/2-1:1.1/0003:2FEB:0002.000D/input/input66 [ +0,062599] hid-generic 0003:2FEB:0002.000D: input,hidraw1: USB HID v1.00 Keyboard [VEIKK.INC A30] on usb-0000:00:14.0-1/input1 [ +0,001208] hid-generic 0003:2FEB:0002.000E: hiddev0,hidraw2: USB HID v1.00 Device [VEIKK.INC A30] on usb-0000:00:14.0-1/input2
Edit2:嗯,似乎可以讀取發送壓力的設備,
sudo cat /dev/hidraw0
因為模式因我施加的壓力而異。我不知道如何閱讀這個二進製文件^^如果可以,也許我可以使用uinput將其映射到新設備?^^ 請注意,/dev/input
包含mouseX、eventY 和mouse1 之類的文件與數位板相關,但與壓力無關,並且此文件夾中唯一顯示與數位板相關的大量資訊的文件是文件/dev/input/by-id/usb-VEIKK.INC_A30_0000001-event-mouse
。但是模式是否與壓力匹配不太清楚,這裡發送的資訊太多。如果您知道如何解析它們,請告訴我!Edit3:所以我還沒有驅動程序,但至少從設備原始通信中讀取輸入似乎很容易。我製作了這個 python 腳本作為概念證明:
#!/usr/bin/env python3 import struct PRINT_TIMESTAMP = True # Open the file in the read-binary mode f = open("/dev/input/by-id/usb-VEIKK.INC_A30_0000001-event-mouse", "rb" ) while 1: data = f.read(24) # print struct.unpack('4IHHI',data) ###### FORMAT = ( Time Stamp_INT , 0 , Time Stamp_DEC , 0 , ###### type , code ( key pressed ) , value (press/release) ) time_int, _, time_dec, _, ev_type, ev_code, ev_val = struct.unpack('4IHHI',data) t = (ev_type, ev_code) if ((t == (0,0) and ev_val == 0) or (t == (4, 4) and ev_val >= 589825 and ev_val <= 589827)): # Redundant as it's for normal/bottom/top clicks # (same code for press/release), or just garbage 0,0,0 continue if PRINT_TIMESTAMP: print("[{:.2f}] ".format(time_int + time_dec/1e6), end="", flush=True) if t == (3,0): print("Pos x: {} ({:.2f}%)".format(ev_val, 100*ev_val/32767), flush=True) elif t == (3,1): print("Pos y: {} ({:.2f}%)".format(ev_val, 100*ev_val/32767), flush=True) elif t == (3,24): print("Pression: {} ({:.2f}%)".format(ev_val, 100*ev_val/8191), flush=True) elif t == (1,272): print("Normal click ({})".format("press" if ev_val else "release"), flush=True) elif t == (1,273): print("click button 2 (bottom) ({})".format("press" if ev_val else "release"), flush=True) elif t == (1,274): print("click button 3 (top) ({})".format("press" if ev_val else "release"), flush=True) else: print("Unknow: type={}, code={}, value={}".format(ev_type, ev_code, ev_val), flush=True)
展示:
[1553182025.55] Pos y: 11458 (34.97%) [1553182025.55] Pos x: 14310 (43.67%) [1553182025.56] Pos x: 14314 (43.68%) [1553182025.56] Pos x: 14318 (43.70%) [1553182025.57] Pos x: 14321 (43.71%) [1553182025.57] Normal click (press) [1553182025.57] Pos x: 14323 (43.71%) [1553182025.57] Pression: 1122 (13.70%) [1553182025.57] Pos x: 14326 (43.72%) [1553182025.57] Pos y: 11466 (34.99%) [1553182025.57] Pression: 1260 (15.38%) [1553182025.58] Pos x: 14329 (43.73%) [1553182025.58] Pression: 1337 (16.32%) [1553182025.58] Pos x: 14330 (43.73%) [1553182025.58] Pos y: 11494 (35.08%) [1553182025.58] Pression: 1515 (18.50%) [1553182025.59] Pos y: 11506 (35.11%) [1553182025.59] Pression: 1687 (20.60%) [1553182025.59] Pos y: 11517 (35.15%) [1553182025.59] Pression: 1689 (20.62%) [1553182025.59] Pos y: 11529 (35.18%) [1553182025.59] Pression: 1789 (21.84%) [1553182025.60] Pos y: 11536 (35.21%) [1553182025.60] Pression: 1829 (22.33%) [1553182025.60] Pos y: 11542 (35.22%) [1553182025.60] Pression: 1907 (23.28%) [1553182025.61] Pression: 2031 (24.80%) [1553182025.61] Pos y: 11549 (35.25%) [1553182025.61] Pression: 2140 (26.13%)
編輯 4:驚人的頁面:https ://digimend.github.io/support/howto/trbl/locating_failure/然而,一切都在這里工作……除了我想測試它的最後一步。我嘗試使用 MyPaint 進行測試,但它沒有檢測到壓力。
我還嘗試編寫自己的程式碼,基本上將事件文件中的輸入複製到這樣的新設備中:
#!/usr/bin/env python3 import sys import libevdev import time def print_capabilities(l): v = l.driver_version print("Input driver version is {}.{}.{}".format(v >> 16, (v >> 8) & 0xff, v & 0xff)) id = l.id print("Input device ID: bus {:#x} vendor {:#x} product {:#x} version {:#x}".format( id["bustype"], id["vendor"], id["product"], id["version"], )) print("Input device name: {}".format(l.name)) print("Supported events:") for t, cs in l.evbits.items(): print(" Event type {} ({})".format(t.value, t.name)) for c in cs: if t in [libevdev.EV_LED, libevdev.EV_SND, libevdev.EV_SW]: v = l.value[c] print(" Event code {} ({}) state {}".format(c.value, c.name, v)) else: print(" Event code {} ({})".format(c.value, c.name)) if t == libevdev.EV_ABS: a = l.absinfo[c] print(" {:10s} {:6d}".format('Value', a.value)) print(" {:10s} {:6d}".format('Minimum', a.minimum)) print(" {:10s} {:6d}".format('Maximum', a.maximum)) print(" {:10s} {:6d}".format('Fuzz', a.fuzz)) print(" {:10s} {:6d}".format('Flat', a.flat)) print(" {:10s} {:6d}".format('Resolution', a.resolution)) print("Properties:") for p in l.properties: print(" Property type {} ({})".format(p.value, p.name)) def print_event(e): print("Event: time {}.{:06d}, ".format(e.sec, e.usec), end='') if e.matches(libevdev.EV_SYN): if e.matches(libevdev.EV_SYN.SYN_MT_REPORT): print("++++++++++++++ {} ++++++++++++".format(e.code.name)) elif e.matches(libevdev.EV_SYN.SYN_DROPPED): print(">>>>>>>>>>>>>> {} >>>>>>>>>>>>".format(e.code.name)) else: print("-------------- {} ------------".format(e.code.name)) else: print("type {:02x} {} code {:03x} {:20s} value {:4d}".format(e.type.value, e.type.name, e.code.value, e.code.name, e.value)) def main(args): path = args[1] dev = libevdev.Device() dev.name = "Combined Both Devices" 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.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. time.sleep(1) with open(path, "rb") as fd: l = libevdev.Device(fd) print_capabilities(l) print("################################\n" "# Waiting for events #\n" "################################") while True: try: ev = l.events() for e in ev: uinput.send_events([e]) print_event(e) if e.matches(libevdev.EV_ABS.ABS_PRESSURE): print("Pressure! Will send another packeton Z axis!") uinput.send_events([libevdev.InputEvent(libevdev.EV_ABS.ABS_Z, e.value)]) uinput.send_events([libevdev.InputEvent(libevdev.EV_ABS.ABS_0B, e.value)]) uinput.send_events([libevdev.InputEvent(libevdev.EV_ABS.ABS_DISTANCE, e.value)]) except libevdev.EventsDroppedException: for e in l.sync(): print_event(e) uinput.send_events([e]) except KeyboardInterrupt: pass except IOError as e: import errno if e.errno == errno.EACCES: print("Insufficient permissions to access {}".format(path)) elif e.errno == errno.ENOENT: print("Device {} does not exist".format(path)) else: raise e except OSError as e: print(e) if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: {} /dev/input/eventX".format(sys.argv[0])) sys.exit(1) main(sys.argv)
我意識到它或多或少與 Veikk 滑鼠相同,而且效果並不好。
優優!!!我設法製作了一個 python 腳本,讓我在新的虛擬設備上恢復了壓力 :-D 現在我可以將它與 Gimp/Krita/ 一起使用…剩下要做的唯一部分是編寫一個合適的 C 驅動程序並載入它直接在核心中…如果您有任何想法,請告訴我!
證明:
所以腳本的想法是從圖形輸入板的事件文件中讀取輸入,然後創建好的事件(沒有更多
BTN_LEFT
,而是……有關更多詳細資訊,請參閱我的其他問題BTN_TOUCH
/答案)。要執行腳本(見下文),請將其保存在 下
combine_both.py
,並使其可執行/安裝 deps:$ chmod +x combine_both.py $ sudo pip3 install libevdev
然後,檢查可用的輸入設備:
$ xinput list
這應該給你幾個條目,其中一個條目像
VEIKK.INC A30 Mouse id=12 [slave pointer (2)]
注意 id(
<id veikk>
後面會提到),以及虛擬核心指針的 id<id core>
:Virtual core pointer id=2 [master pointer (3)]
然後,您需要知道
/dev/input/eventX
要選擇哪個文件,最簡單的方法是執行sudo evtest
並讀取與VEIKK.INC A30 Mouse
. 然後在參數中使用此文件執行腳本,如:sudo ./combine_both.py /dev/input/event7
當您嘗試在設備上點擊/移動時,此腳本應輸出內容。此外
xinput list
,還應該列出這個設備的名稱Tablet alone Pen (0)
和 id<id fake tablet>
,並且xinput test <id fake tablet>
應該給你類似的東西(注意 x/y/pressure 的 3 列):$ xinput test 21 [...] 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
現在,要使其與 Gimp/Krita 一起使用,您需要禁用真正的滑鼠(否則您會在真假平板電腦之間發生衝突),使用
xinput float <id veikk>
完成後,您可以重新連接真實設備
xinput reattach <id veikk> <id core>
在 gimp 中,不要忘記
Tablet alone Pen (0)
使用mode=screen
in進行設置Edit/input devices
,並確保VEIKK.INC A30 Mouse
禁用了真正的平板電腦。最後,選擇了一個好的動態,喜歡Pencil Generic
測試!享受!(如果我在某些時候編寫 C 驅動程序,我會告訴你)
腳本:
#!/usr/bin/env python3 import sys import libevdev import datetime import time def print_capabilities(l): v = l.driver_version print("Input driver version is {}.{}.{}".format(v >> 16, (v >> 8) & 0xff, v & 0xff)) id = l.id print("Input device ID: bus {:#x} vendor {:#x} product {:#x} version {:#x}".format( id["bustype"], id["vendor"], id["product"], id["version"], )) print("Input device name: {}".format(l.name)) print("Supported events:") for t, cs in l.evbits.items(): print(" Event type {} ({})".format(t.value, t.name)) for c in cs: if t in [libevdev.EV_LED, libevdev.EV_SND, libevdev.EV_SW]: v = l.value[c] print(" Event code {} ({}) state {}".format(c.value, c.name, v)) else: print(" Event code {} ({})".format(c.value, c.name)) if t == libevdev.EV_ABS: a = l.absinfo[c] print(" {:10s} {:6d}".format('Value', a.value)) print(" {:10s} {:6d}".format('Minimum', a.minimum)) print(" {:10s} {:6d}".format('Maximum', a.maximum)) print(" {:10s} {:6d}".format('Fuzz', a.fuzz)) print(" {:10s} {:6d}".format('Flat', a.flat)) print(" {:10s} {:6d}".format('Resolution', a.resolution)) print("Properties:") for p in l.properties: print(" Property type {} ({})".format(p.value, p.name)) def print_event(e): print("Event: time {}.{:06d}, ".format(e.sec, e.usec), end='') if e.matches(libevdev.EV_SYN): if e.matches(libevdev.EV_SYN.SYN_MT_REPORT): print("++++++++++++++ {} ++++++++++++".format(e.code.name)) elif e.matches(libevdev.EV_SYN.SYN_DROPPED): print(">>>>>>>>>>>>>> {} >>>>>>>>>>>>".format(e.code.name)) else: print("-------------- {} ------------".format(e.code.name)) else: print("type {:02x} {} code {:03x} {:20s} value {:4d}".format(e.type.value, e.type.name, e.code.value, e.code.name, e.value)) class Tablet(): def __init__(self, tablet_name): self.tablet_name = tablet_name def __enter__(self): self.dev = libevdev.Device() self.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 self.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) self.dev.enable(libevdev.EV_KEY.BTN_TOOL_PEN) self.dev.enable(libevdev.EV_KEY.BTN_TOOL_RUBBER) # Click self.dev.enable(libevdev.EV_KEY.BTN_TOUCH) # Press button 1 on pen self.dev.enable(libevdev.EV_KEY.BTN_STYLUS) # Press button 2 on pen, see great doc self.dev.enable(libevdev.EV_KEY.BTN_STYLUS2) # Send absolute X coordinate self.dev.enable(libevdev.EV_ABS.ABS_X, libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100)) # Send absolute Y coordinate self.dev.enable(libevdev.EV_ABS.ABS_Y, libevdev.InputAbsInfo(minimum=0, maximum=32767, resolution=100)) # Send absolute pressure self.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) self.dev.enable(libevdev.EV_SYN.SYN_REPORT) # Report buffer overflow self.dev.enable(libevdev.EV_SYN.SYN_DROPPED) self.uinput = self.dev.create_uinput_device() print("New device at {} ({})".format(self.uinput.devnode, self.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) # self.simulate_first_click() self.reset_state() return self def __exit__(self, type, value, traceback): pass def reset_state(self): self.is_away = True self.is_touching = False self.pressed_button_1 = False self.pressed_button_2 = False self.lastmodif = datetime.datetime.now() def send_events(self, events, is_away=False): self.lastmodif = datetime.datetime.now() self.is_away = is_away self.uinput.send_events(events) def simulate_first_click(self): """Useful only the first time to make sure xinput detected the input""" # 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. self.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. self.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), ]) def send_state_no_pos(self, is_away=False): self.lastmodif = datetime.datetime.now() self.is_away = is_away print("Away: {}, Touching: {}".format(self.is_away, self.is_touching)) self.uinput.send_events([ libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, value=1 if self.is_touching else 0), libevdev.InputEvent(libevdev.EV_KEY.BTN_TOOL_PEN, value=1 if not self.is_away else 0), libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS, value=1 if self.pressed_button_1 else 0), libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2, value=1 if self.pressed_button_2 else 0), libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, value=0), ]) def touch_press(self): self.is_touching = True self.send_state_no_pos() def touch_release(self): self.is_touching = False self.send_state_no_pos() def button_1_press(self): self.pressed_button_1 = True self.send_state_no_pos() def button_1_release(self): self.pressed_button_1 = False self.send_state_no_pos() def button_2_press(self): self.pressed_button_2 = True self.send_state_no_pos() def button_2_release(self): self.pressed_button_2 = False self.send_state_no_pos() def move_x(self, abs_x): self.send_events([ libevdev.InputEvent(libevdev.EV_ABS.ABS_X, value=abs_x), libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, value=0), ]) def move_y(self, abs_y): self.send_events([ libevdev.InputEvent(libevdev.EV_ABS.ABS_Y, value=abs_y), libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, value=0), ]) def change_pressure(self, pressure): self.send_events([ libevdev.InputEvent(libevdev.EV_ABS.ABS_PRESSURE, value=pressure), libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT, value=0), ]) def handle_event(self, e): if e.matches(libevdev.EV_ABS.ABS_PRESSURE): self.change_pressure(e.value) elif e.matches(libevdev.EV_ABS.ABS_X): self.move_x(e.value) elif e.matches(libevdev.EV_ABS.ABS_Y): self.move_y(e.value) elif e.matches(libevdev.EV_KEY.BTN_LEFT): if e.value == 1: self.touch_press() else: self.touch_release() elif e.matches(libevdev.EV_SYN.SYN_REPORT): pass else: print("Unkown event:") print_event(e) def main(args): path = args[1] try: with Tablet("Tablet alone") as tablet: ### Read the events from real graphics tablet with open(path, "rb") as fd: l = libevdev.Device(fd) print_capabilities(l) print("################################\n" "# Waiting for events #\n" "################################") while True: try: ev = l.events() for e in ev: print_event(e) tablet.handle_event(e) except libevdev.EventsDroppedException: for e in l.sync(): print_event(e) tablet.handle_event(e) except KeyboardInterrupt: pass except IOError as e: import errno if e.errno == errno.EACCES: print("Insufficient permissions to access {}".format(path)) elif e.errno == errno.ENOENT: print("Device {} does not exist".format(path)) else: raise e except OSError as e: print(e) if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: sudo {} /dev/input/eventX".format(sys.argv[0])) print(" $ sudo evtest") print("can help you to know which file to use.") sys.exit(1) main(sys.argv)