macOS-style polish: fix seam, bidirectional Alt+Tab, minimized windows in switcher, rounded corners, deskbar context menu
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.
This commit is contained in:
8 files changed
+311
-42
No files matched your search
@@ -44,6 +44,12 @@
|
|||||||
#define DB_TRAY_MAX 16 /* maximum simultaneous tray icons */
|
#define DB_TRAY_MAX 16 /* maximum simultaneous tray icons */
|
||||||
#define DB_MAX_APPS 32
|
#define DB_MAX_APPS 32
|
||||||
|
|
||||||
|
/* ── per-app context menu ────────────────────────────────────────────── */
|
||||||
|
#define DB_CTX_W 140 /* context menu width */
|
||||||
|
#define DB_CTX_ITEM_H 26 /* item row height */
|
||||||
|
#define DB_CTX_PAD 4 /* top/bottom padding */
|
||||||
|
#define DB_CTX_N 3 /* number of items: Raise, Minimize, Close */
|
||||||
|
|
||||||
/* ── fly-out menu ────────────────────────────────────────────────────── */
|
/* ── fly-out menu ────────────────────────────────────────────────────── */
|
||||||
#define DB_FLYOUT_W 196 /* fly-out menu panel width */
|
#define DB_FLYOUT_W 196 /* fly-out menu panel width */
|
||||||
#define DB_FLYOUT_IH 28 /* item row height */
|
#define DB_FLYOUT_IH 28 /* item row height */
|
||||||
@@ -139,6 +145,11 @@ typedef struct Deskbar {
|
|||||||
int sub_hov; /* hovered item in sub-menu */
|
int sub_hov; /* hovered item in sub-menu */
|
||||||
int sw_cache; /* cached screen width for menu positioning */
|
int sw_cache; /* cached screen width for menu positioning */
|
||||||
|
|
||||||
|
/* per-app right-click context menu */
|
||||||
|
Window ctx_win; /* None when closed */
|
||||||
|
int ctx_app_idx; /* which app row the ctx menu is for */
|
||||||
|
int ctx_hov; /* hovered item index, -1 = none */
|
||||||
|
|
||||||
/* system tray (freedesktop.org XEmbed tray protocol) */
|
/* system tray (freedesktop.org XEmbed tray protocol) */
|
||||||
Window tray_icons[DB_TRAY_MAX];
|
Window tray_icons[DB_TRAY_MAX];
|
||||||
int n_tray_icons;
|
int n_tray_icons;
|
||||||
|
|||||||
@@ -809,6 +809,168 @@ static void handle_menu_press(Deskbar *db, int rx, int ry) {
|
|||||||
db_menu_close(db);
|
db_menu_close(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── per-app context menu ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static int ctx_total_h(void) {
|
||||||
|
return DB_CTX_PAD + DB_CTX_N * DB_CTX_ITEM_H + DB_CTX_PAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void db_ctx_draw(Deskbar *db) {
|
||||||
|
if (!db->ctx_win) return;
|
||||||
|
int ww = DB_CTX_W;
|
||||||
|
int wh = ctx_total_h();
|
||||||
|
int mini = (db->ctx_app_idx >= 0 && db->ctx_app_idx < db->napps)
|
||||||
|
? db->apps[db->ctx_app_idx].minimized : 0;
|
||||||
|
|
||||||
|
static const char *labels_normal[] = { "Raise", "Minimize", "Close" };
|
||||||
|
static const char *labels_mini[] = { "Restore", "Restore", "Close" };
|
||||||
|
const char **labels = mini ? labels_mini : labels_normal;
|
||||||
|
|
||||||
|
XftDraw *xd = XftDrawCreate(db->dpy, db->ctx_win, db->visual, db->cmap);
|
||||||
|
|
||||||
|
/* background */
|
||||||
|
XSetForeground(db->dpy, db->gc, db_color(db, 0x2C2C2C));
|
||||||
|
XFillRectangle(db->dpy, db->ctx_win, db->gc, 0, 0, ww, wh);
|
||||||
|
|
||||||
|
/* 1px border */
|
||||||
|
XSetForeground(db->dpy, db->gc, db_color(db, 0x505050));
|
||||||
|
XDrawRectangle(db->dpy, db->ctx_win, db->gc, 0, 0, ww - 1, wh - 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < DB_CTX_N; i++) {
|
||||||
|
int iy = DB_CTX_PAD + i * DB_CTX_ITEM_H;
|
||||||
|
|
||||||
|
if (i == db->ctx_hov) {
|
||||||
|
XSetForeground(db->dpy, db->gc, db_color(db, 0x4A90D9));
|
||||||
|
XFillRectangle(db->dpy, db->ctx_win, db->gc,
|
||||||
|
2, iy + 1, ww - 4, DB_CTX_ITEM_H - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "Close" item tinted red */
|
||||||
|
unsigned long tc = (i == db->ctx_hov) ? 0xFFFFFF
|
||||||
|
: (i == DB_CTX_N - 1) ? 0xFF6B6B /* Close = red tint */
|
||||||
|
: 0xE8E8E8;
|
||||||
|
draw_str(db, xd, db->font_ui, tc,
|
||||||
|
14, str_baseline(db->font_ui, iy, DB_CTX_ITEM_H),
|
||||||
|
labels[i], 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
XftDrawDestroy(xd);
|
||||||
|
XFlush(db->dpy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void db_ctx_close(Deskbar *db) {
|
||||||
|
if (!db->ctx_win) return;
|
||||||
|
XUngrabPointer(db->dpy, CurrentTime);
|
||||||
|
XDestroyWindow(db->dpy, db->ctx_win);
|
||||||
|
db->ctx_win = None;
|
||||||
|
db->ctx_app_idx = -1;
|
||||||
|
db->ctx_hov = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void db_ctx_open(Deskbar *db, int app_idx, int rx, int ry) {
|
||||||
|
db_ctx_close(db);
|
||||||
|
|
||||||
|
int sw = DisplayWidth(db->dpy, db->screen);
|
||||||
|
int sh = DisplayHeight(db->dpy, db->screen);
|
||||||
|
int wh = ctx_total_h();
|
||||||
|
int wx = sw - DB_W - DB_CTX_W - 2;
|
||||||
|
int wy = ry;
|
||||||
|
if (wy + wh > sh) wy = sh - wh - 2;
|
||||||
|
if (wy < 0) wy = 0;
|
||||||
|
|
||||||
|
XSetWindowAttributes swa;
|
||||||
|
swa.override_redirect = True;
|
||||||
|
swa.background_pixel = db_color(db, 0x2C2C2C);
|
||||||
|
swa.event_mask = ExposureMask;
|
||||||
|
db->ctx_win = XCreateWindow(
|
||||||
|
db->dpy, db->root, wx, wy, DB_CTX_W, wh, 1,
|
||||||
|
db->depth, InputOutput, db->visual,
|
||||||
|
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
|
||||||
|
|
||||||
|
XSetWindowBorder(db->dpy, db->ctx_win, db_color(db, 0x505050));
|
||||||
|
db->ctx_app_idx = app_idx;
|
||||||
|
db->ctx_hov = -1;
|
||||||
|
XMapRaised(db->dpy, db->ctx_win);
|
||||||
|
db_ctx_draw(db);
|
||||||
|
|
||||||
|
XGrabPointer(db->dpy, db->ctx_win, True,
|
||||||
|
ButtonPressMask | PointerMotionMask | ButtonReleaseMask,
|
||||||
|
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void db_ctx_handle_motion(Deskbar *db, int rx, int ry) {
|
||||||
|
if (!db->ctx_win) return;
|
||||||
|
int cx, cy; unsigned cw, ch, bw, dep; Window cr;
|
||||||
|
XGetGeometry(db->dpy, db->ctx_win, &cr, &cx, &cy, &cw, &ch, &bw, &dep);
|
||||||
|
if (rx < cx || ry < cy || rx >= cx + (int)cw || ry >= cy + (int)ch) {
|
||||||
|
if (db->ctx_hov != -1) { db->ctx_hov = -1; db_ctx_draw(db); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int lx = ry - cy - DB_CTX_PAD;
|
||||||
|
int idx = (lx >= 0) ? lx / DB_CTX_ITEM_H : -1;
|
||||||
|
if (idx >= DB_CTX_N) idx = -1;
|
||||||
|
if (idx != db->ctx_hov) { db->ctx_hov = idx; db_ctx_draw(db); }
|
||||||
|
}
|
||||||
|
|
||||||
|
static void db_ctx_handle_press(Deskbar *db, int rx, int ry) {
|
||||||
|
if (!db->ctx_win) return;
|
||||||
|
int cx, cy; unsigned cw, ch, bw, dep; Window cr;
|
||||||
|
XGetGeometry(db->dpy, db->ctx_win, &cr, &cx, &cy, &cw, &ch, &bw, &dep);
|
||||||
|
|
||||||
|
int app_idx = db->ctx_app_idx;
|
||||||
|
int in_menu = (rx >= cx && ry >= cy && rx < cx + (int)cw && ry < cy + (int)ch);
|
||||||
|
|
||||||
|
if (!in_menu || app_idx < 0 || app_idx >= db->napps) {
|
||||||
|
db_ctx_close(db);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lx = ry - cy - DB_CTX_PAD;
|
||||||
|
int idx = (lx >= 0) ? lx / DB_CTX_ITEM_H : -1;
|
||||||
|
Window target = db->apps[app_idx].win;
|
||||||
|
int mini = db->apps[app_idx].minimized;
|
||||||
|
db_ctx_close(db);
|
||||||
|
|
||||||
|
if (idx < 0 || idx >= DB_CTX_N) return;
|
||||||
|
|
||||||
|
XEvent ev = {0};
|
||||||
|
ev.xclient.type = ClientMessage;
|
||||||
|
ev.xclient.format = 32;
|
||||||
|
|
||||||
|
if (idx == 0) {
|
||||||
|
/* Raise / Restore */
|
||||||
|
ev.xclient.window = target;
|
||||||
|
ev.xclient.message_type = XInternAtom(db->dpy, "_NET_ACTIVE_WINDOW", False);
|
||||||
|
ev.xclient.data.l[0] = 2;
|
||||||
|
ev.xclient.data.l[1] = CurrentTime;
|
||||||
|
XSendEvent(db->dpy, db->root, False,
|
||||||
|
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
|
||||||
|
} else if (idx == 1) {
|
||||||
|
if (mini) {
|
||||||
|
/* Restore */
|
||||||
|
ev.xclient.window = target;
|
||||||
|
ev.xclient.message_type = XInternAtom(db->dpy, "_NET_ACTIVE_WINDOW", False);
|
||||||
|
ev.xclient.data.l[0] = 2;
|
||||||
|
ev.xclient.data.l[1] = CurrentTime;
|
||||||
|
} else {
|
||||||
|
/* Minimize via WM_CHANGE_STATE IconicState */
|
||||||
|
ev.xclient.window = target;
|
||||||
|
ev.xclient.message_type = XInternAtom(db->dpy, "WM_CHANGE_STATE", False);
|
||||||
|
ev.xclient.data.l[0] = 3; /* IconicState */
|
||||||
|
}
|
||||||
|
XSendEvent(db->dpy, db->root, False,
|
||||||
|
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
|
||||||
|
} else if (idx == 2) {
|
||||||
|
/* Close via _NET_CLOSE_WINDOW */
|
||||||
|
ev.xclient.window = target;
|
||||||
|
ev.xclient.message_type = XInternAtom(db->dpy, "_NET_CLOSE_WINDOW", False);
|
||||||
|
ev.xclient.data.l[0] = CurrentTime;
|
||||||
|
ev.xclient.data.l[1] = 2;
|
||||||
|
XSendEvent(db->dpy, db->root, False,
|
||||||
|
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ── input ────────────────────────────────────────────────────────────── */
|
/* ── input ────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
void db_hover(Deskbar *db, int x, int y) {
|
void db_hover(Deskbar *db, int x, int y) {
|
||||||
@@ -981,6 +1143,26 @@ void db_run(Deskbar *db) {
|
|||||||
XEvent e;
|
XEvent e;
|
||||||
XNextEvent(db->dpy, &e);
|
XNextEvent(db->dpy, &e);
|
||||||
|
|
||||||
|
/* While the per-app context menu is open, the pointer is grabbed.
|
||||||
|
Route motion and button events through the ctx handlers. */
|
||||||
|
if (db->ctx_win) {
|
||||||
|
if (e.type == MotionNotify) {
|
||||||
|
db_ctx_handle_motion(db, e.xmotion.x_root, e.xmotion.y_root);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (e.type == ButtonPress) {
|
||||||
|
db_ctx_handle_press(db, e.xbutton.x_root, e.xbutton.y_root);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (e.type == ButtonRelease)
|
||||||
|
continue;
|
||||||
|
if (e.type == Expose && e.xexpose.count == 0) {
|
||||||
|
if (e.xany.window == db->ctx_win) db_ctx_draw(db);
|
||||||
|
else if (e.xany.window == db->win) db_draw(db);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* While the fly-out menu is open, the pointer is grabbed.
|
/* While the fly-out menu is open, the pointer is grabbed.
|
||||||
Route all motion and button events through the menu handlers
|
Route all motion and button events through the menu handlers
|
||||||
(they use root-relative coords to determine which panel was hit). */
|
(they use root-relative coords to determine which panel was hit). */
|
||||||
@@ -1026,6 +1208,18 @@ void db_run(Deskbar *db) {
|
|||||||
system("pactl set-sink-volume @DEFAULT_SINK@ -5% 2>/dev/null &");
|
system("pactl set-sink-volume @DEFAULT_SINK@ -5% 2>/dev/null &");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (e.xbutton.button == Button3) {
|
||||||
|
/* right-click on an app row → open context menu */
|
||||||
|
int ay = db_apps_y();
|
||||||
|
int by = e.xbutton.y;
|
||||||
|
if (by >= ay && by < ay + db->napps * DB_ITEM_H) {
|
||||||
|
int idx = (by - ay) / DB_ITEM_H;
|
||||||
|
if (idx >= 0 && idx < db->napps)
|
||||||
|
db_ctx_open(db, idx,
|
||||||
|
e.xbutton.x_root, e.xbutton.y_root);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (e.xbutton.button == Button1 && e.xbutton.y >= sy)
|
if (e.xbutton.button == Button1 && e.xbutton.y >= sy)
|
||||||
system("pactl set-sink-mute @DEFAULT_SINK@ toggle 2>/dev/null &");
|
system("pactl set-sink-mute @DEFAULT_SINK@ toggle 2>/dev/null &");
|
||||||
else
|
else
|
||||||
|
|||||||
+25
-20
@@ -1,22 +1,29 @@
|
|||||||
# OrionOS picom compositor config
|
# OrionOS picom compositor config — macOS-inspired
|
||||||
# Goal: subtle window shadows, no transparency, fast rendering.
|
# Rounded corners, soft macOS-style shadows, fast fading.
|
||||||
# BeOS/OrionOS didn't have transparency — we keep it clean.
|
|
||||||
|
|
||||||
backend = "xrender"; # xrender works on all hardware; switch to glx for performance
|
backend = "xrender";
|
||||||
vsync = false;
|
vsync = false;
|
||||||
|
|
||||||
|
# ── rounded corners ───────────────────────────────────────────────────────
|
||||||
|
corner-radius = 10;
|
||||||
|
rounded-corners-exclude = [
|
||||||
|
"_NET_WM_WINDOW_TYPE@:a *= 'DOCK'",
|
||||||
|
"_NET_WM_WINDOW_TYPE@:a *= 'DESKTOP'",
|
||||||
|
"_NET_WM_WINDOW_TYPE@:a *= 'TOOLTIP'",
|
||||||
|
"_NET_WM_WINDOW_TYPE@:a *= 'NOTIFICATION'",
|
||||||
|
"override_redirect = 1 && !WM_CLASS@:s"
|
||||||
|
];
|
||||||
|
|
||||||
# ── shadows ───────────────────────────────────────────────────────────────
|
# ── shadows ───────────────────────────────────────────────────────────────
|
||||||
|
# macOS-style: larger, softer, offset down-right
|
||||||
shadow = true;
|
shadow = true;
|
||||||
shadow-radius = 8;
|
shadow-radius = 18;
|
||||||
shadow-offset-x = 4;
|
shadow-offset-x = 0;
|
||||||
shadow-offset-y = 4;
|
shadow-offset-y = 8;
|
||||||
shadow-opacity = 0.30;
|
shadow-opacity = 0.35;
|
||||||
shadow-color = "#000000";
|
shadow-color = "#000000";
|
||||||
|
|
||||||
# Don't shadow the deskbar, menus, or tooltips
|
|
||||||
shadow-exclude = [
|
shadow-exclude = [
|
||||||
"class_g = 'skydeskbar'",
|
|
||||||
"class_g = 'skylauncher'",
|
|
||||||
"_NET_WM_WINDOW_TYPE@:a *= 'DOCK'",
|
"_NET_WM_WINDOW_TYPE@:a *= 'DOCK'",
|
||||||
"_NET_WM_WINDOW_TYPE@:a *= 'TOOLTIP'",
|
"_NET_WM_WINDOW_TYPE@:a *= 'TOOLTIP'",
|
||||||
"_NET_WM_WINDOW_TYPE@:a *= 'NOTIFICATION'",
|
"_NET_WM_WINDOW_TYPE@:a *= 'NOTIFICATION'",
|
||||||
@@ -27,20 +34,18 @@ shadow-exclude = [
|
|||||||
|
|
||||||
# ── fading ────────────────────────────────────────────────────────────────
|
# ── fading ────────────────────────────────────────────────────────────────
|
||||||
fading = true;
|
fading = true;
|
||||||
fade-in-step = 0.06;
|
fade-in-step = 0.08;
|
||||||
fade-out-step = 0.06;
|
fade-out-step = 0.08;
|
||||||
fade-delta = 8; # ms between steps — snappy, not sluggish
|
fade-delta = 6;
|
||||||
no-fading-openclose = false;
|
no-fading-openclose = false;
|
||||||
|
|
||||||
# ── opacity ───────────────────────────────────────────────────────────────
|
# ── opacity ───────────────────────────────────────────────────────────────
|
||||||
# No transparency — BeOS aesthetic is solid, crisp windows.
|
|
||||||
inactive-opacity = 1.0;
|
inactive-opacity = 1.0;
|
||||||
active-opacity = 1.0;
|
active-opacity = 1.0;
|
||||||
frame-opacity = 1.0;
|
frame-opacity = 1.0;
|
||||||
inactive-opacity-override = false;
|
inactive-opacity-override = false;
|
||||||
|
|
||||||
# ── blur ─────────────────────────────────────────────────────────────────
|
# ── blur ─────────────────────────────────────────────────────────────────
|
||||||
# Disabled — keep it clean.
|
|
||||||
blur-method = "none";
|
blur-method = "none";
|
||||||
|
|
||||||
# ── focus & refresh ───────────────────────────────────────────────────────
|
# ── focus & refresh ───────────────────────────────────────────────────────
|
||||||
@@ -55,9 +60,9 @@ mark-ovredir-focused = false;
|
|||||||
wintypes:
|
wintypes:
|
||||||
{
|
{
|
||||||
tooltip = { fade = true; shadow = false; opacity = 1.0; focus = true; };
|
tooltip = { fade = true; shadow = false; opacity = 1.0; focus = true; };
|
||||||
dock = { shadow = false; clip-shadow-above = true; };
|
dock = { shadow = false; clip-shadow-above = true; corner-radius = 0; };
|
||||||
dnd = { shadow = false; };
|
dnd = { shadow = false; };
|
||||||
popup_menu = { opacity = 1.0; shadow = false; };
|
popup_menu = { opacity = 1.0; shadow = false; corner-radius = 8; };
|
||||||
dropdown_menu = { opacity = 1.0; shadow = false; };
|
dropdown_menu = { opacity = 1.0; shadow = false; corner-radius = 8; };
|
||||||
notification = { shadow = false; };
|
notification = { shadow = false; corner-radius = 8; };
|
||||||
};
|
};
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
#define COLOR_SNAP_OVERLAY 0x4A90D9 /* macOS blue snap highlight */
|
#define COLOR_SNAP_OVERLAY 0x4A90D9 /* macOS blue snap highlight */
|
||||||
|
|
||||||
/* ── geometry ────────────────────────────────────────────────────────── */
|
/* ── geometry ────────────────────────────────────────────────────────── */
|
||||||
|
#define DESKBAR_W 200 /* must match DB_W in deskbar/include/deskbar.h */
|
||||||
#define TITLE_HEIGHT 28 /* macOS-style height */
|
#define TITLE_HEIGHT 28 /* macOS-style height */
|
||||||
#define TITLE_TAB_W 200
|
#define TITLE_TAB_W 200
|
||||||
#define BORDER_W 1 /* thin 1px macOS-style border */
|
#define BORDER_W 1 /* thin 1px macOS-style border */
|
||||||
@@ -130,6 +131,7 @@ typedef struct WM {
|
|||||||
int swl_count;
|
int swl_count;
|
||||||
Window swl_wins[64];
|
Window swl_wins[64];
|
||||||
char swl_titles[64][128];
|
char swl_titles[64][128];
|
||||||
|
int swl_mini[64]; /* 1 if corresponding entry is minimized */
|
||||||
} WM;
|
} WM;
|
||||||
|
|
||||||
/* core */
|
/* core */
|
||||||
@@ -175,6 +177,7 @@ int menu_item_at(int py);
|
|||||||
/* switcher */
|
/* switcher */
|
||||||
void switcher_show(WM *wm);
|
void switcher_show(WM *wm);
|
||||||
void switcher_next(WM *wm);
|
void switcher_next(WM *wm);
|
||||||
|
void switcher_prev(WM *wm);
|
||||||
void switcher_hide(WM *wm, int commit);
|
void switcher_hide(WM *wm, int commit);
|
||||||
void switcher_draw(WM *wm);
|
void switcher_draw(WM *wm);
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -65,9 +65,9 @@ Client *client_add(WM *wm, Window win) {
|
|||||||
c->x = wa.x;
|
c->x = wa.x;
|
||||||
c->y = wa.y;
|
c->y = wa.y;
|
||||||
}
|
}
|
||||||
/* never land behind deskbar (top-right panel ~180px wide) */
|
/* never land behind deskbar */
|
||||||
int sw = DisplayWidth(wm->dpy, wm->screen);
|
int sw = DisplayWidth(wm->dpy, wm->screen);
|
||||||
if (c->x + (int)c->w > sw - 185 && c->y < 200)
|
if (c->x + (int)c->w > sw - DESKBAR_W && c->y < 200)
|
||||||
c->y = 40;
|
c->y = 40;
|
||||||
|
|
||||||
/* fetch title */
|
/* fetch title */
|
||||||
@@ -228,7 +228,7 @@ void client_maximize(WM *wm, Client *c) {
|
|||||||
int sh = DisplayHeight(wm->dpy, wm->screen);
|
int sh = DisplayHeight(wm->dpy, wm->screen);
|
||||||
|
|
||||||
/* leave room for deskbar on the right */
|
/* leave room for deskbar on the right */
|
||||||
int deskbar_w = 185;
|
int deskbar_w = DESKBAR_W;
|
||||||
int new_w = sw - deskbar_w - BORDER_W * 2;
|
int new_w = sw - deskbar_w - BORDER_W * 2;
|
||||||
int new_h = sh - TITLE_HEIGHT - BORDER_W;
|
int new_h = sh - TITLE_HEIGHT - BORDER_W;
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ void client_snap_left(WM *wm, Client *c) {
|
|||||||
if (c->maximized) client_unmaximize(wm, c);
|
if (c->maximized) client_unmaximize(wm, c);
|
||||||
int sw = DisplayWidth(wm->dpy, wm->screen);
|
int sw = DisplayWidth(wm->dpy, wm->screen);
|
||||||
int sh = DisplayHeight(wm->dpy, wm->screen);
|
int sh = DisplayHeight(wm->dpy, wm->screen);
|
||||||
int deskbar_w = 185;
|
int deskbar_w = DESKBAR_W;
|
||||||
int half_w = (sw - deskbar_w) / 2;
|
int half_w = (sw - deskbar_w) / 2;
|
||||||
|
|
||||||
c->x = 0;
|
c->x = 0;
|
||||||
@@ -299,7 +299,7 @@ void client_snap_right(WM *wm, Client *c) {
|
|||||||
if (c->maximized) client_unmaximize(wm, c);
|
if (c->maximized) client_unmaximize(wm, c);
|
||||||
int sw = DisplayWidth(wm->dpy, wm->screen);
|
int sw = DisplayWidth(wm->dpy, wm->screen);
|
||||||
int sh = DisplayHeight(wm->dpy, wm->screen);
|
int sh = DisplayHeight(wm->dpy, wm->screen);
|
||||||
int deskbar_w = 185;
|
int deskbar_w = DESKBAR_W;
|
||||||
int half_w = (sw - deskbar_w) / 2;
|
int half_w = (sw - deskbar_w) / 2;
|
||||||
|
|
||||||
c->x = half_w;
|
c->x = half_w;
|
||||||
|
|||||||
+38
-5
@@ -33,7 +33,7 @@ static void check_snap(WM *wm, Client *c) {
|
|||||||
} else {
|
} else {
|
||||||
client_snap_left(wm, c);
|
client_snap_left(wm, c);
|
||||||
}
|
}
|
||||||
} else if (c->x + (int)(c->w + BORDER_W * 2) >= sw - 185 - SNAP_DIST) {
|
} else if (c->x + (int)(c->w + BORDER_W * 2) >= sw - DESKBAR_W - SNAP_DIST) {
|
||||||
client_snap_right(wm, c);
|
client_snap_right(wm, c);
|
||||||
} else if (c->y <= SNAP_DIST) {
|
} else if (c->y <= SNAP_DIST) {
|
||||||
client_maximize(wm, c);
|
client_maximize(wm, c);
|
||||||
@@ -95,10 +95,34 @@ void on_enter_notify(WM *wm, XCrossingEvent *e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void on_client_message(WM *wm, XClientMessageEvent *e) {
|
void on_client_message(WM *wm, XClientMessageEvent *e) {
|
||||||
Atom net_active = XInternAtom(wm->dpy, "_NET_ACTIVE_WINDOW", False);
|
Atom net_active = XInternAtom(wm->dpy, "_NET_ACTIVE_WINDOW", False);
|
||||||
|
Atom net_close = XInternAtom(wm->dpy, "_NET_CLOSE_WINDOW", False);
|
||||||
|
Atom wm_change_state = XInternAtom(wm->dpy, "WM_CHANGE_STATE", False);
|
||||||
|
|
||||||
if (e->message_type == net_active) {
|
if (e->message_type == net_active) {
|
||||||
Client *c = client_find_by_win(wm, e->window);
|
Client *c = client_find_by_win(wm, e->window);
|
||||||
if (c) client_restore(wm, c);
|
if (c) client_restore(wm, c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e->message_type == net_close) {
|
||||||
|
Client *c = client_find_by_win(wm, e->window);
|
||||||
|
if (c) {
|
||||||
|
XEvent ev = {0};
|
||||||
|
ev.xclient.type = ClientMessage;
|
||||||
|
ev.xclient.window = c->win;
|
||||||
|
ev.xclient.message_type = wm->wm_protocols;
|
||||||
|
ev.xclient.format = 32;
|
||||||
|
ev.xclient.data.l[0] = wm->wm_delete_window;
|
||||||
|
ev.xclient.data.l[1] = CurrentTime;
|
||||||
|
XSendEvent(wm->dpy, c->win, False, NoEventMask, &ev);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* WM_CHANGE_STATE IconicState → minimize */
|
||||||
|
if (e->message_type == wm_change_state && e->data.l[0] == 3) {
|
||||||
|
Client *c = client_find_by_win(wm, e->window);
|
||||||
|
if (c && !c->minimized) client_minimize(wm, c);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,10 +287,19 @@ void on_key_press(WM *wm, XKeyEvent *e) {
|
|||||||
if ((e->state & Mod1Mask) && sym == XK_F4 && wm->focused)
|
if ((e->state & Mod1Mask) && sym == XK_F4 && wm->focused)
|
||||||
send_delete(wm, wm->focused->win);
|
send_delete(wm, wm->focused->win);
|
||||||
|
|
||||||
/* Alt+Tab — visual switcher */
|
/* Alt+Tab / Shift+Alt+Tab — visual switcher */
|
||||||
if ((e->state & Mod1Mask) && sym == XK_Tab) {
|
if ((e->state & Mod1Mask) && sym == XK_Tab) {
|
||||||
if (wm->swl_visible) switcher_next(wm);
|
int backward = (e->state & ShiftMask) != 0;
|
||||||
else if (wm->clients) switcher_show(wm);
|
if (wm->swl_visible) {
|
||||||
|
if (backward) switcher_prev(wm);
|
||||||
|
else switcher_next(wm);
|
||||||
|
} else if (wm->clients) {
|
||||||
|
switcher_show(wm);
|
||||||
|
if (backward && wm->swl_visible) {
|
||||||
|
wm->swl_idx = wm->swl_count - 1;
|
||||||
|
switcher_draw(wm);
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+34
-12
@@ -8,17 +8,19 @@
|
|||||||
|
|
||||||
static void swl_populate(WM *wm) {
|
static void swl_populate(WM *wm) {
|
||||||
wm->swl_count = 0;
|
wm->swl_count = 0;
|
||||||
/* current focused window goes first */
|
/* current focused (non-minimized) window goes first */
|
||||||
if (wm->focused && !wm->focused->minimized) {
|
if (wm->focused && !wm->focused->minimized) {
|
||||||
wm->swl_wins[0] = wm->focused->win;
|
wm->swl_wins[0] = wm->focused->win;
|
||||||
|
wm->swl_mini[0] = 0;
|
||||||
strncpy(wm->swl_titles[0], wm->focused->title, 127);
|
strncpy(wm->swl_titles[0], wm->focused->title, 127);
|
||||||
wm->swl_titles[0][127] = '\0';
|
wm->swl_titles[0][127] = '\0';
|
||||||
wm->swl_count = 1;
|
wm->swl_count = 1;
|
||||||
}
|
}
|
||||||
|
/* all other windows, including minimized ones */
|
||||||
for (Client *c = wm->clients; c && wm->swl_count < 64; c = c->next) {
|
for (Client *c = wm->clients; c && wm->swl_count < 64; c = c->next) {
|
||||||
if (c->minimized) continue;
|
|
||||||
if (wm->focused && c == wm->focused) continue;
|
if (wm->focused && c == wm->focused) continue;
|
||||||
wm->swl_wins[wm->swl_count] = c->win;
|
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);
|
strncpy(wm->swl_titles[wm->swl_count], c->title, 127);
|
||||||
wm->swl_titles[wm->swl_count][127] = '\0';
|
wm->swl_titles[wm->swl_count][127] = '\0';
|
||||||
wm->swl_count++;
|
wm->swl_count++;
|
||||||
@@ -61,29 +63,34 @@ void switcher_draw(WM *wm) {
|
|||||||
for (int vi = 0; vi < vis; vi++) {
|
for (int vi = 0; vi < vis; vi++) {
|
||||||
int i = vstart + vi;
|
int i = vstart + vi;
|
||||||
int selected = (i == wm->swl_idx);
|
int selected = (i == wm->swl_idx);
|
||||||
|
int mini = wm->swl_mini[i];
|
||||||
int ix = SWL_PAD + vi * (SWL_ITEM_W + SWL_PAD);
|
int ix = SWL_PAD + vi * (SWL_ITEM_W + SWL_PAD);
|
||||||
int iy = SWL_PAD;
|
int iy = SWL_PAD;
|
||||||
|
|
||||||
unsigned long card_bg = color_alloc(wm, selected ? COLOR_TITLE_ACTIVE : 0x1C2338);
|
unsigned long card_bg = color_alloc(wm,
|
||||||
|
selected ? COLOR_TITLE_ACTIVE : (mini ? 0x141820 : 0x1C2338));
|
||||||
XSetForeground(wm->dpy, wm->gc, card_bg);
|
XSetForeground(wm->dpy, wm->gc, card_bg);
|
||||||
XFillRectangle(wm->dpy, wm->swl_win, wm->gc, ix, iy, SWL_ITEM_W, SWL_ITEM_H);
|
XFillRectangle(wm->dpy, wm->swl_win, wm->gc, ix, iy, SWL_ITEM_W, SWL_ITEM_H);
|
||||||
|
|
||||||
XSetForeground(wm->dpy, wm->gc,
|
XSetForeground(wm->dpy, wm->gc,
|
||||||
color_alloc(wm, selected ? COLOR_BORDER_LIGHT : 0x242C3E));
|
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);
|
XDrawRectangle(wm->dpy, wm->swl_win, wm->gc, ix, iy, SWL_ITEM_W - 1, SWL_ITEM_H - 1);
|
||||||
|
|
||||||
/* letter icon */
|
/* letter icon — dimmed for minimized */
|
||||||
int icon_x = ix + (SWL_ITEM_W - SWL_ICON_SZ) / 2;
|
int icon_x = ix + (SWL_ITEM_W - SWL_ICON_SZ) / 2;
|
||||||
int icon_y = iy + 8;
|
int icon_y = iy + 8;
|
||||||
XSetForeground(wm->dpy, wm->gc,
|
XSetForeground(wm->dpy, wm->gc,
|
||||||
color_alloc(wm, selected ? 0x285898 : 0x182538));
|
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);
|
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' };
|
char letter[2] = { wm->swl_titles[i][0] ? wm->swl_titles[i][0] : '?', '\0' };
|
||||||
if (letter[0] >= 'a' && letter[0] <= 'z') letter[0] -= 32;
|
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;
|
XftColor icon_col;
|
||||||
xft_color(wm, selected ? COLOR_TITLE_TEXT_ACT : COLOR_TITLE_TEXT_INACT, &icon_col);
|
xft_color(wm, icon_rgb, &icon_col);
|
||||||
XGlyphInfo ext;
|
XGlyphInfo ext;
|
||||||
XftTextExtentsUtf8(wm->dpy, wm->font_title, (const FcChar8 *)letter, 1, &ext);
|
XftTextExtentsUtf8(wm->dpy, wm->font_title, (const FcChar8 *)letter, 1, &ext);
|
||||||
int lx = icon_x + (SWL_ICON_SZ - (int)ext.width) / 2;
|
int lx = icon_x + (SWL_ICON_SZ - (int)ext.width) / 2;
|
||||||
@@ -91,10 +98,19 @@ void switcher_draw(WM *wm) {
|
|||||||
XftDrawStringUtf8(xd, &icon_col, wm->font_title, lx, ly, (const FcChar8 *)letter, 1);
|
XftDrawStringUtf8(xd, &icon_col, wm->font_title, lx, ly, (const FcChar8 *)letter, 1);
|
||||||
XftColorFree(wm->dpy, wm->visual, wm->cmap, &icon_col);
|
XftColorFree(wm->dpy, wm->visual, wm->cmap, &icon_col);
|
||||||
|
|
||||||
/* title below icon */
|
/* title below icon — prefix [..] for minimized */
|
||||||
const char *title = wm->swl_titles[i];
|
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;
|
XftColor title_col;
|
||||||
xft_color(wm, selected ? COLOR_TITLE_TEXT_ACT : COLOR_TITLE_TEXT_INACT, &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;
|
XGlyphInfo text_ext;
|
||||||
XftTextExtentsUtf8(wm->dpy, wm->font_title,
|
XftTextExtentsUtf8(wm->dpy, wm->font_title,
|
||||||
(const FcChar8 *)title, strlen(title), &text_ext);
|
(const FcChar8 *)title, strlen(title), &text_ext);
|
||||||
@@ -163,6 +179,12 @@ void switcher_next(WM *wm) {
|
|||||||
switcher_draw(wm);
|
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) {
|
void switcher_hide(WM *wm, int commit) {
|
||||||
if (!wm->swl_visible) return;
|
if (!wm->swl_visible) return;
|
||||||
XUngrabKeyboard(wm->dpy, CurrentTime);
|
XUngrabKeyboard(wm->dpy, CurrentTime);
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ void wm_init(WM *wm) {
|
|||||||
/* global key grabs */
|
/* global key grabs */
|
||||||
struct { KeySym sym; unsigned mod; } grabs[] = {
|
struct { KeySym sym; unsigned mod; } grabs[] = {
|
||||||
{ XK_Tab, Mod1Mask },
|
{ XK_Tab, Mod1Mask },
|
||||||
|
{ XK_Tab, Mod1Mask | ShiftMask }, /* Shift+Alt+Tab — backwards */
|
||||||
{ XK_F4, Mod1Mask },
|
{ XK_F4, Mod1Mask },
|
||||||
{ XK_F9, Mod1Mask }, /* minimize */
|
{ XK_F9, Mod1Mask }, /* minimize */
|
||||||
{ XK_F10, Mod1Mask }, /* maximize */
|
{ XK_F10, Mod1Mask }, /* maximize */
|
||||||
|
|||||||
Reference in new issue
Block a user