spiegel-keyman/windows/src/engine/keyman32/kmprocessactions.cpp
rc-swag 02db51c9b6
Some checks failed
Keyman Build Summary / Summarize build status checks (push) Has been cancelled
fix(windows): address review comments
2026-04-28 23:16:17 +10:00

162 lines
5.2 KiB
C++

/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Description: Used for apply core processor actions to
* Keyman for Windows engine.
*/
#include "pch.h"
/**
* Adds characters to the queue to be inserted into the application
* @param app A pointer to the AITIP instance.
* @param outputString A null-terminated string of characters to insert into the application
*/
static void
processUnicodeChar(AITIP* app, const km_core_usv* outputString) {
while (*outputString) {
if (Uni_IsSMP(*outputString)) {
app->QueueAction(QIT_CHAR, (Uni_UTF32ToSurrogate1(*outputString)));
app->QueueAction(QIT_CHAR, (Uni_UTF32ToSurrogate2(*outputString)));
} else {
app->QueueAction(QIT_CHAR, *outputString);
}
outputString++;
}
}
static void processAlert(AITIP* app) {
app->QueueAction(QIT_BELL, 0);
}
static void
processBack(
AITIP* app,
const unsigned int code_points_to_delete,
const km_core_usv* delete_context,
BOOL* emitKeystroke,
WORD vkey) {
if (app->IsLegacy()) {
for (unsigned int i = 0; i < code_points_to_delete; i++) {
app->QueueAction(QIT_BACK, BK_DEFAULT);
}
}
else {
// If there is a selection emit the key (backspace)
// allowing the application handle clearing selected text in the correct manner.
if (app->IsTextSelected() && (vkey == VK_BACK)) {
*emitKeystroke = TRUE;
return;
}
km_core_usv const* delete_context_ptr = delete_context;
while (*delete_context_ptr) {
delete_context_ptr++;
}
delete_context_ptr--;
for (; delete_context_ptr >= delete_context; delete_context_ptr--) {
if (Uni_IsSMP(*delete_context_ptr)) {
app->QueueAction(QIT_BACK, BK_DEFAULT | BK_SURROGATE);
}
else {
app->QueueAction(QIT_BACK, BK_DEFAULT);
}
}
}
}
static void
processPersistOpt(km_core_actions const* actions, LPINTKEYBOARDINFO activeKeyboard
) {
for (auto option = actions->persist_options; option->key; option++) {
SendDebugMessageFormat("Saving option to registry for keyboard [%s].", activeKeyboard->Name);
SaveKeyboardOptionCoretoRegistry(
activeKeyboard, reinterpret_cast<LPCWSTR>(option->key), reinterpret_cast<LPCWSTR>(option->value));
}
}
static void processCapsLock(const km_core_caps_state caps_state_change, BOOL isUp, BOOL Updateable, BOOL externalEvent) {
BOOL isCapsOn = IsCapsLockOn();
// This debug message is useful for understanding the sequence of events around caps lock changes
//SendDebugMessageFormat("ACTION CAPS STATE:%d FIsUp=%d Updateable=%d ExternalEvent=%d CapsState=%d", caps_state_change, isUp, Updateable,
// externalEvent, isCapsOn);
// We only want to process the Caps Lock key event once;
// it has to be when updateble=1 as TSF does not consistently
// have updateable=0 events.
if (!Updateable || caps_state_change == KM_CORE_CAPS_UNCHANGED) {
return;
}
// Turn three state value into a boolean for whether capslock should be on or off,
// we only want to process the key event if the state is changing.
BOOL required_caps_state = (caps_state_change == KM_CORE_CAPS_ON);
if (isCapsOn != required_caps_state) {
SendDebugMessageFormat(
"Simulate CapsLock %s: FIsUp=%d CurrentCapsState=%d ExternalEvent=%d",
required_caps_state ? "ON" : "OFF", isUp, isCapsOn, externalEvent);
keybd_event(VK_CAPITAL, SCAN_FLAG_KEYMAN_KEY_EVENT, 0, 0);
keybd_event(VK_CAPITAL, SCAN_FLAG_KEYMAN_KEY_EVENT, KEYEVENTF_KEYUP, 0);
}
}
BOOL ProcessActions(BOOL* emitKeystroke)
{
PKEYMAN64THREADDATA _td = ThreadGlobals();
if (!_td) return FALSE;
km_core_actions const* core_actions = km_core_state_get_actions(_td->lpActiveKeyboard->lpCoreKeyboardState);
_td->CoreProcessEventRun = FALSE;
processBack(_td->app, core_actions->code_points_to_delete, core_actions->deleted_context, emitKeystroke, _td->state.vkey);
processUnicodeChar(_td->app, core_actions->output);
if (core_actions->persist_options != NULL) {
processPersistOpt(core_actions, _td->lpActiveKeyboard);
}
if (core_actions->do_alert) {
processAlert(_td->app);
}
if (core_actions->emit_keystroke) {
*emitKeystroke = TRUE;
}
processCapsLock(core_actions->new_caps_lock_state, !_td->state.isDown, _td->TIPFUpdateable, FALSE);
return TRUE;
}
BOOL
ProcessActionsNonUpdatableParse(BOOL* emitKeystroke) {
PKEYMAN64THREADDATA _td = ThreadGlobals();
if (!_td) {
return FALSE;
}
if (_td->TIPFUpdateable) { // ensure only run when not updateable
return FALSE;
}
_td->CoreProcessEventRun = TRUE;
km_core_actions const* core_actions = km_core_state_get_actions(_td->lpActiveKeyboard->lpCoreKeyboardState);
if (core_actions->emit_keystroke) {
*emitKeystroke = TRUE;
SendDebugMessageFormat("EMIT_KEYSTROKE");
_td->CoreProcessEventRun = FALSE; // If we emit the key stroke on this parse we don't need the second parse
}
return TRUE;
}
BOOL
ProcessActionsExternalEvent() {
PKEYMAN64THREADDATA _td = ThreadGlobals();
if (!_td) {
return FALSE;
}
km_core_actions const* core_actions = km_core_state_get_actions(_td->lpActiveKeyboard->lpCoreKeyboardState);
processCapsLock(core_actions->new_caps_lock_state, !_td->state.isDown, TRUE, TRUE);
return TRUE;
}