mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Windows matches a filename without regard to its case, so Dikte.exe and dikte.exe were one file in the installation directory: PyInstaller wrote the console build second and that is the only one that landed. Every launcher then opened a console window behind the tray, and closing it killed the application. The console one is dikte-cli.exe now, which the setup's shim names. The build reads the PE subsystem of both afterwards, since a filesystem that keeps the two names apart is exactly what the check would run on otherwise.
110 lines
5.4 KiB
PowerShell
110 lines
5.4 KiB
PowerShell
#!/usr/bin/env pwsh
|
|
# The Windows download: one setup program, carrying everything Dikte needs to
|
|
# record and to be started again after a sign-in.
|
|
#
|
|
# Run from anywhere; it works in build\ at the top of the checkout and leaves
|
|
# the finished .exe in dist\. x64 only, because that is what the PyQt6 wheel and
|
|
# whisper.cpp both publish for Windows; a Windows on ARM machine runs it under
|
|
# the emulation it runs everything else under.
|
|
#
|
|
# powershell -ExecutionPolicy Bypass -File packaging\build-windows.ps1
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$root = Split-Path -Parent $PSScriptRoot
|
|
$build = Join-Path $root "build"
|
|
$out = Join-Path $root "dist"
|
|
$dist = Join-Path $build "dist\dikte"
|
|
|
|
$env:PYTHONPATH = $root
|
|
$version = & python -c "import dikte; print(dikte.__version__)"
|
|
if ($LASTEXITCODE -ne 0) { throw "could not read the version out of dikte/__init__.py" }
|
|
|
|
# A pinned tag and a checksum rather than "whatever is newest": this binary goes
|
|
# out inside something people run, so what it is has to be decided here and not
|
|
# by whoever pushes to that repository next. The same release the disk image
|
|
# takes its ffmpeg from, which is gyan.dev's essentials build repackaged, and
|
|
# dshow is in it, which is the one part of ffmpeg recording here goes through.
|
|
$ffmpegTag = "b6.1.1"
|
|
$ffmpegAsset = "ffmpeg-win32-x64.gz"
|
|
$ffmpegSha = "8883A3DFFBD0A16CF4EF95206EA05283F78908DBFB118F73C83F4951DCC06D77"
|
|
|
|
if (Test-Path $build) { Remove-Item $build -Recurse -Force }
|
|
if (Test-Path $out) { Remove-Item $out -Recurse -Force }
|
|
New-Item -ItemType Directory -Path $build, $out | Out-Null
|
|
|
|
# 1. The icon ---------------------------------------------------------------
|
|
# Drawn by Dikte itself, offscreen, which is why there is no image file in the
|
|
# repository. Before the application, because PyInstaller writes it into the
|
|
# executable rather than beside it, and the setup program uses the same file.
|
|
$icon = Join-Path $build "Dikte.ico"
|
|
$env:QT_QPA_PLATFORM = "offscreen"
|
|
& python -m dikte.trayicon --ico $icon
|
|
if ($LASTEXITCODE -ne 0) { throw "the icon would not draw" }
|
|
Remove-Item Env:\QT_QPA_PLATFORM
|
|
$env:DIKTE_ICO = $icon
|
|
|
|
# 2. The application --------------------------------------------------------
|
|
# Two executables in the one directory: Dikte.exe, which is windowed and is
|
|
# what a shortcut starts, and dikte-cli.exe, which has a console and is what
|
|
# the `dikte` command runs. Their names differ by more than case on purpose;
|
|
# Windows would otherwise keep one file for both.
|
|
& python -m PyInstaller (Join-Path $root "packaging\dikte.spec") `
|
|
--distpath (Join-Path $build "dist") --workpath (Join-Path $build "work") `
|
|
--noconfirm --clean
|
|
if ($LASTEXITCODE -ne 0) { throw "PyInstaller failed" }
|
|
|
|
# And the check that the two are really two. Windows matches a filename
|
|
# without regard to its case, so a rename that leaves them a case apart puts
|
|
# one file in the directory and whichever was written second is what both
|
|
# names find. It cost a windowed build once, silently: the machines the
|
|
# packaging is otherwise checked on all have case-sensitive filesystems, so
|
|
# this only ever shows up here. Subsystem 2 is windowed, 3 is a console, and
|
|
# it is the field the loader reads to decide which to give.
|
|
function Get-PeSubsystem($path) {
|
|
$bytes = [System.IO.File]::ReadAllBytes($path)
|
|
$header = [BitConverter]::ToInt32($bytes, 0x3C)
|
|
return [BitConverter]::ToUInt16($bytes, $header + 0x5C)
|
|
}
|
|
foreach ($pair in @(@("Dikte.exe", 2), @("dikte-cli.exe", 3))) {
|
|
$subsystem = Get-PeSubsystem (Join-Path $dist $pair[0])
|
|
if ($subsystem -ne $pair[1]) {
|
|
throw "$($pair[0]) is subsystem $subsystem, expected $($pair[1]): the two executables collided"
|
|
}
|
|
}
|
|
|
|
# 3. ffmpeg -----------------------------------------------------------------
|
|
# Recording on Windows goes through ffmpeg's DirectShow input, and Windows
|
|
# ships nothing like it, so without this the download would be an application
|
|
# that cannot record until the person who downloaded it installs one. bin\
|
|
# beside the executables, because integrate.py puts that directory in front of
|
|
# PATH at startup and everything reaching for ffmpeg goes through shutil.which.
|
|
$archive = Join-Path $build $ffmpegAsset
|
|
Invoke-WebRequest -UseBasicParsing -OutFile $archive `
|
|
"https://github.com/eugeneware/ffmpeg-static/releases/download/$ffmpegTag/$ffmpegAsset"
|
|
$got = (Get-FileHash $archive -Algorithm SHA256).Hash
|
|
if ($got -ne $ffmpegSha) { throw "ffmpeg checksum: expected $ffmpegSha, got $got" }
|
|
|
|
$bin = Join-Path $dist "bin"
|
|
New-Item -ItemType Directory -Path $bin | Out-Null
|
|
$compressed = [System.IO.File]::OpenRead($archive)
|
|
$stream = New-Object System.IO.Compression.GzipStream(
|
|
$compressed, [System.IO.Compression.CompressionMode]::Decompress)
|
|
$binary = [System.IO.File]::Create((Join-Path $bin "ffmpeg.exe"))
|
|
try { $stream.CopyTo($binary) } finally { $binary.Dispose(); $stream.Dispose(); $compressed.Dispose() }
|
|
|
|
# 4. The setup program ------------------------------------------------------
|
|
# Inno Setup comes with the GitHub runner. On a machine that has not got it:
|
|
# winget install JRSoftware.InnoSetup
|
|
$iscc = (Get-Command iscc -ErrorAction SilentlyContinue).Source
|
|
if (-not $iscc) {
|
|
$iscc = Join-Path ${env:ProgramFiles(x86)} "Inno Setup 6\ISCC.exe"
|
|
}
|
|
if (-not (Test-Path $iscc)) {
|
|
throw "no Inno Setup found. Install it with: winget install JRSoftware.InnoSetup"
|
|
}
|
|
& $iscc "/DVersion=$version" "/DSource=$dist" "/DIcon=$icon" `
|
|
(Join-Path $root "packaging\dikte.iss")
|
|
if ($LASTEXITCODE -ne 0) { throw "Inno Setup failed" }
|
|
|
|
Write-Host "dist\Dikte-$version-x64-setup.exe"
|