2025-12-28 03:14:31 +01:00
|
|
|
/*
|
|
|
|
|
* DWN - Desktop Window Manager
|
2025-12-28 04:30:10 +01:00
|
|
|
* retoor <retoor@molodetz.nl>
|
2025-12-28 03:14:31 +01:00
|
|
|
* Window decorations implementation
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "decorations.h"
|
|
|
|
|
#include "client.h"
|
|
|
|
|
#include "config.h"
|
|
|
|
|
#include "util.h"
|
|
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <X11/Xft/Xft.h>
|
|
|
|
|
|
|
|
|
|
#define BUTTON_SIZE 16
|
|
|
|
|
#define BUTTON_PADDING 4
|
|
|
|
|
|
|
|
|
|
#define RESIZE_EDGE 8
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void decorations_init(void)
|
|
|
|
|
{
|
|
|
|
|
LOG_DEBUG("Decorations system initialized");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void decorations_cleanup(void)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
XSetForeground(dpy, dwn->gc, bg_color);
|
|
|
|
|
XFillRectangle(dpy, client->frame, dwn->gc,
|
|
|
|
|
border, border,
|
|
|
|
|
client->width, title_height);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING;
|
2025-12-28 09:26:53 +01:00
|
|
|
if (max_width < BUTTON_SIZE) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-12-28 03:14:31 +01:00
|
|
|
char display_title[256];
|
2025-12-28 05:01:46 +01:00
|
|
|
strncpy(display_title, client->title, sizeof(display_title) - 4);
|
2025-12-28 03:14:31 +01:00
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
bool title_truncated = false;
|
|
|
|
|
while (text_width > max_width && strlen(display_title) > 3) {
|
|
|
|
|
size_t len = strlen(display_title);
|
|
|
|
|
size_t cut = len - 1;
|
|
|
|
|
while (cut > 0 && (display_title[cut] & 0xC0) == 0x80) {
|
2025-12-28 05:01:46 +01:00
|
|
|
cut--;
|
2025-12-28 03:14:31 +01:00
|
|
|
}
|
2025-12-28 05:01:46 +01:00
|
|
|
if (cut > 0) cut--;
|
2025-12-28 03:14:31 +01:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 09:26:53 +01:00
|
|
|
int min_buttons_width = 3 * (BUTTON_SIZE + BUTTON_PADDING) + BUTTON_PADDING;
|
|
|
|
|
if (client->width < min_buttons_width) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 03:14:31 +01:00
|
|
|
Display *dpy = dwn->display;
|
|
|
|
|
const ColorScheme *colors = config_get_colors();
|
|
|
|
|
int title_height = config_get_title_height();
|
|
|
|
|
int border = client->border_width;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2025-12-28 09:26:53 +01:00
|
|
|
if (close_x < border || max_x < border || min_x < border) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 03:14:31 +01:00
|
|
|
unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg;
|
|
|
|
|
|
|
|
|
|
XSetForeground(dpy, dwn->gc, bg_color);
|
|
|
|
|
XFillRectangle(dpy, client->frame, dwn->gc, close_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
|
2025-12-28 05:01:46 +01:00
|
|
|
XSetForeground(dpy, dwn->gc, 0xcc4444);
|
2025-12-28 03:14:31 +01:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
XSetForeground(dpy, dwn->gc, bg_color);
|
|
|
|
|
XFillRectangle(dpy, client->frame, dwn->gc, max_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
|
2025-12-28 05:01:46 +01:00
|
|
|
XSetForeground(dpy, dwn->gc, 0x44cc44);
|
2025-12-28 03:14:31 +01:00
|
|
|
XDrawRectangle(dpy, client->frame, dwn->gc,
|
|
|
|
|
max_x + 3, button_y + 3,
|
|
|
|
|
BUTTON_SIZE - 7, BUTTON_SIZE - 7);
|
|
|
|
|
|
|
|
|
|
XSetForeground(dpy, dwn->gc, bg_color);
|
|
|
|
|
XFillRectangle(dpy, client->frame, dwn->gc, min_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
|
2025-12-28 05:01:46 +01:00
|
|
|
XSetForeground(dpy, dwn->gc, 0xcccc44);
|
2025-12-28 03:14:31 +01:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ButtonType decorations_hit_test_button(Client *client, int x, int y)
|
|
|
|
|
{
|
|
|
|
|
if (client == NULL) {
|
2025-12-28 05:01:46 +01:00
|
|
|
return BUTTON_COUNT;
|
2025-12-28 03:14:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int title_height = config_get_title_height();
|
|
|
|
|
int border = client->border_width;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
if (x >= close_x && x < close_x + BUTTON_SIZE &&
|
|
|
|
|
y >= button_y && y < button_y + BUTTON_SIZE) {
|
|
|
|
|
return BUTTON_CLOSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (x >= max_x && x < max_x + BUTTON_SIZE &&
|
|
|
|
|
y >= button_y && y < button_y + BUTTON_SIZE) {
|
|
|
|
|
return BUTTON_MAXIMIZE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (left) *direction |= 1;
|
|
|
|
|
if (right) *direction |= 2;
|
|
|
|
|
if (top) *direction |= 4;
|
|
|
|
|
if (bottom) *direction |= 8;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XSetForeground(dwn->display, gc, color);
|
|
|
|
|
XDrawString(dwn->display, window, gc, x, y, text, strlen(text));
|
|
|
|
|
}
|