diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs index b59883ef94f..cfda68f9e87 100644 --- a/src/Avalonia.X11/X11Atoms.cs +++ b/src/Avalonia.X11/X11Atoms.cs @@ -155,6 +155,7 @@ internal class X11Atoms public readonly IntPtr _NET_FRAME_EXTENTS; public readonly IntPtr _NET_WM_PING; public readonly IntPtr _NET_WM_SYNC_REQUEST; + public readonly IntPtr _NET_WM_SYNC_REQUEST_COUNTER; public readonly IntPtr _NET_SYSTEM_TRAY_S; public readonly IntPtr _NET_SYSTEM_TRAY_ORIENTATION; public readonly IntPtr _NET_SYSTEM_TRAY_OPCODE; diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index 9920907601d..13dc460f45a 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/src/Avalonia.X11/X11Info.cs @@ -33,6 +33,7 @@ unsafe class X11Info public IntPtr LastActivityTimestamp { get; set; } public XVisualInfo? TransparentVisualInfo { get; set; } public bool HasXim { get; set; } + public bool HasXSync { get; set; } public IntPtr DefaultFontSet { get; set; } public unsafe X11Info(IntPtr display, IntPtr deferredDisplay, bool useXim) @@ -101,6 +102,15 @@ public unsafe X11Info(IntPtr display, IntPtr deferredDisplay, bool useXim) { //Ignore, XI is not supported } + + try + { + HasXSync = XSyncInitialize(display, out _, out _) != Status.Success; + } + catch + { + //Ignore, XSync is not supported + } } } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 066156a6524..6d0ca9422b1 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -45,6 +45,8 @@ unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client, private IntPtr _handle; private IntPtr _xic; private IntPtr _renderHandle; + private IntPtr _xSyncCounter; + private XSyncValue _xSyncValue; private bool _mapped; private bool _wasMappedAtLeastOnce = false; private double? _scalingOverride; @@ -190,6 +192,16 @@ public X11Window(AvaloniaX11Platform platform, IWindowImpl popupParent) NativeMenuExporter = DBusMenuExporter.TryCreateTopLevelNativeMenu(_handle); NativeControlHost = new X11NativeControlHost(_platform, this); 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); + + if (_x11.HasXSync) + { + _xSyncCounter = XSyncCreateCounter(_x11.Display, _xSyncValue); + XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_SYNC_REQUEST_COUNTER, + _x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref _xSyncCounter, 1); + } } class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo @@ -383,15 +395,7 @@ void OnEvent(ref XEvent ev) (ev.type == XEventName.VisibilityNotify && ev.VisibilityEvent.state < 2)) { - if (!_triggeredExpose) - { - _triggeredExpose = true; - Dispatcher.UIThread.Post(() => - { - _triggeredExpose = false; - DoPaint(); - }, DispatcherPriority.Render); - } + EnqueuePaint(); } else if (ev.type == XEventName.FocusIn) { @@ -503,6 +507,7 @@ void OnEvent(ref XEvent ev) if (_useRenderWindow) XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width, ev.ConfigureEvent.height); + EnqueuePaint(); } else if (ev.type == XEventName.DestroyNotify && ev.DestroyWindowEvent.window == _handle) @@ -518,7 +523,11 @@ void OnEvent(ref XEvent ev) if (Closing?.Invoke() != true) Dispose(); } - + else if (ev.ClientMessageEvent.ptr1 == _x11.Atoms._NET_WM_SYNC_REQUEST) + { + _xSyncValue.Lo = new UIntPtr(ev.ClientMessageEvent.ptr3.ToPointer()).ToUInt32(); + _xSyncValue.Hi = ev.ClientMessageEvent.ptr4.ToInt32(); + } } } else if (ev.type == XEventName.KeyPress || ev.type == XEventName.KeyRelease) @@ -730,9 +739,24 @@ void MouseEvent(RawPointerEventType type, ref XEvent ev, XModifierMask mods) ScheduleInput(mev, ref ev); } + void EnqueuePaint() + { + if (!_triggeredExpose) + { + _triggeredExpose = true; + Dispatcher.UIThread.Post(() => + { + _triggeredExpose = false; + DoPaint(); + }, DispatcherPriority.Render); + } + } + void DoPaint() { Paint?.Invoke(new Rect()); + if (_xSyncCounter != IntPtr.Zero) + XSyncSetCounter(_x11.Display, _xSyncCounter, _xSyncValue); } public void Invalidate(Rect rect) diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index e2b370821ff..464ec4f1c89 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -542,6 +542,18 @@ public static extern bool XQueryExtension(IntPtr display, [MarshalAs(UnmanagedTy public static extern int XRRQueryExtension (IntPtr dpy, out int event_base_return, out int error_base_return); + + [DllImport(libX11Ext)] + public static extern Status XSyncInitialize(IntPtr dpy, out int event_base_return, out int error_base_return); + + [DllImport(libX11Ext)] + public static extern IntPtr XSyncCreateCounter(IntPtr dpy, XSyncValue initialValue); + + [DllImport(libX11Ext)] + public static extern int XSyncDestroyCounter(IntPtr dpy, IntPtr counter); + + [DllImport(libX11Ext)] + public static extern int XSyncSetCounter(IntPtr dpy, IntPtr counter, XSyncValue value); [DllImport(libX11Randr)] public static extern int XRRQueryVersion(IntPtr dpy, @@ -627,6 +639,11 @@ public struct XGeometry public int bw; public int d; } + + public struct XSyncValue { + public int Hi; + public uint Lo; + } public static bool XGetGeometry(IntPtr display, IntPtr window, out XGeometry geo) {