This commit is contained in:
Devaev Maxim
2020-02-28 04:44:05 +03:00
parent a84b6bd31a
commit 1470ebe6fa
11 changed files with 241 additions and 121 deletions

View File

@@ -20,17 +20,12 @@
# ========================================================================== #
import asyncio
import concurrent.futures
import multiprocessing
import multiprocessing.queues
import queue
import functools
from typing import Dict
from typing import AsyncGenerator
from typing import Any
from .... import aiomulti
from ....yamlconf import Option
from ....validators.basic import valid_bool
@@ -54,10 +49,10 @@ class Plugin(BaseHid):
noop: bool,
) -> None:
self.__changes_queue: multiprocessing.queues.Queue = multiprocessing.Queue()
self.__state_notifier = aiomulti.AioProcessNotifier()
self.__keyboard_proc = KeyboardProcess(noop=noop, changes_queue=self.__changes_queue, **keyboard)
self.__mouse_proc = MouseProcess(noop=noop, changes_queue=self.__changes_queue, **mouse)
self.__keyboard_proc = KeyboardProcess(noop=noop, state_notifier=self.__state_notifier, **keyboard)
self.__mouse_proc = MouseProcess(noop=noop, state_notifier=self.__state_notifier, **mouse)
@classmethod
def get_plugin_options(cls) -> Dict:
@@ -81,31 +76,30 @@ class Plugin(BaseHid):
self.__keyboard_proc.start()
self.__mouse_proc.start()
def get_state(self) -> Dict:
keyboard_state = self.__keyboard_proc.get_state()
mouse_state = self.__mouse_proc.get_state()
async def get_state(self) -> Dict:
keyboard_state = await self.__keyboard_proc.get_state()
mouse_state = await self.__mouse_proc.get_state()
return {
"online": (keyboard_state["online"] and mouse_state["online"]),
"keyboard": {"features": {"leds": True}, **keyboard_state},
"keyboard": {
"online": keyboard_state["online"],
"leds": {
"caps": keyboard_state["caps"],
"scroll": keyboard_state["scroll"],
"num": keyboard_state["num"],
},
},
"mouse": mouse_state,
}
async def poll_state(self) -> AsyncGenerator[Dict, None]:
loop = asyncio.get_running_loop()
wait_for_changes = functools.partial(self.__changes_queue.get, timeout=1)
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
prev_state: Dict = {}
while True:
state = self.get_state()
if state != prev_state:
yield state
prev_state = state
while True:
try:
await loop.run_in_executor(executor, wait_for_changes)
break
except queue.Empty:
pass
prev_state: Dict = {}
while True:
state = await self.get_state()
if state != prev_state:
yield state
prev_state = state
await self.__state_notifier.wait()
async def reset(self) -> None:
self.__keyboard_proc.send_reset_event()

View File

@@ -30,12 +30,13 @@ import errno
import time
from typing import Dict
from typing import Any
import setproctitle
from ....logging import get_logger
from .... import aiomulti
# =====
class BaseEvent:
@@ -48,7 +49,7 @@ class BaseDeviceProcess(multiprocessing.Process): # pylint: disable=too-many-in
name: str,
read_size: int,
initial_state: Dict,
changes_queue: multiprocessing.queues.Queue,
state_notifier: aiomulti.AioProcessNotifier,
device_path: str,
select_timeout: float,
@@ -61,7 +62,6 @@ class BaseDeviceProcess(multiprocessing.Process): # pylint: disable=too-many-in
self.__name = name
self.__read_size = read_size
self.__changes_queue = changes_queue
self.__device_path = device_path
self.__select_timeout = select_timeout
@@ -71,7 +71,7 @@ class BaseDeviceProcess(multiprocessing.Process): # pylint: disable=too-many-in
self.__fd = -1
self.__events_queue: multiprocessing.queues.Queue = multiprocessing.Queue()
self.__state_shared = multiprocessing.Manager().dict(online=True, **initial_state) # type: ignore
self.__state_flags = aiomulti.AioSharedFlags({"online": True, **initial_state}, state_notifier)
self.__stop_event = multiprocessing.Event()
def run(self) -> None:
@@ -100,8 +100,8 @@ class BaseDeviceProcess(multiprocessing.Process): # pylint: disable=too-many-in
self.__close_device()
def get_state(self) -> Dict:
return dict(self.__state_shared)
async def get_state(self) -> Dict:
return (await self.__state_flags.get())
# =====
@@ -111,6 +111,9 @@ class BaseDeviceProcess(multiprocessing.Process): # pylint: disable=too-many-in
def _process_read_report(self, report: bytes) -> None:
pass
def _update_state(self, **kwargs: bool) -> None:
self.__state_flags.update(**kwargs)
# =====
def _stop(self) -> None:
@@ -134,11 +137,6 @@ class BaseDeviceProcess(multiprocessing.Process): # pylint: disable=too-many-in
if close:
self.__close_device()
def _update_state(self, key: str, value: Any) -> None:
if self.__state_shared[key] != value:
self.__state_shared[key] = value
self.__changes_queue.put(None)
# =====
def __write_report(self, report: bytes) -> bool:
@@ -153,7 +151,7 @@ class BaseDeviceProcess(multiprocessing.Process): # pylint: disable=too-many-in
try:
written = os.write(self.__fd, report)
if written == len(report):
self._update_state("online", True)
self.__state_flags.update(online=True)
return True
else:
logger.error("HID-%s write() error: written (%s) != report length (%d)",
@@ -223,7 +221,7 @@ class BaseDeviceProcess(multiprocessing.Process): # pylint: disable=too-many-in
if self.__fd >= 0:
try:
if select.select([], [self.__fd], [], self.__select_timeout)[1]:
self._update_state("online", True)
self.__state_flags.update(online=True)
return True
else:
logger.debug("HID-%s is busy/unplugged (write select)", self.__name)
@@ -231,7 +229,7 @@ class BaseDeviceProcess(multiprocessing.Process): # pylint: disable=too-many-in
logger.error("Can't select() for write HID-%s: %s: %s", self.__name, type(err).__name__, err)
self.__close_device()
self._update_state("online", False)
self.__state_flags.update(online=False)
return False
def __close_device(self) -> None:

View File

@@ -68,7 +68,7 @@ class KeyboardProcess(BaseDeviceProcess):
super().__init__(
name="keyboard",
read_size=1,
initial_state={"leds": {"caps": False, "scroll": False, "num": False}},
initial_state={"caps": False, "scroll": False, "num": False},
**kwargs,
)
@@ -98,11 +98,11 @@ class KeyboardProcess(BaseDeviceProcess):
def _process_read_report(self, report: bytes) -> None:
# https://wiki.osdev.org/USB_Human_Interface_Devices#LED_lamps
assert len(report) == 1, report
self._update_state("leds", {
"caps": bool(report[0] & 2),
"scroll": bool(report[0] & 4),
"num": bool(report[0] & 1),
})
self._update_state(
caps=bool(report[0] & 2),
scroll=bool(report[0] & 4),
num=bool(report[0] & 1),
)
# =====