com2tty 0.1.2__tar.gz → 0.1.3__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 (32) hide show
  1. com2tty-0.1.3/PKG-INFO +452 -0
  2. com2tty-0.1.3/README.md +436 -0
  3. {com2tty-0.1.2 → com2tty-0.1.3}/pyproject.toml +1 -1
  4. com2tty-0.1.3/src/com2tty/__init__.py +1 -0
  5. {com2tty-0.1.2 → com2tty-0.1.3}/src/com2tty/cli.py +72 -4
  6. {com2tty-0.1.2 → com2tty-0.1.3}/src/com2tty/host.py +140 -0
  7. com2tty-0.1.3/src/com2tty/pad_bridge.py +536 -0
  8. com2tty-0.1.3/src/com2tty/xinput.py +111 -0
  9. com2tty-0.1.3/src/com2tty.egg-info/PKG-INFO +452 -0
  10. {com2tty-0.1.2 → com2tty-0.1.3}/src/com2tty.egg-info/SOURCES.txt +5 -1
  11. com2tty-0.1.3/tests/test_cli.py +155 -0
  12. {com2tty-0.1.2 → com2tty-0.1.3}/tests/test_host.py +194 -0
  13. com2tty-0.1.3/tests/test_pad_bridge.py +422 -0
  14. com2tty-0.1.3/tests/test_xinput.py +136 -0
  15. com2tty-0.1.2/PKG-INFO +0 -133
  16. com2tty-0.1.2/README.md +0 -117
  17. com2tty-0.1.2/src/com2tty/__init__.py +0 -1
  18. com2tty-0.1.2/src/com2tty.egg-info/PKG-INFO +0 -133
  19. com2tty-0.1.2/tests/test_cli.py +0 -80
  20. {com2tty-0.1.2 → com2tty-0.1.3}/LICENSE +0 -0
  21. {com2tty-0.1.2 → com2tty-0.1.3}/setup.cfg +0 -0
  22. {com2tty-0.1.2 → com2tty-0.1.3}/setup.py +0 -0
  23. {com2tty-0.1.2 → com2tty-0.1.3}/src/com2tty/__main__.py +0 -0
  24. {com2tty-0.1.2 → com2tty-0.1.3}/src/com2tty/bridge.py +0 -0
  25. {com2tty-0.1.2 → com2tty-0.1.3}/src/com2tty/rfc2217_server.py +0 -0
  26. {com2tty-0.1.2 → com2tty-0.1.3}/src/com2tty.egg-info/dependency_links.txt +0 -0
  27. {com2tty-0.1.2 → com2tty-0.1.3}/src/com2tty.egg-info/entry_points.txt +0 -0
  28. {com2tty-0.1.2 → com2tty-0.1.3}/src/com2tty.egg-info/requires.txt +0 -0
  29. {com2tty-0.1.2 → com2tty-0.1.3}/src/com2tty.egg-info/top_level.txt +0 -0
  30. {com2tty-0.1.2 → com2tty-0.1.3}/tests/test_bridge_script.py +0 -0
  31. {com2tty-0.1.2 → com2tty-0.1.3}/tests/test_main.py +0 -0
  32. {com2tty-0.1.2 → com2tty-0.1.3}/tests/test_rfc2217_server.py +0 -0
com2tty-0.1.3/PKG-INFO ADDED
@@ -0,0 +1,452 @@
1
+ Metadata-Version: 2.4
2
+ Name: com2tty
3
+ Version: 0.1.3
4
+ Summary: A Windows COM port to WSL ttyUSB forwarder
5
+ Author-email: yichengs <yichengs.tw+com2tty@gmail.com>
6
+ Project-URL: Homepage, https://github.com/Yi-Cheng-Wang/com2tty
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: Microsoft :: Windows
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: pyserial>=3.5
15
+ Dynamic: license-file
16
+
17
+ # com2tty
18
+
19
+ `com2tty` is a Python package that runs on a Windows host and forwards a device
20
+ attached to Windows into a Windows Subsystem for Linux (WSL) instance, where it
21
+ appears as a native Linux device. It supports two kinds of forwarding. The first
22
+ forwards a Windows COM port into WSL as a virtual serial device such as
23
+ `/tmp/ttyUSB0`. The second forwards a Windows XInput game controller into WSL as
24
+ a Linux evdev gamepad. Both kinds use the same transport: a low-latency,
25
+ firewall-resilient bridge built on standard input and output redirection between
26
+ the Windows host process and a helper process running inside WSL. No network
27
+ configuration, port forwarding, or firewall change is required.
28
+
29
+ The intended users are developers who work inside WSL but whose hardware is bound
30
+ to the Windows host: embedded developers who flash and monitor microcontrollers
31
+ over USB-to-serial adapters, and developers who need a game controller available
32
+ to Linux tools running in WSL.
33
+
34
+ ## Table of contents
35
+
36
+ - [Requirements](#requirements)
37
+ - [Installation](#installation)
38
+ - [Configuration](#configuration)
39
+ - [Usage](#usage)
40
+ - [Bridging a serial port](#bridging-a-serial-port)
41
+ - [Automatic baud-rate detection](#automatic-baud-rate-detection)
42
+ - [Configuring /dev/ttyUSB0 in WSL](#configuring-devttyusb0-in-wsl)
43
+ - [Firmware upload through the bridge](#firmware-upload-through-the-bridge)
44
+ - [Gamepad mode](#gamepad-mode)
45
+ - [Architecture overview](#architecture-overview)
46
+ - [Development setup](#development-setup)
47
+ - [Contributing](#contributing)
48
+ - [Troubleshooting](#troubleshooting)
49
+ - [License](#license)
50
+
51
+ ## Requirements
52
+
53
+ The Windows host requires Python 3.8 or later. The `pyserial` package, version
54
+ 3.5 or later, is the only runtime dependency and is installed automatically with
55
+ the package. A working WSL installation is required, and the WSL distribution
56
+ must provide `python3` on its `PATH`. The WSL helper uses only the Python
57
+ standard library and therefore needs no additional packages inside WSL.
58
+
59
+ Serial forwarding requires a COM port that Windows can open. Gamepad forwarding
60
+ requires a controller that the Windows XInput driver recognises, which is the
61
+ standard case for Xbox and XInput-compatible controllers. The opt-in gamepad tier
62
+ that creates a real Linux input device additionally requires a one-time
63
+ privileged setup inside WSL, described in [Gamepad mode](#gamepad-mode).
64
+
65
+ ## Installation
66
+
67
+ Install the released package from PyPI on the Windows host.
68
+
69
+ ```cmd
70
+ pip install com2tty
71
+ ```
72
+
73
+ Alternatively, install from a checkout of the source by running the following
74
+ in the project root.
75
+
76
+ ```cmd
77
+ pip install .
78
+ ```
79
+
80
+ To work on the package itself, install it in editable mode.
81
+
82
+ ```cmd
83
+ pip install -e .
84
+ ```
85
+
86
+ Installation registers a console entry point named `com2tty`. If the entry point
87
+ is not on your `PATH`, the package can also be invoked as a module with
88
+ `python -m com2tty`.
89
+
90
+ ## Configuration
91
+
92
+ `com2tty` is configured entirely through command-line arguments. There are no
93
+ configuration files and no environment variables that the tool itself reads.
94
+ Note that in serial mode the WSL helper writes environment variables into the
95
+ WSL user's `~/.bashrc`; this behaviour is described in
96
+ [Firmware upload through the bridge](#firmware-upload-through-the-bridge).
97
+
98
+ The first positional argument is the COM port. It is required in serial mode and
99
+ is omitted in gamepad mode, which is selected with `--gamepad`.
100
+
101
+ ### Options common to both modes
102
+
103
+ The following option applies to both modes.
104
+
105
+ ```text
106
+ -d, --debug Enable verbose debug logging on standard error.
107
+ ```
108
+
109
+ ### Serial-mode options
110
+
111
+ ```text
112
+ port Windows COM port to bridge, for example COM3. Required
113
+ unless --gamepad is given.
114
+ -b, --baud BAUD Baud rate, or the literal value "auto" to detect the
115
+ rate Windows has configured for the port
116
+ (default: auto; falls back to 9600 if detection fails).
117
+ -w, --wsl-tty PATH Target symlink path created inside WSL
118
+ (default: /tmp/ttyUSB0).
119
+ --rfc2217-port PORT TCP port for the in-WSL RFC 2217 forwarder
120
+ (default: 4000). The UF2 relay uses PORT + 1.
121
+ --bytesize {5,6,7,8} Serial byte size (default: 8).
122
+ --parity {N,E,O,S,M} Parity: none, even, odd, space, or mark (default: N).
123
+ --stopbits {1,1.5,2} Stop bits (default: 1).
124
+ --xonxoff Enable software flow control (XON/XOFF).
125
+ --rtscts Enable hardware flow control (RTS/CTS).
126
+ --dsrdtr Enable hardware flow control (DSR/DTR).
127
+ ```
128
+
129
+ ### Gamepad-mode options
130
+
131
+ ```text
132
+ --gamepad Select gamepad mode. No COM port is required.
133
+ --pad-index {0,1,2,3} XInput controller slot to forward (default: 0).
134
+ --pad-name NAME Device name advertised inside WSL
135
+ (default: "Microsoft X-Box 360 pad").
136
+ --uinput Create a real /dev/input device through /dev/uinput
137
+ instead of the default /tmp event stream.
138
+ --wsl-pad PATH FIFO path for the default /tmp event stream
139
+ (default: /tmp/com2pad0).
140
+ --poll-hz HZ XInput polling rate in hertz (default: 250). Frames are
141
+ sent only when the controller state changes.
142
+ ```
143
+
144
+ ## Usage
145
+
146
+ Run `com2tty` from any Windows terminal, either PowerShell or Command Prompt. The
147
+ process runs in the foreground and is stopped with Ctrl+C.
148
+
149
+ ### Bridging a serial port
150
+
151
+ Bridge `COM3` to the default WSL path `/tmp/ttyUSB0` at 115200 baud.
152
+
153
+ ```cmd
154
+ com2tty COM3 --baud 115200
155
+ ```
156
+
157
+ Bridge `COM5` to a custom WSL device path at 9600 baud.
158
+
159
+ ```cmd
160
+ com2tty COM5 --baud 9600 -w /tmp/my_device
161
+ ```
162
+
163
+ While the bridge is active, a Linux program inside WSL opens the symlinked path
164
+ and reads from and writes to it as if it were a local serial device. Data is
165
+ relayed in both directions between the Windows COM port and the WSL pseudo
166
+ terminal. Dynamic changes that a WSL program makes to the line settings, such as
167
+ the baud rate, are detected and applied to the underlying Windows COM port.
168
+
169
+ ### Automatic baud-rate detection
170
+
171
+ When the baud rate is left at its default value of `auto`, com2tty queries the
172
+ rate that Windows has configured for the port and uses it. If detection fails,
173
+ the bridge falls back to 9600 baud. To set the rate explicitly, pass a numeric
174
+ value to `--baud`.
175
+
176
+ ```cmd
177
+ com2tty COM3 --baud auto
178
+ ```
179
+
180
+ ### Configuring /dev/ttyUSB0 in WSL
181
+
182
+ In Linux the `/dev` directory is owned by `root`. Running com2tty as an ordinary
183
+ Windows user means the WSL helper cannot create a symlink directly under `/dev`.
184
+ For this reason the default target is `/tmp/ttyUSB0`, which is user-writable, and
185
+ com2tty never requires elevated privileges at run time. If a path under `/dev` is
186
+ requested and permission is denied, the helper automatically falls back to the
187
+ equivalent path under `/tmp` and prints instructions.
188
+
189
+ To expose the device at a stable `/dev` path without granting com2tty privileges,
190
+ create a one-time symlink inside WSL that points from `/dev` to the stable `/tmp`
191
+ path.
192
+
193
+ ```bash
194
+ sudo ln -sf /tmp/ttyUSB0 /dev/ttyUSB0
195
+ ```
196
+
197
+ Each time com2tty starts, it repoints `/tmp/ttyUSB0` at the active pseudo
198
+ terminal, so `/dev/ttyUSB0` continues to resolve correctly. After this one-time
199
+ step, WSL programs such as `minicom`, `screen`, the ESP-IDF tools, or Python
200
+ scripts can use `/dev/ttyUSB0` directly.
201
+
202
+ ### Firmware upload through the bridge
203
+
204
+ In serial mode com2tty additionally supports flashing microcontroller firmware
205
+ from build tools running inside WSL, so that a PlatformIO project in WSL can
206
+ upload to a board attached to Windows. This support is enabled by default and
207
+ involves three mechanisms.
208
+
209
+ First, the WSL helper starts an RFC 2217 forwarder that listens on
210
+ `127.0.0.1:<rfc2217-port>` inside WSL, where the port defaults to 4000. To make
211
+ PlatformIO use it, the helper appends environment variables to the WSL user's
212
+ `~/.bashrc`: `PLATFORMIO_UPLOAD_PORT` is set to
213
+ `rfc2217://127.0.0.1:<rfc2217-port>` and `PLATFORMIO_MONITOR_PORT` is set to the
214
+ serial symlink path. Because these variables are written to `~/.bashrc`, open a
215
+ new WSL shell or run `source ~/.bashrc` after starting com2tty for them to take
216
+ effect. The variables are removed when com2tty exits.
217
+
218
+ Second, com2tty detects the connected board type from its USB vendor identifier
219
+ and performs the appropriate hardware reset on the Windows side. For ESP32-class
220
+ boards it performs the DTR and RTS auto-reset sequence to enter the download
221
+ mode. For RP2040 and RP2350 boards it performs the 1200-baud touch that triggers
222
+ the BOOTSEL mass-storage mode.
223
+
224
+ Third, for RP2040 and RP2350 boards, com2tty intercepts the `picotool` invocation
225
+ inside WSL. When PlatformIO calls `picotool` to flash a `.uf2` image, a wrapper
226
+ transfers the image back to the Windows host over a relay that listens on
227
+ `127.0.0.1:<rfc2217-port + 1>`. The host then triggers BOOTSEL mode, locates the
228
+ board's mass-storage drive, verifies the transferred image against an MD5
229
+ checksum, and writes the image to the drive. The original `picotool` is restored
230
+ when com2tty exits.
231
+
232
+ These mechanisms operate without any additional flags. The startup banner reports
233
+ the detected board type, the RFC 2217 port, the UF2 relay port, and the board's
234
+ USB serial number.
235
+
236
+ ### Gamepad mode
237
+
238
+ Gamepad mode forwards a Windows XInput controller into WSL. It exists because
239
+ forwarding a controller with `usbipd` does not work in a default WSL2 setup: the
240
+ stock WSL2 kernel is built without the `xpad` driver, so an attached controller
241
+ is enumerated but never produces a usable input device. Gamepad mode keeps the
242
+ controller on Windows, where the native XInput driver handles it, reads its state
243
+ on the Windows side, and streams that state through the same bridge used for
244
+ serial forwarding. Inside WSL a helper, which uses only the Python standard
245
+ library, turns the state into a Linux evdev `input_event` stream describing a
246
+ Microsoft X-Box 360 pad, identified by USB vendor 0x045e and product 0x028e.
247
+
248
+ The controller must be visible to Windows XInput. If the controller has been
249
+ bound or attached with `usbipd`, Windows no longer owns it and XInput reports no
250
+ controller; unbind it from `usbipd` so that Windows holds the controller before
251
+ using gamepad mode.
252
+
253
+ Gamepad mode provides two tiers. Both emit the identical evdev byte stream, so a
254
+ single reader works against either, and com2tty itself never requires elevated
255
+ privileges at run time.
256
+
257
+ #### Default tier: the /tmp event stream
258
+
259
+ The default tier writes the evdev event stream to a FIFO under `/tmp`, by default
260
+ `/tmp/com2pad0`, and requires no privileged setup.
261
+
262
+ ```cmd
263
+ com2tty --gamepad
264
+ ```
265
+
266
+ A consumer inside WSL reads 24-byte Linux `input_event` records from the FIFO and
267
+ interprets them using the device profile below. This tier is suited to programs
268
+ that read the stream directly. Standard applications and game engines that
269
+ enumerate `/dev/input` devices do not read a FIFO and require the uinput tier.
270
+
271
+ #### Opt-in tier: a real device through /dev/uinput
272
+
273
+ The opt-in tier creates a real system-wide device under `/dev/input` so that SDL2
274
+ applications, emulators, and tools such as `evtest` recognise a normally attached
275
+ controller.
276
+
277
+ ```cmd
278
+ com2tty --gamepad --uinput
279
+ ```
280
+
281
+ If `/dev/uinput` is not accessible, com2tty prints the one-time setup instructions
282
+ and automatically falls back to the `/tmp` event stream so that forwarding
283
+ continues to work.
284
+
285
+ #### One-time setup for the uinput tier
286
+
287
+ Creating a real input device requires access to `/dev/uinput`, which Linux
288
+ restricts to `root`, and reading the resulting `/dev/input/event*` node requires
289
+ membership of the `input` group. Both are granted once, inside WSL, and com2tty
290
+ still runs without privileges thereafter. Either use `sudo`, or run the commands
291
+ as root from Windows with `wsl -u root`, which requires no password.
292
+
293
+ ```bash
294
+ sudo modprobe uinput
295
+ sudo chmod 0666 /dev/uinput
296
+ sudo usermod -aG input "$USER"
297
+ ```
298
+
299
+ The permission granted by `chmod` does not survive `wsl --shutdown`. To make it
300
+ persist, add a boot command to `/etc/wsl.conf`, which runs as root on every WSL
301
+ start.
302
+
303
+ ```ini
304
+ [boot]
305
+ command = modprobe uinput && chmod 0666 /dev/uinput
306
+ ```
307
+
308
+ After editing `/etc/wsl.conf`, run `wsl --shutdown` once from Windows. This also
309
+ refreshes the group membership granted by `usermod`.
310
+
311
+ The stock WSL2 kernel sets `CONFIG_INPUT_UINPUT` as a module, which works, but
312
+ does not set `CONFIG_INPUT_JOYDEV`. As a result the legacy `/dev/input/js*` node
313
+ is absent. This is not a problem for modern applications and SDL2, which read
314
+ `/dev/input/event*` directly.
315
+
316
+ #### Verifying the uinput tier
317
+
318
+ With com2tty running in `--uinput` mode, confirm the device inside WSL with
319
+ `evtest`.
320
+
321
+ ```bash
322
+ sudo apt install evtest
323
+ evtest
324
+ ```
325
+
326
+ Select the Microsoft X-Box 360 pad device, then move the sticks and press buttons
327
+ on Windows and observe the events appear in WSL.
328
+
329
+ #### Device profile
330
+
331
+ Both tiers emit the same evdev codes. Buttons are reported as `BTN_A`, `BTN_B`,
332
+ `BTN_X`, `BTN_Y`, `BTN_TL`, `BTN_TR`, `BTN_SELECT`, `BTN_START`, `BTN_THUMBL`,
333
+ and `BTN_THUMBR`. The sticks are reported as `ABS_X` and `ABS_Y` for the left
334
+ stick and `ABS_RX` and `ABS_RY` for the right stick, each spanning the signed
335
+ 16-bit range. The triggers are reported as `ABS_Z` for the left trigger and
336
+ `ABS_RZ` for the right trigger, each spanning 0 to 255. The directional pad is
337
+ reported as `ABS_HAT0X` and `ABS_HAT0Y` with values of -1, 0, or 1. The stick Y
338
+ axes are inverted to follow the Linux convention in which pushing up produces a
339
+ negative value.
340
+
341
+ The forwarded signal matches a real controller at the level of these event codes,
342
+ ranges, and resolutions, but it is not bit-for-bit identical to a controller
343
+ driven by the kernel `xpad` driver. The timing and latency differ because the
344
+ path is polled and piped rather than delivered by a fixed USB interrupt interval.
345
+ The Guide button is not reported, because the standard XInput state query does not
346
+ expose it. Force feedback is not implemented. These differences are inherent to
347
+ the approach.
348
+
349
+ ## Architecture overview
350
+
351
+ The package is organised around a host process on Windows and a helper process
352
+ inside WSL connected by the standard input and output streams of the helper.
353
+
354
+ `cli.py` parses the command line and dispatches to one of two entry functions in
355
+ `host.py`. In serial mode it calls `run_bridge`; in gamepad mode it calls
356
+ `run_gamepad_bridge`. `__main__.py` and the console entry point both call
357
+ `cli.main`, and `__init__.py` holds the package version.
358
+
359
+ `host.py` is the Windows side. In serial mode `run_bridge` opens the COM port with
360
+ `pyserial`, spawns the WSL helper with `wsl python3 -u bridge.py`, and runs three
361
+ threads: one relays bytes from the COM port to the helper's standard input, one
362
+ relays bytes from the helper's standard output to the COM port, and one reads the
363
+ helper's standard error. The standard error stream carries a line-oriented control
364
+ protocol whose messages are prefixed with `[CONTROL]`; these messages drive
365
+ dynamic serial-setting changes, the RFC 2217 session lifecycle, and the UF2 upload
366
+ sequence. `host.py` also contains the board detection and reset logic and the
367
+ routine that writes a transferred UF2 image to the correct Windows drive.
368
+
369
+ `bridge.py` is the WSL side for serial forwarding. It creates a pseudo terminal
370
+ with `openpty`, symlinks the requested path to the pseudo-terminal slave, falling
371
+ back to `/tmp` if the requested path is not writable, and runs a `select` loop
372
+ that relays data between the helper's standard input and output and the
373
+ pseudo-terminal master. It also starts the RFC 2217 forwarder thread and the UF2
374
+ relay thread, writes the PlatformIO environment variables into `~/.bashrc`, and
375
+ installs the `picotool` interceptor. `rfc2217_server.py` provides the redirector
376
+ that implements the RFC 2217 protocol for the forwarder.
377
+
378
+ The gamepad path reuses the same spawn-and-pipe transport. `xinput.py` is the
379
+ Windows side: it polls an XInput controller slot through `ctypes` and packs each
380
+ state snapshot into a fixed 16-byte frame, sending a frame only when the state
381
+ changes. `pad_bridge.py` is the WSL side: it parses the frames, translates them
382
+ into evdev events, and writes them to one of two sinks. The default sink writes to
383
+ a `/tmp` FIFO, and the opt-in sink creates a real device through `/dev/uinput`
384
+ using raw `ioctl` calls. Both sinks share the same event-encoding code, so the
385
+ byte stream they produce is identical.
386
+
387
+ ## Development setup
388
+
389
+ Install the package in editable mode together with the test tools.
390
+
391
+ ```bash
392
+ pip install -e .
393
+ pip install pytest pytest-cov
394
+ ```
395
+
396
+ Run the test suite with coverage.
397
+
398
+ ```bash
399
+ pytest --cov=src/com2tty --cov-report=term-missing tests/
400
+ ```
401
+
402
+ A passing run reports all tests passing and full line coverage for the package.
403
+ The test suite is cross-platform. The `tests/conftest.py` file substitutes a mock
404
+ `termios` module on Windows so that the WSL-side modules import for testing, and
405
+ the platform-specific system calls used by the gamepad sinks are mocked so that
406
+ the suite runs on both Windows and Linux.
407
+
408
+ Continuous integration is defined in `.github/workflows/ci.yml`. It runs the test
409
+ suite on `windows-latest` and `ubuntu-latest` against Python 3.8, 3.9, 3.10,
410
+ 3.11, and 3.12, and it enforces 100 percent line coverage by running pytest with
411
+ `--cov-fail-under=100`. A separate job publishes the package to PyPI on pushes to
412
+ the `main` branch.
413
+
414
+ ## Contributing
415
+
416
+ Base feature branches on the `develop` branch. Commit messages follow the
417
+ Conventional Commits format, for example `feat(gamepad): ...` or
418
+ `test(host): ...`, as established in the project history. Every change must keep
419
+ the test suite passing with 100 percent line coverage on both Windows and Ubuntu
420
+ across the supported Python versions, because continuous integration enforces
421
+ this. Add or update tests for any behavioural change. Open pull requests against
422
+ `develop`.
423
+
424
+ ## Troubleshooting
425
+
426
+ If the WSL helper reports a permission error while creating the serial symlink,
427
+ the requested path under `/dev` is not writable; the helper falls back to `/tmp`
428
+ and prints the one-time command to link the `/dev` path to it.
429
+
430
+ If the serial port reports that it is busy or access is denied, ensure no other
431
+ Windows application, such as a serial monitor or a second com2tty instance, is
432
+ holding the COM port open.
433
+
434
+ If gamepad mode reports all values as zero, Windows XInput is not receiving the
435
+ controller. Confirm the controller is not bound or attached through `usbipd`, so
436
+ that Windows owns it, and confirm it is on the expected XInput slot, which can be
437
+ changed with `--pad-index`.
438
+
439
+ If the `--uinput` tier cannot open `/dev/uinput`, complete the one-time setup
440
+ described in [Gamepad mode](#gamepad-mode). Until then, com2tty falls back to the
441
+ `/tmp` event stream.
442
+
443
+ For detailed logs and transfer statistics, run com2tty with `-d` or `--debug`.
444
+
445
+ ```cmd
446
+ com2tty COM3 --debug
447
+ ```
448
+
449
+ ## License
450
+
451
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file
452
+ for the full text.