Native GNUstep Display Server for Linux — KMS/DRM + GPU Compositing
WindowServer.app is a native GNUstep display server that eliminates the X11 and Wayland dependency entirely. It follows Apple's macOS model: WindowServer owns the display hardware, and all GUI applications render through it. The architecture maximizes use of GNUstep/Apple-native frameworks.
| Layer | Component | Source | Role |
|---|---|---|---|
| App Drawing | AppKit (libs-gui) | GNUstep | NSWindow, NSView, NSBezierPath, NSImage — the public drawing API |
| 2D Rendering | Core Graphics (libs-opal) | GNUstep | CGContext rasterizes drawing commands to pixel buffers via Cairo internally |
| GPU Compositing | Core Animation (libs-quartzcore) | GNUstep | CARenderer composites window textures with transforms, opacity, shadows via OpenGL |
| Display Server | WindowServer.app | Gershwin (new) | NSApplication subclass owning the screen, input, and compositor |
| IPC | Distributed Objects | GNUstep | NSConnection for window operations between client apps and WindowServer |
| Pixel Transport | POSIX Shared Memory | Linux | shm_open/mmap for zero-copy pixel buffer sharing |
| GPU Context | EGL + GBM | Linux | OpenGL context on DRM without X11/Wayland |
| Display | libdrm / KMS | Linux | Modesetting, page flip, hardware cursor |
| Input | libinput | Linux | Keyboard, mouse, touchpad → NSEvent via NSRunLoop |
| Keyboard Layout | libxkbcommon | Linux | Keycode → keysym/character translation |
| Session | LoginWindow (root) | Gershwin | Launches WindowServer with root privileges; direct DRM/input device access |
These GNUstep repositories need to be cloned and built as part of the Gershwin system. The first six are already in Checkout.sh. The last two are new additions.
| Repository | Framework | Status | Notes |
|---|---|---|---|
gnustep/libobjc2 |
Objective-C Runtime | Already checked out | — |
gnustep/tools-make |
GNUstep Make | Already checked out | — |
gnustep/libs-base |
Foundation | Already checked out | — |
gnustep/libs-gui |
AppKit | Already checked out | — |
gnustep/libs-back |
Backend (display server) | Already checked out | Will add new drm + opal server/graphics backend |
apple/swift-corelibs-libdispatch |
Grand Central Dispatch | Already checked out | — |
gnustep/libs-opal |
Core Graphics (Quartz 2D) | NEW — add to Checkout.sh | github.com/gnustep/libs-opal CGContext, CGPath, CGImage, CGColor, CGFont + CoreText basics. Built on Cairo internally. Depends on: cairo, freetype, fontconfig, lcms2, libpng, libjpeg, libtiff. |
gnustep/libs-quartzcore |
Core Animation (QuartzCore) | NEW — add to Checkout.sh | github.com/gnustep/libs-quartzcore CALayer, CARenderer (OpenGL 2.0 compositor), CABasicAnimation, CATransaction. Uses GLSL shaders for compositing and gaussian blur shadows. Depends on: libs-opal, OpenGL, GLU. |
# Add to the REPOS list:
https://github.com/gnustep/libs-opal.git
https://github.com/gnustep/libs-quartzcore.git
1. swift-corelibs-libdispatch (CMake)
2. tools-make (configure)
3. libobjc2 (CMake)
4. libs-base (configure)
5. libs-opal (GNUstep Make) ← NEW
6. libs-gui (configure)
7. libs-back --enable-server=drm --enable-graphics=opal (configure)
8. libs-quartzcore (GNUstep Make) ← NEW
9. gershwin-windowserver (GNUstep Make) ← NEW
10. gershwin-* components
Target: Devuan GNU/Linux 6 (Excalibur), amd64.
Devuan uses eudev instead of systemd-udev.
All package names verified via apt-cache search on a live Devuan 6 system.
| Package | Purpose |
|---|---|
build-essential | GCC, make, libc headers |
clang | Objective-C compiler |
cmake | Build system for libobjc2, libdispatch |
make | GNU Make |
autoconf | libs-back configure generation |
automake | libs-back build system |
libcairo2-dev | 2D rendering (used internally by libs-opal) |
libpng-dev | PNG image I/O (libs-opal) |
libturbojpeg0-dev | JPEG image I/O |
libtiff5-dev | TIFF image I/O (libs-opal) |
libgif-dev | GIF image I/O |
libxml2-dev | XML parsing (Foundation) |
libxslt1-dev | XSLT processing |
libgnutls28-dev | TLS/SSL |
libffi-dev | Foreign function interface |
libicu-dev | Unicode support |
libcurl4-openssl-dev | URL loading |
libdbus-1-dev | D-Bus IPC |
libpam0g-dev | Authentication |
libcups2-dev | Printing |
| Package | Purpose | Category |
|---|---|---|
libdrm-dev |
DRM/KMS ioctls — modesetting, page flip, dumb buffers | Display |
libgbm-dev |
Generic Buffer Management — GPU buffer allocation for EGL | Display |
libegl-dev |
EGL API — OpenGL context creation on GBM/DRM (no X11) | GPU |
libgles-dev |
OpenGL ES headers (alternative to desktop GL for embedded/modern) | GPU |
libgl-dev |
OpenGL development files — required by libs-quartzcore CARenderer | GPU |
libglu1-mesa-dev |
OpenGL Utility Library — required by libs-quartzcore | GPU |
libinput-dev |
Input device handling — keyboard, mouse, touchpad events | Input |
libxkbcommon-dev |
Keyboard layout/keymap handling (keycode → keysym translation) | Input |
libseat-dev |
— | |
libeudev-dev |
Device enumeration (Devuan's eudev, replaces systemd's libudev-dev) | Session |
liblcms2-dev |
Color management — required by libs-opal for CGColorSpace | Opal |
libfreetype-dev |
Font rendering — required by libs-opal for CGFont | Opal |
libfontconfig-dev |
Font discovery — required by libs-opal for font enumeration | Opal |
mesa-common-dev |
Mesa common development files | GPU |
apt-get install -y \
libdrm-dev libgbm-dev libegl-dev libgles-dev libgl-dev libglu1-mesa-dev \
mesa-common-dev libinput-dev libxkbcommon-dev libeudev-dev \
liblcms2-dev libfreetype-dev libfontconfig-dev
libeudev-dev (not libudev-dev).
The libudev-dev package exists as a transitional wrapper that pulls in libeudev-dev,
but it is better to depend on libeudev-dev directly on Devuan.
Phase 1 Get pixels on screen with GPU compositing. A new --enable-server=drm backend.
libs-back/Source/drm/| File | Purpose |
|---|---|
DRMServer.h | GSDisplayServer subclass — header |
DRMServer.m | Core server: DRM init, modesetting, window registry, screen info |
DRMServerEvent.m | libinput event loop integrated with NSRunLoop, NSEvent generation |
DRMServerWindow.m | Window operations: create, move, order, resize, focus |
DRMOutput.h / .m | Per-CRTC output: GBM surface, EGL context, page flipping |
DRMCompositor.h / .m | Wraps CARenderer — feeds CALayer tree, drives page flip |
GNUmakefile | Subproject build rules |
GNUmakefile.preamble | Link flags: -ldrm -linput -lxkbcommon -ludev |
libs-back/Source/cairo/| File | Purpose |
|---|---|
DRMCairoSurface.h / .m | CairoSurface subclass — per-window offscreen Cairo image surface. On
handleExposeRect:, notifies compositor to recomposite. |
| File | Change |
|---|---|
configure.ac |
Add SERVER_drm 5. Add --enable-server=drm option.Add pkg-config checks for libdrm, libgbm, libegl, libinput, libxkbcommon, libseat, libudev. |
Source/GSBackend.m |
Add #elif BUILD_SERVER == SERVER_drm block to initialize DRMServer. |
Source/cairo/CairoContext.m |
Add #elif BUILD_SERVER == SERVER_drm block to wire DRMCairoSurface. |
Source/cairo/GNUmakefile |
Add ifeq ($(BUILD_SERVER),drm) to compile DRMCairoSurface.m. |
-initWithAttributes:
1. Open DRM device directly: open("/dev/dri/card0", O_RDWR)
2. drmModeGetResources() → enumerate connectors, CRTCs, encoders
3. For each connected output, create DRMOutput:
a. Find preferred mode from connector
b. gbm_create_device(drm_fd)
c. eglGetDisplay(gbm_device) → EGL context
d. Create GBM surface for scanout
e. drmModeSetCrtc() for initial mode
4. libinput_udev_create_context() → input handling
5. xkb_context_new() → keyboard layout
6. Add DRM fd + libinput fd to [NSRunLoop currentRunLoop]
via NSFileHandle or -addEvent:type:watcher:forMode:
DRMCompositor wraps libs-quartzcore's CARenderer:
- Root CALayer sized to screen resolution
- Each window gets a child CALayer
- layer.contents = CGImageRef from the window's CairoSurface bitmap
- layer.frame = window position and size
- layer.zPosition = window level / z-order
- CARenderer renders the layer tree via OpenGL into the EGL/GBM surface
- drmModePageFlip() presents the frame
Window animations (minimize, zoom, fade) come from CABasicAnimation
on the window's CALayer properties (transform, opacity, bounds).
DRMServerEvent.m:
libinput fd → NSRunLoop callback → libinput_dispatch()
LIBINPUT_EVENT_POINTER_MOTION → NSMouseMoved / NSLeftMouseDragged
LIBINPUT_EVENT_POINTER_BUTTON → NSLeftMouseDown / NSLeftMouseUp / NSRightMouse*
LIBINPUT_EVENT_KEYBOARD_KEY → NSKeyDown / NSKeyUp (via libxkbcommon)
LIBINPUT_EVENT_POINTER_SCROLL_* → NSScrollWheel
Hit-test pointer against window registry for target window.
Post via [self postEvent:atStart:]
Phase 2 A standalone NSApplication that uses the DRM backend, runs as PID 1 of the graphical session.
gershwin-windowserver/
GNUmakefile
GNUmakefile.preamble
WindowServerApplication.h / .m ← NSApplication subclass
WSCompositor.h / .m ← CARenderer + layer tree management
WSDisplayManager.h / .m ← Multi-monitor DRM output management
WSInputManager.h / .m ← libinput → NSEvent translation
WSWindowRegistry.h / .m ← Window list, z-order, focus policy
WSCursorManager.h / .m ← Hardware/software cursor
WSSessionManager.h / .m ← VT switching via ioctl
Resources/
Info-gnustep.plist
WindowServer.png
main.m
| API | Usage |
|---|---|
NSApplication | Main event loop, application lifecycle |
NSRunLoop | DRM fd, libinput fd, DO port monitoring |
NSEvent | All input event representation and dispatch |
NSScreen | Display info from DRM connector/mode data |
NSImage / NSBitmapImageRep | Cursor images, window thumbnails |
NSNotificationCenter | Internal compositor events (window added, removed, resized) |
NSUserDefaults | Configuration (resolution, refresh rate, cursor size) |
NSTimer | Animation timing, idle detection |
GSTheme | Window decoration rendering (Phase 4) |
CALayer / CARenderer | GPU-accelerated window compositing |
CABasicAnimation | Window animations (minimize, zoom, fade) |
CGContext / CGImage | 2D rendering for window contents |
main.m:
[WindowServerApplication sharedApplication]
[NSApp run]
-finishLaunching:
1. WSDisplayManager opens /dev/dri/card0 directly (running as root via LoginWindow)
2. WSDisplayManager enumerates outputs, sets modes, creates EGL/GBM surfaces
3. WSInputManager opens libinput, adds fd to [NSRunLoop currentRunLoop]
4. WSCompositor creates root CALayer, initializes CARenderer with EGL context
5. DRM page-flip fd added to NSRunLoop
Compositing loop (driven by vsync):
DRM page-flip completion event → NSRunLoop callback
→ WSCompositor checks damage
→ Updates CALayer contents for dirty windows
→ [CARenderer render]
→ eglSwapBuffers / drmModePageFlip()
Phase 3 Client GNUstep apps run as separate processes, communicating with WindowServer.app via Distributed Objects.
@protocol WSWindowServerProtocol <NSObject>
// Window lifecycle
- (int)createWindowWithFrame:(NSRect)frame
backingStore:(NSBackingStoreType)type
style:(unsigned int)style
screen:(int)screen;
- (void)destroyWindow:(int)windowId;
// Window operations
- (void)orderWindow:(int)windowId operation:(int)op relativeTo:(int)other;
- (void)moveWindow:(int)windowId toPoint:(NSPoint)point;
- (void)resizeWindow:(int)windowId toSize:(NSSize)size;
- (void)setWindowLevel:(int)level forWindow:(int)windowId;
- (void)setWindowTitle:(NSString *)title forWindow:(int)windowId;
- (void)setInputFocus:(int)windowId;
// Rendering (shared memory path)
- (NSString *)sharedMemoryPathForWindow:(int)windowId;
- (void)flushRect:(NSRect)rect forWindow:(int)windowId;
// Screen info
- (NSArray *)screenList;
- (NSRect)boundsForScreen:(int)screen;
// Cursor
- (void)setCursorImage:(NSData *)imageData hotspot:(NSPoint)hotspot;
- (void)hideCursor;
- (void)showCursor;
@end
Client Process:
shm_open(path) → mmap()
cairo_image_surface_create_for_data(shm_ptr, ARGB32, w, h, stride)
↓ normal GNUstep drawing via Core Graphics (libs-opal) / CairoGState
↓ flushwindowrect:: triggers
↓ [proxy flushRect:forWindow:] (lightweight DO call)
WindowServer.app:
← receives flushRect notification
← reads pixels from shared memory (same physical pages, zero-copy)
← uploads to CALayer.contents as CGImageRef / GL texture
← [CARenderer render]
← drmModePageFlip()
A GSDisplayServer subclass that connects to WindowServer.app via
NSConnection on launch. All window operations become remote invocations
on the WSWindowServerProtocol proxy. Pixel buffers use POSIX shared memory
for zero-copy rendering.
Phase 4 Server-side window chrome and global menu bar.
WindowServer.app draws title bars using GSTheme, following the same approach as
URSThemeIntegration in the current window manager. The Eau theme renders directly.
Each window's frame decoration is a separate CALayer above the content layer.
The active application publishes its menu structure over DO to WindowServer.app.
WindowServer renders the menu bar at the top of the screen as a CALayer.
Based on the existing gershwin-components/Menu architecture, adapted
from X11 properties to Distributed Objects.
Phase 5 Production readiness.
DRMOutput objects, one per CRTC. Each gets its own GBM surface and EGL context. NSScreen objects reflect each output.ioctl(fd, VT_SETMODE, ...) and signals directly. WindowServer pauses rendering, releases DRM master, restores on return.drmModeSetCrtc() with new mode, reallocate GBM surface and EGL context.| File | Why It Matters |
|---|---|
libs-back/Source/headless/HeadlessServer.m |
Minimal GSDisplayServer — skeleton to start from (115 lines) |
libs-back/Source/wayland/WaylandServer.m |
Real-world GSDisplayServer with Cairo integration and input handling |
libs-back/Source/cairo/WaylandCairoShmSurface.m |
Pattern for CairoSurface subclass backed by a pixel buffer |
libs-back/Source/cairo/CairoContext.m:45-83 |
Where to add #elif BUILD_SERVER == SERVER_drm |
libs-back/Source/GSBackend.m:38-83 |
Where to add #elif BUILD_SERVER == SERVER_drm |
libs-back/configure.ac:759-777 |
Where to add SERVER_drm 5 and pkg-config checks |
libs-quartzcore/Source/CARenderer.m |
OpenGL layer-tree compositor — the GPU compositing engine |
libs-opal/Source/OpalGraphics/ |
Core Graphics implementation — CGContext, CGImage, etc. |
gershwin-windowmanager/WindowManager/THEORY.md |
Architecture reference for compositing and event flow |
gershwin-windowmanager/WindowManager/URSCompositingManager.m |
Compositor patterns: damage tracking, z-order, frame pacing |
# Build libs-back with DRM backend
cd libs-back
./configure --enable-server=drm --enable-graphics=opal
make
sudo -E make install
# Minimal test program
#import <AppKit/AppKit.h>
int main() {
[NSApplication sharedApplication];
NSWindow *win = [[NSWindow alloc]
initWithContentRect:NSMakeRect(100, 100, 400, 300)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[win setTitle:@"Hello KMS"];
[win makeKeyAndOrderFront:nil];
[NSApp run];
}
# Run from a Linux TTY (NOT inside X11/Wayland):
./test_app
# Should see a window rendered on the bare framebuffer
# Launch WindowServer from TTY:
/System/Library/Services/WindowServer.app/WindowServer
# Should see: desktop background, hardware cursor
# Keyboard and mouse input should work
# WindowServer running, then launch a client app:
/System/Library/Services/WindowServer.app/WindowServer &
TextEdit.app/TextEdit
# Verify: window appears, move, resize, focus,
# keyboard input, mouse clicks, menu bar
Gershwin Desktop — WindowServer.app Development Plan
Generated 2026-03-31 — Target: Devuan GNU/Linux 6 (Excalibur)