Warn instead of failing when a forwarded host port is in use in the Windows 11 launcher

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GuofangandClaude Sonnet 5.5 committed 2026-10-15 12:40:19 +08:00
1 parent e1e5a2149c
commit c63c25c7b1
1 file changed
+29 -3
+29 -3
View File
@@ -4,6 +4,7 @@
import os import os
import platform import platform
import shutil import shutil
import socket
import string import string
import subprocess import subprocess
import sys import sys
@@ -33,6 +34,17 @@ def capture(command):
return result.stdout.rstrip("\n") return result.stdout.rstrip("\n")
def port_in_use(port):
"""True if a host TCP port cannot be bound, as QEMU does for a hostfwd rule."""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
probe.bind(("", int(port)))
except OSError:
return True
return False
def is_executable(path): def is_executable(path):
return path.is_file() and os.access(path, os.X_OK) return path.is_file() and os.access(path, os.X_OK)
@@ -296,6 +308,19 @@ except (KeyError, ValueError) as error:
die(f"invalid placeholder in {qemu_config_template}: {error}") die(f"invalid placeholder in {qemu_config_template}: {error}")
qemu_config.write_text(qemu_config_text) qemu_config.write_text(qemu_config_text)
# QEMU refuses to start when a forwarded host port is taken, so warn and leave
# that forward out instead.
forwards = []
forwarded_ports = []
for label, host_port, guest_port in (("RDP", rdp_port, 3389), ("SSH", ssh_port, 22)):
in_use = port_in_use(host_port)
forwarded_ports.append((label, host_port, not in_use))
if in_use:
print(f"Warning: host port {host_port} is already in use; "
f"{label} will not be forwarded to the guest.", file=sys.stderr)
else:
forwards.append(f"hostfwd=tcp::{host_port}-:{guest_port}")
qemu_args = [ qemu_args = [
"-readconfig", str(qemu_config), "-readconfig", str(qemu_config),
"-readconfig", str(qemu_arch_config), "-readconfig", str(qemu_arch_config),
@@ -304,7 +329,7 @@ qemu_args = [
# A config file section cannot hold two hostfwd entries, so the network # A config file section cannot hold two hostfwd entries, so the network
# that the NIC in the config file uses is defined here. -nic none stops # that the NIC in the config file uses is defined here. -nic none stops
# QEMU from adding its default NIC as well. # QEMU from adding its default NIC as well.
"-netdev", f"user,id=net0,hostfwd=tcp::{rdp_port}-:3389,hostfwd=tcp::{ssh_port}-:22", "-netdev", ",".join(["user,id=net0", *forwards]),
"-nic", "none", "-nic", "none",
"-display", display_backend, "-display", display_backend,
# "-display", "none", "-spice", "port=5900,addr=127.0.0.1,disable-ticketing=on,agent-mouse=off", "-device", "virtio-vga", # "-display", "none", "-spice", "port=5900,addr=127.0.0.1,disable-ticketing=on,agent-mouse=off", "-device", "virtio-vga",
@@ -324,8 +349,9 @@ elif installing:
print("Starting Windows 11 from the QCOW2 base image.") print("Starting Windows 11 from the QCOW2 base image.")
else: else:
print("Starting Windows 11 normally.") print("Starting Windows 11 normally.")
print(f"Account: guofang / password; RDP: localhost:{rdp_port}; SSH: localhost:{ssh_port}", print("Account: guofang / password; " + "; ".join(
flush=True) f"{label}: localhost:{port}" for label, port, forwarded in forwarded_ports if forwarded),
flush=True)
if daemonize: if daemonize:
log_file.write_text("") log_file.write_text("")