spiegel-keyman/windows/src/engine/keyman/UfrmKeyman7Main.pas
rc-swag d52b48cbbb feat(windows): handle arm64 complier environment
Update the build.bat to handle configuring the build
environment with vsdevcmd correctly for arm64 2019 doesn't
work very well so have to have pretty weird if statement.
also checked in some missed updates to function name change
2025-10-28 16:40:42 +10:00

2129 lines
73 KiB
ObjectPascal

(*
Name: UfrmKeyman7Main
Copyright: Copyright (C) SIL International.
Documentation:
Description:
Create Date: 20 Jun 2006
Modified Date: 25 Oct 2016
Authors: mcdurdin
Related Files:
Dependencies:
Bugs:
Todo:
Notes:
History: 20 Jun 2006 - mcdurdin - Initial version
01 Aug 2006 - mcdurdin - Use TKeymanTrayIcon, rework for Keyman 7
14 Sep 2006 - mcdurdin - Remove debug controls
14 Sep 2006 - mcdurdin - Rework menu items as public for calling from visual keyboard
14 Sep 2006 - mcdurdin - Support icons for keyboards as well as bitmaps
14 Sep 2006 - mcdurdin - Refactor menu rendering and construction
04 Dec 2006 - mcdurdin - Load icons appropriately
04 Dec 2006 - mcdurdin - Support Vista
04 Dec 2006 - mcdurdin - Lots of UI tweak and polish
04 Dec 2006 - mcdurdin - Support KMC_GETLOADED for wm_keyman_control
04 Jan 2007 - mcdurdin - Add vista alt fix
04 Jan 2007 - mcdurdin - ShutdownKeyman32Engine when closing down
04 Jan 2007 - mcdurdin - Add OpenKeyboardHelp function
04 Jan 2007 - mcdurdin - Add help support
15 Jan 2007 - mcdurdin - Don't show the OSK on first startup
22 Jan 2007 - mcdurdin - Implemented run a program with the RunProgram action
19 Mar 2007 - mcdurdin - I701 - running products not freed
16 May 2007 - mcdurdin - I819 - Test if Keyman is functioning when keyboard menu opened
19 Jun 2007 - mcdurdin - I815 - Support current-user font install
19 Jun 2007 - mcdurdin - I819 - Improve Keyman stability
23 Aug 2007 - mcdurdin - Refactor package welcome
12 Oct 2007 - mcdurdin - I984, I1077, I1078 - Handle crash when starting Keyman with Windows and the tray is unresponsive
12 Oct 2007 - mcdurdin - I1096 - Avoid silent exception when OPSK is not visible and Keyman exits
12 Oct 2007 - mcdurdin - I1019, I1020, I1028, I1047, I1086 - Avoid crash when installing user fonts and starting Keyman
12 Oct 2007 - mcdurdin - I958 - Handle OSK disappearing when it should be appearing
19 Nov 2007 - mcdurdin - I1148 - Fix crash starting Windows (show error code)
27 Mar 2008 - mcdurdin - I1248 - Integrated Welcome process
27 Mar 2008 - mcdurdin - I1288 - OSK auto-popup
27 Mar 2008 - mcdurdin - I1375 - Switch to OSK page automatically when keyboard selected
27 Mar 2008 - mcdurdin - I1376 - Sometimes OSK shows wrong keyboard when it starts
27 Mar 2008 - mcdurdin - I1374 - Support font helper
27 Mar 2008 - mcdurdin - I1377 - Show menu near tray icon when using hotkey
27 Mar 2008 - mcdurdin - I1287 - Support switching languages with keyboards
27 Mar 2008 - mcdurdin - I1292 - InstallUserFonts using ansi filepath
27 Mar 2008 - mcdurdin - I1123, I1136 - Fix InstallUserFonts crashes
27 Mar 2008 - mcdurdin - I1236 - Integrate keyboard help with Keyman help
27 Mar 2008 - mcdurdin - I1289 - Focus not returned to active app when OSK opened
27 Mar 2008 - mcdurdin - I1274 - Avoid crash if OSK already closed when closing OSK
14 Jun 2008 - mcdurdin - Improve performance of OSK
16 Jun 2008 - mcdurdin - I1082 - Avoid nasty menu flicker
20 Jul 2008 - mcdurdin - I1412 - Performance when starting On Screen Keyboard in tutorial
28 Jul 2008 - mcdurdin - I1510 - context help
10 Sep 2008 - mcdurdin - I1627 - Switching languages with keyboard should be in keyman32.dll
19 Sep 2008 - mcdurdin - I1649 - Tray icon robustness
29 Sep 2008 - mcdurdin - I1649 - Additional tray icon robustness - ask message after 120 seconds only
16 Jan 2009 - mcdurdin - I1649 - Additional trayicon robust work
16 Jan 2009 - mcdurdin - I1486 - Restart Keyman Engine if it was previously locked and didn't start correctly
16 Jan 2009 - mcdurdin - I1656 - Fix crash when OSK does not exist (e.g. fails to create)
27 Jan 2009 - mcdurdin - I1826 - Improve performance of fontchange
25 May 2009 - mcdurdin - I1951 - Fix crash when starting 2 instances of Keyman Engine at same time
11 Dec 2009 - mcdurdin - I934 - x64 support - platform comms
11 Dec 2009 - mcdurdin - I1455 - Keyboard switching not working correctly in multi-process/multi-thread apps
11 Dec 2009 - mcdurdin - I823 - Use GetGUIThreadInfo
12 Mar 2010 - mcdurdin - I2226 - Add text editor, charmap, keyboard usage, font helper to menu
29 Mar 2010 - mcdurdin - I1089 - Switch keyboard for all applications support
06 Apr 2010 - mcdurdin - I2271 - Select Keyboard tidy up
09 Apr 2010 - mcdurdin - I2287 - Menu links not opening correct OSK tool
19 Apr 2010 - mcdurdin - I2297 - Switching languages automatically was not quite stable
04 May 2010 - mcdurdin - I2349 - Debug log capture hotkey
04 May 2010 - mcdurdin - I2348 - Rework of debug log format
04 May 2010 - mcdurdin - I2350 - Support debugging from keyman.exe
04 May 2010 - mcdurdin - I2297 - Window and keyboard activation work
14 May 2010 - mcdurdin - I2226 - Improve Keyman menu
25 May 2010 - mcdurdin - I1694 - Select Keyman UI language rework
28 Jun 2010 - mcdurdin - I2444 - Keyman tries to re-init because OSK was not unregistering until after Keyman_Exit
22 Oct 2010 - mcdurdin - I2522 - Language switch window - control Windows' hotkeys
30 Nov 2010 - mcdurdin - I2543 - Support switching to TSF addins in Language Switch window
11 Jan 2011 - mcdurdin - I1867 - Sort keyboards alphabetically - means we now refer to keyboards by KeymanID instead of index
11 Jan 2011 - mcdurdin - I2287 - Open correct tool in OSK when selecting from menu
25 Jan 2011 - mcdurdin - I2569 - Keyboard welcome needs to run from kmshell
31 Jan 2011 - mcdurdin - I2692 - Ensure OSK closes before Keyman does
28 Feb 2011 - mcdurdin - I2398 - After reloading configuration, OSK does not show active keyboard in toolbar
03 May 2011 - mcdurdin - I2890 - Record diagnostic data when encountering registry errors
19 Aug 2011 - mcdurdin - I3010 - Hint balloon on Keyman tray icon has hardcoded message
22 Aug 2011 - mcdurdin - I3042 - Null dereference showing balloon hint
03 Oct 2011 - mcdurdin - I3092 - Keyman Engine does not restart nicely when shutdown uncleanly
18 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls + Win9x support
02 Dec 2011 - mcdurdin - I3160 - When multiple OEM products are running, hotkeys can fail to activate keyboards
24 Jan 2012 - mcdurdin - I3139 - Crash at shutdown when keyman32.dll unloaded early
02 Feb 2012 - mcdurdin - I2975 - VistaAltFixUnit causes exception on shutdown
03 Nov 2012 - mcdurdin - I3515 - V9.0 - Merge of I3139 - Crash at shutdown when keyman32.dll unloaded early
03 Nov 2012 - mcdurdin - I3516 - V9.0 - Merge of I3160 - When multiple OEM products are running, hotkeys can fail to activate keyboards
17 Jan 2013 - mcdurdin - I3758 - V9.0 - keymanx64 can find incorrect window handle for keyman.exe
24 Oct 2013 - mcdurdin - I3933 - V9.0 - Keyman tray icon menu is not showing installed keyboards
07 Nov 2013 - mcdurdin - I3949 - V9.0 - Keyboard selection and notification needs consolidation
11 Nov 2013 - mcdurdin - I3961 - V9.0 - Clean up communication between keyman32 and keyman
29 Nov 2013 - mcdurdin - I3990 - V9.0 - Keyman keyboards menu should be used on OSK toolbar
29 Nov 2013 - mcdurdin - I3991 - V9.0 - Eliminate some now unnecessary interference checks when showing menu
17 Dec 2013 - mcdurdin - I4004 - V9.0 - Remove old Keyman keyboard code from LangSwitchManager
06 Mar 2014 - mcdurdin - I4124 - V9.0 - Language switch dialog is not working in v9
24 Apr 2014 - mcdurdin - I4191 - V9.0 - Lang switch window shows wrong selection with Alt+LeftShift when TIP is active
02 May 2014 - mcdurdin - I4225 - V9.0- Opening font helper or keyboard usage from Keyman menu on Win 8 still shows HTML outside window
28 May 2014 - mcdurdin - I4243 - V9.0 - Remove kmuspmgr and keymanuc
16 Jun 2014 - mcdurdin - I4271 - V9.0 - Switch language for all applications is not working
23 Jun 2014 - mcdurdin - I4285 - V9.0 - Profile change needs debug data for tracing source profile change thread
23 Jun 2014 - mcdurdin - I4286 - V9.0 - If an atom is deleted before profile change is received, then we must ignore it
03 Aug 2014 - mcdurdin - I4359 - V9.0 - OSK shows wrong base keyboard and doesn't refresh
03 Aug 2014 - mcdurdin - I4326 - V9.0 - Switch-off hotkey not working, then keyboard hotkey stopped working (win 8.1 jeremy) [High]
12 Aug 2014 - mcdurdin - I4374 - V9.0 - Refactor wow64 tests
28 Aug 2014 - mcdurdin - I4390 - V9.0 - Free vs Pro
28 Aug 2014 - mcdurdin - I4390 - V9.0 - Free vs Pro
13 Oct 2014 - mcdurdin - I4451 - V9.0 - Language hotkeys are not working
23 Oct 2014 - mcdurdin - I4458 - Crash showing keyboard menu when product missing [CrashID:keyman.exe_9.0.472.0_2C5933D2_EAccessViolation]
03 Feb 2015 - mcdurdin - I4576 - V9.0 - Switch for all languages not disabled on Win8 upgrades
06 Feb 2015 - mcdurdin - I4576 - V9.0 - Switch for all languages not disabled on Win8 upgrades
04 Mar 2015 - mcdurdin - I4606 - V9.0 - Support single keyboard buttons on OSK toolbar
22 Apr 2015 - mcdurdin - I4674 - V9.0 - Hotkeys do not always work consistently
02 Jun 2015 - mcdurdin - I4715 - Language profile change notification while Keyman menu is visible sometimes causes a crash [CrashID:keyman.exe_9.0.492.0_00000000_EAccessViolation]
02 Jun 2015 - mcdurdin - I4731 - Keyman loses focus sometimes when switching keyboards using the menu
02 Jun 2015 - mcdurdin - I4731 - Keyman loses focus sometimes when switching keyboards using the menu
12 Sep 2016 - mcdurdin - I5086 - Keyboard hotkey toggles are not working in 9.0
25 Oct 2016 - mcdurdin - I5132 - keyman.exe crashes on start with access violation
25 Oct 2016 - mcdurdin - I5133 - Keyman.exe starts and exits because Control.StartKeyman32Engiene is passed a zero ProductID
25 Oct 2016 - mcdurdin - I5136 - Remove additional product references from Keyman Engine
*)
unit UfrmKeyman7Main; // I3306 // I4004 // I5136
interface
uses
Winapi.Windows,
Winapi.Messages,
System.Classes,
System.Contnrs,
System.SysUtils,
System.UITypes,
Vcl.Graphics,
Vcl.Controls,
Vcl.Forms,
Vcl.Dialogs,
Vcl.ExtCtrls,
Vcl.Menus,
Vcl.StdCtrls,
Vcl.ImgList,
Keyman.System.DebugLogClient,
Keyman.System.DebugLogManager,
Keyman.System.FrameworkInputPane,
keymanapi_TLB,
//TOUCH UfrmTouchKeyboard,
GlobalKeyboardChangeManager,
UfrmVisualKeyboard,
IntegerList,
KeymanTrayIcon,
KeymanMenuItem,
custinterfaces,
Menu_KeyboardItems,
UserMessages,
UfrmKeymanMenu,
UfrmLanguageSwitch,
LangSwitchManager;
const
TKF_PING = 0;
TKF_RESPONSE = 1;
type
TMouseButtons = set of TMouseButton;
TRunningProduct = class
private
FOwner: TComponent;
public
Name: WideString;
HIcon: THandle;
FTrayIcon: TKeymanTrayIcon;
FLangSwitchConfiguration: TLangSwitchConfiguration;
constructor Create(AOwner: TComponent);
destructor Destroy; override;
end;
TTestKeymanFunctionType = (tkfPopupMenu, tkfKeyboard);
TTestKeymanFunctioning = record
FunctionType: TTestKeymanFunctionType;
RunOnSuccess: Boolean;
Count: Integer;
end;
THandleArray = array of THandle; // I4731
TfrmKeyman7Main = class(TForm)
mnu: TPopupMenu;
tmrTestKeymanFunctioning: TTimer;
tmrBackgroundUpdateCheck: TTimer;
tmrCheckInputPane: TTimer;
tmrRefresh: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure tmrTestKeymanFunctioningTimer(Sender: TObject);
procedure tmrBackgroundUpdateCheckTimer(Sender: TObject);
procedure tmrCheckInputPaneTimer(Sender: TObject);
procedure tmrRefreshTimer(Sender: TObject);
private
InMenuLoop: Integer; // I1082 - Avoid menu nasty flicker with rapid click
FClosingApp: Boolean;
FRunningProduct: TRunningProduct;
sMsg_TaskbarRestart: Cardinal;
FTrayButtonDown: TMouseButtons;
FTestKeymanFunctioning: TTestKeymanFunctioning;
FWelcomeWindow: THandle;
FInUpdateOSKVisibility: Boolean;
FOSKManuallyClosedThisSession: Boolean;
FLangSwitchRefreshWatcher: TThread;
FLastFocus: THandle;
FLastActive: THandle;
//FLastKeymanID: Integer;
FLastHKL: Integer;
FLangSwitchManager: TLangSwitchManager; // I3933
FGlobalKeyboardChangeManager: TGlobalKeyboardChangeManager; // I4271
FActiveHKL: Integer;
FTrayIcon: TIcon; // I4359
FInputPane: TFrameworkInputPane;
FIsInputPaneVisible: Boolean;
//TOUCH FCurrentContext: string;
function AddTaskbarIcon: Boolean;
function IsProductLoaded: Boolean;
function LoadProduct: Boolean;
procedure UnloadProduct;
procedure ShowVisualKeyboard(Page: TOSKActivePage = apUndefined);
procedure HideVisualKeyboard;
procedure TrayIconUnresponsive(Sender: TObject; RetryCount: Integer; var ShouldCancel: Boolean);
procedure TrayIconMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure ShowBalloon(Value: Integer);
procedure WMUserStart(var Message: TMessage); message WM_USER_Start;
procedure WMUserParameterPass(var Message: TMessage); message WM_USER_ParameterPass;
procedure WMUserSendFontChange(var Message: TMessage); message WM_USER_SendFontChange;
procedure WMUserVisualKeyboardClosed(var Message: TMessage); message WM_USER_VisualKeyboardClosed; // I4243
procedure SetTrayIcon(rp: TRunningProduct; kbd: IKeymanKeyboardInstalled);
procedure TrayIconMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure TrayIconDblClick(Sender: TObject);
//function CreateKeymanMenuItem(Owner: TPopupMenu; rp: TRunningProduct; cmi: IKeymanCustomisationMenuItem): TKeymanMenuItem;
function AppMessage(var Message: TMessage): Boolean;
procedure TestKeymanFunctioning(FunctionType: TTestKeymanFunctionType; RunOnSuccess, ResetCounter: Boolean);
function ShouldTestKeymanFunctioning: Boolean;
procedure TrayIconDebugMessage(Sender: TObject; msg: Integer);
procedure TrayIconBalloonClick(Sender: TObject);
procedure UpdateOSKVisibility;
function GetOption_AutoOpenOSK: Boolean;
function GetOption_AutoSwitchOSKPages: Boolean;
//procedure SnapToolHelp;
procedure RecreateTaskbarIcons;
function StartKeymanEngine: Boolean;
procedure StartKeymanHPX64;
procedure StartKeymanHParm64; // TODO could combine this function
procedure OpenTextEditor;
//function GetCachedKeymanID(hkl: DWORD): DWORD;
procedure ShowLanguageSwitchForm;
procedure LanguageSwitchFormHidden(Sender: TObject);
procedure RegisterControllerWindows; // I3092
procedure TrayIconMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure ProcessProfileChange(CommandAndAtom: DWORD); // I3933 // I3949
function GetActiveKeymanID: Integer; // I3949
procedure RequestCurrentActiveKeyboard(Command: WORD); // I3961
function ProcessWMKeymanControl(Command, WParam: Word; LParam: DWord): LResult; // I3961
//TOUCH procedure HideTouchKeyboard;
//TOUCH procedure ShowTouchKeyboard;
//TOUCH function UseTouchKeyboard: Boolean;
//TOUCH function TouchKeyboardVisible: Boolean;
//TOUCH procedure ProcessContextChange(Atom: DWORD);
procedure PostGlobalKeyboardChange(FActiveKeyboard: TLangSwitchKeyboard); // I4271
function IsControllerWindow(AHandle: THandle): Boolean; // I4731
procedure UpdateFocusInfo; // I4731
procedure UnregisterControllerWindows; // I4731
function IsSysTrayWindow(AHandle: THandle): Boolean;
procedure GetTrayIconHandle; // I4731
protected
procedure DoInterfaceHotkey(Target: Integer);
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure WndProc(var Message: TMessage); override;
procedure CreateWnd; override;
public
frmKeymanMenu: TfrmKeymanMenu;
frmVisualKeyboard: TfrmVisualKeyboard;
frmLanguageSwitch: TfrmLanguageSwitch;
procedure SetLastFocus;
procedure ShowMenu(Sender: TObject; Location: TCustomisationMenuItemLocation; TriggeredByKeyboard: Boolean); // I3961 // I3990
procedure ActivateKeyboard(Keyboard: TLangSwitchKeyboard); // I4326
function ActiveKeyboard: IKeymanKeyboardInstalled;
{ ICustMenuActions_Main }
function VisualKeyboardVisible(Page: TOSKActivePage = apUndefined): Boolean;
procedure MnuKeyboardClick(Sender: TObject);
procedure MnuExitKeyman(Sender: TObject);
procedure MnuOpenKeymanConfiguration(Sender: TObject);
procedure MnuOpenProductHelp(Sender: TObject);
procedure MnuOpenKeyboardHelp(Sender: TObject);
procedure MnuOpenProductAbout(Sender: TObject);
procedure MnuVisualKeyboard(Sender: TObject);
procedure MnuCharacterMap(Sender: TObject);
procedure MnuFontHelper(Sender: TObject);
procedure MnuRunProgram(Sender: TObject);
procedure MnuSelectKeyboard(Sender: TObject); // I4606
procedure MnuError(Sender: TObject);
procedure MnuOpenTextEditor(Sender: TObject);
function GetLangSwitchKeyboard(const name: string; fallback: Boolean): TLangSwitchKeyboard; // I4606
procedure SwitchToFirstWindowsKeyboard; // I1867
property RunningProduct: TRunningProduct read FRunningProduct;
property ActiveKeymanID: Integer read GetActiveKeymanID; // I3949
property ActiveHKL: Integer read FActiveHKL; // I4359
property LangSwitchManager: TLangSwitchManager read FLangSwitchManager; // I3933
property Option_AutoOpenOSK: Boolean read GetOption_AutoOpenOSK;
property Option_AutoSwitchOSKPages: Boolean read GetOption_AutoSwitchOSKPages;
end;
var
frmKeyman7Main: TfrmKeyman7Main;
hProgramMutex: THandle;
wm_keyman_globalswitch, wm_keyman_globalswitch_process, wm_keyman_control, wm_keyman_control_internal, wm_test_keyman_functioning: Cardinal;
FEnableCrashTest: Boolean = False;
const
KMC_StartProduct = 0;
KMC_StopProduct = 1;
KMC_ShowVisualKeyboard = 2;
KMC_HideVisualKeyboard = 3;
KMCI_SELECTKEYBOARD = 3; // I3933
KMCI_SELECTKEYBOARD_TSF = 4; // I3933
KMCI_GETACTIVEKEYBOARD = 5; // I3933
KMCI_SETFOREGROUND = 6; // I3933
KMCI_SELECTKEYBOARD_BACKGROUND = 7; // I4271
KMCI_SELECTKEYBOARD_BACKGROUND_TSF = 8; // I4271
const
MSGFLT_ADD = 1;
const
KEYMANID_NONKEYMAN = -1;
KEYMANID_INVALID: Cardinal = $FFFFFFFD;
const
skHKL = 0; // A windows language has been selected so select the most appropriate Keyman keyboard
skKeymanID = 1; // Select the requested Keyman keyboard
skSelectHKL = 2; // Select the requested Windows language (and therefore the most appropriate Keyman keyboard)
skSelectHKLOnly = 3; // Select the requested Windows language and turn Keyman keyboard off
skTSF = 4; // A TSF TIP
implementation
uses
DebugPaths,
UILanguages,
UfrmOSKCharacterMap,
utilstr,
utilwow64,
Keyman.System.KeymanSentryClient,
KeymanEngineControl,
KeymanControlMessages,
KeymanDesktopShell,
KeymanPaths,
KeymanVersion,
kmint,
Winapi.ShellApi,
BitmapIPicture,
Winapi.ActiveX,
ErrorControlledRegistry,
RegistryKeys,
klog,
GetOsVersion,
System.StrUtils,
System.Win.ComObj, {tlhelp32,}
VistaMessages,
Vcl.AxCtrls,
Vcl.Buttons,
Vcl.ComCtrls,
InterfaceHotkeys,
utilhotkey,
MessageIdentifiers,
MessageIdentifierConsts,
utilsystem,
System.Types,
KeymanHints,
HintConsts;
{$R *.DFM}
function TfrmKeyman7Main.AddTaskbarIcon: Boolean;
var
rp: TRunningProduct;
cust: IKeymanCustomisation;
//icofilename: WideString;
istrm: IStream;
olestrm: TOLEStream;
begin
Result := True;
RegisterClasses([TImage, TButton, TBitBtn, TTabSheet, TPageControl, TBevel, TShape, TLabel]);
if not Assigned(FRunningProduct) then
begin
Result := False;
Exit;
end;
rp := FRunningProduct;
cust := kmint.KeymanCustomisation;
istrm := cust.CustFile['appicon.ico'];
if istrm <> nil then
begin
olestrm := TOLEStream.Create(istrm);
try
// In some situations, launching the app multiple times rapidly can
// cause the icon to be loaded multiple times. Make sure we reset the
// stream position before we try and read.
olestrm.Position := 0;
Application.Icon.LoadFromStream(olestrm);
finally
olestrm.Free;
istrm := nil;
end;
end;
if rp.FTrayIcon.Visible then Exit;
GetTrayIconHandle;
rp.FTrayIcon.OnMessage := TrayIconDebugMessage;
rp.FTrayIcon.OnBalloonClick := TrayIconBalloonClick; // I1248 - Integrate with new welcome
rp.FTrayIcon.OnMouseMove := TrayIconMouseMove;
rp.FTrayIcon.OnMouseDown := TrayIconMouseDown;
rp.FTrayIcon.OnMouseUp := TrayIconMouseUp;
rp.FTrayIcon.OnDblClick := TrayIconDblClick;
rp.FTrayIcon.OnUnresponsive := TrayIconUnresponsive;
//rp.FTrayIcon.OnClick := TrayIconClick;
// rp.FTrayIcon.Icon.ReleaseHandle;
rp.FTrayIcon.Icon.Assign(FTrayIcon);
rp.FTrayIcon.Hint := MsgFromId(SKApplicationTitle);
rp.FTrayIcon.Visible := True;
end;
procedure TfrmKeyman7Main.GetTrayIconHandle;
var
istrm: IStream;
olestrm: TOLEStream;
begin
if Assigned(FTrayIcon) then
Exit;
FTrayIcon := TIcon.Create;
istrm := kmint.KeymanCustomisation.CustFile['trayicon.ico'];
if istrm <> nil then
begin
olestrm := TOLEStream.Create(istrm);
try
olestrm.Seek(0, soFromBeginning);
FTrayIcon.LoadFromStream(olestrm);
finally
olestrm.Free;
end;
end;
end;
procedure TfrmKeyman7Main.FormCreate(Sender: TObject);
begin
TKeymanSentryClient.Breadcrumb('trace', 'TfrmKeyman7Main.FormCreate');
if GetOs in [osVista, osWin7] then // I4576
FGlobalKeyboardChangeManager := TGlobalKeyboardChangeManager.Create; // I4271
frmKeymanMenu := TfrmKeymanMenu.Create(Self);
FLangSwitchManager := TLangSwitchManager.Create; // I3933
//FLastKeymanID := -1;
FLastHKL := GetKeyboardLayout(0);
try
GetDebugManager(Handle);
except
on E:Exception do
KL.LogError('GetDebugManager failed: '+E.Message);
end;
Application.HookMainWindow(AppMessage);
// FRunningProduct := TRunningProduct.Create;
sMsg_TaskbarRestart := RegisterWindowMessage('TaskbarCreated');
ChangeWindowMessageFilter(sMsg_TaskbarRestart, MSGFLT_ADD);
FInputPane := TFrameworkInputPane.Create;
PostMessage(Handle, WM_USER_Start, 0, 0);
end;
{procedure TfrmKeyman7Main.AppDebug(var Msg: TMsg; var Handled: Boolean);
begin
KL.Log('MESSAGE %8.8x %x [%x, %x]', [Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam]);
end;}
procedure TfrmKeyman7Main.FormDestroy(Sender: TObject);
begin
TKeymanSentryClient.Breadcrumb('trace', 'TfrmKeyman7Main.FormDestroy');
if Assigned(FLangSwitchRefreshWatcher) then
begin
FLangSwitchRefreshWatcher.Terminate;
FreeAndNil(FLangSwitchRefreshWatcher);
end;
FreeAndNil(FInputPane);
with TRegistryErrorControlled.Create do // I2890
try
if OpenKey(SRegKey_KeymanOSK_CU, True) then
WriteBool(SRegValue_OSK_ShowVisualKeyboard, VisualKeyboardVisible);
finally
Free;
end;
if Assigned(frmVisualKeyboard) then // I1096 - silent exception when Keyman closes
begin
//frmVisualKeyboard.Hide;
//frmVisualKeyboard.Release;
FreeAndNil(frmVisualKeyboard); // I2692
end;
Application.UnhookMainWindow(AppMessage);
UnregisterControllerWindows; // I4731
if Assigned(FRunningProduct) then
UnloadProduct;
FreeAndNil(frmKeymanMenu); // I3139 - attempt to avoid crash because keyman32.dll is unloaded and then additional messages sent? // I3515
FreeAndNil(frmLanguageSwitch); // I3139 - attempt to avoid crash because keyman32.dll is unloaded and then additional messages sent? // I3515
if kmint.KeymanEngineControl <> nil then
kmint.KeymanEngineControl.ShutdownKeyman32Engine;
FreeAndNil(FLangSwitchManager); // I3933
//Windows.MessageBox(Handle, PChar(IntToStr(kmcom._AddRef)), 'RefCount+1', MB_OK);
kmint.kmcom := nil; // I5132
FreeAndNil(FGlobalKeyboardChangeManager); // I4271
end;
function TfrmKeyman7Main.GetOption_AutoOpenOSK: Boolean;
begin
Result := kmcom.Options.Items['koAutoOpenOSK'].Value;
end;
function TfrmKeyman7Main.GetOption_AutoSwitchOSKPages: Boolean;
begin
Result := kmcom.Options.Items['koAutoSwitchOSKPages'].Value;
end;
function TfrmKeyman7Main.IsControllerWindow(AHandle: THandle): Boolean; // I4731
begin
Result := GetWindowThreadProcessId(AHandle, nil) = GetCurrentThreadId;
end;
function TfrmKeyman7Main.IsSysTrayWindow(AHandle: THandle): Boolean; // I4731
var
buf: array[0..64] of char;
begin
GetClassName(AHandle, buf, 63);
Result := SameText(buf, 'SysTabControl32') or
SameText(buf, 'TrayNotifyWnd') or
SameText(buf, 'Shell_TrayWnd') or
SameText(buf, 'NotifyIconOverflowWindow');
end;
procedure TfrmKeyman7Main.RegisterControllerWindows; // I3092
begin
if kmint.KeymanEngineControl = nil then
begin
TKeymanSentryClient.ReportMessage('RegisterControllerWindows: KeymanEngineControl was unexpectedly nil');
Exit;
end;
kmint.KeymanEngineControl.RegisterMasterController(Application.Handle);
kmint.KeymanEngineControl.RegisterControllerThread(GetCurrentThreadId);
end;
procedure TfrmKeyman7Main.UnregisterControllerWindows; // I4731
begin
if kmint.KeymanEngineControl = nil then
begin
TKeymanSentryClient.ReportMessage('UnregisterControllerWindows: KeymanEngineControl was unexpectedly nil');
Exit;
end;
kmint.KeymanEngineControl.UnregisterMasterController(Application.Handle);
kmint.KeymanEngineControl.UnregisterControllerThread(GetCurrentThreadId);
end;
function TfrmKeyman7Main.LoadProduct: Boolean;
begin
Result := False;
if not Assigned(FRunningProduct) then
begin
if not Assigned(kmcom) and not StartKeymanEngine then Exit; // I1951
if kmint.KeymanEngineControl = nil then
begin
TKeymanSentryClient.ReportMessage('LoadProduct: KeymanEngineControl was unexpectedly nil');
Exit;
end;
kmint.KeymanEngineControl.ResetKeyman32Engine; // pre-initialise // I3092 // I5133
RegisterControllerWindows; // I3092
kmint.KeymanEngineControl.StartKeyman32Engine; // I5133
FRunningProduct := TRunningProduct.Create(Self);
FRunningProduct.Name := MsgFromId(SKApplicationTitle);
AddTaskbarIcon;
CreateUILanguages;
end;
Result := True;
end;
procedure TfrmKeyman7Main.UnloadProduct;
begin
if Assigned(FRunningProduct) then
begin
if not Assigned(kmcom) and not StartKeymanEngine then Exit; // I1951
if kmint.KeymanEngineControl = nil then
begin
TKeymanSentryClient.ReportMessage('UnloadProduct: KeymanEngineControl was unexpectedly nil');
end
else
begin
kmint.KeymanEngineControl.StopKeyman32Engine; // I5133
end;
FreeAndNil(FRunningProduct);
FClosingApp := True;
ReleaseMutex(hProgramMutex); // We are not controlling Keyman any more - another copy of Keyman.exe may start
Close;
end;
end;
{ TRunningProduct }
constructor TRunningProduct.Create(AOwner: TComponent);
begin
inherited Create;
FOwner := AOwner;
FLangSwitchConfiguration := TLangSwitchConfiguration.Create;
FTrayIcon := TKeymanTrayIcon.Create(FOwner);
end;
destructor TRunningProduct.Destroy;
begin
FreeAndNil(FTrayIcon);
FreeAndNil(FLangSwitchConfiguration);
inherited;
end;
function TfrmKeyman7Main.AppMessage(var Message: TMessage): Boolean;
begin
if Message.Msg = WM_ACTIVATEAPP then
begin
{ Stop Delphi from de-topmosting Visual Keyboard!}
with Message do
Result := DefWindowProc(Handle, Msg, WParam, LParam);
Result := True;
end
else if Message.Msg = sMsg_TaskbarRestart then
begin
// I1649 - Increase robustness of icon creation
RecreateTaskbarIcons;
Result := False;
end
else if Message.Msg = WM_FONTCHANGE then // 1374 - Support font helper
begin
if Assigned(frmVisualKeyboard) then
frmVisualKeyboard.Dispatch(Message);
Result := False;
end
else if Message.Msg = wm_keyman_globalswitch then
begin
TDebugLogClient.Instance.WriteMessage('wm_keyman_globalswitch for Application Handle: %x %x', [Message.wParam, Message.lParam]);
case Message.wParam of
skHKL, // A windows language has been selected so select the most appropriate Keyman keyboard
skSelectHKL: // Select the requested Windows language (and therefore the most appropriate Keyman keyboard)
FLastHKL := Message.lParam;
end;
Result := True;
end
else if Message.Msg = wm_keyman_control then
begin
Result := True;
if FClosingApp then
begin
Message.Result := 0;
Exit;
end;
TDebugLogClient.Instance.WriteMessage('wm_keyman_control for Application Handle: %x %x', [Message.wParam, Message.lParam]);
Message.Result := ProcessWMKeymanControl(LoWord(Message.WParam), HiWord(Message.WParam), Message.LParam); // I3961
TDebugLogClient.Instance.WriteMessage('wm_keyman_control-exit for Application Handle: %x %x -> %x', [Message.wParam, Message.lParam, Message.Result]); // I3961
end
else
Result := False;
end;
procedure TfrmKeyman7Main.UpdateFocusInfo; // I4731
var
gti: TGUIThreadInfo;
begin
gti.cbSize := SizeOf(gti);
if not GetGUIThreadInfo(0, gti) then
begin
TDebugLogClient.Instance.WriteLastError('UpdateFocusInfo', 'GetGUIThreadInfo');
Exit;
end;
TDebugLogClient.Instance.WriteMessage('UpdateFocusInfo: last focus=%x last active=%x', [FLastFocus, FLastActive]);
TDebugLogClient.Instance.WriteMessage('UpdateFocusInfo: new focus=%x new active=%x', [gti.hwndFocus, gti.hwndActive]);
if IsControllerWindow(gti.hwndFocus) then // I4731
TDebugLogClient.Instance.WriteMessage('UpdateFocusInfo: new focus is controller window, not updating', [])
else if IsSysTrayWindow(gti.hwndFocus) then // I4731
TDebugLogClient.Instance.WriteMessage('UpdateFocusInfo: new focus is systray window, not updating', [])
else if IsControllerWindow(gti.hwndActive) then // I4731
TDebugLogClient.Instance.WriteMessage('UpdateFocusInfo: new active is controller window, not updating', [])
else if IsSysTrayWindow(gti.hwndActive) then // I4731
TDebugLogClient.Instance.WriteMessage('UpdateFocusInfo: new active is systray window, not updating', [])
else
begin
FLastFocus := gti.hwndFocus;
FLastActive := gti.hwndActive;
end;
end;
function TfrmKeyman7Main.ProcessWMKeymanControl(Command, WParam: Word; LParam: DWord): LResult; // I3961
begin
Result := 0;
case Command of
KMC_GETLASTHKL:
begin
Result := FLastHKL;
end;
KMC_SETFOCUSINFO:
begin
UpdateFocusInfo; // I4731
RequestCurrentActiveKeyboard(PC_UPDATE); // I3961
end;
KMC_INTERFACEHOTKEY:
begin
DoInterfaceHotkey(wParam);
end;
KMC_ONSCREENKEYBOARD:
begin
if LParam <> 0
then ShowVisualKeyboard
else HideVisualKeyboard;
end;
KMC_GETLOADED:
begin
if IsProductLoaded then Result := 1 else Result := 0;
end;
KMC_REFRESH:
begin
if wParam = 1 then
begin
// We're going to keep checking for changes for
// the next few seconds because Windows is in the
// process of updating its keyboard list
tmrRefresh.Tag := 0;
tmrRefresh.Enabled := False;
tmrRefresh.Enabled := True;
end;
if VisualKeyboardVisible then
frmVisualKeyboard.PreRefreshKeyman;
kmcom.Refresh;
FLangSwitchManager.Refresh;
if VisualKeyboardVisible then
begin
frmVisualKeyboard.RefreshKeyman;
frmVisualKeyboard.RefreshSelectedKeyboard; // I2398
end;
FOSKManuallyClosedThisSession := False;
FRunningProduct.FLangSwitchConfiguration.Refresh;
end;
KMC_NOTIFYWELCOME: // I1248 - Redesigned welcome
begin
case wParam of
NW_NOTIFYHANDLE: FWelcomeWindow := LParam;
NW_SENDBALLOON: ShowBalloon(LParam);
end;
end;
KMC_GETLASTFOCUS:
begin
Result := FLastFocus;
end;
KMC_GETLASTACTIVE:
begin
Result := FLastActive;
end;
KMC_PROFILECHANGED: // I3933
begin
// Record the active profile
ProcessProfileChange(LParam);
end;
KMC_HINTRESPONSE:
begin
ResetHintState;
if (TKeymanHint(lParam) = KH_EXITPRODUCT) and (wParam = mrOk) then
UnloadProduct;
end;
//TOUCH KMC_CONTEXT:
//TOUCH begin
//TOUCH if LParam <> 0 then
//TOUCH ProcessContextChange(LParam);
//TOUCH end;
end;
end;
procedure TfrmKeyman7Main.DoInterfaceHotkey(Target: Integer);
begin
if not Assigned(FRunningProduct) then
Exit;
case Target of
khKeymanOff: SwitchToFirstWindowsKeyboard; // I1867
khKeyboardMenu: ShowMenu(FRunningProduct.FTrayIcon, milLeft, True); // I1377 - Show the menu down near the tray when hotkey is pressed // I3990
khVisualKeyboard: MnuVisualKeyboard(nil);
khKeymanConfiguration: TKeymanDesktopShell.RunKeymanConfiguration('-c 0');
khFontHelper: MnuFontHelper(nil);
khCharacterMap: MnuCharacterMap(nil);
khTextEditor: MnuOpenTextEditor(nil);
khLanguageSwitch:
RequestCurrentActiveKeyboard(PC_UPDATE_LANGUAGESWITCH); // I4124
end;
end;
procedure TfrmKeyman7Main.ShowBalloon(Value: Integer);
var
FMessage: WideString;
begin
if not Assigned(FRunningProduct) then Exit;
case Value of
NWB_IDENTIFYICON: FMessage := MsgFromId(SKBalloonClickToSelectKeyboard); // I3010 // I3042
NWB_TUTORIALFINISHED: FMessage := MsgFromId(SKBalloonOSKClosed); // I3010 // I3042
NWB_KEYMANRUNNING:
begin
FMessage := MsgFromId(SKBalloonKeymanIsRunning);
if FMessage = '' then
FMessage := MsgFromId(SKBalloonClickToSelectKeyboard);
end;
else Exit;
end;
if FMessage <> '' then
begin
FRunningProduct.FTrayIcon.BalloonTitle := FRunningProduct.Name; // I3010
FRunningProduct.FTrayIcon.BalloonFlags := bfInfo;
FRunningProduct.FTrayIcon.BalloonTimeout := 30000;
FRunningProduct.FTrayIcon.BalloonHint := FMessage; // I3042
FRunningProduct.FTrayIcon.ShowBalloonHint;
end;
end;
procedure TfrmKeyman7Main.ShowLanguageSwitchForm;
var
Keyboard: TLangSwitchKeyboard;
begin
FLangSwitchManager.Refresh; // ?? is this really needed. It seems in some situations that the refresh
// occurs too soon after keyboard installation and background processing
// has not completed, so this makes sure it's all okay. Long term, if we
// keep this pattern, we should not have a global FLangSwitchManager but
// instead always build an instance of the lang switch manager when the
// form is shown. Performance may be a concern.
if not Assigned(frmLanguageSwitch) then
begin
frmLanguageSwitch := TfrmLanguageSwitch.Create(Self);
frmLanguageSwitch.OnHidden := LanguageSwitchFormHidden;
end;
Keyboard := FLangSwitchManager.ActiveKeyboard; // I3949
if Assigned(Keyboard) then // I3949
begin
///GetDebugManager(Handle).WriteMessage('TfrmKeyman7Main.ShowLanguageSwitchForm: Keyboard '+Keyboard.Caption+' is active before lang switch window shows', []);
frmLanguageSwitch.SelectItem(Keyboard); // I4191
end;
///else
///GetDebugManager(Handle).WriteMessage('TfrmKeyman7Main.ShowLanguageSwitchForm: No keyboard is active before lang switch window shows', []);
frmLanguageSwitch.Show;
SetWindowPos(frmLanguageSwitch.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOMOVE or SWP_NOREDRAW or SWP_NOACTIVATE);
end;
procedure TfrmKeyman7Main.LanguageSwitchFormHidden(Sender: TObject);
var
hwnd: THandle;
kbd: TLangSwitchKeyboard;
begin
hwnd := FLastFocus;
kbd := frmLanguageSwitch.GetSelection;
if Assigned(kbd) then
begin
case kbd.ItemType of
lsitWinKeyboard: TDebugLogClient.Instance.WriteMessage('LanguageSwitchFormHidden: kbd assigned, type = lsitWinKeyboard, value = %x', [(kbd as TLangSwitchKeyboard_WinKeyboard).HKL]);
lsitTIP: TDebugLogClient.Instance.WriteMessage('LanguageSwitchFormHidden: kbd assigned, type = lsitTIP, value = %s', [(kbd as TLangSwitchKeyboard_TIP).Caption]);
else TDebugLogClient.Instance.WriteMessage('LanguageSwitchFormHidden: kbd assigned, type = somthing else', []);
end;
kbd.Activate(hwnd);
end
else
TDebugLogClient.Instance.WriteMessage('LanguageSwitchFormHidden: kbd NOT assigned',[]);
end;
procedure TfrmKeyman7Main.ShowMenu(Sender: TObject; Location: TCustomisationMenuItemLocation; TriggeredByKeyboard: Boolean); // I3990 // I3991
var
hwndTray, hwndSysTray: THandle;
function FindSysTrayLocation: TAlign;
var
r: TRect;
m: TMonitor;
begin
// Note: we don't use SHAppBarMessage because it is re-entrant
// It also does not honour the DPI awareness of the caller
// If we can't find the tray window, assume it is at the bottom of the
// primary screen
if (hwndTray = 0) or not GetWindowRect(hwndTray, r) then
Exit(alBottom);
m := Screen.MonitorFromPoint(r.CenterPoint, mdNearest);
if not Assigned(m) then
// Could not find monitor, perhaps a race condition, see I2693, I2820
// This may never happen as it may have been a VCL bug, but I'm including
// it just for safety given it can return nil.
Exit(alBottom);
if r.Left = m.Left then
begin
if r.Top = m.Top then
begin
if r.Right = m.BoundsRect.Right then
Exit(alTop);
Exit(alLeft);
end;
Exit(alBottom)
end;
Exit(alRight);
end;
function GetMenuAnchorPoint(systraylocation: TAlign): TPoint;
type
TGetDpiForWindow = function(hwnd: HWND): UINT; stdcall;
var
pt: TPoint;
r: TRect;
ident: NOTIFYICONIDENTIFIER;
dpi: Cardinal;
m: TMonitor;
FGetDpiForWindow: TGetDpiForWindow;
begin
// We'll try and get the actual location of our icon
ident.cbSize := SizeOf(ident);
ident.hWnd := FRunningProduct.FTrayIcon.NotificationWindow;
ident.uID := 1;
ident.guidItem := GUID_NULL;
if TriggeredByKeyboard or (Shell_NotifyIconGetRect(ident, r) <> S_OK) then
begin
// User has requested the menu with a hotkey, so show it near the
// SystemNotificationArea, but not actually where our icon is, because
// the icon may be hidden (or if we fail to get the location of our icon,
// for any reason)
if not GetWindowRect(hwndSysTray, r) then
r := TRect.Empty;
end
else
begin
// Shell_NotifyIconGetRect does not honour the DPI awareness of the caller
// and as we do not yet support per-monitor DPI awareness, we have to
// scale the rectangle by its actual DPI to our fixed 96 DPI value to
// get the location of the rect in our 96 DPI world. As a monitor may have
// a non-zero origin, we have to deal with that too!
// Don't use Delphi's GetDpiForWindow declaration as we have to verify
// Windows version first, which cascades into potential updates to the
// manifest, which we don't want to do just now.
FGetDpiForWindow := GetProcAddress(GetModuleHandle(user32), 'GetDpiForWindow');
if Assigned(FGetDpiForWindow) then
begin
dpi := FGetDpiForWindow(hwndTray);
m := Screen.MonitorFromWindow(hwndTray);
if Assigned(m) then
begin
r.Right := MulDiv(r.Right-m.Left, 96, dpi) + m.Left;
r.Bottom := MulDiv(r.Bottom-m.Top, 96, dpi) + m.Top;
r.Left := MulDiv(r.Left-m.Left, 96, dpi) + m.Left;
r.Top := MulDiv(r.Top-m.Top, 96, dpi) + m.Top;
end
else if not GetWindowRect(hwndSysTray, r) then
r := TRect.Empty;
end;
end;
// Align the menu according to the location of the taskbar
case systraylocation of
alTop: begin pt.X := (r.Right + r.Left) div 2; pt.Y := r.Bottom; end;
alBottom: begin pt.X := (r.Right + r.Left) div 2; pt.Y := r.Top; end;
alLeft: begin pt.X := r.Right; pt.Y := r.Top; end;
alRight: begin pt.X := r.Left; pt.Y := r.Top; end;
end;
Result := pt;
end;
var
systraylocation: TAlign;
hwnd: THandle;
pt: TPoint;
begin // I3933
if not Assigned(Sender) and not Assigned(FRunningProduct) then
Exit;
if InMenuLoop > 0 then Exit; // I1082 - Avoid menu nasty flicker with rapid click
hwndTray := FindWindow('Shell_TrayWnd', nil);
hwndSysTray := FindWindowEx(hwndTray, 0, 'TrayNotifyWnd', nil);
BuildCustMenu(kmcom, mnu, Location); // I3933
GetCursorPos(pt);
mnu.Alignment := paCenter;
systraylocation := FindSysTrayLocation;
pt := GetMenuAnchorPoint(systraylocation);
case Location of
milLeft: mnu.TrackButton := tbLeftButton;
milRight: mnu.TrackButton := tbRightButton;
end;
hwnd := FLastFocus;
if not IsDebuggerPresent then
begin
// We don't risk attaching to the debugger thread because that can cause a deadlock
AttachThreadInput(GetCurrentThreadId, GetWindowThreadProcessId(hwnd, nil), TRUE);
Winapi.Windows.SetForegroundWindow(frmKeymanMenu.Handle);
AttachThreadInput(GetCurrentThreadId, GetwindowThreadProcessId(hwnd, nil), FALSE);
end;
frmKeymanMenu.PopupEx(mnu, pt.x, pt.y, systraylocation); // I3990
end;
procedure TfrmKeyman7Main.PostGlobalKeyboardChange(FActiveKeyboard: TLangSwitchKeyboard); // I4271
begin
if Assigned(FActiveKeyboard) and
Assigned(kmcom) and
(GetOs in [osVista, osWin7]) and // I4576 // I4576 // I4576
kmcom.Options.Items['koSwitchLanguageForAllApplications'].Value and
Assigned(FGlobalKeyboardChangeManager) then
FGlobalKeyboardChangeManager.PostChange(FActiveKeyboard);
end;
procedure TfrmKeyman7Main.TrayIconDblClick(Sender: TObject);
begin
SetLastFocus;
if VisualKeyboardVisible
then HideVisualKeyboard
else ShowVisualKeyboard;
end;
procedure TfrmKeyman7Main.TrayIconMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
Exit;
if FTrayButtonDown <> [] then Exit;
UpdateFocusInfo; // I4731
end;
procedure TfrmKeyman7Main.TrayIconMouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
Include(FTrayButtonDown, Button);
end;
procedure TfrmKeyman7Main.TrayIconBalloonClick(Sender: TObject);
var
rp: TRunningProduct;
begin
FTrayButtonDown := [mbLeft,mbRight,mbMiddle]; // balloon click evil flag
rp := FRunningProduct;
if rp = nil then Exit;
TestKeymanFunctioning(tkfPopupMenu, True, True);
end;
procedure TfrmKeyman7Main.TrayIconMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
rp: TRunningProduct;
begin
if Button in FTrayButtonDown then
begin
rp := FRunningProduct;
if rp = nil then Exit;
if FTrayButtonDown = [mbLeft,mbRight,mbMiddle]
then begin FTrayButtonDown := []; end // balloon click
else Exclude(FTrayButtonDown, Button);
if Button = mbLeft
then TestKeymanFunctioning(tkfPopupMenu, True, True)
else ShowMenu(Sender, milRight, False); // I3990
end;
end;
procedure TfrmKeyman7Main.TrayIconUnresponsive(Sender: TObject;
RetryCount: Integer; var ShouldCancel: Boolean);
var
rp: TRunningProduct;
begin
ShouldCancel := False;
KL.Log('TrayIconUnresponsive: %d', [RetryCount]);
if ((RetryCount mod 120) = 0) and (RetryCount > 0) then
begin
KL.Log('TrayIconUnresponsive: asking about cancel');
// I1649 - display this message only after 120 seconds!
// I1148 - display the error code for the end user
if MessageDlg('Keyman Engine is unable to create its taskbar notification icon after 2 minutes. The error code returned was '+
IntToStr((Sender as TKeymanTrayIcon).LastError)+', '+SysErrorMessage((Sender as TKeymanTrayIcon).LastError)+#13#10#13#10+
' Do you want to continue trying? If you click No, Keyman Engine will exit.',
mtWarning, [mbYes, mbNo], 0, mbYes) = mrNo then
begin
ShouldCancel := True;
rp := FRunningProduct;
if rp = nil then Close;
UnloadProduct;
end;
end;
end;
procedure TfrmKeyman7Main.MnuError(Sender: TObject);
begin
ShowMessage('Invalid menu item');
end;
procedure TfrmKeyman7Main.MnuExitKeyman(Sender: TObject);
begin
if not ShowKeymanHint(KH_EXITPRODUCT) then Exit;
UnloadProduct;
end;
procedure TfrmKeyman7Main.SetTrayIcon(rp: TRunningProduct; kbd: IKeymanKeyboardInstalled);
var
cust: IKeymanCustomisation;
bb: IPicture;
h: OLE_HANDLE;
begin
if Assigned(rp) then
begin
bb := kbd.Bitmap;
if Assigned(bb) then
begin
bb.get_Handle(h);
rp.FTrayIcon.Icon.Handle := CopyIcon(h);
end;
if VisualKeyboardVisible then
begin
frmVisualKeyboard.Caption := rp.Name + ' - ' + kbd.Name;
end;
end
else
begin
if VisualKeyboardVisible then
if Assigned(FRunningProduct) then
frmVisualKeyboard.Caption := FRunningProduct.Name
else
frmVisualKeyboard.Caption := SKeymanDesktopName;
end;
if not Assigned(rp) then
begin
FRunningProduct.FTrayIcon.Hint := FRunningProduct.Name;
cust := kmint.KeymanCustomisation as IKeymanCustomisation;
if not Assigned(cust) then
raise Exception.Create('Product '+FRunningProduct.Name+' has unexpectedly disappeared!');
FRunningProduct.FTrayIcon.Icon.Assign(FTrayIcon);
end;
end;
procedure TfrmKeyman7Main.WMUserParameterPass(var Message: TMessage);
begin
case Message.wParam of
KMC_StartProduct: LoadProduct;
KMC_StopProduct: UnloadProduct;
KMC_ShowVisualKeyboard: ShowVisualKeyboard;
KMC_HideVisualKeyboard: HideVisualKeyboard;
end;
end;
procedure TfrmKeyman7Main.WMUserSendFontChange(var Message: TMessage);
begin
PostMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
// I1826 - post instead of send for improved performance and no blocking
end;
procedure TfrmKeyman7Main.WMUserStart(var Message: TMessage);
begin
StartKeymanEngine; // I1951
end;
procedure TfrmKeyman7Main.WMUserVisualKeyboardClosed(var Message: TMessage); // I4243
begin
ShowKeymanHint(KH_CLOSEOSK); // I1232 - Show hint on how to get OSK back when closing
// This only happens if hint is already visibler
FOSKManuallyClosedThisSession := True;
end;
function TfrmKeyman7Main.StartKeymanEngine: Boolean; // I1951
begin
Result := True;
TKeymanSentryClient.Breadcrumb('trace', 'TfrmKeyman7Main.StartKeymanEngine:kmcom='+IfThen(Assigned(kmcom),'assigned','nil'));
if Assigned(kmcom) then Exit;
try
kmcom := CoKeyman.Create;
except
on E:Exception do
begin
TKeymanSentryClient.ReportHandledException(E, 'StartKeymanEngine: Instantiating kmcom');
ShowMessage(E.Message);
Application.Terminate;
Result := False;
Exit;
end;
end;
//Windows.MessageBox(Handle, PChar(IntToStr(kmcom._AddRef)), 'RefCount+1', MB_OK);
try
kmcom.AutoApply := False;
kmcom.Keyboards.Apply;
RegisterControllerWindows; // I3092
except
on E:Exception do
begin
TKeymanSentryClient.ReportHandledException(E, 'StartKeymanEngine: Registering controller windows');
Application.ShowException(E);
Application.Terminate;
Result := False;
Exit;
end;
end;
if not LoadProduct then
begin
TKeymanSentryClient.ReportMessage('StartKeymanEngine: Unable to load product');
ShowMessage('Unable to load product.');
Application.Terminate;
Application.ShowMainForm := False;
Result := False;
Exit;
end;
with TRegistryErrorControlled.Create do // I2890
try
if OpenKeyReadOnly(SRegKey_KeymanOSK_CU) and ValueExists(SRegValue_OSK_ShowVisualKeyboard) and ReadBool(SRegValue_OSK_ShowVisualKeyboard) then
ShowVisualKeyboard;
finally
Free;
end;
if kmint.KeymanEngineControl = nil then
begin
TKeymanSentryClient.ReportMessage('StartKeymanEngine: KeymanEngineControl was unexpectedly nil');
end
else
kmint.KeymanEngineControl.RestartEngine; // I1486
StartKeymanHPX64;
StartKeymanHPARM64
end;
procedure TfrmKeyman7Main.RequestCurrentActiveKeyboard(Command: WORD); // I3961
begin
if FLastFocus <> 0 then
PostMessage(FLastFocus, wm_keyman_control_internal, KMCI_GETACTIVEKEYBOARD, Command); // I3949
end;
procedure TfrmKeyman7Main.WndProc(var Message: TMessage);
begin
inherited;
if Message.Msg = wm_keyman_control then
begin
// While keyman32 will never send messages to this window,
// kmcomapi currently can.
Message.Result := ProcessWMKeymanControl(LoWord(Message.WParam), HiWord(Message.WParam), Message.LParam); // I3961
end
else if Message.Msg = wm_test_keyman_functioning then
begin
if Message.wParam = TKF_RESPONSE then
begin
tmrTestKeymanFunctioning.Enabled := False;
end;
end;
end;
procedure TfrmKeyman7Main.ProcessProfileChange(CommandAndAtom: DWORD); // I3933 // I3949
procedure ProcessHotkeyChange(hkl: DWORD; guidProfile: TGUID);
var
kbd: TLangSwitchKeyboard;
begin
kbd := FLangSwitchManager.FindKeyboard(hkl, guidProfile);
if Assigned(kbd) then
begin
// Handle toggle hotkey
if (kbd = FLangSwitchManager.ActiveKeyboard) and (kmcom.Options['koKeyboardHotkeysAreToggle'].Value) then
kbd := FLangSwitchManager.Languages[0].Keyboards[0];
ActivateKeyboard(kbd);
end;
end;
var
val, FLangID: Integer;
param2, param3: string;
FClsid, FProfileGuid: TGUID;
buftext: array[0..128] of char;
buf: string;
FActiveKeyboard: TLangSwitchKeyboard;
i: Integer;
wCommand: Word;
begin
if GlobalGetAtomName(HiWord(CommandAndAtom), buftext, 128) = 0 then // I3949
Exit; // I4286
wCommand := LoWord(CommandAndAtom);
GlobalDeleteAtom(HiWord(CommandAndAtom)); // I3949
buf := buftext;
//OutputDebugString(PChar('ProcessProfileChange("'+buf+'")'#13#10));
{debug := } StrToken(buf, '|'); // I4285
if not TryStrToInt(StrToken(buf, '|'), FLangID) then Exit;
param2 := StrToken(buf, '|');
if buf = '' then
begin
if not TryStrToInt(param2, val) then
begin
OutputDebugString(PChar('Could not process profile change'#13#10));
Exit;
end;
if (wCommand = PC_UPDATE) or (wCommand = PC_UPDATE_LANGUAGESWITCH) then
begin
FLangSwitchManager.UpdateActive(FLangID, lsitWinKeyboard, val); // I3949
if FLangSwitchManager.ActiveKeyboard = nil then // I4715
begin
FLangSwitchManager.Refresh;
FLangSwitchManager.UpdateActive(FLangID, lsitWinKeyboard, val); // I3949
end;
FActiveHKL := val; // I4359
end
else if wCommand = PC_HOTKEYCHANGE then
begin
ProcessHotkeyChange(val, GUID_NULL);
Exit;
end;
end
else
begin
param3 := StrToken(buf, '|');
try
FClsid := StringToGUID(param2);
FProfileGuid := StringToGUID(param3);
except
on E:EOleException do
begin
OutputDebugString(PChar(E.Message+#13#10));
Exit;
end;
end;
if (wCommand = PC_UPDATE) or (wCommand = PC_UPDATE_LANGUAGESWITCH) then
begin
FLangSwitchManager.UpdateActive(FLangID, lsitTIP, FClsid, FProfileGuid);
if FLangSwitchManager.ActiveKeyboard = nil then // I4715
begin
FLangSwitchManager.Refresh;
FLangSwitchManager.UpdateActive(FLangID, lsitTIP, FClsid, FProfileGuid);
end;
end
else if wCommand = PC_HOTKEYCHANGE then
begin
ProcessHotkeyChange(0, FProfileGuid);
Exit;
end;
end;
// Update the icon and the visual keyboard
FActiveKeyboard := FLangSwitchManager.ActiveKeyboard; // I3949
if not Assigned(FActiveKeyboard) or (FActiveKeyboard.KeymanID = KEYMANID_NONKEYMAN) then
begin
SetTrayIcon(nil, nil);
end
else
begin
for i := 0 to kmcom.Keyboards.Count - 1 do
if (kmcom.Keyboards[i].KeymanID = FActiveKeyboard.KeymanID) then
begin
SetTrayIcon(FRunningProduct, kmcom.Keyboards[i]);
Break;
end;
UpdateOSKVisibility;
end;
PostGlobalKeyboardChange(FActiveKeyboard); // I4271
if Assigned(frmVisualKeyboard) then // I3949
begin
frmVisualKeyboard.RefreshSelectedKeyboard; // I2398
end;
//TOUCH if Assigned(frmTouchKeyboard) then
//TOUCH begin
//TOUCH frmTouchKeyboard.RefreshSelectedKeyboard;
//TOUCH end;
if wCommand = PC_UPDATE_LANGUAGESWITCH then // I4124
begin // TODO fixup product id
//OutputDebugString(PChar('Showing language switch form'#13#10));
ShowLanguageSwitchForm;
end;
end;
function AllowSetForegroundWindow(dwProcessId: DWORD): BOOL; stdcall; external 'user32.dll';
const ASFW_ANY: Cardinal = $FFFFFFFF;
procedure TfrmKeyman7Main.MnuKeyboardClick(Sender: TObject);
begin
if Sender is TKeymanMenuItem then
with Sender as TKeymanMenuItem do
begin
if Assigned(Keyboard) then
ActivateKeyboard(Keyboard); // I4326
end;
end;
procedure TfrmKeyman7Main.ActivateKeyboard(Keyboard: TLangSwitchKeyboard); // I4326
var
hwnd: THandle;
begin
TDebugLogClient.Instance.WriteMessage('ActivateKeyboard: LastFocus=%x LastActive=%x Keyboard=%s KeymanID=%d', [FLastFocus, FLastActive, Keyboard.Caption, Keyboard.KeymanID]); // I4674
hwnd := FLastFocus;
AllowSetForegroundWindow(ASFW_ANY); // I3933
PostMessage(FLastActive, wm_keyman_control_internal, KMCI_SETFOREGROUND, FLastFocus); // I3933
Keyboard.Activate(hwnd); // I3933
end;
procedure TfrmKeyman7Main.SwitchToFirstWindowsKeyboard; // I4326
var
i: Integer;
j: Integer;
begin
for i := 0 to FLangSwitchManager.LanguageCount - 1 do
for j := 0 to FLangSwitchManager.Languages[i].KeyboardCount - 1 do
if FLangSwitchManager.Languages[i].Keyboards[j].KeymanID = KEYMANID_NONKEYMAN then
begin
ActivateKeyboard(FLangSwitchManager.Languages[i].Keyboards[j]);
Exit;
end;
end;
procedure TfrmKeyman7Main.SetLastFocus;
var
tid: DWORD;
begin
tid := GetWindowThreadProcessId(FLastFocus, nil);
if tid = 0 then
begin
TDebugLogClient.Instance.WriteLastError('SetLastFocus', 'GetWindowThreadProcessId');
Exit;
end;
if not AttachThreadInput(tid, GetCurrentThreadId, TRUE) then
begin
TDebugLogClient.Instance.WriteLastError('SetLastFocus', 'AttachThreadInput:1');
Exit;
end;
//Windows.SetForegroundWindow(FLastFocus); //FLastActive);
if Winapi.Windows.SetFocus(FLastFocus) = 0 then
TDebugLogClient.Instance.WriteLastError('SetLastFocus', 'SetFocus');
if not AttachThreadInput(tid, GetCurrentThreadId, FALSE) then
begin
TDebugLogClient.Instance.WriteLastError('SetLastFocus', 'AttachThreadInput:2');
Exit;
end;
end;
function TfrmKeyman7Main.IsProductLoaded: Boolean;
begin
Result := Assigned(FRunningProduct);
end;
procedure TfrmKeyman7Main.RecreateTaskbarIcons;
begin
if Assigned(FRunningProduct) then
begin
FRunningProduct.FTrayIcon.Visible := False;
FRunningProduct.FTrayIcon.Visible := True;
end;
end;
procedure TfrmKeyman7Main.MnuOpenKeyboardHelp(Sender: TObject);
var
kbd: IKeymanKeyboardInstalled;
begin
kbd := ActiveKeyboard;
if not Assigned(kbd) then Exit;
kmcom.Control.ShowKeyboardWelcome(kbd);
end;
function TfrmKeyman7Main.ActiveKeyboard: IKeymanKeyboardInstalled;
var
i: Integer;
FActiveKeyboard: TLangSwitchKeyboard;
begin
FActiveKeyboard := FLangSwitchManager.ActiveKeyboard; // I3949
if not Assigned(FActiveKeyboard) or (FActiveKeyboard.KeymanID = KEYMANID_NONKEYMAN) then
Exit(nil); // I3949
for i := 0 to kmcom.Keyboards.Count - 1 do
if kmcom.Keyboards[i].KeymanID = FActiveKeyboard.KeymanID then
Exit(kmcom.Keyboards[i]); // I3949
end;
procedure TfrmKeyman7Main.MnuOpenKeymanConfiguration(Sender: TObject);
begin
TKeymanDesktopShell.RunKeymanConfiguration('-c');
end;
procedure TfrmKeyman7Main.MnuOpenProductAbout(Sender: TObject);
begin
TKeymanDesktopShell.RunKeymanConfiguration('-a');
end;
procedure TfrmKeyman7Main.MnuOpenProductHelp(Sender: TObject);
begin
// Ctrl+Shift+Help command, with command line -sentry-client-test-exception
if FEnableCrashTest and (GetKeyState(VK_CONTROL) < 0) and (GetKeyState(VK_SHIFT) < 0) then
TKeymanSentryClient.Validate(True);
TKeymanDesktopShell.OpenHelpJump('index', Self.ActiveKeyboard);
end;
procedure TfrmKeyman7Main.MnuRunProgram(Sender: TObject); // I4606
var
CmdLine: string;
errmsg: string;
begin
if Sender is TKeymanMenuItem then
CmdLine := ((Sender as TKeymanMenuItem).CustomisationMenuItem).CmdLine
else if Sender is TToolButton then
CmdLine := (Sender as TKeymanToolButton).CmdLine;
if not ExecuteProgram(CmdLine, TKeymanPaths.KeymanDesktopInstallDir, errmsg) then
ShowMessage(errmsg);
// todo: complete mnurunprogram
end;
procedure TfrmKeyman7Main.MnuSelectKeyboard(Sender: TObject); // I4606
var
kbd: TLangSwitchKeyboard;
begin
if Sender is TKeymanMenuItem then
kbd := (Sender as TKeymanMenuItem).Keyboard
else if Sender is TToolButton then
kbd := GetLangSwitchKeyboard((Sender as TKeymanToolButton).KeyboardName, True)
else
kbd := nil;
if Assigned(kbd) then
ActivateKeyboard(kbd);
end;
function TfrmKeyman7Main.GetLangSwitchKeyboard(const name: string; fallback: Boolean): TLangSwitchKeyboard; // I4606
var
i: Integer;
j: Integer;
begin
Result := nil;
for i := 0 to FLangSwitchManager.LanguageCount - 1 do
begin
for j := 0 to FLangSwitchManager.Languages[i].KeyboardCount - 1 do
begin
if SameText(FLangSwitchManager.Languages[i].Keyboards[j].Caption, name) then
Exit(FLangSwitchManager.Languages[i].Keyboards[j])
else if (Result = nil) and fallback then
Result := FLangSwitchManager.Languages[i].Keyboards[j];
end;
end;
end;
procedure TfrmKeyman7Main.MnuVisualKeyboard(Sender: TObject);
begin
SetLastFocus; // I1289 - Focus not returned to active app when OSK opened
//TOUCH if UseTouchKeyboard then
//TOUCH begin
//TOUCH if TouchKeyboardVisible
//TOUCH then HideTouchKeyboard
//TOUCH else ShowTouchKeyboard;
//TOUCH end
//TOUCH else
//TOUCH begin
if VisualKeyboardVisible(apKeyboard)
then HideVisualKeyboard
else ShowVisualKeyboard(apKeyboard);
//TOUCH end;
end;
procedure TfrmKeyman7Main.MnuFontHelper(Sender: TObject);
begin
SetLastFocus; // I1289 - Focus not returned to active app when OSK opened
if VisualKeyboardVisible(apFontHelper)
then HideVisualKeyboard
else ShowVisualKeyboard(apFontHelper);
end;
procedure TfrmKeyman7Main.MnuCharacterMap(Sender: TObject);
begin
SetLastFocus; // I1289 - Focus not returned to active app when OSK opened
if VisualKeyboardVisible(apCharacterMap)
then HideVisualKeyboard
else ShowVisualKeyboard(apCharacterMap);
end;
procedure TfrmKeyman7Main.MnuOpenTextEditor(Sender: TObject);
begin
OpenTextEditor;
end;
procedure TfrmKeyman7Main.OpenTextEditor;
begin
TKeymanDesktopShell.RunKeymanConfiguration('-t');
end;
procedure TfrmKeyman7Main.Notification(AComponent: TComponent; Operation: TOperation);
begin
if (AComponent = frmVisualKeyboard) and (Operation = opRemove) then
frmVisualKeyboard := nil;
inherited Notification(AComponent, Operation);
end;
procedure TfrmKeyman7Main.HideVisualKeyboard;
begin
if not Assigned(kmcom) and not StartKeymanEngine then Exit;
//TOUCH if UseTouchKeyboard then
//TOUCH begin
//TOUCH HideTouchKeyboard;
//TOUCH Exit;
//TOUCH end;
if Assigned(frmVisualKeyboard) then // I1274 - Avoid crash when closing OSK when already closed!
frmVisualKeyboard.Release;
end;
procedure TfrmKeyman7Main.ShowVisualKeyboard(Page: TOSKActivePage);
begin
if not Assigned(kmcom) and not StartKeymanEngine then Exit;
//TOUCH if UseTouchKeyboard then
//TOUCH begin
//TOUCH ShowTouchKeyboard;
//TOUCH Exit;
//TOUCH end;
//TOUCH HideTouchKeyboard;
if not Assigned(frmVisualKeyboard) then // I1274 - Related, don't recreate OSK if already exists
begin
frmVisualKeyboard := TfrmVisualKeyboard.Create(nil);
frmVisualKeyboard.FreeNotification(Self);
frmVisualKeyboard.Show;
frmVisualKeyboard.ActivePage := Page; // I2287 // I4225
end
else if Page <> apUndefined then
frmVisualKeyboard.ActivePage := Page;
end;
function TfrmKeyman7Main.VisualKeyboardVisible(Page: TOSKActivePage): Boolean;
begin
Result := Assigned(frmVisualKeyboard) and ((Page = apUndefined) or (frmVisualKeyboard.ActivePage = Page));
end;
//TOUCH function TfrmKeyman7Main.TouchKeyboardVisible: Boolean;
//TOUCH begin
//TOUCH Result := Assigned(frmTouchKeyboard);
//TOUCH end;
procedure TfrmKeyman7Main.TestKeymanFunctioning(FunctionType: TTestKeymanFunctionType; RunOnSuccess, ResetCounter: Boolean);
begin
tmrTestKeymanFunctioning.Enabled := False;
if FunctionType = tkfPopupMenu then
begin
// We are only going to do it on SetActiveKeyboard for now
ShowMenu(FRunningProduct.FTrayIcon, milLeft, False); // I3990
Exit;
end;
FTestKeymanFunctioning.FunctionType := FunctionType;
FTestKeymanFunctioning.RunOnSuccess := RunOnSuccess;
if ResetCounter then FTestKeymanFunctioning.Count := 0;
if ShouldTestKeymanFunctioning then
begin
PostMessage(Handle, wm_test_keyman_functioning, TKF_PING, 0);
tmrTestKeymanFunctioning.Enabled := True;
end
else if RunOnSuccess then
begin
case FunctionType of
tkfPopupMenu: ShowMenu(FRunningProduct.FTrayIcon, milLeft, False); // I3990
// we know this is from a mouse click as kbd method tells us we know Keyman is working
end;
end;
end;
//TOUCH procedure TfrmKeyman7Main.ShowTouchKeyboard;
//TOUCH var
//TOUCH FProduct: IKeymanProduct;
//TOUCH begin
//TOUCH if not Assigned(kmcom) and not StartKeymanEngine then Exit;
//TOUCH
//TOUCH if Assigned(frmVisualKeyboard) then // I1274 - Avoid crash when closing OSK when already closed!
//TOUCH frmVisualKeyboard.Release;
//TOUCH
//TOUCH if not Assigned(frmTouchKeyboard) then
//TOUCH begin
//TOUCH FProduct := ProductFromMenuItem(nil);
//TOUCH if not Assigned(FProduct) then Exit;
//TOUCH
//TOUCH frmTouchKeyboard := TfrmTouchKeyboard.Create(nil, FProduct);
//TOUCH frmTouchKeyboard.FreeNotification(Self);
//TOUCH frmTouchKeyboard.SetContext(FCurrentContext);
//TOUCH //frmTouchKeyboard.Show;
//TOUCH end;
//TOUCH end;
//TOUCH procedure TfrmKeyman7Main.HideTouchKeyboard;
//TOUCH begin
//TOUCH if not Assigned(kmcom) and not StartKeymanEngine then Exit;
//TOUCH
//TOUCH FreeAndNil(frmTouchKeyboard);
//TOUCH end;
//TOUCH function TfrmKeyman7Main.UseTouchKeyboard: Boolean;
//TOUCH begin
//TOUCH if not Assigned(kmcom)
//TOUCH then Result := False
//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;
if kmint.KeymanEngineControl <> nil then
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.tmrBackgroundUpdateCheckTimer(Sender: TObject);
begin
with TRegistryErrorControlled.Create do // I2890
try
if OpenKeyReadOnly(SRegKey_KeymanEngine_CU) then
begin
if ValueExists(SRegValue_CheckForUpdates) and not ReadBool(SRegValue_CheckForUpdates) then Exit;
if ValueExists(SRegValue_LastUpdateCheckTime) and (Now - ReadDateTime(SRegValue_LastUpdateCheckTime) < 7) then Exit;
TKeymanDesktopShell.RunKeymanConfiguration('-buc');
end;
finally
Free;
end;
end;
procedure TfrmKeyman7Main.tmrRefreshTimer(Sender: TObject);
const
SECONDS_TO_CHECK = 5;
begin
if tmrRefresh.Tag > SECONDS_TO_CHECK * (1000 div Integer(tmrRefresh.Interval)) then
begin
tmrRefresh.Enabled := False;
tmrRefresh.Tag := 0;
end
else
tmrRefresh.Tag := tmrRefresh.Tag + 1;
PostMessage(Application.Handle, wm_keyman_control, KMC_REFRESH, 0);
end;
procedure TfrmKeyman7Main.tmrTestKeymanFunctioningTimer(Sender: TObject);
begin
tmrTestKeymanFunctioning.Enabled := False;
if FTestKeymanFunctioning.Count < 3 then
begin
if kmint.KeymanEngineControl <> nil then
kmint.KeymanEngineControl.RestartEngine;
Inc(FTestKeymanFunctioning.Count);
TestKeymanFunctioning(FTestKeymanFunctioning.FunctionType, FTestKeymanFunctioning.RunOnSuccess, False);
end
else
begin
if Winapi.Windows.MessageBox(Handle,
'Keyman Engine does not appear to be receiving messages from Windows normally. You may have a conflicting application. '+
'Do you want to diagnose this issue now?',
PChar('Keyman Engine'), MB_ICONHAND or MB_TOPMOST or MB_OKCANCEL) = IDCANCEL then Exit;
kmcom.Control.OpenDiagnostics;
end;
end;
function TfrmKeyman7Main.ShouldTestKeymanFunctioning: Boolean;
begin
Result := kmcom.Options.Items['koTestKeymanFunctioning'].Value;
end;
procedure TfrmKeyman7Main.TrayIconDebugMessage(Sender: TObject; msg: Integer);
var
Point: TPoint;
begin
if FWelcomeWindow <> 0 then
begin
GetCursorPos(Point);
PostMessage(FWelcomeWindow, wm_keyman_control, msg, Integer(PointToSmallPoint(Point)));
end;
end;
function TfrmKeyman7Main.GetActiveKeymanID: Integer; // I3949
var
FActiveKeyboard: TLangSwitchKeyboard;
begin
FActiveKeyboard := FLangSwitchManager.ActiveKeyboard;
if not Assigned(FActiveKeyboard) then
Exit(KEYMANID_NONKEYMAN);
Exit(FActiveKeyboard.KeymanID);
end;
procedure TfrmKeyman7Main.UpdateOSKVisibility; // I1288 - Show OSK when keyboard is selected
begin
if not Option_AutoOpenOSK or FOSKManuallyClosedThisSession then
Exit;
if FInUpdateOSKVisibility then Exit;
FInUpdateOSKVisibility := True;
if (ActiveKeymanID <> KEYMANID_NONKEYMAN) and not Assigned(frmVisualKeyboard) then // I3949
ShowVisualKeyboard;
FInUpdateOSKVisibility := False;
end;
type
TLangSwitchRefreshWatcher = class(TThread)
private
FOwnerHandle: THandle;
hTerminatedEvent: THandle;
protected
procedure Execute; override;
procedure TerminatedSet; override;
public
constructor Create(AOwnerHandle: THandle); reintroduce;
destructor Destroy; override;
end;
procedure TfrmKeyman7Main.CreateWnd;
begin
inherited;
if Assigned(FLangSwitchRefreshWatcher) then
begin
FLangSwitchRefreshWatcher.Terminate;
FreeAndNil(FLangSwitchRefreshWatcher);
end;
FLangSwitchRefreshWatcher := TLangSwitchRefreshWatcher.Create(Application.Handle);
FLangSwitchRefreshWatcher.Start;
end;
procedure TfrmKeyman7Main.StartKeymanHPX64;
var
cmd, dir, params: string;
sei: TShellExecuteInfoW;
begin
if not IsWow64 then Exit; // I4374
try
// TODO: use TKeymanPaths to find keymanx64?
dir := ExtractFilePath(ParamStr(0));
cmd := dir + 'keymanhp.x64.exe';
params := Format('%d %d', [GetCurrentProcessId, Application.Handle]);
if not FileExists(cmd) then
// We'll get notification of the issue but it won't
// crash the process
raise Exception.Create(cmd+' could not be found');
FillChar(sei, SizeOf(sei), 0);
sei.cbSize := SizeOf(sei);
sei.Wnd := Handle;
sei.lpVerb := 'open';
sei.lpFile := PWideChar(cmd);
sei.lpParameters := PChar(params);
sei.lpDirectory := PWideChar(dir);
sei.nShow := SW_SHOW;
if not ShellExecuteExW(@sei) then
RaiseLastOSError;
except
on E:Exception do
begin
// We're going to handle any exceptions here but we'd like to know that
// they happened
TKeymanSentryClient.ReportHandledException(E, 'Error starting keymanhp.x64', True);
end;
end;
end;
procedure TfrmKeyman7Main.StartKeymanHPARM64;
var
cmd, dir, params: string;
sei: TShellExecuteInfoW;
begin
if not IsNativeMachineArm64 then Exit;
try
// TODO: use TKeymanPaths to find keymanx64?
dir := ExtractFilePath(ParamStr(0));
cmd := dir + 'keymanhp.arm64.exe';
params := Format('%d %d', [GetCurrentProcessId, Application.Handle]);
if not FileExists(cmd) then
// We'll get notification of the issue but it won't
// crash the process
raise Exception.Create(cmd+' could not be found');
FillChar(sei, SizeOf(sei), 0);
sei.cbSize := SizeOf(sei);
sei.Wnd := Handle;
sei.lpVerb := 'open';
sei.lpFile := PWideChar(cmd);
sei.lpParameters := PChar(params);
sei.lpDirectory := PWideChar(dir);
sei.nShow := SW_SHOW;
if not ShellExecuteExW(@sei) then
RaiseLastOSError;
except
on E:Exception do
begin
// We're going to handle any exceptions here but we'd like to know that
// they happened
TKeymanSentryClient.ReportHandledException(E, 'Error starting keymanhp.x64', True);
end;
end;
end;
function KeymanHotkeyModifiersToWindowsHotkeyModifiers(v: KeymanHotkeyModifiers): Integer;
begin
Result := 0;
if (v and HK_SHIFT) = HK_SHIFT then Result := Result or MOD_SHIFT;
if (v and HK_CTRL) = HK_CTRL then Result := Result or MOD_CONTROL;
if (v and HK_ALT) = HK_ALT then Result := Result or MOD_ALT;
end;
{ TLangSwitchRefreshWatcher }
constructor TLangSwitchRefreshWatcher.Create(AOwnerHandle: THandle);
begin
inherited Create(True);
FOwnerHandle := AOwnerHandle;
hTerminatedEvent := CreateEvent(nil, True, False, nil);
end;
destructor TLangSwitchRefreshWatcher.Destroy;
begin
CloseHandle(hTerminatedEvent);
inherited Destroy;
end;
procedure TLangSwitchRefreshWatcher.Execute;
var
hk: HKEY;
hEvent: THandle;
hEvents: array[0..1] of THandle;
const
// We don't really know how long the changes are going to take
// so we'll wait 500 msec and then ask the main thread to reload
// the changes from the registry
ARBITRARY_WAIT_FOR_CHANGES = 1000;
begin
if RegOpenKeyEx(HKEY_CURRENT_USER, PChar(SRegKey_ControlPanelInternationalUserProfile), 0,
KEY_NOTIFY, hk) <> ERROR_SUCCESS then
Exit;
try
hEvent := CreateEvent(nil, True, False, nil);
if hEvent = 0 then
Exit;
try
// Watch the registry key for any changes
if RegNotifyChangeKeyValue(hk, True, REG_NOTIFY_CHANGE_NAME or
REG_NOTIFY_CHANGE_LAST_SET, hEvent, True) <> ERROR_SUCCESS then
Exit;
repeat
// Wait for either the registry to change or for us to be terminated
hEvents[0] := hEvent;
hEvents[1] := hTerminatedEvent;
if WaitForMultipleObjects(2, @hEvents[0], False, INFINITE) <> WAIT_OBJECT_0 then
Exit;
ResetEvent(hEvent);
// Start watching for changes again before requesting a refresh, so we
// don't miss any additional changes that come through
if RegNotifyChangeKeyValue(hk, True, REG_NOTIFY_CHANGE_NAME or
REG_NOTIFY_CHANGE_LAST_SET, hEvent, True) <> ERROR_SUCCESS then
Exit;
// Finally, tell the master controller to get ready to process the refresh
PostMessage(FOwnerHandle, wm_keyman_control, MAKELONG(KMC_REFRESH, 1), 0);
until False;
finally
CloseHandle(hEvent);
end;
finally
RegCloseKey(hk);
end;
end;
procedure TLangSwitchRefreshWatcher.TerminatedSet;
begin
inherited;
SetEvent(hTerminatedEvent);
end;
initialization
wm_test_keyman_functioning := RegisterWindowMessage('wm_test_keyman_functioning');
wm_keyman_globalswitch := RegisterWindowMessage('WM_KEYMAN_GLOBALSWITCH');
wm_keyman_globalswitch_process := RegisterWindowMessage('WM_KEYMAN_GLOBALSWITCH_PROCESS');
wm_keyman_control := RegisterWindowMessage('WM_KEYMAN_CONTROL');
wm_keyman_control_internal := RegisterWindowMessage('WM_KEYMAN_CONTROL_INTERNAL'); // I3933
ChangeWindowMessageFilter(wm_keyman_control, MSGFLT_ADD);
ChangeWindowMessageFilter(wm_keyman_globalswitch, MSGFLT_ADD);
ChangeWindowMessageFilter(wm_keyman_globalswitch_process, MSGFLT_ADD);
ChangeWindowMessageFilter(wm_keyman_control_internal, MSGFLT_ADD); // I3933
end.