mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-08 18:05:32 +00:00
fix(windows): Warn if we reach maximum transient languages
Fixes #3749. Fixes #3759. Adds a warning dialog when user attempts to add a transient language to a keyboard but the maximum number of transient languages is already installed. If this issue arises when a user is installing a keyboard for the first time, Keyman instead installs the keyboard for the user's default language. This may be a little confusing, but the error condition is difficult to explain and non-technical users will probably be stuck and need to install under their default language in any case. Happy to receive any pushback on this decision.
This commit is contained in:
parent
d10be475c0
commit
246cb70840
5 changed files with 166 additions and 30 deletions
|
|
@ -31,6 +31,9 @@ type
|
|||
/// <summary>Helper function to get default BCP47 tag for an installed keyboard</summary>
|
||||
class function GetFirstLanguage(Keyboard: IKeymanKeyboardInstalled): string; overload;
|
||||
class function GetFirstLanguage(Keyboard: IKeymanKeyboardFile): string; overload;
|
||||
|
||||
/// <summary>Get the BCP47 tag for the user's default language</summary>
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1068,6 +1068,12 @@ keyboard that you use in Windows. Keyman keyboards will adapt automatically to
|
|||
<!-- String Type: FormatString -->
|
||||
<string name="SKKeyboardPartOfPackage" comment="Notice of need to uninstall an entire package">The keyboard '%0:s' is part of package '%1:s'. You must uninstall the entire package. Continue?</string>
|
||||
|
||||
<!-- PArameters: %0:s = BCP 47 code -->
|
||||
<!-- Context: Formatted Messages -->
|
||||
<!-- Introduced: 14.0.211 -->
|
||||
<!-- String Type: FormatString -->
|
||||
<string name="SKInstallLanguageTransientLimit" comment="Message shown when Keyman is unable to install a Windows language for a keyboard">Unable to install keyboard language; Windows has a limit of 4 custom 'transient' languages, and you may have reached this limit.</string>
|
||||
|
||||
<!-- Parameters: %0:s = Package Name -->
|
||||
<!-- Context: Formatted Messages -->
|
||||
<!-- Introduced: 7.0.230.0 -->
|
||||
|
|
|
|||
|
|
@ -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
|
||||
//
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue