Add 6 desktop features: workspace shortcuts, Exposé, deskbar icons, notifications, screen lock, move-to-desktop
- Super+1-4: switch virtual desktop; Super+Shift+1-4: move focused window to desktop N - Super+Space: Exposé window overview (tiled grid), click to select, Escape to cancel - wm/src/expose.c: expose_show/expose_hide using ceil(sqrt) grid layout with saved/restored geometry - client_move_to_desktop(): moves window between desktops, fires _NET_WM_DESKTOP update - on_client_message: handles _NET_WM_DESKTOP to move windows from external requests - deskbar: optional imlib2 app icon rendering (Papirus-Dark → Papirus → hicolor → pixmaps fallback) - deskbar: XGetClassHint populates cls field for better icon lookup - deskbar: context menu extended with separator + "→ Desktop 1-4" items via _NET_WM_DESKTOP - orionnotify/: freedesktop.org notification daemon (libdbus-1), stacks up to 5 popups top-right - orionlock/: PAM-authenticated fullscreen screen lock with clock, date, password dots - Lock Screen menu item wired to orionlock - Root Makefile includes orionnotify and orionlock targets Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHiBQKPfUoaJgCKuA9op1H
This commit is contained in:
14 files changed
+977
-31
No files matched your search
@@ -7,6 +7,14 @@ ifeq ($(PKG_XFT_C),)
|
||||
PKG_XFT_L := -L/opt/homebrew/lib -lXft -lfontconfig
|
||||
endif
|
||||
|
||||
# optional imlib2 for app icon rendering
|
||||
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 -Iinclude $(PKG_XFT_C)
|
||||
LDFLAGS = $(PKG_XFT_L)
|
||||
|
||||
|
||||
@@ -45,10 +45,12 @@
|
||||
#define DB_MAX_APPS 32
|
||||
|
||||
/* ── per-app context menu ────────────────────────────────────────────── */
|
||||
#define DB_CTX_W 140 /* context menu width */
|
||||
#define DB_CTX_W 160 /* 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 */
|
||||
#define DB_CTX_N 3 /* base items: Raise, Minimize, Close */
|
||||
#define DB_CTX_NDESK 4 /* number of "→ Desktop N" items */
|
||||
#define DB_CTX_SEP_H 6 /* separator line height */
|
||||
|
||||
/* ── fly-out menu ────────────────────────────────────────────────────── */
|
||||
#define DB_FLYOUT_W 196 /* fly-out menu panel width */
|
||||
@@ -102,8 +104,10 @@ static const unsigned long DB_ICON_COLORS[] = {
|
||||
typedef struct AppEntry {
|
||||
Window win;
|
||||
char title[64];
|
||||
char cls[64]; /* WM_CLASS instance for icon lookup */
|
||||
int focused;
|
||||
int minimized;
|
||||
int desktop; /* which desktop this window is on */
|
||||
} AppEntry;
|
||||
|
||||
typedef struct Deskbar {
|
||||
|
||||
+137
-21
@@ -1,5 +1,9 @@
|
||||
#include "deskbar.h"
|
||||
|
||||
#ifdef HAVE_IMLIB2
|
||||
#include <Imlib2.h>
|
||||
#endif
|
||||
|
||||
#define TRAY_REQUEST_DOCK 0
|
||||
#define XEMBED_EMBEDDED_NOTIFY 0
|
||||
#define XEMBED_VERSION 0
|
||||
@@ -141,6 +145,24 @@ void db_update_apps(Deskbar *db) {
|
||||
ewmh_get_title(db, wins[i],
|
||||
db->apps[db->napps].title,
|
||||
sizeof(db->apps[db->napps].title));
|
||||
|
||||
/* WM_CLASS instance for icon lookup */
|
||||
db->apps[db->napps].cls[0] = '\0';
|
||||
{
|
||||
XClassHint ch = {NULL, NULL};
|
||||
if (XGetClassHint(db->dpy, wins[i], &ch)) {
|
||||
if (ch.res_name) {
|
||||
strncpy(db->apps[db->napps].cls, ch.res_name,
|
||||
sizeof(db->apps[db->napps].cls) - 1);
|
||||
XFree(ch.res_name);
|
||||
}
|
||||
if (ch.res_class) XFree(ch.res_class);
|
||||
}
|
||||
}
|
||||
|
||||
/* store which desktop this window is on */
|
||||
db->apps[db->napps].desktop = (int)win_desk;
|
||||
|
||||
if (db->apps[db->napps].title[0] != '\0')
|
||||
db->napps++;
|
||||
}
|
||||
@@ -426,12 +448,52 @@ void db_draw(Deskbar *db) {
|
||||
XSetForeground(db->dpy, db->gc, row_bg);
|
||||
XFillRectangle(db->dpy, db->win, db->gc, 0, iy, DB_W, DB_ITEM_H);
|
||||
|
||||
/* letter icon */
|
||||
unsigned long icon_bg = db_app_icon_color(db->apps[i].title);
|
||||
/* icon: imlib2 if available, else letter-square */
|
||||
int icon_x = 5;
|
||||
int icon_y = iy + (DB_ITEM_H - DB_ICON_SZ) / 2;
|
||||
draw_letter_icon(db, xd, icon_x, icon_y,
|
||||
db->apps[i].title, icon_bg, mini);
|
||||
int icon_drawn = 0;
|
||||
#ifdef HAVE_IMLIB2
|
||||
{
|
||||
static const char *dirs[] = {
|
||||
"/usr/share/icons/Papirus-Dark/22x22/apps",
|
||||
"/usr/share/icons/Papirus/22x22/apps",
|
||||
"/usr/share/icons/hicolor/22x22/apps",
|
||||
"/usr/share/icons/hicolor/32x32/apps",
|
||||
"/usr/share/pixmaps",
|
||||
NULL
|
||||
};
|
||||
const char *names[2] = { db->apps[i].cls, db->apps[i].title };
|
||||
for (int ni = 0; ni < 2 && !icon_drawn; ni++) {
|
||||
if (!names[ni][0]) continue;
|
||||
for (int di = 0; dirs[di] && !icon_drawn; di++) {
|
||||
char path[512];
|
||||
const char *exts[] = { "png", "svg", "xpm", NULL };
|
||||
for (int ei = 0; exts[ei] && !icon_drawn; ei++) {
|
||||
snprintf(path, sizeof(path), "%s/%s.%s",
|
||||
dirs[di], names[ni], exts[ei]);
|
||||
imlib_context_set_display(db->dpy);
|
||||
imlib_context_set_visual(db->visual);
|
||||
imlib_context_set_colormap(db->cmap);
|
||||
imlib_context_set_drawable(db->win);
|
||||
Imlib_Image img = imlib_load_image_immediately(path);
|
||||
if (img) {
|
||||
imlib_context_set_image(img);
|
||||
imlib_context_set_blend(1);
|
||||
imlib_render_image_on_drawable_at_size(
|
||||
icon_x, icon_y, DB_ICON_SZ, DB_ICON_SZ);
|
||||
imlib_free_image();
|
||||
icon_drawn = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!icon_drawn) {
|
||||
unsigned long icon_bg = db_app_icon_color(db->apps[i].title);
|
||||
draw_letter_icon(db, xd, icon_x, icon_y,
|
||||
db->apps[i].title, icon_bg, mini);
|
||||
}
|
||||
|
||||
/* app name */
|
||||
int name_x = icon_x + DB_ICON_SZ + 6;
|
||||
@@ -584,7 +646,7 @@ static const MI MENU[] = {
|
||||
{ "Settings", NULL, 0, 1, 1 },
|
||||
{ NULL, NULL, 1, 0,-1 },
|
||||
{ "About OrionOS", "alacritty -e bash -c 'fastfetch; read' &", 0, 0,-1 },
|
||||
{ "Lock Screen", "xscreensaver-command -lock &", 0, 0,-1 },
|
||||
{ "Lock Screen", "orionlock &", 0, 0,-1 },
|
||||
{ NULL, NULL, 1, 0,-1 },
|
||||
{ "Restart", "systemctl reboot", 0, 0,-1 },
|
||||
{ "Shut Down", "systemctl poweroff", 0, 0,-1 },
|
||||
@@ -818,7 +880,28 @@ static void handle_menu_press(Deskbar *db, int rx, int ry) {
|
||||
/* ── per-app context menu ─────────────────────────────────────────────── */
|
||||
|
||||
static int ctx_total_h(void) {
|
||||
return DB_CTX_PAD + DB_CTX_N * DB_CTX_ITEM_H + DB_CTX_PAD;
|
||||
return DB_CTX_PAD + DB_CTX_N * DB_CTX_ITEM_H
|
||||
+ DB_CTX_SEP_H + DB_CTX_NDESK * DB_CTX_ITEM_H
|
||||
+ DB_CTX_PAD;
|
||||
}
|
||||
|
||||
/* y offset of a context-menu item (base items, then sep, then desk items) */
|
||||
static int ctx_item_y(int idx) {
|
||||
if (idx < DB_CTX_N)
|
||||
return DB_CTX_PAD + idx * DB_CTX_ITEM_H;
|
||||
/* desktop items come after separator */
|
||||
return DB_CTX_PAD + DB_CTX_N * DB_CTX_ITEM_H
|
||||
+ DB_CTX_SEP_H + (idx - DB_CTX_N) * DB_CTX_ITEM_H;
|
||||
}
|
||||
|
||||
static int ctx_item_at(int lx) {
|
||||
/* lx = ry - ctx_win_y */
|
||||
int total = DB_CTX_N + DB_CTX_NDESK;
|
||||
for (int i = 0; i < total; i++) {
|
||||
int iy = ctx_item_y(i);
|
||||
if (lx >= iy && lx < iy + DB_CTX_ITEM_H) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void db_ctx_draw(Deskbar *db) {
|
||||
@@ -827,6 +910,8 @@ static void db_ctx_draw(Deskbar *db) {
|
||||
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;
|
||||
int cur_desk = (db->ctx_app_idx >= 0 && db->ctx_app_idx < db->napps)
|
||||
? db->apps[db->ctx_app_idx].desktop : -1;
|
||||
|
||||
static const char *labels_normal[] = { "Raise", "Minimize", "Close" };
|
||||
static const char *labels_mini[] = { "Restore", "Restore", "Close" };
|
||||
@@ -842,8 +927,9 @@ static void db_ctx_draw(Deskbar *db) {
|
||||
XSetForeground(db->dpy, db->gc, db_color(db, 0x505050));
|
||||
XDrawRectangle(db->dpy, db->ctx_win, db->gc, 0, 0, ww - 1, wh - 1);
|
||||
|
||||
/* base items */
|
||||
for (int i = 0; i < DB_CTX_N; i++) {
|
||||
int iy = DB_CTX_PAD + i * DB_CTX_ITEM_H;
|
||||
int iy = ctx_item_y(i);
|
||||
|
||||
if (i == db->ctx_hov) {
|
||||
XSetForeground(db->dpy, db->gc, db_color(db, 0x4A90D9));
|
||||
@@ -851,15 +937,42 @@ static void db_ctx_draw(Deskbar *db) {
|
||||
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;
|
||||
unsigned long tc = (i == db->ctx_hov) ? 0xFFFFFF
|
||||
: (i == DB_CTX_N - 1) ? 0xFF6B6B
|
||||
: 0xE8E8E8;
|
||||
draw_str(db, xd, db->font_ui, tc,
|
||||
14, str_baseline(db->font_ui, iy, DB_CTX_ITEM_H),
|
||||
labels[i], 0, 0);
|
||||
}
|
||||
|
||||
/* separator */
|
||||
{
|
||||
int sep_y = DB_CTX_PAD + DB_CTX_N * DB_CTX_ITEM_H + DB_CTX_SEP_H / 2;
|
||||
XSetForeground(db->dpy, db->gc, db_color(db, 0x505050));
|
||||
XDrawLine(db->dpy, db->ctx_win, db->gc, 8, sep_y, ww - 9, sep_y);
|
||||
}
|
||||
|
||||
/* "→ Desktop N" items */
|
||||
for (int d = 0; d < DB_CTX_NDESK; d++) {
|
||||
int item_idx = DB_CTX_N + d;
|
||||
int iy = ctx_item_y(item_idx);
|
||||
|
||||
if (item_idx == 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);
|
||||
}
|
||||
|
||||
char label[32];
|
||||
snprintf(label, sizeof(label), "\xe2\x86\x92 Desktop %d", d + 1);
|
||||
unsigned long tc = (item_idx == db->ctx_hov) ? 0xFFFFFF
|
||||
: (d == cur_desk) ? 0x88AACC /* current = dimmed */
|
||||
: 0xE8E8E8;
|
||||
draw_str(db, xd, db->font_ui, tc,
|
||||
14, str_baseline(db->font_ui, iy, DB_CTX_ITEM_H),
|
||||
label, 0, 0);
|
||||
}
|
||||
|
||||
XftDrawDestroy(xd);
|
||||
XFlush(db->dpy);
|
||||
}
|
||||
@@ -912,9 +1025,7 @@ static void db_ctx_handle_motion(Deskbar *db, int rx, int ry) {
|
||||
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;
|
||||
int idx = ctx_item_at(ry - cy);
|
||||
if (idx != db->ctx_hov) { db->ctx_hov = idx; db_ctx_draw(db); }
|
||||
}
|
||||
|
||||
@@ -931,13 +1042,12 @@ static void db_ctx_handle_press(Deskbar *db, int rx, int ry) {
|
||||
return;
|
||||
}
|
||||
|
||||
int lx = ry - cy - DB_CTX_PAD;
|
||||
int idx = (lx >= 0) ? lx / DB_CTX_ITEM_H : -1;
|
||||
int idx = ctx_item_at(ry - cy);
|
||||
Window target = db->apps[app_idx].win;
|
||||
int mini = db->apps[app_idx].minimized;
|
||||
int mini = db->apps[app_idx].minimized;
|
||||
db_ctx_close(db);
|
||||
|
||||
if (idx < 0 || idx >= DB_CTX_N) return;
|
||||
if (idx < 0) return;
|
||||
|
||||
XEvent ev = {0};
|
||||
ev.xclient.type = ClientMessage;
|
||||
@@ -953,13 +1063,11 @@ static void db_ctx_handle_press(Deskbar *db, int rx, int ry) {
|
||||
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 */
|
||||
@@ -967,13 +1075,21 @@ static void db_ctx_handle_press(Deskbar *db, int rx, int ry) {
|
||||
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);
|
||||
} else if (idx >= DB_CTX_N && idx < DB_CTX_N + DB_CTX_NDESK) {
|
||||
/* Move to Desktop N via _NET_WM_DESKTOP */
|
||||
int new_desk = idx - DB_CTX_N;
|
||||
ev.xclient.window = target;
|
||||
ev.xclient.message_type = XInternAtom(db->dpy, "_NET_WM_DESKTOP", False);
|
||||
ev.xclient.data.l[0] = new_desk;
|
||||
ev.xclient.data.l[1] = 2;
|
||||
XSendEvent(db->dpy, db->root, False,
|
||||
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user