csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
208 lines
7.2 KiB
208 lines
7.2 KiB
using System;
|
|
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()
|
|
{
|
|
if (KeyboardDevice.Instance is not { } keyboardDevice)
|
|
return;
|
|
|
|
keyboardDevice.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();
|
|
}
|
|
}
|
|
|