#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); } } }