Skip to content
Merged
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
22 changes: 7 additions & 15 deletions .github/workflows/alpha.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,22 +85,14 @@ jobs:
"path=$dst" >> $env:GITHUB_OUTPUT
"sha256=$((Get-FileHash $dst -Algorithm SHA256).Hash)" >> $env:GITHUB_OUTPUT

# Same split as release.yml: a private source repo cannot serve downloadable assets,
# so alphas are published to the public releases repo when RELEASES_TOKEN is set.
- name: Publish the pre-release
shell: pwsh
env:
GH_TOKEN: ${{ secrets.RELEASES_TOKEN || github.token }}
TARGET_REPO: ${{ secrets.RELEASES_TOKEN && 'Maxaubert/Wind-releases' || github.repository }}
HAS_TOKEN: ${{ secrets.RELEASES_TOKEN != '' }}
IS_PRIVATE: ${{ github.event.repository.private }}
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
$ErrorActionPreference = 'Continue'
$PSNativeCommandUseErrorActionPreference = $false
# See release.yml: alphas stay in this private repo until RELEASES_TOKEN is set.
if ($env:IS_PRIVATE -eq 'true' -and $env:HAS_TOKEN -ne 'true') {
Write-Host "::notice::Publishing to the PRIVATE repo. Set RELEASES_TOKEN to publish to Maxaubert/Wind-releases instead."
}
$tag = '${{ steps.ver.outputs.tag }}'
$exe = '${{ steps.asset.outputs.path }}'
$note = '${{ inputs.note }}'
Expand All @@ -120,27 +112,27 @@ jobs:
Unsigned, like the stable build - SmartScreen will warn.
"@
$body | Set-Content notes.md -NoNewline
gh release create $tag $exe --repo $env:TARGET_REPO --prerelease --title "Wind ${{ steps.ver.outputs.alpha }}" --notes-file notes.md
gh release create $tag $exe --prerelease --title "Wind ${{ steps.ver.outputs.alpha }}" --notes-file notes.md
if ($LASTEXITCODE -ne 0) { throw 'gh release create failed' }

# Alphas are disposable and accumulate fast. Keep the newest few so a bisect across recent
# builds is still possible, and delete the rest along with their tags.
- name: Prune old alphas
shell: pwsh
env:
GH_TOKEN: ${{ secrets.RELEASES_TOKEN || github.token }}
TARGET_REPO: ${{ secrets.RELEASES_TOKEN && 'Maxaubert/Wind-releases' || github.repository }}
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
$ErrorActionPreference = 'Continue'
$PSNativeCommandUseErrorActionPreference = $false
$keep = 5
$all = gh release list --repo $env:TARGET_REPO --limit 100 --json tagName,isPrerelease,createdAt | ConvertFrom-Json
$all = gh release list --limit 100 --json tagName,isPrerelease,createdAt | ConvertFrom-Json
$alphas = @($all | Where-Object { $_.isPrerelease -and $_.tagName -like '*-alpha.*' } |
Sort-Object { [datetime]$_.createdAt } -Descending)
if ($alphas.Count -le $keep) { "nothing to prune ($($alphas.Count) alphas)"; exit 0 }
foreach ($old in $alphas[$keep..($alphas.Count-1)]) {
"pruning $($old.tagName)"
gh release delete $old.tagName --repo $env:TARGET_REPO --yes --cleanup-tag
gh release delete $old.tagName --yes --cleanup-tag
}

- name: Summary
Expand Down
29 changes: 6 additions & 23 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,11 @@ jobs:
# Create the release the first time this version is seen; afterwards replace the asset
# in place so the download always matches main. --clobber is what makes the refresh
# idempotent: without it a second push to the same version fails on a duplicate name.
# Assets are published to a SEPARATE PUBLIC repo, because this repo is private and a
# private repo's release assets need an authenticated request to download - i.e. nobody
# can get the installer. RELEASES_TOKEN is a fine-grained PAT with contents:write on
# that public repo and nothing else. While it is unset the job falls back to publishing
# here, so the pipeline keeps working during the move and on a fork.
- name: Publish or refresh the release
shell: pwsh
env:
GH_TOKEN: ${{ secrets.RELEASES_TOKEN || github.token }}
TARGET_REPO: ${{ secrets.RELEASES_TOKEN && 'Maxaubert/Wind-releases' || github.repository }}
HAS_TOKEN: ${{ secrets.RELEASES_TOKEN != '' }}
IS_PRIVATE: ${{ github.event.repository.private }}
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
# Actions runs pwsh with $ErrorActionPreference='Stop', and PowerShell 7.4 turns a
# non-zero NATIVE exit into a throw. `gh release view` exits 1 when the tag does not
Expand All @@ -113,31 +106,21 @@ jobs:
$v = '${{ steps.ver.outputs.version }}'
$tag = "v$v"
$exe = "dist/Wind-Setup-x64-$v.exe"
# DELIBERATE while Wind is pre-launch: releases go to this PRIVATE repo, so the
# installer sits behind authentication and only Max can fetch it. That is the point
# for now. Setting RELEASES_TOKEN (a fine-grained PAT with Contents: read and write
# on Maxaubert/Wind-releases) is the single switch that moves publishing to the
# public download repo, for when the product goes out to Vispero, Statped or NAV.
if ($env:IS_PRIVATE -eq 'true' -and $env:HAS_TOKEN -ne 'true') {
Write-Host "::notice::Publishing to the PRIVATE repo. The asset will not be publicly downloadable. Set RELEASES_TOKEN to publish to Maxaubert/Wind-releases instead."
}
# A stable-named copy of the same installer (#343), so this link never needs updating:
# https://github.com/<repo>/releases/latest/download/Wind-Setup-x64.exe always serves
# the newest release. The versioned name stays the primary asset.
$stable = "dist/Wind-Setup-x64.exe"
Copy-Item $exe $stable -Force
$repo = $env:TARGET_REPO
Write-Host "publishing to $repo"
gh release view $tag --repo $repo 2>$null | Out-Null
gh release view $tag 2>$null | Out-Null
if ($LASTEXITCODE -eq 0) {
Write-Host "refreshing existing release $tag"
gh release edit $tag --repo $repo --notes-file notes.md
gh release edit $tag --notes-file notes.md
if ($LASTEXITCODE -ne 0) { throw 'gh release edit failed' }
gh release upload $tag $exe $stable --repo $repo --clobber
gh release upload $tag $exe $stable --clobber
if ($LASTEXITCODE -ne 0) { throw 'gh release upload failed' }
} else {
Write-Host "creating release $tag"
gh release create $tag $exe $stable --repo $repo --title "Wind $v" --notes-file notes.md
gh release create $tag $exe $stable --title "Wind $v" --notes-file notes.md
if ($LASTEXITCODE -ne 0) { throw 'gh release create failed' }
}

Expand Down
2 changes: 1 addition & 1 deletion assets/wind-badge-taskbar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 2 additions & 3 deletions installer/app.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ Var LicenceDir ; where "Read the full licence" put its copy, empty until then
; ---- stop a running Wind -----------------------------------------------------
; An upgrade always runs over a live tray app holding its own exe open. Wind exposes an
; auto-reset named event for exactly this (src\main.cpp:2199); setting it makes Wind exit
; CLEANLY, which restores the OS cursor, releases any ClipCursor, releases the shared
; Magnification runtime, and restores the user's native-Magnifier registry backup if the
; magnify model ever modified it. Killing the process skips all of that and can leave the
; CLEANLY, which restores the OS cursor, releases any ClipCursor and releases the shared
; Magnification runtime. Killing the process skips all of that and can leave the
; pointer hidden or pinned to one pixel. So: ask, wait, and only then kill.
;
; The wait is on Wind's single-instance mutex rather than a tasklist poll, which is both
Expand Down
18 changes: 3 additions & 15 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ Config ParseConfig(const std::string& text) {
else if (key == "cursorConstantSize") c.cursorConstantSize = std::stoi(val);
else if (key == "cursorVisibility") c.cursorVisibility = val;
else if (key == "model") c.model = val;
else if (key == "magnifyStep") c.magnifyStep = std::stoi(val);
else if (key == "fastPan") c.fastPan = std::stoi(val);
else if (key == "smoothPan") c.smoothPan = std::stoi(val);
else if (key == "cursorSprite") c.cursorSprite = std::stoi(val);
Expand Down Expand Up @@ -223,7 +222,6 @@ Config ParseConfig(const std::string& text) {
else if (key == "txSamplingMode") c.txSamplingMode = std::stoi(val);
else if (key == "txWobbleCage") c.txWobbleCage = std::stoi(val);
else if (key == "txWobbleCageSize") c.txWobbleCageSize = std::stoi(val);
else if (key == "txKeepAliveMaxLevel")c.txKeepAliveMaxLevel = std::stoi(val);
else if (key == "txWarmMode") c.txWarmMode = std::stoi(val);
else if (key == "txTrace") c.txTrace = std::stoi(val);
else if (key == "txRestLevel") c.txRestLevel = std::stod(val);
Expand Down Expand Up @@ -292,9 +290,6 @@ Config ParseConfig(const std::string& text) {
if (c.tdrTest > 4) c.tdrTest = 4;
if (c.ixDecimate < 1) c.ixDecimate = 1; // 1 = publish every changed tick
if (c.ixDecimate > 16) c.ixDecimate = 16;
// 0 is meaningful here (keep-alive OFF, the shipped default), so the floor is 0 not 1.
if (c.txKeepAliveMaxLevel < 0) c.txKeepAliveMaxLevel = 0;
if (c.txKeepAliveMaxLevel > 50) c.txKeepAliveMaxLevel = 50;
if (c.txIdleReleaseMs < 0) c.txIdleReleaseMs = 0;
if (c.txRestLevel < 1.0) c.txRestLevel = 1.0;
if (c.txRestLevel > 1.01) c.txRestLevel = 1.01; // visually identity only
Expand Down Expand Up @@ -325,16 +320,14 @@ Config ParseConfig(const std::string& text) {
// "transform" is a first-class model again (revived for issue #148: the compositor-internal
// zoom that stays smooth over heavy games); anything unknown falls back to hybrid, the
// product default ("Auto" in the UI) - same fallback as a missing key (struct default).
// "magnify" (System, the Windows Magnifier driver) is no longer offered (Max, 2026-10-02): an old
// ini that still says so runs Auto. The engine code stays, it just cannot be picked.
// "magnify" (the retired Windows Magnifier driver) is unknown too, so an old ini that still
// says so runs Auto.
if (c.model != "render" && c.model != "transform" && c.model != "hybrid") c.model = "hybrid";
// (The old transform/hybrid maxLevel<=12 clamp is GONE: the "TDR territory above 12x" was
// root-caused 2026-07-26 to NVIDIA's 16-bit MPO plane-programming overflow - see issue
// #148 - which the mapper's MPO-aware pan wall now guards at ANY level, so maxLevel is one
// shared setting across all models. High levels still cost DWM re-scale time; that is a
// perf trade the user owns, not a crash.)
if (c.magnifyStep < 5) c.magnifyStep = 5; // Windows Settings' own range is 5..400
if (c.magnifyStep > 400) c.magnifyStep = 400;
// Reject keybinds to keys Wind must never swallow (see IsForbiddenBindVk). A bound key is
// eaten system-wide, so binding e.g. Backspace or the Windows key would make it unusable
// everywhere; treat a forbidden bind as unbound regardless of how it got into the ini.
Expand Down Expand Up @@ -550,9 +543,7 @@ std::string DefaultIniText() {
"; model: hybrid = Auto (default): picks render or transform per zoom-in (games get\n"
"; the compositor-internal transform, everything else the GPU overlay).\n"
"; render = GPU capture+overlay (high fidelity). transform = DWM fullscreen\n"
"; transform only. magnify = drive the native Windows Magnifier (works over DRM\n"
"; video like Netflix, which blanks in render; handles its own cursor; ignores\n"
"; the render-only knobs; max zoom 1600%). Restart to switch.\n"
"; transform only. Anything else runs hybrid. Restart to switch.\n"
"model=hybrid\n"
"; transformExclude (Auto/hybrid only): exe names that must never get the transform\n"
"; engine even when fullscreen+borderless. Fullscreen browser video looks exactly\n"
Expand Down Expand Up @@ -592,9 +583,6 @@ std::string DefaultIniText() {
"; Empty (default) = keys stay swallowed everywhere, as before. The Settings UI\n"
"; manages this list (Keybinds -> Release keys in these apps).\n"
"noSwallowApps=\n"
"; magnifyStep (magnify only): Windows Magnifier zoom increment, percent points per\n"
"; step (5-400). Lower = smoother and slower. Applies live.\n"
"magnifyStep=50\n"
"; multiMonitor: 1=magnify whichever monitor the cursor is on at zoom-in; 0=primary only\n"
"multiMonitor=0\n"
"; cropCapture (opt-in): 0=always copy all changed regions (cache never stale, default);\n"
Expand Down
Loading
Loading