Design Patterns & Architecture

Comprehensive documentation of the design patterns, architectural decisions, and research that shaped DWN's implementation.

Overview

DWN is built using professional C design patterns that provide encapsulation, modularity, and maintainability without the overhead of C++. This document details the patterns used, the rationale behind architectural decisions, and links to authoritative sources.

Design Philosophy

DWN prioritizes simplicity, readability, and defensive programming. Each module has a single responsibility with well-defined interfaces. The codebase follows the principle: "Simple is better than complex."

C Design Patterns

While C lacks native object-oriented features, these patterns provide equivalent functionality with minimal overhead.

Opaque Pointer Pattern

Purpose

Hide implementation details from API consumers, enabling changes without recompilation.

How It Works

The header declares a pointer to an incomplete type. The struct definition exists only in the implementation file, preventing direct member access.

Header (public)
typedef struct config_t *Config;
Config config_create(void);
void config_destroy(Config cfg);
Implementation (private)
struct config_t {
    int border_width;
    char terminal[128];
};

Benefits

  • API consumers cannot access internal fields directly
  • Implementation can change without breaking client code
  • Enforces encapsulation at compile time

Used In

config.c - Configuration management

Research Sources

Goto Cleanup Pattern

Purpose

Centralize resource cleanup in functions that acquire multiple resources, preventing memory leaks and ensuring proper deallocation on all code paths.

How It Works

Resources are initialized to safe values (NULL). On error, execution jumps to a cleanup label. The cleanup section safely releases all resources.

Example
int process_file(const char *path) {
    char *buf = NULL;
    FILE *f = NULL;
    int status = -1;

    buf = malloc(1024);
    if (!buf) goto cleanup;

    f = fopen(path, "r");
    if (!f) goto cleanup;

    // ... processing ...
    status = 0;

cleanup:
    free(buf);       // safe: free(NULL) is no-op
    if (f) fclose(f);
    return status;
}

Benefits

  • Single cleanup point prevents code duplication
  • All error paths properly release resources
  • Used extensively in Linux kernel and SQLite

Used In

config.c, news.c, ai.c - File and network operations

Research Sources

Vtable Polymorphism

Purpose

Enable runtime polymorphism in C using function pointer tables, allowing different implementations to share a common interface.

How It Works

A struct of function pointers (vtable) defines the interface. Objects contain a pointer to their vtable. Calling through the vtable invokes the correct implementation.

Example
typedef struct Widget Widget;
typedef struct {
    void (*draw)(Widget *self);
    void (*destroy)(Widget *self);
} WidgetVtable;

struct Widget {
    const WidgetVtable *vtable;
    int x, y, width, height;
};

// Usage: widget->vtable->draw(widget);

Benefits

  • Runtime dispatch without language support
  • New implementations added without modifying existing code
  • Same pattern used by C++ compilers internally

Used In

panel.c - Widget rendering system

Research Sources

Factory Pattern

Purpose

Encapsulate object creation logic, allowing the system to create objects without specifying their exact types.

How It Works

A factory function takes parameters describing what to create and returns a pointer to the appropriate object type.

Example
typedef enum { WIDGET_BUTTON, WIDGET_LABEL } WidgetType;

Widget *widget_create(WidgetType type) {
    switch (type) {
        case WIDGET_BUTTON: return button_create();
        case WIDGET_LABEL:  return label_create();
        default: return NULL;
    }
}

Benefits

  • Centralizes object creation logic
  • New types added without changing client code
  • Supports Open/Closed Principle

Used In

config.c, client.c, workspace.c - Object lifecycle management

Research Sources

Observer Pattern

Purpose

Implement event-driven communication where subjects notify observers of state changes without tight coupling.

How It Works

Observers register callback functions with subjects. When events occur, the subject iterates through registered callbacks and invokes them.

Example
typedef void (*EventCallback)(void *data);

void events_register(EventType type, EventCallback cb, void *data);
void events_emit(EventType type);

// Notification triggers all registered callbacks

Benefits

  • Loose coupling between event sources and handlers
  • Observers added/removed without modifying subjects
  • Foundation of event-driven programming

Used In

keys.c - Keyboard event handling, notifications.c - D-Bus signals

Research Sources

Singleton / Global State

Purpose

Provide a single, globally accessible instance of the window manager state, simplifying module communication.

Implementation in DWN

DWN uses a single DWNState structure accessible via the global dwn pointer. This is appropriate for a window manager where exactly one instance exists per X11 session.

Example
// Global state pointer
extern DWNState *dwn;

// Access from any module
if (dwn != NULL && dwn->config != NULL) {
    return dwn->config->terminal;
}

Rationale

  • Window manager is inherently a singleton per X session
  • Simplifies inter-module communication
  • All state centralized for easier debugging

Research Sources

Double Fork Daemon Pattern

Purpose

Launch background processes that are fully detached from the parent, preventing zombie processes and terminal reattachment.

How It Works

The pattern uses two fork() calls: the first creates a child that calls setsid() to become a session leader, then forks again. The grandchild cannot reacquire a controlling terminal.

Implementation in DWN
int spawn_async(const char *cmd) {
    pid_t pid = fork();
    if (pid == 0) {
        setsid();              // New session
        pid_t pid2 = fork();   // Second fork
        if (pid2 == 0) {
            execl("/bin/sh", "sh", "-c", cmd, NULL);
            _exit(EXIT_FAILURE);
        }
        _exit(EXIT_SUCCESS);   // Intermediate exits
    }
    waitpid(pid, &status, 0);  // Only wait for intermediate
    return 0;
}

Benefits

  • Process fully detached from parent
  • No zombie processes (intermediate is reaped immediately)
  • Cannot reacquire controlling terminal
  • Non-blocking for the caller

Used In

util.c - spawn_async(), autostart.c, applauncher.c

Research Sources

X11 Window Manager Protocols

DWN implements several freedesktop.org specifications for cross-desktop compatibility.

Extended Window Manager Hints (EWMH)

Purpose

Define interactions between window managers, compositing managers, applications, and desktop utilities in a standardized way.

Key Features Implemented

  • _NET_SUPPORTED - List of supported hints
  • _NET_CLIENT_LIST - List of managed windows
  • _NET_CURRENT_DESKTOP - Active workspace
  • _NET_WM_STATE - Window states (fullscreen, maximized)
  • _NET_ACTIVE_WINDOW - Currently focused window
  • _NET_WM_WINDOW_TYPE - Window type classification

Implementation

atoms.c manages X11 atom creation and EWMH property updates. Properties are updated on window focus changes, workspace switches, and state changes.

Research Sources

Inter-Client Communication Conventions Manual (ICCCM)

Purpose

Define low-level conventions for X11 client communication, including selections, window management, and session management.

Key Features Implemented

  • WM_PROTOCOLS - Window close handling
  • WM_DELETE_WINDOW - Graceful window closing
  • WM_NAME / _NET_WM_NAME - Window titles
  • WM_CLASS - Application classification
  • WM_HINTS - Window hints (urgency, input model)

Research Sources

XEmbed Protocol

Purpose

Enable embedding of controls from one application into another, forming the basis of the system tray implementation.

How It Works

The embedder (DWN panel) acts as a window manager for embedded clients. Client windows are reparented into the embedder, and events are coordinated through XEMBED messages.

Key Messages

  • XEMBED_EMBEDDED_NOTIFY - Sent when embedding completes
  • XEMBED_FOCUS_IN/OUT - Focus coordination
  • XEMBED_WINDOW_ACTIVATE - Window activation

Implementation

systray.c implements the embedder side, reparenting tray icons and forwarding click events.

Research Sources

System Tray Protocol

Purpose

Enable applications to display status icons in a desktop panel, providing a standardized notification area.

How It Works

DWN acquires the _NET_SYSTEM_TRAY_S0 selection to become the tray manager. Applications send SYSTEM_TRAY_REQUEST_DOCK messages to dock their icons.

Docking Process

  1. DWN acquires _NET_SYSTEM_TRAY_S0 selection
  2. Application sends SYSTEM_TRAY_REQUEST_DOCK client message
  3. DWN creates embedding window and reparents icon
  4. DWN sends XEMBED_EMBEDDED_NOTIFY to icon
  5. Click events forwarded to icon window

Supported Applications

nm-applet, blueman-applet, Telegram, pasystray, udiskie, and any XEmbed-compatible tray icon.

Research Sources

XDG Specifications

Desktop Entry Specification

Purpose

Standard format for application metadata files (.desktop files) used by application launchers and autostart systems.

Key Fields Parsed

  • Exec - Command to execute
  • TryExec - Check if binary exists
  • Hidden - Entry is disabled
  • OnlyShowIn / NotShowIn - Desktop environment filters
  • Terminal - Run in terminal

Implementation

applauncher.c parses .desktop files for the application menu. autostart.c parses autostart entries.

Research Sources

Desktop Application Autostart Specification

Purpose

Define standard locations and format for applications that should start automatically when the user logs in.

Directories Scanned

  • /etc/xdg/autostart/ - System-wide autostart
  • ~/.config/autostart/ - User autostart
  • ~/.config/dwn/autostart.d/ - DWN-specific (symlinks)

Key Behavior

  • User entries override system entries with same filename
  • Hidden=true disables an entry
  • TryExec prevents running if binary missing

Research Sources

Desktop Notifications Specification

Purpose

Standard D-Bus interface for applications to display passive notifications to users without blocking.

D-Bus Interface

DWN implements org.freedesktop.Notifications on the session bus at path /org/freedesktop/Notifications.

Key Methods

  • Notify - Display a notification
  • CloseNotification - Dismiss a notification
  • GetCapabilities - Query supported features
  • GetServerInformation - Server metadata

Research Sources

Async Programming Patterns

libcurl Multi Interface

Purpose

Perform HTTP requests asynchronously without blocking the main event loop, essential for AI features and news fetching.

How It Works

Instead of blocking on network I/O, the multi interface allows the main loop to poll for completion. curl_multi_perform() advances transfers incrementally.

Integration Pattern
// In event loop (16ms intervals)
curl_multi_perform(multi_handle, &running);
CURLMsg *msg = curl_multi_info_read(multi_handle, &msgs_left);
if (msg && msg->msg == CURLMSG_DONE) {
    // Request completed, process response
}

Implementation

ai.c uses curl_multi for OpenRouter API calls. Responses processed in ai_process_pending() called from main loop.

Research Sources

pthread for Background I/O

Purpose

Offload blocking operations to separate threads when async APIs are unavailable or impractical.

Implementation

news.c spawns a detached thread for RSS fetching. Mutex guards shared state, atomic flags prevent concurrent fetches.

Pattern
static pthread_mutex_t news_mutex = PTHREAD_MUTEX_INITIALIZER;
static atomic_int fetch_running = 0;

void news_fetch_async(void) {
    if (atomic_load(&fetch_running)) return;
    atomic_store(&fetch_running, 1);
    pthread_create(&fetch_thread, NULL, fetch_thread_func, NULL);
}

Modular Architecture

Design Principle

Each module has a single responsibility with well-defined interfaces. Modules communicate through the global dwn state and function calls, never through shared mutable state.

Module Structure

  • include/module.h - Public API declarations
  • src/module.c - Private implementation
  • Static functions for internal logic
  • module_init() / module_cleanup() lifecycle

Module Responsibilities

Module Responsibility
main.cEvent loop, initialization orchestration
client.cWindow management, focus handling
workspace.cVirtual desktop management
layout.cTiling algorithms
panel.cUI panels and widgets
systray.cSystem tray protocol
notifications.cD-Bus notification daemon
autostart.cXDG autostart support
config.cConfiguration parsing
ai.cAI integration

Research Sources

Defensive Programming

Core Principles

  • Null Checks - All pointer parameters validated before use
  • Bounds Checking - Array indices and string lengths verified
  • Return Value Checking - malloc(), fopen(), etc. checked for failure
  • String Safety - strncpy() with size-1, explicit null termination
  • Assertions - assert() for programmer errors in debug builds

Examples in DWN

Null Check Pattern
const char *config_get_terminal(void) {
    if (dwn != NULL && dwn->config != NULL) {
        return dwn->config->terminal;
    }
    return "xterm";  // Safe fallback
}
String Safety Pattern
strncpy(cfg->terminal, value, sizeof(cfg->terminal) - 1);
cfg->terminal[sizeof(cfg->terminal) - 1] = '\0';  // Guarantee null termination

Research Sources

Complete Research Sources

C Design Patterns

X11 and freedesktop.org

Libraries and APIs

Unix Programming

Community Resources