diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index b661e0b78a..9d153e0336 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -76,7 +76,7 @@ jobs: # ================================ linux: name: Linux - runs-on: ubuntu-latest + runs-on: ${{ matrix.runtime == 'linux-x64' && 'ubuntu-24.04' || 'ubuntu-24.04-arm' }} strategy: matrix: runtime: [ linux-x64, linux-arm64, linux-arm ] @@ -89,6 +89,13 @@ jobs: with: dotnet-version: 10.0.x + - name: Install cross-compilation toolchain (arm) + if: matrix.runtime == 'linux-arm' + run: | + sudo apt-get update + # Brings in cross-compilation toolchain for armhf including binutils + sudo apt-get install -y gcc-arm-linux-gnueabihf + - name: Install dependencies run: dotnet restore diff --git a/Directory.Packages.props b/Directory.Packages.props index d19eef0708..2374d94c61 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,10 +6,9 @@ - - - - + + + @@ -23,7 +22,6 @@ - diff --git a/src/linux/Packaging.Linux/install-from-source.sh b/src/linux/Packaging.Linux/install-from-source.sh index 1337b75273..64f925811f 100755 --- a/src/linux/Packaging.Linux/install-from-source.sh +++ b/src/linux/Packaging.Linux/install-from-source.sh @@ -259,5 +259,9 @@ if [ -z "$DOTNET_ROOT" ]; then fi cd "$toplevel_path" -$sudo_cmd env "PATH=$PATH" $DOTNET_ROOT/dotnet build ./src/linux/Packaging.Linux/Packaging.Linux.csproj -c Release -p:InstallFromSource=true -p:installPrefix=$installPrefix +# Build a non-AOT binary so installing from source needs only the .NET SDK, +# not a C toolchain (clang + zlib) to link a native build. PublishAot is read +# from the environment by Git-Credential-Manager.csproj and inherited by the +# nested publish. +$sudo_cmd env "PATH=$PATH" PublishAot=false $DOTNET_ROOT/dotnet build ./src/linux/Packaging.Linux/Packaging.Linux.csproj -c Release -p:InstallFromSource=true -p:installPrefix=$installPrefix add_to_PATH "$installPrefix/bin" diff --git a/src/shared/Atlassian.Bitbucket/BitbucketOAuth2Client.cs b/src/shared/Atlassian.Bitbucket/BitbucketOAuth2Client.cs index 1ca23d0f57..84e76518f3 100644 --- a/src/shared/Atlassian.Bitbucket/BitbucketOAuth2Client.cs +++ b/src/shared/Atlassian.Bitbucket/BitbucketOAuth2Client.cs @@ -42,7 +42,9 @@ protected override bool TryCreateTokenEndpointResult(string json, out OAuth2Toke // We override the token endpoint response parsing because the Bitbucket authority returns // the non-standard 'scopes' property for the list of scopes, rather than the (optional) // 'scope' (note the singular vs plural) property as outlined in the standard. - if (TryDeserializeJson(json, out BitbucketTokenEndpointResponseJson jsonObj)) + if (TryDeserializeJson(json, + BitbucketOAuthJsonContext.Default.BitbucketTokenEndpointResponseJson, + out BitbucketTokenEndpointResponseJson jsonObj)) { result = jsonObj.ToResult(); return true; diff --git a/src/shared/Atlassian.Bitbucket/BitbucketTokenEndpointResponseJson.cs b/src/shared/Atlassian.Bitbucket/BitbucketTokenEndpointResponseJson.cs index c28e63de11..afc8c43a5f 100644 --- a/src/shared/Atlassian.Bitbucket/BitbucketTokenEndpointResponseJson.cs +++ b/src/shared/Atlassian.Bitbucket/BitbucketTokenEndpointResponseJson.cs @@ -5,6 +5,9 @@ namespace Atlassian.Bitbucket { + [JsonSerializable(typeof(BitbucketTokenEndpointResponseJson))] + public partial class BitbucketOAuthJsonContext : JsonSerializerContext; + [JsonConverter(typeof(BitbucketCustomTokenEndpointResponseJsonConverter))] public class BitbucketTokenEndpointResponseJson : TokenEndpointResponseJson { diff --git a/src/shared/Atlassian.Bitbucket/Cloud/BitbucketRestApi.cs b/src/shared/Atlassian.Bitbucket/Cloud/BitbucketRestApi.cs index 94021e14da..c90932f381 100644 --- a/src/shared/Atlassian.Bitbucket/Cloud/BitbucketRestApi.cs +++ b/src/shared/Atlassian.Bitbucket/Cloud/BitbucketRestApi.cs @@ -8,6 +8,13 @@ namespace Atlassian.Bitbucket.Cloud { + [JsonSerializable(typeof(UserInfo))] + [JsonSourceGenerationOptions( + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + )] + public partial class BitbucketCloudRestApiJsonContext : JsonSerializerContext; + public class BitbucketRestApi : IBitbucketRestApi { private readonly ICommandContext _context; @@ -43,11 +50,7 @@ public async Task> GetUserInformationAsync(string userN if (response.IsSuccessStatusCode) { - var obj = JsonSerializer.Deserialize(json, - new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - }); + UserInfo obj = JsonSerializer.Deserialize(json, BitbucketCloudRestApiJsonContext.Default.UserInfo); return new RestApiResult(response.StatusCode, obj); } diff --git a/src/shared/Atlassian.Bitbucket/DataCenter/BitbucketRestApi.cs b/src/shared/Atlassian.Bitbucket/DataCenter/BitbucketRestApi.cs index 159229885d..65c2520d16 100644 --- a/src/shared/Atlassian.Bitbucket/DataCenter/BitbucketRestApi.cs +++ b/src/shared/Atlassian.Bitbucket/DataCenter/BitbucketRestApi.cs @@ -10,6 +10,15 @@ namespace Atlassian.Bitbucket.DataCenter { + [JsonSerializable(typeof(UserInfo))] + [JsonSerializable(typeof(LoginOption))] + [JsonSerializable(typeof(LoginOptions))] + [JsonSourceGenerationOptions( + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + )] + public partial class BitbucketDataCenterRestApiJsonContext : JsonSerializerContext; + public class BitbucketRestApi : IBitbucketRestApi { private readonly ICommandContext _context; @@ -107,12 +116,8 @@ public async Task> GetAuthenticationMethodsAsync() if (response.IsSuccessStatusCode) { - var loginOptions = JsonSerializer.Deserialize(json, - new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }); + LoginOptions loginOptions = JsonSerializer.Deserialize( + json, BitbucketDataCenterRestApiJsonContext.Default.LoginOptions); if (loginOptions.Results.Any(r => "LOGIN_FORM".Equals(r.Type))) { @@ -151,4 +156,4 @@ private Uri ApiUri } } } -} \ No newline at end of file +} diff --git a/src/shared/Atlassian.Bitbucket/UI/Views/CredentialsView.axaml b/src/shared/Atlassian.Bitbucket/UI/Views/CredentialsView.axaml index 717500b7be..895587b610 100644 --- a/src/shared/Atlassian.Bitbucket/UI/Views/CredentialsView.axaml +++ b/src/shared/Atlassian.Bitbucket/UI/Views/CredentialsView.axaml @@ -5,6 +5,7 @@ xmlns:vm="clr-namespace:Atlassian.Bitbucket.UI.ViewModels;assembly=Atlassian.Bitbucket" xmlns:converters="clr-namespace:GitCredentialManager.UI.Converters;assembly=gcmcore" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + x:DataType="vm:CredentialsViewModel" x:Class="Atlassian.Bitbucket.UI.Views.CredentialsView"> @@ -63,11 +64,11 @@ - - + + + + + - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + - - - + + diff --git a/src/shared/Core/UI/Controls/ProgressWindow.axaml b/src/shared/Core/UI/Controls/ProgressWindow.axaml index 3bfc20f5ca..e8a7346774 100644 --- a/src/shared/Core/UI/Controls/ProgressWindow.axaml +++ b/src/shared/Core/UI/Controls/ProgressWindow.axaml @@ -4,7 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="182" d:DesignHeight="46" SizeToContent="WidthAndHeight" CanResize="False" Topmost="True" - ExtendClientAreaChromeHints="NoChrome" ExtendClientAreaToDecorationsHint="True" + WindowDecorations="None" ShowInTaskbar="False" Title="Git Credential Manager" WindowStartupLocation="CenterScreen" x:Class="GitCredentialManager.UI.Controls.ProgressWindow"> ( - x => x - ? ExtendClientAreaChromeHints.NoChrome - : ExtendClientAreaChromeHints.PreferSystemChrome); - } -} diff --git a/src/shared/Core/UI/ViewModels/WindowViewModel.cs b/src/shared/Core/UI/ViewModels/WindowViewModel.cs index 078d7c3b83..f95c845a1b 100644 --- a/src/shared/Core/UI/ViewModels/WindowViewModel.cs +++ b/src/shared/Core/UI/ViewModels/WindowViewModel.cs @@ -1,14 +1,17 @@ using System; +using Avalonia.Controls; namespace GitCredentialManager.UI.ViewModels { public class WindowViewModel : ViewModel { private bool _extendClientArea; - private bool _showCustomChromeOverride; + private WindowDecorations _windowDecorations; private bool _showDebugControls; private string _title; + public static readonly WindowDecorations[] WindowDecorationsValues = Enum.GetValues(); + public event EventHandler Accepted; public event EventHandler Canceled; @@ -18,6 +21,13 @@ public WindowViewModel() // Extend the client area on Windows and macOS only ExtendClientArea = PlatformUtils.IsMacOS() || PlatformUtils.IsWindows(); + + // On Windows we prefer to show our own title bar, but we want the system + // to continue to draw the window border for us, which includes rounded + // window corners and shadow. + WindowDecorations = PlatformUtils.IsWindows() + ? WindowDecorations.BorderOnly + : WindowDecorations.Full; } public bool WindowResult { get; private set; } @@ -28,39 +38,24 @@ public bool ShowDebugControls set => SetAndRaisePropertyChanged(ref _showDebugControls, value); } - public bool ShowCustomChrome - { - // On macOS we typically do NOT want to show the custom chrome if we've extended the client area - // because the native 'traffic light' controls will still be visible and we don't want to show our own. - get => ShowCustomChromeOverride || (ExtendClientArea && !PlatformUtils.IsMacOS()); - } - - public bool ShowCustomWindowBorder - { - // Draw the window border explicitly on Windows - get => ShowCustomChrome && PlatformUtils.IsWindows(); - } - - public bool ShowCustomChromeOverride + public WindowDecorations WindowDecorations { - get => _showCustomChromeOverride; + get => _windowDecorations; set { - SetAndRaisePropertyChanged(ref _showCustomChromeOverride, value); - RaisePropertyChanged(nameof(ShowCustomChrome)); - RaisePropertyChanged(nameof(ShowCustomWindowBorder)); + SetAndRaisePropertyChanged(ref _windowDecorations, value); + RaisePropertyChanged(nameof(ShowCustomTitleBar)); } } + // Without system window decorations there's now way to close the window, + // so we need to draw our own title bar and close button. + public bool ShowCustomTitleBar => WindowDecorations != WindowDecorations.Full; + public bool ExtendClientArea { get => _extendClientArea; - set - { - SetAndRaisePropertyChanged(ref _extendClientArea, value); - RaisePropertyChanged(nameof(ShowCustomChrome)); - RaisePropertyChanged(nameof(ShowCustomWindowBorder)); - } + set => SetAndRaisePropertyChanged(ref _extendClientArea, value); } public string Title diff --git a/src/shared/Core/UI/Views/CredentialsView.axaml b/src/shared/Core/UI/Views/CredentialsView.axaml index 9e85c48720..0fb41261b3 100644 --- a/src/shared/Core/UI/Views/CredentialsView.axaml +++ b/src/shared/Core/UI/Views/CredentialsView.axaml @@ -5,6 +5,7 @@ xmlns:vm="clr-namespace:GitCredentialManager.UI.ViewModels;assembly=gcmcore" xmlns:converters="clr-namespace:GitCredentialManager.UI.Converters;assembly=gcmcore" mc:Ignorable="d" d:DesignWidth="420" + x:DataType="vm:CredentialsViewModel" x:Class="GitCredentialManager.UI.Views.CredentialsView"> @@ -34,10 +35,10 @@