From 1b3af49ff708ef27366facda61d33d0ddc03dc75 Mon Sep 17 00:00:00 2001 From: Logan Rupe <11291831+LoganRupe@users.noreply.github.com> Date: Sat, 10 Oct 2026 19:36:34 +1100 Subject: [PATCH 1/6] feat(ci): build and draft GitHub releases from version tags Add a Release workflow: pushing a v* tag builds the self-signed macOS DMG (identity imported from repo secrets into a temporary keychain) and the Windows MSI, then attaches both to a draft release with the CHANGELOG section as notes. Manual runs build artifacts only. Windows publishing no longer depends on gitignored publish profiles, which never existed in the repo, so a clean clone built a framework-dependent app. The csproj now sets SelfContained and WindowsAppSDKSelfContained, and publish.ps1 fails if coreclr.dll or the Windows App SDK runtime is missing from the payload. The About dialog now shows major.minor.patch. --- .github/workflows/release.yml | 117 +++++++++++++++++++++++++ CursorFollowWin/CursorFollowWin.csproj | 9 +- CursorFollowWin/README.md | 11 +-- CursorFollowWin/SettingsWindow.xaml.cs | 2 +- CursorFollowWin/scripts/publish.ps1 | 12 ++- Docs/Distribution/Packaging.md | 7 ++ README.md | 10 ++- 7 files changed, 148 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9940857 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,117 @@ +name: Release + +# Push a v* tag (matching the app versions) to build the macOS DMG and Windows MSI and +# attach them to a draft GitHub release. Review the draft, then publish it. +# Run manually (Actions -> Release -> Run workflow) to build the installers as workflow +# artifacts without touching releases; pass an existing tag to (re)build its draft release. + +on: + push: + tags: ['v*'] + workflow_dispatch: + inputs: + tag: + description: Existing tag to build a draft release for (blank = artifacts only) + required: false + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }}-${{ inputs.tag }} + cancel-in-progress: false + +env: + TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }} + +jobs: + macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ env.TAG || github.ref }} + + - name: Import self-signed signing identity + env: + P12_BASE64: ${{ secrets.MAC_SIGNING_P12 }} + P12_PASSWORD: ${{ secrets.MAC_SIGNING_P12_PASSWORD }} + run: | + if [ -z "$P12_BASE64" ]; then + if [ -n "$TAG" ]; then echo "::error::MAC_SIGNING_P12 secret is not set"; exit 1; fi + echo "::warning::No signing secret; building ad-hoc signed (artifacts only)" + echo "SIGN_IDENTITY=-" >> "$GITHUB_ENV" + exit 0 + fi + KC="$RUNNER_TEMP/signing.keychain-db" + KC_PASS="$(openssl rand -hex 24)" + security create-keychain -p "$KC_PASS" "$KC" + security set-keychain-settings -lut 21600 "$KC" + security unlock-keychain -p "$KC_PASS" "$KC" + echo "$P12_BASE64" | base64 --decode > "$RUNNER_TEMP/identity.p12" + security import "$RUNNER_TEMP/identity.p12" -k "$KC" -P "$P12_PASSWORD" -T /usr/bin/codesign + rm -f "$RUNNER_TEMP/identity.p12" + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KC_PASS" "$KC" >/dev/null + security list-keychains -d user -s "$KC" $(security list-keychains -d user | tr -d '"') + + - name: Build, sign, package, verify + run: ./CursorFollowMac/scripts/package.sh + + - uses: actions/upload-artifact@v4 + with: + name: macos-dmg + path: build/CursorFollowMac-*.dmg + if-no-files-found: error + + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ env.TAG || github.ref }} + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + - name: Build MSI (unsigned) + shell: pwsh + run: ./CursorFollowWin/scripts/package.ps1 -Rid win-x64 + - uses: actions/upload-artifact@v4 + with: + name: windows-msi + path: CursorFollowWin/installer/dist/*.msi + if-no-files-found: error + + release: + if: ${{ github.event_name == 'push' || inputs.tag != '' }} + needs: [macos, windows] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ env.TAG }} + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + - name: Check installer versions match the tag + run: | + v="${TAG#v}" + ls dist + ls dist/CursorFollowMac-"$v".dmg dist/CursorFollowWin-"$v"-win-x64.msi + - name: Release notes from CHANGELOG.md + run: | + v="${TAG#v}" + awk -v v="$v" '$0 == "## " v {f=1; next} /^## / {f=0} f' CHANGELOG.md > notes.md + test -s notes.md || { echo "::error::No '## $v' section in CHANGELOG.md"; exit 1; } + - name: Create or update draft release + env: + GH_TOKEN: ${{ github.token }} + run: | + if gh release view "$TAG" >/dev/null 2>&1; then + gh release upload "$TAG" dist/* --clobber + gh release edit "$TAG" --notes-file notes.md + else + gh release create "$TAG" dist/* --draft --verify-tag --title "CursorFollow ${TAG#v}" --notes-file notes.md + fi diff --git a/CursorFollowWin/CursorFollowWin.csproj b/CursorFollowWin/CursorFollowWin.csproj index c9bb839..a7a4292 100644 --- a/CursorFollowWin/CursorFollowWin.csproj +++ b/CursorFollowWin/CursorFollowWin.csproj @@ -25,7 +25,6 @@ 0.5.1 0.5.1.0 0.5.1.0 - 1.0 Logan Rupe CursorFollow Logan Rupe @@ -43,10 +42,6 @@ win-x64 - - win-$(Platform).pubxml - - @@ -112,6 +107,10 @@ + + true + true False True - ICE38;ICE43;ICE57;ICE61;ICE91 + + ICE03;ICE38;ICE43;ICE57;ICE61;ICE64;ICE91 diff --git a/CursorFollowWin/installer/Package.wxs b/CursorFollowWin/installer/Package.wxs index 71378a7..4e300c6 100644 --- a/CursorFollowWin/installer/Package.wxs +++ b/CursorFollowWin/installer/Package.wxs @@ -103,6 +103,8 @@ Installer has no single-value remove-on-uninstall, and we must not delete the shared Run key wholesale). RemoveDataFolder removes %LocalAppData%\CursorFollowWin (settings.json + Logs\). + RemoveInstallTree removes what is left of the install folder after RemoveFiles: the + per-language subfolders of the self-contained Windows App SDK. Impersonate=yes so HKCU / %LOCALAPPDATA% resolve to the installing user; Return=ignore so a missing key/folder never fails the uninstall. @@ -115,11 +117,17 @@ Directory="INSTALLFOLDER" ExeCommand="cmd.exe /c rmdir /s /q "%LOCALAPPDATA%\CursorFollowWin"" Execute="deferred" Impersonate="yes" Return="ignore" /> + + From 5ecbc112357b429e89f522a5844666ede720cb5e Mon Sep 17 00:00:00 2001 From: Logan Rupe <11291831+LoganRupe@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:50:24 +1100 Subject: [PATCH 5/6] fix(win/app): close every window before Application.Exit so Quit exits With the Settings window open, Application.Exit closed only some of the three windows and returned. The window it skipped (the output window in one run, Settings in another) never got a close, so the WinUI message loop kept running and the process stayed alive. A trace of Closed events showed Exit never touched the surviving window. ExitApplication now closes Settings, the panel and the output window itself, then calls Exit. Not a regression from the self-contained publish: the framework-dependent Debug build hangs the same way. Fixes #5 --- CHANGELOG.md | 2 ++ CursorFollowWin/App.xaml.cs | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7897f51..7588b91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- **Windows:** Quit now exits the process. If the Settings window had been opened, the app could hang on Quit with the CursorFollow Output window still up ([#5](https://github.com/LoganRupe/CursorFollow/issues/5)). + ## 0.5.1 First public release of **CursorFollow**, open source under the MIT License at [github.com/LoganRupe/CursorFollow](https://github.com/LoganRupe/CursorFollow). Runs on **macOS**, **Windows** and **Linux (Hyprland)**. diff --git a/CursorFollowWin/App.xaml.cs b/CursorFollowWin/App.xaml.cs index e531556..bb33486 100644 --- a/CursorFollowWin/App.xaml.cs +++ b/CursorFollowWin/App.xaml.cs @@ -169,6 +169,13 @@ private void ExitApplication() _outputWindow?.DisposeCapture(); _singleInstanceMutex?.Dispose(); AppLog.Shutdown(); + + // Close every window ourselves rather than relying on Application.Exit to do it. With the + // Settings window open (three windows), Exit closed only some of them and returned; the + // window it skipped kept the message loop, and so the process, alive (issue #5). + _settingsWindow?.Close(); + _panelWindow?.Close(); + _outputWindow?.Close(); Current.Exit(); } From b2b3cfc3cd8611e4b1a951843ba87037b973fb8c Mon Sep 17 00:00:00 2001 From: Logan Rupe <11291831+LoganRupe@users.noreply.github.com> Date: Sat, 10 Oct 2026 20:54:36 +1100 Subject: [PATCH 6/6] docs(changelog): list the Windows quit fix under 0.5.1 --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7588b91..4667cb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,6 @@ ## Unreleased -- **Windows:** Quit now exits the process. If the Settings window had been opened, the app could hang on Quit with the CursorFollow Output window still up ([#5](https://github.com/LoganRupe/CursorFollow/issues/5)). - ## 0.5.1 First public release of **CursorFollow**, open source under the MIT License at [github.com/LoganRupe/CursorFollow](https://github.com/LoganRupe/CursorFollow). Runs on **macOS**, **Windows** and **Linux (Hyprland)**. @@ -32,6 +30,7 @@ CursorFollow captures a target window and renders it into a stable, shareable wi - Auto-follow (cursor-under-window with foreground fallback), live frame telemetry, and single-instance guard. - Global hotkeys, Start-with-Windows, and capture-quality presets. - Distributed as a per-user **WiX MSI** installer (no admin required; clean upgrade and uninstall). +- Fixed: Quit could hang, leaving the process and output window running, if the Settings window had been opened ([#5](https://github.com/LoganRupe/CursorFollow/issues/5)). ### Linux