|
/*
|
|
* 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)");
|
|
}
|