usbguard-gui 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. usbguard_gui-0.3.1/.claude/settings.local.json +22 -0
  2. usbguard_gui-0.3.1/.copr/Makefile +8 -0
  3. usbguard_gui-0.3.1/.editorconfig +22 -0
  4. usbguard_gui-0.3.1/.github/workflows/makefile.yml +23 -0
  5. usbguard_gui-0.3.1/.gitignore +31 -0
  6. usbguard_gui-0.3.1/AGENTS.md +176 -0
  7. usbguard_gui-0.3.1/CHANGELOG.md +196 -0
  8. usbguard_gui-0.3.1/CLAUDE.md +3 -0
  9. usbguard_gui-0.3.1/DESIGN.md +203 -0
  10. usbguard_gui-0.3.1/LICENSE +15 -0
  11. usbguard_gui-0.3.1/Makefile +155 -0
  12. usbguard_gui-0.3.1/PKG-INFO +200 -0
  13. usbguard_gui-0.3.1/PyGObject.md +8 -0
  14. usbguard_gui-0.3.1/README.md +171 -0
  15. usbguard_gui-0.3.1/TODO.md +8 -0
  16. usbguard_gui-0.3.1/pyproject.toml +75 -0
  17. usbguard_gui-0.3.1/release.py +139 -0
  18. usbguard_gui-0.3.1/rpm/70-usbguard_gui.rules +27 -0
  19. usbguard_gui-0.3.1/rpm/usbguard_gui-autostart.desktop +10 -0
  20. usbguard_gui-0.3.1/rpm/usbguard_gui.desktop +12 -0
  21. usbguard_gui-0.3.1/rpm/usbguard_gui.spec.in +115 -0
  22. usbguard_gui-0.3.1/rpm/usbguard_gui.svg +19 -0
  23. usbguard_gui-0.3.1/screenshot_20260411_091821.png +0 -0
  24. usbguard_gui-0.3.1/screenshot_20260411_091821.xcf +0 -0
  25. usbguard_gui-0.3.1/src/usbguard_gui/__init__.py +9 -0
  26. usbguard_gui-0.3.1/src/usbguard_gui/__main__.py +6 -0
  27. usbguard_gui-0.3.1/src/usbguard_gui/app.py +397 -0
  28. usbguard_gui-0.3.1/src/usbguard_gui/dbus_client.py +299 -0
  29. usbguard_gui-0.3.1/src/usbguard_gui/device.py +196 -0
  30. usbguard_gui-0.3.1/src/usbguard_gui/device_dialog.py +130 -0
  31. usbguard_gui-0.3.1/src/usbguard_gui/device_list.py +275 -0
  32. usbguard_gui-0.3.1/src/usbguard_gui/introspection/org.freedesktop.ScreenSaver.xml +9 -0
  33. usbguard_gui-0.3.1/src/usbguard_gui/introspection/org.usbguard.Devices1.xml +30 -0
  34. usbguard_gui-0.3.1/src/usbguard_gui/introspection/org.usbguard.Policy1.xml +12 -0
  35. usbguard_gui-0.3.1/src/usbguard_gui/screensaver.py +248 -0
  36. usbguard_gui-0.3.1/src/usbguard_gui/settings.py +23 -0
  37. usbguard_gui-0.3.1/tests/test_app.py +332 -0
  38. usbguard_gui-0.3.1/tests/test_async_api.py +221 -0
  39. usbguard_gui-0.3.1/tests/test_dbus_client.py +457 -0
  40. usbguard_gui-0.3.1/tests/test_device.py +160 -0
  41. usbguard_gui-0.3.1/tests/test_device_list.py +171 -0
  42. usbguard_gui-0.3.1/tests/test_release.py +321 -0
  43. usbguard_gui-0.3.1/tox.ini +13 -0
@@ -0,0 +1,22 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(git -C /home/dgunchev/github/gunchev/usbguard_gui/ log --oneline -3)",
5
+ "Read(//home/dgunchev/github/gunchev/**)",
6
+ "Bash(uv sync:*)",
7
+ "Bash(uv run:*)",
8
+ "Bash(git -C /home/dgunchev/github/gunchev/usbguard_gui/ status)",
9
+ "Bash(git:*)",
10
+ "Bash(make:*)",
11
+ "Bash(uv pip:*)",
12
+ "Bash(uv add:*)",
13
+ "Bash(ls *.py release*)",
14
+ "Bash(grep -E \"\\\\.\\(toml|cfg|ini|txt\\)$\")",
15
+ "Bash(xargs ls:*)",
16
+ "Bash(rpm -ql python3-gobject-base)",
17
+ "Bash(python3:*)",
18
+ "Bash(wc:*)",
19
+ "Bash(python:*)"
20
+ ]
21
+ }
22
+ }
@@ -0,0 +1,8 @@
1
+ export TOP:=$(shell dirname "$(abspath $(lastword $(MAKEFILE_LIST)))")
2
+ outdir ?= dist
3
+
4
+ .PHONY: srpm
5
+ srpm:
6
+ dnf -y install uv python3 python3-build python3-hatchling
7
+ $(MAKE) -C $(TOP)/.. srpm
8
+ if test -n "$(outdir)"; then cp -a $(TOP)/../dist/*.src.rpm "$(outdir)"; fi
@@ -0,0 +1,22 @@
1
+ # EditorConfig is awesome: https://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ [*]
7
+ charset = utf-8
8
+ end_of_line = lf
9
+ insert_final_newline = true
10
+ trim_trailing_whitespace = true
11
+ max_line_length = 120
12
+
13
+ [*.{cfg,ini,py,rst,toml,txt}]
14
+ indent_style = space
15
+ indent_size = 4
16
+
17
+ [*.{htm,html,js,json,md,xml,yaml,yml}]
18
+ indent_style = space
19
+ indent_size = 2
20
+
21
+ [{Makefile,*.go}]
22
+ indent_style = tab
@@ -0,0 +1,23 @@
1
+ name: Makefile CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ "master" ]
6
+ pull_request:
7
+ branches: [ "master" ]
8
+
9
+ jobs:
10
+ build:
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Run tox (py310–py314) on Fedora
18
+ run: |
19
+ sudo apt-get update && sudo apt-get install -y podman
20
+ podman run --rm -v "$PWD:/work" -w "/work" quay.io/fedora/fedora:latest bash -c '
21
+ dnf -y install uv python3 python3.10 python3.11 python3.12 python3.13 make gcc fedora-packager python3-devel python3-cairo cairo-gobject-devel cairo-devel libglvnd-glx libglvnd-egl
22
+ UV_PYTHON_DOWNLOADS=never QT_QPA_PLATFORM=offscreen uv run tox
23
+ '
@@ -0,0 +1,31 @@
1
+ /dist/
2
+ /build/
3
+ /htmlcov/
4
+ __pycache__/
5
+ *.py[cod]
6
+ *.egg-info/
7
+ *.egg
8
+ .eggs/
9
+ .tox/
10
+ .pytest_cache/
11
+ .coverage
12
+ *.py,cover
13
+ site.py
14
+ __pypackages__/
15
+
16
+ # uv
17
+ /.venv/
18
+ /uv.lock
19
+
20
+ # Local wheels cache
21
+ /whl_local/
22
+
23
+ # Packaging
24
+ /*.spec
25
+ /VERSION
26
+
27
+ # Editor
28
+ /.idea/
29
+ /.vscode/
30
+ *.swp
31
+ *~
@@ -0,0 +1,176 @@
1
+ # AGENTS.md
2
+
3
+ Instructions for agentic coding agents working in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ KDE/Qt system tray GUI for USBGuard — responds to USB device insertions with Allow, Block, or Reject actions.
8
+
9
+ - **Language**: Python >= 3.10
10
+ - **UI Framework**: PyQt6
11
+ - **IPC**: D-Bus (via dbus-fast)
12
+ - **Layout**: src-layout (sources in `src/usbguard_gui/`)
13
+
14
+ ## Build & Run Commands
15
+
16
+ ### Using uv (recommended)
17
+
18
+ ```bash
19
+ uv run usbguard_gui # run the app
20
+ uv run python -m usbguard_gui # alternative entry point
21
+ uv run pytest # run all tests
22
+ uv run pytest tests/test_file.py # run single test file
23
+ uv run pytest -k test_name # run single test by name
24
+ uv run pytest -v --cov . --cov-report=term-missing # coverage
25
+ uv run ruff check src/ tests/ # lint
26
+ uv run autopep8 --in-place --recursive src/ tests/ # format
27
+ uv run tox # test across Python versions
28
+ ```
29
+
30
+ ### Using Make
31
+
32
+ ```bash
33
+ make test # run all tests (uv run pytest -v)
34
+ make check # lint + test
35
+ make lint # ruff check + autopep8 format check
36
+ make format # auto-format with autopep8
37
+ make coverage # test with coverage report
38
+ make build # build wheel package
39
+ make clean # clean build artifacts
40
+ make run # sync dev deps and run app
41
+ ```
42
+
43
+ ## Code Style Guidelines
44
+
45
+ ### Formatting
46
+
47
+ - **Line length**: 120 characters maximum
48
+ - **Indentation**: 4 spaces for Python files
49
+ - **Line endings**: LF
50
+ - **Formatter**: autopep8
51
+ - **Charset**: UTF-8
52
+ - **Trailing whitespace**: trimmed
53
+ - **Final newline**: required
54
+ - See `.editorconfig` for additional editor-specific settings
55
+
56
+ ### Linter/Formatter Configuration (pyproject.toml)
57
+
58
+ ```toml
59
+ [tool.ruff]
60
+ line-length = 120
61
+ target-version = "py310"
62
+
63
+ [tool.ruff.lint]
64
+ select = ["E", "F", "W", "UP", "B", "SIM", "RUF"]
65
+
66
+ [tool.autopep8]
67
+ max_line_length = 120
68
+ ```
69
+
70
+ ### Imports
71
+
72
+ - Always use `from __future__ import annotations` for postponed annotations
73
+ - Group imports in order: stdlib, third-party, local
74
+ - Use absolute imports: `from usbguard_gui.device import ...`
75
+ - Sort imports with ruff (I001)
76
+
77
+ ### Type Hints
78
+
79
+ - Required on all public APIs (functions, classes, methods)
80
+ - Use Python 3.10+ syntax (`X | None` over `Optional[X]`)
81
+ - Return type annotations: always present on public methods
82
+
83
+ ### Naming Conventions
84
+
85
+ - **Classes**: `PascalCase` (e.g., `USBGuardClient`, `DeviceActionDialog`)
86
+ - **Functions/methods**: `snake_case` (e.g., `apply_device_policy`, `_handle_disconnect`)
87
+ - **Constants**: `SCREAMING_SNAKE_CASE` (e.g., `USBGUARD_BUS_NAME`)
88
+ - **Private members**: leading underscore (e.g., `self._client`, `self._connected`)
89
+
90
+ ### Docstrings
91
+
92
+ - Module-level: `"""Module description."""`
93
+ - Classes: `"""Short description.\n\nExtended explanation if needed.\n"""`
94
+ - Methods: `"""One-line description."""` or multi-line for complex methods
95
+
96
+ ### Error Handling
97
+
98
+ - Use logging (`log = logging.getLogger(__name__)`) for errors and warnings
99
+ - D-Bus errors: catch `DBusError` from `dbus_fast.error`
100
+ - Permission errors: check error names via `_is_permission_error()` pattern
101
+ - Never expose secrets or credentials in logs
102
+
103
+ ### Dataclasses & Enums
104
+
105
+ - Use `@dataclass` for data models with `field(default_factory=...)` for mutable defaults
106
+ - Use `IntEnum` for integer-backed enums (e.g., `DeviceTarget`, `PresenceEvent`)
107
+ - Prefer `.name` over string comparison when available
108
+
109
+ ### Qt/PyQt6 Patterns
110
+
111
+ - Subclass `QObject` for classes that emit signals
112
+ - Use `pyqtSignal` for typed signals
113
+ - Parent parameter in `__init__(self, parent: QObject | None = None)`
114
+ - Connect signals in `_connect_signals()` method
115
+ - Use `QTimer.singleShot()` for deferred actions
116
+
117
+ ## Project Structure
118
+
119
+ ```
120
+ src/usbguard_gui/
121
+ __init__.py # Package init (can export VERSION)
122
+ __main__.py # python -m entry point
123
+ app.py # Main tray application
124
+ dbus_client.py # D-Bus client for USBGuard daemon
125
+ device.py # Device model and rule parsing
126
+ device_dialog.py # Device action dialog window
127
+ device_list.py # Device list window
128
+ screensaver.py # Screensaver state monitoring
129
+ settings.py # Application settings
130
+
131
+ tests/
132
+ test_app.py # Tests for main tray application
133
+ test_device.py # Tests for device model
134
+ test_device_list.py # Tests for device list window
135
+ test_dbus_client.py # Tests for D-Bus client
136
+ test_async_api.py # Tests for async signal-based API
137
+ test_release.py # Tests for release scripts
138
+ ```
139
+
140
+ ## Testing Conventions
141
+
142
+ - Use `pytest` with `pytest-mock` and `pytest-cov`
143
+ - Test files: `tests/test_<module>.py`
144
+ - Test classes: `TestClassName` with descriptive methods
145
+ - Test methods: `test_<what_is_tested>`
146
+ - Use `pytest.mark.parametrize` for multiple test cases
147
+ - Private helper methods in tests prefixed with `_`
148
+ - Use sentinel pattern (`object()`) for special default values
149
+
150
+ ### Example Test Structure
151
+
152
+ ```python
153
+ """Tests for the device model and rule parser."""
154
+
155
+ from usbguard_gui.device import Device, DeviceTarget, parse_device_rule
156
+
157
+
158
+ class TestParseDeviceRule:
159
+ """Test rule string parsing."""
160
+
161
+ RULE_ALLOW = 'allow id 1d6b:0002 serial "..." name "..." ...'
162
+
163
+ def test_allow_rule(self):
164
+ result = parse_device_rule(self.RULE_ALLOW)
165
+ assert result["rule"] == "allow"
166
+ ```
167
+
168
+ ## Pre-commit Checklist
169
+
170
+ Before submitting changes:
171
+
172
+ 1. Run `make check` (lint + tests)
173
+ 2. Ensure all new public APIs have type hints
174
+ 3. Add tests for new functionality
175
+ 4. Update docstrings for user-facing APIs
176
+ 5. No commented-out code in final submissions
@@ -0,0 +1,196 @@
1
+ ## 0.3.1 — 2026-04-11
2
+
3
+ ### Changes since v0.3.0
4
+
5
+ - dd7565d Try integrating tags with COPR builds.
6
+ - 004266e COPR take 3.
7
+ - 393d0bf Fix spec file version sed-s.
8
+ - 2acf64f Makefile: fix RPM_VER fallback for empty git output
9
+ - d884b58 Makefile: fallback RPM_VER from __version__ if git fails
10
+ - 05617c2 Fedora COPR Makefile integration take 2.
11
+
12
+ ## 0.3.0 — 2026-04-11
13
+
14
+ ### Changes since v0.2.3
15
+
16
+ - a047831 Fix ScreenSaver inhibited detection, update dcs.
17
+ - 1b51b2a Fast-fail D-Bus scheduler calls when disconnected
18
+ - 6e1c049 Update the RPM spec file description.
19
+
20
+ ## 0.2.3 — 2026-04-11
21
+
22
+ ### Changes since v0.2.2
23
+
24
+ - 72264b5 Fix f-string without placeholders in test_app.py and improve AGENTS.md
25
+ - fbd4070 Increase HID lock delay from 3 to 4 seconds
26
+ - bfd0eac Add HID disable option, fix config names, UI polish, and bug fixes
27
+
28
+ ## 0.2.2 — 2026-04-11
29
+
30
+ ### Changes since v0.2.1
31
+
32
+ - d527c45 Add upgrade restart mechanism with SIGUSR1
33
+
34
+ ## 0.2.1 — 2026-04-11
35
+
36
+ ### Changes since v0.2.0
37
+
38
+ - 5a70f2d Fedora COPR Makefile integration?
39
+
40
+ ## 0.2.0 — 2026-04-11
41
+
42
+ ### Changes since v0.1.0
43
+
44
+ - 0aa9fd7 Release 0.2.0
45
+ - f5ed81f Add option to disable special HID device treatment
46
+ - cd2c7d5 Claude Opus fixes.
47
+ - 89d6b07 Cleanup.
48
+ - 5508d9b fix: Screen lock not triggered on HID device insertion
49
+ - f947d8a fix: Treat composite HID devices with the same security path as pure HID
50
+ - 3179df3 chore: Replace ruff format with autopep8 for formatting
51
+
52
+ ## 0.2.0 — 2026-04-11
53
+
54
+ ### Changes since v0.1.0
55
+
56
+ - f5ed81f Add option to disable special HID device treatment
57
+ - cd2c7d5 Claude Opus fixes.
58
+ - 89d6b07 Cleanup.
59
+ - 5508d9b fix: Screen lock not triggered on HID device insertion
60
+ - f947d8a fix: Treat composite HID devices with the same security path as pure HID
61
+ - 3179df3 chore: Replace ruff format with autopep8 for formatting
62
+
63
+ ## 0.1.0 — 2026-04-10
64
+
65
+ ### Changes since v0.0.13
66
+
67
+ - e00caa3 feat: Toggle device list visibility on tray icon click
68
+ - 91f6cd9 OpenCode is no Anthropic ;-)
69
+ - ebaf3ac CI: add libglvnd-egl for libEGL.so.1 required by PyQt6
70
+ - 0c2d19c Return some needed deps.
71
+ - 4fad4fa Fix tox: add pytest-qt, tox-uv; run all Pythons in CI via Fedora packages
72
+ - 567e4da fix: Fix ImportError: libGL.so.1, CI
73
+
74
+ ## 0.0.13 — 2026-04-10
75
+
76
+ ### Changes since v0.0.12
77
+
78
+ - dff22be Fix clean exit and persist device list window state
79
+
80
+ ## 0.0.12 — 2026-04-10
81
+
82
+ ### Changes since v0.0.11
83
+
84
+ - bc80337 Enforce single application instance using QLockFile
85
+ - 34bedf1 Improve devices dialog: sortable, resizable, and reorderable columns
86
+ - 2cfd0f5 Add documentation on how the application works and its architecture
87
+ - cda1066 Clean up test_device_list.py: remove unused variables and noqa comments
88
+
89
+ ## 0.0.11 — 2026-04-05
90
+
91
+ ### Changes since v0.0.10
92
+
93
+ - 0d5f69e Fix device list never populated due to two independent bugs
94
+ - 2d15e00 Fix device list refresh by using refresh ID for signal correlation
95
+ - 7f02094 Fix device list not updating due to missing pending_devices storage
96
+ - b835a3a Add tests for async signal-based API
97
+ - 520b74b Fix app.py and device_list.py for async API
98
+ - dbf08f2 Add DESIGN.md documenting the PyGObject replacement approaches
99
+ - 3f83c62 Replace PyGObject dependency with dbus-fast (QThread+asyncio)
100
+ - db4f6db Update GitHub Actions workflow to use Podman
101
+
102
+ ## 0.0.10 — 2026-04-05
103
+
104
+ ### Changes since v0.0.9
105
+
106
+ - 3a94720 fix: release.py
107
+ - 5b72884 Configure ruff to prefer compact formatting: (by Qwen3.6 Plus Free)
108
+ - 059df0f Remove empty file.
109
+ - 7af2f9c Fix medium-priority issues: (by Qwen3.6 Plus Free)
110
+ - 025d8a1 Fix high-priority issues: (by Qwen3.6 Plus Free)
111
+ - 2d8f605 Fix critical bugs: by Qwen3.6 Plus Free - release.py build_impl crash - HID stale device reference - dialog
112
+ WA_DeleteOnClose access
113
+ - ef68b4c Fix TODO.md: add trailing newline
114
+ - 3d586a3 Add TODO.md with circuit breaker pattern suggestion for future improvements
115
+ - 805d07d Improve error handling: Add logging, exponential backoff, and enhanced error context
116
+ - f4e668d Fix bug in device.py, add type hints, add __all__ exports, add pyqt6-stubs
117
+ - 4cd31a5 expand dbus_client tests to 100% coverage (by big-pickle/OpenCode)
118
+ - 3ce112f refactor release.py for testability, add comprehensive tests (by big-pickle/OpenCode)
119
+ - 81a1213 make CLAUDE.md a redirect to AGENTS.md
120
+
121
+ ## 0.0.9 — 2026-04-05
122
+
123
+ ### Changes since v0.0.8
124
+
125
+ - f604cac dynamic versioning from __init__.py (by big-pickle/OpenCode)
126
+
127
+ ## 0.0.8 — 2026-04-05
128
+
129
+ ### Changes since v0.0.7
130
+
131
+ - 6ac501c release.py: remove automatic dev version bump after release (by big-pickle/OpenCode)
132
+ - ad32718 release.py: fix version mismatch after release, format fixes, add AGENTS.md
133
+ - 797fb54 README: add credits section
134
+ - 69be8be Start 0.0.8-dev
135
+
136
+ ## 0.0.7 — 2026-04-04
137
+
138
+ ### Changes since v0.0.6
139
+
140
+ - 1e4fc6d rpm: install XDG autostart entry for session auto-start
141
+ - e983a43 Start 0.0.7-dev
142
+
143
+ ## 0.0.6 — 2026-04-04
144
+
145
+ ### Changes since v0.0.5
146
+
147
+ - 0be0a9c app: use usbguard_gui theme icon with fallback to source-tree SVG
148
+ - e41aa82 device_list: remove permanent rule when demoting to temporary allow
149
+ - 85808c5 device_list: refresh immediately after context-menu action
150
+ - b347e57 device_list: blue background for temporarily allowed devices
151
+ - ebd9daa app: fix spurious device dialogs for allowed/non-insert events
152
+ - dbb035c app: add detailed debug logging for device presence events
153
+ - e638935 Makefile: use python -m usbguard_gui in run target
154
+ - df30bd7 app: fix spurious device dialogs for allowed/non-insert events
155
+ - 713e99b Start 0.0.6-dev
156
+
157
+ ## 0.0.5 — 2026-04-04
158
+
159
+ ### Changes since v0.0.4
160
+
161
+ - 96bdf78 app: use usbguard_gui theme icon with fallback
162
+ - 8c2b9d7 Start 0.0.5-dev
163
+
164
+ ## 0.0.4 — 2026-04-04
165
+
166
+ ### Changes since v0.0.3
167
+
168
+ - 28396f0 Add whl_local/.gitignore
169
+ - 49fbc4d rpm: fix %preun, drop %postun and unused systemd macros
170
+ - 7b50b6f rpm: force-enable usbguard-dbus.service on install
171
+ - f40e501 Start 0.0.4-dev
172
+
173
+ ## 0.0.3 — 2026-04-04
174
+
175
+ ### Changes since v0.0.2
176
+
177
+ - 49c55d1 device_list: use dark row colors with white text
178
+ - ff03739 rpm: require usbguard-dbus, enable its service on install
179
+ - 552a629 Fix duplicate signals, auth errors, add polkit rule
180
+ - 58e907d Start 0.0.3-dev
181
+
182
+ ## 0.0.2 — 2026-04-04
183
+
184
+ ### Changes since v0.0.1
185
+
186
+ - b2408ca Add RPM packaging
187
+ - 430cd16 Start 0.0.2-dev
188
+
189
+ ## 0.0.1 — 2026-04-04
190
+
191
+ ### Changes since beginning
192
+
193
+ - a071a82 Rename to usbguard_gui, python things...
194
+ - 76f5233 Add release.py.
195
+ - 83838a4 Add Makefile
196
+ - adc75c8 Initial implementation of usbguard_gui
@@ -0,0 +1,3 @@
1
+ # usbguard_gui
2
+
3
+ See [AGENTS.md](AGENTS.md) for full instructions.
@@ -0,0 +1,203 @@
1
+ # Replacing PyGObject with dbus-fast
2
+
3
+ This document describes the migration from `dasbus + PyGObject` to `dbus-fast` to eliminate the PyGObject dependency.
4
+
5
+ ## Motivation
6
+
7
+ dasbus uses PyGObject (gi) internally for GLib type system integration. Even though the application uses PyQt6 (not
8
+ GTK), dasbus requires GLib's type introspection layer. PyGObject is a heavy dependency that many systems may not have
9
+ pre-installed.
10
+
11
+ ## Solution: dbus-fast
12
+
13
+ `dbus-fast` is a pure-Python asyncio D-Bus library with no GLib dependency. Two integration approaches were explored:
14
+
15
+ ### Branch 1: `dev-glib`
16
+
17
+ Uses `dbus_fast.glib` which integrates with GLib's main loop.
18
+
19
+ ### Branch 2: `dev-qthread` (This branch)
20
+
21
+ Uses `dbus_fast.aio` in a dedicated QThread with its own asyncio event loop.
22
+
23
+ ---
24
+
25
+ ## Implementation Details
26
+
27
+ ### Dependencies Changed
28
+
29
+ **Before:**
30
+
31
+ ```toml
32
+ dependencies = [
33
+ "PyQt6>=6.5",
34
+ "dasbus>=1.7",
35
+ "PyGObject>=3.42",
36
+ ]
37
+ ```
38
+
39
+ **After:**
40
+
41
+ ```toml
42
+ dependencies = [
43
+ "PyQt6>=6.5",
44
+ "dbus-fast>=1.0",
45
+ ]
46
+ ```
47
+
48
+ ### New Files
49
+
50
+ ```
51
+ src/usbguard_gui/introspection/
52
+ ├── org.usbguard.Devices1.xml # USBGuard Devices interface
53
+ ├── org.usbguard.Policy1.xml # USBGuard Policy interface
54
+ └── org.freedesktop.ScreenSaver.xml # ScreenSaver interface
55
+ ```
56
+
57
+ ### API Changes
58
+
59
+ | dasbus | dbus-fast |
60
+ |-------------------------------|--------------------------------------------------------------|
61
+ | `SystemMessageBus()` | `MessageBus(bus_type=BusType.SYSTEM)` |
62
+ | `bus.get_proxy(name, path)` | `bus.get_proxy_object(name, path, xml).get_interface(iface)` |
63
+ | `proxy.listDevices()` | `proxy.call_list_devices()` |
64
+ | `proxy.Signal.connect(cb)` | `proxy.on_signal(cb)` |
65
+ | `proxy.Signal.disconnect(cb)` | `proxy.off_signal(cb)` |
66
+ | `DBusError.error_name` | `DBusError.type` |
67
+
68
+ ### Error Handling
69
+
70
+ ```python
71
+ # dasbus
72
+ def _is_permission_error(e: DBusError) -> bool:
73
+ name = getattr(e, "error_name", "") or ""
74
+
75
+
76
+ # dbus-fast
77
+ def _is_permission_error(e: DBusError) -> bool:
78
+ error_name = getattr(e, "type", "") or ""
79
+ ```
80
+
81
+ ---
82
+
83
+ ## This Branch: QThread + asyncio
84
+
85
+ This implementation uses a dedicated QThread running an asyncio event loop to handle D-Bus communication.
86
+
87
+ ### Architecture
88
+
89
+ ```
90
+ USBGuardClient (QObject) _DBusThread (QThread)
91
+ ├── Public API (unchanged) ├── Runs asyncio event loop
92
+ ├── Receives results via signals ├── Connects to D-Bus
93
+ └── Forwards signals to GUI ├── Schedules commands from queue
94
+ └── Emits results as Qt signals
95
+
96
+ ScreensaverMonitor (QObject) _ScreensaverThread (QThread)
97
+ ├── Public API (unchanged) ├── Runs asyncio event loop
98
+ ├── Receives active_changed via signal └── Connects to session D-Bus
99
+ ```
100
+
101
+ ### Key Components
102
+
103
+ **`_DBusThread`**: QThread subclass that:
104
+
105
+ - Creates a new asyncio event loop
106
+ - Connects to system D-Bus using `dbus_fast.aio.MessageBus`
107
+ - Maintains proxy objects for USBGuard interfaces
108
+ - Subscribes to D-Bus signals (DevicePresenceChanged, DevicePolicyChanged)
109
+ - Processes commands from a queue (thread-safe communication)
110
+ - Emits Qt signals for device events and method results
111
+
112
+ **Command Queue Pattern**:
113
+
114
+ ```python
115
+ def list_devices(self, query: str = "match") -> None:
116
+ if self._devices_iface and self._loop:
117
+ self._schedule(self._do_list_devices(query))
118
+
119
+
120
+ def _schedule(self, coro: asyncio.coroutine) -> None:
121
+ if self._loop and self._running:
122
+ self._loop.call_soon_threadsafe(asyncio.ensure_future, coro)
123
+ ```
124
+
125
+ ### API Changes in This Branch
126
+
127
+ Methods are now **asynchronous fire-and-forget**:
128
+
129
+ - `client.list_devices()` → returns via `list_devices_result` signal
130
+ - `client.apply_device_policy()` → returns via `apply_policy_result` signal
131
+ - `client.list_rules()` → returns via `list_rules_result` signal
132
+ - `client.remove_rule()` → returns via `remove_rule_result` signal
133
+
134
+ ### New Signals Added
135
+
136
+ | Signal | Parameters | Description |
137
+ |-----------------------|-------------------------|---------------------------------|
138
+ | `list_devices_result` | `list[Device]` | Result of list_devices() |
139
+ | `apply_policy_result` | `int \| None` | Result of apply_device_policy() |
140
+ | `list_rules_result` | `list[tuple[int, str]]` | Result of list_rules() |
141
+ | `remove_rule_result` | `bool` | Result of remove_rule() |
142
+
143
+ ---
144
+
145
+ ## Approach Comparison
146
+
147
+ ### dev-glib: GLib Integration
148
+
149
+ **Pros:**
150
+
151
+ - Simpler implementation
152
+ - No additional threads needed
153
+ - Direct signal/callback integration with Qt
154
+ - Synchronous methods (return values directly)
155
+
156
+ **Cons:**
157
+
158
+ - Still requires GLib dependency
159
+
160
+ ### dev-qthread: QThread + asyncio (This branch)
161
+
162
+ **Pros:**
163
+
164
+ - Pure asyncio, no GLib dependency
165
+ - Clear separation of async and sync code
166
+ - More control over event loop
167
+
168
+ **Cons:**
169
+
170
+ - More complex architecture
171
+ - Command queue pattern required for thread-safe communication
172
+ - Asynchronous methods become fire-and-forget (results via signals)
173
+ - Additional Qt signals needed for results
174
+
175
+ ---
176
+
177
+ ## Files Modified
178
+
179
+ | File | Changes |
180
+ |-----------------------------|-----------------------------------------------------------|
181
+ | `pyproject.toml` | Replace dasbus + PyGObject with dbus-fast |
182
+ | `dbus_client.py` | Add `_DBusThread` class, refactor to async pattern |
183
+ | `screensaver.py` | Add `_ScreensaverThread` class, refactor to async pattern |
184
+ | `tests/test_dbus_client.py` | Update mocks for new architecture |
185
+
186
+ ## Verification
187
+
188
+ Both branches pass:
189
+
190
+ - All unit tests (75 tests)
191
+ - Lint checks (ruff)
192
+ - Format checks (ruff)
193
+
194
+ ## Recommendation
195
+
196
+ **Use `dev-glib`** for production. It's simpler, has fewer components, and the GLib dependency is already present on
197
+ most Linux systems that would run USBGuard.
198
+
199
+ Use `dev-qthread` only if:
200
+
201
+ - GLib dependency must be completely avoided
202
+ - Maximum isolation of async code is required
203
+ - Debugging async behavior is easier with a separate thread