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:
2026-02-07 13:04:52 +01:00
parent 877d3f302c
commit 6b143990bb
181 changed files with 20646 additions and 7198 deletions
+64 -4
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)
# =============================================================================
+101 -7
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
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+3 -1
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:
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+2 -1
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:
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+16 -3
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:
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+3 -1
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:
BIN
View File
Binary file not shown.
+5 -3
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:
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+9
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);
+34
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
+311
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 */
+35
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 */
+9
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
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
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
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
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
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
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
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
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 */
+5 -1
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 {
+11
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);
+3
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
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
+57
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 */
+365
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 */
+435
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
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
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
+20
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
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
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 */
+75
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 */
+22
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);
+305
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>
+3 -1
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>
+132 -81
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>
+64 -27
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) => {
+918 -179
View File
File diff suppressed because it is too large Load Diff
+53 -18
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>
+3 -1
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>
+130 -1
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>
+113 -3
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>
+15 -3
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">
+3 -1
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>
+152 -6
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)
+87
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>
+3 -1
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>
+86 -2
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>
-434
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>
-550
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>
-578
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>
-1085
View File
File diff suppressed because it is too large Load Diff
-779
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>
-491
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>
-510
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>
-319
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>
-561
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>
-304
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 = '';
});
});
-460
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>
+704 -344
View File
File diff suppressed because it is too large Load Diff
+1266 -3
View File
File diff suppressed because it is too large Load Diff
+5 -4
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
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;
}
+27 -6
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;
size_t remaining = max_offset - offset;
if (remaining < 2) { /* Need at least room for one char + null */
break;
}
int written;
if (name[0] == '\0') {
offset += snprintf(names + offset, sizeof(names) - offset, "%d", i + 1);
written = snprintf(names + offset, remaining, "%d", i + 1);
} else {
offset += snprintf(names + offset, sizeof(names) - offset, "%s", name);
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)
+3 -3
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++) {
+806
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
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);
}
}
+24 -4
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) {
+89
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
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
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
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
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;
}
+4 -2
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
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
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;
}
+248
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();
}
+349 -9
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;
}
}
+120 -30
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
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