Files

513 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AtomUI.Native — Linux 平台 API 文档
## 概述
Linux 平台通过 XCB(X C Binding)库调用 X11 协议的 **SHAPE 扩展**,利用输入形状(Input Shape)机制实现窗口级鼠标事件穿透。所有 API 均标注 `[SupportedOSPlatform("linux")]`。
**依赖的原生库:**
| 库 | 说明 |
|----|------|
| `libxcb.so.1` | XCB 核心库(X11 连接、窗口几何查询等) |
| `libxcb-shape.so.0` | XCB SHAPE 扩展库(窗口形状操作) |
---
## 1. 公共扩展方法
### 1.1 SetWindowIgnoreMouseEvents
设置窗口是否忽略(穿透)鼠标输入事件。
```csharp
internal static void SetWindowIgnoreMouseEvents(this WindowBase window, bool flag)
```
**参数:**
| 参数 | 类型 | 说明 |
|------|------|------|
| `window` | `WindowBase` | Avalonia 窗口实例 |
| `flag` | `bool` | `true` 启用穿透,`false` 禁用穿透 |
**异常:**
| 异常类型 | 条件 |
|----------|------|
| `ArgumentException` | 窗口句柄无效(`IntPtr.Zero`) |
| `InvalidOperationException` | XCB 连接失败 |
| `InvalidOperationException` | SHAPE 扩展不可用 |
| `InvalidOperationException` | 禁用穿透时获取窗口几何信息失败 |
**Linux 实现原理:**
利用 X11 SHAPE 扩展的 `Input Shape`(输入形状)概念:
- **启用穿透**:将窗口的输入形状设置为 **空区域**(`rects_len=0, rects=NULL`),所有鼠标事件将穿过该窗口
- **禁用穿透**:将窗口的输入形状恢复为 **整个窗口区域**(一个覆盖全窗口的矩形)
**启用穿透流程:**
```
1. xcb_connect() → 连接 X 服务器
2. IsShapeExtensionAvailable() → 检查 SHAPE 扩展
3. xcb_shape_rectangles_checked(
op=SET, kind=INPUT,
rects_len=0, rects=NULL) → 设置输入形状为空
4. xcb_request_check() → 检查错误
5. xcb_flush() → 刷新请求
6. xcb_disconnect() → 断开连接
```
**禁用穿透流程:**
```
1. xcb_connect() → 连接 X 服务器
2. IsShapeExtensionAvailable() → 检查 SHAPE 扩展
3. xcb_get_geometry() → 获取窗口尺寸
4. 创建 xcb_rectangle_t { 0, 0, width, height }
5. Marshal.AllocHGlobal() → 分配非托管内存
6. xcb_shape_rectangles_checked(
op=SET, kind=INPUT,
rects_len=1, rects=&rect) → 恢复输入形状为全窗口
7. Marshal.FreeHGlobal() → 释放非托管内存
8. xcb_request_check() → 检查错误
9. xcb_flush() → 刷新请求
10. xcb_disconnect() → 断开连接
```
---
### 1.2 IsWindowIgnoreMouseEvents
查询窗口当前是否处于鼠标事件穿透状态。
```csharp
internal static bool IsWindowIgnoreMouseEvents(this WindowBase window)
```
**参数:**
| 参数 | 类型 | 说明 |
|------|------|------|
| `window` | `WindowBase` | Avalonia 窗口实例 |
**返回值:**
| 类型 | 说明 |
|------|------|
| `bool` | `true` 表示窗口正在穿透鼠标事件(输入形状矩形数量为 0) |
**Linux 实现原理:**
查询窗口 Input Shape 中的矩形数量:
```
1. xcb_connect() → 连接 X 服务器
2. IsShapeExtensionAvailable() → 检查 SHAPE 扩展
3. xcb_shape_get_rectangles(kind=INPUT) → 查询输入形状矩形
4. xcb_shape_get_rectangles_rectangles_length() → 获取矩形数量
5. numRects == 0 → true (穿透中)
6. xcb_disconnect() → 断开连接
```
---
## 2. 内部辅助方法
### 2.1 IsShapeExtensionAvailable
检查 X 服务器是否支持 SHAPE 扩展。
```csharp
[SupportedOSPlatform("linux")]
private static bool IsShapeExtensionAvailable(IntPtr connection)
```
| 参数 | 类型 | 说明 |
|------|------|------|
| `connection` | `IntPtr` | XCB 连接指针 |
**返回值:** `true` 表示 SHAPE 扩展可用。
**实现:** 调用 `xcb_shape_query_version` 并检查是否返回有效的版本信息。
---
### 2.2 GetWindowGeometry
获取窗口的几何信息(位置和尺寸)。
```csharp
[SupportedOSPlatform("linux")]
private static IntPtr GetWindowGeometry(IntPtr connection, uint windowId)
```
| 参数 | 类型 | 说明 |
|------|------|------|
| `connection` | `IntPtr` | XCB 连接指针 |
| `windowId` | `uint` | X11 窗口 ID |
**返回值:** 指向 `xcb_get_geometry_reply_t` 的非托管内存指针。调用方负责使用 `Marshal.FreeHGlobal` 释放。
---
## 3. P/Invoke 声明
> 命名空间:`AtomUI.Native.Linux`
> 类:`internal static class WindowUtilsInterop`
> 平台:`[SupportedOSPlatform("linux")]`
### 3.1 XCB 核心函数 (libxcb.so.1)
#### xcb_connect
连接到 X 服务器。
```csharp
[DllImport("libxcb.so.1")]
internal static extern IntPtr xcb_connect(string display, IntPtr screen);
```
| 参数 | 类型 | 说明 |
|------|------|------|
| `display` | `string` | X 显示标识(空字符串使用默认 `$DISPLAY`) |
| `screen` | `IntPtr` | 屏幕号输出指针(可传 `IntPtr.Zero`) |
**返回值:** XCB 连接指针。
---
#### xcb_disconnect
断开 X 服务器连接。
```csharp
[DllImport("libxcb.so.1")]
internal static extern void xcb_disconnect(IntPtr connection);
```
---
#### xcb_connection_has_error
检查连接是否有错误。
```csharp
[DllImport("libxcb.so.1")]
internal static extern int xcb_connection_has_error(IntPtr connection);
```
**返回值:** `0` 表示无错误,非零值表示有错误。
---
#### xcb_flush
刷新待发送的请求到 X 服务器。
```csharp
[DllImport("libxcb.so.1")]
internal static extern int xcb_flush(IntPtr connection);
```
---
#### xcb_request_check
检查已发送请求的错误状态。
```csharp
[DllImport("libxcb.so.1")]
internal static extern IntPtr xcb_request_check(IntPtr connection, xcb_void_cookie_t cookie);
```
**返回值:** 错误指针,`IntPtr.Zero` 表示无错误。
---
#### xcb_get_geometry / xcb_get_geometry_reply
获取窗口几何信息(位置、尺寸、深度等)。
```csharp
[DllImport("libxcb.so.1")]
internal static extern uint xcb_get_geometry(IntPtr connection, uint window);
[DllImport("libxcb.so.1")]
internal static extern IntPtr xcb_get_geometry_reply(IntPtr connection, uint cookie, IntPtr error);
```
---
### 3.2 XCB SHAPE 扩展函数 (libxcb-shape.so.0)
#### xcb_shape_query_version / _reply
查询 SHAPE 扩展版本。
```csharp
[DllImport("libxcb-shape.so.0")]
internal static extern uint xcb_shape_query_version(IntPtr connection);
[DllImport("libxcb-shape.so.0")]
internal static extern IntPtr xcb_shape_query_version_reply(
IntPtr connection, uint cookie, IntPtr error);
```
---
#### xcb_shape_rectangles_checked
设置窗口的形状区域(核心操作)。
```csharp
[DllImport("libxcb-shape.so.0")]
internal static extern xcb_void_cookie_t xcb_shape_rectangles_checked(
IntPtr c,
xcb_shape_op_t operation,
xcb_shape_kind_t destination_kind,
byte ordering,
uint destination_window,
short x_offset,
short y_offset,
uint rectangles_len,
IntPtr rectangles);
```
| 参数 | 类型 | 说明 |
|------|------|------|
| `c` | `IntPtr` | XCB 连接 |
| `operation` | `xcb_shape_op_t` | 形状操作类型(通常使用 `SET`) |
| `destination_kind` | `xcb_shape_kind_t` | 形状类别(`INPUT` 用于鼠标穿透) |
| `ordering` | `byte` | 矩形排序方式 |
| `destination_window` | `uint` | 目标窗口 ID |
| `x_offset` | `short` | X 偏移 |
| `y_offset` | `short` | Y 偏移 |
| `rectangles_len` | `uint` | 矩形数量(`0` = 空区域 = 穿透) |
| `rectangles` | `IntPtr` | 矩形数组指针(`IntPtr.Zero` = 空) |
---
#### xcb_shape_get_rectangles / _reply / _rectangles_length
查询窗口的形状矩形。
```csharp
[DllImport("libxcb-shape.so.0")]
internal static extern uint xcb_shape_get_rectangles(
IntPtr connection, uint window, xcb_shape_kind_t shapeKind);
[DllImport("libxcb-shape.so.0")]
internal static extern IntPtr xcb_shape_get_rectangles_reply(
IntPtr connection, uint cookie, IntPtr error);
[DllImport("libxcb-shape.so.0")]
internal static extern int xcb_shape_get_rectangles_rectangles_length(IntPtr reply);
```
---
### 3.3 结构体定义
#### xcb_connection_t
XCB 连接句柄(不透明类型)。
```csharp
internal struct xcb_connection_t { }
```
#### xcb_window_t
X11 窗口标识符。
```csharp
[StructLayout(LayoutKind.Sequential)]
internal struct xcb_window_t
{
public uint Id;
}
```
#### xcb_void_cookie_t
异步请求的 Cookie,用于后续错误检查。
```csharp
[StructLayout(LayoutKind.Sequential)]
internal struct xcb_void_cookie_t
{
public uint Sequence;
}
```
#### xcb_rectangle_t
矩形区域,用于定义窗口形状。
```csharp
[StructLayout(LayoutKind.Sequential)]
internal struct xcb_rectangle_t
{
public short X; // 左上角 X
public short Y; // 左上角 Y
public ushort Width; // 宽度
public ushort Height; // 高度
}
```
#### xcb_generic_error_t
通用错误信息结构体。
```csharp
[StructLayout(LayoutKind.Sequential)]
internal struct xcb_generic_error_t
{
public byte ResponseType;
public byte ErrorCode; // 错误代码
public ushort Sequence;
public uint ResourceId;
public ushort MinorCode;
public byte MajorCode;
public byte Pad0;
public uint FullSequence;
}
```
#### xcb_get_geometry_reply_t
窗口几何信息。
```csharp
[StructLayout(LayoutKind.Sequential)]
internal struct xcb_get_geometry_reply_t
{
public byte ResponseType;
public byte Depth; // 颜色深度
public ushort Sequence;
public uint Length;
public short X; // 窗口 X 坐标
public short Y; // 窗口 Y 坐标
public ushort Width; // 窗口宽度
public ushort Height; // 窗口高度
public ushort BorderWidth; // 边框宽度
public byte Pad0;
public byte Pad1;
}
```
#### xcb_shape_query_version_reply_t
SHAPE 扩展版本信息。
```csharp
[StructLayout(LayoutKind.Sequential)]
internal struct xcb_shape_query_version_reply_t
{
public byte ResponseType;
public byte Pad0;
public ushort Sequence;
public uint Length;
public ushort MajorVersion; // 主版本号
public ushort MinorVersion; // 次版本号
public uint Pad1;
}
```
#### xcb_shape_get_rectangles_reply_t
SHAPE 矩形查询结果。
```csharp
[StructLayout(LayoutKind.Sequential)]
internal struct xcb_shape_get_rectangles_reply_t
{
public byte ResponseType;
public byte Ordering;
public ushort Sequence;
public uint Length;
public uint RectanglesLen; // 矩形数量
public uint Pad0;
public uint Pad1;
public uint Pad2;
}
```
---
### 3.4 枚举定义
#### xcb_shape_op_t
形状操作类型。
```csharp
internal enum xcb_shape_op_t : byte
{
XCB_SHAPE_SO_SET = 0, // 替换(覆盖现有形状)
XCB_SHAPE_SO_UNION = 1, // 合并
XCB_SHAPE_SO_INTERSECT = 2, // 交集
XCB_SHAPE_SO_SUBTRACT = 3, // 差集
XCB_SHAPE_SO_INVERT = 4 // 反转
}
```
#### xcb_shape_kind_t
形状类别。
```csharp
internal enum xcb_shape_kind_t : byte
{
XCB_SHAPE_SK_BOUNDING = 0, // 边界形状(影响窗口可见区域)
XCB_SHAPE_SK_CLIP = 1, // 裁剪形状(影响子窗口裁剪)
XCB_SHAPE_SK_INPUT = 2 // 输入形状(影响鼠标事件接收区域)⭐
}
```
> ⭐ `XCB_SHAPE_SK_INPUT` 是实现鼠标穿透的关键类别。
#### xcb_clip_ordering_t
裁剪矩形排序方式。
```csharp
internal enum xcb_clip_ordering_t : byte
{
XCB_CLIP_ORDERING_UNSORTED = 0, // 未排序
XCB_CLIP_ORDERING_Y_SORTED = 1, // 按 Y 排序
XCB_CLIP_ORDERING_YX_SORTED = 2, // 按 Y-X 排序
XCB_CLIP_ORDERING_YX_BANDED = 3 // 按 Y-X 带状排序
}
```
---
## 4. 使用示例
```csharp
// 启用窗口鼠标事件穿透
window.SetWindowIgnoreMouseEvents(true);
// 查询是否处于穿透状态
bool isPassThrough = window.IsWindowIgnoreMouseEvents();
// 禁用穿透,恢复正常鼠标交互
window.SetWindowIgnoreMouseEvents(false);
```
---
## 5. 注意事项
1. **SHAPE 扩展依赖** — 实现依赖 `libxcb-shape.so.0`,如果目标系统缺少该库会抛出 `DllNotFoundException`
2. **连接管理** — 每次操作都会创建新的 XCB 连接并在完成后断开(`try/finally` 保证释放),不复用连接
3. **非托管内存安全** — 禁用穿透时通过 `Marshal.AllocHGlobal` 分配的矩形内存在 `try/finally` 中确保释放
4. **Wayland 兼容性** — 当前实现基于 X11/XCB 协议,在纯 Wayland 环境下不可用。Wayland 需要通过 compositor 特定协议实现类似功能
5. **句柄转换** — Avalonia 的窗口句柄(`IntPtr`)通过 `(uint)handle.ToInt64()` 转换为 X11 窗口 ID
6. **错误日志** — SHAPE 操作错误通过 `Console.Error.WriteLine` 输出到标准错误流,不会抛出异常中断执行
7. **SHAPE 扩展版本检查** — 每次操作前通过 `xcb_shape_query_version` 验证扩展可用性,提供优雅的错误报告