Browse Source

Merge pull request #12751 from vlad-lubenskyi/x11-focus-proxy

Use the focus proxy window in X11
release/11.0.6
Max Katz 3 years ago
parent
commit
1ea054ad65
  1. 90
      src/Avalonia.X11/X11FocusProxy.cs
  2. 20
      src/Avalonia.X11/X11Window.cs

90
src/Avalonia.X11/X11FocusProxy.cs

@ -0,0 +1,90 @@
using System;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
/// <summary>
/// An invisible X window that owns the input focus and forwards events to the owner window.
/// </summary>
/// <remarks>
/// <para>
/// This is a known Linux technique for an auxiliary invisible window to hold the input focus
/// for the main window. It is required by XEmbed protocol, but it also works for regular cases
/// that don't imply embedded windows.
/// </para>
/// </remarks>
///
/// <see href="https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html" />
/// <see href="https://gitlab.gnome.org/GNOME/gtk/-/blob/3.22.30/gdk/x11/gdkwindow-x11.c#L823" />
internal class X11FocusProxy
{
private const int InvisibleBorder = 0;
private const int DepthCopyFromParent = 0;
private readonly IntPtr _visualCopyFromParent = IntPtr.Zero;
private readonly (int X, int Y) _outOfScreen = (-1, -1);
private readonly (int Width, int Height) _smallest = (1, 1);
internal IntPtr _handle;
private readonly AvaloniaX11Platform _platform;
private readonly X11PlatformThreading.EventHandler _ownerEventHandler;
/// <summary>
/// Initializes instance and creates the underlying X window.
/// </summary>
///
/// <param name="platform">The X11 platform.</param>
/// <param name="parent">The parent window to proxy the focus for.</param>
/// <param name="eventHandler">An event handler that will handle X events that come to the proxy.</param>
internal X11FocusProxy(AvaloniaX11Platform platform, IntPtr parent,
X11PlatformThreading.EventHandler eventHandler)
{
_handle = PrepareXWindow(platform.Info.Display, parent);
_platform = platform;
_ownerEventHandler = eventHandler;
_platform.Windows[_handle] = OnEvent;
}
internal void Cleanup()
{
if (_handle != IntPtr.Zero)
{
_platform.Windows.Remove(_handle);
_handle = IntPtr.Zero;
}
}
private void OnEvent(ref XEvent ev)
{
if (ev.type is XEventName.FocusIn or XEventName.FocusOut)
{
this._ownerEventHandler(ref ev);
}
if (ev.type is XEventName.KeyPress or XEventName.KeyRelease)
{
this._ownerEventHandler(ref ev);
}
}
private IntPtr PrepareXWindow(IntPtr display, IntPtr parent)
{
var valueMask = default(EventMask)
| EventMask.FocusChangeMask
| EventMask.KeyPressMask
| EventMask.KeyReleaseMask;
var attrs = new XSetWindowAttributes();
var handle = XCreateWindow(display, parent,
_outOfScreen.X, _outOfScreen.Y,
_smallest.Width, _smallest.Height,
InvisibleBorder,
DepthCopyFromParent,
(int)CreateWindowArgs.InputOutput,
_visualCopyFromParent,
new UIntPtr((uint)valueMask),
ref attrs);
XMapWindow(display, handle);
XSelectInput(display, handle, new IntPtr((uint)valueMask));
return handle;
}
}
}

20
src/Avalonia.X11/X11Window.cs

@ -64,6 +64,7 @@ namespace Avalonia.X11
private RawEventGrouper? _rawEventGrouper;
private bool _useRenderWindow = false;
private bool _usePositioningFlags = false;
private X11FocusProxy _focusProxy;
private enum XSyncState
{
@ -157,6 +158,8 @@ namespace Avalonia.X11
_renderHandle = _handle;
Handle = new PlatformHandle(_handle, "XID");
_focusProxy = new X11FocusProxy(platform, _handle, OnEvent);
SetWmClass(_focusProxy._handle, "FocusProxy");
_realSize = new PixelSize(defaultWidth, defaultHeight);
platform.Windows[_handle] = OnEvent;
XEventMask ignoredMask = XEventMask.SubstructureRedirectMask
@ -176,7 +179,7 @@ namespace Avalonia.X11
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM,
32, PropertyMode.Replace, new[] { _x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL }, 1);
SetWmClass(_platform.Options.WmClass);
SetWmClass(_handle, _platform.Options.WmClass);
}
var surfaces = new List<object>
@ -213,7 +216,7 @@ namespace Avalonia.X11
InitializeIme();
XChangeProperty(_x11.Display, _handle, _x11.Atoms.WM_PROTOCOLS, _x11.Atoms.XA_ATOM, 32,
PropertyMode.Replace, new[] { _x11.Atoms.WM_DELETE_WINDOW, _x11.Atoms._NET_WM_SYNC_REQUEST }, 2);
PropertyMode.Replace, new[] { _x11.Atoms.WM_DELETE_WINDOW, _x11.Atoms.WM_TAKE_FOCUS, _x11.Atoms._NET_WM_SYNC_REQUEST }, 3);
if (_x11.HasXSync)
{
@ -548,6 +551,11 @@ namespace Avalonia.X11
_xSyncValue.Hi = ev.ClientMessageEvent.ptr4.ToInt32();
_xSyncState = XSyncState.WaitConfigure;
}
else if (ev.ClientMessageEvent.ptr1 == _x11.Atoms.WM_TAKE_FOCUS)
{
IntPtr time = ev.ClientMessageEvent.ptr2;
XSetInputFocus(_x11.Display, _focusProxy._handle, RevertTo.Parent, time);
}
}
}
else if (ev.type == XEventName.KeyPress || ev.type == XEventName.KeyRelease)
@ -923,6 +931,8 @@ namespace Avalonia.X11
{
_renderHandle = IntPtr.Zero;
}
_focusProxy.Cleanup();
}
private bool ActivateTransientChildIfNeeded()
@ -1078,7 +1088,7 @@ namespace Avalonia.X11
else
{
XRaiseWindow(_x11.Display, _handle);
XSetInputFocus(_x11.Display, _handle, 0, IntPtr.Zero);
XSetInputFocus(_x11.Display, _focusProxy._handle, 0, IntPtr.Zero);
}
}
@ -1170,7 +1180,7 @@ namespace Avalonia.X11
}
}
public void SetWmClass(string wmClass)
public void SetWmClass(IntPtr handle, string wmClass)
{
// See https://tronche.com/gui/x/icccm/sec-4.html#WM_CLASS
// We don't actually parse the application's command line, so we only use RESOURCE_NAME and argv[0]
@ -1186,7 +1196,7 @@ namespace Avalonia.X11
{
hint->res_name = pAppId;
hint->res_class = pWmClass;
XSetClassHint(_x11.Display, _handle, hint);
XSetClassHint(_x11.Display, handle, hint);
}
XFree(hint);

Loading…
Cancel
Save