Skip to content

Commit

Permalink
No longer implicitly waiting for key release before timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
houmain committed Dec 29, 2023
1 parent 9104337 commit ab6d011
Show file tree
Hide file tree
Showing 14 changed files with 355 additions and 233 deletions.
35 changes: 17 additions & 18 deletions src/config/ParseKeySequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,11 @@ void ParseKeySequence::up_any_keys_not_up_yet() {
m_keys_not_up.clear();
}

void ParseKeySequence::sync_adjacent_to_timeout() {
// sync before every timeout
for (auto i = 1u; i < m_sequence.size(); ++i)
if (m_sequence[i].key == Key::timeout)
if (m_sequence[i - 1].state == KeyState::UpAsync)
m_sequence[i - 1].state = KeyState::Up;

// sync after not-timeouts
void ParseKeySequence::sync_after_not_timeouts() {
for (auto i = 1u; i + 1 < m_sequence.size(); ++i)
if (m_sequence[i].key == Key::timeout &&
m_sequence[i].state == KeyState::Not)
if (m_sequence[i + 1].state == KeyState::UpAsync)
m_sequence[i + 1].state = KeyState::Up;
if (is_not_timeout(m_sequence[i]) &&
m_sequence[i + 1].state == KeyState::UpAsync)
m_sequence[i + 1].state = KeyState::Up;
}

bool ParseKeySequence::all_pressed_at_once() const {
Expand Down Expand Up @@ -151,13 +143,21 @@ Key ParseKeySequence::read_key(It* it, const It end) {
throw ParseError("Invalid key '" + key_name + "'");
}

void ParseKeySequence::add_timeout_event(KeyState state, uint16_t timeout) {
void ParseKeySequence::add_timeout_event(uint16_t timeout, bool is_not, bool cancel_on_up) {
flush_key_buffer(true);
if (m_is_input && !has_key_down(m_sequence))
throw ParseError("Input sequence must not start with timeout");
if (!m_is_input && state == KeyState::Not)
if (!m_is_input && is_not)
throw ParseError("Ouput sequence must not contain a not-timeout");

// in output expressions always use state Down
// in input expressions use Up or Down depending on what should cancel a timeout:
const auto state = (!m_is_input ? KeyState::Down :
(is_not ? (cancel_on_up ? KeyState::NotTimeout_cancel_on_up_down :
KeyState::NotTimeout_cancel_on_down) :
(cancel_on_up ? KeyState::Timeout_cancel_on_up_down :
KeyState::Timeout_cancel_on_down)));

// try to merge with previous timeout
if (!m_sequence.empty() &&
m_sequence.back().key == Key::timeout &&
Expand Down Expand Up @@ -236,7 +236,7 @@ void ParseKeySequence::parse(It it, const It end) {
if (auto timeout = try_read_timeout(&it, end)) {
if (in_together_group)
throw ParseError("Timeout not allowed in group");
add_timeout_event(KeyState::Not, *timeout);
add_timeout_event(*timeout, true, in_modified_group);
continue;
}

Expand Down Expand Up @@ -347,8 +347,7 @@ void ParseKeySequence::parse(It it, const It end) {
else if (auto timeout = try_read_timeout(&it, end)) {
if (in_together_group)
throw ParseError("Timeout not allowed in group");
const auto state = (m_is_input ? KeyState::Up : KeyState::Down);
add_timeout_event(state, *timeout);
add_timeout_event(*timeout, false, in_modified_group);
}
else {
if (!in_together_group ||
Expand All @@ -368,7 +367,7 @@ void ParseKeySequence::parse(It it, const It end) {
throw ParseError("Expected '}'");

if (m_is_input) {
sync_adjacent_to_timeout();
sync_after_not_timeouts();
}
else {
up_any_keys_not_up_yet();
Expand Down
4 changes: 2 additions & 2 deletions src/config/ParseKeySequence.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ class ParseKeySequence {
Key read_key(It* it, const It end);
void add_key_to_sequence(Key key, KeyState state);
void add_key_to_buffer(Key key);
void add_timeout_event(KeyState state, uint16_t timeout);
void add_timeout_event(uint16_t timeout, bool is_not, bool cancel_on_up);
bool add_string_typing(std::string_view string);
bool remove_from_keys_not_up(Key key);
void flush_key_buffer(bool up_immediately);
void up_any_keys_not_up_yet();
void sync_adjacent_to_timeout();
void sync_after_not_timeouts();
bool all_pressed_at_once() const;
void remove_any_up_from_end();

Expand Down
20 changes: 20 additions & 0 deletions src/runtime/KeyEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ enum class KeyState : uint16_t {
DownAsync, // only in input expression
OutputOnRelease, // only in output expression
DownMatched, // only in sequence

// only in input timeout events (mostly renaming standard states)
NotTimeout_cancel_on_up_down,
NotTimeout_cancel_on_down = Not,
Timeout_cancel_on_up_down = Up,
Timeout_cancel_on_down = Down,
};

struct KeyEvent {
Expand Down Expand Up @@ -50,6 +56,20 @@ struct KeyEvent {
}
};

inline bool is_not_timeout(KeyState state) {
return (state == KeyState::NotTimeout_cancel_on_down ||
state == KeyState::NotTimeout_cancel_on_up_down);
}

inline bool cancel_timeout_on_up(KeyState state) {
return (state == KeyState::NotTimeout_cancel_on_up_down ||
state == KeyState::Timeout_cancel_on_up_down);
}

inline bool is_not_timeout(const KeyEvent& event) {
return (event.key == Key::timeout && is_not_timeout(event.state));
}

class KeySequence : public std::vector<KeyEvent> {
public:
KeySequence() = default;
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/MatchKeySequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace {
// not commutative, first parameter needs to be input sequence
bool timeout_unifiable(const KeyEvent& se, const KeyEvent& ee) {
const auto time_reached = (se.timeout >= ee.timeout);
const auto is_not = (se.state == KeyState::Not || ee.state == KeyState::Not);
const auto is_not = (is_not_timeout(se.state) || is_not_timeout(ee.state));
return (is_not ? !time_reached : time_reached);
}

Expand Down
39 changes: 22 additions & 17 deletions src/runtime/Stage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ const KeySequence* Stage::find_output(const Context& context, int output_index)
}

auto Stage::match_input(ConstKeySequenceRange sequence,
int device_index, bool accept_might_match) -> MatchInputResult {
int device_index, bool accept_might_match, bool is_key_up_event) -> MatchInputResult {
for (auto context_index : m_active_contexts) {
const auto& context = m_contexts[context_index];
if (!device_matches_filter(context, device_index))
Expand All @@ -219,19 +219,21 @@ auto Stage::match_input(ConstKeySequenceRange sequence,
if (accept_might_match && result == MatchResult::might_match) {

if (input_timeout_event.key == Key::timeout) {
// next apply_input should reply timeout Up event
m_output_buffer.emplace_back(Key::timeout,
KeyState::Up, +input_timeout_event.timeout);
// request client to inject timeout event
m_output_buffer.push_back(input_timeout_event);

// track timeout - use last key Down as trigger
if (auto trigger = find_last_down_event(sequence))
if (auto trigger = find_last_down_event(sequence)) {
if (!m_current_timeout ||
m_current_timeout->state != input_timeout_event.state ||
m_current_timeout->trigger != trigger->key)
m_current_timeout = {
input_timeout_event,
trigger->key
};
*m_current_timeout != input_timeout_event ||
m_current_timeout->trigger != trigger->key) {
m_current_timeout = { input_timeout_event, trigger->key };
}
else if (is_key_up_event) {
// timeout did not change, undo adding to output buffer
m_output_buffer.pop_back();
}
}
}
return { MatchResult::might_match, nullptr, context_index };
}
Expand Down Expand Up @@ -263,7 +265,7 @@ void Stage::apply_input(const KeyEvent event, int device_index) {
}

// suppress short timeout after not-timeout was exceeded
if (m_current_timeout && m_current_timeout->state == KeyState::Not) {
if (m_current_timeout && is_not_timeout(m_current_timeout->state)) {
if (event.key == Key::timeout) {
if (m_current_timeout->timeout == event.timeout) {
m_current_timeout->not_exceeded = true;
Expand Down Expand Up @@ -309,10 +311,12 @@ void Stage::apply_input(const KeyEvent event, int device_index) {
for (auto& output : m_output_down)
output.suppressed = false;

const auto is_key_up_event = (event.state == KeyState::Up && event.key != Key::timeout);
while (has_non_optional(m_sequence)) {
// find first mapping which matches or might match sequence
auto sequence = ConstKeySequenceRange(m_sequence);
auto [result, output, context_index] = match_input(sequence, device_index, true);
auto [result, output, context_index] = match_input(
sequence, device_index, true, is_key_up_event);

// virtual key events need to match directly or never
if (is_virtual_key(event.key) &&
Expand All @@ -336,7 +340,8 @@ void Stage::apply_input(const KeyEvent event, int device_index) {
if (!has_unmatched_down(sequence))
break;

std::tie(result, output, context_index) = match_input(sequence, device_index, false);
std::tie(result, output, context_index) =
match_input(sequence, device_index, false, is_key_up_event);
if (result == MatchResult::match)
break;
}
Expand All @@ -345,7 +350,7 @@ void Stage::apply_input(const KeyEvent event, int device_index) {

// when a timeout matched once, prevent following timeout
// cancellation from matching another input
if (m_current_timeout && m_current_timeout->state == KeyState::Up) {
if (m_current_timeout && !is_not_timeout(m_current_timeout->state)) {
if (event.key == Key::timeout) {
if (result == MatchResult::match) {
if (!m_current_timeout->matched_output) {
Expand All @@ -362,7 +367,7 @@ void Stage::apply_input(const KeyEvent event, int device_index) {
}

// prevent match after not-timeout did not match once
if (m_current_timeout && m_current_timeout->state == KeyState::Not) {
if (m_current_timeout && is_not_timeout(m_current_timeout->state)) {
if (result == MatchResult::no_match)
m_current_timeout->not_exceeded = true;
}
Expand Down Expand Up @@ -531,7 +536,7 @@ void Stage::update_output(const KeyEvent& event, Key trigger) {
it->pressed_twice = false;
}
}
m_output_buffer.emplace_back(event.key, KeyState::Down, event.timeout);
m_output_buffer.push_back(event);
break;
}

Expand Down
2 changes: 1 addition & 1 deletion src/runtime/Stage.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class Stage {
const KeySequence* find_output(const Context& context, int output_index) const;
bool device_matches_filter(const Context& context, int device_index) const;
MatchInputResult match_input(ConstKeySequenceRange sequence, int device_index,
bool accept_might_match);
bool accept_might_match, bool is_key_up_event);
void apply_input(KeyEvent event, int device_index);
void release_triggered(Key key);
void forward_from_sequence();
Expand Down
4 changes: 0 additions & 4 deletions src/runtime/Timeout.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ KeyEvent make_input_timeout_event(const std::chrono::duration<R, P>& duration) {
return KeyEvent(Key::timeout, KeyState::Up, duration_to_timeout(duration));
}

inline bool is_input_timeout_event(const KeyEvent& event) {
return (event.key == Key::timeout && event.state == KeyState::Up);
}

inline uint16_t sum_timeouts(uint16_t a, uint16_t b) {
const auto max = (1 << KeyEvent::timeout_bits) - 1;
return static_cast<uint16_t>(std::min(
Expand Down
10 changes: 7 additions & 3 deletions src/server/unix/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace {
std::vector<KeyEvent> g_send_buffer;
std::optional<Clock::time_point> g_flush_scheduled_at;
std::optional<Clock::time_point> g_input_timeout_start;
bool g_cancel_timeout_on_up;
std::chrono::milliseconds g_input_timeout;
std::vector<Key> g_virtual_keys_down;
KeyEvent g_last_key_event;
Expand Down Expand Up @@ -144,7 +145,8 @@ namespace {
return;

// cancel timeout when key is released/another is pressed
if (g_input_timeout_start) {
if (g_input_timeout_start &&
(input.state == KeyState::Down || g_cancel_timeout_on_up)) {
const auto time_since_timeout_start =
(Clock::now() - *g_input_timeout_start);
g_input_timeout_start.reset();
Expand All @@ -160,9 +162,11 @@ namespace {
verbose_debug_io(input, output, true);

// waiting for input timeout
if (!output.empty() && is_input_timeout_event(output.back())) {
if (!output.empty() && output.back().key == Key::timeout) {
const auto& request = output.back();
g_cancel_timeout_on_up = cancel_timeout_on_up(request.state);
g_input_timeout_start = Clock::now();
g_input_timeout = timeout_to_milliseconds(output.back().timeout);
g_input_timeout = timeout_to_milliseconds(request.timeout);
output.pop_back();
}

Expand Down
17 changes: 12 additions & 5 deletions src/server/windows/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace {
KeyEvent g_last_key_event;
std::chrono::milliseconds g_timeout_ms;
std::optional<Clock::time_point> g_timeout_start_at;
bool g_cancel_timeout_on_up;
std::vector<Key> g_virtual_keys_down;

void apply_updates();
Expand Down Expand Up @@ -217,9 +218,10 @@ namespace {
g_sending_key = false;
}

void schedule_timeout(std::chrono::milliseconds timeout) {
void schedule_timeout(std::chrono::milliseconds timeout, bool cancel_on_up) {
g_timeout_ms = timeout;
g_timeout_start_at = Clock::now();
g_cancel_timeout_on_up = cancel_on_up;
SetTimer(g_window, TIMER_TIMEOUT,
static_cast<UINT>(timeout.count()), nullptr);
}
Expand All @@ -243,7 +245,8 @@ namespace {
}

auto cancelled_timeout = false;
if (g_timeout_start_at) {
if (g_timeout_start_at &&
(input.state == KeyState::Down || g_cancel_timeout_on_up)) {
// cancel current time out, inject event with elapsed time
const auto time_since_timeout_start =
(Clock::now() - *g_timeout_start_at);
Expand All @@ -260,7 +263,8 @@ namespace {
input.key = Key::Pause;
translated_numlock_to_pause = true;
}
g_last_key_event = input;
if (input.key != Key::timeout)
g_last_key_event = input;

apply_updates();

Expand All @@ -274,8 +278,11 @@ namespace {
}

// waiting for input timeout
if (!output.empty() && is_input_timeout_event(output.back())) {
schedule_timeout(timeout_to_milliseconds(output.back().timeout));
if (!output.empty() && output.back().key == Key::timeout) {
const auto& request = output.back();
schedule_timeout(
timeout_to_milliseconds(request.timeout),
cancel_timeout_on_up(request.state));
output.pop_back();
}

Expand Down
35 changes: 21 additions & 14 deletions src/test/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ void error(const char* format, ...) { }
void verbose(const char* format, ...) { }

std::ostream& operator<<(std::ostream& os, const KeyEvent& event) {
if (event.key != Key::timeout)
switch (event.state) {
case KeyState::Up: os << '-'; break;
case KeyState::Down: os << '+'; break;
case KeyState::UpAsync: os << '~'; break;
case KeyState::DownAsync: os << '*'; break;
case KeyState::Not: os << '!'; break;
case KeyState::DownMatched: os << '#'; break;
case KeyState::OutputOnRelease: os << '^'; break;
}
switch (event.state) {
case KeyState::Up: os << '-'; break;
case KeyState::Down: os << '+'; break;
case KeyState::UpAsync: os << '~'; break;
case KeyState::DownAsync: os << '*'; break;
case KeyState::Not: os << '!'; break;
case KeyState::DownMatched: os << '#'; break;
case KeyState::OutputOnRelease: os << '^'; break;
case KeyState::NotTimeout_cancel_on_up_down: os << '?'; break;
}

if (is_virtual_key(event.key)) {
os << "Virtual" << (*event.key - *Key::first_virtual);
Expand Down Expand Up @@ -129,13 +129,20 @@ Stage create_stage(const char* string) {
return stage;
}

KeyEvent make_timeout_ms(int timeout_ms) {
return KeyEvent(Key::timeout, KeyState::Up,
KeyEvent reply_timeout_ms(int timeout_ms) {
return KeyEvent(Key::timeout, KeyState::Up,
duration_to_timeout(std::chrono::milliseconds(timeout_ms)));
}

KeyEvent make_timeout_ms(int timeout_ms, bool cancel_on_up) {
return KeyEvent(Key::timeout, (cancel_on_up ?
KeyState::Timeout_cancel_on_up_down : KeyState::Timeout_cancel_on_down),
duration_to_timeout(std::chrono::milliseconds(timeout_ms)));
}

KeyEvent make_not_timeout_ms(int timeout_ms) {
return KeyEvent(Key::timeout, KeyState::Not,
KeyEvent make_not_timeout_ms(int timeout_ms, bool cancel_on_up) {
return KeyEvent(Key::timeout, (cancel_on_up ?
KeyState::NotTimeout_cancel_on_up_down : KeyState::NotTimeout_cancel_on_down),
duration_to_timeout(std::chrono::milliseconds(timeout_ms)));
}

Expand Down
Loading

0 comments on commit ab6d011

Please sign in to comment.