Compare commits
7 Commits
f4af2b1f1e
...
b0b1a854cf
| Author | SHA1 | Date | |
|---|---|---|---|
| b0b1a854cf | |||
| 2017a9b666 | |||
| a75c1d90cc | |||
| deb173e2d2 | |||
| b59c279bb0 | |||
| f55f024d22 | |||
| ea12677dac |
6
Makefile
6
Makefile
@ -5,7 +5,7 @@
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -Wpedantic -Wshadow -O2 -I./include
|
||||
CFLAGS += -fstack-protector-strong -D_FORTIFY_SOURCE=2
|
||||
LDFLAGS = -lX11 -lXext -lXinerama -lXrandr -lXft -lfontconfig -ldbus-1 -lcurl -lm -lpthread -lXtst
|
||||
LDFLAGS = -lX11 -lXext -lXinerama -lXrandr -lXft -lfontconfig -ldbus-1 -lcurl -lm -lpthread -lXtst -lXi
|
||||
|
||||
# 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 xtst libpng tesseract lept 2>/dev/null)
|
||||
PKG_LIBS := $(shell pkg-config --libs x11 xext xinerama xrandr xft fontconfig dbus-1 xtst libpng tesseract lept 2>/dev/null)
|
||||
PKG_CFLAGS := $(shell pkg-config --cflags x11 xext xinerama xrandr xft fontconfig dbus-1 xtst xi libpng tesseract lept 2>/dev/null)
|
||||
PKG_LIBS := $(shell pkg-config --libs x11 xext xinerama xrandr xft fontconfig dbus-1 xtst xi libpng tesseract lept 2>/dev/null)
|
||||
|
||||
# Use pkg-config if available
|
||||
ifneq ($(PKG_LIBS),)
|
||||
|
||||
471
README.md
471
README.md
@ -2,100 +2,240 @@
|
||||
|
||||
retoor <retoor@molodetz.nl>
|
||||
|
||||
A production-ready X11 window manager written in ANSI C. DWN implements EWMH and ICCCM protocols for cross-compatibility while providing tiling-first workflow management with integrated desktop components.
|
||||
A production-ready X11 window manager written in ANSI C with EWMH/ICCCM protocol compliance, tiling-first workflow, integrated desktop components, and optional AI integration via OpenRouter API.
|
||||
|
||||
## Architecture
|
||||
## Design Philosophy
|
||||
|
||||
DWN uses a modular architecture with specialized subsystems for window management, layout algorithms, panel rendering, system tray, notifications, and optional AI integration. All state is managed through a global singleton with strict encapsulation patterns.
|
||||
DWN prioritizes a seamless, distraction-free desktop experience:
|
||||
|
||||
The window manager operates with zero window borders and zero gaps between tiles, providing a seamless, professional appearance. Title bars remain at 28px height for window controls.
|
||||
- **Zero borders and gaps**: Windows tile edge-to-edge without visual separation
|
||||
- **Minimal chrome**: 28px title bars provide window controls without excess decoration
|
||||
- **Tiling-first**: Master-stack layout as the default, with floating and monocle modes available
|
||||
- **Keyboard-driven**: Comprehensive shortcuts for all operations
|
||||
- **Modular architecture**: Single-responsibility modules with strict encapsulation
|
||||
- **ANSI C**: Maximum portability and minimal dependencies
|
||||
|
||||
## Core Features
|
||||
## Features
|
||||
|
||||
### Layout Management
|
||||
- **Tiling Mode**: Master-stack layout with configurable ratios
|
||||
- **Floating Mode**: Traditional overlapping windows
|
||||
- **Monocle Mode**: Single fullscreen window
|
||||
- **Composable Snapping**: Quarter-screen, half-screen, and full-screen snapping via Super+Arrow keys
|
||||
### Window Management
|
||||
|
||||
**Layout Modes**
|
||||
- **Tiling**: Master-stack layout with configurable master ratio (0.1-0.9) and master window count
|
||||
- **Floating**: Traditional overlapping windows with free positioning
|
||||
- **Monocle**: Single maximized window per workspace
|
||||
|
||||
**Composable Window Snapping**
|
||||
- Super+Arrow keys snap windows to half-screen positions
|
||||
- Double-press extends to full width/height
|
||||
- Combinations create quarter-screen positions (e.g., Super+Left then Super+Up = top-left quarter)
|
||||
|
||||
**Directional Resizing**
|
||||
- Resize windows from any edge or corner
|
||||
- Respects layout bounds and snap constraints
|
||||
|
||||
**Window States**
|
||||
- Fullscreen (Alt+F11): Covers entire screen including panels
|
||||
- Maximized (Alt+F10): Fills usable area with title bar
|
||||
- Minimized (Alt+F9): Hidden from view
|
||||
- Floating (Super+F9): Exempt from tiling layout
|
||||
- Sticky: Visible on all workspaces
|
||||
- Urgent: Demands attention with taskbar highlight
|
||||
|
||||
### Workspace System
|
||||
|
||||
- 9 independent virtual workspaces (F1-F9)
|
||||
- Per-workspace layout persistence
|
||||
- Workspace indicators in panel
|
||||
- Fast workspace switching with Shift+F1-F9 to move windows
|
||||
- MRU (Most Recently Used) focus stack per workspace
|
||||
- Alt-Tab cycles through MRU order
|
||||
- Shift+F1-F9 moves focused window to target workspace
|
||||
- Ctrl+Alt+Left/Right navigates adjacent workspaces
|
||||
|
||||
### Integrated Components
|
||||
- **Top Panel**: Workspace indicators, taskbar, system tray, clock
|
||||
- **Bottom Panel**: Additional status information
|
||||
- **System Tray**: XEmbed protocol implementation supporting external applications
|
||||
- **System Widgets**: Battery indicator, volume control, WiFi manager
|
||||
- **Notification Daemon**: D-Bus org.freedesktop.Notifications implementation
|
||||
### Panels
|
||||
|
||||
**Top Panel**
|
||||
- Workspace indicators with active/inactive/urgent states
|
||||
- Taskbar with window buttons
|
||||
- System tray with XEmbed protocol support
|
||||
- Battery indicator (multi-battery support)
|
||||
- Volume control with slider
|
||||
- Top memory process display
|
||||
- Top CPU process display
|
||||
- Key press counter
|
||||
- Mouse distance traveled
|
||||
- Clock
|
||||
|
||||
**Bottom Panel**
|
||||
- News ticker with scrolling headlines
|
||||
- Configurable visibility
|
||||
|
||||
### System Tray
|
||||
|
||||
**XEmbed Protocol**
|
||||
- Acquires `_NET_SYSTEM_TRAY_S0` selection
|
||||
- Docks external application icons (nm-applet, blueman, Telegram, etc.)
|
||||
- Forwards click events to icon windows
|
||||
- Automatic cleanup on application close
|
||||
|
||||
**Built-in Widgets**
|
||||
- Battery: Percentage display, charging indicator, multi-battery support
|
||||
- Volume: Click for slider, scroll to adjust, right-click to mute
|
||||
- Process monitors: Rotating display of top CPU/memory consumers
|
||||
|
||||
### Ambient Glow Effects
|
||||
|
||||
Visual feedback system with phase-offset animations:
|
||||
- Panel background subtle color cycling
|
||||
- Workspace indicator glow
|
||||
- Taskbar button highlighting
|
||||
- Clock and statistics display
|
||||
- Window title text glow on focus
|
||||
- Configurable animation speed
|
||||
|
||||
### Activity Tracking
|
||||
|
||||
- **Key press counter**: Total keypresses displayed in panel
|
||||
- **Mouse distance**: Cumulative mouse movement in pixels
|
||||
- **XInput2 integration**: Raw event monitoring for accurate tracking
|
||||
|
||||
### Focus Transitions
|
||||
|
||||
- Animated color transitions on window focus changes
|
||||
- Taskbar button color interpolation
|
||||
- Title bar glow animations
|
||||
- Bold font rendering for Alt-Tab selection
|
||||
|
||||
### Notification System
|
||||
|
||||
D-Bus implementation of `org.freedesktop.Notifications`:
|
||||
- Receives notifications from all applications
|
||||
- Stacked display with timeout management
|
||||
- Urgency levels: low, normal, critical
|
||||
- Click to dismiss
|
||||
|
||||
### News Ticker
|
||||
- Real-time news feed in panel
|
||||
- Scrolling article titles
|
||||
- Super+Return to open current article in browser
|
||||
- Configurable news sources
|
||||
|
||||
- Fetches headlines from configured sources
|
||||
- Smooth pixel-based scrolling animation
|
||||
- Super+Return opens current article in browser
|
||||
- Automatic refresh interval
|
||||
- Sentiment indicators (positive/negative/neutral)
|
||||
|
||||
### AI Integration
|
||||
- **Command Palette** (Super+Shift+A): Natural language window management
|
||||
- **Context Analysis** (Super+A): Workspace and task detection
|
||||
- **Exa Semantic Search** (Super+Shift+E): Intelligent web search
|
||||
- Powered by OpenRouter API (supports multiple LLM providers)
|
||||
|
||||
### Interactive Features
|
||||
- **Tutorial System** (Super+T): Step-by-step keyboard shortcut training
|
||||
- **Demo Mode** (Super+Shift+D): Automated feature showcase
|
||||
- **Keyboard Shortcuts Help** (Super+S): Quick reference overlay
|
||||
**Command Palette (Super+Shift+A)**
|
||||
- Natural language command execution
|
||||
- OpenRouter API with configurable model selection
|
||||
- Context-aware responses based on current workspace
|
||||
|
||||
**Context Analysis (Super+A)**
|
||||
- Task type detection (coding, browsing, communication)
|
||||
- Window and application analysis
|
||||
- Intelligent suggestions
|
||||
|
||||
**Exa Semantic Search (Super+Shift+E)**
|
||||
- Meaning-based web search
|
||||
- Results displayed in dmenu/rofi
|
||||
- Select to open in browser
|
||||
|
||||
### Screenshot and OCR
|
||||
|
||||
**Screenshot API**
|
||||
- Fullscreen capture
|
||||
- Active window capture
|
||||
- Area selection capture
|
||||
- Async capture with callbacks
|
||||
- Base64 encoding for API transmission
|
||||
- PNG output
|
||||
|
||||
**OCR API**
|
||||
- Tesseract-based text extraction
|
||||
- Multi-language support
|
||||
- Confidence scoring
|
||||
- Async processing
|
||||
|
||||
### WebSocket API
|
||||
|
||||
Programmatic control on port 8777:
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `get_status` | Window manager state |
|
||||
| `get_workspaces` | Workspace information |
|
||||
| `get_clients` | All managed windows |
|
||||
| `switch_workspace` | Change active workspace |
|
||||
| `focus_client` | Focus specific window |
|
||||
| `run_command` | Execute shell command |
|
||||
| `screenshot` | Capture screen |
|
||||
| `ocr` | Extract text from image |
|
||||
|
||||
### Automation
|
||||
- **XDG Autostart**: Automatic application launching from .desktop files
|
||||
- **Service Manager**: Background process management
|
||||
- **WebSocket API**: Remote control and state monitoring on port 8777
|
||||
|
||||
**XDG Autostart**
|
||||
- Scans `/etc/xdg/autostart/` and `~/.config/autostart/`
|
||||
- Parses `.desktop` files
|
||||
- Custom autostart directory support
|
||||
|
||||
**Service Manager**
|
||||
- Background process management
|
||||
- Automatic restart on failure
|
||||
|
||||
### Interactive Features
|
||||
|
||||
**Tutorial (Super+T)**
|
||||
- Step-by-step keyboard shortcut training
|
||||
- Waits for correct input
|
||||
- Progressive difficulty
|
||||
|
||||
**Demo Mode (Super+Shift+D)**
|
||||
- Automated feature showcase
|
||||
- Configurable timing
|
||||
- Covers all major features
|
||||
|
||||
**Shortcuts Help (Super+S)**
|
||||
- Quick reference overlay
|
||||
- All keyboard bindings displayed
|
||||
|
||||
## Installation
|
||||
|
||||
### Dependencies
|
||||
|
||||
Required packages (via pkg-config):
|
||||
- X11, Xext, Xinerama, Xrandr, Xft
|
||||
Required libraries (via pkg-config):
|
||||
- X11, Xext, Xinerama, Xrandr, Xft, Xi
|
||||
- fontconfig
|
||||
- libdbus-1
|
||||
- libcurl
|
||||
- libm
|
||||
- libpng
|
||||
- tesseract (optional, for OCR)
|
||||
|
||||
### Build Process
|
||||
### Build
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-repo/dwn.git
|
||||
cd dwn
|
||||
|
||||
make deps # Auto-install dependencies (apt/dnf/pacman)
|
||||
make # Build release version with -O2 optimization
|
||||
make install # Install to /usr/local/bin
|
||||
make # Build with -O2 optimization
|
||||
make install # Install to /usr/local/bin (PREFIX configurable)
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
make run # Launch in nested Xephyr window (safe testing)
|
||||
make run # Launch in nested Xephyr window
|
||||
```
|
||||
|
||||
### Build Targets
|
||||
|
||||
```bash
|
||||
make # Release build with optimization
|
||||
make debug # Debug build with -g -DDEBUG
|
||||
make clean # Remove build artifacts
|
||||
make format # Run clang-format
|
||||
make check # Run cppcheck static analysis
|
||||
```
|
||||
| Target | Description |
|
||||
|--------|-------------|
|
||||
| `make` | Release build with optimization |
|
||||
| `make debug` | Debug build with -g -DDEBUG |
|
||||
| `make clean` | Remove build artifacts |
|
||||
| `make format` | Run clang-format |
|
||||
| `make check` | Run cppcheck static analysis |
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration file: `~/.config/dwn/config`
|
||||
Configuration file: `~/.config/dwn/config` (INI format)
|
||||
|
||||
### General Settings
|
||||
### General
|
||||
|
||||
```ini
|
||||
[general]
|
||||
@ -111,10 +251,10 @@ decorations = true
|
||||
|
||||
```ini
|
||||
[appearance]
|
||||
border_width = 0 # 0-50px (0 for seamless)
|
||||
border_width = 0 # 0-50px
|
||||
title_height = 28 # 0-100px
|
||||
panel_height = 32 # 0-100px
|
||||
gap = 0 # 0-100px (0 for seamless)
|
||||
gap = 0 # 0-100px
|
||||
font = fixed
|
||||
```
|
||||
|
||||
@ -154,7 +294,7 @@ notification_bg = #111111
|
||||
notification_fg = #00ffff
|
||||
```
|
||||
|
||||
### AI Integration
|
||||
### AI
|
||||
|
||||
```ini
|
||||
[ai]
|
||||
@ -169,7 +309,7 @@ export OPENROUTER_API_KEY=sk-or-v1-your-key
|
||||
export EXA_API_KEY=your-exa-key
|
||||
```
|
||||
|
||||
Get API keys:
|
||||
API keys:
|
||||
- OpenRouter: https://openrouter.ai/keys
|
||||
- Exa: https://dashboard.exa.ai/api-keys
|
||||
|
||||
@ -178,11 +318,11 @@ Get API keys:
|
||||
```ini
|
||||
[autostart]
|
||||
enabled = true
|
||||
xdg_autostart = true # Scan /etc/xdg/autostart and ~/.config/autostart
|
||||
xdg_autostart = true
|
||||
path = ~/.config/dwn/autostart.d
|
||||
```
|
||||
|
||||
### WebSocket API
|
||||
### API
|
||||
|
||||
```ini
|
||||
[api]
|
||||
@ -190,18 +330,19 @@ enabled = true
|
||||
port = 8777
|
||||
```
|
||||
|
||||
### Demo Mode
|
||||
### Demo
|
||||
|
||||
```ini
|
||||
[demo]
|
||||
step_delay = 4000 # 1000-30000ms between steps
|
||||
ai_timeout = 15000 # 5000-60000ms for AI responses
|
||||
window_timeout = 5000 # 1000-30000ms for window spawns
|
||||
step_delay = 4000 # 1000-30000ms
|
||||
ai_timeout = 15000 # 5000-60000ms
|
||||
window_timeout = 5000 # 1000-30000ms
|
||||
```
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
### Application Launchers
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Ctrl+Alt+T` | Terminal |
|
||||
@ -211,10 +352,11 @@ window_timeout = 5000 # 1000-30000ms for window spawns
|
||||
| `Print` | Screenshot |
|
||||
|
||||
### Window Management
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Alt+F4` | Close window |
|
||||
| `Alt+Tab` | Next window |
|
||||
| `Alt+Tab` | Next window (MRU order) |
|
||||
| `Alt+Shift+Tab` | Previous window |
|
||||
| `Alt+F9` | Toggle minimize |
|
||||
| `Alt+F10` | Toggle maximize |
|
||||
@ -222,6 +364,7 @@ window_timeout = 5000 # 1000-30000ms for window spawns
|
||||
| `Super+F9` | Toggle floating |
|
||||
|
||||
### Workspace Navigation
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `F1` - `F9` | Switch to workspace 1-9 |
|
||||
@ -230,85 +373,109 @@ window_timeout = 5000 # 1000-30000ms for window spawns
|
||||
| `Ctrl+Alt+Left` | Previous workspace |
|
||||
|
||||
### Layout Control
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Super+Space` | Cycle layout (tiling/floating/monocle) |
|
||||
| `Super+Space` | Cycle layout mode |
|
||||
| `Super+H` | Shrink master area |
|
||||
| `Super+L` | Expand master area |
|
||||
| `Super+I` | Increase master count |
|
||||
| `Super+D` | Decrease master count |
|
||||
| `Super+D` | Decrease master count / Show desktop |
|
||||
|
||||
### Window Snapping
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Super+Left` | Snap left (50% or 100% width) |
|
||||
| `Super+Right` | Snap right (50% or 100% width) |
|
||||
| `Super+Up` | Snap top (50% or 100% height) |
|
||||
| `Super+Down` | Snap bottom (50% or 100% height) |
|
||||
|
||||
Snapping is composable: Super+Left then Super+Up snaps to top-left quarter.
|
||||
| `Super+Left` | Snap left (50% or 100%) |
|
||||
| `Super+Right` | Snap right (50% or 100%) |
|
||||
| `Super+Up` | Snap top (50% or 100%) |
|
||||
| `Super+Down` | Snap bottom (50% or 100%) |
|
||||
|
||||
### AI Features
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Super+A` | Show AI context analysis |
|
||||
| `Super+Shift+A` | AI command palette |
|
||||
| `Super+A` | Context analysis |
|
||||
| `Super+Shift+A` | Command palette |
|
||||
| `Super+Shift+E` | Exa semantic search |
|
||||
|
||||
### News & Help
|
||||
### News and Help
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Super+Return` | Open current news article |
|
||||
| `Super+S` | Show shortcuts help |
|
||||
| `Super+Return` | Open current article |
|
||||
| `Super+S` | Shortcuts help |
|
||||
| `Super+T` | Interactive tutorial |
|
||||
| `Super+Shift+D` | Toggle demo mode |
|
||||
| `Super+Backspace` | Quit DWN |
|
||||
|
||||
## WebSocket API
|
||||
## Architecture
|
||||
|
||||
Connect to `ws://localhost:8777/ws` for programmatic control.
|
||||
### Module Structure
|
||||
|
||||
### Commands
|
||||
| Module | Responsibility |
|
||||
|--------|----------------|
|
||||
| main.c | X11 initialization, event loop, signal handling |
|
||||
| client.c | Window management, focus, frame creation |
|
||||
| workspace.c | Virtual desktop management, MRU stacks |
|
||||
| layout.c | Tiling, floating, monocle algorithms |
|
||||
| decorations.c | Title bars, borders, glow animations |
|
||||
| panel.c | Top/bottom panel rendering, widgets |
|
||||
| systray.c | XEmbed system tray, battery/volume widgets |
|
||||
| notifications.c | D-Bus notification daemon |
|
||||
| atoms.c | EWMH/ICCCM atom management |
|
||||
| keys.c | Keyboard shortcut handling |
|
||||
| config.c | INI configuration parser |
|
||||
| ai.c | OpenRouter API, Exa search integration |
|
||||
| news.c | News ticker with scrolling animation |
|
||||
| screenshot.c | Screen capture API |
|
||||
| ocr.c | Tesseract text extraction |
|
||||
| autostart.c | XDG autostart implementation |
|
||||
| services.c | Background service management |
|
||||
| demo.c | Automated feature demonstration |
|
||||
| api.c | WebSocket server |
|
||||
| util.c | Logging, memory, string utilities, glow effects |
|
||||
|
||||
#### get_status
|
||||
Returns window manager state.
|
||||
```json
|
||||
{"command": "get_status"}
|
||||
```
|
||||
### Design Patterns
|
||||
|
||||
#### get_workspaces
|
||||
Returns array of workspace objects.
|
||||
```json
|
||||
{"command": "get_workspaces"}
|
||||
```
|
||||
**Encapsulation**
|
||||
- Opaque pointer types hide internal structures
|
||||
- Header exposes only public API
|
||||
- Implementation details remain private
|
||||
|
||||
#### get_clients
|
||||
Returns array of all managed windows.
|
||||
```json
|
||||
{"command": "get_clients"}
|
||||
```
|
||||
**Error Handling**
|
||||
- Status code return values
|
||||
- Output parameters for results
|
||||
- Enum-based error codes
|
||||
|
||||
Response includes window ID, title, class, workspace, geometry, focus state.
|
||||
**Resource Management**
|
||||
- Goto cleanup pattern for multi-resource functions
|
||||
- Every allocation has corresponding free
|
||||
- XGrabServer/XUngrabServer for atomic X11 operations
|
||||
|
||||
#### switch_workspace
|
||||
Change active workspace.
|
||||
```json
|
||||
{"command": "switch_workspace", "workspace": 2}
|
||||
```
|
||||
**Event Architecture**
|
||||
- Select-based multiplexed I/O
|
||||
- 60fps animation loop with 16ms timeout
|
||||
- Async request queues for AI/screenshot/OCR
|
||||
|
||||
#### run_command
|
||||
Execute shell command.
|
||||
```json
|
||||
{"command": "run_command", "exec": "firefox"}
|
||||
```
|
||||
**Naming Conventions**
|
||||
- Module prefix for functions: `client_focus()`, `workspace_switch()`
|
||||
- Snake_case for functions and variables
|
||||
- CamelCase for types and structs
|
||||
|
||||
#### focus_client
|
||||
Focus specific window by ID.
|
||||
```json
|
||||
{"command": "focus_client", "window": 12345678}
|
||||
```
|
||||
### Key Constants
|
||||
|
||||
### Python Example
|
||||
| Constant | Value |
|
||||
|----------|-------|
|
||||
| MAX_CLIENTS | 256 |
|
||||
| MAX_WORKSPACES | 9 |
|
||||
| MAX_MONITORS | 8 |
|
||||
| MAX_NOTIFICATIONS | 32 |
|
||||
| MAX_KEYBINDINGS | 64 |
|
||||
|
||||
## WebSocket API Examples
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import websocket
|
||||
@ -316,76 +483,50 @@ import json
|
||||
|
||||
ws = websocket.create_connection("ws://localhost:8777/ws")
|
||||
|
||||
ws.send(json.dumps({"command": "get_clients"}))
|
||||
clients = json.loads(ws.recv())
|
||||
|
||||
ws.send(json.dumps({"command": "switch_workspace", "workspace": 3}))
|
||||
|
||||
ws.send(json.dumps({"command": "get_clients"}))
|
||||
print(json.loads(ws.recv()))
|
||||
ws.send(json.dumps({"command": "focus_client", "window": 12345678}))
|
||||
|
||||
ws.close()
|
||||
```
|
||||
|
||||
## Module Architecture
|
||||
### Response Format
|
||||
|
||||
### Core Modules (src/)
|
||||
|
||||
| Module | Responsibility |
|
||||
|--------|----------------|
|
||||
| main.c | X11 initialization, event loop, signal handling |
|
||||
| client.c | Window management, focus, frame creation |
|
||||
| workspace.c | Virtual desktop management |
|
||||
| layout.c | Tiling, floating, monocle algorithms |
|
||||
| decorations.c | Title bars and borders |
|
||||
| panel.c | Top and bottom panel rendering |
|
||||
| systray.c | XEmbed system tray, battery/volume/WiFi widgets |
|
||||
| notifications.c | D-Bus notification daemon |
|
||||
| atoms.c | EWMH/ICCCM atom management |
|
||||
| keys.c | Keyboard shortcut handling |
|
||||
| config.c | INI configuration parser |
|
||||
| ai.c | OpenRouter API, Exa search integration |
|
||||
| news.c | News ticker with scrolling animation |
|
||||
| autostart.c | XDG autostart implementation |
|
||||
| services.c | Background service management |
|
||||
| demo.c | Automated feature demonstration |
|
||||
| api.c | WebSocket server and JSON handler |
|
||||
| util.c | Logging, memory, string utilities |
|
||||
|
||||
### Design Patterns
|
||||
|
||||
- **Encapsulation**: Opaque pointer types hide internal structures
|
||||
- **Error Handling**: Status codes with output parameters
|
||||
- **Resource Management**: Goto cleanup pattern for multi-resource functions
|
||||
- **Modularity**: Single responsibility per source file
|
||||
- **Naming**: Module prefix convention (e.g., `client_focus()`, `workspace_switch()`)
|
||||
|
||||
## Development
|
||||
|
||||
### Standards
|
||||
|
||||
- ANSI C (C89/C90) for maximum compatibility
|
||||
- Snake_case for functions and variables
|
||||
- CamelCase for types and structs
|
||||
- No external dependencies beyond X11 and standard libraries
|
||||
- Defensive programming: validate inputs, check malloc, null-terminate strings
|
||||
|
||||
### Code Style
|
||||
|
||||
```bash
|
||||
make format # Apply clang-format
|
||||
make check # Run cppcheck
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"clients": [
|
||||
{
|
||||
"window": 12345678,
|
||||
"title": "Firefox",
|
||||
"class": "firefox",
|
||||
"workspace": 1,
|
||||
"x": 0, "y": 32,
|
||||
"width": 960, "height": 540,
|
||||
"focused": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
dwn/
|
||||
├── src/ # Implementation
|
||||
├── include/ # Headers
|
||||
├── src/ # Implementation files
|
||||
├── include/ # Header files
|
||||
├── config/ # Configuration templates
|
||||
├── scripts/ # Utilities and API examples
|
||||
├── scripts/ # Utility scripts
|
||||
├── examples/ # API usage examples
|
||||
├── site/ # Documentation website
|
||||
└── build/ # Build artifacts
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see LICENSE file for details.
|
||||
MIT License - see LICENSE file.
|
||||
|
||||
BIN
build/ai.o
BIN
build/ai.o
Binary file not shown.
BIN
build/atoms.o
BIN
build/atoms.o
Binary file not shown.
BIN
build/client.o
BIN
build/client.o
Binary file not shown.
@ -1,7 +1,9 @@
|
||||
build/decorations.o: src/decorations.c include/decorations.h \
|
||||
include/dwn.h include/client.h include/config.h include/util.h
|
||||
include/dwn.h include/client.h include/config.h include/util.h \
|
||||
include/workspace.h
|
||||
include/decorations.h:
|
||||
include/dwn.h:
|
||||
include/client.h:
|
||||
include/config.h:
|
||||
include/util.h:
|
||||
include/workspace.h:
|
||||
|
||||
Binary file not shown.
BIN
build/keys.o
BIN
build/keys.o
Binary file not shown.
BIN
build/layout.o
BIN
build/layout.o
Binary file not shown.
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
BIN
build/news.o
BIN
build/news.o
Binary file not shown.
Binary file not shown.
BIN
build/panel.o
BIN
build/panel.o
Binary file not shown.
BIN
build/systray.o
BIN
build/systray.o
Binary file not shown.
BIN
build/util.o
BIN
build/util.o
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/api.h
|
||||
include/config.h include/panel.h include/api.h include/decorations.h
|
||||
include/workspace.h:
|
||||
include/dwn.h:
|
||||
include/client.h:
|
||||
@ -10,3 +10,4 @@ include/util.h:
|
||||
include/config.h:
|
||||
include/panel.h:
|
||||
include/api.h:
|
||||
include/decorations.h:
|
||||
|
||||
Binary file not shown.
@ -70,4 +70,9 @@ Client *client_get_prev(Client *client);
|
||||
Client *client_get_first(void);
|
||||
Client *client_get_last(void);
|
||||
|
||||
void client_start_focus_animation(Client *client, bool gaining_focus);
|
||||
unsigned long client_get_animated_title_color(Client *client);
|
||||
unsigned long client_get_glow_text_color(Client *client);
|
||||
void client_update_animations(void);
|
||||
|
||||
#endif
|
||||
|
||||
@ -25,6 +25,11 @@
|
||||
#define MAX_NOTIFICATIONS 32
|
||||
#define MAX_KEYBINDINGS 64
|
||||
|
||||
#define RESIZE_LEFT 1
|
||||
#define RESIZE_RIGHT 2
|
||||
#define RESIZE_TOP 4
|
||||
#define RESIZE_BOTTOM 8
|
||||
|
||||
#define DEFAULT_BORDER_WIDTH 0
|
||||
#define DEFAULT_TITLE_HEIGHT 28
|
||||
#define DEFAULT_PANEL_HEIGHT 32
|
||||
@ -82,6 +87,21 @@ typedef struct {
|
||||
long timestamp;
|
||||
} SnapConstraint;
|
||||
|
||||
typedef struct {
|
||||
long start_time;
|
||||
float progress;
|
||||
unsigned long from_color;
|
||||
unsigned long to_color;
|
||||
bool active;
|
||||
} ColorAnimation;
|
||||
|
||||
typedef struct {
|
||||
float phase;
|
||||
float speed;
|
||||
unsigned long base_color;
|
||||
bool active;
|
||||
} TextGlowAnimation;
|
||||
|
||||
typedef struct Client Client;
|
||||
typedef struct Workspace Workspace;
|
||||
typedef struct Monitor Monitor;
|
||||
@ -101,6 +121,9 @@ struct Client {
|
||||
char title[256];
|
||||
char class[64];
|
||||
SnapConstraint snap;
|
||||
unsigned long taskbar_color;
|
||||
ColorAnimation title_anim;
|
||||
TextGlowAnimation text_glow;
|
||||
Client *next;
|
||||
Client *prev;
|
||||
Client *mru_next;
|
||||
@ -161,6 +184,7 @@ typedef struct {
|
||||
GC gc;
|
||||
XFontStruct *font;
|
||||
XftFont *xft_font;
|
||||
XftFont *xft_font_bold;
|
||||
Colormap colormap;
|
||||
|
||||
Client *drag_client;
|
||||
@ -168,6 +192,7 @@ typedef struct {
|
||||
int drag_orig_x, drag_orig_y;
|
||||
int drag_orig_w, drag_orig_h;
|
||||
bool resizing;
|
||||
int drag_direction;
|
||||
|
||||
Client *pending_focus_client;
|
||||
long pending_focus_time;
|
||||
@ -178,8 +203,34 @@ typedef struct {
|
||||
bool desktop_shown;
|
||||
Window desktop_minimized[MAX_CLIENTS];
|
||||
int desktop_minimized_count;
|
||||
|
||||
float ambient_phase;
|
||||
float ambient_speed;
|
||||
long typing_activity_time;
|
||||
|
||||
unsigned long key_press_count;
|
||||
long key_delete_flicker_time;
|
||||
int xi2_opcode;
|
||||
double mouse_distance_pixels;
|
||||
int last_mouse_x;
|
||||
int last_mouse_y;
|
||||
bool mouse_tracking_initialized;
|
||||
} DWNState;
|
||||
|
||||
#define PHASE_OFFSET_PANEL_BG 0.0f
|
||||
#define PHASE_OFFSET_WORKSPACES 0.8f
|
||||
#define PHASE_OFFSET_TASKBAR 1.6f
|
||||
#define PHASE_OFFSET_CLOCK 2.4f
|
||||
#define PHASE_OFFSET_STATS 3.2f
|
||||
#define PHASE_OFFSET_TOP_MEM 4.0f
|
||||
#define PHASE_OFFSET_TOP_CPU 4.4f
|
||||
#define PHASE_OFFSET_DECORATIONS 4.8f
|
||||
#define PHASE_OFFSET_SYSTRAY 5.6f
|
||||
#define PHASE_OFFSET_NEWS 1.2f
|
||||
#define PHASE_OFFSET_KEY_COUNTER 5.2f
|
||||
#define PHASE_OFFSET_MOUSE_DIST 5.8f
|
||||
#define PHASE_OFFSET_DISK_SPACE 6.2f
|
||||
|
||||
extern DWNState *dwn;
|
||||
|
||||
int dwn_init(void);
|
||||
|
||||
@ -22,6 +22,7 @@ struct Panel {
|
||||
int width, height;
|
||||
bool visible;
|
||||
Pixmap buffer;
|
||||
bool dirty;
|
||||
};
|
||||
|
||||
Panel *panel_create(PanelPosition position);
|
||||
@ -31,6 +32,13 @@ void panels_cleanup(void);
|
||||
|
||||
void panel_render(Panel *panel);
|
||||
void panel_render_all(void);
|
||||
void panel_invalidate(void);
|
||||
void panel_invalidate_taskbar(void);
|
||||
void panel_invalidate_news(void);
|
||||
void panel_set_news_region(int x, int width);
|
||||
void panel_render_news_only(void);
|
||||
bool panel_needs_render(void);
|
||||
bool panel_news_needs_render(void);
|
||||
void panel_render_workspaces(Panel *panel, int x, int *width);
|
||||
void panel_render_taskbar(Panel *panel, int x, int *width);
|
||||
void panel_render_clock(Panel *panel, int x, int *width);
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#define MAX_TRAY_ICONS 32
|
||||
#define MAX_BATTERIES 4
|
||||
#define TRAY_ICON_SIZE 22
|
||||
#define TRAY_ICON_SPACING 4
|
||||
|
||||
@ -59,6 +60,22 @@ typedef struct {
|
||||
int time_remaining;
|
||||
} BatteryState;
|
||||
|
||||
typedef struct {
|
||||
char name[32];
|
||||
bool present;
|
||||
bool charging;
|
||||
int percentage;
|
||||
unsigned long energy_now;
|
||||
unsigned long energy_full;
|
||||
} SingleBattery;
|
||||
|
||||
typedef struct {
|
||||
int count;
|
||||
SingleBattery batteries[MAX_BATTERIES];
|
||||
bool any_charging;
|
||||
int combined_percentage;
|
||||
} MultiBatteryState;
|
||||
|
||||
typedef struct {
|
||||
Window window;
|
||||
int x, y;
|
||||
@ -70,6 +87,7 @@ typedef struct {
|
||||
extern WifiState wifi_state;
|
||||
extern AudioState audio_state;
|
||||
extern BatteryState battery_state;
|
||||
extern MultiBatteryState multi_battery_state;
|
||||
extern VolumeSlider *volume_slider;
|
||||
|
||||
void systray_init(void);
|
||||
@ -106,6 +124,7 @@ void systray_lock(void);
|
||||
void systray_unlock(void);
|
||||
|
||||
BatteryState systray_get_battery_snapshot(void);
|
||||
MultiBatteryState systray_get_multi_battery_snapshot(void);
|
||||
AudioState systray_get_audio_snapshot(void);
|
||||
|
||||
void xembed_init(void);
|
||||
|
||||
@ -53,6 +53,14 @@ char *expand_path(const char *path);
|
||||
|
||||
unsigned long parse_color(const char *color_str);
|
||||
void color_to_rgb(unsigned long color, int *r, int *g, int *b);
|
||||
unsigned long rgb_to_pixel(int r, int g, int b);
|
||||
unsigned long generate_unique_color(void);
|
||||
unsigned long adjust_color_for_background(unsigned long color, unsigned long bg);
|
||||
unsigned long interpolate_color(unsigned long from, unsigned long to, float progress);
|
||||
unsigned long dim_color(unsigned long color, float factor);
|
||||
unsigned long glow_color(unsigned long base, float phase);
|
||||
unsigned long ambient_glow_bg(unsigned long base, float phase);
|
||||
unsigned long ambient_glow_accent(unsigned long base, float phase);
|
||||
|
||||
long get_time_ms(void);
|
||||
void sleep_ms(int ms);
|
||||
|
||||
100
src/client.c
100
src/client.c
@ -90,6 +90,12 @@ Client *client_create(Window window)
|
||||
|
||||
client_update_title(client);
|
||||
client_update_class(client);
|
||||
client->taskbar_color = generate_unique_color();
|
||||
|
||||
client->text_glow.phase = (float)(rand() % 628) / 100.0f;
|
||||
client->text_glow.speed = 0.8f + (float)(rand() % 40) / 100.0f;
|
||||
client->text_glow.base_color = client->taskbar_color;
|
||||
client->text_glow.active = true;
|
||||
|
||||
return client;
|
||||
}
|
||||
@ -353,9 +359,12 @@ void client_focus(Client *client, bool update_mru)
|
||||
Window prev_window = None;
|
||||
if (ws != NULL && ws->focused != NULL && ws->focused != client) {
|
||||
prev_window = ws->focused->window;
|
||||
client_start_focus_animation(ws->focused, false);
|
||||
client_unfocus(ws->focused);
|
||||
}
|
||||
|
||||
client_start_focus_animation(client, true);
|
||||
|
||||
client_sync_log("client_focus: XSetInputFocus");
|
||||
|
||||
XSetInputFocus(dwn->display, client->window, RevertToPointerRoot, CurrentTime);
|
||||
@ -1283,3 +1292,94 @@ Client *client_get_last(void)
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
#define FOCUS_ANIMATION_DURATION_MS 400
|
||||
|
||||
void client_start_focus_animation(Client *client, bool gaining_focus)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long full_color = client->taskbar_color;
|
||||
unsigned long dimmed_color = dim_color(client->taskbar_color, 0.35f);
|
||||
|
||||
if (gaining_focus) {
|
||||
client->title_anim.from_color = dimmed_color;
|
||||
client->title_anim.to_color = full_color;
|
||||
} else {
|
||||
client->title_anim.from_color = full_color;
|
||||
client->title_anim.to_color = dimmed_color;
|
||||
}
|
||||
|
||||
client->title_anim.start_time = get_time_ms();
|
||||
client->title_anim.progress = 0.0f;
|
||||
client->title_anim.active = true;
|
||||
}
|
||||
|
||||
unsigned long client_get_animated_title_color(Client *client)
|
||||
{
|
||||
if (client == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Workspace *ws = workspace_get(client->workspace);
|
||||
bool is_focused = (ws != NULL && ws->focused == client);
|
||||
|
||||
const ColorScheme *colors = config_get_colors();
|
||||
return is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
|
||||
}
|
||||
|
||||
unsigned long client_get_glow_text_color(Client *client)
|
||||
{
|
||||
if (client == NULL || !client->text_glow.active) {
|
||||
return 0xFFFFFF;
|
||||
}
|
||||
float phase = dwn->ambient_phase + client->text_glow.phase;
|
||||
return glow_color(client->text_glow.base_color, phase);
|
||||
}
|
||||
|
||||
#define TEXT_GLOW_THROTTLE_MS 100
|
||||
|
||||
void client_update_animations(void)
|
||||
{
|
||||
if (dwn == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
static long last_glow_update = 0;
|
||||
long now = get_time_ms();
|
||||
bool should_update_glow = (now - last_glow_update >= TEXT_GLOW_THROTTLE_MS);
|
||||
bool needs_redraw = false;
|
||||
|
||||
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
|
||||
if (c->title_anim.active) {
|
||||
long elapsed = now - c->title_anim.start_time;
|
||||
c->title_anim.progress = (float)elapsed / (float)FOCUS_ANIMATION_DURATION_MS;
|
||||
|
||||
if (c->title_anim.progress >= 1.0f) {
|
||||
c->title_anim.progress = 1.0f;
|
||||
c->title_anim.active = false;
|
||||
}
|
||||
needs_redraw = true;
|
||||
}
|
||||
|
||||
if (should_update_glow && c->text_glow.active &&
|
||||
c->workspace == (unsigned int)dwn->current_workspace) {
|
||||
c->text_glow.phase += 0.05f * c->text_glow.speed;
|
||||
if (c->text_glow.phase > 6.283f) {
|
||||
c->text_glow.phase -= 6.283f;
|
||||
}
|
||||
needs_redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (should_update_glow) {
|
||||
last_glow_update = now;
|
||||
}
|
||||
|
||||
if (needs_redraw) {
|
||||
panel_invalidate_taskbar();
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include "client.h"
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
#include "workspace.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
@ -18,6 +19,83 @@
|
||||
|
||||
#define RESIZE_EDGE 8
|
||||
|
||||
#define DEC_XFT_COLOR_CACHE_SIZE 8
|
||||
|
||||
typedef struct {
|
||||
unsigned long pixel;
|
||||
XftColor xft_color;
|
||||
bool valid;
|
||||
int lru_counter;
|
||||
} DecCachedXftColor;
|
||||
|
||||
static DecCachedXftColor dec_xft_color_cache[DEC_XFT_COLOR_CACHE_SIZE];
|
||||
static Visual *dec_cached_visual = NULL;
|
||||
static int dec_xft_color_lru_counter = 0;
|
||||
|
||||
static XftDraw *dec_cached_xft_draw = NULL;
|
||||
static Window dec_cached_window = None;
|
||||
|
||||
static XftDraw *dec_get_xft_draw(Window w)
|
||||
{
|
||||
if (dec_cached_window == w && dec_cached_xft_draw != NULL) {
|
||||
return dec_cached_xft_draw;
|
||||
}
|
||||
if (dec_cached_xft_draw != NULL) {
|
||||
XftDrawDestroy(dec_cached_xft_draw);
|
||||
}
|
||||
if (dec_cached_visual == NULL) {
|
||||
dec_cached_visual = DefaultVisual(dwn->display, dwn->screen);
|
||||
}
|
||||
dec_cached_xft_draw = XftDrawCreate(dwn->display, w, dec_cached_visual, dwn->colormap);
|
||||
dec_cached_window = w;
|
||||
return dec_cached_xft_draw;
|
||||
}
|
||||
|
||||
static XftColor *dec_get_cached_xft_color(unsigned long color)
|
||||
{
|
||||
if (dec_cached_visual == NULL) {
|
||||
dec_cached_visual = DefaultVisual(dwn->display, dwn->screen);
|
||||
}
|
||||
|
||||
for (int i = 0; i < DEC_XFT_COLOR_CACHE_SIZE; i++) {
|
||||
if (dec_xft_color_cache[i].valid && dec_xft_color_cache[i].pixel == color) {
|
||||
dec_xft_color_cache[i].lru_counter = ++dec_xft_color_lru_counter;
|
||||
return &dec_xft_color_cache[i].xft_color;
|
||||
}
|
||||
}
|
||||
|
||||
int oldest_idx = 0;
|
||||
int oldest_lru = dec_xft_color_cache[0].lru_counter;
|
||||
for (int i = 1; i < DEC_XFT_COLOR_CACHE_SIZE; i++) {
|
||||
if (!dec_xft_color_cache[i].valid) {
|
||||
oldest_idx = i;
|
||||
break;
|
||||
}
|
||||
if (dec_xft_color_cache[i].lru_counter < oldest_lru) {
|
||||
oldest_lru = dec_xft_color_cache[i].lru_counter;
|
||||
oldest_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (dec_xft_color_cache[oldest_idx].valid) {
|
||||
XftColorFree(dwn->display, dec_cached_visual, dwn->colormap,
|
||||
&dec_xft_color_cache[oldest_idx].xft_color);
|
||||
}
|
||||
|
||||
XRenderColor render_color;
|
||||
render_color.red = ((color >> 16) & 0xFF) * 257;
|
||||
render_color.green = ((color >> 8) & 0xFF) * 257;
|
||||
render_color.blue = (color & 0xFF) * 257;
|
||||
render_color.alpha = 0xFFFF;
|
||||
|
||||
XftColorAllocValue(dwn->display, dec_cached_visual, dwn->colormap,
|
||||
&render_color, &dec_xft_color_cache[oldest_idx].xft_color);
|
||||
dec_xft_color_cache[oldest_idx].pixel = color;
|
||||
dec_xft_color_cache[oldest_idx].valid = true;
|
||||
dec_xft_color_cache[oldest_idx].lru_counter = ++dec_xft_color_lru_counter;
|
||||
|
||||
return &dec_xft_color_cache[oldest_idx].xft_color;
|
||||
}
|
||||
|
||||
void decorations_init(void)
|
||||
{
|
||||
@ -46,6 +124,8 @@ void decorations_render(Client *client, bool focused)
|
||||
|
||||
void decorations_render_title_bar(Client *client, bool focused)
|
||||
{
|
||||
(void)focused;
|
||||
|
||||
if (client == NULL || client->frame == None) {
|
||||
return;
|
||||
}
|
||||
@ -55,20 +135,28 @@ void decorations_render_title_bar(Client *client, bool focused)
|
||||
}
|
||||
|
||||
Display *dpy = dwn->display;
|
||||
const ColorScheme *colors = config_get_colors();
|
||||
int title_height = config_get_title_height();
|
||||
int border = client->border_width;
|
||||
const ColorScheme *colors = config_get_colors();
|
||||
|
||||
unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg;
|
||||
unsigned long fg_color = focused ? colors->title_focused_fg : colors->title_unfocused_fg;
|
||||
Workspace *ws = workspace_get(client->workspace);
|
||||
bool is_focused = (ws != NULL && ws->focused == client);
|
||||
unsigned long base_bg = is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
|
||||
unsigned long bg_color = ambient_glow_bg(base_bg, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
|
||||
unsigned long fg_color = client->taskbar_color;
|
||||
fg_color = adjust_color_for_background(fg_color, bg_color);
|
||||
|
||||
XSetForeground(dpy, dwn->gc, bg_color);
|
||||
XFillRectangle(dpy, client->frame, dwn->gc,
|
||||
border, border,
|
||||
client->width, title_height);
|
||||
|
||||
if (client->title[0] != '\0' && dwn->xft_font != NULL) {
|
||||
int text_y = border + (title_height + dwn->xft_font->ascent) / 2;
|
||||
bool is_alt_tab_selection = (dwn->is_alt_tabbing && dwn->alt_tab_client == client);
|
||||
XftFont *title_font = (is_alt_tab_selection && dwn->xft_font_bold != NULL) ?
|
||||
dwn->xft_font_bold : dwn->xft_font;
|
||||
|
||||
if (client->title[0] != '\0' && title_font != NULL) {
|
||||
int text_y = border + (title_height + title_font->ascent) / 2;
|
||||
int text_x = border + BUTTON_PADDING;
|
||||
|
||||
int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING;
|
||||
@ -80,7 +168,7 @@ void decorations_render_title_bar(Client *client, bool focused)
|
||||
display_title[sizeof(display_title) - 4] = '\0';
|
||||
|
||||
XGlyphInfo extents;
|
||||
XftTextExtentsUtf8(dpy, dwn->xft_font,
|
||||
XftTextExtentsUtf8(dpy, title_font,
|
||||
(const FcChar8 *)display_title, strlen(display_title), &extents);
|
||||
int text_width = extents.xOff;
|
||||
|
||||
@ -98,7 +186,7 @@ void decorations_render_title_bar(Client *client, bool focused)
|
||||
display_title[cut] = '\0';
|
||||
title_truncated = true;
|
||||
|
||||
XftTextExtentsUtf8(dpy, dwn->xft_font,
|
||||
XftTextExtentsUtf8(dpy, title_font,
|
||||
(const FcChar8 *)display_title, strlen(display_title), &extents);
|
||||
text_width = extents.xOff;
|
||||
}
|
||||
@ -106,27 +194,12 @@ void decorations_render_title_bar(Client *client, bool focused)
|
||||
strncat(display_title, "...", sizeof(display_title) - strlen(display_title) - 1);
|
||||
}
|
||||
|
||||
XftDraw *xft_draw = XftDrawCreate(dpy, client->frame,
|
||||
DefaultVisual(dpy, dwn->screen),
|
||||
dwn->colormap);
|
||||
XftDraw *xft_draw = dec_get_xft_draw(client->frame);
|
||||
if (xft_draw != NULL) {
|
||||
XftColor xft_color;
|
||||
XRenderColor render_color;
|
||||
render_color.red = ((fg_color >> 16) & 0xFF) * 257;
|
||||
render_color.green = ((fg_color >> 8) & 0xFF) * 257;
|
||||
render_color.blue = (fg_color & 0xFF) * 257;
|
||||
render_color.alpha = 0xFFFF;
|
||||
|
||||
XftColorAllocValue(dpy, DefaultVisual(dpy, dwn->screen),
|
||||
dwn->colormap, &render_color, &xft_color);
|
||||
|
||||
XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font,
|
||||
XftColor *xft_color = dec_get_cached_xft_color(fg_color);
|
||||
XftDrawStringUtf8(xft_draw, xft_color, title_font,
|
||||
text_x, text_y,
|
||||
(const FcChar8 *)display_title, strlen(display_title));
|
||||
|
||||
XftColorFree(dpy, DefaultVisual(dpy, dwn->screen),
|
||||
dwn->colormap, &xft_color);
|
||||
XftDrawDestroy(xft_draw);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -147,7 +220,7 @@ void decorations_render_buttons(Client *client, bool focused)
|
||||
}
|
||||
|
||||
Display *dpy = dwn->display;
|
||||
const ColorScheme *colors = config_get_colors();
|
||||
(void)focused;
|
||||
int title_height = config_get_title_height();
|
||||
int border = client->border_width;
|
||||
|
||||
@ -160,11 +233,19 @@ void decorations_render_buttons(Client *client, bool focused)
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg;
|
||||
const ColorScheme *colors = config_get_colors();
|
||||
Workspace *ws = workspace_get(client->workspace);
|
||||
bool is_focused = (ws != NULL && ws->focused == client);
|
||||
unsigned long base_bg = is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
|
||||
unsigned long bg_color = ambient_glow_bg(base_bg, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
|
||||
|
||||
unsigned long close_color = ambient_glow_accent(0xff0055, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
|
||||
unsigned long max_color = ambient_glow_accent(0x00ff00, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
|
||||
unsigned long min_color = ambient_glow_accent(0xffff00, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
|
||||
|
||||
XSetForeground(dpy, dwn->gc, bg_color);
|
||||
XFillRectangle(dpy, client->frame, dwn->gc, close_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
|
||||
XSetForeground(dpy, dwn->gc, 0xff0055); // Neon Red/Pink
|
||||
XSetForeground(dpy, dwn->gc, close_color);
|
||||
XDrawLine(dpy, client->frame, dwn->gc,
|
||||
close_x + 3, button_y + 3,
|
||||
close_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 4);
|
||||
@ -174,14 +255,14 @@ void decorations_render_buttons(Client *client, bool focused)
|
||||
|
||||
XSetForeground(dpy, dwn->gc, bg_color);
|
||||
XFillRectangle(dpy, client->frame, dwn->gc, max_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
|
||||
XSetForeground(dpy, dwn->gc, 0x00ff00); // Neon Green
|
||||
XSetForeground(dpy, dwn->gc, max_color);
|
||||
XDrawRectangle(dpy, client->frame, dwn->gc,
|
||||
max_x + 3, button_y + 3,
|
||||
BUTTON_SIZE - 7, BUTTON_SIZE - 7);
|
||||
|
||||
XSetForeground(dpy, dwn->gc, bg_color);
|
||||
XFillRectangle(dpy, client->frame, dwn->gc, min_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
|
||||
XSetForeground(dpy, dwn->gc, 0xffff00); // Neon Yellow
|
||||
XSetForeground(dpy, dwn->gc, min_color);
|
||||
XDrawLine(dpy, client->frame, dwn->gc,
|
||||
min_x + 3, button_y + BUTTON_SIZE - 5,
|
||||
min_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 5);
|
||||
@ -315,26 +396,11 @@ void decorations_draw_text(Window window, GC gc, int x, int y,
|
||||
}
|
||||
|
||||
if (dwn->xft_font != NULL) {
|
||||
XftDraw *xft_draw = XftDrawCreate(dwn->display, window,
|
||||
DefaultVisual(dwn->display, dwn->screen),
|
||||
dwn->colormap);
|
||||
XftDraw *xft_draw = dec_get_xft_draw(window);
|
||||
if (xft_draw != NULL) {
|
||||
XftColor xft_color;
|
||||
XRenderColor render_color;
|
||||
render_color.red = ((color >> 16) & 0xFF) * 257;
|
||||
render_color.green = ((color >> 8) & 0xFF) * 257;
|
||||
render_color.blue = (color & 0xFF) * 257;
|
||||
render_color.alpha = 0xFFFF;
|
||||
|
||||
XftColorAllocValue(dwn->display, DefaultVisual(dwn->display, dwn->screen),
|
||||
dwn->colormap, &render_color, &xft_color);
|
||||
|
||||
XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font,
|
||||
XftColor *xft_color = dec_get_cached_xft_color(color);
|
||||
XftDrawStringUtf8(xft_draw, xft_color, dwn->xft_font,
|
||||
x, y, (const FcChar8 *)text, strlen(text));
|
||||
|
||||
XftColorFree(dwn->display, DefaultVisual(dwn->display, dwn->screen),
|
||||
dwn->colormap, &xft_color);
|
||||
XftDrawDestroy(xft_draw);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -436,6 +436,7 @@ void keys_handle_press(XKeyEvent *ev)
|
||||
|
||||
KeySym keysym = XLookupKeysym(ev, 0);
|
||||
|
||||
|
||||
if (keysym == XK_Super_L || keysym == XK_Super_R) {
|
||||
super_pressed = true;
|
||||
super_used_in_combo = false;
|
||||
|
||||
39
src/layout.c
39
src/layout.c
@ -84,6 +84,7 @@ void layout_arrange_tiling(int workspace)
|
||||
}
|
||||
|
||||
int stack_width = area_width - master_width - 3 * gap;
|
||||
if (stack_width < 50) stack_width = 50;
|
||||
|
||||
int i = 0;
|
||||
int master_y = area_y + gap;
|
||||
@ -120,6 +121,16 @@ void layout_arrange_tiling(int workspace)
|
||||
stack_y += h + gap;
|
||||
}
|
||||
|
||||
int max_y = area_y + area_height - gap;
|
||||
int max_x = area_x + area_width - gap;
|
||||
|
||||
if (y + h > max_y) {
|
||||
h = max_y - y;
|
||||
}
|
||||
if (x + w > max_x) {
|
||||
w = max_x - x;
|
||||
}
|
||||
|
||||
int actual_h = h - title_height - 2 * border;
|
||||
int actual_w = w - 2 * border;
|
||||
|
||||
@ -268,21 +279,25 @@ void layout_apply_snap_constraint(Client *c, int area_x, int area_y,
|
||||
return;
|
||||
}
|
||||
|
||||
int title_h = (dwn->config && dwn->config->show_decorations) ?
|
||||
config_get_title_height() : 0;
|
||||
int border = c->border_width;
|
||||
|
||||
int half_w = area_w / 2;
|
||||
int half_h = area_h / 2;
|
||||
|
||||
switch (c->snap.horizontal) {
|
||||
case SNAP_H_LEFT:
|
||||
c->x = area_x + gap;
|
||||
c->width = half_w - gap * 2;
|
||||
c->x = area_x + gap + border;
|
||||
c->width = half_w - gap * 2 - 2 * border;
|
||||
break;
|
||||
case SNAP_H_RIGHT:
|
||||
c->x = area_x + half_w + gap;
|
||||
c->width = half_w - gap * 2;
|
||||
c->x = area_x + half_w + gap + border;
|
||||
c->width = half_w - gap * 2 - 2 * border;
|
||||
break;
|
||||
case SNAP_H_FULL:
|
||||
c->x = area_x + gap;
|
||||
c->width = area_w - gap * 2;
|
||||
c->x = area_x + gap + border;
|
||||
c->width = area_w - gap * 2 - 2 * border;
|
||||
break;
|
||||
case SNAP_H_NONE:
|
||||
break;
|
||||
@ -290,16 +305,16 @@ void layout_apply_snap_constraint(Client *c, int area_x, int area_y,
|
||||
|
||||
switch (c->snap.vertical) {
|
||||
case SNAP_V_TOP:
|
||||
c->y = area_y + gap;
|
||||
c->height = half_h - gap * 2;
|
||||
c->y = area_y + gap + title_h + border;
|
||||
c->height = half_h - gap * 2 - title_h - 2 * border;
|
||||
break;
|
||||
case SNAP_V_BOTTOM:
|
||||
c->y = area_y + half_h + gap;
|
||||
c->height = half_h - gap * 2;
|
||||
c->y = area_y + half_h + gap + title_h + border;
|
||||
c->height = half_h - gap * 2 - title_h - 2 * border;
|
||||
break;
|
||||
case SNAP_V_FULL:
|
||||
c->y = area_y + gap;
|
||||
c->height = area_h - gap * 2;
|
||||
c->y = area_y + gap + title_h + border;
|
||||
c->height = area_h - gap * 2 - title_h - 2 * border;
|
||||
break;
|
||||
case SNAP_V_NONE:
|
||||
break;
|
||||
|
||||
177
src/main.c
177
src/main.c
@ -31,10 +31,14 @@
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
#include <sys/select.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/cursorfont.h>
|
||||
#include <X11/keysymdef.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/extensions/Xinerama.h>
|
||||
#include <X11/extensions/XInput2.h>
|
||||
|
||||
DWNState *dwn = NULL;
|
||||
static DWNState dwn_state;
|
||||
@ -134,6 +138,86 @@ static bool check_other_wm(void)
|
||||
return wm_detected != 0;
|
||||
}
|
||||
|
||||
static void init_xinput2(void)
|
||||
{
|
||||
int event, error;
|
||||
if (!XQueryExtension(dwn->display, "XInputExtension",
|
||||
&dwn->xi2_opcode, &event, &error)) {
|
||||
LOG_WARN("XInput2 extension not available - key counting disabled");
|
||||
dwn->xi2_opcode = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int major = 2, minor = 0;
|
||||
if (XIQueryVersion(dwn->display, &major, &minor) != Success) {
|
||||
LOG_WARN("XInput2 version 2.0 not available - key counting disabled");
|
||||
dwn->xi2_opcode = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
XIEventMask evmask;
|
||||
unsigned char mask[XIMaskLen(XI_LASTEVENT)] = {0};
|
||||
|
||||
evmask.deviceid = XIAllMasterDevices;
|
||||
evmask.mask_len = sizeof(mask);
|
||||
evmask.mask = mask;
|
||||
|
||||
XISetMask(mask, XI_RawKeyPress);
|
||||
XISetMask(mask, XI_RawKeyRelease);
|
||||
XISetMask(mask, XI_RawMotion);
|
||||
|
||||
XISelectEvents(dwn->display, dwn->root, &evmask, 1);
|
||||
|
||||
LOG_INFO("XInput2 initialized for raw key and mouse event monitoring");
|
||||
}
|
||||
|
||||
static void handle_xi2_event(XGenericEventCookie *cookie)
|
||||
{
|
||||
if (cookie->type != GenericEvent || cookie->extension != dwn->xi2_opcode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!XGetEventData(dwn->display, cookie)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cookie->evtype == XI_RawKeyPress) {
|
||||
XIRawEvent *raw = (XIRawEvent *)cookie->data;
|
||||
KeyCode keycode = (KeyCode)raw->detail;
|
||||
KeySym keysym = XkbKeycodeToKeysym(dwn->display, keycode, 0, 0);
|
||||
|
||||
if (keysym != XK_Super_L && keysym != XK_Super_R &&
|
||||
keysym != XK_Alt_L && keysym != XK_Alt_R &&
|
||||
keysym != XK_Control_L && keysym != XK_Control_R &&
|
||||
keysym != XK_Shift_L && keysym != XK_Shift_R &&
|
||||
keysym != XK_Caps_Lock && keysym != XK_Num_Lock &&
|
||||
keysym != XK_Scroll_Lock) {
|
||||
dwn->key_press_count++;
|
||||
dwn->typing_activity_time = get_time_ms();
|
||||
|
||||
if (keysym == XK_BackSpace || keysym == XK_Delete) {
|
||||
dwn->key_delete_flicker_time = get_time_ms();
|
||||
}
|
||||
}
|
||||
} else if (cookie->evtype == XI_RawMotion) {
|
||||
XIRawEvent *raw = (XIRawEvent *)cookie->data;
|
||||
if (raw->valuators.mask_len > 0) {
|
||||
double dx = 0.0, dy = 0.0;
|
||||
double *values = raw->raw_values;
|
||||
int idx = 0;
|
||||
if (XIMaskIsSet(raw->valuators.mask, 0)) {
|
||||
dx = values[idx++];
|
||||
}
|
||||
if (XIMaskIsSet(raw->valuators.mask, 1)) {
|
||||
dy = values[idx];
|
||||
}
|
||||
dwn->mouse_distance_pixels += sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
}
|
||||
|
||||
XFreeEventData(dwn->display, cookie);
|
||||
}
|
||||
|
||||
static void init_monitors(void)
|
||||
{
|
||||
if (!XineramaIsActive(dwn->display)) {
|
||||
@ -324,7 +408,7 @@ static void handle_property_notify(XPropertyEvent *ev)
|
||||
|
||||
if (ev->atom == icccm.WM_NAME || ev->atom == ewmh.NET_WM_NAME) {
|
||||
client_update_title(c);
|
||||
panel_render_all();
|
||||
panel_invalidate_taskbar();
|
||||
}
|
||||
}
|
||||
|
||||
@ -467,6 +551,7 @@ static void handle_button_press(XButtonEvent *ev)
|
||||
dwn->drag_orig_x = c->x;
|
||||
dwn->drag_orig_y = c->y;
|
||||
dwn->resizing = false;
|
||||
dwn->drag_direction = 0;
|
||||
|
||||
api_emit_drag_started(c->window, false);
|
||||
|
||||
@ -488,6 +573,7 @@ static void handle_button_press(XButtonEvent *ev)
|
||||
dwn->drag_orig_w = c->width;
|
||||
dwn->drag_orig_h = c->height;
|
||||
dwn->resizing = true;
|
||||
dwn->drag_direction = direction;
|
||||
|
||||
api_emit_drag_started(c->window, true);
|
||||
|
||||
@ -545,11 +631,36 @@ static void handle_motion_notify(XMotionEvent *ev)
|
||||
int dy = ev->y_root - dwn->drag_start_y;
|
||||
|
||||
if (dwn->resizing) {
|
||||
int new_w = dwn->drag_orig_w + dx;
|
||||
int new_h = dwn->drag_orig_h + dy;
|
||||
if (new_w < 50) new_w = 50;
|
||||
if (new_h < 50) new_h = 50;
|
||||
client_resize(c, new_w, new_h);
|
||||
int dir = dwn->drag_direction;
|
||||
int new_x = dwn->drag_orig_x;
|
||||
int new_y = dwn->drag_orig_y;
|
||||
int new_w = dwn->drag_orig_w;
|
||||
int new_h = dwn->drag_orig_h;
|
||||
|
||||
if (dir & RESIZE_LEFT) {
|
||||
new_x = dwn->drag_orig_x + dx;
|
||||
new_w = dwn->drag_orig_w - dx;
|
||||
} else if (dir & RESIZE_RIGHT) {
|
||||
new_w = dwn->drag_orig_w + dx;
|
||||
}
|
||||
|
||||
if (dir & RESIZE_TOP) {
|
||||
new_y = dwn->drag_orig_y + dy;
|
||||
new_h = dwn->drag_orig_h - dy;
|
||||
} else if (dir & RESIZE_BOTTOM) {
|
||||
new_h = dwn->drag_orig_h + dy;
|
||||
}
|
||||
|
||||
if (new_w < 50) {
|
||||
if (dir & RESIZE_LEFT) new_x = dwn->drag_orig_x + dwn->drag_orig_w - 50;
|
||||
new_w = 50;
|
||||
}
|
||||
if (new_h < 50) {
|
||||
if (dir & RESIZE_TOP) new_y = dwn->drag_orig_y + dwn->drag_orig_h - 50;
|
||||
new_h = 50;
|
||||
}
|
||||
|
||||
client_move_resize(c, new_x, new_y, new_w, new_h);
|
||||
} else {
|
||||
client_move(c, dwn->drag_orig_x + dx, dwn->drag_orig_y + dy);
|
||||
}
|
||||
@ -746,6 +857,12 @@ int dwn_init(void)
|
||||
LOG_INFO("Loaded Xft font for UTF-8 support");
|
||||
}
|
||||
|
||||
dwn->xft_font_bold = XftFontOpenName(dwn->display, dwn->screen,
|
||||
"monospace:size=10:bold:antialias=true");
|
||||
if (dwn->xft_font_bold == NULL) {
|
||||
dwn->xft_font_bold = dwn->xft_font;
|
||||
}
|
||||
|
||||
XGCValues gcv;
|
||||
gcv.foreground = dwn->config->colors.panel_fg;
|
||||
gcv.background = dwn->config->colors.panel_bg;
|
||||
@ -757,6 +874,8 @@ int dwn_init(void)
|
||||
|
||||
init_monitors();
|
||||
|
||||
init_xinput2();
|
||||
|
||||
workspace_init();
|
||||
|
||||
decorations_init();
|
||||
@ -809,6 +928,10 @@ int dwn_init(void)
|
||||
|
||||
dwn->running = true;
|
||||
|
||||
dwn->ambient_phase = 0.0f;
|
||||
dwn->ambient_speed = 0.5f;
|
||||
dwn->typing_activity_time = 0;
|
||||
|
||||
LOG_INFO("DWN initialized successfully");
|
||||
|
||||
return 0;
|
||||
@ -845,6 +968,9 @@ void dwn_cleanup(void)
|
||||
if (dwn->font != NULL) {
|
||||
XFreeFont(dwn->display, dwn->font);
|
||||
}
|
||||
if (dwn->xft_font_bold != NULL && dwn->xft_font_bold != dwn->xft_font) {
|
||||
XftFontClose(dwn->display, dwn->xft_font_bold);
|
||||
}
|
||||
if (dwn->xft_font != NULL) {
|
||||
XftFontClose(dwn->display, dwn->xft_font);
|
||||
}
|
||||
@ -877,6 +1003,11 @@ void dwn_run(void)
|
||||
while (XPending(dwn->display)) {
|
||||
XEvent ev;
|
||||
XNextEvent(dwn->display, &ev);
|
||||
|
||||
if (ev.type == GenericEvent && dwn->xi2_opcode > 0) {
|
||||
handle_xi2_event(&ev.xcookie);
|
||||
}
|
||||
|
||||
dwn_handle_event(&ev);
|
||||
}
|
||||
|
||||
@ -894,11 +1025,24 @@ void dwn_run(void)
|
||||
|
||||
demo_update();
|
||||
|
||||
long now = get_time_ms();
|
||||
|
||||
float speed_multiplier = 1.0f;
|
||||
if (now - dwn->typing_activity_time < 500) {
|
||||
speed_multiplier = 10.0f;
|
||||
}
|
||||
|
||||
dwn->ambient_phase += 0.008f * dwn->ambient_speed * speed_multiplier;
|
||||
if (dwn->ambient_phase > 6.283f) {
|
||||
dwn->ambient_phase -= 6.283f;
|
||||
}
|
||||
|
||||
client_update_animations();
|
||||
|
||||
notifications_update();
|
||||
|
||||
if (dwn->pending_focus_client != NULL) {
|
||||
long now_focus = get_time_ms();
|
||||
if (now_focus >= dwn->pending_focus_time) {
|
||||
if (now >= dwn->pending_focus_time) {
|
||||
Workspace *ws = workspace_get_current();
|
||||
if (ws != NULL && ws->focused != dwn->pending_focus_client) {
|
||||
if (dwn->pending_focus_client->workspace == (unsigned int)dwn->current_workspace) {
|
||||
@ -909,14 +1053,23 @@ void dwn_run(void)
|
||||
}
|
||||
}
|
||||
|
||||
long now = get_time_ms();
|
||||
bool news_rendered = false;
|
||||
|
||||
if (now - last_news_update >= 16) {
|
||||
if (now - last_news_update >= 50) {
|
||||
news_update();
|
||||
panel_render_all();
|
||||
if (panel_news_needs_render()) {
|
||||
panel_render_news_only();
|
||||
news_rendered = true;
|
||||
}
|
||||
last_news_update = now;
|
||||
}
|
||||
|
||||
if (panel_needs_render()) {
|
||||
panel_render_all();
|
||||
} else if (news_rendered) {
|
||||
XFlush(dwn->display);
|
||||
}
|
||||
|
||||
if (now - last_clock_update >= 1000) {
|
||||
panel_update_clock();
|
||||
panel_update_system_stats();
|
||||
@ -933,7 +1086,7 @@ void dwn_run(void)
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 16000;
|
||||
tv.tv_usec = 50000;
|
||||
|
||||
int max_fd = x11_fd;
|
||||
if (dbus_fd > max_fd) max_fd = dbus_fd;
|
||||
|
||||
130
src/news.c
130
src/news.c
@ -261,6 +261,86 @@ static int parse_news_json(const char *json_str)
|
||||
|
||||
static int news_find_article_at_x(int click_x);
|
||||
|
||||
static XftDraw *news_xft_draw = NULL;
|
||||
static Drawable news_xft_drawable = None;
|
||||
static Visual *news_cached_visual = NULL;
|
||||
|
||||
#define NEWS_XFT_COLOR_CACHE_SIZE 8
|
||||
|
||||
typedef struct {
|
||||
unsigned long pixel;
|
||||
XftColor xft_color;
|
||||
bool valid;
|
||||
int lru_counter;
|
||||
} NewsCachedColor;
|
||||
|
||||
static NewsCachedColor news_color_cache[NEWS_XFT_COLOR_CACHE_SIZE];
|
||||
static int news_color_lru_counter = 0;
|
||||
|
||||
static int cached_separator_width = -1;
|
||||
|
||||
static XftDraw *news_get_xft_draw(Drawable d)
|
||||
{
|
||||
if (news_xft_drawable == d && news_xft_draw != NULL) {
|
||||
return news_xft_draw;
|
||||
}
|
||||
if (news_xft_draw != NULL) {
|
||||
XftDrawDestroy(news_xft_draw);
|
||||
}
|
||||
if (news_cached_visual == NULL) {
|
||||
news_cached_visual = DefaultVisual(dwn->display, dwn->screen);
|
||||
}
|
||||
news_xft_draw = XftDrawCreate(dwn->display, d, news_cached_visual, dwn->colormap);
|
||||
news_xft_drawable = d;
|
||||
return news_xft_draw;
|
||||
}
|
||||
|
||||
static XftColor *news_get_cached_color(unsigned long color)
|
||||
{
|
||||
if (news_cached_visual == NULL) {
|
||||
news_cached_visual = DefaultVisual(dwn->display, dwn->screen);
|
||||
}
|
||||
|
||||
for (int i = 0; i < NEWS_XFT_COLOR_CACHE_SIZE; i++) {
|
||||
if (news_color_cache[i].valid && news_color_cache[i].pixel == color) {
|
||||
news_color_cache[i].lru_counter = ++news_color_lru_counter;
|
||||
return &news_color_cache[i].xft_color;
|
||||
}
|
||||
}
|
||||
|
||||
int oldest_idx = 0;
|
||||
int oldest_lru = news_color_cache[0].lru_counter;
|
||||
for (int i = 1; i < NEWS_XFT_COLOR_CACHE_SIZE; i++) {
|
||||
if (!news_color_cache[i].valid) {
|
||||
oldest_idx = i;
|
||||
break;
|
||||
}
|
||||
if (news_color_cache[i].lru_counter < oldest_lru) {
|
||||
oldest_lru = news_color_cache[i].lru_counter;
|
||||
oldest_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (news_color_cache[oldest_idx].valid) {
|
||||
XftColorFree(dwn->display, news_cached_visual, dwn->colormap,
|
||||
&news_color_cache[oldest_idx].xft_color);
|
||||
}
|
||||
|
||||
XRenderColor render_color;
|
||||
render_color.red = ((color >> 16) & 0xFF) * 257;
|
||||
render_color.green = ((color >> 8) & 0xFF) * 257;
|
||||
render_color.blue = (color & 0xFF) * 257;
|
||||
render_color.alpha = 0xFFFF;
|
||||
|
||||
XftColorAllocValue(dwn->display, news_cached_visual, dwn->colormap,
|
||||
&render_color, &news_color_cache[oldest_idx].xft_color);
|
||||
news_color_cache[oldest_idx].pixel = color;
|
||||
news_color_cache[oldest_idx].valid = true;
|
||||
news_color_cache[oldest_idx].lru_counter = ++news_color_lru_counter;
|
||||
|
||||
return &news_color_cache[oldest_idx].xft_color;
|
||||
}
|
||||
|
||||
static int news_text_width(const char *text)
|
||||
{
|
||||
if (dwn == NULL || text == NULL) return 0;
|
||||
@ -285,9 +365,7 @@ static void news_draw_text_clipped(Drawable d, int x, int y, const char *text,
|
||||
if (dwn == NULL || dwn->display == NULL || text == NULL) return;
|
||||
|
||||
if (dwn->xft_font != NULL) {
|
||||
XftDraw *xft_draw = XftDrawCreate(dwn->display, d,
|
||||
DefaultVisual(dwn->display, dwn->screen),
|
||||
dwn->colormap);
|
||||
XftDraw *xft_draw = news_get_xft_draw(d);
|
||||
if (xft_draw != NULL) {
|
||||
if (clip != NULL) {
|
||||
Region region = XCreateRegion();
|
||||
@ -296,25 +374,13 @@ static void news_draw_text_clipped(Drawable d, int x, int y, const char *text,
|
||||
XDestroyRegion(region);
|
||||
}
|
||||
|
||||
XftColor xft_color;
|
||||
XRenderColor render_color;
|
||||
|
||||
render_color.red = ((color >> 16) & 0xFF) * 257;
|
||||
render_color.green = ((color >> 8) & 0xFF) * 257;
|
||||
render_color.blue = (color & 0xFF) * 257;
|
||||
render_color.alpha = 0xFFFF;
|
||||
|
||||
XftColorAllocValue(dwn->display,
|
||||
DefaultVisual(dwn->display, dwn->screen),
|
||||
dwn->colormap, &render_color, &xft_color);
|
||||
|
||||
XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font,
|
||||
XftColor *xft_color = news_get_cached_color(color);
|
||||
XftDrawStringUtf8(xft_draw, xft_color, dwn->xft_font,
|
||||
x, y, (const FcChar8 *)text, strlen(text));
|
||||
|
||||
XftColorFree(dwn->display,
|
||||
DefaultVisual(dwn->display, dwn->screen),
|
||||
dwn->colormap, &xft_color);
|
||||
XftDrawDestroy(xft_draw);
|
||||
if (clip != NULL) {
|
||||
XftDrawSetClip(xft_draw, None);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -349,7 +415,10 @@ static void news_recalc_widths(void)
|
||||
return;
|
||||
}
|
||||
|
||||
int separator_width = news_text_width(NEWS_SEPARATOR);
|
||||
if (cached_separator_width < 0) {
|
||||
cached_separator_width = news_text_width(NEWS_SEPARATOR);
|
||||
}
|
||||
int separator_width = cached_separator_width;
|
||||
news_state.total_width = 0;
|
||||
|
||||
for (int i = 0; i < news_state.article_count; i++) {
|
||||
@ -479,16 +548,22 @@ void news_update(void)
|
||||
pthread_mutex_lock(&news_mutex);
|
||||
}
|
||||
|
||||
bool scrolled = false;
|
||||
if (!news_state.interactive_mode && news_state.article_count > 0) {
|
||||
if (news_state.last_scroll_update > 0) {
|
||||
long delta_ms = now - news_state.last_scroll_update;
|
||||
double scroll_amount = (SCROLL_SPEED_PPS * delta_ms) / 1000.0;
|
||||
news_state.scroll_offset += scroll_amount;
|
||||
scrolled = true;
|
||||
}
|
||||
news_state.last_scroll_update = now;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&news_mutex);
|
||||
|
||||
if (scrolled) {
|
||||
panel_invalidate_news();
|
||||
}
|
||||
}
|
||||
|
||||
void news_next_article(void)
|
||||
@ -554,7 +629,10 @@ static int news_find_article_at_x(int click_x)
|
||||
return -1;
|
||||
}
|
||||
|
||||
int separator_width = news_text_width(NEWS_SEPARATOR);
|
||||
if (cached_separator_width < 0) {
|
||||
cached_separator_width = news_text_width(NEWS_SEPARATOR);
|
||||
}
|
||||
int separator_width = cached_separator_width;
|
||||
int scroll_offset = (int)news_state.scroll_offset % news_state.total_width;
|
||||
int rel_x = click_x - news_state.render_x;
|
||||
|
||||
@ -618,7 +696,10 @@ void news_render(Panel *panel, int x, int max_width, int *used_width)
|
||||
news_state.render_x = x;
|
||||
news_state.render_width = max_width;
|
||||
|
||||
int separator_width = news_text_width(NEWS_SEPARATOR);
|
||||
if (cached_separator_width < 0) {
|
||||
cached_separator_width = news_text_width(NEWS_SEPARATOR);
|
||||
}
|
||||
int separator_width = cached_separator_width;
|
||||
int scroll_offset = (int)news_state.scroll_offset % news_state.total_width;
|
||||
|
||||
XRectangle clip_rect = { x, 0, max_width, panel->height };
|
||||
@ -645,6 +726,7 @@ void news_render(Panel *panel, int x, int max_width, int *used_width)
|
||||
|
||||
unsigned long article_color = news_sentiment_color(
|
||||
news_state.articles[current_article].sentiment);
|
||||
article_color = ambient_glow_bg(article_color, dwn->ambient_phase + PHASE_OFFSET_NEWS);
|
||||
|
||||
if (draw_x + news_state.display_widths[current_article] > x) {
|
||||
news_draw_text_clipped(panel->buffer, draw_x, text_y, display_text,
|
||||
@ -654,7 +736,7 @@ void news_render(Panel *panel, int x, int max_width, int *used_width)
|
||||
|
||||
if (draw_x < x + max_width) {
|
||||
news_draw_text_clipped(panel->buffer, draw_x, text_y, NEWS_SEPARATOR,
|
||||
colors->panel_fg, &clip_rect);
|
||||
ambient_glow_bg(colors->panel_fg, dwn->ambient_phase + PHASE_OFFSET_NEWS), &clip_rect);
|
||||
}
|
||||
draw_x += separator_width;
|
||||
|
||||
|
||||
808
src/panel.c
808
src/panel.c
File diff suppressed because it is too large
Load Diff
202
src/systray.c
202
src/systray.c
@ -43,6 +43,7 @@
|
||||
WifiState wifi_state = {0};
|
||||
AudioState audio_state = {0};
|
||||
BatteryState battery_state = {0};
|
||||
MultiBatteryState multi_battery_state = {0};
|
||||
VolumeSlider *volume_slider = NULL;
|
||||
|
||||
TrayIcon tray_icons[MAX_TRAY_ICONS];
|
||||
@ -62,6 +63,83 @@ static int xembed_icons_x = 0;
|
||||
static void wifi_update_state_internal(void);
|
||||
static void audio_update_state_internal(void);
|
||||
|
||||
static XftDraw *systray_xft_draw = NULL;
|
||||
static Drawable systray_xft_drawable = None;
|
||||
static Visual *systray_cached_visual = NULL;
|
||||
|
||||
#define SYSTRAY_XFT_COLOR_CACHE_SIZE 8
|
||||
|
||||
typedef struct {
|
||||
unsigned long pixel;
|
||||
XftColor xft_color;
|
||||
bool valid;
|
||||
int lru_counter;
|
||||
} SystrayCachedColor;
|
||||
|
||||
static SystrayCachedColor systray_color_cache[SYSTRAY_XFT_COLOR_CACHE_SIZE];
|
||||
static int systray_color_lru_counter = 0;
|
||||
|
||||
static XftDraw *systray_get_xft_draw(Drawable d)
|
||||
{
|
||||
if (systray_xft_drawable == d && systray_xft_draw != NULL) {
|
||||
return systray_xft_draw;
|
||||
}
|
||||
if (systray_xft_draw != NULL) {
|
||||
XftDrawDestroy(systray_xft_draw);
|
||||
}
|
||||
if (systray_cached_visual == NULL) {
|
||||
systray_cached_visual = DefaultVisual(dwn->display, dwn->screen);
|
||||
}
|
||||
systray_xft_draw = XftDrawCreate(dwn->display, d, systray_cached_visual, dwn->colormap);
|
||||
systray_xft_drawable = d;
|
||||
return systray_xft_draw;
|
||||
}
|
||||
|
||||
static XftColor *systray_get_cached_color(unsigned long color)
|
||||
{
|
||||
if (systray_cached_visual == NULL) {
|
||||
systray_cached_visual = DefaultVisual(dwn->display, dwn->screen);
|
||||
}
|
||||
|
||||
for (int i = 0; i < SYSTRAY_XFT_COLOR_CACHE_SIZE; i++) {
|
||||
if (systray_color_cache[i].valid && systray_color_cache[i].pixel == color) {
|
||||
systray_color_cache[i].lru_counter = ++systray_color_lru_counter;
|
||||
return &systray_color_cache[i].xft_color;
|
||||
}
|
||||
}
|
||||
|
||||
int oldest_idx = 0;
|
||||
int oldest_lru = systray_color_cache[0].lru_counter;
|
||||
for (int i = 1; i < SYSTRAY_XFT_COLOR_CACHE_SIZE; i++) {
|
||||
if (!systray_color_cache[i].valid) {
|
||||
oldest_idx = i;
|
||||
break;
|
||||
}
|
||||
if (systray_color_cache[i].lru_counter < oldest_lru) {
|
||||
oldest_lru = systray_color_cache[i].lru_counter;
|
||||
oldest_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (systray_color_cache[oldest_idx].valid) {
|
||||
XftColorFree(dwn->display, systray_cached_visual, dwn->colormap,
|
||||
&systray_color_cache[oldest_idx].xft_color);
|
||||
}
|
||||
|
||||
XRenderColor render_color;
|
||||
render_color.red = ((color >> 16) & 0xFF) * 257;
|
||||
render_color.green = ((color >> 8) & 0xFF) * 257;
|
||||
render_color.blue = (color & 0xFF) * 257;
|
||||
render_color.alpha = 0xFFFF;
|
||||
|
||||
XftColorAllocValue(dwn->display, systray_cached_visual, dwn->colormap,
|
||||
&render_color, &systray_color_cache[oldest_idx].xft_color);
|
||||
systray_color_cache[oldest_idx].pixel = color;
|
||||
systray_color_cache[oldest_idx].valid = true;
|
||||
systray_color_cache[oldest_idx].lru_counter = ++systray_color_lru_counter;
|
||||
|
||||
return &systray_color_cache[oldest_idx].xft_color;
|
||||
}
|
||||
|
||||
static void draw_utf8_text(Drawable d, int x, int y, const char *text, unsigned long color)
|
||||
{
|
||||
@ -70,29 +148,11 @@ static void draw_utf8_text(Drawable d, int x, int y, const char *text, unsigned
|
||||
}
|
||||
|
||||
if (dwn->xft_font != NULL) {
|
||||
XftDraw *xft_draw = XftDrawCreate(dwn->display, d,
|
||||
DefaultVisual(dwn->display, dwn->screen),
|
||||
dwn->colormap);
|
||||
XftDraw *xft_draw = systray_get_xft_draw(d);
|
||||
if (xft_draw != NULL) {
|
||||
XftColor xft_color;
|
||||
XRenderColor render_color;
|
||||
|
||||
render_color.red = ((color >> 16) & 0xFF) * 257;
|
||||
render_color.green = ((color >> 8) & 0xFF) * 257;
|
||||
render_color.blue = (color & 0xFF) * 257;
|
||||
render_color.alpha = 0xFFFF;
|
||||
|
||||
XftColorAllocValue(dwn->display,
|
||||
DefaultVisual(dwn->display, dwn->screen),
|
||||
dwn->colormap, &render_color, &xft_color);
|
||||
|
||||
XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font,
|
||||
XftColor *xft_color = systray_get_cached_color(color);
|
||||
XftDrawStringUtf8(xft_draw, xft_color, dwn->xft_font,
|
||||
x, y, (const FcChar8 *)text, strlen(text));
|
||||
|
||||
XftColorFree(dwn->display,
|
||||
DefaultVisual(dwn->display, dwn->screen),
|
||||
dwn->colormap, &xft_color);
|
||||
XftDrawDestroy(xft_draw);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -183,6 +243,7 @@ void battery_update_state(void)
|
||||
char value[64];
|
||||
FILE *fp;
|
||||
|
||||
memset(&multi_battery_state, 0, sizeof(multi_battery_state));
|
||||
battery_state.present = false;
|
||||
battery_state.charging = false;
|
||||
battery_state.percentage = 0;
|
||||
@ -192,24 +253,75 @@ void battery_update_state(void)
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long total_energy_now = 0;
|
||||
unsigned long total_energy_full = 0;
|
||||
bool any_charging = false;
|
||||
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (entry->d_name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (multi_battery_state.count >= MAX_BATTERIES) {
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/type", entry->d_name);
|
||||
fp = fopen(path, "r");
|
||||
if (fp != NULL) {
|
||||
if (fgets(value, sizeof(value), fp) != NULL) {
|
||||
value[strcspn(value, "\n")] = '\0';
|
||||
if (strcmp(value, "Battery") == 0) {
|
||||
battery_state.present = true;
|
||||
SingleBattery *bat = &multi_battery_state.batteries[multi_battery_state.count];
|
||||
bat->present = true;
|
||||
size_t name_len = strlen(entry->d_name);
|
||||
if (name_len >= sizeof(bat->name)) {
|
||||
name_len = sizeof(bat->name) - 1;
|
||||
}
|
||||
memcpy(bat->name, entry->d_name, name_len);
|
||||
bat->name[name_len] = '\0';
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/energy_now", entry->d_name);
|
||||
FILE *energy_fp = fopen(path, "r");
|
||||
if (energy_fp != NULL) {
|
||||
if (fgets(value, sizeof(value), energy_fp) != NULL) {
|
||||
bat->energy_now = strtoul(value, NULL, 10);
|
||||
}
|
||||
fclose(energy_fp);
|
||||
} else {
|
||||
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/charge_now", entry->d_name);
|
||||
energy_fp = fopen(path, "r");
|
||||
if (energy_fp != NULL) {
|
||||
if (fgets(value, sizeof(value), energy_fp) != NULL) {
|
||||
bat->energy_now = strtoul(value, NULL, 10);
|
||||
}
|
||||
fclose(energy_fp);
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/energy_full", entry->d_name);
|
||||
energy_fp = fopen(path, "r");
|
||||
if (energy_fp != NULL) {
|
||||
if (fgets(value, sizeof(value), energy_fp) != NULL) {
|
||||
bat->energy_full = strtoul(value, NULL, 10);
|
||||
}
|
||||
fclose(energy_fp);
|
||||
} else {
|
||||
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/charge_full", entry->d_name);
|
||||
energy_fp = fopen(path, "r");
|
||||
if (energy_fp != NULL) {
|
||||
if (fgets(value, sizeof(value), energy_fp) != NULL) {
|
||||
bat->energy_full = strtoul(value, NULL, 10);
|
||||
}
|
||||
fclose(energy_fp);
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path), "/sys/class/power_supply/%s/capacity", entry->d_name);
|
||||
FILE *cap_fp = fopen(path, "r");
|
||||
if (cap_fp != NULL) {
|
||||
if (fgets(value, sizeof(value), cap_fp) != NULL) {
|
||||
battery_state.percentage = atoi(value);
|
||||
bat->percentage = atoi(value);
|
||||
}
|
||||
fclose(cap_fp);
|
||||
}
|
||||
@ -219,20 +331,42 @@ void battery_update_state(void)
|
||||
if (status_fp != NULL) {
|
||||
if (fgets(value, sizeof(value), status_fp) != NULL) {
|
||||
value[strcspn(value, "\n")] = '\0';
|
||||
battery_state.charging = (strcmp(value, "Charging") == 0 ||
|
||||
strcmp(value, "Full") == 0);
|
||||
bat->charging = (strcmp(value, "Charging") == 0 ||
|
||||
strcmp(value, "Full") == 0);
|
||||
if (bat->charging) {
|
||||
any_charging = true;
|
||||
}
|
||||
}
|
||||
fclose(status_fp);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
break;
|
||||
total_energy_now += bat->energy_now;
|
||||
total_energy_full += bat->energy_full;
|
||||
multi_battery_state.count++;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
multi_battery_state.any_charging = any_charging;
|
||||
|
||||
if (total_energy_full > 0) {
|
||||
multi_battery_state.combined_percentage = (int)((total_energy_now * 100) / total_energy_full);
|
||||
} else if (multi_battery_state.count > 0) {
|
||||
int total_pct = 0;
|
||||
for (int i = 0; i < multi_battery_state.count; i++) {
|
||||
total_pct += multi_battery_state.batteries[i].percentage;
|
||||
}
|
||||
multi_battery_state.combined_percentage = total_pct / multi_battery_state.count;
|
||||
}
|
||||
|
||||
if (multi_battery_state.count > 0) {
|
||||
battery_state.present = true;
|
||||
battery_state.charging = any_charging;
|
||||
battery_state.percentage = multi_battery_state.combined_percentage;
|
||||
}
|
||||
}
|
||||
|
||||
const char *battery_get_icon(void)
|
||||
@ -483,7 +617,8 @@ void volume_slider_render(VolumeSlider *slider)
|
||||
int text_width = get_text_width(vol_text);
|
||||
int text_x = (slider->width - text_width) / 2;
|
||||
|
||||
draw_utf8_text(slider->window, text_x, slider->height - 4, vol_text, colors->panel_fg);
|
||||
draw_utf8_text(slider->window, text_x, slider->height - 4, vol_text,
|
||||
ambient_glow_bg(colors->panel_fg, dwn->ambient_phase + PHASE_OFFSET_SYSTRAY));
|
||||
|
||||
XFlush(dpy);
|
||||
}
|
||||
@ -602,6 +737,7 @@ void systray_render(Panel *panel, int x, int *width)
|
||||
snprintf(audio_label, sizeof(audio_label), "%s%d%%", audio_icon, audio_snap.volume);
|
||||
|
||||
unsigned long audio_color = audio_snap.muted ? colors->workspace_inactive : colors->panel_fg;
|
||||
audio_color = ambient_glow_bg(audio_color, dwn->ambient_phase + PHASE_OFFSET_SYSTRAY);
|
||||
draw_utf8_text(panel->buffer, current_x, text_y, audio_label, audio_color);
|
||||
current_x += get_text_width(audio_label) + SYSTRAY_SPACING;
|
||||
|
||||
@ -617,6 +753,7 @@ void systray_render(Panel *panel, int x, int *width)
|
||||
}
|
||||
|
||||
unsigned long wifi_color = wifi_snap.connected ? colors->panel_fg : colors->workspace_inactive;
|
||||
wifi_color = ambient_glow_bg(wifi_color, dwn->ambient_phase + PHASE_OFFSET_SYSTRAY);
|
||||
draw_utf8_text(panel->buffer, current_x, text_y, wifi_label, wifi_color);
|
||||
current_x += get_text_width(wifi_label);
|
||||
|
||||
@ -707,6 +844,15 @@ BatteryState systray_get_battery_snapshot(void)
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
MultiBatteryState systray_get_multi_battery_snapshot(void)
|
||||
{
|
||||
MultiBatteryState snapshot;
|
||||
pthread_mutex_lock(&state_mutex);
|
||||
snapshot = multi_battery_state;
|
||||
pthread_mutex_unlock(&state_mutex);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
AudioState systray_get_audio_snapshot(void)
|
||||
{
|
||||
AudioState snapshot;
|
||||
|
||||
238
src/util.c
238
src/util.c
@ -20,6 +20,7 @@
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <fcntl.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
#define LOG_RING_SIZE 256
|
||||
@ -649,3 +650,240 @@ char *spawn_capture(const char *cmd)
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
static void hsl_to_rgb(double h, double s, double l, int *r, int *g, int *b)
|
||||
{
|
||||
double c = (1.0 - fabs(2.0 * l - 1.0)) * s;
|
||||
double x = c * (1.0 - fabs(fmod(h / 60.0, 2.0) - 1.0));
|
||||
double m = l - c / 2.0;
|
||||
|
||||
double rp = 0, gp = 0, bp = 0;
|
||||
|
||||
if (h < 60) {
|
||||
rp = c; gp = x; bp = 0;
|
||||
} else if (h < 120) {
|
||||
rp = x; gp = c; bp = 0;
|
||||
} else if (h < 180) {
|
||||
rp = 0; gp = c; bp = x;
|
||||
} else if (h < 240) {
|
||||
rp = 0; gp = x; bp = c;
|
||||
} else if (h < 300) {
|
||||
rp = x; gp = 0; bp = c;
|
||||
} else {
|
||||
rp = c; gp = 0; bp = x;
|
||||
}
|
||||
|
||||
*r = (int)((rp + m) * 255);
|
||||
*g = (int)((gp + m) * 255);
|
||||
*b = (int)((bp + m) * 255);
|
||||
}
|
||||
|
||||
unsigned long rgb_to_pixel(int r, int g, int b)
|
||||
{
|
||||
if (dwn == NULL || dwn->display == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
XColor color;
|
||||
color.red = (unsigned short)(r << 8);
|
||||
color.green = (unsigned short)(g << 8);
|
||||
color.blue = (unsigned short)(b << 8);
|
||||
color.flags = DoRed | DoGreen | DoBlue;
|
||||
|
||||
if (XAllocColor(dwn->display, dwn->colormap, &color)) {
|
||||
return color.pixel;
|
||||
}
|
||||
|
||||
return (unsigned long)((r << 16) | (g << 8) | b);
|
||||
}
|
||||
|
||||
unsigned long generate_unique_color(void)
|
||||
{
|
||||
static unsigned int color_index = 0;
|
||||
double golden_angle = 137.5;
|
||||
double hue = fmod(color_index * golden_angle, 360.0);
|
||||
color_index++;
|
||||
|
||||
double saturation = 0.75;
|
||||
double lightness = 0.65;
|
||||
|
||||
int r, g, b;
|
||||
hsl_to_rgb(hue, saturation, lightness, &r, &g, &b);
|
||||
|
||||
return rgb_to_pixel(r, g, b);
|
||||
}
|
||||
|
||||
static double calculate_luminance(int r, int g, int b)
|
||||
{
|
||||
double rs = r / 255.0;
|
||||
double gs = g / 255.0;
|
||||
double bs = b / 255.0;
|
||||
|
||||
rs = (rs <= 0.03928) ? rs / 12.92 : pow((rs + 0.055) / 1.055, 2.4);
|
||||
gs = (gs <= 0.03928) ? gs / 12.92 : pow((gs + 0.055) / 1.055, 2.4);
|
||||
bs = (bs <= 0.03928) ? bs / 12.92 : pow((bs + 0.055) / 1.055, 2.4);
|
||||
|
||||
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
|
||||
}
|
||||
|
||||
unsigned long adjust_color_for_background(unsigned long color, unsigned long bg)
|
||||
{
|
||||
int cr, cg, cb;
|
||||
int br, bg_g, bb;
|
||||
|
||||
color_to_rgb(color, &cr, &cg, &cb);
|
||||
color_to_rgb(bg, &br, &bg_g, &bb);
|
||||
|
||||
double color_lum = calculate_luminance(cr, cg, cb);
|
||||
double bg_lum = calculate_luminance(br, bg_g, bb);
|
||||
|
||||
double lighter = (color_lum > bg_lum) ? color_lum : bg_lum;
|
||||
double darker = (color_lum > bg_lum) ? bg_lum : color_lum;
|
||||
double contrast = (lighter + 0.05) / (darker + 0.05);
|
||||
|
||||
if (contrast < 3.0) {
|
||||
double factor = (bg_lum > 0.5) ? 0.3 : 1.8;
|
||||
cr = (int)(cr * factor);
|
||||
cg = (int)(cg * factor);
|
||||
cb = (int)(cb * factor);
|
||||
|
||||
if (cr > 255) cr = 255;
|
||||
if (cg > 255) cg = 255;
|
||||
if (cb > 255) cb = 255;
|
||||
if (cr < 0) cr = 0;
|
||||
if (cg < 0) cg = 0;
|
||||
if (cb < 0) cb = 0;
|
||||
}
|
||||
|
||||
return rgb_to_pixel(cr, cg, cb);
|
||||
}
|
||||
|
||||
unsigned long interpolate_color(unsigned long from, unsigned long to, float progress)
|
||||
{
|
||||
if (progress <= 0.0f) return from;
|
||||
if (progress >= 1.0f) return to;
|
||||
|
||||
int fr, fg_c, fb;
|
||||
int tr, tg, tb;
|
||||
|
||||
color_to_rgb(from, &fr, &fg_c, &fb);
|
||||
color_to_rgb(to, &tr, &tg, &tb);
|
||||
|
||||
int r = fr + (int)((tr - fr) * progress);
|
||||
int g = fg_c + (int)((tg - fg_c) * progress);
|
||||
int b = fb + (int)((tb - fb) * progress);
|
||||
|
||||
return rgb_to_pixel(r, g, b);
|
||||
}
|
||||
|
||||
unsigned long dim_color(unsigned long color, float factor)
|
||||
{
|
||||
int r, g, b;
|
||||
color_to_rgb(color, &r, &g, &b);
|
||||
|
||||
r = (int)(r * factor);
|
||||
g = (int)(g * factor);
|
||||
b = (int)(b * factor);
|
||||
|
||||
if (r > 255) r = 255;
|
||||
if (g > 255) g = 255;
|
||||
if (b > 255) b = 255;
|
||||
if (r < 0) r = 0;
|
||||
if (g < 0) g = 0;
|
||||
if (b < 0) b = 0;
|
||||
|
||||
return rgb_to_pixel(r, g, b);
|
||||
}
|
||||
|
||||
#define SINE_TABLE_SIZE 256
|
||||
#define TWO_PI 6.283185307f
|
||||
|
||||
static float sine_table[SINE_TABLE_SIZE];
|
||||
static bool sine_table_initialized = false;
|
||||
|
||||
static float fast_sin(float phase)
|
||||
{
|
||||
if (!sine_table_initialized) {
|
||||
for (int i = 0; i < SINE_TABLE_SIZE; i++) {
|
||||
sine_table[i] = sinf((float)i * TWO_PI / (float)SINE_TABLE_SIZE);
|
||||
}
|
||||
sine_table_initialized = true;
|
||||
}
|
||||
|
||||
while (phase < 0.0f) phase += TWO_PI;
|
||||
while (phase >= TWO_PI) phase -= TWO_PI;
|
||||
|
||||
int idx = (int)(phase * (float)SINE_TABLE_SIZE / TWO_PI);
|
||||
if (idx >= SINE_TABLE_SIZE) idx = SINE_TABLE_SIZE - 1;
|
||||
if (idx < 0) idx = 0;
|
||||
|
||||
return sine_table[idx];
|
||||
}
|
||||
|
||||
unsigned long glow_color(unsigned long base, float phase)
|
||||
{
|
||||
int r, g, b;
|
||||
color_to_rgb(base, &r, &g, &b);
|
||||
|
||||
float mod_r = 0.25f * fast_sin(phase);
|
||||
float mod_g = 0.25f * fast_sin(phase + 2.094f);
|
||||
float mod_b = 0.25f * fast_sin(phase + 4.189f);
|
||||
|
||||
r = r + (int)(r * mod_r);
|
||||
g = g + (int)(g * mod_g);
|
||||
b = b + (int)(b * mod_b);
|
||||
|
||||
if (r > 255) r = 255;
|
||||
if (r < 0) r = 0;
|
||||
if (g > 255) g = 255;
|
||||
if (g < 0) g = 0;
|
||||
if (b > 255) b = 255;
|
||||
if (b < 0) b = 0;
|
||||
|
||||
return rgb_to_pixel(r, g, b);
|
||||
}
|
||||
|
||||
unsigned long ambient_glow_bg(unsigned long base, float phase)
|
||||
{
|
||||
int r, g, b;
|
||||
color_to_rgb(base, &r, &g, &b);
|
||||
|
||||
float mod = 0.25f * fast_sin(phase);
|
||||
|
||||
r = r + (int)(r * mod);
|
||||
g = g + (int)(g * mod);
|
||||
b = b + (int)(b * mod);
|
||||
|
||||
if (r > 255) r = 255;
|
||||
if (r < 0) r = 0;
|
||||
if (g > 255) g = 255;
|
||||
if (g < 0) g = 0;
|
||||
if (b > 255) b = 255;
|
||||
if (b < 0) b = 0;
|
||||
|
||||
return rgb_to_pixel(r, g, b);
|
||||
}
|
||||
|
||||
unsigned long ambient_glow_accent(unsigned long base, float phase)
|
||||
{
|
||||
int r, g, b;
|
||||
color_to_rgb(base, &r, &g, &b);
|
||||
|
||||
float mod_r = 0.45f * fast_sin(phase);
|
||||
float mod_g = 0.45f * fast_sin(phase + 2.094f);
|
||||
float mod_b = 0.45f * fast_sin(phase + 4.189f);
|
||||
|
||||
r = r + (int)(r * mod_r);
|
||||
g = g + (int)(g * mod_g);
|
||||
b = b + (int)(b * mod_b);
|
||||
|
||||
if (r > 255) r = 255;
|
||||
if (r < 0) r = 0;
|
||||
if (g > 255) g = 255;
|
||||
if (g < 0) g = 0;
|
||||
if (b > 255) b = 255;
|
||||
if (b < 0) b = 0;
|
||||
|
||||
return rgb_to_pixel(r, g, b);
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include "config.h"
|
||||
#include "panel.h"
|
||||
#include "api.h"
|
||||
#include "decorations.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
@ -551,10 +552,11 @@ void workspace_alt_tab_next(void)
|
||||
if (!dwn->is_alt_tabbing) {
|
||||
dwn->is_alt_tabbing = true;
|
||||
dwn->alt_tab_client = ws->focused;
|
||||
XGrabKeyboard(dwn->display, dwn->root, True,
|
||||
XGrabKeyboard(dwn->display, dwn->root, True,
|
||||
GrabModeAsync, GrabModeAsync, CurrentTime);
|
||||
}
|
||||
|
||||
Client *previous_selection = dwn->alt_tab_client;
|
||||
Client *current = dwn->alt_tab_client;
|
||||
if (current == NULL) {
|
||||
current = ws->mru_head;
|
||||
@ -566,8 +568,15 @@ void workspace_alt_tab_next(void)
|
||||
dwn->alt_tab_client = ws->mru_head;
|
||||
}
|
||||
|
||||
if (previous_selection != NULL && previous_selection->frame != None) {
|
||||
decorations_render(previous_selection, false);
|
||||
}
|
||||
|
||||
if (dwn->alt_tab_client != NULL) {
|
||||
client_focus(dwn->alt_tab_client, false);
|
||||
if (dwn->alt_tab_client->frame != None) {
|
||||
decorations_render(dwn->alt_tab_client, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -581,21 +590,28 @@ void workspace_alt_tab_prev(void)
|
||||
if (!dwn->is_alt_tabbing) {
|
||||
dwn->is_alt_tabbing = true;
|
||||
dwn->alt_tab_client = ws->focused;
|
||||
XGrabKeyboard(dwn->display, dwn->root, True,
|
||||
XGrabKeyboard(dwn->display, dwn->root, True,
|
||||
GrabModeAsync, GrabModeAsync, CurrentTime);
|
||||
}
|
||||
|
||||
Client *previous_selection = dwn->alt_tab_client;
|
||||
Client *current = dwn->alt_tab_client;
|
||||
|
||||
|
||||
if (current != NULL && current->mru_prev != NULL) {
|
||||
dwn->alt_tab_client = current->mru_prev;
|
||||
} else {
|
||||
dwn->alt_tab_client = ws->mru_tail;
|
||||
}
|
||||
|
||||
if (previous_selection != NULL && previous_selection->frame != None) {
|
||||
decorations_render(previous_selection, false);
|
||||
}
|
||||
|
||||
if (dwn->alt_tab_client != NULL) {
|
||||
client_focus(dwn->alt_tab_client, false);
|
||||
if (dwn->alt_tab_client->frame != None) {
|
||||
decorations_render(dwn->alt_tab_client, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user