Update with recovered code from VM binaries (Jan 1)
Recovered from decompiled OpenMaui.Controls.Linux.dll: - SkiaShell.cs: FlyoutHeader, FlyoutFooter, scroll support (918 -> 1325 lines) - X11Window.cs: Cursor support (XCreateFontCursor, XDefineCursor) - All handlers with dark mode support - All services with latest implementations - LinuxApplication with theme change handling
This commit is contained in:
8
Window/CursorType.cs
Normal file
8
Window/CursorType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Microsoft.Maui.Platform.Linux.Window;
|
||||
|
||||
public enum CursorType
|
||||
{
|
||||
Arrow,
|
||||
Hand,
|
||||
Text
|
||||
}
|
||||
345
Window/GtkHostWindow.cs
Normal file
345
Window/GtkHostWindow.cs
Normal file
@@ -0,0 +1,345 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Maui.Platform.Linux.Native;
|
||||
using Microsoft.Maui.Platform.Linux.Rendering;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Window;
|
||||
|
||||
public sealed class GtkHostWindow : IDisposable
|
||||
{
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate bool DeleteEventDelegate(IntPtr widget, IntPtr eventData, IntPtr userData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate bool ConfigureEventDelegate(IntPtr widget, IntPtr eventData, IntPtr userData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate bool ButtonEventDelegate(IntPtr widget, IntPtr eventData, IntPtr userData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate bool MotionEventDelegate(IntPtr widget, IntPtr eventData, IntPtr userData);
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct GdkEventButton
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public int type;
|
||||
|
||||
[FieldOffset(8)]
|
||||
public IntPtr window;
|
||||
|
||||
[FieldOffset(16)]
|
||||
public sbyte send_event;
|
||||
|
||||
[FieldOffset(20)]
|
||||
public uint time;
|
||||
|
||||
[FieldOffset(24)]
|
||||
public double x;
|
||||
|
||||
[FieldOffset(32)]
|
||||
public double y;
|
||||
|
||||
[FieldOffset(40)]
|
||||
public IntPtr axes;
|
||||
|
||||
[FieldOffset(48)]
|
||||
public uint state;
|
||||
|
||||
[FieldOffset(52)]
|
||||
public uint button;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct GdkEventMotion
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public int type;
|
||||
|
||||
[FieldOffset(8)]
|
||||
public IntPtr window;
|
||||
|
||||
[FieldOffset(16)]
|
||||
public sbyte send_event;
|
||||
|
||||
[FieldOffset(20)]
|
||||
public uint time;
|
||||
|
||||
[FieldOffset(24)]
|
||||
public double x;
|
||||
|
||||
[FieldOffset(32)]
|
||||
public double y;
|
||||
}
|
||||
|
||||
private IntPtr _window;
|
||||
|
||||
private IntPtr _overlay;
|
||||
|
||||
private IntPtr _webViewLayer;
|
||||
|
||||
private GtkSkiaSurfaceWidget? _skiaSurface;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
private int _width;
|
||||
|
||||
private int _height;
|
||||
|
||||
private readonly DeleteEventDelegate _deleteEventHandler;
|
||||
|
||||
private readonly ConfigureEventDelegate _configureEventHandler;
|
||||
|
||||
private readonly ButtonEventDelegate _buttonPressHandler;
|
||||
|
||||
private readonly ButtonEventDelegate _buttonReleaseHandler;
|
||||
|
||||
private readonly MotionEventDelegate _motionHandler;
|
||||
|
||||
public IntPtr Window => _window;
|
||||
|
||||
public IntPtr Overlay => _overlay;
|
||||
|
||||
public IntPtr WebViewLayer => _webViewLayer;
|
||||
|
||||
public GtkSkiaSurfaceWidget? SkiaSurface => _skiaSurface;
|
||||
|
||||
public int Width => _width;
|
||||
|
||||
public int Height => _height;
|
||||
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
public event EventHandler<(int Width, int Height)>? Resized;
|
||||
|
||||
public event EventHandler? CloseRequested;
|
||||
|
||||
public event EventHandler<(double X, double Y, int Button)>? PointerPressed;
|
||||
|
||||
public event EventHandler<(double X, double Y, int Button)>? PointerReleased;
|
||||
|
||||
public event EventHandler<(double X, double Y)>? PointerMoved;
|
||||
|
||||
public GtkHostWindow(string title, int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
Environment.SetEnvironmentVariable("GDK_BACKEND", "x11");
|
||||
Environment.SetEnvironmentVariable("WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS", "1");
|
||||
Environment.SetEnvironmentVariable("LIBGL_ALWAYS_SOFTWARE", "1");
|
||||
int argc = 0;
|
||||
IntPtr argv = IntPtr.Zero;
|
||||
if (!GtkNative.gtk_init_check(ref argc, ref argv))
|
||||
{
|
||||
throw new InvalidOperationException("Failed to initialize GTK. Is a display available?");
|
||||
}
|
||||
_window = GtkNative.gtk_window_new(0);
|
||||
if (_window == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create GTK window");
|
||||
}
|
||||
GtkNative.gtk_window_set_title(_window, title);
|
||||
GtkNative.gtk_window_set_default_size(_window, width, height);
|
||||
_overlay = GtkNative.gtk_overlay_new();
|
||||
GtkNative.gtk_container_add(_window, _overlay);
|
||||
_skiaSurface = new GtkSkiaSurfaceWidget(width, height);
|
||||
GtkNative.gtk_container_add(_overlay, _skiaSurface.Widget);
|
||||
_webViewLayer = GtkNative.gtk_fixed_new();
|
||||
GtkNative.gtk_overlay_add_overlay(_overlay, _webViewLayer);
|
||||
GtkNative.gtk_widget_set_can_focus(_webViewLayer, canFocus: false);
|
||||
GtkNative.gtk_overlay_set_overlay_pass_through(_overlay, _webViewLayer, passThrough: true);
|
||||
_deleteEventHandler = OnDeleteEvent;
|
||||
_configureEventHandler = OnConfigureEvent;
|
||||
_buttonPressHandler = OnButtonPress;
|
||||
_buttonReleaseHandler = OnButtonRelease;
|
||||
_motionHandler = OnMotion;
|
||||
ConnectSignal(_window, "delete-event", Marshal.GetFunctionPointerForDelegate(_deleteEventHandler));
|
||||
ConnectSignal(_window, "configure-event", Marshal.GetFunctionPointerForDelegate(_configureEventHandler));
|
||||
GtkNative.gtk_widget_add_events(_window, 772);
|
||||
ConnectSignal(_window, "button-press-event", Marshal.GetFunctionPointerForDelegate(_buttonPressHandler));
|
||||
ConnectSignal(_window, "button-release-event", Marshal.GetFunctionPointerForDelegate(_buttonReleaseHandler));
|
||||
ConnectSignal(_window, "motion-notify-event", Marshal.GetFunctionPointerForDelegate(_motionHandler));
|
||||
Console.WriteLine($"[GtkHostWindow] Created GTK window on X11: {width}x{height}");
|
||||
}
|
||||
|
||||
private void ConnectSignal(IntPtr widget, string signal, IntPtr handler)
|
||||
{
|
||||
GtkNative.g_signal_connect_data(widget, signal, handler, IntPtr.Zero, IntPtr.Zero, 0);
|
||||
}
|
||||
|
||||
private bool OnDeleteEvent(IntPtr widget, IntPtr eventData, IntPtr userData)
|
||||
{
|
||||
this.CloseRequested?.Invoke(this, EventArgs.Empty);
|
||||
_isRunning = false;
|
||||
GtkNative.gtk_main_quit();
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool OnConfigureEvent(IntPtr widget, IntPtr eventData, IntPtr userData)
|
||||
{
|
||||
GtkNative.gtk_window_get_size(_window, out var width, out var height);
|
||||
if (width != _width || height != _height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
_skiaSurface?.Resize(width, height);
|
||||
this.Resized?.Invoke(this, (_width, _height));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool OnButtonPress(IntPtr widget, IntPtr eventData, IntPtr userData)
|
||||
{
|
||||
(double x, double y, int button) tuple = ParseButtonEvent(eventData);
|
||||
double item = tuple.x;
|
||||
double item2 = tuple.y;
|
||||
int item3 = tuple.button;
|
||||
string value = item3 switch
|
||||
{
|
||||
3 => "Right",
|
||||
2 => "Middle",
|
||||
1 => "Left",
|
||||
_ => $"Other({item3})",
|
||||
};
|
||||
Console.WriteLine($"[GtkHostWindow] ButtonPress at ({item:F1}, {item2:F1}), button={item3} ({value})");
|
||||
this.PointerPressed?.Invoke(this, (item, item2, item3));
|
||||
_skiaSurface?.RaisePointerPressed(item, item2, item3);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool OnButtonRelease(IntPtr widget, IntPtr eventData, IntPtr userData)
|
||||
{
|
||||
var (num, num2, num3) = ParseButtonEvent(eventData);
|
||||
this.PointerReleased?.Invoke(this, (num, num2, num3));
|
||||
_skiaSurface?.RaisePointerReleased(num, num2, num3);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool OnMotion(IntPtr widget, IntPtr eventData, IntPtr userData)
|
||||
{
|
||||
var (num, num2) = ParseMotionEvent(eventData);
|
||||
this.PointerMoved?.Invoke(this, (num, num2));
|
||||
_skiaSurface?.RaisePointerMoved(num, num2);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static (double x, double y, int button) ParseButtonEvent(IntPtr eventData)
|
||||
{
|
||||
GdkEventButton gdkEventButton = Marshal.PtrToStructure<GdkEventButton>(eventData);
|
||||
return (x: gdkEventButton.x, y: gdkEventButton.y, button: (int)gdkEventButton.button);
|
||||
}
|
||||
|
||||
private static (double x, double y) ParseMotionEvent(IntPtr eventData)
|
||||
{
|
||||
GdkEventMotion gdkEventMotion = Marshal.PtrToStructure<GdkEventMotion>(eventData);
|
||||
return (x: gdkEventMotion.x, y: gdkEventMotion.y);
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
GtkNative.gtk_widget_show_all(_window);
|
||||
_isRunning = true;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
GtkNative.gtk_widget_hide(_window);
|
||||
}
|
||||
|
||||
public void SetTitle(string title)
|
||||
{
|
||||
GtkNative.gtk_window_set_title(_window, title);
|
||||
}
|
||||
|
||||
public void SetIcon(string iconPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(iconPath) || !File.Exists(iconPath))
|
||||
{
|
||||
Console.WriteLine("[GtkHostWindow] Icon file not found: " + iconPath);
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
IntPtr intPtr = GtkNative.gdk_pixbuf_new_from_file(iconPath, IntPtr.Zero);
|
||||
if (intPtr != IntPtr.Zero)
|
||||
{
|
||||
GtkNative.gtk_window_set_icon(_window, intPtr);
|
||||
GtkNative.g_object_unref(intPtr);
|
||||
Console.WriteLine("[GtkHostWindow] Set window icon: " + iconPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[GtkHostWindow] Failed to set icon: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Resize(int width, int height)
|
||||
{
|
||||
GtkNative.gtk_window_resize(_window, width, height);
|
||||
}
|
||||
|
||||
public void AddWebView(IntPtr webViewWidget, int x, int y, int width, int height)
|
||||
{
|
||||
GtkNative.gtk_widget_set_size_request(webViewWidget, width, height);
|
||||
GtkNative.gtk_fixed_put(_webViewLayer, webViewWidget, x, y);
|
||||
GtkNative.gtk_widget_show(webViewWidget);
|
||||
Console.WriteLine($"[GtkHostWindow] Added WebView at ({x}, {y}) size {width}x{height}");
|
||||
}
|
||||
|
||||
public void MoveResizeWebView(IntPtr webViewWidget, int x, int y, int width, int height)
|
||||
{
|
||||
GtkNative.gtk_widget_set_size_request(webViewWidget, width, height);
|
||||
GtkNative.gtk_fixed_move(_webViewLayer, webViewWidget, x, y);
|
||||
}
|
||||
|
||||
public void RemoveWebView(IntPtr webViewWidget)
|
||||
{
|
||||
GtkNative.gtk_container_remove(_webViewLayer, webViewWidget);
|
||||
}
|
||||
|
||||
public void RequestRedraw()
|
||||
{
|
||||
if (_skiaSurface != null)
|
||||
{
|
||||
GtkNative.gtk_widget_queue_draw(_skiaSurface.Widget);
|
||||
}
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
Show();
|
||||
GtkNative.gtk_main();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_isRunning = false;
|
||||
GtkNative.gtk_main_quit();
|
||||
}
|
||||
|
||||
public void ProcessEvents()
|
||||
{
|
||||
while (GtkNative.gtk_events_pending())
|
||||
{
|
||||
GtkNative.gtk_main_iteration_do(blocking: false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
_skiaSurface?.Dispose();
|
||||
if (_window != IntPtr.Zero)
|
||||
{
|
||||
GtkNative.gtk_widget_destroy(_window);
|
||||
_window = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,464 +1,494 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Platform.Linux.Interop;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Maui.Platform.Linux.Input;
|
||||
using Microsoft.Maui.Platform.Linux.Interop;
|
||||
using SkiaSharp;
|
||||
using Svg.Skia;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Window;
|
||||
|
||||
/// <summary>
|
||||
/// X11 window implementation for Linux.
|
||||
/// </summary>
|
||||
public class X11Window : IDisposable
|
||||
{
|
||||
private IntPtr _display;
|
||||
private IntPtr _window;
|
||||
private IntPtr _wmDeleteMessage;
|
||||
private int _screen;
|
||||
private bool _disposed;
|
||||
private bool _isRunning;
|
||||
|
||||
private int _width;
|
||||
private int _height;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the native display handle.
|
||||
/// </summary>
|
||||
public IntPtr Display => _display;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the native window handle.
|
||||
/// </summary>
|
||||
public IntPtr Handle => _window;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the window width.
|
||||
/// </summary>
|
||||
public int Width => _width;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the window height.
|
||||
/// </summary>
|
||||
public int Height => _height;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the window is running.
|
||||
/// </summary>
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a key is pressed.
|
||||
/// </summary>
|
||||
public event EventHandler<KeyEventArgs>? KeyDown;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a key is released.
|
||||
/// </summary>
|
||||
public event EventHandler<KeyEventArgs>? KeyUp;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when text is input.
|
||||
/// </summary>
|
||||
public event EventHandler<TextInputEventArgs>? TextInput;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the pointer moves.
|
||||
/// </summary>
|
||||
public event EventHandler<PointerEventArgs>? PointerMoved;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a pointer button is pressed.
|
||||
/// </summary>
|
||||
public event EventHandler<PointerEventArgs>? PointerPressed;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a pointer button is released.
|
||||
/// </summary>
|
||||
public event EventHandler<PointerEventArgs>? PointerReleased;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the mouse wheel is scrolled.
|
||||
/// </summary>
|
||||
public event EventHandler<ScrollEventArgs>? Scroll;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the window needs to be redrawn.
|
||||
/// </summary>
|
||||
public event EventHandler? Exposed;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the window is resized.
|
||||
/// </summary>
|
||||
public event EventHandler<(int Width, int Height)>? Resized;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the window close is requested.
|
||||
/// </summary>
|
||||
public event EventHandler? CloseRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the window gains focus.
|
||||
/// </summary>
|
||||
public event EventHandler? FocusGained;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the window loses focus.
|
||||
/// </summary>
|
||||
public event EventHandler? FocusLost;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new X11 window.
|
||||
/// </summary>
|
||||
public X11Window(string title, int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
|
||||
// Open display
|
||||
_display = X11.XOpenDisplay(IntPtr.Zero);
|
||||
if (_display == IntPtr.Zero)
|
||||
throw new InvalidOperationException("Failed to open X11 display. Is X11 running?");
|
||||
|
||||
_screen = X11.XDefaultScreen(_display);
|
||||
var rootWindow = X11.XRootWindow(_display, _screen);
|
||||
|
||||
// Create window
|
||||
_window = X11.XCreateSimpleWindow(
|
||||
_display,
|
||||
rootWindow,
|
||||
0, 0,
|
||||
(uint)width, (uint)height,
|
||||
0,
|
||||
0,
|
||||
0xFFFFFF // White background
|
||||
);
|
||||
|
||||
if (_window == IntPtr.Zero)
|
||||
throw new InvalidOperationException("Failed to create X11 window");
|
||||
|
||||
// Set window title
|
||||
X11.XStoreName(_display, _window, title);
|
||||
|
||||
// Select input events
|
||||
X11.XSelectInput(_display, _window,
|
||||
XEventMask.KeyPressMask |
|
||||
XEventMask.KeyReleaseMask |
|
||||
XEventMask.ButtonPressMask |
|
||||
XEventMask.ButtonReleaseMask |
|
||||
XEventMask.PointerMotionMask |
|
||||
XEventMask.EnterWindowMask |
|
||||
XEventMask.LeaveWindowMask |
|
||||
XEventMask.ExposureMask |
|
||||
XEventMask.StructureNotifyMask |
|
||||
XEventMask.FocusChangeMask);
|
||||
|
||||
// Set up WM_DELETE_WINDOW protocol for proper close handling
|
||||
_wmDeleteMessage = X11.XInternAtom(_display, "WM_DELETE_WINDOW", false);
|
||||
|
||||
// Would need XSetWMProtocols here, simplified for now
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the window.
|
||||
/// </summary>
|
||||
public void Show()
|
||||
{
|
||||
X11.XMapWindow(_display, _window);
|
||||
X11.XFlush(_display);
|
||||
_isRunning = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides the window.
|
||||
/// </summary>
|
||||
public void Hide()
|
||||
{
|
||||
X11.XUnmapWindow(_display, _window);
|
||||
X11.XFlush(_display);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the window title.
|
||||
/// </summary>
|
||||
public void SetTitle(string title)
|
||||
{
|
||||
X11.XStoreName(_display, _window, title);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes the window.
|
||||
/// </summary>
|
||||
public void Resize(int width, int height)
|
||||
{
|
||||
X11.XResizeWindow(_display, _window, (uint)width, (uint)height);
|
||||
X11.XFlush(_display);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes pending X11 events.
|
||||
/// </summary>
|
||||
public void ProcessEvents()
|
||||
{
|
||||
while (X11.XPending(_display) > 0)
|
||||
{
|
||||
X11.XNextEvent(_display, out var xEvent);
|
||||
HandleEvent(ref xEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the event loop.
|
||||
/// </summary>
|
||||
public void Run()
|
||||
{
|
||||
_isRunning = true;
|
||||
while (_isRunning)
|
||||
{
|
||||
X11.XNextEvent(_display, out var xEvent);
|
||||
HandleEvent(ref xEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the event loop.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
private void HandleEvent(ref XEvent xEvent)
|
||||
{
|
||||
switch (xEvent.Type)
|
||||
{
|
||||
case XEventType.KeyPress:
|
||||
HandleKeyPress(ref xEvent.KeyEvent);
|
||||
break;
|
||||
|
||||
case XEventType.KeyRelease:
|
||||
HandleKeyRelease(ref xEvent.KeyEvent);
|
||||
break;
|
||||
|
||||
case XEventType.ButtonPress:
|
||||
HandleButtonPress(ref xEvent.ButtonEvent);
|
||||
break;
|
||||
|
||||
case XEventType.ButtonRelease:
|
||||
HandleButtonRelease(ref xEvent.ButtonEvent);
|
||||
break;
|
||||
|
||||
case XEventType.MotionNotify:
|
||||
HandleMotion(ref xEvent.MotionEvent);
|
||||
break;
|
||||
|
||||
case XEventType.Expose:
|
||||
if (xEvent.ExposeEvent.Count == 0)
|
||||
{
|
||||
Exposed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
break;
|
||||
|
||||
case XEventType.ConfigureNotify:
|
||||
HandleConfigure(ref xEvent.ConfigureEvent);
|
||||
break;
|
||||
|
||||
case XEventType.FocusIn:
|
||||
FocusGained?.Invoke(this, EventArgs.Empty);
|
||||
break;
|
||||
|
||||
case XEventType.FocusOut:
|
||||
FocusLost?.Invoke(this, EventArgs.Empty);
|
||||
break;
|
||||
|
||||
case XEventType.ClientMessage:
|
||||
if (xEvent.ClientMessageEvent.Data.L0 == (long)_wmDeleteMessage)
|
||||
{
|
||||
CloseRequested?.Invoke(this, EventArgs.Empty);
|
||||
_isRunning = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleKeyPress(ref XKeyEvent keyEvent)
|
||||
{
|
||||
var keysym = KeyMapping.GetKeysym(_display, keyEvent.Keycode, (keyEvent.State & 0x01) != 0);
|
||||
var key = KeyMapping.FromKeysym(keysym);
|
||||
var modifiers = KeyMapping.GetModifiers(keyEvent.State);
|
||||
|
||||
KeyDown?.Invoke(this, new KeyEventArgs(key, modifiers));
|
||||
|
||||
// Generate text input for printable characters, but NOT when Control or Alt is held
|
||||
// (those are keyboard shortcuts, not text input)
|
||||
bool isControlHeld = (keyEvent.State & 0x04) != 0; // ControlMask
|
||||
bool isAltHeld = (keyEvent.State & 0x08) != 0; // Mod1Mask (Alt)
|
||||
|
||||
if (keysym >= 32 && keysym <= 126 && !isControlHeld && !isAltHeld)
|
||||
{
|
||||
TextInput?.Invoke(this, new TextInputEventArgs(((char)keysym).ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleKeyRelease(ref XKeyEvent keyEvent)
|
||||
{
|
||||
var keysym = KeyMapping.GetKeysym(_display, keyEvent.Keycode, (keyEvent.State & 0x01) != 0);
|
||||
var key = KeyMapping.FromKeysym(keysym);
|
||||
var modifiers = KeyMapping.GetModifiers(keyEvent.State);
|
||||
|
||||
KeyUp?.Invoke(this, new KeyEventArgs(key, modifiers));
|
||||
}
|
||||
|
||||
private void HandleButtonPress(ref XButtonEvent buttonEvent)
|
||||
{
|
||||
// Buttons 4 and 5 are scroll wheel
|
||||
if (buttonEvent.Button == 4)
|
||||
{
|
||||
Scroll?.Invoke(this, new ScrollEventArgs(buttonEvent.X, buttonEvent.Y, 0, -1));
|
||||
return;
|
||||
}
|
||||
if (buttonEvent.Button == 5)
|
||||
{
|
||||
Scroll?.Invoke(this, new ScrollEventArgs(buttonEvent.X, buttonEvent.Y, 0, 1));
|
||||
return;
|
||||
}
|
||||
|
||||
var button = MapButton(buttonEvent.Button);
|
||||
PointerPressed?.Invoke(this, new PointerEventArgs(buttonEvent.X, buttonEvent.Y, button));
|
||||
}
|
||||
|
||||
private void HandleButtonRelease(ref XButtonEvent buttonEvent)
|
||||
{
|
||||
// Ignore scroll wheel releases
|
||||
if (buttonEvent.Button == 4 || buttonEvent.Button == 5)
|
||||
return;
|
||||
|
||||
var button = MapButton(buttonEvent.Button);
|
||||
PointerReleased?.Invoke(this, new PointerEventArgs(buttonEvent.X, buttonEvent.Y, button));
|
||||
}
|
||||
|
||||
private void HandleMotion(ref XMotionEvent motionEvent)
|
||||
{
|
||||
PointerMoved?.Invoke(this, new PointerEventArgs(motionEvent.X, motionEvent.Y));
|
||||
}
|
||||
|
||||
private void HandleConfigure(ref XConfigureEvent configureEvent)
|
||||
{
|
||||
if (configureEvent.Width != _width || configureEvent.Height != _height)
|
||||
{
|
||||
_width = configureEvent.Width;
|
||||
_height = configureEvent.Height;
|
||||
Resized?.Invoke(this, (_width, _height));
|
||||
}
|
||||
}
|
||||
|
||||
private static PointerButton MapButton(uint button) => button switch
|
||||
{
|
||||
1 => PointerButton.Left,
|
||||
2 => PointerButton.Middle,
|
||||
3 => PointerButton.Right,
|
||||
8 => PointerButton.XButton1,
|
||||
9 => PointerButton.XButton2,
|
||||
_ => PointerButton.None
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the X11 file descriptor for use with select/poll.
|
||||
/// </summary>
|
||||
public int GetFileDescriptor()
|
||||
{
|
||||
return X11.XConnectionNumber(_display);
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (_window != IntPtr.Zero)
|
||||
{
|
||||
X11.XDestroyWindow(_display, _window);
|
||||
_window = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (_display != IntPtr.Zero)
|
||||
{
|
||||
X11.XCloseDisplay(_display);
|
||||
_display = IntPtr.Zero;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws pixel data to the window.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// Draws pixel data to the window.
|
||||
/// </summary>
|
||||
public void DrawPixels(IntPtr pixels, int width, int height, int stride)
|
||||
{
|
||||
if (_display == IntPtr.Zero || _window == IntPtr.Zero) return;
|
||||
|
||||
var gc = X11.XDefaultGC(_display, _screen);
|
||||
var visual = X11.XDefaultVisual(_display, _screen);
|
||||
var depth = X11.XDefaultDepth(_display, _screen);
|
||||
|
||||
// Allocate unmanaged memory and copy the pixel data
|
||||
var dataSize = height * stride;
|
||||
var unmanagedData = System.Runtime.InteropServices.Marshal.AllocHGlobal(dataSize);
|
||||
|
||||
try
|
||||
{
|
||||
// Copy pixel data to unmanaged memory
|
||||
unsafe
|
||||
{
|
||||
Buffer.MemoryCopy((void*)pixels, (void*)unmanagedData, dataSize, dataSize);
|
||||
}
|
||||
|
||||
// Create XImage from the unmanaged pixel data
|
||||
var image = X11.XCreateImage(
|
||||
_display,
|
||||
visual,
|
||||
(uint)depth,
|
||||
X11.ZPixmap,
|
||||
0,
|
||||
unmanagedData,
|
||||
(uint)width,
|
||||
(uint)height,
|
||||
32,
|
||||
stride);
|
||||
|
||||
if (image != IntPtr.Zero)
|
||||
{
|
||||
X11.XPutImage(_display, _window, gc, image, 0, 0, 0, 0, (uint)width, (uint)height);
|
||||
X11.XDestroyImage(image); // This will free unmanagedData
|
||||
}
|
||||
else
|
||||
{
|
||||
// If XCreateImage failed, free the memory ourselves
|
||||
System.Runtime.InteropServices.Marshal.FreeHGlobal(unmanagedData);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
System.Runtime.InteropServices.Marshal.FreeHGlobal(unmanagedData);
|
||||
throw;
|
||||
}
|
||||
|
||||
X11.XFlush(_display);
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~X11Window()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
private IntPtr _display;
|
||||
|
||||
private IntPtr _window;
|
||||
|
||||
private IntPtr _wmDeleteMessage;
|
||||
|
||||
private int _screen;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
private int _width;
|
||||
|
||||
private int _height;
|
||||
|
||||
private IntPtr _arrowCursor;
|
||||
|
||||
private IntPtr _handCursor;
|
||||
|
||||
private IntPtr _textCursor;
|
||||
|
||||
private IntPtr _currentCursor;
|
||||
|
||||
private CursorType _currentCursorType;
|
||||
|
||||
private static int _eventCounter;
|
||||
|
||||
public IntPtr Display => _display;
|
||||
|
||||
public IntPtr Handle => _window;
|
||||
|
||||
public int Width => _width;
|
||||
|
||||
public int Height => _height;
|
||||
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
public event EventHandler<KeyEventArgs>? KeyDown;
|
||||
|
||||
public event EventHandler<KeyEventArgs>? KeyUp;
|
||||
|
||||
public event EventHandler<TextInputEventArgs>? TextInput;
|
||||
|
||||
public event EventHandler<PointerEventArgs>? PointerMoved;
|
||||
|
||||
public event EventHandler<PointerEventArgs>? PointerPressed;
|
||||
|
||||
public event EventHandler<PointerEventArgs>? PointerReleased;
|
||||
|
||||
public event EventHandler<ScrollEventArgs>? Scroll;
|
||||
|
||||
public event EventHandler? Exposed;
|
||||
|
||||
public event EventHandler<(int Width, int Height)>? Resized;
|
||||
|
||||
public event EventHandler? CloseRequested;
|
||||
|
||||
public event EventHandler? FocusGained;
|
||||
|
||||
public event EventHandler? FocusLost;
|
||||
|
||||
public X11Window(string title, int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
_display = X11.XOpenDisplay(IntPtr.Zero);
|
||||
if (_display == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to open X11 display. Is X11 running?");
|
||||
}
|
||||
_screen = X11.XDefaultScreen(_display);
|
||||
IntPtr parent = X11.XRootWindow(_display, _screen);
|
||||
_window = X11.XCreateSimpleWindow(_display, parent, 0, 0, (uint)width, (uint)height, 0u, 0uL, 16777215uL);
|
||||
if (_window == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create X11 window");
|
||||
}
|
||||
X11.XStoreName(_display, _window, title);
|
||||
long num = 2261119L;
|
||||
Console.WriteLine($"[X11Window] Setting event mask: {num} (0x{num:X})");
|
||||
X11.XSelectInput(_display, _window, num);
|
||||
_wmDeleteMessage = X11.XInternAtom(_display, "WM_DELETE_WINDOW", onlyIfExists: false);
|
||||
_arrowCursor = X11.XCreateFontCursor(_display, 68u);
|
||||
_handCursor = X11.XCreateFontCursor(_display, 60u);
|
||||
_textCursor = X11.XCreateFontCursor(_display, 152u);
|
||||
_currentCursor = _arrowCursor;
|
||||
}
|
||||
|
||||
public void SetCursor(CursorType cursorType)
|
||||
{
|
||||
if (_currentCursorType != cursorType)
|
||||
{
|
||||
_currentCursorType = cursorType;
|
||||
IntPtr intPtr = cursorType switch
|
||||
{
|
||||
CursorType.Hand => _handCursor,
|
||||
CursorType.Text => _textCursor,
|
||||
_ => _arrowCursor,
|
||||
};
|
||||
if (intPtr != _currentCursor)
|
||||
{
|
||||
_currentCursor = intPtr;
|
||||
X11.XDefineCursor(_display, _window, _currentCursor);
|
||||
X11.XFlush(_display);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
X11.XMapWindow(_display, _window);
|
||||
X11.XFlush(_display);
|
||||
_isRunning = true;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
X11.XUnmapWindow(_display, _window);
|
||||
X11.XFlush(_display);
|
||||
}
|
||||
|
||||
public void SetTitle(string title)
|
||||
{
|
||||
X11.XStoreName(_display, _window, title);
|
||||
}
|
||||
|
||||
public void Resize(int width, int height)
|
||||
{
|
||||
X11.XResizeWindow(_display, _window, (uint)width, (uint)height);
|
||||
X11.XFlush(_display);
|
||||
}
|
||||
|
||||
public unsafe void SetIcon(string iconPath)
|
||||
{
|
||||
//IL_004e: Unknown result type (might be due to invalid IL or missing references)
|
||||
//IL_0055: Expected O, but got Unknown
|
||||
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
|
||||
//IL_0076: Unknown result type (might be due to invalid IL or missing references)
|
||||
//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
|
||||
//IL_00b2: Expected O, but got Unknown
|
||||
//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
|
||||
//IL_00ba: Expected O, but got Unknown
|
||||
//IL_0183: Unknown result type (might be due to invalid IL or missing references)
|
||||
//IL_018a: Expected O, but got Unknown
|
||||
//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
|
||||
//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
|
||||
//IL_01d9: Unknown result type (might be due to invalid IL or missing references)
|
||||
if (string.IsNullOrEmpty(iconPath) || !File.Exists(iconPath))
|
||||
{
|
||||
Console.WriteLine("[X11Window] Icon file not found: " + iconPath);
|
||||
return;
|
||||
}
|
||||
Console.WriteLine("[X11Window] SetIcon called: " + iconPath);
|
||||
try
|
||||
{
|
||||
SKBitmap val = null;
|
||||
if (iconPath.EndsWith(".svg", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine("[X11Window] Loading SVG icon");
|
||||
SKSvg val2 = new SKSvg();
|
||||
try
|
||||
{
|
||||
val2.Load(iconPath);
|
||||
if (val2.Picture != null)
|
||||
{
|
||||
SKRect cullRect = val2.Picture.CullRect;
|
||||
float num = 48f / Math.Max(((SKRect)(ref cullRect)).Width, ((SKRect)(ref cullRect)).Height);
|
||||
int num2 = (int)(((SKRect)(ref cullRect)).Width * num);
|
||||
int num3 = (int)(((SKRect)(ref cullRect)).Height * num);
|
||||
val = new SKBitmap(num2, num3, false);
|
||||
SKCanvas val3 = new SKCanvas(val);
|
||||
try
|
||||
{
|
||||
val3.Clear(SKColors.Transparent);
|
||||
val3.Scale(num);
|
||||
val3.DrawPicture(val2.Picture, (SKPaint)null);
|
||||
}
|
||||
finally
|
||||
{
|
||||
((IDisposable)val3)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
((IDisposable)val2)?.Dispose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[X11Window] Loading raster icon");
|
||||
val = SKBitmap.Decode(iconPath);
|
||||
}
|
||||
if (val == null)
|
||||
{
|
||||
Console.WriteLine("[X11Window] Failed to load icon: " + iconPath);
|
||||
return;
|
||||
}
|
||||
Console.WriteLine($"[X11Window] Loaded bitmap: {val.Width}x{val.Height}");
|
||||
int num4 = 64;
|
||||
if (val.Width != num4 || val.Height != num4)
|
||||
{
|
||||
SKBitmap val4 = new SKBitmap(num4, num4, false);
|
||||
val.ScalePixels(val4, (SKFilterQuality)3);
|
||||
((SKNativeObject)val).Dispose();
|
||||
val = val4;
|
||||
}
|
||||
int width = val.Width;
|
||||
int height = val.Height;
|
||||
int num5 = 2 + width * height;
|
||||
uint[] array = new uint[num5];
|
||||
array[0] = (uint)width;
|
||||
array[1] = (uint)height;
|
||||
for (int i = 0; i < height; i++)
|
||||
{
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
SKColor pixel = val.GetPixel(j, i);
|
||||
array[2 + i * width + j] = (uint)((((SKColor)(ref pixel)).Alpha << 24) | (((SKColor)(ref pixel)).Red << 16) | (((SKColor)(ref pixel)).Green << 8) | ((SKColor)(ref pixel)).Blue);
|
||||
}
|
||||
}
|
||||
((SKNativeObject)val).Dispose();
|
||||
IntPtr property = X11.XInternAtom(_display, "_NET_WM_ICON", onlyIfExists: false);
|
||||
IntPtr type = X11.XInternAtom(_display, "CARDINAL", onlyIfExists: false);
|
||||
fixed (uint* data = array)
|
||||
{
|
||||
X11.XChangeProperty(_display, _window, property, type, 32, 0, (nint)data, num5);
|
||||
}
|
||||
X11.XFlush(_display);
|
||||
Console.WriteLine($"[X11Window] Set window icon: {width}x{height}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[X11Window] Failed to set icon: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessEvents()
|
||||
{
|
||||
int num = X11.XPending(_display);
|
||||
if (num > 0)
|
||||
{
|
||||
if (_eventCounter % 100 == 0)
|
||||
{
|
||||
Console.WriteLine($"[X11Window] ProcessEvents: {num} pending events");
|
||||
}
|
||||
_eventCounter++;
|
||||
while (X11.XPending(_display) > 0)
|
||||
{
|
||||
Console.WriteLine("[X11Window] About to call XNextEvent");
|
||||
Console.Out.Flush();
|
||||
X11.XNextEvent(_display, out var eventReturn);
|
||||
Console.WriteLine($"[X11Window] XNextEvent returned, type={eventReturn.Type}");
|
||||
Console.Out.Flush();
|
||||
HandleEvent(ref eventReturn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
_isRunning = true;
|
||||
while (_isRunning)
|
||||
{
|
||||
X11.XNextEvent(_display, out var eventReturn);
|
||||
HandleEvent(ref eventReturn);
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
private void HandleEvent(ref XEvent xEvent)
|
||||
{
|
||||
Console.WriteLine($"[X11Window] Event: type={xEvent.Type}");
|
||||
switch (xEvent.Type)
|
||||
{
|
||||
case 2:
|
||||
Console.WriteLine("[X11Window] KeyPress event");
|
||||
HandleKeyPress(ref xEvent.KeyEvent);
|
||||
break;
|
||||
case 3:
|
||||
HandleKeyRelease(ref xEvent.KeyEvent);
|
||||
break;
|
||||
case 4:
|
||||
Console.WriteLine($"[X11Window] ButtonPress event at ({xEvent.ButtonEvent.X}, {xEvent.ButtonEvent.Y}) button={xEvent.ButtonEvent.Button}");
|
||||
HandleButtonPress(ref xEvent.ButtonEvent);
|
||||
break;
|
||||
case 5:
|
||||
HandleButtonRelease(ref xEvent.ButtonEvent);
|
||||
break;
|
||||
case 6:
|
||||
HandleMotion(ref xEvent.MotionEvent);
|
||||
break;
|
||||
case 12:
|
||||
if (xEvent.ExposeEvent.Count == 0)
|
||||
{
|
||||
this.Exposed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
break;
|
||||
case 22:
|
||||
HandleConfigure(ref xEvent.ConfigureEvent);
|
||||
break;
|
||||
case 9:
|
||||
this.FocusGained?.Invoke(this, EventArgs.Empty);
|
||||
break;
|
||||
case 10:
|
||||
this.FocusLost?.Invoke(this, EventArgs.Empty);
|
||||
break;
|
||||
case 33:
|
||||
if (xEvent.ClientMessageEvent.Data.L0 == (long)_wmDeleteMessage)
|
||||
{
|
||||
this.CloseRequested?.Invoke(this, EventArgs.Empty);
|
||||
_isRunning = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleKeyPress(ref XKeyEvent keyEvent)
|
||||
{
|
||||
ulong keysym = KeyMapping.GetKeysym(_display, keyEvent.Keycode, (keyEvent.State & 1) != 0);
|
||||
Key key = KeyMapping.FromKeysym(keysym);
|
||||
KeyModifiers modifiers = KeyMapping.GetModifiers(keyEvent.State);
|
||||
this.KeyDown?.Invoke(this, new KeyEventArgs(key, modifiers));
|
||||
bool flag = (keyEvent.State & 4) != 0;
|
||||
bool flag2 = (keyEvent.State & 8) != 0;
|
||||
if (keysym >= 32 && keysym <= 126 && !flag && !flag2)
|
||||
{
|
||||
this.TextInput?.Invoke(this, new TextInputEventArgs(((char)keysym).ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleKeyRelease(ref XKeyEvent keyEvent)
|
||||
{
|
||||
Key key = KeyMapping.FromKeysym(KeyMapping.GetKeysym(_display, keyEvent.Keycode, (keyEvent.State & 1) != 0));
|
||||
KeyModifiers modifiers = KeyMapping.GetModifiers(keyEvent.State);
|
||||
this.KeyUp?.Invoke(this, new KeyEventArgs(key, modifiers));
|
||||
}
|
||||
|
||||
private void HandleButtonPress(ref XButtonEvent buttonEvent)
|
||||
{
|
||||
Console.WriteLine($"[X11Window] HandleButtonPress: button={buttonEvent.Button}, pos=({buttonEvent.X}, {buttonEvent.Y}), hasHandler={this.PointerPressed != null}");
|
||||
if (buttonEvent.Button == 4)
|
||||
{
|
||||
this.Scroll?.Invoke(this, new ScrollEventArgs(buttonEvent.X, buttonEvent.Y, 0f, -1f));
|
||||
return;
|
||||
}
|
||||
if (buttonEvent.Button == 5)
|
||||
{
|
||||
this.Scroll?.Invoke(this, new ScrollEventArgs(buttonEvent.X, buttonEvent.Y, 0f, 1f));
|
||||
return;
|
||||
}
|
||||
PointerButton pointerButton = MapButton(buttonEvent.Button);
|
||||
Console.WriteLine($"[X11Window] Invoking PointerPressed with button={pointerButton}");
|
||||
this.PointerPressed?.Invoke(this, new PointerEventArgs(buttonEvent.X, buttonEvent.Y, pointerButton));
|
||||
}
|
||||
|
||||
private void HandleButtonRelease(ref XButtonEvent buttonEvent)
|
||||
{
|
||||
Console.WriteLine($"[X11Window] HandleButtonRelease: button={buttonEvent.Button}, pos=({buttonEvent.X}, {buttonEvent.Y})");
|
||||
if (buttonEvent.Button != 4 && buttonEvent.Button != 5)
|
||||
{
|
||||
PointerButton button = MapButton(buttonEvent.Button);
|
||||
Console.WriteLine($"[X11Window] Invoking PointerReleased, hasHandler={this.PointerReleased != null}");
|
||||
this.PointerReleased?.Invoke(this, new PointerEventArgs(buttonEvent.X, buttonEvent.Y, button));
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleMotion(ref XMotionEvent motionEvent)
|
||||
{
|
||||
this.PointerMoved?.Invoke(this, new PointerEventArgs(motionEvent.X, motionEvent.Y));
|
||||
}
|
||||
|
||||
private void HandleConfigure(ref XConfigureEvent configureEvent)
|
||||
{
|
||||
if (configureEvent.Width != _width || configureEvent.Height != _height)
|
||||
{
|
||||
_width = configureEvent.Width;
|
||||
_height = configureEvent.Height;
|
||||
this.Resized?.Invoke(this, (_width, _height));
|
||||
}
|
||||
}
|
||||
|
||||
private static PointerButton MapButton(uint button)
|
||||
{
|
||||
return button switch
|
||||
{
|
||||
1u => PointerButton.Left,
|
||||
2u => PointerButton.Middle,
|
||||
3u => PointerButton.Right,
|
||||
8u => PointerButton.XButton1,
|
||||
9u => PointerButton.XButton2,
|
||||
_ => PointerButton.None,
|
||||
};
|
||||
}
|
||||
|
||||
public int GetFileDescriptor()
|
||||
{
|
||||
return X11.XConnectionNumber(_display);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (_arrowCursor != IntPtr.Zero)
|
||||
{
|
||||
X11.XFreeCursor(_display, _arrowCursor);
|
||||
_arrowCursor = IntPtr.Zero;
|
||||
}
|
||||
if (_handCursor != IntPtr.Zero)
|
||||
{
|
||||
X11.XFreeCursor(_display, _handCursor);
|
||||
_handCursor = IntPtr.Zero;
|
||||
}
|
||||
if (_textCursor != IntPtr.Zero)
|
||||
{
|
||||
X11.XFreeCursor(_display, _textCursor);
|
||||
_textCursor = IntPtr.Zero;
|
||||
}
|
||||
if (_window != IntPtr.Zero)
|
||||
{
|
||||
X11.XDestroyWindow(_display, _window);
|
||||
_window = IntPtr.Zero;
|
||||
}
|
||||
if (_display != IntPtr.Zero)
|
||||
{
|
||||
X11.XCloseDisplay(_display);
|
||||
_display = IntPtr.Zero;
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void DrawPixels(IntPtr pixels, int width, int height, int stride)
|
||||
{
|
||||
if (_display == IntPtr.Zero || _window == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
IntPtr gc = X11.XDefaultGC(_display, _screen);
|
||||
IntPtr visual = X11.XDefaultVisual(_display, _screen);
|
||||
int depth = X11.XDefaultDepth(_display, _screen);
|
||||
int num = height * stride;
|
||||
IntPtr intPtr = Marshal.AllocHGlobal(num);
|
||||
try
|
||||
{
|
||||
Buffer.MemoryCopy((void*)pixels, (void*)intPtr, num, num);
|
||||
IntPtr intPtr2 = X11.XCreateImage(_display, visual, (uint)depth, 2, 0, intPtr, (uint)width, (uint)height, 32, stride);
|
||||
if (intPtr2 != IntPtr.Zero)
|
||||
{
|
||||
X11.XPutImage(_display, _window, gc, intPtr2, 0, 0, 0, 0, (uint)width, (uint)height);
|
||||
X11.XDestroyImage(intPtr2);
|
||||
}
|
||||
else
|
||||
{
|
||||
Marshal.FreeHGlobal(intPtr);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Marshal.FreeHGlobal(intPtr);
|
||||
throw;
|
||||
}
|
||||
X11.XFlush(_display);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~X11Window()
|
||||
{
|
||||
Dispose(disposing: false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user