From addd3e7f1aeb670dd91d26005aaeccce3efb1ae7 Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Sat, 25 May 2024 22:43:51 +0200 Subject: [PATCH] xwayland: move to hyprland impl (#6086) --- CMakeLists.txt | 1 + protocols/meson.build | 1 + src/Compositor.cpp | 28 +- src/Compositor.hpp | 18 - src/desktop/Window.cpp | 164 +++- src/desktop/Window.hpp | 40 +- src/events/Events.hpp | 3 - src/events/Misc.cpp | 44 - src/events/Windows.cpp | 270 +----- src/helpers/XWaylandStubs.hpp | 172 ---- src/includes.hpp | 5 - src/layout/IHyprLayout.cpp | 9 +- src/managers/CursorManager.cpp | 11 +- src/managers/CursorManager.hpp | 3 +- src/managers/ProtocolManager.cpp | 2 + src/managers/SeatManager.cpp | 4 + src/managers/SeatManager.hpp | 2 + src/managers/XWaylandManager.cpp | 134 +-- src/managers/XWaylandManager.hpp | 29 +- src/managers/input/InputManager.cpp | 18 +- src/protocols/XDGOutput.cpp | 3 +- src/protocols/XWaylandShell.cpp | 84 ++ src/protocols/XWaylandShell.hpp | 66 ++ src/protocols/types/DataDevice.cpp | 4 + src/protocols/types/DataDevice.hpp | 6 + src/render/Renderer.cpp | 10 +- src/xwayland/Server.cpp | 427 ++++++++++ src/xwayland/Server.hpp | 48 ++ src/xwayland/XDataSource.cpp | 97 +++ src/xwayland/XDataSource.hpp | 22 + src/xwayland/XSurface.cpp | 291 +++++++ src/xwayland/XSurface.hpp | 120 +++ src/xwayland/XWM.cpp | 1210 +++++++++++++++++++++++++++ src/xwayland/XWM.hpp | 160 ++++ src/xwayland/XWayland.cpp | 28 + src/xwayland/XWayland.hpp | 129 +++ 36 files changed, 2956 insertions(+), 707 deletions(-) delete mode 100644 src/helpers/XWaylandStubs.hpp create mode 100644 src/protocols/XWaylandShell.cpp create mode 100644 src/protocols/XWaylandShell.hpp create mode 100644 src/xwayland/Server.cpp create mode 100644 src/xwayland/Server.hpp create mode 100644 src/xwayland/XDataSource.cpp create mode 100644 src/xwayland/XDataSource.hpp create mode 100644 src/xwayland/XSurface.cpp create mode 100644 src/xwayland/XSurface.hpp create mode 100644 src/xwayland/XWM.cpp create mode 100644 src/xwayland/XWM.hpp create mode 100644 src/xwayland/XWayland.cpp create mode 100644 src/xwayland/XWayland.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index adc9a5a2..3e780ac0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -315,6 +315,7 @@ protocolNew("stable/tablet" "tablet-v2" false) protocolNew("stable/presentation-time" "presentation-time" false) protocolNew("stable/xdg-shell" "xdg-shell" false) protocolNew("unstable/primary-selection" "primary-selection-unstable-v1" false) +protocolNew("staging/xwayland-shell" "xwayland-shell-v1" false) protocolWayland() diff --git a/protocols/meson.build b/protocols/meson.build index 2c331e4b..f491bb09 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -63,6 +63,7 @@ new_protocols = [ [wl_protocol_dir, 'stable/presentation-time/presentation-time.xml'], [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], [wl_protocol_dir, 'unstable/primary-selection/primary-selection-unstable-v1.xml'], + [wl_protocol_dir, 'staging/xwayland-shell/xwayland-shell-v1.xml'], ] wl_protos_src = [] diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 529a3b3d..a14e91fd 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -20,6 +20,7 @@ #include "protocols/LayerShell.hpp" #include "protocols/XDGShell.hpp" #include "desktop/LayerSurface.hpp" +#include "xwayland/XWayland.hpp" #include #include @@ -332,12 +333,9 @@ void CCompositor::cleanup() { m->state.commit(); } - m_vMonitors.clear(); + g_pXWayland.reset(); - if (g_pXWaylandManager->m_sWLRXWayland) { - wlr_xwayland_destroy(g_pXWaylandManager->m_sWLRXWayland); - g_pXWaylandManager->m_sWLRXWayland = nullptr; - } + m_vMonitors.clear(); wl_display_destroy_clients(g_pCompositor->m_sWLDisplay); removeAllSignals(); @@ -462,6 +460,9 @@ void CCompositor::initManagers(eManagersInitStage stage) { Debug::log(LOG, "Creating the CursorManager!"); g_pCursorManager = std::make_unique(); + + Debug::log(LOG, "Starting XWayland"); + g_pXWayland = std::make_unique(); } break; default: UNREACHABLE(); } @@ -669,7 +670,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper if (properties & ALLOW_FLOATING) { for (auto& w : m_vWindows | std::views::reverse) { const auto BB = w->getWindowBoxUnified(properties); - CBox box = {BB.x - BORDER_GRAB_AREA, BB.y - BORDER_GRAB_AREA, BB.width + 2 * BORDER_GRAB_AREA, BB.height + 2 * BORDER_GRAB_AREA}; + CBox box = BB.copy().expand(w->m_iX11Type == 2 ? BORDER_GRAB_AREA : 0); if (w->m_bIsFloating && w->m_bIsMapped && !w->isHidden() && !w->m_bX11ShouldntFocus && w->m_bPinned && !w->m_sAdditionalConfigData.noFocus && w != pIgnoreWindow) { if (box.containsPoint(g_pPointerManager->position())) return w; @@ -698,7 +699,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper BB.x + BB.width <= PWINDOWMONITOR->vecPosition.x + PWINDOWMONITOR->vecSize.x && BB.y + BB.height <= PWINDOWMONITOR->vecPosition.y + PWINDOWMONITOR->vecSize.y) continue; - CBox box = {BB.x - BORDER_GRAB_AREA, BB.y - BORDER_GRAB_AREA, BB.width + 2 * BORDER_GRAB_AREA, BB.height + 2 * BORDER_GRAB_AREA}; + CBox box = BB.copy().expand(w->m_iX11Type == 2 ? BORDER_GRAB_AREA : 0); if (w->m_bIsFloating && w->m_bIsMapped && isWorkspaceVisible(w->m_pWorkspace) && !w->isHidden() && !w->m_bPinned && !w->m_sAdditionalConfigData.noFocus && w != pIgnoreWindow && (!aboveFullscreen || w->m_bCreatedOverFullscreen)) { // OR windows should add focus to parent @@ -707,7 +708,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper if (box.containsPoint(g_pPointerManager->position())) { - if (w->m_bIsX11 && w->m_iX11Type == 2 && !wlr_xwayland_or_surface_wants_focus(w->m_uSurface.xwayland)) { + if (w->m_bIsX11 && w->m_iX11Type == 2 && !w->m_pXWaylandSurface->wantsFocus()) { // Override Redirect return g_pCompositor->m_pLastWindow.lock(); // we kinda trick everything here. // TODO: this is wrong, we should focus the parent, but idk how to get it considering it's nullptr in most cases. @@ -892,7 +893,7 @@ void CCompositor::focusWindow(PHLWINDOW pWindow, wlr_surface* pSurface) { return; } - if (pWindow && pWindow->m_bIsX11 && pWindow->m_iX11Type == 2 && !wlr_xwayland_or_surface_wants_focus(pWindow->m_uSurface.xwayland)) + if (pWindow && pWindow->m_bIsX11 && pWindow->m_iX11Type == 2 && !pWindow->m_pXWaylandSurface->wantsFocus()) return; g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow); @@ -1273,10 +1274,9 @@ void CCompositor::changeWindowZOrder(PHLWINDOW pWindow, bool top) { if (top) pWindow->m_bCreatedOverFullscreen = true; - if (!pWindow->m_bIsX11) { + if (!pWindow->m_bIsX11) moveToZ(pWindow, top); - return; - } else { + else { // move X11 window stack std::deque toMove; @@ -1288,7 +1288,7 @@ void CCompositor::changeWindowZOrder(PHLWINDOW pWindow, bool top) { toMove.emplace_front(pw); for (auto& w : m_vWindows) { - if (w->m_bIsMapped && !w->isHidden() && w->m_bIsX11 && w->X11TransientFor() == pw) { + if (w->m_bIsMapped && !w->isHidden() && w->m_bIsX11 && w->X11TransientFor() == pw && w != pw && std::find(toMove.begin(), toMove.end(), w) == toMove.end()) { x11Stack(w, top, x11Stack); } } @@ -2226,7 +2226,7 @@ PHLWINDOW CCompositor::getX11Parent(PHLWINDOW pWindow) { if (!w->m_bIsX11) continue; - if (w->m_uSurface.xwayland == pWindow->m_uSurface.xwayland->parent) + if (w->m_pXWaylandSurface == pWindow->m_pXWaylandSurface->parent) return w; } diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 574889bc..94d9e4d0 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -189,21 +189,3 @@ class CCompositor { }; inline std::unique_ptr g_pCompositor; - -// For XWayland -inline std::map HYPRATOMS = {HYPRATOM("_NET_WM_WINDOW_TYPE"), - HYPRATOM("_NET_WM_WINDOW_TYPE_NORMAL"), - HYPRATOM("_NET_WM_WINDOW_TYPE_DOCK"), - HYPRATOM("_NET_WM_WINDOW_TYPE_DIALOG"), - HYPRATOM("_NET_WM_WINDOW_TYPE_UTILITY"), - HYPRATOM("_NET_WM_WINDOW_TYPE_TOOLBAR"), - HYPRATOM("_NET_WM_WINDOW_TYPE_SPLASH"), - HYPRATOM("_NET_WM_WINDOW_TYPE_MENU"), - HYPRATOM("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"), - HYPRATOM("_NET_WM_WINDOW_TYPE_POPUP_MENU"), - HYPRATOM("_NET_WM_WINDOW_TYPE_TOOLTIP"), - HYPRATOM("_NET_WM_WINDOW_TYPE_NOTIFICATION"), - HYPRATOM("_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"), - HYPRATOM("_NET_SUPPORTING_WM_CHECK"), - HYPRATOM("_NET_WM_NAME"), - HYPRATOM("UTF8_STRING")}; diff --git a/src/desktop/Window.cpp b/src/desktop/Window.cpp index 7a06aa61..093cab5e 100644 --- a/src/desktop/Window.cpp +++ b/src/desktop/Window.cpp @@ -7,11 +7,14 @@ #include #include "../managers/TokenManager.hpp" #include "../protocols/XDGShell.hpp" +#include "../xwayland/XWayland.hpp" -PHLWINDOW CWindow::create() { - PHLWINDOW pWindow = SP(new CWindow); +PHLWINDOW CWindow::create(SP surface) { + PHLWINDOW pWindow = SP(new CWindow(surface)); - pWindow->m_pSelf = pWindow; + pWindow->m_pSelf = pWindow; + pWindow->m_bIsX11 = true; + pWindow->m_iX11Type = surface->overrideRedirect ? 2 : 1; pWindow->m_vRealPosition.create(g_pConfigManager->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); pWindow->m_vRealSize.create(g_pConfigManager->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); @@ -61,8 +64,19 @@ CWindow::CWindow(SP resource) : m_pXDGSurface(resource) { listeners.updateMetadata = m_pXDGSurface->toplevel->events.metadataChanged.registerListener([this](std::any d) { onUpdateMeta(); }); } -CWindow::CWindow() { - ; +CWindow::CWindow(SP surface) : m_pXWaylandSurface(surface) { + listeners.map = m_pXWaylandSurface->events.map.registerListener([this](std::any d) { Events::listener_mapWindow(this, nullptr); }); + listeners.unmap = m_pXWaylandSurface->events.unmap.registerListener([this](std::any d) { Events::listener_unmapWindow(this, nullptr); }); + listeners.destroy = m_pXWaylandSurface->events.destroy.registerListener([this](std::any d) { Events::listener_destroyWindow(this, nullptr); }); + listeners.commit = m_pXWaylandSurface->events.commit.registerListener([this](std::any d) { Events::listener_commitWindow(this, nullptr); }); + listeners.configure = m_pXWaylandSurface->events.configure.registerListener([this](std::any d) { onX11Configure(std::any_cast(d)); }); + listeners.updateState = m_pXWaylandSurface->events.stateChanged.registerListener([this](std::any d) { onUpdateState(); }); + listeners.updateMetadata = m_pXWaylandSurface->events.metadataChanged.registerListener([this](std::any d) { onUpdateMeta(); }); + listeners.resourceChange = m_pXWaylandSurface->events.resourceChange.registerListener([this](std::any d) { onResourceChangeX11(); }); + listeners.activate = m_pXWaylandSurface->events.activate.registerListener([this](std::any d) { Events::listener_activateX11(this, nullptr); }); + + if (m_pXWaylandSurface->overrideRedirect) + listeners.setGeometry = m_pXWaylandSurface->events.setGeometry.registerListener([this](std::any d) { Events::listener_unmanagedSetGeometry(this, nullptr); }); } CWindow::~CWindow() { @@ -300,10 +314,10 @@ pid_t CWindow::getPID() { wl_client_get_credentials(m_pXDGSurface->owner->client(), &PID, nullptr, nullptr); } else { - if (!m_uSurface.xwayland) + if (!m_pXWaylandSurface) return -1; - PID = m_uSurface.xwayland->pid; + PID = m_pXWaylandSurface->pid; } return PID; @@ -426,22 +440,23 @@ void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) { } PHLWINDOW CWindow::X11TransientFor() { - if (!m_bIsX11) + if (!m_pXWaylandSurface || !m_pXWaylandSurface->parent) return nullptr; - if (!m_uSurface.xwayland->parent) - return nullptr; - - auto PPARENT = g_pCompositor->getWindowFromSurface(m_uSurface.xwayland->parent->surface); - - while (validMapped(PPARENT) && PPARENT->m_uSurface.xwayland->parent) { - PPARENT = g_pCompositor->getWindowFromSurface(PPARENT->m_uSurface.xwayland->parent->surface); + auto s = m_pXWaylandSurface->parent; + while (s) { + if (!s->parent) + break; + s = s->parent; } - if (!validMapped(PPARENT)) - return nullptr; + for (auto& w : g_pCompositor->m_vWindows) { + if (w->m_pXWaylandSurface != s) + continue; + return w; + } - return PPARENT; + return nullptr; } void CWindow::removeDecorationByType(eDecorationType type) { @@ -494,8 +509,6 @@ void CWindow::onUnmap() { std::erase_if(g_pCompositor->m_vWindowFocusHistory, [&](const auto& other) { return other.expired() || other.lock().get() == this; }); - hyprListener_unmapWindow.removeCallback(); - if (*PCLOSEONLASTSPECIAL && g_pCompositor->getWindowsOnWorkspace(workspaceID()) == 0 && onSpecialWorkspace()) { const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID); if (PMONITOR && PMONITOR->activeSpecialWorkspace && PMONITOR->activeSpecialWorkspace == m_pWorkspace) @@ -548,9 +561,6 @@ void CWindow::onMap() { g_pCompositor->m_vWindowFocusHistory.push_back(m_pSelf); - if (m_bIsX11) - hyprListener_unmapWindow.initCallback(&m_uSurface.xwayland->surface->events.unmap, &Events::listener_unmapWindow, this, "CWindow"); - m_vReportedSize = m_vPendingReportedSize; m_bAnimatingIn = true; @@ -1119,9 +1129,9 @@ bool CWindow::opaque() { return false; if (m_bIsX11) - return !m_uSurface.xwayland->has_alpha; + return false; - if (m_pXDGSurface->surface->opaque) + if (m_pXDGSurface && m_pXDGSurface->surface->opaque) return true; const auto EXTENTS = pixman_region32_extents(&m_pXDGSurface->surface->opaque_region); @@ -1337,23 +1347,23 @@ void CWindow::activate(bool force) { } void CWindow::onUpdateState() { - if (!m_pXDGSurface) - return; + std::optional requestsFS = m_pXDGSurface ? m_pXDGSurface->toplevel->state.requestsFullscreen : m_pXWaylandSurface->state.requestsFullscreen; + std::optional requestsMX = m_pXDGSurface ? m_pXDGSurface->toplevel->state.requestsMaximize : m_pXWaylandSurface->state.requestsMaximize; - if (m_pXDGSurface->toplevel->state.requestsFullscreen && !(m_eSuppressedEvents & SUPPRESS_FULLSCREEN)) { - bool fs = m_pXDGSurface->toplevel->state.requestsFullscreen.value(); + if (requestsFS.has_value() && !(m_eSuppressedEvents & SUPPRESS_FULLSCREEN)) { + bool fs = requestsFS.value(); - if (fs != m_bIsFullscreen && m_pXDGSurface->mapped) + if (fs != m_bIsFullscreen && m_bIsMapped) g_pCompositor->setWindowFullscreen(m_pSelf.lock(), fs, FULLSCREEN_FULL); - if (!m_pXDGSurface->mapped) + if (!m_bIsMapped) m_bWantsInitialFullscreen = fs; } - if (m_pXDGSurface->toplevel->state.requestsMaximize && !(m_eSuppressedEvents & SUPPRESS_MAXIMIZE)) { - bool fs = m_pXDGSurface->toplevel->state.requestsMaximize.value(); + if (requestsMX.has_value() && !(m_eSuppressedEvents & SUPPRESS_MAXIMIZE)) { + bool fs = requestsMX.value(); - if (fs != m_bIsFullscreen && m_pXDGSurface->mapped) + if (fs != m_bIsFullscreen && m_bIsMapped) g_pCompositor->setWindowFullscreen(m_pSelf.lock(), fs, FULLSCREEN_MAXIMIZED); } } @@ -1402,8 +1412,8 @@ std::string CWindow::fetchTitle() { if (m_pXDGSurface && m_pXDGSurface->toplevel) return m_pXDGSurface->toplevel->state.title; } else { - if (m_uSurface.xwayland && m_uSurface.xwayland->title) - return m_uSurface.xwayland->title; + if (m_pXWaylandSurface) + return m_pXWaylandSurface->state.title; } return ""; @@ -1414,8 +1424,8 @@ std::string CWindow::fetchClass() { if (m_pXDGSurface && m_pXDGSurface->toplevel) return m_pXDGSurface->toplevel->state.appid; } else { - if (m_uSurface.xwayland && m_uSurface.xwayland->_class) - return m_uSurface.xwayland->_class; + if (m_pXWaylandSurface) + return m_pXWaylandSurface->state.appid; } return ""; @@ -1429,4 +1439,80 @@ void CWindow::onAck(uint32_t serial) { m_pPendingSizeAck = *SERIAL; std::erase_if(m_vPendingSizeAcks, [&](const auto& el) { return el.first <= SERIAL->first; }); -} \ No newline at end of file +} + +void CWindow::onResourceChangeX11() { + if (m_pXWaylandSurface->surface && !m_pWLSurface.wlr()) + m_pWLSurface.assign(m_pXWaylandSurface->surface, m_pSelf.lock()); + else if (!m_pXWaylandSurface->surface && m_pWLSurface.wlr()) + m_pWLSurface.unassign(); + + // update metadata as well, + // could be first assoc and we need to catch the class + onUpdateMeta(); + + Debug::log(LOG, "xwayland window {:x} -> association to {:x}", (uintptr_t)m_pXWaylandSurface.get(), (uintptr_t)m_pWLSurface.wlr()); +} + +void CWindow::onX11Configure(CBox box) { + + if (!m_pXWaylandSurface->surface || !m_pXWaylandSurface->mapped || !m_bIsMapped) { + m_pXWaylandSurface->configure(box); + m_vPendingReportedSize = box.size(); + m_vReportedSize = box.size(); + if (const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID); PMONITOR) + m_fX11SurfaceScaledBy = PMONITOR->scale; + return; + } + + g_pHyprRenderer->damageWindow(m_pSelf.lock()); + + if (!m_bIsFloating || m_bIsFullscreen || g_pInputManager->currentlyDraggedWindow == m_pSelf) { + g_pXWaylandManager->setWindowSize(m_pSelf.lock(), m_vRealSize.goal(), true); + g_pInputManager->refocus(); + g_pHyprRenderer->damageWindow(m_pSelf.lock()); + return; + } + + if (box.size() > Vector2D{1, 1}) + setHidden(false); + else + setHidden(true); + + const auto LOGICALPOS = g_pXWaylandManager->xwaylandToWaylandCoords(box.pos()); + + m_vRealPosition.setValueAndWarp(LOGICALPOS); + m_vRealSize.setValueAndWarp(box.size()); + + static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); + if (*PXWLFORCESCALEZERO) { + if (const auto PMONITOR = g_pCompositor->getMonitorFromID(m_iMonitorID); PMONITOR) { + m_vRealSize.setValueAndWarp(m_vRealSize.goal() / PMONITOR->scale); + m_fX11SurfaceScaledBy = PMONITOR->scale; + } + } + + m_vPosition = m_vRealPosition.value(); + m_vSize = m_vRealSize.value(); + + m_pXWaylandSurface->configure(box); + + m_vPendingReportedSize = box.size(); + m_vReportedSize = box.size(); + + updateWindowDecos(); + + if (!g_pCompositor->isWorkspaceVisible(m_pWorkspace)) + return; // further things are only for visible windows + + m_pWorkspace = g_pCompositor->getMonitorFromVector(m_vRealPosition.value() + m_vRealSize.value() / 2.f)->activeWorkspace; + + g_pCompositor->changeWindowZOrder(m_pSelf.lock(), true); + + m_bCreatedOverFullscreen = true; + + if (!m_sAdditionalConfigData.windowDanceCompat) + g_pInputManager->refocus(); + + g_pHyprRenderer->damageWindow(m_pSelf.lock()); +} diff --git a/src/desktop/Window.hpp b/src/desktop/Window.hpp index 24d9562b..473ce361 100644 --- a/src/desktop/Window.hpp +++ b/src/desktop/Window.hpp @@ -15,6 +15,7 @@ #include "../helpers/signal/Signal.hpp" class CXDGSurfaceResource; +class CXWaylandSurface; enum eIdleInhibitMode { IDLEINHIBIT_NONE = 0, @@ -199,48 +200,23 @@ struct SInitialWorkspaceToken { class CWindow { public: static PHLWINDOW create(SP); - // xwl - static PHLWINDOW create(); + static PHLWINDOW create(SP); private: CWindow(SP resource); - CWindow(); + CWindow(SP surface); public: ~CWindow(); - DYNLISTENER(commitWindow); - DYNLISTENER(mapWindow); - DYNLISTENER(unmapWindow); - DYNLISTENER(destroyWindow); - DYNLISTENER(setTitleWindow); - DYNLISTENER(setGeometryX11U); - DYNLISTENER(fullscreenWindow); - DYNLISTENER(requestMove); - DYNLISTENER(requestMinimize); - DYNLISTENER(requestMaximize); - DYNLISTENER(requestResize); - DYNLISTENER(activateX11); - DYNLISTENER(configureX11); - DYNLISTENER(toplevelClose); - DYNLISTENER(toplevelActivate); - DYNLISTENER(toplevelFullscreen); - DYNLISTENER(setOverrideRedirect); - DYNLISTENER(associateX11); - DYNLISTENER(dissociateX11); - DYNLISTENER(ackConfigure); - // DYNLISTENER(newSubsurfaceWindow); - CWLSurface m_pWLSurface; struct { CSignal destroy; } events; - union { - wlr_xwayland_surface* xwayland; - } m_uSurface; WP m_pXDGSurface; + WP m_pXWaylandSurface; // this is the position and size of the "bounding box" Vector2D m_vPosition = Vector2D(0, 0); @@ -391,7 +367,7 @@ class CWindow { // For the list lookup bool operator==(const CWindow& rhs) { - return m_pXDGSurface == rhs.m_pXDGSurface && m_uSurface.xwayland == rhs.m_uSurface.xwayland && m_vPosition == rhs.m_vPosition && m_vSize == rhs.m_vSize && + return m_pXDGSurface == rhs.m_pXDGSurface && m_pXWaylandSurface == rhs.m_pXWaylandSurface && m_vPosition == rhs.m_vPosition && m_vSize == rhs.m_vSize && m_bFadingOut == rhs.m_bFadingOut; } @@ -459,6 +435,8 @@ class CWindow { void onWorkspaceAnimUpdate(); void onUpdateState(); void onUpdateMeta(); + void onX11Configure(CBox box); + void onResourceChangeX11(); std::string fetchTitle(); std::string fetchClass(); @@ -478,8 +456,12 @@ class CWindow { CHyprSignalListener unmap; CHyprSignalListener commit; CHyprSignalListener destroy; + CHyprSignalListener activate; + CHyprSignalListener configure; + CHyprSignalListener setGeometry; CHyprSignalListener updateState; CHyprSignalListener updateMetadata; + CHyprSignalListener resourceChange; } listeners; private: diff --git a/src/events/Events.hpp b/src/events/Events.hpp index 0b757842..f8eb9d2f 100644 --- a/src/events/Events.hpp +++ b/src/events/Events.hpp @@ -33,8 +33,6 @@ namespace Events { DYNLISTENFUNC(requestMinimize); DYNLISTENFUNC(requestMaximize); DYNLISTENFUNC(setOverrideRedirect); - DYNLISTENFUNC(associateX11); - DYNLISTENFUNC(dissociateX11); DYNLISTENFUNC(ackConfigure); LISTENER(newInput); @@ -56,7 +54,6 @@ namespace Events { DYNLISTENFUNC(monitorBind); // XWayland - LISTENER(readyXWayland); LISTENER(surfaceXWayland); // Renderer destroy diff --git a/src/events/Misc.cpp b/src/events/Misc.cpp index 7152730e..6580d93e 100644 --- a/src/events/Misc.cpp +++ b/src/events/Misc.cpp @@ -25,50 +25,6 @@ void Events::listener_leaseRequest(wl_listener* listener, void* data) { } } -void Events::listener_readyXWayland(wl_listener* listener, void* data) { -#ifndef NO_XWAYLAND - const auto XCBCONNECTION = xcb_connect(g_pXWaylandManager->m_sWLRXWayland->display_name, NULL); - const auto ERR = xcb_connection_has_error(XCBCONNECTION); - if (ERR) { - Debug::log(LogLevel::ERR, "XWayland -> xcb_connection_has_error failed with {}", ERR); - return; - } - - for (auto& ATOM : HYPRATOMS) { - xcb_intern_atom_cookie_t cookie = xcb_intern_atom(XCBCONNECTION, 0, ATOM.first.length(), ATOM.first.c_str()); - xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(XCBCONNECTION, cookie, NULL); - - if (!reply) { - Debug::log(LogLevel::ERR, "XWayland -> Atom failed: {}", ATOM.first); - continue; - } - - ATOM.second = reply->atom; - - free(reply); - } - - //wlr_xwayland_set_seat(g_pXWaylandManager->m_sWLRXWayland, g_pCompositor->m_sSeat.seat); - - g_pCursorManager->setXWaylandCursor(g_pXWaylandManager->m_sWLRXWayland); - - const auto ROOT = xcb_setup_roots_iterator(xcb_get_setup(XCBCONNECTION)).data->root; - auto cookie = xcb_get_property(XCBCONNECTION, 0, ROOT, HYPRATOMS["_NET_SUPPORTING_WM_CHECK"], XCB_ATOM_ANY, 0, 2048); - auto reply = xcb_get_property_reply(XCBCONNECTION, cookie, nullptr); - - const auto XWMWINDOW = *(xcb_window_t*)xcb_get_property_value(reply); - const char* name = "Hyprland"; - - xcb_change_property(wlr_xwayland_get_xwm_connection(g_pXWaylandManager->m_sWLRXWayland), XCB_PROP_MODE_REPLACE, XWMWINDOW, HYPRATOMS["_NET_WM_NAME"], HYPRATOMS["UTF8_STRING"], - 8, // format - strlen(name), name); - - free(reply); - - xcb_disconnect(XCBCONNECTION); -#endif -} - void Events::listener_RendererDestroy(wl_listener* listener, void* data) { Debug::log(LOG, "!!Renderer destroyed!!"); } diff --git a/src/events/Windows.cpp b/src/events/Windows.cpp index 384ea9ed..9044bc61 100644 --- a/src/events/Windows.cpp +++ b/src/events/Windows.cpp @@ -9,6 +9,7 @@ #include "../config/ConfigValue.hpp" #include "../protocols/LayerShell.hpp" #include "../protocols/XDGShell.hpp" +#include "../xwayland/XSurface.hpp" // ------------------------------------------------------------ // // __ _______ _ _ _____ ______ _______ // @@ -20,19 +21,6 @@ // // // ------------------------------------------------------------ // -void addViewCoords(void* pWindow, int* x, int* y) { - const auto PWINDOW = (CWindow*)pWindow; - *x += PWINDOW->m_vRealPosition.goal().x; - *y += PWINDOW->m_vRealPosition.goal().y; - - if (!PWINDOW->m_bIsX11 && PWINDOW->m_bIsMapped) { - Vector2D pos = PWINDOW->m_pXDGSurface->current.geometry.pos(); - - *x -= pos.x; - *y -= pos.y; - } -} - void setAnimToMove(void* data) { auto* const PANIMCFG = g_pConfigManager->getAnimationPropertyConfig("windowsMove"); @@ -68,7 +56,6 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->m_bReadyToDelete = false; PWINDOW->m_bFadingOut = false; PWINDOW->m_szTitle = PWINDOW->fetchTitle(); - PWINDOW->m_iX11Type = PWINDOW->m_bIsX11 ? (PWINDOW->m_uSurface.xwayland->override_redirect ? 2 : 1) : 1; PWINDOW->m_bFirstMap = true; PWINDOW->m_szInitialTitle = PWINDOW->m_szTitle; PWINDOW->m_szInitialClass = PWINDOW->fetchClass(); @@ -129,8 +116,7 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->m_bRequestsFloat = true; } - PWINDOW->m_bX11ShouldntFocus = - PWINDOW->m_bX11ShouldntFocus || (PWINDOW->m_bIsX11 && PWINDOW->m_iX11Type == 2 && !wlr_xwayland_or_surface_wants_focus(PWINDOW->m_uSurface.xwayland)); + PWINDOW->m_bX11ShouldntFocus = PWINDOW->m_bX11ShouldntFocus || (PWINDOW->m_bIsX11 && PWINDOW->m_iX11Type == 2 && !PWINDOW->m_pXWaylandSurface->wantsFocus()); if (PWORKSPACE->m_bDefaultFloating) PWINDOW->m_bIsFloating = true; @@ -144,7 +130,7 @@ void Events::listener_mapWindow(void* owner, void* data) { // window rules PWINDOW->m_vMatchedRules = g_pConfigManager->getMatchingRules(PWINDOW, false); - bool requestsFullscreen = PWINDOW->m_bWantsInitialFullscreen || (PWINDOW->m_bIsX11 && PWINDOW->m_uSurface.xwayland->fullscreen); + bool requestsFullscreen = PWINDOW->m_bWantsInitialFullscreen || (PWINDOW->m_bIsX11 && PWINDOW->m_pXWaylandSurface->fullscreen); bool requestsFakeFullscreen = false; bool requestsMaximize = false; bool overridingNoFullscreen = false; @@ -492,9 +478,8 @@ void Events::listener_mapWindow(void* owner, void* data) { } if (!PWINDOW->m_sAdditionalConfigData.noFocus && !PWINDOW->m_bNoInitialFocus && - (PWINDOW->m_iX11Type != 2 || - (PWINDOW->m_bIsX11 && PWINDOW->m_uSurface.xwayland->window_type_len > 0 && wlr_xwayland_or_surface_wants_focus(PWINDOW->m_uSurface.xwayland))) && - !workspaceSilent && (!PFORCEFOCUS || PFORCEFOCUS == PWINDOW) && !g_pInputManager->isConstrained()) { + (PWINDOW->m_iX11Type != 2 || (PWINDOW->m_bIsX11 && PWINDOW->m_pXWaylandSurface->wantsFocus())) && !workspaceSilent && (!PFORCEFOCUS || PFORCEFOCUS == PWINDOW) && + !g_pInputManager->isConstrained()) { g_pCompositor->focusWindow(PWINDOW); PWINDOW->m_fActiveInactiveAlpha.setValueAndWarp(*PACTIVEALPHA); PWINDOW->m_fDimPercent.setValueAndWarp(PWINDOW->m_sAdditionalConfigData.forceNoDim ? 0.f : *PDIMSTRENGTH); @@ -503,22 +488,6 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->m_fDimPercent.setValueAndWarp(0); } - if (PWINDOW->m_bIsX11) { - PWINDOW->hyprListener_fullscreenWindow.initCallback(&PWINDOW->m_uSurface.xwayland->events.request_fullscreen, &Events::listener_fullscreenWindow, PWINDOW.get(), - "XWayland Window Late"); - PWINDOW->hyprListener_activateX11.initCallback(&PWINDOW->m_uSurface.xwayland->events.request_activate, &Events::listener_activateX11, PWINDOW.get(), - "XWayland Window Late"); - PWINDOW->hyprListener_setTitleWindow.initCallback(&PWINDOW->m_uSurface.xwayland->events.set_title, &Events::listener_setTitleWindow, PWINDOW.get(), "XWayland Window Late"); - PWINDOW->hyprListener_requestMinimize.initCallback(&PWINDOW->m_uSurface.xwayland->events.request_minimize, &Events::listener_requestMinimize, PWINDOW.get(), - "Xwayland Window Late"); - PWINDOW->hyprListener_requestMaximize.initCallback(&PWINDOW->m_uSurface.xwayland->events.request_maximize, &Events::listener_requestMaximize, PWINDOW.get(), - "Xwayland Window Late"); - - if (PWINDOW->m_iX11Type == 2) - PWINDOW->hyprListener_setGeometryX11U.initCallback(&PWINDOW->m_uSurface.xwayland->events.set_geometry, &Events::listener_unmanagedSetGeometry, PWINDOW.get(), - "XWayland Window Late"); - } - if ((requestsFullscreen && (!(PWINDOW->m_eSuppressedEvents & SUPPRESS_FULLSCREEN) || overridingNoFullscreen)) || (requestsMaximize && (!(PWINDOW->m_eSuppressedEvents & SUPPRESS_MAXIMIZE) || overridingNoMaximize)) || requestsFakeFullscreen) { // fix fullscreen on requested (basically do a switcheroo) @@ -687,16 +656,6 @@ void Events::listener_unmapWindow(void* owner, void* data) { g_pProtocolManager->m_pToplevelExportProtocolManager->onWindowUnmap(PWINDOW); - if (!PWINDOW->m_bIsX11) { - Debug::log(LOG, "Unregistered late callbacks XWL"); - PWINDOW->hyprListener_fullscreenWindow.removeCallback(); - PWINDOW->hyprListener_activateX11.removeCallback(); - PWINDOW->hyprListener_setTitleWindow.removeCallback(); - PWINDOW->hyprListener_setGeometryX11U.removeCallback(); - PWINDOW->hyprListener_requestMaximize.removeCallback(); - PWINDOW->hyprListener_requestMinimize.removeCallback(); - } - if (PWINDOW->m_bIsFullscreen) g_pCompositor->setWindowFullscreen(PWINDOW, false, FULLSCREEN_FULL); @@ -859,9 +818,6 @@ void Events::listener_destroyWindow(void* owner, void* data) { Debug::log(LOG, "{:c} destroyed, queueing.", PWINDOW); - if (PWINDOW->m_bIsX11) - Debug::log(LOG, "XWayland class raw: {}", PWINDOW->m_uSurface.xwayland->_class ? PWINDOW->m_uSurface.xwayland->_class : "null"); - if (PWINDOW == g_pCompositor->m_pLastWindow.lock()) { g_pCompositor->m_pLastWindow.reset(); g_pCompositor->m_pLastFocus = nullptr; @@ -869,15 +825,6 @@ void Events::listener_destroyWindow(void* owner, void* data) { PWINDOW->m_pWLSurface.unassign(); - PWINDOW->hyprListener_commitWindow.removeCallback(); - PWINDOW->hyprListener_mapWindow.removeCallback(); - PWINDOW->hyprListener_unmapWindow.removeCallback(); - PWINDOW->hyprListener_destroyWindow.removeCallback(); - PWINDOW->hyprListener_configureX11.removeCallback(); - PWINDOW->hyprListener_setOverrideRedirect.removeCallback(); - PWINDOW->hyprListener_associateX11.removeCallback(); - PWINDOW->hyprListener_dissociateX11.removeCallback(); - PWINDOW->listeners = {}; g_pLayoutManager->getCurrentLayout()->onWindowRemoved(PWINDOW); @@ -901,39 +848,6 @@ void Events::listener_setTitleWindow(void* owner, void* data) { PWINDOW->onUpdateMeta(); } -void Events::listener_fullscreenWindow(void* owner, void* data) { - PHLWINDOW PWINDOW = ((CWindow*)owner)->m_pSelf.lock(); - - // x11 only - - if (!PWINDOW->m_bIsMapped) { - PWINDOW->m_bWantsInitialFullscreen = true; - return; - } - - if (PWINDOW->isHidden() || (PWINDOW->m_eSuppressedEvents & SUPPRESS_FULLSCREEN)) - return; - - bool requestedFullState = false; - - if (!PWINDOW->m_uSurface.xwayland->surface->mapped) - return; - - if (!PWINDOW->m_bFakeFullscreenState) - g_pCompositor->setWindowFullscreen(PWINDOW, PWINDOW->m_uSurface.xwayland->fullscreen, FULLSCREEN_FULL); - - requestedFullState = PWINDOW->m_uSurface.xwayland->fullscreen; - - if (!requestedFullState && PWINDOW->m_bFakeFullscreenState) { - g_pXWaylandManager->setWindowFullscreen(PWINDOW, false); // fixes for apps expecting a de-fullscreen (e.g. ff) - g_pXWaylandManager->setWindowFullscreen(PWINDOW, true); - } - - PWINDOW->updateToplevel(); - - Debug::log(LOG, "{} fullscreen to {}", PWINDOW, PWINDOW->m_bIsFullscreen); -} - void Events::listener_activateX11(void* owner, void* data) { PHLWINDOW PWINDOW = ((CWindow*)owner)->m_pSelf.lock(); @@ -946,7 +860,7 @@ void Events::listener_activateX11(void* owner, void* data) { if (g_pCompositor->m_pLastWindow.lock() && g_pCompositor->m_pLastWindow->getPID() != PWINDOW->getPID()) return; - if (!wlr_xwayland_or_surface_wants_focus(PWINDOW->m_uSurface.xwayland)) + if (!PWINDOW->m_pXWaylandSurface->wantsFocus()) return; g_pCompositor->focusWindow(PWINDOW); @@ -959,82 +873,16 @@ void Events::listener_activateX11(void* owner, void* data) { PWINDOW->activate(); } -void Events::listener_configureX11(void* owner, void* data) { - PHLWINDOW PWINDOW = ((CWindow*)owner)->m_pSelf.lock(); - - const auto E = (wlr_xwayland_surface_configure_event*)data; - - if (!PWINDOW->m_uSurface.xwayland->surface || !PWINDOW->m_uSurface.xwayland->surface->mapped || !PWINDOW->m_bIsMapped) { - wlr_xwayland_surface_configure(PWINDOW->m_uSurface.xwayland, E->x, E->y, E->width, E->height); - PWINDOW->m_vPendingReportedSize = {E->width, E->height}; - PWINDOW->m_vReportedSize = {E->width, E->height}; - if (const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); PMONITOR) - PWINDOW->m_fX11SurfaceScaledBy = PMONITOR->scale; - return; - } - - g_pHyprRenderer->damageWindow(PWINDOW); - - if (!PWINDOW->m_bIsFloating || PWINDOW->m_bIsFullscreen || g_pInputManager->currentlyDraggedWindow.lock() == PWINDOW) { - g_pXWaylandManager->setWindowSize(PWINDOW, PWINDOW->m_vRealSize.goal(), true); - g_pInputManager->refocus(); - g_pHyprRenderer->damageWindow(PWINDOW); - return; - } - - if (E->width > 1 && E->height > 1) - PWINDOW->setHidden(false); - else - PWINDOW->setHidden(true); - - const auto LOGICALPOS = g_pXWaylandManager->xwaylandToWaylandCoords({E->x, E->y}); - - PWINDOW->m_vRealPosition.setValueAndWarp(LOGICALPOS); - PWINDOW->m_vRealSize.setValueAndWarp(Vector2D(E->width, E->height)); - - static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - if (*PXWLFORCESCALEZERO) { - if (const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); PMONITOR) { - PWINDOW->m_vRealSize.setValueAndWarp(PWINDOW->m_vRealSize.goal() / PMONITOR->scale); - PWINDOW->m_fX11SurfaceScaledBy = PMONITOR->scale; - } - } - - PWINDOW->m_vPosition = PWINDOW->m_vRealPosition.value(); - PWINDOW->m_vSize = PWINDOW->m_vRealSize.value(); - - wlr_xwayland_surface_configure(PWINDOW->m_uSurface.xwayland, E->x, E->y, E->width, E->height); - - PWINDOW->m_vPendingReportedSize = {E->width, E->height}; - PWINDOW->m_vReportedSize = {E->width, E->height}; - - PWINDOW->updateWindowDecos(); - - if (!g_pCompositor->isWorkspaceVisible(PWINDOW->m_pWorkspace)) - return; // further things are only for visible windows - - PWINDOW->m_pWorkspace = g_pCompositor->getMonitorFromVector(PWINDOW->m_vRealPosition.value() + PWINDOW->m_vRealSize.value() / 2.f)->activeWorkspace; - - g_pCompositor->changeWindowZOrder(PWINDOW, true); - - PWINDOW->m_bCreatedOverFullscreen = true; - - if (!PWINDOW->m_sAdditionalConfigData.windowDanceCompat) - g_pInputManager->refocus(); - - g_pHyprRenderer->damageWindow(PWINDOW); -} - void Events::listener_unmanagedSetGeometry(void* owner, void* data) { PHLWINDOW PWINDOW = ((CWindow*)owner)->m_pSelf.lock(); - if (!PWINDOW->m_bIsMapped) + if (!PWINDOW->m_bIsMapped || !PWINDOW->m_pXWaylandSurface || !PWINDOW->m_pXWaylandSurface->overrideRedirect) return; const auto POS = PWINDOW->m_vRealPosition.goal(); const auto SIZ = PWINDOW->m_vRealSize.goal(); - if (PWINDOW->m_uSurface.xwayland->width > 1 && PWINDOW->m_uSurface.xwayland->height > 1) + if (PWINDOW->m_pXWaylandSurface->geometry.size() > Vector2D{1, 1}) PWINDOW->setHidden(false); else PWINDOW->setHidden(true); @@ -1047,18 +895,17 @@ void Events::listener_unmanagedSetGeometry(void* owner, void* data) { static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - const auto LOGICALPOS = g_pXWaylandManager->xwaylandToWaylandCoords({PWINDOW->m_uSurface.xwayland->x, PWINDOW->m_uSurface.xwayland->y}); + const auto LOGICALPOS = g_pXWaylandManager->xwaylandToWaylandCoords(PWINDOW->m_pXWaylandSurface->geometry.pos()); - if (abs(std::floor(POS.x) - LOGICALPOS.x) > 2 || abs(std::floor(POS.y) - LOGICALPOS.y) > 2 || abs(std::floor(SIZ.x) - PWINDOW->m_uSurface.xwayland->width) > 2 || - abs(std::floor(SIZ.y) - PWINDOW->m_uSurface.xwayland->height) > 2) { - Debug::log(LOG, "Unmanaged window {} requests geometry update to {:j} {} {}", PWINDOW, LOGICALPOS, (int)PWINDOW->m_uSurface.xwayland->width, - (int)PWINDOW->m_uSurface.xwayland->height); + if (abs(std::floor(POS.x) - LOGICALPOS.x) > 2 || abs(std::floor(POS.y) - LOGICALPOS.y) > 2 || abs(std::floor(SIZ.x) - PWINDOW->m_pXWaylandSurface->geometry.width) > 2 || + abs(std::floor(SIZ.y) - PWINDOW->m_pXWaylandSurface->geometry.height) > 2) { + Debug::log(LOG, "Unmanaged window {} requests geometry update to {:j} {:j}", PWINDOW, LOGICALPOS, PWINDOW->m_pXWaylandSurface->geometry.size()); g_pHyprRenderer->damageWindow(PWINDOW); PWINDOW->m_vRealPosition.setValueAndWarp(Vector2D(LOGICALPOS.x, LOGICALPOS.y)); - if (abs(std::floor(SIZ.x) - PWINDOW->m_uSurface.xwayland->width) > 2 || abs(std::floor(SIZ.y) - PWINDOW->m_uSurface.xwayland->height) > 2) - PWINDOW->m_vRealSize.setValueAndWarp(Vector2D(PWINDOW->m_uSurface.xwayland->width, PWINDOW->m_uSurface.xwayland->height)); + if (abs(std::floor(SIZ.x) - PWINDOW->m_pXWaylandSurface->geometry.w) > 2 || abs(std::floor(SIZ.y) - PWINDOW->m_pXWaylandSurface->geometry.h) > 2) + PWINDOW->m_vRealSize.setValueAndWarp(PWINDOW->m_pXWaylandSurface->geometry.size()); if (*PXWLFORCESCALEZERO) { if (const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); PMONITOR) { @@ -1079,92 +926,3 @@ void Events::listener_unmanagedSetGeometry(void* owner, void* data) { PWINDOW->m_vPendingReportedSize = PWINDOW->m_vRealSize.goal(); } } - -void Events::listener_setOverrideRedirect(void* owner, void* data) { - // const auto PWINDOW = (CWindow*)owner; - - //if (!PWINDOW->m_bIsMapped && PWINDOW->m_uSurface.xwayland->mapped) { - // Events::listener_mapWindow(PWINDOW, nullptr); - //} -} - -void Events::listener_associateX11(void* owner, void* data) { - PHLWINDOW PWINDOW = ((CWindow*)owner)->m_pSelf.lock(); - - PWINDOW->hyprListener_mapWindow.initCallback(&PWINDOW->m_uSurface.xwayland->surface->events.map, &Events::listener_mapWindow, PWINDOW.get(), "XWayland Window"); - PWINDOW->hyprListener_commitWindow.initCallback(&PWINDOW->m_uSurface.xwayland->surface->events.commit, &Events::listener_commitWindow, PWINDOW.get(), "XWayland Window"); - - PWINDOW->m_pWLSurface.assign(PWINDOW->m_uSurface.xwayland->surface, PWINDOW); -} - -void Events::listener_dissociateX11(void* owner, void* data) { - PHLWINDOW PWINDOW = ((CWindow*)owner)->m_pSelf.lock(); - - PWINDOW->m_pWLSurface.unassign(); - - PWINDOW->hyprListener_mapWindow.removeCallback(); - PWINDOW->hyprListener_commitWindow.removeCallback(); -} - -void Events::listener_surfaceXWayland(wl_listener* listener, void* data) { - const auto XWSURFACE = (wlr_xwayland_surface*)data; - - Debug::log(LOG, "New XWayland Surface created (class {}).", XWSURFACE->_class ? XWSURFACE->_class : "null"); - if (XWSURFACE->parent) - Debug::log(LOG, "Window parent data: {} at {:x}", XWSURFACE->parent->_class ? XWSURFACE->parent->_class : "null", (uintptr_t)XWSURFACE->parent); - - const auto PNEWWINDOW = g_pCompositor->m_vWindows.emplace_back(CWindow::create()); - - PNEWWINDOW->m_uSurface.xwayland = XWSURFACE; - PNEWWINDOW->m_iX11Type = XWSURFACE->override_redirect ? 2 : 1; - PNEWWINDOW->m_bIsX11 = true; - - PNEWWINDOW->m_pX11Parent = g_pCompositor->getX11Parent(PNEWWINDOW); - - PNEWWINDOW->hyprListener_associateX11.initCallback(&XWSURFACE->events.associate, &Events::listener_associateX11, PNEWWINDOW.get(), "XWayland Window"); - PNEWWINDOW->hyprListener_dissociateX11.initCallback(&XWSURFACE->events.dissociate, &Events::listener_dissociateX11, PNEWWINDOW.get(), "XWayland Window"); - PNEWWINDOW->hyprListener_destroyWindow.initCallback(&XWSURFACE->events.destroy, &Events::listener_destroyWindow, PNEWWINDOW.get(), "XWayland Window"); - PNEWWINDOW->hyprListener_setOverrideRedirect.initCallback(&XWSURFACE->events.set_override_redirect, &Events::listener_setOverrideRedirect, PNEWWINDOW.get(), "XWayland Window"); - PNEWWINDOW->hyprListener_configureX11.initCallback(&XWSURFACE->events.request_configure, &Events::listener_configureX11, PNEWWINDOW.get(), "XWayland Window"); -} - -void Events::listener_requestMaximize(void* owner, void* data) { - PHLWINDOW PWINDOW = ((CWindow*)owner)->m_pSelf.lock(); - - if (PWINDOW->m_eSuppressedEvents & SUPPRESS_MAXIMIZE) - return; - - Debug::log(LOG, "Maximize request for {}", PWINDOW); - if (!PWINDOW->m_bIsX11) { - - g_pCompositor->setWindowFullscreen(PWINDOW, !PWINDOW->m_bIsFullscreen, - FULLSCREEN_MAXIMIZED); // this will be rejected if there already is a fullscreen window - - } else { - if (!PWINDOW->m_bIsMapped || PWINDOW->m_iX11Type != 1) - return; - - g_pCompositor->setWindowFullscreen(PWINDOW, !PWINDOW->m_bIsFullscreen, FULLSCREEN_MAXIMIZED); - } -} - -void Events::listener_requestMinimize(void* owner, void* data) { - PHLWINDOW PWINDOW = ((CWindow*)owner)->m_pSelf.lock(); - - Debug::log(LOG, "Minimize request for {}", PWINDOW); - - if (PWINDOW->m_bIsX11) { - if (!PWINDOW->m_bIsMapped || PWINDOW->m_iX11Type != 1) - return; - - const auto E = (wlr_xwayland_minimize_event*)data; - - g_pEventManager->postEvent({"minimize", std::format("{:x},{}", (uintptr_t)PWINDOW.get(), (int)E->minimize)}); - EMIT_HOOK_EVENT("minimize", (std::vector{PWINDOW, E->minimize})); - - wlr_xwayland_surface_set_minimized(PWINDOW->m_uSurface.xwayland, E->minimize && g_pCompositor->m_pLastWindow.lock() != PWINDOW); // fucking DXVK - } else { - g_pEventManager->postEvent({"minimize", std::format("{:x},{}", (uintptr_t)PWINDOW.get(), 1)}); - EMIT_HOOK_EVENT("minimize", (std::vector{PWINDOW, (int64_t)(1)})); - } -} diff --git a/src/helpers/XWaylandStubs.hpp b/src/helpers/XWaylandStubs.hpp deleted file mode 100644 index c21041cd..00000000 --- a/src/helpers/XWaylandStubs.hpp +++ /dev/null @@ -1,172 +0,0 @@ -#pragma once - -#include - -typedef unsigned int xcb_atom_t; -struct xcb_icccm_wm_hints_t; -typedef struct { - /** User specified flags */ - uint32_t flags; - /** User-specified position */ - int32_t x, y; - /** User-specified size */ - int32_t width, height; - /** Program-specified minimum size */ - int32_t min_width, min_height; - /** Program-specified maximum size */ - int32_t max_width, max_height; - /** Program-specified resize increments */ - int32_t width_inc, height_inc; - /** Program-specified minimum aspect ratios */ - int32_t min_aspect_num, min_aspect_den; - /** Program-specified maximum aspect ratios */ - int32_t max_aspect_num, max_aspect_den; - /** Program-specified base size */ - int32_t base_width, base_height; - /** Program-specified window gravity */ - uint32_t win_gravity; -} xcb_size_hints_t; -typedef unsigned int xcb_window_t; - -typedef enum xcb_stack_mode_t { - XCB_STACK_MODE_ABOVE = 0, - XCB_STACK_MODE_BELOW = 1, - XCB_STACK_MODE_TOP_IF = 2, - XCB_STACK_MODE_BOTTOM_IF = 3, - XCB_STACK_MODE_OPPOSITE = 4 -} xcb_stack_mode_t; - -struct wlr_xwayland { - struct wlr_xwayland_server* server; - struct wlr_xwm* xwm; - struct wlr_xwayland_cursor* cursor; - - const char* display_name; - - struct wl_display* wl_display; - struct wlr_compositor* compositor; - struct wlr_seat* seat; - - void* data; -}; - -struct wlr_xwayland_surface { - xcb_window_t window_id; - struct wlr_xwm* xwm; - uint32_t surface_id; - - struct wl_list link; - struct wl_list stack_link; - struct wl_list unpaired_link; - - struct wlr_surface* surface; - int16_t x, y; - uint16_t width, height; - uint16_t saved_width, saved_height; - bool override_redirect; - bool mapped; - - char* title; - char* _class; - char* instance; - char* role; - char* startup_id; - pid_t pid; - bool has_utf8_title; - - struct wl_list children; // wlr_xwayland_surface::parent_link - struct wlr_xwayland_surface* parent; - struct wl_list parent_link; // wlr_xwayland_surface::children - - xcb_atom_t* window_type; - size_t window_type_len; - - xcb_atom_t* protocols; - size_t protocols_len; - - uint32_t decorations; - xcb_icccm_wm_hints_t* hints; - xcb_size_hints_t* size_hints; - - bool pinging; - struct wl_event_source* ping_timer; - - // _NET_WM_STATE - bool modal; - bool fullscreen; - bool maximized_vert, maximized_horz; - bool minimized; - - bool has_alpha; - - struct { - struct wl_signal destroy; - struct wl_signal request_configure; - struct wl_signal request_move; - struct wl_signal request_resize; - struct wl_signal request_minimize; - struct wl_signal request_maximize; - struct wl_signal request_fullscreen; - struct wl_signal request_activate; - - struct wl_signal map; - struct wl_signal unmap; - struct wl_signal associate; - struct wl_signal dissociate; - struct wl_signal set_title; - struct wl_signal set_class; - struct wl_signal set_role; - struct wl_signal set_parent; - struct wl_signal set_startup_id; - struct wl_signal set_window_type; - struct wl_signal set_hints; - struct wl_signal set_decorations; - struct wl_signal set_override_redirect; - struct wl_signal set_geometry; - struct wl_signal ping_timeout; - } events; -}; - -struct wlr_xwayland_surface_configure_event { - struct wlr_xwayland_surface* surface; - int16_t x, y; - uint16_t width, height; - uint16_t mask; // xcb_config_window_t -}; - -struct wlr_xwayland_minimize_event { - struct wlr_xwayland_surface* surface; - bool minimize; -}; - -inline void wlr_xwayland_destroy(wlr_xwayland*) {} - -inline void wlr_xwayland_surface_configure(wlr_xwayland_surface*, int, int, int, int) {} - -inline bool wlr_surface_is_xwayland_surface(void*) { - return false; -} - -inline void wlr_xwayland_surface_activate(wlr_xwayland_surface*, bool) {} - -inline void wlr_xwayland_surface_restack(wlr_xwayland_surface*, void*, xcb_stack_mode_t) {} - -inline wlr_xwayland_surface* wlr_xwayland_surface_from_wlr_surface(void*) { - return nullptr; -} - -inline void wlr_xwayland_surface_close(wlr_xwayland_surface*) {} - -inline void wlr_xwayland_surface_set_fullscreen(wlr_xwayland_surface*, bool) {} - -inline void wlr_xwayland_surface_set_minimized(wlr_xwayland_surface*, bool) {} - -inline wlr_xwayland_surface* wlr_xwayland_surface_try_from_wlr_surface(wlr_surface*) { - return nullptr; -} - -inline bool wlr_xwayland_or_surface_wants_focus(const wlr_xwayland_surface*) { - return false; -} - -inline void wlr_xwayland_set_cursor(wlr_xwayland* wlr_xwayland, uint8_t* pixels, uint32_t stride, uint32_t width, uint32_t height, int32_t hotspot_x, int32_t hotspot_y) {} \ No newline at end of file diff --git a/src/includes.hpp b/src/includes.hpp index dfbe0221..dbae7635 100644 --- a/src/includes.hpp +++ b/src/includes.hpp @@ -82,10 +82,6 @@ extern "C" { #if WLR_HAS_X11_BACKEND #include #endif - -#ifndef NO_XWAYLAND -#include -#endif } #undef delete @@ -110,7 +106,6 @@ extern "C" { #ifdef NO_XWAYLAND #define XWAYLAND false -#include "helpers/XWaylandStubs.hpp" #else #define XWAYLAND true #endif diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp index ffd9ddea..108f9039 100644 --- a/src/layout/IHyprLayout.cpp +++ b/src/layout/IHyprLayout.cpp @@ -5,6 +5,7 @@ #include "../config/ConfigValue.hpp" #include "../desktop/Window.hpp" #include "../protocols/XDGShell.hpp" +#include "../xwayland/XSurface.hpp" void IHyprLayout::onWindowCreated(PHLWINDOW pWindow, eDirection direction) { if (pWindow->m_bIsFloating) { @@ -111,10 +112,10 @@ void IHyprLayout::onWindowCreatedFloating(PHLWINDOW pWindow) { if (pWindow->m_vRealSize.goal().x <= 5 || pWindow->m_vRealSize.goal().y <= 5) pWindow->m_vRealSize = PMONITOR->vecSize / 2.f; - if (pWindow->m_bIsX11 && pWindow->m_uSurface.xwayland->override_redirect) { + if (pWindow->m_bIsX11 && pWindow->m_iX11Type == 2) { - if (pWindow->m_uSurface.xwayland->x != 0 && pWindow->m_uSurface.xwayland->y != 0) - pWindow->m_vRealPosition = g_pXWaylandManager->xwaylandToWaylandCoords({pWindow->m_uSurface.xwayland->x, pWindow->m_uSurface.xwayland->y}); + if (pWindow->m_pXWaylandSurface->geometry.x != 0 && pWindow->m_pXWaylandSurface->geometry.y != 0) + pWindow->m_vRealPosition = g_pXWaylandManager->xwaylandToWaylandCoords(pWindow->m_pXWaylandSurface->geometry.pos()); else pWindow->m_vRealPosition = Vector2D(PMONITOR->vecPosition.x + (PMONITOR->vecSize.x - pWindow->m_vRealSize.goal().x) / 2.f, PMONITOR->vecPosition.y + (PMONITOR->vecSize.y - pWindow->m_vRealSize.goal().y) / 2.f); @@ -161,7 +162,7 @@ void IHyprLayout::onWindowCreatedFloating(PHLWINDOW pWindow) { if (*PXWLFORCESCALEZERO && pWindow->m_bIsX11) pWindow->m_vRealSize = pWindow->m_vRealSize.goal() / PMONITOR->scale; - if (pWindow->m_bX11DoesntWantBorders || (pWindow->m_bIsX11 && pWindow->m_uSurface.xwayland->override_redirect)) { + if (pWindow->m_bX11DoesntWantBorders || (pWindow->m_bIsX11 && pWindow->m_iX11Type == 2)) { pWindow->m_vRealPosition.warp(); pWindow->m_vRealSize.warp(); } diff --git a/src/managers/CursorManager.cpp b/src/managers/CursorManager.cpp index ea36b9b2..c9783844 100644 --- a/src/managers/CursorManager.cpp +++ b/src/managers/CursorManager.cpp @@ -2,6 +2,7 @@ #include "Compositor.hpp" #include "../config/ConfigValue.hpp" #include "PointerManager.hpp" +#include "../xwayland/XWayland.hpp" extern "C" { #include @@ -246,14 +247,14 @@ SCursorImageData CCursorManager::dataFor(const std::string& name) { return IMAGES.images[0]; } -void CCursorManager::setXWaylandCursor(wlr_xwayland* xwayland) { +void CCursorManager::setXWaylandCursor() { const auto CURSOR = dataFor("left_ptr"); if (CURSOR.surface) { - wlr_xwayland_set_cursor(xwayland, cairo_image_surface_get_data(CURSOR.surface), cairo_image_surface_get_stride(CURSOR.surface), CURSOR.size, CURSOR.size, CURSOR.hotspotX, - CURSOR.hotspotY); + g_pXWayland->setCursor(cairo_image_surface_get_data(CURSOR.surface), cairo_image_surface_get_stride(CURSOR.surface), {CURSOR.size, CURSOR.size}, + {CURSOR.hotspotX, CURSOR.hotspotY}); } else if (const auto XCURSOR = wlr_xcursor_manager_get_xcursor(m_pWLRXCursorMgr, "left_ptr", 1); XCURSOR) { - wlr_xwayland_set_cursor(xwayland, XCURSOR->images[0]->buffer, XCURSOR->images[0]->width * 4, XCURSOR->images[0]->width, XCURSOR->images[0]->height, - XCURSOR->images[0]->hotspot_x, XCURSOR->images[0]->hotspot_y); + g_pXWayland->setCursor(XCURSOR->images[0]->buffer, XCURSOR->images[0]->width * 4, {XCURSOR->images[0]->width, XCURSOR->images[0]->height}, + {XCURSOR->images[0]->hotspot_x, XCURSOR->images[0]->hotspot_y}); } else Debug::log(ERR, "CursorManager: no valid cursor for xwayland"); } diff --git a/src/managers/CursorManager.hpp b/src/managers/CursorManager.hpp index b4c5232a..4ff9adeb 100644 --- a/src/managers/CursorManager.hpp +++ b/src/managers/CursorManager.hpp @@ -8,7 +8,6 @@ struct wlr_buffer; struct wlr_xcursor_manager; -struct wlr_xwayland; class CWLSurface; class CCursorManager { @@ -25,7 +24,7 @@ class CCursorManager { void changeTheme(const std::string& name, const int size); void updateTheme(); SCursorImageData dataFor(const std::string& name); // for xwayland - void setXWaylandCursor(wlr_xwayland* xwayland); + void setXWaylandCursor(); void tickAnimatedCursor(); diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index c43e4c56..feff69e0 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -31,6 +31,7 @@ #include "../protocols/XDGShell.hpp" #include "../protocols/DataDeviceWlr.hpp" #include "../protocols/PrimarySelection.hpp" +#include "../protocols/XWaylandShell.hpp" #include "../protocols/core/Seat.hpp" #include "../protocols/core/DataDevice.hpp" @@ -73,6 +74,7 @@ CProtocolManager::CProtocolManager() { PROTO::xdgShell = std::make_unique(&xdg_wm_base_interface, 6, "XDGShell"); PROTO::dataWlr = std::make_unique(&zwlr_data_control_manager_v1_interface, 2, "DataDeviceWlr"); PROTO::primarySelection = std::make_unique(&zwp_primary_selection_device_manager_v1_interface, 1, "PrimarySelection"); + PROTO::xwaylandShell = std::make_unique(&xwayland_shell_v1_interface, 1, "XWaylandShell"); // Old protocol implementations. // TODO: rewrite them to use hyprwayland-scanner. diff --git a/src/managers/SeatManager.cpp b/src/managers/SeatManager.cpp index bbbe01d4..ce40650d 100644 --- a/src/managers/SeatManager.cpp +++ b/src/managers/SeatManager.cpp @@ -498,6 +498,8 @@ void CSeatManager::setCurrentSelection(SP source) { PROTO::data->setSelection(source); PROTO::dataWlr->setSelection(source, false); } + + events.setSelection.emit(); } void CSeatManager::setCurrentPrimarySelection(SP source) { @@ -521,6 +523,8 @@ void CSeatManager::setCurrentPrimarySelection(SP source) { PROTO::primarySelection->setSelection(source); PROTO::dataWlr->setSelection(source, true); } + + events.setPrimarySelection.emit(); } void CSeatManager::setGrab(SP grab) { diff --git a/src/managers/SeatManager.hpp b/src/managers/SeatManager.hpp index fee3efa9..b88058a8 100644 --- a/src/managers/SeatManager.hpp +++ b/src/managers/SeatManager.hpp @@ -106,6 +106,8 @@ class CSeatManager { CSignal pointerFocusChange; CSignal touchFocusChange; CSignal setCursor; // SSetCursorEvent + CSignal setSelection; + CSignal setPrimarySelection; } events; struct { diff --git a/src/managers/XWaylandManager.cpp b/src/managers/XWaylandManager.cpp index 390c6ece..4aebefcb 100644 --- a/src/managers/XWaylandManager.cpp +++ b/src/managers/XWaylandManager.cpp @@ -3,29 +3,14 @@ #include "../events/Events.hpp" #include "../config/ConfigValue.hpp" #include "../protocols/XDGShell.hpp" +#include "../xwayland/XWayland.hpp" #define OUTPUT_MANAGER_VERSION 3 #define OUTPUT_DONE_DEPRECATED_SINCE_VERSION 3 #define OUTPUT_DESCRIPTION_MUTABLE_SINCE_VERSION 3 CHyprXWaylandManager::CHyprXWaylandManager() { -#ifndef NO_XWAYLAND - m_sWLRXWayland = wlr_xwayland_create(g_pCompositor->m_sWLDisplay, g_pCompositor->m_sWLRCompositor, 1); - - if (!m_sWLRXWayland) { - Debug::log(ERR, "Couldn't start up the XWaylandManager because wlr_xwayland_create returned a nullptr!"); - return; - } - - addWLSignal(&m_sWLRXWayland->events.ready, &Events::listen_readyXWayland, m_sWLRXWayland, "XWayland Manager"); - addWLSignal(&m_sWLRXWayland->events.new_surface, &Events::listen_surfaceXWayland, m_sWLRXWayland, "XWayland Manager"); - - setenv("DISPLAY", m_sWLRXWayland->display_name, 1); - - Debug::log(LOG, "CHyprXWaylandManager started on display {}", m_sWLRXWayland->display_name); -#else - unsetenv("DISPLAY"); // unset DISPLAY so that X11 apps do not try to start on a different/invalid DISPLAY -#endif + ; } CHyprXWaylandManager::~CHyprXWaylandManager() { @@ -42,24 +27,23 @@ void CHyprXWaylandManager::activateSurface(wlr_surface* pSurface, bool activate) if (!pSurface) return; - if (wlr_xwayland_surface_try_from_wlr_surface(pSurface)) { - const auto XSURF = wlr_xwayland_surface_try_from_wlr_surface(pSurface); - wlr_xwayland_surface_activate(XSURF, activate); - - if (activate && !XSURF->override_redirect) - wlr_xwayland_surface_restack(XSURF, nullptr, XCB_STACK_MODE_ABOVE); - } - // TODO: // this cannot be nicely done until we rewrite wlr_surface for (auto& w : g_pCompositor->m_vWindows) { - if (w->m_bIsX11 || !w->m_bIsMapped) + if (!w->m_bIsMapped) continue; if (w->m_pWLSurface.wlr() != pSurface) continue; - w->m_pXDGSurface->toplevel->setActive(activate); + if (w->m_bIsX11) { + if (activate) { + w->m_pXWaylandSurface->setMinimized(false); + w->m_pXWaylandSurface->restackToTop(); + } + w->m_pXWaylandSurface->activate(activate); + } else + w->m_pXDGSurface->toplevel->setActive(activate); } } @@ -68,12 +52,12 @@ void CHyprXWaylandManager::activateWindow(PHLWINDOW pWindow, bool activate) { setWindowSize(pWindow, pWindow->m_vRealSize.value()); // update xwayland output pos if (activate) { - wlr_xwayland_surface_set_minimized(pWindow->m_uSurface.xwayland, false); - if (!pWindow->m_uSurface.xwayland->override_redirect) - wlr_xwayland_surface_restack(pWindow->m_uSurface.xwayland, nullptr, XCB_STACK_MODE_ABOVE); + pWindow->m_pXWaylandSurface->setMinimized(false); + if (pWindow->m_iX11Type != 2) + pWindow->m_pXWaylandSurface->restackToTop(); } - wlr_xwayland_surface_activate(pWindow->m_uSurface.xwayland, activate); + pWindow->m_pXWaylandSurface->activate(activate); } else if (pWindow->m_pXDGSurface && pWindow->m_pXDGSurface->toplevel) pWindow->m_pXDGSurface->toplevel->setActive(activate); @@ -88,7 +72,7 @@ void CHyprXWaylandManager::activateWindow(PHLWINDOW pWindow, bool activate) { void CHyprXWaylandManager::getGeometryForWindow(PHLWINDOW pWindow, CBox* pbox) { if (pWindow->m_bIsX11) { - const auto SIZEHINTS = pWindow->m_uSurface.xwayland->size_hints; + const auto SIZEHINTS = pWindow->m_pXWaylandSurface->sizeHints.get(); if (SIZEHINTS && pWindow->m_iX11Type != 2) { pbox->x = SIZEHINTS->x; @@ -96,10 +80,7 @@ void CHyprXWaylandManager::getGeometryForWindow(PHLWINDOW pWindow, CBox* pbox) { pbox->width = SIZEHINTS->width; pbox->height = SIZEHINTS->height; } else { - pbox->x = pWindow->m_uSurface.xwayland->x; - pbox->y = pWindow->m_uSurface.xwayland->y; - pbox->width = pWindow->m_uSurface.xwayland->width; - pbox->height = pWindow->m_uSurface.xwayland->height; + *pbox = pWindow->m_pXWaylandSurface->geometry; } } else if (pWindow->m_pXDGSurface) *pbox = pWindow->m_pXDGSurface->current.geometry; @@ -107,7 +88,7 @@ void CHyprXWaylandManager::getGeometryForWindow(PHLWINDOW pWindow, CBox* pbox) { void CHyprXWaylandManager::sendCloseWindow(PHLWINDOW pWindow) { if (pWindow->m_bIsX11) - wlr_xwayland_surface_close(pWindow->m_uSurface.xwayland); + pWindow->m_pXWaylandSurface->close(); else if (pWindow->m_pXDGSurface && pWindow->m_pXDGSurface->toplevel) pWindow->m_pXDGSurface->toplevel->close(); } @@ -145,7 +126,7 @@ void CHyprXWaylandManager::setWindowSize(PHLWINDOW pWindow, Vector2D size, bool } if (pWindow->m_bIsX11) - wlr_xwayland_surface_configure(pWindow->m_uSurface.xwayland, windowPos.x, windowPos.y, size.x, size.y); + pWindow->m_pXWaylandSurface->configure({windowPos, size}); else if (pWindow->m_pXDGSurface->toplevel) pWindow->m_vPendingSizeAcks.push_back(std::make_pair<>(pWindow->m_pXDGSurface->toplevel->setSize(size), size.floor())); } @@ -156,45 +137,36 @@ wlr_surface* CHyprXWaylandManager::surfaceAt(PHLWINDOW pWindow, const Vector2D& bool CHyprXWaylandManager::shouldBeFloated(PHLWINDOW pWindow, bool pending) { if (pWindow->m_bIsX11) { - for (size_t i = 0; i < pWindow->m_uSurface.xwayland->window_type_len; i++) - if (pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_DIALOG"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_SPLASH"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_TOOLBAR"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_UTILITY"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_TOOLTIP"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_POPUP_MENU"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_DOCK"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_MENU"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"]) { + for (auto& a : pWindow->m_pXWaylandSurface->atoms) + if (a == HYPRATOMS["_NET_WM_WINDOW_TYPE_DIALOG"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_SPLASH"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_TOOLBAR"] || + a == HYPRATOMS["_NET_WM_WINDOW_TYPE_UTILITY"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_TOOLTIP"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_POPUP_MENU"] || + a == HYPRATOMS["_NET_WM_WINDOW_TYPE_DOCK"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_MENU"] || + a == HYPRATOMS["_KDE_NET_WM_WINDOW_TYPE_OVERRIDE"]) { - if (pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_MENU"]) + if (a == HYPRATOMS["_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_MENU"]) pWindow->m_bX11ShouldntFocus = true; pWindow->m_bNoInitialFocus = true; return true; } - if (pWindow->m_uSurface.xwayland->role) { - try { - std::string winrole = std::string(pWindow->m_uSurface.xwayland->role); - if (winrole.contains("pop-up") || winrole.contains("task_dialog")) { - return true; - } - } catch (std::exception& e) { Debug::log(ERR, "Error in shouldBeFloated, winrole threw {}", e.what()); } - } - - if (pWindow->m_uSurface.xwayland->modal) { + if (pWindow->m_pXWaylandSurface->modal) { pWindow->m_bIsModal = true; return true; } - if (pWindow->m_iX11Type == 2) - return true; // override_redirect + if (pWindow->m_pXWaylandSurface->transient) + return true; - const auto SIZEHINTS = pWindow->m_uSurface.xwayland->size_hints; - if (SIZEHINTS && (pWindow->m_uSurface.xwayland->parent || ((SIZEHINTS->min_width == SIZEHINTS->max_width) && (SIZEHINTS->min_height == SIZEHINTS->max_height)))) + if (pWindow->m_pXWaylandSurface->role.contains("task_dialog") || pWindow->m_pXWaylandSurface->role.contains("pop-up")) + return true; + + if (pWindow->m_pXWaylandSurface->overrideRedirect) + return true; + + const auto SIZEHINTS = pWindow->m_pXWaylandSurface->sizeHints.get(); + if (pWindow->m_pXWaylandSurface->transient || pWindow->m_pXWaylandSurface->parent || + (SIZEHINTS && (SIZEHINTS->min_width == SIZEHINTS->max_width) && (SIZEHINTS->min_height == SIZEHINTS->max_height))) return true; } else { const auto PSTATE = pending ? &pWindow->m_pXDGSurface->toplevel->pending : &pWindow->m_pXDGSurface->toplevel->current; @@ -207,28 +179,14 @@ bool CHyprXWaylandManager::shouldBeFloated(PHLWINDOW pWindow, bool pending) { return false; } -void CHyprXWaylandManager::moveXWaylandWindow(PHLWINDOW pWindow, const Vector2D& pos) { - if (!validMapped(pWindow)) - return; - - if (!pWindow->m_bIsX11) - return; - - wlr_xwayland_surface_configure(pWindow->m_uSurface.xwayland, pos.x, pos.y, pWindow->m_vRealSize.value().x, pWindow->m_vRealSize.value().y); -} - void CHyprXWaylandManager::checkBorders(PHLWINDOW pWindow) { if (!pWindow->m_bIsX11) return; - for (size_t i = 0; i < pWindow->m_uSurface.xwayland->window_type_len; i++) { - if (pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_POPUP_MENU"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_NOTIFICATION"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_COMBO"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_MENU"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_SPLASH"] || - pWindow->m_uSurface.xwayland->window_type[i] == HYPRATOMS["_NET_WM_WINDOW_TYPE_TOOLTIP"]) { + for (auto& a : pWindow->m_pXWaylandSurface->atoms) { + if (a == HYPRATOMS["_NET_WM_WINDOW_TYPE_POPUP_MENU"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_NOTIFICATION"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"] || + a == HYPRATOMS["_NET_WM_WINDOW_TYPE_COMBO"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_MENU"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_SPLASH"] || + a == HYPRATOMS["_NET_WM_WINDOW_TYPE_TOOLTIP"]) { pWindow->m_bX11DoesntWantBorders = true; return; @@ -242,7 +200,7 @@ void CHyprXWaylandManager::checkBorders(PHLWINDOW pWindow) { void CHyprXWaylandManager::setWindowFullscreen(PHLWINDOW pWindow, bool fullscreen) { if (pWindow->m_bIsX11) - wlr_xwayland_surface_set_fullscreen(pWindow->m_uSurface.xwayland, fullscreen); + pWindow->m_pXWaylandSurface->setFullscreen(fullscreen); else if (pWindow->m_pXDGSurface && pWindow->m_pXDGSurface->toplevel) pWindow->m_pXDGSurface->toplevel->setFullscreen(fullscreen); } @@ -251,10 +209,10 @@ Vector2D CHyprXWaylandManager::getMaxSizeForWindow(PHLWINDOW pWindow) { if (!validMapped(pWindow)) return Vector2D(99999, 99999); - if ((pWindow->m_bIsX11 && !pWindow->m_uSurface.xwayland->size_hints) || (!pWindow->m_bIsX11 && !pWindow->m_pXDGSurface->toplevel) || pWindow->m_sAdditionalConfigData.noMaxSize) + if ((pWindow->m_bIsX11 && !pWindow->m_pXWaylandSurface->sizeHints) || (!pWindow->m_bIsX11 && !pWindow->m_pXDGSurface->toplevel) || pWindow->m_sAdditionalConfigData.noMaxSize) return Vector2D(99999, 99999); - auto MAXSIZE = pWindow->m_bIsX11 ? Vector2D(pWindow->m_uSurface.xwayland->size_hints->max_width, pWindow->m_uSurface.xwayland->size_hints->max_height) : + auto MAXSIZE = pWindow->m_bIsX11 ? Vector2D(pWindow->m_pXWaylandSurface->sizeHints->max_width, pWindow->m_pXWaylandSurface->sizeHints->max_height) : pWindow->m_pXDGSurface->toplevel->current.maxSize; if (MAXSIZE.x < 5) @@ -269,10 +227,10 @@ Vector2D CHyprXWaylandManager::getMinSizeForWindow(PHLWINDOW pWindow) { if (!validMapped(pWindow)) return Vector2D(0, 0); - if ((pWindow->m_bIsX11 && !pWindow->m_uSurface.xwayland->size_hints) || (!pWindow->m_bIsX11 && !pWindow->m_pXDGSurface->toplevel)) + if ((pWindow->m_bIsX11 && !pWindow->m_pXWaylandSurface->sizeHints) || (!pWindow->m_bIsX11 && !pWindow->m_pXDGSurface->toplevel)) return Vector2D(0, 0); - auto MINSIZE = pWindow->m_bIsX11 ? Vector2D(pWindow->m_uSurface.xwayland->size_hints->min_width, pWindow->m_uSurface.xwayland->size_hints->min_height) : + auto MINSIZE = pWindow->m_bIsX11 ? Vector2D(pWindow->m_pXWaylandSurface->sizeHints->min_width, pWindow->m_pXWaylandSurface->sizeHints->min_height) : pWindow->m_pXDGSurface->toplevel->current.minSize; MINSIZE = MINSIZE.clamp({1, 1}); diff --git a/src/managers/XWaylandManager.hpp b/src/managers/XWaylandManager.hpp index 8889eb61..5bbab802 100644 --- a/src/managers/XWaylandManager.hpp +++ b/src/managers/XWaylandManager.hpp @@ -11,22 +11,19 @@ class CHyprXWaylandManager { CHyprXWaylandManager(); ~CHyprXWaylandManager(); - wlr_xwayland* m_sWLRXWayland = nullptr; - - wlr_surface* getWindowSurface(PHLWINDOW); - void activateSurface(wlr_surface*, bool); - void activateWindow(PHLWINDOW, bool); - void getGeometryForWindow(PHLWINDOW, CBox*); - void sendCloseWindow(PHLWINDOW); - void setWindowSize(PHLWINDOW, Vector2D, bool force = false); - void setWindowFullscreen(PHLWINDOW, bool); - wlr_surface* surfaceAt(PHLWINDOW, const Vector2D&, Vector2D&); - bool shouldBeFloated(PHLWINDOW, bool pending = false); - void moveXWaylandWindow(PHLWINDOW, const Vector2D&); - void checkBorders(PHLWINDOW); - Vector2D getMaxSizeForWindow(PHLWINDOW); - Vector2D getMinSizeForWindow(PHLWINDOW); - Vector2D xwaylandToWaylandCoords(const Vector2D&); + wlr_surface* getWindowSurface(PHLWINDOW); + void activateSurface(wlr_surface*, bool); + void activateWindow(PHLWINDOW, bool); + void getGeometryForWindow(PHLWINDOW, CBox*); + void sendCloseWindow(PHLWINDOW); + void setWindowSize(PHLWINDOW, Vector2D, bool force = false); + void setWindowFullscreen(PHLWINDOW, bool); + wlr_surface* surfaceAt(PHLWINDOW, const Vector2D&, Vector2D&); + bool shouldBeFloated(PHLWINDOW, bool pending = false); + void checkBorders(PHLWINDOW); + Vector2D getMaxSizeForWindow(PHLWINDOW); + Vector2D getMinSizeForWindow(PHLWINDOW); + Vector2D xwaylandToWaylandCoords(const Vector2D&); }; inline std::unique_ptr g_pXWaylandManager; \ No newline at end of file diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index 52927d95..5a7688cd 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -661,7 +661,7 @@ void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e) { // clicking on border triggers resize // TODO detect click on LS properly - if (*PRESIZEONBORDER && !m_bLastFocusOnLS && e.state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (*PRESIZEONBORDER && !m_bLastFocusOnLS && e.state == WL_POINTER_BUTTON_STATE_PRESSED && (!w || w->m_iX11Type != 2)) { if (w && !w->m_bIsFullscreen) { const CBox real = {w->m_vRealPosition.value().x, w->m_vRealPosition.value().y, w->m_vRealSize.value().x, w->m_vRealSize.value().y}; const CBox grab = {real.x - BORDER_GRAB_AREA, real.y - BORDER_GRAB_AREA, real.width + 2 * BORDER_GRAB_AREA, real.height + 2 * BORDER_GRAB_AREA}; @@ -674,7 +674,7 @@ void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e) { } switch (e.state) { - case WL_POINTER_BUTTON_STATE_PRESSED: + case WL_POINTER_BUTTON_STATE_PRESSED: { if (*PFOLLOWMOUSE == 3) // don't refocus on full loose break; @@ -692,10 +692,16 @@ void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e) { } // if clicked on a floating window make it top - if (g_pCompositor->m_pLastWindow.lock() && g_pCompositor->m_pLastWindow->m_bIsFloating) - g_pCompositor->changeWindowZOrder(g_pCompositor->m_pLastWindow.lock(), true); + if (!g_pSeatManager->state.pointerFocus) + break; + + auto HLSurf = CWLSurface::surfaceFromWlr(g_pSeatManager->state.pointerFocus); + + if (HLSurf && HLSurf->getWindow()) + g_pCompositor->changeWindowZOrder(HLSurf->getWindow(), true); break; + } case WL_POINTER_BUTTON_STATE_RELEASED: break; } @@ -1677,6 +1683,10 @@ void CInputManager::setCursorIconOnBorder(PHLWINDOW w) { return; } + // ignore X11 OR windows, they shouldn't be touched + if (w->m_bIsX11 && w->m_iX11Type == 2) + return; + static auto PEXTENDBORDERGRAB = CConfigValue("general:extend_border_grab_area"); const int BORDERSIZE = w->getRealBorderSize(); const int ROUNDING = w->rounding(); diff --git a/src/protocols/XDGOutput.cpp b/src/protocols/XDGOutput.cpp index 9d3e2ac9..771f5f78 100644 --- a/src/protocols/XDGOutput.cpp +++ b/src/protocols/XDGOutput.cpp @@ -1,6 +1,7 @@ #include "XDGOutput.hpp" #include "../Compositor.hpp" #include "../config/ConfigValue.hpp" +#include "../xwayland/XWayland.hpp" #define OUTPUT_MANAGER_VERSION 3 #define OUTPUT_DONE_DEPRECATED_SINCE_VERSION 3 @@ -55,7 +56,7 @@ void CXDGOutputProtocol::onManagerGetXDGOutput(CZxdgOutputManagerV1* mgr, uint32 CXDGOutput* pXDGOutput = m_vXDGOutputs.emplace_back(std::make_unique(makeShared(CLIENT, mgr->version(), id), PMONITOR)).get(); #ifndef NO_XWAYLAND - if (g_pXWaylandManager->m_sWLRXWayland && g_pXWaylandManager->m_sWLRXWayland->server && g_pXWaylandManager->m_sWLRXWayland->server->client == CLIENT) + if (g_pXWayland && g_pXWayland->pServer && g_pXWayland->pServer->xwaylandClient == CLIENT) pXDGOutput->isXWayland = true; #endif pXDGOutput->client = CLIENT; diff --git a/src/protocols/XWaylandShell.cpp b/src/protocols/XWaylandShell.cpp new file mode 100644 index 00000000..8b9905f8 --- /dev/null +++ b/src/protocols/XWaylandShell.cpp @@ -0,0 +1,84 @@ +#include "XWaylandShell.hpp" +#include + +#define LOGM PROTO::xwaylandShell->protoLog + +CXWaylandSurfaceResource::CXWaylandSurfaceResource(SP resource_, wlr_surface* surface_) : surface(surface_), resource(resource_) { + if (!good()) + return; + + resource->setDestroy([this](CXwaylandSurfaceV1* r) { + events.destroy.emit(); + PROTO::xwaylandShell->destroyResource(this); + }); + resource->setOnDestroy([this](CXwaylandSurfaceV1* r) { + events.destroy.emit(); + PROTO::xwaylandShell->destroyResource(this); + }); + + pClient = resource->client(); + + resource->setSetSerial([this](CXwaylandSurfaceV1* r, uint32_t lo, uint32_t hi) { + serial = (((uint64_t)hi) << 32) + lo; + PROTO::xwaylandShell->events.newSurface.emit(self.lock()); + }); +} + +CXWaylandSurfaceResource::~CXWaylandSurfaceResource() { + events.destroy.emit(); +} + +bool CXWaylandSurfaceResource::good() { + return resource->resource(); +} + +wl_client* CXWaylandSurfaceResource::client() { + return pClient; +} + +CXWaylandShellResource::CXWaylandShellResource(SP resource_) : resource(resource_) { + if (!good()) + return; + + resource->setDestroy([this](CXwaylandShellV1* r) { PROTO::xwaylandShell->destroyResource(this); }); + resource->setOnDestroy([this](CXwaylandShellV1* r) { PROTO::xwaylandShell->destroyResource(this); }); + + resource->setGetXwaylandSurface([this](CXwaylandShellV1* r, uint32_t id, wl_resource* surface) { + const auto RESOURCE = PROTO::xwaylandShell->m_vSurfaces.emplace_back( + makeShared(makeShared(r->client(), r->version(), id), wlr_surface_from_resource(surface))); + + if (!RESOURCE->good()) { + r->noMemory(); + PROTO::xwaylandShell->m_vSurfaces.pop_back(); + return; + } + + RESOURCE->self = RESOURCE; + }); +} + +bool CXWaylandShellResource::good() { + return resource->resource(); +} + +CXWaylandShellProtocol::CXWaylandShellProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CXWaylandShellProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_vManagers.emplace_back(makeShared(makeShared(client, ver, id))); + + if (!RESOURCE->good()) { + wl_client_post_no_memory(client); + m_vManagers.pop_back(); + return; + } +} + +void CXWaylandShellProtocol::destroyResource(CXWaylandShellResource* resource) { + std::erase_if(m_vManagers, [&](const auto& other) { return other.get() == resource; }); +} + +void CXWaylandShellProtocol::destroyResource(CXWaylandSurfaceResource* resource) { + std::erase_if(m_vSurfaces, [&](const auto& other) { return other.get() == resource; }); +} diff --git a/src/protocols/XWaylandShell.hpp b/src/protocols/XWaylandShell.hpp new file mode 100644 index 00000000..2c03d172 --- /dev/null +++ b/src/protocols/XWaylandShell.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include +#include "WaylandProtocol.hpp" +#include "xwayland-shell-v1.hpp" +#include "../helpers/signal/Signal.hpp" + +class CXWaylandSurfaceResource { + public: + CXWaylandSurfaceResource(SP resource_, wlr_surface* surface_); + ~CXWaylandSurfaceResource(); + + bool good(); + wl_client* client(); + + struct { + CSignal destroy; + } events; + + uint64_t serial = 0; + wlr_surface* surface = nullptr; + + WP self; + + private: + SP resource; + wl_client* pClient = nullptr; +}; + +class CXWaylandShellResource { + public: + CXWaylandShellResource(SP resource_); + + bool good(); + + private: + SP resource; +}; + +class CXWaylandShellProtocol : public IWaylandProtocol { + public: + CXWaylandShellProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + + struct { + CSignal newSurface; // SP. Fired when it sets a serial, otherwise it's useless + } events; + + private: + void destroyResource(CXWaylandSurfaceResource* resource); + void destroyResource(CXWaylandShellResource* resource); + + // + std::vector> m_vManagers; + std::vector> m_vSurfaces; + + friend class CXWaylandSurfaceResource; + friend class CXWaylandShellResource; +}; + +namespace PROTO { + inline UP xwaylandShell; +}; diff --git a/src/protocols/types/DataDevice.cpp b/src/protocols/types/DataDevice.cpp index 47cbda8b..eb6969cc 100644 --- a/src/protocols/types/DataDevice.cpp +++ b/src/protocols/types/DataDevice.cpp @@ -15,3 +15,7 @@ bool IDataSource::used() { void IDataSource::markUsed() { wasUsed = true; } + +eDataSourceType IDataSource::type() { + return DATA_SOURCE_TYPE_WAYLAND; +} diff --git a/src/protocols/types/DataDevice.hpp b/src/protocols/types/DataDevice.hpp index 98a97c14..948f47a0 100644 --- a/src/protocols/types/DataDevice.hpp +++ b/src/protocols/types/DataDevice.hpp @@ -5,6 +5,11 @@ #include #include "../../helpers/signal/Signal.hpp" +enum eDataSourceType { + DATA_SOURCE_TYPE_WAYLAND = 0, + DATA_SOURCE_TYPE_X11, +}; + class IDataSource { public: IDataSource() {} @@ -19,6 +24,7 @@ class IDataSource { virtual bool used(); virtual void markUsed(); virtual void error(uint32_t code, const std::string& msg) = 0; + virtual eDataSourceType type(); struct { CSignal destroy; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index c5c87ea1..40ae953e 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -192,14 +192,10 @@ static void renderSurface(struct wlr_surface* surface, int x, int y, void* data) g_pHyprOpenGL->blend(true); if (RDATA->surface && surface == RDATA->surface) { - if (wlr_xwayland_surface_try_from_wlr_surface(surface) && !wlr_xwayland_surface_try_from_wlr_surface(surface)->has_alpha && ALPHA == 1.f) { + if (RDATA->blur) + g_pHyprOpenGL->renderTextureWithBlur(TEXTURE, &windowBox, ALPHA, surface, rounding, RDATA->blockBlurOptimization, RDATA->fadeAlpha); + else g_pHyprOpenGL->renderTexture(TEXTURE, &windowBox, ALPHA, rounding, true); - } else { - if (RDATA->blur) - g_pHyprOpenGL->renderTextureWithBlur(TEXTURE, &windowBox, ALPHA, surface, rounding, RDATA->blockBlurOptimization, RDATA->fadeAlpha); - else - g_pHyprOpenGL->renderTexture(TEXTURE, &windowBox, ALPHA, rounding, true); - } } else { if (RDATA->blur && RDATA->popup) g_pHyprOpenGL->renderTextureWithBlur(TEXTURE, &windowBox, ALPHA, surface, rounding, true, RDATA->fadeAlpha); diff --git a/src/xwayland/Server.cpp b/src/xwayland/Server.cpp new file mode 100644 index 00000000..510ad737 --- /dev/null +++ b/src/xwayland/Server.cpp @@ -0,0 +1,427 @@ +#ifndef NO_XWAYLAND + +#include "Server.hpp" +#include "../defines.hpp" +#include "../Compositor.hpp" +#include "../managers/CursorManager.hpp" +#include "XWayland.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TODO: cleanup +static bool set_cloexec(int fd, bool cloexec) { + int flags = fcntl(fd, F_GETFD); + if (flags == -1) { + wlr_log_errno(WLR_ERROR, "fcntl failed"); + return false; + } + if (cloexec) { + flags = flags | FD_CLOEXEC; + } else { + flags = flags & ~FD_CLOEXEC; + } + if (fcntl(fd, F_SETFD, flags) == -1) { + wlr_log_errno(WLR_ERROR, "fcntl failed"); + return false; + } + return true; +} + +static int openSocket(struct sockaddr_un* addr, size_t path_size) { + int fd, rc; + socklen_t size = offsetof(struct sockaddr_un, sun_path) + path_size + 1; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + wlr_log_errno(WLR_ERROR, "Failed to create socket %c%s", addr->sun_path[0] ? addr->sun_path[0] : '@', addr->sun_path + 1); + return -1; + } + if (!set_cloexec(fd, true)) { + close(fd); + return -1; + } + + if (addr->sun_path[0]) { + unlink(addr->sun_path); + } + if (bind(fd, (struct sockaddr*)addr, size) < 0) { + rc = errno; + wlr_log_errno(WLR_ERROR, "Failed to bind socket %c%s", addr->sun_path[0] ? addr->sun_path[0] : '@', addr->sun_path + 1); + goto cleanup; + } + if (listen(fd, 1) < 0) { + rc = errno; + wlr_log_errno(WLR_ERROR, "Failed to listen to socket %c%s", addr->sun_path[0] ? addr->sun_path[0] : '@', addr->sun_path + 1); + goto cleanup; + } + + return fd; + +cleanup: + close(fd); + if (addr->sun_path[0]) { + unlink(addr->sun_path); + } + errno = rc; + return -1; +} + +static bool checkPermissionsForSocketDir(void) { + struct stat buf; + + if (lstat("/tmp/.X11-unix", &buf)) { + Debug::log(ERR, "Failed statting X11 socket dir"); + return false; + } + + if (!(buf.st_mode & S_IFDIR)) { + Debug::log(ERR, "X11 socket dir is not a dir"); + return false; + } + + if (!((buf.st_uid == 0) || (buf.st_uid == getuid()))) { + Debug::log(ERR, "X11 socket dir is not ours"); + return false; + } + + if (!(buf.st_mode & S_ISVTX)) { + if ((buf.st_mode & (S_IWGRP | S_IWOTH))) { + Debug::log(ERR, "X11 socket dir is sticky by others"); + return false; + } + } + + return true; +} + +static bool openSockets(std::array& sockets, int display) { + auto ret = mkdir("/tmp/.X11-unix", 755); + + if (ret != 0) { + if (errno == EEXIST) { + if (!checkPermissionsForSocketDir()) + return false; + } else { + Debug::log(ERR, "XWayland: couldn't create socket dir"); + return false; + } + } + + std::string path; + sockaddr_un addr = {.sun_family = AF_UNIX}; + +#ifdef __linux__ + // cursed... + addr.sun_path[0] = 0; + path = std::format("/tmp/.X11-unix/X{}", display); + strncpy(addr.sun_path + 1, path.c_str(), path.length() + 1); +#else + path = std::format("/tmp/.X11-unix/X{}_", display); + strncpy(addr.sun_path, path.c_str(), path.length() + 1); +#endif + sockets[0] = openSocket(&addr, path.length()); + if (sockets[0] < 0) + return false; + + path = std::format("/tmp/.X11-unix/X{}", display); + strncpy(addr.sun_path, path.c_str(), path.length() + 1); + sockets[1] = openSocket(&addr, path.length()); + if (sockets[1] < 0) { + close(sockets[0]); + sockets[0] = -1; + return false; + } + + return true; +} + +static void startServer(void* data) { + if (!g_pXWayland->pServer->start()) + Debug::log(ERR, "The XWayland server could not start! XWayland will not work..."); +} + +static int xwaylandReady(int fd, uint32_t mask, void* data) { + return g_pXWayland->pServer->ready(fd, mask); +} + +bool CXWaylandServer::tryOpenSockets() { + for (size_t i = 0; i <= 32; ++i) { + auto LOCK = std::format("/tmp/.X{}-lock", i); + + if (int fd = open(LOCK.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0444); fd >= 0) { + // we managed to open the lock + if (!openSockets(xFDs, i)) { + std::filesystem::remove(LOCK); + close(fd); + continue; + } + + const auto PIDSTR = std::format("{}", getpid()); + + if (write(fd, PIDSTR.c_str(), PIDSTR.length()) != (long)PIDSTR.length()) { + std::filesystem::remove(LOCK); + close(fd); + continue; + } + + close(fd); + + display = i; + displayName = std::format(":{}", display); + break; + } + + int fd = open(LOCK.c_str(), O_RDONLY | O_CLOEXEC); + + if (fd < 0) + continue; + + char pidstr[12] = {0}; + read(fd, pidstr, sizeof(pidstr) - 1); + close(fd); + + uint64_t pid = 0; + try { + pid = std::stoi(std::string{pidstr, 11}); + } catch (...) { continue; } + + if (kill(pid, 0) != 0 && errno == ESRCH) { + if (!std::filesystem::remove(LOCK)) + continue; + + i--; + } + } + + if (display < 0) { + Debug::log(ERR, "Failed to find a suitable socket for xwayland"); + return false; + } + + Debug::log(LOG, "XWayland found a suitable display socket at DISPLAY: {}", displayName); + return true; +} + +CXWaylandServer::CXWaylandServer() { + ; +} + +CXWaylandServer::~CXWaylandServer() { + die(); + if (display < 0) + return; + + if (xFDs[0]) + close(xFDs[0]); + if (xFDs[1]) + close(xFDs[1]); + + auto LOCK = std::format("/tmp/.X{}-lock", display); + std::filesystem::remove(LOCK); + + std::string path; +#ifdef __linux__ + path = std::format("/tmp/.X11-unix/X{}", display); +#else + path = std::format("/tmp/.X11-unix/X{}_", display); +#endif + std::filesystem::remove(path); +} + +void CXWaylandServer::die() { + if (display < 0) + return; + + if (xFDReadEvents[0]) { + wl_event_source_remove(xFDReadEvents[0]); + wl_event_source_remove(xFDReadEvents[1]); + + xFDReadEvents = {nullptr, nullptr}; + } + + if (pipeSource) + wl_event_source_remove(pipeSource); + + if (waylandFDs[0]) + close(waylandFDs[0]); + if (waylandFDs[1]) + close(waylandFDs[1]); + if (xwmFDs[0]) + close(xwmFDs[0]); + if (xwmFDs[1]) + close(xwmFDs[1]); + + if (xwaylandClient) + wl_client_destroy(xwaylandClient); + + xwaylandClient = nullptr; + waylandFDs = {-1, -1}; + xwmFDs = {-1, -1}; +} + +bool CXWaylandServer::create() { + if (!tryOpenSockets()) + return false; + + setenv("DISPLAY", displayName.c_str(), true); + + // TODO: lazy mode + + idleSource = wl_event_loop_add_idle(g_pCompositor->m_sWLEventLoop, ::startServer, nullptr); + + return true; +} + +void CXWaylandServer::runXWayland(int notifyFD) { + if (!set_cloexec(xFDs[0], false) || !set_cloexec(xFDs[1], false) || !set_cloexec(waylandFDs[1], false) || !set_cloexec(xwmFDs[1], false)) { + Debug::log(ERR, "Failed to unset cloexec on fds"); + _exit(EXIT_FAILURE); + } + + auto cmd = std::format("Xwayland {} -rootless -core -listenfd {} -listenfd {} -displayfd {} -wm {}", displayName, xFDs[0], xFDs[1], notifyFD, xwmFDs[1]); + + auto waylandSocket = std::format("{}", waylandFDs[1]); + setenv("WAYLAND_SOCKET", waylandSocket.c_str(), true); + + Debug::log(LOG, "Starting XWayland with \"{}\", bon voyage!", cmd); + + execl("/bin/sh", "/bin/sh", "-c", cmd.c_str(), nullptr); + + Debug::log(ERR, "XWayland failed to open"); + _exit(1); +} + +bool CXWaylandServer::start() { + idleSource = nullptr; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, waylandFDs.data()) != 0) { + Debug::log(ERR, "socketpair failed (1)"); + die(); + return false; + } + + if (!set_cloexec(waylandFDs[0], true) || !set_cloexec(waylandFDs[1], true)) { + Debug::log(ERR, "set_cloexec failed (1)"); + die(); + return false; + } + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, xwmFDs.data()) != 0) { + Debug::log(ERR, "socketpair failed (2)"); + die(); + return false; + } + + if (!set_cloexec(xwmFDs[0], true) || !set_cloexec(xwmFDs[1], true)) { + Debug::log(ERR, "set_cloexec failed (2)"); + die(); + return false; + } + + xwaylandClient = wl_client_create(g_pCompositor->m_sWLDisplay, waylandFDs[0]); + if (!xwaylandClient) { + Debug::log(ERR, "wl_client_create failed"); + die(); + return false; + } + + waylandFDs[0] = -1; + + int notify[2] = {-1, -1}; + if (pipe(notify) < 0) { + Debug::log(ERR, "pipe failed"); + die(); + return false; + } + + if (!set_cloexec(notify[0], true)) { + Debug::log(ERR, "set_cloexec failed (3)"); + close(notify[0]); + close(notify[1]); + die(); + return false; + } + + pipeSource = wl_event_loop_add_fd(g_pCompositor->m_sWLEventLoop, notify[0], WL_EVENT_READABLE, ::xwaylandReady, nullptr); + + serverPID = fork(); + if (serverPID < 0) { + Debug::log(ERR, "fork failed"); + close(notify[0]); + close(notify[1]); + die(); + return false; + } else if (serverPID == 0) { + pid_t pid = fork(); + if (pid < 0) { + Debug::log(ERR, "second fork failed"); + _exit(1); + } else if (pid == 0) { + runXWayland(notify[1]); + } + + _exit(0); + } + + close(notify[1]); + close(waylandFDs[1]); + close(xwmFDs[1]); + waylandFDs[1] = -1; + xwmFDs[1] = -1; + + return true; +} + +int CXWaylandServer::ready(int fd, uint32_t mask) { + if (mask & WL_EVENT_READABLE) { + // xwayland writes twice + char buf[64]; + ssize_t n = read(fd, buf, sizeof(buf)); + if (n < 0 && errno != EINTR) { + Debug::log(ERR, "Xwayland: read from displayFd failed"); + mask = 0; + } else if (n <= 0 || buf[n - 1] != '\n') + return 1; + } + + while (waitpid(serverPID, nullptr, 0) < 0) { + if (errno == EINTR) + continue; + Debug::log(ERR, "Xwayland: waitpid for fork failed"); + g_pXWayland->pServer.reset(); + return 1; + } + + // if we don't have readable here, it failed + if (!(mask & WL_EVENT_READABLE)) { + Debug::log(ERR, "Xwayland: startup failed, not setting up xwm"); + g_pXWayland->pServer.reset(); + return 1; + } + + Debug::log(LOG, "XWayland is ready"); + + close(fd); + wl_event_source_remove(pipeSource); + pipeSource = nullptr; + + // start the wm + g_pXWayland->pWM = std::make_unique(); + + g_pCursorManager->setXWaylandCursor(); + + return 0; +} + +#endif diff --git a/src/xwayland/Server.hpp b/src/xwayland/Server.hpp new file mode 100644 index 00000000..0c06a56c --- /dev/null +++ b/src/xwayland/Server.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include "../helpers/signal/Signal.hpp" + +struct wl_event_source; +struct wl_client; + +// TODO: add lazy mode +class CXWaylandServer { + public: + CXWaylandServer(); + ~CXWaylandServer(); + + // create the server. + bool create(); + + // starts the server, meant to be called by CXWaylandServer. + bool start(); + + // called on ready + int ready(int fd, uint32_t mask); + + void die(); + + struct { + CSignal ready; + } events; + + wl_client* xwaylandClient = nullptr; + + private: + bool tryOpenSockets(); + void runXWayland(int notifyFD); + + pid_t serverPID = 0; + + std::string displayName; + int display = -1; + std::array xFDs = {-1, -1}; + std::array xFDReadEvents = {nullptr, nullptr}; + wl_event_source* idleSource = nullptr; + wl_event_source* pipeSource = nullptr; + std::array xwmFDs = {-1, -1}; + std::array waylandFDs = {-1, -1}; + + friend class CXWM; +}; diff --git a/src/xwayland/XDataSource.cpp b/src/xwayland/XDataSource.cpp new file mode 100644 index 00000000..98e0701b --- /dev/null +++ b/src/xwayland/XDataSource.cpp @@ -0,0 +1,97 @@ +#ifndef NO_XWAYLAND + +#include "XDataSource.hpp" +#include "XWayland.hpp" +#include "../defines.hpp" + +#include + +CXDataSource::CXDataSource(SXSelection& sel_) : selection(sel_) { + xcb_get_property_cookie_t cookie = xcb_get_property(g_pXWayland->pWM->connection, + 1, // delete + selection.window, HYPRATOMS["_WL_SELECTION"], XCB_GET_PROPERTY_TYPE_ANY, 0, 4096); + + xcb_get_property_reply_t* reply = xcb_get_property_reply(g_pXWayland->pWM->connection, cookie, NULL); + if (!reply) + return; + + if (reply->type != XCB_ATOM_ATOM) { + free(reply); + return; + } + + auto value = (xcb_atom_t*)xcb_get_property_value(reply); + for (uint32_t i = 0; i < reply->value_len; i++) { + if (value[i] == HYPRATOMS["UTF8_STRING"]) + mimeTypes.push_back("text/plain;charset=utf-8"); + else if (value[i] == HYPRATOMS["TEXT"]) + mimeTypes.push_back("text/plain"); + else if (value[i] != HYPRATOMS["TARGETS"] && value[i] != HYPRATOMS["TIMESTAMP"]) { + + auto type = g_pXWayland->pWM->mimeFromAtom(value[i]); + + if (type == "INVALID") + continue; + + mimeTypes.push_back(type); + } + + mimeAtoms.push_back(value[i]); + } + + free(reply); +} + +std::vector CXDataSource::mimes() { + return mimeTypes; +} + +void CXDataSource::send(const std::string& mime, uint32_t fd) { + xcb_atom_t mimeAtom = 0; + + for (size_t i = 0; i < mimeTypes.size(); ++i) { + if (mimeTypes.at(i) == mime) { + mimeAtom = mimeAtoms.at(i); + break; + } + } + + if (!mimeAtom) { + Debug::log(ERR, "[XDataSource] mime atom not found"); + close(fd); + return; + } + + Debug::log(LOG, "[XDataSource] send with mime {} to fd {}", mime, fd); + + selection.transfer = std::make_unique(selection); + selection.transfer->incomingWindow = xcb_generate_id(g_pXWayland->pWM->connection); + const uint32_t MASK = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_create_window(g_pXWayland->pWM->connection, XCB_COPY_FROM_PARENT, selection.transfer->incomingWindow, g_pXWayland->pWM->screen->root, 0, 0, 10, 10, 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, g_pXWayland->pWM->screen->root_visual, XCB_CW_EVENT_MASK, &MASK); + + xcb_convert_selection(g_pXWayland->pWM->connection, selection.transfer->incomingWindow, HYPRATOMS["CLIPBOARD"], mimeAtom, HYPRATOMS["_WL_SELECTION"], XCB_TIME_CURRENT_TIME); + + xcb_flush(g_pXWayland->pWM->connection); + + fcntl(fd, F_SETFL, O_WRONLY | O_NONBLOCK); + selection.transfer->wlFD = fd; +} + +void CXDataSource::accepted(const std::string& mime) { + Debug::log(LOG, "[XDataSource] accepted is a stub"); +} + +void CXDataSource::cancelled() { + Debug::log(LOG, "[XDataSource] cancelled is a stub"); +} + +void CXDataSource::error(uint32_t code, const std::string& msg) { + Debug::log(LOG, "[XDataSource] error is a stub: err {}: {}", code, msg); +} + +eDataSourceType CXDataSource::type() { + return DATA_SOURCE_TYPE_X11; +} + +#endif \ No newline at end of file diff --git a/src/xwayland/XDataSource.hpp b/src/xwayland/XDataSource.hpp new file mode 100644 index 00000000..c629aa2a --- /dev/null +++ b/src/xwayland/XDataSource.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "../protocols/types/DataDevice.hpp" + +struct SXSelection; + +class CXDataSource : public IDataSource { + public: + CXDataSource(SXSelection&); + + virtual std::vector mimes(); + virtual void send(const std::string& mime, uint32_t fd); + virtual void accepted(const std::string& mime); + virtual void cancelled(); + virtual void error(uint32_t code, const std::string& msg); + virtual eDataSourceType type(); + + private: + SXSelection& selection; + std::vector mimeTypes; // these two have shared idx + std::vector mimeAtoms; // +}; \ No newline at end of file diff --git a/src/xwayland/XSurface.cpp b/src/xwayland/XSurface.cpp new file mode 100644 index 00000000..8994e975 --- /dev/null +++ b/src/xwayland/XSurface.cpp @@ -0,0 +1,291 @@ +#include "XSurface.hpp" +#include "XWayland.hpp" +#include "../protocols/XWaylandShell.hpp" + +#ifndef NO_XWAYLAND + +#include "../Compositor.hpp" +#include + +CXWaylandSurface::CXWaylandSurface(uint32_t xID_, CBox geometry_, bool OR) : xID(xID_), geometry(geometry_), overrideRedirect(OR) { + xcb_res_query_client_ids_cookie_t client_id_cookie = {0}; + if (g_pXWayland->pWM->xres) { + xcb_res_client_id_spec_t spec = {.client = xID, .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID}; + client_id_cookie = xcb_res_query_client_ids(g_pXWayland->pWM->connection, 1, &spec); + } + + uint32_t values[1]; + values[0] = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_change_window_attributes(g_pXWayland->pWM->connection, xID, XCB_CW_EVENT_MASK, values); + + if (g_pXWayland->pWM->xres) { + xcb_res_query_client_ids_reply_t* reply = xcb_res_query_client_ids_reply(g_pXWayland->pWM->connection, client_id_cookie, nullptr); + if (!reply) + return; + + uint32_t* ppid = nullptr; + xcb_res_client_id_value_iterator_t iter = xcb_res_query_client_ids_ids_iterator(reply); + while (iter.rem > 0) { + if (iter.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID && xcb_res_client_id_value_value_length(iter.data) > 0) { + ppid = xcb_res_client_id_value_value(iter.data); + break; + } + xcb_res_client_id_value_next(&iter); + } + if (ppid == NULL) { + free(reply); + return; + } + pid = *ppid; + free(reply); + } + + events.resourceChange.registerStaticListener([this](void* data, std::any d) { ensureListeners(); }, nullptr); +} + +void CXWaylandSurface::ensureListeners() { + bool connected = hyprListener_surfaceDestroy.isConnected(); + + if (connected && !surface) { + hyprListener_surfaceDestroy.removeCallback(); + hyprListener_surfaceCommit.removeCallback(); + } else if (!connected && surface) { + hyprListener_surfaceDestroy.initCallback( + &surface->events.destroy, + [this](void* owner, void* data) { + if (mapped) + unmap(); + + surface = nullptr; + hyprListener_surfaceDestroy.removeCallback(); + hyprListener_surfaceCommit.removeCallback(); + events.resourceChange.emit(); + }, + nullptr, "CXWaylandSurface"); + hyprListener_surfaceCommit.initCallback( + &surface->events.commit, + [this](void* owner, void* data) { + if (surface->pending.buffer_width > 0 && surface->pending.buffer_height > 0 && !mapped) { + map(); + return; + } + + if (surface->pending.buffer_width <= 0 && surface->pending.buffer_height <= 0 && mapped) { + unmap(); + return; + } + + events.commit.emit(); + }, + nullptr, "CXWaylandSurface"); + } + + if (resource) { + listeners.destroyResource = resource->events.destroy.registerListener([this](std::any d) { + unmap(); + surface = nullptr; + events.resourceChange.emit(); + }); + } +} + +void CXWaylandSurface::map() { + if (mapped) + return; + + ASSERT(surface); + + g_pXWayland->pWM->mappedSurfaces.emplace_back(self); + g_pXWayland->pWM->mappedSurfacesStacking.emplace_back(self); + + mapped = true; + wlr_surface_map(surface); + + Debug::log(LOG, "XWayland surface {:x} mapping", (uintptr_t)this); + + events.map.emit(); + + g_pXWayland->pWM->updateClientList(); +} + +void CXWaylandSurface::unmap() { + if (!mapped) + return; + + ASSERT(surface); + + std::erase(g_pXWayland->pWM->mappedSurfaces, self); + std::erase(g_pXWayland->pWM->mappedSurfacesStacking, self); + + mapped = false; + wlr_surface_unmap(surface); + + Debug::log(LOG, "XWayland surface {:x} unmapping", (uintptr_t)this); + + events.unmap.emit(); + + g_pXWayland->pWM->updateClientList(); +} + +void CXWaylandSurface::considerMap() { + if (mapped) + return; + + if (!surface) { + Debug::log(LOG, "XWayland surface: considerMap, nope, no surface"); + return; + } + + if (surface->pending.buffer_width > 0 && surface->pending.buffer_height > 0) { + Debug::log(LOG, "XWayland surface: considerMap, sure, we have a buffer"); + map(); + return; + } + + Debug::log(LOG, "XWayland surface: considerMap, nope, we don't have a buffer"); +} + +bool CXWaylandSurface::wantsFocus() { + if (atoms.empty()) + return true; + + const std::array search = { + HYPRATOMS["_NET_WM_WINDOW_TYPE_COMBO"], HYPRATOMS["_NET_WM_WINDOW_TYPE_DND"], HYPRATOMS["_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"], + HYPRATOMS["_NET_WM_WINDOW_TYPE_MENU"], HYPRATOMS["_NET_WM_WINDOW_TYPE_NOTIFICATION"], HYPRATOMS["_NET_WM_WINDOW_TYPE_POPUP_MENU"], + HYPRATOMS["_NET_WM_WINDOW_TYPE_SPLASH"], HYPRATOMS["_NET_WM_WINDOW_TYPE_DESKTOP"], HYPRATOMS["_NET_WM_WINDOW_TYPE_TOOLTIP"], + HYPRATOMS["_NET_WM_WINDOW_TYPE_UTILITY"], + }; + + for (auto& searched : search) { + for (auto& a : atoms) { + if (a == searched) + return false; + } + } + + return true; +} + +void CXWaylandSurface::configure(const CBox& box) { + Vector2D oldSize = geometry.size(); + + geometry = box; + + uint32_t mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | XCB_CONFIG_WINDOW_BORDER_WIDTH; + uint32_t values[] = {box.x, box.y, box.width, box.height, 0}; + xcb_configure_window(g_pXWayland->pWM->connection, xID, mask, values); + + g_pXWayland->pWM->updateClientList(); + + xcb_flush(g_pXWayland->pWM->connection); +} + +void CXWaylandSurface::activate(bool activate) { + if (overrideRedirect && !activate) + return; + g_pXWayland->pWM->activateSurface(self.lock()); +} + +void CXWaylandSurface::setFullscreen(bool fs) { + fullscreen = fs; + g_pXWayland->pWM->sendState(self.lock()); +} + +void CXWaylandSurface::setMinimized(bool mz) { + minimized = mz; + g_pXWayland->pWM->sendState(self.lock()); +} + +void CXWaylandSurface::restackToTop() { + uint32_t values[1] = {XCB_STACK_MODE_ABOVE}; + + xcb_configure_window(g_pXWayland->pWM->connection, xID, XCB_CONFIG_WINDOW_STACK_MODE, values); + + for (auto it = g_pXWayland->pWM->mappedSurfacesStacking.begin(); it != g_pXWayland->pWM->mappedSurfacesStacking.end(); ++it) { + if (*it == self) { + std::rotate(it, it + 1, g_pXWayland->pWM->mappedSurfacesStacking.end()); + break; + } + } + + g_pXWayland->pWM->updateClientList(); + + xcb_flush(g_pXWayland->pWM->connection); +} + +void CXWaylandSurface::close() { + xcb_client_message_data_t msg = {0}; + msg.data32[0] = HYPRATOMS["WM_DELETE_WINDOW"]; + msg.data32[1] = XCB_CURRENT_TIME; + g_pXWayland->pWM->sendWMMessage(self.lock(), &msg, XCB_EVENT_MASK_NO_EVENT); +} + +void CXWaylandSurface::setWithdrawn(bool withdrawn_) { + withdrawn = withdrawn_; + std::vector props = {XCB_ICCCM_WM_STATE_NORMAL, XCB_WINDOW_NONE}; + + if (withdrawn) + props[0] = XCB_ICCCM_WM_STATE_WITHDRAWN; + else if (minimized) + props[0] = XCB_ICCCM_WM_STATE_ICONIC; + else + props[0] = XCB_ICCCM_WM_STATE_NORMAL; + + xcb_change_property(g_pXWayland->pWM->connection, XCB_PROP_MODE_REPLACE, xID, HYPRATOMS["WM_STATE"], HYPRATOMS["WM_STATE"], 32, props.size(), props.data()); +} + +#else + +CXWaylandSurface::CXWaylandSurface(uint32_t xID_, CBox geometry_, bool OR) : xID(xID_), geometry(geometry_), overrideRedirect(OR) { + ; +} + +void CXWaylandSurface::ensureListeners() { + ; +} + +void CXWaylandSurface::map() { + ; +} + +void CXWaylandSurface::unmap() { + ; +} + +bool CXWaylandSurface::wantsFocus() { + return false; +} + +void CXWaylandSurface::configure(const CBox& box) { + ; +} + +void CXWaylandSurface::activate(bool activate) { + ; +} + +void CXWaylandSurface::setFullscreen(bool fs) { + ; +} + +void CXWaylandSurface::setMinimized(bool mz) { + ; +} + +void CXWaylandSurface::restackToTop() { + ; +} + +void CXWaylandSurface::close() { + ; +} + +void CXWaylandSurface::considerMap() { + ; +} + +void CXWaylandSurface::setWithdrawn(bool withdrawn) { + ; +} + +#endif diff --git a/src/xwayland/XSurface.hpp b/src/xwayland/XSurface.hpp new file mode 100644 index 00000000..73cb89a5 --- /dev/null +++ b/src/xwayland/XSurface.hpp @@ -0,0 +1,120 @@ +#pragma once + +#include "../helpers/WLListener.hpp" +#include "../helpers/signal/Signal.hpp" +#include "../helpers/Box.hpp" +#include + +struct wlr_surface; +class CXWaylandSurfaceResource; + +#ifdef NO_XWAYLAND +typedef uint32_t xcb_pixmap_t; +typedef uint32_t xcb_window_t; +typedef struct { + int32_t flags; + uint32_t input; + int32_t initial_state; + xcb_pixmap_t icon_pixmap; + xcb_window_t icon_window; + int32_t icon_x, icon_y; + xcb_pixmap_t icon_mask; + xcb_window_t window_group; +} xcb_icccm_wm_hints_t; +typedef struct { + uint32_t flags; + int32_t x, y; + int32_t width, height; + int32_t min_width, min_height; + int32_t max_width, max_height; + int32_t width_inc, height_inc; + int32_t min_aspect_num, min_aspect_den; + int32_t max_aspect_num, max_aspect_den; + int32_t base_width, base_height; + uint32_t win_gravity; +} xcb_size_hints_t; +#else +#include +#endif + +class CXWaylandSurface { + public: + wlr_surface* surface = nullptr; + WP resource; + + struct { + CSignal stateChanged; // maximized, fs, minimized, etc. + CSignal metadataChanged; // title, appid + CSignal destroy; + + CSignal resourceChange; // associated / dissociated + + CSignal setGeometry; + CSignal configure; // CBox + + CSignal map; + CSignal unmap; + CSignal commit; + + CSignal activate; + } events; + + struct { + std::string title; + std::string appid; + + // volatile state: is reset after the stateChanged signal fires + std::optional requestsMaximize; + std::optional requestsFullscreen; + std::optional requestsMinimize; + } state; + + uint32_t xID = 0; + uint64_t wlID = 0; + uint64_t wlSerial = 0; + pid_t pid = 0; + CBox geometry; + bool overrideRedirect = false; + bool withdrawn = false; + bool fullscreen = false; + bool maximized = false; + bool minimized = false; + bool mapped = false; + bool modal = false; + + WP parent; + WP self; + std::vector> children; + + UP hints; + UP sizeHints; + std::vector atoms; + std::string role = ""; + bool transient = false; + + bool wantsFocus(); + void configure(const CBox& box); + void activate(bool activate); + void setFullscreen(bool fs); + void setMinimized(bool mz); + void restackToTop(); + void close(); + + private: + CXWaylandSurface(uint32_t xID, CBox geometry, bool OR); + + void ensureListeners(); + void map(); + void unmap(); + void considerMap(); + void setWithdrawn(bool withdrawn); + + DYNLISTENER(surfaceDestroy); + DYNLISTENER(surfaceCommit); + + struct { + CHyprSignalListener destroyResource; + } listeners; + + friend class CXWM; +}; \ No newline at end of file diff --git a/src/xwayland/XWM.cpp b/src/xwayland/XWM.cpp new file mode 100644 index 00000000..9e308314 --- /dev/null +++ b/src/xwayland/XWM.cpp @@ -0,0 +1,1210 @@ +#include "helpers/Vector2D.hpp" +#ifndef NO_XWAYLAND + +#include "XWayland.hpp" +#include "../defines.hpp" +#include +#include "../Compositor.hpp" +#include "../protocols/XWaylandShell.hpp" +#include "../managers/SeatManager.hpp" +#include "../protocols/core/Seat.hpp" +#include +#include +#include +#include + +#include + +#define XCB_EVENT_RESPONSE_TYPE_MASK 0x7f +#define INCR_CHUNK_SIZE (64 * 1024) + +static int onX11Event(int fd, uint32_t mask, void* data) { + return g_pXWayland->pWM->onEvent(fd, mask); +} + +SP CXWM::windowForXID(xcb_window_t wid) { + for (auto& s : surfaces) { + if (s->xID == wid) + return s; + } + + return nullptr; +} + +void CXWM::handleCreate(xcb_create_notify_event_t* e) { + if (isWMWindow(e->window)) + return; + + const auto XSURF = surfaces.emplace_back(SP(new CXWaylandSurface(e->window, CBox{e->x, e->y, e->width, e->height}, e->override_redirect))); + XSURF->self = XSURF; + Debug::log(LOG, "[xwm] New XSurface at {:x} with xid of {}", (uintptr_t)XSURF.get(), e->window); + + const auto WINDOW = CWindow::create(XSURF); + g_pCompositor->m_vWindows.emplace_back(WINDOW); + WINDOW->m_pSelf = WINDOW; + Debug::log(LOG, "[xwm] New XWayland window at {:x} for surf {:x}", (uintptr_t)WINDOW.get(), (uintptr_t)XSURF.get()); +} + +void CXWM::handleDestroy(xcb_destroy_notify_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + XSURF->events.destroy.emit(); + std::erase_if(surfaces, [XSURF](const auto& other) { return XSURF == other; }); +} + +void CXWM::handleConfigure(xcb_configure_request_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + const uint16_t MASK = e->value_mask; + constexpr uint16_t GEOMETRY = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + if (!(MASK & GEOMETRY)) + return; + + XSURF->events.configure.emit(CBox{MASK & XCB_CONFIG_WINDOW_X ? e->x : XSURF->geometry.x, MASK & XCB_CONFIG_WINDOW_Y ? e->y : XSURF->geometry.y, + MASK & XCB_CONFIG_WINDOW_WIDTH ? e->width : XSURF->geometry.width, MASK & XCB_CONFIG_WINDOW_HEIGHT ? e->height : XSURF->geometry.height}); +} + +void CXWM::handleConfigureNotify(xcb_configure_notify_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + if (XSURF->geometry == CBox{e->x, e->y, e->width, e->height}) + return; + + XSURF->geometry = {e->x, e->y, e->width, e->height}; + XSURF->events.setGeometry.emit(); +} + +void CXWM::handleMapRequest(xcb_map_request_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + xcb_map_window(connection, e->window); + + XSURF->restackToTop(); + + const bool SMALL = + XSURF->geometry.size() < Vector2D{2, 2} || (XSURF->sizeHints && XSURF->geometry.size() < Vector2D{XSURF->sizeHints->min_width, XSURF->sizeHints->min_height}); + const bool HAS_HINTS = XSURF->sizeHints && Vector2D{XSURF->sizeHints->base_width, XSURF->sizeHints->base_height} > Vector2D{5, 5}; + const auto DESIREDSIZE = HAS_HINTS ? Vector2D{XSURF->sizeHints->base_width, XSURF->sizeHints->base_height} : Vector2D{800, 800}; + + // if it's too small, or its base size is set, configure it. + if ((SMALL || HAS_HINTS) && !XSURF->overrideRedirect) // default to 800 x 800 + XSURF->configure({XSURF->geometry.pos(), DESIREDSIZE}); + + Debug::log(LOG, "[xwm] Mapping window {} in X (geometry {}x{} at {}x{}))", e->window, XSURF->geometry.width, XSURF->geometry.height, XSURF->geometry.x, XSURF->geometry.y); + + // read data again. Some apps for some reason fail to send WINDOW_TYPE + // this shouldn't happen but does, I prolly fucked up somewhere, this is a band-aid + readWindowData(XSURF); + + XSURF->considerMap(); +} + +void CXWM::handleMapNotify(xcb_map_notify_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF || XSURF->overrideRedirect) + return; + + XSURF->setWithdrawn(false); + sendState(XSURF); + xcb_flush(connection); + + XSURF->considerMap(); +} + +void CXWM::handleUnmapNotify(xcb_unmap_notify_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + XSURF->unmap(); + dissociate(XSURF); + + if (XSURF->overrideRedirect) + return; + + XSURF->setWithdrawn(true); + sendState(XSURF); + xcb_flush(connection); +} + +static bool lookupParentExists(SP XSURF, SP prospectiveChild) { + while (XSURF->parent) { + if (XSURF->parent == prospectiveChild) + return true; + XSURF = XSURF->parent.lock(); + } + + return false; +} + +void CXWM::readProp(SP XSURF, uint32_t atom, xcb_get_property_reply_t* reply) { + std::string propName = "?"; + for (auto& ha : HYPRATOMS) { + if (ha.second != atom) + continue; + + propName = ha.first; + break; + } + + if (atom == XCB_ATOM_WM_CLASS) { + size_t len = xcb_get_property_value_length(reply); + char* string = (char*)xcb_get_property_value(reply); + XSURF->state.appid = std::string{string, len}; + if (std::count(XSURF->state.appid.begin(), XSURF->state.appid.end(), '\000') == 2) + XSURF->state.appid = XSURF->state.appid.substr(XSURF->state.appid.find_first_of('\000') + 1); // fuck you X + if (!XSURF->state.appid.empty()) + XSURF->state.appid.pop_back(); + XSURF->events.metadataChanged.emit(); + } else if (atom == XCB_ATOM_WM_NAME || atom == HYPRATOMS["_NET_WM_NAME"]) { + size_t len = xcb_get_property_value_length(reply); + char* string = (char*)xcb_get_property_value(reply); + XSURF->state.title = std::string{string, len}; + XSURF->events.metadataChanged.emit(); + } else if (atom == HYPRATOMS["_NET_WM_WINDOW_TYPE"]) { + xcb_atom_t* atomsArr = (xcb_atom_t*)xcb_get_property_value(reply); + size_t atomsNo = reply->value_len; + XSURF->atoms.clear(); + for (size_t i = 0; i < atomsNo; ++i) { + XSURF->atoms.push_back(atomsArr[i]); + } + } else if (atom == HYPRATOMS["_NET_WM_STATE"]) { + xcb_atom_t* atoms = (xcb_atom_t*)xcb_get_property_value(reply); + for (uint32_t i = 0; i < reply->value_len; i++) { + if (atoms[i] == HYPRATOMS["_NET_WM_STATE_MODAL"]) + XSURF->modal = true; + } + } else if (atom == HYPRATOMS["WM_HINTS"]) { + if (reply->value_len != 0) { + XSURF->hints = std::make_unique(); + xcb_icccm_get_wm_hints_from_reply(XSURF->hints.get(), reply); + + if (!(XSURF->hints->flags & XCB_ICCCM_WM_HINT_INPUT)) + XSURF->hints->input = true; + } + } else if (atom == HYPRATOMS["WM_WINDOW_ROLE"]) { + size_t len = xcb_get_property_value_length(reply); + + if (len <= 0) + XSURF->role = ""; + else { + XSURF->role = std::string{(char*)xcb_get_property_value(reply), len}; + XSURF->role = XSURF->role.substr(0, XSURF->role.find_first_of('\000')); + } + } else if (atom == XCB_ATOM_WM_TRANSIENT_FOR) { + if (reply->type == XCB_ATOM_WINDOW) { + const auto XID = (xcb_window_t*)xcb_get_property_value(reply); + XSURF->transient = XID; + if (XID) { + if (const auto NEWXSURF = windowForXID(*XID); !lookupParentExists(XSURF, NEWXSURF)) { + XSURF->parent = NEWXSURF; + NEWXSURF->children.push_back(XSURF); + } else + Debug::log(LOG, "[xwm] Denying transient because it would create a loop"); + } + } + } else if (atom == HYPRATOMS["WM_NORMAL_HINTS"]) { + if (reply->type == HYPRATOMS["WM_SIZE_HINTS"] && reply->value_len > 0) { + XSURF->sizeHints = std::make_unique(); + std::memset(XSURF->sizeHints.get(), 0, sizeof(xcb_size_hints_t)); + + xcb_icccm_get_wm_size_hints_from_reply(XSURF->sizeHints.get(), reply); + + const int32_t FLAGS = XSURF->sizeHints->flags; + const bool HASMIN = (FLAGS & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE); + const bool HASBASE = (FLAGS & XCB_ICCCM_SIZE_HINT_BASE_SIZE); + + if (!HASMIN && !HASBASE) { + XSURF->sizeHints->min_width = -1; + XSURF->sizeHints->min_height = -1; + XSURF->sizeHints->base_width = -1; + XSURF->sizeHints->base_height = -1; + } else if (!HASBASE) { + XSURF->sizeHints->base_width = XSURF->sizeHints->min_width; + XSURF->sizeHints->base_height = XSURF->sizeHints->min_height; + } else if (!HASMIN) { + XSURF->sizeHints->min_width = XSURF->sizeHints->base_width; + XSURF->sizeHints->min_height = XSURF->sizeHints->base_height; + } + + if (!(FLAGS & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) { + XSURF->sizeHints->max_width = -1; + XSURF->sizeHints->max_height = -1; + } + } + } else { + Debug::log(LOG, "[xwm] Unhandled prop {} -> {}", atom, propName); + return; + } + + Debug::log(LOG, "[xwm] Handled prop {} -> {}", atom, propName); +} + +void CXWM::handlePropertyNotify(xcb_property_notify_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + xcb_get_property_cookie_t cookie = xcb_get_property(connection, 0, XSURF->xID, e->atom, XCB_ATOM_ANY, 0, 2048); + xcb_get_property_reply_t* reply = xcb_get_property_reply(connection, cookie, nullptr); + if (!reply) { + Debug::log(ERR, "[xwm] Failed to read property notify cookie"); + return; + } + + readProp(XSURF, e->atom, reply); + + free(reply); +} + +void CXWM::handleClientMessage(xcb_client_message_event_t* e) { + const auto XSURF = windowForXID(e->window); + + if (!XSURF) + return; + + std::string propName = "?"; + for (auto& ha : HYPRATOMS) { + if (ha.second != e->type) + continue; + + propName = ha.first; + break; + } + + if (e->type == HYPRATOMS["WL_SURFACE_ID"]) { + if (XSURF->surface) { + Debug::log(WARN, "[xwm] Re-assignment of WL_SURFACE_ID"); + dissociate(XSURF); + } + + auto id = e->data.data32[0]; + auto resource = wl_client_get_object(g_pXWayland->pServer->xwaylandClient, id); + if (resource) { + auto wlrSurface = wlr_surface_from_resource(resource); + associate(XSURF, wlrSurface); + } + } else if (e->type == HYPRATOMS["WL_SURFACE_SERIAL"]) { + if (XSURF->wlSerial) { + Debug::log(WARN, "[xwm] Re-assignment of WL_SURFACE_SERIAL"); + dissociate(XSURF); + } + + uint32_t serialLow = e->data.data32[0]; + uint32_t serialHigh = e->data.data32[1]; + XSURF->wlSerial = ((uint64_t)serialHigh << 32) | serialLow; + + Debug::log(LOG, "[xwm] surface {:x} requests serial {:x}", (uintptr_t)XSURF.get(), XSURF->wlSerial); + + for (auto& res : shellResources) { + if (!res) + continue; + + if (res->serial != XSURF->wlSerial || !XSURF->wlSerial) + continue; + + associate(XSURF, res->surface); + break; + } + + } else if (e->type == HYPRATOMS["_NET_WM_STATE"]) { + if (e->format == 32) { + uint32_t action = e->data.data32[0]; + for (size_t i = 0; i < 2; ++i) { + xcb_atom_t prop = e->data.data32[1 + i]; + + auto updateState = [XSURF](int action, bool current) -> bool { + switch (action) { + case 0: + /* remove */ + return false; + case 1: + /* add */ + return true; + case 2: + /* toggle */ + return !current; + default: return false; + } + return false; + }; + + if (prop == HYPRATOMS["_NET_WM_STATE_FULLSCREEN"]) + XSURF->state.requestsFullscreen = updateState(action, XSURF->fullscreen); + } + + XSURF->events.stateChanged.emit(); + } + } else if (e->type == HYPRATOMS["_NET_ACTIVE_WINDOW"]) { + XSURF->events.activate.emit(); + } else { + Debug::log(LOG, "[xwm] Unhandled message prop {} -> {}", e->type, propName); + return; + } + + Debug::log(LOG, "[xwm] Handled message prop {} -> {}", e->type, propName); +} + +void CXWM::handleFocusIn(xcb_focus_in_event_t* e) { + if (e->mode == XCB_NOTIFY_MODE_GRAB || e->mode == XCB_NOTIFY_MODE_UNGRAB || e->detail == XCB_NOTIFY_DETAIL_POINTER) + return; + + const auto XSURF = windowForXID(e->event); + + if (!XSURF) + return; + + if (focusedSurface && focusedSurface->pid == XSURF->pid && e->sequence - lastFocusSeq <= 255) + focusWindow(XSURF); + else + focusWindow(focusedSurface.lock()); +} + +void CXWM::sendWMMessage(SP surf, xcb_client_message_data_t* data, uint32_t mask) { + xcb_client_message_event_t event = { + .response_type = XCB_CLIENT_MESSAGE, + .format = 32, + .sequence = 0, + .window = surf->xID, + .type = HYPRATOMS["WM_PROTOCOLS"], + .data = *data, + }; + + xcb_send_event(connection, 0, surf->xID, mask, (const char*)&event); + xcb_flush(connection); +} + +void CXWM::focusWindow(SP surf) { + if (surf == focusedSurface) + return; + + auto oldSurf = focusedSurface.lock(); + focusedSurface = surf; + + if (oldSurf) + sendState(oldSurf); + + if (!surf) { + xcb_set_input_focus_checked(connection, XCB_INPUT_FOCUS_POINTER_ROOT, XCB_NONE, XCB_CURRENT_TIME); + return; + } + + if (surf->overrideRedirect) + return; + + xcb_client_message_data_t msg = {0}; + msg.data32[0] = HYPRATOMS["WM_TAKE_FOCUS"]; + msg.data32[1] = XCB_TIME_CURRENT_TIME; + + if (surf->hints && !surf->hints->input) + sendWMMessage(surf, &msg, XCB_EVENT_MASK_NO_EVENT); + else { + sendWMMessage(surf, &msg, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT); + + xcb_void_cookie_t cookie = xcb_set_input_focus(connection, XCB_INPUT_FOCUS_POINTER_ROOT, surf->xID, XCB_CURRENT_TIME); + lastFocusSeq = cookie.sequence; + } +} + +void CXWM::handleError(xcb_value_error_t* e) { + const char* major_name = xcb_errors_get_name_for_major_code(errors, e->major_opcode); + if (!major_name) { + Debug::log(ERR, "xcb error happened, but could not get major name"); + return; + } + + const char* minor_name = xcb_errors_get_name_for_minor_code(errors, e->major_opcode, e->minor_opcode); + + const char* extension; + const char* error_name = xcb_errors_get_name_for_error(errors, e->error_code, &extension); + if (!error_name) { + Debug::log(ERR, "xcb error happened, but could not get error name"); + return; + } + + Debug::log(ERR, "[xwm] xcb error: {} ({}), code {} ({}), seq {}, val {}", major_name, minor_name ? minor_name : "no minor", error_name, extension ? extension : "no extension", + e->sequence, e->bad_value); +} + +void CXWM::selectionSendNotify(xcb_selection_request_event_t* e, bool success) { + xcb_selection_notify_event_t selection_notify = { + .response_type = XCB_SELECTION_NOTIFY, + .sequence = 0, + .time = e->time, + .requestor = e->requestor, + .selection = e->selection, + .target = e->target, + .property = success ? e->property : (uint32_t)XCB_ATOM_NONE, + }; + + xcb_send_event(connection, 0, e->requestor, XCB_EVENT_MASK_NO_EVENT, (const char*)&selection_notify); + xcb_flush(connection); +} + +xcb_atom_t CXWM::mimeToAtom(const std::string& mime) { + if (mime == "text/plain;charset=utf-8") + return HYPRATOMS["UTF8_STRING"]; + if (mime == "text/plain") + return HYPRATOMS["TEXT"]; + + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(connection, 0, mime.length(), mime.c_str()); + xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(connection, cookie, nullptr); + if (!reply) + return XCB_ATOM_NONE; + xcb_atom_t atom = reply->atom; + free(reply); + return atom; +} + +std::string CXWM::mimeFromAtom(xcb_atom_t atom) { + if (atom == HYPRATOMS["UTF8_STRING"]) + return "text/plain;charset=utf-8"; + if (atom == HYPRATOMS["TEXT"]) + return "text/plain"; + + xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name(connection, atom); + xcb_get_atom_name_reply_t* reply = xcb_get_atom_name_reply(connection, cookie, nullptr); + if (!reply) + return "INVALID"; + size_t len = xcb_get_atom_name_name_length(reply); + char* buf = xcb_get_atom_name_name(reply); // not a C string + std::string SZNAME{buf, len}; + free(reply); + return SZNAME; +} + +void CXWM::handleSelectionNotify(xcb_selection_notify_event_t* e) { + Debug::log(LOG, "[xwm] Selection notify for {} prop {} target {}", e->selection, e->property, e->target); + + SXSelection& sel = clipboard; + + if (e->property == XCB_ATOM_NONE) { + if (sel.transfer) { + Debug::log(ERR, "[xwm] converting selection failed"); + sel.transfer.reset(); + } + } else if (e->target == HYPRATOMS["TARGETS"]) { + if (!focusedSurface) { + Debug::log(LOG, "[xwm] denying access to write to clipboard because no X client is in focus"); + return; + } + + setClipboardToWayland(sel); + } else if (sel.transfer) + getTransferData(sel); +} + +bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) { + // Debug::log(LOG, "[xwm] Selection property notify for {} target {}", e->atom, e->window); + + // Debug::log(ERR, "[xwm] FIXME: CXWM::handleSelectionPropertyNotify stub"); + + return true; +} + +void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { + Debug::log(LOG, "[xwm] Selection request for {} prop {} target {} time {} requestor {} selection {}", e->selection, e->property, e->target, e->time, e->requestor, + e->selection); + + SXSelection& sel = clipboard; + + if (!g_pSeatManager->selection.currentSelection) { + Debug::log(ERR, "[xwm] No selection"); + selectionSendNotify(e, false); + return; + } + + if (e->selection == HYPRATOMS["CLIPBOARD_MANAGER"]) { + selectionSendNotify(e, true); + return; + } + + if (sel.window != e->owner && e->time != XCB_CURRENT_TIME && e->time < sel.timestamp) { + Debug::log(ERR, "[xwm] outdated selection request. Time {} < {}", e->time, sel.timestamp); + selectionSendNotify(e, false); + return; + } + + if (!g_pSeatManager->state.keyboardFocusResource || g_pSeatManager->state.keyboardFocusResource->client() != g_pXWayland->pServer->xwaylandClient) { + Debug::log(LOG, "[xwm] Ignoring clipboard access: xwayland not in focus"); + selectionSendNotify(e, false); + return; + } + + if (e->target == HYPRATOMS["TARGETS"]) { + // send mime types + auto mimes = g_pSeatManager->selection.currentSelection->mimes(); + + std::vector atoms; + atoms.push_back(HYPRATOMS["TIMESTAMP"]); + atoms.push_back(HYPRATOMS["TARGETS"]); + + for (auto& m : mimes) { + atoms.push_back(mimeToAtom(m)); + } + + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, e->requestor, e->property, XCB_ATOM_ATOM, 32, atoms.size(), atoms.data()); + selectionSendNotify(e, true); + } else if (e->target == HYPRATOMS["TIMESTAMP"]) { + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, e->requestor, e->property, XCB_ATOM_INTEGER, 32, 1, &sel.timestamp); + selectionSendNotify(e, true); + } else if (e->target == HYPRATOMS["DELETE"]) { + selectionSendNotify(e, true); + } else { + + std::string mime = mimeFromAtom(e->target); + + if (mime == "INVALID") { + Debug::log(LOG, "[xwm] Ignoring clipboard access: invalid mime atom {}", e->target); + selectionSendNotify(e, false); + return; + } + + if (!sel.sendData(e, mime)) { + Debug::log(LOG, "[xwm] Failed to send selection :("); + selectionSendNotify(e, false); + return; + } + } +} + +bool CXWM::handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e) { + Debug::log(LOG, "[xwm] Selection xfixes notify for {}", e->selection); + + // IMPORTANT: mind the g_pSeatManager below + SXSelection& sel = clipboard; + + if (e->owner == XCB_WINDOW_NONE) { + if (sel.owner != sel.window) + g_pSeatManager->setCurrentSelection(nullptr); + + sel.owner = 0; + return true; + } + + sel.owner = e->owner; + + if (sel.owner == sel.window) { + sel.timestamp = e->timestamp; + return true; + } + + xcb_convert_selection(connection, sel.window, HYPRATOMS["CLIPBOARD"], HYPRATOMS["TARGETS"], HYPRATOMS["_WL_SELECTION"], e->timestamp); + xcb_flush(connection); + + return true; +} + +bool CXWM::handleSelectionEvent(xcb_generic_event_t* e) { + switch (e->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { + case XCB_SELECTION_NOTIFY: { + handleSelectionNotify((xcb_selection_notify_event_t*)e); + return true; + } + case XCB_PROPERTY_NOTIFY: { + return handleSelectionPropertyNotify((xcb_property_notify_event_t*)e); + } + case XCB_SELECTION_REQUEST: { + handleSelectionRequest((xcb_selection_request_event_t*)e); + return true; + } + } + + if (e->response_type - xfixes->first_event == XCB_XFIXES_SELECTION_NOTIFY) + return handleSelectionXFixesNotify((xcb_xfixes_selection_notify_event_t*)e); + + return 0; +} + +int CXWM::onEvent(int fd, uint32_t mask) { + + if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { + Debug::log(ERR, "XWayland has yeeten the xwm off?!"); + Debug::log(CRIT, "XWayland has yeeten the xwm off?!"); + g_pXWayland->pWM.reset(); + g_pXWayland->pServer.reset(); + return 0; + } + + int count = 0; + + while (42069) { + xcb_generic_event_t* event = xcb_poll_for_event(connection); + if (!event) + break; + + count++; + + if (handleSelectionEvent(event)) { + free(event); + continue; + } + + switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) { + case XCB_CREATE_NOTIFY: handleCreate((xcb_create_notify_event_t*)event); break; + case XCB_DESTROY_NOTIFY: handleDestroy((xcb_destroy_notify_event_t*)event); break; + case XCB_CONFIGURE_REQUEST: handleConfigure((xcb_configure_request_event_t*)event); break; + case XCB_CONFIGURE_NOTIFY: handleConfigureNotify((xcb_configure_notify_event_t*)event); break; + case XCB_MAP_REQUEST: handleMapRequest((xcb_map_request_event_t*)event); break; + case XCB_MAP_NOTIFY: handleMapNotify((xcb_map_notify_event_t*)event); break; + case XCB_UNMAP_NOTIFY: handleUnmapNotify((xcb_unmap_notify_event_t*)event); break; + case XCB_PROPERTY_NOTIFY: handlePropertyNotify((xcb_property_notify_event_t*)event); break; + case XCB_CLIENT_MESSAGE: handleClientMessage((xcb_client_message_event_t*)event); break; + case XCB_FOCUS_IN: handleFocusIn((xcb_focus_in_event_t*)event); break; + case 0: handleError((xcb_value_error_t*)event); break; + default: { + ; + } + } + free(event); + } + + if (count) + xcb_flush(connection); + + return count; +} + +void CXWM::gatherResources() { + xcb_prefetch_extension_data(connection, &xcb_xfixes_id); + xcb_prefetch_extension_data(connection, &xcb_composite_id); + xcb_prefetch_extension_data(connection, &xcb_res_id); + + for (auto& ATOM : HYPRATOMS) { + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(connection, 0, ATOM.first.length(), ATOM.first.c_str()); + xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(connection, cookie, nullptr); + + if (!reply) { + Debug::log(ERR, "[xwm] Atom failed: {}", ATOM.first); + continue; + } + + ATOM.second = reply->atom; + free(reply); + } + + xfixes = xcb_get_extension_data(connection, &xcb_xfixes_id); + + if (!xfixes || !xfixes->present) + Debug::log(WARN, "XFixes not available"); + + xcb_xfixes_query_version_cookie_t xfixes_cookie; + xcb_xfixes_query_version_reply_t* xfixes_reply; + xfixes_cookie = xcb_xfixes_query_version(connection, XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION); + xfixes_reply = xcb_xfixes_query_version_reply(connection, xfixes_cookie, NULL); + + Debug::log(LOG, "xfixes version: {}.{}", xfixes_reply->major_version, xfixes_reply->minor_version); + xfixesMajor = xfixes_reply->major_version; + + free(xfixes_reply); + + const xcb_query_extension_reply_t* xresReply1 = xcb_get_extension_data(connection, &xcb_res_id); + if (!xresReply1 || !xresReply1->present) + return; + + xcb_res_query_version_cookie_t xres_cookie = xcb_res_query_version(connection, XCB_RES_MAJOR_VERSION, XCB_RES_MINOR_VERSION); + xcb_res_query_version_reply_t* xres_reply = xcb_res_query_version_reply(connection, xres_cookie, NULL); + if (xres_reply == NULL) + return; + + Debug::log(LOG, "xres version: {}.{}", xres_reply->server_major, xres_reply->server_minor); + if (xres_reply->server_major > 1 || (xres_reply->server_major == 1 && xres_reply->server_minor >= 2)) { + xres = xresReply1; + } + free(xres_reply); +} + +void CXWM::getVisual() { + xcb_depth_iterator_t d_iter; + xcb_visualtype_iterator_t vt_iter; + xcb_visualtype_t* visualtype; + + d_iter = xcb_screen_allowed_depths_iterator(screen); + visualtype = NULL; + while (d_iter.rem > 0) { + if (d_iter.data->depth == 32) { + vt_iter = xcb_depth_visuals_iterator(d_iter.data); + visualtype = vt_iter.data; + break; + } + + xcb_depth_next(&d_iter); + } + + if (visualtype == NULL) { + wlr_log(WLR_DEBUG, "No 32 bit visualtype\n"); + return; + } + + visual_id = visualtype->visual_id; + colormap = xcb_generate_id(connection); + xcb_create_colormap(connection, XCB_COLORMAP_ALLOC_NONE, colormap, screen->root, visual_id); +} + +void CXWM::getRenderFormat() { + xcb_render_query_pict_formats_cookie_t cookie = xcb_render_query_pict_formats(connection); + xcb_render_query_pict_formats_reply_t* reply = xcb_render_query_pict_formats_reply(connection, cookie, NULL); + if (!reply) { + wlr_log(WLR_ERROR, "Did not get any reply from xcb_render_query_pict_formats"); + return; + } + xcb_render_pictforminfo_iterator_t iter = xcb_render_query_pict_formats_formats_iterator(reply); + xcb_render_pictforminfo_t* format = NULL; + while (iter.rem > 0) { + if (iter.data->depth == 32) { + format = iter.data; + break; + } + + xcb_render_pictforminfo_next(&iter); + } + + if (format == NULL) { + wlr_log(WLR_DEBUG, "No 32 bit render format"); + free(reply); + return; + } + + render_format_id = format->id; + free(reply); +} + +CXWM::CXWM() { + connection = xcb_connect_to_fd(g_pXWayland->pServer->xwmFDs[0], nullptr); + + if (int ret = xcb_connection_has_error(connection); ret) { + Debug::log(ERR, "[xwm] Couldn't start, error {}", ret); + return; + } + + if (xcb_errors_context_new(connection, &errors)) { + Debug::log(ERR, "[xwm] Couldn't allocate errors context"); + return; + } + + xcb_screen_iterator_t screen_iterator = xcb_setup_roots_iterator(xcb_get_setup(connection)); + screen = screen_iterator.data; + + eventSource = wl_event_loop_add_fd(g_pCompositor->m_sWLEventLoop, g_pXWayland->pServer->xwmFDs[0], WL_EVENT_READABLE, ::onX11Event, nullptr); + wl_event_source_check(eventSource); + + gatherResources(); + getVisual(); + getRenderFormat(); + + uint32_t values[] = { + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_PROPERTY_CHANGE, + }; + xcb_change_window_attributes(connection, screen->root, XCB_CW_EVENT_MASK, values); + + xcb_composite_redirect_subwindows(connection, screen->root, XCB_COMPOSITE_REDIRECT_MANUAL); + + xcb_atom_t supported[] = { + HYPRATOMS["_NET_WM_STATE"], HYPRATOMS["_NET_ACTIVE_WINDOW"], HYPRATOMS["_NET_WM_MOVERESIZE"], HYPRATOMS["_NET_WM_STATE_FOCUSED"], + HYPRATOMS["_NET_WM_STATE_MODAL"], HYPRATOMS["_NET_WM_STATE_FULLSCREEN"], HYPRATOMS["_NET_WM_STATE_MAXIMIZED_VERT"], HYPRATOMS["_NET_WM_STATE_MAXIMIZED_HORZ"], + HYPRATOMS["_NET_WM_STATE_HIDDEN"], HYPRATOMS["_NET_CLIENT_LIST"], HYPRATOMS["_NET_CLIENT_LIST_STACKING"], + }; + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, HYPRATOMS["_NET_SUPPORTED"], XCB_ATOM_ATOM, 32, sizeof(supported) / sizeof(*supported), supported); + + xcb_flush(connection); + + setActiveWindow(XCB_WINDOW_NONE); + + initSelection(); + + hyprListener_newSurface.initCallback( + &g_pCompositor->m_sWLRCompositor->events.new_surface, [this](void* owner, void* data) { onNewSurface((wlr_surface*)data); }, nullptr, "XWM"); + + listeners.newXShellSurface = PROTO::xwaylandShell->events.newSurface.registerListener([this](std::any d) { onNewResource(std::any_cast>(d)); }); + + createWMWindow(); + + xcb_flush(connection); +} + +void CXWM::setActiveWindow(xcb_window_t window) { + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, HYPRATOMS["_NET_ACTIVE_WINDOW"], HYPRATOMS["WINDOW"], 32, 1, &window); +} + +void CXWM::createWMWindow() { + constexpr const char* wmName = "Hyprland :D"; + wmWindow = xcb_generate_id(connection); + xcb_create_window(connection, XCB_COPY_FROM_PARENT, wmWindow, screen->root, 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, 0, NULL); + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, wmWindow, HYPRATOMS["_NET_WM_NAME"], HYPRATOMS["UTF8_STRING"], + 8, // format + strlen(wmName), wmName); + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, HYPRATOMS["_NET_SUPPORTING_WM_CHECK"], XCB_ATOM_WINDOW, + 32, // format + 1, &wmWindow); + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, wmWindow, HYPRATOMS["_NET_SUPPORTING_WM_CHECK"], XCB_ATOM_WINDOW, + 32, // format + 1, &wmWindow); + xcb_set_selection_owner(connection, wmWindow, HYPRATOMS["WM_S0"], XCB_CURRENT_TIME); + xcb_set_selection_owner(connection, wmWindow, HYPRATOMS["_NET_WM_CM_S0"], XCB_CURRENT_TIME); +} + +void CXWM::activateSurface(SP surf) { + if (surf == focusedSurface || (surf && surf->overrideRedirect)) + return; + + setActiveWindow(surf ? surf->xID : (uint32_t)XCB_WINDOW_NONE); + + focusWindow(surf); + + xcb_flush(connection); +} + +void CXWM::sendState(SP surf) { + if (surf->withdrawn) { + xcb_delete_property(connection, surf->xID, HYPRATOMS["_NET_WM_STATE"]); + return; + } + + std::vector props; + if (surf->modal) + props.push_back(HYPRATOMS["_NET_WM_STATE_MODAL"]); + if (surf->fullscreen) + props.push_back(HYPRATOMS["_NET_WM_STATE_FULLSCREEN"]); + if (surf->maximized) { + props.push_back(HYPRATOMS["NET_WM_STATE_MAXIMIZED_VERT"]); + props.push_back(HYPRATOMS["NET_WM_STATE_MAXIMIZED_HORZ"]); + } + if (surf->minimized) + props.push_back(HYPRATOMS["_NET_WM_STATE_HIDDEN"]); + if (surf == focusedSurface) + props.push_back(HYPRATOMS["_NET_WM_STATE_FOCUSED"]); + + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, surf->xID, HYPRATOMS["_NET_WM_STATE"], XCB_ATOM_ATOM, 32, props.size(), props.data()); +} + +void CXWM::onNewSurface(wlr_surface* surf) { + if (wl_resource_get_client(surf->resource) != g_pXWayland->pServer->xwaylandClient) + return; + + Debug::log(LOG, "[xwm] New XWayland surface at {:x}", (uintptr_t)surf); + + const auto WLID = wl_resource_get_id(surf->resource); + + for (auto& sr : surfaces) { + if (sr->surface || sr->wlID != WLID) + continue; + + associate(sr, surf); + return; + } + + Debug::log(WARN, "[xwm] CXWM::onNewSurface: no matching xwaylandSurface"); +} + +void CXWM::onNewResource(SP resource) { + Debug::log(LOG, "[xwm] New XWayland resource at {:x}", (uintptr_t)resource.get()); + + std::erase_if(shellResources, [](const auto& e) { return e.expired(); }); + shellResources.push_back(resource); + + for (auto& surf : surfaces) { + if (surf->resource || surf->wlSerial != resource->serial) + continue; + + associate(surf, resource->surface); + break; + } +} + +void CXWM::readWindowData(SP surf) { + const std::array interestingProps = { + XCB_ATOM_WM_CLASS, XCB_ATOM_WM_NAME, XCB_ATOM_WM_TRANSIENT_FOR, HYPRATOMS["WM_HINTS"], + HYPRATOMS["_NET_WM_STATE"], HYPRATOMS["_NET_WM_NAME"], HYPRATOMS["_NET_WM_WINDOW_TYPE"], HYPRATOMS["WM_NORMAL_HINTS"], + }; + + for (size_t i = 0; i < interestingProps.size(); i++) { + xcb_get_property_cookie_t cookie = xcb_get_property(connection, 0, surf->xID, interestingProps.at(i), XCB_ATOM_ANY, 0, 2048); + xcb_get_property_reply_t* reply = xcb_get_property_reply(connection, cookie, nullptr); + if (!reply) { + Debug::log(ERR, "[xwm] Failed to get window property"); + continue; + } + readProp(surf, interestingProps.at(i), reply); + free(reply); + } +} + +void CXWM::associate(SP surf, wlr_surface* wlSurf) { + if (surf->surface) + return; + + auto existing = std::find_if(surfaces.begin(), surfaces.end(), [wlSurf](const auto& e) { return e->surface == wlSurf; }); + + if (existing != surfaces.end()) { + Debug::log(WARN, "[xwm] associate() called but surface is already associated to {:x}, ignoring...", (uintptr_t)surf.get()); + return; + } + + surf->surface = wlSurf; + surf->ensureListeners(); + + readWindowData(surf); + + surf->events.resourceChange.emit(); +} + +void CXWM::dissociate(SP surf) { + if (!surf->surface) + return; + + if (surf->mapped) + surf->unmap(); + + surf->surface = nullptr; + surf->events.resourceChange.emit(); + + Debug::log(LOG, "Dissociate for {:x}", (uintptr_t)surf.get()); +} + +void CXWM::updateClientList() { + std::erase_if(mappedSurfaces, [](const auto& e) { return e.expired() || !e->mapped; }); + std::erase_if(mappedSurfacesStacking, [](const auto& e) { return e.expired() || !e->mapped; }); + + std::vector windows; + for (auto& m : mappedSurfaces) { + windows.push_back(m->xID); + } + + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, HYPRATOMS["_NET_CLIENT_LIST"], XCB_ATOM_WINDOW, 32, windows.size(), windows.data()); + + windows.clear(); + + for (auto& m : mappedSurfacesStacking) { + windows.push_back(m->xID); + } + + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, HYPRATOMS["_NET_CLIENT_LIST_STACKING"], XCB_ATOM_WINDOW, 32, windows.size(), windows.data()); +} + +bool CXWM::isWMWindow(xcb_window_t w) { + return w == wmWindow || w == clipboard.window; +} + +void CXWM::initSelection() { + clipboard.window = xcb_generate_id(connection); + uint32_t mask[1] = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE}; + xcb_create_window(connection, XCB_COPY_FROM_PARENT, clipboard.window, screen->root, 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, XCB_CW_EVENT_MASK, + mask); + xcb_set_selection_owner(connection, clipboard.window, HYPRATOMS["CLIPBOARD_MANAGER"], XCB_TIME_CURRENT_TIME); + + uint32_t mask2 = + XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE; + xcb_xfixes_select_selection_input(connection, clipboard.window, HYPRATOMS["CLIPBOARD"], mask2); + + clipboard.listeners.setSelection = g_pSeatManager->events.setSelection.registerListener([this](std::any d) { clipboard.onSelection(); }); +} + +void CXWM::setClipboardToWayland(SXSelection& sel) { + sel.dataSource = makeShared(sel); + if (sel.dataSource->mimes().empty()) { + Debug::log(ERR, "[xwm] can't set clipboard: no MIMEs"); + sel.dataSource.reset(); + } + + if (sel.dataSource) { + Debug::log(LOG, "[xwm] X clipboard at {:x} takes clipboard", (uintptr_t)sel.dataSource.get()); + g_pSeatManager->setCurrentSelection(sel.dataSource); + } +} + +void CXWM::getTransferData(SXSelection& sel) { + Debug::log(LOG, "[xwm] getTransferData"); + + sel.transfer->getIncomingSelectionProp(true); + + if (sel.transfer->propertyReply->type == HYPRATOMS["INCR"]) { + Debug::log(ERR, "[xwm] Transfer is INCR, which we don't support :("); + close(sel.transfer->wlFD); + sel.transfer.reset(); + return; + } else { + char* property = (char*)xcb_get_property_value(sel.transfer->propertyReply); + int remainder = xcb_get_property_value_length(sel.transfer->propertyReply) - sel.transfer->propertyStart; + + ssize_t len = write(sel.transfer->wlFD, property + sel.transfer->propertyStart, remainder); + if (len == -1) { + Debug::log(ERR, "[xwm] write died in transfer get"); + close(sel.transfer->wlFD); + sel.transfer.reset(); + return; + } + + if (len < remainder) { + sel.transfer->propertyStart += len; + Debug::log(ERR, "[xwm] wl client read partially: len {}", len); + return; + } else { + Debug::log(LOG, "[xwm] cb transfer to wl client complete, read {} bytes", len); + close(sel.transfer->wlFD); + sel.transfer.reset(); + } + } +} + +void CXWM::setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot) { + if (!render_format_id) { + Debug::log(ERR, "[xwm] can't set cursor: no render format"); + return; + } + + if (cursorXID) + xcb_free_cursor(connection, cursorXID); + + constexpr int CURSOR_DEPTH = 32; + + xcb_pixmap_t pix = xcb_generate_id(connection); + xcb_create_pixmap(connection, CURSOR_DEPTH, pix, screen->root, size.x, size.y); + + xcb_render_picture_t pic = xcb_generate_id(connection); + xcb_render_create_picture(connection, pic, pix, render_format_id, 0, 0); + + xcb_gcontext_t gc = xcb_generate_id(connection); + xcb_create_gc(connection, gc, pix, 0, NULL); + + xcb_put_image(connection, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, size.x, size.y, 0, 0, 0, CURSOR_DEPTH, stride * size.y * sizeof(uint8_t), pixData); + xcb_free_gc(connection, gc); + + cursorXID = xcb_generate_id(connection); + xcb_render_create_cursor(connection, cursorXID, pic, hotspot.x, hotspot.y); + xcb_free_pixmap(connection, pix); + xcb_render_free_picture(connection, pic); + + uint32_t values[] = {cursorXID}; + xcb_change_window_attributes(connection, screen->root, XCB_CW_CURSOR, values); + xcb_flush(connection); +} + +void SXSelection::onSelection() { + if (g_pSeatManager->selection.currentSelection && g_pSeatManager->selection.currentSelection->type() == DATA_SOURCE_TYPE_X11) + return; + + if (g_pSeatManager->selection.currentSelection) { + xcb_set_selection_owner(g_pXWayland->pWM->connection, g_pXWayland->pWM->clipboard.window, HYPRATOMS["CLIPBOARD"], XCB_TIME_CURRENT_TIME); + xcb_flush(g_pXWayland->pWM->connection); + } +} + +int SXSelection::onRead(int fd, uint32_t mask) { + // TODO: support INCR + + size_t pre = transfer->data.size(); + transfer->data.resize(INCR_CHUNK_SIZE + pre); + + auto len = read(fd, transfer->data.data() + pre, INCR_CHUNK_SIZE - 1); + if (len < 0) { + Debug::log(ERR, "[xwm] readDataSource died"); + g_pXWayland->pWM->selectionSendNotify(&transfer->request, false); + transfer.reset(); + return 0; + } + + transfer->data.resize(pre + len); + + if (len == 0) { + Debug::log(LOG, "[xwm] Received all the bytes, final length {}", transfer->data.size()); + xcb_change_property(g_pXWayland->pWM->connection, XCB_PROP_MODE_REPLACE, transfer->request.requestor, transfer->request.property, transfer->request.target, 8, + transfer->data.size(), transfer->data.data()); + xcb_flush(g_pXWayland->pWM->connection); + g_pXWayland->pWM->selectionSendNotify(&transfer->request, true); + transfer.reset(); + } else + Debug::log(LOG, "[xwm] Received {} bytes, waiting...", len); + + return 1; +} + +static int readDataSource(int fd, uint32_t mask, void* data) { + Debug::log(LOG, "[xwm] readDataSource on fd {}", fd); + + auto selection = (SXSelection*)data; + + return selection->onRead(fd, mask); +} + +bool SXSelection::sendData(xcb_selection_request_event_t* e, std::string mime) { + WP selection = g_pSeatManager->selection.currentSelection; + + if (!selection) + return false; + + const auto MIMES = selection->mimes(); + + if (MIMES.empty()) + return false; + + if (std::find(MIMES.begin(), MIMES.end(), mime) == MIMES.end()) { + Debug::log(ERR, "[xwm] X Client asked for an invalid MIME, sending the first advertised. THIS SHIT MAY BREAK!"); + mime = *MIMES.begin(); + } + + transfer = std::make_unique(*this); + transfer->request = *e; + + int p[2]; + if (pipe(p) == -1) { + Debug::log(ERR, "[xwm] selection: pipe() failed"); + return false; + } + + fcntl(p[0], F_SETFD, FD_CLOEXEC); + fcntl(p[0], F_SETFL, O_NONBLOCK); + fcntl(p[1], F_SETFD, FD_CLOEXEC); + fcntl(p[1], F_SETFL, O_NONBLOCK); + + transfer->wlFD = p[0]; + + Debug::log(LOG, "[xwm] sending wayland selection to xwayland with mime {}, target {}, fds {} {}", mime, e->target, p[0], p[1]); + + selection->send(mime, p[1]); + + transfer->eventSource = wl_event_loop_add_fd(g_pCompositor->m_sWLEventLoop, transfer->wlFD, WL_EVENT_READABLE, ::readDataSource, this); + + return true; +} + +SXTransfer::~SXTransfer() { + if (wlFD) + close(wlFD); + if (eventSource) + wl_event_source_remove(eventSource); + if (incomingWindow) + xcb_destroy_window(g_pXWayland->pWM->connection, incomingWindow); + if (propertyReply) + free(propertyReply); +} + +bool SXTransfer::getIncomingSelectionProp(bool erase) { + xcb_get_property_cookie_t cookie = xcb_get_property(g_pXWayland->pWM->connection, erase, incomingWindow, HYPRATOMS["_WL_SELECTION"], XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff); + + propertyStart = 0; + propertyReply = xcb_get_property_reply(g_pXWayland->pWM->connection, cookie, nullptr); + + if (!propertyReply) { + Debug::log(ERR, "[SXTransfer] couldn't get a prop reply"); + return false; + } + + return true; +} + +#endif diff --git a/src/xwayland/XWM.hpp b/src/xwayland/XWM.hpp new file mode 100644 index 00000000..b312f4a9 --- /dev/null +++ b/src/xwayland/XWM.hpp @@ -0,0 +1,160 @@ +#pragma once + +#include "../helpers/signal/Listener.hpp" +#include "../helpers/WLListener.hpp" +#include "../macros.hpp" + +#include "XDataSource.hpp" + +#include +#include +#include +#include +#include + +struct wl_event_source; +class CXWaylandSurfaceResource; +struct SXSelection; + +struct SXTransfer { + ~SXTransfer(); + + SXSelection& selection; + bool out = true; + + bool incremental = false; + bool flushOnDelete = false; + bool propertySet = false; + + int wlFD = -1; + wl_event_source* eventSource = nullptr; + + std::vector data; + + xcb_selection_request_event_t request; + + int propertyStart; + xcb_get_property_reply_t* propertyReply; + xcb_window_t incomingWindow; + + bool getIncomingSelectionProp(bool erase); +}; + +struct SXSelection { + xcb_window_t window = 0; + xcb_window_t owner = 0; + xcb_timestamp_t timestamp = 0; + SP dataSource; + + void onSelection(); + bool sendData(xcb_selection_request_event_t* e, std::string mime); + int onRead(int fd, uint32_t mask); + + struct { + CHyprSignalListener setSelection; + } listeners; + + std::unique_ptr transfer; +}; + +class CXWM { + public: + CXWM(); + + int onEvent(int fd, uint32_t mask); + + private: + void setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot); + + void gatherResources(); + void getVisual(); + void getRenderFormat(); + void createWMWindow(); + void initSelection(); + + void onNewSurface(wlr_surface* surf); + void onNewResource(SP resource); + + void setActiveWindow(xcb_window_t window); + void sendState(SP surf); + void focusWindow(SP surf); + void activateSurface(SP surf); + bool isWMWindow(xcb_window_t w); + + void sendWMMessage(SP surf, xcb_client_message_data_t* data, uint32_t mask); + + SP windowForXID(xcb_window_t wid); + + void readWindowData(SP surf); + void associate(SP surf, wlr_surface* wlSurf); + void dissociate(SP surf); + + void updateClientList(); + + // event handlers + void handleCreate(xcb_create_notify_event_t* e); + void handleDestroy(xcb_destroy_notify_event_t* e); + void handleConfigure(xcb_configure_request_event_t* e); + void handleConfigureNotify(xcb_configure_notify_event_t* e); + void handleMapRequest(xcb_map_request_event_t* e); + void handleMapNotify(xcb_map_notify_event_t* e); + void handleUnmapNotify(xcb_unmap_notify_event_t* e); + void handlePropertyNotify(xcb_property_notify_event_t* e); + void handleClientMessage(xcb_client_message_event_t* e); + void handleFocusIn(xcb_focus_in_event_t* e); + void handleError(xcb_value_error_t* e); + + bool handleSelectionEvent(xcb_generic_event_t* e); + void handleSelectionNotify(xcb_selection_notify_event_t* e); + bool handleSelectionPropertyNotify(xcb_property_notify_event_t* e); + void handleSelectionRequest(xcb_selection_request_event_t* e); + bool handleSelectionXFixesNotify(xcb_xfixes_selection_notify_event_t* e); + + void selectionSendNotify(xcb_selection_request_event_t* e, bool success); + xcb_atom_t mimeToAtom(const std::string& mime); + std::string mimeFromAtom(xcb_atom_t atom); + void setClipboardToWayland(SXSelection& sel); + void getTransferData(SXSelection& sel); + void readProp(SP XSURF, uint32_t atom, xcb_get_property_reply_t* reply); + + // + xcb_connection_t* connection = nullptr; + xcb_errors_context_t* errors = nullptr; + xcb_screen_t* screen = nullptr; + + xcb_window_t wmWindow; + + wl_event_source* eventSource = nullptr; + + const xcb_query_extension_reply_t* xfixes = nullptr; + const xcb_query_extension_reply_t* xres = nullptr; + int xfixesMajor = 0; + + xcb_visualid_t visual_id; + xcb_colormap_t colormap; + uint32_t cursorXID = 0; + + xcb_render_pictformat_t render_format_id; + + std::vector> shellResources; + std::vector> surfaces; + std::vector> mappedSurfaces; // ordered by map time + std::vector> mappedSurfacesStacking; // ordered by stacking + + WP focusedSurface; + uint64_t lastFocusSeq = 0; + + SXSelection clipboard; + + DYNLISTENER(newSurface); + + struct { + CHyprSignalListener newXShellSurface; + } listeners; + + friend class CXWaylandSurface; + friend class CXWayland; + friend class CXDataSource; + friend struct SXSelection; + friend struct SXTransfer; +}; diff --git a/src/xwayland/XWayland.cpp b/src/xwayland/XWayland.cpp new file mode 100644 index 00000000..8d45fa63 --- /dev/null +++ b/src/xwayland/XWayland.cpp @@ -0,0 +1,28 @@ +#include "XWayland.hpp" +#include "../debug/Log.hpp" + +CXWayland::CXWayland() { +#ifndef NO_XWAYLAND + Debug::log(LOG, "Starting up the XWayland server"); + + pServer = std::make_unique(); + + if (!pServer->create()) { + Debug::log(ERR, "XWayland failed to start: it will not work."); + return; + } +#else + Debug::log(LOG, "Not starting XWayland: disabled at compile time"); +#endif +} + +void CXWayland::setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot) { +#ifndef NO_XWAYLAND + if (!pWM) { + Debug::log(ERR, "Couldn't set XCursor: no XWM yet"); + return; + } + + pWM->setCursor(pixData, stride, size, hotspot); +#endif +} \ No newline at end of file diff --git a/src/xwayland/XWayland.hpp b/src/xwayland/XWayland.hpp new file mode 100644 index 00000000..c7981251 --- /dev/null +++ b/src/xwayland/XWayland.hpp @@ -0,0 +1,129 @@ +#pragma once + +#include +#include "../helpers/signal/Signal.hpp" + +#include "XSurface.hpp" + +#ifndef NO_XWAYLAND +#include "Server.hpp" +#include "XWM.hpp" +#else +class CXWaylandServer; +class CXWM; +#endif + +class CXWayland { + public: + CXWayland(); + +#ifndef NO_XWAYLAND + std::unique_ptr pServer; + std::unique_ptr pWM; +#endif + + void setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot); + + struct { + CSignal newSurface; + } events; +}; + +inline std::unique_ptr g_pXWayland; + +#define HYPRATOM(name) \ + { name, 0 } +inline std::unordered_map HYPRATOMS = { + HYPRATOM("_NET_SUPPORTED"), + HYPRATOM("_NET_SUPPORTING_WM_CHECK"), + HYPRATOM("_NET_WM_NAME"), + HYPRATOM("_NET_WM_VISIBLE_NAME"), + HYPRATOM("_NET_WM_MOVERESIZE"), + HYPRATOM("_NET_WM_STATE_STICKY"), + HYPRATOM("_NET_WM_STATE_FULLSCREEN"), + HYPRATOM("_NET_WM_STATE_DEMANDS_ATTENTION"), + HYPRATOM("_NET_WM_STATE_MODAL"), + HYPRATOM("_NET_WM_STATE_HIDDEN"), + HYPRATOM("_NET_WM_STATE_FOCUSED"), + HYPRATOM("_NET_WM_STATE"), + HYPRATOM("_NET_WM_WINDOW_TYPE"), + HYPRATOM("_NET_WM_WINDOW_TYPE_NORMAL"), + HYPRATOM("_NET_WM_WINDOW_TYPE_DOCK"), + HYPRATOM("_NET_WM_WINDOW_TYPE_DIALOG"), + HYPRATOM("_NET_WM_WINDOW_TYPE_UTILITY"), + HYPRATOM("_NET_WM_WINDOW_TYPE_TOOLBAR"), + HYPRATOM("_NET_WM_WINDOW_TYPE_SPLASH"), + HYPRATOM("_NET_WM_WINDOW_TYPE_MENU"), + HYPRATOM("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"), + HYPRATOM("_NET_WM_WINDOW_TYPE_POPUP_MENU"), + HYPRATOM("_NET_WM_WINDOW_TYPE_TOOLTIP"), + HYPRATOM("_NET_WM_WINDOW_TYPE_NOTIFICATION"), + HYPRATOM("_NET_WM_WINDOW_TYPE_COMBO"), + HYPRATOM("_NET_WM_WINDOW_TYPE_DND"), + HYPRATOM("_NET_WM_WINDOW_TYPE_DESKTOP"), + HYPRATOM("_NET_WM_STATE_MAXIMIZED_HORZ"), + HYPRATOM("_NET_WM_STATE_MAXIMIZED_VERT"), + HYPRATOM("_NET_WM_DESKTOP"), + HYPRATOM("_NET_WM_STRUT_PARTIAL"), + HYPRATOM("_NET_CLIENT_LIST"), + HYPRATOM("_NET_CLIENT_LIST_STACKING"), + HYPRATOM("_NET_CURRENT_DESKTOP"), + HYPRATOM("_NET_NUMBER_OF_DESKTOPS"), + HYPRATOM("_NET_DESKTOP_NAMES"), + HYPRATOM("_NET_DESKTOP_VIEWPORT"), + HYPRATOM("_NET_ACTIVE_WINDOW"), + HYPRATOM("_NET_CLOSE_WINDOW"), + HYPRATOM("_NET_MOVERESIZE_WINDOW"), + HYPRATOM("_NET_WM_USER_TIME"), + HYPRATOM("_NET_STARTUP_ID"), + HYPRATOM("_NET_WORKAREA"), + HYPRATOM("_NET_WM_ICON"), + HYPRATOM("_NET_WM_CM_S0"), + HYPRATOM("WM_PROTOCOLS"), + HYPRATOM("WM_HINTS"), + HYPRATOM("WM_DELETE_WINDOW"), + HYPRATOM("UTF8_STRING"), + HYPRATOM("WM_STATE"), + HYPRATOM("WM_CLIENT_LEADER"), + HYPRATOM("WM_TAKE_FOCUS"), + HYPRATOM("WM_NORMAL_HINTS"), + HYPRATOM("WM_SIZE_HINTS"), + HYPRATOM("WM_WINDOW_ROLE"), + HYPRATOM("_NET_REQUEST_FRAME_EXTENTS"), + HYPRATOM("_NET_FRAME_EXTENTS"), + HYPRATOM("_MOTIF_WM_HINTS"), + HYPRATOM("WM_CHANGE_STATE"), + HYPRATOM("_NET_SYSTEM_TRAY_OPCODE"), + HYPRATOM("_NET_SYSTEM_TRAY_COLORS"), + HYPRATOM("_NET_SYSTEM_TRAY_VISUAL"), + HYPRATOM("_NET_SYSTEM_TRAY_ORIENTATION"), + HYPRATOM("_XEMBED_INFO"), + HYPRATOM("MANAGER"), + HYPRATOM("XdndSelection"), + HYPRATOM("XdndAware"), + HYPRATOM("XdndStatus"), + HYPRATOM("XdndPosition"), + HYPRATOM("XdndEnter"), + HYPRATOM("XdndLeave"), + HYPRATOM("XdndDrop"), + HYPRATOM("XdndFinished"), + HYPRATOM("XdndProxy"), + HYPRATOM("XdndTypeList"), + HYPRATOM("XdndActionMove"), + HYPRATOM("XdndActionCopy"), + HYPRATOM("XdndActionAsk"), + HYPRATOM("XdndActionPrivate"), + HYPRATOM("CLIPBOARD"), + HYPRATOM("PRIMARY"), + HYPRATOM("_WL_SELECTION"), + HYPRATOM("CLIPBOARD_MANAGER"), + HYPRATOM("WINDOW"), + HYPRATOM("WM_S0"), + HYPRATOM("WL_SURFACE_ID"), + HYPRATOM("WL_SURFACE_SERIAL"), + HYPRATOM("TARGETS"), + HYPRATOM("TIMESTAMP"), + HYPRATOM("DELETE"), + HYPRATOM("TEXT"), + HYPRATOM("INCR"), +}; \ No newline at end of file