Skip to content

Commit f4e82cf

Browse files
authored
chore: Re-implement the calculation method of exclusion zone (#1227)
Consider multiple screen combination styles Log: as title Pms: TASK-380785
1 parent 4966741 commit f4e82cf

File tree

2 files changed

+279
-26
lines changed

2 files changed

+279
-26
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# DDE-Shell 独占区域计算规则
2+
3+
## 概述
4+
5+
独占区域(Exclusion Zone)用于向窗口管理器声明面板/任务栏需要保留的屏幕空间,确保其他窗口不会覆盖在任务栏上。
6+
7+
## 关键概念
8+
9+
### Qt混合坐标系统
10+
- **X/Y坐标**: 物理像素坐标(不需要缩放转换)
11+
- **宽度/高度**: 逻辑像素尺寸(需要乘以缩放系数转换为物理像素)
12+
13+
### 计算公式
14+
```
15+
独占区域 = 屏幕边界距离(物理像素) + 任务栏尺寸(物理像素)
16+
物理像素 = 逻辑像素 × 缩放系数
17+
```
18+
19+
## 布局示意图
20+
21+
### 垂直布局(1K屏上方 + 2K屏下方,2倍缩放)
22+
```
23+
实际拼接布局(控制中心的示意图):
24+
25+
┌──────────┐
26+
│ 1K屏 │
27+
┌┴──────────┤──────────
28+
│ 2K屏 │
29+
│ │
30+
└──────────────────────┘
31+
32+
屏幕坐标信息:
33+
• 1K屏幕: (265, 0, 960, 540) - 物理区域: (265,0) 到 (2185,540)
34+
• 2K屏幕: (0, 1080, 1280, 720) - 物理区域: (0,1080) 到 (2560,1800)
35+
36+
假设:任务栏停靠屏幕下边缘的时候,1倍缩放高度为40
37+
38+
任务栏在1K屏右侧计算:
39+
- 1K屏物理右边界: 265 + (960×2) = 2185
40+
- 桌面物理右边界: 为2k最大宽度 = 2560
41+
- 距离: 2560 - 2185 = 375
42+
- 独占区域: 375 + 80 = 455
43+
```
44+
45+
### 水平布局(2K屏左侧 + 1K屏右侧,下边缘对齐,2倍缩放)
46+
```
47+
实际拼接布局(控制中心的示意图):
48+
49+
┌───────────┬
50+
│ │
51+
│ 2K屏 │
52+
│ │
53+
│ ┌─────────────────┤
54+
│ │ 1K屏 │
55+
│ │ │
56+
└───────────┴─────────────────┘
57+
58+
屏幕坐标信息:
59+
• 2K屏幕: (0, 0, 1280, 720) - 物理区域: (0,0) 到 (2560,1440)
60+
• 1K屏幕: (2560, 360, 960, 540) - 物理区域: (2560,360) 到 (4480,1440)
61+
62+
假设:任务栏停靠屏幕下边缘的时候,1倍缩放高度为40
63+
64+
任务栏在2K屏下边计算:
65+
- 2K屏物理底边: 0 + (720×2) = 1440
66+
- 1K屏物理底边: 360 + (540×2) = 1440
67+
- 桌面物理底边: max(1440, 1440) = 1440
68+
- 距离: 1440 - 1440 = 0(下边缘对齐)
69+
- 独占区域: 0 + 80 = 80
70+
```
71+
72+
## 各锚定类型计算规则
73+
74+
### AnchorLeft(左侧)
75+
```cpp
76+
strut_partial.left = screenX + exclusionZone × scaleFactor
77+
```
78+
- **screenX**: 屏幕X坐标(物理像素)
79+
- **示例**: 1K屏(265,0) 左侧任务栏 = 265 + (40×2) = 345
80+
81+
### AnchorRight(右侧)
82+
```cpp
83+
desktopRightBoundary = max(所有屏幕的物理右边界)
84+
currentScreenRight = screenX + screenWidth × scaleFactor
85+
distance = desktopRightBoundary - currentScreenRight
86+
strut_partial.right = distance + exclusionZone × scaleFactor
87+
```
88+
- **复杂度**: 需计算到整个桌面右边界的距离
89+
- **示例**: 1K屏(265,0) 右侧任务栏 = (2560-2185) + 80 = 455
90+
91+
### AnchorTop(上边)
92+
```cpp
93+
strut_partial.top = screenY + exclusionZone × scaleFactor
94+
```
95+
- **screenY**: 屏幕Y坐标(物理像素)
96+
- **示例**: 1K屏(265,0) 上边任务栏 = 0 + (40×2) = 80
97+
98+
### AnchorBottom(下边)
99+
```cpp
100+
// 优先查找下方重叠屏幕
101+
if (找到下方屏幕) {
102+
strut_partial.bottom = belowScreensHeight × scaleFactor + exclusionZone × scaleFactor
103+
} else {
104+
// 回退算法:计算到桌面底边的距离
105+
desktopBottomBoundary = max(所有屏幕的物理底边界)
106+
distance = desktopBottomBoundary - currentScreenBottom
107+
strut_partial.bottom = distance + exclusionZone × scaleFactor
108+
}
109+
```
110+
- **垂直布局示例**: 1K屏(265,0) 下边任务栏 = (720×2) + 80 = 1520
111+
- **水平布局示例**: 2K屏(0,0) 下边任务栏 = 0 + 80 = 80
112+
113+
## 关键特性
114+
115+
### 布局兼容性
116+
-**垂直布局**: 屏幕上下排列
117+
-**水平布局**: 屏幕左右排列
118+
-**混合布局**: 复杂屏幕排列
119+
-**单屏配置**: 独占区域仅为任务栏尺寸
120+
121+
### 缩放支持
122+
-**1.0x**: 无缩放
123+
-**1.5x**: 高DPI显示器
124+
-**2.0x**: 超高DPI显示器
125+
-**任意比例**: 自动适应
126+
127+
### 边界处理
128+
- **智能检测**: 自动识别屏幕位置关系
129+
- **重叠判断**: 准确计算屏幕间的重叠区域
130+
- **回退机制**: 处理特殊配置和边界情况
131+
132+
## 坐标系统示例
133+
134+
### 实际系统配置
135+
```
136+
QScreen::geometry() 返回值(垂直布局示例):
137+
- 1K屏幕: (265, 0, 960, 540) // X/Y物理坐标,宽度/高度逻辑像素
138+
- 2K屏幕: (0, 1080, 1280, 720) // X/Y物理坐标,宽度/高度逻辑像素
139+
140+
转换为完整物理坐标区域:
141+
- 1K屏幕: 物理区域 (265, 0) 到 (2185, 540)
142+
- 2K屏幕: 物理区域 (0, 1080) 到 (2560, 1800)
143+
144+
QScreen::geometry() 返回值(水平布局示例):
145+
- 2K屏幕: (0, 0, 1280, 720) // X/Y物理坐标,宽度/高度逻辑像素
146+
- 1K屏幕: (2560, 360, 960, 540) // X/Y物理坐标,宽度/高度逻辑像素
147+
148+
转换为完整物理坐标区域:
149+
- 2K屏幕: 物理区域 (0, 0) 到 (2560, 1440)
150+
- 1K屏幕: 物理区域 (2560, 360) 到 (4480, 1440)
151+
```
152+
153+
### 关键公式验证
154+
```
155+
用户验证公式(1K屏(265,0)右侧任务栏,任务栏宽度40逻辑像素):
156+
2560 - 265 - 1920 + 80 = 455 ✓
157+
158+
算法计算过程:
159+
- 1K屏左上角坐标: (265, 0)
160+
- 桌面右边界: 2560
161+
- 1K屏右边界: 265 + (960×2) = 2185
162+
- 距离: 2560 - 2185 = 375
163+
- 独占区域: 375 + (40×2) = 455 ✓
164+
```
165+
166+
## 调试信息
167+
168+
### 简化日志格式
169+
```
170+
假设任务栏尺寸:40逻辑像素
171+
172+
AnchorLeft: screen.x=265 exclusionZone=40 result=345
173+
AnchorRight: desktopRightBoundary=2560 currentScreenRight=2185 distance=375 result=455
174+
AnchorTop: screen.y=0 exclusionZone=40 result=80
175+
AnchorBottom: belowScreensHeight=720 exclusionZone=40 result=1520
176+
```
177+
178+
### 最终汇总
179+
```
180+
示例输出(基于任务栏40逻辑像素):
181+
update exclusion zone, winId:123456, (left, right, top, bottom) 345 455 80 1520
182+
```
183+
184+
---
185+
186+
**注意**: 本算法完全支持Qt混合坐标系统,确保在所有屏幕配置和缩放比例下都能准确计算独占区域。

frame/layershell/x11dlayershellemulation.cpp

Lines changed: 93 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -165,38 +165,105 @@ void LayerShellEmulation::onExclusionZoneChanged()
165165
xcb_ewmh_wm_strut_partial_t strut_partial;
166166
memset(&strut_partial, 0, sizeof(xcb_ewmh_wm_strut_partial_t));
167167
auto anchors = m_dlayerShellWindow->anchors();
168-
QRect rect = m_window->screen()->geometry();
169-
if ((anchors == DLayerShellWindow::AnchorLeft) || (anchors ^ DLayerShellWindow::AnchorLeft) == (DLayerShellWindow::AnchorTop | DLayerShellWindow::AnchorBottom)) {
170-
strut_partial.left = rect.x() + (m_dlayerShellWindow->exclusionZone()) * scaleFactor;
171-
strut_partial.left_start_y = rect.y();
172-
strut_partial.left_end_y = rect.y() + m_window->height();
168+
QScreen *currentScreen = m_window->screen();
169+
if ((anchors == DLayerShellWindow::AnchorLeft) || (anchors ^ DLayerShellWindow::AnchorLeft) == (DLayerShellWindow::AnchorTop | DLayerShellWindow::AnchorBottom)) {
170+
// 计算独占区域:屏幕X坐标 + 任务栏物理宽度
171+
// 注意:QScreen::geometry().x() 已经是设备无关像素,不需要缩放
172+
// 只有exclusionZone需要转换为物理像素
173+
strut_partial.left = static_cast<uint32_t>(currentScreen->geometry().x() + m_dlayerShellWindow->exclusionZone() * scaleFactor);
174+
strut_partial.left_start_y = static_cast<uint32_t>(m_window->geometry().y());
175+
strut_partial.left_end_y = static_cast<uint32_t>(m_window->geometry().y() + m_window->geometry().height() * scaleFactor);
176+
177+
qCDebug(layershell) << "AnchorLeft: screen.x=" << currentScreen->geometry().x() << "exclusionZone=" << m_dlayerShellWindow->exclusionZone()
178+
<< "result=" << strut_partial.left;
173179
} else if ((anchors == DLayerShellWindow::AnchorRight) || (anchors ^ DLayerShellWindow::AnchorRight) == (DLayerShellWindow::AnchorTop | DLayerShellWindow::AnchorBottom)) {
174-
int boundary = 0;
180+
// 找到桌面的最右边界(物理像素)
181+
// 注意:QScreen::geometry()返回混合坐标系统 - X/Y是物理像素,宽度/高度是逻辑像素
182+
int desktopRightBoundaryPhysical = 0;
175183
for (auto screen : qApp->screens()) {
176-
int right = screen->geometry().x()/scaleFactor + screen->geometry().width();
177-
if (boundary < right)
178-
boundary = right;
184+
// 屏幕物理右边界 = X坐标(物理) + 宽度(逻辑) * 缩放系数
185+
int screenRightPhysical = screen->geometry().x() + static_cast<int>(screen->geometry().width() * scaleFactor);
186+
if (desktopRightBoundaryPhysical < screenRightPhysical)
187+
desktopRightBoundaryPhysical = screenRightPhysical;
179188
}
180-
strut_partial.right = (boundary - rect.right() + m_dlayerShellWindow->exclusionZone()) * scaleFactor;
181-
strut_partial.right_start_y = rect.y();
182-
strut_partial.right_end_y = rect.y() + m_window->height();
183-
} else if ((anchors == DLayerShellWindow::AnchorTop) || (anchors ^ DLayerShellWindow::AnchorTop) == (DLayerShellWindow::AnchorLeft | DLayerShellWindow::AnchorRight)) {
184-
strut_partial.top = rect.y() + (m_dlayerShellWindow->exclusionZone()) * scaleFactor;
185-
strut_partial.top_start_x = rect.x();
186-
strut_partial.top_end_x = rect.x() + m_window->width();
189+
190+
// 计算当前屏幕物理右边界
191+
int currentScreenRightPhysical = currentScreen->geometry().x() + static_cast<int>(currentScreen->geometry().width() * scaleFactor);
192+
193+
// 计算到桌面右边界的物理距离
194+
int distanceToDesktopRightPhysical = desktopRightBoundaryPhysical - currentScreenRightPhysical;
195+
196+
// 独占区域 = 到桌面右边界的物理距离 + 任务栏物理宽度
197+
strut_partial.right = static_cast<uint32_t>(distanceToDesktopRightPhysical + m_dlayerShellWindow->exclusionZone() * scaleFactor);
198+
199+
qCDebug(layershell) << "AnchorRight: desktopRightBoundary=" << desktopRightBoundaryPhysical << "currentScreenRight=" << currentScreenRightPhysical
200+
<< "distance=" << distanceToDesktopRightPhysical << "result=" << strut_partial.right;
201+
202+
strut_partial.right_start_y = static_cast<uint32_t>(m_window->geometry().y());
203+
strut_partial.right_end_y = static_cast<uint32_t>(m_window->geometry().y() + m_window->geometry().height() * scaleFactor);
204+
} else if ((anchors == DLayerShellWindow::AnchorTop) || (anchors ^ DLayerShellWindow::AnchorTop) == (DLayerShellWindow::AnchorLeft | DLayerShellWindow::AnchorRight)) {
205+
// 计算独占区域:屏幕Y坐标 + 任务栏物理高度
206+
// 注意:QScreen::geometry().y() 已经是设备无关像素,不需要缩放
207+
// 只有exclusionZone需要转换为物理像素
208+
strut_partial.top = static_cast<uint32_t>(currentScreen->geometry().y() + m_dlayerShellWindow->exclusionZone() * scaleFactor);
209+
strut_partial.top_start_x = static_cast<uint32_t>(m_window->geometry().x());
210+
strut_partial.top_end_x = static_cast<uint32_t>(m_window->geometry().x() + m_window->geometry().width() * scaleFactor);
211+
212+
qCDebug(layershell) << "AnchorTop: screen.y=" << currentScreen->geometry().y() << "exclusionZone=" << m_dlayerShellWindow->exclusionZone()
213+
<< "result=" << strut_partial.top;
187214
} else if ((anchors == DLayerShellWindow::AnchorBottom) || (anchors ^ DLayerShellWindow::AnchorBottom) == (DLayerShellWindow::AnchorLeft | DLayerShellWindow::AnchorRight)) {
188-
// Note: In the X environment,
189-
// the upper screen's exclusive zone spans across the entire lower screen when there are multiple screens,
190-
// but there is no issue.
191-
int boundary = 0;
215+
// 计算当前屏幕的物理底边界(修正混合坐标系统)
216+
int currentScreenBottomPhysical = currentScreen->geometry().y() + static_cast<int>(currentScreen->geometry().height() * scaleFactor);
217+
218+
// 查找紧邻下方的屏幕,支持垂直布局
219+
// 算法:找到在当前屏幕下方且与当前屏幕水平重叠的屏幕
220+
int belowScreensHeight = 0;
221+
QRect currentRect = currentScreen->geometry();
192222
for (auto screen : qApp->screens()) {
193-
int botton = screen->geometry().y()/scaleFactor + screen->geometry().height();
194-
if (boundary < botton)
195-
boundary = botton;
223+
if (screen == currentScreen)
224+
continue;
225+
QRect screenRect = screen->geometry();
226+
227+
// 检查屏幕是否在当前屏幕下方(使用物理坐标)
228+
if (screenRect.y() >= currentScreenBottomPhysical) {
229+
// 检查是否有水平重叠(支持垂直布局)
230+
// 使用物理坐标计算重叠
231+
int screenLeftPhysical = screenRect.x();
232+
int screenRightPhysical = screenRect.x() + static_cast<int>(screenRect.width() * scaleFactor);
233+
int currentLeftPhysical = currentRect.x();
234+
int currentRightPhysical = currentRect.x() + static_cast<int>(currentRect.width() * scaleFactor);
235+
bool hasHorizontalOverlap = (screenLeftPhysical < currentRightPhysical && screenRightPhysical > currentLeftPhysical);
236+
if (hasHorizontalOverlap) {
237+
// 累加下方屏幕的高度(逻辑像素)
238+
belowScreensHeight += screenRect.height();
239+
qCDebug(layershell) << "Found below screen:" << screenRect.height() << "px";
240+
}
241+
}
242+
}
243+
244+
// 如果没有找到下方屏幕,使用修正的回退算法(支持水平布局)
245+
if (belowScreensHeight == 0) {
246+
// 找到桌面最底边的物理边界
247+
int desktopBottomBoundaryPhysical = 0;
248+
for (auto screen : qApp->screens()) {
249+
int screenBottomPhysical = screen->geometry().y() + static_cast<int>(screen->geometry().height() * scaleFactor);
250+
if (desktopBottomBoundaryPhysical < screenBottomPhysical)
251+
desktopBottomBoundaryPhysical = screenBottomPhysical;
252+
}
253+
254+
// 计算物理距离,然后转换为逻辑像素用于后续计算
255+
int distancePhysical = desktopBottomBoundaryPhysical - currentScreenBottomPhysical;
256+
belowScreensHeight = static_cast<int>(distancePhysical / scaleFactor);
196257
}
197-
strut_partial.bottom = (boundary - rect.bottom() + m_dlayerShellWindow->exclusionZone()) * scaleFactor;
198-
strut_partial.bottom_start_x = rect.x();
199-
strut_partial.bottom_end_x = rect.x() + m_window->width();
258+
259+
// 独占区域 = 下方区域物理高度 + 任务栏物理高度
260+
strut_partial.bottom = static_cast<uint32_t>(belowScreensHeight * scaleFactor + m_dlayerShellWindow->exclusionZone() * scaleFactor);
261+
262+
qCDebug(layershell) << "AnchorBottom: belowScreensHeight=" << belowScreensHeight << "exclusionZone=" << m_dlayerShellWindow->exclusionZone()
263+
<< "result=" << strut_partial.bottom;
264+
265+
strut_partial.bottom_start_x = static_cast<uint32_t>(m_window->geometry().x());
266+
strut_partial.bottom_end_x = static_cast<uint32_t>(m_window->geometry().x() + m_window->geometry().width() * scaleFactor);
200267
}
201268

202269
qCDebug(layershell) << "update exclusion zone, winId:" << m_window->winId()

0 commit comments

Comments
 (0)