Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor the base module and make it an API for low level commands #2615

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9e6bd8b
hidapi: Completely remove dependency on gi
MattHag Sep 20, 2024
f3ce935
Clarify that fake hidpp is used
MattHag Sep 15, 2024
e638250
Simplify setup with pathlib
MattHag Sep 15, 2024
7b4d93a
Fix test coverage reporting
MattHag Sep 18, 2024
37f4be1
Introduce test coverage threshold
MattHag Sep 18, 2024
ef54d28
Setup reports and upload codecov
MattHag Sep 19, 2024
9e946a0
Add code coverage badge
MattHag Sep 19, 2024
f6ac185
Rename lr/notify module to desktop_notifications
MattHag Sep 18, 2024
be81eea
Make lr/desktop_notifications testable
MattHag Sep 18, 2024
8db32bd
Rename ui/notify module to desktop notifications
MattHag Sep 18, 2024
bc82f79
Make ui/desktop_notifications testable
MattHag Sep 18, 2024
ef65d79
settings_template: Prepare removal of desktop_notifications dependency
MattHag Sep 18, 2024
823bf29
base: Simplify receiver info retrieval
MattHag Sep 15, 2024
0212338
Replace global sw_id with function call
MattHag Sep 15, 2024
61a27d5
base: Add type hints
MattHag Sep 15, 2024
f4866f7
solaar: Add type hints
MattHag Sep 15, 2024
563d56a
Upload test coverage reports solely after merging (#7)
MattHag Sep 25, 2024
337eb4a
keysymdef: Rename key symbols
MattHag Sep 25, 2024
c58075b
keysyms: Introduce tests for this package
MattHag Sep 25, 2024
b127e6e
Test coverage: Fix keysyms to be visible (#9)
MattHag Sep 25, 2024
b9eed6c
device: Remove hard dependency on hidapi
MattHag Sep 25, 2024
2182d90
receiver: Remove hard dependency on hidapi
MattHag Sep 25, 2024
2705f57
hidapi: Explicitly load hidapi/udev implementation
MattHag Sep 26, 2024
65d738f
ui/about: Use Model-View-Presenter pattern for testability
MattHag Sep 26, 2024
be196b5
base_usb: Add external interface
MattHag Sep 27, 2024
755d2f0
Fix about dialog
MattHag Sep 27, 2024
cc1bf11
base: Add find_paired_node functions
MattHag Sep 28, 2024
5e37364
base: Add find_paired_node functions
MattHag Sep 28, 2024
cced4dc
Add type hints and clean up
MattHag Sep 28, 2024
aaa1f22
base: Add test for filter_products_of_interest
MattHag Sep 28, 2024
01fa61b
device: Remove hard dependency on base
MattHag Sep 28, 2024
134e69e
receiver: Remove hard dependency on base
MattHag Sep 28, 2024
0b77dba
Remove factory wrapper classes
MattHag Sep 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[run]
branch = True

source =
hid_parser
hidapi
keysyms
logitech_receiver
solaar

omit =
*/tests/*
*/setup.py
*/__main__.py

[report]
exclude_lines =
pragma: no cover
if __name__ == '__main__':
if typing.TYPE_CHECKING

fail_under = 40
23 changes: 22 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ jobs:
run: |
make test

- name: Upload coverage to Codecov
if: github.ref == 'refs/heads/master'
uses: codecov/codecov-action@v4.5.0
with:
directory: ./coverage/reports/
env_vars: OS, PYTHON
files: ./coverage.xml
flags: unittests
name: codecov-umbrella
token: ${{ secrets.CODECOV_TOKEN }}

macos-tests:
runs-on: macos-latest

Expand All @@ -55,4 +66,14 @@ jobs:
make install_pip PIP_ARGS='.["test"]'
- name: Run tests on macOS
run: |
export DYLD_LIBRARY_PATH=$(brew --prefix hidapi)/lib:$DYLD_LIBRARY_PATH && pytest --cov=lib/ tests/
export DYLD_LIBRARY_PATH=$(brew --prefix hidapi)/lib:$DYLD_LIBRARY_PATH && pytest --cov --cov-report=xml
- name: Upload coverage to Codecov
if: github.ref == 'refs/heads/master'
uses: codecov/codecov-action@v4.5.0
with:
directory: ./coverage/reports/
env_vars: OS, PYTHON
files: ./coverage.xml
flags: unittests
name: codecov-umbrella
token: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,4 @@ lint:

test:
@echo "Running Solaar tests"
pytest --cov=lib/ tests/
pytest --cov --cov-report=xml
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ that are otherwise ignored by the Linux input system.
<a href="https://pwr-solaar.github.io/Solaar/installation">Manual Installation</a>


[![codecov](https://codecov.io/gh/pwr-Solaar/Solaar/graph/badge.svg?token=D7YWFEWID6)](https://codecov.io/gh/pwr-Solaar/Solaar)
[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2+-blue.svg)](../LICENSE.txt)

<p align="center">
Expand Down
47 changes: 0 additions & 47 deletions lib/hidapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,47 +0,0 @@
## Copyright (C) 2012-2013 Daniel Pavel
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License along
## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Generic Human Interface Device API."""

import platform

if platform.system() in ("Darwin", "Windows"):
from hidapi.hidapi_impl import close # noqa: F401
from hidapi.hidapi_impl import enumerate # noqa: F401
from hidapi.hidapi_impl import find_paired_node # noqa: F401
from hidapi.hidapi_impl import find_paired_node_wpid # noqa: F401
from hidapi.hidapi_impl import get_manufacturer # noqa: F401
from hidapi.hidapi_impl import get_product # noqa: F401
from hidapi.hidapi_impl import get_serial # noqa: F401
from hidapi.hidapi_impl import monitor_glib # noqa: F401
from hidapi.hidapi_impl import open # noqa: F401
from hidapi.hidapi_impl import open_path # noqa: F401
from hidapi.hidapi_impl import read # noqa: F401
from hidapi.hidapi_impl import write # noqa: F401
else:
from hidapi.udev_impl import close # noqa: F401
from hidapi.udev_impl import enumerate # noqa: F401
from hidapi.udev_impl import find_paired_node # noqa: F401
from hidapi.udev_impl import find_paired_node_wpid # noqa: F401
from hidapi.udev_impl import get_manufacturer # noqa: F401
from hidapi.udev_impl import get_product # noqa: F401
from hidapi.udev_impl import get_serial # noqa: F401
from hidapi.udev_impl import monitor_glib # noqa: F401
from hidapi.udev_impl import open # noqa: F401
from hidapi.udev_impl import open_path # noqa: F401
from hidapi.udev_impl import read # noqa: F401
from hidapi.udev_impl import write # noqa: F401

__version__ = "0.9"
14 changes: 7 additions & 7 deletions lib/hidapi/hidapi_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@
from threading import Thread
from time import sleep

import gi

from hidapi.common import DeviceInfo

if typing.TYPE_CHECKING:
import gi

gi.require_version("Gdk", "3.0")
from gi.repository import GLib # NOQA: E402

Expand Down Expand Up @@ -263,10 +263,10 @@ def _match(action, device, filterfn):
if not device["hidpp_short"] and not device["hidpp_long"]:
return None

filter = filterfn(bus_id, vid, pid, device["hidpp_short"], device["hidpp_long"])
if not filter:
filter_func = filterfn(bus_id, vid, pid, device["hidpp_short"], device["hidpp_long"])
if not filter_func:
return
isDevice = filter.get("isDevice")
isDevice = filter_func.get("isDevice")

if action == "add":
d_info = DeviceInfo(
Expand Down Expand Up @@ -305,12 +305,12 @@ def _match(action, device, filterfn):
return d_info


def find_paired_node(receiver_path, index, timeout):
def find_paired_node(receiver_path: str, index: int, timeout: int):
"""Find the node of a device paired with a receiver"""
return None


def find_paired_node_wpid(receiver_path, index):
def find_paired_node_wpid(receiver_path: str, index: int):
"""Find the node of a device paired with a receiver, get wpid from udev"""
return None

Expand Down
6 changes: 5 additions & 1 deletion lib/hidapi/hidconsole.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import argparse
import os
import os.path
import platform
import readline
import sys
import time
Expand All @@ -27,7 +28,10 @@
from threading import Lock
from threading import Thread

import hidapi
if platform.system() == "Linux":
import hidapi.udev_impl as hidapi
else:
import hidapi.hidapi_impl as hidapi

LOGITECH_VENDOR_ID = 0x046D

Expand Down
19 changes: 10 additions & 9 deletions lib/hidapi/udev_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,13 @@
from time import sleep
from time import time

import gi
import pyudev

from hidapi.common import DeviceInfo

if typing.TYPE_CHECKING:
import gi

gi.require_version("Gdk", "3.0")
from gi.repository import GLib # NOQA: E402

Expand Down Expand Up @@ -77,7 +78,7 @@ def exit():
# The filterfn is used to determine whether this is a device of interest to Solaar.
# It is given the bus id, vendor id, and product id and returns a dictionary
# with the required hid_driver and usb_interface and whether this is a receiver or device.
def _match(action, device, filterfn):
def _match(action, device, filter_func: typing.Callable[[int, int, int, bool, bool], dict[str, typing.Any]]):
if logger.isEnabledFor(logging.DEBUG):
logger.debug(f"Dbus event {action} {device}")
hid_device = device.find_parent("hid")
Expand Down Expand Up @@ -112,11 +113,11 @@ def _match(action, device, filterfn):
"Report Descriptor not processed for DEVICE %s BID %s VID %s PID %s: %s", device.device_node, bid, vid, pid, e
)

filter = filterfn(int(bid, 16), int(vid, 16), int(pid, 16), hidpp_short, hidpp_long)
if not filter:
filtered_result = filter_func(int(bid, 16), int(vid, 16), int(pid, 16), hidpp_short, hidpp_long)
if not filtered_result:
return
interface_number = filter.get("usb_interface")
isDevice = filter.get("isDevice")
interface_number = filtered_result.get("usb_interface")
isDevice = filtered_result.get("isDevice")

if action == "add":
hid_driver_name = hid_device.properties.get("DRIVER")
Expand Down Expand Up @@ -175,7 +176,7 @@ def _match(action, device, filterfn):
return d_info


def find_paired_node(receiver_path, index, timeout):
def find_paired_node(receiver_path: str, index: int, timeout: int):
"""Find the node of a device paired with a receiver"""
context = pyudev.Context()
receiver_phys = pyudev.Devices.from_device_file(context, receiver_path).find_parent("hid").get("HID_PHYS")
Expand Down Expand Up @@ -259,7 +260,7 @@ def _process_udev_event(monitor, condition, cb, filterfn):
m.start()


def enumerate(filterfn):
def enumerate(filter_func: typing.Callable[[int, int, int, bool, bool], dict[str, typing.Any]]):
"""Enumerate the HID Devices.

List all the HID devices attached to the system, optionally filtering by
Expand All @@ -271,7 +272,7 @@ def enumerate(filterfn):
if logger.isEnabledFor(logging.DEBUG):
logger.debug("Starting dbus enumeration")
for dev in pyudev.Context().list_devices(subsystem="hidraw"):
dev_info = _match("add", dev, filterfn)
dev_info = _match("add", dev, filter_func)
if dev_info:
yield dev_info

Expand Down
34 changes: 15 additions & 19 deletions lib/keysyms/generate.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,37 @@
#!/usr/bin/env python3
"""Extract key symbol encodings from X11 header files."""

from pathlib import Path
from pprint import pprint
from re import findall
from subprocess import run
from tempfile import TemporaryDirectory

repo = "https://github.com/freedesktop/xorg-proto-x11proto.git"
xx = "https://gitlab.freedesktop.org/xorg/proto/xorgproto/-/tree/master/include/X11/"
repo = "https://gitlab.freedesktop.org/xorg/proto/xorgproto.git"
pattern = r"#define XK_(\w+)\s+0x(\w+)(?:\s+/\*\s+U\+(\w+))?"
xf86pattern = r"#define XF86XK_(\w+)\s+0x(\w+)(?:\s+/\*\s+U\+(\w+))?"


def main():
keysymdef = {}
keysym_files = [
("include/X11/keysymdef.h", pattern, ""),
("include/X11/XF86keysym.h", xf86pattern, "XF86_"),
]

with TemporaryDirectory() as temp:
run(["git", "clone", repo, "."], cwd=temp)
# text = Path(temp, 'keysymdef.h').read_text()
text = Path(temp, "include/X11/keysymdef.h").read_text()
for name, sym, uni in findall(pattern, text):
sym = int(sym, 16)
uni = int(uni, 16) if uni else None
if keysymdef.get(name, None):
print("KEY DUP", name)
keysymdef[name] = sym
# text = Path(temp, 'keysymdef.h').read_text()
text = Path(temp, "include/X11/XF86keysym.h").read_text()
for name, sym, uni in findall(xf86pattern, text):
sym = int(sym, 16)
uni = int(uni, 16) if uni else None
if keysymdef.get("XF86_" + name, None):
print("KEY DUP", "XF86_" + name)
keysymdef["XF86_" + name] = sym

for filename, extraction_pattern, prefix in keysym_files:
text = Path(temp, filename).read_text()
for name, sym, _ in findall(extraction_pattern, text):
sym = int(sym, 16)
if keysymdef.get(f"{prefix}{name}", None):
print(f"KEY DUP {prefix}{name}")
keysymdef[f"{prefix}{name}"] = sym

with open("keysymdef.py", "w") as f:
f.write("# flake8: noqa\nkeysymdef = \\\n")
f.write("# flake8: noqa\nkey_symbols = \\\n")
pprint(keysymdef, f)


Expand Down
2 changes: 1 addition & 1 deletion lib/keysyms/keysymdef.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# flake8: noqa
keysymdef = {
key_symbols = {
"0": 48,
"1": 49,
"2": 50,
Expand Down
Loading
Loading