Refactor: Use dataclasses and enums

Replace NamedTuples with more flexible dataclass, which also support
type hints. Introduce enums to replace constant strings, which need to
be kept in sync. Also enhances interfaces by limiting it to the enum
values. Remove unused variables.
This commit is contained in:
Matthias Hagmann 2024-05-10 13:27:18 +02:00 committed by Peter F. Patel-Schneider
parent 500b9998c5
commit 7d171b1d09
4 changed files with 56 additions and 67 deletions

18
lib/hidapi/common.py Normal file
View File

@ -0,0 +1,18 @@
import dataclasses
@dataclasses.dataclass
class DeviceInfo:
path: str
bus_id: str
vendor_id: str
product_id: str
interface: str
driver: str
manufacturer: str
product: str
serial: str
release: str
isDevice: bool
hidpp_short: str
hidpp_long: str

View File

@ -28,38 +28,18 @@ import ctypes
import logging
import platform
from collections import namedtuple
from threading import Thread
from time import sleep
import gi
from hidapi.common import DeviceInfo
gi.require_version("Gdk", "3.0")
from gi.repository import GLib # NOQA: E402
logger = logging.getLogger(__name__)
native_implementation = "hidapi"
# Device info as expected by Solaar
DeviceInfo = namedtuple(
"DeviceInfo",
[
"path",
"bus_id",
"vendor_id",
"product_id",
"interface",
"driver",
"manufacturer",
"product",
"serial",
"release",
"isDevice",
"hidpp_short",
"hidpp_long",
],
)
# Global handle to hidapi
_hidapi = None

View File

@ -29,7 +29,6 @@ import warnings
# the tuple object we'll expose when enumerating devices
from collections import namedtuple
from select import select
from time import sleep
from time import time
@ -37,33 +36,15 @@ from time import time
import gi
import pyudev
from hidapi.common import DeviceInfo
gi.require_version("Gdk", "3.0")
from gi.repository import GLib # NOQA: E402
logger = logging.getLogger(__name__)
native_implementation = "udev"
fileopen = open
DeviceInfo = namedtuple(
"DeviceInfo",
[
"path",
"bus_id",
"vendor_id",
"product_id",
"interface",
"driver",
"manufacturer",
"product",
"serial",
"release",
"isDevice",
"hidpp_short",
"hidpp_long",
],
)
#
# exposed API
# docstrings mostly copied from hidapi.h

View File

@ -21,6 +21,7 @@ from collections import defaultdict
from copy import copy
from dataclasses import dataclass
from dataclasses import field
from enum import Enum
from shlex import quote as shlex_quote
from typing import Any
from typing import Dict
@ -932,6 +933,12 @@ class DeviceInfo:
codename: str = ""
settings: Dict[str, _Setting] = field(default_factory=dict)
def __post_init__(self):
if self.serial is None or self.serial == "?":
self.serial = ""
if self.unitId is None or self.unitId == "?":
self.unitId = ""
@property
def id(self):
return self.serial or self.unitId or ""
@ -944,12 +951,6 @@ class DeviceInfo:
def display_name(self):
return f"{self.codename} ({self.id})"
def __post_init__(self):
if self.serial is None or self.serial == "?":
self.serial = ""
if self.unitId is None or self.unitId == "?":
self.unitId = ""
def matches(self, search):
return search and search in (self.serial, self.unitId, self.display_name)
@ -960,8 +961,10 @@ class DeviceInfo:
if v and v != "?":
setattr(self, k, copy(v) if k != "settings" else {s.name: s for s in v})
class DeviceInfoFactory:
@classmethod
def from_device(cls, device):
def create_device_info(cls, device) -> DeviceInfo:
d = DeviceInfo()
d.update(device)
return d
@ -992,7 +995,8 @@ class AllDevicesInfo:
existing = self[device.serial] or self[device.unitId]
if not existing:
updated = True
self._devices.append(DeviceInfo.from_device(device))
device_info = DeviceInfoFactory.create_device_info(device)
self._devices.append(device_info)
elif not existing.settings and device.settings:
updated = True
existing.update(device)
@ -1076,7 +1080,6 @@ class LaterUI(RuleComponentUI):
self.field.set_halign(Gtk.Align.CENTER)
self.field.set_valign(Gtk.Align.CENTER)
self.field.set_hexpand(True)
# self.field.set_vexpand(True)
self.field.connect("value-changed", self._on_update)
self.widgets[self.field] = (0, 1, 1, 1)
@ -1126,6 +1129,13 @@ def _from_named_ints(v, all_values):
return v
class SetValueControlKinds(Enum):
TOGGLE = "toggle"
RANGE = "range"
RANGE_WITH_KEY = "range_with_key"
CHOICE = "choice"
class SetValueControl(Gtk.HBox):
def __init__(self, on_change, *args, accept_toggle=True, **kwargs):
super().__init__(*args, **kwargs)
@ -1156,17 +1166,17 @@ class SetValueControl(Gtk.HBox):
self.pack_end(w, True, True, 0)
w.hide()
self.unsupp_value = None
self.current_kind = None
self.current_kind: Optional[SetValueControlKinds] = None
self.sub_key_range_items = None
def _changed(self, widget, *args):
if widget.get_visible():
value = self.get_value()
if self.current_kind == "choice":
if self.current_kind == SetValueControlKinds.CHOICE:
value = widget.get_value()
icon = "dialog-warning" if widget._allowed_values and (value not in widget._allowed_values) else ""
widget.get_child().set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon)
elif self.current_kind == "range_with_key" and widget == self.sub_key_widget:
elif self.current_kind == SetValueControlKinds.RANGE_WITH_KEY and widget == self.sub_key_widget:
key = self.sub_key_widget.get_value()
selected_item = (
next((item for item in self.sub_key_range_items if key == item.id), None)
@ -1182,27 +1192,27 @@ class SetValueControl(Gtk.HBox):
w.hide()
def get_value(self):
if self.current_kind == "toggle":
if self.current_kind == SetValueControlKinds.TOGGLE:
return self.toggle_widget.get_value()
if self.current_kind == "range":
if self.current_kind == SetValueControlKinds.RANGE:
return int(self.range_widget.get_value())
if self.current_kind == "range_with_key":
if self.current_kind == SetValueControlKinds.RANGE_WITH_KEY:
return {self.sub_key_widget.get_value(): int(self.range_widget.get_value())}
if self.current_kind == "choice":
if self.current_kind == SetValueControlKinds.CHOICE:
return self.choice_widget.get_value()
return self.unsupp_value
def set_value(self, value):
if self.current_kind == "toggle":
if self.current_kind == SetValueControlKinds.TOGGLE:
self.toggle_widget.set_value(value if value is not None else "")
elif self.current_kind == "range":
elif self.current_kind == SetValueControlKinds.RANGE:
minimum, maximum = self.range_widget.get_range()
try:
v = round(float(value))
except (ValueError, TypeError):
v = minimum
self.range_widget.set_value(max(minimum, min(maximum, v)))
elif self.current_kind == "range_with_key":
elif self.current_kind == SetValueControlKinds.RANGE_WITH_KEY:
if not (isinstance(value, dict) and len(value) == 1):
value = {None: None}
key = next(iter(value.keys()))
@ -1216,7 +1226,7 @@ class SetValueControl(Gtk.HBox):
v = minimum
self.sub_key_widget.set_value(key or "")
self.range_widget.set_value(max(minimum, min(maximum, v)))
elif self.current_kind == "choice":
elif self.current_kind == SetValueControlKinds.CHOICE:
self.choice_widget.set_value(value)
else:
self.unsupp_value = value
@ -1228,18 +1238,18 @@ class SetValueControl(Gtk.HBox):
self.choice_widget.set_value("")
def make_toggle(self):
self.current_kind = "toggle"
self.current_kind = SetValueControlKinds.TOGGLE
self._hide_all()
self.toggle_widget.show()
def make_range(self, minimum, maximum):
self.current_kind = "range"
self.current_kind = SetValueControlKinds.RANGE
self._hide_all()
self.range_widget.set_range(minimum, maximum)
self.range_widget.show()
def make_range_with_key(self, items, labels=None):
self.current_kind = "range_with_key"
self.current_kind = SetValueControlKinds.RANGE_WITH_KEY
self._hide_all()
self.sub_key_range_items = items or None
if not labels:
@ -1252,7 +1262,7 @@ class SetValueControl(Gtk.HBox):
def make_choice(self, values, extra=None):
# if extra is not in values, it is ignored
self.current_kind = "choice"
self.current_kind = SetValueControlKinds.CHOICE
self._hide_all()
sort_key = int if all((v == extra or str(v).isdigit()) for v in values) else str
if extra is not None and extra in values: