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