Skip to content

Move the frame dump and GPU capture hotkey to Ctrl+Shift+F7 - #1075

Merged
athei merged 1 commit into
mainfrom
capture-hotkey-f7
Oct 6, 2026
Merged

athei merged 1 commit into
mainfrom
capture-hotkey-f7

Conversation

@athei

@athei athei commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Symptoms

Pressing Ctrl+Shift+F12 in a game did not arm the frame dump and GPU capture. On macOS the Metal Performance HUD library (/usr/lib/libMTLHud.dylib) adds a "Metal HUD" menu to the app's menu bar, and that menu binds Ctrl+Shift+F9, F10, F11 and F12 to "Generate Performance Report" for 5 s, 30 s, 1 min and 5 min. The menu consumes the key before Wine sees it, so the layer never got the press, and in a game test the 5-minute report then crashed the game inside Apple's code.

The same menu also takes Shift+F8 (logging), Shift+F9 (enable HUD), Shift+F11 (reset metrics) and Shift+F12 (configuration panel); its key-down watcher handles Shift+F7 (move HUD) and Shift+F10 (layout), and with MTL_HUD_DISABLE_MENU_BAR=1 Shift+F5, F6, F8, F9 and F12. Plain F12 is Steam's screenshot key, macOS uses Ctrl+F1 to Ctrl+F8 without Shift for focus moves, and F11 for Show Desktop.

Changes

The hotkey is now a fixed Ctrl+Shift+F7: F7 going down while Control and Shift are held and Alt is up (winemac reports either Command key as Alt). The edge semantics are the same as before: a held chord fires once, Control and Shift pressed after F7 is already down never fire, and the modifiers are read only on the present where F7 goes down.

windows/core/src/capture_chord.rs now owns the key codes: CAPTURE_KEY (VK_F7) and Modifier::virtual_key for VK_CONTROL, VK_SHIFT and VK_MENU. windows/d3d9/src/capture.rs drops its local VK_* constants and reads those, and its latch static is renamed from F12_DOWN_LAST to CAPTURE_KEY_DOWN_LAST with its doc-block argument kept.

The unit tests in capture_chord/tests.rs now model each sample as the set of virtual keys held and read the capture key and modifiers by the module's codes, with the codes restated from the Win32 headers so a moved code fails a test. They check that Ctrl+Shift+F7 fires once per press and not while held, and that F7, Shift+F7, Ctrl+F7, Ctrl+Alt+Shift+F7, F12, Ctrl+Shift+F12, the HUD's Shift+F8 to F12 and Ctrl+Shift+F9 to F12, and the near miss Ctrl+Shift+F8 do not.

Every doc and comment that named Ctrl+Shift+F12 now names Ctrl+Shift+F7: the README, docs/ARCHITECTURE.md (section heading and its anchor), the link to it in docs/GAMES.md, CONTRIBUTING.md, the bench-shape text in the Makefile, mtld3d.conf, the bench runner's messages and the code comments on both sides. The README and the architecture section replace the old reason with the actual collisions (Steam's F12 and the HUD's Shift+F8 to F12 and Ctrl+Shift+F9 to F12), keep the Control-not-Command note, and add that a MacBook keyboard needs Fn for F7 unless "Use F1, F2, etc. keys as standard function keys" is on. The mentions of dumps that were taken with plain F12 before the chord existed (the bench_wow112.rs source line and its COVERAGE row, the bench_frame_shape.rs module doc) stay as they are, since they describe how those dumps were made.

Alternatives considered

Keeping F12 with a different modifier set: every Shift and Ctrl+Shift combination on F12 is the HUD's, and plain F12 is Steam's. Ctrl+Shift+F9 to F11 are HUD report keys too. Ctrl+F1 to Ctrl+F8 without Shift are macOS focus moves, and F11 is Show Desktop. Shift+F7 belongs to the HUD's key-down watcher, but the watcher requires Control to be up for it, and the HUD's menu has no F7 entry, so Ctrl+Shift+F7 is not a HUD key; it is not among the macOS shortcuts listed above either, which is why F7 with both modifiers was picked.

A configurable key was not added: the chord stays fixed, with no config key and no environment variable, as before.

Verification

make fmt, make check (rustfmt, clippy with nursery and pedantic on both workspaces and the PE targets, make audit, make doc) and make test-unit ran green: 1970 windows-workspace tests and 755 unix-workspace tests passed, including the 12 capture_chord tests. No end-to-end test uses the hotkey, so make test was not run. The new key has not been pressed in a game yet; a reviewer can check it by pressing Ctrl+Shift+F7 in any game with MTL_CAPTURE_ENABLED=1 and looking for the [dump] lines and a <exe>-<pid>-<n>.gputrace beside the log.

Rules check

The change adds no new static: the existing per-poll latch is renamed, and its doc block keeps the "resource is process-wide" argument from docs/CONVENTIONS.md (State lives on an object, not in a static). It adds no config key, no environment variable, no wire field, no dependency, no derive and no lint suppression. The virtual-key codes move from windows/d3d9 into mtld3d-core rather than being duplicated, which follows "Factor pure functionality into mtld3d-core; d3d9 is wiring"; the tests restate them on purpose as the independent check. The tests stay in capture_chord/tests.rs (Unit tests live in <stem>/tests.rs). Doc blocks keep the title, blank, body shape. make check enforces these through clippy, make audit and make doc, and it ran green.

Ctrl+Shift+F12 is taken by the macOS Metal Performance HUD. The HUD
library adds a "Metal HUD" menu to the app's menu bar with Shift+F8
logging, Shift+F9 to enable the HUD, Shift+F11 to reset its metrics,
Shift+F12 for its configuration panel, and Ctrl+Shift+F9 to
Ctrl+Shift+F12 to generate a performance report over 5 seconds to 5
minutes. The menu consumes the key before Wine sees it, so the chord
never reached the layer, and the 5-minute report then crashed the game
inside the HUD's own code. Plain F12 stays out because it is Steam's
screenshot key.

The hotkey is now F7 going down while Control and Shift are held and
Alt (which winemac maps from Command) is up. The edge semantics are
unchanged: a held chord fires once, modifiers pressed after the key
never fire, and the modifiers are read only on the sample where the
key goes down.

The capture key's virtual-key code and the modifiers' codes move into
mtld3d-core's capture_chord, so the unit tests read real key codes
from each sample. They check that Ctrl+Shift+F7 fires and that F7,
Shift+F7, Ctrl+F7, Ctrl+Alt+Shift+F7, F12, Ctrl+Shift+F12, the HUD's
Shift+F8 to F12 and Ctrl+Shift+F9 to F12, and the near miss
Ctrl+Shift+F8 do not. The d3d9 poll's
latch is renamed for the capture key, and every doc and comment that
named Ctrl+Shift+F12 now names Ctrl+Shift+F7. The README and the
architecture notes give the real collision list and add that a
MacBook keyboard needs Fn for F7 unless the standard function key
setting is on.
@athei
athei merged commit 3ef6372 into main Oct 6, 2026
14 checks passed
@athei
athei deleted the capture-hotkey-f7 branch October 6, 2026 10:05
athei added a commit that referenced this pull request Oct 6, 2026
## Symptoms

Ctrl+Shift+F7 (#1075) does not arm the frame dump and GPU capture,
because the key never reaches Wine. A game run with Wine's `+key` trace
shows Fn+F7 alone arriving as `press 98` (kVK_F7), but with Control and
Shift held Wine receives only the Shift (56) and Control (59) presses
and no F7. Ctrl+Shift+F6 delivers nothing either.

macOS takes Ctrl+F1 to Ctrl+F8 for its keyboard-navigation shortcuts and
still takes them with Shift held. The Metal HUD's menu takes Shift+F8 to
Shift+F12 and Ctrl+Shift+F9 to Ctrl+Shift+F12, and Steam takes a bare
F12. So no Ctrl+Shift function-key chord reaches the game.

## Changes

The hotkey is now a fixed Ctrl+Shift+P: P going down while Control and
Shift are held and Alt is up (winemac reports either Command key as
Alt). The edge semantics are unchanged: a held chord fires once, Control
and Shift pressed after P is already down never fire, and the modifiers
are read only on the present where P goes down. `CAPTURE_KEY` in
`windows/core/src/capture_chord.rs` becomes `VK_P` (0x50);
`windows/d3d9/src/capture.rs` already reads it from there, so only its
comments change.

P sits at the same place on QWERTY, QWERTZ and AZERTY, so its
virtual-key code does not move with the keyboard layout, and as a letter
it needs no Fn on a MacBook keyboard. macOS, the Metal HUD and Steam do
not use it, and games that bind P rarely bind Ctrl+Shift+P.

The unit tests in `capture_chord/tests.rs` check that Ctrl+Shift+P fires
once per press and not while held, and that P, Shift+P, Ctrl+P and
Ctrl+Alt+Shift+P do not. They also check that the chords that never
reach Wine do not fire: F12, Ctrl+Shift+F12, the old Ctrl+Shift+F7, the
HUD's Shift+F8 to F12 and Ctrl+Shift+F9 to F12, and Ctrl+F1 to F8 with
or without Shift. The tests for modifiers pressed after the key and for
reading modifiers only on the press are kept.

Every doc and comment that named Ctrl+Shift+F7 now names Ctrl+Shift+P:
the README, `docs/ARCHITECTURE.md` (section heading and its anchor), the
link to it in `docs/GAMES.md`, `CONTRIBUTING.md`, the `bench-shape` text
in the Makefile, `mtld3d.conf`, the bench runner's messages and the code
comments on both sides. The README and the architecture section give the
new reason (Steam's F12, the HUD's Shift+F8 to F12 and Ctrl+Shift+F9 to
F12, and macOS's Ctrl+F1 to F8, which it takes even with Shift held),
say that P sits at the same place on the common layouts, keep the
Control-not-Command note, and drop the MacBook Fn note.

## Alternatives considered

Another function key: Ctrl+F1 to F8 belong to macOS with or without
Shift, Ctrl+Shift+F9 to F12 to the HUD, and F12 to Steam, so every
Ctrl+Shift function-key chord is taken before Wine sees it. A different
letter: a letter that moves between QWERTY, QWERTZ and AZERTY (such as
Y, Z, A, Q or M) would put the chord on a different key position, or a
different virtual-key code, depending on the layout, while P does not
move. A configurable key was not added: the chord stays fixed, with no
config key and no environment variable.

## Verification

`make fmt`, `make check` (rustfmt, clippy with nursery and pedantic on
both workspaces and the PE targets, `make audit`, `make doc`) and `make
test-unit` ran green: 1972 windows-workspace tests, including the 14
`capture_chord` tests, and 755 unix-workspace tests passed. No
end-to-end test uses the hotkey, so `make test` was not run.

The reason for the change comes from the `+key` trace above: Fn+F7 alone
reaches Wine as `press 98`, Ctrl+Shift+F7 and Ctrl+Shift+F6 deliver only
the modifier presses. Ctrl+Shift+P was pressed in World of Warcraft 1.12
with Wine's `+key` trace on, and Wine received `press 59` (Control),
`press 56` (Shift) and `press 35` (kVK_ANSI_P) with modifiers
0x00060103, so P arrives with Control and Shift held. The build
installed for that run predates this change, so the dump itself has not
fired from the new chord yet; a reviewer can check it by pressing
Ctrl+Shift+P in a game started with `MTL_CAPTURE_ENABLED=1` and looking
for the `[dump]` lines and a `<exe>-<pid>-<n>.gputrace` beside the log.

## Rules check

The change adds no static, config key, environment variable, wire field,
dependency, derive or lint suppression, and duplicates nothing: it
changes the value of the existing `CAPTURE_KEY` in `mtld3d-core` and the
existing per-poll latch keeps its doc-block argument. The tests stay in
`capture_chord/tests.rs` (Unit tests live in `<stem>/tests.rs`), and doc
blocks keep the title, blank, body shape. `make check` enforces these
through clippy, `make audit` and `make doc`, and it ran green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant