From 2ca4f1a47cf2dc569e6e7034e53c4cd9b25e36a8 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Thu, 14 May 2026 05:29:28 -0700 Subject: [PATCH 1/9] Fix overview layout crashes on Hyprland 0.55 --- src/Layout.cpp | 21 ++++++++++++++------- src/Overview.cpp | 2 +- src/main.cpp | 10 ++++++---- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Layout.cpp b/src/Layout.cpp index 632d657..ab32878 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -11,12 +11,18 @@ void CHyprspaceWidget::updateLayout() { const auto pMonitor = getOwner(); if (!pMonitor) return; - static auto PGAPSINDATA = CConfigValue("general:gaps_in"); - static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); - auto* const PGAPSIN = (Config::CCssGapData*)(PGAPSINDATA.ptr())->getData(); - auto* const PGAPSOUT = (Config::CCssGapData*)(PGAPSOUTDATA.ptr())->getData(); + static auto PGAPSINDATA = CConfigValue("general:gaps_in"); + static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); + if (!PGAPSINDATA.good() || !PGAPSOUTDATA.good()) return; - if (active) { + const auto PGAPSINBASE = PGAPSINDATA.ptr(); + const auto PGAPSOUTBASE = PGAPSOUTDATA.ptr(); + if (!PGAPSINBASE || !PGAPSOUTBASE || PGAPSINBASE->getDataType() != Config::CVD_TYPE_CSS_VALUE || PGAPSOUTBASE->getDataType() != Config::CVD_TYPE_CSS_VALUE) return; + + auto* const PGAPSIN = static_cast(PGAPSINBASE); + auto* const PGAPSOUT = static_cast(PGAPSOUTBASE); + + if (active) { if (!Config::onBottom) pMonitor->m_reservedArea = Desktop::CReservedArea(currentHeight, 0, 0, 0); else @@ -34,9 +40,10 @@ void CHyprspaceWidget::updateLayout() { // Geneva Convention violation type hack but idc atm if (active) { const auto oActiveWorkspace = pMonitor->m_activeWorkspace; + if (!oActiveWorkspace) return; for (auto& ws : g_pCompositor->getWorkspaces()) { // HACK: recalculate other workspaces without reserved area - if (ws->m_monitor->m_id == ownerID && ws->m_id != oActiveWorkspace->m_id) { + if (ws && ws->m_monitor && ws->m_monitor->m_id == ownerID && ws->m_id != oActiveWorkspace->m_id) { pMonitor->m_activeWorkspace = ws.lock(); const auto curRules = std::to_string(pMonitor->activeWorkspaceID()) + ", gapsin:" + PGAPSIN->toString() + ", gapsout:" + PGAPSOUT->toString(); if (Config::overrideGaps) { @@ -58,7 +65,7 @@ void CHyprspaceWidget::updateLayout() { } else { for (auto& ws : g_pCompositor->getWorkspaces()) { - if (ws->m_monitor->m_id == ownerID) { + if (ws && ws->m_monitor && ws->m_monitor->m_id == ownerID) { const auto curRules = std::to_string(ws->m_id) + ", gapsin:" + PGAPSIN->toString() + ", gapsout:" + PGAPSOUT->toString(); if (Config::overrideGaps) { if (const auto legacy = Config::Legacy::mgr().lock()) diff --git a/src/Overview.cpp b/src/Overview.cpp index 9fc4744..b69e380 100644 --- a/src/Overview.cpp +++ b/src/Overview.cpp @@ -34,7 +34,7 @@ void CHyprspaceWidget::show() { if (prevFullscreen.empty()) { // unfullscreen all windows for (auto& ws : g_pCompositor->getWorkspaces()) { - if (ws->m_monitor->m_id == ownerID) { + if (ws && ws->m_monitor && ws->m_monitor->m_id == ownerID) { const auto w = ws->getFullscreenWindow(); if (w != nullptr && ws->m_fullscreenMode != FSMODE_NONE) { // use fakefullscreenstate to preserve client's internal state diff --git a/src/main.cpp b/src/main.cpp index f7825f4..9dd0286 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -457,10 +457,12 @@ void reloadConfig() { for (auto& widget : g_overviewWidgets) { widget->updateConfig(); - widget->hide(); - IPointer::SSwipeEndEvent dummy; - dummy.cancelled = true; - widget->endSwipe(dummy); + if (widget->isActive() || widget->isSwiping()) { + widget->hide(); + IPointer::SSwipeEndEvent dummy; + dummy.cancelled = true; + widget->endSwipe(dummy); + } } Config::dragAlpha = getConfigValueOr("plugin:overview:dragAlpha", Config::dragAlpha); From bf2ef21007e8963fcb0016ae26b6b21704946f80 Mon Sep 17 00:00:00 2001 From: Ivan Malison Date: Fri, 15 May 2026 01:01:15 -0700 Subject: [PATCH 2/9] Clip Hyprspace overview surfaces per thumbnail --- src/Render.cpp | 130 +++++++++++++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 53 deletions(-) diff --git a/src/Render.cpp b/src/Render.cpp index 003a20e..52fb0ef 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -36,21 +37,18 @@ void renderBorder(CBox box, const Config::CGradientValueData& gradient, int size g_pHyprRenderer->m_renderPass.add(makeUnique(data)); } -void renderWindowStub(PHLWINDOW pWindow, PHLMONITOR pMonitor, PHLWORKSPACE pWorkspaceOverride, CBox rectOverride, const Time::steady_tp& time) { +void renderWindowStub(PHLWINDOW pWindow, PHLMONITOR pMonitor, PHLWORKSPACE pWorkspaceOverride, CBox rectOverride, CBox clipBox, const Time::steady_tp& time) { if (!pWindow || !pMonitor || !pWorkspaceOverride) return; + if (!pWindow->m_isMapped || !pWindow->wlSurface() || !pWindow->wlSurface()->resource()) return; Render::SRenderModifData renderModif; - const auto oWorkspace = pWindow->m_workspace; - const auto oFullscreen = pWindow->m_fullscreenState; const auto oRealPosition = pWindow->m_realPosition->value(); const auto oSize = pWindow->m_realSize->value(); - const auto oUseNearestNeighbor = pWindow->m_ruleApplicator->nearestNeighbor(); - const auto oPinned = pWindow->m_pinned; - const auto oFloating = pWindow->m_isFloating; - const float logicalW = std::max((float)oSize.x, 5.F); const float scaleMod = rectOverride.w / std::max(logicalW * pMonitor->m_scale, 5.F); + if (!(scaleMod > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) return; + const Vector2D logicalTL = oRealPosition + pWindow->m_floatingOffset; const Vector2D scaledTL = (logicalTL - pMonitor->m_position) * pMonitor->m_scale; const Vector2D translate = rectOverride.pos() / scaleMod - scaledTL; @@ -58,12 +56,6 @@ void renderWindowStub(PHLWINDOW pWindow, PHLMONITOR pMonitor, PHLWORKSPACE pWork renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, std::any(translate))); renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, std::any(scaleMod))); renderModif.enabled = true; - pWindow->m_workspace = pWorkspaceOverride; - pWindow->m_fullscreenState = Desktop::View::SFullscreenState{FSMODE_NONE}; - pWindow->m_ruleApplicator->nearestNeighbor().set(false, Desktop::Types::PRIORITY_SET_PROP); - pWindow->m_isFloating = false; - pWindow->m_pinned = true; - pWindow->m_ruleApplicator->rounding().set(pWindow->rounding() * scaleMod * pMonitor->m_scale, Desktop::Types::PRIORITY_SET_PROP); g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = renderModif})); Hyprutils::Utils::CScopeGuard x([] { @@ -72,46 +64,92 @@ void renderWindowStub(PHLWINDOW pWindow, PHLMONITOR pMonitor, PHLWORKSPACE pWork g_pHyprRenderer->damageWindow(pWindow); - (*(tRenderWindow)pRenderWindow)(g_pHyprRenderer.get(), pWindow, pMonitor, time, true, Render::RENDER_PASS_ALL, false, false); - - // restore values for normal window render - pWindow->m_workspace = oWorkspace; - pWindow->m_fullscreenState = oFullscreen; - pWindow->m_ruleApplicator->rounding().unset(Desktop::Types::PRIORITY_SET_PROP); - pWindow->m_isFloating = oFloating; - pWindow->m_pinned = oPinned; - pWindow->m_ruleApplicator->rounding().unset(Desktop::Types::PRIORITY_SET_PROP); + CSurfacePassElement::SRenderData renderdata = {pMonitor, time}; + renderdata.pos = oRealPosition + pWindow->m_floatingOffset; + renderdata.w = std::max(oSize.x, 5.0); + renderdata.h = std::max(oSize.y, 5.0); + renderdata.surface = pWindow->wlSurface()->resource(); + renderdata.dontRound = pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); + renderdata.fadeAlpha = 1.F; + renderdata.alpha = 1.F; + renderdata.decorate = false; + renderdata.rounding = renderdata.dontRound ? 0 : pWindow->rounding() * scaleMod * pMonitor->m_scale; + renderdata.roundingPower = renderdata.dontRound ? 2.0F : pWindow->roundingPower(); + renderdata.blur = false; + renderdata.pWindow = pWindow; + renderdata.clipBox = clipBox; + renderdata.useNearestNeighbor = false; + renderdata.squishOversized = true; + renderdata.surfaceCounter = 0; + + pWindow->wlSurface()->resource()->breadthfirst( + [&renderdata, &pWindow](SP s, const Vector2D& offset, void* data) { + if (!s || !s->m_current.texture) + return; + + if (s->m_current.size.x < 1 || s->m_current.size.y < 1) + return; + + renderdata.localPos = offset; + renderdata.texture = s->m_current.texture; + renderdata.surface = s; + renderdata.mainSurface = s == pWindow->wlSurface()->resource(); + g_pHyprRenderer->m_renderPass.add(makeUnique(renderdata)); + renderdata.surfaceCounter++; + }, + nullptr); } -void renderLayerStub(PHLLS pLayer, PHLMONITOR pMonitor, CBox rectOverride, const Time::steady_tp& time) { +void renderLayerStub(PHLLS pLayer, PHLMONITOR pMonitor, CBox rectOverride, CBox clipBox, const Time::steady_tp& time) { if (!pLayer || !pMonitor) return; - if (!pLayer->m_mapped || pLayer->m_readyToDelete || !pLayer->m_layerSurface) return; + if (!pLayer->m_mapped || pLayer->m_readyToDelete || !pLayer->m_layerSurface || !pLayer->wlSurface() || !pLayer->wlSurface()->resource()) return; Vector2D oRealPosition = pLayer->m_realPosition->value(); Vector2D oSize = pLayer->m_realSize->value(); - float oAlpha = pLayer->m_alpha->value(); // set to 1 to show hidden top layer - const auto oFadingOut = pLayer->m_fadingOut; const float curScaling = rectOverride.w / (oSize.x); + if (!(curScaling > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) return; Render::SRenderModifData renderModif; renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, std::any(pMonitor->m_position + (rectOverride.pos() / curScaling) - oRealPosition))); renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, std::any(curScaling))); renderModif.enabled = true; - pLayer->m_alpha->setValue(1); - pLayer->m_fadingOut = false; g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = renderModif})); Hyprutils::Utils::CScopeGuard x([] { g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = Render::SRenderModifData{}})); }); - (*(tRenderLayer)pRenderLayer)(g_pHyprRenderer.get(), pLayer, pMonitor, time, false, false); - - pLayer->m_fadingOut = oFadingOut; - pLayer->m_alpha->setValue(oAlpha); + CSurfacePassElement::SRenderData renderdata = {pMonitor, time, oRealPosition}; + renderdata.fadeAlpha = 1.F; + renderdata.alpha = 1.F; + renderdata.blur = false; + renderdata.surface = pLayer->wlSurface()->resource(); + renderdata.decorate = false; + renderdata.w = oSize.x; + renderdata.h = oSize.y; + renderdata.pLS = pLayer; + renderdata.clipBox = clipBox; + renderdata.surfaceCounter = 0; + + pLayer->wlSurface()->resource()->breadthfirst( + [&renderdata, &pLayer](SP s, const Vector2D& offset, void* data) { + if (!s || !s->m_current.texture) + return; + + if (s->m_current.size.x < 1 || s->m_current.size.y < 1) + return; + + renderdata.localPos = offset; + renderdata.texture = s->m_current.texture; + renderdata.surface = s; + renderdata.mainSurface = s == pLayer->wlSurface()->resource(); + g_pHyprRenderer->m_renderPass.add(makeUnique(renderdata)); + renderdata.surfaceCounter++; + }, + &renderdata); } // NOTE: rects and clipbox positions are relative to the monitor, while damagebox and layers are not, what the fuck? xd @@ -269,15 +307,11 @@ void CHyprspaceWidget::draw() { if (!Config::hideBackgroundLayers) { for (auto& ls : owner->m_layerSurfaceLayers[0]) { CBox layerBox = {curWorkspaceBox.pos() + (ls->m_realPosition->value() - owner->m_position) * monitorSizeScaleFactor, ls->m_realSize->value() * monitorSizeScaleFactor}; - g_pHyprRenderer->m_renderData.clipBox = curWorkspaceBox; - renderLayerStub(ls.lock(), owner, layerBox, time); - g_pHyprRenderer->m_renderData.clipBox = monitorClip; + renderLayerStub(ls.lock(), owner, layerBox, curWorkspaceBox, time); } for (auto& ls : owner->m_layerSurfaceLayers[1]) { CBox layerBox = {curWorkspaceBox.pos() + (ls->m_realPosition->value() - owner->m_position) * monitorSizeScaleFactor, ls->m_realSize->value() * monitorSizeScaleFactor}; - g_pHyprRenderer->m_renderData.clipBox = curWorkspaceBox; - renderLayerStub(ls.lock(), owner, layerBox, time); - g_pHyprRenderer->m_renderData.clipBox = monitorClip; + renderLayerStub(ls.lock(), owner, layerBox, curWorkspaceBox, time); } } @@ -307,10 +341,8 @@ void CHyprspaceWidget::draw() { double wH = w->m_realSize->value().y * monitorSizeScaleFactor * owner->m_scale; if (!(wW > 0 && wH > 0)) continue; CBox curWindowBox = {wX, wY, wW, wH}; - g_pHyprRenderer->m_renderData.clipBox = curWorkspaceBox; //g_pHyprOpenGL->renderRectWithBlur(&curWindowBox, CHyprColor(0, 0, 0, 0)); - renderWindowStub(w, owner, owner->m_activeWorkspace, curWindowBox, time); - g_pHyprRenderer->m_renderData.clipBox = monitorClip; + renderWindowStub(w, owner, owner->m_activeWorkspace, curWindowBox, curWorkspaceBox, time); } } // draw floating windows @@ -323,10 +355,8 @@ void CHyprspaceWidget::draw() { double wH = w->m_realSize->value().y * monitorSizeScaleFactor * owner->m_scale; if (!(wW > 0 && wH > 0)) continue; CBox curWindowBox = {wX, wY, wW, wH}; - g_pHyprRenderer->m_renderData.clipBox = curWorkspaceBox; //g_pHyprOpenGL->renderRectWithBlur(&curWindowBox, CHyprColor(0, 0, 0, 0)); - renderWindowStub(w, owner, owner->m_activeWorkspace, curWindowBox, time); - g_pHyprRenderer->m_renderData.clipBox = monitorClip; + renderWindowStub(w, owner, owner->m_activeWorkspace, curWindowBox, curWorkspaceBox, time); } } // draw last focused floating window on top @@ -339,10 +369,8 @@ void CHyprspaceWidget::draw() { double wH = w->m_realSize->value().y * monitorSizeScaleFactor * owner->m_scale; if (!(wW > 0 && wH > 0)) continue; CBox curWindowBox = {wX, wY, wW, wH}; - g_pHyprRenderer->m_renderData.clipBox = curWorkspaceBox; //g_pHyprOpenGL->renderRectWithBlur(&curWindowBox, CHyprColor(0, 0, 0, 0)); - renderWindowStub(w, owner, owner->m_activeWorkspace, curWindowBox, time); - g_pHyprRenderer->m_renderData.clipBox = monitorClip; + renderWindowStub(w, owner, owner->m_activeWorkspace, curWindowBox, curWorkspaceBox, time); } } @@ -351,17 +379,13 @@ void CHyprspaceWidget::draw() { if (!Config::hideTopLayers) for (auto& ls : owner->m_layerSurfaceLayers[2]) { CBox layerBox = {curWorkspaceBox.pos() + (ls->m_realPosition->value() - owner->m_position) * monitorSizeScaleFactor, ls->m_realSize->value() * monitorSizeScaleFactor}; - g_pHyprRenderer->m_renderData.clipBox = curWorkspaceBox; - renderLayerStub(ls.lock(), owner, layerBox, time); - g_pHyprRenderer->m_renderData.clipBox = monitorClip; + renderLayerStub(ls.lock(), owner, layerBox, curWorkspaceBox, time); } if (!Config::hideOverlayLayers) for (auto& ls : owner->m_layerSurfaceLayers[3]) { CBox layerBox = {curWorkspaceBox.pos() + (ls->m_realPosition->value() - owner->m_position) * monitorSizeScaleFactor, ls->m_realSize->value() * monitorSizeScaleFactor}; - g_pHyprRenderer->m_renderData.clipBox = curWorkspaceBox; - renderLayerStub(ls.lock(), owner, layerBox, time); - g_pHyprRenderer->m_renderData.clipBox = monitorClip; + renderLayerStub(ls.lock(), owner, layerBox, curWorkspaceBox, time); } } From 322343008dd7346afe0f469469f666a44877f7ea Mon Sep 17 00:00:00 2001 From: 0xl30 Date: Thu, 21 May 2026 23:09:19 +0530 Subject: [PATCH 3/9] Improve --- Hyprspace.lua | 211 +++++++----------- src/Globals.hpp | 10 +- src/Input.cpp | 241 +++++++++----------- src/Layout.cpp | 79 +------ src/Overview.cpp | 2 +- src/Overview.hpp | 2 +- src/Render.cpp | 563 ++++++++++++++++++++++++----------------------- src/main.cpp | 30 +-- 8 files changed, 498 insertions(+), 640 deletions(-) diff --git a/Hyprspace.lua b/Hyprspace.lua index 33628b8..e8cb4b5 100644 --- a/Hyprspace.lua +++ b/Hyprspace.lua @@ -1,8 +1,8 @@ local M = {} -local HOME = os.getenv("HOME") or "" -local MATUGEN_PATH = HOME .. "/.config/matugen/generated/hyprland-colors.lua" -local pluginPath = os.getenv("HYPRSPACE_PLUGIN_PATH") +local HOME = os.getenv("HOME") or "" +local IMPORT_COLOR = HOME .. "/.config/matugen/generated/hyprland-colors.lua" +local plugin_path = os.getenv("HYPRSPACE_PLUGIN_PATH") local function file_exists(path) if type(path) ~= "string" or path == "" then @@ -22,14 +22,15 @@ local function shell_quote(value) return "'" .. tostring(value):gsub("'", "'\\''") .. "'" end -local function load_lua_file(path) +local function load_colors(path) local env = {} local chunk = loadfile(path, "t", env) if not chunk then return {} end - if not pcall(chunk) then + local ok = pcall(chunk) + if not ok then return {} end @@ -37,103 +38,34 @@ local function load_lua_file(path) end local function rgba_to_aarrggbb(value, fallback) - if type(value) ~= "string" then - return fallback + if type(fallback) ~= "number" then + return nil end - local hex = value:match("^rgba%((%x%x%x%x%x%x%x%x)%)$") + local hex = type(value) == "string" and value:match("^rgba%((%x%x%x%x%x%x%x%x)%)$") if not hex then return fallback end - local rr = hex:sub(1, 2) - local gg = hex:sub(3, 4) - local bb = hex:sub(5, 6) - local aa = hex:sub(7, 8) - return tonumber("0x" .. aa .. rr .. gg .. bb) -end - -local function color_with_alpha(value, alpha, fallback) - local base = rgba_to_aarrggbb(value, fallback) - if not base then + local parsed = tonumber("0x" .. hex:sub(7, 8) .. hex:sub(1, 2) .. hex:sub(3, 4) .. hex:sub(5, 6)) + if type(parsed) ~= "number" then return fallback end - local rgb = base & 0x00ffffff - return ((alpha & 0xff) << 24) | rgb + return parsed end -local function matugen_colors() - return load_lua_file(MATUGEN_PATH) -end +local function with_alpha(value, alpha, fallback) + if type(fallback) ~= "number" then + return nil + end -local function build_config() - local colors = matugen_colors() + local base = rgba_to_aarrggbb(value, fallback) + if type(base) ~= "number" then + base = fallback + end - return { - plugin = { - hyprspace = { - -- Theme colors are derived from the current matugen palette, but - -- each slot keeps an explicit fallback so the overview remains - -- usable even when the generated file is missing. - panel_color = color_with_alpha(colors.surface_container_high or colors.surface, 0xd8, 0xd8261d20), - panel_border_color = color_with_alpha(colors.primary or colors.outline, 0x88, 0x88ffb0cf), - workspace_active_background = color_with_alpha(colors.surface_container_highest or colors.surface_container_high, 0xb8, 0xb83c3235), - workspace_inactive_background = color_with_alpha(colors.surface_container or colors.surface, 0xde, 0xde261d20), - workspace_active_border = color_with_alpha(colors.primary or colors.on_surface, 0xb0, 0xb0ffb0cf), - workspace_inactive_border = color_with_alpha(colors.outline_variant or colors.outline, 0x55, 0x55504348), - - -- Layout tuning: - -- Keep the overview compact and reserve only the Waybar strip. - -- `panel_height` is the visible overview height. - -- `reserved_area` is the top gap already occupied by Waybar. - panel_height = 220, - panel_border_width = 2, - workspace_margin = 10, - reserved_area = 35, - workspace_border_size = 1, - - adaptive_height = false, - center_aligned = true, - on_bottom = false, - hide_background_layers = false, - hide_top_layers = false, - hide_overlay_layers = false, - draw_active_workspace = true, - hide_real_layers = false, - affect_strut = false, - - override_gaps = true, - gaps_in = 20, - gaps_out = 60, - - auto_drag = true, - auto_scroll = true, - exit_on_click = true, - switch_on_drop = false, - exit_on_switch = false, - show_new_workspace = true, - show_empty_workspace = true, - show_special_workspace = false, - - disable_gestures = false, - reverse_swipe = false, - swipe_fingers = 3, - swipe_distance = 300, - swipe_force_speed = 30, - swipe_cancel_ratio = 0.5, - swipe_threshold = 10.0, - swipe_closed_padding = 10.0, - workspace_scroll_speed = 2.0, - - disable_blur = false, - override_anim_speed = 0.0, - drag_alpha = 0.2, - exit_key = "Escape", - click_release_threshold_ms = 200, - }, - }, - } + return ((alpha & 0xff) << 24) | (base & 0x00ffffff) end local function plugin_loaded() @@ -145,67 +77,94 @@ local function ensure_plugin_loaded() return true end - -- hyprpm restores plugins itself. Only fall back to a manual binary path - -- when one was explicitly provided. - if not file_exists(pluginPath) then + if not file_exists(plugin_path) then return false end - hl.exec_cmd("hyprctl plugin load " .. shell_quote(pluginPath)) + hl.exec_cmd("hyprctl plugin load " .. shell_quote(plugin_path)) return plugin_loaded() end -function M.set_plugin_path(path) - pluginPath = path -end - -function M.apply_config() - if not plugin_loaded() then - return false - end - - hl.config(build_config()) - return true -end +local function build_config() + local colors = load_colors(IMPORT_COLOR) -function M.reload() - M.apply_config() - hl.exec_cmd("hyprctl reload") + return { + plugin = { + hyprspace = { + panel_color = with_alpha(colors.surface_container_high or colors.surface, 0xd8, 0xd8261d20), + panel_border_color = with_alpha(colors.primary or colors.outline, 0x88, 0x88ffb0cf), + workspace_active_background = with_alpha(colors.surface_container_highest or colors.surface_container_high, 0xb8, 0xb83c3235), + workspace_inactive_background = with_alpha(colors.surface_container or colors.surface, 0xde, 0xde261d20), + workspace_active_border = with_alpha(colors.primary or colors.on_surface, 0xb0, 0xb0ffb0cf), + workspace_inactive_border = with_alpha(colors.outline_variant or colors.outline, 0x55, 0x55504348), + + panel_height = 220, + panel_border_width = 2, + workspace_margin = 10, + reserved_area = 35, + workspace_border_size = 1, + + adaptive_height = false, + center_aligned = true, + on_bottom = false, + hide_background_layers = false, + hide_top_layers = false, + hide_overlay_layers = false, + draw_active_workspace = true, + hide_real_layers = false, + affect_strut = false, + + auto_drag = true, + auto_scroll = true, + exit_on_click = true, + switch_on_drop = false, + exit_on_switch = false, + show_new_workspace = true, + show_empty_workspace = true, + show_special_workspace = false, + + disable_gestures = false, + reverse_swipe = false, + swipe_fingers = 3, + swipe_distance = 300, + swipe_force_speed = 30, + swipe_cancel_ratio = 0.5, + swipe_threshold = 10.0, + swipe_closed_padding = 10.0, + workspace_scroll_speed = 2.0, + + disable_blur = false, + override_anim_speed = 0.0, + drag_alpha = 0.2, + exit_key = "Escape", + click_release_threshold_ms = 200, + }, + }, + } end -function M.overview(action) +local function apply_config() if not plugin_loaded() then return false end - M.apply_config() - hl.plugin.Hyprspace.overview(action or "toggle") + -- Hyprland 0.55+ native config path. + hl.config(build_config()) return true end -function M.toggle() - return M.overview("toggle") -end - function M.setup(opts) if type(opts) == "table" and type(opts.plugin_path) == "string" then - pluginPath = opts.plugin_path + plugin_path = opts.plugin_path end - -- Startup path: - -- 1. hyprpm-managed plugins are already loaded by Hyprland - -- 2. local/manual installs can opt-in by passing `plugin_path` - -- 3. apply the latest Lua config once the plugin is available hl.on("hyprland.start", function() ensure_plugin_loaded() - M.apply_config() + apply_config() end) - -- Config reload path: - -- if the plugin is already present, refresh its settings immediately. - if plugin_loaded() then - M.apply_config() - end + ensure_plugin_loaded() + apply_config() end return M diff --git a/src/Globals.hpp b/src/Globals.hpp index b13d827..423c834 100644 --- a/src/Globals.hpp +++ b/src/Globals.hpp @@ -42,8 +42,6 @@ inline HANDLE pHandle = NULL; typedef void (*tRenderWindow)(void*, PHLWINDOW, PHLMONITOR, const Time::steady_tp&, bool, Render::eRenderPassMode, bool, bool); extern void* pRenderWindow; -typedef void (*tRenderLayer)(void*, PHLLS, PHLMONITOR, const Time::steady_tp&, bool, bool); -extern void* pRenderLayer; namespace Config { extern CHyprColor panelBaseColor; extern CHyprColor panelBorderColor; @@ -57,9 +55,9 @@ namespace Config { extern int workspaceMargin; extern int reservedArea; extern int workspaceBorderSize; - extern bool adaptiveHeight; // TODO: implement + extern bool adaptiveHeight; extern bool centerAligned; - extern bool onBottom; // TODO: implement + extern bool onBottom; extern bool hideBackgroundLayers; extern bool hideTopLayers; extern bool hideOverlayLayers; @@ -67,10 +65,6 @@ namespace Config { extern bool hideRealLayers; extern bool affectStrut; - extern bool overrideGaps; - extern int gapsIn; - extern int gapsOut; - extern bool autoDrag; extern bool autoScroll; extern bool exitOnClick; diff --git a/src/Input.cpp b/src/Input.cpp index ba50243..715c7dc 100644 --- a/src/Input.cpp +++ b/src/Input.cpp @@ -1,14 +1,11 @@ #include "Overview.hpp" #include "Globals.hpp" +#include #include #include -// 컴পাইলারের Deprecation Warning ইগনোর করার জন্য প্রাগমা যুক্ত করা হলো -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - namespace { double panelTravel(PHLMONITOR owner) { @@ -26,120 +23,106 @@ double swipeVisibleThreshold() { return Config::swipeThreshold; } -} +} // namespace bool CHyprspaceWidget::buttonEvent(bool pressed, Vector2D coords) { - bool Return; + const auto owner = getOwner(); + if (!owner) + return true; - const auto dragTarget = g_layoutManager->dragController()->target(); + const auto dragTarget = g_layoutManager->dragController()->target(); const auto targetWindow = dragTarget ? dragTarget->window() : nullptr; - // this is for click to exit, we set a timeout for button release bool couldExit = false; - if (pressed) + if (pressed) { lastPressedTime = std::chrono::high_resolution_clock::now(); - else - if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - lastPressedTime).count() < Config::clickReleaseThresholdMs) - couldExit = true; - - int targetWorkspaceID = SPECIAL_WORKSPACE_START - 1; - - // find which workspace the mouse hovers over - for (auto& w : workspaceBoxes) { - auto wi = std::get<0>(w); - auto wb = std::get<1>(w); - if (wb.containsPoint(coords)) { - targetWorkspaceID = wi; - break; - } + } else if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - lastPressedTime).count() < Config::clickReleaseThresholdMs) { + couldExit = true; } - auto targetWorkspace = g_pCompositor->getWorkspaceByID(targetWorkspaceID); + WORKSPACEID targetWorkspaceID = SPECIAL_WORKSPACE_START - 1; - // create new workspace - if (targetWorkspace == nullptr && targetWorkspaceID >= SPECIAL_WORKSPACE_START) { - targetWorkspace = g_pCompositor->createNewWorkspace(targetWorkspaceID, getOwner()->m_id); + for (const auto& [workspaceID, workspaceBox] : workspaceBoxes) { + if (!workspaceBox.containsPoint(coords)) + continue; + + targetWorkspaceID = workspaceID; + break; } - // if the cursor is hovering over workspace, clicking should switch workspace instead of starting window drag + auto targetWorkspace = g_pCompositor->getWorkspaceByID(targetWorkspaceID); + if (!targetWorkspace && targetWorkspaceID >= SPECIAL_WORKSPACE_START) + targetWorkspace = g_pCompositor->createNewWorkspace(targetWorkspaceID, owner->m_id); + if (Config::autoDrag && (targetWorkspace == nullptr || !pressed)) { if (g_layoutManager->dragController()->target()) g_layoutManager->endDragTarget(); if (pressed) { - const auto PWINDOW = g_pCompositor->vectorToWindowUnified(coords, Desktop::View::WINDOW_ONLY, nullptr); - if (PWINDOW) { - const auto LT = PWINDOW->layoutTarget(); - if (LT) - g_layoutManager->beginDragTarget(LT, MBIND_MOVE); + const auto window = g_pCompositor->vectorToWindowUnified(coords, Desktop::View::WINDOW_ONLY, nullptr); + if (window) { + const auto target = window->layoutTarget(); + if (target) + g_layoutManager->beginDragTarget(target, MBIND_MOVE); } } } - Return = false; - // release window on workspace to drop it in if (targetWindow && targetWorkspace != nullptr && !pressed) { g_pCompositor->moveWindowToWorkspaceSafe(targetWindow, targetWorkspace); if (targetWindow->m_isFloating) { - auto targetPos = getOwner()->m_position + (getOwner()->m_size / 2.) - (targetWindow->m_reportedSize / 2.); + const auto targetPos = owner->m_position + (owner->m_size / 2.) - (targetWindow->m_reportedSize / 2.); targetWindow->m_position = targetPos; *targetWindow->m_realPosition = targetPos; } + if (Config::switchOnDrop) { g_pCompositor->getMonitorFromID(targetWorkspace->m_monitor->m_id)->changeWorkspace(targetWorkspace->m_id); - if (Config::exitOnSwitch && active) hide(); + if (Config::exitOnSwitch && active) + hide(); } + updateLayout(); - } - // click workspace to change to workspace and exit overview - else if (targetWorkspace && !pressed) { - if (targetWorkspace->m_isSpecialWorkspace) - getOwner()->activeSpecialWorkspaceID() == targetWorkspaceID ? getOwner()->setSpecialWorkspace(nullptr) : getOwner()->setSpecialWorkspace(targetWorkspaceID); - else { + } else if (targetWorkspace && !pressed) { + if (targetWorkspace->m_isSpecialWorkspace) { + owner->activeSpecialWorkspaceID() == targetWorkspaceID ? owner->setSpecialWorkspace(nullptr) : owner->setSpecialWorkspace(targetWorkspaceID); + } else { g_pCompositor->getMonitorFromID(targetWorkspace->m_monitor->m_id)->changeWorkspace(targetWorkspace->m_id); } - if (Config::exitOnSwitch && active) hide(); + + if (Config::exitOnSwitch && active) + hide(); + } else if (Config::exitOnClick && targetWorkspace == nullptr && active && couldExit && !pressed) { + hide(); } - // click elsewhere to exit overview - else if (Config::exitOnClick && targetWorkspace == nullptr && active && couldExit && !pressed) hide(); - return Return; + // While overview is active all left-click input is consumed by the panel. + return false; } bool CHyprspaceWidget::axisEvent(double delta, wl_pointer_axis axis, Vector2D coords) { - const auto owner = getOwner(); + if (!owner) + return true; + const auto travel = panelTravel(owner); - CBox widgetBox = {owner->m_position.x, owner->m_position.y - curYOffset->value(), owner->m_transformedSize.x, travel}; + CBox panelBox = {owner->m_position.x, owner->m_position.y - curYOffset->value(), owner->m_transformedSize.x, travel}; if (Config::onBottom) - widgetBox = {owner->m_position.x, owner->m_position.y + owner->m_transformedSize.y - travel + curYOffset->value(), owner->m_transformedSize.x, travel}; + panelBox = {owner->m_position.x, owner->m_position.y + owner->m_transformedSize.y - travel + curYOffset->value(), owner->m_transformedSize.x, travel}; - // scroll through panel if cursor is on it - if (widgetBox.containsPoint(coords * getOwner()->m_scale)) { - // only horizontal scroll pans the panel; ignore vertical scroll here + if (panelBox.containsPoint(coords * owner->m_scale)) { if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) *workspaceScrollOffset = workspaceScrollOffset->goal() - delta * Config::workspaceScrollSpeed; - } - // otherwise, scroll to switch active workspace (vertical scroll only) - else if (Config::autoScroll && axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { - if (delta < 0) { - SWorkspaceIDName wsIDName = getWorkspaceIDNameFromString("r-1"); - if (g_pCompositor->getWorkspaceByID(wsIDName.id) == nullptr) { - auto newWorkspace = g_pCompositor->createNewWorkspace(wsIDName.id, ownerID); - (void)newWorkspace; - } - getOwner()->changeWorkspace(wsIDName.id); - } - else { - SWorkspaceIDName wsIDName = getWorkspaceIDNameFromString("r+1"); - if (g_pCompositor->getWorkspaceByID(wsIDName.id) == nullptr) { - auto newWorkspace = g_pCompositor->createNewWorkspace(wsIDName.id, ownerID); - (void)newWorkspace; - } - getOwner()->changeWorkspace(wsIDName.id); - } + } else if (Config::autoScroll && axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + const auto relative = delta < 0 ? "r-1" : "r+1"; + const auto wsIDName = getWorkspaceIDNameFromString(relative); + if (!g_pCompositor->getWorkspaceByID(wsIDName.id)) + (void)g_pCompositor->createNewWorkspace(wsIDName.id, ownerID); + + owner->changeWorkspace(wsIDName.id); } + // Keep wheel events scoped to overview while it is active. return false; } @@ -148,21 +131,20 @@ bool CHyprspaceWidget::isSwiping() { } bool CHyprspaceWidget::beginSwipe(IPointer::SSwipeBeginEvent e) { - swiping = true; + (void)e; + swiping = true; activeBeforeSwipe = active; - avgSwipeSpeed = 0; - swipePoints = 0; + avgSwipeSpeed = 0.; + swipePoints = 0; return false; } bool CHyprspaceWidget::updateSwipe(IPointer::SSwipeUpdateEvent e) { - // restrict swipe to a axis with the most significant movement to prevent misinput - const auto absY = std::abs(e.delta.y); - const auto verticalSwipe = absY > 0.0 && (std::abs(e.delta.x) / absY) < 1.0; + const auto absY = std::abs(e.delta.y); + const bool verticalSwipe = absY > 0.0 && (std::abs(e.delta.x) / absY) < 1.0; if (verticalSwipe) { if (swiping && e.fingers == static_cast(Config::swipeFingers)) { - const auto owner = getOwner(); if (!owner) return true; @@ -171,12 +153,12 @@ bool CHyprspaceWidget::updateSwipe(IPointer::SSwipeUpdateEvent e) { const auto currentScaling = owner->m_size.x / static_cast(distance); const auto travel = panelTravel(owner); - double scrollDifferential = e.delta.y * (Config::reverseSwipe ? -1 : 1) * (Config::onBottom ? -1 : 1) * currentScaling; + const double scrollDelta = e.delta.y * (Config::reverseSwipe ? -1 : 1) * (Config::onBottom ? -1 : 1) * currentScaling; - curSwipeOffset += scrollDifferential; + curSwipeOffset += scrollDelta; curSwipeOffset = std::clamp(curSwipeOffset, swipeClosedOffset(), travel); - avgSwipeSpeed = (avgSwipeSpeed * swipePoints + scrollDifferential) / (swipePoints + 1); + avgSwipeSpeed = (avgSwipeSpeed * swipePoints + scrollDelta) / (swipePoints + 1); swipePoints++; curYOffset->setValueAndWarp(travel - curSwipeOffset); @@ -188,77 +170,66 @@ bool CHyprspaceWidget::updateSwipe(IPointer::SSwipeUpdateEvent e) { return false; } - } - else { - // scroll through panel - if (e.fingers == static_cast(Config::swipeFingers) && active) { - const auto owner = getOwner(); - const auto travel = panelTravel(owner); - CBox widgetBox = {owner->m_position.x, owner->m_position.y - curYOffset->value(), owner->m_transformedSize.x, travel}; - if (Config::onBottom) - widgetBox = {owner->m_position.x, owner->m_position.y + owner->m_transformedSize.y - travel + curYOffset->value(), owner->m_transformedSize.x, travel}; - if (widgetBox.containsPoint(g_pInputManager->getMouseCoordsInternal() * getOwner()->m_scale)) { - workspaceScrollOffset->setValueAndWarp(workspaceScrollOffset->goal() + e.delta.x * Config::workspaceScrollSpeed); - return false; - } + } else if (e.fingers == static_cast(Config::swipeFingers) && active) { + const auto owner = getOwner(); + if (!owner) + return true; + + const auto travel = panelTravel(owner); + CBox panelBox = {owner->m_position.x, owner->m_position.y - curYOffset->value(), owner->m_transformedSize.x, travel}; + if (Config::onBottom) + panelBox = {owner->m_position.x, owner->m_position.y + owner->m_transformedSize.y - travel + curYOffset->value(), owner->m_transformedSize.x, travel}; + + if (panelBox.containsPoint(g_pInputManager->getMouseCoordsInternal() * owner->m_scale)) { + workspaceScrollOffset->setValueAndWarp(workspaceScrollOffset->goal() + e.delta.x * Config::workspaceScrollSpeed); + return false; } } - // otherwise, do not cancel the event and perform workspace swipe normally + return true; } -// janky asf bool CHyprspaceWidget::endSwipe(IPointer::SSwipeEndEvent e) { swiping = false; - const auto owner = getOwner(); + + const auto owner = getOwner(); const auto travel = panelTravel(owner); - // force cancel swipe + if (e.cancelled) { if (active) hide(); curSwipeOffset = swipeClosedOffset(); - } - else { - if (activeBeforeSwipe) { - if ((curSwipeOffset < travel * Config::swipeCancelRatio) || avgSwipeSpeed < -Config::swipeForceSpeed) { - if (active) - hide(); - else { - *curYOffset = travel; - curSwipeOffset = swipeClosedOffset(); - } - } + } else if (activeBeforeSwipe) { + if ((curSwipeOffset < travel * Config::swipeCancelRatio) || avgSwipeSpeed < -Config::swipeForceSpeed) { + if (active) + hide(); else { - // cancel - if (!active) - show(); - else { - *curYOffset = 0; - curSwipeOffset = travel; - } + *curYOffset = travel; + curSwipeOffset = swipeClosedOffset(); } + } else if (!active) { + show(); + } else { + *curYOffset = 0; + curSwipeOffset = travel; } - else { - if ((curSwipeOffset > travel * (1.F - Config::swipeCancelRatio)) || avgSwipeSpeed > Config::swipeForceSpeed) { - if (!active) - show(); - else { - *curYOffset = 0; - curSwipeOffset = travel; - } - } + } else { + if ((curSwipeOffset > travel * (1.F - Config::swipeCancelRatio)) || avgSwipeSpeed > Config::swipeForceSpeed) { + if (!active) + show(); else { - // cancel - if (active) - hide(); - else { - *curYOffset = travel; - curSwipeOffset = swipeClosedOffset(); - } + *curYOffset = 0; + curSwipeOffset = travel; } + } else if (active) { + hide(); + } else { + *curYOffset = travel; + curSwipeOffset = swipeClosedOffset(); } } - avgSwipeSpeed = 0; - swipePoints = 0; + + avgSwipeSpeed = 0.; + swipePoints = 0; return false; } diff --git a/src/Layout.cpp b/src/Layout.cpp index ab32878..ba6c890 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -1,78 +1,21 @@ #include "Overview.hpp" #include "Globals.hpp" -#include -// FIXME: preserve original workspace rules -void CHyprspaceWidget::updateLayout() { - - if (!Config::affectStrut) return; - - const auto currentHeight = Config::panelHeight + Config::reservedArea; - const auto pMonitor = getOwner(); - if (!pMonitor) return; +#include - static auto PGAPSINDATA = CConfigValue("general:gaps_in"); - static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); - if (!PGAPSINDATA.good() || !PGAPSOUTDATA.good()) return; - - const auto PGAPSINBASE = PGAPSINDATA.ptr(); - const auto PGAPSOUTBASE = PGAPSOUTDATA.ptr(); - if (!PGAPSINBASE || !PGAPSOUTBASE || PGAPSINBASE->getDataType() != Config::CVD_TYPE_CSS_VALUE || PGAPSOUTBASE->getDataType() != Config::CVD_TYPE_CSS_VALUE) return; - - auto* const PGAPSIN = static_cast(PGAPSINBASE); - auto* const PGAPSOUT = static_cast(PGAPSOUTBASE); +void CHyprspaceWidget::updateLayout() { + const auto monitor = getOwner(); + if (!monitor) + return; - if (active) { - if (!Config::onBottom) - pMonitor->m_reservedArea = Desktop::CReservedArea(currentHeight, 0, 0, 0); - else - pMonitor->m_reservedArea = Desktop::CReservedArea(0, 0, currentHeight, 0); + const auto reservedHeight = std::max(0, Config::panelHeight + Config::reservedArea); + if (active && Config::affectStrut) { + monitor->m_reservedArea = Config::onBottom ? Desktop::CReservedArea(0, 0, reservedHeight, 0) : Desktop::CReservedArea(reservedHeight, 0, 0, 0); } else { - pMonitor->m_reservedArea = Desktop::CReservedArea(); + monitor->m_reservedArea = Desktop::CReservedArea(); } g_pHyprRenderer->arrangeLayersForMonitor(ownerID); - - // gaps are created via workspace rules - // there are no way to write to m_dWorkspaceRules directly - // and we want to refrain from using function hooks - // so we create a workspace rule for ALL workspaces through handleWorkspaceRules - // Geneva Convention violation type hack but idc atm - if (active) { - const auto oActiveWorkspace = pMonitor->m_activeWorkspace; - if (!oActiveWorkspace) return; - - for (auto& ws : g_pCompositor->getWorkspaces()) { // HACK: recalculate other workspaces without reserved area - if (ws && ws->m_monitor && ws->m_monitor->m_id == ownerID && ws->m_id != oActiveWorkspace->m_id) { - pMonitor->m_activeWorkspace = ws.lock(); - const auto curRules = std::to_string(pMonitor->activeWorkspaceID()) + ", gapsin:" + PGAPSIN->toString() + ", gapsout:" + PGAPSOUT->toString(); - if (Config::overrideGaps) { - if (const auto legacy = Config::Legacy::mgr().lock()) - legacy->handleWorkspaceRules("", curRules); - } - g_layoutManager->recalculateMonitor(pMonitor); - } - } - pMonitor->m_activeWorkspace = oActiveWorkspace; - - const auto curRules = std::to_string(pMonitor->activeWorkspaceID()) + ", gapsin:" + std::to_string(Config::gapsIn) + ", gapsout:" + std::to_string(Config::gapsOut); - if (Config::overrideGaps) { - if (const auto legacy = Config::Legacy::mgr().lock()) - legacy->handleWorkspaceRules("", curRules); - } - g_layoutManager->recalculateMonitor(pMonitor); - - } - else { - for (auto& ws : g_pCompositor->getWorkspaces()) { - if (ws && ws->m_monitor && ws->m_monitor->m_id == ownerID) { - const auto curRules = std::to_string(ws->m_id) + ", gapsin:" + PGAPSIN->toString() + ", gapsout:" + PGAPSOUT->toString(); - if (Config::overrideGaps) { - if (const auto legacy = Config::Legacy::mgr().lock()) - legacy->handleWorkspaceRules("", curRules); - } - } - } - g_layoutManager->recalculateMonitor(pMonitor); - } + g_layoutManager->recalculateMonitor(monitor); + g_pCompositor->scheduleFrameForMonitor(monitor); } diff --git a/src/Overview.cpp b/src/Overview.cpp index 2c1c5a6..7f3114e 100644 --- a/src/Overview.cpp +++ b/src/Overview.cpp @@ -52,7 +52,7 @@ void CHyprspaceWidget::restoreFullscreenWindows() { if (!window) continue; - g_pCompositor->setWindowFullscreenState(window, Desktop::View::SFullscreenState(fullscreenMode)); + g_pCompositor->setWindowFullscreenState(window, Desktop::View::SFullscreenState{.internal = fullscreenMode, .client = fullscreenMode}); if (fullscreenMode == FSMODE_FULLSCREEN) window->m_wantsInitialFullscreen = false; } diff --git a/src/Overview.hpp b/src/Overview.hpp index f0b28a3..5c7777b 100644 --- a/src/Overview.hpp +++ b/src/Overview.hpp @@ -14,7 +14,7 @@ class CHyprspaceWidget { // for checking mouse hover for workspace drag and move // modified on draw call, accessed on mouse click and release - std::vector> workspaceBoxes; + std::vector> workspaceBoxes; // for storing the fullscreen state of windows prior to overview activation (which unfullscreens all windows) std::vector> prevFullscreen; diff --git a/src/Render.cpp b/src/Render.cpp index 52fb0ef..e730cef 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -1,56 +1,69 @@ #include "Overview.hpp" #include "Globals.hpp" -#include + +#include +#include +#include +#include + #include -#include +#include #include -#include +#include #include -#include +#include #include -#include -#include +namespace { + +struct SWorkspaceWindows { + std::vector tiled; + std::vector floating; +}; void renderRect(CBox box, CHyprColor color) { - CRectPassElement::SRectData rectdata; - rectdata.color = color; - rectdata.box = box; - g_pHyprRenderer->m_renderPass.add(makeUnique(rectdata)); + CRectPassElement::SRectData rectData; + rectData.color = color; + rectData.box = box; + g_pHyprRenderer->m_renderPass.add(makeUnique(rectData)); } void renderRectWithBlur(CBox box, CHyprColor color) { - CRectPassElement::SRectData rectdata; - rectdata.color = color; - rectdata.box = box; - rectdata.blur = true; - g_pHyprRenderer->m_renderPass.add(makeUnique(rectdata)); + CRectPassElement::SRectData rectData; + rectData.color = color; + rectData.box = box; + rectData.blur = true; + g_pHyprRenderer->m_renderPass.add(makeUnique(rectData)); } void renderBorder(CBox box, const Config::CGradientValueData& gradient, int size) { - CBorderPassElement::SBorderData data; - data.box = box; - data.grad1 = gradient; - data.round = 0; - data.a = 1.f; - data.borderSize = size; - g_pHyprRenderer->m_renderPass.add(makeUnique(data)); + CBorderPassElement::SBorderData borderData; + borderData.box = box; + borderData.grad1 = gradient; + borderData.round = 0; + borderData.a = 1.F; + borderData.borderSize = size; + g_pHyprRenderer->m_renderPass.add(makeUnique(borderData)); } -void renderWindowStub(PHLWINDOW pWindow, PHLMONITOR pMonitor, PHLWORKSPACE pWorkspaceOverride, CBox rectOverride, CBox clipBox, const Time::steady_tp& time) { - if (!pWindow || !pMonitor || !pWorkspaceOverride) return; - if (!pWindow->m_isMapped || !pWindow->wlSurface() || !pWindow->wlSurface()->resource()) return; +void renderWindowStub(PHLWINDOW window, PHLMONITOR monitor, CBox rectOverride, CBox clipBox, const Time::steady_tp& time) { + if (!window || !monitor) + return; + + if (!window->m_isMapped || !window->wlSurface() || !window->wlSurface()->resource()) + return; Render::SRenderModifData renderModif; - const auto oRealPosition = pWindow->m_realPosition->value(); - const auto oSize = pWindow->m_realSize->value(); - const float logicalW = std::max((float)oSize.x, 5.F); - const float scaleMod = rectOverride.w / std::max(logicalW * pMonitor->m_scale, 5.F); - if (!(scaleMod > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) return; + const auto realPosition = window->m_realPosition->value(); + const auto realSize = window->m_realSize->value(); + const float logicalW = std::max(static_cast(realSize.x), 5.F); + const float scaleMod = rectOverride.w / std::max(logicalW * monitor->m_scale, 5.F); + if (!(scaleMod > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) + return; - const Vector2D logicalTL = oRealPosition + pWindow->m_floatingOffset; - const Vector2D scaledTL = (logicalTL - pMonitor->m_position) * pMonitor->m_scale; + const Vector2D logicalTL = realPosition + window->m_floatingOffset; + const Vector2D scaledTL = (logicalTL - monitor->m_position) * monitor->m_scale; const Vector2D translate = rectOverride.pos() / scaleMod - scaledTL; renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, std::any(translate))); @@ -58,352 +71,346 @@ void renderWindowStub(PHLWINDOW pWindow, PHLMONITOR pMonitor, PHLWORKSPACE pWork renderModif.enabled = true; g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = renderModif})); - Hyprutils::Utils::CScopeGuard x([] { + Hyprutils::Utils::CScopeGuard clearHints([] { g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = Render::SRenderModifData{}})); }); - g_pHyprRenderer->damageWindow(pWindow); - - CSurfacePassElement::SRenderData renderdata = {pMonitor, time}; - renderdata.pos = oRealPosition + pWindow->m_floatingOffset; - renderdata.w = std::max(oSize.x, 5.0); - renderdata.h = std::max(oSize.y, 5.0); - renderdata.surface = pWindow->wlSurface()->resource(); - renderdata.dontRound = pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); - renderdata.fadeAlpha = 1.F; - renderdata.alpha = 1.F; - renderdata.decorate = false; - renderdata.rounding = renderdata.dontRound ? 0 : pWindow->rounding() * scaleMod * pMonitor->m_scale; - renderdata.roundingPower = renderdata.dontRound ? 2.0F : pWindow->roundingPower(); - renderdata.blur = false; - renderdata.pWindow = pWindow; - renderdata.clipBox = clipBox; - renderdata.useNearestNeighbor = false; - renderdata.squishOversized = true; - renderdata.surfaceCounter = 0; - - pWindow->wlSurface()->resource()->breadthfirst( - [&renderdata, &pWindow](SP s, const Vector2D& offset, void* data) { - if (!s || !s->m_current.texture) + g_pHyprRenderer->damageWindow(window); + + CSurfacePassElement::SRenderData renderData = {monitor, time}; + renderData.pos = realPosition + window->m_floatingOffset; + renderData.w = std::max(realSize.x, 5.0); + renderData.h = std::max(realSize.y, 5.0); + renderData.surface = window->wlSurface()->resource(); + renderData.dontRound = window->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); + renderData.fadeAlpha = 1.F; + renderData.alpha = 1.F; + renderData.decorate = false; + renderData.rounding = renderData.dontRound ? 0 : window->rounding() * scaleMod * monitor->m_scale; + renderData.roundingPower = renderData.dontRound ? 2.0F : window->roundingPower(); + renderData.blur = false; + renderData.pWindow = window; + renderData.clipBox = clipBox; + renderData.useNearestNeighbor = false; + renderData.squishOversized = true; + renderData.surfaceCounter = 0; + + window->wlSurface()->resource()->breadthfirst( + [&renderData, &window](SP surface, const Vector2D& offset, void* data) { + (void)data; + if (!surface || !surface->m_current.texture) return; - if (s->m_current.size.x < 1 || s->m_current.size.y < 1) + if (surface->m_current.size.x < 1 || surface->m_current.size.y < 1) return; - renderdata.localPos = offset; - renderdata.texture = s->m_current.texture; - renderdata.surface = s; - renderdata.mainSurface = s == pWindow->wlSurface()->resource(); - g_pHyprRenderer->m_renderPass.add(makeUnique(renderdata)); - renderdata.surfaceCounter++; + renderData.localPos = offset; + renderData.texture = surface->m_current.texture; + renderData.surface = surface; + renderData.mainSurface = surface == window->wlSurface()->resource(); + g_pHyprRenderer->m_renderPass.add(makeUnique(renderData)); + renderData.surfaceCounter++; }, nullptr); } -void renderLayerStub(PHLLS pLayer, PHLMONITOR pMonitor, CBox rectOverride, CBox clipBox, const Time::steady_tp& time) { - if (!pLayer || !pMonitor) return; +void renderLayerStub(PHLLS layer, PHLMONITOR monitor, CBox rectOverride, CBox clipBox, const Time::steady_tp& time) { + if (!layer || !monitor) + return; - if (!pLayer->m_mapped || pLayer->m_readyToDelete || !pLayer->m_layerSurface || !pLayer->wlSurface() || !pLayer->wlSurface()->resource()) return; + if (!layer->m_mapped || layer->m_readyToDelete || !layer->m_layerSurface || !layer->wlSurface() || !layer->wlSurface()->resource()) + return; - Vector2D oRealPosition = pLayer->m_realPosition->value(); - Vector2D oSize = pLayer->m_realSize->value(); + const Vector2D realPosition = layer->m_realPosition->value(); + const Vector2D realSize = layer->m_realSize->value(); - const float curScaling = rectOverride.w / (oSize.x); - if (!(curScaling > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) return; + const float scale = rectOverride.w / realSize.x; + if (!(scale > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) + return; Render::SRenderModifData renderModif; - - renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, std::any(pMonitor->m_position + (rectOverride.pos() / curScaling) - oRealPosition))); - renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, std::any(curScaling))); + renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, std::any(monitor->m_position + (rectOverride.pos() / scale) - realPosition))); + renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, std::any(scale))); renderModif.enabled = true; g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = renderModif})); - Hyprutils::Utils::CScopeGuard x([] { + Hyprutils::Utils::CScopeGuard clearHints([] { g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = Render::SRenderModifData{}})); }); - CSurfacePassElement::SRenderData renderdata = {pMonitor, time, oRealPosition}; - renderdata.fadeAlpha = 1.F; - renderdata.alpha = 1.F; - renderdata.blur = false; - renderdata.surface = pLayer->wlSurface()->resource(); - renderdata.decorate = false; - renderdata.w = oSize.x; - renderdata.h = oSize.y; - renderdata.pLS = pLayer; - renderdata.clipBox = clipBox; - renderdata.surfaceCounter = 0; - - pLayer->wlSurface()->resource()->breadthfirst( - [&renderdata, &pLayer](SP s, const Vector2D& offset, void* data) { - if (!s || !s->m_current.texture) + CSurfacePassElement::SRenderData renderData = {monitor, time, realPosition}; + renderData.fadeAlpha = 1.F; + renderData.alpha = 1.F; + renderData.blur = false; + renderData.surface = layer->wlSurface()->resource(); + renderData.decorate = false; + renderData.w = realSize.x; + renderData.h = realSize.y; + renderData.pLS = layer; + renderData.clipBox = clipBox; + renderData.surfaceCounter = 0; + + layer->wlSurface()->resource()->breadthfirst( + [&renderData, &layer](SP surface, const Vector2D& offset, void* data) { + (void)data; + if (!surface || !surface->m_current.texture) return; - if (s->m_current.size.x < 1 || s->m_current.size.y < 1) + if (surface->m_current.size.x < 1 || surface->m_current.size.y < 1) return; - renderdata.localPos = offset; - renderdata.texture = s->m_current.texture; - renderdata.surface = s; - renderdata.mainSurface = s == pLayer->wlSurface()->resource(); - g_pHyprRenderer->m_renderPass.add(makeUnique(renderdata)); - renderdata.surfaceCounter++; + renderData.localPos = offset; + renderData.texture = surface->m_current.texture; + renderData.surface = surface; + renderData.mainSurface = surface == layer->wlSurface()->resource(); + g_pHyprRenderer->m_renderPass.add(makeUnique(renderData)); + renderData.surfaceCounter++; }, - &renderdata); + &renderData); } -// NOTE: rects and clipbox positions are relative to the monitor, while damagebox and layers are not, what the fuck? xd -void CHyprspaceWidget::draw() { +bool renderWindowPreview(PHLWINDOW window, PHLMONITOR owner, double workspaceX, double workspaceY, double monitorScaleFactor, CBox workspaceClip, const Time::steady_tp& time) { + if (!window || !owner) + return false; - workspaceBoxes.clear(); + const double wX = workspaceX + ((window->m_realPosition->value().x - owner->m_position.x) * monitorScaleFactor * owner->m_scale); + const double wY = workspaceY + ((window->m_realPosition->value().y - owner->m_position.y) * monitorScaleFactor * owner->m_scale); + const double wW = window->m_realSize->value().x * monitorScaleFactor * owner->m_scale; + const double wH = window->m_realSize->value().y * monitorScaleFactor * owner->m_scale; + if (!(wW > 0 && wH > 0)) + return false; + + renderWindowStub(window, owner, {wX, wY, wW, wH}, workspaceClip, time); + return true; +} + +} // namespace - if (!active && !curYOffset->isBeingAnimated()) return; +void CHyprspaceWidget::draw() { + workspaceBoxes.clear(); - auto owner = getOwner(); + if (!active && !curYOffset->isBeingAnimated()) + return; - if (!owner) return; + const auto owner = getOwner(); + if (!owner) + return; - // Full-monitor clip in monitor-local coords. Never use default CBox() to "clear" clipBox — - // hyprutils::CBox() only sets w/h to 0 and leaves x/y uninitialized, which corrupts scissor state. const CBox monitorClip = {{0, 0}, owner->m_transformedSize}; - - const auto time = Time::steadyNow(); + const auto time = Time::steadyNow(); owner->m_blurFBShouldRender = true; - int bottomInvert = 1; - if (Config::onBottom) bottomInvert = -1; + const int panelDirection = Config::onBottom ? -1 : 1; - // Background box - CBox widgetBox = {owner->m_position.x, owner->m_position.y + (Config::onBottom * (owner->m_transformedSize.y - ((Config::panelHeight + Config::reservedArea) * owner->m_scale))) - (bottomInvert * curYOffset->value()), owner->m_transformedSize.x, (Config::panelHeight + Config::reservedArea) * owner->m_scale}; //TODO: update size on monitor change + CBox panelBox = {owner->m_position.x, + owner->m_position.y + (Config::onBottom * (owner->m_transformedSize.y - ((Config::panelHeight + Config::reservedArea) * owner->m_scale))) - (panelDirection * curYOffset->value()), + owner->m_transformedSize.x, + (Config::panelHeight + Config::reservedArea) * owner->m_scale}; - // set widgetBox relative to current monitor for rendering panel - widgetBox.x -= owner->m_position.x; - widgetBox.y -= owner->m_position.y; + panelBox.x -= owner->m_position.x; + panelBox.y -= owner->m_position.y; g_pHyprRenderer->m_renderData.clipBox = monitorClip; - if (!Config::disableBlur) { - renderRectWithBlur(widgetBox, Config::panelBaseColor); - } - else { - renderRect(widgetBox, Config::panelBaseColor); - } + if (!Config::disableBlur) + renderRectWithBlur(panelBox, Config::panelBaseColor); + else + renderRect(panelBox, Config::panelBaseColor); - // Panel Border if (Config::panelBorderWidth > 0) { - // Border box - CBox borderBox = {widgetBox.x, owner->m_position.y + (Config::onBottom * owner->m_transformedSize.y) + (Config::panelHeight + Config::reservedArea - curYOffset->value() * owner->m_scale) * bottomInvert, owner->m_transformedSize.x, static_cast(Config::panelBorderWidth)}; + CBox borderBox = {panelBox.x, + owner->m_position.y + (Config::onBottom * owner->m_transformedSize.y) + (Config::panelHeight + Config::reservedArea - curYOffset->value() * owner->m_scale) * panelDirection, + owner->m_transformedSize.x, + static_cast(Config::panelBorderWidth)}; borderBox.y -= owner->m_position.y; - renderRect(borderBox, Config::panelBorderColor); } - - // unscaled and relative to owner - //CBox damageBox = {0, (Config::onBottom * (owner->m_transformedSize.y - ((Config::panelHeight + Config::reservedArea)))) - (bottomInvert * curYOffset->value()), owner->m_transformedSize.x, (Config::panelHeight + Config::reservedArea) * owner->m_scale}; - - //owner->addDamage(damageBox); g_pHyprRenderer->damageMonitor(owner); - // damage the entire monitor to ensure full redraw during overview - g_pHyprRenderer->damageMonitor(owner); - - // the list of workspaces to show - std::vector workspaces; + std::vector workspaces; - if (Config::showSpecialWorkspace) { + if (Config::showSpecialWorkspace) workspaces.push_back(SPECIAL_WORKSPACE_START); - } - // find the lowest and highest workspace id to determine which empty workspaces to insert - int lowestID = INT_MAX; - int highestID = 1; - for (auto& ws : g_pCompositor->getWorkspaces()) { - if (!ws) continue; - // normal workspaces start from 1, special workspaces ends on -2 - if (ws->m_id < 1) continue; - if (ws->m_monitor->m_id == ownerID) { - workspaces.push_back(ws->m_id); - if (highestID < ws->m_id) highestID = ws->m_id; - if (lowestID > ws->m_id) lowestID = ws->m_id; - } + WORKSPACEID lowestID = std::numeric_limits::max(); + WORKSPACEID highestID = 1; + for (const auto& ws : g_pCompositor->getWorkspaces()) { + if (!ws || ws->m_id < 1 || !ws->m_monitor || ws->m_monitor->m_id != ownerID) + continue; + + workspaces.push_back(ws->m_id); + highestID = std::max(highestID, ws->m_id); + lowestID = std::min(lowestID, ws->m_id); } - // include empty workspaces that are between non-empty ones if (Config::showEmptyWorkspace) { - int wsIDStart = 1; - int wsIDEnd = highestID; + WORKSPACEID wsIDStart = 1; + WORKSPACEID wsIDEnd = highestID; - // hyprsplit/split-monitor-workspaces compatibility if (numWorkspaces > 0) { - wsIDStart = std::min(numWorkspaces * ownerID + 1, lowestID); - wsIDEnd = std::max(numWorkspaces * ownerID + 1, highestID); // always show the initial workspace for current monitor + const auto baseWorkspace = static_cast(numWorkspaces * ownerID + 1); + wsIDStart = std::min(baseWorkspace, lowestID); + wsIDEnd = std::max(baseWorkspace, highestID); } - for (int i = wsIDStart; i <= wsIDEnd; i++) { - if (i == owner->activeSpecialWorkspaceID()) continue; - const auto pWorkspace = g_pCompositor->getWorkspaceByID(i); - if (pWorkspace == nullptr) - workspaces.push_back(i); + for (WORKSPACEID id = wsIDStart; id <= wsIDEnd; id++) { + if (id == owner->activeSpecialWorkspaceID()) + continue; + + if (g_pCompositor->getWorkspaceByID(id) == nullptr) + workspaces.push_back(id); } } - // add a new empty workspace at last if (Config::showNewWorkspace) { - // get the lowest empty workspce id after the highest id of current workspace - while (g_pCompositor->getWorkspaceByID(highestID) != nullptr) highestID++; + while (g_pCompositor->getWorkspaceByID(highestID) != nullptr) + highestID++; workspaces.push_back(highestID); } std::sort(workspaces.begin(), workspaces.end()); + workspaces.erase(std::unique(workspaces.begin(), workspaces.end()), workspaces.end()); + + const auto workspaceCount = static_cast(workspaces.size()); + if (workspaceCount == 0) + return; + + const double monitorScaleFactor = ((Config::panelHeight - 2 * Config::workspaceMargin) / owner->m_transformedSize.y) * owner->m_scale; + const double workspaceBoxW = owner->m_transformedSize.x * monitorScaleFactor; + const double workspaceBoxH = owner->m_transformedSize.y * monitorScaleFactor; + const double workspaceGroupW = workspaceBoxW * workspaceCount + (Config::workspaceMargin * owner->m_scale) * (workspaceCount - 1); + double workspaceOffsetX = Config::centerAligned ? workspaceScrollOffset->value() + (panelBox.w / 2.) - (workspaceGroupW / 2.) : workspaceScrollOffset->value() + Config::workspaceMargin; + const double workspaceOffsetY = !Config::onBottom ? (((Config::reservedArea + Config::workspaceMargin) * owner->m_scale) - curYOffset->value()) : (owner->m_transformedSize.y - ((Config::reservedArea + Config::workspaceMargin) * owner->m_scale) - workspaceBoxH + curYOffset->value()); + const double workspaceOverflow = std::max(((workspaceGroupW - panelBox.w) / 2.) + (Config::workspaceMargin * owner->m_scale), 0.); + + *workspaceScrollOffset = std::clamp(workspaceScrollOffset->goal(), -workspaceOverflow, workspaceOverflow); + + if (!(workspaceBoxW > 0 && workspaceBoxH > 0)) + return; + + std::unordered_map windowsByWorkspace; + windowsByWorkspace.reserve(workspaceCount + 2); + for (const auto& window : g_pCompositor->m_windows) { + if (!window || !window->m_workspace) + continue; + + auto& bucket = windowsByWorkspace[window->m_workspace->m_id]; + if (window->m_isFloating) + bucket.floating.push_back(window); + else + bucket.tiled.push_back(window); + } - // render workspace boxes - int wsCount = workspaces.size(); - double monitorSizeScaleFactor = ((Config::panelHeight - 2 * Config::workspaceMargin) / (owner->m_transformedSize.y)) * owner->m_scale; // scale box with panel height - double workspaceBoxW = owner->m_transformedSize.x * monitorSizeScaleFactor; - double workspaceBoxH = owner->m_transformedSize.y * monitorSizeScaleFactor; - double workspaceGroupWidth = workspaceBoxW * wsCount + (Config::workspaceMargin * owner->m_scale) * (wsCount - 1); - double curWorkspaceRectOffsetX = Config::centerAligned ? workspaceScrollOffset->value() + (widgetBox.w / 2.) - (workspaceGroupWidth / 2.) : workspaceScrollOffset->value() + Config::workspaceMargin; - double curWorkspaceRectOffsetY = !Config::onBottom ? (((Config::reservedArea + Config::workspaceMargin) * owner->m_scale) - curYOffset->value()) : (owner->m_transformedSize.y - ((Config::reservedArea + Config::workspaceMargin) * owner->m_scale) - workspaceBoxH + curYOffset->value()); - double workspaceOverflowSize = std::max(((workspaceGroupWidth - widgetBox.w) / 2) + (Config::workspaceMargin * owner->m_scale), 0); + for (const auto wsID : workspaces) { + const auto ws = g_pCompositor->getWorkspaceByID(wsID); + CBox box = {workspaceOffsetX, workspaceOffsetY, workspaceBoxW, workspaceBoxH}; - *workspaceScrollOffset = std::clamp(workspaceScrollOffset->goal(), -workspaceOverflowSize, workspaceOverflowSize); + if (ws == owner->m_activeWorkspace) { + if (Config::workspaceBorderSize >= 1 && Config::workspaceActiveBorder.a > 0) + renderBorder(box, Config::CGradientValueData(Config::workspaceActiveBorder), Config::workspaceBorderSize); - if (!(workspaceBoxW > 0 && workspaceBoxH > 0)) return; - for (auto wsID : workspaces) { - const auto ws = g_pCompositor->getWorkspaceByID(wsID); - CBox curWorkspaceBox = {curWorkspaceRectOffsetX, curWorkspaceRectOffsetY, workspaceBoxW, workspaceBoxH}; + if (!Config::disableBlur) + renderRectWithBlur(box, Config::workspaceActiveBackground); + else + renderRect(box, Config::workspaceActiveBackground); - // workspace background rect (NOT background layer) and border - if (ws == owner->m_activeWorkspace) { - if (Config::workspaceBorderSize >= 1 && Config::workspaceActiveBorder.a > 0) { - renderBorder(curWorkspaceBox, Config::CGradientValueData(Config::workspaceActiveBorder), Config::workspaceBorderSize); - } - if (!Config::disableBlur) { - renderRectWithBlur(curWorkspaceBox, Config::workspaceActiveBackground); // cant really round it until I find a proper way to clip windows to a rounded rect - } - else { - renderRect(curWorkspaceBox, Config::workspaceActiveBackground); - } if (!Config::drawActiveWorkspace) { - curWorkspaceRectOffsetX += workspaceBoxW + (Config::workspaceMargin * owner->m_scale); + workspaceOffsetX += workspaceBoxW + (Config::workspaceMargin * owner->m_scale); continue; } - } - else { - if (Config::workspaceBorderSize >= 1 && Config::workspaceInactiveBorder.a > 0) { - renderBorder(curWorkspaceBox, Config::CGradientValueData(Config::workspaceInactiveBorder), Config::workspaceBorderSize); - } - if (!Config::disableBlur) { - renderRectWithBlur(curWorkspaceBox, Config::workspaceInactiveBackground); - } - else { - renderRect(curWorkspaceBox, Config::workspaceInactiveBackground); - } + } else { + if (Config::workspaceBorderSize >= 1 && Config::workspaceInactiveBorder.a > 0) + renderBorder(box, Config::CGradientValueData(Config::workspaceInactiveBorder), Config::workspaceBorderSize); + + if (!Config::disableBlur) + renderRectWithBlur(box, Config::workspaceInactiveBackground); + else + renderRect(box, Config::workspaceInactiveBackground); } - // background and bottom layers if (!Config::hideBackgroundLayers) { - for (auto& ls : owner->m_layerSurfaceLayers[0]) { - CBox layerBox = {curWorkspaceBox.pos() + (ls->m_realPosition->value() - owner->m_position) * monitorSizeScaleFactor, ls->m_realSize->value() * monitorSizeScaleFactor}; - renderLayerStub(ls.lock(), owner, layerBox, curWorkspaceBox, time); + for (const auto& layerRef : owner->m_layerSurfaceLayers[0]) { + const auto layer = layerRef.lock(); + if (!layer) + continue; + + CBox layerBox = {box.pos() + (layer->m_realPosition->value() - owner->m_position) * monitorScaleFactor, layer->m_realSize->value() * monitorScaleFactor}; + renderLayerStub(layer, owner, layerBox, box, time); } - for (auto& ls : owner->m_layerSurfaceLayers[1]) { - CBox layerBox = {curWorkspaceBox.pos() + (ls->m_realPosition->value() - owner->m_position) * monitorSizeScaleFactor, ls->m_realSize->value() * monitorSizeScaleFactor}; - renderLayerStub(ls.lock(), owner, layerBox, curWorkspaceBox, time); + + for (const auto& layerRef : owner->m_layerSurfaceLayers[1]) { + const auto layer = layerRef.lock(); + if (!layer) + continue; + + CBox layerBox = {box.pos() + (layer->m_realPosition->value() - owner->m_position) * monitorScaleFactor, layer->m_realSize->value() * monitorScaleFactor}; + renderLayerStub(layer, owner, layerBox, box, time); } } - // the mini panel to cover the awkward empty space reserved by the panel if (owner->m_activeWorkspace == ws && Config::affectStrut) { - CBox miniPanelBox = {curWorkspaceRectOffsetX, curWorkspaceRectOffsetY, widgetBox.w * monitorSizeScaleFactor, widgetBox.h * monitorSizeScaleFactor}; - if (Config::onBottom) miniPanelBox = {curWorkspaceRectOffsetX, curWorkspaceRectOffsetY + workspaceBoxH - widgetBox.h * monitorSizeScaleFactor, widgetBox.w * monitorSizeScaleFactor, widgetBox.h * monitorSizeScaleFactor}; + CBox miniPanelBox = {workspaceOffsetX, workspaceOffsetY, panelBox.w * monitorScaleFactor, panelBox.h * monitorScaleFactor}; + if (Config::onBottom) + miniPanelBox = {workspaceOffsetX, workspaceOffsetY + workspaceBoxH - panelBox.h * monitorScaleFactor, panelBox.w * monitorScaleFactor, panelBox.h * monitorScaleFactor}; - if (!Config::disableBlur) { + if (!Config::disableBlur) renderRectWithBlur(miniPanelBox, CHyprColor(0, 0, 0, 0)); - } - else { - // what + else renderRect(miniPanelBox, CHyprColor(0, 0, 0, 0)); - } - } - if (ws != nullptr) { - // draw tiled windows - for (auto& w : g_pCompositor->m_windows) { - if (!w) continue; - if (w->m_workspace == ws && !w->m_isFloating) { - double wX = curWorkspaceRectOffsetX + ((w->m_realPosition->value().x - owner->m_position.x) * monitorSizeScaleFactor * owner->m_scale); - double wY = curWorkspaceRectOffsetY + ((w->m_realPosition->value().y - owner->m_position.y) * monitorSizeScaleFactor * owner->m_scale); - double wW = w->m_realSize->value().x * monitorSizeScaleFactor * owner->m_scale; - double wH = w->m_realSize->value().y * monitorSizeScaleFactor * owner->m_scale; - if (!(wW > 0 && wH > 0)) continue; - CBox curWindowBox = {wX, wY, wW, wH}; - //g_pHyprOpenGL->renderRectWithBlur(&curWindowBox, CHyprColor(0, 0, 0, 0)); - renderWindowStub(w, owner, owner->m_activeWorkspace, curWindowBox, curWorkspaceBox, time); - } - } - // draw floating windows - for (auto& w : g_pCompositor->m_windows) { - if (!w) continue; - if (w->m_workspace == ws && w->m_isFloating && ws->getLastFocusedWindow() != w) { - double wX = curWorkspaceRectOffsetX + ((w->m_realPosition->value().x - owner->m_position.x) * monitorSizeScaleFactor * owner->m_scale); - double wY = curWorkspaceRectOffsetY + ((w->m_realPosition->value().y - owner->m_position.y) * monitorSizeScaleFactor * owner->m_scale); - double wW = w->m_realSize->value().x * monitorSizeScaleFactor * owner->m_scale; - double wH = w->m_realSize->value().y * monitorSizeScaleFactor * owner->m_scale; - if (!(wW > 0 && wH > 0)) continue; - CBox curWindowBox = {wX, wY, wW, wH}; - //g_pHyprOpenGL->renderRectWithBlur(&curWindowBox, CHyprColor(0, 0, 0, 0)); - renderWindowStub(w, owner, owner->m_activeWorkspace, curWindowBox, curWorkspaceBox, time); + if (ws) { + const auto windowsIt = windowsByWorkspace.find(ws->m_id); + if (windowsIt != windowsByWorkspace.end()) { + for (const auto& window : windowsIt->second.tiled) + renderWindowPreview(window, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, box, time); + + const auto focused = ws->getLastFocusedWindow(); + for (const auto& window : windowsIt->second.floating) { + if (window == focused) + continue; + + renderWindowPreview(window, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, box, time); } + + if (focused && focused->m_isFloating) + renderWindowPreview(focused, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, box, time); } - // draw last focused floating window on top - if (ws->getLastFocusedWindow()) - if (ws->getLastFocusedWindow()->m_isFloating) { - const auto w = ws->getLastFocusedWindow(); - double wX = curWorkspaceRectOffsetX + ((w->m_realPosition->value().x - owner->m_position.x) * monitorSizeScaleFactor * owner->m_scale); - double wY = curWorkspaceRectOffsetY + ((w->m_realPosition->value().y - owner->m_position.y) * monitorSizeScaleFactor * owner->m_scale); - double wW = w->m_realSize->value().x * monitorSizeScaleFactor * owner->m_scale; - double wH = w->m_realSize->value().y * monitorSizeScaleFactor * owner->m_scale; - if (!(wW > 0 && wH > 0)) continue; - CBox curWindowBox = {wX, wY, wW, wH}; - //g_pHyprOpenGL->renderRectWithBlur(&curWindowBox, CHyprColor(0, 0, 0, 0)); - renderWindowStub(w, owner, owner->m_activeWorkspace, curWindowBox, curWorkspaceBox, time); - } } if (owner->m_activeWorkspace != ws || !Config::hideRealLayers) { - // this layer is hidden for real workspace when panel is displayed - if (!Config::hideTopLayers) - for (auto& ls : owner->m_layerSurfaceLayers[2]) { - CBox layerBox = {curWorkspaceBox.pos() + (ls->m_realPosition->value() - owner->m_position) * monitorSizeScaleFactor, ls->m_realSize->value() * monitorSizeScaleFactor}; - renderLayerStub(ls.lock(), owner, layerBox, curWorkspaceBox, time); + if (!Config::hideTopLayers) { + for (const auto& layerRef : owner->m_layerSurfaceLayers[2]) { + const auto layer = layerRef.lock(); + if (!layer) + continue; + + CBox layerBox = {box.pos() + (layer->m_realPosition->value() - owner->m_position) * monitorScaleFactor, layer->m_realSize->value() * monitorScaleFactor}; + renderLayerStub(layer, owner, layerBox, box, time); } + } + + if (!Config::hideOverlayLayers) { + for (const auto& layerRef : owner->m_layerSurfaceLayers[3]) { + const auto layer = layerRef.lock(); + if (!layer) + continue; - if (!Config::hideOverlayLayers) - for (auto& ls : owner->m_layerSurfaceLayers[3]) { - CBox layerBox = {curWorkspaceBox.pos() + (ls->m_realPosition->value() - owner->m_position) * monitorSizeScaleFactor, ls->m_realSize->value() * monitorSizeScaleFactor}; - renderLayerStub(ls.lock(), owner, layerBox, curWorkspaceBox, time); + CBox layerBox = {box.pos() + (layer->m_realPosition->value() - owner->m_position) * monitorScaleFactor, layer->m_realSize->value() * monitorScaleFactor}; + renderLayerStub(layer, owner, layerBox, box, time); } + } } + // Input hitboxes are evaluated in monitor-space logical coordinates. + box.scale(1 / owner->m_scale); + box.x += owner->m_position.x; + box.y += owner->m_position.y; + workspaceBoxes.emplace_back(wsID, box); - // Resets workspaceBox to scaled absolute coordinates for input detection. - // While rendering is done in pixel coordinates, input detection is done in - // scaled coordinates, taking into account monitor scaling. - // Since the monitor position is already given in scaled coordinates, - // we only have to scale all relative coordinates, then add them to the - // monitor position to get a scaled absolute position. - curWorkspaceBox.scale(1 / owner->m_scale); - - curWorkspaceBox.x += owner->m_position.x; - curWorkspaceBox.y += owner->m_position.y; - workspaceBoxes.emplace_back(std::make_tuple(wsID, curWorkspaceBox)); - - // set the current position to the next workspace box - curWorkspaceRectOffsetX += workspaceBoxW + Config::workspaceMargin * owner->m_scale; + workspaceOffsetX += workspaceBoxW + Config::workspaceMargin * owner->m_scale; } g_pHyprRenderer->m_renderData.clipBox = monitorClip; diff --git a/src/main.cpp b/src/main.cpp index 664ef29..2ea4b26 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -26,7 +25,6 @@ extern "C" { #include "Overview.hpp" void* pRenderWindow; -void* pRenderLayer; std::vector> g_overviewWidgets; @@ -51,9 +49,6 @@ bool Config::hideOverlayLayers = false; bool Config::drawActiveWorkspace = true; bool Config::hideRealLayers = false; bool Config::affectStrut = false; -bool Config::overrideGaps = true; -int Config::gapsIn = 20; -int Config::gapsOut = 60; bool Config::autoDrag = true; bool Config::autoScroll = true; bool Config::exitOnClick = true; @@ -106,16 +101,13 @@ using Config::Values::CColorValue; using Config::Values::CFloatValue; using Config::Values::CIntValue; using Config::Values::CStringValue; -using Config::Values::IValue; - -constexpr const char* PLUGIN_PREFIX = "plugin:hyprspace:"; struct SPluginConfigValues { SP panelColor, panelBorderColor, workspaceActiveBackground, workspaceInactiveBackground, workspaceActiveBorder, workspaceInactiveBorder; SP panelHeight, panelBorderWidth, workspaceMargin, reservedArea, workspaceBorderSize; SP adaptiveHeight, centerAligned, onBottom, hideBackgroundLayers, hideTopLayers, hideOverlayLayers, drawActiveWorkspace, hideRealLayers, affectStrut; - SP overrideGaps, autoDrag, autoScroll, exitOnClick, switchOnDrop, exitOnSwitch, showNewWorkspace, showEmptyWorkspace, showSpecialWorkspace; - SP gapsIn, gapsOut, swipeFingers, swipeDistance, swipeForceSpeed, clickReleaseThresholdMs; + SP autoDrag, autoScroll, exitOnClick, switchOnDrop, exitOnSwitch, showNewWorkspace, showEmptyWorkspace, showSpecialWorkspace; + SP swipeFingers, swipeDistance, swipeForceSpeed, clickReleaseThresholdMs; SP disableGestures, reverseSwipe, disableBlur; SP swipeCancelRatio, swipeThreshold, swipeClosedPadding, workspaceScrollSpeed, overrideAnimSpeed, dragAlpha; SP exitKey; @@ -204,10 +196,6 @@ void registerConfigValues() { g_pluginConfigValues.hideRealLayers = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:hide_real_layers", "Temporarily hide real top and overlay layers while overview is active", Config::hideRealLayers))); g_pluginConfigValues.affectStrut = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:affect_strut", "Reserve monitor space while the overview is visible", Config::affectStrut))); - g_pluginConfigValues.overrideGaps = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:override_gaps", "Override workspace gaps while the overview is visible", Config::overrideGaps))); - g_pluginConfigValues.gapsIn = registerPluginValue(SP(new CIntValue("plugin:hyprspace:gaps_in", "Inner gaps to use while overview is active", Config::gapsIn))); - g_pluginConfigValues.gapsOut = registerPluginValue(SP(new CIntValue("plugin:hyprspace:gaps_out", "Outer gaps to use while overview is active", Config::gapsOut))); - g_pluginConfigValues.autoDrag = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:auto_drag", "Start dragging a hovered window on press", Config::autoDrag))); g_pluginConfigValues.autoScroll = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:auto_scroll", "Allow wheel scrolling outside the panel to switch workspaces", Config::autoScroll))); g_pluginConfigValues.exitOnClick = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:exit_on_click", "Close the overview when clicking outside a workspace", Config::exitOnClick))); @@ -299,6 +287,11 @@ void onRender(eRenderStage renderStage) { return; } + if (!pRenderWindow) { + g_oAlpha = -1; + return; + } + curWindow->alpha(Desktop::View::WINDOW_ALPHA_ACTIVE)->setValueAndWarp(Config::dragAlpha); curWindow->m_ruleApplicator->noBlur().unset(Desktop::Types::PRIORITY_SET_PROP); const auto time = Time::steadyNow(); @@ -548,10 +541,6 @@ void reloadConfig() { Config::hideRealLayers = readBoolValue(g_pluginConfigValues.hideRealLayers, Config::hideRealLayers); Config::affectStrut = readBoolValue(g_pluginConfigValues.affectStrut, Config::affectStrut); - Config::overrideGaps = readBoolValue(g_pluginConfigValues.overrideGaps, Config::overrideGaps); - Config::gapsIn = readIntValue(g_pluginConfigValues.gapsIn, Config::gapsIn); - Config::gapsOut = readIntValue(g_pluginConfigValues.gapsOut, Config::gapsOut); - Config::autoDrag = readBoolValue(g_pluginConfigValues.autoDrag, Config::autoDrag); Config::autoScroll = readBoolValue(g_pluginConfigValues.autoScroll, Config::autoScroll); Config::exitOnClick = readBoolValue(g_pluginConfigValues.exitOnClick, Config::exitOnClick); @@ -636,10 +625,6 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE inHandle) { if (!pRenderWindow) pRenderWindow = findFunctionBySymbol(pHandle, "renderWindow", "CHyprRenderer::renderWindow"); - pRenderLayer = findFunctionBySymbol(pHandle, "renderLayer", "IHyprRenderer::renderLayer"); - if (!pRenderLayer) - pRenderLayer = findFunctionBySymbol(pHandle, "renderLayer", "CHyprRenderer::renderLayer"); - registerMonitors(); g_pAddMonitorHook = Event::bus()->m_events.monitor.added.listen([](PHLMONITOR) { registerMonitors(); }); g_pRemoveMonitorHook = Event::bus()->m_events.monitor.removed.listen([](PHLMONITOR monitor) { removeMonitorWidget(monitor); }); @@ -673,6 +658,5 @@ APICALL EXPORT void PLUGIN_EXIT() { g_overviewWidgets.clear(); pRenderWindow = nullptr; - pRenderLayer = nullptr; pHandle = nullptr; } From 9baeb55ad763f3239aa436d296b854838e1d60cd Mon Sep 17 00:00:00 2001 From: 0xl30 Date: Thu, 21 May 2026 23:26:39 +0530 Subject: [PATCH 4/9] Fixed Broken Frame Issue --- src/Globals.hpp | 2 + src/Overview.cpp | 14 +++-- src/Render.cpp | 133 +++++++++++++++++------------------------------ src/main.cpp | 6 +++ 4 files changed, 67 insertions(+), 88 deletions(-) diff --git a/src/Globals.hpp b/src/Globals.hpp index 423c834..78e9f3d 100644 --- a/src/Globals.hpp +++ b/src/Globals.hpp @@ -41,7 +41,9 @@ CHyprSignalListener listenCancellable(Signal& signal, std::functionm_damage.damageEntire(); + g_pCompositor->scheduleFrameForMonitor(owner); +} + } // namespace CHyprspaceWidget::CHyprspaceWidget(uint64_t inOwnerID) : ownerID(inOwnerID) { @@ -91,7 +99,7 @@ void CHyprspaceWidget::cleanup(PHLMONITOR owner) { owner->m_reservedArea = Desktop::CReservedArea(); g_pHyprRenderer->arrangeLayersForMonitor(ownerID); g_layoutManager->recalculateMonitor(owner); - g_pCompositor->scheduleFrameForMonitor(owner); + requestFullMonitorRedraw(owner); } } @@ -144,7 +152,7 @@ void CHyprspaceWidget::show() { updateLayout(); g_pHyprRenderer->damageMonitor(owner); - g_pCompositor->scheduleFrameForMonitor(owner); + requestFullMonitorRedraw(owner); } void CHyprspaceWidget::hide() { @@ -163,7 +171,7 @@ void CHyprspaceWidget::hide() { } updateLayout(); - g_pCompositor->scheduleFrameForMonitor(owner); + requestFullMonitorRedraw(owner); } void CHyprspaceWidget::updateConfig() { diff --git a/src/Render.cpp b/src/Render.cpp index e730cef..985f677 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include namespace { @@ -46,8 +45,8 @@ void renderBorder(CBox box, const Config::CGradientValueData& gradient, int size g_pHyprRenderer->m_renderPass.add(makeUnique(borderData)); } -void renderWindowStub(PHLWINDOW window, PHLMONITOR monitor, CBox rectOverride, CBox clipBox, const Time::steady_tp& time) { - if (!window || !monitor) +void renderWindowStub(PHLWINDOW window, PHLMONITOR monitor, PHLWORKSPACE workspaceOverride, CBox rectOverride, const Time::steady_tp& time) { + if (!window || !monitor || !workspaceOverride) return; if (!window->m_isMapped || !window->wlSurface() || !window->wlSurface()->resource()) @@ -55,10 +54,14 @@ void renderWindowStub(PHLWINDOW window, PHLMONITOR monitor, CBox rectOverride, C Render::SRenderModifData renderModif; - const auto realPosition = window->m_realPosition->value(); - const auto realSize = window->m_realSize->value(); - const float logicalW = std::max(static_cast(realSize.x), 5.F); - const float scaleMod = rectOverride.w / std::max(logicalW * monitor->m_scale, 5.F); + const auto workspace = window->m_workspace; + const auto fullscreenState = window->m_fullscreenState; + const auto realPosition = window->m_realPosition->value(); + const auto realSize = window->m_realSize->value(); + const auto pinned = window->m_pinned; + const auto floating = window->m_isFloating; + const float logicalW = std::max(static_cast(realSize.x), 5.F); + const float scaleMod = rectOverride.w / std::max(logicalW * monitor->m_scale, 5.F); if (!(scaleMod > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) return; @@ -70,51 +73,31 @@ void renderWindowStub(PHLWINDOW window, PHLMONITOR monitor, CBox rectOverride, C renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, std::any(scaleMod))); renderModif.enabled = true; + window->m_workspace = workspaceOverride; + window->m_fullscreenState = Desktop::View::SFullscreenState{FSMODE_NONE}; + window->m_ruleApplicator->nearestNeighbor().set(false, Desktop::Types::PRIORITY_SET_PROP); + window->m_isFloating = false; + window->m_pinned = true; + window->m_ruleApplicator->rounding().set(window->rounding() * scaleMod * monitor->m_scale, Desktop::Types::PRIORITY_SET_PROP); + g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = renderModif})); Hyprutils::Utils::CScopeGuard clearHints([] { g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = Render::SRenderModifData{}})); }); g_pHyprRenderer->damageWindow(window); - - CSurfacePassElement::SRenderData renderData = {monitor, time}; - renderData.pos = realPosition + window->m_floatingOffset; - renderData.w = std::max(realSize.x, 5.0); - renderData.h = std::max(realSize.y, 5.0); - renderData.surface = window->wlSurface()->resource(); - renderData.dontRound = window->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); - renderData.fadeAlpha = 1.F; - renderData.alpha = 1.F; - renderData.decorate = false; - renderData.rounding = renderData.dontRound ? 0 : window->rounding() * scaleMod * monitor->m_scale; - renderData.roundingPower = renderData.dontRound ? 2.0F : window->roundingPower(); - renderData.blur = false; - renderData.pWindow = window; - renderData.clipBox = clipBox; - renderData.useNearestNeighbor = false; - renderData.squishOversized = true; - renderData.surfaceCounter = 0; - - window->wlSurface()->resource()->breadthfirst( - [&renderData, &window](SP surface, const Vector2D& offset, void* data) { - (void)data; - if (!surface || !surface->m_current.texture) - return; - - if (surface->m_current.size.x < 1 || surface->m_current.size.y < 1) - return; - - renderData.localPos = offset; - renderData.texture = surface->m_current.texture; - renderData.surface = surface; - renderData.mainSurface = surface == window->wlSurface()->resource(); - g_pHyprRenderer->m_renderPass.add(makeUnique(renderData)); - renderData.surfaceCounter++; - }, - nullptr); + if (pRenderWindow) + (*(tRenderWindow)pRenderWindow)(g_pHyprRenderer.get(), window, monitor, time, true, Render::RENDER_PASS_ALL, false, false); + + window->m_workspace = workspace; + window->m_fullscreenState = fullscreenState; + window->m_ruleApplicator->nearestNeighbor().unset(Desktop::Types::PRIORITY_SET_PROP); + window->m_isFloating = floating; + window->m_pinned = pinned; + window->m_ruleApplicator->rounding().unset(Desktop::Types::PRIORITY_SET_PROP); } -void renderLayerStub(PHLLS layer, PHLMONITOR monitor, CBox rectOverride, CBox clipBox, const Time::steady_tp& time) { +void renderLayerStub(PHLLS layer, PHLMONITOR monitor, CBox rectOverride, const Time::steady_tp& time) { if (!layer || !monitor) return; @@ -123,6 +106,8 @@ void renderLayerStub(PHLLS layer, PHLMONITOR monitor, CBox rectOverride, CBox cl const Vector2D realPosition = layer->m_realPosition->value(); const Vector2D realSize = layer->m_realSize->value(); + const float alpha = layer->m_alpha->value(); + const bool fadingOut = layer->m_fadingOut; const float scale = rectOverride.w / realSize.x; if (!(scale > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) @@ -133,44 +118,22 @@ void renderLayerStub(PHLLS layer, PHLMONITOR monitor, CBox rectOverride, CBox cl renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, std::any(scale))); renderModif.enabled = true; + layer->m_alpha->setValueAndWarp(1.F); + layer->m_fadingOut = false; + g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = renderModif})); Hyprutils::Utils::CScopeGuard clearHints([] { g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = Render::SRenderModifData{}})); }); + if (pRenderLayer) + (*(tRenderLayer)pRenderLayer)(g_pHyprRenderer.get(), layer, monitor, time, false, false); - CSurfacePassElement::SRenderData renderData = {monitor, time, realPosition}; - renderData.fadeAlpha = 1.F; - renderData.alpha = 1.F; - renderData.blur = false; - renderData.surface = layer->wlSurface()->resource(); - renderData.decorate = false; - renderData.w = realSize.x; - renderData.h = realSize.y; - renderData.pLS = layer; - renderData.clipBox = clipBox; - renderData.surfaceCounter = 0; - - layer->wlSurface()->resource()->breadthfirst( - [&renderData, &layer](SP surface, const Vector2D& offset, void* data) { - (void)data; - if (!surface || !surface->m_current.texture) - return; - - if (surface->m_current.size.x < 1 || surface->m_current.size.y < 1) - return; - - renderData.localPos = offset; - renderData.texture = surface->m_current.texture; - renderData.surface = surface; - renderData.mainSurface = surface == layer->wlSurface()->resource(); - g_pHyprRenderer->m_renderPass.add(makeUnique(renderData)); - renderData.surfaceCounter++; - }, - &renderData); + layer->m_fadingOut = fadingOut; + layer->m_alpha->setValueAndWarp(alpha); } -bool renderWindowPreview(PHLWINDOW window, PHLMONITOR owner, double workspaceX, double workspaceY, double monitorScaleFactor, CBox workspaceClip, const Time::steady_tp& time) { - if (!window || !owner) +bool renderWindowPreview(PHLWINDOW window, PHLWORKSPACE workspace, PHLMONITOR owner, double workspaceX, double workspaceY, double monitorScaleFactor, const Time::steady_tp& time) { + if (!window || !workspace || !owner) return false; const double wX = workspaceX + ((window->m_realPosition->value().x - owner->m_position.x) * monitorScaleFactor * owner->m_scale); @@ -180,7 +143,7 @@ bool renderWindowPreview(PHLWINDOW window, PHLMONITOR owner, double workspaceX, if (!(wW > 0 && wH > 0)) return false; - renderWindowStub(window, owner, {wX, wY, wW, wH}, workspaceClip, time); + renderWindowStub(window, owner, workspace, {wX, wY, wW, wH}, time); return true; } @@ -337,7 +300,7 @@ void CHyprspaceWidget::draw() { continue; CBox layerBox = {box.pos() + (layer->m_realPosition->value() - owner->m_position) * monitorScaleFactor, layer->m_realSize->value() * monitorScaleFactor}; - renderLayerStub(layer, owner, layerBox, box, time); + renderLayerStub(layer, owner, layerBox, time); } for (const auto& layerRef : owner->m_layerSurfaceLayers[1]) { @@ -346,7 +309,7 @@ void CHyprspaceWidget::draw() { continue; CBox layerBox = {box.pos() + (layer->m_realPosition->value() - owner->m_position) * monitorScaleFactor, layer->m_realSize->value() * monitorScaleFactor}; - renderLayerStub(layer, owner, layerBox, box, time); + renderLayerStub(layer, owner, layerBox, time); } } @@ -363,20 +326,20 @@ void CHyprspaceWidget::draw() { if (ws) { const auto windowsIt = windowsByWorkspace.find(ws->m_id); - if (windowsIt != windowsByWorkspace.end()) { - for (const auto& window : windowsIt->second.tiled) - renderWindowPreview(window, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, box, time); + if (windowsIt != windowsByWorkspace.end()) { + for (const auto& window : windowsIt->second.tiled) + renderWindowPreview(window, ws, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, time); const auto focused = ws->getLastFocusedWindow(); for (const auto& window : windowsIt->second.floating) { if (window == focused) continue; - renderWindowPreview(window, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, box, time); + renderWindowPreview(window, ws, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, time); } if (focused && focused->m_isFloating) - renderWindowPreview(focused, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, box, time); + renderWindowPreview(focused, ws, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, time); } } @@ -388,7 +351,7 @@ void CHyprspaceWidget::draw() { continue; CBox layerBox = {box.pos() + (layer->m_realPosition->value() - owner->m_position) * monitorScaleFactor, layer->m_realSize->value() * monitorScaleFactor}; - renderLayerStub(layer, owner, layerBox, box, time); + renderLayerStub(layer, owner, layerBox, time); } } @@ -399,7 +362,7 @@ void CHyprspaceWidget::draw() { continue; CBox layerBox = {box.pos() + (layer->m_realPosition->value() - owner->m_position) * monitorScaleFactor, layer->m_realSize->value() * monitorScaleFactor}; - renderLayerStub(layer, owner, layerBox, box, time); + renderLayerStub(layer, owner, layerBox, time); } } } diff --git a/src/main.cpp b/src/main.cpp index 2ea4b26..2805db6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,6 +25,7 @@ extern "C" { #include "Overview.hpp" void* pRenderWindow; +void* pRenderLayer; std::vector> g_overviewWidgets; @@ -625,6 +626,10 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE inHandle) { if (!pRenderWindow) pRenderWindow = findFunctionBySymbol(pHandle, "renderWindow", "CHyprRenderer::renderWindow"); + pRenderLayer = findFunctionBySymbol(pHandle, "renderLayer", "IHyprRenderer::renderLayer"); + if (!pRenderLayer) + pRenderLayer = findFunctionBySymbol(pHandle, "renderLayer", "CHyprRenderer::renderLayer"); + registerMonitors(); g_pAddMonitorHook = Event::bus()->m_events.monitor.added.listen([](PHLMONITOR) { registerMonitors(); }); g_pRemoveMonitorHook = Event::bus()->m_events.monitor.removed.listen([](PHLMONITOR monitor) { removeMonitorWidget(monitor); }); @@ -658,5 +663,6 @@ APICALL EXPORT void PLUGIN_EXIT() { g_overviewWidgets.clear(); pRenderWindow = nullptr; + pRenderLayer = nullptr; pHandle = nullptr; } From 12a0a2cc18ce4cf582a54ed817373d3ff660416b Mon Sep 17 00:00:00 2001 From: 0xl30 Date: Fri, 22 May 2026 09:49:00 +0530 Subject: [PATCH 5/9] Added Lua-Based Config And Readme --- Hyprspace.lua | 245 +++++++++++++++++++++++++++++++++++++------------- README.md | 113 ++++++++++++----------- 2 files changed, 241 insertions(+), 117 deletions(-) diff --git a/Hyprspace.lua b/Hyprspace.lua index e8cb4b5..ee8e421 100644 --- a/Hyprspace.lua +++ b/Hyprspace.lua @@ -2,7 +2,15 @@ local M = {} local HOME = os.getenv("HOME") or "" local IMPORT_COLOR = HOME .. "/.config/matugen/generated/hyprland-colors.lua" -local plugin_path = os.getenv("HYPRSPACE_PLUGIN_PATH") +local ENV_PLUGIN_PATH = os.getenv("HYPRSPACE_PLUGIN_PATH") +local MODULE_DIR = (debug.getinfo(1, "S").source or ""):match("^@(.+)/[^/]+$") +local LOCAL_PLUGIN_PATH = MODULE_DIR and (MODULE_DIR .. "/Hyprspace.so") or nil + +local state = { + mode = "auto", + plugin_path = nil, + hooks_registered = false, +} local function file_exists(path) if type(path) ~= "string" or path == "" then @@ -29,8 +37,7 @@ local function load_colors(path) return {} end - local ok = pcall(chunk) - if not ok then + if not pcall(chunk) then return {} end @@ -56,13 +63,9 @@ local function rgba_to_aarrggbb(value, fallback) end local function with_alpha(value, alpha, fallback) - if type(fallback) ~= "number" then - return nil - end - local base = rgba_to_aarrggbb(value, fallback) if type(base) ~= "number" then - base = fallback + return fallback end return ((alpha & 0xff) << 24) | (base & 0x00ffffff) @@ -72,16 +75,50 @@ local function plugin_loaded() return hl.plugin and hl.plugin.Hyprspace and type(hl.plugin.Hyprspace.overview) == "function" end +local function plugin_paths() + local paths = {} + local seen = {} + + local function add(path) + if type(path) ~= "string" or path == "" or seen[path] then + return + end + + seen[path] = true + table.insert(paths, path) + end + + if state.mode == "manual" then + add(state.plugin_path) + end + + add(ENV_PLUGIN_PATH) + add(LOCAL_PLUGIN_PATH) + + return paths +end + +local function resolve_plugin_path() + for _, path in ipairs(plugin_paths()) do + if file_exists(path) then + return path + end + end + + return nil +end + local function ensure_plugin_loaded() if plugin_loaded() then return true end - if not file_exists(plugin_path) then + local path = resolve_plugin_path() + if not path then return false end - hl.exec_cmd("hyprctl plugin load " .. shell_quote(plugin_path)) + hl.exec_cmd("hyprctl plugin load " .. shell_quote(path)) return plugin_loaded() end @@ -91,53 +128,53 @@ local function build_config() return { plugin = { hyprspace = { - panel_color = with_alpha(colors.surface_container_high or colors.surface, 0xd8, 0xd8261d20), - panel_border_color = with_alpha(colors.primary or colors.outline, 0x88, 0x88ffb0cf), - workspace_active_background = with_alpha(colors.surface_container_highest or colors.surface_container_high, 0xb8, 0xb83c3235), + panel_color = with_alpha(colors.surface_container_high or colors.surface, 0xd8, 0xd8261d20), + panel_border_color = with_alpha(colors.primary or colors.outline, 0x88, 0x88ffb0cf), + workspace_active_background = with_alpha(colors.surface_container_highest or colors.surface_container_high, 0xb8, 0xb83c3235), workspace_inactive_background = with_alpha(colors.surface_container or colors.surface, 0xde, 0xde261d20), - workspace_active_border = with_alpha(colors.primary or colors.on_surface, 0xb0, 0xb0ffb0cf), - workspace_inactive_border = with_alpha(colors.outline_variant or colors.outline, 0x55, 0x55504348), - - panel_height = 220, - panel_border_width = 2, - workspace_margin = 10, - reserved_area = 35, - workspace_border_size = 1, - - adaptive_height = false, - center_aligned = true, - on_bottom = false, - hide_background_layers = false, - hide_top_layers = false, - hide_overlay_layers = false, - draw_active_workspace = true, - hide_real_layers = false, - affect_strut = false, - - auto_drag = true, - auto_scroll = true, - exit_on_click = true, - switch_on_drop = false, - exit_on_switch = false, - show_new_workspace = true, - show_empty_workspace = true, - show_special_workspace = false, - - disable_gestures = false, - reverse_swipe = false, - swipe_fingers = 3, - swipe_distance = 300, - swipe_force_speed = 30, - swipe_cancel_ratio = 0.5, - swipe_threshold = 10.0, - swipe_closed_padding = 10.0, - workspace_scroll_speed = 2.0, - - disable_blur = false, - override_anim_speed = 0.0, - drag_alpha = 0.2, - exit_key = "Escape", - click_release_threshold_ms = 200, + workspace_active_border = with_alpha(colors.primary or colors.on_surface, 0xb0, 0xb0ffb0cf), + workspace_inactive_border = with_alpha(colors.outline_variant or colors.outline, 0x55, 0x55504348), + + panel_height = 220, + panel_border_width = 2, + workspace_margin = 10, + reserved_area = 35, + workspace_border_size = 1, + + adaptive_height = false, + center_aligned = true, + on_bottom = false, + hide_background_layers = false, + hide_top_layers = false, + hide_overlay_layers = false, + draw_active_workspace = true, + hide_real_layers = false, + affect_strut = false, + + auto_drag = true, + auto_scroll = true, + exit_on_click = true, + switch_on_drop = false, + exit_on_switch = false, + show_new_workspace = true, + show_empty_workspace = true, + show_special_workspace = false, + + disable_gestures = false, + reverse_swipe = false, + swipe_fingers = 3, + swipe_distance = 300, + swipe_force_speed = 30, + swipe_cancel_ratio = 0.5, + swipe_threshold = 10.0, + swipe_closed_padding = 10.0, + workspace_scroll_speed = 2.0, + + disable_blur = false, + override_anim_speed = 0.0, + drag_alpha = 0.2, + exit_key = "Escape", + click_release_threshold_ms = 200, }, }, } @@ -148,23 +185,103 @@ local function apply_config() return false end - -- Hyprland 0.55+ native config path. hl.config(build_config()) return true end -function M.setup(opts) - if type(opts) == "table" and type(opts.plugin_path) == "string" then - plugin_path = opts.plugin_path +local function sync_plugin() + ensure_plugin_loaded() + return apply_config() +end + +local function dispatch_overview(action) + local command = action or "toggle" + local valid = { + toggle = true, + open = true, + close = true, + toggle_all = true, + open_all = true, + close_all = true, + } + + if not valid[command] then + return false + end + + if command:sub(-4) == "_all" then + hl.exec_cmd("hyprctl dispatch overview:" .. command:sub(1, -5) .. " all") + else + hl.exec_cmd("hyprctl dispatch overview:" .. command) + end + + return true +end + +local function schedule_sync(delay_ms, attempts) + hl.timer(function() + if sync_plugin() or attempts <= 1 then + return + end + + schedule_sync(delay_ms, attempts - 1) + end, { timeout = delay_ms, type = "oneshot" }) +end + +local function register_hooks() + if state.hooks_registered then + return end hl.on("hyprland.start", function() - ensure_plugin_loaded() - apply_config() + schedule_sync(150, 6) end) - ensure_plugin_loaded() - apply_config() + hl.on("config.reloaded", function() + schedule_sync(150, 4) + end) + + state.hooks_registered = true +end + +function M.apply_config() + return sync_plugin() +end + +function M.reload() + local applied = sync_plugin() + hl.exec_cmd("hyprctl reload") + return applied +end + +function M.overview(action) + local command = action or "toggle" + + sync_plugin() + + if plugin_loaded() then + hl.plugin.Hyprspace.overview(command) + return true + end + + return dispatch_overview(command) +end + +function M.toggle() + return M.overview("toggle") +end + +function M.setup(opts) + state.mode = "auto" + state.plugin_path = nil + + if type(opts) == "table" and type(opts.plugin_path) == "string" and opts.plugin_path ~= "" then + state.mode = "manual" + state.plugin_path = opts.plugin_path + end + + register_hooks() + schedule_sync(150, 3) end return M diff --git a/README.md b/README.md index c53693b..9341e53 100644 --- a/README.md +++ b/README.md @@ -19,94 +19,108 @@ This fork is adapted for current Hyprland with Lua-based configuration instead o - Hyprland development headers / pkg-config metadata for manual builds - A recent C++ compiler with C++23 support -## Installation +## Setup -### Manual install +### `hyprpm` auto setup -Build the plugin from the repo root: +Install and enable the plugin: ```bash -cd ~/.config/Hyprspace -make all +hyprpm add https://github.com/0xl30/Hyprspace.git +hyprpm enable Hyprspace +hyprpm reload ``` -That produces: +Then in [source/plugins.lua](/home/xlord/.config/hypr/edit_here/source/plugins.lua): -```text -~/.config/Hyprspace/Hyprspace.so +```lua +require("edit_here.Hyprspace.Hyprspace").setup("auto") ``` -Load it manually: +Reload Hyprland: ```bash -hyprctl plugin load ~/.config/Hyprspace/Hyprspace.so +hyprctl reload ``` -To unload it: +### Manual `hyprctl` setup + +Build the local plugin: ```bash -hyprctl plugin unload ~/.config/Hyprspace/Hyprspace.so +cd ~/.config/hypr/edit_here/Hyprspace +make all ``` -### `hyprpm` install +This creates: + +```text +~/.config/hypr/edit_here/Hyprspace/Hyprspace.so +``` -If this repo is pushed to Git and the updated [hyprpm.toml](./hyprpm.toml) is included, install it with: +Load it manually: ```bash -hyprpm add https://github.com/0xl30/Hyprspace.git -hyprpm enable Hyprspace +hyprctl plugin load ~/.config/hypr/edit_here/Hyprspace/Hyprspace.so +``` + +Then in [source/plugins.lua](/home/xlord/.config/hypr/edit_here/source/plugins.lua): + +```lua +local HOME = os.getenv("HOME") or "" + +require("edit_here.Hyprspace.Hyprspace").setup({ + plugin_path = HOME .. "/.config/hypr/edit_here/Hyprspace/Hyprspace.so", +}) ``` -Examples: +Reload after Lua changes: ```bash -hyprpm add https://github.com/0xl30/Hyprspace.git -hyprpm enable Hyprspace +hyprctl reload ``` -Notes: +Unload manually if needed: -- `hyprpm` matches your Hyprland commit against `commit_pins` in `hyprpm.toml`. -- For Hyprland `v0.55.2`, this repo already includes a matching pin. -- After updating plugin code, commit and push before expecting `hyprpm` to fetch the new version. +```bash +hyprctl plugin unload ~/.config/hypr/edit_here/Hyprspace/Hyprspace.so +``` ## Lua setup ### 1. Plugin helper -Add this to start-up [plugins.lua](../plugins.lua): +Use one of these in [source/plugins.lua](/home/xlord/.config/hypr/edit_here/source/plugins.lua): + +`hyprpm` / auto: ```lua -require("Hyprspace.Hyprspace").setup() +require("edit_here.Hyprspace.Hyprspace").setup("auto") ``` -This is the preferred setup for `hyprpm` installs and already-loaded plugins. - -If you want local manual loading from the Lua helper instead of loading the `.so` yourself, use: +Manual local `.so`: ```lua -require("Hyprspace.Hyprspace").setup({ - plugin_path = HOME .. "/.config/Hyprspace/Hyprspace.so", +local HOME = os.getenv("HOME") or "" + +require("edit_here.Hyprspace.Hyprspace").setup({ + plugin_path = HOME .. "/.config/hypr/edit_here/Hyprspace/Hyprspace.so", }) ``` -Use that only for a manual local build. Do not pass `plugin_path` when the plugin is managed by `hyprpm`. - ### 2. Keybind -Add a keybind in [keybinds.lua](../keybinds.lua): +Add a keybind in [source/keybinds.lua](/home/xlord/.config/hypr/edit_here/source/keybinds.lua): ```lua +local hyprspace = require("edit_here.Hyprspace.Hyprspace") + hl.unbind("SUPER + A") hl.bind("SUPER + A", function() - if hl.plugin and hl.plugin.Hyprspace and type(hl.plugin.Hyprspace.overview) == "function" then - hl.plugin.Hyprspace.overview("toggle") - end + hyprspace.toggle() end) ``` -This file does not need to `require(...)` the helper directly. - ### 3. Reload After changing the Lua config: @@ -118,10 +132,10 @@ hyprctl reload After rebuilding the plugin binary: ```bash -cd ~/.config/Hyprspace +cd ~/.config/hypr/edit_here/Hyprspace make all -hyprctl plugin unload ~/.config/Hyprspace/Hyprspace.so -hyprctl plugin load ~/.config/Hyprspace/Hyprspace.so +hyprctl plugin unload ~/.config/hypr/edit_here/Hyprspace/Hyprspace.so +hyprctl plugin load ~/.config/hypr/edit_here/Hyprspace/Hyprspace.so hyprctl reload ``` @@ -140,7 +154,7 @@ hyprctl reload The helper module exports: ```lua -local hyprspace = require("Hyprspace.Hyprspace") +local hyprspace = require("edit_here.Hyprspace.Hyprspace") hyprspace.setup() hyprspace.apply_config() @@ -162,7 +176,7 @@ hl.plugin.Hyprspace.overview("close") ## Configuration -The plugin is configured from [Hyprspace.lua](./Hyprspace.lua). The helper builds a Lua config table and applies it through `hl.config(...)`. +The plugin is configured from [Hyprspace.lua](./Hyprspace.lua). The helper reads Matugen colors from `IMPORT_COLOR`, builds a Lua config table, and applies it through `hl.config(...)`. ### Example @@ -181,10 +195,6 @@ plugin = { hide_real_layers = false, affect_strut = false, - override_gaps = true, - gaps_in = 20, - gaps_out = 60, - auto_drag = true, auto_scroll = true, exit_on_click = true, @@ -229,9 +239,6 @@ plugin = { - `draw_active_workspace` - `hide_real_layers` - `affect_strut` -- `override_gaps` -- `gaps_in` -- `gaps_out` #### Behavior @@ -281,17 +288,17 @@ The helper converts Matugen `rgba(rrggbbaa)` strings into the integer color form Check that the plugin is actually loaded: ```bash -hyprctl plugins +hyprctl plugin list ``` If you are using a manual build, load the `.so` first. ### Lua settings are not applied -Make sure [source/plugins.lua](../plugins.lua) calls: +Make sure [source/plugins.lua](/home/xlord/.config/hypr/edit_here/source/plugins.lua) calls: ```lua -require("Hyprspace.Hyprspace").setup() +require("edit_here.Hyprspace.Hyprspace").setup("auto") ``` Then run: From 628d88122ac5ded7334618e516fed4dd227edc6e Mon Sep 17 00:00:00 2001 From: 0xl30 Date: Mon, 25 May 2026 02:18:58 +0530 Subject: [PATCH 6/9] Fixed Readme.md --- Hyprspace.lua | 10 +- README.md | 265 ++++++++++++++++++-------------------------------- 2 files changed, 102 insertions(+), 173 deletions(-) diff --git a/Hyprspace.lua b/Hyprspace.lua index ee8e421..73d6aad 100644 --- a/Hyprspace.lua +++ b/Hyprspace.lua @@ -114,11 +114,15 @@ local function ensure_plugin_loaded() end local path = resolve_plugin_path() - if not path then - return false + if path then + hl.exec_cmd("hyprctl plugin load " .. shell_quote(path)) + if plugin_loaded() then + return true + end end - hl.exec_cmd("hyprctl plugin load " .. shell_quote(path)) + -- hyprpm: enabled plugins may not be loaded yet when Lua config runs + hl.exec_cmd("hyprpm reload") return plugin_loaded() end diff --git a/README.md b/README.md index 9341e53..94d7fc5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Hyprspace -Hyprspace is a Hyprland overview / workspace-expo plugin. It renders a top or bottom workspace strip, shows live workspace thumbnails, and lets you switch or move windows between workspaces. +Hyprland overview / workspace-expo plugin. Renders a top or bottom workspace strip with live thumbnails; switch or move windows between workspaces. -This fork is adapted for current Hyprland with Lua-based configuration instead of the old `.conf` / Hyprlang plugin config flow. +This fork targets current Hyprland with **Lua-based configuration** (not the old `.conf` / Hyprlang plugin config flow). ## Features @@ -10,151 +10,118 @@ This fork is adapted for current Hyprland with Lua-based configuration instead o - Multi-monitor support - Mouse drag/drop between workspaces - Optional gesture support -- Lua-native configuration -- `hyprpm` install support +- Lua-native configuration via [Hyprspace.lua](./Hyprspace.lua) +- Manual build or `hyprpm` install ## Requirements - Hyprland with plugin support -- Hyprland development headers / pkg-config metadata for manual builds -- A recent C++ compiler with C++23 support +- Hyprland development headers / pkg-config metadata (manual builds) +- C++ compiler with C++23 support ## Setup -### `hyprpm` auto setup +Pick **one** load method. Do not enable `hyprpm` and also `hyprctl plugin load` the same plugin twice. -Install and enable the plugin: +### 1. Local build (recommended for this repo) -```bash -hyprpm add https://github.com/0xl30/Hyprspace.git -hyprpm enable Hyprspace -hyprpm reload -``` - -Then in [source/plugins.lua](/home/xlord/.config/hypr/edit_here/source/plugins.lua): - -```lua -require("edit_here.Hyprspace.Hyprspace").setup("auto") -``` - -Reload Hyprland: +Clone or keep the repo next to your Hyprland config, then build: ```bash -hyprctl reload +git clone https://github.com/0xl30/Hyprspace.git ~/.config/hypr/Hyprspace +cd ~/.config/hypr/Hyprspace +make all ``` -### Manual `hyprctl` setup +Produces `Hyprspace.so` in that directory (same folder as `Hyprspace.lua`). -Build the local plugin: +**`hyprland.lua`** (or your overlay entrypoint) — add the module search path: -```bash -cd ~/.config/hypr/edit_here/Hyprspace -make all +```lua +package.path = package.path .. ";" .. HOME .. "/.config/hypr/Hyprspace/?.lua" ``` -This creates: +Use `?.lua`, not the full `Hyprspace.lua` filename. Lua replaces `?` with the module name for `require("Hyprspace")`. -```text -~/.config/hypr/edit_here/Hyprspace/Hyprspace.so +**`plugins.lua`:** + +```lua +local hyprspace = require("Hyprspace") +hyprspace.setup() ``` -Load it manually: +Optional explicit path (manual mode — only this path is tried): -```bash -hyprctl plugin load ~/.config/hypr/edit_here/Hyprspace/Hyprspace.so +```lua +hyprspace.setup({ + plugin_path = HOME .. "/.config/hypr/Hyprspace/Hyprspace.so", +}) ``` -Then in [source/plugins.lua](/home/xlord/.config/hypr/edit_here/source/plugins.lua): +**Keybinds** (e.g. in `keybinds.lua`): ```lua -local HOME = os.getenv("HOME") or "" +local hyprspace = require("Hyprspace") -require("edit_here.Hyprspace.Hyprspace").setup({ - plugin_path = HOME .. "/.config/hypr/edit_here/Hyprspace/Hyprspace.so", -}) +hl.unbind("SUPER + A") +hl.bind("SUPER + A", function() + hyprspace.toggle() +end) ``` -Reload after Lua changes: +Reload: ```bash hyprctl reload ``` -Unload manually if needed: +### 2. `hyprpm` ```bash -hyprctl plugin unload ~/.config/hypr/edit_here/Hyprspace/Hyprspace.so +hyprpm add https://github.com/0xl30/Hyprspace.git +hyprpm enable Hyprspace +hyprpm reload ``` -## Lua setup - -### 1. Plugin helper +Still add `package.path` (same as above) so `require("Hyprspace")` finds the Lua helper. -Use one of these in [source/plugins.lua](/home/xlord/.config/hypr/edit_here/source/plugins.lua): - -`hyprpm` / auto: +**`plugins.lua`:** ```lua -require("edit_here.Hyprspace.Hyprspace").setup("auto") -``` +local hyprspace = require("Hyprspace") -Manual local `.so`: - -```lua -local HOME = os.getenv("HOME") or "" - -require("edit_here.Hyprspace.Hyprspace").setup({ - plugin_path = HOME .. "/.config/hypr/edit_here/Hyprspace/Hyprspace.so", -}) +hyprspace.setup() ``` -### 2. Keybind +`setup()` does **not** scan hyprpm’s install tree for a `.so` path. It: -Add a keybind in [source/keybinds.lua](/home/xlord/.config/hypr/edit_here/source/keybinds.lua): +1. Uses `plugin_path` if you passed one (manual mode) +2. Else `HYPRSPACE_PLUGIN_PATH` if set +3. Else `Hyprspace.so` next to `Hyprspace.lua` +4. Else runs `hyprpm reload` and checks whether `hl.plugin.Hyprspace` is available -```lua -local hyprspace = require("edit_here.Hyprspace.Hyprspace") - -hl.unbind("SUPER + A") -hl.bind("SUPER + A", function() - hyprspace.toggle() -end) -``` - -### 3. Reload - -After changing the Lua config: +After Hyprland or plugin updates: ```bash +hyprpm update +hyprpm reload hyprctl reload ``` -After rebuilding the plugin binary: - -```bash -cd ~/.config/hypr/edit_here/Hyprspace -make all -hyprctl plugin unload ~/.config/hypr/edit_here/Hyprspace/Hyprspace.so -hyprctl plugin load ~/.config/hypr/edit_here/Hyprspace/Hyprspace.so -hyprctl reload -``` +### Environment variable -If the plugin is installed through `hyprpm`, use: +Override the `.so` location without editing Lua: ```bash -hyprpm update -hyprpm reload -hyprctl reload +export HYPRSPACE_PLUGIN_PATH="$HOME/.config/hypr/Hyprspace/Hyprspace.so" ``` ## Usage ### Lua API -The helper module exports: - ```lua -local hyprspace = require("edit_here.Hyprspace.Hyprspace") +local hyprspace = require("Hyprspace") hyprspace.setup() hyprspace.apply_config() @@ -164,9 +131,7 @@ hyprspace.overview("close") hyprspace.reload() ``` -### Plugin Lua entrypoint - -Once the plugin is loaded, you can also call: +### Plugin API (after load) ```lua hl.plugin.Hyprspace.overview("toggle") @@ -176,9 +141,15 @@ hl.plugin.Hyprspace.overview("close") ## Configuration -The plugin is configured from [Hyprspace.lua](./Hyprspace.lua). The helper reads Matugen colors from `IMPORT_COLOR`, builds a Lua config table, and applies it through `hl.config(...)`. +Defaults and Matugen colors live in [Hyprspace.lua](./Hyprspace.lua). The helper reads: + +```text +~/.config/matugen/generated/hyprland-colors.lua +``` + +If missing, built-in fallback colors are used. Matugen `rgba(rrggbbaa)` values are converted to the integer format the plugin expects. -### Example +Applied via `hl.config({ plugin = { hyprspace = { ... } } })`. Example: ```lua plugin = { @@ -206,7 +177,7 @@ plugin = { swipe_force_speed = 30, swipe_cancel_ratio = 0.5, click_release_threshold_ms = 200, - } + }, } ``` @@ -214,113 +185,67 @@ plugin = { #### Colors -- `panel_color` -- `panel_border_color` -- `workspace_active_background` -- `workspace_inactive_background` -- `workspace_active_border` -- `workspace_inactive_border` -- `drag_alpha` -- `disable_blur` +- `panel_color`, `panel_border_color` +- `workspace_active_background`, `workspace_inactive_background` +- `workspace_active_border`, `workspace_inactive_border` +- `drag_alpha`, `disable_blur` #### Layout -- `panel_height` -- `panel_border_width` -- `workspace_margin` -- `reserved_area` -- `workspace_border_size` -- `adaptive_height` -- `center_aligned` -- `on_bottom` -- `hide_background_layers` -- `hide_top_layers` -- `hide_overlay_layers` -- `draw_active_workspace` -- `hide_real_layers` -- `affect_strut` +- `panel_height`, `panel_border_width`, `workspace_margin`, `reserved_area`, `workspace_border_size` +- `adaptive_height`, `center_aligned`, `on_bottom` +- `hide_background_layers`, `hide_top_layers`, `hide_overlay_layers` +- `draw_active_workspace`, `hide_real_layers`, `affect_strut` #### Behavior -- `auto_drag` -- `auto_scroll` -- `exit_on_click` -- `switch_on_drop` -- `exit_on_switch` -- `show_new_workspace` -- `show_empty_workspace` -- `show_special_workspace` -- `exit_key` +- `auto_drag`, `auto_scroll`, `exit_on_click`, `switch_on_drop`, `exit_on_switch` +- `show_new_workspace`, `show_empty_workspace`, `show_special_workspace`, `exit_key` #### Gestures and input -- `disable_gestures` -- `reverse_swipe` -- `swipe_fingers` -- `swipe_distance` -- `swipe_force_speed` -- `swipe_cancel_ratio` -- `swipe_threshold` -- `swipe_closed_padding` -- `workspace_scroll_speed` +- `disable_gestures`, `reverse_swipe`, `swipe_fingers`, `swipe_distance`, `swipe_force_speed` +- `swipe_cancel_ratio`, `swipe_threshold`, `swipe_closed_padding`, `workspace_scroll_speed` - `click_release_threshold_ms` #### Animation - `override_anim_speed` -## Theming - -By default, [Hyprspace.lua](./Hyprspace.lua) reads colors from: - -```text -~/.config/matugen/generated/hyprland-colors.lua -``` - -If that file does not exist, Hyprspace falls back to built-in colors. - -The helper converts Matugen `rgba(rrggbbaa)` strings into the integer color format expected by the plugin. - ## Troubleshooting -### The keybind does nothing - -Check that the plugin is actually loaded: +### Keybind does nothing ```bash hyprctl plugin list ``` -If you are using a manual build, load the `.so` first. +Expect `Plugin Hyprspace`. If empty: -### Lua settings are not applied +- Local: `make all` in the Hyprspace repo, then `hyprctl reload` +- hyprpm: `hyprpm reload`, then `hyprctl reload` +- Ensure `plugins.lua` calls `hyprspace.setup()` (not `hyprspace.setup(auto)` — `auto` is not a valid argument) -Make sure [source/plugins.lua](/home/xlord/.config/hypr/edit_here/source/plugins.lua) calls: +### Lua colors / settings not applied -```lua -require("edit_here.Hyprspace.Hyprspace").setup("auto") -``` +`plugins.lua` must call `hyprspace.setup()` or `hyprspace.setup({ plugin_path = "..." })` so hooks run and `hl.config(...)` is applied after the plugin loads. -Then run: +### `require("Hyprspace")` fails -```bash -hyprctl reload -``` - -### `hyprpm enable` fails +Add to your Hyprland Lua entrypoint: -Check: +```lua +package.path = package.path .. ";" .. HOME .. "/.config/hypr/Hyprspace/?.lua" +``` -- the repo URL is correct -- `hyprpm.toml` contains a pin for your current Hyprland commit -- the plugin builds cleanly on that commit +Do **not** append `Hyprspace/Hyprspace.lua` directly — use `?.lua`. -### The plugin crashes on load +### `hyprpm enable` fails -Check the latest Hyprland crash report under: +- Correct repo URL +- `hyprpm.toml` has a commit pin for your Hyprland version +- Plugin builds cleanly on that commit (`hyprpm update` / rebuild) -```text -~/.cache/hyprland/ -``` +### Plugin crashes on load -and verify the plugin was built against the same Hyprland version you are running. +Check `~/.cache/hyprland/` crash reports. Rebuild the plugin against the same Hyprland version you are running (`hyprctl version`). From 74213c87be6064ce14f9b1560121c270462f33af Mon Sep 17 00:00:00 2001 From: 0xl30 Date: Wed, 10 Jun 2026 13:18:46 +0530 Subject: [PATCH 7/9] Fixed Sleep Mode Crash Hyprland --- src/Overview.cpp | 2 +- src/Render.cpp | 21 +++++++++++++-------- src/main.cpp | 34 +++++++++++++++++++++++++++------- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/Overview.cpp b/src/Overview.cpp index b377e2f..585c7e6 100644 --- a/src/Overview.cpp +++ b/src/Overview.cpp @@ -109,7 +109,7 @@ PHLMONITOR CHyprspaceWidget::getOwner() { void CHyprspaceWidget::show() { auto owner = getOwner(); - if (!owner) + if (!owner || !owner->m_enabled || g_pCompositor->m_unsafeState) return; if (prevFullscreen.empty()) { diff --git a/src/Render.cpp b/src/Render.cpp index e83c94d..bb29ca6 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -79,6 +79,15 @@ void renderWindowStub(PHLWINDOW window, PHLMONITOR monitor, PHLWORKSPACE workspa window->m_pinned = true; window->m_ruleApplicator->rounding().set(window->rounding() * scaleMod * monitor->m_scale, Desktop::Types::PRIORITY_SET_PROP); + Hyprutils::Utils::CScopeGuard restoreWindowState([&] { + window->m_workspace = workspace; + window->m_fullscreenState = fullscreenState; + window->m_ruleApplicator->nearestNeighbor().unset(Desktop::Types::PRIORITY_SET_PROP); + window->m_isFloating = floating; + window->m_pinned = pinned; + window->m_ruleApplicator->rounding().unset(Desktop::Types::PRIORITY_SET_PROP); + }); + g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = renderModif})); Hyprutils::Utils::CScopeGuard clearHints([] { g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = Render::SRenderModifData{}})); @@ -87,13 +96,6 @@ void renderWindowStub(PHLWINDOW window, PHLMONITOR monitor, PHLWORKSPACE workspa g_pHyprRenderer->damageWindow(window); if (pRenderWindow) (*(tRenderWindow)pRenderWindow)(g_pHyprRenderer.get(), window, monitor, time, true, Render::RENDER_PASS_ALL, false, false); - - window->m_workspace = workspace; - window->m_fullscreenState = fullscreenState; - window->m_ruleApplicator->nearestNeighbor().unset(Desktop::Types::PRIORITY_SET_PROP); - window->m_isFloating = floating; - window->m_pinned = pinned; - window->m_ruleApplicator->rounding().unset(Desktop::Types::PRIORITY_SET_PROP); } void renderLayerStub(PHLLS layer, PHLMONITOR monitor, CBox rectOverride, const Time::steady_tp& time) { @@ -151,11 +153,14 @@ bool renderWindowPreview(PHLWINDOW window, PHLWORKSPACE workspace, PHLMONITOR ow void CHyprspaceWidget::draw() { workspaceBoxes.clear(); + if (g_pCompositor->m_unsafeState) + return; + if (!active && !curYOffset->isBeingAnimated()) return; const auto owner = getOwner(); - if (!owner) + if (!owner || !owner->m_enabled) return; const CBox monitorClip = {{0, 0}, owner->m_transformedSize}; diff --git a/src/main.cpp b/src/main.cpp index 2805db6..e6ed9b2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -90,6 +90,7 @@ CHyprSignalListener g_pSwipeEndHook; CHyprSignalListener g_pKeyPressHook; CHyprSignalListener g_pSwitchWorkspaceHook; CHyprSignalListener g_pAddMonitorHook; +CHyprSignalListener g_pPreRemoveMonitorHook; CHyprSignalListener g_pRemoveMonitorHook; CHyprSignalListener g_pStartHook; @@ -154,20 +155,34 @@ void endSwipeIfNeeded(const std::shared_ptr& widget) { widget->endSwipe(cancelled); } -void removeMonitorWidget(PHLMONITOR monitor) { +void teardownMonitorWidget(PHLMONITOR monitor) { if (!monitor) return; + for (auto& widget : g_overviewWidgets) { + if (!widget) + continue; + + const auto owner = widget->getOwner(); + if (owner && owner != monitor) + continue; + + endSwipeIfNeeded(widget); + if (widget->isActive()) + widget->hide(); + widget->cleanup(monitor); + } +} + +void removeMonitorWidget(PHLMONITOR monitor) { + teardownMonitorWidget(monitor); + g_overviewWidgets.erase(std::remove_if(g_overviewWidgets.begin(), g_overviewWidgets.end(), [monitor](const std::shared_ptr& widget) { if (!widget) return true; const auto owner = widget->getOwner(); - if (owner && owner != monitor) - return false; - - widget->cleanup(monitor); return owner == monitor || !owner; }), g_overviewWidgets.end()); @@ -252,6 +267,9 @@ bool g_layoutNeedsRefresh = true; float g_oAlpha = -1; void onRender(eRenderStage renderStage) { + if (g_pCompositor->m_unsafeState) + return; + if (renderStage == eRenderStage::RENDER_PRE) { if (g_layoutNeedsRefresh) { refreshWidgets(); @@ -631,8 +649,9 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE inHandle) { pRenderLayer = findFunctionBySymbol(pHandle, "renderLayer", "CHyprRenderer::renderLayer"); registerMonitors(); - g_pAddMonitorHook = Event::bus()->m_events.monitor.added.listen([](PHLMONITOR) { registerMonitors(); }); - g_pRemoveMonitorHook = Event::bus()->m_events.monitor.removed.listen([](PHLMONITOR monitor) { removeMonitorWidget(monitor); }); + g_pAddMonitorHook = Event::bus()->m_events.monitor.added.listen([](PHLMONITOR) { registerMonitors(); }); + g_pPreRemoveMonitorHook = Event::bus()->m_events.monitor.preRemoved.listen([](PHLMONITOR monitor) { teardownMonitorWidget(monitor); }); + g_pRemoveMonitorHook = Event::bus()->m_events.monitor.removed.listen([](PHLMONITOR monitor) { removeMonitorWidget(monitor); }); return {"Hyprspace", "Workspace overview", "KZdkm", "0.2"}; } @@ -653,6 +672,7 @@ APICALL EXPORT void PLUGIN_EXIT() { g_pKeyPressHook.reset(); g_pSwitchWorkspaceHook.reset(); g_pAddMonitorHook.reset(); + g_pPreRemoveMonitorHook.reset(); g_pRemoveMonitorHook.reset(); g_pStartHook.reset(); From 9300af62f5773a121be121be4d6a2eb220be24a6 Mon Sep 17 00:00:00 2001 From: 0xl30 Date: Tue, 21 Jul 2026 11:44:36 +0530 Subject: [PATCH 8/9] Fix For Hyprland v0.56.0 --- .gitignore | 3 +- README.md | 10 ++++-- flake.nix | 8 +++-- hyprpm.toml | 2 ++ meson.build | 2 +- src/Globals.hpp | 45 ++++++++++++++++++++++-- src/Input.cpp | 44 +++++++++++------------ src/Layout.cpp | 2 +- src/Overview.cpp | 89 +++++++++++++++++++++++++++------------------- src/Overview.hpp | 10 +++--- src/Render.cpp | 91 ++++++++++++++++++++++-------------------------- src/main.cpp | 49 +++++++++++++------------- 12 files changed, 209 insertions(+), 146 deletions(-) diff --git a/.gitignore b/.gitignore index 30de1b3..e32e4a5 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,5 @@ meson-logs meson-private Hyprspace.conf -log.txt \ No newline at end of file +log.txt +flow-chart.md \ No newline at end of file diff --git a/README.md b/README.md index 94d7fc5..329d7a0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Hyprland overview / workspace-expo plugin. Renders a top or bottom workspace strip with live thumbnails; switch or move windows between workspaces. -This fork targets current Hyprland with **Lua-based configuration** (not the old `.conf` / Hyprlang plugin config flow). +This fork targets Hyprland **0.56.0+** with **Lua-based configuration** (not the old `.conf` / Hyprlang plugin config flow). ## Features @@ -15,7 +15,7 @@ This fork targets current Hyprland with **Lua-based configuration** (not the old ## Requirements -- Hyprland with plugin support +- Hyprland **0.56.0+** (Animation / State API refactor) - Hyprland development headers / pkg-config metadata (manual builds) - C++ compiler with C++23 support @@ -243,9 +243,13 @@ Do **not** append `Hyprspace/Hyprspace.lua` directly — use `?.lua`. ### `hyprpm enable` fails - Correct repo URL -- `hyprpm.toml` has a commit pin for your Hyprland version +- `hyprpm.toml` has a commit pin for your Hyprland version (for 0.56.0, pin after the State/Animation API port is committed) - Plugin builds cleanly on that commit (`hyprpm update` / rebuild) +### Build fails: missing `AnimationManager.hpp` + +On Hyprland 0.56+, animation headers moved from `managers/animation/` to `animation/`, and compositor helpers moved into `State::` / `Desktop::` / `Fullscreen::` trackers. Rebuild this fork against matching headers (`pkg-config --modversion hyprland`). + ### Plugin crashes on load Check `~/.cache/hyprland/` crash reports. Rebuild the plugin against the same Hyprland version you are running (`hyprctl version`). diff --git a/flake.nix b/flake.nix index 24a4678..431e6b3 100644 --- a/flake.nix +++ b/flake.nix @@ -11,9 +11,11 @@ owner = "hyprwm"; repo = "Hyprland"; type = "github"; - ref = "v0.55.0"; + ref = "v0.56.0"; inputs.systems.follows = "systems"; }; + # After changing the Hyprland ref, refresh the lock with: + # nix flake update hyprland }; outputs = { @@ -50,7 +52,7 @@ Hyprspace = let hyprlandPkg = hyprland.packages.${system}.hyprland; in - pkgs.gcc14Stdenv.mkDerivation { + pkgs.gcc16Stdenv.mkDerivation { pname = "Hyprspace"; inherit version; src = ./.; @@ -78,7 +80,7 @@ devShells = perSystem (system: pkgs: { default = pkgs.mkShell { name = "Hyprspace-shell"; - nativeBuildInputs = with pkgs; [gcc14 clang-tools bear]; + nativeBuildInputs = with pkgs; [gcc16 clang-tools bear]; buildInputs = [hyprland.packages.${system}.hyprland]; inputsFrom = [ hyprland.packages.${system}.hyprland diff --git a/hyprpm.toml b/hyprpm.toml index 6eb43e9..e8fb6fb 100644 --- a/hyprpm.toml +++ b/hyprpm.toml @@ -20,6 +20,8 @@ commit_pins = [ ["0bd541f2fd902dbfa04c3ea2ccf679395e316887", "cd58d2e47b575c66c2682436ba425ccdc8462998"], # 0.46.2 ["4e242d086e20b32951fdc0ebcbfb4d41b5be8dcc", "0a82e3724f929de8ad8fb04d2b7fa128493f24f7"], # 0.50.1 ["39d7e209c79d451efab1b21151d5938289da838d", "f4b2c2537b2c02a477f17e3a684d4b542d82537b"], # 0.55.2 + # 0.56.0 — pin plugin commit after publishing the State/Animation API port: + # ["36b2e0cfe0c6094dbc47bd42a437431315bb3087", ""], # 0.56.0 ] [Hyprspace] diff --git a/meson.build b/meson.build index 94a4cbe..7dc9253 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('Hyprspace', 'cpp', - version: '0.1', + version: '0.3', default_options: ['buildtype=release'], ) diff --git a/src/Globals.hpp b/src/Globals.hpp index 78e9f3d..b9c1a11 100644 --- a/src/Globals.hpp +++ b/src/Globals.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -9,14 +10,22 @@ #include #include +#include #include #include #include +#include #include -#include #include #include +#include #include +#include +#include +#include +#include +#include +#include // Hyprland v0.54+: cancellable input uses Event::SCallbackInfo (not legacy CEvent*). using SCallbackInfo = Event::SCallbackInfo; @@ -38,6 +47,33 @@ CHyprSignalListener listenCancellable(Signal& signal, std::functionquery().id(id).run(); +} + +inline PHLMONITOR monitorFromName(std::string_view name) { + if (name.empty()) + return nullptr; + return State::monitorState()->query().name(name).run(); +} + +inline PHLMONITOR monitorFromCursor() { + return State::monitorState()->query().vec(g_pInputManager->getMouseCoordsInternal()).run(); +} + +inline PHLWORKSPACE workspaceByID(WORKSPACEID id) { + return State::workspaceState()->query().id(id).run(); +} + +inline PHLWORKSPACE createWorkspace(WORKSPACEID id, MONITORID monid) { + return State::workspaceState()->create(id, monid); +} + +inline bool compositorUnsafe() { + return !g_pCompositor || !g_pCompositor->m_sessionActive || State::monitorState()->monitors().empty(); +} + inline HANDLE pHandle = NULL; typedef void (*tRenderWindow)(void*, PHLWINDOW, PHLMONITOR, const Time::steady_tp&, bool, Render::eRenderPassMode, bool, bool); @@ -94,4 +130,9 @@ namespace Config { extern float workspaceScrollSpeed; } -extern int numWorkspaces; +inline double panelTravel(PHLMONITOR owner) { + if (!owner) + return 0.; + + return (Config::panelHeight + Config::reservedArea) * owner->m_scale; +} diff --git a/src/Input.cpp b/src/Input.cpp index 715c7dc..06c189f 100644 --- a/src/Input.cpp +++ b/src/Input.cpp @@ -8,13 +8,6 @@ namespace { -double panelTravel(PHLMONITOR owner) { - if (!owner) - return 0.; - - return (Config::panelHeight + Config::reservedArea) * owner->m_scale; -} - double swipeClosedOffset() { return -Config::swipeClosedPadding; } @@ -27,7 +20,7 @@ double swipeVisibleThreshold() { bool CHyprspaceWidget::buttonEvent(bool pressed, Vector2D coords) { const auto owner = getOwner(); - if (!owner) + if (!owner || !animationsOk()) return true; const auto dragTarget = g_layoutManager->dragController()->target(); @@ -50,16 +43,16 @@ bool CHyprspaceWidget::buttonEvent(bool pressed, Vector2D coords) { break; } - auto targetWorkspace = g_pCompositor->getWorkspaceByID(targetWorkspaceID); + auto targetWorkspace = workspaceByID(targetWorkspaceID); if (!targetWorkspace && targetWorkspaceID >= SPECIAL_WORKSPACE_START) - targetWorkspace = g_pCompositor->createNewWorkspace(targetWorkspaceID, owner->m_id); + targetWorkspace = createWorkspace(targetWorkspaceID, owner->m_id); if (Config::autoDrag && (targetWorkspace == nullptr || !pressed)) { if (g_layoutManager->dragController()->target()) g_layoutManager->endDragTarget(); if (pressed) { - const auto window = g_pCompositor->vectorToWindowUnified(coords, Desktop::View::WINDOW_ONLY, nullptr); + const auto window = Desktop::viewState()->hitTest().windowAt(coords, Desktop::View::WINDOW_ONLY); if (window) { const auto target = window->layoutTarget(); if (target) @@ -69,15 +62,16 @@ bool CHyprspaceWidget::buttonEvent(bool pressed, Vector2D coords) { } if (targetWindow && targetWorkspace != nullptr && !pressed) { - g_pCompositor->moveWindowToWorkspaceSafe(targetWindow, targetWorkspace); + Desktop::globalWindowController()->moveWindowToWorkspace(targetWindow, targetWorkspace); if (targetWindow->m_isFloating) { const auto targetPos = owner->m_position + (owner->m_size / 2.) - (targetWindow->m_reportedSize / 2.); - targetWindow->m_position = targetPos; - *targetWindow->m_realPosition = targetPos; + targetWindow->move(targetPos); + targetWindow->positionAnimation()->setValueAndWarp(targetPos); } if (Config::switchOnDrop) { - g_pCompositor->getMonitorFromID(targetWorkspace->m_monitor->m_id)->changeWorkspace(targetWorkspace->m_id); + if (const auto monitor = monitorFromID(targetWorkspace->m_monitor->m_id)) + monitor->changeWorkspace(targetWorkspace->m_id); if (Config::exitOnSwitch && active) hide(); } @@ -86,8 +80,8 @@ bool CHyprspaceWidget::buttonEvent(bool pressed, Vector2D coords) { } else if (targetWorkspace && !pressed) { if (targetWorkspace->m_isSpecialWorkspace) { owner->activeSpecialWorkspaceID() == targetWorkspaceID ? owner->setSpecialWorkspace(nullptr) : owner->setSpecialWorkspace(targetWorkspaceID); - } else { - g_pCompositor->getMonitorFromID(targetWorkspace->m_monitor->m_id)->changeWorkspace(targetWorkspace->m_id); + } else if (const auto monitor = monitorFromID(targetWorkspace->m_monitor->m_id)) { + monitor->changeWorkspace(targetWorkspace->m_id); } if (Config::exitOnSwitch && active) @@ -102,7 +96,7 @@ bool CHyprspaceWidget::buttonEvent(bool pressed, Vector2D coords) { bool CHyprspaceWidget::axisEvent(double delta, wl_pointer_axis axis, Vector2D coords) { const auto owner = getOwner(); - if (!owner) + if (!owner || !animationsOk()) return true; const auto travel = panelTravel(owner); @@ -116,8 +110,8 @@ bool CHyprspaceWidget::axisEvent(double delta, wl_pointer_axis axis, Vector2D co } else if (Config::autoScroll && axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { const auto relative = delta < 0 ? "r-1" : "r+1"; const auto wsIDName = getWorkspaceIDNameFromString(relative); - if (!g_pCompositor->getWorkspaceByID(wsIDName.id)) - (void)g_pCompositor->createNewWorkspace(wsIDName.id, ownerID); + if (!workspaceByID(wsIDName.id)) + (void)createWorkspace(wsIDName.id, ownerID); owner->changeWorkspace(wsIDName.id); } @@ -146,7 +140,7 @@ bool CHyprspaceWidget::updateSwipe(IPointer::SSwipeUpdateEvent e) { if (verticalSwipe) { if (swiping && e.fingers == static_cast(Config::swipeFingers)) { const auto owner = getOwner(); - if (!owner) + if (!owner || !animationsOk()) return true; const auto distance = std::max(Config::swipeDistance, 1); @@ -172,7 +166,7 @@ bool CHyprspaceWidget::updateSwipe(IPointer::SSwipeUpdateEvent e) { } } else if (e.fingers == static_cast(Config::swipeFingers) && active) { const auto owner = getOwner(); - if (!owner) + if (!owner || !animationsOk()) return true; const auto travel = panelTravel(owner); @@ -195,6 +189,12 @@ bool CHyprspaceWidget::endSwipe(IPointer::SSwipeEndEvent e) { const auto owner = getOwner(); const auto travel = panelTravel(owner); + if (!owner || !animationsOk()) { + avgSwipeSpeed = 0.; + swipePoints = 0; + return false; + } + if (e.cancelled) { if (active) hide(); diff --git a/src/Layout.cpp b/src/Layout.cpp index ba6c890..0d90280 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -17,5 +17,5 @@ void CHyprspaceWidget::updateLayout() { g_pHyprRenderer->arrangeLayersForMonitor(ownerID); g_layoutManager->recalculateMonitor(monitor); - g_pCompositor->scheduleFrameForMonitor(monitor); + monitor->scheduleFrame(); } diff --git a/src/Overview.cpp b/src/Overview.cpp index 585c7e6..8b41e6f 100644 --- a/src/Overview.cpp +++ b/src/Overview.cpp @@ -7,19 +7,12 @@ namespace { -double panelTravelForMonitor(PHLMONITOR owner) { - if (!owner) - return 0.; - - return (Config::panelHeight + Config::reservedArea) * owner->m_scale; -} - double closedSwipeOffset() { return -Config::swipeClosedPadding; } double shownSwipeOffset(PHLMONITOR owner) { - return panelTravelForMonitor(owner); + return panelTravel(owner); } void requestFullMonitorRedraw(PHLMONITOR owner) { @@ -27,7 +20,11 @@ void requestFullMonitorRedraw(PHLMONITOR owner) { return; owner->m_damage.damageEntire(); - g_pCompositor->scheduleFrameForMonitor(owner); + owner->scheduleFrame(); +} + +PHLANIMVAR& layerFadeAlpha(PHLLS layer) { + return layer->alpha()[Desktop::View::LS_ALPHA_FADE]; } } // namespace @@ -38,48 +35,66 @@ CHyprspaceWidget::CHyprspaceWidget(uint64_t inOwnerID) : ownerID(inOwnerID) { CHyprspaceWidget::~CHyprspaceWidget() { cleanup(getOwner()); - curYOffset.reset(); - workspaceScrollOffset.reset(); + releaseAnimations(); } void CHyprspaceWidget::restoreHiddenLayers() { for (const auto& [layer, alpha] : oLayerAlpha) { - if (!layer || layer->m_readyToDelete || !layer->m_mapped) + if (!layer || !layer->m_mapped) continue; - layer->m_fadingOut = false; - *layer->m_alpha = alpha; + *layerFadeAlpha(layer) = alpha; } oLayerAlpha.clear(); } void CHyprspaceWidget::restoreFullscreenWindows() { - for (const auto& [windowRef, fullscreenMode] : prevFullscreen) { + for (const auto& [windowRef, modes] : prevFullscreen) { const auto window = windowRef.lock(); if (!window) continue; - g_pCompositor->setWindowFullscreenState(window, Desktop::View::SFullscreenState{.internal = fullscreenMode, .client = fullscreenMode}); - if (fullscreenMode == FSMODE_FULLSCREEN) + Fullscreen::controller()->setFullscreenMode(window, modes.internal, modes.client); + if (modes.internal == Fullscreen::FSMODE_FULLSCREEN) window->m_wantsInitialFullscreen = false; } prevFullscreen.clear(); } +void CHyprspaceWidget::releaseAnimations() { + if (curYOffset) { + curYOffset->warp(true); + curYOffset.reset(); + } + + if (workspaceScrollOffset) { + workspaceScrollOffset->warp(true); + workspaceScrollOffset.reset(); + } + + m_animationConfig.reset(); +} + +bool CHyprspaceWidget::animationsOk() const { + return curYOffset && curYOffset->ok() && workspaceScrollOffset && workspaceScrollOffset->ok(); +} + void CHyprspaceWidget::resetAnimationState(PHLMONITOR owner) { - curAnimationConfig = *Config::animationTree()->getAnimationPropertyConfig("windows"); - curAnimation = *curAnimationConfig.pValues.lock(); - *curAnimationConfig.pValues.lock() = curAnimation; + releaseAnimations(); + const auto base = Config::animationTree()->getAnimationPropertyConfig("windows"); + m_animationConfig = makeShared(*base); if (Config::overrideAnimSpeed > 0) - curAnimation.internalSpeed = Config::overrideAnimSpeed; + m_animationConfig->internalSpeed = Config::overrideAnimSpeed; + m_animationConfig->pValues = m_animationConfig; + curAnimationConfig = *m_animationConfig; - g_pAnimationManager->createAnimation(0.F, curYOffset, curAnimationConfig.pValues.lock(), AVARDAMAGE_ENTIRE); - g_pAnimationManager->createAnimation(0.F, workspaceScrollOffset, curAnimationConfig.pValues.lock(), AVARDAMAGE_ENTIRE); + Animation::mgr()->createAnimation(0.F, curYOffset, m_animationConfig, AVARDAMAGE_ENTIRE); + Animation::mgr()->createAnimation(0.F, workspaceScrollOffset, m_animationConfig, AVARDAMAGE_ENTIRE); - const auto hiddenOffset = panelTravelForMonitor(owner); + const auto hiddenOffset = panelTravel(owner); curYOffset->setValueAndWarp(active ? 0.F : hiddenOffset); workspaceScrollOffset->setValueAndWarp(0.F); curSwipeOffset = active ? shownSwipeOffset(owner) : closedSwipeOffset(); @@ -104,28 +119,30 @@ void CHyprspaceWidget::cleanup(PHLMONITOR owner) { } PHLMONITOR CHyprspaceWidget::getOwner() { - return g_pCompositor->getMonitorFromID(ownerID); + return monitorFromID(ownerID); } void CHyprspaceWidget::show() { auto owner = getOwner(); - if (!owner || !owner->m_enabled || g_pCompositor->m_unsafeState) + if (!owner || !owner->m_enabled || compositorUnsafe() || !animationsOk()) return; if (prevFullscreen.empty()) { - for (auto& ws : g_pCompositor->getWorkspaces()) { + for (auto& wsRef : State::workspaceState()->workspaces()) { + const auto ws = wsRef.lock(); if (!ws || !ws->m_monitor || ws->m_monitor->m_id != ownerID) continue; - const auto window = ws->getFullscreenWindow(); - if (!window || ws->m_fullscreenMode == FSMODE_NONE) + const auto window = Fullscreen::controller()->getFullscreenWindow(ws); + const auto modes = Fullscreen::controller()->getFullscreenModes(ws); + if (!window || modes.internal == Fullscreen::FSMODE_NONE) continue; - if (ws->m_fullscreenMode == FSMODE_FULLSCREEN) + if (modes.internal == Fullscreen::FSMODE_FULLSCREEN) window->m_wantsInitialFullscreen = true; - prevFullscreen.emplace_back(PHLWINDOWREF(window), ws->m_fullscreenMode); - g_pCompositor->setWindowFullscreenState(window, Desktop::View::SFullscreenState{.internal = FSMODE_NONE, .client = FSMODE_NONE}); + prevFullscreen.emplace_back(PHLWINDOWREF(window), modes); + Fullscreen::controller()->setFullscreenMode(window, Fullscreen::FSMODE_NONE, Fullscreen::FSMODE_NONE); } } @@ -136,9 +153,9 @@ void CHyprspaceWidget::show() { if (!layer) continue; - oLayerAlpha.emplace_back(layer, layer->m_alpha->goal()); - *layer->m_alpha = 0.F; - layer->m_fadingOut = true; + auto& fade = layerFadeAlpha(layer); + oLayerAlpha.emplace_back(layer, fade->goal()); + *fade = 0.F; } } } @@ -157,7 +174,7 @@ void CHyprspaceWidget::show() { void CHyprspaceWidget::hide() { auto owner = getOwner(); - if (!owner) + if (!owner || !animationsOk()) return; restoreHiddenLayers(); diff --git a/src/Overview.hpp b/src/Overview.hpp index 5c7777b..2fd099d 100644 --- a/src/Overview.hpp +++ b/src/Overview.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include class CHyprspaceWidget { @@ -8,22 +9,21 @@ class CHyprspaceWidget { int64_t ownerID; - // animation override stuff Hyprutils::Animation::SAnimationPropertyConfig curAnimationConfig; - Hyprutils::Animation::SAnimationPropertyConfig curAnimation; + SP m_animationConfig; // for checking mouse hover for workspace drag and move // modified on draw call, accessed on mouse click and release std::vector> workspaceBoxes; // for storing the fullscreen state of windows prior to overview activation (which unfullscreens all windows) - std::vector> prevFullscreen; + std::vector> prevFullscreen; // for storing the layer alpha values prior to overview activation (which sets all panel to transparent when configured) std::vector> oLayerAlpha; // for click-to-exit - std::chrono::system_clock::time_point lastPressedTime = std::chrono::high_resolution_clock::now(); + std::chrono::high_resolution_clock::time_point lastPressedTime = std::chrono::high_resolution_clock::now(); bool swiping = false; // whether if the panel is active before the current swiping event @@ -38,6 +38,8 @@ class CHyprspaceWidget { void restoreHiddenLayers(); void restoreFullscreenWindows(); + void releaseAnimations(); + bool animationsOk() const; void resetAnimationState(PHLMONITOR owner); public: diff --git a/src/Render.cpp b/src/Render.cpp index bb29ca6..b8cdfc3 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -1,8 +1,6 @@ #include "Overview.hpp" #include "Globals.hpp" #include -#include -#include #include #include @@ -48,17 +46,17 @@ void renderWindowStub(PHLWINDOW window, PHLMONITOR monitor, PHLWORKSPACE workspa if (!window || !monitor || !workspaceOverride) return; - if (!window->m_isMapped || !window->wlSurface() || !window->wlSurface()->resource()) + if (!window->m_isMapped || !window->wlSurface() || !window->wlSurface()->resource() || !window->m_ruleApplicator) return; Render::SRenderModifData renderModif; - const auto workspace = window->m_workspace; - const auto fullscreenState = window->m_fullscreenState; - const auto realPosition = window->m_realPosition->value(); - const auto realSize = window->m_realSize->value(); - const auto pinned = window->m_pinned; - const auto floating = window->m_isFloating; + const auto workspace = window->m_workspace; + const auto fsModes = Fullscreen::controller()->getFullscreenModes(window); + const auto realPosition = window->positionAnimation()->value(); + const auto realSize = window->sizeAnimation()->value(); + const auto pinned = window->m_pinned; + const auto floating = window->m_isFloating; const float logicalW = std::max(static_cast(realSize.x), 5.F); const float scaleMod = rectOverride.w / std::max(logicalW * monitor->m_scale, 5.F); if (!(scaleMod > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) @@ -72,16 +70,20 @@ void renderWindowStub(PHLWINDOW window, PHLMONITOR monitor, PHLWORKSPACE workspa renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, std::any(scaleMod))); renderModif.enabled = true; - window->m_workspace = workspaceOverride; - window->m_fullscreenState = Desktop::View::SFullscreenState{FSMODE_NONE}; + window->m_workspace = workspaceOverride; window->m_ruleApplicator->nearestNeighbor().set(false, Desktop::Types::PRIORITY_SET_PROP); window->m_isFloating = false; window->m_pinned = true; window->m_ruleApplicator->rounding().set(window->rounding() * scaleMod * monitor->m_scale, Desktop::Types::PRIORITY_SET_PROP); + const bool wasFullscreen = fsModes.internal != Fullscreen::FSMODE_NONE; + if (wasFullscreen) + Fullscreen::controller()->setFullscreenMode(window, Fullscreen::FSMODE_NONE, Fullscreen::FSMODE_NONE); + Hyprutils::Utils::CScopeGuard restoreWindowState([&] { - window->m_workspace = workspace; - window->m_fullscreenState = fullscreenState; + if (wasFullscreen) + Fullscreen::controller()->setFullscreenMode(window, fsModes.internal, fsModes.client); + window->m_workspace = workspace; window->m_ruleApplicator->nearestNeighbor().unset(Desktop::Types::PRIORITY_SET_PROP); window->m_isFloating = floating; window->m_pinned = pinned; @@ -102,13 +104,13 @@ void renderLayerStub(PHLLS layer, PHLMONITOR monitor, CBox rectOverride, const T if (!layer || !monitor) return; - if (!layer->m_mapped || layer->m_readyToDelete || !layer->m_layerSurface || !layer->wlSurface() || !layer->wlSurface()->resource()) + if (!layer->m_mapped || !layer->m_layerSurface || !layer->wlSurface() || !layer->wlSurface()->resource()) return; - const Vector2D realPosition = layer->m_realPosition->value(); - const Vector2D realSize = layer->m_realSize->value(); - const float alpha = layer->m_alpha->value(); - const bool fadingOut = layer->m_fadingOut; + const Vector2D realPosition = layer->positionAnimation()->value(); + const Vector2D realSize = layer->sizeAnimation()->value(); + auto& fadeAlpha = layer->alpha()[Desktop::View::LS_ALPHA_FADE]; + const float alpha = fadeAlpha->value(); const float scale = rectOverride.w / realSize.x; if (!(scale > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) @@ -119,8 +121,7 @@ void renderLayerStub(PHLLS layer, PHLMONITOR monitor, CBox rectOverride, const T renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, std::any(scale))); renderModif.enabled = true; - layer->m_alpha->setValueAndWarp(1.F); - layer->m_fadingOut = false; + fadeAlpha->setValueAndWarp(1.F); g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = renderModif})); Hyprutils::Utils::CScopeGuard clearHints([] { @@ -129,18 +130,17 @@ void renderLayerStub(PHLLS layer, PHLMONITOR monitor, CBox rectOverride, const T if (pRenderLayer) (*(tRenderLayer)pRenderLayer)(g_pHyprRenderer.get(), layer, monitor, time, false, false); - layer->m_fadingOut = fadingOut; - layer->m_alpha->setValueAndWarp(alpha); + fadeAlpha->setValueAndWarp(alpha); } bool renderWindowPreview(PHLWINDOW window, PHLWORKSPACE workspace, PHLMONITOR owner, double workspaceX, double workspaceY, double monitorScaleFactor, const Time::steady_tp& time) { if (!window || !workspace || !owner) return false; - const double wX = workspaceX + ((window->m_realPosition->value().x - owner->m_position.x) * monitorScaleFactor * owner->m_scale); - const double wY = workspaceY + ((window->m_realPosition->value().y - owner->m_position.y) * monitorScaleFactor * owner->m_scale); - const double wW = window->m_realSize->value().x * monitorScaleFactor * owner->m_scale; - const double wH = window->m_realSize->value().y * monitorScaleFactor * owner->m_scale; + const double wX = workspaceX + ((window->positionAnimation()->value().x - owner->m_position.x) * monitorScaleFactor * owner->m_scale); + const double wY = workspaceY + ((window->positionAnimation()->value().y - owner->m_position.y) * monitorScaleFactor * owner->m_scale); + const double wW = window->sizeAnimation()->value().x * monitorScaleFactor * owner->m_scale; + const double wH = window->sizeAnimation()->value().y * monitorScaleFactor * owner->m_scale; if (!(wW > 0 && wH > 0)) return false; @@ -153,7 +153,7 @@ bool renderWindowPreview(PHLWINDOW window, PHLWORKSPACE workspace, PHLMONITOR ow void CHyprspaceWidget::draw() { workspaceBoxes.clear(); - if (g_pCompositor->m_unsafeState) + if (compositorUnsafe() || !animationsOk()) return; if (!active && !curYOffset->isBeingAnimated()) @@ -201,38 +201,31 @@ void CHyprspaceWidget::draw() { if (Config::showSpecialWorkspace) workspaces.push_back(SPECIAL_WORKSPACE_START); - WORKSPACEID lowestID = std::numeric_limits::max(); WORKSPACEID highestID = 1; - for (const auto& ws : g_pCompositor->getWorkspaces()) { + for (const auto& wsRef : State::workspaceState()->workspaces()) { + const auto ws = wsRef.lock(); if (!ws || ws->m_id < 1 || !ws->m_monitor || ws->m_monitor->m_id != ownerID) continue; workspaces.push_back(ws->m_id); highestID = std::max(highestID, ws->m_id); - lowestID = std::min(lowestID, ws->m_id); } if (Config::showEmptyWorkspace) { - WORKSPACEID wsIDStart = 1; - WORKSPACEID wsIDEnd = highestID; - - if (numWorkspaces > 0) { - const auto baseWorkspace = static_cast(numWorkspaces * ownerID + 1); - wsIDStart = std::min(baseWorkspace, lowestID); - wsIDEnd = std::max(baseWorkspace, highestID); - } + const WORKSPACEID wsIDStart = 1; + const WORKSPACEID wsIDEnd = highestID; for (WORKSPACEID id = wsIDStart; id <= wsIDEnd; id++) { if (id == owner->activeSpecialWorkspaceID()) continue; - if (g_pCompositor->getWorkspaceByID(id) == nullptr) + if (workspaceByID(id) == nullptr) workspaces.push_back(id); } } if (Config::showNewWorkspace) { - while (g_pCompositor->getWorkspaceByID(highestID) != nullptr) + while (workspaceByID(highestID) != nullptr) highestID++; workspaces.push_back(highestID); } @@ -259,7 +252,7 @@ void CHyprspaceWidget::draw() { std::unordered_map windowsByWorkspace; windowsByWorkspace.reserve(workspaceCount + 2); - for (const auto& window : g_pCompositor->m_windows) { + for (const auto& window : Desktop::windowState()->windows()) { if (!window || !window->m_workspace) continue; @@ -271,7 +264,7 @@ void CHyprspaceWidget::draw() { } for (const auto wsID : workspaces) { - const auto ws = g_pCompositor->getWorkspaceByID(wsID); + const auto ws = workspaceByID(wsID); CBox box = {workspaceOffsetX, workspaceOffsetY, workspaceBoxW, workspaceBoxH}; if (ws == owner->m_activeWorkspace) { @@ -303,7 +296,7 @@ void CHyprspaceWidget::draw() { if (!layer) continue; - CBox layerBox = {box.pos() + (layer->m_realPosition->value() - owner->m_position) * monitorScaleFactor, layer->m_realSize->value() * monitorScaleFactor}; + CBox layerBox = {box.pos() + (layer->positionAnimation()->value() - owner->m_position) * monitorScaleFactor, layer->sizeAnimation()->value() * monitorScaleFactor}; renderLayerStub(layer, owner, layerBox, time); } @@ -312,7 +305,7 @@ void CHyprspaceWidget::draw() { if (!layer) continue; - CBox layerBox = {box.pos() + (layer->m_realPosition->value() - owner->m_position) * monitorScaleFactor, layer->m_realSize->value() * monitorScaleFactor}; + CBox layerBox = {box.pos() + (layer->positionAnimation()->value() - owner->m_position) * monitorScaleFactor, layer->sizeAnimation()->value() * monitorScaleFactor}; renderLayerStub(layer, owner, layerBox, time); } } @@ -330,9 +323,9 @@ void CHyprspaceWidget::draw() { if (ws) { const auto windowsIt = windowsByWorkspace.find(ws->m_id); - if (windowsIt != windowsByWorkspace.end()) { - for (const auto& window : windowsIt->second.tiled) - renderWindowPreview(window, ws, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, time); + if (windowsIt != windowsByWorkspace.end()) { + for (const auto& window : windowsIt->second.tiled) + renderWindowPreview(window, ws, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, time); const auto focused = ws->getLastFocusedWindow(); for (const auto& window : windowsIt->second.floating) { @@ -354,7 +347,7 @@ void CHyprspaceWidget::draw() { if (!layer) continue; - CBox layerBox = {box.pos() + (layer->m_realPosition->value() - owner->m_position) * monitorScaleFactor, layer->m_realSize->value() * monitorScaleFactor}; + CBox layerBox = {box.pos() + (layer->positionAnimation()->value() - owner->m_position) * monitorScaleFactor, layer->sizeAnimation()->value() * monitorScaleFactor}; renderLayerStub(layer, owner, layerBox, time); } } @@ -365,7 +358,7 @@ void CHyprspaceWidget::draw() { if (!layer) continue; - CBox layerBox = {box.pos() + (layer->m_realPosition->value() - owner->m_position) * monitorScaleFactor, layer->m_realSize->value() * monitorScaleFactor}; + CBox layerBox = {box.pos() + (layer->positionAnimation()->value() - owner->m_position) * monitorScaleFactor, layer->sizeAnimation()->value() * monitorScaleFactor}; renderLayerStub(layer, owner, layerBox, time); } } diff --git a/src/main.cpp b/src/main.cpp index e6ed9b2..c47d1c3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -73,8 +73,6 @@ float Config::swipeThreshold = 10.F; float Config::swipeClosedPadding = 10.F; float Config::workspaceScrollSpeed = 2.F; -int numWorkspaces = -1; - CHyprSignalListener g_pRenderHook; CHyprSignalListener g_pConfigReloadHook; CHyprSignalListener g_pOpenLayerHook; @@ -267,7 +265,7 @@ bool g_layoutNeedsRefresh = true; float g_oAlpha = -1; void onRender(eRenderStage renderStage) { - if (g_pCompositor->m_unsafeState) + if (compositorUnsafe()) return; if (renderStage == eRenderStage::RENDER_PRE) { @@ -312,7 +310,7 @@ void onRender(eRenderStage renderStage) { } curWindow->alpha(Desktop::View::WINDOW_ALPHA_ACTIVE)->setValueAndWarp(Config::dragAlpha); - curWindow->m_ruleApplicator->noBlur().unset(Desktop::Types::PRIORITY_SET_PROP); + curWindow->m_ruleApplicator->noBlur().set(true, Desktop::Types::PRIORITY_SET_PROP); const auto time = Time::steadyNow(); (*(tRenderWindow)pRenderWindow)(g_pHyprRenderer.get(), curWindow, widget->getOwner(), time, true, Render::RENDER_PASS_MAIN, false, false); curWindow->m_ruleApplicator->noBlur().unset(Desktop::Types::PRIORITY_SET_PROP); @@ -322,10 +320,10 @@ void onRender(eRenderStage renderStage) { } void onWorkspaceChange(PHLWORKSPACE workspace) { - if (!workspace) + if (!workspace || !workspace->m_monitor) return; - const auto widget = getWidgetForMonitor(g_pCompositor->getMonitorFromID(workspace->m_monitor->m_id)); + const auto widget = getWidgetForMonitor(monitorFromID(workspace->m_monitor->m_id)); if (widget && widget->isActive()) widget->show(); } @@ -335,7 +333,7 @@ void onMouseButton(const IPointer::SButtonEvent& event, SCallbackInfo& info) { if (!pointer || event.button != BTN_LEFT) return; - const auto monitor = g_pCompositor->getMonitorFromCursor(); + const auto monitor = monitorFromCursor(); if (!monitor) return; @@ -345,7 +343,7 @@ void onMouseButton(const IPointer::SButtonEvent& event, SCallbackInfo& info) { } void onMouseAxis(const IPointer::SAxisEvent& event, SCallbackInfo& info) { - const auto monitor = g_pCompositor->getMonitorFromCursor(); + const auto monitor = monitorFromCursor(); if (!monitor) return; @@ -358,7 +356,7 @@ void onSwipeBegin(const IPointer::SSwipeBeginEvent& event, SCallbackInfo& info) if (Config::disableGestures) return; - const auto widget = getWidgetForMonitor(g_pCompositor->getMonitorFromCursor()); + const auto widget = getWidgetForMonitor(monitorFromCursor()); if (widget) widget->beginSwipe(event); @@ -375,7 +373,7 @@ void onSwipeUpdate(const IPointer::SSwipeUpdateEvent& event, SCallbackInfo& info if (Config::disableGestures) return; - const auto widget = getWidgetForMonitor(g_pCompositor->getMonitorFromCursor()); + const auto widget = getWidgetForMonitor(monitorFromCursor()); if (widget) info.cancelled = !widget->updateSwipe(event); } @@ -384,7 +382,7 @@ void onSwipeEnd(const IPointer::SSwipeEndEvent& event, SCallbackInfo& info) { if (Config::disableGestures) return; - const auto widget = getWidgetForMonitor(g_pCompositor->getMonitorFromCursor()); + const auto widget = getWidgetForMonitor(monitorFromCursor()); if (widget) widget->endSwipe(event); } @@ -419,8 +417,8 @@ void onTouchDown(const ITouch::SDownEvent& event, SCallbackInfo& info) { if (!event.device) return; - auto targetMonitor = g_pCompositor->getMonitorFromName(!event.device->m_boundOutput.empty() ? event.device->m_boundOutput : ""); - targetMonitor = targetMonitor ? targetMonitor : g_pCompositor->getMonitorFromCursor(); + auto targetMonitor = monitorFromName(!event.device->m_boundOutput.empty() ? event.device->m_boundOutput : ""); + targetMonitor = targetMonitor ? targetMonitor : monitorFromCursor(); const auto widget = getWidgetForMonitor(targetMonitor); if (widget && targetMonitor && widget->isActive()) { @@ -428,7 +426,7 @@ void onTouchDown(const ITouch::SDownEvent& event, SCallbackInfo& info) { info.cancelled = !widget->buttonEvent(true, pos); if (info.cancelled) { g_pTouchedMonitor = targetMonitor; - g_pCompositor->warpCursorTo(pos); + Pointer::pointerController()->warpTo(pos); g_pInputManager->refocus(); } } @@ -438,7 +436,7 @@ void onTouchMove(const ITouch::SMotionEvent& event, SCallbackInfo& info) { if (!g_pTouchedMonitor) return; - g_pCompositor->warpCursorTo(g_pTouchedMonitor->m_position + g_pTouchedMonitor->m_size * event.pos); + Pointer::pointerController()->warpTo(g_pTouchedMonitor->m_position + g_pTouchedMonitor->m_size * event.pos); g_pInputManager->simulateMouseMovement(); } @@ -451,7 +449,7 @@ void onTouchUp(const ITouch::SUpEvent& event, SCallbackInfo& info) { } static SDispatchResult dispatchToggleOverview(std::string arg) { - const auto currentMonitor = g_pCompositor->getMonitorFromCursor(); + const auto currentMonitor = monitorFromCursor(); const auto widget = getWidgetForMonitor(currentMonitor); if (!widget) return {}; @@ -480,7 +478,7 @@ static SDispatchResult dispatchOpenOverview(std::string arg) { widget->show(); } } else { - const auto widget = getWidgetForMonitor(g_pCompositor->getMonitorFromCursor()); + const auto widget = getWidgetForMonitor(monitorFromCursor()); if (widget && !widget->isActive()) widget->show(); } @@ -495,7 +493,7 @@ static SDispatchResult dispatchCloseOverview(std::string arg) { widget->hide(); } } else { - const auto widget = getWidgetForMonitor(g_pCompositor->getMonitorFromCursor()); + const auto widget = getWidgetForMonitor(monitorFromCursor()); if (widget && widget->isActive()) widget->hide(); } @@ -585,8 +583,6 @@ void reloadConfig() { Config::exitKey = readStringValue(g_pluginConfigValues.exitKey, Config::exitKey); Config::clickReleaseThresholdMs = readIntValue(g_pluginConfigValues.clickReleaseThresholdMs, Config::clickReleaseThresholdMs); - numWorkspaces = -1; - for (auto& widget : g_overviewWidgets) { if (!widget) continue; @@ -599,7 +595,7 @@ void reloadConfig() { } void registerMonitors() { - for (auto& monitor : g_pCompositor->m_monitors) { + for (auto& monitor : State::monitorState()->monitors()) { if (getWidgetForMonitor(monitor)) continue; @@ -653,7 +649,7 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE inHandle) { g_pPreRemoveMonitorHook = Event::bus()->m_events.monitor.preRemoved.listen([](PHLMONITOR monitor) { teardownMonitorWidget(monitor); }); g_pRemoveMonitorHook = Event::bus()->m_events.monitor.removed.listen([](PHLMONITOR monitor) { removeMonitorWidget(monitor); }); - return {"Hyprspace", "Workspace overview", "KZdkm", "0.2"}; + return {"Hyprspace", "Workspace overview", "KZdkm", "0.3"}; } APICALL EXPORT void PLUGIN_EXIT() { @@ -677,8 +673,13 @@ APICALL EXPORT void PLUGIN_EXIT() { g_pStartHook.reset(); for (auto& widget : g_overviewWidgets) { - if (widget) - widget->cleanup(widget->getOwner()); + if (!widget) + continue; + + endSwipeIfNeeded(widget); + if (widget->isActive()) + widget->hide(); + widget->cleanup(widget->getOwner()); } g_overviewWidgets.clear(); From 8b4284e123bda4c4be9c40fea89f2abd6487aa55 Mon Sep 17 00:00:00 2001 From: 0xl30 Date: Tue, 21 Jul 2026 12:02:53 +0530 Subject: [PATCH 9/9] Fix For Hyprland v0.56.0 (ImanolBarba PR) --- .gitignore | 4 - Hyprspace.lua | 291 ------------------ Makefile | 2 +- README.md | 415 ++++++++++++------------- flake.nix | 6 +- hyprpm.toml | 6 +- meson.build | 2 +- src/Globals.hpp | 233 ++++++++------ src/Input.cpp | 302 +++++++++--------- src/Layout.cpp | 80 ++++- src/Lua.cpp | 28 ++ src/Lua.hpp | 4 + src/Overview.cpp | 245 ++++++--------- src/Overview.hpp | 18 +- src/Render.cpp | 568 ++++++++++++++++++---------------- src/main.cpp | 782 ++++++++++++++++++----------------------------- 16 files changed, 1283 insertions(+), 1703 deletions(-) delete mode 100644 Hyprspace.lua create mode 100644 src/Lua.cpp create mode 100644 src/Lua.hpp diff --git a/.gitignore b/.gitignore index e32e4a5..47f3855 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,3 @@ meson-out meson-info meson-logs meson-private - -Hyprspace.conf -log.txt -flow-chart.md \ No newline at end of file diff --git a/Hyprspace.lua b/Hyprspace.lua deleted file mode 100644 index 73d6aad..0000000 --- a/Hyprspace.lua +++ /dev/null @@ -1,291 +0,0 @@ -local M = {} - -local HOME = os.getenv("HOME") or "" -local IMPORT_COLOR = HOME .. "/.config/matugen/generated/hyprland-colors.lua" -local ENV_PLUGIN_PATH = os.getenv("HYPRSPACE_PLUGIN_PATH") -local MODULE_DIR = (debug.getinfo(1, "S").source or ""):match("^@(.+)/[^/]+$") -local LOCAL_PLUGIN_PATH = MODULE_DIR and (MODULE_DIR .. "/Hyprspace.so") or nil - -local state = { - mode = "auto", - plugin_path = nil, - hooks_registered = false, -} - -local function file_exists(path) - if type(path) ~= "string" or path == "" then - return false - end - - local file = io.open(path, "r") - if not file then - return false - end - - file:close() - return true -end - -local function shell_quote(value) - return "'" .. tostring(value):gsub("'", "'\\''") .. "'" -end - -local function load_colors(path) - local env = {} - local chunk = loadfile(path, "t", env) - if not chunk then - return {} - end - - if not pcall(chunk) then - return {} - end - - return env -end - -local function rgba_to_aarrggbb(value, fallback) - if type(fallback) ~= "number" then - return nil - end - - local hex = type(value) == "string" and value:match("^rgba%((%x%x%x%x%x%x%x%x)%)$") - if not hex then - return fallback - end - - local parsed = tonumber("0x" .. hex:sub(7, 8) .. hex:sub(1, 2) .. hex:sub(3, 4) .. hex:sub(5, 6)) - if type(parsed) ~= "number" then - return fallback - end - - return parsed -end - -local function with_alpha(value, alpha, fallback) - local base = rgba_to_aarrggbb(value, fallback) - if type(base) ~= "number" then - return fallback - end - - return ((alpha & 0xff) << 24) | (base & 0x00ffffff) -end - -local function plugin_loaded() - return hl.plugin and hl.plugin.Hyprspace and type(hl.plugin.Hyprspace.overview) == "function" -end - -local function plugin_paths() - local paths = {} - local seen = {} - - local function add(path) - if type(path) ~= "string" or path == "" or seen[path] then - return - end - - seen[path] = true - table.insert(paths, path) - end - - if state.mode == "manual" then - add(state.plugin_path) - end - - add(ENV_PLUGIN_PATH) - add(LOCAL_PLUGIN_PATH) - - return paths -end - -local function resolve_plugin_path() - for _, path in ipairs(plugin_paths()) do - if file_exists(path) then - return path - end - end - - return nil -end - -local function ensure_plugin_loaded() - if plugin_loaded() then - return true - end - - local path = resolve_plugin_path() - if path then - hl.exec_cmd("hyprctl plugin load " .. shell_quote(path)) - if plugin_loaded() then - return true - end - end - - -- hyprpm: enabled plugins may not be loaded yet when Lua config runs - hl.exec_cmd("hyprpm reload") - return plugin_loaded() -end - -local function build_config() - local colors = load_colors(IMPORT_COLOR) - - return { - plugin = { - hyprspace = { - panel_color = with_alpha(colors.surface_container_high or colors.surface, 0xd8, 0xd8261d20), - panel_border_color = with_alpha(colors.primary or colors.outline, 0x88, 0x88ffb0cf), - workspace_active_background = with_alpha(colors.surface_container_highest or colors.surface_container_high, 0xb8, 0xb83c3235), - workspace_inactive_background = with_alpha(colors.surface_container or colors.surface, 0xde, 0xde261d20), - workspace_active_border = with_alpha(colors.primary or colors.on_surface, 0xb0, 0xb0ffb0cf), - workspace_inactive_border = with_alpha(colors.outline_variant or colors.outline, 0x55, 0x55504348), - - panel_height = 220, - panel_border_width = 2, - workspace_margin = 10, - reserved_area = 35, - workspace_border_size = 1, - - adaptive_height = false, - center_aligned = true, - on_bottom = false, - hide_background_layers = false, - hide_top_layers = false, - hide_overlay_layers = false, - draw_active_workspace = true, - hide_real_layers = false, - affect_strut = false, - - auto_drag = true, - auto_scroll = true, - exit_on_click = true, - switch_on_drop = false, - exit_on_switch = false, - show_new_workspace = true, - show_empty_workspace = true, - show_special_workspace = false, - - disable_gestures = false, - reverse_swipe = false, - swipe_fingers = 3, - swipe_distance = 300, - swipe_force_speed = 30, - swipe_cancel_ratio = 0.5, - swipe_threshold = 10.0, - swipe_closed_padding = 10.0, - workspace_scroll_speed = 2.0, - - disable_blur = false, - override_anim_speed = 0.0, - drag_alpha = 0.2, - exit_key = "Escape", - click_release_threshold_ms = 200, - }, - }, - } -end - -local function apply_config() - if not plugin_loaded() then - return false - end - - hl.config(build_config()) - return true -end - -local function sync_plugin() - ensure_plugin_loaded() - return apply_config() -end - -local function dispatch_overview(action) - local command = action or "toggle" - local valid = { - toggle = true, - open = true, - close = true, - toggle_all = true, - open_all = true, - close_all = true, - } - - if not valid[command] then - return false - end - - if command:sub(-4) == "_all" then - hl.exec_cmd("hyprctl dispatch overview:" .. command:sub(1, -5) .. " all") - else - hl.exec_cmd("hyprctl dispatch overview:" .. command) - end - - return true -end - -local function schedule_sync(delay_ms, attempts) - hl.timer(function() - if sync_plugin() or attempts <= 1 then - return - end - - schedule_sync(delay_ms, attempts - 1) - end, { timeout = delay_ms, type = "oneshot" }) -end - -local function register_hooks() - if state.hooks_registered then - return - end - - hl.on("hyprland.start", function() - schedule_sync(150, 6) - end) - - hl.on("config.reloaded", function() - schedule_sync(150, 4) - end) - - state.hooks_registered = true -end - -function M.apply_config() - return sync_plugin() -end - -function M.reload() - local applied = sync_plugin() - hl.exec_cmd("hyprctl reload") - return applied -end - -function M.overview(action) - local command = action or "toggle" - - sync_plugin() - - if plugin_loaded() then - hl.plugin.Hyprspace.overview(command) - return true - end - - return dispatch_overview(command) -end - -function M.toggle() - return M.overview("toggle") -end - -function M.setup(opts) - state.mode = "auto" - state.plugin_path = nil - - if type(opts) == "table" and type(opts.plugin_path) == "string" and opts.plugin_path ~= "" then - state.mode = "manual" - state.plugin_path = opts.plugin_path - end - - register_hooks() - schedule_sync(150, 3) -end - -return M diff --git a/Makefile b/Makefile index 6e7276b..f9ff52c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CXXFLAGS=-shared -fPIC --no-gnu-unique -Wall -g -DWLR_USE_UNSTABLE -std=c++2b -O2 -INCLUDES = `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` +INCLUDES = `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon lua` SRC = $(wildcard src/*.cpp) TARGET = Hyprspace.so PREFIX ?= /usr/local diff --git a/README.md b/README.md index 329d7a0..9201aef 100644 --- a/README.md +++ b/README.md @@ -1,255 +1,218 @@ # Hyprspace -Hyprland overview / workspace-expo plugin. Renders a top or bottom workspace strip with live thumbnails; switch or move windows between workspaces. - -This fork targets Hyprland **0.56.0+** with **Lua-based configuration** (not the old `.conf` / Hyprlang plugin config flow). - -## Features - -- Live workspace overview with window thumbnails -- Multi-monitor support -- Mouse drag/drop between workspaces -- Optional gesture support -- Lua-native configuration via [Hyprspace.lua](./Hyprspace.lua) -- Manual build or `hyprpm` install - -## Requirements - -- Hyprland **0.56.0+** (Animation / State API refactor) -- Hyprland development headers / pkg-config metadata (manual builds) -- C++ compiler with C++23 support - -## Setup - -Pick **one** load method. Do not enable `hyprpm` and also `hyprctl plugin load` the same plugin twice. - -### 1. Local build (recommended for this repo) - -Clone or keep the repo next to your Hyprland config, then build: - -```bash -git clone https://github.com/0xl30/Hyprspace.git ~/.config/hypr/Hyprspace -cd ~/.config/hypr/Hyprspace -make all -``` - -Produces `Hyprspace.so` in that directory (same folder as `Hyprspace.lua`). - -**`hyprland.lua`** (or your overlay entrypoint) — add the module search path: - -```lua -package.path = package.path .. ";" .. HOME .. "/.config/hypr/Hyprspace/?.lua" -``` - -Use `?.lua`, not the full `Hyprspace.lua` filename. Lua replaces `?` with the module name for `require("Hyprspace")`. - -**`plugins.lua`:** - -```lua -local hyprspace = require("Hyprspace") -hyprspace.setup() -``` - -Optional explicit path (manual mode — only this path is tried): - -```lua -hyprspace.setup({ - plugin_path = HOME .. "/.config/hypr/Hyprspace/Hyprspace.so", -}) +A plugin for Hyprland that implements a workspace overview feature similar to that of KDE Plasma, GNOME and macOS, aimed to provide a efficient way of workspace and window management. + +> [!NOTE] +> This plugin is still maintained, by combined efforts of me and all the awesome contributors. However, I do not have as much time that I could spend on this plugin as before, and Hyprland is a rapidly changing codebase. Therefore, at this time, I could not guarantee that new issues could be resolved promptly. I appreciate your acknowledgement and support for this project! +> +> P.S.: I could recommend giving [niri](https://github.com/YaLTeR/niri) a try for anyone who is considering an alternative to Hyprland. It is a scrolling window manager that offers a great built-in overview feature that allows dragging windows in-between workspaces just like this plugin does. It also has better workspace management as it cleans up empty workspaces like GNOME does. + +https://github.com/KZDKM/Hyprspace/assets/41317840/ed1a585a-30d5-4a79-a6da-8cc0713828f9 + +### [Jump to installation](#installation) + +## Plugin Compatibility +- [x] [hyprsplit](https://github.com/shezdy/hyprsplit) (tested, explicit support) +- [x] [split-monitor-workspaces](https://github.com/Duckonaut/split-monitor-workspaces) (tested, explicit support) +- [x] [hyprexpo](https://github.com/hyprwm/hyprland-plugins/tree/main/hyprexpo) (tested, minor bugs) +- [x] Any layout plugin (except ones that override Hyprland's workspace management) + +## Roadmap +- [x] Overview interface + - [x] Workspace minimap + - [x] Workspace display +- [ ] Mouse controls + - [x] Moving window between workspaces + - [x] Creating new workspaces + - [ ] Dragging windows between workspace views +- [ ] Configurability + - [ ] Styling + - [x] Panel background + - [x] Workspace background & border + - [x] Panel on Bottom + - [ ] Vertical layout (on left / right) + - [x] Panel top padding (reserved for bar / notch) + - [x] Panel border (color / thickness) + - [ ] Unique styling for special workspaces + - [x] Behavior + - [x] Autodrag windows + - [x] Autoscroll workspaces + - [x] Responsive workspace switching + - [x] Responsive exiting + - [x] Exit on click / switch + - [x] Exit with escape key + - [x] Blacklisting workspaces + - [x] Show / hide new workspace and empty workspaces + - [x] Show / hide special workspace (#11) +- [x] Animation support +- [x] Multi-monitor support (tested) +- [x] Monitor scaling support (tested) +- [x] aarch64 support (No function hook used) +- [x] Touchpad & gesture support + - [x] Workspace swipe (#9) + - [x] Scrolling through workspace panel + - [x] Swipe to open + +## Installation + +### Manual + +To build, have hyprland headers installed and under the repo directory do: ``` - -**Keybinds** (e.g. in `keybinds.lua`): - -```lua -local hyprspace = require("Hyprspace") - -hl.unbind("SUPER + A") -hl.bind("SUPER + A", function() - hyprspace.toggle() -end) +make all ``` +Then use `hyprctl plugin load` followed by the absolute path to the `.so` file to load, you could add this to your `exec-once` to load the plugin on startup -Reload: - -```bash -hyprctl reload +### Hyprpm ``` - -### 2. `hyprpm` - -```bash -hyprpm add https://github.com/0xl30/Hyprspace.git +hyprpm add https://github.com/KZDKM/Hyprspace hyprpm enable Hyprspace -hyprpm reload ``` -Still add `package.path` (same as above) so `require("Hyprspace")` finds the Lua helper. - -**`plugins.lua`:** - -```lua -local hyprspace = require("Hyprspace") - -hyprspace.setup() -``` - -`setup()` does **not** scan hyprpm’s install tree for a `.so` path. It: - -1. Uses `plugin_path` if you passed one (manual mode) -2. Else `HYPRSPACE_PLUGIN_PATH` if set -3. Else `Hyprspace.so` next to `Hyprspace.lua` -4. Else runs `hyprpm reload` and checks whether `hl.plugin.Hyprspace` is available - -After Hyprland or plugin updates: - -```bash -hyprpm update -hyprpm reload -hyprctl reload -``` - -### Environment variable - -Override the `.so` location without editing Lua: - -```bash -export HYPRSPACE_PLUGIN_PATH="$HOME/.config/hypr/Hyprspace/Hyprspace.so" +### Nix +Refer to the [Hyprland wiki](https://wiki.hyprland.org/Nix/Hyprland-on-Home-Manager/#plugins) on plugins, but your flake might look like this: +```nix +{ + inputs = { + # Hyprland is **such** eye candy + hyprland = { + type = "git"; + url = "https://github.com/hyprwm/Hyprland"; + submodules = true; + inputs.nixpkgs.follows = "nixpkgs"; + }; + Hyprspace = { + url = "github:KZDKM/Hyprspace"; + + # Hyprspace uses latest Hyprland. We declare this to keep them in sync. + inputs.hyprland.follows = "hyprland"; + }; + }; + +... # your normal setup with hyprland + + wayland.windowManager.hyprland.plugins = [ + # ... whatever + inputs.Hyprspace.packages.${pkgs.system}.Hyprspace + ]; +} ``` ## Usage -### Lua API - -```lua -local hyprspace = require("Hyprspace") - -hyprspace.setup() -hyprspace.apply_config() -hyprspace.toggle() -hyprspace.overview("open") -hyprspace.overview("close") -hyprspace.reload() -``` - -### Plugin API (after load) - -```lua -hl.plugin.Hyprspace.overview("toggle") -hl.plugin.Hyprspace.overview("open") -hl.plugin.Hyprspace.overview("close") -``` +### Opening Overview +- Bind the `overview:toggle` or perform a workspace swipe vertically would open / close the panel. +### Interaction +- Window management: + - Click on workspace to change to it + - Click on a window to drag it + - Drag a window into a workspace would move the window to the workspace +- Exiting + - Click without dragging the window exits the overview + - Pressing ESC exits the overview +- Navigating + - When there are many workspaces open, scroll / swipe on the panel to pan through opened workspaces ## Configuration +### Dispatchers +- Use `overview:toggle` dispatcher to toggle workspace overview on current monitor +- Use `overview:close` to close the overview on current monitor if opened +- Use `overview:open` to open the overview on current monitor if closed +- Adding the `all` argument to these dispatchers would toggle / open / close overview on all monitors -Defaults and Matugen colors live in [Hyprspace.lua](./Hyprspace.lua). The helper reads: +### Lua Configuration (Hyprland 0.55+) -```text -~/.config/matugen/generated/hyprland-colors.lua -``` - -If missing, built-in fallback colors are used. Matugen `rgba(rrggbbaa)` values are converted to the integer format the plugin expects. +Starting with Hyprland 0.55+, the configuration format has transitioned to Lua as the default (`~/.config/hypr/hyprland.lua`). -Applied via `hl.config({ plugin = { hyprspace = { ... } } })`. Example: +#### 1. Setting Config Options +Plugin settings are structured nested within the `hl.config()` table under `plugin.overview`. ```lua -plugin = { - hyprspace = { - panel_height = 220, - panel_border_width = 2, - workspace_margin = 10, - reserved_area = 35, - workspace_border_size = 1, - - center_aligned = true, - on_bottom = false, - draw_active_workspace = true, - hide_real_layers = false, - affect_strut = false, - - auto_drag = true, - auto_scroll = true, - exit_on_click = true, - exit_on_switch = false, - - disable_gestures = false, - swipe_fingers = 3, - swipe_distance = 300, - swipe_force_speed = 30, - swipe_cancel_ratio = 0.5, - click_release_threshold_ms = 200, - }, -} -``` - -### Main options - -#### Colors - -- `panel_color`, `panel_border_color` -- `workspace_active_background`, `workspace_inactive_background` -- `workspace_active_border`, `workspace_inactive_border` -- `drag_alpha`, `disable_blur` - -#### Layout - -- `panel_height`, `panel_border_width`, `workspace_margin`, `reserved_area`, `workspace_border_size` -- `adaptive_height`, `center_aligned`, `on_bottom` -- `hide_background_layers`, `hide_top_layers`, `hide_overlay_layers` -- `draw_active_workspace`, `hide_real_layers`, `affect_strut` - -#### Behavior - -- `auto_drag`, `auto_scroll`, `exit_on_click`, `switch_on_drop`, `exit_on_switch` -- `show_new_workspace`, `show_empty_workspace`, `show_special_workspace`, `exit_key` - -#### Gestures and input - -- `disable_gestures`, `reverse_swipe`, `swipe_fingers`, `swipe_distance`, `swipe_force_speed` -- `swipe_cancel_ratio`, `swipe_threshold`, `swipe_closed_padding`, `workspace_scroll_speed` -- `click_release_threshold_ms` - -#### Animation - -- `override_anim_speed` - -## Troubleshooting - -### Keybind does nothing - -```bash -hyprctl plugin list +hl.config({ + plugin = { + overview = { + panelColor = "#1e1e2e", + panelBorderColor = "#cba6f7", + workspaceActiveBackground = "rgba(49, 50, 68, 0.8)", + workspaceActiveBorder = "rgb(203, 166, 247)", + onBottom = false, + autoDrag = true, + -- Add other overview settings here + } + } +}) ``` -Expect `Plugin Hyprspace`. If empty: - -- Local: `make all` in the Hyprspace repo, then `hyprctl reload` -- hyprpm: `hyprpm reload`, then `hyprctl reload` -- Ensure `plugins.lua` calls `hyprspace.setup()` (not `hyprspace.setup(auto)` — `auto` is not a valid argument) +#### 2. Using Lua Dispatchers +To invoke dispatchers, Hyprspace registers Lua wrapper functions in the `hl.plugin.overview` namespace. These can be bound in your configuration, run inside callbacks, or executed from the command line. -### Lua colors / settings not applied +The following Lua functions are available: +* `hl.plugin.overview.toggle(arg)`: Toggles the overview on the current monitor. +* `hl.plugin.overview.open(arg)`: Opens the overview on the current monitor. +* `hl.plugin.overview.close(arg)`: Closes the overview on the current monitor. -`plugins.lua` must call `hyprspace.setup()` or `hyprspace.setup({ plugin_path = "..." })` so hooks run and `hl.config(...)` is applied after the plugin loads. +#### 3. Examples -### `require("Hyprspace")` fails - -Add to your Hyprland Lua entrypoint: +##### Keybindings in `hyprland.lua`: +When binding Lua wrapper functions using `hl.bind`, you must wrap the call in a function definition: ```lua -package.path = package.path .. ";" .. HOME .. "/.config/hypr/Hyprspace/?.lua" -``` - -Do **not** append `Hyprspace/Hyprspace.lua` directly — use `?.lua`. - -### `hyprpm enable` fails - -- Correct repo URL -- `hyprpm.toml` has a commit pin for your Hyprland version (for 0.56.0, pin after the State/Animation API port is committed) -- Plugin builds cleanly on that commit (`hyprpm update` / rebuild) +-- Toggle overview on the current monitor using SUPER + TAB +hl.bind("SUPER + TAB", function() + hl.plugin.overview.toggle() +end, { description = "Toggle Hyprspace overview" }) -### Build fails: missing `AnimationManager.hpp` - -On Hyprland 0.56+, animation headers moved from `managers/animation/` to `animation/`, and compositor helpers moved into `State::` / `Desktop::` / `Fullscreen::` trackers. Rebuild this fork against matching headers (`pkg-config --modversion hyprland`). - -### Plugin crashes on load +-- Toggle overview on all monitors +hl.bind("SUPER + SHIFT + TAB", function() + hl.plugin.overview.toggle("all") +end, { description = "Toggle Hyprspace overview on all monitors" }) +``` -Check `~/.cache/hyprland/` crash reports. Rebuild the plugin against the same Hyprland version you are running (`hyprctl version`). +### Styling +#### Colors +- `plugin:overview:panelColor` +- `plugin:overview:panelBorderColor` +- `plugin:overview:workspaceActiveBackground` +- `plugin:overview:workspaceInactiveBackground` +- `plugin:overview:workspaceActiveBorder` +- `plugin:overview:workspaceInactiveBorder` +- `plugin:overview:dragAlpha` overrides the alpha of window when dragged in overview (0 - 1, 0 = transparent, 1 = opaque) +- `plugin:overview:disableBlur` +#### Layout +- `plugin:overview:panelHeight` +- `plugin:overview:panelBorderWidth` +- `plugin:overview:onBottom` whether if panel should be on bottom instead of top +- `plugin:overview:workspaceMargin` spacing of workspaces with eachother and the edge of the panel +- `plugin:overview:reservedArea` padding on top of the panel, for Macbook camera notch +- `plugin:overview:workspaceBorderSize` +- `plugin:overview:centerAligned` whether if workspaces should be aligned at the center (KDE / macOS style) or at the left (Windows style) +- `plugin:overview:hideBackgroundLayers` do not draw background and bottom layers in overview +- `plugin:overview:hideTopLayers` do not draw top layers in overview +- `plugin:overview:hideOverlayLayers` do not draw overlay layers in overview +- `plugin:overview:hideRealLayers` whether to hide layers in actual workspace +- `plugin:overview:drawActiveWorkspace` draw the active workspace in overview as-is +- `plugin:overview:overrideGaps` whether if overview should override the layout gaps in the current workspace using the following values +- `plugin:overview:gapsIn` +- `plugin:overview:gapsOut` +- `plugin:overview:affectStrut` whether the panel should push window aside, disabling this option also disables `overrideGaps` + +### Animation +- The panel uses the `windows` curve for a slide-in animation +- Use `plugin:overview:overrideAnimSpeed` to override the animation speed + +### Behaviors +- `plugin:overview:autoDrag` mouse click always drags window when overview is open +- `plugin:overview:autoScroll` mouse scroll on active workspace area always switch workspace +- `plugin:overview:exitOnClick` mouse click without dragging exits overview +- `plugin:overview:switchOnDrop` switch to the workspace when a window is droppped into it +- `plugin:overview:exitOnSwitch` overview exits when overview is switched by clicking on workspace view or by `switchOnDrop` +- `plugin:overview:showNewWorkspace` add a new empty workspace at the end of workspaces view +- `plugin:overview:showEmptyWorkspace` show empty workspaces that are inbetween non-empty workspaces +- `plugin:overview:showSpecialWorkspace` defaults to false +- `plugin:overview:disableGestures` +- `plugin:overview:reverseSwipe` reverses the direction of swipe gesture, for macOS peeps? +- `plugin:overview:exitKey` key used to exit overview mode (default: Escape). Leave empty to disable keyboard exit. +- Touchpad gesture behavior follows Hyprland workspace swipe behavior + - `gestures:workspace_swipe_fingers` + - `gestures:workspace_swipe_cancel_ratio` + - `gestures:workspace_swipe_min_speed_to_force` diff --git a/flake.nix b/flake.nix index 431e6b3..405126a 100644 --- a/flake.nix +++ b/flake.nix @@ -14,8 +14,6 @@ ref = "v0.56.0"; inputs.systems.follows = "systems"; }; - # After changing the Hyprland ref, refresh the lock with: - # nix flake update hyprland }; outputs = { @@ -52,7 +50,7 @@ Hyprspace = let hyprlandPkg = hyprland.packages.${system}.hyprland; in - pkgs.gcc16Stdenv.mkDerivation { + pkgs.gcc14Stdenv.mkDerivation { pname = "Hyprspace"; inherit version; src = ./.; @@ -80,7 +78,7 @@ devShells = perSystem (system: pkgs: { default = pkgs.mkShell { name = "Hyprspace-shell"; - nativeBuildInputs = with pkgs; [gcc16 clang-tools bear]; + nativeBuildInputs = with pkgs; [gcc14 clang-tools bear]; buildInputs = [hyprland.packages.${system}.hyprland]; inputsFrom = [ hyprland.packages.${system}.hyprland diff --git a/hyprpm.toml b/hyprpm.toml index e8fb6fb..6aae4f3 100644 --- a/hyprpm.toml +++ b/hyprpm.toml @@ -19,9 +19,9 @@ commit_pins = [ ["254fc2bc6000075f660b4b8ed818a6af544d1d64", "49ef4c37c6f84dcbf7fb641bad3d60703a2bb068"], # 0.46.1 ["0bd541f2fd902dbfa04c3ea2ccf679395e316887", "cd58d2e47b575c66c2682436ba425ccdc8462998"], # 0.46.2 ["4e242d086e20b32951fdc0ebcbfb4d41b5be8dcc", "0a82e3724f929de8ad8fb04d2b7fa128493f24f7"], # 0.50.1 - ["39d7e209c79d451efab1b21151d5938289da838d", "f4b2c2537b2c02a477f17e3a684d4b542d82537b"], # 0.55.2 - # 0.56.0 — pin plugin commit after publishing the State/Animation API port: - # ["36b2e0cfe0c6094dbc47bd42a437431315bb3087", ""], # 0.56.0 + ["a0136d8c04687bb36eb8a28eb9d1ff92aea99704", "3624878a7b6c00dfa77e351438fa209df99ad81d"], # 0.55.4 + ["6a8eb0490ce3ff3cbf9b00815f7a215e895d87e0", "777d73b09c7537548288128f90dff41bb8a79888"], # 0.55.4 + ["36b2e0cfe0c6094dbc47bd42a437431315bb3087", "45d65ec8cfa1a916dece34bc2f9117a01eb4b6f3"], # 0.56.0 ] [Hyprspace] diff --git a/meson.build b/meson.build index 7dc9253..94a4cbe 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('Hyprspace', 'cpp', - version: '0.3', + version: '0.1', default_options: ['buildtype=release'], ) diff --git a/src/Globals.hpp b/src/Globals.hpp index b9c1a11..336e1fa 100644 --- a/src/Globals.hpp +++ b/src/Globals.hpp @@ -1,8 +1,6 @@ #pragma once #include -#include -#include #include #include @@ -10,22 +8,20 @@ #include #include -#include #include #include #include -#include #include +#include #include +#include +#include #include -#include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include // Hyprland v0.54+: cancellable input uses Event::SCallbackInfo (not legacy CEvent*). using SCallbackInfo = Event::SCallbackInfo; @@ -47,92 +43,145 @@ CHyprSignalListener listenCancellable(Signal& signal, std::functionquery().id(id).run(); -} - -inline PHLMONITOR monitorFromName(std::string_view name) { - if (name.empty()) - return nullptr; - return State::monitorState()->query().name(name).run(); -} - -inline PHLMONITOR monitorFromCursor() { - return State::monitorState()->query().vec(g_pInputManager->getMouseCoordsInternal()).run(); -} - -inline PHLWORKSPACE workspaceByID(WORKSPACEID id) { - return State::workspaceState()->query().id(id).run(); -} - -inline PHLWORKSPACE createWorkspace(WORKSPACEID id, MONITORID monid) { - return State::workspaceState()->create(id, monid); -} - -inline bool compositorUnsafe() { - return !g_pCompositor || !g_pCompositor->m_sessionActive || State::monitorState()->monitors().empty(); -} - inline HANDLE pHandle = NULL; typedef void (*tRenderWindow)(void*, PHLWINDOW, PHLMONITOR, const Time::steady_tp&, bool, Render::eRenderPassMode, bool, bool); -typedef void (*tRenderLayer)(void*, PHLLS, PHLMONITOR, const Time::steady_tp&, bool, bool); extern void* pRenderWindow; +typedef void (*tRenderLayer)(void*, PHLLS, PHLMONITOR, const Time::steady_tp&, bool, bool); extern void* pRenderLayer; -namespace Config { - extern CHyprColor panelBaseColor; - extern CHyprColor panelBorderColor; - extern CHyprColor workspaceActiveBackground; - extern CHyprColor workspaceInactiveBackground; - extern CHyprColor workspaceActiveBorder; - extern CHyprColor workspaceInactiveBorder; - - extern int panelHeight; - extern int panelBorderWidth; - extern int workspaceMargin; - extern int reservedArea; - extern int workspaceBorderSize; - extern bool adaptiveHeight; - extern bool centerAligned; - extern bool onBottom; - extern bool hideBackgroundLayers; - extern bool hideTopLayers; - extern bool hideOverlayLayers; - extern bool drawActiveWorkspace; - extern bool hideRealLayers; - extern bool affectStrut; - - extern bool autoDrag; - extern bool autoScroll; - extern bool exitOnClick; - extern bool switchOnDrop; - extern bool exitOnSwitch; - extern bool showNewWorkspace; - extern bool showEmptyWorkspace; - extern bool showSpecialWorkspace; - - extern bool disableGestures; - extern bool reverseSwipe; - - extern bool disableBlur; - extern float overrideAnimSpeed; - extern float dragAlpha; - extern std::string exitKey; - - extern int clickReleaseThresholdMs; - extern int swipeFingers; - extern int swipeDistance; - extern int swipeForceSpeed; - extern float swipeCancelRatio; - extern float swipeThreshold; - extern float swipeClosedPadding; - extern float workspaceScrollSpeed; -} -inline double panelTravel(PHLMONITOR owner) { - if (!owner) - return 0.; +struct SConfig { + SP panelBaseColor; + SP panelBorderColor; + SP workspaceActiveBackground; + SP workspaceInactiveBackground; + SP workspaceActiveBorder; + SP workspaceInactiveBorder; + + SP panelHeight; + SP panelBorderWidth; + SP workspaceMargin; + SP workspaceBorderSize; + SP reservedArea; + SP adaptiveHeight; + SP centerAligned; + SP onBottom; + SP hideBackgroundLayers; + SP hideTopLayers; + SP hideOverlayLayers; + SP drawActiveWorkspace; + SP hideRealLayers; + SP affectStrut; + + SP overrideGaps; + SP gapsIn; + SP gapsOut; + + SP autoDrag; + SP autoScroll; + SP exitOnClick; + SP switchOnDrop; + SP exitOnSwitch; + SP showNewWorkspace; + SP showEmptyWorkspace; + SP showSpecialWorkspace; + + SP disableGestures; + SP reverseSwipe; + + SP disableBlur; + SP overrideAnimSpeed; + SP dragAlpha; + SP exitKey; +}; + +inline SConfig config = { + .panelBaseColor = makeShared("plugin:overview:panelColor", "description", CHyprColor(0, 0, 0, 0).getAsHex()), + .panelBorderColor = makeShared("plugin:overview:panelBorderColor", "description", CHyprColor(0, 0, 0, 0).getAsHex()), + .workspaceActiveBackground = makeShared("plugin:overview:workspaceActiveBackground", "description", CHyprColor(0, 0, 0, 0.25).getAsHex()), + .workspaceInactiveBackground = makeShared("plugin:overview:workspaceInactiveBackground", "description", CHyprColor(0, 0, 0, 0.5).getAsHex()), + .workspaceActiveBorder = makeShared("plugin:overview:workspaceActiveBorder", "description", CHyprColor(1, 1, 1, 0.25).getAsHex()), + .workspaceInactiveBorder = makeShared("plugin:overview:workspaceInactiveBorder", "description", CHyprColor(1, 1, 1, 0).getAsHex()), + + .panelHeight = makeShared("plugin:overview:panelHeight", "description", 250), + .panelBorderWidth = makeShared("plugin:overview:panelBorderWidth", "description", 2), + .workspaceMargin = makeShared("plugin:overview:workspaceMargin", "description", 12), + .workspaceBorderSize = makeShared("plugin:overview:workspaceBorderSize", "description", 1), + .reservedArea = makeShared("plugin:overview:reservedArea", "description", 0), + .adaptiveHeight = makeShared("plugin:overview:adaptiveHeight", "description", 0), // TODO: implement + .centerAligned = makeShared("plugin:overview:centerAligned", "description", 1), + .onBottom = makeShared("plugin:overview:onBottom", "description", 0), // TODO: implement + .hideBackgroundLayers = makeShared("plugin:overview:hideBackgroundLayers", "description", 0), + .hideTopLayers = makeShared("plugin:overview:hideTopLayers", "description", 0), + .hideOverlayLayers = makeShared("plugin:overview:hideOverlayLayers", "description", 0), + .drawActiveWorkspace = makeShared("plugin:overview:drawActiveWorkspace", "description", 1), + .hideRealLayers = makeShared("plugin:overview:hideRealLayers", "description", 1), + .affectStrut = makeShared("plugin:overview:affectStrut", "description", 1), + + .overrideGaps = makeShared("plugin:overview:overrideGaps", "description", 1), + .gapsIn = makeShared("plugin:overview:gapsIn", "description", 20), + .gapsOut = makeShared("plugin:overview:gapsOut", "description", 60), + + .autoDrag = makeShared("plugin:overview:autoDrag", "description", 1), + .autoScroll = makeShared("plugin:overview:autoScroll", "description", 1), + .exitOnClick = makeShared("plugin:overview:exitOnClick", "description", 1), + .switchOnDrop = makeShared("plugin:overview:switchOnDrop", "description", 0), + .exitOnSwitch = makeShared("plugin:overview:exitOnSwitch", "description", 0), + .showNewWorkspace = makeShared("plugin:overview:showNewWorkspace", "description", 1), + .showEmptyWorkspace = makeShared("plugin:overview:showEmptyWorkspace", "description", 1), + .showSpecialWorkspace = makeShared("plugin:overview:showSpecialWorkspace", "description", 0), + + .disableGestures = makeShared("plugin:overview:disableGestures", "description", 1), + .reverseSwipe = makeShared("plugin:overview:reverseSwipe", "description", 0), + + .disableBlur = makeShared("plugin:overview:disableBlur", "description", 0), + .overrideAnimSpeed = makeShared("plugin:overview:overrideAnimSpeed", "description", 0.0), + .dragAlpha = makeShared("plugin:overview:dragAlpha", "description", 0.2), + .exitKey = makeShared("plugin:overview:exitKey", "description", "Escape") +}; + +namespace HyprConfig { + inline std::optional getIntegerSafe(const std::string& name) { + auto reply = Config::mgr()->getConfigValue(name); + if (reply.dataptr && reply.type) { + if (*reply.type == typeid(Config::INTEGER)) { + return **reinterpret_cast(reply.dataptr); + } + } + return std::nullopt; + } + + inline std::optional getFloatSafe(const std::string& name) { + auto reply = Config::mgr()->getConfigValue(name); + if (reply.dataptr && reply.type) { + if (*reply.type == typeid(Config::FLOAT)) { + return **reinterpret_cast(reply.dataptr); + } + } + return std::nullopt; + } + + inline std::optional getWorkspaceSwipeDistance() { + return getIntegerSafe("gestures:workspace_swipe_distance"); + } + inline std::optional getWorkspaceSwipeMinSpeedToForce() { + return getIntegerSafe("gestures:workspace_swipe_min_speed_to_force"); + } + inline std::optional getWorkspaceSwipeCancelRatio() { + return getFloatSafe("gestures:workspace_swipe_cancel_ratio"); + } + inline std::optional getHyprsplitNumWorkspaces() { + return getIntegerSafe("plugin:hyprsplit:num_workspaces"); + } + inline std::optional getSplitMonitorWorkspacesCount() { + return getIntegerSafe("plugin:split-monitor-workspaces:count"); + } +} - return (Config::panelHeight + Config::reservedArea) * owner->m_scale; +namespace Dispatchers { + SDispatchResult dispatchToggleOverview(std::string arg); + SDispatchResult dispatchOpenOverview(std::string arg); + SDispatchResult dispatchCloseOverview(std::string arg); } + +extern int numWorkspaces; diff --git a/src/Input.cpp b/src/Input.cpp index 06c189f..0db1b50 100644 --- a/src/Input.cpp +++ b/src/Input.cpp @@ -1,122 +1,121 @@ -#include "Overview.hpp" -#include "Globals.hpp" - -#include -#include - #include +#include +#include +#include -namespace { - -double swipeClosedOffset() { - return -Config::swipeClosedPadding; -} - -double swipeVisibleThreshold() { - return Config::swipeThreshold; -} - -} // namespace +#include "Overview.hpp" +#include "Globals.hpp" bool CHyprspaceWidget::buttonEvent(bool pressed, Vector2D coords) { - const auto owner = getOwner(); - if (!owner || !animationsOk()) - return true; + bool Return; - const auto dragTarget = g_layoutManager->dragController()->target(); + const auto dragTarget = g_layoutManager->dragController()->target(); const auto targetWindow = dragTarget ? dragTarget->window() : nullptr; + // this is for click to exit, we set a timeout for button release bool couldExit = false; - if (pressed) { + if (pressed) lastPressedTime = std::chrono::high_resolution_clock::now(); - } else if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - lastPressedTime).count() < Config::clickReleaseThresholdMs) { - couldExit = true; + else + if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - lastPressedTime).count() < 200) + couldExit = true; + + int targetWorkspaceID = SPECIAL_WORKSPACE_START - 1; + + // find which workspace the mouse hovers over + for (auto& w : workspaceBoxes) { + auto wi = std::get<0>(w); + auto wb = std::get<1>(w); + if (wb.containsPoint(coords)) { + targetWorkspaceID = wi; + break; + } } - WORKSPACEID targetWorkspaceID = SPECIAL_WORKSPACE_START - 1; - - for (const auto& [workspaceID, workspaceBox] : workspaceBoxes) { - if (!workspaceBox.containsPoint(coords)) - continue; + auto targetWorkspace = State::workspaceState()->query().id(targetWorkspaceID).run(); - targetWorkspaceID = workspaceID; - break; + // create new workspace + if (targetWorkspace == nullptr && targetWorkspaceID >= SPECIAL_WORKSPACE_START) { + targetWorkspace = State::workspaceState()->create(targetWorkspaceID, getOwner()->m_id); } - auto targetWorkspace = workspaceByID(targetWorkspaceID); - if (!targetWorkspace && targetWorkspaceID >= SPECIAL_WORKSPACE_START) - targetWorkspace = createWorkspace(targetWorkspaceID, owner->m_id); - - if (Config::autoDrag && (targetWorkspace == nullptr || !pressed)) { + // if the cursor is hovering over workspace, clicking should switch workspace instead of starting window drag + if (config.autoDrag->value() && (targetWorkspace == nullptr || !pressed)) { if (g_layoutManager->dragController()->target()) g_layoutManager->endDragTarget(); if (pressed) { - const auto window = Desktop::viewState()->hitTest().windowAt(coords, Desktop::View::WINDOW_ONLY); - if (window) { - const auto target = window->layoutTarget(); - if (target) - g_layoutManager->beginDragTarget(target, MBIND_MOVE); + const auto PWINDOW = Desktop::viewState()->hitTest().windowAt(coords, Desktop::View::WINDOW_ONLY, nullptr); + if (PWINDOW) { + const auto LT = PWINDOW->layoutTarget(); + if (LT) + g_layoutManager->beginDragTarget(LT, MBIND_MOVE); } } } + Return = false; + // release window on workspace to drop it in if (targetWindow && targetWorkspace != nullptr && !pressed) { Desktop::globalWindowController()->moveWindowToWorkspace(targetWindow, targetWorkspace); if (targetWindow->m_isFloating) { - const auto targetPos = owner->m_position + (owner->m_size / 2.) - (targetWindow->m_reportedSize / 2.); - targetWindow->move(targetPos); - targetWindow->positionAnimation()->setValueAndWarp(targetPos); + auto targetPos = getOwner()->m_position + (getOwner()->m_size / 2.) - (targetWindow->m_reportedSize / 2.); + targetWindow->layoutBox().pos() = targetPos; + *targetWindow->positionAnimation() = targetPos; } - - if (Config::switchOnDrop) { - if (const auto monitor = monitorFromID(targetWorkspace->m_monitor->m_id)) - monitor->changeWorkspace(targetWorkspace->m_id); - if (Config::exitOnSwitch && active) - hide(); + if (config.switchOnDrop->value()) { + State::monitorState()->query().id(targetWorkspace->m_monitor->m_id).run()->changeWorkspace(targetWorkspace->m_id); + if (config.exitOnSwitch->value() && active) hide(); } - updateLayout(); - } else if (targetWorkspace && !pressed) { - if (targetWorkspace->m_isSpecialWorkspace) { - owner->activeSpecialWorkspaceID() == targetWorkspaceID ? owner->setSpecialWorkspace(nullptr) : owner->setSpecialWorkspace(targetWorkspaceID); - } else if (const auto monitor = monitorFromID(targetWorkspace->m_monitor->m_id)) { - monitor->changeWorkspace(targetWorkspace->m_id); + } + // click workspace to change to workspace and exit overview + else if (targetWorkspace && !pressed) { + if (targetWorkspace->m_isSpecialWorkspace) + getOwner()->activeSpecialWorkspaceID() == targetWorkspaceID ? getOwner()->setSpecialWorkspace(nullptr) : getOwner()->setSpecialWorkspace(targetWorkspaceID); + else { + State::monitorState()->query().id(targetWorkspace->m_monitor->m_id).run()->changeWorkspace(targetWorkspace->m_id); } - - if (Config::exitOnSwitch && active) - hide(); - } else if (Config::exitOnClick && targetWorkspace == nullptr && active && couldExit && !pressed) { - hide(); + if (config.exitOnSwitch->value() && active) hide(); } + // click elsewhere to exit overview + else if (config.exitOnClick->value() && targetWorkspace == nullptr && active && couldExit && !pressed) hide(); - // While overview is active all left-click input is consumed by the panel. - return false; + return Return; } bool CHyprspaceWidget::axisEvent(double delta, wl_pointer_axis axis, Vector2D coords) { - const auto owner = getOwner(); - if (!owner || !animationsOk()) - return true; - const auto travel = panelTravel(owner); - CBox panelBox = {owner->m_position.x, owner->m_position.y - curYOffset->value(), owner->m_transformedSize.x, travel}; - if (Config::onBottom) - panelBox = {owner->m_position.x, owner->m_position.y + owner->m_transformedSize.y - travel + curYOffset->value(), owner->m_transformedSize.x, travel}; + const auto owner = getOwner(); + CBox widgetBox = {owner->m_position.x, owner->m_position.y - curYOffset->value(), owner->m_transformedSize.x, (config.panelHeight->value() + config.reservedArea->value()) * owner->m_scale}; + if (config.onBottom->value()) widgetBox = {owner->m_position.x, owner->m_position.y + owner->m_transformedSize.y - ((config.panelHeight->value() + config.reservedArea->value()) * owner->m_scale) + curYOffset->value(), owner->m_transformedSize.x, (config.panelHeight->value() + config.reservedArea->value()) * owner->m_scale}; - if (panelBox.containsPoint(coords * owner->m_scale)) { + // scroll through panel if cursor is on it + if (widgetBox.containsPoint(coords * getOwner()->m_scale)) { + // only horizontal scroll pans the panel; ignore vertical scroll here if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) - *workspaceScrollOffset = workspaceScrollOffset->goal() - delta * Config::workspaceScrollSpeed; - } else if (Config::autoScroll && axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { - const auto relative = delta < 0 ? "r-1" : "r+1"; - const auto wsIDName = getWorkspaceIDNameFromString(relative); - if (!workspaceByID(wsIDName.id)) - (void)createWorkspace(wsIDName.id, ownerID); - - owner->changeWorkspace(wsIDName.id); + *workspaceScrollOffset = workspaceScrollOffset->goal() - delta * 2; + } + // otherwise, scroll to switch active workspace (vertical scroll only) + else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + if (delta < 0) { + SWorkspaceIDName wsIDName = getWorkspaceIDNameFromString("r-1"); + if (State::workspaceState()->query().id(wsIDName.id).run() == nullptr) { + auto newWorkspace = State::workspaceState()->create(wsIDName.id, ownerID); + (void)newWorkspace; + } + getOwner()->changeWorkspace(wsIDName.id); + } + else { + SWorkspaceIDName wsIDName = getWorkspaceIDNameFromString("r+1"); + if (State::workspaceState()->query().id(wsIDName.id).run() == nullptr) { + auto newWorkspace = State::workspaceState()->create(wsIDName.id, ownerID); + (void)newWorkspace; + } + getOwner()->changeWorkspace(wsIDName.id); + } } - // Keep wheel events scoped to overview while it is active. return false; } @@ -125,111 +124,102 @@ bool CHyprspaceWidget::isSwiping() { } bool CHyprspaceWidget::beginSwipe(IPointer::SSwipeBeginEvent e) { - (void)e; - swiping = true; + swiping = true; activeBeforeSwipe = active; - avgSwipeSpeed = 0.; - swipePoints = 0; + avgSwipeSpeed = 0; + swipePoints = 0; return false; } bool CHyprspaceWidget::updateSwipe(IPointer::SSwipeUpdateEvent e) { - const auto absY = std::abs(e.delta.y); - const bool verticalSwipe = absY > 0.0 && (std::abs(e.delta.x) / absY) < 1.0; + constexpr int fingers = 3; + const auto distance = HyprConfig::getWorkspaceSwipeDistance().value(); - if (verticalSwipe) { - if (swiping && e.fingers == static_cast(Config::swipeFingers)) { - const auto owner = getOwner(); - if (!owner || !animationsOk()) - return true; + // restrict swipe to a axis with the most significant movement to prevent misinput + if (abs(e.delta.x) / abs(e.delta.y) < 1) { + if (swiping && e.fingers == (uint32_t)fingers) { - const auto distance = std::max(Config::swipeDistance, 1); - const auto currentScaling = owner->m_size.x / static_cast(distance); - const auto travel = panelTravel(owner); + float currentScaling = State::monitorState()->query().vec(g_pInputManager->getMouseCoordsInternal()).run()->m_size.x / distance; - const double scrollDelta = e.delta.y * (Config::reverseSwipe ? -1 : 1) * (Config::onBottom ? -1 : 1) * currentScaling; + double scrollDifferential = e.delta.y * (config.reverseSwipe->value() ? -1 : 1) * (config.onBottom->value() ? -1 : 1) * currentScaling; - curSwipeOffset += scrollDelta; - curSwipeOffset = std::clamp(curSwipeOffset, swipeClosedOffset(), travel); + curSwipeOffset += scrollDifferential; + curSwipeOffset = std::clamp(curSwipeOffset, -10, ((config.panelHeight->value() + config.reservedArea->value()) * getOwner()->m_scale)); - avgSwipeSpeed = (avgSwipeSpeed * swipePoints + scrollDelta) / (swipePoints + 1); - swipePoints++; + avgSwipeSpeed = (avgSwipeSpeed * swipePoints + scrollDifferential) / (swipePoints + 1); - curYOffset->setValueAndWarp(travel - curSwipeOffset); + curYOffset->setValueAndWarp(((config.panelHeight->value() + config.reservedArea->value()) * getOwner()->m_scale) - curSwipeOffset); - if (curSwipeOffset < swipeVisibleThreshold() && active) - hide(); - else if (curSwipeOffset > swipeVisibleThreshold() && !active) - show(); + if (curSwipeOffset < 10 && active) hide(); + else if (curSwipeOffset > 10 && !active) show(); return false; } - } else if (e.fingers == static_cast(Config::swipeFingers) && active) { - const auto owner = getOwner(); - if (!owner || !animationsOk()) - return true; - - const auto travel = panelTravel(owner); - CBox panelBox = {owner->m_position.x, owner->m_position.y - curYOffset->value(), owner->m_transformedSize.x, travel}; - if (Config::onBottom) - panelBox = {owner->m_position.x, owner->m_position.y + owner->m_transformedSize.y - travel + curYOffset->value(), owner->m_transformedSize.x, travel}; - - if (panelBox.containsPoint(g_pInputManager->getMouseCoordsInternal() * owner->m_scale)) { - workspaceScrollOffset->setValueAndWarp(workspaceScrollOffset->goal() + e.delta.x * Config::workspaceScrollSpeed); - return false; + } + else { + // scroll through panel + if (e.fingers == (uint32_t)fingers && active) { + const auto owner = getOwner(); + CBox widgetBox = {owner->m_position.x, owner->m_position.y - curYOffset->value(), owner->m_transformedSize.x, (config.panelHeight->value() + config.reservedArea->value()) * owner->m_scale}; + if (config.onBottom->value()) widgetBox = {owner->m_position.x, owner->m_position.y + owner->m_transformedSize.y - ((config.panelHeight->value() + config.reservedArea->value()) * owner->m_scale) + curYOffset->value(), owner->m_transformedSize.x, (config.panelHeight->value() + config.reservedArea->value()) * owner->m_scale}; + if (widgetBox.containsPoint(g_pInputManager->getMouseCoordsInternal() * getOwner()->m_scale)) { + workspaceScrollOffset->setValueAndWarp(workspaceScrollOffset->goal() + e.delta.x * 2); + return false; + } } } - + // otherwise, do not cancel the event and perform workspace swipe normally return true; } +// janky asf bool CHyprspaceWidget::endSwipe(IPointer::SSwipeEndEvent e) { swiping = false; - - const auto owner = getOwner(); - const auto travel = panelTravel(owner); - - if (!owner || !animationsOk()) { - avgSwipeSpeed = 0.; - swipePoints = 0; - return false; - } - + // force cancel swipe if (e.cancelled) { - if (active) - hide(); - curSwipeOffset = swipeClosedOffset(); - } else if (activeBeforeSwipe) { - if ((curSwipeOffset < travel * Config::swipeCancelRatio) || avgSwipeSpeed < -Config::swipeForceSpeed) { - if (active) - hide(); + if (active) hide(); + curSwipeOffset = -10.; + } + else { + const auto swipeForceSpeed = HyprConfig::getWorkspaceSwipeMinSpeedToForce().value(); + const auto cancelRatio = HyprConfig::getWorkspaceSwipeCancelRatio().value(); + double swipeTravel = (config.panelHeight->value() + config.reservedArea->value()) * getOwner()->m_scale; + if (activeBeforeSwipe) { + if ((curSwipeOffset < swipeTravel * cancelRatio) || avgSwipeSpeed < -swipeForceSpeed) { + if (active) hide(); + else { + *curYOffset = (config.panelHeight->value() + config.reservedArea->value()) * getOwner()->m_scale; + curSwipeOffset = -10.; + } + } else { - *curYOffset = travel; - curSwipeOffset = swipeClosedOffset(); + // cancel + if (!active) show(); + else { + *curYOffset = 0; + curSwipeOffset = (config.panelHeight->value() + config.reservedArea->value()) * getOwner()->m_scale; + } } - } else if (!active) { - show(); - } else { - *curYOffset = 0; - curSwipeOffset = travel; } - } else { - if ((curSwipeOffset > travel * (1.F - Config::swipeCancelRatio)) || avgSwipeSpeed > Config::swipeForceSpeed) { - if (!active) - show(); + else { + if ((curSwipeOffset > swipeTravel * (1.f - cancelRatio)) || avgSwipeSpeed > swipeForceSpeed) { + if (!active) show(); + else { + *curYOffset = 0; + curSwipeOffset = (config.panelHeight->value() + config.reservedArea->value()) * getOwner()->m_scale; + } + } else { - *curYOffset = 0; - curSwipeOffset = travel; + // cancel + if (active) hide(); + else { + *curYOffset = (config.panelHeight->value() + config.reservedArea->value()) * getOwner()->m_scale; + curSwipeOffset = -10.; + } } - } else if (active) { - hide(); - } else { - *curYOffset = travel; - curSwipeOffset = swipeClosedOffset(); } } - - avgSwipeSpeed = 0.; - swipePoints = 0; + avgSwipeSpeed = 0; + swipePoints = 0; return false; } diff --git a/src/Layout.cpp b/src/Layout.cpp index 0d90280..9bd6819 100644 --- a/src/Layout.cpp +++ b/src/Layout.cpp @@ -1,21 +1,79 @@ #include "Overview.hpp" #include "Globals.hpp" +#include +#include -#include - +// FIXME: preserve original workspace rules void CHyprspaceWidget::updateLayout() { - const auto monitor = getOwner(); - if (!monitor) - return; - const auto reservedHeight = std::max(0, Config::panelHeight + Config::reservedArea); - if (active && Config::affectStrut) { - monitor->m_reservedArea = Config::onBottom ? Desktop::CReservedArea(0, 0, reservedHeight, 0) : Desktop::CReservedArea(reservedHeight, 0, 0, 0); + if (!config.affectStrut->value()) return; + + const auto currentHeight = config.panelHeight->value() + config.reservedArea->value(); + const auto pMonitor = getOwner(); + if (!pMonitor) return; + + static auto PGAPSINDATA = CConfigValue("general:gaps_in"); + static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); + if (!PGAPSINDATA.good() || !PGAPSOUTDATA.good()) return; + + const auto PGAPSINBASE = PGAPSINDATA.ptr(); + const auto PGAPSOUTBASE = PGAPSOUTDATA.ptr(); + if (!PGAPSINBASE || !PGAPSOUTBASE || PGAPSINBASE->getDataType() != Config::CVD_TYPE_CSS_VALUE || PGAPSOUTBASE->getDataType() != Config::CVD_TYPE_CSS_VALUE) return; + + auto* const PGAPSIN = static_cast(PGAPSINBASE); + auto* const PGAPSOUT = static_cast(PGAPSOUTBASE); + + if (active) { + if (!config.onBottom->value()) + pMonitor->m_reservedArea = Desktop::CReservedArea(currentHeight, 0, 0, 0); + else + pMonitor->m_reservedArea = Desktop::CReservedArea(0, 0, currentHeight, 0); } else { - monitor->m_reservedArea = Desktop::CReservedArea(); + pMonitor->m_reservedArea = Desktop::CReservedArea(); } g_pHyprRenderer->arrangeLayersForMonitor(ownerID); - g_layoutManager->recalculateMonitor(monitor); - monitor->scheduleFrame(); + + // gaps are created via workspace rules + // there are no way to write to m_dWorkspaceRules directly + // and we want to refrain from using function hooks + // so we create a workspace rule for ALL workspaces through handleWorkspaceRules + // Geneva Convention violation type hack but idc atm + if (active) { + const auto oActiveWorkspace = pMonitor->m_activeWorkspace; + if (!oActiveWorkspace) return; + + for (auto& ws : State::workspaceState()->workspaces()) { // HACK: recalculate other workspaces without reserved area + if (ws && ws->m_monitor && ws->m_monitor->m_id == ownerID && ws->m_id != oActiveWorkspace->m_id) { + pMonitor->m_activeWorkspace = ws.lock(); + const auto curRules = std::to_string(pMonitor->activeWorkspaceID()) + ", gapsin:" + PGAPSIN->toString() + ", gapsout:" + PGAPSOUT->toString(); + if (config.overrideGaps->value()) { + if (const auto legacy = Config::Legacy::mgr().lock()) + legacy->handleWorkspaceRules("", curRules); + } + g_layoutManager->recalculateMonitor(pMonitor); + } + } + pMonitor->m_activeWorkspace = oActiveWorkspace; + + const auto curRules = std::to_string(pMonitor->activeWorkspaceID()) + ", gapsin:" + std::to_string(config.gapsIn->value()) + ", gapsout:" + std::to_string(config.gapsOut->value()); + if (config.overrideGaps->value()) { + if (const auto legacy = Config::Legacy::mgr().lock()) + legacy->handleWorkspaceRules("", curRules); + } + g_layoutManager->recalculateMonitor(pMonitor); + + } + else { + for (auto& ws : State::workspaceState()->workspaces()) { + if (ws && ws->m_monitor && ws->m_monitor->m_id == ownerID) { + const auto curRules = std::to_string(ws->m_id) + ", gapsin:" + PGAPSIN->toString() + ", gapsout:" + PGAPSOUT->toString(); + if (config.overrideGaps->value()) { + if (const auto legacy = Config::Legacy::mgr().lock()) + legacy->handleWorkspaceRules("", curRules); + } + } + } + g_layoutManager->recalculateMonitor(pMonitor); + } } diff --git a/src/Lua.cpp b/src/Lua.cpp new file mode 100644 index 0000000..c52f636 --- /dev/null +++ b/src/Lua.cpp @@ -0,0 +1,28 @@ +#include "Lua.hpp" +#include "Globals.hpp" +#include + +extern "C" { + #include + #include +} + +template +int luaDispatcherWrapper(lua_State* L) { + const std::string arg = luaL_optstring(L, 1, ""); + SDispatchResult result = DispatcherFunc(arg); + lua_pushboolean(L, result.success); + if (!result.success) { + lua_pushstring(L, result.error.c_str()); + return 2; + } + return 1; +} + +void registerLuaBindings(HANDLE handle) { + if (Config::mgr() && Config::mgr()->type() == Config::CONFIG_LUA) { + HyprlandAPI::addLuaFunction(handle, "overview", "toggle", luaDispatcherWrapper); + HyprlandAPI::addLuaFunction(handle, "overview", "open", luaDispatcherWrapper); + HyprlandAPI::addLuaFunction(handle, "overview", "close", luaDispatcherWrapper); + } +} \ No newline at end of file diff --git a/src/Lua.hpp b/src/Lua.hpp new file mode 100644 index 0000000..f12c240 --- /dev/null +++ b/src/Lua.hpp @@ -0,0 +1,4 @@ +#pragma once +#include + +void registerLuaBindings(HANDLE handle); \ No newline at end of file diff --git a/src/Overview.cpp b/src/Overview.cpp index 8b41e6f..b02b914 100644 --- a/src/Overview.cpp +++ b/src/Overview.cpp @@ -1,198 +1,145 @@ #include "Overview.hpp" #include "Globals.hpp" - -#include - #include +#include +#include -namespace { - -double closedSwipeOffset() { - return -Config::swipeClosedPadding; -} - -double shownSwipeOffset(PHLMONITOR owner) { - return panelTravel(owner); -} - -void requestFullMonitorRedraw(PHLMONITOR owner) { - if (!owner) - return; - - owner->m_damage.damageEntire(); - owner->scheduleFrame(); -} - -PHLANIMVAR& layerFadeAlpha(PHLLS layer) { - return layer->alpha()[Desktop::View::LS_ALPHA_FADE]; -} - -} // namespace - -CHyprspaceWidget::CHyprspaceWidget(uint64_t inOwnerID) : ownerID(inOwnerID) { - resetAnimationState(getOwner()); -} - -CHyprspaceWidget::~CHyprspaceWidget() { - cleanup(getOwner()); - releaseAnimations(); -} - -void CHyprspaceWidget::restoreHiddenLayers() { - for (const auto& [layer, alpha] : oLayerAlpha) { - if (!layer || !layer->m_mapped) - continue; - - *layerFadeAlpha(layer) = alpha; - } - - oLayerAlpha.clear(); -} - -void CHyprspaceWidget::restoreFullscreenWindows() { - for (const auto& [windowRef, modes] : prevFullscreen) { - const auto window = windowRef.lock(); - if (!window) - continue; - - Fullscreen::controller()->setFullscreenMode(window, modes.internal, modes.client); - if (modes.internal == Fullscreen::FSMODE_FULLSCREEN) - window->m_wantsInitialFullscreen = false; - } - - prevFullscreen.clear(); -} - -void CHyprspaceWidget::releaseAnimations() { - if (curYOffset) { - curYOffset->warp(true); - curYOffset.reset(); - } - - if (workspaceScrollOffset) { - workspaceScrollOffset->warp(true); - workspaceScrollOffset.reset(); - } - - m_animationConfig.reset(); -} - -bool CHyprspaceWidget::animationsOk() const { - return curYOffset && curYOffset->ok() && workspaceScrollOffset && workspaceScrollOffset->ok(); -} +CHyprspaceWidget::CHyprspaceWidget(uint64_t inOwnerID) { + ownerID = inOwnerID; -void CHyprspaceWidget::resetAnimationState(PHLMONITOR owner) { - releaseAnimations(); + curAnimationConfig = *Config::animationTree()->getAnimationPropertyConfig("windows"); - const auto base = Config::animationTree()->getAnimationPropertyConfig("windows"); - m_animationConfig = makeShared(*base); - if (Config::overrideAnimSpeed > 0) - m_animationConfig->internalSpeed = Config::overrideAnimSpeed; - m_animationConfig->pValues = m_animationConfig; - curAnimationConfig = *m_animationConfig; + // the fuck is pValues??? + curAnimation = *curAnimationConfig.pValues.lock(); + *curAnimationConfig.pValues.lock() = curAnimation; - Animation::mgr()->createAnimation(0.F, curYOffset, m_animationConfig, AVARDAMAGE_ENTIRE); - Animation::mgr()->createAnimation(0.F, workspaceScrollOffset, m_animationConfig, AVARDAMAGE_ENTIRE); + if (config.overrideAnimSpeed->value() > 0) + curAnimation.internalSpeed = config.overrideAnimSpeed->value(); - const auto hiddenOffset = panelTravel(owner); - curYOffset->setValueAndWarp(active ? 0.F : hiddenOffset); - workspaceScrollOffset->setValueAndWarp(0.F); - curSwipeOffset = active ? shownSwipeOffset(owner) : closedSwipeOffset(); + Animation::mgr()->createAnimation(0.F, curYOffset, curAnimationConfig.pValues.lock(), AVARDAMAGE_ENTIRE); + Animation::mgr()->createAnimation(0.F, workspaceScrollOffset, curAnimationConfig.pValues.lock(), AVARDAMAGE_ENTIRE); + curYOffset->setValueAndWarp(config.panelHeight->value()); + workspaceScrollOffset->setValueAndWarp(0); } -void CHyprspaceWidget::cleanup(PHLMONITOR owner) { - restoreHiddenLayers(); - restoreFullscreenWindows(); - workspaceBoxes.clear(); - swiping = false; - activeBeforeSwipe = false; - avgSwipeSpeed = 0.; - swipePoints = 0; - active = false; - - if (owner) { - owner->m_reservedArea = Desktop::CReservedArea(); - g_pHyprRenderer->arrangeLayersForMonitor(ownerID); - g_layoutManager->recalculateMonitor(owner); - requestFullMonitorRedraw(owner); - } -} +// TODO: implement deconstructor and delete widget on monitor unplug +CHyprspaceWidget::~CHyprspaceWidget() {} PHLMONITOR CHyprspaceWidget::getOwner() { - return monitorFromID(ownerID); + return State::monitorState()->query().id(ownerID).run(); } void CHyprspaceWidget::show() { auto owner = getOwner(); - if (!owner || !owner->m_enabled || compositorUnsafe() || !animationsOk()) - return; + if (!owner) return; if (prevFullscreen.empty()) { - for (auto& wsRef : State::workspaceState()->workspaces()) { - const auto ws = wsRef.lock(); - if (!ws || !ws->m_monitor || ws->m_monitor->m_id != ownerID) - continue; - - const auto window = Fullscreen::controller()->getFullscreenWindow(ws); - const auto modes = Fullscreen::controller()->getFullscreenModes(ws); - if (!window || modes.internal == Fullscreen::FSMODE_NONE) - continue; - - if (modes.internal == Fullscreen::FSMODE_FULLSCREEN) - window->m_wantsInitialFullscreen = true; - - prevFullscreen.emplace_back(PHLWINDOWREF(window), modes); - Fullscreen::controller()->setFullscreenMode(window, Fullscreen::FSMODE_NONE, Fullscreen::FSMODE_NONE); + // unfullscreen all windows + for (auto& ws : State::workspaceState()->workspaces()) { + if (ws && ws->m_monitor && ws->m_monitor->m_id == ownerID) { + const auto pWorkspace = ws.lock(); + const auto w = Fullscreen::controller()->getFullscreenWindow(pWorkspace); + const auto modes = Fullscreen::controller()->getFullscreenModes(pWorkspace); + if (w != nullptr && modes.internal != Fullscreen::FSMODE_NONE) { + // use fakefullscreenstate to preserve client's internal state + // fixes youtube fullscreen not restoring properly + if (modes.internal == Fullscreen::FSMODE_FULLSCREEN) w->m_wantsInitialFullscreen = true; + prevFullscreen.emplace_back(std::make_tuple(PHLWINDOWREF(w), modes)); + Fullscreen::controller()->setFullscreenMode(w, Fullscreen::FSMODE_NONE, Fullscreen::FSMODE_NONE); + } + } } } - if (oLayerAlpha.empty() && Config::hideRealLayers) { - for (int layerIdx : {2, 3}) { - for (auto& layerRef : owner->m_layerSurfaceLayers[layerIdx]) { - const auto layer = layerRef.lock(); - if (!layer) - continue; - - auto& fade = layerFadeAlpha(layer); - oLayerAlpha.emplace_back(layer, fade->goal()); - *fade = 0.F; - } + // hide top and overlay layers + // FIXME: ensure input is disabled for hidden layers + if (oLayerAlpha.empty() && config.hideRealLayers->value()) { + for (auto& ls : owner->m_layerSurfaceLayers[2]) { + //ls->startAnimation(false); + oLayerAlpha.emplace_back(std::make_tuple(ls.lock(), ls->alpha()[Desktop::View::LS_ALPHA_FADE]->goal())); + *ls->alpha()[Desktop::View::LS_ALPHA_FADE] = 0.f; + } + for (auto& ls : owner->m_layerSurfaceLayers[3]) { + //ls->startAnimation(false); + oLayerAlpha.emplace_back(std::make_tuple(ls.lock(), ls->alpha()[Desktop::View::LS_ALPHA_FADE]->goal())); + *ls->alpha()[Desktop::View::LS_ALPHA_FADE] = 0.f; } } active = true; + // panel offset should be handled by swipe event when swiping if (!swiping) { - *curYOffset = 0.F; - curSwipeOffset = shownSwipeOffset(owner); + *curYOffset = 0; + curSwipeOffset = (config.panelHeight->value() + config.reservedArea->value()) * owner->m_scale; } updateLayout(); g_pHyprRenderer->damageMonitor(owner); - requestFullMonitorRedraw(owner); + owner->scheduleFrame(); } void CHyprspaceWidget::hide() { auto owner = getOwner(); - if (!owner || !animationsOk()) - return; + if (!owner) return; + + // restore layer state + for (auto& ls : owner->m_layerSurfaceLayers[2]) { + if (ls->m_mapped) { + auto oAlpha = std::find_if(oLayerAlpha.begin(), oLayerAlpha.end(), [&] (const auto& tuple) {return std::get<0>(tuple) == ls;}); + if (oAlpha != oLayerAlpha.end()) { + *ls->alpha()[Desktop::View::LS_ALPHA_FADE] = std::get<1>(*oAlpha); + } + //ls->startAnimation(true); + } + } + for (auto& ls : owner->m_layerSurfaceLayers[3]) { + if (ls->m_mapped) { + auto oAlpha = std::find_if(oLayerAlpha.begin(), oLayerAlpha.end(), [&] (const auto& tuple) {return std::get<0>(tuple) == ls;}); + if (oAlpha != oLayerAlpha.end()) { + *ls->alpha()[Desktop::View::LS_ALPHA_FADE] = std::get<1>(*oAlpha); + } + //ls->startAnimation(true); + } + } + oLayerAlpha.clear(); - restoreHiddenLayers(); - restoreFullscreenWindows(); + // restore fullscreen state + for (auto& fs : prevFullscreen) { + const auto w = std::get<0>(fs).lock(); + if (!w) continue; + const auto modes = std::get<1>(fs); + Fullscreen::controller()->setFullscreenMode(w, modes.internal, modes.client); + if (modes.internal == Fullscreen::FSMODE_FULLSCREEN) w->m_wantsInitialFullscreen = false; + } + prevFullscreen.clear(); active = false; + // panel offset should be handled by swipe event when swiping if (!swiping) { - *curYOffset = shownSwipeOffset(owner); - curSwipeOffset = closedSwipeOffset(); + *curYOffset = (config.panelHeight->value() + config.reservedArea->value()) * owner->m_scale; + curSwipeOffset = -10.; } updateLayout(); - requestFullMonitorRedraw(owner); + owner->scheduleFrame(); } void CHyprspaceWidget::updateConfig() { - resetAnimationState(getOwner()); + curAnimationConfig = *Config::animationTree()->getAnimationPropertyConfig("windows"); + + // the fuck is pValues??? + curAnimation = *curAnimationConfig.pValues.lock(); + *curAnimationConfig.pValues.lock() = curAnimation; + + if (config.overrideAnimSpeed->value() > 0) + curAnimation.internalSpeed = config.overrideAnimSpeed->value(); + + Animation::mgr()->createAnimation(0.F, curYOffset, curAnimationConfig.pValues.lock(), AVARDAMAGE_ENTIRE); + Animation::mgr()->createAnimation(0.F, workspaceScrollOffset, curAnimationConfig.pValues.lock(), AVARDAMAGE_ENTIRE); + curYOffset->setValueAndWarp(config.panelHeight->value()); + workspaceScrollOffset->setValueAndWarp(0); } bool CHyprspaceWidget::isActive() { diff --git a/src/Overview.hpp b/src/Overview.hpp index 2fd099d..68a6aea 100644 --- a/src/Overview.hpp +++ b/src/Overview.hpp @@ -9,12 +9,13 @@ class CHyprspaceWidget { int64_t ownerID; + // animation override stuff Hyprutils::Animation::SAnimationPropertyConfig curAnimationConfig; - SP m_animationConfig; + Hyprutils::Animation::SAnimationPropertyConfig curAnimation; // for checking mouse hover for workspace drag and move // modified on draw call, accessed on mouse click and release - std::vector> workspaceBoxes; + std::vector> workspaceBoxes; // for storing the fullscreen state of windows prior to overview activation (which unfullscreens all windows) std::vector> prevFullscreen; @@ -23,7 +24,7 @@ class CHyprspaceWidget { std::vector> oLayerAlpha; // for click-to-exit - std::chrono::high_resolution_clock::time_point lastPressedTime = std::chrono::high_resolution_clock::now(); + std::chrono::system_clock::time_point lastPressedTime = std::chrono::high_resolution_clock::now(); bool swiping = false; // whether if the panel is active before the current swiping event @@ -31,17 +32,11 @@ class CHyprspaceWidget { double avgSwipeSpeed = 0.; // number of swiping speed frames recorded int swipePoints = 0; - // Swipe travel in monitor-scaled pixels, where 0 is hidden and panelTravel() is fully shown. - double curSwipeOffset = 0.; + // on second thought, this seems redundant as we could just write to curYOffset while swiping + double curSwipeOffset = 10.; PHLANIMVAR workspaceScrollOffset; - void restoreHiddenLayers(); - void restoreFullscreenWindows(); - void releaseAnimations(); - bool animationsOk() const; - void resetAnimationState(PHLMONITOR owner); - public: // for slide-in animation and swiping @@ -57,7 +52,6 @@ class CHyprspaceWidget { void hide(); void updateConfig(); - void cleanup(PHLMONITOR owner = nullptr); // should be called active or not void draw(); diff --git a/src/Render.cpp b/src/Render.cpp index b8cdfc3..3329659 100644 --- a/src/Render.cpp +++ b/src/Render.cpp @@ -1,376 +1,412 @@ #include "Overview.hpp" #include "Globals.hpp" -#include -#include - -#include +#include #include -#include +#include #include +#include +#include #include +#include +#include +#include #include +#include +#include -namespace { - -struct SWorkspaceWindows { - std::vector tiled; - std::vector floating; -}; void renderRect(CBox box, CHyprColor color) { - CRectPassElement::SRectData rectData; - rectData.color = color; - rectData.box = box; - g_pHyprRenderer->m_renderPass.add(makeUnique(rectData)); + CRectPassElement::SRectData rectdata; + rectdata.color = color; + rectdata.box = box; + g_pHyprRenderer->m_renderPass.add(makeUnique(rectdata)); } void renderRectWithBlur(CBox box, CHyprColor color) { - CRectPassElement::SRectData rectData; - rectData.color = color; - rectData.box = box; - rectData.blur = true; - g_pHyprRenderer->m_renderPass.add(makeUnique(rectData)); + CRectPassElement::SRectData rectdata; + rectdata.color = color; + rectdata.box = box; + rectdata.blur = true; + g_pHyprRenderer->m_renderPass.add(makeUnique(rectdata)); } void renderBorder(CBox box, const Config::CGradientValueData& gradient, int size) { - CBorderPassElement::SBorderData borderData; - borderData.box = box; - borderData.grad1 = gradient; - borderData.round = 0; - borderData.a = 1.F; - borderData.borderSize = size; - g_pHyprRenderer->m_renderPass.add(makeUnique(borderData)); + CBorderPassElement::SBorderData data; + data.box = box; + data.grad1 = gradient; + data.round = 0; + data.a = 1.f; + data.borderSize = size; + g_pHyprRenderer->m_renderPass.add(makeUnique(data)); } -void renderWindowStub(PHLWINDOW window, PHLMONITOR monitor, PHLWORKSPACE workspaceOverride, CBox rectOverride, const Time::steady_tp& time) { - if (!window || !monitor || !workspaceOverride) - return; - - if (!window->m_isMapped || !window->wlSurface() || !window->wlSurface()->resource() || !window->m_ruleApplicator) - return; +void renderWindowStub(PHLWINDOW pWindow, PHLMONITOR pMonitor, PHLWORKSPACE pWorkspaceOverride, CBox rectOverride, CBox clipBox, const Time::steady_tp& time) { + if (!pWindow || !pMonitor || !pWorkspaceOverride) return; + if (!pWindow->m_isMapped || !pWindow->wlSurface() || !pWindow->wlSurface()->resource()) return; Render::SRenderModifData renderModif; - const auto workspace = window->m_workspace; - const auto fsModes = Fullscreen::controller()->getFullscreenModes(window); - const auto realPosition = window->positionAnimation()->value(); - const auto realSize = window->sizeAnimation()->value(); - const auto pinned = window->m_pinned; - const auto floating = window->m_isFloating; - const float logicalW = std::max(static_cast(realSize.x), 5.F); - const float scaleMod = rectOverride.w / std::max(logicalW * monitor->m_scale, 5.F); - if (!(scaleMod > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) - return; - - const Vector2D logicalTL = realPosition + window->m_floatingOffset; - const Vector2D scaledTL = (logicalTL - monitor->m_position) * monitor->m_scale; + const auto oRealPosition = pWindow->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT); + const auto oSize = pWindow->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT); + const float logicalW = std::max((float)oSize.x, 5.F); + const float scaleMod = rectOverride.w / std::max(logicalW * pMonitor->m_scale, 5.F); + if (!(scaleMod > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) return; + + const Vector2D logicalTL = oRealPosition + pWindow->m_floatingOffset; + const Vector2D scaledTL = (logicalTL - pMonitor->m_position) * pMonitor->m_scale; const Vector2D translate = rectOverride.pos() / scaleMod - scaledTL; renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, std::any(translate))); renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, std::any(scaleMod))); renderModif.enabled = true; - window->m_workspace = workspaceOverride; - window->m_ruleApplicator->nearestNeighbor().set(false, Desktop::Types::PRIORITY_SET_PROP); - window->m_isFloating = false; - window->m_pinned = true; - window->m_ruleApplicator->rounding().set(window->rounding() * scaleMod * monitor->m_scale, Desktop::Types::PRIORITY_SET_PROP); - - const bool wasFullscreen = fsModes.internal != Fullscreen::FSMODE_NONE; - if (wasFullscreen) - Fullscreen::controller()->setFullscreenMode(window, Fullscreen::FSMODE_NONE, Fullscreen::FSMODE_NONE); - - Hyprutils::Utils::CScopeGuard restoreWindowState([&] { - if (wasFullscreen) - Fullscreen::controller()->setFullscreenMode(window, fsModes.internal, fsModes.client); - window->m_workspace = workspace; - window->m_ruleApplicator->nearestNeighbor().unset(Desktop::Types::PRIORITY_SET_PROP); - window->m_isFloating = floating; - window->m_pinned = pinned; - window->m_ruleApplicator->rounding().unset(Desktop::Types::PRIORITY_SET_PROP); - }); - g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = renderModif})); - Hyprutils::Utils::CScopeGuard clearHints([] { + Hyprutils::Utils::CScopeGuard x([] { g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = Render::SRenderModifData{}})); }); - g_pHyprRenderer->damageWindow(window); - if (pRenderWindow) - (*(tRenderWindow)pRenderWindow)(g_pHyprRenderer.get(), window, monitor, time, true, Render::RENDER_PASS_ALL, false, false); + g_pHyprRenderer->damageWindow(pWindow); + + CSurfacePassElement::SRenderData renderdata = {pMonitor, time}; + renderdata.pos = oRealPosition + pWindow->m_floatingOffset; + renderdata.w = std::max(oSize.x, 5.0); + renderdata.h = std::max(oSize.y, 5.0); + renderdata.surface = pWindow->wlSurface()->resource(); + renderdata.dontRound = Fullscreen::controller()->isFullscreen(pWindow, Fullscreen::FSMODE_FULLSCREEN); + renderdata.fadeAlpha = 1.F; + renderdata.alpha = 0.999F; + renderdata.decorate = false; + renderdata.rounding = renderdata.dontRound ? 0 : pWindow->rounding() * scaleMod * pMonitor->m_scale; + renderdata.roundingPower = renderdata.dontRound ? 2.0F : pWindow->roundingPower(); + renderdata.blur = false; + renderdata.pWindow = pWindow; + renderdata.clipBox = clipBox; + renderdata.useNearestNeighbor = false; + renderdata.squishOversized = true; + renderdata.surfaceCounter = 0; + + pWindow->wlSurface()->resource()->breadthfirst( + [&renderdata, &pWindow](SP s, const Vector2D& offset, void* data) { + if (!s || !s->m_current.texture) + return; + + if (s->m_current.size.x < 1 || s->m_current.size.y < 1) + return; + + renderdata.localPos = offset; + renderdata.texture = s->m_current.texture; + renderdata.surface = s; + renderdata.mainSurface = s == pWindow->wlSurface()->resource(); + g_pHyprRenderer->m_renderPass.add(makeUnique(renderdata)); + renderdata.surfaceCounter++; + }, + nullptr); } -void renderLayerStub(PHLLS layer, PHLMONITOR monitor, CBox rectOverride, const Time::steady_tp& time) { - if (!layer || !monitor) - return; +void renderLayerStub(PHLLS pLayer, PHLMONITOR pMonitor, CBox rectOverride, CBox clipBox, const Time::steady_tp& time) { + if (!pLayer || !pMonitor) return; - if (!layer->m_mapped || !layer->m_layerSurface || !layer->wlSurface() || !layer->wlSurface()->resource()) - return; + if (!pLayer->m_mapped || !pLayer->m_layerSurface || !pLayer->wlSurface() || !pLayer->wlSurface()->resource()) return; - const Vector2D realPosition = layer->positionAnimation()->value(); - const Vector2D realSize = layer->sizeAnimation()->value(); - auto& fadeAlpha = layer->alpha()[Desktop::View::LS_ALPHA_FADE]; - const float alpha = fadeAlpha->value(); + Vector2D oRealPosition = pLayer->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT); + Vector2D oSize = pLayer->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT); - const float scale = rectOverride.w / realSize.x; - if (!(scale > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) - return; + const float curScaling = rectOverride.w / (oSize.x); + if (!(curScaling > 0.F) || !(rectOverride.w > 0 && rectOverride.h > 0)) return; Render::SRenderModifData renderModif; - renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, std::any(monitor->m_position + (rectOverride.pos() / scale) - realPosition))); - renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, std::any(scale))); - renderModif.enabled = true; - fadeAlpha->setValueAndWarp(1.F); + renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, std::any(pMonitor->m_position + (rectOverride.pos() / curScaling) - oRealPosition))); + renderModif.modifs.push_back(std::make_pair(Render::SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, std::any(curScaling))); + renderModif.enabled = true; g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = renderModif})); - Hyprutils::Utils::CScopeGuard clearHints([] { + Hyprutils::Utils::CScopeGuard x([] { g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{.renderModif = Render::SRenderModifData{}})); }); - if (pRenderLayer) - (*(tRenderLayer)pRenderLayer)(g_pHyprRenderer.get(), layer, monitor, time, false, false); - fadeAlpha->setValueAndWarp(alpha); + CSurfacePassElement::SRenderData renderdata = {pMonitor, time, oRealPosition}; + renderdata.fadeAlpha = 1.F; + renderdata.alpha = 0.999F; + renderdata.blur = false; + renderdata.surface = pLayer->wlSurface()->resource(); + renderdata.decorate = false; + renderdata.w = oSize.x; + renderdata.h = oSize.y; + renderdata.pLS = pLayer; + renderdata.clipBox = clipBox; + renderdata.surfaceCounter = 0; + + pLayer->wlSurface()->resource()->breadthfirst( + [&renderdata, &pLayer](SP s, const Vector2D& offset, void* data) { + if (!s || !s->m_current.texture) + return; + + if (s->m_current.size.x < 1 || s->m_current.size.y < 1) + return; + + renderdata.localPos = offset; + renderdata.texture = s->m_current.texture; + renderdata.surface = s; + renderdata.mainSurface = s == pLayer->wlSurface()->resource(); + g_pHyprRenderer->m_renderPass.add(makeUnique(renderdata)); + renderdata.surfaceCounter++; + }, + &renderdata); } -bool renderWindowPreview(PHLWINDOW window, PHLWORKSPACE workspace, PHLMONITOR owner, double workspaceX, double workspaceY, double monitorScaleFactor, const Time::steady_tp& time) { - if (!window || !workspace || !owner) - return false; - - const double wX = workspaceX + ((window->positionAnimation()->value().x - owner->m_position.x) * monitorScaleFactor * owner->m_scale); - const double wY = workspaceY + ((window->positionAnimation()->value().y - owner->m_position.y) * monitorScaleFactor * owner->m_scale); - const double wW = window->sizeAnimation()->value().x * monitorScaleFactor * owner->m_scale; - const double wH = window->sizeAnimation()->value().y * monitorScaleFactor * owner->m_scale; - if (!(wW > 0 && wH > 0)) - return false; - - renderWindowStub(window, owner, workspace, {wX, wY, wW, wH}, time); - return true; -} - -} // namespace - +// NOTE: rects and clipbox positions are relative to the monitor, while damagebox and layers are not, what the fuck? xd void CHyprspaceWidget::draw() { + workspaceBoxes.clear(); - if (compositorUnsafe() || !animationsOk()) - return; + if (!active && !curYOffset->isBeingAnimated()) return; - if (!active && !curYOffset->isBeingAnimated()) - return; + auto owner = getOwner(); - const auto owner = getOwner(); - if (!owner || !owner->m_enabled) - return; + if (!owner) return; + // Full-monitor clip in monitor-local coords. Never use default CBox() to "clear" clipBox — + // hyprutils::CBox() only sets w/h to 0 and leaves x/y uninitialized, which corrupts scissor state. const CBox monitorClip = {{0, 0}, owner->m_transformedSize}; - const auto time = Time::steadyNow(); + + const auto time = Time::steadyNow(); owner->m_blurFBShouldRender = true; - const int panelDirection = Config::onBottom ? -1 : 1; + int bottomInvert = 1; + if (config.onBottom->value()) bottomInvert = -1; - CBox panelBox = {owner->m_position.x, - owner->m_position.y + (Config::onBottom * (owner->m_transformedSize.y - ((Config::panelHeight + Config::reservedArea) * owner->m_scale))) - (panelDirection * curYOffset->value()), - owner->m_transformedSize.x, - (Config::panelHeight + Config::reservedArea) * owner->m_scale}; + // Background box + CBox widgetBox = {owner->m_position.x, owner->m_position.y + (config.onBottom->value() * (owner->m_transformedSize.y - ((config.panelHeight->value() + config.reservedArea->value()) * owner->m_scale))) - (bottomInvert * curYOffset->value()), owner->m_transformedSize.x, (config.panelHeight->value() + config.reservedArea->value()) * owner->m_scale}; //TODO: update size on monitor change - panelBox.x -= owner->m_position.x; - panelBox.y -= owner->m_position.y; + // set widgetBox relative to current monitor for rendering panel + widgetBox.x -= owner->m_position.x; + widgetBox.y -= owner->m_position.y; g_pHyprRenderer->m_renderData.clipBox = monitorClip; - if (!Config::disableBlur) - renderRectWithBlur(panelBox, Config::panelBaseColor); - else - renderRect(panelBox, Config::panelBaseColor); + if (!config.disableBlur->value()) { + renderRectWithBlur(widgetBox, config.panelBaseColor->value()); + } + else { + renderRect(widgetBox, config.panelBaseColor->value()); + } - if (Config::panelBorderWidth > 0) { - CBox borderBox = {panelBox.x, - owner->m_position.y + (Config::onBottom * owner->m_transformedSize.y) + (Config::panelHeight + Config::reservedArea - curYOffset->value() * owner->m_scale) * panelDirection, - owner->m_transformedSize.x, - static_cast(Config::panelBorderWidth)}; + // Panel Border + if (config.panelBorderWidth->value() > 0) { + // Border box + CBox borderBox = {widgetBox.x, owner->m_position.y + (config.onBottom->value() * owner->m_transformedSize.y) + (config.panelHeight->value() + config.reservedArea->value() - curYOffset->value() * owner->m_scale) * bottomInvert, owner->m_transformedSize.x, static_cast(config.panelBorderWidth->value())}; borderBox.y -= owner->m_position.y; - renderRect(borderBox, Config::panelBorderColor); + + renderRect(borderBox, config.panelBorderColor->value()); } + + // unscaled and relative to owner + //CBox damageBox = {0, (Config::onBottom * (owner->m_transformedSize.y - ((Config::panelHeight + Config::reservedArea)))) - (bottomInvert * curYOffset->value()), owner->m_transformedSize.x, (Config::panelHeight + Config::reservedArea) * owner->m_scale}; + + //owner->addDamage(damageBox); g_pHyprRenderer->damageMonitor(owner); - std::vector workspaces; + // damage the entire monitor to ensure full redraw during overview + g_pHyprRenderer->damageMonitor(owner); - if (Config::showSpecialWorkspace) - workspaces.push_back(SPECIAL_WORKSPACE_START); + // the list of workspaces to show + std::vector workspaces; - WORKSPACEID highestID = 1; - for (const auto& wsRef : State::workspaceState()->workspaces()) { - const auto ws = wsRef.lock(); - if (!ws || ws->m_id < 1 || !ws->m_monitor || ws->m_monitor->m_id != ownerID) - continue; + if (config.showSpecialWorkspace->value()) { + workspaces.push_back(SPECIAL_WORKSPACE_START); + } - workspaces.push_back(ws->m_id); - highestID = std::max(highestID, ws->m_id); + // find the lowest and highest workspace id to determine which empty workspaces to insert + int lowestID = INT_MAX; + int highestID = 1; + for (auto& ws : State::workspaceState()->workspaces()) { + if (!ws) continue; + // normal workspaces start from 1, special workspaces ends on -2 + if (ws->m_id < 1) continue; + if (ws->m_monitor->m_id == ownerID) { + workspaces.push_back(ws->m_id); + if (highestID < ws->m_id) highestID = ws->m_id; + if (lowestID > ws->m_id) lowestID = ws->m_id; + } } - if (Config::showEmptyWorkspace) { - const WORKSPACEID wsIDStart = 1; - const WORKSPACEID wsIDEnd = highestID; + // include empty workspaces that are between non-empty ones + if (config.showEmptyWorkspace->value()) { + int wsIDStart = 1; + int wsIDEnd = highestID; - for (WORKSPACEID id = wsIDStart; id <= wsIDEnd; id++) { - if (id == owner->activeSpecialWorkspaceID()) - continue; + // hyprsplit/split-monitor-workspaces compatibility + if (numWorkspaces > 0) { + wsIDStart = std::min(numWorkspaces * ownerID + 1, lowestID); + wsIDEnd = std::max(numWorkspaces * ownerID + 1, highestID); // always show the initial workspace for current monitor + } - if (workspaceByID(id) == nullptr) - workspaces.push_back(id); + for (int i = wsIDStart; i <= wsIDEnd; i++) { + if (i == owner->activeSpecialWorkspaceID()) continue; + const auto pWorkspace = State::workspaceState()->query().id(i).run(); + if (pWorkspace == nullptr) + workspaces.push_back(i); } } - if (Config::showNewWorkspace) { - while (workspaceByID(highestID) != nullptr) - highestID++; + // add a new empty workspace at last + if (config.showNewWorkspace->value()) { + // get the lowest empty workspce id after the highest id of current workspace + while (State::workspaceState()->query().id(highestID).run() != nullptr) highestID++; workspaces.push_back(highestID); } std::sort(workspaces.begin(), workspaces.end()); - workspaces.erase(std::unique(workspaces.begin(), workspaces.end()), workspaces.end()); - - const auto workspaceCount = static_cast(workspaces.size()); - if (workspaceCount == 0) - return; - - const double monitorScaleFactor = ((Config::panelHeight - 2 * Config::workspaceMargin) / owner->m_transformedSize.y) * owner->m_scale; - const double workspaceBoxW = owner->m_transformedSize.x * monitorScaleFactor; - const double workspaceBoxH = owner->m_transformedSize.y * monitorScaleFactor; - const double workspaceGroupW = workspaceBoxW * workspaceCount + (Config::workspaceMargin * owner->m_scale) * (workspaceCount - 1); - double workspaceOffsetX = Config::centerAligned ? workspaceScrollOffset->value() + (panelBox.w / 2.) - (workspaceGroupW / 2.) : workspaceScrollOffset->value() + Config::workspaceMargin; - const double workspaceOffsetY = !Config::onBottom ? (((Config::reservedArea + Config::workspaceMargin) * owner->m_scale) - curYOffset->value()) : (owner->m_transformedSize.y - ((Config::reservedArea + Config::workspaceMargin) * owner->m_scale) - workspaceBoxH + curYOffset->value()); - const double workspaceOverflow = std::max(((workspaceGroupW - panelBox.w) / 2.) + (Config::workspaceMargin * owner->m_scale), 0.); - - *workspaceScrollOffset = std::clamp(workspaceScrollOffset->goal(), -workspaceOverflow, workspaceOverflow); - - if (!(workspaceBoxW > 0 && workspaceBoxH > 0)) - return; - - std::unordered_map windowsByWorkspace; - windowsByWorkspace.reserve(workspaceCount + 2); - for (const auto& window : Desktop::windowState()->windows()) { - if (!window || !window->m_workspace) - continue; - - auto& bucket = windowsByWorkspace[window->m_workspace->m_id]; - if (window->m_isFloating) - bucket.floating.push_back(window); - else - bucket.tiled.push_back(window); - } - for (const auto wsID : workspaces) { - const auto ws = workspaceByID(wsID); - CBox box = {workspaceOffsetX, workspaceOffsetY, workspaceBoxW, workspaceBoxH}; + // render workspace boxes + int wsCount = workspaces.size(); + double monitorSizeScaleFactor = ((config.panelHeight->value() - 2 * config.workspaceMargin->value()) / (owner->m_transformedSize.y)) * owner->m_scale; // scale box with panel height + double workspaceBoxW = owner->m_transformedSize.x * monitorSizeScaleFactor; + double workspaceBoxH = owner->m_transformedSize.y * monitorSizeScaleFactor; + double workspaceGroupWidth = workspaceBoxW * wsCount + (config.workspaceMargin->value() * owner->m_scale) * (wsCount - 1); + double curWorkspaceRectOffsetX = config.centerAligned->value() ? workspaceScrollOffset->value() + (widgetBox.w / 2.) - (workspaceGroupWidth / 2.) : workspaceScrollOffset->value() + config.workspaceMargin->value(); + double curWorkspaceRectOffsetY = !config.onBottom->value() ? (((config.reservedArea->value() + config.workspaceMargin->value()) * owner->m_scale) - curYOffset->value()) : (owner->m_transformedSize.y - ((config.reservedArea->value() + config.workspaceMargin->value()) * owner->m_scale) - workspaceBoxH + curYOffset->value()); + double workspaceOverflowSize = std::max(((workspaceGroupWidth - widgetBox.w) / 2) + (config.workspaceMargin->value() * owner->m_scale), 0); - if (ws == owner->m_activeWorkspace) { - if (Config::workspaceBorderSize >= 1 && Config::workspaceActiveBorder.a > 0) - renderBorder(box, Config::CGradientValueData(Config::workspaceActiveBorder), Config::workspaceBorderSize); + *workspaceScrollOffset = std::clamp(workspaceScrollOffset->goal(), -workspaceOverflowSize, workspaceOverflowSize); - if (!Config::disableBlur) - renderRectWithBlur(box, Config::workspaceActiveBackground); - else - renderRect(box, Config::workspaceActiveBackground); + if (!(workspaceBoxW > 0 && workspaceBoxH > 0)) return; + for (auto wsID : workspaces) { + const auto ws = State::workspaceState()->query().id(wsID).run(); + CBox curWorkspaceBox = {curWorkspaceRectOffsetX, curWorkspaceRectOffsetY, workspaceBoxW, workspaceBoxH}; - if (!Config::drawActiveWorkspace) { - workspaceOffsetX += workspaceBoxW + (Config::workspaceMargin * owner->m_scale); + // workspace background rect (NOT background layer) and border + if (ws == owner->m_activeWorkspace) { + if (config.workspaceBorderSize->value() >= 1 && CHyprColor(config.workspaceActiveBorder->value()).a > 0) { + renderBorder(curWorkspaceBox, Config::CGradientValueData(config.workspaceActiveBorder->value()), config.workspaceBorderSize->value()); + } + if (!config.disableBlur->value()) { + renderRectWithBlur(curWorkspaceBox, config.workspaceActiveBackground->value()); // cant really round it until I find a proper way to clip windows to a rounded rect + } + else { + renderRect(curWorkspaceBox, config.workspaceActiveBackground->value()); + } + if (!config.drawActiveWorkspace->value()) { + curWorkspaceRectOffsetX += workspaceBoxW + (config.workspaceMargin->value() * owner->m_scale); continue; } - } else { - if (Config::workspaceBorderSize >= 1 && Config::workspaceInactiveBorder.a > 0) - renderBorder(box, Config::CGradientValueData(Config::workspaceInactiveBorder), Config::workspaceBorderSize); - - if (!Config::disableBlur) - renderRectWithBlur(box, Config::workspaceInactiveBackground); - else - renderRect(box, Config::workspaceInactiveBackground); } - - if (!Config::hideBackgroundLayers) { - for (const auto& layerRef : owner->m_layerSurfaceLayers[0]) { - const auto layer = layerRef.lock(); - if (!layer) - continue; - - CBox layerBox = {box.pos() + (layer->positionAnimation()->value() - owner->m_position) * monitorScaleFactor, layer->sizeAnimation()->value() * monitorScaleFactor}; - renderLayerStub(layer, owner, layerBox, time); + else { + if (config.workspaceBorderSize->value() >= 1 && CHyprColor(config.workspaceInactiveBorder->value()).a > 0) { + renderBorder(curWorkspaceBox, Config::CGradientValueData(config.workspaceInactiveBorder->value()), config.workspaceBorderSize->value()); } + if (!config.disableBlur->value()) { + renderRectWithBlur(curWorkspaceBox, config.workspaceInactiveBackground->value()); + } + else { + renderRect(curWorkspaceBox, config.workspaceInactiveBackground->value()); + } + } - for (const auto& layerRef : owner->m_layerSurfaceLayers[1]) { - const auto layer = layerRef.lock(); - if (!layer) - continue; - - CBox layerBox = {box.pos() + (layer->positionAnimation()->value() - owner->m_position) * monitorScaleFactor, layer->sizeAnimation()->value() * monitorScaleFactor}; - renderLayerStub(layer, owner, layerBox, time); + // background and bottom layers + if (!config.hideBackgroundLayers->value()) { + for (auto& ls : owner->m_layerSurfaceLayers[0]) { + CBox layerBox = {curWorkspaceBox.pos() + (ls->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT) - owner->m_position) * monitorSizeScaleFactor, ls->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT) * monitorSizeScaleFactor}; + renderLayerStub(ls.lock(), owner, layerBox, curWorkspaceBox, time); + } + for (auto& ls : owner->m_layerSurfaceLayers[1]) { + CBox layerBox = {curWorkspaceBox.pos() + (ls->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT) - owner->m_position) * monitorSizeScaleFactor, ls->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT) * monitorSizeScaleFactor}; + renderLayerStub(ls.lock(), owner, layerBox, curWorkspaceBox, time); } } - if (owner->m_activeWorkspace == ws && Config::affectStrut) { - CBox miniPanelBox = {workspaceOffsetX, workspaceOffsetY, panelBox.w * monitorScaleFactor, panelBox.h * monitorScaleFactor}; - if (Config::onBottom) - miniPanelBox = {workspaceOffsetX, workspaceOffsetY + workspaceBoxH - panelBox.h * monitorScaleFactor, panelBox.w * monitorScaleFactor, panelBox.h * monitorScaleFactor}; + // the mini panel to cover the awkward empty space reserved by the panel + if (owner->m_activeWorkspace == ws && config.affectStrut->value()) { + CBox miniPanelBox = {curWorkspaceRectOffsetX, curWorkspaceRectOffsetY, widgetBox.w * monitorSizeScaleFactor, widgetBox.h * monitorSizeScaleFactor}; + if (config.onBottom->value()) miniPanelBox = {curWorkspaceRectOffsetX, curWorkspaceRectOffsetY + workspaceBoxH - widgetBox.h * monitorSizeScaleFactor, widgetBox.w * monitorSizeScaleFactor, widgetBox.h * monitorSizeScaleFactor}; - if (!Config::disableBlur) + if (!config.disableBlur->value()) { renderRectWithBlur(miniPanelBox, CHyprColor(0, 0, 0, 0)); - else + } + else { + // what renderRect(miniPanelBox, CHyprColor(0, 0, 0, 0)); - } - - if (ws) { - const auto windowsIt = windowsByWorkspace.find(ws->m_id); - if (windowsIt != windowsByWorkspace.end()) { - for (const auto& window : windowsIt->second.tiled) - renderWindowPreview(window, ws, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, time); + } - const auto focused = ws->getLastFocusedWindow(); - for (const auto& window : windowsIt->second.floating) { - if (window == focused) - continue; + } - renderWindowPreview(window, ws, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, time); + if (ws != nullptr) { + // draw tiled windows + for (auto& w : Desktop::windowState()->windows()) { + if (!w) continue; + if (w->m_workspace == ws && !w->m_isFloating) { + double wX = curWorkspaceRectOffsetX + ((w->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT).x - owner->m_position.x) * monitorSizeScaleFactor * owner->m_scale); + double wY = curWorkspaceRectOffsetY + ((w->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT).y - owner->m_position.y) * monitorSizeScaleFactor * owner->m_scale); + double wW = w->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT).x * monitorSizeScaleFactor * owner->m_scale; + double wH = w->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT).y * monitorSizeScaleFactor * owner->m_scale; + if (!(wW > 0 && wH > 0)) continue; + CBox curWindowBox = {wX, wY, wW, wH}; + //g_pHyprOpenGL->renderRectWithBlur(&curWindowBox, CHyprColor(0, 0, 0, 0)); + renderWindowStub(w, owner, owner->m_activeWorkspace, curWindowBox, curWorkspaceBox, time); } - - if (focused && focused->m_isFloating) - renderWindowPreview(focused, ws, owner, workspaceOffsetX, workspaceOffsetY, monitorScaleFactor, time); } - } - - if (owner->m_activeWorkspace != ws || !Config::hideRealLayers) { - if (!Config::hideTopLayers) { - for (const auto& layerRef : owner->m_layerSurfaceLayers[2]) { - const auto layer = layerRef.lock(); - if (!layer) - continue; - - CBox layerBox = {box.pos() + (layer->positionAnimation()->value() - owner->m_position) * monitorScaleFactor, layer->sizeAnimation()->value() * monitorScaleFactor}; - renderLayerStub(layer, owner, layerBox, time); + // draw floating windows + for (auto& w : Desktop::windowState()->windows()) { + if (!w) continue; + if (w->m_workspace == ws && w->m_isFloating && ws->getLastFocusedWindow() != w) { + double wX = curWorkspaceRectOffsetX + ((w->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT).x - owner->m_position.x) * monitorSizeScaleFactor * owner->m_scale); + double wY = curWorkspaceRectOffsetY + ((w->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT).y - owner->m_position.y) * monitorSizeScaleFactor * owner->m_scale); + double wW = w->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT).x * monitorSizeScaleFactor * owner->m_scale; + double wH = w->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT).y * monitorSizeScaleFactor * owner->m_scale; + if (!(wW > 0 && wH > 0)) continue; + CBox curWindowBox = {wX, wY, wW, wH}; + //g_pHyprOpenGL->renderRectWithBlur(&curWindowBox, CHyprColor(0, 0, 0, 0)); + renderWindowStub(w, owner, owner->m_activeWorkspace, curWindowBox, curWorkspaceBox, time); } } + // draw last focused floating window on top + if (ws->getLastFocusedWindow()) + if (ws->getLastFocusedWindow()->m_isFloating) { + const auto w = ws->getLastFocusedWindow(); + double wX = curWorkspaceRectOffsetX + ((w->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT).x - owner->m_position.x) * monitorSizeScaleFactor * owner->m_scale); + double wY = curWorkspaceRectOffsetY + ((w->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT).y - owner->m_position.y) * monitorSizeScaleFactor * owner->m_scale); + double wW = w->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT).x * monitorSizeScaleFactor * owner->m_scale; + double wH = w->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT).y * monitorSizeScaleFactor * owner->m_scale; + if (!(wW > 0 && wH > 0)) continue; + CBox curWindowBox = {wX, wY, wW, wH}; + //g_pHyprOpenGL->renderRectWithBlur(&curWindowBox, CHyprColor(0, 0, 0, 0)); + renderWindowStub(w, owner, owner->m_activeWorkspace, curWindowBox, curWorkspaceBox, time); + } + } - if (!Config::hideOverlayLayers) { - for (const auto& layerRef : owner->m_layerSurfaceLayers[3]) { - const auto layer = layerRef.lock(); - if (!layer) - continue; + if (owner->m_activeWorkspace != ws || !config.hideRealLayers->value()) { + // this layer is hidden for real workspace when panel is displayed + if (!config.hideTopLayers->value()) + for (auto& ls : owner->m_layerSurfaceLayers[2]) { + CBox layerBox = {curWorkspaceBox.pos() + (ls->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT) - owner->m_position) * monitorSizeScaleFactor, ls->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT) * monitorSizeScaleFactor}; + renderLayerStub(ls.lock(), owner, layerBox, curWorkspaceBox, time); + } - CBox layerBox = {box.pos() + (layer->positionAnimation()->value() - owner->m_position) * monitorScaleFactor, layer->sizeAnimation()->value() * monitorScaleFactor}; - renderLayerStub(layer, owner, layerBox, time); + if (!config.hideOverlayLayers->value()) + for (auto& ls : owner->m_layerSurfaceLayers[3]) { + CBox layerBox = {curWorkspaceBox.pos() + (ls->position(Desktop::View::IGeometric::GEOMETRIC_CURRENT) - owner->m_position) * monitorSizeScaleFactor, ls->size(Desktop::View::IGeometric::GEOMETRIC_CURRENT) * monitorSizeScaleFactor}; + renderLayerStub(ls.lock(), owner, layerBox, curWorkspaceBox, time); } - } } - // Input hitboxes are evaluated in monitor-space logical coordinates. - box.scale(1 / owner->m_scale); - box.x += owner->m_position.x; - box.y += owner->m_position.y; - workspaceBoxes.emplace_back(wsID, box); - workspaceOffsetX += workspaceBoxW + Config::workspaceMargin * owner->m_scale; + // Resets workspaceBox to scaled absolute coordinates for input detection. + // While rendering is done in pixel coordinates, input detection is done in + // scaled coordinates, taking into account monitor scaling. + // Since the monitor position is already given in scaled coordinates, + // we only have to scale all relative coordinates, then add them to the + // monitor position to get a scaled absolute position. + curWorkspaceBox.scale(1 / owner->m_scale); + + curWorkspaceBox.x += owner->m_position.x; + curWorkspaceBox.y += owner->m_position.y; + workspaceBoxes.emplace_back(std::make_tuple(wsID, curWorkspaceBox)); + + // set the current position to the next workspace box + curWorkspaceRectOffsetX += workspaceBoxW + config.workspaceMargin->value() * owner->m_scale; } g_pHyprRenderer->m_renderData.clipBox = monitorClip; diff --git a/src/main.cpp b/src/main.cpp index c47d1c3..22d7086 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,78 +1,27 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include +#include #include +#include #include -#include -#include +#include +#include +#include #include - -extern "C" { -#include -#include -} - -#include "Globals.hpp" +#include #include "Overview.hpp" +#include "Globals.hpp" +#include "Lua.hpp" void* pRenderWindow; void* pRenderLayer; std::vector> g_overviewWidgets; -CHyprColor Config::panelBaseColor = CHyprColor(0xCC111111); -CHyprColor Config::panelBorderColor = CHyprColor(0x66FF99CC); -CHyprColor Config::workspaceActiveBackground = CHyprColor(0x66111111); -CHyprColor Config::workspaceInactiveBackground = CHyprColor(0xAA111111); -CHyprColor Config::workspaceActiveBorder = CHyprColor(1, 1, 1, 0.3); -CHyprColor Config::workspaceInactiveBorder = CHyprColor(1, 1, 1, 0); - -int Config::panelHeight = 250; -int Config::panelBorderWidth = 2; -int Config::workspaceMargin = 12; -int Config::reservedArea = 0; -int Config::workspaceBorderSize = 1; -bool Config::adaptiveHeight = false; -bool Config::centerAligned = true; -bool Config::onBottom = false; -bool Config::hideBackgroundLayers = false; -bool Config::hideTopLayers = false; -bool Config::hideOverlayLayers = false; -bool Config::drawActiveWorkspace = true; -bool Config::hideRealLayers = false; -bool Config::affectStrut = false; -bool Config::autoDrag = true; -bool Config::autoScroll = true; -bool Config::exitOnClick = true; -bool Config::switchOnDrop = false; -bool Config::exitOnSwitch = false; -bool Config::showNewWorkspace = true; -bool Config::showEmptyWorkspace = true; -bool Config::showSpecialWorkspace = false; -bool Config::disableGestures = false; -bool Config::reverseSwipe = false; -bool Config::disableBlur = false; -float Config::overrideAnimSpeed = 0.F; -float Config::dragAlpha = 0.2F; -std::string Config::exitKey = "Escape"; -int Config::clickReleaseThresholdMs = 200; -int Config::swipeFingers = 3; -int Config::swipeDistance = 300; -int Config::swipeForceSpeed = 30; -float Config::swipeCancelRatio = 0.5F; -float Config::swipeThreshold = 10.F; -float Config::swipeClosedPadding = 10.F; -float Config::workspaceScrollSpeed = 2.F; +int numWorkspaces = -1; //hyprsplit/split-monitor-workspaces support +// Event listener handles (auto-unregister when destroyed) CHyprSignalListener g_pRenderHook; CHyprSignalListener g_pConfigReloadHook; CHyprSignalListener g_pOpenLayerHook; @@ -88,327 +37,206 @@ CHyprSignalListener g_pSwipeEndHook; CHyprSignalListener g_pKeyPressHook; CHyprSignalListener g_pSwitchWorkspaceHook; CHyprSignalListener g_pAddMonitorHook; -CHyprSignalListener g_pPreRemoveMonitorHook; -CHyprSignalListener g_pRemoveMonitorHook; CHyprSignalListener g_pStartHook; -void reloadConfig(); - -namespace { - -using Config::Values::CBoolValue; -using Config::Values::CColorValue; -using Config::Values::CFloatValue; -using Config::Values::CIntValue; -using Config::Values::CStringValue; - -struct SPluginConfigValues { - SP panelColor, panelBorderColor, workspaceActiveBackground, workspaceInactiveBackground, workspaceActiveBorder, workspaceInactiveBorder; - SP panelHeight, panelBorderWidth, workspaceMargin, reservedArea, workspaceBorderSize; - SP adaptiveHeight, centerAligned, onBottom, hideBackgroundLayers, hideTopLayers, hideOverlayLayers, drawActiveWorkspace, hideRealLayers, affectStrut; - SP autoDrag, autoScroll, exitOnClick, switchOnDrop, exitOnSwitch, showNewWorkspace, showEmptyWorkspace, showSpecialWorkspace; - SP swipeFingers, swipeDistance, swipeForceSpeed, clickReleaseThresholdMs; - SP disableGestures, reverseSwipe, disableBlur; - SP swipeCancelRatio, swipeThreshold, swipeClosedPadding, workspaceScrollSpeed, overrideAnimSpeed, dragAlpha; - SP exitKey; -}; - -SPluginConfigValues g_pluginConfigValues; - -template -SP registerPluginValue(const SP& value) { - if (HyprlandAPI::addConfigValueV2(pHandle, value)) - return value; - - Log::logger->log(Log::ERR, "Hyprspace: failed to register config value {}", value->name()); - return nullptr; -} - -int readIntValue(const SP& value, int fallback) { - return value ? static_cast(value->value()) : fallback; -} - -bool readBoolValue(const SP& value, bool fallback) { - return value ? value->value() : fallback; -} - -float readFloatValue(const SP& value, float fallback) { - return value ? value->value() : fallback; -} - -std::string readStringValue(const SP& value, const std::string& fallback) { - return value ? value->value() : fallback; -} - -CHyprColor readColorValue(const SP& value, const CHyprColor& fallback) { - return value ? CHyprColor(static_cast(value->value())) : fallback; -} - -void endSwipeIfNeeded(const std::shared_ptr& widget) { - if (!widget || !widget->isSwiping()) - return; - - IPointer::SSwipeEndEvent cancelled; - cancelled.cancelled = true; - widget->endSwipe(cancelled); -} - -void teardownMonitorWidget(PHLMONITOR monitor) { - if (!monitor) - return; - - for (auto& widget : g_overviewWidgets) { - if (!widget) - continue; - - const auto owner = widget->getOwner(); - if (owner && owner != monitor) - continue; - - endSwipeIfNeeded(widget); - if (widget->isActive()) - widget->hide(); - widget->cleanup(monitor); - } -} - -void removeMonitorWidget(PHLMONITOR monitor) { - teardownMonitorWidget(monitor); - - g_overviewWidgets.erase(std::remove_if(g_overviewWidgets.begin(), g_overviewWidgets.end(), - [monitor](const std::shared_ptr& widget) { - if (!widget) - return true; - - const auto owner = widget->getOwner(); - return owner == monitor || !owner; - }), - g_overviewWidgets.end()); -} - -void registerConfigValues() { - g_pluginConfigValues.panelColor = registerPluginValue(SP(new CColorValue("plugin:hyprspace:panel_color", "Overview panel fill color", Config::panelBaseColor.getAsHex()))); - g_pluginConfigValues.panelBorderColor = registerPluginValue(SP(new CColorValue("plugin:hyprspace:panel_border_color", "Overview panel border color", Config::panelBorderColor.getAsHex()))); - g_pluginConfigValues.workspaceActiveBackground = registerPluginValue(SP(new CColorValue("plugin:hyprspace:workspace_active_background", "Active workspace preview background", Config::workspaceActiveBackground.getAsHex()))); - g_pluginConfigValues.workspaceInactiveBackground = registerPluginValue(SP(new CColorValue("plugin:hyprspace:workspace_inactive_background", "Inactive workspace preview background", Config::workspaceInactiveBackground.getAsHex()))); - g_pluginConfigValues.workspaceActiveBorder = registerPluginValue(SP(new CColorValue("plugin:hyprspace:workspace_active_border", "Active workspace preview border", Config::workspaceActiveBorder.getAsHex()))); - g_pluginConfigValues.workspaceInactiveBorder = registerPluginValue(SP(new CColorValue("plugin:hyprspace:workspace_inactive_border", "Inactive workspace preview border", Config::workspaceInactiveBorder.getAsHex()))); - - g_pluginConfigValues.panelHeight = registerPluginValue(SP(new CIntValue("plugin:hyprspace:panel_height", "Overview panel height in logical pixels", Config::panelHeight))); - g_pluginConfigValues.panelBorderWidth = registerPluginValue(SP(new CIntValue("plugin:hyprspace:panel_border_width", "Overview panel border width in pixels", Config::panelBorderWidth))); - g_pluginConfigValues.workspaceMargin = registerPluginValue(SP(new CIntValue("plugin:hyprspace:workspace_margin", "Spacing between workspace previews", Config::workspaceMargin))); - g_pluginConfigValues.reservedArea = registerPluginValue(SP(new CIntValue("plugin:hyprspace:reserved_area", "Padding reserved inside the overview panel", Config::reservedArea))); - g_pluginConfigValues.workspaceBorderSize = registerPluginValue(SP(new CIntValue("plugin:hyprspace:workspace_border_size", "Workspace preview border width", Config::workspaceBorderSize))); - - g_pluginConfigValues.adaptiveHeight = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:adaptive_height", "Reserved for future adaptive overview sizing", Config::adaptiveHeight))); - g_pluginConfigValues.centerAligned = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:center_aligned", "Center workspace previews when they fit", Config::centerAligned))); - g_pluginConfigValues.onBottom = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:on_bottom", "Render the overview panel at the bottom edge", Config::onBottom))); - g_pluginConfigValues.hideBackgroundLayers = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:hide_background_layers", "Skip background and bottom layer previews", Config::hideBackgroundLayers))); - g_pluginConfigValues.hideTopLayers = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:hide_top_layers", "Skip top layer previews", Config::hideTopLayers))); - g_pluginConfigValues.hideOverlayLayers = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:hide_overlay_layers", "Skip overlay layer previews", Config::hideOverlayLayers))); - g_pluginConfigValues.drawActiveWorkspace = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:draw_active_workspace", "Draw the active workspace contents in the overview", Config::drawActiveWorkspace))); - g_pluginConfigValues.hideRealLayers = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:hide_real_layers", "Temporarily hide real top and overlay layers while overview is active", Config::hideRealLayers))); - g_pluginConfigValues.affectStrut = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:affect_strut", "Reserve monitor space while the overview is visible", Config::affectStrut))); - - g_pluginConfigValues.autoDrag = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:auto_drag", "Start dragging a hovered window on press", Config::autoDrag))); - g_pluginConfigValues.autoScroll = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:auto_scroll", "Allow wheel scrolling outside the panel to switch workspaces", Config::autoScroll))); - g_pluginConfigValues.exitOnClick = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:exit_on_click", "Close the overview when clicking outside a workspace", Config::exitOnClick))); - g_pluginConfigValues.switchOnDrop = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:switch_on_drop", "Switch to a workspace after dropping a window into it", Config::switchOnDrop))); - g_pluginConfigValues.exitOnSwitch = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:exit_on_switch", "Close the overview after a workspace switch", Config::exitOnSwitch))); - g_pluginConfigValues.showNewWorkspace = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:show_new_workspace", "Append a synthetic empty workspace preview at the end", Config::showNewWorkspace))); - g_pluginConfigValues.showEmptyWorkspace = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:show_empty_workspace", "Display empty workspaces between populated ones", Config::showEmptyWorkspace))); - g_pluginConfigValues.showSpecialWorkspace = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:show_special_workspace", "Display the special workspace preview", Config::showSpecialWorkspace))); - - g_pluginConfigValues.disableGestures = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:disable_gestures", "Disable overview gesture handling", Config::disableGestures))); - g_pluginConfigValues.reverseSwipe = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:reverse_swipe", "Reverse overview swipe direction", Config::reverseSwipe))); - g_pluginConfigValues.swipeFingers = registerPluginValue(SP(new CIntValue("plugin:hyprspace:swipe_fingers", "Number of fingers required for overview swipes", Config::swipeFingers, {.min = 1}))); - g_pluginConfigValues.swipeDistance = registerPluginValue(SP(new CIntValue("plugin:hyprspace:swipe_distance", "Gesture distance divisor for overview swipes", Config::swipeDistance, {.min = 1}))); - g_pluginConfigValues.swipeForceSpeed = registerPluginValue(SP(new CIntValue("plugin:hyprspace:swipe_force_speed", "Swipe speed required to force open or close", Config::swipeForceSpeed, {.min = 0}))); - g_pluginConfigValues.swipeCancelRatio = registerPluginValue(SP(new CFloatValue("plugin:hyprspace:swipe_cancel_ratio", "Swipe travel ratio needed to complete the gesture", Config::swipeCancelRatio, {.min = 0.F, .max = 1.F}))); - g_pluginConfigValues.swipeThreshold = registerPluginValue(SP(new CFloatValue("plugin:hyprspace:swipe_threshold", "Swipe offset threshold that toggles visible state", Config::swipeThreshold, {.min = 0.F}))); - g_pluginConfigValues.swipeClosedPadding = registerPluginValue(SP(new CFloatValue("plugin:hyprspace:swipe_closed_padding", "Closed-state overshoot padding for swipe gestures", Config::swipeClosedPadding, {.min = 0.F}))); - g_pluginConfigValues.workspaceScrollSpeed = registerPluginValue(SP(new CFloatValue("plugin:hyprspace:workspace_scroll_speed", "Horizontal panel scroll multiplier for wheel and swipe input", Config::workspaceScrollSpeed, {.min = 0.F}))); - - g_pluginConfigValues.disableBlur = registerPluginValue(SP(new CBoolValue("plugin:hyprspace:disable_blur", "Disable blur while rendering the overview", Config::disableBlur))); - g_pluginConfigValues.overrideAnimSpeed = registerPluginValue(SP(new CFloatValue("plugin:hyprspace:override_anim_speed", "Override the window animation speed used by the overview", Config::overrideAnimSpeed, {.min = 0.F}))); - g_pluginConfigValues.dragAlpha = registerPluginValue(SP(new CFloatValue("plugin:hyprspace:drag_alpha", "Temporary alpha applied to dragged windows inside the overview", Config::dragAlpha, {.min = 0.F, .max = 1.F}))); - g_pluginConfigValues.exitKey = registerPluginValue(SP(new CStringValue("plugin:hyprspace:exit_key", "Keysyms used to close the overview, empty disables it", Config::exitKey))); - g_pluginConfigValues.clickReleaseThresholdMs = registerPluginValue(SP(new CIntValue("plugin:hyprspace:click_release_threshold_ms", "Maximum press duration that still counts as a click", Config::clickReleaseThresholdMs, {.min = 0}))); -} - -} // namespace - APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } -std::shared_ptr getWidgetForMonitor(PHLMONITORREF monitor) { +std::shared_ptr getWidgetForMonitor(PHLMONITORREF pMonitor) { for (auto& widget : g_overviewWidgets) { - if (!widget) - continue; - - if (widget->getOwner() == monitor) + if (!widget) continue; + if (!widget->getOwner()) continue; + if (widget->getOwner() == pMonitor) { return widget; + } } - return nullptr; } +// used to enforce the layout void refreshWidgets() { for (auto& widget : g_overviewWidgets) { - if (widget && widget->isActive()) - widget->show(); + if (widget != nullptr) + if (widget->isActive()) + widget->show(); } } -bool g_layoutNeedsRefresh = true; -float g_oAlpha = -1; +bool g_layoutNeedsRefresh = true; + +// for restroing dragged window's alpha value +float g_oAlpha = -1; void onRender(eRenderStage renderStage) { - if (compositorUnsafe()) - return; + // refresh layout after scheduled recalculation on monitors were carried out in renderMonitor if (renderStage == eRenderStage::RENDER_PRE) { if (g_layoutNeedsRefresh) { refreshWidgets(); g_layoutNeedsRefresh = false; } - } else if (renderStage == eRenderStage::RENDER_PRE_WINDOWS) { - const auto widget = getWidgetForMonitor(g_pHyprRenderer->m_renderData.pMonitor); - if (widget && widget->getOwner()) { - const auto dragTarget = g_layoutManager->dragController()->target(); - const auto curWindow = dragTarget ? dragTarget->window() : nullptr; - if (curWindow && widget->isActive()) { - g_oAlpha = curWindow->alpha(Desktop::View::WINDOW_ALPHA_ACTIVE)->goal(); - curWindow->alpha(Desktop::View::WINDOW_ALPHA_ACTIVE)->setValueAndWarp(0); - } else - g_oAlpha = -1; - } else - g_oAlpha = -1; - } else if (renderStage == eRenderStage::RENDER_POST_WINDOWS) { - const auto widget = getWidgetForMonitor(g_pHyprRenderer->m_renderData.pMonitor); - if (!widget || !widget->getOwner()) { - g_oAlpha = -1; - return; - } + } + else if (renderStage == eRenderStage::RENDER_PRE_WINDOWS) { - widget->draw(); - if (g_oAlpha == -1) - return; + const auto widget = getWidgetForMonitor(g_pHyprRenderer->m_renderData.pMonitor); + if (widget != nullptr) + if (widget->getOwner()) { + //widget->draw(); + const auto dragTarget = g_layoutManager->dragController()->target(); + const auto curWindow = dragTarget ? dragTarget->window() : nullptr; + if (curWindow) { + if (widget->isActive()) { + g_oAlpha = curWindow->alpha(Desktop::View::WINDOW_ALPHA_ACTIVE)->goal(); + curWindow->alpha(Desktop::View::WINDOW_ALPHA_ACTIVE)->setValueAndWarp(0); // HACK: hide dragged window for the actual pass + } + } + else g_oAlpha = -1; + } + else g_oAlpha = -1; + else g_oAlpha = -1; - const auto dragTarget = g_layoutManager->dragController()->target(); - const auto curWindow = dragTarget ? dragTarget->window() : nullptr; - if (!curWindow) { - g_oAlpha = -1; - return; - } + } + else if (renderStage == eRenderStage::RENDER_POST_WINDOWS) { - if (!pRenderWindow) { - g_oAlpha = -1; - return; - } + const auto widget = getWidgetForMonitor(g_pHyprRenderer->m_renderData.pMonitor); + + if (widget != nullptr) + if (widget->getOwner()) { + widget->draw(); + if (g_oAlpha != -1) { + const auto dragTarget = g_layoutManager->dragController()->target(); + const auto curWindow = dragTarget ? dragTarget->window() : nullptr; + if (curWindow) { + curWindow->alpha(Desktop::View::WINDOW_ALPHA_ACTIVE)->setValueAndWarp(config.dragAlpha->value()); + curWindow->m_ruleApplicator->noBlur().unset(Desktop::Types::PRIORITY_SET_PROP); + const auto time = Time::steadyNow(); + (*(tRenderWindow)pRenderWindow)(g_pHyprRenderer.get(), curWindow, widget->getOwner(), time, true, Render::RENDER_PASS_MAIN, false, false); + curWindow->m_ruleApplicator->noBlur().unset(Desktop::Types::PRIORITY_SET_PROP); + curWindow->alpha(Desktop::View::WINDOW_ALPHA_ACTIVE)->setValueAndWarp(g_oAlpha); + } + } + g_oAlpha = -1; + } - curWindow->alpha(Desktop::View::WINDOW_ALPHA_ACTIVE)->setValueAndWarp(Config::dragAlpha); - curWindow->m_ruleApplicator->noBlur().set(true, Desktop::Types::PRIORITY_SET_PROP); - const auto time = Time::steadyNow(); - (*(tRenderWindow)pRenderWindow)(g_pHyprRenderer.get(), curWindow, widget->getOwner(), time, true, Render::RENDER_PASS_MAIN, false, false); - curWindow->m_ruleApplicator->noBlur().unset(Desktop::Types::PRIORITY_SET_PROP); - curWindow->alpha(Desktop::View::WINDOW_ALPHA_ACTIVE)->setValueAndWarp(g_oAlpha); - g_oAlpha = -1; } } -void onWorkspaceChange(PHLWORKSPACE workspace) { - if (!workspace || !workspace->m_monitor) - return; +// event hook, currently this is only here to re-hide top layer panels on workspace change +void onWorkspaceChange(PHLWORKSPACE pWorkspace) { - const auto widget = getWidgetForMonitor(monitorFromID(workspace->m_monitor->m_id)); - if (widget && widget->isActive()) - widget->show(); + if (!pWorkspace) return; + + auto widget = getWidgetForMonitor(State::monitorState()->query().id(pWorkspace->m_monitor->m_id).run()); + if (widget != nullptr) + if (widget->isActive()) + widget->show(); } +// event hook for click and drag interaction void onMouseButton(const IPointer::SButtonEvent& event, SCallbackInfo& info) { const SP pointer = g_pSeatManager->m_mouse.lock(); - if (!pointer || event.button != BTN_LEFT) + if (!pointer) return; - const auto monitor = monitorFromCursor(); - if (!monitor) - return; + if (event.button != BTN_LEFT) return; + + const auto pressed = event.state == WL_POINTER_BUTTON_STATE_PRESSED; + const auto pMonitor = State::monitorState()->query().vec(g_pInputManager->getMouseCoordsInternal()).run(); + if (pMonitor) { + const auto widget = getWidgetForMonitor(pMonitor); + if (widget) { + if (widget->isActive()) { + info.cancelled = !widget->buttonEvent(pressed, g_pInputManager->getMouseCoordsInternal()); + } + } + } - const auto widget = getWidgetForMonitor(monitor); - if (widget && widget->isActive()) - info.cancelled = !widget->buttonEvent(event.state == WL_POINTER_BUTTON_STATE_PRESSED, g_pInputManager->getMouseCoordsInternal()); } +// event hook for scrolling through panel and workspaces void onMouseAxis(const IPointer::SAxisEvent& event, SCallbackInfo& info) { - const auto monitor = monitorFromCursor(); - if (!monitor) - return; - const auto widget = getWidgetForMonitor(monitor); - if (widget && widget->isActive()) - info.cancelled = !widget->axisEvent(event.delta, event.axis, g_pInputManager->getMouseCoordsInternal()); + const auto pMonitor = State::monitorState()->query().vec(g_pInputManager->getMouseCoordsInternal()).run(); + if (pMonitor) { + const auto widget = getWidgetForMonitor(pMonitor); + if (widget) { + if (widget->isActive()) { + info.cancelled = !widget->axisEvent(event.delta, event.axis, g_pInputManager->getMouseCoordsInternal()); + } + } + } + } +// event hook for swipe void onSwipeBegin(const IPointer::SSwipeBeginEvent& event, SCallbackInfo& info) { - if (Config::disableGestures) - return; - const auto widget = getWidgetForMonitor(monitorFromCursor()); - if (widget) + if (config.disableGestures->value()) return; + + const auto widget = getWidgetForMonitor(State::monitorState()->query().vec(g_pInputManager->getMouseCoordsInternal()).run()); + if (widget != nullptr) widget->beginSwipe(event); - for (auto& other : g_overviewWidgets) { - if (other && other != widget && other->isSwiping()) { - IPointer::SSwipeEndEvent cancelled; - cancelled.cancelled = true; - other->endSwipe(cancelled); + // end other widget swipe + for (auto& w : g_overviewWidgets) { + if (w != widget && w->isSwiping()) { + IPointer::SSwipeEndEvent dummy; + dummy.cancelled = true; + w->endSwipe(dummy); } } } +// event hook for update swipe, most of the swiping mechanics are here void onSwipeUpdate(const IPointer::SSwipeUpdateEvent& event, SCallbackInfo& info) { - if (Config::disableGestures) - return; - const auto widget = getWidgetForMonitor(monitorFromCursor()); - if (widget) + if (config.disableGestures->value()) return; + + const auto widget = getWidgetForMonitor(State::monitorState()->query().vec(g_pInputManager->getMouseCoordsInternal()).run()); + if (widget != nullptr) info.cancelled = !widget->updateSwipe(event); } +// event hook for end swipe void onSwipeEnd(const IPointer::SSwipeEndEvent& event, SCallbackInfo& info) { - if (Config::disableGestures) - return; - const auto widget = getWidgetForMonitor(monitorFromCursor()); - if (widget) + if (config.disableGestures->value()) return; + + const auto widget = getWidgetForMonitor(State::monitorState()->query().vec(g_pInputManager->getMouseCoordsInternal()).run()); + if (widget != nullptr) widget->endSwipe(event); } +// Close overview with configurable key void onKeyPress(const IKeyboard::SKeyEvent& event, SCallbackInfo& info) { const SP keyboard = g_pSeatManager->m_keyboard.lock(); - if (!keyboard || !keyboard->m_xkbSymState || Config::exitKey.empty()) + if (!keyboard || !keyboard->m_xkbSymState) return; - const auto keycode = event.keycode + 8; - const xkb_keysym_t keysym = xkb_state_key_get_one_sym(keyboard->m_xkbSymState, keycode); - const xkb_keysym_t configured = xkb_keysym_from_name(Config::exitKey.c_str(), XKB_KEYSYM_CASE_INSENSITIVE); + const auto keycode = event.keycode + 8; // Because to xkbcommon it's +8 from libinput + const xkb_keysym_t keysym = xkb_state_key_get_one_sym(keyboard->m_xkbSymState, keycode); - if (configured == XKB_KEY_NoSymbol || keysym != configured) + const std::string cfgExitKey = config.exitKey->value(); + if (cfgExitKey.empty()) return; - bool overviewActive = false; - for (auto& widget : g_overviewWidgets) { - if (widget && widget->isActive()) { - widget->hide(); - overviewActive = true; + const xkb_keysym_t cfgExitKeysym = xkb_keysym_from_name(cfgExitKey.c_str(), XKB_KEYSYM_CASE_INSENSITIVE); + + if (keysym == cfgExitKeysym) { + // close all panels + bool overviewActive = false; + for (auto& widget : g_overviewWidgets) { + if (widget != nullptr && widget->isActive()) { + widget->hide(); + overviewActive = true; + } } + // Only cancel event if overview was active and closed + if (overviewActive) + info.cancelled = true; } - - if (overviewActive) - info.cancelled = true; } PHLMONITOR g_pTouchedMonitor; @@ -417,24 +245,25 @@ void onTouchDown(const ITouch::SDownEvent& event, SCallbackInfo& info) { if (!event.device) return; - auto targetMonitor = monitorFromName(!event.device->m_boundOutput.empty() ? event.device->m_boundOutput : ""); - targetMonitor = targetMonitor ? targetMonitor : monitorFromCursor(); + auto targetMonitor = State::monitorState()->query().name(!event.device->m_boundOutput.empty() ? event.device->m_boundOutput : "").run(); + targetMonitor = targetMonitor ? targetMonitor : State::monitorState()->query().vec(g_pInputManager->getMouseCoordsInternal()).run(); const auto widget = getWidgetForMonitor(targetMonitor); - if (widget && targetMonitor && widget->isActive()) { - const Vector2D pos = targetMonitor->m_position + event.pos * targetMonitor->m_size; - info.cancelled = !widget->buttonEvent(true, pos); - if (info.cancelled) { - g_pTouchedMonitor = targetMonitor; - Pointer::pointerController()->warpTo(pos); - g_pInputManager->refocus(); + if (widget != nullptr && targetMonitor != nullptr) { + if (widget->isActive()) { + Vector2D pos = targetMonitor->m_position + event.pos * targetMonitor->m_size; + info.cancelled = !widget->buttonEvent(true, pos); + if (info.cancelled) { + g_pTouchedMonitor = targetMonitor; + Pointer::pointerController()->warpTo(pos); + g_pInputManager->refocus(); + } } } } void onTouchMove(const ITouch::SMotionEvent& event, SCallbackInfo& info) { - if (!g_pTouchedMonitor) - return; + if (g_pTouchedMonitor == nullptr) return; Pointer::pointerController()->warpTo(g_pTouchedMonitor->m_position + g_pTouchedMonitor->m_size * event.pos); g_pInputManager->simulateMouseMovement(); @@ -442,214 +271,204 @@ void onTouchMove(const ITouch::SMotionEvent& event, SCallbackInfo& info) { void onTouchUp(const ITouch::SUpEvent& event, SCallbackInfo& info) { const auto widget = getWidgetForMonitor(g_pTouchedMonitor); - if (widget && g_pTouchedMonitor && widget->isActive()) - info.cancelled = !widget->buttonEvent(false, g_pInputManager->getMouseCoordsInternal()); + if (widget != nullptr && g_pTouchedMonitor != nullptr) + if (widget->isActive()) + info.cancelled = !widget->buttonEvent(false, g_pInputManager->getMouseCoordsInternal()); g_pTouchedMonitor = nullptr; } -static SDispatchResult dispatchToggleOverview(std::string arg) { - const auto currentMonitor = monitorFromCursor(); - const auto widget = getWidgetForMonitor(currentMonitor); - if (!widget) - return {}; - - if (arg.contains("all")) { - const bool anyActive = widget->isActive(); - for (auto& overviewWidget : g_overviewWidgets) { - if (!overviewWidget) - continue; - - if (anyActive && overviewWidget->isActive()) - overviewWidget->hide(); - else if (!anyActive && !overviewWidget->isActive()) - overviewWidget->show(); +SDispatchResult Dispatchers::dispatchToggleOverview(std::string arg) { + auto currentMonitor = State::monitorState()->query().vec(g_pInputManager->getMouseCoordsInternal()).run(); + auto widget = getWidgetForMonitor(currentMonitor); + if (widget) { + if (arg.contains("all")) { + if (widget->isActive()) { + for (auto& widget : g_overviewWidgets) { + if (widget != nullptr) + if (widget->isActive()) + widget->hide(); + } + } + else { + for (auto& widget : g_overviewWidgets) { + if (widget != nullptr) + if (!widget->isActive()) + widget->show(); + } + } } - } else - widget->isActive() ? widget->hide() : widget->show(); - - return {}; + else + widget->isActive() ? widget->hide() : widget->show(); + } + return SDispatchResult{}; } -static SDispatchResult dispatchOpenOverview(std::string arg) { +SDispatchResult Dispatchers::dispatchOpenOverview(std::string arg) { if (arg.contains("all")) { for (auto& widget : g_overviewWidgets) { - if (widget && !widget->isActive()) - widget->show(); + if (!widget->isActive()) widget->show(); } - } else { - const auto widget = getWidgetForMonitor(monitorFromCursor()); - if (widget && !widget->isActive()) - widget->show(); } - - return {}; + else { + auto currentMonitor = State::monitorState()->query().vec(g_pInputManager->getMouseCoordsInternal()).run(); + auto widget = getWidgetForMonitor(currentMonitor); + if (widget) + if (!widget->isActive()) widget->show(); + } + return SDispatchResult{}; } -static SDispatchResult dispatchCloseOverview(std::string arg) { +SDispatchResult Dispatchers::dispatchCloseOverview(std::string arg) { if (arg.contains("all")) { for (auto& widget : g_overviewWidgets) { - if (widget && widget->isActive()) - widget->hide(); + if (widget->isActive()) widget->hide(); } - } else { - const auto widget = getWidgetForMonitor(monitorFromCursor()); - if (widget && widget->isActive()) - widget->hide(); } - - return {}; -} - -int luaOverview(lua_State* L) { - const char* arg = luaL_optstring(L, 1, "toggle"); - const std::string command = arg ? arg : "toggle"; - - // Pull the latest Lua-side config into the runtime snapshot before changing overview state. - reloadConfig(); - - if (command == "toggle") - dispatchToggleOverview(""); - else if (command == "open") - dispatchOpenOverview(""); - else if (command == "close") - dispatchCloseOverview(""); - else if (command == "toggle_all") - dispatchToggleOverview("all"); - else if (command == "open_all") - dispatchOpenOverview("all"); - else if (command == "close_all") - dispatchCloseOverview("all"); - else - return luaL_error(L, "Hyprspace.overview: expected toggle, open, close, toggle_all, open_all, or close_all"); - - return 0; + else { + auto currentMonitor = State::monitorState()->query().vec(g_pInputManager->getMouseCoordsInternal()).run(); + auto widget = getWidgetForMonitor(currentMonitor); + if (widget) + if (widget->isActive()) widget->hide(); + } + return SDispatchResult{}; } void* findFunctionBySymbol(HANDLE inHandle, const std::string func, const std::string sym) { - for (const auto& match : HyprlandAPI::findFunctionsByName(inHandle, func)) { - if (match.demangled.contains(sym)) - return match.address; + // should return all functions + auto funcSearch = HyprlandAPI::findFunctionsByName(inHandle, func); + for (auto f : funcSearch) { + if (f.demangled.contains(sym)) + return f.address; } - return nullptr; } void reloadConfig() { - Config::panelBaseColor = readColorValue(g_pluginConfigValues.panelColor, Config::panelBaseColor); - Config::panelBorderColor = readColorValue(g_pluginConfigValues.panelBorderColor, Config::panelBorderColor); - Config::workspaceActiveBackground = readColorValue(g_pluginConfigValues.workspaceActiveBackground, Config::workspaceActiveBackground); - Config::workspaceInactiveBackground = readColorValue(g_pluginConfigValues.workspaceInactiveBackground, Config::workspaceInactiveBackground); - Config::workspaceActiveBorder = readColorValue(g_pluginConfigValues.workspaceActiveBorder, Config::workspaceActiveBorder); - Config::workspaceInactiveBorder = readColorValue(g_pluginConfigValues.workspaceInactiveBorder, Config::workspaceInactiveBorder); - - Config::panelHeight = readIntValue(g_pluginConfigValues.panelHeight, Config::panelHeight); - Config::panelBorderWidth = readIntValue(g_pluginConfigValues.panelBorderWidth, Config::panelBorderWidth); - Config::workspaceMargin = readIntValue(g_pluginConfigValues.workspaceMargin, Config::workspaceMargin); - Config::reservedArea = readIntValue(g_pluginConfigValues.reservedArea, Config::reservedArea); - Config::workspaceBorderSize = readIntValue(g_pluginConfigValues.workspaceBorderSize, Config::workspaceBorderSize); - Config::adaptiveHeight = readBoolValue(g_pluginConfigValues.adaptiveHeight, Config::adaptiveHeight); - Config::centerAligned = readBoolValue(g_pluginConfigValues.centerAligned, Config::centerAligned); - Config::onBottom = readBoolValue(g_pluginConfigValues.onBottom, Config::onBottom); - Config::hideBackgroundLayers = readBoolValue(g_pluginConfigValues.hideBackgroundLayers, Config::hideBackgroundLayers); - Config::hideTopLayers = readBoolValue(g_pluginConfigValues.hideTopLayers, Config::hideTopLayers); - Config::hideOverlayLayers = readBoolValue(g_pluginConfigValues.hideOverlayLayers, Config::hideOverlayLayers); - Config::drawActiveWorkspace = readBoolValue(g_pluginConfigValues.drawActiveWorkspace, Config::drawActiveWorkspace); - Config::hideRealLayers = readBoolValue(g_pluginConfigValues.hideRealLayers, Config::hideRealLayers); - Config::affectStrut = readBoolValue(g_pluginConfigValues.affectStrut, Config::affectStrut); - - Config::autoDrag = readBoolValue(g_pluginConfigValues.autoDrag, Config::autoDrag); - Config::autoScroll = readBoolValue(g_pluginConfigValues.autoScroll, Config::autoScroll); - Config::exitOnClick = readBoolValue(g_pluginConfigValues.exitOnClick, Config::exitOnClick); - Config::switchOnDrop = readBoolValue(g_pluginConfigValues.switchOnDrop, Config::switchOnDrop); - Config::exitOnSwitch = readBoolValue(g_pluginConfigValues.exitOnSwitch, Config::exitOnSwitch); - Config::showNewWorkspace = readBoolValue(g_pluginConfigValues.showNewWorkspace, Config::showNewWorkspace); - Config::showEmptyWorkspace = readBoolValue(g_pluginConfigValues.showEmptyWorkspace, Config::showEmptyWorkspace); - Config::showSpecialWorkspace = readBoolValue(g_pluginConfigValues.showSpecialWorkspace, Config::showSpecialWorkspace); - - Config::disableGestures = readBoolValue(g_pluginConfigValues.disableGestures, Config::disableGestures); - Config::reverseSwipe = readBoolValue(g_pluginConfigValues.reverseSwipe, Config::reverseSwipe); - Config::swipeFingers = readIntValue(g_pluginConfigValues.swipeFingers, Config::swipeFingers); - Config::swipeDistance = readIntValue(g_pluginConfigValues.swipeDistance, Config::swipeDistance); - Config::swipeForceSpeed = readIntValue(g_pluginConfigValues.swipeForceSpeed, Config::swipeForceSpeed); - Config::swipeCancelRatio = readFloatValue(g_pluginConfigValues.swipeCancelRatio, Config::swipeCancelRatio); - Config::swipeThreshold = readFloatValue(g_pluginConfigValues.swipeThreshold, Config::swipeThreshold); - Config::swipeClosedPadding = readFloatValue(g_pluginConfigValues.swipeClosedPadding, Config::swipeClosedPadding); - Config::workspaceScrollSpeed = readFloatValue(g_pluginConfigValues.workspaceScrollSpeed, Config::workspaceScrollSpeed); - - Config::disableBlur = readBoolValue(g_pluginConfigValues.disableBlur, Config::disableBlur); - Config::overrideAnimSpeed = readFloatValue(g_pluginConfigValues.overrideAnimSpeed, Config::overrideAnimSpeed); - Config::dragAlpha = readFloatValue(g_pluginConfigValues.dragAlpha, Config::dragAlpha); - Config::exitKey = readStringValue(g_pluginConfigValues.exitKey, Config::exitKey); - Config::clickReleaseThresholdMs = readIntValue(g_pluginConfigValues.clickReleaseThresholdMs, Config::clickReleaseThresholdMs); - for (auto& widget : g_overviewWidgets) { - if (!widget) - continue; - - if (widget->isActive()) - widget->hide(); - endSwipeIfNeeded(widget); widget->updateConfig(); + if (widget->isActive() || widget->isSwiping()) { + widget->hide(); + IPointer::SSwipeEndEvent dummy; + dummy.cancelled = true; + widget->endSwipe(dummy); + } } + + // get number of workspaces from hyprsplit or split-monitor-workspaces plugin config + numWorkspaces = -1; + auto numWorkspacesOpt = HyprConfig::getHyprsplitNumWorkspaces(); + if (!numWorkspacesOpt.has_value()) + numWorkspacesOpt = HyprConfig::getSplitMonitorWorkspacesCount(); + if (numWorkspacesOpt.has_value()) + numWorkspaces = numWorkspacesOpt.value(); + + // TODO: schedule frame for monitor? } void registerMonitors() { - for (auto& monitor : State::monitorState()->monitors()) { - if (getWidgetForMonitor(monitor)) - continue; - - g_overviewWidgets.emplace_back(std::make_shared(monitor->m_id)); + // create a widget for each monitor + for (auto& m : State::monitorState()->monitors()) { + if (getWidgetForMonitor(m) != nullptr) continue; + CHyprspaceWidget* widget = new CHyprspaceWidget(m->m_id); + g_overviewWidgets.emplace_back(widget); } } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE inHandle) { pHandle = inHandle; - Log::logger->log(Log::DEBUG, "Loading Hyprspace"); - - registerConfigValues(); - HyprlandAPI::addLuaFunction(pHandle, "Hyprspace", "overview", ::luaOverview); + Log::logger->log(Log::DEBUG, "Loading overview plugin"); + + HyprlandAPI::addConfigValueV2(pHandle, config.panelBaseColor); + HyprlandAPI::addConfigValueV2(pHandle, config.panelBorderColor); + HyprlandAPI::addConfigValueV2(pHandle, config.workspaceActiveBackground); + HyprlandAPI::addConfigValueV2(pHandle, config.workspaceInactiveBackground); + HyprlandAPI::addConfigValueV2(pHandle, config.workspaceActiveBorder); + HyprlandAPI::addConfigValueV2(pHandle, config.workspaceInactiveBorder); + + HyprlandAPI::addConfigValueV2(pHandle, config.panelHeight); + HyprlandAPI::addConfigValueV2(pHandle, config.panelBorderWidth); + HyprlandAPI::addConfigValueV2(pHandle, config.workspaceMargin); + HyprlandAPI::addConfigValueV2(pHandle, config.workspaceBorderSize); + HyprlandAPI::addConfigValueV2(pHandle, config.reservedArea); + HyprlandAPI::addConfigValueV2(pHandle, config.adaptiveHeight); + HyprlandAPI::addConfigValueV2(pHandle, config.centerAligned); + HyprlandAPI::addConfigValueV2(pHandle, config.onBottom); + HyprlandAPI::addConfigValueV2(pHandle, config.hideBackgroundLayers); + HyprlandAPI::addConfigValueV2(pHandle, config.hideTopLayers); + HyprlandAPI::addConfigValueV2(pHandle, config.hideOverlayLayers); + HyprlandAPI::addConfigValueV2(pHandle, config.drawActiveWorkspace); + HyprlandAPI::addConfigValueV2(pHandle, config.hideRealLayers); + HyprlandAPI::addConfigValueV2(pHandle, config.affectStrut); + + HyprlandAPI::addConfigValueV2(pHandle, config.overrideGaps); + HyprlandAPI::addConfigValueV2(pHandle, config.gapsIn); + HyprlandAPI::addConfigValueV2(pHandle, config.gapsOut); + + HyprlandAPI::addConfigValueV2(pHandle, config.autoDrag); + HyprlandAPI::addConfigValueV2(pHandle, config.autoScroll); + HyprlandAPI::addConfigValueV2(pHandle, config.exitOnClick); + HyprlandAPI::addConfigValueV2(pHandle, config.switchOnDrop); + HyprlandAPI::addConfigValueV2(pHandle, config.exitOnSwitch); + HyprlandAPI::addConfigValueV2(pHandle, config.showNewWorkspace); + HyprlandAPI::addConfigValueV2(pHandle, config.showEmptyWorkspace); + HyprlandAPI::addConfigValueV2(pHandle, config.showSpecialWorkspace); + + HyprlandAPI::addConfigValueV2(pHandle, config.disableGestures); + HyprlandAPI::addConfigValueV2(pHandle, config.reverseSwipe); + + HyprlandAPI::addConfigValueV2(pHandle, config.disableBlur); + HyprlandAPI::addConfigValueV2(pHandle, config.overrideAnimSpeed); + HyprlandAPI::addConfigValueV2(pHandle, config.dragAlpha); + HyprlandAPI::addConfigValueV2(pHandle, config.exitKey); g_pConfigReloadHook = Event::bus()->m_events.config.reloaded.listen([]() { reloadConfig(); }); - g_pStartHook = Event::bus()->m_events.start.listen([]() { + g_pStartHook = Event::bus()->m_events.start.listen([]() { reloadConfig(); registerMonitors(); }); HyprlandAPI::reloadConfig(); - HyprlandAPI::addDispatcherV2(pHandle, "overview:toggle", ::dispatchToggleOverview); - HyprlandAPI::addDispatcherV2(pHandle, "overview:open", ::dispatchOpenOverview); - HyprlandAPI::addDispatcherV2(pHandle, "overview:close", ::dispatchCloseOverview); - - g_pRenderHook = Event::bus()->m_events.render.stage.listen([](eRenderStage stage) { onRender(stage); }); - g_pOpenLayerHook = Event::bus()->m_events.layer.opened.listen([](PHLLS) { g_layoutNeedsRefresh = true; }); - g_pCloseLayerHook = Event::bus()->m_events.layer.closed.listen([](PHLLS) { g_layoutNeedsRefresh = true; }); - g_pMouseButtonHook = listenCancellable(Event::bus()->m_events.input.mouse.button, onMouseButton); - g_pMouseAxisHook = listenCancellable(Event::bus()->m_events.input.mouse.axis, onMouseAxis); - g_pTouchDownHook = listenCancellable(Event::bus()->m_events.input.touch.down, onTouchDown); - g_pTouchMoveHook = listenCancellable(Event::bus()->m_events.input.touch.motion, onTouchMove); - g_pTouchUpHook = listenCancellable(Event::bus()->m_events.input.touch.up, onTouchUp); - g_pSwipeBeginHook = listenCancellable(Event::bus()->m_events.gesture.swipe.begin, onSwipeBegin); - g_pSwipeUpdateHook = listenCancellable(Event::bus()->m_events.gesture.swipe.update, onSwipeUpdate); - g_pSwipeEndHook = listenCancellable(Event::bus()->m_events.gesture.swipe.end, onSwipeEnd); - g_pKeyPressHook = listenCancellable(Event::bus()->m_events.input.keyboard.key, onKeyPress); + HyprlandAPI::addDispatcherV2(pHandle, "overview:toggle", Dispatchers::dispatchToggleOverview); + HyprlandAPI::addDispatcherV2(pHandle, "overview:open", Dispatchers::dispatchOpenOverview); + HyprlandAPI::addDispatcherV2(pHandle, "overview:close", Dispatchers::dispatchCloseOverview); + + registerLuaBindings(pHandle); + + g_pRenderHook = Event::bus()->m_events.render.stage.listen([](eRenderStage stage) { onRender(stage); }); + + // refresh on layer change + g_pOpenLayerHook = Event::bus()->m_events.layer.opened.listen([](PHLLS) { g_layoutNeedsRefresh = true; }); + g_pCloseLayerHook = Event::bus()->m_events.layer.closed.listen([](PHLLS) { g_layoutNeedsRefresh = true; }); + + + g_pMouseButtonHook = listenCancellable(Event::bus()->m_events.input.mouse.button, onMouseButton); + g_pMouseAxisHook = listenCancellable(Event::bus()->m_events.input.mouse.axis, onMouseAxis); + + g_pTouchDownHook = listenCancellable(Event::bus()->m_events.input.touch.down, onTouchDown); + g_pTouchMoveHook = listenCancellable(Event::bus()->m_events.input.touch.motion, onTouchMove); + g_pTouchUpHook = listenCancellable(Event::bus()->m_events.input.touch.up, onTouchUp); + + g_pSwipeBeginHook = listenCancellable(Event::bus()->m_events.gesture.swipe.begin, onSwipeBegin); + g_pSwipeUpdateHook = listenCancellable(Event::bus()->m_events.gesture.swipe.update, onSwipeUpdate); + g_pSwipeEndHook = listenCancellable(Event::bus()->m_events.gesture.swipe.end, onSwipeEnd); + + g_pKeyPressHook = listenCancellable(Event::bus()->m_events.input.keyboard.key, onKeyPress); + g_pSwitchWorkspaceHook = Event::bus()->m_events.workspace.active.listen(onWorkspaceChange); pRenderWindow = findFunctionBySymbol(pHandle, "renderWindow", "IHyprRenderer::renderWindow"); if (!pRenderWindow) pRenderWindow = findFunctionBySymbol(pHandle, "renderWindow", "CHyprRenderer::renderWindow"); - pRenderLayer = findFunctionBySymbol(pHandle, "renderLayer", "IHyprRenderer::renderLayer"); if (!pRenderLayer) pRenderLayer = findFunctionBySymbol(pHandle, "renderLayer", "CHyprRenderer::renderLayer"); registerMonitors(); - g_pAddMonitorHook = Event::bus()->m_events.monitor.added.listen([](PHLMONITOR) { registerMonitors(); }); - g_pPreRemoveMonitorHook = Event::bus()->m_events.monitor.preRemoved.listen([](PHLMONITOR monitor) { teardownMonitorWidget(monitor); }); - g_pRemoveMonitorHook = Event::bus()->m_events.monitor.removed.listen([](PHLMONITOR monitor) { removeMonitorWidget(monitor); }); + g_pAddMonitorHook = Event::bus()->m_events.monitor.added.listen([](PHLMONITOR) { registerMonitors(); }); - return {"Hyprspace", "Workspace overview", "KZdkm", "0.3"}; + return {"Hyprspace", "Workspace overview", "KZdkm", "0.1"}; } APICALL EXPORT void PLUGIN_EXIT() { @@ -668,22 +487,11 @@ APICALL EXPORT void PLUGIN_EXIT() { g_pKeyPressHook.reset(); g_pSwitchWorkspaceHook.reset(); g_pAddMonitorHook.reset(); - g_pPreRemoveMonitorHook.reset(); - g_pRemoveMonitorHook.reset(); g_pStartHook.reset(); - for (auto& widget : g_overviewWidgets) { - if (!widget) - continue; - - endSwipeIfNeeded(widget); - if (widget->isActive()) - widget->hide(); - widget->cleanup(widget->getOwner()); - } g_overviewWidgets.clear(); pRenderWindow = nullptr; - pRenderLayer = nullptr; - pHandle = nullptr; + pRenderLayer = nullptr; + pHandle = nullptr; }