Hyprland/src/layout/target/WindowTarget.cpp
Skidam 457617b5a3
xwayland: normalize OR geometry to logical coords with force_zero_scaling (#13359)
Fixes X11 popups, tooltips, and menus showing black boxes on scaled monitors with xwayland:force_zero_scaling = 1 #13334
2026-02-25 12:29:12 +00:00

374 lines
14 KiB
C++

#include "WindowTarget.hpp"
#include "../space/Space.hpp"
#include "../algorithm/Algorithm.hpp"
#include "../../protocols/core/Compositor.hpp"
#include "../../config/ConfigManager.hpp"
#include "../../helpers/Monitor.hpp"
#include "../../xwayland/XSurface.hpp"
#include "../../Compositor.hpp"
#include "../../render/Renderer.hpp"
using namespace Layout;
SP<ITarget> CWindowTarget::create(PHLWINDOW w) {
auto target = SP<CWindowTarget>(new CWindowTarget(w));
target->m_self = target;
return target;
}
CWindowTarget::CWindowTarget(PHLWINDOW w) : m_window(w) {
;
}
eTargetType CWindowTarget::type() {
return TARGET_TYPE_WINDOW;
}
void CWindowTarget::setPositionGlobal(const CBox& box) {
ITarget::setPositionGlobal(box);
updatePos();
}
void CWindowTarget::updatePos() {
if (!m_space)
return;
if (fullscreenMode() == FSMODE_FULLSCREEN)
return;
if (floating() && fullscreenMode() != FSMODE_MAXIMIZED) {
m_window->m_position = m_box.pos();
m_window->m_size = m_box.size();
*m_window->m_realPosition = m_box.pos();
*m_window->m_realSize = m_box.size();
m_window->sendWindowSize();
m_window->updateWindowDecos();
return;
}
// Tiled is more complicated.
const auto PMONITOR = m_space->workspace()->m_monitor;
const auto PWORKSPACE = m_space->workspace();
// for gaps outer
const auto MONITOR_WORKAREA = m_space->workArea();
const bool DISPLAYLEFT = STICKS(m_box.x, MONITOR_WORKAREA.x);
const bool DISPLAYRIGHT = STICKS(m_box.x + m_box.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w);
const bool DISPLAYTOP = STICKS(m_box.y, MONITOR_WORKAREA.y);
const bool DISPLAYBOTTOM = STICKS(m_box.y + m_box.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h);
// this is used for scrolling, so that the gaps are correct when a window is the full width and has neighbors
const bool DISPLAYINVERSELEFT = STICKS(m_box.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w);
const bool DISPLAYINVERSERIGHT = STICKS(m_box.x + m_box.w, MONITOR_WORKAREA.x);
// get specific gaps and rules for this workspace,
// if user specified them in config
const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(PWORKSPACE);
if (!validMapped(m_window)) {
if (m_window)
g_layoutManager->removeTarget(m_window->layoutTarget());
return;
}
if (fullscreenMode() == FSMODE_FULLSCREEN)
return;
g_pHyprRenderer->damageWindow(window());
static auto PGAPSINDATA = CConfigValue<Hyprlang::CUSTOMTYPE>("general:gaps_in");
auto* const PGAPSIN = sc<CCssGapData*>((PGAPSINDATA.ptr())->getData());
auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN);
CBox nodeBox = m_box;
nodeBox.round();
m_window->m_size = nodeBox.size();
m_window->m_position = nodeBox.pos();
m_window->updateWindowDecos();
auto calcPos = m_window->m_position;
auto calcSize = m_window->m_size;
const static auto REQUESTEDRATIO = CConfigValue<Hyprlang::VEC2>("layout:single_window_aspect_ratio");
const static auto REQUESTEDRATIOTOLERANCE = CConfigValue<Hyprlang::FLOAT>("layout:single_window_aspect_ratio_tolerance");
Vector2D ratioPadding;
if ((*REQUESTEDRATIO).y != 0 && m_space->algorithm()->tiledTargets() <= 1) {
const Vector2D originalSize = MONITOR_WORKAREA.size();
const double requestedRatio = (*REQUESTEDRATIO).x / (*REQUESTEDRATIO).y;
const double originalRatio = originalSize.x / originalSize.y;
if (requestedRatio > originalRatio) {
double padding = originalSize.y - (originalSize.x / requestedRatio);
if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.y)
ratioPadding = Vector2D{0., padding};
} else if (requestedRatio < originalRatio) {
double padding = originalSize.x - (originalSize.y * requestedRatio);
if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.x)
ratioPadding = Vector2D{padding, 0.};
}
}
const auto GAPOFFSETTOPLEFT = Vector2D(sc<double>(DISPLAYLEFT ? 0 : (DISPLAYINVERSELEFT ? 2 * gapsIn.m_left : gapsIn.m_left)), sc<double>(DISPLAYTOP ? 0 : gapsIn.m_top));
const auto GAPOFFSETBOTTOMRIGHT =
Vector2D(sc<double>(DISPLAYRIGHT ? 0 : (DISPLAYINVERSERIGHT ? 2 * gapsIn.m_right : gapsIn.m_right)), sc<double>(DISPLAYBOTTOM ? 0 : gapsIn.m_bottom));
calcPos = calcPos + GAPOFFSETTOPLEFT + ratioPadding / 2;
calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding;
if (isPseudo()) {
// Calculate pseudo
float scale = 1;
// adjust if doesn't fit
if (m_pseudoSize.x > calcSize.x || m_pseudoSize.y > calcSize.y) {
if (m_pseudoSize.x > calcSize.x)
scale = calcSize.x / m_pseudoSize.x;
if (m_pseudoSize.y * scale > calcSize.y)
scale = calcSize.y / m_pseudoSize.y;
auto DELTA = calcSize - m_pseudoSize * scale;
calcSize = m_pseudoSize * scale;
calcPos = calcPos + DELTA / 2.f; // center
} else {
auto DELTA = calcSize - m_pseudoSize;
calcPos = calcPos + DELTA / 2.f; // center
calcSize = m_pseudoSize;
}
}
const auto RESERVED = m_window->getFullWindowReservedArea();
calcPos = calcPos + RESERVED.topLeft;
calcSize = calcSize - (RESERVED.topLeft + RESERVED.bottomRight);
Vector2D availableSpace = calcSize;
static auto PCLAMP_TILED = CConfigValue<Hyprlang::INT>("misc:size_limits_tiled");
if (*PCLAMP_TILED) {
const auto borderSize = m_window->getRealBorderSize();
Vector2D monitorAvailable = MONITOR_WORKAREA.size() - Vector2D{2.0 * borderSize, 2.0 * borderSize};
Vector2D minSize = m_window->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable);
Vector2D maxSize = m_window->isFullscreen() ? Vector2D{INFINITY, INFINITY} :
m_window->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable);
calcSize = calcSize.clamp(minSize, maxSize);
calcPos += (availableSpace - calcSize) / 2.0;
calcPos.x = std::clamp(calcPos.x, MONITOR_WORKAREA.x + borderSize, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w - calcSize.x - borderSize);
calcPos.y = std::clamp(calcPos.y, MONITOR_WORKAREA.y + borderSize, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h - calcSize.y - borderSize);
}
if (m_window->onSpecialWorkspace() && !m_window->isFullscreen()) {
// if special, we adjust the coords a bit
static auto PSCALEFACTOR = CConfigValue<Hyprlang::FLOAT>("dwindle:special_scale_factor");
CBox wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR};
wb.round(); // avoid rounding mess
*m_window->m_realPosition = wb.pos();
*m_window->m_realSize = wb.size();
} else {
CBox wb = {calcPos, calcSize};
wb.round(); // avoid rounding mess
*m_window->m_realSize = wb.size();
*m_window->m_realPosition = wb.pos();
}
m_window->updateWindowDecos();
}
void CWindowTarget::assignToSpace(const SP<CSpace>& space, std::optional<Vector2D> focalPoint) {
if (!space) {
ITarget::assignToSpace(space, focalPoint);
return;
}
// keep the ref here so that moveToWorkspace doesn't unref the workspace
// and assignToSpace doesn't think this is a new target because space wp is dead
const auto WSREF = space->workspace();
m_window->m_monitor = space->workspace()->m_monitor;
m_window->moveToWorkspace(space->workspace());
// layout and various update fns want the target to already have m_workspace set
ITarget::assignToSpace(space, focalPoint);
m_window->updateToplevel();
m_window->updateWindowDecos();
}
bool CWindowTarget::floating() {
return m_window->m_isFloating;
}
void CWindowTarget::setFloating(bool x) {
if (x == m_window->m_isFloating)
return;
m_window->m_isFloating = x;
m_window->m_pinned = false;
m_window->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FLOATING);
}
Vector2D CWindowTarget::clampSizeForDesired(const Vector2D& size) const {
Vector2D newSize = size;
if (const auto m = m_window->minSize(); m)
newSize = newSize.clamp(*m);
if (const auto m = m_window->maxSize(); m)
newSize = newSize.clamp(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}, *m);
return newSize;
}
std::expected<SGeometryRequested, eGeometryFailure> CWindowTarget::desiredGeometry() {
SGeometryRequested requested;
CBox DESIRED_GEOM = g_pXWaylandManager->getGeometryForWindow(m_window.lock());
const auto PMONITOR = m_window->m_monitor.lock();
requested.size = clampSizeForDesired(DESIRED_GEOM.size());
if (m_window->m_isX11) {
Vector2D xy = {DESIRED_GEOM.x, DESIRED_GEOM.y};
xy = g_pXWaylandManager->xwaylandToWaylandCoords(xy);
requested.pos = xy;
DESIRED_GEOM.x = xy.x;
DESIRED_GEOM.y = xy.y;
}
const auto STOREDSIZE = m_window->m_ruleApplicator->persistentSize().valueOrDefault() ? g_pConfigManager->getStoredFloatingSize(m_window.lock()) : std::nullopt;
if (STOREDSIZE)
requested.size = clampSizeForDesired(*STOREDSIZE);
if (!PMONITOR) {
Log::logger->log(Log::ERR, "{:m} has an invalid monitor in desiredGeometry!", m_window.lock());
return std::unexpected(GEOMETRY_NO_DESIRED);
}
static auto PXWLFORCESCALEZERO = CConfigValue<Hyprlang::INT>("xwayland:force_zero_scaling");
const auto toLogical = [&](SGeometryRequested& req) {
if (m_window->m_isX11 && *PXWLFORCESCALEZERO && PMONITOR)
req.size /= PMONITOR->m_scale;
};
if (DESIRED_GEOM.width <= 2 || DESIRED_GEOM.height <= 2) {
const auto SURFACE = m_window->wlSurface()->resource();
if (SURFACE->m_current.size.x > 5 && SURFACE->m_current.size.y > 5) {
// center on mon and call it a day
requested.pos.reset();
requested.size = clampSizeForDesired(SURFACE->m_current.size);
toLogical(requested);
return requested;
}
if (m_window->m_isX11 && m_window->isX11OverrideRedirect()) {
// check OR windows, they like their shit
const auto SIZE = clampSizeForDesired(m_window->m_xwaylandSurface->m_geometry.w > 0 && m_window->m_xwaylandSurface->m_geometry.h > 0 ?
m_window->m_xwaylandSurface->m_geometry.size() :
Vector2D{600, 400});
if (m_window->m_xwaylandSurface->m_geometry.x != 0 && m_window->m_xwaylandSurface->m_geometry.y != 0) {
requested.size = SIZE;
requested.pos = g_pXWaylandManager->xwaylandToWaylandCoords(m_window->m_xwaylandSurface->m_geometry.pos());
toLogical(requested);
return requested;
}
}
return std::unexpected(m_window->m_isX11 && m_window->isX11OverrideRedirect() ? GEOMETRY_INVALID_DESIRED : GEOMETRY_NO_DESIRED);
}
// TODO: detect a popup in a more consistent way.
if ((DESIRED_GEOM.x == 0 && DESIRED_GEOM.y == 0) || !m_window->m_isX11) {
// middle of parent if available
if (!m_window->m_isX11) {
if (const auto PARENT = m_window->parent(); PARENT) {
const auto POS = PARENT->m_realPosition->goal() + PARENT->m_realSize->goal() / 2.F - DESIRED_GEOM.size() / 2.F;
requested.pos = POS;
}
}
} else {
// if it is, we respect where it wants to put itself, but apply monitor offset if outside
// most of these are popups
Vector2D pos;
if (const auto POPENMON = g_pCompositor->getMonitorFromVector(DESIRED_GEOM.middle()); POPENMON->m_id != PMONITOR->m_id)
pos = Vector2D(DESIRED_GEOM.x, DESIRED_GEOM.y) - POPENMON->m_position + PMONITOR->m_position;
else
pos = Vector2D(DESIRED_GEOM.x, DESIRED_GEOM.y);
requested.pos = pos;
}
if (DESIRED_GEOM.w <= 2 || DESIRED_GEOM.h <= 2)
return std::unexpected(GEOMETRY_NO_DESIRED);
toLogical(requested);
return requested;
}
PHLWINDOW CWindowTarget::window() const {
return m_window.lock();
}
eFullscreenMode CWindowTarget::fullscreenMode() {
return m_window->m_fullscreenState.internal;
}
void CWindowTarget::setFullscreenMode(eFullscreenMode mode) {
if (floating() && m_window->m_fullscreenState.internal == FSMODE_NONE)
rememberFloatingSize(m_box.size());
m_window->m_fullscreenState.internal = mode;
}
std::optional<Vector2D> CWindowTarget::minSize() {
return m_window->minSize();
}
std::optional<Vector2D> CWindowTarget::maxSize() {
return m_window->maxSize();
}
void CWindowTarget::damageEntire() {
g_pHyprRenderer->damageWindow(m_window.lock());
}
void CWindowTarget::warpPositionSize() {
m_window->m_realSize->warp();
m_window->m_realPosition->warp();
m_window->updateWindowDecos();
}
void CWindowTarget::onUpdateSpace() {
if (!space())
return;
m_window->m_monitor = space()->workspace()->m_monitor;
m_window->moveToWorkspace(space()->workspace());
m_window->updateToplevel();
m_window->updateWindowDecos();
}