diff --git a/Cargo.lock b/Cargo.lock index 3248cad..2b3c47c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1866,6 +1866,20 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml", + "vswhom", + "winreg", +] + [[package]] name = "endi" version = "1.1.1" @@ -4387,6 +4401,7 @@ dependencies = [ "eframe", "egui", "egui-phosphor", + "embed-resource", "futures", "image", "parking_lot", @@ -6248,6 +6263,26 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -7161,6 +7196,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + [[package]] name = "wit-bindgen" version = "0.57.1" diff --git a/Cargo.toml b/Cargo.toml index 9d91509..63fe8b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,6 +103,8 @@ dbus = "0.9" windows-sys = "0.61" # Toasts on Windows. Linux notifications go over the `dbus` above. tauri-winrt-notification = "0.8" +# The exe's own icon resource, which is what Explorer and a taskbar pin draw. +embed-resource = "3" rpassword = "7" libloading = "0.8" lru = "0.18" diff --git a/crates/pstr-app/Cargo.toml b/crates/pstr-app/Cargo.toml index b40587e..7580863 100644 --- a/crates/pstr-app/Cargo.toml +++ b/crates/pstr-app/Cargo.toml @@ -34,6 +34,9 @@ directories.workspace = true chrono.workspace = true souvlaki.workspace = true +[build-dependencies] +embed-resource.workspace = true + [dev-dependencies] skrifa.workspace = true diff --git a/crates/pstr-app/build.rs b/crates/pstr-app/build.rs new file mode 100644 index 0000000..31d5fa8 --- /dev/null +++ b/crates/pstr-app/build.rs @@ -0,0 +1,15 @@ +//! Compiles `proton-stream.rc` into the Windows exe; nothing on any other +//! target. `rc.exe` on an MSVC build, mingw's `windres` on the cross build +//! from Linux. + +fn main() { + println!("cargo:rerun-if-changed=proton-stream.rc"); + println!("cargo:rerun-if-changed=../../packaging/windows/proton-stream.ico"); + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") { + return; + } + // Only the GUI gets the icon; `pstr` is a console tool. + embed_resource::compile_for("proton-stream.rc", ["proton-stream"], embed_resource::NONE) + .manifest_optional() + .expect("compile the icon resource"); +} diff --git a/crates/pstr-app/proton-stream.rc b/crates/pstr-app/proton-stream.rc new file mode 100644 index 0000000..3951549 --- /dev/null +++ b/crates/pstr-app/proton-stream.rc @@ -0,0 +1,4 @@ +// The exe's icon. The window sets one at runtime too, but that only lasts as +// long as the window: Explorer, the Start menu and a taskbar pin read the icon +// out of the file, and without this one they fall back to the generic one. +1 ICON "../../packaging/windows/proton-stream.ico" diff --git a/docs/BUGS.md b/docs/BUGS.md index 4e647cc..83fe739 100644 --- a/docs/BUGS.md +++ b/docs/BUGS.md @@ -146,6 +146,54 @@ and [B44](#b44--r8-broke-the-app-under-instrumentation-in-five-places). ## Fixed +### B63 — a taskbar pin on Windows lost its icon + +**Symptom.** Proton Stream pinned to the Windows taskbar showed its icon at +first, then after a while — reliably after an update — the generic program +icon. + +**Cause.** Two, each enough on its own. `proton-stream.exe` carried no icon +resource: the window sets one at runtime, which is what a pin shows while the +icon cache still holds it, but a pin made from the running window points at +the exe, and once the cache is rebuilt the exe has nothing to give. A pin made +from the Start Menu shortcut instead took the shortcut's `Icon`, which Windows +Installer keeps in `C:\Windows\Installer\{ProductCode}`. Every upgrade has a +new ProductCode and deletes the old product's folder, icon included. + +**Fix.** `crates/pstr-app/build.rs` compiles `proton-stream.rc`, which embeds +`packaging/windows/proton-stream.ico`, into the GUI exe: `rc.exe` on MSVC, +mingw's `windres` on the cross build. Both installer definitions drop the +shortcut's `Icon` (and the WiX one `Advertise`), so the shortcut shows the +exe's icon too. A pin that already lost its icon keeps the dead reference; +unpin and pin again once. + +**Verified.** `objdump -p` on the cross-built exe lists the `RT_ICON` entries, +all seven sizes. `msiinfo export … Shortcut` on a wixl build has an empty +`Icon_` column. Not yet checked on a Windows taskbar across an upgrade. + +### B62 — an MSI upgrade left the app without libmpv + +**Symptom.** Installing a new `.msi` over an old one ended with an app that +would not start for want of mpv. Installing the same `.msi` a second time +fixed it. + +**Cause.** Both installers remove the old product early — the wixl one at +`RemoveExistingProducts` after `InstallInitialize`, WiX's `MajorUpgrade` by +default after `InstallValidate` — but costing runs before either, at +`CostFinalize`. Costing found the old `libmpv-2.dll` on disk with the same +version as the new one, since mpv is pinned, and decided not to copy it. Then +removing the old product deleted it. The second install found no DLL on disk, +so it copied one. + +**Fix.** `REINSTALLMODE=amus` in both installer definitions, which copies every +file whatever is on disk. Every file is private to the install directory, so +the forced overwrite can downgrade nothing shared. + +**Verified.** `msiinfo export … InstallExecuteSequence` on a wixl build shows +`FileCost` 900 and `CostFinalize` 1000 ahead of `RemoveExistingProducts` 1501, +and `msiinfo export … Property` has `REINSTALLMODE amus`. Not yet checked as an +upgrade on Windows. + ### B61 — later seasons were captioned with season one's episode names **Symptom.** On a 42-title AniList library, Attack on Titan seasons 2–4 and diff --git a/packaging/README.md b/packaging/README.md index 7cd2f7a..87917e0 100644 --- a/packaging/README.md +++ b/packaging/README.md @@ -124,7 +124,7 @@ The `.icns` is rasterized from `io.narl.proton-stream.svg` when `rsvg-convert` o | `io.narl.proton-stream.desktop` / `.svg` | Freedesktop entry and icon | | `macos/Info.plist.in` | `.app` bundle plist; `@VERSION@`/`@BIN@`/`@APP_ID@` substituted at build time | | `windows/proton-stream.wxs` | WiX v4+ installer definition | -| `windows/proton-stream.ico` | MSI and shortcut icon, rasterized from the SVG | +| `windows/proton-stream.ico` | Exe and Add/Remove Programs icon, rasterized from the SVG; `crates/pstr-app/build.rs` compiles it into `proton-stream.exe` | | `windows/license.rtf` | Fallback for the installer's license dialog; the repo's own `LICENSE` is preferred when it exists | The Debian `control` file has no template — `scripts/build.sh` writes it, because diff --git a/packaging/windows/proton-stream.wixl.wxs b/packaging/windows/proton-stream.wixl.wxs index 94b7e74..59f8189 100644 --- a/packaging/windows/proton-stream.wixl.wxs +++ b/packaging/windows/proton-stream.wixl.wxs @@ -78,6 +78,15 @@ + + + @@ -98,14 +107,19 @@ and writes the advertised form anyway, with the feature name in Shortcut/Target. What selects the non-advertised form is being a child of the Component rather than of the File, plus an - explicit formatted Target. Keep it out of the . --> + explicit formatted Target. Keep it out of the . + + No Icon either. A shortcut's Icon is copied to + C:\Windows\Installer\{ProductCode}, and every upgrade has a + new ProductCode and deletes the old folder — taking the icon + of every taskbar pin made from the shortcut with it. Without + one, the shortcut shows the exe's own icon resource. --> + WorkingDirectory="INSTALLDIR" /> + + @@ -45,12 +54,16 @@ + + Target="[#AppExe]" + WorkingDirectory="INSTALLFOLDER" />