From 1ebe2f5fa177ad5aaaf25c3f36cc2ec83007ec03 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 4 Feb 2020 16:14:37 +1100 Subject: [PATCH] fix(windows): Windows touch keyboard would cancel on each keystroke Fixes #2450. When Keyman serialized input, it would cause the touch keyboard to cancel because the touch keyboard thought the received event meant the user was touching the physical keyboard. This resolves that problem, using a watch timer to check the visibility of the touch input panel. --- windows/src/engine/keyman/UfrmKeyman7Main.dfm | 6 + windows/src/engine/keyman/UfrmKeyman7Main.pas | 27 ++ windows/src/engine/keyman/keyman.dpr | 3 +- windows/src/engine/keyman/keyman.dproj | 321 +++++++++++++++++- .../Keyman.System.FrameworkInputPane.pas | 106 ++++++ windows/src/engine/keyman32/KEYMAN32.DEF | 2 + .../keyman32/k32_lowlevelkeyboardhook.cpp | 26 ++ .../kmcomapi/com/system/keymancontrol.pas | 16 +- .../delphi/general/KeymanEngineControl.pas | 1 + 9 files changed, 500 insertions(+), 8 deletions(-) create mode 100644 windows/src/engine/keyman/touchkeyboard/Keyman.System.FrameworkInputPane.pas diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.dfm b/windows/src/engine/keyman/UfrmKeyman7Main.dfm index c62349ce3c..35fe2372ca 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.dfm +++ b/windows/src/engine/keyman/UfrmKeyman7Main.dfm @@ -34,4 +34,10 @@ object frmKeyman7Main: TfrmKeyman7Main Left = 280 Top = 104 end + object tmrCheckInputPane: TTimer + Interval = 500 + OnTimer = tmrCheckInputPaneTimer + Left = 96 + Top = 104 + end end diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.pas b/windows/src/engine/keyman/UfrmKeyman7Main.pas index 010e5af2a9..68da5b982a 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.pas +++ b/windows/src/engine/keyman/UfrmKeyman7Main.pas @@ -150,6 +150,7 @@ uses Keyman.System.DebugLogClient, Keyman.System.DebugLogManager, + Keyman.System.FrameworkInputPane, keymanapi_TLB, //TOUCH UfrmTouchKeyboard, @@ -198,10 +199,12 @@ type mnu: TPopupMenu; tmrTestKeymanFunctioning: TTimer; tmrOnlineUpdateCheck: TTimer; + tmrCheckInputPane: TTimer; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure tmrTestKeymanFunctioningTimer(Sender: TObject); procedure tmrOnlineUpdateCheckTimer(Sender: TObject); + procedure tmrCheckInputPaneTimer(Sender: TObject); private InMenuLoop: Integer; // I1082 - Avoid menu nasty flicker with rapid click FClosingApp: Boolean; @@ -225,6 +228,9 @@ type FHotkeyWindow: HWND; FHotkeys: TIntegerList; + FInputPane: TFrameworkInputPane; + FIsInputPaneVisible: Boolean; + //TOUCH FCurrentContext: string; function AddTaskbarIcon: Boolean; @@ -533,6 +539,8 @@ begin ChangeWindowMessageFilter(sMsg_TaskbarRestart, MSGFLT_ADD); ChangeWindowMessageFilter(WM_USER_PlatformComms, MSGFLT_ADD); + FInputPane := TFrameworkInputPane.Create; + PostMessage(Handle, WM_USER_Start, 0, 0); end; @@ -543,6 +551,8 @@ end;} procedure TfrmKeyman7Main.FormDestroy(Sender: TObject); begin + FreeAndNil(FInputPane); + ClosePlatformComms64; UnregisterHotkeys; @@ -1766,6 +1776,23 @@ end; //TOUCH else Result := kmcom.Options['koUseTouchLayout'].Value; //TOUCH end; +procedure TfrmKeyman7Main.tmrCheckInputPaneTimer(Sender: TObject); +var + r: TRect; + isVisible: Boolean; +begin + if Assigned(FInputPane) and FInputPane.GetLocation(r) then + begin + isVisible := not r.IsEmpty; + if FIsInputPaneVisible <> isVisible then + begin + FIsInputPaneVisible := isVisible; + kmint.KeymanEngineControl.UpdateTouchPanelVisibility(isVisible); + end; + //TDebugLogClient.Instance.WriteMessage('InputPane Location: %d, %d, %d, %d', [r.Left, r.Top, r.Right, r.Bottom]); + end; +end; + procedure TfrmKeyman7Main.tmrOnlineUpdateCheckTimer(Sender: TObject); begin with TRegistryErrorControlled.Create do // I2890 diff --git a/windows/src/engine/keyman/keyman.dpr b/windows/src/engine/keyman/keyman.dpr index 2f9bed1197..7fc5b4d64a 100644 --- a/windows/src/engine/keyman/keyman.dpr +++ b/windows/src/engine/keyman/keyman.dpr @@ -140,7 +140,8 @@ uses Keyman.System.DebugLogCommon in '..\..\global\delphi\debug\Keyman.System.DebugLogCommon.pas', Keyman.System.SharedBuffers in 'Keyman.System.SharedBuffers.pas', Keyman.System.Security in '..\..\global\delphi\general\Keyman.System.Security.pas', - Keyman.Winapi.VersionHelpers in '..\..\global\delphi\winapi\Keyman.Winapi.VersionHelpers.pas'; + Keyman.Winapi.VersionHelpers in '..\..\global\delphi\winapi\Keyman.Winapi.VersionHelpers.pas', + Keyman.System.FrameworkInputPane in 'touchkeyboard\Keyman.System.FrameworkInputPane.pas'; {$R ICONS.RES} {$R VERSION.RES} diff --git a/windows/src/engine/keyman/keyman.dproj b/windows/src/engine/keyman/keyman.dproj index ff068dcffb..88961fc506 100644 --- a/windows/src/engine/keyman/keyman.dproj +++ b/windows/src/engine/keyman/keyman.dproj @@ -7,7 +7,7 @@ 1 Console VCL - 18.4 + 18.7 Win32 @@ -265,6 +265,7 @@ + Cfg_2 @@ -327,9 +328,9 @@ False - + - keyman.exe + .\ true @@ -339,9 +340,9 @@ true - + - .\ + keyman.exe true @@ -350,7 +351,6 @@ 1 - Contents\MacOS 0 @@ -360,6 +360,12 @@ 1 + + + res\xml + 1 + + library\lib\armeabi-v7a @@ -396,6 +402,18 @@ 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + res\drawable @@ -432,6 +450,36 @@ 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + res\drawable-small @@ -456,6 +504,12 @@ 1 + + + res\values + 1 + + 1 @@ -472,6 +526,10 @@ 1 .framework + + 1 + .framework + 0 @@ -481,6 +539,10 @@ 1 .dylib + + 1 + .dylib + 0 .dll;.bpl @@ -503,6 +565,10 @@ 1 .dylib + + 1 + .dylib + 0 .bpl @@ -524,6 +590,9 @@ 0 + + 0 + 0 @@ -539,6 +608,17 @@ 1 + + + 1 + + + 1 + + + 1 + + 1 @@ -550,6 +630,39 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -561,6 +674,61 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -572,6 +740,116 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -605,6 +883,28 @@ 1 + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + 1 @@ -634,6 +934,7 @@ 1 + @@ -641,6 +942,10 @@ Contents\Resources 1 + + Contents\Resources + 1 + @@ -662,6 +967,9 @@ 1 + + 1 + 0 @@ -701,6 +1009,7 @@ + diff --git a/windows/src/engine/keyman/touchkeyboard/Keyman.System.FrameworkInputPane.pas b/windows/src/engine/keyman/touchkeyboard/Keyman.System.FrameworkInputPane.pas new file mode 100644 index 0000000000..5b8163b1e9 --- /dev/null +++ b/windows/src/engine/keyman/touchkeyboard/Keyman.System.FrameworkInputPane.pas @@ -0,0 +1,106 @@ +unit Keyman.System.FrameworkInputPane; + +interface + +uses + Winapi.Windows; + +// See https://stackoverflow.com/a/55513524/1836776 +// https://gist.github.com/DelphiWorlds/2098ebafd20aa43f6c5a69503b06c4ca +// https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-iframeworkinputpane + +const + CLSID_FrameworkInputPane: TGUID = '{D5120AA3-46BA-44C5-822D-CA8092C1FC72}'; + +type + IFrameworkInputPaneHandler = interface + ['{226C537B-1E76-4D9E-A760-33DB29922F18}'] + + function Showing( + var rcInputPaneScreenLocation: TRect; + fEnsureFocusedElementInView: BOOL + ): HResult; stdcall; + + function Hiding( + fEnsureFocusedElementInView: BOOL + ): HResult; stdcall; + end; + + IFrameworkInputPane = interface + ['{5752238B-24F0-495A-82F1-2FD593056796}'] + + function Advise( + pWindow: IUnknown; + pHandler: IFrameworkInputPaneHandler; + var pdwCookie: DWORD + ): HRESULT; stdcall; + + function AdviseWithHWND( + hwnd: HWND; + pHandler: IFrameworkInputPaneHandler; + var pdwCookie: DWORD + ): HRESULT; stdcall; + + function Unadvise( + dwCookie: DWORD + ): HRESULT; stdcall; + + function Location( + var prcInputPaneScreenLocation: TRect + ): HRESULT; stdcall; + end; + + TFrameworkInputPane = class + private + FInputPane: IFrameworkInputPane; + public + constructor Create; + destructor Destroy; override; + + function GetLocation(var rt: TRECT): Boolean; + end; + +implementation + +uses + Winapi.ActiveX, + Keyman.System.DebugLogClient; + +{ TFrameworkInputWrapper } + +constructor TFrameworkInputPane.Create; +var + hr: HRESULT; +begin + inherited Create; + + FInputPane := nil; + + hr := CoCreateInstance( + CLSID_FrameworkInputPane, nil, CLSCTX_ALL {CLSCTX_INPROC_SERVER}, IFrameworkInputPane, FInputPane); + if not Succeeded(hr) then + begin + // We'll silently fail; this may be an older OS + FInputPane := nil; + TDebugLogClient.Instance.WriteMessage('Unable to instantiate IFrameworkInputPane: %x', [hr]); + end; +end; + +destructor TFrameworkInputPane.Destroy; +begin + FInputPane := nil; + inherited Destroy; +end; + +function TFrameworkInputPane.GetLocation(var rt: TRECT): Boolean; +begin + Result := False; + rt := rt.Empty; + + if Assigned(FInputPane) then + begin + Result := not FAILED(FInputPane.Location(rt)); + end; +end; + +end. diff --git a/windows/src/engine/keyman32/KEYMAN32.DEF b/windows/src/engine/keyman32/KEYMAN32.DEF index cf866b87bb..199297f8e9 100644 --- a/windows/src/engine/keyman32/KEYMAN32.DEF +++ b/windows/src/engine/keyman32/KEYMAN32.DEF @@ -34,3 +34,5 @@ EXPORTS Keyman_ResetInitialisation Keyman_WriteDebugEvent + + Keyman_UpdateTouchPanelVisibility diff --git a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp index fc16f037cd..c57f02a796 100644 --- a/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp +++ b/windows/src/engine/keyman32/k32_lowlevelkeyboardhook.cpp @@ -94,6 +94,25 @@ BOOL IsConsoleWindow(HWND hwnd) { return last_isConsoleWindow; } + +/* + Test for touch panel visibility (#2450). UpdateTouchPanelVisibility is called periodically by + keyman.exe to refresh the visibility flag. +*/ + +static BOOL touchPanelVisible; + +void WINAPI Keyman_UpdateTouchPanelVisibility(BOOL isVisible) { + touchPanelVisible = isVisible; + SendDebugMessageFormat(0, sdmAIDefault, 0, "Keyman_UpdateTouchPanelVisibility: isVisible=%d", touchPanelVisible); +} + +BOOL IsTouchPanelVisible() { + // Note: GetCurrentInputMessageSource does not work in this context + // Using IFrameworkInputPaneHandler events only works for a specific window, so not helpful for us. + return touchPanelVisible; +} + /* Cache UseRegisterHotkey debug flag for this session */ @@ -132,6 +151,13 @@ LRESULT _kmnLowLevelKeyboardProc( return CallNextHookEx(Globals::get_hhookLowLevelKeyboardProc(), nCode, wParam, lParam); } + if (IsTouchPanelVisible()) { + // See #2450. The touch panel will close automatically if we reprocess key events + // So we don't want to reprocess events when it is visible. + //SendDebugMessageFormat(0, sdmAIDefault, 0, "kmnLowLevelKeyboardProc: touch panel is visible. Not reprocessing keystrokes"); + return CallNextHookEx(Globals::get_hhookLowLevelKeyboardProc(), nCode, wParam, lParam); + } + DWORD Flag = 0; if (UseRegisterHotkey()) { switch (hs->vkCode) { diff --git a/windows/src/engine/kmcomapi/com/system/keymancontrol.pas b/windows/src/engine/kmcomapi/com/system/keymancontrol.pas index c5d55d478a..693ab1922d 100644 --- a/windows/src/engine/kmcomapi/com/system/keymancontrol.pas +++ b/windows/src/engine/kmcomapi/com/system/keymancontrol.pas @@ -73,6 +73,7 @@ type TKeyman32GetInitialisedFunction = function(var FSingleApp: BOOL): BOOL; stdcall; TKeyman32ControllerSendMessageFunction = function(msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall; TKeyman32ControllerPostMessageFunction = procedure(msg: UINT; wParam: WPARAM; lParam: LPARAM); stdcall; + TKeyman32UpdateTouchPanelVisibilityFunction = procedure(Value: BOOL); stdcall; TKeymanControl = class(TKeymanAutoObject, IKeymanCustomisationAccess, IIntKeymanControl, IKeymanControl, IKeymanEngineControl) private @@ -92,6 +93,7 @@ type FKeyman_PostMasterController: TKeyman32ControllerPostMessageFunction; FKeyman_PostControllers: TKeyman32ControllerPostMessageFunction; FKeyman_StartExit: TKeyman32ExitFunction; // I3092 + FKeyman_UpdateTouchPanelVisibility: TKeyman32UpdateTouchPanelVisibilityFunction; procedure LoadKeyman32; procedure StartKeyman32; procedure Do_Keyman_Exit; @@ -133,7 +135,7 @@ type procedure StopKeyman; safecall; procedure StopVisualKeyboard; safecall; - { IKeymanControlRestart } + { IKeymanEngineControl } procedure RestartEngine; safecall; // 32 bit only procedure ShutdownKeyman32Engine; safecall; // 32 bit only procedure StartKeyman32Engine; safecall; // 32 bit only @@ -143,6 +145,7 @@ type procedure UnregisterControllerWindow(Value: LongWord); safecall; // 32 bit only procedure DisableUserInterface; safecall; procedure EnableUserInterface; safecall; + procedure UpdateTouchPanelVisibility(Value: Boolean); safecall; { IIntKeymanControl } procedure AutoApplyKeyman; @@ -570,6 +573,16 @@ begin {$ENDIF} end; +procedure TKeymanControl.UpdateTouchPanelVisibility(Value: Boolean); +begin +{$IFDEF WIN64} + Error(Cardinal(E_NOTIMPL)); +{$ELSE} + LoadKeyman32; + FKeyman_UpdateTouchPanelVisibility(Value); +{$ENDIF} +end; + procedure TKeymanControl.ShutdownKeyman32Engine; begin {$IFDEF WIN64} @@ -707,6 +720,7 @@ begin @FKeyman_SendMasterController := ProcAddr('Keyman_SendMasterController'); @FKeyman_PostMasterController := ProcAddr('Keyman_PostMasterController'); @FKeyman_PostControllers := ProcAddr('Keyman_PostControllers'); + @FKeyman_UpdateTouchPanelVisibility := ProcAddr('Keyman_UpdateTouchPanelVisibility'); end; end; diff --git a/windows/src/global/delphi/general/KeymanEngineControl.pas b/windows/src/global/delphi/general/KeymanEngineControl.pas index 6fae144f2c..5ddf601f1a 100644 --- a/windows/src/global/delphi/general/KeymanEngineControl.pas +++ b/windows/src/global/delphi/general/KeymanEngineControl.pas @@ -31,6 +31,7 @@ type procedure UnregisterControllerWindow(Value: LongWord); safecall; procedure DisableUserInterface; safecall; procedure EnableUserInterface; safecall; + procedure UpdateTouchPanelVisibility(Value: Boolean); safecall; end; implementation