fix(input): a mouse button released outside the game is not a click on return #265

Merged
HumanBean merged 1 commit from jarynclouatre/Longshot:fix/mouse-release-outside into main 2026-11-13 18:30:39 +00:00
Contributor

Why

A left button held on a page of the pause screen's panel and let go outside the game became a click when the pointer came back. On the Map page: press on the R sign, Alt-Tab away, let go, come back, and the page turns with no click.

On the panel's pages (Key Items, Threads of Time, the Map) a left press is held until its release and is a click there, so a drag can pan or turn instead. mouse_tick_locked (host/oot_menu.c) returned at once while there was no pointer (hos_mouse_game_pointer returns 0 outside the window or under the Insert menu), and the pending press, declared below that return, stayed set. At the first tick back the button read as up, and the release made the click at the place of the press.

What changed

  • input (host/oot_menu.c, mouse_tick_locked): the pending press is declared at the top of the function and dropped while there is no pointer, and on the R button's item choice, which returns early too. A press is only kept while the pointer stays with the game. hos_mouse_game_pointer returns 0 for a pointer that is away, not for a frame without a new sample, so a normal click keeps its press. A drag carried out of the window ends there, since its release cannot be seen.
  • tests (tests/test_oot_menu_mouse.py): on Key Items, a press on the R sign let go there taps R once; a press on it, then the pointer away (another window in front), the button let go and the pointer back, taps nothing. The left button's held state the menu reads (GetAsyncKeyState) is now the fixture's, through a header included first in both files, so the desk's own mouse cannot change the result.

Flags and settings

None.

Measurements

main: f3fe1763 (2026-11-13)
this PR: fix/mouse-release-outside @ ac3a5fc3
Hardware: a Linux x86-64 PC for the suite (g++ 15.2, Python 3.14.4); a Windows 11 PC (Ryzen 7 9800X3D) for the mouse fixture
Scene: none, the game was not run
Method: python3 -I -B tests/run_all.py, two runs each, interleaved (main, PR, main, PR, with another branch's runs between); both runs of a side gave the same counts. tests/test_oot_menu_mouse.py runs on Windows only and is skipped on Linux; its fixture was cross-built with llvm-mingw 20261104 from this branch's test, once against this branch's oot_menu.c and once against main's, and both builds were run on the Windows PC.
How to reproduce: the commands above in a checkout of each commit

main @ f3fe1763 this PR @ ac3a5fc3
tests/run_all.py (run 1, run 2) 451 passed, 2 failed, 79 skipped (both) 451 passed, 2 failed, 79 skipped (both)
failing checks test_bake_portability.py, test_menu_i18n.py the same two
test_oot_menu_mouse.py fixture built with llvm-mingw (-Wall) builds builds, no new warnings
that fixture run on Windows 11 fails the new case (line 259: taps "0:R/75;", the release made a tap) passes

test_bake_portability.py fails here for a missing Python module (zstandard), on main as well. test_menu_i18n.py fails on main itself: a pt_BR hint (the testing tools' row) is no longer a text of vk_menu.cpp.

Testing

Tested: the game-free suite on a Linux PC, and the mouse fixture on a Windows 11 PC (main's oot_menu.c fails the new case, this branch passes), as in Measurements.

Not tested: the game.

Needs other testers: None.

Screenshots

None.

Risk and rollback

A drag on the map or a stone that leaves the window now ends there; before, it went on when the pointer came back with the button still held. Reverting the commit puts the old order back.

Reviewer notes

  • Found reading the menu's mouse code, not from a player's report.

Issues

None.

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
## Why A left button held on a page of the pause screen's panel and let go outside the game became a click when the pointer came back. On the Map page: press on the R sign, Alt-Tab away, let go, come back, and the page turns with no click. On the panel's pages (Key Items, Threads of Time, the Map) a left press is held until its release and is a click there, so a drag can pan or turn instead. `mouse_tick_locked` (`host/oot_menu.c`) returned at once while there was no pointer (`hos_mouse_game_pointer` returns 0 outside the window or under the Insert menu), and the pending press, declared below that return, stayed set. At the first tick back the button read as up, and the release made the click at the place of the press. ## What changed - input (`host/oot_menu.c`, `mouse_tick_locked`): the pending press is declared at the top of the function and dropped while there is no pointer, and on the R button's item choice, which returns early too. A press is only kept while the pointer stays with the game. `hos_mouse_game_pointer` returns 0 for a pointer that is away, not for a frame without a new sample, so a normal click keeps its press. A drag carried out of the window ends there, since its release cannot be seen. - tests (`tests/test_oot_menu_mouse.py`): on Key Items, a press on the R sign let go there taps R once; a press on it, then the pointer away (another window in front), the button let go and the pointer back, taps nothing. The left button's held state the menu reads (`GetAsyncKeyState`) is now the fixture's, through a header included first in both files, so the desk's own mouse cannot change the result. ## Flags and settings None. ## Measurements main: f3fe1763 (2026-11-13) this PR: fix/mouse-release-outside @ ac3a5fc3 Hardware: a Linux x86-64 PC for the suite (g++ 15.2, Python 3.14.4); a Windows 11 PC (Ryzen 7 9800X3D) for the mouse fixture Scene: none, the game was not run Method: `python3 -I -B tests/run_all.py`, two runs each, interleaved (main, PR, main, PR, with another branch's runs between); both runs of a side gave the same counts. `tests/test_oot_menu_mouse.py` runs on Windows only and is skipped on Linux; its fixture was cross-built with llvm-mingw 20261104 from this branch's test, once against this branch's `oot_menu.c` and once against main's, and both builds were run on the Windows PC. How to reproduce: the commands above in a checkout of each commit | | main @ f3fe1763 | this PR @ ac3a5fc3 | |---|---|---| | `tests/run_all.py` (run 1, run 2) | 451 passed, 2 failed, 79 skipped (both) | 451 passed, 2 failed, 79 skipped (both) | | failing checks | `test_bake_portability.py`, `test_menu_i18n.py` | the same two | | `test_oot_menu_mouse.py` fixture built with llvm-mingw (`-Wall`) | builds | builds, no new warnings | | that fixture run on Windows 11 | fails the new case (line 259: `taps "0:R/75;"`, the release made a tap) | passes | `test_bake_portability.py` fails here for a missing Python module (zstandard), on main as well. `test_menu_i18n.py` fails on main itself: a pt_BR hint (the testing tools' row) is no longer a text of `vk_menu.cpp`. ## Testing **Tested:** the game-free suite on a Linux PC, and the mouse fixture on a Windows 11 PC (main's `oot_menu.c` fails the new case, this branch passes), as in Measurements. **Not tested:** the game. **Needs other testers:** None. ## Screenshots None. ## Risk and rollback A drag on the map or a stone that leaves the window now ends there; before, it went on when the pointer came back with the button still held. Reverting the commit puts the old order back. ## Reviewer notes - Found reading the menu's mouse code, not from a player's report. ## Issues None. ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
fix(input): a mouse button released outside the game is not a click on return
Some checks failed
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Failing after 4s
ac3a5fc393
A left press on a panel page (Key Items, Threads of Time, the map) is held until
its release and becomes a click there. mouse_tick_locked returned early while the
pointer was away (outside the window, another window in front) and kept the press,
so a button let go there clicked once the pointer came back: on the map's R sign
the page turned. The pending press now sits above the early returns and is
dropped while there is no pointer and on the item choice.
HumanBean added this to the v3.0.0 milestone 2026-11-13 18:30:32 +00:00
HumanBean changed title from WIP: fix(input): a mouse button released outside the game is not a click on return to fix(input): a mouse button released outside the game is not a click on return 2026-11-13 18:30:33 +00:00
HumanBean deleted branch fix/mouse-release-outside 2026-11-13 18:30:39 +00:00
Author
Contributor

The mouse fixture ran on a Windows 11 PC (llvm-mingw 20261104 builds of this branch's test): against main's oot_menu.c it fails the new case at line 259 (taps "0:R/75;", the release came back as a tap), against this branch's it passes. Measurements and Testing are updated; out of WIP.

The mouse fixture ran on a Windows 11 PC (llvm-mingw 20261104 builds of this branch's test): against main's `oot_menu.c` it fails the new case at line 259 (`taps "0:R/75;"`, the release came back as a tap), against this branch's it passes. Measurements and Testing are updated; out of WIP.
Some checks failed
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Failing after 4s
Some workflows are waiting to be reviewed.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!265
No description provided.