diff --git a/src/Modern.WindowKit/Avalonia.Mac/WindowImpl.cs b/src/Modern.WindowKit/Avalonia.Mac/WindowImpl.cs index 6153ea7..49a6e1f 100644 --- a/src/Modern.WindowKit/Avalonia.Mac/WindowImpl.cs +++ b/src/Modern.WindowKit/Avalonia.Mac/WindowImpl.cs @@ -21,6 +21,7 @@ internal partial class WindowImpl : WindowBaseImpl, IWindowImpl //private DoubleClickHelper _doubleClickHelper; //private readonly ITopLevelNativeMenuExporter _nativeMenuExporter; //private readonly AvaloniaNativeTextInputMethod _inputMethod; + private bool _canResize = true; internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts ) : base(factory, opts) @@ -75,6 +76,7 @@ void IAvnWindowEvents.GotInputWhenDisabled() public void CanResize(bool value) { + _canResize = value; _native.SetCanResize(value.AsComBool()); } @@ -137,14 +139,10 @@ public override void Show(bool activate, bool isDialog) // { // if (_doubleClickHelper.IsDoubleClick(e.Timestamp, e.Position)) // { - // // TOGGLE WINDOW STATE. - // if (WindowState == WindowState.Maximized || WindowState == WindowState.FullScreen) + // if (_canResize) // { - // WindowState = WindowState.Normal; - // } - // else - // { - // WindowState = WindowState.Maximized; + // WindowState = WindowState is WindowState.Maximized or WindowState.FullScreen ? + // WindowState.Normal : WindowState.Maximized; // } // } // else diff --git a/src/Modern.WindowKit/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Modern.WindowKit/Avalonia.Win32/WindowImpl.AppWndProc.cs index 8de033d..d2b3177 100644 --- a/src/Modern.WindowKit/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Modern.WindowKit/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -85,6 +85,9 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, case WindowsMessage.WM_DESTROY: { + // The first and foremost thing to do - notify the TopLevel + Closed?.Invoke(); + //if (UiaCoreTypesApi.IsNetComInteropAvailable) //{ // UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, IntPtr.Zero, IntPtr.Zero, null); @@ -96,11 +99,22 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, // Imm32InputMethod.Current.ClearLanguageAndWindow(); //} + // Cleanup render targets + (_gl as IDisposable)?.Dispose(); + + //if (_dropTarget != null) + //{ + // OleContext.Current?.UnregisterDragDrop(Handle); + // _dropTarget.Dispose(); + // _dropTarget = null; + //} + + _framebuffer.Dispose(); + //Window doesn't exist anymore _hwnd = IntPtr.Zero; //Remove root reference to this class, so unmanaged delegate can be collected s_instances.Remove(this); - Closed?.Invoke(); _mouseDevice.Dispose(); _touchDevice.Dispose(); @@ -717,36 +731,25 @@ protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, } case WindowsMessage.WM_IME_COMPOSITION: { - //var previousComposition = Imm32InputMethod.Current.Composition; - //var flags = (GCS)ToInt32(lParam); - //var currentComposition = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_COMPSTR); + //if ((flags & GCS.GCS_COMPSTR) != 0) + //{ + // var currentComposition = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_COMPSTR); - //Imm32InputMethod.Current.CompositionChanged(currentComposition); + // Imm32InputMethod.Current.CompositionChanged(currentComposition); + //} - //switch (flags) + //if ((flags & GCS.GCS_RESULTSTR) != 0) //{ - // case GCS.GCS_RESULTSTR: - // { - // if(!string.IsNullOrEmpty(previousComposition) && ToInt32(wParam) >= 32) - // { - // Imm32InputMethod.Current.Composition = previousComposition; - - // _ignoreWmChar = true; - // } - // break; - // } - // case GCS.GCS_RESULTREADCLAUSE | GCS.GCS_RESULTSTR | GCS.GCS_RESULTCLAUSE: - // { - // // Chinese IME sends WM_CHAR after composition has finished. - // break; - // } - // case GCS.GCS_RESULTREADSTR | GCS.GCS_RESULTREADCLAUSE | GCS.GCS_RESULTSTR | GCS.GCS_RESULTCLAUSE: - // { - // // Japanese IME sends WM_CHAR after composition has finished. - // break; - // } + // var result = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_RESULTSTR); + + // if (!string.IsNullOrEmpty(result)) + // { + // Imm32InputMethod.Current.Composition = result; + + // _ignoreWmChar = true; + // } //} break; diff --git a/src/Modern.WindowKit/Avalonia.Win32/WindowImpl.cs b/src/Modern.WindowKit/Avalonia.Win32/WindowImpl.cs index 3ebfa29..3297a7a 100644 --- a/src/Modern.WindowKit/Avalonia.Win32/WindowImpl.cs +++ b/src/Modern.WindowKit/Avalonia.Win32/WindowImpl.cs @@ -150,7 +150,12 @@ public WindowImpl() CreateWindow(); _framebuffer = new FramebufferManager(_hwnd); - UpdateInputMethod(GetKeyboardLayout(0)); + + if (this is not PopupImpl) + { + UpdateInputMethod(GetKeyboardLayout(0)); + } + //if (glPlatform != null) //{ // if (_isUsingComposition) @@ -619,15 +624,6 @@ public void Activate() public void Dispose() { - //(_gl as IDisposable)?.Dispose(); - - //if (_dropTarget != null) - //{ - // OleContext.Current?.UnregisterDragDrop(Handle); - // _dropTarget.Dispose(); - // _dropTarget = null; - //} - if (_hwnd != IntPtr.Zero) { // Detect if we are being closed programmatically - this would mean that WM_CLOSE was not called @@ -640,8 +636,6 @@ public void Dispose() DestroyWindow(_hwnd); _hwnd = IntPtr.Zero; } - - //_framebuffer.Dispose(); } public void Invalidate(Rect rect) diff --git a/src/Modern.WindowKit/Avalonia.X11/X11Platform.cs b/src/Modern.WindowKit/Avalonia.X11/X11Platform.cs index b5125da..7c45f64 100644 --- a/src/Modern.WindowKit/Avalonia.X11/X11Platform.cs +++ b/src/Modern.WindowKit/Avalonia.X11/X11Platform.cs @@ -50,9 +50,8 @@ public void Initialize(X11PlatformOptions options) // useXim = true; //} - // XIM doesn't work at all otherwise - if (useXim) - setlocale(0, ""); + // We have problems with text input otherwise + setlocale(0, ""); XInitThreads(); Display = XOpenDisplay(IntPtr.Zero); diff --git a/src/Modern.WindowKit/Avalonia.X11/X11Window.Ime.cs b/src/Modern.WindowKit/Avalonia.X11/X11Window.Ime.cs index a31c9f9..b7715f0 100644 --- a/src/Modern.WindowKit/Avalonia.X11/X11Window.Ime.cs +++ b/src/Modern.WindowKit/Avalonia.X11/X11Window.Ime.cs @@ -89,7 +89,7 @@ private void InitializeIme() // } } - private void UpdateImePosition() { } // => _imeControl?.UpdateWindowInfo(Position, RenderScaling); + private void UpdateImePosition() { } // => _imeControl?.UpdateWindowInfo(_position ?? default, RenderScaling); private void HandleKeyEvent(ref XEvent ev) { diff --git a/src/Modern.WindowKit/Avalonia.X11/X11Window.cs b/src/Modern.WindowKit/Avalonia.X11/X11Window.cs index 0127139..be78659 100644 --- a/src/Modern.WindowKit/Avalonia.X11/X11Window.cs +++ b/src/Modern.WindowKit/Avalonia.X11/X11Window.cs @@ -24,6 +24,7 @@ using Modern.WindowKit.X11.NativeDialogs; using static Modern.WindowKit.X11.XLib; using Modern.WindowKit.Input.Platform; +using System.Runtime.InteropServices; // ReSharper disable IdentifierTypo // ReSharper disable StringLiteralTypo @@ -48,6 +49,7 @@ internal unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client //private readonly X11NativeControlHost _nativeControlHost; private PixelPoint? _position; private PixelSize _realSize; + private bool _cleaningUp; private IntPtr _handle; private IntPtr _xic; private IntPtr _renderHandle; @@ -124,7 +126,7 @@ public X11Window(AvaloniaX11Platform platform, IWindowImpl? popupParent) if (!_popup && Screen != null) { var monitor = Screen.AllScreens.OrderBy(x => x.Scaling) - .FirstOrDefault(m => m.Bounds.Contains(Position)); + .FirstOrDefault(m => m.Bounds.Contains(_position ?? default)); if (monitor != null) { @@ -183,7 +185,7 @@ public X11Window(AvaloniaX11Platform platform, IWindowImpl? popupParent) // surfaces.Insert(0, // new EglGlPlatformSurface(new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle))); //if (glx != null) - // surfaces.Insert(0, new GlxGlPlatformSurface(new SurfaceInfo(this, _x11.Display, _handle, _renderHandle))); + // surfaces.Insert(0, new GlxGlPlatformSurface(new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle))); surfaces.Add(Handle); @@ -326,23 +328,16 @@ public Size? FrameSize { get { - XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_FRAME_EXTENTS, IntPtr.Zero, - new IntPtr(4), false, (IntPtr)Atom.AnyPropertyType, out var _, - out var _, out var nitems, out var _, out var prop); + var extents = GetFrameExtents(); - if (nitems.ToInt64() != 4) + if(extents == null) { - // Window hasn't been mapped by the WM yet, so can't get the extents. return null; } - var data = (IntPtr*)prop.ToPointer(); - var extents = new Thickness(data[0].ToInt32(), data[2].ToInt32(), data[1].ToInt32(), data[3].ToInt32()); - XFree(prop); - return new Size( - (_realSize.Width + extents.Left + extents.Right) / RenderScaling, - (_realSize.Height + extents.Top + extents.Bottom) / RenderScaling); + (_realSize.Width + extents.Value.Left + extents.Value.Right) / RenderScaling, + (_realSize.Height + extents.Value.Top + extents.Value.Bottom) / RenderScaling); } } @@ -529,7 +524,7 @@ private void OnEvent(ref XEvent ev) else if (ev.type == XEventName.DestroyNotify && ev.DestroyWindowEvent.window == _handle) { - Cleanup(); + Cleanup(true); } else if (ev.type == XEventName.ClientMessage) { @@ -556,6 +551,25 @@ private void OnEvent(ref XEvent ev) } } + private Thickness? GetFrameExtents() + { + XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_FRAME_EXTENTS, IntPtr.Zero, + new IntPtr(4), false, (IntPtr)Atom.AnyPropertyType, out var _, + out var _, out var nitems, out var _, out var prop); + + if (nitems.ToInt64() != 4) + { + // Window hasn't been mapped by the WM yet, so can't get the extents. + return null; + } + + var data = (IntPtr*)prop.ToPointer(); + var extents = new Thickness(data[0].ToInt32(), data[2].ToInt32(), data[1].ToInt32(), data[3].ToInt32()); + XFree(prop); + + return extents; + } + private bool UpdateScaling(bool skipResize = false) { double newScaling; @@ -564,7 +578,7 @@ private bool UpdateScaling(bool skipResize = false) else { var monitor = _platform.X11Screens.Screens.OrderBy(x => x.Scaling) - .FirstOrDefault(m => m.Bounds.Contains(Position)); + .FirstOrDefault(m => m.Bounds.Contains(_position ?? default)); newScaling = monitor?.Scaling ?? RenderScaling; } @@ -609,13 +623,15 @@ public WindowState WindowState ChangeWMAtoms(true, _x11.Atoms._NET_WM_STATE_FULLSCREEN); ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT, _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); - } + } else { ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_HIDDEN); ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_FULLSCREEN); ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT, _x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ); + SendNetWMMessage(_x11.Atoms._NET_ACTIVE_WINDOW, (IntPtr)1, _x11.LastActivityTimestamp, + IntPtr.Zero); } } } @@ -722,6 +738,10 @@ private void DispatchInput(RawInputEventArgs args) { if (_inputRoot is null) return; + + if (_disabled && args is RawPointerEventArgs pargs && pargs.Type == RawPointerEventType.Move) + return; + Input?.Invoke(args); //if (!args.Handled && args is RawKeyEventArgsWithText text && !string.IsNullOrEmpty(text.Text)) // Input?.Invoke(new RawTextInputEventArgs(_keyboard, args.Timestamp, _inputRoot, text.Text)); @@ -804,7 +824,7 @@ public void SetInputRoot(IInputRoot inputRoot) public void Dispose() { - Cleanup(); + Cleanup(false); } public virtual object? TryGetFeature(Type featureType) @@ -837,8 +857,17 @@ public void Dispose() return null; } - private void Cleanup() + private void Cleanup(bool fromDestroyNotification) { + // Prevent reentrancy + if(_cleaningUp) + return; + _cleaningUp = true; + + // Before doing anything else notify the TopLevel that ITopLevelImpl is no longer valid + if (_handle != IntPtr.Zero) + Closed?.Invoke(); + if (_rawEventGrouper != null) { _rawEventGrouper.Dispose(); @@ -876,9 +905,9 @@ private void Cleanup() _platform.XI2?.OnWindowDestroyed(_handle); var handle = _handle; _handle = IntPtr.Zero; - Closed?.Invoke(); _mouse.Dispose(); _touch.Dispose(); + if (!fromDestroyNotification) XDestroyWindow(_x11.Display, handle); } @@ -916,11 +945,11 @@ public void Show(bool activate, bool isDialog) public void Hide() => XUnmapWindow(_x11.Display, _handle); - public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / RenderScaling, (point.Y - Position.Y) / RenderScaling); + public Point PointToClient(PixelPoint point) => new Point((point.X - (_position ?? default).X) / RenderScaling, (point.Y - (_position ?? default).Y) / RenderScaling); public PixelPoint PointToScreen(Point point) => new PixelPoint( - (int)(point.X * RenderScaling + Position.X), - (int)(point.Y * RenderScaling + Position.Y)); + (int)(point.X * RenderScaling + (_position ?? default).X), + (int)(point.Y * RenderScaling + (_position ?? default).Y)); public void SetSystemDecorations(SystemDecorations enabled) { @@ -984,10 +1013,25 @@ public void SetCursor(ICursorImpl? cursor) public PixelPoint Position { - get => _position ?? default; + get + { + if(_position == null) + { + return default; + } + + var extents = GetFrameExtents(); + + if(extents == null) + { + extents = default(Thickness); + } + + return new PixelPoint(_position.Value.X - (int)extents.Value.Left, _position.Value.Y - (int)extents.Value.Top); + } set { - if(!_usePositioningFlags) + if (!_usePositioningFlags) { _usePositioningFlags = true; UpdateSizeHints(null); @@ -995,9 +1039,10 @@ public PixelPoint Position var changes = new XWindowChanges { - x = (int)value.X, + x = value.X, y = (int)value.Y }; + XConfigureWindow(_x11.Display, _handle, ChangeWindowFlags.CWX | ChangeWindowFlags.CWY, ref changes); XFlush(_x11.Display); @@ -1006,7 +1051,6 @@ public PixelPoint Position _position = value; PositionChanged?.Invoke(value); } - } } @@ -1164,11 +1208,37 @@ public void SetTopmost(bool value) public void SetEnabled(bool enable) { _disabled = !enable; + + UpdateWMHints(); + } + + private void UpdateWMHints() + { + var wmHintsPtr = XGetWMHints(_x11.Display, _handle); + + XWMHints hints = default; + + if (wmHintsPtr != IntPtr.Zero) + { + hints = Marshal.PtrToStructure(wmHintsPtr); + } + + var flags = hints.flags.ToInt64(); + flags |= (long)XWMHintsFlags.InputHint; + hints.flags = (IntPtr)flags; + hints.input = !_disabled ? 1 : 0; + + XSetWMHints(_x11.Display, _handle, ref hints); + + if (wmHintsPtr != IntPtr.Zero) + { + XFree(wmHintsPtr); + } } public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint) { -} + } public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints) { @@ -1253,6 +1323,8 @@ public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } public bool NeedsManagedDecorations => false; + public bool IsEnabled => !_disabled; + public class SurfacePlatformHandle : IPlatformNativeSurfaceHandle { private readonly X11Window _owner; diff --git a/src/Modern.WindowKit/Avalonia.X11/XI2Manager.cs b/src/Modern.WindowKit/Avalonia.X11/XI2Manager.cs index 5b4220f..aec415d 100644 --- a/src/Modern.WindowKit/Avalonia.X11/XI2Manager.cs +++ b/src/Modern.WindowKit/Avalonia.X11/XI2Manager.cs @@ -231,7 +231,7 @@ private void OnDeviceEvent(IXI2Client client, ParsedDeviceEvent ev) return; } - if (_multitouch && ev.Emulated) + if (!client.IsEnabled || (_multitouch && ev.Emulated)) return; if (ev.Type == XiEventType.XI_Motion) @@ -370,6 +370,7 @@ public ParsedDeviceEvent(XIDeviceEvent* ev) internal interface IXI2Client { + bool IsEnabled { get; } IInputRoot InputRoot { get; } void ScheduleXI2Input(RawInputEventArgs args); IMouseDevice MouseDevice { get; } diff --git a/src/Modern.WindowKit/Avalonia.X11/XLib.cs b/src/Modern.WindowKit/Avalonia.X11/XLib.cs index 4fdfbf5..d18b6fb 100644 --- a/src/Modern.WindowKit/Avalonia.X11/XLib.cs +++ b/src/Modern.WindowKit/Avalonia.X11/XLib.cs @@ -375,6 +375,9 @@ public static extern void XGetWMNormalHints(IntPtr display, IntPtr window, ref X [DllImport(libX11)] public static extern void XSetWMHints(IntPtr display, IntPtr window, ref XWMHints wmhints); + [DllImport(libX11)] + public static extern IntPtr XGetWMHints(IntPtr display, IntPtr window); + [DllImport(libX11)] public static extern int XGetIconSizes(IntPtr display, IntPtr window, out IntPtr size_list, out int count); diff --git a/src/Modern.WindowKit/Dispatcher.Invoke.cs b/src/Modern.WindowKit/Dispatcher.Invoke.cs index 0a3f40c..9c0ca1e 100644 --- a/src/Modern.WindowKit/Dispatcher.Invoke.cs +++ b/src/Modern.WindowKit/Dispatcher.Invoke.cs @@ -557,10 +557,10 @@ public void Post(Action action, DispatcherPriority priority = default) /// /// An task that completes after the task returned from callback finishes /// - public Task InvokeTaskAsync(Func callback, DispatcherPriority priority = default) + public Task InvokeAsync(Func callback, DispatcherPriority priority = default) { _ = callback ?? throw new ArgumentNullException(nameof(callback)); - return InvokeAsync(callback, priority).GetTask().Unwrap(); + return InvokeAsync(callback, priority).GetTask().Unwrap(); } /// @@ -578,10 +578,10 @@ public Task InvokeTaskAsync(Func callback, DispatcherPriority priority = d /// /// An task that completes after the task returned from callback finishes /// - public Task InvokeTaskAsync(Func> action, DispatcherPriority priority = default) + public Task InvokeAsync(Func> action, DispatcherPriority priority = default) { _ = action ?? throw new ArgumentNullException(nameof(action)); - return InvokeAsync(action, priority).GetTask().Unwrap(); + return InvokeAsync>(action, priority).GetTask().Unwrap(); } /// diff --git a/src/Modern.WindowKit/Rect.cs b/src/Modern.WindowKit/Rect.cs index 5b238a6..6f5ce49 100644 --- a/src/Modern.WindowKit/Rect.cs +++ b/src/Modern.WindowKit/Rect.cs @@ -526,6 +526,15 @@ public Rect Union(Rect rect) } } + internal static Rect? Union(Rect? left, Rect? right) + { + if (left == null) + return right; + if (right == null) + return left; + return left.Value.Union(right.Value); + } + /// /// Returns a new with the specified X position. ///