com2tty 0.2.0__tar.gz → 0.3.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.
- {com2tty-0.2.0/src/com2tty.egg-info → com2tty-0.3.0}/PKG-INFO +131 -49
- {com2tty-0.2.0 → com2tty-0.3.0}/README.md +130 -48
- {com2tty-0.2.0 → com2tty-0.3.0}/pyproject.toml +4 -1
- com2tty-0.3.0/src/com2tty/__init__.py +1 -0
- com2tty-0.3.0/src/com2tty/bridge.py +21 -0
- com2tty-0.2.0/src/com2tty/cli.py → com2tty-0.3.0/src/com2tty/cli/__init__.py +109 -22
- {com2tty-0.2.0/src/com2tty → com2tty-0.3.0/src/com2tty/cli}/profiles.py +11 -1
- com2tty-0.3.0/src/com2tty/core/__init__.py +6 -0
- com2tty-0.3.0/src/com2tty/core/boards.py +77 -0
- com2tty-0.3.0/src/com2tty/core/constants.py +91 -0
- com2tty-0.3.0/src/com2tty/core/frames.py +136 -0
- com2tty-0.3.0/src/com2tty/core/protocol.py +125 -0
- com2tty-0.3.0/src/com2tty/pad_bridge.py +21 -0
- com2tty-0.3.0/src/com2tty/windows/__init__.py +6 -0
- com2tty-0.2.0/src/com2tty/boards.py → com2tty-0.3.0/src/com2tty/windows/board_reset.py +27 -80
- com2tty-0.3.0/src/com2tty/windows/bridge_app.py +402 -0
- com2tty-0.3.0/src/com2tty/windows/control_handler.py +482 -0
- {com2tty-0.2.0/src/com2tty → com2tty-0.3.0/src/com2tty/windows}/discovery.py +13 -4
- com2tty-0.3.0/src/com2tty/windows/doctor.py +206 -0
- com2tty-0.3.0/src/com2tty/windows/gamepad_app.py +217 -0
- com2tty-0.2.0/src/com2tty/xinput.py → com2tty-0.3.0/src/com2tty/windows/gamepad_host.py +15 -60
- com2tty-0.3.0/src/com2tty/windows/os_hacks/__init__.py +12 -0
- com2tty-0.2.0/src/com2tty/uf2.py → com2tty-0.3.0/src/com2tty/windows/os_hacks/autoplay.py +11 -80
- com2tty-0.3.0/src/com2tty/windows/os_hacks/device_watcher.py +197 -0
- com2tty-0.3.0/src/com2tty/windows/os_hacks/explorer.py +125 -0
- com2tty-0.2.0/src/com2tty/rfc2217_server.py → com2tty-0.3.0/src/com2tty/windows/rfc2217_redirector.py +29 -0
- com2tty-0.3.0/src/com2tty/windows/serial_host.py +304 -0
- com2tty-0.3.0/src/com2tty/windows/uf2_flash.py +84 -0
- com2tty-0.3.0/src/com2tty/windows/wsl_process.py +131 -0
- com2tty-0.3.0/src/com2tty/wsl/__init__.py +14 -0
- com2tty-0.3.0/src/com2tty/wsl/assets/picotool_wrapper.py.in +42 -0
- com2tty-0.2.0/src/com2tty/pad_bridge.py → com2tty-0.3.0/src/com2tty/wsl/evdev_sink.py +91 -203
- com2tty-0.3.0/src/com2tty/wsl/gamepad_app.py +99 -0
- com2tty-0.3.0/src/com2tty/wsl/integrations/__init__.py +9 -0
- com2tty-0.3.0/src/com2tty/wsl/integrations/picotool.py +133 -0
- com2tty-0.3.0/src/com2tty/wsl/integrations/shell_env.py +193 -0
- com2tty-0.3.0/src/com2tty/wsl/liveness.py +67 -0
- com2tty-0.3.0/src/com2tty/wsl/pty_manager.py +100 -0
- com2tty-0.3.0/src/com2tty/wsl/serial_app.py +208 -0
- com2tty-0.3.0/src/com2tty/wsl/servers/__init__.py +8 -0
- com2tty-0.3.0/src/com2tty/wsl/servers/base.py +144 -0
- com2tty-0.3.0/src/com2tty/wsl/servers/rfc2217_forwarder.py +85 -0
- com2tty-0.3.0/src/com2tty/wsl/servers/uf2_relay.py +117 -0
- {com2tty-0.2.0 → com2tty-0.3.0/src/com2tty.egg-info}/PKG-INFO +131 -49
- com2tty-0.3.0/src/com2tty.egg-info/SOURCES.txt +83 -0
- com2tty-0.3.0/tests/test_autoplay.py +267 -0
- com2tty-0.3.0/tests/test_board_reset.py +71 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/tests/test_boards.py +19 -17
- com2tty-0.3.0/tests/test_bridge_app.py +821 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/tests/test_cli.py +95 -8
- com2tty-0.3.0/tests/test_console.py +79 -0
- com2tty-0.3.0/tests/test_control_handler.py +1151 -0
- com2tty-0.3.0/tests/test_core_frames.py +109 -0
- com2tty-0.3.0/tests/test_core_protocol.py +129 -0
- com2tty-0.3.0/tests/test_devnotify.py +237 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/tests/test_discovery.py +25 -2
- com2tty-0.3.0/tests/test_doctor.py +323 -0
- com2tty-0.3.0/tests/test_entry_shims.py +80 -0
- com2tty-0.2.0/tests/test_pad_bridge.py → com2tty-0.3.0/tests/test_evdev_sink.py +180 -89
- com2tty-0.3.0/tests/test_gamepad_app.py +331 -0
- com2tty-0.3.0/tests/test_liveness.py +117 -0
- com2tty-0.3.0/tests/test_picotool.py +333 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/tests/test_profiles.py +30 -5
- com2tty-0.3.0/tests/test_pty_manager.py +73 -0
- com2tty-0.3.0/tests/test_rfc2217_forwarder.py +173 -0
- com2tty-0.2.0/tests/test_rfc2217_server.py → com2tty-0.3.0/tests/test_rfc2217_redirector.py +64 -3
- com2tty-0.3.0/tests/test_serial_app.py +452 -0
- com2tty-0.3.0/tests/test_serial_host.py +493 -0
- com2tty-0.3.0/tests/test_shell_env.py +426 -0
- com2tty-0.3.0/tests/test_uf2_flash.py +108 -0
- com2tty-0.3.0/tests/test_uf2_relay.py +366 -0
- com2tty-0.3.0/tests/test_wsl_gamepad_app.py +51 -0
- com2tty-0.3.0/tests/test_wsl_process.py +110 -0
- com2tty-0.3.0/tests/test_wsl_servers_base.py +103 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/tests/test_xinput.py +10 -10
- com2tty-0.2.0/src/com2tty/__init__.py +0 -1
- com2tty-0.2.0/src/com2tty/bridge.py +0 -666
- com2tty-0.2.0/src/com2tty/host.py +0 -1166
- com2tty-0.2.0/src/com2tty.egg-info/SOURCES.txt +0 -33
- com2tty-0.2.0/tests/test_bridge_script.py +0 -1572
- com2tty-0.2.0/tests/test_host.py +0 -3021
- {com2tty-0.2.0 → com2tty-0.3.0}/LICENSE +0 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/setup.cfg +0 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/setup.py +0 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/src/com2tty/__main__.py +0 -0
- /com2tty-0.2.0/src/com2tty/banner.py → /com2tty-0.3.0/src/com2tty/windows/os_hacks/console.py +0 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/src/com2tty.egg-info/dependency_links.txt +0 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/src/com2tty.egg-info/entry_points.txt +0 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/src/com2tty.egg-info/requires.txt +0 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/src/com2tty.egg-info/top_level.txt +0 -0
- {com2tty-0.2.0 → com2tty-0.3.0}/tests/test_main.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: com2tty
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A Windows COM port to WSL ttyUSB forwarder
|
|
5
5
|
Author-email: yichengs <yichengs.tw+com2tty@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/Yi-Cheng-Wang/com2tty
|
|
@@ -117,6 +117,19 @@ The following options apply to both modes.
|
|
|
117
117
|
-l, --list List the serial ports Windows can see (device name,
|
|
118
118
|
VID:PID, USB bus id, serial number, detected board,
|
|
119
119
|
description) and exit.
|
|
120
|
+
--json With --list: print the port list as a JSON array
|
|
121
|
+
instead of an aligned table, for scripts and IDE
|
|
122
|
+
integrations.
|
|
123
|
+
--doctor Run an environment self-check (WSL, python3, drive
|
|
124
|
+
automounting, fuser, TCP port availability, leftovers
|
|
125
|
+
from crashed sessions, /dev/uinput, the XInput DLL)
|
|
126
|
+
and exit. Exit status 1 when a required check fails.
|
|
127
|
+
--auto-respawn Rebuild the bridge automatically when the WSL helper
|
|
128
|
+
dies, for example after `wsl --shutdown` or a WSL
|
|
129
|
+
servicing update: com2tty waits until WSL answers
|
|
130
|
+
again and re-creates the same endpoint. In serial
|
|
131
|
+
mode this implies --wait. Applies to serial and
|
|
132
|
+
gamepad mode alike.
|
|
120
133
|
-d, --debug Enable verbose debug logging on standard error.
|
|
121
134
|
--distro NAME WSL distribution to use (default: the WSL default
|
|
122
135
|
distribution). Useful when the default distribution
|
|
@@ -138,6 +151,10 @@ port [port ...] Windows COM port(s) to bridge, for example COM3, or
|
|
|
138
151
|
--rfc2217-port PORT TCP port for the in-WSL RFC 2217 forwarder
|
|
139
152
|
(default: 4000). The UF2 relay uses PORT + 1; with
|
|
140
153
|
multiple ports, each additional port uses PORT + 2i.
|
|
154
|
+
--wait If the COM port is not present yet, wait for it to
|
|
155
|
+
appear instead of failing, then bridge it. Useful
|
|
156
|
+
when com2tty is started before the device is
|
|
157
|
+
plugged in.
|
|
141
158
|
--bytesize {5,6,7,8} Serial byte size (default: 8).
|
|
142
159
|
--parity {N,E,O,S,M} Parity: none, even, odd, space, or mark (default: N).
|
|
143
160
|
--stopbits {1,1.5,2} Stop bits (default: 1).
|
|
@@ -155,7 +172,12 @@ port [port ...] Windows COM port(s) to bridge, for example COM3, or
|
|
|
155
172
|
|
|
156
173
|
```text
|
|
157
174
|
--gamepad Select gamepad mode. No COM port is required.
|
|
158
|
-
--pad-index {0,1,2,3}
|
|
175
|
+
--pad-index {0,1,2,3} [...]
|
|
176
|
+
XInput controller slot(s) to forward (default: 0).
|
|
177
|
+
Several slots may be given (--pad-index 0 1) to
|
|
178
|
+
forward multiple controllers at once; each gets its
|
|
179
|
+
own WSL helper and endpoint (/tmp/com2pad0,
|
|
180
|
+
/tmp/com2pad1, ...).
|
|
159
181
|
--pad-name NAME Device name advertised inside WSL
|
|
160
182
|
(default: "Microsoft X-Box 360 pad").
|
|
161
183
|
--uinput Create a real /dev/input device through /dev/uinput
|
|
@@ -218,7 +240,15 @@ stale Windows handle and waits for the device to come back, first under its
|
|
|
218
240
|
original COM name and then by scanning for its USB serial number, because
|
|
219
241
|
Windows may assign a different COM number after a replug. Once the device
|
|
220
242
|
reappears the bridge resumes automatically; the WSL endpoint stays in place
|
|
221
|
-
the whole time.
|
|
243
|
+
the whole time. The waiting loops are event-driven: com2tty registers for
|
|
244
|
+
Windows device-change notifications (`WM_DEVICECHANGE`), so a replugged
|
|
245
|
+
device resumes the moment Windows enumerates it rather than on the next
|
|
246
|
+
polling tick (plain polling remains as the fallback).
|
|
247
|
+
|
|
248
|
+
The complementary case — WSL itself going away, for example through
|
|
249
|
+
`wsl --shutdown` or a WSL update — is covered by `--auto-respawn`: instead
|
|
250
|
+
of exiting when the WSL helper dies, com2tty waits until WSL answers again
|
|
251
|
+
and rebuilds the bridge with the same endpoint paths.
|
|
222
252
|
|
|
223
253
|
### Bridging multiple ports
|
|
224
254
|
|
|
@@ -269,9 +299,11 @@ com2tty @pad
|
|
|
269
299
|
### Automatic baud-rate detection
|
|
270
300
|
|
|
271
301
|
When the baud rate is left at its default value of `auto`, com2tty queries the
|
|
272
|
-
rate that Windows has configured for the port and uses it.
|
|
273
|
-
the
|
|
274
|
-
|
|
302
|
+
rate that Windows has configured for the port and uses it. The rate is read
|
|
303
|
+
directly from the Win32 `GetCommState` API, which works regardless of the
|
|
304
|
+
Windows display language; parsing the `mode.com` output is kept only as a
|
|
305
|
+
fallback. If detection fails, the bridge falls back to 9600 baud. To set the
|
|
306
|
+
rate explicitly, pass a numeric value to `--baud`.
|
|
275
307
|
|
|
276
308
|
```cmd
|
|
277
309
|
com2tty COM3 --baud auto
|
|
@@ -359,7 +391,13 @@ these ports during an upload. Run com2tty only on hosts you trust, and choose a
|
|
|
359
391
|
non-default `--rfc2217-port` if another local service needs the default port. To
|
|
360
392
|
reclaim a port left open by a previous com2tty session, the helper only
|
|
361
393
|
terminates processes whose command line identifies them as a com2tty bridge; an
|
|
362
|
-
unrelated service occupying the port is never killed.
|
|
394
|
+
unrelated service occupying the port is never killed. A *running* com2tty
|
|
395
|
+
session is never killed either: each session refreshes a heartbeat marker for
|
|
396
|
+
its ports, so a second invocation that reuses the same `--rfc2217-port` reports
|
|
397
|
+
the conflict and leaves the first bridge intact. Two sessions can run
|
|
398
|
+
concurrently by giving the second one a different `--rfc2217-port` and
|
|
399
|
+
`--wsl-tty`; each session removes only its own block from the shell startup
|
|
400
|
+
files when it exits.
|
|
363
401
|
|
|
364
402
|
### Gamepad mode
|
|
365
403
|
|
|
@@ -396,6 +434,24 @@ interprets them using the device profile below. This tier is suited to programs
|
|
|
396
434
|
that read the stream directly. Standard applications and game engines that
|
|
397
435
|
enumerate `/dev/input` devices do not read a FIFO and require the uinput tier.
|
|
398
436
|
|
|
437
|
+
Force feedback is available in this tier through a second FIFO created at
|
|
438
|
+
`<path>.ff` (by default `/tmp/com2pad0.ff`): the consumer writes 6-byte rumble
|
|
439
|
+
frames into it — the bytes `0xFB 0xFE` followed by the strong (left,
|
|
440
|
+
low-frequency) and weak (right, high-frequency) motor magnitudes as two
|
|
441
|
+
little-endian unsigned 16-bit values — and com2tty forwards them to the
|
|
442
|
+
physical controller's motors, exactly as the uinput tier does for kernel
|
|
443
|
+
`FF_RUMBLE` effects.
|
|
444
|
+
|
|
445
|
+
To forward several controllers at once, pass several slots:
|
|
446
|
+
|
|
447
|
+
```cmd
|
|
448
|
+
com2tty --gamepad --pad-index 0 1
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Each slot gets its own WSL helper and its own endpoint (`/tmp/com2pad0`,
|
|
452
|
+
`/tmp/com2pad1`, ...); in the uinput tier each helper creates its own
|
|
453
|
+
`/dev/input` device, as if several physical controllers were attached.
|
|
454
|
+
|
|
399
455
|
#### Opt-in tier: a real device through /dev/uinput
|
|
400
456
|
|
|
401
457
|
The opt-in tier creates a real system-wide device under `/dev/input` so that SDL2
|
|
@@ -472,8 +528,9 @@ feedback. When a game or emulator inside WSL plays a rumble effect, the
|
|
|
472
528
|
effect's magnitudes travel back through the bridge to the Windows host, which
|
|
473
529
|
drives the physical controller's motors through `XInputSetState`. The strong
|
|
474
530
|
(left, low-frequency) and weak (right, high-frequency) motors map directly to
|
|
475
|
-
their XInput counterparts.
|
|
476
|
-
|
|
531
|
+
their XInput counterparts. In the `/tmp` stream tier the same reverse channel
|
|
532
|
+
is reached by writing rumble frames into the `<path>.ff` FIFO, as described
|
|
533
|
+
in [the default tier](#default-tier-the-tmp-event-stream).
|
|
477
534
|
|
|
478
535
|
The forwarded signal matches a real controller at the level of these event codes,
|
|
479
536
|
ranges, and resolutions, but it is not bit-for-bit identical to a controller
|
|
@@ -487,48 +544,68 @@ available. These differences are inherent to the approach.
|
|
|
487
544
|
|
|
488
545
|
The package is organised around a host process on Windows and a helper process
|
|
489
546
|
inside WSL connected by the standard input and output streams of the helper.
|
|
547
|
+
The code under `src/com2tty/` is split by where it runs: `cli/` is the
|
|
548
|
+
command-line layer, `core/` holds the dependency-free definitions both sides
|
|
549
|
+
share, `windows/` runs on the Windows interpreter, and `wsl/` runs on the
|
|
550
|
+
Linux interpreter inside WSL.
|
|
490
551
|
|
|
491
|
-
`cli
|
|
492
|
-
tokens) and dispatches to an entry function in `
|
|
552
|
+
`cli/` parses the command line (after `cli/profiles.py` expands any `@profile`
|
|
553
|
+
tokens) and dispatches to an entry function in `windows/`: `run_bridge` in
|
|
493
554
|
serial mode, `run_multi_bridge` when several ports are given, and
|
|
494
|
-
`run_gamepad_bridge` in gamepad mode. `discovery.py` implements
|
|
495
|
-
`
|
|
496
|
-
`__init__.py` holds the package
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
`
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
555
|
+
`run_gamepad_bridge` in gamepad mode. `windows/discovery.py` implements
|
|
556
|
+
`--list` and `windows/doctor.py` implements `--doctor`. `__main__.py` and the
|
|
557
|
+
console entry point both call `cli.main`, and `__init__.py` holds the package
|
|
558
|
+
version.
|
|
559
|
+
|
|
560
|
+
`core/` defines the contracts both interpreters rely on: `core/protocol.py`
|
|
561
|
+
holds the `[CONTROL]` message catalogue and the dispatcher the host routes
|
|
562
|
+
stderr lines through, `core/frames.py` the binary gamepad frame codecs,
|
|
563
|
+
`core/boards.py` the USB VID classification with the reset timing data, and
|
|
564
|
+
`core/constants.py` the shared paths, ports, and marker strings.
|
|
565
|
+
|
|
566
|
+
`windows/` is the Windows side. In serial mode `run_bridge` in
|
|
567
|
+
`windows/bridge_app.py` opens the COM port with `pyserial`, spawns the WSL
|
|
568
|
+
helper with `wsl python3 -u bridge.py` (through `windows/wsl_process.py`), and
|
|
569
|
+
runs three threads: one relays bytes from the COM port to the helper's
|
|
570
|
+
standard input, one relays bytes from the helper's standard output to the COM
|
|
571
|
+
port, and one reads the helper's standard error. The standard error stream
|
|
572
|
+
carries a line-oriented control protocol whose messages are prefixed with
|
|
573
|
+
`[CONTROL]`; the handlers in `windows/control_handler.py` drive dynamic
|
|
574
|
+
serial-setting changes, the RFC 2217 session lifecycle, and the UF2 upload
|
|
575
|
+
sequence (including the routine that writes a transferred UF2 image to the
|
|
576
|
+
correct Windows drive, via `windows/uf2_flash.py`). The hot-plug reconnect
|
|
577
|
+
logic lives in `windows/serial_host.py` and the board reset sequences in
|
|
578
|
+
`windows/board_reset.py`. The raw OS-level interventions -- AutoPlay
|
|
579
|
+
suppression, Explorer window closing, `WM_DEVICECHANGE` wake-ups, and console
|
|
580
|
+
VT mode -- are isolated under `windows/os_hacks/`.
|
|
581
|
+
|
|
582
|
+
`wsl/` is the WSL side, restricted to the Python standard library.
|
|
583
|
+
`wsl/serial_app.py` (launched through the `bridge.py` shim at the package
|
|
584
|
+
root) creates a pseudo terminal with `openpty` via `wsl/pty_manager.py`,
|
|
585
|
+
symlinks the requested path to the pseudo-terminal slave, falling back to
|
|
586
|
+
`/tmp` if the requested path is not writable, and runs a `select` loop that
|
|
587
|
+
relays data between the helper's standard input and output and the
|
|
588
|
+
pseudo-terminal master. It also starts the RFC 2217 forwarder and UF2 relay
|
|
589
|
+
threads (`wsl/servers/`), writes the PlatformIO environment variables into
|
|
590
|
+
`~/.bashrc` (`wsl/integrations/shell_env.py`), and installs the `picotool`
|
|
591
|
+
interceptor (`wsl/integrations/picotool.py`).
|
|
592
|
+
`windows/rfc2217_redirector.py` provides the redirector that implements the
|
|
593
|
+
RFC 2217 protocol for the forwarder.
|
|
594
|
+
|
|
595
|
+
The gamepad path reuses the same spawn-and-pipe transport.
|
|
596
|
+
`windows/gamepad_host.py` is the Windows side: it polls an XInput controller
|
|
597
|
+
slot through `ctypes` (preferring the `XInputGetStateEx` export so the Guide
|
|
598
|
+
button is visible) and packs each state snapshot into a fixed 16-byte frame,
|
|
599
|
+
sending a frame only when the state changes. `wsl/gamepad_app.py` (launched
|
|
600
|
+
through the `pad_bridge.py` shim) is the WSL side: it parses the frames,
|
|
601
|
+
translates them into evdev events, and writes them to one of the two sinks in
|
|
602
|
+
`wsl/evdev_sink.py`. The default sink writes to a `/tmp` FIFO, and the opt-in
|
|
603
|
+
sink creates a real device through `/dev/uinput` using raw `ioctl` calls. Both
|
|
604
|
+
sinks share the same event-encoding code, so the byte stream they produce is
|
|
605
|
+
identical. In the uinput sink the helper also services the kernel's
|
|
606
|
+
force-feedback upload handshake and streams played rumble effects back over
|
|
607
|
+
its stdout, where the host applies them to the physical controller with
|
|
608
|
+
`XInputSetState`.
|
|
532
609
|
|
|
533
610
|
For a detailed account of the control protocol, the board reset sequences, the
|
|
534
611
|
reconnection model, the binary frame formats, and the known hardware-unverified
|
|
@@ -588,6 +665,11 @@ step, is documented in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
|
588
665
|
|
|
589
666
|
## Troubleshooting
|
|
590
667
|
|
|
668
|
+
Run `com2tty --doctor` first: it probes the whole environment (WSL, the
|
|
669
|
+
selected distribution's `python3`, drive automounting, `fuser`, the RFC 2217
|
|
670
|
+
and UF2 relay TCP ports, leftovers from crashed sessions, `/dev/uinput`
|
|
671
|
+
access, and the XInput DLL) and prints one actionable line per check.
|
|
672
|
+
|
|
591
673
|
At startup com2tty verifies the WSL environment and reports a specific error if
|
|
592
674
|
a prerequisite is missing. The checks and their remedies are:
|
|
593
675
|
|
|
@@ -101,6 +101,19 @@ The following options apply to both modes.
|
|
|
101
101
|
-l, --list List the serial ports Windows can see (device name,
|
|
102
102
|
VID:PID, USB bus id, serial number, detected board,
|
|
103
103
|
description) and exit.
|
|
104
|
+
--json With --list: print the port list as a JSON array
|
|
105
|
+
instead of an aligned table, for scripts and IDE
|
|
106
|
+
integrations.
|
|
107
|
+
--doctor Run an environment self-check (WSL, python3, drive
|
|
108
|
+
automounting, fuser, TCP port availability, leftovers
|
|
109
|
+
from crashed sessions, /dev/uinput, the XInput DLL)
|
|
110
|
+
and exit. Exit status 1 when a required check fails.
|
|
111
|
+
--auto-respawn Rebuild the bridge automatically when the WSL helper
|
|
112
|
+
dies, for example after `wsl --shutdown` or a WSL
|
|
113
|
+
servicing update: com2tty waits until WSL answers
|
|
114
|
+
again and re-creates the same endpoint. In serial
|
|
115
|
+
mode this implies --wait. Applies to serial and
|
|
116
|
+
gamepad mode alike.
|
|
104
117
|
-d, --debug Enable verbose debug logging on standard error.
|
|
105
118
|
--distro NAME WSL distribution to use (default: the WSL default
|
|
106
119
|
distribution). Useful when the default distribution
|
|
@@ -122,6 +135,10 @@ port [port ...] Windows COM port(s) to bridge, for example COM3, or
|
|
|
122
135
|
--rfc2217-port PORT TCP port for the in-WSL RFC 2217 forwarder
|
|
123
136
|
(default: 4000). The UF2 relay uses PORT + 1; with
|
|
124
137
|
multiple ports, each additional port uses PORT + 2i.
|
|
138
|
+
--wait If the COM port is not present yet, wait for it to
|
|
139
|
+
appear instead of failing, then bridge it. Useful
|
|
140
|
+
when com2tty is started before the device is
|
|
141
|
+
plugged in.
|
|
125
142
|
--bytesize {5,6,7,8} Serial byte size (default: 8).
|
|
126
143
|
--parity {N,E,O,S,M} Parity: none, even, odd, space, or mark (default: N).
|
|
127
144
|
--stopbits {1,1.5,2} Stop bits (default: 1).
|
|
@@ -139,7 +156,12 @@ port [port ...] Windows COM port(s) to bridge, for example COM3, or
|
|
|
139
156
|
|
|
140
157
|
```text
|
|
141
158
|
--gamepad Select gamepad mode. No COM port is required.
|
|
142
|
-
--pad-index {0,1,2,3}
|
|
159
|
+
--pad-index {0,1,2,3} [...]
|
|
160
|
+
XInput controller slot(s) to forward (default: 0).
|
|
161
|
+
Several slots may be given (--pad-index 0 1) to
|
|
162
|
+
forward multiple controllers at once; each gets its
|
|
163
|
+
own WSL helper and endpoint (/tmp/com2pad0,
|
|
164
|
+
/tmp/com2pad1, ...).
|
|
143
165
|
--pad-name NAME Device name advertised inside WSL
|
|
144
166
|
(default: "Microsoft X-Box 360 pad").
|
|
145
167
|
--uinput Create a real /dev/input device through /dev/uinput
|
|
@@ -202,7 +224,15 @@ stale Windows handle and waits for the device to come back, first under its
|
|
|
202
224
|
original COM name and then by scanning for its USB serial number, because
|
|
203
225
|
Windows may assign a different COM number after a replug. Once the device
|
|
204
226
|
reappears the bridge resumes automatically; the WSL endpoint stays in place
|
|
205
|
-
the whole time.
|
|
227
|
+
the whole time. The waiting loops are event-driven: com2tty registers for
|
|
228
|
+
Windows device-change notifications (`WM_DEVICECHANGE`), so a replugged
|
|
229
|
+
device resumes the moment Windows enumerates it rather than on the next
|
|
230
|
+
polling tick (plain polling remains as the fallback).
|
|
231
|
+
|
|
232
|
+
The complementary case — WSL itself going away, for example through
|
|
233
|
+
`wsl --shutdown` or a WSL update — is covered by `--auto-respawn`: instead
|
|
234
|
+
of exiting when the WSL helper dies, com2tty waits until WSL answers again
|
|
235
|
+
and rebuilds the bridge with the same endpoint paths.
|
|
206
236
|
|
|
207
237
|
### Bridging multiple ports
|
|
208
238
|
|
|
@@ -253,9 +283,11 @@ com2tty @pad
|
|
|
253
283
|
### Automatic baud-rate detection
|
|
254
284
|
|
|
255
285
|
When the baud rate is left at its default value of `auto`, com2tty queries the
|
|
256
|
-
rate that Windows has configured for the port and uses it.
|
|
257
|
-
the
|
|
258
|
-
|
|
286
|
+
rate that Windows has configured for the port and uses it. The rate is read
|
|
287
|
+
directly from the Win32 `GetCommState` API, which works regardless of the
|
|
288
|
+
Windows display language; parsing the `mode.com` output is kept only as a
|
|
289
|
+
fallback. If detection fails, the bridge falls back to 9600 baud. To set the
|
|
290
|
+
rate explicitly, pass a numeric value to `--baud`.
|
|
259
291
|
|
|
260
292
|
```cmd
|
|
261
293
|
com2tty COM3 --baud auto
|
|
@@ -343,7 +375,13 @@ these ports during an upload. Run com2tty only on hosts you trust, and choose a
|
|
|
343
375
|
non-default `--rfc2217-port` if another local service needs the default port. To
|
|
344
376
|
reclaim a port left open by a previous com2tty session, the helper only
|
|
345
377
|
terminates processes whose command line identifies them as a com2tty bridge; an
|
|
346
|
-
unrelated service occupying the port is never killed.
|
|
378
|
+
unrelated service occupying the port is never killed. A *running* com2tty
|
|
379
|
+
session is never killed either: each session refreshes a heartbeat marker for
|
|
380
|
+
its ports, so a second invocation that reuses the same `--rfc2217-port` reports
|
|
381
|
+
the conflict and leaves the first bridge intact. Two sessions can run
|
|
382
|
+
concurrently by giving the second one a different `--rfc2217-port` and
|
|
383
|
+
`--wsl-tty`; each session removes only its own block from the shell startup
|
|
384
|
+
files when it exits.
|
|
347
385
|
|
|
348
386
|
### Gamepad mode
|
|
349
387
|
|
|
@@ -380,6 +418,24 @@ interprets them using the device profile below. This tier is suited to programs
|
|
|
380
418
|
that read the stream directly. Standard applications and game engines that
|
|
381
419
|
enumerate `/dev/input` devices do not read a FIFO and require the uinput tier.
|
|
382
420
|
|
|
421
|
+
Force feedback is available in this tier through a second FIFO created at
|
|
422
|
+
`<path>.ff` (by default `/tmp/com2pad0.ff`): the consumer writes 6-byte rumble
|
|
423
|
+
frames into it — the bytes `0xFB 0xFE` followed by the strong (left,
|
|
424
|
+
low-frequency) and weak (right, high-frequency) motor magnitudes as two
|
|
425
|
+
little-endian unsigned 16-bit values — and com2tty forwards them to the
|
|
426
|
+
physical controller's motors, exactly as the uinput tier does for kernel
|
|
427
|
+
`FF_RUMBLE` effects.
|
|
428
|
+
|
|
429
|
+
To forward several controllers at once, pass several slots:
|
|
430
|
+
|
|
431
|
+
```cmd
|
|
432
|
+
com2tty --gamepad --pad-index 0 1
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Each slot gets its own WSL helper and its own endpoint (`/tmp/com2pad0`,
|
|
436
|
+
`/tmp/com2pad1`, ...); in the uinput tier each helper creates its own
|
|
437
|
+
`/dev/input` device, as if several physical controllers were attached.
|
|
438
|
+
|
|
383
439
|
#### Opt-in tier: a real device through /dev/uinput
|
|
384
440
|
|
|
385
441
|
The opt-in tier creates a real system-wide device under `/dev/input` so that SDL2
|
|
@@ -456,8 +512,9 @@ feedback. When a game or emulator inside WSL plays a rumble effect, the
|
|
|
456
512
|
effect's magnitudes travel back through the bridge to the Windows host, which
|
|
457
513
|
drives the physical controller's motors through `XInputSetState`. The strong
|
|
458
514
|
(left, low-frequency) and weak (right, high-frequency) motors map directly to
|
|
459
|
-
their XInput counterparts.
|
|
460
|
-
|
|
515
|
+
their XInput counterparts. In the `/tmp` stream tier the same reverse channel
|
|
516
|
+
is reached by writing rumble frames into the `<path>.ff` FIFO, as described
|
|
517
|
+
in [the default tier](#default-tier-the-tmp-event-stream).
|
|
461
518
|
|
|
462
519
|
The forwarded signal matches a real controller at the level of these event codes,
|
|
463
520
|
ranges, and resolutions, but it is not bit-for-bit identical to a controller
|
|
@@ -471,48 +528,68 @@ available. These differences are inherent to the approach.
|
|
|
471
528
|
|
|
472
529
|
The package is organised around a host process on Windows and a helper process
|
|
473
530
|
inside WSL connected by the standard input and output streams of the helper.
|
|
531
|
+
The code under `src/com2tty/` is split by where it runs: `cli/` is the
|
|
532
|
+
command-line layer, `core/` holds the dependency-free definitions both sides
|
|
533
|
+
share, `windows/` runs on the Windows interpreter, and `wsl/` runs on the
|
|
534
|
+
Linux interpreter inside WSL.
|
|
474
535
|
|
|
475
|
-
`cli
|
|
476
|
-
tokens) and dispatches to an entry function in `
|
|
536
|
+
`cli/` parses the command line (after `cli/profiles.py` expands any `@profile`
|
|
537
|
+
tokens) and dispatches to an entry function in `windows/`: `run_bridge` in
|
|
477
538
|
serial mode, `run_multi_bridge` when several ports are given, and
|
|
478
|
-
`run_gamepad_bridge` in gamepad mode. `discovery.py` implements
|
|
479
|
-
`
|
|
480
|
-
`__init__.py` holds the package
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
`
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
539
|
+
`run_gamepad_bridge` in gamepad mode. `windows/discovery.py` implements
|
|
540
|
+
`--list` and `windows/doctor.py` implements `--doctor`. `__main__.py` and the
|
|
541
|
+
console entry point both call `cli.main`, and `__init__.py` holds the package
|
|
542
|
+
version.
|
|
543
|
+
|
|
544
|
+
`core/` defines the contracts both interpreters rely on: `core/protocol.py`
|
|
545
|
+
holds the `[CONTROL]` message catalogue and the dispatcher the host routes
|
|
546
|
+
stderr lines through, `core/frames.py` the binary gamepad frame codecs,
|
|
547
|
+
`core/boards.py` the USB VID classification with the reset timing data, and
|
|
548
|
+
`core/constants.py` the shared paths, ports, and marker strings.
|
|
549
|
+
|
|
550
|
+
`windows/` is the Windows side. In serial mode `run_bridge` in
|
|
551
|
+
`windows/bridge_app.py` opens the COM port with `pyserial`, spawns the WSL
|
|
552
|
+
helper with `wsl python3 -u bridge.py` (through `windows/wsl_process.py`), and
|
|
553
|
+
runs three threads: one relays bytes from the COM port to the helper's
|
|
554
|
+
standard input, one relays bytes from the helper's standard output to the COM
|
|
555
|
+
port, and one reads the helper's standard error. The standard error stream
|
|
556
|
+
carries a line-oriented control protocol whose messages are prefixed with
|
|
557
|
+
`[CONTROL]`; the handlers in `windows/control_handler.py` drive dynamic
|
|
558
|
+
serial-setting changes, the RFC 2217 session lifecycle, and the UF2 upload
|
|
559
|
+
sequence (including the routine that writes a transferred UF2 image to the
|
|
560
|
+
correct Windows drive, via `windows/uf2_flash.py`). The hot-plug reconnect
|
|
561
|
+
logic lives in `windows/serial_host.py` and the board reset sequences in
|
|
562
|
+
`windows/board_reset.py`. The raw OS-level interventions -- AutoPlay
|
|
563
|
+
suppression, Explorer window closing, `WM_DEVICECHANGE` wake-ups, and console
|
|
564
|
+
VT mode -- are isolated under `windows/os_hacks/`.
|
|
565
|
+
|
|
566
|
+
`wsl/` is the WSL side, restricted to the Python standard library.
|
|
567
|
+
`wsl/serial_app.py` (launched through the `bridge.py` shim at the package
|
|
568
|
+
root) creates a pseudo terminal with `openpty` via `wsl/pty_manager.py`,
|
|
569
|
+
symlinks the requested path to the pseudo-terminal slave, falling back to
|
|
570
|
+
`/tmp` if the requested path is not writable, and runs a `select` loop that
|
|
571
|
+
relays data between the helper's standard input and output and the
|
|
572
|
+
pseudo-terminal master. It also starts the RFC 2217 forwarder and UF2 relay
|
|
573
|
+
threads (`wsl/servers/`), writes the PlatformIO environment variables into
|
|
574
|
+
`~/.bashrc` (`wsl/integrations/shell_env.py`), and installs the `picotool`
|
|
575
|
+
interceptor (`wsl/integrations/picotool.py`).
|
|
576
|
+
`windows/rfc2217_redirector.py` provides the redirector that implements the
|
|
577
|
+
RFC 2217 protocol for the forwarder.
|
|
578
|
+
|
|
579
|
+
The gamepad path reuses the same spawn-and-pipe transport.
|
|
580
|
+
`windows/gamepad_host.py` is the Windows side: it polls an XInput controller
|
|
581
|
+
slot through `ctypes` (preferring the `XInputGetStateEx` export so the Guide
|
|
582
|
+
button is visible) and packs each state snapshot into a fixed 16-byte frame,
|
|
583
|
+
sending a frame only when the state changes. `wsl/gamepad_app.py` (launched
|
|
584
|
+
through the `pad_bridge.py` shim) is the WSL side: it parses the frames,
|
|
585
|
+
translates them into evdev events, and writes them to one of the two sinks in
|
|
586
|
+
`wsl/evdev_sink.py`. The default sink writes to a `/tmp` FIFO, and the opt-in
|
|
587
|
+
sink creates a real device through `/dev/uinput` using raw `ioctl` calls. Both
|
|
588
|
+
sinks share the same event-encoding code, so the byte stream they produce is
|
|
589
|
+
identical. In the uinput sink the helper also services the kernel's
|
|
590
|
+
force-feedback upload handshake and streams played rumble effects back over
|
|
591
|
+
its stdout, where the host applies them to the physical controller with
|
|
592
|
+
`XInputSetState`.
|
|
516
593
|
|
|
517
594
|
For a detailed account of the control protocol, the board reset sequences, the
|
|
518
595
|
reconnection model, the binary frame formats, and the known hardware-unverified
|
|
@@ -572,6 +649,11 @@ step, is documented in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
|
572
649
|
|
|
573
650
|
## Troubleshooting
|
|
574
651
|
|
|
652
|
+
Run `com2tty --doctor` first: it probes the whole environment (WSL, the
|
|
653
|
+
selected distribution's `python3`, drive automounting, `fuser`, the RFC 2217
|
|
654
|
+
and UF2 relay TCP ports, leftovers from crashed sessions, `/dev/uinput`
|
|
655
|
+
access, and the XInput DLL) and prints one actionable line per check.
|
|
656
|
+
|
|
575
657
|
At startup com2tty verifies the WSL environment and reports a specific error if
|
|
576
658
|
a prerequisite is missing. The checks and their remedies are:
|
|
577
659
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "com2tty"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "A Windows COM port to WSL ttyUSB forwarder"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -33,6 +33,9 @@ package-dir = {"" = "src"}
|
|
|
33
33
|
[tool.setuptools.packages.find]
|
|
34
34
|
where = ["src"]
|
|
35
35
|
|
|
36
|
+
[tool.setuptools.package-data]
|
|
37
|
+
"com2tty.wsl" = ["assets/*.in"]
|
|
38
|
+
|
|
36
39
|
[tool.ruff]
|
|
37
40
|
target-version = "py38"
|
|
38
41
|
line-length = 120
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Entry shim for the WSL serial-bridge helper.
|
|
2
|
+
|
|
3
|
+
The Windows host launches this file directly inside WSL with
|
|
4
|
+
``wsl --exec python3 -u .../com2tty/bridge.py``, so it cannot rely on the
|
|
5
|
+
package being importable: when run as a standalone script it puts the
|
|
6
|
+
package's parent directory on ``sys.path`` first. Keeping this file at the
|
|
7
|
+
package root (rather than moving it into ``com2tty/wsl``) preserves the
|
|
8
|
+
exact path the host resolves and verifies (see ``--doctor``).
|
|
9
|
+
|
|
10
|
+
The implementation lives in ``com2tty.wsl`` (``serial_app`` and friends).
|
|
11
|
+
"""
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
if __package__ in (None, ""): # executed as a script inside WSL
|
|
16
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
17
|
+
|
|
18
|
+
from com2tty.wsl.serial_app import main # noqa: E402
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__": # pragma: no cover
|
|
21
|
+
main()
|