diff --git a/src/Avalonia.X11/X11FocusProxy.cs b/src/Avalonia.X11/X11FocusProxy.cs
new file mode 100644
index 00000000000..4fdaf643abe
--- /dev/null
+++ b/src/Avalonia.X11/X11FocusProxy.cs
@@ -0,0 +1,90 @@
+using System;
+using static Avalonia.X11.XLib;
+
+namespace Avalonia.X11
+{
+ ///
+ /// An invisible X window that owns the input focus and forwards events to the owner window.
+ ///
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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;
+
+ ///
+ /// Initializes instance and creates the underlying X window.
+ ///
+ ///
+ /// The X11 platform.
+ /// The parent window to proxy the focus for.
+ /// An event handler that will handle X events that come to the proxy.
+ 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;
+ }
+ }
+}
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index a2019c276b8..77db7301c9a 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -64,6 +64,7 @@ internal unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client
private RawEventGrouper? _rawEventGrouper;
private bool _useRenderWindow = false;
private bool _usePositioningFlags = false;
+ private X11FocusProxy _focusProxy;
private enum XSyncState
{
@@ -157,6 +158,8 @@ public X11Window(AvaloniaX11Platform platform, IWindowImpl? popupParent, bool ov
_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 @@ public X11Window(AvaloniaX11Platform platform, IWindowImpl? popupParent, bool ov
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