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

@ -1,10 +1,13 @@
#include "Workspace.hpp"
#include "view/Group.hpp"
#include "../Compositor.hpp"
#include "../config/ConfigValue.hpp"
#include "config/ConfigManager.hpp"
#include "managers/animation/AnimationManager.hpp"
#include "../managers/EventManager.hpp"
#include "../managers/HookSystemManager.hpp"
#include "../layout/space/Space.hpp"
#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp"
#include <hyprutils/animation/AnimatedVariable.hpp>
#include <hyprutils/string/String.hpp>
@ -41,6 +44,9 @@ void CWorkspace::init(PHLWORKSPACE self) {
m_lastFocusedWindow.reset();
});
m_space = Layout::CSpace::create(m_self.lock());
m_space->setAlgorithmProvider(Layout::Supplementary::algoMatcher()->createAlgorithmForWorkspace(m_self.lock()));
m_inert = false;
const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(self);
@ -406,14 +412,19 @@ bool CWorkspace::isVisibleNotCovered() {
int CWorkspace::getWindows(std::optional<bool> onlyTiled, std::optional<bool> onlyPinned, std::optional<bool> onlyVisible) {
int no = 0;
for (auto const& w : g_pCompositor->m_windows) {
if (w->workspaceID() != m_id || !w->m_isMapped)
if (!m_space)
return 0;
for (auto const& t : m_space->targets()) {
if (!t)
continue;
if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value())
if (onlyTiled.has_value() && t->floating() == onlyTiled.value())
continue;
if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value())
if (onlyPinned.has_value() && (!t->window() || t->window()->m_pinned != onlyPinned.value()))
continue;
if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value())
if (onlyVisible.has_value() && (!t->window() || t->window()->isHidden() == onlyVisible.value()))
continue;
no++;
}
@ -423,16 +434,16 @@ int CWorkspace::getWindows(std::optional<bool> onlyTiled, std::optional<bool> on
int CWorkspace::getGroups(std::optional<bool> onlyTiled, std::optional<bool> onlyPinned, std::optional<bool> onlyVisible) {
int no = 0;
for (auto const& w : g_pCompositor->m_windows) {
if (w->workspaceID() != m_id || !w->m_isMapped)
for (auto const& g : Desktop::View::groups()) {
const auto HEAD = g->head();
if (HEAD->workspaceID() != m_id || !HEAD->m_isMapped)
continue;
if (!w->m_groupData.head)
if (onlyTiled.has_value() && HEAD->m_isFloating == onlyTiled.value())
continue;
if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value())
if (onlyPinned.has_value() && HEAD->m_pinned != onlyPinned.value())
continue;
if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value())
continue;
if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value())
if (onlyVisible.has_value() && g->current()->isHidden() == onlyVisible.value())
continue;
no++;
}
@ -514,13 +525,11 @@ void CWorkspace::rename(const std::string& name) {
}
void CWorkspace::updateWindows() {
m_hasFullscreenWindow = std::ranges::any_of(g_pCompositor->m_windows, [this](const auto& w) { return w->m_isMapped && w->m_workspace == m_self && w->isFullscreen(); });
m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t->fullscreenMode() != FSMODE_NONE; });
for (auto const& w : g_pCompositor->m_windows) {
if (!w->m_isMapped || w->m_workspace != m_self)
continue;
w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);
for (auto const& t : m_space->targets()) {
if (t->window())
t->window()->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE);
}
}

View file

@ -6,6 +6,10 @@
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/signal/Signal.hpp"
namespace Layout {
class CSpace;
};
enum eFullscreenMode : int8_t {
FSMODE_NONE = 0,
FSMODE_MAXIMIZED = 1 << 0,
@ -20,7 +24,9 @@ class CWorkspace {
CWorkspace(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special = false, bool isEmpty = true);
~CWorkspace();
WP<CWorkspace> m_self;
WP<CWorkspace> m_self;
SP<Layout::CSpace> m_space;
// Workspaces ID-based have IDs > 0
// and workspaces name-based have IDs starting with -1337

View file

@ -20,7 +20,7 @@ CWindowHistoryTracker::CWindowHistoryTracker() {
});
static auto P1 = g_pHookSystem->hookDynamic("activeWindow", [this](void* self, SCallbackInfo& info, std::any data) {
auto window = std::any_cast<PHLWINDOW>(data);
auto window = std::any_cast<Desktop::View::SWindowActiveEvent>(data).window;
track(window);
});

View file

@ -77,7 +77,7 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) {
return false;
break;
case RULE_PROP_GROUP:
if (!engine->match(w->m_groupData.pNextWindow))
if (!engine->match(!!w->m_group))
return false;
break;
case RULE_PROP_MODAL:

View file

@ -4,7 +4,6 @@
#include "../utils/SetUtils.hpp"
#include "../../view/Window.hpp"
#include "../../types/OverridableVar.hpp"
#include "../../../managers/LayoutManager.hpp"
#include "../../../managers/HookSystemManager.hpp"
#include <hyprutils/string/String.hpp>

View file

@ -3,15 +3,19 @@
#include "../../Compositor.hpp"
#include "../../protocols/XDGShell.hpp"
#include "../../render/Renderer.hpp"
#include "../../managers/LayoutManager.hpp"
#include "../../managers/EventManager.hpp"
#include "../../managers/HookSystemManager.hpp"
#include "../../managers/input/InputManager.hpp"
#include "../../managers/SeatManager.hpp"
#include "../../xwayland/XSurface.hpp"
#include "../../protocols/PointerConstraints.hpp"
#include "managers/animation/DesktopAnimationManager.hpp"
#include "../../layout/LayoutManager.hpp"
using namespace Desktop;
#define COMMA ,
SP<CFocusState> Desktop::focusState() {
static SP<CFocusState> state = makeShared<CFocusState>();
return state;
@ -63,7 +67,7 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO
return {};
}
void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surface, bool forceFSCycle) {
void CFocusState::fullWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLSurfaceResource> surface, bool forceFSCycle) {
if (pWindow) {
if (!pWindow->m_workspace)
return;
@ -83,10 +87,10 @@ void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surf
return;
}
rawWindowFocus(pWindow, surface);
rawWindowFocus(pWindow, reason, surface);
}
void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surface) {
void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP<CWLSurfaceResource> surface) {
static auto PFOLLOWMOUSE = CConfigValue<Hyprlang::INT>("input:follow_mouse");
static auto PSPECIALFALLTHROUGH = CConfigValue<Hyprlang::INT>("input:special_fallthrough");
@ -105,7 +109,9 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surfa
if (pWindow && pWindow->m_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus())
return;
g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow);
// m_target on purpose, this avoids the group
if (pWindow)
g_layoutManager->bringTargetToTop(pWindow->m_target);
if (!pWindow || !validMapped(pWindow)) {
@ -127,9 +133,7 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surfa
g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","});
g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""});
EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr});
g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(nullptr);
EMIT_HOOK_EVENT("activeWindow", Desktop::View::SWindowActiveEvent{nullptr COMMA reason});
m_focusSurface.reset();
@ -196,16 +200,14 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP<CWLSurfaceResource> surfa
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = pWindow->m_class + "," + pWindow->m_title});
g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc<uintptr_t>(pWindow.get()))});
EMIT_HOOK_EVENT("activeWindow", pWindow);
g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(pWindow);
EMIT_HOOK_EVENT("activeWindow", Desktop::View::SWindowActiveEvent{pWindow COMMA reason});
g_pInputManager->recheckIdleInhibitorStatus();
if (*PFOLLOWMOUSE == 0)
g_pInputManager->sendMotionEventsToFocused();
if (pWindow->m_groupData.pNextWindow)
if (pWindow->m_group)
pWindow->deactivateGroupMembers();
}
@ -296,3 +298,7 @@ void CFocusState::resetWindowFocus() {
m_focusWindow.reset();
m_focusSurface.reset();
}
bool Desktop::isHardInputFocusReason(eFocusReason r) {
return r == FOCUS_REASON_NEW_WINDOW || r == FOCUS_REASON_KEYBIND || r == FOCUS_REASON_GHOSTS || r == FOCUS_REASON_CLICK || r == FOCUS_REASON_DESKTOP_STATE_CHANGE;
}

View file

@ -6,6 +6,19 @@
class CWLSurfaceResource;
namespace Desktop {
enum eFocusReason : uint8_t {
FOCUS_REASON_UNKNOWN = 0,
FOCUS_REASON_FFM,
FOCUS_REASON_KEYBIND,
FOCUS_REASON_CLICK,
FOCUS_REASON_OTHER,
FOCUS_REASON_DESKTOP_STATE_CHANGE,
FOCUS_REASON_NEW_WINDOW,
FOCUS_REASON_GHOSTS,
};
bool isHardInputFocusReason(eFocusReason r);
class CFocusState {
public:
CFocusState();
@ -15,8 +28,8 @@ namespace Desktop {
CFocusState(CFocusState&) = delete;
CFocusState(const CFocusState&) = delete;
void fullWindowFocus(PHLWINDOW w, SP<CWLSurfaceResource> surface = nullptr, bool forceFSCycle = false);
void rawWindowFocus(PHLWINDOW w, SP<CWLSurfaceResource> surface = nullptr);
void fullWindowFocus(PHLWINDOW w, eFocusReason reason, SP<CWLSurfaceResource> surface = nullptr, bool forceFSCycle = false);
void rawWindowFocus(PHLWINDOW w, eFocusReason reason, SP<CWLSurfaceResource> surface = nullptr);
void rawSurfaceFocus(SP<CWLSurfaceResource> s, PHLWINDOW pWindowOwner = nullptr);
void rawMonitorFocus(PHLMONITOR m);

337
src/desktop/view/Group.cpp Normal file
View file

@ -0,0 +1,337 @@
#include "Group.hpp"
#include "Window.hpp"
#include "../../render/decorations/CHyprGroupBarDecoration.hpp"
#include "../../layout/target/WindowGroupTarget.hpp"
#include "../../layout/target/WindowTarget.hpp"
#include "../../layout/target/Target.hpp"
#include "../../layout/space/Space.hpp"
#include "../../layout/LayoutManager.hpp"
#include "../../desktop/state/FocusState.hpp"
#include "../../Compositor.hpp"
#include <algorithm>
using namespace Desktop;
using namespace Desktop::View;
std::vector<WP<CGroup>>& View::groups() {
static std::vector<WP<CGroup>> g;
return g;
}
SP<CGroup> CGroup::create(std::vector<PHLWINDOWREF>&& windows) {
auto x = SP<CGroup>(new CGroup(std::move(windows)));
x->m_self = x;
x->m_target = Layout::CWindowGroupTarget::create(x);
groups().emplace_back(x);
x->init();
return x;
}
CGroup::CGroup(std::vector<PHLWINDOWREF>&& windows) : m_windows(std::move(windows)) {
;
}
void CGroup::init() {
// for proper group logic:
// - add all windows to us
// - replace the first window with our target
// - remove all window targets from layout
// - apply updates
// FIXME: what if some windows are grouped? For now we only do 1-window but YNK
for (const auto& w : m_windows) {
RASSERT(!w->m_group, "CGroup: windows cannot contain grouped in init, this will explode");
w->m_group = m_self.lock();
}
g_layoutManager->switchTargets(m_windows.at(0)->m_target, m_target);
for (const auto& w : m_windows) {
w->m_target->setSpaceGhost(m_target->space());
}
for (const auto& w : m_windows) {
applyWindowDecosAndUpdates(w.lock());
}
updateWindowVisibility();
}
void CGroup::destroy() {
while (true) {
if (m_windows.size() == 1) {
remove(m_windows.at(0).lock());
break;
}
remove(m_windows.at(0).lock());
}
}
CGroup::~CGroup() {
if (m_target->space())
m_target->assignToSpace(nullptr);
std::erase_if(groups(), [this](const auto& e) { return !e || e == m_self; });
}
bool CGroup::has(PHLWINDOW w) const {
return std::ranges::contains(m_windows, w);
}
void CGroup::add(PHLWINDOW w) {
static auto INSERT_AFTER_CURRENT = CConfigValue<Hyprlang::INT>("group:insert_after_current");
if (w->m_group) {
if (w->m_group == m_self)
return;
const auto WINDOWS = w->m_group->windows();
for (const auto& w : WINDOWS) {
w->m_group->remove(w.lock());
add(w.lock());
}
return;
}
if (w->layoutTarget()->space()) {
// remove the target from a space if it is in one
g_layoutManager->removeTarget(w->layoutTarget());
}
w->m_group = m_self.lock();
w->m_target->setSpaceGhost(m_target->space());
w->m_target->setFloating(m_target->floating());
if (*INSERT_AFTER_CURRENT) {
m_windows.insert(m_windows.begin() + m_current + 1, w);
m_current++;
} else {
m_windows.emplace_back(w);
m_current = m_windows.size() - 1;
}
applyWindowDecosAndUpdates(w);
updateWindowVisibility();
m_target->recalc();
}
void CGroup::remove(PHLWINDOW w) {
std::optional<size_t> idx;
for (size_t i = 0; i < m_windows.size(); ++i) {
if (m_windows.at(i) == w) {
idx = i;
break;
}
}
if (!idx)
return;
if ((m_current >= *idx && idx != 0) || (m_current >= m_windows.size() - 1 && m_current > 0))
m_current--;
auto g = m_self.lock(); // keep ref to avoid uaf after w->m_group.reset()
w->m_group.reset();
removeWindowDecos(w);
w->setHidden(false);
const bool REMOVING_GROUP = m_windows.size() <= 1;
if (REMOVING_GROUP) {
w->m_target->assignToSpace(nullptr);
g_layoutManager->switchTargets(m_target, w->m_target);
}
// we do it after the above because switchTargets expects this to be a valid group
m_windows.erase(m_windows.begin() + *idx);
if (!m_windows.empty())
updateWindowVisibility();
// do this here: otherwise the new current is hidden and workspace rules get wrong data
if (!REMOVING_GROUP)
w->m_target->assignToSpace(m_target->space());
}
void CGroup::moveCurrent(bool next) {
size_t idx = m_current;
if (next) {
idx++;
if (idx >= m_windows.size())
idx = 0;
} else {
if (idx == 0)
idx = m_windows.size() - 1;
else
idx--;
}
setCurrent(idx);
}
void CGroup::setCurrent(size_t idx) {
if (idx == m_current)
return;
const auto FS_STATE = m_target->fullscreenMode();
const auto WASFOCUS = Desktop::focusState()->window() == current();
auto oldWindow = m_windows.at(m_current).lock();
if (FS_STATE != FSMODE_NONE)
g_pCompositor->setWindowFullscreenInternal(oldWindow, FSMODE_NONE);
m_current = std::clamp(idx, sc<size_t>(0), m_windows.size() - 1);
updateWindowVisibility();
auto newWindow = m_windows.at(m_current).lock();
if (FS_STATE != FSMODE_NONE) {
g_pCompositor->setWindowFullscreenInternal(newWindow, FS_STATE);
newWindow->m_target->warpPositionSize();
oldWindow->m_target->setPositionGlobal(newWindow->m_target->position()); // TODO: this is a hack and sucks
}
if (WASFOCUS)
Desktop::focusState()->rawWindowFocus(current(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
}
void CGroup::setCurrent(PHLWINDOW w) {
if (w == current())
return;
for (size_t i = 0; i < m_windows.size(); ++i) {
if (m_windows.at(i) == w) {
setCurrent(i);
return;
}
}
}
size_t CGroup::getCurrentIdx() const {
return m_current;
}
PHLWINDOW CGroup::head() const {
return m_windows.front().lock();
}
PHLWINDOW CGroup::tail() const {
return m_windows.back().lock();
}
PHLWINDOW CGroup::current() const {
return m_windows.at(m_current).lock();
}
PHLWINDOW CGroup::next() const {
return (m_current >= m_windows.size() - 1 ? m_windows.front() : m_windows.at(m_current + 1)).lock();
}
PHLWINDOW CGroup::fromIndex(size_t idx) const {
if (idx >= m_windows.size())
return nullptr;
return m_windows.at(idx).lock();
}
const std::vector<PHLWINDOWREF>& CGroup::windows() const {
return m_windows;
}
void CGroup::applyWindowDecosAndUpdates(PHLWINDOW x) {
x->addWindowDeco(makeUnique<CHyprGroupBarDecoration>(x));
x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
x->updateWindowDecos();
x->updateDecorationValues();
}
void CGroup::removeWindowDecos(PHLWINDOW x) {
x->removeWindowDeco(x->getDecorationByType(DECORATION_GROUPBAR));
x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
x->updateWindowDecos();
x->updateDecorationValues();
}
void CGroup::updateWindowVisibility() {
for (size_t i = 0; i < m_windows.size(); ++i) {
if (i == m_current) {
auto& x = m_windows.at(i);
x->setHidden(false);
x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE);
x->updateWindowDecos();
x->updateDecorationValues();
} else
m_windows.at(i)->setHidden(true);
}
m_target->recalc();
m_target->damageEntire();
}
size_t CGroup::size() const {
return m_windows.size();
}
bool CGroup::locked() const {
return m_locked;
}
void CGroup::setLocked(bool x) {
m_locked = x;
}
bool CGroup::denied() const {
return m_deny;
}
void CGroup::setDenied(bool x) {
m_deny = x;
}
void CGroup::updateWorkspace(PHLWORKSPACE ws) {
if (!ws)
return;
for (const auto& w : windows()) {
w->m_monitor = ws->m_monitor;
w->moveToWorkspace(ws);
w->updateToplevel();
w->updateWindowDecos();
w->m_target->setSpaceGhost(ws->m_space);
}
}
void CGroup::swapWithNext() {
const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current);
size_t idx = m_current + 1 >= m_windows.size() ? 0 : m_current + 1;
std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx);
updateWindowVisibility();
if (HAD_FOCUS)
Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
}
void CGroup::swapWithLast() {
const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current);
size_t idx = m_current == 0 ? m_windows.size() - 1 : m_current - 1;
std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx);
updateWindowVisibility();
if (HAD_FOCUS)
Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE);
}

View file

@ -0,0 +1,68 @@
#pragma once
#include "../DesktopTypes.hpp"
#include <vector>
namespace Layout {
class CWindowGroupTarget;
};
namespace Desktop::View {
class CGroup {
public:
static SP<CGroup> create(std::vector<PHLWINDOWREF>&& windows);
~CGroup();
bool has(PHLWINDOW w) const;
void add(PHLWINDOW w);
void remove(PHLWINDOW w);
void moveCurrent(bool next);
void setCurrent(size_t idx);
void setCurrent(PHLWINDOW w);
size_t getCurrentIdx() const;
size_t size() const;
void destroy();
void updateWorkspace(PHLWORKSPACE);
void swapWithNext();
void swapWithLast();
PHLWINDOW head() const;
PHLWINDOW tail() const;
PHLWINDOW current() const;
PHLWINDOW next() const;
PHLWINDOW fromIndex(size_t idx) const;
bool locked() const;
void setLocked(bool x);
bool denied() const;
void setDenied(bool x);
const std::vector<PHLWINDOWREF>& windows() const;
SP<Layout::CWindowGroupTarget> m_target;
private:
CGroup(std::vector<PHLWINDOWREF>&& windows);
void applyWindowDecosAndUpdates(PHLWINDOW x);
void removeWindowDecos(PHLWINDOW x);
void init();
void updateWindowVisibility();
WP<CGroup> m_self;
std::vector<PHLWINDOWREF> m_windows;
bool m_locked = false;
bool m_deny = false;
size_t m_current = 0;
};
std::vector<WP<CGroup>>& groups();
};

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
}

View file

@ -26,8 +26,19 @@ struct SWorkspaceRule;
class IWindowTransformer;
namespace Layout {
class ITarget;
class CWindowTarget;
}
namespace Desktop {
enum eFocusReason : uint8_t;
}
namespace Desktop::View {
class CGroup;
enum eGroupRules : uint8_t {
// effective only during first map, except for _ALWAYS variant
GROUP_NONE = 0,
@ -38,6 +49,7 @@ namespace Desktop::View {
GROUP_LOCK_ALWAYS = 1 << 4,
GROUP_INVADE = 1 << 5, // Force enter a group, event if lock is engaged
GROUP_OVERRIDE = 1 << 6, // Override other rules
GROUP_DENY = 1 << 7, // deny
};
enum eGetWindowProperties : uint8_t {
@ -61,6 +73,11 @@ namespace Desktop::View {
SUPPRESS_FULLSCREEN_OUTPUT = 1 << 4,
};
struct SWindowActiveEvent {
PHLWINDOW window = nullptr;
eFocusReason reason = sc<eFocusReason>(0) /* unknown */;
};
struct SInitialWorkspaceToken {
PHLWINDOWREF primaryOwner;
std::string workspace;
@ -97,6 +114,8 @@ namespace Desktop::View {
WP<CXDGSurfaceResource> m_xdgSurface;
WP<CXWaylandSurface> m_xwaylandSurface;
SP<Layout::ITarget> m_target;
// this is the position and size of the "bounding box"
Vector2D m_position = Vector2D(0, 0);
Vector2D m_size = Vector2D(0, 0);
@ -112,23 +131,14 @@ namespace Desktop::View {
std::optional<std::pair<uint32_t, Vector2D>> m_pendingSizeAck;
std::vector<std::pair<uint32_t, Vector2D>> m_pendingSizeAcks;
// for restoring floating statuses
Vector2D m_lastFloatingSize;
Vector2D m_lastFloatingPosition;
// for floating window offset in workspace animations
Vector2D m_floatingOffset = Vector2D(0, 0);
// this is used for pseudotiling
bool m_isPseudotiled = false;
Vector2D m_pseudoSize = Vector2D(1280, 720);
// for recovering relative cursor position
Vector2D m_relativeCursorCoordsOnLastWarp = Vector2D(-1, -1);
bool m_firstMap = false; // for layouts
bool m_isFloating = false;
bool m_draggingTiled = false; // for dragging around tiled windows
SFullscreenState m_fullscreenState = {.internal = FSMODE_NONE, .client = FSMODE_NONE};
std::string m_title = "";
std::string m_class = "";
@ -229,15 +239,10 @@ namespace Desktop::View {
std::string m_initialWorkspaceToken = "";
// for groups
struct SGroupData {
PHLWINDOWREF pNextWindow; // nullptr means no grouping. Self means single group.
bool head = false;
bool locked = false; // per group lock
bool deny = false; // deny window from enter a group or made a group
} m_groupData;
uint16_t m_groupRules = Desktop::View::GROUP_NONE;
SP<CGroup> m_group;
uint16_t m_groupRules = Desktop::View::GROUP_NONE;
bool m_tearingHint = false;
bool m_tearingHint = false;
// Stable ID for ext_foreign_toplevel_list
const uint64_t m_stableID = 0x2137;
@ -303,21 +308,6 @@ namespace Desktop::View {
bool isInCurvedCorner(double x, double y);
bool hasPopupAt(const Vector2D& pos);
int popupsCount();
void applyGroupRules();
void createGroup();
void destroyGroup();
PHLWINDOW getGroupHead();
PHLWINDOW getGroupTail();
PHLWINDOW getGroupCurrent();
PHLWINDOW getGroupPrevious();
PHLWINDOW getGroupWindowByIndex(int);
bool hasInGroup(PHLWINDOW);
int getGroupSize();
bool canBeGroupedInto(PHLWINDOW pWindow);
void setGroupCurrent(PHLWINDOW pWindow);
void insertWindowToGroup(PHLWINDOW pWindow);
void updateGroupOutputs();
void switchWithWindowInGroup(PHLWINDOW pWindow);
void setAnimationsToMove();
void onWorkspaceAnimUpdate();
void onFocusAnimUpdate();
@ -350,6 +340,8 @@ namespace Desktop::View {
std::optional<Vector2D> calculateExpression(const std::string& s);
std::optional<Vector2D> minSize();
std::optional<Vector2D> maxSize();
SP<Layout::ITarget> layoutTarget();
bool canBeGroupedInto(SP<CGroup> group);
CBox getWindowMainSurfaceBox() const {
return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y};