Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cdab2fde1 | ||
|
|
6239780978 | ||
|
|
67cf3044f1 | ||
|
|
66be35c3d4 | ||
|
|
046cfe62b8 |
No files matched your search
@@ -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"
|
||||
@@ -4359,7 +4373,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pstr-android"
|
||||
version = "1.1.1"
|
||||
version = "1.1.3"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"jni 0.21.1",
|
||||
@@ -4378,7 +4392,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pstr-app"
|
||||
version = "1.1.1"
|
||||
version = "1.1.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -4387,6 +4401,7 @@ dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
"egui-phosphor",
|
||||
"embed-resource",
|
||||
"futures",
|
||||
"image",
|
||||
"parking_lot",
|
||||
@@ -4409,7 +4424,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pstr-cli"
|
||||
version = "1.1.1"
|
||||
version = "1.1.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -4427,7 +4442,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pstr-core"
|
||||
version = "1.1.1"
|
||||
version = "1.1.3"
|
||||
dependencies = [
|
||||
"directories",
|
||||
"futures",
|
||||
@@ -4445,7 +4460,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pstr-meta"
|
||||
version = "1.1.1"
|
||||
version = "1.1.3"
|
||||
dependencies = [
|
||||
"pstr-core",
|
||||
"reqwest",
|
||||
@@ -4458,7 +4473,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pstr-player"
|
||||
version = "1.1.1"
|
||||
version = "1.1.3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"libloading",
|
||||
@@ -4473,7 +4488,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pstr-stream"
|
||||
version = "1.1.1"
|
||||
version = "1.1.3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"futures",
|
||||
@@ -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"
|
||||
|
||||
@@ -11,7 +11,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.1.1"
|
||||
version = "1.1.3"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/narrrl/proton-stream"
|
||||
@@ -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"
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.narl.protonstream.ui.theme
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -9,6 +10,7 @@ import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBarItemDefaults
|
||||
import androidx.compose.material3.NavigationDrawerItemDefaults
|
||||
@@ -18,14 +20,18 @@ import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteDefaul
|
||||
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteItemColors
|
||||
import androidx.compose.material3.SliderState
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
@@ -68,17 +74,30 @@ internal fun AccentButton(
|
||||
content: @Composable RowScope.() -> Unit,
|
||||
) {
|
||||
val shape = MaterialTheme.shapes.small
|
||||
Button(
|
||||
onClick = onClick,
|
||||
// Only while it is live: a disabled button keeps Material's flat
|
||||
// disabled container, because a greyed control that still wears the
|
||||
// one strong colour in the window reads as pressable.
|
||||
modifier = if (enabled) modifier.background(accentBrush(), shape) else modifier,
|
||||
enabled = enabled,
|
||||
shape = shape,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),
|
||||
content = content,
|
||||
)
|
||||
// The button pads itself out to the 48dp minimum touch target, and a
|
||||
// background on its modifier fills that whole target — 8dp taller than
|
||||
// the 40dp container every other button draws. So the padding is taken
|
||||
// here, outside the ramp, and the button inside is told not to add its
|
||||
// own. The box hands its size down, so a `fillMaxWidth` still reaches it.
|
||||
Box(
|
||||
modifier.minimumInteractiveComponentSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
propagateMinConstraints = true,
|
||||
) {
|
||||
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides Dp.Unspecified) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
// Only while it is live: a disabled button keeps Material's flat
|
||||
// disabled container, because a greyed control that still wears the
|
||||
// one strong colour in the window reads as pressable.
|
||||
modifier = if (enabled) Modifier.background(accentBrush(), shape) else Modifier,
|
||||
enabled = enabled,
|
||||
shape = shape,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
|
Before Width: | Height: | Size: 813 KiB After Width: | Height: | Size: 813 KiB |
|
Before Width: | Height: | Size: 769 KiB After Width: | Height: | Size: 768 KiB |
|
Before Width: | Height: | Size: 807 KiB After Width: | Height: | Size: 806 KiB |
|
Before Width: | Height: | Size: 638 KiB After Width: | Height: | Size: 631 KiB |
|
Before Width: | Height: | Size: 678 KiB After Width: | Height: | Size: 669 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
@@ -34,6 +34,9 @@ directories.workspace = true
|
||||
chrono.workspace = true
|
||||
souvlaki.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
embed-resource.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
skrifa.workspace = true
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# builds from the registry alone.
|
||||
|
||||
pkgname=proton-stream
|
||||
pkgver=1.1.1
|
||||
pkgver=1.1.3
|
||||
pkgrel=1
|
||||
pkgdesc="Netflix-style desktop client for Proton Drive public links: browse and stream a share"
|
||||
arch=('x86_64')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -78,6 +78,15 @@
|
||||
<RemoveExistingProducts After="InstallInitialize" />
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<!-- Copy every file on an upgrade, whatever is on disk. Costing runs
|
||||
before RemoveExistingProducts, so it sees the old libmpv-2.dll — the
|
||||
same version, since mpv is pinned — and decides not to copy it. The
|
||||
removal of the old product then deletes it, and the upgrade ends with
|
||||
no DLL: "mpv missing" until a second install, which finds nothing on
|
||||
disk and copies it. Every file here is private to INSTALLDIR, so
|
||||
overwriting unconditionally downgrades nothing shared. -->
|
||||
<Property Id="REINSTALLMODE" Value="amus" />
|
||||
|
||||
<Media Id="1" Cabinet="proton_stream.cab" EmbedCab="yes" />
|
||||
|
||||
<Icon Id="proton-stream.ico" SourceFile="$(var.IconFile)" />
|
||||
@@ -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 <File>. -->
|
||||
explicit formatted Target. Keep it out of the <File>.
|
||||
|
||||
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. -->
|
||||
<Shortcut Id="StartMenuShortcut"
|
||||
Directory="ProgramMenuFolder"
|
||||
Name="Proton Stream"
|
||||
Description="Browse and stream a Proton Drive public link"
|
||||
Target="[INSTALLDIR]proton-stream.exe"
|
||||
WorkingDirectory="INSTALLDIR"
|
||||
Icon="proton-stream.ico" />
|
||||
WorkingDirectory="INSTALLDIR" />
|
||||
</Component>
|
||||
|
||||
<!-- The CLI, and the PATH entry that makes it worth installing. Tied
|
||||
|
||||
@@ -26,6 +26,15 @@
|
||||
DowngradeErrorMessage="A newer version of Proton Stream is already installed." />
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<!-- Copy every file on an upgrade, whatever is on disk. Costing runs
|
||||
before RemoveExistingProducts, so it sees the old libmpv-2.dll — the
|
||||
same version, since mpv is pinned — and decides not to copy it. The
|
||||
removal of the old product then deletes it, and the upgrade ends with
|
||||
no DLL: "mpv missing" until a second install, which finds nothing on
|
||||
disk and copies it. Every file here is private to INSTALLFOLDER, so
|
||||
overwriting unconditionally downgrades nothing shared. -->
|
||||
<Property Id="REINSTALLMODE" Value="amus" />
|
||||
|
||||
<Icon Id="AppIcon" SourceFile="$(var.IconFile)" />
|
||||
<Property Id="ARPPRODUCTICON" Value="AppIcon" />
|
||||
<Property Id="ARPURLINFOABOUT" Value="https://github.com/narrrl/proton-stream" />
|
||||
@@ -45,12 +54,16 @@
|
||||
<ComponentGroup Id="AppComponents" Directory="INSTALLFOLDER">
|
||||
<Component Guid="623c8cb8-b826-4d57-a054-e5be56f24c19">
|
||||
<File Id="AppExe" Source="$(var.BinDir)\proton-stream.exe" KeyPath="yes" />
|
||||
<!-- Not advertised, and no Icon: either one puts the shortcut's icon
|
||||
in C:\Windows\Installer\{ProductCode}, which the next upgrade
|
||||
deletes along with the icon of every taskbar pin made from the
|
||||
shortcut. This way it shows the exe's own icon resource. -->
|
||||
<Shortcut Id="StartMenuShortcut"
|
||||
Directory="ProgramMenuFolder"
|
||||
Name="Proton Stream"
|
||||
Description="Browse and stream a Proton Drive public link"
|
||||
Icon="AppIcon"
|
||||
Advertise="yes" />
|
||||
Target="[#AppExe]"
|
||||
WorkingDirectory="INSTALLFOLDER" />
|
||||
</Component>
|
||||
|
||||
<!-- The CLI, and the PATH entry that makes it worth installing. Tied to
|
||||
|
||||