mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-10 10:55:33 +00:00
400 lines
11 KiB
ObjectPascal
400 lines
11 KiB
ObjectPascal
{
|
|
* Keyman is copyright (C) SIL International. MIT License.
|
|
*
|
|
* Wrapper around Core debug events and actions, interleaved into a single list.
|
|
}
|
|
unit Keyman.System.Debug.DebugEvent;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.Classes,
|
|
System.Generics.Collections,
|
|
System.SysUtils,
|
|
Winapi.Windows,
|
|
|
|
debugkeyboard,
|
|
Keyman.System.KeymanCore,
|
|
Keyman.System.KeymanCoreDebug;
|
|
|
|
const
|
|
MAXSTOREOFFSETS=20;
|
|
|
|
type
|
|
TAIDebugKeyInfo = record
|
|
VirtualKey: UINT;
|
|
Modifiers: DWORD;
|
|
end;
|
|
|
|
PAIDebugKeyInfo = ^TAIDebugKeyInfo;
|
|
|
|
{ DebugEvent structure -- an event has occurred }
|
|
|
|
TDebugEventActionData = class
|
|
ActionType: km_kbp_action_type;
|
|
dwData: Integer;
|
|
Text: WideString;
|
|
end;
|
|
|
|
TDebugEventRuleData = class
|
|
ItemType, Line: Integer;
|
|
Flags: DWord;
|
|
Rule: TKeymanKeyEx;
|
|
Group: TKeymanGroupEx;
|
|
OptionStoreName: string;
|
|
OptionValue: string;
|
|
Key: TAIDebugKeyInfo;
|
|
Context: WideString;
|
|
StoreOffsets: array[0..20] of Word; //TKeymanStoreEx;
|
|
nStores: Integer;
|
|
procedure FillStoreList(event: pkm_core_state_debug_item; KeyboardMemory: PChar);
|
|
end;
|
|
|
|
TDebugEventType = (etAction, etRuleMatch);
|
|
|
|
TDebugEvent = class
|
|
private
|
|
FEventType: TDebugEventType;
|
|
FAction: TDebugEventActionData;
|
|
FRule: TDebugEventRuleData;
|
|
procedure SetEventType(const Value: TDebugEventType);
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
property Action: TDebugEventActionData read FAction;
|
|
property Rule: TDebugEventRuleData read FRule;
|
|
property EventType: TDebugEventType read FEventType write SetEventType;
|
|
end;
|
|
|
|
TDebugEventList = class(TObjectList<TDebugEvent>)
|
|
private
|
|
procedure Action_Char(const character: km_kbp_usv);
|
|
procedure Action_DeleteBack(expected_type: uint8_t;
|
|
expected_value: uintptr_t);
|
|
procedure Action_EmitKeystroke(const key: Word);
|
|
procedure Action_Marker(marker: uintptr_t);
|
|
function AddActionItem(key: Word; action: pkm_core_action_item): Boolean;
|
|
procedure AddDebugItem(
|
|
debug: pkm_core_state_debug_item;
|
|
debugkeyboard: TDebugKeyboard;
|
|
vk: uint16_t;
|
|
modifier_state: uint16_t
|
|
);
|
|
public
|
|
function AddStateItems(
|
|
state: pkm_core_state;
|
|
vk: uint16_t;
|
|
modifier_state: uint16_t;
|
|
debugkeyboard: TDebugKeyboard
|
|
): Boolean; overload;
|
|
|
|
function AddStateItems(
|
|
state: pkm_core_state;
|
|
vk: uint16_t;
|
|
modifier_state: uint16_t
|
|
): Boolean; overload;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
kmxfile,
|
|
Unicode;
|
|
|
|
{ TDebugEvent }
|
|
|
|
constructor TDebugEvent.Create;
|
|
begin
|
|
inherited Create;
|
|
FEventType := etAction;
|
|
FAction := TDebugEventActionData.Create;
|
|
end;
|
|
|
|
destructor TDebugEvent.Destroy;
|
|
begin
|
|
FreeAndNil(FAction);
|
|
FreeAndNil(FRule);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TDebugEvent.SetEventType(const Value: TDebugEventType);
|
|
begin
|
|
if FEventType <> Value then
|
|
begin
|
|
case FEventType of
|
|
etAction: FreeAndNil(FAction);
|
|
etRuleMatch: FreeAndNil(FRule);
|
|
end;
|
|
FEventType := Value;
|
|
case FEventType of
|
|
etAction: FAction := TDebugEventActionData.Create;
|
|
etRuleMatch: FRule := TDebugEventRuleData.Create;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TDebugEventList.AddActionItem(key: Word; action: pkm_core_action_item): Boolean;
|
|
begin
|
|
Result := True;
|
|
case action._type of
|
|
KM_KBP_IT_CHAR: Action_Char(action.character);
|
|
KM_KBP_IT_MARKER: Action_Marker(action.marker); // Correlates to kmn's "deadkey" markers.
|
|
KM_KBP_IT_ALERT: ; // TODO: The keyboard has triggered a alert/beep/bell.
|
|
KM_KBP_IT_BACK: Action_DeleteBack(action.backspace.expected_type, action.backspace.expected_value);
|
|
KM_KBP_IT_PERSIST_OPT: ; // TODO: The indicated option needs to be stored.
|
|
KM_KBP_IT_EMIT_KEYSTROKE: Action_EmitKeystroke(key);
|
|
KM_KBP_IT_CAPSLOCK: ; // TODO: Caps lock state needs to be updated
|
|
KM_KBP_IT_INVALIDATE_CONTEXT: ;
|
|
else Assert(False, 'Action type '+IntToStr(Ord(action._type))+' is unexpected.');
|
|
end;
|
|
end;
|
|
|
|
procedure TDebugEventList.Action_EmitKeystroke(const key: Word);
|
|
var
|
|
event: TDebugEvent;
|
|
begin
|
|
case key of
|
|
VK_TAB: Action_Char(9); // Emit a tab character
|
|
VK_RETURN: Action_Char(13); // New line character
|
|
VK_BACK: Action_DeleteBack(Ord(KM_KBP_BT_UNKNOWN), 0);
|
|
else
|
|
begin
|
|
event := TDebugEvent.Create;
|
|
event.EventType := etAction;
|
|
event.Action.ActionType := KM_KBP_IT_EMIT_KEYSTROKE;
|
|
event.Action.dwData := key;
|
|
Add(event);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
///
|
|
/// Insert a UTF-32 character at the insertion point.
|
|
/// TODO: define behaviour around selection
|
|
///
|
|
procedure TDebugEventList.Action_Char(const character: km_kbp_usv);
|
|
var
|
|
event: TDebugEvent;
|
|
begin
|
|
event := TDebugEvent.Create;
|
|
event.EventType := etAction;
|
|
event.Action.ActionType := KM_KBP_IT_CHAR;
|
|
event.Action.Text := Uni_UTF32CharToUTF16(character);
|
|
Add(event);
|
|
end;
|
|
|
|
///
|
|
/// Delete the codepoint preceding the insertion point.
|
|
/// TODO: define behaviour around selection
|
|
///
|
|
procedure TDebugEventList.Action_DeleteBack(
|
|
expected_type: uint8_t; /// one of KM_KBP_BT_CHAR, KM_KBP_BT_MARKER, KM_KBP_BT_UNKNOWN
|
|
expected_value: uintptr_t /// used mainly in unit tests
|
|
);
|
|
var
|
|
event: TDebugEvent;
|
|
begin
|
|
event := TDebugEvent.Create;
|
|
event.EventType := etAction;
|
|
event.Action.ActionType := KM_KBP_IT_BACK;
|
|
event.Action.dwData := expected_type;
|
|
Add(event);
|
|
end;
|
|
|
|
///
|
|
/// Insert a deadkey marker into the output
|
|
///
|
|
procedure TDebugEventList.Action_Marker(marker: uintptr_t);
|
|
var
|
|
event: TDebugEvent;
|
|
begin
|
|
// kmx requires that markers are between 1 and $FFFF
|
|
// ($FFFD in practical terms)
|
|
Assert((marker > 0) and (marker <= High(WORD)));
|
|
|
|
event := TDebugEvent.Create;
|
|
event.EventType := etAction;
|
|
event.Action.ActionType := KM_KBP_IT_MARKER;
|
|
event.Action.dwData := marker;
|
|
Add(event);
|
|
end;
|
|
|
|
procedure TDebugEventList.AddDebugItem(
|
|
debug: pkm_core_state_debug_item;
|
|
debugkeyboard: TDebugKeyboard;
|
|
vk: uint16_t;
|
|
modifier_state: uint16_t);
|
|
|
|
function DiscoverRuleLine: Integer;
|
|
var
|
|
grp, i: Integer;
|
|
group: PKeymanGroup;
|
|
begin
|
|
grp := -1;
|
|
Result := 0;
|
|
|
|
if Assigned(debug.kmx_info.group) then
|
|
begin
|
|
group := PKeymanGroup(debug.kmx_info.group);
|
|
for i := 0 to debugkeyboard.Groups.Count - 1 do
|
|
if group.dpName = debugkeyboard.Groups[i].Name then
|
|
begin
|
|
grp := i;
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
case debug._type of
|
|
KM_KBP_DEBUG_END: Result := debugkeyboard.BeginUnicodeLine;
|
|
KM_KBP_DEBUG_BEGIN: Result := debugkeyboard.BeginUnicodeLine;
|
|
KM_KBP_DEBUG_GROUP_ENTER,
|
|
KM_KBP_DEBUG_GROUP_EXIT: if grp > -1 then Result := debugkeyboard.Groups[grp].Line;
|
|
KM_KBP_DEBUG_MATCH_ENTER,
|
|
KM_KBP_DEBUG_MATCH_EXIT: if grp > -1 then Result := debugkeyboard.Groups[grp].MatchLine;
|
|
KM_KBP_DEBUG_NOMATCH_ENTER,
|
|
KM_KBP_DEBUG_NOMATCH_EXIT: if grp > -1 then Result := debugkeyboard.Groups[grp].NomatchLine;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
ev: TDebugEvent;
|
|
rule: PKeymanKey;
|
|
group: PKeymanGroup;
|
|
store: PKeymanStore;
|
|
begin
|
|
if not Assigned(debugkeyboard) then Exit;
|
|
|
|
ev := TDebugEvent.Create;
|
|
ev.EventType := etRuleMatch;
|
|
ev.Rule.ItemType := debug._type;
|
|
ev.Rule.Flags := debug.flags; // TODO: are these flags right?
|
|
|
|
if Assigned(debug.kmx_info.rule) then
|
|
begin
|
|
rule := PKeymanKey(debug.kmx_info.rule);
|
|
ev.Rule.Line := rule.Line;
|
|
ev.Rule.Rule.Key := rule.Key;
|
|
ev.Rule.Rule.Line := rule.Line;
|
|
ev.Rule.Rule.ShiftFlags := rule.ShiftFlags;
|
|
ev.Rule.Rule.dpOutput := rule.dpOutput;
|
|
ev.Rule.Rule.dpContext := rule.dpContext;
|
|
ev.Rule.FillStoreList(debug, debugkeyboard.Memory.Memory);
|
|
end
|
|
else
|
|
ev.Rule.Line := DiscoverRuleLine;
|
|
|
|
ev.Rule.Context := debug.kmx_info.Context;
|
|
if Assigned(debug.kmx_info.group) then
|
|
begin
|
|
group := PKeymanGroup(debug.kmx_info.group);
|
|
ev.Rule.Group.dpName := group.dpName;
|
|
ev.Rule.Group.dpMatch := group.dpMatch;
|
|
ev.Rule.Group.dpNoMatch := group.dpNoMatch;
|
|
ev.Rule.Group.fUsingKeys := group.fUsingKeys;
|
|
end;
|
|
|
|
Add(ev);
|
|
|
|
{ Update user interface }
|
|
|
|
if ev.Rule.ItemType = KM_KBP_DEBUG_BEGIN then
|
|
begin
|
|
ev.Rule.Key.VirtualKey := vk;
|
|
ev.Rule.Key.Modifiers := modifier_state;
|
|
end;
|
|
|
|
if ev.Rule.ItemType = KM_KBP_DEBUG_SET_OPTION then
|
|
begin
|
|
store := PKeymanStore(debug.kmx_info.option.store);
|
|
ev.Rule.OptionStoreName := store.dpName;
|
|
ev.Rule.OptionValue := debug.kmx_info.option.value;
|
|
end;
|
|
end;
|
|
|
|
function TDebugEventList.AddStateItems(
|
|
state: pkm_core_state;
|
|
vk: uint16_t;
|
|
modifier_state: uint16_t
|
|
): Boolean;
|
|
var
|
|
action: pkm_core_action_item;
|
|
begin
|
|
Result := True;
|
|
action := km_kbp_state_action_items(state, nil);
|
|
while (action._type <> KM_KBP_IT_END) do
|
|
begin
|
|
Result := Result and AddActionItem(vk, action);
|
|
Inc(action);
|
|
end;
|
|
end;
|
|
|
|
function TDebugEventList.AddStateItems(
|
|
state: pkm_core_state;
|
|
vk: uint16_t;
|
|
modifier_state: uint16_t;
|
|
debugkeyboard: TDebugKeyboard
|
|
): Boolean;
|
|
var
|
|
action: pkm_core_action_item;
|
|
debug: pkm_core_state_debug_item;
|
|
action_index: Integer;
|
|
begin
|
|
Result := True;
|
|
debug := km_kbp_state_debug_items(state, nil);
|
|
action := km_kbp_state_action_items(state, nil);
|
|
action_index := 0;
|
|
while debug._type <> KM_KBP_DEBUG_END do
|
|
begin
|
|
if debug.kmx_info.first_action > action_index then
|
|
begin
|
|
while (action._type <> KM_KBP_IT_END) and (action_index < debug.kmx_info.first_action) do
|
|
begin
|
|
Result := Result and AddActionItem(vk, action);
|
|
Inc(action);
|
|
Inc(action_index);
|
|
end;
|
|
end;
|
|
AddDebugItem(debug, debugkeyboard, vk, modifier_state);
|
|
Inc(debug);
|
|
end;
|
|
|
|
AddDebugItem(debug, debugkeyboard, vk, modifier_state);
|
|
|
|
if action._type = KM_KBP_IT_EMIT_KEYSTROKE then
|
|
begin
|
|
// The EMIT_KEYSTROKE action comes after all rules have completed processing
|
|
Result := Result and AddActionItem(vk, action);
|
|
Inc(action);
|
|
end;
|
|
|
|
// By the time we get to the end of rule processing, all actions should have
|
|
// already been undertaken
|
|
Assert(action._type = KM_KBP_IT_END);
|
|
end;
|
|
|
|
{ TDebugEventRuleData }
|
|
|
|
procedure TDebugEventRuleData.FillStoreList(event: pkm_core_state_debug_item; KeyboardMemory: PChar);
|
|
function StoreOffset(kfh: PKeyboardFileHeader; i: Word): PChar;
|
|
begin
|
|
Result := KeyboardMemory;
|
|
Inc(Result, kfh.dpStoreArray);
|
|
while i > 0 do
|
|
begin
|
|
Inc(Result, SizeOf(TKeyboardFileStore));
|
|
Dec(i);
|
|
end;
|
|
end;
|
|
begin
|
|
nStores := 0;
|
|
// kfh := PKeyboardFileHeader(KeyboardMemory);
|
|
while event.kmx_info.store_offsets[nStores*2] <> $FFFF do
|
|
begin
|
|
StoreOffsets[nStores] := event.kmx_info.store_offsets[nStores*2+1];
|
|
// Stores[nStores].Store := PKeyboardFileStore(StoreOffset(kfh, di.StoreOffsets[nStores*2]))^;
|
|
// Stores[nStores].MatchPosition := di.StoreOffsets[nStores*2+1];
|
|
Inc(nStores);
|
|
end;
|
|
end;
|
|
|
|
end.
|