From 165c686cb4bdb1fde98de46d9499a1c710096eee Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 15 Oct 2025 09:39:06 +0200 Subject: [PATCH] refactor(windows): consolidate Engine install path helper functions WIP; I need to do more work on this Test-bot: skip Build-bot: skip build:windows --- common/windows/delphi/general/KeymanPaths.pas | 37 +++++++++++++++++-- windows/src/engine/keyman/UfrmKeyman7Main.pas | 16 +++----- .../kmcomapi/com/system/keymancontrol.pas | 28 +------------- .../processes/package/kpinstallpackage.pas | 2 +- .../src/engine/kmcomapi/util/utilkeyman.pas | 37 ++++--------------- .../delphi/general/Keyman.System.Settings.pas | 14 +------ 6 files changed, 50 insertions(+), 84 deletions(-) diff --git a/common/windows/delphi/general/KeymanPaths.pas b/common/windows/delphi/general/KeymanPaths.pas index 4086be32ce..d0d08bf58e 100644 --- a/common/windows/delphi/general/KeymanPaths.pas +++ b/common/windows/delphi/general/KeymanPaths.pas @@ -37,7 +37,8 @@ type class function KeymanHelpPath(const HelpFile: string): string; static; class function KeymanUpdateCachePath(const filename: string = ''): string; static; class function KeymanDesktopInstallPath(const filename: string = ''): string; static; - class function KeymanEngineInstallPath(const filename: string = ''): string; static; + class function KeymanEngineInstallPath(const filename: string = ''; RaiseOnNotFound: Boolean = True): string; static; + class function KeymanEngineExecutablePath(const Executable: string): string; static; class function KeymanDesktopInstallDir: string; static; class function KeymanEngineInstallDir: string; static; class function KeyboardsInstallPath(const filename: string = ''): string; static; @@ -207,7 +208,7 @@ begin Result := ExtractFileDir(KeymanEngineInstallPath); end; -class function TKeymanPaths.KeymanEngineInstallPath(const filename: string = ''): string; +class function TKeymanPaths.KeymanEngineInstallPath(const filename: string = ''; RaiseOnNotFound: Boolean): string; begin with TRegistry.Create do // I2890 try @@ -221,7 +222,11 @@ begin Result := GetDebugPath('KeymanEngine', Result); if Result = '' then - raise EKeymanPath.Create('Unable to find the Keyman Engine directory. You should reinstall the product.'); + begin + if RaiseOnNotFound + then raise EKeymanPath.Create('Unable to find the Keyman Engine directory. You should reinstall the product.') + else Exit; + end; Result := IncludeTrailingPathDelimiter(Result) + filename; end; @@ -341,7 +346,7 @@ begin begin Result := GetFolderPath(CSIDL_COMMON_APPDATA); if Result = '' then - Result := TKeymanPaths.KeymanEngineInstallPath(TKeymanPaths.S_FallbackKeyboardPath) + Result := TKeymanPaths.KeymanEngineInstallPath(TKeymanPaths.S_FallbackKeyboardPath, False) else Result := Result + SFolderKeymanKeyboard; end; @@ -430,6 +435,30 @@ begin Result := ''; end; +class function TKeymanPaths.KeymanEngineExecutablePath(const Executable: string): string; +var + keyman_root: string; +begin + // On developer machines, if we are running within the source repo, then use + // those paths + if TKeymanPaths.RunningFromSource(keyman_root) then + begin + // Source repo, bin folder + Result := keyman_root + 'windows\bin\engine\' + Executable; + if FileExists(Result) then Exit; + end; + + // For a standard installation, we should find the install path + Result := TKeymanPaths.KeymanEngineInstallPath(Executable, False); + if FileExists(Result) then Exit; + + // Fall back to same folder as executable + Result := ExtractFilePath(ParamStr(0)) + Executable; + if FileExists(Result) then Exit; + + raise EKeymanPath.Create('Could not find Keyman Engine executable '+Executable); +end; + class function TKeymanPaths.KeymanUpdateCachePath(const filename: string): string; begin Result := GetFolderPath(CSIDL_LOCAL_APPDATA) + S_KeymanAppData_UpdateCache + filename; diff --git a/windows/src/engine/keyman/UfrmKeyman7Main.pas b/windows/src/engine/keyman/UfrmKeyman7Main.pas index 7f948d0518..f93eba8501 100644 --- a/windows/src/engine/keyman/UfrmKeyman7Main.pas +++ b/windows/src/engine/keyman/UfrmKeyman7Main.pas @@ -1962,23 +1962,17 @@ begin if not IsWow64 then Exit; // I4374 try - // TODO: use TKeymanPaths to find keymanx64? - dir := ExtractFilePath(ParamStr(0)); - cmd := dir + 'keymanx64.exe'; + cmd := TKeymanPaths.KeymanEngineExecutablePath('keymanx64.exe'); + dir := ExtractFileDir(cmd); 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.lpFile := PChar(cmd); sei.lpParameters := PChar(params); - sei.lpDirectory := PWideChar(dir); + sei.lpDirectory := PChar(dir); sei.nShow := SW_SHOW; if not ShellExecuteExW(@sei) then @@ -1989,6 +1983,8 @@ begin // We're going to handle any exceptions here but we'd like to know that // they happened TKeymanSentryClient.ReportHandledException(E, 'Error starting keymanx64', True); + // TODO: it might be important to tell the user that Keyman is not running + // normally - x64 processes will not receive mapping end; end; end; diff --git a/windows/src/engine/kmcomapi/com/system/keymancontrol.pas b/windows/src/engine/kmcomapi/com/system/keymancontrol.pas index 90bab26746..66acb462b9 100644 --- a/windows/src/engine/kmcomapi/com/system/keymancontrol.pas +++ b/windows/src/engine/kmcomapi/com/system/keymancontrol.pas @@ -688,32 +688,6 @@ end; procedure TKeymanControl.LoadKeyman32; - function GetKeymanInstallPath: string; // I3598 - var - buf: array[0..260] of char; - RootPath: string; - begin - RootPath := ''; - with TRegistryErrorControlled.Create do // I2890 - try - RootKey := HKEY_LOCAL_MACHINE; - if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_RootPath) then - RootPath := ReadString(SRegValue_RootPath); - finally - Free; - end; - - RootPath := GetDebugPath('Debug_Keyman32Path', RootPath); // I2825 - - if RootPath = '' then - begin - GetModuleFileName(HInstance, buf, 260); - RootPath := ExtractFilePath(buf); - end; - - Result := IncludeTrailingPathDelimiter(RootPath); - end; - function ProcAddr(const Name: string): FARPROC; begin Result := GetProcAddress(hlibKeyman32, PChar(Name)); @@ -726,7 +700,7 @@ begin if hlibKeyman32 = 0 then begin - s := GetKeymanInstallPath+SKeyman32Filename; // I3598 + s := TKeymanPaths.KeymanEngineInstallPath(SKeyman32Filename); // I3598 if not FileExists(s) then ErrorFmt(KMN_E_KeymanControl_CannotLoadKeyman32, VarArrayOf([Integer(GetLastError), 'Failed to find '+SKeyman32Filename+' at "'+s+'", '+SysErrorMessage(GetLastError)])); diff --git a/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas b/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas index 98edfbf3fb..553dfcceba 100644 --- a/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas +++ b/windows/src/engine/kmcomapi/processes/package/kpinstallpackage.pas @@ -322,7 +322,7 @@ begin prog := Trim(inf.Options.ExecuteProgram); if prog <> '' then begin - prog := StringReplace(prog, '$keyman', ExtractFileDir(GetKeymanInstallPath), [rfIgnoreCase, rfReplaceAll]); + prog := StringReplace(prog, '$keyman', TKeymanPaths.KeymanEngineInstallDir, [rfIgnoreCase, rfReplaceAll]); if not ExecuteProgram(prog, PChar(ExtractFileDir(dest)), errmsg) then WarnFmt(KMN_W_InstallPackage_CannotRunExternalProgram, VarArrayOf([prog, errmsg])); end; diff --git a/windows/src/engine/kmcomapi/util/utilkeyman.pas b/windows/src/engine/kmcomapi/util/utilkeyman.pas index 62e87acaa6..7fdf5c7e63 100644 --- a/windows/src/engine/kmcomapi/util/utilkeyman.pas +++ b/windows/src/engine/kmcomapi/util/utilkeyman.pas @@ -1,18 +1,18 @@ (* Name: utilkeyman Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 20 Jun 2006 Modified Date: 13 Mar 2015 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 20 Jun 2006 - mcdurdin - Initial version 01 Jun 2009 - mcdurdin - I2001 - use current user not local machine when testing root keyboard path 03 May 2011 - mcdurdin - I2890 - Record diagnostic data when encountering registry errors @@ -54,8 +54,6 @@ function PackageInstalled(const PackageName: string; var FIsAdmin: Boolean): Boo function GetKeyboardIconFileName(const KeyboardFileName: string): string; // I3599 -function GetKeymanInstallPath: string; - function GetDefaultHKL: HKL; // I3581 // I3619 // I3619 var @@ -275,27 +273,6 @@ begin end; end; -function GetKeymanInstallPath: string; -var - RootPath: string; -begin - RootPath := ExtractFilePath(ParamStr(0)); - with TRegistryErrorControlled.Create do // I2890 - try - RootKey := HKEY_LOCAL_MACHINE; - if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) then - if ValueExists(SRegValue_RootPath) then - RootPath := ReadString(SRegValue_RootPath); - finally - Free; - end; - Result := IncludeTrailingPathDelimiter(RootPath); - - if not FileExists(Result + 'keyman32.dll') then - raise EKeymanNotInstalled.Create( 'The executable keyman32.dll could not '+ - 'be found. You should reinstall.'); -end; - function GetKeyboardIconFileName(const KeyboardFileName: string): string; // I3599 begin Result := ChangeFileExt(KeyboardFileName, '.kmx.ico'); // I3581 diff --git a/windows/src/global/delphi/general/Keyman.System.Settings.pas b/windows/src/global/delphi/general/Keyman.System.Settings.pas index d8acd9d25a..772dc3ee32 100644 --- a/windows/src/global/delphi/general/Keyman.System.Settings.pas +++ b/windows/src/global/delphi/general/Keyman.System.Settings.pas @@ -95,7 +95,7 @@ const ValueType: kstInteger ); - BaseKeymanSettings: array[0..31] of TKeymanSettingBase = ( + BaseKeymanSettings: array[0..30] of TKeymanSettingBase = ( // TIKE:UTikeDebugMode.TikeDebugMode ( @@ -161,16 +161,6 @@ const DefaultDesc: '%ProgramData%\Keyman\Keyman Engine\Keyboard' ), - // kmcomapi:keymancontrol.TKeymanControl.LoadKeyman32.GetKeymanInstallPath - // keyman32_int.GetKeymanInstallPath - ( - ID: 'development.paths.keyman32'; - Name: 'Debug_Keyman32Path'; - RootKey: HKCU; - Key: SRegKey_KeymanDebug_CU; - Description: 'Path for keyman32.dll; use this when debugging Keyman Core' - ), - // KeymanPaths.TKeymanPaths.KeymanEngineInstallPath ( ID: 'development.paths.keyman_engine_home'; @@ -178,7 +168,7 @@ const RootKey: HKCU; Key: SRegKey_KeymanDebug_CU; Description: 'Path for keyman.exe and related resources; use this when '+ - 'debugging Keyman Configuration'; + 'debugging Keyman Configuration or Keyman Engine'; DefaultDesc: '%CommonProgramFiles(x86)%\Keyman\Keyman Engine' ),