Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions frame/layershell/exclusion_zone_calculation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# DDE-Shell 独占区域计算规则

## 概述

独占区域(Exclusion Zone)用于向窗口管理器声明面板/任务栏需要保留的屏幕空间,确保其他窗口不会覆盖在任务栏上。

## 关键概念

### Qt混合坐标系统
- **X/Y坐标**: 物理像素坐标(不需要缩放转换)
- **宽度/高度**: 逻辑像素尺寸(需要乘以缩放系数转换为物理像素)

### 计算公式
```
独占区域 = 屏幕边界距离(物理像素) + 任务栏尺寸(物理像素)
物理像素 = 逻辑像素 × 缩放系数
```

## 布局示意图

### 垂直布局(1K屏上方 + 2K屏下方,2倍缩放)
```
实际拼接布局(控制中心的示意图):

┌──────────┐
│ 1K屏 │
┌┴──────────┤──────────
│ 2K屏 │
│ │
└──────────────────────┘

屏幕坐标信息:
• 1K屏幕: (265, 0, 960, 540) - 物理区域: (265,0) 到 (2185,540)
• 2K屏幕: (0, 1080, 1280, 720) - 物理区域: (0,1080) 到 (2560,1800)

假设:任务栏停靠屏幕下边缘的时候,1倍缩放高度为40

任务栏在1K屏右侧计算:
- 1K屏物理右边界: 265 + (960×2) = 2185
- 桌面物理右边界: 为2k最大宽度 = 2560
- 距离: 2560 - 2185 = 375
- 独占区域: 375 + 80 = 455
```

### 水平布局(2K屏左侧 + 1K屏右侧,下边缘对齐,2倍缩放)
```
实际拼接布局(控制中心的示意图):

┌───────────┬
│ │
│ 2K屏 │
│ │
│ ┌─────────────────┤
│ │ 1K屏 │
│ │ │
└───────────┴─────────────────┘

屏幕坐标信息:
• 2K屏幕: (0, 0, 1280, 720) - 物理区域: (0,0) 到 (2560,1440)
• 1K屏幕: (2560, 360, 960, 540) - 物理区域: (2560,360) 到 (4480,1440)

假设:任务栏停靠屏幕下边缘的时候,1倍缩放高度为40

任务栏在2K屏下边计算:
- 2K屏物理底边: 0 + (720×2) = 1440
- 1K屏物理底边: 360 + (540×2) = 1440
- 桌面物理底边: max(1440, 1440) = 1440
- 距离: 1440 - 1440 = 0(下边缘对齐)
- 独占区域: 0 + 80 = 80
```

## 各锚定类型计算规则

### AnchorLeft(左侧)
```cpp
strut_partial.left = screenX + exclusionZone × scaleFactor
```
- **screenX**: 屏幕X坐标(物理像素)
- **示例**: 1K屏(265,0) 左侧任务栏 = 265 + (40×2) = 345

### AnchorRight(右侧)
```cpp
desktopRightBoundary = max(所有屏幕的物理右边界)
currentScreenRight = screenX + screenWidth × scaleFactor
distance = desktopRightBoundary - currentScreenRight
strut_partial.right = distance + exclusionZone × scaleFactor
```
- **复杂度**: 需计算到整个桌面右边界的距离
- **示例**: 1K屏(265,0) 右侧任务栏 = (2560-2185) + 80 = 455

### AnchorTop(上边)
```cpp
strut_partial.top = screenY + exclusionZone × scaleFactor
```
- **screenY**: 屏幕Y坐标(物理像素)
- **示例**: 1K屏(265,0) 上边任务栏 = 0 + (40×2) = 80

### AnchorBottom(下边)
```cpp
// 优先查找下方重叠屏幕
if (找到下方屏幕) {
strut_partial.bottom = belowScreensHeight × scaleFactor + exclusionZone × scaleFactor
} else {
// 回退算法:计算到桌面底边的距离
desktopBottomBoundary = max(所有屏幕的物理底边界)
distance = desktopBottomBoundary - currentScreenBottom
strut_partial.bottom = distance + exclusionZone × scaleFactor
}
```
- **垂直布局示例**: 1K屏(265,0) 下边任务栏 = (720×2) + 80 = 1520
- **水平布局示例**: 2K屏(0,0) 下边任务栏 = 0 + 80 = 80

## 关键特性

### 布局兼容性
- ✅ **垂直布局**: 屏幕上下排列
- ✅ **水平布局**: 屏幕左右排列
- ✅ **混合布局**: 复杂屏幕排列
- ✅ **单屏配置**: 独占区域仅为任务栏尺寸

### 缩放支持
- ✅ **1.0x**: 无缩放
- ✅ **1.5x**: 高DPI显示器
- ✅ **2.0x**: 超高DPI显示器
- ✅ **任意比例**: 自动适应

### 边界处理
- **智能检测**: 自动识别屏幕位置关系
- **重叠判断**: 准确计算屏幕间的重叠区域
- **回退机制**: 处理特殊配置和边界情况

## 坐标系统示例

### 实际系统配置
```
QScreen::geometry() 返回值(垂直布局示例):
- 1K屏幕: (265, 0, 960, 540) // X/Y物理坐标,宽度/高度逻辑像素
- 2K屏幕: (0, 1080, 1280, 720) // X/Y物理坐标,宽度/高度逻辑像素

转换为完整物理坐标区域:
- 1K屏幕: 物理区域 (265, 0) 到 (2185, 540)
- 2K屏幕: 物理区域 (0, 1080) 到 (2560, 1800)

QScreen::geometry() 返回值(水平布局示例):
- 2K屏幕: (0, 0, 1280, 720) // X/Y物理坐标,宽度/高度逻辑像素
- 1K屏幕: (2560, 360, 960, 540) // X/Y物理坐标,宽度/高度逻辑像素

转换为完整物理坐标区域:
- 2K屏幕: 物理区域 (0, 0) 到 (2560, 1440)
- 1K屏幕: 物理区域 (2560, 360) 到 (4480, 1440)
```

### 关键公式验证
```
用户验证公式(1K屏(265,0)右侧任务栏,任务栏宽度40逻辑像素):
2560 - 265 - 1920 + 80 = 455 ✓

算法计算过程:
- 1K屏左上角坐标: (265, 0)
- 桌面右边界: 2560
- 1K屏右边界: 265 + (960×2) = 2185
- 距离: 2560 - 2185 = 375
- 独占区域: 375 + (40×2) = 455 ✓
```

## 调试信息

### 简化日志格式
```
假设任务栏尺寸:40逻辑像素

AnchorLeft: screen.x=265 exclusionZone=40 result=345
AnchorRight: desktopRightBoundary=2560 currentScreenRight=2185 distance=375 result=455
AnchorTop: screen.y=0 exclusionZone=40 result=80
AnchorBottom: belowScreensHeight=720 exclusionZone=40 result=1520
```

### 最终汇总
```
示例输出(基于任务栏40逻辑像素):
update exclusion zone, winId:123456, (left, right, top, bottom) 345 455 80 1520
```

---

**注意**: 本算法完全支持Qt混合坐标系统,确保在所有屏幕配置和缩放比例下都能准确计算独占区域。
119 changes: 93 additions & 26 deletions frame/layershell/x11dlayershellemulation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,38 +165,105 @@ void LayerShellEmulation::onExclusionZoneChanged()
xcb_ewmh_wm_strut_partial_t strut_partial;
memset(&strut_partial, 0, sizeof(xcb_ewmh_wm_strut_partial_t));
auto anchors = m_dlayerShellWindow->anchors();
QRect rect = m_window->screen()->geometry();
if ((anchors == DLayerShellWindow::AnchorLeft) || (anchors ^ DLayerShellWindow::AnchorLeft) == (DLayerShellWindow::AnchorTop | DLayerShellWindow::AnchorBottom)) {
strut_partial.left = rect.x() + (m_dlayerShellWindow->exclusionZone()) * scaleFactor;
strut_partial.left_start_y = rect.y();
strut_partial.left_end_y = rect.y() + m_window->height();
QScreen *currentScreen = m_window->screen();
if ((anchors == DLayerShellWindow::AnchorLeft) || (anchors ^ DLayerShellWindow::AnchorLeft) == (DLayerShellWindow::AnchorTop | DLayerShellWindow::AnchorBottom)) {
// 计算独占区域:屏幕X坐标 + 任务栏物理宽度
// 注意:QScreen::geometry().x() 已经是设备无关像素,不需要缩放
// 只有exclusionZone需要转换为物理像素
strut_partial.left = static_cast<uint32_t>(currentScreen->geometry().x() + m_dlayerShellWindow->exclusionZone() * scaleFactor);
strut_partial.left_start_y = static_cast<uint32_t>(m_window->geometry().y());
strut_partial.left_end_y = static_cast<uint32_t>(m_window->geometry().y() + m_window->geometry().height() * scaleFactor);

qCDebug(layershell) << "AnchorLeft: screen.x=" << currentScreen->geometry().x() << "exclusionZone=" << m_dlayerShellWindow->exclusionZone()
<< "result=" << strut_partial.left;
} else if ((anchors == DLayerShellWindow::AnchorRight) || (anchors ^ DLayerShellWindow::AnchorRight) == (DLayerShellWindow::AnchorTop | DLayerShellWindow::AnchorBottom)) {
int boundary = 0;
// 找到桌面的最右边界(物理像素)
// 注意:QScreen::geometry()返回混合坐标系统 - X/Y是物理像素,宽度/高度是逻辑像素
int desktopRightBoundaryPhysical = 0;
for (auto screen : qApp->screens()) {
int right = screen->geometry().x()/scaleFactor + screen->geometry().width();
if (boundary < right)
boundary = right;
// 屏幕物理右边界 = X坐标(物理) + 宽度(逻辑) * 缩放系数
int screenRightPhysical = screen->geometry().x() + static_cast<int>(screen->geometry().width() * scaleFactor);
if (desktopRightBoundaryPhysical < screenRightPhysical)
desktopRightBoundaryPhysical = screenRightPhysical;
}
strut_partial.right = (boundary - rect.right() + m_dlayerShellWindow->exclusionZone()) * scaleFactor;
strut_partial.right_start_y = rect.y();
strut_partial.right_end_y = rect.y() + m_window->height();
} else if ((anchors == DLayerShellWindow::AnchorTop) || (anchors ^ DLayerShellWindow::AnchorTop) == (DLayerShellWindow::AnchorLeft | DLayerShellWindow::AnchorRight)) {
strut_partial.top = rect.y() + (m_dlayerShellWindow->exclusionZone()) * scaleFactor;
strut_partial.top_start_x = rect.x();
strut_partial.top_end_x = rect.x() + m_window->width();

// 计算当前屏幕物理右边界
int currentScreenRightPhysical = currentScreen->geometry().x() + static_cast<int>(currentScreen->geometry().width() * scaleFactor);

// 计算到桌面右边界的物理距离
int distanceToDesktopRightPhysical = desktopRightBoundaryPhysical - currentScreenRightPhysical;

// 独占区域 = 到桌面右边界的物理距离 + 任务栏物理宽度
strut_partial.right = static_cast<uint32_t>(distanceToDesktopRightPhysical + m_dlayerShellWindow->exclusionZone() * scaleFactor);

qCDebug(layershell) << "AnchorRight: desktopRightBoundary=" << desktopRightBoundaryPhysical << "currentScreenRight=" << currentScreenRightPhysical
<< "distance=" << distanceToDesktopRightPhysical << "result=" << strut_partial.right;

strut_partial.right_start_y = static_cast<uint32_t>(m_window->geometry().y());
strut_partial.right_end_y = static_cast<uint32_t>(m_window->geometry().y() + m_window->geometry().height() * scaleFactor);
} else if ((anchors == DLayerShellWindow::AnchorTop) || (anchors ^ DLayerShellWindow::AnchorTop) == (DLayerShellWindow::AnchorLeft | DLayerShellWindow::AnchorRight)) {
// 计算独占区域:屏幕Y坐标 + 任务栏物理高度
// 注意:QScreen::geometry().y() 已经是设备无关像素,不需要缩放
// 只有exclusionZone需要转换为物理像素
strut_partial.top = static_cast<uint32_t>(currentScreen->geometry().y() + m_dlayerShellWindow->exclusionZone() * scaleFactor);
strut_partial.top_start_x = static_cast<uint32_t>(m_window->geometry().x());
strut_partial.top_end_x = static_cast<uint32_t>(m_window->geometry().x() + m_window->geometry().width() * scaleFactor);

qCDebug(layershell) << "AnchorTop: screen.y=" << currentScreen->geometry().y() << "exclusionZone=" << m_dlayerShellWindow->exclusionZone()
<< "result=" << strut_partial.top;
} else if ((anchors == DLayerShellWindow::AnchorBottom) || (anchors ^ DLayerShellWindow::AnchorBottom) == (DLayerShellWindow::AnchorLeft | DLayerShellWindow::AnchorRight)) {
// Note: In the X environment,
// the upper screen's exclusive zone spans across the entire lower screen when there are multiple screens,
// but there is no issue.
int boundary = 0;
// 计算当前屏幕的物理底边界(修正混合坐标系统)
int currentScreenBottomPhysical = currentScreen->geometry().y() + static_cast<int>(currentScreen->geometry().height() * scaleFactor);

// 查找紧邻下方的屏幕,支持垂直布局
// 算法:找到在当前屏幕下方且与当前屏幕水平重叠的屏幕
int belowScreensHeight = 0;
QRect currentRect = currentScreen->geometry();
for (auto screen : qApp->screens()) {
int botton = screen->geometry().y()/scaleFactor + screen->geometry().height();
if (boundary < botton)
boundary = botton;
if (screen == currentScreen)
continue;
QRect screenRect = screen->geometry();

// 检查屏幕是否在当前屏幕下方(使用物理坐标)
if (screenRect.y() >= currentScreenBottomPhysical) {
// 检查是否有水平重叠(支持垂直布局)
// 使用物理坐标计算重叠
int screenLeftPhysical = screenRect.x();
int screenRightPhysical = screenRect.x() + static_cast<int>(screenRect.width() * scaleFactor);
int currentLeftPhysical = currentRect.x();
int currentRightPhysical = currentRect.x() + static_cast<int>(currentRect.width() * scaleFactor);
bool hasHorizontalOverlap = (screenLeftPhysical < currentRightPhysical && screenRightPhysical > currentLeftPhysical);
if (hasHorizontalOverlap) {
// 累加下方屏幕的高度(逻辑像素)
belowScreensHeight += screenRect.height();
qCDebug(layershell) << "Found below screen:" << screenRect.height() << "px";
}
}
}

// 如果没有找到下方屏幕,使用修正的回退算法(支持水平布局)
if (belowScreensHeight == 0) {
// 找到桌面最底边的物理边界
int desktopBottomBoundaryPhysical = 0;
for (auto screen : qApp->screens()) {
int screenBottomPhysical = screen->geometry().y() + static_cast<int>(screen->geometry().height() * scaleFactor);
if (desktopBottomBoundaryPhysical < screenBottomPhysical)
desktopBottomBoundaryPhysical = screenBottomPhysical;
}

// 计算物理距离,然后转换为逻辑像素用于后续计算
int distancePhysical = desktopBottomBoundaryPhysical - currentScreenBottomPhysical;
belowScreensHeight = static_cast<int>(distancePhysical / scaleFactor);
}
strut_partial.bottom = (boundary - rect.bottom() + m_dlayerShellWindow->exclusionZone()) * scaleFactor;
strut_partial.bottom_start_x = rect.x();
strut_partial.bottom_end_x = rect.x() + m_window->width();

// 独占区域 = 下方区域物理高度 + 任务栏物理高度
strut_partial.bottom = static_cast<uint32_t>(belowScreensHeight * scaleFactor + m_dlayerShellWindow->exclusionZone() * scaleFactor);

qCDebug(layershell) << "AnchorBottom: belowScreensHeight=" << belowScreensHeight << "exclusionZone=" << m_dlayerShellWindow->exclusionZone()
<< "result=" << strut_partial.bottom;

strut_partial.bottom_start_x = static_cast<uint32_t>(m_window->geometry().x());
strut_partial.bottom_end_x = static_cast<uint32_t>(m_window->geometry().x() + m_window->geometry().width() * scaleFactor);
Comment on lines +265 to +266
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Potential for overflow or precision loss when casting to uint32_t.

Use a safe rounding method and validate for overflow before casting to uint32_t to prevent data loss.

Suggested change
strut_partial.bottom_start_x = static_cast<uint32_t>(m_window->geometry().x());
strut_partial.bottom_end_x = static_cast<uint32_t>(m_window->geometry().x() + m_window->geometry().width() * scaleFactor);
{
double start_x = m_window->geometry().x();
double end_x = m_window->geometry().x() + m_window->geometry().width() * scaleFactor;
auto safe_cast = [](double value) -> uint32_t {
if (value < 0) {
qWarning() << "Value underflow in bottom_start_x/bottom_end_x:" << value << ", clamping to 0";
return 0;
}
if (value > static_cast<double>(UINT32_MAX)) {
qWarning() << "Value overflow in bottom_start_x/bottom_end_x:" << value << ", clamping to UINT32_MAX";
return UINT32_MAX;
}
return static_cast<uint32_t>(std::round(value));
};
strut_partial.bottom_start_x = safe_cast(start_x);
strut_partial.bottom_end_x = safe_cast(end_x);
}

}

qCDebug(layershell) << "update exclusion zone, winId:" << m_window->winId()
Expand Down
Loading