Skip to content
Merged

B2 #136

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

| <img height="20" src="https://raw.githubusercontent.com/BurntToasters/bcls/main/media/windows.png" /> Windows | <img height="20" src="https://raw.githubusercontent.com/BurntToasters/bcls/main/media/mac.png" /> macOS | <img height="20" src="https://raw.githubusercontent.com/BurntToasters/bcls/main/media/linux.png" /> Linux |
| :--- | :--- | :--- |
| **MSI:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Windows-x64.msi) <!-- / [arm64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Windows-arm64.msi) --> | **[Universal DMG](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-macOS.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-x86_64.AppImage) <!-- / [arm64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-arm64.AppImage) --> |
| | **[Universal ZIP](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-macOS.zip)** | **DEB:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-amd64.deb) <!-- / [arm64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-arm64.deb) --> |
| | | **RPM:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-x86_64.rpm) <!-- / [arm64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-aarch64.rpm) --> |
| | | **Flatpak:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-x86_64.flatpak) <!-- / [arm64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-aarch64.flatpak) --> |
| | | **TAR (Generic Linux):** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.1/Dacx-Linux-x86_64.tar.gz) |
| **MSI:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Windows-x64.msi) <!-- / [arm64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Windows-arm64.msi) --> | **[Universal DMG](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-macOS.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-x86_64.AppImage) <!-- / [arm64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-arm64.AppImage) --> |
| | **[Universal ZIP](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-macOS.zip)** | **DEB:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-amd64.deb) <!-- / [arm64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-arm64.deb) --> |
| | | **RPM:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-x86_64.rpm) <!-- / [arm64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-aarch64.rpm) --> |
| | | **Flatpak:** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-x86_64.flatpak) <!-- / [arm64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-aarch64.flatpak) --> |
| | | **TAR (Generic Linux):** [x64](https://github.com/BurntToasters/Dacx/releases/download/v0.11.1-beta.2/Dacx-Linux-x86_64.tar.gz) |

> [!IMPORTANT]
> The `.asc` files are my normal GPG signatures which you can verify using my GPG Public Key: https://tuxedo.rosie.run/GPG/BurntToasters_0xF2FBC20F_public.asc.
Expand All @@ -18,6 +18,10 @@

### ℹ️ Enjoying Dacx? Consider [❤️ Supporting Me! ❤️](https://rosie.run/support)

## Changes in `v0.11.1-beta.2:`

- **Fix - Windows updater Authenticode check:** Self-update failed with `Get-AuthenticodeSignature ... LiteralPath is null` because PowerShell `-Command <string>` does not populate `$args` from trailing process argv. The MSI path is now embedded (and single-quote-escaped) in the verification script. Sorry about that! This is why we have betas lol :P

## Changes in `v0.11.1-beta.1:`

- **Fix - Windows tray:** Minimize-to-tray left an empty notification-area slot because the tray API requires a multi-size `.ico`, not a PNG; tray and bundled assets now use `assets/icon/icon.ico` generated from `assets/icon/icon.png`.
Expand Down
2 changes: 1 addition & 1 deletion flatpak/run.rosie.dacx.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# x-version: 0.11.1-beta.1
# x-version: 0.11.1-beta.2
app-id: run.rosie.dacx
runtime: org.freedesktop.Platform
runtime-version: "25.08"
Expand Down
38 changes: 29 additions & 9 deletions lib/services/self_update_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,34 @@ class SelfUpdateService {
return hash.toLowerCase();
}

/// Escapes a path for use inside a PowerShell single-quoted string.
@visibleForTesting
static String escapePowerShellSingleQuoted(String value) {
return value.replaceAll("'", "''");
}

/// Builds the `-Command` script that reads Authenticode for [msiPath].
///
/// The MSI path is embedded in the script. PowerShell's `-Command <string>`
/// form does **not** populate `$args` from trailing process argv (so
/// `-LiteralPath $args[0]` always sees null and breaks Windows self-update).
@visibleForTesting
static String buildWindowsAuthenticodeCommand(String msiPath) {
final pathLiteral = escapePowerShellSingleQuoted(msiPath);
return [
r"$ErrorActionPreference = 'Stop';",
r"$securityModule = Join-Path $PSHOME 'Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1';",
r"Import-Module -Name $securityModule -Force -ErrorAction Stop;",
"\$sig = Get-AuthenticodeSignature -LiteralPath '$pathLiteral';",
r"if ($null -eq $sig) { Write-Output 'UnknownError|||Get-AuthenticodeSignature returned null'; exit 1 }",
r"$thumb = if ($sig.SignerCertificate) { $sig.SignerCertificate.Thumbprint } else { '' };",
r"$publisher = if ($sig.SignerCertificate) { $sig.SignerCertificate.GetNameInfo([System.Security.Cryptography.X509Certificates.X509NameType]::SimpleName, $false) } else { '' };",
r"$status = if ($null -ne $sig.Status) { $sig.Status.ToString() } else { 'UnknownError' };",
r"$message = if ($null -ne $sig.StatusMessage) { $sig.StatusMessage } else { '' };",
r"Write-Output ($status + '|' + $thumb + '|publisher:' + $publisher + '|' + $message)",
].join(' ');
}

Future<SelfUpdateResult> _validateWindowsInstallerSignature(File file) async {
final expected = normalizeCertificateThumbprint(
_expectedWindowsSignerThumbprintOverride ??
Expand All @@ -889,21 +917,13 @@ class SelfUpdateService {
);
}

final authenticodeCommand = [
r"$securityModule = Join-Path $PSHOME 'Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1';",
r"Import-Module -Name $securityModule -Force -ErrorAction Stop;",
r"$sig = Get-AuthenticodeSignature -LiteralPath $args[0];",
r"$thumb = if ($sig.SignerCertificate) { $sig.SignerCertificate.Thumbprint } else { '' };",
r"$publisher = if ($sig.SignerCertificate) { $sig.SignerCertificate.GetNameInfo([System.Security.Cryptography.X509Certificates.X509NameType]::SimpleName, $false) } else { '' };",
r"Write-Output ($sig.Status.ToString() + '|' + $thumb + '|publisher:' + $publisher + '|' + $sig.StatusMessage)",
].join(' ');
final authenticodeCommand = buildWindowsAuthenticodeCommand(file.path);
final result = await _processRun(WindowsSystemPaths.powershell(), [
'-NoProfile',
'-ExecutionPolicy',
'Bypass',
'-Command',
authenticodeCommand,
file.path,
]);
if (result.exitCode != 0) {
return SelfUpdateResult(
Expand Down
2 changes: 1 addition & 1 deletion linux/packaging/control.template
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Package: dacx
Version: 0.11.1~beta.1
Version: 0.11.1~beta.2
Section: sound
Priority: optional
Architecture: amd64
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dacx",
"version": "0.11.1-beta.1",
"version": "0.11.1-beta.2",
"private": true,
"description": "Fast, lightweight cross-platform music and video player for Windows, macOS, and Linux.",
"license": "GPL-3.0-only",
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: dacx
description: "Quick, lightweight cross-platform media player."
publish_to: 'none'
version: 0.11.1-beta.1+1101
version: 0.11.1-beta.2+1101

environment:
sdk: ^3.10.7
Expand Down
5 changes: 5 additions & 0 deletions run.rosie.dacx.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@
<mediatype>application/pls+xml</mediatype>
</provides>
<releases>
<release version="0.11.1-beta.2" date="2026-07-29">
<description>
<p>Fix Windows self-update Authenticode verification (MSI path was not passed into PowerShell correctly).</p>
</description>
</release>
<release version="0.11.1-beta.1" date="2026-07-29">
<description>
<p>Fix Windows system tray icon (multi-size ICO). macOS menu bar tray template icon. Unified Windows icon.ico from icon.png. FVM setup repairs SDK and regenerates l10n.</p>
Expand Down
30 changes: 29 additions & 1 deletion test/services/self_update_windows_validation_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,18 @@ void main() {
);
final result = await svc.validateWindowsInstallerSignatureForTesting(msi);
expect(result.outcome, SelfUpdateOutcome.spawned);
final joined = invokedArguments?.join(' ') ?? '';
expect(
invokedArguments?.join(' '),
joined,
contains(
r'Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1',
),
);
// Path must be embedded in -Command; trailing argv is not $args for
// PowerShell -Command <string> and previously made LiteralPath null.
expect(joined, contains(msi.path));
expect(joined, isNot(contains(r'$args[0]')));
expect(invokedArguments, isNot(contains(msi.path)));
});

test('returns signatureInvalid when PowerShell exits non-zero', () async {
Expand All @@ -305,4 +311,26 @@ void main() {
expect(result.message, contains('access denied'));
});
});

group('SelfUpdateService.buildWindowsAuthenticodeCommand', () {
test('embeds MSI path and escapes single quotes', () {
final command = SelfUpdateService.buildWindowsAuthenticodeCommand(
r"C:\Users\O'Brien\AppData\Local\Dacx\updates\Dacx.msi",
);
expect(
command,
contains(
r"Get-AuthenticodeSignature -LiteralPath 'C:\Users\O''Brien\AppData\Local\Dacx\updates\Dacx.msi'",
),
);
expect(command, isNot(contains(r'$args[0]')));
});

test('escapePowerShellSingleQuoted doubles apostrophes', () {
expect(
SelfUpdateService.escapePowerShellSingleQuoted("a'b'c"),
"a''b''c",
);
});
});
}