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
11 changes: 11 additions & 0 deletions .github/workflows/source-regressions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ on:
- gpu/vk/movie_upscale_policy.h
- tests/test_movie_upscale_policy.cpp
- tests/test_movie_upscale_policy.py
- host/display.c
- host/posix/display_sdl.c
- host/nn_hid.c
- tests/test_alt_enter.py
- nx2/test_import_windows.py
- tests/test_handle_lookup.py
- builder/linux/hookshot-launch
Expand Down Expand Up @@ -117,6 +121,10 @@ on:
- gpu/vk/movie_upscale_policy.h
- tests/test_movie_upscale_policy.cpp
- tests/test_movie_upscale_policy.py
- host/display.c
- host/posix/display_sdl.c
- host/nn_hid.c
- tests/test_alt_enter.py
- nx2/test_import_windows.py
- tests/test_handle_lookup.py
- builder/linux/hookshot-launch
Expand Down Expand Up @@ -227,6 +235,9 @@ jobs:
- name: Check movie upscale fallback policy
if: runner.os == 'Linux'
run: python -I -B tests/test_movie_upscale_policy.py
- name: Check Alt+Enter is fullscreen, not Plus
if: runner.os == 'Linux'
run: python -I -B tests/test_alt_enter.py
- name: Check import windows
if: runner.os == 'Linux'
run: python -I -B nx2/test_import_windows.py --cxx g++
Expand Down
11 changes: 10 additions & 1 deletion host/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
no emulated GPU, so the renderer is the only way to a picture.
HOS_HEADLESS=1: no window (scripted runs; the renderer draws offscreen). Closing the window ends the run.
HOS_WINDOW_NOACTIVATE=1: the window opens behind the one in front (test runs do not take the keyboard).
HOS_WINDOW_SIZE=<w>x<h>: the client area (default 1280x720 at 96 dpi). F11: fullscreen.
HOS_WINDOW_SIZE=<w>x<h>: the client area (default 1280x720 at 96 dpi). F11 or Alt+Enter: fullscreen.
Mouse: a click in the window captures the mouse (hidden, kept in the window), Esc or leaving the window releases
it (nn_hid.c reads its motion). */
#include "hos.h"
Expand Down Expand Up @@ -113,6 +113,15 @@ static LRESULT CALLBACK wndproc(HWND w, UINT msg, WPARAM wp, LPARAM lp) {
if (wp == VK_ESCAPE) mouse_capture(w, 0);
if (wp == VK_F11 && !(lp & (1 << 30))) set_fullscreen(w, !fullscreen);
return 0;
case WM_SYSKEYDOWN: /* Alt+Enter, as on Linux (display_sdl.c); other Alt keys (Alt+F4) go on to Windows */
if (wp == VK_RETURN && (lp & (1 << 29))) {
if (!(lp & (1 << 30))) set_fullscreen(w, !fullscreen);
return 0;
}
break;
case WM_SYSCHAR: /* (its character: without this Windows beeps, as for an Alt key with no menu) */
if (wp == '\r') return 0;
break;
case WM_APP + 7: /* (the Insert menu's row) */
set_fullscreen(w, (int)wp);
return 0;
Expand Down
6 changes: 5 additions & 1 deletion host/nn_hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ static int mouse_stick(int32_t *rx, int32_t *ry, int pointing) {
return 1;
}

/* A bound key is down: Enter held with Alt is no button (Alt+Enter is fullscreen, host/display.c and
host/posix/display_sdl.c). tests/test_alt_enter.py */
static int kmap_down(int vk, int (*down)(int)) { return down(vk) && !(vk == VK_RETURN && down(VK_MENU)); }

static int game_in_front(void) {
DWORD pid = 0;
HWND w = GetForegroundWindow();
Expand Down Expand Up @@ -543,7 +547,7 @@ static void read_input(uint64_t *buttons, int32_t stick[4]) {
binds_load(); /* (the keys: key_default's, or hookshot_options.ini's) */
for (int i = 0; i < HOS_SW_N; i++)
for (int k = 0; k < 2; k++)
if (key_bind[i][k] && key(key_bind[i][k])) *buttons |= 1ULL << i;
if (key_bind[i][k] && kmap_down(key_bind[i][k], key)) *buttons |= 1ULL << i;
int32_t mouse[4] = {0};
/* left button Y (attack), right button ZL (shield, lock on), while the mouse is captured; a button held when
the capture began counts only after it was let go (the click that captures never reaches the game) */
Expand Down
96 changes: 96 additions & 0 deletions tests/test_alt_enter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Alt+Enter toggles fullscreen and presses no button (Enter is Plus by default, and any button can be bound to it; issue #390).
The bound keys' helper (host/nn_hid.c's kmap_down) is compiled alone against fake key states; the window
procedures (host/display.c, host/posix/display_sdl.c) are checked to toggle on Alt+Enter, once per press, and to
leave other Alt keys (Alt+F4) to Windows.
python -I -B tests/test_alt_enter.py [--cc gcc]
"""
import argparse
import subprocess
import tempfile
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]


def block(text, marker):
start = text.index(marker)
end = text.index("{", start) + 1
depth = 1
while depth:
depth += (text[end] == "{") - (text[end] == "}")
end += 1
return text[start:end]


def case(text, label):
"""The body of one `case label:` of a switch, up to the next case"""
start = text.index("case %s:" % label)
end = text.index(" case ", start + 1)
return text[start:end]


def check_windows():
src = (ROOT / "host" / "display.c").read_text(encoding="utf-8")
sys_down = case(src, "WM_SYSKEYDOWN")
for need, why in (("VK_RETURN", "Enter"), ("1 << 29", "the Alt bit"), ("1 << 30", "a held key repeating"),
("set_fullscreen(w, !fullscreen)", "the toggle"), ("break;", "other Alt keys to DefWindowProc")):
if need not in sys_down:
raise SystemExit("display.c: WM_SYSKEYDOWN lacks %s (%s)" % (need, why))
if "'\\r'" not in case(src, "WM_SYSCHAR"):
raise SystemExit("display.c: Alt+Enter's WM_SYSCHAR is not taken (Windows beeps)")


def check_linux():
src = (ROOT / "host" / "posix" / "display_sdl.c").read_text(encoding="utf-8")
if "scancode == 40 && (mod & 0x300)" not in src:
raise SystemExit("display_sdl.c: Alt+Enter no longer toggles fullscreen")


FIXTURE = r'''#include <stdio.h>
#include <stdlib.h>
#define VK_RETURN 0x0D
#define VK_MENU 0x12
#define VK_TAB 0x09
#define CHECK(x) do { if (!(x)) { fprintf(stderr, "FAIL:%d %s\n", __LINE__, #x); exit(1); } } while (0)
static int held[256];
static int down(int vk) { return held[vk & 255]; }
@KMAP_DOWN@
int main(void) {
held[VK_RETURN] = 1;
CHECK(kmap_down(VK_RETURN, down)); /* Enter alone is Plus */
held[VK_MENU] = 1;
CHECK(!kmap_down(VK_RETURN, down)); /* Alt+Enter is not */
held[VK_TAB] = 1;
CHECK(kmap_down(VK_TAB, down)); /* Alt does not hide the other keys */
held[VK_RETURN] = 0;
CHECK(!kmap_down(VK_RETURN, down));
held[VK_MENU] = 0;
CHECK(!kmap_down(VK_MENU, down) && kmap_down(VK_TAB, down));
printf("PASS: Alt+Enter is fullscreen, not Plus\n");
return 0;
}
'''


def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--cc", default="gcc")
a = p.parse_args()
check_windows()
check_linux()
hid = (ROOT / "host" / "nn_hid.c").read_text(encoding="utf-8")
fn = block(hid, "static int kmap_down(")
if "kmap_down(key_bind[i][k], key)" not in hid:
raise SystemExit("nn_hid.c: the bound keys no longer go through kmap_down")
with tempfile.TemporaryDirectory(prefix="alt-enter-") as tmp:
c = Path(tmp) / "fixture.c"
exe = Path(tmp) / "fixture"
c.write_text(FIXTURE.replace("@KMAP_DOWN@", fn), encoding="utf-8")
subprocess.run([a.cc, "-std=c11", "-Wall", "-Wextra", "-Werror", str(c), "-o", str(exe)], check=True)
subprocess.run([str(exe)], check=True)


if __name__ == "__main__":
main()
Loading