diff --git a/CMakeLists.txt b/CMakeLists.txt
index adc9a5a2f..3e780ac09 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 2c331e4b2..f491bb096 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 529a3b3dd..a14e91fda 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 <sys/types.h>
 #include <sys/stat.h>
@@ -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<CCursorManager>();
+
+            Debug::log(LOG, "Starting XWayland");
+            g_pXWayland = std::make_unique<CXWayland>();
         } 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<PHLWINDOW> 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 574889bc4..94d9e4d07 100644
--- a/src/Compositor.hpp
+++ b/src/Compositor.hpp
@@ -189,21 +189,3 @@ class CCompositor {
 };
 
 inline std::unique_ptr<CCompositor> g_pCompositor;
-
-// For XWayland
-inline std::map<std::string, xcb_atom_t> 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 7a06aa614..093cab5e6 100644
--- a/src/desktop/Window.cpp
+++ b/src/desktop/Window.cpp
@@ -7,11 +7,14 @@
 #include <any>
 #include "../managers/TokenManager.hpp"
 #include "../protocols/XDGShell.hpp"
+#include "../xwayland/XWayland.hpp"
 
-PHLWINDOW CWindow::create() {
-    PHLWINDOW pWindow = SP<CWindow>(new CWindow);
+PHLWINDOW CWindow::create(SP<CXWaylandSurface> surface) {
+    PHLWINDOW pWindow = SP<CWindow>(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<CXDGSurfaceResource> resource) : m_pXDGSurface(resource) {
     listeners.updateMetadata = m_pXDGSurface->toplevel->events.metadataChanged.registerListener([this](std::any d) { onUpdateMeta(); });
 }
 
-CWindow::CWindow() {
-    ;
+CWindow::CWindow(SP<CXWaylandSurface> 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<CBox>(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<bool> requestsFS = m_pXDGSurface ? m_pXDGSurface->toplevel->state.requestsFullscreen : m_pXWaylandSurface->state.requestsFullscreen;
+    std::optional<bool> 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<Hyprlang::INT>("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 24d9562bf..473ce361c 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<CXDGSurfaceResource>);
-    // xwl
-    static PHLWINDOW create();
+    static PHLWINDOW create(SP<CXWaylandSurface>);
 
   private:
     CWindow(SP<CXDGSurfaceResource> resource);
-    CWindow();
+    CWindow(SP<CXWaylandSurface> 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<CXDGSurfaceResource> m_pXDGSurface;
+    WP<CXWaylandSurface>    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 0b7578424..f8eb9d2fb 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 7152730e4..6580d93e6 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 384ea9eda..9044bc61a 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<Hyprlang::INT>("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<Hyprlang::INT>("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<std::any>{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<std::any>{PWINDOW, (int64_t)(1)}));
-    }
-}
diff --git a/src/helpers/XWaylandStubs.hpp b/src/helpers/XWaylandStubs.hpp
deleted file mode 100644
index c21041cd5..000000000
--- a/src/helpers/XWaylandStubs.hpp
+++ /dev/null
@@ -1,172 +0,0 @@
-#pragma once
-
-#include <wayland-server.h>
-
-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 dfbe02214..dbae76358 100644
--- a/src/includes.hpp
+++ b/src/includes.hpp
@@ -82,10 +82,6 @@ extern "C" {
 #if WLR_HAS_X11_BACKEND
 #include <wlr/backend/x11.h>
 #endif
-
-#ifndef NO_XWAYLAND
-#include <wlr/xwayland.h>
-#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 ffd9ddeae..108f90391 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 ea36b9b20..c97838449 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 <wlr/interfaces/wlr_buffer.h>
@@ -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 b4c5232af..4ff9adeb6 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 c43e4c565..feff69e0f 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<CXDGShellProtocol>(&xdg_wm_base_interface, 6, "XDGShell");
     PROTO::dataWlr             = std::make_unique<CDataDeviceWLRProtocol>(&zwlr_data_control_manager_v1_interface, 2, "DataDeviceWlr");
     PROTO::primarySelection    = std::make_unique<CPrimarySelectionProtocol>(&zwp_primary_selection_device_manager_v1_interface, 1, "PrimarySelection");
+    PROTO::xwaylandShell       = std::make_unique<CXWaylandShellProtocol>(&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 bbbe01d43..ce40650d8 100644
--- a/src/managers/SeatManager.cpp
+++ b/src/managers/SeatManager.cpp
@@ -498,6 +498,8 @@ void CSeatManager::setCurrentSelection(SP<IDataSource> source) {
         PROTO::data->setSelection(source);
         PROTO::dataWlr->setSelection(source, false);
     }
+
+    events.setSelection.emit();
 }
 
 void CSeatManager::setCurrentPrimarySelection(SP<IDataSource> source) {
@@ -521,6 +523,8 @@ void CSeatManager::setCurrentPrimarySelection(SP<IDataSource> source) {
         PROTO::primarySelection->setSelection(source);
         PROTO::dataWlr->setSelection(source, true);
     }
+
+    events.setPrimarySelection.emit();
 }
 
 void CSeatManager::setGrab(SP<CSeatGrab> grab) {
diff --git a/src/managers/SeatManager.hpp b/src/managers/SeatManager.hpp
index fee3efa9f..b88058a8f 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 390c6ece2..4aebefcb4 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 8889eb614..5bbab802a 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<CHyprXWaylandManager> g_pXWaylandManager;
\ No newline at end of file
diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp
index 52927d952..5a7688cdd 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<Hyprlang::INT>("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 9d3e2ac90..771f5f781 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<CXDGOutput>(makeShared<CZxdgOutputV1>(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 000000000..8b9905f8c
--- /dev/null
+++ b/src/protocols/XWaylandShell.cpp
@@ -0,0 +1,84 @@
+#include "XWaylandShell.hpp"
+#include <algorithm>
+
+#define LOGM PROTO::xwaylandShell->protoLog
+
+CXWaylandSurfaceResource::CXWaylandSurfaceResource(SP<CXwaylandSurfaceV1> 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<CXwaylandShellV1> 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<CXWaylandSurfaceResource>(makeShared<CXwaylandSurfaceV1>(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<CXWaylandShellResource>(makeShared<CXwaylandShellV1>(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 000000000..2c03d172e
--- /dev/null
+++ b/src/protocols/XWaylandShell.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <cstdint>
+#include "WaylandProtocol.hpp"
+#include "xwayland-shell-v1.hpp"
+#include "../helpers/signal/Signal.hpp"
+
+class CXWaylandSurfaceResource {
+  public:
+    CXWaylandSurfaceResource(SP<CXwaylandSurfaceV1> resource_, wlr_surface* surface_);
+    ~CXWaylandSurfaceResource();
+
+    bool       good();
+    wl_client* client();
+
+    struct {
+        CSignal destroy;
+    } events;
+
+    uint64_t                     serial  = 0;
+    wlr_surface*                 surface = nullptr;
+
+    WP<CXWaylandSurfaceResource> self;
+
+  private:
+    SP<CXwaylandSurfaceV1> resource;
+    wl_client*             pClient = nullptr;
+};
+
+class CXWaylandShellResource {
+  public:
+    CXWaylandShellResource(SP<CXwaylandShellV1> resource_);
+
+    bool good();
+
+  private:
+    SP<CXwaylandShellV1> 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<CXWaylandSurfaceResource>. Fired when it sets a serial, otherwise it's useless
+    } events;
+
+  private:
+    void destroyResource(CXWaylandSurfaceResource* resource);
+    void destroyResource(CXWaylandShellResource* resource);
+
+    //
+    std::vector<SP<CXWaylandShellResource>>   m_vManagers;
+    std::vector<SP<CXWaylandSurfaceResource>> m_vSurfaces;
+
+    friend class CXWaylandSurfaceResource;
+    friend class CXWaylandShellResource;
+};
+
+namespace PROTO {
+    inline UP<CXWaylandShellProtocol> xwaylandShell;
+};
diff --git a/src/protocols/types/DataDevice.cpp b/src/protocols/types/DataDevice.cpp
index 47cbda8b1..eb6969ccc 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 98a97c14c..948f47a06 100644
--- a/src/protocols/types/DataDevice.hpp
+++ b/src/protocols/types/DataDevice.hpp
@@ -5,6 +5,11 @@
 #include <cstdint>
 #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 c5c87ea10..40ae953ef 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 000000000..510ad7376
--- /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 <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+// 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<int, 2>& 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<CXWM>();
+
+    g_pCursorManager->setXWaylandCursor();
+
+    return 0;
+}
+
+#endif
diff --git a/src/xwayland/Server.hpp b/src/xwayland/Server.hpp
new file mode 100644
index 000000000..0c06a56c5
--- /dev/null
+++ b/src/xwayland/Server.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <array>
+#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<int, 2>              xFDs          = {-1, -1};
+    std::array<wl_event_source*, 2> xFDReadEvents = {nullptr, nullptr};
+    wl_event_source*                idleSource    = nullptr;
+    wl_event_source*                pipeSource    = nullptr;
+    std::array<int, 2>              xwmFDs        = {-1, -1};
+    std::array<int, 2>              waylandFDs    = {-1, -1};
+
+    friend class CXWM;
+};
diff --git a/src/xwayland/XDataSource.cpp b/src/xwayland/XDataSource.cpp
new file mode 100644
index 000000000..98e0701b3
--- /dev/null
+++ b/src/xwayland/XDataSource.cpp
@@ -0,0 +1,97 @@
+#ifndef NO_XWAYLAND
+
+#include "XDataSource.hpp"
+#include "XWayland.hpp"
+#include "../defines.hpp"
+
+#include <fcntl.h>
+
+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<std::string> 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<SXTransfer>(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 000000000..c629aa2a9
--- /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<std::string> 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<std::string> mimeTypes; // these two have shared idx
+    std::vector<uint32_t>    mimeAtoms; //
+};
\ No newline at end of file
diff --git a/src/xwayland/XSurface.cpp b/src/xwayland/XSurface.cpp
new file mode 100644
index 000000000..8994e9753
--- /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 <ranges>
+
+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<uint32_t, 10> 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<uint32_t> 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 000000000..73cb89a54
--- /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 <vector>
+
+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 <xcb/xcb_icccm.h>
+#endif
+
+class CXWaylandSurface {
+  public:
+    wlr_surface*                 surface = nullptr;
+    WP<CXWaylandSurfaceResource> 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<bool> requestsMaximize;
+        std::optional<bool> requestsFullscreen;
+        std::optional<bool> 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<CXWaylandSurface>              parent;
+    WP<CXWaylandSurface>              self;
+    std::vector<WP<CXWaylandSurface>> children;
+
+    UP<xcb_icccm_wm_hints_t>          hints;
+    UP<xcb_size_hints_t>              sizeHints;
+    std::vector<uint32_t>             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 000000000..9e3083140
--- /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 <unordered_map>
+#include "../Compositor.hpp"
+#include "../protocols/XWaylandShell.hpp"
+#include "../managers/SeatManager.hpp"
+#include "../protocols/core/Seat.hpp"
+#include <ranges>
+#include <algorithm>
+#include <fcntl.h>
+#include <cstring>
+
+#include <xcb/xcb_icccm.h>
+
+#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<CXWaylandSurface> 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<CXWaylandSurface>(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<CXWaylandSurface> XSURF, SP<CXWaylandSurface> prospectiveChild) {
+    while (XSURF->parent) {
+        if (XSURF->parent == prospectiveChild)
+            return true;
+        XSURF = XSURF->parent.lock();
+    }
+
+    return false;
+}
+
+void CXWM::readProp(SP<CXWaylandSurface> 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_wm_hints_t>();
+            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<xcb_size_hints_t>();
+            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<CXWaylandSurface> 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<CXWaylandSurface> 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<xcb_atom_t> 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<SP<CXWaylandSurfaceResource>>(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<CXWaylandSurface> 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<CXWaylandSurface> surf) {
+    if (surf->withdrawn) {
+        xcb_delete_property(connection, surf->xID, HYPRATOMS["_NET_WM_STATE"]);
+        return;
+    }
+
+    std::vector<uint32_t> 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<CXWaylandSurfaceResource> 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<CXWaylandSurface> surf) {
+    const std::array<xcb_atom_t, 8> 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<CXWaylandSurface> 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<CXWaylandSurface> 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<xcb_window_t> 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<CXDataSource>(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<IDataSource> 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<SXTransfer>(*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 000000000..b312f4a9c
--- /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 <xcb/xcb.h>
+#include <xcb/xcb_errors.h>
+#include <xcb/composite.h>
+#include <xcb/xfixes.h>
+#include <xcb/res.h>
+
+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<uint8_t>          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<CXDataSource> 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<SXTransfer> 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<CXWaylandSurfaceResource> resource);
+
+    void                 setActiveWindow(xcb_window_t window);
+    void                 sendState(SP<CXWaylandSurface> surf);
+    void                 focusWindow(SP<CXWaylandSurface> surf);
+    void                 activateSurface(SP<CXWaylandSurface> surf);
+    bool                 isWMWindow(xcb_window_t w);
+
+    void                 sendWMMessage(SP<CXWaylandSurface> surf, xcb_client_message_data_t* data, uint32_t mask);
+
+    SP<CXWaylandSurface> windowForXID(xcb_window_t wid);
+
+    void                 readWindowData(SP<CXWaylandSurface> surf);
+    void                 associate(SP<CXWaylandSurface> surf, wlr_surface* wlSurf);
+    void                 dissociate(SP<CXWaylandSurface> 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<CXWaylandSurface> 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<WP<CXWaylandSurfaceResource>> shellResources;
+    std::vector<SP<CXWaylandSurface>>         surfaces;
+    std::vector<WP<CXWaylandSurface>>         mappedSurfaces;         // ordered by map time
+    std::vector<WP<CXWaylandSurface>>         mappedSurfacesStacking; // ordered by stacking
+
+    WP<CXWaylandSurface>                      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 000000000..8d45fa63a
--- /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<CXWaylandServer>();
+
+    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 000000000..c79812516
--- /dev/null
+++ b/src/xwayland/XWayland.hpp
@@ -0,0 +1,129 @@
+#pragma once
+
+#include <memory>
+#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<CXWaylandServer> pServer;
+    std::unique_ptr<CXWM>            pWM;
+#endif
+
+    void setCursor(unsigned char* pixData, uint32_t stride, const Vector2D& size, const Vector2D& hotspot);
+
+    struct {
+        CSignal newSurface;
+    } events;
+};
+
+inline std::unique_ptr<CXWayland> g_pXWayland;
+
+#define HYPRATOM(name)                                                                                                                                                             \
+    { name, 0 }
+inline std::unordered_map<std::string, uint32_t> 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