feat: introduce v2.0 abstraction layer with core types, backend interface, and plugin system

feat: add fade effects with API control and real-time event subscription
test: add API integration tests with isolated test environment and coverage reporting
build: update Makefile for recursive source finding, dynamic linking, and test targets
docs: document abstraction layer, fade effects, and API enhancements in README
This commit is contained in:
retoor 2026-02-07 13:04:52 +01:00
parent 877d3f302c
commit 6b143990bb
181 changed files with 20648 additions and 7200 deletions

View File

@ -5,7 +5,7 @@
CC = gcc
CFLAGS = -Wall -Wextra -Wpedantic -Wshadow -O2 -I./include
CFLAGS += -fstack-protector-strong -D_FORTIFY_SOURCE=2
LDFLAGS = -lX11 -lXext -lXinerama -lXrandr -lXft -lfontconfig -ldbus-1 -lcurl -lm -lpthread -lXtst -lXi
LDFLAGS = -lX11 -lXext -lXinerama -lXrandr -lXft -lfontconfig -ldbus-1 -lcurl -lm -lpthread -lXtst -lXi -ldl
# Directories
SRC_DIR = src
@ -13,9 +13,9 @@ INC_DIR = include
BUILD_DIR = build
BIN_DIR = bin
# Find all source files automatically
SRCS = $(wildcard $(SRC_DIR)/*.c)
OBJS = $(SRCS:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o)
# Find all source files automatically (including subdirectories)
SRCS = $(shell find $(SRC_DIR) -name '*.c' -type f)
OBJS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRCS))
DEPS = $(OBJS:.o=.d)
# Output binary
@ -78,6 +78,11 @@ help:
@echo " make deps - Install dependencies (needs sudo)"
@echo " make help - Show this help"
@echo ""
@echo "API TESTING:"
@echo " make test - Run API integration tests"
@echo " make test-quick - Run quick tests (skip OCR)"
@echo " make test-coverage- Run tests with coverage report"
@echo ""
@echo "AFTER INSTALL:"
@echo " 1. Log out of your current session"
@echo " 2. At login screen, select 'DWN' as your session"
@ -108,6 +113,7 @@ $(TARGET): $(OBJS) | $(BIN_DIR)
# Compile each source file
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR)
@echo "Compiling $<..."
@mkdir -p $(dir $@)
@$(CC) $(CFLAGS) -MMD -MP -c $< -o $@
# Create build directory
@ -271,6 +277,60 @@ deps:
exit 1; \
fi
# =============================================================================
# API INTEGRATION TESTS
# =============================================================================
TEST_PORT ?= 18777
TEST_DISPLAY ?= :99
.PHONY: test test-quick test-coverage test-isolated
test:
@echo "Running API integration tests..."
@echo "Note: DWN must be running with API enabled (port 8777)"
@cd tests && python -m pytest -v
test-quick:
@echo "Running quick API tests (skipping OCR)..."
@cd tests && python -m pytest -v -x --ignore=test_ocr_commands.py
test-coverage:
@echo "Running API tests with coverage..."
@cd tests && python -m pytest --cov=. --cov-report=term-missing
test-isolated: $(TARGET)
@echo "Starting isolated DWN test instance on port $(TEST_PORT)..."
@-kill $$(cat /tmp/dwn_test_dwn.pid 2>/dev/null) 2>/dev/null; true
@-kill $$(cat /tmp/dwn_test_xephyr.pid 2>/dev/null) 2>/dev/null; true
@rm -f /tmp/dwn_test_xephyr.pid /tmp/dwn_test_dwn.pid /tmp/dwn_test.log
@sleep 1
@Xephyr $(TEST_DISPLAY) -screen 1280x720 2>/dev/null & echo $$! > /tmp/dwn_test_xephyr.pid
@sleep 2
@DISPLAY=$(TEST_DISPLAY) $(TARGET) -p $(TEST_PORT) > /tmp/dwn_test.log 2>&1 & echo $$! > /tmp/dwn_test_dwn.pid
@sleep 3
@for i in 1 2 3 4 5; do \
if curl -s http://localhost:$(TEST_PORT)/api/status >/dev/null 2>&1; then \
echo "DWN API ready on port $(TEST_PORT)"; \
break; \
fi; \
echo "Waiting for DWN API... ($$i/5)"; \
sleep 1; \
done
@if ! curl -s http://localhost:$(TEST_PORT)/api/status >/dev/null 2>&1; then \
echo "ERROR: DWN API not responding. Check /tmp/dwn_test.log"; \
cat /tmp/dwn_test.log 2>/dev/null | tail -50; \
kill $$(cat /tmp/dwn_test_dwn.pid 2>/dev/null) 2>/dev/null || true; \
kill $$(cat /tmp/dwn_test_xephyr.pid 2>/dev/null) 2>/dev/null || true; \
exit 1; \
fi
@DWN_TEST_PORT=$(TEST_PORT) python -m pytest tests/ -v; \
EXIT_CODE=$$?; \
kill $$(cat /tmp/dwn_test_dwn.pid 2>/dev/null) 2>/dev/null || true; \
kill $$(cat /tmp/dwn_test_xephyr.pid 2>/dev/null) 2>/dev/null || true; \
rm -f /tmp/dwn_test_xephyr.pid /tmp/dwn_test_dwn.pid; \
exit $$EXIT_CODE
# =============================================================================
# CODE QUALITY (for developers)
# =============================================================================

108
README.md
View File

@ -79,6 +79,7 @@ DWN prioritizes a seamless, distraction-free desktop experience:
**Built-in Widgets**
- Battery: Percentage display, charging indicator, multi-battery support
- Volume: Click for slider, scroll to adjust, right-click to mute
- Fade effects: Click "S:X.X" or "I:XX%" to adjust animation speed and glow intensity
- Process monitors: Rotating display of top CPU/memory consumers
### Ambient Glow Effects
@ -167,6 +168,37 @@ Programmatic control on port 8777:
| `run_command` | Execute shell command |
| `screenshot` | Capture screen |
| `ocr` | Extract text from image |
| `get_fade_settings` | Get fade effect settings |
| `set_fade_speed` | Set animation speed (0.1-3.0) |
| `set_fade_intensity` | Set glow intensity (0.0-1.0) |
**Event Subscription**
Subscribe to real-time events including fade changes:
```bash
# Subscribe to fade events
python3 examples/dwn_api_client.py subscribe fade_speed_changed fade_intensity_changed
# Listen for all events
python3 examples/dwn_api_client.py listen
```
**Fade Control Example**
```bash
# Get current fade settings
python3 examples/dwn_api_client.py fade-settings
# Set fade speed (faster animation)
python3 examples/dwn_api_client.py fade-speed 1.5
# Set fade intensity (dimmer glow)
python3 examples/dwn_api_client.py fade-intensity 0.5
# Run interactive demo
python3 examples/fade_control_demo.py
```
### Automation
@ -436,6 +468,61 @@ window_timeout = 5000 # 1000-30000ms
| api.c | WebSocket server |
| util.c | Logging, memory, string utilities, glow effects |
### New Abstraction Layer (v2.0)
DWN now includes a modern abstraction layer for future extensibility:
**Core Abstractions** (`include/core/`)
- `wm_types.h` - Abstract handles, geometry, colors, events
- `wm_string.h/c` - Safe dynamic strings with automatic memory management
- `wm_list.h/c` - Dynamic array container with sorting and iteration
- `wm_hashmap.h/c` - Hash table with automatic resizing
- `wm_client.h/c` - Abstract client type with bidirectional legacy sync
**Backend Interface** (`include/backends/`)
- `backend_interface.h` - Backend-agnostic vtable (80+ operations)
- `x11/x11_backend.h/c` - X11 implementation with event translation
- Designed for future Wayland and headless backends
**Plugin System** (`include/plugins/`)
- `layout_plugin.h` - Layout plugin API with state management
- `widget_plugin.h` - Widget plugin API for panel components
- Built-in layouts: tiling, floating, monocle, grid
- Support for dynamic plugin loading
The abstraction layer maintains **100% API compatibility** with existing code while enabling:
- Backend portability (X11 → Wayland migration path)
- Dynamic plugin loading for layouts and widgets
- Type-safe handles replacing void* casts
- Memory-safe string and container operations
### Design Patterns
**Encapsulation**
- Opaque pointer types hide internal structures
- Header exposes only public API
- Implementation details remain private
**Error Handling**
- Status code return values
- Output parameters for results
- Enum-based error codes
**Resource Management**
- Goto cleanup pattern for multi-resource functions
- Every allocation has corresponding free
- XGrabServer/XUngrabServer for atomic X11 operations
**Event Architecture**
- Select-based multiplexed I/O
- 60fps animation loop with 16ms timeout
- Async request queues for AI/screenshot/OCR
**Naming Conventions**
- Module prefix for functions: `client_focus()`, `workspace_switch()`
- Snake_case for functions and variables
- CamelCase for types and structs
### Design Patterns
**Encapsulation**
@ -518,13 +605,20 @@ ws.close()
```
dwn/
├── src/ # Implementation files
├── include/ # Header files
├── config/ # Configuration templates
├── scripts/ # Utility scripts
├── examples/ # API usage examples
├── site/ # Documentation website
└── build/ # Build artifacts
├── src/ # Implementation files
│ ├── core/ # Abstract core types (string, list, hashmap, client)
│ ├── backends/x11/ # X11 backend implementation
│ └── plugins/ # Plugin system (layout, widget managers)
│ └── layouts/ # Built-in layout plugins
├── include/ # Header files
│ ├── core/ # Core abstraction headers
│ ├── backends/ # Backend interface headers
│ └── plugins/ # Plugin API headers
├── manual/ # Documentation website (HTML)
├── config/ # Configuration templates
├── scripts/ # Utility scripts
├── examples/ # API usage examples
└── build/ # Build artifacts
```
## License

BIN
bin/dwn

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -19,7 +19,7 @@ build/client.o: src/client.c include/client.h include/dwn.h \
/usr/include/dbus-1.0/dbus/dbus-signature.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
/usr/include/dbus-1.0/dbus/dbus-threads.h include/layout.h \
include/panel.h include/api.h
include/panel.h include/api.h include/rules.h include/marks.h
include/client.h:
include/dwn.h:
include/atoms.h:
@ -49,3 +49,5 @@ include/notifications.h:
include/layout.h:
include/panel.h:
include/api.h:
include/rules.h:
include/marks.h:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -19,7 +19,7 @@ build/keys.o: src/keys.c include/keys.h include/dwn.h include/client.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
/usr/include/dbus-1.0/dbus/dbus-threads.h include/news.h \
include/applauncher.h include/decorations.h include/demo.h \
include/layout.h include/api.h
include/layout.h include/api.h include/marks.h
include/keys.h:
include/dwn.h:
include/client.h:
@ -52,3 +52,4 @@ include/decorations.h:
include/demo.h:
include/layout.h:
include/api.h:
include/marks.h:

Binary file not shown.

Binary file not shown.

View File

@ -19,9 +19,12 @@ build/main.o: src/main.c include/dwn.h include/config.h include/dwn.h \
/usr/include/dbus-1.0/dbus/dbus-signature.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
/usr/include/dbus-1.0/dbus/dbus-threads.h include/systray.h \
include/news.h include/applauncher.h include/ai.h include/autostart.h \
include/services.h include/api.h include/demo.h include/screenshot.h \
include/ocr.h include/util.h
include/slider.h include/news.h include/applauncher.h include/ai.h \
include/autostart.h include/services.h include/api.h include/demo.h \
include/screenshot.h include/ocr.h include/util.h include/slider.h \
include/rules.h include/marks.h include/core/wm_client.h \
include/core/wm_types.h include/core/wm_string.h include/core/wm_types.h \
include/plugins/layout_plugin.h include/plugins/builtin_layouts.h
include/dwn.h:
include/config.h:
include/dwn.h:
@ -52,6 +55,7 @@ include/notifications.h:
/usr/include/dbus-1.0/dbus/dbus-syntax.h:
/usr/include/dbus-1.0/dbus/dbus-threads.h:
include/systray.h:
include/slider.h:
include/news.h:
include/applauncher.h:
include/ai.h:
@ -62,3 +66,12 @@ include/demo.h:
include/screenshot.h:
include/ocr.h:
include/util.h:
include/slider.h:
include/rules.h:
include/marks.h:
include/core/wm_client.h:
include/core/wm_types.h:
include/core/wm_string.h:
include/core/wm_types.h:
include/plugins/layout_plugin.h:
include/plugins/builtin_layouts.h:

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,7 @@
build/panel.o: src/panel.c include/panel.h include/dwn.h \
include/workspace.h include/layout.h include/client.h include/config.h \
include/util.h include/atoms.h include/systray.h include/news.h
include/util.h include/atoms.h include/systray.h include/slider.h \
include/news.h
include/panel.h:
include/dwn.h:
include/workspace.h:
@ -10,4 +11,5 @@ include/config.h:
include/util.h:
include/atoms.h:
include/systray.h:
include/slider.h:
include/news.h:

Binary file not shown.

View File

@ -1,6 +1,6 @@
build/systray.o: src/systray.c include/systray.h include/dwn.h \
include/panel.h include/config.h include/util.h include/notifications.h \
/usr/include/dbus-1.0/dbus/dbus.h \
include/slider.h include/panel.h include/config.h include/util.h \
include/notifications.h /usr/include/dbus-1.0/dbus/dbus.h \
/usr/lib/x86_64-linux-gnu/dbus-1.0/include/dbus/dbus-arch-deps.h \
/usr/include/dbus-1.0/dbus/dbus-macros.h \
/usr/include/dbus-1.0/dbus/dbus-address.h \
@ -17,9 +17,10 @@ build/systray.o: src/systray.c include/systray.h include/dwn.h \
/usr/include/dbus-1.0/dbus/dbus-server.h \
/usr/include/dbus-1.0/dbus/dbus-signature.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
/usr/include/dbus-1.0/dbus/dbus-threads.h include/atoms.h
/usr/include/dbus-1.0/dbus/dbus-threads.h include/atoms.h include/api.h
include/systray.h:
include/dwn.h:
include/slider.h:
include/panel.h:
include/config.h:
include/util.h:
@ -43,3 +44,4 @@ include/notifications.h:
/usr/include/dbus-1.0/dbus/dbus-syntax.h:
/usr/include/dbus-1.0/dbus/dbus-threads.h:
include/atoms.h:
include/api.h:

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -10,6 +10,11 @@
#include "dwn.h"
#include <stdbool.h>
/* Forward declarations for libcurl types */
struct curl_slist;
struct Curl_easy;
typedef struct Curl_easy DWN_CURL;
typedef enum {
AI_STATE_IDLE,
AI_STATE_PENDING,
@ -24,6 +29,8 @@ typedef struct AIRequest {
void (*callback)(struct AIRequest *req);
void *user_data;
struct AIRequest *next;
struct curl_slist *headers; /* Stored for proper cleanup */
struct Curl_easy *curl_handle; /* Stored for cancellation */
} AIRequest;
typedef struct {
@ -74,6 +81,8 @@ typedef struct ExaRequest {
void (*callback)(struct ExaRequest *req);
void *user_data;
struct ExaRequest *next;
struct curl_slist *headers; /* Stored for proper cleanup */
struct Curl_easy *curl_handle; /* Stored for cancellation */
} ExaRequest;
bool exa_is_available(void);

View File

@ -55,6 +55,23 @@ typedef enum {
EVENT_NOTIFICATION_SHOWN,
EVENT_NOTIFICATION_CLOSED,
EVENT_FADE_SPEED_CHANGED,
EVENT_FADE_INTENSITY_CHANGED,
EVENT_WINDOW_SNAPPED,
EVENT_AUDIO_VOLUME_CHANGED,
EVENT_AUDIO_MUTE_TOGGLED,
EVENT_PANEL_VISIBILITY_CHANGED,
EVENT_CONFIG_RELOADED,
EVENT_AI_RESPONSE_RECEIVED,
EVENT_EXA_SEARCH_COMPLETED,
EVENT_NEWS_ARTICLE_CHANGED,
EVENT_DEMO_STARTED,
EVENT_DEMO_STOPPED,
EVENT_DEMO_PHASE_CHANGED,
EVENT_TUTORIAL_STARTED,
EVENT_TUTORIAL_STOPPED,
EVENT_COUNT
} ApiEventType;
@ -104,4 +121,21 @@ void api_emit_show_desktop_toggled(bool shown);
void api_emit_notification_shown(unsigned int id, const char *summary, const char *body);
void api_emit_notification_closed(unsigned int id);
void api_emit_fade_speed_changed(float old_speed, float new_speed);
void api_emit_fade_intensity_changed(float old_intensity, float new_intensity);
void api_emit_window_snapped(Window window, int horizontal, int vertical);
void api_emit_audio_volume_changed(int old_volume, int new_volume);
void api_emit_audio_mute_toggled(bool muted);
void api_emit_panel_visibility_changed(const char *panel, bool visible);
void api_emit_config_reloaded(void);
void api_emit_ai_response_received(const char *prompt, const char *response, bool success);
void api_emit_exa_search_completed(const char *query, int result_count, bool success);
void api_emit_news_article_changed(int old_index, int new_index, const char *title);
void api_emit_demo_started(void);
void api_emit_demo_stopped(void);
void api_emit_demo_phase_changed(int phase, const char *phase_name);
void api_emit_tutorial_started(void);
void api_emit_tutorial_stopped(void);
#endif

View File

@ -0,0 +1,311 @@
/*
* DWN - Desktop Window Manager
* Backend Abstraction Interface
*/
#ifndef BACKEND_INTERFACE_H
#define BACKEND_INTERFACE_H
#include "core/wm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Backend Capabilities
*============================================================================*/
typedef enum {
WM_BACKEND_CAP_WINDOWS = (1 << 0),
WM_BACKEND_CAP_COMPOSITING = (1 << 1),
WM_BACKEND_CAP_TRANSPARENCY = (1 << 2),
WM_BACKEND_CAP_ANIMATIONS = (1 << 3),
WM_BACKEND_CAP_MULTI_MONITOR = (1 << 4),
WM_BACKEND_CAP_INPUT_EVENTS = (1 << 5),
WM_BACKEND_CAP_CLIPBOARD = (1 << 6),
WM_BACKEND_CAP_DRAG_DROP = (1 << 7),
WM_BACKEND_CAP_TOUCH = (1 << 8),
WM_BACKEND_CAP_GESTURES = (1 << 9)
} WmBackendCapabilities;
/*==============================================================================
* Backend Information
*============================================================================*/
typedef struct {
const char *name;
const char *version;
const char *description;
uint32_t capabilities;
bool supports_multiple_instances;
bool requires_compositor;
} WmBackendInfo;
/*==============================================================================
* Backend Event Types
*============================================================================*/
typedef enum {
WM_BACKEND_EVENT_NONE,
WM_BACKEND_EVENT_EXPOSE,
WM_BACKEND_EVENT_CONFIGURE,
WM_BACKEND_EVENT_MAP,
WM_BACKEND_EVENT_UNMAP,
WM_BACKEND_EVENT_DESTROY,
WM_BACKEND_EVENT_FOCUS_IN,
WM_BACKEND_EVENT_FOCUS_OUT,
WM_BACKEND_EVENT_KEY_PRESS,
WM_BACKEND_EVENT_KEY_RELEASE,
WM_BACKEND_EVENT_BUTTON_PRESS,
WM_BACKEND_EVENT_BUTTON_RELEASE,
WM_BACKEND_EVENT_MOTION,
WM_BACKEND_EVENT_ENTER,
WM_BACKEND_EVENT_LEAVE,
WM_BACKEND_EVENT_PROPERTY,
WM_BACKEND_EVENT_CLIENT_MESSAGE,
WM_BACKEND_EVENT_SELECTION,
} WmBackendEventType;
typedef struct {
WmBackendEventType type;
WmWindowHandle window;
WmTime timestamp;
union {
struct { int x, y, width, height; } configure;
struct { int x, y; } point;
struct { unsigned int keycode; unsigned int state; } key;
struct { unsigned int button; int x, y; unsigned int state; } button;
struct { const char *name; const void *data; size_t size; } property;
} data;
} WmBackendEvent;
/*==============================================================================
* Backend Interface Definition
*============================================================================*/
typedef struct BackendInterface {
/* Identification */
const char *name;
WmBackendInfo (*get_info)(void);
/* Lifecycle */
bool (*init)(void *config);
void (*shutdown)(void);
bool (*is_initialized)(void);
/* Connection */
bool (*connect)(void);
void (*disconnect)(void);
bool (*is_connected)(void);
int (*get_file_descriptor)(void);
void (*flush)(void);
void (*sync)(void);
/* Screen/Display */
void (*get_screen_dimensions)(int *width, int *height);
int (*get_screen_count)(void);
void (*get_screen_geometry)(int screen, WmRect *geometry);
/* Monitor Management */
int (*get_monitor_count)(void);
void (*get_monitor_geometry)(int monitor, WmRect *geometry);
bool (*get_monitor_primary)(int monitor);
const char* (*get_monitor_name)(int monitor);
/* Work Area */
void (*get_work_area)(int monitor, WmRect *area);
void (*set_work_area)(int monitor, const WmRect *area);
/* Window Management - Lifecycle */
WmWindowHandle (*window_create)(const WmRect *geometry, uint32_t flags);
void (*window_destroy)(WmWindowHandle window);
WmWindowHandle (*window_create_frame)(WmWindowHandle parent, const WmRect *geometry);
/* Window Management - Geometry */
void (*window_get_geometry)(WmWindowHandle window, WmRect *geometry);
void (*window_set_geometry)(WmWindowHandle window, const WmRect *geometry);
void (*window_move)(WmWindowHandle window, int x, int y);
void (*window_resize)(WmWindowHandle window, int width, int height);
void (*window_move_resize)(WmWindowHandle window, const WmRect *geometry);
/* Window Management - Visibility */
void (*window_show)(WmWindowHandle window);
void (*window_hide)(WmWindowHandle window);
bool (*window_is_visible)(WmWindowHandle window);
void (*window_map)(WmWindowHandle window);
void (*window_unmap)(WmWindowHandle window);
bool (*window_is_mapped)(WmWindowHandle window);
/* Window Management - Stacking */
void (*window_raise)(WmWindowHandle window);
void (*window_lower)(WmWindowHandle window);
void (*window_raise_above)(WmWindowHandle window, WmWindowHandle above);
void (*window_lower_below)(WmWindowHandle window, WmWindowHandle below);
void (*window_set_stack_position)(WmWindowHandle window, int position);
int (*window_get_stack_position)(WmWindowHandle window);
/* Window Management - Reparenting */
void (*window_reparent)(WmWindowHandle window, WmWindowHandle parent, int x, int y);
WmWindowHandle (*window_get_parent)(WmWindowHandle window);
/* Window Management - Focus */
void (*window_focus)(WmWindowHandle window);
void (*window_unfocus)(WmWindowHandle window);
bool (*window_is_focused)(WmWindowHandle window);
WmWindowHandle (*window_get_focused)(void);
/* Window Management - Decoration */
void (*window_set_decorated)(WmWindowHandle window, bool decorated);
bool (*window_is_decorated)(WmWindowHandle window);
void (*window_set_border_width)(WmWindowHandle window, int width);
int (*window_get_border_width)(WmWindowHandle window);
void (*window_set_border_color)(WmWindowHandle window, WmColor color);
/* Window Management - Properties */
void (*window_set_title)(WmWindowHandle window, const char *title);
char* (*window_get_title)(WmWindowHandle window);
void (*window_set_class)(WmWindowHandle window, const char *class, const char *instance);
void (*window_get_class)(WmWindowHandle window, char **class, char **instance);
void (*window_set_icon_name)(WmWindowHandle window, const char *name);
/* Window Management - Protocols */
bool (*window_supports_protocol)(WmWindowHandle window, const char *protocol);
void (*window_send_protocol)(WmWindowHandle window, const char *protocol, WmTime timestamp);
void (*window_close)(WmWindowHandle window);
void (*window_kill)(WmWindowHandle window);
/* Window Management - State */
void (*window_set_fullscreen)(WmWindowHandle window, bool fullscreen);
bool (*window_is_fullscreen)(WmWindowHandle window);
void (*window_set_maximized)(WmWindowHandle window, bool maximized);
bool (*window_is_maximized)(WmWindowHandle window);
void (*window_set_minimized)(WmWindowHandle window, bool minimized);
bool (*window_is_minimized)(WmWindowHandle window);
void (*window_set_modal)(WmWindowHandle window, bool modal);
void (*window_set_sticky)(WmWindowHandle window, bool sticky);
void (*window_set_shaded)(WmWindowHandle window, bool shaded);
void (*window_set_skip_taskbar)(WmWindowHandle window, bool skip);
void (*window_set_skip_pager)(WmWindowHandle window, bool skip);
void (*window_set_urgent)(WmWindowHandle window, bool urgent);
bool (*window_is_urgent)(WmWindowHandle window);
/* Window Management - Type */
void (*window_set_type)(WmWindowHandle window, WmWindowType type);
WmWindowType (*window_get_type)(WmWindowHandle window);
/* Window Management - Selection */
void (*window_set_selection_owner)(WmWindowHandle window, const char *selection, WmTime time);
WmWindowHandle (*window_get_selection_owner)(const char *selection);
void (*window_convert_selection)(WmWindowHandle requestor, const char *selection,
const char *target, WmTime time);
/* Window Management - Client List */
WmContainer* (*window_get_stacking_list)(void);
WmContainer* (*window_get_client_list)(void);
/* Input - Events */
bool (*poll_event)(WmBackendEvent *event);
void (*wait_event)(WmBackendEvent *event);
bool (*check_mask_event)(uint32_t mask, WmBackendEvent *event);
void (*put_back_event)(const WmBackendEvent *event);
/* Input - Key Grabbing */
void (*grab_key)(int keycode, uint32_t modifiers, WmWindowHandle window,
bool owner_events, uint32_t pointer_mode, uint32_t keyboard_mode);
void (*ungrab_key)(int keycode, uint32_t modifiers, WmWindowHandle window);
void (*grab_keyboard)(WmWindowHandle window, bool owner_events,
uint32_t pointer_mode, uint32_t keyboard_mode, WmTime time);
void (*ungrab_keyboard)(WmTime time);
/* Input - Button Grabbing */
void (*grab_button)(int button, uint32_t modifiers, WmWindowHandle window,
bool owner_events, uint32_t event_mask,
uint32_t pointer_mode, uint32_t keyboard_mode,
WmWindowHandle confine_to, uint32_t cursor);
void (*ungrab_button)(int button, uint32_t modifiers, WmWindowHandle window);
void (*grab_pointer)(WmWindowHandle window, bool owner_events, uint32_t event_mask,
uint32_t pointer_mode, uint32_t keyboard_mode,
WmWindowHandle confine_to, uint32_t cursor, WmTime time);
void (*ungrab_pointer)(WmTime time);
/* Input - Pointer */
void (*query_pointer)(WmWindowHandle window, int *root_x, int *root_y,
int *win_x, int *win_y, uint32_t *mask);
void (*warp_pointer)(WmWindowHandle dest_w, int dest_x, int dest_y);
void (*set_cursor)(WmWindowHandle window, uint32_t cursor);
/* Rendering - Basic */
void (*fill_rectangle)(WmWindowHandle window, const WmRect *rect, WmColor color);
void (*draw_rectangle)(WmWindowHandle window, const WmRect *rect, WmColor color, int line_width);
void (*draw_line)(WmWindowHandle window, int x1, int y1, int x2, int y2,
WmColor color, int line_width);
void (*clear_window)(WmWindowHandle window);
void (*copy_area)(WmWindowHandle src, WmWindowHandle dst,
int src_x, int src_y, int width, int height,
int dst_x, int dst_y);
/* Rendering - Text (if supported) */
void* (*font_load)(const char *name, int size);
void (*font_destroy)(void *font);
int (*font_text_width)(void *font, const char *text, int len);
void (*draw_text)(WmWindowHandle window, void *font, int x, int y,
const char *text, int len, WmColor color);
/* Rendering - Images (if supported) */
void* (*image_load)(const uint8_t *data, size_t size);
void (*image_destroy)(void *image);
void (*image_get_size)(void *image, int *width, int *height);
void (*draw_image)(WmWindowHandle window, void *image, int x, int y,
int width, int height);
/* Rendering - Sync */
void (*begin_paint)(WmWindowHandle window);
void (*end_paint)(WmWindowHandle window);
/* Atoms/Properties */
uint32_t (*atom_get)(const char *name);
const char* (*atom_get_name)(uint32_t atom);
void (*property_set)(WmWindowHandle window, uint32_t property, uint32_t type,
int format, const void *data, int num_items);
int (*property_get)(WmWindowHandle window, uint32_t property, uint32_t type,
void **data, int *num_items);
void (*property_delete)(WmWindowHandle window, uint32_t property);
/* Session Management */
void (*set_selection_owner)(WmWindowHandle owner, const char *selection);
void (*send_client_message)(WmWindowHandle window, const char *message_type,
const void *data, int format);
/* Error Handling */
int (*get_last_error)(void);
void (*set_error_handler)(int (*handler)(void *display, void *event));
void (*set_io_error_handler)(int (*handler)(void *display));
} BackendInterface;
/*==============================================================================
* Backend Registration
*============================================================================*/
typedef const BackendInterface* (*BackendEntryFunc)(void);
void wm_backend_register(const char *name, BackendEntryFunc entry);
const BackendInterface* wm_backend_get(const char *name);
WmContainer* wm_backend_get_available(void);
/*==============================================================================
* Backend Helpers
*============================================================================*/
static inline bool wm_backend_has_capability(const BackendInterface *backend,
WmBackendCapabilities cap) {
WmBackendInfo info = backend->get_info();
return (info.capabilities & cap) != 0;
}
#ifdef __cplusplus
}
#endif
#endif /* BACKEND_INTERFACE_H */

View File

@ -0,0 +1,35 @@
/*
* DWN - Desktop Window Manager
* X11 Backend Header
*/
#ifndef X11_BACKEND_H
#define X11_BACKEND_H
#include "backends/backend_interface.h"
#include <X11/Xlib.h>
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* X11 Backend Access
*============================================================================*/
const BackendInterface* x11_backend_get_interface(void);
Display* x11_backend_get_display(void);
Window x11_backend_get_root(void);
int x11_backend_get_screen(void);
static inline bool x11_backend_is_available(void) {
const BackendInterface *iface = x11_backend_get_interface();
return iface && iface->init(NULL);
}
#ifdef __cplusplus
}
#endif
#endif /* X11_BACKEND_H */

View File

@ -68,6 +68,10 @@ struct Config {
int demo_ai_timeout_ms;
int demo_window_timeout_ms;
/* Fade effects */
float fade_speed; /* Animation speed multiplier (0.1 - 3.0) */
float fade_intensity; /* Glow intensity (0.0 - 1.0) */
char color_panel_bg[16];
char color_panel_fg[16];
char color_workspace_active[16];
@ -88,6 +92,7 @@ void config_destroy(Config *cfg);
bool config_load(Config *cfg, const char *path);
bool config_reload(Config *cfg);
void config_set_defaults(Config *cfg);
void config_validate(Config *cfg);
const char *config_get_terminal(void);
const char *config_get_launcher(void);
@ -96,6 +101,10 @@ int config_get_title_height(void);
int config_get_panel_height(void);
int config_get_gap(void);
const ColorScheme *config_get_colors(void);
float config_get_fade_speed(void);
float config_get_fade_intensity(void);
void config_set_fade_speed(float speed);
void config_set_fade_intensity(float intensity);
typedef void (*ConfigCallback)(const char *section, const char *key,
const char *value, void *user_data);

523
include/core/wm_client.h Normal file
View File

@ -0,0 +1,523 @@
/*
* DWN - Desktop Window Manager
* Abstract Client Interface
*/
#ifndef WM_CLIENT_H
#define WM_CLIENT_H
#include "core/wm_types.h"
#include "core/wm_string.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Forward declarations */
typedef struct Client Client; /* Legacy client structure */
/*==============================================================================
* Abstract Client Type
*============================================================================*/
typedef struct AbstractClient AbstractClient;
/*==============================================================================
* Client State Flags
*============================================================================*/
typedef enum {
WM_CLIENT_STATE_NORMAL = 0,
WM_CLIENT_STATE_FLOATING = (1 << 0),
WM_CLIENT_STATE_FULLSCREEN = (1 << 1),
WM_CLIENT_STATE_MAXIMIZED = (1 << 2),
WM_CLIENT_STATE_MINIMIZED = (1 << 3),
WM_CLIENT_STATE_URGENT = (1 << 4),
WM_CLIENT_STATE_STICKY = (1 << 5),
WM_CLIENT_STATE_HIDDEN = (1 << 6),
WM_CLIENT_STATE_FOCUS = (1 << 7),
WM_CLIENT_STATE_MAPPED = (1 << 8),
WM_CLIENT_STATE_MANAGED = (1 << 9),
WM_CLIENT_STATE_DESTROYING = (1 << 10)
} WmClientState;
/*==============================================================================
* Client Window Type
*============================================================================*/
typedef enum {
WM_CLIENT_TYPE_UNKNOWN = 0,
WM_CLIENT_TYPE_NORMAL,
WM_CLIENT_TYPE_DIALOG,
WM_CLIENT_TYPE_DOCK,
WM_CLIENT_TYPE_DESKTOP,
WM_CLIENT_TYPE_TOOLBAR,
WM_CLIENT_TYPE_MENU,
WM_CLIENT_TYPE_UTILITY,
WM_CLIENT_TYPE_SPLASH,
WM_CLIENT_TYPE_NOTIFICATION
} WmClientType;
/*==============================================================================
* Client Lifecycle
*============================================================================*/
/**
* Create an abstract client from a native window handle.
* This takes ownership of the native window.
*/
AbstractClient* wm_client_create(WmWindowHandle window, WmClientType type);
/**
* Destroy a client and release all associated resources.
*/
void wm_client_destroy(AbstractClient *client);
/**
* Check if a client is valid (not NULL and not being destroyed).
*/
bool wm_client_is_valid(const AbstractClient *client);
/*==============================================================================
* Native Access (for compatibility during migration)
*============================================================================*/
/**
* Get the native window handle.
* Note: This should be avoided in backend-agnostic code.
*/
WmWindowHandle wm_client_get_window(const AbstractClient *client);
/**
* Get the native frame window handle (if reparented).
*/
WmWindowHandle wm_client_get_frame(const AbstractClient *client);
/**
* Get the legacy Client structure.
* Note: This is for gradual migration only.
*/
Client* wm_client_get_legacy(const AbstractClient *client);
/**
* Create an abstract client wrapper around an existing legacy Client.
* Note: The abstract client does NOT own the legacy client.
*/
AbstractClient* wm_client_from_legacy(Client *client);
/*==============================================================================
* Client Identification
*============================================================================*/
/**
* Get the unique client ID.
*/
WmClientId wm_client_get_id(const AbstractClient *client);
/**
* Get the client window type.
*/
WmClientType wm_client_get_type(const AbstractClient *client);
/**
* Set the client window type.
*/
void wm_client_set_type(AbstractClient *client, WmClientType type);
/*==============================================================================
* Client State
*============================================================================*/
/**
* Get the current state flags.
*/
WmClientState wm_client_get_state(const AbstractClient *client);
/**
* Set state flags.
*/
void wm_client_set_state(AbstractClient *client, WmClientState state);
/**
* Add state flags.
*/
void wm_client_add_state(AbstractClient *client, WmClientState state);
/**
* Remove state flags.
*/
void wm_client_remove_state(AbstractClient *client, WmClientState state);
/**
* Toggle state flags.
*/
void wm_client_toggle_state(AbstractClient *client, WmClientState state);
/**
* Check if a specific state is set.
*/
bool wm_client_has_state(const AbstractClient *client, WmClientState state);
/* Convenience state checks */
static inline bool wm_client_is_floating(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_FLOATING);
}
static inline bool wm_client_is_fullscreen(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_FULLSCREEN);
}
static inline bool wm_client_is_maximized(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_MAXIMIZED);
}
static inline bool wm_client_is_minimized(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_MINIMIZED);
}
static inline bool wm_client_is_urgent(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_URGENT);
}
static inline bool wm_client_is_sticky(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_STICKY);
}
static inline bool wm_client_is_focused(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_FOCUS);
}
static inline bool wm_client_is_mapped(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_MAPPED);
}
/*==============================================================================
* Client Geometry
*============================================================================*/
/**
* Get the client geometry (position and size).
*/
WmRect wm_client_get_geometry(const AbstractClient *client);
/**
* Set the client geometry.
*/
void wm_client_set_geometry(AbstractClient *client, const WmRect *geometry);
/**
* Get the client position.
*/
WmPoint wm_client_get_position(const AbstractClient *client);
/**
* Set the client position.
*/
void wm_client_set_position(AbstractClient *client, int x, int y);
/**
* Get the client size.
*/
WmSize wm_client_get_size(const AbstractClient *client);
/**
* Set the client size.
*/
void wm_client_set_size(AbstractClient *client, int width, int height);
/**
* Get the border width.
*/
int wm_client_get_border_width(const AbstractClient *client);
/**
* Set the border width.
*/
void wm_client_set_border_width(AbstractClient *client, int width);
/**
* Store previous geometry (for restore after maximize/fullscreen).
*/
void wm_client_save_geometry(AbstractClient *client);
/**
* Restore previous geometry.
*/
void wm_client_restore_geometry(AbstractClient *client);
/*==============================================================================
* Client Workspace
*============================================================================*/
/**
* Get the client's workspace ID.
*/
WmWorkspaceId wm_client_get_workspace(const AbstractClient *client);
/**
* Set the client's workspace ID.
*/
void wm_client_set_workspace(AbstractClient *client, WmWorkspaceId workspace);
/**
* Check if the client is on a specific workspace.
*/
bool wm_client_is_on_workspace(const AbstractClient *client, WmWorkspaceId workspace);
/**
* Check if the client is visible on the current workspace.
*/
bool wm_client_is_visible_on_current(const AbstractClient *client);
/*==============================================================================
* Client Properties
*============================================================================*/
/**
* Get the client title.
* Returns a reference to the internal string (do not free).
*/
const char* wm_client_get_title(const AbstractClient *client);
/**
* Set the client title.
*/
void wm_client_set_title(AbstractClient *client, const char *title);
/**
* Get the client class (application class).
*/
const char* wm_client_get_class(const AbstractClient *client);
/**
* Set the client class.
*/
void wm_client_set_class(AbstractClient *client, const char *class_name);
/**
* Get the client instance name.
*/
const char* wm_client_get_instance(const AbstractClient *client);
/**
* Set the client instance name.
*/
void wm_client_set_instance(AbstractClient *client, const char *instance);
/**
* Get the client's taskbar color.
*/
WmColor wm_client_get_color(const AbstractClient *client);
/**
* Set the client's taskbar color.
*/
void wm_client_set_color(AbstractClient *client, WmColor color);
/*==============================================================================
* Client Actions
*============================================================================*/
/**
* Focus the client.
*/
void wm_client_focus(AbstractClient *client);
/**
* Unfocus the client.
*/
void wm_client_unfocus(AbstractClient *client);
/**
* Raise the client to the top of the stack.
*/
void wm_client_raise(AbstractClient *client);
/**
* Lower the client to the bottom of the stack.
*/
void wm_client_lower(AbstractClient *client);
/**
* Show/map the client window.
*/
void wm_client_show(AbstractClient *client);
/**
* Hide/unmap the client window.
*/
void wm_client_hide(AbstractClient *client);
/**
* Minimize the client.
*/
void wm_client_minimize(AbstractClient *client);
/**
* Restore the client from minimized state.
*/
void wm_client_restore(AbstractClient *client);
/**
* Close the client (politely request close).
*/
void wm_client_close(AbstractClient *client);
/**
* Kill the client (forcefully terminate).
*/
void wm_client_kill(AbstractClient *client);
/*==============================================================================
* Client Management
*============================================================================*/
/**
* Reparent the client into a frame window.
*/
void wm_client_reparent(AbstractClient *client, WmWindowHandle frame);
/**
* Unreparent the client from its frame.
*/
void wm_client_unreparent(AbstractClient *client);
/**
* Configure the client (apply size hints, constraints).
*/
void wm_client_configure(AbstractClient *client);
/**
* Apply size hints to constrain width/height.
*/
void wm_client_apply_size_hints(AbstractClient *client, int *width, int *height);
/**
* Move and resize a client.
*/
void wm_client_move_resize(AbstractClient *client, int x, int y, int width, int height);
/*==============================================================================
* Client List Management
*============================================================================*/
/**
* Get the next client in the global list.
*/
AbstractClient* wm_client_get_next(const AbstractClient *client);
/**
* Get the previous client in the global list.
*/
AbstractClient* wm_client_get_prev(const AbstractClient *client);
/**
* Get the first client in the global list.
*/
AbstractClient* wm_client_get_first(void);
/**
* Get the last client in the global list.
*/
AbstractClient* wm_client_get_last(void);
/**
* Get the next client in MRU (Most Recently Used) order.
*/
AbstractClient* wm_client_get_next_mru(const AbstractClient *client);
/**
* Get the previous client in MRU order.
*/
AbstractClient* wm_client_get_prev_mru(const AbstractClient *client);
/*==============================================================================
* Client Lookup
*============================================================================*/
/**
* Find a client by its window handle.
*/
AbstractClient* wm_client_find_by_window(WmWindowHandle window);
/**
* Find a client by its frame handle.
*/
AbstractClient* wm_client_find_by_frame(WmWindowHandle frame);
/**
* Find a client by its ID.
*/
AbstractClient* wm_client_find_by_id(WmClientId id);
/*==============================================================================
* Client Iteration
*============================================================================*/
typedef bool (*WmClientForeachFunc)(AbstractClient *client, void *user_data);
/**
* Iterate over all clients.
*/
void wm_client_foreach(WmClientForeachFunc func, void *user_data);
/**
* Iterate over clients on a specific workspace.
*/
void wm_client_foreach_on_workspace(WmWorkspaceId workspace,
WmClientForeachFunc func, void *user_data);
/**
* Count total clients.
*/
int wm_client_count(void);
/**
* Count clients on a specific workspace.
*/
int wm_client_count_on_workspace(WmWorkspaceId workspace);
/*==============================================================================
* Client Manager
*============================================================================*/
typedef struct WmClientManager WmClientManager;
/**
* Get the global client manager.
*/
WmClientManager* wm_client_manager_get(void);
/**
* Initialize the client manager.
*/
bool wm_client_manager_init(void);
/**
* Shutdown the client manager.
*/
void wm_client_manager_shutdown(void);
/**
* Register a client with the manager.
*/
void wm_client_manager_add(WmClientManager *mgr, AbstractClient *client);
/**
* Unregister a client from the manager.
*/
void wm_client_manager_remove(WmClientManager *mgr, AbstractClient *client);
/**
* Move a client to the front of the MRU list.
*/
void wm_client_manager_touch(WmClientManager *mgr, AbstractClient *client);
/**
* Get the focused client.
*/
AbstractClient* wm_client_manager_get_focused(const WmClientManager *mgr);
/**
* Set the focused client.
*/
void wm_client_manager_set_focused(WmClientManager *mgr, AbstractClient *client);
#ifdef __cplusplus
}
#endif
#endif /* WM_CLIENT_H */

220
include/core/wm_container.h Normal file
View File

@ -0,0 +1,220 @@
/*
* DWN - Desktop Window Manager
* Generic Container Interface
*/
#ifndef WM_CONTAINER_H
#define WM_CONTAINER_H
#include "wm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Container Lifecycle
*============================================================================*/
WmContainer* wm_container_create(WmContainerType type, WmDestroyFunc destroy_fn);
void wm_container_destroy(WmContainer *container);
void wm_container_clear(WmContainer *container);
WmContainer* wm_container_clone(const WmContainer *container, WmCloneFunc clone_fn);
/*==============================================================================
* Container Properties
*============================================================================*/
WmContainerType wm_container_get_type(const WmContainer *container);
size_t wm_container_size(const WmContainer *container);
bool wm_container_is_empty(const WmContainer *container);
/*==============================================================================
* List Operations
*============================================================================*/
/* These work for LIST and QUEUE */
void wm_list_append(WmContainer *list, void *data);
void wm_list_prepend(WmContainer *list, void *data);
void wm_list_insert(WmContainer *list, size_t index, void *data);
bool wm_list_remove(WmContainer *list, void *data);
void* wm_list_remove_at(WmContainer *list, size_t index);
void* wm_list_get(const WmContainer *list, size_t index);
void* wm_list_get_first(const WmContainer *list);
void* wm_list_get_last(const WmContainer *list);
size_t wm_list_index_of(const WmContainer *list, void *data);
bool wm_list_contains(const WmContainer *list, void *data);
void* wm_list_find(const WmContainer *list, WmCompareFunc cmp, const void *key);
void* wm_list_find_custom(const WmContainer *list,
bool (*predicate)(const void *item, const void *user_data),
const void *user_data);
void wm_list_sort(WmContainer *list, WmCompareFunc cmp);
void wm_list_reverse(WmContainer *list);
/*==============================================================================
* Array Operations
*============================================================================*/
void wm_array_append(WmContainer *array, void *data);
void wm_array_insert(WmContainer *array, size_t index, void *data);
void wm_array_set(WmContainer *array, size_t index, void *data);
void* wm_array_get(const WmContainer *array, size_t index);
void* wm_array_remove_at(WmContainer *array, size_t index);
void wm_array_resize(WmContainer *array, size_t new_size);
void wm_array_reserve(WmContainer *array, size_t capacity);
size_t wm_array_capacity(const WmContainer *array);
void wm_array_compact(WmContainer *array);
void wm_array_sort(WmContainer *array, WmCompareFunc cmp);
/*==============================================================================
* HashMap Operations
*============================================================================*/
/* Key types supported */
typedef enum {
WM_HASH_KEY_STRING,
WM_HASH_KEY_INT,
WM_HASH_KEY_POINTER
} WmHashKeyType;
WmContainer* wm_hashmap_create(WmHashKeyType key_type, WmDestroyFunc destroy_fn);
void wm_hashmap_insert(WmContainer *map, const void *key, void *value);
void* wm_hashmap_get(const WmContainer *map, const void *key);
bool wm_hashmap_contains(const WmContainer *map, const void *key);
bool wm_hashmap_remove(WmContainer *map, const void *key);
void* wm_hashmap_lookup(const WmContainer *map, WmCompareFunc cmp, const void *key);
WmContainer* wm_hashmap_get_keys(const WmContainer *map);
WmContainer* wm_hashmap_get_values(const WmContainer *map);
void wm_hashmap_rehash(WmContainer *map, size_t new_capacity);
float wm_hashmap_load_factor(const WmContainer *map);
/* Convenience functions for string keys */
void wm_hashmap_insert_string(WmContainer *map, const char *key, void *value);
void* wm_hashmap_get_string(const WmContainer *map, const char *key);
bool wm_hashmap_contains_string(const WmContainer *map, const char *key);
bool wm_hashmap_remove_string(WmContainer *map, const char *key);
/* Convenience functions for int keys */
void wm_hashmap_insert_int(WmContainer *map, int key, void *value);
void* wm_hashmap_get_int(const WmContainer *map, int key);
bool wm_hashmap_contains_int(const WmContainer *map, int key);
bool wm_hashmap_remove_int(WmContainer *map, int key);
/*==============================================================================
* Stack Operations
*============================================================================*/
void wm_stack_push(WmContainer *stack, void *data);
void* wm_stack_pop(WmContainer *stack);
void* wm_stack_peek(const WmContainer *stack);
/*==============================================================================
* Queue Operations
*============================================================================*/
void wm_queue_enqueue(WmContainer *queue, void *data);
void* wm_queue_dequeue(WmContainer *queue);
void* wm_queue_peek(const WmContainer *queue);
void* wm_queue_peek_tail(const WmContainer *queue);
/*==============================================================================
* Tree Operations (if type is TREE)
*============================================================================*/
typedef struct WmTreeNode WmTreeNode;
WmTreeNode* wm_tree_get_root(const WmContainer *tree);
WmTreeNode* wm_tree_node_get_left(const WmTreeNode *node);
WmTreeNode* wm_tree_node_get_right(const WmTreeNode *node);
WmTreeNode* wm_tree_node_get_parent(const WmTreeNode *node);
void* wm_tree_node_get_data(const WmTreeNode *node);
/*==============================================================================
* Iteration
*============================================================================*/
WmIterator* wm_container_iterator(const WmContainer *container);
WmIterator* wm_container_iterator_reverse(const WmContainer *container);
void wm_iterator_destroy(WmIterator *it);
bool wm_iterator_has_next(const WmIterator *it);
void* wm_iterator_next(WmIterator *it);
const void* wm_iterator_peek(const WmIterator *it);
/* HashMap iteration - returns key-value pairs */
typedef struct {
const void *key;
void *value;
} WmHashEntry;
WmIterator* wm_hashmap_iterator(const WmContainer *map);
WmHashEntry* wm_hashmap_iterator_next(WmIterator *it);
/*==============================================================================
* Functional Operations
*============================================================================*/
typedef void (*WmForeachFunc)(void *item, void *user_data);
typedef void* (*WmMapFunc)(const void *item, void *user_data);
typedef bool (*WmFilterFunc)(const void *item, void *user_data);
typedef void* (*WmReduceFunc)(void *accumulator, const void *item, void *user_data);
void wm_container_foreach(const WmContainer *container, WmForeachFunc func, void *user_data);
WmContainer* wm_container_map(const WmContainer *container, WmContainerType new_type,
WmMapFunc func, WmDestroyFunc destroy_fn, void *user_data);
WmContainer* wm_container_filter(const WmContainer *container,
WmFilterFunc predicate, void *user_data);
void* wm_container_reduce(const WmContainer *container,
WmReduceFunc func, void *initial, void *user_data);
bool wm_container_all_match(const WmContainer *container,
WmFilterFunc predicate, void *user_data);
bool wm_container_any_match(const WmContainer *container,
WmFilterFunc predicate, void *user_data);
/*==============================================================================
* Convenience Constructors
*============================================================================*/
static inline WmContainer* wm_list_create(WmDestroyFunc destroy_fn) {
return wm_container_create(WM_CONTAINER_LIST, destroy_fn);
}
static inline WmContainer* wm_array_create(WmDestroyFunc destroy_fn) {
return wm_container_create(WM_CONTAINER_ARRAY, destroy_fn);
}
static inline WmContainer* wm_hashmap_create_string(WmDestroyFunc destroy_fn) {
return wm_hashmap_create(WM_HASH_KEY_STRING, destroy_fn);
}
static inline WmContainer* wm_queue_create(WmDestroyFunc destroy_fn) {
return wm_container_create(WM_CONTAINER_QUEUE, destroy_fn);
}
static inline WmContainer* wm_stack_create(WmDestroyFunc destroy_fn) {
return wm_container_create(WM_CONTAINER_STACK, destroy_fn);
}
#ifdef __cplusplus
}
#endif
#endif /* WM_CONTAINER_H */

265
include/core/wm_event.h Normal file
View File

@ -0,0 +1,265 @@
/*
* DWN - Desktop Window Manager
* Event Bus System
*/
#ifndef WM_EVENT_H
#define WM_EVENT_H
#include "wm_types.h"
#include "wm_client.h"
#include "wm_string.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Event Data Structures
*============================================================================*/
typedef struct {
WmEventType type;
WmTime timestamp;
WmId source_id;
} WmEventHeader;
/* Window Events */
typedef struct {
WmEventHeader header;
AbstractClient *client;
} WmEventWindowBase;
typedef struct {
WmEventHeader header;
AbstractClient *client;
} WmEventWindowCreated;
typedef struct {
WmEventHeader header;
AbstractClient *client;
WmString *old_title;
WmString *new_title;
} WmEventWindowTitleChanged;
typedef struct {
WmEventHeader header;
AbstractClient *client;
WmRect old_geometry;
WmRect new_geometry;
} WmEventWindowMoved;
typedef struct {
WmEventHeader header;
AbstractClient *client;
WmRect old_geometry;
WmRect new_geometry;
} WmEventWindowResized;
typedef struct {
WmEventHeader header;
AbstractClient *client;
WmClientFlags old_flags;
WmClientFlags new_flags;
} WmEventWindowStateChanged;
/* Workspace Events */
typedef struct {
WmEventHeader header;
WmWorkspaceId workspace;
} WmEventWorkspaceBase;
typedef struct {
WmEventHeader header;
WmWorkspaceId old_workspace;
WmWorkspaceId new_workspace;
} WmEventWorkspaceSwitched;
typedef struct {
WmEventHeader header;
WmWorkspaceId workspace;
WmLayoutType old_layout;
WmLayoutType new_layout;
} WmEventWorkspaceLayoutChanged;
typedef struct {
WmEventHeader header;
WmWorkspaceId workspace;
AbstractClient *client;
} WmEventClientWorkspaceChanged;
/* Input Events */
typedef struct {
WmEventHeader header;
unsigned int keycode;
unsigned int keysym;
unsigned int modifiers;
const char *key_name;
} WmEventKey;
typedef struct {
WmEventHeader header;
unsigned int button;
int x;
int y;
unsigned int modifiers;
} WmEventButton;
typedef struct {
WmEventHeader header;
int x;
int y;
int delta_x;
int delta_y;
unsigned int modifiers;
} WmEventMotion;
typedef struct {
WmEventHeader header;
int delta;
int x;
int y;
} WmEventScroll;
/* Monitor Events */
typedef struct {
WmEventHeader header;
int monitor_index;
WmRect geometry;
bool primary;
} WmEventMonitorAdded;
typedef struct {
WmEventHeader header;
int monitor_index;
} WmEventMonitorRemoved;
/* Configuration Events */
typedef struct {
WmEventHeader header;
WmString *config_path;
bool success;
WmString *error_message;
} WmEventConfigReloaded;
/* Command Events */
typedef struct {
WmEventHeader header;
WmCommandType command;
bool success;
WmString *error_message;
} WmEventCommandExecuted;
/* Custom Event */
typedef struct {
WmEventHeader header;
WmString *event_name;
void *custom_data;
WmDestroyFunc destroy_func;
} WmEventCustom;
/*==============================================================================
* Event Bus Lifecycle
*============================================================================*/
WmEventBus* wm_event_bus_create(void);
void wm_event_bus_destroy(WmEventBus *bus);
WmEventBus* wm_event_bus_get_default(void);
void wm_event_bus_set_default(WmEventBus *bus);
/*==============================================================================
* Event Subscription
*============================================================================*/
WmEventSubscription* wm_event_subscribe(WmEventBus *bus,
WmEventType type,
WmEventHandler handler,
void *user_data);
WmEventSubscription* wm_event_subscribe_pattern(WmEventBus *bus,
WmEventType type_min,
WmEventType type_max,
WmEventHandler handler,
void *user_data);
WmEventSubscription* wm_event_subscribe_all(WmEventBus *bus,
WmEventHandler handler,
void *user_data);
void wm_event_unsubscribe(WmEventBus *bus, WmEventSubscription *subscription);
void wm_event_unsubscribe_all(WmEventBus *bus, void *user_data);
/*==============================================================================
* Event Emission
*============================================================================*/
void wm_event_emit(WmEventBus *bus, WmEventType type, const void *event_data);
void wm_event_emit_async(WmEventBus *bus, WmEventType type, const void *event_data);
/* Convenience emitters */
void wm_event_emit_window_created(WmEventBus *bus, AbstractClient *client);
void wm_event_emit_window_destroyed(WmEventBus *bus, AbstractClient *client);
void wm_event_emit_window_focused(WmEventBus *bus, AbstractClient *client, AbstractClient *prev_focus);
void wm_event_emit_window_moved(WmEventBus *bus, AbstractClient *client, const WmRect *old_geom);
void wm_event_emit_window_resized(WmEventBus *bus, AbstractClient *client, const WmRect *old_geom);
void wm_event_emit_window_state_changed(WmEventBus *bus, AbstractClient *client,
WmClientFlags old_flags, WmClientFlags new_flags);
void wm_event_emit_workspace_switched(WmEventBus *bus, WmWorkspaceId old_ws, WmWorkspaceId new_ws);
void wm_event_emit_workspace_layout_changed(WmEventBus *bus, WmWorkspaceId ws,
WmLayoutType old_layout, WmLayoutType new_layout);
/*==============================================================================
* Event Processing
*============================================================================*/
void wm_event_process(WmEventBus *bus);
void wm_event_process_all(WmEventBus *bus);
bool wm_event_process_one(WmEventBus *bus);
void wm_event_dispatch_pending(WmEventBus *bus);
int wm_event_get_pending_count(const WmEventBus *bus);
/*==============================================================================
* Event Data Helpers
*============================================================================*/
WmEventHeader* wm_event_get_header(void *event_data);
WmTime wm_event_get_timestamp(const void *event_data);
const char* wm_event_type_to_string(WmEventType type);
WmEventType wm_event_type_from_string(const char *str);
/*==============================================================================
* Event Blocking/Filtering
*============================================================================*/
void wm_event_begin_block(WmEventBus *bus, WmEventType type);
void wm_event_end_block(WmEventBus *bus, WmEventType type);
bool wm_event_is_blocked(const WmEventBus *bus, WmEventType type);
void wm_event_suppress(WmEventBus *bus, WmEventType type);
void wm_event_unsuppress(WmEventBus *bus, WmEventType type);
bool wm_event_is_suppressed(const WmEventBus *bus, WmEventType type);
/*==============================================================================
* Custom Events
*============================================================================*/
void wm_event_emit_custom(WmEventBus *bus, const char *event_name, void *data, WmDestroyFunc destroy_fn);
WmEventSubscription* wm_event_subscribe_custom(WmEventBus *bus, const char *event_name,
WmEventHandler handler, void *user_data);
/*==============================================================================
* Event Statistics
*============================================================================*/
uint64_t wm_event_get_emit_count(const WmEventBus *bus, WmEventType type);
uint64_t wm_event_get_total_emit_count(const WmEventBus *bus);
void wm_event_reset_statistics(WmEventBus *bus);
#ifdef __cplusplus
}
#endif
#endif /* WM_EVENT_H */

107
include/core/wm_hashmap.h Normal file
View File

@ -0,0 +1,107 @@
/*
* DWN - Desktop Window Manager
* Abstract Hash Map Container
*/
#ifndef WM_HASHMAP_H
#define WM_HASHMAP_H
#include "core/wm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Opaque Types
*============================================================================*/
typedef struct WmHashMap WmHashMap;
/*==============================================================================
* Function Types
*============================================================================*/
typedef uint32_t (*WmHashFunc)(const void *key);
typedef bool (*WmKeyEqualFunc)(const void *a, const void *b);
typedef void (*WmHashForeachFunc)(void *key, void *value, void *user_data);
/*==============================================================================
* Hash Functions
*============================================================================*/
static inline uint32_t wm_hash_ptr(const void *ptr) {
uintptr_t p = (uintptr_t)ptr;
return (uint32_t)(p ^ (p >> 32));
}
static inline uint32_t wm_hash_int(int key) {
return (uint32_t)key;
}
/*==============================================================================
* Lifecycle
*============================================================================*/
WmHashMap* wm_hashmap_new(void);
WmHashMap* wm_hashmap_new_full(WmHashFunc hash_func, WmKeyEqualFunc key_equal,
WmFreeFunc key_free, WmFreeFunc value_free);
WmHashMap* wm_hashmap_new_string_key(void);
void wm_hashmap_destroy(WmHashMap *map);
WmHashMap* wm_hashmap_clone(const WmHashMap *map);
/*==============================================================================
* Capacity
*============================================================================*/
size_t wm_hashmap_size(const WmHashMap *map);
bool wm_hashmap_is_empty(const WmHashMap *map);
void wm_hashmap_clear(WmHashMap *map);
/*==============================================================================
* Element Access
*============================================================================*/
void* wm_hashmap_get(const WmHashMap *map, const void *key);
void* wm_hashmap_get_or_default(const WmHashMap *map, const void *key, void *default_val);
bool wm_hashmap_contains(const WmHashMap *map, const void *key);
/*==============================================================================
* Modifiers
*============================================================================*/
bool wm_hashmap_insert(WmHashMap *map, void *key, void *value);
bool wm_hashmap_insert_no_replace(WmHashMap *map, void *key, void *value);
void* wm_hashmap_set(WmHashMap *map, void *key, void *value);
void* wm_hashmap_remove(WmHashMap *map, const void *key);
bool wm_hashmap_steal(WmHashMap *map, const void *key);
/*==============================================================================
* Iteration
*============================================================================*/
typedef struct {
void *map; /* Opaque pointer to hashmap */
size_t bucket;
void *entry; /* Opaque pointer to current entry */
void *next_entry; /* Opaque pointer to next entry */
} WmHashMapIter;
void wm_hashmap_iter_init(WmHashMapIter *iter, WmHashMap *map);
bool wm_hashmap_iter_next(WmHashMapIter *iter, void **key, void **value);
void wm_hashmap_foreach(const WmHashMap *map, WmHashForeachFunc func, void *user_data);
/*==============================================================================
* String Helpers
*============================================================================*/
bool wm_hashmap_insert_string(WmHashMap *map, const char *key, void *value);
void* wm_hashmap_get_string(const WmHashMap *map, const char *key);
bool wm_hashmap_contains_string(const WmHashMap *map, const char *key);
void* wm_hashmap_remove_string(WmHashMap *map, const char *key);
#ifdef __cplusplus
}
#endif
#endif /* WM_HASHMAP_H */

123
include/core/wm_list.h Normal file
View File

@ -0,0 +1,123 @@
/*
* DWN - Desktop Window Manager
* Abstract List Container
*/
#ifndef WM_LIST_H
#define WM_LIST_H
#include "core/wm_types.h"
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Opaque Types
*============================================================================*/
typedef struct WmList WmList;
/*==============================================================================
* Function Types
*============================================================================*/
typedef void (*WmFreeFunc)(void *item);
typedef int (*WmCompareFunc)(const void *a, const void *b);
typedef bool (*WmForeachFunc)(void *item, size_t index, void *user_data);
typedef void* (*WmCloneFunc)(const void *item);
/*==============================================================================
* Lifecycle
*============================================================================*/
WmList* wm_list_new(void);
WmList* wm_list_new_sized(size_t initial_capacity);
void wm_list_destroy(WmList *list);
void wm_list_destroy_no_free(WmList *list);
WmList* wm_list_clone(const WmList *list);
void wm_list_set_free_func(WmList *list, WmFreeFunc func);
/*==============================================================================
* Capacity
*============================================================================*/
size_t wm_list_size(const WmList *list);
size_t wm_list_capacity(const WmList *list);
bool wm_list_is_empty(const WmList *list);
void wm_list_reserve(WmList *list, size_t capacity);
void wm_list_compact(WmList *list);
void wm_list_clear(WmList *list);
/*==============================================================================
* Modifiers
*============================================================================*/
void wm_list_append(WmList *list, void *item);
void wm_list_prepend(WmList *list, void *item);
void wm_list_insert(WmList *list, size_t index, void *item);
void wm_list_insert_sorted(WmList *list, void *item, WmCompareFunc compare);
void wm_list_remove(WmList *list, void *item);
void wm_list_remove_at(WmList *list, size_t index);
void* wm_list_take_at(WmList *list, size_t index);
bool wm_list_remove_one(WmList *list, void *item);
bool wm_list_remove_all(WmList *list, void *item);
void* wm_list_pop_back(WmList *list);
void* wm_list_pop_front(WmList *list);
/*==============================================================================
* Accessors
*============================================================================*/
void* wm_list_get(const WmList *list, size_t index);
void* wm_list_first(const WmList *list);
void* wm_list_last(const WmList *list);
void* wm_list_front(const WmList *list);
void* wm_list_back(const WmList *list);
void wm_list_set(WmList *list, size_t index, void *item);
/*==============================================================================
* Iteration
*============================================================================*/
void wm_list_foreach(const WmList *list, WmForeachFunc func, void *user_data);
void wm_list_foreach_reverse(const WmList *list, WmForeachFunc func, void *user_data);
/*==============================================================================
* Search
*============================================================================*/
ssize_t wm_list_index_of(const WmList *list, void *item);
bool wm_list_contains(const WmList *list, void *item);
void* wm_list_find(const WmList *list, WmCompareFunc compare, const void *key);
ssize_t wm_list_find_index(const WmList *list, WmCompareFunc compare, const void *key);
/*==============================================================================
* Sorting
*============================================================================*/
void wm_list_sort(WmList *list, WmCompareFunc compare);
/*==============================================================================
* Data Operations
*============================================================================*/
void** wm_list_data(WmList *list);
void* wm_list_steal(WmList *list, size_t index);
WmList* wm_list_slice(const WmList *list, size_t start, size_t end);
void wm_list_move(WmList *list, size_t from, size_t to);
void wm_list_swap(WmList *list, size_t i, size_t j);
/*==============================================================================
* Comparison
*============================================================================*/
bool wm_list_equals(const WmList *list1, const WmList *list2);
#ifdef __cplusplus
}
#endif
#endif /* WM_LIST_H */

222
include/core/wm_string.h Normal file
View File

@ -0,0 +1,222 @@
/*
* DWN - Desktop Window Manager
* Abstract String Type
*/
#ifndef WM_STRING_H
#define WM_STRING_H
#include "wm_types.h"
#include <stdarg.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* String Lifecycle
*============================================================================*/
WmString* wm_string_new(const char *str);
WmString* wm_string_new_empty(void);
WmString* wm_string_new_sized(size_t capacity);
WmString* wm_string_new_printf(const char *fmt, ...);
WmString* wm_string_new_vprintf(const char *fmt, va_list args);
WmString* wm_string_new_n(const char *str, size_t n);
void wm_string_destroy(WmString *str);
WmString* wm_string_clone(const WmString *str);
/*==============================================================================
* String Operations
*============================================================================*/
void wm_string_append(WmString *str, const char *suffix);
void wm_string_append_char(WmString *str, char c);
void wm_string_append_n(WmString *str, const char *suffix, size_t n);
void wm_string_append_printf(WmString *str, const char *fmt, ...);
void wm_string_append_vprintf(WmString *str, const char *fmt, va_list args);
void wm_string_append_string(WmString *str, const WmString *suffix);
void wm_string_prepend(WmString *str, const char *prefix);
void wm_string_prepend_char(WmString *str, char c);
void wm_string_insert(WmString *str, size_t pos, const char *insert);
void wm_string_insert_char(WmString *str, size_t pos, char c);
void wm_string_insert_string(WmString *str, size_t pos, const WmString *insert);
void wm_string_erase(WmString *str, size_t pos, size_t len);
void wm_string_clear(WmString *str);
/*==============================================================================
* String Queries
*============================================================================*/
const char* wm_string_cstr(const WmString *str);
char* wm_string_detach(WmString *str);
size_t wm_string_length(const WmString *str);
size_t wm_string_capacity(const WmString *str);
size_t wm_string_bytes(const WmString *str);
bool wm_string_is_empty(const WmString *str);
bool wm_string_is_null(const WmString *str);
char wm_string_char_at(const WmString *str, size_t pos);
/*==============================================================================
* String Comparison
*============================================================================*/
bool wm_string_equals(const WmString *str1, const WmString *str2);
bool wm_string_equals_cstr(const WmString *str, const char *cstr);
int wm_string_compare(const WmString *str1, const WmString *str2);
int wm_string_compare_cstr(const WmString *str, const char *cstr);
int wm_string_case_compare(const WmString *str1, const WmString *str2);
int wm_string_case_compare_cstr(const WmString *str, const char *cstr);
/*==============================================================================
* String Searching
*============================================================================*/
bool wm_string_contains(const WmString *str, const char *needle);
bool wm_string_contains_char(const WmString *str, char c);
size_t wm_string_find(const WmString *str, const char *needle, size_t start);
size_t wm_string_find_char(const WmString *str, char c, size_t start);
size_t wm_string_find_last(const WmString *str, const char *needle);
size_t wm_string_find_last_char(const WmString *str, char c);
bool wm_string_starts_with(const WmString *str, const char *prefix);
bool wm_string_starts_with_char(const WmString *str, char c);
bool wm_string_ends_with(const WmString *str, const char *suffix);
bool wm_string_ends_with_char(const WmString *str, char c);
/*==============================================================================
* String Modification
*============================================================================*/
void wm_string_trim(WmString *str);
void wm_string_trim_left(WmString *str);
void wm_string_trim_right(WmString *str);
void wm_string_replace(WmString *str, const char *search, const char *replace);
void wm_string_replace_char(WmString *str, char search, char replace);
size_t wm_string_replace_all(WmString *str, const char *search, const char *replace);
void wm_string_to_lower(WmString *str);
void wm_string_to_upper(WmString *str);
void wm_string_reverse(WmString *str);
/*==============================================================================
* String Substrings
*============================================================================*/
WmString* wm_string_substring(const WmString *str, size_t start, size_t len);
WmString* wm_string_left(const WmString *str, size_t n);
WmString* wm_string_right(const WmString *str, size_t n);
/*==============================================================================
* String Splitting and Joining
*============================================================================*/
WmContainer* wm_string_split(const WmString *str, const char *delimiter);
WmContainer* wm_string_split_chars(const WmString *str, const char *delimiters);
WmContainer* wm_string_split_lines(const WmString *str);
WmString* wm_string_join(const WmContainer *strings, const char *separator);
WmString* wm_string_join_cstr(const char **strings, size_t count, const char *separator);
/*==============================================================================
* String Formatting
*============================================================================*/
void wm_string_printf(WmString *str, const char *fmt, ...);
void wm_string_vprintf(WmString *str, const char *fmt, va_list args);
/*==============================================================================
* String Validation
*============================================================================*/
bool wm_string_is_valid_utf8(const WmString *str);
bool wm_string_is_ascii(const WmString *str);
bool wm_string_is_numeric(const WmString *str);
bool wm_string_is_integer(const WmString *str);
bool wm_string_is_float(const WmString *str);
/*==============================================================================
* String Conversion
*============================================================================*/
int wm_string_to_int(const WmString *str, int default_val);
long wm_string_to_long(const WmString *str, long default_val);
float wm_string_to_float(const WmString *str, float default_val);
double wm_string_to_double(const WmString *str, double default_val);
bool wm_string_to_bool(const WmString *str, bool default_val);
WmString* wm_string_from_int(int val);
WmString* wm_string_from_long(long val);
WmString* wm_string_from_float(float val, int precision);
WmString* wm_string_from_double(double val, int precision);
WmString* wm_string_from_bool(bool val);
/*==============================================================================
* String Hash
*============================================================================*/
uint32_t wm_string_hash(const WmString *str);
uint32_t wm_string_hash_cstr(const char *str);
/*==============================================================================
* String Escaping
*============================================================================*/
WmString* wm_string_escape_json(const WmString *str);
WmString* wm_string_escape_xml(const WmString *str);
WmString* wm_string_escape_html(const WmString *str);
WmString* wm_string_escape_shell(const WmString *str);
WmString* wm_string_escape_regex(const WmString *str);
WmString* wm_string_unescape_json(const WmString *str);
WmString* wm_string_unescape_xml(const WmString *str);
WmString* wm_string_unescape_html(const WmString *str);
/*==============================================================================
* Static String Helpers
*============================================================================*/
static inline bool wm_cstr_is_empty(const char *str) {
return str == NULL || str[0] == '\0';
}
static inline size_t wm_cstr_length(const char *str) {
return str == NULL ? 0 : strlen(str);
}
static inline bool wm_cstr_equals(const char *a, const char *b) {
if (a == b) return true;
if (a == NULL || b == NULL) return false;
return strcmp(a, b) == 0;
}
static inline bool wm_cstr_case_equals(const char *a, const char *b) {
if (a == b) return true;
if (a == NULL || b == NULL) return false;
return strcasecmp(a, b) == 0;
}
static inline void wm_cstr_copy(char *dest, const char *src, size_t size) {
if (size == 0) return;
strncpy(dest, src, size - 1);
dest[size - 1] = '\0';
}
#ifdef __cplusplus
}
#endif
#endif /* WM_STRING_H */

490
include/core/wm_types.h Normal file
View File

@ -0,0 +1,490 @@
/*
* DWN - Desktop Window Manager
* Core Abstract Types
* Extreme Abstraction Architecture
*/
#ifndef WM_TYPES_H
#define WM_TYPES_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Version and API Information
*============================================================================*/
#define WM_CORE_VERSION_MAJOR 2
#define WM_CORE_VERSION_MINOR 0
#define WM_CORE_VERSION_PATCH 0
#define WM_PLUGIN_API_VERSION 1
/*==============================================================================
* Opaque Type Declarations
*============================================================================*/
typedef struct WmCore WmCore;
typedef struct WmBackend WmBackend;
typedef struct WmPlugin WmPlugin;
typedef struct WmPluginManager WmPluginManager;
typedef struct WmEventBus WmEventBus;
typedef struct WmEventSubscription WmEventSubscription;
typedef struct WmContainer WmContainer;
typedef struct WmIterator WmIterator;
typedef struct WmString WmString;
typedef struct WmRenderer WmRenderer;
typedef struct WmSurface WmSurface;
typedef struct WmFont WmFont;
typedef struct WmImage WmImage;
typedef struct WmCommand WmCommand;
typedef struct WmCommandBuilder WmCommandBuilder;
typedef struct WmConfigSchema WmConfigSchema;
typedef struct WmConfigInstance WmConfigInstance;
typedef struct WmLayoutEngine WmLayoutEngine;
typedef struct WmPanelSystem WmPanelSystem;
typedef struct WmAnimation WmAnimation;
typedef struct WmTimer WmTimer;
typedef struct WmMutex WmMutex;
typedef struct WmCondition WmCondition;
typedef struct WmThread WmThread;
/*==============================================================================
* Basic Type Definitions
*============================================================================*/
typedef uint32_t WmColor;
typedef uint64_t WmTime;
typedef uint32_t WmHandle;
typedef uint32_t WmId;
typedef WmId WmClientId;
typedef int WmWorkspaceId;
typedef void* WmWindowHandle;
typedef void* WmNativeHandle;
/*==============================================================================
* Window Types
*============================================================================*/
typedef enum {
WM_WINDOW_TYPE_UNKNOWN = 0,
WM_WINDOW_TYPE_NORMAL,
WM_WINDOW_TYPE_DIALOG,
WM_WINDOW_TYPE_DOCK,
WM_WINDOW_TYPE_DESKTOP,
WM_WINDOW_TYPE_TOOLBAR,
WM_WINDOW_TYPE_MENU,
WM_WINDOW_TYPE_UTILITY,
WM_WINDOW_TYPE_SPLASH,
WM_WINDOW_TYPE_NOTIFICATION,
WM_WINDOW_TYPE_COMBO,
WM_WINDOW_TYPE_DND,
WM_WINDOW_TYPE_POPUP_MENU,
WM_WINDOW_TYPE_TOOLTIP
} WmWindowType;
/*==============================================================================
* Geometry Types
*============================================================================*/
typedef struct {
int x;
int y;
} WmPoint;
typedef struct {
int width;
int height;
} WmSize;
typedef struct {
int x;
int y;
int width;
int height;
} WmRect;
typedef struct {
WmPoint location;
WmSize size;
} WmGeometry;
typedef struct {
int top;
int bottom;
int left;
int right;
} WmInsets;
typedef struct {
float x;
float y;
} WmPointF;
typedef struct {
float width;
float height;
} WmSizeF;
typedef struct {
WmPointF location;
WmSizeF size;
} WmGeometryF;
/*==============================================================================
* Rectangle Operations (Inline)
*============================================================================*/
static inline WmRect wm_rect_make(int x, int y, int width, int height) {
WmRect r = { x, y, width, height };
return r;
}
static inline bool wm_rect_is_empty(const WmRect *r) {
return r == NULL || r->width <= 0 || r->height <= 0;
}
static inline bool wm_rect_contains_point(const WmRect *r, int x, int y) {
return r != NULL &&
x >= r->x && x < r->x + r->width &&
y >= r->y && y < r->y + r->height;
}
static inline bool wm_rect_intersects(const WmRect *a, const WmRect *b) {
if (a == NULL || b == NULL) return false;
return !(a->x + a->width <= b->x ||
b->x + b->width <= a->x ||
a->y + a->height <= b->y ||
b->y + b->height <= a->y);
}
static inline WmRect wm_rect_intersection(const WmRect *a, const WmRect *b) {
WmRect r = { 0, 0, 0, 0 };
if (a == NULL || b == NULL) return r;
int x1 = a->x > b->x ? a->x : b->x;
int y1 = a->y > b->y ? a->y : b->y;
int x2 = (a->x + a->width) < (b->x + b->width) ? (a->x + a->width) : (b->x + b->width);
int y2 = (a->y + a->height) < (b->y + b->height) ? (a->y + a->height) : (b->y + b->height);
if (x2 > x1 && y2 > y1) {
r.x = x1;
r.y = y1;
r.width = x2 - x1;
r.height = y2 - y1;
}
return r;
}
static inline WmRect wm_rect_union(const WmRect *a, const WmRect *b) {
WmRect r = { 0, 0, 0, 0 };
if (a == NULL && b == NULL) return r;
if (a == NULL) return *b;
if (b == NULL) return *a;
int x1 = a->x < b->x ? a->x : b->x;
int y1 = a->y < b->y ? a->y : b->y;
int x2 = (a->x + a->width) > (b->x + b->width) ? (a->x + a->width) : (b->x + b->width);
int y2 = (a->y + a->height) > (b->y + b->height) ? (a->y + a->height) : (b->y + b->height);
r.x = x1;
r.y = y1;
r.width = x2 - x1;
r.height = y2 - y1;
return r;
}
/*==============================================================================
* Color Operations (Inline)
*============================================================================*/
static inline WmColor wm_color_rgb(uint8_t r, uint8_t g, uint8_t b) {
return (0xFF << 24) | (r << 16) | (g << 8) | b;
}
static inline WmColor wm_color_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
return (a << 24) | (r << 16) | (g << 8) | b;
}
static inline uint8_t wm_color_get_red(WmColor c) {
return (c >> 16) & 0xFF;
}
static inline uint8_t wm_color_get_green(WmColor c) {
return (c >> 8) & 0xFF;
}
static inline uint8_t wm_color_get_blue(WmColor c) {
return c & 0xFF;
}
static inline uint8_t wm_color_get_alpha(WmColor c) {
return (c >> 24) & 0xFF;
}
/*==============================================================================
* Client State Flags
*============================================================================*/
typedef enum {
WM_CLIENT_FLAG_NONE = 0,
WM_CLIENT_FLAG_FLOATING = (1 << 0),
WM_CLIENT_FLAG_FULLSCREEN = (1 << 1),
WM_CLIENT_FLAG_URGENT = (1 << 2),
WM_CLIENT_FLAG_MINIMIZED = (1 << 3),
WM_CLIENT_FLAG_STICKY = (1 << 4),
WM_CLIENT_FLAG_MAXIMIZED = (1 << 5),
WM_CLIENT_FLAG_UNMANAGING = (1 << 6),
WM_CLIENT_FLAG_FOCUSED = (1 << 7),
WM_CLIENT_FLAG_MAPPED = (1 << 8),
WM_CLIENT_FLAG_DECORATED = (1 << 9),
WM_CLIENT_FLAG_DIALOG = (1 << 10),
WM_CLIENT_FLAG_DOCK = (1 << 11),
WM_CLIENT_FLAG_UTILITY = (1 << 12),
WM_CLIENT_FLAG_SPLASH = (1 << 13),
WM_CLIENT_FLAG_MENU = (1 << 14),
WM_CLIENT_FLAG_TOOLBAR = (1 << 15)
} WmClientFlags;
/*==============================================================================
* Layout Types
*============================================================================*/
typedef enum {
WM_LAYOUT_TYPE_TILING,
WM_LAYOUT_TYPE_FLOATING,
WM_LAYOUT_TYPE_MONOCLE,
WM_LAYOUT_TYPE_GRID,
WM_LAYOUT_TYPE_SPIRAL,
WM_LAYOUT_TYPE_DWINDLE,
WM_LAYOUT_TYPE_MAX
} WmLayoutType;
/*==============================================================================
* Panel Positions
*============================================================================*/
typedef enum {
WM_PANEL_POSITION_TOP,
WM_PANEL_POSITION_BOTTOM,
WM_PANEL_POSITION_LEFT,
WM_PANEL_POSITION_RIGHT
} WmPanelPosition;
/*==============================================================================
* Event Types
*============================================================================*/
typedef enum {
/* Window events */
WM_EVENT_NONE = 0,
WM_EVENT_WINDOW_CREATED,
WM_EVENT_WINDOW_DESTROYED,
WM_EVENT_WINDOW_MAPPED,
WM_EVENT_WINDOW_UNMAPPED,
WM_EVENT_WINDOW_CONFIGURED,
WM_EVENT_WINDOW_FOCUSED,
WM_EVENT_WINDOW_UNFOCUSED,
WM_EVENT_WINDOW_RAISED,
WM_EVENT_WINDOW_LOWERED,
WM_EVENT_WINDOW_MINIMIZED,
WM_EVENT_WINDOW_RESTORED,
WM_EVENT_WINDOW_MAXIMIZED,
WM_EVENT_WINDOW_UNMAXIMIZED,
WM_EVENT_WINDOW_FULLSCREENED,
WM_EVENT_WINDOW_UNFULLSCREENED,
WM_EVENT_WINDOW_FLOATING_CHANGED,
WM_EVENT_WINDOW_PROPERTY_CHANGED,
WM_EVENT_WINDOW_STATE_CHANGED,
WM_EVENT_WINDOW_TITLE_CHANGED,
WM_EVENT_WINDOW_CLASS_CHANGED,
WM_EVENT_WINDOW_ROLE_CHANGED,
WM_EVENT_WINDOW_URGENCY_CHANGED,
WM_EVENT_WINDOW_MOVED,
WM_EVENT_WINDOW_RESIZED,
/* Workspace events */
WM_EVENT_WORKSPACE_CREATED,
WM_EVENT_WORKSPACE_DESTROYED,
WM_EVENT_WORKSPACE_SWITCHED,
WM_EVENT_WORKSPACE_RENAMED,
WM_EVENT_WORKSPACE_LAYOUT_CHANGED,
WM_EVENT_WORKSPACE_MASTER_RATIO_CHANGED,
WM_EVENT_WORKSPACE_MASTER_COUNT_CHANGED,
/* Client/workspace relationship */
WM_EVENT_CLIENT_ADDED_TO_WORKSPACE,
WM_EVENT_CLIENT_REMOVED_FROM_WORKSPACE,
/* Input events */
WM_EVENT_KEY_PRESSED,
WM_EVENT_KEY_RELEASED,
WM_EVENT_BUTTON_PRESSED,
WM_EVENT_BUTTON_RELEASED,
WM_EVENT_MOTION,
WM_EVENT_ENTER,
WM_EVENT_LEAVE,
WM_EVENT_SCROLL,
/* System events */
WM_EVENT_MONITOR_ADDED,
WM_EVENT_MONITOR_REMOVED,
WM_EVENT_MONITOR_CONFIGURED,
WM_EVENT_SCREEN_RESIZED,
/* Configuration events */
WM_EVENT_CONFIG_RELOADED,
WM_EVENT_CONFIG_CHANGED,
/* Lifecycle events */
WM_EVENT_WM_STARTED,
WM_EVENT_WM_SHUTDOWN,
WM_EVENT_WM_READY,
/* Plugin events */
WM_EVENT_PLUGIN_LOADED,
WM_EVENT_PLUGIN_UNLOADED,
WM_EVENT_PLUGIN_ERROR,
/* Custom events for plugins */
WM_EVENT_CUSTOM = 1000
} WmEventType;
/*==============================================================================
* Container Types
*============================================================================*/
typedef enum {
WM_CONTAINER_LIST,
WM_CONTAINER_ARRAY,
WM_CONTAINER_HASHMAP,
WM_CONTAINER_QUEUE,
WM_CONTAINER_STACK,
WM_CONTAINER_TREE
} WmContainerType;
/*==============================================================================
* Command Types
*============================================================================*/
typedef enum {
WM_COMMAND_NONE = 0,
/* Window commands */
WM_COMMAND_WINDOW_FOCUS,
WM_COMMAND_WINDOW_MOVE,
WM_COMMAND_WINDOW_RESIZE,
WM_COMMAND_WINDOW_MOVE_RESIZE,
WM_COMMAND_WINDOW_CLOSE,
WM_COMMAND_WINDOW_KILL,
WM_COMMAND_WINDOW_MINIMIZE,
WM_COMMAND_WINDOW_RESTORE,
WM_COMMAND_WINDOW_MAXIMIZE,
WM_COMMAND_WINDOW_UNMAXIMIZE,
WM_COMMAND_WINDOW_FULLSCREEN,
WM_COMMAND_WINDOW_UNFULLSCREEN,
WM_COMMAND_WINDOW_FLOAT,
WM_COMMAND_WINDOW_UNFLOAT,
WM_COMMAND_WINDOW_RAISE,
WM_COMMAND_WINDOW_LOWER,
WM_COMMAND_WINDOW_SET_WORKSPACE,
/* Workspace commands */
WM_COMMAND_WORKSPACE_SWITCH,
WM_COMMAND_WORKSPACE_CREATE,
WM_COMMAND_WORKSPACE_DESTROY,
WM_COMMAND_WORKSPACE_RENAME,
WM_COMMAND_WORKSPACE_SET_LAYOUT,
WM_COMMAND_WORKSPACE_ADJUST_MASTER_RATIO,
WM_COMMAND_WORKSPACE_ADJUST_MASTER_COUNT,
WM_COMMAND_WORKSPACE_SWAP,
/* Client manipulation */
WM_COMMAND_CLIENT_SWAP,
WM_COMMAND_CLIENT_SWAP_MASTER,
WM_COMMAND_CLIENT_CYCLE,
WM_COMMAND_CLIENT_CYCLE_REVERSE,
/* System commands */
WM_COMMAND_RELOAD_CONFIG,
WM_COMMAND_QUIT,
WM_COMMAND_RESTART,
/* Plugin commands */
WM_COMMAND_PLUGIN_LOAD,
WM_COMMAND_PLUGIN_UNLOAD,
WM_COMMAND_PLUGIN_ENABLE,
WM_COMMAND_PLUGIN_DISABLE
} WmCommandType;
/*==============================================================================
* Result/Error Types
*============================================================================*/
typedef enum {
WM_RESULT_OK = 0,
WM_RESULT_ERROR_GENERIC = -1,
WM_RESULT_ERROR_INVALID_ARG = -2,
WM_RESULT_ERROR_NO_MEMORY = -3,
WM_RESULT_ERROR_NOT_FOUND = -4,
WM_RESULT_ERROR_EXISTS = -5,
WM_RESULT_ERROR_PERMISSION = -6,
WM_RESULT_ERROR_NOT_SUPPORTED = -7,
WM_RESULT_ERROR_NOT_IMPLEMENTED = -8,
WM_RESULT_ERROR_BACKEND = -9,
WM_RESULT_ERROR_PLUGIN = -10,
WM_RESULT_ERROR_TIMEOUT = -11,
WM_RESULT_ERROR_CANCELED = -12
} WmResult;
/*==============================================================================
* Callback Types
*============================================================================*/
typedef void (*WmDestroyFunc)(void *data);
typedef int (*WmCompareFunc)(const void *a, const void *b);
typedef uint32_t (*WmHashFunc)(const void *key);
typedef void* (*WmCloneFunc)(const void *data);
typedef void (*WmEventHandler)(WmEventType type, const void *event_data, void *user_data);
typedef void (*WmTimerCallback)(WmTimer *timer, void *user_data);
typedef void* (*WmThreadFunc)(void *arg);
/*==============================================================================
* Memory Management Types
*============================================================================*/
typedef void* (*WmAllocFunc)(size_t size);
typedef void* (*WmReallocFunc)(void *ptr, size_t old_size, size_t new_size);
typedef void (*WmFreeFunc)(void *ptr);
typedef struct {
WmAllocFunc alloc;
WmReallocFunc realloc;
WmFreeFunc free;
} WmAllocator;
/*==============================================================================
* Logging Types
*============================================================================*/
typedef enum {
WM_LOG_LEVEL_DEBUG,
WM_LOG_LEVEL_INFO,
WM_LOG_LEVEL_WARN,
WM_LOG_LEVEL_ERROR,
WM_LOG_LEVEL_FATAL
} WmLogLevel;
typedef void (*WmLogHandler)(WmLogLevel level, const char *file, int line,
const char *func, const char *message);
#ifdef __cplusplus
}
#endif
#endif /* WM_TYPES_H */

161
include/core/wm_workspace.h Normal file
View File

@ -0,0 +1,161 @@
/*
* DWN - Desktop Window Manager
* Abstract Workspace Interface
*/
#ifndef WM_WORKSPACE_H
#define WM_WORKSPACE_H
#include "wm_types.h"
#include "wm_client.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Workspace Configuration
*============================================================================*/
#define WM_MAX_WORKSPACES 32
typedef struct {
WmLayoutType layout_type;
float master_ratio;
int master_count;
char name[64];
bool persistent;
WmString *custom_data;
} WmWorkspaceConfig;
/*==============================================================================
* Workspace Lifecycle
*============================================================================*/
bool wm_workspace_exists(WmCore *core, WmWorkspaceId id);
WmWorkspaceId wm_workspace_create(WmCore *core, const char *name);
void wm_workspace_destroy(WmCore *core, WmWorkspaceId id);
void wm_workspace_init_all(WmCore *core);
void wm_workspace_cleanup_all(WmCore *core);
/*==============================================================================
* Workspace Properties
*============================================================================*/
const char* wm_workspace_get_name(WmCore *core, WmWorkspaceId id);
void wm_workspace_set_name(WmCore *core, WmWorkspaceId id, const char *name);
bool wm_workspace_is_empty(WmCore *core, WmWorkspaceId id);
size_t wm_workspace_get_client_count(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* Current Workspace
*============================================================================*/
WmWorkspaceId wm_workspace_get_current(WmCore *core);
void wm_workspace_set_current(WmCore *core, WmWorkspaceId id);
void wm_workspace_switch(WmCore *core, WmWorkspaceId id);
void wm_workspace_switch_next(WmCore *core);
void wm_workspace_switch_prev(WmCore *core);
/*==============================================================================
* Client Management
*============================================================================*/
void wm_workspace_add_client(WmCore *core, WmWorkspaceId id, AbstractClient *client);
void wm_workspace_remove_client(WmCore *core, WmWorkspaceId id, AbstractClient *client);
void wm_workspace_move_client(WmCore *core, AbstractClient *client, WmWorkspaceId to_workspace);
WmContainer* wm_workspace_get_clients(WmCore *core, WmWorkspaceId id);
AbstractClient* wm_workspace_get_focused_client(WmCore *core, WmWorkspaceId id);
void wm_workspace_set_focused_client(WmCore *core, WmWorkspaceId id, AbstractClient *client);
AbstractClient* wm_workspace_get_first_client(WmCore *core, WmWorkspaceId id);
AbstractClient* wm_workspace_get_last_client(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* Layout Management
*============================================================================*/
WmLayoutType wm_workspace_get_layout(WmCore *core, WmWorkspaceId id);
void wm_workspace_set_layout(WmCore *core, WmWorkspaceId id, WmLayoutType layout);
void wm_workspace_cycle_layout(WmCore *core, WmWorkspaceId id);
void wm_workspace_cycle_layout_reverse(WmCore *core, WmWorkspaceId id);
float wm_workspace_get_master_ratio(WmCore *core, WmWorkspaceId id);
void wm_workspace_set_master_ratio(WmCore *core, WmWorkspaceId id, float ratio);
void wm_workspace_adjust_master_ratio(WmCore *core, WmWorkspaceId id, float delta);
int wm_workspace_get_master_count(WmCore *core, WmWorkspaceId id);
void wm_workspace_set_master_count(WmCore *core, WmWorkspaceId id, int count);
void wm_workspace_adjust_master_count(WmCore *core, WmWorkspaceId id, int delta);
/*==============================================================================
* Workspace Arrangement
*============================================================================*/
void wm_workspace_arrange(WmCore *core, WmWorkspaceId id);
void wm_workspace_arrange_all(WmCore *core);
void wm_workspace_arrange_current(WmCore *core);
WmRect wm_workspace_get_arrange_area(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* Workspace Visibility
*============================================================================*/
void wm_workspace_show(WmCore *core, WmWorkspaceId id);
void wm_workspace_hide(WmCore *core, WmWorkspaceId id);
bool wm_workspace_is_visible(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* Focus Management
*============================================================================*/
void wm_workspace_focus_next(WmCore *core, WmWorkspaceId id);
void wm_workspace_focus_prev(WmCore *core, WmWorkspaceId id);
void wm_workspace_focus_master(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* MRU (Most Recently Used) Stack
*============================================================================*/
void wm_workspace_mru_push(WmCore *core, WmWorkspaceId id, AbstractClient *client);
void wm_workspace_mru_remove(WmCore *core, WmWorkspaceId id, AbstractClient *client);
AbstractClient* wm_workspace_mru_get_next(WmCore *core, WmWorkspaceId id, AbstractClient *current);
AbstractClient* wm_workspace_mru_get_prev(WmCore *core, WmWorkspaceId id, AbstractClient *current);
/*==============================================================================
* Alt-Tab Navigation
*============================================================================*/
void wm_workspace_alt_tab_start(WmCore *core, WmWorkspaceId id);
void wm_workspace_alt_tab_next(WmCore *core, WmWorkspaceId id);
void wm_workspace_alt_tab_prev(WmCore *core, WmWorkspaceId id);
void wm_workspace_alt_tab_end(WmCore *core, WmWorkspaceId id);
bool wm_workspace_alt_tab_is_active(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* Workspace Iteration
*============================================================================*/
WmWorkspaceId wm_workspace_get_first(WmCore *core);
WmWorkspaceId wm_workspace_get_last(WmCore *core);
WmWorkspaceId wm_workspace_get_next(WmCore *core, WmWorkspaceId id);
WmWorkspaceId wm_workspace_get_prev(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* Configuration
*============================================================================*/
void wm_workspace_save_config(WmCore *core, WmWorkspaceId id, const WmWorkspaceConfig *config);
bool wm_workspace_load_config(WmCore *core, WmWorkspaceId id, WmWorkspaceConfig *config);
#ifdef __cplusplus
}
#endif
#endif /* WM_WORKSPACE_H */

View File

@ -49,6 +49,9 @@ typedef enum {
LAYOUT_TILING,
LAYOUT_FLOATING,
LAYOUT_MONOCLE,
LAYOUT_CENTERED_MASTER,
LAYOUT_COLUMNS,
LAYOUT_FIBONACCI,
LAYOUT_COUNT
} LayoutType;
@ -64,7 +67,8 @@ typedef enum {
CLIENT_URGENT = (1 << 2),
CLIENT_MINIMIZED = (1 << 3),
CLIENT_STICKY = (1 << 4),
CLIENT_MAXIMIZED = (1 << 5)
CLIENT_MAXIMIZED = (1 << 5),
CLIENT_UNMANAGING = (1 << 6) /* Being destroyed, skip processing */
} ClientFlags;
typedef enum {

View File

@ -89,6 +89,17 @@ void key_start_demo(void);
void key_show_desktop(void);
void key_move_to_workspace_prev(void);
void key_move_to_workspace_next(void);
void key_kill_all_clients(void);
void key_move_window_left(void);
void key_move_window_right(void);
void key_move_window_up(void);
void key_move_window_down(void);
void key_resize_window_left(void);
void key_resize_window_right(void);
void key_resize_window_up(void);
void key_resize_window_down(void);
void key_set_mark(void);
void key_goto_mark(void);
void tutorial_start(void);
void tutorial_stop(void);

View File

@ -13,6 +13,9 @@ void layout_arrange(int workspace);
void layout_arrange_tiling(int workspace);
void layout_arrange_floating(int workspace);
void layout_arrange_monocle(int workspace);
void layout_arrange_centered_master(int workspace);
void layout_arrange_columns(int workspace);
void layout_arrange_fibonacci(int workspace);
int layout_get_usable_area(int *x, int *y, int *width, int *height);
int layout_count_tiled_clients(int workspace);

33
include/marks.h Normal file
View File

@ -0,0 +1,33 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Window marks for quick navigation
*/
#ifndef DWN_MARKS_H
#define DWN_MARKS_H
#include "dwn.h"
#include <stdbool.h>
#define MAX_MARKS 26
void marks_init(void);
void marks_cleanup(void);
void marks_set(char mark, Client *client);
Client *marks_get(char mark);
void marks_clear(char mark);
void marks_clear_all(void);
void marks_remove_client(Client *client);
bool marks_is_waiting_for_mark(void);
bool marks_is_waiting_for_goto(void);
void marks_start_set_mode(void);
void marks_start_goto_mode(void);
void marks_cancel_mode(void);
bool marks_handle_key(char key);
char marks_get_mark_for_client(Client *client);
#endif

View File

@ -0,0 +1,57 @@
/*
* DWN - Desktop Window Manager
* Built-in Layout Registration
*/
#ifndef BUILTIN_LAYOUTS_H
#define BUILTIN_LAYOUTS_H
#include "plugins/layout_plugin.h"
#include "dwn.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Initialize and register all built-in layouts.
* Called during DWN startup.
*/
bool wm_layouts_builtin_init(void);
/**
* Get a layout plugin by legacy LayoutType.
*/
LayoutPlugin* wm_layout_get_by_type(LayoutManager *mgr, LayoutType type);
/**
* Bridge function to arrange a workspace using the plugin system.
*/
bool wm_layout_arrange_workspace(LayoutManager *mgr, int workspace, LayoutType type);
/**
* Get symbol for a layout type.
*/
const char* wm_layout_get_symbol_for_type(LayoutType type);
/**
* Get name for a layout type.
*/
const char* wm_layout_get_name_for_type(LayoutType type);
/**
* Get LayoutType from name.
*/
LayoutType wm_layout_type_from_name(const char *name);
/* External layout interface getters */
const LayoutPluginInterface* wm_layout_tiling_get_interface(void);
const LayoutPluginInterface* wm_layout_floating_get_interface(void);
const LayoutPluginInterface* wm_layout_monocle_get_interface(void);
const LayoutPluginInterface* wm_layout_grid_get_interface(void);
#ifdef __cplusplus
}
#endif
#endif /* BUILTIN_LAYOUTS_H */

View File

@ -0,0 +1,365 @@
/*
* DWN - Desktop Window Manager
* Layout Plugin Interface
*/
#ifndef LAYOUT_PLUGIN_H
#define LAYOUT_PLUGIN_H
#include "core/wm_types.h"
#include "core/wm_client.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Layout Plugin API Version
*============================================================================*/
#define WM_LAYOUT_PLUGIN_API_VERSION 1
/*==============================================================================
* Forward Declarations
*============================================================================*/
typedef struct LayoutPlugin LayoutPlugin;
typedef struct LayoutContext LayoutContext;
/*==============================================================================
* Layout Geometry
*============================================================================*/
/**
* Layout geometry for a single client.
*/
typedef struct {
int x, y;
int width, height;
bool floating; /* If true, layout doesn't manage this client */
bool fullscreen; /* If true, client should be fullscreen */
bool hidden; /* If true, client should be hidden */
} WmLayoutGeometry;
/**
* Work area for layout calculations.
*/
typedef struct {
int x, y;
int width, height;
int padding_top;
int padding_bottom;
int padding_left;
int padding_right;
int gap;
} WmLayoutWorkArea;
/*==============================================================================
* Layout Configuration
*============================================================================*/
/**
* Layout configuration parameter.
*/
typedef struct {
const char *name;
const char *description;
union {
int int_value;
float float_value;
bool bool_value;
const char *string_value;
} default_value;
enum {
WM_LAYOUT_PARAM_INT,
WM_LAYOUT_PARAM_FLOAT,
WM_LAYOUT_PARAM_BOOL,
WM_LAYOUT_PARAM_STRING,
WM_LAYOUT_PARAM_CHOICE
} type;
/* For PARAM_CHOICE */
const char **choices;
int num_choices;
} WmLayoutParameter;
/**
* Layout configuration instance.
*/
typedef struct {
WmLayoutParameter *parameters;
int num_parameters;
void *user_data; /* Plugin can store parsed config here */
} WmLayoutConfig;
/*==============================================================================
* Layout State
*============================================================================*/
/**
* Opaque layout state per workspace.
* Layout plugins can store workspace-specific state here.
*/
typedef struct WmLayoutState WmLayoutState;
/*==============================================================================
* Layout Context
*============================================================================*/
/**
* Context passed to layout operations.
*/
struct LayoutContext {
/* Workspace info */
WmWorkspaceId workspace;
WmLayoutWorkArea work_area;
/* Client counts */
int client_count;
int master_count;
int stack_count;
/* Layout parameters */
float master_ratio;
int master_count_target;
int gap;
/* Configuration */
WmLayoutConfig *config;
/* Layout state (managed by plugin) */
WmLayoutState *state;
/* User data (for plugin use) */
void *user_data;
};
/*==============================================================================
* Layout Plugin Interface
*============================================================================*/
/**
* Layout plugin interface vtable.
* All layouts must implement these functions.
*/
typedef struct {
/* Version check */
int api_version;
/* Metadata */
const char *name;
const char *description;
const char *author;
const char *version;
/* Configuration */
WmLayoutParameter *parameters;
int num_parameters;
/* Lifecycle */
bool (*init)(LayoutPlugin *plugin);
void (*shutdown)(LayoutPlugin *plugin);
/* State management */
WmLayoutState* (*state_create)(LayoutPlugin *plugin, WmWorkspaceId workspace);
void (*state_destroy)(LayoutPlugin *plugin, WmLayoutState *state);
void (*state_reset)(LayoutPlugin *plugin, WmLayoutState *state);
/* Configuration */
bool (*config_parse)(LayoutPlugin *plugin, WmLayoutConfig *config,
const char *key, const char *value);
void (*config_apply)(LayoutPlugin *plugin, WmLayoutConfig *config);
/* Layout calculation */
bool (*arrange)(LayoutPlugin *plugin, LayoutContext *ctx,
AbstractClient **clients, int num_clients,
WmLayoutGeometry *geometries);
/* Client operations */
void (*client_added)(LayoutPlugin *plugin, LayoutContext *ctx,
AbstractClient *client);
void (*client_removed)(LayoutPlugin *plugin, LayoutContext *ctx,
AbstractClient *client);
void (*client_floating_changed)(LayoutPlugin *plugin, LayoutContext *ctx,
AbstractClient *client, bool floating);
/* Layout manipulation */
void (*inc_master)(LayoutPlugin *plugin, LayoutContext *ctx, int delta);
void (*set_master_ratio)(LayoutPlugin *plugin, LayoutContext *ctx, float ratio);
void (*set_master_count)(LayoutPlugin *plugin, LayoutContext *ctx, int count);
void (*swap_master)(LayoutPlugin *plugin, LayoutContext *ctx);
void (*rotate)(LayoutPlugin *plugin, LayoutContext *ctx, bool clockwise);
/* Mouse resizing support */
void (*resize_master)(LayoutPlugin *plugin, LayoutContext *ctx,
int x, int y, int *new_master_count, float *new_ratio);
} LayoutPluginInterface;
/*==============================================================================
* Layout Plugin Structure
*============================================================================*/
/**
* Layout plugin instance.
*/
struct LayoutPlugin {
/* Interface vtable */
const LayoutPluginInterface *interface;
/* Plugin handle (for dynamic loading) */
void *handle;
/* Plugin path */
char *path;
/* Enabled state */
bool enabled;
/* Global configuration */
WmLayoutConfig config;
/* Per-workspace states */
WmLayoutState **workspace_states;
int num_workspaces;
/* Plugin-private data */
void *user_data;
};
/*==============================================================================
* Layout Manager
*============================================================================*/
/**
* Layout manager handles all loaded layouts.
*/
typedef struct LayoutManager LayoutManager;
/**
* Get the global layout manager.
*/
LayoutManager* wm_layout_manager_get(void);
/**
* Initialize the layout manager.
*/
bool wm_layout_manager_init(void);
/**
* Shutdown the layout manager.
*/
void wm_layout_manager_shutdown(void);
/**
* Register a layout plugin.
*/
bool wm_layout_manager_register(LayoutManager *mgr, LayoutPlugin *plugin);
/**
* Unregister a layout plugin.
*/
void wm_layout_manager_unregister(LayoutManager *mgr, LayoutPlugin *plugin);
/**
* Load a layout plugin from a file.
*/
LayoutPlugin* wm_layout_manager_load(LayoutManager *mgr, const char *path);
/**
* Unload a layout plugin.
*/
void wm_layout_manager_unload(LayoutManager *mgr, LayoutPlugin *plugin);
/**
* Get a loaded layout by name.
*/
LayoutPlugin* wm_layout_manager_get_layout(LayoutManager *mgr, const char *name);
/**
* Get the default layout.
*/
LayoutPlugin* wm_layout_manager_get_default(LayoutManager *mgr);
/**
* Set the default layout.
*/
void wm_layout_manager_set_default(LayoutManager *mgr, LayoutPlugin *plugin);
/**
* Get list of available layouts.
*/
const char** wm_layout_manager_list(LayoutManager *mgr, int *count);
/*==============================================================================
* Layout Operations
*============================================================================*/
/**
* Apply a layout to a workspace.
*/
bool wm_layout_arrange(LayoutPlugin *plugin, WmWorkspaceId workspace);
/**
* Apply layout to specific clients.
*/
bool wm_layout_arrange_clients(LayoutPlugin *plugin, LayoutContext *ctx,
AbstractClient **clients, int num_clients);
/**
* Create a layout context for a workspace.
*/
bool wm_layout_context_init(LayoutContext *ctx, WmWorkspaceId workspace);
/**
* Clean up a layout context.
*/
void wm_layout_context_cleanup(LayoutContext *ctx);
/*==============================================================================
* Built-in Layouts
*============================================================================*/
/* Tiling layout */
extern const LayoutPluginInterface wm_layout_tiling;
/* Floating layout */
extern const LayoutPluginInterface wm_layout_floating;
/* Monocle (maximized) layout */
extern const LayoutPluginInterface wm_layout_monocle;
/* Grid layout */
extern const LayoutPluginInterface wm_layout_grid;
/* Spiral layout */
extern const LayoutPluginInterface wm_layout_spiral;
/* Dwindle layout */
extern const LayoutPluginInterface wm_layout_dwindle;
/*==============================================================================
* Plugin Entry Point
*============================================================================*/
/**
* Layout plugins must export this function.
*/
typedef const LayoutPluginInterface* (*LayoutPluginEntryFunc)(void);
#define WM_LAYOUT_PLUGIN_ENTRY "wm_layout_plugin_entry"
/*==============================================================================
* Helper Macros
*============================================================================*/
#define WM_LAYOUT_REGISTER(name, iface) \
__attribute__((unused)) static const LayoutPluginInterface* wm_layout_plugin_entry(void) { return &(iface); }
/* Typedef for built-in layout getters */
typedef const LayoutPluginInterface* (*WmLayoutBuiltinFunc)(void);
#ifdef __cplusplus
}
#endif
#endif /* LAYOUT_PLUGIN_H */

View File

@ -0,0 +1,435 @@
/*
* DWN - Desktop Window Manager
* Widget Plugin Interface
* Panel components (taskbar, clock, workspaces, etc.)
*/
#ifndef WIDGET_PLUGIN_H
#define WIDGET_PLUGIN_H
#include "core/wm_types.h"
#include "core/wm_client.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Widget Plugin API Version
*============================================================================*/
#define WM_WIDGET_PLUGIN_API_VERSION 1
/*==============================================================================
* Forward Declarations
*============================================================================*/
typedef struct WidgetPlugin WidgetPlugin;
typedef struct WidgetContext WidgetContext;
typedef struct WidgetInstance WidgetInstance;
/*==============================================================================
* Widget Position
*============================================================================*/
typedef enum {
WM_WIDGET_POSITION_LEFT,
WM_WIDGET_POSITION_CENTER,
WM_WIDGET_POSITION_RIGHT
} WmWidgetPosition;
/*==============================================================================
* Widget Size Constraints
*============================================================================*/
typedef struct {
int min_width;
int min_height;
int max_width;
int max_height;
bool expand_horizontal; /* Take available horizontal space */
bool expand_vertical; /* Take available vertical space */
} WmWidgetConstraints;
/*==============================================================================
* Widget Geometry
*============================================================================*/
typedef struct {
int x, y;
int width, height;
} WmWidgetGeometry;
/*==============================================================================
* Widget State
*============================================================================*/
typedef enum {
WM_WIDGET_STATE_NORMAL = 0,
WM_WIDGET_STATE_HOVERED = (1 << 0),
WM_WIDGET_STATE_PRESSED = (1 << 1),
WM_WIDGET_STATE_ACTIVE = (1 << 2),
WM_WIDGET_STATE_DISABLED = (1 << 3),
WM_WIDGET_STATE_HIDDEN = (1 << 4),
WM_WIDGET_STATE_URGENT = (1 << 5)
} WmWidgetState;
/*==============================================================================
* Widget Context
*============================================================================*/
struct WidgetContext {
/* Position in panel */
WmWidgetPosition position;
/* Geometry */
WmWidgetGeometry geometry;
WmWidgetConstraints constraints;
/* Panel info */
WmRect panel_geometry;
bool is_top_panel;
/* Rendering context */
void *render_context; /* Backend-specific (X11 GC, cairo, etc.) */
WmColor bg_color;
WmColor fg_color;
/* Font */
void *font; /* Opaque font handle */
int font_size;
/* User data */
void *user_data;
};
/*==============================================================================
* Widget Event Types
*============================================================================*/
typedef enum {
WM_WIDGET_EVENT_NONE,
WM_WIDGET_EVENT_MOUSE_ENTER,
WM_WIDGET_EVENT_MOUSE_LEAVE,
WM_WIDGET_EVENT_MOUSE_MOVE,
WM_WIDGET_EVENT_BUTTON_PRESS,
WM_WIDGET_EVENT_BUTTON_RELEASE,
WM_WIDGET_EVENT_SCROLL,
WM_WIDGET_EVENT_KEY_PRESS,
WM_WIDGET_EVENT_FOCUS_IN,
WM_WIDGET_EVENT_FOCUS_OUT,
WM_WIDGET_EVENT_UPDATE, /* Request to redraw */
WM_WIDGET_EVENT_CONFIGURE, /* Size/position changed */
WM_WIDGET_EVENT_DATA_CHANGED /* External data changed */
} WmWidgetEventType;
typedef struct {
WmWidgetEventType type;
WmWidgetGeometry widget_geometry;
union {
struct { int x, y; } point;
struct { int button; int x, y; } button;
struct { int delta; bool horizontal; } scroll;
struct { unsigned int keycode; unsigned int state; } key;
} data;
} WmWidgetEvent;
/*==============================================================================
* Widget Configuration
*============================================================================*/
typedef struct {
const char *name;
const char *value;
} WmWidgetConfigOption;
typedef struct {
WmWidgetConfigOption *options;
int num_options;
} WmWidgetConfig;
/*==============================================================================
* Widget Information
*============================================================================*/
typedef struct {
const char *id; /* Unique identifier */
const char *name; /* Display name */
const char *description; /* Description */
const char *author; /* Author */
const char *version; /* Version */
/* Default constraints */
WmWidgetConstraints default_constraints;
/* Default position */
WmWidgetPosition default_position;
/* Configuration schema */
const char **config_options; /* List of accepted config option names */
int num_config_options;
} WmWidgetInfo;
/*==============================================================================
* Widget Plugin Interface
*============================================================================*/
typedef struct {
/* Version check */
int api_version;
/* Metadata */
WmWidgetInfo info;
/* Lifecycle */
bool (*init)(WidgetPlugin *plugin);
void (*shutdown)(WidgetPlugin *plugin);
/* Instance management */
WidgetInstance* (*instance_create)(WidgetPlugin *plugin,
const WmWidgetConfig *config);
void (*instance_destroy)(WidgetPlugin *plugin, WidgetInstance *instance);
void (*instance_configure)(WidgetPlugin *plugin, WidgetInstance *instance,
const WmWidgetConfig *config);
/* Rendering */
void (*render)(WidgetPlugin *plugin, WidgetInstance *instance,
WidgetContext *ctx);
/* Event handling */
bool (*handle_event)(WidgetPlugin *plugin, WidgetInstance *instance,
const WmWidgetEvent *event);
/* Update notification */
void (*update)(WidgetPlugin *plugin, WidgetInstance *instance);
/* Size calculation */
void (*get_preferred_size)(WidgetPlugin *plugin, WidgetInstance *instance,
int *width, int *height);
} WidgetPluginInterface;
/*==============================================================================
* Widget Instance
*============================================================================*/
struct WidgetInstance {
/* Plugin that created this instance */
WidgetPlugin *plugin;
/* Instance state */
WmWidgetState state;
WmWidgetGeometry geometry;
WmWidgetConstraints constraints;
/* Configuration */
WmWidgetConfig config;
/* Context */
WidgetContext *context;
/* Plugin-private data */
void *user_data;
/* Linked list for panel */
WidgetInstance *next;
WidgetInstance *prev;
};
/*==============================================================================
* Widget Plugin Structure
*============================================================================*/
struct WidgetPlugin {
/* Interface vtable */
const WidgetPluginInterface *interface;
/* Plugin handle (for dynamic loading) */
void *handle;
/* Plugin path */
char *path;
/* Enabled state */
bool enabled;
/* Instances */
WidgetInstance *instances;
int instance_count;
/* Plugin-private data */
void *user_data;
};
/*==============================================================================
* Widget Manager
*============================================================================*/
typedef struct WidgetManager WidgetManager;
/**
* Get the global widget manager.
*/
WidgetManager* wm_widget_manager_get(void);
/**
* Initialize the widget manager.
*/
bool wm_widget_manager_init(void);
/**
* Shutdown the widget manager.
*/
void wm_widget_manager_shutdown(void);
/**
* Register a widget plugin.
*/
bool wm_widget_manager_register(WidgetManager *mgr, WidgetPlugin *plugin);
/**
* Unregister a widget plugin.
*/
void wm_widget_manager_unregister(WidgetManager *mgr, WidgetPlugin *plugin);
/**
* Load a widget plugin from a file.
*/
WidgetPlugin* wm_widget_manager_load(WidgetManager *mgr, const char *path);
/**
* Unload a widget plugin.
*/
void wm_widget_manager_unload(WidgetManager *mgr, WidgetPlugin *plugin);
/**
* Get a loaded widget by ID.
*/
WidgetPlugin* wm_widget_manager_get_widget(WidgetManager *mgr, const char *id);
/**
* Get list of available widgets.
*/
const char** wm_widget_manager_list(WidgetManager *mgr, int *count);
/*==============================================================================
* Widget Instance Operations
*============================================================================*/
/**
* Create a widget instance.
*/
WidgetInstance* wm_widget_create_instance(WidgetPlugin *plugin,
const WmWidgetConfig *config);
/**
* Destroy a widget instance.
*/
void wm_widget_destroy_instance(WidgetInstance *instance);
/**
* Render a widget instance.
*/
void wm_widget_render(WidgetInstance *instance, WidgetContext *ctx);
/**
* Send an event to a widget instance.
*/
bool wm_widget_send_event(WidgetInstance *instance, const WmWidgetEvent *event);
/**
* Update a widget instance (request redraw).
*/
void wm_widget_update(WidgetInstance *instance);
/**
* Get preferred size for a widget instance.
*/
void wm_widget_get_preferred_size(WidgetInstance *instance,
int *width, int *height);
/*==============================================================================
* Widget Panel Integration
*============================================================================*/
typedef struct WidgetPanel WidgetPanel;
/**
* Create a widget panel (container for widget instances).
*/
WidgetPanel* wm_widget_panel_create(void);
/**
* Destroy a widget panel.
*/
void wm_widget_panel_destroy(WidgetPanel *panel);
/**
* Add a widget instance to a panel.
*/
void wm_widget_panel_add(WidgetPanel *panel, WidgetInstance *instance,
WmWidgetPosition position);
/**
* Remove a widget instance from a panel.
*/
void wm_widget_panel_remove(WidgetPanel *panel, WidgetInstance *instance);
/**
* Arrange widgets in a panel.
*/
void wm_widget_panel_arrange(WidgetPanel *panel, const WmRect *panel_geometry);
/**
* Render all widgets in a panel.
*/
void wm_widget_panel_render(WidgetPanel *panel);
/**
* Handle mouse event in a panel.
*/
bool wm_widget_panel_handle_event(WidgetPanel *panel,
const WmWidgetEvent *event);
/*==============================================================================
* Built-in Widgets
*============================================================================*/
/* Workspace switcher widget */
extern const WidgetPluginInterface wm_widget_workspaces;
/* Taskbar widget */
extern const WidgetPluginInterface wm_widget_taskbar;
/* Clock widget */
extern const WidgetPluginInterface wm_widget_clock;
/* System tray widget */
extern const WidgetPluginInterface wm_widget_systray;
/* CPU/Memory monitor widget */
extern const WidgetPluginInterface wm_widget_system_stats;
/* News ticker widget */
extern const WidgetPluginInterface wm_widget_news;
/*==============================================================================
* Plugin Entry Point
*============================================================================*/
typedef const WidgetPluginInterface* (*WidgetPluginEntryFunc)(void);
#define WM_WIDGET_PLUGIN_ENTRY "wm_widget_plugin_entry"
/*==============================================================================
* Helper Macros
*============================================================================*/
#define WM_WIDGET_REGISTER(name, iface) \
static const WidgetPluginInterface* wm_widget_plugin_entry(void) { return &(iface); }
#ifdef __cplusplus
}
#endif
#endif /* WIDGET_PLUGIN_H */

61
include/rules.h Normal file
View File

@ -0,0 +1,61 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Window rules engine for auto-applying settings
*/
#ifndef DWN_RULES_H
#define DWN_RULES_H
#include "dwn.h"
#include <stdbool.h>
#define MAX_RULES 64
#define RULE_PATTERN_SIZE 128
typedef enum {
RULE_MATCH_CLASS,
RULE_MATCH_TITLE,
RULE_MATCH_CLASS_AND_TITLE
} RuleMatchType;
typedef struct {
RuleMatchType match_type;
char class_pattern[RULE_PATTERN_SIZE];
char title_pattern[RULE_PATTERN_SIZE];
bool has_workspace;
int workspace;
bool has_floating;
bool floating;
bool has_sticky;
bool sticky;
bool has_fullscreen;
bool fullscreen;
bool has_size;
int width;
int height;
bool has_position;
int x;
int y;
bool has_opacity;
float opacity;
} WindowRule;
void rules_init(void);
void rules_cleanup(void);
bool rules_load(const char *path);
void rules_add(const WindowRule *rule);
void rules_clear(void);
int rules_count(void);
bool rules_apply_to_client(Client *client);
bool rules_match_pattern(const char *str, const char *pattern);
#endif

72
include/slider.h Normal file
View File

@ -0,0 +1,72 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Generic slider widget for panel controls
*/
#ifndef DWN_SLIDER_H
#define DWN_SLIDER_H
#include "dwn.h"
#include <stdbool.h>
#define SLIDER_WIDTH 30
#define SLIDER_HEIGHT 120
#define SLIDER_PADDING 8
#define SLIDER_KNOB_HEIGHT 8
/* Forward declaration */
typedef struct GenericSlider GenericSlider;
/* Callback type for value changes */
typedef void (*SliderValueChangedCallback)(GenericSlider *slider, int value);
/* Generic slider structure */
struct GenericSlider {
Window window;
int x, y;
int width, height;
bool visible;
bool dragging;
/* Value (always 0-100 internally) */
int value;
/* Display */
const char *label_prefix;
bool show_percentage;
/* Callback */
SliderValueChangedCallback on_value_changed;
void *user_data;
};
/* Lifecycle */
GenericSlider *slider_create(int x, int y,
const char *label_prefix,
bool show_percentage,
SliderValueChangedCallback callback,
void *user_data);
void slider_destroy(GenericSlider *slider);
/* Visibility */
void slider_show(GenericSlider *slider, int x, int y);
void slider_hide(GenericSlider *slider);
bool slider_is_visible(const GenericSlider *slider);
/* Rendering */
void slider_render(GenericSlider *slider);
/* Event handling */
void slider_handle_click(GenericSlider *slider, int x, int y);
void slider_handle_motion(GenericSlider *slider, int x, int y);
void slider_handle_release(GenericSlider *slider);
/* Value */
void slider_set_value(GenericSlider *slider, int value);
int slider_get_value(const GenericSlider *slider);
/* Hit testing */
bool slider_hit_test(const GenericSlider *slider, int x, int y);
#endif

View File

@ -8,6 +8,7 @@
#define DWN_SYSTRAY_H
#include "dwn.h"
#include "slider.h"
#include <stdbool.h>
#define MAX_TRAY_ICONS 32
@ -84,12 +85,22 @@ typedef struct {
bool dragging;
} VolumeSlider;
/* Generic slider wrapper for fade controls */
typedef struct {
GenericSlider *slider;
int icon_x; /* Position for popup */
} FadeControl;
extern WifiState wifi_state;
extern AudioState audio_state;
extern BatteryState battery_state;
extern MultiBatteryState multi_battery_state;
extern VolumeSlider *volume_slider;
/* Fade control externs */
extern FadeControl fade_speed_control;
extern FadeControl fade_intensity_control;
void systray_init(void);
void systray_cleanup(void);
@ -113,6 +124,15 @@ void volume_slider_handle_click(VolumeSlider *slider, int x, int y);
void volume_slider_handle_motion(VolumeSlider *slider, int x, int y);
void volume_slider_handle_release(VolumeSlider *slider);
/* Fade control functions */
void fade_controls_init(void);
void fade_controls_cleanup(void);
void fade_controls_render(Panel *panel, int x);
int fade_controls_get_width(void);
void fade_controls_handle_click(int control_id, int x, int y, int button);
int fade_controls_hit_test(int x);
void fade_controls_hide_all(void);
void systray_render(Panel *panel, int x, int *width);
int systray_get_width(void);
void systray_handle_click(int x, int y, int button);

263
include/thread_workers.h Normal file
View File

@ -0,0 +1,263 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Specialized Worker Threads - High-Level API
*
* Pre-built worker types for common async operations:
* - I/O Worker: File operations, networking
* - Compute Worker: CPU-intensive tasks
* - System Worker: /proc monitoring, system stats
* - Deferred Worker: Delayed/scheduled execution
*/
#ifndef DWN_THREAD_WORKERS_H
#define DWN_THREAD_WORKERS_H
#include "threading.h"
/* ============================================================================
* I/O Worker - Async File and Network Operations
* ============================================================================ */
typedef void (*IoCallback)(void *result, size_t size, int error, void *user_data);
typedef void (*IoProgressCallback)(size_t processed, size_t total, void *user_data);
/**
* Initialize I/O worker subsystem
*/
bool io_worker_init(void);
/**
* Shutdown I/O worker subsystem
*/
void io_worker_shutdown(void);
/**
* Read file asynchronously
* @param path File path
* @param callback Called with file contents
* @param user_data Passed to callback
* @return Task handle or NULL
*/
TaskHandle io_read_file_async(const char *path, IoCallback callback, void *user_data);
/**
* Write file asynchronously
*/
TaskHandle io_write_file_async(const char *path, const void *data, size_t size,
IoCallback callback, void *user_data);
/**
* Read directory contents asynchronously
* Result is a null-separated list of filenames
*/
TaskHandle io_read_dir_async(const char *path, IoCallback callback, void *user_data);
/**
* HTTP GET request asynchronously
* @param url URL to fetch
* @param callback Called with response body
* @param user_data Passed to callback
* @return Task handle or NULL
*/
TaskHandle io_http_get_async(const char *url, IoCallback callback, void *user_data);
/**
* HTTP POST request asynchronously
*/
TaskHandle io_http_post_async(const char *url, const void *data, size_t size,
IoCallback callback, void *user_data);
/**
* Download file with progress callback
*/
TaskHandle io_download_async(const char *url, const char *dest_path,
IoProgressCallback progress,
IoCallback callback, void *user_data);
/* ============================================================================
* Compute Worker - CPU-Intensive Tasks
* ============================================================================ */
typedef void (*ComputeCallback)(void *result, void *user_data);
/**
* Initialize compute worker subsystem
*/
bool compute_worker_init(void);
/**
* Shutdown compute worker subsystem
*/
void compute_worker_shutdown(void);
/**
* Parallel for - distribute work across threads
* @param start Start index
* @param end End index (exclusive)
* @param func Function called for each index
* @param user_data Passed to each invocation
*/
void compute_parallel_for(int start, int end,
void (*func)(int index, void *user_data),
void *user_data);
/**
* Map operation - apply function to each element
*/
void* compute_map(void *array, size_t count, size_t elem_size,
void (*func)(void *in, void *out, void *user_data),
void *user_data);
/**
* Reduce operation - aggregate values
*/
void* compute_reduce(void *array, size_t count, size_t elem_size,
void (*func)(void *accum, void *elem, void *user_data),
void *initial, void *user_data);
/**
* Submit compute task
*/
TaskHandle compute_submit(void (*func)(void *user_data), void *user_data,
ComputeCallback callback, void *callback_data);
/* ============================================================================
* System Worker - System Monitoring
* ============================================================================ */
typedef struct {
float cpu_percent;
uint64_t memory_used;
uint64_t memory_total;
float memory_percent;
float load_avg[3];
uint64_t uptime_seconds;
} SystemStats;
typedef struct {
uint32_t pid;
char name[64];
float cpu_percent;
uint64_t memory_bytes;
} ProcessInfo;
typedef void (*SystemStatsCallback)(const SystemStats *stats, void *user_data);
typedef void (*ProcessListCallback)(ProcessInfo *processes, uint32_t count, void *user_data);
/**
* Initialize system worker
*/
bool system_worker_init(void);
/**
* Shutdown system worker
*/
void system_worker_shutdown(void);
/**
* Get cached system stats (non-blocking, updated periodically)
*/
bool system_worker_get_cached_stats(SystemStats *stats);
/**
* Get fresh system stats (blocking)
*/
Future* system_worker_get_stats_async(void);
/**
* Get process list asynchronously
*/
TaskHandle system_worker_get_processes_async(ProcessListCallback callback, void *user_data);
/**
* Subscribe to periodic stats updates
* @param interval_ms Update interval (0 to disable)
* @param callback Called with new stats
* @param user_data Passed to callback
* @return Subscription ID
*/
uint32_t system_worker_subscribe_stats(uint32_t interval_ms,
SystemStatsCallback callback,
void *user_data);
/**
* Unsubscribe from stats updates
*/
bool system_worker_unsubscribe_stats(uint32_t subscription_id);
/* ============================================================================
* Deferred Worker - Delayed Execution
* ============================================================================ */
typedef uint64_t DeferredId;
typedef void (*DeferredCallback)(DeferredId id, void *user_data);
/**
* Initialize deferred worker
*/
bool deferred_worker_init(void);
/**
* Shutdown deferred worker
*/
void deferred_worker_shutdown(void);
/**
* Execute after delay
* @param delay_ms Milliseconds to wait
* @param callback Function to call
* @param user_data Passed to callback
* @return Deferred ID
*/
DeferredId deferred_after(uint64_t delay_ms, DeferredCallback callback, void *user_data);
/**
* Execute at regular interval
*/
DeferredId deferred_every(uint64_t interval_ms, DeferredCallback callback, void *user_data);
/**
* Cancel deferred execution
*/
bool deferred_cancel(DeferredId id);
/**
* Process deferred tasks (call from main loop)
* @return Number of tasks executed
*/
uint32_t deferred_process(void);
/**
* Get poll fd for select()
*/
int deferred_get_poll_fd(void);
/* ============================================================================
* High-Level Integration API
* ============================================================================ */
/**
* Initialize all worker subsystems
*/
bool thread_workers_init(void);
/**
* Shutdown all worker subsystems
*/
void thread_workers_shutdown(void);
/**
* Process all pending async work (call from main loop)
* @return true if work was processed
*/
bool thread_workers_process_all(void);
/**
* Get combined poll fds for select()
* @param fds Array to fill (size at least 4)
* @return Number of fds filled
*/
int thread_workers_get_poll_fds(int *fds);
#endif /* DWN_THREAD_WORKERS_H */

704
include/threading.h Normal file
View File

@ -0,0 +1,704 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Extensive Abstract Threading Framework
*
* This module provides a comprehensive, abstract threading system with:
* - Lock-free data structures
* - Thread pools with work stealing
* - Async/await pattern support
* - Thread-safe event bus
* - Memory barriers and atomic operations
*/
#ifndef DWN_THREADING_H
#define DWN_THREADING_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <pthread.h>
#include <stdatomic.h>
/* ============================================================================
* Platform Abstraction Layer
* ============================================================================ */
/* Cache line size (commonly 64 bytes on x86_64) */
#define CACHE_LINE_SIZE 64
/* Align to cache line to prevent false sharing */
#define CACHE_ALIGN __attribute__((aligned(CACHE_LINE_SIZE)))
/* Memory ordering shortcuts */
#define ATOMIC_RELAXED memory_order_relaxed
#define ATOMIC_ACQUIRE memory_order_acquire
#define ATOMIC_RELEASE memory_order_release
#define ATOMIC_ACQ_REL memory_order_acq_rel
#define ATOMIC_SEQ_CST memory_order_seq_cst
/* ============================================================================
* Core Types and Status Codes
* ============================================================================ */
typedef enum {
THREAD_OK = 0,
THREAD_ERROR = -1,
THREAD_ERROR_NOMEM = -2,
THREAD_ERROR_BUSY = -3,
THREAD_ERROR_CLOSED = -4,
THREAD_ERROR_TIMEOUT = -5,
THREAD_ERROR_INVALID = -6
} ThreadStatus;
typedef enum {
TASK_PRIORITY_CRITICAL = 0, /* UI-critical, must execute immediately */
TASK_PRIORITY_HIGH = 1, /* User-initiated actions */
TASK_PRIORITY_NORMAL = 2, /* Default background work */
TASK_PRIORITY_LOW = 3, /* Maintenance tasks */
TASK_PRIORITY_IDLE = 4, /* Only when system idle */
TASK_PRIORITY_COUNT
} TaskPriority;
typedef enum {
TASK_STATE_PENDING = 0,
TASK_STATE_RUNNING = 1,
TASK_STATE_COMPLETED = 2,
TASK_STATE_CANCELLED = 3,
TASK_STATE_ERROR = 4
} TaskState;
/* Forward declarations */
typedef struct ThreadPool ThreadPool;
typedef struct Task Task;
typedef struct Channel Channel;
typedef struct Future Future;
typedef struct EventBus EventBus;
typedef struct AsyncContext AsyncContext;
typedef struct Worker Worker;
/* ============================================================================
* Task System - Abstract Unit of Work
* ============================================================================ */
/* Task function signature - receives user data and cancellation flag */
typedef void (*TaskFunc)(void *user_data, atomic_int *cancelled);
/* Callback for task completion (called in worker thread) */
typedef void (*TaskCallback)(Task *task, void *user_data, ThreadStatus status);
/* Task handle - opaque pointer */
typedef Task* TaskHandle;
/**
* Create a new task
* @param func The function to execute
* @param user_data Data passed to the function
* @param priority Task priority level
* @return Task handle or NULL on error
*/
TaskHandle task_create(TaskFunc func, void *user_data, TaskPriority priority);
/**
* Create a task with completion callback
*/
TaskHandle task_create_with_callback(TaskFunc func, void *user_data,
TaskPriority priority,
TaskCallback on_complete,
void *callback_data);
/**
* Destroy a task (only if not submitted)
*/
void task_destroy(TaskHandle task);
/**
* Cancel a task (best effort - may already be running)
*/
bool task_cancel(TaskHandle task);
/**
* Get current task state
*/
TaskState task_get_state(TaskHandle task);
/**
* Wait for task completion (blocking)
*/
ThreadStatus task_wait(TaskHandle task, uint64_t timeout_ms);
/**
* Check if task was cancelled
*/
bool task_is_cancelled(TaskHandle task);
/* ============================================================================
* Thread Pool - Manage Worker Threads
* ============================================================================ */
/* Thread pool configuration */
typedef struct {
uint32_t min_threads; /* Minimum threads to keep alive */
uint32_t max_threads; /* Maximum threads allowed */
uint32_t queue_capacity; /* Task queue capacity per priority */
uint32_t steal_attempts; /* Work stealing attempts before blocking */
uint64_t idle_timeout_ms; /* Time before idle thread terminates */
bool enable_work_stealing; /* Enable work stealing between queues */
} ThreadPoolConfig;
/* Default configuration */
#define THREAD_POOL_DEFAULT_CONFIG ((ThreadPoolConfig){ \
.min_threads = 2, \
.max_threads = 8, \
.queue_capacity = 256, \
.steal_attempts = 4, \
.idle_timeout_ms = 60000, \
.enable_work_stealing = true \
})
/**
* Create a thread pool
*/
ThreadPool* thread_pool_create(const ThreadPoolConfig *config);
/**
* Destroy thread pool, cancelling pending tasks
*/
void thread_pool_destroy(ThreadPool *pool);
/**
* Submit a task to the pool
*/
ThreadStatus thread_pool_submit(ThreadPool *pool, TaskHandle task);
/**
* Submit a function as a task (convenience)
*/
ThreadStatus thread_pool_submit_func(ThreadPool *pool, TaskFunc func,
void *user_data, TaskPriority priority);
/**
* Get number of active threads
*/
uint32_t thread_pool_active_count(ThreadPool *pool);
/**
* Get number of pending tasks
*/
uint32_t thread_pool_pending_count(ThreadPool *pool);
/**
* Shutdown pool gracefully, waiting for tasks to complete
*/
ThreadStatus thread_pool_shutdown(ThreadPool *pool, uint64_t timeout_ms);
/**
* Get the default/global thread pool
*/
ThreadPool* thread_pool_default(void);
/**
* Initialize the default thread pool
*/
ThreadStatus thread_pool_init_default(const ThreadPoolConfig *config);
/**
* Shutdown the default thread pool
*/
void thread_pool_shutdown_default(void);
/* ============================================================================
* Lock-Free Queue - Single Producer Single Consumer
* ============================================================================ */
#define SPSC_QUEUE_SIZE 1024
typedef struct {
_Atomic uint64_t head CACHE_ALIGN; /* Write index - producer only */
_Atomic uint64_t tail CACHE_ALIGN; /* Read index - consumer only */
void *buffer[SPSC_QUEUE_SIZE];
} SpscQueue;
/**
* Initialize SPSC queue
*/
void spsc_queue_init(SpscQueue *q);
/**
* Push item (producer only)
* @return false if queue full
*/
bool spsc_queue_push(SpscQueue *q, void *item);
/**
* Pop item (consumer only)
* @return false if queue empty
*/
bool spsc_queue_pop(SpscQueue *q, void **item);
/**
* Check if queue is empty (consumer only)
*/
bool spsc_queue_empty(SpscQueue *q);
/**
* Get approximate size (not synchronized)
*/
uint64_t spsc_queue_size_approx(SpscQueue *q);
/* ============================================================================
* Lock-Free Queue - Multi Producer Single Consumer
* ============================================================================ */
typedef struct MpscQueue MpscQueue;
/**
* Create MPSC queue
*/
MpscQueue* mpsc_queue_create(uint32_t capacity);
/**
* Destroy MPSC queue
*/
void mpsc_queue_destroy(MpscQueue *q);
/**
* Push item (thread-safe, any producer)
* @return false if queue full
*/
bool mpsc_queue_push(MpscQueue *q, void *item);
/**
* Pop item (consumer only - single thread)
* @return false if queue empty
*/
bool mpsc_queue_pop(MpscQueue *q, void **item);
/**
* Check if queue is empty
*/
bool mpsc_queue_empty(MpscQueue *q);
/* ============================================================================
* Channel - Thread-Safe Communication
* ============================================================================ */
typedef enum {
CHANNEL_UNBUFFERED = 0, /* Synchronous - sender blocks until received */
CHANNEL_BUFFERED = 1 /* Asynchronous - uses internal buffer */
} ChannelType;
/**
* Create a channel
* @param capacity Buffer size (0 for unbuffered synchronous channel)
*/
Channel* channel_create(uint32_t capacity);
/**
* Destroy channel
*/
void channel_destroy(Channel *ch);
/**
* Send data through channel (blocking)
* @return THREAD_OK on success, THREAD_ERROR_CLOSED if closed
*/
ThreadStatus channel_send(Channel *ch, void *data);
/**
* Send with timeout
*/
ThreadStatus channel_send_timeout(Channel *ch, void *data, uint64_t timeout_ms);
/**
* Try send (non-blocking)
*/
ThreadStatus channel_try_send(Channel *ch, void *data);
/**
* Receive from channel (blocking)
*/
ThreadStatus channel_recv(Channel *ch, void **data);
/**
* Receive with timeout
*/
ThreadStatus channel_recv_timeout(Channel *ch, void **data, uint64_t timeout_ms);
/**
* Try receive (non-blocking)
*/
ThreadStatus channel_try_recv(Channel *ch, void **data);
/**
* Close channel (no more sends allowed)
*/
void channel_close(Channel *ch);
/**
* Check if channel is closed
*/
bool channel_is_closed(Channel *ch);
/**
* Select on multiple channels (like Go's select)
* Returns index of ready channel or -1 on timeout
*/
int channel_select(Channel **channels, uint32_t count, uint64_t timeout_ms,
void **out_data);
/* ============================================================================
* Future/Promise - Async Result Handling
* ============================================================================ */
typedef void* FutureResult;
typedef void (*FutureCallback)(Future *f, FutureResult result, void *user_data);
/**
* Create a future
*/
Future* future_create(void);
/**
* Destroy future
*/
void future_destroy(Future *f);
/**
* Set the result (called by producer)
*/
void future_set_result(Future *f, FutureResult result);
/**
* Set error result
*/
void future_set_error(Future *f, int error_code);
/**
* Get result (blocking)
*/
FutureResult future_get(Future *f, ThreadStatus *status);
/**
* Get result with timeout
*/
FutureResult future_get_timeout(Future *f, uint64_t timeout_ms, ThreadStatus *status);
/**
* Check if future is ready
*/
bool future_is_ready(Future *f);
/**
* Attach callback to be called when ready (thread-safe)
*/
void future_then(Future *f, FutureCallback callback, void *user_data);
/**
* Create future that completes when all given futures complete
*/
Future* future_all(Future **futures, uint32_t count);
/**
* Create future that completes when any given future completes
*/
Future* future_any(Future **futures, uint32_t count);
/* ============================================================================
* Event Bus - Thread-Safe Pub/Sub
* ============================================================================ */
typedef uint32_t EventType;
typedef uint32_t SubscriptionId;
/* Event handler signature */
typedef void (*EventHandler)(EventType type, void *event_data, void *user_data);
/* Event filter - return true to allow event */
typedef bool (*EventFilter)(EventType type, void *event_data, void *user_data);
/**
* Create event bus
*/
EventBus* event_bus_create(void);
/**
* Destroy event bus
*/
void event_bus_destroy(EventBus *bus);
/**
* Subscribe to event type
* @return Subscription ID or 0 on error
*/
SubscriptionId event_bus_subscribe(EventBus *bus, EventType type,
EventHandler handler, void *user_data);
/**
* Subscribe with filter
*/
SubscriptionId event_bus_subscribe_filtered(EventBus *bus, EventType type,
EventHandler handler, void *user_data,
EventFilter filter, void *filter_data);
/**
* Unsubscribe
*/
bool event_bus_unsubscribe(EventBus *bus, SubscriptionId id);
/**
* Publish event (thread-safe)
*/
void event_bus_publish(EventBus *bus, EventType type, void *event_data);
/**
* Publish event with custom free function
*/
void event_bus_publish_owned(EventBus *bus, EventType type, void *event_data,
void (*free_fn)(void*));
/**
* Process pending events (call from main thread)
* @return number of events processed
*/
uint32_t event_bus_process(EventBus *bus);
/**
* Set processing limit per call
*/
void event_bus_set_batch_size(EventBus *bus, uint32_t batch_size);
/**
* Get the global/default event bus
*/
EventBus* event_bus_default(void);
/**
* Initialize default event bus
*/
bool event_bus_init_default(void);
/**
* Shutdown default event bus
*/
void event_bus_shutdown_default(void);
/* ============================================================================
* Async Context - Per-Module Async State
* ============================================================================ */
/* Context for managing async operations within a module */
struct AsyncContext {
ThreadPool *pool;
EventBus *event_bus;
Channel *completion_channel;
atomic_int operation_count;
atomic_int shutdown_requested;
pthread_mutex_t mutex;
};
/**
* Create async context
*/
AsyncContext* async_context_create(const char *name);
/**
* Destroy async context (cancels all pending operations)
*/
void async_context_destroy(AsyncContext *ctx);
/**
* Submit work to context's thread pool
*/
TaskHandle async_submit(AsyncContext *ctx, TaskFunc func, void *user_data,
TaskPriority priority);
/**
* Submit work and get future
*/
Future* async_submit_future(AsyncContext *ctx, TaskFunc func, void *user_data,
TaskPriority priority);
/**
* Check for completed operations (call from main thread)
*/
uint32_t async_poll(AsyncContext *ctx);
/**
* Get file descriptor for select() integration
* Returns -1 if not available
*/
int async_get_poll_fd(AsyncContext *ctx);
/* ============================================================================
* Timer/Scheduler - Delayed Execution
* ============================================================================ */
typedef uint64_t TimerId;
typedef void (*TimerCallback)(TimerId id, void *user_data);
/**
* Schedule one-shot timer
*/
TimerId timer_schedule(uint64_t delay_ms, TimerCallback callback, void *user_data);
/**
* Schedule repeating timer
*/
TimerId timer_schedule_repeating(uint64_t interval_ms, TimerCallback callback,
void *user_data);
/**
* Cancel timer
*/
bool timer_cancel(TimerId id);
/**
* Check if timer exists
*/
bool timer_exists(TimerId id);
/**
* Process timer events (call from main thread)
* @return Number of timers fired
*/
uint32_t timer_process(void);
/**
* Get timer file descriptor for select()
*/
int timer_get_poll_fd(void);
/**
* Initialize timer subsystem
*/
bool timer_init(void);
/**
* Shutdown timer subsystem
*/
void timer_shutdown(void);
/* ============================================================================
* Thread-Local Storage Abstraction
* ============================================================================ */
typedef struct TlsKey TlsKey;
/**
* Create TLS key
*/
TlsKey* tls_create(void (*destructor)(void*));
/**
* Destroy TLS key
*/
void tls_destroy(TlsKey *key);
/**
* Set TLS value
*/
void tls_set(TlsKey *key, void *value);
/**
* Get TLS value
*/
void* tls_get(TlsKey *key);
/* ============================================================================
* Read-Write Lock Wrapper
* ============================================================================ */
typedef struct RwLock RwLock;
RwLock* rwlock_create(void);
void rwlock_destroy(RwLock *lock);
void rwlock_read_lock(RwLock *lock);
bool rwlock_read_trylock(RwLock *lock);
void rwlock_read_unlock(RwLock *lock);
void rwlock_write_lock(RwLock *lock);
bool rwlock_write_trylock(RwLock *lock);
void rwlock_write_unlock(RwLock *lock);
/* ============================================================================
* Barrier and Synchronization
* ============================================================================ */
typedef struct Barrier Barrier;
Barrier* barrier_create(uint32_t count);
void barrier_destroy(Barrier *b);
bool barrier_wait(Barrier *b, uint64_t timeout_ms);
/* ============================================================================
* Initialization and Cleanup
* ============================================================================ */
/**
* Initialize entire threading subsystem
*/
bool threading_init(void);
/**
* Shutdown entire threading subsystem
*/
void threading_shutdown(void);
/**
* Get number of hardware threads
*/
uint32_t threading_hw_concurrency(void);
/**
* Current thread ID
*/
uint64_t threading_current_thread_id(void);
/**
* Set current thread name (for debugging)
*/
void threading_set_name(const char *name);
/**
* Yield current thread
*/
void threading_yield(void);
/* ============================================================================
* Utility Macros
* ============================================================================ */
/* Run function in background */
#define ASYNC(func, data) \
thread_pool_submit_func(thread_pool_default(), (func), (data), TASK_PRIORITY_NORMAL)
/* Run function with priority */
#define ASYNC_PRIORITY(func, data, prio) \
thread_pool_submit_func(thread_pool_default(), (func), (data), (prio))
/* Create future and submit */
#define ASYNC_FUTURE(ctx, func, data) \
async_submit_future((ctx), (func), (data), TASK_PRIORITY_NORMAL)
/* Synchronized block using mutex */
#define WITH_MUTEX(mutex, code) do { \
pthread_mutex_lock(&(mutex)); \
code; \
pthread_mutex_unlock(&(mutex)); \
} while(0)
/* Read lock block */
#define WITH_READ_LOCK(rwlock, code) do { \
rwlock_read_lock((rwlock)); \
code; \
rwlock_read_unlock((rwlock)); \
} while(0)
/* Write lock block */
#define WITH_WRITE_LOCK(rwlock, code) do { \
rwlock_write_lock((rwlock)); \
code; \
rwlock_write_unlock((rwlock)); \
} while(0)
#endif /* DWN_THREADING_H */

View File

@ -0,0 +1,75 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Threading Integration Header
*/
#ifndef DWN_THREADING_INTEGRATION_H
#define DWN_THREADING_INTEGRATION_H
#include "threading.h"
#include "thread_workers.h"
#include <sys/select.h>
/**
* Initialize threading integration with main loop
*/
bool threading_integration_init(void);
/**
* Shutdown threading integration
*/
void threading_integration_shutdown(void);
/**
* Prepare fd_set for select() - adds threading fds
*/
void threading_integration_prepare_select(int xfd, fd_set *fds, int *max_fd);
/**
* Process threading events after select() returns
*/
void threading_integration_process(fd_set *fds);
/**
* Non-blocking poll for async work
*/
void threading_integration_poll(void);
/**
* Run one iteration with integrated threading
*/
void threading_integration_run_iteration(int xfd, void (*handle_xevent)(void));
/**
* Get stats string for display
*/
void threading_integration_get_stats(char *buf, size_t bufsize);
/**
* Initialize per-module async contexts
*/
bool threading_integration_init_module_contexts(void);
/**
* Cleanup per-module async contexts
*/
void threading_integration_cleanup_module_contexts(void);
/**
* Get module-specific async contexts
*/
AsyncContext* threading_integration_get_ai_context(void);
AsyncContext* threading_integration_get_screenshot_context(void);
AsyncContext* threading_integration_get_ocr_context(void);
/**
* High-level task submission
*/
TaskHandle threading_integration_submit_ai(TaskFunc func, void *user_data);
TaskHandle threading_integration_submit_screenshot(TaskFunc func, void *user_data);
TaskHandle threading_integration_submit_ocr(TaskFunc func, void *user_data);
TaskHandle threading_integration_submit_ui(TaskFunc func, void *user_data);
TaskHandle threading_integration_submit_background(TaskFunc func, void *user_data);
#endif /* DWN_THREADING_INTEGRATION_H */

View File

@ -11,6 +11,7 @@
#include <stddef.h>
#include <stdarg.h>
#include <assert.h>
#include <string.h>
#define DWN_ASSERT(cond) assert(cond)
#define DWN_ASSERT_MSG(cond, msg) assert((cond) && (msg))
@ -46,6 +47,27 @@ bool str_ends_with(const char *str, const char *suffix);
int str_split(char *str, char delim, char **parts, int max_parts);
char *shell_escape(const char *str);
/* Safe string copy that always null-terminates */
static inline void safe_strncpy(char *dest, const char *src, size_t n)
{
if (n == 0) return;
size_t src_len = strlen(src);
size_t copy_len = (src_len < n - 1) ? src_len : n - 1;
memcpy(dest, src, copy_len);
dest[copy_len] = '\0';
}
/* Check if value is within valid range */
static inline bool in_range_int(int val, int min, int max)
{
return val >= min && val <= max;
}
static inline bool in_range_size_t(size_t val, size_t min, size_t max)
{
return val >= min && val <= max;
}
bool file_exists(const char *path);
char *file_read_all(const char *path);
bool file_write_all(const char *path, const char *content);

View File

@ -0,0 +1,305 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Abstraction Layer - DWN Documentation</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<button class="mobile-menu-btn">Menu</button>
<div class="layout">
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
<input type="text" class="search-input" placeholder="Search docs...">
</div>
<nav class="sidebar-nav">
<div class="nav-section">
<div class="nav-section-title">Getting Started</div>
<a href="index.html" class="nav-link">Introduction</a>
<a href="installation.html" class="nav-link">Installation</a>
<a href="quickstart.html" class="nav-link">Quick Start</a>
</div>
<div class="nav-section">
<div class="nav-section-title">User Guide</div>
<a href="features.html" class="nav-link">Features</a>
<a href="shortcuts.html" class="nav-link">Keyboard Shortcuts</a>
<a href="configuration.html" class="nav-link">Configuration</a>
<a href="layouts.html" class="nav-link">Layouts</a>
<a href="ai-features.html" class="nav-link">AI Integration</a>
</div>
<div class="nav-section">
<div class="nav-section-title">API Reference</div>
<a href="api-overview.html" class="nav-link">API Overview</a>
<a href="api-reference.html" class="nav-link">API Reference</a>
<a href="api-examples.html" class="nav-link">API Examples</a>
</div>
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link active">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
</aside>
<main class="main-content">
<div class="content">
<div class="page-header">
<h1>Abstraction Layer</h1>
<p class="lead">Backend-agnostic architecture for future extensibility</p>
</div>
<div class="toc">
<div class="toc-title">On this page</div>
<ul class="toc-list">
<li><a href="#overview">Overview</a></li>
<li><a href="#core-types">Core Types</a></li>
<li><a href="#backend-interface">Backend Interface</a></li>
<li><a href="#client-abstraction">Client Abstraction</a></li>
<li><a href="#containers">Container Types</a></li>
<li><a href="#migration">Migration Strategy</a></li>
</ul>
</div>
<h2 id="overview">Overview</h2>
<p>DWN v2.0 introduces a comprehensive abstraction layer that separates the window manager logic from backend-specific implementations. This architecture enables:</p>
<ul>
<li><strong>Backend Portability</strong> - Clean migration path from X11 to Wayland</li>
<li><strong>Type Safety</strong> - Strongly typed handles eliminate void* casting</li>
<li><strong>Memory Safety</strong> - Abstract strings and containers prevent buffer overflows</li>
<li><strong>Plugin Extensibility</strong> - Dynamic loading of layouts and widgets</li>
<li><strong>100% Compatibility</strong> - Existing code continues to work unchanged</li>
</ul>
<h2 id="core-types">Core Types</h2>
<p>The abstraction layer provides type-safe replacements for backend-specific types:</p>
<div class="table-container">
<table>
<thead>
<tr>
<th>Abstract Type</th>
<th>X11 Equivalent</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>WmWindowHandle</code></td>
<td><code>Window</code></td>
<td>Opaque window reference</td>
</tr>
<tr>
<td><code>WmClientId</code></td>
<td>-</td>
<td>Unique client identifier</td>
</tr>
<tr>
<td><code>WmWorkspaceId</code></td>
<td><code>int</code></td>
<td>Workspace identifier</td>
</tr>
<tr>
<td><code>WmRect</code></td>
<td>-</td>
<td>Rectangle geometry (x, y, w, h)</td>
</tr>
<tr>
<td><code>WmColor</code></td>
<td><code>unsigned long</code></td>
<td>RGBA color value</td>
</tr>
</tbody>
</table>
</div>
<h3>Geometry Operations</h3>
<p>Inline functions for rectangle operations:</p>
<div class="code-block">
<pre><code>WmRect rect = wm_rect_make(0, 0, 1920, 1080);
bool contains = wm_rect_contains_point(&rect, 100, 100);
bool intersects = wm_rect_intersects(&rect1, &rect2);
WmRect intersection = wm_rect_intersection(&rect1, &rect2);</code></pre>
</div>
<h3>Color Operations</h3>
<div class="code-block">
<pre><code>WmColor color = wm_color_rgb(255, 0, 128); // RGB
WmColor color = wm_color_rgba(255, 0, 128, 200); // RGBA
uint8_t r = wm_color_get_red(color);
uint8_t a = wm_color_get_alpha(color);</code></pre>
</div>
<h2 id="backend-interface">Backend Interface</h2>
<p>The backend interface defines a vtable of operations that any backend must implement:</p>
<div class="code-block">
<pre><code>typedef struct BackendInterface {
/* Identification */
const char *name;
WmBackendInfo (*get_info)(void);
/* Lifecycle */
bool (*init)(void *config);
void (*shutdown)(void);
/* Window Management */
void (*window_move)(WmWindowHandle window, int x, int y);
void (*window_resize)(WmWindowHandle window, int width, int height);
void (*window_focus)(WmWindowHandle window);
/* Events */
bool (*poll_event)(WmBackendEvent *event_out);
/* ... 80+ operations */
} BackendInterface;</code></pre>
</div>
<h3>X11 Backend</h3>
<p>The X11 backend is the reference implementation, translating abstract operations to X11 calls:</p>
<ul>
<li>Event translation (X11 → abstract events)</li>
<li>Protocol support (ICCCM, EWMH)</li>
<li>Property management with atom caching</li>
<li>Error handling with custom handlers</li>
</ul>
<h3>Future Backends</h3>
<p>The architecture supports multiple backends:</p>
<ul>
<li><strong>X11</strong> - Current, fully implemented</li>
<li><strong>Wayland</strong> - Planned for future</li>
<li><strong>Headless</strong> - For testing and CI</li>
</ul>
<h2 id="client-abstraction">Client Abstraction</h2>
<p>The <code>AbstractClient</code> type provides a backend-agnostic representation of a managed window:</p>
<div class="code-block">
<pre><code>/* Create from native window */
AbstractClient* client = wm_client_create(window, WM_CLIENT_TYPE_NORMAL);
/* State management */
wm_client_set_state(client, WM_CLIENT_STATE_FULLSCREEN);
wm_client_add_state(client, WM_CLIENT_STATE_FLOATING);
bool is_floating = wm_client_is_floating(client);
/* Geometry */
WmRect geom = wm_client_get_geometry(client);
wm_client_set_geometry(client, &new_geom);
wm_client_move_resize(client, x, y, width, height);
/* Properties */
wm_client_set_title(client, "New Title");
const char* title = wm_client_get_title(client);</code></pre>
</div>
<h3>Legacy Compatibility</h3>
<p>Abstract clients maintain bidirectional synchronization with legacy <code>Client</code> structures:</p>
<div class="code-block">
<pre><code>/* Wrap existing legacy client */
AbstractClient* abs_client = wm_client_from_legacy(legacy_client);
/* Access legacy client when needed */
Client* legacy = wm_client_get_legacy(abs_client);</code></pre>
</div>
<h2 id="containers">Container Types</h2>
<p>Safe, dynamic container implementations:</p>
<h3>WmString (Dynamic Strings)</h3>
<div class="code-block">
<pre><code>WmString* str = wm_string_new("Hello");
wm_string_append(str, " World");
wm_string_append_printf(str, " %d", 42);
const char* cstr = wm_string_cstr(str);
bool empty = wm_string_is_empty(str);
wm_string_destroy(str);</code></pre>
</div>
<h3>WmList (Dynamic Arrays)</h3>
<div class="code-block">
<pre><code>WmList* list = wm_list_new();
wm_list_append(list, item1);
wm_list_prepend(list, item2);
void* item = wm_list_get(list, 0);
wm_list_foreach(list, my_callback, user_data);
wm_list_destroy(list);</code></pre>
</div>
<h3>WmHashMap</h3>
<div class="code-block">
<pre><code>WmHashMap* map = wm_hashmap_new_string_key();
wm_hashmap_insert_string(map, "key", value);
void* value = wm_hashmap_get_string(map, "key");
bool exists = wm_hashmap_contains_string(map, "key");
wm_hashmap_destroy(map);</code></pre>
</div>
<h2 id="migration">Migration Strategy</h2>
<p>The abstraction layer uses an incremental migration approach:</p>
<ol>
<li><strong>Phase 1: Infrastructure</strong> (Complete)
<ul>
<li>Core types defined</li>
<li>Backend interface specified</li>
<li>X11 backend implemented</li>
</ul>
</li>
<li><strong>Phase 2: Client Migration</strong> (Complete)
<ul>
<li>AbstractClient type created</li>
<li>Bidirectional sync with legacy Client</li>
<li>Client manager with MRU tracking</li>
</ul>
</li>
<li><strong>Phase 3: Plugin System</strong> (Complete)
<ul>
<li>Layout plugin API</li>
<li>Widget plugin API</li>
<li>4 built-in layout plugins</li>
</ul>
</li>
<li><strong>Phase 4: Future</strong>
<ul>
<li>Gradual migration of existing code</li>
<li>Wayland backend implementation</li>
<li>Legacy code deprecation (long-term)</li>
</ul>
</li>
</ol>
<div class="alert alert-info">
<strong>Note:</strong> The abstraction layer is fully backward compatible. Existing code using X11 types continues to work unchanged.
</div>
<footer>
<p>DWN Window Manager - retoor &lt;retoor@molodetz.nl&gt;</p>
</footer>
</div>
</main>
</div>
<script src="js/main.js"></script>
</body>
</html>

View File

@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>

View File

@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@ -65,6 +67,7 @@
<li><a href="#python">Python Examples</a></li>
<li><a href="#javascript">JavaScript Examples</a></li>
<li><a href="#bash">Bash Examples</a></li>
<li><a href="#fade-control">Fade Effect Control</a></li>
<li><a href="#events">Event Subscription</a></li>
<li><a href="#automation">Automation Recipes</a></li>
<li><a href="#browser-ocr">Browser Automation with OCR</a></li>
@ -78,17 +81,17 @@
<pre><code>import json
import websocket
ws = websocket.create_connection("ws://localhost:8777")
ws = websocket.create_connection("ws://localhost:8777/ws")
def send_command(command, **params):
request = {"command": command, **params}
ws.send(json.dumps(request))
return json.loads(ws.recv())
# List all windows
result = send_command("list_windows")
for window in result["windows"]:
print(f"{window['title']} ({window['class']})")
# List all clients
result = send_command("get_clients")
for client in result["clients"]:
print(f"{client['title']} ({client['class']})")
ws.close()</code></pre>
</div>
@ -100,21 +103,21 @@ ws.close()</code></pre>
client = DWNClient()
client.connect()
# Get all windows
windows = client.list_windows()
print(f"Found {len(windows)} windows")
# Get all clients
clients = client.get_clients()
print(f"Found {len(clients)} clients")
# Focus window by title
for w in windows:
if "Firefox" in w["title"]:
client.focus_window(w["id"])
# Focus client by title
for c in clients:
if "Firefox" in c["title"]:
client.focus_client(c["window"])
break
# Switch to workspace 3
client.switch_workspace(2)
# Type some text
client.type_text("Hello from Python!")
client.key_type("Hello from Python!")
client.disconnect()</code></pre>
</div>
@ -145,30 +148,30 @@ print(ocr_result["text"])
client.disconnect()</code></pre>
</div>
<h3>Window Arrangement Script</h3>
<h3>Client Arrangement Script</h3>
<div class="code-block">
<pre><code>from dwn_api_client import DWNClient
def arrange_coding_setup(client):
"""Arrange windows for coding: editor left, terminal right"""
windows = client.list_windows()
"""Arrange clients for coding: editor left, terminal right"""
clients = client.get_clients()
# Find VS Code and terminal
vscode = None
terminal = None
for w in windows:
if "code" in w["class"].lower():
vscode = w
elif "terminal" in w["class"].lower():
terminal = w
for c in clients:
if "code" in c["class"].lower():
vscode = c
elif "terminal" in c["class"].lower():
terminal = c
if vscode:
client.move_window(vscode["id"], 0, 32)
client.resize_window(vscode["id"], 960, 1048)
client.move_client(vscode["window"], 0, 32)
client.resize_client(vscode["window"], 960, 1048)
if terminal:
client.move_window(terminal["id"], 960, 32)
client.resize_window(terminal["id"], 960, 1048)
client.move_client(terminal["window"], 960, 32)
client.resize_client(terminal["window"], 960, 1048)
client = DWNClient()
client.connect()
@ -183,15 +186,15 @@ import json
import websockets
async def main():
async with websockets.connect("ws://localhost:8777") as ws:
async with websockets.connect("ws://localhost:8777/ws") as ws:
# Send command
await ws.send(json.dumps({"command": "list_windows"}))
await ws.send(json.dumps({"command": "get_clients"}))
# Receive response
response = json.loads(await ws.recv())
for window in response["windows"]:
print(f"Window: {window['title']}")
for client in response["clients"]:
print(f"Client: {client['title']}")
asyncio.run(main())</code></pre>
</div>
@ -201,7 +204,7 @@ asyncio.run(main())</code></pre>
<h3>Browser WebSocket</h3>
<div class="code-block">
<pre><code>class DWNClient {
constructor(url = 'ws://localhost:8777') {
constructor(url = 'ws://localhost:8777/ws') {
this.url = url;
this.ws = null;
this.pending = new Map();
@ -228,16 +231,16 @@ asyncio.run(main())</code></pre>
this.ws.send(JSON.stringify(request));
}
async listWindows() {
this.send('list_windows');
async getClients() {
this.send('get_clients');
}
async focusWindow(windowId) {
this.send('focus_window', { window: windowId });
async focusClient(windowId) {
this.send('focus_client', { window: windowId });
}
async typeText(text) {
this.send('type_text', { text });
async keyType(text) {
this.send('key_type', { text });
}
async screenshot(mode = 'fullscreen') {
@ -248,28 +251,28 @@ asyncio.run(main())</code></pre>
// Usage
const client = new DWNClient();
await client.connect();
await client.listWindows();</code></pre>
await client.getClients();</code></pre>
</div>
<h3>Node.js Client</h3>
<div class="code-block">
<pre><code>const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8777');
const ws = new WebSocket('ws://localhost:8777/ws');
ws.on('open', () => {
console.log('Connected to DWN');
// List windows
ws.send(JSON.stringify({ command: 'list_windows' }));
// List clients
ws.send(JSON.stringify({ command: 'get_clients' }));
});
ws.on('message', (data) => {
const response = JSON.parse(data);
if (response.windows) {
response.windows.forEach(w => {
console.log(`${w.title} - ${w.class}`);
if (response.clients) {
response.clients.forEach(c => {
console.log(`${c.title} - ${c.class}`);
});
}
@ -311,18 +314,18 @@ ws.on('error', (err) => {
<div class="code-block">
<pre><code>#!/bin/bash
# List windows
echo '{"command": "list_windows"}' | websocat ws://localhost:8777
# List clients
echo '{"command": "get_clients"}' | websocat ws://localhost:8777/ws
# Focus window by ID
echo '{"command": "focus_window", "window": 12345678}' | websocat ws://localhost:8777
# Focus client by ID
echo '{"command": "focus_client", "window": 12345678}' | websocat ws://localhost:8777/ws
# Switch workspace
echo '{"command": "switch_workspace", "workspace": 2}' | websocat ws://localhost:8777
echo '{"command": "switch_workspace", "workspace": 2}' | websocat ws://localhost:8777/ws
# Take screenshot and save
echo '{"command": "screenshot", "mode": "fullscreen"}' | \
websocat ws://localhost:8777 | \
websocat ws://localhost:8777/ws | \
jq -r '.data' | \
base64 -d > screenshot.png</code></pre>
</div>
@ -332,10 +335,10 @@ echo '{"command": "screenshot", "mode": "fullscreen"}' | \
<pre><code>#!/bin/bash
# One-liner command
echo '{"command": "list_windows"}' | wscat -c ws://localhost:8777 -w 1
echo '{"command": "get_clients"}' | wscat -c ws://localhost:8777/ws -w 1
# Interactive session
wscat -c ws://localhost:8777
wscat -c ws://localhost:8777/ws
# Then type commands manually</code></pre>
</div>
@ -344,17 +347,65 @@ wscat -c ws://localhost:8777
<pre><code>#!/bin/bash
dwn_command() {
echo "$1" | websocat -n1 ws://localhost:8777
echo "$1" | websocat -n1 ws://localhost:8777/ws
}
# Get focused window
dwn_command '{"command": "get_focused"}' | jq '.window.title'
# Get focused client
dwn_command '{"command": "get_focused_client"}' | jq '.client.title'
# Type text
dwn_command '{"command": "type_text", "text": "Hello!"}'
dwn_command '{"command": "key_type", "text": "Hello!"}'
# Launch application
dwn_command '{"command": "spawn", "program": "firefox"}'</code></pre>
dwn_command '{"command": "run_command", "exec": "firefox"}'</code></pre>
</div>
<h3>Fade Effect Control</h3>
<p>Control DWN's fade animation effects via API:</p>
<div class="code-block">
<pre><code>import json
import websocket
ws = websocket.create_connection("ws://localhost:8777/ws")
# Get current fade settings
ws.send(json.dumps({"command": "get_fade_settings"}))
response = json.loads(ws.recv())
print(f"Speed: {response['fade_speed']}, Intensity: {response['fade_intensity']}")
# Set fade animation speed (0.1 - 3.0)
ws.send(json.dumps({"command": "set_fade_speed", "speed": 1.5}))
# Set fade glow intensity (0.0 - 1.0)
ws.send(json.dumps({"command": "set_fade_intensity", "intensity": 0.8}))
# Subscribe to fade change events
ws.send(json.dumps({
"command": "subscribe",
"events": ["fade_speed_changed", "fade_intensity_changed"]
}))
# Listen for events
while True:
event = json.loads(ws.recv())
if event.get("type") == "event":
print(f"Event: {event['event']}")
print(f"Data: {event['data']}")</code></pre>
</div>
<p>Using the provided fade control demo script:</p>
<div class="code-block">
<pre><code># Get current fade settings
python3 examples/fade_control_demo.py
# Set fade speed
python3 examples/fade_control_demo.py --speed 1.5
# Set fade intensity
python3 examples/fade_control_demo.py --intensity 0.8
# Listen for fade events
python3 examples/fade_control_demo.py --listen --duration 30</code></pre>
</div>
<h2 id="events">Event Subscription</h2>
@ -452,7 +503,7 @@ activity_logger()</code></pre>
<h3>JavaScript Event Listener</h3>
<div class="code-block">
<pre><code>const ws = new WebSocket('ws://localhost:8777');
<pre><code>const ws = new WebSocket('ws://localhost:8777/ws');
ws.onopen = () => {
console.log('Connected to DWN');
@ -491,7 +542,7 @@ ws.onclose = () => {
};</code></pre>
</div>
<h3>Reactive Window Arrangement</h3>
<h3>Reactive Client Arrangement</h3>
<div class="code-block">
<pre><code>from dwn_api_client import DWNClient
@ -508,7 +559,7 @@ def auto_arrange():
client.subscribe(events=["window_created"])
print("Auto-arranging windows...")
print("Auto-arranging clients...")
try:
while True:
@ -541,7 +592,7 @@ auto_arrange()</code></pre>
<h2 id="automation">Automation Recipes</h2>
<h3>Auto-Arrange Windows by Class</h3>
<h3>Auto-Arrange Clients by Class</h3>
<div class="code-block">
<pre><code>from dwn_api_client import DWNClient
@ -556,17 +607,17 @@ def auto_arrange():
client = DWNClient()
client.connect()
windows = client.list_windows()
for w in windows:
win_class = w["class"].lower()
clients = client.get_clients()
for c in clients:
wm_class = c["class"].lower()
for pattern, rules in LAYOUT_RULES.items():
if pattern in win_class:
if w["workspace"] != rules["workspace"]:
client.move_window_to_workspace(
w["id"], rules["workspace"]
if pattern in wm_class:
if c["workspace"] != rules["workspace"]:
client.move_client_to_workspace(
c["window"], rules["workspace"]
)
if w["floating"] != rules["floating"]:
client.set_floating(w["id"], rules["floating"])
if c["floating"] != rules["floating"]:
client.float_client(c["window"], rules["floating"])
break
client.disconnect()
@ -609,14 +660,14 @@ def screenshot_monitor(interval=60, output_dir="screenshots"):
screenshot_monitor(interval=300) # Every 5 minutes</code></pre>
</div>
<h3>Window Focus Logger</h3>
<h3>Client Focus Logger</h3>
<div class="code-block">
<pre><code>import time
from datetime import datetime
from dwn_api_client import DWNClient
def focus_logger(log_file="focus_log.txt"):
"""Log window focus changes"""
"""Log client focus changes"""
client = DWNClient()
client.connect()
@ -625,16 +676,16 @@ def focus_logger(log_file="focus_log.txt"):
try:
with open(log_file, "a") as f:
while True:
windows = client.list_windows()
focused = next((w for w in windows if w["focused"]), None)
clients = client.get_clients()
focused = next((c for c in clients if c["focused"]), None)
if focused and focused["id"] != last_focused:
if focused and focused["window"] != last_focused:
timestamp = datetime.now().isoformat()
entry = f"{timestamp} | {focused['title']} ({focused['class']})\n"
f.write(entry)
f.flush()
print(entry.strip())
last_focused = focused["id"]
last_focused = focused["window"]
time.sleep(1)
except KeyboardInterrupt:
@ -682,24 +733,24 @@ client.disconnect()</code></pre>
<div class="code-block">
<pre><code>from dwn_api_client import DWNClient
def read_active_window():
"""Extract and print text from active window"""
def read_active_client():
"""Extract and print text from active client"""
client = DWNClient()
client.connect()
# Capture active window
# Capture active client
screenshot = client.screenshot("active")
# Extract text
ocr_result = client.ocr(screenshot["data"])
print(f"Text from active window (confidence: {ocr_result['confidence']:.0%}):")
print(f"Text from active client (confidence: {ocr_result['confidence']:.0%}):")
print("-" * 40)
print(ocr_result["text"])
client.disconnect()
read_active_window()</code></pre>
read_active_client()</code></pre>
</div>
<h3 id="browser-ocr">Browser Automation with OCR</h3>

View File

@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@ -98,7 +100,7 @@ port = 8777</code></pre>
</div>
<h2 id="connecting">Connecting</h2>
<p>Connect via WebSocket to <code>ws://localhost:8777</code> (or your configured port).</p>
<p>Connect via WebSocket to <code>ws://localhost:8777/ws</code> (or your configured port). The <code>/ws</code> path is required.</p>
<h3>Testing with wscat</h3>
<div class="code-block">
@ -106,11 +108,11 @@ port = 8777</code></pre>
npm install -g wscat
# Connect to DWN
wscat -c ws://localhost:8777
wscat -c ws://localhost:8777/ws
# Send a command
> {"command": "list_windows"}
< {"status": "ok", "windows": [...]}</code></pre>
> {"command": "get_clients"}
< {"status": "ok", "clients": [...]}</code></pre>
</div>
<h3>Testing with websocat</h3>
@ -119,7 +121,7 @@ wscat -c ws://localhost:8777
cargo install websocat
# Connect and send command
echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
echo '{"command": "get_clients"}' | websocat ws://localhost:8777/ws</code></pre>
</div>
<h2 id="protocol">Protocol</h2>
@ -161,24 +163,24 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
</thead>
<tbody>
<tr>
<td>Windows</td>
<td>list_windows, focus_window, close_window, move_window, resize_window</td>
<td>Clients</td>
<td>get_clients, find_clients, get_focused_client, focus_client, focus_next, focus_prev, focus_master, close_client, kill_client, move_client, resize_client, minimize_client, restore_client, maximize_client, fullscreen_client, float_client, raise_client, lower_client, snap_client</td>
</tr>
<tr>
<td>Workspaces</td>
<td>list_workspaces, switch_workspace, move_to_workspace</td>
<td>get_workspaces, switch_workspace, switch_workspace_next, switch_workspace_prev, move_client_to_workspace</td>
</tr>
<tr>
<td>Layout</td>
<td>get_layout, set_layout, set_master_ratio</td>
<td>set_layout, cycle_layout, set_master_ratio, adjust_master_ratio, set_master_count, adjust_master_count</td>
</tr>
<tr>
<td>Keyboard</td>
<td>key_press, key_release, type_text</td>
<td>key_press, key_release, key_tap, key_type, get_keybindings</td>
</tr>
<tr>
<td>Mouse</td>
<td>mouse_move, mouse_click, mouse_drag</td>
<td>mouse_move, mouse_move_relative, mouse_click, mouse_press, mouse_release, mouse_scroll, get_mouse_position</td>
</tr>
<tr>
<td>Screenshot</td>
@ -190,7 +192,39 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
</tr>
<tr>
<td>System</td>
<td>get_monitors, spawn, notify</td>
<td>get_status, get_screen_info, run_command, show_desktop, get_config, reload_config</td>
</tr>
<tr>
<td>Notifications</td>
<td>notify, close_notification, get_notifications</td>
</tr>
<tr>
<td>System Tray</td>
<td>get_battery_state, get_audio_state, set_audio_volume, toggle_audio_mute, get_wifi_state</td>
</tr>
<tr>
<td>Panels</td>
<td>get_panel_state, show_panel, hide_panel, toggle_panel</td>
</tr>
<tr>
<td>AI</td>
<td>ai_is_available, ai_command, exa_is_available, exa_search</td>
</tr>
<tr>
<td>News</td>
<td>get_news, news_next, news_prev, news_open</td>
</tr>
<tr>
<td>Demo/Tutorial</td>
<td>start_demo, stop_demo, get_demo_state, start_tutorial, stop_tutorial, get_tutorial_state</td>
</tr>
<tr>
<td>Events</td>
<td>subscribe, unsubscribe, list_events</td>
</tr>
<tr>
<td>Fade Effects</td>
<td>get_fade_settings, set_fade_speed, set_fade_intensity</td>
</tr>
</tbody>
</table>
@ -198,32 +232,35 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
<h2 id="quick-start">Quick Start</h2>
<h3>List Windows</h3>
<h3>List Clients</h3>
<div class="code-block">
<pre><code>// Request
{"command": "list_windows"}
{"command": "get_clients"}
// Response
{
"status": "ok",
"windows": [
"clients": [
{
"id": 12345678,
"window": 12345678,
"title": "Firefox",
"class": "firefox",
"workspace": 0,
"x": 0, "y": 32,
"width": 960, "height": 540,
"focused": true,
"floating": false
"floating": false,
"fullscreen": false,
"maximized": false,
"minimized": false
}
]
}</code></pre>
</div>
<h3>Focus a Window</h3>
<h3>Focus a Client</h3>
<div class="code-block">
<pre><code>{"command": "focus_window", "window": 12345678}</code></pre>
<pre><code>{"command": "focus_client", "window": 12345678}</code></pre>
</div>
<h3>Switch Workspace</h3>
@ -233,7 +270,7 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
<h3>Type Text</h3>
<div class="code-block">
<pre><code>{"command": "type_text", "text": "Hello, World!"}</code></pre>
<pre><code>{"command": "key_type", "text": "Hello, World!"}</code></pre>
</div>
<h3>Take Screenshot</h3>
@ -263,11 +300,11 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
client = DWNClient()
client.connect()
# List windows
windows = client.list_windows()
# List clients
clients = client.get_clients()
# Focus a window
client.focus_window(windows[0]['id'])
# Focus a client
client.focus_client(clients[0]['window'])
# Take screenshot
result = client.screenshot('fullscreen')
@ -280,10 +317,10 @@ client.disconnect()</code></pre>
<h3>JavaScript (Browser)</h3>
<div class="code-block">
<pre><code>const ws = new WebSocket('ws://localhost:8777');
<pre><code>const ws = new WebSocket('ws://localhost:8777/ws');
ws.onopen = () => {
ws.send(JSON.stringify({command: 'list_windows'}));
ws.send(JSON.stringify({command: 'get_clients'}));
};
ws.onmessage = (event) => {

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link active">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@ -63,6 +65,7 @@
<div class="toc-title">On this page</div>
<ul class="toc-list">
<li><a href="#overview">Overview</a></li>
<li><a href="#abstraction">Abstraction Layer</a></li>
<li><a href="#modules">Module Structure</a></li>
<li><a href="#event-loop">Event Loop</a></li>
<li><a href="#data-structures">Core Data Structures</a></li>
@ -72,30 +75,62 @@
</div>
<h2 id="overview">Overview</h2>
<p>DWN is written in ANSI C for X11/Xorg. It uses a modular architecture with a global state singleton and event-driven design.</p>
<p>DWN is written in ANSI C for X11/Xorg. It uses a modular architecture with a global state singleton and event-driven design. Version 2.0 introduces a comprehensive abstraction layer enabling backend portability and plugin extensibility.</p>
<h2 id="abstraction">Abstraction Layer (v2.0)</h2>
<p>The new abstraction layer provides backend-agnostic types and a plugin system:</p>
<div class="code-block">
<pre><code>┌─────────────────────────────────────────────────────────┐
DWN Window Manager
DWN Application
├─────────────────────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ main │ │ keys │ │ panel │ │ api │ │
│ │ loop │ │ handler │ │ render │ │ server │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ ┌────┴────────────┴────────────┴────────────┴────┐ │
│ │ DWNState (Global Singleton) │ │
│ └────┬────────────┬────────────┬────────────┬────┘ │
│ │ │ │ │ │
│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │
│ │ client │ │workspace│ │ layout │ │ atoms │ │
│ │ mgmt │ │ mgmt │ │ engine │ │ (EWMH) │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
├─────────────────────────────────────────────────────────┤
│ X11 / Xlib │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Layout │ │ Widget │ │ AI Provider │ │
│ │ Plugin │ │ Plugin │ │ API │ │
│ │ System │ │ System │ │ (future) │ │
│ └──────┬──────┘ └──────┬──────┘ └─────────────────┘ │
│ │ │ │
│ ┌──────┴────────────────┴───────────────────────────┐ │
│ │ Abstract Client Manager │ │
│ │ (wm_client.h/c - bidirectional sync) │ │
│ └───────────────────────┬───────────────────────────┘ │
│ │ │
│ ┌───────────────────────┴───────────────────────────┐ │
│ │ Backend Abstraction Interface │ │
│ │ (backend_interface.h - vtable-based) │ │
│ └───────────────────────┬───────────────────────────┘ │
│ │ │
│ ┌───────────────────────┴───────────────────────────┐ │
│ │ X11 Backend │ Wayland Backend │ Headless │ │
│ │ (complete) │ (future) │ (future) │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘</code></pre>
</div>
<h3>Core Abstractions</h3>
<ul>
<li><strong>wm_types.h</strong> - Abstract handles, geometry, colors, events</li>
<li><strong>wm_string.h/c</strong> - Safe dynamic strings</li>
<li><strong>wm_list.h/c</strong> - Dynamic arrays</li>
<li><strong>wm_hashmap.h/c</strong> - Hash tables</li>
<li><strong>wm_client.h/c</strong> - Abstract client type</li>
</ul>
<h3>Plugin System</h3>
<ul>
<li><strong>Layout Plugins</strong> - Custom window arrangements (tiling, floating, monocle, grid)</li>
<li><strong>Widget Plugins</strong> - Panel components (taskbar, clock, system stats)</li>
<li><strong>Dynamic Loading</strong> - Load plugins from shared libraries</li>
</ul>
<h3>Backend Interface</h3>
<p>The backend interface defines 80+ operations for window management, events, and rendering. Current implementations:</p>
<ul>
<li><strong>X11</strong> - Full implementation with event translation</li>
<li><strong>Wayland</strong> - Planned for future</li>
<li><strong>Headless</strong> - For testing and CI</li>
</ul>
<h2 id="modules">Module Structure</h2>
<p>Each module has a header in <code>include/</code> and implementation in <code>src/</code>.</p>

View File

@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link active">Building from Source</a>
</div>
</nav>

View File

@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@ -309,6 +311,133 @@ ai_timeout = 15000
window_timeout = 5000</code></pre>
</div>
<h3 id="rules">[rules] - Window Rules</h3>
<p>Automatically apply settings to windows based on their class or title. Rules use glob patterns for matching.</p>
<div class="code-block">
<pre><code>[rules]
Firefox = workspace:2, floating:false
class:*terminal* = workspace:1
title:*Calculator* = floating:true, width:400, height:300
class:Telegram = workspace:9, sticky:true
Gimp = floating:true
class:mpv = fullscreen:true</code></pre>
</div>
<h4>Rule Syntax</h4>
<p>Each rule has the format: <code>pattern = property:value, property:value, ...</code></p>
<div class="table-container">
<table>
<thead>
<tr>
<th>Pattern Prefix</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>class:</code></td>
<td>Match by WM_CLASS (default if no prefix)</td>
</tr>
<tr>
<td><code>title:</code></td>
<td>Match by window title</td>
</tr>
<tr>
<td>(no prefix)</td>
<td>Treated as class pattern</td>
</tr>
</tbody>
</table>
</div>
<h4>Available Properties</h4>
<div class="table-container">
<table>
<thead>
<tr>
<th>Property</th>
<th>Values</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>workspace</code></td>
<td>1-9</td>
<td>Move to specific workspace</td>
</tr>
<tr>
<td><code>floating</code></td>
<td>true/false</td>
<td>Set floating mode</td>
</tr>
<tr>
<td><code>sticky</code></td>
<td>true/false</td>
<td>Visible on all workspaces</td>
</tr>
<tr>
<td><code>fullscreen</code></td>
<td>true/false</td>
<td>Start in fullscreen mode</td>
</tr>
<tr>
<td><code>width</code></td>
<td>pixels</td>
<td>Set initial width</td>
</tr>
<tr>
<td><code>height</code></td>
<td>pixels</td>
<td>Set initial height</td>
</tr>
<tr>
<td><code>x</code></td>
<td>pixels</td>
<td>Set initial X position</td>
</tr>
<tr>
<td><code>y</code></td>
<td>pixels</td>
<td>Set initial Y position</td>
</tr>
</tbody>
</table>
</div>
<h4>Pattern Matching</h4>
<ul>
<li><code>*</code> - Matches any sequence of characters</li>
<li><code>?</code> - Matches any single character</li>
<li><code>[abc]</code> - Matches any character in brackets</li>
<li>Matching is case-insensitive</li>
</ul>
<h4>Examples</h4>
<div class="code-block">
<pre><code>[rules]
# Open Firefox on workspace 2
Firefox = workspace:2
# All terminals on workspace 1
class:*terminal* = workspace:1
# Calculator always floating with specific size
title:*Calculator* = floating:true, width:400, height:300
# Picture-in-picture windows
title:Picture-in-Picture = floating:true, sticky:true
# Gimp dialogs floating
class:Gimp = floating:true
# Video players fullscreen
class:mpv = fullscreen:true
class:vlc = fullscreen:true</code></pre>
</div>
<h2>Environment Variables</h2>
<div class="table-container">
<table>

View File

@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@ -63,6 +65,8 @@
<div class="toc-title">On this page</div>
<ul class="toc-list">
<li><a href="#window-management">Window Management</a></li>
<li><a href="#window-rules">Window Rules</a></li>
<li><a href="#window-marks">Window Marks</a></li>
<li><a href="#workspaces">Virtual Workspaces</a></li>
<li><a href="#layouts">Layout System</a></li>
<li><a href="#panels">Panels & System Tray</a></li>
@ -120,6 +124,85 @@
<h3>Alt-Tab Window Cycling</h3>
<p>DWN maintains a Most Recently Used (MRU) stack per workspace. <code>Alt+Tab</code> cycles through windows in order of recent use, not visual order.</p>
<h3>Keyboard Window Control</h3>
<p>Move and resize floating windows without leaving the keyboard:</p>
<ul>
<li><code>Super+Alt+Arrow</code> - Move window by 20 pixels</li>
<li><code>Super+Ctrl+Arrow</code> - Resize window by 20 pixels</li>
</ul>
<h2 id="window-rules">Window Rules</h2>
<p>Automatically apply settings to windows based on their class or title. Rules are matched using glob patterns with case-insensitive matching.</p>
<h3>Matching Criteria</h3>
<ul>
<li><strong>Class Pattern</strong> - Match by WM_CLASS (e.g., <code>Firefox</code>, <code>*terminal*</code>)</li>
<li><strong>Title Pattern</strong> - Match by window title (e.g., <code>*Calculator*</code>)</li>
<li><strong>Combined</strong> - Match both class and title simultaneously</li>
</ul>
<h3>Available Actions</h3>
<div class="table-container">
<table>
<thead>
<tr>
<th>Property</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>workspace</code></td>
<td>Move window to specific workspace (1-9)</td>
</tr>
<tr>
<td><code>floating</code></td>
<td>Set floating state (true/false)</td>
</tr>
<tr>
<td><code>sticky</code></td>
<td>Make window visible on all workspaces</td>
</tr>
<tr>
<td><code>fullscreen</code></td>
<td>Start window in fullscreen mode</td>
</tr>
<tr>
<td><code>width</code>, <code>height</code></td>
<td>Set initial window size</td>
</tr>
<tr>
<td><code>x</code>, <code>y</code></td>
<td>Set initial window position</td>
</tr>
</tbody>
</table>
</div>
<p>See <a href="configuration.html#rules">Configuration</a> for rule syntax.</p>
<h2 id="window-marks">Window Marks</h2>
<p>Mark windows with letters (a-z) for instant navigation, inspired by Vim marks.</p>
<div class="feature-grid">
<div class="feature-card">
<div class="feature-title">Set Mark</div>
<div class="feature-desc">Press <code>Super+M</code> then <code>a-z</code> to mark the focused window.</div>
</div>
<div class="feature-card">
<div class="feature-title">Jump to Mark</div>
<div class="feature-desc">Press <code>Super+'</code> then <code>a-z</code> to instantly focus the marked window.</div>
</div>
<div class="feature-card">
<div class="feature-title">Cross-Workspace</div>
<div class="feature-desc">Marks work across workspaces, automatically switching if needed.</div>
</div>
<div class="feature-card">
<div class="feature-title">26 Slots</div>
<div class="feature-desc">Use any letter a-z for marks. Reassigning overwrites the previous mark.</div>
</div>
</div>
<h2 id="workspaces">Virtual Workspaces</h2>
<p>DWN provides 9 virtual workspaces for organizing your windows.</p>
@ -143,7 +226,7 @@
</div>
<h2 id="layouts">Layout System</h2>
<p>Three layout modes available per workspace:</p>
<p>Six layout modes available per workspace:</p>
<h3>Tiling Layout</h3>
<p>Master-stack tiling with configurable ratios. The first window(s) occupy the master area, others stack on the side.</p>
@ -159,7 +242,22 @@
<h3>Monocle Layout</h3>
<p>All windows fullscreen and stacked. Use Alt-Tab to switch between them. Ideal for focused work.</p>
<p>See <a href="layouts.html">Layouts</a> for detailed documentation.</p>
<h3>Centered Master Layout</h3>
<p>The master window is centered on screen with stack windows divided on left and right sides. Great for wide monitors.</p>
<h3>Columns Layout</h3>
<p>All windows arranged in equal-width vertical columns. Each window takes full height.</p>
<h3>Fibonacci Layout</h3>
<p>Windows arranged in a spiral pattern using recursive splitting. Creates a visually interesting and space-efficient arrangement.</p>
<h3>Grid Layout</h3>
<p>Windows arranged in an automatically-calculated grid pattern. Perfect for comparing multiple documents or monitoring many terminals simultaneously.</p>
<h3>Plugin System</h3>
<p>DWN v2.0 supports custom layout plugins. Create your own window arrangements using the Layout Plugin API. Both built-in and dynamically loaded plugins are supported.</p>
<p>See <a href="layouts.html">Layouts</a> and <a href="plugin-development.html">Plugin Development</a> for detailed documentation.</p>
<h2 id="panels">Panels & System Tray</h2>
@ -277,6 +375,18 @@
<p>See <a href="api-overview.html">API Overview</a> for getting started.</p>
<h2>Abstraction Layer (v2.0)</h2>
<p>DWN v2.0 introduces a comprehensive abstraction layer enabling backend portability and plugin extensibility:</p>
<ul>
<li><strong>Backend Interface</strong> - 80+ operation vtable for X11/Wayland portability</li>
<li><strong>Type Safety</strong> - Strongly typed handles replacing void* casts</li>
<li><strong>Memory Safety</strong> - Safe string and container abstractions</li>
<li><strong>Plugin API</strong> - Extensible architecture for layouts and widgets</li>
<li><strong>100% Compatible</strong> - Existing code works unchanged</li>
</ul>
<p>See <a href="abstraction-layer.html">Abstraction Layer</a> for technical details.</p>
<h2>Protocol Compliance</h2>
<p>DWN implements standard X11 protocols for compatibility:</p>
<ul>

View File

@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@ -56,7 +58,7 @@
<div class="content">
<div class="hero">
<h1>DWN</h1>
<p class="tagline">A modern X11 window manager with AI integration and WebSocket API</p>
<p class="tagline">A modern X11 window manager with AI integration, WebSocket API, and plugin extensibility</p>
<div class="btn-group">
<a href="installation.html" class="btn">Get Started</a>
<a href="api-overview.html" class="btn btn-outline">API Docs</a>
@ -67,7 +69,7 @@
<div class="feature-card">
<div class="feature-icon">🪟</div>
<div class="feature-title">Multiple Layouts</div>
<div class="feature-desc">Tiling, floating, and monocle layouts with per-workspace configuration. Master-stack tiling with adjustable ratios.</div>
<div class="feature-desc">Tiling, floating, monocle, and grid layouts with per-workspace configuration. Plugin API for custom layouts.</div>
</div>
<div class="feature-card">
<div class="feature-icon">🖥️</div>
@ -94,6 +96,16 @@
<div class="feature-title">Notifications</div>
<div class="feature-desc">Built-in D-Bus notification daemon with configurable appearance.</div>
</div>
<div class="feature-card">
<div class="feature-icon">🔧</div>
<div class="feature-title">Plugin System</div>
<div class="feature-desc">Extensible plugin architecture for layouts and widgets. Dynamic loading support.</div>
</div>
<div class="feature-card">
<div class="feature-icon">📐</div>
<div class="feature-title">Abstraction Layer</div>
<div class="feature-desc">Backend-agnostic architecture. Ready for X11, Wayland, and headless backends.</div>
</div>
</div>
<div class="quick-start">

View File

@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>

View File

@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@ -66,15 +68,20 @@
<li><a href="#tiling">Tiling Layout</a></li>
<li><a href="#floating">Floating Layout</a></li>
<li><a href="#monocle">Monocle Layout</a></li>
<li><a href="#centered-master">Centered Master Layout</a></li>
<li><a href="#columns">Columns Layout</a></li>
<li><a href="#fibonacci">Fibonacci Layout</a></li>
<li><a href="#grid">Grid Layout</a></li>
<li><a href="#plugin-system">Plugin System</a></li>
<li><a href="#per-workspace">Per-Workspace Settings</a></li>
<li><a href="#shortcuts">Layout Shortcuts</a></li>
</ul>
</div>
<h2 id="overview">Overview</h2>
<p>DWN provides three layout modes that determine how windows are arranged on screen. Each workspace maintains its own layout settings independently.</p>
<p>DWN provides six layout modes that determine how windows are arranged on screen. Each workspace maintains its own layout settings independently.</p>
<p>Press <code>Super+Space</code> to cycle through layouts: Tiling → Floating → Monocle → Tiling</p>
<p>Press <code>Super+Space</code> to cycle through layouts: Tiling → Floating → Monocle → Centered Master → Columns → Fibonacci → Tiling</p>
<div class="alert alert-info">
<strong>Tip:</strong> The current layout is shown in the panel with a symbol: <code>[]=</code> for tiling, <code>&gt;&lt;&gt;</code> for floating, <code>[M]</code> for monocle.
@ -182,6 +189,141 @@
<strong>Note:</strong> Windows maintain decorations in monocle mode unless fullscreen (<code>Alt+F11</code>).
</div>
<h2 id="centered-master">Centered Master Layout</h2>
<p>The master window is centered on screen with stack windows divided on left and right sides.</p>
<h3>How It Works</h3>
<div class="code-block">
<pre><code>+--------+------------------+--------+
| Stack | Master | Stack |
| 1 | Window | 3 |
+--------+ +--------+
| Stack | | Stack |
| 2 | | 4 |
+--------+------------------+--------+</code></pre>
</div>
<ul>
<li>The <strong>master window</strong> occupies the center of the screen</li>
<li>Stack windows are divided between left and right sides</li>
<li>Odd-numbered stack windows go left, even go right</li>
<li>Excellent for wide monitors and ultrawide displays</li>
</ul>
<h3>Use Cases</h3>
<ul>
<li>Keeping primary work centered while referencing side content</li>
<li>Video editing with timeline and tools on sides</li>
<li>Development with editor centered and documentation on sides</li>
</ul>
<h2 id="columns">Columns Layout</h2>
<p>All windows arranged in equal-width vertical columns spanning the full height.</p>
<h3>How It Works</h3>
<div class="code-block">
<pre><code>+----------+----------+----------+----------+
| | | | |
| Window | Window | Window | Window |
| 1 | 2 | 3 | 4 |
| | | | |
| | | | |
+----------+----------+----------+----------+</code></pre>
</div>
<ul>
<li>Each window gets an equal-width column</li>
<li>Windows span the full usable height</li>
<li>Simple and predictable arrangement</li>
</ul>
<h3>Use Cases</h3>
<ul>
<li>Comparing multiple files side-by-side</li>
<li>Monitoring multiple log files</li>
<li>Multi-column text editing</li>
<li>Concurrent terminal sessions</li>
</ul>
<h2 id="fibonacci">Fibonacci Layout</h2>
<p>Windows arranged in a spiral pattern using recursive splitting, inspired by the Fibonacci sequence.</p>
<h3>How It Works</h3>
<div class="code-block">
<pre><code>+------------------+----------+
| | |
| Window 1 | Window 2 |
| +----+-----+
| | W3 | |
+------------------+----+ W4 |
| Window 5 | |
+-----------------------+-----+</code></pre>
</div>
<ul>
<li>First window takes half the screen</li>
<li>Each subsequent window takes half the remaining space</li>
<li>Alternates between horizontal and vertical splits</li>
<li>Creates a visually interesting spiral pattern</li>
</ul>
<h3>Use Cases</h3>
<ul>
<li>Hierarchical window importance (larger = more important)</li>
<li>Primary workspace with progressively smaller utilities</li>
<li>Creative workflows with main canvas and tool windows</li>
</ul>
<h2 id="grid">Grid Layout</h2>
<p>Windows are arranged in a grid pattern with automatic row/column calculation.</p>
<h3>How It Works</h3>
<div class="code-block">
<pre><code>+----------+----------+----------+
| Window | Window | Window |
| 1 | 2 | 3 |
+----------+----------+----------+
| Window | Window |
| 4 | 5 |
+----------+----------+</code></pre>
</div>
<ul>
<li>Automatically calculates optimal rows and columns</li>
<li>Uses square root for balanced grid</li>
<li>All windows have equal size</li>
<li>Great for viewing multiple documents simultaneously</li>
</ul>
<h3>Use Cases</h3>
<ul>
<li>Comparing multiple documents or files</li>
<li>Monitoring multiple terminals</li>
<li>Dashboard-style workflows</li>
<li>Multi-way video calls</li>
</ul>
<h2 id="plugin-system">Plugin System (v2.0)</h2>
<p>DWN v2.0 introduces a layout plugin system that allows custom layout algorithms to be loaded dynamically or built-in.</p>
<h3>Built-in Layouts</h3>
<ul>
<li><strong>tiling</strong> - Master-stack tiling (default)</li>
<li><strong>floating</strong> - Traditional floating windows</li>
<li><strong>monocle</strong> - Single maximized window</li>
<li><strong>centered-master</strong> - Master centered with stacks on sides</li>
<li><strong>columns</strong> - Equal-width vertical columns</li>
<li><strong>fibonacci</strong> - Spiral recursive splitting</li>
<li><strong>grid</strong> - Grid arrangement</li>
</ul>
<h3>Custom Layouts</h3>
<p>Developers can create custom layout plugins using the Layout Plugin API. See <a href="plugin-development.html">Plugin Development</a> for details.</p>
<div class="alert alert-info">
<strong>Plugin API:</strong> Layouts implement the <code>LayoutPluginInterface</code> vtable with an <code>arrange()</code> method that calculates window geometries.
</div>
<h2 id="per-workspace">Per-Workspace Settings</h2>
<p>Each workspace maintains independent layout settings:</p>
@ -198,7 +340,7 @@
<tr>
<td>Layout Mode</td>
<td>Per-workspace</td>
<td>Tiling, floating, or monocle</td>
<td>Tiling, floating, monocle, centered-master, columns, or fibonacci</td>
</tr>
<tr>
<td>Master Ratio</td>
@ -224,7 +366,9 @@
<li>Workspace 1: Tiling layout for coding (editor + terminal)</li>
<li>Workspace 2: Floating layout for design work</li>
<li>Workspace 3: Monocle layout for focused writing</li>
<li>Workspace 4: Tiling with master count 2 for comparison</li>
<li>Workspace 4: Centered-master for wide monitor development</li>
<li>Workspace 5: Columns layout for log monitoring</li>
<li>Workspace 6: Fibonacci for hierarchical work</li>
</ul>
<h2 id="shortcuts">Layout Shortcuts</h2>
@ -278,7 +422,9 @@
<div class="code-block">
<pre><code>[layout]
default = tiling # Default layout for new workspaces
# Default layout for new workspaces
# Options: tiling, floating, monocle, centered-master, columns, fibonacci
default = tiling
master_ratio = 0.55 # Default master area ratio (0.1-0.9)
master_count = 1 # Default master window count (1-10)

View File

@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Plugin Development - DWN Documentation</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<button class="mobile-menu-btn">Menu</button>
<div class="layout">
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
<input type="text" class="search-input" placeholder="Search docs...">
</div>
<nav class="sidebar-nav">
<div class="nav-section">
<div class="nav-section-title">Getting Started</div>
<a href="index.html" class="nav-link">Introduction</a>
<a href="installation.html" class="nav-link">Installation</a>
<a href="quickstart.html" class="nav-link">Quick Start</a>
</div>
<div class="nav-section">
<div class="nav-section-title">User Guide</div>
<a href="features.html" class="nav-link">Features</a>
<a href="shortcuts.html" class="nav-link">Keyboard Shortcuts</a>
<a href="configuration.html" class="nav-link">Configuration</a>
<a href="layouts.html" class="nav-link">Layouts</a>
<a href="ai-features.html" class="nav-link">AI Integration</a>
</div>
<div class="nav-section">
<div class="nav-section-title">API Reference</div>
<a href="api-overview.html" class="nav-link">API Overview</a>
<a href="api-reference.html" class="nav-link">API Reference</a>
<a href="api-examples.html" class="nav-link">API Examples</a>
</div>
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link active">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
</aside>
<main class="main-content">
<div class="content">
<div class="page-header">
<h1>Plugin Development</h1>
<p class="lead">Create custom layouts and widgets for DWN</p>
</div>
<h2>Overview</h2>
<p>DWN v2.0 introduces a plugin system for extending functionality. Two types of plugins are supported:</p>
<ul>
<li><strong>Layout Plugins</strong> - Custom window arrangement algorithms</li>
<li><strong>Widget Plugins</strong> - Panel components like taskbar, clock, system monitors</li>
</ul>
<h2>Layout Plugins</h2>
<p>Layout plugins implement the LayoutPluginInterface vtable. See the abstraction-layer.html documentation for details.</p>
<h2>Widget Plugins</h2>
<p>Widget plugins create panel components with custom rendering and event handling.</p>
<footer>
<p>DWN Window Manager - retoor &lt;retoor@molodetz.nl&gt;</p>
</footer>
</div>
</main>
</div>
<script src="js/main.js"></script>
</body>
</html>

View File

@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>

View File

@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@ -64,6 +66,8 @@
<ul class="toc-list">
<li><a href="#launchers">Application Launchers</a></li>
<li><a href="#windows">Window Management</a></li>
<li><a href="#keyboard-control">Keyboard Window Control</a></li>
<li><a href="#marks">Window Marks</a></li>
<li><a href="#workspaces">Workspace Navigation</a></li>
<li><a href="#layouts">Layout Control</a></li>
<li><a href="#snapping">Window Snapping</a></li>
@ -148,6 +152,86 @@
</table>
</div>
<h2 id="keyboard-control">Keyboard Window Control</h2>
<p>Move and resize floating windows without using the mouse.</p>
<div class="table-container">
<table class="shortcut-table">
<thead>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Super+Alt+Left</code></td>
<td>Move window left by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Alt+Right</code></td>
<td>Move window right by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Alt+Up</code></td>
<td>Move window up by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Alt+Down</code></td>
<td>Move window down by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Ctrl+Left</code></td>
<td>Shrink window width by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Ctrl+Right</code></td>
<td>Grow window width by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Ctrl+Up</code></td>
<td>Shrink window height by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Ctrl+Down</code></td>
<td>Grow window height by 20 pixels</td>
</tr>
</tbody>
</table>
</div>
<div class="alert alert-info">
<strong>Note:</strong> Window must be floating for keyboard move/resize to work. Toggle floating with <code>Super+F9</code>.
</div>
<h2 id="marks">Window Marks</h2>
<p>Mark windows with letters (a-z) for instant switching, similar to Vim marks.</p>
<div class="table-container">
<table class="shortcut-table">
<thead>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Super+M</code> then <code>a-z</code></td>
<td>Mark current window with letter</td>
</tr>
<tr>
<td><code>Super+'</code> then <code>a-z</code></td>
<td>Jump to window marked with letter</td>
</tr>
</tbody>
</table>
</div>
<div class="alert alert-info">
<strong>Tip:</strong> Use marks to quickly switch between frequently used windows. For example, mark your editor with <code>e</code> and terminal with <code>t</code>.
</div>
<h2 id="workspaces">Workspace Navigation</h2>
<div class="table-container">
<table class="shortcut-table">
@ -190,7 +274,7 @@
<tbody>
<tr>
<td><code>Super+Space</code></td>
<td>Cycle layout mode (tiling → floating → monocle)</td>
<td>Cycle layout mode (tiling → floating → monocle → centered-master → columns → fibonacci)</td>
</tr>
<tr>
<td><code>Super+H</code></td>

View File

@ -1,434 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="AI integration in DWN window manager - command palette, semantic search, and context analysis.">
<title>AI Features - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html" class="active">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content">
<h1>AI Integration</h1>
<p class="subtitle">
Control your desktop with natural language and get intelligent assistance.
</p>
</div>
</section>
<section class="section">
<div class="container">
<div class="alert alert-info" style="margin-bottom: 2rem;">
<strong class="alert-title">Optional Features</strong>
<p style="margin: 0;">AI features are completely optional and require external API keys.
DWN works perfectly without them.</p>
</div>
<h2>Overview</h2>
<p>
DWN integrates with two AI services to provide intelligent desktop assistance:
</p>
<ul style="margin-bottom: 2rem;">
<li><strong>OpenRouter API</strong> - Powers the AI command palette and context analysis</li>
<li><strong>Exa API</strong> - Provides semantic web search capabilities</li>
</ul>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">&#129302;</div>
<h3>AI Command Palette</h3>
<p>Type natural language commands to control your desktop.
Launch apps, query system info, and more.</p>
<p style="margin-top: 1rem;"><kbd>Super</kbd> + <kbd>A</kbd></p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128065;</div>
<h3>Context Analysis</h3>
<p>AI analyzes your current workspace to understand your task
and provide relevant suggestions.</p>
<p style="margin-top: 1rem;"><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd></p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128269;</div>
<h3>Semantic Search</h3>
<p>Search the web using meaning, not just keywords.
Find relevant content instantly.</p>
<p style="margin-top: 1rem;"><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>E</kbd></p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128221;</div>
<h3>Auto-Organization</h3>
<p>Context-aware window placement and workspace optimization
based on your current workflow.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128276;</div>
<h3>Smart Notifications</h3>
<p>Intelligent filtering and prioritization of system notifications
using application context.</p>
</div>
</div>
<h2 id="openrouter" style="margin-top: 4rem;">Setting Up OpenRouter</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
OpenRouter provides access to multiple AI models through a single API.
You can use free models or paid ones depending on your needs.
</p>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Get an API Key</h4>
<p>Visit <a href="https://openrouter.ai/keys" target="_blank">https://openrouter.ai/keys</a>
and create a free account to get your API key.</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Set the Environment Variable</h4>
<p>Add to your shell profile (~/.bashrc, ~/.zshrc, etc.):</p>
<div class="code-header">
<span>~/.bashrc</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>export OPENROUTER_API_KEY="sk-or-v1-your-key-here"</code></pre>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>Choose a Model (Optional)</h4>
<p>Configure the AI model in your DWN config file:</p>
<div class="code-header">
<span>~/.config/dwn/config</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[ai]
model = google/gemini-2.0-flash-exp:free</code></pre>
<p style="margin-top: 0.5rem; font-size: 0.875rem; color: var(--text-muted);">
Browse available models at <a href="https://openrouter.ai/models" target="_blank">openrouter.ai/models</a>
</p>
</div>
</div>
</div>
<div class="card" style="margin-top: 2rem;">
<h3>Recommended Free Models</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Model ID</th>
<th>Provider</th>
<th>Best For</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>google/gemini-2.0-flash-exp:free</code></td>
<td>Google</td>
<td>Fast responses, good general use</td>
</tr>
<tr>
<td><code>meta-llama/llama-3.2-3b-instruct:free</code></td>
<td>Meta</td>
<td>Quick commands, lightweight</td>
</tr>
<tr>
<td><code>mistralai/mistral-7b-instruct:free</code></td>
<td>Mistral</td>
<td>Balanced performance</td>
</tr>
</tbody>
</table>
</div>
</div>
<h2 id="command-palette" style="margin-top: 4rem;">AI Command Palette</h2>
<p>
Press <kbd>Super</kbd> + <kbd>A</kbd> to open the command palette.
Type natural language commands and press Enter.
</p>
<h3>Supported Commands</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Category</th>
<th>Examples</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Launch Applications</strong></td>
<td>
"open firefox"<br>
"run terminal"<br>
"launch file manager"<br>
"start chrome"
</td>
</tr>
<tr>
<td><strong>System Queries</strong></td>
<td>
"what time is it"<br>
"how much memory is free"<br>
"what's my IP address"<br>
"show disk usage"
</td>
</tr>
<tr>
<td><strong>General Questions</strong></td>
<td>
"how do I resize the master area"<br>
"what's the shortcut for fullscreen"<br>
"explain tiling mode"
</td>
</tr>
</tbody>
</table>
</div>
<div class="alert alert-success" style="margin-top: 1.5rem;">
<strong class="alert-title">Pro Tip</strong>
<p style="margin: 0;">The AI understands context. You can say "open browser" instead of
remembering the exact application name - it will figure out what you mean.</p>
</div>
<h2 id="context" style="margin-top: 4rem;">Context Analysis</h2>
<p>
Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd> to see AI-powered analysis of your current workspace.
</p>
<div class="card">
<h3>What It Shows</h3>
<ul style="padding-left: 1.25rem;">
<li><strong>Task Type</strong> - Coding, browsing, communication, etc.</li>
<li><strong>Focused Window</strong> - Currently active application</li>
<li><strong>Suggestions</strong> - Relevant shortcuts or actions based on context</li>
<li><strong>Workspace Summary</strong> - Overview of open applications</li>
</ul>
</div>
<h2 id="exa" style="margin-top: 4rem;">Setting Up Exa Search</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Exa provides semantic search - finding content based on meaning rather than exact keywords.
</p>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Get an Exa API Key</h4>
<p>Visit <a href="https://dashboard.exa.ai/api-keys" target="_blank">https://dashboard.exa.ai/api-keys</a>
and create an account to get your API key.</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Set the Environment Variable</h4>
<div class="code-header">
<span>~/.bashrc</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>export EXA_API_KEY="your-exa-key-here"</code></pre>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>Start Searching</h4>
<p>Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>E</kbd> and type your query.</p>
</div>
</div>
</div>
<h2 id="search" style="margin-top: 4rem;">Using Semantic Search</h2>
<p>
Unlike traditional search, Exa understands the meaning of your query.
</p>
<div class="comparison" style="grid-template-columns: repeat(2, 1fr);">
<div class="comparison-card">
<h3>Traditional Search</h3>
<p style="color: var(--text-muted);">Keyword matching</p>
<ul>
<li>Exact keyword matches</li>
<li>Boolean operators needed</li>
<li>Miss relevant results</li>
</ul>
<p style="margin-top: 1rem; font-style: italic; color: var(--text-muted);">
"nginx reverse proxy setup tutorial"
</p>
</div>
<div class="comparison-card featured">
<h3>Semantic Search</h3>
<p style="color: var(--text-muted);">Meaning-based</p>
<ul>
<li>Understands intent</li>
<li>Natural language</li>
<li>Finds related content</li>
</ul>
<p style="margin-top: 1rem; font-style: italic; color: var(--text-muted);">
"how to configure nginx as a reverse proxy"
</p>
</div>
</div>
<h3 style="margin-top: 2rem;">Search Tips</h3>
<ul>
<li>Use natural, conversational queries</li>
<li>Be specific about what you're looking for</li>
<li>Results appear in a dmenu/rofi list - select to open in browser</li>
<li>Search includes articles, documentation, tutorials, and more</li>
</ul>
<h2 id="privacy" style="margin-top: 4rem;">Privacy Considerations</h2>
<div class="alert alert-warning">
<strong class="alert-title">Data Sent to External Services</strong>
<p style="margin: 0;">When using AI features, the following data is sent to external APIs:</p>
</div>
<div class="table-wrapper" style="margin-top: 1rem;">
<table>
<thead>
<tr>
<th>Feature</th>
<th>Data Sent</th>
<th>Service</th>
</tr>
</thead>
<tbody>
<tr>
<td>Command Palette</td>
<td>Your typed command</td>
<td>OpenRouter (then to model provider)</td>
</tr>
<tr>
<td>Context Analysis</td>
<td>Window titles, app names</td>
<td>OpenRouter (then to model provider)</td>
</tr>
<tr>
<td>Semantic Search</td>
<td>Your search query</td>
<td>Exa</td>
</tr>
</tbody>
</table>
</div>
<p style="margin-top: 1rem; color: var(--text-muted);">
If you're concerned about privacy, you can:
</p>
<ul style="color: var(--text-muted);">
<li>Not configure API keys (AI features simply won't work)</li>
<li>Use OpenRouter with privacy-focused models</li>
<li>Only use AI features when needed</li>
</ul>
<h2 id="troubleshooting" style="margin-top: 4rem;">Troubleshooting</h2>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
AI commands don't work - "API key not configured"
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>Make sure your API key is properly set:</p>
<ol style="padding-left: 1.25rem; margin-top: 0.5rem;">
<li>Check the environment variable: <code>echo $OPENROUTER_API_KEY</code></li>
<li>Ensure the variable is exported in your shell profile</li>
<li>Log out and back in, or source your profile: <code>source ~/.bashrc</code></li>
<li>Restart DWN</li>
</ol>
</div>
</div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
Slow responses from AI
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>Try using a faster model. Free models can sometimes be slow due to rate limiting.
Gemini Flash is usually the fastest free option.</p>
</div>
</div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
Exa search returns no results
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>Check your Exa API key and ensure you have remaining credits.
Visit the <a href="https://dashboard.exa.ai">Exa dashboard</a> to check your usage.</p>
</div>
</div>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View File

@ -1,550 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Technical architecture documentation for DWN window manager - codebase structure, modules, and internals.">
<title>Architecture - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html" class="active">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content">
<h1>Architecture</h1>
<p class="subtitle">
Technical documentation for developers and contributors.
</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>Overview</h2>
<p>
DWN is written in ANSI C (C99) and follows a modular single-responsibility architecture.
A global <code>DWNState</code> singleton manages all state, and the main event loop
dispatches X11 events to specialized modules.
</p>
<div class="card" style="margin: 2rem 0;">
<h3>Project Statistics</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; text-align: center;">
<div>
<div style="font-size: 2rem; font-weight: 700; color: var(--primary);">~10K</div>
<div style="color: var(--text-muted);">Lines of Code</div>
</div>
<div>
<div style="font-size: 2rem; font-weight: 700; color: var(--primary);">13</div>
<div style="color: var(--text-muted);">Core Modules</div>
</div>
<div>
<div style="font-size: 2rem; font-weight: 700; color: var(--primary);">C99</div>
<div style="color: var(--text-muted);">Standard</div>
</div>
<div>
<div style="font-size: 2rem; font-weight: 700; color: var(--primary);">MIT</div>
<div style="color: var(--text-muted);">License</div>
</div>
</div>
</div>
<h2 id="structure" style="margin-top: 3rem;">Directory Structure</h2>
<div class="code-header">
<span>Project Layout</span>
</div>
<pre><code>dwn/
├── src/ # Source files (.c)
│ ├── main.c # Entry point, event loop
│ ├── client.c # Window management
│ ├── workspace.c # Virtual desktops
│ ├── layout.c # Tiling algorithms
│ ├── decorations.c # Title bars, borders
│ ├── panel.c # Top/bottom panels
│ ├── systray.c # System tray widgets
│ ├── notifications.c # D-Bus notifications
│ ├── atoms.c # X11 atoms (EWMH/ICCCM)
│ ├── keys.c # Keyboard handling
│ ├── config.c # INI parser
│ ├── ai.c # AI integration
│ └── util.c # Utilities
├── include/ # Header files (.h)
├── site/ # Documentation website
├── Makefile # Build system
├── CLAUDE.md # AI assistant context
└── README.md # Project readme</code></pre>
<h2 id="modules" style="margin-top: 3rem;">Core Modules</h2>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Module</th>
<th>File</th>
<th>Responsibility</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Main</strong></td>
<td><code>main.c</code></td>
<td>X11 initialization, event loop, signal handling, module orchestration</td>
</tr>
<tr>
<td><strong>Client</strong></td>
<td><code>client.c</code></td>
<td>Window lifecycle, focus management, frame creation, client list</td>
</tr>
<tr>
<td><strong>Workspace</strong></td>
<td><code>workspace.c</code></td>
<td>9 virtual desktops, per-workspace state, window assignment</td>
</tr>
<tr>
<td><strong>Layout</strong></td>
<td><code>layout.c</code></td>
<td>Tiling (master+stack), floating, monocle layout algorithms</td>
</tr>
<tr>
<td><strong>Decorations</strong></td>
<td><code>decorations.c</code></td>
<td>Window title bars, borders, decoration rendering</td>
</tr>
<tr>
<td><strong>Panel</strong></td>
<td><code>panel.c</code></td>
<td>Top panel (taskbar, workspace indicators), bottom panel (clock)</td>
</tr>
<tr>
<td><strong>Systray</strong></td>
<td><code>systray.c</code></td>
<td>System tray with WiFi/audio/battery indicators, dropdowns</td>
</tr>
<tr>
<td><strong>Notifications</strong></td>
<td><code>notifications.c</code></td>
<td>D-Bus notification daemon (org.freedesktop.Notifications)</td>
</tr>
<tr>
<td><strong>Atoms</strong></td>
<td><code>atoms.c</code></td>
<td>X11 EWMH/ICCCM atom management and property handling</td>
</tr>
<tr>
<td><strong>Keys</strong></td>
<td><code>keys.c</code></td>
<td>Keyboard shortcut capture, keybinding registry, callbacks</td>
</tr>
<tr>
<td><strong>Config</strong></td>
<td><code>config.c</code></td>
<td>INI-style config loading and parsing</td>
</tr>
<tr>
<td><strong>AI</strong></td>
<td><code>ai.c</code></td>
<td>Async OpenRouter API integration, Exa semantic search</td>
</tr>
<tr>
<td><strong>News</strong></td>
<td><code>news.c</code></td>
<td>News ticker with API integration, scrolling animation, article navigation</td>
</tr>
<tr>
<td><strong>Autostart</strong></td>
<td><code>autostart.c</code></td>
<td>XDG Autostart support, .desktop file parsing, concurrent app launch</td>
</tr>
<tr>
<td><strong>Services</strong></td>
<td><code>services.c</code></td>
<td>Background service management, process lifecycle control</td>
</tr>
<tr>
<td><strong>Demo</strong></td>
<td><code>demo.c</code></td>
<td>Automated feature demonstration mode, tutorial system</td>
</tr>
<tr>
<td><strong>API</strong></td>
<td><code>api.c</code></td>
<td>WebSocket server, JSON-RPC command handler, remote control interface</td>
</tr>
<tr>
<td><strong>Util</strong></td>
<td><code>util.c</code></td>
<td>Logging, memory allocation, string utilities, file helpers</td>
</tr>
</tbody>
</table>
</div>
<h2 id="dependencies" style="margin-top: 3rem;">Module Dependencies</h2>
<div class="card">
<pre style="margin: 0; background: transparent; border: none; padding: 0;"><code>main.c (orchestrator)
├── client.c
│ ├── decorations.c
│ ├── config.c
│ └── atoms.c
├── workspace.c
│ ├── client.c
│ ├── layout.c
│ └── atoms.c
├── panel.c
│ ├── client.c
│ └── config.c
├── systray.c
│ └── config.c
├── notifications.c (independent)
├── ai.c (independent)
├── autostart.c
│ └── config.c
└── keys.c
└── config.c</code></pre>
</div>
<h2 id="state" style="margin-top: 3rem;">Global State (DWNState)</h2>
<p>
All window manager state is centralized in a single <code>DWNState</code> structure.
This simplifies state management and makes the codebase easier to understand.
</p>
<div class="code-header">
<span>include/dwn.h (simplified)</span>
</div>
<pre><code>typedef struct {
Display *display; // X11 connection
Window root; // Root window
int screen; // Default screen
Client *clients[MAX_CLIENTS]; // All managed windows
int client_count;
Workspace workspaces[MAX_WORKSPACES]; // Virtual desktops
int current_workspace;
Panel top_panel;
Panel bottom_panel;
Config config; // User configuration
KeyBinding keys[MAX_KEYBINDINGS];
// EWMH atoms
Atom atoms[ATOM_COUNT];
} DWNState;
extern DWNState *dwn; // Global singleton</code></pre>
<h2 id="events" style="margin-top: 3rem;">Event Loop</h2>
<p>
DWN uses a traditional X11 event loop with XNextEvent. Events are dispatched
to appropriate handlers based on type.
</p>
<div class="code-header">
<span>main.c (simplified)</span>
</div>
<pre><code>int main(int argc, char *argv[]) {
dwn_init(); // Initialize X11, atoms, config
setup_keybindings(); // Register keyboard shortcuts
setup_panels(); // Create panel windows
XEvent event;
while (running) {
XNextEvent(dwn->display, &event);
switch (event.type) {
case MapRequest:
handle_map_request(&event.xmaprequest);
break;
case UnmapNotify:
handle_unmap_notify(&event.xunmap);
break;
case KeyPress:
handle_key_press(&event.xkey);
break;
case ButtonPress:
handle_button_press(&event.xbutton);
break;
case ConfigureRequest:
handle_configure_request(&event.xconfigurerequest);
break;
// ... more event types
}
}
dwn_cleanup();
return 0;
}</code></pre>
<h2 id="constants" style="margin-top: 3rem;">Key Constants</h2>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Constant</th>
<th>Value</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>MAX_CLIENTS</code></td>
<td>256</td>
<td>Maximum managed windows</td>
</tr>
<tr>
<td><code>MAX_WORKSPACES</code></td>
<td>9</td>
<td>Number of virtual desktops</td>
</tr>
<tr>
<td><code>MAX_MONITORS</code></td>
<td>8</td>
<td>Multi-monitor support limit</td>
</tr>
<tr>
<td><code>MAX_NOTIFICATIONS</code></td>
<td>32</td>
<td>Concurrent notifications</td>
</tr>
<tr>
<td><code>MAX_KEYBINDINGS</code></td>
<td>64</td>
<td>Registered keyboard shortcuts</td>
</tr>
</tbody>
</table>
</div>
<h2 id="conventions" style="margin-top: 3rem;">Coding Conventions</h2>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card">
<h3>Naming</h3>
<ul style="padding-left: 1.25rem;">
<li><code>snake_case</code> for functions and variables</li>
<li><code>CamelCase</code> for types and structs</li>
<li>Module prefix for functions (e.g., <code>client_focus()</code>)</li>
<li>Constants in <code>UPPER_SNAKE_CASE</code></li>
</ul>
</div>
<div class="card">
<h3>Style</h3>
<ul style="padding-left: 1.25rem;">
<li>4-space indentation</li>
<li>K&R brace style</li>
<li>Max 100 characters per line</li>
<li>clang-format for consistency</li>
</ul>
</div>
</div>
<div class="code-header" style="margin-top: 1.5rem;">
<span>Example Function</span>
</div>
<pre><code>void client_focus(Client *c) {
if (!c) return;
// Unfocus previous
if (dwn->focused && dwn->focused != c) {
client_unfocus(dwn->focused);
}
dwn->focused = c;
XSetInputFocus(dwn->display, c->window, RevertToPointerRoot, CurrentTime);
XRaiseWindow(dwn->display, c->frame);
decorations_update(c);
atoms_set_active_window(c->window);
}</code></pre>
<h2 id="protocols" style="margin-top: 3rem;">EWMH/ICCCM Support</h2>
<p>
DWN implements key Extended Window Manager Hints and ICCCM protocols
for compatibility with modern applications.
</p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card">
<h3>EWMH Atoms</h3>
<ul style="padding-left: 1.25rem; font-family: var(--font-mono); font-size: 0.875rem;">
<li>_NET_SUPPORTED</li>
<li>_NET_CLIENT_LIST</li>
<li>_NET_CLIENT_LIST_STACKING</li>
<li>_NET_ACTIVE_WINDOW</li>
<li>_NET_CURRENT_DESKTOP</li>
<li>_NET_NUMBER_OF_DESKTOPS</li>
<li>_NET_WM_STATE</li>
<li>_NET_WM_STATE_FULLSCREEN</li>
<li>_NET_WM_STATE_MAXIMIZED_*</li>
<li>_NET_WM_WINDOW_TYPE</li>
<li>_NET_WM_NAME</li>
</ul>
</div>
<div class="card">
<h3>ICCCM Support</h3>
<ul style="padding-left: 1.25rem; font-family: var(--font-mono); font-size: 0.875rem;">
<li>WM_STATE</li>
<li>WM_PROTOCOLS</li>
<li>WM_DELETE_WINDOW</li>
<li>WM_TAKE_FOCUS</li>
<li>WM_NORMAL_HINTS</li>
<li>WM_SIZE_HINTS</li>
<li>WM_CLASS</li>
<li>WM_NAME</li>
<li>WM_TRANSIENT_FOR</li>
</ul>
</div>
</div>
<h2 id="build" style="margin-top: 3rem;">Build System</h2>
<p>
DWN uses a simple Makefile-based build system with pkg-config for dependency detection.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Target</th>
<th>Command</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Build (release)</td>
<td><code>make</code></td>
<td>Optimized build with -O2</td>
</tr>
<tr>
<td>Build (debug)</td>
<td><code>make debug</code></td>
<td>Debug symbols, -DDEBUG flag</td>
</tr>
<tr>
<td>Install</td>
<td><code>sudo make install</code></td>
<td>Install to PREFIX (/usr/local)</td>
</tr>
<tr>
<td>Clean</td>
<td><code>make clean</code></td>
<td>Remove build artifacts</td>
</tr>
<tr>
<td>Format</td>
<td><code>make format</code></td>
<td>Run clang-format on sources</td>
</tr>
<tr>
<td>Check</td>
<td><code>make check</code></td>
<td>Run cppcheck static analysis</td>
</tr>
<tr>
<td>Test</td>
<td><code>make run</code></td>
<td>Run in Xephyr nested server</td>
</tr>
<tr>
<td>Dependencies</td>
<td><code>make deps</code></td>
<td>Auto-install for your distro</td>
</tr>
</tbody>
</table>
</div>
<h2 id="contributing" style="margin-top: 3rem;">Contributing</h2>
<p>
Contributions are welcome! Here's how to get started:
</p>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Fork & Clone</h4>
<p>Fork the repository and clone your fork locally.</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Create a Branch</h4>
<p>Create a feature branch: <code>git checkout -b feature/my-feature</code></p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>Make Changes</h4>
<p>Follow coding conventions. Run <code>make format</code> and <code>make check</code>.</p>
</div>
</div>
<div class="step">
<div class="step-number">4</div>
<div class="step-content">
<h4>Test</h4>
<p>Test your changes with <code>make run</code> in a nested X server.</p>
</div>
</div>
<div class="step">
<div class="step-number">5</div>
<div class="step-content">
<h4>Submit PR</h4>
<p>Push your branch and open a pull request with a clear description.</p>
</div>
</div>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View File

@ -1,578 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Complete configuration guide for DWN window manager - customize colors, behavior, and more.">
<title>Configuration - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html" class="active">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content">
<h1>Configuration Guide</h1>
<p class="subtitle">
Customize every aspect of DWN to match your workflow and style.
</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>Configuration File</h2>
<p>
DWN reads its configuration from <code>~/.config/dwn/config</code> using an INI-style format.
Changes take effect on restart (or you can reload in a future version).
</p>
<div class="alert alert-info">
<strong class="alert-title">First Run</strong>
<p style="margin: 0;">DWN creates a default configuration file on first run if one doesn't exist.
You can also copy the example config from the source repository.</p>
</div>
<h2 id="general" style="margin-top: 3rem;">[general] - Core Settings</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Basic behavior settings for applications and focus handling.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>terminal</code></td>
<td><code>xfce4-terminal</code></td>
<td>Terminal emulator launched with Ctrl+Alt+T</td>
</tr>
<tr>
<td><code>launcher</code></td>
<td><code>dmenu_run</code></td>
<td>Application launcher for Alt+F2</td>
</tr>
<tr>
<td><code>file_manager</code></td>
<td><code>thunar</code></td>
<td>File manager for Super+E</td>
</tr>
<tr>
<td><code>focus_mode</code></td>
<td><code>click</code></td>
<td><code>click</code> or <code>follow</code> (sloppy focus)</td>
</tr>
<tr>
<td><code>focus_follow_delay</code></td>
<td><code>100</code></td>
<td>Delay in ms before focus switches in follow mode (0-1000)</td>
</tr>
<tr>
<td><code>decorations</code></td>
<td><code>true</code></td>
<td>Show window title bars and borders</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[general]
terminal = alacritty
launcher = rofi -show run
file_manager = nautilus
focus_mode = follow
focus_follow_delay = 100
decorations = true</code></pre>
<h2 id="appearance" style="margin-top: 3rem;">[appearance] - Visual Settings</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Control the visual appearance of windows, panels, and gaps.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Default</th>
<th>Range</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>border_width</code></td>
<td><code>0</code></td>
<td>0-50</td>
<td>Window border width in pixels (0 for seamless design)</td>
</tr>
<tr>
<td><code>title_height</code></td>
<td><code>28</code></td>
<td>0-100</td>
<td>Title bar height in pixels</td>
</tr>
<tr>
<td><code>panel_height</code></td>
<td><code>32</code></td>
<td>0-100</td>
<td>Top/bottom panel height</td>
</tr>
<tr>
<td><code>gap</code></td>
<td><code>0</code></td>
<td>0-100</td>
<td>Gap between tiled windows (0 for edge-to-edge tiling)</td>
</tr>
<tr>
<td><code>font</code></td>
<td><code>fixed</code></td>
<td>-</td>
<td>X11 font name for text</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[appearance]
border_width = 0
title_height = 28
panel_height = 32
gap = 0
font = fixed</code></pre>
<h2 id="layout" style="margin-top: 3rem;">[layout] - Layout Behavior</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Configure the default layout mode and tiling parameters.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Default</th>
<th>Range</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>default</code></td>
<td><code>tiling</code></td>
<td>-</td>
<td><code>tiling</code>, <code>floating</code>, or <code>monocle</code></td>
</tr>
<tr>
<td><code>master_ratio</code></td>
<td><code>0.55</code></td>
<td>0.1-0.9</td>
<td>Portion of screen for master area</td>
</tr>
<tr>
<td><code>master_count</code></td>
<td><code>1</code></td>
<td>1-10</td>
<td>Number of windows in master area</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[layout]
default = tiling
master_ratio = 0.60
master_count = 1</code></pre>
<h2 id="panels" style="margin-top: 3rem;">[panels] - Panel Visibility</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Control which panels are displayed.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>top</code></td>
<td><code>true</code></td>
<td>Show top panel (workspaces, taskbar, systray)</td>
</tr>
<tr>
<td><code>bottom</code></td>
<td><code>true</code></td>
<td>Show bottom panel (clock)</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example - Minimal Setup</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[panels]
top = true
bottom = false</code></pre>
<h2 id="colors" style="margin-top: 3rem;">[colors] - Color Scheme</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Customize all colors using hex format (#RRGGBB).
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>panel_bg</code></td>
<td><code>#1a1a2e</code></td>
<td>Panel background color</td>
</tr>
<tr>
<td><code>panel_fg</code></td>
<td><code>#e0e0e0</code></td>
<td>Panel text color</td>
</tr>
<tr>
<td><code>workspace_active</code></td>
<td><code>#4a90d9</code></td>
<td>Active workspace indicator</td>
</tr>
<tr>
<td><code>workspace_inactive</code></td>
<td><code>#3a3a4e</code></td>
<td>Inactive workspace indicator</td>
</tr>
<tr>
<td><code>workspace_urgent</code></td>
<td><code>#d94a4a</code></td>
<td>Urgent workspace indicator</td>
</tr>
<tr>
<td><code>title_focused_bg</code></td>
<td><code>#2d3a4a</code></td>
<td>Focused window title background</td>
</tr>
<tr>
<td><code>title_focused_fg</code></td>
<td><code>#ffffff</code></td>
<td>Focused window title text</td>
</tr>
<tr>
<td><code>title_unfocused_bg</code></td>
<td><code>#1a1a1a</code></td>
<td>Unfocused window title background</td>
</tr>
<tr>
<td><code>title_unfocused_fg</code></td>
<td><code>#808080</code></td>
<td>Unfocused window title text</td>
</tr>
<tr>
<td><code>border_focused</code></td>
<td><code>#4a90d9</code></td>
<td>Focused window border</td>
</tr>
<tr>
<td><code>border_unfocused</code></td>
<td><code>#333333</code></td>
<td>Unfocused window border</td>
</tr>
<tr>
<td><code>notification_bg</code></td>
<td><code>#2a2a3e</code></td>
<td>Notification background</td>
</tr>
<tr>
<td><code>notification_fg</code></td>
<td><code>#ffffff</code></td>
<td>Notification text</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example - Nord Theme</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[colors]
panel_bg = #2e3440
panel_fg = #eceff4
workspace_active = #88c0d0
workspace_inactive = #4c566a
workspace_urgent = #bf616a
title_focused_bg = #3b4252
title_focused_fg = #eceff4
title_unfocused_bg = #2e3440
title_unfocused_fg = #4c566a
border_focused = #88c0d0
border_unfocused = #3b4252
notification_bg = #3b4252
notification_fg = #eceff4</code></pre>
<h2 id="ai" style="margin-top: 3rem;">[ai] - AI Integration</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Configure AI features. See <a href="ai-features.html">AI Features</a> for full setup instructions.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>model</code></td>
<td>OpenRouter model ID (e.g., <code>google/gemini-2.0-flash-exp:free</code>)</td>
</tr>
<tr>
<td><code>openrouter_api_key</code></td>
<td>Your OpenRouter API key (or use environment variable)</td>
</tr>
<tr>
<td><code>exa_api_key</code></td>
<td>Your Exa API key (or use environment variable)</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[ai]
model = google/gemini-2.0-flash-exp:free
openrouter_api_key = sk-or-v1-your-key-here
exa_api_key = your-exa-key-here</code></pre>
<div class="alert alert-warning" style="margin-top: 1rem;">
<strong class="alert-title">Security Note</strong>
<p style="margin: 0;">For better security, use environment variables instead of storing API keys in the config file:
<code>export OPENROUTER_API_KEY=sk-or-v1-...</code></p>
</div>
<h2 id="autostart" style="margin-top: 3rem;">[autostart] - XDG Autostart</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Configure automatic application startup. DWN follows the XDG Autostart specification.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>enabled</code></td>
<td>Enable/disable all autostart functionality (default: <code>true</code>)</td>
</tr>
<tr>
<td><code>xdg_autostart</code></td>
<td>Scan XDG .desktop files from /etc/xdg/autostart and ~/.config/autostart (default: <code>true</code>)</td>
</tr>
<tr>
<td><code>path</code></td>
<td>Additional directory for symlinks/scripts (default: <code>~/.config/dwn/autostart.d</code>)</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[autostart]
enabled = true
xdg_autostart = true
path = ~/.config/dwn/autostart.d</code></pre>
<p style="color: var(--text-muted); margin-top: 1rem;">
<strong>Directories scanned:</strong>
</p>
<ul style="color: var(--text-muted);">
<li><code>/etc/xdg/autostart/</code> - System defaults (nm-applet, blueman, power-manager)</li>
<li><code>~/.config/autostart/</code> - User XDG autostart entries</li>
<li><code>~/.config/dwn/autostart.d/</code> - DWN-specific symlinks and scripts</li>
</ul>
<h2 id="demo" style="margin-top: 3rem;">[demo] - Demo Mode</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Configure demo mode timing. The demo showcases DWN features including live AI and search functionality.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>step_delay</code></td>
<td>Time between demo steps in milliseconds (1000-30000, default: 4000)</td>
</tr>
<tr>
<td><code>ai_timeout</code></td>
<td>Timeout for AI/Exa API responses in milliseconds (5000-60000, default: 15000)</td>
</tr>
<tr>
<td><code>window_timeout</code></td>
<td>Timeout for window spawn operations in milliseconds (1000-30000, default: 5000)</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[demo]
step_delay = 4000
ai_timeout = 15000
window_timeout = 5000</code></pre>
<h2 style="margin-top: 3rem;">Complete Configuration Example</h2>
<div class="code-header">
<span>~/.config/dwn/config</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># DWN Window Manager Configuration
# https://dwn.github.io
[general]
terminal = alacritty
launcher = rofi -show drun
file_manager = thunar
focus_mode = click
focus_follow_delay = 100
decorations = true
[appearance]
border_width = 0
title_height = 28
panel_height = 32
gap = 0
font = fixed
[layout]
default = tiling
master_ratio = 0.55
master_count = 1
[panels]
top = true
bottom = true
[colors]
panel_bg = #1a1a2e
panel_fg = #e0e0e0
workspace_active = #4a90d9
workspace_inactive = #3a3a4e
workspace_urgent = #d94a4a
title_focused_bg = #2d3a4a
title_focused_fg = #ffffff
title_unfocused_bg = #1a1a1a
title_unfocused_fg = #808080
border_focused = #4a90d9
border_unfocused = #333333
notification_bg = #2a2a3e
notification_fg = #ffffff
[ai]
model = google/gemini-2.0-flash-exp:free
# API keys via environment variables recommended
[autostart]
enabled = true
xdg_autostart = true
path = ~/.config/dwn/autostart.d
[demo]
step_delay = 4000
ai_timeout = 15000
window_timeout = 5000</code></pre>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,779 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="DWN Design Patterns - Comprehensive documentation of design patterns, architectural decisions, and research sources used in the DWN window manager.">
<meta name="keywords" content="design patterns, C programming, opaque pointer, vtable, factory pattern, observer pattern, EWMH, ICCCM, XEmbed">
<title>Design Patterns - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html" class="active">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero hero-small">
<div class="container">
<h1>Design Patterns & Architecture</h1>
<p class="subtitle">
Comprehensive documentation of the design patterns, architectural decisions,
and research that shaped DWN's implementation.
</p>
</div>
</section>
<section class="section">
<div class="container docs-container">
<aside class="docs-sidebar">
<h3>Contents</h3>
<ul>
<li><a href="#overview">Overview</a></li>
<li><a href="#c-patterns">C Design Patterns</a></li>
<li><a href="#opaque-pointer">Opaque Pointer</a></li>
<li><a href="#goto-cleanup">Goto Cleanup</a></li>
<li><a href="#vtable">Vtable Polymorphism</a></li>
<li><a href="#factory">Factory Pattern</a></li>
<li><a href="#observer">Observer Pattern</a></li>
<li><a href="#singleton">Singleton Pattern</a></li>
<li><a href="#double-fork">Double Fork Daemon</a></li>
<li><a href="#x11-protocols">X11 Protocols</a></li>
<li><a href="#ewmh">EWMH Specification</a></li>
<li><a href="#icccm">ICCCM Standard</a></li>
<li><a href="#xembed">XEmbed Protocol</a></li>
<li><a href="#systray">System Tray Protocol</a></li>
<li><a href="#xdg">XDG Specifications</a></li>
<li><a href="#async">Async Patterns</a></li>
<li><a href="#modular">Modular Architecture</a></li>
<li><a href="#defensive">Defensive Programming</a></li>
<li><a href="#sources">Research Sources</a></li>
</ul>
</aside>
<div class="docs-content">
<h2 id="overview">Overview</h2>
<p>
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.
</p>
<div class="alert alert-info">
<strong class="alert-title">Design Philosophy</strong>
<p style="margin: 0;">
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."
</p>
</div>
<h2 id="c-patterns" style="margin-top: 3rem;">C Design Patterns</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">
While C lacks native object-oriented features, these patterns provide
equivalent functionality with minimal overhead.
</p>
<h3 id="opaque-pointer">Opaque Pointer Pattern</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Hide implementation details from API consumers, enabling changes without recompilation.</p>
<h4>How It Works</h4>
<p>The header declares a pointer to an incomplete type. The struct definition
exists only in the implementation file, preventing direct member access.</p>
<div class="code-header">
<span>Header (public)</span>
</div>
<pre><code>typedef struct config_t *Config;
Config config_create(void);
void config_destroy(Config cfg);</code></pre>
<div class="code-header">
<span>Implementation (private)</span>
</div>
<pre><code>struct config_t {
int border_width;
char terminal[128];
};</code></pre>
<h4>Benefits</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>API consumers cannot access internal fields directly</li>
<li>Implementation can change without breaking client code</li>
<li>Enforces encapsulation at compile time</li>
</ul>
<h4>Used In</h4>
<p style="color: var(--text-muted);">
<code>config.c</code> - Configuration management
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://interrupt.memfault.com/blog/opaque-pointers" target="_blank">Practical Design Patterns: Opaque Pointers and Objects in C</a> - Memfault</li>
<li><a href="https://en.wikipedia.org/wiki/Opaque_pointer" target="_blank">Opaque Pointer</a> - Wikipedia</li>
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/DCL12-C.+Implement+abstract+data+types+using+opaque+types" target="_blank">DCL12-C: Implement abstract data types using opaque types</a> - SEI CERT C Coding Standard</li>
<li><a href="https://blog.mbedded.ninja/programming/design-patterns/opaque-pointers/" target="_blank">Opaque Pointers</a> - mbedded.ninja</li>
</ul>
</div>
<h3 id="goto-cleanup">Goto Cleanup Pattern</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Centralize resource cleanup in functions that acquire multiple resources,
preventing memory leaks and ensuring proper deallocation on all code paths.</p>
<h4>How It Works</h4>
<p>Resources are initialized to safe values (NULL). On error, execution jumps
to a cleanup label. The cleanup section safely releases all resources.</p>
<div class="code-header">
<span>Example</span>
</div>
<pre><code>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;
}</code></pre>
<h4>Benefits</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>Single cleanup point prevents code duplication</li>
<li>All error paths properly release resources</li>
<li>Used extensively in Linux kernel and SQLite</li>
</ul>
<h4>Used In</h4>
<p style="color: var(--text-muted);">
<code>config.c</code>, <code>news.c</code>, <code>ai.c</code> - File and network operations
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/MEM12-C.+Consider+using+a+goto+chain+when+leaving+a+function+on+error+when+using+and+releasing+resources" target="_blank">MEM12-C: Using goto chain for error handling</a> - SEI CERT C Coding Standard</li>
<li><a href="https://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-in-c" target="_blank">Using goto for error handling in C</a> - Eli Bendersky</li>
<li><a href="https://www.geeksforgeeks.org/c/using-goto-for-exception-handling-in-c/" target="_blank">Using goto for Exception Handling in C</a> - GeeksforGeeks</li>
<li><a href="https://ayende.com/blog/183521-C/error-handling-via-goto-in-c" target="_blank">Error handling via GOTO in C</a> - Ayende Rahien</li>
</ul>
</div>
<h3 id="vtable">Vtable Polymorphism</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Enable runtime polymorphism in C using function pointer tables,
allowing different implementations to share a common interface.</p>
<h4>How It Works</h4>
<p>A struct of function pointers (vtable) defines the interface. Objects contain
a pointer to their vtable. Calling through the vtable invokes the correct implementation.</p>
<div class="code-header">
<span>Example</span>
</div>
<pre><code>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);</code></pre>
<h4>Benefits</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>Runtime dispatch without language support</li>
<li>New implementations added without modifying existing code</li>
<li>Same pattern used by C++ compilers internally</li>
</ul>
<h4>Used In</h4>
<p style="color: var(--text-muted);">
<code>panel.c</code> - Widget rendering system
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://en.wikipedia.org/wiki/Virtual_method_table" target="_blank">Virtual Method Table</a> - Wikipedia</li>
<li><a href="https://embeddedartistry.com/fieldatlas/technique-inheritance-and-polymorphism-in-c/" target="_blank">Inheritance and Polymorphism in C</a> - Embedded Artistry</li>
<li><a href="https://www.state-machine.com/doc/AN_Simple_OOP_in_C.pdf" target="_blank">Object-Oriented Programming in C</a> - Quantum Leaps (PDF)</li>
<li><a href="https://www.embedded.com/programming-embedded-systems-polymorphism-in-c-2/" target="_blank">Programming embedded systems: polymorphism in C</a> - Embedded.com</li>
</ul>
</div>
<h3 id="factory">Factory Pattern</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Encapsulate object creation logic, allowing the system to create objects
without specifying their exact types.</p>
<h4>How It Works</h4>
<p>A factory function takes parameters describing what to create and returns
a pointer to the appropriate object type.</p>
<div class="code-header">
<span>Example</span>
</div>
<pre><code>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;
}
}</code></pre>
<h4>Benefits</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>Centralizes object creation logic</li>
<li>New types added without changing client code</li>
<li>Supports Open/Closed Principle</li>
</ul>
<h4>Used In</h4>
<p style="color: var(--text-muted);">
<code>config.c</code>, <code>client.c</code>, <code>workspace.c</code> - Object lifecycle management
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://refactoring.guru/design-patterns/factory-method" target="_blank">Factory Method Pattern</a> - Refactoring Guru</li>
<li><a href="https://en.wikipedia.org/wiki/Factory_method_pattern" target="_blank">Factory Method Pattern</a> - Wikipedia</li>
<li><a href="https://sourcemaking.com/design_patterns" target="_blank">Design Patterns</a> - SourceMaking</li>
</ul>
</div>
<h3 id="observer">Observer Pattern</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Implement event-driven communication where subjects notify observers
of state changes without tight coupling.</p>
<h4>How It Works</h4>
<p>Observers register callback functions with subjects. When events occur,
the subject iterates through registered callbacks and invokes them.</p>
<div class="code-header">
<span>Example</span>
</div>
<pre><code>typedef void (*EventCallback)(void *data);
void events_register(EventType type, EventCallback cb, void *data);
void events_emit(EventType type);
// Notification triggers all registered callbacks</code></pre>
<h4>Benefits</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>Loose coupling between event sources and handlers</li>
<li>Observers added/removed without modifying subjects</li>
<li>Foundation of event-driven programming</li>
</ul>
<h4>Used In</h4>
<p style="color: var(--text-muted);">
<code>keys.c</code> - Keyboard event handling, <code>notifications.c</code> - D-Bus signals
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://refactoring.guru/design-patterns/observer" target="_blank">Observer Pattern</a> - Refactoring Guru</li>
<li><a href="https://en.wikipedia.org/wiki/Observer_pattern" target="_blank">Observer Pattern</a> - Wikipedia</li>
<li><a href="https://learn.microsoft.com/en-us/dotnet/standard/events/observer-design-pattern" target="_blank">Observer Design Pattern</a> - Microsoft Learn</li>
</ul>
</div>
<h3 id="singleton">Singleton / Global State</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Provide a single, globally accessible instance of the window manager state,
simplifying module communication.</p>
<h4>Implementation in DWN</h4>
<p>DWN uses a single <code>DWNState</code> structure accessible via the global
<code>dwn</code> pointer. This is appropriate for a window manager where
exactly one instance exists per X11 session.</p>
<div class="code-header">
<span>Example</span>
</div>
<pre><code>// Global state pointer
extern DWNState *dwn;
// Access from any module
if (dwn != NULL && dwn->config != NULL) {
return dwn->config->terminal;
}</code></pre>
<h4>Rationale</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>Window manager is inherently a singleton per X session</li>
<li>Simplifies inter-module communication</li>
<li>All state centralized for easier debugging</li>
</ul>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://refactoring.guru/design-patterns/singleton" target="_blank">Singleton Pattern</a> - Refactoring Guru</li>
<li><a href="https://gameprogrammingpatterns.com/singleton.html" target="_blank">Singleton</a> - Game Programming Patterns</li>
<li><a href="https://en.wikipedia.org/wiki/Singleton_pattern" target="_blank">Singleton Pattern</a> - Wikipedia</li>
</ul>
</div>
<h3 id="double-fork">Double Fork Daemon Pattern</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Launch background processes that are fully detached from the parent,
preventing zombie processes and terminal reattachment.</p>
<h4>How It Works</h4>
<p>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.</p>
<div class="code-header">
<span>Implementation in DWN</span>
</div>
<pre><code>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;
}</code></pre>
<h4>Benefits</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>Process fully detached from parent</li>
<li>No zombie processes (intermediate is reaped immediately)</li>
<li>Cannot reacquire controlling terminal</li>
<li>Non-blocking for the caller</li>
</ul>
<h4>Used In</h4>
<p style="color: var(--text-muted);">
<code>util.c</code> - spawn_async(), <code>autostart.c</code>, <code>applauncher.c</code>
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://0xjet.github.io/3OHA/2022/04/11/post.html" target="_blank">UNIX daemonization and the double fork</a> - Juan Tapiador</li>
<li><a href="https://www.digitalbunker.dev/understanding-daemons-unix/" target="_blank">Understanding Daemons</a> - Digital Bunker</li>
<li><a href="https://lloydrochester.com/post/c/unix-daemon-example/" target="_blank">Daemon Example in C</a> - Lloyd Rochester</li>
<li><a href="https://goral.net.pl/post/double-fork/" target="_blank">Double Fork</a> - Michal Goral</li>
</ul>
</div>
<h2 id="x11-protocols" style="margin-top: 3rem;">X11 Window Manager Protocols</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">
DWN implements several freedesktop.org specifications for cross-desktop compatibility.
</p>
<h3 id="ewmh">Extended Window Manager Hints (EWMH)</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Define interactions between window managers, compositing managers, applications,
and desktop utilities in a standardized way.</p>
<h4>Key Features Implemented</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>_NET_SUPPORTED</code> - List of supported hints</li>
<li><code>_NET_CLIENT_LIST</code> - List of managed windows</li>
<li><code>_NET_CURRENT_DESKTOP</code> - Active workspace</li>
<li><code>_NET_WM_STATE</code> - Window states (fullscreen, maximized)</li>
<li><code>_NET_ACTIVE_WINDOW</code> - Currently focused window</li>
<li><code>_NET_WM_WINDOW_TYPE</code> - Window type classification</li>
</ul>
<h4>Implementation</h4>
<p style="color: var(--text-muted);">
<code>atoms.c</code> manages X11 atom creation and EWMH property updates.
Properties are updated on window focus changes, workspace switches, and state changes.
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/wm/latest/" target="_blank">Extended Window Manager Hints Specification</a> - freedesktop.org</li>
<li><a href="https://en.wikipedia.org/wiki/Extended_Window_Manager_Hints" target="_blank">Extended Window Manager Hints</a> - Wikipedia</li>
</ul>
</div>
<h3 id="icccm">Inter-Client Communication Conventions Manual (ICCCM)</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Define low-level conventions for X11 client communication, including
selections, window management, and session management.</p>
<h4>Key Features Implemented</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>WM_PROTOCOLS</code> - Window close handling</li>
<li><code>WM_DELETE_WINDOW</code> - Graceful window closing</li>
<li><code>WM_NAME</code> / <code>_NET_WM_NAME</code> - Window titles</li>
<li><code>WM_CLASS</code> - Application classification</li>
<li><code>WM_HINTS</code> - Window hints (urgency, input model)</li>
</ul>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html" target="_blank">Inter-Client Communication Conventions Manual</a> - X.Org</li>
<li><a href="https://tronche.com/gui/x/icccm/" target="_blank">ICCCM Reference</a> - Christophe Tronche</li>
</ul>
</div>
<h3 id="xembed">XEmbed Protocol</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Enable embedding of controls from one application into another,
forming the basis of the system tray implementation.</p>
<h4>How It Works</h4>
<p>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.</p>
<h4>Key Messages</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>XEMBED_EMBEDDED_NOTIFY</code> - Sent when embedding completes</li>
<li><code>XEMBED_FOCUS_IN/OUT</code> - Focus coordination</li>
<li><code>XEMBED_WINDOW_ACTIVATE</code> - Window activation</li>
</ul>
<h4>Implementation</h4>
<p style="color: var(--text-muted);">
<code>systray.c</code> implements the embedder side, reparenting tray icons
and forwarding click events.
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/xembed-spec/latest/" target="_blank">XEmbed Protocol Specification</a> - freedesktop.org</li>
<li><a href="https://www.freedesktop.org/wiki/Specifications/xembed-spec/" target="_blank">XEmbed Spec Wiki</a> - freedesktop.org</li>
</ul>
</div>
<h3 id="systray">System Tray Protocol</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Enable applications to display status icons in a desktop panel,
providing a standardized notification area.</p>
<h4>How It Works</h4>
<p>DWN acquires the <code>_NET_SYSTEM_TRAY_S0</code> selection to become the
tray manager. Applications send <code>SYSTEM_TRAY_REQUEST_DOCK</code> messages
to dock their icons.</p>
<h4>Docking Process</h4>
<ol style="padding-left: 1.25rem; color: var(--text-muted);">
<li>DWN acquires <code>_NET_SYSTEM_TRAY_S0</code> selection</li>
<li>Application sends <code>SYSTEM_TRAY_REQUEST_DOCK</code> client message</li>
<li>DWN creates embedding window and reparents icon</li>
<li>DWN sends <code>XEMBED_EMBEDDED_NOTIFY</code> to icon</li>
<li>Click events forwarded to icon window</li>
</ol>
<h4>Supported Applications</h4>
<p style="color: var(--text-muted);">
nm-applet, blueman-applet, Telegram, pasystray, udiskie, and any XEmbed-compatible tray icon.
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-0.3.html" target="_blank">System Tray Protocol Specification</a> - freedesktop.org</li>
</ul>
</div>
<h2 id="xdg" style="margin-top: 3rem;">XDG Specifications</h2>
<h3>Desktop Entry Specification</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Standard format for application metadata files (.desktop files) used
by application launchers and autostart systems.</p>
<h4>Key Fields Parsed</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>Exec</code> - Command to execute</li>
<li><code>TryExec</code> - Check if binary exists</li>
<li><code>Hidden</code> - Entry is disabled</li>
<li><code>OnlyShowIn</code> / <code>NotShowIn</code> - Desktop environment filters</li>
<li><code>Terminal</code> - Run in terminal</li>
</ul>
<h4>Implementation</h4>
<p style="color: var(--text-muted);">
<code>applauncher.c</code> parses .desktop files for the application menu.
<code>autostart.c</code> parses autostart entries.
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/desktop-entry-spec/latest/" target="_blank">Desktop Entry Specification</a> - freedesktop.org</li>
<li><a href="https://wiki.archlinux.org/title/Desktop_entries" target="_blank">Desktop Entries</a> - ArchWiki</li>
</ul>
</div>
<h3>Desktop Application Autostart Specification</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Define standard locations and format for applications that should
start automatically when the user logs in.</p>
<h4>Directories Scanned</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>/etc/xdg/autostart/</code> - System-wide autostart</li>
<li><code>~/.config/autostart/</code> - User autostart</li>
<li><code>~/.config/dwn/autostart.d/</code> - DWN-specific (symlinks)</li>
</ul>
<h4>Key Behavior</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>User entries override system entries with same filename</li>
<li><code>Hidden=true</code> disables an entry</li>
<li><code>TryExec</code> prevents running if binary missing</li>
</ul>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html" target="_blank">Desktop Application Autostart Specification</a> - freedesktop.org</li>
<li><a href="https://wiki.archlinux.org/title/XDG_Autostart" target="_blank">XDG Autostart</a> - ArchWiki</li>
</ul>
</div>
<h3>Desktop Notifications Specification</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Standard D-Bus interface for applications to display passive notifications
to users without blocking.</p>
<h4>D-Bus Interface</h4>
<p style="color: var(--text-muted);">
DWN implements <code>org.freedesktop.Notifications</code> on the session bus
at path <code>/org/freedesktop/Notifications</code>.
</p>
<h4>Key Methods</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>Notify</code> - Display a notification</li>
<li><code>CloseNotification</code> - Dismiss a notification</li>
<li><code>GetCapabilities</code> - Query supported features</li>
<li><code>GetServerInformation</code> - Server metadata</li>
</ul>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/notification-spec/latest/" target="_blank">Desktop Notifications Specification</a> - freedesktop.org</li>
<li><a href="https://wiki.archlinux.org/title/Desktop_notifications" target="_blank">Desktop Notifications</a> - ArchWiki</li>
</ul>
</div>
<h2 id="async" style="margin-top: 3rem;">Async Programming Patterns</h2>
<h3>libcurl Multi Interface</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Perform HTTP requests asynchronously without blocking the main event loop,
essential for AI features and news fetching.</p>
<h4>How It Works</h4>
<p>Instead of blocking on network I/O, the multi interface allows the main
loop to poll for completion. <code>curl_multi_perform()</code> advances
transfers incrementally.</p>
<div class="code-header">
<span>Integration Pattern</span>
</div>
<pre><code>// 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
}</code></pre>
<h4>Implementation</h4>
<p style="color: var(--text-muted);">
<code>ai.c</code> uses curl_multi for OpenRouter API calls.
Responses processed in <code>ai_process_pending()</code> called from main loop.
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://curl.se/libcurl/c/libcurl-multi.html" target="_blank">libcurl multi interface overview</a> - curl.se</li>
<li><a href="https://curl.se/libcurl/c/libcurl-tutorial.html" target="_blank">libcurl programming tutorial</a> - curl.se</li>
<li><a href="https://curl.se/libcurl/c/multi-app.html" target="_blank">multi-app.c example</a> - curl.se</li>
</ul>
</div>
<h3>pthread for Background I/O</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Offload blocking operations to separate threads when async APIs
are unavailable or impractical.</p>
<h4>Implementation</h4>
<p style="color: var(--text-muted);">
<code>news.c</code> spawns a detached thread for RSS fetching.
Mutex guards shared state, atomic flags prevent concurrent fetches.
</p>
<div class="code-header">
<span>Pattern</span>
</div>
<pre><code>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);
}</code></pre>
</div>
<h2 id="modular" style="margin-top: 3rem;">Modular Architecture</h2>
<div class="card" style="margin-bottom: 2rem;">
<h4>Design Principle</h4>
<p>Each module has a single responsibility with well-defined interfaces.
Modules communicate through the global <code>dwn</code> state and
function calls, never through shared mutable state.</p>
<h4>Module Structure</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>include/module.h</code> - Public API declarations</li>
<li><code>src/module.c</code> - Private implementation</li>
<li>Static functions for internal logic</li>
<li>module_init() / module_cleanup() lifecycle</li>
</ul>
<h4>Module Responsibilities</h4>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Module</th>
<th>Responsibility</th>
</tr>
</thead>
<tbody>
<tr><td>main.c</td><td>Event loop, initialization orchestration</td></tr>
<tr><td>client.c</td><td>Window management, focus handling</td></tr>
<tr><td>workspace.c</td><td>Virtual desktop management</td></tr>
<tr><td>layout.c</td><td>Tiling algorithms</td></tr>
<tr><td>panel.c</td><td>UI panels and widgets</td></tr>
<tr><td>systray.c</td><td>System tray protocol</td></tr>
<tr><td>notifications.c</td><td>D-Bus notification daemon</td></tr>
<tr><td>autostart.c</td><td>XDG autostart support</td></tr>
<tr><td>config.c</td><td>Configuration parsing</td></tr>
<tr><td>ai.c</td><td>AI integration</td></tr>
</tbody>
</table>
</div>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://en.wikipedia.org/wiki/Modular_programming" target="_blank">Modular Programming</a> - Wikipedia</li>
<li><a href="https://en.wikipedia.org/wiki/Separation_of_concerns" target="_blank">Separation of Concerns</a> - Wikipedia</li>
<li><a href="https://thecloudstrap.com/chapter-14-modular-programming-in-c/" target="_blank">Modular Programming in C</a> - TheCloudStrap</li>
</ul>
</div>
<h2 id="defensive" style="margin-top: 3rem;">Defensive Programming</h2>
<div class="card" style="margin-bottom: 2rem;">
<h4>Core Principles</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><strong>Null Checks</strong> - All pointer parameters validated before use</li>
<li><strong>Bounds Checking</strong> - Array indices and string lengths verified</li>
<li><strong>Return Value Checking</strong> - malloc(), fopen(), etc. checked for failure</li>
<li><strong>String Safety</strong> - strncpy() with size-1, explicit null termination</li>
<li><strong>Assertions</strong> - assert() for programmer errors in debug builds</li>
</ul>
<h4>Examples in DWN</h4>
<div class="code-header">
<span>Null Check Pattern</span>
</div>
<pre><code>const char *config_get_terminal(void) {
if (dwn != NULL && dwn->config != NULL) {
return dwn->config->terminal;
}
return "xterm"; // Safe fallback
}</code></pre>
<div class="code-header">
<span>String Safety Pattern</span>
</div>
<pre><code>strncpy(cfg->terminal, value, sizeof(cfg->terminal) - 1);
cfg->terminal[sizeof(cfg->terminal) - 1] = '\0'; // Guarantee null termination</code></pre>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/API00-C.+Functions+should+validate+their+parameters" target="_blank">API00-C: Functions should validate their parameters</a> - SEI CERT</li>
<li><a href="https://enterprisecraftsmanship.com/posts/defensive-programming/" target="_blank">Defensive programming: the good, the bad and the ugly</a> - Enterprise Craftsmanship</li>
<li><a href="https://www.cse.psu.edu/~gxt29/teaching/cs447s19/slides/05defensiveProg.pdf" target="_blank">Defensive Programming</a> - Penn State (PDF)</li>
</ul>
</div>
<h2 id="sources" style="margin-top: 3rem;">Complete Research Sources</h2>
<div class="card">
<h4>C Design Patterns</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://interrupt.memfault.com/blog/opaque-pointers" target="_blank">Practical Design Patterns: Opaque Pointers and Objects in C</a> - Memfault</li>
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/" target="_blank">SEI CERT C Coding Standard</a> - Carnegie Mellon University</li>
<li><a href="https://refactoring.guru/design-patterns" target="_blank">Design Patterns Catalog</a> - Refactoring Guru</li>
<li><a href="https://sourcemaking.com/design_patterns" target="_blank">Design Patterns</a> - SourceMaking</li>
<li><a href="https://embeddedartistry.com/fieldatlas/" target="_blank">Embedded Artistry Field Atlas</a> - Embedded Artistry</li>
<li><a href="https://www.state-machine.com/doc/AN_Simple_OOP_in_C.pdf" target="_blank">Object-Oriented Programming in C</a> - Quantum Leaps</li>
</ul>
<h4>X11 and freedesktop.org</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/wm/latest/" target="_blank">Extended Window Manager Hints (EWMH)</a> - freedesktop.org</li>
<li><a href="https://specifications.freedesktop.org/xembed-spec/latest/" target="_blank">XEmbed Protocol</a> - freedesktop.org</li>
<li><a href="https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-0.3.html" target="_blank">System Tray Protocol</a> - freedesktop.org</li>
<li><a href="https://specifications.freedesktop.org/notification-spec/latest/" target="_blank">Desktop Notifications</a> - freedesktop.org</li>
<li><a href="https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html" target="_blank">Desktop Application Autostart</a> - freedesktop.org</li>
<li><a href="https://specifications.freedesktop.org/desktop-entry-spec/latest/" target="_blank">Desktop Entry Specification</a> - freedesktop.org</li>
</ul>
<h4>Libraries and APIs</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://curl.se/libcurl/c/libcurl-multi.html" target="_blank">libcurl multi interface</a> - curl.se</li>
<li><a href="https://dbus.freedesktop.org/doc/dbus-specification.html" target="_blank">D-Bus Specification</a> - freedesktop.org</li>
<li><a href="https://www.x.org/releases/current/doc/" target="_blank">X Window System Documentation</a> - X.Org</li>
</ul>
<h4>Unix Programming</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://0xjet.github.io/3OHA/2022/04/11/post.html" target="_blank">UNIX daemonization and the double fork</a> - Juan Tapiador</li>
<li><a href="https://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-in-c" target="_blank">Using goto for error handling in C</a> - Eli Bendersky</li>
<li><a href="https://gameprogrammingpatterns.com/" target="_blank">Game Programming Patterns</a> - Robert Nystrom</li>
</ul>
<h4>Community Resources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://wiki.archlinux.org/" target="_blank">ArchWiki</a> - Comprehensive Linux documentation</li>
<li><a href="https://en.wikipedia.org/" target="_blank">Wikipedia</a> - Design pattern overviews</li>
</ul>
</div>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Source Code</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Author</h4>
<p style="color: var(--text-muted);">
retoor &lt;retoor@molodetz.nl&gt;
</p>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View File

@ -1,491 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Getting started with DWN window manager - learn the basics and become productive quickly.">
<title>Documentation - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html" class="active">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<div class="docs-layout container">
<aside class="docs-sidebar">
<ul>
<li>
<span class="section-title">Getting Started</span>
<ul>
<li><a href="#introduction" class="active">Introduction</a></li>
<li><a href="#first-steps">First Steps</a></li>
<li><a href="#basic-concepts">Basic Concepts</a></li>
<li><a href="#tutorial">Interactive Tutorial</a></li>
</ul>
</li>
<li>
<span class="section-title">Core Usage</span>
<ul>
<li><a href="#windows">Managing Windows</a></li>
<li><a href="#workspaces">Using Workspaces</a></li>
<li><a href="#layouts">Layout Modes</a></li>
<li><a href="#panels">Panels & Systray</a></li>
</ul>
</li>
<li>
<span class="section-title">Reference</span>
<ul>
<li><a href="#api">WebSocket API</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="ai-features.html">AI Features</a></li>
<li><a href="architecture.html">Architecture</a></li>
</ul>
</li>
</ul>
</aside>
<main class="docs-content">
<h1 id="introduction">Getting Started with DWN</h1>
<p class="lead">
Learn the fundamentals of DWN and become productive in minutes.
</p>
<h2 id="first-steps">First Steps</h2>
<p>
After <a href="installation.html">installing DWN</a> and starting your session,
you'll see a clean desktop with two panels: a top panel with workspace indicators,
taskbar, and system tray, and a bottom panel showing the clock.
</p>
<h3>Opening Your First Application</h3>
<p>Start by launching a terminal and application launcher:</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>T</kbd></td>
<td>Open terminal</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>F2</kbd></td>
<td>Open application launcher (dmenu/rofi)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>E</kbd></td>
<td>Open file manager</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>B</kbd></td>
<td>Open web browser</td>
</tr>
</tbody>
</table>
</div>
<div class="alert alert-info">
<strong class="alert-title">Tip: Run the Tutorial</strong>
<p style="margin: 0;">Press <kbd>Super</kbd> + <kbd>T</kbd> to start an interactive
tutorial that will guide you through all essential shortcuts.</p>
</div>
<h2 id="basic-concepts">Basic Concepts</h2>
<h3>The Super Key</h3>
<p>
Most DWN shortcuts use the <kbd>Super</kbd> key (often the Windows key or Command key).
This keeps shortcuts separate from application shortcuts that typically use
<kbd>Ctrl</kbd> or <kbd>Alt</kbd>.
</p>
<h3>Focus Model</h3>
<p>
By default, DWN uses "click to focus" - you click on a window to focus it.
You can change this to "focus follows mouse" (sloppy focus) in the configuration.
</p>
<h3>Window Decorations</h3>
<p>
DWN features a professional borderless design for maximum screen utilization:
</p>
<ul>
<li><strong>0px borders</strong> - No visible borders around windows</li>
<li><strong>0px gaps</strong> - Windows tile edge-to-edge without spacing</li>
<li><strong>28px title bars</strong> - Minimal height showing window name and controls</li>
</ul>
<p>
The title bar color indicates focus:
</p>
<ul>
<li><strong>Bright title bar</strong> - Focused window</li>
<li><strong>Dim title bar</strong> - Unfocused window</li>
</ul>
<p>
This seamless design creates a smooth, professional appearance where content takes
center stage. All settings can be customized in <code>~/.config/dwn/config</code>.
</p>
<h2 id="tutorial">Interactive Tutorial</h2>
<p>
DWN includes a built-in interactive tutorial that teaches you essential shortcuts
step by step. The tutorial:
</p>
<ul>
<li>Shows instructions for each shortcut</li>
<li>Waits for you to press the correct keys</li>
<li>Automatically advances when you complete each step</li>
<li>Covers all essential shortcuts from basic to advanced</li>
</ul>
<div class="card">
<h3>Start the Tutorial</h3>
<p>Press <kbd>Super</kbd> + <kbd>T</kbd> at any time to start or resume the tutorial.</p>
</div>
<h2 id="demo">Demo Mode</h2>
<p>
DWN includes an automated demo mode that showcases all features without requiring
any interaction. The demo:
</p>
<ul>
<li>Automatically demonstrates window management operations</li>
<li>Shows workspace switching and layout changes</li>
<li>Highlights panel features and system tray widgets</li>
<li>Demonstrates AI integration capabilities (if configured)</li>
<li>Displays the complete keyboard shortcut reference</li>
</ul>
<div class="card">
<h3>Start Demo Mode</h3>
<p>Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>D</kbd> to start the demo.
Press the same shortcut again to stop it at any time.</p>
</div>
<h2 id="windows">Managing Windows</h2>
<h3>Window Operations</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Alt</kbd> + <kbd>F4</kbd></td>
<td>Close focused window</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>Tab</kbd></td>
<td>Cycle to next window</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
<td>Cycle to previous window</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>F10</kbd></td>
<td>Toggle maximize</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>F11</kbd></td>
<td>Toggle fullscreen</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>F9</kbd></td>
<td>Toggle floating for current window</td>
</tr>
</tbody>
</table>
</div>
<h3>Moving and Resizing</h3>
<p>In floating mode, you can move and resize windows with the mouse:</p>
<ul>
<li><strong>Move</strong> - Click and drag the title bar</li>
<li><strong>Resize</strong> - Drag any window edge or corner</li>
</ul>
<h2 id="workspaces">Using Workspaces</h2>
<p>
DWN provides 9 virtual workspaces to organize your windows. You can see which
workspaces are active in the top panel.
</p>
<h3>Workspace Navigation</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>F1</kbd> - <kbd>F9</kbd></td>
<td>Switch to workspace 1-9</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F1</kbd> - <kbd>F9</kbd></td>
<td>Move window to workspace 1-9</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Right</kbd></td>
<td>Next workspace</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Left</kbd></td>
<td>Previous workspace</td>
</tr>
</tbody>
</table>
</div>
<h3>Workspace Organization Tips</h3>
<ul>
<li><strong>Workspace 1</strong> - Main work (editor, terminal)</li>
<li><strong>Workspace 2</strong> - Web browser, documentation</li>
<li><strong>Workspace 3</strong> - Communication (email, chat)</li>
<li><strong>Workspace 4-9</strong> - Project-specific contexts</li>
</ul>
<h2 id="layouts">Layout Modes</h2>
<p>
DWN supports three layout modes. Press <kbd>Super</kbd> + <kbd>Space</kbd> to cycle
through them.
</p>
<div class="features-grid" style="grid-template-columns: repeat(3, 1fr); gap: 1rem; margin: 1.5rem 0;">
<div class="card" style="padding: 1.5rem;">
<h4>Tiling</h4>
<p style="color: var(--text-muted); font-size: 0.9rem;">
Windows automatically arranged in master-stack layout.
Perfect for development workflows.
</p>
</div>
<div class="card" style="padding: 1.5rem;">
<h4>Floating</h4>
<p style="color: var(--text-muted); font-size: 0.9rem;">
Traditional overlapping windows.
Move and resize freely.
</p>
</div>
<div class="card" style="padding: 1.5rem;">
<h4>Monocle</h4>
<p style="color: var(--text-muted); font-size: 0.9rem;">
One fullscreen window at a time.
Great for focused work.
</p>
</div>
</div>
<h3>Tiling Layout Controls</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>H</kbd></td>
<td>Shrink master area</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>L</kbd></td>
<td>Expand master area</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>I</kbd></td>
<td>Increase master window count</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>D</kbd></td>
<td>Decrease master window count</td>
</tr>
</tbody>
</table>
</div>
<h2 id="panels">Panels & System Tray</h2>
<h3>Top Panel</h3>
<p>The top panel contains:</p>
<ul>
<li><strong>Workspace indicators</strong> - Click to switch, highlighted when occupied</li>
<li><strong>Taskbar</strong> - Shows windows on current workspace</li>
<li><strong>System tray</strong> - Battery, volume, WiFi (see below)</li>
</ul>
<h3>System Tray</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Indicator</th>
<th>Click</th>
<th>Right-click</th>
<th>Scroll</th>
</tr>
</thead>
<tbody>
<tr>
<td>Volume</td>
<td>Show slider</td>
<td>Toggle mute</td>
<td>Adjust volume</td>
</tr>
<tr>
<td>WiFi</td>
<td>Show networks</td>
<td>Disconnect</td>
<td>-</td>
</tr>
<tr>
<td>Battery</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
</tbody>
</table>
</div>
<h3>Bottom Panel</h3>
<p>
The bottom panel shows the current time and a scrolling news ticker. Open
the current article with <kbd>Super</kbd> + <kbd>Return</kbd>. Both panels can
be hidden in the configuration if you prefer a minimal setup.
</p>
<h2 id="api">WebSocket API</h2>
<p>
DWN exposes a real-time WebSocket API (default port <code>8777</code>) that allows for
complete programmatic control of the window manager.
</p>
<div class="card">
<h3>Connecting</h3>
<p>Endpoint: <code>ws://localhost:8777/ws</code></p>
<p>The API must be enabled in your configuration:</p>
<pre><code>[api]
enabled = true
port = 8777</code></pre>
</div>
<h3>Supported Commands</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Command</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>get_status</code></td>
<td>Get WM version and current workspace ID</td>
</tr>
<tr>
<td><code>get_workspaces</code></td>
<td>List all workspaces and their client counts</td>
</tr>
<tr>
<td><code>get_clients</code></td>
<td>Get details of all managed windows</td>
</tr>
<tr>
<td><code>switch_workspace</code></td>
<td>Change to a specific workspace ID</td>
</tr>
<tr>
<td><code>run_command</code></td>
<td>Execute a shell command</td>
</tr>
<tr>
<td><code>focus_client</code></td>
<td>Focus a specific window by ID</td>
</tr>
</tbody>
</table>
</div>
<h3>Advanced Examples</h3>
<p>
Check the <code>scripts/api_examples/</code> directory in the source tree for
sophisticated implementations:
</p>
<ul>
<li><strong>Window Picker</strong> (<code>window_picker_fzf.py</code>) - Fuzzy finder for windows.</li>
<li><strong>Session Manager</strong> (<code>session_manager.py</code>) - Save/restore window layouts.</li>
<li><strong>Web Remote</strong> (<code>web_remote.html</code>) - Browser-based dashboard.</li>
<li><strong>Auto-Manager</strong> (<code>listen.py</code>) - Proactive workspace optimization.</li>
</ul>
<h2>Next Steps</h2>
<p>Now that you know the basics, explore these topics:</p>
<ul>
<li><a href="shortcuts.html">Complete Keyboard Shortcuts Reference</a></li>
<li><a href="configuration.html">Customizing DWN</a></li>
<li><a href="ai-features.html">Using AI Features</a></li>
</ul>
</main>
</div>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View File

@ -1,510 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Explore the powerful features of DWN window manager - tiling layouts, workspaces, AI integration, and more.">
<title>Features - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html" class="active">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content">
<h1>Powerful Features</h1>
<p class="subtitle">
Everything you need for a productive desktop experience, without the bloat.
</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>Window Management</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
DWN provides flexible window management that adapts to your workflow, whether you prefer
the precision of tiling or the freedom of floating windows.
</p>
<div class="features-grid">
<div class="card">
<h3><span class="card-icon">&#9783;</span> Tiling Layout</h3>
<p>Master-stack tiling with configurable master area ratio. Windows automatically
organize into a primary area and a stack, maximizing screen real estate.</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Adjustable master area ratio (0.1 - 0.9)</li>
<li>Multiple windows in master area</li>
<li>Smart stack arrangement</li>
<li>Seamless borderless design (0px borders, 0px gaps)</li>
</ul>
</div>
<div class="card">
<h3><span class="card-icon">&#10063;</span> Floating Layout</h3>
<p>Traditional floating window management with drag-and-drop positioning.
Perfect for workflows that need overlapping windows or free-form arrangement.</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Click and drag to move windows</li>
<li>Resize from any edge or corner</li>
<li>Window snapping support</li>
<li>Respect minimum size hints</li>
</ul>
</div>
<div class="card">
<h3><span class="card-icon">&#9744;</span> Monocle Layout</h3>
<p>Full-screen single window mode for focused work. Each window takes up
the entire workspace, perfect for presentations or deep concentration.</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Maximize focused window</li>
<li>Quick window cycling</li>
<li>Ideal for single-task focus</li>
<li>Works great on small screens</li>
</ul>
</div>
</div>
<div class="alert alert-info" style="margin-top: 2rem;">
<strong class="alert-title">Pro Tip</strong>
<p style="margin: 0;">Switch layouts instantly with <kbd>Super</kbd> + <kbd>Space</kbd>.
Your window arrangement is preserved when switching back.</p>
</div>
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">Borderless Design</h3>
<div class="card">
<p>DWN employs a professional, seamless aesthetic with zero window borders and zero gaps:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li><strong>0px borders</strong> - No visible borders around windows</li>
<li><strong>0px gaps</strong> - Windows tile edge-to-edge without spacing</li>
<li><strong>28px title bars</strong> - Minimal height for window controls</li>
<li><strong>Maximum screen utilization</strong> - Every pixel counts</li>
</ul>
<p style="margin-top: 1rem; color: var(--text-muted);">
This creates a smooth, professional appearance where content takes center stage. The focus
remains on your work, not on window decorations. All settings are configurable via
<code>~/.config/dwn/config</code> if you prefer different values.
</p>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2>Virtual Workspaces</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
Nine virtual desktops give you unlimited room to organize your work.
Each workspace maintains its own window state and layout preferences.
</p>
<div class="features-grid" style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));">
<div class="feature-card">
<div class="feature-icon">1-9</div>
<h3>9 Workspaces</h3>
<p>Quick access via F1-F9 keys. Organize projects, contexts, or tasks across
dedicated spaces.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#8596;</div>
<h3>Window Transfer</h3>
<p>Move windows between workspaces with Shift+F1-F9. Quick and keyboard-driven.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128200;</div>
<h3>Per-Workspace State</h3>
<p>Each workspace remembers its layout mode, window positions, and focused window.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128065;</div>
<h3>Visual Indicators</h3>
<p>Panel shows active and occupied workspaces at a glance with color coding.</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Panels & System Tray</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
Built-in panels provide essential information and quick access to common functions
without needing external tools or status bars.
</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
<div class="card">
<h3>Top Panel</h3>
<p>The top panel contains your main controls and information:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li><strong>Workspace Indicators</strong> - Click or use shortcuts to switch</li>
<li><strong>Taskbar</strong> - Shows windows on current workspace</li>
<li><strong>System Tray</strong> - Battery, volume, WiFi indicators</li>
</ul>
</div>
<div class="card">
<h3>Bottom Panel</h3>
<p>Optional bottom panel for additional information:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li><strong>Clock Display</strong> - Time and date</li>
<li><strong>News Ticker</strong> - Scrolling news feed with navigation</li>
<li><strong>Customizable</strong> - Can be hidden in config</li>
</ul>
</div>
</div>
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">News Ticker</h3>
<div class="card">
<p>The bottom panel includes a scrolling news ticker that displays headlines from a news feed.
Navigate through articles using keyboard shortcuts:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li><kbd>Super</kbd> + <kbd>Down</kbd> - Next article</li>
<li><kbd>Super</kbd> + <kbd>Up</kbd> - Previous article</li>
<li><kbd>Super</kbd> + <kbd>Return</kbd> - Open in browser</li>
</ul>
<p style="margin-top: 1rem; color: var(--text-muted);">
The ticker updates automatically and caches up to 50 articles. Smooth scrolling animation
at 80 pixels per second keeps you informed without distraction.
</p>
</div>
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">XEmbed System Tray</h3>
<div class="card" style="margin-bottom: 2rem;">
<h3>&#128241; External Application Icons</h3>
<p>DWN implements the freedesktop.org XEmbed System Tray protocol, allowing external applications
to dock their status icons in the panel - just like XFCE, GNOME, or KDE.</p>
<p style="margin-top: 1rem; color: var(--text-muted);">Supported applications include:</p>
<ul style="margin-top: 0.5rem; padding-left: 1.25rem; color: var(--text-muted);">
<li><strong>Telegram</strong> - Notification icons for messages</li>
<li><strong>nm-applet</strong> - NetworkManager GUI</li>
<li><strong>blueman-applet</strong> - Bluetooth manager</li>
<li><strong>pasystray</strong> - PulseAudio control</li>
<li><strong>udiskie</strong> - USB automounter</li>
<li>Any application with tray icon support</li>
</ul>
<p style="margin-top: 1rem; color: var(--text-muted);">
Simply launch any tray-enabled application and its icon will automatically appear in the panel.
Click on icons to interact - all events are forwarded to the application.
</p>
</div>
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">XDG Autostart</h3>
<div class="card" style="margin-bottom: 2rem;">
<h3>Automatic Application Startup</h3>
<p>DWN follows the XDG Autostart specification, automatically starting system services
and tray applications - just like traditional desktop environments.</p>
<p style="margin-top: 1rem; color: var(--text-muted);">Directories scanned at startup:</p>
<ul style="margin-top: 0.5rem; padding-left: 1.25rem; color: var(--text-muted);">
<li><code>/etc/xdg/autostart/</code> - System defaults (nm-applet, blueman, power-manager)</li>
<li><code>~/.config/autostart/</code> - User XDG autostart entries</li>
<li><code>~/.config/dwn/autostart.d/</code> - DWN-specific symlinks and scripts</li>
</ul>
<p style="margin-top: 1rem; color: var(--text-muted);">
All applications launch concurrently for fastest boot time. Properly handles .desktop
file fields including Hidden, TryExec, OnlyShowIn, and NotShowIn.
</p>
</div>
<h3 style="margin-top: 2rem; margin-bottom: 1.5rem;">Built-in Widgets</h3>
<div class="features-grid">
<div class="card">
<h3>&#128267; Battery Monitor</h3>
<p>Shows current battery percentage with color-coded status:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Red when below 20%</li>
<li>Blue when charging</li>
<li>Auto-hides on desktops</li>
</ul>
</div>
<div class="card">
<h3>&#128266; Volume Control</h3>
<p>Full audio control at your fingertips:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Click for volume slider</li>
<li>Scroll to adjust</li>
<li>Right-click to mute</li>
</ul>
</div>
<div class="card">
<h3>&#128246; WiFi Manager</h3>
<p>Network management made simple:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Click for network list</li>
<li>Signal strength indicators</li>
<li>Current SSID display</li>
</ul>
</div>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2>Notification System</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
Built-in D-Bus notification daemon following freedesktop.org standards.
No need for external notification tools like dunst or notify-osd.
</p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card">
<h3>Standards Compliant</h3>
<p>Implements the org.freedesktop.Notifications D-Bus interface.
Works seamlessly with any application that sends desktop notifications.</p>
</div>
<div class="card">
<h3>Customizable Appearance</h3>
<p>Configure notification colors and positioning through the config file.
Notifications match your overall color scheme automatically.</p>
</div>
</div>
<div class="alert alert-success" style="margin-top: 2rem;">
<strong class="alert-title">Capacity</strong>
<p style="margin: 0;">DWN can display up to 32 notifications simultaneously,
with automatic queuing and timeout management.</p>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>AI Integration</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
Optional AI features powered by OpenRouter API and Exa semantic search.
Control your desktop with natural language and get intelligent assistance.
</p>
<div class="features-grid">
<div class="card">
<h3>&#129302; AI Command Palette</h3>
<p>Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd> and type natural
language commands like "open firefox" or "launch terminal".</p>
<a href="ai-features.html" class="btn btn-sm btn-secondary" style="margin-top: 1rem;">Learn More</a>
</div>
<div class="card">
<h3>&#128269; Semantic Web Search</h3>
<p>Search the web semantically with Exa integration. Find relevant content based
on meaning, not just keywords.</p>
<a href="ai-features.html" class="btn btn-sm btn-secondary" style="margin-top: 1rem;">Learn More</a>
</div>
<div class="card">
<h3>&#127891; Context Analysis</h3>
<p>AI analyzes your current workspace to understand what you're working on
and provides relevant suggestions.</p>
<a href="ai-features.html" class="btn btn-sm btn-secondary" style="margin-top: 1rem;">Learn More</a>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Extensible API</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
DWN exposes a real-time WebSocket API that allows for complete programmatic
control and monitoring of your desktop environment.
</p>
<div class="features-grid">
<div class="card">
<h3>&#128161; Real-time Monitoring</h3>
<p>Subscribe to window manager events and state changes. Build custom dashboards,
status bars, or automation scripts.</p>
</div>
<div class="card">
<h3>&#127915; Remote Control</h3>
<p>Change workspaces, focus windows, and launch applications programmatically
from any language that supports WebSockets.</p>
</div>
<div class="card">
<h3>&#128187; Integration Ready</h3>
<p>Native support for JSON-RPC style commands makes it easy to integrate with
external tools, mobile apps, or web interfaces.</p>
</div>
</div>
<div class="alert alert-info" style="margin-top: 2rem;">
<p style="margin: 0;">Detailed API documentation and examples are available in the
<a href="documentation.html#api">Documentation section</a>.</p>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2>Standards Compliance</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
DWN implements EWMH and ICCCM protocols for maximum compatibility with X11 applications.
</p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card">
<h3>EWMH Support</h3>
<p>Extended Window Manager Hints for modern application features:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>_NET_WM_STATE (fullscreen, maximized, etc.)</li>
<li>_NET_ACTIVE_WINDOW</li>
<li>_NET_CLIENT_LIST and _NET_CLIENT_LIST_STACKING</li>
<li>_NET_CURRENT_DESKTOP and _NET_NUMBER_OF_DESKTOPS</li>
<li>_NET_WM_WINDOW_TYPE</li>
</ul>
</div>
<div class="card">
<h3>ICCCM Compliance</h3>
<p>Inter-Client Communication Conventions Manual support:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>WM_STATE management</li>
<li>WM_PROTOCOLS (WM_DELETE_WINDOW, WM_TAKE_FOCUS)</li>
<li>WM_NORMAL_HINTS (size hints)</li>
<li>WM_CLASS for window matching</li>
<li>WM_NAME and _NET_WM_NAME</li>
</ul>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Technical Specifications</h2>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Specification</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Language</td>
<td>ANSI C (C99)</td>
</tr>
<tr>
<td>Maximum Clients</td>
<td>256 windows</td>
</tr>
<tr>
<td>Workspaces</td>
<td>9 virtual desktops</td>
</tr>
<tr>
<td>Monitor Support</td>
<td>Up to 8 monitors (Xinerama/Xrandr)</td>
</tr>
<tr>
<td>Notifications</td>
<td>32 concurrent</td>
</tr>
<tr>
<td>Keybindings</td>
<td>64 configurable shortcuts</td>
</tr>
<tr>
<td>Memory Usage</td>
<td>&lt; 5MB typical</td>
</tr>
<tr>
<td>Configuration</td>
<td>INI-style (~/.config/dwn/config)</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Learning DWN</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
Two built-in modes help you learn DWN quickly: an interactive tutorial and
an automated demo that showcases all features.
</p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card">
<h3><span class="card-icon">&#128218;</span> Interactive Tutorial</h3>
<p>Press <kbd>Super</kbd> + <kbd>T</kbd> to start a hands-on tutorial that:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Guides you through essential shortcuts step-by-step</li>
<li>Waits for you to press the correct key combination</li>
<li>Automatically advances when you complete each step</li>
<li>Can be restarted at any time</li>
</ul>
</div>
<div class="card">
<h3><span class="card-icon">&#127916;</span> Demo Mode</h3>
<p>Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>D</kbd> for an automated showcase:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Demonstrates window management, workspaces, and layouts</li>
<li>Shows panel features and system tray</li>
<li>Highlights AI integration and news ticker</li>
<li>Displays complete keyboard shortcut reference</li>
</ul>
</div>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container" style="text-align: center;">
<h2>Ready to Try DWN?</h2>
<p style="color: var(--text-muted); max-width: 500px; margin: 0 auto 2rem;">
Get started in minutes with our simple installation process.
</p>
<div class="hero-buttons" style="justify-content: center;">
<a href="installation.html" class="btn btn-primary btn-lg">Install Now</a>
<a href="shortcuts.html" class="btn btn-secondary btn-lg">View Shortcuts</a>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View File

@ -1,319 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="DWN - A modern, production-ready X11 window manager with XFCE-like functionality and optional AI integration.">
<meta name="keywords" content="window manager, X11, Linux, tiling, floating, EWMH, AI, productivity">
<title>DWN Window Manager - Modern X11 Window Management</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html" class="active">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero">
<div class="container hero-content">
<h1>Modern Window Management for X11</h1>
<p class="subtitle">
DWN is a production-ready window manager written in ANSI C with XFCE-like functionality,
powerful tiling layouts, and optional AI integration. Seamless borderless design, zero gaps,
and professional appearance. Fast, flexible, and fully featured.
</p>
<div class="hero-buttons">
<a href="installation.html" class="btn btn-primary btn-lg">Get Started</a>
<a href="features.html" class="btn btn-secondary btn-lg">Explore Features</a>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 style="text-align: center; margin-bottom: 1rem;">Why Choose DWN?</h2>
<p style="text-align: center; color: var(--text-muted); max-width: 600px; margin: 0 auto 3rem;">
DWN combines the simplicity of traditional floating window managers with the productivity
of tiling layouts, all wrapped in a modern, customizable package.
</p>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">&#9776;</div>
<h3>Multiple Layouts</h3>
<p>Switch seamlessly between tiling, floating, and monocle layouts.
Resize master areas and organize windows exactly how you work.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#9881;</div>
<h3>9 Workspaces</h3>
<p>Organize your workflow across 9 virtual desktops with per-workspace
state. Move windows between workspaces with a single keystroke.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#127912;</div>
<h3>Fully Customizable</h3>
<p>INI-style configuration with extensive theming options. Customize colors,
fonts, borders, gaps, and behavior to match your style.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128161;</div>
<h3>AI Integration</h3>
<p>Optional AI command palette and semantic web search. Control your desktop
with natural language and get intelligent suggestions.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128172;</div>
<h3>Extensible API</h3>
<p>Real-time WebSocket interface for programmatic control. Build custom
dashboards, mobile remotes, and automated workflows.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128276;</div>
<h3>Notification Daemon</h3>
<p>Built-in D-Bus notification support following freedesktop.org standards.
No need for external notification daemons.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128187;</div>
<h3>System Tray</h3>
<p>XEmbed protocol for external app icons (Telegram, Bluetooth, etc.) plus
built-in battery, volume, and WiFi widgets with interactive controls.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#9889;</div>
<h3>XDG Autostart</h3>
<p>Automatic startup of system services and tray applications following the
XDG Autostart spec. Works with nm-applet, blueman, and more.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128240;</div>
<h3>News Ticker</h3>
<p>Real-time news feed with scrolling article titles in the panel. Press
Super+Return to open the current article in your browser.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#127891;</div>
<h3>Interactive Learning</h3>
<p>Built-in tutorial system (Super+T) guides you through keyboard shortcuts.
Demo mode (Super+Shift+D) showcases all features automatically.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128208;</div>
<h3>Borderless Design</h3>
<p>Zero window borders and zero gaps for a seamless, professional appearance.
Windows tile edge-to-edge for maximum screen utilization.</p>
</div>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<div class="stats">
<div class="stat-item">
<h3>~15K</h3>
<p>Lines of Pure C</p>
</div>
<div class="stat-item">
<h3>0</h3>
<p>Runtime Dependencies</p>
</div>
<div class="stat-item">
<h3>&lt;5MB</h3>
<p>Memory Footprint</p>
</div>
<div class="stat-item">
<h3>43+</h3>
<p>Keyboard Shortcuts</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 style="text-align: center;">Get Up and Running in Minutes</h2>
<p style="text-align: center; color: var(--text-muted); max-width: 600px; margin: 0 auto 3rem;">
DWN is designed for easy installation and immediate productivity.
Build from source or use your distribution's package manager.
</p>
<div class="card" style="max-width: 700px; margin: 0 auto;">
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Clone the repository
git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn
# Install dependencies (auto-detects your distro)
make deps
# Build and install
make
sudo make install
# Add to your .xinitrc
echo "exec dwn" >> ~/.xinitrc</code></pre>
</div>
<p style="text-align: center; margin-top: 2rem;">
<a href="installation.html" class="btn btn-primary">Full Installation Guide</a>
</p>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2 style="text-align: center;">See DWN in Action</h2>
<p style="text-align: center; color: var(--text-muted); max-width: 600px; margin: 0 auto 3rem;">
A clean, modern interface that stays out of your way while providing everything you need.
</p>
<div class="screenshot-grid">
<div class="screenshot">
<div class="screenshot-placeholder">
[Tiling Layout with Terminal and Editor]
</div>
<div class="screenshot-caption">Master-stack tiling layout perfect for development</div>
</div>
<div class="screenshot">
<div class="screenshot-placeholder">
[System Tray and Notifications]
</div>
<div class="screenshot-caption">Integrated system tray with volume and WiFi controls</div>
</div>
<div class="screenshot">
<div class="screenshot-placeholder">
[AI Command Palette]
</div>
<div class="screenshot-caption">AI-powered command palette for natural language control</div>
</div>
<div class="screenshot">
<div class="screenshot-placeholder">
[WebSocket API Dashboard]
</div>
<div class="screenshot-caption">Real-time WebSocket API for custom integrations</div>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 style="text-align: center;">How DWN Compares</h2>
<p style="text-align: center; color: var(--text-muted); max-width: 600px; margin: 0 auto 3rem;">
DWN bridges the gap between minimal tiling managers and full desktop environments.
</p>
<div class="comparison">
<div class="comparison-card">
<h3>Minimal Tiling WMs</h3>
<p style="color: var(--text-muted);">dwm, i3, bspwm</p>
<ul>
<li>Lightweight and fast</li>
<li>Keyboard-driven workflow</li>
<li>Highly customizable</li>
<li>Steep learning curve</li>
<li>Requires additional tools</li>
</ul>
</div>
<div class="comparison-card featured">
<h3>DWN</h3>
<p style="color: var(--text-muted);">Best of both worlds</p>
<ul>
<li>Lightweight and fast</li>
<li>Keyboard-driven workflow</li>
<li>Highly customizable</li>
<li>Interactive tutorial</li>
<li>Built-in panels and systray</li>
<li>AI integration optional</li>
<li>Notification daemon included</li>
</ul>
</div>
<div class="comparison-card">
<h3>Desktop Environments</h3>
<p style="color: var(--text-muted);">XFCE, GNOME, KDE</p>
<ul>
<li>Feature complete</li>
<li>User-friendly</li>
<li>Heavy resource usage</li>
<li>Less customizable</li>
<li>Slower performance</li>
</ul>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container" style="text-align: center;">
<h2>Ready to Try DWN?</h2>
<div class="hero-buttons" style="justify-content: center;">
<a href="installation.html" class="btn btn-primary btn-lg">Install DWN</a>
<a href="documentation.html" class="btn btn-secondary btn-lg">Read the Docs</a>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View File

@ -1,561 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Install DWN window manager - step by step guide for Debian, Ubuntu, Fedora, Arch Linux and more.">
<title>Installation - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html" class="active">Install</a></li>
<li class="dropdown">
<a href="documentation.html">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content">
<h1>Installation Guide</h1>
<p class="subtitle">
Get DWN running on your system in just a few minutes.
</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>Requirements</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">
DWN requires X11 and a few common libraries. Most Linux distributions include these by default.
</p>
<div class="card">
<h3>Required Dependencies</h3>
<div class="table-wrapper" style="margin-top: 1rem;">
<table>
<thead>
<tr>
<th>Library</th>
<th>Purpose</th>
<th>Package (Debian/Ubuntu)</th>
</tr>
</thead>
<tbody>
<tr>
<td>libX11</td>
<td>X Window System client library</td>
<td><code>libx11-dev</code></td>
</tr>
<tr>
<td>libXext</td>
<td>X extensions library</td>
<td><code>libxext-dev</code></td>
</tr>
<tr>
<td>libXinerama</td>
<td>Multi-monitor support</td>
<td><code>libxinerama-dev</code></td>
</tr>
<tr>
<td>libXrandr</td>
<td>Display configuration</td>
<td><code>libxrandr-dev</code></td>
</tr>
<tr>
<td>libXft</td>
<td>Font rendering</td>
<td><code>libxft-dev</code></td>
</tr>
<tr>
<td>fontconfig</td>
<td>Font configuration</td>
<td><code>libfontconfig1-dev</code></td>
</tr>
<tr>
<td>libdbus-1</td>
<td>D-Bus for notifications</td>
<td><code>libdbus-1-dev</code></td>
</tr>
<tr>
<td>libcurl</td>
<td>AI features (optional)</td>
<td><code>libcurl4-openssl-dev</code></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2>Quick Installation</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">
The fastest way to get started. Our build system auto-detects your distribution.
</p>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Clone the Repository</h4>
<p>Download the latest source code from GitHub.</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn</code></pre>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Install Dependencies</h4>
<p>Automatically install required packages for your distribution.</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>make deps</code></pre>
<p style="margin-top: 0.5rem; font-size: 0.875rem; color: var(--text-muted);">
Supports Debian, Ubuntu, Fedora, Arch, openSUSE, and Void Linux.
</p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>Build DWN</h4>
<p>Compile the window manager with optimizations.</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>make</code></pre>
</div>
</div>
<div class="step">
<div class="step-number">4</div>
<div class="step-content">
<h4>Install System-wide</h4>
<p>Install the binary to your system PATH.</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>sudo make install</code></pre>
<p style="margin-top: 0.5rem; font-size: 0.875rem; color: var(--text-muted);">
Default location: <code>/usr/local/bin/dwn</code>. Override with <code>PREFIX=/custom/path make install</code>
</p>
</div>
</div>
<div class="step">
<div class="step-number">5</div>
<div class="step-content">
<h4>Configure Your Session</h4>
<p>Add DWN to your X session startup.</p>
<div class="code-header">
<span>~/.xinitrc</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>exec dwn</code></pre>
</div>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Distribution-Specific Instructions</h2>
<div class="tabs">
<button class="tab active" onclick="showTab('debian')">Debian/Ubuntu</button>
<button class="tab" onclick="showTab('fedora')">Fedora</button>
<button class="tab" onclick="showTab('arch')">Arch Linux</button>
<button class="tab" onclick="showTab('void')">Void Linux</button>
</div>
<div id="debian" class="tab-content active">
<h3>Debian / Ubuntu / Linux Mint</h3>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Install dependencies
sudo apt update
sudo apt install -y \
build-essential \
libx11-dev \
libxext-dev \
libxinerama-dev \
libxrandr-dev \
libxft-dev \
libfontconfig1-dev \
libdbus-1-dev \
libcurl4-openssl-dev \
pkg-config
# Build and install
git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn
make
sudo make install</code></pre>
</div>
<div id="fedora" class="tab-content">
<h3>Fedora / RHEL / CentOS</h3>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Install dependencies
sudo dnf install -y \
gcc \
make \
libX11-devel \
libXext-devel \
libXinerama-devel \
libXrandr-devel \
libXft-devel \
fontconfig-devel \
dbus-devel \
libcurl-devel \
pkg-config
# Build and install
git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn
make
sudo make install</code></pre>
</div>
<div id="arch" class="tab-content">
<h3>Arch Linux / Manjaro</h3>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Install dependencies
sudo pacman -S --needed \
base-devel \
libx11 \
libxext \
libxinerama \
libxrandr \
libxft \
fontconfig \
dbus \
curl \
pkg-config
# Build and install
git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn
make
sudo make install</code></pre>
<div class="alert alert-info" style="margin-top: 1rem;">
<strong>AUR Package</strong>
<p style="margin: 0;">An AUR package <code>dwn-git</code> may also be available:
<code>yay -S dwn-git</code></p>
</div>
</div>
<div id="void" class="tab-content">
<h3>Void Linux</h3>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Install dependencies
sudo xbps-install -S \
base-devel \
libX11-devel \
libXext-devel \
libXinerama-devel \
libXrandr-devel \
libXft-devel \
fontconfig-devel \
dbus-devel \
libcurl-devel \
pkg-config
# Build and install
git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn
make
sudo make install</code></pre>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2>Session Setup</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">
Configure your display manager or xinit to start DWN.
</p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card">
<h3>Using xinit / startx</h3>
<p>For minimal setups using startx:</p>
<div class="code-header">
<span>~/.xinitrc</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Optional: set display settings
xrandr --output DP-1 --mode 2560x1440
# Optional: set wallpaper
feh --bg-fill ~/wallpaper.jpg
# Start DWN
exec dwn</code></pre>
<p style="margin-top: 1rem; font-size: 0.875rem; color: var(--text-muted);">
Then run <code>startx</code> from a TTY.
</p>
</div>
<div class="card">
<h3>Using a Display Manager</h3>
<p>Create a desktop entry for GDM, LightDM, etc:</p>
<div class="code-header">
<span>/usr/share/xsessions/dwn.desktop</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[Desktop Entry]
Name=DWN
Comment=DWN Window Manager
Exec=dwn
Type=Application
DesktopNames=DWN</code></pre>
<p style="margin-top: 1rem; font-size: 0.875rem; color: var(--text-muted);">
DWN will appear in your display manager's session menu.
</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Testing in a Nested X Server</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">
Test DWN without leaving your current session using Xephyr.
</p>
<div class="card">
<h3>Using make run</h3>
<p>The easiest way to test DWN safely:</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Make sure Xephyr is installed
# Debian/Ubuntu: sudo apt install xserver-xephyr
# Fedora: sudo dnf install xorg-x11-server-Xephyr
# Arch: sudo pacman -S xorg-server-xephyr
# Run DWN in a nested window
make run</code></pre>
<p style="margin-top: 1rem; color: var(--text-muted);">
This opens a 1280x720 window running DWN. Perfect for experimenting with configuration changes.
</p>
</div>
<div class="card" style="margin-top: 1.5rem;">
<h3>Manual Xephyr Setup</h3>
<p>For more control over the test environment:</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Start Xephyr on display :1
Xephyr :1 -screen 1920x1080 &
# Run DWN on that display
DISPLAY=:1 ./dwn
# Open a terminal in the test environment
DISPLAY=:1 xterm &</code></pre>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2>Post-Installation</h2>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Create Configuration Directory</h4>
<p>DWN will create this automatically on first run, but you can set it up in advance:</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>mkdir -p ~/.config/dwn</code></pre>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Run the Interactive Tutorial</h4>
<p>Once DWN is running, press <kbd>Super</kbd> + <kbd>T</kbd> to start the built-in tutorial
that will teach you all the essential shortcuts.</p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>View All Shortcuts</h4>
<p>Press <kbd>Super</kbd> + <kbd>S</kbd> to see a complete list of keyboard shortcuts.</p>
</div>
</div>
<div class="step">
<div class="step-number">4</div>
<div class="step-content">
<h4>Customize Your Setup</h4>
<p>See the <a href="configuration.html">Configuration Guide</a> to personalize DWN to your liking.</p>
</div>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Troubleshooting</h2>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
DWN doesn't start - "cannot open display"
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>This error means DWN can't connect to an X server. Make sure:</p>
<ul style="padding-left: 1.25rem; margin-top: 0.5rem;">
<li>You're running from a TTY with <code>startx</code>, not from within another X session</li>
<li>The DISPLAY environment variable is set correctly</li>
<li>X server is installed and working (<code>Xorg -configure</code> to test)</li>
</ul>
</div>
</div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
Build fails - "pkg-config: command not found"
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>Install pkg-config for your distribution:</p>
<pre style="margin-top: 0.5rem;"><code>sudo apt install pkg-config # Debian/Ubuntu
sudo dnf install pkg-config # Fedora
sudo pacman -S pkg-config # Arch</code></pre>
</div>
</div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
Missing header files during build
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>Make sure you have the development packages installed, not just the runtime libraries.
On Debian/Ubuntu, install packages ending with <code>-dev</code>.
Run <code>make deps</code> to auto-install everything.</p>
</div>
</div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
Keyboard shortcuts don't work
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>Check for conflicts with other programs grabbing keys:</p>
<ul style="padding-left: 1.25rem; margin-top: 0.5rem;">
<li>Make sure no other window manager is running</li>
<li>Check if compositor (like picom) is grabbing keys</li>
<li>Verify keyboard layout is correct with <code>setxkbmap</code></li>
</ul>
</div>
</div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
Fonts look bad or missing
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>DWN uses Xft for font rendering. Install some good fonts:</p>
<pre style="margin-top: 0.5rem;"><code>sudo apt install fonts-dejavu fonts-liberation # Debian/Ubuntu
sudo dnf install dejavu-fonts-all liberation-fonts # Fedora</code></pre>
<p style="margin-top: 0.5rem;">You can configure the font in <code>~/.config/dwn/config</code>.</p>
</div>
</div>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container" style="text-align: center;">
<h2>Installation Complete?</h2>
<p style="color: var(--text-muted); max-width: 500px; margin: 0 auto 2rem;">
Learn how to use DWN effectively with our documentation.
</p>
<div class="hero-buttons" style="justify-content: center;">
<a href="documentation.html" class="btn btn-primary btn-lg">Getting Started Guide</a>
<a href="shortcuts.html" class="btn btn-secondary btn-lg">Keyboard Shortcuts</a>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

View File

@ -1,304 +0,0 @@
/**
* DWN Window Manager - Website JavaScript
* Vanilla JS for interactivity
*/
// Mobile Navigation Toggle
function toggleNav() {
const navLinks = document.querySelector('.nav-links');
const toggle = document.querySelector('.nav-toggle');
navLinks.classList.toggle('active');
toggle.classList.toggle('active');
}
// Close mobile nav when clicking outside
document.addEventListener('click', function(e) {
const nav = document.querySelector('nav');
const navLinks = document.querySelector('.nav-links');
if (navLinks && navLinks.classList.contains('active')) {
if (!nav.contains(e.target)) {
navLinks.classList.remove('active');
}
}
});
// Close mobile nav when clicking a link
document.querySelectorAll('.nav-links a').forEach(link => {
link.addEventListener('click', () => {
const navLinks = document.querySelector('.nav-links');
if (navLinks) {
navLinks.classList.remove('active');
}
});
});
// Tab functionality
function showTab(tabId) {
// Hide all tab contents
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// Deactivate all tabs
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
// Show selected tab content
const selectedContent = document.getElementById(tabId);
if (selectedContent) {
selectedContent.classList.add('active');
}
// Activate clicked tab
event.target.classList.add('active');
}
// FAQ Accordion
function toggleFaq(button) {
const faqItem = button.closest('.faq-item');
const isActive = faqItem.classList.contains('active');
// Close all FAQ items
document.querySelectorAll('.faq-item').forEach(item => {
item.classList.remove('active');
});
// Open clicked item if it wasn't already open
if (!isActive) {
faqItem.classList.add('active');
}
}
// Copy to clipboard functionality
function copyCode(button) {
const codeBlock = button.closest('.code-header').nextElementSibling;
const code = codeBlock.querySelector('code');
if (code) {
const text = code.textContent;
navigator.clipboard.writeText(text).then(() => {
// Show feedback
const originalText = button.textContent;
button.textContent = 'Copied!';
button.style.background = 'var(--success)';
setTimeout(() => {
button.textContent = originalText;
button.style.background = '';
}, 2000);
}).catch(err => {
console.error('Failed to copy:', err);
button.textContent = 'Error';
});
}
}
// Shortcut search/filter functionality
function filterShortcuts() {
const searchInput = document.getElementById('shortcut-search');
if (!searchInput) return;
const filter = searchInput.value.toLowerCase();
const tables = document.querySelectorAll('.shortcuts-table');
tables.forEach(table => {
const rows = table.querySelectorAll('tbody tr');
let visibleCount = 0;
rows.forEach(row => {
const text = row.textContent.toLowerCase();
if (text.includes(filter)) {
row.style.display = '';
visibleCount++;
} else {
row.style.display = 'none';
}
});
// Show/hide the entire table section if no matches
const section = table.closest('.table-wrapper');
const header = section ? section.previousElementSibling : null;
if (section && header && header.tagName === 'H2') {
if (visibleCount === 0 && filter !== '') {
section.style.display = 'none';
header.style.display = 'none';
} else {
section.style.display = '';
header.style.display = '';
}
}
});
}
// Smooth scroll for anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
const href = this.getAttribute('href');
if (href === '#') return;
e.preventDefault();
const target = document.querySelector(href);
if (target) {
const headerOffset = 80; // Account for fixed header
const elementPosition = target.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
// Update URL without scrolling
history.pushState(null, null, href);
}
});
});
// Highlight active section in docs sidebar
function updateActiveSection() {
const sidebar = document.querySelector('.docs-sidebar');
if (!sidebar) return;
const sections = document.querySelectorAll('h2[id], h3[id]');
const links = sidebar.querySelectorAll('a[href^="#"]');
let currentSection = '';
const scrollPos = window.scrollY + 100;
sections.forEach(section => {
if (section.offsetTop <= scrollPos) {
currentSection = section.id;
}
});
links.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === '#' + currentSection) {
link.classList.add('active');
}
});
}
// Throttle scroll events
let scrollTimeout;
window.addEventListener('scroll', () => {
if (scrollTimeout) return;
scrollTimeout = setTimeout(() => {
updateActiveSection();
scrollTimeout = null;
}, 100);
});
// Header background on scroll
function updateHeaderBackground() {
const header = document.querySelector('header');
if (!header) return;
if (window.scrollY > 50) {
header.style.background = 'rgba(26, 26, 46, 0.98)';
} else {
header.style.background = 'rgba(26, 26, 46, 0.95)';
}
}
window.addEventListener('scroll', updateHeaderBackground);
// Animate elements on scroll (intersection observer)
function initScrollAnimations() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('fade-in');
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
});
// Observe feature cards and other elements
document.querySelectorAll('.feature-card, .card, .comparison-card, .testimonial').forEach(el => {
el.style.opacity = '0';
observer.observe(el);
});
}
// Keyboard shortcut for search (/)
document.addEventListener('keydown', (e) => {
if (e.key === '/' && !['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) {
const searchInput = document.getElementById('shortcut-search');
if (searchInput) {
e.preventDefault();
searchInput.focus();
}
}
// Escape to close search/nav
if (e.key === 'Escape') {
const searchInput = document.getElementById('shortcut-search');
if (searchInput && document.activeElement === searchInput) {
searchInput.blur();
searchInput.value = '';
filterShortcuts();
}
const navLinks = document.querySelector('.nav-links');
if (navLinks) {
navLinks.classList.remove('active');
}
}
});
// External link handling - open in new tab
document.querySelectorAll('a[href^="http"]').forEach(link => {
if (!link.hostname.includes(window.location.hostname)) {
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
}
});
// Initialize on DOM ready
document.addEventListener('DOMContentLoaded', () => {
initScrollAnimations();
updateActiveSection();
updateHeaderBackground();
// Set current year in footer if needed
const yearSpan = document.querySelector('.current-year');
if (yearSpan) {
yearSpan.textContent = new Date().getFullYear();
}
});
// Print-friendly handling
window.addEventListener('beforeprint', () => {
// Expand all FAQs for printing
document.querySelectorAll('.faq-item').forEach(item => {
item.classList.add('active');
});
// Show all tab contents
document.querySelectorAll('.tab-content').forEach(content => {
content.style.display = 'block';
});
});
window.addEventListener('afterprint', () => {
// Restore FAQ state
document.querySelectorAll('.faq-item').forEach(item => {
item.classList.remove('active');
});
// Restore tab state
document.querySelectorAll('.tab-content').forEach(content => {
content.style.display = '';
});
});

View File

@ -1,460 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Complete keyboard shortcuts reference for DWN window manager.">
<title>Keyboard Shortcuts - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html" class="active">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content">
<h1>Keyboard Shortcuts</h1>
<p class="subtitle">
Complete reference for all DWN keyboard shortcuts.
Press <kbd>Super</kbd> + <kbd>S</kbd> in DWN to view this list.
</p>
</div>
</section>
<section class="section">
<div class="container">
<div class="search-box">
<input type="text" id="shortcut-search" placeholder="Search shortcuts..." onkeyup="filterShortcuts()">
</div>
<h2 id="launchers">Application Launchers</h2>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>T</kbd></td>
<td>Open terminal (configurable)</td>
</tr>
<tr>
<td><kbd>Super</kbd> / <kbd>Alt</kbd> + <kbd>F2</kbd></td>
<td>Open application launcher (dmenu/rofi)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>E</kbd></td>
<td>Open file manager (configurable)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>B</kbd></td>
<td>Open web browser</td>
</tr>
<tr>
<td><kbd>Print</kbd></td>
<td>Take screenshot (xfce4-screenshooter)</td>
</tr>
</tbody>
</table>
</div>
<h2 id="windows">Window Management</h2>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Alt</kbd> + <kbd>F4</kbd></td>
<td>Close focused window</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>Tab</kbd></td>
<td>Cycle to next window</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
<td>Cycle to previous window</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>F9</kbd></td>
<td>Toggle minimize/restore</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>F10</kbd></td>
<td>Toggle maximize</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>F11</kbd></td>
<td>Toggle fullscreen</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>F9</kbd></td>
<td>Toggle floating mode for focused window</td>
</tr>
</tbody>
</table>
</div>
<h2 id="workspaces">Workspace Navigation</h2>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>F1</kbd></td>
<td>Switch to workspace 1</td>
</tr>
<tr>
<td><kbd>F2</kbd></td>
<td>Switch to workspace 2</td>
</tr>
<tr>
<td><kbd>F3</kbd></td>
<td>Switch to workspace 3</td>
</tr>
<tr>
<td><kbd>F4</kbd></td>
<td>Switch to workspace 4</td>
</tr>
<tr>
<td><kbd>F5</kbd></td>
<td>Switch to workspace 5</td>
</tr>
<tr>
<td><kbd>F6</kbd></td>
<td>Switch to workspace 6</td>
</tr>
<tr>
<td><kbd>F7</kbd></td>
<td>Switch to workspace 7</td>
</tr>
<tr>
<td><kbd>F8</kbd></td>
<td>Switch to workspace 8</td>
</tr>
<tr>
<td><kbd>F9</kbd></td>
<td>Switch to workspace 9</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F1</kbd></td>
<td>Move focused window to workspace 1</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F2</kbd></td>
<td>Move focused window to workspace 2</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F3</kbd></td>
<td>Move focused window to workspace 3</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F4</kbd></td>
<td>Move focused window to workspace 4</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F5</kbd></td>
<td>Move focused window to workspace 5</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F6</kbd></td>
<td>Move focused window to workspace 6</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F7</kbd></td>
<td>Move focused window to workspace 7</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F8</kbd></td>
<td>Move focused window to workspace 8</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F9</kbd></td>
<td>Move focused window to workspace 9</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Right</kbd></td>
<td>Switch to next workspace</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Left</kbd></td>
<td>Switch to previous workspace</td>
</tr>
</tbody>
</table>
</div>
<h2 id="layouts">Layout Control</h2>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>Space</kbd></td>
<td>Cycle layout mode (tiling → floating → monocle)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>H</kbd></td>
<td>Shrink master area</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>L</kbd></td>
<td>Expand master area</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>I</kbd></td>
<td>Increase master window count</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>D</kbd></td>
<td>Decrease master window count</td>
</tr>
</tbody>
</table>
</div>
<h2 id="snapping">Window Snapping (Composable)</h2>
<p style="color: var(--text-muted); margin-bottom: 1rem;">
Snapping shortcuts are composable: press Super+Left then Super+Up for top-left quarter. Press the same key twice to expand to full in that axis.
</p>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>Left</kbd></td>
<td>Snap left 50% (press twice for full width)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Right</kbd></td>
<td>Snap right 50% (press twice for full width)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Up</kbd></td>
<td>Snap top 50% (press twice for full height)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Down</kbd></td>
<td>Snap bottom 50% (press twice for full height)</td>
</tr>
</tbody>
</table>
</div>
<h2 id="ai">AI Features</h2>
<p style="color: var(--text-muted); margin-bottom: 1rem;">
These shortcuts require API keys to be configured. See <a href="ai-features.html">AI Features</a> for setup.
</p>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>A</kbd></td>
<td>Open AI command palette</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd></td>
<td>Show AI context analysis</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>E</kbd></td>
<td>Open Exa semantic web search</td>
</tr>
</tbody>
</table>
</div>
<h2 id="news">News Ticker</h2>
<p style="color: var(--text-muted); margin-bottom: 1rem;">
The scrolling news ticker is displayed in the bottom panel.
</p>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>Return</kbd></td>
<td>Open current article in browser</td>
</tr>
</tbody>
</table>
</div>
<h2 id="system">Help & System</h2>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>S</kbd></td>
<td>Show all keyboard shortcuts</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>T</kbd></td>
<td>Start/continue interactive tutorial</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>D</kbd></td>
<td>Start/stop demo mode</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Backspace</kbd></td>
<td>Quit DWN</td>
</tr>
</tbody>
</table>
</div>
<div class="card" style="margin-top: 3rem;">
<h3>Printable Quick Reference</h3>
<p>Essential shortcuts to memorize when starting with DWN:</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;">
<div>
<h4 style="color: var(--primary); margin-bottom: 0.75rem;">Must Know</h4>
<ul style="list-style: none; padding: 0;">
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>T</kbd> Terminal
</li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Alt</kbd>+<kbd>F2</kbd> Launcher
</li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Alt</kbd>+<kbd>F4</kbd> Close window
</li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Alt</kbd>+<kbd>Tab</kbd> Switch windows
</li>
<li style="padding: 0.5rem 0;">
<kbd>F1</kbd>-<kbd>F9</kbd> Workspaces
</li>
</ul>
</div>
<div>
<h4 style="color: var(--primary); margin-bottom: 0.75rem;">Power User</h4>
<ul style="list-style: none; padding: 0;">
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Super</kbd>+<kbd>Space</kbd> Change layout
</li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Super</kbd>+<kbd>H</kbd>/<kbd>L</kbd> Resize master
</li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Super</kbd>+Arrows Composable snap
</li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Shift</kbd>+<kbd>F1-9</kbd> Move to workspace
</li>
<li style="padding: 0.5rem 0;">
<kbd>Super</kbd>+<kbd>S</kbd> Show shortcuts
</li>
</ul>
</div>
</div>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>

1048
src/ai.c

File diff suppressed because it is too large Load Diff

1269
src/api.c

File diff suppressed because it is too large Load Diff

View File

@ -16,10 +16,11 @@
#include <unistd.h>
#include <ctype.h>
#include <pwd.h>
#include <limits.h>
static AppLauncherState launcher_state = {0};
static char recent_file_path[512] = {0};
static char recent_file_path[PATH_MAX] = {0};
static void scan_desktop_dir(const char *dir);
static int parse_desktop_file(const char *path, AppEntry *entry);
@ -42,7 +43,7 @@ void applauncher_init(void)
snprintf(recent_file_path, sizeof(recent_file_path),
"%s/.local/share/dwn/recent_apps", home);
char dir_path[512];
char dir_path[PATH_MAX];
snprintf(dir_path, sizeof(dir_path), "%s/.local/share/dwn", home);
mkdir(dir_path, 0755);
@ -66,7 +67,7 @@ void applauncher_refresh(void)
const char *home = getenv("HOME");
if (home) {
char user_apps[512];
char user_apps[PATH_MAX];
snprintf(user_apps, sizeof(user_apps), "%s/.local/share/applications", home);
scan_desktop_dir(user_apps);
}
@ -271,7 +272,7 @@ static void add_to_recent(const char *desktop_id)
static char *strip_field_codes(const char *exec)
{
static char result[512];
static char result[PATH_MAX];
size_t j = 0;
for (size_t i = 0; exec[i] && j < sizeof(result) - 1; i++) {

206
src/async_context.c Normal file
View File

@ -0,0 +1,206 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Async Context Implementation - Per-Module Async State
*/
#include "threading.h"
#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/eventfd.h>
/* Full definition of AsyncContext implementation */
struct AsyncContextImpl {
char name[32];
ThreadPool *pool;
EventBus *event_bus;
Channel *completion_channel;
_Atomic int operation_count;
_Atomic int shutdown_requested;
pthread_mutex_t mutex;
int event_fd;
};
typedef struct {
struct AsyncContextImpl *ctx;
TaskFunc user_func;
void *user_data;
Future *future;
} AsyncWrapperData;
static void async_wrapper_func(void *data, atomic_int *cancelled)
{
AsyncWrapperData *wrap = data;
/* Execute user's function */
if (wrap->user_func) {
wrap->user_func(wrap->user_data, cancelled);
}
/* Signal completion via channel */
TaskHandle *task_ptr = dwn_malloc(sizeof(TaskHandle));
if (task_ptr) {
*task_ptr = (TaskHandle)wrap; /* Just use wrapper as identifier */
channel_try_send(wrap->ctx->completion_channel, task_ptr);
}
atomic_fetch_sub_explicit(&wrap->ctx->operation_count, 1, ATOMIC_RELAXED);
}
AsyncContext* async_context_create(const char *name)
{
struct AsyncContextImpl *ctx = dwn_malloc(sizeof(struct AsyncContextImpl));
if (!ctx) return NULL;
strncpy(ctx->name, name ? name : "unnamed", sizeof(ctx->name) - 1);
ctx->name[sizeof(ctx->name) - 1] = '\0';
ctx->pool = thread_pool_default();
ctx->event_bus = event_bus_default();
ctx->completion_channel = channel_create(64);
if (!ctx->completion_channel) {
dwn_free(ctx);
return NULL;
}
atomic_store_explicit(&ctx->operation_count, 0, ATOMIC_RELEASE);
atomic_store_explicit(&ctx->shutdown_requested, 0, ATOMIC_RELEASE);
if (pthread_mutex_init(&ctx->mutex, NULL) != 0) {
channel_destroy(ctx->completion_channel);
dwn_free(ctx);
return NULL;
}
/* We use the channel's eventfd for polling */
ctx->event_fd = -1; /* Will use channel's fd via async_get_poll_fd */
return (AsyncContext*)ctx;
}
void async_context_destroy(AsyncContext *ctx)
{
if (!ctx) return;
struct AsyncContextImpl *impl = (struct AsyncContextImpl*)ctx;
atomic_store_explicit(&impl->shutdown_requested, 1, ATOMIC_RELEASE);
/* Wait for operations to complete */
int timeout = 100; /* 10 seconds max wait */
while (atomic_load_explicit(&impl->operation_count, ATOMIC_ACQUIRE) > 0 && timeout > 0) {
usleep(100000); /* 100ms */
timeout--;
}
channel_close(impl->completion_channel);
channel_destroy(impl->completion_channel);
pthread_mutex_destroy(&impl->mutex);
dwn_free(impl);
}
TaskHandle async_submit(AsyncContext *ctx, TaskFunc func, void *user_data,
TaskPriority priority)
{
if (!ctx || !func) return NULL;
struct AsyncContextImpl *impl = (struct AsyncContextImpl*)ctx;
if (atomic_load_explicit(&impl->shutdown_requested, ATOMIC_ACQUIRE)) {
return NULL;
}
AsyncWrapperData *wrap = dwn_malloc(sizeof(AsyncWrapperData));
if (!wrap) return NULL;
wrap->ctx = impl;
wrap->user_func = func;
wrap->user_data = user_data;
wrap->future = NULL;
atomic_fetch_add_explicit(&impl->operation_count, 1, ATOMIC_RELAXED);
TaskHandle task = task_create_with_callback(
async_wrapper_func, wrap, priority,
NULL, NULL
);
if (!task || thread_pool_submit(thread_pool_default(), task) != THREAD_OK) {
atomic_fetch_sub_explicit(&impl->operation_count, 1, ATOMIC_RELAXED);
dwn_free(wrap);
return NULL;
}
return task;
}
Future* async_submit_future(AsyncContext *ctx, TaskFunc func, void *user_data,
TaskPriority priority)
{
if (!ctx || !func) return NULL;
struct AsyncContextImpl *impl = (struct AsyncContextImpl*)ctx;
Future *f = future_create();
if (!f) return NULL;
AsyncWrapperData *wrap = dwn_malloc(sizeof(AsyncWrapperData));
if (!wrap) {
future_destroy(f);
return NULL;
}
wrap->ctx = impl;
wrap->user_func = func;
wrap->user_data = user_data;
wrap->future = f;
atomic_fetch_add_explicit(&impl->operation_count, 1, ATOMIC_RELAXED);
TaskHandle task = task_create(async_wrapper_func, wrap, priority);
if (!task) {
atomic_fetch_sub_explicit(&impl->operation_count, 1, ATOMIC_RELAXED);
dwn_free(wrap);
future_destroy(f);
return NULL;
}
ThreadStatus status = thread_pool_submit(thread_pool_default(), task);
if (status != THREAD_OK) {
task_destroy(task);
atomic_fetch_sub_explicit(&impl->operation_count, 1, ATOMIC_RELAXED);
dwn_free(wrap);
future_destroy(f);
return NULL;
}
return f;
}
uint32_t async_poll(AsyncContext *ctx)
{
if (!ctx) return 0;
struct AsyncContextImpl *impl = (struct AsyncContextImpl*)ctx;
uint32_t processed = 0;
void *data;
while (channel_try_recv(impl->completion_channel, &data) == THREAD_OK) {
dwn_free(data);
processed++;
}
return processed;
}
int async_get_poll_fd(AsyncContext *ctx)
{
if (!ctx) return -1;
struct AsyncContextImpl *impl = (struct AsyncContextImpl*)ctx;
return impl->event_fd;
}

View File

@ -202,22 +202,43 @@ void atoms_update_desktop_names(void)
return;
}
char names[MAX_WORKSPACES * 32];
int offset = 0;
/* Buffer needs to account for:
- MAX_WORKSPACES names
- Each name up to sizeof(Workspace.name) - 1 = 31 chars
- Plus null terminator for each name
- Plus 8 characters for numeric fallback (e.g., "9\0")
*/
char names[MAX_WORKSPACES * (32 + 1)];
size_t offset = 0;
size_t max_offset = sizeof(names);
for (int i = 0; i < MAX_WORKSPACES; i++) {
const char *name = dwn->workspaces[i].name;
if (name[0] == '\0') {
offset += snprintf(names + offset, sizeof(names) - offset, "%d", i + 1);
} else {
offset += snprintf(names + offset, sizeof(names) - offset, "%s", name);
size_t remaining = max_offset - offset;
if (remaining < 2) { /* Need at least room for one char + null */
break;
}
int written;
if (name[0] == '\0') {
written = snprintf(names + offset, remaining, "%d", i + 1);
} else {
written = snprintf(names + offset, remaining, "%s", name);
}
if (written < 0 || (size_t)written >= remaining) {
/* Truncation or error - ensure null termination */
names[offset] = '\0';
offset++;
} else {
offset += (size_t)written + 1; /* Include null terminator */
}
names[offset++] = '\0';
}
XChangeProperty(dwn->display, dwn->root, ewmh.NET_DESKTOP_NAMES,
misc_atoms.UTF8_STRING, 8, PropModeReplace,
(unsigned char *)names, offset);
(unsigned char *)names, (int)offset);
}
void atoms_set_current_desktop(int desktop)

View File

@ -110,7 +110,7 @@ static void scan_xdg_directory(const char *dir_path)
continue;
}
char exec_cmd[512];
char exec_cmd[PATH_MAX];
if (parse_desktop_file(full_path, exec_cmd, sizeof(exec_cmd)) == 0) {
LOG_INFO("Autostart: %s -> %s", entry->d_name, exec_cmd);
spawn_async(exec_cmd);
@ -236,7 +236,7 @@ static int parse_desktop_file(const char *path, char *exec_cmd, size_t exec_size
}
if (access(try_exec, X_OK) != 0) {
char which_cmd[512];
char which_cmd[PATH_MAX];
snprintf(which_cmd, sizeof(which_cmd), "which %s >/dev/null 2>&1", try_exec);
if (system(which_cmd) != 0) {
exec_cmd[0] = '\0';
@ -256,7 +256,7 @@ static void strip_exec_field_codes(char *exec)
return;
}
char result[512];
char result[PATH_MAX];
size_t j = 0;
for (size_t i = 0; exec[i] != '\0' && j < sizeof(result) - 1; i++) {

View File

@ -0,0 +1,806 @@
/*
* DWN - Desktop Window Manager
* X11 Backend Implementation
*/
#define _GNU_SOURCE
#include "backends/x11/x11_backend.h"
#include "core/wm_types.h"
#include "core/wm_string.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/*==============================================================================
* X11 Backend State
*============================================================================*/
static struct {
Display *display;
int screen;
Window root;
bool initialized;
int fd;
/* Error handling */
int last_error;
XErrorHandler error_handler;
XIOErrorHandler io_error_handler;
/* Cursor cache */
Cursor cursor_cache[XC_num_glyphs];
bool cursor_loaded[XC_num_glyphs];
/* Atoms cache */
Atom atom_wm_protocols;
Atom atom_wm_delete_window;
Atom atom_wm_state;
Atom atom_wm_fullscreen;
Atom atom_wm_maximized_horz;
Atom atom_wm_maximized_vert;
Atom atom_net_wm_name;
Atom atom_utf8_string;
} x11_state = {0};
/*==============================================================================
* Error Handlers
*============================================================================*/
static int x11_error_handler(Display *dpy, XErrorEvent *ev) {
x11_state.last_error = ev->error_code;
char msg[256];
XGetErrorText(dpy, ev->error_code, msg, sizeof(msg));
fprintf(stderr, "X11 Error: %s (request %d, minor %d)\n",
msg, ev->request_code, ev->minor_code);
return 0;
}
static int x11_io_error_handler(Display *dpy) {
(void)dpy;
fprintf(stderr, "X11 IO Error: Connection to display lost\n");
x11_state.initialized = false;
return 0;
}
/*==============================================================================
* Backend Information
*============================================================================*/
static WmBackendInfo x11_get_info(void) {
return (WmBackendInfo){
.name = "x11",
.version = "1.0",
.description = "X11 Window System Backend",
.capabilities =
WM_BACKEND_CAP_WINDOWS |
WM_BACKEND_CAP_INPUT_EVENTS |
WM_BACKEND_CAP_MULTI_MONITOR |
WM_BACKEND_CAP_CLIPBOARD,
.supports_multiple_instances = false,
.requires_compositor = false
};
}
/*==============================================================================
* Lifecycle
*============================================================================*/
static bool x11_init(void *config) {
(void)config;
if (x11_state.initialized) return true;
XSetErrorHandler(x11_error_handler);
XSetIOErrorHandler(x11_io_error_handler);
return true;
}
static void x11_shutdown(void) {
if (!x11_state.initialized) return;
/* Free cached cursors */
for (int i = 0; i < XC_num_glyphs; i++) {
if (x11_state.cursor_loaded[i]) {
XFreeCursor(x11_state.display, x11_state.cursor_cache[i]);
}
}
x11_state.initialized = false;
}
static bool x11_is_initialized(void) {
return x11_state.initialized;
}
/*==============================================================================
* Connection
*============================================================================*/
static bool x11_connect(void) {
if (x11_state.display) return true;
x11_state.display = XOpenDisplay(NULL);
if (!x11_state.display) {
fprintf(stderr, "Failed to open X11 display\n");
return false;
}
x11_state.screen = DefaultScreen(x11_state.display);
x11_state.root = RootWindow(x11_state.display, x11_state.screen);
x11_state.fd = ConnectionNumber(x11_state.display);
x11_state.initialized = true;
/* Cache atoms */
x11_state.atom_wm_protocols = XInternAtom(x11_state.display, "WM_PROTOCOLS", False);
x11_state.atom_wm_delete_window = XInternAtom(x11_state.display, "WM_DELETE_WINDOW", False);
x11_state.atom_wm_state = XInternAtom(x11_state.display, "_NET_WM_STATE", False);
x11_state.atom_wm_fullscreen = XInternAtom(x11_state.display, "_NET_WM_STATE_FULLSCREEN", False);
x11_state.atom_wm_maximized_horz = XInternAtom(x11_state.display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
x11_state.atom_wm_maximized_vert = XInternAtom(x11_state.display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
x11_state.atom_net_wm_name = XInternAtom(x11_state.display, "_NET_WM_NAME", False);
x11_state.atom_utf8_string = XInternAtom(x11_state.display, "UTF8_STRING", False);
return true;
}
static void x11_disconnect(void) {
if (x11_state.display) {
XCloseDisplay(x11_state.display);
x11_state.display = NULL;
x11_state.initialized = false;
}
}
static bool x11_is_connected(void) {
return x11_state.display != NULL && x11_state.initialized;
}
static int x11_get_file_descriptor(void) {
return x11_state.fd;
}
static void x11_flush(void) {
if (x11_state.display) XFlush(x11_state.display);
}
static void x11_sync(void) {
if (x11_state.display) XSync(x11_state.display, False);
}
/*==============================================================================
* Screen/Display
*============================================================================*/
static void x11_get_screen_dimensions(int *width, int *height) {
if (!x11_state.display) {
if (width) *width = 0;
if (height) *height = 0;
return;
}
if (width) *width = DisplayWidth(x11_state.display, x11_state.screen);
if (height) *height = DisplayHeight(x11_state.display, x11_state.screen);
}
static int x11_get_screen_count(void) {
return x11_state.display ? ScreenCount(x11_state.display) : 0;
}
static void x11_get_screen_geometry(int screen, WmRect *geometry) {
if (!x11_state.display || !geometry) return;
geometry->x = 0;
geometry->y = 0;
geometry->width = DisplayWidth(x11_state.display, screen);
geometry->height = DisplayHeight(x11_state.display, screen);
}
/*==============================================================================
* Window Management - Geometry
*============================================================================*/
static void x11_window_get_geometry(WmWindowHandle window, WmRect *geometry) {
if (!x11_state.display || !geometry) return;
Window root;
int x, y;
unsigned int width, height, border, depth;
if (XGetGeometry(x11_state.display, (Window)window, &root,
&x, &y, &width, &height, &border, &depth)) {
geometry->x = x;
geometry->y = y;
geometry->width = (int)width;
geometry->height = (int)height;
}
}
static void x11_window_set_geometry(WmWindowHandle window, const WmRect *geometry) {
if (!x11_state.display || !geometry) return;
XMoveResizeWindow(x11_state.display, (Window)window,
geometry->x, geometry->y,
(unsigned int)geometry->width,
(unsigned int)geometry->height);
}
static void x11_window_move(WmWindowHandle window, int x, int y) {
if (!x11_state.display) return;
XMoveWindow(x11_state.display, (Window)window, x, y);
}
static void x11_window_resize(WmWindowHandle window, int width, int height) {
if (!x11_state.display) return;
XResizeWindow(x11_state.display, (Window)window,
(unsigned int)width, (unsigned int)height);
}
static void x11_window_move_resize(WmWindowHandle window, const WmRect *geometry) {
x11_window_set_geometry(window, geometry);
}
/*==============================================================================
* Window Management - Visibility
*============================================================================*/
static void x11_window_show(WmWindowHandle window) {
if (!x11_state.display) return;
XMapWindow(x11_state.display, (Window)window);
}
static void x11_window_hide(WmWindowHandle window) {
if (!x11_state.display) return;
XUnmapWindow(x11_state.display, (Window)window);
}
static bool x11_window_is_visible(WmWindowHandle window) {
if (!x11_state.display) return false;
XWindowAttributes attrs;
if (XGetWindowAttributes(x11_state.display, (Window)window, &attrs)) {
return attrs.map_state == IsViewable;
}
return false;
}
static void x11_window_map(WmWindowHandle window) {
x11_window_show(window);
}
static void x11_window_unmap(WmWindowHandle window) {
x11_window_hide(window);
}
static bool x11_window_is_mapped(WmWindowHandle window) {
return x11_window_is_visible(window);
}
/*==============================================================================
* Window Management - Stacking
*============================================================================*/
static void x11_window_raise(WmWindowHandle window) {
if (!x11_state.display) return;
XRaiseWindow(x11_state.display, (Window)window);
}
static void x11_window_lower(WmWindowHandle window) {
if (!x11_state.display) return;
XLowerWindow(x11_state.display, (Window)window);
}
static void x11_window_reparent(WmWindowHandle window, WmWindowHandle parent, int x, int y) {
if (!x11_state.display) return;
XReparentWindow(x11_state.display, (Window)window, (Window)parent, x, y);
}
/*==============================================================================
* Window Management - Focus
*============================================================================*/
static void x11_window_focus(WmWindowHandle window) {
if (!x11_state.display) return;
XSetInputFocus(x11_state.display, (Window)window, RevertToPointerRoot, CurrentTime);
}
static void x11_window_unfocus(WmWindowHandle window) {
(void)window;
if (!x11_state.display) return;
XSetInputFocus(x11_state.display, PointerRoot, RevertToPointerRoot, CurrentTime);
}
static bool x11_window_is_focused(WmWindowHandle window) {
if (!x11_state.display) return false;
Window focused;
int revert_to;
XGetInputFocus(x11_state.display, &focused, &revert_to);
return (Window)window == focused;
}
static WmWindowHandle x11_window_get_focused(void) {
if (!x11_state.display) return NULL;
Window focused;
int revert_to;
XGetInputFocus(x11_state.display, &focused, &revert_to);
return (WmWindowHandle)focused;
}
/*==============================================================================
* Window Management - Border
*============================================================================*/
static void x11_window_set_border_width(WmWindowHandle window, int width) {
if (!x11_state.display) return;
XSetWindowBorderWidth(x11_state.display, (Window)window, (unsigned int)width);
}
static int x11_window_get_border_width(WmWindowHandle window) {
if (!x11_state.display) return 0;
XWindowAttributes attrs;
if (XGetWindowAttributes(x11_state.display, (Window)window, &attrs)) {
return (int)attrs.border_width;
}
return 0;
}
/*==============================================================================
* Window Management - Properties
*============================================================================*/
static void x11_window_set_title(WmWindowHandle window, const char *title) {
if (!x11_state.display || !title) return;
XStoreName(x11_state.display, (Window)window, title);
if (x11_state.atom_net_wm_name != None && x11_state.atom_utf8_string != None) {
XChangeProperty(x11_state.display, (Window)window,
x11_state.atom_net_wm_name, x11_state.atom_utf8_string,
8, PropModeReplace,
(const unsigned char*)title, (int)strlen(title));
}
}
static char* x11_window_get_title(WmWindowHandle window) {
if (!x11_state.display) return NULL;
char *title = NULL;
/* Try _NET_WM_NAME first (UTF-8) */
if (x11_state.atom_net_wm_name != None) {
Atom actual_type;
int actual_format;
unsigned long nitems, bytes_after;
unsigned char *data = NULL;
if (XGetWindowProperty(x11_state.display, (Window)window,
x11_state.atom_net_wm_name, 0, ~0L, False,
x11_state.atom_utf8_string, &actual_type,
&actual_format, &nitems, &bytes_after, &data) == Success) {
if (data && actual_type == x11_state.atom_utf8_string) {
title = strdup((char*)data);
XFree(data);
if (title) return title;
}
if (data) XFree(data);
}
}
/* Fall back to WM_NAME */
char *name = NULL;
if (XFetchName(x11_state.display, (Window)window, &name)) {
title = strdup(name);
XFree(name);
}
return title;
}
/*==============================================================================
* Window Management - Protocols
*============================================================================*/
static bool x11_window_supports_protocol(WmWindowHandle window, const char *protocol) {
if (!x11_state.display || !protocol) return false;
Atom proto = XInternAtom(x11_state.display, protocol, True);
if (proto == None) return false;
Atom *protocols = NULL;
int count = 0;
if (XGetWMProtocols(x11_state.display, (Window)window, &protocols, &count)) {
for (int i = 0; i < count; i++) {
if (protocols[i] == proto) {
XFree(protocols);
return true;
}
}
XFree(protocols);
}
return false;
}
static void x11_window_close(WmWindowHandle window) {
if (!x11_state.display) return;
if (x11_window_supports_protocol(window, "WM_DELETE_WINDOW")) {
XEvent ev;
ev.type = ClientMessage;
ev.xclient.window = (Window)window;
ev.xclient.message_type = x11_state.atom_wm_protocols;
ev.xclient.format = 32;
ev.xclient.data.l[0] = (long)x11_state.atom_wm_delete_window;
ev.xclient.data.l[1] = CurrentTime;
XSendEvent(x11_state.display, (Window)window, False, NoEventMask, &ev);
} else {
XKillClient(x11_state.display, (Window)window);
}
}
static void x11_window_kill(WmWindowHandle window) {
if (!x11_state.display) return;
XKillClient(x11_state.display, (Window)window);
}
/*==============================================================================
* Window Management - State
*============================================================================*/
static void x11_window_set_fullscreen(WmWindowHandle window, bool fullscreen) {
if (!x11_state.display) return;
XEvent ev;
ev.type = ClientMessage;
ev.xclient.window = (Window)window;
ev.xclient.message_type = x11_state.atom_wm_state;
ev.xclient.format = 32;
ev.xclient.data.l[0] = fullscreen ? 1 : 0; /* _NET_WM_STATE_ADD or _NET_WM_STATE_REMOVE */
ev.xclient.data.l[1] = (long)x11_state.atom_wm_fullscreen;
ev.xclient.data.l[2] = 0;
ev.xclient.data.l[3] = 1; /* Source indication: normal application */
ev.xclient.data.l[4] = 0;
XSendEvent(x11_state.display, x11_state.root, False,
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
}
static void x11_window_set_urgent(WmWindowHandle window, bool urgent) {
if (!x11_state.display) return;
XWMHints *hints = XGetWMHints(x11_state.display, (Window)window);
if (!hints) {
hints = XAllocWMHints();
if (!hints) return;
}
if (urgent) {
hints->flags |= XUrgencyHint;
} else {
hints->flags &= ~XUrgencyHint;
}
XSetWMHints(x11_state.display, (Window)window, hints);
XFree(hints);
}
static bool x11_window_is_urgent(WmWindowHandle window) {
if (!x11_state.display) return false;
XWMHints *hints = XGetWMHints(x11_state.display, (Window)window);
if (!hints) return false;
bool urgent = (hints->flags & XUrgencyHint) != 0;
XFree(hints);
return urgent;
}
/*==============================================================================
* Input - Events
*============================================================================*/
static bool x11_poll_event(WmBackendEvent *event) {
if (!x11_state.display || !event) return false;
if (!XPending(x11_state.display)) return false;
XEvent xev;
XNextEvent(x11_state.display, &xev);
/* Translate X11 event to backend event */
memset(event, 0, sizeof(*event));
event->window = (WmWindowHandle)xev.xany.window;
event->timestamp = (uint64_t)xev.xany.serial;
switch (xev.type) {
case Expose:
event->type = WM_BACKEND_EVENT_EXPOSE;
event->data.configure.x = xev.xexpose.x;
event->data.configure.y = xev.xexpose.y;
event->data.configure.width = xev.xexpose.width;
event->data.configure.height = xev.xexpose.height;
break;
case ConfigureNotify:
event->type = WM_BACKEND_EVENT_CONFIGURE;
event->data.configure.x = xev.xconfigure.x;
event->data.configure.y = xev.xconfigure.y;
event->data.configure.width = xev.xconfigure.width;
event->data.configure.height = xev.xconfigure.height;
break;
case MapNotify:
event->type = WM_BACKEND_EVENT_MAP;
break;
case UnmapNotify:
event->type = WM_BACKEND_EVENT_UNMAP;
break;
case DestroyNotify:
event->type = WM_BACKEND_EVENT_DESTROY;
break;
case FocusIn:
event->type = WM_BACKEND_EVENT_FOCUS_IN;
break;
case FocusOut:
event->type = WM_BACKEND_EVENT_FOCUS_OUT;
break;
case KeyPress:
event->type = WM_BACKEND_EVENT_KEY_PRESS;
event->data.key.keycode = xev.xkey.keycode;
event->data.key.state = xev.xkey.state;
break;
case KeyRelease:
event->type = WM_BACKEND_EVENT_KEY_RELEASE;
event->data.key.keycode = xev.xkey.keycode;
event->data.key.state = xev.xkey.state;
break;
case ButtonPress:
event->type = WM_BACKEND_EVENT_BUTTON_PRESS;
event->data.button.button = xev.xbutton.button;
event->data.button.x = xev.xbutton.x;
event->data.button.y = xev.xbutton.y;
event->data.button.state = xev.xbutton.state;
break;
case ButtonRelease:
event->type = WM_BACKEND_EVENT_BUTTON_RELEASE;
event->data.button.button = xev.xbutton.button;
event->data.button.x = xev.xbutton.x;
event->data.button.y = xev.xbutton.y;
event->data.button.state = xev.xbutton.state;
break;
case MotionNotify:
event->type = WM_BACKEND_EVENT_MOTION;
event->data.button.x = xev.xmotion.x;
event->data.button.y = xev.xmotion.y;
event->data.button.state = xev.xmotion.state;
break;
case EnterNotify:
event->type = WM_BACKEND_EVENT_ENTER;
break;
case LeaveNotify:
event->type = WM_BACKEND_EVENT_LEAVE;
break;
case PropertyNotify:
event->type = WM_BACKEND_EVENT_PROPERTY;
event->data.property.name = XGetAtomName(x11_state.display, xev.xproperty.atom);
break;
case ClientMessage:
event->type = WM_BACKEND_EVENT_CLIENT_MESSAGE;
break;
default:
event->type = WM_BACKEND_EVENT_NONE;
break;
}
return true;
}
static void x11_wait_event(WmBackendEvent *event) {
if (!x11_state.display || !event) return;
while (!x11_poll_event(event)) {
/* Wait for event */
}
}
/*==============================================================================
* Input - Grabbing
*============================================================================*/
static void x11_grab_key(int keycode, uint32_t modifiers, WmWindowHandle window,
bool owner_events, uint32_t pointer_mode, uint32_t keyboard_mode) {
if (!x11_state.display) return;
XGrabKey(x11_state.display, keycode, modifiers, (Window)window,
owner_events, pointer_mode, keyboard_mode);
}
static void x11_ungrab_key(int keycode, uint32_t modifiers, WmWindowHandle window) {
if (!x11_state.display) return;
XUngrabKey(x11_state.display, keycode, modifiers, (Window)window);
}
static void x11_grab_button(int button, uint32_t modifiers, WmWindowHandle window,
bool owner_events, uint32_t event_mask,
uint32_t pointer_mode, uint32_t keyboard_mode,
WmWindowHandle confine_to, uint32_t cursor) {
if (!x11_state.display) return;
XGrabButton(x11_state.display, button, modifiers, (Window)window,
owner_events, event_mask, pointer_mode, keyboard_mode,
(Window)confine_to, cursor);
}
static void x11_ungrab_button(int button, uint32_t modifiers, WmWindowHandle window) {
if (!x11_state.display) return;
XUngrabButton(x11_state.display, button, modifiers, (Window)window);
}
static void x11_grab_pointer(WmWindowHandle window, bool owner_events, uint32_t event_mask,
uint32_t pointer_mode, uint32_t keyboard_mode,
WmWindowHandle confine_to, uint32_t cursor, WmTime time) {
if (!x11_state.display) return;
XGrabPointer(x11_state.display, (Window)window, owner_events, event_mask,
pointer_mode, keyboard_mode, (Window)confine_to,
(Cursor)cursor, (Time)time);
}
static void x11_ungrab_pointer(WmTime time) {
if (!x11_state.display) return;
XUngrabPointer(x11_state.display, (Time)time);
}
/*==============================================================================
* Input - Pointer
*============================================================================*/
static void x11_query_pointer(WmWindowHandle window, int *root_x, int *root_y,
int *win_x, int *win_y, uint32_t *mask) {
if (!x11_state.display) return;
Window root, child;
int rx, ry, wx, wy;
unsigned int m;
XQueryPointer(x11_state.display, (Window)window, &root, &child,
&rx, &ry, &wx, &wy, &m);
if (root_x) *root_x = rx;
if (root_y) *root_y = ry;
if (win_x) *win_x = wx;
if (win_y) *win_y = wy;
if (mask) *mask = m;
}
static void x11_warp_pointer(WmWindowHandle dest_w, int dest_x, int dest_y) {
if (!x11_state.display) return;
XWarpPointer(x11_state.display, None, (Window)dest_w, 0, 0, 0, 0, dest_x, dest_y);
}
/*==============================================================================
* Atoms/Properties
*============================================================================*/
static uint32_t x11_atom_get(const char *name) {
if (!x11_state.display || !name) return 0;
return (uint32_t)XInternAtom(x11_state.display, name, False);
}
static const char* x11_atom_get_name(uint32_t atom) {
if (!x11_state.display || atom == 0) return NULL;
return XGetAtomName(x11_state.display, (Atom)atom);
}
/*==============================================================================
* Backend Interface
*============================================================================*/
static const BackendInterface x11_backend_interface = {
.name = "x11",
.get_info = x11_get_info,
.init = x11_init,
.shutdown = x11_shutdown,
.is_initialized = x11_is_initialized,
.connect = x11_connect,
.disconnect = x11_disconnect,
.is_connected = x11_is_connected,
.get_file_descriptor = x11_get_file_descriptor,
.flush = x11_flush,
.sync = x11_sync,
.get_screen_dimensions = x11_get_screen_dimensions,
.get_screen_count = x11_get_screen_count,
.get_screen_geometry = x11_get_screen_geometry,
.window_get_geometry = x11_window_get_geometry,
.window_set_geometry = x11_window_set_geometry,
.window_move = x11_window_move,
.window_resize = x11_window_resize,
.window_move_resize = x11_window_move_resize,
.window_show = x11_window_show,
.window_hide = x11_window_hide,
.window_is_visible = x11_window_is_visible,
.window_map = x11_window_map,
.window_unmap = x11_window_unmap,
.window_is_mapped = x11_window_is_mapped,
.window_raise = x11_window_raise,
.window_lower = x11_window_lower,
.window_reparent = x11_window_reparent,
.window_focus = x11_window_focus,
.window_unfocus = x11_window_unfocus,
.window_is_focused = x11_window_is_focused,
.window_get_focused = x11_window_get_focused,
.window_set_border_width = x11_window_set_border_width,
.window_get_border_width = x11_window_get_border_width,
.window_set_title = x11_window_set_title,
.window_get_title = x11_window_get_title,
.window_supports_protocol = x11_window_supports_protocol,
.window_close = x11_window_close,
.window_kill = x11_window_kill,
.window_set_fullscreen = x11_window_set_fullscreen,
.window_set_urgent = x11_window_set_urgent,
.window_is_urgent = x11_window_is_urgent,
.poll_event = x11_poll_event,
.wait_event = x11_wait_event,
.grab_key = x11_grab_key,
.ungrab_key = x11_ungrab_key,
.grab_button = x11_grab_button,
.ungrab_button = x11_ungrab_button,
.grab_pointer = x11_grab_pointer,
.ungrab_pointer = x11_ungrab_pointer,
.query_pointer = x11_query_pointer,
.warp_pointer = x11_warp_pointer,
.atom_get = x11_atom_get,
.atom_get_name = x11_atom_get_name,
};
const BackendInterface* x11_backend_get_interface(void) {
return &x11_backend_interface;
}
Display* x11_backend_get_display(void) {
return x11_state.display;
}
Window x11_backend_get_root(void) {
return x11_state.root;
}
int x11_backend_get_screen(void) {
return x11_state.screen;
}

366
src/channels.c Normal file
View File

@ -0,0 +1,366 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Channel Implementation - Thread-Safe Communication
*/
#include "threading.h"
#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/eventfd.h>
#include <unistd.h>
/* ============================================================================
* Channel Implementation
* ============================================================================ */
struct Channel {
uint32_t capacity;
uint32_t head;
uint32_t tail;
uint32_t count;
ChannelType type;
void **buffer;
pthread_mutex_t mutex;
pthread_cond_t not_full;
pthread_cond_t not_empty;
int event_fd; /* For select() integration */
_Atomic int closed;
_Atomic int waiters; /* Number of threads waiting */
};
Channel* channel_create(uint32_t capacity)
{
Channel *ch = dwn_malloc(sizeof(Channel));
if (!ch) return NULL;
ch->capacity = capacity;
ch->head = 0;
ch->tail = 0;
ch->count = 0;
ch->type = (capacity == 0) ? CHANNEL_UNBUFFERED : CHANNEL_BUFFERED;
ch->event_fd = -1;
atomic_store_explicit(&ch->closed, 0, ATOMIC_RELEASE);
atomic_store_explicit(&ch->waiters, 0, ATOMIC_RELEASE);
if (capacity > 0) {
ch->buffer = dwn_malloc(sizeof(void*) * capacity);
if (!ch->buffer) {
dwn_free(ch);
return NULL;
}
} else {
ch->buffer = NULL;
}
if (pthread_mutex_init(&ch->mutex, NULL) != 0) {
dwn_free(ch->buffer);
dwn_free(ch);
return NULL;
}
if (pthread_cond_init(&ch->not_full, NULL) != 0) {
pthread_mutex_destroy(&ch->mutex);
dwn_free(ch->buffer);
dwn_free(ch);
return NULL;
}
if (pthread_cond_init(&ch->not_empty, NULL) != 0) {
pthread_cond_destroy(&ch->not_full);
pthread_mutex_destroy(&ch->mutex);
dwn_free(ch->buffer);
dwn_free(ch);
return NULL;
}
/* Create eventfd for async notification */
ch->event_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (ch->event_fd < 0) {
pthread_cond_destroy(&ch->not_empty);
pthread_cond_destroy(&ch->not_full);
pthread_mutex_destroy(&ch->mutex);
dwn_free(ch->buffer);
dwn_free(ch);
return NULL;
}
return ch;
}
void channel_destroy(Channel *ch)
{
if (!ch) return;
channel_close(ch);
pthread_cond_destroy(&ch->not_empty);
pthread_cond_destroy(&ch->not_full);
pthread_mutex_destroy(&ch->mutex);
if (ch->event_fd >= 0) {
close(ch->event_fd);
}
dwn_free(ch->buffer);
dwn_free(ch);
}
static void channel_notify(Channel *ch)
{
/* Signal eventfd */
uint64_t one = 1;
ssize_t w __attribute__((unused)) = write(ch->event_fd, &one, sizeof(one));
}
static bool channel_is_full(Channel *ch)
{
return ch->count >= ch->capacity;
}
static bool channel_is_empty(Channel *ch)
{
return ch->count == 0;
}
static void channel_push_internal(Channel *ch, void *data)
{
if (ch->capacity > 0) {
ch->buffer[ch->head] = data;
ch->head = (ch->head + 1) % ch->capacity;
ch->count++;
}
}
static void* channel_pop_internal(Channel *ch)
{
if (ch->capacity > 0 && ch->count > 0) {
void *data = ch->buffer[ch->tail];
ch->tail = (ch->tail + 1) % ch->capacity;
ch->count--;
return data;
}
return NULL;
}
ThreadStatus channel_send(Channel *ch, void *data)
{
return channel_send_timeout(ch, data, 0);
}
ThreadStatus channel_send_timeout(Channel *ch, void *data, uint64_t timeout_ms)
{
if (!ch || !data) return THREAD_ERROR_INVALID;
pthread_mutex_lock(&ch->mutex);
if (atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_CLOSED;
}
/* Wait for space */
if (ch->capacity > 0) {
while (channel_is_full(ch) && !atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
atomic_fetch_add_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
if (timeout_ms == 0) {
pthread_cond_wait(&ch->not_full, &ch->mutex);
} else {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
int ret = pthread_cond_timedwait(&ch->not_full, &ch->mutex, &ts);
if (ret == ETIMEDOUT) {
atomic_fetch_sub_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_TIMEOUT;
}
}
atomic_fetch_sub_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
}
}
if (atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_CLOSED;
}
channel_push_internal(ch, data);
pthread_cond_signal(&ch->not_empty);
pthread_mutex_unlock(&ch->mutex);
channel_notify(ch);
return THREAD_OK;
}
ThreadStatus channel_try_send(Channel *ch, void *data)
{
if (!ch || !data) return THREAD_ERROR_INVALID;
if (pthread_mutex_trylock(&ch->mutex) != 0) {
return THREAD_ERROR_BUSY;
}
if (atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_CLOSED;
}
if (ch->capacity > 0 && channel_is_full(ch)) {
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_BUSY;
}
channel_push_internal(ch, data);
pthread_cond_signal(&ch->not_empty);
pthread_mutex_unlock(&ch->mutex);
channel_notify(ch);
return THREAD_OK;
}
ThreadStatus channel_recv(Channel *ch, void **data)
{
return channel_recv_timeout(ch, data, 0);
}
ThreadStatus channel_recv_timeout(Channel *ch, void **data, uint64_t timeout_ms)
{
if (!ch || !data) return THREAD_ERROR_INVALID;
pthread_mutex_lock(&ch->mutex);
/* Wait for data */
while (channel_is_empty(ch) && !atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
atomic_fetch_add_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
if (timeout_ms == 0) {
pthread_cond_wait(&ch->not_empty, &ch->mutex);
} else {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
int ret = pthread_cond_timedwait(&ch->not_empty, &ch->mutex, &ts);
if (ret == ETIMEDOUT) {
atomic_fetch_sub_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_TIMEOUT;
}
}
atomic_fetch_sub_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
}
if (channel_is_empty(ch)) {
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_CLOSED;
}
*data = channel_pop_internal(ch);
pthread_cond_signal(&ch->not_full);
pthread_mutex_unlock(&ch->mutex);
return THREAD_OK;
}
ThreadStatus channel_try_recv(Channel *ch, void **data)
{
if (!ch || !data) return THREAD_ERROR_INVALID;
if (pthread_mutex_trylock(&ch->mutex) != 0) {
return THREAD_ERROR_BUSY;
}
if (channel_is_empty(ch)) {
pthread_mutex_unlock(&ch->mutex);
return atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE) ?
THREAD_ERROR_CLOSED : THREAD_ERROR_BUSY;
}
*data = channel_pop_internal(ch);
pthread_cond_signal(&ch->not_full);
pthread_mutex_unlock(&ch->mutex);
return THREAD_OK;
}
void channel_close(Channel *ch)
{
if (!ch) return;
pthread_mutex_lock(&ch->mutex);
atomic_store_explicit(&ch->closed, 1, ATOMIC_RELEASE);
pthread_cond_broadcast(&ch->not_full);
pthread_cond_broadcast(&ch->not_empty);
pthread_mutex_unlock(&ch->mutex);
channel_notify(ch);
}
bool channel_is_closed(Channel *ch)
{
if (!ch) return true;
return atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE) != 0;
}
/* ============================================================================
* Select Implementation
* ============================================================================ */
int channel_select(Channel **channels, uint32_t count, uint64_t timeout_ms,
void **out_data)
{
if (!channels || count == 0) return -1;
uint64_t start_time = 0;
if (timeout_ms > 0) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
start_time = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
for (;;) {
/* Try non-blocking receive on all channels */
for (uint32_t i = 0; i < count; i++) {
if (channel_try_recv(channels[i], out_data) == THREAD_OK) {
return (int)i;
}
}
/* Check timeout */
if (timeout_ms > 0) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
uint64_t now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
if (now - start_time >= timeout_ms) {
return -1;
}
}
/* Short sleep to avoid busy-wait */
struct timespec sleep_ts = {0, 1000000}; /* 1ms */
nanosleep(&sleep_ts, NULL);
}
}

View File

@ -14,6 +14,8 @@
#include "layout.h"
#include "panel.h"
#include "api.h"
#include "rules.h"
#include "marks.h"
#include <string.h>
#include <stdlib.h>
@ -235,11 +237,25 @@ Client *client_manage(Window window)
client_sync_log("client_manage: syncing X");
XSync(dwn->display, False);
client_sync_log("client_manage: applying rules");
bool rules_applied = rules_apply_to_client(client);
if (rules_applied) {
client_configure(client);
if (client->workspace != (unsigned int)dwn->current_workspace) {
client_hide(client);
}
}
client_sync_log("client_manage: showing");
client_show(client);
if (client->workspace == (unsigned int)dwn->current_workspace) {
client_show(client);
}
client_sync_log("client_manage: focusing");
client_focus(client, true);
if (client->workspace == (unsigned int)dwn->current_workspace) {
client_focus(client, true);
}
api_emit_window_created(client->window, client->title, client->class, client->workspace);
@ -258,6 +274,8 @@ void client_unmanage(Client *client)
api_emit_window_destroyed(client->window, client->title, client->workspace);
marks_remove_client(client);
LOG_DEBUG("Unmanaging window: %lu", client->window);
if (dwn->alt_tab_client == client) {
@ -725,8 +743,10 @@ void client_update_title(Client *client)
}
char old_title[256];
strncpy(old_title, client->title, sizeof(old_title) - 1);
old_title[sizeof(old_title) - 1] = '\0';
size_t title_len = strlen(client->title);
if (title_len >= sizeof(old_title)) title_len = sizeof(old_title) - 1;
memcpy(old_title, client->title, title_len);
old_title[title_len] = '\0';
char *name = atoms_get_window_name(client->window);
if (name != NULL) {

View File

@ -97,6 +97,9 @@ void config_set_defaults(Config *cfg)
cfg->demo_ai_timeout_ms = 15000;
cfg->demo_window_timeout_ms = 5000;
cfg->fade_speed = 0.5f; /* Default moderate speed */
cfg->fade_intensity = 1.0f; /* Default full intensity */
strncpy(cfg->color_panel_bg, DEFAULT_PANEL_BG, sizeof(cfg->color_panel_bg) - 1);
strncpy(cfg->color_panel_fg, DEFAULT_PANEL_FG, sizeof(cfg->color_panel_fg) - 1);
strncpy(cfg->color_workspace_active, DEFAULT_WS_ACTIVE, sizeof(cfg->color_workspace_active) - 1);
@ -227,6 +230,14 @@ static void handle_config_entry(const char *section, const char *key,
int val = atoi(value);
cfg->demo_window_timeout_ms = (val >= 1000 && val <= 30000) ? val : 5000;
}
} else if (strcmp(section, "effects") == 0) {
if (strcmp(key, "fade_speed") == 0) {
float val = atof(value);
cfg->fade_speed = (val >= 0.1f && val <= 3.0f) ? val : 0.5f;
} else if (strcmp(key, "fade_intensity") == 0) {
float val = atof(value);
cfg->fade_intensity = (val >= 0.0f && val <= 1.0f) ? val : 1.0f;
}
} else if (strcmp(section, "colors") == 0) {
if (strcmp(key, "panel_bg") == 0) {
strncpy(cfg->color_panel_bg, value, sizeof(cfg->color_panel_bg) - 1);
@ -350,9 +361,53 @@ bool config_load(Config *cfg, const char *path)
LOG_INFO("AI features enabled");
}
/* Validate and clamp all config values to safe ranges */
config_validate(cfg);
return true;
}
void config_validate(Config *cfg)
{
if (cfg == NULL) return;
/* Panel height: 10-100 pixels */
if (cfg->panel_height < 10) cfg->panel_height = 10;
if (cfg->panel_height > 100) cfg->panel_height = 100;
/* Border width: 0-20 pixels */
if (cfg->border_width < 0) cfg->border_width = 0;
if (cfg->border_width > 20) cfg->border_width = 20;
/* Title height: 0-50 pixels */
if (cfg->title_height < 0) cfg->title_height = 0;
if (cfg->title_height > 50) cfg->title_height = 50;
/* Default master ratio: 0.1-0.9 */
if (cfg->default_master_ratio < 0.1f) cfg->default_master_ratio = 0.1f;
if (cfg->default_master_ratio > 0.9f) cfg->default_master_ratio = 0.9f;
/* Default master count: 1-8 */
if (cfg->default_master_count < 1) cfg->default_master_count = 1;
if (cfg->default_master_count > 8) cfg->default_master_count = 8;
/* Gap: 0-50 pixels */
if (cfg->gap < 0) cfg->gap = 0;
if (cfg->gap > 50) cfg->gap = 50;
/* Fade speed: 0.1-3.0 */
if (cfg->fade_speed < 0.1f) cfg->fade_speed = 0.1f;
if (cfg->fade_speed > 3.0f) cfg->fade_speed = 3.0f;
/* Fade intensity: 0.0-1.0 */
if (cfg->fade_intensity < 0.0f) cfg->fade_intensity = 0.0f;
if (cfg->fade_intensity > 1.0f) cfg->fade_intensity = 1.0f;
/* API port: 1024-65535 (avoid well-known ports) */
if (cfg->api_port < 1024) cfg->api_port = 8777;
if (cfg->api_port > 65535) cfg->api_port = 8777;
}
bool config_reload(Config *cfg)
{
if (cfg == NULL) {
@ -440,3 +495,37 @@ void config_init_colors(Config *cfg)
cfg->colors.notification_bg = parse_color(cfg->color_notification_bg);
cfg->colors.notification_fg = parse_color(cfg->color_notification_fg);
}
float config_get_fade_speed(void)
{
if (dwn != NULL && dwn->config != NULL) {
return dwn->config->fade_speed;
}
return 0.5f;
}
float config_get_fade_intensity(void)
{
if (dwn != NULL && dwn->config != NULL) {
return dwn->config->fade_intensity;
}
return 1.0f;
}
void config_set_fade_speed(float speed)
{
if (dwn != NULL && dwn->config != NULL) {
if (speed < 0.1f) speed = 0.1f;
if (speed > 3.0f) speed = 3.0f;
dwn->config->fade_speed = speed;
}
}
void config_set_fade_intensity(float intensity)
{
if (dwn != NULL && dwn->config != NULL) {
if (intensity < 0.0f) intensity = 0.0f;
if (intensity > 1.0f) intensity = 1.0f;
dwn->config->fade_intensity = intensity;
}
}

909
src/core/wm_client.c Normal file
View File

@ -0,0 +1,909 @@
/*
* DWN - Desktop Window Manager
* Abstract Client Implementation
*/
#include "core/wm_client.h"
#include "core/wm_hashmap.h"
#include "core/wm_list.h"
#include "client.h"
#include "dwn.h"
#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/*==============================================================================
* Internal Structures
*============================================================================*/
struct AbstractClient {
/* Core identification */
WmClientId id;
WmWindowHandle window;
WmWindowHandle frame;
WmClientType type;
/* Legacy client pointer (for compatibility during migration) */
Client *legacy;
bool owns_legacy;
/* State */
WmClientState state;
WmWorkspaceId workspace;
/* Geometry */
WmRect geometry;
WmRect saved_geometry;
int border_width;
/* Properties */
char title[256];
char class_name[64];
char instance_name[64];
WmColor color;
/* List links */
AbstractClient *next;
AbstractClient *prev;
AbstractClient *mru_next;
AbstractClient *mru_prev;
};
struct WmClientManager {
AbstractClient *clients; /* Global client list */
AbstractClient *clients_tail; /* Tail of global list */
AbstractClient *mru_head; /* Most recently used */
AbstractClient *mru_tail; /* Least recently used */
WmHashMap *by_window; /* Map: window handle -> client */
WmHashMap *by_frame; /* Map: frame handle -> client */
WmHashMap *by_id; /* Map: client ID -> client */
AbstractClient *focused;
WmClientId next_id;
int count;
};
/*==============================================================================
* Global State
*============================================================================*/
static WmClientManager *g_client_manager = NULL;
/*==============================================================================
* Internal Helpers
*============================================================================*/
static WmClientId generate_client_id(void) {
if (!g_client_manager) return 0;
return g_client_manager->next_id++;
}
static void link_client(AbstractClient *client) {
if (!g_client_manager || !client) return;
/* Add to global list */
if (g_client_manager->clients_tail) {
g_client_manager->clients_tail->next = client;
client->prev = g_client_manager->clients_tail;
g_client_manager->clients_tail = client;
} else {
g_client_manager->clients = client;
g_client_manager->clients_tail = client;
}
/* Add to MRU list (at head = most recent) */
if (g_client_manager->mru_head) {
client->mru_next = g_client_manager->mru_head;
g_client_manager->mru_head->mru_prev = client;
g_client_manager->mru_head = client;
} else {
g_client_manager->mru_head = client;
g_client_manager->mru_tail = client;
}
g_client_manager->count++;
}
static void unlink_client(AbstractClient *client) {
if (!g_client_manager || !client) return;
/* Remove from global list */
if (client->prev) {
client->prev->next = client->next;
} else {
g_client_manager->clients = client->next;
}
if (client->next) {
client->next->prev = client->prev;
} else {
g_client_manager->clients_tail = client->prev;
}
/* Remove from MRU list */
if (client->mru_prev) {
client->mru_prev->mru_next = client->mru_next;
} else {
g_client_manager->mru_head = client->mru_next;
}
if (client->mru_next) {
client->mru_next->mru_prev = client->mru_prev;
} else {
g_client_manager->mru_tail = client->mru_prev;
}
g_client_manager->count--;
}
/*==============================================================================
* Client Lifecycle
*============================================================================*/
AbstractClient* wm_client_create(WmWindowHandle window, WmClientType type) {
if (!window) return NULL;
AbstractClient *client = calloc(1, sizeof(AbstractClient));
if (!client) return NULL;
client->id = generate_client_id();
client->window = window;
client->type = type;
client->state = WM_CLIENT_STATE_NORMAL | WM_CLIENT_STATE_MAPPED;
client->workspace = 0;
client->border_width = 0;
client->color = 0;
/* Register with manager */
if (g_client_manager) {
link_client(client);
wm_hashmap_insert(g_client_manager->by_window, window, client);
int id_key = (int)client->id;
wm_hashmap_insert(g_client_manager->by_id, (void*)(intptr_t)id_key, client);
}
LOG_DEBUG("Created abstract client %u for window %p", client->id, window);
return client;
}
void wm_client_destroy(AbstractClient *client) {
if (!client) return;
LOG_DEBUG("Destroying abstract client %u", client->id);
/* Remove from manager */
if (g_client_manager) {
wm_hashmap_remove(g_client_manager->by_window, client->window);
if (client->frame) {
wm_hashmap_remove(g_client_manager->by_frame, client->frame);
}
int id_key = (int)client->id;
wm_hashmap_remove(g_client_manager->by_id, (void*)(intptr_t)id_key);
unlink_client(client);
/* Clear focus if this was focused */
if (g_client_manager->focused == client) {
g_client_manager->focused = NULL;
}
}
/* Destroy legacy client if we own it */
if (client->legacy && client->owns_legacy) {
client_destroy(client->legacy);
}
free(client);
}
bool wm_client_is_valid(const AbstractClient *client) {
return client != NULL &&
!(client->state & WM_CLIENT_STATE_DESTROYING);
}
/*==============================================================================
* Native Access
*============================================================================*/
WmWindowHandle wm_client_get_window(const AbstractClient *client) {
return client ? client->window : NULL;
}
WmWindowHandle wm_client_get_frame(const AbstractClient *client) {
return client ? client->frame : NULL;
}
Client* wm_client_get_legacy(const AbstractClient *client) {
return client ? client->legacy : NULL;
}
AbstractClient* wm_client_from_legacy(Client *legacy_client) {
if (!legacy_client) return NULL;
/* Check if we already have a wrapper for this client */
AbstractClient *existing = wm_client_find_by_window((WmWindowHandle)legacy_client->window);
if (existing) return existing;
/* Create wrapper */
AbstractClient *client = calloc(1, sizeof(AbstractClient));
if (!client) return NULL;
client->id = generate_client_id();
client->window = (WmWindowHandle)legacy_client->window;
client->frame = (WmWindowHandle)legacy_client->frame;
client->legacy = legacy_client;
client->owns_legacy = false;
/* Sync state from legacy */
client->workspace = legacy_client->workspace;
client->border_width = legacy_client->border_width;
client->geometry.x = legacy_client->x;
client->geometry.y = legacy_client->y;
client->geometry.width = legacy_client->width;
client->geometry.height = legacy_client->height;
size_t title_len = strlen(legacy_client->title);
if (title_len >= sizeof(client->title)) title_len = sizeof(client->title) - 1;
memcpy(client->title, legacy_client->title, title_len);
client->title[title_len] = '\0';
size_t class_len = strlen(legacy_client->class);
if (class_len >= sizeof(client->class_name)) class_len = sizeof(client->class_name) - 1;
memcpy(client->class_name, legacy_client->class, class_len);
client->class_name[class_len] = '\0';
client->color = legacy_client->taskbar_color;
/* Convert flags */
if (legacy_client->flags & CLIENT_FLOATING) {
client->state |= WM_CLIENT_STATE_FLOATING;
}
if (legacy_client->flags & CLIENT_FULLSCREEN) {
client->state |= WM_CLIENT_STATE_FULLSCREEN;
}
if (legacy_client->flags & CLIENT_URGENT) {
client->state |= WM_CLIENT_STATE_URGENT;
}
if (legacy_client->flags & CLIENT_MINIMIZED) {
client->state |= WM_CLIENT_STATE_MINIMIZED;
}
if (legacy_client->flags & CLIENT_STICKY) {
client->state |= WM_CLIENT_STATE_STICKY;
}
if (legacy_client->flags & CLIENT_MAXIMIZED) {
client->state |= WM_CLIENT_STATE_MAXIMIZED;
}
/* Register */
if (g_client_manager) {
link_client(client);
wm_hashmap_insert(g_client_manager->by_window, client->window, client);
if (client->frame) {
wm_hashmap_insert(g_client_manager->by_frame, client->frame, client);
}
int id_key = (int)client->id;
wm_hashmap_insert(g_client_manager->by_id, (void*)(intptr_t)id_key, client);
}
return client;
}
/*==============================================================================
* Client Identification
*============================================================================*/
WmClientId wm_client_get_id(const AbstractClient *client) {
return client ? client->id : 0;
}
WmClientType wm_client_get_type(const AbstractClient *client) {
return client ? client->type : WM_CLIENT_TYPE_UNKNOWN;
}
void wm_client_set_type(AbstractClient *client, WmClientType type) {
if (client) client->type = type;
}
/*==============================================================================
* Client State
*============================================================================*/
WmClientState wm_client_get_state(const AbstractClient *client) {
return client ? client->state : 0;
}
void wm_client_set_state(AbstractClient *client, WmClientState state) {
if (client) client->state = state;
}
void wm_client_add_state(AbstractClient *client, WmClientState state) {
if (client) client->state |= state;
}
void wm_client_remove_state(AbstractClient *client, WmClientState state) {
if (client) client->state &= ~state;
}
void wm_client_toggle_state(AbstractClient *client, WmClientState state) {
if (client) client->state ^= state;
}
bool wm_client_has_state(const AbstractClient *client, WmClientState state) {
return client && (client->state & state) != 0;
}
/*==============================================================================
* Client Geometry
*============================================================================*/
WmRect wm_client_get_geometry(const AbstractClient *client) {
if (!client) return (WmRect){0, 0, 0, 0};
/* Sync from legacy if available */
if (client->legacy) {
return (WmRect){
client->legacy->x,
client->legacy->y,
client->legacy->width,
client->legacy->height
};
}
return client->geometry;
}
void wm_client_set_geometry(AbstractClient *client, const WmRect *geometry) {
if (!client || !geometry) return;
client->geometry = *geometry;
/* Sync to legacy if available */
if (client->legacy) {
client->legacy->x = geometry->x;
client->legacy->y = geometry->y;
client->legacy->width = geometry->width;
client->legacy->height = geometry->height;
}
}
WmPoint wm_client_get_position(const AbstractClient *client) {
WmRect geom = wm_client_get_geometry(client);
return (WmPoint){geom.x, geom.y};
}
void wm_client_set_position(AbstractClient *client, int x, int y) {
if (!client) return;
client->geometry.x = x;
client->geometry.y = y;
if (client->legacy) {
client->legacy->x = x;
client->legacy->y = y;
}
}
WmSize wm_client_get_size(const AbstractClient *client) {
WmRect geom = wm_client_get_geometry(client);
return (WmSize){geom.width, geom.height};
}
void wm_client_set_size(AbstractClient *client, int width, int height) {
if (!client) return;
client->geometry.width = width;
client->geometry.height = height;
if (client->legacy) {
client->legacy->width = width;
client->legacy->height = height;
}
}
int wm_client_get_border_width(const AbstractClient *client) {
if (!client) return 0;
if (client->legacy) return client->legacy->border_width;
return client->border_width;
}
void wm_client_set_border_width(AbstractClient *client, int width) {
if (!client) return;
client->border_width = width;
if (client->legacy) {
client->legacy->border_width = width;
}
}
void wm_client_save_geometry(AbstractClient *client) {
if (!client) return;
client->saved_geometry = wm_client_get_geometry(client);
if (client->legacy) {
client->legacy->old_x = client->saved_geometry.x;
client->legacy->old_y = client->saved_geometry.y;
client->legacy->old_width = client->saved_geometry.width;
client->legacy->old_height = client->saved_geometry.height;
}
}
void wm_client_restore_geometry(AbstractClient *client) {
if (!client) return;
wm_client_set_geometry(client, &client->saved_geometry);
}
/*==============================================================================
* Client Workspace
*============================================================================*/
WmWorkspaceId wm_client_get_workspace(const AbstractClient *client) {
return client ? client->workspace : 0;
}
void wm_client_set_workspace(AbstractClient *client, WmWorkspaceId workspace) {
if (!client) return;
client->workspace = workspace;
if (client->legacy) {
client->legacy->workspace = workspace;
}
}
bool wm_client_is_on_workspace(const AbstractClient *client, WmWorkspaceId workspace) {
if (!client) return false;
if (client->state & WM_CLIENT_STATE_STICKY) return true;
return client->workspace == workspace;
}
bool wm_client_is_visible_on_current(const AbstractClient *client) {
if (!client || !dwn) return false;
return wm_client_is_on_workspace(client, dwn->current_workspace);
}
/*==============================================================================
* Client Properties
*============================================================================*/
const char* wm_client_get_title(const AbstractClient *client) {
if (!client) return "";
if (client->legacy) return client->legacy->title;
return client->title;
}
void wm_client_set_title(AbstractClient *client, const char *title) {
if (!client || !title) return;
strncpy(client->title, title, sizeof(client->title) - 1);
client->title[sizeof(client->title) - 1] = '\0';
if (client->legacy) {
strncpy(client->legacy->title, title, sizeof(client->legacy->title) - 1);
client->legacy->title[sizeof(client->legacy->title) - 1] = '\0';
}
}
const char* wm_client_get_class(const AbstractClient *client) {
if (!client) return "";
if (client->legacy) return client->legacy->class;
return client->class_name;
}
void wm_client_set_class(AbstractClient *client, const char *class_name) {
if (!client || !class_name) return;
strncpy(client->class_name, class_name, sizeof(client->class_name) - 1);
client->class_name[sizeof(client->class_name) - 1] = '\0';
if (client->legacy) {
strncpy(client->legacy->class, class_name, sizeof(client->legacy->class) - 1);
client->legacy->class[sizeof(client->legacy->class) - 1] = '\0';
}
}
const char* wm_client_get_instance(const AbstractClient *client) {
if (!client) return "";
return client->instance_name;
}
void wm_client_set_instance(AbstractClient *client, const char *instance) {
if (!client || !instance) return;
strncpy(client->instance_name, instance, sizeof(client->instance_name) - 1);
client->instance_name[sizeof(client->instance_name) - 1] = '\0';
}
WmColor wm_client_get_color(const AbstractClient *client) {
if (!client) return 0;
if (client->legacy) return client->legacy->taskbar_color;
return client->color;
}
void wm_client_set_color(AbstractClient *client, WmColor color) {
if (!client) return;
client->color = color;
if (client->legacy) {
client->legacy->taskbar_color = color;
}
}
/*==============================================================================
* Client Actions (delegate to legacy for now)
*============================================================================*/
void wm_client_focus(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_focus(client->legacy, true);
}
client->state |= WM_CLIENT_STATE_FOCUS;
if (g_client_manager) {
wm_client_manager_touch(g_client_manager, client);
g_client_manager->focused = client;
}
}
void wm_client_unfocus(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_unfocus(client->legacy);
}
client->state &= ~WM_CLIENT_STATE_FOCUS;
if (g_client_manager && g_client_manager->focused == client) {
g_client_manager->focused = NULL;
}
}
void wm_client_raise(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_raise(client->legacy);
}
}
void wm_client_lower(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_lower(client->legacy);
}
}
void wm_client_show(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_show(client->legacy);
}
client->state |= WM_CLIENT_STATE_MAPPED;
}
void wm_client_hide(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_hide(client->legacy);
}
client->state &= ~WM_CLIENT_STATE_MAPPED;
}
void wm_client_minimize(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_minimize(client->legacy);
}
client->state |= WM_CLIENT_STATE_MINIMIZED;
}
void wm_client_restore(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_restore(client->legacy);
}
client->state &= ~WM_CLIENT_STATE_MINIMIZED;
}
void wm_client_close(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_close(client->legacy);
}
}
void wm_client_kill(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_kill(client->legacy);
}
}
/*==============================================================================
* Client Management
*============================================================================*/
void wm_client_reparent(AbstractClient *client, WmWindowHandle frame) {
if (!client) return;
client->frame = frame;
/* Update frame lookup */
if (g_client_manager) {
if (client->frame) {
wm_hashmap_remove(g_client_manager->by_frame, client->frame);
}
wm_hashmap_insert(g_client_manager->by_frame, frame, client);
}
if (client->legacy) {
/* Legacy doesn't have a direct reparent function for frames */
}
}
void wm_client_unreparent(AbstractClient *client) {
if (!client) return;
if (g_client_manager && client->frame) {
wm_hashmap_remove(g_client_manager->by_frame, client->frame);
}
client->frame = NULL;
}
void wm_client_configure(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_configure(client->legacy);
}
}
void wm_client_apply_size_hints(AbstractClient *client, int *width, int *height) {
if (!client || !width || !height) return;
if (client->legacy) {
client_apply_size_hints(client->legacy, width, height);
}
}
void wm_client_move_resize(AbstractClient *client, int x, int y, int width, int height) {
if (!client) return;
client->geometry.x = x;
client->geometry.y = y;
client->geometry.width = width;
client->geometry.height = height;
if (client->legacy) {
client_move_resize(client->legacy, x, y, width, height);
}
}
/*==============================================================================
* Client List Management
*============================================================================*/
AbstractClient* wm_client_get_next(const AbstractClient *client) {
return client ? client->next : NULL;
}
AbstractClient* wm_client_get_prev(const AbstractClient *client) {
return client ? client->prev : NULL;
}
AbstractClient* wm_client_get_first(void) {
return g_client_manager ? g_client_manager->clients : NULL;
}
AbstractClient* wm_client_get_last(void) {
return g_client_manager ? g_client_manager->clients_tail : NULL;
}
AbstractClient* wm_client_get_next_mru(const AbstractClient *client) {
return client ? client->mru_next : NULL;
}
AbstractClient* wm_client_get_prev_mru(const AbstractClient *client) {
return client ? client->mru_prev : NULL;
}
/*==============================================================================
* Client Lookup
*============================================================================*/
AbstractClient* wm_client_find_by_window(WmWindowHandle window) {
if (!g_client_manager || !window) return NULL;
return wm_hashmap_get(g_client_manager->by_window, window);
}
AbstractClient* wm_client_find_by_frame(WmWindowHandle frame) {
if (!g_client_manager || !frame) return NULL;
return wm_hashmap_get(g_client_manager->by_frame, frame);
}
AbstractClient* wm_client_find_by_id(WmClientId id) {
if (!g_client_manager) return NULL;
return wm_hashmap_get(g_client_manager->by_id, (void*)(intptr_t)id);
}
/*==============================================================================
* Client Iteration
*============================================================================*/
void wm_client_foreach(WmClientForeachFunc func, void *user_data) {
if (!func) return;
AbstractClient *client = wm_client_get_first();
while (client) {
AbstractClient *next = client->next;
if (!func(client, user_data)) break;
client = next;
}
}
typedef struct {
WmWorkspaceId workspace;
WmClientForeachFunc func;
void *user_data;
} ForeachWorkspaceData;
static bool foreach_workspace_wrapper(AbstractClient *client, void *data) {
ForeachWorkspaceData *fwd = data;
if (wm_client_is_on_workspace(client, fwd->workspace)) {
return fwd->func(client, fwd->user_data);
}
return true;
}
void wm_client_foreach_on_workspace(WmWorkspaceId workspace,
WmClientForeachFunc func, void *user_data) {
ForeachWorkspaceData data = { workspace, func, user_data };
wm_client_foreach(foreach_workspace_wrapper, &data);
}
int wm_client_count(void) {
return g_client_manager ? g_client_manager->count : 0;
}
typedef struct {
int count;
WmWorkspaceId workspace;
} CountWorkspaceData;
static bool count_workspace(AbstractClient *client, void *data) {
CountWorkspaceData *cwd = data;
if (wm_client_is_on_workspace(client, cwd->workspace)) {
cwd->count++;
}
return true;
}
int wm_client_count_on_workspace(WmWorkspaceId workspace) {
CountWorkspaceData data = { 0, workspace };
wm_client_foreach(count_workspace, &data);
return data.count;
}
/*==============================================================================
* Client Manager
*============================================================================*/
WmClientManager* wm_client_manager_get(void) {
return g_client_manager;
}
bool wm_client_manager_init(void) {
if (g_client_manager) return true;
g_client_manager = calloc(1, sizeof(WmClientManager));
if (!g_client_manager) return false;
g_client_manager->by_window = wm_hashmap_new();
g_client_manager->by_frame = wm_hashmap_new();
g_client_manager->by_id = wm_hashmap_new();
if (!g_client_manager->by_window ||
!g_client_manager->by_frame ||
!g_client_manager->by_id) {
wm_client_manager_shutdown();
return false;
}
g_client_manager->next_id = 1;
LOG_INFO("Client manager initialized");
return true;
}
void wm_client_manager_shutdown(void) {
if (!g_client_manager) return;
/* Destroy all abstract clients (but not legacy ones - we don't own them) */
AbstractClient *client = g_client_manager->clients;
while (client) {
AbstractClient *next = client->next;
if (client->owns_legacy) {
client_destroy(client->legacy);
}
free(client);
client = next;
}
wm_hashmap_destroy(g_client_manager->by_window);
wm_hashmap_destroy(g_client_manager->by_frame);
wm_hashmap_destroy(g_client_manager->by_id);
free(g_client_manager);
g_client_manager = NULL;
LOG_INFO("Client manager shutdown");
}
void wm_client_manager_add(WmClientManager *mgr, AbstractClient *client) {
if (!mgr || !client) return;
/* Client is already linked when created */
}
void wm_client_manager_remove(WmClientManager *mgr, AbstractClient *client) {
if (!mgr || !client) return;
/* Client is already unlinked when destroyed */
}
void wm_client_manager_touch(WmClientManager *mgr, AbstractClient *client) {
if (!mgr || !client) return;
/* Already at head - nothing to do */
if (mgr->mru_head == client) return;
/* Remove from current position */
if (client->mru_prev) {
client->mru_prev->mru_next = client->mru_next;
}
if (client->mru_next) {
client->mru_next->mru_prev = client->mru_prev;
}
if (mgr->mru_tail == client) {
mgr->mru_tail = client->mru_prev;
}
/* Insert at head */
client->mru_prev = NULL;
client->mru_next = mgr->mru_head;
if (mgr->mru_head) {
mgr->mru_head->mru_prev = client;
}
mgr->mru_head = client;
}
AbstractClient* wm_client_manager_get_focused(const WmClientManager *mgr) {
return mgr ? mgr->focused : NULL;
}
void wm_client_manager_set_focused(WmClientManager *mgr, AbstractClient *client) {
if (!mgr) return;
mgr->focused = client;
}

416
src/core/wm_hashmap.c Normal file
View File

@ -0,0 +1,416 @@
/*
* DWN - Desktop Window Manager
* Abstract Hash Map Implementation
*/
#include "core/wm_hashmap.h"
#include "core/wm_string.h"
#include <stdlib.h>
#include <string.h>
/*==============================================================================
* Constants
*============================================================================*/
#define WM_HASHMAP_INITIAL_CAPACITY 16
#define WM_HASHMAP_LOAD_FACTOR 0.75
/*==============================================================================
* Entry Structure
*============================================================================*/
typedef struct WmHashMapEntry {
uint32_t hash;
void *key;
void *value;
struct WmHashMapEntry *next;
} WmHashMapEntry;
/*==============================================================================
* Hash Map Structure
*============================================================================*/
struct WmHashMap {
WmHashMapEntry **buckets;
size_t bucket_count;
size_t size;
WmHashFunc hash_func;
WmKeyEqualFunc key_equal;
WmFreeFunc key_free;
WmFreeFunc value_free;
};
/*==============================================================================
* Internal Helpers
*============================================================================*/
static void hashmap_clone_insert(void *key, void *value, void *user_data) {
wm_hashmap_insert((WmHashMap *)user_data, key, value);
}
/*==============================================================================
* Default Hash Functions
*============================================================================*/
static uint32_t wm_hash_pointer(const void *key) {
/* FNV-1a hash for pointer */
uintptr_t ptr = (uintptr_t)key;
uint32_t hash = 2166136261u;
for (size_t i = 0; i < sizeof(ptr); i++) {
hash ^= (ptr >> (i * 8)) & 0xFF;
hash *= 16777619u;
}
return hash;
}
static uint32_t wm_hash_string(const void *key) {
const char *str = key;
uint32_t hash = 2166136261u;
while (*str) {
hash ^= (uint8_t)*str++;
hash *= 16777619u;
}
return hash;
}
static bool wm_equal_pointer(const void *a, const void *b) {
return a == b;
}
static bool wm_equal_string(const void *a, const void *b) {
return strcmp((const char*)a, (const char*)b) == 0;
}
/*==============================================================================
* Internal Helpers
*============================================================================*/
static bool wm_hashmap_resize(WmHashMap *map) {
size_t old_count = map->bucket_count;
WmHashMapEntry **old_buckets = map->buckets;
size_t new_count = old_count * 2;
WmHashMapEntry **new_buckets = calloc(new_count, sizeof(WmHashMapEntry*));
if (!new_buckets) return false;
map->buckets = new_buckets;
map->bucket_count = new_count;
/* Rehash all entries */
for (size_t i = 0; i < old_count; i++) {
WmHashMapEntry *entry = old_buckets[i];
while (entry) {
WmHashMapEntry *next = entry->next;
size_t new_index = entry->hash % new_count;
entry->next = new_buckets[new_index];
new_buckets[new_index] = entry;
entry = next;
}
}
free(old_buckets);
return true;
}
/*==============================================================================
* Lifecycle
*============================================================================*/
WmHashMap* wm_hashmap_new(void) {
return wm_hashmap_new_full(NULL, NULL, NULL, NULL);
}
WmHashMap* wm_hashmap_new_full(WmHashFunc hash_func, WmKeyEqualFunc key_equal,
WmFreeFunc key_free, WmFreeFunc value_free) {
WmHashMap *map = calloc(1, sizeof(WmHashMap));
if (!map) return NULL;
map->bucket_count = WM_HASHMAP_INITIAL_CAPACITY;
map->buckets = calloc(map->bucket_count, sizeof(WmHashMapEntry*));
if (!map->buckets) {
free(map);
return NULL;
}
map->hash_func = hash_func ? hash_func : wm_hash_pointer;
map->key_equal = key_equal ? key_equal : wm_equal_pointer;
map->key_free = key_free;
map->value_free = value_free;
return map;
}
WmHashMap* wm_hashmap_new_string_key(void) {
return wm_hashmap_new_full(wm_hash_string, wm_equal_string, free, NULL);
}
void wm_hashmap_destroy(WmHashMap *map) {
if (!map) return;
wm_hashmap_clear(map);
free(map->buckets);
free(map);
}
WmHashMap* wm_hashmap_clone(const WmHashMap *map) {
if (!map) return NULL;
WmHashMap *clone = wm_hashmap_new_full(map->hash_func, map->key_equal,
NULL, NULL);
if (!clone) return NULL;
/* Note: This creates a shallow copy */
wm_hashmap_foreach(map, hashmap_clone_insert, clone);
clone->key_free = map->key_free;
clone->value_free = map->value_free;
return clone;
}
/*==============================================================================
* Capacity
*============================================================================*/
size_t wm_hashmap_size(const WmHashMap *map) {
return map ? map->size : 0;
}
bool wm_hashmap_is_empty(const WmHashMap *map) {
return !map || map->size == 0;
}
void wm_hashmap_clear(WmHashMap *map) {
if (!map) return;
for (size_t i = 0; i < map->bucket_count; i++) {
WmHashMapEntry *entry = map->buckets[i];
while (entry) {
WmHashMapEntry *next = entry->next;
if (map->key_free && entry->key) map->key_free(entry->key);
if (map->value_free && entry->value) map->value_free(entry->value);
free(entry);
entry = next;
}
map->buckets[i] = NULL;
}
map->size = 0;
}
/*==============================================================================
* Element Access
*============================================================================*/
void* wm_hashmap_get(const WmHashMap *map, const void *key) {
if (!map || !key) return NULL;
uint32_t hash = map->hash_func(key);
size_t index = hash % map->bucket_count;
WmHashMapEntry *entry = map->buckets[index];
while (entry) {
if (entry->hash == hash && map->key_equal(entry->key, key)) {
return entry->value;
}
entry = entry->next;
}
return NULL;
}
void* wm_hashmap_get_or_default(const WmHashMap *map, const void *key, void *default_val) {
void *val = wm_hashmap_get(map, key);
return val ? val : default_val;
}
bool wm_hashmap_contains(const WmHashMap *map, const void *key) {
return wm_hashmap_get(map, key) != NULL;
}
/*==============================================================================
* Modifiers
*============================================================================*/
bool wm_hashmap_insert(WmHashMap *map, void *key, void *value) {
if (!map || !key) return false;
/* Check if resize needed */
if ((double)map->size / map->bucket_count > WM_HASHMAP_LOAD_FACTOR) {
if (!wm_hashmap_resize(map)) return false;
}
uint32_t hash = map->hash_func(key);
size_t index = hash % map->bucket_count;
/* Check if key exists */
WmHashMapEntry *entry = map->buckets[index];
while (entry) {
if (entry->hash == hash && map->key_equal(entry->key, key)) {
/* Update existing entry */
if (map->value_free) map->value_free(entry->value);
entry->value = value;
return true;
}
entry = entry->next;
}
/* Insert new entry */
entry = malloc(sizeof(WmHashMapEntry));
if (!entry) return false;
entry->hash = hash;
entry->key = key;
entry->value = value;
entry->next = map->buckets[index];
map->buckets[index] = entry;
map->size++;
return true;
}
bool wm_hashmap_insert_no_replace(WmHashMap *map, void *key, void *value) {
if (!map || !key) return false;
if (wm_hashmap_contains(map, key)) return false;
return wm_hashmap_insert(map, key, value);
}
void* wm_hashmap_set(WmHashMap *map, void *key, void *value) {
if (!map || !key) return NULL;
void *old_value = wm_hashmap_get(map, key);
wm_hashmap_insert(map, key, value);
return old_value;
}
void* wm_hashmap_remove(WmHashMap *map, const void *key) {
if (!map || !key) return NULL;
uint32_t hash = map->hash_func(key);
size_t index = hash % map->bucket_count;
WmHashMapEntry *entry = map->buckets[index];
WmHashMapEntry *prev = NULL;
while (entry) {
if (entry->hash == hash && map->key_equal(entry->key, key)) {
void *value = entry->value;
if (prev) {
prev->next = entry->next;
} else {
map->buckets[index] = entry->next;
}
if (map->key_free) map->key_free(entry->key);
free(entry);
map->size--;
return value;
}
prev = entry;
entry = entry->next;
}
return NULL;
}
bool wm_hashmap_steal(WmHashMap *map, const void *key) {
return wm_hashmap_remove(map, key) != NULL;
}
/*==============================================================================
* Iteration
*============================================================================*/
typedef struct WmHashMapIterInternal {
WmHashMap *map;
size_t bucket;
WmHashMapEntry *entry;
WmHashMapEntry *next_entry;
} WmHashMapIterInternal;
void wm_hashmap_iter_init(WmHashMapIter *iter, WmHashMap *map) {
if (!iter) return;
iter->map = map;
iter->bucket = 0;
iter->entry = NULL;
iter->next_entry = NULL;
if (!map) return;
/* Find first bucket with entry */
for (size_t i = 0; i < map->bucket_count; i++) {
if (map->buckets[i]) {
iter->bucket = i;
iter->entry = map->buckets[i];
iter->next_entry = ((WmHashMapEntry*)iter->entry)->next;
break;
}
}
}
bool wm_hashmap_iter_next(WmHashMapIter *iter, void **key, void **value) {
if (!iter || !iter->entry) return false;
WmHashMapEntry *entry = (WmHashMapEntry*)iter->entry;
*key = entry->key;
*value = entry->value;
/* Move to next entry */
entry = entry->next;
if (!entry) {
/* Find next bucket with entries */
WmHashMap *map = (WmHashMap*)iter->map;
for (size_t i = iter->bucket + 1; i < map->bucket_count; i++) {
if (map->buckets[i]) {
iter->bucket = i;
entry = map->buckets[i];
break;
}
}
}
iter->entry = entry;
return true;
}
void wm_hashmap_foreach(const WmHashMap *map, WmHashForeachFunc func, void *user_data) {
if (!map || !func) return;
for (size_t i = 0; i < map->bucket_count; i++) {
WmHashMapEntry *entry = map->buckets[i];
while (entry) {
func(entry->key, entry->value, user_data);
entry = entry->next;
}
}
}
/*==============================================================================
* String Helpers
*============================================================================*/
bool wm_hashmap_insert_string(WmHashMap *map, const char *key, void *value) {
if (!map || !key) return false;
char *key_copy = strdup(key);
if (!key_copy) return false;
if (!wm_hashmap_insert(map, key_copy, value)) {
free(key_copy);
return false;
}
return true;
}
void* wm_hashmap_get_string(const WmHashMap *map, const char *key) {
return wm_hashmap_get(map, key);
}
bool wm_hashmap_contains_string(const WmHashMap *map, const char *key) {
return wm_hashmap_contains(map, key);
}
void* wm_hashmap_remove_string(WmHashMap *map, const char *key) {
return wm_hashmap_remove(map, key);
}

432
src/core/wm_list.c Normal file
View File

@ -0,0 +1,432 @@
/*
* DWN - Desktop Window Manager
* Abstract List Implementation (Dynamic Array)
*/
#include "core/wm_list.h"
#include "core/wm_types.h"
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
/*==============================================================================
* List Structure
*============================================================================*/
struct WmList {
void **data;
size_t size;
size_t capacity;
WmFreeFunc free_func;
};
/*==============================================================================
* Lifecycle
*============================================================================*/
WmList* wm_list_new(void) {
WmList *list = calloc(1, sizeof(WmList));
if (!list) return NULL;
list->capacity = 8;
list->data = malloc(list->capacity * sizeof(void*));
if (!list->data) {
free(list);
return NULL;
}
return list;
}
WmList* wm_list_new_sized(size_t initial_capacity) {
WmList *list = calloc(1, sizeof(WmList));
if (!list) return NULL;
list->capacity = initial_capacity > 0 ? initial_capacity : 8;
list->data = malloc(list->capacity * sizeof(void*));
if (!list->data) {
free(list);
return NULL;
}
return list;
}
void wm_list_destroy(WmList *list) {
if (!list) return;
if (list->free_func) {
for (size_t i = 0; i < list->size; i++) {
list->free_func(list->data[i]);
}
}
free(list->data);
free(list);
}
void wm_list_destroy_no_free(WmList *list) {
if (!list) return;
free(list->data);
free(list);
}
WmList* wm_list_clone(const WmList *list) {
if (!list) return NULL;
WmList *clone = wm_list_new_sized(list->capacity);
if (!clone) return NULL;
memcpy(clone->data, list->data, list->size * sizeof(void*));
clone->size = list->size;
clone->free_func = list->free_func;
return clone;
}
void wm_list_set_free_func(WmList *list, WmFreeFunc func) {
if (list) list->free_func = func;
}
/*==============================================================================
* Capacity
*============================================================================*/
static bool wm_list_grow(WmList *list, size_t min_capacity) {
if (list->capacity >= min_capacity) return true;
size_t new_capacity = list->capacity * 2;
if (new_capacity < min_capacity) new_capacity = min_capacity;
void **new_data = realloc(list->data, new_capacity * sizeof(void*));
if (!new_data) return false;
list->data = new_data;
list->capacity = new_capacity;
return true;
}
size_t wm_list_size(const WmList *list) {
return list ? list->size : 0;
}
size_t wm_list_capacity(const WmList *list) {
return list ? list->capacity : 0;
}
bool wm_list_is_empty(const WmList *list) {
return !list || list->size == 0;
}
void wm_list_reserve(WmList *list, size_t capacity) {
if (!list) return;
wm_list_grow(list, capacity);
}
void wm_list_compact(WmList *list) {
if (!list || list->capacity <= list->size) return;
size_t new_capacity = list->size > 0 ? list->size : 1;
void **new_data = realloc(list->data, new_capacity * sizeof(void*));
if (new_data) {
list->data = new_data;
list->capacity = new_capacity;
}
}
void wm_list_clear(WmList *list) {
if (!list) return;
if (list->free_func) {
for (size_t i = 0; i < list->size; i++) {
list->free_func(list->data[i]);
}
}
list->size = 0;
}
/*==============================================================================
* Modifiers
*============================================================================*/
void wm_list_append(WmList *list, void *item) {
if (!list) return;
if (!wm_list_grow(list, list->size + 1)) return;
list->data[list->size++] = item;
}
void wm_list_prepend(WmList *list, void *item) {
wm_list_insert(list, 0, item);
}
void wm_list_insert(WmList *list, size_t index, void *item) {
if (!list || index > list->size) return;
if (!wm_list_grow(list, list->size + 1)) return;
memmove(&list->data[index + 1], &list->data[index],
(list->size - index) * sizeof(void*));
list->data[index] = item;
list->size++;
}
void wm_list_insert_sorted(WmList *list, void *item, WmCompareFunc compare) {
if (!list) return;
size_t i = 0;
while (i < list->size && compare(list->data[i], item) < 0) {
i++;
}
wm_list_insert(list, i, item);
}
void wm_list_remove(WmList *list, void *item) {
if (!list) return;
for (size_t i = 0; i < list->size; i++) {
if (list->data[i] == item) {
wm_list_remove_at(list, i);
return;
}
}
}
void wm_list_remove_at(WmList *list, size_t index) {
if (!list || index >= list->size) return;
if (list->free_func) {
list->free_func(list->data[index]);
}
memmove(&list->data[index], &list->data[index + 1],
(list->size - index - 1) * sizeof(void*));
list->size--;
}
void* wm_list_take_at(WmList *list, size_t index) {
if (!list || index >= list->size) return NULL;
void *item = list->data[index];
memmove(&list->data[index], &list->data[index + 1],
(list->size - index - 1) * sizeof(void*));
list->size--;
return item;
}
bool wm_list_remove_one(WmList *list, void *item) {
if (!list) return false;
for (size_t i = 0; i < list->size; i++) {
if (list->data[i] == item) {
wm_list_remove_at(list, i);
return true;
}
}
return false;
}
bool wm_list_remove_all(WmList *list, void *item) {
if (!list) return false;
bool removed = false;
for (size_t i = list->size; i-- > 0; ) {
if (list->data[i] == item) {
wm_list_remove_at(list, i);
removed = true;
}
}
return removed;
}
void* wm_list_pop_back(WmList *list) {
if (!list || list->size == 0) return NULL;
return list->data[--list->size];
}
void* wm_list_pop_front(WmList *list) {
return wm_list_take_at(list, 0);
}
/*==============================================================================
* Accessors
*============================================================================*/
void* wm_list_get(const WmList *list, size_t index) {
if (!list || index >= list->size) return NULL;
return list->data[index];
}
void* wm_list_first(const WmList *list) {
if (!list || list->size == 0) return NULL;
return list->data[0];
}
void* wm_list_last(const WmList *list) {
if (!list || list->size == 0) return NULL;
return list->data[list->size - 1];
}
void* wm_list_front(const WmList *list) {
return wm_list_first(list);
}
void* wm_list_back(const WmList *list) {
return wm_list_last(list);
}
void wm_list_set(WmList *list, size_t index, void *item) {
if (!list || index >= list->size) return;
if (list->free_func) {
list->free_func(list->data[index]);
}
list->data[index] = item;
}
/*==============================================================================
* Iteration
*============================================================================*/
void wm_list_foreach(const WmList *list, WmForeachFunc func, void *user_data) {
if (!list || !func) return;
for (size_t i = 0; i < list->size; i++) {
if (!func(list->data[i], i, user_data)) break;
}
}
void wm_list_foreach_reverse(const WmList *list, WmForeachFunc func, void *user_data) {
if (!list || !func) return;
for (size_t i = list->size; i-- > 0; ) {
if (!func(list->data[i], i, user_data)) break;
}
}
/*==============================================================================
* Search
*============================================================================*/
ssize_t wm_list_index_of(const WmList *list, void *item) {
if (!list) return -1;
for (size_t i = 0; i < list->size; i++) {
if (list->data[i] == item) return (ssize_t)i;
}
return -1;
}
bool wm_list_contains(const WmList *list, void *item) {
return wm_list_index_of(list, item) >= 0;
}
void* wm_list_find(const WmList *list, WmCompareFunc compare, const void *key) {
if (!list || !compare) return NULL;
for (size_t i = 0; i < list->size; i++) {
if (compare(list->data[i], key) == 0) return list->data[i];
}
return NULL;
}
ssize_t wm_list_find_index(const WmList *list, WmCompareFunc compare, const void *key) {
if (!list || !compare) return -1;
for (size_t i = 0; i < list->size; i++) {
if (compare(list->data[i], key) == 0) return (ssize_t)i;
}
return -1;
}
/*==============================================================================
* Sorting
*============================================================================*/
static void wm_list_qsort_helper(void **data, size_t left, size_t right,
WmCompareFunc compare) {
if (left >= right) return;
void *pivot = data[(left + right) / 2];
size_t i = left, j = right;
while (i <= j) {
while (compare(data[i], pivot) < 0) i++;
while (compare(data[j], pivot) > 0) j--;
if (i <= j) {
void *tmp = data[i];
data[i] = data[j];
data[j] = tmp;
i++;
if (j > 0) j--;
}
}
if (j > left) wm_list_qsort_helper(data, left, j, compare);
if (i < right) wm_list_qsort_helper(data, i, right, compare);
}
void wm_list_sort(WmList *list, WmCompareFunc compare) {
if (!list || list->size < 2) return;
wm_list_qsort_helper(list->data, 0, list->size - 1, compare);
}
/*==============================================================================
* Data Operations
*============================================================================*/
void** wm_list_data(WmList *list) {
return list ? list->data : NULL;
}
void* wm_list_steal(WmList *list, size_t index) {
return wm_list_take_at(list, index);
}
WmList* wm_list_slice(const WmList *list, size_t start, size_t end) {
if (!list || start >= list->size || start >= end) return wm_list_new();
if (end > list->size) end = list->size;
size_t count = end - start;
WmList *slice = wm_list_new_sized(count);
if (!slice) return NULL;
memcpy(slice->data, &list->data[start], count * sizeof(void*));
slice->size = count;
return slice;
}
void wm_list_move(WmList *list, size_t from, size_t to) {
if (!list || from >= list->size || to >= list->size || from == to) return;
void *item = list->data[from];
if (from < to) {
memmove(&list->data[from], &list->data[from + 1],
(to - from) * sizeof(void*));
} else {
memmove(&list->data[to + 1], &list->data[to],
(from - to) * sizeof(void*));
}
list->data[to] = item;
}
void wm_list_swap(WmList *list, size_t i, size_t j) {
if (!list || i >= list->size || j >= list->size) return;
void *tmp = list->data[i];
list->data[i] = list->data[j];
list->data[j] = tmp;
}
/*==============================================================================
* Comparison
*============================================================================*/
bool wm_list_equals(const WmList *list1, const WmList *list2) {
if (list1 == list2) return true;
if (!list1 || !list2) return false;
if (list1->size != list2->size) return false;
for (size_t i = 0; i < list1->size; i++) {
if (list1->data[i] != list2->data[i]) return false;
}
return true;
}

650
src/core/wm_string.c Normal file
View File

@ -0,0 +1,650 @@
/*
* DWN - Desktop Window Manager
* Abstract String Implementation
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <assert.h>
#include "core/wm_string.h"
#include "core/wm_types.h"
/*==============================================================================
* String Structure
*============================================================================*/
struct WmString {
char *data;
size_t length;
size_t capacity;
};
/*==============================================================================
* Internal Helpers
*============================================================================*/
static bool wm_string_grow(WmString *str, size_t min_capacity) {
if (str->capacity >= min_capacity) return true;
size_t new_capacity = str->capacity * 2;
if (new_capacity < 16) new_capacity = 16;
if (new_capacity < min_capacity) new_capacity = min_capacity;
char *new_data = realloc(str->data, new_capacity);
if (!new_data) return false;
str->data = new_data;
str->capacity = new_capacity;
return true;
}
/*==============================================================================
* Lifecycle
*============================================================================*/
WmString* wm_string_new(const char *str) {
if (!str) return wm_string_new_empty();
return wm_string_new_n(str, strlen(str));
}
WmString* wm_string_new_empty(void) {
WmString *s = calloc(1, sizeof(WmString));
if (!s) return NULL;
s->data = malloc(1);
if (!s->data) {
free(s);
return NULL;
}
s->data[0] = '\0';
s->length = 0;
s->capacity = 1;
return s;
}
WmString* wm_string_new_sized(size_t capacity) {
WmString *s = calloc(1, sizeof(WmString));
if (!s) return NULL;
s->data = malloc(capacity + 1);
if (!s->data) {
free(s);
return NULL;
}
s->data[0] = '\0';
s->length = 0;
s->capacity = capacity + 1;
return s;
}
WmString* wm_string_new_printf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
WmString *s = wm_string_new_vprintf(fmt, args);
va_end(args);
return s;
}
WmString* wm_string_new_vprintf(const char *fmt, va_list args) {
va_list args_copy;
va_copy(args_copy, args);
int len = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
if (len < 0) return NULL;
WmString *s = wm_string_new_sized(len + 1);
if (!s) return NULL;
vsnprintf(s->data, s->capacity, fmt, args);
s->length = len;
return s;
}
WmString* wm_string_new_n(const char *str, size_t n) {
if (!str) return wm_string_new_empty();
WmString *s = calloc(1, sizeof(WmString));
if (!s) return NULL;
s->data = malloc(n + 1);
if (!s->data) {
free(s);
return NULL;
}
memcpy(s->data, str, n);
s->data[n] = '\0';
s->length = n;
s->capacity = n + 1;
return s;
}
void wm_string_destroy(WmString *str) {
if (!str) return;
free(str->data);
free(str);
}
WmString* wm_string_clone(const WmString *str) {
if (!str) return NULL;
return wm_string_new_n(str->data, str->length);
}
/*==============================================================================
* Operations
*============================================================================*/
void wm_string_append(WmString *str, const char *suffix) {
if (!str || !suffix) return;
wm_string_append_n(str, suffix, strlen(suffix));
}
void wm_string_append_char(WmString *str, char c) {
if (!str) return;
if (!wm_string_grow(str, str->length + 2)) return;
str->data[str->length++] = c;
str->data[str->length] = '\0';
}
void wm_string_append_n(WmString *str, const char *suffix, size_t n) {
if (!str || !suffix || n == 0) return;
if (!wm_string_grow(str, str->length + n + 1)) return;
memcpy(str->data + str->length, suffix, n);
str->length += n;
str->data[str->length] = '\0';
}
void wm_string_append_printf(WmString *str, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
wm_string_append_vprintf(str, fmt, args);
va_end(args);
}
void wm_string_append_vprintf(WmString *str, const char *fmt, va_list args) {
if (!str || !fmt) return;
va_list args_copy;
va_copy(args_copy, args);
int len = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
if (len < 0) return;
if (!wm_string_grow(str, str->length + len + 1)) return;
vsnprintf(str->data + str->length, str->capacity - str->length, fmt, args);
str->length += len;
}
void wm_string_append_string(WmString *str, const WmString *suffix) {
if (!suffix) return;
wm_string_append_n(str, suffix->data, suffix->length);
}
void wm_string_prepend(WmString *str, const char *prefix) {
if (!str || !prefix) return;
size_t prefix_len = strlen(prefix);
if (prefix_len == 0) return;
if (!wm_string_grow(str, str->length + prefix_len + 1)) return;
memmove(str->data + prefix_len, str->data, str->length + 1);
memcpy(str->data, prefix, prefix_len);
str->length += prefix_len;
}
void wm_string_prepend_char(WmString *str, char c) {
if (!str) return;
if (!wm_string_grow(str, str->length + 2)) return;
memmove(str->data + 1, str->data, str->length + 1);
str->data[0] = c;
str->length++;
}
void wm_string_insert(WmString *str, size_t pos, const char *insert) {
if (!str || !insert || pos > str->length) return;
size_t insert_len = strlen(insert);
if (insert_len == 0) return;
if (!wm_string_grow(str, str->length + insert_len + 1)) return;
memmove(str->data + pos + insert_len, str->data + pos, str->length - pos + 1);
memcpy(str->data + pos, insert, insert_len);
str->length += insert_len;
}
void wm_string_insert_char(WmString *str, size_t pos, char c) {
if (!str || pos > str->length) return;
if (!wm_string_grow(str, str->length + 2)) return;
memmove(str->data + pos + 1, str->data + pos, str->length - pos + 1);
str->data[pos] = c;
str->length++;
}
void wm_string_insert_string(WmString *str, size_t pos, const WmString *insert) {
if (!insert) return;
wm_string_insert(str, pos, insert->data);
}
void wm_string_erase(WmString *str, size_t pos, size_t len) {
if (!str || pos >= str->length) return;
if (len > str->length - pos) len = str->length - pos;
memmove(str->data + pos, str->data + pos + len, str->length - pos - len + 1);
str->length -= len;
}
void wm_string_clear(WmString *str) {
if (!str) return;
str->length = 0;
str->data[0] = '\0';
}
/*==============================================================================
* Queries
*============================================================================*/
const char* wm_string_cstr(const WmString *str) {
return str ? str->data : "";
}
char* wm_string_detach(WmString *str) {
if (!str) return NULL;
char *data = str->data;
free(str);
return data;
}
size_t wm_string_length(const WmString *str) {
return str ? str->length : 0;
}
size_t wm_string_capacity(const WmString *str) {
return str ? str->capacity : 0;
}
size_t wm_string_bytes(const WmString *str) {
return str ? str->length + 1 : 0;
}
bool wm_string_is_empty(const WmString *str) {
return !str || str->length == 0;
}
bool wm_string_is_null(const WmString *str) {
return str == NULL;
}
char wm_string_char_at(const WmString *str, size_t pos) {
if (!str || pos >= str->length) return '\0';
return str->data[pos];
}
/*==============================================================================
* Comparison
*============================================================================*/
bool wm_string_equals(const WmString *str1, const WmString *str2) {
if (str1 == str2) return true;
if (!str1 || !str2) return false;
if (str1->length != str2->length) return false;
return memcmp(str1->data, str2->data, str1->length) == 0;
}
bool wm_string_equals_cstr(const WmString *str, const char *cstr) {
if (!str) return !cstr || cstr[0] == '\0';
if (!cstr) return str->length == 0;
return strcmp(str->data, cstr) == 0;
}
int wm_string_compare(const WmString *str1, const WmString *str2) {
if (str1 == str2) return 0;
if (!str1) return -1;
if (!str2) return 1;
return strcmp(str1->data, str2->data);
}
int wm_string_compare_cstr(const WmString *str, const char *cstr) {
if (!str) return cstr ? -1 : 0;
if (!cstr) return 1;
return strcmp(str->data, cstr);
}
int wm_string_case_compare(const WmString *str1, const WmString *str2) {
if (str1 == str2) return 0;
if (!str1) return -1;
if (!str2) return 1;
return strcasecmp(str1->data, str2->data);
}
int wm_string_case_compare_cstr(const WmString *str, const char *cstr) {
if (!str) return cstr ? -1 : 0;
if (!cstr) return 1;
return strcasecmp(str->data, cstr);
}
/*==============================================================================
* Searching
*============================================================================*/
bool wm_string_contains(const WmString *str, const char *needle) {
if (!str || !needle) return false;
return strstr(str->data, needle) != NULL;
}
bool wm_string_contains_char(const WmString *str, char c) {
if (!str) return false;
return strchr(str->data, c) != NULL;
}
size_t wm_string_find(const WmString *str, const char *needle, size_t start) {
if (!str || !needle || start > str->length) return (size_t)-1;
const char *found = strstr(str->data + start, needle);
return found ? (size_t)(found - str->data) : (size_t)-1;
}
size_t wm_string_find_char(const WmString *str, char c, size_t start) {
if (!str || start > str->length) return (size_t)-1;
const char *found = strchr(str->data + start, c);
return found ? (size_t)(found - str->data) : (size_t)-1;
}
size_t wm_string_find_last(const WmString *str, const char *needle) {
if (!str || !needle) return (size_t)-1;
/* Simple implementation since strrstr is not standard */
size_t needle_len = strlen(needle);
if (needle_len == 0) return str->length;
for (size_t i = str->length - needle_len + 1; i-- > 0; ) {
if (memcmp(str->data + i, needle, needle_len) == 0) {
return i;
}
}
return (size_t)-1;
}
size_t wm_string_find_last_char(const WmString *str, char c) {
if (!str) return (size_t)-1;
const char *found = strrchr(str->data, c);
return found ? (size_t)(found - str->data) : (size_t)-1;
}
bool wm_string_starts_with(const WmString *str, const char *prefix) {
if (!str || !prefix) return false;
size_t prefix_len = strlen(prefix);
if (prefix_len > str->length) return false;
return strncmp(str->data, prefix, prefix_len) == 0;
}
bool wm_string_starts_with_char(const WmString *str, char c) {
return str && str->length > 0 && str->data[0] == c;
}
bool wm_string_ends_with(const WmString *str, const char *suffix) {
if (!str || !suffix) return false;
size_t suffix_len = strlen(suffix);
if (suffix_len > str->length) return false;
return strncmp(str->data + str->length - suffix_len, suffix, suffix_len) == 0;
}
bool wm_string_ends_with_char(const WmString *str, char c) {
return str && str->length > 0 && str->data[str->length - 1] == c;
}
/*==============================================================================
* Modification
*============================================================================*/
void wm_string_trim(WmString *str) {
wm_string_trim_left(str);
wm_string_trim_right(str);
}
void wm_string_trim_left(WmString *str) {
if (!str) return;
size_t i = 0;
while (i < str->length && isspace((unsigned char)str->data[i])) i++;
if (i > 0) {
memmove(str->data, str->data + i, str->length - i + 1);
str->length -= i;
}
}
void wm_string_trim_right(WmString *str) {
if (!str) return;
while (str->length > 0 && isspace((unsigned char)str->data[str->length - 1])) {
str->data[--str->length] = '\0';
}
}
void wm_string_replace(WmString *str, const char *search, const char *replace) {
if (!str || !search || !replace) return;
size_t count = wm_string_replace_all(str, search, replace);
(void)count;
}
void wm_string_replace_char(WmString *str, char search, char replace) {
if (!str) return;
for (size_t i = 0; i < str->length; i++) {
if (str->data[i] == search) str->data[i] = replace;
}
}
size_t wm_string_replace_all(WmString *str, const char *search, const char *replace) {
if (!str || !search || !replace) return 0;
size_t search_len = strlen(search);
size_t replace_len = strlen(replace);
size_t count = 0;
char *pos = str->data;
while ((pos = strstr(pos, search)) != NULL) {
count++;
pos += search_len;
}
if (count == 0) return 0;
size_t new_length = str->length + count * (replace_len - search_len);
if (!wm_string_grow(str, new_length + 1)) return 0;
WmString *result = wm_string_new_sized(new_length);
if (!result) return 0;
char *src = str->data;
char *dst = result->data;
while ((pos = strstr(src, search)) != NULL) {
size_t prefix_len = pos - src;
memcpy(dst, src, prefix_len);
dst += prefix_len;
memcpy(dst, replace, replace_len);
dst += replace_len;
src = pos + search_len;
}
size_t remaining = new_length - (size_t)(dst - result->data);
strncpy(dst, src, remaining);
dst[remaining] = '\0';
result->length = new_length;
free(str->data);
str->data = result->data;
str->length = result->length;
str->capacity = result->capacity;
free(result);
return count;
}
void wm_string_to_lower(WmString *str) {
if (!str) return;
for (size_t i = 0; i < str->length; i++) {
str->data[i] = tolower((unsigned char)str->data[i]);
}
}
void wm_string_to_upper(WmString *str) {
if (!str) return;
for (size_t i = 0; i < str->length; i++) {
str->data[i] = toupper((unsigned char)str->data[i]);
}
}
void wm_string_reverse(WmString *str) {
if (!str) return;
for (size_t i = 0, j = str->length - 1; i < j; i++, j--) {
char tmp = str->data[i];
str->data[i] = str->data[j];
str->data[j] = tmp;
}
}
/*==============================================================================
* Substrings
*============================================================================*/
WmString* wm_string_substring(const WmString *str, size_t start, size_t len) {
if (!str || start > str->length) return wm_string_new_empty();
if (start + len > str->length) len = str->length - start;
return wm_string_new_n(str->data + start, len);
}
WmString* wm_string_left(const WmString *str, size_t n) {
if (!str) return wm_string_new_empty();
if (n > str->length) n = str->length;
return wm_string_new_n(str->data, n);
}
WmString* wm_string_right(const WmString *str, size_t n) {
if (!str) return wm_string_new_empty();
if (n > str->length) n = str->length;
return wm_string_new_n(str->data + str->length - n, n);
}
/*==============================================================================
* Formatting
*============================================================================*/
void wm_string_printf(WmString *str, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
wm_string_clear(str);
wm_string_append_vprintf(str, fmt, args);
va_end(args);
}
void wm_string_vprintf(WmString *str, const char *fmt, va_list args) {
wm_string_clear(str);
wm_string_append_vprintf(str, fmt, args);
}
/*==============================================================================
* Conversion
*============================================================================*/
int wm_string_to_int(const WmString *str, int default_val) {
if (!str || str->length == 0) return default_val;
char *end;
long val = strtol(str->data, &end, 10);
if (end == str->data) return default_val;
return (int)val;
}
long wm_string_to_long(const WmString *str, long default_val) {
if (!str || str->length == 0) return default_val;
char *end;
long val = strtol(str->data, &end, 10);
if (end == str->data) return default_val;
return val;
}
float wm_string_to_float(const WmString *str, float default_val) {
if (!str || str->length == 0) return default_val;
char *end;
float val = strtof(str->data, &end);
if (end == str->data) return default_val;
return val;
}
double wm_string_to_double(const WmString *str, double default_val) {
if (!str || str->length == 0) return default_val;
char *end;
double val = strtod(str->data, &end);
if (end == str->data) return default_val;
return val;
}
bool wm_string_to_bool(const WmString *str, bool default_val) {
if (!str || str->length == 0) return default_val;
if (strcasecmp(str->data, "true") == 0 ||
strcmp(str->data, "1") == 0 ||
strcasecmp(str->data, "yes") == 0) {
return true;
}
if (strcasecmp(str->data, "false") == 0 ||
strcmp(str->data, "0") == 0 ||
strcasecmp(str->data, "no") == 0) {
return false;
}
return default_val;
}
WmString* wm_string_from_int(int val) {
return wm_string_new_printf("%d", val);
}
WmString* wm_string_from_long(long val) {
return wm_string_new_printf("%ld", val);
}
WmString* wm_string_from_float(float val, int precision) {
return wm_string_new_printf("%.*f", precision, val);
}
WmString* wm_string_from_double(double val, int precision) {
return wm_string_new_printf("%.*lf", precision, val);
}
WmString* wm_string_from_bool(bool val) {
return wm_string_new(val ? "true" : "false");
}
/*==============================================================================
* Hash
*============================================================================*/
uint32_t wm_string_hash(const WmString *str) {
if (!str) return 0;
return wm_string_hash_cstr(str->data);
}
uint32_t wm_string_hash_cstr(const char *str) {
if (!str) return 0;
/* FNV-1a hash */
uint32_t hash = 2166136261u;
while (*str) {
hash ^= (uint8_t)*str++;
hash *= 16777619u;
}
return hash;
}

View File

@ -164,8 +164,10 @@ void decorations_render_title_bar(Client *client, bool focused)
return;
}
char display_title[256];
strncpy(display_title, client->title, sizeof(display_title) - 4);
display_title[sizeof(display_title) - 4] = '\0';
size_t disp_len = strlen(client->title);
if (disp_len >= sizeof(display_title) - 3) disp_len = sizeof(display_title) - 4;
memcpy(display_title, client->title, disp_len);
display_title[disp_len] = '\0';
XGlyphInfo extents;
XftTextExtentsUtf8(dpy, title_font,

329
src/event_bus.c Normal file
View File

@ -0,0 +1,329 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Event Bus Implementation - Thread-Safe Pub/Sub
*/
#include "threading.h"
#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/eventfd.h>
#define MAX_SUBSCRIPTIONS_PER_TYPE 64
#define EVENT_QUEUE_SIZE 4096
/* ============================================================================
* Event Bus Types
* ============================================================================ */
typedef struct Subscription {
SubscriptionId id;
EventType type;
EventHandler handler;
void *user_data;
EventFilter filter;
void *filter_data;
bool active;
} Subscription;
typedef struct EventNode {
EventType type;
void *data;
void (*free_fn)(void*);
struct EventNode *next;
} EventNode;
struct EventBus {
Subscription subscriptions[MAX_SUBSCRIPTIONS_PER_TYPE * 32]; /* Fixed pool */
uint32_t subscription_count;
_Atomic SubscriptionId next_id;
/* Event queue (MPSC) */
pthread_mutex_t queue_mutex;
EventNode *event_head;
EventNode *event_tail;
_Atomic uint32_t event_count;
int event_fd;
_Atomic int shutdown;
uint32_t batch_size;
pthread_mutex_t sub_mutex;
};
static SubscriptionId generate_subscription_id(EventBus *bus)
{
return atomic_fetch_add_explicit(&bus->next_id, 1, ATOMIC_RELAXED) + 1;
}
/* ============================================================================
* Event Bus Implementation
* ============================================================================ */
EventBus* event_bus_create(void)
{
EventBus *bus = dwn_malloc(sizeof(EventBus));
if (!bus) return NULL;
memset(bus->subscriptions, 0, sizeof(bus->subscriptions));
bus->subscription_count = 0;
atomic_store_explicit(&bus->next_id, 0, ATOMIC_RELEASE);
bus->event_head = NULL;
bus->event_tail = NULL;
atomic_store_explicit(&bus->event_count, 0, ATOMIC_RELEASE);
atomic_store_explicit(&bus->shutdown, 0, ATOMIC_RELEASE);
bus->batch_size = 100;
if (pthread_mutex_init(&bus->queue_mutex, NULL) != 0) {
dwn_free(bus);
return NULL;
}
if (pthread_mutex_init(&bus->sub_mutex, NULL) != 0) {
pthread_mutex_destroy(&bus->queue_mutex);
dwn_free(bus);
return NULL;
}
bus->event_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (bus->event_fd < 0) {
pthread_mutex_destroy(&bus->sub_mutex);
pthread_mutex_destroy(&bus->queue_mutex);
dwn_free(bus);
return NULL;
}
return bus;
}
void event_bus_destroy(EventBus *bus)
{
if (!bus) return;
atomic_store_explicit(&bus->shutdown, 1, ATOMIC_RELEASE);
/* Drain remaining events */
event_bus_process(bus);
/* Free remaining events */
pthread_mutex_lock(&bus->queue_mutex);
EventNode *node = bus->event_head;
while (node) {
EventNode *next = node->next;
if (node->free_fn) {
node->free_fn(node->data);
}
dwn_free(node);
node = next;
}
pthread_mutex_unlock(&bus->queue_mutex);
close(bus->event_fd);
pthread_mutex_destroy(&bus->sub_mutex);
pthread_mutex_destroy(&bus->queue_mutex);
dwn_free(bus);
}
SubscriptionId event_bus_subscribe(EventBus *bus, EventType type,
EventHandler handler, void *user_data)
{
return event_bus_subscribe_filtered(bus, type, handler, user_data, NULL, NULL);
}
SubscriptionId event_bus_subscribe_filtered(EventBus *bus, EventType type,
EventHandler handler, void *user_data,
EventFilter filter, void *filter_data)
{
if (!bus || !handler || type >= 32) return 0;
pthread_mutex_lock(&bus->sub_mutex);
if (bus->subscription_count >= sizeof(bus->subscriptions) / sizeof(bus->subscriptions[0])) {
pthread_mutex_unlock(&bus->sub_mutex);
return 0;
}
/* Find slot */
for (uint32_t i = 0; i < sizeof(bus->subscriptions) / sizeof(bus->subscriptions[0]); i++) {
if (!bus->subscriptions[i].active) {
Subscription *sub = &bus->subscriptions[i];
sub->id = generate_subscription_id(bus);
sub->type = type;
sub->handler = handler;
sub->user_data = user_data;
sub->filter = filter;
sub->filter_data = filter_data;
sub->active = true;
bus->subscription_count++;
pthread_mutex_unlock(&bus->sub_mutex);
return sub->id;
}
}
pthread_mutex_unlock(&bus->sub_mutex);
return 0;
}
bool event_bus_unsubscribe(EventBus *bus, SubscriptionId id)
{
if (!bus || id == 0) return false;
pthread_mutex_lock(&bus->sub_mutex);
for (uint32_t i = 0; i < sizeof(bus->subscriptions) / sizeof(bus->subscriptions[0]); i++) {
if (bus->subscriptions[i].active && bus->subscriptions[i].id == id) {
bus->subscriptions[i].active = false;
bus->subscription_count--;
pthread_mutex_unlock(&bus->sub_mutex);
return true;
}
}
pthread_mutex_unlock(&bus->sub_mutex);
return false;
}
void event_bus_publish(EventBus *bus, EventType type, void *event_data)
{
event_bus_publish_owned(bus, type, event_data, NULL);
}
void event_bus_publish_owned(EventBus *bus, EventType type, void *event_data,
void (*free_fn)(void*))
{
if (!bus || type >= 32) {
if (free_fn && event_data) free_fn(event_data);
return;
}
if (atomic_load_explicit(&bus->shutdown, ATOMIC_ACQUIRE)) {
if (free_fn && event_data) free_fn(event_data);
return;
}
EventNode *node = dwn_malloc(sizeof(EventNode));
if (!node) {
if (free_fn && event_data) free_fn(event_data);
return;
}
node->type = type;
node->data = event_data;
node->free_fn = free_fn;
node->next = NULL;
pthread_mutex_lock(&bus->queue_mutex);
if (bus->event_tail) {
bus->event_tail->next = node;
} else {
bus->event_head = node;
}
bus->event_tail = node;
atomic_fetch_add_explicit(&bus->event_count, 1, ATOMIC_RELAXED);
pthread_mutex_unlock(&bus->queue_mutex);
/* Signal via eventfd */
uint64_t one = 1;
ssize_t w __attribute__((unused)) = write(bus->event_fd, &one, sizeof(one));
}
uint32_t event_bus_process(EventBus *bus)
{
if (!bus) return 0;
/* Acknowledge eventfd */
uint64_t val;
ssize_t r __attribute__((unused)) = read(bus->event_fd, &val, sizeof(val));
uint32_t processed = 0;
while (processed < bus->batch_size) {
/* Dequeue event */
pthread_mutex_lock(&bus->queue_mutex);
EventNode *node = bus->event_head;
if (!node) {
pthread_mutex_unlock(&bus->queue_mutex);
break;
}
bus->event_head = node->next;
if (!bus->event_head) {
bus->event_tail = NULL;
}
atomic_fetch_sub_explicit(&bus->event_count, 1, ATOMIC_RELAXED);
pthread_mutex_unlock(&bus->queue_mutex);
/* Dispatch to subscribers */
pthread_mutex_lock(&bus->sub_mutex);
for (uint32_t i = 0; i < sizeof(bus->subscriptions) / sizeof(bus->subscriptions[0]); i++) {
Subscription *sub = &bus->subscriptions[i];
if (sub->active && sub->type == node->type) {
/* Check filter if present */
bool should_handle = true;
if (sub->filter) {
should_handle = sub->filter(node->type, node->data, sub->filter_data);
}
if (should_handle) {
sub->handler(node->type, node->data, sub->user_data);
}
}
}
pthread_mutex_unlock(&bus->sub_mutex);
/* Free event data */
if (node->free_fn) {
node->free_fn(node->data);
}
dwn_free(node);
processed++;
}
return processed;
}
void event_bus_set_batch_size(EventBus *bus, uint32_t batch_size)
{
if (!bus) return;
bus->batch_size = batch_size ? batch_size : 100;
}
/* ============================================================================
* Default Event Bus
* ============================================================================ */
static EventBus *g_default_bus = NULL;
EventBus* event_bus_default(void)
{
return g_default_bus;
}
bool event_bus_init_default(void)
{
if (g_default_bus) return true;
g_default_bus = event_bus_create();
return g_default_bus != NULL;
}
void event_bus_shutdown_default(void)
{
if (g_default_bus) {
event_bus_destroy(g_default_bus);
g_default_bus = NULL;
}
}

333
src/futures.c Normal file
View File

@ -0,0 +1,333 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Future/Promise Implementation - Async Result Handling
*/
#include "threading.h"
#include "util.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
/* ============================================================================
* Future Implementation
* ============================================================================ */
typedef struct FutureCallbackNode {
FutureCallback callback;
void *user_data;
struct FutureCallbackNode *next;
} FutureCallbackNode;
struct Future {
pthread_mutex_t mutex;
pthread_cond_t ready;
FutureResult result;
int error_code;
_Atomic int ready_flag;
_Atomic int has_result;
FutureCallbackNode *callbacks;
_Atomic int callback_count;
};
Future* future_create(void)
{
Future *f = dwn_malloc(sizeof(Future));
if (!f) return NULL;
atomic_store_explicit(&f->ready_flag, 0, ATOMIC_RELEASE);
atomic_store_explicit(&f->has_result, 0, ATOMIC_RELEASE);
atomic_store_explicit(&f->callback_count, 0, ATOMIC_RELAXED);
f->result = NULL;
f->error_code = 0;
f->callbacks = NULL;
if (pthread_mutex_init(&f->mutex, NULL) != 0) {
dwn_free(f);
return NULL;
}
if (pthread_cond_init(&f->ready, NULL) != 0) {
pthread_mutex_destroy(&f->mutex);
dwn_free(f);
return NULL;
}
return f;
}
void future_destroy(Future *f)
{
if (!f) return;
/* Free callback chain */
FutureCallbackNode *node = f->callbacks;
while (node) {
FutureCallbackNode *next = node->next;
dwn_free(node);
node = next;
}
pthread_cond_destroy(&f->ready);
pthread_mutex_destroy(&f->mutex);
dwn_free(f);
}
void future_set_result(Future *f, FutureResult result)
{
if (!f) return;
pthread_mutex_lock(&f->mutex);
if (atomic_load_explicit(&f->has_result, ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&f->mutex);
return; /* Already set */
}
f->result = result;
f->error_code = 0;
atomic_store_explicit(&f->has_result, 1, ATOMIC_RELEASE);
atomic_store_explicit(&f->ready_flag, 1, ATOMIC_RELEASE);
/* Execute callbacks */
FutureCallbackNode *node = f->callbacks;
while (node) {
node->callback(f, result, node->user_data);
node = node->next;
}
pthread_cond_broadcast(&f->ready);
pthread_mutex_unlock(&f->mutex);
}
void future_set_error(Future *f, int error_code)
{
if (!f) return;
pthread_mutex_lock(&f->mutex);
if (atomic_load_explicit(&f->has_result, ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&f->mutex);
return;
}
f->result = NULL;
f->error_code = error_code;
atomic_store_explicit(&f->has_result, 1, ATOMIC_RELEASE);
atomic_store_explicit(&f->ready_flag, 1, ATOMIC_RELEASE);
/* Execute callbacks with NULL result */
FutureCallbackNode *node = f->callbacks;
while (node) {
node->callback(f, NULL, node->user_data);
node = node->next;
}
pthread_cond_broadcast(&f->ready);
pthread_mutex_unlock(&f->mutex);
}
FutureResult future_get(Future *f, ThreadStatus *status)
{
return future_get_timeout(f, 0, status);
}
FutureResult future_get_timeout(Future *f, uint64_t timeout_ms, ThreadStatus *status)
{
if (!f) {
if (status) *status = THREAD_ERROR_INVALID;
return NULL;
}
pthread_mutex_lock(&f->mutex);
if (!atomic_load_explicit(&f->ready_flag, ATOMIC_ACQUIRE)) {
if (timeout_ms == 0) {
pthread_cond_wait(&f->ready, &f->mutex);
} else {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
int ret = pthread_cond_timedwait(&f->ready, &f->mutex, &ts);
if (ret == ETIMEDOUT) {
pthread_mutex_unlock(&f->mutex);
if (status) *status = THREAD_ERROR_TIMEOUT;
return NULL;
}
}
}
FutureResult result = f->result;
if (status) {
*status = (f->error_code != 0) ? THREAD_ERROR : THREAD_OK;
}
pthread_mutex_unlock(&f->mutex);
return result;
}
bool future_is_ready(Future *f)
{
if (!f) return false;
return atomic_load_explicit(&f->ready_flag, ATOMIC_ACQUIRE) != 0;
}
void future_then(Future *f, FutureCallback callback, void *user_data)
{
if (!f || !callback) return;
pthread_mutex_lock(&f->mutex);
/* If already ready, execute immediately */
if (atomic_load_explicit(&f->ready_flag, ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&f->mutex);
callback(f, f->result, user_data);
return;
}
/* Add to callback chain */
FutureCallbackNode *node = dwn_malloc(sizeof(FutureCallbackNode));
if (!node) {
pthread_mutex_unlock(&f->mutex);
return;
}
node->callback = callback;
node->user_data = user_data;
node->next = f->callbacks;
f->callbacks = node;
atomic_fetch_add_explicit(&f->callback_count, 1, ATOMIC_RELAXED);
pthread_mutex_unlock(&f->mutex);
}
/* ============================================================================
* Composite Futures
* ============================================================================ */
typedef struct {
Future *composite;
_Atomic int remaining;
uint32_t total;
pthread_mutex_t mutex;
} CompositeFutureData;
static void all_callback(Future *f, FutureResult result, void *user_data)
{
(void)f;
(void)result;
CompositeFutureData *data = user_data;
int rem = atomic_fetch_sub_explicit(&data->remaining, 1, ATOMIC_ACQ_REL) - 1;
if (rem == 0) {
future_set_result(data->composite, (void*)(uintptr_t)data->total);
}
}
Future* future_all(Future **futures, uint32_t count)
{
if (!futures || count == 0) return NULL;
Future *composite = future_create();
if (!composite) return NULL;
if (count == 1) {
/* Optimize single future */
FutureResult r = future_get(futures[0], NULL);
future_set_result(composite, r);
return composite;
}
CompositeFutureData *data = dwn_malloc(sizeof(CompositeFutureData));
if (!data) {
future_destroy(composite);
return NULL;
}
data->composite = composite;
atomic_store_explicit(&data->remaining, count, ATOMIC_RELEASE);
data->total = count;
pthread_mutex_init(&data->mutex, NULL);
/* Attach callback to each future */
for (uint32_t i = 0; i < count; i++) {
if (future_is_ready(futures[i])) {
/* Already done */
if (atomic_fetch_sub_explicit(&data->remaining, 1, ATOMIC_ACQ_REL) - 1 == 0) {
future_set_result(composite, (void*)(uintptr_t)count);
pthread_mutex_destroy(&data->mutex);
dwn_free(data);
return composite;
}
} else {
future_then(futures[i], all_callback, data);
}
}
return composite;
}
static void any_callback(Future *f, FutureResult result, void *user_data)
{
(void)f;
CompositeFutureData *data = user_data;
pthread_mutex_lock(&data->mutex);
if (!future_is_ready(data->composite)) {
future_set_result(data->composite, result);
}
pthread_mutex_unlock(&data->mutex);
}
Future* future_any(Future **futures, uint32_t count)
{
if (!futures || count == 0) return NULL;
Future *composite = future_create();
if (!composite) return NULL;
if (count == 1) {
FutureResult r = future_get(futures[0], NULL);
future_set_result(composite, r);
return composite;
}
CompositeFutureData *data = dwn_malloc(sizeof(CompositeFutureData));
if (!data) {
future_destroy(composite);
return NULL;
}
data->composite = composite;
pthread_mutex_init(&data->mutex, NULL);
/* Attach callback to each future */
for (uint32_t i = 0; i < count; i++) {
if (future_is_ready(futures[i])) {
future_set_result(composite, futures[i]->result);
pthread_mutex_destroy(&data->mutex);
dwn_free(data);
return composite;
} else {
future_then(futures[i], any_callback, data);
}
}
return composite;
}

View File

@ -17,6 +17,7 @@
#include "demo.h"
#include "layout.h"
#include "api.h"
#include "marks.h"
#include <stdio.h>
#include <string.h>
@ -71,6 +72,7 @@ void key_news_open(void);
void key_show_shortcuts(void);
void key_start_tutorial(void);
void key_screenshot(void);
void key_kill_all_clients(void);
static KeyBinding bindings[MAX_KEYBINDINGS];
static int binding_count = 0;
@ -436,6 +438,21 @@ void keys_handle_press(XKeyEvent *ev)
KeySym keysym = XLookupKeysym(ev, 0);
if (marks_is_waiting_for_mark() || marks_is_waiting_for_goto()) {
char key_buf[32] = {0};
XLookupString(ev, key_buf, sizeof(key_buf) - 1, NULL, NULL);
if (keysym == XK_Escape) {
marks_cancel_mode();
return;
}
if (key_buf[0] != '\0') {
if (marks_handle_key(key_buf[0])) {
return;
}
}
}
if (keysym == XK_Super_L || keysym == XK_Super_R) {
super_pressed = true;
@ -578,6 +595,21 @@ void keys_setup_defaults(void)
keys_bind(MOD_SUPER, XK_Return, key_news_open, "Open news article");
keys_bind(0, XK_Print, key_screenshot, "Take screenshot");
keys_bind(MOD_SUPER, XK_k, key_kill_all_clients, "Kill all applications");
keys_bind(MOD_SUPER | MOD_ALT, XK_Left, key_move_window_left, "Move window left");
keys_bind(MOD_SUPER | MOD_ALT, XK_Right, key_move_window_right, "Move window right");
keys_bind(MOD_SUPER | MOD_ALT, XK_Up, key_move_window_up, "Move window up");
keys_bind(MOD_SUPER | MOD_ALT, XK_Down, key_move_window_down, "Move window down");
keys_bind(MOD_SUPER | MOD_CTRL, XK_Left, key_resize_window_left, "Shrink window width");
keys_bind(MOD_SUPER | MOD_CTRL, XK_Right, key_resize_window_right, "Grow window width");
keys_bind(MOD_SUPER | MOD_CTRL, XK_Up, key_resize_window_up, "Shrink window height");
keys_bind(MOD_SUPER | MOD_CTRL, XK_Down, key_resize_window_down, "Grow window height");
keys_bind(MOD_SUPER, XK_m, key_set_mark, "Set window mark");
keys_bind(MOD_SUPER, XK_apostrophe, key_goto_mark, "Go to marked window");
}
@ -614,6 +646,22 @@ void key_close_window(void)
}
}
void key_kill_all_clients(void)
{
if (dwn == NULL || dwn->client_list == NULL) {
return;
}
Client *c = dwn->client_list;
while (c != NULL) {
Client *next = c->next;
if (c->workspace == (unsigned int)dwn->current_workspace) {
client_kill(c);
}
c = next;
}
}
void key_quit_dwn(void)
{
LOG_INFO("Quit requested via keyboard shortcut");
@ -889,6 +937,7 @@ void key_show_shortcuts(void)
"Alt+F10 Toggle maximize\n"
"Alt+F11 Toggle fullscreen\n"
"Super+F9 Toggle floating\n"
"Super+K Kill all applications\n"
"\n"
"=== Workspaces ===\n"
"F1-F9 Switch to workspace\n"
@ -909,6 +958,14 @@ void key_show_shortcuts(void)
"Super+Up Top 50% (2x=full height)\n"
"Super+Down Bottom 50% (2x=full height)\n"
"\n"
"=== Keyboard Window Control ===\n"
"Super+Alt+Arrow Move window by 20px\n"
"Super+Ctrl+Arrow Resize window by 20px\n"
"\n"
"=== Window Marks ===\n"
"Super+M then a-z Set mark for window\n"
"Super+' then a-z Jump to marked window\n"
"\n"
"=== AI Features ===\n"
"Super+A AI command palette\n"
"Super+Shift+A Show AI context\n"
@ -1088,3 +1145,194 @@ void key_start_demo(void)
demo_start();
}
}
#define KEYBOARD_MOVE_STEP 20
#define KEYBOARD_RESIZE_STEP 20
void key_move_window_left(void)
{
Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) {
return;
}
Client *c = ws->focused;
if (!client_is_floating(c)) {
client_set_floating(c, true);
workspace_arrange_current();
}
c->snap.horizontal = SNAP_H_NONE;
c->snap.vertical = SNAP_V_NONE;
client_move(c, c->x - KEYBOARD_MOVE_STEP, c->y);
decorations_render(c, true);
}
void key_move_window_right(void)
{
Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) {
return;
}
Client *c = ws->focused;
if (!client_is_floating(c)) {
client_set_floating(c, true);
workspace_arrange_current();
}
c->snap.horizontal = SNAP_H_NONE;
c->snap.vertical = SNAP_V_NONE;
client_move(c, c->x + KEYBOARD_MOVE_STEP, c->y);
decorations_render(c, true);
}
void key_move_window_up(void)
{
Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) {
return;
}
Client *c = ws->focused;
if (!client_is_floating(c)) {
client_set_floating(c, true);
workspace_arrange_current();
}
c->snap.horizontal = SNAP_H_NONE;
c->snap.vertical = SNAP_V_NONE;
client_move(c, c->x, c->y - KEYBOARD_MOVE_STEP);
decorations_render(c, true);
}
void key_move_window_down(void)
{
Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) {
return;
}
Client *c = ws->focused;
if (!client_is_floating(c)) {
client_set_floating(c, true);
workspace_arrange_current();
}
c->snap.horizontal = SNAP_H_NONE;
c->snap.vertical = SNAP_V_NONE;
client_move(c, c->x, c->y + KEYBOARD_MOVE_STEP);
decorations_render(c, true);
}
void key_resize_window_left(void)
{
Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) {
return;
}
Client *c = ws->focused;
if (!client_is_floating(c)) {
client_set_floating(c, true);
workspace_arrange_current();
}
c->snap.horizontal = SNAP_H_NONE;
c->snap.vertical = SNAP_V_NONE;
int new_width = c->width - KEYBOARD_RESIZE_STEP;
if (new_width < 50) {
new_width = 50;
}
client_resize(c, new_width, c->height);
decorations_render(c, true);
}
void key_resize_window_right(void)
{
Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) {
return;
}
Client *c = ws->focused;
if (!client_is_floating(c)) {
client_set_floating(c, true);
workspace_arrange_current();
}
c->snap.horizontal = SNAP_H_NONE;
c->snap.vertical = SNAP_V_NONE;
client_resize(c, c->width + KEYBOARD_RESIZE_STEP, c->height);
decorations_render(c, true);
}
void key_resize_window_up(void)
{
Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) {
return;
}
Client *c = ws->focused;
if (!client_is_floating(c)) {
client_set_floating(c, true);
workspace_arrange_current();
}
c->snap.horizontal = SNAP_H_NONE;
c->snap.vertical = SNAP_V_NONE;
int new_height = c->height - KEYBOARD_RESIZE_STEP;
if (new_height < 50) {
new_height = 50;
}
client_resize(c, c->width, new_height);
decorations_render(c, true);
}
void key_resize_window_down(void)
{
Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) {
return;
}
Client *c = ws->focused;
if (!client_is_floating(c)) {
client_set_floating(c, true);
workspace_arrange_current();
}
c->snap.horizontal = SNAP_H_NONE;
c->snap.vertical = SNAP_V_NONE;
client_resize(c, c->width, c->height + KEYBOARD_RESIZE_STEP);
decorations_render(c, true);
}
void key_set_mark(void)
{
marks_start_set_mode();
}
void key_goto_mark(void)
{
marks_start_goto_mode();
}

View File

@ -15,13 +15,19 @@
static const char *layout_names[] = {
"Tiling",
"Floating",
"Monocle"
"Monocle",
"Centered",
"Columns",
"Fibonacci"
};
static const char *layout_symbols[] = {
"[]=",
"><>",
"[M]"
"[M]",
"|M|",
"|||",
"[@]"
};
@ -42,6 +48,15 @@ void layout_arrange(int workspace)
case LAYOUT_MONOCLE:
layout_arrange_monocle(workspace);
break;
case LAYOUT_CENTERED_MASTER:
layout_arrange_centered_master(workspace);
break;
case LAYOUT_COLUMNS:
layout_arrange_columns(workspace);
break;
case LAYOUT_FIBONACCI:
layout_arrange_fibonacci(workspace);
break;
default:
layout_arrange_tiling(workspace);
break;
@ -90,11 +105,21 @@ void layout_arrange_tiling(int workspace)
int master_y = area_y + gap;
int stack_y = area_y + gap;
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->workspace != (unsigned int)workspace) {
for (Client *c = dwn->client_list; c != NULL; ) {
Client *next = c->next;
if (c->flags & CLIENT_UNMANAGING) {
c = next;
continue;
}
if (c->workspace != (unsigned int)workspace) {
c = next;
continue;
}
if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) {
c = next;
continue;
}
@ -141,6 +166,8 @@ void layout_arrange_tiling(int workspace)
actual_w, actual_h);
i++;
c = next;
}
}
@ -150,11 +177,21 @@ void layout_arrange_floating(int workspace)
int area_x, area_y, area_width, area_height;
layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->workspace != (unsigned int)workspace) {
for (Client *c = dwn->client_list; c != NULL; ) {
Client *next = c->next;
if (c->flags & CLIENT_UNMANAGING) {
c = next;
continue;
}
if (c->workspace != (unsigned int)workspace) {
c = next;
continue;
}
if (client_is_minimized(c)) {
c = next;
continue;
}
@ -196,6 +233,8 @@ void layout_arrange_floating(int workspace)
if (c->height < 50) c->height = 50;
client_configure(c);
c = next;
}
}
@ -209,11 +248,21 @@ void layout_arrange_monocle(int workspace)
int title_height = config_get_title_height();
int border = config_get_border_width();
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->workspace != (unsigned int)workspace) {
for (Client *c = dwn->client_list; c != NULL; ) {
Client *next = c->next;
if (c->flags & CLIENT_UNMANAGING) {
c = next;
continue;
}
if (c->workspace != (unsigned int)workspace) {
c = next;
continue;
}
if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) {
c = next;
continue;
}
@ -226,6 +275,8 @@ void layout_arrange_monocle(int workspace)
if (w < 50) w = 50;
client_move_resize(c, x + border, y + title_height + border, w, h);
c = next;
}
}
@ -259,14 +310,27 @@ int layout_count_tiled_clients(int workspace)
{
int count = 0;
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
for (Client *c = dwn->client_list; c != NULL; ) {
Client *next = c->next;
if (c->flags & CLIENT_UNMANAGING) {
c = next;
continue;
}
if (c->workspace != (unsigned int)workspace) {
c = next;
continue;
}
if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) {
c = next;
continue;
}
count++;
c = next;
}
return count;
@ -336,3 +400,279 @@ const char *layout_get_symbol(LayoutType layout)
}
return "???";
}
void layout_arrange_centered_master(int workspace)
{
Workspace *ws = workspace_get(workspace);
if (ws == NULL) {
return;
}
int area_x, area_y, area_width, area_height;
layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);
int gap = config_get_gap();
int title_height = config_get_title_height();
int border = config_get_border_width();
int n = layout_count_tiled_clients(workspace);
if (n == 0) {
return;
}
int master_count = ws->master_count;
if (master_count > n) {
master_count = n;
}
if (n == 1) {
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->flags & CLIENT_UNMANAGING) continue;
if (c->workspace != (unsigned int)workspace) continue;
if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) continue;
int x = area_x + gap + border;
int y = area_y + gap + title_height + border;
int w = area_width - 2 * gap - 2 * border;
int h = area_height - 2 * gap - title_height - 2 * border;
client_move_resize(c, x, y, w, h);
break;
}
return;
}
int stack_count = n - master_count;
int left_stack = stack_count / 2;
int right_stack = stack_count - left_stack;
int master_width = (int)((area_width - 4 * gap) * ws->master_ratio);
int side_width = (area_width - master_width - 4 * gap) / 2;
int master_x = area_x + gap + side_width + gap;
int left_x = area_x + gap;
int right_x = master_x + master_width + gap;
int i = 0;
int master_y = area_y + gap;
int left_y = area_y + gap;
int right_y = area_y + gap;
int left_i = 0;
int right_i = 0;
for (Client *c = dwn->client_list; c != NULL; ) {
Client *next = c->next;
if (c->flags & CLIENT_UNMANAGING) {
c = next;
continue;
}
if (c->workspace != (unsigned int)workspace) {
c = next;
continue;
}
if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) {
c = next;
continue;
}
int x, y, w, h;
if (i < master_count) {
int master_h = (area_height - 2 * gap - (master_count - 1) * gap) / master_count;
x = master_x;
y = master_y;
w = master_width;
h = master_h;
master_y += h + gap;
} else if (left_i < left_stack) {
int left_h = left_stack > 0 ?
(area_height - 2 * gap - (left_stack - 1) * gap) / left_stack : 0;
x = left_x;
y = left_y;
w = side_width;
h = left_h;
left_y += h + gap;
left_i++;
} else {
int right_h = right_stack > 0 ?
(area_height - 2 * gap - (right_stack - 1) * gap) / right_stack : 0;
x = right_x;
y = right_y;
w = side_width;
h = right_h;
right_y += h + gap;
right_i++;
}
int actual_h = h - title_height - 2 * border;
int actual_w = w - 2 * border;
if (actual_h < 50) actual_h = 50;
if (actual_w < 50) actual_w = 50;
client_move_resize(c, x + border, y + title_height + border,
actual_w, actual_h);
i++;
c = next;
}
}
void layout_arrange_columns(int workspace)
{
Workspace *ws = workspace_get(workspace);
if (ws == NULL) {
return;
}
int area_x, area_y, area_width, area_height;
layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);
int gap = config_get_gap();
int title_height = config_get_title_height();
int border = config_get_border_width();
int n = layout_count_tiled_clients(workspace);
if (n == 0) {
return;
}
int col_width = (area_width - (n + 1) * gap) / n;
if (col_width < 50) col_width = 50;
int i = 0;
int col_x = area_x + gap;
for (Client *c = dwn->client_list; c != NULL; ) {
Client *next = c->next;
if (c->flags & CLIENT_UNMANAGING) {
c = next;
continue;
}
if (c->workspace != (unsigned int)workspace) {
c = next;
continue;
}
if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) {
c = next;
continue;
}
int x = col_x;
int y = area_y + gap;
int w = col_width - 2 * border;
int h = area_height - 2 * gap - title_height - 2 * border;
if (w < 50) w = 50;
if (h < 50) h = 50;
client_move_resize(c, x + border, y + title_height + border, w, h);
col_x += col_width + gap;
i++;
c = next;
}
}
void layout_arrange_fibonacci(int workspace)
{
Workspace *ws = workspace_get(workspace);
if (ws == NULL) {
return;
}
int area_x, area_y, area_width, area_height;
layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);
int gap = config_get_gap();
int title_height = config_get_title_height();
int border = config_get_border_width();
int n = layout_count_tiled_clients(workspace);
if (n == 0) {
return;
}
int fib_x = area_x + gap;
int fib_y = area_y + gap;
int fib_w = area_width - 2 * gap;
int fib_h = area_height - 2 * gap;
int i = 0;
for (Client *c = dwn->client_list; c != NULL; ) {
Client *next = c->next;
if (c->flags & CLIENT_UNMANAGING) {
c = next;
continue;
}
if (c->workspace != (unsigned int)workspace) {
c = next;
continue;
}
if (client_is_floating(c) || client_is_fullscreen(c) || client_is_minimized(c)) {
c = next;
continue;
}
int x = fib_x;
int y = fib_y;
int w = fib_w;
int h = fib_h;
if (i < n - 1) {
switch (i % 4) {
case 0:
w = (int)(fib_w * ws->master_ratio);
fib_x += w + gap;
fib_w -= w + gap;
break;
case 1:
h = (int)(fib_h * ws->master_ratio);
fib_y += h + gap;
fib_h -= h + gap;
break;
case 2:
w = (int)(fib_w * ws->master_ratio);
fib_w -= w + gap;
x = fib_x + fib_w + gap;
break;
case 3:
h = (int)(fib_h * ws->master_ratio);
fib_h -= h + gap;
y = fib_y + fib_h + gap;
break;
}
}
int actual_w = w - 2 * border;
int actual_h = h - title_height - 2 * border;
if (actual_w < 50) actual_w = 50;
if (actual_h < 50) actual_h = 50;
client_move_resize(c, x + border, y + title_height + border,
actual_w, actual_h);
i++;
c = next;
}
}

View File

@ -25,6 +25,14 @@
#include "screenshot.h"
#include "ocr.h"
#include "util.h"
#include "slider.h"
#include "rules.h"
#include "marks.h"
/* New abstraction layer */
#include "core/wm_client.h"
#include "plugins/layout_plugin.h"
#include "plugins/builtin_layouts.h"
#include <stdio.h>
#include <stdlib.h>
@ -40,6 +48,10 @@
#include <X11/extensions/Xinerama.h>
#include <X11/extensions/XInput2.h>
/* Forward declarations for fade controls (defined in systray.c) */
void fade_controls_init(void);
void fade_controls_cleanup(void);
DWNState *dwn = NULL;
static DWNState dwn_state;
@ -62,35 +74,36 @@ static int last_x_error = 0;
static int x_error_handler(Display *dpy, XErrorEvent *ev)
{
char error_text[256];
XGetErrorText(dpy, ev->error_code, error_text, sizeof(error_text));
(void)dpy;
last_x_error = ev->error_code;
FILE *crash = fopen("/tmp/dwn_crash.log", "a");
if (crash) {
fprintf(crash, "[X_ERROR] code=%d request=%d resource=%lu: %s\n",
ev->error_code, ev->request_code, ev->resourceid, error_text);
fflush(crash);
fsync(fileno(crash));
fclose(crash);
}
/* Async-signal-safe error logging using write() only.
* Do NOT use stdio functions (fprintf, fflush, etc.) here as this
* handler may be called from signal context. */
char buf[512];
int len;
if (ev->error_code == BadWindow) {
LOG_DEBUG("X11 BadWindow error (request %d, resource %lu) - window was destroyed",
ev->request_code, ev->resourceid);
return 0;
len = snprintf(buf, sizeof(buf),
"[X_ERROR] BadWindow (request %d, resource %lu)\n",
ev->request_code, ev->resourceid);
} else if (ev->error_code == BadMatch || ev->error_code == BadValue ||
ev->error_code == BadDrawable || ev->error_code == BadPixmap) {
len = snprintf(buf, sizeof(buf),
"[X_ERROR] code=%d request=%d - continuing\n",
ev->error_code, ev->request_code);
} else {
len = snprintf(buf, sizeof(buf),
"[X_ERROR] code=%d request=%d resource=%lu\n",
ev->error_code, ev->request_code, ev->resourceid);
}
if (ev->error_code == BadMatch || ev->error_code == BadValue ||
ev->error_code == BadDrawable || ev->error_code == BadPixmap) {
LOG_WARN("X11 error: %s (request %d, resource %lu) - continuing",
error_text, ev->request_code, ev->resourceid);
return 0;
if (len > 0 && len < (int)sizeof(buf)) {
ssize_t _wrote = write(STDERR_FILENO, buf, (size_t)len);
(void)_wrote; /* Best effort in signal handler */
}
LOG_WARN("X11 error: %s (request %d, resource %lu)",
error_text, ev->request_code, ev->resourceid);
return 0;
}
@ -98,13 +111,10 @@ static int x_io_error_handler(Display *dpy)
{
(void)dpy;
FILE *crash = fopen("/tmp/dwn_crash.log", "a");
if (crash) {
fprintf(crash, "[IO_ERROR] Fatal X11 I/O error - X server connection lost\n");
fflush(crash);
fsync(fileno(crash));
fclose(crash);
}
/* Async-signal-safe logging - write directly to stderr */
const char msg[] = "[IO_ERROR] Fatal X11 I/O error - X server connection lost\n";
ssize_t _wrote = write(STDERR_FILENO, msg, sizeof(msg) - 1);
(void)_wrote; /* Best effort in signal handler */
fprintf(stderr, "[IO_ERROR] Fatal X11 I/O error - X server connection lost\n");
fflush(stderr);
@ -502,6 +512,27 @@ static void handle_button_press(XButtonEvent *ev)
}
}
/* Handle fade control sliders */
if (fade_speed_control.slider != NULL && fade_speed_control.slider->visible) {
if (ev->window == fade_speed_control.slider->window) {
LOG_DEBUG("Fade speed slider click: x=%d, y=%d", ev->x, ev->y);
slider_handle_click(fade_speed_control.slider, ev->x, ev->y);
return;
} else {
slider_hide(fade_speed_control.slider);
}
}
if (fade_intensity_control.slider != NULL && fade_intensity_control.slider->visible) {
if (ev->window == fade_intensity_control.slider->window) {
LOG_DEBUG("Fade intensity slider click: x=%d, y=%d", ev->x, ev->y);
slider_handle_click(fade_intensity_control.slider, ev->x, ev->y);
return;
} else {
slider_hide(fade_intensity_control.slider);
}
}
Notification *notif = notification_find_by_window(ev->window);
if (notif != NULL) {
notification_close(notif->id);
@ -597,6 +628,14 @@ static void handle_button_release(XButtonEvent *ev)
volume_slider_handle_release(volume_slider);
}
if (fade_speed_control.slider != NULL && fade_speed_control.slider->visible && fade_speed_control.slider->dragging) {
slider_handle_release(fade_speed_control.slider);
}
if (fade_intensity_control.slider != NULL && fade_intensity_control.slider->visible && fade_intensity_control.slider->dragging) {
slider_handle_release(fade_intensity_control.slider);
}
if (dwn->drag_client != NULL) {
api_emit_drag_ended(dwn->drag_client->window, dwn->resizing);
XUngrabPointer(dwn->display, CurrentTime);
@ -617,6 +656,18 @@ static void handle_motion_notify(XMotionEvent *ev)
return;
}
if (fade_speed_control.slider != NULL && fade_speed_control.slider->visible && ev->window == fade_speed_control.slider->window) {
LOG_DEBUG("Fade speed slider motion: x=%d, y=%d", ev->x, ev->y);
slider_handle_motion(fade_speed_control.slider, ev->x, ev->y);
return;
}
if (fade_intensity_control.slider != NULL && fade_intensity_control.slider->visible && ev->window == fade_intensity_control.slider->window) {
LOG_DEBUG("Fade intensity slider motion: x=%d, y=%d", ev->x, ev->y);
slider_handle_motion(fade_intensity_control.slider, ev->x, ev->y);
return;
}
if (dwn->drag_client == NULL) {
return;
}
@ -842,6 +893,12 @@ int dwn_init(void)
extern void config_init_colors(Config *cfg);
config_init_colors(dwn->config);
extern int cli_port;
if (cli_port > 0) {
dwn->config->api_port = cli_port;
LOG_INFO("API port overridden by CLI: %d", cli_port);
}
dwn->font = XLoadQueryFont(dwn->display, dwn->config->font_name);
if (dwn->font == NULL) {
dwn->font = XLoadQueryFont(dwn->display, "fixed");
@ -878,11 +935,18 @@ int dwn_init(void)
workspace_init();
/* Initialize new abstraction layer */
wm_client_manager_init();
wm_layout_manager_init();
wm_layouts_builtin_init();
LOG_INFO("Layout plugin system initialized");
decorations_init();
panels_init();
systray_init();
fade_controls_init();
xembed_init();
@ -910,6 +974,11 @@ int dwn_init(void)
demo_init();
rules_init();
rules_load(NULL);
marks_init();
atoms_setup_ewmh();
XSelectInput(dwn->display, dwn->root,
@ -941,6 +1010,12 @@ void dwn_cleanup(void)
{
LOG_INFO("DWN shutting down");
/* Cleanup new abstraction layer */
wm_layout_manager_shutdown();
wm_client_manager_shutdown();
marks_cleanup();
rules_cleanup();
demo_cleanup();
api_cleanup();
ocr_cleanup();
@ -953,6 +1028,7 @@ void dwn_cleanup(void)
autostart_cleanup();
keys_cleanup();
xembed_cleanup();
fade_controls_cleanup();
systray_cleanup();
panels_cleanup();
decorations_cleanup();
@ -1110,9 +1186,10 @@ static void print_usage(const char *program)
printf("Usage: %s [OPTIONS]\n", program);
printf("\n");
printf("Options:\n");
printf(" -h, --help Show this help message\n");
printf(" -v, --version Show version information\n");
printf(" -c CONFIG Use specified config file\n");
printf(" -h, --help Show this help message\n");
printf(" -v, --version Show version information\n");
printf(" -c, --config FILE Use specified config file\n");
printf(" -p, --port PORT Use specified API port (overrides config)\n");
printf("\n");
printf("Environment variables:\n");
printf(" OPENROUTER_API_KEY Enable AI features\n");
@ -1125,6 +1202,9 @@ static void print_version(void)
printf("AI-enhanced X11 window manager\n");
}
int cli_port = -1;
static char *cli_config = NULL;
int main(int argc, char *argv[])
{
for (int i = 1; i < argc; i++) {
@ -1136,6 +1216,16 @@ int main(int argc, char *argv[])
print_version();
return 0;
}
if ((strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) && i + 1 < argc) {
cli_port = atoi(argv[++i]);
if (cli_port <= 0 || cli_port > 65535) {
fprintf(stderr, "Invalid port number: %s\n", argv[i]);
return EXIT_FAILURE;
}
}
if ((strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config") == 0) && i + 1 < argc) {
cli_config = argv[++i];
}
}
XInitThreads();

193
src/marks.c Normal file
View File

@ -0,0 +1,193 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Window marks implementation
*/
#include "marks.h"
#include "client.h"
#include "workspace.h"
#include "notifications.h"
#include "util.h"
#include <string.h>
#include <ctype.h>
typedef enum {
MARKS_MODE_NONE,
MARKS_MODE_SET,
MARKS_MODE_GOTO
} MarksMode;
static Client *marked_clients[MAX_MARKS];
static MarksMode current_mode = MARKS_MODE_NONE;
void marks_init(void)
{
memset(marked_clients, 0, sizeof(marked_clients));
current_mode = MARKS_MODE_NONE;
LOG_INFO("Window marks system initialized");
}
void marks_cleanup(void)
{
marks_clear_all();
}
static int mark_to_index(char mark)
{
char lower = (char)tolower((unsigned char)mark);
if (lower >= 'a' && lower <= 'z') {
return lower - 'a';
}
return -1;
}
void marks_set(char mark, Client *client)
{
int idx = mark_to_index(mark);
if (idx < 0 || client == NULL) {
return;
}
for (int i = 0; i < MAX_MARKS; i++) {
if (marked_clients[i] == client) {
marked_clients[i] = NULL;
}
}
marked_clients[idx] = client;
char msg[128];
snprintf(msg, sizeof(msg), "Window marked as '%c'", mark);
notification_show("DWN Marks", "Mark Set", msg, NULL, 1500);
LOG_DEBUG("Set mark '%c' for window '%s'", mark, client->title);
}
Client *marks_get(char mark)
{
int idx = mark_to_index(mark);
if (idx < 0) {
return NULL;
}
return marked_clients[idx];
}
void marks_clear(char mark)
{
int idx = mark_to_index(mark);
if (idx >= 0) {
marked_clients[idx] = NULL;
}
}
void marks_clear_all(void)
{
memset(marked_clients, 0, sizeof(marked_clients));
}
void marks_remove_client(Client *client)
{
if (client == NULL) {
return;
}
for (int i = 0; i < MAX_MARKS; i++) {
if (marked_clients[i] == client) {
marked_clients[i] = NULL;
}
}
}
bool marks_is_waiting_for_mark(void)
{
return current_mode == MARKS_MODE_SET;
}
bool marks_is_waiting_for_goto(void)
{
return current_mode == MARKS_MODE_GOTO;
}
void marks_start_set_mode(void)
{
Workspace *ws = workspace_get_current();
if (ws == NULL || ws->focused == NULL) {
notification_show("DWN Marks", "No Window",
"Focus a window first to set a mark", NULL, 2000);
return;
}
current_mode = MARKS_MODE_SET;
notification_show("DWN Marks", "Set Mark",
"Press a-z to set mark for this window", NULL, 0);
}
void marks_start_goto_mode(void)
{
current_mode = MARKS_MODE_GOTO;
notification_show("DWN Marks", "Go to Mark",
"Press a-z to jump to marked window", NULL, 0);
}
void marks_cancel_mode(void)
{
if (current_mode != MARKS_MODE_NONE) {
current_mode = MARKS_MODE_NONE;
notification_close_all();
}
}
bool marks_handle_key(char key)
{
if (current_mode == MARKS_MODE_NONE) {
return false;
}
char lower = (char)tolower((unsigned char)key);
if (lower < 'a' || lower > 'z') {
marks_cancel_mode();
return true;
}
notification_close_all();
if (current_mode == MARKS_MODE_SET) {
Workspace *ws = workspace_get_current();
if (ws != NULL && ws->focused != NULL) {
marks_set(lower, ws->focused);
}
} else if (current_mode == MARKS_MODE_GOTO) {
Client *c = marks_get(lower);
if (c != NULL) {
if (c->workspace != (unsigned int)dwn->current_workspace) {
workspace_switch((int)c->workspace);
}
client_focus(c, true);
client_raise(c);
} else {
char msg[64];
snprintf(msg, sizeof(msg), "No window marked as '%c'", lower);
notification_show("DWN Marks", "Mark Not Found", msg, NULL, 1500);
}
}
current_mode = MARKS_MODE_NONE;
return true;
}
char marks_get_mark_for_client(Client *client)
{
if (client == NULL) {
return '\0';
}
for (int i = 0; i < MAX_MARKS; i++) {
if (marked_clients[i] == client) {
return (char)('a' + i);
}
}
return '\0';
}

Some files were not shown because too many files have changed in this diff Show More