spiegel-keyman/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas
2023-10-09 14:52:23 +07:00

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.