From 3cf6dfd7e6ac6f122db730de2a0846960eabb7bd Mon Sep 17 00:00:00 2001 From: Tom Englund Date: Thu, 11 Dec 2025 01:32:11 +0100 Subject: [PATCH 01/15] opengl: default initialize m_capStatus (#12619) ubsan reports under wonky situation a runtime error of uninitialized value lookups because of m_capStatus isnt initialized. so default initialize it. OpenGL.cpp:3331:26: runtime error: load of value 190, which is not a valid value for type 'bool' --- src/render/OpenGL.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index 8d0c48aa1..855a94398 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -355,7 +355,7 @@ class CHyprOpenGLImpl { GLsizei height = 0; } m_lastViewport; - std::array m_capStatus; + std::array m_capStatus = {}; std::vector m_drmFormats; bool m_hasModifiers = false; From 1ff801f5f32c2d905aee9ce3845e7c350479e53b Mon Sep 17 00:00:00 2001 From: Maximilian Seidler <78690852+PaideiaDilemma@users.noreply.github.com> Date: Thu, 11 Dec 2025 00:32:51 +0000 Subject: [PATCH 02/15] Nix: fix glaze build for CI and devShell (#12616) --- nix/default.nix | 4 ++-- nix/overlays.nix | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index 1531a7a28..27ecdf604 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -11,7 +11,7 @@ cairo, epoll-shim, git, - glaze, + glaze-hyprland, gtest, hyprcursor, hyprgraphics, @@ -143,7 +143,7 @@ in aquamarine cairo git - glaze + glaze-hyprland gtest hyprcursor hyprgraphics diff --git a/nix/overlays.nix b/nix/overlays.nix index 9d855e77d..fdb3e6528 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -30,6 +30,7 @@ in { inputs.hyprwayland-scanner.overlays.default inputs.hyprwire.overlays.default self.overlays.udis86 + self.overlays.glaze # Hyprland packages themselves (final: _prev: let @@ -110,4 +111,13 @@ in { patches = []; }); }; + + # Even though glaze itself disables it by default, nixpkgs sets ENABLE_SSL set to true. + # Since we don't include openssl, the build failes without the `enableSSL = false;` override + glaze = final: prev: { + glaze-hyprland = prev.glaze.override { + enableSSL = false; + enableInterop = false; + }; + }; } From 9aa313402b1be3df2925076bb1292d03e68bb47f Mon Sep 17 00:00:00 2001 From: Vaxry Date: Thu, 11 Dec 2025 00:50:45 +0000 Subject: [PATCH 03/15] protocols/datadevice: avoid double leave ref https://github.com/hyprwm/Hyprland/discussions/12494 --- src/protocols/core/DataDevice.cpp | 7 +++++++ src/protocols/core/DataDevice.hpp | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/protocols/core/DataDevice.cpp b/src/protocols/core/DataDevice.cpp index a42933ed8..4a24e861c 100644 --- a/src/protocols/core/DataDevice.cpp +++ b/src/protocols/core/DataDevice.cpp @@ -298,10 +298,17 @@ void CWLDataDeviceResource::sendDataOffer(SP offer) { void CWLDataDeviceResource::sendEnter(uint32_t serial, SP surf, const Vector2D& local, SP offer) { if (const auto WL = offer->getWayland(); WL) m_resource->sendEnterRaw(serial, surf->getResource()->resource(), wl_fixed_from_double(local.x), wl_fixed_from_double(local.y), WL->m_resource->resource()); + + m_entered = surf; + // FIXME: X11 } void CWLDataDeviceResource::sendLeave() { + if (!m_entered) + return; + + m_entered.reset(); m_resource->sendLeave(); } diff --git a/src/protocols/core/DataDevice.hpp b/src/protocols/core/DataDevice.hpp index 8b52933e3..b4ad378fa 100644 --- a/src/protocols/core/DataDevice.hpp +++ b/src/protocols/core/DataDevice.hpp @@ -111,8 +111,10 @@ class CWLDataDeviceResource : public IDataDevice { WP m_self; private: - SP m_resource; - wl_client* m_client = nullptr; + SP m_resource; + wl_client* m_client = nullptr; + + WP m_entered; friend class CWLDataDeviceProtocol; }; From 2ca7ad7efc1e20588af5c823ee46f23afad6cf91 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Thu, 11 Dec 2025 12:40:02 +0000 Subject: [PATCH 04/15] ci: disable comments for members --- .github/workflows/new-pr-comment.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/new-pr-comment.yml b/.github/workflows/new-pr-comment.yml index be017db81..36ea19098 100644 --- a/.github/workflows/new-pr-comment.yml +++ b/.github/workflows/new-pr-comment.yml @@ -7,6 +7,13 @@ on: jobs: comment: + if: > + github.event.pull_request.user.login != 'vaxerski' && + github.event.pull_request.user.login != 'fufexan' && + github.event.pull_request.user.login != 'gulafaran' && + github.event.pull_request.user.login != 'ujint34' && + github.event.pull_request.user.login != 'paideiadilemma' && + github.event.pull_request.user.login != 'notashelf' runs-on: ubuntu-latest permissions: pull-requests: write From 5dd224805deb437f6fa83359ee66a4d0f52262d2 Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Thu, 11 Dec 2025 16:29:26 +0000 Subject: [PATCH 05/15] desktop/view: use aliveAndVisible for most things (#12631) --- src/Compositor.cpp | 6 +++--- src/desktop/view/GlobalViewMethods.cpp | 10 +++++----- src/desktop/view/LayerSurface.hpp | 4 ++-- src/desktop/view/View.cpp | 12 ++++++++++++ src/desktop/view/View.hpp | 1 + src/desktop/view/WLSurface.cpp | 10 ++-------- src/desktop/view/WLSurface.hpp | 1 - src/desktop/view/Window.cpp | 2 +- src/managers/input/IdleInhibitor.cpp | 8 ++++---- src/render/Renderer.cpp | 9 +++++---- 10 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 0d9c2f8c8..a0a8c9ac8 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -1130,7 +1130,7 @@ PHLMONITOR CCompositor::getRealMonitorFromOutput(SP out) { SP CCompositor::vectorToLayerPopupSurface(const Vector2D& pos, PHLMONITOR monitor, Vector2D* sCoords, PHLLS* ppLayerSurfaceFound) { for (auto const& lsl : monitor->m_layerSurfaceLayers | std::views::reverse) { for (auto const& ls : lsl | std::views::reverse) { - if (!ls->visible() || ls->m_fadingOut) + if (!ls->aliveAndVisible()) continue; auto SURFACEAT = ls->m_popupHead->at(pos, true); @@ -1150,7 +1150,7 @@ SP CCompositor::vectorToLayerSurface(const Vector2D& pos, st bool aboveLockscreen) { for (auto const& ls : *layerSurfaces | std::views::reverse) { - if (!ls->visible() || ls->m_fadingOut || (aboveLockscreen && ls->m_ruleApplicator->aboveLock().valueOrDefault() != 2)) + if (!ls->aliveAndVisible() || (aboveLockscreen && ls->m_ruleApplicator->aboveLock().valueOrDefault() != 2)) continue; auto [surf, local] = ls->m_layerSurface->m_surface->at(pos - ls->m_geometry.pos(), true); @@ -2347,7 +2347,7 @@ PHLLS CCompositor::getLayerSurfaceFromSurface(SP pSurface) { std::pair, bool> result = {pSurface, false}; for (auto const& ls : m_layers) { - if (!ls->visible() || ls->m_fadingOut) + if (!ls->aliveAndVisible()) continue; if (ls->m_layerSurface->m_surface == pSurface) diff --git a/src/desktop/view/GlobalViewMethods.cpp b/src/desktop/view/GlobalViewMethods.cpp index 97dc99606..83513e816 100644 --- a/src/desktop/view/GlobalViewMethods.cpp +++ b/src/desktop/view/GlobalViewMethods.cpp @@ -18,7 +18,7 @@ std::vector> View::getViewsForWorkspace(PHLWORKSPACE ws) { std::vector> views; for (const auto& w : g_pCompositor->m_windows) { - if (!w->visible() || w->m_workspace != ws) + if (!w->aliveAndVisible() || w->m_workspace != ws) continue; views.emplace_back(w); @@ -38,7 +38,7 @@ std::vector> View::getViewsForWorkspace(PHLWORKSPACE ws) { w->m_popupHead->breadthfirst( [&views](SP s, void* data) { auto surf = s->wlSurface(); - if (!surf || !s->visible()) + if (!surf || !s->aliveAndVisible()) return; views.emplace_back(surf->view()); @@ -48,7 +48,7 @@ std::vector> View::getViewsForWorkspace(PHLWORKSPACE ws) { } for (const auto& l : g_pCompositor->m_layers) { - if (!l->visible() || l->m_monitor != ws->m_monitor) + if (!l->aliveAndVisible() || l->m_monitor != ws->m_monitor) continue; views.emplace_back(l); @@ -56,7 +56,7 @@ std::vector> View::getViewsForWorkspace(PHLWORKSPACE ws) { l->m_popupHead->breadthfirst( [&views](SP p, void* data) { auto surf = p->wlSurface(); - if (!surf || !p->visible()) + if (!surf || !p->aliveAndVisible()) return; views.emplace_back(surf->view()); @@ -65,7 +65,7 @@ std::vector> View::getViewsForWorkspace(PHLWORKSPACE ws) { } for (const auto& v : g_pCompositor->m_otherViews) { - if (!v->visible() || !v->desktopComponent()) + if (!v->aliveAndVisible() || !v->desktopComponent()) continue; if (v->type() == VIEW_TYPE_LOCK_SCREEN) { diff --git a/src/desktop/view/LayerSurface.hpp b/src/desktop/view/LayerSurface.hpp index 3bca03d63..3660ee743 100644 --- a/src/desktop/view/LayerSurface.hpp +++ b/src/desktop/view/LayerSurface.hpp @@ -93,13 +93,13 @@ namespace Desktop::View { inline bool validMapped(PHLLS l) { if (!valid(l)) return false; - return l->visible(); + return l->aliveAndVisible(); } inline bool validMapped(PHLLSREF l) { if (!valid(l)) return false; - return l->visible(); + return l->aliveAndVisible(); } } diff --git a/src/desktop/view/View.cpp b/src/desktop/view/View.cpp index e7c6ce3ae..17a10c64b 100644 --- a/src/desktop/view/View.cpp +++ b/src/desktop/view/View.cpp @@ -1,4 +1,5 @@ #include "View.hpp" +#include "../../protocols/core/Compositor.hpp" using namespace Desktop; using namespace Desktop::View; @@ -14,3 +15,14 @@ IView::IView(SP pWlSurface) : m_wlSurface(pWlSurface) SP IView::resource() const { return m_wlSurface ? m_wlSurface->resource() : nullptr; } + +bool IView::aliveAndVisible() const { + auto res = resource(); + if (!res) + return false; + + if (!res->m_mapped) + return false; + + return visible(); +} diff --git a/src/desktop/view/View.hpp b/src/desktop/view/View.hpp index 0f412d2a1..4d777c36d 100644 --- a/src/desktop/view/View.hpp +++ b/src/desktop/view/View.hpp @@ -18,6 +18,7 @@ namespace Desktop::View { virtual SP wlSurface() const; virtual SP resource() const; + virtual bool aliveAndVisible() const; virtual eViewType type() const = 0; virtual bool visible() const = 0; virtual bool desktopComponent() const = 0; diff --git a/src/desktop/view/WLSurface.cpp b/src/desktop/view/WLSurface.cpp index 1bf90ae8d..8c3ce9db4 100644 --- a/src/desktop/view/WLSurface.cpp +++ b/src/desktop/view/WLSurface.cpp @@ -38,7 +38,7 @@ SP CWLSurface::resource() const { } bool CWLSurface::small() const { - if (!m_view || !m_view->visible() || m_view->type() != VIEW_TYPE_WINDOW || !exists()) + if (!m_view || !m_view->aliveAndVisible() || m_view->type() != VIEW_TYPE_WINDOW || !exists()) return false; if (!m_resource->m_current.texture) @@ -51,7 +51,7 @@ bool CWLSurface::small() const { } Vector2D CWLSurface::correctSmallVec() const { - if (!m_view || !m_view->visible() || m_view->type() != VIEW_TYPE_WINDOW || !exists() || !small() || !m_fillIgnoreSmall) + if (!m_view || !m_view->aliveAndVisible() || m_view->type() != VIEW_TYPE_WINDOW || !exists() || !small() || !m_fillIgnoreSmall) return {}; const auto SIZE = getViewporterCorrectedSize(); @@ -171,12 +171,6 @@ SP CWLSurface::constraint() const { return m_constraint.lock(); } -bool CWLSurface::visible() { - if (m_view) - return m_view->visible(); - return true; // non-desktop, we don't know much. -} - SP CWLSurface::fromResource(SP pSurface) { if (!pSurface) return nullptr; diff --git a/src/desktop/view/WLSurface.hpp b/src/desktop/view/WLSurface.hpp index 13c825941..944e863b7 100644 --- a/src/desktop/view/WLSurface.hpp +++ b/src/desktop/view/WLSurface.hpp @@ -38,7 +38,6 @@ namespace Desktop::View { Vector2D correctSmallVecBuf() const; // returns a corrective vector for small() surfaces, in BL coords Vector2D getViewporterCorrectedSize() const; CRegion computeDamage() const; // logical coordinates. May be wrong if the surface is unassigned - bool visible(); bool keyboardFocusable() const; SP view() const; diff --git a/src/desktop/view/Window.cpp b/src/desktop/view/Window.cpp index e27129a1f..f83235403 100644 --- a/src/desktop/view/Window.cpp +++ b/src/desktop/view/Window.cpp @@ -154,7 +154,7 @@ eViewType CWindow::type() const { } bool CWindow::visible() const { - return m_isMapped && !m_hidden && m_wlSurface && m_wlSurface->resource(); + return !m_hidden && ((m_isMapped && m_wlSurface && m_wlSurface->resource()) || (m_fadingOut && m_alpha->value() != 0.F)); } std::optional CWindow::logicalBox() const { diff --git a/src/managers/input/IdleInhibitor.cpp b/src/managers/input/IdleInhibitor.cpp index 0034a10fa..5750080c2 100644 --- a/src/managers/input/IdleInhibitor.cpp +++ b/src/managers/input/IdleInhibitor.cpp @@ -40,10 +40,10 @@ void CInputManager::recheckIdleInhibitorStatus() { auto WLSurface = Desktop::View::CWLSurface::fromResource(ii->inhibitor->m_surface.lock()); - if (!WLSurface) + if (!WLSurface || !WLSurface->view()) continue; - if (WLSurface->visible()) { + if (WLSurface->view()->aliveAndVisible()) { PROTO::idle->setInhibit(true); return; } @@ -85,10 +85,10 @@ bool CInputManager::isWindowInhibiting(const PHLWINDOW& w, bool onlyHl) { auto WLSurface = Desktop::View::CWLSurface::fromResource(surf); - if (!WLSurface) + if (!WLSurface || !WLSurface->view()) return; - if (WLSurface->visible()) + if (WLSurface->view()->aliveAndVisible()) *sc(data) = true; }, &isInhibiting); diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 38cd951d9..b75dd1e0f 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -684,8 +684,9 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T return; } - if (!popup->visible()) + if (!popup->aliveAndVisible()) return; + const auto pos = popup->coordsRelativeToParent(); const Vector2D oldPos = renderdata.pos; renderdata.pos += pos; @@ -802,7 +803,7 @@ void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::s if (popups) { pLayer->m_popupHead->breadthfirst( [this, &renderdata](WP popup, void* data) { - if (!popup->visible()) + if (!popup->aliveAndVisible()) return; const auto SURF = popup->wlSurface()->resource(); @@ -1708,7 +1709,7 @@ void CHyprRenderer::renderWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace void CHyprRenderer::sendFrameEventsToWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now) { for (const auto& view : Desktop::View::getViewsForWorkspace(pWorkspace)) { - if (!view->visible()) + if (!view->aliveAndVisible()) continue; view->wlSurface()->resource()->frame(now); @@ -2505,7 +2506,7 @@ void CHyprRenderer::makeSnapshot(WP popup) { if (!PMONITOR || !PMONITOR->m_output || PMONITOR->m_pixelSize.x <= 0 || PMONITOR->m_pixelSize.y <= 0) return; - if (!popup->visible()) + if (!popup->aliveAndVisible()) return; Debug::log(LOG, "renderer: making a snapshot of {:x}", rc(popup.get())); From 75f6435f70dee8f8b685a02c52db7ba16f5db39c Mon Sep 17 00:00:00 2001 From: Tom Englund Date: Thu, 11 Dec 2025 19:54:43 +0100 Subject: [PATCH 06/15] window: only damage floating on clamped size change (#12633) currently it damage the entire window if its floating and not x11 nor fullscreen meaning damage isnt working at all for floating. im tracing this back to a364df4 where the logic changed from damaging window only if size was being forced to now unconditonally doing it. change clampWindowSize to return as a bool if size changed and only damage window if it got clamped. --- src/desktop/view/Window.cpp | 17 +++++++++++------ src/desktop/view/Window.hpp | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/desktop/view/Window.cpp b/src/desktop/view/Window.cpp index f83235403..b01dcabc0 100644 --- a/src/desktop/view/Window.cpp +++ b/src/desktop/view/Window.cpp @@ -1197,14 +1197,19 @@ int CWindow::surfacesCount() { return no; } -void CWindow::clampWindowSize(const std::optional minSize, const std::optional maxSize) { +bool CWindow::clampWindowSize(const std::optional minSize, const std::optional maxSize) { const Vector2D REALSIZE = m_realSize->goal(); const Vector2D MAX = isFullscreen() ? Vector2D{INFINITY, INFINITY} : maxSize.value_or(Vector2D{INFINITY, INFINITY}); const Vector2D NEWSIZE = REALSIZE.clamp(minSize.value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), MAX); - const Vector2D DELTA = REALSIZE - NEWSIZE; + const bool changed = !(NEWSIZE == REALSIZE); - *m_realPosition = m_realPosition->goal() + DELTA / 2.0; - *m_realSize = NEWSIZE; + if (changed) { + const Vector2D DELTA = REALSIZE - NEWSIZE; + *m_realPosition = m_realPosition->goal() + DELTA / 2.0; + *m_realSize = NEWSIZE; + } + + return changed; } bool CWindow::isFullscreen() { @@ -2554,8 +2559,8 @@ void CWindow::commitWindow() { const auto MINSIZE = m_xdgSurface->m_toplevel->layoutMinSize(); const auto MAXSIZE = m_xdgSurface->m_toplevel->layoutMaxSize(); - clampWindowSize(MINSIZE, MAXSIZE > Vector2D{1, 1} ? std::optional{MAXSIZE} : std::nullopt); - g_pHyprRenderer->damageWindow(m_self.lock()); + if (clampWindowSize(MINSIZE, MAXSIZE > Vector2D{1, 1} ? std::optional{MAXSIZE} : std::nullopt)) + g_pHyprRenderer->damageWindow(m_self.lock()); } if (!m_workspace->m_visible) diff --git a/src/desktop/view/Window.hpp b/src/desktop/view/Window.hpp index e1a42eda5..d5c86aac2 100644 --- a/src/desktop/view/Window.hpp +++ b/src/desktop/view/Window.hpp @@ -286,7 +286,7 @@ namespace Desktop::View { bool onSpecialWorkspace(); void activate(bool force = false); int surfacesCount(); - void clampWindowSize(const std::optional minSize, const std::optional maxSize); + bool clampWindowSize(const std::optional minSize, const std::optional maxSize); bool isFullscreen(); bool isEffectiveInternalFSMode(const eFullscreenMode) const; int getRealBorderSize() const; From 5700736505557363f8574f5abcc6c0f489821466 Mon Sep 17 00:00:00 2001 From: Dominick DiMaggio Date: Thu, 11 Dec 2025 18:50:57 -0500 Subject: [PATCH 07/15] cm: handle CM for SDR content with cm=hdr, cm_sdr_eotf=2 (#12127) --- src/helpers/Monitor.cpp | 13 ++++++++----- src/render/OpenGL.cpp | 4 +++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index fa76a982d..d01d2ac85 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -1799,8 +1799,8 @@ uint16_t CMonitor::isDSBlocked(bool full) { return reasons; } - if (needsCM() && *PNONSHADER != CM_NS_IGNORE && !canNoShaderCM() && (!inHDR() || (PSURFACE->m_colorManagement.valid() && PSURFACE->m_colorManagement->isWindowsScRGB())) && - *PPASS != 1) + const bool surfaceIsHDR = PSURFACE->m_colorManagement.valid() && (PSURFACE->m_colorManagement->isHDR() || PSURFACE->m_colorManagement->isWindowsScRGB()); + if (needsCM() && *PNONSHADER != CM_NS_IGNORE && !canNoShaderCM() && ((inHDR() && (*PPASS == 0 || !surfaceIsHDR)) || (!inHDR() && (*PPASS != 1 || surfaceIsHDR)))) reasons |= DS_BLOCK_CM; return reasons; @@ -2065,10 +2065,13 @@ bool CMonitor::canNoShaderCM() { if (SRC_DESC->icc.fd >= 0 || m_imageDescription.icc.fd >= 0) return false; // no ICC support + static auto PSDREOTF = CConfigValue("render:cm_sdr_eotf"); // only primaries differ - if (SRC_DESC->transferFunction == m_imageDescription.transferFunction && SRC_DESC->transferFunctionPower == m_imageDescription.transferFunctionPower && - (!inHDR() || SRC_DESC->luminances == m_imageDescription.luminances) && SRC_DESC->masteringLuminances == m_imageDescription.masteringLuminances && - SRC_DESC->maxCLL == m_imageDescription.maxCLL && SRC_DESC->maxFALL == m_imageDescription.maxFALL) + if ((SRC_DESC->transferFunction == m_imageDescription.transferFunction || + (*PSDREOTF == 2 && SRC_DESC->transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB && + m_imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22)) && + SRC_DESC->transferFunctionPower == m_imageDescription.transferFunctionPower && (!inHDR() || SRC_DESC->luminances == m_imageDescription.luminances) && + SRC_DESC->masteringLuminances == m_imageDescription.masteringLuminances && SRC_DESC->maxCLL == m_imageDescription.maxCLL && SRC_DESC->maxFALL == m_imageDescription.maxFALL) return true; return false; diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index ae01d2a11..6b5e35f1d 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1715,7 +1715,9 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c const bool skipCM = !*PENABLECM || !m_cmSupported /* CM unsupported or disabled */ || m_renderData.pMonitor->doesNoShaderCM() /* no shader needed */ || (imageDescription == m_renderData.pMonitor->m_imageDescription && !data.cmBackToSRGB) /* Source and target have the same image description */ - || (((*PPASS && canPassHDRSurface) || (*PPASS == 1 && !isHDRSurface)) && m_renderData.pMonitor->inFullscreenMode()) /* Fullscreen window with pass cm enabled */; + || (((*PPASS && canPassHDRSurface) || + (*PPASS == 1 && !isHDRSurface && m_renderData.pMonitor->m_cmType != NCMType::CM_HDR && m_renderData.pMonitor->m_cmType != NCMType::CM_HDR_EDID)) && + m_renderData.pMonitor->inFullscreenMode()) /* Fullscreen window with pass cm enabled */; if (!skipCM && !usingFinalShader && (texType == TEXTURE_RGBA || texType == TEXTURE_RGBX)) shader = &m_shaders->m_shCM; From 8dfdcfb35385eabb821e668d327b30ea3e483ab8 Mon Sep 17 00:00:00 2001 From: Tom Englund Date: Fri, 12 Dec 2025 00:59:47 +0100 Subject: [PATCH 08/15] compositor: dont try to focus unmapped window (#12629) * compositor: dont try to focus unmapped window if lastwindow is unmapped it hits getWindowInDirection and nullptr derefs window->m_workspace. and coredumps. triggered by dual monitor and one client on each surface with a combination of animation and killactive / movefocus keybind. * keybindmgr: use newly added aliveAndVisible() use newly added aliveAndVisible() over visible() --- src/Compositor.cpp | 3 +++ src/managers/KeybindManager.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index a0a8c9ac8..b9e2f3157 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -1380,6 +1380,9 @@ PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) { const auto WINDOWIDEALBB = pWindow->isFullscreen() ? CBox{PMONITOR->m_position, PMONITOR->m_size} : pWindow->getWindowIdealBoundingBoxIgnoreReserved(); const auto PWORKSPACE = pWindow->m_workspace; + if (!PWORKSPACE) + return nullptr; // ?? + return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow, pWindow->m_isFloating); } diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index bda1ff5a3..b33ca3c1e 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -1487,7 +1487,7 @@ SDispatchResult CKeybindManager::moveFocusTo(std::string args) { } const auto PLASTWINDOW = Desktop::focusState()->window(); - if (!PLASTWINDOW) { + if (!PLASTWINDOW || !PLASTWINDOW->aliveAndVisible()) { if (*PMONITORFALLBACK) tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(arg)); From 69db0bcae640410b6c587cb0ffd0c89bc8166ff0 Mon Sep 17 00:00:00 2001 From: Tom Englund Date: Fri, 12 Dec 2025 13:47:56 +0100 Subject: [PATCH 09/15] compositor: early return on no monitor (#12637) getMonitorFromVector can return nullptr on empty m_monitors, as such is the case when the compositor is going down and a surface exist. return early in vectorToWindowUnified if that happends. --- src/Compositor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index b9e2f3157..57fac42b4 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -894,7 +894,10 @@ bool CCompositor::monitorExists(PHLMONITOR pMonitor) { } PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t properties, PHLWINDOW pIgnoreWindow) { - const auto PMONITOR = getMonitorFromVector(pos); + const auto PMONITOR = getMonitorFromVector(pos); + if (!PMONITOR) + return nullptr; + static auto PRESIZEONBORDER = CConfigValue("general:resize_on_border"); static auto PBORDERSIZE = CConfigValue("general:border_size"); static auto PBORDERGRABEXTEND = CConfigValue("general:extend_border_grab_area"); From fd5e790d08ae5504a9604d3537172b2c944b003b Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sat, 13 Dec 2025 13:54:59 +0000 Subject: [PATCH 10/15] compositor: return nullptr when cursor is outside of a maximized windows' box --- src/Compositor.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 57fac42b4..d83b93a51 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -996,8 +996,18 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper const WORKSPACEID WSPID = special ? PMONITOR->activeSpecialWorkspaceID() : PMONITOR->activeWorkspaceID(); const auto PWORKSPACE = getWorkspaceByID(WSPID); - if (PWORKSPACE->m_hasFullscreenWindow && !(properties & Desktop::View::SKIP_FULLSCREEN_PRIORITY) && !ONLY_PRIORITY) - return PWORKSPACE->getFullscreenWindow(); + if (PWORKSPACE->m_hasFullscreenWindow && !(properties & Desktop::View::SKIP_FULLSCREEN_PRIORITY) && !ONLY_PRIORITY) { + const auto FS_WINDOW = PWORKSPACE->getFullscreenWindow(); + + if (!FS_WINDOW) + return nullptr; + + // for maximized windows, don't return a window if we are not directly on it. + if (FS_WINDOW->m_fullscreenState.internal != FSMODE_MAXIMIZED || FS_WINDOW->getWindowBoxUnified(properties).containsPoint(pos)) + return PWORKSPACE->getFullscreenWindow(); + else + return nullptr; + } auto found = floating(false); if (found) From 09e195d1f293a876ce21a077af3d7c5047881b79 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sat, 13 Dec 2025 13:55:12 +0000 Subject: [PATCH 11/15] compositor: fix isPointOnReservedArea --- src/Compositor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index d83b93a51..3c67979f7 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -1637,12 +1637,12 @@ bool CCompositor::isPointOnReservedArea(const Vector2D& point, const PHLMONITOR const auto PMONITOR = pMonitor ? pMonitor : getMonitorFromVector(point); auto box = PMONITOR->logicalBox(); - if (VECNOTINRECT(point, box.x - 1, box.y - 1, box.w + 2, box.h + 2)) + if (VECNOTINRECT(point, box.x - 1, box.y - 1, box.x + box.w + 1, box.y + box.h + 1)) return false; PMONITOR->m_reservedArea.applyip(box); - return VECNOTINRECT(point, box.x, box.y, box.x, box.y); + return VECNOTINRECT(point, box.x, box.y, box.x + box.w, box.y + box.h); } CBox CCompositor::calculateX11WorkArea() { From 05ccbb2f2dc3121a48e9c1925357039b27d22a92 Mon Sep 17 00:00:00 2001 From: Luke Barkess <57995669+Brumus14@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:16:58 +0000 Subject: [PATCH 12/15] hyprpm: added plugin author (#12594) --- hyprpm/src/core/DataState.cpp | 57 +++++++++++++++++++------------ hyprpm/src/core/DataState.hpp | 8 ++--- hyprpm/src/core/Plugin.cpp | 48 ++++++++++++++++++++++++++ hyprpm/src/core/Plugin.hpp | 23 ++++++++++++- hyprpm/src/core/PluginManager.cpp | 45 ++++++++++++++---------- hyprpm/src/core/PluginManager.hpp | 8 +++-- hyprpm/src/main.cpp | 38 ++++++++++----------- 7 files changed, 160 insertions(+), 67 deletions(-) create mode 100644 hyprpm/src/core/Plugin.cpp diff --git a/hyprpm/src/core/DataState.cpp b/hyprpm/src/core/DataState.cpp index 42f1d4289..64f3cfa02 100644 --- a/hyprpm/src/core/DataState.cpp +++ b/hyprpm/src/core/DataState.cpp @@ -93,6 +93,7 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) { auto DATA = toml::table{ {"repository", toml::table{ {"name", repo.name}, + {"author", repo.author}, {"hash", repo.hash}, {"url", repo.url}, {"rev", repo.rev} @@ -122,31 +123,32 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) { Debug::die("{}", failureString("Failed to write plugin state")); } -bool DataState::pluginRepoExists(const std::string& urlOrName) { +bool DataState::pluginRepoExists(const SPluginRepoIdentifier identifier) { ensureStateStoreExists(); for (const auto& stateFile : getPluginStates()) { - const auto STATE = toml::parse_file(stateFile.c_str()); - const auto NAME = STATE["repository"]["name"].value_or(""); - const auto URL = STATE["repository"]["url"].value_or(""); + const auto STATE = toml::parse_file(stateFile.c_str()); + const auto NAME = STATE["repository"]["name"].value_or(""); + const auto AUTHOR = STATE["repository"]["author"].value_or(""); + const auto URL = STATE["repository"]["url"].value_or(""); - if (URL == urlOrName || NAME == urlOrName) + if (identifier.matches(URL, NAME, AUTHOR)) return true; } return false; } -void DataState::removePluginRepo(const std::string& urlOrName) { +void DataState::removePluginRepo(const SPluginRepoIdentifier identifier) { ensureStateStoreExists(); for (const auto& stateFile : getPluginStates()) { - const auto STATE = toml::parse_file(stateFile.c_str()); - const auto NAME = STATE["repository"]["name"].value_or(""); - const auto URL = STATE["repository"]["url"].value_or(""); - - if (URL == urlOrName || NAME == urlOrName) { + const auto STATE = toml::parse_file(stateFile.c_str()); + const auto NAME = STATE["repository"]["name"].value_or(""); + const auto AUTHOR = STATE["repository"]["author"].value_or(""); + const auto URL = STATE["repository"]["url"].value_or(""); + if (identifier.matches(URL, NAME, AUTHOR)) { // unload the plugins!! for (const auto& file : std::filesystem::directory_iterator(stateFile.parent_path())) { if (!file.path().string().ends_with(".so")) @@ -219,16 +221,18 @@ std::vector DataState::getAllRepositories() { for (const auto& stateFile : getPluginStates()) { const auto STATE = toml::parse_file(stateFile.c_str()); - const auto NAME = STATE["repository"]["name"].value_or(""); - const auto URL = STATE["repository"]["url"].value_or(""); - const auto REV = STATE["repository"]["rev"].value_or(""); - const auto HASH = STATE["repository"]["hash"].value_or(""); + const auto NAME = STATE["repository"]["name"].value_or(""); + const auto AUTHOR = STATE["repository"]["author"].value_or(""); + const auto URL = STATE["repository"]["url"].value_or(""); + const auto REV = STATE["repository"]["rev"].value_or(""); + const auto HASH = STATE["repository"]["hash"].value_or(""); SPluginRepository repo; - repo.hash = HASH; - repo.name = NAME; - repo.url = URL; - repo.rev = REV; + repo.hash = HASH; + repo.name = NAME; + repo.author = AUTHOR; + repo.url = URL; + repo.rev = REV; for (const auto& [key, val] : STATE) { if (key == "repository") @@ -247,7 +251,7 @@ std::vector DataState::getAllRepositories() { return repos; } -bool DataState::setPluginEnabled(const std::string& name, bool enabled) { +bool DataState::setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled) { ensureStateStoreExists(); for (const auto& stateFile : getPluginStates()) { @@ -256,8 +260,17 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) { if (key == "repository") continue; - if (key.str() != name) - continue; + switch (identifier.type) { + case IDENTIFIER_NAME: + if (key.str() != identifier.name) + continue; + break; + case IDENTIFIER_AUTHOR_NAME: + if (STATE["repository"]["author"] != identifier.author || key.str() != identifier.name) + continue; + break; + default: return false; + } const auto FAILED = STATE[key]["failed"].value_or(false); diff --git a/hyprpm/src/core/DataState.hpp b/hyprpm/src/core/DataState.hpp index dfab535a7..d9872b907 100644 --- a/hyprpm/src/core/DataState.hpp +++ b/hyprpm/src/core/DataState.hpp @@ -15,11 +15,11 @@ namespace DataState { std::vector getPluginStates(); void ensureStateStoreExists(); void addNewPluginRepo(const SPluginRepository& repo); - void removePluginRepo(const std::string& urlOrName); - bool pluginRepoExists(const std::string& urlOrName); + void removePluginRepo(const SPluginRepoIdentifier identifier); + bool pluginRepoExists(const SPluginRepoIdentifier identifier); void updateGlobalState(const SGlobalState& state); void purgeAllCache(); SGlobalState getGlobalState(); - bool setPluginEnabled(const std::string& name, bool enabled); + bool setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled); std::vector getAllRepositories(); -}; \ No newline at end of file +}; diff --git a/hyprpm/src/core/Plugin.cpp b/hyprpm/src/core/Plugin.cpp new file mode 100644 index 000000000..3aaa85925 --- /dev/null +++ b/hyprpm/src/core/Plugin.cpp @@ -0,0 +1,48 @@ +#include "Plugin.hpp" + +SPluginRepoIdentifier SPluginRepoIdentifier::fromUrl(const std::string& url) { + return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = url}; +} + +SPluginRepoIdentifier SPluginRepoIdentifier::fromName(const std::string& name) { + return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = name}; +} + +SPluginRepoIdentifier SPluginRepoIdentifier::fromAuthorName(const std::string& author, const std::string& name) { + return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author}; +} + +SPluginRepoIdentifier SPluginRepoIdentifier::fromString(const std::string& string) { + if (string.find(':') != std::string::npos) { + return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = string}; + } else { + auto slashPos = string.find('/'); + if (slashPos != std::string::npos) { + std::string author = string.substr(0, slashPos); + std::string name = string.substr(slashPos + 1, string.size() - slashPos - 1); + return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author}; + } else { + return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = string}; + } + } +} + +std::string SPluginRepoIdentifier::toString() const { + switch (type) { + case IDENTIFIER_NAME: return name; + case IDENTIFIER_AUTHOR_NAME: return author + '/' + name; + case IDENTIFIER_URL: return url; + } + + return ""; +} + +bool SPluginRepoIdentifier::matches(const std::string& url, const std::string& name, const std::string& author) const { + switch (type) { + case IDENTIFIER_URL: return this->url == url; + case IDENTIFIER_NAME: return this->name == name; + case IDENTIFIER_AUTHOR_NAME: return this->author == author && this->name == name; + } + + return false; +} diff --git a/hyprpm/src/core/Plugin.hpp b/hyprpm/src/core/Plugin.hpp index e66031c98..a8c740848 100644 --- a/hyprpm/src/core/Plugin.hpp +++ b/hyprpm/src/core/Plugin.hpp @@ -14,6 +14,27 @@ struct SPluginRepository { std::string url; std::string rev; std::string name; + std::string author; std::vector plugins; std::string hash; -}; \ No newline at end of file +}; + +enum ePluginRepoIdentifierType { + IDENTIFIER_URL, + IDENTIFIER_NAME, + IDENTIFIER_AUTHOR_NAME +}; + +struct SPluginRepoIdentifier { + ePluginRepoIdentifierType type; + std::string url = ""; + std::string name = ""; + std::string author = ""; + + static SPluginRepoIdentifier fromString(const std::string& string); + static SPluginRepoIdentifier fromUrl(const std::string& Url); + static SPluginRepoIdentifier fromName(const std::string& name); + static SPluginRepoIdentifier fromAuthorName(const std::string& author, const std::string& name); + std::string toString() const; + bool matches(const std::string& url, const std::string& name, const std::string& author) const; +}; diff --git a/hyprpm/src/core/PluginManager.cpp b/hyprpm/src/core/PluginManager.cpp index ed952eecf..0d35b4ae0 100644 --- a/hyprpm/src/core/PluginManager.cpp +++ b/hyprpm/src/core/PluginManager.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -136,7 +137,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& return false; } - if (DataState::pluginRepoExists(url)) { + if (DataState::pluginRepoExists(SPluginRepoIdentifier::fromUrl(url))) { std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Repository already installed.")); return false; } @@ -333,10 +334,13 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& std::string repohash = execAndGet("cd " + m_szWorkingPluginDirectory + " && git rev-parse HEAD"); if (repohash.length() > 0) repohash.pop_back(); - repo.name = pManifest->m_repository.name.empty() ? url.substr(url.find_last_of('/') + 1) : pManifest->m_repository.name; - repo.url = url; - repo.rev = rev; - repo.hash = repohash; + auto lastSlash = url.find_last_of('/'); + auto secondLastSlash = url.find_last_of('/', lastSlash - 1); + repo.name = pManifest->m_repository.name.empty() ? url.substr(lastSlash + 1) : pManifest->m_repository.name; + repo.author = url.substr(secondLastSlash + 1, lastSlash - secondLastSlash - 1); + repo.url = url; + repo.rev = rev; + repo.hash = repohash; for (auto const& p : pManifest->m_plugins) { repo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, false, p.failed}); } @@ -356,13 +360,13 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& return true; } -bool CPluginManager::removePluginRepo(const std::string& urlOrName) { - if (!DataState::pluginRepoExists(urlOrName)) { +bool CPluginManager::removePluginRepo(const SPluginRepoIdentifier identifier) { + if (!DataState::pluginRepoExists(identifier)) { std::println(stderr, "\n{}", failureString("Could not remove the repository. Repository is not installed.")); return false; } - std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << urlOrName << "\n " + std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << identifier.toString() << "\n " << "Are you sure? [Y/n] "; std::fflush(stdout); std::string input; @@ -373,7 +377,7 @@ bool CPluginManager::removePluginRepo(const std::string& urlOrName) { return false; } - DataState::removePluginRepo(urlOrName); + DataState::removePluginRepo(identifier); return true; } @@ -444,7 +448,6 @@ eHeadersErrors CPluginManager::headersValid() { } bool CPluginManager::updateHeaders(bool force) { - DataState::ensureStateStoreExists(); const auto HLVER = getHyprlandVersion(false); @@ -772,7 +775,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; }); newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false}); } - DataState::removePluginRepo(newrepo.name); + DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name)); DataState::addNewPluginRepo(newrepo); std::filesystem::remove_all(m_szWorkingPluginDirectory); @@ -797,17 +800,23 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { return true; } -bool CPluginManager::enablePlugin(const std::string& name) { - bool ret = DataState::setPluginEnabled(name, true); +bool CPluginManager::enablePlugin(const SPluginRepoIdentifier identifier) { + bool ret = false; + + switch (identifier.type) { + case IDENTIFIER_NAME: + case IDENTIFIER_AUTHOR_NAME: ret = DataState::setPluginEnabled(identifier, true); break; + default: return false; + } if (ret) - std::println("{}", successString("Enabled {}", name)); + std::println("{}", successString("Enabled {}", identifier.name)); return ret; } -bool CPluginManager::disablePlugin(const std::string& name) { - bool ret = DataState::setPluginEnabled(name, false); +bool CPluginManager::disablePlugin(const SPluginRepoIdentifier identifier) { + bool ret = DataState::setPluginEnabled(identifier, false); if (ret) - std::println("{}", successString("Disabled {}", name)); + std::println("{}", successString("Disabled {}", identifier.name)); return ret; } @@ -928,7 +937,7 @@ void CPluginManager::listAllPlugins() { const auto REPOS = DataState::getAllRepositories(); for (auto const& r : REPOS) { - std::println("{}", infoString("Repository {}:", r.name)); + std::println("{}", infoString("Repository {} (by {}):", r.name, r.author)); for (auto const& p : r.plugins) { std::println(" │ Plugin {}", p.name); diff --git a/hyprpm/src/core/PluginManager.hpp b/hyprpm/src/core/PluginManager.hpp index 2425f5ec9..10a71469d 100644 --- a/hyprpm/src/core/PluginManager.hpp +++ b/hyprpm/src/core/PluginManager.hpp @@ -2,8 +2,10 @@ #include #include +#include #include #include +#include "Plugin.hpp" enum eHeadersErrors { HEADERS_OK = 0, @@ -46,7 +48,7 @@ class CPluginManager { CPluginManager(); bool addNewPluginRepo(const std::string& url, const std::string& rev); - bool removePluginRepo(const std::string& urlOrName); + bool removePluginRepo(const SPluginRepoIdentifier identifier); eHeadersErrors headersValid(); bool updateHeaders(bool force = false); @@ -54,8 +56,8 @@ class CPluginManager { void listAllPlugins(); - bool enablePlugin(const std::string& name); - bool disablePlugin(const std::string& name); + bool enablePlugin(const SPluginRepoIdentifier identifier); + bool disablePlugin(const SPluginRepoIdentifier identifier); ePluginLoadStateReturn ensurePluginsLoadState(bool forceReload = false); bool loadUnloadPlugin(const std::string& path, bool load); diff --git a/hyprpm/src/main.cpp b/hyprpm/src/main.cpp index 817049ff5..dced58e75 100644 --- a/hyprpm/src/main.cpp +++ b/hyprpm/src/main.cpp @@ -13,25 +13,25 @@ using namespace Hyprutils::Utils; constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager ┃ -┣ add [url] [git rev] → Install a new plugin repository from git. Git revision -┃ is optional, when set, commit locks are ignored. -┣ remove [url/name] → Remove an installed plugin repository. -┣ enable [name] → Enable a plugin. -┣ disable [name] → Disable a plugin. -┣ update → Check and update all plugins if needed. -┣ reload → Reload hyprpm state. Ensure all enabled plugins are loaded. -┣ list → List all installed plugins. -┣ purge-cache → Remove the entire hyprpm cache, built plugins, hyprpm settings and headers. +┣ add [git rev] → Install a new plugin repository from git. Git revision +┃ is optional, when set, commit locks are ignored. +┣ remove → Remove an installed plugin repository. +┣ enable → Enable a plugin. +┣ disable → Disable a plugin. +┣ update → Check and update all plugins if needed. +┣ reload → Reload hyprpm state. Ensure all enabled plugins are loaded. +┣ list → List all installed plugins. +┣ purge-cache → Remove the entire hyprpm cache, built plugins, hyprpm settings and headers. ┃ ┣ Flags: ┃ -┣ --notify | -n → Send a hyprland notification confirming successful plugin load. -┃ Warnings/Errors trigger notifications regardless of this flag. -┣ --help | -h → Show this menu. -┣ --verbose | -v → Enable too much logging. -┣ --force | -f → Force an operation ignoring checks (e.g. update -f). -┣ --no-shallow | -s → Disable shallow cloning of Hyprland sources. -┣ --hl-url | → Pass a custom hyprland source url. +┣ --notify | -n → Send a hyprland notification confirming successful plugin load. +┃ Warnings/Errors trigger notifications regardless of this flag. +┣ --help | -h → Show this menu. +┣ --verbose | -v → Enable too much logging. +┣ --force | -f → Force an operation ignoring checks (e.g. update -f). +┣ --no-shallow | -s → Disable shallow cloning of Hyprland sources. +┣ --hl-url | → Pass a custom hyprland source url. ┗ )#"; @@ -126,7 +126,7 @@ int main(int argc, char** argv, char** envp) { NSys::root::cacheSudo(); CScopeGuard x([] { NSys::root::dropSudo(); }); - return g_pPluginManager->removePluginRepo(command[1]) ? 0 : 1; + return g_pPluginManager->removePluginRepo(SPluginRepoIdentifier::fromString(command[1])) ? 0 : 1; } else if (command[0] == "update") { NSys::root::cacheSudo(); CScopeGuard x([] { NSys::root::dropSudo(); }); @@ -160,7 +160,7 @@ int main(int argc, char** argv, char** envp) { return 1; } - if (!g_pPluginManager->enablePlugin(command[1])) { + if (!g_pPluginManager->enablePlugin(SPluginRepoIdentifier::fromString(command[1]))) { std::println(stderr, "{}", failureString("Couldn't enable plugin (missing?)")); return 1; } @@ -181,7 +181,7 @@ int main(int argc, char** argv, char** envp) { return 1; } - if (!g_pPluginManager->disablePlugin(command[1])) { + if (!g_pPluginManager->disablePlugin(SPluginRepoIdentifier::fromString(command[1]))) { std::println(stderr, "{}", failureString("Couldn't disable plugin (missing?)")); return 1; } From 6535ff07c9f16dbd4928f1ef8a12a939db59f7b5 Mon Sep 17 00:00:00 2001 From: Luke Barkess <57995669+Brumus14@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:19:35 +0000 Subject: [PATCH 13/15] anr: don't create for anr dialogs (#12601) --- src/helpers/AsyncDialogBox.cpp | 4 ++++ src/helpers/AsyncDialogBox.hpp | 3 ++- src/managers/ANRManager.cpp | 9 +++++++-- src/managers/ANRManager.hpp | 4 ++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/helpers/AsyncDialogBox.cpp b/src/helpers/AsyncDialogBox.cpp index 6257dcb0d..b36886807 100644 --- a/src/helpers/AsyncDialogBox.cpp +++ b/src/helpers/AsyncDialogBox.cpp @@ -147,6 +147,10 @@ bool CAsyncDialogBox::isRunning() const { return m_readEventSource; } +pid_t CAsyncDialogBox::getPID() const { + return m_dialogPid; +} + SP CAsyncDialogBox::lockSelf() { return m_selfWeakReference.lock(); } diff --git a/src/helpers/AsyncDialogBox.hpp b/src/helpers/AsyncDialogBox.hpp index 5f94be0da..8db516cea 100644 --- a/src/helpers/AsyncDialogBox.hpp +++ b/src/helpers/AsyncDialogBox.hpp @@ -26,6 +26,7 @@ class CAsyncDialogBox { SP> open(); void kill(); bool isRunning() const; + pid_t getPID() const; SP lockSelf(); @@ -51,4 +52,4 @@ class CAsyncDialogBox { // WARNING: cyclic reference. This will be removed once the event source is removed to avoid dangling pointers SP m_selfReference; WP m_selfWeakReference; -}; \ No newline at end of file +}; diff --git a/src/managers/ANRManager.cpp b/src/managers/ANRManager.cpp index daab4d0a9..db7a245f5 100644 --- a/src/managers/ANRManager.cpp +++ b/src/managers/ANRManager.cpp @@ -1,4 +1,5 @@ #include "ANRManager.hpp" + #include "../helpers/fs/FsUtils.hpp" #include "../debug/Log.hpp" #include "../macros.hpp" @@ -29,6 +30,10 @@ CANRManager::CANRManager() { auto window = std::any_cast(data); for (const auto& d : m_data) { + // Window is ANR dialog + if (d->isRunning() && d->dialogBox->getPID() == window->getPID()) + return; + if (d->fitsWindow(window)) return; } @@ -84,7 +89,7 @@ void CANRManager::onTick() { if (data->missedResponses >= *PANRTHRESHOLD) { if (!data->isRunning() && !data->dialogSaidWait) { - data->runDialog(firstWindow->m_title, firstWindow->m_class, data->getPid()); + data->runDialog(firstWindow->m_title, firstWindow->m_class, data->getPID()); for (const auto& w : g_pCompositor->m_windows) { if (!w->m_isMapped) @@ -240,7 +245,7 @@ bool CANRManager::SANRData::isDefunct() const { return xdgBase.expired() && xwaylandSurface.expired(); } -pid_t CANRManager::SANRData::getPid() const { +pid_t CANRManager::SANRData::getPID() const { if (xdgBase) { pid_t pid = 0; wl_client_get_credentials(xdgBase->client(), &pid, nullptr, nullptr); diff --git a/src/managers/ANRManager.hpp b/src/managers/ANRManager.hpp index 286e834f9..3880249dc 100644 --- a/src/managers/ANRManager.hpp +++ b/src/managers/ANRManager.hpp @@ -44,7 +44,7 @@ class CANRManager { void killDialog(); bool isDefunct() const; bool fitsWindow(PHLWINDOW pWindow) const; - pid_t getPid() const; + pid_t getPID() const; void ping(); }; @@ -57,4 +57,4 @@ class CANRManager { std::vector> m_data; }; -inline UP g_pANRManager; \ No newline at end of file +inline UP g_pANRManager; From e4a8f2b14f789075612bfd26e8153039c8f295f2 Mon Sep 17 00:00:00 2001 From: jmanc3 Date: Sun, 14 Dec 2025 13:42:02 -0600 Subject: [PATCH 14/15] renderer: add zoom with detached camera (#12548) --- src/config/ConfigDescriptions.hpp | 6 ++ src/config/ConfigManager.cpp | 1 + src/helpers/Monitor.hpp | 3 + src/helpers/MonitorZoomController.cpp | 97 +++++++++++++++++++++++++++ src/helpers/MonitorZoomController.hpp | 19 ++++++ src/render/OpenGL.cpp | 18 +---- 6 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 src/helpers/MonitorZoomController.cpp create mode 100644 src/helpers/MonitorZoomController.hpp diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index 85655dfd8..38bb0a20c 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -1658,6 +1658,12 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, }, + SConfigOptionDescription{ + .value = "cursor:zoom_detached_camera", + .description = "Detaches the camera from the mouse when zoomed in, only ever moving to keep the mouse in view", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, SConfigOptionDescription{ .value = "cursor:enable_hyprcursor", .description = "whether to enable hyprcursor support", diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index f8acb4738..94147f499 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -734,6 +734,7 @@ CConfigManager::CConfigManager() { registerConfigVar("cursor:zoom_factor", {1.f}); registerConfigVar("cursor:zoom_rigid", Hyprlang::INT{0}); registerConfigVar("cursor:zoom_disable_aa", Hyprlang::INT{0}); + registerConfigVar("cursor:zoom_detached_camera", Hyprlang::INT{1}); registerConfigVar("cursor:enable_hyprcursor", Hyprlang::INT{1}); registerConfigVar("cursor:sync_gsettings_theme", Hyprlang::INT{1}); registerConfigVar("cursor:hide_on_key_press", Hyprlang::INT{0}); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index debf2ec74..95e5ce5cb 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -11,6 +11,7 @@ #include "CMType.hpp" #include +#include "MonitorZoomController.hpp" #include "time/Timer.hpp" #include "math/Math.hpp" #include "../desktop/reserved/ReservedArea.hpp" @@ -130,6 +131,8 @@ class CMonitor { uint32_t m_drmFormat = DRM_FORMAT_INVALID; uint32_t m_prevDrmFormat = DRM_FORMAT_INVALID; + CMonitorZoomController m_zoomController; + bool m_dpmsStatus = true; bool m_vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it. bool m_enabled10bit = false; // as above, this can be TRUE even if 10 bit failed. diff --git a/src/helpers/MonitorZoomController.cpp b/src/helpers/MonitorZoomController.cpp new file mode 100644 index 000000000..d90f416fe --- /dev/null +++ b/src/helpers/MonitorZoomController.cpp @@ -0,0 +1,97 @@ +#include "MonitorZoomController.hpp" + +#include +#include "../config/ConfigValue.hpp" +#include "../managers/input/InputManager.hpp" +#include "../render/OpenGL.hpp" +#include "desktop/DesktopTypes.hpp" +#include "render/Renderer.hpp" + +void CMonitorZoomController::zoomWithDetachedCamera(CBox& result, const SCurrentRenderData& m_renderData) { + const auto m = m_renderData.pMonitor; + auto monbox = CBox(0, 0, m->m_size.x, m->m_size.y); + const auto ZOOM = m_renderData.mouseZoomFactor; + const auto MOUSE = g_pInputManager->getMouseCoordsInternal() - m->m_position; + + if (m_lastZoomLevel != ZOOM) { + if (m_resetCameraState) { + m_resetCameraState = false; + m_camera = CBox(0, 0, m->m_size.x, m->m_size.y); + m_lastZoomLevel = 1.0f; + } + const CBox old = m_camera; + + // mouse normalized inside screen (0..1) + const float mx = MOUSE.x / m->m_size.x; + const float my = MOUSE.y / m->m_size.y; + // world-space point under the cursor before zoom + const float mouseWorldX = old.x + (mx * old.w); + const float mouseWorldY = old.y + (my * old.h); + + const auto CAMERAW = monbox.w / ZOOM; + const auto CAMERAH = monbox.h / ZOOM; + + // compute new top-left so the same world point stays under the cursor + const float newX = mouseWorldX - (mx * CAMERAW); + const float newY = mouseWorldY - (my * CAMERAH); + + m_camera = CBox(newX, newY, CAMERAW, CAMERAH); + // Detect if this zoom would've caused jerk to keep mouse in view and disable edges if so + if (!m_camera.copy().scaleFromCenter(.9).containsPoint(MOUSE)) + m_padCamEdges = false; + m_lastZoomLevel = ZOOM; + } + + // Keep mouse inside cameraview + auto smallerbox = m_camera; + // Prevent zoom step from causing us to jerk to keep mouse in padded camera view, + // but let us switch to the padded camera once the mouse moves into the safe area + if (!m_padCamEdges) + if (smallerbox.copy().scaleFromCenter(.9).containsPoint(MOUSE)) + m_padCamEdges = true; + if (m_padCamEdges) + smallerbox.scaleFromCenter(.9); + if (!smallerbox.containsPoint(MOUSE)) { + if (MOUSE.x < smallerbox.x) + m_camera.x -= smallerbox.x - MOUSE.x; + if (MOUSE.y < smallerbox.y) + m_camera.y -= smallerbox.y - MOUSE.y; + if (MOUSE.y > smallerbox.y + smallerbox.h) + m_camera.y += MOUSE.y - (smallerbox.y + smallerbox.h); + if (MOUSE.x > smallerbox.x + smallerbox.w) + m_camera.x += MOUSE.x - (smallerbox.x + smallerbox.w); + } + + auto z = ZOOM * m->m_scale; + monbox.scale(z).translate(-m_camera.pos() * z); + + result = monbox; +} + +void CMonitorZoomController::applyZoomTransform(CBox& monbox, const SCurrentRenderData& m_renderData) { + static auto PZOOMRIGID = CConfigValue("cursor:zoom_rigid"); + static auto PZOOMDETACHEDCAMERA = CConfigValue("cursor:zoom_detached_camera"); + const auto ZOOM = m_renderData.mouseZoomFactor; + + if (ZOOM == 1.0f) + return; + + const auto m = m_renderData.pMonitor; + const auto ORIGINAL = monbox; + const auto INITANIM = m->m_zoomAnimProgress->value() != 1.0; + + if (*PZOOMDETACHEDCAMERA && !INITANIM) + zoomWithDetachedCamera(monbox, m_renderData); + else { + const auto ZOOMCENTER = m_renderData.mouseZoomUseMouse ? (g_pInputManager->getMouseCoordsInternal() - m->m_position) * m->m_scale : m->m_transformedSize / 2.f; + + monbox.translate(-ZOOMCENTER).scale(ZOOM).translate(*PZOOMRIGID ? m->m_transformedSize / 2.0 : ZOOMCENTER); + } + + monbox.x = std::min(monbox.x, 0.0); + monbox.y = std::min(monbox.y, 0.0); + if (monbox.x + monbox.width < ORIGINAL.w) + monbox.x = ORIGINAL.w - monbox.width; + if (monbox.y + monbox.height < ORIGINAL.h) + monbox.y = ORIGINAL.h - monbox.height; +} diff --git a/src/helpers/MonitorZoomController.hpp b/src/helpers/MonitorZoomController.hpp new file mode 100644 index 000000000..4f7c9d7a2 --- /dev/null +++ b/src/helpers/MonitorZoomController.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "./math/Math.hpp" + +struct SCurrentRenderData; + +class CMonitorZoomController { + public: + bool m_resetCameraState = true; + + void applyZoomTransform(CBox& monbox, const SCurrentRenderData& m_renderData); + + private: + void zoomWithDetachedCamera(CBox& result, const SCurrentRenderData& m_renderData); + + CBox m_camera; + float m_lastZoomLevel = 1.0f; + bool m_padCamEdges = true; +}; diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 6b5e35f1d..198ba0e4a 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -849,7 +849,6 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFrameb } void CHyprOpenGLImpl::end() { - static auto PZOOMRIGID = CConfigValue("cursor:zoom_rigid"); static auto PZOOMDISABLEAA = CConfigValue("cursor:zoom_disable_aa"); TRACY_GPU_ZONE("RenderEnd"); @@ -861,20 +860,9 @@ void CHyprOpenGLImpl::end() { CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; - if (m_renderData.mouseZoomFactor != 1.f) { - const auto ZOOMCENTER = m_renderData.mouseZoomUseMouse ? - (g_pInputManager->getMouseCoordsInternal() - m_renderData.pMonitor->m_position) * m_renderData.pMonitor->m_scale : - m_renderData.pMonitor->m_transformedSize / 2.f; - - monbox.translate(-ZOOMCENTER).scale(m_renderData.mouseZoomFactor).translate(*PZOOMRIGID ? m_renderData.pMonitor->m_transformedSize / 2.0 : ZOOMCENTER); - - monbox.x = std::min(monbox.x, 0.0); - monbox.y = std::min(monbox.y, 0.0); - if (monbox.x + monbox.width < m_renderData.pMonitor->m_transformedSize.x) - monbox.x = m_renderData.pMonitor->m_transformedSize.x - monbox.width; - if (monbox.y + monbox.height < m_renderData.pMonitor->m_transformedSize.y) - monbox.y = m_renderData.pMonitor->m_transformedSize.y - monbox.height; - } + if (g_pHyprRenderer->m_renderMode == RENDER_MODE_NORMAL && m_renderData.mouseZoomFactor == 1.0f) + m_renderData.pMonitor->m_zoomController.m_resetCameraState = true; + m_renderData.pMonitor->m_zoomController.applyZoomTransform(monbox, m_renderData); m_applyFinalShader = !m_renderData.blockScreenShader; if (m_renderData.mouseZoomUseMouse && *PZOOMDISABLEAA) From 7ccc57eb7cacded5e7a8835b705bba48963d3cb3 Mon Sep 17 00:00:00 2001 From: Maximilian Seidler <78690852+PaideiaDilemma@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:46:49 +0000 Subject: [PATCH 15/15] animation: migrate PHLANIMVAR from SP to UP (#12486) --- CMakeLists.txt | 2 +- src/desktop/view/Window.cpp | 17 +++++++---------- src/helpers/AnimatedVariable.hpp | 2 +- src/managers/animation/AnimationManager.cpp | 3 +-- src/managers/animation/AnimationManager.hpp | 10 ++++------ 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 07a33cb6d..a192a6942 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,7 +128,7 @@ find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION}) set(AQUAMARINE_MINIMUM_VERSION 0.9.3) set(HYPRLANG_MINIMUM_VERSION 0.6.7) set(HYPRCURSOR_MINIMUM_VERSION 0.1.7) -set(HYPRUTILS_MINIMUM_VERSION 0.10.2) +set(HYPRUTILS_MINIMUM_VERSION 0.11.0) set(HYPRGRAPHICS_MINIMUM_VERSION 0.1.6) pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=${AQUAMARINE_MINIMUM_VERSION}) diff --git a/src/desktop/view/Window.cpp b/src/desktop/view/Window.cpp index b01dcabc0..2fc15566a 100644 --- a/src/desktop/view/Window.cpp +++ b/src/desktop/view/Window.cpp @@ -639,14 +639,13 @@ void CWindow::onMap() { } void CWindow::onBorderAngleAnimEnd(WP pav) { - const auto PAV = pav.lock(); - if (!PAV) + if (!pav) return; - if (PAV->getStyle() != "loop" || !PAV->enabled()) + if (pav->getStyle() != "loop" || !pav->enabled()) return; - const auto PANIMVAR = dc*>(PAV.get()); + const auto PANIMVAR = dc*>(pav.get()); PANIMVAR->setCallbackOnEnd(nullptr); // we remove the callback here because otherwise setvalueandwarp will recurse this @@ -1912,16 +1911,14 @@ std::optional CWindow::calculateExpression(const std::string& s) { } static void setVector2DAnimToMove(WP pav) { - const auto PAV = pav.lock(); - if (!PAV) + if (!pav) return; - CAnimatedVariable* animvar = dc*>(PAV.get()); + CAnimatedVariable* animvar = dc*>(pav.get()); animvar->setConfig(g_pConfigManager->getAnimationPropertyConfig("windowsMove")); - const auto PHLWINDOW = animvar->m_Context.pWindow.lock(); - if (PHLWINDOW) - PHLWINDOW->m_animatingIn = false; + if (animvar->m_Context.pWindow) + animvar->m_Context.pWindow->m_animatingIn = false; } void CWindow::mapWindow() { diff --git a/src/helpers/AnimatedVariable.hpp b/src/helpers/AnimatedVariable.hpp index e7d5fd8cd..f0bdc5a81 100644 --- a/src/helpers/AnimatedVariable.hpp +++ b/src/helpers/AnimatedVariable.hpp @@ -67,7 +67,7 @@ template using CAnimatedVariable = Hyprutils::Animation::CGenericAnimatedVariable; template -using PHLANIMVAR = SP>; +using PHLANIMVAR = UP>; template using PHLANIMVARREF = WP>; diff --git a/src/managers/animation/AnimationManager.cpp b/src/managers/animation/AnimationManager.cpp index a09f391b0..b4255a33c 100644 --- a/src/managers/animation/AnimationManager.cpp +++ b/src/managers/animation/AnimationManager.cpp @@ -209,8 +209,7 @@ void CHyprAnimationManager::tick() { static auto PANIMENABLED = CConfigValue("animations:enabled"); - for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) { - const auto PAV = m_vActiveAnimatedVariables[i].lock(); + for (const auto& PAV : m_vActiveAnimatedVariables) { if (!PAV) continue; diff --git a/src/managers/animation/AnimationManager.hpp b/src/managers/animation/AnimationManager.hpp index 2f411879b..b8acc53e7 100644 --- a/src/managers/animation/AnimationManager.hpp +++ b/src/managers/animation/AnimationManager.hpp @@ -22,13 +22,11 @@ class CHyprAnimationManager : public Hyprutils::Animation::CAnimationManager { template void createAnimation(const VarType& v, PHLANIMVAR& pav, SP pConfig, eAVarDamagePolicy policy) { constexpr const eAnimatedVarType EAVTYPE = typeToeAnimatedVarType; - const auto PAV = makeShared>(); + pav = makeUnique>(); - PAV->create(EAVTYPE, sc(this), PAV, v); - PAV->setConfig(pConfig); - PAV->m_Context.eDamagePolicy = policy; - - pav = std::move(PAV); + pav->create2(EAVTYPE, sc(this), pav, v); + pav->setConfig(pConfig); + pav->m_Context.eDamagePolicy = policy; } template