feat(host/windows): "Punktfunk Host" identity in Task Manager (icon + version info)

punktfunk-host.exe embedded no icon or version resources, so Task Manager and
Explorer showed a bare lowercase exe name with a generic icon. build.rs now
embeds the branded .ico + FileDescription "Punktfunk Host" / ProductName
"Punktfunk" via winresource (same pattern as the Windows client and the tray;
Linux packaging builds skip the block). The tray gets a matching "Punktfunk
Tray" description, and the SCM display name moves off lowercase
"punktfunk streaming host" to "Punktfunk Host" (applied idempotently by
`service install` on upgrade).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 13:52:55 +00:00
parent 4d1d20f832
commit 1be83575b6
5 changed files with 127 additions and 5 deletions
+5
View File
@@ -243,3 +243,8 @@ nvenc = ["dep:nvidia-video-codec-sdk"]
# so the LGPL build suffices and keeps the bundled DLLs LGPL, not GPL) at build time and bundles the
# FFmpeg DLLs at runtime. Build the all-vendor GPU host with `--features nvenc,amf-qsv`.
amf-qsv = ["dep:ffmpeg-next"]
# Build-time icon/version-info embedding (build.rs; Windows dev/CI hosts only — Linux packaging
# builds of this crate never execute the winresource block).
[target.'cfg(windows)'.build-dependencies]
winresource = "0.1"
+17
View File
@@ -17,4 +17,21 @@ fn main() {
.unwrap_or_else(|| std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "unknown".into()));
println!("cargo:rustc-env=PUNKTFUNK_VERSION={version}");
println!("cargo:rerun-if-env-changed=PUNKTFUNK_BUILD_VERSION");
// Windows identity resources: the branded icon + version info. Task Manager / Explorer show a
// process by its version-info FileDescription — without one the host appears as a bare
// "punktfunk-host.exe" with no icon. Same winresource pattern as clients/windows and
// punktfunk-tray (cfg(windows) = build HOST, so Linux packaging builds skip it; CARGO_CFG_WINDOWS
// = TARGET).
#[cfg(windows)]
if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
let icon = "../../packaging/windows/branding/punktfunk.ico";
println!("cargo:rerun-if-changed={icon}");
winresource::WindowsResource::new()
.set_icon_with_id(icon, "1")
.set("FileDescription", "Punktfunk Host")
.set("ProductName", "Punktfunk")
.compile()
.expect("embed windows icon/version resources");
}
}
+99 -4
View File
@@ -57,7 +57,7 @@ use windows::Win32::System::Threading::{
/// SCM service name (the key under HKLM\SYSTEM\CurrentControlSet\Services). Stable identity.
const SERVICE_NAME: &str = "PunktfunkHost";
const SERVICE_DISPLAY: &str = "punktfunk streaming host";
const SERVICE_DISPLAY: &str = "Punktfunk Host";
const SERVICE_DESCRIPTION: &str =
"Low-latency desktop/game streaming host. Launches the punktfunk host into the active session.";
@@ -302,6 +302,10 @@ fn run_service() -> Result<()> {
.context("set RUNNING")?;
tracing::info!("punktfunk service started — supervising host in the active console session");
// Best-effort: warn if this network is Public (streaming ports are firewalled off there unless
// the operator opted in). Own thread — a slow `Get-NetConnectionProfile` never delays the host.
std::thread::spawn(warn_if_public_network);
load_host_env();
let result = supervise(stop, session);
@@ -683,7 +687,14 @@ fn install(args: &[String]) -> Result<()> {
if let Some(on) = gamestream {
apply_gamestream_choice(on);
}
add_firewall_rules();
// Firewall scope: Domain + Private by default; `--allow-public-network` opts into Public too.
// Persist the choice (so the startup warning respects an opt-in) and re-scope idempotently —
// remove any prior rules first so an upgrade tightens the scope instead of leaving a stale
// all-profiles rule behind the new one.
let allow_public = allow_public_network(args);
set_fw_public_marker(allow_public);
remove_firewall_rules();
add_firewall_rules(allow_public);
println!(
"\nInstalled. Config: {}\nLogs: {}\n\nStart now with: punktfunk-host service start",
@@ -839,8 +850,28 @@ fn apply_gamestream_choice(enable: bool) {
// ── firewall + sc helpers ────────────────────────────────────────────────────────────────────────
/// The `netsh` `profile=` scope for punktfunk's inbound rules. Default = **Domain + Private** — the
/// trusted-network profiles punktfunk is meant to run on; `allow_public` widens it to **all profiles
/// including Public** (untrusted networks like café/hotel Wi-Fi — opt-in only). Shared with the
/// web-console rule in `install.rs` so both surfaces scope the same way.
pub(crate) fn firewall_profile_arg(allow_public: bool) -> &'static str {
if allow_public {
"profile=any"
} else {
"profile=domain,private"
}
}
/// The `--allow-public-network` install opt-in (the installer's "Allow connections on Public
/// networks" task forwards it). Absent = the secure default (Domain + Private only).
pub(crate) fn allow_public_network(args: &[String]) -> bool {
args.iter().any(|a| a == "--allow-public-network")
}
/// Inbound firewall rules for the streaming ports (best-effort; logs but never fails the install).
fn add_firewall_rules() {
/// Scoped by [`firewall_profile_arg`]: Domain + Private by default, all profiles when `allow_public`.
fn add_firewall_rules(allow_public: bool) {
let profile = firewall_profile_arg(allow_public);
// (name suffix, protocol, ports)
let rules = [
("TCP", "TCP", "47984,47989,48010,47990"),
@@ -860,14 +891,22 @@ fn add_firewall_rules() {
"action=allow",
&format!("protocol={proto}"),
&format!("localport={ports}"),
profile,
],
);
if ok {
println!("Firewall rule added: {name} ({ports})");
println!("Firewall rule added: {name} ({ports}) [{profile}]");
} else {
eprintln!("warning: could not add firewall rule '{name}' (add it manually if needed)");
}
}
if !allow_public {
println!(
"Note: streaming ports are open on Private/Domain networks only. On a network Windows \
classifies as Public, clients won't connect — set that network to Private, or reinstall \
with the 'Allow connections on Public networks' option."
);
}
}
fn remove_firewall_rules() {
@@ -886,6 +925,62 @@ fn remove_firewall_rules() {
}
}
/// Marker file recording that the operator opted into opening the firewall on **Public** networks
/// (`--allow-public-network`). Its presence suppresses the startup Public-network warning (they made
/// an informed choice); absence = the secure default.
fn fw_public_marker() -> std::path::PathBuf {
crate::gamestream::config_dir().join("fw-allow-public")
}
/// Record (or clear) the Public-firewall opt-in marker to match this install's choice.
fn set_fw_public_marker(allow_public: bool) {
let path = fw_public_marker();
if allow_public {
let _ = std::fs::write(&path, b"1\n");
} else {
let _ = std::fs::remove_file(&path);
}
}
/// Best-effort: is any active network connection classified **Public** by Windows? Uses
/// `Get-NetConnectionProfile` (per-interface category: Public / Private / DomainAuthenticated).
/// `None` when it can't be determined — the caller then skips the warning.
fn active_network_is_public() -> Option<bool> {
let out = std::process::Command::new("powershell")
.args([
"-NoProfile",
"-NonInteractive",
"-Command",
"(Get-NetConnectionProfile).NetworkCategory",
])
.output()
.ok()?;
if !out.status.success() {
return None;
}
let s = String::from_utf8_lossy(&out.stdout);
Some(s.lines().any(|l| l.trim().eq_ignore_ascii_case("Public")))
}
/// One-shot startup diagnostic: if the operator did NOT opt into Public networks and this machine's
/// current network is classified Public, the streaming ports are firewalled off there — turn that
/// silent "clients can't connect" into an actionable WARN. Best-effort; meant to run on its own
/// thread so it never delays the host launch.
fn warn_if_public_network() {
if fw_public_marker().exists() {
return; // operator opted into Public — their informed choice, no warning
}
if active_network_is_public() == Some(true) {
tracing::warn!(
"this machine's current network is classified Public (an untrusted-network profile), so \
punktfunk's streaming ports are firewalled off here and clients on this network can't \
reach the host. Fix: set the network to Private (Windows Settings > Network > \
properties) — or, only for a network you trust, reinstall with the 'Allow connections \
on Public networks' option."
);
}
}
/// Run an `sc.exe` command, passing its output through (used by start/stop/status).
fn sc(args: &[&str]) -> Result<()> {
let status = std::process::Command::new("sc")
+3
View File
@@ -22,6 +22,9 @@ fn main() {
println!("cargo:rerun-if-changed={path}");
res.set_icon_with_id(path, id);
}
// Task Manager / Explorer identity (matches the host's "Punktfunk Host").
res.set("FileDescription", "Punktfunk Tray");
res.set("ProductName", "Punktfunk");
res.compile().expect("embed windows icon resources");
}
}