/*
* DWN - Desktop Window Manager
* retoor <retoor@molodetz.nl>
* Panel system implementation
*/
#include "panel.h"
#include "workspace.h"
#include "layout.h"
#include "client.h"
#include "config.h"
#include "util.h"
#include "atoms.h"
#include "systray.h"
#include "news.h"
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/statvfs.h>
#include <X11/Xft/Xft.h>
#define PANEL_PADDING 12
#define WIDGET_SPACING 16
#define WORKSPACE_WIDTH 36
#define TASKBAR_ITEM_WIDTH 180
#define TOP_PROCESS_NAME_MAX_LEN 12
#define TOP_PROCESS_WIDGET_WIDTH 150
#define CLOCK_FORMAT "%H:%M:%S"
#define DATE_FORMAT "%Y-%m-%d"
#define XFT_COLOR_CACHE_SIZE 16
#define PROCESS_SCAN_INTERVAL_MS 30000
#define PROC_CPU_HISTORY_SIZE 1024
#define DISK_UPDATE_INTERVAL_MS 60000
static char clock_buffer[32] = "";
static char date_buffer[32] = "";
static double disk_free_gb = 0.0;
static double disk_total_gb = 0.0;
static long disk_last_update = 0;
static bool panels_dirty = true;
static int news_region_x = 0;
static int news_region_width = 0;
static bool news_region_dirty = false;
typedef struct {
unsigned long pixel;
XftColor xft_color;
bool valid;
int lru_counter;
} CachedXftColor;
static CachedXftColor xft_color_cache[XFT_COLOR_CACHE_SIZE];
static Visual *cached_visual = NULL;
static int xft_color_lru_counter = 0;
static bool xft_cache_initialized = false;
static XftDraw *cached_xft_draw = NULL;
static Drawable cached_drawable = None;
static long last_memory_scan = 0;
typedef struct {
int pid;
unsigned long long prev_utime;
unsigned long long prev_stime;
unsigned long long prev_total_cpu;
} ProcCpuState;
typedef struct {
int cpu_percent;
int mem_percent;
int mem_used_mb;
int mem_total_mb;
float load_1min;
float load_5min;
float load_15min;
unsigned long long prev_idle;
unsigned long long prev_total;
char top_mem_processes[3][64];
int top_mem_percents[3];
int top_mem_count;
char top_cpu_processes[3][64];
int top_cpu_percents[3];
int top_cpu_count;
int process_display_index;
int process_category;
long process_last_cycle;
bool process_show_cpu;
long process_toggle_time;
} SystemStats;
static ProcCpuState proc_cpu_history[PROC_CPU_HISTORY_SIZE];
static int proc_cpu_history_count = 0;
static unsigned long long prev_total_cpu_time = 0;
static SystemStats sys_stats = {0};
static void panel_render_system_stats(Panel *panel, int x, int *width);
static int panel_calculate_stats_width(void);
static void panel_render_top_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 panel_render_disk_space(Panel *panel, int x, int *width);
static void update_disk_space(void);
static void update_top_processes(void);
static int panel_text_width(const char *text, int len)
{
if (text == NULL || dwn == NULL) return 0;
if (dwn->xft_font != NULL) {
XGlyphInfo extents;
XftTextExtentsUtf8(dwn->display, dwn->xft_font,
(const FcChar8 *)text, len, &extents);
return extents.xOff;
}
if (dwn->font != NULL) {
return XTextWidth(dwn->font, text, len);
}
return 0;
}
static XftDraw *panel_get_xft_draw(Drawable d)
{
if (cached_drawable == d && cached_xft_draw != NULL) {
return cached_xft_draw;
}
if (cached_xft_draw != NULL) {
XftDrawDestroy(cached_xft_draw);
}
if (cached_visual == NULL) {
cached_visual = DefaultVisual(dwn->display, dwn->screen);
}
cached_xft_draw = XftDrawCreate(dwn->display, d, cached_visual, dwn->colormap);
cached_drawable = d;
return cached_xft_draw;
}
static XftColor *panel_get_cached_xft_color(unsigned long color)
{
if (cached_visual == NULL) {
cached_visual = DefaultVisual(dwn->display, dwn->screen);
}
/* Initialize cache on first use */
if (!xft_cache_initialized) {
memset(xft_color_cache, 0, sizeof(xft_color_cache));
xft_cache_initialized = true;
}
for (int i = 0; i < XFT_COLOR_CACHE_SIZE; i++) {
if (xft_color_cache[i].valid && xft_color_cache[i].pixel == color) {
xft_color_cache[i].lru_counter = ++xft_color_lru_counter;
return &xft_color_cache[i].xft_color;
}
}
int oldest_idx = 0;
int oldest_lru = xft_color_cache[0].lru_counter;
bool found_invalid = false;
for (int i = 0; i < XFT_COLOR_CACHE_SIZE; i++) {
if (!xft_color_cache[i].valid) {
oldest_idx = i;
found_invalid = true;
break;
}
}
if (!found_invalid) {
for (int i = 1; i < XFT_COLOR_CACHE_SIZE; i++) {
if (xft_color_cache[i].lru_counter < oldest_lru) {
oldest_lru = xft_color_cache[i].lru_counter;
oldest_idx = i;
}
}
}
if (xft_color_cache[oldest_idx].valid) {
XftColorFree(dwn->display, cached_visual, dwn->colormap,
&xft_color_cache[oldest_idx].xft_color);
}
XRenderColor render_color;
render_color.red = ((color >> 16) & 0xFF) * 257;
render_color.green = ((color >> 8) & 0xFF) * 257;
render_color.blue = (color & 0xFF) * 257;
render_color.alpha = 0xFFFF;
XftColorAllocValue(dwn->display, cached_visual, dwn->colormap,
&render_color, &xft_color_cache[oldest_idx].xft_color);
xft_color_cache[oldest_idx].pixel = color;
xft_color_cache[oldest_idx].valid = true;
xft_color_cache[oldest_idx].lru_counter = ++xft_color_lru_counter;
return &xft_color_cache[oldest_idx].xft_color;
}
static void panel_draw_text_with_font(Drawable d, int x, int y, const char *text,
int len, unsigned long color, XftFont *font)
{
if (text == NULL || dwn == NULL || dwn->display == NULL) return;
if (font != NULL) {
XftDraw *xft_draw = panel_get_xft_draw(d);
if (xft_draw != NULL) {
XftColor *xft_color = panel_get_cached_xft_color(color);
XftDrawStringUtf8(xft_draw, xft_color, font,
x, y, (const FcChar8 *)text, len);
return;
}
}
XSetForeground(dwn->display, dwn->gc, color);
XDrawString(dwn->display, d, dwn->gc, x, y, text, len);
}
static void panel_draw_text(Drawable d, int x, int y, const char *text,
int len, unsigned long color)
{
panel_draw_text_with_font(d, x, y, text, len, color, dwn->xft_font);
}
static void panel_draw_text_bold(Drawable d, int x, int y, const char *text,
int len, unsigned long color)
{
XftFont *font = dwn->xft_font_bold ? dwn->xft_font_bold : dwn->xft_font;
panel_draw_text_with_font(d, x, y, text, len, color, font);
}
static int panel_text_y(int panel_height)
{
if (dwn->xft_font != NULL) {
return (panel_height + dwn->xft_font->ascent) / 2;
}
if (dwn->font != NULL) {
return (panel_height + dwn->font->ascent - dwn->font->descent) / 2;
}
return panel_height / 2;
}
Panel *panel_create(PanelPosition position)
{
if (dwn == NULL || dwn->display == NULL) {
return NULL;
}
Panel *panel = dwn_calloc(1, sizeof(Panel));
panel->position = position;
panel->width = dwn->screen_width;
panel->height = config_get_panel_height();
panel->x = 0;
panel->visible = true;
if (position == PANEL_TOP) {
panel->y = 0;
} else {
panel->y = dwn->screen_height - panel->height;
}
XSetWindowAttributes swa;
swa.override_redirect = True;
swa.background_pixel = dwn->config->colors.panel_bg;
swa.event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask;
panel->window = XCreateWindow(dwn->display, dwn->root,
panel->x, panel->y,
panel->width, panel->height,
0,
CopyFromParent, InputOutput, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWEventMask,
&swa);
panel->buffer = XCreatePixmap(dwn->display, panel->window,
panel->width, panel->height,
DefaultDepth(dwn->display, dwn->screen));
long strut[4] = { 0, 0, 0, 0 };
if (position == PANEL_TOP) {
strut[2] = panel->height;
} else {
strut[3] = panel->height;
}
XChangeProperty(dwn->display, panel->window, ewmh.NET_WM_STRUT,
XA_CARDINAL, 32, PropModeReplace,
(unsigned char *)strut, 4);
long strut_partial[12] = { 0 };
if (position == PANEL_TOP) {
strut_partial[2] = panel->height;
strut_partial[8] = 0;
strut_partial[9] = dwn->screen_width - 1;
} else {
strut_partial[3] = panel->height;
strut_partial[10] = 0;
strut_partial[11] = dwn->screen_width - 1;
}
XChangeProperty(dwn->display, panel->window, ewmh.NET_WM_STRUT_PARTIAL,
XA_CARDINAL, 32, PropModeReplace,
(unsigned char *)strut_partial, 12);
XChangeProperty(dwn->display, panel->window, ewmh.NET_WM_WINDOW_TYPE,
XA_ATOM, 32, PropModeReplace,
(unsigned char *)&ewmh.NET_WM_WINDOW_TYPE_DOCK, 1);
LOG_DEBUG("Created %s panel at y=%d",
position == PANEL_TOP ? "top" : "bottom", panel->y);
return panel;
}
void panel_destroy(Panel *panel)
{
if (panel == NULL) {
return;
}
if (panel->buffer != None) {
XFreePixmap(dwn->display, panel->buffer);
}
if (panel->window != None) {
XDestroyWindow(dwn->display, panel->window);
}
dwn_free(panel);
}
void panels_init(void)
{
if (dwn == NULL || dwn->config == NULL) {
return;
}
if (dwn->config->top_panel_enabled) {
dwn->top_panel = panel_create(PANEL_TOP);
if (dwn->top_panel != NULL) {
XMapRaised(dwn->display, dwn->top_panel->window);
}
}
if (dwn->config->bottom_panel_enabled) {
dwn->bottom_panel = panel_create(PANEL_BOTTOM);
if (dwn->bottom_panel != NULL) {
XMapRaised(dwn->display, dwn->bottom_panel->window);
}
}
panel_update_clock();
panel_update_system_stats();
LOG_INFO("Panels initialized");
}
void panels_cleanup(void)
{
if (dwn->top_panel != NULL) {
panel_destroy(dwn->top_panel);
dwn->top_panel = NULL;
}
if (dwn->bottom_panel != NULL) {
panel_destroy(dwn->bottom_panel);
dwn->bottom_panel = NULL;
}
}
void panel_render(Panel *panel)
{
if (panel == NULL || !panel->visible) {
return;
}
Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors();
unsigned long bg = ambient_glow_bg(colors->panel_bg, dwn->ambient_phase + PHASE_OFFSET_PANEL_BG);
XSetForeground(dpy, dwn->gc, bg);
XFillRectangle(dpy, panel->buffer, dwn->gc, 0, 0, panel->width, panel->height);
int x = PANEL_PADDING;
int width;
if (panel->position == PANEL_TOP) {
panel_render_workspaces(panel, x, &width);
x += width + WIDGET_SPACING;
panel_render_layout_indicator(panel, x, &width);
int taskbar_start = x + width + WIDGET_SPACING;
int systray_actual_width = systray_get_width();
int systray_x = panel->width - systray_actual_width - PANEL_PADDING;
int fade_width = fade_controls_get_width();
int fade_x = systray_x - fade_width - WIDGET_SPACING;
int ai_x = 0;
if (dwn->ai_enabled) {
int ai_width = panel_text_width("[AI]", 4);
ai_x = fade_x - ai_width - WIDGET_SPACING;
}
panel_render_taskbar(panel, taskbar_start, &width);
if (dwn->ai_enabled) {
panel_render_ai_status(panel, ai_x, &width);
}
fade_controls_render(panel, fade_x);
systray_render(panel, systray_x, &width);
} else {
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), date_fg);
date_width = panel_text_width(date_buffer, strlen(date_buffer));
}
int top_proc_width = 0;
int top_proc_x = PANEL_PADDING + date_width + WIDGET_SPACING;
panel_render_top_process(panel, top_proc_x, &top_proc_width);
int key_counter_width = 0;
int key_counter_x = top_proc_x + top_proc_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 disk_space_width = 0;
int disk_space_x = mouse_dist_x + mouse_dist_width + WIDGET_SPACING;
panel_render_disk_space(panel, disk_space_x, &disk_space_width);
int clock_width = panel_text_width(clock_buffer, strlen(clock_buffer));
int stats_width = panel_calculate_stats_width();
int clock_x = panel->width - clock_width - PANEL_PADDING;
panel_render_clock(panel, clock_x, &width);
int stats_x = clock_x - stats_width - WIDGET_SPACING;
panel_render_system_stats(panel, stats_x, &width);
int news_start = disk_space_x + disk_space_width + WIDGET_SPACING;
int news_max_width = stats_x - news_start - WIDGET_SPACING;
if (news_max_width > 100) {
panel_set_news_region(news_start, news_max_width);
int news_width = 0;
news_render(panel, news_start, news_max_width, &news_width);
}
}
XCopyArea(dpy, panel->buffer, panel->window, dwn->gc,
0, 0, panel->width, panel->height, 0, 0);
}
void panel_render_all(void)
{
if (dwn->top_panel != NULL) {
panel_render(dwn->top_panel);
dwn->top_panel->dirty = false;
}
if (dwn->bottom_panel != NULL) {
panel_render(dwn->bottom_panel);
dwn->bottom_panel->dirty = false;
}
panels_dirty = false;
XFlush(dwn->display);
}
void panel_render_workspaces(Panel *panel, int x, int *width)
{
if (panel == NULL || width == NULL) {
return;
}
Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors();
int text_y = panel_text_y(panel->height);
*width = 0;
for (int i = 0; i < MAX_WORKSPACES; i++) {
bool active = (i == dwn->current_workspace);
bool has_clients = !workspace_is_empty(i);
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);
char num[4];
snprintf(num, sizeof(num), "%d", i + 1);
unsigned long fg = active ? colors->panel_bg :
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);
}
*width = MAX_WORKSPACES * WORKSPACE_WIDTH;
}
void panel_render_taskbar(Panel *panel, int x, int *width)
{
if (panel == NULL || width == NULL) {
return;
}
Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors();
int text_y = panel_text_y(panel->height);
int current_x = x;
int systray_actual_width = systray_get_width();
int fade_width = fade_controls_get_width();
int ai_width = 0;
if (dwn->ai_enabled) {
ai_width = panel_text_width("[AI]", 4);
}
int right_reserve = PANEL_PADDING + systray_actual_width + WIDGET_SPACING
+ fade_width + WIDGET_SPACING
+ (ai_width > 0 ? ai_width + WIDGET_SPACING : 0);
int available_width = panel->width - x - right_reserve;
if (available_width < 0) available_width = 0;
int item_count = 0;
Workspace *ws = workspace_get_current();
if (ws == NULL) {
*width = 0;
return;
}
for (Client *c = ws->mru_head; c != NULL; c = c->mru_next) {
item_count++;
}
if (item_count == 0) {
*width = 0;
return;
}
int item_width = available_width / item_count;
if (item_width > TASKBAR_ITEM_WIDTH) {
item_width = TASKBAR_ITEM_WIDTH;
}
for (Client *c = ws->mru_head; c != NULL; c = c->mru_next) {
if (c->workspace != (unsigned int)dwn->current_workspace) {
continue;
}
bool focused = (ws->focused == c);
bool minimized = client_is_minimized(c);
unsigned long bg;
if (minimized) {
bg = colors->workspace_inactive;
} else {
bg = colors->panel_bg;
}
XSetForeground(dpy, dwn->gc, bg);
XFillRectangle(dpy, panel->buffer, dwn->gc,
current_x, 0, item_width, panel->height);
char title[260];
if (minimized) {
snprintf(title, sizeof(title), "[%s]", c->title);
} else {
snprintf(title, sizeof(title), "%s", c->title);
}
int max_text_width = item_width - 8;
bool truncated = false;
size_t title_len = strlen(title);
while (panel_text_width(title, (int)title_len) > max_text_width && title_len > 3) {
size_t cut = title_len - 1;
while (cut > 0 && (title[cut] & 0xC0) == 0x80) {
cut--;
}
if (cut > 0) cut--;
while (cut > 0 && (title[cut] & 0xC0) == 0x80) {
cut--;
}
if (cut > sizeof(title) - 4) {
cut = sizeof(title) - 4;
}
title[cut] = '\0';
title_len = cut;
truncated = true;
}
if (truncated) {
strncat(title, "...", sizeof(title) - title_len - 1);
title_len += 3;
}
unsigned long fg;
if (minimized) {
fg = ambient_glow_bg(colors->panel_fg, dwn->ambient_phase + PHASE_OFFSET_TASKBAR);
} else {
fg = client_get_glow_text_color(c);
}
if (focused && !minimized) {
panel_draw_text_bold(panel->buffer, current_x + 4, text_y, title, strlen(title), fg);
} else {
panel_draw_text(panel->buffer, current_x + 4, text_y, title, strlen(title), fg);
}
current_x += item_width;
}
*width = current_x - x;
}
void panel_render_clock(Panel *panel, int x, int *width)
{
if (panel == NULL || width == NULL) {
return;
}
if (dwn->xft_font == NULL && dwn->font == NULL) {
return;
}
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), fg);
*width = panel_text_width(clock_buffer, strlen(clock_buffer));
}
void panel_render_systray(Panel *panel, int x, int *width)
{
(void)panel;
(void)x;
*width = 0;
}
void panel_render_layout_indicator(Panel *panel, int x, int *width)
{
if (panel == NULL || width == NULL) {
return;
}
if (dwn->xft_font == NULL && dwn->font == NULL) {
return;
}
const ColorScheme *colors = config_get_colors();
int text_y = panel_text_y(panel->height);
Workspace *ws = workspace_get_current();
const char *symbol = layout_get_symbol(ws != NULL ? ws->layout : LAYOUT_TILING);
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));
}
void panel_render_ai_status(Panel *panel, int x, int *width)
{
if (panel == NULL || width == NULL) {
return;
}
if (dwn->xft_font == NULL && dwn->font == NULL) {
return;
}
const ColorScheme *colors = config_get_colors();
int text_y = panel_text_y(panel->height);
const char *status = dwn->ai_enabled ? "[AI]" : "";
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));
}
void panel_handle_click(Panel *panel, int x, int y, int button)
{
if (panel == NULL) {
return;
}
if (panel->position == PANEL_TOP) {
/* Check fade controls first */
int fade_hit = fade_controls_hit_test(x);
LOG_DEBUG("Panel click: x=%d, fade_hit=%d", x, fade_hit);
if (fade_hit > 0) {
LOG_DEBUG("Fade control clicked: control_id=%d", fade_hit);
fade_controls_handle_click(fade_hit, x, y, button);
return;
}
int systray_actual_width = systray_get_width();
int systray_start = panel->width - systray_actual_width - PANEL_PADDING;
if (x >= systray_start) {
systray_handle_click(x, y, button);
return;
}
int ws = panel_hit_test_workspace(panel, x, y);
if (ws >= 0 && ws < MAX_WORKSPACES) {
if (button == 1) {
workspace_switch(ws);
}
return;
}
Client *c = panel_hit_test_taskbar(panel, x, y);
if (c != NULL) {
if (button == 1) {
if (client_is_minimized(c)) {
client_restore(c);
} else {
Workspace *current = workspace_get(dwn->current_workspace);
if (current != NULL && current->focused == c) {
client_minimize(c);
} else {
client_focus(c, true);
}
}
} else if (button == 3) {
client_close(c);
}
return;
}
} else if (panel->position == PANEL_BOTTOM) {
if (button == 1) {
news_handle_click(x, y);
}
}
}
int panel_hit_test_workspace(Panel *panel, int x, int y)
{
(void)y;
if (panel == NULL || panel->position != PANEL_TOP) {
return -1;
}
if (x < PANEL_PADDING || x >= PANEL_PADDING + MAX_WORKSPACES * WORKSPACE_WIDTH) {
return -1;
}
return (x - PANEL_PADDING) / WORKSPACE_WIDTH;
}
Client *panel_hit_test_taskbar(Panel *panel, int x, int y)
{
(void)y;
if (panel == NULL || panel->position != PANEL_TOP) {
return NULL;
}
Workspace *ws = workspace_get_current();
if (ws == NULL) {
return NULL;
}
const char *layout_symbol = layout_get_symbol(ws->layout);
int layout_width = panel_text_width(layout_symbol, strlen(layout_symbol));
int taskbar_start = PANEL_PADDING + MAX_WORKSPACES * WORKSPACE_WIDTH + WIDGET_SPACING + layout_width + WIDGET_SPACING;
if (x < taskbar_start) {
return NULL;
}
int systray_actual_width = systray_get_width();
int fade_width = fade_controls_get_width();
int ai_width = 0;
if (dwn->ai_enabled) {
ai_width = panel_text_width("[AI]", 4);
}
int right_reserve = PANEL_PADDING + systray_actual_width + WIDGET_SPACING
+ fade_width + WIDGET_SPACING
+ (ai_width > 0 ? ai_width + WIDGET_SPACING : 0);
int available_width = panel->width - taskbar_start - right_reserve;
if (available_width < 0) available_width = 0;
int item_count = 0;
for (Client *c = ws->mru_head; c != NULL; c = c->mru_next) {
item_count++;
}
if (item_count == 0) {
return NULL;
}
int item_width = available_width / item_count;
if (item_width > TASKBAR_ITEM_WIDTH) {
item_width = TASKBAR_ITEM_WIDTH;
}
int index = (x - taskbar_start) / item_width;
int i = 0;
for (Client *c = ws->mru_head; c != NULL; c = c->mru_next) {
if (i == index) {
return c;
}
i++;
}
return NULL;
}
void panel_show(Panel *panel)
{
if (panel == NULL) {
return;
}
panel->visible = true;
XMapRaised(dwn->display, panel->window);
panel_render(panel);
}
void panel_hide(Panel *panel)
{
if (panel == NULL) {
return;
}
panel->visible = false;
XUnmapWindow(dwn->display, panel->window);
}
void panel_toggle(Panel *panel)
{
if (panel == NULL) {
return;
}
if (panel->visible) {
panel_hide(panel);
} else {
panel_show(panel);
}
}
void panel_raise_all(void)
{
if (dwn == NULL || dwn->display == NULL) {
return;
}
if (dwn->top_panel != NULL && dwn->top_panel->visible) {
XRaiseWindow(dwn->display, dwn->top_panel->window);
}
if (dwn->bottom_panel != NULL && dwn->bottom_panel->visible) {
XRaiseWindow(dwn->display, dwn->bottom_panel->window);
}
}
void panel_update_clock(void)
{
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
strftime(clock_buffer, sizeof(clock_buffer), CLOCK_FORMAT, tm_info);
strftime(date_buffer, sizeof(date_buffer), DATE_FORMAT, tm_info);
}
void panel_update_system_stats(void)
{
FILE *fp;
char line[256];
fp = fopen("/proc/stat", "r");
if (fp != NULL) {
if (fgets(line, sizeof(line), fp) != NULL) {
unsigned long long user, nice, system, idle, iowait, irq, softirq;
if (sscanf(line, "cpu %llu %llu %llu %llu %llu %llu %llu",
&user, &nice, &system, &idle, &iowait, &irq, &softirq) >= 4) {
unsigned long long total = user + nice + system + idle + iowait + irq + softirq;
unsigned long long idle_time = idle + iowait;
if (sys_stats.prev_total > 0) {
unsigned long long total_diff = total - sys_stats.prev_total;
unsigned long long idle_diff = idle_time - sys_stats.prev_idle;
if (total_diff > 0) {
sys_stats.cpu_percent = (int)(100 * (total_diff - idle_diff) / total_diff);
}
}
sys_stats.prev_total = total;
sys_stats.prev_idle = idle_time;
}
}
fclose(fp);
}
fp = fopen("/proc/meminfo", "r");
if (fp != NULL) {
unsigned long mem_total = 0, mem_free = 0, buffers = 0, cached = 0;
while (fgets(line, sizeof(line), fp) != NULL) {
if (strncmp(line, "MemTotal:", 9) == 0) {
sscanf(line + 9, " %lu", &mem_total);
} else if (strncmp(line, "MemFree:", 8) == 0) {
sscanf(line + 8, " %lu", &mem_free);
} else if (strncmp(line, "Buffers:", 8) == 0) {
sscanf(line + 8, " %lu", &buffers);
} else if (strncmp(line, "Cached:", 7) == 0) {
sscanf(line + 7, " %lu", &cached);
break;
}
}
fclose(fp);
if (mem_total > 0) {
unsigned long used = mem_total - mem_free - buffers - cached;
sys_stats.mem_total_mb = (int)(mem_total / 1024);
sys_stats.mem_used_mb = (int)(used / 1024);
sys_stats.mem_percent = (int)(100 * used / mem_total);
}
}
fp = fopen("/proc/loadavg", "r");
if (fp != NULL) {
if (fscanf(fp, "%f %f %f",
&sys_stats.load_1min,
&sys_stats.load_5min,
&sys_stats.load_15min) != 3) {
sys_stats.load_1min = 0;
sys_stats.load_5min = 0;
sys_stats.load_15min = 0;
}
fclose(fp);
}
long now = get_time_ms();
if (now - last_memory_scan >= PROCESS_SCAN_INTERVAL_MS) {
update_top_processes();
last_memory_scan = now;
}
static int prev_cpu = -1;
static int prev_mem = -1;
bool stats_changed = (prev_cpu != sys_stats.cpu_percent) ||
(prev_mem != sys_stats.mem_percent);
if (stats_changed) {
prev_cpu = sys_stats.cpu_percent;
prev_mem = sys_stats.mem_percent;
panel_invalidate();
}
}
static ProcCpuState *find_cpu_history(int pid)
{
for (int i = 0; i < proc_cpu_history_count; i++) {
if (proc_cpu_history[i].pid == pid) {
return &proc_cpu_history[i];
}
}
return NULL;
}
static ProcCpuState *add_cpu_history(int pid)
{
if (proc_cpu_history_count < PROC_CPU_HISTORY_SIZE) {
ProcCpuState *state = &proc_cpu_history[proc_cpu_history_count++];
state->pid = pid;
state->prev_utime = 0;
state->prev_stime = 0;
state->prev_total_cpu = 0;
return state;
}
char path[64];
for (int i = 0; i < PROC_CPU_HISTORY_SIZE; i++) {
snprintf(path, sizeof(path), "/proc/%d", proc_cpu_history[i].pid);
if (access(path, F_OK) != 0) {
proc_cpu_history[i].pid = pid;
proc_cpu_history[i].prev_utime = 0;
proc_cpu_history[i].prev_stime = 0;
proc_cpu_history[i].prev_total_cpu = 0;
return &proc_cpu_history[i];
}
}
proc_cpu_history[0].pid = pid;
proc_cpu_history[0].prev_utime = 0;
proc_cpu_history[0].prev_stime = 0;
proc_cpu_history[0].prev_total_cpu = 0;
return &proc_cpu_history[0];
}
static unsigned long long read_total_cpu_time(void)
{
FILE *fp = fopen("/proc/stat", "r");
if (fp == NULL) return 0;
char line[256];
unsigned long long total = 0;
if (fgets(line, sizeof(line), fp) != NULL) {
unsigned long long user, nice, system, idle, iowait, irq, softirq, steal;
if (sscanf(line, "cpu %llu %llu %llu %llu %llu %llu %llu %llu",
&user, &nice, &system, &idle, &iowait, &irq, &softirq, &steal) >= 4) {
total = user + nice + system + idle + iowait + irq + softirq + steal;
}
}
fclose(fp);
return total;
}
static void update_top_processes(void)
{
DIR *proc_dir = opendir("/proc");
if (proc_dir == NULL) {
sys_stats.top_mem_count = 0;
sys_stats.top_cpu_count = 0;
return;
}
unsigned long long current_total_cpu = read_total_cpu_time();
unsigned long long cpu_delta = current_total_cpu - prev_total_cpu_time;
unsigned long top_rss[3] = {0, 0, 0};
char top_mem_names[3][64] = {"", "", ""};
unsigned long mem_total_kb = (unsigned long)sys_stats.mem_total_mb * 1024;
int top_cpu_pcts[3] = {0, 0, 0};
char top_cpu_names[3][64] = {"", "", ""};
struct dirent *entry;
while ((entry = readdir(proc_dir)) != NULL) {
bool is_pid = true;
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;
int pid = atoi(entry->d_name);
char status_path[64];
snprintf(status_path, sizeof(status_path), "/proc/%d/status", pid);
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 > 0 && name[0] != '\0') {
for (int i = 0; i < 3; i++) {
if (rss_kb > top_rss[i]) {
for (int j = 2; j > i; j--) {
top_rss[j] = top_rss[j - 1];
memcpy(top_mem_names[j], top_mem_names[j - 1], 64);
}
top_rss[i] = rss_kb;
memcpy(top_mem_names[i], name, 64);
break;
}
}
}
char stat_path[64];
snprintf(stat_path, sizeof(stat_path), "/proc/%d/stat", pid);
fp = fopen(stat_path, "r");
if (fp == NULL) continue;
unsigned long long utime = 0, stime = 0;
char stat_line[512];
if (fgets(stat_line, sizeof(stat_line), fp) != NULL) {
char *close_paren = strrchr(stat_line, ')');
if (close_paren != NULL) {
if (sscanf(close_paren + 2, "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %llu %llu",
&utime, &stime) == 2) {
ProcCpuState *hist = find_cpu_history(pid);
if (hist == NULL) {
hist = add_cpu_history(pid);
}
if (hist->prev_total_cpu > 0 && cpu_delta > 0) {
unsigned long long proc_delta = (utime + stime) - (hist->prev_utime + hist->prev_stime);
int num_cpus = (int)sysconf(_SC_NPROCESSORS_ONLN);
if (num_cpus < 1) num_cpus = 1;
int cpu_pct = (int)((proc_delta * 100 * (unsigned long long)num_cpus) / cpu_delta);
if (cpu_pct > 100) cpu_pct = 100;
for (int i = 0; i < 3; i++) {
if (cpu_pct > top_cpu_pcts[i] || top_cpu_names[i][0] == '\0') {
if (cpu_pct > top_cpu_pcts[i]) {
for (int j = 2; j > i; j--) {
top_cpu_pcts[j] = top_cpu_pcts[j - 1];
memcpy(top_cpu_names[j], top_cpu_names[j - 1], 64);
}
}
top_cpu_pcts[i] = cpu_pct;
memcpy(top_cpu_names[i], name, 64);
break;
}
}
}
hist->prev_utime = utime;
hist->prev_stime = stime;
hist->prev_total_cpu = current_total_cpu;
}
}
}
fclose(fp);
}
closedir(proc_dir);
prev_total_cpu_time = current_total_cpu;
sys_stats.top_mem_count = 0;
for (int i = 0; i < 3; i++) {
if (top_mem_names[i][0] != '\0') {
memcpy(sys_stats.top_mem_processes[i], top_mem_names[i], 64);
if (mem_total_kb > 0) {
sys_stats.top_mem_percents[i] = (int)((top_rss[i] * 100) / mem_total_kb);
} else {
sys_stats.top_mem_percents[i] = 0;
}
sys_stats.top_mem_count++;
}
}
sys_stats.top_cpu_count = 0;
for (int i = 0; i < 3; i++) {
if (top_cpu_names[i][0] != '\0') {
memcpy(sys_stats.top_cpu_processes[i], top_cpu_names[i], 64);
sys_stats.top_cpu_percents[i] = top_cpu_pcts[i];
sys_stats.top_cpu_count++;
}
}
}
static int panel_calculate_stats_width(void)
{
if (dwn->xft_font == NULL && dwn->font == NULL) return 0;
char buf[256];
int total = 0;
int len = snprintf(buf, sizeof(buf), "CPU:%2d%%", sys_stats.cpu_percent);
total += panel_text_width(buf, len) + WIDGET_SPACING;
if (sys_stats.mem_total_mb >= 1024) {
len = snprintf(buf, sizeof(buf), "MEM:%.1fG/%dG",
sys_stats.mem_used_mb / 1024.0f, sys_stats.mem_total_mb / 1024);
} else {
len = snprintf(buf, sizeof(buf), "MEM:%dM/%dM",
sys_stats.mem_used_mb, sys_stats.mem_total_mb);
}
total += panel_text_width(buf, len) + WIDGET_SPACING;
len = snprintf(buf, sizeof(buf), "Load:%.2f %.2f %.2f",
sys_stats.load_1min, sys_stats.load_5min, sys_stats.load_15min);
total += panel_text_width(buf, len) + WIDGET_SPACING;
MultiBatteryState multi_bat = systray_get_multi_battery_snapshot();
if (multi_bat.count > 0) {
const char *bat_icon = multi_bat.any_charging ? "[+]" : "[=]";
if (multi_bat.count == 1) {
len = snprintf(buf, sizeof(buf), "%s%d%%", bat_icon, multi_bat.combined_percentage);
} else {
int offset = snprintf(buf, sizeof(buf), "%s%d%% (", bat_icon, multi_bat.combined_percentage);
for (int i = 0; i < multi_bat.count && offset < (int)sizeof(buf) - 10; i++) {
if (i > 0) {
offset += snprintf(buf + offset, sizeof(buf) - offset, " ");
}
offset += snprintf(buf + offset, sizeof(buf) - offset, "B%d:%d%%",
i + 1, multi_bat.batteries[i].percentage);
}
snprintf(buf + offset, sizeof(buf) - offset, ")");
len = strlen(buf);
}
total += panel_text_width(buf, len) + WIDGET_SPACING;
}
return total;
}
static void panel_render_system_stats(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);
int current_x = x;
char stats_buf[256];
int len;
unsigned long cpu_color = colors->panel_fg;
if (sys_stats.cpu_percent >= 90) {
cpu_color = colors->workspace_urgent;
} 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);
current_x += panel_text_width(stats_buf, len) + WIDGET_SPACING;
unsigned long mem_color = colors->panel_fg;
if (sys_stats.mem_percent >= 90) {
mem_color = colors->workspace_urgent;
} 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",
sys_stats.mem_used_mb / 1024.0f, sys_stats.mem_total_mb / 1024);
} else {
len = snprintf(stats_buf, sizeof(stats_buf), "MEM:%dM/%dM",
sys_stats.mem_used_mb, sys_stats.mem_total_mb);
}
panel_draw_text(panel->buffer, current_x, text_y, stats_buf, len, mem_color);
current_x += panel_text_width(stats_buf, len) + WIDGET_SPACING;
unsigned long load_color = colors->panel_fg;
if (sys_stats.load_1min >= 4.0f) {
load_color = colors->workspace_urgent;
} 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);
panel_draw_text(panel->buffer, current_x, text_y, stats_buf, len, load_color);
current_x += panel_text_width(stats_buf, len) + WIDGET_SPACING;
MultiBatteryState multi_bat = systray_get_multi_battery_snapshot();
if (multi_bat.count > 0) {
unsigned long bat_color = colors->panel_fg;
if (multi_bat.combined_percentage <= 20 && !multi_bat.any_charging) {
bat_color = colors->workspace_urgent;
} else if (multi_bat.combined_percentage <= 40 && !multi_bat.any_charging) {
bat_color = 0xFFA500;
} 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) {
len = snprintf(stats_buf, sizeof(stats_buf), "%s%d%%", bat_icon, multi_bat.combined_percentage);
} else {
int offset = snprintf(stats_buf, sizeof(stats_buf), "%s%d%% (", bat_icon, multi_bat.combined_percentage);
for (int i = 0; i < multi_bat.count && offset < (int)sizeof(stats_buf) - 10; i++) {
if (i > 0) {
offset += snprintf(stats_buf + offset, sizeof(stats_buf) - offset, " ");
}
offset += snprintf(stats_buf + offset, sizeof(stats_buf) - offset, "B%d:%d%%",
i + 1, multi_bat.batteries[i].percentage);
}
snprintf(stats_buf + offset, sizeof(stats_buf) - offset, ")");
len = strlen(stats_buf);
}
panel_draw_text(panel->buffer, current_x, text_y, stats_buf, len, bat_color);
current_x += panel_text_width(stats_buf, len) + WIDGET_SPACING;
}
*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 update_disk_space(void)
{
long now = get_time_ms();
if (now - disk_last_update < DISK_UPDATE_INTERVAL_MS && disk_last_update > 0) {
return;
}
struct statvfs stat;
if (statvfs("/", &stat) == 0) {
unsigned long long block_size = stat.f_frsize;
disk_free_gb = (double)(stat.f_bavail * block_size) / (1024.0 * 1024.0 * 1024.0);
disk_total_gb = (double)(stat.f_blocks * block_size) / (1024.0 * 1024.0 * 1024.0);
}
disk_last_update = now;
}
static void panel_render_disk_space(Panel *panel, int x, int *width)
{
if (panel == NULL || width == NULL) {
*width = 0;
return;
}
if (dwn->xft_font == NULL && dwn->font == NULL) {
*width = 0;
return;
}
update_disk_space();
const ColorScheme *colors = config_get_colors();
int text_y = panel_text_y(panel->height);
char buf[32];
int len;
if (disk_total_gb >= 1000.0) {
len = snprintf(buf, sizeof(buf), "Disk:%.1fT/%.1fT",
disk_free_gb / 1024.0, disk_total_gb / 1024.0);
} else {
len = snprintf(buf, sizeof(buf), "Disk:%.0fG/%.0fG",
disk_free_gb, disk_total_gb);
}
int used_percent = 0;
if (disk_total_gb > 0.0) {
used_percent = (int)(((disk_total_gb - disk_free_gb) / disk_total_gb) * 100.0);
}
unsigned long color = colors->panel_fg;
if (used_percent >= 90) {
color = colors->workspace_urgent;
} else if (used_percent >= 75) {
color = 0xFFA500;
}
unsigned long fg = ambient_glow_bg(color, dwn->ambient_phase + PHASE_OFFSET_DISK_SPACE);
panel_draw_text(panel->buffer, x, text_y, buf, len, fg);
*width = panel_text_width(buf, len);
}
static void panel_render_top_process(Panel *panel, int x, int *width)
{
if (panel == NULL || width == NULL) {
*width = 0;
return;
}
if (dwn->xft_font == NULL && dwn->font == NULL) {
*width = 0;
return;
}
int mem_count = sys_stats.top_mem_count;
int cpu_count = sys_stats.top_cpu_count;
if (mem_count == 0 && cpu_count == 0) {
*width = 0;
return;
}
long now = get_time_ms();
if (now - sys_stats.process_toggle_time >= 3000) {
sys_stats.process_show_cpu = !sys_stats.process_show_cpu;
sys_stats.process_toggle_time = now;
}
const ColorScheme *colors = config_get_colors();
int text_y = panel_text_y(panel->height);
char buf[128];
char name_buf[TOP_PROCESS_NAME_MAX_LEN + 1];
int len;
unsigned long color;
const char *proc_name;
int prefix_width = panel_text_width("M:", 2);
int suffix_width = panel_text_width(" 100%", 5);
int available_for_name = TOP_PROCESS_WIDGET_WIDTH - prefix_width - suffix_width;
bool show_cpu = sys_stats.process_show_cpu;
if (show_cpu && cpu_count == 0) show_cpu = false;
if (!show_cpu && mem_count == 0) show_cpu = true;
if (show_cpu && cpu_count > 0) {
int percent = sys_stats.top_cpu_percents[0];
color = colors->panel_fg;
if (percent >= 50) {
color = 0x4169E1;
} else if (percent >= 25) {
color = 0x00CED1;
}
color = ambient_glow_bg(color, dwn->ambient_phase + PHASE_OFFSET_TOP_CPU);
proc_name = sys_stats.top_cpu_processes[0];
size_t name_len = strlen(proc_name);
if (name_len > TOP_PROCESS_NAME_MAX_LEN) name_len = TOP_PROCESS_NAME_MAX_LEN;
memcpy(name_buf, proc_name, name_len);
name_buf[name_len] = '\0';
while (strlen(name_buf) > 2 && panel_text_width(name_buf, (int)strlen(name_buf)) > available_for_name) {
name_buf[strlen(name_buf) - 1] = '\0';
}
len = snprintf(buf, sizeof(buf), "C:%s %d%%", name_buf, percent);
} else if (mem_count > 0) {
int percent = sys_stats.top_mem_percents[0];
color = colors->panel_fg;
if (percent >= 50) {
color = colors->workspace_urgent;
} else if (percent >= 25) {
color = 0xFFA500;
}
color = ambient_glow_bg(color, dwn->ambient_phase + PHASE_OFFSET_TOP_MEM);
proc_name = sys_stats.top_mem_processes[0];
size_t name_len = strlen(proc_name);
if (name_len > TOP_PROCESS_NAME_MAX_LEN) name_len = TOP_PROCESS_NAME_MAX_LEN;
memcpy(name_buf, proc_name, name_len);
name_buf[name_len] = '\0';
while (strlen(name_buf) > 2 && panel_text_width(name_buf, (int)strlen(name_buf)) > available_for_name) {
name_buf[strlen(name_buf) - 1] = '\0';
}
len = snprintf(buf, sizeof(buf), "M:%s %d%%", name_buf, percent);
} else {
*width = 0;
return;
}
panel_draw_text(panel->buffer, x, text_y, buf, len, color);
*width = TOP_PROCESS_WIDGET_WIDTH;
}
void panel_invalidate(void)
{
panels_dirty = true;
if (dwn->top_panel) dwn->top_panel->dirty = true;
if (dwn->bottom_panel) dwn->bottom_panel->dirty = true;
}
void panel_invalidate_taskbar(void)
{
panels_dirty = true;
if (dwn->top_panel) dwn->top_panel->dirty = true;
}
void panel_invalidate_news(void)
{
news_region_dirty = true;
}
void panel_set_news_region(int x, int width)
{
news_region_x = x;
news_region_width = width;
}
void panel_render_news_only(void)
{
if (dwn->bottom_panel == NULL || !news_region_dirty) {
return;
}
if (news_region_width <= 0) {
news_region_dirty = false;
return;
}
Panel *panel = dwn->bottom_panel;
Display *dpy = dwn->display;
const ColorScheme *colors = config_get_colors();
unsigned long bg = ambient_glow_bg(colors->panel_bg,
dwn->ambient_phase + PHASE_OFFSET_PANEL_BG);
XSetForeground(dpy, dwn->gc, bg);
XFillRectangle(dpy, panel->buffer, dwn->gc,
news_region_x, 0, (unsigned int)news_region_width, (unsigned int)panel->height);
int width;
news_render(panel, news_region_x, news_region_width, &width);
XCopyArea(dpy, panel->buffer, panel->window, dwn->gc,
news_region_x, 0, (unsigned int)news_region_width, (unsigned int)panel->height,
news_region_x, 0);
news_region_dirty = false;
}
bool panel_needs_render(void)
{
return panels_dirty;
}
bool panel_news_needs_render(void)
{
return news_region_dirty;
}
void panel_init_systray(void)
{
LOG_DEBUG("System tray initialization (placeholder)");
}
void panel_add_systray_icon(Window icon)
{
(void)icon;
LOG_DEBUG("Add systray icon (placeholder)");
}
void panel_remove_systray_icon(Window icon)
{
(void)icon;
LOG_DEBUG("Remove systray icon (placeholder)");
}