Implement key_expose_all function bound to Alt+Space that unminimizes, unmaximizes, and unfullscreens all windows on current workspace then rearranges. Add floating_before_maximize boolean to Client struct to preserve floating state when toggling maximize. Enable Alt+LeftDrag for moving and Alt+RightDrag for resizing windows from any position with directional cursor support. Increase resize edge detection from 8 to 12 pixels. Add panel.h dependency to keys build target and update shortcut help text with new bindings.
413 lines
13 KiB
C
413 lines
13 KiB
C
/*
|
|
* DWN - Desktop Window Manager
|
|
* retoor <retoor@molodetz.nl>
|
|
* Window decorations implementation
|
|
*/
|
|
|
|
#include "decorations.h"
|
|
#include "client.h"
|
|
#include "config.h"
|
|
#include "util.h"
|
|
#include "workspace.h"
|
|
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <X11/Xft/Xft.h>
|
|
|
|
#define BUTTON_SIZE 16
|
|
#define BUTTON_PADDING 4
|
|
|
|
#define RESIZE_EDGE 12
|
|
|
|
#define DEC_XFT_COLOR_CACHE_SIZE 8
|
|
|
|
typedef struct {
|
|
unsigned long pixel;
|
|
XftColor xft_color;
|
|
bool valid;
|
|
int lru_counter;
|
|
} DecCachedXftColor;
|
|
|
|
static DecCachedXftColor dec_xft_color_cache[DEC_XFT_COLOR_CACHE_SIZE];
|
|
static Visual *dec_cached_visual = NULL;
|
|
static int dec_xft_color_lru_counter = 0;
|
|
|
|
static XftDraw *dec_cached_xft_draw = NULL;
|
|
static Window dec_cached_window = None;
|
|
|
|
static XftDraw *dec_get_xft_draw(Window w)
|
|
{
|
|
if (dec_cached_window == w && dec_cached_xft_draw != NULL) {
|
|
return dec_cached_xft_draw;
|
|
}
|
|
if (dec_cached_xft_draw != NULL) {
|
|
XftDrawDestroy(dec_cached_xft_draw);
|
|
}
|
|
if (dec_cached_visual == NULL) {
|
|
dec_cached_visual = DefaultVisual(dwn->display, dwn->screen);
|
|
}
|
|
dec_cached_xft_draw = XftDrawCreate(dwn->display, w, dec_cached_visual, dwn->colormap);
|
|
dec_cached_window = w;
|
|
return dec_cached_xft_draw;
|
|
}
|
|
|
|
static XftColor *dec_get_cached_xft_color(unsigned long color)
|
|
{
|
|
if (dec_cached_visual == NULL) {
|
|
dec_cached_visual = DefaultVisual(dwn->display, dwn->screen);
|
|
}
|
|
|
|
for (int i = 0; i < DEC_XFT_COLOR_CACHE_SIZE; i++) {
|
|
if (dec_xft_color_cache[i].valid && dec_xft_color_cache[i].pixel == color) {
|
|
dec_xft_color_cache[i].lru_counter = ++dec_xft_color_lru_counter;
|
|
return &dec_xft_color_cache[i].xft_color;
|
|
}
|
|
}
|
|
|
|
int oldest_idx = 0;
|
|
int oldest_lru = dec_xft_color_cache[0].lru_counter;
|
|
for (int i = 1; i < DEC_XFT_COLOR_CACHE_SIZE; i++) {
|
|
if (!dec_xft_color_cache[i].valid) {
|
|
oldest_idx = i;
|
|
break;
|
|
}
|
|
if (dec_xft_color_cache[i].lru_counter < oldest_lru) {
|
|
oldest_lru = dec_xft_color_cache[i].lru_counter;
|
|
oldest_idx = i;
|
|
}
|
|
}
|
|
|
|
if (dec_xft_color_cache[oldest_idx].valid) {
|
|
XftColorFree(dwn->display, dec_cached_visual, dwn->colormap,
|
|
&dec_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, dec_cached_visual, dwn->colormap,
|
|
&render_color, &dec_xft_color_cache[oldest_idx].xft_color);
|
|
dec_xft_color_cache[oldest_idx].pixel = color;
|
|
dec_xft_color_cache[oldest_idx].valid = true;
|
|
dec_xft_color_cache[oldest_idx].lru_counter = ++dec_xft_color_lru_counter;
|
|
|
|
return &dec_xft_color_cache[oldest_idx].xft_color;
|
|
}
|
|
|
|
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)
|
|
{
|
|
(void)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;
|
|
int title_height = config_get_title_height();
|
|
int border = client->border_width;
|
|
const ColorScheme *colors = config_get_colors();
|
|
|
|
Workspace *ws = workspace_get(client->workspace);
|
|
bool is_focused = (ws != NULL && ws->focused == client);
|
|
unsigned long base_bg = is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
|
|
unsigned long bg_color = ambient_glow_bg(base_bg, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
|
|
unsigned long fg_color = client->taskbar_color;
|
|
fg_color = adjust_color_for_background(fg_color, bg_color);
|
|
|
|
XSetForeground(dpy, dwn->gc, bg_color);
|
|
XFillRectangle(dpy, client->frame, dwn->gc,
|
|
border, border,
|
|
client->width, title_height);
|
|
|
|
bool is_alt_tab_selection = (dwn->is_alt_tabbing && dwn->alt_tab_client == client);
|
|
XftFont *title_font = (is_alt_tab_selection && dwn->xft_font_bold != NULL) ?
|
|
dwn->xft_font_bold : dwn->xft_font;
|
|
|
|
if (client->title[0] != '\0' && title_font != NULL) {
|
|
int text_y = border + (title_height + title_font->ascent) / 2;
|
|
int text_x = border + BUTTON_PADDING;
|
|
|
|
int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING;
|
|
if (max_width < BUTTON_SIZE) {
|
|
return;
|
|
}
|
|
char display_title[256];
|
|
size_t disp_len = strlen(client->title);
|
|
if (disp_len >= sizeof(display_title) - 3) disp_len = sizeof(display_title) - 4;
|
|
memcpy(display_title, client->title, disp_len);
|
|
display_title[disp_len] = '\0';
|
|
|
|
XGlyphInfo extents;
|
|
XftTextExtentsUtf8(dpy, title_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) {
|
|
cut--;
|
|
}
|
|
if (cut > 0) cut--;
|
|
while (cut > 0 && (display_title[cut] & 0xC0) == 0x80) {
|
|
cut--;
|
|
}
|
|
display_title[cut] = '\0';
|
|
title_truncated = true;
|
|
|
|
XftTextExtentsUtf8(dpy, title_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 = dec_get_xft_draw(client->frame);
|
|
if (xft_draw != NULL) {
|
|
XftColor *xft_color = dec_get_cached_xft_color(fg_color);
|
|
XftDrawStringUtf8(xft_draw, xft_color, title_font,
|
|
text_x, text_y,
|
|
(const FcChar8 *)display_title, strlen(display_title));
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int min_buttons_width = 3 * (BUTTON_SIZE + BUTTON_PADDING) + BUTTON_PADDING;
|
|
if (client->width < min_buttons_width) {
|
|
return;
|
|
}
|
|
|
|
Display *dpy = dwn->display;
|
|
(void)focused;
|
|
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;
|
|
|
|
if (close_x < border || max_x < border || min_x < border) {
|
|
return;
|
|
}
|
|
|
|
const ColorScheme *colors = config_get_colors();
|
|
Workspace *ws = workspace_get(client->workspace);
|
|
bool is_focused = (ws != NULL && ws->focused == client);
|
|
unsigned long base_bg = is_focused ? colors->title_focused_bg : colors->title_unfocused_bg;
|
|
unsigned long bg_color = ambient_glow_bg(base_bg, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
|
|
|
|
unsigned long close_color = ambient_glow_accent(0xff0055, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
|
|
unsigned long max_color = ambient_glow_accent(0x00ff00, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
|
|
unsigned long min_color = ambient_glow_accent(0xffff00, dwn->ambient_phase + PHASE_OFFSET_DECORATIONS);
|
|
|
|
XSetForeground(dpy, dwn->gc, bg_color);
|
|
XFillRectangle(dpy, client->frame, dwn->gc, close_x, button_y, BUTTON_SIZE, BUTTON_SIZE);
|
|
XSetForeground(dpy, dwn->gc, close_color);
|
|
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);
|
|
XSetForeground(dpy, dwn->gc, max_color);
|
|
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);
|
|
XSetForeground(dpy, dwn->gc, min_color);
|
|
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)
|
|
{
|
|
/* Borders are disabled for a smooth look */
|
|
(void)client;
|
|
(void)focused;
|
|
}
|
|
|
|
|
|
ButtonType decorations_hit_test_button(Client *client, int x, int y)
|
|
{
|
|
if (client == NULL) {
|
|
return BUTTON_COUNT;
|
|
}
|
|
|
|
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_maximize(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 = dec_get_xft_draw(window);
|
|
if (xft_draw != NULL) {
|
|
XftColor *xft_color = dec_get_cached_xft_color(color);
|
|
XftDrawStringUtf8(xft_draw, xft_color, dwn->xft_font,
|
|
x, y, (const FcChar8 *)text, strlen(text));
|
|
return;
|
|
}
|
|
}
|
|
|
|
XSetForeground(dwn->display, gc, color);
|
|
XDrawString(dwn->display, window, gc, x, y, text, strlen(text));
|
|
}
|