feat: add screenshot and ocr functionality with web interface
build: update makefile with new dependencies for tesseract and libpng chore: reorganize example files into examples directory
This commit is contained in:
parent
8d3dfcbc7e
commit
f4af2b1f1e
26
Makefile
26
Makefile
@ -5,7 +5,7 @@
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -Wpedantic -Wshadow -O2 -I./include
|
||||
CFLAGS += -fstack-protector-strong -D_FORTIFY_SOURCE=2
|
||||
LDFLAGS = -lX11 -lXext -lXinerama -lXrandr -lXft -lfontconfig -ldbus-1 -lcurl -lm -lpthread
|
||||
LDFLAGS = -lX11 -lXext -lXinerama -lXrandr -lXft -lfontconfig -ldbus-1 -lcurl -lm -lpthread -lXtst
|
||||
|
||||
# Directories
|
||||
SRC_DIR = src
|
||||
@ -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 2>/dev/null)
|
||||
PKG_LIBS := $(shell pkg-config --libs x11 xext xinerama xrandr xft fontconfig dbus-1 2>/dev/null)
|
||||
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)
|
||||
|
||||
# Use pkg-config if available
|
||||
ifneq ($(PKG_LIBS),)
|
||||
@ -128,7 +128,7 @@ $(BIN_DIR):
|
||||
install: $(TARGET)
|
||||
@echo "Installing DWN..."
|
||||
@install -Dm755 $(TARGET) $(DESTDIR)$(BINDIR)/dwn
|
||||
@install -Dm644 scripts/dwn.desktop $(DESTDIR)$(DATADIR)/xsessions/dwn.desktop
|
||||
@install -Dm644 examples/dwn.desktop $(DESTDIR)$(DATADIR)/xsessions/dwn.desktop
|
||||
@mkdir -p $(DESTDIR)$(SYSCONFDIR)/dwn
|
||||
@install -Dm644 config/config.example $(DESTDIR)$(SYSCONFDIR)/dwn/config.example
|
||||
@echo ""
|
||||
@ -204,9 +204,15 @@ deps:
|
||||
libxinerama-dev \
|
||||
libxrandr-dev \
|
||||
libxft-dev \
|
||||
libxtst-dev \
|
||||
libfontconfig1-dev \
|
||||
libdbus-1-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libpng-dev \
|
||||
libtesseract-dev \
|
||||
libleptonica-dev \
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-eng \
|
||||
xserver-xephyr \
|
||||
dmenu; \
|
||||
echo ""; \
|
||||
@ -220,8 +226,13 @@ deps:
|
||||
libXext-devel \
|
||||
libXinerama-devel \
|
||||
libXrandr-devel \
|
||||
libXtst-devel \
|
||||
dbus-devel \
|
||||
libcurl-devel \
|
||||
libpng-devel \
|
||||
tesseract-devel \
|
||||
leptonica-devel \
|
||||
tesseract-langpack-eng \
|
||||
xorg-x11-server-Xephyr \
|
||||
dmenu; \
|
||||
echo ""; \
|
||||
@ -235,8 +246,13 @@ deps:
|
||||
libxext \
|
||||
libxinerama \
|
||||
libxrandr \
|
||||
libxtst \
|
||||
dbus \
|
||||
curl \
|
||||
libpng \
|
||||
tesseract \
|
||||
tesseract-data-eng \
|
||||
leptonica \
|
||||
xorg-server-xephyr \
|
||||
dmenu; \
|
||||
echo ""; \
|
||||
@ -247,7 +263,7 @@ deps:
|
||||
echo "Please install these packages manually:"; \
|
||||
echo " - GCC and Make"; \
|
||||
echo " - pkg-config"; \
|
||||
echo " - X11, Xext, Xinerama, Xrandr development libraries"; \
|
||||
echo " - X11, Xext, Xinerama, Xrandr, Xtst development libraries"; \
|
||||
echo " - D-Bus development library"; \
|
||||
echo " - libcurl development library"; \
|
||||
echo " - Xephyr (for testing)"; \
|
||||
|
||||
@ -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/panel.h include/api.h
|
||||
include/client.h:
|
||||
include/dwn.h:
|
||||
include/atoms.h:
|
||||
@ -48,3 +48,4 @@ include/notifications.h:
|
||||
/usr/include/dbus-1.0/dbus/dbus-threads.h:
|
||||
include/layout.h:
|
||||
include/panel.h:
|
||||
include/api.h:
|
||||
|
||||
BIN
build/client.o
BIN
build/client.o
Binary file not shown.
@ -19,7 +19,7 @@ build/keys.o: src/keys.c include/keys.h include/dwn.h include/client.h \
|
||||
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
|
||||
/usr/include/dbus-1.0/dbus/dbus-threads.h include/news.h \
|
||||
include/applauncher.h include/decorations.h include/demo.h \
|
||||
include/layout.h
|
||||
include/layout.h include/api.h
|
||||
include/keys.h:
|
||||
include/dwn.h:
|
||||
include/client.h:
|
||||
@ -51,3 +51,4 @@ include/applauncher.h:
|
||||
include/decorations.h:
|
||||
include/demo.h:
|
||||
include/layout.h:
|
||||
include/api.h:
|
||||
|
||||
BIN
build/keys.o
BIN
build/keys.o
Binary file not shown.
@ -20,7 +20,8 @@ build/main.o: src/main.c include/dwn.h include/config.h include/dwn.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/util.h
|
||||
include/services.h include/api.h include/demo.h include/screenshot.h \
|
||||
include/ocr.h include/util.h
|
||||
include/dwn.h:
|
||||
include/config.h:
|
||||
include/dwn.h:
|
||||
@ -58,4 +59,6 @@ include/autostart.h:
|
||||
include/services.h:
|
||||
include/api.h:
|
||||
include/demo.h:
|
||||
include/screenshot.h:
|
||||
include/ocr.h:
|
||||
include/util.h:
|
||||
|
||||
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
@ -17,7 +17,7 @@ build/notifications.o: src/notifications.c include/notifications.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/config.h \
|
||||
include/util.h
|
||||
include/util.h include/api.h
|
||||
include/notifications.h:
|
||||
include/dwn.h:
|
||||
/usr/include/dbus-1.0/dbus/dbus.h:
|
||||
@ -40,3 +40,4 @@ include/dwn.h:
|
||||
/usr/include/dbus-1.0/dbus/dbus-threads.h:
|
||||
include/config.h:
|
||||
include/util.h:
|
||||
include/api.h:
|
||||
|
||||
Binary file not shown.
@ -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/config.h include/panel.h include/api.h
|
||||
include/workspace.h:
|
||||
include/dwn.h:
|
||||
include/client.h:
|
||||
@ -9,3 +9,4 @@ include/atoms.h:
|
||||
include/util.h:
|
||||
include/config.h:
|
||||
include/panel.h:
|
||||
include/api.h:
|
||||
|
||||
Binary file not shown.
BIN
examples/__pycache__/dwn_automation_demo.cpython-313.pyc
Normal file
BIN
examples/__pycache__/dwn_automation_demo.cpython-313.pyc
Normal file
Binary file not shown.
@ -18,16 +18,15 @@
|
||||
<body>
|
||||
<div class="container py-5">
|
||||
<h1 class="mb-4 text-primary">DWN Remote</h1>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card p-3">
|
||||
<h3>Workspaces</h3>
|
||||
<div id="workspace-grid" class="d-flex flex-wrap">
|
||||
<!-- Workspaces injected here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card p-3">
|
||||
<h3>Quick Launch</h3>
|
||||
<div class="input-group mb-3">
|
||||
@ -35,13 +34,32 @@
|
||||
<button class="btn btn-outline-primary" onclick="launchCmd()">Run</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card p-3">
|
||||
<h3>Screenshot</h3>
|
||||
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||
<button class="btn btn-outline-info" onclick="takeScreenshot('fullscreen')">Fullscreen</button>
|
||||
<button class="btn btn-outline-info" onclick="takeScreenshot('active')">Active Window</button>
|
||||
</div>
|
||||
<div id="screenshot-container" style="display:none;">
|
||||
<img id="screenshot-img" class="img-fluid rounded" style="max-height: 300px;">
|
||||
<div class="mt-2">
|
||||
<small id="screenshot-info" class="text-muted"></small>
|
||||
<button class="btn btn-sm btn-outline-success ms-2" onclick="downloadScreenshot()">Download</button>
|
||||
<button class="btn btn-sm btn-outline-warning ms-2" onclick="runOCR()">OCR</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ocr-result" class="mt-3" style="display:none;">
|
||||
<h6>OCR Result <small class="text-muted" id="ocr-confidence"></small></h6>
|
||||
<pre class="bg-dark p-2 rounded" style="max-height:150px;overflow:auto;"><code id="ocr-text"></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="card p-3">
|
||||
<h3>Active Windows</h3>
|
||||
<div id="client-list">
|
||||
<!-- Clients injected here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -50,6 +68,7 @@
|
||||
|
||||
<script>
|
||||
let ws;
|
||||
let lastScreenshotData = null;
|
||||
const port = 8777;
|
||||
const uri = `ws://${window.location.hostname || 'localhost'}:${port}/ws`;
|
||||
|
||||
@ -61,18 +80,20 @@
|
||||
};
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
if (Array.isArray(data)) {
|
||||
if (data.format === 'png' && data.encoding === 'base64') {
|
||||
handleScreenshotResponse(data);
|
||||
} else if (data.text !== undefined && data.confidence !== undefined) {
|
||||
handleOCRResponse(data);
|
||||
} else if (Array.isArray(data)) {
|
||||
if (data.length > 0 && 'title' in data[0]) renderClients(data);
|
||||
else if (data.length > 0 && 'layout' in data[0]) renderWorkspaces(data);
|
||||
} else if (data.status === 'ok') {
|
||||
// Refresh status if needed
|
||||
}
|
||||
};
|
||||
ws.onclose = () => setTimeout(connect, 2000);
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({command: "get_workspaces"}));
|
||||
ws.send(JSON.stringify({command: "get_clients"}));
|
||||
}
|
||||
@ -122,6 +143,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
function takeScreenshot(mode) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({command: "screenshot", mode: mode}));
|
||||
}
|
||||
}
|
||||
|
||||
function handleScreenshotResponse(data) {
|
||||
if (data.status === 'ok' && data.data) {
|
||||
lastScreenshotData = data.data;
|
||||
const img = document.getElementById('screenshot-img');
|
||||
img.src = 'data:image/png;base64,' + data.data;
|
||||
document.getElementById('screenshot-container').style.display = 'block';
|
||||
document.getElementById('screenshot-info').textContent = `${data.width}x${data.height}`;
|
||||
document.getElementById('ocr-result').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function downloadScreenshot() {
|
||||
if (!lastScreenshotData) return;
|
||||
const link = document.createElement('a');
|
||||
link.href = 'data:image/png;base64,' + lastScreenshotData;
|
||||
link.download = 'screenshot.png';
|
||||
link.click();
|
||||
}
|
||||
|
||||
function runOCR() {
|
||||
if (!lastScreenshotData || !ws || ws.readyState !== WebSocket.OPEN) return;
|
||||
ws.send(JSON.stringify({command: "ocr", image: lastScreenshotData, language: "eng"}));
|
||||
}
|
||||
|
||||
function handleOCRResponse(data) {
|
||||
if (data.status === 'ok') {
|
||||
document.getElementById('ocr-result').style.display = 'block';
|
||||
document.getElementById('ocr-confidence').textContent = `(${(data.confidence * 100).toFixed(1)}% confidence)`;
|
||||
document.getElementById('ocr-text').textContent = data.text || '(No text detected)';
|
||||
}
|
||||
}
|
||||
|
||||
connect();
|
||||
setInterval(refresh, 3000);
|
||||
</script>
|
||||
@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
# retoor <retoor@molodetz.nl>
|
||||
# Example .xinitrc for starting DWN with startx
|
||||
# Copy this to ~/.xinitrc and modify as needed
|
||||
|
||||
# Set environment variables
|
||||
export XDG_SESSION_TYPE=x11
|
||||
export XDG_CURRENT_DESKTOP=DWN
|
||||
|
||||
@ -10,7 +10,6 @@ export XDG_CURRENT_DESKTOP=DWN
|
||||
# export OPENROUTER_API_KEY="your-api-key-here"
|
||||
# export EXA_API_KEY="your-exa-key-here"
|
||||
|
||||
# Optional: Start background services
|
||||
# Start D-Bus session bus if not already running
|
||||
if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
|
||||
eval $(dbus-launch --sh-syntax)
|
||||
@ -22,9 +21,6 @@ fi
|
||||
# Optional: Set wallpaper
|
||||
# feh --bg-scale ~/.wallpaper.png &
|
||||
|
||||
# Optional: Start notification daemon (DWN has built-in)
|
||||
# Note: DWN includes its own notification daemon
|
||||
|
||||
# Optional: Start XFCE components for additional functionality
|
||||
# xfce4-power-manager &
|
||||
# xfsettingsd &
|
||||
@ -38,5 +34,4 @@ fi
|
||||
# Optional: Start clipboard manager
|
||||
# xfce4-clipman &
|
||||
|
||||
# Start DWN
|
||||
exec dwn
|
||||
@ -1,16 +1,107 @@
|
||||
/*
|
||||
* retoor <retoor@molodetz.nl>
|
||||
* DWN - Desktop Window Manager
|
||||
* WebSocket API
|
||||
* WebSocket API with event subscription system
|
||||
*/
|
||||
|
||||
#ifndef DWN_API_H
|
||||
#define DWN_API_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
typedef enum {
|
||||
EVENT_WINDOW_CREATED,
|
||||
EVENT_WINDOW_DESTROYED,
|
||||
EVENT_WINDOW_FOCUSED,
|
||||
EVENT_WINDOW_UNFOCUSED,
|
||||
EVENT_WINDOW_MOVED,
|
||||
EVENT_WINDOW_RESIZED,
|
||||
EVENT_WINDOW_MINIMIZED,
|
||||
EVENT_WINDOW_RESTORED,
|
||||
EVENT_WINDOW_MAXIMIZED,
|
||||
EVENT_WINDOW_UNMAXIMIZED,
|
||||
EVENT_WINDOW_FULLSCREEN,
|
||||
EVENT_WINDOW_UNFULLSCREEN,
|
||||
EVENT_WINDOW_FLOATING,
|
||||
EVENT_WINDOW_UNFLOATING,
|
||||
EVENT_WINDOW_TITLE_CHANGED,
|
||||
EVENT_WINDOW_RAISED,
|
||||
EVENT_WINDOW_LOWERED,
|
||||
|
||||
EVENT_WORKSPACE_SWITCHED,
|
||||
EVENT_WORKSPACE_WINDOW_ADDED,
|
||||
EVENT_WORKSPACE_WINDOW_REMOVED,
|
||||
EVENT_WORKSPACE_LAYOUT_CHANGED,
|
||||
EVENT_WORKSPACE_MASTER_RATIO_CHANGED,
|
||||
EVENT_WORKSPACE_MASTER_COUNT_CHANGED,
|
||||
EVENT_WORKSPACE_ARRANGED,
|
||||
|
||||
EVENT_LAYOUT_CHANGED,
|
||||
|
||||
EVENT_KEY_PRESSED,
|
||||
EVENT_KEY_RELEASED,
|
||||
EVENT_SHORTCUT_TRIGGERED,
|
||||
|
||||
EVENT_MOUSE_MOVED,
|
||||
EVENT_MOUSE_BUTTON_PRESSED,
|
||||
EVENT_MOUSE_BUTTON_RELEASED,
|
||||
|
||||
EVENT_DRAG_STARTED,
|
||||
EVENT_DRAG_ENDED,
|
||||
|
||||
EVENT_SHOW_DESKTOP_TOGGLED,
|
||||
|
||||
EVENT_NOTIFICATION_SHOWN,
|
||||
EVENT_NOTIFICATION_CLOSED,
|
||||
|
||||
EVENT_COUNT
|
||||
} ApiEventType;
|
||||
|
||||
void api_init(void);
|
||||
void api_process(void);
|
||||
void api_cleanup(void);
|
||||
void api_handle_json_command(const char *json_str);
|
||||
|
||||
const char *api_event_name(ApiEventType type);
|
||||
|
||||
void api_emit_window_created(Window window, const char *title, const char *class_name, int workspace);
|
||||
void api_emit_window_destroyed(Window window, const char *title, int workspace);
|
||||
void api_emit_window_focused(Window window, const char *title, Window prev_window);
|
||||
void api_emit_window_unfocused(Window window, const char *title);
|
||||
void api_emit_window_moved(Window window, int old_x, int old_y, int new_x, int new_y);
|
||||
void api_emit_window_resized(Window window, int old_w, int old_h, int new_w, int new_h);
|
||||
void api_emit_window_minimized(Window window);
|
||||
void api_emit_window_restored(Window window);
|
||||
void api_emit_window_maximized(Window window, bool maximized);
|
||||
void api_emit_window_fullscreen(Window window, bool fullscreen);
|
||||
void api_emit_window_floating(Window window, bool floating);
|
||||
void api_emit_window_title_changed(Window window, const char *old_title, const char *new_title);
|
||||
void api_emit_window_raised(Window window);
|
||||
void api_emit_window_lowered(Window window);
|
||||
|
||||
void api_emit_workspace_switched(int old_workspace, int new_workspace);
|
||||
void api_emit_workspace_window_added(int workspace, Window window);
|
||||
void api_emit_workspace_window_removed(int workspace, Window window);
|
||||
void api_emit_workspace_layout_changed(int workspace, int old_layout, int new_layout);
|
||||
void api_emit_workspace_master_ratio_changed(int workspace, float old_ratio, float new_ratio);
|
||||
void api_emit_workspace_master_count_changed(int workspace, int old_count, int new_count);
|
||||
void api_emit_workspace_arranged(int workspace, int layout);
|
||||
|
||||
void api_emit_key_pressed(unsigned int keycode, unsigned int keysym, unsigned int modifiers);
|
||||
void api_emit_key_released(unsigned int keycode, unsigned int keysym, unsigned int modifiers);
|
||||
void api_emit_shortcut_triggered(const char *name, const char *description);
|
||||
|
||||
void api_emit_mouse_moved(int x, int y);
|
||||
void api_emit_mouse_button_pressed(int button, int x, int y);
|
||||
void api_emit_mouse_button_released(int button, int x, int y);
|
||||
|
||||
void api_emit_drag_started(Window window, bool is_resize);
|
||||
void api_emit_drag_ended(Window window, bool is_resize);
|
||||
|
||||
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);
|
||||
|
||||
#endif
|
||||
|
||||
70
include/ocr.h
Normal file
70
include/ocr.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* retoor <retoor@molodetz.nl>
|
||||
* OCR text extraction API
|
||||
*/
|
||||
|
||||
#ifndef DWN_OCR_H
|
||||
#define DWN_OCR_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <pthread.h>
|
||||
|
||||
typedef enum {
|
||||
OCR_OK = 0,
|
||||
OCR_ERROR_INVALID_ARG,
|
||||
OCR_ERROR_INIT,
|
||||
OCR_ERROR_IMAGE,
|
||||
OCR_ERROR_NO_MEMORY,
|
||||
OCR_ERROR_NOT_AVAILABLE
|
||||
} OcrStatus;
|
||||
|
||||
typedef enum {
|
||||
OCR_STATE_IDLE,
|
||||
OCR_STATE_PENDING,
|
||||
OCR_STATE_COMPLETED,
|
||||
OCR_STATE_ERROR
|
||||
} OcrState;
|
||||
|
||||
typedef struct {
|
||||
char *text;
|
||||
size_t length;
|
||||
float confidence;
|
||||
} OcrResult;
|
||||
|
||||
typedef struct OcrRequest {
|
||||
unsigned char *png_data;
|
||||
size_t png_size;
|
||||
char language[16];
|
||||
|
||||
OcrResult result;
|
||||
OcrStatus status;
|
||||
OcrState state;
|
||||
|
||||
void (*callback)(struct OcrRequest *req);
|
||||
void *user_data;
|
||||
|
||||
pthread_t thread;
|
||||
struct OcrRequest *next;
|
||||
} OcrRequest;
|
||||
|
||||
bool ocr_init(void);
|
||||
void ocr_cleanup(void);
|
||||
bool ocr_is_available(void);
|
||||
|
||||
OcrStatus ocr_extract_from_png(const unsigned char *png_data, size_t size,
|
||||
const char *language, OcrResult *result);
|
||||
|
||||
OcrRequest *ocr_extract_async(const unsigned char *png_data, size_t size,
|
||||
const char *language,
|
||||
void (*callback)(OcrRequest *));
|
||||
|
||||
void ocr_process_pending(void);
|
||||
void ocr_cancel(OcrRequest *req);
|
||||
|
||||
void ocr_result_free(OcrResult *result);
|
||||
|
||||
const char *ocr_status_string(OcrStatus status);
|
||||
|
||||
#endif
|
||||
84
include/screenshot.h
Normal file
84
include/screenshot.h
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* retoor <retoor@molodetz.nl>
|
||||
* Screenshot capture API
|
||||
*/
|
||||
|
||||
#ifndef DWN_SCREENSHOT_H
|
||||
#define DWN_SCREENSHOT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <pthread.h>
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
typedef enum {
|
||||
SCREENSHOT_OK = 0,
|
||||
SCREENSHOT_ERROR_INVALID_ARG,
|
||||
SCREENSHOT_ERROR_X11,
|
||||
SCREENSHOT_ERROR_PNG,
|
||||
SCREENSHOT_ERROR_NO_MEMORY,
|
||||
SCREENSHOT_ERROR_WINDOW_NOT_FOUND
|
||||
} ScreenshotStatus;
|
||||
|
||||
typedef enum {
|
||||
SCREENSHOT_STATE_IDLE,
|
||||
SCREENSHOT_STATE_PENDING,
|
||||
SCREENSHOT_STATE_COMPLETED,
|
||||
SCREENSHOT_STATE_ERROR
|
||||
} ScreenshotState;
|
||||
|
||||
typedef enum {
|
||||
SCREENSHOT_MODE_FULLSCREEN,
|
||||
SCREENSHOT_MODE_WINDOW,
|
||||
SCREENSHOT_MODE_ACTIVE,
|
||||
SCREENSHOT_MODE_AREA
|
||||
} ScreenshotMode;
|
||||
|
||||
typedef struct {
|
||||
unsigned char *data;
|
||||
size_t size;
|
||||
int width;
|
||||
int height;
|
||||
} ScreenshotResult;
|
||||
|
||||
typedef struct ScreenshotRequest {
|
||||
ScreenshotMode mode;
|
||||
Window window;
|
||||
int x, y, width, height;
|
||||
|
||||
ScreenshotResult result;
|
||||
ScreenshotStatus status;
|
||||
ScreenshotState state;
|
||||
|
||||
void (*callback)(struct ScreenshotRequest *req);
|
||||
void *user_data;
|
||||
|
||||
pthread_t thread;
|
||||
struct ScreenshotRequest *next;
|
||||
} ScreenshotRequest;
|
||||
|
||||
bool screenshot_init(void);
|
||||
void screenshot_cleanup(void);
|
||||
|
||||
ScreenshotStatus screenshot_capture_fullscreen(ScreenshotResult *result);
|
||||
ScreenshotStatus screenshot_capture_window(Window window, ScreenshotResult *result);
|
||||
ScreenshotStatus screenshot_capture_active(ScreenshotResult *result);
|
||||
ScreenshotStatus screenshot_capture_area(int x, int y, int width, int height, ScreenshotResult *result);
|
||||
|
||||
ScreenshotRequest *screenshot_capture_async(ScreenshotMode mode,
|
||||
void (*callback)(ScreenshotRequest *));
|
||||
ScreenshotRequest *screenshot_capture_window_async(Window window,
|
||||
void (*callback)(ScreenshotRequest *));
|
||||
ScreenshotRequest *screenshot_capture_area_async(int x, int y, int w, int h,
|
||||
void (*callback)(ScreenshotRequest *));
|
||||
|
||||
void screenshot_process_pending(void);
|
||||
void screenshot_cancel(ScreenshotRequest *req);
|
||||
|
||||
char *screenshot_to_base64(const ScreenshotResult *result, size_t *out_len);
|
||||
void screenshot_result_free(ScreenshotResult *result);
|
||||
|
||||
const char *screenshot_status_string(ScreenshotStatus status);
|
||||
|
||||
#endif
|
||||
338
manual/ai-features.html
Normal file
338
manual/ai-features.html
Normal file
@ -0,0 +1,338 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Integration - 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">v1.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 active">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="building.html" class="nav-link">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="content">
|
||||
<div class="page-header">
|
||||
<h1>AI Integration</h1>
|
||||
<p class="lead">Setup and usage of AI-powered features</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="#setup">Setup</a></li>
|
||||
<li><a href="#command-palette">AI Command Palette</a></li>
|
||||
<li><a href="#context-analysis">Context Analysis</a></li>
|
||||
<li><a href="#exa-search">Exa Semantic Search</a></li>
|
||||
<li><a href="#configuration">Configuration</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
<p>DWN includes optional AI integration for intelligent command execution and semantic web search. These features require API keys from external services.</p>
|
||||
|
||||
<div class="feature-grid">
|
||||
<div class="feature-card">
|
||||
<div class="feature-title">AI Command Palette</div>
|
||||
<div class="feature-desc">Natural language commands via OpenRouter API. Ask AI to launch apps, answer questions, or perform tasks.</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-title">Context Analysis</div>
|
||||
<div class="feature-desc">AI analyzes your current workspace to understand what task you're working on.</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-title">Exa Search</div>
|
||||
<div class="feature-desc">Semantic web search that understands meaning, not just keywords. Find relevant docs and tutorials.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="setup">Setup</h2>
|
||||
|
||||
<h3>OpenRouter API Key</h3>
|
||||
<p>Required for AI Command Palette and Context Analysis.</p>
|
||||
|
||||
<ol>
|
||||
<li>Visit <a href="https://openrouter.ai/keys" target="_blank">https://openrouter.ai/keys</a></li>
|
||||
<li>Create an account and generate an API key</li>
|
||||
<li>Set the key via environment variable or config file</li>
|
||||
</ol>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><code># Option 1: Environment variable (add to ~/.bashrc or ~/.zshrc)
|
||||
export OPENROUTER_API_KEY=sk-or-v1-your-key-here
|
||||
|
||||
# Option 2: Config file (~/.config/dwn/config)
|
||||
[ai]
|
||||
openrouter_api_key = sk-or-v1-your-key-here</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Exa API Key</h3>
|
||||
<p>Required for Exa Semantic Search.</p>
|
||||
|
||||
<ol>
|
||||
<li>Visit <a href="https://dashboard.exa.ai/api-keys" target="_blank">https://dashboard.exa.ai/api-keys</a></li>
|
||||
<li>Create an account and generate an API key</li>
|
||||
<li>Set the key via environment variable or config file</li>
|
||||
</ol>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><code># Option 1: Environment variable
|
||||
export EXA_API_KEY=your-exa-key-here
|
||||
|
||||
# Option 2: Config file (~/.config/dwn/config)
|
||||
[ai]
|
||||
exa_api_key = your-exa-key-here</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Note:</strong> AI features are optional. DWN functions fully without them.
|
||||
</div>
|
||||
|
||||
<h2 id="command-palette">AI Command Palette</h2>
|
||||
<p>Press <code>Super+Shift+A</code> to open the AI command palette.</p>
|
||||
|
||||
<h3>Usage</h3>
|
||||
<ol>
|
||||
<li>Press <code>Super+Shift+A</code></li>
|
||||
<li>Type a natural language command</li>
|
||||
<li>Press Enter</li>
|
||||
<li>AI interprets and executes your request</li>
|
||||
</ol>
|
||||
|
||||
<h3>Example Commands</h3>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Command</th>
|
||||
<th>Result</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>"open firefox"</td>
|
||||
<td>Launches Firefox browser</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>"launch terminal"</td>
|
||||
<td>Opens configured terminal</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>"run file manager"</td>
|
||||
<td>Opens configured file manager</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>"what time is it"</td>
|
||||
<td>Shows current time</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>"how much memory is free"</td>
|
||||
<td>Shows system memory info</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>Supported Actions</h3>
|
||||
<ul>
|
||||
<li><strong>Application launching</strong> - "open", "launch", "run", "start"</li>
|
||||
<li><strong>System queries</strong> - Time, date, system information</li>
|
||||
<li><strong>General questions</strong> - AI will provide helpful responses</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="context-analysis">Context Analysis</h2>
|
||||
<p>Press <code>Super+A</code> to see AI analysis of your current workspace.</p>
|
||||
|
||||
<h3>What It Shows</h3>
|
||||
<ul>
|
||||
<li>Detected task type (coding, browsing, communication, etc.)</li>
|
||||
<li>Currently focused window</li>
|
||||
<li>AI-generated suggestions based on context</li>
|
||||
</ul>
|
||||
|
||||
<h3>Example Output</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>Current Task: Software Development
|
||||
|
||||
Focused: Visual Studio Code - project.py
|
||||
Windows: VS Code, Terminal, Firefox (Stack Overflow)
|
||||
|
||||
Suggestions:
|
||||
- Run tests with Ctrl+Shift+T
|
||||
- Toggle terminal with Ctrl+`
|
||||
- Search documentation with Super+Shift+E</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="exa-search">Exa Semantic Search</h2>
|
||||
<p>Press <code>Super+Shift+E</code> to open semantic web search.</p>
|
||||
|
||||
<h3>How It Works</h3>
|
||||
<p>Exa uses neural search to find content based on meaning rather than keyword matching. This produces more relevant results for technical queries.</p>
|
||||
|
||||
<h3>Usage</h3>
|
||||
<ol>
|
||||
<li>Press <code>Super+Shift+E</code></li>
|
||||
<li>Type a natural language query</li>
|
||||
<li>Press Enter</li>
|
||||
<li>Results appear in a selection menu</li>
|
||||
<li>Select a result to open in browser</li>
|
||||
</ol>
|
||||
|
||||
<h3>Example Queries</h3>
|
||||
<ul>
|
||||
<li>"how to configure nginx reverse proxy"</li>
|
||||
<li>"python async await tutorial"</li>
|
||||
<li>"X11 window manager development guide"</li>
|
||||
<li>"systemd service file examples"</li>
|
||||
</ul>
|
||||
|
||||
<h3>Best Practices</h3>
|
||||
<ul>
|
||||
<li>Use natural language, not keywords</li>
|
||||
<li>Be specific about what you're looking for</li>
|
||||
<li>Include context (language, framework, etc.)</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="configuration">Configuration</h2>
|
||||
<p>AI settings in <code>~/.config/dwn/config</code>:</p>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><code>[ai]
|
||||
# AI model for OpenRouter (see https://openrouter.ai/models)
|
||||
model = google/gemini-2.0-flash-exp:free
|
||||
|
||||
# API keys (can also use environment variables)
|
||||
openrouter_api_key = sk-or-v1-your-key
|
||||
exa_api_key = your-exa-key</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Available Models</h3>
|
||||
<p>OpenRouter provides access to many AI models. Some options:</p>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Model</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>google/gemini-2.0-flash-exp:free</code></td>
|
||||
<td>Fast, free tier available (default)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>anthropic/claude-3-haiku</code></td>
|
||||
<td>Fast and efficient</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>openai/gpt-4o-mini</code></td>
|
||||
<td>Good balance of speed and quality</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>x-ai/grok-code-fast-1</code></td>
|
||||
<td>Optimized for code tasks</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>See <a href="https://openrouter.ai/models" target="_blank">OpenRouter Models</a> for the full list.</p>
|
||||
|
||||
<h2>Keyboard Shortcuts</h2>
|
||||
<div class="table-container">
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Super+A</code></td>
|
||||
<td>Show AI context analysis</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Shift+A</code></td>
|
||||
<td>Open AI command palette</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Shift+E</code></td>
|
||||
<td>Open Exa semantic search</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<h3>AI features not working</h3>
|
||||
<ol>
|
||||
<li>Verify API key is set correctly</li>
|
||||
<li>Check network connectivity</li>
|
||||
<li>Look for errors in <code>~/.local/share/dwn/dwn.log</code></li>
|
||||
</ol>
|
||||
|
||||
<h3>Slow responses</h3>
|
||||
<ul>
|
||||
<li>Try a faster model (e.g., <code>google/gemini-2.0-flash-exp:free</code>)</li>
|
||||
<li>Check network latency to API endpoints</li>
|
||||
</ul>
|
||||
|
||||
<h3>API rate limits</h3>
|
||||
<p>Free tiers have usage limits. Consider upgrading for heavy use or use a paid model.</p>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
828
manual/api-examples.html
Normal file
828
manual/api-examples.html
Normal file
@ -0,0 +1,828 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API Examples - 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">v1.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 active">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="building.html" class="nav-link">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="content">
|
||||
<div class="page-header">
|
||||
<h1>API Examples</h1>
|
||||
<p class="lead">Code examples for common automation tasks</p>
|
||||
</div>
|
||||
|
||||
<div class="toc">
|
||||
<div class="toc-title">On this page</div>
|
||||
<ul class="toc-list">
|
||||
<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="#events">Event Subscription</a></li>
|
||||
<li><a href="#automation">Automation Recipes</a></li>
|
||||
<li><a href="#browser-ocr">Browser Automation with OCR</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="python">Python Examples</h2>
|
||||
|
||||
<h3>Basic Connection</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>import json
|
||||
import websocket
|
||||
|
||||
ws = websocket.create_connection("ws://localhost:8777")
|
||||
|
||||
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']})")
|
||||
|
||||
ws.close()</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Using the Client Library</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>from dwn_api_client import DWNClient
|
||||
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
|
||||
# Get all windows
|
||||
windows = client.list_windows()
|
||||
print(f"Found {len(windows)} windows")
|
||||
|
||||
# Focus window by title
|
||||
for w in windows:
|
||||
if "Firefox" in w["title"]:
|
||||
client.focus_window(w["id"])
|
||||
break
|
||||
|
||||
# Switch to workspace 3
|
||||
client.switch_workspace(2)
|
||||
|
||||
# Type some text
|
||||
client.type_text("Hello from Python!")
|
||||
|
||||
client.disconnect()</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Screenshot and OCR</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>import base64
|
||||
from dwn_api_client import DWNClient
|
||||
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
|
||||
# Take fullscreen screenshot
|
||||
result = client.screenshot("fullscreen")
|
||||
|
||||
# Save to file
|
||||
png_data = base64.b64decode(result["data"])
|
||||
with open("screenshot.png", "wb") as f:
|
||||
f.write(png_data)
|
||||
|
||||
print(f"Saved {result['width']}x{result['height']} screenshot")
|
||||
|
||||
# Extract text with OCR
|
||||
ocr_result = client.ocr(result["data"])
|
||||
print(f"Extracted text (confidence: {ocr_result['confidence']:.0%}):")
|
||||
print(ocr_result["text"])
|
||||
|
||||
client.disconnect()</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Window 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()
|
||||
|
||||
# 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
|
||||
|
||||
if vscode:
|
||||
client.move_window(vscode["id"], 0, 32)
|
||||
client.resize_window(vscode["id"], 960, 1048)
|
||||
|
||||
if terminal:
|
||||
client.move_window(terminal["id"], 960, 32)
|
||||
client.resize_window(terminal["id"], 960, 1048)
|
||||
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
arrange_coding_setup(client)
|
||||
client.disconnect()</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Async Client</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>import asyncio
|
||||
import json
|
||||
import websockets
|
||||
|
||||
async def main():
|
||||
async with websockets.connect("ws://localhost:8777") as ws:
|
||||
# Send command
|
||||
await ws.send(json.dumps({"command": "list_windows"}))
|
||||
|
||||
# Receive response
|
||||
response = json.loads(await ws.recv())
|
||||
|
||||
for window in response["windows"]:
|
||||
print(f"Window: {window['title']}")
|
||||
|
||||
asyncio.run(main())</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="javascript">JavaScript Examples</h2>
|
||||
|
||||
<h3>Browser WebSocket</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>class DWNClient {
|
||||
constructor(url = 'ws://localhost:8777') {
|
||||
this.url = url;
|
||||
this.ws = null;
|
||||
this.pending = new Map();
|
||||
this.requestId = 0;
|
||||
}
|
||||
|
||||
connect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.ws = new WebSocket(this.url);
|
||||
this.ws.onopen = () => resolve();
|
||||
this.ws.onerror = (e) => reject(e);
|
||||
this.ws.onmessage = (e) => this.handleMessage(e);
|
||||
});
|
||||
}
|
||||
|
||||
handleMessage(event) {
|
||||
const response = JSON.parse(event.data);
|
||||
// Handle response
|
||||
console.log('Received:', response);
|
||||
}
|
||||
|
||||
send(command, params = {}) {
|
||||
const request = { command, ...params };
|
||||
this.ws.send(JSON.stringify(request));
|
||||
}
|
||||
|
||||
async listWindows() {
|
||||
this.send('list_windows');
|
||||
}
|
||||
|
||||
async focusWindow(windowId) {
|
||||
this.send('focus_window', { window: windowId });
|
||||
}
|
||||
|
||||
async typeText(text) {
|
||||
this.send('type_text', { text });
|
||||
}
|
||||
|
||||
async screenshot(mode = 'fullscreen') {
|
||||
this.send('screenshot', { mode });
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const client = new DWNClient();
|
||||
await client.connect();
|
||||
await client.listWindows();</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');
|
||||
|
||||
ws.on('open', () => {
|
||||
console.log('Connected to DWN');
|
||||
|
||||
// List windows
|
||||
ws.send(JSON.stringify({ command: 'list_windows' }));
|
||||
});
|
||||
|
||||
ws.on('message', (data) => {
|
||||
const response = JSON.parse(data);
|
||||
|
||||
if (response.windows) {
|
||||
response.windows.forEach(w => {
|
||||
console.log(`${w.title} - ${w.class}`);
|
||||
});
|
||||
}
|
||||
|
||||
ws.close();
|
||||
});
|
||||
|
||||
ws.on('error', (err) => {
|
||||
console.error('Error:', err.message);
|
||||
});</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Screenshot to Canvas</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>async function captureToCanvas(client, canvasId) {
|
||||
return new Promise((resolve) => {
|
||||
client.ws.onmessage = (event) => {
|
||||
const response = JSON.parse(event.data);
|
||||
if (response.data) {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const canvas = document.getElementById(canvasId);
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = response.width;
|
||||
canvas.height = response.height;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
resolve();
|
||||
};
|
||||
img.src = 'data:image/png;base64,' + response.data;
|
||||
}
|
||||
};
|
||||
client.send('screenshot', { mode: 'fullscreen' });
|
||||
});
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="bash">Bash Examples</h2>
|
||||
|
||||
<h3>Using websocat</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>#!/bin/bash
|
||||
|
||||
# List windows
|
||||
echo '{"command": "list_windows"}' | websocat ws://localhost:8777
|
||||
|
||||
# Focus window by ID
|
||||
echo '{"command": "focus_window", "window": 12345678}' | websocat ws://localhost:8777
|
||||
|
||||
# Switch workspace
|
||||
echo '{"command": "switch_workspace", "workspace": 2}' | websocat ws://localhost:8777
|
||||
|
||||
# Take screenshot and save
|
||||
echo '{"command": "screenshot", "mode": "fullscreen"}' | \
|
||||
websocat ws://localhost:8777 | \
|
||||
jq -r '.data' | \
|
||||
base64 -d > screenshot.png</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Using wscat</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>#!/bin/bash
|
||||
|
||||
# One-liner command
|
||||
echo '{"command": "list_windows"}' | wscat -c ws://localhost:8777 -w 1
|
||||
|
||||
# Interactive session
|
||||
wscat -c ws://localhost:8777
|
||||
# Then type commands manually</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Using curl with websocat</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>#!/bin/bash
|
||||
|
||||
dwn_command() {
|
||||
echo "$1" | websocat -n1 ws://localhost:8777
|
||||
}
|
||||
|
||||
# Get focused window
|
||||
dwn_command '{"command": "get_focused"}' | jq '.window.title'
|
||||
|
||||
# Type text
|
||||
dwn_command '{"command": "type_text", "text": "Hello!"}'
|
||||
|
||||
# Launch application
|
||||
dwn_command '{"command": "spawn", "program": "firefox"}'</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="events">Event Subscription</h2>
|
||||
|
||||
<h3>Using dwn_automation_demo.py</h3>
|
||||
<p>The included automation demo script provides ready-to-use event monitoring:</p>
|
||||
<div class="code-block">
|
||||
<pre><code># List available events
|
||||
python3 examples/dwn_automation_demo.py events --list
|
||||
|
||||
# Monitor all events (Ctrl+C to stop)
|
||||
python3 examples/dwn_automation_demo.py events --monitor
|
||||
|
||||
# Monitor specific events
|
||||
python3 examples/dwn_automation_demo.py events -e window_focused -e workspace_switched
|
||||
|
||||
# Run event demo (10 seconds)
|
||||
python3 examples/dwn_automation_demo.py demo events
|
||||
|
||||
# Full event stream demo with summary
|
||||
python3 examples/dwn_automation_demo.py demo events-all</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Python Event Listener</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>from dwn_api_client import DWNClient
|
||||
|
||||
def on_event(event):
|
||||
event_name = event.get("event", "unknown")
|
||||
data = event.get("data", {})
|
||||
|
||||
if event_name == "window_focused":
|
||||
print(f"Focus: {data.get('title')}")
|
||||
elif event_name == "workspace_switched":
|
||||
print(f"Workspace: {data.get('new_workspace') + 1}")
|
||||
elif event_name == "shortcut_triggered":
|
||||
print(f"Shortcut: {data.get('description')}")
|
||||
|
||||
return True # Continue listening
|
||||
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
|
||||
# Subscribe and listen
|
||||
client.listen_events(on_event, events=[
|
||||
"window_focused",
|
||||
"workspace_switched",
|
||||
"shortcut_triggered"
|
||||
])</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Window Activity Logger</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>import json
|
||||
from datetime import datetime
|
||||
from dwn_api_client import DWNClient
|
||||
|
||||
def activity_logger():
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
|
||||
client.subscribe(events=[
|
||||
"window_focused",
|
||||
"window_created",
|
||||
"window_destroyed"
|
||||
])
|
||||
|
||||
print("Logging window activity (Ctrl+C to stop)...")
|
||||
|
||||
try:
|
||||
with open("activity.log", "a") as f:
|
||||
while True:
|
||||
event = client.receive_event(timeout=1.0)
|
||||
if event and event.get("type") == "event":
|
||||
timestamp = datetime.now().isoformat()
|
||||
ev_name = event.get("event")
|
||||
data = event.get("data", {})
|
||||
|
||||
log_entry = {
|
||||
"time": timestamp,
|
||||
"event": ev_name,
|
||||
"data": data
|
||||
}
|
||||
f.write(json.dumps(log_entry) + "\\n")
|
||||
f.flush()
|
||||
|
||||
print(f"[{timestamp}] {ev_name}")
|
||||
except KeyboardInterrupt:
|
||||
print("\\nStopped logging")
|
||||
finally:
|
||||
client.disconnect()
|
||||
|
||||
activity_logger()</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>JavaScript Event Listener</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>const ws = new WebSocket('ws://localhost:8777');
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log('Connected to DWN');
|
||||
|
||||
// Subscribe to events
|
||||
ws.send(JSON.stringify({
|
||||
command: 'subscribe',
|
||||
events: ['window_focused', 'workspace_switched']
|
||||
}));
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
|
||||
if (msg.type === 'event') {
|
||||
console.log(`Event: ${msg.event}`, msg.data);
|
||||
|
||||
// Handle specific events
|
||||
switch (msg.event) {
|
||||
case 'window_focused':
|
||||
document.title = msg.data.title || 'DWN';
|
||||
break;
|
||||
case 'workspace_switched':
|
||||
updateWorkspaceIndicator(msg.data.new_workspace);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (err) => {
|
||||
console.error('WebSocket error:', err);
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('Disconnected from DWN');
|
||||
};</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Reactive Window Arrangement</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>from dwn_api_client import DWNClient
|
||||
|
||||
RULES = {
|
||||
"code": {"floating": False, "workspace": 0},
|
||||
"firefox": {"floating": False, "workspace": 1},
|
||||
"slack": {"floating": True, "workspace": 2},
|
||||
"telegram": {"floating": True, "workspace": 2},
|
||||
}
|
||||
|
||||
def auto_arrange():
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
|
||||
client.subscribe(events=["window_created"])
|
||||
|
||||
print("Auto-arranging windows...")
|
||||
|
||||
try:
|
||||
while True:
|
||||
event = client.receive_event(timeout=1.0)
|
||||
if event and event.get("event") == "window_created":
|
||||
data = event.get("data", {})
|
||||
window_id = data.get("window")
|
||||
wm_class = data.get("class", "").lower()
|
||||
|
||||
for pattern, rules in RULES.items():
|
||||
if pattern in wm_class:
|
||||
print(f"Applying rules to {wm_class}")
|
||||
|
||||
if "workspace" in rules:
|
||||
client.move_client_to_workspace(
|
||||
window_id, rules["workspace"]
|
||||
)
|
||||
if "floating" in rules:
|
||||
client.float_client(
|
||||
window_id, rules["floating"]
|
||||
)
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
client.disconnect()
|
||||
|
||||
auto_arrange()</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="automation">Automation Recipes</h2>
|
||||
|
||||
<h3>Auto-Arrange Windows by Class</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>from dwn_api_client import DWNClient
|
||||
|
||||
LAYOUT_RULES = {
|
||||
"code": {"workspace": 0, "floating": False},
|
||||
"firefox": {"workspace": 1, "floating": False},
|
||||
"telegram": {"workspace": 2, "floating": True},
|
||||
"slack": {"workspace": 2, "floating": True},
|
||||
}
|
||||
|
||||
def auto_arrange():
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
|
||||
windows = client.list_windows()
|
||||
for w in windows:
|
||||
win_class = w["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 w["floating"] != rules["floating"]:
|
||||
client.set_floating(w["id"], rules["floating"])
|
||||
break
|
||||
|
||||
client.disconnect()
|
||||
|
||||
auto_arrange()</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Screenshot Monitor</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>import time
|
||||
import base64
|
||||
from datetime import datetime
|
||||
from dwn_api_client import DWNClient
|
||||
|
||||
def screenshot_monitor(interval=60, output_dir="screenshots"):
|
||||
"""Take periodic screenshots"""
|
||||
import os
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
|
||||
try:
|
||||
while True:
|
||||
result = client.screenshot("fullscreen")
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"{output_dir}/screen_{timestamp}.png"
|
||||
|
||||
with open(filename, "wb") as f:
|
||||
f.write(base64.b64decode(result["data"]))
|
||||
|
||||
print(f"Saved: {filename}")
|
||||
time.sleep(interval)
|
||||
except KeyboardInterrupt:
|
||||
print("Stopped")
|
||||
finally:
|
||||
client.disconnect()
|
||||
|
||||
screenshot_monitor(interval=300) # Every 5 minutes</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Window 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"""
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
|
||||
last_focused = None
|
||||
|
||||
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)
|
||||
|
||||
if focused and focused["id"] != 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"]
|
||||
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
print("Stopped")
|
||||
finally:
|
||||
client.disconnect()
|
||||
|
||||
focus_logger()</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Keyboard Macro</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>from dwn_api_client import DWNClient
|
||||
import time
|
||||
|
||||
def run_macro(client, actions, delay=0.1):
|
||||
"""Execute a sequence of actions"""
|
||||
for action in actions:
|
||||
if action["type"] == "key":
|
||||
client.key_press(action["key"], action.get("modifiers", []))
|
||||
elif action["type"] == "type":
|
||||
client.type_text(action["text"])
|
||||
elif action["type"] == "click":
|
||||
client.mouse_click(action.get("button", 1),
|
||||
action.get("x"), action.get("y"))
|
||||
elif action["type"] == "wait":
|
||||
time.sleep(action["seconds"])
|
||||
time.sleep(delay)
|
||||
|
||||
# Example: Open terminal and run command
|
||||
macro = [
|
||||
{"type": "key", "key": "t", "modifiers": ["ctrl", "alt"]},
|
||||
{"type": "wait", "seconds": 1},
|
||||
{"type": "type", "text": "ls -la"},
|
||||
{"type": "key", "key": "Return"},
|
||||
]
|
||||
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
run_macro(client, macro)
|
||||
client.disconnect()</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>OCR Screen Reader</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>from dwn_api_client import DWNClient
|
||||
|
||||
def read_active_window():
|
||||
"""Extract and print text from active window"""
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
|
||||
# Capture active window
|
||||
screenshot = client.screenshot("active")
|
||||
|
||||
# Extract text
|
||||
ocr_result = client.ocr(screenshot["data"])
|
||||
|
||||
print(f"Text from active window (confidence: {ocr_result['confidence']:.0%}):")
|
||||
print("-" * 40)
|
||||
print(ocr_result["text"])
|
||||
|
||||
client.disconnect()
|
||||
|
||||
read_active_window()</code></pre>
|
||||
</div>
|
||||
|
||||
<h3 id="browser-ocr">Browser Automation with OCR</h3>
|
||||
<p>Complete example that opens a browser, performs a Google search, scrolls through results, and extracts text using OCR. See <code>examples/browser_ocr_demo.py</code> for the full script.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>#!/usr/bin/env python3
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
import base64
|
||||
from typing import List
|
||||
|
||||
class DWNAutomation:
|
||||
def __init__(self, host: str = "localhost", port: int = 8777):
|
||||
self.uri = f"ws://{host}:{port}/ws"
|
||||
self.ws = None
|
||||
|
||||
async def connect(self):
|
||||
import websockets
|
||||
self.ws = await websockets.connect(self.uri)
|
||||
|
||||
async def disconnect(self):
|
||||
if self.ws:
|
||||
await self.ws.close()
|
||||
|
||||
async def send_command(self, command: dict) -> dict:
|
||||
await self.ws.send(json.dumps(command))
|
||||
return json.loads(await self.ws.recv())
|
||||
|
||||
async def run_command(self, exec_cmd: str) -> dict:
|
||||
return await self.send_command({"command": "run_command", "exec": exec_cmd})
|
||||
|
||||
async def get_focused_client(self) -> dict:
|
||||
return await self.send_command({"command": "get_focused_client"})
|
||||
|
||||
async def focus_client(self, window_id: int) -> dict:
|
||||
return await self.send_command({"command": "focus_client", "window": window_id})
|
||||
|
||||
async def key_tap(self, keysym: str, modifiers: List[str] = None) -> dict:
|
||||
cmd = {"command": "key_tap", "keysym": keysym}
|
||||
if modifiers:
|
||||
cmd["modifiers"] = modifiers
|
||||
return await self.send_command(cmd)
|
||||
|
||||
async def key_type(self, text: str) -> dict:
|
||||
return await self.send_command({"command": "key_type", "text": text})
|
||||
|
||||
async def mouse_scroll(self, direction: str, amount: int = 1) -> dict:
|
||||
return await self.send_command({
|
||||
"command": "mouse_scroll", "direction": direction, "amount": amount
|
||||
})
|
||||
|
||||
async def screenshot(self, mode: str = "active") -> dict:
|
||||
return await self.send_command({"command": "screenshot", "mode": mode})
|
||||
|
||||
async def ocr(self, image_base64: str) -> dict:
|
||||
return await self.send_command({"command": "ocr", "image": image_base64})
|
||||
|
||||
|
||||
async def main():
|
||||
automation = DWNAutomation()
|
||||
await automation.connect()
|
||||
|
||||
# Open default browser with Google
|
||||
await automation.run_command("xdg-open https://www.google.nl")
|
||||
await asyncio.sleep(5.0)
|
||||
|
||||
# Get focused browser window
|
||||
result = await automation.get_focused_client()
|
||||
window_id = int(result.get("client", {}).get("window", 0))
|
||||
|
||||
# Search for something
|
||||
await automation.key_type("ponies")
|
||||
await automation.key_tap("Return")
|
||||
await asyncio.sleep(4.0)
|
||||
|
||||
# Scroll and collect OCR text from multiple pages
|
||||
all_text = []
|
||||
for i in range(4):
|
||||
# Take screenshot and run OCR
|
||||
screenshot = await automation.screenshot("active")
|
||||
image_data = screenshot.get("data", "")
|
||||
|
||||
# Save screenshot
|
||||
with open(f"screenshot_{i+1}.png", "wb") as f:
|
||||
f.write(base64.b64decode(image_data))
|
||||
|
||||
# Extract text
|
||||
ocr_result = await automation.ocr(image_data)
|
||||
text = ocr_result.get("text", "").strip()
|
||||
if text:
|
||||
all_text.append(f"--- Page {i+1} ---\n{text}")
|
||||
|
||||
# Scroll down for next page
|
||||
if i < 3:
|
||||
await automation.mouse_scroll("down", 5)
|
||||
await asyncio.sleep(1.5)
|
||||
|
||||
# Print combined results
|
||||
print("EXTRACTED TEXT:")
|
||||
print("\n\n".join(all_text))
|
||||
|
||||
await automation.disconnect()
|
||||
|
||||
asyncio.run(main())</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Run the included demo script:</p>
|
||||
<div class="code-block">
|
||||
<pre><code># Install dependency
|
||||
pip install websockets
|
||||
|
||||
# Run the demo
|
||||
python3 examples/browser_ocr_demo.py</code></pre>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
313
manual/api-overview.html
Normal file
313
manual/api-overview.html
Normal file
@ -0,0 +1,313 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API Overview - 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">v1.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 active">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="building.html" class="nav-link">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="content">
|
||||
<div class="page-header">
|
||||
<h1>API Overview</h1>
|
||||
<p class="lead">Introduction to DWN's WebSocket API</p>
|
||||
</div>
|
||||
|
||||
<div class="toc">
|
||||
<div class="toc-title">On this page</div>
|
||||
<ul class="toc-list">
|
||||
<li><a href="#introduction">Introduction</a></li>
|
||||
<li><a href="#enabling">Enabling the API</a></li>
|
||||
<li><a href="#connecting">Connecting</a></li>
|
||||
<li><a href="#protocol">Protocol</a></li>
|
||||
<li><a href="#quick-start">Quick Start</a></li>
|
||||
<li><a href="#clients">Client Libraries</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="introduction">Introduction</h2>
|
||||
<p>DWN provides a WebSocket API for full programmatic control of the window manager. This enables:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>Test Automation</strong> - Automated UI testing and scripting</li>
|
||||
<li><strong>Custom Hotkeys</strong> - Build custom keyboard shortcuts</li>
|
||||
<li><strong>Remote Control</strong> - Control DWN from other machines or devices</li>
|
||||
<li><strong>Integration</strong> - Connect DWN to other applications</li>
|
||||
<li><strong>Accessibility</strong> - Build alternative input methods</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="enabling">Enabling the API</h2>
|
||||
<p>The API is disabled by default. Enable it in <code>~/.config/dwn/config</code>:</p>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><code>[api]
|
||||
enabled = true
|
||||
port = 8777</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Restart DWN for changes to take effect.</p>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Security Note:</strong> The API listens on localhost only by default. Be cautious when exposing to network.
|
||||
</div>
|
||||
|
||||
<h2 id="connecting">Connecting</h2>
|
||||
<p>Connect via WebSocket to <code>ws://localhost:8777</code> (or your configured port).</p>
|
||||
|
||||
<h3>Testing with wscat</h3>
|
||||
<div class="code-block">
|
||||
<pre><code># Install wscat
|
||||
npm install -g wscat
|
||||
|
||||
# Connect to DWN
|
||||
wscat -c ws://localhost:8777
|
||||
|
||||
# Send a command
|
||||
> {"command": "list_windows"}
|
||||
< {"status": "ok", "windows": [...]}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Testing with websocat</h3>
|
||||
<div class="code-block">
|
||||
<pre><code># Install websocat
|
||||
cargo install websocat
|
||||
|
||||
# Connect and send command
|
||||
echo '{"command": "list_windows"}' | websocat ws://localhost:8777</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="protocol">Protocol</h2>
|
||||
|
||||
<h3>Request Format</h3>
|
||||
<p>All requests are JSON objects with a <code>command</code> field:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{
|
||||
"command": "command_name",
|
||||
"param1": "value1",
|
||||
"param2": "value2"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Response Format</h3>
|
||||
<p>Responses include a <code>status</code> field:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Success
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {...}
|
||||
}
|
||||
|
||||
// Error
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Error description"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Command Categories</h3>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Category</th>
|
||||
<th>Commands</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Windows</td>
|
||||
<td>list_windows, focus_window, close_window, move_window, resize_window</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Workspaces</td>
|
||||
<td>list_workspaces, switch_workspace, move_to_workspace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Layout</td>
|
||||
<td>get_layout, set_layout, set_master_ratio</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Keyboard</td>
|
||||
<td>key_press, key_release, type_text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mouse</td>
|
||||
<td>mouse_move, mouse_click, mouse_drag</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Screenshot</td>
|
||||
<td>screenshot</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>OCR</td>
|
||||
<td>ocr</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>System</td>
|
||||
<td>get_monitors, spawn, notify</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="quick-start">Quick Start</h2>
|
||||
|
||||
<h3>List Windows</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{"command": "list_windows"}
|
||||
|
||||
// Response
|
||||
{
|
||||
"status": "ok",
|
||||
"windows": [
|
||||
{
|
||||
"id": 12345678,
|
||||
"title": "Firefox",
|
||||
"class": "firefox",
|
||||
"workspace": 0,
|
||||
"x": 0, "y": 32,
|
||||
"width": 960, "height": 540,
|
||||
"focused": true,
|
||||
"floating": false
|
||||
}
|
||||
]
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Focus a Window</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "focus_window", "window": 12345678}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Switch Workspace</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "switch_workspace", "workspace": 2}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Type Text</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "type_text", "text": "Hello, World!"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Take Screenshot</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{"command": "screenshot", "mode": "fullscreen"}
|
||||
|
||||
// Response
|
||||
{
|
||||
"status": "ok",
|
||||
"format": "png",
|
||||
"encoding": "base64",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"data": "iVBORw0KGgo..."
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="clients">Client Libraries</h2>
|
||||
|
||||
<h3>Python Client</h3>
|
||||
<p>A Python client is included in the <code>examples/</code> directory:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>from dwn_api_client import DWNClient
|
||||
|
||||
# Connect
|
||||
client = DWNClient()
|
||||
client.connect()
|
||||
|
||||
# List windows
|
||||
windows = client.list_windows()
|
||||
|
||||
# Focus a window
|
||||
client.focus_window(windows[0]['id'])
|
||||
|
||||
# Take screenshot
|
||||
result = client.screenshot('fullscreen')
|
||||
with open('screenshot.png', 'wb') as f:
|
||||
f.write(base64.b64decode(result['data']))
|
||||
|
||||
# Disconnect
|
||||
client.disconnect()</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>JavaScript (Browser)</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>const ws = new WebSocket('ws://localhost:8777');
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.send(JSON.stringify({command: 'list_windows'}));
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const response = JSON.parse(event.data);
|
||||
console.log(response);
|
||||
};</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Web Remote</h3>
|
||||
<p>A web-based remote control interface is included at <code>examples/web_remote.html</code>. Open it in a browser to control DWN graphically.</p>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
<ul>
|
||||
<li><a href="api-reference.html">API Reference</a> - Complete command documentation</li>
|
||||
<li><a href="api-examples.html">API Examples</a> - Code examples for common tasks</li>
|
||||
</ul>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
858
manual/api-reference.html
Normal file
858
manual/api-reference.html
Normal file
@ -0,0 +1,858 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API Reference - 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">v1.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 active">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="building.html" class="nav-link">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="content">
|
||||
<div class="page-header">
|
||||
<h1>API Reference</h1>
|
||||
<p class="lead">Complete WebSocket API command reference</p>
|
||||
</div>
|
||||
|
||||
<div class="toc">
|
||||
<div class="toc-title">On this page</div>
|
||||
<ul class="toc-list">
|
||||
<li><a href="#windows">Window Commands</a></li>
|
||||
<li><a href="#workspaces">Workspace Commands</a></li>
|
||||
<li><a href="#layout">Layout Commands</a></li>
|
||||
<li><a href="#keyboard">Keyboard Commands</a></li>
|
||||
<li><a href="#mouse">Mouse Commands</a></li>
|
||||
<li><a href="#screenshot">Screenshot Commands</a></li>
|
||||
<li><a href="#ocr">OCR Commands</a></li>
|
||||
<li><a href="#events">Event Subscription</a></li>
|
||||
<li><a href="#system">System Commands</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="windows">Window Commands</h2>
|
||||
|
||||
<h3>list_windows</h3>
|
||||
<p>Get list of all managed windows.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{"command": "list_windows"}
|
||||
|
||||
// Response
|
||||
{
|
||||
"status": "ok",
|
||||
"windows": [
|
||||
{
|
||||
"id": 12345678,
|
||||
"title": "Window Title",
|
||||
"class": "window-class",
|
||||
"workspace": 0,
|
||||
"x": 0,
|
||||
"y": 32,
|
||||
"width": 960,
|
||||
"height": 540,
|
||||
"focused": true,
|
||||
"floating": false,
|
||||
"fullscreen": false,
|
||||
"maximized": false,
|
||||
"minimized": false
|
||||
}
|
||||
]
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>focus_window</h3>
|
||||
<p>Focus a window by ID.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{"command": "focus_window", "window": 12345678}
|
||||
|
||||
// Response
|
||||
{"status": "ok"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>close_window</h3>
|
||||
<p>Close a window by ID.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{"command": "close_window", "window": 12345678}
|
||||
|
||||
// Response
|
||||
{"status": "ok"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>move_window</h3>
|
||||
<p>Move a window to specified position.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{
|
||||
"command": "move_window",
|
||||
"window": 12345678,
|
||||
"x": 100,
|
||||
"y": 100
|
||||
}
|
||||
|
||||
// Response
|
||||
{"status": "ok"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>resize_window</h3>
|
||||
<p>Resize a window to specified dimensions.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{
|
||||
"command": "resize_window",
|
||||
"window": 12345678,
|
||||
"width": 800,
|
||||
"height": 600
|
||||
}
|
||||
|
||||
// Response
|
||||
{"status": "ok"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>minimize_window</h3>
|
||||
<p>Minimize a window.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "minimize_window", "window": 12345678}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>maximize_window</h3>
|
||||
<p>Maximize a window.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "maximize_window", "window": 12345678}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>restore_window</h3>
|
||||
<p>Restore a minimized or maximized window.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "restore_window", "window": 12345678}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>set_fullscreen</h3>
|
||||
<p>Set window fullscreen state.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "set_fullscreen", "window": 12345678, "fullscreen": true}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>set_floating</h3>
|
||||
<p>Set window floating state.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "set_floating", "window": 12345678, "floating": true}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>raise_window</h3>
|
||||
<p>Raise window to top of stack.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "raise_window", "window": 12345678}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>lower_window</h3>
|
||||
<p>Lower window to bottom of stack.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "lower_window", "window": 12345678}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="workspaces">Workspace Commands</h2>
|
||||
|
||||
<h3>list_workspaces</h3>
|
||||
<p>Get list of all workspaces.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{"command": "list_workspaces"}
|
||||
|
||||
// Response
|
||||
{
|
||||
"status": "ok",
|
||||
"workspaces": [
|
||||
{
|
||||
"index": 0,
|
||||
"name": "1",
|
||||
"layout": "tiling",
|
||||
"window_count": 3,
|
||||
"current": true
|
||||
}
|
||||
],
|
||||
"current": 0
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>switch_workspace</h3>
|
||||
<p>Switch to specified workspace (0-8).</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "switch_workspace", "workspace": 2}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>move_to_workspace</h3>
|
||||
<p>Move focused window to specified workspace.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "move_to_workspace", "workspace": 3}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>move_window_to_workspace</h3>
|
||||
<p>Move specific window to workspace.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{
|
||||
"command": "move_window_to_workspace",
|
||||
"window": 12345678,
|
||||
"workspace": 3
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="layout">Layout Commands</h2>
|
||||
|
||||
<h3>get_layout</h3>
|
||||
<p>Get current workspace layout.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{"command": "get_layout"}
|
||||
|
||||
// Response
|
||||
{
|
||||
"status": "ok",
|
||||
"layout": "tiling",
|
||||
"master_ratio": 0.55,
|
||||
"master_count": 1
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>set_layout</h3>
|
||||
<p>Set workspace layout mode.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Options: "tiling", "floating", "monocle"
|
||||
{"command": "set_layout", "layout": "floating"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>cycle_layout</h3>
|
||||
<p>Cycle to next layout mode.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "cycle_layout"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>set_master_ratio</h3>
|
||||
<p>Set master area ratio (0.1 to 0.9).</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "set_master_ratio", "ratio": 0.6}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>adjust_master_ratio</h3>
|
||||
<p>Adjust master ratio by delta.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "adjust_master_ratio", "delta": 0.05}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>set_master_count</h3>
|
||||
<p>Set number of master windows (1 to 10).</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "set_master_count", "count": 2}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>adjust_master_count</h3>
|
||||
<p>Adjust master count by delta.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "adjust_master_count", "delta": 1}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="keyboard">Keyboard Commands</h2>
|
||||
|
||||
<h3>key_press</h3>
|
||||
<p>Simulate key press. Supports modifiers.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Simple key
|
||||
{"command": "key_press", "key": "a"}
|
||||
|
||||
// With modifiers
|
||||
{"command": "key_press", "key": "c", "modifiers": ["ctrl"]}
|
||||
|
||||
// Special keys
|
||||
{"command": "key_press", "key": "Return"}
|
||||
{"command": "key_press", "key": "Tab"}
|
||||
{"command": "key_press", "key": "Escape"}
|
||||
{"command": "key_press", "key": "F1"}</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Available modifiers: <code>ctrl</code>, <code>shift</code>, <code>alt</code>, <code>super</code></p>
|
||||
|
||||
<h3>key_release</h3>
|
||||
<p>Simulate key release.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "key_release", "key": "a"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>type_text</h3>
|
||||
<p>Type a string of text.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "type_text", "text": "Hello, World!"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>key_combo</h3>
|
||||
<p>Press a key combination.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Ctrl+Alt+T
|
||||
{
|
||||
"command": "key_combo",
|
||||
"keys": ["ctrl", "alt", "t"]
|
||||
}
|
||||
|
||||
// Ctrl+Shift+S
|
||||
{
|
||||
"command": "key_combo",
|
||||
"keys": ["ctrl", "shift", "s"]
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="mouse">Mouse Commands</h2>
|
||||
|
||||
<h3>mouse_move</h3>
|
||||
<p>Move mouse to absolute position.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "mouse_move", "x": 500, "y": 300}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>mouse_move_relative</h3>
|
||||
<p>Move mouse relative to current position.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "mouse_move_relative", "dx": 10, "dy": -5}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>mouse_click</h3>
|
||||
<p>Click mouse button.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Left click at current position
|
||||
{"command": "mouse_click", "button": 1}
|
||||
|
||||
// Right click at position
|
||||
{"command": "mouse_click", "button": 3, "x": 100, "y": 200}
|
||||
|
||||
// Double click
|
||||
{"command": "mouse_click", "button": 1, "count": 2}</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Buttons: 1=left, 2=middle, 3=right, 4=scroll up, 5=scroll down</p>
|
||||
|
||||
<h3>mouse_down</h3>
|
||||
<p>Press mouse button without releasing.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "mouse_down", "button": 1}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>mouse_up</h3>
|
||||
<p>Release mouse button.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "mouse_up", "button": 1}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>mouse_drag</h3>
|
||||
<p>Drag from one position to another.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{
|
||||
"command": "mouse_drag",
|
||||
"from_x": 100,
|
||||
"from_y": 100,
|
||||
"to_x": 500,
|
||||
"to_y": 300,
|
||||
"button": 1
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>mouse_scroll</h3>
|
||||
<p>Scroll mouse wheel.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Scroll up
|
||||
{"command": "mouse_scroll", "direction": "up", "amount": 3}
|
||||
|
||||
// Scroll down
|
||||
{"command": "mouse_scroll", "direction": "down", "amount": 3}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="screenshot">Screenshot Commands</h2>
|
||||
|
||||
<h3>screenshot</h3>
|
||||
<p>Capture screenshot. Returns base64-encoded PNG.</p>
|
||||
|
||||
<h4>Async Mode (Default)</h4>
|
||||
<p>By default, screenshot operations run asynchronously to avoid blocking the API. Set <code>"async": false</code> for synchronous behavior.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Async (default) - returns when capture completes
|
||||
{"command": "screenshot", "mode": "fullscreen"}
|
||||
|
||||
// Synchronous - blocks until complete
|
||||
{"command": "screenshot", "mode": "fullscreen", "async": false}</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Fullscreen Mode</h4>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{"command": "screenshot", "mode": "fullscreen"}
|
||||
|
||||
// Response
|
||||
{
|
||||
"status": "ok",
|
||||
"format": "png",
|
||||
"encoding": "base64",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"data": "iVBORw0KGgoAAAANSUhEUg..."
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Active Window Mode</h4>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "screenshot", "mode": "active"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Specific Window Mode</h4>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "screenshot", "mode": "window", "window": 12345678}</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Area Mode</h4>
|
||||
<div class="code-block">
|
||||
<pre><code>{
|
||||
"command": "screenshot",
|
||||
"mode": "area",
|
||||
"x": 100,
|
||||
"y": 100,
|
||||
"width": 400,
|
||||
"height": 300
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="ocr">OCR Commands</h2>
|
||||
|
||||
<h3>ocr</h3>
|
||||
<p>Extract text from image using OCR.</p>
|
||||
|
||||
<h4>Async Mode (Default)</h4>
|
||||
<p>OCR operations run asynchronously by default since text extraction can take 1-10 seconds. Set <code>"async": false</code> for synchronous behavior.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Async (default)
|
||||
{"command": "ocr", "image": "...", "language": "eng"}
|
||||
|
||||
// Synchronous
|
||||
{"command": "ocr", "image": "...", "language": "eng", "async": false}</code></pre>
|
||||
</div>
|
||||
|
||||
<h4>Request/Response</h4>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{
|
||||
"command": "ocr",
|
||||
"image": "iVBORw0KGgoAAAANSUhEUg...", // base64 PNG
|
||||
"language": "eng" // optional, default "eng"
|
||||
}
|
||||
|
||||
// Response
|
||||
{
|
||||
"status": "ok",
|
||||
"text": "Extracted text from image...",
|
||||
"confidence": 0.95
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Common language codes: <code>eng</code> (English), <code>deu</code> (German), <code>fra</code> (French), <code>spa</code> (Spanish)</p>
|
||||
|
||||
<h2 id="events">Event Subscription</h2>
|
||||
|
||||
<p>DWN supports real-time event streaming over WebSocket. Subscribe to events to receive notifications about window actions, workspace changes, input events, and more.</p>
|
||||
|
||||
<h3>list_events</h3>
|
||||
<p>Get list of all available event types.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Request
|
||||
{"command": "list_events"}
|
||||
|
||||
// Response
|
||||
{
|
||||
"status": "ok",
|
||||
"events": [
|
||||
"window_created",
|
||||
"window_destroyed",
|
||||
"window_focused",
|
||||
"window_unfocused",
|
||||
"window_moved",
|
||||
"window_resized",
|
||||
"window_minimized",
|
||||
"window_restored",
|
||||
"window_maximized",
|
||||
"window_unmaximized",
|
||||
"window_fullscreen",
|
||||
"window_unfullscreen",
|
||||
"window_floating",
|
||||
"window_unfloating",
|
||||
"window_title_changed",
|
||||
"window_raised",
|
||||
"window_lowered",
|
||||
"workspace_switched",
|
||||
"workspace_window_added",
|
||||
"workspace_window_removed",
|
||||
"workspace_layout_changed",
|
||||
"workspace_master_ratio_changed",
|
||||
"workspace_master_count_changed",
|
||||
"workspace_arranged",
|
||||
"layout_changed",
|
||||
"key_pressed",
|
||||
"key_released",
|
||||
"shortcut_triggered",
|
||||
"mouse_moved",
|
||||
"mouse_button_pressed",
|
||||
"mouse_button_released",
|
||||
"drag_started",
|
||||
"drag_ended",
|
||||
"show_desktop_toggled",
|
||||
"notification_shown",
|
||||
"notification_closed"
|
||||
]
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>subscribe</h3>
|
||||
<p>Subscribe to specific events or all events.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Subscribe to specific events
|
||||
{"command": "subscribe", "events": ["window_focused", "window_created"]}
|
||||
|
||||
// Subscribe to all events
|
||||
{"command": "subscribe", "all": true}
|
||||
|
||||
// Response
|
||||
{
|
||||
"status": "ok",
|
||||
"subscribed": ["window_focused", "window_created"]
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>unsubscribe</h3>
|
||||
<p>Unsubscribe from specific events or all events.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Unsubscribe from specific events
|
||||
{"command": "unsubscribe", "events": ["window_focused"]}
|
||||
|
||||
// Unsubscribe from all events
|
||||
{"command": "unsubscribe", "all": true}
|
||||
|
||||
// Response
|
||||
{"status": "ok"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Event Message Format</h3>
|
||||
<p>When subscribed, events are pushed to the client as JSON messages:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Window focus event
|
||||
{
|
||||
"type": "event",
|
||||
"event": "window_focused",
|
||||
"data": {
|
||||
"window": 12345678,
|
||||
"title": "Firefox",
|
||||
"previous_window": 87654321
|
||||
}
|
||||
}
|
||||
|
||||
// Window moved event
|
||||
{
|
||||
"type": "event",
|
||||
"event": "window_moved",
|
||||
"data": {
|
||||
"window": 12345678,
|
||||
"old_x": 0,
|
||||
"old_y": 32,
|
||||
"new_x": 100,
|
||||
"new_y": 100
|
||||
}
|
||||
}
|
||||
|
||||
// Workspace switched event
|
||||
{
|
||||
"type": "event",
|
||||
"event": "workspace_switched",
|
||||
"data": {
|
||||
"old_workspace": 0,
|
||||
"new_workspace": 2
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard shortcut triggered
|
||||
{
|
||||
"type": "event",
|
||||
"event": "shortcut_triggered",
|
||||
"data": {
|
||||
"name": "Return",
|
||||
"description": "Open terminal"
|
||||
}
|
||||
}
|
||||
|
||||
// Notification shown
|
||||
{
|
||||
"type": "event",
|
||||
"event": "notification_shown",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"summary": "New Message",
|
||||
"body": "You have a new message"
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Event Types Reference</h3>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Description</th>
|
||||
<th>Data Fields</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>window_created</code></td>
|
||||
<td>New window managed</td>
|
||||
<td>window, title, class, workspace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>window_destroyed</code></td>
|
||||
<td>Window closed</td>
|
||||
<td>window, title, workspace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>window_focused</code></td>
|
||||
<td>Window received focus</td>
|
||||
<td>window, title, previous_window</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>window_unfocused</code></td>
|
||||
<td>Window lost focus</td>
|
||||
<td>window, title</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>window_moved</code></td>
|
||||
<td>Window position changed</td>
|
||||
<td>window, old_x, old_y, new_x, new_y</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>window_resized</code></td>
|
||||
<td>Window size changed</td>
|
||||
<td>window, old_width, old_height, new_width, new_height</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>window_minimized</code></td>
|
||||
<td>Window minimized</td>
|
||||
<td>window</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>window_restored</code></td>
|
||||
<td>Window restored from minimized</td>
|
||||
<td>window</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>window_maximized</code></td>
|
||||
<td>Window maximized/unmaximized</td>
|
||||
<td>window, maximized</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>window_fullscreen</code></td>
|
||||
<td>Fullscreen toggled</td>
|
||||
<td>window, fullscreen</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>window_floating</code></td>
|
||||
<td>Floating mode toggled</td>
|
||||
<td>window, floating</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>window_title_changed</code></td>
|
||||
<td>Window title updated</td>
|
||||
<td>window, old_title, new_title</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>workspace_switched</code></td>
|
||||
<td>Active workspace changed</td>
|
||||
<td>old_workspace, new_workspace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>workspace_layout_changed</code></td>
|
||||
<td>Workspace layout changed</td>
|
||||
<td>workspace, old_layout, new_layout</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>key_pressed</code></td>
|
||||
<td>Key pressed</td>
|
||||
<td>keycode, keysym, modifiers</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>shortcut_triggered</code></td>
|
||||
<td>Keyboard shortcut executed</td>
|
||||
<td>name, description</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>mouse_button_pressed</code></td>
|
||||
<td>Mouse button pressed</td>
|
||||
<td>button, x, y</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>drag_started</code></td>
|
||||
<td>Window drag/resize started</td>
|
||||
<td>window, is_resize</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>drag_ended</code></td>
|
||||
<td>Window drag/resize ended</td>
|
||||
<td>window, is_resize</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>show_desktop_toggled</code></td>
|
||||
<td>Show desktop mode toggled</td>
|
||||
<td>shown</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>notification_shown</code></td>
|
||||
<td>Notification displayed</td>
|
||||
<td>id, summary, body</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>notification_closed</code></td>
|
||||
<td>Notification closed</td>
|
||||
<td>id</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="system">System Commands</h2>
|
||||
|
||||
<h3>get_monitors</h3>
|
||||
<p>Get monitor information.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Response
|
||||
{
|
||||
"status": "ok",
|
||||
"monitors": [
|
||||
{
|
||||
"index": 0,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"primary": true
|
||||
}
|
||||
]
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>spawn</h3>
|
||||
<p>Spawn a process.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "spawn", "program": "firefox"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>notify</h3>
|
||||
<p>Show a notification.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{
|
||||
"command": "notify",
|
||||
"summary": "Title",
|
||||
"body": "Notification body text",
|
||||
"timeout": 5000
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>get_focused</h3>
|
||||
<p>Get currently focused window.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// Response
|
||||
{
|
||||
"status": "ok",
|
||||
"window": {
|
||||
"id": 12345678,
|
||||
"title": "...",
|
||||
...
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>show_desktop</h3>
|
||||
<p>Toggle show desktop mode.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "show_desktop"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>quit</h3>
|
||||
<p>Quit DWN.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{"command": "quit"}</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Error Responses</h2>
|
||||
<p>All commands may return errors:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>{
|
||||
"status": "error",
|
||||
"message": "Window not found"
|
||||
}
|
||||
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Invalid parameter: workspace must be 0-8"
|
||||
}
|
||||
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Unknown command: invalid_command"
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
510
manual/architecture.html
Normal file
510
manual/architecture.html
Normal file
@ -0,0 +1,510 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Architecture - 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">v1.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 active">Architecture</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>Architecture</h1>
|
||||
<p class="lead">Technical overview of DWN's design and implementation</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="#modules">Module Structure</a></li>
|
||||
<li><a href="#event-loop">Event Loop</a></li>
|
||||
<li><a href="#data-structures">Core Data Structures</a></li>
|
||||
<li><a href="#protocols">X11 Protocols</a></li>
|
||||
<li><a href="#design-patterns">Design Patterns</a></li>
|
||||
</ul>
|
||||
</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>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><code>┌─────────────────────────────────────────────────────────┐
|
||||
│ DWN Window Manager │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ main │ │ keys │ │ panel │ │ api │ │
|
||||
│ │ loop │ │ handler │ │ render │ │ server │ │
|
||||
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌────┴────────────┴────────────┴────────────┴────┐ │
|
||||
│ │ DWNState (Global Singleton) │ │
|
||||
│ └────┬────────────┬────────────┬────────────┬────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │
|
||||
│ │ client │ │workspace│ │ layout │ │ atoms │ │
|
||||
│ │ mgmt │ │ mgmt │ │ engine │ │ (EWMH) │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ X11 / Xlib │
|
||||
└─────────────────────────────────────────────────────────┘</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="modules">Module Structure</h2>
|
||||
<p>Each module has a header in <code>include/</code> and implementation in <code>src/</code>.</p>
|
||||
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Module</th>
|
||||
<th>Responsibility</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>main.c</code></td>
|
||||
<td>X11 initialization, event loop, signal handling</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>client.c</code></td>
|
||||
<td>Window management, focus, frame creation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>workspace.c</code></td>
|
||||
<td>9 virtual desktops, per-workspace state</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>layout.c</code></td>
|
||||
<td>Tiling, floating, monocle algorithms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>decorations.c</code></td>
|
||||
<td>Window title bars and borders</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>panel.c</code></td>
|
||||
<td>Top/bottom panels, taskbar, workspace indicators</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>systray.c</code></td>
|
||||
<td>XEmbed system tray, WiFi/audio/battery widgets</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>notifications.c</code></td>
|
||||
<td>D-Bus notification daemon</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>atoms.c</code></td>
|
||||
<td>X11 EWMH/ICCCM atom management</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>keys.c</code></td>
|
||||
<td>Keyboard shortcut capture and callbacks</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>config.c</code></td>
|
||||
<td>INI-style config loading</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>api.c</code></td>
|
||||
<td>WebSocket JSON API server</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>screenshot.c</code></td>
|
||||
<td>X11 capture + PNG encoding</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ocr.c</code></td>
|
||||
<td>Tesseract OCR integration</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ai.c</code></td>
|
||||
<td>OpenRouter API, Exa search</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>util.c</code></td>
|
||||
<td>Logging, memory, string utilities</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="event-loop">Event Loop</h2>
|
||||
<p>The main event loop uses <code>select()</code> for multiplexed I/O across X11, D-Bus, and timers.</p>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><code>void dwn_run(void) {
|
||||
int x11_fd = ConnectionNumber(dwn->display);
|
||||
int dbus_fd = /* from dbus_connection */;
|
||||
|
||||
while (dwn->running) {
|
||||
// 1. Process all pending X11 events
|
||||
while (XPending(dwn->display)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dwn->display, &ev);
|
||||
dwn_handle_event(&ev);
|
||||
}
|
||||
|
||||
// 2. Process D-Bus messages
|
||||
notifications_process_messages();
|
||||
|
||||
// 3. Process async AI/Exa requests
|
||||
ai_process_pending();
|
||||
|
||||
// 4. Check notification timeouts
|
||||
notifications_update();
|
||||
|
||||
// 5. Handle delayed focus (focus-follow mode)
|
||||
handle_pending_focus();
|
||||
|
||||
// 6. Periodic updates (animation, clock)
|
||||
if (now - last_update >= 16) { // 60fps
|
||||
news_update();
|
||||
panel_render_all();
|
||||
}
|
||||
|
||||
// 7. Wait for events with timeout
|
||||
fd_set fds;
|
||||
FD_SET(x11_fd, &fds);
|
||||
FD_SET(dbus_fd, &fds);
|
||||
struct timeval tv = {0, 16000}; // 16ms
|
||||
select(max_fd + 1, &fds, NULL, NULL, &tv);
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>X11 Event Dispatch</h3>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Handler</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>MapRequest</code></td>
|
||||
<td>New window → client_manage()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>UnmapNotify</code></td>
|
||||
<td>Window hidden → possibly client_unmanage()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>DestroyNotify</code></td>
|
||||
<td>Window destroyed → client_unmanage()</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ConfigureRequest</code></td>
|
||||
<td>Window resize request</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>PropertyNotify</code></td>
|
||||
<td>Property changed → update title</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Expose</code></td>
|
||||
<td>Repaint needed → render</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ButtonPress</code></td>
|
||||
<td>Mouse click → focus, drag, panel click</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>MotionNotify</code></td>
|
||||
<td>Mouse move → window drag/resize</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>KeyPress</code></td>
|
||||
<td>Key pressed → shortcut callback</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ClientMessage</code></td>
|
||||
<td>EWMH requests, systray dock</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="data-structures">Core Data Structures</h2>
|
||||
|
||||
<h3>DWNState</h3>
|
||||
<p>Global singleton containing all window manager state.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>typedef struct {
|
||||
Display *display; // X11 connection
|
||||
int screen; // Default screen
|
||||
Window root; // Root window
|
||||
int screen_width, screen_height;
|
||||
|
||||
Monitor monitors[MAX_MONITORS];
|
||||
int monitor_count;
|
||||
|
||||
Workspace workspaces[MAX_WORKSPACES]; // 9 workspaces
|
||||
int current_workspace;
|
||||
|
||||
Client *client_list; // Doubly-linked list head
|
||||
int client_count;
|
||||
|
||||
Panel *top_panel;
|
||||
Panel *bottom_panel;
|
||||
Config *config;
|
||||
|
||||
bool running;
|
||||
bool ai_enabled;
|
||||
|
||||
// Drag state
|
||||
Client *drag_client;
|
||||
int drag_start_x, drag_start_y;
|
||||
bool resizing;
|
||||
|
||||
// Alt-Tab state
|
||||
bool is_alt_tabbing;
|
||||
Client *alt_tab_client;
|
||||
} DWNState;</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Client</h3>
|
||||
<p>Represents a managed window with decoration frame.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>struct Client {
|
||||
Window window; // Application window
|
||||
Window frame; // Decoration frame (parent)
|
||||
int x, y, width, height; // Current geometry
|
||||
int old_x, old_y; // Saved for restore
|
||||
int old_width, old_height;
|
||||
uint32_t flags; // CLIENT_FLOATING, etc.
|
||||
unsigned int workspace; // Workspace index (0-8)
|
||||
char title[256];
|
||||
char class[64];
|
||||
SnapConstraint snap; // Snap state
|
||||
Client *next, *prev; // Global list
|
||||
Client *mru_next, *mru_prev; // MRU stack
|
||||
};</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Workspace</h3>
|
||||
<p>Per-workspace layout and window state.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>struct Workspace {
|
||||
Client *clients; // Workspace client list
|
||||
Client *focused; // Currently focused
|
||||
Client *mru_head, *mru_tail; // MRU stack
|
||||
LayoutType layout; // tiling/floating/monocle
|
||||
float master_ratio; // 0.1 to 0.9
|
||||
int master_count; // 1 to 10
|
||||
char name[32];
|
||||
};</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Client Flags</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>#define CLIENT_NORMAL 0
|
||||
#define CLIENT_FLOATING (1 << 0)
|
||||
#define CLIENT_FULLSCREEN (1 << 1)
|
||||
#define CLIENT_URGENT (1 << 2)
|
||||
#define CLIENT_MINIMIZED (1 << 3)
|
||||
#define CLIENT_STICKY (1 << 4)
|
||||
#define CLIENT_MAXIMIZED (1 << 5)
|
||||
|
||||
// Usage
|
||||
if (c->flags & CLIENT_FLOATING) { ... }
|
||||
c->flags |= CLIENT_FLOATING; // Set
|
||||
c->flags &= ~CLIENT_FLOATING; // Clear</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="protocols">X11 Protocols</h2>
|
||||
|
||||
<h3>EWMH (Extended Window Manager Hints)</h3>
|
||||
<p>Standard hints for modern window manager features.</p>
|
||||
<ul>
|
||||
<li><code>_NET_SUPPORTED</code> - List of supported atoms</li>
|
||||
<li><code>_NET_CLIENT_LIST</code> - List of managed windows</li>
|
||||
<li><code>_NET_CURRENT_DESKTOP</code> - Current workspace</li>
|
||||
<li><code>_NET_ACTIVE_WINDOW</code> - Focused window</li>
|
||||
<li><code>_NET_WM_STATE</code> - Window state (fullscreen, etc.)</li>
|
||||
<li><code>_NET_WM_WINDOW_TYPE</code> - Window type (dialog, etc.)</li>
|
||||
</ul>
|
||||
|
||||
<h3>ICCCM (Inter-Client Communication)</h3>
|
||||
<p>Core X11 window manager protocol.</p>
|
||||
<ul>
|
||||
<li><code>WM_PROTOCOLS</code> - Supported protocols (WM_DELETE_WINDOW)</li>
|
||||
<li><code>WM_NAME</code> - Window title</li>
|
||||
<li><code>WM_CLASS</code> - Application class</li>
|
||||
<li><code>WM_HINTS</code> - Input model, icons</li>
|
||||
<li><code>WM_NORMAL_HINTS</code> - Size constraints</li>
|
||||
</ul>
|
||||
|
||||
<h3>XEmbed (System Tray)</h3>
|
||||
<p>Protocol for embedding application icons in system tray.</p>
|
||||
<ul>
|
||||
<li>Acquire <code>_NET_SYSTEM_TRAY_S0</code> selection</li>
|
||||
<li>Handle <code>SYSTEM_TRAY_REQUEST_DOCK</code> messages</li>
|
||||
<li>Send <code>XEMBED_EMBEDDED_NOTIFY</code> to docked icons</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="design-patterns">Design Patterns</h2>
|
||||
|
||||
<h3>Opaque Pointers</h3>
|
||||
<p>Hide implementation details. Header exposes typedef, source defines struct.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// header.h
|
||||
typedef struct module_t* Module;
|
||||
Module module_create(void);
|
||||
void module_destroy(Module m);
|
||||
|
||||
// source.c
|
||||
struct module_t {
|
||||
int private_field;
|
||||
};</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Return Code Error Handling</h3>
|
||||
<p>Functions return status codes, pass results via output parameters.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>typedef enum {
|
||||
STATUS_OK = 0,
|
||||
STATUS_ERROR_INVALID_ARG,
|
||||
STATUS_ERROR_NO_MEMORY
|
||||
} Status;
|
||||
|
||||
Status do_work(int input, int *output);</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Goto Cleanup</h3>
|
||||
<p>Centralized resource cleanup for functions with multiple allocations.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>int process(void) {
|
||||
char *buf = NULL;
|
||||
int status = -1;
|
||||
|
||||
buf = malloc(1024);
|
||||
if (!buf) goto cleanup;
|
||||
|
||||
// ... work ...
|
||||
|
||||
status = 0;
|
||||
|
||||
cleanup:
|
||||
free(buf);
|
||||
return status;
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Module Prefix Convention</h3>
|
||||
<p>All public functions prefixed with module name.</p>
|
||||
<div class="code-block">
|
||||
<pre><code>// client.h
|
||||
void client_focus(Client *c);
|
||||
void client_move(Client *c, int x, int y);
|
||||
void client_resize(Client *c, int w, int h);
|
||||
|
||||
// workspace.h
|
||||
void workspace_switch(int index);
|
||||
void workspace_arrange(int index);</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Key Constants</h2>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Constant</th>
|
||||
<th>Value</th>
|
||||
<th>Description</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>Virtual desktops</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>MAX_MONITORS</code></td>
|
||||
<td>8</td>
|
||||
<td>Multi-monitor support</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>MAX_NOTIFICATIONS</code></td>
|
||||
<td>32</td>
|
||||
<td>Visible notifications</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>MAX_KEYBINDINGS</code></td>
|
||||
<td>64</td>
|
||||
<td>Keyboard shortcuts</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>MAX_TRAY_ICONS</code></td>
|
||||
<td>32</td>
|
||||
<td>System tray icons</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
438
manual/building.html
Normal file
438
manual/building.html
Normal file
@ -0,0 +1,438 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Building from Source - 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">v1.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="building.html" class="nav-link active">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="content">
|
||||
<div class="page-header">
|
||||
<h1>Building from Source</h1>
|
||||
<p class="lead">Compile DWN for development or custom builds</p>
|
||||
</div>
|
||||
|
||||
<div class="toc">
|
||||
<div class="toc-title">On this page</div>
|
||||
<ul class="toc-list">
|
||||
<li><a href="#requirements">Requirements</a></li>
|
||||
<li><a href="#dependencies">Dependencies</a></li>
|
||||
<li><a href="#building">Building</a></li>
|
||||
<li><a href="#targets">Make Targets</a></li>
|
||||
<li><a href="#development">Development</a></li>
|
||||
<li><a href="#troubleshooting">Troubleshooting</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="requirements">Requirements</h2>
|
||||
<ul>
|
||||
<li>Linux with X11 (Xorg)</li>
|
||||
<li>GCC compiler (or Clang)</li>
|
||||
<li>GNU Make</li>
|
||||
<li>pkg-config</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="dependencies">Dependencies</h2>
|
||||
|
||||
<h3>Required Libraries</h3>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Library</th>
|
||||
<th>Purpose</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>libX11</td>
|
||||
<td>Core X11 protocol</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>libXext</td>
|
||||
<td>X11 extensions</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>libXinerama</td>
|
||||
<td>Multi-monitor support</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>libXrandr</td>
|
||||
<td>Display configuration</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>libXft</td>
|
||||
<td>Font rendering</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>libXtst</td>
|
||||
<td>Input simulation (API)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>fontconfig</td>
|
||||
<td>Font discovery</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>libdbus-1</td>
|
||||
<td>Notifications</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>libcurl</td>
|
||||
<td>HTTP for AI features</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>libpng</td>
|
||||
<td>Screenshot encoding</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>libtesseract</td>
|
||||
<td>OCR text extraction</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>libleptonica</td>
|
||||
<td>Image processing for OCR</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>Ubuntu / Debian</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>sudo apt update && sudo apt install -y \
|
||||
build-essential \
|
||||
pkg-config \
|
||||
libx11-dev \
|
||||
libxext-dev \
|
||||
libxinerama-dev \
|
||||
libxrandr-dev \
|
||||
libxft-dev \
|
||||
libxtst-dev \
|
||||
libfontconfig1-dev \
|
||||
libdbus-1-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libpng-dev \
|
||||
libtesseract-dev \
|
||||
libleptonica-dev \
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-eng</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Fedora / RHEL</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>sudo dnf install -y \
|
||||
gcc make \
|
||||
pkg-config \
|
||||
libX11-devel \
|
||||
libXext-devel \
|
||||
libXinerama-devel \
|
||||
libXrandr-devel \
|
||||
libXtst-devel \
|
||||
dbus-devel \
|
||||
libcurl-devel \
|
||||
libpng-devel \
|
||||
tesseract-devel \
|
||||
leptonica-devel \
|
||||
tesseract-langpack-eng</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Arch Linux</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>sudo pacman -S --needed \
|
||||
base-devel \
|
||||
pkg-config \
|
||||
libx11 \
|
||||
libxext \
|
||||
libxinerama \
|
||||
libxrandr \
|
||||
libxtst \
|
||||
dbus \
|
||||
curl \
|
||||
libpng \
|
||||
tesseract \
|
||||
tesseract-data-eng \
|
||||
leptonica</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Automatic Installation</h3>
|
||||
<p>The Makefile can auto-detect your package manager:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>make deps</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="building">Building</h2>
|
||||
|
||||
<h3>Clone Repository</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>git clone https://github.com/retoor/dwn.git
|
||||
cd dwn</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Build Release</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>make</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Output: <code>bin/dwn</code></p>
|
||||
|
||||
<h3>Build Debug</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>make debug</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Includes debug symbols (<code>-g</code>) and <code>-DDEBUG</code> define.</p>
|
||||
|
||||
<h3>Build with Sanitizers</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>make sanitize</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Enables AddressSanitizer and UndefinedBehaviorSanitizer for debugging memory issues.</p>
|
||||
|
||||
<h3>Install System-wide</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>sudo make install</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Installs to:</p>
|
||||
<ul>
|
||||
<li><code>/usr/local/bin/dwn</code> - Binary</li>
|
||||
<li><code>/usr/local/share/xsessions/dwn.desktop</code> - Session file</li>
|
||||
<li><code>/etc/dwn/config.example</code> - Example config</li>
|
||||
</ul>
|
||||
|
||||
<h3>Custom Prefix</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>make PREFIX=/opt/dwn install</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="targets">Make Targets</h2>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Target</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>make</code></td>
|
||||
<td>Build release version (optimized)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>make debug</code></td>
|
||||
<td>Build with debug symbols</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>make sanitize</code></td>
|
||||
<td>Build with sanitizers</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>make clean</code></td>
|
||||
<td>Remove build artifacts</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>make install</code></td>
|
||||
<td>Install to PREFIX</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>make uninstall</code></td>
|
||||
<td>Remove installation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>make run</code></td>
|
||||
<td>Test in Xephyr</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>make deps</code></td>
|
||||
<td>Install dependencies</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>make format</code></td>
|
||||
<td>Run clang-format</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>make check</code></td>
|
||||
<td>Run cppcheck static analysis</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="development">Development</h2>
|
||||
|
||||
<h3>Testing in Xephyr</h3>
|
||||
<p>Test without affecting your current session:</p>
|
||||
<div class="code-block">
|
||||
<pre><code># Automatic (recommended)
|
||||
make run
|
||||
|
||||
# Manual
|
||||
Xephyr :1 -screen 1920x1080 &
|
||||
DISPLAY=:1 ./bin/dwn</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Code Formatting</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>make format</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Uses clang-format with project style.</p>
|
||||
|
||||
<h3>Static Analysis</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>make check</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Runs cppcheck for common issues.</p>
|
||||
|
||||
<h3>Debug Logging</h3>
|
||||
<p>Debug builds write to <code>~/.local/share/dwn/dwn.log</code>:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>tail -f ~/.local/share/dwn/dwn.log</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Directory Structure</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>dwn/
|
||||
├── include/ # Header files
|
||||
│ ├── dwn.h # Main state struct
|
||||
│ ├── client.h # Window management
|
||||
│ ├── workspace.h # Virtual desktops
|
||||
│ ├── layout.h # Layout algorithms
|
||||
│ ├── panel.h # Panels
|
||||
│ ├── api.h # WebSocket API
|
||||
│ ├── screenshot.h
|
||||
│ ├── ocr.h
|
||||
│ └── ...
|
||||
├── src/ # Implementation
|
||||
│ ├── main.c # Entry point, event loop
|
||||
│ ├── client.c
|
||||
│ ├── workspace.c
|
||||
│ ├── layout.c
|
||||
│ ├── panel.c
|
||||
│ ├── api.c
|
||||
│ ├── screenshot.c
|
||||
│ ├── ocr.c
|
||||
│ └── ...
|
||||
├── build/ # Object files
|
||||
├── bin/ # Output binary
|
||||
├── examples/ # Client examples
|
||||
├── manual/ # Documentation
|
||||
├── Makefile
|
||||
└── CLAUDE.md # Development guide</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Adding a New Module</h3>
|
||||
<ol>
|
||||
<li>Create <code>include/newmodule.h</code> with public API</li>
|
||||
<li>Create <code>src/newmodule.c</code> with implementation</li>
|
||||
<li>Add <code>newmodule_init()</code> call to <code>main.c:dwn_init()</code></li>
|
||||
<li>Add <code>newmodule_cleanup()</code> call to <code>main.c:dwn_cleanup()</code></li>
|
||||
<li>Makefile automatically picks up new .c files</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="troubleshooting">Troubleshooting</h2>
|
||||
|
||||
<h3>Missing pkg-config</h3>
|
||||
<div class="code-block">
|
||||
<pre><code># Check if library is found
|
||||
pkg-config --cflags --libs x11
|
||||
|
||||
# If not found, install dev package
|
||||
sudo apt install libx11-dev # Debian/Ubuntu</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Tesseract not found</h3>
|
||||
<div class="code-block">
|
||||
<pre><code># Check installation
|
||||
pkg-config --cflags --libs tesseract
|
||||
|
||||
# Install if missing
|
||||
sudo apt install libtesseract-dev tesseract-ocr tesseract-ocr-eng</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Linker errors</h3>
|
||||
<p>Ensure all -dev packages are installed. The Makefile uses pkg-config to find libraries.</p>
|
||||
|
||||
<h3>Runtime errors</h3>
|
||||
<ol>
|
||||
<li>Check log file: <code>~/.local/share/dwn/dwn.log</code></li>
|
||||
<li>Build with debug: <code>make clean && make debug</code></li>
|
||||
<li>Build with sanitizers: <code>make clean && make sanitize</code></li>
|
||||
<li>Run in GDB: <code>DISPLAY=:1 gdb ./bin/dwn</code></li>
|
||||
</ol>
|
||||
|
||||
<h3>X11 errors</h3>
|
||||
<p>Run with synchronous X11 for detailed errors:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>DISPLAY=:1 ./bin/dwn --sync</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Xephyr not starting</h3>
|
||||
<div class="code-block">
|
||||
<pre><code># Check if display :1 is in use
|
||||
ls /tmp/.X11-unix/
|
||||
|
||||
# Use different display
|
||||
Xephyr :2 -screen 1920x1080 &
|
||||
DISPLAY=:2 ./bin/dwn</code></pre>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
389
manual/configuration.html
Normal file
389
manual/configuration.html
Normal file
@ -0,0 +1,389 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Configuration - 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">v1.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="building.html" class="nav-link">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="content">
|
||||
<div class="page-header">
|
||||
<h1>Configuration</h1>
|
||||
<p class="lead">Customize DWN to your preferences</p>
|
||||
</div>
|
||||
|
||||
<h2>Configuration File</h2>
|
||||
<p>DWN uses an INI-style configuration file located at:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>~/.config/dwn/config</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Create it from the example:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>mkdir -p ~/.config/dwn
|
||||
cp /etc/dwn/config.example ~/.config/dwn/config</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Configuration Sections</h2>
|
||||
|
||||
<h3>[general] - Core Settings</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>[general]
|
||||
terminal = xfce4-terminal
|
||||
launcher = dmenu_run
|
||||
file_manager = thunar
|
||||
focus_mode = click
|
||||
focus_follow_delay = 100
|
||||
decorations = true</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>terminal</code></td>
|
||||
<td>xfce4-terminal</td>
|
||||
<td>Terminal emulator command</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>launcher</code></td>
|
||||
<td>dmenu_run</td>
|
||||
<td>Application launcher command</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_manager</code></td>
|
||||
<td>thunar</td>
|
||||
<td>File manager command</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>focus_mode</code></td>
|
||||
<td>click</td>
|
||||
<td><code>click</code> or <code>follow</code> (sloppy focus)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>focus_follow_delay</code></td>
|
||||
<td>100</td>
|
||||
<td>Delay in ms for focus-follow mode (0-1000)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>decorations</code></td>
|
||||
<td>true</td>
|
||||
<td>Show window decorations (title bar)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>[appearance] - Visual Settings</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>[appearance]
|
||||
border_width = 0
|
||||
title_height = 28
|
||||
panel_height = 32
|
||||
gap = 0
|
||||
font = fixed</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th>Range</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>border_width</code></td>
|
||||
<td>0-50</td>
|
||||
<td>Window border width in pixels</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>title_height</code></td>
|
||||
<td>0-100</td>
|
||||
<td>Title bar height in pixels</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>panel_height</code></td>
|
||||
<td>0-100</td>
|
||||
<td>Panel height in pixels</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>gap</code></td>
|
||||
<td>0-100</td>
|
||||
<td>Gap between windows in pixels</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>font</code></td>
|
||||
<td>-</td>
|
||||
<td>X11 font name</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>[layout] - Layout Behavior</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>[layout]
|
||||
default = tiling
|
||||
master_ratio = 0.55
|
||||
master_count = 1</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th>Values</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>default</code></td>
|
||||
<td>tiling, floating, monocle</td>
|
||||
<td>Default layout for new workspaces</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>master_ratio</code></td>
|
||||
<td>0.1-0.9</td>
|
||||
<td>Master area ratio in tiling layout</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>master_count</code></td>
|
||||
<td>1-10</td>
|
||||
<td>Number of windows in master area</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>[panels] - Panel Visibility</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>[panels]
|
||||
top = true
|
||||
bottom = true</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>[colors] - Color Scheme</h3>
|
||||
<p>Colors are specified in hex format <code>#RRGGBB</code>.</p>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><code>[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</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>[api] - WebSocket API</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>[api]
|
||||
enabled = true
|
||||
port = 8777</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>enabled</code></td>
|
||||
<td>false</td>
|
||||
<td>Enable WebSocket API server</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>port</code></td>
|
||||
<td>8777</td>
|
||||
<td>API server port number</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>[ai] - AI Integration</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>[ai]
|
||||
model = google/gemini-2.0-flash-exp:free
|
||||
openrouter_api_key = sk-or-v1-your-key
|
||||
exa_api_key = your-exa-key</code></pre>
|
||||
</div>
|
||||
|
||||
<p>API keys can also be set via environment variables:</p>
|
||||
<ul>
|
||||
<li><code>OPENROUTER_API_KEY</code></li>
|
||||
<li><code>EXA_API_KEY</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>[autostart] - XDG Autostart</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>[autostart]
|
||||
enabled = true
|
||||
xdg_autostart = true
|
||||
path = ~/.config/dwn/autostart.d</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Directories scanned when <code>xdg_autostart = true</code>:</p>
|
||||
<ul>
|
||||
<li><code>/etc/xdg/autostart/*.desktop</code> - System defaults</li>
|
||||
<li><code>~/.config/autostart/*.desktop</code> - User autostart</li>
|
||||
<li><code>~/.config/dwn/autostart.d/*</code> - DWN-specific scripts</li>
|
||||
</ul>
|
||||
|
||||
<h3>[demo] - Demo Mode Timing</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>[demo]
|
||||
step_delay = 4000
|
||||
ai_timeout = 15000
|
||||
window_timeout = 5000</code></pre>
|
||||
</div>
|
||||
|
||||
<h2>Environment Variables</h2>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>OPENROUTER_API_KEY</code></td>
|
||||
<td>API key for AI command palette</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>EXA_API_KEY</code></td>
|
||||
<td>API key for Exa semantic search</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>DISPLAY</code></td>
|
||||
<td>X server to connect to</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Example Configuration</h2>
|
||||
<div class="code-block">
|
||||
<pre><code>[general]
|
||||
terminal = alacritty
|
||||
launcher = rofi -show drun
|
||||
file_manager = nautilus
|
||||
focus_mode = click
|
||||
decorations = true
|
||||
|
||||
[appearance]
|
||||
border_width = 2
|
||||
title_height = 24
|
||||
panel_height = 28
|
||||
gap = 5
|
||||
font = monospace
|
||||
|
||||
[layout]
|
||||
default = tiling
|
||||
master_ratio = 0.55
|
||||
master_count = 1
|
||||
|
||||
[panels]
|
||||
top = true
|
||||
bottom = false
|
||||
|
||||
[colors]
|
||||
panel_bg = #282c34
|
||||
panel_fg = #abb2bf
|
||||
workspace_active = #61afef
|
||||
workspace_inactive = #3e4451
|
||||
border_focused = #61afef
|
||||
border_unfocused = #3e4451
|
||||
|
||||
[api]
|
||||
enabled = true
|
||||
port = 8777
|
||||
|
||||
[autostart]
|
||||
enabled = true
|
||||
xdg_autostart = true</code></pre>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
611
manual/css/style.css
Normal file
611
manual/css/style.css
Normal file
@ -0,0 +1,611 @@
|
||||
/* retoor <retoor@molodetz.nl> */
|
||||
/* DWN Documentation Styles */
|
||||
|
||||
:root {
|
||||
--bg-primary: #1a1a2e;
|
||||
--bg-secondary: #16213e;
|
||||
--bg-tertiary: #0f3460;
|
||||
--bg-code: #0d1117;
|
||||
--text-primary: #e0e0e0;
|
||||
--text-secondary: #a0a0a0;
|
||||
--text-muted: #666;
|
||||
--accent: #4a90d9;
|
||||
--accent-hover: #5da0e9;
|
||||
--border-color: #2d3748;
|
||||
--success: #48bb78;
|
||||
--warning: #ed8936;
|
||||
--error: #f56565;
|
||||
--sidebar-width: 280px;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--accent-hover);
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: var(--sidebar-width);
|
||||
background: var(--bg-secondary);
|
||||
border-right: 1px solid var(--border-color);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.sidebar-header h1 {
|
||||
font-size: 1.5rem;
|
||||
color: var(--accent);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.sidebar-header .version {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.nav-section-title {
|
||||
padding: 8px 20px;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: block;
|
||||
padding: 8px 20px 8px 30px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--accent);
|
||||
border-left-color: var(--accent);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
margin-left: var(--sidebar-width);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 40px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.page-header .lead {
|
||||
font-size: 1.2rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.75rem;
|
||||
margin: 40px 0 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.35rem;
|
||||
margin: 30px 0 15px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.1rem;
|
||||
margin: 20px 0 10px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 0 0 20px 25px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
||||
background: var(--bg-code);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--bg-code);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
overflow-x: auto;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
th {
|
||||
background: var(--bg-secondary);
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 20px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
transition: transform 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.shortcut-table code {
|
||||
background: var(--bg-tertiary);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.badge-get { background: var(--success); color: #000; }
|
||||
.badge-post { background: var(--accent); color: #fff; }
|
||||
.badge-required { background: var(--error); color: #fff; }
|
||||
.badge-optional { background: var(--text-muted); color: #fff; }
|
||||
|
||||
.method-block {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
margin: 25px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.method-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 15px 20px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.method-name {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.method-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.param-table {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.param-table th {
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.example-tabs {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.example-tab {
|
||||
padding: 8px 16px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-bottom: none;
|
||||
border-radius: 6px 6px 0 0;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.example-tab:hover,
|
||||
.example-tab.active {
|
||||
background: var(--bg-code);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.example-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.example-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background: rgba(74, 144, 217, 0.1);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background: rgba(237, 137, 54, 0.1);
|
||||
border-color: var(--warning);
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: rgba(72, 187, 120, 0.1);
|
||||
border-color: var(--success);
|
||||
}
|
||||
|
||||
.toc {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.toc-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.toc-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toc-list li {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.toc-list a {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 5px 10px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 60px;
|
||||
padding: 30px 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.mobile-menu-btn {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
z-index: 200;
|
||||
padding: 10px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.mobile-menu-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hero .tagline {
|
||||
font-size: 1.3rem;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: var(--accent-hover);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
border: 2px solid var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.quick-start {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
margin: 40px 0;
|
||||
}
|
||||
|
||||
.quick-start h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.step-num {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background: var(--accent);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.anchor {
|
||||
color: var(--text-muted);
|
||||
margin-left: 8px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
h2:hover .anchor,
|
||||
h3:hover .anchor {
|
||||
opacity: 1;
|
||||
}
|
||||
298
manual/features.html
Normal file
298
manual/features.html
Normal file
@ -0,0 +1,298 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Features - 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">v1.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 active">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="building.html" class="nav-link">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="content">
|
||||
<div class="page-header">
|
||||
<h1>Features</h1>
|
||||
<p class="lead">Comprehensive overview of DWN capabilities</p>
|
||||
</div>
|
||||
|
||||
<div class="toc">
|
||||
<div class="toc-title">On this page</div>
|
||||
<ul class="toc-list">
|
||||
<li><a href="#window-management">Window Management</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>
|
||||
<li><a href="#notifications">Notifications</a></li>
|
||||
<li><a href="#screenshot-ocr">Screenshot & OCR</a></li>
|
||||
<li><a href="#ai">AI Integration</a></li>
|
||||
<li><a href="#api">WebSocket API</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="window-management">Window Management</h2>
|
||||
<p>DWN provides comprehensive window management with both manual and automatic control.</p>
|
||||
|
||||
<h3>Window States</h3>
|
||||
<ul>
|
||||
<li><strong>Normal</strong> - Standard window state, subject to layout rules</li>
|
||||
<li><strong>Floating</strong> - Exempt from tiling, freely movable and resizable</li>
|
||||
<li><strong>Maximized</strong> - Fills usable area with decorations</li>
|
||||
<li><strong>Fullscreen</strong> - Fills entire screen, no decorations or panels</li>
|
||||
<li><strong>Minimized</strong> - Hidden from view, accessible via taskbar</li>
|
||||
</ul>
|
||||
|
||||
<h3>Window Snapping</h3>
|
||||
<p>Snap windows to screen edges with <code>Super+Arrow</code> keys. Snapping is composable:</p>
|
||||
<ul>
|
||||
<li><code>Super+Left</code> - Left half (press twice for full width)</li>
|
||||
<li><code>Super+Right</code> - Right half</li>
|
||||
<li><code>Super+Up</code> - Top half</li>
|
||||
<li><code>Super+Down</code> - Bottom half</li>
|
||||
<li>Combine for quarter-screen: <code>Super+Left</code> then <code>Super+Up</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Focus Modes</h3>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mode</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>click</code></td>
|
||||
<td>Focus window on mouse click (default)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>follow</code></td>
|
||||
<td>Focus follows mouse pointer with configurable delay</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
<h2 id="workspaces">Virtual Workspaces</h2>
|
||||
<p>DWN provides 9 virtual workspaces for organizing your windows.</p>
|
||||
|
||||
<div class="feature-grid">
|
||||
<div class="feature-card">
|
||||
<div class="feature-title">Independent Layouts</div>
|
||||
<div class="feature-desc">Each workspace maintains its own layout mode, master ratio, and master count.</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-title">Quick Switching</div>
|
||||
<div class="feature-desc">Switch with F1-F9, or Ctrl+Alt+Left/Right for sequential navigation.</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-title">Window Movement</div>
|
||||
<div class="feature-desc">Move windows between workspaces with Shift+F1-F9.</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-title">Workspace Indicator</div>
|
||||
<div class="feature-desc">Panel shows which workspaces have windows and current workspace.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="layouts">Layout System</h2>
|
||||
<p>Three 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>
|
||||
<ul>
|
||||
<li><code>Super+H/L</code> - Adjust master area size</li>
|
||||
<li><code>Super+I/D</code> - Adjust master window count</li>
|
||||
<li>Default master ratio: 55%</li>
|
||||
</ul>
|
||||
|
||||
<h3>Floating Layout</h3>
|
||||
<p>Traditional floating window management. Drag title bars to move, drag edges to resize.</p>
|
||||
|
||||
<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>
|
||||
|
||||
<h2 id="panels">Panels & System Tray</h2>
|
||||
|
||||
<h3>Top Panel</h3>
|
||||
<p>Contains workspace indicators, taskbar, layout indicator, and system tray.</p>
|
||||
|
||||
<h3>Bottom Panel</h3>
|
||||
<p>Contains clock and news ticker (when configured).</p>
|
||||
|
||||
<h3>System Tray</h3>
|
||||
<p>Full XEmbed protocol support for external application icons plus built-in widgets:</p>
|
||||
<ul>
|
||||
<li><strong>Battery</strong> - Percentage display with charging indicator</li>
|
||||
<li><strong>Volume</strong> - Click for slider, scroll to adjust, right-click to mute</li>
|
||||
<li><strong>WiFi</strong> - SSID display, click for network list</li>
|
||||
<li><strong>External Icons</strong> - nm-applet, blueman, Telegram, etc.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="notifications">Notifications</h2>
|
||||
<p>DWN includes a built-in D-Bus notification daemon implementing the freedesktop.org specification.</p>
|
||||
|
||||
<ul>
|
||||
<li>Automatic service registration on startup</li>
|
||||
<li>Configurable colors and timeout</li>
|
||||
<li>Stacking notification display</li>
|
||||
<li>Urgency level support (low, normal, critical)</li>
|
||||
</ul>
|
||||
|
||||
<p>No external notification daemon required.</p>
|
||||
|
||||
<h2 id="screenshot-ocr">Screenshot & OCR</h2>
|
||||
<p>Built-in screenshot capture and OCR text extraction, accessible via the WebSocket API.</p>
|
||||
|
||||
<h3>Screenshot Modes</h3>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mode</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>fullscreen</code></td>
|
||||
<td>Capture entire screen</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>window</code></td>
|
||||
<td>Capture specific window by ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>active</code></td>
|
||||
<td>Capture currently focused window</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>area</code></td>
|
||||
<td>Capture arbitrary rectangle</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>OCR Text Extraction</h3>
|
||||
<p>Extract text from screenshots using Tesseract OCR engine:</p>
|
||||
<ul>
|
||||
<li>Multi-language support (English by default)</li>
|
||||
<li>Confidence score reporting</li>
|
||||
<li>Useful for automation and accessibility</li>
|
||||
</ul>
|
||||
|
||||
<p>See <a href="api-reference.html">API Reference</a> for usage details.</p>
|
||||
|
||||
<h2 id="ai">AI Integration</h2>
|
||||
<p>Optional AI features powered by OpenRouter API:</p>
|
||||
|
||||
<h3>AI Command Palette</h3>
|
||||
<p>Natural language command input (<code>Super+Shift+A</code>). Ask the AI to:</p>
|
||||
<ul>
|
||||
<li>Launch applications ("open firefox")</li>
|
||||
<li>Answer questions</li>
|
||||
<li>Get system information</li>
|
||||
</ul>
|
||||
|
||||
<h3>AI Context Analysis</h3>
|
||||
<p>Press <code>Super+A</code> to see AI analysis of your current task based on open windows.</p>
|
||||
|
||||
<h3>Exa Semantic Search</h3>
|
||||
<p>Web search with semantic understanding (<code>Super+Shift+E</code>). Find documentation, tutorials, and resources.</p>
|
||||
|
||||
<p>See <a href="ai-features.html">AI Integration</a> for setup instructions.</p>
|
||||
|
||||
<h2 id="api">WebSocket API</h2>
|
||||
<p>Full programmatic control via JSON WebSocket API on configurable port (default 8777).</p>
|
||||
|
||||
<h3>Capabilities</h3>
|
||||
<ul>
|
||||
<li>Window management (list, focus, move, resize, close)</li>
|
||||
<li>Workspace control (switch, move windows)</li>
|
||||
<li>Layout management (mode, master ratio)</li>
|
||||
<li>Keyboard simulation (key presses, typing)</li>
|
||||
<li>Mouse simulation (move, click, drag)</li>
|
||||
<li>Screenshot capture and OCR</li>
|
||||
<li>System queries (windows, workspaces, monitors)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Use Cases</h3>
|
||||
<ul>
|
||||
<li>Test automation</li>
|
||||
<li>Custom hotkey scripts</li>
|
||||
<li>Remote desktop control</li>
|
||||
<li>Accessibility tools</li>
|
||||
<li>Integration with other applications</li>
|
||||
</ul>
|
||||
|
||||
<p>See <a href="api-overview.html">API Overview</a> for getting started.</p>
|
||||
|
||||
<h2>Protocol Compliance</h2>
|
||||
<p>DWN implements standard X11 protocols for compatibility:</p>
|
||||
<ul>
|
||||
<li><strong>EWMH</strong> - Extended Window Manager Hints</li>
|
||||
<li><strong>ICCCM</strong> - Inter-Client Communication Conventions</li>
|
||||
<li><strong>XEmbed</strong> - System tray embedding protocol</li>
|
||||
<li><strong>Xinerama</strong> - Multi-monitor support</li>
|
||||
</ul>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
168
manual/index.html
Normal file
168
manual/index.html
Normal file
@ -0,0 +1,168 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DWN - Desktop Window Manager 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">v1.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 active">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="building.html" class="nav-link">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="content">
|
||||
<div class="hero">
|
||||
<h1>DWN</h1>
|
||||
<p class="tagline">A modern X11 window manager with AI integration and WebSocket API</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-grid">
|
||||
<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>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🖥️</div>
|
||||
<div class="feature-title">9 Workspaces</div>
|
||||
<div class="feature-desc">Virtual desktops with independent layout settings. Quick switching with F1-F9 keys.</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🤖</div>
|
||||
<div class="feature-title">AI Integration</div>
|
||||
<div class="feature-desc">OpenRouter API for intelligent command palette and Exa semantic search integration.</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🔌</div>
|
||||
<div class="feature-title">WebSocket API</div>
|
||||
<div class="feature-desc">Full programmatic control via JSON WebSocket API. Automate everything.</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">📸</div>
|
||||
<div class="feature-title">Screenshot & OCR</div>
|
||||
<div class="feature-desc">Capture screenshots and extract text with Tesseract OCR, all via API.</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🔔</div>
|
||||
<div class="feature-title">Notifications</div>
|
||||
<div class="feature-desc">Built-in D-Bus notification daemon with configurable appearance.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quick-start">
|
||||
<h3>Quick Start</h3>
|
||||
<div class="step">
|
||||
<div class="step-num">1</div>
|
||||
<div class="step-content">
|
||||
<strong>Install dependencies</strong>
|
||||
<pre><code>make deps</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-num">2</div>
|
||||
<div class="step-content">
|
||||
<strong>Build DWN</strong>
|
||||
<pre><code>make</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-num">3</div>
|
||||
<div class="step-content">
|
||||
<strong>Test in Xephyr</strong>
|
||||
<pre><code>make run</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-num">4</div>
|
||||
<div class="step-content">
|
||||
<strong>Install system-wide</strong>
|
||||
<pre><code>sudo make install</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Key Features</h2>
|
||||
|
||||
<h3>Window Management</h3>
|
||||
<ul>
|
||||
<li>Three layout modes: tiling, floating, and monocle</li>
|
||||
<li>Window snapping with Super+Arrow keys</li>
|
||||
<li>Alt-Tab window cycling with MRU (Most Recently Used) stack</li>
|
||||
<li>EWMH/ICCCM protocol compliance</li>
|
||||
<li>Multi-monitor support via Xinerama</li>
|
||||
</ul>
|
||||
|
||||
<h3>Automation API</h3>
|
||||
<ul>
|
||||
<li>WebSocket JSON API on configurable port</li>
|
||||
<li>Keyboard and mouse simulation</li>
|
||||
<li>Window management and layout control</li>
|
||||
<li>Screenshot capture and OCR text extraction</li>
|
||||
<li>Python client library included</li>
|
||||
</ul>
|
||||
|
||||
<h3>System Integration</h3>
|
||||
<ul>
|
||||
<li>XDG autostart support</li>
|
||||
<li>System tray with XEmbed protocol</li>
|
||||
<li>D-Bus notification daemon</li>
|
||||
<li>Configurable panels (top and bottom)</li>
|
||||
</ul>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
240
manual/installation.html
Normal file
240
manual/installation.html
Normal file
@ -0,0 +1,240 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Installation - 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">v1.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="building.html" class="nav-link">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="content">
|
||||
<div class="page-header">
|
||||
<h1>Installation</h1>
|
||||
<p class="lead">Install DWN on your Linux system</p>
|
||||
</div>
|
||||
|
||||
<div class="toc">
|
||||
<div class="toc-title">On this page</div>
|
||||
<ul class="toc-list">
|
||||
<li><a href="#requirements">Requirements</a></li>
|
||||
<li><a href="#dependencies">Dependencies</a></li>
|
||||
<li><a href="#building">Building</a></li>
|
||||
<li><a href="#installing">Installing</a></li>
|
||||
<li><a href="#testing">Testing</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="requirements">Requirements</h2>
|
||||
<ul>
|
||||
<li>Linux with X11 (Xorg)</li>
|
||||
<li>GCC compiler</li>
|
||||
<li>Make build system</li>
|
||||
<li>pkg-config</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="dependencies">Dependencies</h2>
|
||||
|
||||
<h3>Ubuntu / Debian</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>sudo apt update && sudo apt install -y \
|
||||
build-essential \
|
||||
pkg-config \
|
||||
libx11-dev \
|
||||
libxext-dev \
|
||||
libxinerama-dev \
|
||||
libxrandr-dev \
|
||||
libxft-dev \
|
||||
libxtst-dev \
|
||||
libfontconfig1-dev \
|
||||
libdbus-1-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libpng-dev \
|
||||
libtesseract-dev \
|
||||
libleptonica-dev \
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-eng \
|
||||
xserver-xephyr \
|
||||
dmenu</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Fedora / RHEL</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>sudo dnf install -y \
|
||||
gcc make \
|
||||
pkg-config \
|
||||
libX11-devel \
|
||||
libXext-devel \
|
||||
libXinerama-devel \
|
||||
libXrandr-devel \
|
||||
libXtst-devel \
|
||||
dbus-devel \
|
||||
libcurl-devel \
|
||||
libpng-devel \
|
||||
tesseract-devel \
|
||||
leptonica-devel \
|
||||
tesseract-langpack-eng \
|
||||
xorg-x11-server-Xephyr \
|
||||
dmenu</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Arch Linux</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>sudo pacman -S --needed \
|
||||
base-devel \
|
||||
pkg-config \
|
||||
libx11 \
|
||||
libxext \
|
||||
libxinerama \
|
||||
libxrandr \
|
||||
libxtst \
|
||||
dbus \
|
||||
curl \
|
||||
libpng \
|
||||
tesseract \
|
||||
tesseract-data-eng \
|
||||
leptonica \
|
||||
xorg-server-xephyr \
|
||||
dmenu</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Automatic Installation</h3>
|
||||
<p>The Makefile can automatically detect your package manager and install dependencies:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>make deps</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="building">Building</h2>
|
||||
|
||||
<h3>Clone the Repository</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>git clone https://github.com/retoor/dwn.git
|
||||
cd dwn</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Build Release Version</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>make</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Build Debug Version</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>make debug</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Build with Sanitizers</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>make sanitize</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="installing">Installing</h2>
|
||||
|
||||
<h3>System-wide Installation</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>sudo make install</code></pre>
|
||||
</div>
|
||||
|
||||
<p>This installs:</p>
|
||||
<ul>
|
||||
<li><code>/usr/local/bin/dwn</code> - The window manager binary</li>
|
||||
<li><code>/usr/local/share/xsessions/dwn.desktop</code> - Session file for display managers</li>
|
||||
<li><code>/etc/dwn/config.example</code> - Example configuration file</li>
|
||||
</ul>
|
||||
|
||||
<h3>User Configuration</h3>
|
||||
<p>Copy the example configuration to your home directory:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>mkdir -p ~/.config/dwn
|
||||
cp /etc/dwn/config.example ~/.config/dwn/config</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Uninstalling</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>sudo make uninstall</code></pre>
|
||||
</div>
|
||||
|
||||
<h2 id="testing">Testing</h2>
|
||||
|
||||
<h3>Test in Xephyr (Recommended)</h3>
|
||||
<p>Test DWN in a nested X server without affecting your current session:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>make run</code></pre>
|
||||
</div>
|
||||
|
||||
<p>This starts DWN in a 1280x720 Xephyr window on display :1.</p>
|
||||
|
||||
<h3>Manual Testing</h3>
|
||||
<div class="code-block">
|
||||
<pre><code># Start Xephyr
|
||||
Xephyr :1 -screen 1920x1080 &
|
||||
|
||||
# Run DWN on the new display
|
||||
DISPLAY=:1 ./bin/dwn</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Using DWN as Your Window Manager</h3>
|
||||
<ol>
|
||||
<li>Log out of your current session</li>
|
||||
<li>At the login screen, select "DWN" from the session menu</li>
|
||||
<li>Log in</li>
|
||||
</ol>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Tip:</strong> Keep a terminal open or know the shortcut <code>Ctrl+Alt+T</code> to open one, in case you need to recover from any issues.
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
119
manual/js/main.js
Normal file
119
manual/js/main.js
Normal file
@ -0,0 +1,119 @@
|
||||
/* retoor <retoor@molodetz.nl> */
|
||||
/* DWN Documentation Scripts */
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
highlightCurrentPage();
|
||||
setupCopyButtons();
|
||||
setupMobileMenu();
|
||||
setupSearch();
|
||||
setupTabs();
|
||||
});
|
||||
|
||||
function highlightCurrentPage() {
|
||||
const currentPath = window.location.pathname.split('/').pop() || 'index.html';
|
||||
const navLinks = document.querySelectorAll('.nav-link');
|
||||
|
||||
navLinks.forEach(link => {
|
||||
const href = link.getAttribute('href');
|
||||
if (href === currentPath || (currentPath === '' && href === 'index.html')) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setupCopyButtons() {
|
||||
const codeBlocks = document.querySelectorAll('pre code');
|
||||
|
||||
codeBlocks.forEach(block => {
|
||||
const wrapper = block.closest('.code-block') || block.parentElement;
|
||||
wrapper.style.position = 'relative';
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'copy-btn';
|
||||
btn.textContent = 'Copy';
|
||||
btn.addEventListener('click', function() {
|
||||
const text = block.textContent;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
btn.textContent = 'Copied!';
|
||||
setTimeout(() => {
|
||||
btn.textContent = 'Copy';
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
wrapper.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
function setupMobileMenu() {
|
||||
const menuBtn = document.querySelector('.mobile-menu-btn');
|
||||
const sidebar = document.querySelector('.sidebar');
|
||||
|
||||
if (menuBtn && sidebar) {
|
||||
menuBtn.addEventListener('click', function() {
|
||||
sidebar.classList.toggle('open');
|
||||
});
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!sidebar.contains(e.target) && !menuBtn.contains(e.target)) {
|
||||
sidebar.classList.remove('open');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setupSearch() {
|
||||
const searchInput = document.querySelector('.search-input');
|
||||
if (!searchInput) return;
|
||||
|
||||
searchInput.addEventListener('input', function(e) {
|
||||
const query = e.target.value.toLowerCase();
|
||||
const navLinks = document.querySelectorAll('.nav-link');
|
||||
|
||||
navLinks.forEach(link => {
|
||||
const text = link.textContent.toLowerCase();
|
||||
const section = link.closest('.nav-section');
|
||||
|
||||
if (query === '' || text.includes(query)) {
|
||||
link.style.display = 'block';
|
||||
} else {
|
||||
link.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupTabs() {
|
||||
const tabGroups = document.querySelectorAll('.example-tabs');
|
||||
|
||||
tabGroups.forEach(group => {
|
||||
const tabs = group.querySelectorAll('.example-tab');
|
||||
const container = group.nextElementSibling;
|
||||
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
const target = this.dataset.tab;
|
||||
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
if (container) {
|
||||
const contents = container.querySelectorAll('.example-content');
|
||||
contents.forEach(c => {
|
||||
c.classList.remove('active');
|
||||
if (c.dataset.tab === target) {
|
||||
c.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function scrollToSection(id) {
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
298
manual/layouts.html
Normal file
298
manual/layouts.html
Normal file
@ -0,0 +1,298 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Layouts - 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">v1.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 active">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="building.html" class="nav-link">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="content">
|
||||
<div class="page-header">
|
||||
<h1>Layouts</h1>
|
||||
<p class="lead">Understanding DWN's window layout system</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="#tiling">Tiling Layout</a></li>
|
||||
<li><a href="#floating">Floating Layout</a></li>
|
||||
<li><a href="#monocle">Monocle Layout</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>Press <code>Super+Space</code> to cycle through layouts: Tiling → Floating → Monocle → Tiling</p>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Tip:</strong> The current layout is shown in the panel with a symbol: <code>[]=</code> for tiling, <code>><></code> for floating, <code>[M]</code> for monocle.
|
||||
</div>
|
||||
|
||||
<h2 id="tiling">Tiling Layout</h2>
|
||||
<p>The default layout. Windows automatically arrange themselves in a master-stack pattern.</p>
|
||||
|
||||
<h3>How It Works</h3>
|
||||
<div class="code-block">
|
||||
<pre><code>+------------------+----------+
|
||||
| | Window |
|
||||
| Master | 2 |
|
||||
| Window +----------+
|
||||
| 1 | Window |
|
||||
| | 3 |
|
||||
+------------------+----------+</code></pre>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li>The <strong>master area</strong> (left) contains the primary window(s)</li>
|
||||
<li>The <strong>stack area</strong> (right) contains secondary windows</li>
|
||||
<li>Windows are automatically sized to fill the screen</li>
|
||||
<li>No manual positioning required</li>
|
||||
</ul>
|
||||
|
||||
<h3>Master Ratio</h3>
|
||||
<p>Controls the width of the master area relative to the screen.</p>
|
||||
<ul>
|
||||
<li><code>Super+H</code> - Decrease master ratio (shrink master area)</li>
|
||||
<li><code>Super+L</code> - Increase master ratio (expand master area)</li>
|
||||
<li>Range: 10% to 90%</li>
|
||||
<li>Default: 55%</li>
|
||||
</ul>
|
||||
|
||||
<h3>Master Count</h3>
|
||||
<p>Controls how many windows occupy the master area.</p>
|
||||
<ul>
|
||||
<li><code>Super+I</code> - Increase master count</li>
|
||||
<li><code>Super+D</code> - Decrease master count</li>
|
||||
<li>Range: 1 to 10</li>
|
||||
<li>Default: 1</li>
|
||||
</ul>
|
||||
|
||||
<p>With master count of 2:</p>
|
||||
<div class="code-block">
|
||||
<pre><code>+--------+---------+----------+
|
||||
| Master | Master | Stack |
|
||||
| 1 | 2 | 3 |
|
||||
| | +----------+
|
||||
| | | Stack |
|
||||
| | | 4 |
|
||||
+--------+---------+----------+</code></pre>
|
||||
</div>
|
||||
|
||||
<h3>Floating Windows in Tiling</h3>
|
||||
<p>Press <code>Super+F9</code> to toggle floating mode for a window. Floating windows:</p>
|
||||
<ul>
|
||||
<li>Are exempt from automatic tiling</li>
|
||||
<li>Can be freely moved and resized</li>
|
||||
<li>Float above tiled windows</li>
|
||||
<li>Useful for dialogs, small utilities, reference windows</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="floating">Floating Layout</h2>
|
||||
<p>Traditional overlapping window management like most desktop environments.</p>
|
||||
|
||||
<h3>Characteristics</h3>
|
||||
<ul>
|
||||
<li>Windows maintain their own position and size</li>
|
||||
<li>No automatic arrangement</li>
|
||||
<li>Drag title bars to move windows</li>
|
||||
<li>Drag window edges to resize</li>
|
||||
<li>Click to raise windows</li>
|
||||
</ul>
|
||||
|
||||
<h3>Window Controls</h3>
|
||||
<p>In floating mode, windows have full manual control:</p>
|
||||
<ul>
|
||||
<li><strong>Move</strong>: Drag the title bar</li>
|
||||
<li><strong>Resize</strong>: Drag the window border</li>
|
||||
<li><strong>Maximize</strong>: <code>Alt+F10</code> or click maximize button</li>
|
||||
<li><strong>Minimize</strong>: <code>Alt+F9</code> or click minimize button</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="monocle">Monocle Layout</h2>
|
||||
<p>Each window takes the full screen. Only one window visible at a time.</p>
|
||||
|
||||
<h3>Use Cases</h3>
|
||||
<ul>
|
||||
<li>Focused work on a single application</li>
|
||||
<li>Small screens or high window count</li>
|
||||
<li>Reading or writing documents</li>
|
||||
<li>Video or media playback</li>
|
||||
</ul>
|
||||
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li><code>Alt+Tab</code> - Switch to next window</li>
|
||||
<li><code>Alt+Shift+Tab</code> - Switch to previous window</li>
|
||||
<li>Taskbar click - Switch to specific window</li>
|
||||
</ul>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Note:</strong> Windows maintain decorations in monocle mode unless fullscreen (<code>Alt+F11</code>).
|
||||
</div>
|
||||
|
||||
<h2 id="per-workspace">Per-Workspace Settings</h2>
|
||||
<p>Each workspace maintains independent layout settings:</p>
|
||||
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Scope</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Layout Mode</td>
|
||||
<td>Per-workspace</td>
|
||||
<td>Tiling, floating, or monocle</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Master Ratio</td>
|
||||
<td>Per-workspace</td>
|
||||
<td>Width of master area</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Master Count</td>
|
||||
<td>Per-workspace</td>
|
||||
<td>Windows in master area</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Window Floating State</td>
|
||||
<td>Per-window</td>
|
||||
<td>Individual floating toggle</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>Example workflow:</p>
|
||||
<ul>
|
||||
<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>
|
||||
</ul>
|
||||
|
||||
<h2 id="shortcuts">Layout Shortcuts</h2>
|
||||
<div class="table-container">
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Super+Space</code></td>
|
||||
<td>Cycle layout mode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+H</code></td>
|
||||
<td>Shrink master area</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+L</code></td>
|
||||
<td>Expand master area</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+I</code></td>
|
||||
<td>Increase master count</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+D</code></td>
|
||||
<td>Decrease master count</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+F9</code></td>
|
||||
<td>Toggle window floating</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Alt+F10</code></td>
|
||||
<td>Toggle maximize</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Alt+F11</code></td>
|
||||
<td>Toggle fullscreen</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Configuration</h2>
|
||||
<p>Set default layout behavior in <code>~/.config/dwn/config</code>:</p>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><code>[layout]
|
||||
default = tiling # Default layout for new workspaces
|
||||
master_ratio = 0.55 # Default master area ratio (0.1-0.9)
|
||||
master_count = 1 # Default master window count (1-10)
|
||||
|
||||
[appearance]
|
||||
gap = 5 # Gap between tiled windows (0-100)</code></pre>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
241
manual/quickstart.html
Normal file
241
manual/quickstart.html
Normal file
@ -0,0 +1,241 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Quick Start - 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">v1.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="building.html" class="nav-link">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="content">
|
||||
<div class="page-header">
|
||||
<h1>Quick Start</h1>
|
||||
<p class="lead">Get up and running with DWN in 5 minutes</p>
|
||||
</div>
|
||||
|
||||
<h2>Essential Shortcuts</h2>
|
||||
<p>These are the most important shortcuts to know:</p>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Ctrl+Alt+T</code></td>
|
||||
<td>Open terminal</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super</code> or <code>Alt+F2</code></td>
|
||||
<td>Open application launcher (dmenu)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Alt+F4</code></td>
|
||||
<td>Close focused window</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Alt+Tab</code></td>
|
||||
<td>Cycle through windows</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>F1</code> - <code>F9</code></td>
|
||||
<td>Switch to workspace 1-9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Space</code></td>
|
||||
<td>Cycle layout mode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Backspace</code></td>
|
||||
<td>Quit DWN</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Your First Session</h2>
|
||||
|
||||
<h3>1. Launch Applications</h3>
|
||||
<p>Press <code>Super</code> (Windows key) to open the application launcher. Type the name of the application and press Enter.</p>
|
||||
|
||||
<h3>2. Navigate Workspaces</h3>
|
||||
<p>DWN provides 9 virtual workspaces. Use <code>F1</code> through <code>F9</code> to switch between them. The panel at the top shows which workspaces have windows.</p>
|
||||
|
||||
<h3>3. Manage Windows</h3>
|
||||
<p>By default, DWN uses a tiling layout where windows automatically arrange themselves. The first window takes the "master" area (left side), and subsequent windows stack on the right.</p>
|
||||
|
||||
<h3>4. Move Windows Between Workspaces</h3>
|
||||
<p>Press <code>Shift+F1</code> through <code>Shift+F9</code> to move the focused window to a different workspace.</p>
|
||||
|
||||
<h2>Layout Modes</h2>
|
||||
<p>Press <code>Super+Space</code> to cycle through layout modes:</p>
|
||||
|
||||
<div class="feature-grid">
|
||||
<div class="feature-card">
|
||||
<div class="feature-title">Tiling</div>
|
||||
<div class="feature-desc">Master-stack layout. First window on the left, others stacked on the right. Use <code>Super+H/L</code> to resize the master area.</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-title">Floating</div>
|
||||
<div class="feature-desc">Traditional floating windows. Drag title bars to move, drag edges to resize.</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-title">Monocle</div>
|
||||
<div class="feature-desc">All windows fullscreen, stacked. Use Alt+Tab to switch between them.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Window Snapping</h2>
|
||||
<p>Use <code>Super+Arrow</code> keys to snap windows:</p>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Super+Left</code></td>
|
||||
<td>Snap to left half (press twice for full width)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Right</code></td>
|
||||
<td>Snap to right half</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Up</code></td>
|
||||
<td>Snap to top half</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Down</code></td>
|
||||
<td>Snap to bottom half</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p>Snapping is composable: <code>Super+Left</code> then <code>Super+Up</code> snaps to the top-left quarter.</p>
|
||||
|
||||
<h2>Window States</h2>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Alt+F9</code></td>
|
||||
<td>Minimize window</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Alt+F10</code></td>
|
||||
<td>Maximize window</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Alt+F11</code></td>
|
||||
<td>Fullscreen (no decorations)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+F9</code></td>
|
||||
<td>Toggle floating mode</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Configuration</h2>
|
||||
<p>DWN reads configuration from <code>~/.config/dwn/config</code>. Create one from the example:</p>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><code>mkdir -p ~/.config/dwn
|
||||
cp /etc/dwn/config.example ~/.config/dwn/config</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Edit the file to customize terminal, launcher, colors, and more. See the <a href="configuration.html">Configuration</a> page for details.</p>
|
||||
|
||||
<h2>Enable the API</h2>
|
||||
<p>To enable the WebSocket API for automation, add this to your config:</p>
|
||||
|
||||
<div class="code-block">
|
||||
<pre><code>[api]
|
||||
enabled = true
|
||||
port = 8777</code></pre>
|
||||
</div>
|
||||
|
||||
<p>Then you can control DWN programmatically. See <a href="api-overview.html">API Overview</a>.</p>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
<ul>
|
||||
<li><a href="shortcuts.html">Full keyboard shortcuts reference</a></li>
|
||||
<li><a href="configuration.html">Configuration options</a></li>
|
||||
<li><a href="ai-features.html">AI integration setup</a></li>
|
||||
<li><a href="api-overview.html">API documentation</a></li>
|
||||
</ul>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
326
manual/shortcuts.html
Normal file
326
manual/shortcuts.html
Normal file
@ -0,0 +1,326 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Keyboard Shortcuts - 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">v1.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="building.html" class="nav-link">Building from Source</a>
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="content">
|
||||
<div class="page-header">
|
||||
<h1>Keyboard Shortcuts</h1>
|
||||
<p class="lead">Complete reference of all keyboard shortcuts</p>
|
||||
</div>
|
||||
|
||||
<div class="toc">
|
||||
<div class="toc-title">On this page</div>
|
||||
<ul class="toc-list">
|
||||
<li><a href="#launchers">Application Launchers</a></li>
|
||||
<li><a href="#windows">Window Management</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>
|
||||
<li><a href="#ai">AI Features</a></li>
|
||||
<li><a href="#system">Help & System</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="launchers">Application Launchers</h2>
|
||||
<div class="table-container">
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Ctrl+Alt+T</code></td>
|
||||
<td>Open terminal (configurable)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super</code> / <code>Alt+F2</code></td>
|
||||
<td>Open application launcher (dmenu/rofi)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+E</code></td>
|
||||
<td>Open file manager (configurable)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+B</code></td>
|
||||
<td>Open web browser</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Print</code></td>
|
||||
<td>Take screenshot (xfce4-screenshooter)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="windows">Window Management</h2>
|
||||
<div class="table-container">
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Alt+F4</code></td>
|
||||
<td>Close focused window</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Alt+Tab</code></td>
|
||||
<td>Cycle to next window (MRU order)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Alt+Shift+Tab</code></td>
|
||||
<td>Cycle to previous window</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Alt+F9</code></td>
|
||||
<td>Toggle minimize/restore</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Alt+F10</code></td>
|
||||
<td>Toggle maximize</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Alt+F11</code></td>
|
||||
<td>Toggle fullscreen (no decorations)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+F9</code></td>
|
||||
<td>Toggle floating mode for focused window</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="workspaces">Workspace Navigation</h2>
|
||||
<div class="table-container">
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>F1</code> - <code>F9</code></td>
|
||||
<td>Switch to workspace 1-9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Shift+F1</code> - <code>Shift+F9</code></td>
|
||||
<td>Move focused window to workspace 1-9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Ctrl+Alt+Right</code></td>
|
||||
<td>Switch to next workspace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Ctrl+Alt+Left</code></td>
|
||||
<td>Switch to previous workspace</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="layouts">Layout Control</h2>
|
||||
<div class="table-container">
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Super+Space</code></td>
|
||||
<td>Cycle layout mode (tiling → floating → monocle)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+H</code></td>
|
||||
<td>Shrink master area</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+L</code></td>
|
||||
<td>Expand master area</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+I</code></td>
|
||||
<td>Increase master window count</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+D</code></td>
|
||||
<td>Decrease master window count</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="snapping">Window Snapping</h2>
|
||||
<p>Window snapping is composable. Press the same key twice to expand to full width/height in that direction.</p>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Super+Left</code></td>
|
||||
<td>Snap left 50% (press twice for full width)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Right</code></td>
|
||||
<td>Snap right 50% (press twice for full width)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Up</code></td>
|
||||
<td>Snap top 50% (press twice for full height)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Down</code></td>
|
||||
<td>Snap bottom 50% (press twice for full height)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Tip:</strong> Combine horizontal and vertical snaps for quarter-screen windows. For example, <code>Super+Left</code> then <code>Super+Up</code> snaps to top-left quarter.
|
||||
</div>
|
||||
|
||||
<h2 id="ai">AI Features</h2>
|
||||
<p>These shortcuts require API keys to be configured. See <a href="ai-features.html">AI Integration</a>.</p>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Super+A</code></td>
|
||||
<td>Show AI context analysis</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Shift+A</code></td>
|
||||
<td>Open AI command palette</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Shift+E</code></td>
|
||||
<td>Open Exa semantic web search</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 id="system">Help & System</h2>
|
||||
<div class="table-container">
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Super+S</code></td>
|
||||
<td>Show all keyboard shortcuts</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+T</code></td>
|
||||
<td>Start/continue interactive tutorial</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Shift+D</code></td>
|
||||
<td>Start/stop demo mode</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+D</code></td>
|
||||
<td>Toggle show desktop</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Return</code></td>
|
||||
<td>Open current news article in browser</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Super+Backspace</code></td>
|
||||
<td>Quit DWN</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>DWN Window Manager - retoor <retoor@molodetz.nl></p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
47
src/client.c
47
src/client.c
@ -13,6 +13,7 @@
|
||||
#include "notifications.h"
|
||||
#include "layout.h"
|
||||
#include "panel.h"
|
||||
#include "api.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@ -234,6 +235,8 @@ Client *client_manage(Window window)
|
||||
client_sync_log("client_manage: focusing");
|
||||
client_focus(client, true);
|
||||
|
||||
api_emit_window_created(client->window, client->title, client->class, client->workspace);
|
||||
|
||||
client_sync_log("client_manage: DONE");
|
||||
return client;
|
||||
}
|
||||
@ -247,6 +250,8 @@ void client_unmanage(Client *client)
|
||||
return;
|
||||
}
|
||||
|
||||
api_emit_window_destroyed(client->window, client->title, client->workspace);
|
||||
|
||||
LOG_DEBUG("Unmanaging window: %lu", client->window);
|
||||
|
||||
if (dwn->alt_tab_client == client) {
|
||||
@ -345,7 +350,9 @@ void client_focus(Client *client, bool update_mru)
|
||||
client_sync_log("client_focus: unfocusing previous");
|
||||
|
||||
Workspace *ws = workspace_get(client->workspace);
|
||||
Window prev_window = None;
|
||||
if (ws != NULL && ws->focused != NULL && ws->focused != client) {
|
||||
prev_window = ws->focused->window;
|
||||
client_unfocus(ws->focused);
|
||||
}
|
||||
|
||||
@ -378,6 +385,8 @@ void client_focus(Client *client, bool update_mru)
|
||||
|
||||
atoms_set_active_window(client->window);
|
||||
|
||||
api_emit_window_focused(client->window, client->title, prev_window);
|
||||
|
||||
client_sync_log("client_focus: DONE");
|
||||
LOG_DEBUG("Focused window: %s", client->title);
|
||||
}
|
||||
@ -395,6 +404,8 @@ void client_unfocus(Client *client)
|
||||
}
|
||||
|
||||
decorations_render(client, false);
|
||||
|
||||
api_emit_window_unfocused(client->window, client->title);
|
||||
}
|
||||
|
||||
void client_raise(Client *client)
|
||||
@ -414,6 +425,8 @@ void client_raise(Client *client)
|
||||
|
||||
XRaiseWindow(dwn->display, win);
|
||||
|
||||
api_emit_window_raised(client->window);
|
||||
|
||||
notifications_raise_all();
|
||||
panel_raise_all();
|
||||
}
|
||||
@ -433,6 +446,8 @@ void client_lower(Client *client)
|
||||
return;
|
||||
}
|
||||
XLowerWindow(dwn->display, win);
|
||||
|
||||
api_emit_window_lowered(client->window);
|
||||
}
|
||||
|
||||
void client_minimize(Client *client)
|
||||
@ -444,6 +459,8 @@ void client_minimize(Client *client)
|
||||
client->flags |= CLIENT_MINIMIZED;
|
||||
client_hide(client);
|
||||
|
||||
api_emit_window_minimized(client->window);
|
||||
|
||||
Workspace *ws = workspace_get(client->workspace);
|
||||
if (ws != NULL && ws->focused == client) {
|
||||
Client *next = workspace_mru_get_previous(client->workspace, client);
|
||||
@ -466,6 +483,8 @@ void client_restore(Client *client)
|
||||
client->flags &= ~CLIENT_MINIMIZED;
|
||||
client_show(client);
|
||||
client_focus(client, true);
|
||||
|
||||
api_emit_window_restored(client->window);
|
||||
}
|
||||
|
||||
|
||||
@ -475,6 +494,9 @@ void client_move(Client *client, int x, int y)
|
||||
return;
|
||||
}
|
||||
|
||||
int old_x = client->x;
|
||||
int old_y = client->y;
|
||||
|
||||
int area_x, area_y, area_w, area_h;
|
||||
layout_get_usable_area(&area_x, &area_y, &area_w, &area_h);
|
||||
|
||||
@ -505,6 +527,10 @@ void client_move(Client *client, int x, int y)
|
||||
client->x = x;
|
||||
client->y = y;
|
||||
client_configure(client);
|
||||
|
||||
if (old_x != client->x || old_y != client->y) {
|
||||
api_emit_window_moved(client->window, old_x, old_y, client->x, client->y);
|
||||
}
|
||||
}
|
||||
|
||||
void client_resize(Client *client, int width, int height)
|
||||
@ -513,6 +539,9 @@ void client_resize(Client *client, int width, int height)
|
||||
return;
|
||||
}
|
||||
|
||||
int old_w = client->width;
|
||||
int old_h = client->height;
|
||||
|
||||
int area_x, area_y, area_w, area_h;
|
||||
layout_get_usable_area(&area_x, &area_y, &area_w, &area_h);
|
||||
|
||||
@ -535,6 +564,10 @@ void client_resize(Client *client, int width, int height)
|
||||
client->width = width;
|
||||
client->height = height;
|
||||
client_configure(client);
|
||||
|
||||
if (old_w != client->width || old_h != client->height) {
|
||||
api_emit_window_resized(client->window, old_w, old_h, client->width, client->height);
|
||||
}
|
||||
}
|
||||
|
||||
void client_move_resize(Client *client, int x, int y, int width, int height)
|
||||
@ -682,6 +715,10 @@ void client_update_title(Client *client)
|
||||
return;
|
||||
}
|
||||
|
||||
char old_title[256];
|
||||
strncpy(old_title, client->title, sizeof(old_title) - 1);
|
||||
old_title[sizeof(old_title) - 1] = '\0';
|
||||
|
||||
char *name = atoms_get_window_name(client->window);
|
||||
if (name != NULL) {
|
||||
strncpy(client->title, name, sizeof(client->title) - 1);
|
||||
@ -691,6 +728,10 @@ void client_update_title(Client *client)
|
||||
strncpy(client->title, "Untitled", sizeof(client->title) - 1);
|
||||
}
|
||||
|
||||
if (strcmp(old_title, client->title) != 0) {
|
||||
api_emit_window_title_changed(client->window, old_title, client->title);
|
||||
}
|
||||
|
||||
if (client->frame != None) {
|
||||
Workspace *ws = workspace_get(client->workspace);
|
||||
bool focused = (ws != NULL && ws->focused == client);
|
||||
@ -733,6 +774,7 @@ void client_set_fullscreen(Client *client, bool fullscreen)
|
||||
|
||||
client->flags |= CLIENT_FULLSCREEN;
|
||||
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_FULLSCREEN, true);
|
||||
api_emit_window_fullscreen(client->window, true);
|
||||
|
||||
client->x = 0;
|
||||
client->y = 0;
|
||||
@ -753,6 +795,7 @@ void client_set_fullscreen(Client *client, bool fullscreen)
|
||||
} else {
|
||||
client->flags &= ~CLIENT_FULLSCREEN;
|
||||
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_FULLSCREEN, false);
|
||||
api_emit_window_fullscreen(client->window, false);
|
||||
|
||||
client->x = client->old_x;
|
||||
client->y = client->old_y;
|
||||
@ -794,6 +837,8 @@ void client_set_floating(Client *client, bool floating)
|
||||
} else {
|
||||
client->flags &= ~CLIENT_FLOATING;
|
||||
}
|
||||
|
||||
api_emit_window_floating(client->window, floating);
|
||||
}
|
||||
|
||||
void client_toggle_floating(Client *client)
|
||||
@ -838,6 +883,7 @@ void client_set_maximize(Client *client, bool maximized)
|
||||
|
||||
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_VERT, true);
|
||||
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_HORZ, true);
|
||||
api_emit_window_maximized(client->window, true);
|
||||
|
||||
int area_x, area_y, area_width, area_height;
|
||||
layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);
|
||||
@ -860,6 +906,7 @@ void client_set_maximize(Client *client, bool maximized)
|
||||
|
||||
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);
|
||||
|
||||
client->x = client->old_x;
|
||||
client->y = client->old_y;
|
||||
|
||||
12
src/keys.c
12
src/keys.c
@ -16,6 +16,7 @@
|
||||
#include "decorations.h"
|
||||
#include "demo.h"
|
||||
#include "layout.h"
|
||||
#include "api.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@ -456,11 +457,17 @@ void keys_handle_press(XKeyEvent *ev)
|
||||
bindings[i].modifiers == clean_mask) {
|
||||
if (bindings[i].callback != NULL) {
|
||||
LOG_DEBUG("Key binding triggered: %s", bindings[i].description);
|
||||
|
||||
const char *name = XKeysymToString(keysym);
|
||||
api_emit_shortcut_triggered(name ? name : "unknown", bindings[i].description);
|
||||
|
||||
bindings[i].callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
api_emit_key_pressed(ev->keycode, keysym, clean_mask);
|
||||
}
|
||||
|
||||
void keys_handle_release(XKeyEvent *ev)
|
||||
@ -470,6 +477,9 @@ void keys_handle_release(XKeyEvent *ev)
|
||||
}
|
||||
|
||||
KeySym keysym = XLookupKeysym(ev, 0);
|
||||
unsigned int clean_mask = ev->state & ~(Mod2Mask | LockMask);
|
||||
|
||||
api_emit_key_released(ev->keycode, keysym, clean_mask);
|
||||
|
||||
if (keysym == XK_Super_L || keysym == XK_Super_R) {
|
||||
if (super_pressed && !super_used_in_combo) {
|
||||
@ -767,6 +777,7 @@ void key_show_desktop(void)
|
||||
}
|
||||
}
|
||||
dwn->desktop_shown = true;
|
||||
api_emit_show_desktop_toggled(true);
|
||||
} else {
|
||||
for (int i = 0; i < dwn->desktop_minimized_count; i++) {
|
||||
Client *c = client_find_by_window(dwn->desktop_minimized[i]);
|
||||
@ -776,6 +787,7 @@ void key_show_desktop(void)
|
||||
}
|
||||
dwn->desktop_minimized_count = 0;
|
||||
dwn->desktop_shown = false;
|
||||
api_emit_show_desktop_toggled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
25
src/main.c
25
src/main.c
@ -22,6 +22,8 @@
|
||||
#include "services.h"
|
||||
#include "api.h"
|
||||
#include "demo.h"
|
||||
#include "screenshot.h"
|
||||
#include "ocr.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdio.h>
|
||||
@ -466,6 +468,8 @@ static void handle_button_press(XButtonEvent *ev)
|
||||
dwn->drag_orig_y = c->y;
|
||||
dwn->resizing = false;
|
||||
|
||||
api_emit_drag_started(c->window, false);
|
||||
|
||||
XGrabPointer(dwn->display, c->frame, True,
|
||||
PointerMotionMask | ButtonReleaseMask,
|
||||
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
|
||||
@ -485,12 +489,16 @@ static void handle_button_press(XButtonEvent *ev)
|
||||
dwn->drag_orig_h = c->height;
|
||||
dwn->resizing = true;
|
||||
|
||||
api_emit_drag_started(c->window, true);
|
||||
|
||||
XGrabPointer(dwn->display, c->frame, True,
|
||||
PointerMotionMask | ButtonReleaseMask,
|
||||
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api_emit_mouse_button_pressed(ev->button, ev->x_root, ev->y_root);
|
||||
}
|
||||
|
||||
static void handle_button_release(XButtonEvent *ev)
|
||||
@ -503,12 +511,13 @@ static void handle_button_release(XButtonEvent *ev)
|
||||
volume_slider_handle_release(volume_slider);
|
||||
}
|
||||
|
||||
(void)ev;
|
||||
|
||||
if (dwn->drag_client != NULL) {
|
||||
api_emit_drag_ended(dwn->drag_client->window, dwn->resizing);
|
||||
XUngrabPointer(dwn->display, CurrentTime);
|
||||
dwn->drag_client = NULL;
|
||||
}
|
||||
|
||||
api_emit_mouse_button_released(ev->button, ev->x_root, ev->y_root);
|
||||
}
|
||||
|
||||
static void handle_motion_notify(XMotionEvent *ev)
|
||||
@ -774,6 +783,10 @@ int dwn_init(void)
|
||||
|
||||
ai_init();
|
||||
|
||||
screenshot_init();
|
||||
|
||||
ocr_init();
|
||||
|
||||
api_init();
|
||||
|
||||
demo_init();
|
||||
@ -807,6 +820,8 @@ void dwn_cleanup(void)
|
||||
|
||||
demo_cleanup();
|
||||
api_cleanup();
|
||||
ocr_cleanup();
|
||||
screenshot_cleanup();
|
||||
ai_cleanup();
|
||||
notifications_cleanup();
|
||||
news_cleanup();
|
||||
@ -873,6 +888,10 @@ void dwn_run(void)
|
||||
|
||||
exa_process_pending();
|
||||
|
||||
screenshot_process_pending();
|
||||
|
||||
ocr_process_pending();
|
||||
|
||||
demo_update();
|
||||
|
||||
notifications_update();
|
||||
@ -966,6 +985,8 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
XInitThreads();
|
||||
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGHUP, signal_handler);
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "notifications.h"
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
#include "api.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@ -520,6 +521,8 @@ uint32_t notification_show(const char *app_name, const char *summary,
|
||||
LOG_DEBUG("Notification %u (%dx%d): %s - %s", notif->id,
|
||||
notif->width, notif->height, notif->summary, notif->body);
|
||||
|
||||
api_emit_notification_shown(notif->id, notif->summary, notif->body);
|
||||
|
||||
return notif->id;
|
||||
}
|
||||
|
||||
@ -545,6 +548,8 @@ void notification_close(uint32_t id)
|
||||
notif->body = NULL;
|
||||
}
|
||||
|
||||
api_emit_notification_closed(id);
|
||||
|
||||
dwn_free(notif);
|
||||
|
||||
notifications_position();
|
||||
|
||||
359
src/ocr.c
Normal file
359
src/ocr.c
Normal file
@ -0,0 +1,359 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* retoor <retoor@molodetz.nl>
|
||||
* OCR text extraction using Tesseract
|
||||
*/
|
||||
|
||||
#include "ocr.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include <tesseract/capi.h>
|
||||
#include <leptonica/allheaders.h>
|
||||
|
||||
static TessBaseAPI *tess_api = NULL;
|
||||
static bool initialized = false;
|
||||
static pthread_mutex_t ocr_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static OcrRequest *ocr_queue = NULL;
|
||||
static atomic_int ocr_shutting_down = 0;
|
||||
|
||||
bool ocr_init(void)
|
||||
{
|
||||
if (initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
atomic_store(&ocr_shutting_down, 0);
|
||||
|
||||
tess_api = TessBaseAPICreate();
|
||||
if (tess_api == NULL) {
|
||||
LOG_ERROR("Failed to create Tesseract API");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TessBaseAPIInit3(tess_api, NULL, "eng") != 0) {
|
||||
LOG_WARN("Tesseract initialization failed, OCR unavailable");
|
||||
TessBaseAPIDelete(tess_api);
|
||||
tess_api = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
LOG_INFO("OCR module initialized with Tesseract");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ocr_cleanup(void)
|
||||
{
|
||||
atomic_store(&ocr_shutting_down, 1);
|
||||
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
while (ocr_queue != NULL) {
|
||||
OcrRequest *req = ocr_queue;
|
||||
ocr_queue = req->next;
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
|
||||
pthread_join(req->thread, NULL);
|
||||
if (req->png_data != NULL) {
|
||||
dwn_free(req->png_data);
|
||||
}
|
||||
ocr_result_free(&req->result);
|
||||
dwn_free(req);
|
||||
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
|
||||
if (tess_api != NULL) {
|
||||
TessBaseAPIEnd(tess_api);
|
||||
TessBaseAPIDelete(tess_api);
|
||||
tess_api = NULL;
|
||||
}
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
bool ocr_is_available(void)
|
||||
{
|
||||
return initialized && tess_api != NULL;
|
||||
}
|
||||
|
||||
const char *ocr_status_string(OcrStatus status)
|
||||
{
|
||||
switch (status) {
|
||||
case OCR_OK:
|
||||
return "OK";
|
||||
case OCR_ERROR_INVALID_ARG:
|
||||
return "Invalid argument";
|
||||
case OCR_ERROR_INIT:
|
||||
return "Initialization error";
|
||||
case OCR_ERROR_IMAGE:
|
||||
return "Image processing error";
|
||||
case OCR_ERROR_NO_MEMORY:
|
||||
return "Out of memory";
|
||||
case OCR_ERROR_NOT_AVAILABLE:
|
||||
return "OCR not available";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
void ocr_result_free(OcrResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return;
|
||||
}
|
||||
if (result->text != NULL) {
|
||||
dwn_free(result->text);
|
||||
result->text = NULL;
|
||||
}
|
||||
result->length = 0;
|
||||
result->confidence = 0.0f;
|
||||
}
|
||||
|
||||
OcrStatus ocr_extract_from_png(const unsigned char *png_data, size_t size,
|
||||
const char *language, OcrResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return OCR_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
memset(result, 0, sizeof(OcrResult));
|
||||
|
||||
if (png_data == NULL || size == 0) {
|
||||
return OCR_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!ocr_is_available()) {
|
||||
return OCR_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
const char *lang = (language != NULL && language[0] != '\0') ? language : "eng";
|
||||
|
||||
if (TessBaseAPIInit3(tess_api, NULL, lang) != 0) {
|
||||
LOG_WARN("Failed to set language '%s', falling back to 'eng'", lang);
|
||||
if (TessBaseAPIInit3(tess_api, NULL, "eng") != 0) {
|
||||
return OCR_ERROR_INIT;
|
||||
}
|
||||
}
|
||||
|
||||
PIX *image = pixReadMem(png_data, size);
|
||||
if (image == NULL) {
|
||||
LOG_ERROR("Failed to read PNG image for OCR");
|
||||
return OCR_ERROR_IMAGE;
|
||||
}
|
||||
|
||||
TessBaseAPISetImage2(tess_api, image);
|
||||
|
||||
char *text = TessBaseAPIGetUTF8Text(tess_api);
|
||||
if (text == NULL) {
|
||||
pixDestroy(&image);
|
||||
return OCR_ERROR_IMAGE;
|
||||
}
|
||||
|
||||
int conf = TessBaseAPIMeanTextConf(tess_api);
|
||||
float confidence = (float)conf / 100.0f;
|
||||
|
||||
size_t text_len = strlen(text);
|
||||
result->text = dwn_malloc(text_len + 1);
|
||||
if (result->text == NULL) {
|
||||
TessDeleteText(text);
|
||||
pixDestroy(&image);
|
||||
return OCR_ERROR_NO_MEMORY;
|
||||
}
|
||||
|
||||
memcpy(result->text, text, text_len + 1);
|
||||
result->length = text_len;
|
||||
result->confidence = confidence;
|
||||
|
||||
TessDeleteText(text);
|
||||
pixDestroy(&image);
|
||||
|
||||
return OCR_OK;
|
||||
}
|
||||
|
||||
static void *ocr_worker(void *arg)
|
||||
{
|
||||
OcrRequest *req = (OcrRequest *)arg;
|
||||
|
||||
if (atomic_load(&ocr_shutting_down)) {
|
||||
req->state = OCR_STATE_ERROR;
|
||||
req->status = OCR_ERROR_NOT_AVAILABLE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TessBaseAPI *local_tess = TessBaseAPICreate();
|
||||
if (local_tess == NULL) {
|
||||
req->status = OCR_ERROR_INIT;
|
||||
req->state = OCR_STATE_ERROR;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *lang = req->language[0] ? req->language : "eng";
|
||||
if (TessBaseAPIInit3(local_tess, NULL, lang) != 0) {
|
||||
if (TessBaseAPIInit3(local_tess, NULL, "eng") != 0) {
|
||||
TessBaseAPIDelete(local_tess);
|
||||
req->status = OCR_ERROR_INIT;
|
||||
req->state = OCR_STATE_ERROR;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
PIX *image = pixReadMem(req->png_data, req->png_size);
|
||||
if (image == NULL) {
|
||||
TessBaseAPIEnd(local_tess);
|
||||
TessBaseAPIDelete(local_tess);
|
||||
req->status = OCR_ERROR_IMAGE;
|
||||
req->state = OCR_STATE_ERROR;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TessBaseAPISetImage2(local_tess, image);
|
||||
char *text = TessBaseAPIGetUTF8Text(local_tess);
|
||||
int conf = TessBaseAPIMeanTextConf(local_tess);
|
||||
|
||||
if (text != NULL) {
|
||||
size_t len = strlen(text);
|
||||
req->result.text = dwn_malloc(len + 1);
|
||||
if (req->result.text) {
|
||||
memcpy(req->result.text, text, len + 1);
|
||||
req->result.length = len;
|
||||
req->result.confidence = (float)conf / 100.0f;
|
||||
req->status = OCR_OK;
|
||||
req->state = OCR_STATE_COMPLETED;
|
||||
} else {
|
||||
req->status = OCR_ERROR_NO_MEMORY;
|
||||
req->state = OCR_STATE_ERROR;
|
||||
}
|
||||
TessDeleteText(text);
|
||||
} else {
|
||||
req->status = OCR_ERROR_IMAGE;
|
||||
req->state = OCR_STATE_ERROR;
|
||||
}
|
||||
|
||||
pixDestroy(&image);
|
||||
TessBaseAPIEnd(local_tess);
|
||||
TessBaseAPIDelete(local_tess);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
OcrRequest *ocr_extract_async(const unsigned char *png_data, size_t size,
|
||||
const char *language,
|
||||
void (*callback)(OcrRequest *))
|
||||
{
|
||||
if (!initialized || atomic_load(&ocr_shutting_down)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (png_data == NULL || size == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
OcrRequest *req = dwn_calloc(1, sizeof(OcrRequest));
|
||||
if (req == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->png_data = dwn_malloc(size);
|
||||
if (req->png_data == NULL) {
|
||||
dwn_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(req->png_data, png_data, size);
|
||||
req->png_size = size;
|
||||
|
||||
if (language != NULL && language[0] != '\0') {
|
||||
strncpy(req->language, language, sizeof(req->language) - 1);
|
||||
req->language[sizeof(req->language) - 1] = '\0';
|
||||
}
|
||||
|
||||
req->state = OCR_STATE_PENDING;
|
||||
req->callback = callback;
|
||||
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
req->next = ocr_queue;
|
||||
ocr_queue = req;
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
|
||||
if (pthread_create(&req->thread, NULL, ocr_worker, req) != 0) {
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
OcrRequest **pp = &ocr_queue;
|
||||
while (*pp != NULL) {
|
||||
if (*pp == req) {
|
||||
*pp = req->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
dwn_free(req->png_data);
|
||||
dwn_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOG_DEBUG("OCR async request started");
|
||||
return req;
|
||||
}
|
||||
|
||||
void ocr_process_pending(void)
|
||||
{
|
||||
if (atomic_load(&ocr_shutting_down)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
OcrRequest **pp = &ocr_queue;
|
||||
|
||||
while (*pp != NULL) {
|
||||
OcrRequest *req = *pp;
|
||||
|
||||
if (req->state == OCR_STATE_COMPLETED ||
|
||||
req->state == OCR_STATE_ERROR) {
|
||||
|
||||
pthread_join(req->thread, NULL);
|
||||
|
||||
*pp = req->next;
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
|
||||
if (req->callback != NULL) {
|
||||
req->callback(req);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
} else {
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
}
|
||||
|
||||
void ocr_cancel(OcrRequest *req)
|
||||
{
|
||||
if (req == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
OcrRequest **pp = &ocr_queue;
|
||||
while (*pp != NULL) {
|
||||
if (*pp == req) {
|
||||
*pp = req->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
|
||||
pthread_cancel(req->thread);
|
||||
pthread_join(req->thread, NULL);
|
||||
|
||||
if (req->png_data != NULL) {
|
||||
dwn_free(req->png_data);
|
||||
}
|
||||
ocr_result_free(&req->result);
|
||||
dwn_free(req);
|
||||
}
|
||||
628
src/screenshot.c
Normal file
628
src/screenshot.c
Normal file
@ -0,0 +1,628 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* retoor <retoor@molodetz.nl>
|
||||
* Screenshot capture implementation using X11 and libpng
|
||||
*/
|
||||
|
||||
#include "screenshot.h"
|
||||
#include "dwn.h"
|
||||
#include "client.h"
|
||||
#include "workspace.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include <png.h>
|
||||
|
||||
static bool initialized = false;
|
||||
static pthread_mutex_t screenshot_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static ScreenshotRequest *screenshot_queue = NULL;
|
||||
static atomic_int screenshot_shutting_down = 0;
|
||||
|
||||
static const char base64_chars[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
bool screenshot_init(void)
|
||||
{
|
||||
atomic_store(&screenshot_shutting_down, 0);
|
||||
initialized = true;
|
||||
LOG_INFO("Screenshot module initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
void screenshot_cleanup(void)
|
||||
{
|
||||
atomic_store(&screenshot_shutting_down, 1);
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
while (screenshot_queue != NULL) {
|
||||
ScreenshotRequest *req = screenshot_queue;
|
||||
screenshot_queue = req->next;
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
pthread_join(req->thread, NULL);
|
||||
screenshot_result_free(&req->result);
|
||||
dwn_free(req);
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
static size_t base64_encoded_size(size_t input_size)
|
||||
{
|
||||
return 4 * ((input_size + 2) / 3);
|
||||
}
|
||||
|
||||
char *screenshot_to_base64(const ScreenshotResult *result, size_t *out_len)
|
||||
{
|
||||
if (result == NULL || result->data == NULL || result->size == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t encoded_size = base64_encoded_size(result->size);
|
||||
char *encoded = dwn_malloc(encoded_size + 1);
|
||||
if (encoded == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const unsigned char *input = result->data;
|
||||
size_t input_len = result->size;
|
||||
char *output = encoded;
|
||||
|
||||
for (size_t i = 0; i < input_len; i += 3) {
|
||||
unsigned int n = ((unsigned int)input[i]) << 16;
|
||||
if (i + 1 < input_len) {
|
||||
n |= ((unsigned int)input[i + 1]) << 8;
|
||||
}
|
||||
if (i + 2 < input_len) {
|
||||
n |= (unsigned int)input[i + 2];
|
||||
}
|
||||
|
||||
*output++ = base64_chars[(n >> 18) & 0x3F];
|
||||
*output++ = base64_chars[(n >> 12) & 0x3F];
|
||||
*output++ = (i + 1 < input_len) ? base64_chars[(n >> 6) & 0x3F] : '=';
|
||||
*output++ = (i + 2 < input_len) ? base64_chars[n & 0x3F] : '=';
|
||||
}
|
||||
|
||||
*output = '\0';
|
||||
if (out_len != NULL) {
|
||||
*out_len = output - encoded;
|
||||
}
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
void screenshot_result_free(ScreenshotResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return;
|
||||
}
|
||||
if (result->data != NULL) {
|
||||
dwn_free(result->data);
|
||||
result->data = NULL;
|
||||
}
|
||||
result->size = 0;
|
||||
result->width = 0;
|
||||
result->height = 0;
|
||||
}
|
||||
|
||||
const char *screenshot_status_string(ScreenshotStatus status)
|
||||
{
|
||||
switch (status) {
|
||||
case SCREENSHOT_OK:
|
||||
return "OK";
|
||||
case SCREENSHOT_ERROR_INVALID_ARG:
|
||||
return "Invalid argument";
|
||||
case SCREENSHOT_ERROR_X11:
|
||||
return "X11 error";
|
||||
case SCREENSHOT_ERROR_PNG:
|
||||
return "PNG encoding error";
|
||||
case SCREENSHOT_ERROR_NO_MEMORY:
|
||||
return "Out of memory";
|
||||
case SCREENSHOT_ERROR_WINDOW_NOT_FOUND:
|
||||
return "Window not found";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
unsigned char *data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
} PngBuffer;
|
||||
|
||||
static void png_write_callback(png_structp png, png_bytep data, png_size_t length)
|
||||
{
|
||||
PngBuffer *buf = (PngBuffer *)png_get_io_ptr(png);
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t new_size = buf->size + length;
|
||||
if (new_size > buf->capacity) {
|
||||
size_t new_capacity = buf->capacity * 2;
|
||||
if (new_capacity < new_size) {
|
||||
new_capacity = new_size + 1024;
|
||||
}
|
||||
unsigned char *new_data = dwn_realloc(buf->data, new_capacity);
|
||||
if (new_data == NULL) {
|
||||
png_error(png, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
buf->data = new_data;
|
||||
buf->capacity = new_capacity;
|
||||
}
|
||||
|
||||
memcpy(buf->data + buf->size, data, length);
|
||||
buf->size = new_size;
|
||||
}
|
||||
|
||||
static void png_flush_callback(png_structp png)
|
||||
{
|
||||
(void)png;
|
||||
}
|
||||
|
||||
static ScreenshotStatus ximage_to_png(XImage *img, ScreenshotResult *result)
|
||||
{
|
||||
if (img == NULL || result == NULL) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
if (png == NULL) {
|
||||
return SCREENSHOT_ERROR_PNG;
|
||||
}
|
||||
|
||||
png_infop info = png_create_info_struct(png);
|
||||
if (info == NULL) {
|
||||
png_destroy_write_struct(&png, NULL);
|
||||
return SCREENSHOT_ERROR_PNG;
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png))) {
|
||||
png_destroy_write_struct(&png, &info);
|
||||
return SCREENSHOT_ERROR_PNG;
|
||||
}
|
||||
|
||||
PngBuffer buf = {0};
|
||||
buf.capacity = (size_t)(img->width * img->height * 3 / 2 + 1024);
|
||||
buf.data = dwn_malloc(buf.capacity);
|
||||
if (buf.data == NULL) {
|
||||
png_destroy_write_struct(&png, &info);
|
||||
return SCREENSHOT_ERROR_NO_MEMORY;
|
||||
}
|
||||
|
||||
png_set_write_fn(png, &buf, png_write_callback, png_flush_callback);
|
||||
|
||||
png_set_IHDR(png, info, img->width, img->height, 8,
|
||||
PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
|
||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
png_write_info(png, info);
|
||||
|
||||
unsigned char *row = dwn_malloc(img->width * 3);
|
||||
if (row == NULL) {
|
||||
dwn_free(buf.data);
|
||||
png_destroy_write_struct(&png, &info);
|
||||
return SCREENSHOT_ERROR_NO_MEMORY;
|
||||
}
|
||||
|
||||
unsigned long red_mask = img->red_mask;
|
||||
unsigned long green_mask = img->green_mask;
|
||||
unsigned long blue_mask = img->blue_mask;
|
||||
|
||||
int red_shift = 0;
|
||||
int green_shift = 0;
|
||||
int blue_shift = 0;
|
||||
|
||||
while ((red_mask & 1) == 0 && red_shift < 32) {
|
||||
red_shift++;
|
||||
red_mask >>= 1;
|
||||
}
|
||||
while ((green_mask & 1) == 0 && green_shift < 32) {
|
||||
green_shift++;
|
||||
green_mask >>= 1;
|
||||
}
|
||||
while ((blue_mask & 1) == 0 && blue_shift < 32) {
|
||||
blue_shift++;
|
||||
blue_mask >>= 1;
|
||||
}
|
||||
|
||||
for (int y = 0; y < img->height; y++) {
|
||||
for (int x = 0; x < img->width; x++) {
|
||||
unsigned long pixel = XGetPixel(img, x, y);
|
||||
row[x * 3 + 0] = (pixel >> red_shift) & 0xFF;
|
||||
row[x * 3 + 1] = (pixel >> green_shift) & 0xFF;
|
||||
row[x * 3 + 2] = (pixel >> blue_shift) & 0xFF;
|
||||
}
|
||||
png_write_row(png, row);
|
||||
}
|
||||
|
||||
png_write_end(png, info);
|
||||
|
||||
dwn_free(row);
|
||||
png_destroy_write_struct(&png, &info);
|
||||
|
||||
result->data = buf.data;
|
||||
result->size = buf.size;
|
||||
result->width = img->width;
|
||||
result->height = img->height;
|
||||
|
||||
return SCREENSHOT_OK;
|
||||
}
|
||||
|
||||
ScreenshotStatus screenshot_capture_fullscreen(ScreenshotResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (dwn == NULL || dwn->display == NULL) {
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
memset(result, 0, sizeof(ScreenshotResult));
|
||||
|
||||
XImage *img = XGetImage(dwn->display, dwn->root,
|
||||
0, 0, dwn->screen_width, dwn->screen_height,
|
||||
AllPlanes, ZPixmap);
|
||||
if (img == NULL) {
|
||||
LOG_ERROR("XGetImage failed for fullscreen capture");
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
ScreenshotStatus status = ximage_to_png(img, result);
|
||||
XDestroyImage(img);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
ScreenshotStatus screenshot_capture_window(Window window, ScreenshotResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (dwn == NULL || dwn->display == NULL) {
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
if (window == None) {
|
||||
return SCREENSHOT_ERROR_WINDOW_NOT_FOUND;
|
||||
}
|
||||
|
||||
memset(result, 0, sizeof(ScreenshotResult));
|
||||
|
||||
XWindowAttributes wa;
|
||||
if (!XGetWindowAttributes(dwn->display, window, &wa)) {
|
||||
return SCREENSHOT_ERROR_WINDOW_NOT_FOUND;
|
||||
}
|
||||
|
||||
int x, y;
|
||||
Window child;
|
||||
XTranslateCoordinates(dwn->display, window, dwn->root, 0, 0, &x, &y, &child);
|
||||
|
||||
int width = wa.width;
|
||||
int height = wa.height;
|
||||
|
||||
if (x < 0) {
|
||||
width += x;
|
||||
x = 0;
|
||||
}
|
||||
if (y < 0) {
|
||||
height += y;
|
||||
y = 0;
|
||||
}
|
||||
if (x + width > dwn->screen_width) {
|
||||
width = dwn->screen_width - x;
|
||||
}
|
||||
if (y + height > dwn->screen_height) {
|
||||
height = dwn->screen_height - y;
|
||||
}
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
XImage *img = XGetImage(dwn->display, dwn->root, x, y, width, height,
|
||||
AllPlanes, ZPixmap);
|
||||
if (img == NULL) {
|
||||
LOG_ERROR("XGetImage failed for window capture");
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
ScreenshotStatus status = ximage_to_png(img, result);
|
||||
XDestroyImage(img);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
ScreenshotStatus screenshot_capture_active(ScreenshotResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (dwn == NULL) {
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
Workspace *ws = workspace_get_current();
|
||||
if (ws == NULL || ws->focused == NULL) {
|
||||
return SCREENSHOT_ERROR_WINDOW_NOT_FOUND;
|
||||
}
|
||||
|
||||
Client *c = ws->focused;
|
||||
Window target = (c->frame != None) ? c->frame : c->window;
|
||||
|
||||
return screenshot_capture_window(target, result);
|
||||
}
|
||||
|
||||
ScreenshotStatus screenshot_capture_area(int x, int y, int width, int height, ScreenshotResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (dwn == NULL || dwn->display == NULL) {
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
memset(result, 0, sizeof(ScreenshotResult));
|
||||
|
||||
if (x < 0) {
|
||||
width += x;
|
||||
x = 0;
|
||||
}
|
||||
if (y < 0) {
|
||||
height += y;
|
||||
y = 0;
|
||||
}
|
||||
if (x + width > dwn->screen_width) {
|
||||
width = dwn->screen_width - x;
|
||||
}
|
||||
if (y + height > dwn->screen_height) {
|
||||
height = dwn->screen_height - y;
|
||||
}
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
XImage *img = XGetImage(dwn->display, dwn->root, x, y, width, height,
|
||||
AllPlanes, ZPixmap);
|
||||
if (img == NULL) {
|
||||
LOG_ERROR("XGetImage failed for area capture");
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
ScreenshotStatus status = ximage_to_png(img, result);
|
||||
XDestroyImage(img);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void *screenshot_worker(void *arg)
|
||||
{
|
||||
ScreenshotRequest *req = (ScreenshotRequest *)arg;
|
||||
|
||||
if (atomic_load(&screenshot_shutting_down)) {
|
||||
req->state = SCREENSHOT_STATE_ERROR;
|
||||
req->status = SCREENSHOT_ERROR_X11;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (req->mode) {
|
||||
case SCREENSHOT_MODE_FULLSCREEN:
|
||||
req->status = screenshot_capture_fullscreen(&req->result);
|
||||
break;
|
||||
case SCREENSHOT_MODE_WINDOW:
|
||||
req->status = screenshot_capture_window(req->window, &req->result);
|
||||
break;
|
||||
case SCREENSHOT_MODE_ACTIVE:
|
||||
req->status = screenshot_capture_active(&req->result);
|
||||
break;
|
||||
case SCREENSHOT_MODE_AREA:
|
||||
req->status = screenshot_capture_area(req->x, req->y,
|
||||
req->width, req->height,
|
||||
&req->result);
|
||||
break;
|
||||
}
|
||||
|
||||
req->state = (req->status == SCREENSHOT_OK)
|
||||
? SCREENSHOT_STATE_COMPLETED
|
||||
: SCREENSHOT_STATE_ERROR;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScreenshotRequest *screenshot_capture_async(ScreenshotMode mode,
|
||||
void (*callback)(ScreenshotRequest *))
|
||||
{
|
||||
if (!initialized || atomic_load(&screenshot_shutting_down)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScreenshotRequest *req = dwn_calloc(1, sizeof(ScreenshotRequest));
|
||||
if (req == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->mode = mode;
|
||||
req->state = SCREENSHOT_STATE_PENDING;
|
||||
req->callback = callback;
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
req->next = screenshot_queue;
|
||||
screenshot_queue = req;
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
if (pthread_create(&req->thread, NULL, screenshot_worker, req) != 0) {
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
ScreenshotRequest **pp = &screenshot_queue;
|
||||
while (*pp != NULL) {
|
||||
if (*pp == req) {
|
||||
*pp = req->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
dwn_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Screenshot async request started (mode=%d)", mode);
|
||||
return req;
|
||||
}
|
||||
|
||||
ScreenshotRequest *screenshot_capture_window_async(Window window,
|
||||
void (*callback)(ScreenshotRequest *))
|
||||
{
|
||||
if (!initialized || atomic_load(&screenshot_shutting_down)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScreenshotRequest *req = dwn_calloc(1, sizeof(ScreenshotRequest));
|
||||
if (req == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->mode = SCREENSHOT_MODE_WINDOW;
|
||||
req->window = window;
|
||||
req->state = SCREENSHOT_STATE_PENDING;
|
||||
req->callback = callback;
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
req->next = screenshot_queue;
|
||||
screenshot_queue = req;
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
if (pthread_create(&req->thread, NULL, screenshot_worker, req) != 0) {
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
ScreenshotRequest **pp = &screenshot_queue;
|
||||
while (*pp != NULL) {
|
||||
if (*pp == req) {
|
||||
*pp = req->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
dwn_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Screenshot async window request started");
|
||||
return req;
|
||||
}
|
||||
|
||||
ScreenshotRequest *screenshot_capture_area_async(int x, int y, int w, int h,
|
||||
void (*callback)(ScreenshotRequest *))
|
||||
{
|
||||
if (!initialized || atomic_load(&screenshot_shutting_down)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScreenshotRequest *req = dwn_calloc(1, sizeof(ScreenshotRequest));
|
||||
if (req == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->mode = SCREENSHOT_MODE_AREA;
|
||||
req->x = x;
|
||||
req->y = y;
|
||||
req->width = w;
|
||||
req->height = h;
|
||||
req->state = SCREENSHOT_STATE_PENDING;
|
||||
req->callback = callback;
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
req->next = screenshot_queue;
|
||||
screenshot_queue = req;
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
if (pthread_create(&req->thread, NULL, screenshot_worker, req) != 0) {
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
ScreenshotRequest **pp = &screenshot_queue;
|
||||
while (*pp != NULL) {
|
||||
if (*pp == req) {
|
||||
*pp = req->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
dwn_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Screenshot async area request started");
|
||||
return req;
|
||||
}
|
||||
|
||||
void screenshot_process_pending(void)
|
||||
{
|
||||
if (atomic_load(&screenshot_shutting_down)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
ScreenshotRequest **pp = &screenshot_queue;
|
||||
|
||||
while (*pp != NULL) {
|
||||
ScreenshotRequest *req = *pp;
|
||||
|
||||
if (req->state == SCREENSHOT_STATE_COMPLETED ||
|
||||
req->state == SCREENSHOT_STATE_ERROR) {
|
||||
|
||||
pthread_join(req->thread, NULL);
|
||||
|
||||
*pp = req->next;
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
if (req->callback != NULL) {
|
||||
req->callback(req);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
} else {
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
}
|
||||
|
||||
void screenshot_cancel(ScreenshotRequest *req)
|
||||
{
|
||||
if (req == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
ScreenshotRequest **pp = &screenshot_queue;
|
||||
while (*pp != NULL) {
|
||||
if (*pp == req) {
|
||||
*pp = req->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
pthread_cancel(req->thread);
|
||||
pthread_join(req->thread, NULL);
|
||||
|
||||
screenshot_result_free(&req->result);
|
||||
dwn_free(req);
|
||||
}
|
||||
@ -11,6 +11,7 @@
|
||||
#include "util.h"
|
||||
#include "config.h"
|
||||
#include "panel.h"
|
||||
#include "api.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
@ -114,7 +115,7 @@ void workspace_switch(int index)
|
||||
XUngrabServer(dwn->display);
|
||||
XSync(dwn->display, False);
|
||||
|
||||
(void)old_workspace;
|
||||
api_emit_workspace_switched(old_workspace, index);
|
||||
}
|
||||
|
||||
void workspace_switch_next(void)
|
||||
@ -143,6 +144,7 @@ void workspace_add_client(int workspace, Client *client)
|
||||
|
||||
client->workspace = workspace;
|
||||
|
||||
api_emit_workspace_window_added(workspace, client->window);
|
||||
}
|
||||
|
||||
void workspace_remove_client(int workspace, Client *client)
|
||||
@ -159,6 +161,8 @@ void workspace_remove_client(int workspace, Client *client)
|
||||
if (ws->focused == client) {
|
||||
ws->focused = NULL;
|
||||
}
|
||||
|
||||
api_emit_workspace_window_removed(workspace, client->window);
|
||||
}
|
||||
|
||||
void workspace_move_client(Client *client, int new_workspace)
|
||||
@ -179,22 +183,20 @@ void workspace_move_client(Client *client, int new_workspace)
|
||||
XGrabServer(dwn->display);
|
||||
|
||||
workspace_remove_client(old_workspace, client);
|
||||
|
||||
workspace_add_client(new_workspace, client);
|
||||
|
||||
atoms_set_window_desktop(client->window, new_workspace);
|
||||
|
||||
if (new_workspace != dwn->current_workspace) {
|
||||
client_hide(client);
|
||||
} else {
|
||||
client_show(client);
|
||||
Workspace *target_ws = workspace_get(new_workspace);
|
||||
if (target_ws != NULL) {
|
||||
target_ws->focused = client;
|
||||
}
|
||||
|
||||
workspace_arrange(old_workspace);
|
||||
workspace_arrange(new_workspace);
|
||||
|
||||
XUngrabServer(dwn->display);
|
||||
XSync(dwn->display, False);
|
||||
|
||||
workspace_switch(new_workspace);
|
||||
}
|
||||
|
||||
Client *workspace_get_first_client(int workspace)
|
||||
@ -226,6 +228,7 @@ void workspace_set_layout(int workspace, LayoutType layout)
|
||||
return;
|
||||
}
|
||||
|
||||
int old_layout = ws->layout;
|
||||
ws->layout = layout;
|
||||
|
||||
XGrabServer(dwn->display);
|
||||
@ -233,6 +236,8 @@ void workspace_set_layout(int workspace, LayoutType layout)
|
||||
XUngrabServer(dwn->display);
|
||||
XSync(dwn->display, False);
|
||||
|
||||
api_emit_workspace_layout_changed(workspace, old_layout, layout);
|
||||
|
||||
LOG_DEBUG("Workspace %d layout set to %d", workspace + 1, layout);
|
||||
}
|
||||
|
||||
@ -249,6 +254,7 @@ void workspace_cycle_layout(int workspace)
|
||||
return;
|
||||
}
|
||||
|
||||
int old_layout = ws->layout;
|
||||
ws->layout = (ws->layout + 1) % LAYOUT_COUNT;
|
||||
|
||||
XGrabServer(dwn->display);
|
||||
@ -256,6 +262,8 @@ void workspace_cycle_layout(int workspace)
|
||||
XUngrabServer(dwn->display);
|
||||
XSync(dwn->display, False);
|
||||
|
||||
api_emit_workspace_layout_changed(workspace, old_layout, ws->layout);
|
||||
|
||||
const char *layout_names[] = { "Tiling", "Floating", "Monocle" };
|
||||
LOG_INFO("Workspace %d: %s layout", workspace + 1, layout_names[ws->layout]);
|
||||
}
|
||||
@ -267,6 +275,8 @@ void workspace_set_master_ratio(int workspace, float ratio)
|
||||
return;
|
||||
}
|
||||
|
||||
float old_ratio = ws->master_ratio;
|
||||
|
||||
if (ratio < 0.1f) ratio = 0.1f;
|
||||
if (ratio > 0.9f) ratio = 0.9f;
|
||||
|
||||
@ -276,6 +286,10 @@ void workspace_set_master_ratio(int workspace, float ratio)
|
||||
workspace_arrange(workspace);
|
||||
XUngrabServer(dwn->display);
|
||||
XSync(dwn->display, False);
|
||||
|
||||
if (old_ratio != ws->master_ratio) {
|
||||
api_emit_workspace_master_ratio_changed(workspace, old_ratio, ws->master_ratio);
|
||||
}
|
||||
}
|
||||
|
||||
void workspace_adjust_master_ratio(int workspace, float delta)
|
||||
@ -293,6 +307,8 @@ void workspace_set_master_count(int workspace, int count)
|
||||
return;
|
||||
}
|
||||
|
||||
int old_count = ws->master_count;
|
||||
|
||||
if (count < 1) count = 1;
|
||||
ws->master_count = count;
|
||||
|
||||
@ -300,6 +316,10 @@ void workspace_set_master_count(int workspace, int count)
|
||||
workspace_arrange(workspace);
|
||||
XUngrabServer(dwn->display);
|
||||
XSync(dwn->display, False);
|
||||
|
||||
if (old_count != ws->master_count) {
|
||||
api_emit_workspace_master_count_changed(workspace, old_count, ws->master_count);
|
||||
}
|
||||
}
|
||||
|
||||
void workspace_adjust_master_count(int workspace, int delta)
|
||||
@ -323,6 +343,8 @@ void workspace_arrange(int workspace)
|
||||
}
|
||||
|
||||
layout_arrange(workspace);
|
||||
|
||||
api_emit_workspace_arranged(workspace, ws->layout);
|
||||
}
|
||||
|
||||
void workspace_arrange_current(void)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user