committed by
GitHub
6 changed files with 251 additions and 65 deletions
@ -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)); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue