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
46 changes: 30 additions & 16 deletions src/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,16 @@
}

static task_pool_util::TaskPool::task_id_t key_press_repeat_id {};
static std::unordered_map<key_press_id_t, bool> key_press {};

/**
* @brief Press state retained so releases and repeats use the key's original identity.
*/
struct key_press_state_t {
bool pressed = false; ///< Whether the key is held.
bool extended = false; ///< Whether its press identified an extended key.
};

static std::unordered_map<key_press_id_t, key_press_state_t> key_press {};

Check failure on line 175 in src/input.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Global variables should be const.

See more on https://sonarcloud.io/project/issues?id=LizardByte_Sunshine&issues=AaDk784hs4mYgcx91dwC&open=AaDk784hs4mYgcx91dwC&pullRequest=5821
static std::array<std::uint8_t, 5> mouse_press {};

static platf::input_t platf_input;
Expand Down Expand Up @@ -1093,15 +1102,16 @@
* @param key_code Platform keycode to emit.
* @param release Whether the key event is a release.
* @param flags Bit flags that modify the requested operation.
* @param extended Whether the client identified an extended key.
*/
void emit_keyboard_update(uint16_t key_code, bool release, uint8_t flags) {
void emit_keyboard_update(uint16_t key_code, bool release, uint8_t flags, bool extended = false) {
#ifdef SUNSHINE_TESTS
if (keyboard_sink()) {
keyboard_sink()(testing::keyboard_event_t {key_code, release, flags});
keyboard_sink()(testing::keyboard_event_t {key_code, release, flags, extended});
return;
}
#endif
platf::keyboard_update(platf_input, key_code, release, flags);
platf::keyboard_update(platf_input, key_code, release, flags, extended);
}

/**
Expand All @@ -1111,8 +1121,9 @@
* @param release Whether the key or button event is a release.
* @param flags Bit flags that modify the requested operation.
* @param synthetic_modifiers Synthetic modifiers.
* @param extended Whether the client identified an extended key.
*/
void send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers) {
void send_key_and_modifiers(uint16_t key_code, bool release, uint8_t flags, uint8_t synthetic_modifiers, bool extended) {
if (!release) {
// Press any synthetic modifiers required for this key
if (synthetic_modifiers & MODIFIER_SHIFT) {
Expand All @@ -1126,7 +1137,7 @@
}
}

emit_keyboard_update(map_keycode(key_code), release, flags);
emit_keyboard_update(map_keycode(key_code), release, flags, extended);

if (!release) {
// Raise any synthetic modifier keys we pressed
Expand All @@ -1151,12 +1162,13 @@
*/
void repeat_key(uint16_t key_code, uint8_t flags, uint8_t synthetic_modifiers) {
// If key no longer pressed, stop repeating
if (!key_press[make_kpid(key_code, flags)]) {
const auto state = key_press[make_kpid(key_code, flags)];
if (!state.pressed) {
key_press_repeat_id = nullptr;
return;
}

send_key_and_modifiers(key_code, false, flags, synthetic_modifiers);
send_key_and_modifiers(key_code, false, flags, synthetic_modifiers, state.extended);

key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code, flags, synthetic_modifiers).task_id;
}
Expand Down Expand Up @@ -1198,15 +1210,17 @@
}
}

auto &pressed = key_press[make_kpid(keyCode, packet->flags)];
if (!pressed) {
auto &state = key_press[make_kpid(keyCode, packet->flags)];
if (!state.pressed) {
if (!release) {
// A new key has been pressed down, we need to check for key combo's
// If a key-combo has been pressed down, don't pass it through
if (input->shortcutFlags == input_t::SHORTCUT && apply_shortcut(keyCode) > 0) {
return;
}

state.extended = (modifiers & MODIFIER_EXTENDED) != 0;

if (key_press_repeat_id) {
task_pool.cancel(key_press_repeat_id);
}
Expand All @@ -1223,9 +1237,9 @@
return;
}

pressed = !release;
state.pressed = !release;

send_key_and_modifiers(keyCode, release, packet->flags, synthetic_modifiers);
send_key_and_modifiers(keyCode, release, packet->flags, synthetic_modifiers, state.extended);

// Track the modifier state the client is holding, not the remapped host key.
// This is compared against packet->modifiers above, which is client-side, so a
Expand Down Expand Up @@ -2243,13 +2257,13 @@
* @brief Release every pressed keyboard key tracked by Sunshine.
*/
void reset_keyboard_keys() {
for (auto &[key, pressed] : key_press) {
if (pressed) {
for (auto &[key, state] : key_press) {
if (state.pressed) {
// key_press is keyed on the client's unmapped virtual-key code, but the press was
// emitted through map_keycode(). Release the host key that actually went down,
// otherwise a remapped modifier stays latched after the client disconnects.
emit_keyboard_update(map_keycode(vk_from_kpid(key) & 0x00FF), true, flags_from_kpid(key));
pressed = false;
emit_keyboard_update(map_keycode(vk_from_kpid(key) & 0x00FF), true, flags_from_kpid(key), state.extended);
state.pressed = false;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ namespace input {
std::uint16_t key_code; ///< Platform keycode after the configured keybinding remap.
bool release; ///< Whether the event releases the key.
std::uint8_t flags; ///< Bit flags carried by the client keyboard packet.
bool extended = false; ///< Whether the client positively identified an extended key.
};

/**
Expand Down
5 changes: 3 additions & 2 deletions src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -1189,11 +1189,12 @@ namespace platf {
* @brief Press or release a virtual keyboard key.
*
* @param input Platform input backend that receives the event.
* @param modcode Modifier key code to update.
* @param modcode Portable key code to update.
* @param release Whether the key or button event is a release.
* @param flags Bit flags that modify the requested operation.
* @param extended Whether the client positively identified an extended key.
*/
void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags);
void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags, bool extended = false);
void gamepad_update(input_t &input, int nr, const gamepad_state_t &gamepad_state);
/**
* @brief Submit UTF-8 text input to the keyboard backend.
Expand Down
11 changes: 6 additions & 5 deletions src/platform/virtualhid_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,11 @@ namespace platf::virtualhid {
};
}

lvh::KeyboardEvent keyboard_event(std::uint16_t modcode, bool release, std::uint8_t flags) {
lvh::KeyboardEvent keyboard_event(std::uint16_t modcode, bool release, std::uint8_t flags, bool extended) {
lvh::KeyboardEvent event {
.key_code = modcode,
.pressed = !release,
.extended = extended,
};

#ifdef _WIN32
Expand Down Expand Up @@ -837,9 +838,9 @@ namespace platf::virtualhid {
}
}

void keyboard_update(input_context_t &context, std::uint16_t modcode, bool release, std::uint8_t flags) {
void keyboard_update(input_context_t &context, std::uint16_t modcode, bool release, std::uint8_t flags, bool extended) {
if (context.keyboard) {
log_failure("submit libvirtualhid keyboard input"sv, context.keyboard->submit(keyboard_event(modcode, release, flags)));
log_failure("submit libvirtualhid keyboard input"sv, context.keyboard->submit(keyboard_event(modcode, release, flags, extended)));
}
}

Expand Down Expand Up @@ -1065,8 +1066,8 @@ namespace platf {
virtualhid::hscroll(virtualhid::get_input_context(input), high_res_distance);
}

void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
virtualhid::keyboard_update(virtualhid::get_input_context(input), modcode, release, flags);
void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags, bool extended) {
virtualhid::keyboard_update(virtualhid::get_input_context(input), modcode, release, flags, extended);
}

void unicode(input_t &input, const char *utf8, int size) {
Expand Down
3 changes: 2 additions & 1 deletion src/platform/virtualhid_input.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,9 @@ namespace platf::virtualhid {
* @param modcode Portable key code.
* @param release Whether the key was released.
* @param flags Bit flags that modify the requested operation.
* @param extended Whether the client positively identified an extended key.
*/
void keyboard_update(input_context_t &context, std::uint16_t modcode, bool release, std::uint8_t flags);
void keyboard_update(input_context_t &context, std::uint16_t modcode, bool release, std::uint8_t flags, bool extended = false);

/**
* @brief Submit UTF-8 text input.
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/platform/test_virtualhid_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,7 @@ TEST_F(VirtualHidDeviceTest, TranslatesMouseAndKeyboardInput) {
auto keyboard_event = context()->keyboard->last_submitted_event();
EXPECT_EQ(keyboard_event.key_code, 0x41);
EXPECT_TRUE(keyboard_event.pressed);
EXPECT_FALSE(keyboard_event.extended);
#ifdef _WIN32
EXPECT_TRUE(keyboard_event.uses_normalized_key_code);
EXPECT_TRUE(keyboard_event.prefer_native_scan_code);
Expand All @@ -758,6 +759,10 @@ TEST_F(VirtualHidDeviceTest, TranslatesMouseAndKeyboardInput) {
keyboard_event = context()->keyboard->last_submitted_event();
#endif
EXPECT_FALSE(keyboard_event.pressed);
platf::virtualhid::keyboard_update(*context(), 0x0D, false, 0, true);
keyboard_event = context()->keyboard->last_submitted_event();
EXPECT_EQ(keyboard_event.key_code, 0x0D);
EXPECT_TRUE(keyboard_event.extended);

const auto keyboard_submit_count = context()->keyboard->submit_count();
const std::string text = "Sunshine \u{2600}";
Expand Down Expand Up @@ -950,6 +955,8 @@ TEST_F(VirtualHidDeviceTest, PlatformWrappersForwardToVirtualHidContext) {
EXPECT_EQ(platform_context.mouse->last_submitted_event().kind, lvh::MouseEventKind::horizontal_scroll);
platf::keyboard_update(platform_input, 0x41, false, 0);
EXPECT_EQ(platform_context.keyboard->last_submitted_event().key_code, 0x41);
platf::keyboard_update(platform_input, 0x0D, false, 0, true);
EXPECT_TRUE(platform_context.keyboard->last_submitted_event().extended);
const std::string text = "wrapper";
const auto keyboard_count = platform_context.keyboard->submit_count();
platf::unicode(platform_input, text.data(), static_cast<int>(text.size()));
Expand Down
29 changes: 29 additions & 0 deletions tests/unit/test_keyboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,35 @@ TEST_F(KeyboardPassthroughTest, ForwardsEveryUnmodifiedKeyUnchanged) {
}
}

TEST_F(KeyboardPassthroughTest, PreservesExtendedEnterOnReleaseAndDisconnect) {
press(VKEY_RETURN);
release(VKEY_RETURN);
auto events = taken_events();
ASSERT_EQ(events.size(), 2U);
EXPECT_FALSE(events[0].extended);
EXPECT_FALSE(events[1].extended);

press(VKEY_RETURN, MODIFIER_EXTENDED | MODIFIER_SHIFT);
events = taken_events();
ASSERT_EQ(events.size(), 3U);
EXPECT_FALSE(events[0].extended);
EXPECT_TRUE(events[1].extended);
EXPECT_FALSE(events[2].extended);

// The bit may be absent on key-up; release the key as it was pressed.
release(VKEY_RETURN);
events = taken_events();
ASSERT_EQ(events.size(), 1U);
EXPECT_TRUE(events[0].extended);

press(VKEY_RETURN, MODIFIER_EXTENDED);
taken_events();
input::testing::release_held_keys();
events = taken_events();
ASSERT_EQ(events.size(), 1U);
EXPECT_TRUE(events[0].extended);
}

TEST_F(KeyboardPassthroughTest, CoversTheWholePrintableAsciiRange) {
// Guard the table itself: every printable ASCII character must be reachable, exactly once.
std::set<char> covered;
Expand Down
Loading