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
54 changes: 51 additions & 3 deletions src/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ extern "C" {
#include <bitset>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <functional>
#include <list>
#include <memory>
Expand Down Expand Up @@ -66,6 +67,16 @@ namespace input {
constexpr auto VKEY_LMENU = 0xA4; ///< Windows virtual-key code for lmenu.
constexpr auto VKEY_RMENU = 0xA5; ///< Windows virtual-key code for rmenu.

constexpr auto SS_KEY_UNDO = 0x0100; ///< Non-normalized client keycode for undo.
constexpr auto SS_KEY_CUT = 0x0101; ///< Non-normalized client keycode for cut.
constexpr auto SS_KEY_COPY = 0x0102; ///< Non-normalized client keycode for copy.
constexpr auto SS_KEY_PASTE = 0x0103; ///< Non-normalized client keycode for paste.

constexpr auto SS_SCANCODE_UNDO = 0x7A; ///< SDL scancode delivered for undo when non-normalized.
constexpr auto SS_SCANCODE_CUT = 0x7B; ///< SDL scancode delivered for cut when non-normalized.
constexpr auto SS_SCANCODE_COPY = 0x7C; ///< SDL scancode delivered for copy when non-normalized.
constexpr auto SS_SCANCODE_PASTE = 0x7D; ///< SDL scancode delivered for paste when non-normalized.

/**
* @brief Enumerates supported button state options.
*/
Expand Down Expand Up @@ -1047,7 +1058,36 @@ namespace input {
}

auto release = util::endian::little(packet->header.magic) == KEY_UP_EVENT_MAGIC;
auto keyCode = packet->keyCode & 0x00FF;
// Keycodes carrying the legacy 0x8000 flag are normalized Windows virtual-key
// codes for which only the low byte is significant. Non-normalized keycodes
// (such as the Undo/Cut/Copy/Paste editing keys) are interpreted as-is.
auto keyCode = (packet->keyCode & 0x8000) ? (packet->keyCode & 0x00FF) : (packet->keyCode & 0xFFFF);

int modifiers = packet->modifiers;

// Clients deliver the Undo/Cut/Copy/Paste editing keys as non-normalized
// keycodes, either as the SDL scancode or a dedicated value. Normalize the
// scancode form to the canonical keycode that the platform backend maps to
// the native editing key. The flag gate prevents the F11-F14 virtual keys
// (0x7A-0x7D) from being reinterpreted.
if ((static_cast<std::byte>(packet->flags) & static_cast<std::byte>(SS_KBE_FLAG_NON_NORMALIZED)) != std::byte {}) {
switch (keyCode) {
case SS_SCANCODE_UNDO:
keyCode = SS_KEY_UNDO;
break;
case SS_SCANCODE_CUT:
keyCode = SS_KEY_CUT;
break;
case SS_SCANCODE_COPY:
keyCode = SS_KEY_COPY;
break;
case SS_SCANCODE_PASTE:
keyCode = SS_KEY_PASTE;
break;
default:
break;
}
}

if (keyCode == VKEY_LMENU) {
input->left_alt_pressed = !release;
Expand All @@ -1056,7 +1096,6 @@ namespace input {
}

// Right-alt maps to meta, so it must not also register as ALT
int modifiers = packet->modifiers;
if (config::input.key_rightalt_to_key_win && input->right_alt_pressed && !input->left_alt_pressed) {
modifiers &= ~MODIFIER_ALT;
}
Expand Down Expand Up @@ -1966,7 +2005,7 @@ namespace input {
void reset_keyboard_keys() {
for (auto &[key, pressed] : key_press) {
if (pressed) {
platf::keyboard_update(platf_input, vk_from_kpid(key) & 0x00FF, true, flags_from_kpid(key));
platf::keyboard_update(platf_input, vk_from_kpid(key), true, flags_from_kpid(key));
pressed = false;
}
}
Expand Down Expand Up @@ -2156,6 +2195,15 @@ namespace input {
}
return input->gamepads[client_index].id;
}

void keyboard_passthrough(std::shared_ptr<input_t> &input, std::uint16_t key_code, std::uint8_t modifiers, std::uint8_t flags, bool release) {
NV_KEYBOARD_PACKET packet {};
packet.header.magic = util::endian::little(static_cast<std::uint32_t>(release ? KEY_UP_EVENT_MAGIC : KEY_DOWN_EVENT_MAGIC));
packet.keyCode = static_cast<short>(key_code);
packet.modifiers = static_cast<char>(modifiers);
packet.flags = static_cast<char>(flags);
passthrough(input, &packet);
}
} // namespace testing
#endif
} // namespace input
11 changes: 11 additions & 0 deletions src/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ namespace input {
* @return Assigned global gamepad slot, or -1 when unallocated.
*/
int gamepad_id(const std::shared_ptr<input_t> &input, std::uint8_t client_index);

/**
* @brief Synchronously dispatch a keyboard event through the passthrough path for a unit test.
*
* @param input Retained input state.
* @param key_code Client keyboard packet key code.
* @param modifiers Client modifier flags.
* @param flags Client keyboard packet flags.
* @param release Whether the event is a key release.
*/
void keyboard_passthrough(std::shared_ptr<input_t> &input, std::uint16_t key_code, std::uint8_t modifiers, std::uint8_t flags, bool release);
} // namespace testing
#endif

Expand Down
55 changes: 55 additions & 0 deletions tests/unit/test_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/

// standard includes
#include <array>
#include <chrono>
#include <memory>
#include <string>

Expand Down Expand Up @@ -116,3 +118,56 @@ TEST_F(InputGamepadSessionTest, RefreshesSharedMouseAfterLicenseStateChanges) {
EXPECT_NE(context().mouse->device_id(), original_mouse_id);
EXPECT_EQ(runtime().active_device_count(), active_devices);
}

TEST_F(InputGamepadSessionTest, TranslatesEditingKeysToCanonicalKeycodes) {
config::input.keyboard = true;
config::input.key_repeat_delay = std::chrono::milliseconds {0};

auto mail = std::make_shared<safe::mail_raw_t>();
auto session = input::alloc(mail, "editing-keys-client");
ASSERT_NE(session, nullptr);
ASSERT_NE(context().keyboard, nullptr);

constexpr std::uint8_t non_normalized = 0x01; // SS_KBE_FLAG_NON_NORMALIZED

struct editing_key_case {
std::uint16_t client_key; ///< Keycode sent by the client.
std::uint16_t expected_key; ///< Canonical keycode forwarded to the backend.
};
const std::array<editing_key_case, 8> cases {{
{0x7A, 0x0100}, // SDL scancode Undo
{0x7B, 0x0101}, // SDL scancode Cut
{0x7C, 0x0102}, // SDL scancode Copy
{0x7D, 0x0103}, // SDL scancode Paste
{0x0100, 0x0100}, // Dedicated Undo
{0x0101, 0x0101}, // Dedicated Cut
{0x0102, 0x0102}, // Dedicated Copy
{0x0103, 0x0103}, // Dedicated Paste
}};

for (const auto &editing_key : cases) {
const auto before = context().keyboard->submit_count();

input::testing::keyboard_passthrough(session, editing_key.client_key, 0, non_normalized, false);
EXPECT_EQ(context().keyboard->submit_count(), before + 1);
const auto press_event = context().keyboard->last_submitted_event();
EXPECT_EQ(press_event.key_code, editing_key.expected_key);
EXPECT_TRUE(press_event.pressed);

input::testing::keyboard_passthrough(session, editing_key.client_key, 0, non_normalized, true);
EXPECT_EQ(context().keyboard->submit_count(), before + 2);
const auto release_event = context().keyboard->last_submitted_event();
EXPECT_EQ(release_event.key_code, editing_key.expected_key);
EXPECT_FALSE(release_event.pressed);
}

// Without the non-normalized flag, 0x7A-0x7D are the F11-F14 virtual keys and
// must pass through untranslated.
const auto before_fn = context().keyboard->submit_count();
input::testing::keyboard_passthrough(session, 0x7A, 0, 0, false);
EXPECT_EQ(context().keyboard->submit_count(), before_fn + 1);
const auto fn_event = context().keyboard->last_submitted_event();
EXPECT_EQ(fn_event.key_code, 0x7A);
EXPECT_TRUE(fn_event.pressed);
input::testing::keyboard_passthrough(session, 0x7A, 0, 0, true);
}