How X11/Wayland Events Are Processed in Hyprland: A Deep Dive into the Source Code
Hyprland processes X11/Wayland events by letting wlroots capture low-level XWayland signals, translating them into compositor events via the XWM layer, and broadcasting those events through a central EventBus to state trackers and the rendering pipeline.
Hyprland is a dynamic tiling Wayland compositor built on top of wlroots. Understanding how X11/Wayland events are processed in Hyprland requires examining its modular XWayland integration, which treats legacy X11 applications as first-class citizens alongside native Wayland clients. The hyprwm/Hyprland repository implements this pipeline through a dedicated XWayland server manager, an internal EventBus, and stateful trackers that keep the compositor synchronized.
XWayland Server Startup and XWM Registration
At initialization, Hyprland spawns the XWayland server and prepares the X Wayland Manager (XWM) to handle X11-specific window events.
Spawning the XWayland Server
In src/xwayland/Server.cpp, the compositor creates the XWayland server by calling wlr_xwayland_create(). This function, provided by wlroots, spawns the XWayland process and establishes a Wayland socket for incoming X11 client connections. According to the Hyprland source code, the server also configures the cursor image through wlr_xwayland_set_cursor() so that X11 clients inherit the correct pointer appearance immediately upon connection.
// src/xwayland/Server.cpp
void createXWaylandServer() {
wlr_xwayland = wlr_xwayland_create(g_pCompositor->m_sWLRDisplay, true);
wlr_xwayland_set_cursor(wlr_xwayland, cursor_image, width, height, hotspot_x, hotspot_y);
// Register callbacks for surface events
wlr_xwayland_set_surface_created(wlr_xwayland, XWM::handleSurfaceCreated);
}
Registering the X Wayland Manager
Once the server exists, src/xwayland/XWM.cpp initializes the XWM component. The XWM class registers callbacks with wlroots for critical X11 lifecycle events such as window creation, destruction, and focus changes. It also maintains the mapping between raw wlr_xwayland_surface objects and Hyprland’s internal window representation. This layer shields the rest of the compositor from direct X11 protocol details.
Surface Mapping and Event Distribution
When an X11 client creates a top-level window, the raw wlroots surface must be adopted into Hyprland’s window tree and announced to the rest of the compositor.
Mapping X11 Surfaces to Hyprland Windows
As implemented in hyprwm/Hyprland, wlroots emits a wlr_xwayland_surface event whenever a new X11 surface appears. The XWM receives this through onSurfaceCreated and, in src/xwayland/XSurface.cpp, wraps the surface into a CWindow object managed by src/desktop/view/Group.cpp. This translation step is essential because Hyprland’s layout engine and rendering pipeline operate on CWindow instances rather than raw wlroots surfaces.
// src/xwayland/XWM.cpp
void XWM::handleSurfaceCreated(wlr_xwayland_surface* surf) {
// Wrap the wlroots surface into a Hyprland CWindow
auto* pWindow = g_pCompositor->createWindowFromXWayland(surf);
EventBus::post<EventXWaylandSurfaceMap>(pWindow);
}
Broadcasting Compositor-Wide Events via EventBus
All compositor-wide events—including those originating from XWayland—are funneled through Hyprland’s EventBus in src/event/EventBus.cpp. Listeners subscribe to specific event types such as eventXWaylandSurfaceMap and eventXWaylandSurfaceUnmap. This publish-subscribe pattern decouples XWayland code from the layout engine, state trackers, and input subsystems.
// src/event/EventBus.cpp
template <typename E, typename... Args>
static void post(Args&&... args) {
for (auto& listener : listeners<E>) {
listener(std::forward<Args>(args)...);
}
}
State Tracking and Rendering
After an event is published, Hyprland updates its internal world state and schedules a new frame.
Synchronizing Workspace and Monitor State
The WorkspaceStateTracker and MonitorStateTracker, implemented in src/state/WorkspaceStateTracker.cpp and src/state/MonitorStateTracker.cpp, listen to the EventBus. When an X11 window maps, unmaps, or moves between outputs, these trackers update the internal state that drives the layout engine and animation system. This design ensures that X11 windows participate fully in Hyprland’s dynamic tiling decisions.
// src/state/WorkspaceStateTracker.cpp
EventBus::subscribe<EventXWaylandSurfaceMap>([](CWindow* win) {
workspaceState->addWindow(win);
});
EventBus::subscribe<EventXWaylandSurfaceUnmap>([](CWindow* win) {
workspaceState->removeWindow(win);
});
Rendering and Visual Effects
Once state is synchronized, the compositor’s render loop draws the window’s texture through wlroots. Before the final frame is presented, Hyprland’s transformer subsystem in src/render/transformer/Transformer.cpp can apply effects such as motion blur or rounded corners. Because X11 surfaces are wrapped into standard CWindow objects, they receive the same visual treatment as native Wayland surfaces without X-specific rendering branches.
Input Forwarding to XWayland Clients
Input events enter Hyprland through native Wayland input devices and are forwarded to X11 clients when appropriate. In src/devices/VirtualPointer.cpp and src/devices/VirtualKeyboard.cpp, the compositor checks whether the focused window is an X11 client. If so, it forwards keyboard and pointer events to the underlying wlr_xwayland_surface through wlroots, which then delivers them to the X client.
// src/devices/VirtualPointer.cpp
if (focusedWindow->isX11()) {
wlr_xwayland_surface_set_keyboard_focus(focusedWindow->xwaylandSurface());
}
This path ensures that mouse, keyboard, and touch events reach legacy X11 applications seamlessly while maintaining Hyprland’s unified input configuration.
Summary
- wlroots bootstraps XWayland:
src/xwayland/Server.cppcreates the server and registers initial callbacks. - XWM adopts surfaces:
src/xwayland/XWM.cppconverts rawwlr_xwayland_surfaceobjects into HyprlandCWindowinstances. - EventBus decouples subsystems:
src/event/EventBus.cppbroadcasts XWayland lifecycle events to listeners across the compositor. - State trackers maintain consistency:
src/state/WorkspaceStateTracker.cppand related files keep workspace and monitor state accurate for both X11 and Wayland windows. - Rendering is surface-agnostic:
src/render/transformer/Transformer.cppapplies effects uniformly because X11 windows are normalized to the internal window type. - Input flows back through wlroots:
src/devices/VirtualPointer.cppforwards Wayland input events to focused XWayland surfaces.
Frequently Asked Questions
How does Hyprland distinguish between X11 and native Wayland windows?
Hyprland wraps every XWayland surface into a CWindow object in src/desktop/view/Group.cpp via the XWM layer. The resulting window carries an internal flag or type check—accessed through methods like isX11()—that the compositor uses to decide whether to forward input through wlr_xwayland_surface routines or standard Wayland surface handlers.
What role does wlroots play in Hyprland's X11 event processing?
wlroots provides the low-level primitives that spawn the XWayland server and emit raw wlr_xwayland_surface events. According to the Hyprland source code, the compositor does not parse X11 wire protocol directly; instead, it relies on wlroots to translate X11 client activity into Wayland-compatible signals that Hyprland consumes through callbacks registered in src/xwayland/XWM.cpp.
Why does Hyprland use an EventBus for XWayland events?
The EventBus in src/event/EventBus.cpp maintains a clean separation between the XWayland integration layer and the rest of the compositor. By publishing events like EventXWaylandSurfaceMap rather than invoking layout or state functions directly, Hyprland keeps the XWM module isolated and allows multiple subsystems—state trackers, render scheduling, and plugin hooks—to react to the same X11 window lifecycle events without tight coupling.
Are visual effects applied differently to X11 windows compared to Wayland windows?
No. Once an X11 surface is mapped, it becomes a standard CWindow and passes through the same rendering pipeline as native Wayland surfaces. The transformer subsystem in src/render/transformer/Transformer.cpp applies effects such as blur and rounding uniformly, because the render loop operates on the internal window abstraction rather than the underlying protocol type.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →