Compare commits

...

7 Commits

Author SHA1 Message Date
b0b1a854cf feat: implement fixed-width toggling top process display for CPU and memory 2026-01-27 12:18:45 +01:00
2017a9b666 docs: expand README with detailed features, architecture, and configuration guides 2026-01-27 02:50:47 +01:00
a75c1d90cc perf: increase speed multiplier from 3.0 to 10.0 2026-01-27 00:51:19 +01:00
deb173e2d2 feat: add ambient glow effects with key counter, mouse distance, and top memory stats
build: add XInput2 library dependency for raw key event monitoring
2026-01-27 00:47:23 +01:00
b59c279bb0 feat: add text glow animation for client titles
feat: enhance alt-tab selection with bold font rendering
refactor: simplify title bar color logic to use focused/unfocused states
perf: increase focus animation duration for smoother transitions
refactor: update build dependencies for decorations and workspace modules
2026-01-26 23:36:06 +01:00
f55f024d22 feat: add animated focus transitions for window titles and taskbar colors 2026-01-26 23:16:04 +01:00
ea12677dac feat: add directional window resizing
feat: support multi-battery monitoring
fix: prevent windows from exceeding layout bounds
refactor: adjust snap constraints for borders and title bars
2026-01-26 20:40:08 +01:00
33 changed files with 2101 additions and 353 deletions

View File

@ -5,7 +5,7 @@
CC = gcc
CFLAGS = -Wall -Wextra -Wpedantic -Wshadow -O2 -I./include
CFLAGS += -fstack-protector-strong -D_FORTIFY_SOURCE=2
LDFLAGS = -lX11 -lXext -lXinerama -lXrandr -lXft -lfontconfig -ldbus-1 -lcurl -lm -lpthread -lXtst
LDFLAGS = -lX11 -lXext -lXinerama -lXrandr -lXft -lfontconfig -ldbus-1 -lcurl -lm -lpthread -lXtst -lXi
# 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
View File

@ -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
bin/dwn

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,9 @@
build/decorations.o: src/decorations.c include/decorations.h \
include/dwn.h include/client.h include/config.h include/util.h
include/dwn.h include/client.h include/config.h include/util.h \
include/workspace.h
include/decorations.h:
include/dwn.h:
include/client.h:
include/config.h:
include/util.h:
include/workspace.h:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,6 @@
build/workspace.o: src/workspace.c include/workspace.h include/dwn.h \
include/client.h include/layout.h include/atoms.h include/util.h \
include/config.h include/panel.h include/api.h
include/config.h include/panel.h include/api.h include/decorations.h
include/workspace.h:
include/dwn.h:
include/client.h:
@ -10,3 +10,4 @@ include/util.h:
include/config.h:
include/panel.h:
include/api.h:
include/decorations.h:

Binary file not shown.

View File

@ -70,4 +70,9 @@ Client *client_get_prev(Client *client);
Client *client_get_first(void);
Client *client_get_last(void);
void client_start_focus_animation(Client *client, bool gaining_focus);
unsigned long client_get_animated_title_color(Client *client);
unsigned long client_get_glow_text_color(Client *client);
void client_update_animations(void);
#endif

View File

@ -25,6 +25,11 @@
#define MAX_NOTIFICATIONS 32
#define MAX_KEYBINDINGS 64
#define RESIZE_LEFT 1
#define RESIZE_RIGHT 2
#define RESIZE_TOP 4
#define RESIZE_BOTTOM 8
#define DEFAULT_BORDER_WIDTH 0
#define DEFAULT_TITLE_HEIGHT 28
#define DEFAULT_PANEL_HEIGHT 32
@ -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);

View File

@ -22,6 +22,7 @@ struct Panel {
int width, height;
bool visible;
Pixmap buffer;
bool dirty;
};
Panel *panel_create(PanelPosition position);
@ -31,6 +32,13 @@ void panels_cleanup(void);
void panel_render(Panel *panel);
void panel_render_all(void);
void panel_invalidate(void);
void panel_invalidate_taskbar(void);
void panel_invalidate_news(void);
void panel_set_news_region(int x, int width);
void panel_render_news_only(void);
bool panel_needs_render(void);
bool panel_news_needs_render(void);
void panel_render_workspaces(Panel *panel, int x, int *width);
void panel_render_taskbar(Panel *panel, int x, int *width);
void panel_render_clock(Panel *panel, int x, int *width);

View File

@ -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);

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}
}
}