diff --git a/windows/src/desktop/kmshell/install/Keyman.Configuration.System.TIPMaintenance.pas b/windows/src/desktop/kmshell/install/Keyman.Configuration.System.TIPMaintenance.pas
index 5161bfd93c..35c683dc56 100644
--- a/windows/src/desktop/kmshell/install/Keyman.Configuration.System.TIPMaintenance.pas
+++ b/windows/src/desktop/kmshell/install/Keyman.Configuration.System.TIPMaintenance.pas
@@ -31,6 +31,9 @@ type
/// Helper function to get default BCP47 tag for an installed keyboard
class function GetFirstLanguage(Keyboard: IKeymanKeyboardInstalled): string; overload;
class function GetFirstLanguage(Keyboard: IKeymanKeyboardFile): string; overload;
+
+ /// Get the BCP47 tag for the user's default language
+ class function GetUserDefaultLanguage: string; static;
private
class function GetKeyboardLanguage(const KeyboardID,
BCP47Tag: string): IKeymanKeyboardLanguageInstalled; static;
@@ -93,6 +96,7 @@ begin
if lang = nil then
Exit(False);
+ // TODO: can this fail?
(lang as IKeymanKeyboardLanguageInstalled2).InstallTip(LangID, KeyboardToRemove);
Result := True;
end;
@@ -106,6 +110,7 @@ begin
if lang = nil then
Exit(False);
+ // TODO: can this fail?
(lang as IKeymanKeyboardLanguageInstalled2).RegisterTip(LangID);
Result := True;
end;
@@ -117,6 +122,7 @@ var
RegistrationRequired: WordBool;
TemporaryKeyboardID: WideString;
LangID: Integer;
+ childExitCode: Cardinal;
begin
lang := GetKeyboardLanguage(KeyboardID, (kmcom as IKeymanBCP47Canonicalization).GetCanonicalTag(BCP47Tag));
if lang = nil then
@@ -131,13 +137,25 @@ begin
LangID := 0;
RegistrationRequired := False;
- if (lang as IKeymanKeyboardLanguageInstalled2).FindInstallationLangID(LangID, TemporaryKeyboardID, RegistrationRequired, kifInstallTransientLanguage) then
+ if not (lang as IKeymanKeyboardLanguageInstalled2).FindInstallationLangID(LangID, TemporaryKeyboardID, RegistrationRequired, kifInstallTransientLanguage) then
begin
- if RegistrationRequired then
- begin
- WaitForElevatedConfiguration(0, '-register-tip '+IntToHex(LangID,4)+' "'+KeyboardID+'" "'+lang.BCP47Code+'"');
- end;
- TUtilExecute.WaitForProcess('"'+ParamStr(0)+'" -install-tip '+IntToHex(LangID,4)+' "'+KeyboardID+'" "'+lang.BCP47Code+'" "'+TemporaryKeyboardID+'"', GetCurrentDir);
+ // We were not able to find a TIP, perhaps all transient TIPs have been used
+ Exit(False);
+ end;
+
+ if RegistrationRequired then
+ begin
+ // This calls back into TTIPMaintenance.RegisterTip
+ if WaitForElevatedConfiguration(0, '-register-tip '+IntToHex(LangID,4)+' "'+KeyboardID+'" "'+lang.BCP47Code+'"') <> 0 then
+ Exit(False);
+ end;
+
+ // This calls back into TTIPMaintenance.InstallTip
+ if not TUtilExecute.WaitForProcess('"'+ParamStr(0)+'" -install-tip '+IntToHex(LangID,4)+' "'+KeyboardID+'" "'+lang.BCP47Code+'" "'+TemporaryKeyboardID+'"',
+ GetCurrentDir, childExitCode) or
+ (childExitCode <> 0) then
+ begin
+ Exit(False);
end;
Result := True;
@@ -178,18 +196,23 @@ begin
Result := False;
end;
+class function TTIPMaintenance.GetUserDefaultLanguage: string;
+begin
+ Result := TLanguageCodeUtils.TranslateWindowsLanguagesToBCP47(HKLToLanguageID(GetDefaultHKL))
+end;
+
class function TTIPMaintenance.GetFirstLanguage(Keyboard: IKeymanKeyboardFile): string;
begin
if Keyboard.Languages.Count > 0
then Result := Keyboard.Languages[0].BCP47Code
- else Result := TLanguageCodeUtils.TranslateWindowsLanguagesToBCP47(HKLToLanguageID(GetDefaultHKL));
+ else Result := GetUserDefaultLanguage;
end;
class function TTIPMaintenance.GetFirstLanguage(Keyboard: IKeymanKeyboardInstalled): string;
begin
if Keyboard.Languages.Count > 0
then Result := Keyboard.Languages[0].BCP47Code
- else Result := TLanguageCodeUtils.TranslateWindowsLanguagesToBCP47(HKLToLanguageID(GetDefaultHKL));
+ else Result := GetUserDefaultLanguage;
end;
class function TTIPMaintenance.GetKeyboardLanguage(const KeyboardID,
diff --git a/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas b/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
index 60ed59279b..48386b9369 100644
--- a/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
+++ b/windows/src/desktop/kmshell/install/UfrmInstallKeyboard.pas
@@ -97,7 +97,7 @@ type
procedure DeleteFileReferences;
procedure CheckLogFileForWarnings(const Filename: string; Silent: Boolean);
function CleanupPaths(var xml: string): string;
- procedure InstallTipForKeyboard(const BCP47Tag: string);
+ function InstallTipForKeyboard(const BCP47Tag: string): Boolean;
protected
procedure FireCommand(const command: WideString; params: TStringList); override;
public
@@ -299,7 +299,10 @@ begin
if WaitForElevatedConfiguration(Handle, '-log "'+t.Name+'" -s -i "'+FInstallFile+'='+BCP47Tag+'" -nowelcome') = 0 then
begin
// install the keyboard tip
- InstallTipForKeyboard(BCP47Tag);
+ if not InstallTipForKeyboard(BCP47Tag) then
+ Exit(False);
+
+ CheckForMitigationWarningFor_Win10_1803(False, '');
ModalResult := mrOk;
end
else
@@ -381,7 +384,11 @@ begin
kmcom.Keyboards.Apply;
kmcom.Keyboards.Refresh;
FInstalledKeyboard := (FKeyboard as IKeymanKeyboardFile2).Install2(True);
- InstallTipForKeyboard(BCP47Tag);
+ if not InstallTipForKeyboard(BCP47Tag) then
+ begin
+ // TODO can we return a failure code?
+ Exit;
+ end;
CheckForMitigationWarningFor_Win10_1803(FSilent, ALogFile);
end
else
@@ -441,7 +448,10 @@ begin
kmcom.Refresh;
- InstallTipForKeyboard(BCP47Tag);
+ if not InstallTipForKeyboard(BCP47Tag) then
+ begin
+ Exit;
+ end;
CheckForMitigationWarningFor_Win10_1803(FSilent, ALogFile);
end;
@@ -467,28 +477,51 @@ begin
ModalResult := mrOk;
end;
-procedure TfrmInstallKeyboard.InstallTipForKeyboard(const BCP47Tag: string);
-var
- i: Integer;
+function TfrmInstallKeyboard.InstallTipForKeyboard(const BCP47Tag: string): Boolean;
+ function DoInstallTipForKeyboard(const BCP47Tag: string): Boolean;
+ var
+ i: Integer;
+ begin
+ // Install the TIP for the current user
+ if Assigned(FKeyboard) then
+ begin
+ if BCP47Tag <> ''
+ then Result := TTIPMaintenance.DoInstall(FKeyboard.ID, BCP47Tag)
+ else Result := TTIPMaintenance.DoInstall(FKeyboard.ID, TTIPMaintenance.GetFirstLanguage(FKeyboard));
+ end
+ else
+ begin
+ if (FPackage.Keyboards.Count = 1) and (BCP47Tag <> '') then
+ Result := TTIPMaintenance.DoInstall(FPackage.Keyboards[0].ID, BCP47Tag)
+ else
+ begin
+ Result := True;
+ for i := 0 to FPackage.Keyboards.Count - 1 do
+ // For a multi-keyboard package, it's hard to know what to do!
+ if not TTIPMaintenance.DoInstall(FPackage.Keyboards[i].ID,
+ TTIPMaintenance.GetFirstLanguage(FPackage.Keyboards[i] as IKeymanKeyboardFile)) then
+ Result := False;
+ end;
+ end;
+ end;
begin
// Ensure keyboard is recorded in CU registry before we install the TIP, otherwise it will be marked as disabled by default,
// as installing the TIP adds CU registry settings, potentially confusing the keyboard settings
kmcom.Refresh;
kmcom.Apply;
- // Install the TIP for the current user
- if Assigned(FKeyboard) then
+
+ Result := DoInstallTipForKeyboard(BCP47Tag);
+ if not Result then
begin
- if BCP47Tag <> ''
- then TTIPMaintenance.DoInstall(FKeyboard.ID, BCP47Tag)
- else TTIPMaintenance.DoInstall(FKeyboard.ID, TTIPMaintenance.GetFirstLanguage(FKeyboard));
- end
- else
- begin
- if (FPackage.Keyboards.Count = 1) and (BCP47Tag <> '') then
- TTIPMaintenance.DoInstall(FPackage.Keyboards[0].ID, BCP47Tag)
- else
- for i := 0 to FPackage.Keyboards.Count - 1 do
- TTIPMaintenance.DoInstall(FPackage.Keyboards[i].ID, TTIPMaintenance.GetFirstLanguage(FPackage.Keyboards[i] as IKeymanKeyboardFile));
+ // We'll silently fall back to installing under the default language;
+ // they may have hit the limit of custom languages but an error message
+ // is likely to be very confusing.
+ Result := DoInstallTipForKeyboard(TTIPMaintenance.GetUserDefaultLanguage);
+ if not Result then
+ begin
+ if not FSilent then
+ ShowMessage(MsgFromIdFormat(SKInstallLanguageTransientLimit, [BCP47Tag]));
+ end;
end;
end;
diff --git a/windows/src/desktop/kmshell/install/UfrmInstallKeyboardLanguage.pas b/windows/src/desktop/kmshell/install/UfrmInstallKeyboardLanguage.pas
index 87966567ce..0f3e98f93b 100644
--- a/windows/src/desktop/kmshell/install/UfrmInstallKeyboardLanguage.pas
+++ b/windows/src/desktop/kmshell/install/UfrmInstallKeyboardLanguage.pas
@@ -135,6 +135,8 @@ uses
Keyman.System.LanguageCodeUtils,
Keyman.Configuration.System.TIPMaintenance,
Keyman.UI.UfrmProgress,
+ MessageIdentifierConsts,
+ MessageIdentifiers,
BCP47Tag,
GetOSVersion,
@@ -148,7 +150,13 @@ uses
function InstallKeyboardLanguage(Owner: TForm; const KeyboardID, ISOCode: string; Silent: Boolean): Boolean;
begin
Result := TTIPMaintenance.DoInstall(KeyboardID, ISOCode);
- if Result then
+ if not Result then
+ begin
+ if not Silent then
+ ShowMessage(MsgFromIdFormat(SKInstallLanguageTransientLimit, [ISOCode]));
+ Exit;
+ end
+ else
CheckForMitigationWarningFor_Win10_1803(Silent, '');
Result := True;
@@ -233,7 +241,11 @@ begin
then FCode := FCustomLanguage.Tag // Using a custom code
else FCode := FLanguageVariant.BCP47Tag;
- TTIPMaintenance.DoInstall(FKeyboard.ID, FCode);
+ if not TTIPMaintenance.DoInstall(FKeyboard.ID, FCode) then
+ begin
+ ShowMessage(MsgFromIdFormat(SKInstallLanguageTransientLimit, [FCode]));
+ Exit(False);
+ end;
FKeyboardID := FKeyboard.ID;
FKeyboard := nil;
diff --git a/windows/src/desktop/kmshell/xml/strings.xml b/windows/src/desktop/kmshell/xml/strings.xml
index cd21fc27da..f09a01d348 100644
--- a/windows/src/desktop/kmshell/xml/strings.xml
+++ b/windows/src/desktop/kmshell/xml/strings.xml
@@ -1068,6 +1068,12 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to
The keyboard '%0:s' is part of package '%1:s'. You must uninstall the entire package. Continue?
+
+
+
+
+ Unable to install keyboard language; Windows has a limit of 4 custom 'transient' languages, and you may have reached this limit.
+
diff --git a/windows/src/engine/kmcomapi/processes/keyboard/Keyman.System.Process.KPInstallKeyboardLanguage.pas b/windows/src/engine/kmcomapi/processes/keyboard/Keyman.System.Process.KPInstallKeyboardLanguage.pas
index 5f6f77c6cd..2017a84fae 100644
--- a/windows/src/engine/kmcomapi/processes/keyboard/Keyman.System.Process.KPInstallKeyboardLanguage.pas
+++ b/windows/src/engine/kmcomapi/processes/keyboard/Keyman.System.Process.KPInstallKeyboardLanguage.pas
@@ -73,6 +73,7 @@ type
function ConvertBCP47TagToLangID(Locale: string; var LangID: Integer): Boolean;
function InstallBCP47Language(const FLocaleName: string): Boolean;
function GetInputProcessorProfileMgr: ITfInputProcessorProfileMgr;
+ function AreMaximumTransientLanguagesInstalled: Boolean;
end;
implementation
@@ -113,6 +114,54 @@ begin
inherited Destroy;
end;
+function TKPInstallKeyboardLanguage.AreMaximumTransientLanguagesInstalled: Boolean;
+const
+ MAX_TRANSIENT_LANGUAGES = 4;
+var
+ r: TRegistry;
+ keys: TStringList;
+ isInstalled: array[0..MAX_TRANSIENT_LANGUAGES-1] of Boolean;
+ i: Integer;
+ v: Integer;
+ key: string;
+begin
+ for i := 0 to high(isInstalled) do isInstalled[i] := False;
+
+ // We'll use Control Panel/International/User Profile to find the full set of
+ // transient languages
+ r := TRegistry.Create;
+ keys := TStringList.Create;
+ try
+ if r.OpenKeyReadOnly(SRegKey_ControlPanelInternationalUserProfile) then
+ begin
+ r.GetKeyNames(keys);
+ for key in keys do
+ begin
+ if r.OpenKeyReadOnly('\' + SRegKey_ControlPanelInternationalUserProfile + '\' + key) then
+ begin
+ if r.ValueExists(SRegValue_CPIUP_TransientLangId) then
+ begin
+ v := r.ReadInteger(SRegValue_CPIUP_TransientLangId);
+ // Transient languages start at $2000 and go up by $400
+ v := (v - $2000) div $400;
+ if (v >= 0) and (v <= High(isInstalled)) then
+ isInstalled[v] := True;
+ end;
+ end;
+ end;
+ end;
+ finally
+ keys.Free;
+ r.Free;
+ end;
+
+ for i := 0 to High(isInstalled) do
+ if not isInstalled[i] then
+ Exit(False);
+
+ Result := True;
+end;
+
// TODO: change LangID to Winapi.Windows.LANGID
function TKPInstallKeyboardLanguage.FindInstallationLangID(const BCP47Tag: string; var LangID: Integer; var TemporaryLayoutString: string; Flags: TKPInstallKeyboardLanguageFlags): Boolean;
var
@@ -152,6 +201,19 @@ begin
Exit(False);
end;
+ //
+ // Windows only appears to support 4 transient languages: 0x2000, 0x2400,
+ // 0x2800, 0x2C00. While Powershell will add extra languages above those,
+ // it will not install TIPs for any additional languages (e.g. 0x3000+),
+ // so we should stop after 4
+ //
+
+ if AreMaximumTransientLanguagesInstalled then
+ begin
+ Warn(KMN_W_ProfileInstall_CustomLocalesNotSupported); // TODO new code
+ Exit(False);
+ end;
+
//
// Install user language with Powershell if it isn't present
//