docs: expand README with detailed features, architecture, and configuration guides
This commit is contained in:
parent
a75c1d90cc
commit
2017a9b666
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/client.o
BIN
build/client.o
Binary file not shown.
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.
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.
@ -223,11 +223,13 @@ typedef struct {
|
||||
#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;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
15
src/client.c
15
src/client.c
@ -1340,13 +1340,17 @@ unsigned long client_get_glow_text_color(Client *client)
|
||||
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) {
|
||||
@ -1361,8 +1365,9 @@ void client_update_animations(void)
|
||||
needs_redraw = true;
|
||||
}
|
||||
|
||||
if (c->text_glow.active && c->workspace == (unsigned int)dwn->current_workspace) {
|
||||
c->text_glow.phase += 0.005f * c->text_glow.speed;
|
||||
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;
|
||||
}
|
||||
@ -1370,7 +1375,11 @@ void client_update_animations(void)
|
||||
}
|
||||
}
|
||||
|
||||
if (should_update_glow) {
|
||||
last_glow_update = now;
|
||||
}
|
||||
|
||||
if (needs_redraw) {
|
||||
panel_render_all();
|
||||
panel_invalidate_taskbar();
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,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)
|
||||
{
|
||||
@ -117,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, title_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -334,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;
|
||||
}
|
||||
}
|
||||
|
||||
51
src/main.c
51
src/main.c
@ -164,10 +164,11 @@ static void init_xinput2(void)
|
||||
|
||||
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 event monitoring");
|
||||
LOG_INFO("XInput2 initialized for raw key and mouse event monitoring");
|
||||
}
|
||||
|
||||
static void handle_xi2_event(XGenericEventCookie *cookie)
|
||||
@ -198,6 +199,20 @@ static void handle_xi2_event(XGenericEventCookie *cookie)
|
||||
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);
|
||||
@ -393,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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1024,21 +1039,6 @@ void dwn_run(void)
|
||||
|
||||
client_update_animations();
|
||||
|
||||
Window root_ret, child_ret;
|
||||
int root_x, root_y, win_x, win_y;
|
||||
unsigned int mask_ret;
|
||||
if (XQueryPointer(dwn->display, dwn->root, &root_ret, &child_ret,
|
||||
&root_x, &root_y, &win_x, &win_y, &mask_ret)) {
|
||||
if (dwn->mouse_tracking_initialized) {
|
||||
int dx = root_x - dwn->last_mouse_x;
|
||||
int dy = root_y - dwn->last_mouse_y;
|
||||
dwn->mouse_distance_pixels += sqrt((double)(dx * dx + dy * dy));
|
||||
}
|
||||
dwn->last_mouse_x = root_x;
|
||||
dwn->last_mouse_y = root_y;
|
||||
dwn->mouse_tracking_initialized = true;
|
||||
}
|
||||
|
||||
notifications_update();
|
||||
|
||||
if (dwn->pending_focus_client != NULL) {
|
||||
@ -1053,12 +1053,23 @@ void dwn_run(void)
|
||||
}
|
||||
}
|
||||
|
||||
if (now - last_news_update >= 16) {
|
||||
bool news_rendered = false;
|
||||
|
||||
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();
|
||||
@ -1075,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;
|
||||
|
||||
127
src/news.c
127
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 };
|
||||
|
||||
619
src/panel.c
619
src/panel.c
@ -21,19 +21,60 @@
|
||||
#include <stdlib.h>
|
||||
#include <dirent.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <X11/Xft/Xft.h>
|
||||
|
||||
#define PANEL_PADDING 12
|
||||
#define WIDGET_SPACING 16
|
||||
#define WORKSPACE_WIDTH 36
|
||||
#define TASKBAR_ITEM_WIDTH 180
|
||||
#define TOP_PROCESS_NAME_MAX_LEN 12
|
||||
|
||||
#define CLOCK_FORMAT "%H:%M:%S"
|
||||
#define DATE_FORMAT "%Y-%m-%d"
|
||||
|
||||
#define XFT_COLOR_CACHE_SIZE 16
|
||||
#define PROCESS_SCAN_INTERVAL_MS 30000
|
||||
#define PROC_CPU_HISTORY_SIZE 1024
|
||||
#define DISK_UPDATE_INTERVAL_MS 60000
|
||||
|
||||
static char clock_buffer[32] = "";
|
||||
static char date_buffer[32] = "";
|
||||
|
||||
static double disk_free_gb = 0.0;
|
||||
static double disk_total_gb = 0.0;
|
||||
static long disk_last_update = 0;
|
||||
|
||||
static bool panels_dirty = true;
|
||||
|
||||
static int news_region_x = 0;
|
||||
static int news_region_width = 0;
|
||||
static bool news_region_dirty = false;
|
||||
|
||||
typedef struct {
|
||||
unsigned long pixel;
|
||||
XftColor xft_color;
|
||||
bool valid;
|
||||
int lru_counter;
|
||||
} CachedXftColor;
|
||||
|
||||
static CachedXftColor xft_color_cache[XFT_COLOR_CACHE_SIZE];
|
||||
static Visual *cached_visual = NULL;
|
||||
static int xft_color_lru_counter = 0;
|
||||
|
||||
static XftDraw *cached_xft_draw = NULL;
|
||||
static Drawable cached_drawable = None;
|
||||
|
||||
static long last_memory_scan = 0;
|
||||
|
||||
typedef struct {
|
||||
int pid;
|
||||
unsigned long long prev_utime;
|
||||
unsigned long long prev_stime;
|
||||
unsigned long long prev_total_cpu;
|
||||
} ProcCpuState;
|
||||
|
||||
typedef struct {
|
||||
int cpu_percent;
|
||||
int mem_percent;
|
||||
@ -44,18 +85,32 @@ typedef struct {
|
||||
float load_15min;
|
||||
unsigned long long prev_idle;
|
||||
unsigned long long prev_total;
|
||||
char top_mem_process[64];
|
||||
int top_mem_percent;
|
||||
char top_mem_processes[3][64];
|
||||
int top_mem_percents[3];
|
||||
int top_mem_count;
|
||||
char top_cpu_processes[3][64];
|
||||
int top_cpu_percents[3];
|
||||
int top_cpu_count;
|
||||
int process_display_index;
|
||||
int process_category;
|
||||
long process_last_cycle;
|
||||
} SystemStats;
|
||||
|
||||
static ProcCpuState proc_cpu_history[PROC_CPU_HISTORY_SIZE];
|
||||
static int proc_cpu_history_count = 0;
|
||||
static unsigned long long prev_total_cpu_time = 0;
|
||||
|
||||
static SystemStats sys_stats = {0};
|
||||
|
||||
static void panel_render_system_stats(Panel *panel, int x, int *width);
|
||||
static int panel_calculate_stats_width(void);
|
||||
static void panel_render_top_mem_process(Panel *panel, int x, int *width);
|
||||
static void panel_render_top_process(Panel *panel, int x, int *width);
|
||||
static int panel_calculate_top_process_width(void);
|
||||
static void panel_render_key_counter(Panel *panel, int x, int *width);
|
||||
static void panel_render_mouse_distance(Panel *panel, int x, int *width);
|
||||
static void update_top_memory_process(void);
|
||||
static void panel_render_disk_space(Panel *panel, int x, int *width);
|
||||
static void update_disk_space(void);
|
||||
static void update_top_processes(void);
|
||||
|
||||
|
||||
static int panel_text_width(const char *text, int len)
|
||||
@ -76,32 +131,79 @@ static int panel_text_width(const char *text, int len)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static XftDraw *panel_get_xft_draw(Drawable d)
|
||||
{
|
||||
if (cached_drawable == d && cached_xft_draw != NULL) {
|
||||
return cached_xft_draw;
|
||||
}
|
||||
if (cached_xft_draw != NULL) {
|
||||
XftDrawDestroy(cached_xft_draw);
|
||||
}
|
||||
if (cached_visual == NULL) {
|
||||
cached_visual = DefaultVisual(dwn->display, dwn->screen);
|
||||
}
|
||||
cached_xft_draw = XftDrawCreate(dwn->display, d, cached_visual, dwn->colormap);
|
||||
cached_drawable = d;
|
||||
return cached_xft_draw;
|
||||
}
|
||||
|
||||
static XftColor *panel_get_cached_xft_color(unsigned long color)
|
||||
{
|
||||
if (cached_visual == NULL) {
|
||||
cached_visual = DefaultVisual(dwn->display, dwn->screen);
|
||||
}
|
||||
|
||||
for (int i = 0; i < XFT_COLOR_CACHE_SIZE; i++) {
|
||||
if (xft_color_cache[i].valid && xft_color_cache[i].pixel == color) {
|
||||
xft_color_cache[i].lru_counter = ++xft_color_lru_counter;
|
||||
return &xft_color_cache[i].xft_color;
|
||||
}
|
||||
}
|
||||
|
||||
int oldest_idx = 0;
|
||||
int oldest_lru = xft_color_cache[0].lru_counter;
|
||||
for (int i = 1; i < XFT_COLOR_CACHE_SIZE; i++) {
|
||||
if (!xft_color_cache[i].valid) {
|
||||
oldest_idx = i;
|
||||
break;
|
||||
}
|
||||
if (xft_color_cache[i].lru_counter < oldest_lru) {
|
||||
oldest_lru = xft_color_cache[i].lru_counter;
|
||||
oldest_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (xft_color_cache[oldest_idx].valid) {
|
||||
XftColorFree(dwn->display, cached_visual, dwn->colormap,
|
||||
&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, cached_visual, dwn->colormap,
|
||||
&render_color, &xft_color_cache[oldest_idx].xft_color);
|
||||
xft_color_cache[oldest_idx].pixel = color;
|
||||
xft_color_cache[oldest_idx].valid = true;
|
||||
xft_color_cache[oldest_idx].lru_counter = ++xft_color_lru_counter;
|
||||
|
||||
return &xft_color_cache[oldest_idx].xft_color;
|
||||
}
|
||||
|
||||
static void panel_draw_text_with_font(Drawable d, int x, int y, const char *text,
|
||||
int len, unsigned long color, XftFont *font)
|
||||
{
|
||||
if (text == NULL || dwn == NULL || dwn->display == NULL) return;
|
||||
|
||||
if (font != NULL) {
|
||||
XftDraw *xft_draw = XftDrawCreate(dwn->display, d,
|
||||
DefaultVisual(dwn->display, dwn->screen),
|
||||
dwn->colormap);
|
||||
XftDraw *xft_draw = panel_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, font,
|
||||
XftColor *xft_color = panel_get_cached_xft_color(color);
|
||||
XftDrawStringUtf8(xft_draw, xft_color, font,
|
||||
x, y, (const FcChar8 *)text, len);
|
||||
|
||||
XftColorFree(dwn->display, DefaultVisual(dwn->display, dwn->screen),
|
||||
dwn->colormap, &xft_color);
|
||||
XftDrawDestroy(xft_draw);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -317,12 +419,13 @@ void panel_render(Panel *panel)
|
||||
int mouse_dist_x = key_counter_x + key_counter_width + WIDGET_SPACING;
|
||||
panel_render_mouse_distance(panel, mouse_dist_x, &mouse_dist_width);
|
||||
|
||||
int top_mem_width = 0;
|
||||
int top_mem_x = mouse_dist_x + mouse_dist_width + WIDGET_SPACING;
|
||||
panel_render_top_mem_process(panel, top_mem_x, &top_mem_width);
|
||||
int disk_space_width = 0;
|
||||
int disk_space_x = mouse_dist_x + mouse_dist_width + WIDGET_SPACING;
|
||||
panel_render_disk_space(panel, disk_space_x, &disk_space_width);
|
||||
|
||||
int clock_width = panel_text_width(clock_buffer, strlen(clock_buffer));
|
||||
int stats_width = panel_calculate_stats_width();
|
||||
int top_proc_width = panel_calculate_top_process_width();
|
||||
|
||||
int clock_x = panel->width - clock_width - PANEL_PADDING;
|
||||
panel_render_clock(panel, clock_x, &width);
|
||||
@ -330,9 +433,14 @@ void panel_render(Panel *panel)
|
||||
int stats_x = clock_x - stats_width - WIDGET_SPACING;
|
||||
panel_render_system_stats(panel, stats_x, &width);
|
||||
|
||||
int news_start = top_mem_x + top_mem_width + WIDGET_SPACING;
|
||||
int news_max_width = stats_x - news_start - WIDGET_SPACING;
|
||||
int top_proc_x = stats_x - top_proc_width - WIDGET_SPACING;
|
||||
int actual_top_proc_width = 0;
|
||||
panel_render_top_process(panel, top_proc_x, &actual_top_proc_width);
|
||||
|
||||
int news_start = disk_space_x + disk_space_width + WIDGET_SPACING;
|
||||
int news_max_width = top_proc_x - news_start - WIDGET_SPACING;
|
||||
if (news_max_width > 100) {
|
||||
panel_set_news_region(news_start, news_max_width);
|
||||
int news_width = 0;
|
||||
news_render(panel, news_start, news_max_width, &news_width);
|
||||
}
|
||||
@ -340,18 +448,20 @@ void panel_render(Panel *panel)
|
||||
|
||||
XCopyArea(dpy, panel->buffer, panel->window, dwn->gc,
|
||||
0, 0, panel->width, panel->height, 0, 0);
|
||||
|
||||
XFlush(dpy);
|
||||
}
|
||||
|
||||
void panel_render_all(void)
|
||||
{
|
||||
if (dwn->top_panel != NULL) {
|
||||
panel_render(dwn->top_panel);
|
||||
dwn->top_panel->dirty = false;
|
||||
}
|
||||
if (dwn->bottom_panel != NULL) {
|
||||
panel_render(dwn->bottom_panel);
|
||||
dwn->bottom_panel->dirty = false;
|
||||
}
|
||||
panels_dirty = false;
|
||||
XFlush(dwn->display);
|
||||
}
|
||||
|
||||
void panel_render_workspaces(Panel *panel, int x, int *width)
|
||||
@ -456,9 +566,9 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
|
||||
|
||||
int max_text_width = item_width - 8;
|
||||
bool truncated = false;
|
||||
while (panel_text_width(title, strlen(title)) > max_text_width && strlen(title) > 3) {
|
||||
size_t len = strlen(title);
|
||||
size_t cut = len - 1;
|
||||
size_t title_len = strlen(title);
|
||||
while (panel_text_width(title, (int)title_len) > max_text_width && title_len > 3) {
|
||||
size_t cut = title_len - 1;
|
||||
while (cut > 0 && (title[cut] & 0xC0) == 0x80) {
|
||||
cut--;
|
||||
}
|
||||
@ -470,10 +580,12 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
|
||||
cut = sizeof(title) - 4;
|
||||
}
|
||||
title[cut] = '\0';
|
||||
title_len = cut;
|
||||
truncated = true;
|
||||
}
|
||||
if (truncated) {
|
||||
strncat(title, "...", sizeof(title) - strlen(title) - 1);
|
||||
strncat(title, "...", sizeof(title) - title_len - 1);
|
||||
title_len += 3;
|
||||
}
|
||||
|
||||
unsigned long fg;
|
||||
@ -805,22 +917,100 @@ void panel_update_system_stats(void)
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
update_top_memory_process();
|
||||
long now = get_time_ms();
|
||||
if (now - last_memory_scan >= PROCESS_SCAN_INTERVAL_MS) {
|
||||
update_top_processes();
|
||||
last_memory_scan = now;
|
||||
}
|
||||
|
||||
static int prev_cpu = -1;
|
||||
static int prev_mem = -1;
|
||||
bool stats_changed = (prev_cpu != sys_stats.cpu_percent) ||
|
||||
(prev_mem != sys_stats.mem_percent);
|
||||
if (stats_changed) {
|
||||
prev_cpu = sys_stats.cpu_percent;
|
||||
prev_mem = sys_stats.mem_percent;
|
||||
panel_invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
static void update_top_memory_process(void)
|
||||
static ProcCpuState *find_cpu_history(int pid)
|
||||
{
|
||||
for (int i = 0; i < proc_cpu_history_count; i++) {
|
||||
if (proc_cpu_history[i].pid == pid) {
|
||||
return &proc_cpu_history[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ProcCpuState *add_cpu_history(int pid)
|
||||
{
|
||||
if (proc_cpu_history_count < PROC_CPU_HISTORY_SIZE) {
|
||||
ProcCpuState *state = &proc_cpu_history[proc_cpu_history_count++];
|
||||
state->pid = pid;
|
||||
state->prev_utime = 0;
|
||||
state->prev_stime = 0;
|
||||
state->prev_total_cpu = 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
char path[32];
|
||||
for (int i = 0; i < PROC_CPU_HISTORY_SIZE; i++) {
|
||||
snprintf(path, sizeof(path), "/proc/%d", proc_cpu_history[i].pid);
|
||||
if (access(path, F_OK) != 0) {
|
||||
proc_cpu_history[i].pid = pid;
|
||||
proc_cpu_history[i].prev_utime = 0;
|
||||
proc_cpu_history[i].prev_stime = 0;
|
||||
proc_cpu_history[i].prev_total_cpu = 0;
|
||||
return &proc_cpu_history[i];
|
||||
}
|
||||
}
|
||||
|
||||
proc_cpu_history[0].pid = pid;
|
||||
proc_cpu_history[0].prev_utime = 0;
|
||||
proc_cpu_history[0].prev_stime = 0;
|
||||
proc_cpu_history[0].prev_total_cpu = 0;
|
||||
return &proc_cpu_history[0];
|
||||
}
|
||||
|
||||
static unsigned long long read_total_cpu_time(void)
|
||||
{
|
||||
FILE *fp = fopen("/proc/stat", "r");
|
||||
if (fp == NULL) return 0;
|
||||
|
||||
char line[256];
|
||||
unsigned long long total = 0;
|
||||
if (fgets(line, sizeof(line), fp) != NULL) {
|
||||
unsigned long long user, nice, system, idle, iowait, irq, softirq, steal;
|
||||
if (sscanf(line, "cpu %llu %llu %llu %llu %llu %llu %llu %llu",
|
||||
&user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal) >= 4) {
|
||||
total = user + nice + system + idle + iowait + irq + softirq + steal;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
return total;
|
||||
}
|
||||
|
||||
static void update_top_processes(void)
|
||||
{
|
||||
DIR *proc_dir = opendir("/proc");
|
||||
if (proc_dir == NULL) {
|
||||
sys_stats.top_mem_process[0] = '\0';
|
||||
sys_stats.top_mem_percent = 0;
|
||||
sys_stats.top_mem_count = 0;
|
||||
sys_stats.top_cpu_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long max_rss = 0;
|
||||
char max_name[64] = "";
|
||||
unsigned long long current_total_cpu = read_total_cpu_time();
|
||||
unsigned long long cpu_delta = current_total_cpu - prev_total_cpu_time;
|
||||
|
||||
unsigned long top_rss[3] = {0, 0, 0};
|
||||
char top_mem_names[3][64] = {"", "", ""};
|
||||
unsigned long mem_total_kb = (unsigned long)sys_stats.mem_total_mb * 1024;
|
||||
|
||||
int top_cpu_pcts[3] = {0, 0, 0};
|
||||
char top_cpu_names[3][64] = {"", "", ""};
|
||||
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(proc_dir)) != NULL) {
|
||||
bool is_pid = true;
|
||||
@ -834,8 +1024,10 @@ static void update_top_memory_process(void)
|
||||
}
|
||||
if (!is_pid || name_len > 20) continue;
|
||||
|
||||
int pid = atoi(entry->d_name);
|
||||
|
||||
char status_path[64];
|
||||
snprintf(status_path, sizeof(status_path), "/proc/%.20s/status", entry->d_name);
|
||||
snprintf(status_path, sizeof(status_path), "/proc/%d/status", pid);
|
||||
|
||||
FILE *fp = fopen(status_path, "r");
|
||||
if (fp == NULL) continue;
|
||||
@ -858,19 +1050,91 @@ static void update_top_memory_process(void)
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
if (rss_kb > max_rss && name[0] != '\0') {
|
||||
max_rss = rss_kb;
|
||||
snprintf(max_name, sizeof(max_name), "%s", name);
|
||||
if (rss_kb > 0 && name[0] != '\0') {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (rss_kb > top_rss[i]) {
|
||||
for (int j = 2; j > i; j--) {
|
||||
top_rss[j] = top_rss[j - 1];
|
||||
memcpy(top_mem_names[j], top_mem_names[j - 1], 64);
|
||||
}
|
||||
top_rss[i] = rss_kb;
|
||||
memcpy(top_mem_names[i], name, 64);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char stat_path[64];
|
||||
snprintf(stat_path, sizeof(stat_path), "/proc/%d/stat", pid);
|
||||
fp = fopen(stat_path, "r");
|
||||
if (fp == NULL) continue;
|
||||
|
||||
unsigned long long utime = 0, stime = 0;
|
||||
char stat_line[512];
|
||||
if (fgets(stat_line, sizeof(stat_line), fp) != NULL) {
|
||||
char *close_paren = strrchr(stat_line, ')');
|
||||
if (close_paren != NULL) {
|
||||
if (sscanf(close_paren + 2, "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %llu %llu",
|
||||
&utime, &stime) == 2) {
|
||||
ProcCpuState *hist = find_cpu_history(pid);
|
||||
if (hist == NULL) {
|
||||
hist = add_cpu_history(pid);
|
||||
}
|
||||
|
||||
if (hist->prev_total_cpu > 0 && cpu_delta > 0) {
|
||||
unsigned long long proc_delta = (utime + stime) - (hist->prev_utime + hist->prev_stime);
|
||||
int num_cpus = (int)sysconf(_SC_NPROCESSORS_ONLN);
|
||||
if (num_cpus < 1) num_cpus = 1;
|
||||
int cpu_pct = (int)((proc_delta * 100 * (unsigned long long)num_cpus) / cpu_delta);
|
||||
if (cpu_pct > 100) cpu_pct = 100;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (cpu_pct > top_cpu_pcts[i] || top_cpu_names[i][0] == '\0') {
|
||||
if (cpu_pct > top_cpu_pcts[i]) {
|
||||
for (int j = 2; j > i; j--) {
|
||||
top_cpu_pcts[j] = top_cpu_pcts[j - 1];
|
||||
memcpy(top_cpu_names[j], top_cpu_names[j - 1], 64);
|
||||
}
|
||||
}
|
||||
top_cpu_pcts[i] = cpu_pct;
|
||||
memcpy(top_cpu_names[i], name, 64);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hist->prev_utime = utime;
|
||||
hist->prev_stime = stime;
|
||||
hist->prev_total_cpu = current_total_cpu;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
closedir(proc_dir);
|
||||
|
||||
snprintf(sys_stats.top_mem_process, sizeof(sys_stats.top_mem_process), "%s", max_name);
|
||||
prev_total_cpu_time = current_total_cpu;
|
||||
|
||||
if (mem_total_kb > 0) {
|
||||
sys_stats.top_mem_percent = (int)((max_rss * 100) / mem_total_kb);
|
||||
} else {
|
||||
sys_stats.top_mem_percent = 0;
|
||||
sys_stats.top_mem_count = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (top_mem_names[i][0] != '\0') {
|
||||
memcpy(sys_stats.top_mem_processes[i], top_mem_names[i], 64);
|
||||
if (mem_total_kb > 0) {
|
||||
sys_stats.top_mem_percents[i] = (int)((top_rss[i] * 100) / mem_total_kb);
|
||||
} else {
|
||||
sys_stats.top_mem_percents[i] = 0;
|
||||
}
|
||||
sys_stats.top_mem_count++;
|
||||
}
|
||||
}
|
||||
|
||||
sys_stats.top_cpu_count = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (top_cpu_names[i][0] != '\0') {
|
||||
memcpy(sys_stats.top_cpu_processes[i], top_cpu_names[i], 64);
|
||||
sys_stats.top_cpu_percents[i] = top_cpu_pcts[i];
|
||||
sys_stats.top_cpu_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1100,7 +1364,23 @@ static void panel_render_mouse_distance(Panel *panel, int x, int *width)
|
||||
*width = panel_text_width(buf, len);
|
||||
}
|
||||
|
||||
static void panel_render_top_mem_process(Panel *panel, int x, int *width)
|
||||
static void update_disk_space(void)
|
||||
{
|
||||
long now = get_time_ms();
|
||||
if (now - disk_last_update < DISK_UPDATE_INTERVAL_MS && disk_last_update > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct statvfs stat;
|
||||
if (statvfs("/", &stat) == 0) {
|
||||
unsigned long long block_size = stat.f_frsize;
|
||||
disk_free_gb = (double)(stat.f_bavail * block_size) / (1024.0 * 1024.0 * 1024.0);
|
||||
disk_total_gb = (double)(stat.f_blocks * block_size) / (1024.0 * 1024.0 * 1024.0);
|
||||
}
|
||||
disk_last_update = now;
|
||||
}
|
||||
|
||||
static void panel_render_disk_space(Panel *panel, int x, int *width)
|
||||
{
|
||||
if (panel == NULL || width == NULL) {
|
||||
*width = 0;
|
||||
@ -1110,30 +1390,247 @@ static void panel_render_top_mem_process(Panel *panel, int x, int *width)
|
||||
*width = 0;
|
||||
return;
|
||||
}
|
||||
if (sys_stats.top_mem_process[0] == '\0') {
|
||||
*width = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
update_disk_space();
|
||||
|
||||
const ColorScheme *colors = config_get_colors();
|
||||
int text_y = panel_text_y(panel->height);
|
||||
|
||||
char buf[32];
|
||||
int len;
|
||||
|
||||
if (disk_total_gb >= 1000.0) {
|
||||
len = snprintf(buf, sizeof(buf), "Disk:%.1fT/%.1fT",
|
||||
disk_free_gb / 1024.0, disk_total_gb / 1024.0);
|
||||
} else {
|
||||
len = snprintf(buf, sizeof(buf), "Disk:%.0fG/%.0fG",
|
||||
disk_free_gb, disk_total_gb);
|
||||
}
|
||||
|
||||
int used_percent = 0;
|
||||
if (disk_total_gb > 0.0) {
|
||||
used_percent = (int)(((disk_total_gb - disk_free_gb) / disk_total_gb) * 100.0);
|
||||
}
|
||||
|
||||
unsigned long color = colors->panel_fg;
|
||||
if (sys_stats.top_mem_percent >= 50) {
|
||||
if (used_percent >= 90) {
|
||||
color = colors->workspace_urgent;
|
||||
} else if (sys_stats.top_mem_percent >= 25) {
|
||||
} else if (used_percent >= 75) {
|
||||
color = 0xFFA500;
|
||||
}
|
||||
color = ambient_glow_bg(color, dwn->ambient_phase + PHASE_OFFSET_TOP_MEM);
|
||||
|
||||
char buf[128];
|
||||
int len = snprintf(buf, sizeof(buf), "%s %d%%",
|
||||
sys_stats.top_mem_process, sys_stats.top_mem_percent);
|
||||
|
||||
panel_draw_text(panel->buffer, x, text_y, buf, len, color);
|
||||
unsigned long fg = ambient_glow_bg(color, dwn->ambient_phase + PHASE_OFFSET_DISK_SPACE);
|
||||
panel_draw_text(panel->buffer, x, text_y, buf, len, fg);
|
||||
*width = panel_text_width(buf, len);
|
||||
}
|
||||
|
||||
static int panel_calculate_top_process_width(void)
|
||||
{
|
||||
if (dwn->xft_font == NULL && dwn->font == NULL) return 0;
|
||||
|
||||
int mem_count = sys_stats.top_mem_count;
|
||||
int cpu_count = sys_stats.top_cpu_count;
|
||||
if (mem_count == 0 && cpu_count == 0) return 0;
|
||||
|
||||
int idx = sys_stats.process_display_index;
|
||||
char buf[128];
|
||||
char name_buf[TOP_PROCESS_NAME_MAX_LEN + 1];
|
||||
const char *proc_name;
|
||||
size_t name_len;
|
||||
int len;
|
||||
int total = 0;
|
||||
|
||||
if (mem_count > 0) {
|
||||
int mem_idx = (idx >= mem_count) ? 0 : idx;
|
||||
proc_name = sys_stats.top_mem_processes[mem_idx];
|
||||
name_len = strlen(proc_name);
|
||||
if (name_len > TOP_PROCESS_NAME_MAX_LEN) {
|
||||
strncpy(name_buf, proc_name, TOP_PROCESS_NAME_MAX_LEN - 2);
|
||||
name_buf[TOP_PROCESS_NAME_MAX_LEN - 2] = '\0';
|
||||
strncat(name_buf, "..", 3);
|
||||
proc_name = name_buf;
|
||||
}
|
||||
len = snprintf(buf, sizeof(buf), "M:%s %d%%", proc_name, sys_stats.top_mem_percents[mem_idx]);
|
||||
total += panel_text_width(buf, len);
|
||||
}
|
||||
|
||||
if (cpu_count > 0) {
|
||||
if (total > 0) total += WIDGET_SPACING;
|
||||
int cpu_idx = (idx >= cpu_count) ? 0 : idx;
|
||||
proc_name = sys_stats.top_cpu_processes[cpu_idx];
|
||||
name_len = strlen(proc_name);
|
||||
if (name_len > TOP_PROCESS_NAME_MAX_LEN) {
|
||||
strncpy(name_buf, proc_name, TOP_PROCESS_NAME_MAX_LEN - 2);
|
||||
name_buf[TOP_PROCESS_NAME_MAX_LEN - 2] = '\0';
|
||||
strncat(name_buf, "..", 3);
|
||||
proc_name = name_buf;
|
||||
}
|
||||
len = snprintf(buf, sizeof(buf), "C:%s %d%%", proc_name, sys_stats.top_cpu_percents[cpu_idx]);
|
||||
total += panel_text_width(buf, len);
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static void panel_render_top_process(Panel *panel, int x, int *width)
|
||||
{
|
||||
if (panel == NULL || width == NULL) {
|
||||
*width = 0;
|
||||
return;
|
||||
}
|
||||
if (dwn->xft_font == NULL && dwn->font == NULL) {
|
||||
*width = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int mem_count = sys_stats.top_mem_count;
|
||||
int cpu_count = sys_stats.top_cpu_count;
|
||||
if (mem_count == 0 && cpu_count == 0) {
|
||||
*width = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
long now = get_time_ms();
|
||||
if (now - sys_stats.process_last_cycle >= 3000) {
|
||||
sys_stats.process_display_index++;
|
||||
if (sys_stats.process_display_index >= 3) {
|
||||
sys_stats.process_display_index = 0;
|
||||
}
|
||||
sys_stats.process_last_cycle = now;
|
||||
}
|
||||
|
||||
const ColorScheme *colors = config_get_colors();
|
||||
int text_y = panel_text_y(panel->height);
|
||||
int idx = sys_stats.process_display_index;
|
||||
|
||||
char buf[128];
|
||||
char name_buf[TOP_PROCESS_NAME_MAX_LEN + 1];
|
||||
int len;
|
||||
unsigned long color;
|
||||
const char *proc_name;
|
||||
size_t name_len;
|
||||
int text_width;
|
||||
|
||||
int total_width = panel_calculate_top_process_width();
|
||||
int right_edge = x + total_width;
|
||||
int current_right = right_edge;
|
||||
|
||||
if (cpu_count > 0) {
|
||||
int cpu_idx = (idx >= cpu_count) ? 0 : idx;
|
||||
int percent = sys_stats.top_cpu_percents[cpu_idx];
|
||||
color = colors->panel_fg;
|
||||
if (percent >= 50) {
|
||||
color = 0x4169E1;
|
||||
} else if (percent >= 25) {
|
||||
color = 0x00CED1;
|
||||
}
|
||||
color = ambient_glow_bg(color, dwn->ambient_phase + PHASE_OFFSET_TOP_CPU);
|
||||
proc_name = sys_stats.top_cpu_processes[cpu_idx];
|
||||
name_len = strlen(proc_name);
|
||||
if (name_len > TOP_PROCESS_NAME_MAX_LEN) {
|
||||
strncpy(name_buf, proc_name, TOP_PROCESS_NAME_MAX_LEN - 2);
|
||||
name_buf[TOP_PROCESS_NAME_MAX_LEN - 2] = '\0';
|
||||
strncat(name_buf, "..", 3);
|
||||
proc_name = name_buf;
|
||||
}
|
||||
len = snprintf(buf, sizeof(buf), "C:%s %d%%", proc_name, percent);
|
||||
text_width = panel_text_width(buf, len);
|
||||
int text_x = current_right - text_width;
|
||||
panel_draw_text(panel->buffer, text_x, text_y, buf, len, color);
|
||||
current_right = text_x - WIDGET_SPACING;
|
||||
}
|
||||
|
||||
if (mem_count > 0) {
|
||||
int mem_idx = (idx >= mem_count) ? 0 : idx;
|
||||
int percent = sys_stats.top_mem_percents[mem_idx];
|
||||
color = colors->panel_fg;
|
||||
if (percent >= 50) {
|
||||
color = colors->workspace_urgent;
|
||||
} else if (percent >= 25) {
|
||||
color = 0xFFA500;
|
||||
}
|
||||
color = ambient_glow_bg(color, dwn->ambient_phase + PHASE_OFFSET_TOP_MEM);
|
||||
proc_name = sys_stats.top_mem_processes[mem_idx];
|
||||
name_len = strlen(proc_name);
|
||||
if (name_len > TOP_PROCESS_NAME_MAX_LEN) {
|
||||
strncpy(name_buf, proc_name, TOP_PROCESS_NAME_MAX_LEN - 2);
|
||||
name_buf[TOP_PROCESS_NAME_MAX_LEN - 2] = '\0';
|
||||
strncat(name_buf, "..", 3);
|
||||
proc_name = name_buf;
|
||||
}
|
||||
len = snprintf(buf, sizeof(buf), "M:%s %d%%", proc_name, percent);
|
||||
text_width = panel_text_width(buf, len);
|
||||
int text_x = current_right - text_width;
|
||||
panel_draw_text(panel->buffer, text_x, text_y, buf, len, color);
|
||||
}
|
||||
|
||||
*width = total_width;
|
||||
}
|
||||
|
||||
|
||||
void panel_invalidate(void)
|
||||
{
|
||||
panels_dirty = true;
|
||||
if (dwn->top_panel) dwn->top_panel->dirty = true;
|
||||
if (dwn->bottom_panel) dwn->bottom_panel->dirty = true;
|
||||
}
|
||||
|
||||
void panel_invalidate_taskbar(void)
|
||||
{
|
||||
panels_dirty = true;
|
||||
if (dwn->top_panel) dwn->top_panel->dirty = true;
|
||||
}
|
||||
|
||||
void panel_invalidate_news(void)
|
||||
{
|
||||
news_region_dirty = true;
|
||||
}
|
||||
|
||||
void panel_set_news_region(int x, int width)
|
||||
{
|
||||
news_region_x = x;
|
||||
news_region_width = width;
|
||||
}
|
||||
|
||||
void panel_render_news_only(void)
|
||||
{
|
||||
if (dwn->bottom_panel == NULL || !news_region_dirty) {
|
||||
return;
|
||||
}
|
||||
if (news_region_width <= 0) {
|
||||
news_region_dirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Panel *panel = dwn->bottom_panel;
|
||||
Display *dpy = dwn->display;
|
||||
const ColorScheme *colors = config_get_colors();
|
||||
|
||||
unsigned long bg = ambient_glow_bg(colors->panel_bg,
|
||||
dwn->ambient_phase + PHASE_OFFSET_PANEL_BG);
|
||||
XSetForeground(dpy, dwn->gc, bg);
|
||||
XFillRectangle(dpy, panel->buffer, dwn->gc,
|
||||
news_region_x, 0, (unsigned int)news_region_width, (unsigned int)panel->height);
|
||||
|
||||
int width;
|
||||
news_render(panel, news_region_x, news_region_width, &width);
|
||||
|
||||
XCopyArea(dpy, panel->buffer, panel->window, dwn->gc,
|
||||
news_region_x, 0, (unsigned int)news_region_width, (unsigned int)panel->height,
|
||||
news_region_x, 0);
|
||||
|
||||
news_region_dirty = false;
|
||||
}
|
||||
|
||||
bool panel_needs_render(void)
|
||||
{
|
||||
return panels_dirty;
|
||||
}
|
||||
|
||||
bool panel_news_needs_render(void)
|
||||
{
|
||||
return news_region_dirty;
|
||||
}
|
||||
|
||||
void panel_init_systray(void)
|
||||
{
|
||||
|
||||
101
src/systray.c
101
src/systray.c
@ -63,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)
|
||||
{
|
||||
@ -71,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;
|
||||
}
|
||||
}
|
||||
|
||||
39
src/util.c
39
src/util.c
@ -796,14 +796,39 @@ unsigned long dim_color(unsigned long color, float factor)
|
||||
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 * sinf(phase);
|
||||
float mod_g = 0.25f * sinf(phase + 2.094f);
|
||||
float mod_b = 0.25f * sinf(phase + 4.189f);
|
||||
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);
|
||||
@ -824,7 +849,7 @@ 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 * sinf(phase);
|
||||
float mod = 0.25f * fast_sin(phase);
|
||||
|
||||
r = r + (int)(r * mod);
|
||||
g = g + (int)(g * mod);
|
||||
@ -845,9 +870,9 @@ 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 * sinf(phase);
|
||||
float mod_g = 0.45f * sinf(phase + 2.094f);
|
||||
float mod_b = 0.45f * sinf(phase + 4.189f);
|
||||
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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user