Skip to content
Draft
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
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ cmake --build build-msvc
SDL2 is optional but is the preferred cross-platform live display backend. If it
is missing from vcpkg, install it with `vcpkg install sdl2:x64-windows`.

Windows packaging lives under `packaging/windows/`:

- `packaging/windows/build-installer.ps1` stages the Release build and runs
`ISCC.exe`.
- `packaging/windows/hasciicam.iss` defines the installer.
- The installer is x64-only, machine-wide, and ships the virtual-camera helper
as an optional default-on component.
- `hasciicam_vcamctl.exe` owns virtual-camera install and uninstall actions.

The root `GNUmakefile` is a legacy Linux-oriented path. It assumes Unix linker
flags such as SDL, X11, ncurses, and `libm`. Do not treat it as the
cross-platform source of truth.
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ cmake --preset windows-vcpkg-ninja
```
Other presets are `linux-ninja`, `macos-ninja`, and `wasm-emscripten`.

## Windows Installer

HasciiCam ships a Windows installer built with Inno Setup 7. The packaging
entry point is `packaging/windows/build-installer.ps1`; it stages the CMake
install tree, copies the repository docs and licenses that CMake does not
install, and then invokes `ISCC.exe`.

The installer is x64-only and machine-wide. The virtual-camera component is
checked by default, requires administrator rights, and targets Windows 11
build 22000 or later. The component can be left out by choosing `Application
only` on the Components page.

More detail lives in `docs/windows-installer.md`.

## On-Screen GUI (SDL Live Mode)

HasciiCam can show an optional on-screen control panel in live SDL mode.
Expand Down
16 changes: 16 additions & 0 deletions docs/smoke-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,22 @@ Checks:
- live SDL output stays responsive while the loopback node is attached
- a consumer application can open `/dev/video10` after the producer starts

## Windows (installer)

Build the installer from a Release Windows build tree:

```powershell
powershell -ExecutionPolicy Bypass -File packaging/windows/build-installer.ps1 -BuildDir build
```

Checks:

- an installer appears in `releases/`
- the installer opens with `Full` selected by default
- the `Application only` type omits the virtual-camera component
- a Full install reports `hasciicam_vcamctl status`
- uninstall removes the PATH entry and the virtual-camera registration

## Linux (size negotiation)

```sh
Expand Down
44 changes: 44 additions & 0 deletions docs/windows-installer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# HasciiCam Windows Installer

The Windows installer is built from `packaging/windows/hasciicam.iss` with the
helper script `packaging/windows/build-installer.ps1`.

## Build

```powershell
powershell -ExecutionPolicy Bypass -File packaging/windows/build-installer.ps1 -BuildDir build
```

The script:

1. Runs `cmake --install` into a staging directory.
2. Copies the repository docs and license tree that CMake does not install.
3. Invokes `ISCC.exe` from `C:\Program Files\Inno Setup 7` unless overridden.

## Installer shape

- x64 only
- machine-wide
- `Full` is the default install type
- `Application only` omits the virtual-camera component
- the virtual-camera component is checked by default

The virtual-camera component requires administrator rights and Windows 11
build 22000 or later. It is installed and registered through
`hasciicam_vcamctl.exe`, not `regsvr32`.

## Smoke test

After building the installer, run a manual install in Windows Sandbox or a
disposable VM and verify:

```powershell
.\releases\hasciicam-<version>-windows-x64-setup.exe
```

Then check:

- `hasciicam --version`
- `hasciicam -h`
- `hasciicam_vcamctl status`
- uninstall removes the PATH entry and the virtual-camera registration
166 changes: 166 additions & 0 deletions packaging/windows/build-installer.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
param(
[Parameter(Mandatory = $false)]
[string]$BuildDir = (Join-Path $PSScriptRoot '..\..\build'),

[Parameter(Mandatory = $false)]
[string]$StageDir = (Join-Path $PSScriptRoot '..\..\build\installer-stage'),

[Parameter(Mandatory = $false)]
[string]$OutputDir = (Join-Path $PSScriptRoot '..\..\releases'),

[Parameter(Mandatory = $false)]
[string]$IsccPath = 'C:\Program Files\Inno Setup 7\ISCC.exe',

[Parameter(Mandatory = $false)]
[string]$Version
)

$ErrorActionPreference = 'Stop'

function Get-RepoRoot {
return (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
}

function Get-CMakeProjectVersion {
param([string]$CachePath)

if (-not (Test-Path $CachePath)) {
throw "Missing CMake cache: $CachePath"
}
$match = Select-String -Path $CachePath -Pattern '^CMAKE_PROJECT_VERSION:STATIC=(.+)$' | Select-Object -First 1
if (-not $match) {
throw "Unable to determine project version from $CachePath"
}
return $match.Matches[0].Groups[1].Value.Trim()
}

function Ensure-EmptyDirectory {
param([string]$Path)

if (Test-Path $Path) {
Remove-Item -LiteralPath $Path -Recurse -Force
}
New-Item -ItemType Directory -Path $Path -Force | Out-Null
}

function Copy-Tree {
param(
[string]$Source,
[string]$Destination
)

if (-not (Test-Path $Source)) {
throw "Missing source path: $Source"
}
New-Item -ItemType Directory -Path $Destination -Force | Out-Null
Copy-Item -Path (Join-Path $Source '*') -Destination $Destination -Recurse -Force
}

function Copy-FileIfPresent {
param(
[string]$Source,
[string]$DestinationDirectory
)

if (Test-Path $Source) {
New-Item -ItemType Directory -Path $DestinationDirectory -Force | Out-Null
Copy-Item -LiteralPath $Source -Destination $DestinationDirectory -Force
}
}

function Invoke-Checked {
param(
[string]$FilePath,
[string[]]$Arguments,
[string]$WorkingDirectory
)

Push-Location $WorkingDirectory
try {
& $FilePath @Arguments
if ($LASTEXITCODE -ne 0) {
throw "Command failed ($LASTEXITCODE): $FilePath $($Arguments -join ' ')"
}
} finally {
Pop-Location
}
}

$RepoRoot = Get-RepoRoot
if (-not (Test-Path $BuildDir)) {
throw "Build directory not found: $BuildDir"
}

if (-not [System.IO.Path]::IsPathRooted($BuildDir)) {
$BuildDir = Join-Path $RepoRoot $BuildDir
}
if (-not [System.IO.Path]::IsPathRooted($StageDir)) {
$StageDir = Join-Path $RepoRoot $StageDir
}
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
$OutputDir = Join-Path $RepoRoot $OutputDir
}

$BuildDir = [System.IO.Path]::GetFullPath($BuildDir)
$StageDir = [System.IO.Path]::GetFullPath($StageDir)
$OutputDir = [System.IO.Path]::GetFullPath($OutputDir)

if (-not $Version) {
$Version = Get-CMakeProjectVersion -CachePath (Join-Path $BuildDir 'CMakeCache.txt')
}

if (-not (Test-Path $IsccPath)) {
throw "Inno Setup compiler not found: $IsccPath"
}

Ensure-EmptyDirectory -Path $StageDir
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null

Invoke-Checked -FilePath 'cmake' -Arguments @('--install', $BuildDir, '--prefix', $StageDir, '--config', 'Release') -WorkingDirectory $RepoRoot

Copy-FileIfPresent -Source (Join-Path $RepoRoot 'README.md') -DestinationDirectory $StageDir
Copy-FileIfPresent -Source (Join-Path $RepoRoot 'COPYING') -DestinationDirectory $StageDir
Copy-FileIfPresent -Source (Join-Path $RepoRoot 'docs\windows-installer.md') -DestinationDirectory (Join-Path $StageDir 'docs')
Copy-Tree -Source (Join-Path $RepoRoot 'LICENSES') -Destination (Join-Path $StageDir 'licenses')
Copy-FileIfPresent -Source (Join-Path $StageDir 'share\man\man1\hasciicam.1') -DestinationDirectory (Join-Path $StageDir 'docs')

Get-ChildItem -Path $BuildDir -Filter '*.dll' -File | ForEach-Object {
if ($_.Name -ne 'hasciicam_virtual_camera_source.dll') {
Copy-Item -LiteralPath $_.FullName -Destination (Join-Path $StageDir 'bin') -Force
}
}

$requiredStageFiles = @(
(Join-Path $StageDir 'bin\hasciicam.exe'),
(Join-Path $StageDir 'bin\hasciicam_vcamctl.exe'),
(Join-Path $StageDir 'bin\hasciicam_virtual_camera_source.dll'),
(Join-Path $StageDir 'README.md'),
(Join-Path $StageDir 'COPYING'),
(Join-Path $StageDir 'docs\hasciicam.1'),
(Join-Path $StageDir 'docs\windows-installer.md'),
(Join-Path $StageDir 'licenses')
)

foreach ($requiredPath in $requiredStageFiles) {
if (-not (Test-Path $requiredPath)) {
throw "Required staging path missing: $requiredPath"
}
}

$HasSDL2Dll = Test-Path (Join-Path $StageDir 'bin\SDL2.dll')

$issPath = Join-Path $PSScriptRoot 'hasciicam.iss'
$defines = @(
"/DMyAppVersion=$Version",
"/DMyBuildHome=$StageDir",
"/DMyOutputDir=$OutputDir"
)
if ($HasSDL2Dll) {
$defines += '/DHasSDL2Dll=1'
} else {
$defines += '/DHasSDL2Dll=0'
}

Invoke-Checked -FilePath $IsccPath -Arguments ($defines + @($issPath)) -WorkingDirectory $RepoRoot

Write-Host "Installer built in $OutputDir"
Loading
Loading