From e83061ef807ddea676eb7e352ec8f3743014c32d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 21 Dec 2020 08:20:28 +1100 Subject: [PATCH] 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. --- .../keymankeyboardlanguageinstalled.pas | 5 +- .../keymankeyboardlanguagesinstalled.pas | 10 +- ...stem.MitigateWin10_1803LanguageInstall.pas | 36 ++++- ...stem.Process.KPInstallKeyboardLanguage.pas | 144 +++++++++--------- .../processes/keyboard/kpinstallkeyboard.pas | 35 +++-- 5 files changed, 141 insertions(+), 89 deletions(-) diff --git a/windows/src/engine/kmcomapi/com/keyboardlanguages/keymankeyboardlanguageinstalled.pas b/windows/src/engine/kmcomapi/com/keyboardlanguages/keymankeyboardlanguageinstalled.pas index b1da8d3b7c..c04cf8bc4c 100644 --- a/windows/src/engine/kmcomapi/com/keyboardlanguages/keymankeyboardlanguageinstalled.pas +++ b/windows/src/engine/kmcomapi/com/keyboardlanguages/keymankeyboardlanguageinstalled.pas @@ -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 diff --git a/windows/src/engine/kmcomapi/com/keyboardlanguages/keymankeyboardlanguagesinstalled.pas b/windows/src/engine/kmcomapi/com/keyboardlanguages/keymankeyboardlanguagesinstalled.pas index 16e26156ca..d226cb8057 100644 --- a/windows/src/engine/kmcomapi/com/keyboardlanguages/keymankeyboardlanguagesinstalled.pas +++ b/windows/src/engine/kmcomapi/com/keyboardlanguages/keymankeyboardlanguagesinstalled.pas @@ -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); diff --git a/windows/src/engine/kmcomapi/processes/keyboard/Keyman.System.MitigateWin10_1803LanguageInstall.pas b/windows/src/engine/kmcomapi/processes/keyboard/Keyman.System.MitigateWin10_1803LanguageInstall.pas index 06eb99733a..615f860692 100644 --- a/windows/src/engine/kmcomapi/processes/keyboard/Keyman.System.MitigateWin10_1803LanguageInstall.pas +++ b/windows/src/engine/kmcomapi/processes/keyboard/Keyman.System.MitigateWin10_1803LanguageInstall.pas @@ -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. 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 2017a84fae..de9b2a1ac2 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 @@ -22,7 +22,8 @@ type /// a temporary keyboard if necessary to establish a transient LangID. /// Current User. /// True if a language ID can be found - /// A canonical BCP47 tag to install + /// A canonical BCP47 tag to install. May be updated + /// for compatibility reasons; see #1285. /// LangID found that corresponds to BCP47Tag /// 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 /// 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. - 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; diff --git a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas index 9dc7404bf4..7c879c4055 100644 --- a/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas +++ b/windows/src/engine/kmcomapi/processes/keyboard/kpinstallkeyboard.pas @@ -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