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
@@ -5,9 +5,9 @@
|
|||||||
# make clean — remove build artifacts
|
# make clean — remove build artifacts
|
||||||
# make iso — build the bootable ISO (requires Arch Linux + archiso)
|
# make iso — build the bootable ISO (requires Arch Linux + archiso)
|
||||||
|
|
||||||
.PHONY: all wm deskbar launcher installer nebula orionpkg skykit rigel quill lens clean install iso
|
.PHONY: all wm deskbar launcher installer nebula orionpkg skykit rigel quill lens orionnotify orionlock clean install iso
|
||||||
|
|
||||||
all: wm deskbar launcher installer nebula orionpkg skykit rigel quill lens
|
all: wm deskbar launcher installer nebula orionpkg skykit rigel quill lens orionnotify orionlock
|
||||||
|
|
||||||
wm:
|
wm:
|
||||||
$(MAKE) -C wm
|
$(MAKE) -C wm
|
||||||
@@ -39,6 +39,12 @@ quill:
|
|||||||
lens:
|
lens:
|
||||||
$(MAKE) -C lens
|
$(MAKE) -C lens
|
||||||
|
|
||||||
|
orionnotify:
|
||||||
|
$(MAKE) -C orionnotify
|
||||||
|
|
||||||
|
orionlock:
|
||||||
|
$(MAKE) -C orionlock
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(MAKE) -C wm clean
|
$(MAKE) -C wm clean
|
||||||
$(MAKE) -C deskbar clean
|
$(MAKE) -C deskbar clean
|
||||||
@@ -49,7 +55,9 @@ clean:
|
|||||||
$(MAKE) -C skykit clean
|
$(MAKE) -C skykit clean
|
||||||
$(MAKE) -C rigel clean
|
$(MAKE) -C rigel clean
|
||||||
$(MAKE) -C quill clean
|
$(MAKE) -C quill clean
|
||||||
$(MAKE) -C lens clean
|
$(MAKE) -C lens clean
|
||||||
|
$(MAKE) -C orionnotify clean
|
||||||
|
$(MAKE) -C orionlock clean
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
# binaries
|
# binaries
|
||||||
@@ -62,6 +70,8 @@ install: all
|
|||||||
install -Dm755 rigel/rigel /usr/local/bin/rigel
|
install -Dm755 rigel/rigel /usr/local/bin/rigel
|
||||||
install -Dm755 quill/quill /usr/local/bin/quill
|
install -Dm755 quill/quill /usr/local/bin/quill
|
||||||
install -Dm755 lens/lens /usr/local/bin/lens
|
install -Dm755 lens/lens /usr/local/bin/lens
|
||||||
|
install -Dm755 orionnotify/orionnotify /usr/local/bin/orionnotify
|
||||||
|
install -Dm755 orionlock/orionlock /usr/local/bin/orionlock
|
||||||
# GTK theme
|
# GTK theme
|
||||||
mkdir -p /usr/share/themes
|
mkdir -p /usr/share/themes
|
||||||
cp -r themes/OrionOS /usr/share/themes/
|
cp -r themes/OrionOS /usr/share/themes/
|
||||||
@@ -77,6 +87,7 @@ install: all
|
|||||||
@echo " orionwallpaper"
|
@echo " orionwallpaper"
|
||||||
@echo " picom --config /etc/orionos/picom.conf &"
|
@echo " picom --config /etc/orionos/picom.conf &"
|
||||||
@echo " oriondeskbar &"
|
@echo " oriondeskbar &"
|
||||||
|
@echo " orionnotify &"
|
||||||
@echo " exec orionwm"
|
@echo " exec orionwm"
|
||||||
|
|
||||||
iso:
|
iso:
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ ifeq ($(PKG_XFT_C),)
|
|||||||
PKG_XFT_L := -L/opt/homebrew/lib -lXft -lfontconfig
|
PKG_XFT_L := -L/opt/homebrew/lib -lXft -lfontconfig
|
||||||
endif
|
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)
|
CFLAGS = -Wall -Wextra -O2 -Iinclude $(PKG_XFT_C)
|
||||||
LDFLAGS = $(PKG_XFT_L)
|
LDFLAGS = $(PKG_XFT_L)
|
||||||
|
|
||||||
|
|||||||
@@ -45,10 +45,12 @@
|
|||||||
#define DB_MAX_APPS 32
|
#define DB_MAX_APPS 32
|
||||||
|
|
||||||
/* ── per-app context menu ────────────────────────────────────────────── */
|
/* ── 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_ITEM_H 26 /* item row height */
|
||||||
#define DB_CTX_PAD 4 /* top/bottom padding */
|
#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 ────────────────────────────────────────────────────── */
|
/* ── fly-out menu ────────────────────────────────────────────────────── */
|
||||||
#define DB_FLYOUT_W 196 /* fly-out menu panel width */
|
#define DB_FLYOUT_W 196 /* fly-out menu panel width */
|
||||||
@@ -102,8 +104,10 @@ static const unsigned long DB_ICON_COLORS[] = {
|
|||||||
typedef struct AppEntry {
|
typedef struct AppEntry {
|
||||||
Window win;
|
Window win;
|
||||||
char title[64];
|
char title[64];
|
||||||
|
char cls[64]; /* WM_CLASS instance for icon lookup */
|
||||||
int focused;
|
int focused;
|
||||||
int minimized;
|
int minimized;
|
||||||
|
int desktop; /* which desktop this window is on */
|
||||||
} AppEntry;
|
} AppEntry;
|
||||||
|
|
||||||
typedef struct Deskbar {
|
typedef struct Deskbar {
|
||||||
|
|||||||
+137
-21
@@ -1,5 +1,9 @@
|
|||||||
#include "deskbar.h"
|
#include "deskbar.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_IMLIB2
|
||||||
|
#include <Imlib2.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#define TRAY_REQUEST_DOCK 0
|
#define TRAY_REQUEST_DOCK 0
|
||||||
#define XEMBED_EMBEDDED_NOTIFY 0
|
#define XEMBED_EMBEDDED_NOTIFY 0
|
||||||
#define XEMBED_VERSION 0
|
#define XEMBED_VERSION 0
|
||||||
@@ -141,6 +145,24 @@ void db_update_apps(Deskbar *db) {
|
|||||||
ewmh_get_title(db, wins[i],
|
ewmh_get_title(db, wins[i],
|
||||||
db->apps[db->napps].title,
|
db->apps[db->napps].title,
|
||||||
sizeof(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')
|
if (db->apps[db->napps].title[0] != '\0')
|
||||||
db->napps++;
|
db->napps++;
|
||||||
}
|
}
|
||||||
@@ -426,12 +448,52 @@ void db_draw(Deskbar *db) {
|
|||||||
XSetForeground(db->dpy, db->gc, row_bg);
|
XSetForeground(db->dpy, db->gc, row_bg);
|
||||||
XFillRectangle(db->dpy, db->win, db->gc, 0, iy, DB_W, DB_ITEM_H);
|
XFillRectangle(db->dpy, db->win, db->gc, 0, iy, DB_W, DB_ITEM_H);
|
||||||
|
|
||||||
/* letter icon */
|
/* icon: imlib2 if available, else letter-square */
|
||||||
unsigned long icon_bg = db_app_icon_color(db->apps[i].title);
|
|
||||||
int icon_x = 5;
|
int icon_x = 5;
|
||||||
int icon_y = iy + (DB_ITEM_H - DB_ICON_SZ) / 2;
|
int icon_y = iy + (DB_ITEM_H - DB_ICON_SZ) / 2;
|
||||||
draw_letter_icon(db, xd, icon_x, icon_y,
|
int icon_drawn = 0;
|
||||||
db->apps[i].title, icon_bg, mini);
|
#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 */
|
/* app name */
|
||||||
int name_x = icon_x + DB_ICON_SZ + 6;
|
int name_x = icon_x + DB_ICON_SZ + 6;
|
||||||
@@ -584,7 +646,7 @@ static const MI MENU[] = {
|
|||||||
{ "Settings", NULL, 0, 1, 1 },
|
{ "Settings", NULL, 0, 1, 1 },
|
||||||
{ NULL, NULL, 1, 0,-1 },
|
{ NULL, NULL, 1, 0,-1 },
|
||||||
{ "About OrionOS", "alacritty -e bash -c 'fastfetch; read' &", 0, 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 },
|
{ NULL, NULL, 1, 0,-1 },
|
||||||
{ "Restart", "systemctl reboot", 0, 0,-1 },
|
{ "Restart", "systemctl reboot", 0, 0,-1 },
|
||||||
{ "Shut Down", "systemctl poweroff", 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 ─────────────────────────────────────────────── */
|
/* ── per-app context menu ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
static int ctx_total_h(void) {
|
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) {
|
static void db_ctx_draw(Deskbar *db) {
|
||||||
@@ -827,6 +910,8 @@ static void db_ctx_draw(Deskbar *db) {
|
|||||||
int wh = ctx_total_h();
|
int wh = ctx_total_h();
|
||||||
int mini = (db->ctx_app_idx >= 0 && db->ctx_app_idx < db->napps)
|
int mini = (db->ctx_app_idx >= 0 && db->ctx_app_idx < db->napps)
|
||||||
? db->apps[db->ctx_app_idx].minimized : 0;
|
? 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_normal[] = { "Raise", "Minimize", "Close" };
|
||||||
static const char *labels_mini[] = { "Restore", "Restore", "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));
|
XSetForeground(db->dpy, db->gc, db_color(db, 0x505050));
|
||||||
XDrawRectangle(db->dpy, db->ctx_win, db->gc, 0, 0, ww - 1, wh - 1);
|
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++) {
|
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) {
|
if (i == db->ctx_hov) {
|
||||||
XSetForeground(db->dpy, db->gc, db_color(db, 0x4A90D9));
|
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);
|
2, iy + 1, ww - 4, DB_CTX_ITEM_H - 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* "Close" item tinted red */
|
unsigned long tc = (i == db->ctx_hov) ? 0xFFFFFF
|
||||||
unsigned long tc = (i == db->ctx_hov) ? 0xFFFFFF
|
: (i == DB_CTX_N - 1) ? 0xFF6B6B
|
||||||
: (i == DB_CTX_N - 1) ? 0xFF6B6B /* Close = red tint */
|
: 0xE8E8E8;
|
||||||
: 0xE8E8E8;
|
|
||||||
draw_str(db, xd, db->font_ui, tc,
|
draw_str(db, xd, db->font_ui, tc,
|
||||||
14, str_baseline(db->font_ui, iy, DB_CTX_ITEM_H),
|
14, str_baseline(db->font_ui, iy, DB_CTX_ITEM_H),
|
||||||
labels[i], 0, 0);
|
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);
|
XftDrawDestroy(xd);
|
||||||
XFlush(db->dpy);
|
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); }
|
if (db->ctx_hov != -1) { db->ctx_hov = -1; db_ctx_draw(db); }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int lx = ry - cy - DB_CTX_PAD;
|
int idx = ctx_item_at(ry - cy);
|
||||||
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); }
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int lx = ry - cy - DB_CTX_PAD;
|
int idx = ctx_item_at(ry - cy);
|
||||||
int idx = (lx >= 0) ? lx / DB_CTX_ITEM_H : -1;
|
|
||||||
Window target = db->apps[app_idx].win;
|
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);
|
db_ctx_close(db);
|
||||||
|
|
||||||
if (idx < 0 || idx >= DB_CTX_N) return;
|
if (idx < 0) return;
|
||||||
|
|
||||||
XEvent ev = {0};
|
XEvent ev = {0};
|
||||||
ev.xclient.type = ClientMessage;
|
ev.xclient.type = ClientMessage;
|
||||||
@@ -953,13 +1063,11 @@ static void db_ctx_handle_press(Deskbar *db, int rx, int ry) {
|
|||||||
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
|
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
|
||||||
} else if (idx == 1) {
|
} else if (idx == 1) {
|
||||||
if (mini) {
|
if (mini) {
|
||||||
/* Restore */
|
|
||||||
ev.xclient.window = target;
|
ev.xclient.window = target;
|
||||||
ev.xclient.message_type = XInternAtom(db->dpy, "_NET_ACTIVE_WINDOW", False);
|
ev.xclient.message_type = XInternAtom(db->dpy, "_NET_ACTIVE_WINDOW", False);
|
||||||
ev.xclient.data.l[0] = 2;
|
ev.xclient.data.l[0] = 2;
|
||||||
ev.xclient.data.l[1] = CurrentTime;
|
ev.xclient.data.l[1] = CurrentTime;
|
||||||
} else {
|
} else {
|
||||||
/* Minimize via WM_CHANGE_STATE IconicState */
|
|
||||||
ev.xclient.window = target;
|
ev.xclient.window = target;
|
||||||
ev.xclient.message_type = XInternAtom(db->dpy, "WM_CHANGE_STATE", False);
|
ev.xclient.message_type = XInternAtom(db->dpy, "WM_CHANGE_STATE", False);
|
||||||
ev.xclient.data.l[0] = 3; /* IconicState */
|
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,
|
XSendEvent(db->dpy, db->root, False,
|
||||||
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
|
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
|
||||||
} else if (idx == 2) {
|
} else if (idx == 2) {
|
||||||
/* Close via _NET_CLOSE_WINDOW */
|
|
||||||
ev.xclient.window = target;
|
ev.xclient.window = target;
|
||||||
ev.xclient.message_type = XInternAtom(db->dpy, "_NET_CLOSE_WINDOW", False);
|
ev.xclient.message_type = XInternAtom(db->dpy, "_NET_CLOSE_WINDOW", False);
|
||||||
ev.xclient.data.l[0] = CurrentTime;
|
ev.xclient.data.l[0] = CurrentTime;
|
||||||
ev.xclient.data.l[1] = 2;
|
ev.xclient.data.l[1] = 2;
|
||||||
XSendEvent(db->dpy, db->root, False,
|
XSendEvent(db->dpy, db->root, False,
|
||||||
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
CC = gcc
|
||||||
|
|
||||||
|
PKG_C := $(shell pkg-config --cflags xft x11 2>/dev/null)
|
||||||
|
PKG_L := $(shell pkg-config --libs xft x11 2>/dev/null)
|
||||||
|
|
||||||
|
ifeq ($(PKG_C),)
|
||||||
|
PKG_C := -I/opt/homebrew/include
|
||||||
|
PKG_L := -L/opt/homebrew/lib -lXft -lfontconfig -lX11
|
||||||
|
endif
|
||||||
|
|
||||||
|
CFLAGS = -Wall -Wextra -O2 $(PKG_C)
|
||||||
|
LDFLAGS = $(PKG_L) -lpam
|
||||||
|
|
||||||
|
BIN = orionlock
|
||||||
|
|
||||||
|
.PHONY: all clean install
|
||||||
|
all: $(BIN)
|
||||||
|
$(BIN): orionlock.c
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||||
|
install: $(BIN)
|
||||||
|
install -Dm755 $(BIN) /usr/local/bin/$(BIN)
|
||||||
|
install -Dm644 orionlock.pam /etc/pam.d/login 2>/dev/null || true
|
||||||
|
clean:
|
||||||
|
rm -f $(BIN)
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
/*
|
||||||
|
* orionlock — PAM-authenticated fullscreen screen lock.
|
||||||
|
* Grabs keyboard + pointer, draws clock and password entry.
|
||||||
|
* On correct password, ungrabs and exits.
|
||||||
|
*/
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
#include <X11/keysym.h>
|
||||||
|
#include <X11/Xft/Xft.h>
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define COL_BG 0x000000
|
||||||
|
#define COL_CLOCK 0xFFFFFF
|
||||||
|
#define COL_DATE 0x8899AA
|
||||||
|
#define COL_DOT 0x4A90D9
|
||||||
|
#define COL_ERR 0xFF5F57
|
||||||
|
#define COL_HINT 0x555566
|
||||||
|
#define PW_MAX 256
|
||||||
|
#define DOT_R 5
|
||||||
|
#define DOT_PAD 8
|
||||||
|
|
||||||
|
static Display *dpy;
|
||||||
|
static int screen;
|
||||||
|
static Window root, lock_win;
|
||||||
|
static Visual *visual;
|
||||||
|
static Colormap cmap;
|
||||||
|
static GC gc;
|
||||||
|
static XftFont *font_clock;
|
||||||
|
static XftFont *font_date;
|
||||||
|
static XftFont *font_ui;
|
||||||
|
|
||||||
|
static char pw_buf[PW_MAX];
|
||||||
|
static int pw_len = 0;
|
||||||
|
static int auth_fail = 0;
|
||||||
|
static int auth_ok = 0;
|
||||||
|
|
||||||
|
static unsigned long alloc_color(unsigned long rgb) {
|
||||||
|
XColor c;
|
||||||
|
c.red = ((rgb >> 16) & 0xFF) * 257;
|
||||||
|
c.green = ((rgb >> 8) & 0xFF) * 257;
|
||||||
|
c.blue = ((rgb) & 0xFF) * 257;
|
||||||
|
c.flags = DoRed | DoGreen | DoBlue;
|
||||||
|
XAllocColor(dpy, cmap, &c);
|
||||||
|
return c.pixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void xft_color(unsigned long rgb, XftColor *out) {
|
||||||
|
XRenderColor rc;
|
||||||
|
rc.red = ((rgb >> 16) & 0xFF) * 257;
|
||||||
|
rc.green = ((rgb >> 8) & 0xFF) * 257;
|
||||||
|
rc.blue = ((rgb) & 0xFF) * 257;
|
||||||
|
rc.alpha = 0xFFFF;
|
||||||
|
XftColorAllocValue(dpy, visual, cmap, &rc, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_lock(void) {
|
||||||
|
int sw = DisplayWidth(dpy, screen);
|
||||||
|
int sh = DisplayHeight(dpy, screen);
|
||||||
|
|
||||||
|
XSetForeground(dpy, gc, alloc_color(COL_BG));
|
||||||
|
XFillRectangle(dpy, lock_win, gc, 0, 0, sw, sh);
|
||||||
|
|
||||||
|
XftDraw *xd = XftDrawCreate(dpy, lock_win, visual, cmap);
|
||||||
|
XftColor col;
|
||||||
|
XGlyphInfo ext;
|
||||||
|
|
||||||
|
/* clock */
|
||||||
|
{
|
||||||
|
time_t t = time(NULL);
|
||||||
|
struct tm *tm = localtime(&t);
|
||||||
|
char clock_str[16], date_str[32];
|
||||||
|
strftime(clock_str, sizeof(clock_str), "%H:%M", tm);
|
||||||
|
strftime(date_str, sizeof(date_str), "%A, %B %d", tm);
|
||||||
|
|
||||||
|
xft_color(COL_CLOCK, &col);
|
||||||
|
XftTextExtentsUtf8(dpy, font_clock, (const FcChar8 *)clock_str,
|
||||||
|
strlen(clock_str), &ext);
|
||||||
|
int cx = (sw - (int)ext.width) / 2;
|
||||||
|
int cy = sh / 2 - 100;
|
||||||
|
XftDrawStringUtf8(xd, &col, font_clock, cx, cy,
|
||||||
|
(const FcChar8 *)clock_str, strlen(clock_str));
|
||||||
|
XftColorFree(dpy, visual, cmap, &col);
|
||||||
|
|
||||||
|
xft_color(COL_DATE, &col);
|
||||||
|
XftTextExtentsUtf8(dpy, font_date, (const FcChar8 *)date_str,
|
||||||
|
strlen(date_str), &ext);
|
||||||
|
cx = (sw - (int)ext.width) / 2;
|
||||||
|
XftDrawStringUtf8(xd, &col, font_date, cx, cy + 40,
|
||||||
|
(const FcChar8 *)date_str, strlen(date_str));
|
||||||
|
XftColorFree(dpy, visual, cmap, &col);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* password dots */
|
||||||
|
{
|
||||||
|
int dots = pw_len;
|
||||||
|
if (dots > 20) dots = 20;
|
||||||
|
int total_w = dots * (DOT_R * 2 + DOT_PAD) - (dots > 0 ? DOT_PAD : 0);
|
||||||
|
int start_x = (sw - total_w) / 2;
|
||||||
|
int dot_y = sh / 2 + 10;
|
||||||
|
unsigned long dot_col = alloc_color(auth_fail ? COL_ERR : COL_DOT);
|
||||||
|
XSetForeground(dpy, gc, dot_col);
|
||||||
|
for (int i = 0; i < dots; i++) {
|
||||||
|
int dx = start_x + i * (DOT_R * 2 + DOT_PAD);
|
||||||
|
XFillArc(dpy, lock_win, gc,
|
||||||
|
dx, dot_y - DOT_R, DOT_R * 2, DOT_R * 2, 0, 360 * 64);
|
||||||
|
}
|
||||||
|
if (dots == 0 && pw_len == 0) {
|
||||||
|
/* hint: show lock icon placeholder */
|
||||||
|
xft_color(COL_HINT, &col);
|
||||||
|
const char *hint = "Type password to unlock";
|
||||||
|
XftTextExtentsUtf8(dpy, font_ui, (const FcChar8 *)hint,
|
||||||
|
strlen(hint), &ext);
|
||||||
|
int hx = (sw - (int)ext.width) / 2;
|
||||||
|
XftDrawStringUtf8(xd, &col, font_ui, hx, dot_y + DOT_R + 18,
|
||||||
|
(const FcChar8 *)hint, strlen(hint));
|
||||||
|
XftColorFree(dpy, visual, cmap, &col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* error message */
|
||||||
|
if (auth_fail) {
|
||||||
|
xft_color(COL_ERR, &col);
|
||||||
|
const char *msg = "Incorrect password";
|
||||||
|
XftTextExtentsUtf8(dpy, font_ui, (const FcChar8 *)msg,
|
||||||
|
strlen(msg), &ext);
|
||||||
|
int ex = (sw - (int)ext.width) / 2;
|
||||||
|
XftDrawStringUtf8(xd, &col, font_ui, ex, sh / 2 + 60,
|
||||||
|
(const FcChar8 *)msg, strlen(msg));
|
||||||
|
XftColorFree(dpy, visual, cmap, &col);
|
||||||
|
}
|
||||||
|
|
||||||
|
XftDrawDestroy(xd);
|
||||||
|
XFlush(dpy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── PAM ─────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static const char *pam_pw = NULL;
|
||||||
|
|
||||||
|
static int pam_conv_fn(int num_msg, const struct pam_message **msg,
|
||||||
|
struct pam_response **resp, void *appdata_ptr) {
|
||||||
|
(void)appdata_ptr;
|
||||||
|
struct pam_response *r = calloc(num_msg, sizeof(struct pam_response));
|
||||||
|
if (!r) return PAM_CONV_ERR;
|
||||||
|
for (int i = 0; i < num_msg; i++) {
|
||||||
|
if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF ||
|
||||||
|
msg[i]->msg_style == PAM_PROMPT_ECHO_ON) {
|
||||||
|
r[i].resp = strdup(pam_pw ? pam_pw : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*resp = r;
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int try_auth(const char *password) {
|
||||||
|
const char *user = getenv("USER");
|
||||||
|
if (!user) user = "root";
|
||||||
|
pam_pw = password;
|
||||||
|
|
||||||
|
struct pam_conv conv = { pam_conv_fn, NULL };
|
||||||
|
pam_handle_t *ph = NULL;
|
||||||
|
int ret = pam_start("login", user, &conv, &ph);
|
||||||
|
if (ret != PAM_SUCCESS) { pam_end(ph, ret); return 0; }
|
||||||
|
ret = pam_authenticate(ph, PAM_SILENT);
|
||||||
|
pam_end(ph, ret);
|
||||||
|
return (ret == PAM_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
dpy = XOpenDisplay(NULL);
|
||||||
|
if (!dpy) { fprintf(stderr, "orionlock: cannot open display\n"); return 1; }
|
||||||
|
screen = DefaultScreen(dpy);
|
||||||
|
root = RootWindow(dpy, screen);
|
||||||
|
visual = DefaultVisual(dpy, screen);
|
||||||
|
cmap = DefaultColormap(dpy, screen);
|
||||||
|
|
||||||
|
int sw = DisplayWidth(dpy, screen);
|
||||||
|
int sh = DisplayHeight(dpy, screen);
|
||||||
|
|
||||||
|
XSetWindowAttributes swa;
|
||||||
|
swa.override_redirect = True;
|
||||||
|
swa.background_pixel = alloc_color(COL_BG);
|
||||||
|
swa.event_mask = ExposureMask | KeyPressMask;
|
||||||
|
lock_win = XCreateWindow(dpy, root, 0, 0, sw, sh, 0,
|
||||||
|
DefaultDepth(dpy, screen), InputOutput,
|
||||||
|
visual, CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
|
||||||
|
gc = XCreateGC(dpy, lock_win, 0, NULL);
|
||||||
|
|
||||||
|
font_clock = XftFontOpenName(dpy, screen, "Sans:size=64:weight=bold");
|
||||||
|
font_date = XftFontOpenName(dpy, screen, "Sans:size=20");
|
||||||
|
font_ui = XftFontOpenName(dpy, screen, "Sans:size=11");
|
||||||
|
if (!font_clock) font_clock = XftFontOpenName(dpy, screen, "fixed:size=20");
|
||||||
|
if (!font_date) font_date = font_clock;
|
||||||
|
if (!font_ui) font_ui = font_clock;
|
||||||
|
|
||||||
|
XMapRaised(dpy, lock_win);
|
||||||
|
XSync(dpy, False);
|
||||||
|
|
||||||
|
/* grab keyboard and pointer */
|
||||||
|
for (int t = 0; t < 10; t++) {
|
||||||
|
int r = XGrabKeyboard(dpy, lock_win, True, GrabModeAsync, GrabModeAsync, CurrentTime);
|
||||||
|
if (r == GrabSuccess) break;
|
||||||
|
usleep(100000);
|
||||||
|
}
|
||||||
|
XGrabPointer(dpy, lock_win, True, ButtonPressMask | ButtonReleaseMask |
|
||||||
|
PointerMotionMask, GrabModeAsync, GrabModeAsync,
|
||||||
|
lock_win, None, CurrentTime);
|
||||||
|
|
||||||
|
draw_lock();
|
||||||
|
|
||||||
|
time_t last_tick = 0;
|
||||||
|
|
||||||
|
while (!auth_ok) {
|
||||||
|
/* update clock every second even with no events */
|
||||||
|
fd_set fds;
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(ConnectionNumber(dpy), &fds);
|
||||||
|
struct timeval tv = { 1, 0 };
|
||||||
|
select(ConnectionNumber(dpy) + 1, &fds, NULL, NULL, &tv);
|
||||||
|
|
||||||
|
time_t now = time(NULL);
|
||||||
|
if (now != last_tick) { last_tick = now; draw_lock(); }
|
||||||
|
|
||||||
|
while (XPending(dpy)) {
|
||||||
|
XEvent e;
|
||||||
|
XNextEvent(dpy, &e);
|
||||||
|
if (e.type == Expose && e.xexpose.count == 0) {
|
||||||
|
draw_lock();
|
||||||
|
} else if (e.type == KeyPress) {
|
||||||
|
char buf[32] = {0};
|
||||||
|
KeySym sym;
|
||||||
|
XLookupString(&e.xkey, buf, sizeof(buf) - 1, &sym, NULL);
|
||||||
|
|
||||||
|
if (sym == XK_Return || sym == XK_KP_Enter) {
|
||||||
|
pw_buf[pw_len] = '\0';
|
||||||
|
if (try_auth(pw_buf)) {
|
||||||
|
auth_ok = 1;
|
||||||
|
} else {
|
||||||
|
auth_fail = 1;
|
||||||
|
pw_len = 0;
|
||||||
|
draw_lock();
|
||||||
|
usleep(400000);
|
||||||
|
auth_fail = 0;
|
||||||
|
}
|
||||||
|
} else if (sym == XK_BackSpace || sym == XK_Delete) {
|
||||||
|
if (pw_len > 0) pw_len--;
|
||||||
|
auth_fail = 0;
|
||||||
|
} else if (sym == XK_Escape) {
|
||||||
|
pw_len = 0;
|
||||||
|
auth_fail = 0;
|
||||||
|
} else if (buf[0] >= 0x20 && pw_len < PW_MAX - 1) {
|
||||||
|
pw_buf[pw_len++] = buf[0];
|
||||||
|
auth_fail = 0;
|
||||||
|
}
|
||||||
|
draw_lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XUngrabKeyboard(dpy, CurrentTime);
|
||||||
|
XUngrabPointer(dpy, CurrentTime);
|
||||||
|
XDestroyWindow(dpy, lock_win);
|
||||||
|
XSync(dpy, False);
|
||||||
|
XCloseDisplay(dpy);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
CC = gcc
|
||||||
|
|
||||||
|
PKG_C := $(shell pkg-config --cflags dbus-1 xft x11 2>/dev/null)
|
||||||
|
PKG_L := $(shell pkg-config --libs dbus-1 xft x11 2>/dev/null)
|
||||||
|
|
||||||
|
ifeq ($(PKG_C),)
|
||||||
|
$(error "Required: libdbus-1-dev libxft-dev libx11-dev")
|
||||||
|
endif
|
||||||
|
|
||||||
|
CFLAGS = -Wall -Wextra -O2 $(PKG_C)
|
||||||
|
LDFLAGS = $(PKG_L)
|
||||||
|
|
||||||
|
BIN = orionnotify
|
||||||
|
|
||||||
|
.PHONY: all clean install
|
||||||
|
all: $(BIN)
|
||||||
|
$(BIN): orionnotify.c
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||||
|
install: $(BIN)
|
||||||
|
install -Dm755 $(BIN) /usr/local/bin/$(BIN)
|
||||||
|
clean:
|
||||||
|
rm -f $(BIN)
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
/*
|
||||||
|
* orionnotify — freedesktop.org notification daemon
|
||||||
|
* Implements org.freedesktop.Notifications via libdbus-1.
|
||||||
|
* Draws popups in the top-right corner (left of the deskbar).
|
||||||
|
*/
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
#include <X11/Xft/Xft.h>
|
||||||
|
#include <dbus/dbus.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define DESKBAR_W 200
|
||||||
|
#define NOTIF_W 320
|
||||||
|
#define NOTIF_H 72
|
||||||
|
#define NOTIF_PAD 10
|
||||||
|
#define NOTIF_MAX 5
|
||||||
|
#define NOTIF_GAP 6
|
||||||
|
#define NOTIF_DEFAULT_MS 4000
|
||||||
|
|
||||||
|
#define COL_BG 0x2A2D36
|
||||||
|
#define COL_BORDER 0x4A6080
|
||||||
|
#define COL_TITLE 0xE8F0FF
|
||||||
|
#define COL_BODY 0xA0B8D0
|
||||||
|
#define COL_ACCENT 0x1E58A8
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Window win;
|
||||||
|
char summary[128];
|
||||||
|
char body[256];
|
||||||
|
int expire_ms;
|
||||||
|
struct timespec show_time;
|
||||||
|
int active;
|
||||||
|
} Notif;
|
||||||
|
|
||||||
|
static Notif notifs[NOTIF_MAX];
|
||||||
|
static int n_notifs = 0;
|
||||||
|
static unsigned int next_id = 1;
|
||||||
|
|
||||||
|
static Display *dpy;
|
||||||
|
static int screen;
|
||||||
|
static Visual *visual;
|
||||||
|
static Colormap cmap;
|
||||||
|
static GC gc;
|
||||||
|
static XftFont *font_title;
|
||||||
|
static XftFont *font_body;
|
||||||
|
|
||||||
|
static unsigned long alloc_color(unsigned long rgb) {
|
||||||
|
XColor c;
|
||||||
|
c.red = ((rgb >> 16) & 0xFF) * 257;
|
||||||
|
c.green = ((rgb >> 8) & 0xFF) * 257;
|
||||||
|
c.blue = ((rgb) & 0xFF) * 257;
|
||||||
|
c.flags = DoRed | DoGreen | DoBlue;
|
||||||
|
XAllocColor(dpy, cmap, &c);
|
||||||
|
return c.pixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void xft_color(unsigned long rgb, XftColor *out) {
|
||||||
|
XRenderColor rc;
|
||||||
|
rc.red = ((rgb >> 16) & 0xFF) * 257;
|
||||||
|
rc.green = ((rgb >> 8) & 0xFF) * 257;
|
||||||
|
rc.blue = ((rgb) & 0xFF) * 257;
|
||||||
|
rc.alpha = 0xFFFF;
|
||||||
|
XftColorAllocValue(dpy, visual, cmap, &rc, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int notif_x(void) {
|
||||||
|
return DisplayWidth(dpy, screen) - DESKBAR_W - NOTIF_W - 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int notif_y(int slot) {
|
||||||
|
return 12 + slot * (NOTIF_H + NOTIF_GAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void notif_draw(int idx) {
|
||||||
|
Notif *n = ¬ifs[idx];
|
||||||
|
if (!n->active || !n->win) return;
|
||||||
|
Window w = n->win;
|
||||||
|
|
||||||
|
XSetForeground(dpy, gc, alloc_color(COL_BG));
|
||||||
|
XFillRectangle(dpy, w, gc, 0, 0, NOTIF_W, NOTIF_H);
|
||||||
|
|
||||||
|
XSetForeground(dpy, gc, alloc_color(COL_ACCENT));
|
||||||
|
XFillRectangle(dpy, w, gc, 0, 0, 3, NOTIF_H);
|
||||||
|
|
||||||
|
XSetForeground(dpy, gc, alloc_color(COL_BORDER));
|
||||||
|
XDrawRectangle(dpy, w, gc, 0, 0, NOTIF_W - 1, NOTIF_H - 1);
|
||||||
|
|
||||||
|
XftDraw *xd = XftDrawCreate(dpy, w, visual, cmap);
|
||||||
|
XftColor col;
|
||||||
|
|
||||||
|
xft_color(COL_TITLE, &col);
|
||||||
|
XftDrawStringUtf8(xd, &col, font_title, NOTIF_PAD + 4, 24,
|
||||||
|
(const FcChar8 *)n->summary, strlen(n->summary));
|
||||||
|
XftColorFree(dpy, visual, cmap, &col);
|
||||||
|
|
||||||
|
xft_color(COL_BODY, &col);
|
||||||
|
XftDrawStringUtf8(xd, &col, font_body, NOTIF_PAD + 4, 50,
|
||||||
|
(const FcChar8 *)n->body, strlen(n->body));
|
||||||
|
XftColorFree(dpy, visual, cmap, &col);
|
||||||
|
|
||||||
|
XftDrawDestroy(xd);
|
||||||
|
XFlush(dpy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void notif_restack(void) {
|
||||||
|
int slot = 0;
|
||||||
|
for (int i = 0; i < NOTIF_MAX; i++) {
|
||||||
|
if (!notifs[i].active) continue;
|
||||||
|
XMoveWindow(dpy, notifs[i].win, notif_x(), notif_y(slot));
|
||||||
|
slot++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void notif_close(int idx) {
|
||||||
|
if (!notifs[idx].active) return;
|
||||||
|
XDestroyWindow(dpy, notifs[idx].win);
|
||||||
|
notifs[idx].win = None;
|
||||||
|
notifs[idx].active = 0;
|
||||||
|
n_notifs--;
|
||||||
|
notif_restack();
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int notif_add(const char *summary, const char *body, int expire_ms) {
|
||||||
|
if (n_notifs >= NOTIF_MAX) {
|
||||||
|
/* evict oldest */
|
||||||
|
for (int i = 0; i < NOTIF_MAX; i++) {
|
||||||
|
if (notifs[i].active) { notif_close(i); break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int slot = -1;
|
||||||
|
for (int i = 0; i < NOTIF_MAX; i++) {
|
||||||
|
if (!notifs[i].active) { slot = i; break; }
|
||||||
|
}
|
||||||
|
if (slot < 0) return 0;
|
||||||
|
|
||||||
|
Notif *n = ¬ifs[slot];
|
||||||
|
snprintf(n->summary, sizeof(n->summary), "%s", summary ? summary : "");
|
||||||
|
snprintf(n->body, sizeof(n->body), "%s", body ? body : "");
|
||||||
|
n->expire_ms = (expire_ms <= 0) ? NOTIF_DEFAULT_MS : expire_ms;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &n->show_time);
|
||||||
|
n->active = 1;
|
||||||
|
n_notifs++;
|
||||||
|
|
||||||
|
int nx = notif_x();
|
||||||
|
int ny = notif_y(n_notifs - 1);
|
||||||
|
|
||||||
|
XSetWindowAttributes swa;
|
||||||
|
swa.override_redirect = True;
|
||||||
|
swa.background_pixel = alloc_color(COL_BG);
|
||||||
|
swa.event_mask = ExposureMask | ButtonPressMask;
|
||||||
|
n->win = XCreateWindow(dpy, RootWindow(dpy, screen),
|
||||||
|
nx, ny, NOTIF_W, NOTIF_H, 0,
|
||||||
|
DefaultDepth(dpy, screen), InputOutput,
|
||||||
|
visual, CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
|
||||||
|
XMapRaised(dpy, n->win);
|
||||||
|
notif_draw(slot);
|
||||||
|
|
||||||
|
return next_id++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void expire_check(void) {
|
||||||
|
struct timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
for (int i = 0; i < NOTIF_MAX; i++) {
|
||||||
|
if (!notifs[i].active) continue;
|
||||||
|
long elapsed_ms = (now.tv_sec - notifs[i].show_time.tv_sec) * 1000
|
||||||
|
+ (now.tv_nsec - notifs[i].show_time.tv_nsec) / 1000000;
|
||||||
|
if (elapsed_ms >= notifs[i].expire_ms)
|
||||||
|
notif_close(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── DBus message handling ───────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static DBusHandlerResult handle_message(DBusConnection *conn,
|
||||||
|
DBusMessage *msg, void *data) {
|
||||||
|
(void)data;
|
||||||
|
|
||||||
|
if (!dbus_message_is_method_call(msg, "org.freedesktop.Notifications", "Notify"))
|
||||||
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||||
|
|
||||||
|
DBusMessageIter iter;
|
||||||
|
dbus_message_iter_init(msg, &iter);
|
||||||
|
|
||||||
|
/* app_name */ dbus_message_iter_next(&iter);
|
||||||
|
/* replaces_id */ dbus_message_iter_next(&iter);
|
||||||
|
/* app_icon */ dbus_message_iter_next(&iter);
|
||||||
|
|
||||||
|
const char *summary = "";
|
||||||
|
if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING)
|
||||||
|
dbus_message_iter_get_basic(&iter, &summary);
|
||||||
|
dbus_message_iter_next(&iter);
|
||||||
|
|
||||||
|
const char *body = "";
|
||||||
|
if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING)
|
||||||
|
dbus_message_iter_get_basic(&iter, &body);
|
||||||
|
dbus_message_iter_next(&iter); /* actions */
|
||||||
|
dbus_message_iter_next(&iter); /* hints */
|
||||||
|
|
||||||
|
dbus_int32_t expire_timeout = NOTIF_DEFAULT_MS;
|
||||||
|
if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_INT32)
|
||||||
|
dbus_message_iter_get_basic(&iter, &expire_timeout);
|
||||||
|
|
||||||
|
unsigned int id = notif_add(summary, body, (int)expire_timeout);
|
||||||
|
|
||||||
|
DBusMessage *reply = dbus_message_new_method_return(msg);
|
||||||
|
dbus_uint32_t uid = id;
|
||||||
|
dbus_message_append_args(reply, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID);
|
||||||
|
dbus_connection_send(conn, reply, NULL);
|
||||||
|
dbus_message_unref(reply);
|
||||||
|
|
||||||
|
return DBUS_HANDLER_RESULT_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
dpy = XOpenDisplay(NULL);
|
||||||
|
if (!dpy) { fprintf(stderr, "orionnotify: cannot open display\n"); return 1; }
|
||||||
|
screen = DefaultScreen(dpy);
|
||||||
|
visual = DefaultVisual(dpy, screen);
|
||||||
|
cmap = DefaultColormap(dpy, screen);
|
||||||
|
gc = XCreateGC(dpy, RootWindow(dpy, screen), 0, NULL);
|
||||||
|
|
||||||
|
font_title = XftFontOpenName(dpy, screen, "Sans:size=10:weight=bold");
|
||||||
|
font_body = XftFontOpenName(dpy, screen, "Sans:size=9");
|
||||||
|
if (!font_title) font_title = XftFontOpenName(dpy, screen, "fixed:size=10");
|
||||||
|
if (!font_body) font_body = font_title;
|
||||||
|
|
||||||
|
DBusError err;
|
||||||
|
dbus_error_init(&err);
|
||||||
|
DBusConnection *conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
|
||||||
|
if (!conn) {
|
||||||
|
fprintf(stderr, "orionnotify: dbus: %s\n", err.message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = dbus_bus_request_name(conn, "org.freedesktop.Notifications",
|
||||||
|
DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
|
||||||
|
if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
|
||||||
|
fprintf(stderr, "orionnotify: could not claim notification bus name\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbus_connection_add_filter(conn, handle_message, NULL, NULL);
|
||||||
|
|
||||||
|
int dbus_fd = -1;
|
||||||
|
dbus_connection_get_unix_fd(conn, &dbus_fd);
|
||||||
|
int xfd = ConnectionNumber(dpy);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
/* pump X events */
|
||||||
|
while (XPending(dpy)) {
|
||||||
|
XEvent e;
|
||||||
|
XNextEvent(dpy, &e);
|
||||||
|
if (e.type == Expose && e.xexpose.count == 0) {
|
||||||
|
for (int i = 0; i < NOTIF_MAX; i++)
|
||||||
|
if (notifs[i].active && notifs[i].win == e.xany.window)
|
||||||
|
notif_draw(i);
|
||||||
|
}
|
||||||
|
if (e.type == ButtonPress) {
|
||||||
|
for (int i = 0; i < NOTIF_MAX; i++)
|
||||||
|
if (notifs[i].active && notifs[i].win == e.xany.window)
|
||||||
|
notif_close(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pump dbus */
|
||||||
|
dbus_connection_read_write(conn, 0);
|
||||||
|
DBusMessage *msg;
|
||||||
|
while ((msg = dbus_connection_pop_message(conn)) != NULL) {
|
||||||
|
handle_message(conn, msg, NULL);
|
||||||
|
dbus_message_unref(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
expire_check();
|
||||||
|
|
||||||
|
/* sleep up to 200ms, woken by X or dbus */
|
||||||
|
fd_set fds;
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(xfd, &fds);
|
||||||
|
if (dbus_fd >= 0) FD_SET(dbus_fd, &fds);
|
||||||
|
int maxfd = xfd > dbus_fd ? xfd : dbus_fd;
|
||||||
|
struct timeval tv = { 0, 200000 };
|
||||||
|
select(maxfd + 1, &fds, NULL, NULL, &tv);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+2
-2
@@ -12,9 +12,9 @@ ifeq ($(PKG_XFT_C),)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
CFLAGS = -Wall -Wextra -O2 -Iinclude $(PKG_XFT_C)
|
CFLAGS = -Wall -Wextra -O2 -Iinclude $(PKG_XFT_C)
|
||||||
LDFLAGS = $(PKG_XFT_L)
|
LDFLAGS = $(PKG_XFT_L) -lm
|
||||||
|
|
||||||
SRC = src/wm.c src/client.c src/events.c src/draw.c src/color.c src/menu.c src/switcher.c
|
SRC = src/wm.c src/client.c src/events.c src/draw.c src/color.c src/menu.c src/switcher.c src/expose.c
|
||||||
OBJ = $(SRC:.c=.o)
|
OBJ = $(SRC:.c=.o)
|
||||||
BIN = orionwm
|
BIN = orionwm
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ typedef struct Client {
|
|||||||
int min_w, min_h; /* PMinSize */
|
int min_w, min_h; /* PMinSize */
|
||||||
int inc_w, inc_h; /* PResizeInc — 0 means no constraint */
|
int inc_w, inc_h; /* PResizeInc — 0 means no constraint */
|
||||||
int base_w, base_h; /* PBaseSize — baseline for increment calc */
|
int base_w, base_h; /* PBaseSize — baseline for increment calc */
|
||||||
|
/* Exposé saved geometry */
|
||||||
|
int exp_x, exp_y;
|
||||||
|
unsigned exp_w, exp_h;
|
||||||
struct Client *next;
|
struct Client *next;
|
||||||
} Client;
|
} Client;
|
||||||
|
|
||||||
@@ -141,6 +144,11 @@ typedef struct WM {
|
|||||||
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 */
|
int swl_mini[64]; /* 1 if corresponding entry is minimized */
|
||||||
|
|
||||||
|
/* Exposé window overview */
|
||||||
|
int expose_active;
|
||||||
|
int expose_count;
|
||||||
|
Window expose_wins[64]; /* frames shown in overview */
|
||||||
} WM;
|
} WM;
|
||||||
|
|
||||||
/* core */
|
/* core */
|
||||||
@@ -163,6 +171,7 @@ void client_toggle_maximize(WM *wm, Client *c);
|
|||||||
void client_snap_left(WM *wm, Client *c);
|
void client_snap_left(WM *wm, Client *c);
|
||||||
void client_snap_right(WM *wm, Client *c);
|
void client_snap_right(WM *wm, Client *c);
|
||||||
void client_set_wm_state(WM *wm, Client *c, long state);
|
void client_set_wm_state(WM *wm, Client *c, long state);
|
||||||
|
void client_move_to_desktop(WM *wm, Client *c, int new_desk);
|
||||||
|
|
||||||
/* drawing */
|
/* drawing */
|
||||||
void frame_draw(WM *wm, Client *c);
|
void frame_draw(WM *wm, Client *c);
|
||||||
@@ -190,6 +199,10 @@ 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);
|
||||||
|
|
||||||
|
/* exposé */
|
||||||
|
void expose_show(WM *wm);
|
||||||
|
void expose_hide(WM *wm, Window selected_frame);
|
||||||
|
|
||||||
/* events */
|
/* events */
|
||||||
void on_map_request(WM *wm, XMapRequestEvent *e);
|
void on_map_request(WM *wm, XMapRequestEvent *e);
|
||||||
void on_unmap_notify(WM *wm, XUnmapEvent *e);
|
void on_unmap_notify(WM *wm, XUnmapEvent *e);
|
||||||
|
|||||||
@@ -328,6 +328,34 @@ void client_snap_left(WM *wm, Client *c) {
|
|||||||
frame_draw(wm, c);
|
frame_draw(wm, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void client_move_to_desktop(WM *wm, Client *c, int new_desk) {
|
||||||
|
if (new_desk < 0 || new_desk >= 4 || c->desktop == new_desk) return;
|
||||||
|
|
||||||
|
/* hide frame if it's on the current (now-active) desktop */
|
||||||
|
if (c->desktop == wm->cur_desktop && !c->minimized)
|
||||||
|
XUnmapWindow(wm->dpy, c->frame);
|
||||||
|
|
||||||
|
c->desktop = new_desk;
|
||||||
|
|
||||||
|
/* update _NET_WM_DESKTOP */
|
||||||
|
long desk = new_desk;
|
||||||
|
XChangeProperty(wm->dpy, c->win, wm->net_wm_desktop, XA_CARDINAL, 32,
|
||||||
|
PropModeReplace, (unsigned char *)&desk, 1);
|
||||||
|
|
||||||
|
/* if we moved the focused window away, re-focus another */
|
||||||
|
if (wm->focused == c) {
|
||||||
|
wm->focused->focused = 0;
|
||||||
|
frame_draw(wm, wm->focused);
|
||||||
|
wm->focused = NULL;
|
||||||
|
for (Client *n = wm->clients; n; n = n->next) {
|
||||||
|
if (!n->minimized && n->desktop == wm->cur_desktop) {
|
||||||
|
client_focus(wm, n);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void client_snap_right(WM *wm, Client *c) {
|
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);
|
||||||
|
|||||||
+56
-3
@@ -74,6 +74,7 @@ void on_expose(WM *wm, XExposeEvent *e) {
|
|||||||
if (c && !c->minimized) frame_draw(wm, c);
|
if (c && !c->minimized) frame_draw(wm, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void on_configure_request(WM *wm, XConfigureRequestEvent *e) {
|
void on_configure_request(WM *wm, XConfigureRequestEvent *e) {
|
||||||
Client *c = client_find_by_win(wm, e->window);
|
Client *c = client_find_by_win(wm, e->window);
|
||||||
XWindowChanges wc;
|
XWindowChanges wc;
|
||||||
@@ -136,6 +137,7 @@ 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 net_close = XInternAtom(wm->dpy, "_NET_CLOSE_WINDOW", False);
|
||||||
Atom wm_change_state = XInternAtom(wm->dpy, "WM_CHANGE_STATE", False);
|
Atom wm_change_state = XInternAtom(wm->dpy, "WM_CHANGE_STATE", False);
|
||||||
|
Atom net_wm_desktop = XInternAtom(wm->dpy, "_NET_WM_DESKTOP", False);
|
||||||
|
|
||||||
/* _NET_CURRENT_DESKTOP → switch virtual desktop */
|
/* _NET_CURRENT_DESKTOP → switch virtual desktop */
|
||||||
if (e->message_type == wm->net_current_desktop && e->window == wm->root) {
|
if (e->message_type == wm->net_current_desktop && e->window == wm->root) {
|
||||||
@@ -143,6 +145,13 @@ void on_client_message(WM *wm, XClientMessageEvent *e) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* _NET_WM_DESKTOP → move window to desktop */
|
||||||
|
if (e->message_type == net_wm_desktop) {
|
||||||
|
Client *c = client_find_by_win(wm, e->window);
|
||||||
|
if (c) client_move_to_desktop(wm, c, (int)e->data.l[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -208,6 +217,14 @@ void on_property_notify(WM *wm, XPropertyEvent *e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void on_button_press(WM *wm, XButtonEvent *e) {
|
void on_button_press(WM *wm, XButtonEvent *e) {
|
||||||
|
/* Exposé — click on any frame to select and exit */
|
||||||
|
if (wm->expose_active) {
|
||||||
|
Client *c = client_find_by_frame(wm, e->window);
|
||||||
|
if (c) expose_hide(wm, c->frame);
|
||||||
|
else expose_hide(wm, None);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* menu window */
|
/* menu window */
|
||||||
if (wm->menu_visible && e->window == wm->menu_win) {
|
if (wm->menu_visible && e->window == wm->menu_win) {
|
||||||
if (e->button == Button1) menu_click(wm, e->x, e->y);
|
if (e->button == Button1) menu_click(wm, e->x, e->y);
|
||||||
@@ -401,9 +418,45 @@ void on_key_press(WM *wm, XKeyEvent *e) {
|
|||||||
if ((e->state & Mod4Mask) && sym == XK_Down && wm->focused)
|
if ((e->state & Mod4Mask) && sym == XK_Down && wm->focused)
|
||||||
client_unmaximize(wm, wm->focused);
|
client_unmaximize(wm, wm->focused);
|
||||||
|
|
||||||
/* Escape — hide menu */
|
/* Super+Space — Exposé window overview */
|
||||||
if (sym == XK_Escape && wm->menu_visible)
|
if ((e->state & Mod4Mask) && sym == XK_space) {
|
||||||
menu_hide(wm);
|
if (wm->expose_active) expose_hide(wm, None);
|
||||||
|
else expose_show(wm);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Super+1-4 — switch to desktop N */
|
||||||
|
if ((e->state & Mod4Mask) && !(e->state & ShiftMask)) {
|
||||||
|
int desk = -1;
|
||||||
|
if (sym == XK_1) desk = 0;
|
||||||
|
else if (sym == XK_2) desk = 1;
|
||||||
|
else if (sym == XK_3) desk = 2;
|
||||||
|
else if (sym == XK_4) desk = 3;
|
||||||
|
if (desk >= 0) {
|
||||||
|
if (wm->expose_active) expose_hide(wm, None);
|
||||||
|
wm_switch_desktop(wm, desk);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Super+Shift+1-4 — move focused window to desktop N */
|
||||||
|
if ((e->state & Mod4Mask) && (e->state & ShiftMask) && wm->focused) {
|
||||||
|
int desk = -1;
|
||||||
|
if (sym == XK_1) desk = 0;
|
||||||
|
else if (sym == XK_2) desk = 1;
|
||||||
|
else if (sym == XK_3) desk = 2;
|
||||||
|
else if (sym == XK_4) desk = 3;
|
||||||
|
if (desk >= 0) {
|
||||||
|
client_move_to_desktop(wm, wm->focused, desk);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Escape — hide menu or exit Exposé */
|
||||||
|
if (sym == XK_Escape) {
|
||||||
|
if (wm->expose_active) expose_hide(wm, None);
|
||||||
|
else if (wm->menu_visible) menu_hide(wm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_key_release(WM *wm, XKeyEvent *e) {
|
void on_key_release(WM *wm, XKeyEvent *e) {
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#include "wm.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
void expose_show(WM *wm) {
|
||||||
|
if (wm->expose_active) return;
|
||||||
|
|
||||||
|
/* collect non-minimized windows on the current desktop */
|
||||||
|
int n = 0;
|
||||||
|
for (Client *c = wm->clients; c; c = c->next) {
|
||||||
|
if (!c->minimized && c->desktop == wm->cur_desktop && n < 64) {
|
||||||
|
/* save original geometry */
|
||||||
|
c->exp_x = c->x;
|
||||||
|
c->exp_y = c->y;
|
||||||
|
c->exp_w = c->w;
|
||||||
|
c->exp_h = c->h;
|
||||||
|
wm->expose_wins[n++] = c->frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (n == 0) return;
|
||||||
|
|
||||||
|
wm->expose_active = 1;
|
||||||
|
wm->expose_count = n;
|
||||||
|
|
||||||
|
int sw = DisplayWidth(wm->dpy, wm->screen) - DESKBAR_W;
|
||||||
|
int sh = DisplayHeight(wm->dpy, wm->screen);
|
||||||
|
|
||||||
|
int cols = (int)ceil(sqrt((double)n));
|
||||||
|
int rows = (n + cols - 1) / cols;
|
||||||
|
int pad = 16;
|
||||||
|
int cell_w = (sw - pad * (cols + 1)) / cols;
|
||||||
|
int cell_h = (sh - pad * (rows + 1)) / rows;
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
for (int row = 0; row < rows; row++) {
|
||||||
|
for (int col = 0; col < cols && idx < n; col++, idx++) {
|
||||||
|
Client *c = client_find_by_frame(wm, wm->expose_wins[idx]);
|
||||||
|
if (!c) continue;
|
||||||
|
|
||||||
|
/* un-maximize so we can freely resize */
|
||||||
|
if (c->maximized) {
|
||||||
|
c->maximized = 0;
|
||||||
|
XDeleteProperty(wm->dpy, c->win, wm->net_wm_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
int tx = pad + col * (cell_w + pad);
|
||||||
|
int ty = pad + row * (cell_h + pad);
|
||||||
|
int tw = cell_w;
|
||||||
|
int th = cell_h;
|
||||||
|
|
||||||
|
c->x = tx; c->y = ty;
|
||||||
|
c->w = (unsigned)(tw - BORDER_W * 2);
|
||||||
|
c->h = (unsigned)(th - TITLE_HEIGHT - BORDER_W);
|
||||||
|
|
||||||
|
int fw = (int)c->w + BORDER_W * 2;
|
||||||
|
int fh = (int)c->h + TITLE_HEIGHT + BORDER_W;
|
||||||
|
XMoveResizeWindow(wm->dpy, c->frame, c->x, c->y, fw, fh);
|
||||||
|
XResizeWindow(wm->dpy, c->win, c->w, c->h);
|
||||||
|
frame_draw(wm, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XSync(wm->dpy, False);
|
||||||
|
}
|
||||||
|
|
||||||
|
void expose_hide(WM *wm, Window selected_frame) {
|
||||||
|
if (!wm->expose_active) return;
|
||||||
|
wm->expose_active = 0;
|
||||||
|
|
||||||
|
Client *to_focus = NULL;
|
||||||
|
|
||||||
|
for (int i = 0; i < wm->expose_count; i++) {
|
||||||
|
Client *c = client_find_by_frame(wm, wm->expose_wins[i]);
|
||||||
|
if (!c) continue;
|
||||||
|
|
||||||
|
c->x = c->exp_x;
|
||||||
|
c->y = c->exp_y;
|
||||||
|
c->w = c->exp_w;
|
||||||
|
c->h = c->exp_h;
|
||||||
|
|
||||||
|
int fw = (int)c->w + BORDER_W * 2;
|
||||||
|
int fh = (int)c->h + TITLE_HEIGHT + BORDER_W;
|
||||||
|
XMoveResizeWindow(wm->dpy, c->frame, c->x, c->y, fw, fh);
|
||||||
|
XResizeWindow(wm->dpy, c->win, c->w, c->h);
|
||||||
|
frame_draw(wm, c);
|
||||||
|
|
||||||
|
if (c->frame == selected_frame) to_focus = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
wm->expose_count = 0;
|
||||||
|
XSync(wm->dpy, False);
|
||||||
|
|
||||||
|
if (to_focus) client_focus(wm, to_focus);
|
||||||
|
else if (wm->focused) client_focus(wm, wm->focused);
|
||||||
|
}
|
||||||
+11
@@ -103,6 +103,17 @@ void wm_init(WM *wm) {
|
|||||||
{ XK_Right, Mod4Mask }, /* snap right */
|
{ XK_Right, Mod4Mask }, /* snap right */
|
||||||
{ XK_Up, Mod4Mask }, /* maximize */
|
{ XK_Up, Mod4Mask }, /* maximize */
|
||||||
{ XK_Down, Mod4Mask }, /* restore */
|
{ XK_Down, Mod4Mask }, /* restore */
|
||||||
|
{ XK_space, Mod4Mask }, /* Exposé overview */
|
||||||
|
/* Super+1-4: switch desktop */
|
||||||
|
{ XK_1, Mod4Mask },
|
||||||
|
{ XK_2, Mod4Mask },
|
||||||
|
{ XK_3, Mod4Mask },
|
||||||
|
{ XK_4, Mod4Mask },
|
||||||
|
/* Super+Shift+1-4: move window to desktop */
|
||||||
|
{ XK_1, Mod4Mask | ShiftMask },
|
||||||
|
{ XK_2, Mod4Mask | ShiftMask },
|
||||||
|
{ XK_3, Mod4Mask | ShiftMask },
|
||||||
|
{ XK_4, Mod4Mask | ShiftMask },
|
||||||
{ XK_Escape, 0 },
|
{ XK_Escape, 0 },
|
||||||
};
|
};
|
||||||
for (int i = 0; i < (int)(sizeof(grabs)/sizeof(grabs[0])); i++) {
|
for (int i = 0; i < (int)(sizeof(grabs)/sizeof(grabs[0])); i++) {
|
||||||
|
|||||||
Reference in new issue
Block a user