mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-07 01:15:33 +00:00
434 lines
13 KiB
ObjectPascal
434 lines
13 KiB
ObjectPascal
unit Keyman.Developer.System.ImportWindowsKeyboard;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils,
|
|
|
|
Keyman.Developer.System.KeyboardProjectTemplate,
|
|
UKeymanTargets;
|
|
|
|
type
|
|
EImportWindowsKeyboard = class(Exception);
|
|
|
|
TImportWindowsKeyboard = class
|
|
private
|
|
FBaseKeyboardID: string;
|
|
FBaseName: string;
|
|
FProjectFilename: string;
|
|
|
|
{ Options }
|
|
FSourceKLID: string;
|
|
FDestinationPath: string;
|
|
FNameTemplate: string;
|
|
FAuthor: string;
|
|
FVersion: string;
|
|
FKeyboardIDTemplate: string;
|
|
FBCP47Tags: string;
|
|
FCopyright: string;
|
|
FTargets: TKeymanTargets;
|
|
FFullCopyright: string;
|
|
FDescription: string;
|
|
function LoadKLIDDetails: Boolean;
|
|
function ImportKeyboard(const DestinationFilename, DestinationKVKSFilename: string): Boolean;
|
|
function GenerateIcon(const IconFilename: string): Boolean;
|
|
procedure SetAuthor(const Value: string);
|
|
procedure SetCopyright(const Value: string);
|
|
procedure SetDestinationPath(const Value: string);
|
|
procedure SetKeyboardIDTemplate(const Value: string);
|
|
procedure SetBCP47Tags(const Value: string);
|
|
procedure SetNameTemplate(const Value: string);
|
|
procedure SetSourceKLID(const Value: string);
|
|
procedure SetVersion(const Value: string);
|
|
procedure InjectSystemStores(const KeyboardFilename, OSKFilename,
|
|
IconFilename, TouchLayoutFilename: string);
|
|
function ConvertOSKToTouchLayout(const OSKFilename, TouchLayoutFilename: string): Boolean;
|
|
function FindBCP47TagForKLID: string; overload;
|
|
function GetProjectFilename: string;
|
|
procedure SetTargets(const Value: TKeymanTargets);
|
|
procedure SetFullCopyright(const Value: string);
|
|
procedure SetDescription(const Value: string);
|
|
public
|
|
function Execute: Boolean; overload;
|
|
|
|
class function FindBCP47TagForKLID(KLID: string): string; overload;
|
|
class function GetKLIDDetails(const KLID: string; var Name,
|
|
KeyboardID: string): Boolean; static;
|
|
|
|
property SourceKLID: string read FSourceKLID write SetSourceKLID;
|
|
property DestinationPath: string read FDestinationPath write SetDestinationPath;
|
|
property KeyboardIDTemplate: string read FKeyboardIDTemplate write SetKeyboardIDTemplate;
|
|
property NameTemplate: string read FNameTemplate write SetNameTemplate;
|
|
property Copyright: string read FCopyright write SetCopyright;
|
|
property FullCopyright: string read FFullCopyright write SetFullCopyright;
|
|
property Version: string read FVersion write SetVersion;
|
|
property BCP47Tags: string read FBCP47Tags write SetBCP47Tags;
|
|
property Author: string read FAuthor write SetAuthor;
|
|
property Description: string read FDescription write SetDescription;
|
|
property Targets: TKeymanTargets read FTargets write SetTargets;
|
|
|
|
property ProjectFilename: string read GetProjectFilename;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.Classes,
|
|
System.Win.Registry,
|
|
Winapi.Windows,
|
|
|
|
BCP47Tag,
|
|
Keyman.Developer.System.ImportKeyboardDLL,
|
|
Keyman.Developer.System.GenerateKeyboardIcon,
|
|
Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter,
|
|
Keyman.System.KeyboardUtils,
|
|
KeymanVersion,
|
|
KeyboardParser,
|
|
kmxfileconsts,
|
|
RegistryKeys,
|
|
TempFileManager,
|
|
utilfiletypes;
|
|
|
|
{ TImportWindowsKeyboard }
|
|
|
|
function Fail(m: string): Boolean;
|
|
begin
|
|
Result := False;
|
|
writeln(m);
|
|
end;
|
|
|
|
function TImportWindowsKeyboard.LoadKLIDDetails: Boolean;
|
|
begin
|
|
Result := GetKLIDDetails(FSourceKLID, FBaseName, FBaseKeyboardID);
|
|
end;
|
|
|
|
class function TImportWindowsKeyboard.GetKLIDDetails(const KLID: string; var Name, KeyboardID: string): Boolean;
|
|
var
|
|
r: TRegistry;
|
|
begin
|
|
r := TRegistry.Create;
|
|
try
|
|
r.RootKey := HKEY_LOCAL_MACHINE;
|
|
if not r.OpenKeyReadOnly(SRegKey_KeyboardLayouts_LM + '\' + KLID) then
|
|
Exit(False);
|
|
|
|
if not r.ValueExists(SRegValue_KeyboardLayoutFile) or not
|
|
r.ValueExists(SRegValue_KeyboardLayoutText) then
|
|
Exit(False);
|
|
|
|
KeyboardID := TKeyboardUtils.CleanKeyboardID(ChangeFileExt(r.ReadString(SRegValue_KeyboardLayoutFile), ''));
|
|
Name := r.ReadString(SRegValue_KeyboardLayoutText);
|
|
finally
|
|
r.Free;
|
|
end;
|
|
Result := True;
|
|
end;
|
|
|
|
function TImportWindowsKeyboard.GetProjectFilename: string;
|
|
var
|
|
FTemplate: TKeyboardProjectTemplate;
|
|
begin
|
|
if FProjectFilename = '' then
|
|
begin
|
|
// Lookup the KLID in the registry and read basic details
|
|
if not LoadKLIDDetails then
|
|
raise EImportWindowsKeyboard.Create('The keyboard identified by '+FSourceKLID+' could not be found.');
|
|
|
|
FTemplate := TKeyboardProjectTemplate.Create(FDestinationPath, Format(FKeyboardIDTemplate, [FBaseKeyboardID]), KMXKeymanTargets + [ktWeb]);
|
|
try
|
|
Result := FTemplate.ProjectFilename;
|
|
finally
|
|
FTemplate.Free;
|
|
end;
|
|
end
|
|
else
|
|
Result := FProjectFIlename;
|
|
end;
|
|
|
|
procedure TImportWindowsKeyboard.SetAuthor(const Value: string);
|
|
begin
|
|
FAuthor := Value;
|
|
end;
|
|
|
|
procedure TImportWindowsKeyboard.SetCopyright(const Value: string);
|
|
begin
|
|
FCopyright := Value;
|
|
end;
|
|
|
|
procedure TImportWindowsKeyboard.SetDescription(const Value: string);
|
|
begin
|
|
FDescription := Value;
|
|
end;
|
|
|
|
procedure TImportWindowsKeyboard.SetDestinationPath(const Value: string);
|
|
begin
|
|
FDestinationPath := IncludeTrailingPathDelimiter(Value);
|
|
end;
|
|
|
|
procedure TImportWindowsKeyboard.SetFullCopyright(const Value: string);
|
|
begin
|
|
FFullCopyright := Value;
|
|
end;
|
|
|
|
procedure TImportWindowsKeyboard.SetKeyboardIDTemplate(const Value: string);
|
|
begin
|
|
if Value = ''
|
|
then FKeyboardIDTemplate := '%s'
|
|
else FKeyboardIDTemplate := Value;
|
|
end;
|
|
|
|
procedure TImportWindowsKeyboard.SetBCP47Tags(const Value: string);
|
|
begin
|
|
FBCP47Tags := Value;
|
|
end;
|
|
|
|
procedure TImportWindowsKeyboard.SetNameTemplate(const Value: string);
|
|
begin
|
|
if Value = ''
|
|
then FNameTemplate := '%s'
|
|
else FNameTemplate := Value;
|
|
end;
|
|
|
|
procedure TImportWindowsKeyboard.SetSourceKLID(const Value: string);
|
|
begin
|
|
FSourceKLID := Value;
|
|
end;
|
|
|
|
procedure TImportWindowsKeyboard.SetTargets(const Value: TKeymanTargets);
|
|
begin
|
|
FTargets := Value;
|
|
end;
|
|
|
|
procedure TImportWindowsKeyboard.SetVersion(const Value: string);
|
|
begin
|
|
FVersion := Value;
|
|
end;
|
|
|
|
function TImportWindowsKeyboard.Execute: Boolean;
|
|
var
|
|
FTemplate: TKeyboardProjectTemplate;
|
|
DestinationOSKTempFile: TTempFile;
|
|
DestinationOSKFilename: string;
|
|
begin
|
|
if not ForceDirectories(FDestinationPath) then
|
|
Exit(Fail('The destination path '+FDestinationPath+' could not be created.'));
|
|
|
|
// Lookup the KLID in the registry and read basic details
|
|
if not LoadKLIDDetails then
|
|
Exit(Fail('The keyboard identified by '+FSourceKLID+' could not be found.'));
|
|
|
|
// Create a new folder in destination path
|
|
|
|
DestinationOSKTempFile := nil;
|
|
FTemplate := TKeyboardProjectTemplate.Create(FDestinationPath, Format(FKeyboardIDTemplate, [FBaseKeyboardID]), FTargets);
|
|
try
|
|
//
|
|
// These parameters apply to .kmn and .kps so set them even though
|
|
// ImportKeyboard will overwrite the template .kmn
|
|
//
|
|
|
|
FTemplate.Name := Format(FNameTemplate, [FBaseName]);
|
|
FTemplate.Copyright := FCopyright;
|
|
FTemplate.FullCopyright := FFullCopyright;
|
|
FTemplate.Author := FAuthor;
|
|
FTemplate.Description := FDescription;
|
|
FTemplate.Version := FVersion;
|
|
FTemplate.IncludeIcon := True;
|
|
|
|
//
|
|
// Set languages in package
|
|
//
|
|
|
|
if FBCP47Tags = '' then
|
|
FBCP47Tags := FindBCP47TagForKLID;
|
|
FTemplate.BCP47Tags := FBCP47Tags;
|
|
|
|
//
|
|
// Creates a .kmn, .kvk, .ico, .kps and any other generic files
|
|
//
|
|
try
|
|
FTemplate.Generate;
|
|
except
|
|
on E:EKeyboardProjectTemplate do
|
|
Exit(Fail('Unable to generate template: '+E.Message));
|
|
end;
|
|
|
|
FProjectFilename := FTemplate.ProjectFilename;
|
|
|
|
if FTemplate.OSKFilename = '' then
|
|
begin
|
|
// We need an OSK file in order to generate the touch layout. OSKFilename
|
|
// will be empty if the user selects a touch-only target. So we'll use a
|
|
// temp file in this scenario.
|
|
DestinationOSKTempFile := TTempFileManager.Get(Ext_VisualKeyboardSource);
|
|
DestinationOSKFilename := DestinationOSKTempFile.Name;
|
|
end
|
|
else
|
|
DestinationOSKFilename := FTemplate.OSKFilename;
|
|
|
|
|
|
// Run importkeyboard into destination file; this replaces the keyboard template
|
|
// file that has been generated
|
|
if not ImportKeyboard(FTemplate.KeyboardFilename, DestinationOSKFilename) then
|
|
Exit(Fail('Unable to run importkeyboard on '+FSourceKLID));
|
|
|
|
// Replace .ico with a new one based on the language id
|
|
// TODO: this goes in the keyboard project template generation I think
|
|
if FTemplate.IconFilename <> '' then
|
|
if not GenerateIcon(FTemplate.IconFilename) then
|
|
Exit(Fail('Unable to generate an icon for '+FTemplate.KeyboardFilename));
|
|
|
|
// Load the source .kmn and add bitmap, copyright, visualkeyboard, touch layout fields
|
|
// note, we use FTemplate.OSKFilename here deliberately, because we only
|
|
// generate OSK-related fields if they are in use
|
|
InjectSystemStores(FTemplate.KeyboardFilename, FTemplate.OSKFilename, FTemplate.IconFilename, FTemplate.TouchLayoutFilename);
|
|
|
|
if FTemplate.TouchLayoutFilename <> '' then
|
|
begin
|
|
// Take the generated OSK and convert it into a default touch layout
|
|
if not ConvertOSKToTouchLayout(DestinationOSKFilename, FTemplate.TouchLayoutFilename) then
|
|
Exit(Fail('Unable to create a default touch layout based on the OSK for '+FTemplate.KeyboardFilename));
|
|
end;
|
|
finally
|
|
FreeAndNil(FTemplate);
|
|
FreeAndNil(DestinationOSKTempFile);
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
class function TImportWindowsKeyboard.FindBCP47TagForKLID(KLID: string): string;
|
|
var
|
|
buf: array[0..8] of char;
|
|
FLanguageID: Word;
|
|
begin
|
|
FLanguageID := LOWORD(StrToInt('$'+KLID));
|
|
if GetLocaleInfo(FLanguageID, LOCALE_SISO639LANGNAME, buf, 8) > 0
|
|
then Result := buf
|
|
else Result := '';
|
|
end;
|
|
|
|
function TImportWindowsKeyboard.FindBCP47TagForKLID: string;
|
|
begin
|
|
Result := FindBCP47TagForKLID(FSourceKLID);
|
|
end;
|
|
|
|
procedure TImportWindowsKeyboard.InjectSystemStores(const KeyboardFilename, OSKFilename, IconFilename, TouchLayoutFilename: string);
|
|
var
|
|
kp: TKeyboardParser;
|
|
sl: TStringList;
|
|
begin
|
|
kp := TKeyboardParser.Create;
|
|
try
|
|
kp.FileName := KeyboardFilename;
|
|
kp.LoadFromFile(KeyboardFilename);
|
|
if IconFilename <> '' then
|
|
kp.Features.Add(kfIcon);
|
|
if OSKFilename <> '' then
|
|
kp.Features.Add(kfOSK);
|
|
if (TouchKeymanTargets+[ktAny]) * FTargets <> [] then
|
|
kp.Features.Add(kfTouchLayout);
|
|
// TODO: Are these file settings actually doing anything? Or is it controlled
|
|
// entirely by kp.Features.Add -- which could cause this to fall over a little
|
|
// if we change filenames for any reason in the future
|
|
kp.SetSystemStoreValue(ssName, Format(FNameTemplate, [FBaseName]));
|
|
kp.SetSystemStoreValue(ssVersion, SKeymanKeyboardVersion);
|
|
if OSKFilename <> '' then
|
|
kp.SetSystemStoreValue(ssVisualKeyboard, ExtractFileName(OSKFilename));
|
|
if IconFilename <> '' then
|
|
kp.SetSystemStoreValue(ssBitmap, ExtractFilename(IconFilename));
|
|
kp.SetSystemStoreValue(ssLayoutFile, ExtractFileName(TouchLayoutFilename));
|
|
kp.SetSystemStoreValue(ssTargets, KeymanTargetsToString(FTargets));
|
|
kp.SetSystemStoreValue(ssCopyright, FCopyright);
|
|
if FVersion <> '' then
|
|
kp.SetSystemStoreValue(ssKeyboardVersion, FVersion);
|
|
if FAuthor <> '' then
|
|
kp.InitialComment := kp.InitialComment + 'Run by: ' + FAuthor + #13#10;
|
|
|
|
sl := TStringList.Create;
|
|
try
|
|
sl.Text := kp.KeyboardText;
|
|
sl.SaveToFile(KeyboardFilename, TEncoding.UTF8);
|
|
finally
|
|
sl.Free;
|
|
end;
|
|
finally
|
|
kp.Free;
|
|
end;
|
|
end;
|
|
|
|
function TImportWindowsKeyboard.ImportKeyboard(const DestinationFilename, DestinationKVKSFilename: string): Boolean;
|
|
var
|
|
ik: TImportKeyboardDLL;
|
|
sl: TStringList;
|
|
ss: TStringStream;
|
|
begin
|
|
ik := TImportKeyboardDLL.Create(FSourceKLID);
|
|
try
|
|
try
|
|
ik.Execute;
|
|
except
|
|
on E:EImportKeyboardDLL do
|
|
begin
|
|
writeln(E.Message);
|
|
Exit(False);
|
|
end;
|
|
end;
|
|
|
|
// Still need a BOM for .kmn ... for now
|
|
sl := TStringList.Create;
|
|
try
|
|
sl.Text := ik.KMN;
|
|
sl.SaveToFile(DestinationFilename, TEncoding.UTF8);
|
|
finally
|
|
sl.Free;
|
|
end;
|
|
|
|
// No BOM for .kvks
|
|
ss := TStringStream.Create(ik.KVKS, TEncoding.UTF8);
|
|
try
|
|
ss.SaveToFile(DestinationKVKSFilename);
|
|
finally
|
|
ss.Free;
|
|
end;
|
|
|
|
finally
|
|
ik.Free;
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
function TImportWindowsKeyboard.GenerateIcon(
|
|
const IconFilename: string): Boolean;
|
|
begin
|
|
Result := TKeyboardIconGenerator.GenerateIcon(FBCP47Tags, IconFilename, 0);
|
|
end;
|
|
|
|
function TImportWindowsKeyboard.ConvertOSKToTouchLayout(const OSKFilename, TouchLayoutFilename: string): Boolean;
|
|
var
|
|
converter: TVisualKeyboardToTouchLayoutConverter;
|
|
FNewLayout: string;
|
|
ss: TStringStream;
|
|
begin
|
|
converter := TVisualKeyboardToTouchLayoutConverter.Create(OSKFilename);
|
|
try
|
|
if converter.Execute(FNewLayout) then
|
|
begin
|
|
ss := TStringStream.Create(FNewLayout, TEncoding.UTF8);
|
|
try
|
|
ss.SaveToFile(TouchLayoutFilename);
|
|
finally
|
|
ss.Free;
|
|
end;
|
|
end
|
|
else
|
|
Exit(False);
|
|
finally
|
|
converter.Free;
|
|
end;
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
end.
|