diff --git a/frame/layershell/exclusion_zone_calculation.md b/frame/layershell/exclusion_zone_calculation.md new file mode 100644 index 000000000..160a1b9b9 --- /dev/null +++ b/frame/layershell/exclusion_zone_calculation.md @@ -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混合坐标系统,确保在所有屏幕配置和缩放比例下都能准确计算独占区域。 diff --git a/frame/layershell/x11dlayershellemulation.cpp b/frame/layershell/x11dlayershellemulation.cpp index 582a1c934..162243068 100644 --- a/frame/layershell/x11dlayershellemulation.cpp +++ b/frame/layershell/x11dlayershellemulation.cpp @@ -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(currentScreen->geometry().x() + m_dlayerShellWindow->exclusionZone() * scaleFactor); + strut_partial.left_start_y = static_cast(m_window->geometry().y()); + strut_partial.left_end_y = static_cast(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(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(currentScreen->geometry().width() * scaleFactor); + + // 计算到桌面右边界的物理距离 + int distanceToDesktopRightPhysical = desktopRightBoundaryPhysical - currentScreenRightPhysical; + + // 独占区域 = 到桌面右边界的物理距离 + 任务栏物理宽度 + strut_partial.right = static_cast(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(m_window->geometry().y()); + strut_partial.right_end_y = static_cast(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(currentScreen->geometry().y() + m_dlayerShellWindow->exclusionZone() * scaleFactor); + strut_partial.top_start_x = static_cast(m_window->geometry().x()); + strut_partial.top_end_x = static_cast(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(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(screenRect.width() * scaleFactor); + int currentLeftPhysical = currentRect.x(); + int currentRightPhysical = currentRect.x() + static_cast(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(screen->geometry().height() * scaleFactor); + if (desktopBottomBoundaryPhysical < screenBottomPhysical) + desktopBottomBoundaryPhysical = screenBottomPhysical; + } + + // 计算物理距离,然后转换为逻辑像素用于后续计算 + int distancePhysical = desktopBottomBoundaryPhysical - currentScreenBottomPhysical; + belowScreensHeight = static_cast(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(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(m_window->geometry().x()); + strut_partial.bottom_end_x = static_cast(m_window->geometry().x() + m_window->geometry().width() * scaleFactor); } qCDebug(layershell) << "update exclusion zone, winId:" << m_window->winId()