airmouse-core 2.0.0__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 (38) hide show
  1. airmouse_core-2.0.0/LICENSE +21 -0
  2. airmouse_core-2.0.0/PKG-INFO +247 -0
  3. airmouse_core-2.0.0/README.md +204 -0
  4. airmouse_core-2.0.0/airmouse/__init__.py +21 -0
  5. airmouse_core-2.0.0/airmouse/cli.py +201 -0
  6. airmouse_core-2.0.0/airmouse/py.typed +1 -0
  7. airmouse_core-2.0.0/airmouse/server.py +442 -0
  8. airmouse_core-2.0.0/airmouse_core.egg-info/PKG-INFO +247 -0
  9. airmouse_core-2.0.0/airmouse_core.egg-info/SOURCES.txt +36 -0
  10. airmouse_core-2.0.0/airmouse_core.egg-info/dependency_links.txt +1 -0
  11. airmouse_core-2.0.0/airmouse_core.egg-info/entry_points.txt +2 -0
  12. airmouse_core-2.0.0/airmouse_core.egg-info/requires.txt +13 -0
  13. airmouse_core-2.0.0/airmouse_core.egg-info/top_level.txt +5 -0
  14. airmouse_core-2.0.0/core/actuator.py +64 -0
  15. airmouse_core-2.0.0/core/jitter_buffer.py +91 -0
  16. airmouse_core-2.0.0/core/mouse_controller.py +69 -0
  17. airmouse_core-2.0.0/core/one_euro.py +67 -0
  18. airmouse_core-2.0.0/network/protocol.py +286 -0
  19. airmouse_core-2.0.0/network/tcp_server.py +214 -0
  20. airmouse_core-2.0.0/network/udp_discovery.py +102 -0
  21. airmouse_core-2.0.0/network/ws_server.py +128 -0
  22. airmouse_core-2.0.0/pyproject.toml +80 -0
  23. airmouse_core-2.0.0/security/auth.py +132 -0
  24. airmouse_core-2.0.0/security/tls_manager.py +145 -0
  25. airmouse_core-2.0.0/setup.cfg +4 -0
  26. airmouse_core-2.0.0/tests/test_actuator_and_buffer.py +67 -0
  27. airmouse_core-2.0.0/tests/test_airmouse_server.py +173 -0
  28. airmouse_core-2.0.0/tests/test_auth.py +23 -0
  29. airmouse_core-2.0.0/tests/test_cli.py +59 -0
  30. airmouse_core-2.0.0/tests/test_filters.py +29 -0
  31. airmouse_core-2.0.0/tests/test_integration.py +537 -0
  32. airmouse_core-2.0.0/tests/test_protocol.py +96 -0
  33. airmouse_core-2.0.0/tests/test_protocol_edge_cases.py +160 -0
  34. airmouse_core-2.0.0/tests/test_tls_manager.py +47 -0
  35. airmouse_core-2.0.0/tests/test_udp_discovery.py +94 -0
  36. airmouse_core-2.0.0/tests/test_ws_server.py +162 -0
  37. airmouse_core-2.0.0/ui/app.py +210 -0
  38. airmouse_core-2.0.0/ui/tray_manager.py +50 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AirMouse Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.4
2
+ Name: airmouse-core
3
+ Version: 2.0.0
4
+ Summary: High-performance, secure Python server and protocol engine for AirMouse remote input devices
5
+ Author-email: AirMouse Team <dev@airmouse.io>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/AirMouse/AirMouse
8
+ Project-URL: Documentation, https://github.com/AirMouse/AirMouse/tree/main/pc/airmouse_py
9
+ Project-URL: Repository, https://github.com/AirMouse/AirMouse.git
10
+ Project-URL: Issues, https://github.com/AirMouse/AirMouse/issues
11
+ Project-URL: Changelog, https://github.com/AirMouse/AirMouse/blob/main/CHANGELOG.md
12
+ Keywords: airmouse,remote-mouse,input-devices,gyroscope,accelerometer,websocket,tcp,udp-discovery,one-euro-filter,jitter-buffer,zero-latency
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: System :: Hardware :: Hardware Drivers
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.9
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: pydantic>=2.0.0
31
+ Requires-Dist: pynput>=1.7.6
32
+ Requires-Dist: cryptography>=42.0.0
33
+ Requires-Dist: websockets>=12.0
34
+ Requires-Dist: qrcode[pil]>=7.4.2
35
+ Requires-Dist: pillow>=10.0.0
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
38
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
39
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
40
+ Requires-Dist: build>=1.0.0; extra == "dev"
41
+ Requires-Dist: twine>=5.0.0; extra == "dev"
42
+ Dynamic: license-file
43
+
44
+ <div align="center">
45
+
46
+ # 🖱️ AirMouse Core (`airmouse-core`)
47
+
48
+ [![PyPI version](https://img.shields.io/pypi/v/airmouse-core.svg?color=blue)](https://pypi.org/project/airmouse-core/)
49
+ [![Python Versions](https://img.shields.io/pypi/pyversions/airmouse-core.svg)](https://pypi.org/project/airmouse-core/)
50
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
51
+ [![Code Style](https://img.shields.io/badge/code%20style-PEP%20561-green.svg)](https://peps.python.org/pep-0561/)
52
+ [![Tests Coverage](https://img.shields.io/badge/coverage-88%25-brightgreen.svg)]()
53
+
54
+ **High-performance, ultra-low-latency remote mouse and input server engine for Python.**
55
+ Seamlessly turns Android, iOS, or web client devices into responsive air mice with hardware-grade cursor smoothing, robust replay-attack mitigation, and TLS 1.2+ encryption.
56
+
57
+ [Key Features](#-key-features) • [Installation](#-installation) • [Quick Start CLI](#-quick-start-cli) • [Python API](#-python-api) • [Architecture](#-architecture) • [Protocol V2](#-protocol-v2-specification) • [Testing](#-test-suite)
58
+
59
+ </div>
60
+
61
+ ---
62
+
63
+ ## ⚡ Key Features
64
+
65
+ - 🔒 **Enterprise-Grade TLS Security**:
66
+ - Full TLS 1.2+ encryption on both TCP and WebSocket connections.
67
+ - Automatic self-signed X.509 certificate provisioning with persistent key storage (`0600` permissions) to prevent TLS pinning breaks on server reboot.
68
+ - 📱 **Zero-Friction Terminal QR Pairing**:
69
+ - Out-of-the-box CLI displays ANSI QR codes right in your terminal for 1-second camera scan pairing from mobile devices.
70
+ - 🎯 **1€ (One Euro) Adaptive Smoothing**:
71
+ - Implements Casiez et al. adaptive low-pass filter algorithm to eliminate micro-jitter during slow precision movements while eliminating lag during fast sweeps.
72
+ - 🌐 **LAN Auto-Discovery**:
73
+ - Multithreaded UDP broadcast responder with cryptographic challenge-response nonces for instantaneous zero-configuration LAN discovery.
74
+ - 🧵 **Thread-Safe Actuation Decoupling**:
75
+ - Completely decouples high-frequency network I/O (`asyncio`) from OS windowing/mouse controller drivers (`pynput`) via an internal thread-safe queue.
76
+ - 🛡️ **Anti-Replay Attack Protection**:
77
+ - Enforces sliding timestamp freshness windows and per-session monotonic sequence numbering.
78
+ - 📦 **PEP 561 Compliant**:
79
+ - Full typing support with `py.typed` marker for static analysis with mypy, pyright, and modern IDE auto-complete.
80
+
81
+ ---
82
+
83
+ ## 📥 Installation
84
+
85
+ Install the stable release directly from PyPI:
86
+
87
+ ```bash
88
+ pip install airmouse-core
89
+ ```
90
+
91
+ Or install with development dependencies:
92
+
93
+ ```bash
94
+ pip install airmouse-core[dev]
95
+ ```
96
+
97
+ ---
98
+
99
+ ## 🚀 Quick Start CLI
100
+
101
+ Run the production server directly from the command line:
102
+
103
+ ```bash
104
+ airmouse-server --port 8080 --ws-port 8081 --udp-port 8083 --qr
105
+ ```
106
+
107
+ ### CLI Options:
108
+
109
+ | Flag | Description | Default |
110
+ |------|-------------|---------|
111
+ | `--host` | Bind interface address | `0.0.0.0` |
112
+ | `--port` | TCP listening port | `8080` |
113
+ | `--ws-port` | WebSocket listening port | `8081` |
114
+ | `--udp-port` | UDP LAN discovery broadcast port | `8083` |
115
+ | `--qr` | Render terminal ANSI QR code for mobile pairing | `False` |
116
+ | `--no-tls` | Disable TLS encryption | `False` (TLS enabled) |
117
+ | `--no-smoothing` | Disable 1€ adaptive filter | `False` (Smoothing enabled) |
118
+ | `--sensitivity` | Mouse cursor speed multiplier | `1.0` |
119
+ | `--name` | Friendly server display name | `AirMouse Server` |
120
+
121
+ ---
122
+
123
+ ## 💻 Python API
124
+
125
+ `airmouse-core` exposes a clean, intuitive public API:
126
+
127
+ ```python
128
+ import asyncio
129
+ from airmouse import (
130
+ AirMouseServer,
131
+ OneEuroFilter,
132
+ UDPJitterBuffer,
133
+ AuthManager,
134
+ ProtocolParser,
135
+ )
136
+
137
+ async def main():
138
+ # 1. Initialize server
139
+ server = AirMouseServer(
140
+ host="0.0.0.0",
141
+ tcp_port=8080,
142
+ ws_port=8081,
143
+ udp_port=8083,
144
+ enable_tls=True,
145
+ sensitivity=1.2,
146
+ smoothing=True,
147
+ )
148
+
149
+ # 2. Register lifecycle and input event callbacks
150
+ server.on_connect(lambda session_id: print(f"Client connected: {session_id}"))
151
+ server.on_disconnect(lambda session_id: print(f"Client disconnected: {session_id}"))
152
+ server.on_move(lambda dx, dy: print(f"Motion: dx={dx:.2f}, dy={dy:.2f}"))
153
+ server.on_click(lambda button: print(f"Click: {button}"))
154
+
155
+ # 3. Start server
156
+ await server.start()
157
+ print("AirMouse Server is running. Press Ctrl+C to exit.")
158
+
159
+ try:
160
+ await asyncio.Event().wait()
161
+ finally:
162
+ await server.stop()
163
+
164
+ if __name__ == "__main__":
165
+ asyncio.run(main())
166
+ ```
167
+
168
+ ---
169
+
170
+ ## 🏗️ Architecture
171
+
172
+ ```
173
+ Mobile / Web Client (Android / iOS / Browser)
174
+ │
175
+ ┌───────────┴───────────┐
176
+ ▼ ▼
177
+ [TCP TLS 1.2+] [WebSocket TLS]
178
+ │ │
179
+ └───────────┬───────────┘
180
+ │ (Asyncio Network Loop)
181
+ ▼
182
+ ProtocolParser (V2)
183
+ [Replay & Bounds Validation]
184
+ │
185
+ ▼
186
+ Thread-Safe Command Queue
187
+ │
188
+ ▼
189
+ MouseActuatorThread
190
+ [OneEuroFilter Smoothing]
191
+ │
192
+ ▼
193
+ OS Cursor Actuation (pynput)
194
+ ```
195
+
196
+ ---
197
+
198
+ ## 📡 Protocol V2 Specification
199
+
200
+ Every wire packet follows the strict canonical V2 envelope:
201
+
202
+ ```json
203
+ {
204
+ "v": 2,
205
+ "type": "move",
206
+ "seq": 1024,
207
+ "ts": 1729771200000,
208
+ "id": "optional-request-id",
209
+ "payload": {
210
+ "dx": 12.5,
211
+ "dy": -3.2,
212
+ "dt": 0.016
213
+ }
214
+ }
215
+ ```
216
+
217
+ ### Supported Message Types:
218
+ - **`hello`**: Initial handshake pairing request carrying HMAC token.
219
+ - **`welcome`**: Server approval response.
220
+ - **`move`**: Relative mouse coordinates (`dx`, `dy`, `dt`).
221
+ - **`click`**: Mouse button clicks (`left`, `right`, `middle`, `double`).
222
+ - **`scroll`**: Wheel scroll offset (`delta`).
223
+ - **`key`**: Keyboard input events (`key`, `modifiers`).
224
+ - **`discover` / `offer`**: UDP LAN pairing handshake.
225
+ - **`ping` / `pong`**: Heartbeat and connection keep-alive.
226
+
227
+ ---
228
+
229
+ ## 🧪 Test Suite
230
+
231
+ The test suite covers 100% of the core functionality with high test coverage:
232
+
233
+ ```bash
234
+ cd pc/airmouse_py
235
+ pytest --cov=. --cov-report=term-missing tests
236
+ ```
237
+
238
+ **Results:**
239
+ - **46 passing unit and integration tests**
240
+ - **88% code coverage**
241
+ - Automated testing for replay protection, crypto HMAC validation, TLS X.509 generation, and socket concurrency.
242
+
243
+ ---
244
+
245
+ ## 📄 License
246
+
247
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,204 @@
1
+ <div align="center">
2
+
3
+ # 🖱️ AirMouse Core (`airmouse-core`)
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/airmouse-core.svg?color=blue)](https://pypi.org/project/airmouse-core/)
6
+ [![Python Versions](https://img.shields.io/pypi/pyversions/airmouse-core.svg)](https://pypi.org/project/airmouse-core/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![Code Style](https://img.shields.io/badge/code%20style-PEP%20561-green.svg)](https://peps.python.org/pep-0561/)
9
+ [![Tests Coverage](https://img.shields.io/badge/coverage-88%25-brightgreen.svg)]()
10
+
11
+ **High-performance, ultra-low-latency remote mouse and input server engine for Python.**
12
+ Seamlessly turns Android, iOS, or web client devices into responsive air mice with hardware-grade cursor smoothing, robust replay-attack mitigation, and TLS 1.2+ encryption.
13
+
14
+ [Key Features](#-key-features) • [Installation](#-installation) • [Quick Start CLI](#-quick-start-cli) • [Python API](#-python-api) • [Architecture](#-architecture) • [Protocol V2](#-protocol-v2-specification) • [Testing](#-test-suite)
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ ## ⚡ Key Features
21
+
22
+ - 🔒 **Enterprise-Grade TLS Security**:
23
+ - Full TLS 1.2+ encryption on both TCP and WebSocket connections.
24
+ - Automatic self-signed X.509 certificate provisioning with persistent key storage (`0600` permissions) to prevent TLS pinning breaks on server reboot.
25
+ - 📱 **Zero-Friction Terminal QR Pairing**:
26
+ - Out-of-the-box CLI displays ANSI QR codes right in your terminal for 1-second camera scan pairing from mobile devices.
27
+ - 🎯 **1€ (One Euro) Adaptive Smoothing**:
28
+ - Implements Casiez et al. adaptive low-pass filter algorithm to eliminate micro-jitter during slow precision movements while eliminating lag during fast sweeps.
29
+ - 🌐 **LAN Auto-Discovery**:
30
+ - Multithreaded UDP broadcast responder with cryptographic challenge-response nonces for instantaneous zero-configuration LAN discovery.
31
+ - 🧵 **Thread-Safe Actuation Decoupling**:
32
+ - Completely decouples high-frequency network I/O (`asyncio`) from OS windowing/mouse controller drivers (`pynput`) via an internal thread-safe queue.
33
+ - 🛡️ **Anti-Replay Attack Protection**:
34
+ - Enforces sliding timestamp freshness windows and per-session monotonic sequence numbering.
35
+ - 📦 **PEP 561 Compliant**:
36
+ - Full typing support with `py.typed` marker for static analysis with mypy, pyright, and modern IDE auto-complete.
37
+
38
+ ---
39
+
40
+ ## 📥 Installation
41
+
42
+ Install the stable release directly from PyPI:
43
+
44
+ ```bash
45
+ pip install airmouse-core
46
+ ```
47
+
48
+ Or install with development dependencies:
49
+
50
+ ```bash
51
+ pip install airmouse-core[dev]
52
+ ```
53
+
54
+ ---
55
+
56
+ ## 🚀 Quick Start CLI
57
+
58
+ Run the production server directly from the command line:
59
+
60
+ ```bash
61
+ airmouse-server --port 8080 --ws-port 8081 --udp-port 8083 --qr
62
+ ```
63
+
64
+ ### CLI Options:
65
+
66
+ | Flag | Description | Default |
67
+ |------|-------------|---------|
68
+ | `--host` | Bind interface address | `0.0.0.0` |
69
+ | `--port` | TCP listening port | `8080` |
70
+ | `--ws-port` | WebSocket listening port | `8081` |
71
+ | `--udp-port` | UDP LAN discovery broadcast port | `8083` |
72
+ | `--qr` | Render terminal ANSI QR code for mobile pairing | `False` |
73
+ | `--no-tls` | Disable TLS encryption | `False` (TLS enabled) |
74
+ | `--no-smoothing` | Disable 1€ adaptive filter | `False` (Smoothing enabled) |
75
+ | `--sensitivity` | Mouse cursor speed multiplier | `1.0` |
76
+ | `--name` | Friendly server display name | `AirMouse Server` |
77
+
78
+ ---
79
+
80
+ ## 💻 Python API
81
+
82
+ `airmouse-core` exposes a clean, intuitive public API:
83
+
84
+ ```python
85
+ import asyncio
86
+ from airmouse import (
87
+ AirMouseServer,
88
+ OneEuroFilter,
89
+ UDPJitterBuffer,
90
+ AuthManager,
91
+ ProtocolParser,
92
+ )
93
+
94
+ async def main():
95
+ # 1. Initialize server
96
+ server = AirMouseServer(
97
+ host="0.0.0.0",
98
+ tcp_port=8080,
99
+ ws_port=8081,
100
+ udp_port=8083,
101
+ enable_tls=True,
102
+ sensitivity=1.2,
103
+ smoothing=True,
104
+ )
105
+
106
+ # 2. Register lifecycle and input event callbacks
107
+ server.on_connect(lambda session_id: print(f"Client connected: {session_id}"))
108
+ server.on_disconnect(lambda session_id: print(f"Client disconnected: {session_id}"))
109
+ server.on_move(lambda dx, dy: print(f"Motion: dx={dx:.2f}, dy={dy:.2f}"))
110
+ server.on_click(lambda button: print(f"Click: {button}"))
111
+
112
+ # 3. Start server
113
+ await server.start()
114
+ print("AirMouse Server is running. Press Ctrl+C to exit.")
115
+
116
+ try:
117
+ await asyncio.Event().wait()
118
+ finally:
119
+ await server.stop()
120
+
121
+ if __name__ == "__main__":
122
+ asyncio.run(main())
123
+ ```
124
+
125
+ ---
126
+
127
+ ## 🏗️ Architecture
128
+
129
+ ```
130
+ Mobile / Web Client (Android / iOS / Browser)
131
+ │
132
+ ┌───────────┴───────────┐
133
+ ▼ ▼
134
+ [TCP TLS 1.2+] [WebSocket TLS]
135
+ │ │
136
+ └───────────┬───────────┘
137
+ │ (Asyncio Network Loop)
138
+ ▼
139
+ ProtocolParser (V2)
140
+ [Replay & Bounds Validation]
141
+ │
142
+ ▼
143
+ Thread-Safe Command Queue
144
+ │
145
+ ▼
146
+ MouseActuatorThread
147
+ [OneEuroFilter Smoothing]
148
+ │
149
+ ▼
150
+ OS Cursor Actuation (pynput)
151
+ ```
152
+
153
+ ---
154
+
155
+ ## 📡 Protocol V2 Specification
156
+
157
+ Every wire packet follows the strict canonical V2 envelope:
158
+
159
+ ```json
160
+ {
161
+ "v": 2,
162
+ "type": "move",
163
+ "seq": 1024,
164
+ "ts": 1729771200000,
165
+ "id": "optional-request-id",
166
+ "payload": {
167
+ "dx": 12.5,
168
+ "dy": -3.2,
169
+ "dt": 0.016
170
+ }
171
+ }
172
+ ```
173
+
174
+ ### Supported Message Types:
175
+ - **`hello`**: Initial handshake pairing request carrying HMAC token.
176
+ - **`welcome`**: Server approval response.
177
+ - **`move`**: Relative mouse coordinates (`dx`, `dy`, `dt`).
178
+ - **`click`**: Mouse button clicks (`left`, `right`, `middle`, `double`).
179
+ - **`scroll`**: Wheel scroll offset (`delta`).
180
+ - **`key`**: Keyboard input events (`key`, `modifiers`).
181
+ - **`discover` / `offer`**: UDP LAN pairing handshake.
182
+ - **`ping` / `pong`**: Heartbeat and connection keep-alive.
183
+
184
+ ---
185
+
186
+ ## 🧪 Test Suite
187
+
188
+ The test suite covers 100% of the core functionality with high test coverage:
189
+
190
+ ```bash
191
+ cd pc/airmouse_py
192
+ pytest --cov=. --cov-report=term-missing tests
193
+ ```
194
+
195
+ **Results:**
196
+ - **46 passing unit and integration tests**
197
+ - **88% code coverage**
198
+ - Automated testing for replay protection, crypto HMAC validation, TLS X.509 generation, and socket concurrency.
199
+
200
+ ---
201
+
202
+ ## 📄 License
203
+
204
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,21 @@
1
+ """
2
+ AirMouse Core Library
3
+ ~~~~~~~~~~~~~~~~~~~~~
4
+ Production-grade Python package for AirMouse servers and input processing.
5
+ """
6
+
7
+ from core.jitter_buffer import UDPJitterBuffer
8
+ from core.one_euro import OneEuroFilter
9
+ from network.protocol import ProtocolParser
10
+ from security.auth import AuthManager
11
+ from airmouse.server import AirMouseServer
12
+
13
+ __version__ = "2.0.0"
14
+
15
+ __all__ = [
16
+ "AirMouseServer",
17
+ "OneEuroFilter",
18
+ "UDPJitterBuffer",
19
+ "AuthManager",
20
+ "ProtocolParser",
21
+ ]
@@ -0,0 +1,201 @@
1
+ """
2
+ airmouse.cli
3
+ ------------
4
+ Command Line Interface for AirMouse server.
5
+ Generates terminal QR code for zero-friction client pairing and runs the server out-of-the-box.
6
+ """
7
+
8
+ import argparse
9
+ import asyncio
10
+ import io
11
+ import json
12
+ import logging
13
+ import os
14
+ import signal
15
+ import socket
16
+ import sys
17
+ from typing import Optional
18
+
19
+ import qrcode
20
+
21
+ from airmouse.server import AirMouseServer
22
+ from security.auth import AuthManager
23
+
24
+ logging.basicConfig(
25
+ level=logging.INFO,
26
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
27
+ )
28
+ logger = logging.getLogger("AirMouseCLI")
29
+
30
+
31
+ def get_local_ip() -> str:
32
+ """Best-effort discovery of non-loopback LAN IP address."""
33
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
34
+ try:
35
+ # Does not actually connect but picks the outgoing route interface
36
+ s.connect(("8.8.8.8", 80))
37
+ ip = s.getsockname()[0]
38
+ except Exception:
39
+ ip = "127.0.0.1"
40
+ finally:
41
+ s.close()
42
+ return ip
43
+
44
+
45
+ def display_terminal_qr(data: str) -> None:
46
+ """Prints an ASCII/ANSI QR code directly in the terminal."""
47
+ qr = qrcode.QRCode(
48
+ version=1,
49
+ error_correction=qrcode.constants.ERROR_CORRECT_L,
50
+ box_size=1,
51
+ border=1,
52
+ )
53
+ qr.add_data(data)
54
+ qr.make(fit=True)
55
+
56
+ f = io.StringIO()
57
+ qr.print_ascii(out=f, invert=True)
58
+ f.seek(0)
59
+ print(f.read())
60
+
61
+
62
+ def parse_args(argv=None) -> argparse.Namespace:
63
+ parser = argparse.ArgumentParser(
64
+ prog="airmouse-server",
65
+ description="AirMouse Server - Zero-latency, TLS-secured remote input server",
66
+ )
67
+ parser.add_argument(
68
+ "--host",
69
+ type=str,
70
+ default="0.0.0.0",
71
+ help="IP address to bind to (default: 0.0.0.0)",
72
+ )
73
+ parser.add_argument(
74
+ "--port",
75
+ type=int,
76
+ default=8080,
77
+ help="TCP listening port (default: 8080)",
78
+ )
79
+ parser.add_argument(
80
+ "--ws-port",
81
+ type=int,
82
+ default=8081,
83
+ help="WebSocket listening port (default: 8081)",
84
+ )
85
+ parser.add_argument(
86
+ "--udp-port",
87
+ type=int,
88
+ default=8083,
89
+ help="UDP discovery broadcast listening port (default: 8083)",
90
+ )
91
+ parser.add_argument(
92
+ "--no-tls",
93
+ action="store_true",
94
+ help="Disable TLS encryption (TLS 1.2+ is enabled by default)",
95
+ )
96
+ parser.add_argument(
97
+ "--no-smoothing",
98
+ action="store_true",
99
+ help="Disable 1€ adaptive low-pass filter smoothing",
100
+ )
101
+ parser.add_argument(
102
+ "--sensitivity",
103
+ type=float,
104
+ default=1.0,
105
+ help="Mouse speed multiplier (default: 1.0)",
106
+ )
107
+ parser.add_argument(
108
+ "--qr",
109
+ action="store_true",
110
+ help="Display terminal QR code for instant Android client pairing",
111
+ )
112
+ parser.add_argument(
113
+ "--name",
114
+ type=str,
115
+ default="AirMouse Server",
116
+ help="Friendly server display name",
117
+ )
118
+ return parser.parse_args(argv)
119
+
120
+
121
+ async def run_server(args: argparse.Namespace) -> None:
122
+ auth_mgr = AuthManager()
123
+ token = auth_mgr.generate_token("cli_client")
124
+ lan_ip = get_local_ip()
125
+
126
+ enable_tls = not args.no_tls
127
+
128
+ pairing_payload = {
129
+ "host": lan_ip,
130
+ "tcp_port": args.port,
131
+ "ws_port": args.ws_port,
132
+ "token": token,
133
+ "tls": enable_tls,
134
+ "name": args.name,
135
+ }
136
+ pairing_json = json.dumps(pairing_payload)
137
+
138
+ print("=" * 60)
139
+ print(f" {args.name}")
140
+ print(f" LAN Address: {lan_ip}")
141
+ print(f" TCP Port : {args.port} (TLS: {enable_tls})")
142
+ print(f" WS Port : {args.ws_port} (TLS: {enable_tls})")
143
+ print(f" UDP Port : {args.udp_port}")
144
+ print(f" Pairing Token: {token}")
145
+ print("=" * 60)
146
+
147
+ if args.qr:
148
+ print("\nScan this QR code with the AirMouse Android App to pair:\n")
149
+ try:
150
+ display_terminal_qr(pairing_json)
151
+ except Exception as e:
152
+ logger.warning(f"Could not print QR code to terminal: {e}")
153
+
154
+ server = AirMouseServer(
155
+ host=args.host,
156
+ tcp_port=args.port,
157
+ ws_port=args.ws_port,
158
+ udp_port=args.udp_port,
159
+ enable_tls=enable_tls,
160
+ auth_manager=auth_mgr,
161
+ sensitivity=args.sensitivity,
162
+ smoothing=not args.no_smoothing,
163
+ server_name=args.name,
164
+ )
165
+
166
+ server.on_connect(lambda s_id: logger.info(f"[Event] Client Connected: {s_id}"))
167
+ server.on_disconnect(lambda s_id: logger.info(f"[Event] Client Disconnected: {s_id}"))
168
+ server.on_click(lambda btn: logger.debug(f"[Event] Mouse Click: {btn}"))
169
+
170
+ await server.start()
171
+ logger.info("AirMouse server started successfully and ready for connections.")
172
+
173
+ stop_event = asyncio.Event()
174
+
175
+ def _shutdown():
176
+ logger.info("Shutdown signal received, shutting down gracefully...")
177
+ stop_event.set()
178
+
179
+ for sig in (signal.SIGINT, signal.SIGTERM):
180
+ try:
181
+ asyncio.get_running_loop().add_signal_handler(sig, _shutdown)
182
+ except NotImplementedError:
183
+ pass
184
+
185
+ try:
186
+ await stop_event.wait()
187
+ finally:
188
+ await server.stop()
189
+ logger.info("AirMouse server shut down cleanly.")
190
+
191
+
192
+ def main(argv=None) -> None:
193
+ args = parse_args(argv)
194
+ try:
195
+ asyncio.run(run_server(args))
196
+ except KeyboardInterrupt:
197
+ pass
198
+
199
+
200
+ if __name__ == "__main__":
201
+ main()
@@ -0,0 +1 @@
1
+ # Marker file for PEP 561 type checking.