diff --git a/CMakeLists.txt b/CMakeLists.txt index 49297a20..032160de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ target_link_libraries(Hyprland GLESv2 pthread ${CMAKE_THREAD_LIBS_INIT} + ${CMAKE_SOURCE_DIR}/ext-workspace-unstable-v1-protocol.o ) IF(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) diff --git a/Makefile b/Makefile index 0451b1a3..c1c8c13a 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,16 @@ wlr-screencopy-unstable-v1-protocol.c: wlr-screencopy-unstable-v1-protocol.o: wlr-screencopy-unstable-v1-protocol.h +ext-workspace-unstable-v1-protocol.h: + $(WAYLAND_SCANNER) server-header \ + protocols/ext-workspace-unstable-v1.xml $@ + +ext-workspace-unstable-v1-protocol.c: + $(WAYLAND_SCANNER) private-code \ + protocols/ext-workspace-unstable-v1.xml $@ + +ext-workspace-unstable-v1-protocol.o: ext-workspace-unstable-v1-protocol.h + idle-protocol.h: $(WAYLAND_SCANNER) server-header \ protocols/idle.xml $@ @@ -78,4 +88,4 @@ install: cp ./assets/wall_4K.png /usr/share/hyprland cp ./assets/wall_8K.png /usr/share/hyprland -config: xdg-shell-protocol.o wlr-layer-shell-unstable-v1-protocol.o wlr-screencopy-unstable-v1-protocol.o idle-protocol.o +config: xdg-shell-protocol.o wlr-layer-shell-unstable-v1-protocol.o wlr-screencopy-unstable-v1-protocol.o idle-protocol.o ext-workspace-unstable-v1-protocol.o diff --git a/README.md b/README.md index abc527f9..9c85cb0c 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Nevertheless, REPORT any you find! Make an issue! - Easily expandable and readable codebase - Rounded corners - Window blur + - Workspaces Protocol support - Fade in/out - Support for docks/whatever - Window rules diff --git a/protocols/ext-workspace-unstable-v1.xml b/protocols/ext-workspace-unstable-v1.xml new file mode 100644 index 00000000..24410b62 --- /dev/null +++ b/protocols/ext-workspace-unstable-v1.xml @@ -0,0 +1,306 @@ + + + + Copyright © 2019 Christopher Billington + Copyright © 2020 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Workspaces, also called virtual desktops, are groups of surfaces. A + compositor with a concept of workspaces may only show some such groups of + surfaces (those of 'active' workspaces) at a time. 'Activating' a + workspace is a request for the compositor to display that workspace's + surfaces as normal, whereas the compositor may hide or otherwise + de-emphasise surfaces that are associated only with 'inactive' workspaces. + Workspaces are grouped by which sets of outputs they correspond to, and + may contain surfaces only from those outputs. In this way, it is possible + for each output to have its own set of workspaces, or for all outputs (or + any other arbitrary grouping) to share workspaces. Compositors may + optionally conceptually arrange each group of workspaces in an + N-dimensional grid. + + The purpose of this protocol is to enable the creation of taskbars and + docks by providing them with a list of workspaces and their properties, + and allowing them to activate and deactivate workspaces. + + After a client binds the zext_workspace_manager_v1, each workspace will be + sent via the workspace event. + + + + + This event is emitted whenever a new workspace group has been created. + + All initial details of the workspace group (workspaces, outputs) will be + sent immediately after this event via the corresponding events in + zext_workspace_group_handle_v1. + + + + + + + The client must send this request after it has finished sending other + requests. The compositor must process a series of requests preceding a + commit request atomically. + + This allows changes to the workspace properties to be seen as atomic, + even if they happen via multiple events, and even if they involve + multiple zext_workspace_handle_v1 objects, for example, deactivating one + workspace and activating another. + + + + + + This event is sent after all changes in all workspace groups have been + sent. + + This allows changes to one or more zext_workspace_group_handle_v1 + properties to be seen as atomic, even if they happen via multiple + events. In particular, an output moving from one workspace group to + another sends an output_enter event and an output_leave event to the two + zext_workspace_group_handle_v1 objects in question. The compositor sends + the done event only after updating the output information in both + workspace groups. + + + + + + This event indicates that the compositor is done sending events to the + zext_workspace_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + + + + + + Indicates the client no longer wishes to receive events for new + workspace groups. However the compositor may emit further workspace + events, until the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + + A zext_workspace_group_handle_v1 object represents a a workspace group + that is assigned a set of outputs and contains a number of workspaces. + + The set of outputs assigned to the workspace group is conveyed to the client via + output_enter and output_leave events, and its workspaces are conveyed with + workspace events. + + For example, a compositor which has a set of workspaces for each output may + advertise a workspace group (and its workspaces) per output, whereas a compositor + where a workspace spans all outputs may advertise a single workspace group for all + outputs. + + + + + This event is emitted whenever an output is assigned to the workspace + group. + + + + + + + This event is emitted whenever an output is removed from the workspace + group. + + + + + + + This event is emitted whenever a new workspace has been created. + + All initial details of the workspace (name, coordinates, state) will + be sent immediately after this event via the corresponding events in + zext_workspace_handle_v1. + + + + + + + This event means the zext_workspace_group_handle_v1 has been destroyed. + It is guaranteed there won't be any more events for this + zext_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes + inert so any requests will be ignored except the destroy request. + + The compositor must remove all workspaces belonging to a workspace group + before removing the workspace group. + + + + + + Request that the compositor create a new workspace with the given name. + + There is no guarantee that the compositor will create a new workspace, + or that the created workspace will have the provided name. + + + + + + + Destroys the zext_workspace_handle_v1 object. + + This request should be called either when the client does not want to + use the workspace object any more or after the remove event to finalize + the destruction of the object. + + + + + + + A zext_workspace_handle_v1 object represents a a workspace that handles a + group of surfaces. + + Each workspace has a name, conveyed to the client with the name event; a + list of states, conveyed to the client with the state event; and + optionally a set of coordinates, conveyed to the client with the + coordinates event. The client may request that the compositor activate or + deactivate the workspace. + + Each workspace can belong to only a single workspace group. + Depepending on the compositor policy, there might be workspaces with + the same name in different workspace groups, but these workspaces are still + separate (e.g. one of them might be active while the other is not). + + + + + This event is emitted immediately after the zext_workspace_handle_v1 is + created and whenever the name of the workspace changes. + + + + + + + This event is used to organize workspaces into an N-dimensional grid + within a workspace group, and if supported, is emitted immediately after + the zext_workspace_handle_v1 is created and whenever the coordinates of + the workspace change. Compositors may not send this event if they do not + conceptually arrange workspaces in this way. If compositors simply + number workspaces, without any geometric interpretation, they may send + 1D coordinates, which clients should not interpret as implying any + geometry. Sending an empty array means that the compositor no longer + orders the workspace geometrically. + + Coordinates have an arbitrary number of dimensions N with an uint32 + position along each dimension. By convention if N > 1, the first + dimension is X, the second Y, the third Z, and so on. The compositor may + chose to utilize these events for a more novel workspace layout + convention, however. No guarantee is made about the grid being filled or + bounded; there may be a workspace at coordinate 1 and another at + coordinate 1000 and none in between. Within a workspace group, however, + workspaces must have unique coordinates of equal dimensionality. + + + + + + + This event is emitted immediately after the zext_workspace_handle_v1 is + created and each time the workspace state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + The different states that a workspace can have. + + + + + + + The workspace is not visible in its workspace group, and clients + attempting to visualize the compositor workspace state should not + display such workspaces. + + + + + + + This event means the zext_workspace_handle_v1 has been destroyed. It is + guaranteed there won't be any more events for this + zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so + any requests will be ignored except the destroy request. + + + + + + Destroys the zext_workspace_handle_v1 object. + + This request should be called either when the client does not want to + use the workspace object any more or after the remove event to finalize + the destruction of the object. + + + + + + Request that this workspace be activated. + + There is no guarantee the workspace will be actually activated, and + behaviour may be compositor-dependent. For example, activating a + workspace may or may not deactivate all other workspaces in the same + group. + + + + + + Request that this workspace be deactivated. + + There is no guarantee the workspace will be actually deactivated. + + + + + + Request that this workspace be removed. + + There is no guarantee the workspace will be actually removed. + + + + diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 319a5eb1..d12cf288 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -90,6 +90,8 @@ CCompositor::CCompositor() { m_sWLRInhibitMgr = wlr_input_inhibit_manager_create(m_sWLDisplay); m_sWLRKbShInhibitMgr = wlr_keyboard_shortcuts_inhibit_v1_create(m_sWLDisplay); + + m_sWLREXTWorkspaceMgr = wlr_ext_workspace_manager_v1_create(m_sWLDisplay); } CCompositor::~CCompositor() { @@ -478,9 +480,9 @@ bool CCompositor::isWorkspaceVisible(const int& w) { return false; } -SWorkspace* CCompositor::getWorkspaceByID(const int& id) { +CWorkspace* CCompositor::getWorkspaceByID(const int& id) { for (auto& w : m_lWorkspaces) { - if (w.ID == id) + if (w.m_iID == id) return &w; } @@ -489,8 +491,9 @@ SWorkspace* CCompositor::getWorkspaceByID(const int& id) { void CCompositor::sanityCheckWorkspaces() { for (auto it = m_lWorkspaces.begin(); it != m_lWorkspaces.end(); ++it) { - if (getWindowsOnWorkspace(it->ID) == 0 && !isWorkspaceVisible(it->ID)) + if (getWindowsOnWorkspace(it->m_iID) == 0 && !isWorkspaceVisible(it->m_iID)) { it = m_lWorkspaces.erase(it); + } } } @@ -524,7 +527,7 @@ void CCompositor::fixXWaylandWindowsOnWorkspace(const int& id) { // moveXWaylandWindow only moves XWayland windows // so there is no need to check here // if the window is XWayland or not. - if (ISVISIBLE && (!PWORKSPACE->hasFullscreenWindow || w.m_bIsFullscreen)) + if (ISVISIBLE && (!PWORKSPACE->m_bHasFullscreenWindow || w.m_bIsFullscreen)) g_pXWaylandManager->moveXWaylandWindow(&w, w.m_vRealPosition); else g_pXWaylandManager->moveXWaylandWindow(&w, Vector2D(42069,42069)); @@ -633,4 +636,11 @@ CWindow* CCompositor::getWindowInDirection(CWindow* pWindow, char dir) { return longestIntersectWindow; return nullptr; +} + +void CCompositor::deactivateAllWLRWorkspaces() { + for (auto& w : m_lWorkspaces) { + if (w.m_pWlrHandle) + wlr_ext_workspace_handle_v1_set_active(w.m_pWlrHandle, false); + } } \ No newline at end of file diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 2eb5b2e2..c6b31e0a 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -49,6 +49,7 @@ public: wlr_keyboard_shortcuts_inhibit_manager_v1* m_sWLRKbShInhibitMgr; wlr_egl* m_sWLREGL; int m_iDRMFD; + wlr_ext_workspace_manager_v1* m_sWLREXTWorkspaceMgr; // ------------------------------------------------- // @@ -57,7 +58,7 @@ public: std::list m_lMonitors; std::list m_lWindows; std::list m_lXDGPopups; - std::list m_lWorkspaces; + std::list m_lWorkspaces; std::list m_lSubsurfaces; std::list m_lWindowsFadingOut; @@ -90,7 +91,7 @@ public: CWindow* getWindowForPopup(wlr_xdg_popup*); CWindow* getWindowFromSurface(wlr_surface*); bool isWorkspaceVisible(const int&); - SWorkspace* getWorkspaceByID(const int&); + CWorkspace* getWorkspaceByID(const int&); void sanityCheckWorkspaces(); int getWindowsOnWorkspace(const int&); CWindow* getFirstWindowOnWorkspace(const int&); @@ -101,6 +102,7 @@ public: void moveWindowToTop(CWindow*); void cleanupWindows(); CWindow* getWindowInDirection(CWindow*, char); + void deactivateAllWLRWorkspaces(); private: void initAllSignals(); diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index c9ab5e30..7956a852 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -35,7 +35,7 @@ std::string workspacesRequest() { std::string result = ""; for (auto& w : g_pCompositor->m_lWorkspaces) { result += getFormat("workspace ID %i on monitor %s:\n\twindows: %i\n\thasfullscreen: %i\n\n", - w.ID, g_pCompositor->getMonitorFromID(w.monitorID)->szName.c_str(), g_pCompositor->getWindowsOnWorkspace(w.ID), (int)w.hasFullscreenWindow); + w.m_iID, g_pCompositor->getMonitorFromID(w.m_iMonitorID)->szName.c_str(), g_pCompositor->getWindowsOnWorkspace(w.m_iID), (int)w.m_bHasFullscreenWindow); } return result; } diff --git a/src/defines.hpp b/src/defines.hpp index 4bd886e7..4bd5f239 100644 --- a/src/defines.hpp +++ b/src/defines.hpp @@ -4,6 +4,8 @@ #include "helpers/WLListener.hpp" #include "helpers/Color.hpp" +#include "wlrunstable/wlr_ext_workspace_v1.hpp" + #ifndef NDEBUG #define ISDEBUG true #else diff --git a/src/events/Monitors.cpp b/src/events/Monitors.cpp index f80cfaa0..ab8c2cb7 100644 --- a/src/events/Monitors.cpp +++ b/src/events/Monitors.cpp @@ -67,20 +67,11 @@ void Events::listener_newOutput(wl_listener* listener, void* data) { newMonitor.vecSize = monitorRule.resolution; newMonitor.refreshRate = monitorRule.refreshRate; - // Workspace - g_pCompositor->m_lWorkspaces.push_back(SWorkspace()); - const auto PNEWWORKSPACE = &g_pCompositor->m_lWorkspaces.back(); - - PNEWWORKSPACE->ID = monitorRule.defaultWorkspaceID == -1 ? g_pCompositor->m_lWorkspaces.size() : monitorRule.defaultWorkspaceID; - PNEWWORKSPACE->monitorID = newMonitor.ID; - - newMonitor.activeWorkspace = PNEWWORKSPACE->ID; - g_pCompositor->m_lMonitors.push_back(newMonitor); - // + const auto PNEWMONITOR = &g_pCompositor->m_lMonitors.back(); - g_pCompositor->m_lMonitors.back().hyprListener_monitorFrame.initCallback(&OUTPUT->events.frame, &Events::listener_monitorFrame, &g_pCompositor->m_lMonitors.back()); - g_pCompositor->m_lMonitors.back().hyprListener_monitorDestroy.initCallback(&OUTPUT->events.destroy, &Events::listener_monitorDestroy, &g_pCompositor->m_lMonitors.back()); + PNEWMONITOR->hyprListener_monitorFrame.initCallback(&OUTPUT->events.frame, &Events::listener_monitorFrame, PNEWMONITOR); + PNEWMONITOR->hyprListener_monitorDestroy.initCallback(&OUTPUT->events.destroy, &Events::listener_monitorDestroy, PNEWMONITOR); wlr_output_enable(OUTPUT, 1); if (!wlr_output_commit(OUTPUT)) { @@ -95,6 +86,26 @@ void Events::listener_newOutput(wl_listener* listener, void* data) { wlr_output_set_custom_mode(OUTPUT, OUTPUT->width, OUTPUT->height, (int)(round(monitorRule.refreshRate * 1000))); Debug::log(LOG, "Added new monitor with name %s at %i,%i with size %ix%i@%i, pointer %x", OUTPUT->name, (int)monitorRule.offset.x, (int)monitorRule.offset.y, (int)monitorRule.resolution.x, (int)monitorRule.resolution.y, (int)monitorRule.refreshRate, OUTPUT); + + // add a WLR workspace group + PNEWMONITOR->pWLRWorkspaceGroupHandle = wlr_ext_workspace_group_handle_v1_create(g_pCompositor->m_sWLREXTWorkspaceMgr); + + // Workspace + const auto WORKSPACEID = monitorRule.defaultWorkspaceID == -1 ? g_pCompositor->m_lWorkspaces.size() : monitorRule.defaultWorkspaceID; + g_pCompositor->m_lWorkspaces.emplace_back(newMonitor.ID); + const auto PNEWWORKSPACE = &g_pCompositor->m_lWorkspaces.back(); + + // We are required to set the name here immediately + wlr_ext_workspace_handle_v1_set_name(PNEWWORKSPACE->m_pWlrHandle, std::to_string(WORKSPACEID).c_str()); + + PNEWWORKSPACE->m_iID = WORKSPACEID; + PNEWWORKSPACE->m_iMonitorID = newMonitor.ID; + + PNEWMONITOR->activeWorkspace = PNEWWORKSPACE->m_iID; + + g_pCompositor->deactivateAllWLRWorkspaces(); + wlr_ext_workspace_handle_v1_set_active(PNEWWORKSPACE->m_pWlrHandle, true); + // } void Events::listener_monitorFrame(void* owner, void* data) { diff --git a/src/events/Windows.cpp b/src/events/Windows.cpp index 722d3ed6..52d6c6f5 100644 --- a/src/events/Windows.cpp +++ b/src/events/Windows.cpp @@ -46,8 +46,8 @@ void Events::listener_mapWindow(void* owner, void* data) { if (g_pXWaylandManager->shouldBeFloated(PWINDOW)) PWINDOW->m_bIsFloating = true; - if (PWORKSPACE->hasFullscreenWindow && !PWINDOW->m_bIsFloating) { - const auto PFULLWINDOW = g_pCompositor->getFullscreenWindowOnWorkspace(PWORKSPACE->ID); + if (PWORKSPACE->m_bHasFullscreenWindow && !PWINDOW->m_bIsFloating) { + const auto PFULLWINDOW = g_pCompositor->getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID); g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(PFULLWINDOW); g_pXWaylandManager->setWindowFullscreen(PFULLWINDOW, PFULLWINDOW->m_bIsFullscreen); } @@ -181,8 +181,8 @@ void Events::listener_unmapWindow(void* owner, void* data) { // remove the fullscreen window status from workspace if we closed it const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(PWINDOW->m_iWorkspaceID); - if (PWORKSPACE->hasFullscreenWindow && PWINDOW->m_bIsFullscreen) - PWORKSPACE->hasFullscreenWindow = false; + if (PWORKSPACE->m_bHasFullscreenWindow && PWINDOW->m_bIsFullscreen) + PWORKSPACE->m_bHasFullscreenWindow = false; g_pLayoutManager->getCurrentLayout()->onWindowRemoved(PWINDOW); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index b3f10893..3f88bed1 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -33,6 +33,10 @@ struct SMonitor { DYNLISTENER(monitorFrame); DYNLISTENER(monitorDestroy); + // hack: a group = workspaces on a monitor. + // I don't really care lol :P + wlr_ext_workspace_group_handle_v1* pWLRWorkspaceGroupHandle = nullptr; + // For the list lookup diff --git a/src/helpers/Workspace.cpp b/src/helpers/Workspace.cpp new file mode 100644 index 00000000..8c535941 --- /dev/null +++ b/src/helpers/Workspace.cpp @@ -0,0 +1,29 @@ +#include "Workspace.hpp" +#include "../Compositor.hpp" + +CWorkspace::CWorkspace(int monitorID) { + const auto PMONITOR = g_pCompositor->getMonitorFromID(monitorID); + + if (!PMONITOR) { + Debug::log(ERR, "Attempted a creation of CWorkspace with an invalid monitor?"); + return; + } + + m_iMonitorID = monitorID; + + m_pWlrHandle = wlr_ext_workspace_handle_v1_create(PMONITOR->pWLRWorkspaceGroupHandle); + + // set geometry here cuz we can + wl_array_init(&m_wlrCoordinateArr); + *reinterpret_cast(wl_array_add(&m_wlrCoordinateArr, sizeof(int))) = (int)PMONITOR->vecPosition.x; + *reinterpret_cast(wl_array_add(&m_wlrCoordinateArr, sizeof(int))) = (int)PMONITOR->vecPosition.y; + wlr_ext_workspace_handle_v1_set_coordinates(m_pWlrHandle, &m_wlrCoordinateArr); +} + +CWorkspace::~CWorkspace() { + if (m_pWlrHandle) { + wlr_ext_workspace_handle_v1_set_active(m_pWlrHandle, false); + wlr_ext_workspace_handle_v1_destroy(m_pWlrHandle); + m_pWlrHandle = nullptr; + } +} \ No newline at end of file diff --git a/src/helpers/Workspace.hpp b/src/helpers/Workspace.hpp index d4ec5b05..465c385b 100644 --- a/src/helpers/Workspace.hpp +++ b/src/helpers/Workspace.hpp @@ -2,8 +2,16 @@ #include "../defines.hpp" -struct SWorkspace { - int ID = -1; - uint64_t monitorID = -1; - bool hasFullscreenWindow = false; +class CWorkspace { +public: + CWorkspace(int monitorID); + ~CWorkspace(); + + int m_iID = -1; + uint64_t m_iMonitorID = -1; + bool m_bHasFullscreenWindow = false; + + wlr_ext_workspace_handle_v1* m_pWlrHandle = nullptr; + + wl_array m_wlrCoordinateArr; }; \ No newline at end of file diff --git a/src/includes.hpp b/src/includes.hpp index b2b07c30..b82319fd 100644 --- a/src/includes.hpp +++ b/src/includes.hpp @@ -88,3 +88,5 @@ extern "C" { #include #include "helpers/Vector2D.hpp" + +#include "../ext-workspace-unstable-v1-protocol.h" \ No newline at end of file diff --git a/src/layout/DwindleLayout.cpp b/src/layout/DwindleLayout.cpp index 0ee86459..b497388a 100644 --- a/src/layout/DwindleLayout.cpp +++ b/src/layout/DwindleLayout.cpp @@ -68,7 +68,7 @@ SDwindleNodeData* CHyprDwindleLayout::getMasterNodeOnWorkspace(const int& id) { } void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode) { - const auto PMONITOR = g_pCompositor->getMonitorFromID(g_pCompositor->getWorkspaceByID(pNode->workspaceID)->monitorID); + const auto PMONITOR = g_pCompositor->getMonitorFromID(g_pCompositor->getWorkspaceByID(pNode->workspaceID)->m_iMonitorID); if (!PMONITOR){ Debug::log(ERR, "Orphaned Node %x (workspace ID: %i)!!", pNode, pNode->workspaceID); @@ -272,7 +272,7 @@ void CHyprDwindleLayout::recalculateMonitor(const int& monid) { const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(PMONITOR->activeWorkspace); // Ignore any recalc events if we have a fullscreen window. - if (PWORKSPACE->hasFullscreenWindow) + if (PWORKSPACE->m_bHasFullscreenWindow) return; const auto TOPNODE = getMasterNodeOnWorkspace(PMONITOR->activeWorkspace); @@ -505,7 +505,7 @@ void CHyprDwindleLayout::fullscreenRequestForWindow(CWindow* pWindow) { const auto PMONITOR = g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID); const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID); - if (PWORKSPACE->hasFullscreenWindow && !pWindow->m_bIsFullscreen) { + if (PWORKSPACE->m_bHasFullscreenWindow && !pWindow->m_bIsFullscreen) { // if the window wants to be fullscreen but there already is one, // ignore the request. return; @@ -513,7 +513,7 @@ void CHyprDwindleLayout::fullscreenRequestForWindow(CWindow* pWindow) { // otherwise, accept it. pWindow->m_bIsFullscreen = !pWindow->m_bIsFullscreen; - PWORKSPACE->hasFullscreenWindow = !PWORKSPACE->hasFullscreenWindow; + PWORKSPACE->m_bHasFullscreenWindow = !PWORKSPACE->m_bHasFullscreenWindow; if (!pWindow->m_bIsFullscreen) { // if it got its fullscreen disabled, set back its node if it had one diff --git a/src/managers/InputManager.cpp b/src/managers/InputManager.cpp index 5157a063..93f842a4 100644 --- a/src/managers/InputManager.cpp +++ b/src/managers/InputManager.cpp @@ -27,17 +27,28 @@ void CInputManager::mouseMoveUnified(uint32_t time) { Vector2D mouseCoords = getMouseCoordsInternal(); const auto PMONITOR = g_pCompositor->getMonitorFromCursor(); - if (PMONITOR) + if (PMONITOR && PMONITOR != g_pCompositor->m_pLastMonitor) { + // update wlr workspaces when this happens + if (g_pCompositor->m_pLastMonitor) + wlr_ext_workspace_group_handle_v1_output_leave(g_pCompositor->m_pLastMonitor->pWLRWorkspaceGroupHandle, g_pCompositor->m_pLastMonitor->output); + g_pCompositor->m_pLastMonitor = PMONITOR; + wlr_ext_workspace_group_handle_v1_output_enter(PMONITOR->pWLRWorkspaceGroupHandle, PMONITOR->output); + + // set active workspace and deactivate all other in wlr + g_pCompositor->deactivateAllWLRWorkspaces(); + wlr_ext_workspace_handle_v1_set_active(g_pCompositor->getWorkspaceByID(PMONITOR->activeWorkspace)->m_pWlrHandle, true); + } + Vector2D surfaceCoords; Vector2D surfacePos = Vector2D(-1337, -1337); CWindow* pFoundWindow = nullptr; // first, we check if the workspace doesnt have a fullscreen window const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(PMONITOR->activeWorkspace); - if (PWORKSPACE->hasFullscreenWindow) { - pFoundWindow = g_pCompositor->getFullscreenWindowOnWorkspace(PWORKSPACE->ID); + if (PWORKSPACE->m_bHasFullscreenWindow) { + pFoundWindow = g_pCompositor->getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID); for (auto w = g_pCompositor->m_lWindows.rbegin(); w != g_pCompositor->m_lWindows.rend(); ++w) { wlr_box box = {w->m_vRealPosition.x, w->m_vRealPosition.y, w->m_vRealSize.x, w->m_vRealSize.y}; diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index d0fcfd83..331273c3 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -136,7 +136,7 @@ void CKeybindManager::changeworkspace(std::string args) { // if it exists, we warp to it if (g_pCompositor->getWorkspaceByID(workspaceToChangeTo)) { - const auto PMONITOR = g_pCompositor->getMonitorFromID(g_pCompositor->getWorkspaceByID(workspaceToChangeTo)->monitorID); + const auto PMONITOR = g_pCompositor->getMonitorFromID(g_pCompositor->getWorkspaceByID(workspaceToChangeTo)->m_iMonitorID); // if it's not visible, make it visible. if (!g_pCompositor->isWorkspaceVisible(workspaceToChangeTo)) { @@ -163,6 +163,12 @@ void CKeybindManager::changeworkspace(std::string args) { // focus the first window g_pCompositor->focusWindow(g_pCompositor->getFirstWindowOnWorkspace(workspaceToChangeTo)); + // set active and deactivate all other in wlr + g_pCompositor->deactivateAllWLRWorkspaces(); + wlr_ext_workspace_handle_v1_set_active(g_pCompositor->getWorkspaceByID(workspaceToChangeTo)->m_pWlrHandle, true); + + Debug::log(LOG, "Changed to workspace %i", workspaceToChangeTo); + return; } @@ -171,17 +177,26 @@ void CKeybindManager::changeworkspace(std::string args) { const auto OLDWORKSPACE = PMONITOR->activeWorkspace; - g_pCompositor->m_lWorkspaces.push_back(SWorkspace()); + g_pCompositor->m_lWorkspaces.emplace_back(PMONITOR->ID); const auto PWORKSPACE = &g_pCompositor->m_lWorkspaces.back(); - PWORKSPACE->ID = workspaceToChangeTo; - PWORKSPACE->monitorID = PMONITOR->ID; + // We are required to set the name here immediately + wlr_ext_workspace_handle_v1_set_name(PWORKSPACE->m_pWlrHandle, std::to_string(workspaceToChangeTo).c_str()); + + PWORKSPACE->m_iID = workspaceToChangeTo; + PWORKSPACE->m_iMonitorID = PMONITOR->ID; PMONITOR->activeWorkspace = workspaceToChangeTo; // we need to move XWayland windows to narnia or otherwise they will still process our cursor and shit // and that'd be annoying as hell g_pCompositor->fixXWaylandWindowsOnWorkspace(OLDWORKSPACE); + + // set active and deactivate all other + g_pCompositor->deactivateAllWLRWorkspaces(); + wlr_ext_workspace_handle_v1_set_active(PWORKSPACE->m_pWlrHandle, true); + + Debug::log(LOG, "Changed to workspace %i", workspaceToChangeTo); } void CKeybindManager::fullscreenActive(std::string args) { @@ -224,15 +239,15 @@ void CKeybindManager::moveActiveToWorkspace(std::string args) { const auto NEWWORKSPACE = g_pCompositor->getWorkspaceByID(workspaceID); - OLDWORKSPACE->hasFullscreenWindow = false; + OLDWORKSPACE->m_bHasFullscreenWindow = false; PWINDOW->m_iWorkspaceID = workspaceID; - PWINDOW->m_iMonitorID = NEWWORKSPACE->monitorID; + PWINDOW->m_iMonitorID = NEWWORKSPACE->m_iMonitorID; PWINDOW->m_bIsFullscreen = false; - if (NEWWORKSPACE->hasFullscreenWindow) { + if (NEWWORKSPACE->m_bHasFullscreenWindow) { g_pCompositor->getFullscreenWindowOnWorkspace(workspaceID)->m_bIsFullscreen = false; - NEWWORKSPACE->hasFullscreenWindow = false; + NEWWORKSPACE->m_bHasFullscreenWindow = false; } // Hack: So that the layout doesnt find our window at the cursor @@ -247,8 +262,8 @@ void CKeybindManager::moveActiveToWorkspace(std::string args) { PWINDOW->m_vRealSize = PSAVEDSIZE; if (PWINDOW->m_bIsFloating) { - PWINDOW->m_vRealPosition = PWINDOW->m_vRealPosition - g_pCompositor->getMonitorFromID(OLDWORKSPACE->monitorID)->vecPosition; - PWINDOW->m_vRealPosition = PWINDOW->m_vRealPosition + g_pCompositor->getMonitorFromID(NEWWORKSPACE->monitorID)->vecPosition; + PWINDOW->m_vRealPosition = PWINDOW->m_vRealPosition - g_pCompositor->getMonitorFromID(OLDWORKSPACE->m_iMonitorID)->vecPosition; + PWINDOW->m_vRealPosition = PWINDOW->m_vRealPosition + g_pCompositor->getMonitorFromID(NEWWORKSPACE->m_iMonitorID)->vecPosition; PWINDOW->m_vEffectivePosition = PWINDOW->m_vRealPosition; PWINDOW->m_vPosition = PWINDOW->m_vRealPosition; } diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 0e950e5f..d4ee312a 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -60,11 +60,11 @@ bool shouldRenderWindow(CWindow* pWindow, SMonitor* pMonitor) { return false; } -void CHyprRenderer::renderWorkspaceWithFullscreenWindow(SMonitor* pMonitor, SWorkspace* pWorkspace, timespec* time) { +void CHyprRenderer::renderWorkspaceWithFullscreenWindow(SMonitor* pMonitor, CWorkspace* pWorkspace, timespec* time) { CWindow* pWorkspaceWindow = nullptr; for (auto& w : g_pCompositor->m_lWindows) { - if (w.m_iWorkspaceID != pWorkspace->ID || !w.m_bIsFullscreen) + if (w.m_iWorkspaceID != pWorkspace->m_iID || !w.m_bIsFullscreen) continue; // found it! @@ -140,7 +140,7 @@ void CHyprRenderer::renderAllClientsForMonitor(const int& ID, timespec* time) { // fullscreen window will hide other windows and top layers const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(PMONITOR->activeWorkspace); - if (PWORKSPACE->hasFullscreenWindow) { + if (PWORKSPACE->m_bHasFullscreenWindow) { renderWorkspaceWithFullscreenWindow(PMONITOR, PWORKSPACE, time); return; } diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index ec1597f8..feca37aa 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -18,7 +18,7 @@ public: private: void arrangeLayerArray(SMonitor*, const std::list&, bool, wlr_box*); void drawBorderForWindow(CWindow*, SMonitor*, float a = 255.f); - void renderWorkspaceWithFullscreenWindow(SMonitor*, SWorkspace*, timespec*); + void renderWorkspaceWithFullscreenWindow(SMonitor*, CWorkspace*, timespec*); void renderWindow(CWindow*, SMonitor*, timespec*, bool); void renderDragIcon(SMonitor*, timespec*); diff --git a/src/wlrunstable/wlr_ext_workspace_v1.cpp b/src/wlrunstable/wlr_ext_workspace_v1.cpp new file mode 100644 index 00000000..d549b42a --- /dev/null +++ b/src/wlrunstable/wlr_ext_workspace_v1.cpp @@ -0,0 +1,640 @@ +#define _POSIX_C_SOURCE 200809L + +#include "../includes.hpp" +#include "../helpers/MiscFunctions.hpp" +#include "../../ext-workspace-unstable-v1-protocol.h" + +#include +#include +#include +#include "wlr_ext_workspace_v1.hpp" +#include +#include + +#define WORKSPACE_V1_VERSION 1 + +static void workspace_handle_destroy(struct wl_client *client, + struct wl_resource *resource) ; + +static void workspace_handle_activate(struct wl_client *client, + struct wl_resource *resource) ; + +static void workspace_handle_deactivate(struct wl_client *client, + struct wl_resource *resource) ; + +static void workspace_handle_remove(struct wl_client *client, + struct wl_resource *resource) ; + +static void workspace_group_handle_handle_create_workspace(struct wl_client *client, + struct wl_resource *resource, const char *arg) ; + +static void workspace_group_handle_handle_destroy(struct wl_client *client, + struct wl_resource *resource) ; + +static void workspace_manager_commit(struct wl_client *client, + struct wl_resource *resource) ; + +static void workspace_manager_stop(struct wl_client *client, + struct wl_resource *resource) ; + +inline static struct zext_workspace_handle_v1_interface workspace_handle_impl = { + .destroy = workspace_handle_destroy, + .activate = workspace_handle_activate, + .deactivate = workspace_handle_deactivate, + .remove = workspace_handle_remove, +}; + +inline static struct zext_workspace_group_handle_v1_interface workspace_group_impl = { + .create_workspace = workspace_group_handle_handle_create_workspace, + .destroy = workspace_group_handle_handle_destroy, +}; + +inline static struct zext_workspace_manager_v1_interface workspace_manager_impl = { + .commit = workspace_manager_commit, + .stop = workspace_manager_stop, +}; + +static void workspace_manager_idle_send_done(void *data) { + struct wlr_ext_workspace_manager_v1 *manager = (wlr_ext_workspace_manager_v1*)data; + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &manager->resources) { + zext_workspace_manager_v1_send_done(resource); + } + + manager->idle_source = NULL; +} + +static void workspace_manager_update_idle_source( + struct wlr_ext_workspace_manager_v1 *manager) { + if (manager->idle_source) { + return; + } + + manager->idle_source = wl_event_loop_add_idle(manager->event_loop, + workspace_manager_idle_send_done, manager); +} + +static struct wlr_ext_workspace_handle_v1 *workspace_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zext_workspace_handle_v1_interface, + &workspace_handle_impl)); + return (wlr_ext_workspace_handle_v1 *)wl_resource_get_user_data(resource); +} + +static struct wlr_ext_workspace_group_handle_v1 *workspace_group_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zext_workspace_group_handle_v1_interface, + &workspace_group_impl)); + return (wlr_ext_workspace_group_handle_v1 *)wl_resource_get_user_data(resource); +} + +static void workspace_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void workspace_handle_activate(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_ext_workspace_handle_v1 *workspace = workspace_from_resource(resource); + if (!workspace) { + return; + } + + workspace->pending |= WLR_EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE; +} + +static void workspace_handle_remove(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_ext_workspace_handle_v1 *workspace = workspace_from_resource(resource); + if (!workspace) { + return; + } + + wlr_signal_emit_safe(&workspace->events.remove_request, NULL); +} + +static void workspace_handle_deactivate(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_ext_workspace_handle_v1 *workspace = workspace_from_resource(resource); + if (!workspace) { + return; + } + + workspace->pending &= ~WLR_EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE; +} + +static void workspace_handle_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static bool push_entry_in_array(struct wl_array *array, uint32_t entry) { + uint32_t *index = (uint32_t *)wl_array_add(array, sizeof(uint32_t)); + if (index == NULL) { + return false; + } + *index = entry; + return true; +} + +static bool fill_array_from_workspace_state(struct wl_array *array, + uint32_t state) { + if ((state & WLR_EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE) && + !push_entry_in_array(array, ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) { + return false; + } + if ((state & WLR_EXT_WORKSPACE_HANDLE_V1_STATE_URGENT) && + !push_entry_in_array(array, ZEXT_WORKSPACE_HANDLE_V1_STATE_URGENT)) { + return false; + } + if ((state & WLR_EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN) && + !push_entry_in_array(array, ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN)) { + return false; + } + + return true; +} + +static void workspace_handle_send_details_to_resource( + struct wlr_ext_workspace_handle_v1 *workspace, struct wl_resource *resource) { + if (workspace->name) { + zext_workspace_handle_v1_send_name(resource, workspace->name); + } + + if (workspace->coordinates.size > 0) { + zext_workspace_handle_v1_send_coordinates(resource, + &workspace->coordinates); + } + + struct wl_array state; + wl_array_init(&state); + if (!fill_array_from_workspace_state(&state, workspace->server_state)) { + wl_resource_post_no_memory(resource); + wl_array_release(&state); + return; + } + + zext_workspace_handle_v1_send_state(resource, &state); +} + + + +void wlr_ext_workspace_handle_v1_set_name(struct wlr_ext_workspace_handle_v1 *workspace, + const char* name) { + free(workspace->name); + workspace->name = strdup(name); + + struct wl_resource *tmp, *resource; + wl_resource_for_each_safe(resource, tmp, &workspace->resources) { + zext_workspace_handle_v1_send_name(resource, name); + } + + workspace_manager_update_idle_source(workspace->group->manager); +} + +void wlr_ext_workspace_handle_v1_set_coordinates( + struct wlr_ext_workspace_handle_v1 *workspace, struct wl_array *coordinates) { + wl_array_copy(&workspace->coordinates, coordinates); + + struct wl_resource *tmp, *resource; + wl_resource_for_each_safe(resource, tmp, &workspace->resources) { + zext_workspace_handle_v1_send_coordinates(resource, coordinates); + } + + workspace_manager_update_idle_source(workspace->group->manager); +} + +static void workspace_send_state(struct wlr_ext_workspace_handle_v1 *workspace) { + struct wl_array state; + wl_array_init(&state); + + if (!fill_array_from_workspace_state(&state, workspace->server_state)) { + struct wl_resource *resource; + wl_resource_for_each(resource, &workspace->resources) { + wl_resource_post_no_memory(resource); + } + + wl_array_release(&state); + return; + } + + struct wl_resource *tmp, *resource; + wl_resource_for_each_safe(resource, tmp, &workspace->resources) { + zext_workspace_handle_v1_send_state(resource, &state); + } + + wl_array_release(&state); + workspace_manager_update_idle_source(workspace->group->manager); +} + +void wlr_ext_workspace_handle_v1_set_active( + struct wlr_ext_workspace_handle_v1 *workspace, bool activate) { + if (activate) { + workspace->server_state |= WLR_EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE; + } else { + workspace->server_state &= ~WLR_EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE; + } + + workspace_send_state(workspace); +} + +void wlr_ext_workspace_handle_v1_set_urgent( + struct wlr_ext_workspace_handle_v1 *workspace, bool urgent) { + if (urgent) { + workspace->server_state |= WLR_EXT_WORKSPACE_HANDLE_V1_STATE_URGENT; + } else { + workspace->server_state &= ~WLR_EXT_WORKSPACE_HANDLE_V1_STATE_URGENT; + } + + workspace_send_state(workspace); +} + +void wlr_ext_workspace_handle_v1_set_hidden( + struct wlr_ext_workspace_handle_v1 *workspace, bool hidden) { + if (hidden) { + workspace->server_state |= WLR_EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN; + } else { + workspace->server_state &= ~WLR_EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN; + } + + workspace_send_state(workspace); +} + +static struct wl_resource *create_workspace_resource_for_group_resource( + struct wlr_ext_workspace_handle_v1 *workspace, + struct wl_resource *group_resource) { + + struct wl_client *client = wl_resource_get_client(group_resource); + struct wl_resource *resource = wl_resource_create(client, + &zext_workspace_handle_v1_interface, + wl_resource_get_version(group_resource), 0); + if (!resource) { + wl_client_post_no_memory(client); + return NULL; + } + + wl_resource_set_implementation(resource, &workspace_handle_impl, workspace, + workspace_handle_resource_destroy); + + wl_list_insert(&workspace->resources, wl_resource_get_link(resource)); + zext_workspace_group_handle_v1_send_workspace(group_resource, resource); + + return resource; +} + +struct wlr_ext_workspace_handle_v1 *wlr_ext_workspace_handle_v1_create( + struct wlr_ext_workspace_group_handle_v1 *group) { + struct wlr_ext_workspace_handle_v1 *workspace = (wlr_ext_workspace_handle_v1 *)calloc(1, + sizeof(struct wlr_ext_workspace_handle_v1)); + if (!workspace) { + return NULL; + } + + workspace->group = group; + wl_list_insert(&group->workspaces, &workspace->link); + wl_array_init(&workspace->coordinates); + wl_list_init(&workspace->resources); + wl_signal_init(&workspace->events.remove_request); + wl_signal_init(&workspace->events.destroy); + + struct wl_resource *tmp, *group_resource; + wl_resource_for_each_safe(group_resource, tmp, &group->resources) { + create_workspace_resource_for_group_resource(workspace, group_resource); + } + + return workspace; +} + +void wlr_ext_workspace_handle_v1_destroy( + struct wlr_ext_workspace_handle_v1 *workspace) { + if (!workspace) { + return; + } + + wlr_signal_emit_safe(&workspace->events.destroy, workspace); + + workspace_manager_update_idle_source(workspace->group->manager); + + struct wl_resource *tmp, *resource; + wl_resource_for_each_safe(resource, tmp, &workspace->resources) { + zext_workspace_handle_v1_send_remove(resource); + + wl_resource_set_user_data(resource, NULL); + wl_list_remove(&resource->link); + wl_list_init(&resource->link); + } + + wl_array_release(&workspace->coordinates); + wl_list_remove(&workspace->link); + free(workspace->name); +} + +static void workspace_group_handle_handle_create_workspace(struct wl_client *client, + struct wl_resource *resource, const char *arg) { + struct wlr_ext_workspace_group_handle_v1 *group = + workspace_group_from_resource(resource); + + struct wlr_ext_workspace_group_handle_v1_create_workspace_event event; + event.workspace_group = group; + event.name = arg; + wlr_signal_emit_safe(&group->events.create_workspace_request, &event); +} + +static void workspace_group_handle_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void workspace_group_resource_destroy(struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +/** + * Create the workspace group resource and child workspace resources as well. + */ +static struct wl_resource *create_workspace_group_resource_for_resource( + struct wlr_ext_workspace_group_handle_v1 *group, + struct wl_resource *manager_resource) { + struct wl_client *client = wl_resource_get_client(manager_resource); + struct wl_resource *resource = wl_resource_create(client, + &zext_workspace_group_handle_v1_interface, + wl_resource_get_version(manager_resource), 0); + if (!resource) { + wl_client_post_no_memory(client); + return NULL; + } + + wl_resource_set_implementation(resource, &workspace_group_impl, group, + workspace_group_resource_destroy); + + wl_list_insert(&group->resources, wl_resource_get_link(resource)); + zext_workspace_manager_v1_send_workspace_group(manager_resource, resource); + + struct wlr_ext_workspace_handle_v1 *tmp, *workspace; + wl_list_for_each_safe(workspace, tmp, &group->workspaces, link) { + struct wl_resource *workspace_resource = + create_workspace_resource_for_group_resource(workspace, resource); + workspace_handle_send_details_to_resource(workspace, workspace_resource); + } + + return resource; +} + +static void send_output_to_group_resource(struct wl_resource *group_resource, + struct wlr_output *output, bool enter) { + struct wl_client *client = wl_resource_get_client(group_resource); + struct wl_resource *output_resource, *tmp; + + wl_resource_for_each_safe(output_resource, tmp, &output->resources) { + if (wl_resource_get_client(output_resource) == client) { + if (enter) { + zext_workspace_group_handle_v1_send_output_enter(group_resource, + output_resource); + } else { + zext_workspace_group_handle_v1_send_output_leave(group_resource, + output_resource); + } + } + } +} + +static void group_send_output(struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_output *output, bool enter) { + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, &group->resources) { + send_output_to_group_resource(resource, output, enter); + } +} + +static void workspace_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_ext_workspace_group_handle_v1_output *output = + wl_container_of(listener, output, output_destroy); + wlr_ext_workspace_group_handle_v1_output_leave(output->group_handle, + output->output); +} + +void wlr_ext_workspace_group_handle_v1_output_enter( + struct wlr_ext_workspace_group_handle_v1 *group, struct wlr_output *output) { + struct wlr_ext_workspace_group_handle_v1_output *group_output; + wl_list_for_each(group_output, &group->outputs, link) { + if (group_output->output == output) { + return; // we have already sent output_enter event + } + } + + group_output = (wlr_ext_workspace_group_handle_v1_output *)calloc(1, sizeof(struct wlr_ext_workspace_group_handle_v1_output)); + if (!group_output) { + wlr_log(WLR_ERROR, "failed to allocate memory for workspace output"); + return; + } + + group_output->output = output; + group_output->group_handle = group; + wl_list_insert(&group->outputs, &group_output->link); + + group_output->output_destroy.notify = workspace_handle_output_destroy; + wl_signal_add(&output->events.destroy, &group_output->output_destroy); + + group_send_output(group, output, true); +} + +static void group_output_destroy( + struct wlr_ext_workspace_group_handle_v1_output *output) { + wl_list_remove(&output->link); + wl_list_remove(&output->output_destroy.link); + free(output); +} + +void wlr_ext_workspace_group_handle_v1_output_leave( + struct wlr_ext_workspace_group_handle_v1 *group, struct wlr_output *output) { + struct wlr_ext_workspace_group_handle_v1_output *group_output_iterator; + struct wlr_ext_workspace_group_handle_v1_output *group_output = NULL; + + wl_list_for_each(group_output_iterator, &group->outputs, link) { + if (group_output_iterator->output == output) { + group_output = group_output_iterator; + break; + } + } + + if (group_output) { + group_send_output(group, output, false); + group_output_destroy(group_output); + } else { + // XXX: log an error? crash? + } +} + +static void group_send_details_to_resource( + struct wlr_ext_workspace_group_handle_v1 *group, + struct wl_resource *resource) { + struct wlr_ext_workspace_group_handle_v1_output *output; + wl_list_for_each(output, &group->outputs, link) { + send_output_to_group_resource(resource, output->output, true); + } +} + +struct wlr_ext_workspace_group_handle_v1 *wlr_ext_workspace_group_handle_v1_create( + struct wlr_ext_workspace_manager_v1 *manager) { + + struct wlr_ext_workspace_group_handle_v1 *group = (wlr_ext_workspace_group_handle_v1 *)calloc(1, + sizeof(struct wlr_ext_workspace_group_handle_v1)); + if (!group) { + return NULL; + } + + group->manager = manager; + wl_list_insert(&manager->groups, &group->link); + + wl_list_init(&group->outputs); + wl_list_init(&group->resources); + wl_list_init(&group->workspaces); + wl_signal_init(&group->events.create_workspace_request); + wl_signal_init(&group->events.destroy); + + struct wl_resource *tmp, *manager_resource; + wl_resource_for_each_safe(manager_resource, tmp, &manager->resources) { + create_workspace_group_resource_for_resource(group, manager_resource); + } + + return group; +} + +void wlr_ext_workspace_group_handle_v1_destroy( + struct wlr_ext_workspace_group_handle_v1 *group) { + if (!group) { + return; + } + + struct wlr_ext_workspace_handle_v1 *workspace, *tmp; + wl_list_for_each_safe(workspace, tmp, &group->workspaces, link) { + wlr_ext_workspace_handle_v1_destroy(workspace); + } + + wlr_signal_emit_safe(&group->events.destroy, group); + workspace_manager_update_idle_source(group->manager); + + struct wlr_ext_workspace_group_handle_v1_output *output, *tmp2; + wl_list_for_each_safe(output, tmp2, &group->outputs, link) { + group_output_destroy(output); + } + + struct wl_resource *tmp3, *resource; + wl_resource_for_each_safe(resource, tmp3, &group->resources) { + zext_workspace_group_handle_v1_send_remove(resource); + + wl_resource_set_user_data(resource, NULL); + wl_list_remove(&resource->link); + wl_list_init(&resource->link); + } + + free(group); +} + +static struct wlr_ext_workspace_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &zext_workspace_manager_v1_interface, + &workspace_manager_impl)); + return (wlr_ext_workspace_manager_v1 *)wl_resource_get_user_data(resource); +} + +static void rworkspace_manager_commit(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_ext_workspace_manager_v1 *manager = manager_from_resource(resource); + if (!manager) { + return; + } + + struct wlr_ext_workspace_group_handle_v1 *group; + struct wlr_ext_workspace_handle_v1 *workspace; + wl_list_for_each(group, &manager->groups, link) { + wl_list_for_each(workspace, &group->workspaces, link) { + workspace->current = workspace->pending; + } + } + + wlr_signal_emit_safe(&manager->events.commit, manager); +} + +static void workspace_manager_stop(struct wl_client *client, + struct wl_resource *resource) { + struct wlr_ext_workspace_manager_v1 *manager = manager_from_resource(resource); + if (!manager) { + return; + } + + zext_workspace_manager_v1_send_finished(resource); + wl_resource_destroy(resource); +} + +static void workspace_manager_resource_destroy( struct wl_resource *resource) { + wl_list_remove(wl_resource_get_link(resource)); +} + +static void workspace_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_ext_workspace_manager_v1 *manager = (wlr_ext_workspace_manager_v1 *)data; + struct wl_resource *resource = wl_resource_create(client, + &zext_workspace_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &workspace_manager_impl, + manager, workspace_manager_resource_destroy); + + wl_list_insert(&manager->resources, wl_resource_get_link(resource)); + + struct wlr_ext_workspace_group_handle_v1 *group, *tmp; + wl_list_for_each_safe(group, tmp, &manager->groups, link) { + struct wl_resource *group_resource = + create_workspace_group_resource_for_resource(group, resource); + group_send_details_to_resource(group, group_resource); + } + + zext_workspace_manager_v1_send_done(resource); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_workspace_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wlr_signal_emit_safe(&manager->events.destroy, manager); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + + free(manager); +} + +struct wlr_ext_workspace_manager_v1 *wlr_ext_workspace_manager_v1_create( + struct wl_display *display) { + + struct wlr_ext_workspace_manager_v1 *manager = (wlr_ext_workspace_manager_v1 *)calloc(1, + sizeof(struct wlr_ext_workspace_manager_v1)); + if (!manager) { + return NULL; + } + + manager->event_loop = wl_display_get_event_loop(display); + manager->global = wl_global_create(display, + &zext_workspace_manager_v1_interface, + WORKSPACE_V1_VERSION, manager, + workspace_manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.destroy); + wl_signal_init(&manager->events.commit); + wl_list_init(&manager->resources); + wl_list_init(&manager->groups); + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/src/wlrunstable/wlr_ext_workspace_v1.hpp b/src/wlrunstable/wlr_ext_workspace_v1.hpp new file mode 100644 index 00000000..1dbd03b0 --- /dev/null +++ b/src/wlrunstable/wlr_ext_workspace_v1.hpp @@ -0,0 +1,135 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ + +#ifndef WLR_TYPES_WLR_WORKSPACE_V1_H +#define WLR_TYPES_WLR_WORKSPACE_V1_H + +#include +#include + +struct wlr_ext_workspace_manager_v1 { + struct wl_event_loop *event_loop; + struct wl_event_source *idle_source; + + struct wl_global *global; + struct wl_list resources; // wl_resource_get_link + struct wl_list groups; // wlr_ext_workspace_group_handle_v1::link + + struct wl_listener display_destroy; + + struct { + struct wl_signal commit; // wlr_ext_workspace_manager_v1 + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_ext_workspace_group_handle_v1 { + struct wl_list link; // wlr_ext_workspace_manager_v1::groups + struct wl_list resources; // wl_ext_resource_get_link + + struct wl_list workspaces; // wlr_ext_workspace_handle_v1::link + struct wl_list outputs; // wlr_ext_workspace_group_handle_v1_output::link + + struct wlr_ext_workspace_manager_v1 *manager; + + struct { + // wlr_ext_workspace_group_handle_v1_create_workspace_event + struct wl_signal create_workspace_request; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_ext_workspace_group_handle_v1_create_workspace_event { + struct wlr_ext_workspace_group_handle_v1 *workspace_group; + const char *name; +}; + +struct wlr_ext_workspace_group_handle_v1_output { + struct wl_list link; // wlr_ext_workspace_group_handle_v1::outputs + struct wl_listener output_destroy; + struct wlr_output *output; + + struct wlr_ext_workspace_group_handle_v1 *group_handle; +}; + +enum wlr_ext_workspace_handle_v1_state +{ + WLR_EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE = 1 << 0, + WLR_EXT_WORKSPACE_HANDLE_V1_STATE_URGENT = 1 << 1, + WLR_EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN = 1 << 2, +}; + +struct wlr_ext_workspace_handle_v1 { + struct wl_list link; // wlr_ext_workspace_group_handle_v1::workspaces + struct wl_list resources; + + struct wlr_ext_workspace_group_handle_v1 *group; + + // request from the client + uint32_t pending, current; + + // set by the compositor + uint32_t server_state; + + char *name; + struct wl_array coordinates; + + struct { + struct wl_signal remove_request; + struct wl_signal destroy; + } events; + + void *data; +}; + +struct wlr_ext_workspace_manager_v1 *wlr_ext_workspace_manager_v1_create( + struct wl_display *display); + +struct wlr_ext_workspace_group_handle_v1 *wlr_ext_workspace_group_handle_v1_create( + struct wlr_ext_workspace_manager_v1 *manager); + +/** + * Destroy the workspace group and all workspaces inside it. + */ +void wlr_ext_workspace_group_handle_v1_destroy( + struct wlr_ext_workspace_group_handle_v1 *group); + +/** + * Create a new workspace in the workspace group. + * Note that the compositor must set the workspace name immediately after + * creating it. + */ +struct wlr_ext_workspace_handle_v1 *wlr_ext_workspace_handle_v1_create( + struct wlr_ext_workspace_group_handle_v1 *group); + +void wlr_ext_workspace_handle_v1_destroy( + struct wlr_ext_workspace_handle_v1 *workspace); + +void wlr_ext_workspace_group_handle_v1_output_enter( + struct wlr_ext_workspace_group_handle_v1 *group, struct wlr_output *output); + +void wlr_ext_workspace_group_handle_v1_output_leave( + struct wlr_ext_workspace_group_handle_v1 *group, struct wlr_output *output); + +void wlr_ext_workspace_handle_v1_set_name( + struct wlr_ext_workspace_handle_v1 *workspace, const char* name); + +void wlr_ext_workspace_handle_v1_set_coordinates( + struct wlr_ext_workspace_handle_v1 *workspace, struct wl_array *coordinates); + +void wlr_ext_workspace_handle_v1_set_active( + struct wlr_ext_workspace_handle_v1 *workspace, bool active); + +void wlr_ext_workspace_handle_v1_set_urgent( + struct wlr_ext_workspace_handle_v1 *workspace, bool urgent); + +void wlr_ext_workspace_handle_v1_set_hidden( + struct wlr_ext_workspace_handle_v1 *workspace, bool hidden); + +#endif