Fix #1: Replace hardcoded 185px deskbar-offset in client.c and events.c with DESKBAR_W constant (200) to eliminate the 15px gap between the WM frame and deskbar edge. Fix #2: Add Shift+Alt+Tab reverse-direction support — grab Mod1+Shift+XK_Tab in wm.c, detect ShiftMask in events.c, and implement switcher_prev() in switcher.c. Fix #3: Include minimized windows in the Alt+Tab switcher. swl_populate() no longer skips minimized clients; swl_mini[] tracks state so switcher_draw() can dim them with bracketed titles and darker card backgrounds. Fix #4: Rewrite themes/picom.conf with macOS-inspired settings — 10px corner radius on normal windows, 8px on menus, soft downward shadow (radius 18, offset-y 8, opacity 0.35), smooth fading (step 0.08), no dock/tray rounding. Fix #5: Add per-app right-click context menu to the deskbar. Right-clicking an app row opens a popup with Raise / Minimize / Close actions (Close shown in red). Actions are sent as EWMH client messages (_NET_ACTIVE_WINDOW, WM_CHANGE_STATE+IconicState, _NET_CLOSE_WINDOW) with matching handlers added to the WM's on_client_message. Pointer grab closes the menu on any outside click.
202 lines
7.7 KiB
C
202 lines
7.7 KiB
C
#include "wm.h"
|
|
|
|
#define SWL_ITEM_W 130
|
|
#define SWL_ITEM_H 68
|
|
#define SWL_ICON_SZ 34
|
|
#define SWL_PAD 12
|
|
#define SWL_MAX_VIS 8
|
|
|
|
static void swl_populate(WM *wm) {
|
|
wm->swl_count = 0;
|
|
/* current focused (non-minimized) window goes first */
|
|
if (wm->focused && !wm->focused->minimized) {
|
|
wm->swl_wins[0] = wm->focused->win;
|
|
wm->swl_mini[0] = 0;
|
|
strncpy(wm->swl_titles[0], wm->focused->title, 127);
|
|
wm->swl_titles[0][127] = '\0';
|
|
wm->swl_count = 1;
|
|
}
|
|
/* all other windows, including minimized ones */
|
|
for (Client *c = wm->clients; c && wm->swl_count < 64; c = c->next) {
|
|
if (wm->focused && c == wm->focused) continue;
|
|
wm->swl_wins[wm->swl_count] = c->win;
|
|
wm->swl_mini[wm->swl_count] = c->minimized;
|
|
strncpy(wm->swl_titles[wm->swl_count], c->title, 127);
|
|
wm->swl_titles[wm->swl_count][127] = '\0';
|
|
wm->swl_count++;
|
|
}
|
|
}
|
|
|
|
static int swl_vis(WM *wm) {
|
|
return wm->swl_count < SWL_MAX_VIS ? wm->swl_count : SWL_MAX_VIS;
|
|
}
|
|
|
|
/* Scroll window that keeps the selected item centered */
|
|
static int swl_view_start(WM *wm) {
|
|
int vis = swl_vis(wm);
|
|
if (wm->swl_count <= SWL_MAX_VIS) return 0;
|
|
int start = wm->swl_idx - vis / 2;
|
|
if (start < 0) start = 0;
|
|
if (start + vis > wm->swl_count) start = wm->swl_count - vis;
|
|
return start;
|
|
}
|
|
|
|
void switcher_draw(WM *wm) {
|
|
if (!wm->swl_visible || !wm->swl_win) return;
|
|
|
|
int vis = swl_vis(wm);
|
|
int vstart = swl_view_start(wm);
|
|
int tw = vis * SWL_ITEM_W + (vis + 1) * SWL_PAD;
|
|
int th = SWL_ITEM_H + SWL_PAD * 2;
|
|
|
|
XSetForeground(wm->dpy, wm->gc, color_alloc(wm, 0x141A28));
|
|
XFillRectangle(wm->dpy, wm->swl_win, wm->gc, 0, 0, tw, th);
|
|
|
|
XSetForeground(wm->dpy, wm->gc, color_alloc(wm, COLOR_BORDER_LIGHT));
|
|
XDrawRectangle(wm->dpy, wm->swl_win, wm->gc, 0, 0, tw - 1, th - 1);
|
|
XSetForeground(wm->dpy, wm->gc, color_alloc(wm, COLOR_BORDER_DARK));
|
|
XDrawRectangle(wm->dpy, wm->swl_win, wm->gc, 1, 1, tw - 3, th - 3);
|
|
|
|
XftDraw *xd = XftDrawCreate(wm->dpy, wm->swl_win, wm->visual, wm->cmap);
|
|
if (!xd) return;
|
|
|
|
for (int vi = 0; vi < vis; vi++) {
|
|
int i = vstart + vi;
|
|
int selected = (i == wm->swl_idx);
|
|
int mini = wm->swl_mini[i];
|
|
int ix = SWL_PAD + vi * (SWL_ITEM_W + SWL_PAD);
|
|
int iy = SWL_PAD;
|
|
|
|
unsigned long card_bg = color_alloc(wm,
|
|
selected ? COLOR_TITLE_ACTIVE : (mini ? 0x141820 : 0x1C2338));
|
|
XSetForeground(wm->dpy, wm->gc, card_bg);
|
|
XFillRectangle(wm->dpy, wm->swl_win, wm->gc, ix, iy, SWL_ITEM_W, SWL_ITEM_H);
|
|
|
|
XSetForeground(wm->dpy, wm->gc,
|
|
color_alloc(wm, selected ? COLOR_BORDER_LIGHT : (mini ? 0x202028 : 0x242C3E)));
|
|
XDrawRectangle(wm->dpy, wm->swl_win, wm->gc, ix, iy, SWL_ITEM_W - 1, SWL_ITEM_H - 1);
|
|
|
|
/* letter icon — dimmed for minimized */
|
|
int icon_x = ix + (SWL_ITEM_W - SWL_ICON_SZ) / 2;
|
|
int icon_y = iy + 8;
|
|
XSetForeground(wm->dpy, wm->gc,
|
|
color_alloc(wm, selected ? 0x285898 : (mini ? 0x1A1E28 : 0x182538)));
|
|
XFillRectangle(wm->dpy, wm->swl_win, wm->gc, icon_x, icon_y, SWL_ICON_SZ, SWL_ICON_SZ);
|
|
|
|
char letter[2] = { wm->swl_titles[i][0] ? wm->swl_titles[i][0] : '?', '\0' };
|
|
if (letter[0] >= 'a' && letter[0] <= 'z') letter[0] -= 32;
|
|
|
|
unsigned long icon_rgb = selected ? COLOR_TITLE_TEXT_ACT
|
|
: (mini ? 0x3A4860
|
|
: COLOR_TITLE_TEXT_INACT);
|
|
XftColor icon_col;
|
|
xft_color(wm, icon_rgb, &icon_col);
|
|
XGlyphInfo ext;
|
|
XftTextExtentsUtf8(wm->dpy, wm->font_title, (const FcChar8 *)letter, 1, &ext);
|
|
int lx = icon_x + (SWL_ICON_SZ - (int)ext.width) / 2;
|
|
int ly = icon_y + (SWL_ICON_SZ + (int)ext.height) / 2 - ext.y;
|
|
XftDrawStringUtf8(xd, &icon_col, wm->font_title, lx, ly, (const FcChar8 *)letter, 1);
|
|
XftColorFree(wm->dpy, wm->visual, wm->cmap, &icon_col);
|
|
|
|
/* title below icon — prefix [..] for minimized */
|
|
char title_buf[132];
|
|
const char *base_title = wm->swl_titles[i];
|
|
if (mini && !selected)
|
|
snprintf(title_buf, sizeof(title_buf), "[%s]", base_title);
|
|
else
|
|
snprintf(title_buf, sizeof(title_buf), "%s", base_title);
|
|
const char *title = title_buf;
|
|
XftColor title_col;
|
|
unsigned long title_rgb = selected ? COLOR_TITLE_TEXT_ACT
|
|
: (mini ? 0x3A4860
|
|
: COLOR_TITLE_TEXT_INACT);
|
|
xft_color(wm, title_rgb, &title_col);
|
|
XGlyphInfo text_ext;
|
|
XftTextExtentsUtf8(wm->dpy, wm->font_title,
|
|
(const FcChar8 *)title, strlen(title), &text_ext);
|
|
int tx = ix + (SWL_ITEM_W - (int)text_ext.width) / 2;
|
|
if (tx < ix + 4) tx = ix + 4;
|
|
int ty = icon_y + SWL_ICON_SZ + 4 + (int)text_ext.height - text_ext.y;
|
|
XRectangle clip = { (short)(ix + 2), (short)(icon_y + SWL_ICON_SZ + 4),
|
|
(unsigned short)(SWL_ITEM_W - 4), 14 };
|
|
XftDrawSetClipRectangles(xd, 0, 0, &clip, 1);
|
|
XftDrawStringUtf8(xd, &title_col, wm->font_title, tx, ty,
|
|
(const FcChar8 *)title, strlen(title));
|
|
XftDrawSetClip(xd, None);
|
|
XftColorFree(wm->dpy, wm->visual, wm->cmap, &title_col);
|
|
}
|
|
|
|
/* scroll arrows when more windows than fit */
|
|
if (wm->swl_count > SWL_MAX_VIS) {
|
|
XftColor arrow;
|
|
xft_color(wm, 0x5878A0, &arrow);
|
|
if (vstart > 0)
|
|
XftDrawStringUtf8(xd, &arrow, wm->font_title, 3, th / 2 + 4,
|
|
(const FcChar8 *)"\xe2\x97\x80", 3);
|
|
if (vstart + SWL_MAX_VIS < wm->swl_count)
|
|
XftDrawStringUtf8(xd, &arrow, wm->font_title, tw - 11, th / 2 + 4,
|
|
(const FcChar8 *)"\xe2\x96\xb6", 3);
|
|
XftColorFree(wm->dpy, wm->visual, wm->cmap, &arrow);
|
|
}
|
|
|
|
XftDrawDestroy(xd);
|
|
XFlush(wm->dpy);
|
|
}
|
|
|
|
void switcher_show(WM *wm) {
|
|
swl_populate(wm);
|
|
if (wm->swl_count < 2) return;
|
|
|
|
wm->swl_idx = 1;
|
|
wm->swl_visible = 1;
|
|
|
|
int vis = swl_vis(wm);
|
|
int tw = vis * SWL_ITEM_W + (vis + 1) * SWL_PAD;
|
|
int th = SWL_ITEM_H + SWL_PAD * 2;
|
|
int sw = DisplayWidth(wm->dpy, wm->screen);
|
|
int sh = DisplayHeight(wm->dpy, wm->screen);
|
|
|
|
XSetWindowAttributes swa;
|
|
swa.override_redirect = True;
|
|
swa.background_pixel = color_alloc(wm, 0x141A28);
|
|
swa.event_mask = ExposureMask;
|
|
wm->swl_win = XCreateWindow(
|
|
wm->dpy, wm->root,
|
|
(sw - tw) / 2, (sh - th) / 2, tw, th, 2,
|
|
CopyFromParent, InputOutput, CopyFromParent,
|
|
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa
|
|
);
|
|
XSetWindowBorder(wm->dpy, wm->swl_win, color_alloc(wm, COLOR_BORDER_LIGHT));
|
|
XMapRaised(wm->dpy, wm->swl_win);
|
|
switcher_draw(wm);
|
|
|
|
XGrabKeyboard(wm->dpy, wm->root, False, GrabModeAsync, GrabModeAsync, CurrentTime);
|
|
}
|
|
|
|
void switcher_next(WM *wm) {
|
|
if (!wm->swl_visible) return;
|
|
wm->swl_idx = (wm->swl_idx + 1) % wm->swl_count;
|
|
switcher_draw(wm);
|
|
}
|
|
|
|
void switcher_prev(WM *wm) {
|
|
if (!wm->swl_visible) return;
|
|
wm->swl_idx = (wm->swl_idx - 1 + wm->swl_count) % wm->swl_count;
|
|
switcher_draw(wm);
|
|
}
|
|
|
|
void switcher_hide(WM *wm, int commit) {
|
|
if (!wm->swl_visible) return;
|
|
XUngrabKeyboard(wm->dpy, CurrentTime);
|
|
if (wm->swl_win) { XDestroyWindow(wm->dpy, wm->swl_win); wm->swl_win = 0; }
|
|
wm->swl_visible = 0;
|
|
|
|
if (commit && wm->swl_count > 0) {
|
|
Client *c = client_find_by_win(wm, wm->swl_wins[wm->swl_idx]);
|
|
if (c) {
|
|
if (c->minimized) client_restore(wm, c);
|
|
else client_focus(wm, c);
|
|
}
|
|
}
|
|
}
|