diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 11ef36d43f..bc8d065137 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,19 +3,23 @@ jobs: pool: vmImage: 'ubuntu-20.04' steps: - - task: CmdLine@2 - displayName: 'Install Nuke' + - task: UseDotNet@2 + displayName: 'Use .NET Core SDK 3.1.414' inputs: - script: | - dotnet tool install --global Nuke.GlobalTool --version 0.24.0 + version: 3.1.414 + + - task: UseDotNet@2 + displayName: 'Use .NET Core SDK 5.0.402' + inputs: + version: 5.0.402 + - task: CmdLine@2 - displayName: 'Run Nuke' + displayName: 'Run Build' inputs: script: | - export PATH="$PATH:$HOME/.dotnet/tools" dotnet --info printenv - nuke --target CiAzureLinux --configuration=Release + ./build.sh --target CiAzureLinux --configuration=Release - task: PublishTestResults@2 inputs: @@ -23,6 +27,7 @@ jobs: testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx' condition: not(canceled()) + - job: macOS variables: SolutionDir: '$(Build.SourcesDirectory)' @@ -30,10 +35,15 @@ jobs: vmImage: 'macOS-10.15' steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 3.1.401' + displayName: 'Use .NET Core SDK 3.1.414' inputs: - version: 3.1.401 + version: 3.1.414 + - task: UseDotNet@2 + displayName: 'Use .NET Core SDK 5.0.402' + inputs: + version: 5.0.402 + - task: CmdLine@2 displayName: 'Install Mono 5.18' inputs: @@ -45,6 +55,7 @@ jobs: displayName: 'Generate avalonia-native' inputs: script: | + export PATH="`pwd`/sdk:$PATH" cd src/tools/MicroComGenerator; dotnet run -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h - task: Xcode@5 @@ -58,13 +69,7 @@ jobs: args: '-derivedDataPath ./' - task: CmdLine@2 - displayName: 'Install Nuke' - inputs: - script: | - dotnet tool install --global Nuke.GlobalTool --version 0.24.0 - - - task: CmdLine@2 - displayName: 'Run Nuke' + displayName: 'Run Build' inputs: script: | export COREHOST_TRACE=0 @@ -72,10 +77,8 @@ jobs: export DOTNET_CLI_TELEMETRY_OPTOUT=1 which dotnet dotnet --info - export PATH="$PATH:$HOME/.dotnet/tools" - dotnet --info printenv - nuke --target CiAzureOSX --configuration Release --skip-previewer + ./build.sh --target CiAzureOSX --configuration Release --skip-previewer - task: PublishTestResults@2 inputs: @@ -102,9 +105,14 @@ jobs: SolutionDir: '$(Build.SourcesDirectory)' steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 3.1.401' + displayName: 'Use .NET Core SDK 3.1.414' + inputs: + version: 3.1.414 + + - task: UseDotNet@2 + displayName: 'Use .NET Core SDK 5.0.402' inputs: - version: 3.1.401 + version: 5.0.402 - task: CmdLine@2 displayName: 'Install Nuke' diff --git a/build.sh b/build.sh index bd162fab9b..9532b4fbe0 100755 --- a/build.sh +++ b/build.sh @@ -20,55 +20,11 @@ SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) ########################################################################### BUILD_PROJECT_FILE="$SCRIPT_DIR/nukebuild/_build.csproj" -TEMP_DIRECTORY="$SCRIPT_DIR//.tmp" - -DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" -DOTNET_INSTALL_URL="https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh" -DOTNET_CHANNEL="Current" export DOTNET_CLI_TELEMETRY_OPTOUT=1 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 export NUGET_XMLDOC_MODE="skip" -########################################################################### -# EXECUTION -########################################################################### - -function FirstJsonValue { - perl -nle 'print $1 if m{"'$1'": "([^"\-]+)",?}' <<< ${@:2} -} - -# If global.json exists, load expected version -if [ -f "$DOTNET_GLOBAL_FILE" ]; then - DOTNET_VERSION=$(FirstJsonValue "version" $(cat "$DOTNET_GLOBAL_FILE")) - if [ "$DOTNET_VERSION" == "" ]; then - unset DOTNET_VERSION - fi -fi - -# If dotnet is installed locally, and expected version is not set or installation matches the expected version -if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") || "$SKIP_DOTNET_DOWNLOAD" == "1" ]]; then - export DOTNET_EXE="$(command -v dotnet)" -else - DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" - export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" - - # Download install script - DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" - mkdir -p "$TEMP_DIRECTORY" - curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" - chmod +x "$DOTNET_INSTALL_FILE" - - # Install by channel or version - if [ -z ${DOTNET_VERSION+x} ]; then - "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path - else - "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path - fi -fi - -export PATH=$DOTNET_DIRECTORY:$PATH - -echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" +dotnet --info -"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]} +dotnet run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]} diff --git a/global.json b/global.json index b2b2da7c4f..9f83c1ea1e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "3.1.401" + "version": "5.0.402" }, "msbuild-sdks": { "Microsoft.Build.Traversal": "1.0.43", diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 26c065fe11..094c1f7c93 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -2389,7 +2389,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent - (void)sendEvent:(NSEvent *)event { - if(_parent != nullptr) + [super sendEvent:event]; + + /// This is to detect non-client clicks. This can only be done on Windows... not popups, hence the dynamic_cast. + if(_parent != nullptr && dynamic_cast(_parent.getRaw()) != nullptr) { switch(event.type) { @@ -2419,8 +2422,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent break; } } - - [super sendEvent:event]; } @end diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index de3830ffea..3f9ccb04eb 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -54,6 +54,7 @@ + diff --git a/readme.md b/readme.md index 7959bc5540..ef8c116728 100644 --- a/readme.md +++ b/readme.md @@ -60,6 +60,9 @@ See the [build instructions here](Documentation/build.md). ## Contributing +This project exists thanks to all the people who contribute. + + Please read the [contribution guidelines](CONTRIBUTING.md) before submitting a pull request. ## Code of Conduct @@ -71,11 +74,6 @@ For more information see the [.NET Foundation Code of Conduct](https://dotnetfou Avalonia is licenced under the [MIT licence](licence.md). -## Contributors - -This project exists thanks to all the people who contribute. [[Contribute](https://avaloniaui.net/contributing)]. - - ### Backers Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)] diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 0c8fc31df1..d8fd864e9b 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -329,16 +329,8 @@ namespace Avalonia.Controls { _popup = new Popup { - HorizontalOffset = HorizontalOffset, - VerticalOffset = VerticalOffset, - PlacementAnchor = PlacementAnchor, - PlacementConstraintAdjustment = PlacementConstraintAdjustment, - PlacementGravity = PlacementGravity, - PlacementMode = PlacementMode, - PlacementRect = PlacementRect, IsLightDismissEnabled = true, OverlayDismissEventPassThrough = true, - WindowManagerAddShadowHint = WindowManagerAddShadowHint, }; _popup.Opened += PopupOpened; @@ -358,6 +350,13 @@ namespace Avalonia.Controls : PlacementMode; _popup.PlacementTarget = placementTarget; + _popup.HorizontalOffset = HorizontalOffset; + _popup.VerticalOffset = VerticalOffset; + _popup.PlacementAnchor = PlacementAnchor; + _popup.PlacementConstraintAdjustment = PlacementConstraintAdjustment; + _popup.PlacementGravity = PlacementGravity; + _popup.PlacementRect = PlacementRect; + _popup.WindowManagerAddShadowHint = WindowManagerAddShadowHint; _popup.Child = this; IsOpen = true; _popup.IsOpen = true; diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index 2eaf24d2f2..2ceaeb6dba 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -16,6 +16,7 @@ namespace Avalonia.Controls private bool _isChecked = false; private NativeMenuItemToggleType _toggleType; private IBitmap _icon; + private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber; private NativeMenu _menu; @@ -47,8 +48,6 @@ namespace Avalonia.Controls } } - private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber; - public NativeMenuItem() { diff --git a/src/Avalonia.Controls/TrayIcon.cs b/src/Avalonia.Controls/TrayIcon.cs index 6bfddfa877..59edb6278a 100644 --- a/src/Avalonia.Controls/TrayIcon.cs +++ b/src/Avalonia.Controls/TrayIcon.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Windows.Input; using Avalonia.Collections; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Platform; +using Avalonia.Utilities; #nullable enable @@ -13,10 +15,13 @@ namespace Avalonia.Controls public sealed class TrayIcons : AvaloniaList { } + + public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable { private readonly ITrayIconImpl? _impl; + private ICommand? _command; private TrayIcon(ITrayIconImpl? impl) { @@ -26,7 +31,15 @@ namespace Avalonia.Controls _impl.SetIsVisible(IsVisible); - _impl.OnClicked = () => Clicked?.Invoke(this, EventArgs.Empty); + _impl.OnClicked = () => + { + Clicked?.Invoke(this, EventArgs.Empty); + + if (Command?.CanExecute(CommandParameter) == true) + { + Command.Execute(CommandParameter); + } + }; } } @@ -64,6 +77,21 @@ namespace Avalonia.Controls /// on OSX this event is not raised. /// public event EventHandler? Clicked; + + /// + /// Defines the property. + /// + public static readonly DirectProperty CommandProperty = + Button.CommandProperty.AddOwner( + trayIcon => trayIcon.Command, + (trayIcon, command) => trayIcon.Command = command, + enableDataValidation: true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty CommandParameterProperty = + Button.CommandParameterProperty.AddOwner(); /// /// Defines the attached property. @@ -98,6 +126,25 @@ namespace Avalonia.Controls public static void SetIcons(AvaloniaObject o, TrayIcons trayIcons) => o.SetValue(IconsProperty, trayIcons); public static TrayIcons GetIcons(AvaloniaObject o) => o.GetValue(IconsProperty); + + /// + /// Gets or sets the property of a TrayIcon. + /// + public ICommand? Command + { + get => _command; + set => SetAndRaise(CommandProperty, ref _command, value); + } + + /// + /// Gets or sets the parameter to pass to the property of a + /// . + /// + public object CommandParameter + { + get { return GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } /// /// Gets or sets the Menu of the TrayIcon. diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs index a79816390d..e8de22710a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs @@ -117,6 +117,11 @@ namespace Avalonia.Diagnostics.ViewModels _currentEvent.HandledBy = link; } } + + if (!Dispatcher.UIThread.CheckAccess()) + Dispatcher.UIThread.Post(handler); + else + handler(); } private static bool BelongsToDevTool(IVisual v) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs index 8069300922..a9f3182bef 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs @@ -64,7 +64,7 @@ namespace Avalonia.Diagnostics.ViewModels if (EventChain.Count > 0) { var prevLink = EventChain[EventChain.Count - 1]; - + if (prevLink.Route != link.Route) { link.BeginsNewRoute = true; diff --git a/src/Avalonia.FreeDesktop/DBusHelper.cs b/src/Avalonia.FreeDesktop/DBusHelper.cs index 4e23711ed4..c14539d7bf 100644 --- a/src/Avalonia.FreeDesktop/DBusHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusHelper.cs @@ -12,7 +12,7 @@ namespace Avalonia.FreeDesktop /// This class uses synchronous execution at DBus connection establishment stage /// then switches to using AvaloniaSynchronizationContext /// - class DBusSyncContext : SynchronizationContext + private class DBusSyncContext : SynchronizationContext { private SynchronizationContext _ctx; private object _lock = new object(); @@ -51,10 +51,10 @@ namespace Avalonia.FreeDesktop public static Connection TryInitialize(string dbusAddress = null) { - return Connection ?? TryGetConnection(dbusAddress); + return Connection ?? TryCreateNewConnection(dbusAddress); } - public static Connection TryGetConnection(string dbusAddress = null) + public static Connection TryCreateNewConnection(string dbusAddress = null) { var oldContext = SynchronizationContext.Current; try diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs similarity index 68% rename from src/Avalonia.X11/X11TrayIconImpl.cs rename to src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index 371ff75408..a7cc4f4cc2 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -6,55 +6,65 @@ using System.Reactive.Disposables; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Avalonia.Controls.Platform; -using Avalonia.FreeDesktop; using Avalonia.Logging; using Avalonia.Platform; using Tmds.DBus; [assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)] -namespace Avalonia.X11 +[assembly: + InternalsVisibleTo( + "Avalonia.X11, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")] + +namespace Avalonia.FreeDesktop { - internal class X11TrayIconImpl : ITrayIconImpl + internal class DBusTrayIconImpl : ITrayIconImpl { private static int s_trayIconInstanceId; + private readonly ObjectPath _dbusMenuPath; - private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj; private readonly Connection? _connection; - private DbusPixmap _icon; + private IDisposable? _serviceWatchDisposable; + private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj; private IStatusNotifierWatcher? _statusNotifierWatcher; + private DbusPixmap _icon; private string? _sysTrayServiceName; private string? _tooltipText; - private bool _isActive; private bool _isDisposed; - private readonly bool _ctorFinished; + private bool _serviceConnected; + private bool _isVisible = true; + public bool IsActive { get; private set; } public INativeMenuExporter? MenuExporter { get; } public Action? OnClicked { get; set; } + public Func? IconConverterDelegate { get; set; } - public X11TrayIconImpl() + public DBusTrayIconImpl() { - _connection = DBusHelper.TryGetConnection(); + _connection = DBusHelper.TryCreateNewConnection(); if (_connection is null) { - Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform) + Logger.TryGet(LogEventLevel.Error, "DBUS") ?.Log(this, "Unable to get a dbus connection for system tray icons."); + return; } + IsActive = true; + _dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; + MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusMenuPath, _connection); - CreateTrayIcon(); - _ctorFinished = true; + + WatchAsync(); } - public async void CreateTrayIcon() + private void InitializeSNWService() { - if (_connection is null) - return; + if (_connection is null || _isDisposed) return; try { @@ -64,12 +74,58 @@ namespace Avalonia.X11 } catch { - Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform) + Logger.TryGet(LogEventLevel.Error, "DBUS") + ?.Log(this, + "org.kde.StatusNotifierWatcher service is not available on this system. Tray Icons will not work without it."); + + return; + } + + _serviceConnected = true; + } + + private async void WatchAsync() + { + try + { + _serviceWatchDisposable = + await _connection?.ResolveServiceOwnerAsync("org.kde.StatusNotifierWatcher", OnNameChange)!; + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, "DBUS") ?.Log(this, - "DBUS: org.kde.StatusNotifierWatcher service is not available on this system. System Tray Icons will not work without it."); + $"Unable to hook watcher method on org.kde.StatusNotifierWatcher: {e}"); } + } - if (_statusNotifierWatcher is null) + private void OnNameChange(ServiceOwnerChangedEventArgs obj) + { + if (_isDisposed) + return; + + if (!_serviceConnected & obj.NewOwner != null) + { + _serviceConnected = true; + InitializeSNWService(); + + DestroyTrayIcon(); + + if (_isVisible) + { + CreateTrayIcon(); + } + } + else if (_serviceConnected & obj.NewOwner is null) + { + DestroyTrayIcon(); + _serviceConnected = false; + } + } + + private void CreateTrayIcon() + { + if (_connection is null || !_serviceConnected || _isDisposed) return; var pid = Process.GetCurrentProcess().Id; @@ -78,45 +134,61 @@ namespace Avalonia.X11 _sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}"; _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_dbusMenuPath); - await _connection.RegisterObjectAsync(_statusNotifierItemDbusObj); - - await _connection.RegisterServiceAsync(_sysTrayServiceName); + try + { + _connection.RegisterObjectAsync(_statusNotifierItemDbusObj); + _connection.RegisterServiceAsync(_sysTrayServiceName); + _statusNotifierWatcher?.RegisterStatusNotifierItemAsync(_sysTrayServiceName); + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, "DBUS") + ?.Log(this, $"Error creating a DBus tray icon: {e}."); - await _statusNotifierWatcher.RegisterStatusNotifierItemAsync(_sysTrayServiceName); + _serviceConnected = false; + } _statusNotifierItemDbusObj.SetTitleAndTooltip(_tooltipText); _statusNotifierItemDbusObj.SetIcon(_icon); _statusNotifierItemDbusObj.ActivationDelegate += OnClicked; - - _isActive = true; } - public async void DestroyTrayIcon() + private void DestroyTrayIcon() { - if (_connection is null) + if (_connection is null || !_serviceConnected || _isDisposed || _statusNotifierItemDbusObj is null) return; + _connection.UnregisterObject(_statusNotifierItemDbusObj); - await _connection.UnregisterServiceAsync(_sysTrayServiceName); - _isActive = false; + _connection.UnregisterServiceAsync(_sysTrayServiceName); } public void Dispose() { + IsActive = false; _isDisposed = true; DestroyTrayIcon(); _connection?.Dispose(); + _serviceWatchDisposable?.Dispose(); } public void SetIcon(IWindowIconImpl? icon) { - if (_isDisposed) + if (_isDisposed || IconConverterDelegate is null) return; - if (!(icon is X11IconData x11icon)) + + if (icon is null) + { + _statusNotifierItemDbusObj?.SetIcon(DbusPixmap.EmptyPixmap); return; + } + + var x11iconData = IconConverterDelegate(icon); + + if (x11iconData.Length == 0) return; - var w = (int)x11icon.Data[0]; - var h = (int)x11icon.Data[1]; + var w = (int)x11iconData[0]; + var h = (int)x11iconData[1]; var pixLength = w * h; var pixByteArrayCounter = 0; @@ -124,7 +196,7 @@ namespace Avalonia.X11 for (var i = 0; i < pixLength; i++) { - var rawPixel = x11icon.Data[i + 2].ToUInt32(); + var rawPixel = x11iconData[i + 2]; pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF000000) >> 24); pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF0000) >> 16); pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF00) >> 8); @@ -137,18 +209,21 @@ namespace Avalonia.X11 public void SetIsVisible(bool visible) { - if (_isDisposed || !_ctorFinished) + if (_isDisposed) return; - if (visible & !_isActive) - { - DestroyTrayIcon(); - CreateTrayIcon(); - } - else if (!visible & _isActive) + switch (visible) { - DestroyTrayIcon(); + case true when !_isVisible: + DestroyTrayIcon(); + CreateTrayIcon(); + break; + case false when _isVisible: + DestroyTrayIcon(); + break; } + + _isVisible = visible; } public void SetToolTipText(string? text) @@ -248,7 +323,20 @@ namespace Avalonia.X11 return Task.FromResult(Disposable.Create(() => NewStatusAsync -= handler)); } - public Task GetAsync(string prop) => Task.FromResult(new object()); + public Task GetAsync(string prop) + { + return Task.FromResult(prop switch + { + nameof(_backingProperties.Category) => _backingProperties.Category, + nameof(_backingProperties.Id) => _backingProperties.Id, + nameof(_backingProperties.Menu) => _backingProperties.Menu, + nameof(_backingProperties.IconPixmap) => _backingProperties.IconPixmap, + nameof(_backingProperties.Status) => _backingProperties.Status, + nameof(_backingProperties.Title) => _backingProperties.Title, + nameof(_backingProperties.ToolTip) => _backingProperties.ToolTip, + _ => null + }); + } public Task GetAllAsync() => Task.FromResult(_backingProperties); @@ -298,17 +386,17 @@ namespace Avalonia.X11 Task WatchNewOverlayIconAsync(Action handler, Action onError); Task WatchNewToolTipAsync(Action handler, Action onError); Task WatchNewStatusAsync(Action handler, Action onError); - Task GetAsync(string prop); + Task GetAsync(string prop); Task GetAllAsync(); Task SetAsync(string prop, object val); Task WatchPropertiesAsync(Action handler); } - [Dictionary] // This class is used by Tmds.Dbus to ferry properties // from the SNI spec. // Don't change this to actual C# properties since // Tmds.Dbus will get confused. + [Dictionary] internal class StatusNotifierItemProperties { public string? Category; @@ -363,5 +451,7 @@ namespace Avalonia.X11 Height = height; Data = data; } + + public static DbusPixmap EmptyPixmap = new DbusPixmap(1, 1, new byte[] { 255, 0, 0, 0 }); } } diff --git a/src/Avalonia.Themes.Default/AutoCompleteBox.xaml b/src/Avalonia.Themes.Default/AutoCompleteBox.xaml index fe4cd48e72..fac8ca51f8 100644 --- a/src/Avalonia.Themes.Default/AutoCompleteBox.xaml +++ b/src/Avalonia.Themes.Default/AutoCompleteBox.xaml @@ -21,7 +21,8 @@ MaxHeight="{TemplateBinding MaxDropDownHeight}" PlacementTarget="{TemplateBinding}" IsLightDismissEnabled="True"> - - diff --git a/src/Avalonia.Themes.Default/ContextMenu.xaml b/src/Avalonia.Themes.Default/ContextMenu.xaml index 0df4866184..987b72aaa2 100644 --- a/src/Avalonia.Themes.Default/ContextMenu.xaml +++ b/src/Avalonia.Themes.Default/ContextMenu.xaml @@ -1,4 +1,5 @@ diff --git a/src/Avalonia.Themes.Default/PopupRoot.xaml b/src/Avalonia.Themes.Default/PopupRoot.xaml index b9f68c8421..9468cc5535 100644 --- a/src/Avalonia.Themes.Default/PopupRoot.xaml +++ b/src/Avalonia.Themes.Default/PopupRoot.xaml @@ -1,6 +1,8 @@ diff --git a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml index f282586908..1e573913b9 100644 --- a/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml @@ -1,6 +1,7 @@