Skip to content

Commit b59f6cb

Browse files
huayingclaude
andcommitted
Release 0.4.11: three small fixes closing out the backlog
- **#40 Skip VDD reinstall on upgrade**: install_vdd() is now idempotent (no-op if MttVDD device node already registered), and uninstall_service no longer auto-removes VDD. The old behaviour shunted user windows off-screen on every phantom-server upgrade because Windows migrated VDD-hosted windows to the physical display during the few seconds VDD was absent. For a full wipe operators now call the new --uninstall-vdd flag explicitly (symmetric to the existing --install-vdd from 0.4.3). - **#41 Error-swallowing cleanup in session.rs**: input-inject + IPC-forward failures now log via tracing::warn! instead of being silently dropped. In Windows Service mode a broken IPC pipe previously silently lost every keystroke with no signal. spawn_receive_thread also now breaks its read loop when the session receiver is gone, instead of reading + discarding from the network until the peer happens to close. - **#42 --decoder flag strict validation**: clap now rejects values outside {auto, openh264, videotoolbox, dav1d, nvdec} instead of silently falling through to OpenH264. Help text updated to explain that on non-macOS `dav1d` / `nvdec` currently still follow the auto- probe chain rather than acting as forced selectors — honest docs over silent misdirection. Also closed task #25 as decided (stay with MTT VDD) and moved #34 (AV1 codec negotiation) to deferred — the blocker is upstream AV1 decode maturity, not the protocol work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 47f4b35 commit b59f6cb

14 files changed

Lines changed: 196 additions & 129 deletions

File tree

Cargo.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bench/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "phantom-bench"
3-
version = "0.4.10"
3+
version = "0.4.11"
44
edition = "2021"
55

66
[dependencies]

crates/bench/src/main.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,7 @@ impl OpenH264Bench {
161161

162162
fn component_bench(cuda: Option<&Arc<CudaLib>>, av1_supported: bool) {
163163
println!("=== Component bench (encoder only, {ROUNDS} rounds + {WARMUP} warmup) ===\n");
164-
println!(
165-
"{:<8} {:>8} {:<10} Result",
166-
"Res", "Bitrate", "Encoder"
167-
);
164+
println!("{:<8} {:>8} {:<10} Result", "Res", "Bitrate", "Encoder");
168165
println!("{}", "-".repeat(80));
169166

170167
for &(w, h, label) in RESOLUTIONS {

crates/client/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "phantom-client"
3-
version = "0.4.10"
3+
version = "0.4.11"
44
edition = "2021"
55

66
[dependencies]

crates/client/src/main.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,20 @@ struct Args {
5454
no_encrypt: bool,
5555
#[arg(long, default_value = "tcp")]
5656
transport: String,
57-
/// Video decoder: auto (default), openh264 (CPU), videotoolbox (macOS GPU).
58-
#[arg(long, default_value = "auto")]
57+
/// Video decoder hint. Accepted values: auto (default), openh264,
58+
/// videotoolbox, dav1d, nvdec.
59+
///
60+
/// The hint is honoured fully on macOS (auto / videotoolbox take the
61+
/// hardware path; anything else forces OpenH264). On Linux / Windows
62+
/// `auto` auto-probes NVDEC → dav1d (AV1 only) → OpenH264, and
63+
/// `dav1d` / `nvdec` currently fall through the same probe chain —
64+
/// they aren't forced selectors. Passing a value outside the list
65+
/// errors here instead of silently falling through like older builds.
66+
#[arg(
67+
long,
68+
default_value = "auto",
69+
value_parser = ["auto", "openh264", "videotoolbox", "dav1d", "nvdec"]
70+
)]
5971
decoder: String,
6072
/// Send a file to the server after connecting.
6173
#[arg(long)]

crates/core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "phantom-core"
3-
version = "0.4.10"
3+
version = "0.4.11"
44
edition = "2021"
55

66
[features]

crates/gpu/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "phantom-gpu"
3-
version = "0.4.10"
3+
version = "0.4.11"
44
edition = "2021"
55

66
[features]

crates/server/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "phantom-server"
3-
version = "0.4.10"
3+
version = "0.4.11"
44
edition = "2021"
55

66
[features]

crates/server/src/main.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,15 @@ struct Args {
103103
#[arg(long)]
104104
install_vdd: bool,
105105

106+
/// (Windows only) Remove only the Virtual Display Driver and leave the
107+
/// phantom-server service alone. Use this when you actually want VDD
108+
/// gone — `--uninstall` no longer touches VDD by default because
109+
/// removing + reinstalling it on every upgrade was shunting user
110+
/// windows off-screen.
111+
#[cfg(target_os = "windows")]
112+
#[arg(long)]
113+
uninstall_vdd: bool,
114+
106115
/// Send a file to the first client that connects.
107116
#[arg(long)]
108117
send_file: Option<String>,
@@ -327,6 +336,15 @@ fn main() -> Result<()> {
327336
);
328337
return service_win::install_vdd(&install_dir);
329338
}
339+
#[cfg(target_os = "windows")]
340+
if args.uninstall_vdd {
341+
let install_dir = std::path::PathBuf::from(r"C:\Program Files\Phantom");
342+
println!(
343+
"Removing Virtual Display Driver at {}",
344+
install_dir.display()
345+
);
346+
return service_win::uninstall_vdd(&install_dir);
347+
}
330348

331349
let frame_interval = Duration::from_secs_f64(1.0 / args.fps as f64);
332350

crates/server/src/service_win.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,9 +1174,42 @@ fn ps_unzip(zip: &std::path::Path, dest: &std::path::Path) -> anyhow::Result<()>
11741174

11751175
/// Install the Virtual Display Driver for headless GPU servers.
11761176
/// Downloads VDD + nefcon from GitHub, installs driver via nefconw.
1177+
/// Ask pnputil whether any MttVDD device node is currently registered.
1178+
/// Used by `install_vdd` to skip the reinstall on upgrade and by
1179+
/// callers that want to check state without doing any install work.
1180+
pub fn vdd_device_present() -> bool {
1181+
let out = std::process::Command::new("pnputil")
1182+
.args(["/enum-devices", "/connected"])
1183+
.output();
1184+
match out {
1185+
Ok(o) => {
1186+
let s = String::from_utf8_lossy(&o.stdout);
1187+
// MttVDD's hardware id is `Root\MttVDD`. Match case-insensitive
1188+
// on "MttVDD" to cover localized pnputil output.
1189+
s.to_lowercase().contains("mttvdd")
1190+
}
1191+
Err(_) => false,
1192+
}
1193+
}
1194+
11771195
pub fn install_vdd(install_dir: &std::path::Path) -> anyhow::Result<()> {
11781196
use anyhow::Context;
11791197

1198+
// Idempotent: if the MttVDD device node is already present, skip the
1199+
// whole download + nefcon dance. This is the upgrade path — phantom-
1200+
// server `--uninstall` + `--install` was previously causing VDD to
1201+
// disappear for a few seconds, which Windows would respond to by
1202+
// migrating all VDD-hosted windows (browser, IDE, ...) onto the
1203+
// physical display. Those windows save the new position and end up
1204+
// off-screen from phantom's capture viewport. Skipping the reinstall
1205+
// when the driver is already registered avoids the window migration
1206+
// entirely. For a full wipe run `--uninstall-vdd` explicitly.
1207+
if vdd_device_present() {
1208+
println!(" Virtual Display Driver already installed — skipping reinstall");
1209+
println!(" (use --uninstall-vdd to force full removal)");
1210+
return Ok(());
1211+
}
1212+
11801213
let vdd_dir = install_dir.join("vdd");
11811214
std::fs::create_dir_all(&vdd_dir).context("create vdd dir")?;
11821215

@@ -1594,10 +1627,14 @@ pub fn uninstall_service() -> anyhow::Result<()> {
15941627
anyhow::bail!("sc delete failed with {status}. Run as Administrator.");
15951628
}
15961629

1597-
// Remove Virtual Display Driver
1598-
let install_dir = std::path::PathBuf::from(r"C:\Program Files\Phantom");
1599-
if let Err(e) = uninstall_vdd(&install_dir) {
1600-
println!(" Warning: VDD uninstall failed: {e}");
1630+
// Intentionally *not* removing the Virtual Display Driver here — we
1631+
// want `--uninstall` to be safe to run as part of an upgrade. The
1632+
// previous behaviour (always removing VDD) caused every upgrade to
1633+
// shuffle user windows onto the physical display while VDD was
1634+
// briefly absent, leaving those windows off-screen after reinstall.
1635+
// For a full wipe, operators call `--uninstall-vdd` explicitly.
1636+
if vdd_device_present() {
1637+
println!(" (Virtual Display Driver left in place; use --uninstall-vdd to remove it)");
16011638
}
16021639

16031640
// Clean up schtasks

0 commit comments

Comments
 (0)