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:
parent
877d3f302c
commit
6b143990bb
68
Makefile
68
Makefile
@ -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)
|
||||
# =============================================================================
|
||||
|
||||
96
README.md
96
README.md
@ -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**
|
||||
@ -519,11 +606,18 @@ ws.close()
|
||||
```
|
||||
dwn/
|
||||
├── 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
|
||||
├── site/ # Documentation website
|
||||
└── build/ # Build artifacts
|
||||
```
|
||||
|
||||
|
||||
BIN
build/ai.o
BIN
build/ai.o
Binary file not shown.
Binary file not shown.
BIN
build/atoms.o
BIN
build/atoms.o
Binary file not shown.
Binary file not shown.
BIN
build/cJSON.o
BIN
build/cJSON.o
Binary file not shown.
@ -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
build/client.o
BIN
build/client.o
Binary file not shown.
BIN
build/config.o
BIN
build/config.o
Binary file not shown.
Binary file not shown.
BIN
build/demo.o
BIN
build/demo.o
Binary file not shown.
@ -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
build/keys.o
BIN
build/keys.o
Binary file not shown.
BIN
build/layout.o
BIN
build/layout.o
Binary file not shown.
19
build/main.d
19
build/main.d
@ -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
build/main.o
BIN
build/main.o
Binary file not shown.
BIN
build/news.o
BIN
build/news.o
Binary file not shown.
Binary file not shown.
@ -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
build/panel.o
BIN
build/panel.o
Binary file not shown.
@ -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
build/systray.o
BIN
build/systray.o
Binary file not shown.
BIN
build/util.o
BIN
build/util.o
Binary file not shown.
Binary file not shown.
@ -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);
|
||||
|
||||
@ -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
include/backends/backend_interface.h
Normal file
311
include/backends/backend_interface.h
Normal 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
include/backends/x11/x11_backend.h
Normal file
35
include/backends/x11/x11_backend.h
Normal 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 */
|
||||
@ -68,6 +68,10 @@ struct Config {
|
||||
int demo_ai_timeout_ms;
|
||||
int demo_window_timeout_ms;
|
||||
|
||||
/* Fade effects */
|
||||
float fade_speed; /* Animation speed multiplier (0.1 - 3.0) */
|
||||
float fade_intensity; /* Glow intensity (0.0 - 1.0) */
|
||||
|
||||
char color_panel_bg[16];
|
||||
char color_panel_fg[16];
|
||||
char color_workspace_active[16];
|
||||
@ -88,6 +92,7 @@ void config_destroy(Config *cfg);
|
||||
bool config_load(Config *cfg, const char *path);
|
||||
bool config_reload(Config *cfg);
|
||||
void config_set_defaults(Config *cfg);
|
||||
void config_validate(Config *cfg);
|
||||
|
||||
const char *config_get_terminal(void);
|
||||
const char *config_get_launcher(void);
|
||||
@ -96,6 +101,10 @@ int config_get_title_height(void);
|
||||
int config_get_panel_height(void);
|
||||
int config_get_gap(void);
|
||||
const ColorScheme *config_get_colors(void);
|
||||
float config_get_fade_speed(void);
|
||||
float config_get_fade_intensity(void);
|
||||
void config_set_fade_speed(float speed);
|
||||
void config_set_fade_intensity(float intensity);
|
||||
|
||||
typedef void (*ConfigCallback)(const char *section, const char *key,
|
||||
const char *value, void *user_data);
|
||||
|
||||
523
include/core/wm_client.h
Normal file
523
include/core/wm_client.h
Normal file
@ -0,0 +1,523 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* Abstract Client Interface
|
||||
*/
|
||||
|
||||
#ifndef WM_CLIENT_H
|
||||
#define WM_CLIENT_H
|
||||
|
||||
#include "core/wm_types.h"
|
||||
#include "core/wm_string.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Forward declarations */
|
||||
typedef struct Client Client; /* Legacy client structure */
|
||||
|
||||
/*==============================================================================
|
||||
* Abstract Client Type
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct AbstractClient AbstractClient;
|
||||
|
||||
/*==============================================================================
|
||||
* Client State Flags
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_CLIENT_STATE_NORMAL = 0,
|
||||
WM_CLIENT_STATE_FLOATING = (1 << 0),
|
||||
WM_CLIENT_STATE_FULLSCREEN = (1 << 1),
|
||||
WM_CLIENT_STATE_MAXIMIZED = (1 << 2),
|
||||
WM_CLIENT_STATE_MINIMIZED = (1 << 3),
|
||||
WM_CLIENT_STATE_URGENT = (1 << 4),
|
||||
WM_CLIENT_STATE_STICKY = (1 << 5),
|
||||
WM_CLIENT_STATE_HIDDEN = (1 << 6),
|
||||
WM_CLIENT_STATE_FOCUS = (1 << 7),
|
||||
WM_CLIENT_STATE_MAPPED = (1 << 8),
|
||||
WM_CLIENT_STATE_MANAGED = (1 << 9),
|
||||
WM_CLIENT_STATE_DESTROYING = (1 << 10)
|
||||
} WmClientState;
|
||||
|
||||
/*==============================================================================
|
||||
* Client Window Type
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_CLIENT_TYPE_UNKNOWN = 0,
|
||||
WM_CLIENT_TYPE_NORMAL,
|
||||
WM_CLIENT_TYPE_DIALOG,
|
||||
WM_CLIENT_TYPE_DOCK,
|
||||
WM_CLIENT_TYPE_DESKTOP,
|
||||
WM_CLIENT_TYPE_TOOLBAR,
|
||||
WM_CLIENT_TYPE_MENU,
|
||||
WM_CLIENT_TYPE_UTILITY,
|
||||
WM_CLIENT_TYPE_SPLASH,
|
||||
WM_CLIENT_TYPE_NOTIFICATION
|
||||
} WmClientType;
|
||||
|
||||
/*==============================================================================
|
||||
* Client Lifecycle
|
||||
*============================================================================*/
|
||||
|
||||
/**
|
||||
* Create an abstract client from a native window handle.
|
||||
* This takes ownership of the native window.
|
||||
*/
|
||||
AbstractClient* wm_client_create(WmWindowHandle window, WmClientType type);
|
||||
|
||||
/**
|
||||
* Destroy a client and release all associated resources.
|
||||
*/
|
||||
void wm_client_destroy(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Check if a client is valid (not NULL and not being destroyed).
|
||||
*/
|
||||
bool wm_client_is_valid(const AbstractClient *client);
|
||||
|
||||
/*==============================================================================
|
||||
* Native Access (for compatibility during migration)
|
||||
*============================================================================*/
|
||||
|
||||
/**
|
||||
* Get the native window handle.
|
||||
* Note: This should be avoided in backend-agnostic code.
|
||||
*/
|
||||
WmWindowHandle wm_client_get_window(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Get the native frame window handle (if reparented).
|
||||
*/
|
||||
WmWindowHandle wm_client_get_frame(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Get the legacy Client structure.
|
||||
* Note: This is for gradual migration only.
|
||||
*/
|
||||
Client* wm_client_get_legacy(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Create an abstract client wrapper around an existing legacy Client.
|
||||
* Note: The abstract client does NOT own the legacy client.
|
||||
*/
|
||||
AbstractClient* wm_client_from_legacy(Client *client);
|
||||
|
||||
/*==============================================================================
|
||||
* Client Identification
|
||||
*============================================================================*/
|
||||
|
||||
/**
|
||||
* Get the unique client ID.
|
||||
*/
|
||||
WmClientId wm_client_get_id(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Get the client window type.
|
||||
*/
|
||||
WmClientType wm_client_get_type(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Set the client window type.
|
||||
*/
|
||||
void wm_client_set_type(AbstractClient *client, WmClientType type);
|
||||
|
||||
/*==============================================================================
|
||||
* Client State
|
||||
*============================================================================*/
|
||||
|
||||
/**
|
||||
* Get the current state flags.
|
||||
*/
|
||||
WmClientState wm_client_get_state(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Set state flags.
|
||||
*/
|
||||
void wm_client_set_state(AbstractClient *client, WmClientState state);
|
||||
|
||||
/**
|
||||
* Add state flags.
|
||||
*/
|
||||
void wm_client_add_state(AbstractClient *client, WmClientState state);
|
||||
|
||||
/**
|
||||
* Remove state flags.
|
||||
*/
|
||||
void wm_client_remove_state(AbstractClient *client, WmClientState state);
|
||||
|
||||
/**
|
||||
* Toggle state flags.
|
||||
*/
|
||||
void wm_client_toggle_state(AbstractClient *client, WmClientState state);
|
||||
|
||||
/**
|
||||
* Check if a specific state is set.
|
||||
*/
|
||||
bool wm_client_has_state(const AbstractClient *client, WmClientState state);
|
||||
|
||||
/* Convenience state checks */
|
||||
static inline bool wm_client_is_floating(const AbstractClient *c) {
|
||||
return c && wm_client_has_state(c, WM_CLIENT_STATE_FLOATING);
|
||||
}
|
||||
static inline bool wm_client_is_fullscreen(const AbstractClient *c) {
|
||||
return c && wm_client_has_state(c, WM_CLIENT_STATE_FULLSCREEN);
|
||||
}
|
||||
static inline bool wm_client_is_maximized(const AbstractClient *c) {
|
||||
return c && wm_client_has_state(c, WM_CLIENT_STATE_MAXIMIZED);
|
||||
}
|
||||
static inline bool wm_client_is_minimized(const AbstractClient *c) {
|
||||
return c && wm_client_has_state(c, WM_CLIENT_STATE_MINIMIZED);
|
||||
}
|
||||
static inline bool wm_client_is_urgent(const AbstractClient *c) {
|
||||
return c && wm_client_has_state(c, WM_CLIENT_STATE_URGENT);
|
||||
}
|
||||
static inline bool wm_client_is_sticky(const AbstractClient *c) {
|
||||
return c && wm_client_has_state(c, WM_CLIENT_STATE_STICKY);
|
||||
}
|
||||
static inline bool wm_client_is_focused(const AbstractClient *c) {
|
||||
return c && wm_client_has_state(c, WM_CLIENT_STATE_FOCUS);
|
||||
}
|
||||
static inline bool wm_client_is_mapped(const AbstractClient *c) {
|
||||
return c && wm_client_has_state(c, WM_CLIENT_STATE_MAPPED);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client Geometry
|
||||
*============================================================================*/
|
||||
|
||||
/**
|
||||
* Get the client geometry (position and size).
|
||||
*/
|
||||
WmRect wm_client_get_geometry(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Set the client geometry.
|
||||
*/
|
||||
void wm_client_set_geometry(AbstractClient *client, const WmRect *geometry);
|
||||
|
||||
/**
|
||||
* Get the client position.
|
||||
*/
|
||||
WmPoint wm_client_get_position(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Set the client position.
|
||||
*/
|
||||
void wm_client_set_position(AbstractClient *client, int x, int y);
|
||||
|
||||
/**
|
||||
* Get the client size.
|
||||
*/
|
||||
WmSize wm_client_get_size(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Set the client size.
|
||||
*/
|
||||
void wm_client_set_size(AbstractClient *client, int width, int height);
|
||||
|
||||
/**
|
||||
* Get the border width.
|
||||
*/
|
||||
int wm_client_get_border_width(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Set the border width.
|
||||
*/
|
||||
void wm_client_set_border_width(AbstractClient *client, int width);
|
||||
|
||||
/**
|
||||
* Store previous geometry (for restore after maximize/fullscreen).
|
||||
*/
|
||||
void wm_client_save_geometry(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Restore previous geometry.
|
||||
*/
|
||||
void wm_client_restore_geometry(AbstractClient *client);
|
||||
|
||||
/*==============================================================================
|
||||
* Client Workspace
|
||||
*============================================================================*/
|
||||
|
||||
/**
|
||||
* Get the client's workspace ID.
|
||||
*/
|
||||
WmWorkspaceId wm_client_get_workspace(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Set the client's workspace ID.
|
||||
*/
|
||||
void wm_client_set_workspace(AbstractClient *client, WmWorkspaceId workspace);
|
||||
|
||||
/**
|
||||
* Check if the client is on a specific workspace.
|
||||
*/
|
||||
bool wm_client_is_on_workspace(const AbstractClient *client, WmWorkspaceId workspace);
|
||||
|
||||
/**
|
||||
* Check if the client is visible on the current workspace.
|
||||
*/
|
||||
bool wm_client_is_visible_on_current(const AbstractClient *client);
|
||||
|
||||
/*==============================================================================
|
||||
* Client Properties
|
||||
*============================================================================*/
|
||||
|
||||
/**
|
||||
* Get the client title.
|
||||
* Returns a reference to the internal string (do not free).
|
||||
*/
|
||||
const char* wm_client_get_title(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Set the client title.
|
||||
*/
|
||||
void wm_client_set_title(AbstractClient *client, const char *title);
|
||||
|
||||
/**
|
||||
* Get the client class (application class).
|
||||
*/
|
||||
const char* wm_client_get_class(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Set the client class.
|
||||
*/
|
||||
void wm_client_set_class(AbstractClient *client, const char *class_name);
|
||||
|
||||
/**
|
||||
* Get the client instance name.
|
||||
*/
|
||||
const char* wm_client_get_instance(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Set the client instance name.
|
||||
*/
|
||||
void wm_client_set_instance(AbstractClient *client, const char *instance);
|
||||
|
||||
/**
|
||||
* Get the client's taskbar color.
|
||||
*/
|
||||
WmColor wm_client_get_color(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Set the client's taskbar color.
|
||||
*/
|
||||
void wm_client_set_color(AbstractClient *client, WmColor color);
|
||||
|
||||
/*==============================================================================
|
||||
* Client Actions
|
||||
*============================================================================*/
|
||||
|
||||
/**
|
||||
* Focus the client.
|
||||
*/
|
||||
void wm_client_focus(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Unfocus the client.
|
||||
*/
|
||||
void wm_client_unfocus(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Raise the client to the top of the stack.
|
||||
*/
|
||||
void wm_client_raise(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Lower the client to the bottom of the stack.
|
||||
*/
|
||||
void wm_client_lower(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Show/map the client window.
|
||||
*/
|
||||
void wm_client_show(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Hide/unmap the client window.
|
||||
*/
|
||||
void wm_client_hide(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Minimize the client.
|
||||
*/
|
||||
void wm_client_minimize(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Restore the client from minimized state.
|
||||
*/
|
||||
void wm_client_restore(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Close the client (politely request close).
|
||||
*/
|
||||
void wm_client_close(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Kill the client (forcefully terminate).
|
||||
*/
|
||||
void wm_client_kill(AbstractClient *client);
|
||||
|
||||
/*==============================================================================
|
||||
* Client Management
|
||||
*============================================================================*/
|
||||
|
||||
/**
|
||||
* Reparent the client into a frame window.
|
||||
*/
|
||||
void wm_client_reparent(AbstractClient *client, WmWindowHandle frame);
|
||||
|
||||
/**
|
||||
* Unreparent the client from its frame.
|
||||
*/
|
||||
void wm_client_unreparent(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Configure the client (apply size hints, constraints).
|
||||
*/
|
||||
void wm_client_configure(AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Apply size hints to constrain width/height.
|
||||
*/
|
||||
void wm_client_apply_size_hints(AbstractClient *client, int *width, int *height);
|
||||
|
||||
/**
|
||||
* Move and resize a client.
|
||||
*/
|
||||
void wm_client_move_resize(AbstractClient *client, int x, int y, int width, int height);
|
||||
|
||||
/*==============================================================================
|
||||
* Client List Management
|
||||
*============================================================================*/
|
||||
|
||||
/**
|
||||
* Get the next client in the global list.
|
||||
*/
|
||||
AbstractClient* wm_client_get_next(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Get the previous client in the global list.
|
||||
*/
|
||||
AbstractClient* wm_client_get_prev(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Get the first client in the global list.
|
||||
*/
|
||||
AbstractClient* wm_client_get_first(void);
|
||||
|
||||
/**
|
||||
* Get the last client in the global list.
|
||||
*/
|
||||
AbstractClient* wm_client_get_last(void);
|
||||
|
||||
/**
|
||||
* Get the next client in MRU (Most Recently Used) order.
|
||||
*/
|
||||
AbstractClient* wm_client_get_next_mru(const AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Get the previous client in MRU order.
|
||||
*/
|
||||
AbstractClient* wm_client_get_prev_mru(const AbstractClient *client);
|
||||
|
||||
/*==============================================================================
|
||||
* Client Lookup
|
||||
*============================================================================*/
|
||||
|
||||
/**
|
||||
* Find a client by its window handle.
|
||||
*/
|
||||
AbstractClient* wm_client_find_by_window(WmWindowHandle window);
|
||||
|
||||
/**
|
||||
* Find a client by its frame handle.
|
||||
*/
|
||||
AbstractClient* wm_client_find_by_frame(WmWindowHandle frame);
|
||||
|
||||
/**
|
||||
* Find a client by its ID.
|
||||
*/
|
||||
AbstractClient* wm_client_find_by_id(WmClientId id);
|
||||
|
||||
/*==============================================================================
|
||||
* Client Iteration
|
||||
*============================================================================*/
|
||||
|
||||
typedef bool (*WmClientForeachFunc)(AbstractClient *client, void *user_data);
|
||||
|
||||
/**
|
||||
* Iterate over all clients.
|
||||
*/
|
||||
void wm_client_foreach(WmClientForeachFunc func, void *user_data);
|
||||
|
||||
/**
|
||||
* Iterate over clients on a specific workspace.
|
||||
*/
|
||||
void wm_client_foreach_on_workspace(WmWorkspaceId workspace,
|
||||
WmClientForeachFunc func, void *user_data);
|
||||
|
||||
/**
|
||||
* Count total clients.
|
||||
*/
|
||||
int wm_client_count(void);
|
||||
|
||||
/**
|
||||
* Count clients on a specific workspace.
|
||||
*/
|
||||
int wm_client_count_on_workspace(WmWorkspaceId workspace);
|
||||
|
||||
/*==============================================================================
|
||||
* Client Manager
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct WmClientManager WmClientManager;
|
||||
|
||||
/**
|
||||
* Get the global client manager.
|
||||
*/
|
||||
WmClientManager* wm_client_manager_get(void);
|
||||
|
||||
/**
|
||||
* Initialize the client manager.
|
||||
*/
|
||||
bool wm_client_manager_init(void);
|
||||
|
||||
/**
|
||||
* Shutdown the client manager.
|
||||
*/
|
||||
void wm_client_manager_shutdown(void);
|
||||
|
||||
/**
|
||||
* Register a client with the manager.
|
||||
*/
|
||||
void wm_client_manager_add(WmClientManager *mgr, AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Unregister a client from the manager.
|
||||
*/
|
||||
void wm_client_manager_remove(WmClientManager *mgr, AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Move a client to the front of the MRU list.
|
||||
*/
|
||||
void wm_client_manager_touch(WmClientManager *mgr, AbstractClient *client);
|
||||
|
||||
/**
|
||||
* Get the focused client.
|
||||
*/
|
||||
AbstractClient* wm_client_manager_get_focused(const WmClientManager *mgr);
|
||||
|
||||
/**
|
||||
* Set the focused client.
|
||||
*/
|
||||
void wm_client_manager_set_focused(WmClientManager *mgr, AbstractClient *client);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WM_CLIENT_H */
|
||||
220
include/core/wm_container.h
Normal file
220
include/core/wm_container.h
Normal file
@ -0,0 +1,220 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* Generic Container Interface
|
||||
*/
|
||||
|
||||
#ifndef WM_CONTAINER_H
|
||||
#define WM_CONTAINER_H
|
||||
|
||||
#include "wm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*==============================================================================
|
||||
* Container Lifecycle
|
||||
*============================================================================*/
|
||||
|
||||
WmContainer* wm_container_create(WmContainerType type, WmDestroyFunc destroy_fn);
|
||||
void wm_container_destroy(WmContainer *container);
|
||||
void wm_container_clear(WmContainer *container);
|
||||
|
||||
WmContainer* wm_container_clone(const WmContainer *container, WmCloneFunc clone_fn);
|
||||
|
||||
/*==============================================================================
|
||||
* Container Properties
|
||||
*============================================================================*/
|
||||
|
||||
WmContainerType wm_container_get_type(const WmContainer *container);
|
||||
size_t wm_container_size(const WmContainer *container);
|
||||
bool wm_container_is_empty(const WmContainer *container);
|
||||
|
||||
/*==============================================================================
|
||||
* List Operations
|
||||
*============================================================================*/
|
||||
|
||||
/* These work for LIST and QUEUE */
|
||||
void wm_list_append(WmContainer *list, void *data);
|
||||
void wm_list_prepend(WmContainer *list, void *data);
|
||||
void wm_list_insert(WmContainer *list, size_t index, void *data);
|
||||
|
||||
bool wm_list_remove(WmContainer *list, void *data);
|
||||
void* wm_list_remove_at(WmContainer *list, size_t index);
|
||||
|
||||
void* wm_list_get(const WmContainer *list, size_t index);
|
||||
void* wm_list_get_first(const WmContainer *list);
|
||||
void* wm_list_get_last(const WmContainer *list);
|
||||
|
||||
size_t wm_list_index_of(const WmContainer *list, void *data);
|
||||
bool wm_list_contains(const WmContainer *list, void *data);
|
||||
|
||||
void* wm_list_find(const WmContainer *list, WmCompareFunc cmp, const void *key);
|
||||
void* wm_list_find_custom(const WmContainer *list,
|
||||
bool (*predicate)(const void *item, const void *user_data),
|
||||
const void *user_data);
|
||||
|
||||
void wm_list_sort(WmContainer *list, WmCompareFunc cmp);
|
||||
void wm_list_reverse(WmContainer *list);
|
||||
|
||||
/*==============================================================================
|
||||
* Array Operations
|
||||
*============================================================================*/
|
||||
|
||||
void wm_array_append(WmContainer *array, void *data);
|
||||
void wm_array_insert(WmContainer *array, size_t index, void *data);
|
||||
void wm_array_set(WmContainer *array, size_t index, void *data);
|
||||
|
||||
void* wm_array_get(const WmContainer *array, size_t index);
|
||||
void* wm_array_remove_at(WmContainer *array, size_t index);
|
||||
|
||||
void wm_array_resize(WmContainer *array, size_t new_size);
|
||||
void wm_array_reserve(WmContainer *array, size_t capacity);
|
||||
size_t wm_array_capacity(const WmContainer *array);
|
||||
|
||||
void wm_array_compact(WmContainer *array);
|
||||
void wm_array_sort(WmContainer *array, WmCompareFunc cmp);
|
||||
|
||||
/*==============================================================================
|
||||
* HashMap Operations
|
||||
*============================================================================*/
|
||||
|
||||
/* Key types supported */
|
||||
typedef enum {
|
||||
WM_HASH_KEY_STRING,
|
||||
WM_HASH_KEY_INT,
|
||||
WM_HASH_KEY_POINTER
|
||||
} WmHashKeyType;
|
||||
|
||||
WmContainer* wm_hashmap_create(WmHashKeyType key_type, WmDestroyFunc destroy_fn);
|
||||
|
||||
void wm_hashmap_insert(WmContainer *map, const void *key, void *value);
|
||||
void* wm_hashmap_get(const WmContainer *map, const void *key);
|
||||
bool wm_hashmap_contains(const WmContainer *map, const void *key);
|
||||
bool wm_hashmap_remove(WmContainer *map, const void *key);
|
||||
|
||||
void* wm_hashmap_lookup(const WmContainer *map, WmCompareFunc cmp, const void *key);
|
||||
|
||||
WmContainer* wm_hashmap_get_keys(const WmContainer *map);
|
||||
WmContainer* wm_hashmap_get_values(const WmContainer *map);
|
||||
|
||||
void wm_hashmap_rehash(WmContainer *map, size_t new_capacity);
|
||||
float wm_hashmap_load_factor(const WmContainer *map);
|
||||
|
||||
/* Convenience functions for string keys */
|
||||
void wm_hashmap_insert_string(WmContainer *map, const char *key, void *value);
|
||||
void* wm_hashmap_get_string(const WmContainer *map, const char *key);
|
||||
bool wm_hashmap_contains_string(const WmContainer *map, const char *key);
|
||||
bool wm_hashmap_remove_string(WmContainer *map, const char *key);
|
||||
|
||||
/* Convenience functions for int keys */
|
||||
void wm_hashmap_insert_int(WmContainer *map, int key, void *value);
|
||||
void* wm_hashmap_get_int(const WmContainer *map, int key);
|
||||
bool wm_hashmap_contains_int(const WmContainer *map, int key);
|
||||
bool wm_hashmap_remove_int(WmContainer *map, int key);
|
||||
|
||||
/*==============================================================================
|
||||
* Stack Operations
|
||||
*============================================================================*/
|
||||
|
||||
void wm_stack_push(WmContainer *stack, void *data);
|
||||
void* wm_stack_pop(WmContainer *stack);
|
||||
void* wm_stack_peek(const WmContainer *stack);
|
||||
|
||||
/*==============================================================================
|
||||
* Queue Operations
|
||||
*============================================================================*/
|
||||
|
||||
void wm_queue_enqueue(WmContainer *queue, void *data);
|
||||
void* wm_queue_dequeue(WmContainer *queue);
|
||||
void* wm_queue_peek(const WmContainer *queue);
|
||||
void* wm_queue_peek_tail(const WmContainer *queue);
|
||||
|
||||
/*==============================================================================
|
||||
* Tree Operations (if type is TREE)
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct WmTreeNode WmTreeNode;
|
||||
|
||||
WmTreeNode* wm_tree_get_root(const WmContainer *tree);
|
||||
WmTreeNode* wm_tree_node_get_left(const WmTreeNode *node);
|
||||
WmTreeNode* wm_tree_node_get_right(const WmTreeNode *node);
|
||||
WmTreeNode* wm_tree_node_get_parent(const WmTreeNode *node);
|
||||
void* wm_tree_node_get_data(const WmTreeNode *node);
|
||||
|
||||
/*==============================================================================
|
||||
* Iteration
|
||||
*============================================================================*/
|
||||
|
||||
WmIterator* wm_container_iterator(const WmContainer *container);
|
||||
WmIterator* wm_container_iterator_reverse(const WmContainer *container);
|
||||
|
||||
void wm_iterator_destroy(WmIterator *it);
|
||||
bool wm_iterator_has_next(const WmIterator *it);
|
||||
void* wm_iterator_next(WmIterator *it);
|
||||
const void* wm_iterator_peek(const WmIterator *it);
|
||||
|
||||
/* HashMap iteration - returns key-value pairs */
|
||||
typedef struct {
|
||||
const void *key;
|
||||
void *value;
|
||||
} WmHashEntry;
|
||||
|
||||
WmIterator* wm_hashmap_iterator(const WmContainer *map);
|
||||
WmHashEntry* wm_hashmap_iterator_next(WmIterator *it);
|
||||
|
||||
/*==============================================================================
|
||||
* Functional Operations
|
||||
*============================================================================*/
|
||||
|
||||
typedef void (*WmForeachFunc)(void *item, void *user_data);
|
||||
typedef void* (*WmMapFunc)(const void *item, void *user_data);
|
||||
typedef bool (*WmFilterFunc)(const void *item, void *user_data);
|
||||
typedef void* (*WmReduceFunc)(void *accumulator, const void *item, void *user_data);
|
||||
|
||||
void wm_container_foreach(const WmContainer *container, WmForeachFunc func, void *user_data);
|
||||
|
||||
WmContainer* wm_container_map(const WmContainer *container, WmContainerType new_type,
|
||||
WmMapFunc func, WmDestroyFunc destroy_fn, void *user_data);
|
||||
|
||||
WmContainer* wm_container_filter(const WmContainer *container,
|
||||
WmFilterFunc predicate, void *user_data);
|
||||
|
||||
void* wm_container_reduce(const WmContainer *container,
|
||||
WmReduceFunc func, void *initial, void *user_data);
|
||||
|
||||
bool wm_container_all_match(const WmContainer *container,
|
||||
WmFilterFunc predicate, void *user_data);
|
||||
|
||||
bool wm_container_any_match(const WmContainer *container,
|
||||
WmFilterFunc predicate, void *user_data);
|
||||
|
||||
/*==============================================================================
|
||||
* Convenience Constructors
|
||||
*============================================================================*/
|
||||
|
||||
static inline WmContainer* wm_list_create(WmDestroyFunc destroy_fn) {
|
||||
return wm_container_create(WM_CONTAINER_LIST, destroy_fn);
|
||||
}
|
||||
|
||||
static inline WmContainer* wm_array_create(WmDestroyFunc destroy_fn) {
|
||||
return wm_container_create(WM_CONTAINER_ARRAY, destroy_fn);
|
||||
}
|
||||
|
||||
static inline WmContainer* wm_hashmap_create_string(WmDestroyFunc destroy_fn) {
|
||||
return wm_hashmap_create(WM_HASH_KEY_STRING, destroy_fn);
|
||||
}
|
||||
|
||||
static inline WmContainer* wm_queue_create(WmDestroyFunc destroy_fn) {
|
||||
return wm_container_create(WM_CONTAINER_QUEUE, destroy_fn);
|
||||
}
|
||||
|
||||
static inline WmContainer* wm_stack_create(WmDestroyFunc destroy_fn) {
|
||||
return wm_container_create(WM_CONTAINER_STACK, destroy_fn);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WM_CONTAINER_H */
|
||||
265
include/core/wm_event.h
Normal file
265
include/core/wm_event.h
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* Event Bus System
|
||||
*/
|
||||
|
||||
#ifndef WM_EVENT_H
|
||||
#define WM_EVENT_H
|
||||
|
||||
#include "wm_types.h"
|
||||
#include "wm_client.h"
|
||||
#include "wm_string.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*==============================================================================
|
||||
* Event Data Structures
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct {
|
||||
WmEventType type;
|
||||
WmTime timestamp;
|
||||
WmId source_id;
|
||||
} WmEventHeader;
|
||||
|
||||
/* Window Events */
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
AbstractClient *client;
|
||||
} WmEventWindowBase;
|
||||
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
AbstractClient *client;
|
||||
} WmEventWindowCreated;
|
||||
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
AbstractClient *client;
|
||||
WmString *old_title;
|
||||
WmString *new_title;
|
||||
} WmEventWindowTitleChanged;
|
||||
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
AbstractClient *client;
|
||||
WmRect old_geometry;
|
||||
WmRect new_geometry;
|
||||
} WmEventWindowMoved;
|
||||
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
AbstractClient *client;
|
||||
WmRect old_geometry;
|
||||
WmRect new_geometry;
|
||||
} WmEventWindowResized;
|
||||
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
AbstractClient *client;
|
||||
WmClientFlags old_flags;
|
||||
WmClientFlags new_flags;
|
||||
} WmEventWindowStateChanged;
|
||||
|
||||
/* Workspace Events */
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
WmWorkspaceId workspace;
|
||||
} WmEventWorkspaceBase;
|
||||
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
WmWorkspaceId old_workspace;
|
||||
WmWorkspaceId new_workspace;
|
||||
} WmEventWorkspaceSwitched;
|
||||
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
WmWorkspaceId workspace;
|
||||
WmLayoutType old_layout;
|
||||
WmLayoutType new_layout;
|
||||
} WmEventWorkspaceLayoutChanged;
|
||||
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
WmWorkspaceId workspace;
|
||||
AbstractClient *client;
|
||||
} WmEventClientWorkspaceChanged;
|
||||
|
||||
/* Input Events */
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
unsigned int keycode;
|
||||
unsigned int keysym;
|
||||
unsigned int modifiers;
|
||||
const char *key_name;
|
||||
} WmEventKey;
|
||||
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
unsigned int button;
|
||||
int x;
|
||||
int y;
|
||||
unsigned int modifiers;
|
||||
} WmEventButton;
|
||||
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
int x;
|
||||
int y;
|
||||
int delta_x;
|
||||
int delta_y;
|
||||
unsigned int modifiers;
|
||||
} WmEventMotion;
|
||||
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
int delta;
|
||||
int x;
|
||||
int y;
|
||||
} WmEventScroll;
|
||||
|
||||
/* Monitor Events */
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
int monitor_index;
|
||||
WmRect geometry;
|
||||
bool primary;
|
||||
} WmEventMonitorAdded;
|
||||
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
int monitor_index;
|
||||
} WmEventMonitorRemoved;
|
||||
|
||||
/* Configuration Events */
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
WmString *config_path;
|
||||
bool success;
|
||||
WmString *error_message;
|
||||
} WmEventConfigReloaded;
|
||||
|
||||
/* Command Events */
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
WmCommandType command;
|
||||
bool success;
|
||||
WmString *error_message;
|
||||
} WmEventCommandExecuted;
|
||||
|
||||
/* Custom Event */
|
||||
typedef struct {
|
||||
WmEventHeader header;
|
||||
WmString *event_name;
|
||||
void *custom_data;
|
||||
WmDestroyFunc destroy_func;
|
||||
} WmEventCustom;
|
||||
|
||||
/*==============================================================================
|
||||
* Event Bus Lifecycle
|
||||
*============================================================================*/
|
||||
|
||||
WmEventBus* wm_event_bus_create(void);
|
||||
void wm_event_bus_destroy(WmEventBus *bus);
|
||||
|
||||
WmEventBus* wm_event_bus_get_default(void);
|
||||
void wm_event_bus_set_default(WmEventBus *bus);
|
||||
|
||||
/*==============================================================================
|
||||
* Event Subscription
|
||||
*============================================================================*/
|
||||
|
||||
WmEventSubscription* wm_event_subscribe(WmEventBus *bus,
|
||||
WmEventType type,
|
||||
WmEventHandler handler,
|
||||
void *user_data);
|
||||
|
||||
WmEventSubscription* wm_event_subscribe_pattern(WmEventBus *bus,
|
||||
WmEventType type_min,
|
||||
WmEventType type_max,
|
||||
WmEventHandler handler,
|
||||
void *user_data);
|
||||
|
||||
WmEventSubscription* wm_event_subscribe_all(WmEventBus *bus,
|
||||
WmEventHandler handler,
|
||||
void *user_data);
|
||||
|
||||
void wm_event_unsubscribe(WmEventBus *bus, WmEventSubscription *subscription);
|
||||
void wm_event_unsubscribe_all(WmEventBus *bus, void *user_data);
|
||||
|
||||
/*==============================================================================
|
||||
* Event Emission
|
||||
*============================================================================*/
|
||||
|
||||
void wm_event_emit(WmEventBus *bus, WmEventType type, const void *event_data);
|
||||
void wm_event_emit_async(WmEventBus *bus, WmEventType type, const void *event_data);
|
||||
|
||||
/* Convenience emitters */
|
||||
void wm_event_emit_window_created(WmEventBus *bus, AbstractClient *client);
|
||||
void wm_event_emit_window_destroyed(WmEventBus *bus, AbstractClient *client);
|
||||
void wm_event_emit_window_focused(WmEventBus *bus, AbstractClient *client, AbstractClient *prev_focus);
|
||||
void wm_event_emit_window_moved(WmEventBus *bus, AbstractClient *client, const WmRect *old_geom);
|
||||
void wm_event_emit_window_resized(WmEventBus *bus, AbstractClient *client, const WmRect *old_geom);
|
||||
void wm_event_emit_window_state_changed(WmEventBus *bus, AbstractClient *client,
|
||||
WmClientFlags old_flags, WmClientFlags new_flags);
|
||||
|
||||
void wm_event_emit_workspace_switched(WmEventBus *bus, WmWorkspaceId old_ws, WmWorkspaceId new_ws);
|
||||
void wm_event_emit_workspace_layout_changed(WmEventBus *bus, WmWorkspaceId ws,
|
||||
WmLayoutType old_layout, WmLayoutType new_layout);
|
||||
|
||||
/*==============================================================================
|
||||
* Event Processing
|
||||
*============================================================================*/
|
||||
|
||||
void wm_event_process(WmEventBus *bus);
|
||||
void wm_event_process_all(WmEventBus *bus);
|
||||
bool wm_event_process_one(WmEventBus *bus);
|
||||
|
||||
void wm_event_dispatch_pending(WmEventBus *bus);
|
||||
int wm_event_get_pending_count(const WmEventBus *bus);
|
||||
|
||||
/*==============================================================================
|
||||
* Event Data Helpers
|
||||
*============================================================================*/
|
||||
|
||||
WmEventHeader* wm_event_get_header(void *event_data);
|
||||
WmTime wm_event_get_timestamp(const void *event_data);
|
||||
const char* wm_event_type_to_string(WmEventType type);
|
||||
WmEventType wm_event_type_from_string(const char *str);
|
||||
|
||||
/*==============================================================================
|
||||
* Event Blocking/Filtering
|
||||
*============================================================================*/
|
||||
|
||||
void wm_event_begin_block(WmEventBus *bus, WmEventType type);
|
||||
void wm_event_end_block(WmEventBus *bus, WmEventType type);
|
||||
bool wm_event_is_blocked(const WmEventBus *bus, WmEventType type);
|
||||
|
||||
void wm_event_suppress(WmEventBus *bus, WmEventType type);
|
||||
void wm_event_unsuppress(WmEventBus *bus, WmEventType type);
|
||||
bool wm_event_is_suppressed(const WmEventBus *bus, WmEventType type);
|
||||
|
||||
/*==============================================================================
|
||||
* Custom Events
|
||||
*============================================================================*/
|
||||
|
||||
void wm_event_emit_custom(WmEventBus *bus, const char *event_name, void *data, WmDestroyFunc destroy_fn);
|
||||
WmEventSubscription* wm_event_subscribe_custom(WmEventBus *bus, const char *event_name,
|
||||
WmEventHandler handler, void *user_data);
|
||||
|
||||
/*==============================================================================
|
||||
* Event Statistics
|
||||
*============================================================================*/
|
||||
|
||||
uint64_t wm_event_get_emit_count(const WmEventBus *bus, WmEventType type);
|
||||
uint64_t wm_event_get_total_emit_count(const WmEventBus *bus);
|
||||
void wm_event_reset_statistics(WmEventBus *bus);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WM_EVENT_H */
|
||||
107
include/core/wm_hashmap.h
Normal file
107
include/core/wm_hashmap.h
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* Abstract Hash Map Container
|
||||
*/
|
||||
|
||||
#ifndef WM_HASHMAP_H
|
||||
#define WM_HASHMAP_H
|
||||
|
||||
#include "core/wm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*==============================================================================
|
||||
* Opaque Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct WmHashMap WmHashMap;
|
||||
|
||||
/*==============================================================================
|
||||
* Function Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef uint32_t (*WmHashFunc)(const void *key);
|
||||
typedef bool (*WmKeyEqualFunc)(const void *a, const void *b);
|
||||
typedef void (*WmHashForeachFunc)(void *key, void *value, void *user_data);
|
||||
|
||||
/*==============================================================================
|
||||
* Hash Functions
|
||||
*============================================================================*/
|
||||
|
||||
static inline uint32_t wm_hash_ptr(const void *ptr) {
|
||||
uintptr_t p = (uintptr_t)ptr;
|
||||
return (uint32_t)(p ^ (p >> 32));
|
||||
}
|
||||
|
||||
static inline uint32_t wm_hash_int(int key) {
|
||||
return (uint32_t)key;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Lifecycle
|
||||
*============================================================================*/
|
||||
|
||||
WmHashMap* wm_hashmap_new(void);
|
||||
WmHashMap* wm_hashmap_new_full(WmHashFunc hash_func, WmKeyEqualFunc key_equal,
|
||||
WmFreeFunc key_free, WmFreeFunc value_free);
|
||||
WmHashMap* wm_hashmap_new_string_key(void);
|
||||
void wm_hashmap_destroy(WmHashMap *map);
|
||||
WmHashMap* wm_hashmap_clone(const WmHashMap *map);
|
||||
|
||||
/*==============================================================================
|
||||
* Capacity
|
||||
*============================================================================*/
|
||||
|
||||
size_t wm_hashmap_size(const WmHashMap *map);
|
||||
bool wm_hashmap_is_empty(const WmHashMap *map);
|
||||
void wm_hashmap_clear(WmHashMap *map);
|
||||
|
||||
/*==============================================================================
|
||||
* Element Access
|
||||
*============================================================================*/
|
||||
|
||||
void* wm_hashmap_get(const WmHashMap *map, const void *key);
|
||||
void* wm_hashmap_get_or_default(const WmHashMap *map, const void *key, void *default_val);
|
||||
bool wm_hashmap_contains(const WmHashMap *map, const void *key);
|
||||
|
||||
/*==============================================================================
|
||||
* Modifiers
|
||||
*============================================================================*/
|
||||
|
||||
bool wm_hashmap_insert(WmHashMap *map, void *key, void *value);
|
||||
bool wm_hashmap_insert_no_replace(WmHashMap *map, void *key, void *value);
|
||||
void* wm_hashmap_set(WmHashMap *map, void *key, void *value);
|
||||
void* wm_hashmap_remove(WmHashMap *map, const void *key);
|
||||
bool wm_hashmap_steal(WmHashMap *map, const void *key);
|
||||
|
||||
/*==============================================================================
|
||||
* Iteration
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct {
|
||||
void *map; /* Opaque pointer to hashmap */
|
||||
size_t bucket;
|
||||
void *entry; /* Opaque pointer to current entry */
|
||||
void *next_entry; /* Opaque pointer to next entry */
|
||||
} WmHashMapIter;
|
||||
|
||||
void wm_hashmap_iter_init(WmHashMapIter *iter, WmHashMap *map);
|
||||
bool wm_hashmap_iter_next(WmHashMapIter *iter, void **key, void **value);
|
||||
void wm_hashmap_foreach(const WmHashMap *map, WmHashForeachFunc func, void *user_data);
|
||||
|
||||
/*==============================================================================
|
||||
* String Helpers
|
||||
*============================================================================*/
|
||||
|
||||
bool wm_hashmap_insert_string(WmHashMap *map, const char *key, void *value);
|
||||
void* wm_hashmap_get_string(const WmHashMap *map, const char *key);
|
||||
bool wm_hashmap_contains_string(const WmHashMap *map, const char *key);
|
||||
void* wm_hashmap_remove_string(WmHashMap *map, const char *key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WM_HASHMAP_H */
|
||||
123
include/core/wm_list.h
Normal file
123
include/core/wm_list.h
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* Abstract List Container
|
||||
*/
|
||||
|
||||
#ifndef WM_LIST_H
|
||||
#define WM_LIST_H
|
||||
|
||||
#include "core/wm_types.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*==============================================================================
|
||||
* Opaque Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct WmList WmList;
|
||||
|
||||
/*==============================================================================
|
||||
* Function Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef void (*WmFreeFunc)(void *item);
|
||||
typedef int (*WmCompareFunc)(const void *a, const void *b);
|
||||
typedef bool (*WmForeachFunc)(void *item, size_t index, void *user_data);
|
||||
typedef void* (*WmCloneFunc)(const void *item);
|
||||
|
||||
/*==============================================================================
|
||||
* Lifecycle
|
||||
*============================================================================*/
|
||||
|
||||
WmList* wm_list_new(void);
|
||||
WmList* wm_list_new_sized(size_t initial_capacity);
|
||||
void wm_list_destroy(WmList *list);
|
||||
void wm_list_destroy_no_free(WmList *list);
|
||||
WmList* wm_list_clone(const WmList *list);
|
||||
void wm_list_set_free_func(WmList *list, WmFreeFunc func);
|
||||
|
||||
/*==============================================================================
|
||||
* Capacity
|
||||
*============================================================================*/
|
||||
|
||||
size_t wm_list_size(const WmList *list);
|
||||
size_t wm_list_capacity(const WmList *list);
|
||||
bool wm_list_is_empty(const WmList *list);
|
||||
void wm_list_reserve(WmList *list, size_t capacity);
|
||||
void wm_list_compact(WmList *list);
|
||||
void wm_list_clear(WmList *list);
|
||||
|
||||
/*==============================================================================
|
||||
* Modifiers
|
||||
*============================================================================*/
|
||||
|
||||
void wm_list_append(WmList *list, void *item);
|
||||
void wm_list_prepend(WmList *list, void *item);
|
||||
void wm_list_insert(WmList *list, size_t index, void *item);
|
||||
void wm_list_insert_sorted(WmList *list, void *item, WmCompareFunc compare);
|
||||
void wm_list_remove(WmList *list, void *item);
|
||||
void wm_list_remove_at(WmList *list, size_t index);
|
||||
void* wm_list_take_at(WmList *list, size_t index);
|
||||
bool wm_list_remove_one(WmList *list, void *item);
|
||||
bool wm_list_remove_all(WmList *list, void *item);
|
||||
void* wm_list_pop_back(WmList *list);
|
||||
void* wm_list_pop_front(WmList *list);
|
||||
|
||||
/*==============================================================================
|
||||
* Accessors
|
||||
*============================================================================*/
|
||||
|
||||
void* wm_list_get(const WmList *list, size_t index);
|
||||
void* wm_list_first(const WmList *list);
|
||||
void* wm_list_last(const WmList *list);
|
||||
void* wm_list_front(const WmList *list);
|
||||
void* wm_list_back(const WmList *list);
|
||||
void wm_list_set(WmList *list, size_t index, void *item);
|
||||
|
||||
/*==============================================================================
|
||||
* Iteration
|
||||
*============================================================================*/
|
||||
|
||||
void wm_list_foreach(const WmList *list, WmForeachFunc func, void *user_data);
|
||||
void wm_list_foreach_reverse(const WmList *list, WmForeachFunc func, void *user_data);
|
||||
|
||||
/*==============================================================================
|
||||
* Search
|
||||
*============================================================================*/
|
||||
|
||||
ssize_t wm_list_index_of(const WmList *list, void *item);
|
||||
bool wm_list_contains(const WmList *list, void *item);
|
||||
void* wm_list_find(const WmList *list, WmCompareFunc compare, const void *key);
|
||||
ssize_t wm_list_find_index(const WmList *list, WmCompareFunc compare, const void *key);
|
||||
|
||||
/*==============================================================================
|
||||
* Sorting
|
||||
*============================================================================*/
|
||||
|
||||
void wm_list_sort(WmList *list, WmCompareFunc compare);
|
||||
|
||||
/*==============================================================================
|
||||
* Data Operations
|
||||
*============================================================================*/
|
||||
|
||||
void** wm_list_data(WmList *list);
|
||||
void* wm_list_steal(WmList *list, size_t index);
|
||||
WmList* wm_list_slice(const WmList *list, size_t start, size_t end);
|
||||
void wm_list_move(WmList *list, size_t from, size_t to);
|
||||
void wm_list_swap(WmList *list, size_t i, size_t j);
|
||||
|
||||
/*==============================================================================
|
||||
* Comparison
|
||||
*============================================================================*/
|
||||
|
||||
bool wm_list_equals(const WmList *list1, const WmList *list2);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WM_LIST_H */
|
||||
222
include/core/wm_string.h
Normal file
222
include/core/wm_string.h
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* Abstract String Type
|
||||
*/
|
||||
|
||||
#ifndef WM_STRING_H
|
||||
#define WM_STRING_H
|
||||
|
||||
#include "wm_types.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*==============================================================================
|
||||
* String Lifecycle
|
||||
*============================================================================*/
|
||||
|
||||
WmString* wm_string_new(const char *str);
|
||||
WmString* wm_string_new_empty(void);
|
||||
WmString* wm_string_new_sized(size_t capacity);
|
||||
WmString* wm_string_new_printf(const char *fmt, ...);
|
||||
WmString* wm_string_new_vprintf(const char *fmt, va_list args);
|
||||
WmString* wm_string_new_n(const char *str, size_t n);
|
||||
|
||||
void wm_string_destroy(WmString *str);
|
||||
WmString* wm_string_clone(const WmString *str);
|
||||
|
||||
/*==============================================================================
|
||||
* String Operations
|
||||
*============================================================================*/
|
||||
|
||||
void wm_string_append(WmString *str, const char *suffix);
|
||||
void wm_string_append_char(WmString *str, char c);
|
||||
void wm_string_append_n(WmString *str, const char *suffix, size_t n);
|
||||
void wm_string_append_printf(WmString *str, const char *fmt, ...);
|
||||
void wm_string_append_vprintf(WmString *str, const char *fmt, va_list args);
|
||||
void wm_string_append_string(WmString *str, const WmString *suffix);
|
||||
|
||||
void wm_string_prepend(WmString *str, const char *prefix);
|
||||
void wm_string_prepend_char(WmString *str, char c);
|
||||
|
||||
void wm_string_insert(WmString *str, size_t pos, const char *insert);
|
||||
void wm_string_insert_char(WmString *str, size_t pos, char c);
|
||||
void wm_string_insert_string(WmString *str, size_t pos, const WmString *insert);
|
||||
|
||||
void wm_string_erase(WmString *str, size_t pos, size_t len);
|
||||
void wm_string_clear(WmString *str);
|
||||
|
||||
/*==============================================================================
|
||||
* String Queries
|
||||
*============================================================================*/
|
||||
|
||||
const char* wm_string_cstr(const WmString *str);
|
||||
char* wm_string_detach(WmString *str);
|
||||
|
||||
size_t wm_string_length(const WmString *str);
|
||||
size_t wm_string_capacity(const WmString *str);
|
||||
size_t wm_string_bytes(const WmString *str);
|
||||
|
||||
bool wm_string_is_empty(const WmString *str);
|
||||
bool wm_string_is_null(const WmString *str);
|
||||
|
||||
char wm_string_char_at(const WmString *str, size_t pos);
|
||||
|
||||
/*==============================================================================
|
||||
* String Comparison
|
||||
*============================================================================*/
|
||||
|
||||
bool wm_string_equals(const WmString *str1, const WmString *str2);
|
||||
bool wm_string_equals_cstr(const WmString *str, const char *cstr);
|
||||
|
||||
int wm_string_compare(const WmString *str1, const WmString *str2);
|
||||
int wm_string_compare_cstr(const WmString *str, const char *cstr);
|
||||
int wm_string_case_compare(const WmString *str1, const WmString *str2);
|
||||
int wm_string_case_compare_cstr(const WmString *str, const char *cstr);
|
||||
|
||||
/*==============================================================================
|
||||
* String Searching
|
||||
*============================================================================*/
|
||||
|
||||
bool wm_string_contains(const WmString *str, const char *needle);
|
||||
bool wm_string_contains_char(const WmString *str, char c);
|
||||
|
||||
size_t wm_string_find(const WmString *str, const char *needle, size_t start);
|
||||
size_t wm_string_find_char(const WmString *str, char c, size_t start);
|
||||
size_t wm_string_find_last(const WmString *str, const char *needle);
|
||||
size_t wm_string_find_last_char(const WmString *str, char c);
|
||||
|
||||
bool wm_string_starts_with(const WmString *str, const char *prefix);
|
||||
bool wm_string_starts_with_char(const WmString *str, char c);
|
||||
bool wm_string_ends_with(const WmString *str, const char *suffix);
|
||||
bool wm_string_ends_with_char(const WmString *str, char c);
|
||||
|
||||
/*==============================================================================
|
||||
* String Modification
|
||||
*============================================================================*/
|
||||
|
||||
void wm_string_trim(WmString *str);
|
||||
void wm_string_trim_left(WmString *str);
|
||||
void wm_string_trim_right(WmString *str);
|
||||
|
||||
void wm_string_replace(WmString *str, const char *search, const char *replace);
|
||||
void wm_string_replace_char(WmString *str, char search, char replace);
|
||||
size_t wm_string_replace_all(WmString *str, const char *search, const char *replace);
|
||||
|
||||
void wm_string_to_lower(WmString *str);
|
||||
void wm_string_to_upper(WmString *str);
|
||||
|
||||
void wm_string_reverse(WmString *str);
|
||||
|
||||
/*==============================================================================
|
||||
* String Substrings
|
||||
*============================================================================*/
|
||||
|
||||
WmString* wm_string_substring(const WmString *str, size_t start, size_t len);
|
||||
WmString* wm_string_left(const WmString *str, size_t n);
|
||||
WmString* wm_string_right(const WmString *str, size_t n);
|
||||
|
||||
/*==============================================================================
|
||||
* String Splitting and Joining
|
||||
*============================================================================*/
|
||||
|
||||
WmContainer* wm_string_split(const WmString *str, const char *delimiter);
|
||||
WmContainer* wm_string_split_chars(const WmString *str, const char *delimiters);
|
||||
WmContainer* wm_string_split_lines(const WmString *str);
|
||||
|
||||
WmString* wm_string_join(const WmContainer *strings, const char *separator);
|
||||
WmString* wm_string_join_cstr(const char **strings, size_t count, const char *separator);
|
||||
|
||||
/*==============================================================================
|
||||
* String Formatting
|
||||
*============================================================================*/
|
||||
|
||||
void wm_string_printf(WmString *str, const char *fmt, ...);
|
||||
void wm_string_vprintf(WmString *str, const char *fmt, va_list args);
|
||||
|
||||
/*==============================================================================
|
||||
* String Validation
|
||||
*============================================================================*/
|
||||
|
||||
bool wm_string_is_valid_utf8(const WmString *str);
|
||||
bool wm_string_is_ascii(const WmString *str);
|
||||
bool wm_string_is_numeric(const WmString *str);
|
||||
bool wm_string_is_integer(const WmString *str);
|
||||
bool wm_string_is_float(const WmString *str);
|
||||
|
||||
/*==============================================================================
|
||||
* String Conversion
|
||||
*============================================================================*/
|
||||
|
||||
int wm_string_to_int(const WmString *str, int default_val);
|
||||
long wm_string_to_long(const WmString *str, long default_val);
|
||||
float wm_string_to_float(const WmString *str, float default_val);
|
||||
double wm_string_to_double(const WmString *str, double default_val);
|
||||
bool wm_string_to_bool(const WmString *str, bool default_val);
|
||||
|
||||
WmString* wm_string_from_int(int val);
|
||||
WmString* wm_string_from_long(long val);
|
||||
WmString* wm_string_from_float(float val, int precision);
|
||||
WmString* wm_string_from_double(double val, int precision);
|
||||
WmString* wm_string_from_bool(bool val);
|
||||
|
||||
/*==============================================================================
|
||||
* String Hash
|
||||
*============================================================================*/
|
||||
|
||||
uint32_t wm_string_hash(const WmString *str);
|
||||
uint32_t wm_string_hash_cstr(const char *str);
|
||||
|
||||
/*==============================================================================
|
||||
* String Escaping
|
||||
*============================================================================*/
|
||||
|
||||
WmString* wm_string_escape_json(const WmString *str);
|
||||
WmString* wm_string_escape_xml(const WmString *str);
|
||||
WmString* wm_string_escape_html(const WmString *str);
|
||||
WmString* wm_string_escape_shell(const WmString *str);
|
||||
WmString* wm_string_escape_regex(const WmString *str);
|
||||
|
||||
WmString* wm_string_unescape_json(const WmString *str);
|
||||
WmString* wm_string_unescape_xml(const WmString *str);
|
||||
WmString* wm_string_unescape_html(const WmString *str);
|
||||
|
||||
/*==============================================================================
|
||||
* Static String Helpers
|
||||
*============================================================================*/
|
||||
|
||||
static inline bool wm_cstr_is_empty(const char *str) {
|
||||
return str == NULL || str[0] == '\0';
|
||||
}
|
||||
|
||||
static inline size_t wm_cstr_length(const char *str) {
|
||||
return str == NULL ? 0 : strlen(str);
|
||||
}
|
||||
|
||||
static inline bool wm_cstr_equals(const char *a, const char *b) {
|
||||
if (a == b) return true;
|
||||
if (a == NULL || b == NULL) return false;
|
||||
return strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
static inline bool wm_cstr_case_equals(const char *a, const char *b) {
|
||||
if (a == b) return true;
|
||||
if (a == NULL || b == NULL) return false;
|
||||
return strcasecmp(a, b) == 0;
|
||||
}
|
||||
|
||||
static inline void wm_cstr_copy(char *dest, const char *src, size_t size) {
|
||||
if (size == 0) return;
|
||||
strncpy(dest, src, size - 1);
|
||||
dest[size - 1] = '\0';
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WM_STRING_H */
|
||||
490
include/core/wm_types.h
Normal file
490
include/core/wm_types.h
Normal file
@ -0,0 +1,490 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* Core Abstract Types
|
||||
* Extreme Abstraction Architecture
|
||||
*/
|
||||
|
||||
#ifndef WM_TYPES_H
|
||||
#define WM_TYPES_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*==============================================================================
|
||||
* Version and API Information
|
||||
*============================================================================*/
|
||||
|
||||
#define WM_CORE_VERSION_MAJOR 2
|
||||
#define WM_CORE_VERSION_MINOR 0
|
||||
#define WM_CORE_VERSION_PATCH 0
|
||||
|
||||
#define WM_PLUGIN_API_VERSION 1
|
||||
|
||||
/*==============================================================================
|
||||
* Opaque Type Declarations
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct WmCore WmCore;
|
||||
typedef struct WmBackend WmBackend;
|
||||
typedef struct WmPlugin WmPlugin;
|
||||
typedef struct WmPluginManager WmPluginManager;
|
||||
typedef struct WmEventBus WmEventBus;
|
||||
typedef struct WmEventSubscription WmEventSubscription;
|
||||
typedef struct WmContainer WmContainer;
|
||||
typedef struct WmIterator WmIterator;
|
||||
typedef struct WmString WmString;
|
||||
typedef struct WmRenderer WmRenderer;
|
||||
typedef struct WmSurface WmSurface;
|
||||
typedef struct WmFont WmFont;
|
||||
typedef struct WmImage WmImage;
|
||||
typedef struct WmCommand WmCommand;
|
||||
typedef struct WmCommandBuilder WmCommandBuilder;
|
||||
typedef struct WmConfigSchema WmConfigSchema;
|
||||
typedef struct WmConfigInstance WmConfigInstance;
|
||||
typedef struct WmLayoutEngine WmLayoutEngine;
|
||||
typedef struct WmPanelSystem WmPanelSystem;
|
||||
typedef struct WmAnimation WmAnimation;
|
||||
typedef struct WmTimer WmTimer;
|
||||
typedef struct WmMutex WmMutex;
|
||||
typedef struct WmCondition WmCondition;
|
||||
typedef struct WmThread WmThread;
|
||||
|
||||
/*==============================================================================
|
||||
* Basic Type Definitions
|
||||
*============================================================================*/
|
||||
|
||||
typedef uint32_t WmColor;
|
||||
typedef uint64_t WmTime;
|
||||
typedef uint32_t WmHandle;
|
||||
typedef uint32_t WmId;
|
||||
typedef WmId WmClientId;
|
||||
typedef int WmWorkspaceId;
|
||||
typedef void* WmWindowHandle;
|
||||
typedef void* WmNativeHandle;
|
||||
|
||||
/*==============================================================================
|
||||
* Window Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_WINDOW_TYPE_UNKNOWN = 0,
|
||||
WM_WINDOW_TYPE_NORMAL,
|
||||
WM_WINDOW_TYPE_DIALOG,
|
||||
WM_WINDOW_TYPE_DOCK,
|
||||
WM_WINDOW_TYPE_DESKTOP,
|
||||
WM_WINDOW_TYPE_TOOLBAR,
|
||||
WM_WINDOW_TYPE_MENU,
|
||||
WM_WINDOW_TYPE_UTILITY,
|
||||
WM_WINDOW_TYPE_SPLASH,
|
||||
WM_WINDOW_TYPE_NOTIFICATION,
|
||||
WM_WINDOW_TYPE_COMBO,
|
||||
WM_WINDOW_TYPE_DND,
|
||||
WM_WINDOW_TYPE_POPUP_MENU,
|
||||
WM_WINDOW_TYPE_TOOLTIP
|
||||
} WmWindowType;
|
||||
|
||||
/*==============================================================================
|
||||
* Geometry Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct {
|
||||
int x;
|
||||
int y;
|
||||
} WmPoint;
|
||||
|
||||
typedef struct {
|
||||
int width;
|
||||
int height;
|
||||
} WmSize;
|
||||
|
||||
typedef struct {
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
} WmRect;
|
||||
|
||||
typedef struct {
|
||||
WmPoint location;
|
||||
WmSize size;
|
||||
} WmGeometry;
|
||||
|
||||
typedef struct {
|
||||
int top;
|
||||
int bottom;
|
||||
int left;
|
||||
int right;
|
||||
} WmInsets;
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
} WmPointF;
|
||||
|
||||
typedef struct {
|
||||
float width;
|
||||
float height;
|
||||
} WmSizeF;
|
||||
|
||||
typedef struct {
|
||||
WmPointF location;
|
||||
WmSizeF size;
|
||||
} WmGeometryF;
|
||||
|
||||
/*==============================================================================
|
||||
* Rectangle Operations (Inline)
|
||||
*============================================================================*/
|
||||
|
||||
static inline WmRect wm_rect_make(int x, int y, int width, int height) {
|
||||
WmRect r = { x, y, width, height };
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline bool wm_rect_is_empty(const WmRect *r) {
|
||||
return r == NULL || r->width <= 0 || r->height <= 0;
|
||||
}
|
||||
|
||||
static inline bool wm_rect_contains_point(const WmRect *r, int x, int y) {
|
||||
return r != NULL &&
|
||||
x >= r->x && x < r->x + r->width &&
|
||||
y >= r->y && y < r->y + r->height;
|
||||
}
|
||||
|
||||
static inline bool wm_rect_intersects(const WmRect *a, const WmRect *b) {
|
||||
if (a == NULL || b == NULL) return false;
|
||||
return !(a->x + a->width <= b->x ||
|
||||
b->x + b->width <= a->x ||
|
||||
a->y + a->height <= b->y ||
|
||||
b->y + b->height <= a->y);
|
||||
}
|
||||
|
||||
static inline WmRect wm_rect_intersection(const WmRect *a, const WmRect *b) {
|
||||
WmRect r = { 0, 0, 0, 0 };
|
||||
if (a == NULL || b == NULL) return r;
|
||||
|
||||
int x1 = a->x > b->x ? a->x : b->x;
|
||||
int y1 = a->y > b->y ? a->y : b->y;
|
||||
int x2 = (a->x + a->width) < (b->x + b->width) ? (a->x + a->width) : (b->x + b->width);
|
||||
int y2 = (a->y + a->height) < (b->y + b->height) ? (a->y + a->height) : (b->y + b->height);
|
||||
|
||||
if (x2 > x1 && y2 > y1) {
|
||||
r.x = x1;
|
||||
r.y = y1;
|
||||
r.width = x2 - x1;
|
||||
r.height = y2 - y1;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline WmRect wm_rect_union(const WmRect *a, const WmRect *b) {
|
||||
WmRect r = { 0, 0, 0, 0 };
|
||||
if (a == NULL && b == NULL) return r;
|
||||
if (a == NULL) return *b;
|
||||
if (b == NULL) return *a;
|
||||
|
||||
int x1 = a->x < b->x ? a->x : b->x;
|
||||
int y1 = a->y < b->y ? a->y : b->y;
|
||||
int x2 = (a->x + a->width) > (b->x + b->width) ? (a->x + a->width) : (b->x + b->width);
|
||||
int y2 = (a->y + a->height) > (b->y + b->height) ? (a->y + a->height) : (b->y + b->height);
|
||||
|
||||
r.x = x1;
|
||||
r.y = y1;
|
||||
r.width = x2 - x1;
|
||||
r.height = y2 - y1;
|
||||
return r;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Color Operations (Inline)
|
||||
*============================================================================*/
|
||||
|
||||
static inline WmColor wm_color_rgb(uint8_t r, uint8_t g, uint8_t b) {
|
||||
return (0xFF << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
static inline WmColor wm_color_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
|
||||
return (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
static inline uint8_t wm_color_get_red(WmColor c) {
|
||||
return (c >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
static inline uint8_t wm_color_get_green(WmColor c) {
|
||||
return (c >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
static inline uint8_t wm_color_get_blue(WmColor c) {
|
||||
return c & 0xFF;
|
||||
}
|
||||
|
||||
static inline uint8_t wm_color_get_alpha(WmColor c) {
|
||||
return (c >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client State Flags
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_CLIENT_FLAG_NONE = 0,
|
||||
WM_CLIENT_FLAG_FLOATING = (1 << 0),
|
||||
WM_CLIENT_FLAG_FULLSCREEN = (1 << 1),
|
||||
WM_CLIENT_FLAG_URGENT = (1 << 2),
|
||||
WM_CLIENT_FLAG_MINIMIZED = (1 << 3),
|
||||
WM_CLIENT_FLAG_STICKY = (1 << 4),
|
||||
WM_CLIENT_FLAG_MAXIMIZED = (1 << 5),
|
||||
WM_CLIENT_FLAG_UNMANAGING = (1 << 6),
|
||||
WM_CLIENT_FLAG_FOCUSED = (1 << 7),
|
||||
WM_CLIENT_FLAG_MAPPED = (1 << 8),
|
||||
WM_CLIENT_FLAG_DECORATED = (1 << 9),
|
||||
WM_CLIENT_FLAG_DIALOG = (1 << 10),
|
||||
WM_CLIENT_FLAG_DOCK = (1 << 11),
|
||||
WM_CLIENT_FLAG_UTILITY = (1 << 12),
|
||||
WM_CLIENT_FLAG_SPLASH = (1 << 13),
|
||||
WM_CLIENT_FLAG_MENU = (1 << 14),
|
||||
WM_CLIENT_FLAG_TOOLBAR = (1 << 15)
|
||||
} WmClientFlags;
|
||||
|
||||
/*==============================================================================
|
||||
* Layout Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_LAYOUT_TYPE_TILING,
|
||||
WM_LAYOUT_TYPE_FLOATING,
|
||||
WM_LAYOUT_TYPE_MONOCLE,
|
||||
WM_LAYOUT_TYPE_GRID,
|
||||
WM_LAYOUT_TYPE_SPIRAL,
|
||||
WM_LAYOUT_TYPE_DWINDLE,
|
||||
WM_LAYOUT_TYPE_MAX
|
||||
} WmLayoutType;
|
||||
|
||||
/*==============================================================================
|
||||
* Panel Positions
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_PANEL_POSITION_TOP,
|
||||
WM_PANEL_POSITION_BOTTOM,
|
||||
WM_PANEL_POSITION_LEFT,
|
||||
WM_PANEL_POSITION_RIGHT
|
||||
} WmPanelPosition;
|
||||
|
||||
/*==============================================================================
|
||||
* Event Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
/* Window events */
|
||||
WM_EVENT_NONE = 0,
|
||||
WM_EVENT_WINDOW_CREATED,
|
||||
WM_EVENT_WINDOW_DESTROYED,
|
||||
WM_EVENT_WINDOW_MAPPED,
|
||||
WM_EVENT_WINDOW_UNMAPPED,
|
||||
WM_EVENT_WINDOW_CONFIGURED,
|
||||
WM_EVENT_WINDOW_FOCUSED,
|
||||
WM_EVENT_WINDOW_UNFOCUSED,
|
||||
WM_EVENT_WINDOW_RAISED,
|
||||
WM_EVENT_WINDOW_LOWERED,
|
||||
WM_EVENT_WINDOW_MINIMIZED,
|
||||
WM_EVENT_WINDOW_RESTORED,
|
||||
WM_EVENT_WINDOW_MAXIMIZED,
|
||||
WM_EVENT_WINDOW_UNMAXIMIZED,
|
||||
WM_EVENT_WINDOW_FULLSCREENED,
|
||||
WM_EVENT_WINDOW_UNFULLSCREENED,
|
||||
WM_EVENT_WINDOW_FLOATING_CHANGED,
|
||||
WM_EVENT_WINDOW_PROPERTY_CHANGED,
|
||||
WM_EVENT_WINDOW_STATE_CHANGED,
|
||||
WM_EVENT_WINDOW_TITLE_CHANGED,
|
||||
WM_EVENT_WINDOW_CLASS_CHANGED,
|
||||
WM_EVENT_WINDOW_ROLE_CHANGED,
|
||||
WM_EVENT_WINDOW_URGENCY_CHANGED,
|
||||
WM_EVENT_WINDOW_MOVED,
|
||||
WM_EVENT_WINDOW_RESIZED,
|
||||
|
||||
/* Workspace events */
|
||||
WM_EVENT_WORKSPACE_CREATED,
|
||||
WM_EVENT_WORKSPACE_DESTROYED,
|
||||
WM_EVENT_WORKSPACE_SWITCHED,
|
||||
WM_EVENT_WORKSPACE_RENAMED,
|
||||
WM_EVENT_WORKSPACE_LAYOUT_CHANGED,
|
||||
WM_EVENT_WORKSPACE_MASTER_RATIO_CHANGED,
|
||||
WM_EVENT_WORKSPACE_MASTER_COUNT_CHANGED,
|
||||
|
||||
/* Client/workspace relationship */
|
||||
WM_EVENT_CLIENT_ADDED_TO_WORKSPACE,
|
||||
WM_EVENT_CLIENT_REMOVED_FROM_WORKSPACE,
|
||||
|
||||
/* Input events */
|
||||
WM_EVENT_KEY_PRESSED,
|
||||
WM_EVENT_KEY_RELEASED,
|
||||
WM_EVENT_BUTTON_PRESSED,
|
||||
WM_EVENT_BUTTON_RELEASED,
|
||||
WM_EVENT_MOTION,
|
||||
WM_EVENT_ENTER,
|
||||
WM_EVENT_LEAVE,
|
||||
WM_EVENT_SCROLL,
|
||||
|
||||
/* System events */
|
||||
WM_EVENT_MONITOR_ADDED,
|
||||
WM_EVENT_MONITOR_REMOVED,
|
||||
WM_EVENT_MONITOR_CONFIGURED,
|
||||
WM_EVENT_SCREEN_RESIZED,
|
||||
|
||||
/* Configuration events */
|
||||
WM_EVENT_CONFIG_RELOADED,
|
||||
WM_EVENT_CONFIG_CHANGED,
|
||||
|
||||
/* Lifecycle events */
|
||||
WM_EVENT_WM_STARTED,
|
||||
WM_EVENT_WM_SHUTDOWN,
|
||||
WM_EVENT_WM_READY,
|
||||
|
||||
/* Plugin events */
|
||||
WM_EVENT_PLUGIN_LOADED,
|
||||
WM_EVENT_PLUGIN_UNLOADED,
|
||||
WM_EVENT_PLUGIN_ERROR,
|
||||
|
||||
/* Custom events for plugins */
|
||||
WM_EVENT_CUSTOM = 1000
|
||||
} WmEventType;
|
||||
|
||||
/*==============================================================================
|
||||
* Container Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_CONTAINER_LIST,
|
||||
WM_CONTAINER_ARRAY,
|
||||
WM_CONTAINER_HASHMAP,
|
||||
WM_CONTAINER_QUEUE,
|
||||
WM_CONTAINER_STACK,
|
||||
WM_CONTAINER_TREE
|
||||
} WmContainerType;
|
||||
|
||||
/*==============================================================================
|
||||
* Command Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_COMMAND_NONE = 0,
|
||||
|
||||
/* Window commands */
|
||||
WM_COMMAND_WINDOW_FOCUS,
|
||||
WM_COMMAND_WINDOW_MOVE,
|
||||
WM_COMMAND_WINDOW_RESIZE,
|
||||
WM_COMMAND_WINDOW_MOVE_RESIZE,
|
||||
WM_COMMAND_WINDOW_CLOSE,
|
||||
WM_COMMAND_WINDOW_KILL,
|
||||
WM_COMMAND_WINDOW_MINIMIZE,
|
||||
WM_COMMAND_WINDOW_RESTORE,
|
||||
WM_COMMAND_WINDOW_MAXIMIZE,
|
||||
WM_COMMAND_WINDOW_UNMAXIMIZE,
|
||||
WM_COMMAND_WINDOW_FULLSCREEN,
|
||||
WM_COMMAND_WINDOW_UNFULLSCREEN,
|
||||
WM_COMMAND_WINDOW_FLOAT,
|
||||
WM_COMMAND_WINDOW_UNFLOAT,
|
||||
WM_COMMAND_WINDOW_RAISE,
|
||||
WM_COMMAND_WINDOW_LOWER,
|
||||
WM_COMMAND_WINDOW_SET_WORKSPACE,
|
||||
|
||||
/* Workspace commands */
|
||||
WM_COMMAND_WORKSPACE_SWITCH,
|
||||
WM_COMMAND_WORKSPACE_CREATE,
|
||||
WM_COMMAND_WORKSPACE_DESTROY,
|
||||
WM_COMMAND_WORKSPACE_RENAME,
|
||||
WM_COMMAND_WORKSPACE_SET_LAYOUT,
|
||||
WM_COMMAND_WORKSPACE_ADJUST_MASTER_RATIO,
|
||||
WM_COMMAND_WORKSPACE_ADJUST_MASTER_COUNT,
|
||||
WM_COMMAND_WORKSPACE_SWAP,
|
||||
|
||||
/* Client manipulation */
|
||||
WM_COMMAND_CLIENT_SWAP,
|
||||
WM_COMMAND_CLIENT_SWAP_MASTER,
|
||||
WM_COMMAND_CLIENT_CYCLE,
|
||||
WM_COMMAND_CLIENT_CYCLE_REVERSE,
|
||||
|
||||
/* System commands */
|
||||
WM_COMMAND_RELOAD_CONFIG,
|
||||
WM_COMMAND_QUIT,
|
||||
WM_COMMAND_RESTART,
|
||||
|
||||
/* Plugin commands */
|
||||
WM_COMMAND_PLUGIN_LOAD,
|
||||
WM_COMMAND_PLUGIN_UNLOAD,
|
||||
WM_COMMAND_PLUGIN_ENABLE,
|
||||
WM_COMMAND_PLUGIN_DISABLE
|
||||
} WmCommandType;
|
||||
|
||||
/*==============================================================================
|
||||
* Result/Error Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_RESULT_OK = 0,
|
||||
WM_RESULT_ERROR_GENERIC = -1,
|
||||
WM_RESULT_ERROR_INVALID_ARG = -2,
|
||||
WM_RESULT_ERROR_NO_MEMORY = -3,
|
||||
WM_RESULT_ERROR_NOT_FOUND = -4,
|
||||
WM_RESULT_ERROR_EXISTS = -5,
|
||||
WM_RESULT_ERROR_PERMISSION = -6,
|
||||
WM_RESULT_ERROR_NOT_SUPPORTED = -7,
|
||||
WM_RESULT_ERROR_NOT_IMPLEMENTED = -8,
|
||||
WM_RESULT_ERROR_BACKEND = -9,
|
||||
WM_RESULT_ERROR_PLUGIN = -10,
|
||||
WM_RESULT_ERROR_TIMEOUT = -11,
|
||||
WM_RESULT_ERROR_CANCELED = -12
|
||||
} WmResult;
|
||||
|
||||
/*==============================================================================
|
||||
* Callback Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef void (*WmDestroyFunc)(void *data);
|
||||
typedef int (*WmCompareFunc)(const void *a, const void *b);
|
||||
typedef uint32_t (*WmHashFunc)(const void *key);
|
||||
typedef void* (*WmCloneFunc)(const void *data);
|
||||
typedef void (*WmEventHandler)(WmEventType type, const void *event_data, void *user_data);
|
||||
typedef void (*WmTimerCallback)(WmTimer *timer, void *user_data);
|
||||
typedef void* (*WmThreadFunc)(void *arg);
|
||||
|
||||
/*==============================================================================
|
||||
* Memory Management Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef void* (*WmAllocFunc)(size_t size);
|
||||
typedef void* (*WmReallocFunc)(void *ptr, size_t old_size, size_t new_size);
|
||||
typedef void (*WmFreeFunc)(void *ptr);
|
||||
|
||||
typedef struct {
|
||||
WmAllocFunc alloc;
|
||||
WmReallocFunc realloc;
|
||||
WmFreeFunc free;
|
||||
} WmAllocator;
|
||||
|
||||
/*==============================================================================
|
||||
* Logging Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_LOG_LEVEL_DEBUG,
|
||||
WM_LOG_LEVEL_INFO,
|
||||
WM_LOG_LEVEL_WARN,
|
||||
WM_LOG_LEVEL_ERROR,
|
||||
WM_LOG_LEVEL_FATAL
|
||||
} WmLogLevel;
|
||||
|
||||
typedef void (*WmLogHandler)(WmLogLevel level, const char *file, int line,
|
||||
const char *func, const char *message);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WM_TYPES_H */
|
||||
161
include/core/wm_workspace.h
Normal file
161
include/core/wm_workspace.h
Normal 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 */
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -13,6 +13,9 @@ void layout_arrange(int workspace);
|
||||
void layout_arrange_tiling(int workspace);
|
||||
void layout_arrange_floating(int workspace);
|
||||
void layout_arrange_monocle(int workspace);
|
||||
void layout_arrange_centered_master(int workspace);
|
||||
void layout_arrange_columns(int workspace);
|
||||
void layout_arrange_fibonacci(int workspace);
|
||||
|
||||
int layout_get_usable_area(int *x, int *y, int *width, int *height);
|
||||
int layout_count_tiled_clients(int workspace);
|
||||
|
||||
33
include/marks.h
Normal file
33
include/marks.h
Normal 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
include/plugins/builtin_layouts.h
Normal file
57
include/plugins/builtin_layouts.h
Normal 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
include/plugins/layout_plugin.h
Normal file
365
include/plugins/layout_plugin.h
Normal 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
include/plugins/widget_plugin.h
Normal file
435
include/plugins/widget_plugin.h
Normal file
@ -0,0 +1,435 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* Widget Plugin Interface
|
||||
* Panel components (taskbar, clock, workspaces, etc.)
|
||||
*/
|
||||
|
||||
#ifndef WIDGET_PLUGIN_H
|
||||
#define WIDGET_PLUGIN_H
|
||||
|
||||
#include "core/wm_types.h"
|
||||
#include "core/wm_client.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Plugin API Version
|
||||
*============================================================================*/
|
||||
|
||||
#define WM_WIDGET_PLUGIN_API_VERSION 1
|
||||
|
||||
/*==============================================================================
|
||||
* Forward Declarations
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct WidgetPlugin WidgetPlugin;
|
||||
typedef struct WidgetContext WidgetContext;
|
||||
typedef struct WidgetInstance WidgetInstance;
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Position
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_WIDGET_POSITION_LEFT,
|
||||
WM_WIDGET_POSITION_CENTER,
|
||||
WM_WIDGET_POSITION_RIGHT
|
||||
} WmWidgetPosition;
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Size Constraints
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct {
|
||||
int min_width;
|
||||
int min_height;
|
||||
int max_width;
|
||||
int max_height;
|
||||
bool expand_horizontal; /* Take available horizontal space */
|
||||
bool expand_vertical; /* Take available vertical space */
|
||||
} WmWidgetConstraints;
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Geometry
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct {
|
||||
int x, y;
|
||||
int width, height;
|
||||
} WmWidgetGeometry;
|
||||
|
||||
/*==============================================================================
|
||||
* Widget State
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_WIDGET_STATE_NORMAL = 0,
|
||||
WM_WIDGET_STATE_HOVERED = (1 << 0),
|
||||
WM_WIDGET_STATE_PRESSED = (1 << 1),
|
||||
WM_WIDGET_STATE_ACTIVE = (1 << 2),
|
||||
WM_WIDGET_STATE_DISABLED = (1 << 3),
|
||||
WM_WIDGET_STATE_HIDDEN = (1 << 4),
|
||||
WM_WIDGET_STATE_URGENT = (1 << 5)
|
||||
} WmWidgetState;
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Context
|
||||
*============================================================================*/
|
||||
|
||||
struct WidgetContext {
|
||||
/* Position in panel */
|
||||
WmWidgetPosition position;
|
||||
|
||||
/* Geometry */
|
||||
WmWidgetGeometry geometry;
|
||||
WmWidgetConstraints constraints;
|
||||
|
||||
/* Panel info */
|
||||
WmRect panel_geometry;
|
||||
bool is_top_panel;
|
||||
|
||||
/* Rendering context */
|
||||
void *render_context; /* Backend-specific (X11 GC, cairo, etc.) */
|
||||
WmColor bg_color;
|
||||
WmColor fg_color;
|
||||
|
||||
/* Font */
|
||||
void *font; /* Opaque font handle */
|
||||
int font_size;
|
||||
|
||||
/* User data */
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Event Types
|
||||
*============================================================================*/
|
||||
|
||||
typedef enum {
|
||||
WM_WIDGET_EVENT_NONE,
|
||||
WM_WIDGET_EVENT_MOUSE_ENTER,
|
||||
WM_WIDGET_EVENT_MOUSE_LEAVE,
|
||||
WM_WIDGET_EVENT_MOUSE_MOVE,
|
||||
WM_WIDGET_EVENT_BUTTON_PRESS,
|
||||
WM_WIDGET_EVENT_BUTTON_RELEASE,
|
||||
WM_WIDGET_EVENT_SCROLL,
|
||||
WM_WIDGET_EVENT_KEY_PRESS,
|
||||
WM_WIDGET_EVENT_FOCUS_IN,
|
||||
WM_WIDGET_EVENT_FOCUS_OUT,
|
||||
WM_WIDGET_EVENT_UPDATE, /* Request to redraw */
|
||||
WM_WIDGET_EVENT_CONFIGURE, /* Size/position changed */
|
||||
WM_WIDGET_EVENT_DATA_CHANGED /* External data changed */
|
||||
} WmWidgetEventType;
|
||||
|
||||
typedef struct {
|
||||
WmWidgetEventType type;
|
||||
WmWidgetGeometry widget_geometry;
|
||||
union {
|
||||
struct { int x, y; } point;
|
||||
struct { int button; int x, y; } button;
|
||||
struct { int delta; bool horizontal; } scroll;
|
||||
struct { unsigned int keycode; unsigned int state; } key;
|
||||
} data;
|
||||
} WmWidgetEvent;
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Configuration
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *value;
|
||||
} WmWidgetConfigOption;
|
||||
|
||||
typedef struct {
|
||||
WmWidgetConfigOption *options;
|
||||
int num_options;
|
||||
} WmWidgetConfig;
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Information
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct {
|
||||
const char *id; /* Unique identifier */
|
||||
const char *name; /* Display name */
|
||||
const char *description; /* Description */
|
||||
const char *author; /* Author */
|
||||
const char *version; /* Version */
|
||||
|
||||
/* Default constraints */
|
||||
WmWidgetConstraints default_constraints;
|
||||
|
||||
/* Default position */
|
||||
WmWidgetPosition default_position;
|
||||
|
||||
/* Configuration schema */
|
||||
const char **config_options; /* List of accepted config option names */
|
||||
int num_config_options;
|
||||
} WmWidgetInfo;
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Plugin Interface
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct {
|
||||
/* Version check */
|
||||
int api_version;
|
||||
|
||||
/* Metadata */
|
||||
WmWidgetInfo info;
|
||||
|
||||
/* Lifecycle */
|
||||
bool (*init)(WidgetPlugin *plugin);
|
||||
void (*shutdown)(WidgetPlugin *plugin);
|
||||
|
||||
/* Instance management */
|
||||
WidgetInstance* (*instance_create)(WidgetPlugin *plugin,
|
||||
const WmWidgetConfig *config);
|
||||
void (*instance_destroy)(WidgetPlugin *plugin, WidgetInstance *instance);
|
||||
void (*instance_configure)(WidgetPlugin *plugin, WidgetInstance *instance,
|
||||
const WmWidgetConfig *config);
|
||||
|
||||
/* Rendering */
|
||||
void (*render)(WidgetPlugin *plugin, WidgetInstance *instance,
|
||||
WidgetContext *ctx);
|
||||
|
||||
/* Event handling */
|
||||
bool (*handle_event)(WidgetPlugin *plugin, WidgetInstance *instance,
|
||||
const WmWidgetEvent *event);
|
||||
|
||||
/* Update notification */
|
||||
void (*update)(WidgetPlugin *plugin, WidgetInstance *instance);
|
||||
|
||||
/* Size calculation */
|
||||
void (*get_preferred_size)(WidgetPlugin *plugin, WidgetInstance *instance,
|
||||
int *width, int *height);
|
||||
|
||||
} WidgetPluginInterface;
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Instance
|
||||
*============================================================================*/
|
||||
|
||||
struct WidgetInstance {
|
||||
/* Plugin that created this instance */
|
||||
WidgetPlugin *plugin;
|
||||
|
||||
/* Instance state */
|
||||
WmWidgetState state;
|
||||
WmWidgetGeometry geometry;
|
||||
WmWidgetConstraints constraints;
|
||||
|
||||
/* Configuration */
|
||||
WmWidgetConfig config;
|
||||
|
||||
/* Context */
|
||||
WidgetContext *context;
|
||||
|
||||
/* Plugin-private data */
|
||||
void *user_data;
|
||||
|
||||
/* Linked list for panel */
|
||||
WidgetInstance *next;
|
||||
WidgetInstance *prev;
|
||||
};
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Plugin Structure
|
||||
*============================================================================*/
|
||||
|
||||
struct WidgetPlugin {
|
||||
/* Interface vtable */
|
||||
const WidgetPluginInterface *interface;
|
||||
|
||||
/* Plugin handle (for dynamic loading) */
|
||||
void *handle;
|
||||
|
||||
/* Plugin path */
|
||||
char *path;
|
||||
|
||||
/* Enabled state */
|
||||
bool enabled;
|
||||
|
||||
/* Instances */
|
||||
WidgetInstance *instances;
|
||||
int instance_count;
|
||||
|
||||
/* Plugin-private data */
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Manager
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct WidgetManager WidgetManager;
|
||||
|
||||
/**
|
||||
* Get the global widget manager.
|
||||
*/
|
||||
WidgetManager* wm_widget_manager_get(void);
|
||||
|
||||
/**
|
||||
* Initialize the widget manager.
|
||||
*/
|
||||
bool wm_widget_manager_init(void);
|
||||
|
||||
/**
|
||||
* Shutdown the widget manager.
|
||||
*/
|
||||
void wm_widget_manager_shutdown(void);
|
||||
|
||||
/**
|
||||
* Register a widget plugin.
|
||||
*/
|
||||
bool wm_widget_manager_register(WidgetManager *mgr, WidgetPlugin *plugin);
|
||||
|
||||
/**
|
||||
* Unregister a widget plugin.
|
||||
*/
|
||||
void wm_widget_manager_unregister(WidgetManager *mgr, WidgetPlugin *plugin);
|
||||
|
||||
/**
|
||||
* Load a widget plugin from a file.
|
||||
*/
|
||||
WidgetPlugin* wm_widget_manager_load(WidgetManager *mgr, const char *path);
|
||||
|
||||
/**
|
||||
* Unload a widget plugin.
|
||||
*/
|
||||
void wm_widget_manager_unload(WidgetManager *mgr, WidgetPlugin *plugin);
|
||||
|
||||
/**
|
||||
* Get a loaded widget by ID.
|
||||
*/
|
||||
WidgetPlugin* wm_widget_manager_get_widget(WidgetManager *mgr, const char *id);
|
||||
|
||||
/**
|
||||
* Get list of available widgets.
|
||||
*/
|
||||
const char** wm_widget_manager_list(WidgetManager *mgr, int *count);
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Instance Operations
|
||||
*============================================================================*/
|
||||
|
||||
/**
|
||||
* Create a widget instance.
|
||||
*/
|
||||
WidgetInstance* wm_widget_create_instance(WidgetPlugin *plugin,
|
||||
const WmWidgetConfig *config);
|
||||
|
||||
/**
|
||||
* Destroy a widget instance.
|
||||
*/
|
||||
void wm_widget_destroy_instance(WidgetInstance *instance);
|
||||
|
||||
/**
|
||||
* Render a widget instance.
|
||||
*/
|
||||
void wm_widget_render(WidgetInstance *instance, WidgetContext *ctx);
|
||||
|
||||
/**
|
||||
* Send an event to a widget instance.
|
||||
*/
|
||||
bool wm_widget_send_event(WidgetInstance *instance, const WmWidgetEvent *event);
|
||||
|
||||
/**
|
||||
* Update a widget instance (request redraw).
|
||||
*/
|
||||
void wm_widget_update(WidgetInstance *instance);
|
||||
|
||||
/**
|
||||
* Get preferred size for a widget instance.
|
||||
*/
|
||||
void wm_widget_get_preferred_size(WidgetInstance *instance,
|
||||
int *width, int *height);
|
||||
|
||||
/*==============================================================================
|
||||
* Widget Panel Integration
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct WidgetPanel WidgetPanel;
|
||||
|
||||
/**
|
||||
* Create a widget panel (container for widget instances).
|
||||
*/
|
||||
WidgetPanel* wm_widget_panel_create(void);
|
||||
|
||||
/**
|
||||
* Destroy a widget panel.
|
||||
*/
|
||||
void wm_widget_panel_destroy(WidgetPanel *panel);
|
||||
|
||||
/**
|
||||
* Add a widget instance to a panel.
|
||||
*/
|
||||
void wm_widget_panel_add(WidgetPanel *panel, WidgetInstance *instance,
|
||||
WmWidgetPosition position);
|
||||
|
||||
/**
|
||||
* Remove a widget instance from a panel.
|
||||
*/
|
||||
void wm_widget_panel_remove(WidgetPanel *panel, WidgetInstance *instance);
|
||||
|
||||
/**
|
||||
* Arrange widgets in a panel.
|
||||
*/
|
||||
void wm_widget_panel_arrange(WidgetPanel *panel, const WmRect *panel_geometry);
|
||||
|
||||
/**
|
||||
* Render all widgets in a panel.
|
||||
*/
|
||||
void wm_widget_panel_render(WidgetPanel *panel);
|
||||
|
||||
/**
|
||||
* Handle mouse event in a panel.
|
||||
*/
|
||||
bool wm_widget_panel_handle_event(WidgetPanel *panel,
|
||||
const WmWidgetEvent *event);
|
||||
|
||||
/*==============================================================================
|
||||
* Built-in Widgets
|
||||
*============================================================================*/
|
||||
|
||||
/* Workspace switcher widget */
|
||||
extern const WidgetPluginInterface wm_widget_workspaces;
|
||||
|
||||
/* Taskbar widget */
|
||||
extern const WidgetPluginInterface wm_widget_taskbar;
|
||||
|
||||
/* Clock widget */
|
||||
extern const WidgetPluginInterface wm_widget_clock;
|
||||
|
||||
/* System tray widget */
|
||||
extern const WidgetPluginInterface wm_widget_systray;
|
||||
|
||||
/* CPU/Memory monitor widget */
|
||||
extern const WidgetPluginInterface wm_widget_system_stats;
|
||||
|
||||
/* News ticker widget */
|
||||
extern const WidgetPluginInterface wm_widget_news;
|
||||
|
||||
/*==============================================================================
|
||||
* Plugin Entry Point
|
||||
*============================================================================*/
|
||||
|
||||
typedef const WidgetPluginInterface* (*WidgetPluginEntryFunc)(void);
|
||||
|
||||
#define WM_WIDGET_PLUGIN_ENTRY "wm_widget_plugin_entry"
|
||||
|
||||
/*==============================================================================
|
||||
* Helper Macros
|
||||
*============================================================================*/
|
||||
|
||||
#define WM_WIDGET_REGISTER(name, iface) \
|
||||
static const WidgetPluginInterface* wm_widget_plugin_entry(void) { return &(iface); }
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* WIDGET_PLUGIN_H */
|
||||
61
include/rules.h
Normal file
61
include/rules.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* retoor <retoor@molodetz.nl>
|
||||
* Window rules engine for auto-applying settings
|
||||
*/
|
||||
|
||||
#ifndef DWN_RULES_H
|
||||
#define DWN_RULES_H
|
||||
|
||||
#include "dwn.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MAX_RULES 64
|
||||
#define RULE_PATTERN_SIZE 128
|
||||
|
||||
typedef enum {
|
||||
RULE_MATCH_CLASS,
|
||||
RULE_MATCH_TITLE,
|
||||
RULE_MATCH_CLASS_AND_TITLE
|
||||
} RuleMatchType;
|
||||
|
||||
typedef struct {
|
||||
RuleMatchType match_type;
|
||||
char class_pattern[RULE_PATTERN_SIZE];
|
||||
char title_pattern[RULE_PATTERN_SIZE];
|
||||
|
||||
bool has_workspace;
|
||||
int workspace;
|
||||
|
||||
bool has_floating;
|
||||
bool floating;
|
||||
|
||||
bool has_sticky;
|
||||
bool sticky;
|
||||
|
||||
bool has_fullscreen;
|
||||
bool fullscreen;
|
||||
|
||||
bool has_size;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
bool has_position;
|
||||
int x;
|
||||
int y;
|
||||
|
||||
bool has_opacity;
|
||||
float opacity;
|
||||
} WindowRule;
|
||||
|
||||
void rules_init(void);
|
||||
void rules_cleanup(void);
|
||||
bool rules_load(const char *path);
|
||||
void rules_add(const WindowRule *rule);
|
||||
void rules_clear(void);
|
||||
int rules_count(void);
|
||||
|
||||
bool rules_apply_to_client(Client *client);
|
||||
bool rules_match_pattern(const char *str, const char *pattern);
|
||||
|
||||
#endif
|
||||
72
include/slider.h
Normal file
72
include/slider.h
Normal 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
|
||||
@ -8,6 +8,7 @@
|
||||
#define DWN_SYSTRAY_H
|
||||
|
||||
#include "dwn.h"
|
||||
#include "slider.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MAX_TRAY_ICONS 32
|
||||
@ -84,12 +85,22 @@ typedef struct {
|
||||
bool dragging;
|
||||
} VolumeSlider;
|
||||
|
||||
/* Generic slider wrapper for fade controls */
|
||||
typedef struct {
|
||||
GenericSlider *slider;
|
||||
int icon_x; /* Position for popup */
|
||||
} FadeControl;
|
||||
|
||||
extern WifiState wifi_state;
|
||||
extern AudioState audio_state;
|
||||
extern BatteryState battery_state;
|
||||
extern MultiBatteryState multi_battery_state;
|
||||
extern VolumeSlider *volume_slider;
|
||||
|
||||
/* Fade control externs */
|
||||
extern FadeControl fade_speed_control;
|
||||
extern FadeControl fade_intensity_control;
|
||||
|
||||
void systray_init(void);
|
||||
void systray_cleanup(void);
|
||||
|
||||
@ -113,6 +124,15 @@ void volume_slider_handle_click(VolumeSlider *slider, int x, int y);
|
||||
void volume_slider_handle_motion(VolumeSlider *slider, int x, int y);
|
||||
void volume_slider_handle_release(VolumeSlider *slider);
|
||||
|
||||
/* Fade control functions */
|
||||
void fade_controls_init(void);
|
||||
void fade_controls_cleanup(void);
|
||||
void fade_controls_render(Panel *panel, int x);
|
||||
int fade_controls_get_width(void);
|
||||
void fade_controls_handle_click(int control_id, int x, int y, int button);
|
||||
int fade_controls_hit_test(int x);
|
||||
void fade_controls_hide_all(void);
|
||||
|
||||
void systray_render(Panel *panel, int x, int *width);
|
||||
int systray_get_width(void);
|
||||
void systray_handle_click(int x, int y, int button);
|
||||
|
||||
263
include/thread_workers.h
Normal file
263
include/thread_workers.h
Normal file
@ -0,0 +1,263 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* retoor <retoor@molodetz.nl>
|
||||
* Specialized Worker Threads - High-Level API
|
||||
*
|
||||
* Pre-built worker types for common async operations:
|
||||
* - I/O Worker: File operations, networking
|
||||
* - Compute Worker: CPU-intensive tasks
|
||||
* - System Worker: /proc monitoring, system stats
|
||||
* - Deferred Worker: Delayed/scheduled execution
|
||||
*/
|
||||
|
||||
#ifndef DWN_THREAD_WORKERS_H
|
||||
#define DWN_THREAD_WORKERS_H
|
||||
|
||||
#include "threading.h"
|
||||
|
||||
/* ============================================================================
|
||||
* I/O Worker - Async File and Network Operations
|
||||
* ============================================================================ */
|
||||
|
||||
typedef void (*IoCallback)(void *result, size_t size, int error, void *user_data);
|
||||
typedef void (*IoProgressCallback)(size_t processed, size_t total, void *user_data);
|
||||
|
||||
/**
|
||||
* Initialize I/O worker subsystem
|
||||
*/
|
||||
bool io_worker_init(void);
|
||||
|
||||
/**
|
||||
* Shutdown I/O worker subsystem
|
||||
*/
|
||||
void io_worker_shutdown(void);
|
||||
|
||||
/**
|
||||
* Read file asynchronously
|
||||
* @param path File path
|
||||
* @param callback Called with file contents
|
||||
* @param user_data Passed to callback
|
||||
* @return Task handle or NULL
|
||||
*/
|
||||
TaskHandle io_read_file_async(const char *path, IoCallback callback, void *user_data);
|
||||
|
||||
/**
|
||||
* Write file asynchronously
|
||||
*/
|
||||
TaskHandle io_write_file_async(const char *path, const void *data, size_t size,
|
||||
IoCallback callback, void *user_data);
|
||||
|
||||
/**
|
||||
* Read directory contents asynchronously
|
||||
* Result is a null-separated list of filenames
|
||||
*/
|
||||
TaskHandle io_read_dir_async(const char *path, IoCallback callback, void *user_data);
|
||||
|
||||
/**
|
||||
* HTTP GET request asynchronously
|
||||
* @param url URL to fetch
|
||||
* @param callback Called with response body
|
||||
* @param user_data Passed to callback
|
||||
* @return Task handle or NULL
|
||||
*/
|
||||
TaskHandle io_http_get_async(const char *url, IoCallback callback, void *user_data);
|
||||
|
||||
/**
|
||||
* HTTP POST request asynchronously
|
||||
*/
|
||||
TaskHandle io_http_post_async(const char *url, const void *data, size_t size,
|
||||
IoCallback callback, void *user_data);
|
||||
|
||||
/**
|
||||
* Download file with progress callback
|
||||
*/
|
||||
TaskHandle io_download_async(const char *url, const char *dest_path,
|
||||
IoProgressCallback progress,
|
||||
IoCallback callback, void *user_data);
|
||||
|
||||
/* ============================================================================
|
||||
* Compute Worker - CPU-Intensive Tasks
|
||||
* ============================================================================ */
|
||||
|
||||
typedef void (*ComputeCallback)(void *result, void *user_data);
|
||||
|
||||
/**
|
||||
* Initialize compute worker subsystem
|
||||
*/
|
||||
bool compute_worker_init(void);
|
||||
|
||||
/**
|
||||
* Shutdown compute worker subsystem
|
||||
*/
|
||||
void compute_worker_shutdown(void);
|
||||
|
||||
/**
|
||||
* Parallel for - distribute work across threads
|
||||
* @param start Start index
|
||||
* @param end End index (exclusive)
|
||||
* @param func Function called for each index
|
||||
* @param user_data Passed to each invocation
|
||||
*/
|
||||
void compute_parallel_for(int start, int end,
|
||||
void (*func)(int index, void *user_data),
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
* Map operation - apply function to each element
|
||||
*/
|
||||
void* compute_map(void *array, size_t count, size_t elem_size,
|
||||
void (*func)(void *in, void *out, void *user_data),
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
* Reduce operation - aggregate values
|
||||
*/
|
||||
void* compute_reduce(void *array, size_t count, size_t elem_size,
|
||||
void (*func)(void *accum, void *elem, void *user_data),
|
||||
void *initial, void *user_data);
|
||||
|
||||
/**
|
||||
* Submit compute task
|
||||
*/
|
||||
TaskHandle compute_submit(void (*func)(void *user_data), void *user_data,
|
||||
ComputeCallback callback, void *callback_data);
|
||||
|
||||
/* ============================================================================
|
||||
* System Worker - System Monitoring
|
||||
* ============================================================================ */
|
||||
|
||||
typedef struct {
|
||||
float cpu_percent;
|
||||
uint64_t memory_used;
|
||||
uint64_t memory_total;
|
||||
float memory_percent;
|
||||
float load_avg[3];
|
||||
uint64_t uptime_seconds;
|
||||
} SystemStats;
|
||||
|
||||
typedef struct {
|
||||
uint32_t pid;
|
||||
char name[64];
|
||||
float cpu_percent;
|
||||
uint64_t memory_bytes;
|
||||
} ProcessInfo;
|
||||
|
||||
typedef void (*SystemStatsCallback)(const SystemStats *stats, void *user_data);
|
||||
typedef void (*ProcessListCallback)(ProcessInfo *processes, uint32_t count, void *user_data);
|
||||
|
||||
/**
|
||||
* Initialize system worker
|
||||
*/
|
||||
bool system_worker_init(void);
|
||||
|
||||
/**
|
||||
* Shutdown system worker
|
||||
*/
|
||||
void system_worker_shutdown(void);
|
||||
|
||||
/**
|
||||
* Get cached system stats (non-blocking, updated periodically)
|
||||
*/
|
||||
bool system_worker_get_cached_stats(SystemStats *stats);
|
||||
|
||||
/**
|
||||
* Get fresh system stats (blocking)
|
||||
*/
|
||||
Future* system_worker_get_stats_async(void);
|
||||
|
||||
/**
|
||||
* Get process list asynchronously
|
||||
*/
|
||||
TaskHandle system_worker_get_processes_async(ProcessListCallback callback, void *user_data);
|
||||
|
||||
/**
|
||||
* Subscribe to periodic stats updates
|
||||
* @param interval_ms Update interval (0 to disable)
|
||||
* @param callback Called with new stats
|
||||
* @param user_data Passed to callback
|
||||
* @return Subscription ID
|
||||
*/
|
||||
uint32_t system_worker_subscribe_stats(uint32_t interval_ms,
|
||||
SystemStatsCallback callback,
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
* Unsubscribe from stats updates
|
||||
*/
|
||||
bool system_worker_unsubscribe_stats(uint32_t subscription_id);
|
||||
|
||||
/* ============================================================================
|
||||
* Deferred Worker - Delayed Execution
|
||||
* ============================================================================ */
|
||||
|
||||
typedef uint64_t DeferredId;
|
||||
typedef void (*DeferredCallback)(DeferredId id, void *user_data);
|
||||
|
||||
/**
|
||||
* Initialize deferred worker
|
||||
*/
|
||||
bool deferred_worker_init(void);
|
||||
|
||||
/**
|
||||
* Shutdown deferred worker
|
||||
*/
|
||||
void deferred_worker_shutdown(void);
|
||||
|
||||
/**
|
||||
* Execute after delay
|
||||
* @param delay_ms Milliseconds to wait
|
||||
* @param callback Function to call
|
||||
* @param user_data Passed to callback
|
||||
* @return Deferred ID
|
||||
*/
|
||||
DeferredId deferred_after(uint64_t delay_ms, DeferredCallback callback, void *user_data);
|
||||
|
||||
/**
|
||||
* Execute at regular interval
|
||||
*/
|
||||
DeferredId deferred_every(uint64_t interval_ms, DeferredCallback callback, void *user_data);
|
||||
|
||||
/**
|
||||
* Cancel deferred execution
|
||||
*/
|
||||
bool deferred_cancel(DeferredId id);
|
||||
|
||||
/**
|
||||
* Process deferred tasks (call from main loop)
|
||||
* @return Number of tasks executed
|
||||
*/
|
||||
uint32_t deferred_process(void);
|
||||
|
||||
/**
|
||||
* Get poll fd for select()
|
||||
*/
|
||||
int deferred_get_poll_fd(void);
|
||||
|
||||
/* ============================================================================
|
||||
* High-Level Integration API
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Initialize all worker subsystems
|
||||
*/
|
||||
bool thread_workers_init(void);
|
||||
|
||||
/**
|
||||
* Shutdown all worker subsystems
|
||||
*/
|
||||
void thread_workers_shutdown(void);
|
||||
|
||||
/**
|
||||
* Process all pending async work (call from main loop)
|
||||
* @return true if work was processed
|
||||
*/
|
||||
bool thread_workers_process_all(void);
|
||||
|
||||
/**
|
||||
* Get combined poll fds for select()
|
||||
* @param fds Array to fill (size at least 4)
|
||||
* @return Number of fds filled
|
||||
*/
|
||||
int thread_workers_get_poll_fds(int *fds);
|
||||
|
||||
#endif /* DWN_THREAD_WORKERS_H */
|
||||
704
include/threading.h
Normal file
704
include/threading.h
Normal 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
include/threading_integration.h
Normal file
75
include/threading_integration.h
Normal 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 */
|
||||
@ -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
manual/abstraction-layer.html
Normal file
305
manual/abstraction-layer.html
Normal 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 <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h1>DWN</h1>
|
||||
<span class="version">v1.0.0</span>
|
||||
<span class="version">v2.0.0</span>
|
||||
</div>
|
||||
|
||||
<div class="search-box">
|
||||
@ -47,6 +47,8 @@
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">Advanced</div>
|
||||
<a href="architecture.html" class="nav-link">Architecture</a>
|
||||
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
|
||||
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
|
||||
<a href="building.html" class="nav-link">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
@ -98,7 +100,7 @@ port = 8777</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="connecting">Connecting</h2>
|
||||
<p>Connect via WebSocket to <code>ws://localhost:8777</code> (or your configured port).</p>
|
||||
<p>Connect via WebSocket to <code>ws://localhost:8777/ws</code> (or your configured port). The <code>/ws</code> path is required.</p>
|
||||
|
||||
<h3>Testing with wscat</h3>
|
||||
<div class="code-block">
|
||||
@ -106,11 +108,11 @@ port = 8777</code></pre>
|
||||
npm install -g wscat
|
||||
|
||||
# Connect to DWN
|
||||
wscat -c ws://localhost:8777
|
||||
wscat -c ws://localhost:8777/ws
|
||||
|
||||
# Send a command
|
||||
> {"command": "list_windows"}
|
||||
< {"status": "ok", "windows": [...]}</code></pre>
|
||||
> {"command": "get_clients"}
|
||||
< {"status": "ok", "clients": [...]}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Testing with websocat</h3>
|
||||
@ -119,7 +121,7 @@ wscat -c ws://localhost:8777
|
||||
cargo install websocat
|
||||
|
||||
# Connect and send command
|
||||
echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
|
||||
echo '{"command": "get_clients"}' | websocat ws://localhost:8777/ws</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="protocol">Protocol</h2>
|
||||
@ -161,24 +163,24 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Windows</td>
|
||||
<td>list_windows, focus_window, close_window, move_window, resize_window</td>
|
||||
<td>Clients</td>
|
||||
<td>get_clients, find_clients, get_focused_client, focus_client, focus_next, focus_prev, focus_master, close_client, kill_client, move_client, resize_client, minimize_client, restore_client, maximize_client, fullscreen_client, float_client, raise_client, lower_client, snap_client</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Workspaces</td>
|
||||
<td>list_workspaces, switch_workspace, move_to_workspace</td>
|
||||
<td>get_workspaces, switch_workspace, switch_workspace_next, switch_workspace_prev, move_client_to_workspace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Layout</td>
|
||||
<td>get_layout, set_layout, set_master_ratio</td>
|
||||
<td>set_layout, cycle_layout, set_master_ratio, adjust_master_ratio, set_master_count, adjust_master_count</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Keyboard</td>
|
||||
<td>key_press, key_release, type_text</td>
|
||||
<td>key_press, key_release, key_tap, key_type, get_keybindings</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mouse</td>
|
||||
<td>mouse_move, mouse_click, mouse_drag</td>
|
||||
<td>mouse_move, mouse_move_relative, mouse_click, mouse_press, mouse_release, mouse_scroll, get_mouse_position</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Screenshot</td>
|
||||
@ -190,7 +192,39 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>System</td>
|
||||
<td>get_monitors, spawn, notify</td>
|
||||
<td>get_status, get_screen_info, run_command, show_desktop, get_config, reload_config</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Notifications</td>
|
||||
<td>notify, close_notification, get_notifications</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>System Tray</td>
|
||||
<td>get_battery_state, get_audio_state, set_audio_volume, toggle_audio_mute, get_wifi_state</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Panels</td>
|
||||
<td>get_panel_state, show_panel, hide_panel, toggle_panel</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>AI</td>
|
||||
<td>ai_is_available, ai_command, exa_is_available, exa_search</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>News</td>
|
||||
<td>get_news, news_next, news_prev, news_open</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Demo/Tutorial</td>
|
||||
<td>start_demo, stop_demo, get_demo_state, start_tutorial, stop_tutorial, get_tutorial_state</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Events</td>
|
||||
<td>subscribe, unsubscribe, list_events</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fade Effects</td>
|
||||
<td>get_fade_settings, set_fade_speed, set_fade_intensity</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -198,32 +232,35 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
|
||||
|
||||
<h2 id="quick-start">Quick Start</h2>
|
||||
|
||||
<h3>List Windows</h3>
|
||||
<h3>List Clients</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{"command": "list_windows"}
|
||||
{"command": "get_clients"}
|
||||
|
||||
// Response
|
||||
{
|
||||
"status": "ok",
|
||||
"windows": [
|
||||
"clients": [
|
||||
{
|
||||
"id": 12345678,
|
||||
"window": 12345678,
|
||||
"title": "Firefox",
|
||||
"class": "firefox",
|
||||
"workspace": 0,
|
||||
"x": 0, "y": 32,
|
||||
"width": 960, "height": 540,
|
||||
"focused": true,
|
||||
"floating": false
|
||||
"floating": false,
|
||||
"fullscreen": false,
|
||||
"maximized": false,
|
||||
"minimized": false
|
||||
}
|
||||
]
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Focus a Window</h3>
|
||||
<h3>Focus a Client</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "focus_window", "window": 12345678}</code></pre>
|
||||
<pre><code>{"command": "focus_client", "window": 12345678}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Switch Workspace</h3>
|
||||
@ -233,7 +270,7 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
|
||||
|
||||
<h3>Type Text</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "type_text", "text": "Hello, World!"}</code></pre>
|
||||
<pre><code>{"command": "key_type", "text": "Hello, World!"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Take Screenshot</h3>
|
||||
@ -263,11 +300,11 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
|
||||
# List windows
|
||||
windows = client.list_windows()
|
||||
# List clients
|
||||
clients = client.get_clients()
|
||||
|
||||
# Focus a window
|
||||
client.focus_window(windows[0]['id'])
|
||||
# Focus a client
|
||||
client.focus_client(clients[0]['window'])
|
||||
|
||||
# Take screenshot
|
||||
result = client.screenshot('fullscreen')
|
||||
@ -280,10 +317,10 @@ client.disconnect()</code></pre>
|
||||
|
||||
<h3>JavaScript (Browser)</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>const ws = new WebSocket('ws://localhost:8777');
|
||||
<pre><code>const ws = new WebSocket('ws://localhost:8777/ws');
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.send(JSON.stringify({command: 'list_windows'}));
|
||||
ws.send(JSON.stringify({command: 'get_clients'}));
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>><></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
manual/plugin-development.html
Normal file
87
manual/plugin-development.html
Normal 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 <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">🤖</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">👁</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">🔍</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">📝</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">🔔</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 <retoor@molodetz.nl> - MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -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 <retoor@molodetz.nl> - MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -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 <retoor@molodetz.nl> - MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1085
site/css/style.css
1085
site/css/style.css
File diff suppressed because it is too large
Load Diff
@ -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 <retoor@molodetz.nl>
|
||||
</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>
|
||||
@ -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 <retoor@molodetz.nl> - MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -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">☷</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">❏</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">☐</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">↔</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">📈</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">👁</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>📱 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>🔋 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>🔊 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>📶 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>🤖 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>🔍 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>🎓 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>💡 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>🎫 Remote Control</h3>
|
||||
<p>Change workspaces, focus windows, and launch applications programmatically
|
||||
from any language that supports WebSockets.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>💻 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>< 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">📚</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">🎬</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 <retoor@molodetz.nl> - MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
319
site/index.html
319
site/index.html
@ -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">☰</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">⚙</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">🎨</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">💡</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">💬</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">🔔</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">💻</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">⚡</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">📰</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">🎓</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">📐</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><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 <retoor@molodetz.nl> - MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -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 <retoor@molodetz.nl> - MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
304
site/js/main.js
304
site/js/main.js
@ -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 = '';
|
||||
});
|
||||
});
|
||||
@ -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 <retoor@molodetz.nl> - MIT License</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -16,10 +16,11 @@
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
#include <pwd.h>
|
||||
#include <limits.h>
|
||||
|
||||
static AppLauncherState launcher_state = {0};
|
||||
|
||||
static char recent_file_path[512] = {0};
|
||||
static char recent_file_path[PATH_MAX] = {0};
|
||||
|
||||
static void scan_desktop_dir(const char *dir);
|
||||
static int parse_desktop_file(const char *path, AppEntry *entry);
|
||||
@ -42,7 +43,7 @@ void applauncher_init(void)
|
||||
snprintf(recent_file_path, sizeof(recent_file_path),
|
||||
"%s/.local/share/dwn/recent_apps", home);
|
||||
|
||||
char dir_path[512];
|
||||
char dir_path[PATH_MAX];
|
||||
snprintf(dir_path, sizeof(dir_path), "%s/.local/share/dwn", home);
|
||||
mkdir(dir_path, 0755);
|
||||
|
||||
@ -66,7 +67,7 @@ void applauncher_refresh(void)
|
||||
|
||||
const char *home = getenv("HOME");
|
||||
if (home) {
|
||||
char user_apps[512];
|
||||
char user_apps[PATH_MAX];
|
||||
snprintf(user_apps, sizeof(user_apps), "%s/.local/share/applications", home);
|
||||
scan_desktop_dir(user_apps);
|
||||
}
|
||||
@ -271,7 +272,7 @@ static void add_to_recent(const char *desktop_id)
|
||||
|
||||
static char *strip_field_codes(const char *exec)
|
||||
{
|
||||
static char result[512];
|
||||
static char result[PATH_MAX];
|
||||
size_t j = 0;
|
||||
|
||||
for (size_t i = 0; exec[i] && j < sizeof(result) - 1; i++) {
|
||||
|
||||
206
src/async_context.c
Normal file
206
src/async_context.c
Normal 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;
|
||||
}
|
||||
37
src/atoms.c
37
src/atoms.c
@ -202,22 +202,43 @@ void atoms_update_desktop_names(void)
|
||||
return;
|
||||
}
|
||||
|
||||
char names[MAX_WORKSPACES * 32];
|
||||
int offset = 0;
|
||||
/* Buffer needs to account for:
|
||||
- MAX_WORKSPACES names
|
||||
- Each name up to sizeof(Workspace.name) - 1 = 31 chars
|
||||
- Plus null terminator for each name
|
||||
- Plus 8 characters for numeric fallback (e.g., "9\0")
|
||||
*/
|
||||
char names[MAX_WORKSPACES * (32 + 1)];
|
||||
size_t offset = 0;
|
||||
size_t max_offset = sizeof(names);
|
||||
|
||||
for (int i = 0; i < MAX_WORKSPACES; i++) {
|
||||
const char *name = dwn->workspaces[i].name;
|
||||
if (name[0] == '\0') {
|
||||
offset += snprintf(names + offset, sizeof(names) - offset, "%d", i + 1);
|
||||
} else {
|
||||
offset += snprintf(names + offset, sizeof(names) - offset, "%s", name);
|
||||
size_t remaining = max_offset - offset;
|
||||
|
||||
if (remaining < 2) { /* Need at least room for one char + null */
|
||||
break;
|
||||
}
|
||||
|
||||
int written;
|
||||
if (name[0] == '\0') {
|
||||
written = snprintf(names + offset, remaining, "%d", i + 1);
|
||||
} else {
|
||||
written = snprintf(names + offset, remaining, "%s", name);
|
||||
}
|
||||
|
||||
if (written < 0 || (size_t)written >= remaining) {
|
||||
/* Truncation or error - ensure null termination */
|
||||
names[offset] = '\0';
|
||||
offset++;
|
||||
} else {
|
||||
offset += (size_t)written + 1; /* Include null terminator */
|
||||
}
|
||||
names[offset++] = '\0';
|
||||
}
|
||||
|
||||
XChangeProperty(dwn->display, dwn->root, ewmh.NET_DESKTOP_NAMES,
|
||||
misc_atoms.UTF8_STRING, 8, PropModeReplace,
|
||||
(unsigned char *)names, offset);
|
||||
(unsigned char *)names, (int)offset);
|
||||
}
|
||||
|
||||
void atoms_set_current_desktop(int desktop)
|
||||
|
||||
@ -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
src/backends/x11/x11_backend.c
Normal file
806
src/backends/x11/x11_backend.c
Normal file
@ -0,0 +1,806 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* X11 Backend Implementation
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "backends/x11/x11_backend.h"
|
||||
#include "core/wm_types.h"
|
||||
#include "core/wm_string.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/cursorfont.h>
|
||||
#include <X11/keysym.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/*==============================================================================
|
||||
* X11 Backend State
|
||||
*============================================================================*/
|
||||
|
||||
static struct {
|
||||
Display *display;
|
||||
int screen;
|
||||
Window root;
|
||||
bool initialized;
|
||||
int fd;
|
||||
|
||||
/* Error handling */
|
||||
int last_error;
|
||||
XErrorHandler error_handler;
|
||||
XIOErrorHandler io_error_handler;
|
||||
|
||||
/* Cursor cache */
|
||||
Cursor cursor_cache[XC_num_glyphs];
|
||||
bool cursor_loaded[XC_num_glyphs];
|
||||
|
||||
/* Atoms cache */
|
||||
Atom atom_wm_protocols;
|
||||
Atom atom_wm_delete_window;
|
||||
Atom atom_wm_state;
|
||||
Atom atom_wm_fullscreen;
|
||||
Atom atom_wm_maximized_horz;
|
||||
Atom atom_wm_maximized_vert;
|
||||
Atom atom_net_wm_name;
|
||||
Atom atom_utf8_string;
|
||||
} x11_state = {0};
|
||||
|
||||
/*==============================================================================
|
||||
* Error Handlers
|
||||
*============================================================================*/
|
||||
|
||||
static int x11_error_handler(Display *dpy, XErrorEvent *ev) {
|
||||
x11_state.last_error = ev->error_code;
|
||||
|
||||
char msg[256];
|
||||
XGetErrorText(dpy, ev->error_code, msg, sizeof(msg));
|
||||
fprintf(stderr, "X11 Error: %s (request %d, minor %d)\n",
|
||||
msg, ev->request_code, ev->minor_code);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int x11_io_error_handler(Display *dpy) {
|
||||
(void)dpy;
|
||||
fprintf(stderr, "X11 IO Error: Connection to display lost\n");
|
||||
x11_state.initialized = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Backend Information
|
||||
*============================================================================*/
|
||||
|
||||
static WmBackendInfo x11_get_info(void) {
|
||||
return (WmBackendInfo){
|
||||
.name = "x11",
|
||||
.version = "1.0",
|
||||
.description = "X11 Window System Backend",
|
||||
.capabilities =
|
||||
WM_BACKEND_CAP_WINDOWS |
|
||||
WM_BACKEND_CAP_INPUT_EVENTS |
|
||||
WM_BACKEND_CAP_MULTI_MONITOR |
|
||||
WM_BACKEND_CAP_CLIPBOARD,
|
||||
.supports_multiple_instances = false,
|
||||
.requires_compositor = false
|
||||
};
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Lifecycle
|
||||
*============================================================================*/
|
||||
|
||||
static bool x11_init(void *config) {
|
||||
(void)config;
|
||||
|
||||
if (x11_state.initialized) return true;
|
||||
|
||||
XSetErrorHandler(x11_error_handler);
|
||||
XSetIOErrorHandler(x11_io_error_handler);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void x11_shutdown(void) {
|
||||
if (!x11_state.initialized) return;
|
||||
|
||||
/* Free cached cursors */
|
||||
for (int i = 0; i < XC_num_glyphs; i++) {
|
||||
if (x11_state.cursor_loaded[i]) {
|
||||
XFreeCursor(x11_state.display, x11_state.cursor_cache[i]);
|
||||
}
|
||||
}
|
||||
|
||||
x11_state.initialized = false;
|
||||
}
|
||||
|
||||
static bool x11_is_initialized(void) {
|
||||
return x11_state.initialized;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Connection
|
||||
*============================================================================*/
|
||||
|
||||
static bool x11_connect(void) {
|
||||
if (x11_state.display) return true;
|
||||
|
||||
x11_state.display = XOpenDisplay(NULL);
|
||||
if (!x11_state.display) {
|
||||
fprintf(stderr, "Failed to open X11 display\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
x11_state.screen = DefaultScreen(x11_state.display);
|
||||
x11_state.root = RootWindow(x11_state.display, x11_state.screen);
|
||||
x11_state.fd = ConnectionNumber(x11_state.display);
|
||||
x11_state.initialized = true;
|
||||
|
||||
/* Cache atoms */
|
||||
x11_state.atom_wm_protocols = XInternAtom(x11_state.display, "WM_PROTOCOLS", False);
|
||||
x11_state.atom_wm_delete_window = XInternAtom(x11_state.display, "WM_DELETE_WINDOW", False);
|
||||
x11_state.atom_wm_state = XInternAtom(x11_state.display, "_NET_WM_STATE", False);
|
||||
x11_state.atom_wm_fullscreen = XInternAtom(x11_state.display, "_NET_WM_STATE_FULLSCREEN", False);
|
||||
x11_state.atom_wm_maximized_horz = XInternAtom(x11_state.display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
|
||||
x11_state.atom_wm_maximized_vert = XInternAtom(x11_state.display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
|
||||
x11_state.atom_net_wm_name = XInternAtom(x11_state.display, "_NET_WM_NAME", False);
|
||||
x11_state.atom_utf8_string = XInternAtom(x11_state.display, "UTF8_STRING", False);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void x11_disconnect(void) {
|
||||
if (x11_state.display) {
|
||||
XCloseDisplay(x11_state.display);
|
||||
x11_state.display = NULL;
|
||||
x11_state.initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool x11_is_connected(void) {
|
||||
return x11_state.display != NULL && x11_state.initialized;
|
||||
}
|
||||
|
||||
static int x11_get_file_descriptor(void) {
|
||||
return x11_state.fd;
|
||||
}
|
||||
|
||||
static void x11_flush(void) {
|
||||
if (x11_state.display) XFlush(x11_state.display);
|
||||
}
|
||||
|
||||
static void x11_sync(void) {
|
||||
if (x11_state.display) XSync(x11_state.display, False);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Screen/Display
|
||||
*============================================================================*/
|
||||
|
||||
static void x11_get_screen_dimensions(int *width, int *height) {
|
||||
if (!x11_state.display) {
|
||||
if (width) *width = 0;
|
||||
if (height) *height = 0;
|
||||
return;
|
||||
}
|
||||
if (width) *width = DisplayWidth(x11_state.display, x11_state.screen);
|
||||
if (height) *height = DisplayHeight(x11_state.display, x11_state.screen);
|
||||
}
|
||||
|
||||
static int x11_get_screen_count(void) {
|
||||
return x11_state.display ? ScreenCount(x11_state.display) : 0;
|
||||
}
|
||||
|
||||
static void x11_get_screen_geometry(int screen, WmRect *geometry) {
|
||||
if (!x11_state.display || !geometry) return;
|
||||
|
||||
geometry->x = 0;
|
||||
geometry->y = 0;
|
||||
geometry->width = DisplayWidth(x11_state.display, screen);
|
||||
geometry->height = DisplayHeight(x11_state.display, screen);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Window Management - Geometry
|
||||
*============================================================================*/
|
||||
|
||||
static void x11_window_get_geometry(WmWindowHandle window, WmRect *geometry) {
|
||||
if (!x11_state.display || !geometry) return;
|
||||
|
||||
Window root;
|
||||
int x, y;
|
||||
unsigned int width, height, border, depth;
|
||||
|
||||
if (XGetGeometry(x11_state.display, (Window)window, &root,
|
||||
&x, &y, &width, &height, &border, &depth)) {
|
||||
geometry->x = x;
|
||||
geometry->y = y;
|
||||
geometry->width = (int)width;
|
||||
geometry->height = (int)height;
|
||||
}
|
||||
}
|
||||
|
||||
static void x11_window_set_geometry(WmWindowHandle window, const WmRect *geometry) {
|
||||
if (!x11_state.display || !geometry) return;
|
||||
|
||||
XMoveResizeWindow(x11_state.display, (Window)window,
|
||||
geometry->x, geometry->y,
|
||||
(unsigned int)geometry->width,
|
||||
(unsigned int)geometry->height);
|
||||
}
|
||||
|
||||
static void x11_window_move(WmWindowHandle window, int x, int y) {
|
||||
if (!x11_state.display) return;
|
||||
XMoveWindow(x11_state.display, (Window)window, x, y);
|
||||
}
|
||||
|
||||
static void x11_window_resize(WmWindowHandle window, int width, int height) {
|
||||
if (!x11_state.display) return;
|
||||
XResizeWindow(x11_state.display, (Window)window,
|
||||
(unsigned int)width, (unsigned int)height);
|
||||
}
|
||||
|
||||
static void x11_window_move_resize(WmWindowHandle window, const WmRect *geometry) {
|
||||
x11_window_set_geometry(window, geometry);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Window Management - Visibility
|
||||
*============================================================================*/
|
||||
|
||||
static void x11_window_show(WmWindowHandle window) {
|
||||
if (!x11_state.display) return;
|
||||
XMapWindow(x11_state.display, (Window)window);
|
||||
}
|
||||
|
||||
static void x11_window_hide(WmWindowHandle window) {
|
||||
if (!x11_state.display) return;
|
||||
XUnmapWindow(x11_state.display, (Window)window);
|
||||
}
|
||||
|
||||
static bool x11_window_is_visible(WmWindowHandle window) {
|
||||
if (!x11_state.display) return false;
|
||||
|
||||
XWindowAttributes attrs;
|
||||
if (XGetWindowAttributes(x11_state.display, (Window)window, &attrs)) {
|
||||
return attrs.map_state == IsViewable;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void x11_window_map(WmWindowHandle window) {
|
||||
x11_window_show(window);
|
||||
}
|
||||
|
||||
static void x11_window_unmap(WmWindowHandle window) {
|
||||
x11_window_hide(window);
|
||||
}
|
||||
|
||||
static bool x11_window_is_mapped(WmWindowHandle window) {
|
||||
return x11_window_is_visible(window);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Window Management - Stacking
|
||||
*============================================================================*/
|
||||
|
||||
static void x11_window_raise(WmWindowHandle window) {
|
||||
if (!x11_state.display) return;
|
||||
XRaiseWindow(x11_state.display, (Window)window);
|
||||
}
|
||||
|
||||
static void x11_window_lower(WmWindowHandle window) {
|
||||
if (!x11_state.display) return;
|
||||
XLowerWindow(x11_state.display, (Window)window);
|
||||
}
|
||||
|
||||
static void x11_window_reparent(WmWindowHandle window, WmWindowHandle parent, int x, int y) {
|
||||
if (!x11_state.display) return;
|
||||
XReparentWindow(x11_state.display, (Window)window, (Window)parent, x, y);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Window Management - Focus
|
||||
*============================================================================*/
|
||||
|
||||
static void x11_window_focus(WmWindowHandle window) {
|
||||
if (!x11_state.display) return;
|
||||
XSetInputFocus(x11_state.display, (Window)window, RevertToPointerRoot, CurrentTime);
|
||||
}
|
||||
|
||||
static void x11_window_unfocus(WmWindowHandle window) {
|
||||
(void)window;
|
||||
if (!x11_state.display) return;
|
||||
XSetInputFocus(x11_state.display, PointerRoot, RevertToPointerRoot, CurrentTime);
|
||||
}
|
||||
|
||||
static bool x11_window_is_focused(WmWindowHandle window) {
|
||||
if (!x11_state.display) return false;
|
||||
|
||||
Window focused;
|
||||
int revert_to;
|
||||
XGetInputFocus(x11_state.display, &focused, &revert_to);
|
||||
return (Window)window == focused;
|
||||
}
|
||||
|
||||
static WmWindowHandle x11_window_get_focused(void) {
|
||||
if (!x11_state.display) return NULL;
|
||||
|
||||
Window focused;
|
||||
int revert_to;
|
||||
XGetInputFocus(x11_state.display, &focused, &revert_to);
|
||||
return (WmWindowHandle)focused;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Window Management - Border
|
||||
*============================================================================*/
|
||||
|
||||
static void x11_window_set_border_width(WmWindowHandle window, int width) {
|
||||
if (!x11_state.display) return;
|
||||
XSetWindowBorderWidth(x11_state.display, (Window)window, (unsigned int)width);
|
||||
}
|
||||
|
||||
static int x11_window_get_border_width(WmWindowHandle window) {
|
||||
if (!x11_state.display) return 0;
|
||||
|
||||
XWindowAttributes attrs;
|
||||
if (XGetWindowAttributes(x11_state.display, (Window)window, &attrs)) {
|
||||
return (int)attrs.border_width;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Window Management - Properties
|
||||
*============================================================================*/
|
||||
|
||||
static void x11_window_set_title(WmWindowHandle window, const char *title) {
|
||||
if (!x11_state.display || !title) return;
|
||||
|
||||
XStoreName(x11_state.display, (Window)window, title);
|
||||
|
||||
if (x11_state.atom_net_wm_name != None && x11_state.atom_utf8_string != None) {
|
||||
XChangeProperty(x11_state.display, (Window)window,
|
||||
x11_state.atom_net_wm_name, x11_state.atom_utf8_string,
|
||||
8, PropModeReplace,
|
||||
(const unsigned char*)title, (int)strlen(title));
|
||||
}
|
||||
}
|
||||
|
||||
static char* x11_window_get_title(WmWindowHandle window) {
|
||||
if (!x11_state.display) return NULL;
|
||||
|
||||
char *title = NULL;
|
||||
|
||||
/* Try _NET_WM_NAME first (UTF-8) */
|
||||
if (x11_state.atom_net_wm_name != None) {
|
||||
Atom actual_type;
|
||||
int actual_format;
|
||||
unsigned long nitems, bytes_after;
|
||||
unsigned char *data = NULL;
|
||||
|
||||
if (XGetWindowProperty(x11_state.display, (Window)window,
|
||||
x11_state.atom_net_wm_name, 0, ~0L, False,
|
||||
x11_state.atom_utf8_string, &actual_type,
|
||||
&actual_format, &nitems, &bytes_after, &data) == Success) {
|
||||
if (data && actual_type == x11_state.atom_utf8_string) {
|
||||
title = strdup((char*)data);
|
||||
XFree(data);
|
||||
if (title) return title;
|
||||
}
|
||||
if (data) XFree(data);
|
||||
}
|
||||
}
|
||||
|
||||
/* Fall back to WM_NAME */
|
||||
char *name = NULL;
|
||||
if (XFetchName(x11_state.display, (Window)window, &name)) {
|
||||
title = strdup(name);
|
||||
XFree(name);
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Window Management - Protocols
|
||||
*============================================================================*/
|
||||
|
||||
static bool x11_window_supports_protocol(WmWindowHandle window, const char *protocol) {
|
||||
if (!x11_state.display || !protocol) return false;
|
||||
|
||||
Atom proto = XInternAtom(x11_state.display, protocol, True);
|
||||
if (proto == None) return false;
|
||||
|
||||
Atom *protocols = NULL;
|
||||
int count = 0;
|
||||
if (XGetWMProtocols(x11_state.display, (Window)window, &protocols, &count)) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (protocols[i] == proto) {
|
||||
XFree(protocols);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
XFree(protocols);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void x11_window_close(WmWindowHandle window) {
|
||||
if (!x11_state.display) return;
|
||||
|
||||
if (x11_window_supports_protocol(window, "WM_DELETE_WINDOW")) {
|
||||
XEvent ev;
|
||||
ev.type = ClientMessage;
|
||||
ev.xclient.window = (Window)window;
|
||||
ev.xclient.message_type = x11_state.atom_wm_protocols;
|
||||
ev.xclient.format = 32;
|
||||
ev.xclient.data.l[0] = (long)x11_state.atom_wm_delete_window;
|
||||
ev.xclient.data.l[1] = CurrentTime;
|
||||
|
||||
XSendEvent(x11_state.display, (Window)window, False, NoEventMask, &ev);
|
||||
} else {
|
||||
XKillClient(x11_state.display, (Window)window);
|
||||
}
|
||||
}
|
||||
|
||||
static void x11_window_kill(WmWindowHandle window) {
|
||||
if (!x11_state.display) return;
|
||||
XKillClient(x11_state.display, (Window)window);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Window Management - State
|
||||
*============================================================================*/
|
||||
|
||||
static void x11_window_set_fullscreen(WmWindowHandle window, bool fullscreen) {
|
||||
if (!x11_state.display) return;
|
||||
|
||||
XEvent ev;
|
||||
ev.type = ClientMessage;
|
||||
ev.xclient.window = (Window)window;
|
||||
ev.xclient.message_type = x11_state.atom_wm_state;
|
||||
ev.xclient.format = 32;
|
||||
ev.xclient.data.l[0] = fullscreen ? 1 : 0; /* _NET_WM_STATE_ADD or _NET_WM_STATE_REMOVE */
|
||||
ev.xclient.data.l[1] = (long)x11_state.atom_wm_fullscreen;
|
||||
ev.xclient.data.l[2] = 0;
|
||||
ev.xclient.data.l[3] = 1; /* Source indication: normal application */
|
||||
ev.xclient.data.l[4] = 0;
|
||||
|
||||
XSendEvent(x11_state.display, x11_state.root, False,
|
||||
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
|
||||
}
|
||||
|
||||
static void x11_window_set_urgent(WmWindowHandle window, bool urgent) {
|
||||
if (!x11_state.display) return;
|
||||
|
||||
XWMHints *hints = XGetWMHints(x11_state.display, (Window)window);
|
||||
if (!hints) {
|
||||
hints = XAllocWMHints();
|
||||
if (!hints) return;
|
||||
}
|
||||
|
||||
if (urgent) {
|
||||
hints->flags |= XUrgencyHint;
|
||||
} else {
|
||||
hints->flags &= ~XUrgencyHint;
|
||||
}
|
||||
|
||||
XSetWMHints(x11_state.display, (Window)window, hints);
|
||||
XFree(hints);
|
||||
}
|
||||
|
||||
static bool x11_window_is_urgent(WmWindowHandle window) {
|
||||
if (!x11_state.display) return false;
|
||||
|
||||
XWMHints *hints = XGetWMHints(x11_state.display, (Window)window);
|
||||
if (!hints) return false;
|
||||
|
||||
bool urgent = (hints->flags & XUrgencyHint) != 0;
|
||||
XFree(hints);
|
||||
return urgent;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Input - Events
|
||||
*============================================================================*/
|
||||
|
||||
static bool x11_poll_event(WmBackendEvent *event) {
|
||||
if (!x11_state.display || !event) return false;
|
||||
|
||||
if (!XPending(x11_state.display)) return false;
|
||||
|
||||
XEvent xev;
|
||||
XNextEvent(x11_state.display, &xev);
|
||||
|
||||
/* Translate X11 event to backend event */
|
||||
memset(event, 0, sizeof(*event));
|
||||
event->window = (WmWindowHandle)xev.xany.window;
|
||||
event->timestamp = (uint64_t)xev.xany.serial;
|
||||
|
||||
switch (xev.type) {
|
||||
case Expose:
|
||||
event->type = WM_BACKEND_EVENT_EXPOSE;
|
||||
event->data.configure.x = xev.xexpose.x;
|
||||
event->data.configure.y = xev.xexpose.y;
|
||||
event->data.configure.width = xev.xexpose.width;
|
||||
event->data.configure.height = xev.xexpose.height;
|
||||
break;
|
||||
|
||||
case ConfigureNotify:
|
||||
event->type = WM_BACKEND_EVENT_CONFIGURE;
|
||||
event->data.configure.x = xev.xconfigure.x;
|
||||
event->data.configure.y = xev.xconfigure.y;
|
||||
event->data.configure.width = xev.xconfigure.width;
|
||||
event->data.configure.height = xev.xconfigure.height;
|
||||
break;
|
||||
|
||||
case MapNotify:
|
||||
event->type = WM_BACKEND_EVENT_MAP;
|
||||
break;
|
||||
|
||||
case UnmapNotify:
|
||||
event->type = WM_BACKEND_EVENT_UNMAP;
|
||||
break;
|
||||
|
||||
case DestroyNotify:
|
||||
event->type = WM_BACKEND_EVENT_DESTROY;
|
||||
break;
|
||||
|
||||
case FocusIn:
|
||||
event->type = WM_BACKEND_EVENT_FOCUS_IN;
|
||||
break;
|
||||
|
||||
case FocusOut:
|
||||
event->type = WM_BACKEND_EVENT_FOCUS_OUT;
|
||||
break;
|
||||
|
||||
case KeyPress:
|
||||
event->type = WM_BACKEND_EVENT_KEY_PRESS;
|
||||
event->data.key.keycode = xev.xkey.keycode;
|
||||
event->data.key.state = xev.xkey.state;
|
||||
break;
|
||||
|
||||
case KeyRelease:
|
||||
event->type = WM_BACKEND_EVENT_KEY_RELEASE;
|
||||
event->data.key.keycode = xev.xkey.keycode;
|
||||
event->data.key.state = xev.xkey.state;
|
||||
break;
|
||||
|
||||
case ButtonPress:
|
||||
event->type = WM_BACKEND_EVENT_BUTTON_PRESS;
|
||||
event->data.button.button = xev.xbutton.button;
|
||||
event->data.button.x = xev.xbutton.x;
|
||||
event->data.button.y = xev.xbutton.y;
|
||||
event->data.button.state = xev.xbutton.state;
|
||||
break;
|
||||
|
||||
case ButtonRelease:
|
||||
event->type = WM_BACKEND_EVENT_BUTTON_RELEASE;
|
||||
event->data.button.button = xev.xbutton.button;
|
||||
event->data.button.x = xev.xbutton.x;
|
||||
event->data.button.y = xev.xbutton.y;
|
||||
event->data.button.state = xev.xbutton.state;
|
||||
break;
|
||||
|
||||
case MotionNotify:
|
||||
event->type = WM_BACKEND_EVENT_MOTION;
|
||||
event->data.button.x = xev.xmotion.x;
|
||||
event->data.button.y = xev.xmotion.y;
|
||||
event->data.button.state = xev.xmotion.state;
|
||||
break;
|
||||
|
||||
case EnterNotify:
|
||||
event->type = WM_BACKEND_EVENT_ENTER;
|
||||
break;
|
||||
|
||||
case LeaveNotify:
|
||||
event->type = WM_BACKEND_EVENT_LEAVE;
|
||||
break;
|
||||
|
||||
case PropertyNotify:
|
||||
event->type = WM_BACKEND_EVENT_PROPERTY;
|
||||
event->data.property.name = XGetAtomName(x11_state.display, xev.xproperty.atom);
|
||||
break;
|
||||
|
||||
case ClientMessage:
|
||||
event->type = WM_BACKEND_EVENT_CLIENT_MESSAGE;
|
||||
break;
|
||||
|
||||
default:
|
||||
event->type = WM_BACKEND_EVENT_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void x11_wait_event(WmBackendEvent *event) {
|
||||
if (!x11_state.display || !event) return;
|
||||
while (!x11_poll_event(event)) {
|
||||
/* Wait for event */
|
||||
}
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Input - Grabbing
|
||||
*============================================================================*/
|
||||
|
||||
static void x11_grab_key(int keycode, uint32_t modifiers, WmWindowHandle window,
|
||||
bool owner_events, uint32_t pointer_mode, uint32_t keyboard_mode) {
|
||||
if (!x11_state.display) return;
|
||||
XGrabKey(x11_state.display, keycode, modifiers, (Window)window,
|
||||
owner_events, pointer_mode, keyboard_mode);
|
||||
}
|
||||
|
||||
static void x11_ungrab_key(int keycode, uint32_t modifiers, WmWindowHandle window) {
|
||||
if (!x11_state.display) return;
|
||||
XUngrabKey(x11_state.display, keycode, modifiers, (Window)window);
|
||||
}
|
||||
|
||||
static void x11_grab_button(int button, uint32_t modifiers, WmWindowHandle window,
|
||||
bool owner_events, uint32_t event_mask,
|
||||
uint32_t pointer_mode, uint32_t keyboard_mode,
|
||||
WmWindowHandle confine_to, uint32_t cursor) {
|
||||
if (!x11_state.display) return;
|
||||
XGrabButton(x11_state.display, button, modifiers, (Window)window,
|
||||
owner_events, event_mask, pointer_mode, keyboard_mode,
|
||||
(Window)confine_to, cursor);
|
||||
}
|
||||
|
||||
static void x11_ungrab_button(int button, uint32_t modifiers, WmWindowHandle window) {
|
||||
if (!x11_state.display) return;
|
||||
XUngrabButton(x11_state.display, button, modifiers, (Window)window);
|
||||
}
|
||||
|
||||
static void x11_grab_pointer(WmWindowHandle window, bool owner_events, uint32_t event_mask,
|
||||
uint32_t pointer_mode, uint32_t keyboard_mode,
|
||||
WmWindowHandle confine_to, uint32_t cursor, WmTime time) {
|
||||
if (!x11_state.display) return;
|
||||
XGrabPointer(x11_state.display, (Window)window, owner_events, event_mask,
|
||||
pointer_mode, keyboard_mode, (Window)confine_to,
|
||||
(Cursor)cursor, (Time)time);
|
||||
}
|
||||
|
||||
static void x11_ungrab_pointer(WmTime time) {
|
||||
if (!x11_state.display) return;
|
||||
XUngrabPointer(x11_state.display, (Time)time);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Input - Pointer
|
||||
*============================================================================*/
|
||||
|
||||
static void x11_query_pointer(WmWindowHandle window, int *root_x, int *root_y,
|
||||
int *win_x, int *win_y, uint32_t *mask) {
|
||||
if (!x11_state.display) return;
|
||||
|
||||
Window root, child;
|
||||
int rx, ry, wx, wy;
|
||||
unsigned int m;
|
||||
|
||||
XQueryPointer(x11_state.display, (Window)window, &root, &child,
|
||||
&rx, &ry, &wx, &wy, &m);
|
||||
|
||||
if (root_x) *root_x = rx;
|
||||
if (root_y) *root_y = ry;
|
||||
if (win_x) *win_x = wx;
|
||||
if (win_y) *win_y = wy;
|
||||
if (mask) *mask = m;
|
||||
}
|
||||
|
||||
static void x11_warp_pointer(WmWindowHandle dest_w, int dest_x, int dest_y) {
|
||||
if (!x11_state.display) return;
|
||||
XWarpPointer(x11_state.display, None, (Window)dest_w, 0, 0, 0, 0, dest_x, dest_y);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Atoms/Properties
|
||||
*============================================================================*/
|
||||
|
||||
static uint32_t x11_atom_get(const char *name) {
|
||||
if (!x11_state.display || !name) return 0;
|
||||
return (uint32_t)XInternAtom(x11_state.display, name, False);
|
||||
}
|
||||
|
||||
static const char* x11_atom_get_name(uint32_t atom) {
|
||||
if (!x11_state.display || atom == 0) return NULL;
|
||||
return XGetAtomName(x11_state.display, (Atom)atom);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Backend Interface
|
||||
*============================================================================*/
|
||||
|
||||
static const BackendInterface x11_backend_interface = {
|
||||
.name = "x11",
|
||||
.get_info = x11_get_info,
|
||||
|
||||
.init = x11_init,
|
||||
.shutdown = x11_shutdown,
|
||||
.is_initialized = x11_is_initialized,
|
||||
|
||||
.connect = x11_connect,
|
||||
.disconnect = x11_disconnect,
|
||||
.is_connected = x11_is_connected,
|
||||
.get_file_descriptor = x11_get_file_descriptor,
|
||||
.flush = x11_flush,
|
||||
.sync = x11_sync,
|
||||
|
||||
.get_screen_dimensions = x11_get_screen_dimensions,
|
||||
.get_screen_count = x11_get_screen_count,
|
||||
.get_screen_geometry = x11_get_screen_geometry,
|
||||
|
||||
.window_get_geometry = x11_window_get_geometry,
|
||||
.window_set_geometry = x11_window_set_geometry,
|
||||
.window_move = x11_window_move,
|
||||
.window_resize = x11_window_resize,
|
||||
.window_move_resize = x11_window_move_resize,
|
||||
|
||||
.window_show = x11_window_show,
|
||||
.window_hide = x11_window_hide,
|
||||
.window_is_visible = x11_window_is_visible,
|
||||
.window_map = x11_window_map,
|
||||
.window_unmap = x11_window_unmap,
|
||||
.window_is_mapped = x11_window_is_mapped,
|
||||
|
||||
.window_raise = x11_window_raise,
|
||||
.window_lower = x11_window_lower,
|
||||
.window_reparent = x11_window_reparent,
|
||||
|
||||
.window_focus = x11_window_focus,
|
||||
.window_unfocus = x11_window_unfocus,
|
||||
.window_is_focused = x11_window_is_focused,
|
||||
.window_get_focused = x11_window_get_focused,
|
||||
|
||||
.window_set_border_width = x11_window_set_border_width,
|
||||
.window_get_border_width = x11_window_get_border_width,
|
||||
|
||||
.window_set_title = x11_window_set_title,
|
||||
.window_get_title = x11_window_get_title,
|
||||
|
||||
.window_supports_protocol = x11_window_supports_protocol,
|
||||
.window_close = x11_window_close,
|
||||
.window_kill = x11_window_kill,
|
||||
|
||||
.window_set_fullscreen = x11_window_set_fullscreen,
|
||||
.window_set_urgent = x11_window_set_urgent,
|
||||
.window_is_urgent = x11_window_is_urgent,
|
||||
|
||||
.poll_event = x11_poll_event,
|
||||
.wait_event = x11_wait_event,
|
||||
|
||||
.grab_key = x11_grab_key,
|
||||
.ungrab_key = x11_ungrab_key,
|
||||
.grab_button = x11_grab_button,
|
||||
.ungrab_button = x11_ungrab_button,
|
||||
.grab_pointer = x11_grab_pointer,
|
||||
.ungrab_pointer = x11_ungrab_pointer,
|
||||
|
||||
.query_pointer = x11_query_pointer,
|
||||
.warp_pointer = x11_warp_pointer,
|
||||
|
||||
.atom_get = x11_atom_get,
|
||||
.atom_get_name = x11_atom_get_name,
|
||||
};
|
||||
|
||||
const BackendInterface* x11_backend_get_interface(void) {
|
||||
return &x11_backend_interface;
|
||||
}
|
||||
|
||||
Display* x11_backend_get_display(void) {
|
||||
return x11_state.display;
|
||||
}
|
||||
|
||||
Window x11_backend_get_root(void) {
|
||||
return x11_state.root;
|
||||
}
|
||||
|
||||
int x11_backend_get_screen(void) {
|
||||
return x11_state.screen;
|
||||
}
|
||||
366
src/channels.c
Normal file
366
src/channels.c
Normal 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
src/client.c
24
src/client.c
@ -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");
|
||||
if (client->workspace == (unsigned int)dwn->current_workspace) {
|
||||
client_show(client);
|
||||
}
|
||||
|
||||
client_sync_log("client_manage: focusing");
|
||||
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
src/config.c
89
src/config.c
@ -97,6 +97,9 @@ void config_set_defaults(Config *cfg)
|
||||
cfg->demo_ai_timeout_ms = 15000;
|
||||
cfg->demo_window_timeout_ms = 5000;
|
||||
|
||||
cfg->fade_speed = 0.5f; /* Default moderate speed */
|
||||
cfg->fade_intensity = 1.0f; /* Default full intensity */
|
||||
|
||||
strncpy(cfg->color_panel_bg, DEFAULT_PANEL_BG, sizeof(cfg->color_panel_bg) - 1);
|
||||
strncpy(cfg->color_panel_fg, DEFAULT_PANEL_FG, sizeof(cfg->color_panel_fg) - 1);
|
||||
strncpy(cfg->color_workspace_active, DEFAULT_WS_ACTIVE, sizeof(cfg->color_workspace_active) - 1);
|
||||
@ -227,6 +230,14 @@ static void handle_config_entry(const char *section, const char *key,
|
||||
int val = atoi(value);
|
||||
cfg->demo_window_timeout_ms = (val >= 1000 && val <= 30000) ? val : 5000;
|
||||
}
|
||||
} else if (strcmp(section, "effects") == 0) {
|
||||
if (strcmp(key, "fade_speed") == 0) {
|
||||
float val = atof(value);
|
||||
cfg->fade_speed = (val >= 0.1f && val <= 3.0f) ? val : 0.5f;
|
||||
} else if (strcmp(key, "fade_intensity") == 0) {
|
||||
float val = atof(value);
|
||||
cfg->fade_intensity = (val >= 0.0f && val <= 1.0f) ? val : 1.0f;
|
||||
}
|
||||
} else if (strcmp(section, "colors") == 0) {
|
||||
if (strcmp(key, "panel_bg") == 0) {
|
||||
strncpy(cfg->color_panel_bg, value, sizeof(cfg->color_panel_bg) - 1);
|
||||
@ -350,9 +361,53 @@ bool config_load(Config *cfg, const char *path)
|
||||
LOG_INFO("AI features enabled");
|
||||
}
|
||||
|
||||
/* Validate and clamp all config values to safe ranges */
|
||||
config_validate(cfg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void config_validate(Config *cfg)
|
||||
{
|
||||
if (cfg == NULL) return;
|
||||
|
||||
/* Panel height: 10-100 pixels */
|
||||
if (cfg->panel_height < 10) cfg->panel_height = 10;
|
||||
if (cfg->panel_height > 100) cfg->panel_height = 100;
|
||||
|
||||
/* Border width: 0-20 pixels */
|
||||
if (cfg->border_width < 0) cfg->border_width = 0;
|
||||
if (cfg->border_width > 20) cfg->border_width = 20;
|
||||
|
||||
/* Title height: 0-50 pixels */
|
||||
if (cfg->title_height < 0) cfg->title_height = 0;
|
||||
if (cfg->title_height > 50) cfg->title_height = 50;
|
||||
|
||||
/* Default master ratio: 0.1-0.9 */
|
||||
if (cfg->default_master_ratio < 0.1f) cfg->default_master_ratio = 0.1f;
|
||||
if (cfg->default_master_ratio > 0.9f) cfg->default_master_ratio = 0.9f;
|
||||
|
||||
/* Default master count: 1-8 */
|
||||
if (cfg->default_master_count < 1) cfg->default_master_count = 1;
|
||||
if (cfg->default_master_count > 8) cfg->default_master_count = 8;
|
||||
|
||||
/* Gap: 0-50 pixels */
|
||||
if (cfg->gap < 0) cfg->gap = 0;
|
||||
if (cfg->gap > 50) cfg->gap = 50;
|
||||
|
||||
/* Fade speed: 0.1-3.0 */
|
||||
if (cfg->fade_speed < 0.1f) cfg->fade_speed = 0.1f;
|
||||
if (cfg->fade_speed > 3.0f) cfg->fade_speed = 3.0f;
|
||||
|
||||
/* Fade intensity: 0.0-1.0 */
|
||||
if (cfg->fade_intensity < 0.0f) cfg->fade_intensity = 0.0f;
|
||||
if (cfg->fade_intensity > 1.0f) cfg->fade_intensity = 1.0f;
|
||||
|
||||
/* API port: 1024-65535 (avoid well-known ports) */
|
||||
if (cfg->api_port < 1024) cfg->api_port = 8777;
|
||||
if (cfg->api_port > 65535) cfg->api_port = 8777;
|
||||
}
|
||||
|
||||
bool config_reload(Config *cfg)
|
||||
{
|
||||
if (cfg == NULL) {
|
||||
@ -440,3 +495,37 @@ void config_init_colors(Config *cfg)
|
||||
cfg->colors.notification_bg = parse_color(cfg->color_notification_bg);
|
||||
cfg->colors.notification_fg = parse_color(cfg->color_notification_fg);
|
||||
}
|
||||
|
||||
float config_get_fade_speed(void)
|
||||
{
|
||||
if (dwn != NULL && dwn->config != NULL) {
|
||||
return dwn->config->fade_speed;
|
||||
}
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
float config_get_fade_intensity(void)
|
||||
{
|
||||
if (dwn != NULL && dwn->config != NULL) {
|
||||
return dwn->config->fade_intensity;
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
void config_set_fade_speed(float speed)
|
||||
{
|
||||
if (dwn != NULL && dwn->config != NULL) {
|
||||
if (speed < 0.1f) speed = 0.1f;
|
||||
if (speed > 3.0f) speed = 3.0f;
|
||||
dwn->config->fade_speed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
void config_set_fade_intensity(float intensity)
|
||||
{
|
||||
if (dwn != NULL && dwn->config != NULL) {
|
||||
if (intensity < 0.0f) intensity = 0.0f;
|
||||
if (intensity > 1.0f) intensity = 1.0f;
|
||||
dwn->config->fade_intensity = intensity;
|
||||
}
|
||||
}
|
||||
|
||||
909
src/core/wm_client.c
Normal file
909
src/core/wm_client.c
Normal file
@ -0,0 +1,909 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* Abstract Client Implementation
|
||||
*/
|
||||
|
||||
#include "core/wm_client.h"
|
||||
#include "core/wm_hashmap.h"
|
||||
#include "core/wm_list.h"
|
||||
#include "client.h"
|
||||
#include "dwn.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
/*==============================================================================
|
||||
* Internal Structures
|
||||
*============================================================================*/
|
||||
|
||||
struct AbstractClient {
|
||||
/* Core identification */
|
||||
WmClientId id;
|
||||
WmWindowHandle window;
|
||||
WmWindowHandle frame;
|
||||
WmClientType type;
|
||||
|
||||
/* Legacy client pointer (for compatibility during migration) */
|
||||
Client *legacy;
|
||||
bool owns_legacy;
|
||||
|
||||
/* State */
|
||||
WmClientState state;
|
||||
WmWorkspaceId workspace;
|
||||
|
||||
/* Geometry */
|
||||
WmRect geometry;
|
||||
WmRect saved_geometry;
|
||||
int border_width;
|
||||
|
||||
/* Properties */
|
||||
char title[256];
|
||||
char class_name[64];
|
||||
char instance_name[64];
|
||||
WmColor color;
|
||||
|
||||
/* List links */
|
||||
AbstractClient *next;
|
||||
AbstractClient *prev;
|
||||
AbstractClient *mru_next;
|
||||
AbstractClient *mru_prev;
|
||||
};
|
||||
|
||||
struct WmClientManager {
|
||||
AbstractClient *clients; /* Global client list */
|
||||
AbstractClient *clients_tail; /* Tail of global list */
|
||||
AbstractClient *mru_head; /* Most recently used */
|
||||
AbstractClient *mru_tail; /* Least recently used */
|
||||
|
||||
WmHashMap *by_window; /* Map: window handle -> client */
|
||||
WmHashMap *by_frame; /* Map: frame handle -> client */
|
||||
WmHashMap *by_id; /* Map: client ID -> client */
|
||||
|
||||
AbstractClient *focused;
|
||||
WmClientId next_id;
|
||||
int count;
|
||||
};
|
||||
|
||||
/*==============================================================================
|
||||
* Global State
|
||||
*============================================================================*/
|
||||
|
||||
static WmClientManager *g_client_manager = NULL;
|
||||
|
||||
/*==============================================================================
|
||||
* Internal Helpers
|
||||
*============================================================================*/
|
||||
|
||||
static WmClientId generate_client_id(void) {
|
||||
if (!g_client_manager) return 0;
|
||||
return g_client_manager->next_id++;
|
||||
}
|
||||
|
||||
static void link_client(AbstractClient *client) {
|
||||
if (!g_client_manager || !client) return;
|
||||
|
||||
/* Add to global list */
|
||||
if (g_client_manager->clients_tail) {
|
||||
g_client_manager->clients_tail->next = client;
|
||||
client->prev = g_client_manager->clients_tail;
|
||||
g_client_manager->clients_tail = client;
|
||||
} else {
|
||||
g_client_manager->clients = client;
|
||||
g_client_manager->clients_tail = client;
|
||||
}
|
||||
|
||||
/* Add to MRU list (at head = most recent) */
|
||||
if (g_client_manager->mru_head) {
|
||||
client->mru_next = g_client_manager->mru_head;
|
||||
g_client_manager->mru_head->mru_prev = client;
|
||||
g_client_manager->mru_head = client;
|
||||
} else {
|
||||
g_client_manager->mru_head = client;
|
||||
g_client_manager->mru_tail = client;
|
||||
}
|
||||
|
||||
g_client_manager->count++;
|
||||
}
|
||||
|
||||
static void unlink_client(AbstractClient *client) {
|
||||
if (!g_client_manager || !client) return;
|
||||
|
||||
/* Remove from global list */
|
||||
if (client->prev) {
|
||||
client->prev->next = client->next;
|
||||
} else {
|
||||
g_client_manager->clients = client->next;
|
||||
}
|
||||
|
||||
if (client->next) {
|
||||
client->next->prev = client->prev;
|
||||
} else {
|
||||
g_client_manager->clients_tail = client->prev;
|
||||
}
|
||||
|
||||
/* Remove from MRU list */
|
||||
if (client->mru_prev) {
|
||||
client->mru_prev->mru_next = client->mru_next;
|
||||
} else {
|
||||
g_client_manager->mru_head = client->mru_next;
|
||||
}
|
||||
|
||||
if (client->mru_next) {
|
||||
client->mru_next->mru_prev = client->mru_prev;
|
||||
} else {
|
||||
g_client_manager->mru_tail = client->mru_prev;
|
||||
}
|
||||
|
||||
g_client_manager->count--;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client Lifecycle
|
||||
*============================================================================*/
|
||||
|
||||
AbstractClient* wm_client_create(WmWindowHandle window, WmClientType type) {
|
||||
if (!window) return NULL;
|
||||
|
||||
AbstractClient *client = calloc(1, sizeof(AbstractClient));
|
||||
if (!client) return NULL;
|
||||
|
||||
client->id = generate_client_id();
|
||||
client->window = window;
|
||||
client->type = type;
|
||||
client->state = WM_CLIENT_STATE_NORMAL | WM_CLIENT_STATE_MAPPED;
|
||||
client->workspace = 0;
|
||||
client->border_width = 0;
|
||||
client->color = 0;
|
||||
|
||||
/* Register with manager */
|
||||
if (g_client_manager) {
|
||||
link_client(client);
|
||||
wm_hashmap_insert(g_client_manager->by_window, window, client);
|
||||
|
||||
int id_key = (int)client->id;
|
||||
wm_hashmap_insert(g_client_manager->by_id, (void*)(intptr_t)id_key, client);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Created abstract client %u for window %p", client->id, window);
|
||||
return client;
|
||||
}
|
||||
|
||||
void wm_client_destroy(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
LOG_DEBUG("Destroying abstract client %u", client->id);
|
||||
|
||||
/* Remove from manager */
|
||||
if (g_client_manager) {
|
||||
wm_hashmap_remove(g_client_manager->by_window, client->window);
|
||||
if (client->frame) {
|
||||
wm_hashmap_remove(g_client_manager->by_frame, client->frame);
|
||||
}
|
||||
int id_key = (int)client->id;
|
||||
wm_hashmap_remove(g_client_manager->by_id, (void*)(intptr_t)id_key);
|
||||
|
||||
unlink_client(client);
|
||||
|
||||
/* Clear focus if this was focused */
|
||||
if (g_client_manager->focused == client) {
|
||||
g_client_manager->focused = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Destroy legacy client if we own it */
|
||||
if (client->legacy && client->owns_legacy) {
|
||||
client_destroy(client->legacy);
|
||||
}
|
||||
|
||||
free(client);
|
||||
}
|
||||
|
||||
bool wm_client_is_valid(const AbstractClient *client) {
|
||||
return client != NULL &&
|
||||
!(client->state & WM_CLIENT_STATE_DESTROYING);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Native Access
|
||||
*============================================================================*/
|
||||
|
||||
WmWindowHandle wm_client_get_window(const AbstractClient *client) {
|
||||
return client ? client->window : NULL;
|
||||
}
|
||||
|
||||
WmWindowHandle wm_client_get_frame(const AbstractClient *client) {
|
||||
return client ? client->frame : NULL;
|
||||
}
|
||||
|
||||
Client* wm_client_get_legacy(const AbstractClient *client) {
|
||||
return client ? client->legacy : NULL;
|
||||
}
|
||||
|
||||
AbstractClient* wm_client_from_legacy(Client *legacy_client) {
|
||||
if (!legacy_client) return NULL;
|
||||
|
||||
/* Check if we already have a wrapper for this client */
|
||||
AbstractClient *existing = wm_client_find_by_window((WmWindowHandle)legacy_client->window);
|
||||
if (existing) return existing;
|
||||
|
||||
/* Create wrapper */
|
||||
AbstractClient *client = calloc(1, sizeof(AbstractClient));
|
||||
if (!client) return NULL;
|
||||
|
||||
client->id = generate_client_id();
|
||||
client->window = (WmWindowHandle)legacy_client->window;
|
||||
client->frame = (WmWindowHandle)legacy_client->frame;
|
||||
client->legacy = legacy_client;
|
||||
client->owns_legacy = false;
|
||||
|
||||
/* Sync state from legacy */
|
||||
client->workspace = legacy_client->workspace;
|
||||
client->border_width = legacy_client->border_width;
|
||||
client->geometry.x = legacy_client->x;
|
||||
client->geometry.y = legacy_client->y;
|
||||
client->geometry.width = legacy_client->width;
|
||||
client->geometry.height = legacy_client->height;
|
||||
|
||||
size_t title_len = strlen(legacy_client->title);
|
||||
if (title_len >= sizeof(client->title)) title_len = sizeof(client->title) - 1;
|
||||
memcpy(client->title, legacy_client->title, title_len);
|
||||
client->title[title_len] = '\0';
|
||||
|
||||
size_t class_len = strlen(legacy_client->class);
|
||||
if (class_len >= sizeof(client->class_name)) class_len = sizeof(client->class_name) - 1;
|
||||
memcpy(client->class_name, legacy_client->class, class_len);
|
||||
client->class_name[class_len] = '\0';
|
||||
client->color = legacy_client->taskbar_color;
|
||||
|
||||
/* Convert flags */
|
||||
if (legacy_client->flags & CLIENT_FLOATING) {
|
||||
client->state |= WM_CLIENT_STATE_FLOATING;
|
||||
}
|
||||
if (legacy_client->flags & CLIENT_FULLSCREEN) {
|
||||
client->state |= WM_CLIENT_STATE_FULLSCREEN;
|
||||
}
|
||||
if (legacy_client->flags & CLIENT_URGENT) {
|
||||
client->state |= WM_CLIENT_STATE_URGENT;
|
||||
}
|
||||
if (legacy_client->flags & CLIENT_MINIMIZED) {
|
||||
client->state |= WM_CLIENT_STATE_MINIMIZED;
|
||||
}
|
||||
if (legacy_client->flags & CLIENT_STICKY) {
|
||||
client->state |= WM_CLIENT_STATE_STICKY;
|
||||
}
|
||||
if (legacy_client->flags & CLIENT_MAXIMIZED) {
|
||||
client->state |= WM_CLIENT_STATE_MAXIMIZED;
|
||||
}
|
||||
|
||||
/* Register */
|
||||
if (g_client_manager) {
|
||||
link_client(client);
|
||||
wm_hashmap_insert(g_client_manager->by_window, client->window, client);
|
||||
if (client->frame) {
|
||||
wm_hashmap_insert(g_client_manager->by_frame, client->frame, client);
|
||||
}
|
||||
int id_key = (int)client->id;
|
||||
wm_hashmap_insert(g_client_manager->by_id, (void*)(intptr_t)id_key, client);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client Identification
|
||||
*============================================================================*/
|
||||
|
||||
WmClientId wm_client_get_id(const AbstractClient *client) {
|
||||
return client ? client->id : 0;
|
||||
}
|
||||
|
||||
WmClientType wm_client_get_type(const AbstractClient *client) {
|
||||
return client ? client->type : WM_CLIENT_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
void wm_client_set_type(AbstractClient *client, WmClientType type) {
|
||||
if (client) client->type = type;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client State
|
||||
*============================================================================*/
|
||||
|
||||
WmClientState wm_client_get_state(const AbstractClient *client) {
|
||||
return client ? client->state : 0;
|
||||
}
|
||||
|
||||
void wm_client_set_state(AbstractClient *client, WmClientState state) {
|
||||
if (client) client->state = state;
|
||||
}
|
||||
|
||||
void wm_client_add_state(AbstractClient *client, WmClientState state) {
|
||||
if (client) client->state |= state;
|
||||
}
|
||||
|
||||
void wm_client_remove_state(AbstractClient *client, WmClientState state) {
|
||||
if (client) client->state &= ~state;
|
||||
}
|
||||
|
||||
void wm_client_toggle_state(AbstractClient *client, WmClientState state) {
|
||||
if (client) client->state ^= state;
|
||||
}
|
||||
|
||||
bool wm_client_has_state(const AbstractClient *client, WmClientState state) {
|
||||
return client && (client->state & state) != 0;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client Geometry
|
||||
*============================================================================*/
|
||||
|
||||
WmRect wm_client_get_geometry(const AbstractClient *client) {
|
||||
if (!client) return (WmRect){0, 0, 0, 0};
|
||||
|
||||
/* Sync from legacy if available */
|
||||
if (client->legacy) {
|
||||
return (WmRect){
|
||||
client->legacy->x,
|
||||
client->legacy->y,
|
||||
client->legacy->width,
|
||||
client->legacy->height
|
||||
};
|
||||
}
|
||||
|
||||
return client->geometry;
|
||||
}
|
||||
|
||||
void wm_client_set_geometry(AbstractClient *client, const WmRect *geometry) {
|
||||
if (!client || !geometry) return;
|
||||
|
||||
client->geometry = *geometry;
|
||||
|
||||
/* Sync to legacy if available */
|
||||
if (client->legacy) {
|
||||
client->legacy->x = geometry->x;
|
||||
client->legacy->y = geometry->y;
|
||||
client->legacy->width = geometry->width;
|
||||
client->legacy->height = geometry->height;
|
||||
}
|
||||
}
|
||||
|
||||
WmPoint wm_client_get_position(const AbstractClient *client) {
|
||||
WmRect geom = wm_client_get_geometry(client);
|
||||
return (WmPoint){geom.x, geom.y};
|
||||
}
|
||||
|
||||
void wm_client_set_position(AbstractClient *client, int x, int y) {
|
||||
if (!client) return;
|
||||
|
||||
client->geometry.x = x;
|
||||
client->geometry.y = y;
|
||||
|
||||
if (client->legacy) {
|
||||
client->legacy->x = x;
|
||||
client->legacy->y = y;
|
||||
}
|
||||
}
|
||||
|
||||
WmSize wm_client_get_size(const AbstractClient *client) {
|
||||
WmRect geom = wm_client_get_geometry(client);
|
||||
return (WmSize){geom.width, geom.height};
|
||||
}
|
||||
|
||||
void wm_client_set_size(AbstractClient *client, int width, int height) {
|
||||
if (!client) return;
|
||||
|
||||
client->geometry.width = width;
|
||||
client->geometry.height = height;
|
||||
|
||||
if (client->legacy) {
|
||||
client->legacy->width = width;
|
||||
client->legacy->height = height;
|
||||
}
|
||||
}
|
||||
|
||||
int wm_client_get_border_width(const AbstractClient *client) {
|
||||
if (!client) return 0;
|
||||
if (client->legacy) return client->legacy->border_width;
|
||||
return client->border_width;
|
||||
}
|
||||
|
||||
void wm_client_set_border_width(AbstractClient *client, int width) {
|
||||
if (!client) return;
|
||||
|
||||
client->border_width = width;
|
||||
|
||||
if (client->legacy) {
|
||||
client->legacy->border_width = width;
|
||||
}
|
||||
}
|
||||
|
||||
void wm_client_save_geometry(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
client->saved_geometry = wm_client_get_geometry(client);
|
||||
|
||||
if (client->legacy) {
|
||||
client->legacy->old_x = client->saved_geometry.x;
|
||||
client->legacy->old_y = client->saved_geometry.y;
|
||||
client->legacy->old_width = client->saved_geometry.width;
|
||||
client->legacy->old_height = client->saved_geometry.height;
|
||||
}
|
||||
}
|
||||
|
||||
void wm_client_restore_geometry(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
wm_client_set_geometry(client, &client->saved_geometry);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client Workspace
|
||||
*============================================================================*/
|
||||
|
||||
WmWorkspaceId wm_client_get_workspace(const AbstractClient *client) {
|
||||
return client ? client->workspace : 0;
|
||||
}
|
||||
|
||||
void wm_client_set_workspace(AbstractClient *client, WmWorkspaceId workspace) {
|
||||
if (!client) return;
|
||||
|
||||
client->workspace = workspace;
|
||||
|
||||
if (client->legacy) {
|
||||
client->legacy->workspace = workspace;
|
||||
}
|
||||
}
|
||||
|
||||
bool wm_client_is_on_workspace(const AbstractClient *client, WmWorkspaceId workspace) {
|
||||
if (!client) return false;
|
||||
if (client->state & WM_CLIENT_STATE_STICKY) return true;
|
||||
return client->workspace == workspace;
|
||||
}
|
||||
|
||||
bool wm_client_is_visible_on_current(const AbstractClient *client) {
|
||||
if (!client || !dwn) return false;
|
||||
return wm_client_is_on_workspace(client, dwn->current_workspace);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client Properties
|
||||
*============================================================================*/
|
||||
|
||||
const char* wm_client_get_title(const AbstractClient *client) {
|
||||
if (!client) return "";
|
||||
if (client->legacy) return client->legacy->title;
|
||||
return client->title;
|
||||
}
|
||||
|
||||
void wm_client_set_title(AbstractClient *client, const char *title) {
|
||||
if (!client || !title) return;
|
||||
|
||||
strncpy(client->title, title, sizeof(client->title) - 1);
|
||||
client->title[sizeof(client->title) - 1] = '\0';
|
||||
|
||||
if (client->legacy) {
|
||||
strncpy(client->legacy->title, title, sizeof(client->legacy->title) - 1);
|
||||
client->legacy->title[sizeof(client->legacy->title) - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
const char* wm_client_get_class(const AbstractClient *client) {
|
||||
if (!client) return "";
|
||||
if (client->legacy) return client->legacy->class;
|
||||
return client->class_name;
|
||||
}
|
||||
|
||||
void wm_client_set_class(AbstractClient *client, const char *class_name) {
|
||||
if (!client || !class_name) return;
|
||||
|
||||
strncpy(client->class_name, class_name, sizeof(client->class_name) - 1);
|
||||
client->class_name[sizeof(client->class_name) - 1] = '\0';
|
||||
|
||||
if (client->legacy) {
|
||||
strncpy(client->legacy->class, class_name, sizeof(client->legacy->class) - 1);
|
||||
client->legacy->class[sizeof(client->legacy->class) - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
const char* wm_client_get_instance(const AbstractClient *client) {
|
||||
if (!client) return "";
|
||||
return client->instance_name;
|
||||
}
|
||||
|
||||
void wm_client_set_instance(AbstractClient *client, const char *instance) {
|
||||
if (!client || !instance) return;
|
||||
|
||||
strncpy(client->instance_name, instance, sizeof(client->instance_name) - 1);
|
||||
client->instance_name[sizeof(client->instance_name) - 1] = '\0';
|
||||
}
|
||||
|
||||
WmColor wm_client_get_color(const AbstractClient *client) {
|
||||
if (!client) return 0;
|
||||
if (client->legacy) return client->legacy->taskbar_color;
|
||||
return client->color;
|
||||
}
|
||||
|
||||
void wm_client_set_color(AbstractClient *client, WmColor color) {
|
||||
if (!client) return;
|
||||
|
||||
client->color = color;
|
||||
|
||||
if (client->legacy) {
|
||||
client->legacy->taskbar_color = color;
|
||||
}
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client Actions (delegate to legacy for now)
|
||||
*============================================================================*/
|
||||
|
||||
void wm_client_focus(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->legacy) {
|
||||
client_focus(client->legacy, true);
|
||||
}
|
||||
|
||||
client->state |= WM_CLIENT_STATE_FOCUS;
|
||||
|
||||
if (g_client_manager) {
|
||||
wm_client_manager_touch(g_client_manager, client);
|
||||
g_client_manager->focused = client;
|
||||
}
|
||||
}
|
||||
|
||||
void wm_client_unfocus(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->legacy) {
|
||||
client_unfocus(client->legacy);
|
||||
}
|
||||
|
||||
client->state &= ~WM_CLIENT_STATE_FOCUS;
|
||||
|
||||
if (g_client_manager && g_client_manager->focused == client) {
|
||||
g_client_manager->focused = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void wm_client_raise(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->legacy) {
|
||||
client_raise(client->legacy);
|
||||
}
|
||||
}
|
||||
|
||||
void wm_client_lower(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->legacy) {
|
||||
client_lower(client->legacy);
|
||||
}
|
||||
}
|
||||
|
||||
void wm_client_show(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->legacy) {
|
||||
client_show(client->legacy);
|
||||
}
|
||||
|
||||
client->state |= WM_CLIENT_STATE_MAPPED;
|
||||
}
|
||||
|
||||
void wm_client_hide(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->legacy) {
|
||||
client_hide(client->legacy);
|
||||
}
|
||||
|
||||
client->state &= ~WM_CLIENT_STATE_MAPPED;
|
||||
}
|
||||
|
||||
void wm_client_minimize(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->legacy) {
|
||||
client_minimize(client->legacy);
|
||||
}
|
||||
|
||||
client->state |= WM_CLIENT_STATE_MINIMIZED;
|
||||
}
|
||||
|
||||
void wm_client_restore(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->legacy) {
|
||||
client_restore(client->legacy);
|
||||
}
|
||||
|
||||
client->state &= ~WM_CLIENT_STATE_MINIMIZED;
|
||||
}
|
||||
|
||||
void wm_client_close(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->legacy) {
|
||||
client_close(client->legacy);
|
||||
}
|
||||
}
|
||||
|
||||
void wm_client_kill(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->legacy) {
|
||||
client_kill(client->legacy);
|
||||
}
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client Management
|
||||
*============================================================================*/
|
||||
|
||||
void wm_client_reparent(AbstractClient *client, WmWindowHandle frame) {
|
||||
if (!client) return;
|
||||
|
||||
client->frame = frame;
|
||||
|
||||
/* Update frame lookup */
|
||||
if (g_client_manager) {
|
||||
if (client->frame) {
|
||||
wm_hashmap_remove(g_client_manager->by_frame, client->frame);
|
||||
}
|
||||
wm_hashmap_insert(g_client_manager->by_frame, frame, client);
|
||||
}
|
||||
|
||||
if (client->legacy) {
|
||||
/* Legacy doesn't have a direct reparent function for frames */
|
||||
}
|
||||
}
|
||||
|
||||
void wm_client_unreparent(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
if (g_client_manager && client->frame) {
|
||||
wm_hashmap_remove(g_client_manager->by_frame, client->frame);
|
||||
}
|
||||
|
||||
client->frame = NULL;
|
||||
}
|
||||
|
||||
void wm_client_configure(AbstractClient *client) {
|
||||
if (!client) return;
|
||||
|
||||
if (client->legacy) {
|
||||
client_configure(client->legacy);
|
||||
}
|
||||
}
|
||||
|
||||
void wm_client_apply_size_hints(AbstractClient *client, int *width, int *height) {
|
||||
if (!client || !width || !height) return;
|
||||
|
||||
if (client->legacy) {
|
||||
client_apply_size_hints(client->legacy, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void wm_client_move_resize(AbstractClient *client, int x, int y, int width, int height) {
|
||||
if (!client) return;
|
||||
|
||||
client->geometry.x = x;
|
||||
client->geometry.y = y;
|
||||
client->geometry.width = width;
|
||||
client->geometry.height = height;
|
||||
|
||||
if (client->legacy) {
|
||||
client_move_resize(client->legacy, x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client List Management
|
||||
*============================================================================*/
|
||||
|
||||
AbstractClient* wm_client_get_next(const AbstractClient *client) {
|
||||
return client ? client->next : NULL;
|
||||
}
|
||||
|
||||
AbstractClient* wm_client_get_prev(const AbstractClient *client) {
|
||||
return client ? client->prev : NULL;
|
||||
}
|
||||
|
||||
AbstractClient* wm_client_get_first(void) {
|
||||
return g_client_manager ? g_client_manager->clients : NULL;
|
||||
}
|
||||
|
||||
AbstractClient* wm_client_get_last(void) {
|
||||
return g_client_manager ? g_client_manager->clients_tail : NULL;
|
||||
}
|
||||
|
||||
AbstractClient* wm_client_get_next_mru(const AbstractClient *client) {
|
||||
return client ? client->mru_next : NULL;
|
||||
}
|
||||
|
||||
AbstractClient* wm_client_get_prev_mru(const AbstractClient *client) {
|
||||
return client ? client->mru_prev : NULL;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client Lookup
|
||||
*============================================================================*/
|
||||
|
||||
AbstractClient* wm_client_find_by_window(WmWindowHandle window) {
|
||||
if (!g_client_manager || !window) return NULL;
|
||||
return wm_hashmap_get(g_client_manager->by_window, window);
|
||||
}
|
||||
|
||||
AbstractClient* wm_client_find_by_frame(WmWindowHandle frame) {
|
||||
if (!g_client_manager || !frame) return NULL;
|
||||
return wm_hashmap_get(g_client_manager->by_frame, frame);
|
||||
}
|
||||
|
||||
AbstractClient* wm_client_find_by_id(WmClientId id) {
|
||||
if (!g_client_manager) return NULL;
|
||||
return wm_hashmap_get(g_client_manager->by_id, (void*)(intptr_t)id);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client Iteration
|
||||
*============================================================================*/
|
||||
|
||||
void wm_client_foreach(WmClientForeachFunc func, void *user_data) {
|
||||
if (!func) return;
|
||||
|
||||
AbstractClient *client = wm_client_get_first();
|
||||
while (client) {
|
||||
AbstractClient *next = client->next;
|
||||
if (!func(client, user_data)) break;
|
||||
client = next;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
WmWorkspaceId workspace;
|
||||
WmClientForeachFunc func;
|
||||
void *user_data;
|
||||
} ForeachWorkspaceData;
|
||||
|
||||
static bool foreach_workspace_wrapper(AbstractClient *client, void *data) {
|
||||
ForeachWorkspaceData *fwd = data;
|
||||
if (wm_client_is_on_workspace(client, fwd->workspace)) {
|
||||
return fwd->func(client, fwd->user_data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void wm_client_foreach_on_workspace(WmWorkspaceId workspace,
|
||||
WmClientForeachFunc func, void *user_data) {
|
||||
ForeachWorkspaceData data = { workspace, func, user_data };
|
||||
wm_client_foreach(foreach_workspace_wrapper, &data);
|
||||
}
|
||||
|
||||
int wm_client_count(void) {
|
||||
return g_client_manager ? g_client_manager->count : 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int count;
|
||||
WmWorkspaceId workspace;
|
||||
} CountWorkspaceData;
|
||||
|
||||
static bool count_workspace(AbstractClient *client, void *data) {
|
||||
CountWorkspaceData *cwd = data;
|
||||
if (wm_client_is_on_workspace(client, cwd->workspace)) {
|
||||
cwd->count++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int wm_client_count_on_workspace(WmWorkspaceId workspace) {
|
||||
CountWorkspaceData data = { 0, workspace };
|
||||
wm_client_foreach(count_workspace, &data);
|
||||
return data.count;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Client Manager
|
||||
*============================================================================*/
|
||||
|
||||
WmClientManager* wm_client_manager_get(void) {
|
||||
return g_client_manager;
|
||||
}
|
||||
|
||||
bool wm_client_manager_init(void) {
|
||||
if (g_client_manager) return true;
|
||||
|
||||
g_client_manager = calloc(1, sizeof(WmClientManager));
|
||||
if (!g_client_manager) return false;
|
||||
|
||||
g_client_manager->by_window = wm_hashmap_new();
|
||||
g_client_manager->by_frame = wm_hashmap_new();
|
||||
g_client_manager->by_id = wm_hashmap_new();
|
||||
|
||||
if (!g_client_manager->by_window ||
|
||||
!g_client_manager->by_frame ||
|
||||
!g_client_manager->by_id) {
|
||||
wm_client_manager_shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
g_client_manager->next_id = 1;
|
||||
|
||||
LOG_INFO("Client manager initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
void wm_client_manager_shutdown(void) {
|
||||
if (!g_client_manager) return;
|
||||
|
||||
/* Destroy all abstract clients (but not legacy ones - we don't own them) */
|
||||
AbstractClient *client = g_client_manager->clients;
|
||||
while (client) {
|
||||
AbstractClient *next = client->next;
|
||||
if (client->owns_legacy) {
|
||||
client_destroy(client->legacy);
|
||||
}
|
||||
free(client);
|
||||
client = next;
|
||||
}
|
||||
|
||||
wm_hashmap_destroy(g_client_manager->by_window);
|
||||
wm_hashmap_destroy(g_client_manager->by_frame);
|
||||
wm_hashmap_destroy(g_client_manager->by_id);
|
||||
|
||||
free(g_client_manager);
|
||||
g_client_manager = NULL;
|
||||
|
||||
LOG_INFO("Client manager shutdown");
|
||||
}
|
||||
|
||||
void wm_client_manager_add(WmClientManager *mgr, AbstractClient *client) {
|
||||
if (!mgr || !client) return;
|
||||
|
||||
/* Client is already linked when created */
|
||||
}
|
||||
|
||||
void wm_client_manager_remove(WmClientManager *mgr, AbstractClient *client) {
|
||||
if (!mgr || !client) return;
|
||||
|
||||
/* Client is already unlinked when destroyed */
|
||||
}
|
||||
|
||||
void wm_client_manager_touch(WmClientManager *mgr, AbstractClient *client) {
|
||||
if (!mgr || !client) return;
|
||||
|
||||
/* Already at head - nothing to do */
|
||||
if (mgr->mru_head == client) return;
|
||||
|
||||
/* Remove from current position */
|
||||
if (client->mru_prev) {
|
||||
client->mru_prev->mru_next = client->mru_next;
|
||||
}
|
||||
if (client->mru_next) {
|
||||
client->mru_next->mru_prev = client->mru_prev;
|
||||
}
|
||||
if (mgr->mru_tail == client) {
|
||||
mgr->mru_tail = client->mru_prev;
|
||||
}
|
||||
|
||||
/* Insert at head */
|
||||
client->mru_prev = NULL;
|
||||
client->mru_next = mgr->mru_head;
|
||||
if (mgr->mru_head) {
|
||||
mgr->mru_head->mru_prev = client;
|
||||
}
|
||||
mgr->mru_head = client;
|
||||
}
|
||||
|
||||
AbstractClient* wm_client_manager_get_focused(const WmClientManager *mgr) {
|
||||
return mgr ? mgr->focused : NULL;
|
||||
}
|
||||
|
||||
void wm_client_manager_set_focused(WmClientManager *mgr, AbstractClient *client) {
|
||||
if (!mgr) return;
|
||||
mgr->focused = client;
|
||||
}
|
||||
416
src/core/wm_hashmap.c
Normal file
416
src/core/wm_hashmap.c
Normal file
@ -0,0 +1,416 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* Abstract Hash Map Implementation
|
||||
*/
|
||||
|
||||
#include "core/wm_hashmap.h"
|
||||
#include "core/wm_string.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/*==============================================================================
|
||||
* Constants
|
||||
*============================================================================*/
|
||||
|
||||
#define WM_HASHMAP_INITIAL_CAPACITY 16
|
||||
#define WM_HASHMAP_LOAD_FACTOR 0.75
|
||||
|
||||
/*==============================================================================
|
||||
* Entry Structure
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct WmHashMapEntry {
|
||||
uint32_t hash;
|
||||
void *key;
|
||||
void *value;
|
||||
struct WmHashMapEntry *next;
|
||||
} WmHashMapEntry;
|
||||
|
||||
/*==============================================================================
|
||||
* Hash Map Structure
|
||||
*============================================================================*/
|
||||
|
||||
struct WmHashMap {
|
||||
WmHashMapEntry **buckets;
|
||||
size_t bucket_count;
|
||||
size_t size;
|
||||
WmHashFunc hash_func;
|
||||
WmKeyEqualFunc key_equal;
|
||||
WmFreeFunc key_free;
|
||||
WmFreeFunc value_free;
|
||||
};
|
||||
|
||||
/*==============================================================================
|
||||
* Internal Helpers
|
||||
*============================================================================*/
|
||||
|
||||
static void hashmap_clone_insert(void *key, void *value, void *user_data) {
|
||||
wm_hashmap_insert((WmHashMap *)user_data, key, value);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Default Hash Functions
|
||||
*============================================================================*/
|
||||
|
||||
static uint32_t wm_hash_pointer(const void *key) {
|
||||
/* FNV-1a hash for pointer */
|
||||
uintptr_t ptr = (uintptr_t)key;
|
||||
uint32_t hash = 2166136261u;
|
||||
for (size_t i = 0; i < sizeof(ptr); i++) {
|
||||
hash ^= (ptr >> (i * 8)) & 0xFF;
|
||||
hash *= 16777619u;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static uint32_t wm_hash_string(const void *key) {
|
||||
const char *str = key;
|
||||
uint32_t hash = 2166136261u;
|
||||
while (*str) {
|
||||
hash ^= (uint8_t)*str++;
|
||||
hash *= 16777619u;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static bool wm_equal_pointer(const void *a, const void *b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
static bool wm_equal_string(const void *a, const void *b) {
|
||||
return strcmp((const char*)a, (const char*)b) == 0;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Internal Helpers
|
||||
*============================================================================*/
|
||||
|
||||
static bool wm_hashmap_resize(WmHashMap *map) {
|
||||
size_t old_count = map->bucket_count;
|
||||
WmHashMapEntry **old_buckets = map->buckets;
|
||||
|
||||
size_t new_count = old_count * 2;
|
||||
WmHashMapEntry **new_buckets = calloc(new_count, sizeof(WmHashMapEntry*));
|
||||
if (!new_buckets) return false;
|
||||
|
||||
map->buckets = new_buckets;
|
||||
map->bucket_count = new_count;
|
||||
|
||||
/* Rehash all entries */
|
||||
for (size_t i = 0; i < old_count; i++) {
|
||||
WmHashMapEntry *entry = old_buckets[i];
|
||||
while (entry) {
|
||||
WmHashMapEntry *next = entry->next;
|
||||
size_t new_index = entry->hash % new_count;
|
||||
entry->next = new_buckets[new_index];
|
||||
new_buckets[new_index] = entry;
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
|
||||
free(old_buckets);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Lifecycle
|
||||
*============================================================================*/
|
||||
|
||||
WmHashMap* wm_hashmap_new(void) {
|
||||
return wm_hashmap_new_full(NULL, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
WmHashMap* wm_hashmap_new_full(WmHashFunc hash_func, WmKeyEqualFunc key_equal,
|
||||
WmFreeFunc key_free, WmFreeFunc value_free) {
|
||||
WmHashMap *map = calloc(1, sizeof(WmHashMap));
|
||||
if (!map) return NULL;
|
||||
|
||||
map->bucket_count = WM_HASHMAP_INITIAL_CAPACITY;
|
||||
map->buckets = calloc(map->bucket_count, sizeof(WmHashMapEntry*));
|
||||
if (!map->buckets) {
|
||||
free(map);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
map->hash_func = hash_func ? hash_func : wm_hash_pointer;
|
||||
map->key_equal = key_equal ? key_equal : wm_equal_pointer;
|
||||
map->key_free = key_free;
|
||||
map->value_free = value_free;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
WmHashMap* wm_hashmap_new_string_key(void) {
|
||||
return wm_hashmap_new_full(wm_hash_string, wm_equal_string, free, NULL);
|
||||
}
|
||||
|
||||
void wm_hashmap_destroy(WmHashMap *map) {
|
||||
if (!map) return;
|
||||
wm_hashmap_clear(map);
|
||||
free(map->buckets);
|
||||
free(map);
|
||||
}
|
||||
|
||||
WmHashMap* wm_hashmap_clone(const WmHashMap *map) {
|
||||
if (!map) return NULL;
|
||||
|
||||
WmHashMap *clone = wm_hashmap_new_full(map->hash_func, map->key_equal,
|
||||
NULL, NULL);
|
||||
if (!clone) return NULL;
|
||||
|
||||
/* Note: This creates a shallow copy */
|
||||
wm_hashmap_foreach(map, hashmap_clone_insert, clone);
|
||||
|
||||
clone->key_free = map->key_free;
|
||||
clone->value_free = map->value_free;
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Capacity
|
||||
*============================================================================*/
|
||||
|
||||
size_t wm_hashmap_size(const WmHashMap *map) {
|
||||
return map ? map->size : 0;
|
||||
}
|
||||
|
||||
bool wm_hashmap_is_empty(const WmHashMap *map) {
|
||||
return !map || map->size == 0;
|
||||
}
|
||||
|
||||
void wm_hashmap_clear(WmHashMap *map) {
|
||||
if (!map) return;
|
||||
|
||||
for (size_t i = 0; i < map->bucket_count; i++) {
|
||||
WmHashMapEntry *entry = map->buckets[i];
|
||||
while (entry) {
|
||||
WmHashMapEntry *next = entry->next;
|
||||
if (map->key_free && entry->key) map->key_free(entry->key);
|
||||
if (map->value_free && entry->value) map->value_free(entry->value);
|
||||
free(entry);
|
||||
entry = next;
|
||||
}
|
||||
map->buckets[i] = NULL;
|
||||
}
|
||||
map->size = 0;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Element Access
|
||||
*============================================================================*/
|
||||
|
||||
void* wm_hashmap_get(const WmHashMap *map, const void *key) {
|
||||
if (!map || !key) return NULL;
|
||||
|
||||
uint32_t hash = map->hash_func(key);
|
||||
size_t index = hash % map->bucket_count;
|
||||
|
||||
WmHashMapEntry *entry = map->buckets[index];
|
||||
while (entry) {
|
||||
if (entry->hash == hash && map->key_equal(entry->key, key)) {
|
||||
return entry->value;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* wm_hashmap_get_or_default(const WmHashMap *map, const void *key, void *default_val) {
|
||||
void *val = wm_hashmap_get(map, key);
|
||||
return val ? val : default_val;
|
||||
}
|
||||
|
||||
bool wm_hashmap_contains(const WmHashMap *map, const void *key) {
|
||||
return wm_hashmap_get(map, key) != NULL;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Modifiers
|
||||
*============================================================================*/
|
||||
|
||||
bool wm_hashmap_insert(WmHashMap *map, void *key, void *value) {
|
||||
if (!map || !key) return false;
|
||||
|
||||
/* Check if resize needed */
|
||||
if ((double)map->size / map->bucket_count > WM_HASHMAP_LOAD_FACTOR) {
|
||||
if (!wm_hashmap_resize(map)) return false;
|
||||
}
|
||||
|
||||
uint32_t hash = map->hash_func(key);
|
||||
size_t index = hash % map->bucket_count;
|
||||
|
||||
/* Check if key exists */
|
||||
WmHashMapEntry *entry = map->buckets[index];
|
||||
while (entry) {
|
||||
if (entry->hash == hash && map->key_equal(entry->key, key)) {
|
||||
/* Update existing entry */
|
||||
if (map->value_free) map->value_free(entry->value);
|
||||
entry->value = value;
|
||||
return true;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
/* Insert new entry */
|
||||
entry = malloc(sizeof(WmHashMapEntry));
|
||||
if (!entry) return false;
|
||||
|
||||
entry->hash = hash;
|
||||
entry->key = key;
|
||||
entry->value = value;
|
||||
entry->next = map->buckets[index];
|
||||
map->buckets[index] = entry;
|
||||
map->size++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wm_hashmap_insert_no_replace(WmHashMap *map, void *key, void *value) {
|
||||
if (!map || !key) return false;
|
||||
if (wm_hashmap_contains(map, key)) return false;
|
||||
return wm_hashmap_insert(map, key, value);
|
||||
}
|
||||
|
||||
void* wm_hashmap_set(WmHashMap *map, void *key, void *value) {
|
||||
if (!map || !key) return NULL;
|
||||
|
||||
void *old_value = wm_hashmap_get(map, key);
|
||||
wm_hashmap_insert(map, key, value);
|
||||
return old_value;
|
||||
}
|
||||
|
||||
void* wm_hashmap_remove(WmHashMap *map, const void *key) {
|
||||
if (!map || !key) return NULL;
|
||||
|
||||
uint32_t hash = map->hash_func(key);
|
||||
size_t index = hash % map->bucket_count;
|
||||
|
||||
WmHashMapEntry *entry = map->buckets[index];
|
||||
WmHashMapEntry *prev = NULL;
|
||||
|
||||
while (entry) {
|
||||
if (entry->hash == hash && map->key_equal(entry->key, key)) {
|
||||
void *value = entry->value;
|
||||
|
||||
if (prev) {
|
||||
prev->next = entry->next;
|
||||
} else {
|
||||
map->buckets[index] = entry->next;
|
||||
}
|
||||
|
||||
if (map->key_free) map->key_free(entry->key);
|
||||
free(entry);
|
||||
map->size--;
|
||||
|
||||
return value;
|
||||
}
|
||||
prev = entry;
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool wm_hashmap_steal(WmHashMap *map, const void *key) {
|
||||
return wm_hashmap_remove(map, key) != NULL;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Iteration
|
||||
*============================================================================*/
|
||||
|
||||
typedef struct WmHashMapIterInternal {
|
||||
WmHashMap *map;
|
||||
size_t bucket;
|
||||
WmHashMapEntry *entry;
|
||||
WmHashMapEntry *next_entry;
|
||||
} WmHashMapIterInternal;
|
||||
|
||||
void wm_hashmap_iter_init(WmHashMapIter *iter, WmHashMap *map) {
|
||||
if (!iter) return;
|
||||
|
||||
iter->map = map;
|
||||
iter->bucket = 0;
|
||||
iter->entry = NULL;
|
||||
iter->next_entry = NULL;
|
||||
|
||||
if (!map) return;
|
||||
|
||||
/* Find first bucket with entry */
|
||||
for (size_t i = 0; i < map->bucket_count; i++) {
|
||||
if (map->buckets[i]) {
|
||||
iter->bucket = i;
|
||||
iter->entry = map->buckets[i];
|
||||
iter->next_entry = ((WmHashMapEntry*)iter->entry)->next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool wm_hashmap_iter_next(WmHashMapIter *iter, void **key, void **value) {
|
||||
if (!iter || !iter->entry) return false;
|
||||
|
||||
WmHashMapEntry *entry = (WmHashMapEntry*)iter->entry;
|
||||
*key = entry->key;
|
||||
*value = entry->value;
|
||||
|
||||
/* Move to next entry */
|
||||
entry = entry->next;
|
||||
if (!entry) {
|
||||
/* Find next bucket with entries */
|
||||
WmHashMap *map = (WmHashMap*)iter->map;
|
||||
for (size_t i = iter->bucket + 1; i < map->bucket_count; i++) {
|
||||
if (map->buckets[i]) {
|
||||
iter->bucket = i;
|
||||
entry = map->buckets[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
iter->entry = entry;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void wm_hashmap_foreach(const WmHashMap *map, WmHashForeachFunc func, void *user_data) {
|
||||
if (!map || !func) return;
|
||||
|
||||
for (size_t i = 0; i < map->bucket_count; i++) {
|
||||
WmHashMapEntry *entry = map->buckets[i];
|
||||
while (entry) {
|
||||
func(entry->key, entry->value, user_data);
|
||||
entry = entry->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* String Helpers
|
||||
*============================================================================*/
|
||||
|
||||
bool wm_hashmap_insert_string(WmHashMap *map, const char *key, void *value) {
|
||||
if (!map || !key) return false;
|
||||
|
||||
char *key_copy = strdup(key);
|
||||
if (!key_copy) return false;
|
||||
|
||||
if (!wm_hashmap_insert(map, key_copy, value)) {
|
||||
free(key_copy);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void* wm_hashmap_get_string(const WmHashMap *map, const char *key) {
|
||||
return wm_hashmap_get(map, key);
|
||||
}
|
||||
|
||||
bool wm_hashmap_contains_string(const WmHashMap *map, const char *key) {
|
||||
return wm_hashmap_contains(map, key);
|
||||
}
|
||||
|
||||
void* wm_hashmap_remove_string(WmHashMap *map, const char *key) {
|
||||
return wm_hashmap_remove(map, key);
|
||||
}
|
||||
432
src/core/wm_list.c
Normal file
432
src/core/wm_list.c
Normal file
@ -0,0 +1,432 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* Abstract List Implementation (Dynamic Array)
|
||||
*/
|
||||
|
||||
#include "core/wm_list.h"
|
||||
#include "core/wm_types.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/*==============================================================================
|
||||
* List Structure
|
||||
*============================================================================*/
|
||||
|
||||
struct WmList {
|
||||
void **data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
WmFreeFunc free_func;
|
||||
};
|
||||
|
||||
/*==============================================================================
|
||||
* Lifecycle
|
||||
*============================================================================*/
|
||||
|
||||
WmList* wm_list_new(void) {
|
||||
WmList *list = calloc(1, sizeof(WmList));
|
||||
if (!list) return NULL;
|
||||
|
||||
list->capacity = 8;
|
||||
list->data = malloc(list->capacity * sizeof(void*));
|
||||
if (!list->data) {
|
||||
free(list);
|
||||
return NULL;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
WmList* wm_list_new_sized(size_t initial_capacity) {
|
||||
WmList *list = calloc(1, sizeof(WmList));
|
||||
if (!list) return NULL;
|
||||
|
||||
list->capacity = initial_capacity > 0 ? initial_capacity : 8;
|
||||
list->data = malloc(list->capacity * sizeof(void*));
|
||||
if (!list->data) {
|
||||
free(list);
|
||||
return NULL;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void wm_list_destroy(WmList *list) {
|
||||
if (!list) return;
|
||||
|
||||
if (list->free_func) {
|
||||
for (size_t i = 0; i < list->size; i++) {
|
||||
list->free_func(list->data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
free(list->data);
|
||||
free(list);
|
||||
}
|
||||
|
||||
void wm_list_destroy_no_free(WmList *list) {
|
||||
if (!list) return;
|
||||
free(list->data);
|
||||
free(list);
|
||||
}
|
||||
|
||||
WmList* wm_list_clone(const WmList *list) {
|
||||
if (!list) return NULL;
|
||||
|
||||
WmList *clone = wm_list_new_sized(list->capacity);
|
||||
if (!clone) return NULL;
|
||||
|
||||
memcpy(clone->data, list->data, list->size * sizeof(void*));
|
||||
clone->size = list->size;
|
||||
clone->free_func = list->free_func;
|
||||
return clone;
|
||||
}
|
||||
|
||||
void wm_list_set_free_func(WmList *list, WmFreeFunc func) {
|
||||
if (list) list->free_func = func;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Capacity
|
||||
*============================================================================*/
|
||||
|
||||
static bool wm_list_grow(WmList *list, size_t min_capacity) {
|
||||
if (list->capacity >= min_capacity) return true;
|
||||
|
||||
size_t new_capacity = list->capacity * 2;
|
||||
if (new_capacity < min_capacity) new_capacity = min_capacity;
|
||||
|
||||
void **new_data = realloc(list->data, new_capacity * sizeof(void*));
|
||||
if (!new_data) return false;
|
||||
|
||||
list->data = new_data;
|
||||
list->capacity = new_capacity;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t wm_list_size(const WmList *list) {
|
||||
return list ? list->size : 0;
|
||||
}
|
||||
|
||||
size_t wm_list_capacity(const WmList *list) {
|
||||
return list ? list->capacity : 0;
|
||||
}
|
||||
|
||||
bool wm_list_is_empty(const WmList *list) {
|
||||
return !list || list->size == 0;
|
||||
}
|
||||
|
||||
void wm_list_reserve(WmList *list, size_t capacity) {
|
||||
if (!list) return;
|
||||
wm_list_grow(list, capacity);
|
||||
}
|
||||
|
||||
void wm_list_compact(WmList *list) {
|
||||
if (!list || list->capacity <= list->size) return;
|
||||
|
||||
size_t new_capacity = list->size > 0 ? list->size : 1;
|
||||
void **new_data = realloc(list->data, new_capacity * sizeof(void*));
|
||||
if (new_data) {
|
||||
list->data = new_data;
|
||||
list->capacity = new_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
void wm_list_clear(WmList *list) {
|
||||
if (!list) return;
|
||||
|
||||
if (list->free_func) {
|
||||
for (size_t i = 0; i < list->size; i++) {
|
||||
list->free_func(list->data[i]);
|
||||
}
|
||||
}
|
||||
list->size = 0;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Modifiers
|
||||
*============================================================================*/
|
||||
|
||||
void wm_list_append(WmList *list, void *item) {
|
||||
if (!list) return;
|
||||
|
||||
if (!wm_list_grow(list, list->size + 1)) return;
|
||||
list->data[list->size++] = item;
|
||||
}
|
||||
|
||||
void wm_list_prepend(WmList *list, void *item) {
|
||||
wm_list_insert(list, 0, item);
|
||||
}
|
||||
|
||||
void wm_list_insert(WmList *list, size_t index, void *item) {
|
||||
if (!list || index > list->size) return;
|
||||
|
||||
if (!wm_list_grow(list, list->size + 1)) return;
|
||||
|
||||
memmove(&list->data[index + 1], &list->data[index],
|
||||
(list->size - index) * sizeof(void*));
|
||||
list->data[index] = item;
|
||||
list->size++;
|
||||
}
|
||||
|
||||
void wm_list_insert_sorted(WmList *list, void *item, WmCompareFunc compare) {
|
||||
if (!list) return;
|
||||
|
||||
size_t i = 0;
|
||||
while (i < list->size && compare(list->data[i], item) < 0) {
|
||||
i++;
|
||||
}
|
||||
wm_list_insert(list, i, item);
|
||||
}
|
||||
|
||||
void wm_list_remove(WmList *list, void *item) {
|
||||
if (!list) return;
|
||||
|
||||
for (size_t i = 0; i < list->size; i++) {
|
||||
if (list->data[i] == item) {
|
||||
wm_list_remove_at(list, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wm_list_remove_at(WmList *list, size_t index) {
|
||||
if (!list || index >= list->size) return;
|
||||
|
||||
if (list->free_func) {
|
||||
list->free_func(list->data[index]);
|
||||
}
|
||||
|
||||
memmove(&list->data[index], &list->data[index + 1],
|
||||
(list->size - index - 1) * sizeof(void*));
|
||||
list->size--;
|
||||
}
|
||||
|
||||
void* wm_list_take_at(WmList *list, size_t index) {
|
||||
if (!list || index >= list->size) return NULL;
|
||||
|
||||
void *item = list->data[index];
|
||||
memmove(&list->data[index], &list->data[index + 1],
|
||||
(list->size - index - 1) * sizeof(void*));
|
||||
list->size--;
|
||||
return item;
|
||||
}
|
||||
|
||||
bool wm_list_remove_one(WmList *list, void *item) {
|
||||
if (!list) return false;
|
||||
|
||||
for (size_t i = 0; i < list->size; i++) {
|
||||
if (list->data[i] == item) {
|
||||
wm_list_remove_at(list, i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool wm_list_remove_all(WmList *list, void *item) {
|
||||
if (!list) return false;
|
||||
|
||||
bool removed = false;
|
||||
for (size_t i = list->size; i-- > 0; ) {
|
||||
if (list->data[i] == item) {
|
||||
wm_list_remove_at(list, i);
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
void* wm_list_pop_back(WmList *list) {
|
||||
if (!list || list->size == 0) return NULL;
|
||||
return list->data[--list->size];
|
||||
}
|
||||
|
||||
void* wm_list_pop_front(WmList *list) {
|
||||
return wm_list_take_at(list, 0);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Accessors
|
||||
*============================================================================*/
|
||||
|
||||
void* wm_list_get(const WmList *list, size_t index) {
|
||||
if (!list || index >= list->size) return NULL;
|
||||
return list->data[index];
|
||||
}
|
||||
|
||||
void* wm_list_first(const WmList *list) {
|
||||
if (!list || list->size == 0) return NULL;
|
||||
return list->data[0];
|
||||
}
|
||||
|
||||
void* wm_list_last(const WmList *list) {
|
||||
if (!list || list->size == 0) return NULL;
|
||||
return list->data[list->size - 1];
|
||||
}
|
||||
|
||||
void* wm_list_front(const WmList *list) {
|
||||
return wm_list_first(list);
|
||||
}
|
||||
|
||||
void* wm_list_back(const WmList *list) {
|
||||
return wm_list_last(list);
|
||||
}
|
||||
|
||||
void wm_list_set(WmList *list, size_t index, void *item) {
|
||||
if (!list || index >= list->size) return;
|
||||
|
||||
if (list->free_func) {
|
||||
list->free_func(list->data[index]);
|
||||
}
|
||||
list->data[index] = item;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Iteration
|
||||
*============================================================================*/
|
||||
|
||||
void wm_list_foreach(const WmList *list, WmForeachFunc func, void *user_data) {
|
||||
if (!list || !func) return;
|
||||
|
||||
for (size_t i = 0; i < list->size; i++) {
|
||||
if (!func(list->data[i], i, user_data)) break;
|
||||
}
|
||||
}
|
||||
|
||||
void wm_list_foreach_reverse(const WmList *list, WmForeachFunc func, void *user_data) {
|
||||
if (!list || !func) return;
|
||||
|
||||
for (size_t i = list->size; i-- > 0; ) {
|
||||
if (!func(list->data[i], i, user_data)) break;
|
||||
}
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Search
|
||||
*============================================================================*/
|
||||
|
||||
ssize_t wm_list_index_of(const WmList *list, void *item) {
|
||||
if (!list) return -1;
|
||||
|
||||
for (size_t i = 0; i < list->size; i++) {
|
||||
if (list->data[i] == item) return (ssize_t)i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool wm_list_contains(const WmList *list, void *item) {
|
||||
return wm_list_index_of(list, item) >= 0;
|
||||
}
|
||||
|
||||
void* wm_list_find(const WmList *list, WmCompareFunc compare, const void *key) {
|
||||
if (!list || !compare) return NULL;
|
||||
|
||||
for (size_t i = 0; i < list->size; i++) {
|
||||
if (compare(list->data[i], key) == 0) return list->data[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ssize_t wm_list_find_index(const WmList *list, WmCompareFunc compare, const void *key) {
|
||||
if (!list || !compare) return -1;
|
||||
|
||||
for (size_t i = 0; i < list->size; i++) {
|
||||
if (compare(list->data[i], key) == 0) return (ssize_t)i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Sorting
|
||||
*============================================================================*/
|
||||
|
||||
static void wm_list_qsort_helper(void **data, size_t left, size_t right,
|
||||
WmCompareFunc compare) {
|
||||
if (left >= right) return;
|
||||
|
||||
void *pivot = data[(left + right) / 2];
|
||||
size_t i = left, j = right;
|
||||
|
||||
while (i <= j) {
|
||||
while (compare(data[i], pivot) < 0) i++;
|
||||
while (compare(data[j], pivot) > 0) j--;
|
||||
if (i <= j) {
|
||||
void *tmp = data[i];
|
||||
data[i] = data[j];
|
||||
data[j] = tmp;
|
||||
i++;
|
||||
if (j > 0) j--;
|
||||
}
|
||||
}
|
||||
|
||||
if (j > left) wm_list_qsort_helper(data, left, j, compare);
|
||||
if (i < right) wm_list_qsort_helper(data, i, right, compare);
|
||||
}
|
||||
|
||||
void wm_list_sort(WmList *list, WmCompareFunc compare) {
|
||||
if (!list || list->size < 2) return;
|
||||
wm_list_qsort_helper(list->data, 0, list->size - 1, compare);
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Data Operations
|
||||
*============================================================================*/
|
||||
|
||||
void** wm_list_data(WmList *list) {
|
||||
return list ? list->data : NULL;
|
||||
}
|
||||
|
||||
void* wm_list_steal(WmList *list, size_t index) {
|
||||
return wm_list_take_at(list, index);
|
||||
}
|
||||
|
||||
WmList* wm_list_slice(const WmList *list, size_t start, size_t end) {
|
||||
if (!list || start >= list->size || start >= end) return wm_list_new();
|
||||
if (end > list->size) end = list->size;
|
||||
|
||||
size_t count = end - start;
|
||||
WmList *slice = wm_list_new_sized(count);
|
||||
if (!slice) return NULL;
|
||||
|
||||
memcpy(slice->data, &list->data[start], count * sizeof(void*));
|
||||
slice->size = count;
|
||||
return slice;
|
||||
}
|
||||
|
||||
void wm_list_move(WmList *list, size_t from, size_t to) {
|
||||
if (!list || from >= list->size || to >= list->size || from == to) return;
|
||||
|
||||
void *item = list->data[from];
|
||||
if (from < to) {
|
||||
memmove(&list->data[from], &list->data[from + 1],
|
||||
(to - from) * sizeof(void*));
|
||||
} else {
|
||||
memmove(&list->data[to + 1], &list->data[to],
|
||||
(from - to) * sizeof(void*));
|
||||
}
|
||||
list->data[to] = item;
|
||||
}
|
||||
|
||||
void wm_list_swap(WmList *list, size_t i, size_t j) {
|
||||
if (!list || i >= list->size || j >= list->size) return;
|
||||
|
||||
void *tmp = list->data[i];
|
||||
list->data[i] = list->data[j];
|
||||
list->data[j] = tmp;
|
||||
}
|
||||
|
||||
/*==============================================================================
|
||||
* Comparison
|
||||
*============================================================================*/
|
||||
|
||||
bool wm_list_equals(const WmList *list1, const WmList *list2) {
|
||||
if (list1 == list2) return true;
|
||||
if (!list1 || !list2) return false;
|
||||
if (list1->size != list2->size) return false;
|
||||
|
||||
for (size_t i = 0; i < list1->size; i++) {
|
||||
if (list1->data[i] != list2->data[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
650
src/core/wm_string.c
Normal file
650
src/core/wm_string.c
Normal 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;
|
||||
}
|
||||
@ -164,8 +164,10 @@ void decorations_render_title_bar(Client *client, bool focused)
|
||||
return;
|
||||
}
|
||||
char display_title[256];
|
||||
strncpy(display_title, client->title, sizeof(display_title) - 4);
|
||||
display_title[sizeof(display_title) - 4] = '\0';
|
||||
size_t disp_len = strlen(client->title);
|
||||
if (disp_len >= sizeof(display_title) - 3) disp_len = sizeof(display_title) - 4;
|
||||
memcpy(display_title, client->title, disp_len);
|
||||
display_title[disp_len] = '\0';
|
||||
|
||||
XGlyphInfo extents;
|
||||
XftTextExtentsUtf8(dpy, title_font,
|
||||
|
||||
329
src/event_bus.c
Normal file
329
src/event_bus.c
Normal file
@ -0,0 +1,329 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* retoor <retoor@molodetz.nl>
|
||||
* Event Bus Implementation - Thread-Safe Pub/Sub
|
||||
*/
|
||||
|
||||
#include "threading.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#define MAX_SUBSCRIPTIONS_PER_TYPE 64
|
||||
#define EVENT_QUEUE_SIZE 4096
|
||||
|
||||
/* ============================================================================
|
||||
* Event Bus Types
|
||||
* ============================================================================ */
|
||||
|
||||
typedef struct Subscription {
|
||||
SubscriptionId id;
|
||||
EventType type;
|
||||
EventHandler handler;
|
||||
void *user_data;
|
||||
EventFilter filter;
|
||||
void *filter_data;
|
||||
bool active;
|
||||
} Subscription;
|
||||
|
||||
typedef struct EventNode {
|
||||
EventType type;
|
||||
void *data;
|
||||
void (*free_fn)(void*);
|
||||
struct EventNode *next;
|
||||
} EventNode;
|
||||
|
||||
struct EventBus {
|
||||
Subscription subscriptions[MAX_SUBSCRIPTIONS_PER_TYPE * 32]; /* Fixed pool */
|
||||
uint32_t subscription_count;
|
||||
_Atomic SubscriptionId next_id;
|
||||
|
||||
/* Event queue (MPSC) */
|
||||
pthread_mutex_t queue_mutex;
|
||||
EventNode *event_head;
|
||||
EventNode *event_tail;
|
||||
_Atomic uint32_t event_count;
|
||||
|
||||
int event_fd;
|
||||
_Atomic int shutdown;
|
||||
|
||||
uint32_t batch_size;
|
||||
pthread_mutex_t sub_mutex;
|
||||
};
|
||||
|
||||
static SubscriptionId generate_subscription_id(EventBus *bus)
|
||||
{
|
||||
return atomic_fetch_add_explicit(&bus->next_id, 1, ATOMIC_RELAXED) + 1;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Event Bus Implementation
|
||||
* ============================================================================ */
|
||||
|
||||
EventBus* event_bus_create(void)
|
||||
{
|
||||
EventBus *bus = dwn_malloc(sizeof(EventBus));
|
||||
if (!bus) return NULL;
|
||||
|
||||
memset(bus->subscriptions, 0, sizeof(bus->subscriptions));
|
||||
bus->subscription_count = 0;
|
||||
atomic_store_explicit(&bus->next_id, 0, ATOMIC_RELEASE);
|
||||
|
||||
bus->event_head = NULL;
|
||||
bus->event_tail = NULL;
|
||||
atomic_store_explicit(&bus->event_count, 0, ATOMIC_RELEASE);
|
||||
|
||||
atomic_store_explicit(&bus->shutdown, 0, ATOMIC_RELEASE);
|
||||
bus->batch_size = 100;
|
||||
|
||||
if (pthread_mutex_init(&bus->queue_mutex, NULL) != 0) {
|
||||
dwn_free(bus);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&bus->sub_mutex, NULL) != 0) {
|
||||
pthread_mutex_destroy(&bus->queue_mutex);
|
||||
dwn_free(bus);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bus->event_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
|
||||
if (bus->event_fd < 0) {
|
||||
pthread_mutex_destroy(&bus->sub_mutex);
|
||||
pthread_mutex_destroy(&bus->queue_mutex);
|
||||
dwn_free(bus);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return bus;
|
||||
}
|
||||
|
||||
void event_bus_destroy(EventBus *bus)
|
||||
{
|
||||
if (!bus) return;
|
||||
|
||||
atomic_store_explicit(&bus->shutdown, 1, ATOMIC_RELEASE);
|
||||
|
||||
/* Drain remaining events */
|
||||
event_bus_process(bus);
|
||||
|
||||
/* Free remaining events */
|
||||
pthread_mutex_lock(&bus->queue_mutex);
|
||||
EventNode *node = bus->event_head;
|
||||
while (node) {
|
||||
EventNode *next = node->next;
|
||||
if (node->free_fn) {
|
||||
node->free_fn(node->data);
|
||||
}
|
||||
dwn_free(node);
|
||||
node = next;
|
||||
}
|
||||
pthread_mutex_unlock(&bus->queue_mutex);
|
||||
|
||||
close(bus->event_fd);
|
||||
pthread_mutex_destroy(&bus->sub_mutex);
|
||||
pthread_mutex_destroy(&bus->queue_mutex);
|
||||
dwn_free(bus);
|
||||
}
|
||||
|
||||
SubscriptionId event_bus_subscribe(EventBus *bus, EventType type,
|
||||
EventHandler handler, void *user_data)
|
||||
{
|
||||
return event_bus_subscribe_filtered(bus, type, handler, user_data, NULL, NULL);
|
||||
}
|
||||
|
||||
SubscriptionId event_bus_subscribe_filtered(EventBus *bus, EventType type,
|
||||
EventHandler handler, void *user_data,
|
||||
EventFilter filter, void *filter_data)
|
||||
{
|
||||
if (!bus || !handler || type >= 32) return 0;
|
||||
|
||||
pthread_mutex_lock(&bus->sub_mutex);
|
||||
|
||||
if (bus->subscription_count >= sizeof(bus->subscriptions) / sizeof(bus->subscriptions[0])) {
|
||||
pthread_mutex_unlock(&bus->sub_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find slot */
|
||||
for (uint32_t i = 0; i < sizeof(bus->subscriptions) / sizeof(bus->subscriptions[0]); i++) {
|
||||
if (!bus->subscriptions[i].active) {
|
||||
Subscription *sub = &bus->subscriptions[i];
|
||||
sub->id = generate_subscription_id(bus);
|
||||
sub->type = type;
|
||||
sub->handler = handler;
|
||||
sub->user_data = user_data;
|
||||
sub->filter = filter;
|
||||
sub->filter_data = filter_data;
|
||||
sub->active = true;
|
||||
|
||||
bus->subscription_count++;
|
||||
pthread_mutex_unlock(&bus->sub_mutex);
|
||||
return sub->id;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&bus->sub_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool event_bus_unsubscribe(EventBus *bus, SubscriptionId id)
|
||||
{
|
||||
if (!bus || id == 0) return false;
|
||||
|
||||
pthread_mutex_lock(&bus->sub_mutex);
|
||||
|
||||
for (uint32_t i = 0; i < sizeof(bus->subscriptions) / sizeof(bus->subscriptions[0]); i++) {
|
||||
if (bus->subscriptions[i].active && bus->subscriptions[i].id == id) {
|
||||
bus->subscriptions[i].active = false;
|
||||
bus->subscription_count--;
|
||||
pthread_mutex_unlock(&bus->sub_mutex);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&bus->sub_mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
void event_bus_publish(EventBus *bus, EventType type, void *event_data)
|
||||
{
|
||||
event_bus_publish_owned(bus, type, event_data, NULL);
|
||||
}
|
||||
|
||||
void event_bus_publish_owned(EventBus *bus, EventType type, void *event_data,
|
||||
void (*free_fn)(void*))
|
||||
{
|
||||
if (!bus || type >= 32) {
|
||||
if (free_fn && event_data) free_fn(event_data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (atomic_load_explicit(&bus->shutdown, ATOMIC_ACQUIRE)) {
|
||||
if (free_fn && event_data) free_fn(event_data);
|
||||
return;
|
||||
}
|
||||
|
||||
EventNode *node = dwn_malloc(sizeof(EventNode));
|
||||
if (!node) {
|
||||
if (free_fn && event_data) free_fn(event_data);
|
||||
return;
|
||||
}
|
||||
|
||||
node->type = type;
|
||||
node->data = event_data;
|
||||
node->free_fn = free_fn;
|
||||
node->next = NULL;
|
||||
|
||||
pthread_mutex_lock(&bus->queue_mutex);
|
||||
|
||||
if (bus->event_tail) {
|
||||
bus->event_tail->next = node;
|
||||
} else {
|
||||
bus->event_head = node;
|
||||
}
|
||||
bus->event_tail = node;
|
||||
|
||||
atomic_fetch_add_explicit(&bus->event_count, 1, ATOMIC_RELAXED);
|
||||
|
||||
pthread_mutex_unlock(&bus->queue_mutex);
|
||||
|
||||
/* Signal via eventfd */
|
||||
uint64_t one = 1;
|
||||
ssize_t w __attribute__((unused)) = write(bus->event_fd, &one, sizeof(one));
|
||||
}
|
||||
|
||||
uint32_t event_bus_process(EventBus *bus)
|
||||
{
|
||||
if (!bus) return 0;
|
||||
|
||||
/* Acknowledge eventfd */
|
||||
uint64_t val;
|
||||
ssize_t r __attribute__((unused)) = read(bus->event_fd, &val, sizeof(val));
|
||||
|
||||
uint32_t processed = 0;
|
||||
|
||||
while (processed < bus->batch_size) {
|
||||
/* Dequeue event */
|
||||
pthread_mutex_lock(&bus->queue_mutex);
|
||||
|
||||
EventNode *node = bus->event_head;
|
||||
if (!node) {
|
||||
pthread_mutex_unlock(&bus->queue_mutex);
|
||||
break;
|
||||
}
|
||||
|
||||
bus->event_head = node->next;
|
||||
if (!bus->event_head) {
|
||||
bus->event_tail = NULL;
|
||||
}
|
||||
|
||||
atomic_fetch_sub_explicit(&bus->event_count, 1, ATOMIC_RELAXED);
|
||||
pthread_mutex_unlock(&bus->queue_mutex);
|
||||
|
||||
/* Dispatch to subscribers */
|
||||
pthread_mutex_lock(&bus->sub_mutex);
|
||||
|
||||
for (uint32_t i = 0; i < sizeof(bus->subscriptions) / sizeof(bus->subscriptions[0]); i++) {
|
||||
Subscription *sub = &bus->subscriptions[i];
|
||||
if (sub->active && sub->type == node->type) {
|
||||
/* Check filter if present */
|
||||
bool should_handle = true;
|
||||
if (sub->filter) {
|
||||
should_handle = sub->filter(node->type, node->data, sub->filter_data);
|
||||
}
|
||||
|
||||
if (should_handle) {
|
||||
sub->handler(node->type, node->data, sub->user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&bus->sub_mutex);
|
||||
|
||||
/* Free event data */
|
||||
if (node->free_fn) {
|
||||
node->free_fn(node->data);
|
||||
}
|
||||
dwn_free(node);
|
||||
|
||||
processed++;
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
void event_bus_set_batch_size(EventBus *bus, uint32_t batch_size)
|
||||
{
|
||||
if (!bus) return;
|
||||
bus->batch_size = batch_size ? batch_size : 100;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Default Event Bus
|
||||
* ============================================================================ */
|
||||
|
||||
static EventBus *g_default_bus = NULL;
|
||||
|
||||
EventBus* event_bus_default(void)
|
||||
{
|
||||
return g_default_bus;
|
||||
}
|
||||
|
||||
bool event_bus_init_default(void)
|
||||
{
|
||||
if (g_default_bus) return true;
|
||||
g_default_bus = event_bus_create();
|
||||
return g_default_bus != NULL;
|
||||
}
|
||||
|
||||
void event_bus_shutdown_default(void)
|
||||
{
|
||||
if (g_default_bus) {
|
||||
event_bus_destroy(g_default_bus);
|
||||
g_default_bus = NULL;
|
||||
}
|
||||
}
|
||||
333
src/futures.c
Normal file
333
src/futures.c
Normal 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
src/keys.c
248
src/keys.c
@ -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();
|
||||
}
|
||||
|
||||
358
src/layout.c
358
src/layout.c
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
146
src/main.c
146
src/main.c
@ -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",
|
||||
len = snprintf(buf, sizeof(buf),
|
||||
"[X_ERROR] BadWindow (request %d, resource %lu)\n",
|
||||
ev->request_code, ev->resourceid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ev->error_code == BadMatch || ev->error_code == BadValue ||
|
||||
} else 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;
|
||||
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 (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();
|
||||
@ -1112,7 +1188,8 @@ static void print_usage(const char *program)
|
||||
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(" -c, --config FILE Use specified config file\n");
|
||||
printf(" -p, --port PORT Use specified API port (overrides config)\n");
|
||||
printf("\n");
|
||||
printf("Environment variables:\n");
|
||||
printf(" OPENROUTER_API_KEY Enable AI features\n");
|
||||
@ -1125,6 +1202,9 @@ static void print_version(void)
|
||||
printf("AI-enhanced X11 window manager\n");
|
||||
}
|
||||
|
||||
int cli_port = -1;
|
||||
static char *cli_config = NULL;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
for (int i = 1; i < argc; i++) {
|
||||
@ -1136,6 +1216,16 @@ int main(int argc, char *argv[])
|
||||
print_version();
|
||||
return 0;
|
||||
}
|
||||
if ((strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) && i + 1 < argc) {
|
||||
cli_port = atoi(argv[++i]);
|
||||
if (cli_port <= 0 || cli_port > 65535) {
|
||||
fprintf(stderr, "Invalid port number: %s\n", argv[i]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
if ((strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config") == 0) && i + 1 < argc) {
|
||||
cli_config = argv[++i];
|
||||
}
|
||||
}
|
||||
|
||||
XInitThreads();
|
||||
|
||||
193
src/marks.c
Normal file
193
src/marks.c
Normal 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
Loading…
Reference in New Issue
Block a user