diff --git a/src/Compositor.cpp b/src/Compositor.cpp index fb02bf40..c1294480 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -454,10 +454,17 @@ CWindow* CCompositor::vectorToWindow(const Vector2D& pos) { } } + // pinned + for (auto w = m_vWindows.rbegin(); w != m_vWindows.rend(); w++) { + wlr_box box = {(*w)->m_vRealPosition.vec().x, (*w)->m_vRealPosition.vec().y, (*w)->m_vRealSize.vec().x, (*w)->m_vRealSize.vec().y}; + if (wlr_box_contains_point(&box, pos.x, pos.y) && (*w)->m_bIsMapped && (*w)->m_bIsFloating && !(*w)->m_bHidden && (*w)->m_bPinned) + return w->get(); + } + // first loop over floating cuz they're above, m_vWindows should be sorted bottom->top, for tiled it doesn't matter. for (auto w = m_vWindows.rbegin(); w != m_vWindows.rend(); w++) { wlr_box box = {(*w)->m_vRealPosition.vec().x, (*w)->m_vRealPosition.vec().y, (*w)->m_vRealSize.vec().x, (*w)->m_vRealSize.vec().y}; - if (wlr_box_contains_point(&box, pos.x, pos.y) && (*w)->m_bIsMapped && (*w)->m_bIsFloating && isWorkspaceVisible((*w)->m_iWorkspaceID) && !(*w)->m_bHidden) + if (wlr_box_contains_point(&box, pos.x, pos.y) && (*w)->m_bIsMapped && (*w)->m_bIsFloating && isWorkspaceVisible((*w)->m_iWorkspaceID) && !(*w)->m_bHidden && !(*w)->m_bPinned) return w->get(); } @@ -517,10 +524,29 @@ CWindow* CCompositor::vectorToWindowIdeal(const Vector2D& pos) { } } + // pinned windows on top of floating regardless + for (auto w = m_vWindows.rbegin(); w != m_vWindows.rend(); w++) { + wlr_box box = {(*w)->m_vRealPosition.vec().x, (*w)->m_vRealPosition.vec().y, (*w)->m_vRealSize.vec().x, (*w)->m_vRealSize.vec().y}; + if ((*w)->m_bIsFloating && (*w)->m_bIsMapped && !(*w)->m_bHidden && !(*w)->m_bX11ShouldntFocus && (*w)->m_bPinned) { + if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y)) + return w->get(); + + if (!(*w)->m_bIsX11) { + wlr_surface* resultSurf = nullptr; + Vector2D origin = (*w)->m_vRealPosition.vec(); + SExtensionFindingData data = {origin, pos, &resultSurf}; + wlr_xdg_surface_for_each_popup_surface((*w)->m_uSurface.xdg, findExtensionForVector2D, &data); + + if (resultSurf) + return w->get(); + } + } + } + // first loop over floating cuz they're above, m_lWindows should be sorted bottom->top, for tiled it doesn't matter. for (auto w = m_vWindows.rbegin(); w != m_vWindows.rend(); w++) { wlr_box box = {(*w)->m_vRealPosition.vec().x, (*w)->m_vRealPosition.vec().y, (*w)->m_vRealSize.vec().x, (*w)->m_vRealSize.vec().y}; - if ((*w)->m_bIsFloating && (*w)->m_bIsMapped && isWorkspaceVisible((*w)->m_iWorkspaceID) && !(*w)->m_bHidden && !(*w)->m_bX11ShouldntFocus) { + if ((*w)->m_bIsFloating && (*w)->m_bIsMapped && isWorkspaceVisible((*w)->m_iWorkspaceID) && !(*w)->m_bHidden && !(*w)->m_bX11ShouldntFocus && !(*w)->m_bPinned) { if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y)) return w->get(); @@ -574,10 +600,17 @@ CWindow* CCompositor::windowFromCursor() { } } + // pinned + for (auto w = m_vWindows.rbegin(); w != m_vWindows.rend(); w++) { + wlr_box box = {(*w)->m_vRealPosition.vec().x, (*w)->m_vRealPosition.vec().y, (*w)->m_vRealSize.vec().x, (*w)->m_vRealSize.vec().y}; + if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && (*w)->m_bIsMapped && (*w)->m_bIsFloating && (*w)->m_bPinned) + return w->get(); + } + // first loop over floating cuz they're above, m_lWindows should be sorted bottom->top, for tiled it doesn't matter. for (auto w = m_vWindows.rbegin(); w != m_vWindows.rend(); w++) { wlr_box box = {(*w)->m_vRealPosition.vec().x, (*w)->m_vRealPosition.vec().y, (*w)->m_vRealSize.vec().x, (*w)->m_vRealSize.vec().y}; - if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && (*w)->m_bIsMapped && (*w)->m_bIsFloating && isWorkspaceVisible((*w)->m_iWorkspaceID)) + if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && (*w)->m_bIsMapped && (*w)->m_bIsFloating && isWorkspaceVisible((*w)->m_iWorkspaceID) && !(*w)->m_bPinned) return w->get(); } @@ -593,7 +626,13 @@ CWindow* CCompositor::windowFromCursor() { CWindow* CCompositor::windowFloatingFromCursor() { for (auto w = m_vWindows.rbegin(); w != m_vWindows.rend(); w++) { wlr_box box = {(*w)->m_vRealPosition.vec().x, (*w)->m_vRealPosition.vec().y, (*w)->m_vRealSize.vec().x, (*w)->m_vRealSize.vec().y}; - if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && (*w)->m_bIsMapped && (*w)->m_bIsFloating && isWorkspaceVisible((*w)->m_iWorkspaceID) && !(*w)->m_bHidden) + if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && (*w)->m_bIsMapped && (*w)->m_bIsFloating && !(*w)->m_bHidden && (*w)->m_bPinned) + return w->get(); + } + + for (auto w = m_vWindows.rbegin(); w != m_vWindows.rend(); w++) { + wlr_box box = {(*w)->m_vRealPosition.vec().x, (*w)->m_vRealPosition.vec().y, (*w)->m_vRealSize.vec().x, (*w)->m_vRealSize.vec().y}; + if (wlr_box_contains_point(&box, m_sWLRCursor->x, m_sWLRCursor->y) && (*w)->m_bIsMapped && (*w)->m_bIsFloating && isWorkspaceVisible((*w)->m_iWorkspaceID) && !(*w)->m_bHidden && !(*w)->m_bPinned) return w->get(); } @@ -676,6 +715,9 @@ void CCompositor::focusWindow(CWindow* pWindow, wlr_surface* pSurface) { if (m_pLastWindow == pWindow && m_sSeat.seat->keyboard_state.focused_surface == pSurface) return; + if (pWindow->m_bPinned) + pWindow->m_iWorkspaceID = m_pLastMonitor->activeWorkspace; + if (!isWorkspaceVisible(pWindow->m_iWorkspaceID)) g_pKeybindManager->changeworkspace("[internal]" + std::to_string(pWindow->m_iWorkspaceID)); @@ -1399,6 +1441,17 @@ void CCompositor::swapActiveWorkspaces(CMonitor* pMonitorA, CMonitor* pMonitorB) } } + // fix pinned windows + for (auto& w : g_pCompositor->m_vWindows) { + if (w->m_iWorkspaceID == pMonitorA->activeWorkspace && w->m_bPinned) { + w->m_iWorkspaceID = PWORKSPACEB->m_iID; + } + + if (w->m_iWorkspaceID == pMonitorB->activeWorkspace && w->m_bPinned) { + w->m_iWorkspaceID = PWORKSPACEA->m_iID; + } + } + pMonitorA->activeWorkspace = PWORKSPACEB->m_iID; pMonitorB->activeWorkspace = PWORKSPACEA->m_iID; diff --git a/src/Window.hpp b/src/Window.hpp index 5e17d6c1..5e2f4ba5 100644 --- a/src/Window.hpp +++ b/src/Window.hpp @@ -119,6 +119,9 @@ public: // For hidden windows and stuff bool m_bHidden = false; + // For pinned (sticky) windows + bool m_bPinned = false; + // for proper cycling. While cycling we can't just move the pointers, so we need to keep track of the last cycled window. CWindow* m_pLastCycledWindow = nullptr; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 2cda382f..473b2fc6 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -739,6 +739,7 @@ bool windowRuleValid(const std::string& RULE) { && RULE != "opaque" && RULE != "forceinput" && RULE != "fullscreen" + && RULE != "pin" && RULE.find("animation") != 0 && RULE.find("rounding") != 0 && RULE.find("workspace") != 0); diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index d48ff19f..55a496e8 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -85,7 +85,8 @@ R"#({ "class": "%s", "title": "%s", "pid": %i, - "xwayland": %s + "xwayland": %s, + "pinned": %s },)#", w.get(), (int)w->m_vRealPosition.vec().x, (int)w->m_vRealPosition.vec().y, @@ -96,7 +97,8 @@ R"#({ escapeJSONStrings(g_pXWaylandManager->getAppIDClass(w.get())).c_str(), escapeJSONStrings(g_pXWaylandManager->getTitle(w.get())).c_str(), w->getPID(), - ((int)w->m_bIsX11 == 1 ? "true" : "false") + ((int)w->m_bIsX11 == 1 ? "true" : "false"), + (w->m_bPinned ? "true" : "false") ); } } @@ -109,8 +111,8 @@ R"#({ } else { for (auto& w : g_pCompositor->m_vWindows) { if (w->m_bIsMapped) { - result += getFormat("Window %x -> %s:\n\tat: %i,%i\n\tsize: %i,%i\n\tworkspace: %i (%s)\n\tfloating: %i\n\tmonitor: %i\n\tclass: %s\n\ttitle: %s\n\tpid: %i\n\txwayland: %i\n\n", - w.get(), w->m_szTitle.c_str(), (int)w->m_vRealPosition.vec().x, (int)w->m_vRealPosition.vec().y, (int)w->m_vRealSize.vec().x, (int)w->m_vRealSize.vec().y, w->m_iWorkspaceID, (w->m_iWorkspaceID == -1 ? "" : g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID) ? g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID)->m_szName.c_str() : std::string("Invalid workspace " + std::to_string(w->m_iWorkspaceID)).c_str()), (int)w->m_bIsFloating, w->m_iMonitorID, g_pXWaylandManager->getAppIDClass(w.get()).c_str(), g_pXWaylandManager->getTitle(w.get()).c_str(), w->getPID(), (int)w->m_bIsX11); + result += getFormat("Window %x -> %s:\n\tat: %i,%i\n\tsize: %i,%i\n\tworkspace: %i (%s)\n\tfloating: %i\n\tmonitor: %i\n\tclass: %s\n\ttitle: %s\n\tpid: %i\n\txwayland: %i\n\tpinned: %i\n\n", + w.get(), w->m_szTitle.c_str(), (int)w->m_vRealPosition.vec().x, (int)w->m_vRealPosition.vec().y, (int)w->m_vRealSize.vec().x, (int)w->m_vRealSize.vec().y, w->m_iWorkspaceID, (w->m_iWorkspaceID == -1 ? "" : g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID) ? g_pCompositor->getWorkspaceByID(w->m_iWorkspaceID)->m_szName.c_str() : std::string("Invalid workspace " + std::to_string(w->m_iWorkspaceID)).c_str()), (int)w->m_bIsFloating, w->m_iMonitorID, g_pXWaylandManager->getAppIDClass(w.get()).c_str(), g_pXWaylandManager->getTitle(w.get()).c_str(), w->getPID(), (int)w->m_bIsX11, (int)w->m_bPinned); } } diff --git a/src/events/Windows.cpp b/src/events/Windows.cpp index 17eaeeb1..8472d208 100644 --- a/src/events/Windows.cpp +++ b/src/events/Windows.cpp @@ -162,6 +162,8 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->m_sAdditionalConfigData.forceOpaque = true; } else if (r.szRule == "forceinput") { PWINDOW->m_sAdditionalConfigData.forceAllowsInput = true; + } else if (r.szRule == "pin") { + PWINDOW->m_bPinned = true; } else if (r.szRule.find("rounding") == 0) { try { PWINDOW->m_sAdditionalConfigData.rounding = std::stoi(r.szRule.substr(r.szRule.find_first_of(' ') + 1)); @@ -188,6 +190,10 @@ void Events::listener_mapWindow(void* owner, void* data) { } } + // disallow tiled pinned + if (PWINDOW->m_bPinned && !PWINDOW->m_bIsFloating) + PWINDOW->m_bPinned = false; + if (requestedWorkspace != "") { // process requested workspace if (requestedWorkspace.contains(' ')) { diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp index 09ca27c7..873e51f4 100644 --- a/src/layout/IHyprLayout.cpp +++ b/src/layout/IHyprLayout.cpp @@ -226,6 +226,8 @@ void IHyprLayout::changeWindowFloatingMode(CWindow* pWindow) { return; } + pWindow->m_bPinned = false; + const auto TILED = isWindowTiled(pWindow); if (!TILED) { diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index d5b6014a..821b0588 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -42,6 +42,7 @@ CKeybindManager::CKeybindManager() { m_mDispatchers["resizewindowpixel"] = resizeWindow; m_mDispatchers["swapnext"] = swapnext; m_mDispatchers["swapactiveworkspaces"] = swapActiveWorkspaces; + m_mDispatchers["pin"] = pinActive; m_tScrollTimer.reset(); } @@ -613,6 +614,13 @@ void CKeybindManager::changeworkspace(std::string args) { if (!g_pCompositor->isWorkspaceVisible(workspaceToChangeTo)) { const auto OLDWORKSPACEID = PMONITOR->activeWorkspace; + // fix pinned windows + for (auto& w : g_pCompositor->m_vWindows) { + if (w->m_iWorkspaceID == PMONITOR->activeWorkspace && w->m_bPinned) { + w->m_iWorkspaceID = workspaceToChangeTo; + } + } + // change it if (workspaceToChangeTo != SPECIAL_WORKSPACE_ID) PMONITOR->activeWorkspace = workspaceToChangeTo; @@ -700,6 +708,13 @@ void CKeybindManager::changeworkspace(std::string args) { PMONITOR->specialWorkspaceOpen = false; + // fix pinned windows + for (auto& w : g_pCompositor->m_vWindows) { + if (w->m_iWorkspaceID == PMONITOR->activeWorkspace && w->m_bPinned) { + w->m_iWorkspaceID = workspaceToChangeTo; + } + } + if (workspaceToChangeTo != SPECIAL_WORKSPACE_ID) PMONITOR->activeWorkspace = workspaceToChangeTo; else @@ -1534,3 +1549,15 @@ void CKeybindManager::swapActiveWorkspaces(std::string args) { g_pCompositor->swapActiveWorkspaces(PMON1, PMON2); } + +void CKeybindManager::pinActive(std::string args) { + if (!g_pCompositor->windowValidMapped(g_pCompositor->m_pLastWindow) || !g_pCompositor->m_pLastWindow->m_bIsFloating) + return; + + g_pCompositor->m_pLastWindow->m_bPinned = !g_pCompositor->m_pLastWindow->m_bPinned; + g_pCompositor->m_pLastWindow->m_iWorkspaceID = g_pCompositor->getMonitorFromID(g_pCompositor->m_pLastWindow->m_iMonitorID)->activeWorkspace; + + const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(g_pCompositor->m_pLastWindow->m_iWorkspaceID); + + PWORKSPACE->m_pLastFocusedWindow = g_pCompositor->vectorToWindowTiled(g_pInputManager->getMouseCoordsInternal()); +} diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp index 2431d2f9..d7233c79 100644 --- a/src/managers/KeybindManager.hpp +++ b/src/managers/KeybindManager.hpp @@ -113,6 +113,7 @@ private: static void dpms(std::string); static void swapnext(std::string); static void swapActiveWorkspaces(std::string); + static void pinActive(std::string); friend class CCompositor; friend class CInputManager; diff --git a/src/managers/XWaylandManager.cpp b/src/managers/XWaylandManager.cpp index 26a6fae4..a8952af0 100644 --- a/src/managers/XWaylandManager.cpp +++ b/src/managers/XWaylandManager.cpp @@ -63,7 +63,8 @@ void CHyprXWaylandManager::activateWindow(CWindow* pWindow, bool activate) { g_pCompositor->m_pLastFocus = getWindowSurface(pWindow); g_pCompositor->m_pLastWindow = pWindow; - g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID)->m_pLastFocusedWindow = pWindow; + if (!pWindow->m_bPinned) + g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID)->m_pLastFocusedWindow = pWindow; } void CHyprXWaylandManager::getGeometryForWindow(CWindow* pWindow, wlr_box* pbox) { diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index 7e4bfadd..2e1d2869 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -156,7 +156,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { // only check floating because tiled cant be over fullscreen for (auto w = g_pCompositor->m_vWindows.rbegin(); w != g_pCompositor->m_vWindows.rend(); w++) { wlr_box box = {(*w)->m_vRealPosition.vec().x, (*w)->m_vRealPosition.vec().y, (*w)->m_vRealSize.vec().x, (*w)->m_vRealSize.vec().y}; - if ((((*w)->m_bIsFloating && (*w)->m_bIsMapped && (*w)->m_bCreatedOverFullscreen) || ((*w)->m_iWorkspaceID == SPECIAL_WORKSPACE_ID && PMONITOR->specialWorkspaceOpen)) && wlr_box_contains_point(&box, mouseCoords.x, mouseCoords.y) && g_pCompositor->isWorkspaceVisible((*w)->m_iWorkspaceID) && !(*w)->m_bHidden) { + if ((((*w)->m_bIsFloating && (*w)->m_bIsMapped && ((*w)->m_bCreatedOverFullscreen || (*w)->m_bPinned)) || ((*w)->m_iWorkspaceID == SPECIAL_WORKSPACE_ID && PMONITOR->specialWorkspaceOpen)) && wlr_box_contains_point(&box, mouseCoords.x, mouseCoords.y) && g_pCompositor->isWorkspaceVisible((*w)->m_iWorkspaceID) && !(*w)->m_bHidden) { pFoundWindow = (*w).get(); if (!pFoundWindow->m_bIsX11) { diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 3db139e2..21c1bdc5 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -65,6 +65,9 @@ bool CHyprRenderer::shouldRenderWindow(CWindow* pWindow, CMonitor* pMonitor) { if (!wlr_output_layout_intersects(g_pCompositor->m_sWLROutputLayout, pMonitor->output, &geometry)) return false; + if (pWindow->m_bPinned) + return true; + // now check if it has the same workspace const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); @@ -99,6 +102,9 @@ bool CHyprRenderer::shouldRenderWindow(CWindow* pWindow) { const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); + if (pWindow->m_bPinned) + return true; + if (g_pCompositor->isWorkspaceVisible(pWindow->m_iWorkspaceID)) return true; @@ -137,7 +143,7 @@ void CHyprRenderer::renderWorkspaceWithFullscreenWindow(CMonitor* pMonitor, CWor // then render windows over fullscreen for (auto& w : g_pCompositor->m_vWindows) { - if (w->m_iWorkspaceID != pWorkspaceWindow->m_iWorkspaceID || !w->m_bCreatedOverFullscreen || !w->m_bIsMapped) + if (w->m_iWorkspaceID != pWorkspaceWindow->m_iWorkspaceID || (!w->m_bCreatedOverFullscreen && !w->m_bPinned) || !w->m_bIsMapped) continue; renderWindow(w.get(), pMonitor, time, true, RENDER_PASS_ALL); @@ -188,7 +194,7 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* } const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); - const auto REALPOS = pWindow->m_vRealPosition.vec() + PWORKSPACE->m_vRenderOffset.vec(); + const auto REALPOS = pWindow->m_vRealPosition.vec() + (pWindow->m_bPinned ? Vector2D{} : PWORKSPACE->m_vRenderOffset.vec()); static const auto PNOFLOATINGBORDERS = &g_pConfigManager->getConfigValuePtr("general:no_border_on_floating")->intValue; SRenderData renderdata = {pMonitor->output, time, REALPOS.x, REALPOS.y}; @@ -196,7 +202,7 @@ void CHyprRenderer::renderWindow(CWindow* pWindow, CMonitor* pMonitor, timespec* renderdata.w = std::clamp(pWindow->m_vRealSize.vec().x, (double)5, (double)1337420); // clamp the size to min 5, renderdata.h = std::clamp(pWindow->m_vRealSize.vec().y, (double)5, (double)1337420); // otherwise we'll have issues later with invalid boxes renderdata.dontRound = (pWindow->m_bIsFullscreen && PWORKSPACE->m_efFullscreenMode == FULLSCREEN_FULL) || (!pWindow->m_sSpecialRenderData.rounding); - renderdata.fadeAlpha = pWindow->m_fAlpha.fl() * (PWORKSPACE->m_fAlpha.fl() / 255.f); + renderdata.fadeAlpha = pWindow->m_fAlpha.fl() * (pWindow->m_bPinned ? 1.f : (PWORKSPACE->m_fAlpha.fl() / 255.f)); renderdata.alpha = pWindow->m_fActiveInactiveAlpha.fl(); renderdata.decorate = decorate && !pWindow->m_bX11DoesntWantBorders && (pWindow->m_bIsFloating ? *PNOFLOATINGBORDERS == 0 : true) && (!pWindow->m_bIsFullscreen || PWORKSPACE->m_efFullscreenMode != FULLSCREEN_FULL); renderdata.rounding = pWindow->m_sAdditionalConfigData.rounding;