From 9a14039e8cb1f91e2d7c45e5cd54a9f315433472 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 22 Nov 2023 14:16:32 +1000 Subject: [PATCH 001/299] =?UTF-8?q?chore:=20establish=20epic/windows-updat?= =?UTF-8?q?es=20=F0=9F=92=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- windows/src/TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/src/TODO.md b/windows/src/TODO.md index 2a0d49470e..301a8409db 100644 --- a/windows/src/TODO.md +++ b/windows/src/TODO.md @@ -3,3 +3,4 @@ 2. Suggestion: the 'repair profiles' pattern should look at the list of enabled Keyman keyboards vs the list of profiles in HKCU\Control Panel\International and fixup discrepancies there. This should probably be an automatic task. This will probably fix the issues that Makara noted around missing keyboards across multiple user profiles etc. 3. uninstall and reinstall of package in same ELEVATED session of kmshell causes error 4. Review Joshua's corrections document +5. Implement Windows Updates Stage 1 -- https://github.com/keymanapp/keyman/issues/10038 \ No newline at end of file From 8756e4016c163aa74f4a7d1f307a43372fa3d623 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 22 Nov 2023 14:23:30 +1000 Subject: [PATCH 002/299] feat(windows): Online Update check stream update data Starts to refactor the online update check in Windows so that the results are stored to disk and can be checked at any time. This commit: * removes keepintouch frame * adds update frame * adds serialization of update check data * presents the serialized update check data in the update frame Online updates won't currently work. --- .../Keyman.System.UpdateCheckResponse.pas | 41 +++++- common/windows/delphi/general/KeymanPaths.pas | 9 ++ windows/src/desktop/kmshell/kmshell.dpr | 6 +- windows/src/desktop/kmshell/kmshell.dproj | 2 + .../main/Keyman.System.UpdateCheckStorage.pas | 52 ++++++++ .../kmshell/main/OnlineUpdateCheck.pas | 106 +++++++++------ windows/src/desktop/kmshell/main/UfrmMain.pas | 17 +++ .../kmshell/render/UpdateXMLRenderer.pas | 91 +++++++++++++ windows/src/desktop/kmshell/xml/config.css | 10 +- windows/src/desktop/kmshell/xml/config.js | 2 +- windows/src/desktop/kmshell/xml/keyman.xsl | 4 +- .../kmshell/xml/keyman_keepintouch.xsl | 23 ---- .../src/desktop/kmshell/xml/keyman_menu.xsl | 6 +- .../desktop/kmshell/xml/keyman_support.xsl | 1 - .../src/desktop/kmshell/xml/keyman_update.xsl | 125 ++++++++++++++++++ 15 files changed, 409 insertions(+), 86 deletions(-) create mode 100644 windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas create mode 100644 windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas delete mode 100644 windows/src/desktop/kmshell/xml/keyman_keepintouch.xsl create mode 100644 windows/src/desktop/kmshell/xml/keyman_update.xsl diff --git a/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas b/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas index 918c396411..adc90ab152 100644 --- a/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas +++ b/common/windows/delphi/general/Keyman.System.UpdateCheckResponse.pas @@ -35,6 +35,7 @@ type TUpdateCheckResponse = record private + FOriginalData: string; FInstallSize: Int64; FInstallURL: string; FNewVersion: string; @@ -46,9 +47,13 @@ type FFileName: string; function ParseKeyboards(nodes: TJSONObject): Boolean; function ParseLanguages(i: Integer; v: TJSONValue): Boolean; + function DoParse(const message, app, currentVersion: string): Boolean; public function Parse(const message: AnsiString; const app, currentVersion: string): Boolean; + procedure SaveToFile(const Filename: string); + function LoadFromFile(const Filename, app, currentVersion: string): Boolean; + property CurrentVersion: string read FCurrentVersion; property NewVersion: string read FNewVersion; property NewVersionWithTag: string read FNewVersionWithTag; @@ -58,25 +63,32 @@ type property ErrorMessage: string read FErrorMessage; property Status: TUpdateCheckResponseStatus read FStatus; property Packages: TUpdateCheckResponsePackages read FPackages; + property OriginalData: string read FOriginalData; end; implementation uses + System.Classes, System.Generics.Collections, versioninfo; { TUpdateCheckResponse } function TUpdateCheckResponse.Parse(const message: AnsiString; const app, currentVersion: string): Boolean; +begin + Result := DoParse(string(UTF8String(message)), app, currentVersion); +end; + +function TUpdateCheckResponse.DoParse(const message, app, currentVersion: string): Boolean; var node, doc: TJSONObject; begin + FOriginalData := message; FCurrentVersion := currentVersion; FStatus := ucrsNoUpdate; - // TODO: test with UTF8 characters in response - doc := TJSONObject.ParseJSONValue(UTF8String(message)) as TJSONObject; + doc := TJSONObject.ParseJSONValue(UTF8String(FOriginalData)) as TJSONObject; if doc = nil then begin FErrorMessage := Format('Invalid response:'#13#10'%s', [string(message)]); @@ -168,4 +180,29 @@ begin Result := True; end; +function TUpdateCheckResponse.LoadFromFile(const Filename, app, currentVersion: string): Boolean; +var + ss: TStringStream; +begin + ss := TStringStream.Create('', TEncoding.UTF8); + try + ss.LoadFromFile(Filename); + Result := DoParse(ss.DataString, app, currentVersion); + finally + ss.Free; + end; +end; + +procedure TUpdateCheckResponse.SaveToFile(const Filename: string); +var + ss: TStringStream; +begin + ss := TStringStream.Create(FOriginalData, TEncoding.UTF8); + try + ss.SaveToFile(Filename); + finally + ss.Free; + end; +end; + end. diff --git a/common/windows/delphi/general/KeymanPaths.pas b/common/windows/delphi/general/KeymanPaths.pas index 88ef8282ef..76251c3b51 100644 --- a/common/windows/delphi/general/KeymanPaths.pas +++ b/common/windows/delphi/general/KeymanPaths.pas @@ -16,6 +16,8 @@ type const S_CEF_LibCef = 'libcef.dll'; const S_CEF_SubProcess = 'kmbrowserhost.exe'; const S_CustomisationFilename = 'desktop_pro.pxx'; + + const S_KeymanAppData_UpdateCache = 'Keyman\UpdateCache\'; public const S_KMShell = 'kmshell.exe'; const S_TSysInfoExe = 'tsysinfo.exe'; @@ -26,8 +28,10 @@ type const S_FallbackKeyboardPath = 'Keyboards\'; const S__Package = '_Package\'; const S_MCompileExe = 'mcompile.exe'; + const S_UpdateCache_Metadata = 'cache.json'; class function ErrorLogPath(const app: string = ''): string; static; class function KeymanHelpPath(const HelpFile: string): string; static; + class function KeymanUpdateCachePath(const filename: string = ''): string; static; class function KeymanDesktopInstallPath(const filename: string = ''): string; static; class function KeymanEngineInstallPath(const filename: string = ''): string; static; class function KeymanDesktopInstallDir: string; static; @@ -417,6 +421,11 @@ begin Result := ''; end; +class function TKeymanPaths.KeymanUpdateCachePath(const filename: string): string; +begin + Result := GetFolderPath(CSIDL_LOCAL_APPDATA) + S_KeymanAppData_UpdateCache + filename; +end; + class function TKeymanPaths.RunningFromSource(var keyman_root: string): Boolean; begin // On developer machines, if we are running within the source repo, then use diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index befb47fcf9..1497982ccb 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -176,7 +176,9 @@ uses TaskScheduler_TLB in '..\..\global\delphi\winapi\TaskScheduler_TLB.pas', Keyman.Configuration.System.HttpServer.App.TextEditorFonts in 'startup\help\Keyman.Configuration.System.HttpServer.App.TextEditorFonts.pas', Keyman.Configuration.System.HttpServer.App.Locale in 'web\Keyman.Configuration.System.HttpServer.App.Locale.pas', - Keyman.System.AndroidStringToKeymanLocaleString in '..\..\..\..\common\windows\delphi\general\Keyman.System.AndroidStringToKeymanLocaleString.pas'; + Keyman.System.AndroidStringToKeymanLocaleString in '..\..\..\..\common\windows\delphi\general\Keyman.System.AndroidStringToKeymanLocaleString.pas', + UpdateXMLRenderer in 'render\UpdateXMLRenderer.pas', + Keyman.System.UpdateCheckStorage in 'main\Keyman.System.UpdateCheckStorage.pas'; {$R VERSION.RES} {$R manifest.res} @@ -198,7 +200,7 @@ begin Application.Initialize; Application.Title := 'Keyman Configuration'; Application.CreateForm(TmodWebHttpServer, modWebHttpServer); - try + try Run; finally FreeAndNil(modWebHttpServer); diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index 5d716d8c4a..71c91d9e56 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -353,6 +353,8 @@ + + Cfg_2 diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas new file mode 100644 index 0000000000..f1a156bfa0 --- /dev/null +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas @@ -0,0 +1,52 @@ +unit Keyman.System.UpdateCheckStorage; + +interface + +uses + Keyman.System.UpdateCheckResponse; + +type + TUpdateCheckStorage = class sealed + private + class function MetadataFilename: string; static; + public + class function HasUpdates: Boolean; static; + class function LoadUpdateCacheData(var data: TUpdateCheckResponse): Boolean; static; + class procedure SaveUpdateCacheData(const data: TUpdateCheckResponse); static; + end; + +implementation + +uses + System.SysUtils, + + KeymanPaths, + KeymanVersion; + +{ TUpdateCheckStorage } + +class function TUpdateCheckStorage.MetadataFilename: string; +begin + Result := TKeymanPaths.KeymanUpdateCachePath(TKeymanPaths.S_UpdateCache_Metadata); +end; + +class procedure TUpdateCheckStorage.SaveUpdateCacheData( + const data: TUpdateCheckResponse); +begin + ForceDirectories(TKeymanPaths.KeymanUpdateCachePath); + data.SaveToFile(MetadataFilename); +end; + +class function TUpdateCheckStorage.HasUpdates: Boolean; +begin + Result := FileExists(MetadataFilename); +end; + +class function TUpdateCheckStorage.LoadUpdateCacheData(var data: TUpdateCheckResponse): Boolean; +begin + Result := + HasUpdates and + data.LoadFromFile(MetadataFilename, 'bundle', CKeymanVersionInfo.Version); +end; + +end. diff --git a/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas b/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas index 15fb06f4e9..c202742813 100644 --- a/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/OnlineUpdateCheck.pas @@ -98,6 +98,7 @@ type FShowErrors: Boolean; FDownload: TOnlineUpdateCheckDownloadParams; + FCheckOnly: Boolean; function DownloadUpdates: Boolean; procedure DoDownloadUpdates(AOwner: TfrmDownloadProgress; var Result: Boolean); @@ -112,7 +113,9 @@ type public public - constructor Create(AOwner: TCustomForm; AForce, ASilent: Boolean); + function ResponseToParams(const ucr: TUpdateCheckResponse): TOnlineUpdateCheckParams; + + constructor Create(AOwner: TCustomForm; AForce, ASilent: Boolean; ACheckOnly: Boolean = False); destructor Destroy; override; function Run: TOnlineUpdateCheckResult; property ShowErrors: Boolean read FShowErrors write FShowErrors; @@ -146,6 +149,7 @@ uses KLog, keymanapi_TLB, KeymanVersion, + Keyman.System.UpdateCheckStorage, kmint, ErrorControlledRegistry, RegistryKeys, @@ -165,7 +169,7 @@ const { TOnlineUpdateCheck } -constructor TOnlineUpdateCheck.Create(AOwner: TCustomForm; AForce, ASilent: Boolean); +constructor TOnlineUpdateCheck.Create(AOwner: TCustomForm; AForce, ASilent: Boolean; ACheckOnly: Boolean); begin inherited Create; @@ -176,6 +180,7 @@ begin FSilent := ASilent; FForce := AForce; + FCheckOnly := ACheckOnly; KL.Log('TOnlineUpdateCheck.Create'); end; @@ -477,10 +482,9 @@ end; function TOnlineUpdateCheck.DoRun: TOnlineUpdateCheckResult; var flags: DWord; - i, n: Integer; - pkg: IKeymanPackage; - j: Integer; + i: Integer; ucr: TUpdateCheckResponse; + pkg: IKeymanPackage; begin {FProxyHost := ''; FProxyPort := 0;} @@ -567,45 +571,15 @@ begin begin if ucr.Parse(Response.MessageBodyAsString, 'bundle', CKeymanVersionInfo.Version) then begin - SetLength(FParams.Packages,0); - for i := Low(ucr.Packages) to High(ucr.Packages) do + ResponseToParams(ucr); + + if FCheckOnly then begin - n := kmcom.Packages.IndexOf(ucr.Packages[i].ID); - if n >= 0 then - begin - pkg := kmcom.Packages[n]; - j := Length(FParams.Packages); - SetLength(FParams.Packages, j+1); - FParams.Packages[j].NewID := ucr.Packages[i].NewID; - FParams.Packages[j].ID := ucr.Packages[i].ID; - FParams.Packages[j].Description := ucr.Packages[i].Name; - FParams.Packages[j].OldVersion := pkg.Version; - FParams.Packages[j].NewVersion := ucr.Packages[i].NewVersion; - FParams.Packages[j].DownloadSize := ucr.Packages[i].DownloadSize; - FParams.Packages[j].DownloadURL := ucr.Packages[i].DownloadURL; - FParams.Packages[j].FileName := ucr.Packages[i].FileName; - pkg := nil; - end - else - FErrorMessage := 'Unable to find package '+ucr.Packages[i].ID; - end; - - case ucr.Status of - ucrsNoUpdate: - begin - FErrorMessage := ucr.ErrorMessage; - end; - ucrsUpdateReady: - begin - FParams.Keyman.OldVersion := ucr.CurrentVersion; - FParams.Keyman.NewVersion := ucr.NewVersion; - FParams.Keyman.DownloadURL := ucr.InstallURL; - FParams.Keyman.DownloadSize := ucr.InstallSize; - FParams.Keyman.FileName := ucr.FileName; - end; - end; - - if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then + // TODO: Refactor this + TUpdateCheckStorage.SaveUpdateCacheData(ucr); + Result := FParams.Result; + end + else if (Length(FParams.Packages) > 0) or (FParams.Keyman.DownloadURL <> '') then begin if not FSilent then ShowUpdateForm @@ -651,6 +625,52 @@ begin end; end; +function TOnlineUpdateCheck.ResponseToParams(const ucr: TUpdateCheckResponse): TOnlineUpdateCheckParams; +var + i, j, n: Integer; + pkg: IKeymanPackage; +begin + SetLength(FParams.Packages,0); + for i := Low(ucr.Packages) to High(ucr.Packages) do + begin + n := kmcom.Packages.IndexOf(ucr.Packages[i].ID); + if n >= 0 then + begin + pkg := kmcom.Packages[n]; + j := Length(FParams.Packages); + SetLength(FParams.Packages, j+1); + FParams.Packages[j].NewID := ucr.Packages[i].NewID; + FParams.Packages[j].ID := ucr.Packages[i].ID; + FParams.Packages[j].Description := ucr.Packages[i].Name; + FParams.Packages[j].OldVersion := pkg.Version; + FParams.Packages[j].NewVersion := ucr.Packages[i].NewVersion; + FParams.Packages[j].DownloadSize := ucr.Packages[i].DownloadSize; + FParams.Packages[j].DownloadURL := ucr.Packages[i].DownloadURL; + FParams.Packages[j].FileName := ucr.Packages[i].FileName; + pkg := nil; + end + else + FErrorMessage := 'Unable to find package '+ucr.Packages[i].ID; + end; + + case ucr.Status of + ucrsNoUpdate: + begin + FErrorMessage := ucr.ErrorMessage; + end; + ucrsUpdateReady: + begin + FParams.Keyman.OldVersion := ucr.CurrentVersion; + FParams.Keyman.NewVersion := ucr.NewVersion; + FParams.Keyman.DownloadURL := ucr.InstallURL; + FParams.Keyman.DownloadSize := ucr.InstallSize; + FParams.Keyman.FileName := ucr.FileName; + end; + end; + + Result := FParams; +end; + procedure OnlineUpdateAdmin(OwnerForm: TCustomForm; Path: string); var Package: TOnlineUpdateCheckParamsPackage; diff --git a/windows/src/desktop/kmshell/main/UfrmMain.pas b/windows/src/desktop/kmshell/main/UfrmMain.pas index d9a6f8b441..969e85d99c 100644 --- a/windows/src/desktop/kmshell/main/UfrmMain.pas +++ b/windows/src/desktop/kmshell/main/UfrmMain.pas @@ -148,6 +148,7 @@ type procedure OpenSite(params: TStringList); procedure DoApply; procedure DoRefresh; + procedure Update_CheckNow; protected procedure FireCommand(const command: WideString; params: TStringList); override; @@ -202,6 +203,7 @@ uses UfrmTextEditor, uninstall, Upload_Settings, + UpdateXMLRenderer, utildir, utilexecute, utilkmshell, @@ -301,6 +303,7 @@ begin FXMLRenderers.Add(TOptionsXMLRenderer.Create(FXMLRenderers)); FXMLRenderers.Add(TLanguagesXMLRenderer.Create(FXMLRenderers)); FXMLRenderers.Add(TSupportXMLRenderer.Create(FXMLRenderers)); + FXMLRenderers.Add(TUpdateXMLRenderer.Create(FXMLRenderers)); xml := FXMLRenderers.RenderToString(s); sharedData.Init( @@ -345,6 +348,9 @@ begin else if command = 'support_updatecheck' then Support_UpdateCheck else if command = 'support_proxyconfig' then Support_ProxyConfig + else if command = 'update_checknow' then Update_CheckNow + + else if command = 'contact_support' then Support_ContactSupport(params) // I4390 else if command = 'opensite' then OpenSite(params) @@ -815,6 +821,17 @@ begin end; end; +procedure TfrmMain.Update_CheckNow; +begin + with TOnlineUpdateCheck.Create(Self, True, True, True) do + try + Run; + finally + Free; + end; + DoRefresh; +end; + procedure TfrmMain.TntFormCloseQuery(Sender: TObject; var CanClose: Boolean); begin inherited; diff --git a/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas b/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas new file mode 100644 index 0000000000..0c102fdca7 --- /dev/null +++ b/windows/src/desktop/kmshell/render/UpdateXMLRenderer.pas @@ -0,0 +1,91 @@ +(* + Name: UpdateXMLRenderer + Copyright: Copyright (C) SIL International. +*) +unit UpdateXMLRenderer; + +interface + +uses + XMLRenderer, + Windows; + +type + TUpdateXMLRenderer = class(TXMLRenderer) + protected + function XMLData: WideString; override; + end; + +implementation + +uses + StrUtils, + SysUtils, + VersionInfo, + kmint, + KeymanVersion, + keymanapi_TLB, + Keyman.System.LocaleStrings, + Keyman.System.UpdateCheckResponse, + Keyman.System.UpdateCheckStorage, + MessageIdentifierConsts, + MessageIdentifiers, + OnlineUpdateCheck, + utilxml; + +{ TUpdateXMLRenderer } + +function TUpdateXMLRenderer.XMLData: WideString; +var + xml: string; + ucr: TUpdateCheckResponse; + ouc: TOnlineUpdateCheck; + params: TOnlineUpdateCheckParams; + i: Integer; +begin + xml := ''; + + if TUpdateCheckStorage.LoadUpdateCacheData(ucr) then + begin + ouc := TOnlineUpdateCheck.Create(nil, False, True); + params := ouc.ResponseToParams(ucr); + + if (Params.Keyman.DownloadURL <> '') then + begin + xml := xml + + ''+ + '0'+ + IfThen(not kmcom.SystemInfo.IsAdministrator, '')+ + ''+ + ''+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_KeymanText, [Params.Keyman.NewVersion]))+''+ + ''+xmlencode(Params.Keyman.NewVersion)+''+ + ''+xmlencode(Params.Keyman.OldVersion)+''+ + ''+xmlencode(Format('%d', [Params.Keyman.DownloadSize div 1024]))+'KB'+ + ''+xmlencode(Params.Keyman.DownloadURL)+''+ + ''+ + ''; + end; + + for i := 0 to High(Params.Packages) do + begin + xml := xml + + ''+ + ''+IntToStr(i+1)+''+ + IfThen(not kmcom.SystemInfo.IsAdministrator, '')+ + ''+ + ''+xmlencode(TLocaleStrings.MsgFromIdFormat(kmcom, SKUpdate_PackageText, + [Params.Packages[i].Description, Params.Packages[i].NewVersion]))+''+ + ''+xmlencode(Params.Packages[i].NewVersion)+''+ + ''+xmlencode(Params.Packages[i].OldVersion)+''+ + ''+xmlencode(Format('%d', [Params.Packages[i].DownloadSize div 1024]))+'KB'+ + ''+xmlencode(Params.Packages[i].DownloadURL)+''+ + ''+ + ''; + end; + end; + + Result := ''+xml+''; +end; + +end. + diff --git a/windows/src/desktop/kmshell/xml/config.css b/windows/src/desktop/kmshell/xml/config.css index 88c8d22b39..704c4e6f92 100644 --- a/windows/src/desktop/kmshell/xml/config.css +++ b/windows/src/desktop/kmshell/xml/config.css @@ -1093,19 +1093,11 @@ th height: 16px; } -#keepintouch_content { +#update_content { height: 100%; overflow: hidden; } -#keepintouch_frame { - box-sizing: border-box; - width:100%; - height:100%; - border:none; - user-select: none; -} - /* QRCodes */ .qrcode { diff --git a/windows/src/desktop/kmshell/xml/config.js b/windows/src/desktop/kmshell/xml/config.js index cf761ce6c2..47b66ca89c 100644 --- a/windows/src/desktop/kmshell/xml/config.js +++ b/windows/src/desktop/kmshell/xml/config.js @@ -35,7 +35,7 @@ function windowResize() e = _$('subcontent_pro'); if(e) e.style.height = h; _$('subcontent_support').style.height = h; - _$('subcontent_keepintouch').style.height = h; + _$('subcontent_update').style.height = h; } } diff --git a/windows/src/desktop/kmshell/xml/keyman.xsl b/windows/src/desktop/kmshell/xml/keyman.xsl index 94c7ed7e95..a774e0aa9c 100644 --- a/windows/src/desktop/kmshell/xml/keyman.xsl +++ b/windows/src/desktop/kmshell/xml/keyman.xsl @@ -12,7 +12,7 @@ - + @@ -50,7 +50,7 @@
-
+
diff --git a/windows/src/desktop/kmshell/xml/keyman_keepintouch.xsl b/windows/src/desktop/kmshell/xml/keyman_keepintouch.xsl deleted file mode 100644 index 780166619f..0000000000 --- a/windows/src/desktop/kmshell/xml/keyman_keepintouch.xsl +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - -
- - -
- -
-
- -
-
-
-
\ No newline at end of file diff --git a/windows/src/desktop/kmshell/xml/keyman_menu.xsl b/windows/src/desktop/kmshell/xml/keyman_menu.xsl index 4fc1b0f225..99d5e05fac 100644 --- a/windows/src/desktop/kmshell/xml/keyman_menu.xsl +++ b/windows/src/desktop/kmshell/xml/keyman_menu.xsl @@ -9,20 +9,20 @@ - + - + diff --git a/windows/src/desktop/kmshell/xml/keyman_support.xsl b/windows/src/desktop/kmshell/xml/keyman_support.xsl index d472f88155..e33d031a47 100644 --- a/windows/src/desktop/kmshell/xml/keyman_support.xsl +++ b/windows/src/desktop/kmshell/xml/keyman_support.xsl @@ -41,7 +41,6 @@