Browse Source

Merge branch 'master' into feature/window-integration-tests

pull/8232/head
Dan Walmsley 4 years ago
parent
commit
5cb8244fdf
  1. 9
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  2. 2
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  3. 13
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  4. 2
      samples/ControlCatalog/MainWindow.xaml.cs
  5. 4
      src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
  6. 4
      src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
  7. 2
      src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
  8. 32
      src/Avalonia.FreeDesktop/DBusFileChooser.cs
  9. 24
      src/Avalonia.FreeDesktop/DBusHelper.cs
  10. 16
      src/Avalonia.FreeDesktop/DBusRequest.cs
  11. 102
      src/Avalonia.FreeDesktop/DBusSystemDialog.cs
  12. 10
      src/Avalonia.X11/X11Platform.cs
  13. 4
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
  14. 11
      src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs

9
native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -33,6 +33,7 @@
bool _isEnabled;
bool _canBecomeKeyWindow;
bool _isExtended;
bool _isTransitioningToFullScreen;
AvnMenu* _menu;
}
@ -175,6 +176,7 @@
[self setBackgroundColor: [NSColor clearColor]];
_isExtended = false;
_isTransitioningToFullScreen = false;
if(self.isDialog)
{
@ -349,6 +351,7 @@
- (void)windowWillEnterFullScreen:(NSNotification *_Nonnull)notification
{
_isTransitioningToFullScreen = true;
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
@ -359,6 +362,7 @@
- (void)windowDidEnterFullScreen:(NSNotification *_Nonnull)notification
{
_isTransitioningToFullScreen = false;
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
@ -441,7 +445,10 @@
_parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint32>([event timestamp] * 1000), AvnInputModifiersNone, point, delta);
}
_parent->BringToFront();
if(!_isTransitioningToFullScreen)
{
_parent->BringToFront();
}
}
break;

2
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@ -90,8 +90,6 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
START_COM_CALL;
@autoreleasepool {
InitialiseNSWindow();
if(hasPosition)
{
SetPosition(lastPositionSet);

13
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -32,11 +32,11 @@ void WindowImpl::HideOrShowTrafficLights() {
}
bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
bool hasTrafficLights = _isClientAreaExtended ? !wantsChrome : _decorations != SystemDecorationsFull;
bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
[[Window standardWindowButton:NSWindowCloseButton] setHidden:hasTrafficLights];
[[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:hasTrafficLights];
[[Window standardWindowButton:NSWindowZoomButton] setHidden:hasTrafficLights];
[[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights];
[[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights];
[[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights];
}
void WindowImpl::OnInitialiseNSWindow(){
@ -564,6 +564,11 @@ bool WindowImpl::IsDialog() {
NSWindowStyleMask WindowImpl::GetStyle() {
unsigned long s = NSWindowStyleMaskBorderless;
if(_actualWindowState == FullScreen)
{
s |= NSWindowStyleMaskFullScreen;
}
switch (_decorations) {
case SystemDecorationsNone:

2
samples/ControlCatalog/MainWindow.xaml.cs

@ -29,8 +29,6 @@ namespace ControlCatalog
DataContext = new MainWindowViewModel(_notificationArea);
_recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu;
ExtendClientAreaChromeHints = Avalonia.Platform.ExtendClientAreaChromeHints.OSXThickTitleBar;
}
public static string MenuQuitHeader => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Quit Avalonia" : "E_xit";

4
src/Avalonia.Dialogs/Avalonia.Dialogs.csproj

@ -14,6 +14,10 @@
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
</ItemGroup>
<ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
</ItemGroup>
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\DevAnalyzers.props" />
</Project>

4
src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs

@ -8,7 +8,7 @@ namespace Avalonia.Dialogs
{
public static class ManagedFileDialogExtensions
{
private class ManagedSystemDialogImpl<T> : ISystemDialogImpl where T : Window, new()
internal class ManagedSystemDialogImpl<T> : ISystemDialogImpl where T : Window, new()
{
async Task<string[]> Show(SystemDialog d, Window parent, ManagedFileDialogOptions options = null)
{
@ -141,7 +141,7 @@ namespace Avalonia.Dialogs
public static Task<string[]> ShowManagedAsync(this OpenFileDialog dialog, Window parent,
ManagedFileDialogOptions options = null) => ShowManagedAsync<Window>(dialog, parent, options);
public static Task<string[]> ShowManagedAsync<TWindow>(this OpenFileDialog dialog, Window parent,
ManagedFileDialogOptions options = null) where TWindow : Window, new()
{

2
src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj

@ -2,10 +2,12 @@
<PropertyGroup>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<PackageReference Include="Tmds.DBus" Version="0.9.0" />
</ItemGroup>
<ItemGroup Label="InternalsVisibleTo">

32
src/Avalonia.FreeDesktop/DBusFileChooser.cs

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Tmds.DBus;
[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)]
namespace Avalonia.FreeDesktop
{
[DBusInterface("org.freedesktop.portal.FileChooser")]
internal interface IFileChooser : IDBusObject
{
Task<ObjectPath> OpenFileAsync(string ParentWindow, string Title, IDictionary<string, object> Options);
Task<ObjectPath> SaveFileAsync(string ParentWindow, string Title, IDictionary<string, object> Options);
Task<ObjectPath> SaveFilesAsync(string ParentWindow, string Title, IDictionary<string, object> Options);
Task<T> GetAsync<T>(string prop);
Task<FileChooserProperties> GetAllAsync();
Task SetAsync(string prop, object val);
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler);
}
[Dictionary]
internal class FileChooserProperties
{
public uint Version { get; set; }
}
internal static class FileChooserExtensions
{
public static Task<uint> GetVersionAsync(this IFileChooser o) => o.GetAsync<uint>("version");
}
}

24
src/Avalonia.FreeDesktop/DBusHelper.cs

@ -6,7 +6,7 @@ using Tmds.DBus;
namespace Avalonia.FreeDesktop
{
public class DBusHelper
public static class DBusHelper
{
/// <summary>
/// This class uses synchronous execution at DBus connection establishment stage
@ -14,14 +14,14 @@ namespace Avalonia.FreeDesktop
/// </summary>
private class DBusSyncContext : SynchronizationContext
{
private SynchronizationContext _ctx;
private object _lock = new object();
private readonly object _lock = new();
private SynchronizationContext? _ctx;
public override void Post(SendOrPostCallback d, object state)
{
lock (_lock)
{
if (_ctx != null)
if (_ctx is not null)
_ctx?.Post(d, state);
else
lock (_lock)
@ -33,10 +33,9 @@ namespace Avalonia.FreeDesktop
{
lock (_lock)
{
if (_ctx != null)
if (_ctx is not null)
_ctx?.Send(d, state);
else
d(state);
}
}
@ -47,15 +46,14 @@ namespace Avalonia.FreeDesktop
_ctx = new AvaloniaSynchronizationContext();
}
}
public static Connection Connection { get; private set; }
public static Connection TryInitialize(string dbusAddress = null)
public static Connection? Connection { get; private set; }
public static Connection? TryInitialize(string? dbusAddress = null)
=> Connection ?? TryCreateNewConnection(dbusAddress);
public static Connection? TryCreateNewConnection(string? dbusAddress = null)
{
return Connection ?? TryCreateNewConnection(dbusAddress);
}
public static Connection TryCreateNewConnection(string dbusAddress = null)
{
var oldContext = SynchronizationContext.Current;
try
{

16
src/Avalonia.FreeDesktop/DBusRequest.cs

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Tmds.DBus;
[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)]
namespace Avalonia.FreeDesktop
{
[DBusInterface("org.freedesktop.portal.Request")]
internal interface IRequest : IDBusObject
{
Task CloseAsync();
Task<IDisposable> WatchResponseAsync(Action<(uint response, IDictionary<string, object> results)> handler, Action<Exception> onError = null);
}
}

102
src/Avalonia.FreeDesktop/DBusSystemDialog.cs

@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Logging;
using Tmds.DBus;
namespace Avalonia.FreeDesktop
{
internal class DBusSystemDialog : ISystemDialogImpl
{
private readonly IFileChooser _fileChooser;
internal static DBusSystemDialog? TryCreate()
{
var fileChooser = DBusHelper.Connection?.CreateProxy<IFileChooser>("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop");
if (fileChooser is null)
return null;
try
{
fileChooser.GetVersionAsync().GetAwaiter().GetResult();
return new DBusSystemDialog(fileChooser);
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform)?.Log(null, $"Unable to connect to org.freedesktop.portal.Desktop: {e.Message}");
return null;
}
}
private DBusSystemDialog(IFileChooser fileChooser)
{
_fileChooser = fileChooser;
}
public async Task<string[]?> ShowFileDialogAsync(FileDialog dialog, Window parent)
{
var parentWindow = $"x11:{parent.PlatformImpl!.Handle.Handle.ToString("X")}";
ObjectPath objectPath;
var options = new Dictionary<string, object>();
if (dialog.Filters is not null)
options.Add("filters", ParseFilters(dialog));
switch (dialog)
{
case OpenFileDialog openFileDialog:
options.Add("multiple", openFileDialog.AllowMultiple);
objectPath = await _fileChooser.OpenFileAsync(parentWindow, openFileDialog.Title ?? string.Empty, options);
break;
case SaveFileDialog saveFileDialog:
if (saveFileDialog.InitialFileName is not null)
options.Add("current_name", saveFileDialog.InitialFileName);
if (saveFileDialog.Directory is not null)
options.Add("current_folder", Encoding.UTF8.GetBytes(saveFileDialog.Directory));
objectPath = await _fileChooser.SaveFileAsync(parentWindow, saveFileDialog.Title ?? string.Empty, options);
break;
}
var request = DBusHelper.Connection!.CreateProxy<IRequest>("org.freedesktop.portal.Request", objectPath);
var tsc = new TaskCompletionSource<string[]?>();
using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException);
var uris = await tsc.Task;
if (uris is null)
return null;
for (var i = 0; i < uris.Length; i++)
uris[i] = new Uri(uris[i]).AbsolutePath;
return uris;
}
public async Task<string?> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent)
{
var parentWindow = $"x11:{parent.PlatformImpl!.Handle.Handle.ToString("X")}";
var options = new Dictionary<string, object>
{
{ "directory", true }
};
var objectPath = await _fileChooser.OpenFileAsync(parentWindow, dialog.Title ?? string.Empty, options);
var request = DBusHelper.Connection!.CreateProxy<IRequest>("org.freedesktop.portal.Request", objectPath);
var tsc = new TaskCompletionSource<string[]?>();
using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException);
var uris = await tsc.Task;
if (uris is null)
return null;
return uris.Length != 1 ? string.Empty : new Uri(uris[0]).AbsolutePath;
}
private static (string name, (uint style, string extension)[])[] ParseFilters(FileDialog dialog)
{
var filters = new (string name, (uint style, string extension)[])[dialog.Filters!.Count];
for (var i = 0; i < filters.Length; i++)
{
var extensions = dialog.Filters[i].Extensions.Select(static x => (0u, x)).ToArray();
filters[i] = (dialog.Filters[i].Name ?? string.Empty, extensions);
}
return filters;
}
}
}

10
src/Avalonia.X11/X11Platform.cs

@ -5,6 +5,7 @@ using System.Reflection;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Dialogs;
using Avalonia.FreeDesktop;
using Avalonia.FreeDesktop.DBusIme;
using Avalonia.Input;
@ -15,7 +16,6 @@ using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.X11;
using Avalonia.X11.Glx;
using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
@ -80,7 +80,7 @@ namespace Avalonia.X11
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader(Info))
.Bind<ISystemDialogImpl>().ToConstant(new GtkSystemDialog())
.Bind<ISystemDialogImpl>().ToConstant(DBusSystemDialog.TryCreate() as ISystemDialogImpl ?? new ManagedFileDialogExtensions.ManagedSystemDialogImpl<Window>())
.Bind<IMountedVolumeInfoProvider>().ToConstant(new LinuxMountedVolumeInfoProvider())
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(new X11PlatformLifetimeEvents(this));
@ -209,10 +209,10 @@ namespace Avalonia
public bool OverlayPopups { get; set; }
/// <summary>
/// Enables global menu support on Linux desktop environments where it's supported (e. g. XFCE and MATE with plugin, KDE, etc).
/// The default value is false.
/// Enables native file dialogs as well as global menu support on Linux desktop environments where it's supported (e. g. XFCE and MATE with plugin, KDE, etc).
/// The default value is true.
/// </summary>
public bool UseDBusMenu { get; set; }
public bool UseDBusMenu { get; set; } = true;
/// <summary>
/// Deferred renderer would be used when set to true. Immediate renderer when set to false. The default value is true.

4
src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs

@ -233,12 +233,12 @@ namespace Avalonia.Web.Blazor
private void OnKeyDown(KeyboardEventArgs e)
{
_topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, e.Code, GetModifiers(e));
_topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, e.Code, e.Key, GetModifiers(e));
}
private void OnKeyUp(KeyboardEventArgs e)
{
_topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyUp, e.Code, GetModifiers(e));
_topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyUp, e.Code, e.Key, GetModifiers(e));
}
private void OnInput(ChangeEventArgs e)

11
src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs

@ -91,9 +91,16 @@ namespace Avalonia.Web.Blazor
}
}
public void RawKeyboardEvent(RawKeyEventType type, string key, RawInputModifiers modifiers)
public void RawKeyboardEvent(RawKeyEventType type, string code, string key, RawInputModifiers modifiers)
{
if (Keycodes.KeyCodes.TryGetValue(key, out var avkey))
if (Keycodes.KeyCodes.TryGetValue(code, out var avkey))
{
if (_inputRoot is { })
{
Input?.Invoke(new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers));
}
}
else if (Keycodes.KeyCodes.TryGetValue(key, out avkey))
{
if (_inputRoot is { })
{

Loading…
Cancel
Save