com2tty 0.3.0__tar.gz → 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.
- {com2tty-0.3.0/src/com2tty.egg-info → com2tty-0.3.1}/PKG-INFO +36 -16
- {com2tty-0.3.0 → com2tty-0.3.1}/README.md +35 -15
- {com2tty-0.3.0 → com2tty-0.3.1}/pyproject.toml +1 -1
- com2tty-0.3.1/src/com2tty/__init__.py +1 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/bridge.py +1 -1
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/cli/__init__.py +4 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/cli/profiles.py +9 -2
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/core/frames.py +7 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/core/protocol.py +9 -5
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/bridge_app.py +3 -1
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/doctor.py +4 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/gamepad_app.py +11 -2
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/gamepad_host.py +8 -1
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/os_hacks/autoplay.py +6 -2
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/os_hacks/console.py +10 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/os_hacks/device_watcher.py +8 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/os_hacks/explorer.py +32 -3
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/serial_host.py +9 -0
- com2tty-0.3.1/src/com2tty/windows/wsl_process.py +264 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/assets/picotool_wrapper.py.in +6 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/evdev_sink.py +20 -4
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/integrations/shell_env.py +34 -2
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/liveness.py +33 -5
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/pty_manager.py +32 -3
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/serial_app.py +78 -2
- {com2tty-0.3.0 → com2tty-0.3.1/src/com2tty.egg-info}/PKG-INFO +36 -16
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty.egg-info/SOURCES.txt +1 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_bridge_app.py +2 -2
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_cli.py +9 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_core_protocol.py +11 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_evdev_sink.py +27 -8
- com2tty-0.3.1/tests/test_explorer.py +204 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_gamepad_app.py +29 -4
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_liveness.py +50 -9
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_profiles.py +6 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_pty_manager.py +46 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_serial_app.py +86 -3
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_shell_env.py +63 -0
- com2tty-0.3.1/tests/test_wsl_process.py +275 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_xinput.py +23 -19
- com2tty-0.3.0/src/com2tty/__init__.py +0 -1
- com2tty-0.3.0/src/com2tty/windows/wsl_process.py +0 -131
- com2tty-0.3.0/tests/test_wsl_process.py +0 -110
- {com2tty-0.3.0 → com2tty-0.3.1}/LICENSE +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/setup.cfg +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/setup.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/__main__.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/core/__init__.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/core/boards.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/core/constants.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/pad_bridge.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/__init__.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/board_reset.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/control_handler.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/discovery.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/os_hacks/__init__.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/rfc2217_redirector.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/windows/uf2_flash.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/__init__.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/gamepad_app.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/integrations/__init__.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/integrations/picotool.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/servers/__init__.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/servers/base.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/servers/rfc2217_forwarder.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty/wsl/servers/uf2_relay.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty.egg-info/dependency_links.txt +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty.egg-info/entry_points.txt +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty.egg-info/requires.txt +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/src/com2tty.egg-info/top_level.txt +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_autoplay.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_board_reset.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_boards.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_console.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_control_handler.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_core_frames.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_devnotify.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_discovery.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_doctor.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_entry_shims.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_main.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_picotool.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_rfc2217_forwarder.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_rfc2217_redirector.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_serial_host.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_uf2_flash.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_uf2_relay.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_wsl_gamepad_app.py +0 -0
- {com2tty-0.3.0 → com2tty-0.3.1}/tests/test_wsl_servers_base.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: com2tty
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
@@ -116,7 +116,8 @@ The following options apply to both modes.
|
|
|
116
116
|
--version Print the com2tty version and exit.
|
|
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
|
-
description) and exit.
|
|
119
|
+
description) and exit. Supplying a COM port together
|
|
120
|
+
with --list is an argument error.
|
|
120
121
|
--json With --list: print the port list as a JSON array
|
|
121
122
|
instead of an aligned table, for scripts and IDE
|
|
122
123
|
integrations.
|
|
@@ -296,6 +297,10 @@ com2tty @myboard --baud 9600
|
|
|
296
297
|
com2tty @pad
|
|
297
298
|
```
|
|
298
299
|
|
|
300
|
+
A literal argument that must begin with `@` is written with a doubled marker:
|
|
301
|
+
`@@value` is passed through as the literal `@value` and is never interpreted as
|
|
302
|
+
a profile reference.
|
|
303
|
+
|
|
299
304
|
### Automatic baud-rate detection
|
|
300
305
|
|
|
301
306
|
When the baud rate is left at its default value of `auto`, com2tty queries the
|
|
@@ -348,9 +353,11 @@ when zsh is in use or that file exists, and to
|
|
|
348
353
|
`~/.config/fish/conf.d/com2tty.fish` when fish is in use or its configuration
|
|
349
354
|
directory exists. Open a new WSL shell or run `source ~/.bashrc` (or
|
|
350
355
|
`source ~/.zshrc`; fish picks the snippet up automatically) after starting
|
|
351
|
-
com2tty for them to take effect. The variables are removed when com2tty exits
|
|
352
|
-
|
|
353
|
-
|
|
356
|
+
com2tty for them to take effect. The variables are removed when com2tty exits,
|
|
357
|
+
including when the console window is closed: the WSL helper is reaped together
|
|
358
|
+
with the host process rather than left running, and it is given the chance to
|
|
359
|
+
run its own cleanup before it is forced down. If a session is nonetheless killed
|
|
360
|
+
before it can clean up, the next run removes the stale block on startup.
|
|
354
361
|
|
|
355
362
|
Second, com2tty detects the connected board type from its USB vendor identifier
|
|
356
363
|
and performs the appropriate hardware reset on the Windows side. For ESP32-class
|
|
@@ -373,10 +380,13 @@ inside WSL. When PlatformIO calls `picotool` to flash a `.uf2` image, a wrapper
|
|
|
373
380
|
transfers the image back to the Windows host over a relay that listens on
|
|
374
381
|
`127.0.0.1:<rfc2217-port + 1>`. The host then triggers BOOTSEL mode, locates the
|
|
375
382
|
board's mass-storage drive, verifies the transferred image against an MD5
|
|
376
|
-
checksum, and writes the image to the drive.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
383
|
+
checksum, and writes the image to the drive. Subcommands that carry no firmware
|
|
384
|
+
image, such as `picotool info`, `picotool reboot`, and `picotool help`, are
|
|
385
|
+
forwarded to the real binary unchanged, so non-flashing uses of `picotool`
|
|
386
|
+
continue to work while the interception is active. The original `picotool` is
|
|
387
|
+
restored when com2tty exits; if a session is killed before it can restore it, the
|
|
388
|
+
next run detects and reverses the leftover interception on startup, so PlatformIO
|
|
389
|
+
uploads are not left broken.
|
|
380
390
|
|
|
381
391
|
These mechanisms operate without any additional flags. The startup banner reports
|
|
382
392
|
the detected board type, the RFC 2217 port, the UF2 relay port, and the board's
|
|
@@ -392,12 +402,14 @@ non-default `--rfc2217-port` if another local service needs the default port. To
|
|
|
392
402
|
reclaim a port left open by a previous com2tty session, the helper only
|
|
393
403
|
terminates processes whose command line identifies them as a com2tty bridge; an
|
|
394
404
|
unrelated service occupying the port is never killed. A *running* com2tty
|
|
395
|
-
session is never
|
|
396
|
-
its
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
405
|
+
session is never disturbed: before it touches any shared state, a new session
|
|
406
|
+
checks whether its RFC 2217 and UF2 relay ports are already bound and, if so,
|
|
407
|
+
refuses to start rather than overwrite the first session's shell configuration
|
|
408
|
+
or steal its serial endpoint. As a further guard, the WSL helper refuses to
|
|
409
|
+
replace a tty symlink that already points at another live session's pseudo
|
|
410
|
+
terminal. Two sessions can run concurrently by giving the second one a different
|
|
411
|
+
`--rfc2217-port` and `--wsl-tty`; each session removes only its own block from
|
|
412
|
+
the shell startup files when it exits.
|
|
401
413
|
|
|
402
414
|
### Gamepad mode
|
|
403
415
|
|
|
@@ -423,7 +435,10 @@ privileges at run time.
|
|
|
423
435
|
#### Default tier: the /tmp event stream
|
|
424
436
|
|
|
425
437
|
The default tier writes the evdev event stream to a FIFO under `/tmp`, by default
|
|
426
|
-
`/tmp/com2pad0`, and requires no privileged setup.
|
|
438
|
+
`/tmp/com2pad0`, and requires no privileged setup. This FIFO and its
|
|
439
|
+
force-feedback companion (described below) are created with owner-only
|
|
440
|
+
permissions, so another local user on the WSL instance cannot read the input
|
|
441
|
+
stream or inject events into it.
|
|
427
442
|
|
|
428
443
|
```cmd
|
|
429
444
|
com2tty --gamepad
|
|
@@ -691,6 +706,11 @@ package; on minimal distributions install it with `sudo apt install psmisc`, or
|
|
|
691
706
|
select a different port with `--rfc2217-port` (the UF2 relay always uses that
|
|
692
707
|
port plus one).
|
|
693
708
|
|
|
709
|
+
If com2tty instead refuses to start with a message that a port is already in
|
|
710
|
+
use, a second com2tty session is already bound to that port. Give the new session
|
|
711
|
+
a different `--rfc2217-port`, and a different `--wsl-tty`, to run the two
|
|
712
|
+
concurrently.
|
|
713
|
+
|
|
694
714
|
The serial-mode environment variables are written to `~/.bashrc`, to `~/.zshrc`
|
|
695
715
|
when zsh is detected or a `~/.zshrc` file exists, and to
|
|
696
716
|
`~/.config/fish/conf.d/com2tty.fish` when fish is detected. Users of other
|
|
@@ -100,7 +100,8 @@ The following options apply to both modes.
|
|
|
100
100
|
--version Print the com2tty version and exit.
|
|
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
|
-
description) and exit.
|
|
103
|
+
description) and exit. Supplying a COM port together
|
|
104
|
+
with --list is an argument error.
|
|
104
105
|
--json With --list: print the port list as a JSON array
|
|
105
106
|
instead of an aligned table, for scripts and IDE
|
|
106
107
|
integrations.
|
|
@@ -280,6 +281,10 @@ com2tty @myboard --baud 9600
|
|
|
280
281
|
com2tty @pad
|
|
281
282
|
```
|
|
282
283
|
|
|
284
|
+
A literal argument that must begin with `@` is written with a doubled marker:
|
|
285
|
+
`@@value` is passed through as the literal `@value` and is never interpreted as
|
|
286
|
+
a profile reference.
|
|
287
|
+
|
|
283
288
|
### Automatic baud-rate detection
|
|
284
289
|
|
|
285
290
|
When the baud rate is left at its default value of `auto`, com2tty queries the
|
|
@@ -332,9 +337,11 @@ when zsh is in use or that file exists, and to
|
|
|
332
337
|
`~/.config/fish/conf.d/com2tty.fish` when fish is in use or its configuration
|
|
333
338
|
directory exists. Open a new WSL shell or run `source ~/.bashrc` (or
|
|
334
339
|
`source ~/.zshrc`; fish picks the snippet up automatically) after starting
|
|
335
|
-
com2tty for them to take effect. The variables are removed when com2tty exits
|
|
336
|
-
|
|
337
|
-
|
|
340
|
+
com2tty for them to take effect. The variables are removed when com2tty exits,
|
|
341
|
+
including when the console window is closed: the WSL helper is reaped together
|
|
342
|
+
with the host process rather than left running, and it is given the chance to
|
|
343
|
+
run its own cleanup before it is forced down. If a session is nonetheless killed
|
|
344
|
+
before it can clean up, the next run removes the stale block on startup.
|
|
338
345
|
|
|
339
346
|
Second, com2tty detects the connected board type from its USB vendor identifier
|
|
340
347
|
and performs the appropriate hardware reset on the Windows side. For ESP32-class
|
|
@@ -357,10 +364,13 @@ inside WSL. When PlatformIO calls `picotool` to flash a `.uf2` image, a wrapper
|
|
|
357
364
|
transfers the image back to the Windows host over a relay that listens on
|
|
358
365
|
`127.0.0.1:<rfc2217-port + 1>`. The host then triggers BOOTSEL mode, locates the
|
|
359
366
|
board's mass-storage drive, verifies the transferred image against an MD5
|
|
360
|
-
checksum, and writes the image to the drive.
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
367
|
+
checksum, and writes the image to the drive. Subcommands that carry no firmware
|
|
368
|
+
image, such as `picotool info`, `picotool reboot`, and `picotool help`, are
|
|
369
|
+
forwarded to the real binary unchanged, so non-flashing uses of `picotool`
|
|
370
|
+
continue to work while the interception is active. The original `picotool` is
|
|
371
|
+
restored when com2tty exits; if a session is killed before it can restore it, the
|
|
372
|
+
next run detects and reverses the leftover interception on startup, so PlatformIO
|
|
373
|
+
uploads are not left broken.
|
|
364
374
|
|
|
365
375
|
These mechanisms operate without any additional flags. The startup banner reports
|
|
366
376
|
the detected board type, the RFC 2217 port, the UF2 relay port, and the board's
|
|
@@ -376,12 +386,14 @@ non-default `--rfc2217-port` if another local service needs the default port. To
|
|
|
376
386
|
reclaim a port left open by a previous com2tty session, the helper only
|
|
377
387
|
terminates processes whose command line identifies them as a com2tty bridge; an
|
|
378
388
|
unrelated service occupying the port is never killed. A *running* com2tty
|
|
379
|
-
session is never
|
|
380
|
-
its
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
389
|
+
session is never disturbed: before it touches any shared state, a new session
|
|
390
|
+
checks whether its RFC 2217 and UF2 relay ports are already bound and, if so,
|
|
391
|
+
refuses to start rather than overwrite the first session's shell configuration
|
|
392
|
+
or steal its serial endpoint. As a further guard, the WSL helper refuses to
|
|
393
|
+
replace a tty symlink that already points at another live session's pseudo
|
|
394
|
+
terminal. Two sessions can run concurrently by giving the second one a different
|
|
395
|
+
`--rfc2217-port` and `--wsl-tty`; each session removes only its own block from
|
|
396
|
+
the shell startup files when it exits.
|
|
385
397
|
|
|
386
398
|
### Gamepad mode
|
|
387
399
|
|
|
@@ -407,7 +419,10 @@ privileges at run time.
|
|
|
407
419
|
#### Default tier: the /tmp event stream
|
|
408
420
|
|
|
409
421
|
The default tier writes the evdev event stream to a FIFO under `/tmp`, by default
|
|
410
|
-
`/tmp/com2pad0`, and requires no privileged setup.
|
|
422
|
+
`/tmp/com2pad0`, and requires no privileged setup. This FIFO and its
|
|
423
|
+
force-feedback companion (described below) are created with owner-only
|
|
424
|
+
permissions, so another local user on the WSL instance cannot read the input
|
|
425
|
+
stream or inject events into it.
|
|
411
426
|
|
|
412
427
|
```cmd
|
|
413
428
|
com2tty --gamepad
|
|
@@ -675,6 +690,11 @@ package; on minimal distributions install it with `sudo apt install psmisc`, or
|
|
|
675
690
|
select a different port with `--rfc2217-port` (the UF2 relay always uses that
|
|
676
691
|
port plus one).
|
|
677
692
|
|
|
693
|
+
If com2tty instead refuses to start with a message that a port is already in
|
|
694
|
+
use, a second com2tty session is already bound to that port. Give the new session
|
|
695
|
+
a different `--rfc2217-port`, and a different `--wsl-tty`, to run the two
|
|
696
|
+
concurrently.
|
|
697
|
+
|
|
678
698
|
The serial-mode environment variables are written to `~/.bashrc`, to `~/.zshrc`
|
|
679
699
|
when zsh is detected or a `~/.zshrc` file exists, and to
|
|
680
700
|
`~/.config/fish/conf.d/com2tty.fish` when fish is detected. Users of other
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.1"
|
|
@@ -232,6 +232,10 @@ def main():
|
|
|
232
232
|
sys.exit(run_doctor(distro=parsed_args.distro,
|
|
233
233
|
rfc2217_port=parsed_args.rfc2217_port))
|
|
234
234
|
|
|
235
|
+
if parsed_args.list_ports and parsed_args.port:
|
|
236
|
+
parser.error("--list does not take a COM port; remove the positional "
|
|
237
|
+
"argument (it would be silently ignored)")
|
|
238
|
+
|
|
235
239
|
if parsed_args.list_ports:
|
|
236
240
|
from com2tty.windows.discovery import print_port_list
|
|
237
241
|
print_port_list(as_json=parsed_args.json)
|
|
@@ -93,10 +93,17 @@ def load_profile_args(name, search_paths=None):
|
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
def expand_profiles(argv, search_paths=None):
|
|
96
|
-
"""Replace every ``@name`` token in argv with that profile's arguments.
|
|
96
|
+
"""Replace every ``@name`` token in argv with that profile's arguments.
|
|
97
|
+
|
|
98
|
+
A literal argument value that must begin with ``@`` can be escaped by
|
|
99
|
+
doubling the marker: ``@@value`` is passed through as the literal
|
|
100
|
+
``@value`` and is never interpreted as a profile reference.
|
|
101
|
+
"""
|
|
97
102
|
expanded = []
|
|
98
103
|
for token in argv:
|
|
99
|
-
if token.startswith("
|
|
104
|
+
if token.startswith("@@"):
|
|
105
|
+
expanded.append(token[1:])
|
|
106
|
+
elif token.startswith("@") and len(token) > 1:
|
|
100
107
|
expanded.extend(load_profile_args(token[1:], search_paths))
|
|
101
108
|
else:
|
|
102
109
|
expanded.append(token)
|
|
@@ -40,6 +40,13 @@ def pack_frame(index, connected, buttons=0, lt=0, rt=0,
|
|
|
40
40
|
lx=0, ly=0, rx=0, ry=0):
|
|
41
41
|
"""Build a 16-byte controller frame. Pure function, testable anywhere."""
|
|
42
42
|
flags = 0x01 if connected else 0x00
|
|
43
|
+
# Clamp the stick axes to the signed 16-bit range. XInput already reports
|
|
44
|
+
# values in range, but an out-of-range caller would otherwise make
|
|
45
|
+
# struct.pack raise and crash the host instead of degrading gracefully
|
|
46
|
+
# (consistent with the masking applied to the other fields).
|
|
47
|
+
def _clip16(v):
|
|
48
|
+
return max(-32768, min(32767, v))
|
|
49
|
+
lx, ly, rx, ry = _clip16(lx), _clip16(ly), _clip16(rx), _clip16(ry)
|
|
43
50
|
return struct.pack(
|
|
44
51
|
FRAME_FORMAT, FRAME_MAGIC0, FRAME_MAGIC1, index & 0xFF, flags,
|
|
45
52
|
buttons & 0xFFFF, lt & 0xFF, rt & 0xFF,
|
|
@@ -61,8 +61,8 @@ UF2_ACK_LINE = b"[CONTROL] UF2_ACK\n"
|
|
|
61
61
|
def format_line(name, payload=None):
|
|
62
62
|
"""Render one control line (without trailing newline)."""
|
|
63
63
|
if payload is None:
|
|
64
|
-
return "
|
|
65
|
-
return "
|
|
64
|
+
return f"{CONTROL_PREFIX} {name}"
|
|
65
|
+
return f"{CONTROL_PREFIX} {name}:{payload}"
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
def emit(stream, name, payload=None):
|
|
@@ -114,10 +114,14 @@ class ControlDispatcher:
|
|
|
114
114
|
# a hypothetical UF2_UPLOAD registration.
|
|
115
115
|
for name in sorted(self._handlers, key=len, reverse=True):
|
|
116
116
|
if body.startswith(name):
|
|
117
|
-
payload = None
|
|
118
117
|
rest = body[len(name):]
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
# Require an exact name match or a ``:`` payload separator
|
|
119
|
+
# so e.g. a hypothetical ``SETTINGS_RESET`` line is not
|
|
120
|
+
# misrouted to the ``SETTINGS`` handler; a non-separator
|
|
121
|
+
# remainder falls through to the next (shorter) candidate.
|
|
122
|
+
if rest != "" and not rest.startswith(":"):
|
|
123
|
+
continue
|
|
124
|
+
payload = rest[1:] if rest.startswith(":") else None
|
|
121
125
|
return self._handlers[name](
|
|
122
126
|
ControlMessage(name, payload, line_str))
|
|
123
127
|
if self._fallback is not None:
|
|
@@ -279,7 +279,9 @@ def _print_bridge_banner(port, board_type, rfc2217_port, usb_serial, env_setup):
|
|
|
279
279
|
print(f"{yellow} [WARNING] Environment variables injected into your WSL shell rc (~/.bashrc, ~/.zshrc){reset}")
|
|
280
280
|
print(f"{yellow} Please OPEN A NEW WSL TERMINAL or run `source ~/.bashrc` (or ~/.zshrc){reset}")
|
|
281
281
|
else:
|
|
282
|
-
print(f"{cyan} Secondary bridge: PlatformIO env vars
|
|
282
|
+
print(f"{cyan} Secondary bridge: PlatformIO env vars were set up once by the{reset}")
|
|
283
|
+
print(f"{cyan} primary (first) port -- no shell changes are needed here. Use the{reset}")
|
|
284
|
+
print(f"{cyan} primary bridge's terminal output for the `source` instructions.{reset}")
|
|
283
285
|
print(f"{yellow}========================================================================{reset}\n")
|
|
284
286
|
|
|
285
287
|
|
|
@@ -167,6 +167,10 @@ def check_xinput(os_name=os.name):
|
|
|
167
167
|
|
|
168
168
|
def run_doctor(distro=None, rfc2217_port=4000):
|
|
169
169
|
"""Run all checks, print one line per result, return the exit status."""
|
|
170
|
+
# Each WSL probe can block for up to 30s if WSL is cold-starting or hung;
|
|
171
|
+
# without this the tool looks frozen while it waits.
|
|
172
|
+
print("Running com2tty environment checks (WSL probes can take a few "
|
|
173
|
+
"seconds each if WSL is starting up)...", flush=True)
|
|
170
174
|
results = [check_wsl_exe()]
|
|
171
175
|
wsl_ok = results[0][0] == OK
|
|
172
176
|
if wsl_ok:
|
|
@@ -99,8 +99,12 @@ def run_gamepad_bridge(pad_index=0, poll_hz=250, name="Microsoft X-Box 360 pad",
|
|
|
99
99
|
break
|
|
100
100
|
for left, right in reader.feed(data):
|
|
101
101
|
src.set_rumble(left, right)
|
|
102
|
-
except Exception:
|
|
103
|
-
|
|
102
|
+
except Exception as e:
|
|
103
|
+
# Don't die silently: without this the rumble channel just stops
|
|
104
|
+
# with no clue why. Shutdown-time pipe errors are expected, so
|
|
105
|
+
# only surface a failure while the bridge is meant to be running.
|
|
106
|
+
if not shutdown_event.is_set():
|
|
107
|
+
logging.warning(f"Gamepad rumble reader thread stopped: {e}")
|
|
104
108
|
|
|
105
109
|
t_logs = threading.Thread(target=read_wsl_logs, daemon=True)
|
|
106
110
|
t_out = threading.Thread(target=drain_wsl_stdout, daemon=True)
|
|
@@ -146,6 +150,11 @@ def run_gamepad_bridge(pad_index=0, poll_hz=250, name="Microsoft X-Box 360 pad",
|
|
|
146
150
|
finally:
|
|
147
151
|
shutdown_event.set()
|
|
148
152
|
logging.info("Cleaning up gamepad bridge...")
|
|
153
|
+
# terminate_wsl_helper closes the helper's stdin, which the helper's
|
|
154
|
+
# select loop sees as EOF and exits on; the daemon reader threads then
|
|
155
|
+
# unblock when proc.stdout/stderr reach EOF. Do NOT close those read
|
|
156
|
+
# pipes here: closing a pipe another thread is blocked reading
|
|
157
|
+
# deadlocks on Windows (the reader holds the file lock).
|
|
149
158
|
terminate_wsl_helper(proc)
|
|
150
159
|
logging.info("Gamepad bridge stopped successfully.")
|
|
151
160
|
return exit_reason
|
|
@@ -8,6 +8,7 @@ sends 6-byte rumble frames back over its stdout. Both formats are defined
|
|
|
8
8
|
in ``com2tty.core.frames``.
|
|
9
9
|
"""
|
|
10
10
|
import ctypes
|
|
11
|
+
import os
|
|
11
12
|
|
|
12
13
|
from ..core.frames import ( # noqa: F401 (re-exports kept for compatibility)
|
|
13
14
|
FRAME_FORMAT,
|
|
@@ -61,10 +62,16 @@ class _XINPUT_VIBRATION(ctypes.Structure):
|
|
|
61
62
|
|
|
62
63
|
|
|
63
64
|
def _load_xinput():
|
|
65
|
+
# Load by absolute System32 path rather than bare name so a stray
|
|
66
|
+
# ``xinput1_4.dll`` in the current working directory cannot be loaded
|
|
67
|
+
# in preference to the system copy (DLL hijacking).
|
|
68
|
+
system_dir = os.path.join(
|
|
69
|
+
os.environ.get("SystemRoot", r"C:\Windows"), "System32")
|
|
64
70
|
last_err = None
|
|
65
71
|
for name in _XINPUT_DLLS:
|
|
72
|
+
dll_path = os.path.join(system_dir, name + ".dll")
|
|
66
73
|
try:
|
|
67
|
-
return
|
|
74
|
+
return ctypes.WinDLL(dll_path)
|
|
68
75
|
except OSError as exc: # pragma: no cover - depends on host DLLs
|
|
69
76
|
last_err = exc
|
|
70
77
|
continue
|
|
@@ -23,8 +23,12 @@ _AUTOPLAY_VALUE_NAME = "DisableAutoplay"
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def _autoplay_marker_path():
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
# Keep the recovery marker in the user's private LocalAppData rather than
|
|
27
|
+
# the world-writable system temp dir: the static filename there is open to
|
|
28
|
+
# file-squatting and symlink/junction attacks that could block execution
|
|
29
|
+
# or feed attacker-controlled values back into the registry restore.
|
|
30
|
+
base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~")
|
|
31
|
+
return os.path.join(base, AUTOPLAY_MARKER_FILENAME)
|
|
28
32
|
|
|
29
33
|
|
|
30
34
|
def _restore_autoplay_state(existed, original_value):
|
|
@@ -9,6 +9,16 @@ def enable_vt_mode():
|
|
|
9
9
|
try:
|
|
10
10
|
import ctypes
|
|
11
11
|
kernel32 = ctypes.windll.kernel32
|
|
12
|
+
# GetStdHandle returns a HANDLE (pointer-sized); without an explicit
|
|
13
|
+
# restype ctypes truncates it to a 32-bit int before it is handed to
|
|
14
|
+
# Get/SetConsoleMode.
|
|
15
|
+
kernel32.GetStdHandle.argtypes = [ctypes.c_uint32]
|
|
16
|
+
kernel32.GetStdHandle.restype = ctypes.c_void_p
|
|
17
|
+
kernel32.GetConsoleMode.argtypes = [
|
|
18
|
+
ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint32)]
|
|
19
|
+
kernel32.GetConsoleMode.restype = ctypes.c_int # BOOL
|
|
20
|
+
kernel32.SetConsoleMode.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
|
|
21
|
+
kernel32.SetConsoleMode.restype = ctypes.c_int # BOOL
|
|
12
22
|
handle = kernel32.GetStdHandle(-11) # STD_OUTPUT_HANDLE
|
|
13
23
|
mode = ctypes.c_uint32()
|
|
14
24
|
if not kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
|
|
@@ -75,6 +75,14 @@ class DeviceChangeWatcher:
|
|
|
75
75
|
user32 = ctypes.windll.user32
|
|
76
76
|
user32.CreateWindowExW.restype = ctypes.c_void_p
|
|
77
77
|
user32.RegisterDeviceNotificationW.restype = ctypes.c_void_p
|
|
78
|
+
# DefWindowProcW returns an LRESULT (pointer-sized). Without an
|
|
79
|
+
# explicit restype ctypes truncates it to a 32-bit int, so the
|
|
80
|
+
# value the window procedure hands back to the OS is corrupted on
|
|
81
|
+
# 64-bit Windows.
|
|
82
|
+
user32.DefWindowProcW.restype = ctypes.c_ssize_t
|
|
83
|
+
user32.DefWindowProcW.argtypes = [
|
|
84
|
+
ctypes.c_void_p, ctypes.c_uint,
|
|
85
|
+
ctypes.c_void_p, ctypes.c_void_p]
|
|
78
86
|
|
|
79
87
|
hwnd = user32.CreateWindowExW(
|
|
80
88
|
0, "STATIC", "com2tty-devnotify", 0, 0, 0, 0, 0,
|
|
@@ -18,8 +18,31 @@ from ...core.constants import EXPLORER_WINDOW_CLASS, UF2_VOLUME_LABELS
|
|
|
18
18
|
WM_CLOSE = 0x0010
|
|
19
19
|
SW_HIDE = 0
|
|
20
20
|
|
|
21
|
-
#: How often the background closer re-scans the desktop window list.
|
|
22
|
-
|
|
21
|
+
#: How often the background closer re-scans the desktop window list. 100 ms
|
|
22
|
+
#: is imperceptible to the user but enumerating every top-level window at
|
|
23
|
+
#: 100 Hz burned noticeable CPU, especially with many windows open.
|
|
24
|
+
CLOSER_SCAN_INTERVAL = 0.1
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _declare_window_apis(ctypes, EnumWindows, EnumWindowsProc, GetClassNameW,
|
|
28
|
+
GetWindowTextW, ShowWindow, PostMessageW):
|
|
29
|
+
"""Pin the user32 signatures so 64-bit HWNDs are not truncated.
|
|
30
|
+
|
|
31
|
+
Window handles are pointer-sized; passed through ctypes' default 32-bit
|
|
32
|
+
int argument type they would be clipped on 64-bit Windows, addressing the
|
|
33
|
+
wrong (or no) window.
|
|
34
|
+
"""
|
|
35
|
+
EnumWindows.argtypes = [EnumWindowsProc, ctypes.c_void_p]
|
|
36
|
+
EnumWindows.restype = ctypes.c_bool
|
|
37
|
+
GetClassNameW.argtypes = [ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int]
|
|
38
|
+
GetClassNameW.restype = ctypes.c_int
|
|
39
|
+
GetWindowTextW.argtypes = [ctypes.c_void_p, ctypes.c_wchar_p, ctypes.c_int]
|
|
40
|
+
GetWindowTextW.restype = ctypes.c_int
|
|
41
|
+
ShowWindow.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
|
42
|
+
ShowWindow.restype = ctypes.c_bool
|
|
43
|
+
PostMessageW.argtypes = [
|
|
44
|
+
ctypes.c_void_p, ctypes.c_uint, ctypes.c_void_p, ctypes.c_void_p]
|
|
45
|
+
PostMessageW.restype = ctypes.c_bool
|
|
23
46
|
|
|
24
47
|
|
|
25
48
|
def close_explorer_for_drive(drive_letter):
|
|
@@ -32,6 +55,9 @@ def close_explorer_for_drive(drive_letter):
|
|
|
32
55
|
GetWindowTextW = ctypes.windll.user32.GetWindowTextW
|
|
33
56
|
ShowWindow = ctypes.windll.user32.ShowWindow
|
|
34
57
|
PostMessageW = ctypes.windll.user32.PostMessageW
|
|
58
|
+
_declare_window_apis(ctypes, EnumWindows, EnumWindowsProc,
|
|
59
|
+
GetClassNameW, GetWindowTextW, ShowWindow,
|
|
60
|
+
PostMessageW)
|
|
35
61
|
|
|
36
62
|
dl = drive_letter[0].upper()
|
|
37
63
|
# Use the parenthesised "(X:)" form Explorer renders in titles; a
|
|
@@ -61,7 +87,7 @@ class BootselWindowCloser:
|
|
|
61
87
|
"""Background thread closing BOOTSEL Explorer windows as they appear.
|
|
62
88
|
|
|
63
89
|
Scans every ``CLOSER_SCAN_INTERVAL`` seconds while a UF2 flash is in
|
|
64
|
-
progress, so a window AutoPlay manages to open is hidden within ~
|
|
90
|
+
progress, so a window AutoPlay manages to open is hidden within ~100 ms.
|
|
65
91
|
``target_letters`` is a *live* list: the flash routine appends the
|
|
66
92
|
discovered drive letter once known, and subsequent scans match it too.
|
|
67
93
|
"""
|
|
@@ -90,6 +116,9 @@ class BootselWindowCloser:
|
|
|
90
116
|
GetWindowTextW = ctypes.windll.user32.GetWindowTextW
|
|
91
117
|
ShowWindow = ctypes.windll.user32.ShowWindow
|
|
92
118
|
PostMessageW = ctypes.windll.user32.PostMessageW
|
|
119
|
+
_declare_window_apis(ctypes, EnumWindows, EnumWindowsProc,
|
|
120
|
+
GetClassNameW, GetWindowTextW, ShowWindow,
|
|
121
|
+
PostMessageW)
|
|
93
122
|
|
|
94
123
|
def foreach_window(hwnd, lParam):
|
|
95
124
|
class_name = ctypes.create_unicode_buffer(256)
|
|
@@ -80,7 +80,16 @@ def get_commstate_baudrate(port, _kernel32=None):
|
|
|
80
80
|
|
|
81
81
|
try:
|
|
82
82
|
kernel32 = _kernel32 if _kernel32 is not None else ctypes.windll.kernel32
|
|
83
|
+
# Declare the Win32 signatures so 64-bit handles/pointers are not
|
|
84
|
+
# truncated to ctypes' default 32-bit int.
|
|
85
|
+
kernel32.CreateFileW.argtypes = [
|
|
86
|
+
ctypes.c_wchar_p, ctypes.c_uint32, ctypes.c_uint32,
|
|
87
|
+
ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_void_p]
|
|
83
88
|
kernel32.CreateFileW.restype = ctypes.c_void_p
|
|
89
|
+
kernel32.GetCommState.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
|
90
|
+
kernel32.GetCommState.restype = ctypes.c_int # BOOL
|
|
91
|
+
kernel32.CloseHandle.argtypes = [ctypes.c_void_p]
|
|
92
|
+
kernel32.CloseHandle.restype = ctypes.c_int # BOOL
|
|
84
93
|
# The \\.\ prefix is required for COM10 and above, harmless below.
|
|
85
94
|
handle = kernel32.CreateFileW(
|
|
86
95
|
"\\\\.\\" + port, GENERIC_READ | GENERIC_WRITE, 0, None,
|