Skip to content

Commit 7e8e7be

Browse files
authored
Merge pull request #9353 from keymanapp/feat/windows/5822/notify-core-on-kbd-activation
feat(windows): Use Keyboard Activated event API call to turn off caps lock (if required) 🚏
2 parents 0d95793 + e79cfc9 commit 7e8e7be

File tree

10 files changed

+207
-17
lines changed

10 files changed

+207
-17
lines changed

core/src/km_kbp_processevent_api.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ km_kbp_event(
2828
switch(event) {
2929
case KM_KBP_EVENT_KEYBOARD_ACTIVATED:
3030
assert(data == nullptr);
31-
if(data == nullptr) {
31+
if(data != nullptr) {
3232
return KM_KBP_STATUS_INVALID_ARGUMENT;
3333
}
3434
break;

core/src/kmx/kmx_processevent.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ class KMX_ProcessEvent {
8686
/* Caps Lock and modifier management */
8787

8888
KMX_BOOL IsCapsLockOn(KMX_DWORD modifiers);
89-
void SetCapsLock(KMX_DWORD &modifiers, KMX_BOOL capsLockOn, KMX_BOOL force = FALSE);
9089
void ResetCapsLock(KMX_DWORD &modifiers, KMX_BOOL isKeyDown);
9190
KMX_BOOL KeyCapsLockPress(KMX_DWORD &modifiers, KMX_BOOL isKeyDown);
9291
void KeyShiftPress(KMX_DWORD &modifiers, KMX_BOOL isKeyDown);
@@ -107,6 +106,7 @@ class KMX_ProcessEvent {
107106
KMX_Environment *GetEnvironment();
108107
KMX_Environment const *GetEnvironment() const;
109108
INTKEYBOARDINFO const *GetKeyboard() const;
109+
void SetCapsLock(KMX_DWORD &modifiers, KMX_BOOL capsLockOn, KMX_BOOL force = FALSE);
110110

111111
// Utility function
112112
public:

core/src/kmx/kmx_processor.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,30 @@ kmx_processor::update_option(
103103
return option(scope, key, value);
104104
}
105105

106+
km_kbp_status
107+
kmx_processor::external_event(
108+
km_kbp_state* state,
109+
uint32_t event,
110+
void* _kmn_unused(data)
111+
) {
112+
113+
switch (event) {
114+
case KM_KBP_EVENT_KEYBOARD_ACTIVATED:
115+
// reset any current actions in the queue as a new keyboard
116+
// has been activated
117+
_kmx.GetActions()->ResetQueue();
118+
state->actions().clear();
119+
if (_kmx.GetKeyboard()->Keyboard->dwFlags & KF_CAPSALWAYSOFF) {
120+
KMX_DWORD dummy_modifiers = 0;
121+
_kmx.SetCapsLock(dummy_modifiers, FALSE, TRUE);
122+
}
123+
break;
124+
default:
125+
return KM_KBP_STATUS_INVALID_ARGUMENT;
126+
}
127+
return internal_process_queued_actions(state);
128+
}
129+
106130
bool
107131
kmx_processor::queue_action(
108132
km_kbp_state * state,

core/src/kmx/kmx_processor.hpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,14 @@ namespace kbp
5959
km_kbp_status
6060
process_queued_actions(
6161
km_kbp_state *state
62-
) override;
62+
) override;
63+
64+
km_kbp_status
65+
external_event(
66+
km_kbp_state* state,
67+
uint32_t event,
68+
void* data
69+
) override;
6370

6471
bool
6572
queue_action(

core/tests/unit/kmnkbd/action_items.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const char *action_item_types[] = {
2323
"KM_KBP_IT_PERSIST_OPT", // = 5, // The indicated option needs to be stored.
2424
"KM_KBP_IT_EMIT_KEYSTROKE", // = 6, // Emit the current keystroke to the application
2525
"KM_KBP_IT_INVALIDATE_CONTEXT", // = 7,
26+
"KM_KBP_IT_CAPSLOCK",// = 8, Enable or disable capsLock
2627
};
2728

2829
void print_action_item(const char *title, km_kbp_action_item const & item) {
@@ -50,6 +51,13 @@ void print_action_item(const char *title, km_kbp_action_item const & item) {
5051
<< " value: " << item.option->value << std::endl
5152
<< " scope: " << item.option->scope << std::endl;
5253
break;
54+
case KM_KBP_IT_CAPSLOCK:
55+
std::cout << " caps lock: " <<
56+
(item.capsLock == 0 ? "off" :
57+
item.capsLock == 1 ? "on" :
58+
"invalid value") << " (" <<
59+
item.capsLock << ")" << std::endl;
60+
break;
5361
}
5462
}
5563

@@ -68,6 +76,7 @@ bool operator==(
6876
lhs.backspace.expected_value == rhs.backspace.expected_value; break;
6977
case KM_KBP_IT_PERSIST_OPT: result = *lhs.option == *rhs.option; break;
7078
case KM_KBP_IT_EMIT_KEYSTROKE: break;
79+
case KM_KBP_IT_CAPSLOCK: result = lhs.capsLock == rhs.capsLock; break;
7180
case KM_KBP_IT_INVALIDATE_CONTEXT: break;
7281
default: std::cout << "unexpected type" << std::endl; return false; //
7382
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
Copyright: © 2023 SIL International.
3+
Description: Tests for external event processing for the kmx processor
4+
Create Date: 28 Jul 2023
5+
Authors: Ross Cruickshank (RC)
6+
7+
*/
8+
9+
#include <kmx/kmx_processevent.h>
10+
11+
#include "path.hpp"
12+
#include "state.hpp"
13+
#include "../kmnkbd/action_items.hpp"
14+
#include <test_assert.h>
15+
#include <test_color.h>
16+
#include "../emscripten_filesystem.h"
17+
18+
#include <map>
19+
#include <iostream>
20+
#include <sstream>
21+
22+
/**
23+
* This test will test the infrastructure around the external event processing
24+
* The functions tested are:
25+
* - km_kbp_event with the event KM_KBP_EVENT_KEYBOARD_ACTIVATED
26+
*/
27+
28+
using namespace km::kbp::kmx;
29+
30+
km_kbp_option_item test_env_opts[] =
31+
{
32+
KM_KBP_OPTIONS_END
33+
};
34+
35+
int error_args() {
36+
std::cerr << "kmx: Not enough arguments." << std::endl;
37+
return 1;
38+
}
39+
40+
void test_external_event(const km::kbp::path &source_file){
41+
42+
km_kbp_keyboard * test_kb = nullptr;
43+
km_kbp_state * test_state = nullptr;
44+
45+
km::kbp::path full_path = source_file;
46+
47+
try_status(km_kbp_keyboard_load(full_path.native().c_str(), &test_kb));
48+
49+
// Setup state, environment
50+
try_status(km_kbp_state_create(test_kb, test_env_opts, &test_state));
51+
52+
// Set up external events to test the processing.
53+
uint32_t event = KM_KBP_EVENT_KEYBOARD_ACTIVATED;
54+
// For this particular test we want to have the capslock state as on.
55+
// then after the km_kbp_event the caps lock should be off as the load
56+
// keyboard is a caps always off keyboard
57+
58+
try_status(km_kbp_event(test_state, event, nullptr));
59+
// The action to turn capslock off must be in the actions list.
60+
assert(action_items(test_state, {{KM_KBP_IT_CAPSLOCK, {0,}, {0}}, {KM_KBP_IT_END}}));
61+
62+
km_kbp_state_dispose(test_state);
63+
km_kbp_keyboard_dispose(test_kb);
64+
65+
}
66+
67+
int main(int argc, char *argv []) {
68+
int first_arg = 1;
69+
70+
if (argc < 2) {
71+
return error_args();
72+
}
73+
74+
auto arg_color = std::string(argv[1]) == "--color";
75+
if(arg_color) {
76+
first_arg++;
77+
if(argc < 3) {
78+
return error_args();
79+
}
80+
}
81+
console_color::enabled = console_color::isaterminal() || arg_color;
82+
km::kbp::kmx::g_debug_ToConsole = TRUE;
83+
84+
#ifdef __EMSCRIPTEN__
85+
test_external_event(get_wasm_file_path(argv[first_arg]));
86+
#else
87+
test_external_event(argv[first_arg]);
88+
#endif
89+
90+
return 0;
91+
}

core/tests/unit/kmx/meson.build

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,21 @@ kbd_log = custom_target(test_kbd + '.kmx'.underscorify(),
208208
command: kmc_cmd + ['build', '--debug', '--no-compiler-version', '@INPUT@', '--out-file', kbd_obj]
209209
)
210210
test('imx_list', imx_e, depends: kbd_log, args: [kbd_obj] )
211+
212+
external_e = executable('ext_event', ['kmx_external_event.cpp', '../emscripten_filesystem.cpp'],
213+
cpp_args: defns + warns,
214+
include_directories: [inc, libsrc],
215+
link_args: links + tests_flags,
216+
objects: lib.extract_all_objects(recursive: false))
217+
218+
test_kbd = 'k_033___caps_always_off'
219+
220+
221+
kbd_src = common_test_keyboards_baseline + '/' + test_kbd + '.kmn'
222+
kbd_obj = join_paths(meson.current_build_dir(), test_kbd) + '.kmx'
223+
kbd_log = custom_target(test_kbd + '.kmx'.underscorify(),
224+
output: test_kbd + '.log',
225+
input: kbd_src,
226+
command: kmc_cmd + ['build', '--debug', '--no-compiler-version', '@INPUT@', '--out-file', kbd_obj]
227+
)
228+
test('ext_event', external_e, depends: kbd_log, args: [kbd_obj] )

windows/src/engine/keyman32/kmprocessactions.cpp

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ static BOOL processInvalidateContext(
8787
}
8888

8989
static BOOL
90-
processCapsLock(const km_kbp_action_item* actionItem, BOOL isUp, BOOL Updateable) {
90+
processCapsLock(const km_kbp_action_item* actionItem, BOOL isUp, BOOL Updateable, BOOL externalEvent) {
9191

9292
// We only want to process the Caps Lock key event once --
9393
// in the first pass (!Updateable).
@@ -114,7 +114,8 @@ processCapsLock(const km_kbp_action_item* actionItem, BOOL isUp, BOOL Updateable
114114
}
115115
else {
116116
// This case would occur for the keyboard system store setting `store(&ShiftFreesCaps) '1'`
117-
if (!isUp && IsCapsLockOn()) {
117+
// OR selecting a keyboard with CAPs always off rule
118+
if ((!isUp && IsCapsLockOn()) || (externalEvent && IsCapsLockOn())) {
118119
SendDebugMessageFormat(0, sdmGlobal, 0, "processCapsLock: TURN CAPS OFF: FIsUp=%d CapsState=%d", isUp, IsCapsLockOn());
119120
keybd_event(VK_CAPITAL, SCAN_FLAG_KEYMAN_KEY_EVENT, 0, 0);
120121
keybd_event(VK_CAPITAL, SCAN_FLAG_KEYMAN_KEY_EVENT, KEYEVENTF_KEYUP, 0);
@@ -160,7 +161,7 @@ BOOL ProcessActions(BOOL* emitKeyStroke)
160161
continueProcessingActions = processInvalidateContext(_td->app, _td->lpActiveKeyboard->lpCoreKeyboardState);
161162
break;
162163
case KM_KBP_IT_CAPSLOCK:
163-
continueProcessingActions = processCapsLock(act, !_td->state.isDown, _td->TIPFUpdateable);
164+
continueProcessingActions = processCapsLock(act, !_td->state.isDown, _td->TIPFUpdateable, FALSE);
164165
break;
165166
case KM_KBP_IT_END:
166167
// fallthrough
@@ -198,7 +199,33 @@ ProcessActionsNonUpdatableParse(BOOL* emitKeyStroke) {
198199
_td->CoreProcessEventRun = FALSE; // If we emit the key stroke on this parse we don't need the second parse
199200
break;
200201
case KM_KBP_IT_CAPSLOCK:
201-
continueProcessingActions = processCapsLock(act, !_td->state.isDown, _td->TIPFUpdateable);
202+
continueProcessingActions = processCapsLock(act, !_td->state.isDown, _td->TIPFUpdateable, FALSE);
203+
break;
204+
case KM_KBP_IT_INVALIDATE_CONTEXT:
205+
continueProcessingActions = processInvalidateContext(_td->app, _td->lpActiveKeyboard->lpCoreKeyboardState);
206+
break;
207+
}
208+
if (!continueProcessingActions) {
209+
return FALSE;
210+
}
211+
}
212+
return TRUE;
213+
}
214+
215+
BOOL
216+
ProcessActionsExternalEvent() {
217+
PKEYMAN64THREADDATA _td = ThreadGlobals();
218+
if (!_td) {
219+
return FALSE;
220+
}
221+
// Currently only a subset of actions are handled.
222+
// Other actions will be added when needed.
223+
BOOL continueProcessingActions = TRUE;
224+
for (auto act = km_kbp_state_action_items(_td->lpActiveKeyboard->lpCoreKeyboardState, nullptr); act->type != KM_KBP_IT_END;
225+
act++) {
226+
switch (act->type) {
227+
case KM_KBP_IT_CAPSLOCK:
228+
continueProcessingActions = processCapsLock(act, !_td->state.isDown, FALSE, TRUE);
202229
break;
203230
case KM_KBP_IT_INVALIDATE_CONTEXT:
204231
continueProcessingActions = processInvalidateContext(_td->app, _td->lpActiveKeyboard->lpCoreKeyboardState);

windows/src/engine/keyman32/kmprocessactions.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,15 @@ BOOL ProcessActions(BOOL* emitKeyStroke);
2626
*/
2727
BOOL ProcessActionsNonUpdatableParse(BOOL* emitKeyStroke);
2828

29+
/**
30+
* This function processes the actions queued in the core processor in
31+
* the external event case. That is actions not caused by a keystroke but from selecting a
32+
* different keyboard for example.
33+
* Currently only KM_KBP_IT_CAPSLOCK and KM_KBP_IT_INVALIDATE_CONTEXT are processed.
34+
*
35+
* @return BOOL TRUE if actions were successfully processed
36+
*/
37+
BOOL ProcessActionsExternalEvent();
38+
39+
2940
#endif

windows/src/engine/keyman32/selectkeyboard.cpp

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ BOOL SelectKeyboard(DWORD KeymanID)
6161
PKEYMAN64THREADDATA _td = ThreadGlobals();
6262
if (!_td) return FALSE;
6363

64-
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "ENTER SelectKeyboardCore-------------------------------------------");
65-
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "ENTER SelectKeyboardCore: Current:(HKL=%x KeymanID=%x %s) New:(ID=%x)", //lpActiveKeyboard=%s ActiveKeymanID: %x sk: %x KeymanID: %d",
64+
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "ENTER SelectKeyboard-------------------------------------------");
65+
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "ENTER SelectKeyboard: Current:(HKL=%x KeymanID=%x %s) New:(ID=%x)", //lpActiveKeyboard=%s ActiveKeymanID: %x sk: %x KeymanID: %d",
6666
GetKeyboardLayout(0),
6767
_td->ActiveKeymanID,
6868
_td->lpActiveKeyboard == NULL ? "NULL" : _td->lpActiveKeyboard->Name,
@@ -86,42 +86,45 @@ BOOL SelectKeyboard(DWORD KeymanID)
8686
{
8787
if (!_td->lpKeyboards[i].lpCoreKeyboard && !LoadlpKeyboard(i))
8888
{
89-
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "SelectKeyboardCore: Unable to load");
89+
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "SelectKeyboard: Unable to load");
9090
return TRUE;
9191
}
9292

9393
_td->lpActiveKeyboard = &_td->lpKeyboards[i];
9494
_td->ActiveKeymanID = _td->lpActiveKeyboard->KeymanID;
9595

96-
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "SelectKeyboardCore: NewKeymanID: %x", _td->ActiveKeymanID);
96+
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "SelectKeyboard: NewKeymanID: %x", _td->ActiveKeymanID);
9797

9898
if (_td->app) _td->app->ResetContext();
99-
100-
// TODO: #5822 tell the core with km_kbp_event so it can reset the capslock state
101-
10299
SelectApplicationIntegration(); // I4287
103100
if (_td->app && !_td->app->IsWindowHandled(hwnd)) _td->app->HandleWindow(hwnd);
104101
_td->state.windowunicode = !_td->app || _td->app->IsUnicode();
105102

106103
ActivateDLLs(_td->lpActiveKeyboard);
107104

105+
if (KM_KBP_STATUS_OK !=
106+
km_kbp_event(_td->lpActiveKeyboard->lpCoreKeyboardState, KM_KBP_EVENT_KEYBOARD_ACTIVATED, nullptr)) {
107+
SendDebugMessageFormat(0, sdmGlobal, 0, "km_kbp_event Failed Result: %d ", FALSE);
108+
} else {
109+
ProcessActionsExternalEvent();
110+
}
108111
return TRUE;
109112
}
110113
if (IsFocusedThread())
111114
{
112-
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "SelectKeyboardCore: Keyboard Not Found");
115+
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "SelectKeyboard: Keyboard Not Found");
113116
}
114117
}
115118
}
116119

117120
__finally
118121
{
119-
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "EXIT SelectKeyboardCore: Current:(HKL=%x KeymanID=%x %s) New:(ID=%x)", //lpActiveKeyboard=%s ActiveKeymanID: %x sk: %x KeymanID: %d",
122+
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "EXIT SelectKeyboard: Current:(HKL=%x KeymanID=%x %s) New:(ID=%x)", //lpActiveKeyboard=%s ActiveKeymanID: %x sk: %x KeymanID: %d",
120123
GetKeyboardLayout(0),
121124
_td->ActiveKeymanID,
122125
_td->lpActiveKeyboard == NULL ? "NULL" : _td->lpActiveKeyboard->Name,
123126
KeymanID);
124-
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "EXIT SelectKeyboardCore-------------------------------------------");
127+
SendDebugMessageFormat(hwnd, sdmGlobal, 0, "EXIT SelectKeyboard-------------------------------------------");
125128
}
126129
return TRUE;
127130
}

0 commit comments

Comments
 (0)