2025-12-19 09:30:16 +00:00
|
|
|
// 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 Microsoft.Maui.Platform.Linux.Input;
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
using SkiaSharp;
|
|
|
|
|
using Svg.Skia;
|
2025-12-19 09:30:16 +00:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
Major production merge: GTK support, context menus, and dispatcher fixes
Core Infrastructure:
- Add Dispatching folder with LinuxDispatcher, LinuxDispatcherProvider, LinuxDispatcherTimer
- Add Native folder with P/Invoke wrappers (GTK, GLib, GDK, Cairo, WebKit)
- Add GTK host window system with GtkHostWindow and GtkSkiaSurfaceWidget
- Update LinuxApplication with GTK mode, theme handling, and icon support
- Fix duplicate LinuxDispatcher in LinuxMauiContext
Handlers:
- Add GtkWebViewManager and GtkWebViewPlatformView for GTK WebView
- Add FlexLayoutHandler and GestureManager
- Update multiple handlers with ToViewHandler fix and missing mappers
- Add MauiHandlerExtensions with ToViewHandler extension method
Views:
- Add SkiaContextMenu with hover, keyboard, and dark theme support
- Add LinuxDialogService with context menu management
- Add SkiaFlexLayout for flex container support
- Update SkiaShell with RefreshTheme, MauiShell, ContentRenderer
- Update SkiaWebView with SetMainWindow, ProcessGtkEvents
- Update SkiaImage with LoadFromBitmap method
Services:
- Add AppInfoService, ConnectivityService, DeviceDisplayService, DeviceInfoService
- Add GtkHostService, GtkContextMenuService, MauiIconGenerator
Window:
- Add CursorType enum and GtkHostWindow
- Update X11Window with SetIcon, SetCursor methods
Build: SUCCESS (0 errors)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 11:19:58 -05:00
|
|
|
// Cursor handles
|
|
|
|
|
private IntPtr _arrowCursor;
|
|
|
|
|
private IntPtr _handCursor;
|
|
|
|
|
private IntPtr _textCursor;
|
|
|
|
|
private IntPtr _currentCursor;
|
|
|
|
|
private CursorType _currentCursorType = CursorType.Arrow;
|
|
|
|
|
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
private static int _eventCounter;
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
/// <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);
|
|
|
|
|
|
Major production merge: GTK support, context menus, and dispatcher fixes
Core Infrastructure:
- Add Dispatching folder with LinuxDispatcher, LinuxDispatcherProvider, LinuxDispatcherTimer
- Add Native folder with P/Invoke wrappers (GTK, GLib, GDK, Cairo, WebKit)
- Add GTK host window system with GtkHostWindow and GtkSkiaSurfaceWidget
- Update LinuxApplication with GTK mode, theme handling, and icon support
- Fix duplicate LinuxDispatcher in LinuxMauiContext
Handlers:
- Add GtkWebViewManager and GtkWebViewPlatformView for GTK WebView
- Add FlexLayoutHandler and GestureManager
- Update multiple handlers with ToViewHandler fix and missing mappers
- Add MauiHandlerExtensions with ToViewHandler extension method
Views:
- Add SkiaContextMenu with hover, keyboard, and dark theme support
- Add LinuxDialogService with context menu management
- Add SkiaFlexLayout for flex container support
- Update SkiaShell with RefreshTheme, MauiShell, ContentRenderer
- Update SkiaWebView with SetMainWindow, ProcessGtkEvents
- Update SkiaImage with LoadFromBitmap method
Services:
- Add AppInfoService, ConnectivityService, DeviceDisplayService, DeviceInfoService
- Add GtkHostService, GtkContextMenuService, MauiIconGenerator
Window:
- Add CursorType enum and GtkHostWindow
- Update X11Window with SetIcon, SetCursor methods
Build: SUCCESS (0 errors)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 11:19:58 -05:00
|
|
|
// Initialize cursors
|
|
|
|
|
_arrowCursor = X11.XCreateFontCursor(_display, 68); // XC_left_ptr
|
|
|
|
|
_handCursor = X11.XCreateFontCursor(_display, 60); // XC_hand2
|
|
|
|
|
_textCursor = X11.XCreateFontCursor(_display, 152); // XC_xterm
|
|
|
|
|
_currentCursor = _arrowCursor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sets the cursor type for this window.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void SetCursor(CursorType cursorType)
|
|
|
|
|
{
|
|
|
|
|
if (_currentCursorType != cursorType)
|
|
|
|
|
{
|
|
|
|
|
_currentCursorType = cursorType;
|
|
|
|
|
IntPtr cursor = cursorType switch
|
|
|
|
|
{
|
|
|
|
|
CursorType.Hand => _handCursor,
|
|
|
|
|
CursorType.Text => _textCursor,
|
|
|
|
|
_ => _arrowCursor,
|
|
|
|
|
};
|
|
|
|
|
if (cursor != _currentCursor)
|
|
|
|
|
{
|
|
|
|
|
_currentCursor = cursor;
|
|
|
|
|
X11.XDefineCursor(_display, _window, _currentCursor);
|
|
|
|
|
X11.XFlush(_display);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
/// Sets the window icon from a file. Supports both raster images and SVG.
|
Major production merge: GTK support, context menus, and dispatcher fixes
Core Infrastructure:
- Add Dispatching folder with LinuxDispatcher, LinuxDispatcherProvider, LinuxDispatcherTimer
- Add Native folder with P/Invoke wrappers (GTK, GLib, GDK, Cairo, WebKit)
- Add GTK host window system with GtkHostWindow and GtkSkiaSurfaceWidget
- Update LinuxApplication with GTK mode, theme handling, and icon support
- Fix duplicate LinuxDispatcher in LinuxMauiContext
Handlers:
- Add GtkWebViewManager and GtkWebViewPlatformView for GTK WebView
- Add FlexLayoutHandler and GestureManager
- Update multiple handlers with ToViewHandler fix and missing mappers
- Add MauiHandlerExtensions with ToViewHandler extension method
Views:
- Add SkiaContextMenu with hover, keyboard, and dark theme support
- Add LinuxDialogService with context menu management
- Add SkiaFlexLayout for flex container support
- Update SkiaShell with RefreshTheme, MauiShell, ContentRenderer
- Update SkiaWebView with SetMainWindow, ProcessGtkEvents
- Update SkiaImage with LoadFromBitmap method
Services:
- Add AppInfoService, ConnectivityService, DeviceDisplayService, DeviceInfoService
- Add GtkHostService, GtkContextMenuService, MauiIconGenerator
Window:
- Add CursorType enum and GtkHostWindow
- Update X11Window with SetIcon, SetCursor methods
Build: SUCCESS (0 errors)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 11:19:58 -05:00
|
|
|
/// </summary>
|
|
|
|
|
public unsafe void SetIcon(string iconPath)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(iconPath) || !System.IO.File.Exists(iconPath))
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("[X11Window] Icon file not found: " + iconPath);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Console.WriteLine("[X11Window] SetIcon called: " + iconPath);
|
|
|
|
|
try
|
|
|
|
|
{
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
SKBitmap? bitmap = null;
|
|
|
|
|
|
|
|
|
|
// Handle SVG icons
|
|
|
|
|
if (iconPath.EndsWith(".svg", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("[X11Window] Loading SVG icon");
|
|
|
|
|
using var svg = new SKSvg();
|
|
|
|
|
svg.Load(iconPath);
|
|
|
|
|
if (svg.Picture != null)
|
|
|
|
|
{
|
|
|
|
|
var cullRect = svg.Picture.CullRect;
|
|
|
|
|
float scale = 48f / Math.Max(cullRect.Width, cullRect.Height);
|
|
|
|
|
int scaledWidth = (int)(cullRect.Width * scale);
|
|
|
|
|
int scaledHeight = (int)(cullRect.Height * scale);
|
|
|
|
|
bitmap = new SKBitmap(scaledWidth, scaledHeight, false);
|
|
|
|
|
using var canvas = new SKCanvas(bitmap);
|
|
|
|
|
canvas.Clear(SKColors.Transparent);
|
|
|
|
|
canvas.Scale(scale);
|
|
|
|
|
canvas.DrawPicture(svg.Picture);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("[X11Window] Loading raster icon");
|
|
|
|
|
bitmap = SKBitmap.Decode(iconPath);
|
|
|
|
|
}
|
|
|
|
|
|
Major production merge: GTK support, context menus, and dispatcher fixes
Core Infrastructure:
- Add Dispatching folder with LinuxDispatcher, LinuxDispatcherProvider, LinuxDispatcherTimer
- Add Native folder with P/Invoke wrappers (GTK, GLib, GDK, Cairo, WebKit)
- Add GTK host window system with GtkHostWindow and GtkSkiaSurfaceWidget
- Update LinuxApplication with GTK mode, theme handling, and icon support
- Fix duplicate LinuxDispatcher in LinuxMauiContext
Handlers:
- Add GtkWebViewManager and GtkWebViewPlatformView for GTK WebView
- Add FlexLayoutHandler and GestureManager
- Update multiple handlers with ToViewHandler fix and missing mappers
- Add MauiHandlerExtensions with ToViewHandler extension method
Views:
- Add SkiaContextMenu with hover, keyboard, and dark theme support
- Add LinuxDialogService with context menu management
- Add SkiaFlexLayout for flex container support
- Update SkiaShell with RefreshTheme, MauiShell, ContentRenderer
- Update SkiaWebView with SetMainWindow, ProcessGtkEvents
- Update SkiaImage with LoadFromBitmap method
Services:
- Add AppInfoService, ConnectivityService, DeviceDisplayService, DeviceInfoService
- Add GtkHostService, GtkContextMenuService, MauiIconGenerator
Window:
- Add CursorType enum and GtkHostWindow
- Update X11Window with SetIcon, SetCursor methods
Build: SUCCESS (0 errors)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 11:19:58 -05:00
|
|
|
if (bitmap == null)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("[X11Window] Failed to load icon: " + iconPath);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Console.WriteLine($"[X11Window] Loaded bitmap: {bitmap.Width}x{bitmap.Height}");
|
|
|
|
|
|
|
|
|
|
// Scale to 64x64 if needed
|
|
|
|
|
int targetSize = 64;
|
|
|
|
|
if (bitmap.Width != targetSize || bitmap.Height != targetSize)
|
|
|
|
|
{
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
var scaled = new SKBitmap(targetSize, targetSize, false);
|
|
|
|
|
bitmap.ScalePixels(scaled, SKFilterQuality.High);
|
Major production merge: GTK support, context menus, and dispatcher fixes
Core Infrastructure:
- Add Dispatching folder with LinuxDispatcher, LinuxDispatcherProvider, LinuxDispatcherTimer
- Add Native folder with P/Invoke wrappers (GTK, GLib, GDK, Cairo, WebKit)
- Add GTK host window system with GtkHostWindow and GtkSkiaSurfaceWidget
- Update LinuxApplication with GTK mode, theme handling, and icon support
- Fix duplicate LinuxDispatcher in LinuxMauiContext
Handlers:
- Add GtkWebViewManager and GtkWebViewPlatformView for GTK WebView
- Add FlexLayoutHandler and GestureManager
- Update multiple handlers with ToViewHandler fix and missing mappers
- Add MauiHandlerExtensions with ToViewHandler extension method
Views:
- Add SkiaContextMenu with hover, keyboard, and dark theme support
- Add LinuxDialogService with context menu management
- Add SkiaFlexLayout for flex container support
- Update SkiaShell with RefreshTheme, MauiShell, ContentRenderer
- Update SkiaWebView with SetMainWindow, ProcessGtkEvents
- Update SkiaImage with LoadFromBitmap method
Services:
- Add AppInfoService, ConnectivityService, DeviceDisplayService, DeviceInfoService
- Add GtkHostService, GtkContextMenuService, MauiIconGenerator
Window:
- Add CursorType enum and GtkHostWindow
- Update X11Window with SetIcon, SetCursor methods
Build: SUCCESS (0 errors)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 11:19:58 -05:00
|
|
|
bitmap.Dispose();
|
|
|
|
|
bitmap = scaled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int width = bitmap.Width;
|
|
|
|
|
int height = bitmap.Height;
|
|
|
|
|
int dataSize = 2 + width * height;
|
|
|
|
|
uint[] iconData = new uint[dataSize];
|
|
|
|
|
iconData[0] = (uint)width;
|
|
|
|
|
iconData[1] = (uint)height;
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < height; y++)
|
|
|
|
|
{
|
|
|
|
|
for (int x = 0; x < width; x++)
|
|
|
|
|
{
|
|
|
|
|
var pixel = bitmap.GetPixel(x, y);
|
|
|
|
|
iconData[2 + y * width + x] = (uint)((pixel.Alpha << 24) | (pixel.Red << 16) | (pixel.Green << 8) | pixel.Blue);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bitmap.Dispose();
|
|
|
|
|
|
|
|
|
|
IntPtr property = X11.XInternAtom(_display, "_NET_WM_ICON", false);
|
|
|
|
|
IntPtr type = X11.XInternAtom(_display, "CARDINAL", false);
|
|
|
|
|
fixed (uint* data = iconData)
|
|
|
|
|
{
|
|
|
|
|
X11.XChangeProperty(_display, _window, property, type, 32, 0, (nint)data, dataSize);
|
|
|
|
|
}
|
|
|
|
|
X11.XFlush(_display);
|
|
|
|
|
Console.WriteLine($"[X11Window] Set window icon: {width}x{height}");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("[X11Window] Failed to set icon: " + ex.Message);
|
|
|
|
|
}
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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>
|
2026-01-17 02:33:00 +00:00
|
|
|
/// Moves the window to the specified position.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void SetPosition(int x, int y)
|
|
|
|
|
{
|
|
|
|
|
X11.XMoveWindow(_display, _window, x, y);
|
|
|
|
|
X11.XFlush(_display);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Maximizes the window.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void Maximize()
|
|
|
|
|
{
|
|
|
|
|
SendWindowStateEvent(true, "_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_MAXIMIZED_HORZ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Minimizes (iconifies) the window.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void Minimize()
|
|
|
|
|
{
|
|
|
|
|
X11.XIconifyWindow(_display, _window, _screen);
|
|
|
|
|
X11.XFlush(_display);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Restores the window from maximized or minimized state.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void Restore()
|
|
|
|
|
{
|
|
|
|
|
// Remove maximized state
|
|
|
|
|
SendWindowStateEvent(false, "_NET_WM_STATE_MAXIMIZED_VERT", "_NET_WM_STATE_MAXIMIZED_HORZ");
|
|
|
|
|
// Map window if it was minimized
|
|
|
|
|
X11.XMapWindow(_display, _window);
|
|
|
|
|
X11.XFlush(_display);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sets fullscreen mode.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void SetFullscreen(bool fullscreen)
|
|
|
|
|
{
|
|
|
|
|
SendWindowStateEvent(fullscreen, "_NET_WM_STATE_FULLSCREEN");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SendWindowStateEvent(bool add, params string[] stateNames)
|
|
|
|
|
{
|
|
|
|
|
var wmState = X11.XInternAtom(_display, "_NET_WM_STATE", false);
|
|
|
|
|
var rootWindow = X11.XRootWindow(_display, _screen);
|
|
|
|
|
|
|
|
|
|
foreach (var stateName in stateNames)
|
|
|
|
|
{
|
|
|
|
|
var stateAtom = X11.XInternAtom(_display, stateName, false);
|
|
|
|
|
|
|
|
|
|
var xev = new XEvent();
|
|
|
|
|
xev.ClientMessageEvent.Type = X11.ClientMessage;
|
|
|
|
|
xev.ClientMessageEvent.Window = _window;
|
|
|
|
|
xev.ClientMessageEvent.MessageType = wmState;
|
|
|
|
|
xev.ClientMessageEvent.Format = 32;
|
|
|
|
|
|
|
|
|
|
// data.l[0] = action (0=remove, 1=add, 2=toggle)
|
|
|
|
|
// data.l[1] = first property
|
|
|
|
|
// data.l[2] = second property (optional)
|
|
|
|
|
// data.l[3] = source indication (1 = normal application)
|
|
|
|
|
xev.ClientMessageEvent.Data.L0 = add ? 1 : 0;
|
|
|
|
|
xev.ClientMessageEvent.Data.L1 = (long)stateAtom;
|
|
|
|
|
xev.ClientMessageEvent.Data.L2 = 0;
|
|
|
|
|
xev.ClientMessageEvent.Data.L3 = 1;
|
|
|
|
|
|
|
|
|
|
X11.XSendEvent(_display, rootWindow, false,
|
|
|
|
|
X11.SubstructureRedirectMask | X11.SubstructureNotifyMask,
|
|
|
|
|
ref xev);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
X11.XFlush(_display);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-12-19 09:30:16 +00:00
|
|
|
/// Processes pending X11 events.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void ProcessEvents()
|
|
|
|
|
{
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
int pending = X11.XPending(_display);
|
|
|
|
|
if (pending > 0)
|
2025-12-19 09:30:16 +00:00
|
|
|
{
|
Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements
Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment
Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs
Build: 0 errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00
|
|
|
if (_eventCounter % 100 == 0)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"[X11Window] ProcessEvents: {pending} pending events");
|
|
|
|
|
}
|
|
|
|
|
_eventCounter++;
|
|
|
|
|
while (X11.XPending(_display) > 0)
|
|
|
|
|
{
|
|
|
|
|
X11.XNextEvent(_display, out var xEvent);
|
|
|
|
|
HandleEvent(ref xEvent);
|
|
|
|
|
}
|
2025-12-19 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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));
|
|
|
|
|
|
2025-12-21 13:26:56 -05:00
|
|
|
// 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)
|
2025-12-19 09:30:16 +00:00
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
}
|