feat: add ambient glow effects with key counter, mouse distance, and top memory stats

build: add XInput2 library dependency for raw key event monitoring
This commit is contained in:
retoor 2026-01-27 00:47:23 +01:00
parent b59c279bb0
commit deb173e2d2
19 changed files with 429 additions and 36 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),)

BIN
bin/dwn

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

@ -203,8 +203,32 @@ 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_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
extern DWNState *dwn;
int dwn_init(void);

View File

@ -59,6 +59,8 @@ 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

@ -1336,7 +1336,8 @@ unsigned long client_get_glow_text_color(Client *client)
if (client == NULL || !client->text_glow.active) {
return 0xFFFFFF;
}
return glow_color(client->text_glow.base_color, client->text_glow.phase);
float phase = dwn->ambient_phase + client->text_glow.phase;
return glow_color(client->text_glow.base_color, phase);
}
void client_update_animations(void)
@ -1361,20 +1362,12 @@ void client_update_animations(void)
}
if (c->text_glow.active && c->workspace == (unsigned int)dwn->current_workspace) {
c->text_glow.phase += 0.01f * c->text_glow.speed;
c->text_glow.phase += 0.005f * c->text_glow.speed;
if (c->text_glow.phase > 6.283f) {
c->text_glow.phase -= 6.283f;
}
needs_redraw = true;
}
if (c->frame != None && c->workspace == (unsigned int)dwn->current_workspace) {
if (!(c->flags & CLIENT_MINIMIZED)) {
Workspace *ws = workspace_get(c->workspace);
bool focused = (ws != NULL && ws->focused == c);
decorations_render(c, focused);
}
}
}
if (needs_redraw) {

View File

@ -64,7 +64,8 @@ void decorations_render_title_bar(Client *client, bool focused)
Workspace *ws = workspace_get(client->workspace);
bool is_focused = (ws != NULL && ws->focused == client);
unsigned long bg_color = is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
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);
@ -173,11 +174,16 @@ void decorations_render_buttons(Client *client, bool focused)
const ColorScheme *colors = config_get_colors();
Workspace *ws = workspace_get(client->workspace);
bool is_focused = (ws != NULL && ws->focused == client);
unsigned long bg_color = is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
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);
@ -187,14 +193,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);

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

@ -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,71 @@ 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);
XISelectEvents(dwn->display, dwn->root, &evmask, 1);
LOG_INFO("XInput2 initialized for raw key 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();
}
}
}
XFreeEventData(dwn->display, cookie);
}
static void init_monitors(void)
{
if (!XineramaIsActive(dwn->display)) {
@ -790,6 +859,8 @@ int dwn_init(void)
init_monitors();
init_xinput2();
workspace_init();
decorations_init();
@ -842,6 +913,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;
@ -913,6 +988,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);
}
@ -930,13 +1010,39 @@ 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 = 3.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();
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) {
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) {
@ -947,8 +1053,6 @@ void dwn_run(void)
}
}
long now = get_time_ms();
if (now - last_news_update >= 16) {
news_update();
panel_render_all();

View File

@ -645,6 +645,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 +655,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;

View File

@ -19,6 +19,8 @@
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <ctype.h>
#include <X11/Xft/Xft.h>
#define PANEL_PADDING 12
@ -42,12 +44,18 @@ typedef struct {
float load_15min;
unsigned long long prev_idle;
unsigned long long prev_total;
char top_mem_process[64];
int top_mem_percent;
} SystemStats;
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_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 int panel_text_width(const char *text, int len)
@ -265,7 +273,8 @@ void panel_render(Panel *panel)
Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors();
XSetForeground(dpy, dwn->gc, colors->panel_bg);
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, 0, 0, panel->width, panel->height);
int x = PANEL_PADDING;
@ -294,11 +303,24 @@ void panel_render(Panel *panel)
int date_width = 0;
if (dwn->xft_font != NULL || dwn->font != NULL) {
int text_y = panel_text_y(panel->height);
unsigned long date_fg = ambient_glow_bg(colors->panel_fg, dwn->ambient_phase + PHASE_OFFSET_CLOCK);
panel_draw_text(panel->buffer, x, text_y, date_buffer,
strlen(date_buffer), colors->panel_fg);
strlen(date_buffer), date_fg);
date_width = panel_text_width(date_buffer, strlen(date_buffer));
}
int key_counter_width = 0;
int key_counter_x = PANEL_PADDING + date_width + WIDGET_SPACING;
panel_render_key_counter(panel, key_counter_x, &key_counter_width);
int mouse_dist_width = 0;
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 clock_width = panel_text_width(clock_buffer, strlen(clock_buffer));
int stats_width = panel_calculate_stats_width();
@ -308,7 +330,7 @@ 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 = PANEL_PADDING + date_width + WIDGET_SPACING * 2;
int news_start = top_mem_x + top_mem_width + WIDGET_SPACING;
int news_max_width = stats_x - news_start - WIDGET_SPACING;
if (news_max_width > 100) {
int news_width = 0;
@ -348,8 +370,10 @@ void panel_render_workspaces(Panel *panel, int x, int *width)
bool active = (i == dwn->current_workspace);
bool has_clients = !workspace_is_empty(i);
unsigned long bg = active ? colors->workspace_active : colors->panel_bg;
XSetForeground(dpy, dwn->gc, bg);
unsigned long bg_color = active ?
ambient_glow_accent(colors->workspace_active, dwn->ambient_phase + PHASE_OFFSET_WORKSPACES) :
ambient_glow_bg(colors->panel_bg, dwn->ambient_phase + PHASE_OFFSET_WORKSPACES);
XSetForeground(dpy, dwn->gc, bg_color);
XFillRectangle(dpy, panel->buffer, dwn->gc,
x + i * WORKSPACE_WIDTH, 0,
WORKSPACE_WIDTH, panel->height);
@ -358,7 +382,7 @@ void panel_render_workspaces(Panel *panel, int x, int *width)
snprintf(num, sizeof(num), "%d", i + 1);
unsigned long fg = active ? colors->panel_bg :
(has_clients ? colors->panel_fg : colors->workspace_inactive);
ambient_glow_bg(has_clients ? colors->panel_fg : colors->workspace_inactive, dwn->ambient_phase + PHASE_OFFSET_WORKSPACES);
int text_x = x + i * WORKSPACE_WIDTH + (WORKSPACE_WIDTH - panel_text_width(num, strlen(num))) / 2;
panel_draw_text(panel->buffer, text_x, text_y, num, strlen(num), fg);
@ -454,7 +478,7 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
unsigned long fg;
if (minimized) {
fg = colors->panel_fg;
fg = ambient_glow_bg(colors->panel_fg, dwn->ambient_phase + PHASE_OFFSET_TASKBAR);
} else {
fg = client_get_glow_text_color(c);
}
@ -483,8 +507,9 @@ void panel_render_clock(Panel *panel, int x, int *width)
const ColorScheme *colors = config_get_colors();
int text_y = panel_text_y(panel->height);
unsigned long fg = ambient_glow_bg(colors->panel_fg, dwn->ambient_phase + PHASE_OFFSET_CLOCK);
panel_draw_text(panel->buffer, x, text_y, clock_buffer,
strlen(clock_buffer), colors->panel_fg);
strlen(clock_buffer), fg);
*width = panel_text_width(clock_buffer, strlen(clock_buffer));
}
@ -511,8 +536,8 @@ void panel_render_layout_indicator(Panel *panel, int x, int *width)
Workspace *ws = workspace_get_current();
const char *symbol = layout_get_symbol(ws != NULL ? ws->layout : LAYOUT_TILING);
panel_draw_text(panel->buffer, x, text_y, symbol, strlen(symbol),
colors->workspace_active);
unsigned long fg = ambient_glow_accent(colors->workspace_active, dwn->ambient_phase + PHASE_OFFSET_WORKSPACES);
panel_draw_text(panel->buffer, x, text_y, symbol, strlen(symbol), fg);
*width = panel_text_width(symbol, strlen(symbol));
}
@ -531,8 +556,8 @@ void panel_render_ai_status(Panel *panel, int x, int *width)
const char *status = dwn->ai_enabled ? "[AI]" : "";
panel_draw_text(panel->buffer, x, text_y, status, strlen(status),
colors->workspace_active);
unsigned long fg = ambient_glow_accent(colors->workspace_active, dwn->ambient_phase + PHASE_OFFSET_SYSTRAY);
panel_draw_text(panel->buffer, x, text_y, status, strlen(status), fg);
*width = panel_text_width(status, strlen(status));
}
@ -779,6 +804,74 @@ void panel_update_system_stats(void)
}
fclose(fp);
}
update_top_memory_process();
}
static void update_top_memory_process(void)
{
DIR *proc_dir = opendir("/proc");
if (proc_dir == NULL) {
sys_stats.top_mem_process[0] = '\0';
sys_stats.top_mem_percent = 0;
return;
}
unsigned long max_rss = 0;
char max_name[64] = "";
unsigned long mem_total_kb = (unsigned long)sys_stats.mem_total_mb * 1024;
struct dirent *entry;
while ((entry = readdir(proc_dir)) != NULL) {
bool is_pid = true;
size_t name_len = 0;
for (int i = 0; entry->d_name[i] != '\0'; i++) {
if (!isdigit((unsigned char)entry->d_name[i])) {
is_pid = false;
break;
}
name_len++;
}
if (!is_pid || name_len > 20) continue;
char status_path[64];
snprintf(status_path, sizeof(status_path), "/proc/%.20s/status", entry->d_name);
FILE *fp = fopen(status_path, "r");
if (fp == NULL) continue;
char line[256];
char name[64] = "";
unsigned long rss_kb = 0;
while (fgets(line, sizeof(line), fp) != NULL) {
if (strncmp(line, "Name:", 5) == 0) {
char *start = line + 5;
while (*start == ' ' || *start == '\t') start++;
size_t len = strlen(start);
if (len > 0 && start[len - 1] == '\n') start[len - 1] = '\0';
snprintf(name, sizeof(name), "%s", start);
} else if (strncmp(line, "VmRSS:", 6) == 0) {
sscanf(line + 6, " %lu", &rss_kb);
break;
}
}
fclose(fp);
if (rss_kb > max_rss && name[0] != '\0') {
max_rss = rss_kb;
snprintf(max_name, sizeof(max_name), "%s", name);
}
}
closedir(proc_dir);
snprintf(sys_stats.top_mem_process, sizeof(sys_stats.top_mem_process), "%s", max_name);
if (mem_total_kb > 0) {
sys_stats.top_mem_percent = (int)((max_rss * 100) / mem_total_kb);
} else {
sys_stats.top_mem_percent = 0;
}
}
static int panel_calculate_stats_width(void)
@ -852,6 +945,7 @@ static void panel_render_system_stats(Panel *panel, int x, int *width)
} else if (sys_stats.cpu_percent >= 70) {
cpu_color = 0xFFA500;
}
cpu_color = ambient_glow_bg(cpu_color, dwn->ambient_phase + PHASE_OFFSET_STATS);
len = snprintf(stats_buf, sizeof(stats_buf), "CPU:%2d%%", sys_stats.cpu_percent);
panel_draw_text(panel->buffer, current_x, text_y, stats_buf, len, cpu_color);
@ -863,6 +957,7 @@ static void panel_render_system_stats(Panel *panel, int x, int *width)
} else if (sys_stats.mem_percent >= 75) {
mem_color = 0xFFA500;
}
mem_color = ambient_glow_bg(mem_color, dwn->ambient_phase + PHASE_OFFSET_STATS);
if (sys_stats.mem_total_mb >= 1024) {
len = snprintf(stats_buf, sizeof(stats_buf), "MEM:%.1fG/%dG",
@ -880,6 +975,7 @@ static void panel_render_system_stats(Panel *panel, int x, int *width)
} else if (sys_stats.load_1min >= 2.0f) {
load_color = 0xFFA500;
}
load_color = ambient_glow_bg(load_color, dwn->ambient_phase + PHASE_OFFSET_STATS);
len = snprintf(stats_buf, sizeof(stats_buf), "Load:%.2f %.2f %.2f",
sys_stats.load_1min, sys_stats.load_5min, sys_stats.load_15min);
@ -896,6 +992,7 @@ static void panel_render_system_stats(Panel *panel, int x, int *width)
} else if (multi_bat.any_charging) {
bat_color = colors->workspace_active;
}
bat_color = ambient_glow_bg(bat_color, dwn->ambient_phase + PHASE_OFFSET_STATS);
const char *bat_icon = multi_bat.any_charging ? "[+]" : "[=]";
if (multi_bat.count == 1) {
@ -919,6 +1016,124 @@ static void panel_render_system_stats(Panel *panel, int x, int *width)
*width = current_x - x;
}
static void panel_render_key_counter(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;
}
const ColorScheme *colors = config_get_colors();
int text_y = panel_text_y(panel->height);
char buf[32];
unsigned long count = dwn->key_press_count;
int len;
if (count >= 1000000) {
len = snprintf(buf, sizeof(buf), "Keys:%.1fM", count / 1000000.0);
} else if (count >= 1000) {
len = snprintf(buf, sizeof(buf), "Keys:%.1fK", count / 1000.0);
} else {
len = snprintf(buf, sizeof(buf), "Keys:%lu", count);
}
long now = get_time_ms();
bool flickering = (now - dwn->key_delete_flicker_time) < 100;
unsigned long fg;
if (flickering) {
unsigned long base = colors->panel_fg;
int r = (int)((base >> 16) & 0xFF);
int g = (int)((base >> 8) & 0xFF);
int b = (int)(base & 0xFF);
r = r + (255 - r) / 2;
g = g + (255 - g) / 2;
b = b + (255 - b) / 2;
fg = ((unsigned long)r << 16) | ((unsigned long)g << 8) | (unsigned long)b;
} else {
fg = ambient_glow_bg(colors->panel_fg,
dwn->ambient_phase + PHASE_OFFSET_KEY_COUNTER);
}
if (flickering) {
panel_draw_text_bold(panel->buffer, x, text_y, buf, len, fg);
} else {
panel_draw_text(panel->buffer, x, text_y, buf, len, fg);
}
*width = panel_text_width(buf, len);
}
static void panel_render_mouse_distance(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;
}
const ColorScheme *colors = config_get_colors();
int text_y = panel_text_y(panel->height);
char buf[32];
double cm = dwn->mouse_distance_pixels / 37.8;
int len;
if (cm >= 100000.0) {
len = snprintf(buf, sizeof(buf), "Mouse:%.2fkm", cm / 100000.0);
} else if (cm >= 100.0) {
len = snprintf(buf, sizeof(buf), "Mouse:%.1fm", cm / 100.0);
} else {
len = snprintf(buf, sizeof(buf), "Mouse:%.0fcm", cm);
}
unsigned long fg = ambient_glow_bg(colors->panel_fg,
dwn->ambient_phase + PHASE_OFFSET_MOUSE_DIST);
panel_draw_text(panel->buffer, x, text_y, buf, len, fg);
*width = panel_text_width(buf, len);
}
static void panel_render_top_mem_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;
}
if (sys_stats.top_mem_process[0] == '\0') {
*width = 0;
return;
}
const ColorScheme *colors = config_get_colors();
int text_y = panel_text_y(panel->height);
unsigned long color = colors->panel_fg;
if (sys_stats.top_mem_percent >= 50) {
color = colors->workspace_urgent;
} else if (sys_stats.top_mem_percent >= 25) {
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);
*width = panel_text_width(buf, len);
}
void panel_init_systray(void)
{

View File

@ -558,7 +558,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);
}
@ -677,6 +678,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;
@ -692,6 +694,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);

View File

@ -818,3 +818,47 @@ unsigned long glow_color(unsigned long base, float phase)
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 * sinf(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 * sinf(phase);
float mod_g = 0.45f * sinf(phase + 2.094f);
float mod_b = 0.45f * sinf(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);
}