From e1064f3bac0326ced158a21a43453ee1dc00a3ee Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Tue, 26 Sep 2023 11:43:50 -0700 Subject: [PATCH 1/3] avalonia: remove workaround for devtools and app lifetime The issue https://github.com/AvaloniaUI/Avalonia/issues/10296 has been fixed so we can now remove this workaround. --- src/shared/Core/UI/AvaloniaUi.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/shared/Core/UI/AvaloniaUi.cs b/src/shared/Core/UI/AvaloniaUi.cs index 65b681884..477979ae6 100644 --- a/src/shared/Core/UI/AvaloniaUi.cs +++ b/src/shared/Core/UI/AvaloniaUi.cs @@ -53,15 +53,7 @@ public static Task ShowWindowAsync(Func windowFunc, object dataContext, #else .UsePlatformDetect() #endif - .LogToTrace() - // Workaround https://github.com/AvaloniaUI/Avalonia/issues/10296 - // by always setting a application lifetime. - .SetupWithLifetime( - new ClassicDesktopStyleApplicationLifetime - { - ShutdownMode = ShutdownMode.OnExplicitShutdown - } - ); + .LogToTrace(); appInitialized.Set(); From 46810dfd9ae67851241ab121b589a54e78e823e7 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Thu, 28 Sep 2023 09:24:36 -0700 Subject: [PATCH 2/3] avalonia: introduce flag to switch to SW rendering Introduce a flag to switch Avalonia to use software rendering rather than hardware/GPU-based. There is an open Avalonia issue[1] on Windows when run on certain ARM64 GPUs. Until this is solved, introduce this workaround flag. [1]: https://github.com/AvaloniaUI/Avalonia/issues/10405 --- docs/configuration.md | 19 +++++++++++ docs/environment.md | 27 ++++++++++++++++ src/shared/Core/ApplicationBase.cs | 7 ++++ src/shared/Core/Constants.cs | 2 ++ src/shared/Core/Settings.cs | 16 ++++++++++ src/shared/Core/UI/AvaloniaUi.cs | 32 +++++++++++++++++-- .../Objects/TestSettings.cs | 2 ++ 7 files changed, 103 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 88a23c103..ac0658b46 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -233,6 +233,24 @@ Defaults to enabled. --- +### credential.guiSoftwareRendering + +Force the use of software rendering for GUI prompts. + +This is currently only applicable on Windows. + +#### Example + +```shell +git config --global credential.guiSoftwareRendering true +``` + +Defaults to false (use hardware acceleration where available). + +**Also see: [GCM_GUI_SOFTWARE_RENDERING][gcm-gui-software-rendering]** + +--- + ### credential.autoDetectTimeout Set the maximum length of time, in milliseconds, that GCM should wait for a @@ -978,6 +996,7 @@ Defaults to disabled. [gcm-github-authmodes]: environment.md#GCM_GITHUB_AUTHMODES [gcm-gitlab-authmodes]:environment.md#GCM_GITLAB_AUTHMODES [gcm-gui-prompt]: environment.md#GCM_GUI_PROMPT +[gcm-gui-software-rendering]: environment.md#GCM_GUI_SOFTWARE_RENDERING [gcm-http-proxy]: environment.md#GCM_HTTP_PROXY-deprecated [gcm-interactive]: environment.md#GCM_INTERACTIVE [gcm-msauth-flow]: environment.md#GCM_MSAUTH_FLOW diff --git a/docs/environment.md b/docs/environment.md index 666604422..3612c1d0f 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -272,6 +272,32 @@ Defaults to enabled. --- +### GCM_GUI_SOFTWARE_RENDERING + +Force the use of software rendering for GUI prompts. + +This is currently only applicable on Windows. + +#### Example + +##### Windows + +```batch +SET GCM_GUI_SOFTWARE_RENDERING=1 +``` + +##### macOS/Linux + +```bash +export GCM_GUI_SOFTWARE_RENDERING=1 +``` + +Defaults to false (use hardware acceleration where available). + +**Also see: [credential.guiSoftwareRendering][credential-guisoftwarerendering]** + +--- + ### GCM_AUTODETECT_TIMEOUT Set the maximum length of time, in milliseconds, that GCM should wait for a @@ -1111,6 +1137,7 @@ Defaults to disabled. [credential-githubauthmodes]: configuration.md#credentialgitHubAuthModes [credential-gitlabauthmodes]: configuration.md#credentialgitLabAuthModes [credential-guiprompt]: configuration.md#credentialguiprompt +[credential-guisoftwarerendering]: configuration.md#credentialguisoftwarerendering [credential-httpproxy]: configuration.md#credentialhttpProxy-deprecated [credential-interactive]: configuration.md#credentialinteractive [credential-namespace]: configuration.md#credentialnamespace diff --git a/src/shared/Core/ApplicationBase.cs b/src/shared/Core/ApplicationBase.cs index 42f1390e9..5ff692a4d 100644 --- a/src/shared/Core/ApplicationBase.cs +++ b/src/shared/Core/ApplicationBase.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using GitCredentialManager.UI; namespace GitCredentialManager { @@ -74,6 +75,12 @@ public Task RunAsync(string[] args) Context.Trace.WriteLine("Tracing of secrets is enabled. Trace output may contain sensitive information."); } + // Set software rendering if defined in settings + if (Context.Settings.UseSoftwareRendering) + { + AvaloniaUi.Initialize(win32SoftwareRendering: true); + } + return RunInternalAsync(args); } diff --git a/src/shared/Core/Constants.cs b/src/shared/Core/Constants.cs index 03f41647a..ac609adaa 100644 --- a/src/shared/Core/Constants.cs +++ b/src/shared/Core/Constants.cs @@ -118,6 +118,7 @@ public static class EnvironmentVariables public const string OAuthClientAuthHeader = "GCM_OAUTH_USE_CLIENT_AUTH_HEADER"; public const string OAuthDefaultUserName = "GCM_OAUTH_DEFAULT_USERNAME"; public const string GcmDevUseLegacyUiHelpers = "GCM_DEV_USELEGACYUIHELPERS"; + public const string GcmGuiSoftwareRendering = "GCM_GUI_SOFTWARE_RENDERING"; } public static class Http @@ -160,6 +161,7 @@ public static class Credential public const string UiHelper = "uiHelper"; public const string DevUseLegacyUiHelpers = "devUseLegacyUiHelpers"; public const string MsAuthUseDefaultAccount = "msauthUseDefaultAccount"; + public const string GuiSoftwareRendering = "guiSoftwareRendering"; public const string OAuthAuthenticationModes = "oauthAuthModes"; public const string OAuthClientId = "oauthClientId"; diff --git a/src/shared/Core/Settings.cs b/src/shared/Core/Settings.cs index 1e60793c1..ac92b23f8 100644 --- a/src/shared/Core/Settings.cs +++ b/src/shared/Core/Settings.cs @@ -184,6 +184,11 @@ public interface ISettings : IDisposable /// bool UseMsAuthDefaultAccount { get; } + /// + /// True if software rendering should be used for graphical user interfaces, false otherwise. + /// + bool UseSoftwareRendering { get; } + /// /// Get TRACE2 settings. /// @@ -559,6 +564,17 @@ public bool IsInteractionAllowed KnownGitCfg.Credential.Trace, out value) && !value.IsFalsey(); + public bool UseSoftwareRendering + { + get + { + return TryGetSetting(KnownEnvars.GcmGuiSoftwareRendering, + KnownGitCfg.Credential.SectionName, + KnownGitCfg.Credential.GuiSoftwareRendering, + out string str) && str.ToBooleanyOrDefault(false); + } + } + public Trace2Settings GetTrace2Settings() { var settings = new Trace2Settings(); diff --git a/src/shared/Core/UI/AvaloniaUi.cs b/src/shared/Core/UI/AvaloniaUi.cs index 477979ae6..0c2ca237e 100644 --- a/src/shared/Core/UI/AvaloniaUi.cs +++ b/src/shared/Core/UI/AvaloniaUi.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Threading; using GitCredentialManager.Interop.Windows.Native; using GitCredentialManager.UI.Controls; @@ -15,6 +14,24 @@ namespace GitCredentialManager.UI public static class AvaloniaUi { private static bool _isAppStarted; + private static bool _win32SoftwareRendering; + + /// + /// Configure the Avalonia application. + /// + /// True to enable software rendering on Windows, false otherwise. + /// + /// This must be invoked before the Avalonia application loop has started. + /// + public static void Initialize(bool win32SoftwareRendering) + { + if (_isAppStarted) + { + throw new InvalidOperationException("Setup must be called before the Avalonia application is started."); + } + + _win32SoftwareRendering = win32SoftwareRendering; + } public static Task ShowViewAsync(Func viewFunc, WindowViewModel viewModel, IntPtr parentHandle, CancellationToken ct) => ShowWindowAsync(() => new DialogWindow(viewFunc()), viewModel, parentHandle, ct); @@ -46,7 +63,18 @@ public static Task ShowWindowAsync(Func windowFunc, object dataContext, // This action only returns on our dispatcher shutdown. Dispatcher.MainThread.Post(appCancelToken => { - AppBuilder.Configure() + var appBuilder = AppBuilder.Configure(); + +#if NETFRAMEWORK + // Set custom rendering options and modes if required + if (PlatformUtils.IsWindows() && _win32SoftwareRendering) + { + appBuilder.With(new Win32PlatformOptions + { RenderingMode = new[] { Win32RenderingMode.Software } }); + } +#endif + + appBuilder #if NETFRAMEWORK .UseWin32() .UseSkia() diff --git a/src/shared/TestInfrastructure/Objects/TestSettings.cs b/src/shared/TestInfrastructure/Objects/TestSettings.cs index 265d411f0..f14bf6cc9 100644 --- a/src/shared/TestInfrastructure/Objects/TestSettings.cs +++ b/src/shared/TestInfrastructure/Objects/TestSettings.cs @@ -187,6 +187,8 @@ ProxyConfiguration ISettings.GetProxyConfiguration() bool ISettings.UseMsAuthDefaultAccount => UseMsAuthDefaultAccount; + bool ISettings.UseSoftwareRendering => false; + #endregion #region IDisposable From 3183801eacdc6653e9046d9d2fbeb6c59efc8408 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Mon, 23 Oct 2023 12:16:12 -0700 Subject: [PATCH 3/3] settings: default SW rendering on Windows+ARM Default to software GUI rendering on Windows on ARM. Users can explicitly set the config to re-enable HW accelerated rendering if they wish. --- docs/configuration.md | 4 ++++ docs/environment.md | 4 ++++ src/shared/Core/PlatformUtils.cs | 16 ++++++++++++++++ src/shared/Core/Settings.cs | 7 ++++++- 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index ac0658b46..2439c9297 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -247,6 +247,10 @@ git config --global credential.guiSoftwareRendering true Defaults to false (use hardware acceleration where available). +> [!NOTE] +> Windows on ARM devices defaults to using software rendering to work around a +> known Avalonia issue: + **Also see: [GCM_GUI_SOFTWARE_RENDERING][gcm-gui-software-rendering]** --- diff --git a/docs/environment.md b/docs/environment.md index 3612c1d0f..18f3f05fe 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -294,6 +294,10 @@ export GCM_GUI_SOFTWARE_RENDERING=1 Defaults to false (use hardware acceleration where available). +> [!NOTE] +> Windows on ARM devices defaults to using software rendering to work around a +> known Avalonia issue: + **Also see: [credential.guiSoftwareRendering][credential-guisoftwarerendering]** --- diff --git a/src/shared/Core/PlatformUtils.cs b/src/shared/Core/PlatformUtils.cs index 212c13219..ed0e2ec7d 100644 --- a/src/shared/Core/PlatformUtils.cs +++ b/src/shared/Core/PlatformUtils.cs @@ -53,6 +53,22 @@ public static bool IsDevBox() #endif } + /// + /// Returns true if the current process is running on an ARM processor. + /// + /// True if ARM(v6,hf) or ARM64, false otherwise + public static bool IsArm() + { + switch (RuntimeInformation.OSArchitecture) + { + case Architecture.Arm: + case Architecture.Arm64: + return true; + default: + return false; + } + } + public static bool IsWindowsBrokerSupported() { if (!IsWindows()) diff --git a/src/shared/Core/Settings.cs b/src/shared/Core/Settings.cs index ac92b23f8..2aa71edf4 100644 --- a/src/shared/Core/Settings.cs +++ b/src/shared/Core/Settings.cs @@ -568,10 +568,15 @@ public bool UseSoftwareRendering { get { + // WORKAROUND: Some Windows ARM devices have a graphics driver issue that causes transparent windows + // when using hardware rendering. Until this is fixed, we will default to software rendering on these + // devices. Users can always override this setting back to HW-accelerated rendering if they wish. + bool defaultValue = PlatformUtils.IsWindows() && PlatformUtils.IsArm(); + return TryGetSetting(KnownEnvars.GcmGuiSoftwareRendering, KnownGitCfg.Credential.SectionName, KnownGitCfg.Credential.GuiSoftwareRendering, - out string str) && str.ToBooleanyOrDefault(false); + out string str) ? str.ToBooleanyOrDefault(defaultValue) : defaultValue; } }