mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-06 00:45:32 +00:00
* Add `TKeymanPaths.KeymanLocalePath` for consistent access to locales folder, and update references. * Remove the `SKDefaultLanguageCode` string and hard code the default language in CustomisationMessages.pas. * Hide the test locale 'qqq' on release builds. Fixes: #15164
498 lines
15 KiB
ObjectPascal
498 lines
15 KiB
ObjectPascal
unit KeymanPaths;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils;
|
|
|
|
type
|
|
EKeymanPath = class(Exception);
|
|
|
|
TKeymanPaths = class
|
|
private
|
|
const S_CEF_DebugPath = 'Debug_CEFPath';
|
|
const S_CEF_EnvVar = 'KEYMAN_CEF4DELPHI_ROOT';
|
|
const S_CEF_SubFolder = 'cef\';
|
|
const S_CEF_LibCef = 'libcef.dll';
|
|
const S_CEF_SubProcess = 'kmbrowserhost.exe';
|
|
const S_CEF_SubProcess_Developer = 'kmdbrowserhost.exe';
|
|
const S_CustomisationFilename = 'desktop_pro.pxx';
|
|
|
|
const S_KeymanAppData_UpdateCache = 'Keyman\UpdateCache\';
|
|
|
|
class var FHaveCachedKeymanRoot: Boolean;
|
|
class var FCachedKeymanRoot: string;
|
|
public
|
|
const S_KMShell = 'kmshell.exe';
|
|
const S_TSysInfoExe = 'tsysinfo.exe';
|
|
const S_KeymanExe = 'keyman.exe';
|
|
const S_CfgIcon = 'cfgicon.ico';
|
|
const S_AppIcon = 'appicon.ico';
|
|
const S_KeymanMenuTitleImage = 'menutitle.png';
|
|
const S_FallbackKeyboardPath = 'Keyboards\';
|
|
const S__Package = '_Package\';
|
|
const S_MCompileExe = 'mcompile.exe';
|
|
const S_UpdateCache_Metadata = 'cache.json';
|
|
class function ErrorLogPath(const app: string = ''): string; static;
|
|
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 KeymanDesktopInstallDir: string; static;
|
|
class function KeymanEngineInstallDir: string; static;
|
|
class function KeyboardsInstallPath(const filename: string = ''): string; static;
|
|
class function KeyboardsInstallDir: string; static;
|
|
class function KeymanConfigStaticHttpFilesPath(const filename: string = ''): string; static;
|
|
class function KeymanCustomisationPath: string; static;
|
|
class function KeymanLocalePath: string; static;
|
|
class function KeymanCoreLibraryPath(const Filename: string): string; static;
|
|
class function CEFPath: string; static; // Chromium Embedded Framework
|
|
class function CEFDataPath(const mode: string): string; static;
|
|
class function CEFSubprocessPath(IsDeveloper: Boolean): string; static;
|
|
|
|
class function RunningFromSource(var keyman_root: string): Boolean; static;
|
|
end;
|
|
|
|
function GetFolderPath(csidl: Integer): string;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Winapi.Windows,
|
|
Winapi.ActiveX,
|
|
Winapi.ShlObj,
|
|
System.Classes,
|
|
System.Win.Registry,
|
|
|
|
DebugPaths,
|
|
RegistryKeys;
|
|
|
|
|
|
// Duplicated from utilsystem to avoid importing other units
|
|
function GetFolderPath(csidl: Integer): string;
|
|
var
|
|
buf: array[0..260] of Char;
|
|
idl: PItemIDList;
|
|
begin
|
|
Result := '';
|
|
if SUCCEEDED(SHGetFolderLocation(0, csidl, 0, 0, idl)) then
|
|
begin
|
|
if SHGetPathFromIDList(idl, buf) then
|
|
Result := buf;
|
|
ILFree(idl);
|
|
end;
|
|
|
|
if (Result = '') and (csidl = CSIDL_PROGRAM_FILES) then
|
|
with TRegistry.Create do // I2890
|
|
try
|
|
RootKey := HKEY_LOCAL_MACHINE;
|
|
if not OpenKeyReadOnly('Software\Microsoft\Windows\CurrentVersion') then // I2890
|
|
RaiseLastOSError(LastError);
|
|
Result := ReadString('ProgramFilesDir');
|
|
finally
|
|
Free;
|
|
end;
|
|
if Result <> '' then
|
|
if Result[Length(Result)] <> '\' then Result := Result + '\';
|
|
end;
|
|
|
|
|
|
class function TKeymanPaths.KeymanDesktopInstallDir: string;
|
|
begin
|
|
Result := ExtractFileDir(KeymanDesktopInstallPath);
|
|
end;
|
|
|
|
type
|
|
TRegSzHelper = class helper for TRegistry
|
|
function ReadMultiSz(const name: string; strings: TStrings): Boolean;
|
|
end;
|
|
|
|
function TRegSzHelper.ReadMultiSz(const name: string; Strings: TStrings): boolean;
|
|
var
|
|
iSizeInWChar, iSizeInByte: integer;
|
|
Buffer: array of WChar;
|
|
p, EndBuffer: PWChar;
|
|
begin
|
|
iSizeInByte := GetDataSize(name);
|
|
iSizeInWChar := iSizeInByte div SizeOf(WChar);
|
|
if iSizeInByte > 0 then
|
|
begin
|
|
SetLength(Buffer, iSizeInWChar);
|
|
iSizeInWChar := ReadBinaryData(name, Buffer[0], iSizeInByte) div sizeof(WChar);
|
|
p := @Buffer[0];
|
|
EndBuffer := p;
|
|
Inc(EndBuffer, iSizeInWChar);
|
|
while p < EndBuffer do
|
|
begin
|
|
Strings.Append(p);
|
|
p := StrScan(p, #0);
|
|
Inc(p);
|
|
end;
|
|
Result := True;
|
|
end
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
class function TKeymanPaths.KeymanDesktopInstallPath(const filename: string = ''): string;
|
|
var
|
|
s: TStrings;
|
|
begin
|
|
//
|
|
// If the current executable is kmshell.exe, then return the folder it is in (unless
|
|
// a debug path overrides it).
|
|
//
|
|
// Otherwise, Keyman takes precedence if installed. If it is not installed,
|
|
// check the installed OEM product registry and the first installed OEM product is
|
|
// used. Currently, I assess that the likelihood of two OEM products being installed
|
|
// without Keyman being installed is low, and we can live with the first one taking
|
|
// precedence.
|
|
//
|
|
// Finally, if no OEM product can be found, then abort.
|
|
//
|
|
|
|
Result := '';
|
|
|
|
if SameText(ExtractFileName(ParamStr(0)), TKeymanPaths.S_KMShell) then
|
|
begin
|
|
// Just for kmshell.exe, use the current folder
|
|
Result := ExtractFilePath(ParamStr(0));
|
|
end;
|
|
|
|
if Result = '' then
|
|
begin
|
|
// If Keyman is installed, then use its install path.
|
|
with TRegistry.Create do // I2890
|
|
try
|
|
RootKey := HKEY_LOCAL_MACHINE;
|
|
if OpenKeyReadOnly(SRegKey_KeymanDesktop_LM) and ValueExists(SRegValue_RootPath) then
|
|
Result := ReadString(SRegValue_RootPath);
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
if Result = '' then
|
|
begin
|
|
// Look in the registry for any installed OEM product; this is a multistring,
|
|
// but we only want the first value
|
|
with TRegistry.Create do
|
|
try
|
|
RootKey := HKEY_LOCAL_MACHINE;
|
|
if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_Engine_OEMProductPath) then
|
|
begin
|
|
s := TStringList.Create;
|
|
try
|
|
ReadMultiSz(SRegValue_Engine_OEMProductPath, s);
|
|
if s.Count > 0 then
|
|
Result := s[0];
|
|
finally
|
|
s.Free;
|
|
end;
|
|
end;
|
|
finally
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
Result := GetDebugPath('KeymanDesktop', Result);
|
|
|
|
if Result = '' then
|
|
raise EKeymanPath.Create('Unable to find the Keyman directory. You should reinstall the product.');
|
|
|
|
Result := IncludeTrailingPathDelimiter(Result) + filename;
|
|
end;
|
|
|
|
class function TKeymanPaths.KeymanEngineInstallDir: string;
|
|
begin
|
|
Result := ExtractFileDir(KeymanEngineInstallPath);
|
|
end;
|
|
|
|
class function TKeymanPaths.KeymanEngineInstallPath(const filename: string = ''): string;
|
|
begin
|
|
with TRegistry.Create do // I2890
|
|
try
|
|
RootKey := HKEY_LOCAL_MACHINE;
|
|
if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_RootPath) then
|
|
Result := ReadString(SRegValue_RootPath);
|
|
finally
|
|
Free;
|
|
end;
|
|
|
|
Result := GetDebugPath('KeymanEngine', Result);
|
|
|
|
if Result = '' then
|
|
raise EKeymanPath.Create('Unable to find the Keyman Engine directory. You should reinstall the product.');
|
|
|
|
Result := IncludeTrailingPathDelimiter(Result) + filename;
|
|
end;
|
|
|
|
class function TKeymanPaths.CEFPath: string;
|
|
|
|
function AppendSlash(const path: string): string;
|
|
begin
|
|
Result := path;
|
|
if Result <> '' then
|
|
Result := IncludeTrailingPathDelimiter(path);
|
|
end;
|
|
|
|
function IsValidPath(const path: string): Boolean;
|
|
begin
|
|
Result := (path <> '') and FileExists(path + S_CEF_LibCef);
|
|
end;
|
|
|
|
begin
|
|
// cef\ subfolder of executable path
|
|
Result := ExtractFilePath(ParamStr(0)) + S_CEF_SubFolder;
|
|
if IsValidPath(Result) then
|
|
Exit;
|
|
|
|
// Debug_CEFPath registry setting
|
|
Result := AppendSlash(GetDebugPath(S_CEF_DebugPath, ''));
|
|
if IsValidPath(Result) then
|
|
Exit;
|
|
|
|
// KEYMAN_CEF4DELPHI_ROOT environment variable
|
|
Result := AppendSlash(GetEnvironmentVariable(S_CEF_EnvVar));
|
|
if IsValidPath(Result) then
|
|
Exit;
|
|
|
|
// Same folder as executable
|
|
Result := ExtractFilePath(ParamStr(0));
|
|
if IsValidPath(Result) then
|
|
Exit;
|
|
|
|
// Keyman Desktop installation folder + cef\
|
|
try
|
|
Result := KeymanDesktopInstallPath+S_CEF_SubFolder
|
|
except
|
|
on E:EKeymanPath do
|
|
Result := '';
|
|
end;
|
|
if IsValidPath(Result) then
|
|
Exit;
|
|
|
|
// Failed, could not find libcef.dll
|
|
Result := '';
|
|
end;
|
|
|
|
class function TKeymanPaths.CEFSubprocessPath(IsDeveloper: Boolean): string;
|
|
var
|
|
keyman_root: string;
|
|
begin
|
|
// Same folder as executable
|
|
|
|
// TODO: make this a little cleaner by passing in expected subprocess name
|
|
if IsDeveloper
|
|
then Result := ExtractFilePath(ParamStr(0)) + S_CEF_SubProcess_Developer
|
|
else Result := ExtractFilePath(ParamStr(0)) + S_CEF_SubProcess;
|
|
|
|
if FileExists(Result) then Exit;
|
|
|
|
// 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\desktop\' + S_CEF_SubProcess;
|
|
if FileExists(Result) then Exit;
|
|
|
|
// Source repo, source folder
|
|
Result := keyman_root + 'windows\bin\desktop\kmbrowserhost\win32\debug\' + S_CEF_SubProcess;
|
|
if FileExists(Result) then Exit;
|
|
|
|
Result := keyman_root + 'windows\bin\desktop\kmbrowserhost\win32\release\' + S_CEF_SubProcess;
|
|
if FileExists(Result) then Exit;
|
|
end;
|
|
|
|
// Check final install location - in Keyman for Windows install folder
|
|
try
|
|
Result := KeymanDesktopInstallPath(S_CEF_SubProcess);
|
|
except
|
|
on E:EKeymanPath do
|
|
Result := '';
|
|
end;
|
|
if (Result <> '') and FileExists(Result) then Exit;
|
|
|
|
Result := '';
|
|
end;
|
|
|
|
class function TKeymanPaths.CEFDataPath(const mode: string): string;
|
|
begin
|
|
Result := GetFolderPath(CSIDL_APPDATA) + SFolderCEFBrowserData + '\' + mode;
|
|
end;
|
|
|
|
class function TKeymanPaths.KeyboardsInstallDir: string;
|
|
begin
|
|
Result := ExcludeTrailingPathDelimiter(KeyboardsInstallPath);
|
|
end;
|
|
|
|
class function TKeymanPaths.KeyboardsInstallPath(const filename: string): string;
|
|
begin
|
|
with TRegistry.Create do // I2890
|
|
try
|
|
// TODO: We only want to use %CommonAppData%\Keyman now for all keyboards
|
|
// So we should eliminate use of RootKeyboardAdminPath and S_FallbackKeyboardPath
|
|
RootKey := HKEY_LOCAL_MACHINE;
|
|
if OpenKeyReadOnly(SRegKey_KeymanEngine_LM) and ValueExists(SRegValue_RootKeyboardAdminPath) then
|
|
begin
|
|
Result := ReadString(SRegValue_RootKeyboardAdminPath)
|
|
end
|
|
else
|
|
begin
|
|
Result := GetFolderPath(CSIDL_COMMON_APPDATA);
|
|
if Result = '' then
|
|
Result := TKeymanPaths.KeymanEngineInstallPath(TKeymanPaths.S_FallbackKeyboardPath)
|
|
else
|
|
Result := Result + SFolderKeymanKeyboard;
|
|
end;
|
|
finally
|
|
Free;
|
|
end;
|
|
|
|
Result := GetDebugPath('Keyboards', Result, True);
|
|
if Result = '' then
|
|
raise EKeymanPath.Create('Unable to find the Keyboards directory. You should reinstall the product.');
|
|
|
|
Result := Result + Filename;
|
|
end;
|
|
|
|
class function TKeymanPaths.ErrorLogPath(const app: string): string;
|
|
begin
|
|
Result := GetFolderPath(CSIDL_LOCAL_APPDATA) + SFolderKeymanEngineDiag + '\';
|
|
ForceDirectories(Result); // I2768
|
|
if app <> '' then
|
|
Result := Result + app + '-' + IntToStr(GetCurrentProcessId) + '.log';
|
|
end;
|
|
|
|
class function TKeymanPaths.KeymanConfigStaticHttpFilesPath(const filename: string): string;
|
|
var
|
|
keyman_root: string;
|
|
begin
|
|
// Look up KEYMAN_ROOT development variable -- if found and executable
|
|
// within that path then use that as source path
|
|
if TKeymanPaths.RunningFromSource(keyman_root) then
|
|
begin
|
|
Exit(keyman_root + 'windows\src\desktop\kmshell\xml\' + filename);
|
|
end;
|
|
|
|
Result := GetDebugPath('KeymanConfigStaticHttpFilesPath', '');
|
|
if Result = '' then
|
|
begin
|
|
Result := ExtractFilePath(ParamStr(0));
|
|
|
|
// The xml files may be in the same folder as the executable
|
|
// for some 3rd party distributions of Keyman files.
|
|
if FileExists(Result + 'xml\strings.xml') then
|
|
Result := Result + 'xml\';
|
|
end;
|
|
|
|
Result := Result + filename;
|
|
end;
|
|
|
|
class function TKeymanPaths.KeymanLocalePath: string;
|
|
var
|
|
keyman_root: string;
|
|
begin
|
|
// Look up KEYMAN_ROOT development variable -- if found and executable
|
|
// within that path then use that as source path
|
|
if TKeymanPaths.RunningFromSource(keyman_root) then
|
|
begin
|
|
Exit(keyman_root + 'windows\src\desktop\kmshell\locale\');
|
|
end;
|
|
|
|
Result := TKeymanPaths.KeymanDesktopInstallPath('locale\');
|
|
end;
|
|
|
|
class function TKeymanPaths.KeymanCustomisationPath: string;
|
|
var
|
|
keyman_root: string;
|
|
begin
|
|
// On developer machines, if we are running within the source repo, then use
|
|
// those paths
|
|
keyman_root := GetEnvironmentVariable('KEYMAN_ROOT').Replace('/', '\');
|
|
if (keyman_root <> '') and SameText(keyman_root, ParamStr(0).Substring(0, keyman_root.Length)) then
|
|
begin
|
|
// Source repo, bin folder
|
|
Result := IncludeTrailingPathDelimiter(keyman_root) + 'windows\src\desktop\branding';
|
|
if DirectoryExists(Result) then
|
|
Exit(Result + '\' + S_CustomisationFilename);
|
|
end;
|
|
|
|
Result := TKeymanPaths.KeymanDesktopInstallPath(S_CustomisationFilename);
|
|
end;
|
|
|
|
class function TKeymanPaths.KeymanHelpPath(const HelpFile: 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\desktop\' + HelpFile;
|
|
if FileExists(Result) then Exit;
|
|
end;
|
|
|
|
// Same folder as executable
|
|
Result := ExtractFilePath(ParamStr(0)) + HelpFile;
|
|
if FileExists(Result) then Exit;
|
|
|
|
Result := TKeymanPaths.KeymanDesktopInstallPath(HelpFile);
|
|
if FileExists(Result) then Exit;
|
|
|
|
Result := '';
|
|
end;
|
|
|
|
class function TKeymanPaths.KeymanUpdateCachePath(const filename: string): string;
|
|
begin
|
|
Result := GetFolderPath(CSIDL_LOCAL_APPDATA) + S_KeymanAppData_UpdateCache + filename;
|
|
end;
|
|
|
|
class function TKeymanPaths.RunningFromSource(var keyman_root: string): Boolean;
|
|
begin
|
|
if FHaveCachedKeymanRoot then
|
|
begin
|
|
keyman_root := FCachedKeymanRoot;
|
|
Exit(keyman_root <> '');
|
|
end;
|
|
// On developer machines, if we are running within the source repo, then use
|
|
// those paths
|
|
keyman_root := GetEnvironmentVariable('KEYMAN_ROOT').Replace('/', '\', [rfReplaceAll]);
|
|
if keyman_root <> '' then
|
|
keyman_root := IncludeTrailingPathDelimiter(keyman_root);
|
|
Result := (keyman_root <> '') and SameText(keyman_root, ParamStr(0).Substring(0, keyman_root.Length));
|
|
|
|
FHaveCachedKeymanRoot := True;
|
|
if Result then
|
|
FCachedKeymanRoot := keyman_root;
|
|
end;
|
|
|
|
class function TKeymanPaths.KeymanCoreLibraryPath(const Filename: string): string;
|
|
var
|
|
keyman_root: string;
|
|
configuration: string;
|
|
begin
|
|
// Look up KEYMAN_ROOT development variable -- if found and executable
|
|
// within that path then use that as source path
|
|
if TKeymanPaths.RunningFromSource(keyman_root) then
|
|
begin
|
|
{$IFDEF DEBUG}
|
|
configuration := 'debug';
|
|
{$ELSE}
|
|
configuration := 'release';
|
|
{$ENDIF}
|
|
Exit(keyman_root + 'core\build\x86\'+configuration+'\src\' + Filename);
|
|
end;
|
|
|
|
Result := GetDebugPath('KeymanCoreLibraryPath', '');
|
|
if Result = '' then
|
|
begin
|
|
Result := ExtractFilePath(ParamStr(0));
|
|
end;
|
|
|
|
Result := IncludeTrailingPathDelimiter(Result) + Filename;
|
|
end;
|
|
|
|
end.
|