Fixes #7–#11: launcher Other tab, ICCCM size hints, icon loading, multi-desktop

Fix #7: Add "Other" catch-all category to the launcher. map_category() now
returns "Other" instead of "" for unrecognized .desktop Categories, and an
"Other" tab is added to the CATS list so those apps are discoverable instead
of silently hidden.

Fix #8: Respect ICCCM WM_NORMAL_HINTS. client_add() reads PMinSize,
PResizeInc, and PBaseSize from the window's size hints. The resize drag
handler in events.c clamps to min_w/min_h and snaps to the
width_inc/height_inc grid, fixing jank in character-cell terminal emulators
(xterm, Alacritty).

Fix #10: Optional freedesktop icon loading via imlib2. The launcher now
parses the Icon= field from .desktop files and attempts to render a PNG icon
from Papirus-Dark (or hicolor fallback). When imlib2 is not present the
existing letter-square is shown unchanged. launcher/Makefile auto-detects
imlib2 via pkg-config and sets -DHAVE_IMLIB2.

Fix #11: True 4-desktop support in the WM. Each Client carries a desktop
field (assigned at map time from wm->cur_desktop). Clicking a workspace
button in the deskbar sends _NET_CURRENT_DESKTOP; the WM now handles it via
wm_switch_desktop() which unmaps frames on the leaving desktop, maps frames
on the arriving desktop, and re-focuses. _NET_WM_DESKTOP is set per window
so the deskbar can filter db_update_apps() to show only current-desktop
windows (sticky windows with desktop 0xFFFFFFFF are always shown).
This commit is contained in:
Claude committed 2026-08-30 20:00:10 +00:00
1 parent 36641e77e2
commit b0f64d9b2e
7 files changed
+173 -16

No files matched your search

+8
View File
@@ -7,6 +7,14 @@ ifeq ($(PKG_XFT_C),)
PKG_XFT_L := -L/opt/homebrew/lib -lXft -lfontconfig
endif
# Optional: imlib2 for freedesktop icon loading
PKG_IL_C := $(shell pkg-config --cflags imlib2 2>/dev/null)
PKG_IL_L := $(shell pkg-config --libs imlib2 2>/dev/null)
ifneq ($(PKG_IL_C),)
PKG_XFT_C += $(PKG_IL_C) -DHAVE_IMLIB2
PKG_XFT_L += $(PKG_IL_L)
endif
CFLAGS = -Wall -Wextra -O2 $(PKG_XFT_C)
LDFLAGS = $(PKG_XFT_L)
+51 -10
View File
@@ -58,13 +58,13 @@ static const unsigned long ICON_BG[] = {
#define FONT_ICON "Sans:size=13:weight=bold"
#define MAX_FLT 64
typedef struct { char name[64]; char cmd[256]; char cat[32]; } App;
typedef struct { char name[64]; char cmd[256]; char cat[32]; char icon[64]; } App;
static App APPS[MAX_APPS];
static int NAPP = 0;
static const char *CATS[] = {
"All", "Internet", "Media", "Office", "System", "Settings"
"All", "Internet", "Media", "Office", "System", "Settings", "Other"
};
#define NCAT ((int)(sizeof(CATS)/sizeof(CATS[0])))
@@ -89,7 +89,7 @@ static const char *map_category(const char *cats) {
strstr(cats, "FileManager") || strstr(cats, "Utility") ||
strstr(cats, "Monitor") || strstr(cats, "Security"))
return "System";
return "";
return "Other";
}
static void clean_exec(char *dst, const char *src, size_t dstsz) {
@@ -108,7 +108,7 @@ static void load_desktop_file(const char *path) {
FILE *f = fopen(path, "r");
if (!f) return;
char name[64] = {0}, exec[256] = {0}, cats[128] = {0};
char name[64] = {0}, exec[256] = {0}, cats[128] = {0}, icon[64] = {0};
int in_entry = 0, nodisplay = 0, is_app = 0;
char line[512];
@@ -126,6 +126,8 @@ static void load_desktop_file(const char *path) {
snprintf(exec, sizeof(exec), "%s", line + 5);
else if (!cats[0] && strncmp(line, "Categories=", 11) == 0)
snprintf(cats, sizeof(cats), "%s", line + 11);
else if (!icon[0] && strncmp(line, "Icon=", 5) == 0)
snprintf(icon, sizeof(icon), "%s", line + 5);
else if (strncmp(line, "NoDisplay=true", 14) == 0)
nodisplay = 1;
else if (strncmp(line, "Type=Application", 16) == 0)
@@ -137,6 +139,7 @@ static void load_desktop_file(const char *path) {
snprintf(APPS[NAPP].name, 64, "%s", name);
clean_exec(APPS[NAPP].cmd, exec, 256);
snprintf(APPS[NAPP].cat, 32, "%s", map_category(cats));
snprintf(APPS[NAPP].icon, 64, "%s", icon);
NAPP++;
}
@@ -292,6 +295,39 @@ static int card_at_vis(int x, int y) {
return row * COLS + col;
}
/* ── icon loading (imlib2, optional) ─────────────────────────────────── */
#ifdef HAVE_IMLIB2
#include <Imlib2.h>
static const char *ICON_DIRS[] = {
"/usr/share/icons/Papirus-Dark/48x48/apps",
"/usr/share/icons/Papirus/48x48/apps",
"/usr/share/icons/hicolor/48x48/apps",
"/usr/share/pixmaps",
NULL
};
static int render_icon(L *l, const char *icon_name, int ix, int iy, int sz) {
if (!icon_name || !icon_name[0]) return 0;
char path[512];
for (int i = 0; ICON_DIRS[i]; i++) {
snprintf(path, sizeof(path), "%s/%s.png", ICON_DIRS[i], icon_name);
if (access(path, R_OK) == 0) goto found;
}
return 0;
found:;
imlib_context_set_display(l->dpy);
imlib_context_set_visual(l->visual);
imlib_context_set_colormap(l->cmap);
imlib_context_set_drawable(l->win);
imlib_context_set_blend(1);
Imlib_Image img = imlib_load_image_immediately(path);
if (!img) return 0;
imlib_context_set_image(img);
imlib_render_image_on_drawable_at_size(ix, iy, sz, sz);
imlib_free_image();
return 1;
}
#endif
/* ── draw ────────────────────────────────────────────────────────────── */
static void draw(L *l) {
XftDraw *xd = XftDrawCreate(l->dpy, l->win, l->visual, l->cmap);
@@ -382,12 +418,17 @@ static void draw(L *l) {
for (const char *p = APPS[app_i].name; *p; p++) h = h * 31 + (unsigned char)*p;
int ix = cx + (CARD_W - ICON_SZ) / 2;
int iy = cy + 10;
XSetForeground(l->dpy, l->gc, mkc(l, ICON_BG[h % NICON]));
XFillRectangle(l->dpy, l->win, l->gc, ix, iy, ICON_SZ, ICON_SZ);
char letter[2] = { APPS[app_i].name[0], '\0' };
if (letter[0] >= 'a' && letter[0] <= 'z') letter[0] -= 32;
ds_ctr(l, xd, l->font_icon, 0xEAF0FF, ix, iy, ICON_SZ, ICON_SZ, letter);
int icon_rendered = 0;
#ifdef HAVE_IMLIB2
icon_rendered = render_icon(l, APPS[app_i].icon, ix, iy, ICON_SZ);
#endif
if (!icon_rendered) {
XSetForeground(l->dpy, l->gc, mkc(l, ICON_BG[h % NICON]));
XFillRectangle(l->dpy, l->win, l->gc, ix, iy, ICON_SZ, ICON_SZ);
char letter[2] = { APPS[app_i].name[0], '\0' };
if (letter[0] >= 'a' && letter[0] <= 'z') letter[0] -= 32;
ds_ctr(l, xd, l->font_icon, 0xEAF0FF, ix, iy, ICON_SZ, ICON_SZ, letter);
}
char lbl[14];
snprintf(lbl, sizeof(lbl), "%.12s", APPS[app_i].name);