Phase 1: Alt+Tab switcher, dark GTK theme, alacritty terminal

- Alt+Tab now shows a centered visual switcher overlay with window cards;
  cycling with Tab, committing on Alt release, cancelling with Escape
- GTK3 and GTK2 themes rewritten in dark navy palette matching skywm/
  skydeskbar color constants (was an off-brand tan/BeOS palette)
- Add alacritty to ISO packages with pre-configured dark color scheme;
  update all menu Terminal entries to launch alacritty instead of xterm
- Add KeyReleaseMask to WM root event selection to detect Alt release

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-07-23 13:11:37 -04:00
1 parent e641720a3c
commit 9089c14a19
10 files changed
+378 -101

No files matched your search

+1 -1
View File
@@ -14,7 +14,7 @@ endif
CFLAGS = -Wall -Wextra -O2 -Iinclude $(PKG_XFT_C)
LDFLAGS = $(PKG_XFT_L)
SRC = src/wm.c src/client.c src/events.c src/draw.c src/color.c src/menu.c
SRC = src/wm.c src/client.c src/events.c src/draw.c src/color.c src/menu.c src/switcher.c
OBJ = $(SRC:.c=.o)
BIN = skywm
+15
View File
@@ -121,6 +121,14 @@ typedef struct WM {
int menu_visible;
int menu_x, menu_y;
int menu_hovered;
/* Alt+Tab visual switcher */
Window swl_win;
int swl_visible;
int swl_idx;
int swl_count;
Window swl_wins[64];
char swl_titles[64][128];
} WM;
/* core */
@@ -163,6 +171,12 @@ void menu_draw(WM *wm);
void menu_click(WM *wm, int px, int py);
int menu_item_at(int py);
/* switcher */
void switcher_show(WM *wm);
void switcher_next(WM *wm);
void switcher_hide(WM *wm, int commit);
void switcher_draw(WM *wm);
/* events */
void on_map_request(WM *wm, XMapRequestEvent *e);
void on_unmap_notify(WM *wm, XUnmapEvent *e);
@@ -176,5 +190,6 @@ void on_key_press(WM *wm, XKeyEvent *e);
void on_enter_notify(WM *wm, XCrossingEvent *e);
void on_client_message(WM *wm, XClientMessageEvent *e);
void on_property_notify(WM *wm, XPropertyEvent *e);
void on_key_release(WM *wm, XKeyEvent *e);
#endif
+23 -7
View File
@@ -66,6 +66,10 @@ void on_expose(WM *wm, XExposeEvent *e) {
menu_draw(wm);
return;
}
if (wm->swl_visible && e->window == wm->swl_win) {
switcher_draw(wm);
return;
}
Client *c = client_find_by_frame(wm, e->window);
if (c && !c->minimized) frame_draw(wm, c);
}
@@ -259,13 +263,17 @@ void on_key_press(WM *wm, XKeyEvent *e) {
if ((e->state & Mod1Mask) && sym == XK_F4 && wm->focused)
send_delete(wm, wm->focused->win);
/* Alt+Tab — cycle windows */
if ((e->state & Mod1Mask) && sym == XK_Tab && wm->clients) {
Client *start = wm->focused ? wm->focused->next : wm->clients;
Client *n = start;
while (n && n->minimized) n = n->next;
if (!n) { n = wm->clients; while (n && n->minimized) n = n->next; }
if (n && n != wm->focused) client_focus(wm, n);
/* Alt+Tab — visual switcher */
if ((e->state & Mod1Mask) && sym == XK_Tab) {
if (wm->swl_visible) switcher_next(wm);
else if (wm->clients) switcher_show(wm);
return;
}
/* While switcher is visible, eat all other keys until Alt released */
if (wm->swl_visible) {
if (sym == XK_Escape) switcher_hide(wm, 0);
return;
}
/* Alt+F9 — minimize */
@@ -300,3 +308,11 @@ void on_key_press(WM *wm, XKeyEvent *e) {
if (sym == XK_Escape && wm->menu_visible)
menu_hide(wm);
}
void on_key_release(WM *wm, XKeyEvent *e) {
if (!wm->swl_visible) return;
KeySym sym = XLookupKeysym(e, 0);
if (sym == XK_Alt_L || sym == XK_Alt_R ||
sym == XK_Meta_L || sym == XK_Meta_R)
switcher_hide(wm, 1);
}
+2 -2
View File
@@ -6,12 +6,12 @@
static const MenuItem MENU_ITEMS[] = {
{ "App Launcher", "skylauncher &" },
{ "Terminal", "xterm &" },
{ "Terminal", "alacritty &" },
{ "File Manager", "thunar &" },
{ "Text Editor", "mousepad &" },
{ "──────────────────", NULL },
{ "Web Browser", "firefox &" },
{ "System Monitor", "xterm -T 'System Monitor' -e htop &" },
{ "System Monitor", "alacritty -T 'System Monitor' -e htop &" },
{ "Volume Control", "pavucontrol &" },
{ "Display Settings", "arandr &" },
{ "──────────────────", NULL },
+179
View File
@@ -0,0 +1,179 @@
#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 window goes first */
if (wm->focused && !wm->focused->minimized) {
wm->swl_wins[0] = wm->focused->win;
strncpy(wm->swl_titles[0], wm->focused->title, 127);
wm->swl_titles[0][127] = '\0';
wm->swl_count = 1;
}
for (Client *c = wm->clients; c && wm->swl_count < 64; c = c->next) {
if (c->minimized) continue;
if (wm->focused && c == wm->focused) continue;
wm->swl_wins[wm->swl_count] = c->win;
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 ix = SWL_PAD + vi * (SWL_ITEM_W + SWL_PAD);
int iy = SWL_PAD;
unsigned long card_bg = color_alloc(wm, selected ? COLOR_TITLE_ACTIVE : 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 : 0x242C3E));
XDrawRectangle(wm->dpy, wm->swl_win, wm->gc, ix, iy, SWL_ITEM_W - 1, SWL_ITEM_H - 1);
/* letter icon */
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 : 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;
XftColor icon_col;
xft_color(wm, selected ? COLOR_TITLE_TEXT_ACT : COLOR_TITLE_TEXT_INACT, &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 */
const char *title = wm->swl_titles[i];
XftColor title_col;
xft_color(wm, selected ? COLOR_TITLE_TEXT_ACT : COLOR_TITLE_TEXT_INACT, &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_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);
}
}
}
+2 -1
View File
@@ -79,7 +79,7 @@ void wm_init(WM *wm) {
XSetErrorHandler(x_error_start);
XSelectInput(wm->dpy, wm->root,
SubstructureRedirectMask | SubstructureNotifyMask |
KeyPressMask | ButtonPressMask);
KeyPressMask | KeyReleaseMask | ButtonPressMask);
XSync(wm->dpy, False);
XSetErrorHandler(x_error_handler);
@@ -138,6 +138,7 @@ void wm_run(WM *wm) {
case ButtonRelease: on_button_release(wm, &e.xbutton); break;
case MotionNotify: on_motion_notify(wm, &e.xmotion); break;
case KeyPress: on_key_press(wm, &e.xkey); break;
case KeyRelease: on_key_release(wm, &e.xkey); break;
case EnterNotify: on_enter_notify(wm, &e.xcrossing); break;
case ClientMessage: on_client_message(wm, &e.xclient); break;
case PropertyNotify: on_property_notify(wm, &e.xproperty); break;