Browse Source

[X11] attempt to use _NET_WM_STATE_FOCUSED or _NET_ACTIVE_WINDOW for tracking window activation, if available (#18464)

pull/18471/head
Nikita Tsukanov 1 year ago
committed by GitHub
parent
commit
410b46edfc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 91
      src/Avalonia.X11/ActivityTrackingHelper.cs
  2. 1
      src/Avalonia.X11/X11Atoms.cs
  3. 77
      src/Avalonia.X11/X11Globals.cs
  4. 116
      src/Avalonia.X11/X11Window.cs
  5. 29
      src/Avalonia.X11/XLib.Helpers.cs
  6. 2
      src/Avalonia.X11/XLib.cs

91
src/Avalonia.X11/ActivityTrackingHelper.cs

@ -0,0 +1,91 @@
using System;
using System.Linq;
using Avalonia.Threading;
namespace Avalonia.X11;
internal class WindowActivationTrackingHelper : IDisposable
{
private readonly AvaloniaX11Platform _platform;
private readonly X11Window _window;
private bool _active;
public event Action<bool>? ActivationChanged;
public WindowActivationTrackingHelper(AvaloniaX11Platform platform, X11Window window)
{
_platform = platform;
_window = window;
_platform.Globals.NetActiveWindowPropertyChanged += OnNetActiveWindowChanged;
_platform.Globals.WindowActivationTrackingModeChanged += OnWindowActivationTrackingModeChanged;
}
void SetActive(bool active)
{
if (active != _active)
{
_active = active;
ActivationChanged?.Invoke(active);
}
}
void RequeryActivation()
{
// Update the active state from WM-set properties
if (Mode == X11Globals.WindowActivationTrackingMode._NET_ACTIVE_WINDOW)
OnNetActiveWindowChanged();
if (Mode == X11Globals.WindowActivationTrackingMode._NET_WM_STATE_FOCUSED)
OnNetWmStateChanged(XLib.XGetWindowPropertyAsIntPtrArray(_platform.Display, _window.Handle.Handle,
_platform.Info.Atoms._NET_WM_STATE, _platform.Info.Atoms.XA_ATOM) ?? []);
}
private void OnWindowActivationTrackingModeChanged() =>
DispatcherTimer.RunOnce(RequeryActivation, TimeSpan.FromSeconds(1), DispatcherPriority.Input);
private X11Globals.WindowActivationTrackingMode Mode => _platform.Globals.ActivationTrackingMode;
public void OnEvent(ref XEvent ev)
{
if (ev.type is not XEventName.FocusIn and not XEventName.FocusOut)
return;
// Always attempt to activate transient children on focus events
if (ev.type == XEventName.FocusIn && _window.ActivateTransientChildIfNeeded()) return;
if (Mode != X11Globals.WindowActivationTrackingMode.FocusEvents)
return;
// See: https://github.com/fltk/fltk/issues/295
if ((NotifyMode)ev.FocusChangeEvent.mode is not NotifyMode.NotifyNormal)
return;
SetActive(ev.type == XEventName.FocusIn);
}
private void OnNetActiveWindowChanged()
{
if (Mode == X11Globals.WindowActivationTrackingMode._NET_ACTIVE_WINDOW)
{
var value = XLib.XGetWindowPropertyAsIntPtrArray(_platform.Display, _platform.Info.RootWindow,
_platform.Info.Atoms._NET_ACTIVE_WINDOW,
(IntPtr)_platform.Info.Atoms.XA_WINDOW);
if (value == null || value.Length == 0)
SetActive(false);
else
SetActive(value[0] == _window.Handle.Handle);
}
}
public void Dispose()
{
_platform.Globals.NetActiveWindowPropertyChanged -= OnNetActiveWindowChanged;
}
public void OnNetWmStateChanged(IntPtr[] atoms)
{
if (Mode == X11Globals.WindowActivationTrackingMode._NET_WM_STATE_FOCUSED)
SetActive(atoms.Contains(_platform.Info.Atoms._NET_WM_STATE_FOCUSED));
}
}

1
src/Avalonia.X11/X11Atoms.cs

@ -193,6 +193,7 @@ namespace Avalonia.X11
public IntPtr MANAGER;
public IntPtr _KDE_NET_WM_BLUR_BEHIND_REGION;
public IntPtr INCR;
public IntPtr _NET_WM_STATE_FOCUSED;
private readonly Dictionary<string, IntPtr> _namesToAtoms = new Dictionary<string, IntPtr>();
private readonly Dictionary<IntPtr, string> _atomsToNames = new Dictionary<IntPtr, string>();

77
src/Avalonia.X11/X11Globals.cs

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using static Avalonia.X11.XLib;
@ -15,11 +16,21 @@ namespace Avalonia.X11
private string? _wmName;
private IntPtr _compositionAtomOwner;
private bool _isCompositionEnabled;
private WindowActivationTrackingMode _activationTrackingMode;
public event Action? WindowManagerChanged;
public event Action? CompositionChanged;
public event Action<IntPtr>? RootPropertyChanged;
public event Action? NetActiveWindowPropertyChanged;
public event Action? RootGeometryChangedChanged;
public event Action? WindowActivationTrackingModeChanged;
public enum WindowActivationTrackingMode
{
FocusEvents,
_NET_ACTIVE_WINDOW,
_NET_WM_STATE_FOCUSED
}
public X11Globals(AvaloniaX11Platform plat)
{
@ -31,7 +42,7 @@ namespace Avalonia.X11
XSelectInput(_x11.Display, _rootWindow,
new IntPtr((int)(EventMask.StructureNotifyMask | EventMask.PropertyChangeMask)));
_compositingAtom = XInternAtom(_x11.Display, "_NET_WM_CM_S" + _screenNumber, false);
UpdateWmName();
OnNewWindowManager();
UpdateCompositingAtomOwner();
}
@ -73,6 +84,19 @@ namespace Avalonia.X11
}
}
}
public WindowActivationTrackingMode ActivationTrackingMode
{
get => _activationTrackingMode;
set
{
if (_activationTrackingMode != value)
{
_activationTrackingMode = value;
WindowActivationTrackingModeChanged?.Invoke();
}
}
}
private IntPtr GetSupportingWmCheck(IntPtr window)
{
@ -128,12 +152,15 @@ namespace Avalonia.X11
UpdateCompositingAtomOwner();
}
private void UpdateWmName() => WmName = GetWmName();
private string? GetWmName()
IntPtr GetActiveWm() => GetSupportingWmCheck(_rootWindow) is { } wmWindow
&& wmWindow != IntPtr.Zero
&& wmWindow == GetSupportingWmCheck(wmWindow)
? wmWindow
: IntPtr.Zero;
private string? GetWmName(IntPtr wm)
{
var wm = GetSupportingWmCheck(_rootWindow);
if (wm == IntPtr.Zero || wm != GetSupportingWmCheck(wm))
if (wm == IntPtr.Zero)
return null;
XGetWindowProperty(_x11.Display, wm, _x11.Atoms._NET_WM_NAME,
IntPtr.Zero, new IntPtr(0x7fffffff),
@ -152,13 +179,47 @@ namespace Avalonia.X11
XFree(prop);
}
}
private WindowActivationTrackingMode GetWindowActivityTrackingMode(IntPtr wm)
{
if (Environment.GetEnvironmentVariable("AVALONIA_DEBUG_FORCE_X11_ACTIVATION_TRACKING_MODE") is
{ } forcedModeString
&& Enum.TryParse<WindowActivationTrackingMode>(forcedModeString, true, out var forcedMode))
return forcedMode;
if (wm == IntPtr.Zero)
return WindowActivationTrackingMode.FocusEvents;
var supportedFeatures = XGetWindowPropertyAsIntPtrArray(_x11.Display, _x11.RootWindow,
_x11.Atoms._NET_SUPPORTED, _x11.Atoms.XA_ATOM) ?? [];
if (supportedFeatures.Contains(_x11.Atoms._NET_WM_STATE_FOCUSED))
return WindowActivationTrackingMode._NET_WM_STATE_FOCUSED;
if (supportedFeatures.Contains(_x11.Atoms._NET_ACTIVE_WINDOW))
return WindowActivationTrackingMode._NET_ACTIVE_WINDOW;
return WindowActivationTrackingMode.FocusEvents;
}
private void OnNewWindowManager()
{
var wm = GetActiveWm();
WmName = GetWmName(wm);
ActivationTrackingMode = GetWindowActivityTrackingMode(wm);
}
private void OnRootWindowEvent(ref XEvent ev)
{
if (ev.type == XEventName.PropertyNotify)
{
if(ev.PropertyEvent.atom == _x11.Atoms._NET_SUPPORTING_WM_CHECK)
UpdateWmName();
if (ev.PropertyEvent.atom == _x11.Atoms._NET_SUPPORTING_WM_CHECK)
{
OnNewWindowManager();
}
if (ev.PropertyEvent.atom == _x11.Atoms._NET_ACTIVE_WINDOW)
NetActiveWindowPropertyChanged?.Invoke();
RootPropertyChanged?.Invoke(ev.PropertyEvent.atom);
}

116
src/Avalonia.X11/X11Window.cs

@ -63,6 +63,7 @@ namespace Avalonia.X11
private double? _scalingOverride;
private bool _disabled;
private TransparencyHelper? _transparencyHelper;
private WindowActivationTrackingHelper? _activationTracker;
private RawEventGrouper? _rawEventGrouper;
private bool _useRenderWindow = false;
private bool _useCompositorDrivenRenderWindowResize = false;
@ -231,6 +232,9 @@ namespace Avalonia.X11
_transparencyHelper = new TransparencyHelper(_x11, _handle, platform.Globals);
_transparencyHelper.SetTransparencyRequest(Array.Empty<WindowTransparencyLevel>());
_activationTracker = new(_platform, this);
_activationTracker.ActivationChanged += HandleActivation;
CreateIC();
XFlush(_x11.Display);
@ -510,6 +514,8 @@ namespace Avalonia.X11
if(_mode.OnEvent(ref ev))
return;
_activationTracker?.OnEvent(ref ev);
if (ev.type == XEventName.MapNotify)
{
_mapped = true;
@ -524,24 +530,6 @@ namespace Avalonia.X11
{
EnqueuePaint();
}
else if (ev.type == XEventName.FocusIn)
{
if (ActivateTransientChildIfNeeded())
return;
// See: https://github.com/fltk/fltk/issues/295
if ((NotifyMode)ev.FocusChangeEvent.mode is not NotifyMode.NotifyNormal)
return;
Activated?.Invoke();
_imeControl?.SetWindowActive(true);
}
else if (ev.type == XEventName.FocusOut)
{
// See: https://github.com/fltk/fltk/issues/295
if ((NotifyMode)ev.FocusChangeEvent.mode is not NotifyMode.NotifyNormal)
return;
_imeControl?.SetWindowActive(false);
Deactivated?.Invoke();
}
else if (ev.type == XEventName.MotionNotify)
MouseEvent(RawPointerEventType.Move, ref ev, ev.MotionEvent.state);
else if (ev.type == XEventName.LeaveNotify)
@ -679,6 +667,22 @@ namespace Avalonia.X11
}
}
private void HandleActivation(bool active)
{
if (active)
{
if (ActivateTransientChildIfNeeded())
return;
Activated?.Invoke();
_imeControl?.SetWindowActive(true);
}
else
{
_imeControl?.SetWindowActive(false);
Deactivated?.Invoke();
}
}
private Thickness? GetFrameExtents()
{
if (_systemDecorations != SystemDecorations.Full)
@ -773,58 +777,56 @@ namespace Avalonia.X11
}
}
private void OnPropertyChange(IntPtr atom, bool hasValue)
private void OnPropertyChange(IntPtr property, bool hasValue)
{
if (atom == _x11.Atoms._NET_FRAME_EXTENTS)
if (property == _x11.Atoms._NET_FRAME_EXTENTS)
{
// Occurs once the window has been mapped, which is the earliest the extents
// can be retrieved, so invoke event to force update of TopLevel.FrameSize.
Resized?.Invoke(ClientSize, WindowResizeReason.Unspecified);
}
if (atom == _x11.Atoms._NET_WM_STATE)
if (property == _x11.Atoms._NET_WM_STATE)
{
WindowState state = WindowState.Normal;
if(hasValue)
var atoms = hasValue
? XGetWindowPropertyAsIntPtrArray(_x11.Display, _handle, _x11.Atoms._NET_WM_STATE,
(IntPtr)Atom.XA_ATOM)
?? []
: [];
int maximized = 0;
foreach (var atom in atoms)
{
if (atom == _x11.Atoms._NET_WM_STATE_HIDDEN)
{
state = WindowState.Minimized;
break;
}
XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_STATE, IntPtr.Zero, new IntPtr(256),
false, (IntPtr)Atom.XA_ATOM, out _, out _, out var nitems, out _,
out var prop);
int maximized = 0;
var pitems = (IntPtr*)prop.ToPointer();
for (var c = 0; c < nitems.ToInt32(); c++)
if(atom == _x11.Atoms._NET_WM_STATE_FULLSCREEN)
{
if (pitems[c] == _x11.Atoms._NET_WM_STATE_HIDDEN)
{
state = WindowState.Minimized;
break;
}
state = WindowState.FullScreen;
break;
}
if(pitems[c] == _x11.Atoms._NET_WM_STATE_FULLSCREEN)
if (atom == _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ ||
atom == _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT)
{
maximized++;
if (maximized == 2)
{
state = WindowState.FullScreen;
state = WindowState.Maximized;
break;
}
if (pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ ||
pitems[c] == _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT)
{
maximized++;
if (maximized == 2)
{
state = WindowState.Maximized;
break;
}
}
}
XFree(prop);
}
if (_lastWindowState != state)
{
_lastWindowState = state;
WindowStateChanged?.Invoke(state);
}
_activationTracker?.OnNetWmStateChanged(atoms);
}
}
@ -1030,6 +1032,12 @@ namespace Avalonia.X11
_rawEventGrouper = null;
}
if (_activationTracker != null)
{
_activationTracker.Dispose();
_activationTracker = null;
}
if (_transparencyHelper != null)
{
_transparencyHelper.Dispose();
@ -1075,7 +1083,7 @@ namespace Avalonia.X11
}
}
private bool ActivateTransientChildIfNeeded()
internal bool ActivateTransientChildIfNeeded()
{
if (_disabled)
{
@ -1448,14 +1456,10 @@ namespace Avalonia.X11
if (!_mapped)
{
XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_STATE, IntPtr.Zero, new IntPtr(256),
false, (IntPtr)Atom.XA_ATOM, out _, out _, out var nitems, out _,
out var prop);
var ptr = (IntPtr*)prop.ToPointer();
var newAtoms = new HashSet<IntPtr>();
for (var c = 0; c < nitems.ToInt64(); c++)
newAtoms.Add(*(ptr+c));
XFree(prop);
var newAtoms = new HashSet<IntPtr>(XGetWindowPropertyAsIntPtrArray(_x11.Display, _handle,
_x11.Atoms._NET_WM_STATE,
(IntPtr)Atom.XA_ATOM) ?? []);
foreach(var atom in atoms)
if (enable)
newAtoms.Add(atom);

29
src/Avalonia.X11/XLib.Helpers.cs

@ -0,0 +1,29 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.X11;
internal static partial class XLib
{
public static IntPtr[]? XGetWindowPropertyAsIntPtrArray(IntPtr display, IntPtr window, IntPtr atom, IntPtr reqType)
{
if (XGetWindowProperty(display, window, atom, IntPtr.Zero, new IntPtr(0x7fffffff),
false, reqType, out var actualType, out var actualFormat, out var nitems, out _,
out var prop) != 0)
return null;
try
{
if (actualType != reqType || actualFormat != 32 || nitems == IntPtr.Zero)
return null;
var buffer = new IntPtr[nitems.ToInt32()];
Marshal.Copy(prop, buffer, 0, buffer.Length);
return buffer;
}
finally
{
XFree(prop);
}
}
}

2
src/Avalonia.X11/XLib.cs

@ -14,7 +14,7 @@ using Avalonia.Platform.Interop;
namespace Avalonia.X11
{
internal unsafe static class XLib
internal unsafe static partial class XLib
{
private const string libX11 = "libX11.so.6";
private const string libX11Randr = "libXrandr.so.2";

Loading…
Cancel
Save