WindowServer.app

Native GNUstep Display Server for Linux — KMS/DRM + GPU Compositing

Table of Contents
  1. Architecture Overview
  2. Technology Stack
  3. GNUstep Repositories Required
  4. Devuan Linux Packages Required
  5. Phase 1: DRM Backend in libs-back
  6. Phase 2: WindowServer.app Application
  7. Phase 3: Multi-Process Client-Server
  8. Phase 4: Window Decorations & Menus
  9. Phase 5: Multi-Monitor & Polish
  10. Key Reference Files
  11. Verification Plan

1. Architecture Overview

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.

GNUstep Application GNUstep NSWindow, NSView, AppKit (libs-gui)
Core Graphics GNUstep CGContext, CGPath, CGImage (libs-opal)
Cairo Linux Hidden internal rasterizer — no direct use; called by libs-opal's CG* functions
↓ bitmap per window uploaded as GL texture
Core Animation GNUstepGPU CALayer, CARenderer (libs-quartzcore)
WindowServer.app GNUstep NSApplication, NSRunLoop, NSConnection (DO)
EGL / OpenGL ES GPULinux GPU context on GBM surfaces
DRM / KMS Linux /dev/dri/card0 — modesetting, page flip, cursor planes
Design Principle: Only use Linux C libraries for hardware-level operations that have no GNUstep equivalent (DRM, input devices, EGL, session management). Everything above the hardware layer uses GNUstep APIs: NSRunLoop, NSEvent, NSConnection/Distributed Objects, NSApplication, Core Graphics, Core Animation.

2. Technology Stack

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

3. GNUstep Repositories Required

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.

Additions to Checkout.sh

# Add to the REPOS list:
https://github.com/gnustep/libs-opal.git
https://github.com/gnustep/libs-quartzcore.git

Build Order

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

4. Devuan Linux Packages Required

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.

Already in devuan.txt (existing dependencies)

PackagePurpose
build-essentialGCC, make, libc headers
clangObjective-C compiler
cmakeBuild system for libobjc2, libdispatch
makeGNU Make
autoconflibs-back configure generation
automakelibs-back build system
libcairo2-dev2D rendering (used internally by libs-opal)
libpng-devPNG image I/O (libs-opal)
libturbojpeg0-devJPEG image I/O
libtiff5-devTIFF image I/O (libs-opal)
libgif-devGIF image I/O
libxml2-devXML parsing (Foundation)
libxslt1-devXSLT processing
libgnutls28-devTLS/SSL
libffi-devForeign function interface
libicu-devUnicode support
libcurl4-openssl-devURL loading
libdbus-1-devD-Bus IPC
libpam0g-devAuthentication
libcups2-devPrinting

New packages for WindowServer.app

PackagePurposeCategory
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 Session/seat managementNOT NEEDED. LoginWindow runs as root, so WindowServer already has permission to open DRM and input devices directly.
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

One-liner install command

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
Note: Devuan uses 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.

5. Phase 1: DRM Backend in libs-back

Phase 1 Get pixels on screen with GPU compositing. A new --enable-server=drm backend.

5.1 New files in libs-back/Source/drm/

FilePurpose
DRMServer.hGSDisplayServer subclass — header
DRMServer.mCore server: DRM init, modesetting, window registry, screen info
DRMServerEvent.mlibinput event loop integrated with NSRunLoop, NSEvent generation
DRMServerWindow.mWindow operations: create, move, order, resize, focus
DRMOutput.h / .mPer-CRTC output: GBM surface, EGL context, page flipping
DRMCompositor.h / .mWraps CARenderer — feeds CALayer tree, drives page flip
GNUmakefileSubproject build rules
GNUmakefile.preambleLink flags: -ldrm -linput -lxkbcommon -ludev

5.2 New files in libs-back/Source/cairo/

FilePurpose
DRMCairoSurface.h / .mCairoSurface subclass — per-window offscreen Cairo image surface. On handleExposeRect:, notifies compositor to recomposite.

5.3 Modified existing files

FileChange
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.

5.4 DRMServer initialization flow

-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:

5.5 Compositing with Core Animation

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).

5.6 Input handling

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:]

6. Phase 2: WindowServer.app Application

Phase 2 A standalone NSApplication that uses the DRM backend, runs as PID 1 of the graphical session.

Application structure

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

GNUstep APIs used in WindowServer.app

APIUsage
NSApplicationMain event loop, application lifecycle
NSRunLoopDRM fd, libinput fd, DO port monitoring
NSEventAll input event representation and dispatch
NSScreenDisplay info from DRM connector/mode data
NSImage / NSBitmapImageRepCursor images, window thumbnails
NSNotificationCenterInternal compositor events (window added, removed, resized)
NSUserDefaultsConfiguration (resolution, refresh rate, cursor size)
NSTimerAnimation timing, idle detection
GSThemeWindow decoration rendering (Phase 4)
CALayer / CARendererGPU-accelerated window compositing
CABasicAnimationWindow animations (minimize, zoom, fade)
CGContext / CGImage2D rendering for window contents

Startup flow

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()

7. Phase 3: Multi-Process Client-Server

Phase 3 Client GNUstep apps run as separate processes, communicating with WindowServer.app via Distributed Objects.

Protocol (using GNUstep DO)

@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

Shared memory rendering path

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()

WSClientServer (new libs-back server type)

New GSDisplayServer subclass for client apps

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.

8. Phase 4: Window Decorations & Menus

Phase 4 Server-side window chrome and global menu bar.

Server-Side Window Decorations

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.

Global Menu Bar

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.

9. Phase 5: Multi-Monitor, VT Switching, Polish

Phase 5 Production readiness.

10. Key Reference Files

FileWhy 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

11. Verification Plan

Phase 1 Smoke Test

# 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

Phase 2 Integration Test

# Launch WindowServer from TTY:
/System/Library/Services/WindowServer.app/WindowServer

# Should see: desktop background, hardware cursor
# Keyboard and mouse input should work

Phase 3 Full Test

# 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)