From 3bbdf9dc5ad2851f5d9f317362fc16705e4fd990 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Thu, 26 Jun 2025 09:32:44 -0700 Subject: protocols: add ext-workspace implementation (#10818) --- src/protocols/ExtWorkspace.cpp | 331 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 src/protocols/ExtWorkspace.cpp (limited to 'src/protocols/ExtWorkspace.cpp') diff --git a/src/protocols/ExtWorkspace.cpp b/src/protocols/ExtWorkspace.cpp new file mode 100644 index 00000000..6c55e76e --- /dev/null +++ b/src/protocols/ExtWorkspace.cpp @@ -0,0 +1,331 @@ +#include "ExtWorkspace.hpp" +#include "../Compositor.hpp" +#include "../managers/HookSystemManager.hpp" +#include "../managers/eventLoop/EventLoopManager.hpp" +#include +#include +#include +#include "core/Output.hpp" + +CExtWorkspaceGroupResource::CExtWorkspaceGroupResource(WP manager, UP resource, PHLMONITORREF monitor) : + m_monitor(std::move(monitor)), m_manager(std::move(manager)), m_resource(std::move(resource)) { + if (!good()) + return; + + m_resource->setData(this); + m_manager->m_resource->sendWorkspaceGroup(m_resource.get()); + + m_listeners.destroyed = m_monitor->m_events.destroy.registerListener([this](auto) { m_resource->sendRemoved(); }); + + m_resource->setOnDestroy([this](auto) { PROTO::extWorkspace->destroyGroup(m_self); }); + m_resource->setDestroy([this](auto) { PROTO::extWorkspace->destroyGroup(m_self); }); + + m_resource->sendCapabilities(static_cast(0)); + + const auto& output = PROTO::outputs.at(m_monitor->m_name); + + if (auto resource = output->outputResourceFrom(m_resource->client())) + m_resource->sendOutputEnter(resource->getResource()->resource()); + + m_listeners.outputBound = output->m_events.outputBound.registerListener([this](std::any data) { + auto resource = std::any_cast>(data); + + if (resource->client() == m_resource->client()) + m_resource->sendOutputEnter(resource->getResource()->resource()); + }); + + m_manager->sendGroupToWorkspaces(m_self); + m_manager->scheduleDone(); +} + +bool CExtWorkspaceGroupResource::good() const { + return m_resource; +} + +WP CExtWorkspaceGroupResource::fromResource(wl_resource* resource) { + auto handle = static_cast(wl_resource_get_user_data(resource))->data(); + auto data = static_cast(handle); + return data ? data->m_self : WP(); +} + +void CExtWorkspaceGroupResource::workspaceEnter(const WP& handle) { + m_resource->sendWorkspaceEnter(handle.get()); +} +void CExtWorkspaceGroupResource::workspaceLeave(const WP& handle) { + m_resource->sendWorkspaceLeave(handle.get()); +} + +CExtWorkspaceResource::CExtWorkspaceResource(WP manager, UP resource, PHLWORKSPACEREF workspace) : + m_manager(std::move(manager)), m_resource(std::move(resource)), m_workspace(std::move(workspace)) { + if (!good()) + return; + + m_resource->setData(this); + m_manager->m_resource->sendWorkspace(m_resource.get()); + + m_listeners.destroyed = m_workspace->m_events.destroy.registerListener([this](auto) { + m_resource->sendRemoved(); + + if (m_manager) + m_manager->scheduleDone(); + }); + + m_listeners.activeChanged = m_workspace->m_events.activeChange.registerListener([this](auto) { + sendState(); + sendCapabilities(); + }); + + m_listeners.monitorChanged = m_workspace->m_events.monitorChange.registerListener([this](auto) { this->sendGroup(); }); + + m_listeners.renamed = m_workspace->m_events.rename.registerListener([this](auto) { + m_resource->sendName(m_workspace->m_name.c_str()); + + if (m_manager) + m_manager->scheduleDone(); + }); + + m_resource->setOnDestroy([this](auto) { PROTO::extWorkspace->destroyWorkspace(m_self); }); + m_resource->setDestroy([this](auto) { PROTO::extWorkspace->destroyWorkspace(m_self); }); + + m_resource->setActivate([this](void*) { m_pendingState.activate = true; }); + m_resource->setDeactivate([this](void*) { m_pendingState.deactivate = true; }); + + m_resource->setAssign([this](void*, wl_resource* groupResource) { + auto group = CExtWorkspaceGroupResource::fromResource(groupResource); + + if (group) + m_pendingState.targetMonitor = group->m_monitor; + }); + + m_resource->sendName(m_workspace->m_name.c_str()); + + wl_array coordinates; + wl_array_init(&coordinates); + + auto id = m_workspace->m_id; + if (id < 0 && !m_workspace->m_name.empty()) + id += UINT32_MAX - 1337; + + if (id > 0) + *static_cast(wl_array_add(&coordinates, sizeof(uint32_t))) = id; + + m_resource->sendCoordinates(&coordinates); + wl_array_release(&coordinates); + + sendState(); + sendCapabilities(); + sendGroup(); + + m_manager->scheduleDone(); +} + +bool CExtWorkspaceResource::good() const { + return m_resource; +} + +bool CExtWorkspaceResource::isActive() const { + if (!m_workspace) + return false; + + auto const& monitor = m_workspace->m_monitor; + auto const& cmpWorkspace = m_workspace->m_isSpecialWorkspace ? monitor->m_activeSpecialWorkspace : monitor->m_activeWorkspace; + return m_workspace == cmpWorkspace; +} + +void CExtWorkspaceResource::sendState() { + uint32_t state = 0; + + if (isActive()) + state |= EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE; + + if (m_workspace->hasUrgentWindow()) + state |= EXT_WORKSPACE_HANDLE_V1_STATE_URGENT; + + if (m_workspace->m_isSpecialWorkspace) + state |= EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN; + + m_resource->sendState(static_cast(state)); + + if (m_manager) + m_manager->scheduleDone(); +} + +void CExtWorkspaceResource::sendCapabilities() { + uint32_t capabilities = EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN; + auto active = isActive(); + + if (!active) + capabilities |= EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE; + + if (active && m_workspace->m_isSpecialWorkspace) + capabilities |= EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE; + + m_resource->sendCapabilities(static_cast(capabilities)); + + if (m_manager) + m_manager->scheduleDone(); +} + +void CExtWorkspaceResource::sendGroup() { + if (m_group) + m_group->workspaceLeave(m_resource); + + if (m_manager) { + m_group = m_manager->findGroup(m_workspace->m_monitor); + + if (m_group) + m_group->workspaceEnter(m_resource); + + m_manager->scheduleDone(); + } +} + +void CExtWorkspaceResource::commit() { + // order is important + + if (m_pendingState.deactivate && isActive() && m_workspace->m_isSpecialWorkspace) + m_workspace->m_monitor->setSpecialWorkspace(nullptr); + + if (m_pendingState.targetMonitor && m_workspace && m_workspace->m_monitor != m_pendingState.targetMonitor) + g_pCompositor->moveWorkspaceToMonitor(m_workspace.lock(), m_pendingState.targetMonitor.lock(), true); + + if (m_pendingState.activate && !isActive() && m_workspace) + m_workspace->m_monitor->changeWorkspace(m_workspace.lock()); + + m_pendingState.activate = false; + m_pendingState.deactivate = false; + m_pendingState.targetMonitor.reset(); +} + +CExtWorkspaceManagerResource::CExtWorkspaceManagerResource(UP resource) : m_resource(std::move(resource)) { + if (!good()) + return; + + m_resource->setOnDestroy([this](auto) { PROTO::extWorkspace->destroyManager(m_self); }); + + m_resource->setStop([this](auto) { + m_resource->sendFinished(); + PROTO::extWorkspace->destroyManager(m_self); + }); + + m_resource->setCommit([this](auto) { + for (auto& workspace : PROTO::extWorkspace->m_workspaces) { + if (workspace->m_manager == m_self) + workspace->commit(); + } + }); +} + +void CExtWorkspaceManagerResource::init(WP self) { + if (!good()) + return; + + m_self = self; + + for (auto const& m : g_pCompositor->m_monitors) { + onMonitorCreated(m); + } + + for (auto const& w : g_pCompositor->m_workspaces) { + onWorkspaceCreated(w); + } +} + +bool CExtWorkspaceManagerResource::good() const { + return m_resource; +} + +void CExtWorkspaceManagerResource::scheduleDone() { + if (m_doneScheduled) + return; + + m_doneScheduled = true; + g_pEventLoopManager->doLater([self = m_self] { + if (!self || !self->m_resource) + return; + + self->m_doneScheduled = false; + self->m_resource->sendDone(); + }); +} + +WP CExtWorkspaceManagerResource::findGroup(const PHLMONITORREF& monitor) const { + auto iter = std::ranges::find_if(PROTO::extWorkspace->m_groups, + [&](const UP& resource) { return resource->m_manager.get() == this && resource->m_monitor == monitor; }); + + return iter != PROTO::extWorkspace->m_groups.end() ? *iter : WP(); +} + +void CExtWorkspaceManagerResource::sendGroupToWorkspaces(const WP& group) { + for (auto& workspace : PROTO::extWorkspace->m_workspaces) { + if (workspace->m_manager == m_self && workspace->m_workspace && workspace->m_workspace->m_monitor == group->m_monitor) + workspace->sendGroup(); + } +} + +void CExtWorkspaceManagerResource::onMonitorCreated(const PHLMONITOR& monitor) { + auto& group = PROTO::extWorkspace->m_groups.emplace_back( + makeUnique(m_self, makeUnique(m_resource->client(), m_resource->version(), 0), monitor)); + group->m_self = group; + + if UNLIKELY (!group->good()) { + LOGM(ERR, "Couldn't create a workspace group object"); + wl_client_post_no_memory(m_resource->client()); + return; + } + + scheduleDone(); +} + +void CExtWorkspaceManagerResource::onWorkspaceCreated(const PHLWORKSPACE& workspace) { + auto& ws = PROTO::extWorkspace->m_workspaces.emplace_back( + makeUnique(m_self, makeUnique(m_resource->client(), m_resource->version(), 0), workspace)); + ws->m_self = ws; + + if UNLIKELY (!ws->good()) { + LOGM(ERR, "Couldn't create a workspace object"); + wl_client_post_no_memory(m_resource->client()); + return; + } +} + +CExtWorkspaceProtocol::CExtWorkspaceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + static auto P1 = g_pHookSystem->hookDynamic("createWorkspace", [this](void* self, SCallbackInfo& info, std::any data) { + auto workspace = std::any_cast(data)->m_self.lock(); + + for (auto const& m : m_managers) { + m->onWorkspaceCreated(workspace); + } + }); + + static auto P2 = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any data) { + auto monitor = std::any_cast(data); + + for (auto const& m : m_managers) { + m->onMonitorCreated(monitor); + } + }); +} + +void CExtWorkspaceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + auto& manager = m_managers.emplace_back(makeUnique(makeUnique(client, ver, id))); + manager->init(manager); + + if UNLIKELY (!manager->good()) { + LOGM(ERR, "Couldn't create a workspace manager"); + wl_client_post_no_memory(client); + return; + } +} + +void CExtWorkspaceProtocol::destroyGroup(const WP& group) { + std::erase_if(m_groups, [&](const UP& resource) { return resource == group; }); +} + +void CExtWorkspaceProtocol::destroyWorkspace(const WP& workspace) { + std::erase_if(m_workspaces, [&](const UP& resource) { return resource == workspace; }); +} + +void CExtWorkspaceProtocol::destroyManager(const WP& manager) { + std::erase_if(PROTO::extWorkspace->m_managers, [&](const UP& resource) { return resource == manager; }); +} -- cgit v1.2.3