Compare commits

..
34 Commits
Author SHA1 Message Date
retoor 3073db38f1 fix: skip unmap notifications for clients undergoing fullscreen unmanagement
Add CLIENT_UNMANAGING flag to prevent redundant unmap processing when client_set_fullscreen transitions a client out of fullscreen mode. Set flag before clearing CLIENT_FULLSCREEN and clear it after decorations render, with early return in handle_unmap_notify when flag is active.
2026-02-19 20:48:46 +00:00
retoor a6ff84dfca feat: add expose all windows keybinding and Alt-drag window manipulation with floating state tracking
Implement key_expose_all function bound to Alt+Space that unminimizes, unmaximizes, and unfullscreens all windows on current workspace then rearranges. Add floating_before_maximize boolean to Client struct to preserve floating state when toggling maximize. Enable Alt+LeftDrag for moving and Alt+RightDrag for resizing windows from any position with directional cursor support. Increase resize edge detection from 8 to 12 pixels. Add panel.h dependency to keys build target and update shortcut help text with new bindings.
2026-02-16 01:24:05 +00:00
retoor cd9e55dc64 build: add -ldl linker flag, recursive source discovery, and API test targets to Makefile 2026-02-07 12:04:52 +00:00
retoor 8e86b2d106 revert: remove dpi detection and configuration for mouse distance feature
This reverts commit df34716ea5, undoing the addition of dpi detection logic and related configuration options for mouse distance calculations.
2026-01-29 15:39:43 +00:00
retoor a462643110 revert: remove sysmon panel toggle click handling and map event integration
This reverts commit aee7030b30, stripping all sysmon references from panel click handlers, map/unmap event logic, expose rendering, button/motion event dispatch, and the Monitor struct sysmon field, along with the sysmon.h include chain in main.c and main.d dependency file.
2026-01-29 15:38:55 +00:00
retoor 1ba5fae79b feat: add system monitor feature with panel toggle and event handling
- Introduce SystemMonitor struct and typedef in dwn.h, add sysmon pointer to Monitor struct
- Include sysmon.h in main.c, integrate sysmon_find_by_window and sysmon_set_client into map request handling
- Add sysmon_render call in expose handler and sysmon_handle_event in button press and motion notify handlers
- Implement clickable stats and top process areas in bottom panel to toggle system monitor via sysmon_toggle
- Update build/main.d dependency file to include sysmon.h
2026-01-29 13:36:28 +00:00
retoor 5404adfcd5 feat: add dpi detection and configuration for mouse distance
Implement automatic DPI detection from EDID display data and a manual `mouse_distance_dpi` config option to convert mouse movement pixels into real-world centimeters. The new `calculate_pixels_per_cm()` function in main.c falls back to manual DPI override, then EDID-based auto-detection with sanity checks, and finally a default 96 DPI (37.8 px/cm). The `Config` struct gains a `mouse_distance_dpi` field parsed from config files, and `DWNState` stores the computed `pixels_per_cm` and a `dpi_auto_detected` flag. The panel renderer now uses `dwn->pixels_per_cm` instead of the hardcoded 37.8 constant for mouse distance display.
2026-01-28 15:55:00 +00:00
retoor 2303b53c70 feat: implement fixed-width toggling top process display for CPU and memory
Add TOP_PROCESS_WIDGET_WIDTH constant and process_show_cpu flag to SystemStats struct, enabling fixed-width rendering of top process panel widget that toggles between CPU and memory usage display. Remove dynamic width calculation function panel_calculate_top_process_width and integrate top process rendering into the main panel layout with consistent spacing.
2026-01-27 11:18:45 +00:00
retoor a0d5b1b091 docs: expand README with design philosophy, features, and architecture sections while adding panel dirty tracking, Xft color caching, and XInput2 mouse distance monitoring 2026-01-27 01:50:47 +00:00
retoor 2f096a55ac perf: increase typing activity speed multiplier from 3.0 to 10.0 in dwn_run
The speed_multiplier variable in dwn_run() is raised from 3.0f to 10.0f when the time since last typing activity is under 500ms, accelerating ambient phase progression during active typing sessions.
2026-01-26 23:51:19 +00:00
retoor 915870875d build: add XInput2 library dependency for raw key event monitoring 2026-01-26 23:47:23 +00:00
retoor 725267fa00 feat: add text glow animation with sinusoidal color cycling and alt-tab bold font rendering 2026-01-26 22:36:06 +00:00
retoor ce6729c53c feat: add animated focus transitions for window titles and taskbar colors 2026-01-26 22:16:04 +00:00
retoor eaa41d70ea feat: add directional window resize constants and multi-battery monitoring with per-battery breakdown in panel 2026-01-26 19:40:08 +00:00
retoor fb28931535 build: add libxtst, libpng, tesseract, and leptonica dependencies to Makefile for screenshot and ocr support 2026-01-24 15:17:47 +00:00
retoor 51c3d4b919 build: enable address/undefined sanitizer build target and add -Wpedantic -Wshadow -fstack-protector-strong flags 2026-01-24 12:26:45 +00:00
retoor c32bda853c chore: add alt-tab navigation with update_mru flag and MRU-based taskbar rendering 2026-01-16 17:24:59 +00:00
retoor a6eeeb978e feat: add 13 color configuration fields for panel, workspace, title, border, and notification themes
Add color customization support to the Config struct with 13 new char[16] fields covering panel background/foreground, workspace active/inactive/urgent, title focused/unfocused background/foreground, border focused/unfocused, and notification background/foreground. Implement default value initialization in config_set_defaults using strncpy with DEFAULT_* constants, and add parsing logic in handle_config_entry for the new "colors" section to populate these fields from configuration files.
2026-01-16 05:10:17 +00:00
retoor 6cd3a50f37 chore: remove unused c and o source files from repository root
Remove the stale binary and object files from the repository root directory
that are no longer referenced by the build system. Additionally, update the
atoms_get_window_name function to use AnyPropertyType instead of specific
atom types for XGetWindowProperty calls, and add a nil check on nitems to
prevent potential null pointer dereferences. Modify panel_handle_click to
minimize the currently focused client when clicking on it, rather than
keeping it focused, improving window management behavior.
2026-01-16 04:41:39 +00:00
retoor 1724f17e92 chore: remove outdated example configs, html docs, and binary artifacts from root and site directories 2026-01-09 13:47:47 +00:00
retoor 115c69f1bc chore: update default color scheme to neon theme and add window constraint logic 2026-01-08 23:41:45 +00:00
retoor daaef6fa17 chore: add web_remote.html with workspace grid and quick launch UI for DWN control 2026-01-08 23:19:20 +00:00
retoor eb492cd5ff chore: add WebSocket API documentation and auto-organization feature cards to site pages 2026-01-08 23:18:43 +00:00
retoor 4793d372ac chore: add WebSocket API with Mongoose, service management, and AI command execution
Add new WebSocket API module (api.c/api.h) using Mongoose library for real-time
window manager control, including workspace and client status endpoints.
Introduce service management module (services.h) with init/run/cleanup lifecycle.
Extend AI module to parse [WM_CMD:] directives from responses and forward JSON
commands to the API handler. Update config with api.enabled, api.port, and
services.path settings. Integrate new modules into main init/run/cleanup flow.
2026-01-08 22:58:19 +00:00
retoor 54565b21bd chore: swap super+a and super+shift+a bindings for ai command and context in keys.c and docs 2026-01-08 22:37:00 +00:00
retoor 8a5454fda0 fix: skip unmanage for maximized or fullscreen windows on UnmapNotify in handle_unmap_notify
Add early return guard in handle_unmap_notify to ignore UnmapNotify events
when the client has CLIENT_MAXIMIZED or CLIENT_FULLSCREEN flags set,
preventing browsers and other apps from becoming orphaned during temporary
unmap transitions like resizing or state changes.
2026-01-08 22:34:10 +00:00
retoor 91cfecb6e5 fix: grab button events on unfocus and ungrab on focus for proper click handling 2026-01-08 22:24:45 +00:00
retoor ab36b7dd2c feat: implement composable window snapping with dual-axis constraints and toggle-to-full behavior
Add SnapHorizontal/SnapVertical enums and SnapConstraint struct to Client for tracking snap state. Introduce layout_apply_snap_constraint() to position windows into quadrants or full axes based on stored snap flags. Extend key_snap_left/right with toggle logic that expands to full width on second press, and add new key_snap_up/down handlers for vertical snapping. Update shortcut documentation and help overlay to describe composable two-key sequences (e.g. Super+Left then Super+Up for top-left quarter).
2025-12-28 10:21:03 +00:00
retoor 64f9761116 feat: add maximize window support with MRU focus tracking and demo timing configs
Implement client maximize/unmaximize toggle and state tracking via CLIENT_MAXIMIZED flag, add MRU (most-recently-used) doubly-linked list per workspace for intelligent focus restoration on client unmanage, introduce configurable focus-follow delay (0-1000ms) for sloppy focus mode, and add demo mode timing parameters (step_delay, ai_timeout, window_timeout) with corresponding wait condition enum and configuration parsing in the [demo] section.
2025-12-28 09:23:03 +00:00
retoor 3364464b43 docs: update README with autostart, demo mode, XEmbed tray, window snapping, and Alt+F9 minimize keybinding 2025-12-28 08:26:53 +00:00
retoor c927e23f23 style: remove all block comments from header files 2025-12-28 04:01:46 +00:00
retoor dc522d5912 docs: add comprehensive readme with installation, features, and usage guide for DWN window manager 2025-12-28 03:34:24 +00:00
retoor 41fc47abac feat: add news ticker with sentiment analysis and error codes to dwn.h and news.h headers
- Add DwnStatus enum with six error codes (OK, ERROR, INVALID_ARG, NO_MEMORY, NOT_FOUND, DISPLAY, IO) to include/dwn.h
- Extend NewsArticle struct with NewsSentiment enum (NEUTRAL, POSITIVE, NEGATIVE) and sentiment_score float field in include/news.h
- Insert DWN_ASSERT and DWN_ASSERT_MSG contract assertion macros plus assert.h include into include/util.h
2025-12-28 03:30:10 +00:00
retoor 21e8bcc88a chore: add MIT license, Makefile, and initial build artifacts for DWN project 2025-12-28 02:14:31 +00:00
185 changed files with 22974 additions and 7551 deletions
+66 -6
View File
@@ -5,7 +5,7 @@
CC = gcc
CFLAGS = -Wall -Wextra -Wpedantic -Wshadow -O2 -I./include
CFLAGS += -fstack-protector-strong -D_FORTIFY_SOURCE=2
LDFLAGS = -lX11 -lXext -lXinerama -lXrandr -lXft -lfontconfig -ldbus-1 -lcurl -lm -lpthread -lXtst
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
@@ -28,8 +28,8 @@ DATADIR = $(PREFIX)/share
SYSCONFDIR = /etc
# Get pkg-config flags (with error handling)
PKG_CFLAGS := $(shell pkg-config --cflags x11 xext xinerama xrandr xft fontconfig dbus-1 xtst libpng tesseract lept 2>/dev/null)
PKG_LIBS := $(shell pkg-config --libs x11 xext xinerama xrandr xft fontconfig dbus-1 xtst libpng tesseract lept 2>/dev/null)
PKG_CFLAGS := $(shell pkg-config --cflags x11 xext xinerama xrandr xft fontconfig dbus-1 xtst xi libpng tesseract lept 2>/dev/null)
PKG_LIBS := $(shell pkg-config --libs x11 xext xinerama xrandr xft fontconfig dbus-1 xtst xi libpng tesseract lept 2>/dev/null)
# Use pkg-config if available
ifneq ($(PKG_LIBS),)
@@ -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)
# =============================================================================
+403 -168
View File
@@ -2,100 +2,272 @@
retoor <retoor@molodetz.nl>
A production-ready X11 window manager written in ANSI C. DWN implements EWMH and ICCCM protocols for cross-compatibility while providing tiling-first workflow management with integrated desktop components.
A production-ready X11 window manager written in ANSI C with EWMH/ICCCM protocol compliance, tiling-first workflow, integrated desktop components, and optional AI integration via OpenRouter API.
## Architecture
## Design Philosophy
DWN uses a modular architecture with specialized subsystems for window management, layout algorithms, panel rendering, system tray, notifications, and optional AI integration. All state is managed through a global singleton with strict encapsulation patterns.
DWN prioritizes a seamless, distraction-free desktop experience:
The window manager operates with zero window borders and zero gaps between tiles, providing a seamless, professional appearance. Title bars remain at 28px height for window controls.
- **Zero borders and gaps**: Windows tile edge-to-edge without visual separation
- **Minimal chrome**: 28px title bars provide window controls without excess decoration
- **Tiling-first**: Master-stack layout as the default, with floating and monocle modes available
- **Keyboard-driven**: Comprehensive shortcuts for all operations
- **Modular architecture**: Single-responsibility modules with strict encapsulation
- **ANSI C**: Maximum portability and minimal dependencies
## Core Features
## Features
### Layout Management
- **Tiling Mode**: Master-stack layout with configurable ratios
- **Floating Mode**: Traditional overlapping windows
- **Monocle Mode**: Single fullscreen window
- **Composable Snapping**: Quarter-screen, half-screen, and full-screen snapping via Super+Arrow keys
### Window Management
**Layout Modes**
- **Tiling**: Master-stack layout with configurable master ratio (0.1-0.9) and master window count
- **Floating**: Traditional overlapping windows with free positioning
- **Monocle**: Single maximized window per workspace
**Composable Window Snapping**
- Super+Arrow keys snap windows to half-screen positions
- Double-press extends to full width/height
- Combinations create quarter-screen positions (e.g., Super+Left then Super+Up = top-left quarter)
**Directional Resizing**
- Resize windows from any edge or corner
- Respects layout bounds and snap constraints
**Window States**
- Fullscreen (Alt+F11): Covers entire screen including panels
- Maximized (Alt+F10): Fills usable area with title bar
- Minimized (Alt+F9): Hidden from view
- Floating (Super+F9): Exempt from tiling layout
- Sticky: Visible on all workspaces
- Urgent: Demands attention with taskbar highlight
### Workspace System
- 9 independent virtual workspaces (F1-F9)
- Per-workspace layout persistence
- Workspace indicators in panel
- Fast workspace switching with Shift+F1-F9 to move windows
- MRU (Most Recently Used) focus stack per workspace
- Alt-Tab cycles through MRU order
- Shift+F1-F9 moves focused window to target workspace
- Ctrl+Alt+Left/Right navigates adjacent workspaces
### Integrated Components
- **Top Panel**: Workspace indicators, taskbar, system tray, clock
- **Bottom Panel**: Additional status information
- **System Tray**: XEmbed protocol implementation supporting external applications
- **System Widgets**: Battery indicator, volume control, WiFi manager
- **Notification Daemon**: D-Bus org.freedesktop.Notifications implementation
### Panels
**Top Panel**
- Workspace indicators with active/inactive/urgent states
- Taskbar with window buttons
- System tray with XEmbed protocol support
- Battery indicator (multi-battery support)
- Volume control with slider
- Top memory process display
- Top CPU process display
- Key press counter
- Mouse distance traveled
- Clock
**Bottom Panel**
- News ticker with scrolling headlines
- Configurable visibility
### System Tray
**XEmbed Protocol**
- Acquires `_NET_SYSTEM_TRAY_S0` selection
- Docks external application icons (nm-applet, blueman, Telegram, etc.)
- Forwards click events to icon windows
- Automatic cleanup on application close
**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
Visual feedback system with phase-offset animations:
- Panel background subtle color cycling
- Workspace indicator glow
- Taskbar button highlighting
- Clock and statistics display
- Window title text glow on focus
- Configurable animation speed
### Activity Tracking
- **Key press counter**: Total keypresses displayed in panel
- **Mouse distance**: Cumulative mouse movement in pixels
- **XInput2 integration**: Raw event monitoring for accurate tracking
### Focus Transitions
- Animated color transitions on window focus changes
- Taskbar button color interpolation
- Title bar glow animations
- Bold font rendering for Alt-Tab selection
### Notification System
D-Bus implementation of `org.freedesktop.Notifications`:
- Receives notifications from all applications
- Stacked display with timeout management
- Urgency levels: low, normal, critical
- Click to dismiss
### News Ticker
- Real-time news feed in panel
- Scrolling article titles
- Super+Return to open current article in browser
- Configurable news sources
- Fetches headlines from configured sources
- Smooth pixel-based scrolling animation
- Super+Return opens current article in browser
- Automatic refresh interval
- Sentiment indicators (positive/negative/neutral)
### AI Integration
- **Command Palette** (Super+Shift+A): Natural language window management
- **Context Analysis** (Super+A): Workspace and task detection
- **Exa Semantic Search** (Super+Shift+E): Intelligent web search
- Powered by OpenRouter API (supports multiple LLM providers)
### Interactive Features
- **Tutorial System** (Super+T): Step-by-step keyboard shortcut training
- **Demo Mode** (Super+Shift+D): Automated feature showcase
- **Keyboard Shortcuts Help** (Super+S): Quick reference overlay
**Command Palette (Super+Shift+A)**
- Natural language command execution
- OpenRouter API with configurable model selection
- Context-aware responses based on current workspace
**Context Analysis (Super+A)**
- Task type detection (coding, browsing, communication)
- Window and application analysis
- Intelligent suggestions
**Exa Semantic Search (Super+Shift+E)**
- Meaning-based web search
- Results displayed in dmenu/rofi
- Select to open in browser
### Screenshot and OCR
**Screenshot API**
- Fullscreen capture
- Active window capture
- Area selection capture
- Async capture with callbacks
- Base64 encoding for API transmission
- PNG output
**OCR API**
- Tesseract-based text extraction
- Multi-language support
- Confidence scoring
- Async processing
### WebSocket API
Programmatic control on port 8777:
| Command | Description |
|---------|-------------|
| `get_status` | Window manager state |
| `get_workspaces` | Workspace information |
| `get_clients` | All managed windows |
| `switch_workspace` | Change active workspace |
| `focus_client` | Focus specific window |
| `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
- **XDG Autostart**: Automatic application launching from .desktop files
- **Service Manager**: Background process management
- **WebSocket API**: Remote control and state monitoring on port 8777
**XDG Autostart**
- Scans `/etc/xdg/autostart/` and `~/.config/autostart/`
- Parses `.desktop` files
- Custom autostart directory support
**Service Manager**
- Background process management
- Automatic restart on failure
### Interactive Features
**Tutorial (Super+T)**
- Step-by-step keyboard shortcut training
- Waits for correct input
- Progressive difficulty
**Demo Mode (Super+Shift+D)**
- Automated feature showcase
- Configurable timing
- Covers all major features
**Shortcuts Help (Super+S)**
- Quick reference overlay
- All keyboard bindings displayed
## Installation
### Dependencies
Required packages (via pkg-config):
- X11, Xext, Xinerama, Xrandr, Xft
Required libraries (via pkg-config):
- X11, Xext, Xinerama, Xrandr, Xft, Xi
- fontconfig
- libdbus-1
- libcurl
- libm
- libpng
- tesseract (optional, for OCR)
### Build Process
### Build
```bash
git clone https://github.com/your-repo/dwn.git
cd dwn
make deps # Auto-install dependencies (apt/dnf/pacman)
make # Build release version with -O2 optimization
make install # Install to /usr/local/bin
make # Build with -O2 optimization
make install # Install to /usr/local/bin (PREFIX configurable)
```
### Testing
```bash
make run # Launch in nested Xephyr window (safe testing)
make run # Launch in nested Xephyr window
```
### Build Targets
```bash
make # Release build with optimization
make debug # Debug build with -g -DDEBUG
make clean # Remove build artifacts
make format # Run clang-format
make check # Run cppcheck static analysis
```
| Target | Description |
|--------|-------------|
| `make` | Release build with optimization |
| `make debug` | Debug build with -g -DDEBUG |
| `make clean` | Remove build artifacts |
| `make format` | Run clang-format |
| `make check` | Run cppcheck static analysis |
## Configuration
Configuration file: `~/.config/dwn/config`
Configuration file: `~/.config/dwn/config` (INI format)
### General Settings
### General
```ini
[general]
@@ -111,10 +283,10 @@ decorations = true
```ini
[appearance]
border_width = 0 # 0-50px (0 for seamless)
border_width = 0 # 0-50px
title_height = 28 # 0-100px
panel_height = 32 # 0-100px
gap = 0 # 0-100px (0 for seamless)
gap = 0 # 0-100px
font = fixed
```
@@ -154,7 +326,7 @@ notification_bg = #111111
notification_fg = #00ffff
```
### AI Integration
### AI
```ini
[ai]
@@ -169,7 +341,7 @@ export OPENROUTER_API_KEY=sk-or-v1-your-key
export EXA_API_KEY=your-exa-key
```
Get API keys:
API keys:
- OpenRouter: https://openrouter.ai/keys
- Exa: https://dashboard.exa.ai/api-keys
@@ -178,11 +350,11 @@ Get API keys:
```ini
[autostart]
enabled = true
xdg_autostart = true # Scan /etc/xdg/autostart and ~/.config/autostart
xdg_autostart = true
path = ~/.config/dwn/autostart.d
```
### WebSocket API
### API
```ini
[api]
@@ -190,18 +362,19 @@ enabled = true
port = 8777
```
### Demo Mode
### Demo
```ini
[demo]
step_delay = 4000 # 1000-30000ms between steps
ai_timeout = 15000 # 5000-60000ms for AI responses
window_timeout = 5000 # 1000-30000ms for window spawns
step_delay = 4000 # 1000-30000ms
ai_timeout = 15000 # 5000-60000ms
window_timeout = 5000 # 1000-30000ms
```
## Keyboard Shortcuts
### Application Launchers
| Shortcut | Action |
|----------|--------|
| `Ctrl+Alt+T` | Terminal |
@@ -211,10 +384,11 @@ window_timeout = 5000 # 1000-30000ms for window spawns
| `Print` | Screenshot |
### Window Management
| Shortcut | Action |
|----------|--------|
| `Alt+F4` | Close window |
| `Alt+Tab` | Next window |
| `Alt+Tab` | Next window (MRU order) |
| `Alt+Shift+Tab` | Previous window |
| `Alt+F9` | Toggle minimize |
| `Alt+F10` | Toggle maximize |
@@ -222,6 +396,7 @@ window_timeout = 5000 # 1000-30000ms for window spawns
| `Super+F9` | Toggle floating |
### Workspace Navigation
| Shortcut | Action |
|----------|--------|
| `F1` - `F9` | Switch to workspace 1-9 |
@@ -230,85 +405,164 @@ window_timeout = 5000 # 1000-30000ms for window spawns
| `Ctrl+Alt+Left` | Previous workspace |
### Layout Control
| Shortcut | Action |
|----------|--------|
| `Super+Space` | Cycle layout (tiling/floating/monocle) |
| `Super+Space` | Cycle layout mode |
| `Super+H` | Shrink master area |
| `Super+L` | Expand master area |
| `Super+I` | Increase master count |
| `Super+D` | Decrease master count |
| `Super+D` | Decrease master count / Show desktop |
### Window Snapping
| Shortcut | Action |
|----------|--------|
| `Super+Left` | Snap left (50% or 100% width) |
| `Super+Right` | Snap right (50% or 100% width) |
| `Super+Up` | Snap top (50% or 100% height) |
| `Super+Down` | Snap bottom (50% or 100% height) |
Snapping is composable: Super+Left then Super+Up snaps to top-left quarter.
| `Super+Left` | Snap left (50% or 100%) |
| `Super+Right` | Snap right (50% or 100%) |
| `Super+Up` | Snap top (50% or 100%) |
| `Super+Down` | Snap bottom (50% or 100%) |
### AI Features
| Shortcut | Action |
|----------|--------|
| `Super+A` | Show AI context analysis |
| `Super+Shift+A` | AI command palette |
| `Super+A` | Context analysis |
| `Super+Shift+A` | Command palette |
| `Super+Shift+E` | Exa semantic search |
### News & Help
### News and Help
| Shortcut | Action |
|----------|--------|
| `Super+Return` | Open current news article |
| `Super+S` | Show shortcuts help |
| `Super+Return` | Open current article |
| `Super+S` | Shortcuts help |
| `Super+T` | Interactive tutorial |
| `Super+Shift+D` | Toggle demo mode |
| `Super+Backspace` | Quit DWN |
## WebSocket API
## Architecture
Connect to `ws://localhost:8777/ws` for programmatic control.
### Module Structure
### Commands
| Module | Responsibility |
|--------|----------------|
| main.c | X11 initialization, event loop, signal handling |
| client.c | Window management, focus, frame creation |
| workspace.c | Virtual desktop management, MRU stacks |
| layout.c | Tiling, floating, monocle algorithms |
| decorations.c | Title bars, borders, glow animations |
| panel.c | Top/bottom panel rendering, widgets |
| systray.c | XEmbed system tray, battery/volume widgets |
| notifications.c | D-Bus notification daemon |
| atoms.c | EWMH/ICCCM atom management |
| keys.c | Keyboard shortcut handling |
| config.c | INI configuration parser |
| ai.c | OpenRouter API, Exa search integration |
| news.c | News ticker with scrolling animation |
| screenshot.c | Screen capture API |
| ocr.c | Tesseract text extraction |
| autostart.c | XDG autostart implementation |
| services.c | Background service management |
| demo.c | Automated feature demonstration |
| api.c | WebSocket server |
| util.c | Logging, memory, string utilities, glow effects |
#### get_status
Returns window manager state.
```json
{"command": "get_status"}
```
### New Abstraction Layer (v2.0)
#### get_workspaces
Returns array of workspace objects.
```json
{"command": "get_workspaces"}
```
DWN now includes a modern abstraction layer for future extensibility:
#### get_clients
Returns array of all managed windows.
```json
{"command": "get_clients"}
```
**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
Response includes window ID, title, class, workspace, geometry, focus state.
**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
#### switch_workspace
Change active workspace.
```json
{"command": "switch_workspace", "workspace": 2}
```
**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
#### run_command
Execute shell command.
```json
{"command": "run_command", "exec": "firefox"}
```
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
#### focus_client
Focus specific window by ID.
```json
{"command": "focus_client", "window": 12345678}
```
### Design Patterns
### Python Example
**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**
- 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
### Key Constants
| Constant | Value |
|----------|-------|
| MAX_CLIENTS | 256 |
| MAX_WORKSPACES | 9 |
| MAX_MONITORS | 8 |
| MAX_NOTIFICATIONS | 32 |
| MAX_KEYBINDINGS | 64 |
## WebSocket API Examples
### Python
```python
import websocket
@@ -316,76 +570,57 @@ import json
ws = websocket.create_connection("ws://localhost:8777/ws")
ws.send(json.dumps({"command": "get_clients"}))
clients = json.loads(ws.recv())
ws.send(json.dumps({"command": "switch_workspace", "workspace": 3}))
ws.send(json.dumps({"command": "get_clients"}))
print(json.loads(ws.recv()))
ws.send(json.dumps({"command": "focus_client", "window": 12345678}))
ws.close()
```
## Module Architecture
### Response Format
### Core Modules (src/)
| Module | Responsibility |
|--------|----------------|
| main.c | X11 initialization, event loop, signal handling |
| client.c | Window management, focus, frame creation |
| workspace.c | Virtual desktop management |
| layout.c | Tiling, floating, monocle algorithms |
| decorations.c | Title bars and borders |
| panel.c | Top and bottom panel rendering |
| systray.c | XEmbed system tray, battery/volume/WiFi widgets |
| notifications.c | D-Bus notification daemon |
| atoms.c | EWMH/ICCCM atom management |
| keys.c | Keyboard shortcut handling |
| config.c | INI configuration parser |
| ai.c | OpenRouter API, Exa search integration |
| news.c | News ticker with scrolling animation |
| autostart.c | XDG autostart implementation |
| services.c | Background service management |
| demo.c | Automated feature demonstration |
| api.c | WebSocket server and JSON handler |
| util.c | Logging, memory, string utilities |
### Design Patterns
- **Encapsulation**: Opaque pointer types hide internal structures
- **Error Handling**: Status codes with output parameters
- **Resource Management**: Goto cleanup pattern for multi-resource functions
- **Modularity**: Single responsibility per source file
- **Naming**: Module prefix convention (e.g., `client_focus()`, `workspace_switch()`)
## Development
### Standards
- ANSI C (C89/C90) for maximum compatibility
- Snake_case for functions and variables
- CamelCase for types and structs
- No external dependencies beyond X11 and standard libraries
- Defensive programming: validate inputs, check malloc, null-terminate strings
### Code Style
```bash
make format # Apply clang-format
make check # Run cppcheck
```json
{
"status": "ok",
"data": {
"clients": [
{
"window": 12345678,
"title": "Firefox",
"class": "firefox",
"workspace": 1,
"x": 0, "y": 32,
"width": 960, "height": 540,
"focused": true
}
]
}
}
```
### Project Structure
## Project Structure
```
dwn/
├── src/ # Implementation
├── include/ # Headers
├── config/ # Configuration templates
├── scripts/ # Utilities and API examples
├── site/ # Documentation website
── build/ # Build artifacts
├── src/ # Implementation files
│ ├── core/ # Abstract core types (string, list, hashmap, client)
│ ├── backends/x11/ # X11 backend implementation
│ └── plugins/ # Plugin system (layout, widget managers)
│ └── layouts/ # Built-in layout plugins
── include/ # Header files
│ ├── core/ # Core abstraction headers
│ ├── backends/ # Backend interface headers
│ └── plugins/ # Plugin API headers
├── manual/ # Documentation website (HTML)
├── config/ # Configuration templates
├── scripts/ # Utility scripts
├── examples/ # API usage examples
└── build/ # Build artifacts
```
## License
MIT License - see LICENSE file for details.
MIT License - see LICENSE file.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -19,7 +19,7 @@ build/client.o: src/client.c include/client.h include/dwn.h \
/usr/include/dbus-1.0/dbus/dbus-signature.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
/usr/include/dbus-1.0/dbus/dbus-threads.h include/layout.h \
include/panel.h include/api.h
include/panel.h include/api.h include/rules.h include/marks.h
include/client.h:
include/dwn.h:
include/atoms.h:
@@ -49,3 +49,5 @@ include/notifications.h:
include/layout.h:
include/panel.h:
include/api.h:
include/rules.h:
include/marks.h:
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -1,7 +1,9 @@
build/decorations.o: src/decorations.c include/decorations.h \
include/dwn.h include/client.h include/config.h include/util.h
include/dwn.h include/client.h include/config.h include/util.h \
include/workspace.h
include/decorations.h:
include/dwn.h:
include/client.h:
include/config.h:
include/util.h:
include/workspace.h:
Binary file not shown.
+3 -1
View File
@@ -19,7 +19,7 @@ build/keys.o: src/keys.c include/keys.h include/dwn.h include/client.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
/usr/include/dbus-1.0/dbus/dbus-threads.h include/news.h \
include/applauncher.h include/decorations.h include/demo.h \
include/layout.h include/api.h
include/layout.h include/api.h include/marks.h include/panel.h
include/keys.h:
include/dwn.h:
include/client.h:
@@ -52,3 +52,5 @@ include/decorations.h:
include/demo.h:
include/layout.h:
include/api.h:
include/marks.h:
include/panel.h:
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+16 -3
View File
@@ -19,9 +19,12 @@ build/main.o: src/main.c include/dwn.h include/config.h include/dwn.h \
/usr/include/dbus-1.0/dbus/dbus-signature.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
/usr/include/dbus-1.0/dbus/dbus-threads.h include/systray.h \
include/news.h include/applauncher.h include/ai.h include/autostart.h \
include/services.h include/api.h include/demo.h include/screenshot.h \
include/ocr.h include/util.h
include/slider.h include/news.h include/applauncher.h include/ai.h \
include/autostart.h include/services.h include/api.h include/demo.h \
include/screenshot.h include/ocr.h include/util.h include/slider.h \
include/rules.h include/marks.h include/core/wm_client.h \
include/core/wm_types.h include/core/wm_string.h include/core/wm_types.h \
include/plugins/layout_plugin.h include/plugins/builtin_layouts.h
include/dwn.h:
include/config.h:
include/dwn.h:
@@ -52,6 +55,7 @@ include/notifications.h:
/usr/include/dbus-1.0/dbus/dbus-syntax.h:
/usr/include/dbus-1.0/dbus/dbus-threads.h:
include/systray.h:
include/slider.h:
include/news.h:
include/applauncher.h:
include/ai.h:
@@ -62,3 +66,12 @@ include/demo.h:
include/screenshot.h:
include/ocr.h:
include/util.h:
include/slider.h:
include/rules.h:
include/marks.h:
include/core/wm_client.h:
include/core/wm_types.h:
include/core/wm_string.h:
include/core/wm_types.h:
include/plugins/layout_plugin.h:
include/plugins/builtin_layouts.h:
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
+3 -1
View File
@@ -1,6 +1,7 @@
build/panel.o: src/panel.c include/panel.h include/dwn.h \
include/workspace.h include/layout.h include/client.h include/config.h \
include/util.h include/atoms.h include/systray.h include/news.h
include/util.h include/atoms.h include/systray.h include/slider.h \
include/news.h
include/panel.h:
include/dwn.h:
include/workspace.h:
@@ -10,4 +11,5 @@ include/config.h:
include/util.h:
include/atoms.h:
include/systray.h:
include/slider.h:
include/news.h:
BIN
View File
Binary file not shown.
+5 -3
View File
@@ -1,6 +1,6 @@
build/systray.o: src/systray.c include/systray.h include/dwn.h \
include/panel.h include/config.h include/util.h include/notifications.h \
/usr/include/dbus-1.0/dbus/dbus.h \
include/slider.h include/panel.h include/config.h include/util.h \
include/notifications.h /usr/include/dbus-1.0/dbus/dbus.h \
/usr/lib/x86_64-linux-gnu/dbus-1.0/include/dbus/dbus-arch-deps.h \
/usr/include/dbus-1.0/dbus/dbus-macros.h \
/usr/include/dbus-1.0/dbus/dbus-address.h \
@@ -17,9 +17,10 @@ build/systray.o: src/systray.c include/systray.h include/dwn.h \
/usr/include/dbus-1.0/dbus/dbus-server.h \
/usr/include/dbus-1.0/dbus/dbus-signature.h \
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
/usr/include/dbus-1.0/dbus/dbus-threads.h include/atoms.h
/usr/include/dbus-1.0/dbus/dbus-threads.h include/atoms.h include/api.h
include/systray.h:
include/dwn.h:
include/slider.h:
include/panel.h:
include/config.h:
include/util.h:
@@ -43,3 +44,4 @@ include/notifications.h:
/usr/include/dbus-1.0/dbus/dbus-syntax.h:
/usr/include/dbus-1.0/dbus/dbus-threads.h:
include/atoms.h:
include/api.h:
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+2 -1
View File
@@ -1,6 +1,6 @@
build/workspace.o: src/workspace.c include/workspace.h include/dwn.h \
include/client.h include/layout.h include/atoms.h include/util.h \
include/config.h include/panel.h include/api.h
include/config.h include/panel.h include/api.h include/decorations.h
include/workspace.h:
include/dwn.h:
include/client.h:
@@ -10,3 +10,4 @@ include/util.h:
include/config.h:
include/panel.h:
include/api.h:
include/decorations.h:
BIN
View File
Binary file not shown.
+9
View File
@@ -10,6 +10,11 @@
#include "dwn.h"
#include <stdbool.h>
/* Forward declarations for libcurl types */
struct curl_slist;
struct Curl_easy;
typedef struct Curl_easy DWN_CURL;
typedef enum {
AI_STATE_IDLE,
AI_STATE_PENDING,
@@ -24,6 +29,8 @@ typedef struct AIRequest {
void (*callback)(struct AIRequest *req);
void *user_data;
struct AIRequest *next;
struct curl_slist *headers; /* Stored for proper cleanup */
struct Curl_easy *curl_handle; /* Stored for cancellation */
} AIRequest;
typedef struct {
@@ -74,6 +81,8 @@ typedef struct ExaRequest {
void (*callback)(struct ExaRequest *req);
void *user_data;
struct ExaRequest *next;
struct curl_slist *headers; /* Stored for proper cleanup */
struct Curl_easy *curl_handle; /* Stored for cancellation */
} ExaRequest;
bool exa_is_available(void);
+34
View File
@@ -55,6 +55,23 @@ typedef enum {
EVENT_NOTIFICATION_SHOWN,
EVENT_NOTIFICATION_CLOSED,
EVENT_FADE_SPEED_CHANGED,
EVENT_FADE_INTENSITY_CHANGED,
EVENT_WINDOW_SNAPPED,
EVENT_AUDIO_VOLUME_CHANGED,
EVENT_AUDIO_MUTE_TOGGLED,
EVENT_PANEL_VISIBILITY_CHANGED,
EVENT_CONFIG_RELOADED,
EVENT_AI_RESPONSE_RECEIVED,
EVENT_EXA_SEARCH_COMPLETED,
EVENT_NEWS_ARTICLE_CHANGED,
EVENT_DEMO_STARTED,
EVENT_DEMO_STOPPED,
EVENT_DEMO_PHASE_CHANGED,
EVENT_TUTORIAL_STARTED,
EVENT_TUTORIAL_STOPPED,
EVENT_COUNT
} ApiEventType;
@@ -104,4 +121,21 @@ void api_emit_show_desktop_toggled(bool shown);
void api_emit_notification_shown(unsigned int id, const char *summary, const char *body);
void api_emit_notification_closed(unsigned int id);
void api_emit_fade_speed_changed(float old_speed, float new_speed);
void api_emit_fade_intensity_changed(float old_intensity, float new_intensity);
void api_emit_window_snapped(Window window, int horizontal, int vertical);
void api_emit_audio_volume_changed(int old_volume, int new_volume);
void api_emit_audio_mute_toggled(bool muted);
void api_emit_panel_visibility_changed(const char *panel, bool visible);
void api_emit_config_reloaded(void);
void api_emit_ai_response_received(const char *prompt, const char *response, bool success);
void api_emit_exa_search_completed(const char *query, int result_count, bool success);
void api_emit_news_article_changed(int old_index, int new_index, const char *title);
void api_emit_demo_started(void);
void api_emit_demo_stopped(void);
void api_emit_demo_phase_changed(int phase, const char *phase_name);
void api_emit_tutorial_started(void);
void api_emit_tutorial_stopped(void);
#endif
+311
View File
@@ -0,0 +1,311 @@
/*
* DWN - Desktop Window Manager
* Backend Abstraction Interface
*/
#ifndef BACKEND_INTERFACE_H
#define BACKEND_INTERFACE_H
#include "core/wm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Backend Capabilities
*============================================================================*/
typedef enum {
WM_BACKEND_CAP_WINDOWS = (1 << 0),
WM_BACKEND_CAP_COMPOSITING = (1 << 1),
WM_BACKEND_CAP_TRANSPARENCY = (1 << 2),
WM_BACKEND_CAP_ANIMATIONS = (1 << 3),
WM_BACKEND_CAP_MULTI_MONITOR = (1 << 4),
WM_BACKEND_CAP_INPUT_EVENTS = (1 << 5),
WM_BACKEND_CAP_CLIPBOARD = (1 << 6),
WM_BACKEND_CAP_DRAG_DROP = (1 << 7),
WM_BACKEND_CAP_TOUCH = (1 << 8),
WM_BACKEND_CAP_GESTURES = (1 << 9)
} WmBackendCapabilities;
/*==============================================================================
* Backend Information
*============================================================================*/
typedef struct {
const char *name;
const char *version;
const char *description;
uint32_t capabilities;
bool supports_multiple_instances;
bool requires_compositor;
} WmBackendInfo;
/*==============================================================================
* Backend Event Types
*============================================================================*/
typedef enum {
WM_BACKEND_EVENT_NONE,
WM_BACKEND_EVENT_EXPOSE,
WM_BACKEND_EVENT_CONFIGURE,
WM_BACKEND_EVENT_MAP,
WM_BACKEND_EVENT_UNMAP,
WM_BACKEND_EVENT_DESTROY,
WM_BACKEND_EVENT_FOCUS_IN,
WM_BACKEND_EVENT_FOCUS_OUT,
WM_BACKEND_EVENT_KEY_PRESS,
WM_BACKEND_EVENT_KEY_RELEASE,
WM_BACKEND_EVENT_BUTTON_PRESS,
WM_BACKEND_EVENT_BUTTON_RELEASE,
WM_BACKEND_EVENT_MOTION,
WM_BACKEND_EVENT_ENTER,
WM_BACKEND_EVENT_LEAVE,
WM_BACKEND_EVENT_PROPERTY,
WM_BACKEND_EVENT_CLIENT_MESSAGE,
WM_BACKEND_EVENT_SELECTION,
} WmBackendEventType;
typedef struct {
WmBackendEventType type;
WmWindowHandle window;
WmTime timestamp;
union {
struct { int x, y, width, height; } configure;
struct { int x, y; } point;
struct { unsigned int keycode; unsigned int state; } key;
struct { unsigned int button; int x, y; unsigned int state; } button;
struct { const char *name; const void *data; size_t size; } property;
} data;
} WmBackendEvent;
/*==============================================================================
* Backend Interface Definition
*============================================================================*/
typedef struct BackendInterface {
/* Identification */
const char *name;
WmBackendInfo (*get_info)(void);
/* Lifecycle */
bool (*init)(void *config);
void (*shutdown)(void);
bool (*is_initialized)(void);
/* Connection */
bool (*connect)(void);
void (*disconnect)(void);
bool (*is_connected)(void);
int (*get_file_descriptor)(void);
void (*flush)(void);
void (*sync)(void);
/* Screen/Display */
void (*get_screen_dimensions)(int *width, int *height);
int (*get_screen_count)(void);
void (*get_screen_geometry)(int screen, WmRect *geometry);
/* Monitor Management */
int (*get_monitor_count)(void);
void (*get_monitor_geometry)(int monitor, WmRect *geometry);
bool (*get_monitor_primary)(int monitor);
const char* (*get_monitor_name)(int monitor);
/* Work Area */
void (*get_work_area)(int monitor, WmRect *area);
void (*set_work_area)(int monitor, const WmRect *area);
/* Window Management - Lifecycle */
WmWindowHandle (*window_create)(const WmRect *geometry, uint32_t flags);
void (*window_destroy)(WmWindowHandle window);
WmWindowHandle (*window_create_frame)(WmWindowHandle parent, const WmRect *geometry);
/* Window Management - Geometry */
void (*window_get_geometry)(WmWindowHandle window, WmRect *geometry);
void (*window_set_geometry)(WmWindowHandle window, const WmRect *geometry);
void (*window_move)(WmWindowHandle window, int x, int y);
void (*window_resize)(WmWindowHandle window, int width, int height);
void (*window_move_resize)(WmWindowHandle window, const WmRect *geometry);
/* Window Management - Visibility */
void (*window_show)(WmWindowHandle window);
void (*window_hide)(WmWindowHandle window);
bool (*window_is_visible)(WmWindowHandle window);
void (*window_map)(WmWindowHandle window);
void (*window_unmap)(WmWindowHandle window);
bool (*window_is_mapped)(WmWindowHandle window);
/* Window Management - Stacking */
void (*window_raise)(WmWindowHandle window);
void (*window_lower)(WmWindowHandle window);
void (*window_raise_above)(WmWindowHandle window, WmWindowHandle above);
void (*window_lower_below)(WmWindowHandle window, WmWindowHandle below);
void (*window_set_stack_position)(WmWindowHandle window, int position);
int (*window_get_stack_position)(WmWindowHandle window);
/* Window Management - Reparenting */
void (*window_reparent)(WmWindowHandle window, WmWindowHandle parent, int x, int y);
WmWindowHandle (*window_get_parent)(WmWindowHandle window);
/* Window Management - Focus */
void (*window_focus)(WmWindowHandle window);
void (*window_unfocus)(WmWindowHandle window);
bool (*window_is_focused)(WmWindowHandle window);
WmWindowHandle (*window_get_focused)(void);
/* Window Management - Decoration */
void (*window_set_decorated)(WmWindowHandle window, bool decorated);
bool (*window_is_decorated)(WmWindowHandle window);
void (*window_set_border_width)(WmWindowHandle window, int width);
int (*window_get_border_width)(WmWindowHandle window);
void (*window_set_border_color)(WmWindowHandle window, WmColor color);
/* Window Management - Properties */
void (*window_set_title)(WmWindowHandle window, const char *title);
char* (*window_get_title)(WmWindowHandle window);
void (*window_set_class)(WmWindowHandle window, const char *class, const char *instance);
void (*window_get_class)(WmWindowHandle window, char **class, char **instance);
void (*window_set_icon_name)(WmWindowHandle window, const char *name);
/* Window Management - Protocols */
bool (*window_supports_protocol)(WmWindowHandle window, const char *protocol);
void (*window_send_protocol)(WmWindowHandle window, const char *protocol, WmTime timestamp);
void (*window_close)(WmWindowHandle window);
void (*window_kill)(WmWindowHandle window);
/* Window Management - State */
void (*window_set_fullscreen)(WmWindowHandle window, bool fullscreen);
bool (*window_is_fullscreen)(WmWindowHandle window);
void (*window_set_maximized)(WmWindowHandle window, bool maximized);
bool (*window_is_maximized)(WmWindowHandle window);
void (*window_set_minimized)(WmWindowHandle window, bool minimized);
bool (*window_is_minimized)(WmWindowHandle window);
void (*window_set_modal)(WmWindowHandle window, bool modal);
void (*window_set_sticky)(WmWindowHandle window, bool sticky);
void (*window_set_shaded)(WmWindowHandle window, bool shaded);
void (*window_set_skip_taskbar)(WmWindowHandle window, bool skip);
void (*window_set_skip_pager)(WmWindowHandle window, bool skip);
void (*window_set_urgent)(WmWindowHandle window, bool urgent);
bool (*window_is_urgent)(WmWindowHandle window);
/* Window Management - Type */
void (*window_set_type)(WmWindowHandle window, WmWindowType type);
WmWindowType (*window_get_type)(WmWindowHandle window);
/* Window Management - Selection */
void (*window_set_selection_owner)(WmWindowHandle window, const char *selection, WmTime time);
WmWindowHandle (*window_get_selection_owner)(const char *selection);
void (*window_convert_selection)(WmWindowHandle requestor, const char *selection,
const char *target, WmTime time);
/* Window Management - Client List */
WmContainer* (*window_get_stacking_list)(void);
WmContainer* (*window_get_client_list)(void);
/* Input - Events */
bool (*poll_event)(WmBackendEvent *event);
void (*wait_event)(WmBackendEvent *event);
bool (*check_mask_event)(uint32_t mask, WmBackendEvent *event);
void (*put_back_event)(const WmBackendEvent *event);
/* Input - Key Grabbing */
void (*grab_key)(int keycode, uint32_t modifiers, WmWindowHandle window,
bool owner_events, uint32_t pointer_mode, uint32_t keyboard_mode);
void (*ungrab_key)(int keycode, uint32_t modifiers, WmWindowHandle window);
void (*grab_keyboard)(WmWindowHandle window, bool owner_events,
uint32_t pointer_mode, uint32_t keyboard_mode, WmTime time);
void (*ungrab_keyboard)(WmTime time);
/* Input - Button Grabbing */
void (*grab_button)(int button, uint32_t modifiers, WmWindowHandle window,
bool owner_events, uint32_t event_mask,
uint32_t pointer_mode, uint32_t keyboard_mode,
WmWindowHandle confine_to, uint32_t cursor);
void (*ungrab_button)(int button, uint32_t modifiers, WmWindowHandle window);
void (*grab_pointer)(WmWindowHandle window, bool owner_events, uint32_t event_mask,
uint32_t pointer_mode, uint32_t keyboard_mode,
WmWindowHandle confine_to, uint32_t cursor, WmTime time);
void (*ungrab_pointer)(WmTime time);
/* Input - Pointer */
void (*query_pointer)(WmWindowHandle window, int *root_x, int *root_y,
int *win_x, int *win_y, uint32_t *mask);
void (*warp_pointer)(WmWindowHandle dest_w, int dest_x, int dest_y);
void (*set_cursor)(WmWindowHandle window, uint32_t cursor);
/* Rendering - Basic */
void (*fill_rectangle)(WmWindowHandle window, const WmRect *rect, WmColor color);
void (*draw_rectangle)(WmWindowHandle window, const WmRect *rect, WmColor color, int line_width);
void (*draw_line)(WmWindowHandle window, int x1, int y1, int x2, int y2,
WmColor color, int line_width);
void (*clear_window)(WmWindowHandle window);
void (*copy_area)(WmWindowHandle src, WmWindowHandle dst,
int src_x, int src_y, int width, int height,
int dst_x, int dst_y);
/* Rendering - Text (if supported) */
void* (*font_load)(const char *name, int size);
void (*font_destroy)(void *font);
int (*font_text_width)(void *font, const char *text, int len);
void (*draw_text)(WmWindowHandle window, void *font, int x, int y,
const char *text, int len, WmColor color);
/* Rendering - Images (if supported) */
void* (*image_load)(const uint8_t *data, size_t size);
void (*image_destroy)(void *image);
void (*image_get_size)(void *image, int *width, int *height);
void (*draw_image)(WmWindowHandle window, void *image, int x, int y,
int width, int height);
/* Rendering - Sync */
void (*begin_paint)(WmWindowHandle window);
void (*end_paint)(WmWindowHandle window);
/* Atoms/Properties */
uint32_t (*atom_get)(const char *name);
const char* (*atom_get_name)(uint32_t atom);
void (*property_set)(WmWindowHandle window, uint32_t property, uint32_t type,
int format, const void *data, int num_items);
int (*property_get)(WmWindowHandle window, uint32_t property, uint32_t type,
void **data, int *num_items);
void (*property_delete)(WmWindowHandle window, uint32_t property);
/* Session Management */
void (*set_selection_owner)(WmWindowHandle owner, const char *selection);
void (*send_client_message)(WmWindowHandle window, const char *message_type,
const void *data, int format);
/* Error Handling */
int (*get_last_error)(void);
void (*set_error_handler)(int (*handler)(void *display, void *event));
void (*set_io_error_handler)(int (*handler)(void *display));
} BackendInterface;
/*==============================================================================
* Backend Registration
*============================================================================*/
typedef const BackendInterface* (*BackendEntryFunc)(void);
void wm_backend_register(const char *name, BackendEntryFunc entry);
const BackendInterface* wm_backend_get(const char *name);
WmContainer* wm_backend_get_available(void);
/*==============================================================================
* Backend Helpers
*============================================================================*/
static inline bool wm_backend_has_capability(const BackendInterface *backend,
WmBackendCapabilities cap) {
WmBackendInfo info = backend->get_info();
return (info.capabilities & cap) != 0;
}
#ifdef __cplusplus
}
#endif
#endif /* BACKEND_INTERFACE_H */
+35
View File
@@ -0,0 +1,35 @@
/*
* DWN - Desktop Window Manager
* X11 Backend Header
*/
#ifndef X11_BACKEND_H
#define X11_BACKEND_H
#include "backends/backend_interface.h"
#include <X11/Xlib.h>
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* X11 Backend Access
*============================================================================*/
const BackendInterface* x11_backend_get_interface(void);
Display* x11_backend_get_display(void);
Window x11_backend_get_root(void);
int x11_backend_get_screen(void);
static inline bool x11_backend_is_available(void) {
const BackendInterface *iface = x11_backend_get_interface();
return iface && iface->init(NULL);
}
#ifdef __cplusplus
}
#endif
#endif /* X11_BACKEND_H */
+5
View File
@@ -70,4 +70,9 @@ Client *client_get_prev(Client *client);
Client *client_get_first(void);
Client *client_get_last(void);
void client_start_focus_animation(Client *client, bool gaining_focus);
unsigned long client_get_animated_title_color(Client *client);
unsigned long client_get_glow_text_color(Client *client);
void client_update_animations(void);
#endif
+9
View File
@@ -68,6 +68,10 @@ struct Config {
int demo_ai_timeout_ms;
int demo_window_timeout_ms;
/* Fade effects */
float fade_speed; /* Animation speed multiplier (0.1 - 3.0) */
float fade_intensity; /* Glow intensity (0.0 - 1.0) */
char color_panel_bg[16];
char color_panel_fg[16];
char color_workspace_active[16];
@@ -88,6 +92,7 @@ void config_destroy(Config *cfg);
bool config_load(Config *cfg, const char *path);
bool config_reload(Config *cfg);
void config_set_defaults(Config *cfg);
void config_validate(Config *cfg);
const char *config_get_terminal(void);
const char *config_get_launcher(void);
@@ -96,6 +101,10 @@ int config_get_title_height(void);
int config_get_panel_height(void);
int config_get_gap(void);
const ColorScheme *config_get_colors(void);
float config_get_fade_speed(void);
float config_get_fade_intensity(void);
void config_set_fade_speed(float speed);
void config_set_fade_intensity(float intensity);
typedef void (*ConfigCallback)(const char *section, const char *key,
const char *value, void *user_data);
+523
View File
@@ -0,0 +1,523 @@
/*
* DWN - Desktop Window Manager
* Abstract Client Interface
*/
#ifndef WM_CLIENT_H
#define WM_CLIENT_H
#include "core/wm_types.h"
#include "core/wm_string.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Forward declarations */
typedef struct Client Client; /* Legacy client structure */
/*==============================================================================
* Abstract Client Type
*============================================================================*/
typedef struct AbstractClient AbstractClient;
/*==============================================================================
* Client State Flags
*============================================================================*/
typedef enum {
WM_CLIENT_STATE_NORMAL = 0,
WM_CLIENT_STATE_FLOATING = (1 << 0),
WM_CLIENT_STATE_FULLSCREEN = (1 << 1),
WM_CLIENT_STATE_MAXIMIZED = (1 << 2),
WM_CLIENT_STATE_MINIMIZED = (1 << 3),
WM_CLIENT_STATE_URGENT = (1 << 4),
WM_CLIENT_STATE_STICKY = (1 << 5),
WM_CLIENT_STATE_HIDDEN = (1 << 6),
WM_CLIENT_STATE_FOCUS = (1 << 7),
WM_CLIENT_STATE_MAPPED = (1 << 8),
WM_CLIENT_STATE_MANAGED = (1 << 9),
WM_CLIENT_STATE_DESTROYING = (1 << 10)
} WmClientState;
/*==============================================================================
* Client Window Type
*============================================================================*/
typedef enum {
WM_CLIENT_TYPE_UNKNOWN = 0,
WM_CLIENT_TYPE_NORMAL,
WM_CLIENT_TYPE_DIALOG,
WM_CLIENT_TYPE_DOCK,
WM_CLIENT_TYPE_DESKTOP,
WM_CLIENT_TYPE_TOOLBAR,
WM_CLIENT_TYPE_MENU,
WM_CLIENT_TYPE_UTILITY,
WM_CLIENT_TYPE_SPLASH,
WM_CLIENT_TYPE_NOTIFICATION
} WmClientType;
/*==============================================================================
* Client Lifecycle
*============================================================================*/
/**
* Create an abstract client from a native window handle.
* This takes ownership of the native window.
*/
AbstractClient* wm_client_create(WmWindowHandle window, WmClientType type);
/**
* Destroy a client and release all associated resources.
*/
void wm_client_destroy(AbstractClient *client);
/**
* Check if a client is valid (not NULL and not being destroyed).
*/
bool wm_client_is_valid(const AbstractClient *client);
/*==============================================================================
* Native Access (for compatibility during migration)
*============================================================================*/
/**
* Get the native window handle.
* Note: This should be avoided in backend-agnostic code.
*/
WmWindowHandle wm_client_get_window(const AbstractClient *client);
/**
* Get the native frame window handle (if reparented).
*/
WmWindowHandle wm_client_get_frame(const AbstractClient *client);
/**
* Get the legacy Client structure.
* Note: This is for gradual migration only.
*/
Client* wm_client_get_legacy(const AbstractClient *client);
/**
* Create an abstract client wrapper around an existing legacy Client.
* Note: The abstract client does NOT own the legacy client.
*/
AbstractClient* wm_client_from_legacy(Client *client);
/*==============================================================================
* Client Identification
*============================================================================*/
/**
* Get the unique client ID.
*/
WmClientId wm_client_get_id(const AbstractClient *client);
/**
* Get the client window type.
*/
WmClientType wm_client_get_type(const AbstractClient *client);
/**
* Set the client window type.
*/
void wm_client_set_type(AbstractClient *client, WmClientType type);
/*==============================================================================
* Client State
*============================================================================*/
/**
* Get the current state flags.
*/
WmClientState wm_client_get_state(const AbstractClient *client);
/**
* Set state flags.
*/
void wm_client_set_state(AbstractClient *client, WmClientState state);
/**
* Add state flags.
*/
void wm_client_add_state(AbstractClient *client, WmClientState state);
/**
* Remove state flags.
*/
void wm_client_remove_state(AbstractClient *client, WmClientState state);
/**
* Toggle state flags.
*/
void wm_client_toggle_state(AbstractClient *client, WmClientState state);
/**
* Check if a specific state is set.
*/
bool wm_client_has_state(const AbstractClient *client, WmClientState state);
/* Convenience state checks */
static inline bool wm_client_is_floating(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_FLOATING);
}
static inline bool wm_client_is_fullscreen(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_FULLSCREEN);
}
static inline bool wm_client_is_maximized(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_MAXIMIZED);
}
static inline bool wm_client_is_minimized(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_MINIMIZED);
}
static inline bool wm_client_is_urgent(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_URGENT);
}
static inline bool wm_client_is_sticky(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_STICKY);
}
static inline bool wm_client_is_focused(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_FOCUS);
}
static inline bool wm_client_is_mapped(const AbstractClient *c) {
return c && wm_client_has_state(c, WM_CLIENT_STATE_MAPPED);
}
/*==============================================================================
* Client Geometry
*============================================================================*/
/**
* Get the client geometry (position and size).
*/
WmRect wm_client_get_geometry(const AbstractClient *client);
/**
* Set the client geometry.
*/
void wm_client_set_geometry(AbstractClient *client, const WmRect *geometry);
/**
* Get the client position.
*/
WmPoint wm_client_get_position(const AbstractClient *client);
/**
* Set the client position.
*/
void wm_client_set_position(AbstractClient *client, int x, int y);
/**
* Get the client size.
*/
WmSize wm_client_get_size(const AbstractClient *client);
/**
* Set the client size.
*/
void wm_client_set_size(AbstractClient *client, int width, int height);
/**
* Get the border width.
*/
int wm_client_get_border_width(const AbstractClient *client);
/**
* Set the border width.
*/
void wm_client_set_border_width(AbstractClient *client, int width);
/**
* Store previous geometry (for restore after maximize/fullscreen).
*/
void wm_client_save_geometry(AbstractClient *client);
/**
* Restore previous geometry.
*/
void wm_client_restore_geometry(AbstractClient *client);
/*==============================================================================
* Client Workspace
*============================================================================*/
/**
* Get the client's workspace ID.
*/
WmWorkspaceId wm_client_get_workspace(const AbstractClient *client);
/**
* Set the client's workspace ID.
*/
void wm_client_set_workspace(AbstractClient *client, WmWorkspaceId workspace);
/**
* Check if the client is on a specific workspace.
*/
bool wm_client_is_on_workspace(const AbstractClient *client, WmWorkspaceId workspace);
/**
* Check if the client is visible on the current workspace.
*/
bool wm_client_is_visible_on_current(const AbstractClient *client);
/*==============================================================================
* Client Properties
*============================================================================*/
/**
* Get the client title.
* Returns a reference to the internal string (do not free).
*/
const char* wm_client_get_title(const AbstractClient *client);
/**
* Set the client title.
*/
void wm_client_set_title(AbstractClient *client, const char *title);
/**
* Get the client class (application class).
*/
const char* wm_client_get_class(const AbstractClient *client);
/**
* Set the client class.
*/
void wm_client_set_class(AbstractClient *client, const char *class_name);
/**
* Get the client instance name.
*/
const char* wm_client_get_instance(const AbstractClient *client);
/**
* Set the client instance name.
*/
void wm_client_set_instance(AbstractClient *client, const char *instance);
/**
* Get the client's taskbar color.
*/
WmColor wm_client_get_color(const AbstractClient *client);
/**
* Set the client's taskbar color.
*/
void wm_client_set_color(AbstractClient *client, WmColor color);
/*==============================================================================
* Client Actions
*============================================================================*/
/**
* Focus the client.
*/
void wm_client_focus(AbstractClient *client);
/**
* Unfocus the client.
*/
void wm_client_unfocus(AbstractClient *client);
/**
* Raise the client to the top of the stack.
*/
void wm_client_raise(AbstractClient *client);
/**
* Lower the client to the bottom of the stack.
*/
void wm_client_lower(AbstractClient *client);
/**
* Show/map the client window.
*/
void wm_client_show(AbstractClient *client);
/**
* Hide/unmap the client window.
*/
void wm_client_hide(AbstractClient *client);
/**
* Minimize the client.
*/
void wm_client_minimize(AbstractClient *client);
/**
* Restore the client from minimized state.
*/
void wm_client_restore(AbstractClient *client);
/**
* Close the client (politely request close).
*/
void wm_client_close(AbstractClient *client);
/**
* Kill the client (forcefully terminate).
*/
void wm_client_kill(AbstractClient *client);
/*==============================================================================
* Client Management
*============================================================================*/
/**
* Reparent the client into a frame window.
*/
void wm_client_reparent(AbstractClient *client, WmWindowHandle frame);
/**
* Unreparent the client from its frame.
*/
void wm_client_unreparent(AbstractClient *client);
/**
* Configure the client (apply size hints, constraints).
*/
void wm_client_configure(AbstractClient *client);
/**
* Apply size hints to constrain width/height.
*/
void wm_client_apply_size_hints(AbstractClient *client, int *width, int *height);
/**
* Move and resize a client.
*/
void wm_client_move_resize(AbstractClient *client, int x, int y, int width, int height);
/*==============================================================================
* Client List Management
*============================================================================*/
/**
* Get the next client in the global list.
*/
AbstractClient* wm_client_get_next(const AbstractClient *client);
/**
* Get the previous client in the global list.
*/
AbstractClient* wm_client_get_prev(const AbstractClient *client);
/**
* Get the first client in the global list.
*/
AbstractClient* wm_client_get_first(void);
/**
* Get the last client in the global list.
*/
AbstractClient* wm_client_get_last(void);
/**
* Get the next client in MRU (Most Recently Used) order.
*/
AbstractClient* wm_client_get_next_mru(const AbstractClient *client);
/**
* Get the previous client in MRU order.
*/
AbstractClient* wm_client_get_prev_mru(const AbstractClient *client);
/*==============================================================================
* Client Lookup
*============================================================================*/
/**
* Find a client by its window handle.
*/
AbstractClient* wm_client_find_by_window(WmWindowHandle window);
/**
* Find a client by its frame handle.
*/
AbstractClient* wm_client_find_by_frame(WmWindowHandle frame);
/**
* Find a client by its ID.
*/
AbstractClient* wm_client_find_by_id(WmClientId id);
/*==============================================================================
* Client Iteration
*============================================================================*/
typedef bool (*WmClientForeachFunc)(AbstractClient *client, void *user_data);
/**
* Iterate over all clients.
*/
void wm_client_foreach(WmClientForeachFunc func, void *user_data);
/**
* Iterate over clients on a specific workspace.
*/
void wm_client_foreach_on_workspace(WmWorkspaceId workspace,
WmClientForeachFunc func, void *user_data);
/**
* Count total clients.
*/
int wm_client_count(void);
/**
* Count clients on a specific workspace.
*/
int wm_client_count_on_workspace(WmWorkspaceId workspace);
/*==============================================================================
* Client Manager
*============================================================================*/
typedef struct WmClientManager WmClientManager;
/**
* Get the global client manager.
*/
WmClientManager* wm_client_manager_get(void);
/**
* Initialize the client manager.
*/
bool wm_client_manager_init(void);
/**
* Shutdown the client manager.
*/
void wm_client_manager_shutdown(void);
/**
* Register a client with the manager.
*/
void wm_client_manager_add(WmClientManager *mgr, AbstractClient *client);
/**
* Unregister a client from the manager.
*/
void wm_client_manager_remove(WmClientManager *mgr, AbstractClient *client);
/**
* Move a client to the front of the MRU list.
*/
void wm_client_manager_touch(WmClientManager *mgr, AbstractClient *client);
/**
* Get the focused client.
*/
AbstractClient* wm_client_manager_get_focused(const WmClientManager *mgr);
/**
* Set the focused client.
*/
void wm_client_manager_set_focused(WmClientManager *mgr, AbstractClient *client);
#ifdef __cplusplus
}
#endif
#endif /* WM_CLIENT_H */
+220
View File
@@ -0,0 +1,220 @@
/*
* DWN - Desktop Window Manager
* Generic Container Interface
*/
#ifndef WM_CONTAINER_H
#define WM_CONTAINER_H
#include "wm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Container Lifecycle
*============================================================================*/
WmContainer* wm_container_create(WmContainerType type, WmDestroyFunc destroy_fn);
void wm_container_destroy(WmContainer *container);
void wm_container_clear(WmContainer *container);
WmContainer* wm_container_clone(const WmContainer *container, WmCloneFunc clone_fn);
/*==============================================================================
* Container Properties
*============================================================================*/
WmContainerType wm_container_get_type(const WmContainer *container);
size_t wm_container_size(const WmContainer *container);
bool wm_container_is_empty(const WmContainer *container);
/*==============================================================================
* List Operations
*============================================================================*/
/* These work for LIST and QUEUE */
void wm_list_append(WmContainer *list, void *data);
void wm_list_prepend(WmContainer *list, void *data);
void wm_list_insert(WmContainer *list, size_t index, void *data);
bool wm_list_remove(WmContainer *list, void *data);
void* wm_list_remove_at(WmContainer *list, size_t index);
void* wm_list_get(const WmContainer *list, size_t index);
void* wm_list_get_first(const WmContainer *list);
void* wm_list_get_last(const WmContainer *list);
size_t wm_list_index_of(const WmContainer *list, void *data);
bool wm_list_contains(const WmContainer *list, void *data);
void* wm_list_find(const WmContainer *list, WmCompareFunc cmp, const void *key);
void* wm_list_find_custom(const WmContainer *list,
bool (*predicate)(const void *item, const void *user_data),
const void *user_data);
void wm_list_sort(WmContainer *list, WmCompareFunc cmp);
void wm_list_reverse(WmContainer *list);
/*==============================================================================
* Array Operations
*============================================================================*/
void wm_array_append(WmContainer *array, void *data);
void wm_array_insert(WmContainer *array, size_t index, void *data);
void wm_array_set(WmContainer *array, size_t index, void *data);
void* wm_array_get(const WmContainer *array, size_t index);
void* wm_array_remove_at(WmContainer *array, size_t index);
void wm_array_resize(WmContainer *array, size_t new_size);
void wm_array_reserve(WmContainer *array, size_t capacity);
size_t wm_array_capacity(const WmContainer *array);
void wm_array_compact(WmContainer *array);
void wm_array_sort(WmContainer *array, WmCompareFunc cmp);
/*==============================================================================
* HashMap Operations
*============================================================================*/
/* Key types supported */
typedef enum {
WM_HASH_KEY_STRING,
WM_HASH_KEY_INT,
WM_HASH_KEY_POINTER
} WmHashKeyType;
WmContainer* wm_hashmap_create(WmHashKeyType key_type, WmDestroyFunc destroy_fn);
void wm_hashmap_insert(WmContainer *map, const void *key, void *value);
void* wm_hashmap_get(const WmContainer *map, const void *key);
bool wm_hashmap_contains(const WmContainer *map, const void *key);
bool wm_hashmap_remove(WmContainer *map, const void *key);
void* wm_hashmap_lookup(const WmContainer *map, WmCompareFunc cmp, const void *key);
WmContainer* wm_hashmap_get_keys(const WmContainer *map);
WmContainer* wm_hashmap_get_values(const WmContainer *map);
void wm_hashmap_rehash(WmContainer *map, size_t new_capacity);
float wm_hashmap_load_factor(const WmContainer *map);
/* Convenience functions for string keys */
void wm_hashmap_insert_string(WmContainer *map, const char *key, void *value);
void* wm_hashmap_get_string(const WmContainer *map, const char *key);
bool wm_hashmap_contains_string(const WmContainer *map, const char *key);
bool wm_hashmap_remove_string(WmContainer *map, const char *key);
/* Convenience functions for int keys */
void wm_hashmap_insert_int(WmContainer *map, int key, void *value);
void* wm_hashmap_get_int(const WmContainer *map, int key);
bool wm_hashmap_contains_int(const WmContainer *map, int key);
bool wm_hashmap_remove_int(WmContainer *map, int key);
/*==============================================================================
* Stack Operations
*============================================================================*/
void wm_stack_push(WmContainer *stack, void *data);
void* wm_stack_pop(WmContainer *stack);
void* wm_stack_peek(const WmContainer *stack);
/*==============================================================================
* Queue Operations
*============================================================================*/
void wm_queue_enqueue(WmContainer *queue, void *data);
void* wm_queue_dequeue(WmContainer *queue);
void* wm_queue_peek(const WmContainer *queue);
void* wm_queue_peek_tail(const WmContainer *queue);
/*==============================================================================
* Tree Operations (if type is TREE)
*============================================================================*/
typedef struct WmTreeNode WmTreeNode;
WmTreeNode* wm_tree_get_root(const WmContainer *tree);
WmTreeNode* wm_tree_node_get_left(const WmTreeNode *node);
WmTreeNode* wm_tree_node_get_right(const WmTreeNode *node);
WmTreeNode* wm_tree_node_get_parent(const WmTreeNode *node);
void* wm_tree_node_get_data(const WmTreeNode *node);
/*==============================================================================
* Iteration
*============================================================================*/
WmIterator* wm_container_iterator(const WmContainer *container);
WmIterator* wm_container_iterator_reverse(const WmContainer *container);
void wm_iterator_destroy(WmIterator *it);
bool wm_iterator_has_next(const WmIterator *it);
void* wm_iterator_next(WmIterator *it);
const void* wm_iterator_peek(const WmIterator *it);
/* HashMap iteration - returns key-value pairs */
typedef struct {
const void *key;
void *value;
} WmHashEntry;
WmIterator* wm_hashmap_iterator(const WmContainer *map);
WmHashEntry* wm_hashmap_iterator_next(WmIterator *it);
/*==============================================================================
* Functional Operations
*============================================================================*/
typedef void (*WmForeachFunc)(void *item, void *user_data);
typedef void* (*WmMapFunc)(const void *item, void *user_data);
typedef bool (*WmFilterFunc)(const void *item, void *user_data);
typedef void* (*WmReduceFunc)(void *accumulator, const void *item, void *user_data);
void wm_container_foreach(const WmContainer *container, WmForeachFunc func, void *user_data);
WmContainer* wm_container_map(const WmContainer *container, WmContainerType new_type,
WmMapFunc func, WmDestroyFunc destroy_fn, void *user_data);
WmContainer* wm_container_filter(const WmContainer *container,
WmFilterFunc predicate, void *user_data);
void* wm_container_reduce(const WmContainer *container,
WmReduceFunc func, void *initial, void *user_data);
bool wm_container_all_match(const WmContainer *container,
WmFilterFunc predicate, void *user_data);
bool wm_container_any_match(const WmContainer *container,
WmFilterFunc predicate, void *user_data);
/*==============================================================================
* Convenience Constructors
*============================================================================*/
static inline WmContainer* wm_list_create(WmDestroyFunc destroy_fn) {
return wm_container_create(WM_CONTAINER_LIST, destroy_fn);
}
static inline WmContainer* wm_array_create(WmDestroyFunc destroy_fn) {
return wm_container_create(WM_CONTAINER_ARRAY, destroy_fn);
}
static inline WmContainer* wm_hashmap_create_string(WmDestroyFunc destroy_fn) {
return wm_hashmap_create(WM_HASH_KEY_STRING, destroy_fn);
}
static inline WmContainer* wm_queue_create(WmDestroyFunc destroy_fn) {
return wm_container_create(WM_CONTAINER_QUEUE, destroy_fn);
}
static inline WmContainer* wm_stack_create(WmDestroyFunc destroy_fn) {
return wm_container_create(WM_CONTAINER_STACK, destroy_fn);
}
#ifdef __cplusplus
}
#endif
#endif /* WM_CONTAINER_H */
+265
View File
@@ -0,0 +1,265 @@
/*
* DWN - Desktop Window Manager
* Event Bus System
*/
#ifndef WM_EVENT_H
#define WM_EVENT_H
#include "wm_types.h"
#include "wm_client.h"
#include "wm_string.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Event Data Structures
*============================================================================*/
typedef struct {
WmEventType type;
WmTime timestamp;
WmId source_id;
} WmEventHeader;
/* Window Events */
typedef struct {
WmEventHeader header;
AbstractClient *client;
} WmEventWindowBase;
typedef struct {
WmEventHeader header;
AbstractClient *client;
} WmEventWindowCreated;
typedef struct {
WmEventHeader header;
AbstractClient *client;
WmString *old_title;
WmString *new_title;
} WmEventWindowTitleChanged;
typedef struct {
WmEventHeader header;
AbstractClient *client;
WmRect old_geometry;
WmRect new_geometry;
} WmEventWindowMoved;
typedef struct {
WmEventHeader header;
AbstractClient *client;
WmRect old_geometry;
WmRect new_geometry;
} WmEventWindowResized;
typedef struct {
WmEventHeader header;
AbstractClient *client;
WmClientFlags old_flags;
WmClientFlags new_flags;
} WmEventWindowStateChanged;
/* Workspace Events */
typedef struct {
WmEventHeader header;
WmWorkspaceId workspace;
} WmEventWorkspaceBase;
typedef struct {
WmEventHeader header;
WmWorkspaceId old_workspace;
WmWorkspaceId new_workspace;
} WmEventWorkspaceSwitched;
typedef struct {
WmEventHeader header;
WmWorkspaceId workspace;
WmLayoutType old_layout;
WmLayoutType new_layout;
} WmEventWorkspaceLayoutChanged;
typedef struct {
WmEventHeader header;
WmWorkspaceId workspace;
AbstractClient *client;
} WmEventClientWorkspaceChanged;
/* Input Events */
typedef struct {
WmEventHeader header;
unsigned int keycode;
unsigned int keysym;
unsigned int modifiers;
const char *key_name;
} WmEventKey;
typedef struct {
WmEventHeader header;
unsigned int button;
int x;
int y;
unsigned int modifiers;
} WmEventButton;
typedef struct {
WmEventHeader header;
int x;
int y;
int delta_x;
int delta_y;
unsigned int modifiers;
} WmEventMotion;
typedef struct {
WmEventHeader header;
int delta;
int x;
int y;
} WmEventScroll;
/* Monitor Events */
typedef struct {
WmEventHeader header;
int monitor_index;
WmRect geometry;
bool primary;
} WmEventMonitorAdded;
typedef struct {
WmEventHeader header;
int monitor_index;
} WmEventMonitorRemoved;
/* Configuration Events */
typedef struct {
WmEventHeader header;
WmString *config_path;
bool success;
WmString *error_message;
} WmEventConfigReloaded;
/* Command Events */
typedef struct {
WmEventHeader header;
WmCommandType command;
bool success;
WmString *error_message;
} WmEventCommandExecuted;
/* Custom Event */
typedef struct {
WmEventHeader header;
WmString *event_name;
void *custom_data;
WmDestroyFunc destroy_func;
} WmEventCustom;
/*==============================================================================
* Event Bus Lifecycle
*============================================================================*/
WmEventBus* wm_event_bus_create(void);
void wm_event_bus_destroy(WmEventBus *bus);
WmEventBus* wm_event_bus_get_default(void);
void wm_event_bus_set_default(WmEventBus *bus);
/*==============================================================================
* Event Subscription
*============================================================================*/
WmEventSubscription* wm_event_subscribe(WmEventBus *bus,
WmEventType type,
WmEventHandler handler,
void *user_data);
WmEventSubscription* wm_event_subscribe_pattern(WmEventBus *bus,
WmEventType type_min,
WmEventType type_max,
WmEventHandler handler,
void *user_data);
WmEventSubscription* wm_event_subscribe_all(WmEventBus *bus,
WmEventHandler handler,
void *user_data);
void wm_event_unsubscribe(WmEventBus *bus, WmEventSubscription *subscription);
void wm_event_unsubscribe_all(WmEventBus *bus, void *user_data);
/*==============================================================================
* Event Emission
*============================================================================*/
void wm_event_emit(WmEventBus *bus, WmEventType type, const void *event_data);
void wm_event_emit_async(WmEventBus *bus, WmEventType type, const void *event_data);
/* Convenience emitters */
void wm_event_emit_window_created(WmEventBus *bus, AbstractClient *client);
void wm_event_emit_window_destroyed(WmEventBus *bus, AbstractClient *client);
void wm_event_emit_window_focused(WmEventBus *bus, AbstractClient *client, AbstractClient *prev_focus);
void wm_event_emit_window_moved(WmEventBus *bus, AbstractClient *client, const WmRect *old_geom);
void wm_event_emit_window_resized(WmEventBus *bus, AbstractClient *client, const WmRect *old_geom);
void wm_event_emit_window_state_changed(WmEventBus *bus, AbstractClient *client,
WmClientFlags old_flags, WmClientFlags new_flags);
void wm_event_emit_workspace_switched(WmEventBus *bus, WmWorkspaceId old_ws, WmWorkspaceId new_ws);
void wm_event_emit_workspace_layout_changed(WmEventBus *bus, WmWorkspaceId ws,
WmLayoutType old_layout, WmLayoutType new_layout);
/*==============================================================================
* Event Processing
*============================================================================*/
void wm_event_process(WmEventBus *bus);
void wm_event_process_all(WmEventBus *bus);
bool wm_event_process_one(WmEventBus *bus);
void wm_event_dispatch_pending(WmEventBus *bus);
int wm_event_get_pending_count(const WmEventBus *bus);
/*==============================================================================
* Event Data Helpers
*============================================================================*/
WmEventHeader* wm_event_get_header(void *event_data);
WmTime wm_event_get_timestamp(const void *event_data);
const char* wm_event_type_to_string(WmEventType type);
WmEventType wm_event_type_from_string(const char *str);
/*==============================================================================
* Event Blocking/Filtering
*============================================================================*/
void wm_event_begin_block(WmEventBus *bus, WmEventType type);
void wm_event_end_block(WmEventBus *bus, WmEventType type);
bool wm_event_is_blocked(const WmEventBus *bus, WmEventType type);
void wm_event_suppress(WmEventBus *bus, WmEventType type);
void wm_event_unsuppress(WmEventBus *bus, WmEventType type);
bool wm_event_is_suppressed(const WmEventBus *bus, WmEventType type);
/*==============================================================================
* Custom Events
*============================================================================*/
void wm_event_emit_custom(WmEventBus *bus, const char *event_name, void *data, WmDestroyFunc destroy_fn);
WmEventSubscription* wm_event_subscribe_custom(WmEventBus *bus, const char *event_name,
WmEventHandler handler, void *user_data);
/*==============================================================================
* Event Statistics
*============================================================================*/
uint64_t wm_event_get_emit_count(const WmEventBus *bus, WmEventType type);
uint64_t wm_event_get_total_emit_count(const WmEventBus *bus);
void wm_event_reset_statistics(WmEventBus *bus);
#ifdef __cplusplus
}
#endif
#endif /* WM_EVENT_H */
+107
View File
@@ -0,0 +1,107 @@
/*
* DWN - Desktop Window Manager
* Abstract Hash Map Container
*/
#ifndef WM_HASHMAP_H
#define WM_HASHMAP_H
#include "core/wm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Opaque Types
*============================================================================*/
typedef struct WmHashMap WmHashMap;
/*==============================================================================
* Function Types
*============================================================================*/
typedef uint32_t (*WmHashFunc)(const void *key);
typedef bool (*WmKeyEqualFunc)(const void *a, const void *b);
typedef void (*WmHashForeachFunc)(void *key, void *value, void *user_data);
/*==============================================================================
* Hash Functions
*============================================================================*/
static inline uint32_t wm_hash_ptr(const void *ptr) {
uintptr_t p = (uintptr_t)ptr;
return (uint32_t)(p ^ (p >> 32));
}
static inline uint32_t wm_hash_int(int key) {
return (uint32_t)key;
}
/*==============================================================================
* Lifecycle
*============================================================================*/
WmHashMap* wm_hashmap_new(void);
WmHashMap* wm_hashmap_new_full(WmHashFunc hash_func, WmKeyEqualFunc key_equal,
WmFreeFunc key_free, WmFreeFunc value_free);
WmHashMap* wm_hashmap_new_string_key(void);
void wm_hashmap_destroy(WmHashMap *map);
WmHashMap* wm_hashmap_clone(const WmHashMap *map);
/*==============================================================================
* Capacity
*============================================================================*/
size_t wm_hashmap_size(const WmHashMap *map);
bool wm_hashmap_is_empty(const WmHashMap *map);
void wm_hashmap_clear(WmHashMap *map);
/*==============================================================================
* Element Access
*============================================================================*/
void* wm_hashmap_get(const WmHashMap *map, const void *key);
void* wm_hashmap_get_or_default(const WmHashMap *map, const void *key, void *default_val);
bool wm_hashmap_contains(const WmHashMap *map, const void *key);
/*==============================================================================
* Modifiers
*============================================================================*/
bool wm_hashmap_insert(WmHashMap *map, void *key, void *value);
bool wm_hashmap_insert_no_replace(WmHashMap *map, void *key, void *value);
void* wm_hashmap_set(WmHashMap *map, void *key, void *value);
void* wm_hashmap_remove(WmHashMap *map, const void *key);
bool wm_hashmap_steal(WmHashMap *map, const void *key);
/*==============================================================================
* Iteration
*============================================================================*/
typedef struct {
void *map; /* Opaque pointer to hashmap */
size_t bucket;
void *entry; /* Opaque pointer to current entry */
void *next_entry; /* Opaque pointer to next entry */
} WmHashMapIter;
void wm_hashmap_iter_init(WmHashMapIter *iter, WmHashMap *map);
bool wm_hashmap_iter_next(WmHashMapIter *iter, void **key, void **value);
void wm_hashmap_foreach(const WmHashMap *map, WmHashForeachFunc func, void *user_data);
/*==============================================================================
* String Helpers
*============================================================================*/
bool wm_hashmap_insert_string(WmHashMap *map, const char *key, void *value);
void* wm_hashmap_get_string(const WmHashMap *map, const char *key);
bool wm_hashmap_contains_string(const WmHashMap *map, const char *key);
void* wm_hashmap_remove_string(WmHashMap *map, const char *key);
#ifdef __cplusplus
}
#endif
#endif /* WM_HASHMAP_H */
+123
View File
@@ -0,0 +1,123 @@
/*
* DWN - Desktop Window Manager
* Abstract List Container
*/
#ifndef WM_LIST_H
#define WM_LIST_H
#include "core/wm_types.h"
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Opaque Types
*============================================================================*/
typedef struct WmList WmList;
/*==============================================================================
* Function Types
*============================================================================*/
typedef void (*WmFreeFunc)(void *item);
typedef int (*WmCompareFunc)(const void *a, const void *b);
typedef bool (*WmForeachFunc)(void *item, size_t index, void *user_data);
typedef void* (*WmCloneFunc)(const void *item);
/*==============================================================================
* Lifecycle
*============================================================================*/
WmList* wm_list_new(void);
WmList* wm_list_new_sized(size_t initial_capacity);
void wm_list_destroy(WmList *list);
void wm_list_destroy_no_free(WmList *list);
WmList* wm_list_clone(const WmList *list);
void wm_list_set_free_func(WmList *list, WmFreeFunc func);
/*==============================================================================
* Capacity
*============================================================================*/
size_t wm_list_size(const WmList *list);
size_t wm_list_capacity(const WmList *list);
bool wm_list_is_empty(const WmList *list);
void wm_list_reserve(WmList *list, size_t capacity);
void wm_list_compact(WmList *list);
void wm_list_clear(WmList *list);
/*==============================================================================
* Modifiers
*============================================================================*/
void wm_list_append(WmList *list, void *item);
void wm_list_prepend(WmList *list, void *item);
void wm_list_insert(WmList *list, size_t index, void *item);
void wm_list_insert_sorted(WmList *list, void *item, WmCompareFunc compare);
void wm_list_remove(WmList *list, void *item);
void wm_list_remove_at(WmList *list, size_t index);
void* wm_list_take_at(WmList *list, size_t index);
bool wm_list_remove_one(WmList *list, void *item);
bool wm_list_remove_all(WmList *list, void *item);
void* wm_list_pop_back(WmList *list);
void* wm_list_pop_front(WmList *list);
/*==============================================================================
* Accessors
*============================================================================*/
void* wm_list_get(const WmList *list, size_t index);
void* wm_list_first(const WmList *list);
void* wm_list_last(const WmList *list);
void* wm_list_front(const WmList *list);
void* wm_list_back(const WmList *list);
void wm_list_set(WmList *list, size_t index, void *item);
/*==============================================================================
* Iteration
*============================================================================*/
void wm_list_foreach(const WmList *list, WmForeachFunc func, void *user_data);
void wm_list_foreach_reverse(const WmList *list, WmForeachFunc func, void *user_data);
/*==============================================================================
* Search
*============================================================================*/
ssize_t wm_list_index_of(const WmList *list, void *item);
bool wm_list_contains(const WmList *list, void *item);
void* wm_list_find(const WmList *list, WmCompareFunc compare, const void *key);
ssize_t wm_list_find_index(const WmList *list, WmCompareFunc compare, const void *key);
/*==============================================================================
* Sorting
*============================================================================*/
void wm_list_sort(WmList *list, WmCompareFunc compare);
/*==============================================================================
* Data Operations
*============================================================================*/
void** wm_list_data(WmList *list);
void* wm_list_steal(WmList *list, size_t index);
WmList* wm_list_slice(const WmList *list, size_t start, size_t end);
void wm_list_move(WmList *list, size_t from, size_t to);
void wm_list_swap(WmList *list, size_t i, size_t j);
/*==============================================================================
* Comparison
*============================================================================*/
bool wm_list_equals(const WmList *list1, const WmList *list2);
#ifdef __cplusplus
}
#endif
#endif /* WM_LIST_H */
+222
View File
@@ -0,0 +1,222 @@
/*
* DWN - Desktop Window Manager
* Abstract String Type
*/
#ifndef WM_STRING_H
#define WM_STRING_H
#include "wm_types.h"
#include <stdarg.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* String Lifecycle
*============================================================================*/
WmString* wm_string_new(const char *str);
WmString* wm_string_new_empty(void);
WmString* wm_string_new_sized(size_t capacity);
WmString* wm_string_new_printf(const char *fmt, ...);
WmString* wm_string_new_vprintf(const char *fmt, va_list args);
WmString* wm_string_new_n(const char *str, size_t n);
void wm_string_destroy(WmString *str);
WmString* wm_string_clone(const WmString *str);
/*==============================================================================
* String Operations
*============================================================================*/
void wm_string_append(WmString *str, const char *suffix);
void wm_string_append_char(WmString *str, char c);
void wm_string_append_n(WmString *str, const char *suffix, size_t n);
void wm_string_append_printf(WmString *str, const char *fmt, ...);
void wm_string_append_vprintf(WmString *str, const char *fmt, va_list args);
void wm_string_append_string(WmString *str, const WmString *suffix);
void wm_string_prepend(WmString *str, const char *prefix);
void wm_string_prepend_char(WmString *str, char c);
void wm_string_insert(WmString *str, size_t pos, const char *insert);
void wm_string_insert_char(WmString *str, size_t pos, char c);
void wm_string_insert_string(WmString *str, size_t pos, const WmString *insert);
void wm_string_erase(WmString *str, size_t pos, size_t len);
void wm_string_clear(WmString *str);
/*==============================================================================
* String Queries
*============================================================================*/
const char* wm_string_cstr(const WmString *str);
char* wm_string_detach(WmString *str);
size_t wm_string_length(const WmString *str);
size_t wm_string_capacity(const WmString *str);
size_t wm_string_bytes(const WmString *str);
bool wm_string_is_empty(const WmString *str);
bool wm_string_is_null(const WmString *str);
char wm_string_char_at(const WmString *str, size_t pos);
/*==============================================================================
* String Comparison
*============================================================================*/
bool wm_string_equals(const WmString *str1, const WmString *str2);
bool wm_string_equals_cstr(const WmString *str, const char *cstr);
int wm_string_compare(const WmString *str1, const WmString *str2);
int wm_string_compare_cstr(const WmString *str, const char *cstr);
int wm_string_case_compare(const WmString *str1, const WmString *str2);
int wm_string_case_compare_cstr(const WmString *str, const char *cstr);
/*==============================================================================
* String Searching
*============================================================================*/
bool wm_string_contains(const WmString *str, const char *needle);
bool wm_string_contains_char(const WmString *str, char c);
size_t wm_string_find(const WmString *str, const char *needle, size_t start);
size_t wm_string_find_char(const WmString *str, char c, size_t start);
size_t wm_string_find_last(const WmString *str, const char *needle);
size_t wm_string_find_last_char(const WmString *str, char c);
bool wm_string_starts_with(const WmString *str, const char *prefix);
bool wm_string_starts_with_char(const WmString *str, char c);
bool wm_string_ends_with(const WmString *str, const char *suffix);
bool wm_string_ends_with_char(const WmString *str, char c);
/*==============================================================================
* String Modification
*============================================================================*/
void wm_string_trim(WmString *str);
void wm_string_trim_left(WmString *str);
void wm_string_trim_right(WmString *str);
void wm_string_replace(WmString *str, const char *search, const char *replace);
void wm_string_replace_char(WmString *str, char search, char replace);
size_t wm_string_replace_all(WmString *str, const char *search, const char *replace);
void wm_string_to_lower(WmString *str);
void wm_string_to_upper(WmString *str);
void wm_string_reverse(WmString *str);
/*==============================================================================
* String Substrings
*============================================================================*/
WmString* wm_string_substring(const WmString *str, size_t start, size_t len);
WmString* wm_string_left(const WmString *str, size_t n);
WmString* wm_string_right(const WmString *str, size_t n);
/*==============================================================================
* String Splitting and Joining
*============================================================================*/
WmContainer* wm_string_split(const WmString *str, const char *delimiter);
WmContainer* wm_string_split_chars(const WmString *str, const char *delimiters);
WmContainer* wm_string_split_lines(const WmString *str);
WmString* wm_string_join(const WmContainer *strings, const char *separator);
WmString* wm_string_join_cstr(const char **strings, size_t count, const char *separator);
/*==============================================================================
* String Formatting
*============================================================================*/
void wm_string_printf(WmString *str, const char *fmt, ...);
void wm_string_vprintf(WmString *str, const char *fmt, va_list args);
/*==============================================================================
* String Validation
*============================================================================*/
bool wm_string_is_valid_utf8(const WmString *str);
bool wm_string_is_ascii(const WmString *str);
bool wm_string_is_numeric(const WmString *str);
bool wm_string_is_integer(const WmString *str);
bool wm_string_is_float(const WmString *str);
/*==============================================================================
* String Conversion
*============================================================================*/
int wm_string_to_int(const WmString *str, int default_val);
long wm_string_to_long(const WmString *str, long default_val);
float wm_string_to_float(const WmString *str, float default_val);
double wm_string_to_double(const WmString *str, double default_val);
bool wm_string_to_bool(const WmString *str, bool default_val);
WmString* wm_string_from_int(int val);
WmString* wm_string_from_long(long val);
WmString* wm_string_from_float(float val, int precision);
WmString* wm_string_from_double(double val, int precision);
WmString* wm_string_from_bool(bool val);
/*==============================================================================
* String Hash
*============================================================================*/
uint32_t wm_string_hash(const WmString *str);
uint32_t wm_string_hash_cstr(const char *str);
/*==============================================================================
* String Escaping
*============================================================================*/
WmString* wm_string_escape_json(const WmString *str);
WmString* wm_string_escape_xml(const WmString *str);
WmString* wm_string_escape_html(const WmString *str);
WmString* wm_string_escape_shell(const WmString *str);
WmString* wm_string_escape_regex(const WmString *str);
WmString* wm_string_unescape_json(const WmString *str);
WmString* wm_string_unescape_xml(const WmString *str);
WmString* wm_string_unescape_html(const WmString *str);
/*==============================================================================
* Static String Helpers
*============================================================================*/
static inline bool wm_cstr_is_empty(const char *str) {
return str == NULL || str[0] == '\0';
}
static inline size_t wm_cstr_length(const char *str) {
return str == NULL ? 0 : strlen(str);
}
static inline bool wm_cstr_equals(const char *a, const char *b) {
if (a == b) return true;
if (a == NULL || b == NULL) return false;
return strcmp(a, b) == 0;
}
static inline bool wm_cstr_case_equals(const char *a, const char *b) {
if (a == b) return true;
if (a == NULL || b == NULL) return false;
return strcasecmp(a, b) == 0;
}
static inline void wm_cstr_copy(char *dest, const char *src, size_t size) {
if (size == 0) return;
strncpy(dest, src, size - 1);
dest[size - 1] = '\0';
}
#ifdef __cplusplus
}
#endif
#endif /* WM_STRING_H */
+490
View File
@@ -0,0 +1,490 @@
/*
* DWN - Desktop Window Manager
* Core Abstract Types
* Extreme Abstraction Architecture
*/
#ifndef WM_TYPES_H
#define WM_TYPES_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Version and API Information
*============================================================================*/
#define WM_CORE_VERSION_MAJOR 2
#define WM_CORE_VERSION_MINOR 0
#define WM_CORE_VERSION_PATCH 0
#define WM_PLUGIN_API_VERSION 1
/*==============================================================================
* Opaque Type Declarations
*============================================================================*/
typedef struct WmCore WmCore;
typedef struct WmBackend WmBackend;
typedef struct WmPlugin WmPlugin;
typedef struct WmPluginManager WmPluginManager;
typedef struct WmEventBus WmEventBus;
typedef struct WmEventSubscription WmEventSubscription;
typedef struct WmContainer WmContainer;
typedef struct WmIterator WmIterator;
typedef struct WmString WmString;
typedef struct WmRenderer WmRenderer;
typedef struct WmSurface WmSurface;
typedef struct WmFont WmFont;
typedef struct WmImage WmImage;
typedef struct WmCommand WmCommand;
typedef struct WmCommandBuilder WmCommandBuilder;
typedef struct WmConfigSchema WmConfigSchema;
typedef struct WmConfigInstance WmConfigInstance;
typedef struct WmLayoutEngine WmLayoutEngine;
typedef struct WmPanelSystem WmPanelSystem;
typedef struct WmAnimation WmAnimation;
typedef struct WmTimer WmTimer;
typedef struct WmMutex WmMutex;
typedef struct WmCondition WmCondition;
typedef struct WmThread WmThread;
/*==============================================================================
* Basic Type Definitions
*============================================================================*/
typedef uint32_t WmColor;
typedef uint64_t WmTime;
typedef uint32_t WmHandle;
typedef uint32_t WmId;
typedef WmId WmClientId;
typedef int WmWorkspaceId;
typedef void* WmWindowHandle;
typedef void* WmNativeHandle;
/*==============================================================================
* Window Types
*============================================================================*/
typedef enum {
WM_WINDOW_TYPE_UNKNOWN = 0,
WM_WINDOW_TYPE_NORMAL,
WM_WINDOW_TYPE_DIALOG,
WM_WINDOW_TYPE_DOCK,
WM_WINDOW_TYPE_DESKTOP,
WM_WINDOW_TYPE_TOOLBAR,
WM_WINDOW_TYPE_MENU,
WM_WINDOW_TYPE_UTILITY,
WM_WINDOW_TYPE_SPLASH,
WM_WINDOW_TYPE_NOTIFICATION,
WM_WINDOW_TYPE_COMBO,
WM_WINDOW_TYPE_DND,
WM_WINDOW_TYPE_POPUP_MENU,
WM_WINDOW_TYPE_TOOLTIP
} WmWindowType;
/*==============================================================================
* Geometry Types
*============================================================================*/
typedef struct {
int x;
int y;
} WmPoint;
typedef struct {
int width;
int height;
} WmSize;
typedef struct {
int x;
int y;
int width;
int height;
} WmRect;
typedef struct {
WmPoint location;
WmSize size;
} WmGeometry;
typedef struct {
int top;
int bottom;
int left;
int right;
} WmInsets;
typedef struct {
float x;
float y;
} WmPointF;
typedef struct {
float width;
float height;
} WmSizeF;
typedef struct {
WmPointF location;
WmSizeF size;
} WmGeometryF;
/*==============================================================================
* Rectangle Operations (Inline)
*============================================================================*/
static inline WmRect wm_rect_make(int x, int y, int width, int height) {
WmRect r = { x, y, width, height };
return r;
}
static inline bool wm_rect_is_empty(const WmRect *r) {
return r == NULL || r->width <= 0 || r->height <= 0;
}
static inline bool wm_rect_contains_point(const WmRect *r, int x, int y) {
return r != NULL &&
x >= r->x && x < r->x + r->width &&
y >= r->y && y < r->y + r->height;
}
static inline bool wm_rect_intersects(const WmRect *a, const WmRect *b) {
if (a == NULL || b == NULL) return false;
return !(a->x + a->width <= b->x ||
b->x + b->width <= a->x ||
a->y + a->height <= b->y ||
b->y + b->height <= a->y);
}
static inline WmRect wm_rect_intersection(const WmRect *a, const WmRect *b) {
WmRect r = { 0, 0, 0, 0 };
if (a == NULL || b == NULL) return r;
int x1 = a->x > b->x ? a->x : b->x;
int y1 = a->y > b->y ? a->y : b->y;
int x2 = (a->x + a->width) < (b->x + b->width) ? (a->x + a->width) : (b->x + b->width);
int y2 = (a->y + a->height) < (b->y + b->height) ? (a->y + a->height) : (b->y + b->height);
if (x2 > x1 && y2 > y1) {
r.x = x1;
r.y = y1;
r.width = x2 - x1;
r.height = y2 - y1;
}
return r;
}
static inline WmRect wm_rect_union(const WmRect *a, const WmRect *b) {
WmRect r = { 0, 0, 0, 0 };
if (a == NULL && b == NULL) return r;
if (a == NULL) return *b;
if (b == NULL) return *a;
int x1 = a->x < b->x ? a->x : b->x;
int y1 = a->y < b->y ? a->y : b->y;
int x2 = (a->x + a->width) > (b->x + b->width) ? (a->x + a->width) : (b->x + b->width);
int y2 = (a->y + a->height) > (b->y + b->height) ? (a->y + a->height) : (b->y + b->height);
r.x = x1;
r.y = y1;
r.width = x2 - x1;
r.height = y2 - y1;
return r;
}
/*==============================================================================
* Color Operations (Inline)
*============================================================================*/
static inline WmColor wm_color_rgb(uint8_t r, uint8_t g, uint8_t b) {
return (0xFF << 24) | (r << 16) | (g << 8) | b;
}
static inline WmColor wm_color_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
return (a << 24) | (r << 16) | (g << 8) | b;
}
static inline uint8_t wm_color_get_red(WmColor c) {
return (c >> 16) & 0xFF;
}
static inline uint8_t wm_color_get_green(WmColor c) {
return (c >> 8) & 0xFF;
}
static inline uint8_t wm_color_get_blue(WmColor c) {
return c & 0xFF;
}
static inline uint8_t wm_color_get_alpha(WmColor c) {
return (c >> 24) & 0xFF;
}
/*==============================================================================
* Client State Flags
*============================================================================*/
typedef enum {
WM_CLIENT_FLAG_NONE = 0,
WM_CLIENT_FLAG_FLOATING = (1 << 0),
WM_CLIENT_FLAG_FULLSCREEN = (1 << 1),
WM_CLIENT_FLAG_URGENT = (1 << 2),
WM_CLIENT_FLAG_MINIMIZED = (1 << 3),
WM_CLIENT_FLAG_STICKY = (1 << 4),
WM_CLIENT_FLAG_MAXIMIZED = (1 << 5),
WM_CLIENT_FLAG_UNMANAGING = (1 << 6),
WM_CLIENT_FLAG_FOCUSED = (1 << 7),
WM_CLIENT_FLAG_MAPPED = (1 << 8),
WM_CLIENT_FLAG_DECORATED = (1 << 9),
WM_CLIENT_FLAG_DIALOG = (1 << 10),
WM_CLIENT_FLAG_DOCK = (1 << 11),
WM_CLIENT_FLAG_UTILITY = (1 << 12),
WM_CLIENT_FLAG_SPLASH = (1 << 13),
WM_CLIENT_FLAG_MENU = (1 << 14),
WM_CLIENT_FLAG_TOOLBAR = (1 << 15)
} WmClientFlags;
/*==============================================================================
* Layout Types
*============================================================================*/
typedef enum {
WM_LAYOUT_TYPE_TILING,
WM_LAYOUT_TYPE_FLOATING,
WM_LAYOUT_TYPE_MONOCLE,
WM_LAYOUT_TYPE_GRID,
WM_LAYOUT_TYPE_SPIRAL,
WM_LAYOUT_TYPE_DWINDLE,
WM_LAYOUT_TYPE_MAX
} WmLayoutType;
/*==============================================================================
* Panel Positions
*============================================================================*/
typedef enum {
WM_PANEL_POSITION_TOP,
WM_PANEL_POSITION_BOTTOM,
WM_PANEL_POSITION_LEFT,
WM_PANEL_POSITION_RIGHT
} WmPanelPosition;
/*==============================================================================
* Event Types
*============================================================================*/
typedef enum {
/* Window events */
WM_EVENT_NONE = 0,
WM_EVENT_WINDOW_CREATED,
WM_EVENT_WINDOW_DESTROYED,
WM_EVENT_WINDOW_MAPPED,
WM_EVENT_WINDOW_UNMAPPED,
WM_EVENT_WINDOW_CONFIGURED,
WM_EVENT_WINDOW_FOCUSED,
WM_EVENT_WINDOW_UNFOCUSED,
WM_EVENT_WINDOW_RAISED,
WM_EVENT_WINDOW_LOWERED,
WM_EVENT_WINDOW_MINIMIZED,
WM_EVENT_WINDOW_RESTORED,
WM_EVENT_WINDOW_MAXIMIZED,
WM_EVENT_WINDOW_UNMAXIMIZED,
WM_EVENT_WINDOW_FULLSCREENED,
WM_EVENT_WINDOW_UNFULLSCREENED,
WM_EVENT_WINDOW_FLOATING_CHANGED,
WM_EVENT_WINDOW_PROPERTY_CHANGED,
WM_EVENT_WINDOW_STATE_CHANGED,
WM_EVENT_WINDOW_TITLE_CHANGED,
WM_EVENT_WINDOW_CLASS_CHANGED,
WM_EVENT_WINDOW_ROLE_CHANGED,
WM_EVENT_WINDOW_URGENCY_CHANGED,
WM_EVENT_WINDOW_MOVED,
WM_EVENT_WINDOW_RESIZED,
/* Workspace events */
WM_EVENT_WORKSPACE_CREATED,
WM_EVENT_WORKSPACE_DESTROYED,
WM_EVENT_WORKSPACE_SWITCHED,
WM_EVENT_WORKSPACE_RENAMED,
WM_EVENT_WORKSPACE_LAYOUT_CHANGED,
WM_EVENT_WORKSPACE_MASTER_RATIO_CHANGED,
WM_EVENT_WORKSPACE_MASTER_COUNT_CHANGED,
/* Client/workspace relationship */
WM_EVENT_CLIENT_ADDED_TO_WORKSPACE,
WM_EVENT_CLIENT_REMOVED_FROM_WORKSPACE,
/* Input events */
WM_EVENT_KEY_PRESSED,
WM_EVENT_KEY_RELEASED,
WM_EVENT_BUTTON_PRESSED,
WM_EVENT_BUTTON_RELEASED,
WM_EVENT_MOTION,
WM_EVENT_ENTER,
WM_EVENT_LEAVE,
WM_EVENT_SCROLL,
/* System events */
WM_EVENT_MONITOR_ADDED,
WM_EVENT_MONITOR_REMOVED,
WM_EVENT_MONITOR_CONFIGURED,
WM_EVENT_SCREEN_RESIZED,
/* Configuration events */
WM_EVENT_CONFIG_RELOADED,
WM_EVENT_CONFIG_CHANGED,
/* Lifecycle events */
WM_EVENT_WM_STARTED,
WM_EVENT_WM_SHUTDOWN,
WM_EVENT_WM_READY,
/* Plugin events */
WM_EVENT_PLUGIN_LOADED,
WM_EVENT_PLUGIN_UNLOADED,
WM_EVENT_PLUGIN_ERROR,
/* Custom events for plugins */
WM_EVENT_CUSTOM = 1000
} WmEventType;
/*==============================================================================
* Container Types
*============================================================================*/
typedef enum {
WM_CONTAINER_LIST,
WM_CONTAINER_ARRAY,
WM_CONTAINER_HASHMAP,
WM_CONTAINER_QUEUE,
WM_CONTAINER_STACK,
WM_CONTAINER_TREE
} WmContainerType;
/*==============================================================================
* Command Types
*============================================================================*/
typedef enum {
WM_COMMAND_NONE = 0,
/* Window commands */
WM_COMMAND_WINDOW_FOCUS,
WM_COMMAND_WINDOW_MOVE,
WM_COMMAND_WINDOW_RESIZE,
WM_COMMAND_WINDOW_MOVE_RESIZE,
WM_COMMAND_WINDOW_CLOSE,
WM_COMMAND_WINDOW_KILL,
WM_COMMAND_WINDOW_MINIMIZE,
WM_COMMAND_WINDOW_RESTORE,
WM_COMMAND_WINDOW_MAXIMIZE,
WM_COMMAND_WINDOW_UNMAXIMIZE,
WM_COMMAND_WINDOW_FULLSCREEN,
WM_COMMAND_WINDOW_UNFULLSCREEN,
WM_COMMAND_WINDOW_FLOAT,
WM_COMMAND_WINDOW_UNFLOAT,
WM_COMMAND_WINDOW_RAISE,
WM_COMMAND_WINDOW_LOWER,
WM_COMMAND_WINDOW_SET_WORKSPACE,
/* Workspace commands */
WM_COMMAND_WORKSPACE_SWITCH,
WM_COMMAND_WORKSPACE_CREATE,
WM_COMMAND_WORKSPACE_DESTROY,
WM_COMMAND_WORKSPACE_RENAME,
WM_COMMAND_WORKSPACE_SET_LAYOUT,
WM_COMMAND_WORKSPACE_ADJUST_MASTER_RATIO,
WM_COMMAND_WORKSPACE_ADJUST_MASTER_COUNT,
WM_COMMAND_WORKSPACE_SWAP,
/* Client manipulation */
WM_COMMAND_CLIENT_SWAP,
WM_COMMAND_CLIENT_SWAP_MASTER,
WM_COMMAND_CLIENT_CYCLE,
WM_COMMAND_CLIENT_CYCLE_REVERSE,
/* System commands */
WM_COMMAND_RELOAD_CONFIG,
WM_COMMAND_QUIT,
WM_COMMAND_RESTART,
/* Plugin commands */
WM_COMMAND_PLUGIN_LOAD,
WM_COMMAND_PLUGIN_UNLOAD,
WM_COMMAND_PLUGIN_ENABLE,
WM_COMMAND_PLUGIN_DISABLE
} WmCommandType;
/*==============================================================================
* Result/Error Types
*============================================================================*/
typedef enum {
WM_RESULT_OK = 0,
WM_RESULT_ERROR_GENERIC = -1,
WM_RESULT_ERROR_INVALID_ARG = -2,
WM_RESULT_ERROR_NO_MEMORY = -3,
WM_RESULT_ERROR_NOT_FOUND = -4,
WM_RESULT_ERROR_EXISTS = -5,
WM_RESULT_ERROR_PERMISSION = -6,
WM_RESULT_ERROR_NOT_SUPPORTED = -7,
WM_RESULT_ERROR_NOT_IMPLEMENTED = -8,
WM_RESULT_ERROR_BACKEND = -9,
WM_RESULT_ERROR_PLUGIN = -10,
WM_RESULT_ERROR_TIMEOUT = -11,
WM_RESULT_ERROR_CANCELED = -12
} WmResult;
/*==============================================================================
* Callback Types
*============================================================================*/
typedef void (*WmDestroyFunc)(void *data);
typedef int (*WmCompareFunc)(const void *a, const void *b);
typedef uint32_t (*WmHashFunc)(const void *key);
typedef void* (*WmCloneFunc)(const void *data);
typedef void (*WmEventHandler)(WmEventType type, const void *event_data, void *user_data);
typedef void (*WmTimerCallback)(WmTimer *timer, void *user_data);
typedef void* (*WmThreadFunc)(void *arg);
/*==============================================================================
* Memory Management Types
*============================================================================*/
typedef void* (*WmAllocFunc)(size_t size);
typedef void* (*WmReallocFunc)(void *ptr, size_t old_size, size_t new_size);
typedef void (*WmFreeFunc)(void *ptr);
typedef struct {
WmAllocFunc alloc;
WmReallocFunc realloc;
WmFreeFunc free;
} WmAllocator;
/*==============================================================================
* Logging Types
*============================================================================*/
typedef enum {
WM_LOG_LEVEL_DEBUG,
WM_LOG_LEVEL_INFO,
WM_LOG_LEVEL_WARN,
WM_LOG_LEVEL_ERROR,
WM_LOG_LEVEL_FATAL
} WmLogLevel;
typedef void (*WmLogHandler)(WmLogLevel level, const char *file, int line,
const char *func, const char *message);
#ifdef __cplusplus
}
#endif
#endif /* WM_TYPES_H */
+161
View File
@@ -0,0 +1,161 @@
/*
* DWN - Desktop Window Manager
* Abstract Workspace Interface
*/
#ifndef WM_WORKSPACE_H
#define WM_WORKSPACE_H
#include "wm_types.h"
#include "wm_client.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Workspace Configuration
*============================================================================*/
#define WM_MAX_WORKSPACES 32
typedef struct {
WmLayoutType layout_type;
float master_ratio;
int master_count;
char name[64];
bool persistent;
WmString *custom_data;
} WmWorkspaceConfig;
/*==============================================================================
* Workspace Lifecycle
*============================================================================*/
bool wm_workspace_exists(WmCore *core, WmWorkspaceId id);
WmWorkspaceId wm_workspace_create(WmCore *core, const char *name);
void wm_workspace_destroy(WmCore *core, WmWorkspaceId id);
void wm_workspace_init_all(WmCore *core);
void wm_workspace_cleanup_all(WmCore *core);
/*==============================================================================
* Workspace Properties
*============================================================================*/
const char* wm_workspace_get_name(WmCore *core, WmWorkspaceId id);
void wm_workspace_set_name(WmCore *core, WmWorkspaceId id, const char *name);
bool wm_workspace_is_empty(WmCore *core, WmWorkspaceId id);
size_t wm_workspace_get_client_count(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* Current Workspace
*============================================================================*/
WmWorkspaceId wm_workspace_get_current(WmCore *core);
void wm_workspace_set_current(WmCore *core, WmWorkspaceId id);
void wm_workspace_switch(WmCore *core, WmWorkspaceId id);
void wm_workspace_switch_next(WmCore *core);
void wm_workspace_switch_prev(WmCore *core);
/*==============================================================================
* Client Management
*============================================================================*/
void wm_workspace_add_client(WmCore *core, WmWorkspaceId id, AbstractClient *client);
void wm_workspace_remove_client(WmCore *core, WmWorkspaceId id, AbstractClient *client);
void wm_workspace_move_client(WmCore *core, AbstractClient *client, WmWorkspaceId to_workspace);
WmContainer* wm_workspace_get_clients(WmCore *core, WmWorkspaceId id);
AbstractClient* wm_workspace_get_focused_client(WmCore *core, WmWorkspaceId id);
void wm_workspace_set_focused_client(WmCore *core, WmWorkspaceId id, AbstractClient *client);
AbstractClient* wm_workspace_get_first_client(WmCore *core, WmWorkspaceId id);
AbstractClient* wm_workspace_get_last_client(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* Layout Management
*============================================================================*/
WmLayoutType wm_workspace_get_layout(WmCore *core, WmWorkspaceId id);
void wm_workspace_set_layout(WmCore *core, WmWorkspaceId id, WmLayoutType layout);
void wm_workspace_cycle_layout(WmCore *core, WmWorkspaceId id);
void wm_workspace_cycle_layout_reverse(WmCore *core, WmWorkspaceId id);
float wm_workspace_get_master_ratio(WmCore *core, WmWorkspaceId id);
void wm_workspace_set_master_ratio(WmCore *core, WmWorkspaceId id, float ratio);
void wm_workspace_adjust_master_ratio(WmCore *core, WmWorkspaceId id, float delta);
int wm_workspace_get_master_count(WmCore *core, WmWorkspaceId id);
void wm_workspace_set_master_count(WmCore *core, WmWorkspaceId id, int count);
void wm_workspace_adjust_master_count(WmCore *core, WmWorkspaceId id, int delta);
/*==============================================================================
* Workspace Arrangement
*============================================================================*/
void wm_workspace_arrange(WmCore *core, WmWorkspaceId id);
void wm_workspace_arrange_all(WmCore *core);
void wm_workspace_arrange_current(WmCore *core);
WmRect wm_workspace_get_arrange_area(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* Workspace Visibility
*============================================================================*/
void wm_workspace_show(WmCore *core, WmWorkspaceId id);
void wm_workspace_hide(WmCore *core, WmWorkspaceId id);
bool wm_workspace_is_visible(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* Focus Management
*============================================================================*/
void wm_workspace_focus_next(WmCore *core, WmWorkspaceId id);
void wm_workspace_focus_prev(WmCore *core, WmWorkspaceId id);
void wm_workspace_focus_master(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* MRU (Most Recently Used) Stack
*============================================================================*/
void wm_workspace_mru_push(WmCore *core, WmWorkspaceId id, AbstractClient *client);
void wm_workspace_mru_remove(WmCore *core, WmWorkspaceId id, AbstractClient *client);
AbstractClient* wm_workspace_mru_get_next(WmCore *core, WmWorkspaceId id, AbstractClient *current);
AbstractClient* wm_workspace_mru_get_prev(WmCore *core, WmWorkspaceId id, AbstractClient *current);
/*==============================================================================
* Alt-Tab Navigation
*============================================================================*/
void wm_workspace_alt_tab_start(WmCore *core, WmWorkspaceId id);
void wm_workspace_alt_tab_next(WmCore *core, WmWorkspaceId id);
void wm_workspace_alt_tab_prev(WmCore *core, WmWorkspaceId id);
void wm_workspace_alt_tab_end(WmCore *core, WmWorkspaceId id);
bool wm_workspace_alt_tab_is_active(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* Workspace Iteration
*============================================================================*/
WmWorkspaceId wm_workspace_get_first(WmCore *core);
WmWorkspaceId wm_workspace_get_last(WmCore *core);
WmWorkspaceId wm_workspace_get_next(WmCore *core, WmWorkspaceId id);
WmWorkspaceId wm_workspace_get_prev(WmCore *core, WmWorkspaceId id);
/*==============================================================================
* Configuration
*============================================================================*/
void wm_workspace_save_config(WmCore *core, WmWorkspaceId id, const WmWorkspaceConfig *config);
bool wm_workspace_load_config(WmCore *core, WmWorkspaceId id, WmWorkspaceConfig *config);
#ifdef __cplusplus
}
#endif
#endif /* WM_WORKSPACE_H */
+68 -1
View File
@@ -25,6 +25,11 @@
#define MAX_NOTIFICATIONS 32
#define MAX_KEYBINDINGS 64
#define RESIZE_LEFT 1
#define RESIZE_RIGHT 2
#define RESIZE_TOP 4
#define RESIZE_BOTTOM 8
#define DEFAULT_BORDER_WIDTH 0
#define DEFAULT_TITLE_HEIGHT 28
#define DEFAULT_PANEL_HEIGHT 32
@@ -44,6 +49,9 @@ typedef enum {
LAYOUT_TILING,
LAYOUT_FLOATING,
LAYOUT_MONOCLE,
LAYOUT_CENTERED_MASTER,
LAYOUT_COLUMNS,
LAYOUT_FIBONACCI,
LAYOUT_COUNT
} LayoutType;
@@ -59,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 {
@@ -82,6 +91,21 @@ typedef struct {
long timestamp;
} SnapConstraint;
typedef struct {
long start_time;
float progress;
unsigned long from_color;
unsigned long to_color;
bool active;
} ColorAnimation;
typedef struct {
float phase;
float speed;
unsigned long base_color;
bool active;
} TextGlowAnimation;
typedef struct Client Client;
typedef struct Workspace Workspace;
typedef struct Monitor Monitor;
@@ -101,6 +125,10 @@ struct Client {
char title[256];
char class[64];
SnapConstraint snap;
bool floating_before_maximize;
unsigned long taskbar_color;
ColorAnimation title_anim;
TextGlowAnimation text_glow;
Client *next;
Client *prev;
Client *mru_next;
@@ -161,6 +189,7 @@ typedef struct {
GC gc;
XFontStruct *font;
XftFont *xft_font;
XftFont *xft_font_bold;
Colormap colormap;
Client *drag_client;
@@ -168,6 +197,7 @@ typedef struct {
int drag_orig_x, drag_orig_y;
int drag_orig_w, drag_orig_h;
bool resizing;
int drag_direction;
Client *pending_focus_client;
long pending_focus_time;
@@ -178,8 +208,45 @@ typedef struct {
bool desktop_shown;
Window desktop_minimized[MAX_CLIENTS];
int desktop_minimized_count;
float ambient_phase;
float ambient_speed;
long typing_activity_time;
unsigned long key_press_count;
long key_delete_flicker_time;
int xi2_opcode;
double mouse_distance_pixels;
int last_mouse_x;
int last_mouse_y;
bool mouse_tracking_initialized;
Cursor cursor_default;
Cursor cursor_resize_top_left;
Cursor cursor_resize_top_right;
Cursor cursor_resize_bottom_left;
Cursor cursor_resize_bottom_right;
Cursor cursor_resize_left;
Cursor cursor_resize_right;
Cursor cursor_resize_top;
Cursor cursor_resize_bottom;
Cursor cursor_move;
} DWNState;
#define PHASE_OFFSET_PANEL_BG 0.0f
#define PHASE_OFFSET_WORKSPACES 0.8f
#define PHASE_OFFSET_TASKBAR 1.6f
#define PHASE_OFFSET_CLOCK 2.4f
#define PHASE_OFFSET_STATS 3.2f
#define PHASE_OFFSET_TOP_MEM 4.0f
#define PHASE_OFFSET_TOP_CPU 4.4f
#define PHASE_OFFSET_DECORATIONS 4.8f
#define PHASE_OFFSET_SYSTRAY 5.6f
#define PHASE_OFFSET_NEWS 1.2f
#define PHASE_OFFSET_KEY_COUNTER 5.2f
#define PHASE_OFFSET_MOUSE_DIST 5.8f
#define PHASE_OFFSET_DISK_SPACE 6.2f
extern DWNState *dwn;
int dwn_init(void);
+12
View File
@@ -89,6 +89,18 @@ 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 key_expose_all(void);
void tutorial_start(void);
void tutorial_stop(void);
+3
View File
@@ -13,6 +13,9 @@ void layout_arrange(int workspace);
void layout_arrange_tiling(int workspace);
void layout_arrange_floating(int workspace);
void layout_arrange_monocle(int workspace);
void layout_arrange_centered_master(int workspace);
void layout_arrange_columns(int workspace);
void layout_arrange_fibonacci(int workspace);
int layout_get_usable_area(int *x, int *y, int *width, int *height);
int layout_count_tiled_clients(int workspace);
+33
View File
@@ -0,0 +1,33 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Window marks for quick navigation
*/
#ifndef DWN_MARKS_H
#define DWN_MARKS_H
#include "dwn.h"
#include <stdbool.h>
#define MAX_MARKS 26
void marks_init(void);
void marks_cleanup(void);
void marks_set(char mark, Client *client);
Client *marks_get(char mark);
void marks_clear(char mark);
void marks_clear_all(void);
void marks_remove_client(Client *client);
bool marks_is_waiting_for_mark(void);
bool marks_is_waiting_for_goto(void);
void marks_start_set_mode(void);
void marks_start_goto_mode(void);
void marks_cancel_mode(void);
bool marks_handle_key(char key);
char marks_get_mark_for_client(Client *client);
#endif
+8
View File
@@ -22,6 +22,7 @@ struct Panel {
int width, height;
bool visible;
Pixmap buffer;
bool dirty;
};
Panel *panel_create(PanelPosition position);
@@ -31,6 +32,13 @@ void panels_cleanup(void);
void panel_render(Panel *panel);
void panel_render_all(void);
void panel_invalidate(void);
void panel_invalidate_taskbar(void);
void panel_invalidate_news(void);
void panel_set_news_region(int x, int width);
void panel_render_news_only(void);
bool panel_needs_render(void);
bool panel_news_needs_render(void);
void panel_render_workspaces(Panel *panel, int x, int *width);
void panel_render_taskbar(Panel *panel, int x, int *width);
void panel_render_clock(Panel *panel, int x, int *width);
+57
View File
@@ -0,0 +1,57 @@
/*
* DWN - Desktop Window Manager
* Built-in Layout Registration
*/
#ifndef BUILTIN_LAYOUTS_H
#define BUILTIN_LAYOUTS_H
#include "plugins/layout_plugin.h"
#include "dwn.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Initialize and register all built-in layouts.
* Called during DWN startup.
*/
bool wm_layouts_builtin_init(void);
/**
* Get a layout plugin by legacy LayoutType.
*/
LayoutPlugin* wm_layout_get_by_type(LayoutManager *mgr, LayoutType type);
/**
* Bridge function to arrange a workspace using the plugin system.
*/
bool wm_layout_arrange_workspace(LayoutManager *mgr, int workspace, LayoutType type);
/**
* Get symbol for a layout type.
*/
const char* wm_layout_get_symbol_for_type(LayoutType type);
/**
* Get name for a layout type.
*/
const char* wm_layout_get_name_for_type(LayoutType type);
/**
* Get LayoutType from name.
*/
LayoutType wm_layout_type_from_name(const char *name);
/* External layout interface getters */
const LayoutPluginInterface* wm_layout_tiling_get_interface(void);
const LayoutPluginInterface* wm_layout_floating_get_interface(void);
const LayoutPluginInterface* wm_layout_monocle_get_interface(void);
const LayoutPluginInterface* wm_layout_grid_get_interface(void);
#ifdef __cplusplus
}
#endif
#endif /* BUILTIN_LAYOUTS_H */
+365
View File
@@ -0,0 +1,365 @@
/*
* DWN - Desktop Window Manager
* Layout Plugin Interface
*/
#ifndef LAYOUT_PLUGIN_H
#define LAYOUT_PLUGIN_H
#include "core/wm_types.h"
#include "core/wm_client.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Layout Plugin API Version
*============================================================================*/
#define WM_LAYOUT_PLUGIN_API_VERSION 1
/*==============================================================================
* Forward Declarations
*============================================================================*/
typedef struct LayoutPlugin LayoutPlugin;
typedef struct LayoutContext LayoutContext;
/*==============================================================================
* Layout Geometry
*============================================================================*/
/**
* Layout geometry for a single client.
*/
typedef struct {
int x, y;
int width, height;
bool floating; /* If true, layout doesn't manage this client */
bool fullscreen; /* If true, client should be fullscreen */
bool hidden; /* If true, client should be hidden */
} WmLayoutGeometry;
/**
* Work area for layout calculations.
*/
typedef struct {
int x, y;
int width, height;
int padding_top;
int padding_bottom;
int padding_left;
int padding_right;
int gap;
} WmLayoutWorkArea;
/*==============================================================================
* Layout Configuration
*============================================================================*/
/**
* Layout configuration parameter.
*/
typedef struct {
const char *name;
const char *description;
union {
int int_value;
float float_value;
bool bool_value;
const char *string_value;
} default_value;
enum {
WM_LAYOUT_PARAM_INT,
WM_LAYOUT_PARAM_FLOAT,
WM_LAYOUT_PARAM_BOOL,
WM_LAYOUT_PARAM_STRING,
WM_LAYOUT_PARAM_CHOICE
} type;
/* For PARAM_CHOICE */
const char **choices;
int num_choices;
} WmLayoutParameter;
/**
* Layout configuration instance.
*/
typedef struct {
WmLayoutParameter *parameters;
int num_parameters;
void *user_data; /* Plugin can store parsed config here */
} WmLayoutConfig;
/*==============================================================================
* Layout State
*============================================================================*/
/**
* Opaque layout state per workspace.
* Layout plugins can store workspace-specific state here.
*/
typedef struct WmLayoutState WmLayoutState;
/*==============================================================================
* Layout Context
*============================================================================*/
/**
* Context passed to layout operations.
*/
struct LayoutContext {
/* Workspace info */
WmWorkspaceId workspace;
WmLayoutWorkArea work_area;
/* Client counts */
int client_count;
int master_count;
int stack_count;
/* Layout parameters */
float master_ratio;
int master_count_target;
int gap;
/* Configuration */
WmLayoutConfig *config;
/* Layout state (managed by plugin) */
WmLayoutState *state;
/* User data (for plugin use) */
void *user_data;
};
/*==============================================================================
* Layout Plugin Interface
*============================================================================*/
/**
* Layout plugin interface vtable.
* All layouts must implement these functions.
*/
typedef struct {
/* Version check */
int api_version;
/* Metadata */
const char *name;
const char *description;
const char *author;
const char *version;
/* Configuration */
WmLayoutParameter *parameters;
int num_parameters;
/* Lifecycle */
bool (*init)(LayoutPlugin *plugin);
void (*shutdown)(LayoutPlugin *plugin);
/* State management */
WmLayoutState* (*state_create)(LayoutPlugin *plugin, WmWorkspaceId workspace);
void (*state_destroy)(LayoutPlugin *plugin, WmLayoutState *state);
void (*state_reset)(LayoutPlugin *plugin, WmLayoutState *state);
/* Configuration */
bool (*config_parse)(LayoutPlugin *plugin, WmLayoutConfig *config,
const char *key, const char *value);
void (*config_apply)(LayoutPlugin *plugin, WmLayoutConfig *config);
/* Layout calculation */
bool (*arrange)(LayoutPlugin *plugin, LayoutContext *ctx,
AbstractClient **clients, int num_clients,
WmLayoutGeometry *geometries);
/* Client operations */
void (*client_added)(LayoutPlugin *plugin, LayoutContext *ctx,
AbstractClient *client);
void (*client_removed)(LayoutPlugin *plugin, LayoutContext *ctx,
AbstractClient *client);
void (*client_floating_changed)(LayoutPlugin *plugin, LayoutContext *ctx,
AbstractClient *client, bool floating);
/* Layout manipulation */
void (*inc_master)(LayoutPlugin *plugin, LayoutContext *ctx, int delta);
void (*set_master_ratio)(LayoutPlugin *plugin, LayoutContext *ctx, float ratio);
void (*set_master_count)(LayoutPlugin *plugin, LayoutContext *ctx, int count);
void (*swap_master)(LayoutPlugin *plugin, LayoutContext *ctx);
void (*rotate)(LayoutPlugin *plugin, LayoutContext *ctx, bool clockwise);
/* Mouse resizing support */
void (*resize_master)(LayoutPlugin *plugin, LayoutContext *ctx,
int x, int y, int *new_master_count, float *new_ratio);
} LayoutPluginInterface;
/*==============================================================================
* Layout Plugin Structure
*============================================================================*/
/**
* Layout plugin instance.
*/
struct LayoutPlugin {
/* Interface vtable */
const LayoutPluginInterface *interface;
/* Plugin handle (for dynamic loading) */
void *handle;
/* Plugin path */
char *path;
/* Enabled state */
bool enabled;
/* Global configuration */
WmLayoutConfig config;
/* Per-workspace states */
WmLayoutState **workspace_states;
int num_workspaces;
/* Plugin-private data */
void *user_data;
};
/*==============================================================================
* Layout Manager
*============================================================================*/
/**
* Layout manager handles all loaded layouts.
*/
typedef struct LayoutManager LayoutManager;
/**
* Get the global layout manager.
*/
LayoutManager* wm_layout_manager_get(void);
/**
* Initialize the layout manager.
*/
bool wm_layout_manager_init(void);
/**
* Shutdown the layout manager.
*/
void wm_layout_manager_shutdown(void);
/**
* Register a layout plugin.
*/
bool wm_layout_manager_register(LayoutManager *mgr, LayoutPlugin *plugin);
/**
* Unregister a layout plugin.
*/
void wm_layout_manager_unregister(LayoutManager *mgr, LayoutPlugin *plugin);
/**
* Load a layout plugin from a file.
*/
LayoutPlugin* wm_layout_manager_load(LayoutManager *mgr, const char *path);
/**
* Unload a layout plugin.
*/
void wm_layout_manager_unload(LayoutManager *mgr, LayoutPlugin *plugin);
/**
* Get a loaded layout by name.
*/
LayoutPlugin* wm_layout_manager_get_layout(LayoutManager *mgr, const char *name);
/**
* Get the default layout.
*/
LayoutPlugin* wm_layout_manager_get_default(LayoutManager *mgr);
/**
* Set the default layout.
*/
void wm_layout_manager_set_default(LayoutManager *mgr, LayoutPlugin *plugin);
/**
* Get list of available layouts.
*/
const char** wm_layout_manager_list(LayoutManager *mgr, int *count);
/*==============================================================================
* Layout Operations
*============================================================================*/
/**
* Apply a layout to a workspace.
*/
bool wm_layout_arrange(LayoutPlugin *plugin, WmWorkspaceId workspace);
/**
* Apply layout to specific clients.
*/
bool wm_layout_arrange_clients(LayoutPlugin *plugin, LayoutContext *ctx,
AbstractClient **clients, int num_clients);
/**
* Create a layout context for a workspace.
*/
bool wm_layout_context_init(LayoutContext *ctx, WmWorkspaceId workspace);
/**
* Clean up a layout context.
*/
void wm_layout_context_cleanup(LayoutContext *ctx);
/*==============================================================================
* Built-in Layouts
*============================================================================*/
/* Tiling layout */
extern const LayoutPluginInterface wm_layout_tiling;
/* Floating layout */
extern const LayoutPluginInterface wm_layout_floating;
/* Monocle (maximized) layout */
extern const LayoutPluginInterface wm_layout_monocle;
/* Grid layout */
extern const LayoutPluginInterface wm_layout_grid;
/* Spiral layout */
extern const LayoutPluginInterface wm_layout_spiral;
/* Dwindle layout */
extern const LayoutPluginInterface wm_layout_dwindle;
/*==============================================================================
* Plugin Entry Point
*============================================================================*/
/**
* Layout plugins must export this function.
*/
typedef const LayoutPluginInterface* (*LayoutPluginEntryFunc)(void);
#define WM_LAYOUT_PLUGIN_ENTRY "wm_layout_plugin_entry"
/*==============================================================================
* Helper Macros
*============================================================================*/
#define WM_LAYOUT_REGISTER(name, iface) \
__attribute__((unused)) static const LayoutPluginInterface* wm_layout_plugin_entry(void) { return &(iface); }
/* Typedef for built-in layout getters */
typedef const LayoutPluginInterface* (*WmLayoutBuiltinFunc)(void);
#ifdef __cplusplus
}
#endif
#endif /* LAYOUT_PLUGIN_H */
+435
View File
@@ -0,0 +1,435 @@
/*
* DWN - Desktop Window Manager
* Widget Plugin Interface
* Panel components (taskbar, clock, workspaces, etc.)
*/
#ifndef WIDGET_PLUGIN_H
#define WIDGET_PLUGIN_H
#include "core/wm_types.h"
#include "core/wm_client.h"
#ifdef __cplusplus
extern "C" {
#endif
/*==============================================================================
* Widget Plugin API Version
*============================================================================*/
#define WM_WIDGET_PLUGIN_API_VERSION 1
/*==============================================================================
* Forward Declarations
*============================================================================*/
typedef struct WidgetPlugin WidgetPlugin;
typedef struct WidgetContext WidgetContext;
typedef struct WidgetInstance WidgetInstance;
/*==============================================================================
* Widget Position
*============================================================================*/
typedef enum {
WM_WIDGET_POSITION_LEFT,
WM_WIDGET_POSITION_CENTER,
WM_WIDGET_POSITION_RIGHT
} WmWidgetPosition;
/*==============================================================================
* Widget Size Constraints
*============================================================================*/
typedef struct {
int min_width;
int min_height;
int max_width;
int max_height;
bool expand_horizontal; /* Take available horizontal space */
bool expand_vertical; /* Take available vertical space */
} WmWidgetConstraints;
/*==============================================================================
* Widget Geometry
*============================================================================*/
typedef struct {
int x, y;
int width, height;
} WmWidgetGeometry;
/*==============================================================================
* Widget State
*============================================================================*/
typedef enum {
WM_WIDGET_STATE_NORMAL = 0,
WM_WIDGET_STATE_HOVERED = (1 << 0),
WM_WIDGET_STATE_PRESSED = (1 << 1),
WM_WIDGET_STATE_ACTIVE = (1 << 2),
WM_WIDGET_STATE_DISABLED = (1 << 3),
WM_WIDGET_STATE_HIDDEN = (1 << 4),
WM_WIDGET_STATE_URGENT = (1 << 5)
} WmWidgetState;
/*==============================================================================
* Widget Context
*============================================================================*/
struct WidgetContext {
/* Position in panel */
WmWidgetPosition position;
/* Geometry */
WmWidgetGeometry geometry;
WmWidgetConstraints constraints;
/* Panel info */
WmRect panel_geometry;
bool is_top_panel;
/* Rendering context */
void *render_context; /* Backend-specific (X11 GC, cairo, etc.) */
WmColor bg_color;
WmColor fg_color;
/* Font */
void *font; /* Opaque font handle */
int font_size;
/* User data */
void *user_data;
};
/*==============================================================================
* Widget Event Types
*============================================================================*/
typedef enum {
WM_WIDGET_EVENT_NONE,
WM_WIDGET_EVENT_MOUSE_ENTER,
WM_WIDGET_EVENT_MOUSE_LEAVE,
WM_WIDGET_EVENT_MOUSE_MOVE,
WM_WIDGET_EVENT_BUTTON_PRESS,
WM_WIDGET_EVENT_BUTTON_RELEASE,
WM_WIDGET_EVENT_SCROLL,
WM_WIDGET_EVENT_KEY_PRESS,
WM_WIDGET_EVENT_FOCUS_IN,
WM_WIDGET_EVENT_FOCUS_OUT,
WM_WIDGET_EVENT_UPDATE, /* Request to redraw */
WM_WIDGET_EVENT_CONFIGURE, /* Size/position changed */
WM_WIDGET_EVENT_DATA_CHANGED /* External data changed */
} WmWidgetEventType;
typedef struct {
WmWidgetEventType type;
WmWidgetGeometry widget_geometry;
union {
struct { int x, y; } point;
struct { int button; int x, y; } button;
struct { int delta; bool horizontal; } scroll;
struct { unsigned int keycode; unsigned int state; } key;
} data;
} WmWidgetEvent;
/*==============================================================================
* Widget Configuration
*============================================================================*/
typedef struct {
const char *name;
const char *value;
} WmWidgetConfigOption;
typedef struct {
WmWidgetConfigOption *options;
int num_options;
} WmWidgetConfig;
/*==============================================================================
* Widget Information
*============================================================================*/
typedef struct {
const char *id; /* Unique identifier */
const char *name; /* Display name */
const char *description; /* Description */
const char *author; /* Author */
const char *version; /* Version */
/* Default constraints */
WmWidgetConstraints default_constraints;
/* Default position */
WmWidgetPosition default_position;
/* Configuration schema */
const char **config_options; /* List of accepted config option names */
int num_config_options;
} WmWidgetInfo;
/*==============================================================================
* Widget Plugin Interface
*============================================================================*/
typedef struct {
/* Version check */
int api_version;
/* Metadata */
WmWidgetInfo info;
/* Lifecycle */
bool (*init)(WidgetPlugin *plugin);
void (*shutdown)(WidgetPlugin *plugin);
/* Instance management */
WidgetInstance* (*instance_create)(WidgetPlugin *plugin,
const WmWidgetConfig *config);
void (*instance_destroy)(WidgetPlugin *plugin, WidgetInstance *instance);
void (*instance_configure)(WidgetPlugin *plugin, WidgetInstance *instance,
const WmWidgetConfig *config);
/* Rendering */
void (*render)(WidgetPlugin *plugin, WidgetInstance *instance,
WidgetContext *ctx);
/* Event handling */
bool (*handle_event)(WidgetPlugin *plugin, WidgetInstance *instance,
const WmWidgetEvent *event);
/* Update notification */
void (*update)(WidgetPlugin *plugin, WidgetInstance *instance);
/* Size calculation */
void (*get_preferred_size)(WidgetPlugin *plugin, WidgetInstance *instance,
int *width, int *height);
} WidgetPluginInterface;
/*==============================================================================
* Widget Instance
*============================================================================*/
struct WidgetInstance {
/* Plugin that created this instance */
WidgetPlugin *plugin;
/* Instance state */
WmWidgetState state;
WmWidgetGeometry geometry;
WmWidgetConstraints constraints;
/* Configuration */
WmWidgetConfig config;
/* Context */
WidgetContext *context;
/* Plugin-private data */
void *user_data;
/* Linked list for panel */
WidgetInstance *next;
WidgetInstance *prev;
};
/*==============================================================================
* Widget Plugin Structure
*============================================================================*/
struct WidgetPlugin {
/* Interface vtable */
const WidgetPluginInterface *interface;
/* Plugin handle (for dynamic loading) */
void *handle;
/* Plugin path */
char *path;
/* Enabled state */
bool enabled;
/* Instances */
WidgetInstance *instances;
int instance_count;
/* Plugin-private data */
void *user_data;
};
/*==============================================================================
* Widget Manager
*============================================================================*/
typedef struct WidgetManager WidgetManager;
/**
* Get the global widget manager.
*/
WidgetManager* wm_widget_manager_get(void);
/**
* Initialize the widget manager.
*/
bool wm_widget_manager_init(void);
/**
* Shutdown the widget manager.
*/
void wm_widget_manager_shutdown(void);
/**
* Register a widget plugin.
*/
bool wm_widget_manager_register(WidgetManager *mgr, WidgetPlugin *plugin);
/**
* Unregister a widget plugin.
*/
void wm_widget_manager_unregister(WidgetManager *mgr, WidgetPlugin *plugin);
/**
* Load a widget plugin from a file.
*/
WidgetPlugin* wm_widget_manager_load(WidgetManager *mgr, const char *path);
/**
* Unload a widget plugin.
*/
void wm_widget_manager_unload(WidgetManager *mgr, WidgetPlugin *plugin);
/**
* Get a loaded widget by ID.
*/
WidgetPlugin* wm_widget_manager_get_widget(WidgetManager *mgr, const char *id);
/**
* Get list of available widgets.
*/
const char** wm_widget_manager_list(WidgetManager *mgr, int *count);
/*==============================================================================
* Widget Instance Operations
*============================================================================*/
/**
* Create a widget instance.
*/
WidgetInstance* wm_widget_create_instance(WidgetPlugin *plugin,
const WmWidgetConfig *config);
/**
* Destroy a widget instance.
*/
void wm_widget_destroy_instance(WidgetInstance *instance);
/**
* Render a widget instance.
*/
void wm_widget_render(WidgetInstance *instance, WidgetContext *ctx);
/**
* Send an event to a widget instance.
*/
bool wm_widget_send_event(WidgetInstance *instance, const WmWidgetEvent *event);
/**
* Update a widget instance (request redraw).
*/
void wm_widget_update(WidgetInstance *instance);
/**
* Get preferred size for a widget instance.
*/
void wm_widget_get_preferred_size(WidgetInstance *instance,
int *width, int *height);
/*==============================================================================
* Widget Panel Integration
*============================================================================*/
typedef struct WidgetPanel WidgetPanel;
/**
* Create a widget panel (container for widget instances).
*/
WidgetPanel* wm_widget_panel_create(void);
/**
* Destroy a widget panel.
*/
void wm_widget_panel_destroy(WidgetPanel *panel);
/**
* Add a widget instance to a panel.
*/
void wm_widget_panel_add(WidgetPanel *panel, WidgetInstance *instance,
WmWidgetPosition position);
/**
* Remove a widget instance from a panel.
*/
void wm_widget_panel_remove(WidgetPanel *panel, WidgetInstance *instance);
/**
* Arrange widgets in a panel.
*/
void wm_widget_panel_arrange(WidgetPanel *panel, const WmRect *panel_geometry);
/**
* Render all widgets in a panel.
*/
void wm_widget_panel_render(WidgetPanel *panel);
/**
* Handle mouse event in a panel.
*/
bool wm_widget_panel_handle_event(WidgetPanel *panel,
const WmWidgetEvent *event);
/*==============================================================================
* Built-in Widgets
*============================================================================*/
/* Workspace switcher widget */
extern const WidgetPluginInterface wm_widget_workspaces;
/* Taskbar widget */
extern const WidgetPluginInterface wm_widget_taskbar;
/* Clock widget */
extern const WidgetPluginInterface wm_widget_clock;
/* System tray widget */
extern const WidgetPluginInterface wm_widget_systray;
/* CPU/Memory monitor widget */
extern const WidgetPluginInterface wm_widget_system_stats;
/* News ticker widget */
extern const WidgetPluginInterface wm_widget_news;
/*==============================================================================
* Plugin Entry Point
*============================================================================*/
typedef const WidgetPluginInterface* (*WidgetPluginEntryFunc)(void);
#define WM_WIDGET_PLUGIN_ENTRY "wm_widget_plugin_entry"
/*==============================================================================
* Helper Macros
*============================================================================*/
#define WM_WIDGET_REGISTER(name, iface) \
static const WidgetPluginInterface* wm_widget_plugin_entry(void) { return &(iface); }
#ifdef __cplusplus
}
#endif
#endif /* WIDGET_PLUGIN_H */
+61
View File
@@ -0,0 +1,61 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Window rules engine for auto-applying settings
*/
#ifndef DWN_RULES_H
#define DWN_RULES_H
#include "dwn.h"
#include <stdbool.h>
#define MAX_RULES 64
#define RULE_PATTERN_SIZE 128
typedef enum {
RULE_MATCH_CLASS,
RULE_MATCH_TITLE,
RULE_MATCH_CLASS_AND_TITLE
} RuleMatchType;
typedef struct {
RuleMatchType match_type;
char class_pattern[RULE_PATTERN_SIZE];
char title_pattern[RULE_PATTERN_SIZE];
bool has_workspace;
int workspace;
bool has_floating;
bool floating;
bool has_sticky;
bool sticky;
bool has_fullscreen;
bool fullscreen;
bool has_size;
int width;
int height;
bool has_position;
int x;
int y;
bool has_opacity;
float opacity;
} WindowRule;
void rules_init(void);
void rules_cleanup(void);
bool rules_load(const char *path);
void rules_add(const WindowRule *rule);
void rules_clear(void);
int rules_count(void);
bool rules_apply_to_client(Client *client);
bool rules_match_pattern(const char *str, const char *pattern);
#endif
+72
View File
@@ -0,0 +1,72 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Generic slider widget for panel controls
*/
#ifndef DWN_SLIDER_H
#define DWN_SLIDER_H
#include "dwn.h"
#include <stdbool.h>
#define SLIDER_WIDTH 30
#define SLIDER_HEIGHT 120
#define SLIDER_PADDING 8
#define SLIDER_KNOB_HEIGHT 8
/* Forward declaration */
typedef struct GenericSlider GenericSlider;
/* Callback type for value changes */
typedef void (*SliderValueChangedCallback)(GenericSlider *slider, int value);
/* Generic slider structure */
struct GenericSlider {
Window window;
int x, y;
int width, height;
bool visible;
bool dragging;
/* Value (always 0-100 internally) */
int value;
/* Display */
const char *label_prefix;
bool show_percentage;
/* Callback */
SliderValueChangedCallback on_value_changed;
void *user_data;
};
/* Lifecycle */
GenericSlider *slider_create(int x, int y,
const char *label_prefix,
bool show_percentage,
SliderValueChangedCallback callback,
void *user_data);
void slider_destroy(GenericSlider *slider);
/* Visibility */
void slider_show(GenericSlider *slider, int x, int y);
void slider_hide(GenericSlider *slider);
bool slider_is_visible(const GenericSlider *slider);
/* Rendering */
void slider_render(GenericSlider *slider);
/* Event handling */
void slider_handle_click(GenericSlider *slider, int x, int y);
void slider_handle_motion(GenericSlider *slider, int x, int y);
void slider_handle_release(GenericSlider *slider);
/* Value */
void slider_set_value(GenericSlider *slider, int value);
int slider_get_value(const GenericSlider *slider);
/* Hit testing */
bool slider_hit_test(const GenericSlider *slider, int x, int y);
#endif
+39
View File
@@ -8,9 +8,11 @@
#define DWN_SYSTRAY_H
#include "dwn.h"
#include "slider.h"
#include <stdbool.h>
#define MAX_TRAY_ICONS 32
#define MAX_BATTERIES 4
#define TRAY_ICON_SIZE 22
#define TRAY_ICON_SPACING 4
@@ -59,6 +61,22 @@ typedef struct {
int time_remaining;
} BatteryState;
typedef struct {
char name[32];
bool present;
bool charging;
int percentage;
unsigned long energy_now;
unsigned long energy_full;
} SingleBattery;
typedef struct {
int count;
SingleBattery batteries[MAX_BATTERIES];
bool any_charging;
int combined_percentage;
} MultiBatteryState;
typedef struct {
Window window;
int x, y;
@@ -67,11 +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);
@@ -95,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);
@@ -106,6 +144,7 @@ void systray_lock(void);
void systray_unlock(void);
BatteryState systray_get_battery_snapshot(void);
MultiBatteryState systray_get_multi_battery_snapshot(void);
AudioState systray_get_audio_snapshot(void);
void xembed_init(void);
+263
View File
@@ -0,0 +1,263 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Specialized Worker Threads - High-Level API
*
* Pre-built worker types for common async operations:
* - I/O Worker: File operations, networking
* - Compute Worker: CPU-intensive tasks
* - System Worker: /proc monitoring, system stats
* - Deferred Worker: Delayed/scheduled execution
*/
#ifndef DWN_THREAD_WORKERS_H
#define DWN_THREAD_WORKERS_H
#include "threading.h"
/* ============================================================================
* I/O Worker - Async File and Network Operations
* ============================================================================ */
typedef void (*IoCallback)(void *result, size_t size, int error, void *user_data);
typedef void (*IoProgressCallback)(size_t processed, size_t total, void *user_data);
/**
* Initialize I/O worker subsystem
*/
bool io_worker_init(void);
/**
* Shutdown I/O worker subsystem
*/
void io_worker_shutdown(void);
/**
* Read file asynchronously
* @param path File path
* @param callback Called with file contents
* @param user_data Passed to callback
* @return Task handle or NULL
*/
TaskHandle io_read_file_async(const char *path, IoCallback callback, void *user_data);
/**
* Write file asynchronously
*/
TaskHandle io_write_file_async(const char *path, const void *data, size_t size,
IoCallback callback, void *user_data);
/**
* Read directory contents asynchronously
* Result is a null-separated list of filenames
*/
TaskHandle io_read_dir_async(const char *path, IoCallback callback, void *user_data);
/**
* HTTP GET request asynchronously
* @param url URL to fetch
* @param callback Called with response body
* @param user_data Passed to callback
* @return Task handle or NULL
*/
TaskHandle io_http_get_async(const char *url, IoCallback callback, void *user_data);
/**
* HTTP POST request asynchronously
*/
TaskHandle io_http_post_async(const char *url, const void *data, size_t size,
IoCallback callback, void *user_data);
/**
* Download file with progress callback
*/
TaskHandle io_download_async(const char *url, const char *dest_path,
IoProgressCallback progress,
IoCallback callback, void *user_data);
/* ============================================================================
* Compute Worker - CPU-Intensive Tasks
* ============================================================================ */
typedef void (*ComputeCallback)(void *result, void *user_data);
/**
* Initialize compute worker subsystem
*/
bool compute_worker_init(void);
/**
* Shutdown compute worker subsystem
*/
void compute_worker_shutdown(void);
/**
* Parallel for - distribute work across threads
* @param start Start index
* @param end End index (exclusive)
* @param func Function called for each index
* @param user_data Passed to each invocation
*/
void compute_parallel_for(int start, int end,
void (*func)(int index, void *user_data),
void *user_data);
/**
* Map operation - apply function to each element
*/
void* compute_map(void *array, size_t count, size_t elem_size,
void (*func)(void *in, void *out, void *user_data),
void *user_data);
/**
* Reduce operation - aggregate values
*/
void* compute_reduce(void *array, size_t count, size_t elem_size,
void (*func)(void *accum, void *elem, void *user_data),
void *initial, void *user_data);
/**
* Submit compute task
*/
TaskHandle compute_submit(void (*func)(void *user_data), void *user_data,
ComputeCallback callback, void *callback_data);
/* ============================================================================
* System Worker - System Monitoring
* ============================================================================ */
typedef struct {
float cpu_percent;
uint64_t memory_used;
uint64_t memory_total;
float memory_percent;
float load_avg[3];
uint64_t uptime_seconds;
} SystemStats;
typedef struct {
uint32_t pid;
char name[64];
float cpu_percent;
uint64_t memory_bytes;
} ProcessInfo;
typedef void (*SystemStatsCallback)(const SystemStats *stats, void *user_data);
typedef void (*ProcessListCallback)(ProcessInfo *processes, uint32_t count, void *user_data);
/**
* Initialize system worker
*/
bool system_worker_init(void);
/**
* Shutdown system worker
*/
void system_worker_shutdown(void);
/**
* Get cached system stats (non-blocking, updated periodically)
*/
bool system_worker_get_cached_stats(SystemStats *stats);
/**
* Get fresh system stats (blocking)
*/
Future* system_worker_get_stats_async(void);
/**
* Get process list asynchronously
*/
TaskHandle system_worker_get_processes_async(ProcessListCallback callback, void *user_data);
/**
* Subscribe to periodic stats updates
* @param interval_ms Update interval (0 to disable)
* @param callback Called with new stats
* @param user_data Passed to callback
* @return Subscription ID
*/
uint32_t system_worker_subscribe_stats(uint32_t interval_ms,
SystemStatsCallback callback,
void *user_data);
/**
* Unsubscribe from stats updates
*/
bool system_worker_unsubscribe_stats(uint32_t subscription_id);
/* ============================================================================
* Deferred Worker - Delayed Execution
* ============================================================================ */
typedef uint64_t DeferredId;
typedef void (*DeferredCallback)(DeferredId id, void *user_data);
/**
* Initialize deferred worker
*/
bool deferred_worker_init(void);
/**
* Shutdown deferred worker
*/
void deferred_worker_shutdown(void);
/**
* Execute after delay
* @param delay_ms Milliseconds to wait
* @param callback Function to call
* @param user_data Passed to callback
* @return Deferred ID
*/
DeferredId deferred_after(uint64_t delay_ms, DeferredCallback callback, void *user_data);
/**
* Execute at regular interval
*/
DeferredId deferred_every(uint64_t interval_ms, DeferredCallback callback, void *user_data);
/**
* Cancel deferred execution
*/
bool deferred_cancel(DeferredId id);
/**
* Process deferred tasks (call from main loop)
* @return Number of tasks executed
*/
uint32_t deferred_process(void);
/**
* Get poll fd for select()
*/
int deferred_get_poll_fd(void);
/* ============================================================================
* High-Level Integration API
* ============================================================================ */
/**
* Initialize all worker subsystems
*/
bool thread_workers_init(void);
/**
* Shutdown all worker subsystems
*/
void thread_workers_shutdown(void);
/**
* Process all pending async work (call from main loop)
* @return true if work was processed
*/
bool thread_workers_process_all(void);
/**
* Get combined poll fds for select()
* @param fds Array to fill (size at least 4)
* @return Number of fds filled
*/
int thread_workers_get_poll_fds(int *fds);
#endif /* DWN_THREAD_WORKERS_H */
+704
View File
@@ -0,0 +1,704 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Extensive Abstract Threading Framework
*
* This module provides a comprehensive, abstract threading system with:
* - Lock-free data structures
* - Thread pools with work stealing
* - Async/await pattern support
* - Thread-safe event bus
* - Memory barriers and atomic operations
*/
#ifndef DWN_THREADING_H
#define DWN_THREADING_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <pthread.h>
#include <stdatomic.h>
/* ============================================================================
* Platform Abstraction Layer
* ============================================================================ */
/* Cache line size (commonly 64 bytes on x86_64) */
#define CACHE_LINE_SIZE 64
/* Align to cache line to prevent false sharing */
#define CACHE_ALIGN __attribute__((aligned(CACHE_LINE_SIZE)))
/* Memory ordering shortcuts */
#define ATOMIC_RELAXED memory_order_relaxed
#define ATOMIC_ACQUIRE memory_order_acquire
#define ATOMIC_RELEASE memory_order_release
#define ATOMIC_ACQ_REL memory_order_acq_rel
#define ATOMIC_SEQ_CST memory_order_seq_cst
/* ============================================================================
* Core Types and Status Codes
* ============================================================================ */
typedef enum {
THREAD_OK = 0,
THREAD_ERROR = -1,
THREAD_ERROR_NOMEM = -2,
THREAD_ERROR_BUSY = -3,
THREAD_ERROR_CLOSED = -4,
THREAD_ERROR_TIMEOUT = -5,
THREAD_ERROR_INVALID = -6
} ThreadStatus;
typedef enum {
TASK_PRIORITY_CRITICAL = 0, /* UI-critical, must execute immediately */
TASK_PRIORITY_HIGH = 1, /* User-initiated actions */
TASK_PRIORITY_NORMAL = 2, /* Default background work */
TASK_PRIORITY_LOW = 3, /* Maintenance tasks */
TASK_PRIORITY_IDLE = 4, /* Only when system idle */
TASK_PRIORITY_COUNT
} TaskPriority;
typedef enum {
TASK_STATE_PENDING = 0,
TASK_STATE_RUNNING = 1,
TASK_STATE_COMPLETED = 2,
TASK_STATE_CANCELLED = 3,
TASK_STATE_ERROR = 4
} TaskState;
/* Forward declarations */
typedef struct ThreadPool ThreadPool;
typedef struct Task Task;
typedef struct Channel Channel;
typedef struct Future Future;
typedef struct EventBus EventBus;
typedef struct AsyncContext AsyncContext;
typedef struct Worker Worker;
/* ============================================================================
* Task System - Abstract Unit of Work
* ============================================================================ */
/* Task function signature - receives user data and cancellation flag */
typedef void (*TaskFunc)(void *user_data, atomic_int *cancelled);
/* Callback for task completion (called in worker thread) */
typedef void (*TaskCallback)(Task *task, void *user_data, ThreadStatus status);
/* Task handle - opaque pointer */
typedef Task* TaskHandle;
/**
* Create a new task
* @param func The function to execute
* @param user_data Data passed to the function
* @param priority Task priority level
* @return Task handle or NULL on error
*/
TaskHandle task_create(TaskFunc func, void *user_data, TaskPriority priority);
/**
* Create a task with completion callback
*/
TaskHandle task_create_with_callback(TaskFunc func, void *user_data,
TaskPriority priority,
TaskCallback on_complete,
void *callback_data);
/**
* Destroy a task (only if not submitted)
*/
void task_destroy(TaskHandle task);
/**
* Cancel a task (best effort - may already be running)
*/
bool task_cancel(TaskHandle task);
/**
* Get current task state
*/
TaskState task_get_state(TaskHandle task);
/**
* Wait for task completion (blocking)
*/
ThreadStatus task_wait(TaskHandle task, uint64_t timeout_ms);
/**
* Check if task was cancelled
*/
bool task_is_cancelled(TaskHandle task);
/* ============================================================================
* Thread Pool - Manage Worker Threads
* ============================================================================ */
/* Thread pool configuration */
typedef struct {
uint32_t min_threads; /* Minimum threads to keep alive */
uint32_t max_threads; /* Maximum threads allowed */
uint32_t queue_capacity; /* Task queue capacity per priority */
uint32_t steal_attempts; /* Work stealing attempts before blocking */
uint64_t idle_timeout_ms; /* Time before idle thread terminates */
bool enable_work_stealing; /* Enable work stealing between queues */
} ThreadPoolConfig;
/* Default configuration */
#define THREAD_POOL_DEFAULT_CONFIG ((ThreadPoolConfig){ \
.min_threads = 2, \
.max_threads = 8, \
.queue_capacity = 256, \
.steal_attempts = 4, \
.idle_timeout_ms = 60000, \
.enable_work_stealing = true \
})
/**
* Create a thread pool
*/
ThreadPool* thread_pool_create(const ThreadPoolConfig *config);
/**
* Destroy thread pool, cancelling pending tasks
*/
void thread_pool_destroy(ThreadPool *pool);
/**
* Submit a task to the pool
*/
ThreadStatus thread_pool_submit(ThreadPool *pool, TaskHandle task);
/**
* Submit a function as a task (convenience)
*/
ThreadStatus thread_pool_submit_func(ThreadPool *pool, TaskFunc func,
void *user_data, TaskPriority priority);
/**
* Get number of active threads
*/
uint32_t thread_pool_active_count(ThreadPool *pool);
/**
* Get number of pending tasks
*/
uint32_t thread_pool_pending_count(ThreadPool *pool);
/**
* Shutdown pool gracefully, waiting for tasks to complete
*/
ThreadStatus thread_pool_shutdown(ThreadPool *pool, uint64_t timeout_ms);
/**
* Get the default/global thread pool
*/
ThreadPool* thread_pool_default(void);
/**
* Initialize the default thread pool
*/
ThreadStatus thread_pool_init_default(const ThreadPoolConfig *config);
/**
* Shutdown the default thread pool
*/
void thread_pool_shutdown_default(void);
/* ============================================================================
* Lock-Free Queue - Single Producer Single Consumer
* ============================================================================ */
#define SPSC_QUEUE_SIZE 1024
typedef struct {
_Atomic uint64_t head CACHE_ALIGN; /* Write index - producer only */
_Atomic uint64_t tail CACHE_ALIGN; /* Read index - consumer only */
void *buffer[SPSC_QUEUE_SIZE];
} SpscQueue;
/**
* Initialize SPSC queue
*/
void spsc_queue_init(SpscQueue *q);
/**
* Push item (producer only)
* @return false if queue full
*/
bool spsc_queue_push(SpscQueue *q, void *item);
/**
* Pop item (consumer only)
* @return false if queue empty
*/
bool spsc_queue_pop(SpscQueue *q, void **item);
/**
* Check if queue is empty (consumer only)
*/
bool spsc_queue_empty(SpscQueue *q);
/**
* Get approximate size (not synchronized)
*/
uint64_t spsc_queue_size_approx(SpscQueue *q);
/* ============================================================================
* Lock-Free Queue - Multi Producer Single Consumer
* ============================================================================ */
typedef struct MpscQueue MpscQueue;
/**
* Create MPSC queue
*/
MpscQueue* mpsc_queue_create(uint32_t capacity);
/**
* Destroy MPSC queue
*/
void mpsc_queue_destroy(MpscQueue *q);
/**
* Push item (thread-safe, any producer)
* @return false if queue full
*/
bool mpsc_queue_push(MpscQueue *q, void *item);
/**
* Pop item (consumer only - single thread)
* @return false if queue empty
*/
bool mpsc_queue_pop(MpscQueue *q, void **item);
/**
* Check if queue is empty
*/
bool mpsc_queue_empty(MpscQueue *q);
/* ============================================================================
* Channel - Thread-Safe Communication
* ============================================================================ */
typedef enum {
CHANNEL_UNBUFFERED = 0, /* Synchronous - sender blocks until received */
CHANNEL_BUFFERED = 1 /* Asynchronous - uses internal buffer */
} ChannelType;
/**
* Create a channel
* @param capacity Buffer size (0 for unbuffered synchronous channel)
*/
Channel* channel_create(uint32_t capacity);
/**
* Destroy channel
*/
void channel_destroy(Channel *ch);
/**
* Send data through channel (blocking)
* @return THREAD_OK on success, THREAD_ERROR_CLOSED if closed
*/
ThreadStatus channel_send(Channel *ch, void *data);
/**
* Send with timeout
*/
ThreadStatus channel_send_timeout(Channel *ch, void *data, uint64_t timeout_ms);
/**
* Try send (non-blocking)
*/
ThreadStatus channel_try_send(Channel *ch, void *data);
/**
* Receive from channel (blocking)
*/
ThreadStatus channel_recv(Channel *ch, void **data);
/**
* Receive with timeout
*/
ThreadStatus channel_recv_timeout(Channel *ch, void **data, uint64_t timeout_ms);
/**
* Try receive (non-blocking)
*/
ThreadStatus channel_try_recv(Channel *ch, void **data);
/**
* Close channel (no more sends allowed)
*/
void channel_close(Channel *ch);
/**
* Check if channel is closed
*/
bool channel_is_closed(Channel *ch);
/**
* Select on multiple channels (like Go's select)
* Returns index of ready channel or -1 on timeout
*/
int channel_select(Channel **channels, uint32_t count, uint64_t timeout_ms,
void **out_data);
/* ============================================================================
* Future/Promise - Async Result Handling
* ============================================================================ */
typedef void* FutureResult;
typedef void (*FutureCallback)(Future *f, FutureResult result, void *user_data);
/**
* Create a future
*/
Future* future_create(void);
/**
* Destroy future
*/
void future_destroy(Future *f);
/**
* Set the result (called by producer)
*/
void future_set_result(Future *f, FutureResult result);
/**
* Set error result
*/
void future_set_error(Future *f, int error_code);
/**
* Get result (blocking)
*/
FutureResult future_get(Future *f, ThreadStatus *status);
/**
* Get result with timeout
*/
FutureResult future_get_timeout(Future *f, uint64_t timeout_ms, ThreadStatus *status);
/**
* Check if future is ready
*/
bool future_is_ready(Future *f);
/**
* Attach callback to be called when ready (thread-safe)
*/
void future_then(Future *f, FutureCallback callback, void *user_data);
/**
* Create future that completes when all given futures complete
*/
Future* future_all(Future **futures, uint32_t count);
/**
* Create future that completes when any given future completes
*/
Future* future_any(Future **futures, uint32_t count);
/* ============================================================================
* Event Bus - Thread-Safe Pub/Sub
* ============================================================================ */
typedef uint32_t EventType;
typedef uint32_t SubscriptionId;
/* Event handler signature */
typedef void (*EventHandler)(EventType type, void *event_data, void *user_data);
/* Event filter - return true to allow event */
typedef bool (*EventFilter)(EventType type, void *event_data, void *user_data);
/**
* Create event bus
*/
EventBus* event_bus_create(void);
/**
* Destroy event bus
*/
void event_bus_destroy(EventBus *bus);
/**
* Subscribe to event type
* @return Subscription ID or 0 on error
*/
SubscriptionId event_bus_subscribe(EventBus *bus, EventType type,
EventHandler handler, void *user_data);
/**
* Subscribe with filter
*/
SubscriptionId event_bus_subscribe_filtered(EventBus *bus, EventType type,
EventHandler handler, void *user_data,
EventFilter filter, void *filter_data);
/**
* Unsubscribe
*/
bool event_bus_unsubscribe(EventBus *bus, SubscriptionId id);
/**
* Publish event (thread-safe)
*/
void event_bus_publish(EventBus *bus, EventType type, void *event_data);
/**
* Publish event with custom free function
*/
void event_bus_publish_owned(EventBus *bus, EventType type, void *event_data,
void (*free_fn)(void*));
/**
* Process pending events (call from main thread)
* @return number of events processed
*/
uint32_t event_bus_process(EventBus *bus);
/**
* Set processing limit per call
*/
void event_bus_set_batch_size(EventBus *bus, uint32_t batch_size);
/**
* Get the global/default event bus
*/
EventBus* event_bus_default(void);
/**
* Initialize default event bus
*/
bool event_bus_init_default(void);
/**
* Shutdown default event bus
*/
void event_bus_shutdown_default(void);
/* ============================================================================
* Async Context - Per-Module Async State
* ============================================================================ */
/* Context for managing async operations within a module */
struct AsyncContext {
ThreadPool *pool;
EventBus *event_bus;
Channel *completion_channel;
atomic_int operation_count;
atomic_int shutdown_requested;
pthread_mutex_t mutex;
};
/**
* Create async context
*/
AsyncContext* async_context_create(const char *name);
/**
* Destroy async context (cancels all pending operations)
*/
void async_context_destroy(AsyncContext *ctx);
/**
* Submit work to context's thread pool
*/
TaskHandle async_submit(AsyncContext *ctx, TaskFunc func, void *user_data,
TaskPriority priority);
/**
* Submit work and get future
*/
Future* async_submit_future(AsyncContext *ctx, TaskFunc func, void *user_data,
TaskPriority priority);
/**
* Check for completed operations (call from main thread)
*/
uint32_t async_poll(AsyncContext *ctx);
/**
* Get file descriptor for select() integration
* Returns -1 if not available
*/
int async_get_poll_fd(AsyncContext *ctx);
/* ============================================================================
* Timer/Scheduler - Delayed Execution
* ============================================================================ */
typedef uint64_t TimerId;
typedef void (*TimerCallback)(TimerId id, void *user_data);
/**
* Schedule one-shot timer
*/
TimerId timer_schedule(uint64_t delay_ms, TimerCallback callback, void *user_data);
/**
* Schedule repeating timer
*/
TimerId timer_schedule_repeating(uint64_t interval_ms, TimerCallback callback,
void *user_data);
/**
* Cancel timer
*/
bool timer_cancel(TimerId id);
/**
* Check if timer exists
*/
bool timer_exists(TimerId id);
/**
* Process timer events (call from main thread)
* @return Number of timers fired
*/
uint32_t timer_process(void);
/**
* Get timer file descriptor for select()
*/
int timer_get_poll_fd(void);
/**
* Initialize timer subsystem
*/
bool timer_init(void);
/**
* Shutdown timer subsystem
*/
void timer_shutdown(void);
/* ============================================================================
* Thread-Local Storage Abstraction
* ============================================================================ */
typedef struct TlsKey TlsKey;
/**
* Create TLS key
*/
TlsKey* tls_create(void (*destructor)(void*));
/**
* Destroy TLS key
*/
void tls_destroy(TlsKey *key);
/**
* Set TLS value
*/
void tls_set(TlsKey *key, void *value);
/**
* Get TLS value
*/
void* tls_get(TlsKey *key);
/* ============================================================================
* Read-Write Lock Wrapper
* ============================================================================ */
typedef struct RwLock RwLock;
RwLock* rwlock_create(void);
void rwlock_destroy(RwLock *lock);
void rwlock_read_lock(RwLock *lock);
bool rwlock_read_trylock(RwLock *lock);
void rwlock_read_unlock(RwLock *lock);
void rwlock_write_lock(RwLock *lock);
bool rwlock_write_trylock(RwLock *lock);
void rwlock_write_unlock(RwLock *lock);
/* ============================================================================
* Barrier and Synchronization
* ============================================================================ */
typedef struct Barrier Barrier;
Barrier* barrier_create(uint32_t count);
void barrier_destroy(Barrier *b);
bool barrier_wait(Barrier *b, uint64_t timeout_ms);
/* ============================================================================
* Initialization and Cleanup
* ============================================================================ */
/**
* Initialize entire threading subsystem
*/
bool threading_init(void);
/**
* Shutdown entire threading subsystem
*/
void threading_shutdown(void);
/**
* Get number of hardware threads
*/
uint32_t threading_hw_concurrency(void);
/**
* Current thread ID
*/
uint64_t threading_current_thread_id(void);
/**
* Set current thread name (for debugging)
*/
void threading_set_name(const char *name);
/**
* Yield current thread
*/
void threading_yield(void);
/* ============================================================================
* Utility Macros
* ============================================================================ */
/* Run function in background */
#define ASYNC(func, data) \
thread_pool_submit_func(thread_pool_default(), (func), (data), TASK_PRIORITY_NORMAL)
/* Run function with priority */
#define ASYNC_PRIORITY(func, data, prio) \
thread_pool_submit_func(thread_pool_default(), (func), (data), (prio))
/* Create future and submit */
#define ASYNC_FUTURE(ctx, func, data) \
async_submit_future((ctx), (func), (data), TASK_PRIORITY_NORMAL)
/* Synchronized block using mutex */
#define WITH_MUTEX(mutex, code) do { \
pthread_mutex_lock(&(mutex)); \
code; \
pthread_mutex_unlock(&(mutex)); \
} while(0)
/* Read lock block */
#define WITH_READ_LOCK(rwlock, code) do { \
rwlock_read_lock((rwlock)); \
code; \
rwlock_read_unlock((rwlock)); \
} while(0)
/* Write lock block */
#define WITH_WRITE_LOCK(rwlock, code) do { \
rwlock_write_lock((rwlock)); \
code; \
rwlock_write_unlock((rwlock)); \
} while(0)
#endif /* DWN_THREADING_H */
+75
View File
@@ -0,0 +1,75 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Threading Integration Header
*/
#ifndef DWN_THREADING_INTEGRATION_H
#define DWN_THREADING_INTEGRATION_H
#include "threading.h"
#include "thread_workers.h"
#include <sys/select.h>
/**
* Initialize threading integration with main loop
*/
bool threading_integration_init(void);
/**
* Shutdown threading integration
*/
void threading_integration_shutdown(void);
/**
* Prepare fd_set for select() - adds threading fds
*/
void threading_integration_prepare_select(int xfd, fd_set *fds, int *max_fd);
/**
* Process threading events after select() returns
*/
void threading_integration_process(fd_set *fds);
/**
* Non-blocking poll for async work
*/
void threading_integration_poll(void);
/**
* Run one iteration with integrated threading
*/
void threading_integration_run_iteration(int xfd, void (*handle_xevent)(void));
/**
* Get stats string for display
*/
void threading_integration_get_stats(char *buf, size_t bufsize);
/**
* Initialize per-module async contexts
*/
bool threading_integration_init_module_contexts(void);
/**
* Cleanup per-module async contexts
*/
void threading_integration_cleanup_module_contexts(void);
/**
* Get module-specific async contexts
*/
AsyncContext* threading_integration_get_ai_context(void);
AsyncContext* threading_integration_get_screenshot_context(void);
AsyncContext* threading_integration_get_ocr_context(void);
/**
* High-level task submission
*/
TaskHandle threading_integration_submit_ai(TaskFunc func, void *user_data);
TaskHandle threading_integration_submit_screenshot(TaskFunc func, void *user_data);
TaskHandle threading_integration_submit_ocr(TaskFunc func, void *user_data);
TaskHandle threading_integration_submit_ui(TaskFunc func, void *user_data);
TaskHandle threading_integration_submit_background(TaskFunc func, void *user_data);
#endif /* DWN_THREADING_INTEGRATION_H */
+30
View File
@@ -11,6 +11,7 @@
#include <stddef.h>
#include <stdarg.h>
#include <assert.h>
#include <string.h>
#define DWN_ASSERT(cond) assert(cond)
#define DWN_ASSERT_MSG(cond, msg) assert((cond) && (msg))
@@ -46,6 +47,27 @@ bool str_ends_with(const char *str, const char *suffix);
int str_split(char *str, char delim, char **parts, int max_parts);
char *shell_escape(const char *str);
/* Safe string copy that always null-terminates */
static inline void safe_strncpy(char *dest, const char *src, size_t n)
{
if (n == 0) return;
size_t src_len = strlen(src);
size_t copy_len = (src_len < n - 1) ? src_len : n - 1;
memcpy(dest, src, copy_len);
dest[copy_len] = '\0';
}
/* Check if value is within valid range */
static inline bool in_range_int(int val, int min, int max)
{
return val >= min && val <= max;
}
static inline bool in_range_size_t(size_t val, size_t min, size_t max)
{
return val >= min && val <= max;
}
bool file_exists(const char *path);
char *file_read_all(const char *path);
bool file_write_all(const char *path, const char *content);
@@ -53,6 +75,14 @@ char *expand_path(const char *path);
unsigned long parse_color(const char *color_str);
void color_to_rgb(unsigned long color, int *r, int *g, int *b);
unsigned long rgb_to_pixel(int r, int g, int b);
unsigned long generate_unique_color(void);
unsigned long adjust_color_for_background(unsigned long color, unsigned long bg);
unsigned long interpolate_color(unsigned long from, unsigned long to, float progress);
unsigned long dim_color(unsigned long color, float factor);
unsigned long glow_color(unsigned long base, float phase);
unsigned long ambient_glow_bg(unsigned long base, float phase);
unsigned long ambient_glow_accent(unsigned long base, float phase);
long get_time_ms(void);
void sleep_ms(int ms);
+305
View File
@@ -0,0 +1,305 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Abstraction Layer - DWN Documentation</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<button class="mobile-menu-btn">Menu</button>
<div class="layout">
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
<input type="text" class="search-input" placeholder="Search docs...">
</div>
<nav class="sidebar-nav">
<div class="nav-section">
<div class="nav-section-title">Getting Started</div>
<a href="index.html" class="nav-link">Introduction</a>
<a href="installation.html" class="nav-link">Installation</a>
<a href="quickstart.html" class="nav-link">Quick Start</a>
</div>
<div class="nav-section">
<div class="nav-section-title">User Guide</div>
<a href="features.html" class="nav-link">Features</a>
<a href="shortcuts.html" class="nav-link">Keyboard Shortcuts</a>
<a href="configuration.html" class="nav-link">Configuration</a>
<a href="layouts.html" class="nav-link">Layouts</a>
<a href="ai-features.html" class="nav-link">AI Integration</a>
</div>
<div class="nav-section">
<div class="nav-section-title">API Reference</div>
<a href="api-overview.html" class="nav-link">API Overview</a>
<a href="api-reference.html" class="nav-link">API Reference</a>
<a href="api-examples.html" class="nav-link">API Examples</a>
</div>
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link active">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
</aside>
<main class="main-content">
<div class="content">
<div class="page-header">
<h1>Abstraction Layer</h1>
<p class="lead">Backend-agnostic architecture for future extensibility</p>
</div>
<div class="toc">
<div class="toc-title">On this page</div>
<ul class="toc-list">
<li><a href="#overview">Overview</a></li>
<li><a href="#core-types">Core Types</a></li>
<li><a href="#backend-interface">Backend Interface</a></li>
<li><a href="#client-abstraction">Client Abstraction</a></li>
<li><a href="#containers">Container Types</a></li>
<li><a href="#migration">Migration Strategy</a></li>
</ul>
</div>
<h2 id="overview">Overview</h2>
<p>DWN v2.0 introduces a comprehensive abstraction layer that separates the window manager logic from backend-specific implementations. This architecture enables:</p>
<ul>
<li><strong>Backend Portability</strong> - Clean migration path from X11 to Wayland</li>
<li><strong>Type Safety</strong> - Strongly typed handles eliminate void* casting</li>
<li><strong>Memory Safety</strong> - Abstract strings and containers prevent buffer overflows</li>
<li><strong>Plugin Extensibility</strong> - Dynamic loading of layouts and widgets</li>
<li><strong>100% Compatibility</strong> - Existing code continues to work unchanged</li>
</ul>
<h2 id="core-types">Core Types</h2>
<p>The abstraction layer provides type-safe replacements for backend-specific types:</p>
<div class="table-container">
<table>
<thead>
<tr>
<th>Abstract Type</th>
<th>X11 Equivalent</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>WmWindowHandle</code></td>
<td><code>Window</code></td>
<td>Opaque window reference</td>
</tr>
<tr>
<td><code>WmClientId</code></td>
<td>-</td>
<td>Unique client identifier</td>
</tr>
<tr>
<td><code>WmWorkspaceId</code></td>
<td><code>int</code></td>
<td>Workspace identifier</td>
</tr>
<tr>
<td><code>WmRect</code></td>
<td>-</td>
<td>Rectangle geometry (x, y, w, h)</td>
</tr>
<tr>
<td><code>WmColor</code></td>
<td><code>unsigned long</code></td>
<td>RGBA color value</td>
</tr>
</tbody>
</table>
</div>
<h3>Geometry Operations</h3>
<p>Inline functions for rectangle operations:</p>
<div class="code-block">
<pre><code>WmRect rect = wm_rect_make(0, 0, 1920, 1080);
bool contains = wm_rect_contains_point(&rect, 100, 100);
bool intersects = wm_rect_intersects(&rect1, &rect2);
WmRect intersection = wm_rect_intersection(&rect1, &rect2);</code></pre>
</div>
<h3>Color Operations</h3>
<div class="code-block">
<pre><code>WmColor color = wm_color_rgb(255, 0, 128); // RGB
WmColor color = wm_color_rgba(255, 0, 128, 200); // RGBA
uint8_t r = wm_color_get_red(color);
uint8_t a = wm_color_get_alpha(color);</code></pre>
</div>
<h2 id="backend-interface">Backend Interface</h2>
<p>The backend interface defines a vtable of operations that any backend must implement:</p>
<div class="code-block">
<pre><code>typedef struct BackendInterface {
/* Identification */
const char *name;
WmBackendInfo (*get_info)(void);
/* Lifecycle */
bool (*init)(void *config);
void (*shutdown)(void);
/* Window Management */
void (*window_move)(WmWindowHandle window, int x, int y);
void (*window_resize)(WmWindowHandle window, int width, int height);
void (*window_focus)(WmWindowHandle window);
/* Events */
bool (*poll_event)(WmBackendEvent *event_out);
/* ... 80+ operations */
} BackendInterface;</code></pre>
</div>
<h3>X11 Backend</h3>
<p>The X11 backend is the reference implementation, translating abstract operations to X11 calls:</p>
<ul>
<li>Event translation (X11 → abstract events)</li>
<li>Protocol support (ICCCM, EWMH)</li>
<li>Property management with atom caching</li>
<li>Error handling with custom handlers</li>
</ul>
<h3>Future Backends</h3>
<p>The architecture supports multiple backends:</p>
<ul>
<li><strong>X11</strong> - Current, fully implemented</li>
<li><strong>Wayland</strong> - Planned for future</li>
<li><strong>Headless</strong> - For testing and CI</li>
</ul>
<h2 id="client-abstraction">Client Abstraction</h2>
<p>The <code>AbstractClient</code> type provides a backend-agnostic representation of a managed window:</p>
<div class="code-block">
<pre><code>/* Create from native window */
AbstractClient* client = wm_client_create(window, WM_CLIENT_TYPE_NORMAL);
/* State management */
wm_client_set_state(client, WM_CLIENT_STATE_FULLSCREEN);
wm_client_add_state(client, WM_CLIENT_STATE_FLOATING);
bool is_floating = wm_client_is_floating(client);
/* Geometry */
WmRect geom = wm_client_get_geometry(client);
wm_client_set_geometry(client, &new_geom);
wm_client_move_resize(client, x, y, width, height);
/* Properties */
wm_client_set_title(client, "New Title");
const char* title = wm_client_get_title(client);</code></pre>
</div>
<h3>Legacy Compatibility</h3>
<p>Abstract clients maintain bidirectional synchronization with legacy <code>Client</code> structures:</p>
<div class="code-block">
<pre><code>/* Wrap existing legacy client */
AbstractClient* abs_client = wm_client_from_legacy(legacy_client);
/* Access legacy client when needed */
Client* legacy = wm_client_get_legacy(abs_client);</code></pre>
</div>
<h2 id="containers">Container Types</h2>
<p>Safe, dynamic container implementations:</p>
<h3>WmString (Dynamic Strings)</h3>
<div class="code-block">
<pre><code>WmString* str = wm_string_new("Hello");
wm_string_append(str, " World");
wm_string_append_printf(str, " %d", 42);
const char* cstr = wm_string_cstr(str);
bool empty = wm_string_is_empty(str);
wm_string_destroy(str);</code></pre>
</div>
<h3>WmList (Dynamic Arrays)</h3>
<div class="code-block">
<pre><code>WmList* list = wm_list_new();
wm_list_append(list, item1);
wm_list_prepend(list, item2);
void* item = wm_list_get(list, 0);
wm_list_foreach(list, my_callback, user_data);
wm_list_destroy(list);</code></pre>
</div>
<h3>WmHashMap</h3>
<div class="code-block">
<pre><code>WmHashMap* map = wm_hashmap_new_string_key();
wm_hashmap_insert_string(map, "key", value);
void* value = wm_hashmap_get_string(map, "key");
bool exists = wm_hashmap_contains_string(map, "key");
wm_hashmap_destroy(map);</code></pre>
</div>
<h2 id="migration">Migration Strategy</h2>
<p>The abstraction layer uses an incremental migration approach:</p>
<ol>
<li><strong>Phase 1: Infrastructure</strong> (Complete)
<ul>
<li>Core types defined</li>
<li>Backend interface specified</li>
<li>X11 backend implemented</li>
</ul>
</li>
<li><strong>Phase 2: Client Migration</strong> (Complete)
<ul>
<li>AbstractClient type created</li>
<li>Bidirectional sync with legacy Client</li>
<li>Client manager with MRU tracking</li>
</ul>
</li>
<li><strong>Phase 3: Plugin System</strong> (Complete)
<ul>
<li>Layout plugin API</li>
<li>Widget plugin API</li>
<li>4 built-in layout plugins</li>
</ul>
</li>
<li><strong>Phase 4: Future</strong>
<ul>
<li>Gradual migration of existing code</li>
<li>Wayland backend implementation</li>
<li>Legacy code deprecation (long-term)</li>
</ul>
</li>
</ol>
<div class="alert alert-info">
<strong>Note:</strong> The abstraction layer is fully backward compatible. Existing code using X11 types continues to work unchanged.
</div>
<footer>
<p>DWN Window Manager - retoor &lt;retoor@molodetz.nl&gt;</p>
</footer>
</div>
</main>
</div>
<script src="js/main.js"></script>
</body>
</html>
+3 -1
View File
@@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
+132 -81
View File
@@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@@ -65,6 +67,7 @@
<li><a href="#python">Python Examples</a></li>
<li><a href="#javascript">JavaScript Examples</a></li>
<li><a href="#bash">Bash Examples</a></li>
<li><a href="#fade-control">Fade Effect Control</a></li>
<li><a href="#events">Event Subscription</a></li>
<li><a href="#automation">Automation Recipes</a></li>
<li><a href="#browser-ocr">Browser Automation with OCR</a></li>
@@ -78,17 +81,17 @@
<pre><code>import json
import websocket
ws = websocket.create_connection("ws://localhost:8777")
ws = websocket.create_connection("ws://localhost:8777/ws")
def send_command(command, **params):
request = {"command": command, **params}
ws.send(json.dumps(request))
return json.loads(ws.recv())
# List all windows
result = send_command("list_windows")
for window in result["windows"]:
print(f"{window['title']} ({window['class']})")
# List all clients
result = send_command("get_clients")
for client in result["clients"]:
print(f"{client['title']} ({client['class']})")
ws.close()</code></pre>
</div>
@@ -100,21 +103,21 @@ ws.close()</code></pre>
client = DWNClient()
client.connect()
# Get all windows
windows = client.list_windows()
print(f"Found {len(windows)} windows")
# Get all clients
clients = client.get_clients()
print(f"Found {len(clients)} clients")
# Focus window by title
for w in windows:
if "Firefox" in w["title"]:
client.focus_window(w["id"])
# Focus client by title
for c in clients:
if "Firefox" in c["title"]:
client.focus_client(c["window"])
break
# Switch to workspace 3
client.switch_workspace(2)
# Type some text
client.type_text("Hello from Python!")
client.key_type("Hello from Python!")
client.disconnect()</code></pre>
</div>
@@ -145,30 +148,30 @@ print(ocr_result["text"])
client.disconnect()</code></pre>
</div>
<h3>Window Arrangement Script</h3>
<h3>Client Arrangement Script</h3>
<div class="code-block">
<pre><code>from dwn_api_client import DWNClient
def arrange_coding_setup(client):
"""Arrange windows for coding: editor left, terminal right"""
windows = client.list_windows()
"""Arrange clients for coding: editor left, terminal right"""
clients = client.get_clients()
# Find VS Code and terminal
vscode = None
terminal = None
for w in windows:
if "code" in w["class"].lower():
vscode = w
elif "terminal" in w["class"].lower():
terminal = w
for c in clients:
if "code" in c["class"].lower():
vscode = c
elif "terminal" in c["class"].lower():
terminal = c
if vscode:
client.move_window(vscode["id"], 0, 32)
client.resize_window(vscode["id"], 960, 1048)
client.move_client(vscode["window"], 0, 32)
client.resize_client(vscode["window"], 960, 1048)
if terminal:
client.move_window(terminal["id"], 960, 32)
client.resize_window(terminal["id"], 960, 1048)
client.move_client(terminal["window"], 960, 32)
client.resize_client(terminal["window"], 960, 1048)
client = DWNClient()
client.connect()
@@ -183,15 +186,15 @@ import json
import websockets
async def main():
async with websockets.connect("ws://localhost:8777") as ws:
async with websockets.connect("ws://localhost:8777/ws") as ws:
# Send command
await ws.send(json.dumps({"command": "list_windows"}))
await ws.send(json.dumps({"command": "get_clients"}))
# Receive response
response = json.loads(await ws.recv())
for window in response["windows"]:
print(f"Window: {window['title']}")
for client in response["clients"]:
print(f"Client: {client['title']}")
asyncio.run(main())</code></pre>
</div>
@@ -201,7 +204,7 @@ asyncio.run(main())</code></pre>
<h3>Browser WebSocket</h3>
<div class="code-block">
<pre><code>class DWNClient {
constructor(url = 'ws://localhost:8777') {
constructor(url = 'ws://localhost:8777/ws') {
this.url = url;
this.ws = null;
this.pending = new Map();
@@ -228,16 +231,16 @@ asyncio.run(main())</code></pre>
this.ws.send(JSON.stringify(request));
}
async listWindows() {
this.send('list_windows');
async getClients() {
this.send('get_clients');
}
async focusWindow(windowId) {
this.send('focus_window', { window: windowId });
async focusClient(windowId) {
this.send('focus_client', { window: windowId });
}
async typeText(text) {
this.send('type_text', { text });
async keyType(text) {
this.send('key_type', { text });
}
async screenshot(mode = 'fullscreen') {
@@ -248,28 +251,28 @@ asyncio.run(main())</code></pre>
// Usage
const client = new DWNClient();
await client.connect();
await client.listWindows();</code></pre>
await client.getClients();</code></pre>
</div>
<h3>Node.js Client</h3>
<div class="code-block">
<pre><code>const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8777');
const ws = new WebSocket('ws://localhost:8777/ws');
ws.on('open', () => {
console.log('Connected to DWN');
// List windows
ws.send(JSON.stringify({ command: 'list_windows' }));
// List clients
ws.send(JSON.stringify({ command: 'get_clients' }));
});
ws.on('message', (data) => {
const response = JSON.parse(data);
if (response.windows) {
response.windows.forEach(w => {
console.log(`${w.title} - ${w.class}`);
if (response.clients) {
response.clients.forEach(c => {
console.log(`${c.title} - ${c.class}`);
});
}
@@ -311,18 +314,18 @@ ws.on('error', (err) => {
<div class="code-block">
<pre><code>#!/bin/bash
# List windows
echo '{"command": "list_windows"}' | websocat ws://localhost:8777
# List clients
echo '{"command": "get_clients"}' | websocat ws://localhost:8777/ws
# Focus window by ID
echo '{"command": "focus_window", "window": 12345678}' | websocat ws://localhost:8777
# Focus client by ID
echo '{"command": "focus_client", "window": 12345678}' | websocat ws://localhost:8777/ws
# Switch workspace
echo '{"command": "switch_workspace", "workspace": 2}' | websocat ws://localhost:8777
echo '{"command": "switch_workspace", "workspace": 2}' | websocat ws://localhost:8777/ws
# Take screenshot and save
echo '{"command": "screenshot", "mode": "fullscreen"}' | \
websocat ws://localhost:8777 | \
websocat ws://localhost:8777/ws | \
jq -r '.data' | \
base64 -d > screenshot.png</code></pre>
</div>
@@ -332,10 +335,10 @@ echo '{"command": "screenshot", "mode": "fullscreen"}' | \
<pre><code>#!/bin/bash
# One-liner command
echo '{"command": "list_windows"}' | wscat -c ws://localhost:8777 -w 1
echo '{"command": "get_clients"}' | wscat -c ws://localhost:8777/ws -w 1
# Interactive session
wscat -c ws://localhost:8777
wscat -c ws://localhost:8777/ws
# Then type commands manually</code></pre>
</div>
@@ -344,17 +347,65 @@ wscat -c ws://localhost:8777
<pre><code>#!/bin/bash
dwn_command() {
echo "$1" | websocat -n1 ws://localhost:8777
echo "$1" | websocat -n1 ws://localhost:8777/ws
}
# Get focused window
dwn_command '{"command": "get_focused"}' | jq '.window.title'
# Get focused client
dwn_command '{"command": "get_focused_client"}' | jq '.client.title'
# Type text
dwn_command '{"command": "type_text", "text": "Hello!"}'
dwn_command '{"command": "key_type", "text": "Hello!"}'
# Launch application
dwn_command '{"command": "spawn", "program": "firefox"}'</code></pre>
dwn_command '{"command": "run_command", "exec": "firefox"}'</code></pre>
</div>
<h3>Fade Effect Control</h3>
<p>Control DWN's fade animation effects via API:</p>
<div class="code-block">
<pre><code>import json
import websocket
ws = websocket.create_connection("ws://localhost:8777/ws")
# Get current fade settings
ws.send(json.dumps({"command": "get_fade_settings"}))
response = json.loads(ws.recv())
print(f"Speed: {response['fade_speed']}, Intensity: {response['fade_intensity']}")
# Set fade animation speed (0.1 - 3.0)
ws.send(json.dumps({"command": "set_fade_speed", "speed": 1.5}))
# Set fade glow intensity (0.0 - 1.0)
ws.send(json.dumps({"command": "set_fade_intensity", "intensity": 0.8}))
# Subscribe to fade change events
ws.send(json.dumps({
"command": "subscribe",
"events": ["fade_speed_changed", "fade_intensity_changed"]
}))
# Listen for events
while True:
event = json.loads(ws.recv())
if event.get("type") == "event":
print(f"Event: {event['event']}")
print(f"Data: {event['data']}")</code></pre>
</div>
<p>Using the provided fade control demo script:</p>
<div class="code-block">
<pre><code># Get current fade settings
python3 examples/fade_control_demo.py
# Set fade speed
python3 examples/fade_control_demo.py --speed 1.5
# Set fade intensity
python3 examples/fade_control_demo.py --intensity 0.8
# Listen for fade events
python3 examples/fade_control_demo.py --listen --duration 30</code></pre>
</div>
<h2 id="events">Event Subscription</h2>
@@ -452,7 +503,7 @@ activity_logger()</code></pre>
<h3>JavaScript Event Listener</h3>
<div class="code-block">
<pre><code>const ws = new WebSocket('ws://localhost:8777');
<pre><code>const ws = new WebSocket('ws://localhost:8777/ws');
ws.onopen = () => {
console.log('Connected to DWN');
@@ -491,7 +542,7 @@ ws.onclose = () => {
};</code></pre>
</div>
<h3>Reactive Window Arrangement</h3>
<h3>Reactive Client Arrangement</h3>
<div class="code-block">
<pre><code>from dwn_api_client import DWNClient
@@ -508,7 +559,7 @@ def auto_arrange():
client.subscribe(events=["window_created"])
print("Auto-arranging windows...")
print("Auto-arranging clients...")
try:
while True:
@@ -541,7 +592,7 @@ auto_arrange()</code></pre>
<h2 id="automation">Automation Recipes</h2>
<h3>Auto-Arrange Windows by Class</h3>
<h3>Auto-Arrange Clients by Class</h3>
<div class="code-block">
<pre><code>from dwn_api_client import DWNClient
@@ -556,17 +607,17 @@ def auto_arrange():
client = DWNClient()
client.connect()
windows = client.list_windows()
for w in windows:
win_class = w["class"].lower()
clients = client.get_clients()
for c in clients:
wm_class = c["class"].lower()
for pattern, rules in LAYOUT_RULES.items():
if pattern in win_class:
if w["workspace"] != rules["workspace"]:
client.move_window_to_workspace(
w["id"], rules["workspace"]
if pattern in wm_class:
if c["workspace"] != rules["workspace"]:
client.move_client_to_workspace(
c["window"], rules["workspace"]
)
if w["floating"] != rules["floating"]:
client.set_floating(w["id"], rules["floating"])
if c["floating"] != rules["floating"]:
client.float_client(c["window"], rules["floating"])
break
client.disconnect()
@@ -609,14 +660,14 @@ def screenshot_monitor(interval=60, output_dir="screenshots"):
screenshot_monitor(interval=300) # Every 5 minutes</code></pre>
</div>
<h3>Window Focus Logger</h3>
<h3>Client Focus Logger</h3>
<div class="code-block">
<pre><code>import time
from datetime import datetime
from dwn_api_client import DWNClient
def focus_logger(log_file="focus_log.txt"):
"""Log window focus changes"""
"""Log client focus changes"""
client = DWNClient()
client.connect()
@@ -625,16 +676,16 @@ def focus_logger(log_file="focus_log.txt"):
try:
with open(log_file, "a") as f:
while True:
windows = client.list_windows()
focused = next((w for w in windows if w["focused"]), None)
clients = client.get_clients()
focused = next((c for c in clients if c["focused"]), None)
if focused and focused["id"] != last_focused:
if focused and focused["window"] != last_focused:
timestamp = datetime.now().isoformat()
entry = f"{timestamp} | {focused['title']} ({focused['class']})\n"
f.write(entry)
f.flush()
print(entry.strip())
last_focused = focused["id"]
last_focused = focused["window"]
time.sleep(1)
except KeyboardInterrupt:
@@ -682,24 +733,24 @@ client.disconnect()</code></pre>
<div class="code-block">
<pre><code>from dwn_api_client import DWNClient
def read_active_window():
"""Extract and print text from active window"""
def read_active_client():
"""Extract and print text from active client"""
client = DWNClient()
client.connect()
# Capture active window
# Capture active client
screenshot = client.screenshot("active")
# Extract text
ocr_result = client.ocr(screenshot["data"])
print(f"Text from active window (confidence: {ocr_result['confidence']:.0%}):")
print(f"Text from active client (confidence: {ocr_result['confidence']:.0%}):")
print("-" * 40)
print(ocr_result["text"])
client.disconnect()
read_active_window()</code></pre>
read_active_client()</code></pre>
</div>
<h3 id="browser-ocr">Browser Automation with OCR</h3>
+64 -27
View File
@@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@@ -98,7 +100,7 @@ port = 8777</code></pre>
</div>
<h2 id="connecting">Connecting</h2>
<p>Connect via WebSocket to <code>ws://localhost:8777</code> (or your configured port).</p>
<p>Connect via WebSocket to <code>ws://localhost:8777/ws</code> (or your configured port). The <code>/ws</code> path is required.</p>
<h3>Testing with wscat</h3>
<div class="code-block">
@@ -106,11 +108,11 @@ port = 8777</code></pre>
npm install -g wscat
# Connect to DWN
wscat -c ws://localhost:8777
wscat -c ws://localhost:8777/ws
# Send a command
> {"command": "list_windows"}
< {"status": "ok", "windows": [...]}</code></pre>
> {"command": "get_clients"}
< {"status": "ok", "clients": [...]}</code></pre>
</div>
<h3>Testing with websocat</h3>
@@ -119,7 +121,7 @@ wscat -c ws://localhost:8777
cargo install websocat
# Connect and send command
echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
echo '{"command": "get_clients"}' | websocat ws://localhost:8777/ws</code></pre>
</div>
<h2 id="protocol">Protocol</h2>
@@ -161,24 +163,24 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
</thead>
<tbody>
<tr>
<td>Windows</td>
<td>list_windows, focus_window, close_window, move_window, resize_window</td>
<td>Clients</td>
<td>get_clients, find_clients, get_focused_client, focus_client, focus_next, focus_prev, focus_master, close_client, kill_client, move_client, resize_client, minimize_client, restore_client, maximize_client, fullscreen_client, float_client, raise_client, lower_client, snap_client</td>
</tr>
<tr>
<td>Workspaces</td>
<td>list_workspaces, switch_workspace, move_to_workspace</td>
<td>get_workspaces, switch_workspace, switch_workspace_next, switch_workspace_prev, move_client_to_workspace</td>
</tr>
<tr>
<td>Layout</td>
<td>get_layout, set_layout, set_master_ratio</td>
<td>set_layout, cycle_layout, set_master_ratio, adjust_master_ratio, set_master_count, adjust_master_count</td>
</tr>
<tr>
<td>Keyboard</td>
<td>key_press, key_release, type_text</td>
<td>key_press, key_release, key_tap, key_type, get_keybindings</td>
</tr>
<tr>
<td>Mouse</td>
<td>mouse_move, mouse_click, mouse_drag</td>
<td>mouse_move, mouse_move_relative, mouse_click, mouse_press, mouse_release, mouse_scroll, get_mouse_position</td>
</tr>
<tr>
<td>Screenshot</td>
@@ -190,7 +192,39 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
</tr>
<tr>
<td>System</td>
<td>get_monitors, spawn, notify</td>
<td>get_status, get_screen_info, run_command, show_desktop, get_config, reload_config</td>
</tr>
<tr>
<td>Notifications</td>
<td>notify, close_notification, get_notifications</td>
</tr>
<tr>
<td>System Tray</td>
<td>get_battery_state, get_audio_state, set_audio_volume, toggle_audio_mute, get_wifi_state</td>
</tr>
<tr>
<td>Panels</td>
<td>get_panel_state, show_panel, hide_panel, toggle_panel</td>
</tr>
<tr>
<td>AI</td>
<td>ai_is_available, ai_command, exa_is_available, exa_search</td>
</tr>
<tr>
<td>News</td>
<td>get_news, news_next, news_prev, news_open</td>
</tr>
<tr>
<td>Demo/Tutorial</td>
<td>start_demo, stop_demo, get_demo_state, start_tutorial, stop_tutorial, get_tutorial_state</td>
</tr>
<tr>
<td>Events</td>
<td>subscribe, unsubscribe, list_events</td>
</tr>
<tr>
<td>Fade Effects</td>
<td>get_fade_settings, set_fade_speed, set_fade_intensity</td>
</tr>
</tbody>
</table>
@@ -198,32 +232,35 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
<h2 id="quick-start">Quick Start</h2>
<h3>List Windows</h3>
<h3>List Clients</h3>
<div class="code-block">
<pre><code>// Request
{"command": "list_windows"}
{"command": "get_clients"}
// Response
{
"status": "ok",
"windows": [
"clients": [
{
"id": 12345678,
"window": 12345678,
"title": "Firefox",
"class": "firefox",
"workspace": 0,
"x": 0, "y": 32,
"width": 960, "height": 540,
"focused": true,
"floating": false
"floating": false,
"fullscreen": false,
"maximized": false,
"minimized": false
}
]
}</code></pre>
</div>
<h3>Focus a Window</h3>
<h3>Focus a Client</h3>
<div class="code-block">
<pre><code>{"command": "focus_window", "window": 12345678}</code></pre>
<pre><code>{"command": "focus_client", "window": 12345678}</code></pre>
</div>
<h3>Switch Workspace</h3>
@@ -233,7 +270,7 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
<h3>Type Text</h3>
<div class="code-block">
<pre><code>{"command": "type_text", "text": "Hello, World!"}</code></pre>
<pre><code>{"command": "key_type", "text": "Hello, World!"}</code></pre>
</div>
<h3>Take Screenshot</h3>
@@ -263,11 +300,11 @@ echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
client = DWNClient()
client.connect()
# List windows
windows = client.list_windows()
# List clients
clients = client.get_clients()
# Focus a window
client.focus_window(windows[0]['id'])
# Focus a client
client.focus_client(clients[0]['window'])
# Take screenshot
result = client.screenshot('fullscreen')
@@ -280,10 +317,10 @@ client.disconnect()</code></pre>
<h3>JavaScript (Browser)</h3>
<div class="code-block">
<pre><code>const ws = new WebSocket('ws://localhost:8777');
<pre><code>const ws = new WebSocket('ws://localhost:8777/ws');
ws.onopen = () => {
ws.send(JSON.stringify({command: 'list_windows'}));
ws.send(JSON.stringify({command: 'get_clients'}));
};
ws.onmessage = (event) => {
+918 -179
View File
File diff suppressed because it is too large Load Diff
+53 -18
View File
@@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link active">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@@ -63,6 +65,7 @@
<div class="toc-title">On this page</div>
<ul class="toc-list">
<li><a href="#overview">Overview</a></li>
<li><a href="#abstraction">Abstraction Layer</a></li>
<li><a href="#modules">Module Structure</a></li>
<li><a href="#event-loop">Event Loop</a></li>
<li><a href="#data-structures">Core Data Structures</a></li>
@@ -72,30 +75,62 @@
</div>
<h2 id="overview">Overview</h2>
<p>DWN is written in ANSI C for X11/Xorg. It uses a modular architecture with a global state singleton and event-driven design.</p>
<p>DWN is written in ANSI C for X11/Xorg. It uses a modular architecture with a global state singleton and event-driven design. Version 2.0 introduces a comprehensive abstraction layer enabling backend portability and plugin extensibility.</p>
<h2 id="abstraction">Abstraction Layer (v2.0)</h2>
<p>The new abstraction layer provides backend-agnostic types and a plugin system:</p>
<div class="code-block">
<pre><code>┌─────────────────────────────────────────────────────────┐
DWN Window Manager
│ DWN Application
├─────────────────────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ main │ │ keys │ │ panel │ │ api
│ │ loop │ │ handler │ │ render │ server │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │
┌────┴────────────┴────────────┴────────────┴────┐
│ DWNState (Global Singleton) │
└────┬────────────┬────────────┬────────────┬────┘
│ │ │
────────┐ ┌────────┐ ┌────────┐ ┌────────
│ client │workspace│ │ layout atoms │
│ mgmt │ │ mgmt │ │ engine │ │ (EWMH) │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
├─────────────────────────────────────────────────────────┤
X11 / Xlib
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Layout │ │ Widget │ │ AI Provider │ │
│ │ Plugin │ │ Plugin │ │ API │
│ System │ │ System │ │ (future)
└──────┬──────┘ └──────┬──────┘ └─────────────────┘
│ │
┌──────┴────────────────┴───────────────────────────┐
│ Abstract Client Manager │
│ │ (wm_client.h/c - bidirectional sync) │
└───────────────────────┬───────────────────────────┘
┌───────────────────────┴───────────────────────────┐
│ Backend Abstraction Interface │
│ │ (backend_interface.h - vtable-based) │ │
└───────────────────────┬───────────────────────────┘
│ │ │
│ ┌───────────────────────┴───────────────────────────┐ │
│ │ X11 Backend │ Wayland Backend │ Headless │ │
│ │ (complete) │ (future) │ (future) │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘</code></pre>
</div>
<h3>Core Abstractions</h3>
<ul>
<li><strong>wm_types.h</strong> - Abstract handles, geometry, colors, events</li>
<li><strong>wm_string.h/c</strong> - Safe dynamic strings</li>
<li><strong>wm_list.h/c</strong> - Dynamic arrays</li>
<li><strong>wm_hashmap.h/c</strong> - Hash tables</li>
<li><strong>wm_client.h/c</strong> - Abstract client type</li>
</ul>
<h3>Plugin System</h3>
<ul>
<li><strong>Layout Plugins</strong> - Custom window arrangements (tiling, floating, monocle, grid)</li>
<li><strong>Widget Plugins</strong> - Panel components (taskbar, clock, system stats)</li>
<li><strong>Dynamic Loading</strong> - Load plugins from shared libraries</li>
</ul>
<h3>Backend Interface</h3>
<p>The backend interface defines 80+ operations for window management, events, and rendering. Current implementations:</p>
<ul>
<li><strong>X11</strong> - Full implementation with event translation</li>
<li><strong>Wayland</strong> - Planned for future</li>
<li><strong>Headless</strong> - For testing and CI</li>
</ul>
<h2 id="modules">Module Structure</h2>
<p>Each module has a header in <code>include/</code> and implementation in <code>src/</code>.</p>
+3 -1
View File
@@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link active">Building from Source</a>
</div>
</nav>
+130 -1
View File
@@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@@ -309,6 +311,133 @@ ai_timeout = 15000
window_timeout = 5000</code></pre>
</div>
<h3 id="rules">[rules] - Window Rules</h3>
<p>Automatically apply settings to windows based on their class or title. Rules use glob patterns for matching.</p>
<div class="code-block">
<pre><code>[rules]
Firefox = workspace:2, floating:false
class:*terminal* = workspace:1
title:*Calculator* = floating:true, width:400, height:300
class:Telegram = workspace:9, sticky:true
Gimp = floating:true
class:mpv = fullscreen:true</code></pre>
</div>
<h4>Rule Syntax</h4>
<p>Each rule has the format: <code>pattern = property:value, property:value, ...</code></p>
<div class="table-container">
<table>
<thead>
<tr>
<th>Pattern Prefix</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>class:</code></td>
<td>Match by WM_CLASS (default if no prefix)</td>
</tr>
<tr>
<td><code>title:</code></td>
<td>Match by window title</td>
</tr>
<tr>
<td>(no prefix)</td>
<td>Treated as class pattern</td>
</tr>
</tbody>
</table>
</div>
<h4>Available Properties</h4>
<div class="table-container">
<table>
<thead>
<tr>
<th>Property</th>
<th>Values</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>workspace</code></td>
<td>1-9</td>
<td>Move to specific workspace</td>
</tr>
<tr>
<td><code>floating</code></td>
<td>true/false</td>
<td>Set floating mode</td>
</tr>
<tr>
<td><code>sticky</code></td>
<td>true/false</td>
<td>Visible on all workspaces</td>
</tr>
<tr>
<td><code>fullscreen</code></td>
<td>true/false</td>
<td>Start in fullscreen mode</td>
</tr>
<tr>
<td><code>width</code></td>
<td>pixels</td>
<td>Set initial width</td>
</tr>
<tr>
<td><code>height</code></td>
<td>pixels</td>
<td>Set initial height</td>
</tr>
<tr>
<td><code>x</code></td>
<td>pixels</td>
<td>Set initial X position</td>
</tr>
<tr>
<td><code>y</code></td>
<td>pixels</td>
<td>Set initial Y position</td>
</tr>
</tbody>
</table>
</div>
<h4>Pattern Matching</h4>
<ul>
<li><code>*</code> - Matches any sequence of characters</li>
<li><code>?</code> - Matches any single character</li>
<li><code>[abc]</code> - Matches any character in brackets</li>
<li>Matching is case-insensitive</li>
</ul>
<h4>Examples</h4>
<div class="code-block">
<pre><code>[rules]
# Open Firefox on workspace 2
Firefox = workspace:2
# All terminals on workspace 1
class:*terminal* = workspace:1
# Calculator always floating with specific size
title:*Calculator* = floating:true, width:400, height:300
# Picture-in-picture windows
title:Picture-in-Picture = floating:true, sticky:true
# Gimp dialogs floating
class:Gimp = floating:true
# Video players fullscreen
class:mpv = fullscreen:true
class:vlc = fullscreen:true</code></pre>
</div>
<h2>Environment Variables</h2>
<div class="table-container">
<table>
+113 -3
View File
@@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@@ -63,6 +65,8 @@
<div class="toc-title">On this page</div>
<ul class="toc-list">
<li><a href="#window-management">Window Management</a></li>
<li><a href="#window-rules">Window Rules</a></li>
<li><a href="#window-marks">Window Marks</a></li>
<li><a href="#workspaces">Virtual Workspaces</a></li>
<li><a href="#layouts">Layout System</a></li>
<li><a href="#panels">Panels & System Tray</a></li>
@@ -120,6 +124,85 @@
<h3>Alt-Tab Window Cycling</h3>
<p>DWN maintains a Most Recently Used (MRU) stack per workspace. <code>Alt+Tab</code> cycles through windows in order of recent use, not visual order.</p>
<h3>Keyboard Window Control</h3>
<p>Move and resize floating windows without leaving the keyboard:</p>
<ul>
<li><code>Super+Alt+Arrow</code> - Move window by 20 pixels</li>
<li><code>Super+Ctrl+Arrow</code> - Resize window by 20 pixels</li>
</ul>
<h2 id="window-rules">Window Rules</h2>
<p>Automatically apply settings to windows based on their class or title. Rules are matched using glob patterns with case-insensitive matching.</p>
<h3>Matching Criteria</h3>
<ul>
<li><strong>Class Pattern</strong> - Match by WM_CLASS (e.g., <code>Firefox</code>, <code>*terminal*</code>)</li>
<li><strong>Title Pattern</strong> - Match by window title (e.g., <code>*Calculator*</code>)</li>
<li><strong>Combined</strong> - Match both class and title simultaneously</li>
</ul>
<h3>Available Actions</h3>
<div class="table-container">
<table>
<thead>
<tr>
<th>Property</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>workspace</code></td>
<td>Move window to specific workspace (1-9)</td>
</tr>
<tr>
<td><code>floating</code></td>
<td>Set floating state (true/false)</td>
</tr>
<tr>
<td><code>sticky</code></td>
<td>Make window visible on all workspaces</td>
</tr>
<tr>
<td><code>fullscreen</code></td>
<td>Start window in fullscreen mode</td>
</tr>
<tr>
<td><code>width</code>, <code>height</code></td>
<td>Set initial window size</td>
</tr>
<tr>
<td><code>x</code>, <code>y</code></td>
<td>Set initial window position</td>
</tr>
</tbody>
</table>
</div>
<p>See <a href="configuration.html#rules">Configuration</a> for rule syntax.</p>
<h2 id="window-marks">Window Marks</h2>
<p>Mark windows with letters (a-z) for instant navigation, inspired by Vim marks.</p>
<div class="feature-grid">
<div class="feature-card">
<div class="feature-title">Set Mark</div>
<div class="feature-desc">Press <code>Super+M</code> then <code>a-z</code> to mark the focused window.</div>
</div>
<div class="feature-card">
<div class="feature-title">Jump to Mark</div>
<div class="feature-desc">Press <code>Super+'</code> then <code>a-z</code> to instantly focus the marked window.</div>
</div>
<div class="feature-card">
<div class="feature-title">Cross-Workspace</div>
<div class="feature-desc">Marks work across workspaces, automatically switching if needed.</div>
</div>
<div class="feature-card">
<div class="feature-title">26 Slots</div>
<div class="feature-desc">Use any letter a-z for marks. Reassigning overwrites the previous mark.</div>
</div>
</div>
<h2 id="workspaces">Virtual Workspaces</h2>
<p>DWN provides 9 virtual workspaces for organizing your windows.</p>
@@ -143,7 +226,7 @@
</div>
<h2 id="layouts">Layout System</h2>
<p>Three layout modes available per workspace:</p>
<p>Six layout modes available per workspace:</p>
<h3>Tiling Layout</h3>
<p>Master-stack tiling with configurable ratios. The first window(s) occupy the master area, others stack on the side.</p>
@@ -159,7 +242,22 @@
<h3>Monocle Layout</h3>
<p>All windows fullscreen and stacked. Use Alt-Tab to switch between them. Ideal for focused work.</p>
<p>See <a href="layouts.html">Layouts</a> for detailed documentation.</p>
<h3>Centered Master Layout</h3>
<p>The master window is centered on screen with stack windows divided on left and right sides. Great for wide monitors.</p>
<h3>Columns Layout</h3>
<p>All windows arranged in equal-width vertical columns. Each window takes full height.</p>
<h3>Fibonacci Layout</h3>
<p>Windows arranged in a spiral pattern using recursive splitting. Creates a visually interesting and space-efficient arrangement.</p>
<h3>Grid Layout</h3>
<p>Windows arranged in an automatically-calculated grid pattern. Perfect for comparing multiple documents or monitoring many terminals simultaneously.</p>
<h3>Plugin System</h3>
<p>DWN v2.0 supports custom layout plugins. Create your own window arrangements using the Layout Plugin API. Both built-in and dynamically loaded plugins are supported.</p>
<p>See <a href="layouts.html">Layouts</a> and <a href="plugin-development.html">Plugin Development</a> for detailed documentation.</p>
<h2 id="panels">Panels & System Tray</h2>
@@ -277,6 +375,18 @@
<p>See <a href="api-overview.html">API Overview</a> for getting started.</p>
<h2>Abstraction Layer (v2.0)</h2>
<p>DWN v2.0 introduces a comprehensive abstraction layer enabling backend portability and plugin extensibility:</p>
<ul>
<li><strong>Backend Interface</strong> - 80+ operation vtable for X11/Wayland portability</li>
<li><strong>Type Safety</strong> - Strongly typed handles replacing void* casts</li>
<li><strong>Memory Safety</strong> - Safe string and container abstractions</li>
<li><strong>Plugin API</strong> - Extensible architecture for layouts and widgets</li>
<li><strong>100% Compatible</strong> - Existing code works unchanged</li>
</ul>
<p>See <a href="abstraction-layer.html">Abstraction Layer</a> for technical details.</p>
<h2>Protocol Compliance</h2>
<p>DWN implements standard X11 protocols for compatibility:</p>
<ul>
+15 -3
View File
@@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@@ -56,7 +58,7 @@
<div class="content">
<div class="hero">
<h1>DWN</h1>
<p class="tagline">A modern X11 window manager with AI integration and WebSocket API</p>
<p class="tagline">A modern X11 window manager with AI integration, WebSocket API, and plugin extensibility</p>
<div class="btn-group">
<a href="installation.html" class="btn">Get Started</a>
<a href="api-overview.html" class="btn btn-outline">API Docs</a>
@@ -67,7 +69,7 @@
<div class="feature-card">
<div class="feature-icon">🪟</div>
<div class="feature-title">Multiple Layouts</div>
<div class="feature-desc">Tiling, floating, and monocle layouts with per-workspace configuration. Master-stack tiling with adjustable ratios.</div>
<div class="feature-desc">Tiling, floating, monocle, and grid layouts with per-workspace configuration. Plugin API for custom layouts.</div>
</div>
<div class="feature-card">
<div class="feature-icon">🖥️</div>
@@ -94,6 +96,16 @@
<div class="feature-title">Notifications</div>
<div class="feature-desc">Built-in D-Bus notification daemon with configurable appearance.</div>
</div>
<div class="feature-card">
<div class="feature-icon">🔧</div>
<div class="feature-title">Plugin System</div>
<div class="feature-desc">Extensible plugin architecture for layouts and widgets. Dynamic loading support.</div>
</div>
<div class="feature-card">
<div class="feature-icon">📐</div>
<div class="feature-title">Abstraction Layer</div>
<div class="feature-desc">Backend-agnostic architecture. Ready for X11, Wayland, and headless backends.</div>
</div>
</div>
<div class="quick-start">
+3 -1
View File
@@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
+152 -6
View File
@@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@@ -66,15 +68,20 @@
<li><a href="#tiling">Tiling Layout</a></li>
<li><a href="#floating">Floating Layout</a></li>
<li><a href="#monocle">Monocle Layout</a></li>
<li><a href="#centered-master">Centered Master Layout</a></li>
<li><a href="#columns">Columns Layout</a></li>
<li><a href="#fibonacci">Fibonacci Layout</a></li>
<li><a href="#grid">Grid Layout</a></li>
<li><a href="#plugin-system">Plugin System</a></li>
<li><a href="#per-workspace">Per-Workspace Settings</a></li>
<li><a href="#shortcuts">Layout Shortcuts</a></li>
</ul>
</div>
<h2 id="overview">Overview</h2>
<p>DWN provides three layout modes that determine how windows are arranged on screen. Each workspace maintains its own layout settings independently.</p>
<p>DWN provides six layout modes that determine how windows are arranged on screen. Each workspace maintains its own layout settings independently.</p>
<p>Press <code>Super+Space</code> to cycle through layouts: Tiling → Floating → Monocle → Tiling</p>
<p>Press <code>Super+Space</code> to cycle through layouts: Tiling → Floating → Monocle → Centered Master → Columns → Fibonacci → Tiling</p>
<div class="alert alert-info">
<strong>Tip:</strong> The current layout is shown in the panel with a symbol: <code>[]=</code> for tiling, <code>&gt;&lt;&gt;</code> for floating, <code>[M]</code> for monocle.
@@ -182,6 +189,141 @@
<strong>Note:</strong> Windows maintain decorations in monocle mode unless fullscreen (<code>Alt+F11</code>).
</div>
<h2 id="centered-master">Centered Master Layout</h2>
<p>The master window is centered on screen with stack windows divided on left and right sides.</p>
<h3>How It Works</h3>
<div class="code-block">
<pre><code>+--------+------------------+--------+
| Stack | Master | Stack |
| 1 | Window | 3 |
+--------+ +--------+
| Stack | | Stack |
| 2 | | 4 |
+--------+------------------+--------+</code></pre>
</div>
<ul>
<li>The <strong>master window</strong> occupies the center of the screen</li>
<li>Stack windows are divided between left and right sides</li>
<li>Odd-numbered stack windows go left, even go right</li>
<li>Excellent for wide monitors and ultrawide displays</li>
</ul>
<h3>Use Cases</h3>
<ul>
<li>Keeping primary work centered while referencing side content</li>
<li>Video editing with timeline and tools on sides</li>
<li>Development with editor centered and documentation on sides</li>
</ul>
<h2 id="columns">Columns Layout</h2>
<p>All windows arranged in equal-width vertical columns spanning the full height.</p>
<h3>How It Works</h3>
<div class="code-block">
<pre><code>+----------+----------+----------+----------+
| | | | |
| Window | Window | Window | Window |
| 1 | 2 | 3 | 4 |
| | | | |
| | | | |
+----------+----------+----------+----------+</code></pre>
</div>
<ul>
<li>Each window gets an equal-width column</li>
<li>Windows span the full usable height</li>
<li>Simple and predictable arrangement</li>
</ul>
<h3>Use Cases</h3>
<ul>
<li>Comparing multiple files side-by-side</li>
<li>Monitoring multiple log files</li>
<li>Multi-column text editing</li>
<li>Concurrent terminal sessions</li>
</ul>
<h2 id="fibonacci">Fibonacci Layout</h2>
<p>Windows arranged in a spiral pattern using recursive splitting, inspired by the Fibonacci sequence.</p>
<h3>How It Works</h3>
<div class="code-block">
<pre><code>+------------------+----------+
| | |
| Window 1 | Window 2 |
| +----+-----+
| | W3 | |
+------------------+----+ W4 |
| Window 5 | |
+-----------------------+-----+</code></pre>
</div>
<ul>
<li>First window takes half the screen</li>
<li>Each subsequent window takes half the remaining space</li>
<li>Alternates between horizontal and vertical splits</li>
<li>Creates a visually interesting spiral pattern</li>
</ul>
<h3>Use Cases</h3>
<ul>
<li>Hierarchical window importance (larger = more important)</li>
<li>Primary workspace with progressively smaller utilities</li>
<li>Creative workflows with main canvas and tool windows</li>
</ul>
<h2 id="grid">Grid Layout</h2>
<p>Windows are arranged in a grid pattern with automatic row/column calculation.</p>
<h3>How It Works</h3>
<div class="code-block">
<pre><code>+----------+----------+----------+
| Window | Window | Window |
| 1 | 2 | 3 |
+----------+----------+----------+
| Window | Window |
| 4 | 5 |
+----------+----------+</code></pre>
</div>
<ul>
<li>Automatically calculates optimal rows and columns</li>
<li>Uses square root for balanced grid</li>
<li>All windows have equal size</li>
<li>Great for viewing multiple documents simultaneously</li>
</ul>
<h3>Use Cases</h3>
<ul>
<li>Comparing multiple documents or files</li>
<li>Monitoring multiple terminals</li>
<li>Dashboard-style workflows</li>
<li>Multi-way video calls</li>
</ul>
<h2 id="plugin-system">Plugin System (v2.0)</h2>
<p>DWN v2.0 introduces a layout plugin system that allows custom layout algorithms to be loaded dynamically or built-in.</p>
<h3>Built-in Layouts</h3>
<ul>
<li><strong>tiling</strong> - Master-stack tiling (default)</li>
<li><strong>floating</strong> - Traditional floating windows</li>
<li><strong>monocle</strong> - Single maximized window</li>
<li><strong>centered-master</strong> - Master centered with stacks on sides</li>
<li><strong>columns</strong> - Equal-width vertical columns</li>
<li><strong>fibonacci</strong> - Spiral recursive splitting</li>
<li><strong>grid</strong> - Grid arrangement</li>
</ul>
<h3>Custom Layouts</h3>
<p>Developers can create custom layout plugins using the Layout Plugin API. See <a href="plugin-development.html">Plugin Development</a> for details.</p>
<div class="alert alert-info">
<strong>Plugin API:</strong> Layouts implement the <code>LayoutPluginInterface</code> vtable with an <code>arrange()</code> method that calculates window geometries.
</div>
<h2 id="per-workspace">Per-Workspace Settings</h2>
<p>Each workspace maintains independent layout settings:</p>
@@ -198,7 +340,7 @@
<tr>
<td>Layout Mode</td>
<td>Per-workspace</td>
<td>Tiling, floating, or monocle</td>
<td>Tiling, floating, monocle, centered-master, columns, or fibonacci</td>
</tr>
<tr>
<td>Master Ratio</td>
@@ -224,7 +366,9 @@
<li>Workspace 1: Tiling layout for coding (editor + terminal)</li>
<li>Workspace 2: Floating layout for design work</li>
<li>Workspace 3: Monocle layout for focused writing</li>
<li>Workspace 4: Tiling with master count 2 for comparison</li>
<li>Workspace 4: Centered-master for wide monitor development</li>
<li>Workspace 5: Columns layout for log monitoring</li>
<li>Workspace 6: Fibonacci for hierarchical work</li>
</ul>
<h2 id="shortcuts">Layout Shortcuts</h2>
@@ -278,7 +422,9 @@
<div class="code-block">
<pre><code>[layout]
default = tiling # Default layout for new workspaces
# Default layout for new workspaces
# Options: tiling, floating, monocle, centered-master, columns, fibonacci
default = tiling
master_ratio = 0.55 # Default master area ratio (0.1-0.9)
master_count = 1 # Default master window count (1-10)
+87
View File
@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Plugin Development - DWN Documentation</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<button class="mobile-menu-btn">Menu</button>
<div class="layout">
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
<input type="text" class="search-input" placeholder="Search docs...">
</div>
<nav class="sidebar-nav">
<div class="nav-section">
<div class="nav-section-title">Getting Started</div>
<a href="index.html" class="nav-link">Introduction</a>
<a href="installation.html" class="nav-link">Installation</a>
<a href="quickstart.html" class="nav-link">Quick Start</a>
</div>
<div class="nav-section">
<div class="nav-section-title">User Guide</div>
<a href="features.html" class="nav-link">Features</a>
<a href="shortcuts.html" class="nav-link">Keyboard Shortcuts</a>
<a href="configuration.html" class="nav-link">Configuration</a>
<a href="layouts.html" class="nav-link">Layouts</a>
<a href="ai-features.html" class="nav-link">AI Integration</a>
</div>
<div class="nav-section">
<div class="nav-section-title">API Reference</div>
<a href="api-overview.html" class="nav-link">API Overview</a>
<a href="api-reference.html" class="nav-link">API Reference</a>
<a href="api-examples.html" class="nav-link">API Examples</a>
</div>
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link active">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
</aside>
<main class="main-content">
<div class="content">
<div class="page-header">
<h1>Plugin Development</h1>
<p class="lead">Create custom layouts and widgets for DWN</p>
</div>
<h2>Overview</h2>
<p>DWN v2.0 introduces a plugin system for extending functionality. Two types of plugins are supported:</p>
<ul>
<li><strong>Layout Plugins</strong> - Custom window arrangement algorithms</li>
<li><strong>Widget Plugins</strong> - Panel components like taskbar, clock, system monitors</li>
</ul>
<h2>Layout Plugins</h2>
<p>Layout plugins implement the LayoutPluginInterface vtable. See the abstraction-layer.html documentation for details.</p>
<h2>Widget Plugins</h2>
<p>Widget plugins create panel components with custom rendering and event handling.</p>
<footer>
<p>DWN Window Manager - retoor &lt;retoor@molodetz.nl&gt;</p>
</footer>
</div>
</main>
</div>
<script src="js/main.js"></script>
</body>
</html>
+3 -1
View File
@@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
+86 -2
View File
@@ -13,7 +13,7 @@
<aside class="sidebar">
<div class="sidebar-header">
<h1>DWN</h1>
<span class="version">v1.0.0</span>
<span class="version">v2.0.0</span>
</div>
<div class="search-box">
@@ -47,6 +47,8 @@
<div class="nav-section">
<div class="nav-section-title">Advanced</div>
<a href="architecture.html" class="nav-link">Architecture</a>
<a href="abstraction-layer.html" class="nav-link">Abstraction Layer</a>
<a href="plugin-development.html" class="nav-link">Plugin Development</a>
<a href="building.html" class="nav-link">Building from Source</a>
</div>
</nav>
@@ -64,6 +66,8 @@
<ul class="toc-list">
<li><a href="#launchers">Application Launchers</a></li>
<li><a href="#windows">Window Management</a></li>
<li><a href="#keyboard-control">Keyboard Window Control</a></li>
<li><a href="#marks">Window Marks</a></li>
<li><a href="#workspaces">Workspace Navigation</a></li>
<li><a href="#layouts">Layout Control</a></li>
<li><a href="#snapping">Window Snapping</a></li>
@@ -148,6 +152,86 @@
</table>
</div>
<h2 id="keyboard-control">Keyboard Window Control</h2>
<p>Move and resize floating windows without using the mouse.</p>
<div class="table-container">
<table class="shortcut-table">
<thead>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Super+Alt+Left</code></td>
<td>Move window left by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Alt+Right</code></td>
<td>Move window right by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Alt+Up</code></td>
<td>Move window up by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Alt+Down</code></td>
<td>Move window down by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Ctrl+Left</code></td>
<td>Shrink window width by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Ctrl+Right</code></td>
<td>Grow window width by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Ctrl+Up</code></td>
<td>Shrink window height by 20 pixels</td>
</tr>
<tr>
<td><code>Super+Ctrl+Down</code></td>
<td>Grow window height by 20 pixels</td>
</tr>
</tbody>
</table>
</div>
<div class="alert alert-info">
<strong>Note:</strong> Window must be floating for keyboard move/resize to work. Toggle floating with <code>Super+F9</code>.
</div>
<h2 id="marks">Window Marks</h2>
<p>Mark windows with letters (a-z) for instant switching, similar to Vim marks.</p>
<div class="table-container">
<table class="shortcut-table">
<thead>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Super+M</code> then <code>a-z</code></td>
<td>Mark current window with letter</td>
</tr>
<tr>
<td><code>Super+'</code> then <code>a-z</code></td>
<td>Jump to window marked with letter</td>
</tr>
</tbody>
</table>
</div>
<div class="alert alert-info">
<strong>Tip:</strong> Use marks to quickly switch between frequently used windows. For example, mark your editor with <code>e</code> and terminal with <code>t</code>.
</div>
<h2 id="workspaces">Workspace Navigation</h2>
<div class="table-container">
<table class="shortcut-table">
@@ -190,7 +274,7 @@
<tbody>
<tr>
<td><code>Super+Space</code></td>
<td>Cycle layout mode (tiling → floating → monocle)</td>
<td>Cycle layout mode (tiling → floating → monocle → centered-master → columns → fibonacci)</td>
</tr>
<tr>
<td><code>Super+H</code></td>
-434
View File
@@ -1,434 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="AI integration in DWN window manager - command palette, semantic search, and context analysis.">
<title>AI Features - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html" class="active">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content">
<h1>AI Integration</h1>
<p class="subtitle">
Control your desktop with natural language and get intelligent assistance.
</p>
</div>
</section>
<section class="section">
<div class="container">
<div class="alert alert-info" style="margin-bottom: 2rem;">
<strong class="alert-title">Optional Features</strong>
<p style="margin: 0;">AI features are completely optional and require external API keys.
DWN works perfectly without them.</p>
</div>
<h2>Overview</h2>
<p>
DWN integrates with two AI services to provide intelligent desktop assistance:
</p>
<ul style="margin-bottom: 2rem;">
<li><strong>OpenRouter API</strong> - Powers the AI command palette and context analysis</li>
<li><strong>Exa API</strong> - Provides semantic web search capabilities</li>
</ul>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">&#129302;</div>
<h3>AI Command Palette</h3>
<p>Type natural language commands to control your desktop.
Launch apps, query system info, and more.</p>
<p style="margin-top: 1rem;"><kbd>Super</kbd> + <kbd>A</kbd></p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128065;</div>
<h3>Context Analysis</h3>
<p>AI analyzes your current workspace to understand your task
and provide relevant suggestions.</p>
<p style="margin-top: 1rem;"><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd></p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128269;</div>
<h3>Semantic Search</h3>
<p>Search the web using meaning, not just keywords.
Find relevant content instantly.</p>
<p style="margin-top: 1rem;"><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>E</kbd></p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128221;</div>
<h3>Auto-Organization</h3>
<p>Context-aware window placement and workspace optimization
based on your current workflow.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128276;</div>
<h3>Smart Notifications</h3>
<p>Intelligent filtering and prioritization of system notifications
using application context.</p>
</div>
</div>
<h2 id="openrouter" style="margin-top: 4rem;">Setting Up OpenRouter</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
OpenRouter provides access to multiple AI models through a single API.
You can use free models or paid ones depending on your needs.
</p>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Get an API Key</h4>
<p>Visit <a href="https://openrouter.ai/keys" target="_blank">https://openrouter.ai/keys</a>
and create a free account to get your API key.</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Set the Environment Variable</h4>
<p>Add to your shell profile (~/.bashrc, ~/.zshrc, etc.):</p>
<div class="code-header">
<span>~/.bashrc</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>export OPENROUTER_API_KEY="sk-or-v1-your-key-here"</code></pre>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>Choose a Model (Optional)</h4>
<p>Configure the AI model in your DWN config file:</p>
<div class="code-header">
<span>~/.config/dwn/config</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[ai]
model = google/gemini-2.0-flash-exp:free</code></pre>
<p style="margin-top: 0.5rem; font-size: 0.875rem; color: var(--text-muted);">
Browse available models at <a href="https://openrouter.ai/models" target="_blank">openrouter.ai/models</a>
</p>
</div>
</div>
</div>
<div class="card" style="margin-top: 2rem;">
<h3>Recommended Free Models</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Model ID</th>
<th>Provider</th>
<th>Best For</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>google/gemini-2.0-flash-exp:free</code></td>
<td>Google</td>
<td>Fast responses, good general use</td>
</tr>
<tr>
<td><code>meta-llama/llama-3.2-3b-instruct:free</code></td>
<td>Meta</td>
<td>Quick commands, lightweight</td>
</tr>
<tr>
<td><code>mistralai/mistral-7b-instruct:free</code></td>
<td>Mistral</td>
<td>Balanced performance</td>
</tr>
</tbody>
</table>
</div>
</div>
<h2 id="command-palette" style="margin-top: 4rem;">AI Command Palette</h2>
<p>
Press <kbd>Super</kbd> + <kbd>A</kbd> to open the command palette.
Type natural language commands and press Enter.
</p>
<h3>Supported Commands</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Category</th>
<th>Examples</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Launch Applications</strong></td>
<td>
"open firefox"<br>
"run terminal"<br>
"launch file manager"<br>
"start chrome"
</td>
</tr>
<tr>
<td><strong>System Queries</strong></td>
<td>
"what time is it"<br>
"how much memory is free"<br>
"what's my IP address"<br>
"show disk usage"
</td>
</tr>
<tr>
<td><strong>General Questions</strong></td>
<td>
"how do I resize the master area"<br>
"what's the shortcut for fullscreen"<br>
"explain tiling mode"
</td>
</tr>
</tbody>
</table>
</div>
<div class="alert alert-success" style="margin-top: 1.5rem;">
<strong class="alert-title">Pro Tip</strong>
<p style="margin: 0;">The AI understands context. You can say "open browser" instead of
remembering the exact application name - it will figure out what you mean.</p>
</div>
<h2 id="context" style="margin-top: 4rem;">Context Analysis</h2>
<p>
Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd> to see AI-powered analysis of your current workspace.
</p>
<div class="card">
<h3>What It Shows</h3>
<ul style="padding-left: 1.25rem;">
<li><strong>Task Type</strong> - Coding, browsing, communication, etc.</li>
<li><strong>Focused Window</strong> - Currently active application</li>
<li><strong>Suggestions</strong> - Relevant shortcuts or actions based on context</li>
<li><strong>Workspace Summary</strong> - Overview of open applications</li>
</ul>
</div>
<h2 id="exa" style="margin-top: 4rem;">Setting Up Exa Search</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Exa provides semantic search - finding content based on meaning rather than exact keywords.
</p>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Get an Exa API Key</h4>
<p>Visit <a href="https://dashboard.exa.ai/api-keys" target="_blank">https://dashboard.exa.ai/api-keys</a>
and create an account to get your API key.</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Set the Environment Variable</h4>
<div class="code-header">
<span>~/.bashrc</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>export EXA_API_KEY="your-exa-key-here"</code></pre>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>Start Searching</h4>
<p>Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>E</kbd> and type your query.</p>
</div>
</div>
</div>
<h2 id="search" style="margin-top: 4rem;">Using Semantic Search</h2>
<p>
Unlike traditional search, Exa understands the meaning of your query.
</p>
<div class="comparison" style="grid-template-columns: repeat(2, 1fr);">
<div class="comparison-card">
<h3>Traditional Search</h3>
<p style="color: var(--text-muted);">Keyword matching</p>
<ul>
<li>Exact keyword matches</li>
<li>Boolean operators needed</li>
<li>Miss relevant results</li>
</ul>
<p style="margin-top: 1rem; font-style: italic; color: var(--text-muted);">
"nginx reverse proxy setup tutorial"
</p>
</div>
<div class="comparison-card featured">
<h3>Semantic Search</h3>
<p style="color: var(--text-muted);">Meaning-based</p>
<ul>
<li>Understands intent</li>
<li>Natural language</li>
<li>Finds related content</li>
</ul>
<p style="margin-top: 1rem; font-style: italic; color: var(--text-muted);">
"how to configure nginx as a reverse proxy"
</p>
</div>
</div>
<h3 style="margin-top: 2rem;">Search Tips</h3>
<ul>
<li>Use natural, conversational queries</li>
<li>Be specific about what you're looking for</li>
<li>Results appear in a dmenu/rofi list - select to open in browser</li>
<li>Search includes articles, documentation, tutorials, and more</li>
</ul>
<h2 id="privacy" style="margin-top: 4rem;">Privacy Considerations</h2>
<div class="alert alert-warning">
<strong class="alert-title">Data Sent to External Services</strong>
<p style="margin: 0;">When using AI features, the following data is sent to external APIs:</p>
</div>
<div class="table-wrapper" style="margin-top: 1rem;">
<table>
<thead>
<tr>
<th>Feature</th>
<th>Data Sent</th>
<th>Service</th>
</tr>
</thead>
<tbody>
<tr>
<td>Command Palette</td>
<td>Your typed command</td>
<td>OpenRouter (then to model provider)</td>
</tr>
<tr>
<td>Context Analysis</td>
<td>Window titles, app names</td>
<td>OpenRouter (then to model provider)</td>
</tr>
<tr>
<td>Semantic Search</td>
<td>Your search query</td>
<td>Exa</td>
</tr>
</tbody>
</table>
</div>
<p style="margin-top: 1rem; color: var(--text-muted);">
If you're concerned about privacy, you can:
</p>
<ul style="color: var(--text-muted);">
<li>Not configure API keys (AI features simply won't work)</li>
<li>Use OpenRouter with privacy-focused models</li>
<li>Only use AI features when needed</li>
</ul>
<h2 id="troubleshooting" style="margin-top: 4rem;">Troubleshooting</h2>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
AI commands don't work - "API key not configured"
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>Make sure your API key is properly set:</p>
<ol style="padding-left: 1.25rem; margin-top: 0.5rem;">
<li>Check the environment variable: <code>echo $OPENROUTER_API_KEY</code></li>
<li>Ensure the variable is exported in your shell profile</li>
<li>Log out and back in, or source your profile: <code>source ~/.bashrc</code></li>
<li>Restart DWN</li>
</ol>
</div>
</div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
Slow responses from AI
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>Try using a faster model. Free models can sometimes be slow due to rate limiting.
Gemini Flash is usually the fastest free option.</p>
</div>
</div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
Exa search returns no results
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>Check your Exa API key and ensure you have remaining credits.
Visit the <a href="https://dashboard.exa.ai">Exa dashboard</a> to check your usage.</p>
</div>
</div>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>
-550
View File
@@ -1,550 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Technical architecture documentation for DWN window manager - codebase structure, modules, and internals.">
<title>Architecture - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html" class="active">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content">
<h1>Architecture</h1>
<p class="subtitle">
Technical documentation for developers and contributors.
</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>Overview</h2>
<p>
DWN is written in ANSI C (C99) and follows a modular single-responsibility architecture.
A global <code>DWNState</code> singleton manages all state, and the main event loop
dispatches X11 events to specialized modules.
</p>
<div class="card" style="margin: 2rem 0;">
<h3>Project Statistics</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; text-align: center;">
<div>
<div style="font-size: 2rem; font-weight: 700; color: var(--primary);">~10K</div>
<div style="color: var(--text-muted);">Lines of Code</div>
</div>
<div>
<div style="font-size: 2rem; font-weight: 700; color: var(--primary);">13</div>
<div style="color: var(--text-muted);">Core Modules</div>
</div>
<div>
<div style="font-size: 2rem; font-weight: 700; color: var(--primary);">C99</div>
<div style="color: var(--text-muted);">Standard</div>
</div>
<div>
<div style="font-size: 2rem; font-weight: 700; color: var(--primary);">MIT</div>
<div style="color: var(--text-muted);">License</div>
</div>
</div>
</div>
<h2 id="structure" style="margin-top: 3rem;">Directory Structure</h2>
<div class="code-header">
<span>Project Layout</span>
</div>
<pre><code>dwn/
├── src/ # Source files (.c)
│ ├── main.c # Entry point, event loop
│ ├── client.c # Window management
│ ├── workspace.c # Virtual desktops
│ ├── layout.c # Tiling algorithms
│ ├── decorations.c # Title bars, borders
│ ├── panel.c # Top/bottom panels
│ ├── systray.c # System tray widgets
│ ├── notifications.c # D-Bus notifications
│ ├── atoms.c # X11 atoms (EWMH/ICCCM)
│ ├── keys.c # Keyboard handling
│ ├── config.c # INI parser
│ ├── ai.c # AI integration
│ └── util.c # Utilities
├── include/ # Header files (.h)
├── site/ # Documentation website
├── Makefile # Build system
├── CLAUDE.md # AI assistant context
└── README.md # Project readme</code></pre>
<h2 id="modules" style="margin-top: 3rem;">Core Modules</h2>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Module</th>
<th>File</th>
<th>Responsibility</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Main</strong></td>
<td><code>main.c</code></td>
<td>X11 initialization, event loop, signal handling, module orchestration</td>
</tr>
<tr>
<td><strong>Client</strong></td>
<td><code>client.c</code></td>
<td>Window lifecycle, focus management, frame creation, client list</td>
</tr>
<tr>
<td><strong>Workspace</strong></td>
<td><code>workspace.c</code></td>
<td>9 virtual desktops, per-workspace state, window assignment</td>
</tr>
<tr>
<td><strong>Layout</strong></td>
<td><code>layout.c</code></td>
<td>Tiling (master+stack), floating, monocle layout algorithms</td>
</tr>
<tr>
<td><strong>Decorations</strong></td>
<td><code>decorations.c</code></td>
<td>Window title bars, borders, decoration rendering</td>
</tr>
<tr>
<td><strong>Panel</strong></td>
<td><code>panel.c</code></td>
<td>Top panel (taskbar, workspace indicators), bottom panel (clock)</td>
</tr>
<tr>
<td><strong>Systray</strong></td>
<td><code>systray.c</code></td>
<td>System tray with WiFi/audio/battery indicators, dropdowns</td>
</tr>
<tr>
<td><strong>Notifications</strong></td>
<td><code>notifications.c</code></td>
<td>D-Bus notification daemon (org.freedesktop.Notifications)</td>
</tr>
<tr>
<td><strong>Atoms</strong></td>
<td><code>atoms.c</code></td>
<td>X11 EWMH/ICCCM atom management and property handling</td>
</tr>
<tr>
<td><strong>Keys</strong></td>
<td><code>keys.c</code></td>
<td>Keyboard shortcut capture, keybinding registry, callbacks</td>
</tr>
<tr>
<td><strong>Config</strong></td>
<td><code>config.c</code></td>
<td>INI-style config loading and parsing</td>
</tr>
<tr>
<td><strong>AI</strong></td>
<td><code>ai.c</code></td>
<td>Async OpenRouter API integration, Exa semantic search</td>
</tr>
<tr>
<td><strong>News</strong></td>
<td><code>news.c</code></td>
<td>News ticker with API integration, scrolling animation, article navigation</td>
</tr>
<tr>
<td><strong>Autostart</strong></td>
<td><code>autostart.c</code></td>
<td>XDG Autostart support, .desktop file parsing, concurrent app launch</td>
</tr>
<tr>
<td><strong>Services</strong></td>
<td><code>services.c</code></td>
<td>Background service management, process lifecycle control</td>
</tr>
<tr>
<td><strong>Demo</strong></td>
<td><code>demo.c</code></td>
<td>Automated feature demonstration mode, tutorial system</td>
</tr>
<tr>
<td><strong>API</strong></td>
<td><code>api.c</code></td>
<td>WebSocket server, JSON-RPC command handler, remote control interface</td>
</tr>
<tr>
<td><strong>Util</strong></td>
<td><code>util.c</code></td>
<td>Logging, memory allocation, string utilities, file helpers</td>
</tr>
</tbody>
</table>
</div>
<h2 id="dependencies" style="margin-top: 3rem;">Module Dependencies</h2>
<div class="card">
<pre style="margin: 0; background: transparent; border: none; padding: 0;"><code>main.c (orchestrator)
├── client.c
│ ├── decorations.c
│ ├── config.c
│ └── atoms.c
├── workspace.c
│ ├── client.c
│ ├── layout.c
│ └── atoms.c
├── panel.c
│ ├── client.c
│ └── config.c
├── systray.c
│ └── config.c
├── notifications.c (independent)
├── ai.c (independent)
├── autostart.c
│ └── config.c
└── keys.c
└── config.c</code></pre>
</div>
<h2 id="state" style="margin-top: 3rem;">Global State (DWNState)</h2>
<p>
All window manager state is centralized in a single <code>DWNState</code> structure.
This simplifies state management and makes the codebase easier to understand.
</p>
<div class="code-header">
<span>include/dwn.h (simplified)</span>
</div>
<pre><code>typedef struct {
Display *display; // X11 connection
Window root; // Root window
int screen; // Default screen
Client *clients[MAX_CLIENTS]; // All managed windows
int client_count;
Workspace workspaces[MAX_WORKSPACES]; // Virtual desktops
int current_workspace;
Panel top_panel;
Panel bottom_panel;
Config config; // User configuration
KeyBinding keys[MAX_KEYBINDINGS];
// EWMH atoms
Atom atoms[ATOM_COUNT];
} DWNState;
extern DWNState *dwn; // Global singleton</code></pre>
<h2 id="events" style="margin-top: 3rem;">Event Loop</h2>
<p>
DWN uses a traditional X11 event loop with XNextEvent. Events are dispatched
to appropriate handlers based on type.
</p>
<div class="code-header">
<span>main.c (simplified)</span>
</div>
<pre><code>int main(int argc, char *argv[]) {
dwn_init(); // Initialize X11, atoms, config
setup_keybindings(); // Register keyboard shortcuts
setup_panels(); // Create panel windows
XEvent event;
while (running) {
XNextEvent(dwn->display, &event);
switch (event.type) {
case MapRequest:
handle_map_request(&event.xmaprequest);
break;
case UnmapNotify:
handle_unmap_notify(&event.xunmap);
break;
case KeyPress:
handle_key_press(&event.xkey);
break;
case ButtonPress:
handle_button_press(&event.xbutton);
break;
case ConfigureRequest:
handle_configure_request(&event.xconfigurerequest);
break;
// ... more event types
}
}
dwn_cleanup();
return 0;
}</code></pre>
<h2 id="constants" style="margin-top: 3rem;">Key Constants</h2>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Constant</th>
<th>Value</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>MAX_CLIENTS</code></td>
<td>256</td>
<td>Maximum managed windows</td>
</tr>
<tr>
<td><code>MAX_WORKSPACES</code></td>
<td>9</td>
<td>Number of virtual desktops</td>
</tr>
<tr>
<td><code>MAX_MONITORS</code></td>
<td>8</td>
<td>Multi-monitor support limit</td>
</tr>
<tr>
<td><code>MAX_NOTIFICATIONS</code></td>
<td>32</td>
<td>Concurrent notifications</td>
</tr>
<tr>
<td><code>MAX_KEYBINDINGS</code></td>
<td>64</td>
<td>Registered keyboard shortcuts</td>
</tr>
</tbody>
</table>
</div>
<h2 id="conventions" style="margin-top: 3rem;">Coding Conventions</h2>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card">
<h3>Naming</h3>
<ul style="padding-left: 1.25rem;">
<li><code>snake_case</code> for functions and variables</li>
<li><code>CamelCase</code> for types and structs</li>
<li>Module prefix for functions (e.g., <code>client_focus()</code>)</li>
<li>Constants in <code>UPPER_SNAKE_CASE</code></li>
</ul>
</div>
<div class="card">
<h3>Style</h3>
<ul style="padding-left: 1.25rem;">
<li>4-space indentation</li>
<li>K&R brace style</li>
<li>Max 100 characters per line</li>
<li>clang-format for consistency</li>
</ul>
</div>
</div>
<div class="code-header" style="margin-top: 1.5rem;">
<span>Example Function</span>
</div>
<pre><code>void client_focus(Client *c) {
if (!c) return;
// Unfocus previous
if (dwn->focused && dwn->focused != c) {
client_unfocus(dwn->focused);
}
dwn->focused = c;
XSetInputFocus(dwn->display, c->window, RevertToPointerRoot, CurrentTime);
XRaiseWindow(dwn->display, c->frame);
decorations_update(c);
atoms_set_active_window(c->window);
}</code></pre>
<h2 id="protocols" style="margin-top: 3rem;">EWMH/ICCCM Support</h2>
<p>
DWN implements key Extended Window Manager Hints and ICCCM protocols
for compatibility with modern applications.
</p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card">
<h3>EWMH Atoms</h3>
<ul style="padding-left: 1.25rem; font-family: var(--font-mono); font-size: 0.875rem;">
<li>_NET_SUPPORTED</li>
<li>_NET_CLIENT_LIST</li>
<li>_NET_CLIENT_LIST_STACKING</li>
<li>_NET_ACTIVE_WINDOW</li>
<li>_NET_CURRENT_DESKTOP</li>
<li>_NET_NUMBER_OF_DESKTOPS</li>
<li>_NET_WM_STATE</li>
<li>_NET_WM_STATE_FULLSCREEN</li>
<li>_NET_WM_STATE_MAXIMIZED_*</li>
<li>_NET_WM_WINDOW_TYPE</li>
<li>_NET_WM_NAME</li>
</ul>
</div>
<div class="card">
<h3>ICCCM Support</h3>
<ul style="padding-left: 1.25rem; font-family: var(--font-mono); font-size: 0.875rem;">
<li>WM_STATE</li>
<li>WM_PROTOCOLS</li>
<li>WM_DELETE_WINDOW</li>
<li>WM_TAKE_FOCUS</li>
<li>WM_NORMAL_HINTS</li>
<li>WM_SIZE_HINTS</li>
<li>WM_CLASS</li>
<li>WM_NAME</li>
<li>WM_TRANSIENT_FOR</li>
</ul>
</div>
</div>
<h2 id="build" style="margin-top: 3rem;">Build System</h2>
<p>
DWN uses a simple Makefile-based build system with pkg-config for dependency detection.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Target</th>
<th>Command</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Build (release)</td>
<td><code>make</code></td>
<td>Optimized build with -O2</td>
</tr>
<tr>
<td>Build (debug)</td>
<td><code>make debug</code></td>
<td>Debug symbols, -DDEBUG flag</td>
</tr>
<tr>
<td>Install</td>
<td><code>sudo make install</code></td>
<td>Install to PREFIX (/usr/local)</td>
</tr>
<tr>
<td>Clean</td>
<td><code>make clean</code></td>
<td>Remove build artifacts</td>
</tr>
<tr>
<td>Format</td>
<td><code>make format</code></td>
<td>Run clang-format on sources</td>
</tr>
<tr>
<td>Check</td>
<td><code>make check</code></td>
<td>Run cppcheck static analysis</td>
</tr>
<tr>
<td>Test</td>
<td><code>make run</code></td>
<td>Run in Xephyr nested server</td>
</tr>
<tr>
<td>Dependencies</td>
<td><code>make deps</code></td>
<td>Auto-install for your distro</td>
</tr>
</tbody>
</table>
</div>
<h2 id="contributing" style="margin-top: 3rem;">Contributing</h2>
<p>
Contributions are welcome! Here's how to get started:
</p>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Fork & Clone</h4>
<p>Fork the repository and clone your fork locally.</p>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Create a Branch</h4>
<p>Create a feature branch: <code>git checkout -b feature/my-feature</code></p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>Make Changes</h4>
<p>Follow coding conventions. Run <code>make format</code> and <code>make check</code>.</p>
</div>
</div>
<div class="step">
<div class="step-number">4</div>
<div class="step-content">
<h4>Test</h4>
<p>Test your changes with <code>make run</code> in a nested X server.</p>
</div>
</div>
<div class="step">
<div class="step-number">5</div>
<div class="step-content">
<h4>Submit PR</h4>
<p>Push your branch and open a pull request with a clear description.</p>
</div>
</div>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>
-578
View File
@@ -1,578 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Complete configuration guide for DWN window manager - customize colors, behavior, and more.">
<title>Configuration - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html" class="active">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content">
<h1>Configuration Guide</h1>
<p class="subtitle">
Customize every aspect of DWN to match your workflow and style.
</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>Configuration File</h2>
<p>
DWN reads its configuration from <code>~/.config/dwn/config</code> using an INI-style format.
Changes take effect on restart (or you can reload in a future version).
</p>
<div class="alert alert-info">
<strong class="alert-title">First Run</strong>
<p style="margin: 0;">DWN creates a default configuration file on first run if one doesn't exist.
You can also copy the example config from the source repository.</p>
</div>
<h2 id="general" style="margin-top: 3rem;">[general] - Core Settings</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Basic behavior settings for applications and focus handling.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>terminal</code></td>
<td><code>xfce4-terminal</code></td>
<td>Terminal emulator launched with Ctrl+Alt+T</td>
</tr>
<tr>
<td><code>launcher</code></td>
<td><code>dmenu_run</code></td>
<td>Application launcher for Alt+F2</td>
</tr>
<tr>
<td><code>file_manager</code></td>
<td><code>thunar</code></td>
<td>File manager for Super+E</td>
</tr>
<tr>
<td><code>focus_mode</code></td>
<td><code>click</code></td>
<td><code>click</code> or <code>follow</code> (sloppy focus)</td>
</tr>
<tr>
<td><code>focus_follow_delay</code></td>
<td><code>100</code></td>
<td>Delay in ms before focus switches in follow mode (0-1000)</td>
</tr>
<tr>
<td><code>decorations</code></td>
<td><code>true</code></td>
<td>Show window title bars and borders</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[general]
terminal = alacritty
launcher = rofi -show run
file_manager = nautilus
focus_mode = follow
focus_follow_delay = 100
decorations = true</code></pre>
<h2 id="appearance" style="margin-top: 3rem;">[appearance] - Visual Settings</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Control the visual appearance of windows, panels, and gaps.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Default</th>
<th>Range</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>border_width</code></td>
<td><code>0</code></td>
<td>0-50</td>
<td>Window border width in pixels (0 for seamless design)</td>
</tr>
<tr>
<td><code>title_height</code></td>
<td><code>28</code></td>
<td>0-100</td>
<td>Title bar height in pixels</td>
</tr>
<tr>
<td><code>panel_height</code></td>
<td><code>32</code></td>
<td>0-100</td>
<td>Top/bottom panel height</td>
</tr>
<tr>
<td><code>gap</code></td>
<td><code>0</code></td>
<td>0-100</td>
<td>Gap between tiled windows (0 for edge-to-edge tiling)</td>
</tr>
<tr>
<td><code>font</code></td>
<td><code>fixed</code></td>
<td>-</td>
<td>X11 font name for text</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[appearance]
border_width = 0
title_height = 28
panel_height = 32
gap = 0
font = fixed</code></pre>
<h2 id="layout" style="margin-top: 3rem;">[layout] - Layout Behavior</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Configure the default layout mode and tiling parameters.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Default</th>
<th>Range</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>default</code></td>
<td><code>tiling</code></td>
<td>-</td>
<td><code>tiling</code>, <code>floating</code>, or <code>monocle</code></td>
</tr>
<tr>
<td><code>master_ratio</code></td>
<td><code>0.55</code></td>
<td>0.1-0.9</td>
<td>Portion of screen for master area</td>
</tr>
<tr>
<td><code>master_count</code></td>
<td><code>1</code></td>
<td>1-10</td>
<td>Number of windows in master area</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[layout]
default = tiling
master_ratio = 0.60
master_count = 1</code></pre>
<h2 id="panels" style="margin-top: 3rem;">[panels] - Panel Visibility</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Control which panels are displayed.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>top</code></td>
<td><code>true</code></td>
<td>Show top panel (workspaces, taskbar, systray)</td>
</tr>
<tr>
<td><code>bottom</code></td>
<td><code>true</code></td>
<td>Show bottom panel (clock)</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example - Minimal Setup</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[panels]
top = true
bottom = false</code></pre>
<h2 id="colors" style="margin-top: 3rem;">[colors] - Color Scheme</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Customize all colors using hex format (#RRGGBB).
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Default</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>panel_bg</code></td>
<td><code>#1a1a2e</code></td>
<td>Panel background color</td>
</tr>
<tr>
<td><code>panel_fg</code></td>
<td><code>#e0e0e0</code></td>
<td>Panel text color</td>
</tr>
<tr>
<td><code>workspace_active</code></td>
<td><code>#4a90d9</code></td>
<td>Active workspace indicator</td>
</tr>
<tr>
<td><code>workspace_inactive</code></td>
<td><code>#3a3a4e</code></td>
<td>Inactive workspace indicator</td>
</tr>
<tr>
<td><code>workspace_urgent</code></td>
<td><code>#d94a4a</code></td>
<td>Urgent workspace indicator</td>
</tr>
<tr>
<td><code>title_focused_bg</code></td>
<td><code>#2d3a4a</code></td>
<td>Focused window title background</td>
</tr>
<tr>
<td><code>title_focused_fg</code></td>
<td><code>#ffffff</code></td>
<td>Focused window title text</td>
</tr>
<tr>
<td><code>title_unfocused_bg</code></td>
<td><code>#1a1a1a</code></td>
<td>Unfocused window title background</td>
</tr>
<tr>
<td><code>title_unfocused_fg</code></td>
<td><code>#808080</code></td>
<td>Unfocused window title text</td>
</tr>
<tr>
<td><code>border_focused</code></td>
<td><code>#4a90d9</code></td>
<td>Focused window border</td>
</tr>
<tr>
<td><code>border_unfocused</code></td>
<td><code>#333333</code></td>
<td>Unfocused window border</td>
</tr>
<tr>
<td><code>notification_bg</code></td>
<td><code>#2a2a3e</code></td>
<td>Notification background</td>
</tr>
<tr>
<td><code>notification_fg</code></td>
<td><code>#ffffff</code></td>
<td>Notification text</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example - Nord Theme</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[colors]
panel_bg = #2e3440
panel_fg = #eceff4
workspace_active = #88c0d0
workspace_inactive = #4c566a
workspace_urgent = #bf616a
title_focused_bg = #3b4252
title_focused_fg = #eceff4
title_unfocused_bg = #2e3440
title_unfocused_fg = #4c566a
border_focused = #88c0d0
border_unfocused = #3b4252
notification_bg = #3b4252
notification_fg = #eceff4</code></pre>
<h2 id="ai" style="margin-top: 3rem;">[ai] - AI Integration</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Configure AI features. See <a href="ai-features.html">AI Features</a> for full setup instructions.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>model</code></td>
<td>OpenRouter model ID (e.g., <code>google/gemini-2.0-flash-exp:free</code>)</td>
</tr>
<tr>
<td><code>openrouter_api_key</code></td>
<td>Your OpenRouter API key (or use environment variable)</td>
</tr>
<tr>
<td><code>exa_api_key</code></td>
<td>Your Exa API key (or use environment variable)</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[ai]
model = google/gemini-2.0-flash-exp:free
openrouter_api_key = sk-or-v1-your-key-here
exa_api_key = your-exa-key-here</code></pre>
<div class="alert alert-warning" style="margin-top: 1rem;">
<strong class="alert-title">Security Note</strong>
<p style="margin: 0;">For better security, use environment variables instead of storing API keys in the config file:
<code>export OPENROUTER_API_KEY=sk-or-v1-...</code></p>
</div>
<h2 id="autostart" style="margin-top: 3rem;">[autostart] - XDG Autostart</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Configure automatic application startup. DWN follows the XDG Autostart specification.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>enabled</code></td>
<td>Enable/disable all autostart functionality (default: <code>true</code>)</td>
</tr>
<tr>
<td><code>xdg_autostart</code></td>
<td>Scan XDG .desktop files from /etc/xdg/autostart and ~/.config/autostart (default: <code>true</code>)</td>
</tr>
<tr>
<td><code>path</code></td>
<td>Additional directory for symlinks/scripts (default: <code>~/.config/dwn/autostart.d</code>)</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[autostart]
enabled = true
xdg_autostart = true
path = ~/.config/dwn/autostart.d</code></pre>
<p style="color: var(--text-muted); margin-top: 1rem;">
<strong>Directories scanned:</strong>
</p>
<ul style="color: var(--text-muted);">
<li><code>/etc/xdg/autostart/</code> - System defaults (nm-applet, blueman, power-manager)</li>
<li><code>~/.config/autostart/</code> - User XDG autostart entries</li>
<li><code>~/.config/dwn/autostart.d/</code> - DWN-specific symlinks and scripts</li>
</ul>
<h2 id="demo" style="margin-top: 3rem;">[demo] - Demo Mode</h2>
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
Configure demo mode timing. The demo showcases DWN features including live AI and search functionality.
</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Option</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>step_delay</code></td>
<td>Time between demo steps in milliseconds (1000-30000, default: 4000)</td>
</tr>
<tr>
<td><code>ai_timeout</code></td>
<td>Timeout for AI/Exa API responses in milliseconds (5000-60000, default: 15000)</td>
</tr>
<tr>
<td><code>window_timeout</code></td>
<td>Timeout for window spawn operations in milliseconds (1000-30000, default: 5000)</td>
</tr>
</tbody>
</table>
</div>
<div class="code-header">
<span>Example</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[demo]
step_delay = 4000
ai_timeout = 15000
window_timeout = 5000</code></pre>
<h2 style="margin-top: 3rem;">Complete Configuration Example</h2>
<div class="code-header">
<span>~/.config/dwn/config</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># DWN Window Manager Configuration
# https://dwn.github.io
[general]
terminal = alacritty
launcher = rofi -show drun
file_manager = thunar
focus_mode = click
focus_follow_delay = 100
decorations = true
[appearance]
border_width = 0
title_height = 28
panel_height = 32
gap = 0
font = fixed
[layout]
default = tiling
master_ratio = 0.55
master_count = 1
[panels]
top = true
bottom = true
[colors]
panel_bg = #1a1a2e
panel_fg = #e0e0e0
workspace_active = #4a90d9
workspace_inactive = #3a3a4e
workspace_urgent = #d94a4a
title_focused_bg = #2d3a4a
title_focused_fg = #ffffff
title_unfocused_bg = #1a1a1a
title_unfocused_fg = #808080
border_focused = #4a90d9
border_unfocused = #333333
notification_bg = #2a2a3e
notification_fg = #ffffff
[ai]
model = google/gemini-2.0-flash-exp:free
# API keys via environment variables recommended
[autostart]
enabled = true
xdg_autostart = true
path = ~/.config/dwn/autostart.d
[demo]
step_delay = 4000
ai_timeout = 15000
window_timeout = 5000</code></pre>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>
-1085
View File
File diff suppressed because it is too large Load Diff
-779
View File
@@ -1,779 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="DWN Design Patterns - Comprehensive documentation of design patterns, architectural decisions, and research sources used in the DWN window manager.">
<meta name="keywords" content="design patterns, C programming, opaque pointer, vtable, factory pattern, observer pattern, EWMH, ICCCM, XEmbed">
<title>Design Patterns - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html" class="active">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero hero-small">
<div class="container">
<h1>Design Patterns & Architecture</h1>
<p class="subtitle">
Comprehensive documentation of the design patterns, architectural decisions,
and research that shaped DWN's implementation.
</p>
</div>
</section>
<section class="section">
<div class="container docs-container">
<aside class="docs-sidebar">
<h3>Contents</h3>
<ul>
<li><a href="#overview">Overview</a></li>
<li><a href="#c-patterns">C Design Patterns</a></li>
<li><a href="#opaque-pointer">Opaque Pointer</a></li>
<li><a href="#goto-cleanup">Goto Cleanup</a></li>
<li><a href="#vtable">Vtable Polymorphism</a></li>
<li><a href="#factory">Factory Pattern</a></li>
<li><a href="#observer">Observer Pattern</a></li>
<li><a href="#singleton">Singleton Pattern</a></li>
<li><a href="#double-fork">Double Fork Daemon</a></li>
<li><a href="#x11-protocols">X11 Protocols</a></li>
<li><a href="#ewmh">EWMH Specification</a></li>
<li><a href="#icccm">ICCCM Standard</a></li>
<li><a href="#xembed">XEmbed Protocol</a></li>
<li><a href="#systray">System Tray Protocol</a></li>
<li><a href="#xdg">XDG Specifications</a></li>
<li><a href="#async">Async Patterns</a></li>
<li><a href="#modular">Modular Architecture</a></li>
<li><a href="#defensive">Defensive Programming</a></li>
<li><a href="#sources">Research Sources</a></li>
</ul>
</aside>
<div class="docs-content">
<h2 id="overview">Overview</h2>
<p>
DWN is built using professional C design patterns that provide encapsulation,
modularity, and maintainability without the overhead of C++. This document
details the patterns used, the rationale behind architectural decisions,
and links to authoritative sources.
</p>
<div class="alert alert-info">
<strong class="alert-title">Design Philosophy</strong>
<p style="margin: 0;">
DWN prioritizes simplicity, readability, and defensive programming.
Each module has a single responsibility with well-defined interfaces.
The codebase follows the principle: "Simple is better than complex."
</p>
</div>
<h2 id="c-patterns" style="margin-top: 3rem;">C Design Patterns</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">
While C lacks native object-oriented features, these patterns provide
equivalent functionality with minimal overhead.
</p>
<h3 id="opaque-pointer">Opaque Pointer Pattern</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Hide implementation details from API consumers, enabling changes without recompilation.</p>
<h4>How It Works</h4>
<p>The header declares a pointer to an incomplete type. The struct definition
exists only in the implementation file, preventing direct member access.</p>
<div class="code-header">
<span>Header (public)</span>
</div>
<pre><code>typedef struct config_t *Config;
Config config_create(void);
void config_destroy(Config cfg);</code></pre>
<div class="code-header">
<span>Implementation (private)</span>
</div>
<pre><code>struct config_t {
int border_width;
char terminal[128];
};</code></pre>
<h4>Benefits</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>API consumers cannot access internal fields directly</li>
<li>Implementation can change without breaking client code</li>
<li>Enforces encapsulation at compile time</li>
</ul>
<h4>Used In</h4>
<p style="color: var(--text-muted);">
<code>config.c</code> - Configuration management
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://interrupt.memfault.com/blog/opaque-pointers" target="_blank">Practical Design Patterns: Opaque Pointers and Objects in C</a> - Memfault</li>
<li><a href="https://en.wikipedia.org/wiki/Opaque_pointer" target="_blank">Opaque Pointer</a> - Wikipedia</li>
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/DCL12-C.+Implement+abstract+data+types+using+opaque+types" target="_blank">DCL12-C: Implement abstract data types using opaque types</a> - SEI CERT C Coding Standard</li>
<li><a href="https://blog.mbedded.ninja/programming/design-patterns/opaque-pointers/" target="_blank">Opaque Pointers</a> - mbedded.ninja</li>
</ul>
</div>
<h3 id="goto-cleanup">Goto Cleanup Pattern</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Centralize resource cleanup in functions that acquire multiple resources,
preventing memory leaks and ensuring proper deallocation on all code paths.</p>
<h4>How It Works</h4>
<p>Resources are initialized to safe values (NULL). On error, execution jumps
to a cleanup label. The cleanup section safely releases all resources.</p>
<div class="code-header">
<span>Example</span>
</div>
<pre><code>int process_file(const char *path) {
char *buf = NULL;
FILE *f = NULL;
int status = -1;
buf = malloc(1024);
if (!buf) goto cleanup;
f = fopen(path, "r");
if (!f) goto cleanup;
// ... processing ...
status = 0;
cleanup:
free(buf); // safe: free(NULL) is no-op
if (f) fclose(f);
return status;
}</code></pre>
<h4>Benefits</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>Single cleanup point prevents code duplication</li>
<li>All error paths properly release resources</li>
<li>Used extensively in Linux kernel and SQLite</li>
</ul>
<h4>Used In</h4>
<p style="color: var(--text-muted);">
<code>config.c</code>, <code>news.c</code>, <code>ai.c</code> - File and network operations
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/MEM12-C.+Consider+using+a+goto+chain+when+leaving+a+function+on+error+when+using+and+releasing+resources" target="_blank">MEM12-C: Using goto chain for error handling</a> - SEI CERT C Coding Standard</li>
<li><a href="https://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-in-c" target="_blank">Using goto for error handling in C</a> - Eli Bendersky</li>
<li><a href="https://www.geeksforgeeks.org/c/using-goto-for-exception-handling-in-c/" target="_blank">Using goto for Exception Handling in C</a> - GeeksforGeeks</li>
<li><a href="https://ayende.com/blog/183521-C/error-handling-via-goto-in-c" target="_blank">Error handling via GOTO in C</a> - Ayende Rahien</li>
</ul>
</div>
<h3 id="vtable">Vtable Polymorphism</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Enable runtime polymorphism in C using function pointer tables,
allowing different implementations to share a common interface.</p>
<h4>How It Works</h4>
<p>A struct of function pointers (vtable) defines the interface. Objects contain
a pointer to their vtable. Calling through the vtable invokes the correct implementation.</p>
<div class="code-header">
<span>Example</span>
</div>
<pre><code>typedef struct Widget Widget;
typedef struct {
void (*draw)(Widget *self);
void (*destroy)(Widget *self);
} WidgetVtable;
struct Widget {
const WidgetVtable *vtable;
int x, y, width, height;
};
// Usage: widget->vtable->draw(widget);</code></pre>
<h4>Benefits</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>Runtime dispatch without language support</li>
<li>New implementations added without modifying existing code</li>
<li>Same pattern used by C++ compilers internally</li>
</ul>
<h4>Used In</h4>
<p style="color: var(--text-muted);">
<code>panel.c</code> - Widget rendering system
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://en.wikipedia.org/wiki/Virtual_method_table" target="_blank">Virtual Method Table</a> - Wikipedia</li>
<li><a href="https://embeddedartistry.com/fieldatlas/technique-inheritance-and-polymorphism-in-c/" target="_blank">Inheritance and Polymorphism in C</a> - Embedded Artistry</li>
<li><a href="https://www.state-machine.com/doc/AN_Simple_OOP_in_C.pdf" target="_blank">Object-Oriented Programming in C</a> - Quantum Leaps (PDF)</li>
<li><a href="https://www.embedded.com/programming-embedded-systems-polymorphism-in-c-2/" target="_blank">Programming embedded systems: polymorphism in C</a> - Embedded.com</li>
</ul>
</div>
<h3 id="factory">Factory Pattern</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Encapsulate object creation logic, allowing the system to create objects
without specifying their exact types.</p>
<h4>How It Works</h4>
<p>A factory function takes parameters describing what to create and returns
a pointer to the appropriate object type.</p>
<div class="code-header">
<span>Example</span>
</div>
<pre><code>typedef enum { WIDGET_BUTTON, WIDGET_LABEL } WidgetType;
Widget *widget_create(WidgetType type) {
switch (type) {
case WIDGET_BUTTON: return button_create();
case WIDGET_LABEL: return label_create();
default: return NULL;
}
}</code></pre>
<h4>Benefits</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>Centralizes object creation logic</li>
<li>New types added without changing client code</li>
<li>Supports Open/Closed Principle</li>
</ul>
<h4>Used In</h4>
<p style="color: var(--text-muted);">
<code>config.c</code>, <code>client.c</code>, <code>workspace.c</code> - Object lifecycle management
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://refactoring.guru/design-patterns/factory-method" target="_blank">Factory Method Pattern</a> - Refactoring Guru</li>
<li><a href="https://en.wikipedia.org/wiki/Factory_method_pattern" target="_blank">Factory Method Pattern</a> - Wikipedia</li>
<li><a href="https://sourcemaking.com/design_patterns" target="_blank">Design Patterns</a> - SourceMaking</li>
</ul>
</div>
<h3 id="observer">Observer Pattern</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Implement event-driven communication where subjects notify observers
of state changes without tight coupling.</p>
<h4>How It Works</h4>
<p>Observers register callback functions with subjects. When events occur,
the subject iterates through registered callbacks and invokes them.</p>
<div class="code-header">
<span>Example</span>
</div>
<pre><code>typedef void (*EventCallback)(void *data);
void events_register(EventType type, EventCallback cb, void *data);
void events_emit(EventType type);
// Notification triggers all registered callbacks</code></pre>
<h4>Benefits</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>Loose coupling between event sources and handlers</li>
<li>Observers added/removed without modifying subjects</li>
<li>Foundation of event-driven programming</li>
</ul>
<h4>Used In</h4>
<p style="color: var(--text-muted);">
<code>keys.c</code> - Keyboard event handling, <code>notifications.c</code> - D-Bus signals
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://refactoring.guru/design-patterns/observer" target="_blank">Observer Pattern</a> - Refactoring Guru</li>
<li><a href="https://en.wikipedia.org/wiki/Observer_pattern" target="_blank">Observer Pattern</a> - Wikipedia</li>
<li><a href="https://learn.microsoft.com/en-us/dotnet/standard/events/observer-design-pattern" target="_blank">Observer Design Pattern</a> - Microsoft Learn</li>
</ul>
</div>
<h3 id="singleton">Singleton / Global State</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Provide a single, globally accessible instance of the window manager state,
simplifying module communication.</p>
<h4>Implementation in DWN</h4>
<p>DWN uses a single <code>DWNState</code> structure accessible via the global
<code>dwn</code> pointer. This is appropriate for a window manager where
exactly one instance exists per X11 session.</p>
<div class="code-header">
<span>Example</span>
</div>
<pre><code>// Global state pointer
extern DWNState *dwn;
// Access from any module
if (dwn != NULL && dwn->config != NULL) {
return dwn->config->terminal;
}</code></pre>
<h4>Rationale</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>Window manager is inherently a singleton per X session</li>
<li>Simplifies inter-module communication</li>
<li>All state centralized for easier debugging</li>
</ul>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://refactoring.guru/design-patterns/singleton" target="_blank">Singleton Pattern</a> - Refactoring Guru</li>
<li><a href="https://gameprogrammingpatterns.com/singleton.html" target="_blank">Singleton</a> - Game Programming Patterns</li>
<li><a href="https://en.wikipedia.org/wiki/Singleton_pattern" target="_blank">Singleton Pattern</a> - Wikipedia</li>
</ul>
</div>
<h3 id="double-fork">Double Fork Daemon Pattern</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Launch background processes that are fully detached from the parent,
preventing zombie processes and terminal reattachment.</p>
<h4>How It Works</h4>
<p>The pattern uses two fork() calls: the first creates a child that calls
setsid() to become a session leader, then forks again. The grandchild
cannot reacquire a controlling terminal.</p>
<div class="code-header">
<span>Implementation in DWN</span>
</div>
<pre><code>int spawn_async(const char *cmd) {
pid_t pid = fork();
if (pid == 0) {
setsid(); // New session
pid_t pid2 = fork(); // Second fork
if (pid2 == 0) {
execl("/bin/sh", "sh", "-c", cmd, NULL);
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS); // Intermediate exits
}
waitpid(pid, &status, 0); // Only wait for intermediate
return 0;
}</code></pre>
<h4>Benefits</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>Process fully detached from parent</li>
<li>No zombie processes (intermediate is reaped immediately)</li>
<li>Cannot reacquire controlling terminal</li>
<li>Non-blocking for the caller</li>
</ul>
<h4>Used In</h4>
<p style="color: var(--text-muted);">
<code>util.c</code> - spawn_async(), <code>autostart.c</code>, <code>applauncher.c</code>
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://0xjet.github.io/3OHA/2022/04/11/post.html" target="_blank">UNIX daemonization and the double fork</a> - Juan Tapiador</li>
<li><a href="https://www.digitalbunker.dev/understanding-daemons-unix/" target="_blank">Understanding Daemons</a> - Digital Bunker</li>
<li><a href="https://lloydrochester.com/post/c/unix-daemon-example/" target="_blank">Daemon Example in C</a> - Lloyd Rochester</li>
<li><a href="https://goral.net.pl/post/double-fork/" target="_blank">Double Fork</a> - Michal Goral</li>
</ul>
</div>
<h2 id="x11-protocols" style="margin-top: 3rem;">X11 Window Manager Protocols</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">
DWN implements several freedesktop.org specifications for cross-desktop compatibility.
</p>
<h3 id="ewmh">Extended Window Manager Hints (EWMH)</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Define interactions between window managers, compositing managers, applications,
and desktop utilities in a standardized way.</p>
<h4>Key Features Implemented</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>_NET_SUPPORTED</code> - List of supported hints</li>
<li><code>_NET_CLIENT_LIST</code> - List of managed windows</li>
<li><code>_NET_CURRENT_DESKTOP</code> - Active workspace</li>
<li><code>_NET_WM_STATE</code> - Window states (fullscreen, maximized)</li>
<li><code>_NET_ACTIVE_WINDOW</code> - Currently focused window</li>
<li><code>_NET_WM_WINDOW_TYPE</code> - Window type classification</li>
</ul>
<h4>Implementation</h4>
<p style="color: var(--text-muted);">
<code>atoms.c</code> manages X11 atom creation and EWMH property updates.
Properties are updated on window focus changes, workspace switches, and state changes.
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/wm/latest/" target="_blank">Extended Window Manager Hints Specification</a> - freedesktop.org</li>
<li><a href="https://en.wikipedia.org/wiki/Extended_Window_Manager_Hints" target="_blank">Extended Window Manager Hints</a> - Wikipedia</li>
</ul>
</div>
<h3 id="icccm">Inter-Client Communication Conventions Manual (ICCCM)</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Define low-level conventions for X11 client communication, including
selections, window management, and session management.</p>
<h4>Key Features Implemented</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>WM_PROTOCOLS</code> - Window close handling</li>
<li><code>WM_DELETE_WINDOW</code> - Graceful window closing</li>
<li><code>WM_NAME</code> / <code>_NET_WM_NAME</code> - Window titles</li>
<li><code>WM_CLASS</code> - Application classification</li>
<li><code>WM_HINTS</code> - Window hints (urgency, input model)</li>
</ul>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html" target="_blank">Inter-Client Communication Conventions Manual</a> - X.Org</li>
<li><a href="https://tronche.com/gui/x/icccm/" target="_blank">ICCCM Reference</a> - Christophe Tronche</li>
</ul>
</div>
<h3 id="xembed">XEmbed Protocol</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Enable embedding of controls from one application into another,
forming the basis of the system tray implementation.</p>
<h4>How It Works</h4>
<p>The embedder (DWN panel) acts as a window manager for embedded clients.
Client windows are reparented into the embedder, and events are coordinated
through XEMBED messages.</p>
<h4>Key Messages</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>XEMBED_EMBEDDED_NOTIFY</code> - Sent when embedding completes</li>
<li><code>XEMBED_FOCUS_IN/OUT</code> - Focus coordination</li>
<li><code>XEMBED_WINDOW_ACTIVATE</code> - Window activation</li>
</ul>
<h4>Implementation</h4>
<p style="color: var(--text-muted);">
<code>systray.c</code> implements the embedder side, reparenting tray icons
and forwarding click events.
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/xembed-spec/latest/" target="_blank">XEmbed Protocol Specification</a> - freedesktop.org</li>
<li><a href="https://www.freedesktop.org/wiki/Specifications/xembed-spec/" target="_blank">XEmbed Spec Wiki</a> - freedesktop.org</li>
</ul>
</div>
<h3 id="systray">System Tray Protocol</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Enable applications to display status icons in a desktop panel,
providing a standardized notification area.</p>
<h4>How It Works</h4>
<p>DWN acquires the <code>_NET_SYSTEM_TRAY_S0</code> selection to become the
tray manager. Applications send <code>SYSTEM_TRAY_REQUEST_DOCK</code> messages
to dock their icons.</p>
<h4>Docking Process</h4>
<ol style="padding-left: 1.25rem; color: var(--text-muted);">
<li>DWN acquires <code>_NET_SYSTEM_TRAY_S0</code> selection</li>
<li>Application sends <code>SYSTEM_TRAY_REQUEST_DOCK</code> client message</li>
<li>DWN creates embedding window and reparents icon</li>
<li>DWN sends <code>XEMBED_EMBEDDED_NOTIFY</code> to icon</li>
<li>Click events forwarded to icon window</li>
</ol>
<h4>Supported Applications</h4>
<p style="color: var(--text-muted);">
nm-applet, blueman-applet, Telegram, pasystray, udiskie, and any XEmbed-compatible tray icon.
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-0.3.html" target="_blank">System Tray Protocol Specification</a> - freedesktop.org</li>
</ul>
</div>
<h2 id="xdg" style="margin-top: 3rem;">XDG Specifications</h2>
<h3>Desktop Entry Specification</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Standard format for application metadata files (.desktop files) used
by application launchers and autostart systems.</p>
<h4>Key Fields Parsed</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>Exec</code> - Command to execute</li>
<li><code>TryExec</code> - Check if binary exists</li>
<li><code>Hidden</code> - Entry is disabled</li>
<li><code>OnlyShowIn</code> / <code>NotShowIn</code> - Desktop environment filters</li>
<li><code>Terminal</code> - Run in terminal</li>
</ul>
<h4>Implementation</h4>
<p style="color: var(--text-muted);">
<code>applauncher.c</code> parses .desktop files for the application menu.
<code>autostart.c</code> parses autostart entries.
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/desktop-entry-spec/latest/" target="_blank">Desktop Entry Specification</a> - freedesktop.org</li>
<li><a href="https://wiki.archlinux.org/title/Desktop_entries" target="_blank">Desktop Entries</a> - ArchWiki</li>
</ul>
</div>
<h3>Desktop Application Autostart Specification</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Define standard locations and format for applications that should
start automatically when the user logs in.</p>
<h4>Directories Scanned</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>/etc/xdg/autostart/</code> - System-wide autostart</li>
<li><code>~/.config/autostart/</code> - User autostart</li>
<li><code>~/.config/dwn/autostart.d/</code> - DWN-specific (symlinks)</li>
</ul>
<h4>Key Behavior</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li>User entries override system entries with same filename</li>
<li><code>Hidden=true</code> disables an entry</li>
<li><code>TryExec</code> prevents running if binary missing</li>
</ul>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html" target="_blank">Desktop Application Autostart Specification</a> - freedesktop.org</li>
<li><a href="https://wiki.archlinux.org/title/XDG_Autostart" target="_blank">XDG Autostart</a> - ArchWiki</li>
</ul>
</div>
<h3>Desktop Notifications Specification</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Standard D-Bus interface for applications to display passive notifications
to users without blocking.</p>
<h4>D-Bus Interface</h4>
<p style="color: var(--text-muted);">
DWN implements <code>org.freedesktop.Notifications</code> on the session bus
at path <code>/org/freedesktop/Notifications</code>.
</p>
<h4>Key Methods</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>Notify</code> - Display a notification</li>
<li><code>CloseNotification</code> - Dismiss a notification</li>
<li><code>GetCapabilities</code> - Query supported features</li>
<li><code>GetServerInformation</code> - Server metadata</li>
</ul>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/notification-spec/latest/" target="_blank">Desktop Notifications Specification</a> - freedesktop.org</li>
<li><a href="https://wiki.archlinux.org/title/Desktop_notifications" target="_blank">Desktop Notifications</a> - ArchWiki</li>
</ul>
</div>
<h2 id="async" style="margin-top: 3rem;">Async Programming Patterns</h2>
<h3>libcurl Multi Interface</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Perform HTTP requests asynchronously without blocking the main event loop,
essential for AI features and news fetching.</p>
<h4>How It Works</h4>
<p>Instead of blocking on network I/O, the multi interface allows the main
loop to poll for completion. <code>curl_multi_perform()</code> advances
transfers incrementally.</p>
<div class="code-header">
<span>Integration Pattern</span>
</div>
<pre><code>// In event loop (16ms intervals)
curl_multi_perform(multi_handle, &running);
CURLMsg *msg = curl_multi_info_read(multi_handle, &msgs_left);
if (msg && msg->msg == CURLMSG_DONE) {
// Request completed, process response
}</code></pre>
<h4>Implementation</h4>
<p style="color: var(--text-muted);">
<code>ai.c</code> uses curl_multi for OpenRouter API calls.
Responses processed in <code>ai_process_pending()</code> called from main loop.
</p>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://curl.se/libcurl/c/libcurl-multi.html" target="_blank">libcurl multi interface overview</a> - curl.se</li>
<li><a href="https://curl.se/libcurl/c/libcurl-tutorial.html" target="_blank">libcurl programming tutorial</a> - curl.se</li>
<li><a href="https://curl.se/libcurl/c/multi-app.html" target="_blank">multi-app.c example</a> - curl.se</li>
</ul>
</div>
<h3>pthread for Background I/O</h3>
<div class="card" style="margin-bottom: 2rem;">
<h4>Purpose</h4>
<p>Offload blocking operations to separate threads when async APIs
are unavailable or impractical.</p>
<h4>Implementation</h4>
<p style="color: var(--text-muted);">
<code>news.c</code> spawns a detached thread for RSS fetching.
Mutex guards shared state, atomic flags prevent concurrent fetches.
</p>
<div class="code-header">
<span>Pattern</span>
</div>
<pre><code>static pthread_mutex_t news_mutex = PTHREAD_MUTEX_INITIALIZER;
static atomic_int fetch_running = 0;
void news_fetch_async(void) {
if (atomic_load(&fetch_running)) return;
atomic_store(&fetch_running, 1);
pthread_create(&fetch_thread, NULL, fetch_thread_func, NULL);
}</code></pre>
</div>
<h2 id="modular" style="margin-top: 3rem;">Modular Architecture</h2>
<div class="card" style="margin-bottom: 2rem;">
<h4>Design Principle</h4>
<p>Each module has a single responsibility with well-defined interfaces.
Modules communicate through the global <code>dwn</code> state and
function calls, never through shared mutable state.</p>
<h4>Module Structure</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><code>include/module.h</code> - Public API declarations</li>
<li><code>src/module.c</code> - Private implementation</li>
<li>Static functions for internal logic</li>
<li>module_init() / module_cleanup() lifecycle</li>
</ul>
<h4>Module Responsibilities</h4>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Module</th>
<th>Responsibility</th>
</tr>
</thead>
<tbody>
<tr><td>main.c</td><td>Event loop, initialization orchestration</td></tr>
<tr><td>client.c</td><td>Window management, focus handling</td></tr>
<tr><td>workspace.c</td><td>Virtual desktop management</td></tr>
<tr><td>layout.c</td><td>Tiling algorithms</td></tr>
<tr><td>panel.c</td><td>UI panels and widgets</td></tr>
<tr><td>systray.c</td><td>System tray protocol</td></tr>
<tr><td>notifications.c</td><td>D-Bus notification daemon</td></tr>
<tr><td>autostart.c</td><td>XDG autostart support</td></tr>
<tr><td>config.c</td><td>Configuration parsing</td></tr>
<tr><td>ai.c</td><td>AI integration</td></tr>
</tbody>
</table>
</div>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://en.wikipedia.org/wiki/Modular_programming" target="_blank">Modular Programming</a> - Wikipedia</li>
<li><a href="https://en.wikipedia.org/wiki/Separation_of_concerns" target="_blank">Separation of Concerns</a> - Wikipedia</li>
<li><a href="https://thecloudstrap.com/chapter-14-modular-programming-in-c/" target="_blank">Modular Programming in C</a> - TheCloudStrap</li>
</ul>
</div>
<h2 id="defensive" style="margin-top: 3rem;">Defensive Programming</h2>
<div class="card" style="margin-bottom: 2rem;">
<h4>Core Principles</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><strong>Null Checks</strong> - All pointer parameters validated before use</li>
<li><strong>Bounds Checking</strong> - Array indices and string lengths verified</li>
<li><strong>Return Value Checking</strong> - malloc(), fopen(), etc. checked for failure</li>
<li><strong>String Safety</strong> - strncpy() with size-1, explicit null termination</li>
<li><strong>Assertions</strong> - assert() for programmer errors in debug builds</li>
</ul>
<h4>Examples in DWN</h4>
<div class="code-header">
<span>Null Check Pattern</span>
</div>
<pre><code>const char *config_get_terminal(void) {
if (dwn != NULL && dwn->config != NULL) {
return dwn->config->terminal;
}
return "xterm"; // Safe fallback
}</code></pre>
<div class="code-header">
<span>String Safety Pattern</span>
</div>
<pre><code>strncpy(cfg->terminal, value, sizeof(cfg->terminal) - 1);
cfg->terminal[sizeof(cfg->terminal) - 1] = '\0'; // Guarantee null termination</code></pre>
<h4>Research Sources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/API00-C.+Functions+should+validate+their+parameters" target="_blank">API00-C: Functions should validate their parameters</a> - SEI CERT</li>
<li><a href="https://enterprisecraftsmanship.com/posts/defensive-programming/" target="_blank">Defensive programming: the good, the bad and the ugly</a> - Enterprise Craftsmanship</li>
<li><a href="https://www.cse.psu.edu/~gxt29/teaching/cs447s19/slides/05defensiveProg.pdf" target="_blank">Defensive Programming</a> - Penn State (PDF)</li>
</ul>
</div>
<h2 id="sources" style="margin-top: 3rem;">Complete Research Sources</h2>
<div class="card">
<h4>C Design Patterns</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://interrupt.memfault.com/blog/opaque-pointers" target="_blank">Practical Design Patterns: Opaque Pointers and Objects in C</a> - Memfault</li>
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/" target="_blank">SEI CERT C Coding Standard</a> - Carnegie Mellon University</li>
<li><a href="https://refactoring.guru/design-patterns" target="_blank">Design Patterns Catalog</a> - Refactoring Guru</li>
<li><a href="https://sourcemaking.com/design_patterns" target="_blank">Design Patterns</a> - SourceMaking</li>
<li><a href="https://embeddedartistry.com/fieldatlas/" target="_blank">Embedded Artistry Field Atlas</a> - Embedded Artistry</li>
<li><a href="https://www.state-machine.com/doc/AN_Simple_OOP_in_C.pdf" target="_blank">Object-Oriented Programming in C</a> - Quantum Leaps</li>
</ul>
<h4>X11 and freedesktop.org</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://specifications.freedesktop.org/wm/latest/" target="_blank">Extended Window Manager Hints (EWMH)</a> - freedesktop.org</li>
<li><a href="https://specifications.freedesktop.org/xembed-spec/latest/" target="_blank">XEmbed Protocol</a> - freedesktop.org</li>
<li><a href="https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-0.3.html" target="_blank">System Tray Protocol</a> - freedesktop.org</li>
<li><a href="https://specifications.freedesktop.org/notification-spec/latest/" target="_blank">Desktop Notifications</a> - freedesktop.org</li>
<li><a href="https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html" target="_blank">Desktop Application Autostart</a> - freedesktop.org</li>
<li><a href="https://specifications.freedesktop.org/desktop-entry-spec/latest/" target="_blank">Desktop Entry Specification</a> - freedesktop.org</li>
</ul>
<h4>Libraries and APIs</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://curl.se/libcurl/c/libcurl-multi.html" target="_blank">libcurl multi interface</a> - curl.se</li>
<li><a href="https://dbus.freedesktop.org/doc/dbus-specification.html" target="_blank">D-Bus Specification</a> - freedesktop.org</li>
<li><a href="https://www.x.org/releases/current/doc/" target="_blank">X Window System Documentation</a> - X.Org</li>
</ul>
<h4>Unix Programming</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://0xjet.github.io/3OHA/2022/04/11/post.html" target="_blank">UNIX daemonization and the double fork</a> - Juan Tapiador</li>
<li><a href="https://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-in-c" target="_blank">Using goto for error handling in C</a> - Eli Bendersky</li>
<li><a href="https://gameprogrammingpatterns.com/" target="_blank">Game Programming Patterns</a> - Robert Nystrom</li>
</ul>
<h4>Community Resources</h4>
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
<li><a href="https://wiki.archlinux.org/" target="_blank">ArchWiki</a> - Comprehensive Linux documentation</li>
<li><a href="https://en.wikipedia.org/" target="_blank">Wikipedia</a> - Design pattern overviews</li>
</ul>
</div>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Source Code</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Author</h4>
<p style="color: var(--text-muted);">
retoor &lt;retoor@molodetz.nl&gt;
</p>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>
-491
View File
@@ -1,491 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Getting started with DWN window manager - learn the basics and become productive quickly.">
<title>Documentation - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html" class="active">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<div class="docs-layout container">
<aside class="docs-sidebar">
<ul>
<li>
<span class="section-title">Getting Started</span>
<ul>
<li><a href="#introduction" class="active">Introduction</a></li>
<li><a href="#first-steps">First Steps</a></li>
<li><a href="#basic-concepts">Basic Concepts</a></li>
<li><a href="#tutorial">Interactive Tutorial</a></li>
</ul>
</li>
<li>
<span class="section-title">Core Usage</span>
<ul>
<li><a href="#windows">Managing Windows</a></li>
<li><a href="#workspaces">Using Workspaces</a></li>
<li><a href="#layouts">Layout Modes</a></li>
<li><a href="#panels">Panels & Systray</a></li>
</ul>
</li>
<li>
<span class="section-title">Reference</span>
<ul>
<li><a href="#api">WebSocket API</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="ai-features.html">AI Features</a></li>
<li><a href="architecture.html">Architecture</a></li>
</ul>
</li>
</ul>
</aside>
<main class="docs-content">
<h1 id="introduction">Getting Started with DWN</h1>
<p class="lead">
Learn the fundamentals of DWN and become productive in minutes.
</p>
<h2 id="first-steps">First Steps</h2>
<p>
After <a href="installation.html">installing DWN</a> and starting your session,
you'll see a clean desktop with two panels: a top panel with workspace indicators,
taskbar, and system tray, and a bottom panel showing the clock.
</p>
<h3>Opening Your First Application</h3>
<p>Start by launching a terminal and application launcher:</p>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>T</kbd></td>
<td>Open terminal</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>F2</kbd></td>
<td>Open application launcher (dmenu/rofi)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>E</kbd></td>
<td>Open file manager</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>B</kbd></td>
<td>Open web browser</td>
</tr>
</tbody>
</table>
</div>
<div class="alert alert-info">
<strong class="alert-title">Tip: Run the Tutorial</strong>
<p style="margin: 0;">Press <kbd>Super</kbd> + <kbd>T</kbd> to start an interactive
tutorial that will guide you through all essential shortcuts.</p>
</div>
<h2 id="basic-concepts">Basic Concepts</h2>
<h3>The Super Key</h3>
<p>
Most DWN shortcuts use the <kbd>Super</kbd> key (often the Windows key or Command key).
This keeps shortcuts separate from application shortcuts that typically use
<kbd>Ctrl</kbd> or <kbd>Alt</kbd>.
</p>
<h3>Focus Model</h3>
<p>
By default, DWN uses "click to focus" - you click on a window to focus it.
You can change this to "focus follows mouse" (sloppy focus) in the configuration.
</p>
<h3>Window Decorations</h3>
<p>
DWN features a professional borderless design for maximum screen utilization:
</p>
<ul>
<li><strong>0px borders</strong> - No visible borders around windows</li>
<li><strong>0px gaps</strong> - Windows tile edge-to-edge without spacing</li>
<li><strong>28px title bars</strong> - Minimal height showing window name and controls</li>
</ul>
<p>
The title bar color indicates focus:
</p>
<ul>
<li><strong>Bright title bar</strong> - Focused window</li>
<li><strong>Dim title bar</strong> - Unfocused window</li>
</ul>
<p>
This seamless design creates a smooth, professional appearance where content takes
center stage. All settings can be customized in <code>~/.config/dwn/config</code>.
</p>
<h2 id="tutorial">Interactive Tutorial</h2>
<p>
DWN includes a built-in interactive tutorial that teaches you essential shortcuts
step by step. The tutorial:
</p>
<ul>
<li>Shows instructions for each shortcut</li>
<li>Waits for you to press the correct keys</li>
<li>Automatically advances when you complete each step</li>
<li>Covers all essential shortcuts from basic to advanced</li>
</ul>
<div class="card">
<h3>Start the Tutorial</h3>
<p>Press <kbd>Super</kbd> + <kbd>T</kbd> at any time to start or resume the tutorial.</p>
</div>
<h2 id="demo">Demo Mode</h2>
<p>
DWN includes an automated demo mode that showcases all features without requiring
any interaction. The demo:
</p>
<ul>
<li>Automatically demonstrates window management operations</li>
<li>Shows workspace switching and layout changes</li>
<li>Highlights panel features and system tray widgets</li>
<li>Demonstrates AI integration capabilities (if configured)</li>
<li>Displays the complete keyboard shortcut reference</li>
</ul>
<div class="card">
<h3>Start Demo Mode</h3>
<p>Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>D</kbd> to start the demo.
Press the same shortcut again to stop it at any time.</p>
</div>
<h2 id="windows">Managing Windows</h2>
<h3>Window Operations</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Alt</kbd> + <kbd>F4</kbd></td>
<td>Close focused window</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>Tab</kbd></td>
<td>Cycle to next window</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
<td>Cycle to previous window</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>F10</kbd></td>
<td>Toggle maximize</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>F11</kbd></td>
<td>Toggle fullscreen</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>F9</kbd></td>
<td>Toggle floating for current window</td>
</tr>
</tbody>
</table>
</div>
<h3>Moving and Resizing</h3>
<p>In floating mode, you can move and resize windows with the mouse:</p>
<ul>
<li><strong>Move</strong> - Click and drag the title bar</li>
<li><strong>Resize</strong> - Drag any window edge or corner</li>
</ul>
<h2 id="workspaces">Using Workspaces</h2>
<p>
DWN provides 9 virtual workspaces to organize your windows. You can see which
workspaces are active in the top panel.
</p>
<h3>Workspace Navigation</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>F1</kbd> - <kbd>F9</kbd></td>
<td>Switch to workspace 1-9</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F1</kbd> - <kbd>F9</kbd></td>
<td>Move window to workspace 1-9</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Right</kbd></td>
<td>Next workspace</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Left</kbd></td>
<td>Previous workspace</td>
</tr>
</tbody>
</table>
</div>
<h3>Workspace Organization Tips</h3>
<ul>
<li><strong>Workspace 1</strong> - Main work (editor, terminal)</li>
<li><strong>Workspace 2</strong> - Web browser, documentation</li>
<li><strong>Workspace 3</strong> - Communication (email, chat)</li>
<li><strong>Workspace 4-9</strong> - Project-specific contexts</li>
</ul>
<h2 id="layouts">Layout Modes</h2>
<p>
DWN supports three layout modes. Press <kbd>Super</kbd> + <kbd>Space</kbd> to cycle
through them.
</p>
<div class="features-grid" style="grid-template-columns: repeat(3, 1fr); gap: 1rem; margin: 1.5rem 0;">
<div class="card" style="padding: 1.5rem;">
<h4>Tiling</h4>
<p style="color: var(--text-muted); font-size: 0.9rem;">
Windows automatically arranged in master-stack layout.
Perfect for development workflows.
</p>
</div>
<div class="card" style="padding: 1.5rem;">
<h4>Floating</h4>
<p style="color: var(--text-muted); font-size: 0.9rem;">
Traditional overlapping windows.
Move and resize freely.
</p>
</div>
<div class="card" style="padding: 1.5rem;">
<h4>Monocle</h4>
<p style="color: var(--text-muted); font-size: 0.9rem;">
One fullscreen window at a time.
Great for focused work.
</p>
</div>
</div>
<h3>Tiling Layout Controls</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>H</kbd></td>
<td>Shrink master area</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>L</kbd></td>
<td>Expand master area</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>I</kbd></td>
<td>Increase master window count</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>D</kbd></td>
<td>Decrease master window count</td>
</tr>
</tbody>
</table>
</div>
<h2 id="panels">Panels & System Tray</h2>
<h3>Top Panel</h3>
<p>The top panel contains:</p>
<ul>
<li><strong>Workspace indicators</strong> - Click to switch, highlighted when occupied</li>
<li><strong>Taskbar</strong> - Shows windows on current workspace</li>
<li><strong>System tray</strong> - Battery, volume, WiFi (see below)</li>
</ul>
<h3>System Tray</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Indicator</th>
<th>Click</th>
<th>Right-click</th>
<th>Scroll</th>
</tr>
</thead>
<tbody>
<tr>
<td>Volume</td>
<td>Show slider</td>
<td>Toggle mute</td>
<td>Adjust volume</td>
</tr>
<tr>
<td>WiFi</td>
<td>Show networks</td>
<td>Disconnect</td>
<td>-</td>
</tr>
<tr>
<td>Battery</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
</tbody>
</table>
</div>
<h3>Bottom Panel</h3>
<p>
The bottom panel shows the current time and a scrolling news ticker. Open
the current article with <kbd>Super</kbd> + <kbd>Return</kbd>. Both panels can
be hidden in the configuration if you prefer a minimal setup.
</p>
<h2 id="api">WebSocket API</h2>
<p>
DWN exposes a real-time WebSocket API (default port <code>8777</code>) that allows for
complete programmatic control of the window manager.
</p>
<div class="card">
<h3>Connecting</h3>
<p>Endpoint: <code>ws://localhost:8777/ws</code></p>
<p>The API must be enabled in your configuration:</p>
<pre><code>[api]
enabled = true
port = 8777</code></pre>
</div>
<h3>Supported Commands</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Command</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>get_status</code></td>
<td>Get WM version and current workspace ID</td>
</tr>
<tr>
<td><code>get_workspaces</code></td>
<td>List all workspaces and their client counts</td>
</tr>
<tr>
<td><code>get_clients</code></td>
<td>Get details of all managed windows</td>
</tr>
<tr>
<td><code>switch_workspace</code></td>
<td>Change to a specific workspace ID</td>
</tr>
<tr>
<td><code>run_command</code></td>
<td>Execute a shell command</td>
</tr>
<tr>
<td><code>focus_client</code></td>
<td>Focus a specific window by ID</td>
</tr>
</tbody>
</table>
</div>
<h3>Advanced Examples</h3>
<p>
Check the <code>scripts/api_examples/</code> directory in the source tree for
sophisticated implementations:
</p>
<ul>
<li><strong>Window Picker</strong> (<code>window_picker_fzf.py</code>) - Fuzzy finder for windows.</li>
<li><strong>Session Manager</strong> (<code>session_manager.py</code>) - Save/restore window layouts.</li>
<li><strong>Web Remote</strong> (<code>web_remote.html</code>) - Browser-based dashboard.</li>
<li><strong>Auto-Manager</strong> (<code>listen.py</code>) - Proactive workspace optimization.</li>
</ul>
<h2>Next Steps</h2>
<p>Now that you know the basics, explore these topics:</p>
<ul>
<li><a href="shortcuts.html">Complete Keyboard Shortcuts Reference</a></li>
<li><a href="configuration.html">Customizing DWN</a></li>
<li><a href="ai-features.html">Using AI Features</a></li>
</ul>
</main>
</div>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>
-510
View File
@@ -1,510 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Explore the powerful features of DWN window manager - tiling layouts, workspaces, AI integration, and more.">
<title>Features - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html" class="active">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content">
<h1>Powerful Features</h1>
<p class="subtitle">
Everything you need for a productive desktop experience, without the bloat.
</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>Window Management</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
DWN provides flexible window management that adapts to your workflow, whether you prefer
the precision of tiling or the freedom of floating windows.
</p>
<div class="features-grid">
<div class="card">
<h3><span class="card-icon">&#9783;</span> Tiling Layout</h3>
<p>Master-stack tiling with configurable master area ratio. Windows automatically
organize into a primary area and a stack, maximizing screen real estate.</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Adjustable master area ratio (0.1 - 0.9)</li>
<li>Multiple windows in master area</li>
<li>Smart stack arrangement</li>
<li>Seamless borderless design (0px borders, 0px gaps)</li>
</ul>
</div>
<div class="card">
<h3><span class="card-icon">&#10063;</span> Floating Layout</h3>
<p>Traditional floating window management with drag-and-drop positioning.
Perfect for workflows that need overlapping windows or free-form arrangement.</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Click and drag to move windows</li>
<li>Resize from any edge or corner</li>
<li>Window snapping support</li>
<li>Respect minimum size hints</li>
</ul>
</div>
<div class="card">
<h3><span class="card-icon">&#9744;</span> Monocle Layout</h3>
<p>Full-screen single window mode for focused work. Each window takes up
the entire workspace, perfect for presentations or deep concentration.</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Maximize focused window</li>
<li>Quick window cycling</li>
<li>Ideal for single-task focus</li>
<li>Works great on small screens</li>
</ul>
</div>
</div>
<div class="alert alert-info" style="margin-top: 2rem;">
<strong class="alert-title">Pro Tip</strong>
<p style="margin: 0;">Switch layouts instantly with <kbd>Super</kbd> + <kbd>Space</kbd>.
Your window arrangement is preserved when switching back.</p>
</div>
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">Borderless Design</h3>
<div class="card">
<p>DWN employs a professional, seamless aesthetic with zero window borders and zero gaps:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li><strong>0px borders</strong> - No visible borders around windows</li>
<li><strong>0px gaps</strong> - Windows tile edge-to-edge without spacing</li>
<li><strong>28px title bars</strong> - Minimal height for window controls</li>
<li><strong>Maximum screen utilization</strong> - Every pixel counts</li>
</ul>
<p style="margin-top: 1rem; color: var(--text-muted);">
This creates a smooth, professional appearance where content takes center stage. The focus
remains on your work, not on window decorations. All settings are configurable via
<code>~/.config/dwn/config</code> if you prefer different values.
</p>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2>Virtual Workspaces</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
Nine virtual desktops give you unlimited room to organize your work.
Each workspace maintains its own window state and layout preferences.
</p>
<div class="features-grid" style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));">
<div class="feature-card">
<div class="feature-icon">1-9</div>
<h3>9 Workspaces</h3>
<p>Quick access via F1-F9 keys. Organize projects, contexts, or tasks across
dedicated spaces.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#8596;</div>
<h3>Window Transfer</h3>
<p>Move windows between workspaces with Shift+F1-F9. Quick and keyboard-driven.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128200;</div>
<h3>Per-Workspace State</h3>
<p>Each workspace remembers its layout mode, window positions, and focused window.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128065;</div>
<h3>Visual Indicators</h3>
<p>Panel shows active and occupied workspaces at a glance with color coding.</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Panels & System Tray</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
Built-in panels provide essential information and quick access to common functions
without needing external tools or status bars.
</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
<div class="card">
<h3>Top Panel</h3>
<p>The top panel contains your main controls and information:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li><strong>Workspace Indicators</strong> - Click or use shortcuts to switch</li>
<li><strong>Taskbar</strong> - Shows windows on current workspace</li>
<li><strong>System Tray</strong> - Battery, volume, WiFi indicators</li>
</ul>
</div>
<div class="card">
<h3>Bottom Panel</h3>
<p>Optional bottom panel for additional information:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li><strong>Clock Display</strong> - Time and date</li>
<li><strong>News Ticker</strong> - Scrolling news feed with navigation</li>
<li><strong>Customizable</strong> - Can be hidden in config</li>
</ul>
</div>
</div>
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">News Ticker</h3>
<div class="card">
<p>The bottom panel includes a scrolling news ticker that displays headlines from a news feed.
Navigate through articles using keyboard shortcuts:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li><kbd>Super</kbd> + <kbd>Down</kbd> - Next article</li>
<li><kbd>Super</kbd> + <kbd>Up</kbd> - Previous article</li>
<li><kbd>Super</kbd> + <kbd>Return</kbd> - Open in browser</li>
</ul>
<p style="margin-top: 1rem; color: var(--text-muted);">
The ticker updates automatically and caches up to 50 articles. Smooth scrolling animation
at 80 pixels per second keeps you informed without distraction.
</p>
</div>
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">XEmbed System Tray</h3>
<div class="card" style="margin-bottom: 2rem;">
<h3>&#128241; External Application Icons</h3>
<p>DWN implements the freedesktop.org XEmbed System Tray protocol, allowing external applications
to dock their status icons in the panel - just like XFCE, GNOME, or KDE.</p>
<p style="margin-top: 1rem; color: var(--text-muted);">Supported applications include:</p>
<ul style="margin-top: 0.5rem; padding-left: 1.25rem; color: var(--text-muted);">
<li><strong>Telegram</strong> - Notification icons for messages</li>
<li><strong>nm-applet</strong> - NetworkManager GUI</li>
<li><strong>blueman-applet</strong> - Bluetooth manager</li>
<li><strong>pasystray</strong> - PulseAudio control</li>
<li><strong>udiskie</strong> - USB automounter</li>
<li>Any application with tray icon support</li>
</ul>
<p style="margin-top: 1rem; color: var(--text-muted);">
Simply launch any tray-enabled application and its icon will automatically appear in the panel.
Click on icons to interact - all events are forwarded to the application.
</p>
</div>
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">XDG Autostart</h3>
<div class="card" style="margin-bottom: 2rem;">
<h3>Automatic Application Startup</h3>
<p>DWN follows the XDG Autostart specification, automatically starting system services
and tray applications - just like traditional desktop environments.</p>
<p style="margin-top: 1rem; color: var(--text-muted);">Directories scanned at startup:</p>
<ul style="margin-top: 0.5rem; padding-left: 1.25rem; color: var(--text-muted);">
<li><code>/etc/xdg/autostart/</code> - System defaults (nm-applet, blueman, power-manager)</li>
<li><code>~/.config/autostart/</code> - User XDG autostart entries</li>
<li><code>~/.config/dwn/autostart.d/</code> - DWN-specific symlinks and scripts</li>
</ul>
<p style="margin-top: 1rem; color: var(--text-muted);">
All applications launch concurrently for fastest boot time. Properly handles .desktop
file fields including Hidden, TryExec, OnlyShowIn, and NotShowIn.
</p>
</div>
<h3 style="margin-top: 2rem; margin-bottom: 1.5rem;">Built-in Widgets</h3>
<div class="features-grid">
<div class="card">
<h3>&#128267; Battery Monitor</h3>
<p>Shows current battery percentage with color-coded status:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Red when below 20%</li>
<li>Blue when charging</li>
<li>Auto-hides on desktops</li>
</ul>
</div>
<div class="card">
<h3>&#128266; Volume Control</h3>
<p>Full audio control at your fingertips:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Click for volume slider</li>
<li>Scroll to adjust</li>
<li>Right-click to mute</li>
</ul>
</div>
<div class="card">
<h3>&#128246; WiFi Manager</h3>
<p>Network management made simple:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Click for network list</li>
<li>Signal strength indicators</li>
<li>Current SSID display</li>
</ul>
</div>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2>Notification System</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
Built-in D-Bus notification daemon following freedesktop.org standards.
No need for external notification tools like dunst or notify-osd.
</p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card">
<h3>Standards Compliant</h3>
<p>Implements the org.freedesktop.Notifications D-Bus interface.
Works seamlessly with any application that sends desktop notifications.</p>
</div>
<div class="card">
<h3>Customizable Appearance</h3>
<p>Configure notification colors and positioning through the config file.
Notifications match your overall color scheme automatically.</p>
</div>
</div>
<div class="alert alert-success" style="margin-top: 2rem;">
<strong class="alert-title">Capacity</strong>
<p style="margin: 0;">DWN can display up to 32 notifications simultaneously,
with automatic queuing and timeout management.</p>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>AI Integration</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
Optional AI features powered by OpenRouter API and Exa semantic search.
Control your desktop with natural language and get intelligent assistance.
</p>
<div class="features-grid">
<div class="card">
<h3>&#129302; AI Command Palette</h3>
<p>Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd> and type natural
language commands like "open firefox" or "launch terminal".</p>
<a href="ai-features.html" class="btn btn-sm btn-secondary" style="margin-top: 1rem;">Learn More</a>
</div>
<div class="card">
<h3>&#128269; Semantic Web Search</h3>
<p>Search the web semantically with Exa integration. Find relevant content based
on meaning, not just keywords.</p>
<a href="ai-features.html" class="btn btn-sm btn-secondary" style="margin-top: 1rem;">Learn More</a>
</div>
<div class="card">
<h3>&#127891; Context Analysis</h3>
<p>AI analyzes your current workspace to understand what you're working on
and provides relevant suggestions.</p>
<a href="ai-features.html" class="btn btn-sm btn-secondary" style="margin-top: 1rem;">Learn More</a>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Extensible API</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
DWN exposes a real-time WebSocket API that allows for complete programmatic
control and monitoring of your desktop environment.
</p>
<div class="features-grid">
<div class="card">
<h3>&#128161; Real-time Monitoring</h3>
<p>Subscribe to window manager events and state changes. Build custom dashboards,
status bars, or automation scripts.</p>
</div>
<div class="card">
<h3>&#127915; Remote Control</h3>
<p>Change workspaces, focus windows, and launch applications programmatically
from any language that supports WebSockets.</p>
</div>
<div class="card">
<h3>&#128187; Integration Ready</h3>
<p>Native support for JSON-RPC style commands makes it easy to integrate with
external tools, mobile apps, or web interfaces.</p>
</div>
</div>
<div class="alert alert-info" style="margin-top: 2rem;">
<p style="margin: 0;">Detailed API documentation and examples are available in the
<a href="documentation.html#api">Documentation section</a>.</p>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2>Standards Compliance</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
DWN implements EWMH and ICCCM protocols for maximum compatibility with X11 applications.
</p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card">
<h3>EWMH Support</h3>
<p>Extended Window Manager Hints for modern application features:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>_NET_WM_STATE (fullscreen, maximized, etc.)</li>
<li>_NET_ACTIVE_WINDOW</li>
<li>_NET_CLIENT_LIST and _NET_CLIENT_LIST_STACKING</li>
<li>_NET_CURRENT_DESKTOP and _NET_NUMBER_OF_DESKTOPS</li>
<li>_NET_WM_WINDOW_TYPE</li>
</ul>
</div>
<div class="card">
<h3>ICCCM Compliance</h3>
<p>Inter-Client Communication Conventions Manual support:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>WM_STATE management</li>
<li>WM_PROTOCOLS (WM_DELETE_WINDOW, WM_TAKE_FOCUS)</li>
<li>WM_NORMAL_HINTS (size hints)</li>
<li>WM_CLASS for window matching</li>
<li>WM_NAME and _NET_WM_NAME</li>
</ul>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Technical Specifications</h2>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Specification</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Language</td>
<td>ANSI C (C99)</td>
</tr>
<tr>
<td>Maximum Clients</td>
<td>256 windows</td>
</tr>
<tr>
<td>Workspaces</td>
<td>9 virtual desktops</td>
</tr>
<tr>
<td>Monitor Support</td>
<td>Up to 8 monitors (Xinerama/Xrandr)</td>
</tr>
<tr>
<td>Notifications</td>
<td>32 concurrent</td>
</tr>
<tr>
<td>Keybindings</td>
<td>64 configurable shortcuts</td>
</tr>
<tr>
<td>Memory Usage</td>
<td>&lt; 5MB typical</td>
</tr>
<tr>
<td>Configuration</td>
<td>INI-style (~/.config/dwn/config)</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Learning DWN</h2>
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
Two built-in modes help you learn DWN quickly: an interactive tutorial and
an automated demo that showcases all features.
</p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card">
<h3><span class="card-icon">&#128218;</span> Interactive Tutorial</h3>
<p>Press <kbd>Super</kbd> + <kbd>T</kbd> to start a hands-on tutorial that:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Guides you through essential shortcuts step-by-step</li>
<li>Waits for you to press the correct key combination</li>
<li>Automatically advances when you complete each step</li>
<li>Can be restarted at any time</li>
</ul>
</div>
<div class="card">
<h3><span class="card-icon">&#127916;</span> Demo Mode</h3>
<p>Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>D</kbd> for an automated showcase:</p>
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
<li>Demonstrates window management, workspaces, and layouts</li>
<li>Shows panel features and system tray</li>
<li>Highlights AI integration and news ticker</li>
<li>Displays complete keyboard shortcut reference</li>
</ul>
</div>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container" style="text-align: center;">
<h2>Ready to Try DWN?</h2>
<p style="color: var(--text-muted); max-width: 500px; margin: 0 auto 2rem;">
Get started in minutes with our simple installation process.
</p>
<div class="hero-buttons" style="justify-content: center;">
<a href="installation.html" class="btn btn-primary btn-lg">Install Now</a>
<a href="shortcuts.html" class="btn btn-secondary btn-lg">View Shortcuts</a>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>
-319
View File
@@ -1,319 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="DWN - A modern, production-ready X11 window manager with XFCE-like functionality and optional AI integration.">
<meta name="keywords" content="window manager, X11, Linux, tiling, floating, EWMH, AI, productivity">
<title>DWN Window Manager - Modern X11 Window Management</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html" class="active">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero">
<div class="container hero-content">
<h1>Modern Window Management for X11</h1>
<p class="subtitle">
DWN is a production-ready window manager written in ANSI C with XFCE-like functionality,
powerful tiling layouts, and optional AI integration. Seamless borderless design, zero gaps,
and professional appearance. Fast, flexible, and fully featured.
</p>
<div class="hero-buttons">
<a href="installation.html" class="btn btn-primary btn-lg">Get Started</a>
<a href="features.html" class="btn btn-secondary btn-lg">Explore Features</a>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 style="text-align: center; margin-bottom: 1rem;">Why Choose DWN?</h2>
<p style="text-align: center; color: var(--text-muted); max-width: 600px; margin: 0 auto 3rem;">
DWN combines the simplicity of traditional floating window managers with the productivity
of tiling layouts, all wrapped in a modern, customizable package.
</p>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">&#9776;</div>
<h3>Multiple Layouts</h3>
<p>Switch seamlessly between tiling, floating, and monocle layouts.
Resize master areas and organize windows exactly how you work.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#9881;</div>
<h3>9 Workspaces</h3>
<p>Organize your workflow across 9 virtual desktops with per-workspace
state. Move windows between workspaces with a single keystroke.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#127912;</div>
<h3>Fully Customizable</h3>
<p>INI-style configuration with extensive theming options. Customize colors,
fonts, borders, gaps, and behavior to match your style.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128161;</div>
<h3>AI Integration</h3>
<p>Optional AI command palette and semantic web search. Control your desktop
with natural language and get intelligent suggestions.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128172;</div>
<h3>Extensible API</h3>
<p>Real-time WebSocket interface for programmatic control. Build custom
dashboards, mobile remotes, and automated workflows.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128276;</div>
<h3>Notification Daemon</h3>
<p>Built-in D-Bus notification support following freedesktop.org standards.
No need for external notification daemons.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128187;</div>
<h3>System Tray</h3>
<p>XEmbed protocol for external app icons (Telegram, Bluetooth, etc.) plus
built-in battery, volume, and WiFi widgets with interactive controls.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#9889;</div>
<h3>XDG Autostart</h3>
<p>Automatic startup of system services and tray applications following the
XDG Autostart spec. Works with nm-applet, blueman, and more.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128240;</div>
<h3>News Ticker</h3>
<p>Real-time news feed with scrolling article titles in the panel. Press
Super+Return to open the current article in your browser.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#127891;</div>
<h3>Interactive Learning</h3>
<p>Built-in tutorial system (Super+T) guides you through keyboard shortcuts.
Demo mode (Super+Shift+D) showcases all features automatically.</p>
</div>
<div class="feature-card">
<div class="feature-icon">&#128208;</div>
<h3>Borderless Design</h3>
<p>Zero window borders and zero gaps for a seamless, professional appearance.
Windows tile edge-to-edge for maximum screen utilization.</p>
</div>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<div class="stats">
<div class="stat-item">
<h3>~15K</h3>
<p>Lines of Pure C</p>
</div>
<div class="stat-item">
<h3>0</h3>
<p>Runtime Dependencies</p>
</div>
<div class="stat-item">
<h3>&lt;5MB</h3>
<p>Memory Footprint</p>
</div>
<div class="stat-item">
<h3>43+</h3>
<p>Keyboard Shortcuts</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 style="text-align: center;">Get Up and Running in Minutes</h2>
<p style="text-align: center; color: var(--text-muted); max-width: 600px; margin: 0 auto 3rem;">
DWN is designed for easy installation and immediate productivity.
Build from source or use your distribution's package manager.
</p>
<div class="card" style="max-width: 700px; margin: 0 auto;">
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Clone the repository
git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn
# Install dependencies (auto-detects your distro)
make deps
# Build and install
make
sudo make install
# Add to your .xinitrc
echo "exec dwn" >> ~/.xinitrc</code></pre>
</div>
<p style="text-align: center; margin-top: 2rem;">
<a href="installation.html" class="btn btn-primary">Full Installation Guide</a>
</p>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2 style="text-align: center;">See DWN in Action</h2>
<p style="text-align: center; color: var(--text-muted); max-width: 600px; margin: 0 auto 3rem;">
A clean, modern interface that stays out of your way while providing everything you need.
</p>
<div class="screenshot-grid">
<div class="screenshot">
<div class="screenshot-placeholder">
[Tiling Layout with Terminal and Editor]
</div>
<div class="screenshot-caption">Master-stack tiling layout perfect for development</div>
</div>
<div class="screenshot">
<div class="screenshot-placeholder">
[System Tray and Notifications]
</div>
<div class="screenshot-caption">Integrated system tray with volume and WiFi controls</div>
</div>
<div class="screenshot">
<div class="screenshot-placeholder">
[AI Command Palette]
</div>
<div class="screenshot-caption">AI-powered command palette for natural language control</div>
</div>
<div class="screenshot">
<div class="screenshot-placeholder">
[WebSocket API Dashboard]
</div>
<div class="screenshot-caption">Real-time WebSocket API for custom integrations</div>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 style="text-align: center;">How DWN Compares</h2>
<p style="text-align: center; color: var(--text-muted); max-width: 600px; margin: 0 auto 3rem;">
DWN bridges the gap between minimal tiling managers and full desktop environments.
</p>
<div class="comparison">
<div class="comparison-card">
<h3>Minimal Tiling WMs</h3>
<p style="color: var(--text-muted);">dwm, i3, bspwm</p>
<ul>
<li>Lightweight and fast</li>
<li>Keyboard-driven workflow</li>
<li>Highly customizable</li>
<li>Steep learning curve</li>
<li>Requires additional tools</li>
</ul>
</div>
<div class="comparison-card featured">
<h3>DWN</h3>
<p style="color: var(--text-muted);">Best of both worlds</p>
<ul>
<li>Lightweight and fast</li>
<li>Keyboard-driven workflow</li>
<li>Highly customizable</li>
<li>Interactive tutorial</li>
<li>Built-in panels and systray</li>
<li>AI integration optional</li>
<li>Notification daemon included</li>
</ul>
</div>
<div class="comparison-card">
<h3>Desktop Environments</h3>
<p style="color: var(--text-muted);">XFCE, GNOME, KDE</p>
<ul>
<li>Feature complete</li>
<li>User-friendly</li>
<li>Heavy resource usage</li>
<li>Less customizable</li>
<li>Slower performance</li>
</ul>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container" style="text-align: center;">
<h2>Ready to Try DWN?</h2>
<div class="hero-buttons" style="justify-content: center;">
<a href="installation.html" class="btn btn-primary btn-lg">Install DWN</a>
<a href="documentation.html" class="btn btn-secondary btn-lg">Read the Docs</a>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>
-561
View File
@@ -1,561 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Install DWN window manager - step by step guide for Debian, Ubuntu, Fedora, Arch Linux and more.">
<title>Installation - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html" class="active">Install</a></li>
<li class="dropdown">
<a href="documentation.html">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content">
<h1>Installation Guide</h1>
<p class="subtitle">
Get DWN running on your system in just a few minutes.
</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>Requirements</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">
DWN requires X11 and a few common libraries. Most Linux distributions include these by default.
</p>
<div class="card">
<h3>Required Dependencies</h3>
<div class="table-wrapper" style="margin-top: 1rem;">
<table>
<thead>
<tr>
<th>Library</th>
<th>Purpose</th>
<th>Package (Debian/Ubuntu)</th>
</tr>
</thead>
<tbody>
<tr>
<td>libX11</td>
<td>X Window System client library</td>
<td><code>libx11-dev</code></td>
</tr>
<tr>
<td>libXext</td>
<td>X extensions library</td>
<td><code>libxext-dev</code></td>
</tr>
<tr>
<td>libXinerama</td>
<td>Multi-monitor support</td>
<td><code>libxinerama-dev</code></td>
</tr>
<tr>
<td>libXrandr</td>
<td>Display configuration</td>
<td><code>libxrandr-dev</code></td>
</tr>
<tr>
<td>libXft</td>
<td>Font rendering</td>
<td><code>libxft-dev</code></td>
</tr>
<tr>
<td>fontconfig</td>
<td>Font configuration</td>
<td><code>libfontconfig1-dev</code></td>
</tr>
<tr>
<td>libdbus-1</td>
<td>D-Bus for notifications</td>
<td><code>libdbus-1-dev</code></td>
</tr>
<tr>
<td>libcurl</td>
<td>AI features (optional)</td>
<td><code>libcurl4-openssl-dev</code></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2>Quick Installation</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">
The fastest way to get started. Our build system auto-detects your distribution.
</p>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Clone the Repository</h4>
<p>Download the latest source code from GitHub.</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn</code></pre>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Install Dependencies</h4>
<p>Automatically install required packages for your distribution.</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>make deps</code></pre>
<p style="margin-top: 0.5rem; font-size: 0.875rem; color: var(--text-muted);">
Supports Debian, Ubuntu, Fedora, Arch, openSUSE, and Void Linux.
</p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>Build DWN</h4>
<p>Compile the window manager with optimizations.</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>make</code></pre>
</div>
</div>
<div class="step">
<div class="step-number">4</div>
<div class="step-content">
<h4>Install System-wide</h4>
<p>Install the binary to your system PATH.</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>sudo make install</code></pre>
<p style="margin-top: 0.5rem; font-size: 0.875rem; color: var(--text-muted);">
Default location: <code>/usr/local/bin/dwn</code>. Override with <code>PREFIX=/custom/path make install</code>
</p>
</div>
</div>
<div class="step">
<div class="step-number">5</div>
<div class="step-content">
<h4>Configure Your Session</h4>
<p>Add DWN to your X session startup.</p>
<div class="code-header">
<span>~/.xinitrc</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>exec dwn</code></pre>
</div>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Distribution-Specific Instructions</h2>
<div class="tabs">
<button class="tab active" onclick="showTab('debian')">Debian/Ubuntu</button>
<button class="tab" onclick="showTab('fedora')">Fedora</button>
<button class="tab" onclick="showTab('arch')">Arch Linux</button>
<button class="tab" onclick="showTab('void')">Void Linux</button>
</div>
<div id="debian" class="tab-content active">
<h3>Debian / Ubuntu / Linux Mint</h3>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Install dependencies
sudo apt update
sudo apt install -y \
build-essential \
libx11-dev \
libxext-dev \
libxinerama-dev \
libxrandr-dev \
libxft-dev \
libfontconfig1-dev \
libdbus-1-dev \
libcurl4-openssl-dev \
pkg-config
# Build and install
git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn
make
sudo make install</code></pre>
</div>
<div id="fedora" class="tab-content">
<h3>Fedora / RHEL / CentOS</h3>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Install dependencies
sudo dnf install -y \
gcc \
make \
libX11-devel \
libXext-devel \
libXinerama-devel \
libXrandr-devel \
libXft-devel \
fontconfig-devel \
dbus-devel \
libcurl-devel \
pkg-config
# Build and install
git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn
make
sudo make install</code></pre>
</div>
<div id="arch" class="tab-content">
<h3>Arch Linux / Manjaro</h3>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Install dependencies
sudo pacman -S --needed \
base-devel \
libx11 \
libxext \
libxinerama \
libxrandr \
libxft \
fontconfig \
dbus \
curl \
pkg-config
# Build and install
git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn
make
sudo make install</code></pre>
<div class="alert alert-info" style="margin-top: 1rem;">
<strong>AUR Package</strong>
<p style="margin: 0;">An AUR package <code>dwn-git</code> may also be available:
<code>yay -S dwn-git</code></p>
</div>
</div>
<div id="void" class="tab-content">
<h3>Void Linux</h3>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Install dependencies
sudo xbps-install -S \
base-devel \
libX11-devel \
libXext-devel \
libXinerama-devel \
libXrandr-devel \
libXft-devel \
fontconfig-devel \
dbus-devel \
libcurl-devel \
pkg-config
# Build and install
git clone https://retoor.molodetz.nl/retoor/dwn.git
cd dwn
make
sudo make install</code></pre>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2>Session Setup</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">
Configure your display manager or xinit to start DWN.
</p>
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
<div class="card">
<h3>Using xinit / startx</h3>
<p>For minimal setups using startx:</p>
<div class="code-header">
<span>~/.xinitrc</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Optional: set display settings
xrandr --output DP-1 --mode 2560x1440
# Optional: set wallpaper
feh --bg-fill ~/wallpaper.jpg
# Start DWN
exec dwn</code></pre>
<p style="margin-top: 1rem; font-size: 0.875rem; color: var(--text-muted);">
Then run <code>startx</code> from a TTY.
</p>
</div>
<div class="card">
<h3>Using a Display Manager</h3>
<p>Create a desktop entry for GDM, LightDM, etc:</p>
<div class="code-header">
<span>/usr/share/xsessions/dwn.desktop</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>[Desktop Entry]
Name=DWN
Comment=DWN Window Manager
Exec=dwn
Type=Application
DesktopNames=DWN</code></pre>
<p style="margin-top: 1rem; font-size: 0.875rem; color: var(--text-muted);">
DWN will appear in your display manager's session menu.
</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Testing in a Nested X Server</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">
Test DWN without leaving your current session using Xephyr.
</p>
<div class="card">
<h3>Using make run</h3>
<p>The easiest way to test DWN safely:</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Make sure Xephyr is installed
# Debian/Ubuntu: sudo apt install xserver-xephyr
# Fedora: sudo dnf install xorg-x11-server-Xephyr
# Arch: sudo pacman -S xorg-server-xephyr
# Run DWN in a nested window
make run</code></pre>
<p style="margin-top: 1rem; color: var(--text-muted);">
This opens a 1280x720 window running DWN. Perfect for experimenting with configuration changes.
</p>
</div>
<div class="card" style="margin-top: 1.5rem;">
<h3>Manual Xephyr Setup</h3>
<p>For more control over the test environment:</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code># Start Xephyr on display :1
Xephyr :1 -screen 1920x1080 &
# Run DWN on that display
DISPLAY=:1 ./dwn
# Open a terminal in the test environment
DISPLAY=:1 xterm &</code></pre>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2>Post-Installation</h2>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Create Configuration Directory</h4>
<p>DWN will create this automatically on first run, but you can set it up in advance:</p>
<div class="code-header">
<span>Terminal</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div>
<pre><code>mkdir -p ~/.config/dwn</code></pre>
</div>
</div>
<div class="step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Run the Interactive Tutorial</h4>
<p>Once DWN is running, press <kbd>Super</kbd> + <kbd>T</kbd> to start the built-in tutorial
that will teach you all the essential shortcuts.</p>
</div>
</div>
<div class="step">
<div class="step-number">3</div>
<div class="step-content">
<h4>View All Shortcuts</h4>
<p>Press <kbd>Super</kbd> + <kbd>S</kbd> to see a complete list of keyboard shortcuts.</p>
</div>
</div>
<div class="step">
<div class="step-number">4</div>
<div class="step-content">
<h4>Customize Your Setup</h4>
<p>See the <a href="configuration.html">Configuration Guide</a> to personalize DWN to your liking.</p>
</div>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2>Troubleshooting</h2>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
DWN doesn't start - "cannot open display"
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>This error means DWN can't connect to an X server. Make sure:</p>
<ul style="padding-left: 1.25rem; margin-top: 0.5rem;">
<li>You're running from a TTY with <code>startx</code>, not from within another X session</li>
<li>The DISPLAY environment variable is set correctly</li>
<li>X server is installed and working (<code>Xorg -configure</code> to test)</li>
</ul>
</div>
</div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
Build fails - "pkg-config: command not found"
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>Install pkg-config for your distribution:</p>
<pre style="margin-top: 0.5rem;"><code>sudo apt install pkg-config # Debian/Ubuntu
sudo dnf install pkg-config # Fedora
sudo pacman -S pkg-config # Arch</code></pre>
</div>
</div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
Missing header files during build
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>Make sure you have the development packages installed, not just the runtime libraries.
On Debian/Ubuntu, install packages ending with <code>-dev</code>.
Run <code>make deps</code> to auto-install everything.</p>
</div>
</div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
Keyboard shortcuts don't work
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>Check for conflicts with other programs grabbing keys:</p>
<ul style="padding-left: 1.25rem; margin-top: 0.5rem;">
<li>Make sure no other window manager is running</li>
<li>Check if compositor (like picom) is grabbing keys</li>
<li>Verify keyboard layout is correct with <code>setxkbmap</code></li>
</ul>
</div>
</div>
</div>
<div class="faq-item">
<button class="faq-question" onclick="toggleFaq(this)">
Fonts look bad or missing
</button>
<div class="faq-answer">
<div class="faq-answer-content">
<p>DWN uses Xft for font rendering. Install some good fonts:</p>
<pre style="margin-top: 0.5rem;"><code>sudo apt install fonts-dejavu fonts-liberation # Debian/Ubuntu
sudo dnf install dejavu-fonts-all liberation-fonts # Fedora</code></pre>
<p style="margin-top: 0.5rem;">You can configure the font in <code>~/.config/dwn/config</code>.</p>
</div>
</div>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container" style="text-align: center;">
<h2>Installation Complete?</h2>
<p style="color: var(--text-muted); max-width: 500px; margin: 0 auto 2rem;">
Learn how to use DWN effectively with our documentation.
</p>
<div class="hero-buttons" style="justify-content: center;">
<a href="documentation.html" class="btn btn-primary btn-lg">Getting Started Guide</a>
<a href="shortcuts.html" class="btn btn-secondary btn-lg">Keyboard Shortcuts</a>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>
-304
View File
@@ -1,304 +0,0 @@
/**
* DWN Window Manager - Website JavaScript
* Vanilla JS for interactivity
*/
// Mobile Navigation Toggle
function toggleNav() {
const navLinks = document.querySelector('.nav-links');
const toggle = document.querySelector('.nav-toggle');
navLinks.classList.toggle('active');
toggle.classList.toggle('active');
}
// Close mobile nav when clicking outside
document.addEventListener('click', function(e) {
const nav = document.querySelector('nav');
const navLinks = document.querySelector('.nav-links');
if (navLinks && navLinks.classList.contains('active')) {
if (!nav.contains(e.target)) {
navLinks.classList.remove('active');
}
}
});
// Close mobile nav when clicking a link
document.querySelectorAll('.nav-links a').forEach(link => {
link.addEventListener('click', () => {
const navLinks = document.querySelector('.nav-links');
if (navLinks) {
navLinks.classList.remove('active');
}
});
});
// Tab functionality
function showTab(tabId) {
// Hide all tab contents
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// Deactivate all tabs
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
// Show selected tab content
const selectedContent = document.getElementById(tabId);
if (selectedContent) {
selectedContent.classList.add('active');
}
// Activate clicked tab
event.target.classList.add('active');
}
// FAQ Accordion
function toggleFaq(button) {
const faqItem = button.closest('.faq-item');
const isActive = faqItem.classList.contains('active');
// Close all FAQ items
document.querySelectorAll('.faq-item').forEach(item => {
item.classList.remove('active');
});
// Open clicked item if it wasn't already open
if (!isActive) {
faqItem.classList.add('active');
}
}
// Copy to clipboard functionality
function copyCode(button) {
const codeBlock = button.closest('.code-header').nextElementSibling;
const code = codeBlock.querySelector('code');
if (code) {
const text = code.textContent;
navigator.clipboard.writeText(text).then(() => {
// Show feedback
const originalText = button.textContent;
button.textContent = 'Copied!';
button.style.background = 'var(--success)';
setTimeout(() => {
button.textContent = originalText;
button.style.background = '';
}, 2000);
}).catch(err => {
console.error('Failed to copy:', err);
button.textContent = 'Error';
});
}
}
// Shortcut search/filter functionality
function filterShortcuts() {
const searchInput = document.getElementById('shortcut-search');
if (!searchInput) return;
const filter = searchInput.value.toLowerCase();
const tables = document.querySelectorAll('.shortcuts-table');
tables.forEach(table => {
const rows = table.querySelectorAll('tbody tr');
let visibleCount = 0;
rows.forEach(row => {
const text = row.textContent.toLowerCase();
if (text.includes(filter)) {
row.style.display = '';
visibleCount++;
} else {
row.style.display = 'none';
}
});
// Show/hide the entire table section if no matches
const section = table.closest('.table-wrapper');
const header = section ? section.previousElementSibling : null;
if (section && header && header.tagName === 'H2') {
if (visibleCount === 0 && filter !== '') {
section.style.display = 'none';
header.style.display = 'none';
} else {
section.style.display = '';
header.style.display = '';
}
}
});
}
// Smooth scroll for anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
const href = this.getAttribute('href');
if (href === '#') return;
e.preventDefault();
const target = document.querySelector(href);
if (target) {
const headerOffset = 80; // Account for fixed header
const elementPosition = target.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
// Update URL without scrolling
history.pushState(null, null, href);
}
});
});
// Highlight active section in docs sidebar
function updateActiveSection() {
const sidebar = document.querySelector('.docs-sidebar');
if (!sidebar) return;
const sections = document.querySelectorAll('h2[id], h3[id]');
const links = sidebar.querySelectorAll('a[href^="#"]');
let currentSection = '';
const scrollPos = window.scrollY + 100;
sections.forEach(section => {
if (section.offsetTop <= scrollPos) {
currentSection = section.id;
}
});
links.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === '#' + currentSection) {
link.classList.add('active');
}
});
}
// Throttle scroll events
let scrollTimeout;
window.addEventListener('scroll', () => {
if (scrollTimeout) return;
scrollTimeout = setTimeout(() => {
updateActiveSection();
scrollTimeout = null;
}, 100);
});
// Header background on scroll
function updateHeaderBackground() {
const header = document.querySelector('header');
if (!header) return;
if (window.scrollY > 50) {
header.style.background = 'rgba(26, 26, 46, 0.98)';
} else {
header.style.background = 'rgba(26, 26, 46, 0.95)';
}
}
window.addEventListener('scroll', updateHeaderBackground);
// Animate elements on scroll (intersection observer)
function initScrollAnimations() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('fade-in');
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
});
// Observe feature cards and other elements
document.querySelectorAll('.feature-card, .card, .comparison-card, .testimonial').forEach(el => {
el.style.opacity = '0';
observer.observe(el);
});
}
// Keyboard shortcut for search (/)
document.addEventListener('keydown', (e) => {
if (e.key === '/' && !['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) {
const searchInput = document.getElementById('shortcut-search');
if (searchInput) {
e.preventDefault();
searchInput.focus();
}
}
// Escape to close search/nav
if (e.key === 'Escape') {
const searchInput = document.getElementById('shortcut-search');
if (searchInput && document.activeElement === searchInput) {
searchInput.blur();
searchInput.value = '';
filterShortcuts();
}
const navLinks = document.querySelector('.nav-links');
if (navLinks) {
navLinks.classList.remove('active');
}
}
});
// External link handling - open in new tab
document.querySelectorAll('a[href^="http"]').forEach(link => {
if (!link.hostname.includes(window.location.hostname)) {
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
}
});
// Initialize on DOM ready
document.addEventListener('DOMContentLoaded', () => {
initScrollAnimations();
updateActiveSection();
updateHeaderBackground();
// Set current year in footer if needed
const yearSpan = document.querySelector('.current-year');
if (yearSpan) {
yearSpan.textContent = new Date().getFullYear();
}
});
// Print-friendly handling
window.addEventListener('beforeprint', () => {
// Expand all FAQs for printing
document.querySelectorAll('.faq-item').forEach(item => {
item.classList.add('active');
});
// Show all tab contents
document.querySelectorAll('.tab-content').forEach(content => {
content.style.display = 'block';
});
});
window.addEventListener('afterprint', () => {
// Restore FAQ state
document.querySelectorAll('.faq-item').forEach(item => {
item.classList.remove('active');
});
// Restore tab state
document.querySelectorAll('.tab-content').forEach(content => {
content.style.display = '';
});
});
-460
View File
@@ -1,460 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Complete keyboard shortcuts reference for DWN window manager.">
<title>Keyboard Shortcuts - DWN Window Manager</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<a href="index.html" class="logo">
<span class="logo-icon">D</span>
<span>DWN</span>
</a>
<ul class="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Install</a></li>
<li class="dropdown">
<a href="documentation.html" class="active">Docs</a>
<div class="dropdown-menu">
<a href="documentation.html">Getting Started</a>
<a href="shortcuts.html">Keyboard Shortcuts</a>
<a href="configuration.html">Configuration</a>
<a href="ai-features.html">AI Features</a>
<a href="architecture.html">Architecture</a>
<a href="design-patterns.html">Design Patterns</a>
</div>
</li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
<div class="nav-toggle" onclick="toggleNav()">
<span></span>
<span></span>
<span></span>
</div>
</nav>
</header>
<main>
<section class="hero" style="padding: 8rem 0 4rem;">
<div class="container hero-content">
<h1>Keyboard Shortcuts</h1>
<p class="subtitle">
Complete reference for all DWN keyboard shortcuts.
Press <kbd>Super</kbd> + <kbd>S</kbd> in DWN to view this list.
</p>
</div>
</section>
<section class="section">
<div class="container">
<div class="search-box">
<input type="text" id="shortcut-search" placeholder="Search shortcuts..." onkeyup="filterShortcuts()">
</div>
<h2 id="launchers">Application Launchers</h2>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>T</kbd></td>
<td>Open terminal (configurable)</td>
</tr>
<tr>
<td><kbd>Super</kbd> / <kbd>Alt</kbd> + <kbd>F2</kbd></td>
<td>Open application launcher (dmenu/rofi)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>E</kbd></td>
<td>Open file manager (configurable)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>B</kbd></td>
<td>Open web browser</td>
</tr>
<tr>
<td><kbd>Print</kbd></td>
<td>Take screenshot (xfce4-screenshooter)</td>
</tr>
</tbody>
</table>
</div>
<h2 id="windows">Window Management</h2>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Alt</kbd> + <kbd>F4</kbd></td>
<td>Close focused window</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>Tab</kbd></td>
<td>Cycle to next window</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
<td>Cycle to previous window</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>F9</kbd></td>
<td>Toggle minimize/restore</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>F10</kbd></td>
<td>Toggle maximize</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>F11</kbd></td>
<td>Toggle fullscreen</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>F9</kbd></td>
<td>Toggle floating mode for focused window</td>
</tr>
</tbody>
</table>
</div>
<h2 id="workspaces">Workspace Navigation</h2>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>F1</kbd></td>
<td>Switch to workspace 1</td>
</tr>
<tr>
<td><kbd>F2</kbd></td>
<td>Switch to workspace 2</td>
</tr>
<tr>
<td><kbd>F3</kbd></td>
<td>Switch to workspace 3</td>
</tr>
<tr>
<td><kbd>F4</kbd></td>
<td>Switch to workspace 4</td>
</tr>
<tr>
<td><kbd>F5</kbd></td>
<td>Switch to workspace 5</td>
</tr>
<tr>
<td><kbd>F6</kbd></td>
<td>Switch to workspace 6</td>
</tr>
<tr>
<td><kbd>F7</kbd></td>
<td>Switch to workspace 7</td>
</tr>
<tr>
<td><kbd>F8</kbd></td>
<td>Switch to workspace 8</td>
</tr>
<tr>
<td><kbd>F9</kbd></td>
<td>Switch to workspace 9</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F1</kbd></td>
<td>Move focused window to workspace 1</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F2</kbd></td>
<td>Move focused window to workspace 2</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F3</kbd></td>
<td>Move focused window to workspace 3</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F4</kbd></td>
<td>Move focused window to workspace 4</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F5</kbd></td>
<td>Move focused window to workspace 5</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F6</kbd></td>
<td>Move focused window to workspace 6</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F7</kbd></td>
<td>Move focused window to workspace 7</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F8</kbd></td>
<td>Move focused window to workspace 8</td>
</tr>
<tr>
<td><kbd>Shift</kbd> + <kbd>F9</kbd></td>
<td>Move focused window to workspace 9</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Right</kbd></td>
<td>Switch to next workspace</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>Left</kbd></td>
<td>Switch to previous workspace</td>
</tr>
</tbody>
</table>
</div>
<h2 id="layouts">Layout Control</h2>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>Space</kbd></td>
<td>Cycle layout mode (tiling → floating → monocle)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>H</kbd></td>
<td>Shrink master area</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>L</kbd></td>
<td>Expand master area</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>I</kbd></td>
<td>Increase master window count</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>D</kbd></td>
<td>Decrease master window count</td>
</tr>
</tbody>
</table>
</div>
<h2 id="snapping">Window Snapping (Composable)</h2>
<p style="color: var(--text-muted); margin-bottom: 1rem;">
Snapping shortcuts are composable: press Super+Left then Super+Up for top-left quarter. Press the same key twice to expand to full in that axis.
</p>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>Left</kbd></td>
<td>Snap left 50% (press twice for full width)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Right</kbd></td>
<td>Snap right 50% (press twice for full width)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Up</kbd></td>
<td>Snap top 50% (press twice for full height)</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Down</kbd></td>
<td>Snap bottom 50% (press twice for full height)</td>
</tr>
</tbody>
</table>
</div>
<h2 id="ai">AI Features</h2>
<p style="color: var(--text-muted); margin-bottom: 1rem;">
These shortcuts require API keys to be configured. See <a href="ai-features.html">AI Features</a> for setup.
</p>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>A</kbd></td>
<td>Open AI command palette</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>A</kbd></td>
<td>Show AI context analysis</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>E</kbd></td>
<td>Open Exa semantic web search</td>
</tr>
</tbody>
</table>
</div>
<h2 id="news">News Ticker</h2>
<p style="color: var(--text-muted); margin-bottom: 1rem;">
The scrolling news ticker is displayed in the bottom panel.
</p>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>Return</kbd></td>
<td>Open current article in browser</td>
</tr>
</tbody>
</table>
</div>
<h2 id="system">Help & System</h2>
<div class="table-wrapper">
<table class="shortcuts-table">
<thead>
<tr>
<th style="width: 40%;">Shortcut</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><kbd>Super</kbd> + <kbd>S</kbd></td>
<td>Show all keyboard shortcuts</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>T</kbd></td>
<td>Start/continue interactive tutorial</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>D</kbd></td>
<td>Start/stop demo mode</td>
</tr>
<tr>
<td><kbd>Super</kbd> + <kbd>Backspace</kbd></td>
<td>Quit DWN</td>
</tr>
</tbody>
</table>
</div>
<div class="card" style="margin-top: 3rem;">
<h3>Printable Quick Reference</h3>
<p>Essential shortcuts to memorize when starting with DWN:</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin-top: 1.5rem;">
<div>
<h4 style="color: var(--primary); margin-bottom: 0.75rem;">Must Know</h4>
<ul style="list-style: none; padding: 0;">
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>T</kbd> Terminal
</li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Alt</kbd>+<kbd>F2</kbd> Launcher
</li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Alt</kbd>+<kbd>F4</kbd> Close window
</li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Alt</kbd>+<kbd>Tab</kbd> Switch windows
</li>
<li style="padding: 0.5rem 0;">
<kbd>F1</kbd>-<kbd>F9</kbd> Workspaces
</li>
</ul>
</div>
<div>
<h4 style="color: var(--primary); margin-bottom: 0.75rem;">Power User</h4>
<ul style="list-style: none; padding: 0;">
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Super</kbd>+<kbd>Space</kbd> Change layout
</li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Super</kbd>+<kbd>H</kbd>/<kbd>L</kbd> Resize master
</li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Super</kbd>+Arrows Composable snap
</li>
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
<kbd>Shift</kbd>+<kbd>F1-9</kbd> Move to workspace
</li>
<li style="padding: 0.5rem 0;">
<kbd>Super</kbd>+<kbd>S</kbd> Show shortcuts
</li>
</ul>
</div>
</div>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-section">
<h4>DWN Window Manager</h4>
<p style="color: var(--text-muted);">
A modern, production-ready X11 window manager with XFCE-like
functionality and optional AI integration.
</p>
</div>
<div class="footer-section">
<h4>Documentation</h4>
<ul>
<li><a href="documentation.html">Getting Started</a></li>
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="architecture.html">Architecture</a></li>
<li><a href="design-patterns.html">Design Patterns</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Resources</h4>
<ul>
<li><a href="features.html">Features</a></li>
<li><a href="installation.html">Installation</a></li>
<li><a href="ai-features.html">AI Integration</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Community</h4>
<ul>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/discussions">Discussions</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/CONTRIBUTING.md">Contributing</a></li>
<li><a href="https://retoor.molodetz.nl/retoor/dwn/blob/main/LICENSE">License (MIT)</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>DWN Window Manager by retoor &lt;retoor@molodetz.nl&gt; - MIT License</p>
</div>
</div>
</footer>
<script src="js/main.js"></script>
</body>
</html>
+704 -344
View File
File diff suppressed because it is too large Load Diff
+1266 -3
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -16,10 +16,11 @@
#include <unistd.h>
#include <ctype.h>
#include <pwd.h>
#include <limits.h>
static AppLauncherState launcher_state = {0};
static char recent_file_path[512] = {0};
static char recent_file_path[PATH_MAX] = {0};
static void scan_desktop_dir(const char *dir);
static int parse_desktop_file(const char *path, AppEntry *entry);
@@ -42,7 +43,7 @@ void applauncher_init(void)
snprintf(recent_file_path, sizeof(recent_file_path),
"%s/.local/share/dwn/recent_apps", home);
char dir_path[512];
char dir_path[PATH_MAX];
snprintf(dir_path, sizeof(dir_path), "%s/.local/share/dwn", home);
mkdir(dir_path, 0755);
@@ -66,7 +67,7 @@ void applauncher_refresh(void)
const char *home = getenv("HOME");
if (home) {
char user_apps[512];
char user_apps[PATH_MAX];
snprintf(user_apps, sizeof(user_apps), "%s/.local/share/applications", home);
scan_desktop_dir(user_apps);
}
@@ -271,7 +272,7 @@ static void add_to_recent(const char *desktop_id)
static char *strip_field_codes(const char *exec)
{
static char result[512];
static char result[PATH_MAX];
size_t j = 0;
for (size_t i = 0; exec[i] && j < sizeof(result) - 1; i++) {
+206
View File
@@ -0,0 +1,206 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Async Context Implementation - Per-Module Async State
*/
#include "threading.h"
#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/eventfd.h>
/* Full definition of AsyncContext implementation */
struct AsyncContextImpl {
char name[32];
ThreadPool *pool;
EventBus *event_bus;
Channel *completion_channel;
_Atomic int operation_count;
_Atomic int shutdown_requested;
pthread_mutex_t mutex;
int event_fd;
};
typedef struct {
struct AsyncContextImpl *ctx;
TaskFunc user_func;
void *user_data;
Future *future;
} AsyncWrapperData;
static void async_wrapper_func(void *data, atomic_int *cancelled)
{
AsyncWrapperData *wrap = data;
/* Execute user's function */
if (wrap->user_func) {
wrap->user_func(wrap->user_data, cancelled);
}
/* Signal completion via channel */
TaskHandle *task_ptr = dwn_malloc(sizeof(TaskHandle));
if (task_ptr) {
*task_ptr = (TaskHandle)wrap; /* Just use wrapper as identifier */
channel_try_send(wrap->ctx->completion_channel, task_ptr);
}
atomic_fetch_sub_explicit(&wrap->ctx->operation_count, 1, ATOMIC_RELAXED);
}
AsyncContext* async_context_create(const char *name)
{
struct AsyncContextImpl *ctx = dwn_malloc(sizeof(struct AsyncContextImpl));
if (!ctx) return NULL;
strncpy(ctx->name, name ? name : "unnamed", sizeof(ctx->name) - 1);
ctx->name[sizeof(ctx->name) - 1] = '\0';
ctx->pool = thread_pool_default();
ctx->event_bus = event_bus_default();
ctx->completion_channel = channel_create(64);
if (!ctx->completion_channel) {
dwn_free(ctx);
return NULL;
}
atomic_store_explicit(&ctx->operation_count, 0, ATOMIC_RELEASE);
atomic_store_explicit(&ctx->shutdown_requested, 0, ATOMIC_RELEASE);
if (pthread_mutex_init(&ctx->mutex, NULL) != 0) {
channel_destroy(ctx->completion_channel);
dwn_free(ctx);
return NULL;
}
/* We use the channel's eventfd for polling */
ctx->event_fd = -1; /* Will use channel's fd via async_get_poll_fd */
return (AsyncContext*)ctx;
}
void async_context_destroy(AsyncContext *ctx)
{
if (!ctx) return;
struct AsyncContextImpl *impl = (struct AsyncContextImpl*)ctx;
atomic_store_explicit(&impl->shutdown_requested, 1, ATOMIC_RELEASE);
/* Wait for operations to complete */
int timeout = 100; /* 10 seconds max wait */
while (atomic_load_explicit(&impl->operation_count, ATOMIC_ACQUIRE) > 0 && timeout > 0) {
usleep(100000); /* 100ms */
timeout--;
}
channel_close(impl->completion_channel);
channel_destroy(impl->completion_channel);
pthread_mutex_destroy(&impl->mutex);
dwn_free(impl);
}
TaskHandle async_submit(AsyncContext *ctx, TaskFunc func, void *user_data,
TaskPriority priority)
{
if (!ctx || !func) return NULL;
struct AsyncContextImpl *impl = (struct AsyncContextImpl*)ctx;
if (atomic_load_explicit(&impl->shutdown_requested, ATOMIC_ACQUIRE)) {
return NULL;
}
AsyncWrapperData *wrap = dwn_malloc(sizeof(AsyncWrapperData));
if (!wrap) return NULL;
wrap->ctx = impl;
wrap->user_func = func;
wrap->user_data = user_data;
wrap->future = NULL;
atomic_fetch_add_explicit(&impl->operation_count, 1, ATOMIC_RELAXED);
TaskHandle task = task_create_with_callback(
async_wrapper_func, wrap, priority,
NULL, NULL
);
if (!task || thread_pool_submit(thread_pool_default(), task) != THREAD_OK) {
atomic_fetch_sub_explicit(&impl->operation_count, 1, ATOMIC_RELAXED);
dwn_free(wrap);
return NULL;
}
return task;
}
Future* async_submit_future(AsyncContext *ctx, TaskFunc func, void *user_data,
TaskPriority priority)
{
if (!ctx || !func) return NULL;
struct AsyncContextImpl *impl = (struct AsyncContextImpl*)ctx;
Future *f = future_create();
if (!f) return NULL;
AsyncWrapperData *wrap = dwn_malloc(sizeof(AsyncWrapperData));
if (!wrap) {
future_destroy(f);
return NULL;
}
wrap->ctx = impl;
wrap->user_func = func;
wrap->user_data = user_data;
wrap->future = f;
atomic_fetch_add_explicit(&impl->operation_count, 1, ATOMIC_RELAXED);
TaskHandle task = task_create(async_wrapper_func, wrap, priority);
if (!task) {
atomic_fetch_sub_explicit(&impl->operation_count, 1, ATOMIC_RELAXED);
dwn_free(wrap);
future_destroy(f);
return NULL;
}
ThreadStatus status = thread_pool_submit(thread_pool_default(), task);
if (status != THREAD_OK) {
task_destroy(task);
atomic_fetch_sub_explicit(&impl->operation_count, 1, ATOMIC_RELAXED);
dwn_free(wrap);
future_destroy(f);
return NULL;
}
return f;
}
uint32_t async_poll(AsyncContext *ctx)
{
if (!ctx) return 0;
struct AsyncContextImpl *impl = (struct AsyncContextImpl*)ctx;
uint32_t processed = 0;
void *data;
while (channel_try_recv(impl->completion_channel, &data) == THREAD_OK) {
dwn_free(data);
processed++;
}
return processed;
}
int async_get_poll_fd(AsyncContext *ctx)
{
if (!ctx) return -1;
struct AsyncContextImpl *impl = (struct AsyncContextImpl*)ctx;
return impl->event_fd;
}
+27 -6
View File
@@ -202,22 +202,43 @@ void atoms_update_desktop_names(void)
return;
}
char names[MAX_WORKSPACES * 32];
int offset = 0;
/* Buffer needs to account for:
- MAX_WORKSPACES names
- Each name up to sizeof(Workspace.name) - 1 = 31 chars
- Plus null terminator for each name
- Plus 8 characters for numeric fallback (e.g., "9\0")
*/
char names[MAX_WORKSPACES * (32 + 1)];
size_t offset = 0;
size_t max_offset = sizeof(names);
for (int i = 0; i < MAX_WORKSPACES; i++) {
const char *name = dwn->workspaces[i].name;
size_t remaining = max_offset - offset;
if (remaining < 2) { /* Need at least room for one char + null */
break;
}
int written;
if (name[0] == '\0') {
offset += snprintf(names + offset, sizeof(names) - offset, "%d", i + 1);
written = snprintf(names + offset, remaining, "%d", i + 1);
} else {
offset += snprintf(names + offset, sizeof(names) - offset, "%s", name);
written = snprintf(names + offset, remaining, "%s", name);
}
if (written < 0 || (size_t)written >= remaining) {
/* Truncation or error - ensure null termination */
names[offset] = '\0';
offset++;
} else {
offset += (size_t)written + 1; /* Include null terminator */
}
names[offset++] = '\0';
}
XChangeProperty(dwn->display, dwn->root, ewmh.NET_DESKTOP_NAMES,
misc_atoms.UTF8_STRING, 8, PropModeReplace,
(unsigned char *)names, offset);
(unsigned char *)names, (int)offset);
}
void atoms_set_current_desktop(int desktop)
+3 -3
View File
@@ -110,7 +110,7 @@ static void scan_xdg_directory(const char *dir_path)
continue;
}
char exec_cmd[512];
char exec_cmd[PATH_MAX];
if (parse_desktop_file(full_path, exec_cmd, sizeof(exec_cmd)) == 0) {
LOG_INFO("Autostart: %s -> %s", entry->d_name, exec_cmd);
spawn_async(exec_cmd);
@@ -236,7 +236,7 @@ static int parse_desktop_file(const char *path, char *exec_cmd, size_t exec_size
}
if (access(try_exec, X_OK) != 0) {
char which_cmd[512];
char which_cmd[PATH_MAX];
snprintf(which_cmd, sizeof(which_cmd), "which %s >/dev/null 2>&1", try_exec);
if (system(which_cmd) != 0) {
exec_cmd[0] = '\0';
@@ -256,7 +256,7 @@ static void strip_exec_field_codes(char *exec)
return;
}
char result[512];
char result[PATH_MAX];
size_t j = 0;
for (size_t i = 0; exec[i] != '\0' && j < sizeof(result) - 1; i++) {
+806
View File
@@ -0,0 +1,806 @@
/*
* DWN - Desktop Window Manager
* X11 Backend Implementation
*/
#define _GNU_SOURCE
#include "backends/x11/x11_backend.h"
#include "core/wm_types.h"
#include "core/wm_string.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#include <X11/keysym.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/*==============================================================================
* X11 Backend State
*============================================================================*/
static struct {
Display *display;
int screen;
Window root;
bool initialized;
int fd;
/* Error handling */
int last_error;
XErrorHandler error_handler;
XIOErrorHandler io_error_handler;
/* Cursor cache */
Cursor cursor_cache[XC_num_glyphs];
bool cursor_loaded[XC_num_glyphs];
/* Atoms cache */
Atom atom_wm_protocols;
Atom atom_wm_delete_window;
Atom atom_wm_state;
Atom atom_wm_fullscreen;
Atom atom_wm_maximized_horz;
Atom atom_wm_maximized_vert;
Atom atom_net_wm_name;
Atom atom_utf8_string;
} x11_state = {0};
/*==============================================================================
* Error Handlers
*============================================================================*/
static int x11_error_handler(Display *dpy, XErrorEvent *ev) {
x11_state.last_error = ev->error_code;
char msg[256];
XGetErrorText(dpy, ev->error_code, msg, sizeof(msg));
fprintf(stderr, "X11 Error: %s (request %d, minor %d)\n",
msg, ev->request_code, ev->minor_code);
return 0;
}
static int x11_io_error_handler(Display *dpy) {
(void)dpy;
fprintf(stderr, "X11 IO Error: Connection to display lost\n");
x11_state.initialized = false;
return 0;
}
/*==============================================================================
* Backend Information
*============================================================================*/
static WmBackendInfo x11_get_info(void) {
return (WmBackendInfo){
.name = "x11",
.version = "1.0",
.description = "X11 Window System Backend",
.capabilities =
WM_BACKEND_CAP_WINDOWS |
WM_BACKEND_CAP_INPUT_EVENTS |
WM_BACKEND_CAP_MULTI_MONITOR |
WM_BACKEND_CAP_CLIPBOARD,
.supports_multiple_instances = false,
.requires_compositor = false
};
}
/*==============================================================================
* Lifecycle
*============================================================================*/
static bool x11_init(void *config) {
(void)config;
if (x11_state.initialized) return true;
XSetErrorHandler(x11_error_handler);
XSetIOErrorHandler(x11_io_error_handler);
return true;
}
static void x11_shutdown(void) {
if (!x11_state.initialized) return;
/* Free cached cursors */
for (int i = 0; i < XC_num_glyphs; i++) {
if (x11_state.cursor_loaded[i]) {
XFreeCursor(x11_state.display, x11_state.cursor_cache[i]);
}
}
x11_state.initialized = false;
}
static bool x11_is_initialized(void) {
return x11_state.initialized;
}
/*==============================================================================
* Connection
*============================================================================*/
static bool x11_connect(void) {
if (x11_state.display) return true;
x11_state.display = XOpenDisplay(NULL);
if (!x11_state.display) {
fprintf(stderr, "Failed to open X11 display\n");
return false;
}
x11_state.screen = DefaultScreen(x11_state.display);
x11_state.root = RootWindow(x11_state.display, x11_state.screen);
x11_state.fd = ConnectionNumber(x11_state.display);
x11_state.initialized = true;
/* Cache atoms */
x11_state.atom_wm_protocols = XInternAtom(x11_state.display, "WM_PROTOCOLS", False);
x11_state.atom_wm_delete_window = XInternAtom(x11_state.display, "WM_DELETE_WINDOW", False);
x11_state.atom_wm_state = XInternAtom(x11_state.display, "_NET_WM_STATE", False);
x11_state.atom_wm_fullscreen = XInternAtom(x11_state.display, "_NET_WM_STATE_FULLSCREEN", False);
x11_state.atom_wm_maximized_horz = XInternAtom(x11_state.display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
x11_state.atom_wm_maximized_vert = XInternAtom(x11_state.display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
x11_state.atom_net_wm_name = XInternAtom(x11_state.display, "_NET_WM_NAME", False);
x11_state.atom_utf8_string = XInternAtom(x11_state.display, "UTF8_STRING", False);
return true;
}
static void x11_disconnect(void) {
if (x11_state.display) {
XCloseDisplay(x11_state.display);
x11_state.display = NULL;
x11_state.initialized = false;
}
}
static bool x11_is_connected(void) {
return x11_state.display != NULL && x11_state.initialized;
}
static int x11_get_file_descriptor(void) {
return x11_state.fd;
}
static void x11_flush(void) {
if (x11_state.display) XFlush(x11_state.display);
}
static void x11_sync(void) {
if (x11_state.display) XSync(x11_state.display, False);
}
/*==============================================================================
* Screen/Display
*============================================================================*/
static void x11_get_screen_dimensions(int *width, int *height) {
if (!x11_state.display) {
if (width) *width = 0;
if (height) *height = 0;
return;
}
if (width) *width = DisplayWidth(x11_state.display, x11_state.screen);
if (height) *height = DisplayHeight(x11_state.display, x11_state.screen);
}
static int x11_get_screen_count(void) {
return x11_state.display ? ScreenCount(x11_state.display) : 0;
}
static void x11_get_screen_geometry(int screen, WmRect *geometry) {
if (!x11_state.display || !geometry) return;
geometry->x = 0;
geometry->y = 0;
geometry->width = DisplayWidth(x11_state.display, screen);
geometry->height = DisplayHeight(x11_state.display, screen);
}
/*==============================================================================
* Window Management - Geometry
*============================================================================*/
static void x11_window_get_geometry(WmWindowHandle window, WmRect *geometry) {
if (!x11_state.display || !geometry) return;
Window root;
int x, y;
unsigned int width, height, border, depth;
if (XGetGeometry(x11_state.display, (Window)window, &root,
&x, &y, &width, &height, &border, &depth)) {
geometry->x = x;
geometry->y = y;
geometry->width = (int)width;
geometry->height = (int)height;
}
}
static void x11_window_set_geometry(WmWindowHandle window, const WmRect *geometry) {
if (!x11_state.display || !geometry) return;
XMoveResizeWindow(x11_state.display, (Window)window,
geometry->x, geometry->y,
(unsigned int)geometry->width,
(unsigned int)geometry->height);
}
static void x11_window_move(WmWindowHandle window, int x, int y) {
if (!x11_state.display) return;
XMoveWindow(x11_state.display, (Window)window, x, y);
}
static void x11_window_resize(WmWindowHandle window, int width, int height) {
if (!x11_state.display) return;
XResizeWindow(x11_state.display, (Window)window,
(unsigned int)width, (unsigned int)height);
}
static void x11_window_move_resize(WmWindowHandle window, const WmRect *geometry) {
x11_window_set_geometry(window, geometry);
}
/*==============================================================================
* Window Management - Visibility
*============================================================================*/
static void x11_window_show(WmWindowHandle window) {
if (!x11_state.display) return;
XMapWindow(x11_state.display, (Window)window);
}
static void x11_window_hide(WmWindowHandle window) {
if (!x11_state.display) return;
XUnmapWindow(x11_state.display, (Window)window);
}
static bool x11_window_is_visible(WmWindowHandle window) {
if (!x11_state.display) return false;
XWindowAttributes attrs;
if (XGetWindowAttributes(x11_state.display, (Window)window, &attrs)) {
return attrs.map_state == IsViewable;
}
return false;
}
static void x11_window_map(WmWindowHandle window) {
x11_window_show(window);
}
static void x11_window_unmap(WmWindowHandle window) {
x11_window_hide(window);
}
static bool x11_window_is_mapped(WmWindowHandle window) {
return x11_window_is_visible(window);
}
/*==============================================================================
* Window Management - Stacking
*============================================================================*/
static void x11_window_raise(WmWindowHandle window) {
if (!x11_state.display) return;
XRaiseWindow(x11_state.display, (Window)window);
}
static void x11_window_lower(WmWindowHandle window) {
if (!x11_state.display) return;
XLowerWindow(x11_state.display, (Window)window);
}
static void x11_window_reparent(WmWindowHandle window, WmWindowHandle parent, int x, int y) {
if (!x11_state.display) return;
XReparentWindow(x11_state.display, (Window)window, (Window)parent, x, y);
}
/*==============================================================================
* Window Management - Focus
*============================================================================*/
static void x11_window_focus(WmWindowHandle window) {
if (!x11_state.display) return;
XSetInputFocus(x11_state.display, (Window)window, RevertToPointerRoot, CurrentTime);
}
static void x11_window_unfocus(WmWindowHandle window) {
(void)window;
if (!x11_state.display) return;
XSetInputFocus(x11_state.display, PointerRoot, RevertToPointerRoot, CurrentTime);
}
static bool x11_window_is_focused(WmWindowHandle window) {
if (!x11_state.display) return false;
Window focused;
int revert_to;
XGetInputFocus(x11_state.display, &focused, &revert_to);
return (Window)window == focused;
}
static WmWindowHandle x11_window_get_focused(void) {
if (!x11_state.display) return NULL;
Window focused;
int revert_to;
XGetInputFocus(x11_state.display, &focused, &revert_to);
return (WmWindowHandle)focused;
}
/*==============================================================================
* Window Management - Border
*============================================================================*/
static void x11_window_set_border_width(WmWindowHandle window, int width) {
if (!x11_state.display) return;
XSetWindowBorderWidth(x11_state.display, (Window)window, (unsigned int)width);
}
static int x11_window_get_border_width(WmWindowHandle window) {
if (!x11_state.display) return 0;
XWindowAttributes attrs;
if (XGetWindowAttributes(x11_state.display, (Window)window, &attrs)) {
return (int)attrs.border_width;
}
return 0;
}
/*==============================================================================
* Window Management - Properties
*============================================================================*/
static void x11_window_set_title(WmWindowHandle window, const char *title) {
if (!x11_state.display || !title) return;
XStoreName(x11_state.display, (Window)window, title);
if (x11_state.atom_net_wm_name != None && x11_state.atom_utf8_string != None) {
XChangeProperty(x11_state.display, (Window)window,
x11_state.atom_net_wm_name, x11_state.atom_utf8_string,
8, PropModeReplace,
(const unsigned char*)title, (int)strlen(title));
}
}
static char* x11_window_get_title(WmWindowHandle window) {
if (!x11_state.display) return NULL;
char *title = NULL;
/* Try _NET_WM_NAME first (UTF-8) */
if (x11_state.atom_net_wm_name != None) {
Atom actual_type;
int actual_format;
unsigned long nitems, bytes_after;
unsigned char *data = NULL;
if (XGetWindowProperty(x11_state.display, (Window)window,
x11_state.atom_net_wm_name, 0, ~0L, False,
x11_state.atom_utf8_string, &actual_type,
&actual_format, &nitems, &bytes_after, &data) == Success) {
if (data && actual_type == x11_state.atom_utf8_string) {
title = strdup((char*)data);
XFree(data);
if (title) return title;
}
if (data) XFree(data);
}
}
/* Fall back to WM_NAME */
char *name = NULL;
if (XFetchName(x11_state.display, (Window)window, &name)) {
title = strdup(name);
XFree(name);
}
return title;
}
/*==============================================================================
* Window Management - Protocols
*============================================================================*/
static bool x11_window_supports_protocol(WmWindowHandle window, const char *protocol) {
if (!x11_state.display || !protocol) return false;
Atom proto = XInternAtom(x11_state.display, protocol, True);
if (proto == None) return false;
Atom *protocols = NULL;
int count = 0;
if (XGetWMProtocols(x11_state.display, (Window)window, &protocols, &count)) {
for (int i = 0; i < count; i++) {
if (protocols[i] == proto) {
XFree(protocols);
return true;
}
}
XFree(protocols);
}
return false;
}
static void x11_window_close(WmWindowHandle window) {
if (!x11_state.display) return;
if (x11_window_supports_protocol(window, "WM_DELETE_WINDOW")) {
XEvent ev;
ev.type = ClientMessage;
ev.xclient.window = (Window)window;
ev.xclient.message_type = x11_state.atom_wm_protocols;
ev.xclient.format = 32;
ev.xclient.data.l[0] = (long)x11_state.atom_wm_delete_window;
ev.xclient.data.l[1] = CurrentTime;
XSendEvent(x11_state.display, (Window)window, False, NoEventMask, &ev);
} else {
XKillClient(x11_state.display, (Window)window);
}
}
static void x11_window_kill(WmWindowHandle window) {
if (!x11_state.display) return;
XKillClient(x11_state.display, (Window)window);
}
/*==============================================================================
* Window Management - State
*============================================================================*/
static void x11_window_set_fullscreen(WmWindowHandle window, bool fullscreen) {
if (!x11_state.display) return;
XEvent ev;
ev.type = ClientMessage;
ev.xclient.window = (Window)window;
ev.xclient.message_type = x11_state.atom_wm_state;
ev.xclient.format = 32;
ev.xclient.data.l[0] = fullscreen ? 1 : 0; /* _NET_WM_STATE_ADD or _NET_WM_STATE_REMOVE */
ev.xclient.data.l[1] = (long)x11_state.atom_wm_fullscreen;
ev.xclient.data.l[2] = 0;
ev.xclient.data.l[3] = 1; /* Source indication: normal application */
ev.xclient.data.l[4] = 0;
XSendEvent(x11_state.display, x11_state.root, False,
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
}
static void x11_window_set_urgent(WmWindowHandle window, bool urgent) {
if (!x11_state.display) return;
XWMHints *hints = XGetWMHints(x11_state.display, (Window)window);
if (!hints) {
hints = XAllocWMHints();
if (!hints) return;
}
if (urgent) {
hints->flags |= XUrgencyHint;
} else {
hints->flags &= ~XUrgencyHint;
}
XSetWMHints(x11_state.display, (Window)window, hints);
XFree(hints);
}
static bool x11_window_is_urgent(WmWindowHandle window) {
if (!x11_state.display) return false;
XWMHints *hints = XGetWMHints(x11_state.display, (Window)window);
if (!hints) return false;
bool urgent = (hints->flags & XUrgencyHint) != 0;
XFree(hints);
return urgent;
}
/*==============================================================================
* Input - Events
*============================================================================*/
static bool x11_poll_event(WmBackendEvent *event) {
if (!x11_state.display || !event) return false;
if (!XPending(x11_state.display)) return false;
XEvent xev;
XNextEvent(x11_state.display, &xev);
/* Translate X11 event to backend event */
memset(event, 0, sizeof(*event));
event->window = (WmWindowHandle)xev.xany.window;
event->timestamp = (uint64_t)xev.xany.serial;
switch (xev.type) {
case Expose:
event->type = WM_BACKEND_EVENT_EXPOSE;
event->data.configure.x = xev.xexpose.x;
event->data.configure.y = xev.xexpose.y;
event->data.configure.width = xev.xexpose.width;
event->data.configure.height = xev.xexpose.height;
break;
case ConfigureNotify:
event->type = WM_BACKEND_EVENT_CONFIGURE;
event->data.configure.x = xev.xconfigure.x;
event->data.configure.y = xev.xconfigure.y;
event->data.configure.width = xev.xconfigure.width;
event->data.configure.height = xev.xconfigure.height;
break;
case MapNotify:
event->type = WM_BACKEND_EVENT_MAP;
break;
case UnmapNotify:
event->type = WM_BACKEND_EVENT_UNMAP;
break;
case DestroyNotify:
event->type = WM_BACKEND_EVENT_DESTROY;
break;
case FocusIn:
event->type = WM_BACKEND_EVENT_FOCUS_IN;
break;
case FocusOut:
event->type = WM_BACKEND_EVENT_FOCUS_OUT;
break;
case KeyPress:
event->type = WM_BACKEND_EVENT_KEY_PRESS;
event->data.key.keycode = xev.xkey.keycode;
event->data.key.state = xev.xkey.state;
break;
case KeyRelease:
event->type = WM_BACKEND_EVENT_KEY_RELEASE;
event->data.key.keycode = xev.xkey.keycode;
event->data.key.state = xev.xkey.state;
break;
case ButtonPress:
event->type = WM_BACKEND_EVENT_BUTTON_PRESS;
event->data.button.button = xev.xbutton.button;
event->data.button.x = xev.xbutton.x;
event->data.button.y = xev.xbutton.y;
event->data.button.state = xev.xbutton.state;
break;
case ButtonRelease:
event->type = WM_BACKEND_EVENT_BUTTON_RELEASE;
event->data.button.button = xev.xbutton.button;
event->data.button.x = xev.xbutton.x;
event->data.button.y = xev.xbutton.y;
event->data.button.state = xev.xbutton.state;
break;
case MotionNotify:
event->type = WM_BACKEND_EVENT_MOTION;
event->data.button.x = xev.xmotion.x;
event->data.button.y = xev.xmotion.y;
event->data.button.state = xev.xmotion.state;
break;
case EnterNotify:
event->type = WM_BACKEND_EVENT_ENTER;
break;
case LeaveNotify:
event->type = WM_BACKEND_EVENT_LEAVE;
break;
case PropertyNotify:
event->type = WM_BACKEND_EVENT_PROPERTY;
event->data.property.name = XGetAtomName(x11_state.display, xev.xproperty.atom);
break;
case ClientMessage:
event->type = WM_BACKEND_EVENT_CLIENT_MESSAGE;
break;
default:
event->type = WM_BACKEND_EVENT_NONE;
break;
}
return true;
}
static void x11_wait_event(WmBackendEvent *event) {
if (!x11_state.display || !event) return;
while (!x11_poll_event(event)) {
/* Wait for event */
}
}
/*==============================================================================
* Input - Grabbing
*============================================================================*/
static void x11_grab_key(int keycode, uint32_t modifiers, WmWindowHandle window,
bool owner_events, uint32_t pointer_mode, uint32_t keyboard_mode) {
if (!x11_state.display) return;
XGrabKey(x11_state.display, keycode, modifiers, (Window)window,
owner_events, pointer_mode, keyboard_mode);
}
static void x11_ungrab_key(int keycode, uint32_t modifiers, WmWindowHandle window) {
if (!x11_state.display) return;
XUngrabKey(x11_state.display, keycode, modifiers, (Window)window);
}
static void x11_grab_button(int button, uint32_t modifiers, WmWindowHandle window,
bool owner_events, uint32_t event_mask,
uint32_t pointer_mode, uint32_t keyboard_mode,
WmWindowHandle confine_to, uint32_t cursor) {
if (!x11_state.display) return;
XGrabButton(x11_state.display, button, modifiers, (Window)window,
owner_events, event_mask, pointer_mode, keyboard_mode,
(Window)confine_to, cursor);
}
static void x11_ungrab_button(int button, uint32_t modifiers, WmWindowHandle window) {
if (!x11_state.display) return;
XUngrabButton(x11_state.display, button, modifiers, (Window)window);
}
static void x11_grab_pointer(WmWindowHandle window, bool owner_events, uint32_t event_mask,
uint32_t pointer_mode, uint32_t keyboard_mode,
WmWindowHandle confine_to, uint32_t cursor, WmTime time) {
if (!x11_state.display) return;
XGrabPointer(x11_state.display, (Window)window, owner_events, event_mask,
pointer_mode, keyboard_mode, (Window)confine_to,
(Cursor)cursor, (Time)time);
}
static void x11_ungrab_pointer(WmTime time) {
if (!x11_state.display) return;
XUngrabPointer(x11_state.display, (Time)time);
}
/*==============================================================================
* Input - Pointer
*============================================================================*/
static void x11_query_pointer(WmWindowHandle window, int *root_x, int *root_y,
int *win_x, int *win_y, uint32_t *mask) {
if (!x11_state.display) return;
Window root, child;
int rx, ry, wx, wy;
unsigned int m;
XQueryPointer(x11_state.display, (Window)window, &root, &child,
&rx, &ry, &wx, &wy, &m);
if (root_x) *root_x = rx;
if (root_y) *root_y = ry;
if (win_x) *win_x = wx;
if (win_y) *win_y = wy;
if (mask) *mask = m;
}
static void x11_warp_pointer(WmWindowHandle dest_w, int dest_x, int dest_y) {
if (!x11_state.display) return;
XWarpPointer(x11_state.display, None, (Window)dest_w, 0, 0, 0, 0, dest_x, dest_y);
}
/*==============================================================================
* Atoms/Properties
*============================================================================*/
static uint32_t x11_atom_get(const char *name) {
if (!x11_state.display || !name) return 0;
return (uint32_t)XInternAtom(x11_state.display, name, False);
}
static const char* x11_atom_get_name(uint32_t atom) {
if (!x11_state.display || atom == 0) return NULL;
return XGetAtomName(x11_state.display, (Atom)atom);
}
/*==============================================================================
* Backend Interface
*============================================================================*/
static const BackendInterface x11_backend_interface = {
.name = "x11",
.get_info = x11_get_info,
.init = x11_init,
.shutdown = x11_shutdown,
.is_initialized = x11_is_initialized,
.connect = x11_connect,
.disconnect = x11_disconnect,
.is_connected = x11_is_connected,
.get_file_descriptor = x11_get_file_descriptor,
.flush = x11_flush,
.sync = x11_sync,
.get_screen_dimensions = x11_get_screen_dimensions,
.get_screen_count = x11_get_screen_count,
.get_screen_geometry = x11_get_screen_geometry,
.window_get_geometry = x11_window_get_geometry,
.window_set_geometry = x11_window_set_geometry,
.window_move = x11_window_move,
.window_resize = x11_window_resize,
.window_move_resize = x11_window_move_resize,
.window_show = x11_window_show,
.window_hide = x11_window_hide,
.window_is_visible = x11_window_is_visible,
.window_map = x11_window_map,
.window_unmap = x11_window_unmap,
.window_is_mapped = x11_window_is_mapped,
.window_raise = x11_window_raise,
.window_lower = x11_window_lower,
.window_reparent = x11_window_reparent,
.window_focus = x11_window_focus,
.window_unfocus = x11_window_unfocus,
.window_is_focused = x11_window_is_focused,
.window_get_focused = x11_window_get_focused,
.window_set_border_width = x11_window_set_border_width,
.window_get_border_width = x11_window_get_border_width,
.window_set_title = x11_window_set_title,
.window_get_title = x11_window_get_title,
.window_supports_protocol = x11_window_supports_protocol,
.window_close = x11_window_close,
.window_kill = x11_window_kill,
.window_set_fullscreen = x11_window_set_fullscreen,
.window_set_urgent = x11_window_set_urgent,
.window_is_urgent = x11_window_is_urgent,
.poll_event = x11_poll_event,
.wait_event = x11_wait_event,
.grab_key = x11_grab_key,
.ungrab_key = x11_ungrab_key,
.grab_button = x11_grab_button,
.ungrab_button = x11_ungrab_button,
.grab_pointer = x11_grab_pointer,
.ungrab_pointer = x11_ungrab_pointer,
.query_pointer = x11_query_pointer,
.warp_pointer = x11_warp_pointer,
.atom_get = x11_atom_get,
.atom_get_name = x11_atom_get_name,
};
const BackendInterface* x11_backend_get_interface(void) {
return &x11_backend_interface;
}
Display* x11_backend_get_display(void) {
return x11_state.display;
}
Window x11_backend_get_root(void) {
return x11_state.root;
}
int x11_backend_get_screen(void) {
return x11_state.screen;
}
+366
View File
@@ -0,0 +1,366 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Channel Implementation - Thread-Safe Communication
*/
#include "threading.h"
#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/eventfd.h>
#include <unistd.h>
/* ============================================================================
* Channel Implementation
* ============================================================================ */
struct Channel {
uint32_t capacity;
uint32_t head;
uint32_t tail;
uint32_t count;
ChannelType type;
void **buffer;
pthread_mutex_t mutex;
pthread_cond_t not_full;
pthread_cond_t not_empty;
int event_fd; /* For select() integration */
_Atomic int closed;
_Atomic int waiters; /* Number of threads waiting */
};
Channel* channel_create(uint32_t capacity)
{
Channel *ch = dwn_malloc(sizeof(Channel));
if (!ch) return NULL;
ch->capacity = capacity;
ch->head = 0;
ch->tail = 0;
ch->count = 0;
ch->type = (capacity == 0) ? CHANNEL_UNBUFFERED : CHANNEL_BUFFERED;
ch->event_fd = -1;
atomic_store_explicit(&ch->closed, 0, ATOMIC_RELEASE);
atomic_store_explicit(&ch->waiters, 0, ATOMIC_RELEASE);
if (capacity > 0) {
ch->buffer = dwn_malloc(sizeof(void*) * capacity);
if (!ch->buffer) {
dwn_free(ch);
return NULL;
}
} else {
ch->buffer = NULL;
}
if (pthread_mutex_init(&ch->mutex, NULL) != 0) {
dwn_free(ch->buffer);
dwn_free(ch);
return NULL;
}
if (pthread_cond_init(&ch->not_full, NULL) != 0) {
pthread_mutex_destroy(&ch->mutex);
dwn_free(ch->buffer);
dwn_free(ch);
return NULL;
}
if (pthread_cond_init(&ch->not_empty, NULL) != 0) {
pthread_cond_destroy(&ch->not_full);
pthread_mutex_destroy(&ch->mutex);
dwn_free(ch->buffer);
dwn_free(ch);
return NULL;
}
/* Create eventfd for async notification */
ch->event_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (ch->event_fd < 0) {
pthread_cond_destroy(&ch->not_empty);
pthread_cond_destroy(&ch->not_full);
pthread_mutex_destroy(&ch->mutex);
dwn_free(ch->buffer);
dwn_free(ch);
return NULL;
}
return ch;
}
void channel_destroy(Channel *ch)
{
if (!ch) return;
channel_close(ch);
pthread_cond_destroy(&ch->not_empty);
pthread_cond_destroy(&ch->not_full);
pthread_mutex_destroy(&ch->mutex);
if (ch->event_fd >= 0) {
close(ch->event_fd);
}
dwn_free(ch->buffer);
dwn_free(ch);
}
static void channel_notify(Channel *ch)
{
/* Signal eventfd */
uint64_t one = 1;
ssize_t w __attribute__((unused)) = write(ch->event_fd, &one, sizeof(one));
}
static bool channel_is_full(Channel *ch)
{
return ch->count >= ch->capacity;
}
static bool channel_is_empty(Channel *ch)
{
return ch->count == 0;
}
static void channel_push_internal(Channel *ch, void *data)
{
if (ch->capacity > 0) {
ch->buffer[ch->head] = data;
ch->head = (ch->head + 1) % ch->capacity;
ch->count++;
}
}
static void* channel_pop_internal(Channel *ch)
{
if (ch->capacity > 0 && ch->count > 0) {
void *data = ch->buffer[ch->tail];
ch->tail = (ch->tail + 1) % ch->capacity;
ch->count--;
return data;
}
return NULL;
}
ThreadStatus channel_send(Channel *ch, void *data)
{
return channel_send_timeout(ch, data, 0);
}
ThreadStatus channel_send_timeout(Channel *ch, void *data, uint64_t timeout_ms)
{
if (!ch || !data) return THREAD_ERROR_INVALID;
pthread_mutex_lock(&ch->mutex);
if (atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_CLOSED;
}
/* Wait for space */
if (ch->capacity > 0) {
while (channel_is_full(ch) && !atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
atomic_fetch_add_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
if (timeout_ms == 0) {
pthread_cond_wait(&ch->not_full, &ch->mutex);
} else {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
int ret = pthread_cond_timedwait(&ch->not_full, &ch->mutex, &ts);
if (ret == ETIMEDOUT) {
atomic_fetch_sub_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_TIMEOUT;
}
}
atomic_fetch_sub_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
}
}
if (atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_CLOSED;
}
channel_push_internal(ch, data);
pthread_cond_signal(&ch->not_empty);
pthread_mutex_unlock(&ch->mutex);
channel_notify(ch);
return THREAD_OK;
}
ThreadStatus channel_try_send(Channel *ch, void *data)
{
if (!ch || !data) return THREAD_ERROR_INVALID;
if (pthread_mutex_trylock(&ch->mutex) != 0) {
return THREAD_ERROR_BUSY;
}
if (atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_CLOSED;
}
if (ch->capacity > 0 && channel_is_full(ch)) {
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_BUSY;
}
channel_push_internal(ch, data);
pthread_cond_signal(&ch->not_empty);
pthread_mutex_unlock(&ch->mutex);
channel_notify(ch);
return THREAD_OK;
}
ThreadStatus channel_recv(Channel *ch, void **data)
{
return channel_recv_timeout(ch, data, 0);
}
ThreadStatus channel_recv_timeout(Channel *ch, void **data, uint64_t timeout_ms)
{
if (!ch || !data) return THREAD_ERROR_INVALID;
pthread_mutex_lock(&ch->mutex);
/* Wait for data */
while (channel_is_empty(ch) && !atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE)) {
atomic_fetch_add_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
if (timeout_ms == 0) {
pthread_cond_wait(&ch->not_empty, &ch->mutex);
} else {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
int ret = pthread_cond_timedwait(&ch->not_empty, &ch->mutex, &ts);
if (ret == ETIMEDOUT) {
atomic_fetch_sub_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_TIMEOUT;
}
}
atomic_fetch_sub_explicit(&ch->waiters, 1, ATOMIC_RELAXED);
}
if (channel_is_empty(ch)) {
pthread_mutex_unlock(&ch->mutex);
return THREAD_ERROR_CLOSED;
}
*data = channel_pop_internal(ch);
pthread_cond_signal(&ch->not_full);
pthread_mutex_unlock(&ch->mutex);
return THREAD_OK;
}
ThreadStatus channel_try_recv(Channel *ch, void **data)
{
if (!ch || !data) return THREAD_ERROR_INVALID;
if (pthread_mutex_trylock(&ch->mutex) != 0) {
return THREAD_ERROR_BUSY;
}
if (channel_is_empty(ch)) {
pthread_mutex_unlock(&ch->mutex);
return atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE) ?
THREAD_ERROR_CLOSED : THREAD_ERROR_BUSY;
}
*data = channel_pop_internal(ch);
pthread_cond_signal(&ch->not_full);
pthread_mutex_unlock(&ch->mutex);
return THREAD_OK;
}
void channel_close(Channel *ch)
{
if (!ch) return;
pthread_mutex_lock(&ch->mutex);
atomic_store_explicit(&ch->closed, 1, ATOMIC_RELEASE);
pthread_cond_broadcast(&ch->not_full);
pthread_cond_broadcast(&ch->not_empty);
pthread_mutex_unlock(&ch->mutex);
channel_notify(ch);
}
bool channel_is_closed(Channel *ch)
{
if (!ch) return true;
return atomic_load_explicit(&ch->closed, ATOMIC_ACQUIRE) != 0;
}
/* ============================================================================
* Select Implementation
* ============================================================================ */
int channel_select(Channel **channels, uint32_t count, uint64_t timeout_ms,
void **out_data)
{
if (!channels || count == 0) return -1;
uint64_t start_time = 0;
if (timeout_ms > 0) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
start_time = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
for (;;) {
/* Try non-blocking receive on all channels */
for (uint32_t i = 0; i < count; i++) {
if (channel_try_recv(channels[i], out_data) == THREAD_OK) {
return (int)i;
}
}
/* Check timeout */
if (timeout_ms > 0) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
uint64_t now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
if (now - start_time >= timeout_ms) {
return -1;
}
}
/* Short sleep to avoid busy-wait */
struct timespec sleep_ts = {0, 1000000}; /* 1ms */
nanosleep(&sleep_ts, NULL);
}
}
+167 -4
View File
@@ -14,6 +14,8 @@
#include "layout.h"
#include "panel.h"
#include "api.h"
#include "rules.h"
#include "marks.h"
#include <string.h>
#include <stdlib.h>
@@ -51,6 +53,7 @@ Client *client_create(Window window)
client->snap.horizontal = SNAP_H_NONE;
client->snap.vertical = SNAP_V_NONE;
client->snap.timestamp = 0;
client->floating_before_maximize = false;
XWindowAttributes wa;
int orig_width = 640, orig_height = 480;
@@ -90,6 +93,12 @@ Client *client_create(Window window)
client_update_title(client);
client_update_class(client);
client->taskbar_color = generate_unique_color();
client->text_glow.phase = (float)(rand() % 628) / 100.0f;
client->text_glow.speed = 0.8f + (float)(rand() % 40) / 100.0f;
client->text_glow.base_color = client->taskbar_color;
client->text_glow.active = true;
return client;
}
@@ -226,14 +235,38 @@ Client *client_manage(Window window)
XGrabButton(dwn->display, Button1, AnyModifier, window, False,
ButtonPressMask, GrabModeSync, GrabModeAsync, None, None);
unsigned int lock_combos[] = { 0, Mod2Mask, LockMask, Mod2Mask | LockMask };
for (size_t gi = 0; gi < sizeof(lock_combos) / sizeof(lock_combos[0]); gi++) {
XGrabButton(dwn->display, Button3, Mod1Mask | lock_combos[gi], window, False,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync, None, None);
XGrabButton(dwn->display, Button1, Mod1Mask | lock_combos[gi], window, False,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync, None, None);
}
client_sync_log("client_manage: syncing X");
XSync(dwn->display, False);
client_sync_log("client_manage: applying rules");
bool rules_applied = rules_apply_to_client(client);
if (rules_applied) {
client_configure(client);
if (client->workspace != (unsigned int)dwn->current_workspace) {
client_hide(client);
}
}
client_sync_log("client_manage: showing");
client_show(client);
if (client->workspace == (unsigned int)dwn->current_workspace) {
client_show(client);
}
client_sync_log("client_manage: focusing");
client_focus(client, true);
if (client->workspace == (unsigned int)dwn->current_workspace) {
client_focus(client, true);
}
api_emit_window_created(client->window, client->title, client->class, client->workspace);
@@ -252,6 +285,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) {
@@ -353,9 +388,12 @@ void client_focus(Client *client, bool update_mru)
Window prev_window = None;
if (ws != NULL && ws->focused != NULL && ws->focused != client) {
prev_window = ws->focused->window;
client_start_focus_animation(ws->focused, false);
client_unfocus(ws->focused);
}
client_start_focus_animation(client, true);
client_sync_log("client_focus: XSetInputFocus");
XSetInputFocus(dwn->display, client->window, RevertToPointerRoot, CurrentTime);
@@ -716,8 +754,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) {
@@ -793,6 +833,7 @@ void client_set_fullscreen(Client *client, bool fullscreen)
XRaiseWindow(dwn->display, client->window);
XSync(dwn->display, False);
} else {
client->flags |= CLIENT_UNMANAGING;
client->flags &= ~CLIENT_FULLSCREEN;
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_FULLSCREEN, false);
api_emit_window_fullscreen(client->window, false);
@@ -815,6 +856,7 @@ void client_set_fullscreen(Client *client, bool fullscreen)
decorations_render(client, true);
client_raise(client);
XSync(dwn->display, False);
client->flags &= ~CLIENT_UNMANAGING;
}
}
@@ -876,6 +918,7 @@ void client_set_maximize(Client *client, bool maximized)
client->old_y = client->y;
client->old_width = client->width;
client->old_height = client->height;
client->floating_before_maximize = (client->flags & CLIENT_FLOATING) != 0;
}
client->flags |= CLIENT_MAXIMIZED;
@@ -904,6 +947,10 @@ void client_set_maximize(Client *client, bool maximized)
} else {
client->flags &= ~CLIENT_MAXIMIZED;
if (!client->floating_before_maximize) {
client->flags &= ~CLIENT_FLOATING;
}
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_VERT, false);
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_HORZ, false);
api_emit_window_maximized(client->window, false);
@@ -913,8 +960,33 @@ void client_set_maximize(Client *client, bool maximized)
client->width = client->old_width;
client->height = client->old_height;
if (client->width < 50) client->width = 50;
if (client->height < 50) client->height = 50;
int area_x, area_y, area_w, area_h;
layout_get_usable_area(&area_x, &area_y, &area_w, &area_h);
if (client->x + client->width > area_x + area_w) {
client->x = area_x + area_w - client->width;
}
if (client->x < area_x) {
client->x = area_x;
}
if (client->y + client->height > area_y + area_h) {
client->y = area_y + area_h - client->height;
}
if (client->y < area_y) {
client->y = area_y;
}
client_configure(client);
decorations_render(client, true);
client_raise(client);
client_focus(client, true);
if (!client->floating_before_maximize) {
workspace_arrange_current();
}
}
}
@@ -1283,3 +1355,94 @@ Client *client_get_last(void)
}
return c;
}
#define FOCUS_ANIMATION_DURATION_MS 400
void client_start_focus_animation(Client *client, bool gaining_focus)
{
if (client == NULL) {
return;
}
unsigned long full_color = client->taskbar_color;
unsigned long dimmed_color = dim_color(client->taskbar_color, 0.35f);
if (gaining_focus) {
client->title_anim.from_color = dimmed_color;
client->title_anim.to_color = full_color;
} else {
client->title_anim.from_color = full_color;
client->title_anim.to_color = dimmed_color;
}
client->title_anim.start_time = get_time_ms();
client->title_anim.progress = 0.0f;
client->title_anim.active = true;
}
unsigned long client_get_animated_title_color(Client *client)
{
if (client == NULL) {
return 0;
}
Workspace *ws = workspace_get(client->workspace);
bool is_focused = (ws != NULL && ws->focused == client);
const ColorScheme *colors = config_get_colors();
return is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
}
unsigned long client_get_glow_text_color(Client *client)
{
if (client == NULL || !client->text_glow.active) {
return 0xFFFFFF;
}
float phase = dwn->ambient_phase + client->text_glow.phase;
return glow_color(client->text_glow.base_color, phase);
}
#define TEXT_GLOW_THROTTLE_MS 100
void client_update_animations(void)
{
if (dwn == NULL) {
return;
}
static long last_glow_update = 0;
long now = get_time_ms();
bool should_update_glow = (now - last_glow_update >= TEXT_GLOW_THROTTLE_MS);
bool needs_redraw = false;
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->title_anim.active) {
long elapsed = now - c->title_anim.start_time;
c->title_anim.progress = (float)elapsed / (float)FOCUS_ANIMATION_DURATION_MS;
if (c->title_anim.progress >= 1.0f) {
c->title_anim.progress = 1.0f;
c->title_anim.active = false;
}
needs_redraw = true;
}
if (should_update_glow && c->text_glow.active &&
c->workspace == (unsigned int)dwn->current_workspace) {
c->text_glow.phase += 0.05f * c->text_glow.speed;
if (c->text_glow.phase > 6.283f) {
c->text_glow.phase -= 6.283f;
}
needs_redraw = true;
}
}
if (should_update_glow) {
last_glow_update = now;
}
if (needs_redraw) {
panel_invalidate_taskbar();
}
}
+89
View File
@@ -97,6 +97,9 @@ void config_set_defaults(Config *cfg)
cfg->demo_ai_timeout_ms = 15000;
cfg->demo_window_timeout_ms = 5000;
cfg->fade_speed = 0.5f; /* Default moderate speed */
cfg->fade_intensity = 1.0f; /* Default full intensity */
strncpy(cfg->color_panel_bg, DEFAULT_PANEL_BG, sizeof(cfg->color_panel_bg) - 1);
strncpy(cfg->color_panel_fg, DEFAULT_PANEL_FG, sizeof(cfg->color_panel_fg) - 1);
strncpy(cfg->color_workspace_active, DEFAULT_WS_ACTIVE, sizeof(cfg->color_workspace_active) - 1);
@@ -227,6 +230,14 @@ static void handle_config_entry(const char *section, const char *key,
int val = atoi(value);
cfg->demo_window_timeout_ms = (val >= 1000 && val <= 30000) ? val : 5000;
}
} else if (strcmp(section, "effects") == 0) {
if (strcmp(key, "fade_speed") == 0) {
float val = atof(value);
cfg->fade_speed = (val >= 0.1f && val <= 3.0f) ? val : 0.5f;
} else if (strcmp(key, "fade_intensity") == 0) {
float val = atof(value);
cfg->fade_intensity = (val >= 0.0f && val <= 1.0f) ? val : 1.0f;
}
} else if (strcmp(section, "colors") == 0) {
if (strcmp(key, "panel_bg") == 0) {
strncpy(cfg->color_panel_bg, value, sizeof(cfg->color_panel_bg) - 1);
@@ -350,9 +361,53 @@ bool config_load(Config *cfg, const char *path)
LOG_INFO("AI features enabled");
}
/* Validate and clamp all config values to safe ranges */
config_validate(cfg);
return true;
}
void config_validate(Config *cfg)
{
if (cfg == NULL) return;
/* Panel height: 10-100 pixels */
if (cfg->panel_height < 10) cfg->panel_height = 10;
if (cfg->panel_height > 100) cfg->panel_height = 100;
/* Border width: 0-20 pixels */
if (cfg->border_width < 0) cfg->border_width = 0;
if (cfg->border_width > 20) cfg->border_width = 20;
/* Title height: 0-50 pixels */
if (cfg->title_height < 0) cfg->title_height = 0;
if (cfg->title_height > 50) cfg->title_height = 50;
/* Default master ratio: 0.1-0.9 */
if (cfg->default_master_ratio < 0.1f) cfg->default_master_ratio = 0.1f;
if (cfg->default_master_ratio > 0.9f) cfg->default_master_ratio = 0.9f;
/* Default master count: 1-8 */
if (cfg->default_master_count < 1) cfg->default_master_count = 1;
if (cfg->default_master_count > 8) cfg->default_master_count = 8;
/* Gap: 0-50 pixels */
if (cfg->gap < 0) cfg->gap = 0;
if (cfg->gap > 50) cfg->gap = 50;
/* Fade speed: 0.1-3.0 */
if (cfg->fade_speed < 0.1f) cfg->fade_speed = 0.1f;
if (cfg->fade_speed > 3.0f) cfg->fade_speed = 3.0f;
/* Fade intensity: 0.0-1.0 */
if (cfg->fade_intensity < 0.0f) cfg->fade_intensity = 0.0f;
if (cfg->fade_intensity > 1.0f) cfg->fade_intensity = 1.0f;
/* API port: 1024-65535 (avoid well-known ports) */
if (cfg->api_port < 1024) cfg->api_port = 8777;
if (cfg->api_port > 65535) cfg->api_port = 8777;
}
bool config_reload(Config *cfg)
{
if (cfg == NULL) {
@@ -440,3 +495,37 @@ void config_init_colors(Config *cfg)
cfg->colors.notification_bg = parse_color(cfg->color_notification_bg);
cfg->colors.notification_fg = parse_color(cfg->color_notification_fg);
}
float config_get_fade_speed(void)
{
if (dwn != NULL && dwn->config != NULL) {
return dwn->config->fade_speed;
}
return 0.5f;
}
float config_get_fade_intensity(void)
{
if (dwn != NULL && dwn->config != NULL) {
return dwn->config->fade_intensity;
}
return 1.0f;
}
void config_set_fade_speed(float speed)
{
if (dwn != NULL && dwn->config != NULL) {
if (speed < 0.1f) speed = 0.1f;
if (speed > 3.0f) speed = 3.0f;
dwn->config->fade_speed = speed;
}
}
void config_set_fade_intensity(float intensity)
{
if (dwn != NULL && dwn->config != NULL) {
if (intensity < 0.0f) intensity = 0.0f;
if (intensity > 1.0f) intensity = 1.0f;
dwn->config->fade_intensity = intensity;
}
}
+909
View File
@@ -0,0 +1,909 @@
/*
* DWN - Desktop Window Manager
* Abstract Client Implementation
*/
#include "core/wm_client.h"
#include "core/wm_hashmap.h"
#include "core/wm_list.h"
#include "client.h"
#include "dwn.h"
#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/*==============================================================================
* Internal Structures
*============================================================================*/
struct AbstractClient {
/* Core identification */
WmClientId id;
WmWindowHandle window;
WmWindowHandle frame;
WmClientType type;
/* Legacy client pointer (for compatibility during migration) */
Client *legacy;
bool owns_legacy;
/* State */
WmClientState state;
WmWorkspaceId workspace;
/* Geometry */
WmRect geometry;
WmRect saved_geometry;
int border_width;
/* Properties */
char title[256];
char class_name[64];
char instance_name[64];
WmColor color;
/* List links */
AbstractClient *next;
AbstractClient *prev;
AbstractClient *mru_next;
AbstractClient *mru_prev;
};
struct WmClientManager {
AbstractClient *clients; /* Global client list */
AbstractClient *clients_tail; /* Tail of global list */
AbstractClient *mru_head; /* Most recently used */
AbstractClient *mru_tail; /* Least recently used */
WmHashMap *by_window; /* Map: window handle -> client */
WmHashMap *by_frame; /* Map: frame handle -> client */
WmHashMap *by_id; /* Map: client ID -> client */
AbstractClient *focused;
WmClientId next_id;
int count;
};
/*==============================================================================
* Global State
*============================================================================*/
static WmClientManager *g_client_manager = NULL;
/*==============================================================================
* Internal Helpers
*============================================================================*/
static WmClientId generate_client_id(void) {
if (!g_client_manager) return 0;
return g_client_manager->next_id++;
}
static void link_client(AbstractClient *client) {
if (!g_client_manager || !client) return;
/* Add to global list */
if (g_client_manager->clients_tail) {
g_client_manager->clients_tail->next = client;
client->prev = g_client_manager->clients_tail;
g_client_manager->clients_tail = client;
} else {
g_client_manager->clients = client;
g_client_manager->clients_tail = client;
}
/* Add to MRU list (at head = most recent) */
if (g_client_manager->mru_head) {
client->mru_next = g_client_manager->mru_head;
g_client_manager->mru_head->mru_prev = client;
g_client_manager->mru_head = client;
} else {
g_client_manager->mru_head = client;
g_client_manager->mru_tail = client;
}
g_client_manager->count++;
}
static void unlink_client(AbstractClient *client) {
if (!g_client_manager || !client) return;
/* Remove from global list */
if (client->prev) {
client->prev->next = client->next;
} else {
g_client_manager->clients = client->next;
}
if (client->next) {
client->next->prev = client->prev;
} else {
g_client_manager->clients_tail = client->prev;
}
/* Remove from MRU list */
if (client->mru_prev) {
client->mru_prev->mru_next = client->mru_next;
} else {
g_client_manager->mru_head = client->mru_next;
}
if (client->mru_next) {
client->mru_next->mru_prev = client->mru_prev;
} else {
g_client_manager->mru_tail = client->mru_prev;
}
g_client_manager->count--;
}
/*==============================================================================
* Client Lifecycle
*============================================================================*/
AbstractClient* wm_client_create(WmWindowHandle window, WmClientType type) {
if (!window) return NULL;
AbstractClient *client = calloc(1, sizeof(AbstractClient));
if (!client) return NULL;
client->id = generate_client_id();
client->window = window;
client->type = type;
client->state = WM_CLIENT_STATE_NORMAL | WM_CLIENT_STATE_MAPPED;
client->workspace = 0;
client->border_width = 0;
client->color = 0;
/* Register with manager */
if (g_client_manager) {
link_client(client);
wm_hashmap_insert(g_client_manager->by_window, window, client);
int id_key = (int)client->id;
wm_hashmap_insert(g_client_manager->by_id, (void*)(intptr_t)id_key, client);
}
LOG_DEBUG("Created abstract client %u for window %p", client->id, window);
return client;
}
void wm_client_destroy(AbstractClient *client) {
if (!client) return;
LOG_DEBUG("Destroying abstract client %u", client->id);
/* Remove from manager */
if (g_client_manager) {
wm_hashmap_remove(g_client_manager->by_window, client->window);
if (client->frame) {
wm_hashmap_remove(g_client_manager->by_frame, client->frame);
}
int id_key = (int)client->id;
wm_hashmap_remove(g_client_manager->by_id, (void*)(intptr_t)id_key);
unlink_client(client);
/* Clear focus if this was focused */
if (g_client_manager->focused == client) {
g_client_manager->focused = NULL;
}
}
/* Destroy legacy client if we own it */
if (client->legacy && client->owns_legacy) {
client_destroy(client->legacy);
}
free(client);
}
bool wm_client_is_valid(const AbstractClient *client) {
return client != NULL &&
!(client->state & WM_CLIENT_STATE_DESTROYING);
}
/*==============================================================================
* Native Access
*============================================================================*/
WmWindowHandle wm_client_get_window(const AbstractClient *client) {
return client ? client->window : NULL;
}
WmWindowHandle wm_client_get_frame(const AbstractClient *client) {
return client ? client->frame : NULL;
}
Client* wm_client_get_legacy(const AbstractClient *client) {
return client ? client->legacy : NULL;
}
AbstractClient* wm_client_from_legacy(Client *legacy_client) {
if (!legacy_client) return NULL;
/* Check if we already have a wrapper for this client */
AbstractClient *existing = wm_client_find_by_window((WmWindowHandle)legacy_client->window);
if (existing) return existing;
/* Create wrapper */
AbstractClient *client = calloc(1, sizeof(AbstractClient));
if (!client) return NULL;
client->id = generate_client_id();
client->window = (WmWindowHandle)legacy_client->window;
client->frame = (WmWindowHandle)legacy_client->frame;
client->legacy = legacy_client;
client->owns_legacy = false;
/* Sync state from legacy */
client->workspace = legacy_client->workspace;
client->border_width = legacy_client->border_width;
client->geometry.x = legacy_client->x;
client->geometry.y = legacy_client->y;
client->geometry.width = legacy_client->width;
client->geometry.height = legacy_client->height;
size_t title_len = strlen(legacy_client->title);
if (title_len >= sizeof(client->title)) title_len = sizeof(client->title) - 1;
memcpy(client->title, legacy_client->title, title_len);
client->title[title_len] = '\0';
size_t class_len = strlen(legacy_client->class);
if (class_len >= sizeof(client->class_name)) class_len = sizeof(client->class_name) - 1;
memcpy(client->class_name, legacy_client->class, class_len);
client->class_name[class_len] = '\0';
client->color = legacy_client->taskbar_color;
/* Convert flags */
if (legacy_client->flags & CLIENT_FLOATING) {
client->state |= WM_CLIENT_STATE_FLOATING;
}
if (legacy_client->flags & CLIENT_FULLSCREEN) {
client->state |= WM_CLIENT_STATE_FULLSCREEN;
}
if (legacy_client->flags & CLIENT_URGENT) {
client->state |= WM_CLIENT_STATE_URGENT;
}
if (legacy_client->flags & CLIENT_MINIMIZED) {
client->state |= WM_CLIENT_STATE_MINIMIZED;
}
if (legacy_client->flags & CLIENT_STICKY) {
client->state |= WM_CLIENT_STATE_STICKY;
}
if (legacy_client->flags & CLIENT_MAXIMIZED) {
client->state |= WM_CLIENT_STATE_MAXIMIZED;
}
/* Register */
if (g_client_manager) {
link_client(client);
wm_hashmap_insert(g_client_manager->by_window, client->window, client);
if (client->frame) {
wm_hashmap_insert(g_client_manager->by_frame, client->frame, client);
}
int id_key = (int)client->id;
wm_hashmap_insert(g_client_manager->by_id, (void*)(intptr_t)id_key, client);
}
return client;
}
/*==============================================================================
* Client Identification
*============================================================================*/
WmClientId wm_client_get_id(const AbstractClient *client) {
return client ? client->id : 0;
}
WmClientType wm_client_get_type(const AbstractClient *client) {
return client ? client->type : WM_CLIENT_TYPE_UNKNOWN;
}
void wm_client_set_type(AbstractClient *client, WmClientType type) {
if (client) client->type = type;
}
/*==============================================================================
* Client State
*============================================================================*/
WmClientState wm_client_get_state(const AbstractClient *client) {
return client ? client->state : 0;
}
void wm_client_set_state(AbstractClient *client, WmClientState state) {
if (client) client->state = state;
}
void wm_client_add_state(AbstractClient *client, WmClientState state) {
if (client) client->state |= state;
}
void wm_client_remove_state(AbstractClient *client, WmClientState state) {
if (client) client->state &= ~state;
}
void wm_client_toggle_state(AbstractClient *client, WmClientState state) {
if (client) client->state ^= state;
}
bool wm_client_has_state(const AbstractClient *client, WmClientState state) {
return client && (client->state & state) != 0;
}
/*==============================================================================
* Client Geometry
*============================================================================*/
WmRect wm_client_get_geometry(const AbstractClient *client) {
if (!client) return (WmRect){0, 0, 0, 0};
/* Sync from legacy if available */
if (client->legacy) {
return (WmRect){
client->legacy->x,
client->legacy->y,
client->legacy->width,
client->legacy->height
};
}
return client->geometry;
}
void wm_client_set_geometry(AbstractClient *client, const WmRect *geometry) {
if (!client || !geometry) return;
client->geometry = *geometry;
/* Sync to legacy if available */
if (client->legacy) {
client->legacy->x = geometry->x;
client->legacy->y = geometry->y;
client->legacy->width = geometry->width;
client->legacy->height = geometry->height;
}
}
WmPoint wm_client_get_position(const AbstractClient *client) {
WmRect geom = wm_client_get_geometry(client);
return (WmPoint){geom.x, geom.y};
}
void wm_client_set_position(AbstractClient *client, int x, int y) {
if (!client) return;
client->geometry.x = x;
client->geometry.y = y;
if (client->legacy) {
client->legacy->x = x;
client->legacy->y = y;
}
}
WmSize wm_client_get_size(const AbstractClient *client) {
WmRect geom = wm_client_get_geometry(client);
return (WmSize){geom.width, geom.height};
}
void wm_client_set_size(AbstractClient *client, int width, int height) {
if (!client) return;
client->geometry.width = width;
client->geometry.height = height;
if (client->legacy) {
client->legacy->width = width;
client->legacy->height = height;
}
}
int wm_client_get_border_width(const AbstractClient *client) {
if (!client) return 0;
if (client->legacy) return client->legacy->border_width;
return client->border_width;
}
void wm_client_set_border_width(AbstractClient *client, int width) {
if (!client) return;
client->border_width = width;
if (client->legacy) {
client->legacy->border_width = width;
}
}
void wm_client_save_geometry(AbstractClient *client) {
if (!client) return;
client->saved_geometry = wm_client_get_geometry(client);
if (client->legacy) {
client->legacy->old_x = client->saved_geometry.x;
client->legacy->old_y = client->saved_geometry.y;
client->legacy->old_width = client->saved_geometry.width;
client->legacy->old_height = client->saved_geometry.height;
}
}
void wm_client_restore_geometry(AbstractClient *client) {
if (!client) return;
wm_client_set_geometry(client, &client->saved_geometry);
}
/*==============================================================================
* Client Workspace
*============================================================================*/
WmWorkspaceId wm_client_get_workspace(const AbstractClient *client) {
return client ? client->workspace : 0;
}
void wm_client_set_workspace(AbstractClient *client, WmWorkspaceId workspace) {
if (!client) return;
client->workspace = workspace;
if (client->legacy) {
client->legacy->workspace = workspace;
}
}
bool wm_client_is_on_workspace(const AbstractClient *client, WmWorkspaceId workspace) {
if (!client) return false;
if (client->state & WM_CLIENT_STATE_STICKY) return true;
return client->workspace == workspace;
}
bool wm_client_is_visible_on_current(const AbstractClient *client) {
if (!client || !dwn) return false;
return wm_client_is_on_workspace(client, dwn->current_workspace);
}
/*==============================================================================
* Client Properties
*============================================================================*/
const char* wm_client_get_title(const AbstractClient *client) {
if (!client) return "";
if (client->legacy) return client->legacy->title;
return client->title;
}
void wm_client_set_title(AbstractClient *client, const char *title) {
if (!client || !title) return;
strncpy(client->title, title, sizeof(client->title) - 1);
client->title[sizeof(client->title) - 1] = '\0';
if (client->legacy) {
strncpy(client->legacy->title, title, sizeof(client->legacy->title) - 1);
client->legacy->title[sizeof(client->legacy->title) - 1] = '\0';
}
}
const char* wm_client_get_class(const AbstractClient *client) {
if (!client) return "";
if (client->legacy) return client->legacy->class;
return client->class_name;
}
void wm_client_set_class(AbstractClient *client, const char *class_name) {
if (!client || !class_name) return;
strncpy(client->class_name, class_name, sizeof(client->class_name) - 1);
client->class_name[sizeof(client->class_name) - 1] = '\0';
if (client->legacy) {
strncpy(client->legacy->class, class_name, sizeof(client->legacy->class) - 1);
client->legacy->class[sizeof(client->legacy->class) - 1] = '\0';
}
}
const char* wm_client_get_instance(const AbstractClient *client) {
if (!client) return "";
return client->instance_name;
}
void wm_client_set_instance(AbstractClient *client, const char *instance) {
if (!client || !instance) return;
strncpy(client->instance_name, instance, sizeof(client->instance_name) - 1);
client->instance_name[sizeof(client->instance_name) - 1] = '\0';
}
WmColor wm_client_get_color(const AbstractClient *client) {
if (!client) return 0;
if (client->legacy) return client->legacy->taskbar_color;
return client->color;
}
void wm_client_set_color(AbstractClient *client, WmColor color) {
if (!client) return;
client->color = color;
if (client->legacy) {
client->legacy->taskbar_color = color;
}
}
/*==============================================================================
* Client Actions (delegate to legacy for now)
*============================================================================*/
void wm_client_focus(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_focus(client->legacy, true);
}
client->state |= WM_CLIENT_STATE_FOCUS;
if (g_client_manager) {
wm_client_manager_touch(g_client_manager, client);
g_client_manager->focused = client;
}
}
void wm_client_unfocus(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_unfocus(client->legacy);
}
client->state &= ~WM_CLIENT_STATE_FOCUS;
if (g_client_manager && g_client_manager->focused == client) {
g_client_manager->focused = NULL;
}
}
void wm_client_raise(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_raise(client->legacy);
}
}
void wm_client_lower(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_lower(client->legacy);
}
}
void wm_client_show(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_show(client->legacy);
}
client->state |= WM_CLIENT_STATE_MAPPED;
}
void wm_client_hide(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_hide(client->legacy);
}
client->state &= ~WM_CLIENT_STATE_MAPPED;
}
void wm_client_minimize(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_minimize(client->legacy);
}
client->state |= WM_CLIENT_STATE_MINIMIZED;
}
void wm_client_restore(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_restore(client->legacy);
}
client->state &= ~WM_CLIENT_STATE_MINIMIZED;
}
void wm_client_close(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_close(client->legacy);
}
}
void wm_client_kill(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_kill(client->legacy);
}
}
/*==============================================================================
* Client Management
*============================================================================*/
void wm_client_reparent(AbstractClient *client, WmWindowHandle frame) {
if (!client) return;
client->frame = frame;
/* Update frame lookup */
if (g_client_manager) {
if (client->frame) {
wm_hashmap_remove(g_client_manager->by_frame, client->frame);
}
wm_hashmap_insert(g_client_manager->by_frame, frame, client);
}
if (client->legacy) {
/* Legacy doesn't have a direct reparent function for frames */
}
}
void wm_client_unreparent(AbstractClient *client) {
if (!client) return;
if (g_client_manager && client->frame) {
wm_hashmap_remove(g_client_manager->by_frame, client->frame);
}
client->frame = NULL;
}
void wm_client_configure(AbstractClient *client) {
if (!client) return;
if (client->legacy) {
client_configure(client->legacy);
}
}
void wm_client_apply_size_hints(AbstractClient *client, int *width, int *height) {
if (!client || !width || !height) return;
if (client->legacy) {
client_apply_size_hints(client->legacy, width, height);
}
}
void wm_client_move_resize(AbstractClient *client, int x, int y, int width, int height) {
if (!client) return;
client->geometry.x = x;
client->geometry.y = y;
client->geometry.width = width;
client->geometry.height = height;
if (client->legacy) {
client_move_resize(client->legacy, x, y, width, height);
}
}
/*==============================================================================
* Client List Management
*============================================================================*/
AbstractClient* wm_client_get_next(const AbstractClient *client) {
return client ? client->next : NULL;
}
AbstractClient* wm_client_get_prev(const AbstractClient *client) {
return client ? client->prev : NULL;
}
AbstractClient* wm_client_get_first(void) {
return g_client_manager ? g_client_manager->clients : NULL;
}
AbstractClient* wm_client_get_last(void) {
return g_client_manager ? g_client_manager->clients_tail : NULL;
}
AbstractClient* wm_client_get_next_mru(const AbstractClient *client) {
return client ? client->mru_next : NULL;
}
AbstractClient* wm_client_get_prev_mru(const AbstractClient *client) {
return client ? client->mru_prev : NULL;
}
/*==============================================================================
* Client Lookup
*============================================================================*/
AbstractClient* wm_client_find_by_window(WmWindowHandle window) {
if (!g_client_manager || !window) return NULL;
return wm_hashmap_get(g_client_manager->by_window, window);
}
AbstractClient* wm_client_find_by_frame(WmWindowHandle frame) {
if (!g_client_manager || !frame) return NULL;
return wm_hashmap_get(g_client_manager->by_frame, frame);
}
AbstractClient* wm_client_find_by_id(WmClientId id) {
if (!g_client_manager) return NULL;
return wm_hashmap_get(g_client_manager->by_id, (void*)(intptr_t)id);
}
/*==============================================================================
* Client Iteration
*============================================================================*/
void wm_client_foreach(WmClientForeachFunc func, void *user_data) {
if (!func) return;
AbstractClient *client = wm_client_get_first();
while (client) {
AbstractClient *next = client->next;
if (!func(client, user_data)) break;
client = next;
}
}
typedef struct {
WmWorkspaceId workspace;
WmClientForeachFunc func;
void *user_data;
} ForeachWorkspaceData;
static bool foreach_workspace_wrapper(AbstractClient *client, void *data) {
ForeachWorkspaceData *fwd = data;
if (wm_client_is_on_workspace(client, fwd->workspace)) {
return fwd->func(client, fwd->user_data);
}
return true;
}
void wm_client_foreach_on_workspace(WmWorkspaceId workspace,
WmClientForeachFunc func, void *user_data) {
ForeachWorkspaceData data = { workspace, func, user_data };
wm_client_foreach(foreach_workspace_wrapper, &data);
}
int wm_client_count(void) {
return g_client_manager ? g_client_manager->count : 0;
}
typedef struct {
int count;
WmWorkspaceId workspace;
} CountWorkspaceData;
static bool count_workspace(AbstractClient *client, void *data) {
CountWorkspaceData *cwd = data;
if (wm_client_is_on_workspace(client, cwd->workspace)) {
cwd->count++;
}
return true;
}
int wm_client_count_on_workspace(WmWorkspaceId workspace) {
CountWorkspaceData data = { 0, workspace };
wm_client_foreach(count_workspace, &data);
return data.count;
}
/*==============================================================================
* Client Manager
*============================================================================*/
WmClientManager* wm_client_manager_get(void) {
return g_client_manager;
}
bool wm_client_manager_init(void) {
if (g_client_manager) return true;
g_client_manager = calloc(1, sizeof(WmClientManager));
if (!g_client_manager) return false;
g_client_manager->by_window = wm_hashmap_new();
g_client_manager->by_frame = wm_hashmap_new();
g_client_manager->by_id = wm_hashmap_new();
if (!g_client_manager->by_window ||
!g_client_manager->by_frame ||
!g_client_manager->by_id) {
wm_client_manager_shutdown();
return false;
}
g_client_manager->next_id = 1;
LOG_INFO("Client manager initialized");
return true;
}
void wm_client_manager_shutdown(void) {
if (!g_client_manager) return;
/* Destroy all abstract clients (but not legacy ones - we don't own them) */
AbstractClient *client = g_client_manager->clients;
while (client) {
AbstractClient *next = client->next;
if (client->owns_legacy) {
client_destroy(client->legacy);
}
free(client);
client = next;
}
wm_hashmap_destroy(g_client_manager->by_window);
wm_hashmap_destroy(g_client_manager->by_frame);
wm_hashmap_destroy(g_client_manager->by_id);
free(g_client_manager);
g_client_manager = NULL;
LOG_INFO("Client manager shutdown");
}
void wm_client_manager_add(WmClientManager *mgr, AbstractClient *client) {
if (!mgr || !client) return;
/* Client is already linked when created */
}
void wm_client_manager_remove(WmClientManager *mgr, AbstractClient *client) {
if (!mgr || !client) return;
/* Client is already unlinked when destroyed */
}
void wm_client_manager_touch(WmClientManager *mgr, AbstractClient *client) {
if (!mgr || !client) return;
/* Already at head - nothing to do */
if (mgr->mru_head == client) return;
/* Remove from current position */
if (client->mru_prev) {
client->mru_prev->mru_next = client->mru_next;
}
if (client->mru_next) {
client->mru_next->mru_prev = client->mru_prev;
}
if (mgr->mru_tail == client) {
mgr->mru_tail = client->mru_prev;
}
/* Insert at head */
client->mru_prev = NULL;
client->mru_next = mgr->mru_head;
if (mgr->mru_head) {
mgr->mru_head->mru_prev = client;
}
mgr->mru_head = client;
}
AbstractClient* wm_client_manager_get_focused(const WmClientManager *mgr) {
return mgr ? mgr->focused : NULL;
}
void wm_client_manager_set_focused(WmClientManager *mgr, AbstractClient *client) {
if (!mgr) return;
mgr->focused = client;
}
+416
View File
@@ -0,0 +1,416 @@
/*
* DWN - Desktop Window Manager
* Abstract Hash Map Implementation
*/
#include "core/wm_hashmap.h"
#include "core/wm_string.h"
#include <stdlib.h>
#include <string.h>
/*==============================================================================
* Constants
*============================================================================*/
#define WM_HASHMAP_INITIAL_CAPACITY 16
#define WM_HASHMAP_LOAD_FACTOR 0.75
/*==============================================================================
* Entry Structure
*============================================================================*/
typedef struct WmHashMapEntry {
uint32_t hash;
void *key;
void *value;
struct WmHashMapEntry *next;
} WmHashMapEntry;
/*==============================================================================
* Hash Map Structure
*============================================================================*/
struct WmHashMap {
WmHashMapEntry **buckets;
size_t bucket_count;
size_t size;
WmHashFunc hash_func;
WmKeyEqualFunc key_equal;
WmFreeFunc key_free;
WmFreeFunc value_free;
};
/*==============================================================================
* Internal Helpers
*============================================================================*/
static void hashmap_clone_insert(void *key, void *value, void *user_data) {
wm_hashmap_insert((WmHashMap *)user_data, key, value);
}
/*==============================================================================
* Default Hash Functions
*============================================================================*/
static uint32_t wm_hash_pointer(const void *key) {
/* FNV-1a hash for pointer */
uintptr_t ptr = (uintptr_t)key;
uint32_t hash = 2166136261u;
for (size_t i = 0; i < sizeof(ptr); i++) {
hash ^= (ptr >> (i * 8)) & 0xFF;
hash *= 16777619u;
}
return hash;
}
static uint32_t wm_hash_string(const void *key) {
const char *str = key;
uint32_t hash = 2166136261u;
while (*str) {
hash ^= (uint8_t)*str++;
hash *= 16777619u;
}
return hash;
}
static bool wm_equal_pointer(const void *a, const void *b) {
return a == b;
}
static bool wm_equal_string(const void *a, const void *b) {
return strcmp((const char*)a, (const char*)b) == 0;
}
/*==============================================================================
* Internal Helpers
*============================================================================*/
static bool wm_hashmap_resize(WmHashMap *map) {
size_t old_count = map->bucket_count;
WmHashMapEntry **old_buckets = map->buckets;
size_t new_count = old_count * 2;
WmHashMapEntry **new_buckets = calloc(new_count, sizeof(WmHashMapEntry*));
if (!new_buckets) return false;
map->buckets = new_buckets;
map->bucket_count = new_count;
/* Rehash all entries */
for (size_t i = 0; i < old_count; i++) {
WmHashMapEntry *entry = old_buckets[i];
while (entry) {
WmHashMapEntry *next = entry->next;
size_t new_index = entry->hash % new_count;
entry->next = new_buckets[new_index];
new_buckets[new_index] = entry;
entry = next;
}
}
free(old_buckets);
return true;
}
/*==============================================================================
* Lifecycle
*============================================================================*/
WmHashMap* wm_hashmap_new(void) {
return wm_hashmap_new_full(NULL, NULL, NULL, NULL);
}
WmHashMap* wm_hashmap_new_full(WmHashFunc hash_func, WmKeyEqualFunc key_equal,
WmFreeFunc key_free, WmFreeFunc value_free) {
WmHashMap *map = calloc(1, sizeof(WmHashMap));
if (!map) return NULL;
map->bucket_count = WM_HASHMAP_INITIAL_CAPACITY;
map->buckets = calloc(map->bucket_count, sizeof(WmHashMapEntry*));
if (!map->buckets) {
free(map);
return NULL;
}
map->hash_func = hash_func ? hash_func : wm_hash_pointer;
map->key_equal = key_equal ? key_equal : wm_equal_pointer;
map->key_free = key_free;
map->value_free = value_free;
return map;
}
WmHashMap* wm_hashmap_new_string_key(void) {
return wm_hashmap_new_full(wm_hash_string, wm_equal_string, free, NULL);
}
void wm_hashmap_destroy(WmHashMap *map) {
if (!map) return;
wm_hashmap_clear(map);
free(map->buckets);
free(map);
}
WmHashMap* wm_hashmap_clone(const WmHashMap *map) {
if (!map) return NULL;
WmHashMap *clone = wm_hashmap_new_full(map->hash_func, map->key_equal,
NULL, NULL);
if (!clone) return NULL;
/* Note: This creates a shallow copy */
wm_hashmap_foreach(map, hashmap_clone_insert, clone);
clone->key_free = map->key_free;
clone->value_free = map->value_free;
return clone;
}
/*==============================================================================
* Capacity
*============================================================================*/
size_t wm_hashmap_size(const WmHashMap *map) {
return map ? map->size : 0;
}
bool wm_hashmap_is_empty(const WmHashMap *map) {
return !map || map->size == 0;
}
void wm_hashmap_clear(WmHashMap *map) {
if (!map) return;
for (size_t i = 0; i < map->bucket_count; i++) {
WmHashMapEntry *entry = map->buckets[i];
while (entry) {
WmHashMapEntry *next = entry->next;
if (map->key_free && entry->key) map->key_free(entry->key);
if (map->value_free && entry->value) map->value_free(entry->value);
free(entry);
entry = next;
}
map->buckets[i] = NULL;
}
map->size = 0;
}
/*==============================================================================
* Element Access
*============================================================================*/
void* wm_hashmap_get(const WmHashMap *map, const void *key) {
if (!map || !key) return NULL;
uint32_t hash = map->hash_func(key);
size_t index = hash % map->bucket_count;
WmHashMapEntry *entry = map->buckets[index];
while (entry) {
if (entry->hash == hash && map->key_equal(entry->key, key)) {
return entry->value;
}
entry = entry->next;
}
return NULL;
}
void* wm_hashmap_get_or_default(const WmHashMap *map, const void *key, void *default_val) {
void *val = wm_hashmap_get(map, key);
return val ? val : default_val;
}
bool wm_hashmap_contains(const WmHashMap *map, const void *key) {
return wm_hashmap_get(map, key) != NULL;
}
/*==============================================================================
* Modifiers
*============================================================================*/
bool wm_hashmap_insert(WmHashMap *map, void *key, void *value) {
if (!map || !key) return false;
/* Check if resize needed */
if ((double)map->size / map->bucket_count > WM_HASHMAP_LOAD_FACTOR) {
if (!wm_hashmap_resize(map)) return false;
}
uint32_t hash = map->hash_func(key);
size_t index = hash % map->bucket_count;
/* Check if key exists */
WmHashMapEntry *entry = map->buckets[index];
while (entry) {
if (entry->hash == hash && map->key_equal(entry->key, key)) {
/* Update existing entry */
if (map->value_free) map->value_free(entry->value);
entry->value = value;
return true;
}
entry = entry->next;
}
/* Insert new entry */
entry = malloc(sizeof(WmHashMapEntry));
if (!entry) return false;
entry->hash = hash;
entry->key = key;
entry->value = value;
entry->next = map->buckets[index];
map->buckets[index] = entry;
map->size++;
return true;
}
bool wm_hashmap_insert_no_replace(WmHashMap *map, void *key, void *value) {
if (!map || !key) return false;
if (wm_hashmap_contains(map, key)) return false;
return wm_hashmap_insert(map, key, value);
}
void* wm_hashmap_set(WmHashMap *map, void *key, void *value) {
if (!map || !key) return NULL;
void *old_value = wm_hashmap_get(map, key);
wm_hashmap_insert(map, key, value);
return old_value;
}
void* wm_hashmap_remove(WmHashMap *map, const void *key) {
if (!map || !key) return NULL;
uint32_t hash = map->hash_func(key);
size_t index = hash % map->bucket_count;
WmHashMapEntry *entry = map->buckets[index];
WmHashMapEntry *prev = NULL;
while (entry) {
if (entry->hash == hash && map->key_equal(entry->key, key)) {
void *value = entry->value;
if (prev) {
prev->next = entry->next;
} else {
map->buckets[index] = entry->next;
}
if (map->key_free) map->key_free(entry->key);
free(entry);
map->size--;
return value;
}
prev = entry;
entry = entry->next;
}
return NULL;
}
bool wm_hashmap_steal(WmHashMap *map, const void *key) {
return wm_hashmap_remove(map, key) != NULL;
}
/*==============================================================================
* Iteration
*============================================================================*/
typedef struct WmHashMapIterInternal {
WmHashMap *map;
size_t bucket;
WmHashMapEntry *entry;
WmHashMapEntry *next_entry;
} WmHashMapIterInternal;
void wm_hashmap_iter_init(WmHashMapIter *iter, WmHashMap *map) {
if (!iter) return;
iter->map = map;
iter->bucket = 0;
iter->entry = NULL;
iter->next_entry = NULL;
if (!map) return;
/* Find first bucket with entry */
for (size_t i = 0; i < map->bucket_count; i++) {
if (map->buckets[i]) {
iter->bucket = i;
iter->entry = map->buckets[i];
iter->next_entry = ((WmHashMapEntry*)iter->entry)->next;
break;
}
}
}
bool wm_hashmap_iter_next(WmHashMapIter *iter, void **key, void **value) {
if (!iter || !iter->entry) return false;
WmHashMapEntry *entry = (WmHashMapEntry*)iter->entry;
*key = entry->key;
*value = entry->value;
/* Move to next entry */
entry = entry->next;
if (!entry) {
/* Find next bucket with entries */
WmHashMap *map = (WmHashMap*)iter->map;
for (size_t i = iter->bucket + 1; i < map->bucket_count; i++) {
if (map->buckets[i]) {
iter->bucket = i;
entry = map->buckets[i];
break;
}
}
}
iter->entry = entry;
return true;
}
void wm_hashmap_foreach(const WmHashMap *map, WmHashForeachFunc func, void *user_data) {
if (!map || !func) return;
for (size_t i = 0; i < map->bucket_count; i++) {
WmHashMapEntry *entry = map->buckets[i];
while (entry) {
func(entry->key, entry->value, user_data);
entry = entry->next;
}
}
}
/*==============================================================================
* String Helpers
*============================================================================*/
bool wm_hashmap_insert_string(WmHashMap *map, const char *key, void *value) {
if (!map || !key) return false;
char *key_copy = strdup(key);
if (!key_copy) return false;
if (!wm_hashmap_insert(map, key_copy, value)) {
free(key_copy);
return false;
}
return true;
}
void* wm_hashmap_get_string(const WmHashMap *map, const char *key) {
return wm_hashmap_get(map, key);
}
bool wm_hashmap_contains_string(const WmHashMap *map, const char *key) {
return wm_hashmap_contains(map, key);
}
void* wm_hashmap_remove_string(WmHashMap *map, const char *key) {
return wm_hashmap_remove(map, key);
}
+432
View File
@@ -0,0 +1,432 @@
/*
* DWN - Desktop Window Manager
* Abstract List Implementation (Dynamic Array)
*/
#include "core/wm_list.h"
#include "core/wm_types.h"
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
/*==============================================================================
* List Structure
*============================================================================*/
struct WmList {
void **data;
size_t size;
size_t capacity;
WmFreeFunc free_func;
};
/*==============================================================================
* Lifecycle
*============================================================================*/
WmList* wm_list_new(void) {
WmList *list = calloc(1, sizeof(WmList));
if (!list) return NULL;
list->capacity = 8;
list->data = malloc(list->capacity * sizeof(void*));
if (!list->data) {
free(list);
return NULL;
}
return list;
}
WmList* wm_list_new_sized(size_t initial_capacity) {
WmList *list = calloc(1, sizeof(WmList));
if (!list) return NULL;
list->capacity = initial_capacity > 0 ? initial_capacity : 8;
list->data = malloc(list->capacity * sizeof(void*));
if (!list->data) {
free(list);
return NULL;
}
return list;
}
void wm_list_destroy(WmList *list) {
if (!list) return;
if (list->free_func) {
for (size_t i = 0; i < list->size; i++) {
list->free_func(list->data[i]);
}
}
free(list->data);
free(list);
}
void wm_list_destroy_no_free(WmList *list) {
if (!list) return;
free(list->data);
free(list);
}
WmList* wm_list_clone(const WmList *list) {
if (!list) return NULL;
WmList *clone = wm_list_new_sized(list->capacity);
if (!clone) return NULL;
memcpy(clone->data, list->data, list->size * sizeof(void*));
clone->size = list->size;
clone->free_func = list->free_func;
return clone;
}
void wm_list_set_free_func(WmList *list, WmFreeFunc func) {
if (list) list->free_func = func;
}
/*==============================================================================
* Capacity
*============================================================================*/
static bool wm_list_grow(WmList *list, size_t min_capacity) {
if (list->capacity >= min_capacity) return true;
size_t new_capacity = list->capacity * 2;
if (new_capacity < min_capacity) new_capacity = min_capacity;
void **new_data = realloc(list->data, new_capacity * sizeof(void*));
if (!new_data) return false;
list->data = new_data;
list->capacity = new_capacity;
return true;
}
size_t wm_list_size(const WmList *list) {
return list ? list->size : 0;
}
size_t wm_list_capacity(const WmList *list) {
return list ? list->capacity : 0;
}
bool wm_list_is_empty(const WmList *list) {
return !list || list->size == 0;
}
void wm_list_reserve(WmList *list, size_t capacity) {
if (!list) return;
wm_list_grow(list, capacity);
}
void wm_list_compact(WmList *list) {
if (!list || list->capacity <= list->size) return;
size_t new_capacity = list->size > 0 ? list->size : 1;
void **new_data = realloc(list->data, new_capacity * sizeof(void*));
if (new_data) {
list->data = new_data;
list->capacity = new_capacity;
}
}
void wm_list_clear(WmList *list) {
if (!list) return;
if (list->free_func) {
for (size_t i = 0; i < list->size; i++) {
list->free_func(list->data[i]);
}
}
list->size = 0;
}
/*==============================================================================
* Modifiers
*============================================================================*/
void wm_list_append(WmList *list, void *item) {
if (!list) return;
if (!wm_list_grow(list, list->size + 1)) return;
list->data[list->size++] = item;
}
void wm_list_prepend(WmList *list, void *item) {
wm_list_insert(list, 0, item);
}
void wm_list_insert(WmList *list, size_t index, void *item) {
if (!list || index > list->size) return;
if (!wm_list_grow(list, list->size + 1)) return;
memmove(&list->data[index + 1], &list->data[index],
(list->size - index) * sizeof(void*));
list->data[index] = item;
list->size++;
}
void wm_list_insert_sorted(WmList *list, void *item, WmCompareFunc compare) {
if (!list) return;
size_t i = 0;
while (i < list->size && compare(list->data[i], item) < 0) {
i++;
}
wm_list_insert(list, i, item);
}
void wm_list_remove(WmList *list, void *item) {
if (!list) return;
for (size_t i = 0; i < list->size; i++) {
if (list->data[i] == item) {
wm_list_remove_at(list, i);
return;
}
}
}
void wm_list_remove_at(WmList *list, size_t index) {
if (!list || index >= list->size) return;
if (list->free_func) {
list->free_func(list->data[index]);
}
memmove(&list->data[index], &list->data[index + 1],
(list->size - index - 1) * sizeof(void*));
list->size--;
}
void* wm_list_take_at(WmList *list, size_t index) {
if (!list || index >= list->size) return NULL;
void *item = list->data[index];
memmove(&list->data[index], &list->data[index + 1],
(list->size - index - 1) * sizeof(void*));
list->size--;
return item;
}
bool wm_list_remove_one(WmList *list, void *item) {
if (!list) return false;
for (size_t i = 0; i < list->size; i++) {
if (list->data[i] == item) {
wm_list_remove_at(list, i);
return true;
}
}
return false;
}
bool wm_list_remove_all(WmList *list, void *item) {
if (!list) return false;
bool removed = false;
for (size_t i = list->size; i-- > 0; ) {
if (list->data[i] == item) {
wm_list_remove_at(list, i);
removed = true;
}
}
return removed;
}
void* wm_list_pop_back(WmList *list) {
if (!list || list->size == 0) return NULL;
return list->data[--list->size];
}
void* wm_list_pop_front(WmList *list) {
return wm_list_take_at(list, 0);
}
/*==============================================================================
* Accessors
*============================================================================*/
void* wm_list_get(const WmList *list, size_t index) {
if (!list || index >= list->size) return NULL;
return list->data[index];
}
void* wm_list_first(const WmList *list) {
if (!list || list->size == 0) return NULL;
return list->data[0];
}
void* wm_list_last(const WmList *list) {
if (!list || list->size == 0) return NULL;
return list->data[list->size - 1];
}
void* wm_list_front(const WmList *list) {
return wm_list_first(list);
}
void* wm_list_back(const WmList *list) {
return wm_list_last(list);
}
void wm_list_set(WmList *list, size_t index, void *item) {
if (!list || index >= list->size) return;
if (list->free_func) {
list->free_func(list->data[index]);
}
list->data[index] = item;
}
/*==============================================================================
* Iteration
*============================================================================*/
void wm_list_foreach(const WmList *list, WmForeachFunc func, void *user_data) {
if (!list || !func) return;
for (size_t i = 0; i < list->size; i++) {
if (!func(list->data[i], i, user_data)) break;
}
}
void wm_list_foreach_reverse(const WmList *list, WmForeachFunc func, void *user_data) {
if (!list || !func) return;
for (size_t i = list->size; i-- > 0; ) {
if (!func(list->data[i], i, user_data)) break;
}
}
/*==============================================================================
* Search
*============================================================================*/
ssize_t wm_list_index_of(const WmList *list, void *item) {
if (!list) return -1;
for (size_t i = 0; i < list->size; i++) {
if (list->data[i] == item) return (ssize_t)i;
}
return -1;
}
bool wm_list_contains(const WmList *list, void *item) {
return wm_list_index_of(list, item) >= 0;
}
void* wm_list_find(const WmList *list, WmCompareFunc compare, const void *key) {
if (!list || !compare) return NULL;
for (size_t i = 0; i < list->size; i++) {
if (compare(list->data[i], key) == 0) return list->data[i];
}
return NULL;
}
ssize_t wm_list_find_index(const WmList *list, WmCompareFunc compare, const void *key) {
if (!list || !compare) return -1;
for (size_t i = 0; i < list->size; i++) {
if (compare(list->data[i], key) == 0) return (ssize_t)i;
}
return -1;
}
/*==============================================================================
* Sorting
*============================================================================*/
static void wm_list_qsort_helper(void **data, size_t left, size_t right,
WmCompareFunc compare) {
if (left >= right) return;
void *pivot = data[(left + right) / 2];
size_t i = left, j = right;
while (i <= j) {
while (compare(data[i], pivot) < 0) i++;
while (compare(data[j], pivot) > 0) j--;
if (i <= j) {
void *tmp = data[i];
data[i] = data[j];
data[j] = tmp;
i++;
if (j > 0) j--;
}
}
if (j > left) wm_list_qsort_helper(data, left, j, compare);
if (i < right) wm_list_qsort_helper(data, i, right, compare);
}
void wm_list_sort(WmList *list, WmCompareFunc compare) {
if (!list || list->size < 2) return;
wm_list_qsort_helper(list->data, 0, list->size - 1, compare);
}
/*==============================================================================
* Data Operations
*============================================================================*/
void** wm_list_data(WmList *list) {
return list ? list->data : NULL;
}
void* wm_list_steal(WmList *list, size_t index) {
return wm_list_take_at(list, index);
}
WmList* wm_list_slice(const WmList *list, size_t start, size_t end) {
if (!list || start >= list->size || start >= end) return wm_list_new();
if (end > list->size) end = list->size;
size_t count = end - start;
WmList *slice = wm_list_new_sized(count);
if (!slice) return NULL;
memcpy(slice->data, &list->data[start], count * sizeof(void*));
slice->size = count;
return slice;
}
void wm_list_move(WmList *list, size_t from, size_t to) {
if (!list || from >= list->size || to >= list->size || from == to) return;
void *item = list->data[from];
if (from < to) {
memmove(&list->data[from], &list->data[from + 1],
(to - from) * sizeof(void*));
} else {
memmove(&list->data[to + 1], &list->data[to],
(from - to) * sizeof(void*));
}
list->data[to] = item;
}
void wm_list_swap(WmList *list, size_t i, size_t j) {
if (!list || i >= list->size || j >= list->size) return;
void *tmp = list->data[i];
list->data[i] = list->data[j];
list->data[j] = tmp;
}
/*==============================================================================
* Comparison
*============================================================================*/
bool wm_list_equals(const WmList *list1, const WmList *list2) {
if (list1 == list2) return true;
if (!list1 || !list2) return false;
if (list1->size != list2->size) return false;
for (size_t i = 0; i < list1->size; i++) {
if (list1->data[i] != list2->data[i]) return false;
}
return true;
}
+650
View File
@@ -0,0 +1,650 @@
/*
* DWN - Desktop Window Manager
* Abstract String Implementation
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <assert.h>
#include "core/wm_string.h"
#include "core/wm_types.h"
/*==============================================================================
* String Structure
*============================================================================*/
struct WmString {
char *data;
size_t length;
size_t capacity;
};
/*==============================================================================
* Internal Helpers
*============================================================================*/
static bool wm_string_grow(WmString *str, size_t min_capacity) {
if (str->capacity >= min_capacity) return true;
size_t new_capacity = str->capacity * 2;
if (new_capacity < 16) new_capacity = 16;
if (new_capacity < min_capacity) new_capacity = min_capacity;
char *new_data = realloc(str->data, new_capacity);
if (!new_data) return false;
str->data = new_data;
str->capacity = new_capacity;
return true;
}
/*==============================================================================
* Lifecycle
*============================================================================*/
WmString* wm_string_new(const char *str) {
if (!str) return wm_string_new_empty();
return wm_string_new_n(str, strlen(str));
}
WmString* wm_string_new_empty(void) {
WmString *s = calloc(1, sizeof(WmString));
if (!s) return NULL;
s->data = malloc(1);
if (!s->data) {
free(s);
return NULL;
}
s->data[0] = '\0';
s->length = 0;
s->capacity = 1;
return s;
}
WmString* wm_string_new_sized(size_t capacity) {
WmString *s = calloc(1, sizeof(WmString));
if (!s) return NULL;
s->data = malloc(capacity + 1);
if (!s->data) {
free(s);
return NULL;
}
s->data[0] = '\0';
s->length = 0;
s->capacity = capacity + 1;
return s;
}
WmString* wm_string_new_printf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
WmString *s = wm_string_new_vprintf(fmt, args);
va_end(args);
return s;
}
WmString* wm_string_new_vprintf(const char *fmt, va_list args) {
va_list args_copy;
va_copy(args_copy, args);
int len = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
if (len < 0) return NULL;
WmString *s = wm_string_new_sized(len + 1);
if (!s) return NULL;
vsnprintf(s->data, s->capacity, fmt, args);
s->length = len;
return s;
}
WmString* wm_string_new_n(const char *str, size_t n) {
if (!str) return wm_string_new_empty();
WmString *s = calloc(1, sizeof(WmString));
if (!s) return NULL;
s->data = malloc(n + 1);
if (!s->data) {
free(s);
return NULL;
}
memcpy(s->data, str, n);
s->data[n] = '\0';
s->length = n;
s->capacity = n + 1;
return s;
}
void wm_string_destroy(WmString *str) {
if (!str) return;
free(str->data);
free(str);
}
WmString* wm_string_clone(const WmString *str) {
if (!str) return NULL;
return wm_string_new_n(str->data, str->length);
}
/*==============================================================================
* Operations
*============================================================================*/
void wm_string_append(WmString *str, const char *suffix) {
if (!str || !suffix) return;
wm_string_append_n(str, suffix, strlen(suffix));
}
void wm_string_append_char(WmString *str, char c) {
if (!str) return;
if (!wm_string_grow(str, str->length + 2)) return;
str->data[str->length++] = c;
str->data[str->length] = '\0';
}
void wm_string_append_n(WmString *str, const char *suffix, size_t n) {
if (!str || !suffix || n == 0) return;
if (!wm_string_grow(str, str->length + n + 1)) return;
memcpy(str->data + str->length, suffix, n);
str->length += n;
str->data[str->length] = '\0';
}
void wm_string_append_printf(WmString *str, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
wm_string_append_vprintf(str, fmt, args);
va_end(args);
}
void wm_string_append_vprintf(WmString *str, const char *fmt, va_list args) {
if (!str || !fmt) return;
va_list args_copy;
va_copy(args_copy, args);
int len = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
if (len < 0) return;
if (!wm_string_grow(str, str->length + len + 1)) return;
vsnprintf(str->data + str->length, str->capacity - str->length, fmt, args);
str->length += len;
}
void wm_string_append_string(WmString *str, const WmString *suffix) {
if (!suffix) return;
wm_string_append_n(str, suffix->data, suffix->length);
}
void wm_string_prepend(WmString *str, const char *prefix) {
if (!str || !prefix) return;
size_t prefix_len = strlen(prefix);
if (prefix_len == 0) return;
if (!wm_string_grow(str, str->length + prefix_len + 1)) return;
memmove(str->data + prefix_len, str->data, str->length + 1);
memcpy(str->data, prefix, prefix_len);
str->length += prefix_len;
}
void wm_string_prepend_char(WmString *str, char c) {
if (!str) return;
if (!wm_string_grow(str, str->length + 2)) return;
memmove(str->data + 1, str->data, str->length + 1);
str->data[0] = c;
str->length++;
}
void wm_string_insert(WmString *str, size_t pos, const char *insert) {
if (!str || !insert || pos > str->length) return;
size_t insert_len = strlen(insert);
if (insert_len == 0) return;
if (!wm_string_grow(str, str->length + insert_len + 1)) return;
memmove(str->data + pos + insert_len, str->data + pos, str->length - pos + 1);
memcpy(str->data + pos, insert, insert_len);
str->length += insert_len;
}
void wm_string_insert_char(WmString *str, size_t pos, char c) {
if (!str || pos > str->length) return;
if (!wm_string_grow(str, str->length + 2)) return;
memmove(str->data + pos + 1, str->data + pos, str->length - pos + 1);
str->data[pos] = c;
str->length++;
}
void wm_string_insert_string(WmString *str, size_t pos, const WmString *insert) {
if (!insert) return;
wm_string_insert(str, pos, insert->data);
}
void wm_string_erase(WmString *str, size_t pos, size_t len) {
if (!str || pos >= str->length) return;
if (len > str->length - pos) len = str->length - pos;
memmove(str->data + pos, str->data + pos + len, str->length - pos - len + 1);
str->length -= len;
}
void wm_string_clear(WmString *str) {
if (!str) return;
str->length = 0;
str->data[0] = '\0';
}
/*==============================================================================
* Queries
*============================================================================*/
const char* wm_string_cstr(const WmString *str) {
return str ? str->data : "";
}
char* wm_string_detach(WmString *str) {
if (!str) return NULL;
char *data = str->data;
free(str);
return data;
}
size_t wm_string_length(const WmString *str) {
return str ? str->length : 0;
}
size_t wm_string_capacity(const WmString *str) {
return str ? str->capacity : 0;
}
size_t wm_string_bytes(const WmString *str) {
return str ? str->length + 1 : 0;
}
bool wm_string_is_empty(const WmString *str) {
return !str || str->length == 0;
}
bool wm_string_is_null(const WmString *str) {
return str == NULL;
}
char wm_string_char_at(const WmString *str, size_t pos) {
if (!str || pos >= str->length) return '\0';
return str->data[pos];
}
/*==============================================================================
* Comparison
*============================================================================*/
bool wm_string_equals(const WmString *str1, const WmString *str2) {
if (str1 == str2) return true;
if (!str1 || !str2) return false;
if (str1->length != str2->length) return false;
return memcmp(str1->data, str2->data, str1->length) == 0;
}
bool wm_string_equals_cstr(const WmString *str, const char *cstr) {
if (!str) return !cstr || cstr[0] == '\0';
if (!cstr) return str->length == 0;
return strcmp(str->data, cstr) == 0;
}
int wm_string_compare(const WmString *str1, const WmString *str2) {
if (str1 == str2) return 0;
if (!str1) return -1;
if (!str2) return 1;
return strcmp(str1->data, str2->data);
}
int wm_string_compare_cstr(const WmString *str, const char *cstr) {
if (!str) return cstr ? -1 : 0;
if (!cstr) return 1;
return strcmp(str->data, cstr);
}
int wm_string_case_compare(const WmString *str1, const WmString *str2) {
if (str1 == str2) return 0;
if (!str1) return -1;
if (!str2) return 1;
return strcasecmp(str1->data, str2->data);
}
int wm_string_case_compare_cstr(const WmString *str, const char *cstr) {
if (!str) return cstr ? -1 : 0;
if (!cstr) return 1;
return strcasecmp(str->data, cstr);
}
/*==============================================================================
* Searching
*============================================================================*/
bool wm_string_contains(const WmString *str, const char *needle) {
if (!str || !needle) return false;
return strstr(str->data, needle) != NULL;
}
bool wm_string_contains_char(const WmString *str, char c) {
if (!str) return false;
return strchr(str->data, c) != NULL;
}
size_t wm_string_find(const WmString *str, const char *needle, size_t start) {
if (!str || !needle || start > str->length) return (size_t)-1;
const char *found = strstr(str->data + start, needle);
return found ? (size_t)(found - str->data) : (size_t)-1;
}
size_t wm_string_find_char(const WmString *str, char c, size_t start) {
if (!str || start > str->length) return (size_t)-1;
const char *found = strchr(str->data + start, c);
return found ? (size_t)(found - str->data) : (size_t)-1;
}
size_t wm_string_find_last(const WmString *str, const char *needle) {
if (!str || !needle) return (size_t)-1;
/* Simple implementation since strrstr is not standard */
size_t needle_len = strlen(needle);
if (needle_len == 0) return str->length;
for (size_t i = str->length - needle_len + 1; i-- > 0; ) {
if (memcmp(str->data + i, needle, needle_len) == 0) {
return i;
}
}
return (size_t)-1;
}
size_t wm_string_find_last_char(const WmString *str, char c) {
if (!str) return (size_t)-1;
const char *found = strrchr(str->data, c);
return found ? (size_t)(found - str->data) : (size_t)-1;
}
bool wm_string_starts_with(const WmString *str, const char *prefix) {
if (!str || !prefix) return false;
size_t prefix_len = strlen(prefix);
if (prefix_len > str->length) return false;
return strncmp(str->data, prefix, prefix_len) == 0;
}
bool wm_string_starts_with_char(const WmString *str, char c) {
return str && str->length > 0 && str->data[0] == c;
}
bool wm_string_ends_with(const WmString *str, const char *suffix) {
if (!str || !suffix) return false;
size_t suffix_len = strlen(suffix);
if (suffix_len > str->length) return false;
return strncmp(str->data + str->length - suffix_len, suffix, suffix_len) == 0;
}
bool wm_string_ends_with_char(const WmString *str, char c) {
return str && str->length > 0 && str->data[str->length - 1] == c;
}
/*==============================================================================
* Modification
*============================================================================*/
void wm_string_trim(WmString *str) {
wm_string_trim_left(str);
wm_string_trim_right(str);
}
void wm_string_trim_left(WmString *str) {
if (!str) return;
size_t i = 0;
while (i < str->length && isspace((unsigned char)str->data[i])) i++;
if (i > 0) {
memmove(str->data, str->data + i, str->length - i + 1);
str->length -= i;
}
}
void wm_string_trim_right(WmString *str) {
if (!str) return;
while (str->length > 0 && isspace((unsigned char)str->data[str->length - 1])) {
str->data[--str->length] = '\0';
}
}
void wm_string_replace(WmString *str, const char *search, const char *replace) {
if (!str || !search || !replace) return;
size_t count = wm_string_replace_all(str, search, replace);
(void)count;
}
void wm_string_replace_char(WmString *str, char search, char replace) {
if (!str) return;
for (size_t i = 0; i < str->length; i++) {
if (str->data[i] == search) str->data[i] = replace;
}
}
size_t wm_string_replace_all(WmString *str, const char *search, const char *replace) {
if (!str || !search || !replace) return 0;
size_t search_len = strlen(search);
size_t replace_len = strlen(replace);
size_t count = 0;
char *pos = str->data;
while ((pos = strstr(pos, search)) != NULL) {
count++;
pos += search_len;
}
if (count == 0) return 0;
size_t new_length = str->length + count * (replace_len - search_len);
if (!wm_string_grow(str, new_length + 1)) return 0;
WmString *result = wm_string_new_sized(new_length);
if (!result) return 0;
char *src = str->data;
char *dst = result->data;
while ((pos = strstr(src, search)) != NULL) {
size_t prefix_len = pos - src;
memcpy(dst, src, prefix_len);
dst += prefix_len;
memcpy(dst, replace, replace_len);
dst += replace_len;
src = pos + search_len;
}
size_t remaining = new_length - (size_t)(dst - result->data);
strncpy(dst, src, remaining);
dst[remaining] = '\0';
result->length = new_length;
free(str->data);
str->data = result->data;
str->length = result->length;
str->capacity = result->capacity;
free(result);
return count;
}
void wm_string_to_lower(WmString *str) {
if (!str) return;
for (size_t i = 0; i < str->length; i++) {
str->data[i] = tolower((unsigned char)str->data[i]);
}
}
void wm_string_to_upper(WmString *str) {
if (!str) return;
for (size_t i = 0; i < str->length; i++) {
str->data[i] = toupper((unsigned char)str->data[i]);
}
}
void wm_string_reverse(WmString *str) {
if (!str) return;
for (size_t i = 0, j = str->length - 1; i < j; i++, j--) {
char tmp = str->data[i];
str->data[i] = str->data[j];
str->data[j] = tmp;
}
}
/*==============================================================================
* Substrings
*============================================================================*/
WmString* wm_string_substring(const WmString *str, size_t start, size_t len) {
if (!str || start > str->length) return wm_string_new_empty();
if (start + len > str->length) len = str->length - start;
return wm_string_new_n(str->data + start, len);
}
WmString* wm_string_left(const WmString *str, size_t n) {
if (!str) return wm_string_new_empty();
if (n > str->length) n = str->length;
return wm_string_new_n(str->data, n);
}
WmString* wm_string_right(const WmString *str, size_t n) {
if (!str) return wm_string_new_empty();
if (n > str->length) n = str->length;
return wm_string_new_n(str->data + str->length - n, n);
}
/*==============================================================================
* Formatting
*============================================================================*/
void wm_string_printf(WmString *str, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
wm_string_clear(str);
wm_string_append_vprintf(str, fmt, args);
va_end(args);
}
void wm_string_vprintf(WmString *str, const char *fmt, va_list args) {
wm_string_clear(str);
wm_string_append_vprintf(str, fmt, args);
}
/*==============================================================================
* Conversion
*============================================================================*/
int wm_string_to_int(const WmString *str, int default_val) {
if (!str || str->length == 0) return default_val;
char *end;
long val = strtol(str->data, &end, 10);
if (end == str->data) return default_val;
return (int)val;
}
long wm_string_to_long(const WmString *str, long default_val) {
if (!str || str->length == 0) return default_val;
char *end;
long val = strtol(str->data, &end, 10);
if (end == str->data) return default_val;
return val;
}
float wm_string_to_float(const WmString *str, float default_val) {
if (!str || str->length == 0) return default_val;
char *end;
float val = strtof(str->data, &end);
if (end == str->data) return default_val;
return val;
}
double wm_string_to_double(const WmString *str, double default_val) {
if (!str || str->length == 0) return default_val;
char *end;
double val = strtod(str->data, &end);
if (end == str->data) return default_val;
return val;
}
bool wm_string_to_bool(const WmString *str, bool default_val) {
if (!str || str->length == 0) return default_val;
if (strcasecmp(str->data, "true") == 0 ||
strcmp(str->data, "1") == 0 ||
strcasecmp(str->data, "yes") == 0) {
return true;
}
if (strcasecmp(str->data, "false") == 0 ||
strcmp(str->data, "0") == 0 ||
strcasecmp(str->data, "no") == 0) {
return false;
}
return default_val;
}
WmString* wm_string_from_int(int val) {
return wm_string_new_printf("%d", val);
}
WmString* wm_string_from_long(long val) {
return wm_string_new_printf("%ld", val);
}
WmString* wm_string_from_float(float val, int precision) {
return wm_string_new_printf("%.*f", precision, val);
}
WmString* wm_string_from_double(double val, int precision) {
return wm_string_new_printf("%.*lf", precision, val);
}
WmString* wm_string_from_bool(bool val) {
return wm_string_new(val ? "true" : "false");
}
/*==============================================================================
* Hash
*============================================================================*/
uint32_t wm_string_hash(const WmString *str) {
if (!str) return 0;
return wm_string_hash_cstr(str->data);
}
uint32_t wm_string_hash_cstr(const char *str) {
if (!str) return 0;
/* FNV-1a hash */
uint32_t hash = 2166136261u;
while (*str) {
hash ^= (uint8_t)*str++;
hash *= 16777619u;
}
return hash;
}
+119 -51
View File
@@ -8,6 +8,7 @@
#include "client.h"
#include "config.h"
#include "util.h"
#include "workspace.h"
#include <string.h>
#include <stdbool.h>
@@ -16,8 +17,85 @@
#define BUTTON_SIZE 16
#define BUTTON_PADDING 4
#define RESIZE_EDGE 8
#define RESIZE_EDGE 12
#define DEC_XFT_COLOR_CACHE_SIZE 8
typedef struct {
unsigned long pixel;
XftColor xft_color;
bool valid;
int lru_counter;
} DecCachedXftColor;
static DecCachedXftColor dec_xft_color_cache[DEC_XFT_COLOR_CACHE_SIZE];
static Visual *dec_cached_visual = NULL;
static int dec_xft_color_lru_counter = 0;
static XftDraw *dec_cached_xft_draw = NULL;
static Window dec_cached_window = None;
static XftDraw *dec_get_xft_draw(Window w)
{
if (dec_cached_window == w && dec_cached_xft_draw != NULL) {
return dec_cached_xft_draw;
}
if (dec_cached_xft_draw != NULL) {
XftDrawDestroy(dec_cached_xft_draw);
}
if (dec_cached_visual == NULL) {
dec_cached_visual = DefaultVisual(dwn->display, dwn->screen);
}
dec_cached_xft_draw = XftDrawCreate(dwn->display, w, dec_cached_visual, dwn->colormap);
dec_cached_window = w;
return dec_cached_xft_draw;
}
static XftColor *dec_get_cached_xft_color(unsigned long color)
{
if (dec_cached_visual == NULL) {
dec_cached_visual = DefaultVisual(dwn->display, dwn->screen);
}
for (int i = 0; i < DEC_XFT_COLOR_CACHE_SIZE; i++) {
if (dec_xft_color_cache[i].valid && dec_xft_color_cache[i].pixel == color) {
dec_xft_color_cache[i].lru_counter = ++dec_xft_color_lru_counter;
return &dec_xft_color_cache[i].xft_color;
}
}
int oldest_idx = 0;
int oldest_lru = dec_xft_color_cache[0].lru_counter;
for (int i = 1; i < DEC_XFT_COLOR_CACHE_SIZE; i++) {
if (!dec_xft_color_cache[i].valid) {
oldest_idx = i;
break;
}
if (dec_xft_color_cache[i].lru_counter < oldest_lru) {
oldest_lru = dec_xft_color_cache[i].lru_counter;
oldest_idx = i;
}
}
if (dec_xft_color_cache[oldest_idx].valid) {
XftColorFree(dwn->display, dec_cached_visual, dwn->colormap,
&dec_xft_color_cache[oldest_idx].xft_color);
}
XRenderColor render_color;
render_color.red = ((color >> 16) & 0xFF) * 257;
render_color.green = ((color >> 8) & 0xFF) * 257;
render_color.blue = (color & 0xFF) * 257;
render_color.alpha = 0xFFFF;
XftColorAllocValue(dwn->display, dec_cached_visual, dwn->colormap,
&render_color, &dec_xft_color_cache[oldest_idx].xft_color);
dec_xft_color_cache[oldest_idx].pixel = color;
dec_xft_color_cache[oldest_idx].valid = true;
dec_xft_color_cache[oldest_idx].lru_counter = ++dec_xft_color_lru_counter;
return &dec_xft_color_cache[oldest_idx].xft_color;
}
void decorations_init(void)
{
@@ -46,6 +124,8 @@ void decorations_render(Client *client, bool focused)
void decorations_render_title_bar(Client *client, bool focused)
{
(void)focused;
if (client == NULL || client->frame == None) {
return;
}
@@ -55,20 +135,28 @@ void decorations_render_title_bar(Client *client, bool focused)
}
Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors();
int title_height = config_get_title_height();
int border = client->border_width;
const ColorScheme *colors = config_get_colors();
unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg;
unsigned long fg_color = focused ? colors->title_focused_fg : colors->title_unfocused_fg;
Workspace *ws = workspace_get(client->workspace);
bool is_focused = (ws != NULL && ws->focused == client);
unsigned long base_bg = is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
unsigned long bg_color = ambient_glow_bg(base_bg, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
unsigned long fg_color = client->taskbar_color;
fg_color = adjust_color_for_background(fg_color, bg_color);
XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc,
border, border,
client->width, title_height);
if (client->title[0] != '\0' && dwn->xft_font != NULL) {
int text_y = border + (title_height + dwn->xft_font->ascent) / 2;
bool is_alt_tab_selection = (dwn->is_alt_tabbing && dwn->alt_tab_client == client);
XftFont *title_font = (is_alt_tab_selection && dwn->xft_font_bold != NULL) ?
dwn->xft_font_bold : dwn->xft_font;
if (client->title[0] != '\0' && title_font != NULL) {
int text_y = border + (title_height + title_font->ascent) / 2;
int text_x = border + BUTTON_PADDING;
int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING;
@@ -76,11 +164,13 @@ 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, dwn->xft_font,
XftTextExtentsUtf8(dpy, title_font,
(const FcChar8 *)display_title, strlen(display_title), &extents);
int text_width = extents.xOff;
@@ -98,7 +188,7 @@ void decorations_render_title_bar(Client *client, bool focused)
display_title[cut] = '\0';
title_truncated = true;
XftTextExtentsUtf8(dpy, dwn->xft_font,
XftTextExtentsUtf8(dpy, title_font,
(const FcChar8 *)display_title, strlen(display_title), &extents);
text_width = extents.xOff;
}
@@ -106,27 +196,12 @@ void decorations_render_title_bar(Client *client, bool focused)
strncat(display_title, "...", sizeof(display_title) - strlen(display_title) - 1);
}
XftDraw *xft_draw = XftDrawCreate(dpy, client->frame,
DefaultVisual(dpy, dwn->screen),
dwn->colormap);
XftDraw *xft_draw = dec_get_xft_draw(client->frame);
if (xft_draw != NULL) {
XftColor xft_color;
XRenderColor render_color;
render_color.red = ((fg_color >> 16) & 0xFF) * 257;
render_color.green = ((fg_color >> 8) & 0xFF) * 257;
render_color.blue = (fg_color & 0xFF) * 257;
render_color.alpha = 0xFFFF;
XftColorAllocValue(dpy, DefaultVisual(dpy, dwn->screen),
dwn->colormap, &render_color, &xft_color);
XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font,
XftColor *xft_color = dec_get_cached_xft_color(fg_color);
XftDrawStringUtf8(xft_draw, xft_color, title_font,
text_x, text_y,
(const FcChar8 *)display_title, strlen(display_title));
XftColorFree(dpy, DefaultVisual(dpy, dwn->screen),
dwn->colormap, &xft_color);
XftDrawDestroy(xft_draw);
}
}
}
@@ -147,7 +222,7 @@ void decorations_render_buttons(Client *client, bool focused)
}
Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors();
(void)focused;
int title_height = config_get_title_height();
int border = client->border_width;
@@ -160,11 +235,19 @@ void decorations_render_buttons(Client *client, bool focused)
return;
}
unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg;
const ColorScheme *colors = config_get_colors();
Workspace *ws = workspace_get(client->workspace);
bool is_focused = (ws != NULL && ws->focused == client);
unsigned long base_bg = is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
unsigned long bg_color = ambient_glow_bg(base_bg, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
unsigned long close_color = ambient_glow_accent(0xff0055, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
unsigned long max_color = ambient_glow_accent(0x00ff00, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
unsigned long min_color = ambient_glow_accent(0xffff00, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc, close_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
XSetForeground(dpy, dwn->gc, 0xff0055); // Neon Red/Pink
XSetForeground(dpy, dwn->gc, close_color);
XDrawLine(dpy, client->frame, dwn->gc,
close_x + 3, button_y + 3,
close_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 4);
@@ -174,14 +257,14 @@ void decorations_render_buttons(Client *client, bool focused)
XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc, max_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
XSetForeground(dpy, dwn->gc, 0x00ff00); // Neon Green
XSetForeground(dpy, dwn->gc, max_color);
XDrawRectangle(dpy, client->frame, dwn->gc,
max_x + 3, button_y + 3,
BUTTON_SIZE - 7, BUTTON_SIZE - 7);
XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, client->frame, dwn->gc, min_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
XSetForeground(dpy, dwn->gc, 0xffff00); // Neon Yellow
XSetForeground(dpy, dwn->gc, min_color);
XDrawLine(dpy, client->frame, dwn->gc,
min_x + 3, button_y + BUTTON_SIZE - 5,
min_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 5);
@@ -315,26 +398,11 @@ void decorations_draw_text(Window window, GC gc, int x, int y,
}
if (dwn->xft_font != NULL) {
XftDraw *xft_draw = XftDrawCreate(dwn->display, window,
DefaultVisual(dwn->display, dwn->screen),
dwn->colormap);
XftDraw *xft_draw = dec_get_xft_draw(window);
if (xft_draw != NULL) {
XftColor xft_color;
XRenderColor render_color;
render_color.red = ((color >> 16) & 0xFF) * 257;
render_color.green = ((color >> 8) & 0xFF) * 257;
render_color.blue = (color & 0xFF) * 257;
render_color.alpha = 0xFFFF;
XftColorAllocValue(dwn->display, DefaultVisual(dwn->display, dwn->screen),
dwn->colormap, &render_color, &xft_color);
XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font,
XftColor *xft_color = dec_get_cached_xft_color(color);
XftDrawStringUtf8(xft_draw, xft_color, dwn->xft_font,
x, y, (const FcChar8 *)text, strlen(text));
XftColorFree(dwn->display, DefaultVisual(dwn->display, dwn->screen),
dwn->colormap, &xft_color);
XftDrawDestroy(xft_draw);
return;
}
}
+329
View File
@@ -0,0 +1,329 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Event Bus Implementation - Thread-Safe Pub/Sub
*/
#include "threading.h"
#include "util.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/eventfd.h>
#define MAX_SUBSCRIPTIONS_PER_TYPE 64
#define EVENT_QUEUE_SIZE 4096
/* ============================================================================
* Event Bus Types
* ============================================================================ */
typedef struct Subscription {
SubscriptionId id;
EventType type;
EventHandler handler;
void *user_data;
EventFilter filter;
void *filter_data;
bool active;
} Subscription;
typedef struct EventNode {
EventType type;
void *data;
void (*free_fn)(void*);
struct EventNode *next;
} EventNode;
struct EventBus {
Subscription subscriptions[MAX_SUBSCRIPTIONS_PER_TYPE * 32]; /* Fixed pool */
uint32_t subscription_count;
_Atomic SubscriptionId next_id;
/* Event queue (MPSC) */
pthread_mutex_t queue_mutex;
EventNode *event_head;
EventNode *event_tail;
_Atomic uint32_t event_count;
int event_fd;
_Atomic int shutdown;
uint32_t batch_size;
pthread_mutex_t sub_mutex;
};
static SubscriptionId generate_subscription_id(EventBus *bus)
{
return atomic_fetch_add_explicit(&bus->next_id, 1, ATOMIC_RELAXED) + 1;
}
/* ============================================================================
* Event Bus Implementation
* ============================================================================ */
EventBus* event_bus_create(void)
{
EventBus *bus = dwn_malloc(sizeof(EventBus));
if (!bus) return NULL;
memset(bus->subscriptions, 0, sizeof(bus->subscriptions));
bus->subscription_count = 0;
atomic_store_explicit(&bus->next_id, 0, ATOMIC_RELEASE);
bus->event_head = NULL;
bus->event_tail = NULL;
atomic_store_explicit(&bus->event_count, 0, ATOMIC_RELEASE);
atomic_store_explicit(&bus->shutdown, 0, ATOMIC_RELEASE);
bus->batch_size = 100;
if (pthread_mutex_init(&bus->queue_mutex, NULL) != 0) {
dwn_free(bus);
return NULL;
}
if (pthread_mutex_init(&bus->sub_mutex, NULL) != 0) {
pthread_mutex_destroy(&bus->queue_mutex);
dwn_free(bus);
return NULL;
}
bus->event_fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
if (bus->event_fd < 0) {
pthread_mutex_destroy(&bus->sub_mutex);
pthread_mutex_destroy(&bus->queue_mutex);
dwn_free(bus);
return NULL;
}
return bus;
}
void event_bus_destroy(EventBus *bus)
{
if (!bus) return;
atomic_store_explicit(&bus->shutdown, 1, ATOMIC_RELEASE);
/* Drain remaining events */
event_bus_process(bus);
/* Free remaining events */
pthread_mutex_lock(&bus->queue_mutex);
EventNode *node = bus->event_head;
while (node) {
EventNode *next = node->next;
if (node->free_fn) {
node->free_fn(node->data);
}
dwn_free(node);
node = next;
}
pthread_mutex_unlock(&bus->queue_mutex);
close(bus->event_fd);
pthread_mutex_destroy(&bus->sub_mutex);
pthread_mutex_destroy(&bus->queue_mutex);
dwn_free(bus);
}
SubscriptionId event_bus_subscribe(EventBus *bus, EventType type,
EventHandler handler, void *user_data)
{
return event_bus_subscribe_filtered(bus, type, handler, user_data, NULL, NULL);
}
SubscriptionId event_bus_subscribe_filtered(EventBus *bus, EventType type,
EventHandler handler, void *user_data,
EventFilter filter, void *filter_data)
{
if (!bus || !handler || type >= 32) return 0;
pthread_mutex_lock(&bus->sub_mutex);
if (bus->subscription_count >= sizeof(bus->subscriptions) / sizeof(bus->subscriptions[0])) {
pthread_mutex_unlock(&bus->sub_mutex);
return 0;
}
/* Find slot */
for (uint32_t i = 0; i < sizeof(bus->subscriptions) / sizeof(bus->subscriptions[0]); i++) {
if (!bus->subscriptions[i].active) {
Subscription *sub = &bus->subscriptions[i];
sub->id = generate_subscription_id(bus);
sub->type = type;
sub->handler = handler;
sub->user_data = user_data;
sub->filter = filter;
sub->filter_data = filter_data;
sub->active = true;
bus->subscription_count++;
pthread_mutex_unlock(&bus->sub_mutex);
return sub->id;
}
}
pthread_mutex_unlock(&bus->sub_mutex);
return 0;
}
bool event_bus_unsubscribe(EventBus *bus, SubscriptionId id)
{
if (!bus || id == 0) return false;
pthread_mutex_lock(&bus->sub_mutex);
for (uint32_t i = 0; i < sizeof(bus->subscriptions) / sizeof(bus->subscriptions[0]); i++) {
if (bus->subscriptions[i].active && bus->subscriptions[i].id == id) {
bus->subscriptions[i].active = false;
bus->subscription_count--;
pthread_mutex_unlock(&bus->sub_mutex);
return true;
}
}
pthread_mutex_unlock(&bus->sub_mutex);
return false;
}
void event_bus_publish(EventBus *bus, EventType type, void *event_data)
{
event_bus_publish_owned(bus, type, event_data, NULL);
}
void event_bus_publish_owned(EventBus *bus, EventType type, void *event_data,
void (*free_fn)(void*))
{
if (!bus || type >= 32) {
if (free_fn && event_data) free_fn(event_data);
return;
}
if (atomic_load_explicit(&bus->shutdown, ATOMIC_ACQUIRE)) {
if (free_fn && event_data) free_fn(event_data);
return;
}
EventNode *node = dwn_malloc(sizeof(EventNode));
if (!node) {
if (free_fn && event_data) free_fn(event_data);
return;
}
node->type = type;
node->data = event_data;
node->free_fn = free_fn;
node->next = NULL;
pthread_mutex_lock(&bus->queue_mutex);
if (bus->event_tail) {
bus->event_tail->next = node;
} else {
bus->event_head = node;
}
bus->event_tail = node;
atomic_fetch_add_explicit(&bus->event_count, 1, ATOMIC_RELAXED);
pthread_mutex_unlock(&bus->queue_mutex);
/* Signal via eventfd */
uint64_t one = 1;
ssize_t w __attribute__((unused)) = write(bus->event_fd, &one, sizeof(one));
}
uint32_t event_bus_process(EventBus *bus)
{
if (!bus) return 0;
/* Acknowledge eventfd */
uint64_t val;
ssize_t r __attribute__((unused)) = read(bus->event_fd, &val, sizeof(val));
uint32_t processed = 0;
while (processed < bus->batch_size) {
/* Dequeue event */
pthread_mutex_lock(&bus->queue_mutex);
EventNode *node = bus->event_head;
if (!node) {
pthread_mutex_unlock(&bus->queue_mutex);
break;
}
bus->event_head = node->next;
if (!bus->event_head) {
bus->event_tail = NULL;
}
atomic_fetch_sub_explicit(&bus->event_count, 1, ATOMIC_RELAXED);
pthread_mutex_unlock(&bus->queue_mutex);
/* Dispatch to subscribers */
pthread_mutex_lock(&bus->sub_mutex);
for (uint32_t i = 0; i < sizeof(bus->subscriptions) / sizeof(bus->subscriptions[0]); i++) {
Subscription *sub = &bus->subscriptions[i];
if (sub->active && sub->type == node->type) {
/* Check filter if present */
bool should_handle = true;
if (sub->filter) {
should_handle = sub->filter(node->type, node->data, sub->filter_data);
}
if (should_handle) {
sub->handler(node->type, node->data, sub->user_data);
}
}
}
pthread_mutex_unlock(&bus->sub_mutex);
/* Free event data */
if (node->free_fn) {
node->free_fn(node->data);
}
dwn_free(node);
processed++;
}
return processed;
}
void event_bus_set_batch_size(EventBus *bus, uint32_t batch_size)
{
if (!bus) return;
bus->batch_size = batch_size ? batch_size : 100;
}
/* ============================================================================
* Default Event Bus
* ============================================================================ */
static EventBus *g_default_bus = NULL;
EventBus* event_bus_default(void)
{
return g_default_bus;
}
bool event_bus_init_default(void)
{
if (g_default_bus) return true;
g_default_bus = event_bus_create();
return g_default_bus != NULL;
}
void event_bus_shutdown_default(void)
{
if (g_default_bus) {
event_bus_destroy(g_default_bus);
g_default_bus = NULL;
}
}
+333
View File
@@ -0,0 +1,333 @@
/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Future/Promise Implementation - Async Result Handling
*/
#include "threading.h"
#include "util.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
/* ============================================================================
* Future Implementation
* ============================================================================ */
typedef struct FutureCallbackNode {
FutureCallback callback;
void *user_data;
struct FutureCallbackNode *next;
} FutureCallbackNode;
struct Future {
pthread_mutex_t mutex;
pthread_cond_t ready;
FutureResult result;
int error_code;
_Atomic int ready_flag;
_Atomic int has_result;
FutureCallbackNode *callbacks;
_Atomic int callback_count;
};
Future* future_create(void)
{
Future *f = dwn_malloc(sizeof(Future));
if (!f) return NULL;
atomic_store_explicit(&f->ready_flag, 0, ATOMIC_RELEASE);
atomic_store_explicit(&f->has_result, 0, ATOMIC_RELEASE);
atomic_store_explicit(&f->callback_count, 0, ATOMIC_RELAXED);
f->result = NULL;
f->error_code = 0;
f->callbacks = NULL;
if (pthread_mutex_init(&f->mutex, NULL) != 0) {
dwn_free(f);
return NULL;
}
if (pthread_cond_init(&f->ready, NULL) != 0) {
pthread_mutex_destroy(&f->mutex);
dwn_free(f);
return NULL;
}
return f;
}
void future_destroy(Future *f)
{
if (!f) return;
/* Free callback chain */
FutureCallbackNode *node = f->callbacks;
while (node) {
FutureCallbackNode *next = node->next;
dwn_free(node);
node = next;
}
pthread_cond_destroy(&f->ready);
pthread_mutex_destroy(&f->mutex);
dwn_free(f);
}
void future_set_result(Future *f, FutureResult result)
{
if (!f) return;
pthread_mutex_lock(&f->mutex);
if (atomic_load_explicit(&f->has_result, ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&f->mutex);
return; /* Already set */
}
f->result = result;
f->error_code = 0;
atomic_store_explicit(&f->has_result, 1, ATOMIC_RELEASE);
atomic_store_explicit(&f->ready_flag, 1, ATOMIC_RELEASE);
/* Execute callbacks */
FutureCallbackNode *node = f->callbacks;
while (node) {
node->callback(f, result, node->user_data);
node = node->next;
}
pthread_cond_broadcast(&f->ready);
pthread_mutex_unlock(&f->mutex);
}
void future_set_error(Future *f, int error_code)
{
if (!f) return;
pthread_mutex_lock(&f->mutex);
if (atomic_load_explicit(&f->has_result, ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&f->mutex);
return;
}
f->result = NULL;
f->error_code = error_code;
atomic_store_explicit(&f->has_result, 1, ATOMIC_RELEASE);
atomic_store_explicit(&f->ready_flag, 1, ATOMIC_RELEASE);
/* Execute callbacks with NULL result */
FutureCallbackNode *node = f->callbacks;
while (node) {
node->callback(f, NULL, node->user_data);
node = node->next;
}
pthread_cond_broadcast(&f->ready);
pthread_mutex_unlock(&f->mutex);
}
FutureResult future_get(Future *f, ThreadStatus *status)
{
return future_get_timeout(f, 0, status);
}
FutureResult future_get_timeout(Future *f, uint64_t timeout_ms, ThreadStatus *status)
{
if (!f) {
if (status) *status = THREAD_ERROR_INVALID;
return NULL;
}
pthread_mutex_lock(&f->mutex);
if (!atomic_load_explicit(&f->ready_flag, ATOMIC_ACQUIRE)) {
if (timeout_ms == 0) {
pthread_cond_wait(&f->ready, &f->mutex);
} else {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
if (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec -= 1000000000;
}
int ret = pthread_cond_timedwait(&f->ready, &f->mutex, &ts);
if (ret == ETIMEDOUT) {
pthread_mutex_unlock(&f->mutex);
if (status) *status = THREAD_ERROR_TIMEOUT;
return NULL;
}
}
}
FutureResult result = f->result;
if (status) {
*status = (f->error_code != 0) ? THREAD_ERROR : THREAD_OK;
}
pthread_mutex_unlock(&f->mutex);
return result;
}
bool future_is_ready(Future *f)
{
if (!f) return false;
return atomic_load_explicit(&f->ready_flag, ATOMIC_ACQUIRE) != 0;
}
void future_then(Future *f, FutureCallback callback, void *user_data)
{
if (!f || !callback) return;
pthread_mutex_lock(&f->mutex);
/* If already ready, execute immediately */
if (atomic_load_explicit(&f->ready_flag, ATOMIC_ACQUIRE)) {
pthread_mutex_unlock(&f->mutex);
callback(f, f->result, user_data);
return;
}
/* Add to callback chain */
FutureCallbackNode *node = dwn_malloc(sizeof(FutureCallbackNode));
if (!node) {
pthread_mutex_unlock(&f->mutex);
return;
}
node->callback = callback;
node->user_data = user_data;
node->next = f->callbacks;
f->callbacks = node;
atomic_fetch_add_explicit(&f->callback_count, 1, ATOMIC_RELAXED);
pthread_mutex_unlock(&f->mutex);
}
/* ============================================================================
* Composite Futures
* ============================================================================ */
typedef struct {
Future *composite;
_Atomic int remaining;
uint32_t total;
pthread_mutex_t mutex;
} CompositeFutureData;
static void all_callback(Future *f, FutureResult result, void *user_data)
{
(void)f;
(void)result;
CompositeFutureData *data = user_data;
int rem = atomic_fetch_sub_explicit(&data->remaining, 1, ATOMIC_ACQ_REL) - 1;
if (rem == 0) {
future_set_result(data->composite, (void*)(uintptr_t)data->total);
}
}
Future* future_all(Future **futures, uint32_t count)
{
if (!futures || count == 0) return NULL;
Future *composite = future_create();
if (!composite) return NULL;
if (count == 1) {
/* Optimize single future */
FutureResult r = future_get(futures[0], NULL);
future_set_result(composite, r);
return composite;
}
CompositeFutureData *data = dwn_malloc(sizeof(CompositeFutureData));
if (!data) {
future_destroy(composite);
return NULL;
}
data->composite = composite;
atomic_store_explicit(&data->remaining, count, ATOMIC_RELEASE);
data->total = count;
pthread_mutex_init(&data->mutex, NULL);
/* Attach callback to each future */
for (uint32_t i = 0; i < count; i++) {
if (future_is_ready(futures[i])) {
/* Already done */
if (atomic_fetch_sub_explicit(&data->remaining, 1, ATOMIC_ACQ_REL) - 1 == 0) {
future_set_result(composite, (void*)(uintptr_t)count);
pthread_mutex_destroy(&data->mutex);
dwn_free(data);
return composite;
}
} else {
future_then(futures[i], all_callback, data);
}
}
return composite;
}
static void any_callback(Future *f, FutureResult result, void *user_data)
{
(void)f;
CompositeFutureData *data = user_data;
pthread_mutex_lock(&data->mutex);
if (!future_is_ready(data->composite)) {
future_set_result(data->composite, result);
}
pthread_mutex_unlock(&data->mutex);
}
Future* future_any(Future **futures, uint32_t count)
{
if (!futures || count == 0) return NULL;
Future *composite = future_create();
if (!composite) return NULL;
if (count == 1) {
FutureResult r = future_get(futures[0], NULL);
future_set_result(composite, r);
return composite;
}
CompositeFutureData *data = dwn_malloc(sizeof(CompositeFutureData));
if (!data) {
future_destroy(composite);
return NULL;
}
data->composite = composite;
pthread_mutex_init(&data->mutex, NULL);
/* Attach callback to each future */
for (uint32_t i = 0; i < count; i++) {
if (future_is_ready(futures[i])) {
future_set_result(composite, futures[i]->result);
pthread_mutex_destroy(&data->mutex);
dwn_free(data);
return composite;
} else {
future_then(futures[i], any_callback, data);
}
}
return composite;
}
+285
View File
@@ -17,6 +17,8 @@
#include "demo.h"
#include "layout.h"
#include "api.h"
#include "marks.h"
#include "panel.h"
#include <stdio.h>
#include <string.h>
@@ -71,6 +73,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 +439,22 @@ 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;
super_used_in_combo = false;
@@ -577,6 +596,23 @@ 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");
keys_bind(MOD_ALT, XK_space, key_expose_all, "Expose all windows");
}
@@ -613,6 +649,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");
@@ -887,7 +939,11 @@ void key_show_shortcuts(void)
"Alt+F9 Toggle minimize\n"
"Alt+F10 Toggle maximize\n"
"Alt+F11 Toggle fullscreen\n"
"Alt+Space Expose all windows\n"
"Super+F9 Toggle floating\n"
"Super+K Kill all applications\n"
"Alt+Drag Move window (any position)\n"
"Alt+Right-Drag Resize window (any position)\n"
"\n"
"=== Workspaces ===\n"
"F1-F9 Switch to workspace\n"
@@ -908,6 +964,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"
@@ -1087,3 +1151,224 @@ 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_expose_all(void)
{
if (dwn == NULL) {
return;
}
if (dwn->desktop_shown) {
dwn->desktop_shown = false;
dwn->desktop_minimized_count = 0;
}
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
if (c->workspace != (unsigned int)dwn->current_workspace) {
continue;
}
if (client_is_fullscreen(c)) {
client_set_fullscreen(c, false);
}
if (client_is_maximized(c)) {
client_set_maximize(c, false);
}
if (client_is_minimized(c)) {
client_restore(c);
}
}
workspace_arrange_current();
panel_render_all();
}
void key_set_mark(void)
{
marks_start_set_mode();
}
void key_goto_mark(void)
{
marks_start_goto_mode();
}
+376 -21
View File
@@ -15,13 +15,19 @@
static const char *layout_names[] = {
"Tiling",
"Floating",
"Monocle"
"Monocle",
"Centered",
"Columns",
"Fibonacci"
};
static const char *layout_symbols[] = {
"[]=",
"><>",
"[M]"
"[M]",
"|M|",
"|||",
"[@]"
};
@@ -42,6 +48,15 @@ void layout_arrange(int workspace)
case LAYOUT_MONOCLE:
layout_arrange_monocle(workspace);
break;
case LAYOUT_CENTERED_MASTER:
layout_arrange_centered_master(workspace);
break;
case LAYOUT_COLUMNS:
layout_arrange_columns(workspace);
break;
case LAYOUT_FIBONACCI:
layout_arrange_fibonacci(workspace);
break;
default:
layout_arrange_tiling(workspace);
break;
@@ -84,16 +99,27 @@ void layout_arrange_tiling(int workspace)
}
int stack_width = area_width - master_width - 3 * gap;
if (stack_width < 50) stack_width = 50;
int i = 0;
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;
}
@@ -120,6 +146,16 @@ void layout_arrange_tiling(int workspace)
stack_y += h + gap;
}
int max_y = area_y + area_height - gap;
int max_x = area_x + area_width - gap;
if (y + h > max_y) {
h = max_y - y;
}
if (x + w > max_x) {
w = max_x - x;
}
int actual_h = h - title_height - 2 * border;
int actual_w = w - 2 * border;
@@ -130,6 +166,8 @@ void layout_arrange_tiling(int workspace)
actual_w, actual_h);
i++;
c = next;
}
}
@@ -139,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;
}
@@ -185,6 +233,8 @@ void layout_arrange_floating(int workspace)
if (c->height < 50) c->height = 50;
client_configure(c);
c = next;
}
}
@@ -198,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;
}
@@ -215,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;
}
}
@@ -248,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;
@@ -268,21 +343,25 @@ void layout_apply_snap_constraint(Client *c, int area_x, int area_y,
return;
}
int title_h = (dwn->config && dwn->config->show_decorations) ?
config_get_title_height() : 0;
int border = c->border_width;
int half_w = area_w / 2;
int half_h = area_h / 2;
switch (c->snap.horizontal) {
case SNAP_H_LEFT:
c->x = area_x + gap;
c->width = half_w - gap * 2;
c->x = area_x + gap + border;
c->width = half_w - gap * 2 - 2 * border;
break;
case SNAP_H_RIGHT:
c->x = area_x + half_w + gap;
c->width = half_w - gap * 2;
c->x = area_x + half_w + gap + border;
c->width = half_w - gap * 2 - 2 * border;
break;
case SNAP_H_FULL:
c->x = area_x + gap;
c->width = area_w - gap * 2;
c->x = area_x + gap + border;
c->width = area_w - gap * 2 - 2 * border;
break;
case SNAP_H_NONE:
break;
@@ -290,16 +369,16 @@ void layout_apply_snap_constraint(Client *c, int area_x, int area_y,
switch (c->snap.vertical) {
case SNAP_V_TOP:
c->y = area_y + gap;
c->height = half_h - gap * 2;
c->y = area_y + gap + title_h + border;
c->height = half_h - gap * 2 - title_h - 2 * border;
break;
case SNAP_V_BOTTOM:
c->y = area_y + half_h + gap;
c->height = half_h - gap * 2;
c->y = area_y + half_h + gap + title_h + border;
c->height = half_h - gap * 2 - title_h - 2 * border;
break;
case SNAP_V_FULL:
c->y = area_y + gap;
c->height = area_h - gap * 2;
c->y = area_y + gap + title_h + border;
c->height = area_h - gap * 2 - title_h - 2 * border;
break;
case SNAP_V_NONE:
break;
@@ -321,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;
}
}

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