/* * DWN - Desktop Window Manager * retoor * 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 #include #include #include #include #include #define PANEL_PADDING 12 #define WIDGET_SPACING 16 #define WORKSPACE_WIDTH 36 #define TASKBAR_ITEM_WIDTH 180 #define CLOCK_FORMAT "%H:%M:%S" #define DATE_FORMAT "%Y-%m-%d" static char clock_buffer[32] = ""; static char date_buffer[32] = ""; 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; } 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 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 void panel_draw_text(Drawable d, int x, int y, const char *text, int len, unsigned long color) { if (text == NULL || dwn == NULL || dwn->display == NULL) return; if (dwn->xft_font != NULL) { XftDraw *xft_draw = XftDrawCreate(dwn->display, d, DefaultVisual(dwn->display, dwn->screen), dwn->colormap); if (xft_draw != NULL) { XftColor xft_color; XRenderColor render_color; render_color.red = ((color >> 16) & 0xFF) * 257; render_color.green = ((color >> 8) & 0xFF) * 257; render_color.blue = (color & 0xFF) * 257; render_color.alpha = 0xFFFF; XftColorAllocValue(dwn->display, DefaultVisual(dwn->display, dwn->screen), dwn->colormap, &render_color, &xft_color); XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font, x, y, (const FcChar8 *)text, len); XftColorFree(dwn->display, DefaultVisual(dwn->display, dwn->screen), dwn->colormap, &xft_color); XftDrawDestroy(xft_draw); return; } } XSetForeground(dwn->display, dwn->gc, color); XDrawString(dwn->display, d, dwn->gc, x, y, text, len); } 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(); XSetForeground(dpy, dwn->gc, colors->panel_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); x += width + WIDGET_SPACING; panel_render_taskbar(panel, x, &width); int systray_actual_width = systray_get_width(); int systray_x = panel->width - systray_actual_width - PANEL_PADDING; systray_render(panel, systray_x, &width); if (dwn->ai_enabled) { int ai_x = systray_x - 60; panel_render_ai_status(panel, ai_x, &width); } } else { int date_width = 0; if (dwn->xft_font != NULL || dwn->font != NULL) { int text_y = panel_text_y(panel->height); panel_draw_text(panel->buffer, x, text_y, date_buffer, strlen(date_buffer), colors->panel_fg); date_width = panel_text_width(date_buffer, strlen(date_buffer)); } 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 = PANEL_PADDING + date_width + WIDGET_SPACING * 2; int news_max_width = stats_x - news_start - WIDGET_SPACING; if (news_max_width > 100) { 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); XFlush(dpy); } void panel_render_all(void) { if (dwn->top_panel != NULL) { panel_render(dwn->top_panel); } if (dwn->bottom_panel != NULL) { panel_render(dwn->bottom_panel); } } 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 = active ? colors->workspace_active : colors->panel_bg; XSetForeground(dpy, dwn->gc, bg); 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 : (has_clients ? colors->panel_fg : colors->workspace_inactive); 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 ai_width = dwn->ai_enabled ? 60 : 0; int right_reserve = systray_actual_width + ai_width + PANEL_PADDING * 2; int available_width = panel->width - x - right_reserve; if (available_width < 0) available_width = 0; int item_count = 0; for (Client *c = dwn->client_list; c != NULL; c = c->next) { if (c->workspace == (unsigned int)dwn->current_workspace) { 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; } Workspace *ws = workspace_get_current(); for (Client *c = dwn->client_list; c != NULL; c = c->next) { if (c->workspace != (unsigned int)dwn->current_workspace) { continue; } bool focused = (ws != NULL && ws->focused == c); bool minimized = client_is_minimized(c); unsigned long bg; if (minimized) { bg = colors->workspace_inactive; } else if (focused) { bg = colors->workspace_active; } 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; while (panel_text_width(title, strlen(title)) > max_text_width && strlen(title) > 3) { size_t len = strlen(title); size_t cut = 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'; truncated = true; } if (truncated) { strncat(title, "...", sizeof(title) - strlen(title) - 1); } unsigned long fg; if (minimized) { fg = colors->panel_fg; } else if (focused) { fg = colors->panel_bg; } else { fg = colors->panel_fg; } 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); panel_draw_text(panel->buffer, x, text_y, clock_buffer, strlen(clock_buffer), colors->panel_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); panel_draw_text(panel->buffer, x, text_y, symbol, strlen(symbol), colors->workspace_active); *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]" : ""; panel_draw_text(panel->buffer, x, text_y, status, strlen(status), colors->workspace_active); *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) { 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 { client_focus(c); } } 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(); const char *layout_symbol = layout_get_symbol(ws != NULL ? ws->layout : LAYOUT_TILING); 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 ai_width = dwn->ai_enabled ? 60 : 0; int right_reserve = systray_actual_width + ai_width + PANEL_PADDING * 2; int available_width = panel->width - taskbar_start - right_reserve; if (available_width < 0) available_width = 0; int item_count = 0; for (Client *c = dwn->client_list; c != NULL; c = c->next) { if (c->workspace == (unsigned int)dwn->current_workspace) { 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 = dwn->client_list; c != NULL; c = c->next) { if (c->workspace == (unsigned int)dwn->current_workspace) { 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); } } 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; BatteryState bat_snap = systray_get_battery_snapshot(); if (bat_snap.present) { const char *bat_icon = bat_snap.charging ? "[+]" : "[=]"; len = snprintf(buf, sizeof(buf), "%s%d%%", bat_icon, bat_snap.percentage); 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; } 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; } 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; } 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; BatteryState bat_snap = systray_get_battery_snapshot(); if (bat_snap.present) { unsigned long bat_color = colors->panel_fg; if (bat_snap.percentage <= 20 && !bat_snap.charging) { bat_color = colors->workspace_urgent; } else if (bat_snap.percentage <= 40 && !bat_snap.charging) { bat_color = 0xFFA500; } else if (bat_snap.charging) { bat_color = colors->workspace_active; } const char *bat_icon = bat_snap.charging ? "[+]" : "[=]"; len = snprintf(stats_buf, sizeof(stats_buf), "%s%d%%", bat_icon, bat_snap.percentage); 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; } 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)"); }