/* * DWN - Desktop Window Manager * Window decorations implementation */ #include "decorations.h" #include "client.h" #include "config.h" #include "util.h" #include #include #include /* Button dimensions */ #define BUTTON_SIZE 16 #define BUTTON_PADDING 4 /* Resize edge size */ #define RESIZE_EDGE 8 /* ========== Initialization ========== */ void decorations_init(void) { LOG_DEBUG("Decorations system initialized"); } void decorations_cleanup(void) { /* Nothing to clean up */ } /* ========== Rendering ========== */ void decorations_render(Client *client, bool focused) { if (client == NULL || client->frame == None) { return; } if (dwn == NULL || dwn->display == NULL || dwn->config == NULL) { return; } decorations_render_title_bar(client, focused); decorations_render_buttons(client, focused); decorations_render_border(client, focused); } void decorations_render_title_bar(Client *client, bool focused) { if (client == NULL || client->frame == None) { return; } if (dwn == NULL || dwn->display == NULL || dwn->gc == None || dwn->config == NULL) { return; } Display *dpy = dwn->display; const ColorScheme *colors = config_get_colors(); int title_height = config_get_title_height(); int border = client->border_width; /* Set colors based on focus */ unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg; unsigned long fg_color = focused ? colors->title_focused_fg : colors->title_unfocused_fg; /* Draw title bar background */ XSetForeground(dpy, dwn->gc, bg_color); XFillRectangle(dpy, client->frame, dwn->gc, border, border, client->width, title_height); /* Draw title text */ if (client->title[0] != '\0' && dwn->xft_font != NULL) { int text_y = border + (title_height + dwn->xft_font->ascent) / 2; int text_x = border + BUTTON_PADDING; /* Truncate title if too long */ int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING; char display_title[256]; strncpy(display_title, client->title, sizeof(display_title) - 4); /* Leave room for "..." */ display_title[sizeof(display_title) - 4] = '\0'; XGlyphInfo extents; XftTextExtentsUtf8(dpy, dwn->xft_font, (const FcChar8 *)display_title, strlen(display_title), &extents); int text_width = extents.xOff; /* Truncate UTF-8 aware: find valid UTF-8 boundary */ bool title_truncated = false; while (text_width > max_width && strlen(display_title) > 3) { size_t len = strlen(display_title); /* Move back to find UTF-8 character boundary */ size_t cut = len - 1; while (cut > 0 && (display_title[cut] & 0xC0) == 0x80) { cut--; /* Skip continuation bytes */ } if (cut > 0) cut--; /* Remove one more character for ellipsis space */ while (cut > 0 && (display_title[cut] & 0xC0) == 0x80) { cut--; } display_title[cut] = '\0'; title_truncated = true; XftTextExtentsUtf8(dpy, dwn->xft_font, (const FcChar8 *)display_title, strlen(display_title), &extents); text_width = extents.xOff; } if (title_truncated) { strncat(display_title, "...", sizeof(display_title) - strlen(display_title) - 1); } /* Draw with Xft for UTF-8 support */ XftDraw *xft_draw = XftDrawCreate(dpy, client->frame, DefaultVisual(dpy, dwn->screen), dwn->colormap); if (xft_draw != NULL) { XftColor xft_color; XRenderColor render_color; render_color.red = ((fg_color >> 16) & 0xFF) * 257; render_color.green = ((fg_color >> 8) & 0xFF) * 257; render_color.blue = (fg_color & 0xFF) * 257; render_color.alpha = 0xFFFF; XftColorAllocValue(dpy, DefaultVisual(dpy, dwn->screen), dwn->colormap, &render_color, &xft_color); XftDrawStringUtf8(xft_draw, &xft_color, dwn->xft_font, text_x, text_y, (const FcChar8 *)display_title, strlen(display_title)); XftColorFree(dpy, DefaultVisual(dpy, dwn->screen), dwn->colormap, &xft_color); XftDrawDestroy(xft_draw); } } } void decorations_render_buttons(Client *client, bool focused) { if (client == NULL || client->frame == None) { return; } if (dwn == NULL || dwn->display == NULL || dwn->gc == None || dwn->config == NULL) { return; } Display *dpy = dwn->display; const ColorScheme *colors = config_get_colors(); int title_height = config_get_title_height(); int border = client->border_width; /* Button positions (right-aligned) */ int button_y = border + (title_height - BUTTON_SIZE) / 2; int close_x = border + client->width - BUTTON_SIZE - BUTTON_PADDING; int max_x = close_x - BUTTON_SIZE - BUTTON_PADDING; int min_x = max_x - BUTTON_SIZE - BUTTON_PADDING; unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg; /* Close button (red X) */ XSetForeground(dpy, dwn->gc, bg_color); XFillRectangle(dpy, client->frame, dwn->gc, close_x, button_y, BUTTON_SIZE, BUTTON_SIZE); XSetForeground(dpy, dwn->gc, 0xcc4444); /* Red */ XDrawLine(dpy, client->frame, dwn->gc, close_x + 3, button_y + 3, close_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 4); XDrawLine(dpy, client->frame, dwn->gc, close_x + BUTTON_SIZE - 4, button_y + 3, close_x + 3, button_y + BUTTON_SIZE - 4); /* Maximize button (square) */ XSetForeground(dpy, dwn->gc, bg_color); XFillRectangle(dpy, client->frame, dwn->gc, max_x, button_y, BUTTON_SIZE, BUTTON_SIZE); XSetForeground(dpy, dwn->gc, 0x44cc44); /* Green */ XDrawRectangle(dpy, client->frame, dwn->gc, max_x + 3, button_y + 3, BUTTON_SIZE - 7, BUTTON_SIZE - 7); /* Minimize button (line) */ XSetForeground(dpy, dwn->gc, bg_color); XFillRectangle(dpy, client->frame, dwn->gc, min_x, button_y, BUTTON_SIZE, BUTTON_SIZE); XSetForeground(dpy, dwn->gc, 0xcccc44); /* Yellow */ XDrawLine(dpy, client->frame, dwn->gc, min_x + 3, button_y + BUTTON_SIZE - 5, min_x + BUTTON_SIZE - 4, button_y + BUTTON_SIZE - 5); } void decorations_render_border(Client *client, bool focused) { if (client == NULL || client->frame == None) { return; } if (dwn == NULL || dwn->display == NULL || dwn->config == NULL) { return; } const ColorScheme *colors = config_get_colors(); unsigned long border_color = focused ? colors->border_focused : colors->border_unfocused; XSetWindowBorder(dwn->display, client->frame, border_color); } /* ========== Hit testing ========== */ ButtonType decorations_hit_test_button(Client *client, int x, int y) { if (client == NULL) { return BUTTON_COUNT; /* No button hit */ } int title_height = config_get_title_height(); int border = client->border_width; /* Check if in title bar area */ if (y < border || y > border + title_height) { return BUTTON_COUNT; } int button_y = border + (title_height - BUTTON_SIZE) / 2; int close_x = border + client->width - BUTTON_SIZE - BUTTON_PADDING; int max_x = close_x - BUTTON_SIZE - BUTTON_PADDING; int min_x = max_x - BUTTON_SIZE - BUTTON_PADDING; /* Check close button */ if (x >= close_x && x < close_x + BUTTON_SIZE && y >= button_y && y < button_y + BUTTON_SIZE) { return BUTTON_CLOSE; } /* Check maximize button */ if (x >= max_x && x < max_x + BUTTON_SIZE && y >= button_y && y < button_y + BUTTON_SIZE) { return BUTTON_MAXIMIZE; } /* Check minimize button */ if (x >= min_x && x < min_x + BUTTON_SIZE && y >= button_y && y < button_y + BUTTON_SIZE) { return BUTTON_MINIMIZE; } return BUTTON_COUNT; } bool decorations_hit_test_title_bar(Client *client, int x, int y) { if (client == NULL) { return false; } int title_height = config_get_title_height(); int border = client->border_width; /* In title bar but not on a button */ if (y >= border && y < border + title_height) { ButtonType btn = decorations_hit_test_button(client, x, y); return btn == BUTTON_COUNT; } return false; } bool decorations_hit_test_resize_area(Client *client, int x, int y, int *direction) { if (client == NULL || direction == NULL) { return false; } int title_height = config_get_title_height(); int border = client->border_width; int frame_width = client->width + 2 * border; int frame_height = client->height + title_height + 2 * border; *direction = 0; /* Check edges */ bool left = (x < RESIZE_EDGE); bool right = (x > frame_width - RESIZE_EDGE); bool top = (y < RESIZE_EDGE); bool bottom = (y > frame_height - RESIZE_EDGE); if (!left && !right && !top && !bottom) { return false; } /* Encode direction as bitmask */ if (left) *direction |= 1; if (right) *direction |= 2; if (top) *direction |= 4; if (bottom) *direction |= 8; return true; } /* ========== Button actions ========== */ void decorations_button_press(Client *client, ButtonType button) { if (client == NULL) { return; } switch (button) { case BUTTON_CLOSE: LOG_DEBUG("Close button pressed for %s", client->title); client_close(client); break; case BUTTON_MAXIMIZE: LOG_DEBUG("Maximize button pressed for %s", client->title); client_toggle_fullscreen(client); break; case BUTTON_MINIMIZE: LOG_DEBUG("Minimize button pressed for %s", client->title); client_minimize(client); break; default: break; } } /* ========== Text rendering ========== */ void decorations_draw_text(Window window, GC gc, int x, int y, const char *text, unsigned long color) { if (text == NULL || dwn == NULL || dwn->display == NULL) { return; } /* Use Xft for UTF-8 support */ if (dwn->xft_font != NULL) { XftDraw *xft_draw = XftDrawCreate(dwn->display, window, 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, strlen(text)); XftColorFree(dwn->display, DefaultVisual(dwn->display, dwn->screen), dwn->colormap, &xft_color); XftDrawDestroy(xft_draw); return; } } /* Fallback to legacy X11 text */ XSetForeground(dwn->display, gc, color); XDrawString(dwn->display, window, gc, x, y, text, strlen(text)); }