layout: rethonk layouts from the ground up (#12890)

Rewrites layouts to be much smaller, and deal with much less annoying
BS. Improves the overall architecture, unifies handling of pseudotiling,
and various other improvements.
This commit is contained in:
Vaxry 2026-02-21 21:30:39 +00:00 committed by GitHub
parent 51f8849e54
commit 723870337f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
82 changed files with 8431 additions and 5527 deletions

View file

@ -3,6 +3,8 @@
#include <hyprutils/animation/AnimatedVariable.hpp>
#include <re2/re2.h>
#include "Group.hpp"
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
#include <sys/types.h>
#include <sys/sysctl.h>
@ -37,12 +39,15 @@
#include "../../helpers/math/Expression.hpp"
#include "../../managers/XWaylandManager.hpp"
#include "../../render/Renderer.hpp"
#include "../../managers/LayoutManager.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../../managers/EventManager.hpp"
#include "../../managers/input/InputManager.hpp"
#include "../../managers/PointerManager.hpp"
#include "../../managers/animation/DesktopAnimationManager.hpp"
#include "../../layout/space/Space.hpp"
#include "../../layout/LayoutManager.hpp"
#include "../../layout/target/WindowTarget.hpp"
#include "../../layout/target/WindowGroupTarget.hpp"
#include <hyprutils/string/String.hpp>
@ -57,6 +62,9 @@ using namespace Desktop::View;
static uint64_t windowIDCounter = 0x18000000;
//
#define COMMA ,
//
PHLWINDOW CWindow::create(SP<CXWaylandSurface> surface) {
PHLWINDOW pWindow = SP<CWindow>(new CWindow(surface));
@ -79,6 +87,8 @@ PHLWINDOW CWindow::create(SP<CXWaylandSurface> surface) {
pWindow->addWindowDeco(makeUnique<CHyprDropShadowDecoration>(pWindow));
pWindow->addWindowDeco(makeUnique<CHyprBorderDecoration>(pWindow));
pWindow->m_target = Layout::CWindowTarget::create(pWindow);
return pWindow;
}
@ -104,6 +114,8 @@ PHLWINDOW CWindow::create(SP<CXDGSurfaceResource> resource) {
pWindow->addWindowDeco(makeUnique<CHyprDropShadowDecoration>(pWindow));
pWindow->addWindowDeco(makeUnique<CHyprBorderDecoration>(pWindow));
pWindow->m_target = Layout::CWindowTarget::create(pWindow);
pWindow->wlSurface()->assign(pWindow->m_xdgSurface->m_surface.lock(), pWindow);
return pWindow;
@ -263,23 +275,24 @@ CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() {
return CBox{sc<int>(POS.x), sc<int>(POS.y), sc<int>(SIZE.x), sc<int>(SIZE.y)};
}
// get work area
const auto WORKAREA = g_pLayoutManager->getCurrentLayout()->workAreaOnWorkspace(m_workspace);
const auto RESERVED = CReservedArea{PMONITOR->logicalBox(), WORKAREA};
// fucker fucking fuck
const auto WORKAREA = m_workspace->m_space->workArea();
const auto& RESERVED = PMONITOR->m_reservedArea;
if (DELTALESSTHAN(POS.y - PMONITOR->m_position.y, RESERVED.top(), 1)) {
POS.y = PMONITOR->m_position.y;
SIZE.y += RESERVED.top();
}
if (DELTALESSTHAN(POS.x - PMONITOR->m_position.x, RESERVED.left(), 1)) {
POS.x = PMONITOR->m_position.x;
if (DELTALESSTHAN(POS.x, WORKAREA.x, 1)) {
POS.x -= RESERVED.left();
SIZE.x += RESERVED.left();
}
if (DELTALESSTHAN(POS.x + SIZE.x - PMONITOR->m_position.x, PMONITOR->m_size.x - RESERVED.right(), 1))
if (DELTALESSTHAN(POS.y, WORKAREA.y, 1)) {
POS.y -= RESERVED.top();
SIZE.y += RESERVED.top();
}
if (DELTALESSTHAN(POS.x + SIZE.x, WORKAREA.x + WORKAREA.width, 1))
SIZE.x += RESERVED.right();
if (DELTALESSTHAN(POS.y + SIZE.y - PMONITOR->m_position.y, PMONITOR->m_size.y - RESERVED.bottom(), 1))
if (DELTALESSTHAN(POS.y + SIZE.y, WORKAREA.y + WORKAREA.height, 1))
SIZE.y += RESERVED.bottom();
return CBox{sc<int>(POS.x), sc<int>(POS.y), sc<int>(SIZE.x), sc<int>(SIZE.y)};
@ -357,14 +370,18 @@ void CWindow::addWindowDeco(UP<IHyprWindowDecoration> deco) {
m_windowDecorations.emplace_back(std::move(deco));
g_pDecorationPositioner->forceRecalcFor(m_self.lock());
updateWindowDecos();
g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock());
if (layoutTarget())
layoutTarget()->recalc();
}
void CWindow::removeWindowDeco(IHyprWindowDecoration* deco) {
m_decosToRemove.push_back(deco);
g_pDecorationPositioner->forceRecalcFor(m_self.lock());
updateWindowDecos();
g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock());
if (layoutTarget())
layoutTarget()->recalc();
}
void CWindow::uncacheWindowDecos() {
@ -495,11 +512,9 @@ void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) {
OLDWORKSPACE->updateWindows();
OLDWORKSPACE->updateWindowData();
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(OLDWORKSPACE->monitorID());
pWorkspace->updateWindows();
pWorkspace->updateWindowData();
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID());
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
@ -591,7 +606,7 @@ void CWindow::onUnmap() {
m_workspace->updateWindows();
m_workspace->updateWindowData();
}
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID());
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
m_workspace.reset();
@ -723,311 +738,6 @@ bool CWindow::hasPopupAt(const Vector2D& pos) {
return popup && popup->wlSurface()->resource();
}
void CWindow::applyGroupRules() {
if ((m_groupRules & GROUP_SET && m_firstMap) || m_groupRules & GROUP_SET_ALWAYS)
createGroup();
if (m_groupData.pNextWindow.lock() && ((m_groupRules & GROUP_LOCK && m_firstMap) || m_groupRules & GROUP_LOCK_ALWAYS))
getGroupHead()->m_groupData.locked = true;
}
void CWindow::createGroup() {
if (m_groupData.deny) {
Log::logger->log(Log::DEBUG, "createGroup: window:{:x},title:{} is denied as a group, ignored", rc<uintptr_t>(this), this->m_title);
return;
}
if (m_groupData.pNextWindow.expired()) {
m_groupData.pNextWindow = m_self;
m_groupData.head = true;
m_groupData.locked = false;
m_groupData.deny = false;
addWindowDeco(makeUnique<CHyprGroupBarDecoration>(m_self.lock()));
if (m_workspace) {
m_workspace->updateWindows();
m_workspace->updateWindowData();
}
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID());
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("1,{:x}", rc<uintptr_t>(this))});
}
m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
}
void CWindow::destroyGroup() {
if (m_groupData.pNextWindow == m_self) {
if (m_groupRules & GROUP_SET_ALWAYS) {
Log::logger->log(Log::DEBUG, "destoryGroup: window:{:x},title:{} has rule [group set always], ignored", rc<uintptr_t>(this), this->m_title);
return;
}
m_groupData.pNextWindow.reset();
m_groupData.head = false;
updateWindowDecos();
if (m_workspace) {
m_workspace->updateWindows();
m_workspace->updateWindowData();
}
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID());
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("0,{:x}", rc<uintptr_t>(this))});
m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
return;
}
std::string addresses;
PHLWINDOW curr = m_self.lock();
std::vector<PHLWINDOW> members;
do {
const auto PLASTWIN = curr;
curr = curr->m_groupData.pNextWindow.lock();
PLASTWIN->m_groupData.pNextWindow.reset();
curr->setHidden(false);
members.push_back(curr);
addresses += std::format("{:x},", rc<uintptr_t>(curr.get()));
} while (curr.get() != this);
for (auto const& w : members) {
if (w->m_groupData.head)
g_pLayoutManager->getCurrentLayout()->onWindowRemoved(curr);
w->m_groupData.head = false;
}
const bool GROUPSLOCKEDPREV = g_pKeybindManager->m_groupsLocked;
g_pKeybindManager->m_groupsLocked = true;
for (auto const& w : members) {
g_pLayoutManager->getCurrentLayout()->onWindowCreated(w);
w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
w->updateWindowDecos();
}
g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV;
if (m_workspace) {
m_workspace->updateWindows();
m_workspace->updateWindowData();
}
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID());
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
if (!addresses.empty())
addresses.pop_back();
m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("0,{}", addresses)});
}
PHLWINDOW CWindow::getGroupHead() {
PHLWINDOW curr = m_self.lock();
while (!curr->m_groupData.head)
curr = curr->m_groupData.pNextWindow.lock();
return curr;
}
PHLWINDOW CWindow::getGroupTail() {
PHLWINDOW curr = m_self.lock();
while (!curr->m_groupData.pNextWindow->m_groupData.head)
curr = curr->m_groupData.pNextWindow.lock();
return curr;
}
PHLWINDOW CWindow::getGroupCurrent() {
PHLWINDOW curr = m_self.lock();
while (curr->isHidden())
curr = curr->m_groupData.pNextWindow.lock();
return curr;
}
int CWindow::getGroupSize() {
int size = 1;
PHLWINDOW curr = m_self.lock();
while (curr->m_groupData.pNextWindow != m_self) {
curr = curr->m_groupData.pNextWindow.lock();
size++;
}
return size;
}
bool CWindow::canBeGroupedInto(PHLWINDOW pWindow) {
static auto ALLOWGROUPMERGE = CConfigValue<Hyprlang::INT>("group:merge_groups_on_drag");
bool isGroup = m_groupData.pNextWindow;
bool disallowDragIntoGroup = g_pInputManager->m_wasDraggingWindow && isGroup && !sc<bool>(*ALLOWGROUPMERGE);
return !g_pKeybindManager->m_groupsLocked // global group lock disengaged
&& ((m_groupRules & GROUP_INVADE && m_firstMap) // window ignore local group locks, or
|| (!pWindow->getGroupHead()->m_groupData.locked // target unlocked
&& !(m_groupData.pNextWindow.lock() && getGroupHead()->m_groupData.locked))) // source unlocked or isn't group
&& !m_groupData.deny // source is not denied entry
&& !(m_groupRules & GROUP_BARRED && m_firstMap) // group rule doesn't prevent adding window
&& !disallowDragIntoGroup; // config allows groups to be merged
}
PHLWINDOW CWindow::getGroupWindowByIndex(int index) {
const int SIZE = getGroupSize();
index = ((index % SIZE) + SIZE) % SIZE;
PHLWINDOW curr = getGroupHead();
while (index > 0) {
curr = curr->m_groupData.pNextWindow.lock();
index--;
}
return curr;
}
bool CWindow::hasInGroup(PHLWINDOW w) {
PHLWINDOW curr = m_groupData.pNextWindow.lock();
while (curr && curr != m_self) {
if (curr == w)
return true;
curr = curr->m_groupData.pNextWindow.lock();
}
return false;
}
void CWindow::setGroupCurrent(PHLWINDOW pWindow) {
PHLWINDOW curr = m_groupData.pNextWindow.lock();
bool isMember = false;
while (curr.get() != this) {
if (curr == pWindow) {
isMember = true;
break;
}
curr = curr->m_groupData.pNextWindow.lock();
}
if (!isMember && pWindow.get() != this)
return;
const auto PCURRENT = getGroupCurrent();
const bool FULLSCREEN = PCURRENT->isFullscreen();
const auto WORKSPACE = PCURRENT->m_workspace;
const auto MODE = PCURRENT->m_fullscreenState.internal;
const auto CURRENTISFOCUS = PCURRENT == Desktop::focusState()->window();
const auto PWINDOWSIZE = PCURRENT->m_realSize->value();
const auto PWINDOWPOS = PCURRENT->m_realPosition->value();
const auto PWINDOWSIZEGOAL = PCURRENT->m_realSize->goal();
const auto PWINDOWPOSGOAL = PCURRENT->m_realPosition->goal();
const auto PWINDOWLASTFLOATINGSIZE = PCURRENT->m_lastFloatingSize;
const auto PWINDOWLASTFLOATINGPOSITION = PCURRENT->m_lastFloatingPosition;
if (FULLSCREEN)
g_pCompositor->setWindowFullscreenInternal(PCURRENT, FSMODE_NONE);
PCURRENT->setHidden(true);
pWindow->setHidden(false); // can remove m_pLastWindow
g_pLayoutManager->getCurrentLayout()->replaceWindowDataWith(PCURRENT, pWindow);
if (PCURRENT->m_isFloating) {
pWindow->m_realPosition->setValueAndWarp(PWINDOWPOSGOAL);
pWindow->m_realSize->setValueAndWarp(PWINDOWSIZEGOAL);
pWindow->sendWindowSize();
}
pWindow->m_realPosition->setValue(PWINDOWPOS);
pWindow->m_realSize->setValue(PWINDOWSIZE);
if (FULLSCREEN)
g_pCompositor->setWindowFullscreenInternal(pWindow, MODE);
pWindow->m_lastFloatingSize = PWINDOWLASTFLOATINGSIZE;
pWindow->m_lastFloatingPosition = PWINDOWLASTFLOATINGPOSITION;
g_pCompositor->updateAllWindowsAnimatedDecorationValues();
if (CURRENTISFOCUS)
Desktop::focusState()->rawWindowFocus(pWindow);
g_pHyprRenderer->damageWindow(pWindow);
pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
pWindow->updateWindowDecos();
}
void CWindow::insertWindowToGroup(PHLWINDOW pWindow) {
const auto BEGINAT = m_self.lock();
const auto ENDAT = m_groupData.pNextWindow.lock();
if (!pWindow->m_groupData.pNextWindow.lock()) {
BEGINAT->m_groupData.pNextWindow = pWindow;
pWindow->m_groupData.pNextWindow = ENDAT;
pWindow->m_groupData.head = false;
pWindow->addWindowDeco(makeUnique<CHyprGroupBarDecoration>(pWindow));
return;
}
const auto SHEAD = pWindow->getGroupHead();
const auto STAIL = pWindow->getGroupTail();
SHEAD->m_groupData.head = false;
BEGINAT->m_groupData.pNextWindow = SHEAD;
STAIL->m_groupData.pNextWindow = ENDAT;
pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
pWindow->updateWindowDecos();
}
PHLWINDOW CWindow::getGroupPrevious() {
PHLWINDOW curr = m_groupData.pNextWindow.lock();
while (curr != m_self && curr->m_groupData.pNextWindow != m_self)
curr = curr->m_groupData.pNextWindow.lock();
return curr;
}
void CWindow::switchWithWindowInGroup(PHLWINDOW pWindow) {
if (!m_groupData.pNextWindow.lock() || !pWindow->m_groupData.pNextWindow.lock())
return;
if (m_groupData.pNextWindow.lock() == pWindow) { // A -> this -> pWindow -> B >> A -> pWindow -> this -> B
getGroupPrevious()->m_groupData.pNextWindow = pWindow;
m_groupData.pNextWindow = pWindow->m_groupData.pNextWindow;
pWindow->m_groupData.pNextWindow = m_self;
} else if (pWindow->m_groupData.pNextWindow == m_self) { // A -> pWindow -> this -> B >> A -> this -> pWindow -> B
pWindow->getGroupPrevious()->m_groupData.pNextWindow = m_self;
pWindow->m_groupData.pNextWindow = m_groupData.pNextWindow;
m_groupData.pNextWindow = pWindow;
} else { // A -> this -> B | C -> pWindow -> D >> A -> pWindow -> B | C -> this -> D
std::swap(m_groupData.pNextWindow, pWindow->m_groupData.pNextWindow);
std::swap(getGroupPrevious()->m_groupData.pNextWindow, pWindow->getGroupPrevious()->m_groupData.pNextWindow);
}
std::swap(m_groupData.head, pWindow->m_groupData.head);
std::swap(m_groupData.locked, pWindow->m_groupData.locked);
pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
pWindow->updateWindowDecos();
}
void CWindow::updateGroupOutputs() {
if (m_groupData.pNextWindow.expired())
return;
PHLWINDOW curr = m_groupData.pNextWindow.lock();
const auto WS = m_workspace;
while (curr.get() != this) {
curr->m_monitor = m_monitor;
curr->moveToWorkspace(WS);
*curr->m_realPosition = m_realPosition->goal();
*curr->m_realSize = m_realSize->goal();
curr = curr->m_groupData.pNextWindow.lock();
}
}
Vector2D CWindow::middle() {
return m_realPosition->goal() + m_realSize->goal() / 2.f;
}
@ -1148,7 +858,7 @@ void CWindow::setAnimationsToMove() {
void CWindow::onWorkspaceAnimUpdate() {
// clip box for animated offsets
if (!m_isFloating || m_pinned || isFullscreen() || m_draggingTiled) {
if (!m_isFloating || m_pinned || isFullscreen()) {
m_floatingOffset = Vector2D(0, 0);
return;
}
@ -1326,7 +1036,7 @@ void CWindow::activate(bool force) {
if (m_isFloating)
g_pCompositor->changeWindowZOrder(m_self.lock(), true);
Desktop::focusState()->fullWindowFocus(m_self.lock());
Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
warpCursor();
}
@ -1378,7 +1088,8 @@ void CWindow::onUpdateMeta() {
if (m_self == Desktop::focusState()->window()) { // if it's the active, let's post an event to update others
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = m_class + "," + m_title});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc<uintptr_t>(this))});
EMIT_HOOK_EVENT("activeWindow", m_self.lock());
// no need for a hook event
}
Log::logger->log(Log::DEBUG, "Window {:x} set title to {}", rc<uintptr_t>(this), m_title);
@ -1392,7 +1103,8 @@ void CWindow::onUpdateMeta() {
if (m_self == Desktop::focusState()->window()) { // if it's the active, let's post an event to update others
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = m_class + "," + m_title});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc<uintptr_t>(this))});
EMIT_HOOK_EVENT("activeWindow", m_self.lock());
// no need for a hook event
}
Log::logger->log(Log::DEBUG, "Window {:x} set class to {}", rc<uintptr_t>(this), m_class);
@ -1472,7 +1184,7 @@ void CWindow::onX11ConfigureRequest(CBox box) {
g_pHyprRenderer->damageWindow(m_self.lock());
if (!m_isFloating || isFullscreen() || g_pInputManager->m_currentlyDraggedWindow == m_self) {
if (!m_isFloating || isFullscreen() || g_layoutManager->dragController()->target() == m_self) {
sendWindowSize(true);
g_pInputManager->refocus();
g_pHyprRenderer->damageWindow(m_self.lock());
@ -1689,20 +1401,17 @@ void CWindow::setContentType(NContentType::eContentType contentType) {
}
void CWindow::deactivateGroupMembers() {
auto curr = getGroupHead();
while (curr) {
if (curr != m_self.lock()) {
if (!m_group)
return;
for (const auto& w : m_group->windows()) {
if (w != m_self.lock()) {
// we don't want to deactivate unfocused xwayland windows
// because X is weird, keep the behavior for wayland windows
// also its not really needed for xwayland windows
// ref: #9760 #9294
if (!curr->m_isX11 && curr->m_xdgSurface && curr->m_xdgSurface->m_toplevel)
curr->m_xdgSurface->m_toplevel->setActive(false);
if (!w->m_isX11 && w->m_xdgSurface && w->m_xdgSurface->m_toplevel)
w->m_xdgSurface->m_toplevel->setActive(false);
}
curr = curr->m_groupData.pNextWindow.lock();
if (curr == getGroupHead())
break;
}
}
@ -1827,21 +1536,13 @@ void CWindow::updateDecorationValues() {
const bool IS_SHADOWED_BY_MODAL = m_xdgSurface && m_xdgSurface->m_toplevel && m_xdgSurface->m_toplevel->anyChildModal();
// border
const auto RENDERDATA = g_pLayoutManager->getCurrentLayout()->requestRenderHints(m_self.lock());
if (RENDERDATA.isBorderGradient)
setBorderColor(*RENDERDATA.borderGradient);
else {
const bool GROUPLOCKED = m_groupData.pNextWindow.lock() ? getGroupHead()->m_groupData.locked : false;
if (m_self == Desktop::focusState()->window()) {
const auto* const ACTIVECOLOR =
!m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);
setBorderColor(m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR));
} else {
const auto* const INACTIVECOLOR =
!m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);
setBorderColor(m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR));
}
const bool GROUPLOCKED = m_group ? m_group->locked() : false;
if (m_self == Desktop::focusState()->window()) {
const auto* const ACTIVECOLOR = !m_group ? (!(m_groupRules & GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL);
setBorderColor(m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR));
} else {
const auto* const INACTIVECOLOR = !m_group ? (!(m_groupRules & GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL);
setBorderColor(m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR));
}
// opacity
@ -1929,6 +1630,7 @@ void CWindow::mapWindow() {
static auto PDIMSTRENGTH = CConfigValue<Hyprlang::FLOAT>("decoration:dim_strength");
static auto PNEWTAKESOVERFS = CConfigValue<Hyprlang::INT>("misc:on_focus_under_fullscreen");
static auto PINITIALWSTRACKING = CConfigValue<Hyprlang::INT>("misc:initial_workspace_tracking");
static auto PAUTOGROUP = CConfigValue<Hyprlang::INT>("group:auto_group");
const auto LAST_FOCUS_WINDOW = Desktop::focusState()->window();
const bool IS_LAST_IN_FS = LAST_FOCUS_WINDOW ? LAST_FOCUS_WINDOW->m_fullscreenState.internal != FSMODE_NONE : false;
@ -2053,8 +1755,8 @@ void CWindow::mapWindow() {
requestedFSMonitor = MONITOR_INVALID;
}
m_isFloating = m_ruleApplicator->static_.floating.value_or(m_isFloating);
m_isPseudotiled = m_ruleApplicator->static_.pseudo.value_or(m_isPseudotiled);
m_isFloating = m_ruleApplicator->static_.floating.value_or(m_isFloating);
m_target->setPseudo(m_ruleApplicator->static_.pseudo.value_or(m_target->isPseudo()));
m_noInitialFocus = m_ruleApplicator->static_.noInitialFocus.value_or(m_noInitialFocus);
m_pinned = m_ruleApplicator->static_.pin.value_or(m_pinned);
@ -2109,7 +1811,7 @@ void CWindow::mapWindow() {
} else if (v == "barred") {
m_groupRules |= Desktop::View::GROUP_BARRED;
} else if (v == "deny") {
m_groupData.deny = true;
m_groupRules |= Desktop::View::GROUP_DENY;
} else if (v == "override") {
// Clear existing rules
m_groupRules = Desktop::View::GROUP_OVERRIDE;
@ -2213,9 +1915,9 @@ void CWindow::mapWindow() {
m_isFloating = true;
if (PWORKSPACE->m_defaultPseudo) {
m_isPseudotiled = true;
CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(m_self.lock());
m_pseudoSize = Vector2D(desiredGeometry.width, desiredGeometry.height);
m_target->setPseudoSize(Vector2D{desiredGeometry.width, desiredGeometry.height});
m_target->setPseudo(true);
}
updateWindowData();
@ -2230,43 +1932,29 @@ void CWindow::mapWindow() {
g_pEventManager->postEvent(SHyprIPCEvent{"openwindow", std::format("{:x},{},{},{}", m_self.lock(), PWORKSPACE->m_name, m_class, m_title)});
EMIT_HOOK_EVENT("openWindowEarly", m_self.lock());
if (*PAUTOGROUP // auto_group enabled
&& Desktop::focusState()->window() // focused window exists
&& canBeGroupedInto(Desktop::focusState()->window()->m_group) // we can group
&& Desktop::focusState()->window()->m_workspace == m_workspace // workspaces match, we're not opening on another ws
&& !isModal() && !(parent() && m_isFloating) && !isX11OverrideRedirect() // not a modal, floating child or X11 OR
) {
// add to group if we are focused on one
Desktop::focusState()->window()->m_group->add(m_self.lock());
} else
g_layoutManager->newTarget(m_target, m_workspace->m_space);
if (!m_group && (m_groupRules & GROUP_SET))
m_group = CGroup::create({m_self});
if (m_isFloating) {
g_pLayoutManager->getCurrentLayout()->onWindowCreated(m_self.lock());
m_createdOverFullscreen = true;
if (!m_ruleApplicator->static_.size.empty()) {
const auto COMPUTED = calculateExpression(m_ruleApplicator->static_.size);
if (!COMPUTED)
Log::logger->log(Log::ERR, "failed to parse {} as an expression", m_ruleApplicator->static_.size);
else {
*m_realSize = *COMPUTED;
setHidden(false);
}
}
if (!m_ruleApplicator->static_.position.empty()) {
const auto COMPUTED = calculateExpression(m_ruleApplicator->static_.position);
if (!COMPUTED)
Log::logger->log(Log::ERR, "failed to parse {} as an expression", m_ruleApplicator->static_.position);
else {
*m_realPosition = *COMPUTED + PMONITOR->m_position;
setHidden(false);
}
}
if (m_ruleApplicator->static_.center.value_or(false)) {
const auto WORKAREA = PMONITOR->logicalBoxMinusReserved();
*m_realPosition = WORKAREA.middle() - m_realSize->goal() / 2.f;
}
// set the pseudo size to the GOAL of our current size
// because the windows are animated on RealSize
m_pseudoSize = m_realSize->goal();
m_target->setPseudoSize(m_realSize->goal());
g_pCompositor->changeWindowZOrder(m_self.lock(), true);
} else {
g_pLayoutManager->getCurrentLayout()->onWindowCreated(m_self.lock());
bool setPseudo = false;
if (!m_ruleApplicator->static_.size.empty()) {
@ -2274,14 +1962,14 @@ void CWindow::mapWindow() {
if (!COMPUTED)
Log::logger->log(Log::ERR, "failed to parse {} as an expression", m_ruleApplicator->static_.size);
else {
setPseudo = true;
m_pseudoSize = *COMPUTED;
setPseudo = true;
m_target->setPseudoSize(*COMPUTED);
setHidden(false);
}
}
if (!setPseudo)
m_pseudoSize = m_realSize->goal() - Vector2D(10, 10);
m_target->setPseudoSize(m_realSize->goal() - Vector2D(10, 10));
}
const auto PFOCUSEDWINDOWPREV = Desktop::focusState()->window();
@ -2311,13 +1999,13 @@ void CWindow::mapWindow() {
(!PFORCEFOCUS || PFORCEFOCUS == m_self.lock()) && !g_pInputManager->isConstrained()) {
// this window should gain focus: if it's grouped, preserve fullscreen state.
const bool SAME_GROUP = hasInGroup(LAST_FOCUS_WINDOW);
const bool SAME_GROUP = m_group && m_group->has(LAST_FOCUS_WINDOW);
if (IS_LAST_IN_FS && SAME_GROUP) {
Desktop::focusState()->rawWindowFocus(m_self.lock());
Desktop::focusState()->rawWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW);
g_pCompositor->setWindowFullscreenInternal(m_self.lock(), LAST_FS_MODE);
} else
Desktop::focusState()->fullWindowFocus(m_self.lock());
Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW);
m_activeInactiveAlpha->setValueAndWarp(*PACTIVEALPHA);
m_dimPercent->setValueAndWarp(m_ruleApplicator->noDim().valueOrDefault() ? 0.f : *PDIMSTRENGTH);
@ -2358,18 +2046,16 @@ void CWindow::mapWindow() {
if (workspaceSilent) {
if (validMapped(PFOCUSEDWINDOWPREV)) {
Desktop::focusState()->rawWindowFocus(PFOCUSEDWINDOWPREV);
Desktop::focusState()->rawWindowFocus(PFOCUSEDWINDOWPREV, FOCUS_REASON_NEW_WINDOW);
PFOCUSEDWINDOWPREV->updateWindowDecos(); // need to for some reason i cba to find out why
} else if (!PFOCUSEDWINDOWPREV)
Desktop::focusState()->rawWindowFocus(nullptr);
Desktop::focusState()->rawWindowFocus(nullptr, FOCUS_REASON_NEW_WINDOW);
}
// swallow
if (SWALLOWER) {
g_pLayoutManager->getCurrentLayout()->onWindowRemoved(SWALLOWER);
g_pHyprRenderer->damageWindow(SWALLOWER);
g_layoutManager->removeTarget(SWALLOWER->layoutTarget());
SWALLOWER->setHidden(true);
g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID());
}
m_firstMap = false;
@ -2382,7 +2068,7 @@ void CWindow::mapWindow() {
// apply data from default decos. Borders, shadows.
g_pDecorationPositioner->forceRecalcFor(m_self.lock());
updateWindowDecos();
g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock());
layoutTarget()->recalc();
// do animations
g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_IN);
@ -2454,10 +2140,10 @@ void CWindow::unmapWindow() {
m_swallowed->m_currentlySwallowed = false;
m_swallowed->setHidden(false);
if (m_groupData.pNextWindow.lock())
if (m_group)
m_swallowed->m_groupSwallowed = true; // flag for the swallowed window to be created into the group where it belongs when auto_group = false.
g_pLayoutManager->getCurrentLayout()->onWindowCreated(m_swallowed.lock());
g_layoutManager->newTarget(m_swallowed->layoutTarget(), m_workspace->m_space);
}
m_swallowed->m_groupSwallowed = false;
@ -2466,7 +2152,7 @@ void CWindow::unmapWindow() {
bool wasLastWindow = false;
PHLWINDOW nextInGroup = [this] -> PHLWINDOW {
if (!m_groupData.pNextWindow)
if (!m_group)
return nullptr;
// walk the history to find a suitable window
@ -2475,7 +2161,7 @@ void CWindow::unmapWindow() {
if (!w || !w->m_isMapped || w == m_self)
continue;
if (!hasInGroup(w.lock()))
if (!m_group->has(w.lock()))
continue;
return w.lock();
@ -2491,7 +2177,7 @@ void CWindow::unmapWindow() {
g_pInputManager->releaseAllMouseButtons();
}
if (m_self.lock() == g_pInputManager->m_currentlyDraggedWindow.lock())
if (m_self.lock() == g_layoutManager->dragController()->target())
CKeybindManager::changeMouseBindMode(MBIND_INVALID);
// remove the fullscreen window status from workspace if we closed it
@ -2500,7 +2186,10 @@ void CWindow::unmapWindow() {
if (PWORKSPACE->m_hasFullscreenWindow && isFullscreen())
PWORKSPACE->m_hasFullscreenWindow = false;
g_pLayoutManager->getCurrentLayout()->onWindowRemoved(m_self.lock());
if (m_group)
m_group->remove(m_self.lock());
g_layoutManager->removeTarget(m_target);
g_pHyprRenderer->damageWindow(m_self.lock());
@ -2516,17 +2205,20 @@ void CWindow::unmapWindow() {
if (*FOCUSONCLOSE)
candidate = (g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(),
Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING));
else
candidate = g_pLayoutManager->getCurrentLayout()->getNextWindowCandidate(m_self.lock());
else {
const auto CAND = g_layoutManager->getNextCandidate(m_workspace->m_space, layoutTarget());
if (CAND)
candidate = CAND->window();
}
}
Log::logger->log(Log::DEBUG, "On closed window, new focused candidate is {}", candidate);
if (candidate != Desktop::focusState()->window() && candidate) {
if (candidate == nextInGroup)
Desktop::focusState()->rawWindowFocus(candidate);
Desktop::focusState()->rawWindowFocus(candidate, FOCUS_REASON_DESKTOP_STATE_CHANGE);
else
Desktop::focusState()->fullWindowFocus(candidate);
Desktop::focusState()->fullWindowFocus(candidate, FOCUS_REASON_DESKTOP_STATE_CHANGE);
if ((*PEXITRETAINSFS || candidate == nextInGroup) && CURRENTWINDOWFSSTATE)
g_pCompositor->setWindowFullscreenInternal(candidate, CURRENTFSMODE);
@ -2541,7 +2233,8 @@ void CWindow::unmapWindow() {
if (m_self.lock() == Desktop::focusState()->window() || !Desktop::focusState()->window()) {
g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","});
g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""});
EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr});
EMIT_HOOK_EVENT("activeWindow", Desktop::View::SWindowActiveEvent{nullptr COMMA FOCUS_REASON_OTHER});
}
} else {
Log::logger->log(Log::DEBUG, "Unmapped was not focused, ignoring a refocus.");
@ -2573,7 +2266,13 @@ void CWindow::commitWindow() {
// try to calculate static rules already for any floats
m_ruleApplicator->readStaticRules(true);
Vector2D predSize = g_pLayoutManager->getCurrentLayout()->predictSizeForNewWindow(m_self.lock());
const Vector2D predSize = !m_ruleApplicator->static_.floating.value_or(false) // no float rule
&& !m_isFloating // not floating
&& !parent() // no parents
&& !g_pXWaylandManager->shouldBeFloated(m_self.lock(), true) // should not be floated
?
g_layoutManager->predictSizeForNewTiledTarget().value_or(Vector2D{}) :
Vector2D{};
Log::logger->log(Log::DEBUG, "Layout predicts size {} for {}", predSize, m_self.lock());
@ -2634,7 +2333,7 @@ void CWindow::destroyWindow() {
m_listeners = {};
g_pLayoutManager->getCurrentLayout()->onWindowRemoved(m_self.lock());
g_layoutManager->removeTarget(m_target);
m_readyToDelete = true;
@ -2664,7 +2363,7 @@ void CWindow::activateX11() {
if (!m_xwaylandSurface->wantsFocus())
return;
Desktop::focusState()->fullWindowFocus(m_self.lock());
Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
return;
}
@ -2764,3 +2463,26 @@ std::optional<Vector2D> CWindow::maxSize() {
return maxSize;
}
SP<Layout::ITarget> CWindow::layoutTarget() {
return m_group ? m_group->m_target : m_target;
}
bool CWindow::canBeGroupedInto(SP<CGroup> group) {
if (!group)
return false;
if (isX11OverrideRedirect())
return false;
static auto ALLOWGROUPMERGE = CConfigValue<Hyprlang::INT>("group:merge_groups_on_drag");
bool isGroup = m_group;
bool disallowDragIntoGroup = g_layoutManager->dragController()->wasDraggingWindow() && isGroup && !sc<bool>(*ALLOWGROUPMERGE);
return !g_pKeybindManager->m_groupsLocked // global group lock disengaged
&& ((m_groupRules & GROUP_INVADE && m_firstMap) // window ignore local group locks, or
|| (!group->locked() // target unlocked
&& !(m_group && m_group->locked()))) // source unlocked or isn't group
&& !(m_groupRules & GROUP_DENY) // source is not denied entry
&& !(m_groupRules & GROUP_BARRED && m_firstMap) // group rule doesn't prevent adding window
&& !disallowDragIntoGroup; // config allows groups to be merged
}