committed by
Max Katz
15 changed files with 729 additions and 47 deletions
@ -0,0 +1,66 @@ |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace XEmbedSample; |
|||
|
|||
/* |
|||
This is needed specifically for GtkSharp: |
|||
https://github.com/mono/SkiaSharp/issues/3038
|
|||
https://github.com/GtkSharp/GtkSharp/issues/443
|
|||
|
|||
Instead of using plain DllImport they are manually calling dlopen with RTLD_GLOBAL and RTLD_LAZY flags: |
|||
https://github.com/GtkSharp/GtkSharp/blob/b7303616129ab5a0ca64def45649ab522d83fa4a/Source/Libs/Shared/FuncLoader.cs#L80-L92
|
|||
|
|||
Which causes libHarfBuzzSharp.so from HarfBuzzSharp to resolve some of the symbols from the system libharfbuzz.so.0 |
|||
which is a _different_ harfbuzz version. |
|||
|
|||
That results in a segfault. |
|||
|
|||
Previously there was a workaround - https://github.com/mono/SkiaSharp/pull/2247 but it got
|
|||
disabled for .NET Core / .NET 5+. |
|||
|
|||
Why linux linker builds shared libraries in a way that makes it possible for them to resolve their own symbols from |
|||
elsewhere escapes me. |
|||
|
|||
Here we are loading libHarfBuzzSharp.so from the .NET-resolved location, saving it, unloading the library |
|||
and then defining a custom resolver that would call dlopen with RTLD_NOW + RTLD_DEEPBIND |
|||
|
|||
*/ |
|||
|
|||
public unsafe class HarfbuzzWorkaround |
|||
{ |
|||
[DllImport("libc")] |
|||
static extern int dlinfo(IntPtr handle, int request, IntPtr info); |
|||
|
|||
[DllImport("libc")] |
|||
static extern IntPtr dlopen(string filename, int flags); |
|||
|
|||
private const int RTLD_DI_ORIGIN = 6; |
|||
private const int RTLD_NOW = 2; |
|||
private const int RTLD_DEEPBIND = 8; |
|||
|
|||
public static void Apply() |
|||
{ |
|||
if (RuntimeInformation.RuntimeIdentifier.Contains("musl")) |
|||
throw new PlatformNotSupportedException("musl doesn't support RTLD_DEEPBIND"); |
|||
|
|||
var libraryPathBytes = Marshal.AllocHGlobal(4096); |
|||
var handle = NativeLibrary.Load("libHarfBuzzSharp", typeof(HarfBuzzSharp.Blob).Assembly, null); |
|||
dlinfo(handle, RTLD_DI_ORIGIN, libraryPathBytes); |
|||
var libraryOrigin = Marshal.PtrToStringUTF8(libraryPathBytes); |
|||
Marshal.FreeHGlobal(libraryPathBytes); |
|||
var libraryPath = Path.Combine(libraryOrigin, "libHarfBuzzSharp.so"); |
|||
|
|||
NativeLibrary.Free(handle); |
|||
var forceLoadedHandle = dlopen(libraryPath, RTLD_NOW | RTLD_DEEPBIND); |
|||
if (forceLoadedHandle == IntPtr.Zero) |
|||
throw new DllNotFoundException($"Unable to load {libraryPath} via dlopen"); |
|||
|
|||
NativeLibrary.SetDllImportResolver(typeof(HarfBuzzSharp.Blob).Assembly, (name, assembly, searchPath) => |
|||
{ |
|||
if (name.Contains("HarfBuzzSharp")) |
|||
return dlopen(libraryPath, RTLD_NOW | RTLD_DEEPBIND); |
|||
return NativeLibrary.Load(name, assembly, searchPath); |
|||
}); |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Media; |
|||
using ControlCatalog; |
|||
using ControlCatalog.Models; |
|||
using Gtk; |
|||
|
|||
namespace XEmbedSample; |
|||
|
|||
class Program |
|||
{ |
|||
static void Main(string[] args) |
|||
{ |
|||
HarfbuzzWorkaround.Apply(); |
|||
AppBuilder.Configure<App>() |
|||
.UseSkia() |
|||
.With(new X11PlatformOptions() |
|||
{ |
|||
UseGLibMainLoop = true, |
|||
ExterinalGLibMainLoopExceptionLogger = e => Console.WriteLine(e.ToString()) |
|||
}) |
|||
.UseX11() |
|||
.SetupWithoutStarting(); |
|||
App.SetCatalogThemes(CatalogTheme.Fluent); |
|||
Gdk.Global.AllowedBackends = "x11"; |
|||
Gtk.Application.Init("myapp", ref args); |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
var w = new Gtk.Window("XEmbed Test Window"); |
|||
var socket = new AvaloniaXEmbedGtkSocket(w.StyleContext.GetBackgroundColor(StateFlags.Normal)) |
|||
{ |
|||
Content = new ScrollViewer() |
|||
{ |
|||
Content = new ControlCatalog.Pages.TextBoxPage(), |
|||
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto |
|||
} |
|||
}; |
|||
var vbox = new Gtk.Box(Gtk.Orientation.Vertical, 5); |
|||
var label = new Gtk.Label("Those are GTK controls"); |
|||
vbox.Add(label); |
|||
vbox.Add(new Gtk.Entry()); |
|||
vbox.Add(new Gtk.Button(new Gtk.Label("Do nothing"))); |
|||
vbox.PackEnd(socket, true, true, 0); |
|||
socket.HeightRequest = 400; |
|||
socket.WidthRequest = 400; |
|||
w.Add(vbox); |
|||
socket.Realize(); |
|||
|
|||
|
|||
w.AddSignalHandler("destroy", new EventHandler((_, __) => |
|||
{ |
|||
Gtk.Application.Quit(); |
|||
socket.Destroy(); |
|||
})); |
|||
w.ShowAll(); |
|||
Gtk.Application.Run(); |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
using Avalonia; |
|||
using Avalonia.X11; |
|||
using Gdk; |
|||
using Color = Cairo.Color; |
|||
|
|||
namespace XEmbedSample; |
|||
|
|||
public class AvaloniaXEmbedGtkSocket : Gtk.Socket |
|||
{ |
|||
private readonly RGBA _backgroundColor; |
|||
private XEmbedPlug? _avaloniaPlug; |
|||
public AvaloniaXEmbedGtkSocket(RGBA backgroundColor) |
|||
{ |
|||
_backgroundColor = backgroundColor; |
|||
} |
|||
|
|||
private object _content; |
|||
public object Content |
|||
{ |
|||
get => _content; |
|||
set |
|||
{ |
|||
_content = value; |
|||
if (_avaloniaPlug != null) |
|||
_avaloniaPlug.Content = _content; |
|||
} |
|||
} |
|||
|
|||
protected override void OnRealized() |
|||
{ |
|||
base.OnRealized(); |
|||
_avaloniaPlug ??= XEmbedPlug.Create(); |
|||
_avaloniaPlug.ScaleFactor = ScaleFactor; |
|||
_avaloniaPlug.BackgroundColor = Avalonia.Media.Color.FromRgb((byte)(_backgroundColor.Red * 255), |
|||
(byte)(_backgroundColor.Green * 255), |
|||
(byte)(_backgroundColor.Blue * 255) |
|||
); |
|||
_avaloniaPlug.Content = _content; |
|||
ApplyInteractiveResize(); |
|||
AddId((ulong)_avaloniaPlug.Handle); |
|||
} |
|||
|
|||
void ApplyInteractiveResize() |
|||
{ |
|||
// This is _NOT_ a part of XEmbed, but allows us to have smooth resize
|
|||
GetAllocatedSize(out var rect, out _); |
|||
var scale = ScaleFactor; |
|||
_avaloniaPlug?.ProcessInteractiveResize(new PixelSize(rect.Width * scale, rect.Height * scale)); |
|||
} |
|||
|
|||
protected override void OnSizeAllocated(Rectangle allocation) |
|||
{ |
|||
base.OnSizeAllocated(allocation); |
|||
Display.Default.Sync(); |
|||
ApplyInteractiveResize(); |
|||
} |
|||
|
|||
protected override void OnDestroyed() |
|||
{ |
|||
_avaloniaPlug?.Dispose(); |
|||
_avaloniaPlug = null; |
|||
base.OnDestroyed(); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<OutputType>Exe</OutputType> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
<Nullable>enable</Nullable> |
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="GtkSharp" Version="3.24.24.95" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> |
|||
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,8 @@ |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.X11.Dispatching; |
|||
|
|||
interface IX11PlatformDispatcher : IDispatcherImpl |
|||
{ |
|||
X11EventDispatcher EventDispatcher { get; } |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.X11; |
|||
|
|||
using static XLib; |
|||
partial class X11Window |
|||
{ |
|||
public class DefaultTopLevelWindowMode : X11WindowMode |
|||
{ |
|||
public override void Activate() |
|||
{ |
|||
if (X11.Atoms._NET_ACTIVE_WINDOW != IntPtr.Zero) |
|||
{ |
|||
Window.SendNetWMMessage(X11.Atoms._NET_ACTIVE_WINDOW, (IntPtr)1, X11.LastActivityTimestamp, |
|||
IntPtr.Zero); |
|||
} |
|||
else |
|||
{ |
|||
XRaiseWindow(X11.Display, Handle); |
|||
OnManualXRaiseWindow(); |
|||
} |
|||
|
|||
base.Activate(); |
|||
} |
|||
|
|||
protected virtual void OnManualXRaiseWindow() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public override void Show(bool activate, bool isDialog) |
|||
{ |
|||
Window._wasMappedAtLeastOnce = true; |
|||
XMapWindow(X11.Display, Handle); |
|||
XFlush(X11.Display); |
|||
base.Show(activate, isDialog); |
|||
} |
|||
|
|||
public override void Hide() |
|||
{ |
|||
XUnmapWindow(X11.Display, Handle); |
|||
base.Hide(); |
|||
} |
|||
|
|||
public override Point PointToClient(PixelPoint point) => new Point( |
|||
(point.X - (Window._position ?? default).X) / Window.RenderScaling, |
|||
(point.Y - (Window._position ?? default).Y) / Window.RenderScaling); |
|||
|
|||
public override PixelPoint PointToScreen(Point point) => new PixelPoint( |
|||
(int)(point.X * Window.RenderScaling + (Window._position ?? default).X), |
|||
(int)(point.Y * Window.RenderScaling + (Window._position ?? default).Y)); |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.X11; |
|||
using static XLib; |
|||
|
|||
partial class X11Window |
|||
{ |
|||
public class InputProxyWindowMode : DefaultTopLevelWindowMode |
|||
{ |
|||
private X11FocusProxy _focusProxy; |
|||
|
|||
public override void OnHandleCreated(IntPtr handle) |
|||
{ |
|||
_focusProxy = new X11FocusProxy(Platform, handle, OnFocusProxyEvent); |
|||
Window.SetWmClass(_focusProxy._handle, "FocusProxy"); |
|||
base.OnHandleCreated(handle); |
|||
} |
|||
|
|||
public override bool OnEvent(ref XEvent ev) |
|||
{ |
|||
if (ev.type == XEventName.ClientMessage && ev.ClientMessageEvent.ptr1 == X11.Atoms.WM_TAKE_FOCUS) |
|||
{ |
|||
XSetInputFocus(X11.Display, _focusProxy!._handle, RevertTo.Parent, ev.ClientMessageEvent.ptr2); |
|||
} |
|||
return base.OnEvent(ref ev); |
|||
} |
|||
|
|||
void OnFocusProxyEvent(ref XEvent xev) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected override void OnManualXRaiseWindow() |
|||
{ |
|||
if (_focusProxy is not null) |
|||
XSetInputFocus(X11.Display, _focusProxy._handle, 0, IntPtr.Zero); |
|||
base.OnManualXRaiseWindow(); |
|||
} |
|||
|
|||
|
|||
public override void OnDestroyNotify() |
|||
{ |
|||
_focusProxy?.Cleanup(); |
|||
_focusProxy = null; |
|||
base.OnDestroyNotify(); |
|||
} |
|||
|
|||
public override void AppendWmProtocols(List<IntPtr> data) |
|||
{ |
|||
data.Add(X11.Atoms.WM_TAKE_FOCUS); |
|||
base.AppendWmProtocols(data); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.X11; |
|||
|
|||
partial class X11Window |
|||
{ |
|||
public abstract class X11WindowMode |
|||
{ |
|||
public X11Window Window { get; private set; } |
|||
protected IntPtr Display; |
|||
protected X11Info X11; |
|||
protected AvaloniaX11Platform Platform; |
|||
protected IntPtr Handle => Window._handle; |
|||
protected IntPtr RenderHandle => Window._renderHandle; |
|||
public virtual bool BlockInput => false; |
|||
|
|||
public void Init(X11Window window) |
|||
{ |
|||
Platform = window._platform; |
|||
Display = window._platform.Display; |
|||
X11 = window._platform.Info; |
|||
Window = window; |
|||
} |
|||
|
|||
public virtual bool OnEvent(ref XEvent ev) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
public virtual void Activate() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public virtual void OnHandleCreated(IntPtr handle) |
|||
{ |
|||
} |
|||
|
|||
public virtual void OnDestroyNotify() |
|||
{ |
|||
} |
|||
|
|||
public virtual void AppendWmProtocols(List<IntPtr> data) |
|||
{ |
|||
} |
|||
|
|||
public virtual void Show(bool activate, bool isDialog) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public abstract PixelPoint PointToScreen(Point pt); |
|||
public abstract Point PointToClient(PixelPoint pt); |
|||
|
|||
public virtual void Hide() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,207 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.ComponentModel; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Embedding; |
|||
using Avalonia.Input; |
|||
|
|||
namespace Avalonia.X11; |
|||
using static XLib; |
|||
partial class X11Window |
|||
{ |
|||
public class XEmbedClientWindowMode : X11WindowMode |
|||
{ |
|||
EmbeddableControlRoot? Root => Window._inputRoot as EmbeddableControlRoot; |
|||
private bool _focusedInEmbedder; |
|||
private bool _embedderActivated; |
|||
private bool _disabled; |
|||
private IntPtr _currentEmbedder; |
|||
private bool _suppressConfigureEvents; |
|||
|
|||
public override bool BlockInput => _disabled; |
|||
public double Scaling |
|||
{ |
|||
get => Window._scalingOverride ?? 1; |
|||
set => Window._scalingOverride = value; |
|||
} |
|||
|
|||
private WeakReference<IInputElement>? _savedFocus; |
|||
|
|||
private IInputElement? SavedFocus |
|||
{ |
|||
get => _savedFocus?.TryGetTarget(out var target) == true ? target : null; |
|||
set => _savedFocus = value == null ? null : new WeakReference<IInputElement>(value); |
|||
} |
|||
|
|||
public override void OnHandleCreated(IntPtr handle) |
|||
{ |
|||
var data = new[] |
|||
{ |
|||
IntPtr.Zero, new(1) /* XEMBED_MAPPED */ |
|||
}; |
|||
|
|||
XChangeProperty(Display, handle, X11.Atoms._XEMBED_INFO, X11.Atoms._XEMBED_INFO, 32, PropertyMode.Replace, |
|||
data, data.Length); |
|||
Scaling = 1; |
|||
|
|||
base.OnHandleCreated(handle); |
|||
} |
|||
|
|||
void SendXEmbedMessage(XEmbedMessage message, IntPtr detail = default, IntPtr data1 = default, IntPtr data2 = default) |
|||
{ |
|||
if (_currentEmbedder == IntPtr.Zero) |
|||
return; |
|||
var xev = new XEvent |
|||
{ |
|||
ClientMessageEvent = |
|||
{ |
|||
type = XEventName.ClientMessage, |
|||
send_event = 1, |
|||
window = _currentEmbedder, |
|||
message_type = X11.Atoms._XEMBED, |
|||
format = 32, |
|||
ptr1 = default, |
|||
ptr2 = new ((int)message), |
|||
ptr3 = detail, |
|||
ptr4 = data1, |
|||
ptr5 = data2 |
|||
} |
|||
}; |
|||
XSendEvent(X11.Display, _currentEmbedder, false, |
|||
new IntPtr((int)(EventMask.NoEventMask)), ref xev); |
|||
} |
|||
|
|||
static XEmbedClientWindowMode() |
|||
{ |
|||
KeyboardDevice.Instance.PropertyChanged += (_, args) => |
|||
{ |
|||
if (args.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement)) |
|||
{ |
|||
if (KeyboardDevice.Instance.FocusedElement is Visual visual |
|||
&& visual.VisualRoot is EmbeddableControlRoot root |
|||
&& root.PlatformImpl is X11Window window |
|||
&& window._mode is XEmbedClientWindowMode xembedMode |
|||
&& xembedMode._currentEmbedder != IntPtr.Zero) |
|||
{ |
|||
xembedMode.SavedFocus = KeyboardDevice.Instance.FocusedElement; |
|||
xembedMode.SendXEmbedMessage(XEmbedMessage.RequestFocus); |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
void Reset() |
|||
{ |
|||
_embedderActivated = false; |
|||
_focusedInEmbedder = false; |
|||
_disabled = false; |
|||
UpdateActivation(); |
|||
} |
|||
|
|||
void OnXEmbedMessage(IntPtr time, XEmbedMessage message, IntPtr detail, IntPtr data1, IntPtr data2) |
|||
{ |
|||
if (message == XEmbedMessage.EmbeddedNotify) |
|||
{ |
|||
Reset(); |
|||
_currentEmbedder = data1; |
|||
} |
|||
else if (message == XEmbedMessage.FocusIn) |
|||
{ |
|||
_focusedInEmbedder = true; |
|||
UpdateActivation(); |
|||
} |
|||
else if (message == XEmbedMessage.FocusOut) |
|||
{ |
|||
_focusedInEmbedder = false; |
|||
UpdateActivation(); |
|||
} |
|||
else if (message == XEmbedMessage.WindowActivate) |
|||
{ |
|||
_embedderActivated = true; |
|||
UpdateActivation(); |
|||
} |
|||
else if (message == XEmbedMessage.WindowDeactivate) |
|||
{ |
|||
_embedderActivated = false; |
|||
UpdateActivation(); |
|||
} |
|||
else if (message == XEmbedMessage.ModalityOn) |
|||
_disabled = true; |
|||
else if (message == XEmbedMessage.ModalityOff) |
|||
_disabled = false; |
|||
} |
|||
|
|||
private void UpdateActivation() |
|||
{ |
|||
var active = _focusedInEmbedder && _embedderActivated; |
|||
|
|||
if (active) |
|||
{ |
|||
((FocusManager?)Root?.FocusManager)?.SetFocusScope(Root); |
|||
SavedFocus?.Focus(); |
|||
SavedFocus = null; |
|||
} |
|||
else |
|||
{ |
|||
SavedFocus = Root?.IsKeyboardFocusWithin == true ? Root.FocusManager?.GetFocusedElement() : null; |
|||
Window.LostFocus?.Invoke(); |
|||
} |
|||
} |
|||
|
|||
public override bool OnEvent(ref XEvent ev) |
|||
{ |
|||
// In this mode we are getting the expected size directly from the embedder
|
|||
if (_suppressConfigureEvents && ev.type == XEventName.ConfigureNotify) |
|||
return true; |
|||
if(ev.type == XEventName.MapNotify) |
|||
Root?.StartRendering(); |
|||
else if (ev.type == XEventName.UnmapNotify) |
|||
Root?.StopRendering(); |
|||
else if (ev.type == XEventName.ReparentNotify) |
|||
{ |
|||
Root?.StopRendering(); |
|||
_currentEmbedder = IntPtr.Zero; |
|||
Reset(); |
|||
} |
|||
else if (ev.type == XEventName.ClientMessage && ev.ClientMessageEvent.message_type == X11.Atoms._XEMBED) |
|||
{ |
|||
OnXEmbedMessage(ev.ClientMessageEvent.ptr1, |
|||
(XEmbedMessage)ev.ClientMessageEvent.ptr2.ToInt32(), |
|||
ev.ClientMessageEvent.ptr3, |
|||
ev.ClientMessageEvent.ptr4, ev.ClientMessageEvent.ptr5); |
|||
return true; |
|||
} |
|||
|
|||
return base.OnEvent(ref ev); |
|||
} |
|||
|
|||
public void ProcessInteractiveResize(PixelSize size) |
|||
{ |
|||
_suppressConfigureEvents = true; |
|||
Window._realSize = size; |
|||
Window.Resized?.Invoke(Window.ClientSize, WindowResizeReason.User); |
|||
Window.Paint?.Invoke(new(Window.ClientSize)); |
|||
} |
|||
|
|||
PixelVector GetWindowOffset() |
|||
{ |
|||
XTranslateCoordinates(Display, Handle, X11.DefaultRootWindow, |
|||
0, 0, out var offsetX, out var offsetY, out _); |
|||
return new PixelVector(offsetX, offsetY); |
|||
} |
|||
|
|||
public override Point PointToClient(PixelPoint point) |
|||
{ |
|||
var pos = GetWindowOffset(); |
|||
return new Point( |
|||
(point.X - pos.X) / Window.RenderScaling, |
|||
(point.Y - pos.Y) / Window.RenderScaling); |
|||
} |
|||
|
|||
public override PixelPoint PointToScreen(Point point) => |
|||
new PixelPoint( |
|||
(int)(point.X * Window.RenderScaling), |
|||
(int)(point.Y * Window.RenderScaling)) |
|||
+ GetWindowOffset(); |
|||
} |
|||
} |
|||
@ -0,0 +1,81 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using Avalonia.Controls.Embedding; |
|||
using Avalonia.Media; |
|||
using Avalonia.Threading; |
|||
using Avalonia.X11.Dispatching; |
|||
|
|||
namespace Avalonia.X11; |
|||
|
|||
public class XEmbedPlug : IDisposable |
|||
{ |
|||
private EmbeddableControlRoot _root; |
|||
private Color _backgroundColor; |
|||
private readonly X11Info _x11; |
|||
private readonly X11Window.XEmbedClientWindowMode _mode; |
|||
|
|||
private XEmbedPlug(IntPtr? parentXid) |
|||
{ |
|||
var platform = AvaloniaLocator.Current.GetService<AvaloniaX11Platform>(); |
|||
_mode = new X11Window.XEmbedClientWindowMode(); |
|||
_root = new EmbeddableControlRoot(new X11Window(platform, null, _mode)); |
|||
_root.Prepare(); |
|||
_x11 = platform.Info; |
|||
if (parentXid.HasValue) |
|||
XLib.XReparentWindow(platform.Display, Handle, parentXid.Value, 0, 0); |
|||
|
|||
// Make sure that the newly created XID is visible for other clients
|
|||
XLib.XSync(platform.Display, false); |
|||
} |
|||
|
|||
public IntPtr Handle => |
|||
_root?.PlatformImpl!.Handle!.Handle ?? throw new ObjectDisposedException(nameof(XEmbedPlug)); |
|||
|
|||
public object Content |
|||
{ |
|||
get => _root.Content; |
|||
set => _root.Content = value; |
|||
} |
|||
|
|||
public Color BackgroundColor |
|||
{ |
|||
get => _backgroundColor; |
|||
set |
|||
{ |
|||
_backgroundColor = value; |
|||
XLib.XSetWindowBackground(_x11.Display, Handle, new IntPtr( |
|||
(int)(value.ToUInt32() | 0xff000000))); |
|||
XLib.XFlush(_x11.Display); |
|||
} |
|||
} |
|||
|
|||
public double ScaleFactor |
|||
{ |
|||
get => _mode.Scaling; |
|||
set => _mode.Scaling = value; |
|||
} |
|||
|
|||
public void ProcessInteractiveResize(PixelSize size) |
|||
{ |
|||
|
|||
var events = (IX11PlatformDispatcher)AvaloniaLocator.Current.GetService<IDispatcherImpl>(); |
|||
events.EventDispatcher.DispatchX11Events(CancellationToken.None); |
|||
_mode.ProcessInteractiveResize(size); |
|||
Dispatcher.UIThread.RunJobs(DispatcherPriority.UiThreadRender); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_root != null) |
|||
{ |
|||
_root.StopRendering(); |
|||
_root.Dispose(); |
|||
_root = null; |
|||
} |
|||
} |
|||
|
|||
public static XEmbedPlug Create() => new(null); |
|||
|
|||
public static XEmbedPlug Create(IntPtr embedderXid) => |
|||
embedderXid == IntPtr.Zero ? throw new ArgumentException() : new XEmbedPlug(embedderXid); |
|||
} |
|||
Loading…
Reference in new issue