mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-07-15 15:19:57 +00:00
@@ -1,3 +1,4 @@
|
||||
using Daybreak.Linux.Utils;
|
||||
using Daybreak.Shared.Models;
|
||||
using Daybreak.Shared.Services.Keyboard;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -5,149 +6,23 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Linux.Services.Keyboard;
|
||||
|
||||
public partial class KeyboardHookService : IHostedService, IKeyboardHookService, IDisposable
|
||||
public sealed class KeyboardHookService : IHostedService, IKeyboardHookService, IDisposable
|
||||
{
|
||||
private const string X11Lib = "libX11.so.6";
|
||||
private const string XtstLib = "libXtst.so.6";
|
||||
|
||||
// X11 event types
|
||||
private const int KeyPress = 2;
|
||||
private const int KeyRelease = 3;
|
||||
|
||||
// XRecord constants
|
||||
private const int XRecordFromServer = 0;
|
||||
private const int XRecordAllClients = 3;
|
||||
|
||||
// XK key codes (from X11/keysymdef.h)
|
||||
private const uint XK_F1 = 0xffbe;
|
||||
private const uint XK_F2 = 0xffbf;
|
||||
private const uint XK_F3 = 0xffc0;
|
||||
private const uint XK_F4 = 0xffc1;
|
||||
private const uint XK_F5 = 0xffc2;
|
||||
private const uint XK_F6 = 0xffc3;
|
||||
private const uint XK_F7 = 0xffc4;
|
||||
private const uint XK_F8 = 0xffc5;
|
||||
private const uint XK_F9 = 0xffc6;
|
||||
private const uint XK_F10 = 0xffc7;
|
||||
private const uint XK_F11 = 0xffc8;
|
||||
private const uint XK_F12 = 0xffc9;
|
||||
|
||||
// XRecordRange structure
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct XRecordRange
|
||||
{
|
||||
public XRecordRange8 core_requests;
|
||||
public XRecordRange8 core_replies;
|
||||
public XRecordExtRange ext_requests;
|
||||
public XRecordExtRange ext_replies;
|
||||
public XRecordRange8 delivered_events;
|
||||
public XRecordRange8 device_events;
|
||||
public XRecordRange8 errors;
|
||||
public byte client_started;
|
||||
public byte client_died;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct XRecordRange8
|
||||
{
|
||||
public byte first;
|
||||
public byte last;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct XRecordExtRange
|
||||
{
|
||||
public XRecordRange16 ext_major;
|
||||
public XRecordRange8 ext_minor;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct XRecordRange16
|
||||
{
|
||||
public ushort first;
|
||||
public ushort last;
|
||||
}
|
||||
|
||||
// Callback delegate for XRecordEnableContext
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate void XRecordInterceptProc(nint closure, ref XRecordInterceptData recorded_data);
|
||||
|
||||
// XRecordInterceptData structure
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct XRecordInterceptData
|
||||
{
|
||||
public nint id_base;
|
||||
public nint server_time;
|
||||
public nint client_seq;
|
||||
public int category;
|
||||
public byte client_swapped;
|
||||
private byte pad1;
|
||||
private byte pad2;
|
||||
private byte pad3;
|
||||
public nint data;
|
||||
public uint data_len;
|
||||
}
|
||||
|
||||
// X11 P/Invoke
|
||||
[LibraryImport(X11Lib, EntryPoint = "XOpenDisplay")]
|
||||
private static partial nint XOpenDisplay(nint display_name);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XCloseDisplay")]
|
||||
private static partial int XCloseDisplay(nint display);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XFlush")]
|
||||
private static partial int XFlush(nint display);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XKeycodeToKeysym")]
|
||||
private static partial uint XKeycodeToKeysym(nint display, byte keycode, int index);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XFree")]
|
||||
private static partial int XFree(nint data);
|
||||
|
||||
// XRecord extension P/Invoke
|
||||
[LibraryImport(XtstLib, EntryPoint = "XRecordQueryVersion")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool XRecordQueryVersion(nint display, out int major, out int minor);
|
||||
|
||||
[LibraryImport(XtstLib, EntryPoint = "XRecordAllocRange")]
|
||||
private static partial nint XRecordAllocRange();
|
||||
|
||||
[LibraryImport(XtstLib, EntryPoint = "XRecordCreateContext")]
|
||||
private static partial ulong XRecordCreateContext(
|
||||
nint display,
|
||||
int flags,
|
||||
nint[] client_specs,
|
||||
int nclients,
|
||||
nint[] ranges,
|
||||
int nranges);
|
||||
|
||||
[LibraryImport(XtstLib, EntryPoint = "XRecordEnableContext")]
|
||||
private static partial int XRecordEnableContext(
|
||||
nint display,
|
||||
ulong context,
|
||||
XRecordInterceptProc callback,
|
||||
nint closure);
|
||||
|
||||
[LibraryImport(XtstLib, EntryPoint = "XRecordDisableContext")]
|
||||
private static partial int XRecordDisableContext(nint display, ulong context);
|
||||
|
||||
[LibraryImport(XtstLib, EntryPoint = "XRecordFreeContext")]
|
||||
private static partial int XRecordFreeContext(nint display, ulong context);
|
||||
|
||||
// Instance fields
|
||||
private nint controlDisplay;
|
||||
private nint dataDisplay;
|
||||
private ulong recordContext;
|
||||
private Thread? recordThread;
|
||||
private Task? recordTask;
|
||||
private CancellationTokenSource? cts;
|
||||
private bool isStarted;
|
||||
private XRecordInterceptProc? callbackDelegate;
|
||||
private NativeMethods.XRecordInterceptProc? callbackDelegate;
|
||||
private ulong appWindowId;
|
||||
|
||||
public event EventHandler<KeyboardEventArgs>? KeyDown;
|
||||
public event EventHandler<KeyboardEventArgs>? KeyUp;
|
||||
|
||||
Task IHostedService.StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
this.appWindowId = FindAppWindowId();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -167,8 +42,8 @@ public partial class KeyboardHookService : IHostedService, IKeyboardHookService,
|
||||
// We need two display connections for XRecord:
|
||||
// - controlDisplay: for controlling the recording (enable/disable)
|
||||
// - dataDisplay: for receiving the recorded data (blocks in XRecordEnableContext)
|
||||
this.controlDisplay = XOpenDisplay(nint.Zero);
|
||||
this.dataDisplay = XOpenDisplay(nint.Zero);
|
||||
this.controlDisplay = NativeMethods.XOpenDisplay(nint.Zero);
|
||||
this.dataDisplay = NativeMethods.XOpenDisplay(nint.Zero);
|
||||
|
||||
if (this.controlDisplay == nint.Zero || this.dataDisplay == nint.Zero)
|
||||
{
|
||||
@@ -177,14 +52,14 @@ public partial class KeyboardHookService : IHostedService, IKeyboardHookService,
|
||||
}
|
||||
|
||||
// Check if XRecord extension is available
|
||||
if (!XRecordQueryVersion(this.controlDisplay, out _, out _))
|
||||
if (!NativeMethods.XRecordQueryVersion(this.controlDisplay, out _, out _))
|
||||
{
|
||||
this.Cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a record range for keyboard events only
|
||||
var rangePtr = XRecordAllocRange();
|
||||
var rangePtr = NativeMethods.XRecordAllocRange();
|
||||
if (rangePtr == nint.Zero)
|
||||
{
|
||||
this.Cleanup();
|
||||
@@ -192,21 +67,21 @@ public partial class KeyboardHookService : IHostedService, IKeyboardHookService,
|
||||
}
|
||||
|
||||
// Set up the range to capture keyboard events (KeyPress and KeyRelease)
|
||||
var range = Marshal.PtrToStructure<XRecordRange>(rangePtr);
|
||||
range.device_events.first = KeyPress;
|
||||
range.device_events.last = KeyRelease;
|
||||
var range = Marshal.PtrToStructure<NativeMethods.XRecordRange>(rangePtr);
|
||||
range.device_events.first = NativeMethods.KeyPress;
|
||||
range.device_events.last = NativeMethods.KeyRelease;
|
||||
Marshal.StructureToPtr(range, rangePtr, false);
|
||||
|
||||
// Create client spec for all clients
|
||||
var clientSpec = new nint[1];
|
||||
clientSpec[0] = XRecordAllClients;
|
||||
clientSpec[0] = NativeMethods.XRecordAllClients;
|
||||
|
||||
var ranges = new nint[1];
|
||||
ranges[0] = rangePtr;
|
||||
|
||||
// Create the record context on the DATA display (not control)
|
||||
// The context must be created and enabled on the same display
|
||||
this.recordContext = XRecordCreateContext(
|
||||
this.recordContext = NativeMethods.XRecordCreateContext(
|
||||
this.dataDisplay,
|
||||
0,
|
||||
clientSpec,
|
||||
@@ -214,7 +89,7 @@ public partial class KeyboardHookService : IHostedService, IKeyboardHookService,
|
||||
ranges,
|
||||
1);
|
||||
|
||||
XFree(rangePtr);
|
||||
NativeMethods.XFree(rangePtr);
|
||||
|
||||
if (this.recordContext == 0)
|
||||
{
|
||||
@@ -226,12 +101,11 @@ public partial class KeyboardHookService : IHostedService, IKeyboardHookService,
|
||||
this.callbackDelegate = this.RecordCallback;
|
||||
|
||||
this.cts = new CancellationTokenSource();
|
||||
this.recordThread = new Thread(this.RecordLoop)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "X11 XRecord Event Loop"
|
||||
};
|
||||
this.recordThread.Start();
|
||||
this.recordTask = Task.Factory.StartNew(
|
||||
this.RecordLoop,
|
||||
this.cts.Token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default);
|
||||
|
||||
this.isStarted = true;
|
||||
}
|
||||
@@ -248,11 +122,11 @@ public partial class KeyboardHookService : IHostedService, IKeyboardHookService,
|
||||
// Disable the context to unblock XRecordEnableContext
|
||||
if (this.controlDisplay != nint.Zero && this.recordContext != 0)
|
||||
{
|
||||
XRecordDisableContext(this.controlDisplay, this.recordContext);
|
||||
XFlush(this.controlDisplay);
|
||||
NativeMethods.XRecordDisableContext(this.controlDisplay, this.recordContext);
|
||||
NativeMethods.XFlush(this.controlDisplay);
|
||||
}
|
||||
|
||||
this.recordThread?.Join(TimeSpan.FromSeconds(2));
|
||||
this.recordTask = null;
|
||||
|
||||
this.Cleanup();
|
||||
this.isStarted = false;
|
||||
@@ -262,19 +136,19 @@ public partial class KeyboardHookService : IHostedService, IKeyboardHookService,
|
||||
{
|
||||
if (this.controlDisplay != nint.Zero && this.recordContext != 0)
|
||||
{
|
||||
XRecordFreeContext(this.controlDisplay, this.recordContext);
|
||||
NativeMethods.XRecordFreeContext(this.controlDisplay, this.recordContext);
|
||||
this.recordContext = 0;
|
||||
}
|
||||
|
||||
if (this.dataDisplay != nint.Zero)
|
||||
{
|
||||
XCloseDisplay(this.dataDisplay);
|
||||
NativeMethods.XCloseDisplay(this.dataDisplay);
|
||||
this.dataDisplay = nint.Zero;
|
||||
}
|
||||
|
||||
if (this.controlDisplay != nint.Zero)
|
||||
{
|
||||
XCloseDisplay(this.controlDisplay);
|
||||
NativeMethods.XCloseDisplay(this.controlDisplay);
|
||||
this.controlDisplay = nint.Zero;
|
||||
}
|
||||
|
||||
@@ -291,12 +165,18 @@ public partial class KeyboardHookService : IHostedService, IKeyboardHookService,
|
||||
private void RecordLoop()
|
||||
{
|
||||
// This call blocks until XRecordDisableContext is called
|
||||
XRecordEnableContext(this.dataDisplay, this.recordContext, this.callbackDelegate!, nint.Zero);
|
||||
NativeMethods.XRecordEnableContext(this.dataDisplay, this.recordContext, this.callbackDelegate!, nint.Zero);
|
||||
}
|
||||
|
||||
private void RecordCallback(nint closure, ref XRecordInterceptData data)
|
||||
private void RecordCallback(nint closure, ref NativeMethods.XRecordInterceptData data)
|
||||
{
|
||||
if (data.category != XRecordFromServer || data.data == nint.Zero)
|
||||
if (data.category != NativeMethods.XRecordFromServer || data.data == nint.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Only process events when our app window is focused
|
||||
if (!this.IsAppWindowFocused())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -306,13 +186,13 @@ public partial class KeyboardHookService : IHostedService, IKeyboardHookService,
|
||||
var eventType = Marshal.ReadByte(data.data, 0);
|
||||
var keycode = Marshal.ReadByte(data.data, 1);
|
||||
|
||||
if (eventType is not KeyPress and not KeyRelease)
|
||||
if (eventType is not NativeMethods.KeyPress and not NativeMethods.KeyRelease)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert keycode to keysym
|
||||
var keysym = XKeycodeToKeysym(this.controlDisplay, keycode, 0);
|
||||
var keysym = NativeMethods.XKeycodeToKeysym(this.controlDisplay, keycode, 0);
|
||||
|
||||
// Only process F1-F12 keys
|
||||
if (!TryMapXKeyToVirtualKey(keysym, out var virtualKey))
|
||||
@@ -320,7 +200,7 @@ public partial class KeyboardHookService : IHostedService, IKeyboardHookService,
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventType == KeyPress)
|
||||
if (eventType == NativeMethods.KeyPress)
|
||||
{
|
||||
this.KeyDown?.Invoke(this, new KeyboardEventArgs(virtualKey));
|
||||
}
|
||||
@@ -335,21 +215,143 @@ public partial class KeyboardHookService : IHostedService, IKeyboardHookService,
|
||||
// Only map F1-F12 - these are the only keys we need to detect
|
||||
virtualKey = keysym switch
|
||||
{
|
||||
XK_F1 => VirtualKey.F1,
|
||||
XK_F2 => VirtualKey.F2,
|
||||
XK_F3 => VirtualKey.F3,
|
||||
XK_F4 => VirtualKey.F4,
|
||||
XK_F5 => VirtualKey.F5,
|
||||
XK_F6 => VirtualKey.F6,
|
||||
XK_F7 => VirtualKey.F7,
|
||||
XK_F8 => VirtualKey.F8,
|
||||
XK_F9 => VirtualKey.F9,
|
||||
XK_F10 => VirtualKey.F10,
|
||||
XK_F11 => VirtualKey.F11,
|
||||
XK_F12 => VirtualKey.F12,
|
||||
NativeMethods.XK_F1 => VirtualKey.F1,
|
||||
NativeMethods.XK_F2 => VirtualKey.F2,
|
||||
NativeMethods.XK_F3 => VirtualKey.F3,
|
||||
NativeMethods.XK_F4 => VirtualKey.F4,
|
||||
NativeMethods.XK_F5 => VirtualKey.F5,
|
||||
NativeMethods.XK_F6 => VirtualKey.F6,
|
||||
NativeMethods.XK_F7 => VirtualKey.F7,
|
||||
NativeMethods.XK_F8 => VirtualKey.F8,
|
||||
NativeMethods.XK_F9 => VirtualKey.F9,
|
||||
NativeMethods.XK_F10 => VirtualKey.F10,
|
||||
NativeMethods.XK_F11 => VirtualKey.F11,
|
||||
NativeMethods.XK_F12 => VirtualKey.F12,
|
||||
_ => default
|
||||
};
|
||||
|
||||
return virtualKey != default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the application window is currently focused.
|
||||
/// </summary>
|
||||
private bool IsAppWindowFocused()
|
||||
{
|
||||
if (this.controlDisplay == nint.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lazily get app window ID if not yet cached
|
||||
if (this.appWindowId == 0)
|
||||
{
|
||||
this.appWindowId = FindAppWindowId();
|
||||
if (this.appWindowId == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (NativeMethods.XGetInputFocus(this.controlDisplay, out var focusedWindow, out _) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if focused window is the app window or a descendant of it
|
||||
return this.IsWindowOrDescendant(focusedWindow, this.appWindowId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the focused window is the target window or a descendant (child) of it.
|
||||
/// This handles cases where focus is on a child widget within the main window.
|
||||
/// </summary>
|
||||
private bool IsWindowOrDescendant(ulong focusedWindow, ulong targetWindow)
|
||||
{
|
||||
if (focusedWindow == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var current = focusedWindow;
|
||||
while (current != 0)
|
||||
{
|
||||
if (current == targetWindow)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get parent of current window
|
||||
if (NativeMethods.XQueryTree(this.controlDisplay, current, out var root, out var parent, out var children, out _) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Free the children list if allocated
|
||||
if (children != nint.Zero)
|
||||
{
|
||||
NativeMethods.XFree(children);
|
||||
}
|
||||
|
||||
// Stop if we've reached the root
|
||||
if (parent == root || parent == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
current = parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the X11 window ID for the application using GTK's toplevel window list.
|
||||
/// </summary>
|
||||
private static ulong FindAppWindowId()
|
||||
{
|
||||
var gtkWindow = FindGtkWindowForCurrentProcess();
|
||||
if (gtkWindow == nint.Zero)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var gdkWindow = NativeMethods.gtk_widget_get_window(gtkWindow);
|
||||
if (gdkWindow == nint.Zero)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return NativeMethods.gdk_x11_window_get_xid(gdkWindow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the GTK window belonging to the current process using gtk_window_list_toplevels.
|
||||
/// </summary>
|
||||
private static nint FindGtkWindowForCurrentProcess()
|
||||
{
|
||||
var list = NativeMethods.gtk_window_list_toplevels();
|
||||
if (list == nint.Zero)
|
||||
{
|
||||
return nint.Zero;
|
||||
}
|
||||
|
||||
var current = list;
|
||||
nint result = nint.Zero;
|
||||
|
||||
while (current != nint.Zero)
|
||||
{
|
||||
var window = NativeMethods.G_list_data(current);
|
||||
if (window != nint.Zero && NativeMethods.gtk_widget_get_visible(window))
|
||||
{
|
||||
// Return the first visible toplevel window (Photino creates one main window)
|
||||
result = window;
|
||||
break;
|
||||
}
|
||||
current = NativeMethods.G_list_next(current);
|
||||
}
|
||||
|
||||
NativeMethods.g_list_free(list);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Daybreak.Linux.Utils;
|
||||
using Daybreak.Shared.Services.Window;
|
||||
using Daybreak.Shared.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -15,23 +16,8 @@ namespace Daybreak.Linux.Services.Window;
|
||||
/// This service requires GDK_BACKEND=x11 to be set when running under Wayland compositors.
|
||||
/// Interactive drag/resize may not work properly under XWayland on some compositors.
|
||||
/// </remarks>
|
||||
internal sealed partial class WindowManipulationService(ILogger<WindowManipulationService> logger) : IWindowManipulationService
|
||||
internal sealed class WindowManipulationService(ILogger<WindowManipulationService> logger) : IWindowManipulationService
|
||||
{
|
||||
private const string GtkLib = "libgtk-3.so.0";
|
||||
private const string GdkLib = "libgdk-3.so.0";
|
||||
private const string GlibLib = "libglib-2.0.so.0";
|
||||
private const string X11Lib = "libX11.so.6";
|
||||
|
||||
// GDK window edge constants (from gdktypes.h)
|
||||
private const int GDK_WINDOW_EDGE_NORTH_WEST = 0;
|
||||
private const int GDK_WINDOW_EDGE_NORTH = 1;
|
||||
private const int GDK_WINDOW_EDGE_NORTH_EAST = 2;
|
||||
private const int GDK_WINDOW_EDGE_WEST = 3;
|
||||
private const int GDK_WINDOW_EDGE_EAST = 4;
|
||||
private const int GDK_WINDOW_EDGE_SOUTH_WEST = 5;
|
||||
private const int GDK_WINDOW_EDGE_SOUTH = 6;
|
||||
private const int GDK_WINDOW_EDGE_SOUTH_EAST = 7;
|
||||
|
||||
private readonly ILogger<WindowManipulationService> logger = logger;
|
||||
private readonly int currentPid = Environment.ProcessId;
|
||||
|
||||
@@ -47,16 +33,16 @@ internal sealed partial class WindowManipulationService(ILogger<WindowManipulati
|
||||
return;
|
||||
}
|
||||
|
||||
var display = gdk_window_get_display(gdkWindow);
|
||||
var seat = gdk_display_get_default_seat(display);
|
||||
var device = gdk_seat_get_pointer(seat);
|
||||
var display = NativeMethods.gdk_window_get_display(gdkWindow);
|
||||
var seat = NativeMethods.gdk_display_get_default_seat(display);
|
||||
var device = NativeMethods.gdk_seat_get_pointer(seat);
|
||||
|
||||
// Get root (screen) coordinates, not window-relative coordinates
|
||||
gdk_device_get_position(device, out _, out var rootX, out var rootY);
|
||||
NativeMethods.gdk_device_get_position(device, out _, out var rootX, out var rootY);
|
||||
var timestamp = GetCurrentEventTime();
|
||||
|
||||
scopedLogger.LogDebug("DragWindow: root position=({rootX}, {rootY}), timestamp={timestamp}", rootX, rootY, timestamp);
|
||||
gdk_window_begin_move_drag_for_device(gdkWindow, device, 1, rootX, rootY, timestamp);
|
||||
NativeMethods.gdk_window_begin_move_drag_for_device(gdkWindow, device, 1, rootX, rootY, timestamp);
|
||||
}
|
||||
|
||||
public void ResizeWindow(ResizeDirection direction)
|
||||
@@ -69,18 +55,18 @@ internal sealed partial class WindowManipulationService(ILogger<WindowManipulati
|
||||
return;
|
||||
}
|
||||
|
||||
var display = gdk_window_get_display(gdkWindow);
|
||||
var seat = gdk_display_get_default_seat(display);
|
||||
var device = gdk_seat_get_pointer(seat);
|
||||
var display = NativeMethods.gdk_window_get_display(gdkWindow);
|
||||
var seat = NativeMethods.gdk_display_get_default_seat(display);
|
||||
var device = NativeMethods.gdk_seat_get_pointer(seat);
|
||||
|
||||
// Get root (screen) coordinates, not window-relative coordinates
|
||||
gdk_device_get_position(device, out _, out var rootX, out var rootY);
|
||||
NativeMethods.gdk_device_get_position(device, out _, out var rootX, out var rootY);
|
||||
var timestamp = GetCurrentEventTime();
|
||||
|
||||
var edge = MapResizeDirectionToGdkEdge(direction);
|
||||
scopedLogger.LogDebug("ResizeWindow: edge={edge}, root position=({rootX}, {rootY}), timestamp={timestamp}", edge, rootX, rootY, timestamp);
|
||||
|
||||
gdk_window_begin_resize_drag_for_device(gdkWindow, edge, device, 1, rootX, rootY, timestamp);
|
||||
NativeMethods.gdk_window_begin_resize_drag_for_device(gdkWindow, edge, device, 1, rootX, rootY, timestamp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -100,7 +86,7 @@ internal sealed partial class WindowManipulationService(ILogger<WindowManipulati
|
||||
var gtkWindow = FindGtkWindowForCurrentProcess();
|
||||
if (gtkWindow != nint.Zero)
|
||||
{
|
||||
var gdkWindow = gtk_widget_get_window(gtkWindow);
|
||||
var gdkWindow = NativeMethods.gtk_widget_get_window(gtkWindow);
|
||||
if (gdkWindow != nint.Zero)
|
||||
{
|
||||
this.cachedGdkWindow = gdkWindow;
|
||||
@@ -112,10 +98,10 @@ internal sealed partial class WindowManipulationService(ILogger<WindowManipulati
|
||||
var x11Window = FindX11WindowByPid(this.currentPid);
|
||||
if (x11Window != 0)
|
||||
{
|
||||
var display = gdk_display_get_default();
|
||||
var display = NativeMethods.gdk_display_get_default();
|
||||
if (display != nint.Zero)
|
||||
{
|
||||
var gdkWindow = gdk_x11_window_foreign_new_for_display(display, x11Window);
|
||||
var gdkWindow = NativeMethods.gdk_x11_window_foreign_new_for_display(display, x11Window);
|
||||
if (gdkWindow != nint.Zero)
|
||||
{
|
||||
this.cachedGdkWindow = gdkWindow;
|
||||
@@ -133,7 +119,7 @@ internal sealed partial class WindowManipulationService(ILogger<WindowManipulati
|
||||
/// </summary>
|
||||
private static nint FindGtkWindowForCurrentProcess()
|
||||
{
|
||||
var list = gtk_window_list_toplevels();
|
||||
var list = NativeMethods.gtk_window_list_toplevels();
|
||||
if (list == nint.Zero)
|
||||
{
|
||||
return nint.Zero;
|
||||
@@ -144,17 +130,17 @@ internal sealed partial class WindowManipulationService(ILogger<WindowManipulati
|
||||
|
||||
while (current != nint.Zero)
|
||||
{
|
||||
var window = G_list_data(current);
|
||||
if (window != nint.Zero && gtk_widget_get_visible(window))
|
||||
var window = NativeMethods.G_list_data(current);
|
||||
if (window != nint.Zero && NativeMethods.gtk_widget_get_visible(window))
|
||||
{
|
||||
// Return the first visible toplevel window (Photino creates one main window)
|
||||
result = window;
|
||||
break;
|
||||
}
|
||||
current = G_list_next(current);
|
||||
current = NativeMethods.G_list_next(current);
|
||||
}
|
||||
|
||||
g_list_free(list);
|
||||
NativeMethods.g_list_free(list);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -163,7 +149,7 @@ internal sealed partial class WindowManipulationService(ILogger<WindowManipulati
|
||||
/// </summary>
|
||||
private static ulong FindX11WindowByPid(int targetPid)
|
||||
{
|
||||
var display = XOpenDisplay(nint.Zero);
|
||||
var display = NativeMethods.XOpenDisplay(nint.Zero);
|
||||
if (display == nint.Zero)
|
||||
{
|
||||
return 0;
|
||||
@@ -171,12 +157,12 @@ internal sealed partial class WindowManipulationService(ILogger<WindowManipulati
|
||||
|
||||
try
|
||||
{
|
||||
var rootWindow = XDefaultRootWindow(display);
|
||||
var rootWindow = NativeMethods.XDefaultRootWindow(display);
|
||||
return FindWindowByPidRecursive(display, rootWindow, targetPid);
|
||||
}
|
||||
finally
|
||||
{
|
||||
XCloseDisplay(display);
|
||||
NativeMethods.XCloseDisplay(display);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +180,7 @@ internal sealed partial class WindowManipulationService(ILogger<WindowManipulati
|
||||
}
|
||||
|
||||
// Recurse into children
|
||||
if (XQueryTree(display, window, out _, out _, out var children, out var nChildren) != 0 && children != nint.Zero)
|
||||
if (NativeMethods.XQueryTree(display, window, out _, out _, out var children, out var nChildren) != 0 && children != nint.Zero)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -212,7 +198,7 @@ internal sealed partial class WindowManipulationService(ILogger<WindowManipulati
|
||||
}
|
||||
finally
|
||||
{
|
||||
XFree(children);
|
||||
NativeMethods.XFree(children);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,40 +207,40 @@ internal sealed partial class WindowManipulationService(ILogger<WindowManipulati
|
||||
|
||||
private static int GetWindowPid(nint display, ulong window)
|
||||
{
|
||||
var pidAtom = XInternAtom(display, "_NET_WM_PID", true);
|
||||
var pidAtom = NativeMethods.XInternAtom(display, "_NET_WM_PID", true);
|
||||
if (pidAtom == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var result = XGetWindowProperty(
|
||||
var result = NativeMethods.XGetWindowProperty(
|
||||
display, window, pidAtom, 0, 1, false, 6 /* XA_CARDINAL */,
|
||||
out _, out _, out var nitems, out _, out var data);
|
||||
|
||||
if (result != 0 || nitems == 0 || data == nint.Zero)
|
||||
{
|
||||
if (data != nint.Zero) XFree(data);
|
||||
if (data != nint.Zero) NativeMethods.XFree(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
var pid = Marshal.ReadInt32(data);
|
||||
XFree(data);
|
||||
NativeMethods.XFree(data);
|
||||
return pid;
|
||||
}
|
||||
|
||||
private static bool HasWmState(nint display, ulong window)
|
||||
{
|
||||
var wmStateAtom = XInternAtom(display, "WM_STATE", true);
|
||||
var wmStateAtom = NativeMethods.XInternAtom(display, "WM_STATE", true);
|
||||
if (wmStateAtom == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = XGetWindowProperty(
|
||||
var result = NativeMethods.XGetWindowProperty(
|
||||
display, window, wmStateAtom, 0, 0, false, 0 /* AnyPropertyType */,
|
||||
out var actualType, out _, out _, out _, out var data);
|
||||
|
||||
if (data != nint.Zero) XFree(data);
|
||||
if (data != nint.Zero) NativeMethods.XFree(data);
|
||||
return result == 0 && actualType != 0;
|
||||
}
|
||||
|
||||
@@ -262,141 +248,29 @@ internal sealed partial class WindowManipulationService(ILogger<WindowManipulati
|
||||
{
|
||||
return direction switch
|
||||
{
|
||||
ResizeDirection.Left => GDK_WINDOW_EDGE_WEST,
|
||||
ResizeDirection.Right => GDK_WINDOW_EDGE_EAST,
|
||||
ResizeDirection.Top => GDK_WINDOW_EDGE_NORTH,
|
||||
ResizeDirection.TopLeft => GDK_WINDOW_EDGE_NORTH_WEST,
|
||||
ResizeDirection.TopRight => GDK_WINDOW_EDGE_NORTH_EAST,
|
||||
ResizeDirection.Bottom => GDK_WINDOW_EDGE_SOUTH,
|
||||
ResizeDirection.BottomLeft => GDK_WINDOW_EDGE_SOUTH_WEST,
|
||||
ResizeDirection.BottomRight => GDK_WINDOW_EDGE_SOUTH_EAST,
|
||||
_ => GDK_WINDOW_EDGE_SOUTH_EAST
|
||||
ResizeDirection.Left => NativeMethods.GDK_WINDOW_EDGE_WEST,
|
||||
ResizeDirection.Right => NativeMethods.GDK_WINDOW_EDGE_EAST,
|
||||
ResizeDirection.Top => NativeMethods.GDK_WINDOW_EDGE_NORTH,
|
||||
ResizeDirection.TopLeft => NativeMethods.GDK_WINDOW_EDGE_NORTH_WEST,
|
||||
ResizeDirection.TopRight => NativeMethods.GDK_WINDOW_EDGE_NORTH_EAST,
|
||||
ResizeDirection.Bottom => NativeMethods.GDK_WINDOW_EDGE_SOUTH,
|
||||
ResizeDirection.BottomLeft => NativeMethods.GDK_WINDOW_EDGE_SOUTH_WEST,
|
||||
ResizeDirection.BottomRight => NativeMethods.GDK_WINDOW_EDGE_SOUTH_EAST,
|
||||
_ => NativeMethods.GDK_WINDOW_EDGE_SOUTH_EAST
|
||||
};
|
||||
}
|
||||
|
||||
private static uint GetCurrentEventTime()
|
||||
{
|
||||
var currentEvent = gtk_get_current_event();
|
||||
var currentEvent = NativeMethods.gtk_get_current_event();
|
||||
if (currentEvent != nint.Zero)
|
||||
{
|
||||
var time = gdk_event_get_time(currentEvent);
|
||||
gdk_event_free(currentEvent);
|
||||
var time = NativeMethods.gdk_event_get_time(currentEvent);
|
||||
NativeMethods.gdk_event_free(currentEvent);
|
||||
return time;
|
||||
}
|
||||
|
||||
// GDK_CURRENT_TIME = 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
// GTK3 P/Invoke declarations
|
||||
[LibraryImport(GtkLib, EntryPoint = "gtk_widget_get_window")]
|
||||
private static partial nint gtk_widget_get_window(nint widget);
|
||||
|
||||
[LibraryImport(GtkLib, EntryPoint = "gtk_widget_get_visible")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool gtk_widget_get_visible(nint widget);
|
||||
|
||||
[LibraryImport(GtkLib, EntryPoint = "gtk_get_current_event")]
|
||||
private static partial nint gtk_get_current_event();
|
||||
|
||||
[LibraryImport(GtkLib, EntryPoint = "gtk_window_list_toplevels")]
|
||||
private static partial nint gtk_window_list_toplevels();
|
||||
|
||||
// GLib P/Invoke declarations
|
||||
[LibraryImport(GlibLib, EntryPoint = "g_list_free")]
|
||||
private static partial void g_list_free(nint list);
|
||||
|
||||
private static nint G_list_data(nint list) =>
|
||||
list != nint.Zero ? Marshal.ReadIntPtr(list) : nint.Zero;
|
||||
|
||||
private static nint G_list_next(nint list) =>
|
||||
list != nint.Zero ? Marshal.ReadIntPtr(list + nint.Size) : nint.Zero;
|
||||
|
||||
// GDK3 P/Invoke declarations
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_display_get_default")]
|
||||
private static partial nint gdk_display_get_default();
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_window_get_display")]
|
||||
private static partial nint gdk_window_get_display(nint window);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_display_get_default_seat")]
|
||||
private static partial nint gdk_display_get_default_seat(nint display);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_seat_get_pointer")]
|
||||
private static partial nint gdk_seat_get_pointer(nint seat);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_device_get_position")]
|
||||
private static partial void gdk_device_get_position(
|
||||
nint device,
|
||||
out nint screen,
|
||||
out int x,
|
||||
out int y);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_window_begin_move_drag_for_device")]
|
||||
private static partial void gdk_window_begin_move_drag_for_device(
|
||||
nint window,
|
||||
nint device,
|
||||
int button,
|
||||
int rootX,
|
||||
int rootY,
|
||||
uint timestamp);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_window_begin_resize_drag_for_device")]
|
||||
private static partial void gdk_window_begin_resize_drag_for_device(
|
||||
nint window,
|
||||
int edge,
|
||||
nint device,
|
||||
int button,
|
||||
int rootX,
|
||||
int rootY,
|
||||
uint timestamp);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_event_get_time")]
|
||||
private static partial uint gdk_event_get_time(nint gdkEvent);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_event_free")]
|
||||
private static partial void gdk_event_free(nint gdkEvent);
|
||||
|
||||
[LibraryImport("libgdk-3.so.0", EntryPoint = "gdk_x11_window_foreign_new_for_display")]
|
||||
private static partial nint gdk_x11_window_foreign_new_for_display(nint display, ulong window);
|
||||
|
||||
// X11 P/Invoke declarations
|
||||
[LibraryImport(X11Lib, EntryPoint = "XOpenDisplay")]
|
||||
private static partial nint XOpenDisplay(nint displayName);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XCloseDisplay")]
|
||||
private static partial int XCloseDisplay(nint display);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XDefaultRootWindow")]
|
||||
private static partial ulong XDefaultRootWindow(nint display);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XQueryTree")]
|
||||
private static partial int XQueryTree(
|
||||
nint display,
|
||||
ulong window,
|
||||
out ulong rootReturn,
|
||||
out ulong parentReturn,
|
||||
out nint childrenReturn,
|
||||
out uint nChildrenReturn);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XFree")]
|
||||
private static partial int XFree(nint data);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XInternAtom", StringMarshalling = StringMarshalling.Utf8)]
|
||||
private static partial ulong XInternAtom(nint display, string atomName, [MarshalAs(UnmanagedType.Bool)] bool onlyIfExists);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XGetWindowProperty")]
|
||||
private static partial int XGetWindowProperty(
|
||||
nint display,
|
||||
ulong window,
|
||||
ulong property,
|
||||
long longOffset,
|
||||
long longLength,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool delete,
|
||||
ulong reqType,
|
||||
out ulong actualTypeReturn,
|
||||
out int actualFormatReturn,
|
||||
out ulong nitemsReturn,
|
||||
out ulong bytesAfterReturn,
|
||||
out nint propReturn);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Daybreak.Linux.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// Linux-specific native method declarations for X11, GTK3, GDK3, GLib, and XRecord APIs.
|
||||
/// </summary>
|
||||
public static partial class NativeMethods
|
||||
{
|
||||
#region Library Names
|
||||
|
||||
public const string X11Lib = "libX11.so.6";
|
||||
public const string XtstLib = "libXtst.so.6";
|
||||
public const string GtkLib = "libgtk-3.so.0";
|
||||
public const string GdkLib = "libgdk-3.so.0";
|
||||
public const string GlibLib = "libglib-2.0.so.0";
|
||||
|
||||
#endregion
|
||||
|
||||
#region X11 Constants
|
||||
|
||||
// X11 event types
|
||||
public const int KeyPress = 2;
|
||||
public const int KeyRelease = 3;
|
||||
|
||||
// XRecord constants
|
||||
public const int XRecordFromServer = 0;
|
||||
public const int XRecordAllClients = 3;
|
||||
|
||||
// XK key codes (from X11/keysymdef.h)
|
||||
public const uint XK_F1 = 0xffbe;
|
||||
public const uint XK_F2 = 0xffbf;
|
||||
public const uint XK_F3 = 0xffc0;
|
||||
public const uint XK_F4 = 0xffc1;
|
||||
public const uint XK_F5 = 0xffc2;
|
||||
public const uint XK_F6 = 0xffc3;
|
||||
public const uint XK_F7 = 0xffc4;
|
||||
public const uint XK_F8 = 0xffc5;
|
||||
public const uint XK_F9 = 0xffc6;
|
||||
public const uint XK_F10 = 0xffc7;
|
||||
public const uint XK_F11 = 0xffc8;
|
||||
public const uint XK_F12 = 0xffc9;
|
||||
|
||||
#endregion
|
||||
|
||||
#region GDK Constants
|
||||
|
||||
// GDK window edge constants (from gdktypes.h)
|
||||
public const int GDK_WINDOW_EDGE_NORTH_WEST = 0;
|
||||
public const int GDK_WINDOW_EDGE_NORTH = 1;
|
||||
public const int GDK_WINDOW_EDGE_NORTH_EAST = 2;
|
||||
public const int GDK_WINDOW_EDGE_WEST = 3;
|
||||
public const int GDK_WINDOW_EDGE_EAST = 4;
|
||||
public const int GDK_WINDOW_EDGE_SOUTH_WEST = 5;
|
||||
public const int GDK_WINDOW_EDGE_SOUTH = 6;
|
||||
public const int GDK_WINDOW_EDGE_SOUTH_EAST = 7;
|
||||
|
||||
#endregion
|
||||
|
||||
#region XRecord Structures
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XRecordRange
|
||||
{
|
||||
public XRecordRange8 core_requests;
|
||||
public XRecordRange8 core_replies;
|
||||
public XRecordExtRange ext_requests;
|
||||
public XRecordExtRange ext_replies;
|
||||
public XRecordRange8 delivered_events;
|
||||
public XRecordRange8 device_events;
|
||||
public XRecordRange8 errors;
|
||||
public byte client_started;
|
||||
public byte client_died;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XRecordRange8
|
||||
{
|
||||
public byte first;
|
||||
public byte last;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XRecordExtRange
|
||||
{
|
||||
public XRecordRange16 ext_major;
|
||||
public XRecordRange8 ext_minor;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XRecordRange16
|
||||
{
|
||||
public ushort first;
|
||||
public ushort last;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XRecordInterceptData
|
||||
{
|
||||
public nint id_base;
|
||||
public nint server_time;
|
||||
public nint client_seq;
|
||||
public int category;
|
||||
public byte client_swapped;
|
||||
private byte pad1;
|
||||
private byte pad2;
|
||||
private byte pad3;
|
||||
public nint data;
|
||||
public uint data_len;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delegates
|
||||
|
||||
/// <summary>
|
||||
/// Callback delegate for XRecordEnableContext.
|
||||
/// </summary>
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void XRecordInterceptProc(nint closure, ref XRecordInterceptData recorded_data);
|
||||
|
||||
#endregion
|
||||
|
||||
#region X11 P/Invoke
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XOpenDisplay")]
|
||||
public static partial nint XOpenDisplay(nint display_name);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XCloseDisplay")]
|
||||
public static partial int XCloseDisplay(nint display);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XFlush")]
|
||||
public static partial int XFlush(nint display);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XKeycodeToKeysym")]
|
||||
public static partial uint XKeycodeToKeysym(nint display, byte keycode, int index);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XFree")]
|
||||
public static partial int XFree(nint data);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XGetInputFocus")]
|
||||
public static partial int XGetInputFocus(nint display, out ulong focus_return, out int revert_to_return);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XQueryTree")]
|
||||
public static partial int XQueryTree(
|
||||
nint display,
|
||||
ulong window,
|
||||
out ulong root_return,
|
||||
out ulong parent_return,
|
||||
out nint children_return,
|
||||
out uint nchildren_return);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XDefaultRootWindow")]
|
||||
public static partial ulong XDefaultRootWindow(nint display);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XInternAtom", StringMarshalling = StringMarshalling.Utf8)]
|
||||
public static partial ulong XInternAtom(nint display, string atomName, [MarshalAs(UnmanagedType.Bool)] bool onlyIfExists);
|
||||
|
||||
[LibraryImport(X11Lib, EntryPoint = "XGetWindowProperty")]
|
||||
public static partial int XGetWindowProperty(
|
||||
nint display,
|
||||
ulong window,
|
||||
ulong property,
|
||||
long longOffset,
|
||||
long longLength,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool delete,
|
||||
ulong reqType,
|
||||
out ulong actualTypeReturn,
|
||||
out int actualFormatReturn,
|
||||
out ulong nitemsReturn,
|
||||
out ulong bytesAfterReturn,
|
||||
out nint propReturn);
|
||||
|
||||
#endregion
|
||||
|
||||
#region XRecord P/Invoke
|
||||
|
||||
[LibraryImport(XtstLib, EntryPoint = "XRecordQueryVersion")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool XRecordQueryVersion(nint display, out int major, out int minor);
|
||||
|
||||
[LibraryImport(XtstLib, EntryPoint = "XRecordAllocRange")]
|
||||
public static partial nint XRecordAllocRange();
|
||||
|
||||
[LibraryImport(XtstLib, EntryPoint = "XRecordCreateContext")]
|
||||
public static partial ulong XRecordCreateContext(
|
||||
nint display,
|
||||
int flags,
|
||||
nint[] client_specs,
|
||||
int nclients,
|
||||
nint[] ranges,
|
||||
int nranges);
|
||||
|
||||
[LibraryImport(XtstLib, EntryPoint = "XRecordEnableContext")]
|
||||
public static partial int XRecordEnableContext(
|
||||
nint display,
|
||||
ulong context,
|
||||
XRecordInterceptProc callback,
|
||||
nint closure);
|
||||
|
||||
[LibraryImport(XtstLib, EntryPoint = "XRecordDisableContext")]
|
||||
public static partial int XRecordDisableContext(nint display, ulong context);
|
||||
|
||||
[LibraryImport(XtstLib, EntryPoint = "XRecordFreeContext")]
|
||||
public static partial int XRecordFreeContext(nint display, ulong context);
|
||||
|
||||
#endregion
|
||||
|
||||
#region GTK3 P/Invoke
|
||||
|
||||
[LibraryImport(GtkLib, EntryPoint = "gtk_widget_get_window")]
|
||||
public static partial nint gtk_widget_get_window(nint widget);
|
||||
|
||||
[LibraryImport(GtkLib, EntryPoint = "gtk_widget_get_visible")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool gtk_widget_get_visible(nint widget);
|
||||
|
||||
[LibraryImport(GtkLib, EntryPoint = "gtk_get_current_event")]
|
||||
public static partial nint gtk_get_current_event();
|
||||
|
||||
[LibraryImport(GtkLib, EntryPoint = "gtk_window_list_toplevels")]
|
||||
public static partial nint gtk_window_list_toplevels();
|
||||
|
||||
#endregion
|
||||
|
||||
#region GLib P/Invoke
|
||||
|
||||
[LibraryImport(GlibLib, EntryPoint = "g_list_free")]
|
||||
public static partial void g_list_free(nint list);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data pointer from a GList node.
|
||||
/// </summary>
|
||||
public static nint G_list_data(nint list) =>
|
||||
list != nint.Zero ? Marshal.ReadIntPtr(list) : nint.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next node pointer from a GList node.
|
||||
/// </summary>
|
||||
public static nint G_list_next(nint list) =>
|
||||
list != nint.Zero ? Marshal.ReadIntPtr(list + nint.Size) : nint.Zero;
|
||||
|
||||
#endregion
|
||||
|
||||
#region GDK3 P/Invoke
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_display_get_default")]
|
||||
public static partial nint gdk_display_get_default();
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_window_get_display")]
|
||||
public static partial nint gdk_window_get_display(nint window);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_display_get_default_seat")]
|
||||
public static partial nint gdk_display_get_default_seat(nint display);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_seat_get_pointer")]
|
||||
public static partial nint gdk_seat_get_pointer(nint seat);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_device_get_position")]
|
||||
public static partial void gdk_device_get_position(
|
||||
nint device,
|
||||
out nint screen,
|
||||
out int x,
|
||||
out int y);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_window_begin_move_drag_for_device")]
|
||||
public static partial void gdk_window_begin_move_drag_for_device(
|
||||
nint window,
|
||||
nint device,
|
||||
int button,
|
||||
int rootX,
|
||||
int rootY,
|
||||
uint timestamp);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_window_begin_resize_drag_for_device")]
|
||||
public static partial void gdk_window_begin_resize_drag_for_device(
|
||||
nint window,
|
||||
int edge,
|
||||
nint device,
|
||||
int button,
|
||||
int rootX,
|
||||
int rootY,
|
||||
uint timestamp);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_event_get_time")]
|
||||
public static partial uint gdk_event_get_time(nint gdkEvent);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_event_free")]
|
||||
public static partial void gdk_event_free(nint gdkEvent);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_x11_window_foreign_new_for_display")]
|
||||
public static partial nint gdk_x11_window_foreign_new_for_display(nint display, ulong window);
|
||||
|
||||
[LibraryImport(GdkLib, EntryPoint = "gdk_x11_window_get_xid")]
|
||||
public static partial ulong gdk_x11_window_get_xid(nint gdkWindow);
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user