fix(windows): ensure mitigation works with new registration strategy

The keyboard profile and registration strategy was not taking into
account the mitigation for Win10 1803 (#1285) and this meant that the
Amharic, Tigrigna and Sinhala keyboards would not install correctly.
This commit is contained in:
Marc Durdin 2020-12-21 08:20:28 +11:00
parent cbc1c8a22b
commit e83061ef80
5 changed files with 141 additions and 89 deletions

View file

@ -145,7 +145,7 @@ function TKeymanKeyboardLanguageInstalled.FindInstallationLangID(
out RegistrationRequired: WordBool; Flags: tagKeymanInstallFlags): WordBool;
var
kp: TKPInstallKeyboardLanguage;
s: string;
BCP47Code, s: string;
KPFlags: TKPInstallKeyboardLanguageFlags;
begin
LangID := Self.Get_LangID;
@ -160,7 +160,8 @@ begin
KPFlags := [];
if (Flags and kifInstallTransientLanguage) <> 0 then
Include(KPFlags, ilkInstallTransientLanguage);
Result := kp.FindInstallationLangID(Self.Get_BCP47Code, LangID, s, KPFlags);
BCP47Code := Self.Get_BCP47Code;
Result := kp.FindInstallationLangID(BCP47Code, LangID, s, KPFlags);
// We only need to register a TIP for user custom installations of languages:
// languages that are suggested already have a TIP registered, and the

View file

@ -69,6 +69,7 @@ uses
Keyman.System.CanonicalLanguageCodeUtils,
Keyman.System.LanguageCodeUtils,
Keyman.System.MitigateWin10_1803LanguageInstall,
Keyman.System.Process.KPInstallKeyboardLanguage,
BCP47Tag,
keymankeyboardlanguageinstalled,
@ -85,15 +86,22 @@ var
FKeyboardLanguage: TKeymanKeyboardLanguageInstalled;
FCanonicalBCP47Tag: string;
i: Integer;
ml: TMitigateWin10_1803.TMitigatedLanguage;
begin
// This adds an in-memory item to the array so that it can be installed
FCanonicalBCP47Tag := TCanonicalLanguageCodeUtils.FindBestTag(BCP47Tag, True);
if FCanonicalBCP47Tag = '' then
Exit(nil);
if TMitigateWin10_1803.IsMitigationRequired(FCanonicalBCP47Tag, ml) then
begin
FCanonicalBCP47Tag := ml.NewLanguage.BCP47;
(Context as TKeymanContext).Errors.AddFmt(KMN_W_ProfileInstall_Win10_1803_MitigationApplied, VarArrayOf([ml.OriginalLanguage.Name, ml.NewLanguage.Name]), kesWarning);
end;
for i := 0 to FLanguages.Count - 1 do
if SameText((FLanguages[i] as IKeymanKeyboardLanguageInstalled).BCP47Code, FCanonicalBCP47Tag) then
Exit(nil);
Exit(FLanguages[i] as IKeymanKeyboardLanguageInstalled);
FKeyboardLanguage := TKeymanKeyboardLanguageInstalled.Create(Context, FOwner, FCanonicalBCP47Tag, 0, GUID_NULL, '');
FLanguages.Add(FKeyboardLanguage);

View file

@ -24,17 +24,20 @@ type
NewLanguage: TLanguageReference;
end;
class function IsMitigationRequired(Code: LANGID; var lang: TMitigateWin10_1803.TMitigatedLanguage): Boolean;
class function IsMitigationRequired(Code: LANGID; var lang: TMitigateWin10_1803.TMitigatedLanguage): Boolean; overload;
class function IsMitigationRequired(BCP47Tag: string; var lang: TMitigateWin10_1803.TMitigatedLanguage): Boolean; overload;
end;
implementation
uses
System.SysUtils;
const
MitigatedLanguages: array[0..2] of TMitigateWin10_1803.TMitigatedLanguage = (
(OriginalLanguage: (Code: $005E; Name: 'Amharic'); NewLanguage: (BCP47: 'gez-Ethi-ET'; Name: 'Geez')),
(OriginalLanguage: (Code: $0073; Name: 'Tigrinya'); NewLanguage: (BCP47: 'gez-Ethi-ET'; Name: 'Geez')),
(OriginalLanguage: (Code: $005B; Name: 'Sinhala'); NewLanguage: (Code: $0409; Name: 'English (US)'))
(OriginalLanguage: (Code: $005E; BCP47: 'am'; Name: 'Amharic'); NewLanguage: (BCP47: 'gez-Ethi-ET'; Name: 'Geez')),
(OriginalLanguage: (Code: $0073; BCP47: 'ti'; Name: 'Tigrinya'); NewLanguage: (BCP47: 'gez-Ethi-ET'; Name: 'Geez')),
(OriginalLanguage: (Code: $005B; BCP47: 'si'; Name: 'Sinhala'); NewLanguage: (Code: $0409; Name: 'English (US)'))
);
{ TMitigateWin10_1803 }
@ -86,4 +89,29 @@ begin
Result := False;
end;
class function TMitigateWin10_1803.IsMitigationRequired(BCP47Tag: string;
var lang: TMitigateWin10_1803.TMitigatedLanguage): Boolean;
var
I: Integer;
begin
if not IsWindows10_1803_OrGreater then
Exit(False);
// We only want to look at the primary language tag for comparison
// And we assume that the tag has been canonicalized from ISO639-3 first
BCP47Tag := LowerCase(BCP47Tag);
I := Pos('-', BCP47Tag);
if I > 0 then
BCP47Tag := Copy(BCP47Tag, 1, I-1);
for I := Low(MitigatedLanguages) to High(MitigatedLanguages) do
if MitigatedLanguages[I].OriginalLanguage.BCP47 = BCP47Tag then
begin
lang := MitigatedLanguages[I];
Exit(True);
end;
// lang value undefined.
Result := False;
end;
end.

View file

@ -22,7 +22,8 @@ type
/// a temporary keyboard if necessary to establish a transient LangID.
/// Current User.</summary>
/// <returns>True if a language ID can be found</return>
/// <param name="BCP47Tag">A canonical BCP47 tag to install</param>
/// <param name="BCP47Tag">A canonical BCP47 tag to install. May be updated
/// for compatibility reasons; see #1285.</param>
/// <param name="LangID">LangID found that corresponds to BCP47Tag</param>
/// <param name="TemporaryLayoutString">If a transient LangID is installed, then
/// Windows will have added a default keyboard for the language which should be
@ -30,7 +31,8 @@ type
/// <param name="Flags">If ilkInstallTransientLanguage is included in flags,
/// will not install a transient language and will return False if no standard
/// Windows LangID can be found that corresponds to the BCP47 tag.</param>
function FindInstallationLangID(const BCP47Tag: string;
function FindInstallationLangID(
var BCP47Tag: string;
var LangID: Integer;
var TemporaryLayoutString: string;
Flags: TKPInstallKeyboardLanguageFlags): Boolean;
@ -163,92 +165,92 @@ begin
end;
// TODO: change LangID to Winapi.Windows.LANGID
function TKPInstallKeyboardLanguage.FindInstallationLangID(const BCP47Tag: string; var LangID: Integer; var TemporaryLayoutString: string; Flags: TKPInstallKeyboardLanguageFlags): Boolean;
function TKPInstallKeyboardLanguage.FindInstallationLangID(var BCP47Tag: string; var LangID: Integer; var TemporaryLayoutString: string; Flags: TKPInstallKeyboardLanguageFlags): Boolean;
var
ml: TMitigateWin10_1803.TMitigatedLanguage;
Win8Lang: TWindows8Language;
tag: string;
begin
if BCP47Tag = '' then
ErrorFmt(KMN_E_ProfileInstall_InvalidBCP47Tag, VarArrayOf([BCP47Tag]));
tag := BCP47Tag;
TemporaryLayoutString := '';
if ConvertBCP47TagToLangID(tag, LangID) then
if ConvertBCP47TagToLangID(BCP47Tag, LangID) then
begin
if TMitigateWin10_1803.IsMitigationRequired(LangID, ml) then
begin
LangID := ml.NewLanguage.Code;
BCP47Tag := ml.NewLanguage.BCP47;
WarnFmt(KMN_W_ProfileInstall_Win10_1803_MitigationApplied, VarArrayOf([ml.OriginalLanguage.Name, ml.NewLanguage.Name]));
end;
if LangID <> 0 then
Exit(True);
end;
if not (ilkInstallTransientLanguage in Flags) then
begin
// No warning, because this is to be expected
Exit(False);
end;
//
// Installing a custom language only supported with Win8 and later
//
if not FWin8Languages.IsSupported then
begin
Warn(KMN_W_ProfileInstall_CustomLocalesNotSupported);
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
//
LangID := 0;
if (BCP47Tag = '') or not InstallBCP47Language(BCP47Tag) then
begin
WarnFmt(KMN_W_ProfileInstall_FailedToInstallLanguage, VarArrayOf([BCP47Tag]));
Exit(False);
end;
//
// Find the new language ID
//
FWin8Languages.Refresh;
Win8Lang := FWin8Languages.FindClosestByBCP47Tag(BCP47Tag);
if not Assigned(Win8Lang) then
begin
WarnFmt(KMN_W_ProfileInstall_LanguageInstalledButNotFound, VarArrayOf([BCP47Tag]));
Exit(False);
end;
LangID := Win8Lang.LangID;
if Win8Lang.InputMethods.Count = 1 then
begin
TemporaryLayoutString := Win8Lang.InputMethods[0];
end
else
begin
if not (ilkInstallTransientLanguage in Flags) then
begin
// No warning, because this is to be expected
Exit(False);
end;
//
// Installing a custom language only supported with Win8 and later
//
if not FWin8Languages.IsSupported then
begin
Warn(KMN_W_ProfileInstall_CustomLocalesNotSupported);
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
//
LangID := 0;
if (tag = '') or not InstallBCP47Language(tag) then
begin
WarnFmt(KMN_W_ProfileInstall_FailedToInstallLanguage, VarArrayOf([tag]));
Exit(False);
end;
//
// Find the new language ID
//
FWin8Languages.Refresh;
Win8Lang := FWin8Languages.FindClosestByBCP47Tag(tag);
if not Assigned(Win8Lang) then
begin
WarnFmt(KMN_W_ProfileInstall_LanguageInstalledButNotFound, VarArrayOf([tag]));
Exit(False);
end;
LangID := Win8Lang.LangID;
if Win8Lang.InputMethods.Count = 1 then
begin
TemporaryLayoutString := Win8Lang.InputMethods[0];
end
else
// We'll continue on, but this is unexpected, so we won't try and uninstall the temporary input method. The user
// will have more than one input method installed.
WarnFmt(KMN_W_ProfileInstall_MoreThanOneInputMethodInstalled, VarArrayOf([Win8Lang.InputMethods.Text]));
end;
// We'll continue on, but this is unexpected, so we won't try and uninstall the temporary input method. The user
// will have more than one input method installed.
WarnFmt(KMN_W_ProfileInstall_MoreThanOneInputMethodInstalled, VarArrayOf([Win8Lang.InputMethods.Text]));
Result := True;
end;

View file

@ -1,18 +1,18 @@
(*
Name: kpinstallkeyboard
Copyright: Copyright (C) SIL International.
Documentation:
Description:
Documentation:
Description:
Create Date: 20 Jun 2006
Modified Date: 30 Apr 2015
Authors: mcdurdin
Related Files:
Dependencies:
Related Files:
Dependencies:
Bugs:
Todo:
Notes:
Bugs:
Todo:
Notes:
History: 20 Jun 2006 - mcdurdin - Initial version
01 Aug 2006 - mcdurdin - Add AutoApplyKeyman call
04 Dec 2006 - mcdurdin - Check product licence on install
@ -82,6 +82,7 @@ uses
Vcl.Graphics,
Keyman.System.LanguageCodeUtils,
Keyman.System.MitigateWin10_1803LanguageInstall,
Keyman.System.CanonicalLanguageCodeUtils,
bcp47tag,
@ -290,17 +291,18 @@ end;
function TKPInstallKeyboard.LegacyRegisterAndInstallLanguageProfile(const BCP47Tag, KeyboardID, KeyboardName, IconFileName, LanguageName: string): Boolean; // I3581 // I3619 // I3707 // I3768 // I4607
var
kpil: TKPInstallKeyboardLanguage;
TemporaryKeyboardID: string;
tag, TemporaryKeyboardID: string;
LangID: Integer;
guid: TGUID;
begin
Result := False;
kpil := TKPInstallKeyboardLanguage.Create(Context);
try
if kpil.FindInstallationLangID(BCP47Tag, LangID, TemporaryKeyboardID, []) then
tag := BCP47Tag;
if kpil.FindInstallationLangID(tag, LangID, TemporaryKeyboardID, []) then
begin
kpil.RegisterTip(KeyboardID, BCP47Tag, KeyboardID, LangID, IconFileName, '');
kpil.InstallTip(KeyboardID, BCP47Tag, LangID, guid);
kpil.RegisterTip(KeyboardID, tag, KeyboardID, LangID, IconFileName, '');
kpil.InstallTip(KeyboardID, tag, LangID, guid);
Result := True;
end;
finally
@ -326,6 +328,7 @@ var
BCP47Tag: string;
i: Integer;
kpil: TKPInstallKeyboardLanguage;
ml: TMitigateWin10_1803.TMitigatedLanguage;
type
TWSLCallback = reference to procedure(r: TRegistryErrorControlled);
@ -400,6 +403,11 @@ begin
BCP47Tag := TCanonicalLanguageCodeUtils.FindBestTag(PackageLanguageMetadata[i].ID, True);
if BCP47Tag <> '' then
begin
if TMitigateWin10_1803.IsMitigationRequired(BCP47Tag, ml) then
begin
BCP47Tag := ml.NewLanguage.BCP47;
end;
// Note: this may return a repeated tag, but FindInstallationLangID
// and RegisterTIP are idempotent, so it doesn't matter.
@ -494,6 +502,11 @@ begin
BCP47Tag := TLanguageCodeUtils.TranslateWindowsLanguagesToBCP47(FLanguages[i]);
if BCP47Tag <> '' then
begin
if TMitigateWin10_1803.IsMitigationRequired(BCP47Tag, ml) then
begin
BCP47Tag := ml.NewLanguage.BCP47;
end;
kpil := TKPInstallKeyboardLanguage.Create(Context);
try
kpil.RegisterTip(kbdname, BCP47Tag, ki.KeyboardName, FLanguages[i], FIconFileName, ''); //TODO: language name