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
41 changes: 39 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,40 @@ on:
pull_request:
workflow_dispatch:

# This workflow only builds and uploads an artifact; it makes no repository
# writes. release.yml needs contents: write for `gh release create` — do not
# copy this block there.
permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

# primary.rc references primary.manifest as an opaque file, so windres
# never preprocesses it and its assemblyIdentity version cannot come from
# app_strings.h — it has to be bumped by hand. This makes the two drifting
# apart fail the build instead of relying on a release-checklist step.
#
# Expects name= and version= on one line of the assemblyIdentity element;
# reformatting that element fails this check rather than skipping it.
- name: Verify the manifest version matches app_strings.h
run: |
maj=$(grep -oP 'APP_VERSION_MAJOR\s+\K\d+' resources/app_strings.h)
min=$(grep -oP 'APP_VERSION_MINOR\s+\K\d+' resources/app_strings.h)
pat=$(grep -oP 'APP_VERSION_PATCH\s+\K\d+' resources/app_strings.h)
# VERSIONINFO is MAJOR,MINOR,PATCH,0; the manifest mirrors that.
want="$maj.$min.$pat.0"
got=$(grep -oP '<assemblyIdentity[^>]*\bname="Primary"[^>]*\bversion="\K[^"]+' \
resources/primary.manifest)
echo "app_strings.h: $want / manifest: ${got:-none}"
if [ "$got" != "$want" ]; then
echo "::error::resources/primary.manifest version ${got:-(not found)} does not match $want from resources/app_strings.h. Update the assemblyIdentity version."
exit 1
fi

- name: Install MinGW-w64
run: |
sudo apt-get update
Expand Down Expand Up @@ -68,10 +96,19 @@ jobs:
done
exit $status

# Extracted per macro rather than with one alternation piped through
# `paste`, which silently assumed MAJOR/MINOR/PATCH appear in that order
# in the header. An empty component would build a nonsense version like
# "1..0", so each is asserted non-empty.
- name: Verify the version resource matches app_strings.h
run: |
src=$(grep -oP 'APP_VERSION_(MAJOR|MINOR|PATCH)\s+\K\d+' \
resources/app_strings.h | paste -sd.)
maj=$(grep -oP 'APP_VERSION_MAJOR\s+\K\d+' resources/app_strings.h)
min=$(grep -oP 'APP_VERSION_MINOR\s+\K\d+' resources/app_strings.h)
pat=$(grep -oP 'APP_VERSION_PATCH\s+\K\d+' resources/app_strings.h)
for c in "$maj" "$min" "$pat"; do
[ -n "$c" ] || { echo "::error::Could not read APP_VERSION_* from resources/app_strings.h"; exit 1; }
done
src="$maj.$min.$pat"
echo "Source version: $src"
strings -el Primary.exe | grep -qF "$src" || {
echo "::error::Binary does not report source version $src"; exit 1; }
Expand Down
32 changes: 30 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,42 @@ jobs:
- name: Verify the tag matches the source version
run: |
tag="${GITHUB_REF_NAME#v}"
src=$(grep -oP 'APP_VERSION_(MAJOR|MINOR|PATCH)\s+\K\d+' \
resources/app_strings.h | paste -sd.)
# Extracted per macro rather than with one alternation piped through
# `paste`, which silently assumed MAJOR/MINOR/PATCH appear in that
# order in the header. An empty component would build a nonsense
# version like "1..0", so each is asserted non-empty.
maj=$(grep -oP 'APP_VERSION_MAJOR\s+\K\d+' resources/app_strings.h)
min=$(grep -oP 'APP_VERSION_MINOR\s+\K\d+' resources/app_strings.h)
pat=$(grep -oP 'APP_VERSION_PATCH\s+\K\d+' resources/app_strings.h)
for c in "$maj" "$min" "$pat"; do
[ -n "$c" ] || { echo "::error::Could not read APP_VERSION_* from resources/app_strings.h"; exit 1; }
done
src="$maj.$min.$pat"
echo "Tag: $tag / source: $src"
if [ "$tag" != "$src" ]; then
echo "::error::Tag $tag does not match source version $src. Bump APP_VERSION_* in resources/app_strings.h."
exit 1
fi

# primary.manifest is referenced by primary.rc as an opaque file, so
# windres never preprocesses it and its assemblyIdentity version has to be
# bumped by hand. Refuse to ship a release where it disagrees with the
# source version. (Kept as a separate step from the tag gate above so the
# failure says which of the two is wrong.)
- name: Verify the manifest version matches the source version
run: |
maj=$(grep -oP 'APP_VERSION_MAJOR\s+\K\d+' resources/app_strings.h)
min=$(grep -oP 'APP_VERSION_MINOR\s+\K\d+' resources/app_strings.h)
pat=$(grep -oP 'APP_VERSION_PATCH\s+\K\d+' resources/app_strings.h)
want="$maj.$min.$pat.0"
got=$(grep -oP '<assemblyIdentity[^>]*\bname="Primary"[^>]*\bversion="\K[^"]+' \
resources/primary.manifest)
echo "app_strings.h: $want / manifest: ${got:-none}"
if [ "$got" != "$want" ]; then
echo "::error::resources/primary.manifest version ${got:-(not found)} does not match $want from resources/app_strings.h. Update the assemblyIdentity version."
exit 1
fi

- name: Build
shell: bash
run: ./build.sh 2>&1 | tee build.log
Expand Down
5 changes: 4 additions & 1 deletion resources/primary.manifest
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<!-- NOTE: this version string is not preprocessed by windres, so it must be
bumped by hand alongside APP_VERSION_* in resources/app_strings.h. -->
bumped by hand alongside APP_VERSION_* in resources/app_strings.h.
Both workflows fail the build if the two disagree, so drift is caught
rather than shipped. Keep name= and version= on this one line: the check
looks for them together. -->
<assemblyIdentity type="win32" name="Primary" version="1.0.0.0"
processorArchitecture="amd64"/>

Expand Down
12 changes: 5 additions & 7 deletions src/primary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,6 @@ INT_PTR CALLBACK OptionsDialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM
// Start or stop monitoring based on setting
if (autoSwitchEnabled) {
StartAutoSwitchMonitoring(g_hwndMain);
CheckAndApplyAutoSwitch(); // Apply immediately (will detect change and apply)
} else {
StopAutoSwitchMonitoring(g_hwndMain);
}
Expand All @@ -529,16 +528,15 @@ INT_PTR CALLBACK OptionsDialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM
L"Failed to update base mouse count. Please check your permissions.",
L"Error",
MB_ICONERROR | MB_OK);
} else {
// Re-check if auto-switch is enabled, to apply new settings immediately
if (autoSwitchEnabled) {
CheckAndApplyAutoSwitch();
}
}

// Single re-apply point: every setting the check reads —
// direction, auto-switch flag, base device count — is
// persisted by now, so one pass covers them all.
//
// Settings may have changed the mapping without the device
// state changing, so discard the cached state to force the
// next check to apply the new configuration.
// check to apply the new configuration.
if (autoSwitchEnabled && autoSwitchWritten) {
g_lastExternalMouseState = EXTERNAL_MOUSE_UNKNOWN;
CheckAndApplyAutoSwitch();
Expand Down
Loading