Skip to content
Open
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: 5 additions & 6 deletions gpu/vk/vk_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
extern "C" {
#endif

#define MKWVK_API_VERSION 21
#define MKWVK_API_VERSION 22
enum { HOS_OPT_CHEAT_HEARTS = 32, HOS_OPT_CHEAT_FLIGHT, HOS_OPT_CHEAT_ACTION, HOS_OPT_CHEAT_CAMERA, HOS_OPT_CHEAT_SPEED };
/* (hookshot) the Gameplay tab's Text size (host/oot_text_size.c): 0 the game's, 1 110%, 2 125%, 3 150%, 4 175% */
enum { HOS_OPT_TEXT_SIZE = 10 };
Expand Down Expand Up @@ -60,13 +60,12 @@ enum { HOS_SW_A, HOS_SW_B, HOS_SW_X, HOS_SW_Y, HOS_SW_LS, HOS_SW_RS, HOS_SW_L, H
enum { HOS_PAD_SOUTH, HOS_PAD_EAST, HOS_PAD_WEST, HOS_PAD_NORTH, HOS_PAD_BACK, HOS_PAD_GUIDE, HOS_PAD_START,
HOS_PAD_LSTICK, HOS_PAD_RSTICK, HOS_PAD_LSHOULDER, HOS_PAD_RSHOULDER, HOS_PAD_UP, HOS_PAD_DOWN, HOS_PAD_LEFT,
HOS_PAD_RIGHT, HOS_PAD_LTRIGGER, HOS_PAD_RTRIGGER, HOS_PAD_RPADDLE1, HOS_PAD_LPADDLE1,
HOS_PAD_RPADDLE2, HOS_PAD_LPADDLE2, HOS_PAD_N };
HOS_PAD_RPADDLE2, HOS_PAD_LPADDLE2, HOS_PAD_TOUCHPAD, HOS_PAD_N };
/* (hookshot) the inputs that have prompt artwork (host/prompts.c, nx2/prompt_assets.py's PADS): the prompt font's code
points go by this count, so the paddles past it show their Switch button's default icon */
points go by this count, so the paddles and the touchpad past it show their Switch button's default icon. HOS_PAD_TOUCHPAD:
a DualShock 4's or DualSense's touchpad click; while no row of the Controls page takes it, it opens the Insert menu as F1
does (gpu/vk/vk_menu.cpp) */
enum { HOS_PAD_ART_N = HOS_PAD_RTRIGGER + 1 };
/* (hookshot) a DualShock 4's or DualSense's touchpad click, which opens the Insert menu as F1 does (host/pad_sdl.c,
gpu/vk/vk_menu.cpp): its own bit past HOS_PAD_N, so it is not one of the inputs the Controls page binds */
enum { HOS_PAD_TOUCHPAD = 31 };
enum { HOS_BIND_PAD, HOS_BIND_KEY };
/* (hookshot) the controller faces of the button prompts (host/prompts.c): the Switch, then nx2/prompt_faces.py FACES' order,
then the keyboard's (nx2/prompt_keys.py: the keys bound, not the pad inputs); the Button prompts row's value is one more (0
Expand Down
15 changes: 12 additions & 3 deletions gpu/vk/vk_menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2405,11 +2405,19 @@ const Control controls[] = {{HOS_SW_A, "A"}, {HOS_SW_B, "B"}, {HOS_SW_X, "X"}, {
constexpr int kControls = sizeof controls / sizeof controls[0]; // (then "Reset to defaults")
// (the stick's and camera's directions: keys only; the ocarina's note rows take a gamepad input too, host/nn_hid.c)
bool keys_only(int i) { return i < kControls && controls[i].button >= HOS_KEY_MOVE_UP && controls[i].button <= HOS_KEY_CAM_RIGHT; }
// a row takes the touchpad click as its Input 1 or Input 2: then the click is that row's, not the menu's
bool touchpad_bound() {
for (int i = 0; host && host->binding && i < kControls; i++)
for (int slot = 0; slot < 2 && !keys_only(i); slot++)
if (host->binding(HOS_BIND_PAD, controls[i].button, slot) == HOS_PAD_TOUCHPAD) return true;
return false;
}
// a pad input's name on the controller of the button prompts (the Button prompts row's choice; Auto: the face in use),
// else by its place
const char* pad_name(int p) {
if (p < 0 || p >= HOS_PAD_N) return "None";
static const char* paddles[] = {"R4", "L4", "R5", "L5"};
if (p == HOS_PAD_TOUCHPAD) return "Touchpad";
if (p >= HOS_PAD_RPADDLE1) return paddles[p - HOS_PAD_RPADDLE1];
static const char* t[HOS_PROMPT_KEYBOARD + 1][HOS_PAD_RPADDLE1] = { // (by place, then HOS_PROMPT_*; the keyboard's: by place)
{"Bottom button", "Right button", "Left button", "Top button", "Select / Back", "Home", "Start", "L stick press",
Expand Down Expand Up @@ -4700,8 +4708,9 @@ void menu_tick() {
if (force) pad_last = !strcmp(force, "pad");
}
auto pad = [&](int p) { return listening != 1 && (pad_press >> p & 1) != 0; };
// (hookshot) a DualShock 4's or DualSense's touchpad click is F1: it opens and closes the menu as the key does
if (pad(HOS_PAD_TOUCHPAD)) press[6] = true;
// (hookshot) a DualShock 4's or DualSense's touchpad click is F1, while no row of the Controls page takes it: it opens and
// closes the menu as the key does
if (pad(HOS_PAD_TOUCHPAD) && !touchpad_bound()) press[6] = true;
if (host && host->binding) // (A and B as bound now: the Controls page or the Layout preset can change them)
menu_keys::confirm_back(host->binding(HOS_BIND_PAD, HOS_SW_A, 0), host->binding(HOS_BIND_PAD, HOS_SW_B, 0), &pad_confirm,
&pad_back);
Expand Down Expand Up @@ -4821,7 +4830,7 @@ void menu_tick() {
if (press[5] || prof_now() > listen_end) listening = 0, changed = true; // (Esc, or nothing for a while)
else if (listening == 1) {
if (!listen_armed) listen_armed = (raw & ~(1u << HOS_PAD_GUIDE)) == 0;
else if (const uint32_t p = pad_press & ~(1u << HOS_PAD_GUIDE) & ~(1u << HOS_PAD_TOUCHPAD)) {
else if (const uint32_t p = pad_press & ~(1u << HOS_PAD_GUIDE)) {
int bit = 0;
while (!(p >> bit & 1)) bit++;
host->bind(HOS_BIND_PAD, controls[csel - kTop].button, ccol, bit);
Expand Down
4 changes: 2 additions & 2 deletions host/pad_sdl.c
Original file line number Diff line number Diff line change
Expand Up @@ -426,8 +426,8 @@ static void pads_poll(void) {
for (int k = BTN_RPADDLE1; k <= BTN_LPADDLE2; k++)
if (hos_sdl_pad_button(gc, k)) sig |= 1ULL << (HOS_PAD_RPADDLE1 + k - BTN_RPADDLE1);
b |= sig;
/* (hookshot) a DualShock 4's or DualSense's touchpad click: it opens the Insert menu as F1 does (vk_menu.cpp).
SDL numbers it 20, past the buttons above; an SDL without it answers 0 */
/* (hookshot) a DualShock 4's or DualSense's touchpad click: a Controls row's input, or unbound it opens the
Insert menu as F1 does (vk_menu.cpp). SDL numbers it 20, past the buttons above; an SDL without it answers 0 */
if (hos_sdl_pad_button(gc, BTN_TOUCHPAD)) b |= 1ULL << HOS_PAD_TOUCHPAD;
if (hos_sdl_pad_axis(gc, AX_LT) > 8000) b |= 1ULL << HOS_PAD_LTRIGGER;
if (hos_sdl_pad_axis(gc, AX_RT) > 8000) b |= 1ULL << HOS_PAD_RTRIGGER;
Expand Down
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,8 @@ shows bands or blocks. Off unless set; `RX_VK_ASTC_RGBA8=1` above still wins whe
## In-game menu

By default, press <kbd>Insert</kbd>, <kbd>F1</kbd> or <kbd>Esc</kbd> in game. On a controller, press Home or Select and
Start together; on a DualShock 4 or DualSense, clicking the touchpad does the same as <kbd>F1</kbd>.
Start together; on a DualShock 4 or DualSense, clicking the touchpad does the same as <kbd>F1</kbd>, unless a row of
Controls takes the touchpad as its input.
The same keys close it; with every Menu shortcut, <kbd>Esc</kbd> goes back a page or closes the open menu.
<kbd>F1</kbd> always works. **Controls → Menu shortcut** can leave <kbd>Insert</kbd> free for another overlay (F1 only).
With Insert or F1, or with F1 only, <kbd>Esc</kbd> does not open the menu: it only lets the mouse go. Options saved by
Expand Down
6 changes: 4 additions & 2 deletions tests/test_bound_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ def between(text, first, last):
CHECK(hos_binding(HOS_BIND_PAD, HOS_SW_X, 1) == -1); /* invalid second value */
CHECK(bound_pad_buttons(face) == a && bound_pad_buttons(paddle) == a && bound_pad_buttons(face | paddle) == a);
CHECK(bound_pad_buttons(1ULL << HOS_PAD_RTRIGGER) == 1ULL << HOS_SW_ZR);
CHECK(bound_pad_buttons(1ULL << HOS_PAD_TOUCHPAD) == 0);
/* the touchpad click, saved as Minus's input (the map, as players ask): read back, and it presses Minus */
CHECK(hos_binding(HOS_BIND_PAD, HOS_SW_MINUS, 0) == HOS_PAD_TOUCHPAD);
CHECK(bound_pad_buttons(1ULL << HOS_PAD_TOUCHPAD) == 1ULL << HOS_SW_MINUS);
hos_bind(HOS_BIND_PAD, HOS_SW_B, 1, HOS_PAD_RPADDLE1);
CHECK(hos_binding(HOS_BIND_PAD, HOS_SW_A, 1) == -1);
CHECK(bound_pad_buttons(paddle) == 1ULL << HOS_SW_B && bound_pad_buttons(face) == a);
Expand Down Expand Up @@ -174,7 +176,7 @@ def test_bindings(self):
with tempfile.TemporaryDirectory(prefix="hookshot-bound-keys-") as tmp:
tmp = Path(tmp)
(tmp / "hookshot_options.ini").write_text(
"key_move_up=90,0\npad_move_up=0\npad_a=1,17\npad_b=0\npad_x=3,99\n", encoding="utf-8"
"key_move_up=90,0\npad_move_up=0\npad_a=1,17\npad_b=0\npad_x=3,99\npad_minus=21\n" # (21: the touchpad), encoding="utf-8"
)
(tmp / "t.c").write_text(PRELUDE + code + MAIN, encoding="utf-8")
binary = tmp / ("t.exe" if sys.platform == "win32" else "t")
Expand Down
88 changes: 73 additions & 15 deletions tests/test_touchpad_menu.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"""Clicking a DualShock 4's or DualSense's touchpad opens the Insert menu, as F1 does.
"""A DualShock 4's or DualSense's touchpad click: a Controls row can take it, and while none does it opens the Insert menu, as
F1 does.

SDL numbers the touchpad click button 20 (SDL_CONTROLLER_BUTTON_TOUCHPAD in SDL2, SDL_GAMEPAD_BUTTON_TOUCHPAD in SDL3),
past the buttons host/pad_sdl.c reads for the game. Its poll turns that button into HOS_PAD_TOUCHPAD, and
gpu/vk/vk_menu.cpp presses F1 (press[6]) with it: the menu opens, and closes again, as the key does. The touchpad is not
one of the inputs the Controls page binds, so its bit is past HOS_PAD_N.
past the buttons host/pad_sdl.c reads for the game. Its poll turns that button into HOS_PAD_TOUCHPAD, the gamepad input
after the paddles. gpu/vk/vk_menu.cpp presses F1 (press[6]) with it while no row has it as Input 1 or Input 2.

The poll's button block is compiled over a stand-in SDL, which reports one button at a time.
It also checks that all four paddle buttons have their own bindable bits.
The poll's button block is compiled over a stand-in SDL, which reports one button at a time; it also checks that all four
paddle buttons have their own bindable bits. Then the menu's Controls table, touchpad_bound and pad_name, its F1 line and
its listening binding's mask are cut from vk_menu.cpp and run with a stand-in host's bindings: unbound, a click is F1;
bound to Minus (or an ocarina note) it is not; a listening binding takes the click; its name is "Touchpad".

python -I -B tests/test_touchpad_menu.py [--cxx clang++]
"""
Expand Down Expand Up @@ -59,16 +61,16 @@ def main():
int main(void) {
/* (SDL2's SDL_CONTROLLER_BUTTON_TOUCHPAD and SDL3's SDL_GAMEPAD_BUTTON_TOUCHPAD are both 20) */
CHECK(BTN_TOUCHPAD == 20);
/* (its own bit, past the positions a Switch button can be bound to) */
CHECK((int)HOS_PAD_TOUCHPAD >= (int)HOS_PAD_N && (int)HOS_PAD_TOUCHPAD < 32);
/* (the gamepad input after the paddles, which a Switch button can be bound to; the menu's masks are 32 bits) */
CHECK((int)HOS_PAD_TOUCHPAD == (int)HOS_PAD_LPADDLE2 + 1 && (int)HOS_PAD_TOUCHPAD < (int)HOS_PAD_N && HOS_PAD_N <= 32);
CHECK(poll_button(-1) == 0);
CHECK(poll_button(BTN_TOUCHPAD) == 1ULL << HOS_PAD_TOUCHPAD);
CHECK(poll_button(BTN_A) == 1ULL << HOS_PAD_SOUTH);
CHECK(poll_button(16) == 1ULL << HOS_PAD_RPADDLE1);
CHECK(poll_button(17) == 1ULL << HOS_PAD_LPADDLE1);
CHECK(poll_button(18) == 1ULL << HOS_PAD_RPADDLE2);
CHECK(poll_button(19) == 1ULL << HOS_PAD_LPADDLE2);
printf("PASS: SDL's touchpad click sets its own bit, past the bindable inputs\n");
printf("PASS: SDL's touchpad click sets its own bit, a bindable input\n");
return 0;
}
'''
Expand All @@ -84,14 +86,70 @@ def main():
# press[6] is F1 (the key that always opens the menu), which the touchpad presses
need(tick, r"keys\[K_N\] = \{VK_INSERT, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_ESCAPE, VK_F1,",
"vk_menu.cpp: press[6] is not F1")
need(tick, r"if \(pad\(HOS_PAD_TOUCHPAD\)\) press\[6\] = true;",
"vk_menu.cpp: a touchpad click does not open the menu as F1")
if re.search(r"press\[(0|5|7)\] \|=? ?pad\(HOS_PAD_TOUCHPAD\)", tick):
raise SystemExit("FAIL: vk_menu.cpp: the touchpad also acts as Insert, confirm or back")
# (a touchpad click while a binding listens is not one: it cannot be bound)
need(tick, r"pad_press & ~\(1u << HOS_PAD_GUIDE\) & ~\(1u << HOS_PAD_TOUCHPAD\)",
"vk_menu.cpp: the Controls page binding can take the touchpad click")
print("PASS: a DualShock 4 / DualSense touchpad click opens the menu as F1")
f1 = re.search(r"^ *if \(pad\(HOS_PAD_TOUCHPAD\)[^\n]*press\[6\] = true;\n", tick, re.M)
listen = re.search(r"else if \(const uint32_t p = (pad_press [^\n]*?)\) \{", tick)
if not f1 or not listen:
raise SystemExit("FAIL: vk_menu.cpp: no touchpad F1 line or no listening binding's mask in menu_tick")
table = menu[menu.index("struct Control {"):menu.index("const char* key_name(")]
driver = r"""#define _CRT_SECURE_NO_WARNINGS 1
#define _Static_assert static_assert // (rx_nvn.h is C)
#include "gpu/vk/vk_api.h"
#include <cstdint>
#include <cstdio>
#include <cstring>
#define CHECK(x) do { if (!(x)) { std::printf("FAIL:%d %s\n", __LINE__, #x); fails++; } } while (0)
static int fails;
static const MkwVkHost* host;
static struct { int prompts; } opts;
static int bound[HOS_KEY_N][2];
static int binding(int set, int button, int slot) { return set == HOS_BIND_PAD ? bound[button][slot] : 0; }
""" + table + r"""
static bool f1(uint32_t pad_press, int listening) {
bool press[8] = {};
auto pad = [&](int p) { return listening != 1 && (pad_press >> p & 1) != 0; };
""" + f1.group(0) + r""" return press[6];
}
static int listened(uint32_t pad_press) { // (the input a listening binding takes, -1 none)
if (const uint32_t p = """ + listen.group(1) + r""") {
int bit = 0;
while (!(p >> bit & 1)) bit++;
return bit;
}
return -1;
}
int main() {
MkwVkHost h{};
h.binding = binding;
host = &h;
for (auto& b : bound) b[0] = b[1] = -1;
bound[HOS_SW_MINUS][0] = HOS_PAD_BACK;
const uint32_t click = 1u << HOS_PAD_TOUCHPAD;
CHECK(f1(click, 0)); // (unbound: the menu, as now)
CHECK(!f1(click, 1)); // (not while a binding listens)
CHECK(listened(click) == HOS_PAD_TOUCHPAD);
CHECK(listened(click | 1u << HOS_PAD_GUIDE) == HOS_PAD_TOUCHPAD);
bound[HOS_SW_MINUS][1] = HOS_PAD_TOUCHPAD;
CHECK(!f1(click, 0)); // (Minus's Input 2: the click is Minus's)
bound[HOS_SW_MINUS][1] = -1, bound[HOS_KEY_OC_NOTE_A][0] = HOS_PAD_TOUCHPAD;
CHECK(!f1(click, 0)); // (an ocarina note's)
bound[HOS_KEY_OC_NOTE_A][0] = -1;
CHECK(f1(click, 0)); // (unbound again)
CHECK(!std::strcmp(pad_name(HOS_PAD_TOUCHPAD), "Touchpad"));
CHECK(!std::strcmp(pad_name(HOS_PAD_LPADDLE2), "L5"));
return fails ? 1 : 0;
}
"""
with tempfile.TemporaryDirectory(prefix="touchpad-bind-") as tmp:
cpp, exe = Path(tmp) / "menu.cpp", Path(tmp) / "menu.exe"
cpp.write_text(driver, encoding="utf-8")
subprocess.run([a.cxx, "-std=c++17", "-Wall", "-Wextra", "-Werror", "-Wno-unused-function", "-Wno-unused-variable",
"-I" + str(ROOT), str(cpp), "-o", str(exe)], check=True)
r = subprocess.run([str(exe)], capture_output=True, text=True)
if r.returncode:
raise SystemExit("FAIL: the touchpad click and the Controls page:\n" + r.stdout + r.stderr)
print("PASS: a touchpad click is a Controls input; unbound, it opens the menu as F1")


if __name__ == "__main__":
Expand Down
Loading