Как сделать так, чтобы это & ​​ldquo; отключало сенсорную панель при вводе & rdquo; скрипт Python включить тачпад быстрее?

Короче говоря: у меня есть клавиатура с сенсорной панелью, которая распознается как «указатель» в xinput и имеет возможности «указателя клавиатуры» в libinput (в отличие от распознавания в качестве сенсорной панели). Свойство libinput «Disable-w-typing» не является avaliabe (оно имеет значение «n / a» в качестве значения в «списках устройств libinput»). Кроме того, Ubuntu не распознает его как сенсорную панель, поэтому я не могу использовать встроенное решение Ubuntu для отключения сенсорной панели во время набора текста.

Читая множество связанных вопросов здесь и в других местах, мне удалось адаптировать этот скрипт Python к моей проблеме. Вот моя версия:

import os
import time 
import subprocess
import threading

def main():
    touch = os.popen("xinput list --id-only 'pointer:SINO WEALTH USB KEYBOARD'").read()[:-1]
    keyboard = os.popen("xinput list --id-only 'keyboard:SINO WEALTH USB KEYBOARD'").read()[:-1]
    subprocess.call('xinput set-prop '+touch+' 142 1', shell=True)
    p = subprocess.Popen('xinput test '+keyboard, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    clickTime = [0, 0]
    def checkTime():
        keys = [37, 50, 62, 64, 105, 108, 133]
        while True:
            out = p.stdout.readline()
            if len(out) < 1:
                break
            key = int(out.split()[-1])
            if key not in keys:
                clickTime[0] = time.time()

    t = threading.Thread(target=checkTime)
    t.start()

    lastTime = 0
    touchpad = True
    while True:
        inactive = time.time() - clickTime[0]
        # print ('inactive for', inactive)
        if inactive > 1:            
            if not touchpad:
                print ('Enable touchpad')
                subprocess.call('xinput set-prop '+touch+' 142 1', shell=True)
            touchpad = True
        else:
            if touchpad:
                print ('Disable touchpad')
                subprocess.call('xinput set-prop '+touch+' 142 0', shell=True)
            touchpad = False
        time.sleep(0.5)

    retval = p.wait()

if __name__ == '__main__':
    main()

Сценарий работает просто отлично. Как только я начинаю печатать, тачпад отключается. Единственная проблема заключается в том, что для включения сенсорной панели требуется около 1 с, что довольно долго, и я не нашел способа уменьшить эту задержку. Установка «time.sleep (0.5)» на меньшее число кажется очевидным выбором, но, например, установка 0.05, кажется, только делает скрипт более требовательным к процессору, но не вносит видимых изменений в задержку между остановками. печатать и тачпад снова активируется.

Моя цель состоит в том, чтобы отключить сенсорную панель во время набора текста и вернуть ее обратно примерно через 300 мс после того, как я перестал печатать.

Мне не нужно обязательно решать эту проблему, используя python, но это единственный способ, которым я смог решить эту проблему в первую очередь. В качестве ответов я могу принять предложения по изменению этого самого скрипта Python, или, может быть, руководство о том, как решить эту проблему с помощью скрипта bash, или действительно любую идею, которая поможет мне решить эту проблему (приветствуется нестандартное мышление).

Запуск Ubuntu 19.04.

0
задан 5 June 2019 в 02:05

2 ответа

Править:

Это - вероятно, надлежащий ответ на Ваш вопрос. Мне удалось заставить его работать независимый от ключевых повторных настроек, и это более ориентировано на многопотоковое исполнение, чем предыдущая версия. Я оставлю другой ответ и сделаю его чистым evdev решением для людей, использующих Уэйленд (когда я получу время).

import time 
import subprocess
import threading
from queue import Queue, Empty

ENABLE = True # only for readability
DISABLE = False

TOUCHPAD_NAME = 'pointer:SINO WEALTH USB KEYBOARD'
KEYBOARD_NAME = 'keyboard:SINO WEALTH USB KEYBOARD'
DELAY = 0.3

def setDeviceEnabled(id, enabled):
    subprocess.call(['xinput', 'set-prop', str(id), 'Device Enabled', '1' if enabled else '0'])
    print('enabled' if enabled else 'disabled')

def main():
    touchpadId = subprocess.check_output(['xinput', 'list' , '--id-only', TOUCHPAD_NAME]).decode('UTF-8').strip()
    keyboardId = subprocess.check_output(['xinput', 'list' , '--id-only', KEYBOARD_NAME]).decode('UTF-8').strip()
    p = subprocess.Popen('xinput test ' + str(keyboardId), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    # we queue events to make sure the main thread doesn't miss consecutive events that happen too fast.
    eventQueue = Queue()

    def eventProducer():
        keysPressed = 0 # the number of keys currently pressed. We only need to enable/disable the touchpad if this transitions to/from 0, respectively.
        ignoreKeyCodes = [37, 50, 62, 64, 105, 108, 133]
        while True:
            out = p.stdout.readline()
            if len(out) < 1:
                break
            event = out.split()
            if not event[0] == b'key': # only react to key events. Enabling a real touchpad results in a "what's this" event on all input devices
                continue
            keyCode = int(event[2])
            if keyCode not in ignoreKeyCodes:
                if event[1] == b'press':
                    keysPressed += 1
                    if keysPressed == 1: # transition from 0 to 1 keys, disable touchpad
                        eventQueue.put([DISABLE])
                else:
                    keysPressed -= 1
                if keysPressed < 1: # transition from 1 to 0 keys, enable touchpad
                    keysPressed = 0 # in case we missed a press (e.g. a key was already pressed on startup), make sure this doesn't become negative
                    eventQueue.put([ENABLE, time.time()])

    t = threading.Thread(target=eventProducer)
    t.start()

    touchpadEnabled = True
    latestEvent = eventQueue.get()
    try:
        while True:
            if latestEvent[0] == DISABLE:
                if touchpadEnabled:
                    setDeviceEnabled(touchpadId, False)
                    touchpadEnabled = False
                latestEvent = eventQueue.get()
            else:
                timeToEnable = latestEvent[1] + DELAY - time.time()
                try:
                    latestEvent = eventQueue.get(timeout = timeToEnable) # Since the last event was ENABLE, the next event must be DISABLE. If it doesn't arrive until the timeout, we can enable the touchpad again.
                except Empty: # executed if no DISABLE event arrived until timeout
                    if not touchpadEnabled:
                        setDeviceEnabled(touchpadId, True)
                        touchpadEnabled = True
                    latestEvent = eventQueue.get()
    finally:
        # reenable the touchpad in any case
        setDeviceEnabled(touchpadId, True)

    retval = p.wait()

if __name__ == '__main__':
    main()
0
ответ дан 5 June 2019 в 02:05

Опрос неактивного времени через регулярные промежутки времени, как делает ваш текущий скрипт, означает, что сенсорная панель будет включена / отключена только на определенной частоте. Это приводит к следующему компромиссу:

  • Если интервал слишком длинный, время до включения или выключения сенсорной панели может быть таким же длинным, как интервал, в вашем случае - полсекунды.
  • Опрос с высокой частотой (например, каждую миллисекунду) заставляет скрипт реагировать очень быстро, но приводит к более высокой загрузке процессора.

Концептуально вы можете сделать следующее:

  • Подождите, пока не произойдет событие клавиатуры.
  • Отключить сенсорную панель.
  • На основании последней отметки времени ключевого события, текущего системного времени и задержки, после которой вы хотите снова включить сенсорную панель, вы можете рассчитать, когда вам нужно будет снова проверить наличие ключевых событий и перейти в спящий режим до тех пор. Если по истечении этого времени никаких дополнительных событий клавиш не произошло, включите сенсорную панель. В противном случае подсчитайте, когда вам нужно проверить еще раз, и повторите этот шаг.

Например: клавиша была нажата в момент времени 0 мс. Вы хотите снова включить сенсорную панель через 350 мс, так что вы можете спать 350 мс. Когда вы просыпаетесь, вы видите, что другая клавиша была нажата во время 250 мс. Основываясь на этой временной метке, текущем системном времени (350 мс) и указанной задержке (350 мс), теперь вы знаете, что вам нужно проверить 350 мс после последнего ключевого события, то есть в момент времени 600 мс, чтобы вы могли снова спать 250 мс.

Таким образом, вы можете убедиться, что сенсорная панель немедленно отключается при нажатии клавиши и снова включается очень близко к 350 мсам после отпускания последней клавиши без необходимости опроса на высокой частоте.


Этот скрипт использует python-evdev для чтения ключевых событий. Это имеет то преимущество, что нам не нужно выполнять какие-либо потоки самостоятельно, и мы можем легко ждать ключевых событий, используя selector. Недостатком является то, что скрипту требуются разрешения на чтение на узле устройства evdev клавиатуры, поэтому его нужно запускать с правами суперпользователя (если только вы не хотите добавить пользователя в группу ввода или изменить разрешения по правилу udev).

Запустите sudo apt install python3-evdev, чтобы установить python-evdev для python3. Измените KEYBOARD_NAME, TOUCHPAD_NAME и DELAY на желаемые значения:

#!/bin/env python3

import subprocess
import time
import evdev
from evdev import ecodes
import selectors
from selectors import DefaultSelector, EVENT_READ


DELAY = 0.35 # time in seconds after which the touchpad will be enabled again

KEYBOARD_NAME = "SINO WEALTH USB KEYBOARD" # the name as shown by evtest
TOUCHPAD_NAME = "pointer:SINO WEALTH USB KEYBOARD" # the name as shown by xinput list

lastKeyPress = 0
touchpadDisabled = False
touchpadId = -1

ignoreKeycodes = [ecodes.KEY_LEFTCTRL, ecodes.KEY_LEFTSHIFT, ecodes.KEY_RIGHTSHIFT, ecodes.KEY_LEFTALT, ecodes.KEY_RIGHTCTRL, ecodes.KEY_RIGHTALT, ecodes.KEY_LEFTMETA]

def getKeyboard():
    for device in evdev.list_devices():
        evdevDevice = evdev.InputDevice(device)
        if evdevDevice.name == KEYBOARD_NAME:
            # If touchpad and keyboard have the same name, check if the device has an ESC key (which a touchpad probably doesn't have)
            caps = evdevDevice.capabilities()
            if ecodes.EV_KEY in caps:
                if ecodes.KEY_ESC in caps[ecodes.EV_KEY]:
                    return evdevDevice
        evdevDevice.close()
    raise OSError("Unable to find keyboard: " + KEYBOARD_NAME)

def updateLastKeypress(event):
    global lastKeyPress
    if event.type == ecodes.EV_KEY:
        if not event.code in ignoreKeycodes:
            lastKeyPress = event.timestamp()

def enableTouchpad(force=False):
    global touchpadDisabled, touchpadId
    if touchpadDisabled or force:
        process = subprocess.run(["xinput", "set-prop", str(touchpadId), "143", "1"])
        touchpadDisabled = False

def disableTouchpad(force=False):
    global touchpadDisabled, touchpadId
    if not touchpadDisabled or force:
        process = subprocess.run(["xinput", "set-prop", str(touchpadId), "143", "0"])
        touchpadDisabled = True

def main():
    global touchpadId
    keyboard = getKeyboard()
    touchpadId = subprocess.check_output(["xinput", "list" , "--id-only", TOUCHPAD_NAME]).decode("UTF-8").strip() # this will raise an exception if it fails since xinput will exit with non-zero status.
    selector = selectors.DefaultSelector()
    selector.register(keyboard, selectors.EVENT_READ)

    while True:
        enableTouchpad()
        for key, mask in selector.select(): # this is where we wait for key events. Execution blocks until an event is available.
            device = key.fileobj

            while True: # we will stay in this loop until we can enable the touchpad again
                try:
                    for event in device.read():
                        updateLastKeypress(event)
                except BlockingIOError: # this will be raised by device.read() if there is no more event to read
                    pass
                timeToSleep = (lastKeyPress + DELAY) - time.time()
                if timeToSleep <= 0.005: # you can set this to 0, but that may result in unnecessarily short (and imperceptible) sleep times.
                    # touchpad can be enabled again, so break loop.
                    break
                else:
                    # disable touchpad and wait until we need to check next. disableTouchpad() takes care of only invoking xinput if necessary.
                    disableTouchpad()
                    time.sleep(timeToSleep)

if __name__ == "__main__":
    try:
        main()
    except:
        # make sure the touchpad is enabled again when any error occurs
        enableTouchpad(force=True)
        raise
0
ответ дан 5 June 2019 в 02:05

Другие вопросы по тегам:

Похожие вопросы: