mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-06 23:57:40 +00:00
582 lines
17 KiB
ObjectPascal
582 lines
17 KiB
ObjectPascal
(*
|
|
Name: KeymanDeveloperUtils
|
|
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
|
|
02 Aug 2006 - mcdurdin - Timeout when Beta expires
|
|
23 Aug 2006 - mcdurdin - Add WM_USER_WEBCOMMAND and WM_USER_INPUTLANGCHANGE
|
|
14 Sep 2006 - mcdurdin - Implement install visual keyboard
|
|
14 Sep 2006 - mcdurdin - Fix crash when installing keyboard or package
|
|
28 Sep 2006 - mcdurdin - Add Uninstall package support
|
|
04 Dec 2006 - mcdurdin - Support uninstalling a keyboard when in a package
|
|
04 Jan 2007 - mcdurdin - Add WC_COMMAND and WC_HELP options for WM_USER_WEBCOMMAND
|
|
23 Aug 2007 - mcdurdin - I826 - Fix open file from explorer not working
|
|
12 Oct 2007 - mcdurdin - I654 - Handle crash when installing or uninstalling a keyboard or visual keyboard or package
|
|
16 Jan 2009 - mcdurdin - I1043, I1138 - Fix crash installing keyboards (and use KMShell to install so we get full UI)
|
|
27 Jan 2009 - mcdurdin - I1814 - Add 'runas' parameter
|
|
25 May 2010 - mcdurdin - I2392 - Activation Client integration
|
|
22 Oct 2010 - mcdurdin - I2519 - Keyman Developer crashes when trying to install a keyboard
|
|
10 Dec 2010 - mcdurdin - I2558 - Remove LazyWrite
|
|
03 May 2011 - mcdurdin - I2890 - Record diagnostic data when encountering registry errors
|
|
18 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls + Win9x support
|
|
17 Aug 2012 - mcdurdin - I3377 - KM9 - Update code references from 8.0 to 9.0
|
|
19 Oct 2012 - mcdurdin - I3475 - V9.0 - Refactor legacy program execution functions
|
|
23 Mar 2012 - mcdurdin - I3283 - Debugger should start Keyman if not running
|
|
03 Nov 2012 - mcdurdin - I3503 - V9.0 - Merge of I3283 - Debugger should start Keyman if it isn't running
|
|
21 Aug 2013 - mcdurdin - I3893 - V9.0 - Keyman Developer crashes if Desktop not installed
|
|
04 Dec 2013 - mcdurdin - I3655 - V9.0 - Keyboard debugger does not appear to function in 9.0.419.0
|
|
12 Jun 2014 - mcdurdin - I4264 - V9.0 - If Keyman Developer is running, double clicking a file in Explorer doesn't open it
|
|
30 Apr 2015 - mcdurdin - I4682 - V9.0 - Installing a keyboard with an OSK from Developer fails to install the OSK
|
|
22 Jun 2015 - mcdurdin - I4749 - Opening a keyboard externally can open it multiple times
|
|
23 Jun 2015 - mcdurdin - I4749 - Opening a keyboard externally can open it multiple times
|
|
25 Oct 2016 - mcdurdin - I5135 - Remove product and licensing references from Developer projects
|
|
*)
|
|
unit KeymanDeveloperUtils; // I3306
|
|
|
|
interface
|
|
|
|
uses
|
|
System.UITypes,
|
|
System.Win.ComObj,
|
|
Vcl.Graphics,
|
|
Winapi.Messages,
|
|
Winapi.Windows,
|
|
|
|
KeymanPaths,
|
|
Sentry.Client,
|
|
UserMessages;
|
|
|
|
const
|
|
MAXMRU = 4;
|
|
MAX_REG_FONT_ENTRIES = 16;
|
|
|
|
function GetFolderPath(csidl: Integer): string;
|
|
function TikeActive: Boolean;
|
|
function CreateLink(const ObjPath, LinkPath, Desc: string): Boolean;
|
|
procedure SetFontFromString(f: TFont; s: string);
|
|
function FontAsString(f: TFont): string;
|
|
|
|
procedure InstallKeyboard(const nm: string; FCanInstallUnreg: Boolean);
|
|
procedure InstallPackage(const nm: string; FCanInstallUnreg: Boolean);
|
|
function UninstallKeyboard(const nm: string): Boolean;
|
|
function UninstallPackage(const nm: string): Boolean;
|
|
|
|
function GetKeymanInstallPath: string;
|
|
|
|
function IsBitmapLine(s: string): Boolean;
|
|
function GetBitmapNameFromLine(FFileName: string; s: string): string;
|
|
|
|
procedure RemoveOldestTikeEditFonts(FMaxLessOne: Boolean);
|
|
procedure RemoveOldestTikeTestFonts(FMaxLessOne: Boolean);
|
|
function GetCurrentDateTime: string;
|
|
function WaitForElevatedConfiguration(WindowHandle: THandle; const Parameters: WideString; FWait: Boolean): Cardinal; // I3655
|
|
|
|
procedure TestSentry;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Winapi.ShellApi,
|
|
Classes, SysUtils,
|
|
ErrorControlledRegistry, ActiveX, shlobj, RegistryKeys, //Dialogs,
|
|
utilsystem, Forms, kmxfile, Dialogs, utilexecute,
|
|
KeymanVersion, CRC32, VisualKeyboard, Controls;
|
|
|
|
function GetKMShellPath(var ps: string): Boolean; forward; // I3655
|
|
|
|
var
|
|
hMutex: THandle;
|
|
|
|
type
|
|
{ IPersist interface }
|
|
|
|
{$EXTERNALSYM IPersist }
|
|
IPersist = interface(IUnknown)
|
|
function GetClassID(var classID: TCLSID): HResult; stdcall;
|
|
end;
|
|
|
|
{ IPersistFile interface }
|
|
|
|
{$EXTERNALSYM IPersistFile }
|
|
IPersistFile = interface(IPersist)
|
|
function IsDirty: HResult; stdcall;
|
|
function Load(pszFileName: POleStr; dwMode: Longint): HResult; stdcall;
|
|
function Save(pszFileName: POleStr; fRemember: BOOL): HResult; stdcall;
|
|
function SaveCompleted(pszFileName: POleStr): HResult; stdcall;
|
|
function GetCurFile(var pszFileName: POleStr): HResult; stdcall;
|
|
end;
|
|
|
|
const
|
|
{$EXTERNALSYM IID_IPersistFile}
|
|
IID_IPersistFile: TGUID = (
|
|
D1:$0000010B;D2:$0000;D3:$0000;D4:($C0,$00,$00,$00,$00,$00,$00,$46));
|
|
|
|
function CreateLink(const ObjPath, LinkPath, Desc: string): Boolean;
|
|
var
|
|
hres: HResult;
|
|
psl: IShellLink;
|
|
ppf: IPersistFile;
|
|
i: Integer;
|
|
InQuotes: Boolean;
|
|
FArgs, FProg: string;
|
|
begin
|
|
// Get a pointer to the IShellLink interface.
|
|
hres := CoCreateInstance(CLSID_ShellLink, nil, CLSCTX_INPROC_SERVER, IID_IShellLinkA, psl);
|
|
if SUCCEEDED(hres) then
|
|
begin
|
|
// Set the path to the shortcut target and add the description.
|
|
i := 1; InQuotes := False;
|
|
FArgs := ''; FProg := ObjPath;
|
|
while i <= Length(ObjPath) do
|
|
begin
|
|
if (ObjPath[i] = ' ') and not InQuotes then
|
|
begin
|
|
FArgs := Copy(ObjPath, i+1, Length(ObjPath));
|
|
FProg := Copy(ObjPath, 1, i-1);
|
|
Break;
|
|
end
|
|
else if ObjPath[i] = '"' then InQuotes := not InQuotes;
|
|
Inc(i);
|
|
end;
|
|
psl.SetPath(PChar(FProg));
|
|
psl.SetArguments(PChar(FArgs));
|
|
psl.SetDescription(PChar(Desc));
|
|
//psl.SetIconLocation(PChar(FIcon), FIconIndex);
|
|
|
|
// Query IShellLink for the IPersistFile interface for saving the
|
|
// shortcut in persistent storage.
|
|
hres := psl.QueryInterface(IID_IPersistFile, ppf);
|
|
|
|
if SUCCEEDED(hres) then
|
|
begin
|
|
// Save the link by calling IPersistFile::Save.
|
|
hres := ppf.Save(PChar(LinkPath), TRUE);
|
|
//ppf.Release;
|
|
end;
|
|
//psl.Release;
|
|
end;
|
|
|
|
Result := SUCCEEDED(hres);
|
|
end;
|
|
|
|
function GetFolderPath(csidl: Integer): string;
|
|
var
|
|
buf: array[0..260] of Char;
|
|
idl: PItemIDList;
|
|
mm: IMalloc;
|
|
begin
|
|
Result := '';
|
|
if SHGetMalloc(mm) = NOERROR then
|
|
begin
|
|
if SHGetSpecialFolderLocation(0, csidl, idl) = NOERROR then
|
|
begin
|
|
if SHGetPathFromIDList(idl, buf) then
|
|
begin
|
|
Result := Buf;
|
|
if Result <> '' then
|
|
if Result[Length(Result)] <> '\' then Result := Result + '\';
|
|
end;
|
|
mm.Free(idl);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TikeActive: Boolean;
|
|
var
|
|
handle: THandle;
|
|
i: Integer;
|
|
FMultiInstance: Boolean;
|
|
begin
|
|
with TRegistryErrorControlled.Create do // I2890
|
|
try
|
|
if OpenKeyReadOnly(SRegKey_IDEOptions_CU) and ValueExists(SRegValue_IDEOptMultipleInstances)
|
|
then FMultiInstance := ReadString(SRegValue_IDEOptMultipleInstances) = '1'
|
|
else FMultiInstance := False;
|
|
finally
|
|
Free;
|
|
end;
|
|
|
|
if FMultiInstance then
|
|
begin
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
|
|
hMutex := CreateMutex(nil, True, 'Tavultesoft Integrated Keyboard Editor Mutex');
|
|
if GetLastError = ERROR_ALREADY_EXISTS then
|
|
begin
|
|
handle := FindWindow('TfrmKeymanDeveloper', nil); // I4264
|
|
if (handle <> 0) then
|
|
begin
|
|
with TRegistryErrorControlled.Create do // I2890
|
|
try
|
|
RootKey := HKEY_CURRENT_USER;
|
|
if not OpenKey(SRegKey_IDEFiles_CU, True) then // I2890
|
|
RaiseLastRegistryError;
|
|
for i := 1 to ParamCount do
|
|
begin
|
|
WriteString(IntToStr(i), ExpandFileNameClean(IncludeTrailingPathDelimiter(GetCurrentDir), ParamStr(i))); // I4749 // I4749
|
|
end;
|
|
finally
|
|
Free;
|
|
end;
|
|
PostMessage(handle, WM_USER_LOADREGFILES, 0, 0);
|
|
SetForegroundWindow(handle);
|
|
end;
|
|
Result := True;
|
|
end
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
function FontAsString(f: TFont): string;
|
|
begin
|
|
Result := f.Name + '|' + IntToStr(f.Size) + '|';
|
|
if fsBold in f.Style then Result := Result + 'B' else Result := Result + ' ';
|
|
if fsItalic in f.Style then Result := Result + 'I' else Result := Result + ' ';
|
|
if fsUnderline in f.Style then Result := Result + 'U' else Result := Result + ' ';
|
|
Result := Result + '|' + IntToStr(f.CharSet);
|
|
end;
|
|
|
|
procedure SetFontFromString(f: TFont; s: string);
|
|
var
|
|
n: Integer;
|
|
fn: string;
|
|
fsz: Integer;
|
|
fb, fi, fu: Boolean;
|
|
fch: Integer;
|
|
FontOnChange: TNotifyEvent;
|
|
begin
|
|
if s = '' then
|
|
begin
|
|
// We set to Consolas, 12px as default
|
|
f.Name := 'Consolas';
|
|
f.Charset := ANSI_CHARSET; // 02/02/2003 MCD: Added, charset not set when resetting to default font
|
|
f.Height := 12;
|
|
f.Style := [];
|
|
f.Color := clWindowText;
|
|
Exit;
|
|
end;
|
|
|
|
n := Pos('|', s); if n = 0 then Exit;
|
|
fn := Copy(s, 1, n-1); Delete(s, 1, n);
|
|
n := Pos('|', s); if n = 0 then Exit;
|
|
fsz := StrToIntDef(Copy(s, 1, n-1), 0); if fsz = 0 then Exit;
|
|
Delete(s, 1, n);
|
|
if Length(s) < 5 then Exit;
|
|
s := UpperCase(s);
|
|
fb := s[1] = 'B';
|
|
fi := s[2] = 'I';
|
|
fu := s[3] = 'U';
|
|
Delete(s, 1, 4);
|
|
fch := StrToIntDef(s, DEFAULT_CHARSET);
|
|
|
|
FontOnChange := f.OnChange;
|
|
f.OnChange := nil;
|
|
try
|
|
f.Name := fn;
|
|
f.Size := fsz;
|
|
f.Style := [];
|
|
if fb then f.Style := f.Style + [fsBold];
|
|
if fi then f.Style := f.Style + [fsItalic];
|
|
if fu then f.Style := f.Style + [fsUnderline];
|
|
f.Charset := fch;
|
|
finally
|
|
f.OnChange := FontOnChange;
|
|
end;
|
|
if Assigned(f.OnChange) then f.OnChange(f);
|
|
end;
|
|
|
|
procedure InstallKeyboard(const nm: string; FCanInstallUnreg: Boolean); // I4682
|
|
var
|
|
kmshell: string;
|
|
begin
|
|
if not GetKMShellPath(kmshell) then // I3655
|
|
Exit;
|
|
|
|
if not TUtilExecute.WaitForProcess(kmshell+' -i "'+nm+'"', ExtractFilePath(nm)) then // I3475
|
|
ShowMessage('Failed to install keyboard');
|
|
end;
|
|
|
|
function IsKeymanDesktopInstalled: Boolean;
|
|
begin
|
|
try
|
|
Result := FileExists(TKeymanPaths.KeymanDesktopInstallPath(TKeymanPaths.S_KMShell));
|
|
except
|
|
on E:EKeymanPath do
|
|
Result := False;
|
|
end;
|
|
end;
|
|
|
|
procedure InstallPackage(const nm: string; FCanInstallUnreg: Boolean);
|
|
var
|
|
kmshell: string;
|
|
begin
|
|
if not GetKmshellPath(kmshell) then
|
|
Exit;
|
|
|
|
if TUtilExecute.WaitForProcess('"'+kmshell+'" -i "'+nm+'"', ExtractFilePath(nm)) = False then // I3475
|
|
ShowMessage('Failed to install package');
|
|
end;
|
|
|
|
function UninstallKeyboard(const nm: string): Boolean;
|
|
var
|
|
kmshell: string;
|
|
begin
|
|
if not GetKmshellPath(kmshell) then
|
|
Exit(False);
|
|
|
|
Result := TUtilExecute.WaitForProcess('"'+kmshell+'" -uk "'+nm+'"', ExtractFilePath(kmshell)); // I3475
|
|
end;
|
|
|
|
function UninstallPackage(const nm: string): Boolean;
|
|
var
|
|
kmshell: string;
|
|
begin
|
|
if not GetKmshellPath(kmshell) then
|
|
Exit(False);
|
|
|
|
Result := TUtilExecute.WaitForProcess('"'+kmshell+'" -up "'+nm+'"', ExtractFilePath(kmshell)); // I3475
|
|
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
|
|
ShowMessage('The executable keyman32.dll could not be found. You should reinstall '+SKeymanDesktopName+'.');
|
|
end;
|
|
|
|
|
|
function IsBitmapLine(s: string): Boolean;
|
|
begin
|
|
Result := True;
|
|
|
|
s := UpperCase(Trim(s));
|
|
if Copy(s, 1, 6) = 'BITMAP' then Exit;
|
|
if Pos('&BITMAP', s) > 0 then Exit;
|
|
|
|
Result := False;
|
|
end;
|
|
|
|
// FFileName = filename of .kmn file; s is line of file with BITMAP "..."
|
|
function GetBitmapNameFromLine(FFileName: string; s: string): string;
|
|
var
|
|
n: Integer;
|
|
begin
|
|
Result := '';
|
|
|
|
s := Trim(s);
|
|
if UpperCase(Copy(s, 1, 6)) = 'BITMAP' then
|
|
begin
|
|
Delete(s,1,6);
|
|
if Copy(s,1,1) = 'S' then
|
|
begin
|
|
Delete(s,1,1);
|
|
end;
|
|
|
|
n := Pos(',', s);
|
|
if n > 0 then Delete(s, n, Length(s));
|
|
|
|
if s = '' then Exit;
|
|
if s[1] <> ' ' then Exit;
|
|
s := Trim(s); if s = '' then Exit;
|
|
if s[1] = '"' then
|
|
begin
|
|
Delete(s,1,1);
|
|
n := Pos('"', s);
|
|
end
|
|
else
|
|
n := Pos(' ', s);
|
|
end
|
|
else if Pos('&BITMAP', UpperCase(s)) > 0 then
|
|
begin
|
|
n := Pos(')', s);
|
|
if n = 0 then Exit;
|
|
Delete(s, 1, n); s := Trim(s);
|
|
if s = '' then Exit;
|
|
|
|
if s[1] = '"' then
|
|
begin
|
|
Delete(s,1,1);
|
|
n := Pos('"', s);
|
|
end
|
|
else if s[1] = '''' then
|
|
begin
|
|
Delete(s,1,1);
|
|
n := Pos('''', s);
|
|
end
|
|
else
|
|
Exit;
|
|
end
|
|
else
|
|
Exit;
|
|
|
|
if n > 0 then Delete(s,n,Length(s));
|
|
|
|
if s = '' then Exit;
|
|
if ExtractFileExt(s) = '' then s := s + '.bmp';
|
|
|
|
if (s[1] <> '\') and (s[2] <> ':') then
|
|
s := ExtractFilePath(FFileName) + s;
|
|
|
|
Result := s;
|
|
end;
|
|
|
|
function GetCurrentDateTime: string;
|
|
var
|
|
s: string;
|
|
begin
|
|
//2147483647
|
|
//0103021450
|
|
s := FormatDateTime('yymmddhhnn', Now);
|
|
while s[1] >= '2' do s[1] := Chr(Ord(s[1]) - 1); // stop integer overflow after 2020 ;->
|
|
Result := s;
|
|
end;
|
|
|
|
procedure RemoveOldestTikeFonts(FMaxLessOne: Boolean; const key: string);
|
|
var
|
|
str: TStringList;
|
|
i, c, max: Integer;
|
|
s, CurExp: string;
|
|
begin
|
|
max := MAX_REG_FONT_ENTRIES;
|
|
if FMaxLessOne then Dec(max);
|
|
CurExp := GetCurrentDateTime;
|
|
str := TStringList.Create;
|
|
with TRegistryErrorControlled.Create do // I2890
|
|
try
|
|
if not OpenKey(key, True) then // I2890
|
|
RaiseLastRegistryError;
|
|
|
|
GetValueNames(str);
|
|
|
|
for i := 0 to str.Count - 1 do
|
|
begin
|
|
s := ReadString(str[i]);
|
|
c := Pos('|', s);
|
|
if (c = 0) or (StrToIntDef(Copy(s,1,c-1), -1) = -1) then
|
|
begin
|
|
WriteString(str[i], CurExp+'|'+s);
|
|
str[i] := Format('%s|%s', [CurExp, str[i]])
|
|
end
|
|
else
|
|
str[i] := Format('%10.10d|%s', [StrToInt(Copy(s,1,c-1)), str[i]]);
|
|
end;
|
|
|
|
str.Sort;
|
|
|
|
{ Delete all old entries }
|
|
for i := 0 to str.Count - max - 1 do
|
|
DeleteValue(Copy(str[i], 16, 260));
|
|
finally
|
|
str.Free;
|
|
Free;
|
|
end;
|
|
end;
|
|
|
|
procedure RemoveOldestTikeEditFonts(FMaxLessOne: Boolean);
|
|
begin
|
|
RemoveOldestTikeFonts(FMaxLessOne, SRegKey_IDEEditFonts_CU);
|
|
end;
|
|
|
|
procedure RemoveOldestTikeTestFonts(FMaxLessOne: Boolean);
|
|
begin
|
|
RemoveOldestTikeFonts(FMaxLessOne, SRegKey_IDETestFonts_CU);
|
|
end;
|
|
|
|
function GetKMShellPath(var ps: string): Boolean; // I3655
|
|
begin
|
|
if not IsKeymanDesktopInstalled then
|
|
begin
|
|
ShowMessage('Keyman for Windows is not installed. You must install Keyman to complete this action.');
|
|
Exit(False);
|
|
end;
|
|
|
|
try
|
|
ps := TKeymanPaths.KeymanDesktopInstallPath(TKeymanPaths.S_KMShell);
|
|
except
|
|
on E:EKeymanPath do
|
|
begin
|
|
ShowMessage('Keyman for Windows is not installed. You must install Keyman to complete this action.');
|
|
Exit(False);
|
|
end;
|
|
end;
|
|
Result := FileExists(ps);
|
|
end;
|
|
|
|
function WaitForElevatedConfiguration(WindowHandle: THandle; const Parameters: WideString; FWait: Boolean): Cardinal;
|
|
var
|
|
execinfo: TShellExecuteInfoW;
|
|
kmshell: string;
|
|
begin
|
|
Result := $FFFFFFFF;
|
|
|
|
if not GetKMShellPath(kmshell) then
|
|
Exit;
|
|
|
|
FillChar(execinfo, sizeof(execinfo), 0);
|
|
execinfo.cbSize := SizeOf(execinfo);
|
|
execinfo.fMask := SEE_MASK_NOCLOSEPROCESS;
|
|
execinfo.Wnd := WindowHandle;
|
|
execinfo.lpVerb := 'runas';
|
|
execinfo.lpFile := PWideChar(kmshell);
|
|
execinfo.lpParameters := PWideChar(Parameters);
|
|
execinfo.lpDirectory := PWideChar(ExtractFileDir(ParamStr(0)));
|
|
execinfo.nShow := SW_SHOWNORMAL;
|
|
if ShellExecuteExW(@execinfo) then
|
|
begin
|
|
if FWait then
|
|
begin
|
|
ShowWindow(WindowHandle, SW_HIDE);
|
|
repeat
|
|
if not GetExitCodeProcess(execinfo.hProcess, Result) then
|
|
begin
|
|
Result := $FFFFFFFF;
|
|
Break;
|
|
end;
|
|
Sleep(10);
|
|
Application.ProcessMessages;
|
|
until Result <> STILL_ACTIVE;
|
|
ShowWindow(WindowHandle, SW_SHOW);
|
|
end
|
|
else
|
|
Result := 0;
|
|
CloseHandle(execinfo.hProcess);
|
|
end
|
|
else
|
|
ShowMessage(SysErrorMessage(GetLastError));
|
|
end;
|
|
|
|
procedure TestSentry;
|
|
begin
|
|
raise ESentryTest.Create('Just testing Sentry integration');
|
|
end;
|
|
|
|
end.
|
|
|