summaryrefslogtreecommitdiff
path: root/src/desktop/Window.cpp
diff options
context:
space:
mode:
authorVaxry <vaxry@vaxry.net>2024-03-20 01:44:51 +0000
committerVaxry <vaxry@vaxry.net>2024-03-20 01:44:51 +0000
commit8593c45be3cfe8055d13315051d88588f848ad39 (patch)
treeb9957bc79aada13bc54ec82bba06edf6113872f1 /src/desktop/Window.cpp
parentf6038837bc12a5f22808ceea0641dd9fe17d8094 (diff)
refactor: move window.hpp to desktop/
Diffstat (limited to 'src/desktop/Window.cpp')
-rw-r--r--src/desktop/Window.cpp1158
1 files changed, 1158 insertions, 0 deletions
diff --git a/src/desktop/Window.cpp b/src/desktop/Window.cpp
new file mode 100644
index 00000000..567c3243
--- /dev/null
+++ b/src/desktop/Window.cpp
@@ -0,0 +1,1158 @@
+#include "Window.hpp"
+#include "../Compositor.hpp"
+#include "../render/decorations/CHyprDropShadowDecoration.hpp"
+#include "../render/decorations/CHyprGroupBarDecoration.hpp"
+#include "../render/decorations/CHyprBorderDecoration.hpp"
+#include "../config/ConfigValue.hpp"
+
+CWindow::CWindow() {
+ m_vRealPosition.create(g_pConfigManager->getAnimationPropertyConfig("windowsIn"), (void*)this, AVARDAMAGE_ENTIRE);
+ m_vRealSize.create(g_pConfigManager->getAnimationPropertyConfig("windowsIn"), (void*)this, AVARDAMAGE_ENTIRE);
+ m_fBorderFadeAnimationProgress.create(g_pConfigManager->getAnimationPropertyConfig("border"), (void*)this, AVARDAMAGE_BORDER);
+ m_fBorderAngleAnimationProgress.create(g_pConfigManager->getAnimationPropertyConfig("borderangle"), (void*)this, AVARDAMAGE_BORDER);
+ m_fAlpha.create(g_pConfigManager->getAnimationPropertyConfig("fadeIn"), (void*)this, AVARDAMAGE_ENTIRE);
+ m_fActiveInactiveAlpha.create(g_pConfigManager->getAnimationPropertyConfig("fadeSwitch"), (void*)this, AVARDAMAGE_ENTIRE);
+ m_cRealShadowColor.create(g_pConfigManager->getAnimationPropertyConfig("fadeShadow"), (void*)this, AVARDAMAGE_SHADOW);
+ m_fDimPercent.create(g_pConfigManager->getAnimationPropertyConfig("fadeDim"), (void*)this, AVARDAMAGE_ENTIRE);
+
+ addWindowDeco(std::make_unique<CHyprDropShadowDecoration>(this));
+ addWindowDeco(std::make_unique<CHyprBorderDecoration>(this));
+}
+
+CWindow::~CWindow() {
+ if (g_pCompositor->isWindowActive(this)) {
+ g_pCompositor->m_pLastFocus = nullptr;
+ g_pCompositor->m_pLastWindow = nullptr;
+ }
+
+ if (!g_pHyprOpenGL)
+ return;
+
+ g_pHyprRenderer->makeEGLCurrent();
+ std::erase_if(g_pHyprOpenGL->m_mWindowFramebuffers, [&](const auto& other) { return other.first == this; });
+}
+
+SWindowDecorationExtents CWindow::getFullWindowExtents() {
+ if (m_bFadingOut)
+ return m_eOriginalClosedExtents;
+
+ const int BORDERSIZE = getRealBorderSize();
+
+ if (m_sAdditionalConfigData.dimAround) {
+ const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
+ return {{m_vRealPosition.value().x - PMONITOR->vecPosition.x, m_vRealPosition.value().y - PMONITOR->vecPosition.y},
+ {PMONITOR->vecSize.x - (m_vRealPosition.value().x - PMONITOR->vecPosition.x), PMONITOR->vecSize.y - (m_vRealPosition.value().y - PMONITOR->vecPosition.y)}};
+ }
+
+ SWindowDecorationExtents maxExtents = {{BORDERSIZE + 2, BORDERSIZE + 2}, {BORDERSIZE + 2, BORDERSIZE + 2}};
+
+ const auto EXTENTS = g_pDecorationPositioner->getWindowDecorationExtents(this);
+
+ if (EXTENTS.topLeft.x > maxExtents.topLeft.x)
+ maxExtents.topLeft.x = EXTENTS.topLeft.x;
+
+ if (EXTENTS.topLeft.y > maxExtents.topLeft.y)
+ maxExtents.topLeft.y = EXTENTS.topLeft.y;
+
+ if (EXTENTS.bottomRight.x > maxExtents.bottomRight.x)
+ maxExtents.bottomRight.x = EXTENTS.bottomRight.x;
+
+ if (EXTENTS.bottomRight.y > maxExtents.bottomRight.y)
+ maxExtents.bottomRight.y = EXTENTS.bottomRight.y;
+
+ if (m_pWLSurface.exists() && !m_bIsX11) {
+ CBox surfaceExtents = {0, 0, 0, 0};
+ // TODO: this could be better, perhaps make a getFullWindowRegion?
+ wlr_xdg_surface_for_each_popup_surface(
+ m_uSurface.xdg,
+ [](wlr_surface* surf, int sx, int sy, void* data) {
+ CBox* pSurfaceExtents = (CBox*)data;
+ if (sx < pSurfaceExtents->x)
+ pSurfaceExtents->x = sx;
+ if (sy < pSurfaceExtents->y)
+ pSurfaceExtents->y = sy;
+ if (sx + surf->current.width > pSurfaceExtents->width)
+ pSurfaceExtents->width = sx + surf->current.width - pSurfaceExtents->x;
+ if (sy + surf->current.height > pSurfaceExtents->height)
+ pSurfaceExtents->height = sy + surf->current.height - pSurfaceExtents->y;
+ },
+ &surfaceExtents);
+
+ if (-surfaceExtents.x > maxExtents.topLeft.x)
+ maxExtents.topLeft.x = -surfaceExtents.x;
+
+ if (-surfaceExtents.y > maxExtents.topLeft.y)
+ maxExtents.topLeft.y = -surfaceExtents.y;
+
+ if (surfaceExtents.x + surfaceExtents.width > m_pWLSurface.wlr()->current.width + maxExtents.bottomRight.x)
+ maxExtents.bottomRight.x = surfaceExtents.x + surfaceExtents.width - m_pWLSurface.wlr()->current.width;
+
+ if (surfaceExtents.y + surfaceExtents.height > m_pWLSurface.wlr()->current.height + maxExtents.bottomRight.y)
+ maxExtents.bottomRight.y = surfaceExtents.y + surfaceExtents.height - m_pWLSurface.wlr()->current.height;
+ }
+
+ return maxExtents;
+}
+
+CBox CWindow::getFullWindowBoundingBox() {
+ if (m_sAdditionalConfigData.dimAround) {
+ const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
+ return {PMONITOR->vecPosition.x, PMONITOR->vecPosition.y, PMONITOR->vecSize.x, PMONITOR->vecSize.y};
+ }
+
+ auto maxExtents = getFullWindowExtents();
+
+ CBox finalBox = {m_vRealPosition.value().x - maxExtents.topLeft.x, m_vRealPosition.value().y - maxExtents.topLeft.y,
+ m_vRealSize.value().x + maxExtents.topLeft.x + maxExtents.bottomRight.x, m_vRealSize.value().y + maxExtents.topLeft.y + maxExtents.bottomRight.y};
+
+ return finalBox;
+}
+
+CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() {
+
+ const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
+
+ auto POS = m_vPosition;
+ auto SIZE = m_vSize;
+
+ if (m_bIsFullscreen) {
+ POS = PMONITOR->vecPosition;
+ SIZE = PMONITOR->vecSize;
+
+ return CBox{(int)POS.x, (int)POS.y, (int)SIZE.x, (int)SIZE.y};
+ }
+
+ if (DELTALESSTHAN(POS.y - PMONITOR->vecPosition.y, PMONITOR->vecReservedTopLeft.y, 1)) {
+ POS.y = PMONITOR->vecPosition.y;
+ SIZE.y += PMONITOR->vecReservedTopLeft.y;
+ }
+ if (DELTALESSTHAN(POS.x - PMONITOR->vecPosition.x, PMONITOR->vecReservedTopLeft.x, 1)) {
+ POS.x = PMONITOR->vecPosition.x;
+ SIZE.x += PMONITOR->vecReservedTopLeft.x;
+ }
+ if (DELTALESSTHAN(POS.x + SIZE.x - PMONITOR->vecPosition.x, PMONITOR->vecSize.x - PMONITOR->vecReservedBottomRight.x, 1)) {
+ SIZE.x += PMONITOR->vecReservedBottomRight.x;
+ }
+ if (DELTALESSTHAN(POS.y + SIZE.y - PMONITOR->vecPosition.y, PMONITOR->vecSize.y - PMONITOR->vecReservedBottomRight.y, 1)) {
+ SIZE.y += PMONITOR->vecReservedBottomRight.y;
+ }
+
+ return CBox{(int)POS.x, (int)POS.y, (int)SIZE.x, (int)SIZE.y};
+}
+
+CBox CWindow::getWindowBoxUnified(uint64_t properties) {
+
+ if (m_sAdditionalConfigData.dimAround) {
+ const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
+ return {PMONITOR->vecPosition.x, PMONITOR->vecPosition.y, PMONITOR->vecSize.x, PMONITOR->vecSize.y};
+ }
+
+ SWindowDecorationExtents EXTENTS = {{0, 0}, {0, 0}};
+ if (properties & RESERVED_EXTENTS)
+ EXTENTS.addExtents(g_pDecorationPositioner->getWindowDecorationReserved(this));
+ if (properties & INPUT_EXTENTS)
+ EXTENTS.addExtents(g_pDecorationPositioner->getWindowDecorationExtents(this, true));
+ if (properties & FULL_EXTENTS)
+ EXTENTS.addExtents(g_pDecorationPositioner->getWindowDecorationExtents(this, false));
+
+ CBox box = {m_vRealPosition.value().x, m_vRealPosition.value().y, m_vRealSize.value().x, m_vRealSize.value().y};
+ box.addExtents(EXTENTS);
+
+ return box;
+}
+
+CBox CWindow::getWindowMainSurfaceBox() {
+ return {m_vRealPosition.value().x, m_vRealPosition.value().y, m_vRealSize.value().x, m_vRealSize.value().y};
+}
+
+SWindowDecorationExtents CWindow::getFullWindowReservedArea() {
+ return g_pDecorationPositioner->getWindowDecorationReserved(this);
+}
+
+void CWindow::updateWindowDecos() {
+ bool recalc = false;
+
+ if (!m_bIsMapped || isHidden())
+ return;
+
+ for (auto& wd : m_vDecosToRemove) {
+ for (auto it = m_dWindowDecorations.begin(); it != m_dWindowDecorations.end(); it++) {
+ if (it->get() == wd) {
+ g_pDecorationPositioner->uncacheDecoration(it->get());
+ it = m_dWindowDecorations.erase(it);
+ recalc = true;
+ if (it == m_dWindowDecorations.end())
+ break;
+ }
+ }
+ }
+
+ g_pDecorationPositioner->onWindowUpdate(this);
+
+ if (recalc)
+ g_pLayoutManager->getCurrentLayout()->recalculateWindow(this);
+
+ m_vDecosToRemove.clear();
+
+ // make a copy because updateWindow can remove decos.
+ std::vector<IHyprWindowDecoration*> decos;
+
+ for (auto& wd : m_dWindowDecorations) {
+ decos.push_back(wd.get());
+ }
+
+ for (auto& wd : decos) {
+ wd->updateWindow(this);
+ }
+}
+
+void CWindow::addWindowDeco(std::unique_ptr<IHyprWindowDecoration> deco) {
+ m_dWindowDecorations.emplace_back(std::move(deco));
+ g_pDecorationPositioner->forceRecalcFor(this);
+ updateWindowDecos();
+ g_pLayoutManager->getCurrentLayout()->recalculateWindow(this);
+}
+
+void CWindow::removeWindowDeco(IHyprWindowDecoration* deco) {
+ m_vDecosToRemove.push_back(deco);
+ g_pDecorationPositioner->forceRecalcFor(this);
+ updateWindowDecos();
+ g_pLayoutManager->getCurrentLayout()->recalculateWindow(this);
+}
+
+void CWindow::uncacheWindowDecos() {
+ for (auto& wd : m_dWindowDecorations) {
+ g_pDecorationPositioner->uncacheDecoration(wd.get());
+ }
+}
+
+bool CWindow::checkInputOnDecos(const eInputType type, const Vector2D& mouseCoords, std::any data) {
+ if (type != INPUT_TYPE_DRAG_END && hasPopupAt(mouseCoords))
+ return false;
+
+ for (auto& wd : m_dWindowDecorations) {
+ if (!(wd->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT))
+ continue;
+
+ if (!g_pDecorationPositioner->getWindowDecorationBox(wd.get()).containsPoint(mouseCoords))
+ continue;
+
+ if (wd->onInputOnDeco(type, mouseCoords, data))
+ return true;
+ }
+
+ return false;
+}
+
+pid_t CWindow::getPID() {
+ pid_t PID = -1;
+ if (!m_bIsX11) {
+ if (!m_uSurface.xdg)
+ return -1;
+
+ wl_client_get_credentials(wl_resource_get_client(m_uSurface.xdg->resource), &PID, nullptr, nullptr);
+ } else {
+ if (!m_uSurface.xwayland)
+ return -1;
+
+ PID = m_uSurface.xwayland->pid;
+ }
+
+ return PID;
+}
+
+IHyprWindowDecoration* CWindow::getDecorationByType(eDecorationType type) {
+ for (auto& wd : m_dWindowDecorations) {
+ if (wd->getDecorationType() == type)
+ return wd.get();
+ }
+
+ return nullptr;
+}
+
+void CWindow::createToplevelHandle() {
+ if (m_bIsX11 && (m_bX11DoesntWantBorders || m_iX11Type == 2))
+ return; // don't create a toplevel
+
+ m_phForeignToplevel = wlr_foreign_toplevel_handle_v1_create(g_pCompositor->m_sWLRToplevelMgr);
+
+ wlr_foreign_toplevel_handle_v1_set_app_id(m_phForeignToplevel, g_pXWaylandManager->getAppIDClass(this).c_str());
+ wlr_foreign_toplevel_handle_v1_output_enter(m_phForeignToplevel, g_pCompositor->getMonitorFromID(m_iMonitorID)->output);
+ wlr_foreign_toplevel_handle_v1_set_title(m_phForeignToplevel, m_szTitle.c_str());
+ wlr_foreign_toplevel_handle_v1_set_maximized(m_phForeignToplevel, false);
+ wlr_foreign_toplevel_handle_v1_set_minimized(m_phForeignToplevel, false);
+ wlr_foreign_toplevel_handle_v1_set_fullscreen(m_phForeignToplevel, false);
+
+ // handle events
+ hyprListener_toplevelActivate.initCallback(
+ &m_phForeignToplevel->events.request_activate, [&](void* owner, void* data) { g_pLayoutManager->getCurrentLayout()->requestFocusForWindow(this); }, this, "Toplevel");
+
+ hyprListener_toplevelFullscreen.initCallback(
+ &m_phForeignToplevel->events.request_fullscreen,
+ [&](void* owner, void* data) {
+ const auto EV = (wlr_foreign_toplevel_handle_v1_fullscreen_event*)data;
+
+ g_pCompositor->setWindowFullscreen(this, EV->fullscreen, FULLSCREEN_FULL);
+ },
+ this, "Toplevel");
+
+ hyprListener_toplevelClose.initCallback(
+ &m_phForeignToplevel->events.request_close, [&](void* owner, void* data) { g_pCompositor->closeWindow(this); }, this, "Toplevel");
+
+ m_iLastToplevelMonitorID = m_iMonitorID;
+}
+
+void CWindow::destroyToplevelHandle() {
+ if (!m_phForeignToplevel)
+ return;
+
+ hyprListener_toplevelActivate.removeCallback();
+ hyprListener_toplevelClose.removeCallback();
+ hyprListener_toplevelFullscreen.removeCallback();
+
+ wlr_foreign_toplevel_handle_v1_destroy(m_phForeignToplevel);
+ m_phForeignToplevel = nullptr;
+}
+
+void CWindow::updateToplevel() {
+ updateSurfaceScaleTransformDetails();
+
+ if (!m_phForeignToplevel)
+ return;
+
+ wlr_foreign_toplevel_handle_v1_set_title(m_phForeignToplevel, m_szTitle.c_str());
+ wlr_foreign_toplevel_handle_v1_set_fullscreen(m_phForeignToplevel, m_bIsFullscreen);
+
+ if (m_iLastToplevelMonitorID != m_iMonitorID) {
+ if (const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iLastToplevelMonitorID); PMONITOR && PMONITOR->m_bEnabled)
+ wlr_foreign_toplevel_handle_v1_output_leave(m_phForeignToplevel, PMONITOR->output);
+ wlr_foreign_toplevel_handle_v1_output_enter(m_phForeignToplevel, g_pCompositor->getMonitorFromID(m_iMonitorID)->output);
+
+ m_iLastToplevelMonitorID = m_iMonitorID;
+ }
+}
+
+void sendEnterIter(wlr_surface* pSurface, int x, int y, void* data) {
+ const auto OUTPUT = (wlr_output*)data;
+ wlr_surface_send_enter(pSurface, OUTPUT);
+}
+
+void sendLeaveIter(wlr_surface* pSurface, int x, int y, void* data) {
+ const auto OUTPUT = (wlr_output*)data;
+ wlr_surface_send_leave(pSurface, OUTPUT);
+}
+
+void CWindow::updateSurfaceScaleTransformDetails() {
+ if (!m_bIsMapped || m_bHidden || g_pCompositor->m_bUnsafeState)
+ return;
+
+ const auto PLASTMONITOR = g_pCompositor->getMonitorFromID(m_iLastSurfaceMonitorID);
+
+ m_iLastSurfaceMonitorID = m_iMonitorID;
+
+ const auto PNEWMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
+
+ if (!PNEWMONITOR)
+ return;
+
+ if (PNEWMONITOR != PLASTMONITOR) {
+ if (PLASTMONITOR && PLASTMONITOR->m_bEnabled)
+ wlr_surface_for_each_surface(m_pWLSurface.wlr(), sendLeaveIter, PLASTMONITOR->output);
+
+ wlr_surface_for_each_surface(m_pWLSurface.wlr(), sendEnterIter, PNEWMONITOR->output);
+ }
+
+ wlr_surface_for_each_surface(
+ m_pWLSurface.wlr(),
+ [](wlr_surface* surf, int x, int y, void* data) {
+ const auto PMONITOR = g_pCompositor->getMonitorFromID(((CWindow*)data)->m_iMonitorID);
+
+ const auto PSURFACE = CWLSurface::surfaceFromWlr(surf);
+ if (PSURFACE && PSURFACE->m_fLastScale == PMONITOR->scale)
+ return;
+
+ g_pCompositor->setPreferredScaleForSurface(surf, PMONITOR->scale);
+ g_pCompositor->setPreferredTransformForSurface(surf, PMONITOR->transform);
+ },
+ this);
+}
+
+void CWindow::moveToWorkspace(int workspaceID) {
+ if (m_iWorkspaceID == workspaceID)
+ return;
+
+ static auto PCLOSEONLASTSPECIAL = CConfigValue<Hyprlang::INT>("misc:close_special_on_empty");
+
+ const int OLDWORKSPACE = m_iWorkspaceID;
+
+ m_iWorkspaceID = workspaceID;
+
+ const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(m_iWorkspaceID);
+
+ setAnimationsToMove();
+
+ updateSpecialRenderData();
+
+ if (PWORKSPACE) {
+ g_pEventManager->postEvent(SHyprIPCEvent{"movewindow", std::format("{:x},{}", (uintptr_t)this, PWORKSPACE->m_szName)});
+ g_pEventManager->postEvent(SHyprIPCEvent{"movewindowv2", std::format("{:x},{},{}", (uintptr_t)this, PWORKSPACE->m_iID, PWORKSPACE->m_szName)});
+ EMIT_HOOK_EVENT("moveWindow", (std::vector<void*>{this, PWORKSPACE}));
+ }
+
+ if (m_pSwallowed) {
+ m_pSwallowed->moveToWorkspace(workspaceID);
+ m_pSwallowed->m_iMonitorID = m_iMonitorID;
+ }
+
+ // update xwayland coords
+ g_pXWaylandManager->setWindowSize(this, m_vRealSize.value());
+
+ if (g_pCompositor->isWorkspaceSpecial(OLDWORKSPACE) && g_pCompositor->getWindowsOnWorkspace(OLDWORKSPACE) == 0 && *PCLOSEONLASTSPECIAL) {
+ const auto PWS = g_pCompositor->getWorkspaceByID(OLDWORKSPACE);
+
+ if (PWS) {
+ if (const auto PMONITOR = g_pCompositor->getMonitorFromID(PWS->m_iMonitorID); PMONITOR)
+ PMONITOR->setSpecialWorkspace(nullptr);
+ }
+ }
+}
+
+CWindow* CWindow::X11TransientFor() {
+ if (!m_bIsX11)
+ return nullptr;
+
+ if (!m_uSurface.xwayland->parent)
+ return nullptr;
+
+ auto PPARENT = g_pCompositor->getWindowFromSurface(m_uSurface.xwayland->parent->surface);
+
+ while (g_pCompositor->windowValidMapped(PPARENT) && PPARENT->m_uSurface.xwayland->parent) {
+ PPARENT = g_pCompositor->getWindowFromSurface(PPARENT->m_uSurface.xwayland->parent->surface);
+ }
+
+ if (!g_pCompositor->windowValidMapped(PPARENT))
+ return nullptr;
+
+ return PPARENT;
+}
+
+void CWindow::removeDecorationByType(eDecorationType type) {
+ for (auto& wd : m_dWindowDecorations) {
+ if (wd->getDecorationType() == type)
+ m_vDecosToRemove.push_back(wd.get());
+ }
+
+ updateWindowDecos();
+}
+
+void unregisterVar(void* ptr) {
+ ((CBaseAnimatedVariable*)ptr)->unregister();
+}
+
+void CWindow::onUnmap() {
+ static auto PCLOSEONLASTSPECIAL = CConfigValue<Hyprlang::INT>("misc:close_special_on_empty");
+
+ if (g_pCompositor->m_pLastWindow == this)
+ g_pCompositor->m_pLastWindow = nullptr;
+
+ m_vRealPosition.setCallbackOnEnd(unregisterVar);
+ m_vRealSize.setCallbackOnEnd(unregisterVar);
+ m_fBorderFadeAnimationProgress.setCallbackOnEnd(unregisterVar);
+ m_fBorderAngleAnimationProgress.setCallbackOnEnd(unregisterVar);
+ m_fActiveInactiveAlpha.setCallbackOnEnd(unregisterVar);
+ m_fAlpha.setCallbackOnEnd(unregisterVar);
+ m_cRealShadowColor.setCallbackOnEnd(unregisterVar);
+ m_fDimPercent.setCallbackOnEnd(unregisterVar);
+
+ m_vRealSize.setCallbackOnBegin(nullptr);
+
+ std::erase_if(g_pCompositor->m_vWindowFocusHistory, [&](const auto& other) { return other == this; });
+
+ m_pWLSurface.unassign();
+
+ hyprListener_unmapWindow.removeCallback();
+
+ if (*PCLOSEONLASTSPECIAL && g_pCompositor->getWindowsOnWorkspace(m_iWorkspaceID) == 0 && g_pCompositor->isWorkspaceSpecial(m_iWorkspaceID)) {
+ const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
+ if (PMONITOR && PMONITOR->specialWorkspaceID == m_iWorkspaceID)
+ PMONITOR->setSpecialWorkspace(nullptr);
+ }
+
+ const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID);
+
+ if (PMONITOR && PMONITOR->solitaryClient == this)
+ PMONITOR->solitaryClient = nullptr;
+
+ g_pCompositor->updateWorkspaceWindows(m_iWorkspaceID);
+
+ if (m_bIsX11)
+ return;
+
+ m_pSubsurfaceHead.reset();
+ m_pPopupHead.reset();
+}
+
+void CWindow::onMap() {
+
+ m_pWLSurface.assign(g_pXWaylandManager->getWindowSurface(this), this);
+
+ // JIC, reset the callbacks. If any are set, we'll make sure they are cleared so we don't accidentally unset them. (In case a window got remapped)
+ m_vRealPosition.resetAllCallbacks();
+ m_vRealSize.resetAllCallbacks();
+ m_fBorderFadeAnimationProgress.resetAllCallbacks();
+ m_fBorderAngleAnimationProgress.resetAllCallbacks();
+ m_fActiveInactiveAlpha.resetAllCallbacks();
+ m_fAlpha.resetAllCallbacks();
+ m_cRealShadowColor.resetAllCallbacks();
+ m_fDimPercent.resetAllCallbacks();
+
+ m_vRealPosition.registerVar();
+ m_vRealSize.registerVar();
+ m_fBorderFadeAnimationProgress.registerVar();
+ m_fBorderAngleAnimationProgress.registerVar();
+ m_fActiveInactiveAlpha.registerVar();
+ m_fAlpha.registerVar();
+ m_cRealShadowColor.registerVar();
+ m_fDimPercent.registerVar();
+
+ m_fBorderAngleAnimationProgress.setCallbackOnEnd([&](void* ptr) { onBorderAngleAnimEnd(ptr); }, false);
+
+ m_fBorderAngleAnimationProgress.setValueAndWarp(0.f);
+ m_fBorderAngleAnimationProgress = 1.f;
+
+ g_pCompositor->m_vWindowFocusHistory.push_back(this);
+
+ hyprListener_unmapWindow.initCallback(m_bIsX11 ? &m_uSurface.xwayland->surface->events.unmap : &m_uSurface.xdg->surface->events.unmap, &Events::listener_unmapWindow, this,
+ "CWindow");
+
+ m_vReportedSize = m_vPendingReportedSize;
+ m_bAnimatingIn = true;
+
+ for (const auto& ctrl : g_pHyprRenderer->m_vTearingControllers) {
+ if (ctrl->pWlrHint->surface != m_pWLSurface.wlr())
+ continue;
+
+ m_bTearingHint = ctrl->pWlrHint->current;
+ break;
+ }
+
+ if (m_bIsX11)
+ return;
+
+ m_pSubsurfaceHead = std::make_unique<CSubsurface>(this);
+ m_pPopupHead = std::make_unique<CPopup>(this);
+}
+
+void CWindow::onBorderAngleAnimEnd(void* ptr) {
+ const auto PANIMVAR = (CAnimatedVariable<float>*)ptr;
+
+ const std::string STYLE = PANIMVAR->getConfig()->pValues->internalStyle;
+
+ if (STYLE != "loop" || !PANIMVAR->getConfig()->pValues->internalEnabled)
+ return;
+
+ PANIMVAR->setCallbackOnEnd(nullptr); // we remove the callback here because otherwise setvalueandwarp will recurse this
+
+ PANIMVAR->setValueAndWarp(0);
+ *PANIMVAR = 1.f;
+
+ PANIMVAR->setCallbackOnEnd([&](void* ptr) { onBorderAngleAnimEnd(ptr); }, false);
+}
+
+void CWindow::setHidden(bool hidden) {
+ m_bHidden = hidden;
+
+ if (hidden && g_pCompositor->m_pLastWindow == this) {
+ g_pCompositor->m_pLastWindow = nullptr;
+ }
+
+ setSuspended(hidden);
+}
+
+bool CWindow::isHidden() {
+ return m_bHidden;
+}
+
+void CWindow::applyDynamicRule(const SWindowRule& r) {
+ if (r.szRule == "noblur") {
+ m_sAdditionalConfigData.forceNoBlur = true;
+ } else if (r.szRule == "noborder") {
+ m_sAdditionalConfigData.forceNoBorder = true;
+ } else if (r.szRule == "noshadow") {
+ m_sAdditionalConfigData.forceNoShadow = true;
+ } else if (r.szRule == "nodim") {
+ m_sAdditionalConfigData.forceNoDim = true;
+ } else if (r.szRule == "forcergbx") {
+ m_sAdditionalConfigData.forceRGBX = true;
+ } else if (r.szRule == "opaque") {
+ if (!m_sAdditionalConfigData.forceOpaqueOverridden)
+ m_sAdditionalConfigData.forceOpaque = true;
+ } else if (r.szRule == "immediate") {
+ m_sAdditionalConfigData.forceTearing = true;
+ } else if (r.szRule == "nearestneighbor") {
+ m_sAdditionalConfigData.nearestNeighbor = true;
+ } else if (r.szRule.starts_with("rounding")) {
+ try {
+ m_sAdditionalConfigData.rounding = std::stoi(r.szRule.substr(r.szRule.find_first_of(' ') + 1));
+ } catch (std::exception& e) { Debug::log(ERR, "Rounding rule \"{}\" failed with: {}", r.szRule, e.what()); }
+ } else if (r.szRule.starts_with("bordersize")) {
+ try {
+ m_sAdditionalConfigData.borderSize = std::stoi(r.szRule.substr(r.szRule.find_first_of(' ') + 1));
+ } catch (std::exception& e) { Debug::log(ERR, "Bordersize rule \"{}\" failed with: {}", r.szRule, e.what()); }
+ } else if (r.szRule.starts_with("opacity")) {
+ try {
+ CVarList vars(r.szRule, 0, ' ');
+
+ int opacityIDX = 0;
+
+ for (auto& r : vars) {
+ if (r == "opacity")
+ continue;
+
+ if (r == "override") {
+ if (opacityIDX == 1) {
+ m_sSpecialRenderData.alphaOverride = true;
+ m_sSpecialRenderData.alphaInactiveOverride = true;
+ } else if (opacityIDX == 2)
+ m_sSpecialRenderData.alphaInactiveOverride = true;
+ } else {
+ if (opacityIDX == 0) {
+ m_sSpecialRenderData.alpha = std::stof(r);
+ m_sSpecialRenderData.alphaInactive = std::stof(r);
+ } else if (opacityIDX == 1) {
+ m_sSpecialRenderData.alphaInactive = std::stof(r);
+ m_sSpecialRenderData.alphaInactiveOverride = false;
+ } else {
+ throw std::runtime_error("more than 2 alpha values");
+ }
+
+ opacityIDX++;
+ }
+ }
+ } catch (std::exception& e) { Debug::log(ERR, "Opacity rule \"{}\" failed with: {}", r.szRule, e.what()); }
+ } else if (r.szRule == "noanim") {
+ m_sAdditionalConfigData.forceNoAnims = true;
+ } else if (r.szRule.starts_with("animation")) {
+ auto STYLE = r.szRule.substr(r.szRule.find_first_of(' ') + 1);
+ m_sAdditionalConfigData.animationStyle = STYLE;
+ } else if (r.szRule.starts_with("bordercolor")) {
+ try {
+ // Each vector will only get used if it has at least one color
+ CGradientValueData activeBorderGradient = {};
+ CGradientValueData inactiveBorderGradient = {};
+ bool active = true;
+ CVarList colorsAndAngles = CVarList(removeBeginEndSpacesTabs(r.szRule.substr(r.szRule.find_first_of(' ') + 1)), 0, 's', true);
+
+ // Basic form has only two colors, everything else can be parsed as a gradient
+ if (colorsAndAngles.size() == 2 && !colorsAndAngles[1].contains("deg")) {
+ m_sSpecialRenderData.activeBorderColor = CGradientValueData(CColor(configStringToInt(colorsAndAngles[0])));
+ m_sSpecialRenderData.inactiveBorderColor = CGradientValueData(CColor(configStringToInt(colorsAndAngles[1])));
+ return;
+ }
+
+ for (auto& token : colorsAndAngles) {
+ // The first angle, or an explicit "0deg", splits the two gradients
+ if (active && token.contains("deg")) {
+ activeBorderGradient.m_fAngle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0);
+ active = false;
+ } else if (token.contains("deg"))
+ inactiveBorderGradient.m_fAngle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0);
+ else if (active)
+ activeBorderGradient.m_vColors.push_back(configStringToInt(token));
+ else
+ inactiveBorderGradient.m_vColors.push_back(configStringToInt(token));
+ }
+
+ // Includes sanity checks for the number of colors in each gradient
+ if (activeBorderGradient.m_vColors.size() > 10 || inactiveBorderGradient.m_vColors.size() > 10)
+ Debug::log(WARN, "Bordercolor rule \"{}\" has more than 10 colors in one gradient, ignoring", r.szRule);
+ else if (activeBorderGradient.m_vColors.empty())
+ Debug::log(WARN, "Bordercolor rule \"{}\" has no colors, ignoring", r.szRule);
+ else if (inactiveBorderGradient.m_vColors.empty())
+ m_sSpecialRenderData.activeBorderColor = activeBorderGradient;
+ else {
+ m_sSpecialRenderData.activeBorderColor = activeBorderGradient;
+ m_sSpecialRenderData.inactiveBorderColor = inactiveBorderGradient;
+ }
+ } catch (std::exception& e) { Debug::log(ERR, "BorderColor rule \"{}\" failed with: {}", r.szRule, e.what()); }
+ } else if (r.szRule == "dimaround") {
+ m_sAdditionalConfigData.dimAround = true;
+ } else if (r.szRule == "keepaspectratio") {
+ m_sAdditionalConfigData.keepAspectRatio = true;
+ } else if (r.szRule.starts_with("xray")) {
+ CVarList vars(r.szRule, 0, ' ');
+
+ try {
+ m_sAdditionalConfigData.xray = configStringToInt(vars[1]);
+ } catch (...) {}
+ } else if (r.szRule.starts_with("idleinhibit")) {
+ auto IDLERULE = r.szRule.substr(r.szRule.find_first_of(' ') + 1);
+
+ if (IDLERULE == "none")
+ m_eIdleInhibitMode = IDLEINHIBIT_NONE;
+ else if (IDLERULE == "always")
+ m_eIdleInhibitMode = IDLEINHIBIT_ALWAYS;
+ else if (IDLERULE == "focus")
+ m_eIdleInhibitMode = IDLEINHIBIT_FOCUS;
+ else if (IDLERULE == "fullscreen")
+ m_eIdleInhibitMode = IDLEINHIBIT_FULLSCREEN;
+ else
+ Debug::log(ERR, "Rule idleinhibit: unknown mode {}", IDLERULE);
+ } else if (r.szRule.starts_with("maxsize")) {
+ try {
+ if (!m_bIsFloating)
+ return;
+ const auto VEC = configStringToVector2D(r.szRule.substr(8));
+ if (VEC.x < 1 || VEC.y < 1) {
+ Debug::log(ERR, "Invalid size for maxsize");
+ return;
+ }
+
+ m_sAdditionalConfigData.maxSize = VEC;
+ m_vRealSize = Vector2D(std::min((double)m_sAdditionalConfigData.maxSize.toUnderlying().x, m_vRealSize.goal().x),
+ std::min((double)m_sAdditionalConfigData.maxSize.toUnderlying().y, m_vRealSize.goal().y));
+ g_pXWaylandManager->setWindowSize(this, m_vRealSize.goal());
+ setHidden(false);
+ } catch (std::exception& e) { Debug::log(ERR, "maxsize rule \"{}\" failed with: {}", r.szRule, e.what()); }
+ } else if (r.szRule.starts_with("minsize")) {
+ try {
+ if (!m_bIsFloating)
+ return;
+ const auto VEC = configStringToVector2D(r.szRule.substr(8));
+ if (VEC.x < 1 || VEC.y < 1) {
+ Debug::log(ERR, "Invalid size for minsize");
+ return;
+ }
+
+ m_sAdditionalConfigData.minSize = VEC;
+ m_vRealSize = Vector2D(std::max((double)m_sAdditionalConfigData.minSize.toUnderlying().x, m_vRealSize.goal().x),
+ std::max((double)m_sAdditionalConfigData.minSize.toUnderlying().y, m_vRealSize.goal().y));
+ g_pXWaylandManager->setWindowSize(this, m_vRealSize.goal());
+ setHidden(false);
+ } catch (std::exception& e) { Debug::log(ERR, "minsize rule \"{}\" failed with: {}", r.szRule, e.what()); }
+ }
+}
+
+void CWindow::updateDynamicRules() {
+ m_sSpecialRenderData.activeBorderColor = CGradientValueData();
+ m_sSpecialRenderData.inactiveBorderColor = CGradientValueData();
+ m_sSpecialRenderData.alpha = 1.f;
+ m_sSpecialRenderData.alphaInactive = -1.f;
+ m_sAdditionalConfigData.forceNoBlur = false;
+ m_sAdditionalConfigData.forceNoBorder = false;
+ m_sAdditionalConfigData.forceNoShadow = false;
+ m_sAdditionalConfigData.forceNoDim = false;
+ if (!m_sAdditionalConfigData.forceOpaqueOverridden)
+ m_sAdditionalConfigData.forceOpaque = false;
+ m_sAdditionalConfigData.maxSize = Vector2D(std::numeric_limits<double>::max(), std::numeric_limits<double>::max());
+ m_sAdditionalConfigData.minSize = Vector2D(20, 20);
+ m_sAdditionalConfigData.forceNoAnims = false;
+ m_sAdditionalConfigData.animationStyle = std::string("");
+ m_sAdditionalConfigData.rounding = -1;
+ m_sAdditionalConfigData.dimAround = false;
+ m_sAdditionalConfigData.forceRGBX = false;
+ m_sAdditionalConfigData.borderSize = -1;
+ m_sAdditionalConfigData.keepAspectRatio = false;
+ m_sAdditionalConfigData.xray = -1;
+ m_sAdditionalConfigData.forceTearing = false;
+ m_sAdditionalConfigData.nearestNeighbor = false;
+ m_eIdleInhibitMode = IDLEINHIBIT_NONE;
+
+ const auto WINDOWRULES = g_pConfigManager->getMatchingRules(this);
+ for (auto& r : WINDOWRULES) {
+ applyDynamicRule(r);
+ }
+
+ g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_iMonitorID);
+}
+
+// check if the point is "hidden" under a rounded corner of the window
+// it is assumed that the point is within the real window box (m_vRealPosition, m_vRealSize)
+// otherwise behaviour is undefined
+bool CWindow::isInCurvedCorner(double x, double y) {
+ const int ROUNDING = rounding();
+ if (getRealBorderSize() >= ROUNDING)
+ return false;
+
+ // (x0, y0), (x0, y1), ... are the center point of rounding at each corner
+ double x0 = m_vRealPosition.value().x + ROUNDING;
+ double y0 = m_vRealPosition.value().y + ROUNDING;
+ double x1 = m_vRealPosition.value().x + m_vRealSize.value().x - ROUNDING;
+ double y1 = m_vRealPosition.value().y + m_vRealSize.value().y - ROUNDING;
+
+ if (x < x0 && y < y0) {
+ return Vector2D{x0, y0}.distance(Vector2D{x, y}) > (double)ROUNDING;
+ }
+ if (x > x1 && y < y0) {
+ return Vector2D{x1, y0}.distance(Vector2D{x, y}) > (double)ROUNDING;
+ }
+ if (x < x0 && y > y1) {
+ return Vector2D{x0, y1}.distance(Vector2D{x, y}) > (double)ROUNDING;
+ }
+ if (x > x1 && y > y1) {
+ return Vector2D{x1, y1}.distance(Vector2D{x, y}) > (double)ROUNDING;
+ }
+
+ return false;
+}
+
+void findExtensionForVector2D(wlr_surface* surface, int x, int y, void* data) {
+ const auto DATA = (SExtensionFindingData*)data;
+
+ CBox box = {DATA->origin.x + x, DATA->origin.y + y, surface->current.width, surface->current.height};
+
+ if (box.containsPoint(DATA->vec))
+ *DATA->found = surface;
+}
+
+// checks if the wayland window has a popup at pos
+bool CWindow::hasPopupAt(const Vector2D& pos) {
+ if (m_bIsX11)
+ return false;
+
+ wlr_surface* resultSurf = nullptr;
+ Vector2D origin = m_vRealPosition.value();
+ SExtensionFindingData data = {origin, pos, &resultSurf};
+ wlr_xdg_surface_for_each_popup_surface(m_uSurface.xdg, findExtensionForVector2D, &data);
+
+ return resultSurf;
+}
+
+void CWindow::applyGroupRules() {
+ if ((m_eGroupRules & GROUP_SET && m_bFirstMap) || m_eGroupRules & GROUP_SET_ALWAYS)
+ createGroup();
+
+ if (m_sGroupData.pNextWindow && ((m_eGroupRules & GROUP_LOCK && m_bFirstMap) || m_eGroupRules & GROUP_LOCK_ALWAYS))
+ getGroupHead()->m_sGroupData.locked = true;
+}
+
+void CWindow::createGroup() {
+ if (m_sGroupData.deny) {
+ Debug::log(LOG, "createGroup: window:{:x},title:{} is denied as a group, ignored", (uintptr_t)this, this->m_szTitle);
+ return;
+ }
+
+ if (!m_sGroupData.pNextWindow) {
+ m_sGroupData.pNextWindow = this;
+ m_sGroupData.head = true;
+ m_sGroupData.locked = false;
+ m_sGroupData.deny = false;
+
+ addWindowDeco(std::make_unique<CHyprGroupBarDecoration>(this));
+
+ g_pLayoutManager->getCurrentLayout()->recalculateWindow(this);
+ g_pCompositor->updateAllWindowsAnimatedDecorationValues();
+ }
+}
+
+void CWindow::destroyGroup() {
+ if (m_sGroupData.pNextWindow == this) {
+ if (m_eGroupRules & GROUP_SET_ALWAYS) {
+ Debug::log(LOG, "destoryGroup: window:{:x},title:{} has rule [group set always], ignored", (uintptr_t)this, this->m_szTitle);
+ return;
+ }
+ m_sGroupData.pNextWindow = nullptr;
+ updateWindowDecos();
+ return;
+ }
+
+ CWindow* curr = this;
+ std::vector<CWindow*> members;
+ do {
+ const auto PLASTWIN = curr;
+ curr = curr->m_sGroupData.pNextWindow;
+ PLASTWIN->m_sGroupData.pNextWindow = nullptr;
+ curr->setHidden(false);
+ members.push_back(curr);
+ } while (curr != this);
+
+ for (auto& w : members) {
+ if (w->m_sGroupData.head)
+ g_pLayoutManager->getCurrentLayout()->onWindowRemoved(curr);
+ w->m_sGroupData.head = false;
+ }
+
+ const bool GROUPSLOCKEDPREV = g_pKeybindManager->m_bGroupsLocked;
+ g_pKeybindManager->m_bGroupsLocked = true;
+ for (auto& w : members) {
+ g_pLayoutManager->getCurrentLayout()->onWindowCreated(w);
+ w->updateWindowDecos();
+ }
+ g_pKeybindManager->m_bGroupsLocked = GROUPSLOCKEDPREV;
+}
+
+CWindow* CWindow::getGroupHead() {
+ CWindow* curr = this;
+ while (!curr->m_sGroupData.head)
+ curr = curr->m_sGroupData.pNextWindow;
+ return curr;
+}
+
+CWindow* CWindow::getGroupTail() {
+ CWindow* curr = this;
+ while (!curr->m_sGroupData.pNextWindow->m_sGroupData.head)
+ curr = curr->m_sGroupData.pNextWindow;
+ return curr;
+}
+
+CWindow* CWindow::getGroupCurrent() {
+ CWindow* curr = this;
+ while (curr->isHidden())
+ curr = curr->m_sGroupData.pNextWindow;
+ return curr;
+}
+
+int CWindow::getGroupSize() {
+ int size = 1;
+ CWindow* curr = this;
+ while (curr->m_sGroupData.pNextWindow != this) {
+ curr = curr->m_sGroupData.pNextWindow;
+ size++;
+ }
+ return size;
+}
+
+bool CWindow::canBeGroupedInto(CWindow* pWindow) {
+ return !g_pKeybindManager->m_bGroupsLocked // global group lock disengaged
+ && ((m_eGroupRules & GROUP_INVADE && m_bFirstMap) // window ignore local group locks, or
+ || (!pWindow->getGroupHead()->m_sGroupData.locked // target unlocked
+ && !(m_sGroupData.pNextWindow && getGroupHead()->m_sGroupData.locked))) // source unlocked or isn't group
+ && !m_sGroupData.deny // source is not denied entry
+ && !(m_eGroupRules & GROUP_BARRED && m_bFirstMap); // group rule doesn't prevent adding window
+}
+
+CWindow* CWindow::getGroupWindowByIndex(int index) {
+ const int SIZE = getGroupSize();
+ index = ((index % SIZE) + SIZE) % SIZE;
+ CWindow* curr = getGroupHead();
+ while (index > 0) {
+ curr = curr->m_sGroupData.pNextWindow;
+ index--;
+ }
+ return curr;
+}
+
+void CWindow::setGroupCurrent(CWindow* pWindow) {
+ CWindow* curr = this->m_sGroupData.pNextWindow;
+ bool isMember = false;
+ while (curr != this) {
+ if (curr == pWindow) {
+ isMember = true;
+ break;
+ }
+ curr = curr->m_sGroupData.pNextWindow;
+ }
+
+ if (!isMember && pWindow != this)
+ return;
+
+ const auto PCURRENT = getGroupCurrent();
+ const bool FULLSCREEN = PCURRENT->m_bIsFullscreen;
+ const auto WORKSPACE = g_pCompositor->getWorkspaceByID(PCURRENT->m_iWorkspaceID);
+
+ const auto PWINDOWSIZE = PCURRENT->m_vRealSize.goal();
+ const auto PWINDOWPOS = PCURRENT->m_vRealPosition.goal();
+
+ const auto CURRENTISFOCUS = PCURRENT == g_pCompositor->m_pLastWindow;
+
+ if (FULLSCREEN)
+ g_pCompositor->setWindowFullscreen(PCURRENT, false, WORKSPACE->m_efFullscreenMode);
+
+ PCURRENT->setHidden(true);
+ pWindow->setHidden(false); // can remove m_pLastWindow
+
+ g_pLayoutManager->getCurrentLayout()->replaceWindowDataWith(PCURRENT, pWindow);
+
+ if (PCURRENT->m_bIsFloating) {
+ pWindow->m_vRealPosition.setValueAndWarp(PWINDOWPOS);
+ pWindow->m_vRealSize.setValueAndWarp(PWINDOWSIZE);
+ }
+
+ g_pCompositor->updateAllWindowsAnimatedDecorationValues();
+
+ if (CURRENTISFOCUS)
+ g_pCompositor->focusWindow(pWindow);
+
+ if (FULLSCREEN)
+ g_pCompositor->setWindowFullscreen(pWindow, true, WORKSPACE->m_efFullscreenMode);
+
+ g_pHyprRenderer->damageWindow(pWindow);
+
+ pWindow->updateWindowDecos();
+}
+
+void CWindow::insertWindowToGroup(CWindow* pWindow) {
+ const auto BEGINAT = this;
+ const auto ENDAT = m_sGroupData.pNextWindow;
+
+ if (!pWindow->getDecorationByType(DECORATION_GROUPBAR))
+ pWindow->addWindowDeco(std::make_unique<CHyprGroupBarDecoration>(pWindow));
+
+ if (!pWindow->m_sGroupData.pNextWindow) {
+ BEGINAT->m_sGroupData.pNextWindow = pWindow;
+ pWindow->m_sGroupData.pNextWindow = ENDAT;
+ pWindow->m_sGroupData.head = false;
+ return;
+ }
+
+ const auto SHEAD = pWindow->getGroupHead();
+ const auto STAIL = pWindow->getGroupTail();
+
+ SHEAD->m_sGroupData.head = false;
+ BEGINAT->m_sGroupData.pNextWindow = SHEAD;
+ STAIL->m_sGroupData.pNextWindow = ENDAT;
+}
+
+CWindow* CWindow::getGroupPrevious() {
+ CWindow* curr = m_sGroupData.pNextWindow;
+
+ while (curr != this && curr->m_sGroupData.pNextWindow != this)
+ curr = curr->m_sGroupData.pNextWindow;
+
+ return curr;
+}
+
+void CWindow::switchWithWindowInGroup(CWindow* pWindow) {
+ if (!m_sGroupData.pNextWindow || !pWindow->m_sGroupData.pNextWindow)
+ return;
+
+ if (m_sGroupData.pNextWindow == pWindow) { // A -> this -> pWindow -> B >> A -> pWindow -> this -> B
+ getGroupPrevious()->m_sGroupData.pNextWindow = pWindow;
+ m_sGroupData.pNextWindow = pWindow->m_sGroupData.pNextWindow;
+ pWindow->m_sGroupData.pNextWindow = this;
+
+ } else if (pWindow->m_sGroupData.pNextWindow == this) { // A -> pWindow -> this -> B >> A -> this -> pWindow -> B
+ pWindow->getGroupPrevious()->m_sGroupData.pNextWindow = this;
+ pWindow->m_sGroupData.pNextWindow = m_sGroupData.pNextWindow;
+ m_sGroupData.pNextWindow = pWindow;
+
+ } else { // A -> this -> B | C -> pWindow -> D >> A -> pWindow -> B | C -> this -> D
+ std::swap(m_sGroupData.pNextWindow, pWindow->m_sGroupData.pNextWindow);
+ std::swap(getGroupPrevious()->m_sGroupData.pNextWindow, pWindow->getGroupPrevious()->m_sGroupData.pNextWindow);
+ }
+
+ std::swap(m_sGroupData.head, pWindow->m_sGroupData.head);
+ std::swap(m_sGroupData.locked, pWindow->m_sGroupData.locked);
+}
+
+void CWindow::updateGroupOutputs() {
+ if (!m_sGroupData.pNextWindow)
+ return;
+
+ CWindow* curr = m_sGroupData.pNextWindow;
+
+ while (curr != this) {
+ curr->m_iMonitorID = m_iMonitorID;
+ curr->moveToWorkspace(m_iWorkspaceID);
+
+ curr->m_vRealPosition = m_vRealPosition.goal();
+ curr->m_vRealSize = m_vRealSize.goal();
+
+ curr = curr->m_sGroupData.pNextWindow;
+ }
+}
+
+Vector2D CWindow::middle() {
+ return m_vRealPosition.goal() + m_vRealSize.goal() / 2.f;
+}
+
+bool CWindow::opaque() {
+ if (m_fAlpha.value() != 1.f || m_fActiveInactiveAlpha.value() != 1.f)
+ return false;
+
+ if (m_vRealSize.goal().floor() != m_vReportedSize)
+ return false;
+
+ const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(m_iWorkspaceID);
+
+ if (m_pWLSurface.small() && !m_pWLSurface.m_bFillIgnoreSmall)
+ return false;
+
+ if (PWORKSPACE->m_fAlpha.value() != 1.f)
+ return false;
+
+ if (m_bIsX11)
+ return !m_uSurface.xwayland->has_alpha;
+
+ if (m_uSurface.xdg->surface->opaque)
+ return true;
+
+ const auto EXTENTS = pixman_region32_extents(&m_uSurface.xdg->surface->opaque_region);
+ if (EXTENTS->x2 - EXTENTS->x1 >= m_uSurface.xdg->surface->current.buffer_width && EXTENTS->y2 - EXTENTS->y1 >= m_uSurface.xdg->surface->current.buffer_height)
+ return true;
+
+ return false;
+}
+
+float CWindow::rounding() {
+ static auto PROUNDING = CConfigValue<Hyprlang::INT>("decoration:rounding");
+
+ float rounding = m_sAdditionalConfigData.rounding.toUnderlying() == -1 ? *PROUNDING : m_sAdditionalConfigData.rounding.toUnderlying();
+
+ return m_sSpecialRenderData.rounding ? rounding : 0;
+}
+
+void CWindow::updateSpecialRenderData() {
+ const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(m_iWorkspaceID);
+ const auto WORKSPACERULE = PWORKSPACE ? g_pConfigManager->getWorkspaceRuleFor(PWORKSPACE) : SWorkspaceRule{};
+ bool border = true;
+
+ static auto PNOBORDERONFLOATING = CConfigValue<Hyprlang::INT>("general:no_border_on_floating");
+
+ if (m_bIsFloating && *PNOBORDERONFLOATING == 1)
+ border = false;
+
+ m_sSpecialRenderData.border = WORKSPACERULE.border.value_or(border);
+ m_sSpecialRenderData.borderSize = WORKSPACERULE.borderSize.value_or(-1);
+ m_sSpecialRenderData.decorate = WORKSPACERULE.decorate.value_or(true);
+ m_sSpecialRenderData.rounding = WORKSPACERULE.rounding.value_or(true);
+ m_sSpecialRenderData.shadow = WORKSPACERULE.shadow.value_or(true);
+}
+
+int CWindow::getRealBorderSize() {
+ if (!m_sSpecialRenderData.border || m_sAdditionalConfigData.forceNoBorder)
+ return 0;
+
+ if (m_sAdditionalConfigData.borderSize.toUnderlying() != -1)
+ return m_sAdditionalConfigData.borderSize.toUnderlying();
+
+ if (m_sSpecialRenderData.borderSize.toUnderlying() != -1)
+ return m_sSpecialRenderData.borderSize.toUnderlying();
+
+ static auto PBORDERSIZE = CConfigValue<Hyprlang::INT>("general:border_size");
+
+ return *PBORDERSIZE;
+}
+
+bool CWindow::canBeTorn() {
+ return (m_sAdditionalConfigData.forceTearing.toUnderlying() || m_bTearingHint);
+}
+
+bool CWindow::shouldSendFullscreenState() {
+ const auto MODE = g_pCompositor->getWorkspaceByID(m_iWorkspaceID)->m_efFullscreenMode;
+ return m_bDontSendFullscreen ? false : (m_bFakeFullscreenState || (m_bIsFullscreen && (MODE == FULLSCREEN_FULL)));
+}
+
+void CWindow::setSuspended(bool suspend) {
+ if (suspend == m_bSuspended)
+ return;
+
+ if (m_bIsX11)
+ return;
+
+ wlr_xdg_toplevel_set_suspended(m_uSurface.xdg->toplevel, suspend);
+ m_bSuspended = suspend;
+}
+
+bool CWindow::visibleOnMonitor(CMonitor* pMonitor) {
+ CBox wbox = {m_vRealPosition.value(), m_vRealSize.value()};
+
+ return wlr_output_layout_intersects(g_pCompositor->m_sWLROutputLayout, pMonitor->output, wbox.pWlr());
+}
+
+void CWindow::setAnimationsToMove() {
+ auto* const PANIMCFG = g_pConfigManager->getAnimationPropertyConfig("windowsMove");
+ m_vRealPosition.setConfig(PANIMCFG);
+ m_vRealSize.setConfig(PANIMCFG);
+ m_bAnimatingIn = false;
+}