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.
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.
typedef struct config_t *Config;
Config config_create(void);
void config_destroy(Config cfg);
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
- Practical Design Patterns: Opaque Pointers and Objects in C - Memfault
- Opaque Pointer - Wikipedia
- DCL12-C: Implement abstract data types using opaque types - SEI CERT C Coding Standard
- Opaque Pointers - mbedded.ninja
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.
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
- MEM12-C: Using goto chain for error handling - SEI CERT C Coding Standard
- Using goto for error handling in C - Eli Bendersky
- Using goto for Exception Handling in C - GeeksforGeeks
- Error handling via GOTO in C - Ayende Rahien
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.
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
- Virtual Method Table - Wikipedia
- Inheritance and Polymorphism in C - Embedded Artistry
- Object-Oriented Programming in C - Quantum Leaps (PDF)
- Programming embedded systems: polymorphism in C - Embedded.com
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.
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
- Factory Method Pattern - Refactoring Guru
- Factory Method Pattern - Wikipedia
- Design Patterns - SourceMaking
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.
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
- Observer Pattern - Refactoring Guru
- Observer Pattern - Wikipedia
- Observer Design Pattern - Microsoft Learn
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.
// 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
- Singleton Pattern - Refactoring Guru
- Singleton - Game Programming Patterns
- Singleton Pattern - Wikipedia
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.
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
- UNIX daemonization and the double fork - Juan Tapiador
- Understanding Daemons - Digital Bunker
- Daemon Example in C - Lloyd Rochester
- Double Fork - Michal Goral
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
- Extended Window Manager Hints Specification - freedesktop.org
- Extended Window Manager Hints - Wikipedia
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 handlingWM_DELETE_WINDOW- Graceful window closingWM_NAME/_NET_WM_NAME- Window titlesWM_CLASS- Application classificationWM_HINTS- Window hints (urgency, input model)
Research Sources
- Inter-Client Communication Conventions Manual - X.Org
- ICCCM Reference - Christophe Tronche
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 completesXEMBED_FOCUS_IN/OUT- Focus coordinationXEMBED_WINDOW_ACTIVATE- Window activation
Implementation
systray.c implements the embedder side, reparenting tray icons
and forwarding click events.
Research Sources
- XEmbed Protocol Specification - freedesktop.org
- XEmbed Spec Wiki - freedesktop.org
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
- DWN acquires
_NET_SYSTEM_TRAY_S0selection - Application sends
SYSTEM_TRAY_REQUEST_DOCKclient message - DWN creates embedding window and reparents icon
- DWN sends
XEMBED_EMBEDDED_NOTIFYto icon - Click events forwarded to icon window
Supported Applications
nm-applet, blueman-applet, Telegram, pasystray, udiskie, and any XEmbed-compatible tray icon.
Research Sources
- System Tray Protocol Specification - freedesktop.org
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 executeTryExec- Check if binary existsHidden- Entry is disabledOnlyShowIn/NotShowIn- Desktop environment filtersTerminal- Run in terminal
Implementation
applauncher.c parses .desktop files for the application menu.
autostart.c parses autostart entries.
Research Sources
- Desktop Entry Specification - freedesktop.org
- Desktop Entries - ArchWiki
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=truedisables an entryTryExecprevents running if binary missing
Research Sources
- Desktop Application Autostart Specification - freedesktop.org
- XDG Autostart - ArchWiki
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 notificationCloseNotification- Dismiss a notificationGetCapabilities- Query supported featuresGetServerInformation- Server metadata
Research Sources
- Desktop Notifications Specification - freedesktop.org
- Desktop Notifications - ArchWiki
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.
// 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
- libcurl multi interface overview - curl.se
- libcurl programming tutorial - curl.se
- multi-app.c example - curl.se
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.
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 declarationssrc/module.c- Private implementation- Static functions for internal logic
- module_init() / module_cleanup() lifecycle
Module Responsibilities
| Module | Responsibility |
|---|---|
| main.c | Event loop, initialization orchestration |
| client.c | Window management, focus handling |
| workspace.c | Virtual desktop management |
| layout.c | Tiling algorithms |
| panel.c | UI panels and widgets |
| systray.c | System tray protocol |
| notifications.c | D-Bus notification daemon |
| autostart.c | XDG autostart support |
| config.c | Configuration parsing |
| ai.c | AI integration |
Research Sources
- Modular Programming - Wikipedia
- Separation of Concerns - Wikipedia
- Modular Programming in C - TheCloudStrap
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
const char *config_get_terminal(void) {
if (dwn != NULL && dwn->config != NULL) {
return dwn->config->terminal;
}
return "xterm"; // Safe fallback
}
strncpy(cfg->terminal, value, sizeof(cfg->terminal) - 1);
cfg->terminal[sizeof(cfg->terminal) - 1] = '\0'; // Guarantee null termination
Research Sources
- API00-C: Functions should validate their parameters - SEI CERT
- Defensive programming: the good, the bad and the ugly - Enterprise Craftsmanship
- Defensive Programming - Penn State (PDF)
Complete Research Sources
C Design Patterns
- Practical Design Patterns: Opaque Pointers and Objects in C - Memfault
- SEI CERT C Coding Standard - Carnegie Mellon University
- Design Patterns Catalog - Refactoring Guru
- Design Patterns - SourceMaking
- Embedded Artistry Field Atlas - Embedded Artistry
- Object-Oriented Programming in C - Quantum Leaps
X11 and freedesktop.org
- Extended Window Manager Hints (EWMH) - freedesktop.org
- XEmbed Protocol - freedesktop.org
- System Tray Protocol - freedesktop.org
- Desktop Notifications - freedesktop.org
- Desktop Application Autostart - freedesktop.org
- Desktop Entry Specification - freedesktop.org
Libraries and APIs
- libcurl multi interface - curl.se
- D-Bus Specification - freedesktop.org
- X Window System Documentation - X.Org
Unix Programming
- UNIX daemonization and the double fork - Juan Tapiador
- Using goto for error handling in C - Eli Bendersky
- Game Programming Patterns - Robert Nystrom