Merge pull request #1 from nmemmert/claude/elemetryos-macos-fork-mmkshh

OrionOS macOS-style fork — full feature build
This commit is contained in:
nmemmert authored and GitHub committed 2026-08-30 17:13:50 -04:00
commit e2e8c49a0a
43 files changed
+3519 -645

No files matched your search

+40 -5
View File
@@ -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 orionshot orionclip 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 orionshot orionclip
wm: wm:
$(MAKE) -C wm $(MAKE) -C wm
@@ -39,6 +39,18 @@ quill:
lens: lens:
$(MAKE) -C lens $(MAKE) -C lens
orionnotify:
$(MAKE) -C orionnotify
orionlock:
$(MAKE) -C orionlock
orionshot:
$(MAKE) -C orionshot
orionclip:
$(MAKE) -C orionclip
clean: clean:
$(MAKE) -C wm clean $(MAKE) -C wm clean
$(MAKE) -C deskbar clean $(MAKE) -C deskbar clean
@@ -49,7 +61,11 @@ 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
$(MAKE) -C orionshot clean
$(MAKE) -C orionclip clean
install: all install: all
# binaries # binaries
@@ -62,6 +78,10 @@ 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
install -Dm755 orionshot/orionshot /usr/local/bin/orionshot
install -Dm755 orionclip/orionclip /usr/local/bin/orionclip
# 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,8 +97,23 @@ 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 " orionclip &"
@echo " exec orionwm" @echo " exec orionwm"
iso: iso:
chmod +x build-iso.sh @if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then \
./build-iso.sh echo "Building OrionOS ISO via Docker (Arch)..."; \
docker build -t orionos-iso-builder -f iso/docker/Dockerfile .; \
mkdir -p iso/output; \
docker run --rm --privileged -v "$$(pwd)/iso/output:/output" orionos-iso-builder; \
elif command -v debootstrap >/dev/null 2>&1; then \
echo "Building OrionOS ISO via debootstrap (Debian/Ubuntu)..."; \
sudo bash iso/build.sh; \
elif command -v mkarchiso >/dev/null 2>&1; then \
echo "Building OrionOS ISO via mkarchiso (Arch native)..."; \
sudo bash iso/build-arch.sh; \
else \
echo "No ISO build tool found. See iso/README.md for options."; \
exit 1; \
fi
+13 -3
View File
@@ -1,12 +1,22 @@
# OrionOS # OrionOS — ElemetryOS macOS Fork
A BeOS-inspired Linux desktop built from scratch in C. Custom window manager, panel, app launcher, settings panel, package manager, and graphical installer — all running on Arch Linux, shipped as a bootable ISO. A macOS-inspired Linux desktop forked from ElemetryOS, built from scratch in C. Custom window manager, panel, app launcher, settings panel, package manager, and graphical installer — all running on Arch Linux, shipped as a bootable ISO.
--- ---
## What it is ## What it is
OrionOS is a complete desktop environment built on X11/Xlib with a dark navy aesthetic. It ships as a bootable ISO you can try live or install to disk. Every shell component is written in native C with no toolkit dependency — just Xlib and Xft. OrionOS is a complete desktop environment built on X11/Xlib with a macOS dark-mode aesthetic. It ships as a bootable ISO you can try live or install to disk. Every shell component is written in native C with no toolkit dependency — just Xlib and Xft.
### macOS design language
This fork re-skins the window manager with macOS design conventions:
- **Traffic-light buttons on the left** — close (red), minimize (yellow), maximize (green), just like macOS
- **macOS dark-mode palette** — near-black desktop, `#3C3C3C` active titlebars, `#2C2C2C` inactive
- **Centered window titles** — title text centered in the titlebar, macOS-style
- **Thin 1 px borders** — minimal window chrome, no thick bevels
- **macOS blue selection** (`#4A90D9`) in context menus and snap overlays
### Components ### Components
+8
View File
@@ -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)
+15
View File
@@ -44,6 +44,14 @@
#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 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 /* 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 */
#define DB_FLYOUT_IH 28 /* item row height */ #define DB_FLYOUT_IH 28 /* item row height */
@@ -96,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 {
@@ -139,6 +149,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;
+323 -5
View File
@@ -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
@@ -111,8 +115,14 @@ void db_update_apps(Deskbar *db) {
Atom net_wm_state = XInternAtom(db->dpy, "_NET_WM_STATE", False); Atom net_wm_state = XInternAtom(db->dpy, "_NET_WM_STATE", False);
Atom net_wm_state_hidden = XInternAtom(db->dpy, "_NET_WM_STATE_HIDDEN", False); Atom net_wm_state_hidden = XInternAtom(db->dpy, "_NET_WM_STATE_HIDDEN", False);
Atom net_wm_desktop = XInternAtom(db->dpy, "_NET_WM_DESKTOP", False);
for (unsigned long i = 0; i < n && db->napps < DB_MAX_APPS; i++) { for (unsigned long i = 0; i < n && db->napps < DB_MAX_APPS; i++) {
/* only show windows on the current desktop (or sticky: desktop == 0xFFFFFFFF) */
long win_desk = ewmh_get_long(db, wins[i], net_wm_desktop);
if (win_desk >= 0 && win_desk != (long)db->cur_ws && win_desk != (long)0xFFFFFFFF)
continue;
db->apps[db->napps].win = wins[i]; db->apps[db->napps].win = wins[i];
db->apps[db->napps].focused = (wins[i] == (Window)active_win); db->apps[db->napps].focused = (wins[i] == (Window)active_win);
db->apps[db->napps].minimized = 0; db->apps[db->napps].minimized = 0;
@@ -135,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++;
} }
@@ -420,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;
@@ -578,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 },
@@ -809,6 +877,222 @@ 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_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) {
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;
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" };
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);
/* base items */
for (int i = 0; i < DB_CTX_N; i++) {
int iy = ctx_item_y(i);
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);
}
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);
}
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 idx = ctx_item_at(ry - cy);
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 idx = ctx_item_at(ry - cy);
Window target = db->apps[app_idx].win;
int mini = db->apps[app_idx].minimized;
db_ctx_close(db);
if (idx < 0) 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) {
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 {
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) {
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);
}
}
/* ── input ────────────────────────────────────────────────────────────── */ /* ── input ────────────────────────────────────────────────────────────── */
void db_hover(Deskbar *db, int x, int y) { void db_hover(Deskbar *db, int x, int y) {
@@ -981,6 +1265,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 +1330,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
@@ -1091,6 +1407,8 @@ void db_run(Deskbar *db) {
db_draw(db); db_draw(db);
} else if (e.xproperty.atom == db->net_current_desktop) { } else if (e.xproperty.atom == db->net_current_desktop) {
db_update_workspace(db); db_update_workspace(db);
db_update_apps(db); /* re-filter by new desktop */
db_resize(db);
db_draw(db); db_draw(db);
} }
break; break;
+51
View File
@@ -0,0 +1,51 @@
# OrionOS ISO Builder
Two build paths are provided. Pick the one that matches your host.
## Path A — Arch Linux host (native, recommended)
Requires `archiso` and `pacman-contrib`.
```bash
sudo pacman -S archiso
sudo make iso # runs iso/build-arch.sh
```
## Path B — Debian / Ubuntu host
Requires `xorriso`, `squashfs-tools`, `debootstrap`, `grub-pc-bin`, `grub-efi-amd64-bin`.
```bash
sudo apt-get install -y xorriso squashfs-tools debootstrap \
grub-pc-bin grub-efi-amd64-bin mtools
sudo bash iso/build.sh
```
Output lands in `iso/output/OrionOS-YYYYMMDD.iso`.
## Path C — Docker (any host with Docker)
```bash
make iso
```
The Dockerfile (`iso/docker/Dockerfile`) builds inside `archlinux:latest`,
compiles OrionOS, and runs `mkarchiso`. Output is written to `iso/output/`.
## Writing the ISO to a USB drive
```bash
# Find your USB device (e.g. /dev/sdb) — be careful
lsblk
sudo dd if=iso/output/OrionOS-*.iso of=/dev/sdX bs=4M status=progress oflag=sync
```
Or use [Ventoy](https://ventoy.net) — just copy the `.iso` onto the Ventoy USB.
## First boot
- Logs in automatically as root on tty1 and starts X / OrionOS.
- Super+F1 or bottom-left hot corner → launcher
- Super+V → clipboard manager
- Print → screenshot (Shift+Print → region)
- Top-left hot corner → Exposé overview
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
# OrionOS live session autostart — sourced by ~/.xinitrc on the ISO
# Compositor
picom --config /etc/orionos/picom.conf &
# Set wallpaper
orionwallpaper &
# Notification daemon
orionnotify &
# Clipboard manager
orionclip &
# Deskbar
oriondeskbar &
# Window manager (blocking)
exec orionwm
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
source /etc/orionos/xinitrc
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# OrionOS ISO builder — Arch Linux host using mkarchiso
# Run as root from the repo root: sudo bash iso/build-arch.sh
set -euo pipefail
REPO="$(cd "$(dirname "$0")/.." && pwd)"
OUT="$REPO/iso/output"
echo "==> Building OrionOS binaries..."
cd "$REPO"
make all
echo "==> Installing OrionOS binaries into profile airootfs..."
install -Dm755 wm/orionwm iso/profile/airootfs/usr/local/bin/orionwm
install -Dm755 deskbar/oriondeskbar iso/profile/airootfs/usr/local/bin/oriondeskbar
install -Dm755 launcher/orionlauncher iso/profile/airootfs/usr/local/bin/orionlauncher
install -Dm755 orionnotify/orionnotify iso/profile/airootfs/usr/local/bin/orionnotify
install -Dm755 orionlock/orionlock iso/profile/airootfs/usr/local/bin/orionlock
install -Dm755 orionshot/orionshot iso/profile/airootfs/usr/local/bin/orionshot
install -Dm755 orionclip/orionclip iso/profile/airootfs/usr/local/bin/orionclip
install -Dm755 orionpkg/orionpkg iso/profile/airootfs/usr/local/bin/orionpkg
install -Dm755 themes/orionwallpaper iso/profile/airootfs/usr/local/bin/orionwallpaper
mkdir -p iso/profile/airootfs/usr/share/themes
cp -r themes/OrionOS iso/profile/airootfs/usr/share/themes/
install -Dm644 themes/picom.conf iso/profile/airootfs/etc/orionos/picom.conf
mkdir -p iso/profile/airootfs/usr/share/orionos/wallpapers
bash themes/wallpapers/generate-default.sh 2>/dev/null || true
cp -f themes/wallpapers/orionos-default.png \
iso/profile/airootfs/usr/share/orionos/wallpapers/ 2>/dev/null || true
echo "==> Running mkarchiso..."
mkdir -p "$OUT"
mkarchiso -v -w /tmp/orionos-arch-work -o "$OUT" iso/profile
echo ""
echo "✓ ISO built in $OUT"
ls -lh "$OUT"/*.iso 2>/dev/null || true
Executable
+169
View File
@@ -0,0 +1,169 @@
#!/usr/bin/env bash
# OrionOS ISO builder (Debian-based live system)
# Run as root from the repo root: sudo bash iso/build.sh
set -euo pipefail
REPO="$(cd "$(dirname "$0")/.." && pwd)"
WORK="/tmp/orionos-build"
OUT="$REPO/iso/output"
FS="$WORK/rootfs"
ISO_STAGE="$WORK/iso"
echo "==> OrionOS ISO builder"
echo " repo: $REPO"
echo " work: $WORK"
echo " output: $OUT"
mkdir -p "$FS" "$ISO_STAGE/live" "$ISO_STAGE/boot/grub" "$OUT"
# ── 1. Bootstrap minimal Debian base ────────────────────────────────────
echo "==> [1/7] Bootstrapping Debian bookworm..."
if [[ ! -f "$FS/bin/bash" ]]; then
debootstrap --arch=amd64 \
--include=linux-image-amd64,live-boot,systemd-sysv,sudo,bash,\
coreutils,util-linux,procps,grep,gawk,sed,tar,gzip,less,vim,git,\
xorg,xinit,xterm,feh,picom,alsa-utils,libx11-6,libxft2,libxfixes3,\
libxext6,libpng16-16,dbus,libpam0g,fontconfig,fonts-dejavu,\
fonts-noto,libdbus-1-3,notify-send,bash-completion,network-manager \
bookworm "$FS" 2>&1 | grep -E '(I:|W:|E:|error|warn)' || true
else
echo " (rootfs already exists, skipping bootstrap)"
fi
# ── 2. Compile OrionOS inside chroot ────────────────────────────────────
echo "==> [2/7] Compiling OrionOS components..."
mkdir -p "$FS/build"
cp -r "$REPO"/{wm,deskbar,launcher,orionnotify,orionlock,orionshot,orionclip,orionpkg,themes} "$FS/build/" 2>/dev/null || true
chroot "$FS" /bin/bash -c '
export DEBIAN_FRONTEND=noninteractive
apt-get install -y --no-install-recommends \
gcc make pkg-config \
libx11-dev libxft-dev libxfixes-dev libxext-dev \
libpng-dev libdbus-1-dev libpam0g-dev \
2>&1 | tail -3
cd /build
for dir in wm deskbar launcher orionnotify orionlock orionshot orionclip orionpkg; do
if [[ -d "$dir" ]]; then
echo " building $dir..."
make -C "$dir" 2>&1 | tail -2 || echo " WARNING: $dir build failed"
fi
done
' 2>&1
# ── 3. Install OrionOS binaries ──────────────────────────────────────────
echo "==> [3/7] Installing OrionOS binaries..."
chroot "$FS" /bin/bash -c '
set -e
install_if() { [[ -f "/build/$1" ]] && install -Dm755 "/build/$1" "/usr/local/bin/$2"; }
install_if wm/orionwm orionwm
install_if deskbar/oriondeskbar oriondeskbar
install_if launcher/orionlauncher orionlauncher
install_if orionnotify/orionnotify orionnotify
install_if orionlock/orionlock orionlock
install_if orionshot/orionshot orionshot
install_if orionclip/orionclip orionclip
install_if orionpkg/orionpkg orionpkg
install -Dm755 /build/themes/orionwallpaper /usr/local/bin/orionwallpaper
mkdir -p /usr/share/themes
cp -r /build/themes/OrionOS /usr/share/themes/ 2>/dev/null || true
mkdir -p /etc/orionos
cp /build/themes/picom.conf /etc/orionos/ 2>/dev/null || true
mkdir -p /usr/share/orionos/wallpapers
bash /build/themes/wallpapers/generate-default.sh 2>/dev/null || true
cp -f /build/themes/wallpapers/orionos-default.png /usr/share/orionos/wallpapers/ 2>/dev/null || true
' 2>&1
# ── 4. Configure live session ────────────────────────────────────────────
echo "==> [4/7] Configuring live session..."
cat > "$FS/etc/orionos/xinitrc" <<'XINITRC'
#!/bin/bash
picom --config /etc/orionos/picom.conf &
orionwallpaper &
orionnotify &
orionclip &
oriondeskbar &
exec orionwm
XINITRC
chmod 755 "$FS/etc/orionos/xinitrc"
cat > "$FS/root/.xinitrc" <<'XINIRC'
#!/bin/bash
source /etc/orionos/xinitrc
XINIRC
chmod 755 "$FS/root/.xinitrc"
# Auto-login and startx on tty1
mkdir -p "$FS/etc/systemd/system/getty@tty1.service.d"
cat > "$FS/etc/systemd/system/getty@tty1.service.d/autologin.conf" <<'UNIT'
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin root --noclear %I $TERM
UNIT
cat > "$FS/root/.bash_profile" <<'PROFILE'
[[ -z "$DISPLAY" && "$XDG_VTNR" == 1 ]] && exec startx
PROFILE
# hostname
echo "orionos" > "$FS/etc/hostname"
# Clean up build artifacts to save space
chroot "$FS" /bin/bash -c '
apt-get remove -y gcc make pkg-config \
libx11-dev libxft-dev libxfixes-dev libxext-dev \
libpng-dev libdbus-1-dev libpam0g-dev 2>&1 | tail -2 || true
apt-get autoremove -y 2>&1 | tail -2 || true
apt-get clean
rm -rf /build /var/cache/apt /tmp/*
' 2>&1
# ── 5. Build squashfs ────────────────────────────────────────────────────
echo "==> [5/7] Building squashfs filesystem..."
mksquashfs "$FS" "$ISO_STAGE/live/filesystem.squashfs" \
-comp zstd -Xcompression-level 6 \
-e boot -noappend 2>&1 | tail -3
# ── 6. Copy kernel + initrd ──────────────────────────────────────────────
echo "==> [6/7] Copying kernel and initrd..."
VMLINUZ="$(ls "$FS/boot/vmlinuz-"* 2>/dev/null | head -1)"
INITRD="$(ls "$FS/boot/initrd.img-"* 2>/dev/null | head -1)"
if [[ -z "$VMLINUZ" || -z "$INITRD" ]]; then
echo "ERROR: kernel or initrd not found in $FS/boot/"
ls "$FS/boot/" || true
exit 1
fi
cp "$VMLINUZ" "$ISO_STAGE/boot/vmlinuz"
cp "$INITRD" "$ISO_STAGE/boot/initrd.img"
# ── 7. GRUB bootloader + ISO ─────────────────────────────────────────────
echo "==> [7/7] Building bootable ISO with GRUB..."
cat > "$ISO_STAGE/boot/grub/grub.cfg" <<'GRUB'
set default=0
set timeout=5
menuentry "OrionOS Live" {
linux /boot/vmlinuz boot=live quiet splash
initrd /boot/initrd.img
}
menuentry "OrionOS Live (safe mode)" {
linux /boot/vmlinuz boot=live nomodeset
initrd /boot/initrd.img
}
GRUB
ISOFILE="$OUT/OrionOS-$(date +%Y%m%d).iso"
grub-mkrescue -o "$ISOFILE" "$ISO_STAGE" \
--modules="normal iso9660 linux ext2 squash4 biosdisk" \
2>&1 | grep -v "^$" | tail -10
SIZE="$(du -sh "$ISOFILE" | cut -f1)"
echo ""
echo "✓ ISO built: $ISOFILE ($SIZE)"
+46 -42
View File
@@ -1,58 +1,62 @@
# OrionOS ISO builder
# Uses official Arch Linux as the base — provides pacman, pacstrap, archiso.
# Build context: the repo root (OS/)
#
# Build: docker build -t orionos-builder -f iso/docker/Dockerfile .
# Run: docker run --privileged -v $(pwd)/dist:/dist orionos-builder
# Output: dist/orionos.iso
FROM archlinux:latest FROM archlinux:latest
# ── disable pacman seccomp sandbox (breaks under QEMU on Apple Silicon) ── # Update and install archiso + OrionOS build dependencies
RUN echo "DisableSandbox" >> /etc/pacman.conf
# ── install build tools ────────────────────────────────────────────────
RUN pacman -Syu --noconfirm && \ RUN pacman -Syu --noconfirm && \
pacman -S --noconfirm \ pacman -S --noconfirm \
archiso \ archiso \
base-devel \ base-devel \
git \
libx11 \ libx11 \
libxft \ libxft \
libxfixes \
libxext \
libpng \
dbus \
pam \
fontconfig \ fontconfig \
freetype2 \ freetype2 \
imlib2 \ xorg-server \
imagemagick \ xorg-xinit \
fakeroot \ feh \
git \ picom \
sudo \ alsa-utils \
&& pacman -Scc --noconfirm ttf-dejavu \
noto-fonts \
pkgconf && \
pacman -Scc --noconfirm
# ── build OnlyOffice from AUR (as a non-root build user) ───────────── WORKDIR /build
RUN useradd -m builder && \
echo "builder ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/builder
RUN su builder -c " \
git clone --depth=1 https://aur.archlinux.org/onlyoffice-bin.git /tmp/onlyoffice && \
cd /tmp/onlyoffice && \
makepkg -s --noconfirm 2>&1 | tail -5 \
" && \
find /tmp/onlyoffice -name '*.pkg.tar.zst' -exec cp {} /onlyoffice.pkg.tar.zst \; || \
echo "OnlyOffice AUR build failed — will be skipped in ISO"
# ── show which mkinitcpio hooks archiso installed (helps debugging) ─── # Copy OrionOS source
RUN echo "=== archiso initcpio hooks ===" && \ COPY . /build/orionos
ls /usr/lib/initcpio/hooks/ 2>/dev/null || echo "(none found)"
# ── copy the entire OrionOS source tree ────────────────────────────── # Build OrionOS components
WORKDIR /orionos RUN cd /build/orionos && make 2>&1 | tail -20
COPY . .
# ── build the OrionOS binaries inside the container ─────────────────── # Install binaries into the staging root that archiso will include
# pkg-config finds X11/Xft headers automatically on Arch — no overrides needed RUN cd /build/orionos && \
RUN make install -Dm755 wm/orionwm /build/orionos/iso/profile/airootfs/usr/local/bin/orionwm && \
install -Dm755 deskbar/oriondeskbar /build/orionos/iso/profile/airootfs/usr/local/bin/oriondeskbar && \
install -Dm755 launcher/orionlauncher /build/orionos/iso/profile/airootfs/usr/local/bin/orionlauncher && \
install -Dm755 themes/orionwallpaper /build/orionos/iso/profile/airootfs/usr/local/bin/orionwallpaper && \
install -Dm755 orionnotify/orionnotify /build/orionos/iso/profile/airootfs/usr/local/bin/orionnotify && \
install -Dm755 orionlock/orionlock /build/orionos/iso/profile/airootfs/usr/local/bin/orionlock && \
install -Dm755 orionshot/orionshot /build/orionos/iso/profile/airootfs/usr/local/bin/orionshot && \
install -Dm755 orionclip/orionclip /build/orionos/iso/profile/airootfs/usr/local/bin/orionclip && \
install -Dm755 orionpkg/orionpkg /build/orionos/iso/profile/airootfs/usr/local/bin/orionpkg && \
cp -r themes/OrionOS /build/orionos/iso/profile/airootfs/usr/share/themes/ && \
install -Dm644 themes/picom.conf /build/orionos/iso/profile/airootfs/etc/orionos/picom.conf
# ── generate the default wallpaper ─────────────────────────────────── # Generate default wallpaper
RUN bash themes/wallpapers/generate-default.sh || true RUN mkdir -p /build/orionos/iso/profile/airootfs/usr/share/orionos/wallpapers && \
bash /build/orionos/themes/wallpapers/generate-default.sh 2>/dev/null && \
cp -f /build/orionos/themes/wallpapers/orionos-default.png \
/build/orionos/iso/profile/airootfs/usr/share/orionos/wallpapers/ 2>/dev/null || true
# ── run the ISO build script ────────────────────────────────────────── # Build the ISO
# --privileged is required for loop device mounts inside the container RUN mkarchiso -v \
ENTRYPOINT ["/orionos/iso/docker/entrypoint.sh"] -w /tmp/iso-work \
-o /output \
/build/orionos/iso/profile
CMD ["/bin/bash"]
+1
View File
@@ -0,0 +1 @@
orionos
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
# OrionOS live session autostart
picom --config /etc/orionos/picom.conf &
orionwallpaper &
orionnotify &
orionclip &
oriondeskbar &
exec orionwm
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
source /etc/orionos/xinitrc
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
source /etc/orionos/xinitrc
+72
View File
@@ -0,0 +1,72 @@
# OrionOS live system packages
# Base system
base
linux
linux-firmware
mkinitcpio
# X11
xorg-server
xorg-xinit
xorg-xrandr
xorg-xsetroot
xorg-xev
# Fonts
ttf-dejavu
noto-fonts
ttf-liberation
# Dependencies for OrionOS components
libx11
libxft
libxfixes
libxext
libpng
dbus
libpam
fontconfig
freetype2
# Desktop utilities
feh
picom
xterm
libnotify
# Audio
alsa-utils
pulseaudio
# Brightness
light
# Media playback control
playerctl
# Arch tools
pacman-contrib
pkexec
polkit
# System tools
bash
coreutils
util-linux
procps-ng
grep
gawk
sed
tar
gzip
# Network
networkmanager
dhcpcd
# Misc
git
vim
less
which
+14
View File
@@ -0,0 +1,14 @@
[options]
HoldPkg = pacman glibc
Architecture = auto
CheckSpace
ParallelDownloads = 5
[core]
Include = /etc/pacman.d/mirrorlist
[extra]
Include = /etc/pacman.d/mirrorlist
[multilib]
Include = /etc/pacman.d/mirrorlist
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# archiso profile definition for OrionOS
iso_name="OrionOS"
iso_label="ORIONOS_$(date +%Y%m)"
iso_publisher="OrionOS Project"
iso_application="OrionOS Live"
iso_version="$(date +%Y.%m)"
install_dir="arch"
buildmodes=('iso')
bootmodes=('bios.syslinux.mbr' 'bios.syslinux.eltorito'
'uefi-ia32.grub.esp' 'uefi-x64.grub.esp'
'uefi-ia32.grub.eltorito' 'uefi-x64.grub.eltorito')
arch="x86_64"
pacman_conf="pacman.conf"
airootfs_image_type="squashfs"
airootfs_image_tool_options=('-comp' 'zstd' '-Xcompression-level' '15')
file_permissions=(
["/etc/orionos/xinitrc"]="0:0:755"
["/root/.xinitrc"]="0:0:755"
["/etc/skel/.xinitrc"]="0:0:755"
)
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# archiso profile definition for OrionOS
iso_name="OrionOS"
iso_label="ORIONOS_$(date +%Y%m)"
iso_publisher="OrionOS Project"
iso_application="OrionOS Live"
iso_version="$(date +%Y.%m)"
install_dir="arch"
buildmodes=('iso')
bootmodes=('bios.syslinux.mbr' 'bios.syslinux.eltorito'
'uefi-ia32.grub.esp' 'uefi-x64.grub.esp'
'uefi-ia32.grub.eltorito' 'uefi-x64.grub.eltorito')
arch="x86_64"
pacman_conf="pacman.conf"
airootfs_image_type="squashfs"
airootfs_image_tool_options=('-comp' 'zstd' '-Xcompression-level' '15')
file_permissions=(
["/etc/orionos/xinitrc"]="0:0:755"
["/root/.xinitrc"]="0:0:755"
)
+8
View File
@@ -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 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) CFLAGS = -Wall -Wextra -O2 $(PKG_XFT_C)
LDFLAGS = $(PKG_XFT_L) 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 FONT_ICON "Sans:size=13:weight=bold"
#define MAX_FLT 64 #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 App APPS[MAX_APPS];
static int NAPP = 0; static int NAPP = 0;
static const char *CATS[] = { 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]))) #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, "FileManager") || strstr(cats, "Utility") ||
strstr(cats, "Monitor") || strstr(cats, "Security")) strstr(cats, "Monitor") || strstr(cats, "Security"))
return "System"; return "System";
return ""; return "Other";
} }
static void clean_exec(char *dst, const char *src, size_t dstsz) { 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"); FILE *f = fopen(path, "r");
if (!f) return; 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; int in_entry = 0, nodisplay = 0, is_app = 0;
char line[512]; char line[512];
@@ -126,6 +126,8 @@ static void load_desktop_file(const char *path) {
snprintf(exec, sizeof(exec), "%s", line + 5); snprintf(exec, sizeof(exec), "%s", line + 5);
else if (!cats[0] && strncmp(line, "Categories=", 11) == 0) else if (!cats[0] && strncmp(line, "Categories=", 11) == 0)
snprintf(cats, sizeof(cats), "%s", line + 11); 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) else if (strncmp(line, "NoDisplay=true", 14) == 0)
nodisplay = 1; nodisplay = 1;
else if (strncmp(line, "Type=Application", 16) == 0) 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); snprintf(APPS[NAPP].name, 64, "%s", name);
clean_exec(APPS[NAPP].cmd, exec, 256); clean_exec(APPS[NAPP].cmd, exec, 256);
snprintf(APPS[NAPP].cat, 32, "%s", map_category(cats)); snprintf(APPS[NAPP].cat, 32, "%s", map_category(cats));
snprintf(APPS[NAPP].icon, 64, "%s", icon);
NAPP++; NAPP++;
} }
@@ -292,6 +295,39 @@ static int card_at_vis(int x, int y) {
return row * COLS + col; 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 ────────────────────────────────────────────────────────────── */ /* ── draw ────────────────────────────────────────────────────────────── */
static void draw(L *l) { static void draw(L *l) {
XftDraw *xd = XftDrawCreate(l->dpy, l->win, l->visual, l->cmap); 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; 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 ix = cx + (CARD_W - ICON_SZ) / 2;
int iy = cy + 10; int iy = cy + 10;
XSetForeground(l->dpy, l->gc, mkc(l, ICON_BG[h % NICON])); int icon_rendered = 0;
XFillRectangle(l->dpy, l->win, l->gc, ix, iy, ICON_SZ, ICON_SZ); #ifdef HAVE_IMLIB2
icon_rendered = render_icon(l, APPS[app_i].icon, ix, iy, ICON_SZ);
char letter[2] = { APPS[app_i].name[0], '\0' }; #endif
if (letter[0] >= 'a' && letter[0] <= 'z') letter[0] -= 32; if (!icon_rendered) {
ds_ctr(l, xd, l->font_icon, 0xEAF0FF, ix, iy, ICON_SZ, ICON_SZ, letter); 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]; char lbl[14];
snprintf(lbl, sizeof(lbl), "%.12s", APPS[app_i].name); snprintf(lbl, sizeof(lbl), "%.12s", APPS[app_i].name);
+22
View File
@@ -0,0 +1,22 @@
CC = gcc
PKG_C := $(shell pkg-config --cflags x11 xfixes xft 2>/dev/null)
PKG_L := $(shell pkg-config --libs x11 xfixes xft 2>/dev/null)
ifeq ($(PKG_C),)
$(error "Required: libx11-dev libxfixes-dev libxft-dev")
endif
CFLAGS = -Wall -Wextra -O2 $(PKG_C)
LDFLAGS = $(PKG_L)
BIN = orionclip
.PHONY: all clean install
all: $(BIN)
$(BIN): orionclip.c
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
install: $(BIN)
install -Dm755 $(BIN) /usr/local/bin/$(BIN)
clean:
rm -f $(BIN)
+407
View File
@@ -0,0 +1,407 @@
/*
* orionclip — clipboard manager for OrionOS
* Usage:
* orionclip run as daemon, monitors clipboard
* orionclip --show toggle the history popup (sent by Super+V hotkey)
*
* Stores up to CLIP_MAX entries. Popup appears in bottom-right corner
* (left of the deskbar). Click an entry to restore it to the clipboard.
* Press Escape or click outside to dismiss.
*
* IPC: the daemon registers the _ORIONCLIP_WIN atom on the root window.
* orionclip --show sends a ClientMessage to that window.
*/
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/Xft/Xft.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#define CLIP_MAX 20
#define POPUP_W 340
#define POPUP_ROW_H 28
#define POPUP_PAD 8
#define DESKBAR_W 200
#define POLL_MS 300 /* clipboard poll interval in ms */
#define COL_BG 0x2A2D36
#define COL_BORDER 0x4A6080
#define COL_TEXT 0xD8E4F4
#define COL_SEL_BG 0x1E58A8
#define COL_SEL_FG 0xF0F8FF
#define COL_ACCENT 0x1E58A8
#define COL_DIM 0x607080
static char clip_history[CLIP_MAX][512];
static int clip_count = 0;
static Display *dpy;
static int screen;
static Visual *visual;
static Colormap cmap;
static GC gc;
static XftFont *font;
static Window popup_win = None;
static int popup_hovered = -1;
static Atom atom_clip_win; /* _ORIONCLIP_WIN — IPC handle */
static Atom atom_show; /* _ORIONCLIP_SHOW — toggle message */
static Atom atom_utf8;
static Atom atom_clipboard;
static Atom atom_targets;
/* ── helpers ─────────────────────────────────────────────────────────── */
static unsigned long xcolor(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);
}
/* ── clipboard read ──────────────────────────────────────────────────── */
static char *clipboard_get(void) {
/* request clipboard content via XConvertSelection */
Window owner = XGetSelectionOwner(dpy, atom_clipboard);
if (owner == None) return NULL;
/* use a small utility window as the requestor */
static Window req_win = None;
if (req_win == None)
req_win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen),
-10, -10, 1, 1, 0, 0, 0);
Atom prop = XInternAtom(dpy, "_ORIONCLIP_PROP", False);
XConvertSelection(dpy, atom_clipboard, atom_utf8, prop, req_win, CurrentTime);
/* wait up to ~300ms for SelectionNotify */
struct timespec deadline, now;
clock_gettime(CLOCK_MONOTONIC, &deadline);
deadline.tv_nsec += 300000000L;
if (deadline.tv_nsec >= 1000000000L) {
deadline.tv_nsec -= 1000000000L;
deadline.tv_sec++;
}
for (;;) {
if (XPending(dpy)) {
XEvent e;
XNextEvent(dpy, &e);
if (e.type == SelectionNotify && e.xselection.requestor == req_win) {
if (e.xselection.property == None) return NULL;
Atom type; int fmt; unsigned long nitems, left;
unsigned char *data = NULL;
XGetWindowProperty(dpy, req_win, prop, 0, 65536/4, True,
atom_utf8, &type, &fmt, &nitems, &left, &data);
if (!data) return NULL;
char *copy = strndup((char *)data, nitems < 511 ? nitems : 511);
XFree(data);
return copy;
}
}
clock_gettime(CLOCK_MONOTONIC, &now);
if (now.tv_sec > deadline.tv_sec ||
(now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec))
break;
usleep(10000);
}
return NULL;
}
static void clipboard_set(const char *text) {
/* put text on the clipboard by becoming the selection owner */
static Window sel_win = None;
static char sel_buf[512];
if (sel_win == None)
sel_win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen),
-10, -10, 1, 1, 0, 0, 0);
strncpy(sel_buf, text, 511); sel_buf[511] = '\0';
XSetSelectionOwner(dpy, atom_clipboard, sel_win, CurrentTime);
/* SelectionRequest events are handled in the main event loop */
(void)sel_buf;
}
/* ── history management ──────────────────────────────────────────────── */
static void history_push(const char *text) {
if (!text || !*text) return;
/* deduplicate: if same text is already entry 0, skip */
if (clip_count > 0 && strcmp(clip_history[0], text) == 0) return;
/* shift down */
int top = clip_count < CLIP_MAX ? clip_count : CLIP_MAX - 1;
for (int i = top; i > 0; i--)
memcpy(clip_history[i], clip_history[i-1], sizeof(clip_history[0]));
strncpy(clip_history[0], text, 511);
clip_history[0][511] = '\0';
if (clip_count < CLIP_MAX) clip_count++;
}
/* ── popup ───────────────────────────────────────────────────────────── */
static int popup_h(void) {
return POPUP_PAD * 2 + clip_count * POPUP_ROW_H;
}
static void popup_draw(void) {
if (!popup_win) return;
int ph = popup_h();
XSetForeground(dpy, gc, xcolor(COL_BG));
XFillRectangle(dpy, popup_win, gc, 0, 0, POPUP_W, ph);
XSetForeground(dpy, gc, xcolor(COL_ACCENT));
XFillRectangle(dpy, popup_win, gc, 0, 0, 3, ph);
XSetForeground(dpy, gc, xcolor(COL_BORDER));
XDrawRectangle(dpy, popup_win, gc, 0, 0, POPUP_W - 1, ph - 1);
XftDraw *xd = XftDrawCreate(dpy, popup_win, visual, cmap);
for (int i = 0; i < clip_count; i++) {
int ry = POPUP_PAD + i * POPUP_ROW_H;
if (i == popup_hovered) {
XSetForeground(dpy, gc, xcolor(COL_SEL_BG));
XFillRectangle(dpy, popup_win, gc, 4, ry, POPUP_W - 8, POPUP_ROW_H - 2);
}
unsigned long text_col = (i == popup_hovered) ? COL_SEL_FG : COL_TEXT;
if (i > 0) text_col = COL_DIM;
XftColor col;
xft_color(text_col, &col);
/* truncate display text */
char disp[64];
strncpy(disp, clip_history[i], 63); disp[63] = '\0';
/* replace newlines with spaces */
for (char *p = disp; *p; p++) if (*p == '\n' || *p == '\r' || *p == '\t') *p = ' ';
XftDrawStringUtf8(xd, &col, font, 12, ry + POPUP_ROW_H - 7,
(const FcChar8 *)disp, strlen(disp));
XftColorFree(dpy, visual, cmap, &col);
}
XftDrawDestroy(xd);
XFlush(dpy);
}
static void popup_show(void) {
if (popup_win != None) { popup_draw(); return; }
if (clip_count == 0) return;
int ph = popup_h();
int sw = DisplayWidth(dpy, screen);
int sh = DisplayHeight(dpy, screen);
int px = sw - DESKBAR_W - POPUP_W - 12;
int py = sh - ph - 40;
XSetWindowAttributes swa;
swa.override_redirect = True;
swa.background_pixel = xcolor(COL_BG);
swa.event_mask = ExposureMask | ButtonPressMask |
PointerMotionMask | LeaveWindowMask;
popup_win = XCreateWindow(dpy, RootWindow(dpy, screen),
px, py, POPUP_W, ph, 0,
DefaultDepth(dpy, screen), InputOutput,
visual, CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
XMapRaised(dpy, popup_win);
popup_draw();
}
static void popup_hide(void) {
if (popup_win == None) return;
XDestroyWindow(dpy, popup_win);
popup_win = None;
popup_hovered = -1;
XFlush(dpy);
}
static void popup_toggle(void) {
if (popup_win != None) popup_hide();
else popup_show();
}
static int popup_row_at(int y) {
int row = (y - POPUP_PAD) / POPUP_ROW_H;
if (row < 0 || row >= clip_count) return -1;
return row;
}
/* ── --show mode: send IPC to daemon ─────────────────────────────────── */
static void send_show(void) {
Display *d = XOpenDisplay(NULL);
if (!d) { fprintf(stderr, "orionclip: cannot open display\n"); return; }
Atom a = XInternAtom(d, "_ORIONCLIP_WIN", True);
if (a == None) { XCloseDisplay(d); return; }
Atom type; int fmt; unsigned long n, left;
unsigned char *data = NULL;
XGetWindowProperty(d, RootWindow(d, DefaultScreen(d)), a, 0, 1, False,
XA_WINDOW, &type, &fmt, &n, &left, &data);
if (!data) { XCloseDisplay(d); return; }
Window target = *(Window *)data;
XFree(data);
Atom show_atom = XInternAtom(d, "_ORIONCLIP_SHOW", False);
XClientMessageEvent ev = {0};
ev.type = ClientMessage;
ev.window = target;
ev.message_type = show_atom;
ev.format = 32;
XSendEvent(d, target, False, 0, (XEvent *)&ev);
XFlush(d);
XCloseDisplay(d);
}
/* ── main daemon ─────────────────────────────────────────────────────── */
int main(int argc, char *argv[]) {
if (argc > 1 && strcmp(argv[1], "--show") == 0) {
send_show();
return 0;
}
dpy = XOpenDisplay(NULL);
if (!dpy) { fprintf(stderr, "orionclip: 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 = XftFontOpenName(dpy, screen, "Sans:size=9");
if (!font) font = XftFontOpenName(dpy, screen, "fixed:size=9");
atom_utf8 = XInternAtom(dpy, "UTF8_STRING", False);
atom_clipboard = XInternAtom(dpy, "CLIPBOARD", False);
atom_targets = XInternAtom(dpy, "TARGETS", False);
atom_clip_win = XInternAtom(dpy, "_ORIONCLIP_WIN", False);
atom_show = XInternAtom(dpy, "_ORIONCLIP_SHOW", False);
/* register ourselves on the root window */
Window self = XCreateSimpleWindow(dpy, RootWindow(dpy, screen),
-10, -10, 1, 1, 0, 0, 0);
XSelectInput(dpy, self, StructureNotifyMask | PropertyChangeMask);
XChangeProperty(dpy, RootWindow(dpy, screen), atom_clip_win, XA_WINDOW, 32,
PropModeReplace, (unsigned char *)&self, 1);
XFlush(dpy);
int xfd = ConnectionNumber(dpy);
struct timespec last_poll = {0, 0};
for (;;) {
/* pump X events */
while (XPending(dpy)) {
XEvent e;
XNextEvent(dpy, &e);
/* IPC: show/hide toggle */
if (e.type == ClientMessage &&
(Atom)e.xclient.message_type == atom_show) {
popup_toggle();
continue;
}
/* selection ownership request — serve clipboard content */
if (e.type == SelectionRequest) {
XSelectionRequestEvent *req = &e.xselectionrequest;
XSelectionEvent sev = {0};
sev.type = SelectionNotify;
sev.requestor = req->requestor;
sev.selection = req->selection;
sev.target = req->target;
sev.time = req->time;
sev.property = None;
if (clip_count > 0) {
if (req->target == atom_targets) {
Atom targets[2] = { atom_targets, atom_utf8 };
XChangeProperty(dpy, req->requestor, req->property, XA_ATOM, 32,
PropModeReplace, (unsigned char *)targets, 2);
sev.property = req->property;
} else if (req->target == atom_utf8) {
XChangeProperty(dpy, req->requestor, req->property, atom_utf8, 8,
PropModeReplace,
(unsigned char *)clip_history[0],
strlen(clip_history[0]));
sev.property = req->property;
}
}
XSendEvent(dpy, req->requestor, False, 0, (XEvent *)&sev);
continue;
}
if (popup_win == None) continue;
if (e.type == Expose && e.xexpose.count == 0 &&
e.xany.window == popup_win)
popup_draw();
if (e.type == LeaveNotify && e.xany.window == popup_win) {
popup_hovered = -1;
popup_draw();
}
if (e.type == MotionNotify && e.xany.window == popup_win) {
int row = popup_row_at(e.xmotion.y);
if (row != popup_hovered) {
popup_hovered = row;
popup_draw();
}
}
if (e.type == ButtonPress && e.xany.window == popup_win) {
int row = popup_row_at(e.xbutton.y);
if (row >= 0 && row < clip_count) {
/* move chosen entry to top and set as clipboard */
char chosen[512];
strncpy(chosen, clip_history[row], 511); chosen[511] = '\0';
history_push(chosen);
clipboard_set(clip_history[0]);
}
popup_hide();
}
}
/* poll clipboard every POLL_MS ms */
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
long elapsed_ms = (now.tv_sec - last_poll.tv_sec) * 1000
+ (now.tv_nsec - last_poll.tv_nsec) / 1000000;
if (elapsed_ms >= POLL_MS) {
last_poll = now;
char *text = clipboard_get();
if (text) {
history_push(text);
free(text);
}
}
fd_set fds;
FD_ZERO(&fds);
FD_SET(xfd, &fds);
struct timeval tv = { 0, POLL_MS * 1000 };
select(xfd + 1, &fds, NULL, NULL, &tv);
}
return 0;
}
+24
View File
@@ -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)
+271
View File
@@ -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;
}
+22
View File
@@ -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)
+314
View File
@@ -0,0 +1,314 @@
/*
* 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>
#include <sys/stat.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 = &notifs[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 void notif_log(const char *summary, const char *body) {
const char *home = getenv("HOME");
if (!home) return;
char dir[512], path[560];
snprintf(dir, sizeof(dir), "%s/.local/share/orionos", home);
snprintf(path, sizeof(path), "%s/notif-history.log", dir);
mkdir(dir, 0755);
FILE *f = fopen(path, "a");
if (!f) return;
time_t t = time(NULL);
/* sanitise: replace tabs and newlines with spaces */
char sum[128], bod[256];
strncpy(sum, summary ? summary : "", 127); sum[127] = '\0';
strncpy(bod, body ? body : "", 255); bod[255] = '\0';
for (char *p = sum; *p; p++) if (*p == '\t' || *p == '\n') *p = ' ';
for (char *p = bod; *p; p++) if (*p == '\t' || *p == '\n') *p = ' ';
fprintf(f, "%ld\t%s\t%s\n", (long)t, sum, bod);
fclose(f);
}
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 = &notifs[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++;
notif_log(summary, body);
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;
}
+17 -10
View File
@@ -1,15 +1,22 @@
CC = gcc
PKG_C := $(shell pkg-config --cflags x11 xft 2>/dev/null)
PKG_L := $(shell pkg-config --libs x11 xft 2>/dev/null)
ifeq ($(PKG_C),)
$(error "Required: libx11-dev libxft-dev")
endif
CFLAGS = -Wall -Wextra -O2 $(PKG_C)
LDFLAGS = $(PKG_L)
BIN = orionpkg BIN = orionpkg
CC = gcc .PHONY: all clean install
CFLAGS = -Wall -Wextra -O2 \ all: $(BIN)
$(shell pkg-config --cflags x11 xft fontconfig 2>/dev/null)
LIBS = $(shell pkg-config --libs x11 xft fontconfig 2>/dev/null) \
-lX11 -lXft -lfontconfig
$(BIN): orionpkg.c $(BIN): orionpkg.c
$(CC) $(CFLAGS) -o $@ $< $(LIBS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
install: $(BIN)
install -Dm755 $(BIN) /usr/local/bin/$(BIN)
clean: clean:
rm -f $(BIN) rm -f $(BIN)
.PHONY: clean
+601 -403
View File
File diff suppressed because it is too large. Load diff
+22
View File
@@ -0,0 +1,22 @@
CC = gcc
PKG_C := $(shell pkg-config --cflags x11 libpng 2>/dev/null)
PKG_L := $(shell pkg-config --libs x11 libpng 2>/dev/null)
ifeq ($(PKG_C),)
$(error "Required: libx11-dev libpng-dev")
endif
CFLAGS = -Wall -Wextra -O2 $(PKG_C)
LDFLAGS = $(PKG_L)
BIN = orionshot
.PHONY: all clean install
all: $(BIN)
$(BIN): orionshot.c
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
install: $(BIN)
install -Dm755 $(BIN) /usr/local/bin/$(BIN)
clean:
rm -f $(BIN)
+229
View File
@@ -0,0 +1,229 @@
/*
* orionshot — X11 screenshot tool for OrionOS
* Usage: orionshot full full-screen PNG capture
* orionshot region interactive rubber-band region select
*
* Saves to ~/Pictures/Screenshots/orionos_YYYYMMDD_HHMMSS.png
* Sends a desktop notification on success.
*/
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#endif
/* ── PNG writer ──────────────────────────────────────────────────────── */
static int get_shift(unsigned long mask) {
if (!mask) return 0;
int s = 0;
while (!(mask & 1)) { mask >>= 1; s++; }
return s;
}
static int write_png(const char *path, XImage *img, int ox, int oy, int w, int h) {
FILE *fp = fopen(path, "wb");
if (!fp) { perror("orionshot: fopen"); return -1; }
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png) { fclose(fp); return -1; }
png_infop info = png_create_info_struct(png);
if (!info) { png_destroy_write_struct(&png, NULL); fclose(fp); return -1; }
if (setjmp(png_jmpbuf(png))) {
png_destroy_write_struct(&png, &info);
fclose(fp);
return -1;
}
png_init_io(png, fp);
png_set_IHDR(png, info, (unsigned)w, (unsigned)h, 8,
PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png, info);
int r_shift = get_shift(img->red_mask);
int g_shift = get_shift(img->green_mask);
int b_shift = get_shift(img->blue_mask);
int r_scale = (img->red_mask >> r_shift) < 0xFF ? 2 : 1;
int g_scale = (img->green_mask >> g_shift) < 0xFF ? 2 : 1;
int b_scale = (img->blue_mask >> b_shift) < 0xFF ? 2 : 1;
(void)r_scale; (void)g_scale; (void)b_scale;
unsigned char *row = malloc((size_t)w * 3);
if (!row) { png_destroy_write_struct(&png, &info); fclose(fp); return -1; }
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
unsigned long px = XGetPixel(img, ox + i, oy + j);
row[i*3+0] = (unsigned char)((px & img->red_mask) >> r_shift);
row[i*3+1] = (unsigned char)((px & img->green_mask) >> g_shift);
row[i*3+2] = (unsigned char)((px & img->blue_mask) >> b_shift);
}
png_write_row(png, row);
}
free(row);
png_write_end(png, NULL);
png_destroy_write_struct(&png, &info);
fclose(fp);
return 0;
}
/* ── output path ─────────────────────────────────────────────────────── */
static void make_save_path(char *buf, size_t bufsz) {
const char *home = getenv("HOME");
if (!home) home = "/tmp";
char dir[512];
snprintf(dir, sizeof(dir), "%s/Pictures/Screenshots", home);
mkdir(dir, 0755);
time_t t = time(NULL);
struct tm *tm = localtime(&t);
char ts[32];
strftime(ts, sizeof(ts), "%Y%m%d_%H%M%S", tm);
snprintf(buf, bufsz, "%s/orionos_%s.png", dir, ts);
}
/* ── region selection ────────────────────────────────────────────────── */
static int select_region(Display *dpy, Window root,
int *rx, int *ry, int *rw, int *rh) {
Cursor cross = XCreateFontCursor(dpy, XC_crosshair);
if (XGrabPointer(dpy, root, False,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync,
root, cross, CurrentTime) != GrabSuccess) {
XFreeCursor(dpy, cross);
return -1;
}
XGrabKeyboard(dpy, root, False, GrabModeAsync, GrabModeAsync, CurrentTime);
GC gc;
XGCValues gcv;
gcv.function = GXxor;
gcv.foreground = 0xFFFFFF;
gcv.subwindow_mode = IncludeInferiors;
gcv.line_width = 1;
gc = XCreateGC(dpy, root,
GCFunction | GCForeground | GCSubwindowMode | GCLineWidth, &gcv);
int sx = 0, sy = 0, ex = 0, ey = 0;
int started = 0, drawn = 0, done = 0, cancelled = 0;
while (!done) {
XEvent e;
XNextEvent(dpy, &e);
if (e.type == ButtonPress && e.xbutton.button == Button1) {
sx = e.xbutton.x_root; sy = e.xbutton.y_root;
ex = sx; ey = sy; started = 1;
} else if (e.type == MotionNotify && started) {
/* erase previous rubber band */
if (drawn) {
int dx = MIN(sx,ex), dy = MIN(sy,ey);
int dw = abs(ex-sx), dh = abs(ey-sy);
if (dw > 0 && dh > 0)
XDrawRectangle(dpy, root, gc, dx, dy, (unsigned)dw, (unsigned)dh);
XFlush(dpy); drawn = 0;
}
ex = e.xmotion.x_root; ey = e.xmotion.y_root;
{
int dx = MIN(sx,ex), dy = MIN(sy,ey);
int dw = abs(ex-sx), dh = abs(ey-sy);
if (dw > 0 && dh > 0) {
XDrawRectangle(dpy, root, gc, dx, dy, (unsigned)dw, (unsigned)dh);
XFlush(dpy); drawn = 1;
}
}
} else if (e.type == ButtonRelease && e.xbutton.button == Button1) {
if (drawn) {
int dx = MIN(sx,ex), dy = MIN(sy,ey);
int dw = abs(ex-sx), dh = abs(ey-sy);
if (dw > 0 && dh > 0)
XDrawRectangle(dpy, root, gc, dx, dy, (unsigned)dw, (unsigned)dh);
XFlush(dpy);
}
ex = e.xbutton.x_root; ey = e.xbutton.y_root;
done = 1;
} else if (e.type == KeyPress) {
cancelled = 1; done = 1;
}
}
XUngrabPointer(dpy, CurrentTime);
XUngrabKeyboard(dpy, CurrentTime);
XFreeGC(dpy, gc);
XFreeCursor(dpy, cross);
XSync(dpy, False);
if (cancelled) return -1;
*rx = MIN(sx,ex); *ry = MIN(sy,ey);
*rw = abs(ex-sx); *rh = abs(ey-sy);
if (*rw < 2 || *rh < 2) return -1;
return 0;
}
/* ── main ────────────────────────────────────────────────────────────── */
int main(int argc, char *argv[]) {
int do_region = (argc > 1 && strcmp(argv[1], "region") == 0);
Display *dpy = XOpenDisplay(NULL);
if (!dpy) { fprintf(stderr, "orionshot: cannot open display\n"); return 1; }
int screen = DefaultScreen(dpy);
Window root = RootWindow(dpy, screen);
int sw = DisplayWidth(dpy, screen);
int sh = DisplayHeight(dpy, screen);
int cx = 0, cy = 0, cw = sw, ch = sh;
if (do_region) {
if (select_region(dpy, root, &cx, &cy, &cw, &ch) < 0) {
XCloseDisplay(dpy);
return 0; /* cancelled — silently exit */
}
/* tiny pause so rubber-band XOR erasure is fully flushed */
usleep(60000);
}
XImage *img = XGetImage(dpy, root, cx, cy, (unsigned)cw, (unsigned)ch,
AllPlanes, ZPixmap);
XCloseDisplay(dpy);
if (!img) { fprintf(stderr, "orionshot: XGetImage failed\n"); return 1; }
char path[1024];
make_save_path(path, sizeof(path));
if (write_png(path, img, 0, 0, cw, ch) < 0) {
XDestroyImage(img);
fprintf(stderr, "orionshot: failed to write %s\n", path);
return 1;
}
XDestroyImage(img);
/* extract just the filename for the notification body */
const char *fname = strrchr(path, '/');
fname = fname ? fname + 1 : path;
char notif[512];
snprintf(notif, sizeof(notif),
"notify-send -t 3000 'Screenshot' '%s' 2>/dev/null &", fname);
system(notif);
fprintf(stderr, "orionshot: saved %s\n", path);
return 0;
}
+3 -3
View File
@@ -4,13 +4,13 @@
@define-color bg_dark #161C2A; @define-color bg_dark #161C2A;
@define-color bg_darker #0E1220; @define-color bg_darker #0E1220;
@define-color bg_light #262E42; @define-color bg_light #262E42;
@define-color border_light #3A6898; @define-color border_light #2A60A8;
@define-color border_dark #080C18; @define-color border_dark #080C18;
@define-color text_color #D8E4F4; @define-color text_color #D8E4F4;
@define-color text_disabled #48587A; @define-color text_disabled #48587A;
@define-color selected_bg #1E4D78; @define-color selected_bg #1E4878;
@define-color selected_fg #F0F6FF; @define-color selected_fg #F0F6FF;
@define-color accent #3A6898; @define-color accent #1E58A8;
@define-color button_bg #202A3C; @define-color button_bg #202A3C;
@define-color button_hover #283650; @define-color button_hover #283650;
@define-color button_active #181E2E; @define-color button_active #181E2E;
+20 -4
View File
@@ -2,10 +2,11 @@
# orionwallpaper — set or restore the OrionOS desktop wallpaper # orionwallpaper — set or restore the OrionOS desktop wallpaper
# #
# Usage: # Usage:
# orionwallpaper # restore saved wallpaper (called from xinitrc) # orionwallpaper # restore saved wallpaper (called from xinitrc)
# orionwallpaper /path/to/image.jpg # set new wallpaper and save it # orionwallpaper /path/to/image.jpg # set new wallpaper and save it
# orionwallpaper --color "#6B8FB5" # set solid color # orionwallpaper --color "#6B8FB5" # set solid color
# orionwallpaper --reset # reset to default blue-gray solid color # orionwallpaper --reset # reset to default blue-gray solid color
# orionwallpaper --slideshow DIR [SECS] # rotate images in DIR every SECS seconds (default 300)
# #
# Wallpaper preference is stored in ~/.config/orionos/wallpaper # Wallpaper preference is stored in ~/.config/orionos/wallpaper
@@ -17,6 +18,21 @@ WALLPAPER_DIR="/usr/share/orionos/wallpapers"
mkdir -p "$CONF_DIR" mkdir -p "$CONF_DIR"
case "${1:-}" in case "${1:-}" in
--slideshow)
DIR="${2:-$WALLPAPER_DIR}"
SECS="${3:-300}"
if [[ ! -d "$DIR" ]]; then
echo "Error: directory not found: $DIR" >&2
exit 1
fi
while true; do
IMG="$(find "$DIR" -maxdepth 1 -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \) | shuf -n1)"
if [[ -n "$IMG" ]]; then
feh --no-fehbg --bg-fill "$IMG"
fi
sleep "$SECS"
done
;;
--reset) --reset)
echo "color:$DEFAULT_COLOR" > "$CONF_FILE" echo "color:$DEFAULT_COLOR" > "$CONF_FILE"
xsetroot -solid "$DEFAULT_COLOR" xsetroot -solid "$DEFAULT_COLOR"
+25 -20
View File
@@ -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; };
}; };
+2 -2
View File
@@ -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
+57 -27
View File
@@ -5,35 +5,40 @@
#include <X11/Xutil.h> #include <X11/Xutil.h>
#include <X11/Xatom.h> #include <X11/Xatom.h>
#include <X11/keysym.h> #include <X11/keysym.h>
#include <X11/XF86keysym.h>
#include <X11/Xft/Xft.h> #include <X11/Xft/Xft.h>
#include <X11/cursorfont.h> #include <X11/cursorfont.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
/* ── palette (modern dark) ───────────────────────────────────────────── */ /* ── palette (macOS dark mode) ───────────────────────────────────────── */
#define COLOR_TITLE_ACTIVE 0x1E4D78 /* deep blue active title bar */ #define COLOR_TITLE_ACTIVE 0x3C3C3C /* macOS dark titlebar */
#define COLOR_TITLE_INACTIVE 0x252830 /* dark grey inactive */ #define COLOR_TITLE_INACTIVE 0x2C2C2C /* inactive, slightly darker */
#define COLOR_TITLE_TEXT_ACT 0xE8EEF8 /* near-white active text */ #define COLOR_TITLE_TEXT_ACT 0xFFFFFF /* white active text */
#define COLOR_TITLE_TEXT_INACT 0x5C6070 /* dimmed inactive text */ #define COLOR_TITLE_TEXT_INACT 0x686868 /* dimmed inactive text */
#define COLOR_BORDER_LIGHT 0x3A6898 /* blue-grey highlight */ #define COLOR_BORDER_LIGHT 0x505050 /* subtle border highlight */
#define COLOR_BORDER_DARK 0x0C1018 /* near-black shadow */ #define COLOR_BORDER_DARK 0x1C1C1C /* near-black shadow */
#define COLOR_CLOSE_BTN 0xCC3333 /* red close */ #define COLOR_CLOSE_BTN 0xFF5F57 /* macOS traffic-light red */
#define COLOR_MAXIMIZE_BTN 0x2E8B44 /* green maximize */ #define COLOR_MAXIMIZE_BTN 0x28C840 /* macOS traffic-light green */
#define COLOR_MINIMIZE_BTN 0xBB8820 /* amber minimize */ #define COLOR_MINIMIZE_BTN 0xFEBC2E /* macOS traffic-light yellow */
#define COLOR_CLOSE_BTN_HOV 0xFF5050 #define COLOR_CLOSE_BTN_HOV 0xFF3B30
#define COLOR_MAXIMIZE_BTN_HOV 0x44CC66 #define COLOR_MAXIMIZE_BTN_HOV 0x34C759
#define COLOR_MINIMIZE_BTN_HOV 0xFFBB30 #define COLOR_MINIMIZE_BTN_HOV 0xFFCC00
#define COLOR_BG 0x1A2840 /* desktop background */ #define COLOR_BG 0x1C1C1C /* near-black desktop */
#define COLOR_SNAP_OVERLAY 0x3A6898 /* snap highlight (unused, placeholder) */ #define COLOR_SNAP_OVERLAY 0x4A90D9 /* macOS blue snap highlight */
/* ── geometry ────────────────────────────────────────────────────────── */ /* ── geometry ────────────────────────────────────────────────────────── */
#define TITLE_HEIGHT 28 /* taller for modern look */ #define DESKBAR_W 200 /* must match DB_W in deskbar/include/deskbar.h */
#define TITLE_HEIGHT 28 /* macOS-style height */
#define TITLE_TAB_W 200 #define TITLE_TAB_W 200
#define BORDER_W 3 #define BORDER_W 1 /* thin 1px macOS-style border */
#define BTN_SIZE 14 #define BTN_SIZE 12 /* macOS traffic-light diameter */
#define BTN_MARGIN 5 #define BTN_MARGIN 8 /* spacing between traffic lights */
#define BTN_LEFT_PAD 10 /* left edge → first button */
#define RESIZE_GRIP 12 #define RESIZE_GRIP 12
#define MIN_WIN_W 120 #define MIN_WIN_W 120
#define MIN_WIN_H 60 #define MIN_WIN_H 60
@@ -41,15 +46,15 @@
#define DBL_CLICK_MS 400 /* double-click threshold */ #define DBL_CLICK_MS 400 /* double-click threshold */
/* /*
* Three buttons on right side (left to right): minimize, maximize, close * macOS-style: three traffic-light buttons on LEFT side
* close = fw - BTN_SIZE - BTN_MARGIN - BORDER_W * close = BTN_LEFT_PAD
* max = close - BTN_SIZE - BTN_MARGIN - 2 * minimize = BTN_LEFT_PAD + BTN_SIZE + BTN_MARGIN
* min = max - BTN_SIZE - BTN_MARGIN - 2 * maximize = BTN_LEFT_PAD + (BTN_SIZE + BTN_MARGIN) * 2
*/ */
#define BTN_CLOSE_X(fw) ((fw) - BTN_SIZE - BTN_MARGIN - BORDER_W) #define BTN_CLOSE_X (BTN_LEFT_PAD)
#define BTN_MAX_X(fw) ((fw) - BTN_SIZE*2 - BTN_MARGIN*2 - BORDER_W - 2) #define BTN_MIN_X (BTN_LEFT_PAD + BTN_SIZE + BTN_MARGIN)
#define BTN_MIN_X(fw) ((fw) - BTN_SIZE*3 - BTN_MARGIN*3 - BORDER_W - 4) #define BTN_MAX_X (BTN_LEFT_PAD + (BTN_SIZE + BTN_MARGIN) * 2)
#define BTN_Y (((TITLE_HEIGHT) - BTN_SIZE) / 2) #define BTN_Y (((TITLE_HEIGHT) - BTN_SIZE) / 2)
/* ── fonts ───────────────────────────────────────────────────────────── */ /* ── fonts ───────────────────────────────────────────────────────────── */
#define FONT_TITLE "Sans:size=9:weight=bold" #define FONT_TITLE "Sans:size=9:weight=bold"
@@ -71,10 +76,18 @@ typedef struct Client {
int focused; int focused;
int minimized; int minimized;
int maximized; int maximized;
int desktop; /* virtual desktop this window lives on */
int saved_x, saved_y; /* pre-maximize position */ int saved_x, saved_y; /* pre-maximize position */
unsigned saved_w, saved_h; /* pre-maximize size */ unsigned saved_w, saved_h; /* pre-maximize size */
Time last_click_time; /* for double-click detection */ Time last_click_time; /* for double-click detection */
char title[256]; char title[256];
/* ICCCM WM_NORMAL_HINTS */
int min_w, min_h; /* PMinSize */
int inc_w, inc_h; /* PResizeInc — 0 means no constraint */
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;
@@ -102,6 +115,9 @@ typedef struct WM {
Client *clients; Client *clients;
Client *focused; Client *focused;
int cur_desktop; /* active virtual desktop (0-based) */
int hot_corner_last; /* last hot-corner zone (0=none,1-4) */
Atom wm_delete_window; Atom wm_delete_window;
Atom wm_protocols; Atom wm_protocols;
Atom wm_state; Atom wm_state;
@@ -110,6 +126,8 @@ typedef struct WM {
Atom net_wm_state_hidden; Atom net_wm_state_hidden;
Atom net_wm_state_maximized_vert; Atom net_wm_state_maximized_vert;
Atom net_wm_state_maximized_horz; Atom net_wm_state_maximized_horz;
Atom net_wm_desktop;
Atom net_current_desktop;
DragMode drag_mode; DragMode drag_mode;
int drag_start_x, drag_start_y; int drag_start_x, drag_start_y;
@@ -129,6 +147,12 @@ 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 */
/* Exposé window overview */
int expose_active;
int expose_count;
Window expose_wins[64]; /* frames shown in overview */
} WM; } WM;
/* core */ /* core */
@@ -151,6 +175,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);
@@ -174,9 +199,14 @@ 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);
/* 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);
+69 -8
View File
@@ -54,9 +54,12 @@ Client *client_add(WM *wm, Window win) {
if (client_find_by_win(wm, win)) return NULL; if (client_find_by_win(wm, win)) return NULL;
Client *c = calloc(1, sizeof(Client)); Client *c = calloc(1, sizeof(Client));
c->win = win; c->win = win;
c->w = wa.width; c->w = wa.width;
c->h = wa.height; c->h = wa.height;
c->desktop = wm->cur_desktop;
c->min_w = MIN_WIN_W;
c->min_h = MIN_WIN_H;
/* use application-requested position if non-zero, otherwise smart place */ /* use application-requested position if non-zero, otherwise smart place */
if (wa.x == 0 && wa.y == 0) { if (wa.x == 0 && wa.y == 0) {
@@ -65,9 +68,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 */
@@ -93,6 +96,36 @@ Client *client_add(WM *wm, Window win) {
} }
} }
/* ICCCM WM_NORMAL_HINTS — size constraints and resize increments */
{
XSizeHints hints;
long supplied = 0;
if (XGetWMNormalHints(wm->dpy, win, &hints, &supplied)) {
if (hints.flags & PMinSize) {
if (hints.min_width > c->min_w) c->min_w = hints.min_width;
if (hints.min_height > c->min_h) c->min_h = hints.min_height;
}
if (hints.flags & PResizeInc) {
c->inc_w = hints.width_inc;
c->inc_h = hints.height_inc;
}
if (hints.flags & PBaseSize) {
c->base_w = hints.base_width;
c->base_h = hints.base_height;
} else if (hints.flags & PMinSize) {
c->base_w = hints.min_width;
c->base_h = hints.min_height;
}
}
}
/* advertise which desktop this window lives on */
{
long desk = wm->cur_desktop;
XChangeProperty(wm->dpy, win, wm->net_wm_desktop, XA_CARDINAL, 32,
PropModeReplace, (unsigned char *)&desk, 1);
}
int fw = (int)c->w + BORDER_W * 2; int fw = (int)c->w + BORDER_W * 2;
int fh = (int)c->h + TITLE_HEIGHT + BORDER_W; int fh = (int)c->h + TITLE_HEIGHT + BORDER_W;
@@ -228,7 +261,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 +313,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;
@@ -295,11 +328,39 @@ 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);
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 -44
View File
@@ -1,33 +1,30 @@
#include "wm.h" #include "wm.h"
/* /*
* Frame layout (3 buttons on right): * Frame layout — macOS style, traffic-light buttons on LEFT:
* *
* ┌─[Title______________]──────[_][□][X]─┐ <- TITLE_HEIGHT * ┌─[●][●][●]──── Title (centered) ────────┐ <- TITLE_HEIGHT
* │ content │ * │ content │
* │ [grip]────┘ * │ [grip]──────┘
*/ */
/* ── hit tests ───────────────────────────────────────────────────────── */ /* ── hit tests ───────────────────────────────────────────────────────── */
int frame_hit_close(Client *c, int x, int y) { int frame_hit_close(Client *c, int x, int y) {
int fw = (int)c->w + BORDER_W * 2; (void)c;
int bx = BTN_CLOSE_X(fw); return (x >= BTN_CLOSE_X && x <= BTN_CLOSE_X + BTN_SIZE &&
return (x >= bx && x <= bx + BTN_SIZE &&
y >= BTN_Y && y <= BTN_Y + BTN_SIZE); y >= BTN_Y && y <= BTN_Y + BTN_SIZE);
} }
int frame_hit_maximize(Client *c, int x, int y) { int frame_hit_maximize(Client *c, int x, int y) {
int fw = (int)c->w + BORDER_W * 2; (void)c;
int bx = BTN_MAX_X(fw); return (x >= BTN_MAX_X && x <= BTN_MAX_X + BTN_SIZE &&
return (x >= bx && x <= bx + BTN_SIZE &&
y >= BTN_Y && y <= BTN_Y + BTN_SIZE); y >= BTN_Y && y <= BTN_Y + BTN_SIZE);
} }
int frame_hit_minimize(Client *c, int x, int y) { int frame_hit_minimize(Client *c, int x, int y) {
int fw = (int)c->w + BORDER_W * 2; (void)c;
int bx = BTN_MIN_X(fw); return (x >= BTN_MIN_X && x <= BTN_MIN_X + BTN_SIZE &&
return (x >= bx && x <= bx + BTN_SIZE &&
y >= BTN_Y && y <= BTN_Y + BTN_SIZE); y >= BTN_Y && y <= BTN_Y + BTN_SIZE);
} }
@@ -70,13 +67,14 @@ static void draw_circle_btn(WM *wm, Drawable d, int bx, int by,
} }
void frame_draw_buttons(WM *wm, Client *c) { void frame_draw_buttons(WM *wm, Client *c) {
int fw = (int)c->w + BORDER_W * 2; /* macOS order left-to-right: close, minimize, maximize */
draw_circle_btn(wm, c->frame, BTN_CLOSE_X(fw), BTN_Y, draw_circle_btn(wm, c->frame, BTN_CLOSE_X, BTN_Y,
color_alloc(wm, COLOR_CLOSE_BTN), 'X'); color_alloc(wm, COLOR_CLOSE_BTN), 'X');
draw_circle_btn(wm, c->frame, BTN_MAX_X(fw), BTN_Y, draw_circle_btn(wm, c->frame, BTN_MIN_X, BTN_Y,
color_alloc(wm, COLOR_MAXIMIZE_BTN), 'O');
draw_circle_btn(wm, c->frame, BTN_MIN_X(fw), BTN_Y,
color_alloc(wm, COLOR_MINIMIZE_BTN), '_'); color_alloc(wm, COLOR_MINIMIZE_BTN), '_');
draw_circle_btn(wm, c->frame, BTN_MAX_X, BTN_Y,
color_alloc(wm, COLOR_MAXIMIZE_BTN), 'O');
(void)c;
} }
/* ── resize grip ─────────────────────────────────────────────────────── */ /* ── resize grip ─────────────────────────────────────────────────────── */
@@ -101,7 +99,6 @@ void frame_draw(WM *wm, Client *c) {
int fw = (int)c->w + BORDER_W * 2; int fw = (int)c->w + BORDER_W * 2;
int fh = (int)c->h + TITLE_HEIGHT + BORDER_W; int fh = (int)c->h + TITLE_HEIGHT + BORDER_W;
unsigned long col_light = color_alloc(wm, COLOR_BORDER_LIGHT);
unsigned long col_dark = color_alloc(wm, COLOR_BORDER_DARK); unsigned long col_dark = color_alloc(wm, COLOR_BORDER_DARK);
/* title bar background */ /* title bar background */
@@ -110,45 +107,42 @@ void frame_draw(WM *wm, Client *c) {
XSetForeground(wm->dpy, wm->gc, col_title); XSetForeground(wm->dpy, wm->gc, col_title);
XFillRectangle(wm->dpy, c->frame, wm->gc, 0, 0, fw, TITLE_HEIGHT); XFillRectangle(wm->dpy, c->frame, wm->gc, 0, 0, fw, TITLE_HEIGHT);
/* subtle gradient effect: lighter top strip (2px) */ /* thin borders (1px, macOS style) */
if (c->focused) {
unsigned long lighter = color_alloc(wm, 0x2A6098);
XSetForeground(wm->dpy, wm->gc, lighter);
XFillRectangle(wm->dpy, c->frame, wm->gc, 0, 0, fw, 2);
}
/* client area background strip (below title) */
XSetForeground(wm->dpy, wm->gc, col_dark); XSetForeground(wm->dpy, wm->gc, col_dark);
XFillRectangle(wm->dpy, c->frame, wm->gc, 0, TITLE_HEIGHT, BORDER_W, fh - TITLE_HEIGHT); XFillRectangle(wm->dpy, c->frame, wm->gc, 0, TITLE_HEIGHT, BORDER_W, fh - TITLE_HEIGHT);
XFillRectangle(wm->dpy, c->frame, wm->gc, fw - BORDER_W, TITLE_HEIGHT, BORDER_W, fh - TITLE_HEIGHT); XFillRectangle(wm->dpy, c->frame, wm->gc, fw - BORDER_W, TITLE_HEIGHT, BORDER_W, fh - TITLE_HEIGHT);
XFillRectangle(wm->dpy, c->frame, wm->gc, 0, fh - BORDER_W, fw, BORDER_W); XFillRectangle(wm->dpy, c->frame, wm->gc, 0, fh - BORDER_W, fw, BORDER_W);
/* outer bevel */ /* subtle 1px separator under titlebar */
XSetForeground(wm->dpy, wm->gc, col_light); unsigned long col_sep = color_alloc(wm, 0x1A1A1A);
XDrawLine(wm->dpy, c->frame, wm->gc, 0, 0, fw - 1, 0); XSetForeground(wm->dpy, wm->gc, col_sep);
XDrawLine(wm->dpy, c->frame, wm->gc, 0, 0, 0, fh - 1);
XSetForeground(wm->dpy, wm->gc, col_dark);
XDrawLine(wm->dpy, c->frame, wm->gc, fw-1, 0, fw-1, fh-1);
XDrawLine(wm->dpy, c->frame, wm->gc, 0, fh-1, fw-1, fh-1);
/* thin separator line under title */
XSetForeground(wm->dpy, wm->gc, col_dark);
XDrawLine(wm->dpy, c->frame, wm->gc, 0, TITLE_HEIGHT - 1, fw - 1, TITLE_HEIGHT - 1); XDrawLine(wm->dpy, c->frame, wm->gc, 0, TITLE_HEIGHT - 1, fw - 1, TITLE_HEIGHT - 1);
/* title text */ /* centered title text (macOS style) */
int btn_area = BTN_SIZE * 3 + BTN_MARGIN * 3 + BORDER_W + 8;
int text_max_w = fw - BORDER_W * 2 - btn_area - 8;
if (text_max_w < 10) text_max_w = 10;
XftDraw *xd = XftDrawCreate(wm->dpy, c->frame, wm->visual, wm->cmap); XftDraw *xd = XftDrawCreate(wm->dpy, c->frame, wm->visual, wm->cmap);
if (xd) { if (xd) {
XftColor col_text; XftColor col_text;
xft_color(wm, c->focused ? COLOR_TITLE_TEXT_ACT : COLOR_TITLE_TEXT_INACT, &col_text); xft_color(wm, c->focused ? COLOR_TITLE_TEXT_ACT : COLOR_TITLE_TEXT_INACT, &col_text);
XRectangle clip = { BORDER_W + 4, 1, (unsigned short)text_max_w, TITLE_HEIGHT - 2 };
XGlyphInfo ext;
XftTextExtentsUtf8(wm->dpy, wm->font_title,
(const FcChar8 *)c->title, strlen(c->title), &ext);
/* center in the full title bar */
int tx = (fw - ext.width) / 2;
/* clamp so text doesn't overlap the left buttons */
int left_btn_end = BTN_MAX_X + BTN_SIZE + 6;
if (tx < left_btn_end) tx = left_btn_end;
int text_max_w = fw - tx - 8;
if (text_max_w < 10) text_max_w = 10;
XRectangle clip = { (short)tx, 1, (unsigned short)text_max_w, TITLE_HEIGHT - 2 };
XftDrawSetClipRectangles(xd, 0, 0, &clip, 1); XftDrawSetClipRectangles(xd, 0, 0, &clip, 1);
int ty = (TITLE_HEIGHT + wm->font_title->ascent - wm->font_title->descent) / 2; int ty = (TITLE_HEIGHT + wm->font_title->ascent - wm->font_title->descent) / 2;
XftDrawStringUtf8(xd, &col_text, wm->font_title, XftDrawStringUtf8(xd, &col_text, wm->font_title,
BORDER_W + 6, ty, tx, ty,
(const FcChar8 *)c->title, strlen(c->title)); (const FcChar8 *)c->title, strlen(c->title));
XftColorFree(wm->dpy, wm->visual, wm->cmap, &col_text); XftColorFree(wm->dpy, wm->visual, wm->cmap, &col_text);
XftDrawDestroy(xd); XftDrawDestroy(xd);
+186 -15
View File
@@ -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);
@@ -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;
@@ -94,11 +95,87 @@ void on_enter_notify(WM *wm, XCrossingEvent *e) {
if (c && !c->minimized) set_cursor(wm, c, e->x, e->y); if (c && !c->minimized) set_cursor(wm, c, e->x, e->y);
} }
static void wm_switch_desktop(WM *wm, int new_desk) {
if (new_desk < 0 || new_desk >= 4 || new_desk == wm->cur_desktop) return;
/* hide windows on old desktop */
for (Client *c = wm->clients; c; c = c->next) {
if (!c->minimized && c->desktop == wm->cur_desktop)
XUnmapWindow(wm->dpy, c->frame);
}
wm->cur_desktop = new_desk;
/* show windows on new desktop */
for (Client *c = wm->clients; c; c = c->next) {
if (!c->minimized && c->desktop == new_desk)
XMapWindow(wm->dpy, c->frame);
}
/* update focus */
if (wm->focused && wm->focused->desktop != new_desk) {
wm->focused->focused = 0;
frame_draw(wm, wm->focused);
wm->focused = NULL;
}
if (!wm->focused) {
for (Client *c = wm->clients; c; c = c->next) {
if (!c->minimized && c->desktop == new_desk) {
client_focus(wm, c);
break;
}
}
}
/* advertise new desktop */
long nd = new_desk;
XChangeProperty(wm->dpy, wm->root, wm->net_current_desktop, XA_CARDINAL, 32,
PropModeReplace, (unsigned char *)&nd, 1);
}
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);
Atom net_wm_desktop = XInternAtom(wm->dpy, "_NET_WM_DESKTOP", False);
/* _NET_CURRENT_DESKTOP → switch virtual desktop */
if (e->message_type == wm->net_current_desktop && e->window == wm->root) {
wm_switch_desktop(wm, (int)e->data.l[0]);
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);
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;
} }
} }
@@ -140,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);
@@ -247,8 +332,18 @@ void on_motion_notify(WM *wm, XMotionEvent *e) {
} else { } else {
int nw = (int)wm->drag_win_w + dx; int nw = (int)wm->drag_win_w + dx;
int nh = (int)wm->drag_win_h + dy; int nh = (int)wm->drag_win_h + dy;
if (nw < MIN_WIN_W) nw = MIN_WIN_W; /* clamp to minimum */
if (nh < MIN_WIN_H) nh = MIN_WIN_H; if (nw < c->min_w) nw = c->min_w;
if (nh < c->min_h) nh = c->min_h;
/* snap to resize-increment grid (e.g. terminal character cells) */
if (c->inc_w > 1) {
nw = c->base_w + ((nw - c->base_w) / c->inc_w) * c->inc_w;
if (nw < c->min_w) nw += c->inc_w;
}
if (c->inc_h > 1) {
nh = c->base_h + ((nh - c->base_h) / c->inc_h) * c->inc_h;
if (nh < c->min_h) nh += c->inc_h;
}
c->w = (unsigned)nw; c->h = (unsigned)nh; c->w = (unsigned)nw; c->h = (unsigned)nh;
XResizeWindow(wm->dpy, c->frame, c->w+BORDER_W*2, c->h+TITLE_HEIGHT+BORDER_W); XResizeWindow(wm->dpy, c->frame, c->w+BORDER_W*2, c->h+TITLE_HEIGHT+BORDER_W);
XResizeWindow(wm->dpy, c->win, c->w, c->h); XResizeWindow(wm->dpy, c->win, c->w, c->h);
@@ -263,10 +358,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;
} }
@@ -288,16 +392,47 @@ void on_key_press(WM *wm, XKeyEvent *e) {
if ((e->state & Mod1Mask) && sym == XK_F1) if ((e->state & Mod1Mask) && sym == XK_F1)
system("orionlauncher &"); system("orionlauncher &");
/* PrintScreen — full screenshot to ~/Pictures */ /* PrintScreen — screenshot */
if (sym == XK_Print) { if (sym == XK_Print) {
if (e->state & ShiftMask) if (e->state & ShiftMask)
system("mkdir -p ~/Pictures && scrot -s ~/Pictures/screenshot_%Y%m%d_%H%M%S.png" system("orionshot region &");
" && notify-send 'Screenshot' 'Region saved to ~/Pictures' &");
else else
system("mkdir -p ~/Pictures && scrot ~/Pictures/screenshot_%Y%m%d_%H%M%S.png" system("orionshot full &");
" && notify-send 'Screenshot' 'Saved to ~/Pictures' &");
} }
/* XF86 media keys */
if (sym == XF86XK_AudioRaiseVolume)
system("pactl set-sink-volume @DEFAULT_SINK@ +5% 2>/dev/null; "
"pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | "
"grep -oP '[0-9]+%' | head -1 | "
"xargs -I{} notify-send -t 1500 'Volume' '{}' 2>/dev/null &");
if (sym == XF86XK_AudioLowerVolume)
system("pactl set-sink-volume @DEFAULT_SINK@ -5% 2>/dev/null; "
"pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | "
"grep -oP '[0-9]+%' | head -1 | "
"xargs -I{} notify-send -t 1500 'Volume' '{}' 2>/dev/null &");
if (sym == XF86XK_AudioMute)
system("pactl set-sink-mute @DEFAULT_SINK@ toggle 2>/dev/null; "
"notify-send -t 1500 'Volume' 'Muted toggled' 2>/dev/null &");
if (sym == XF86XK_AudioPlay)
system("playerctl play-pause 2>/dev/null &");
if (sym == XF86XK_AudioNext)
system("playerctl next 2>/dev/null &");
if (sym == XF86XK_AudioPrev)
system("playerctl previous 2>/dev/null &");
if (sym == XF86XK_MonBrightnessUp)
system("brightnessctl set +10% 2>/dev/null; "
"notify-send -t 1200 'Brightness' "
"\"$(brightnessctl g 2>/dev/null)\" 2>/dev/null &");
if (sym == XF86XK_MonBrightnessDown)
system("brightnessctl set 10%- 2>/dev/null; "
"notify-send -t 1200 'Brightness' "
"\"$(brightnessctl g 2>/dev/null)\" 2>/dev/null &");
/* Super+V — clipboard manager */
if ((e->state & Mod4Mask) && sym == XK_v)
system("orionclip --show &");
/* Super+Left — snap left */ /* Super+Left — snap left */
if ((e->state & Mod4Mask) && sym == XK_Left && wm->focused) if ((e->state & Mod4Mask) && sym == XK_Left && wm->focused)
client_snap_left(wm, wm->focused); client_snap_left(wm, wm->focused);
@@ -314,9 +449,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) {
+93
View File
@@ -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);
}
+15 -15
View File
@@ -1,7 +1,7 @@
#include "wm.h" #include "wm.h"
#define MENU_ITEM_H 24 #define MENU_ITEM_H 26
#define MENU_W 220 #define MENU_W 230
#define MENU_PADDING 5 #define MENU_PADDING 5
static const MenuItem MENU_ITEMS[] = { static const MenuItem MENU_ITEMS[] = {
@@ -51,7 +51,7 @@ void menu_show(WM *wm, int x, int y) {
XSetWindowAttributes swa; XSetWindowAttributes swa;
swa.override_redirect = True; swa.override_redirect = True;
swa.background_pixel = color_alloc(wm, 0x1C2030); /* dark menu bg */ swa.background_pixel = color_alloc(wm, 0x2C2C2C); /* macOS dark menu bg */
swa.event_mask = ExposureMask | ButtonPressMask swa.event_mask = ExposureMask | ButtonPressMask
| ButtonReleaseMask | PointerMotionMask | ButtonReleaseMask | PointerMotionMask
| LeaveWindowMask; | LeaveWindowMask;
@@ -65,8 +65,8 @@ void menu_show(WM *wm, int x, int y) {
&swa &swa
); );
/* 1px accent border */ /* subtle 1px border (macOS-like) */
XSetWindowBorder(wm->dpy, wm->menu_win, color_alloc(wm, COLOR_BORDER_LIGHT)); XSetWindowBorder(wm->dpy, wm->menu_win, color_alloc(wm, 0x505050));
XMapRaised(wm->dpy, wm->menu_win); XMapRaised(wm->dpy, wm->menu_win);
wm->menu_visible = 1; wm->menu_visible = 1;
@@ -110,29 +110,29 @@ void menu_draw(WM *wm) {
if (!xd) return; if (!xd) return;
XftColor col_normal, col_hover_bg, col_hover_text, col_sep; XftColor col_normal, col_hover_bg, col_hover_text, col_sep;
xft_color(wm, 0xC8D4E8, &col_normal); /* light text on dark bg */ xft_color(wm, 0xE8E8E8, &col_normal); /* macOS light menu text */
xft_color(wm, 0x2060A0, &col_hover_bg); /* blue highlight */ xft_color(wm, 0x4A90D9, &col_hover_bg); /* macOS blue selection */
xft_color(wm, 0xF0F4FF, &col_hover_text); xft_color(wm, 0xFFFFFF, &col_hover_text);
xft_color(wm, 0x303848, &col_sep); xft_color(wm, 0x444444, &col_sep);
/* clear background */ /* clear background */
XSetForeground(wm->dpy, wm->gc, color_alloc(wm, 0x1C2030)); XSetForeground(wm->dpy, wm->gc, color_alloc(wm, 0x2C2C2C));
XFillRectangle(wm->dpy, wm->menu_win, wm->gc, 0, 0, MENU_W, menu_height()); XFillRectangle(wm->dpy, wm->menu_win, wm->gc, 0, 0, MENU_W, menu_height());
for (int i = 0; i < MENU_COUNT; i++) { for (int i = 0; i < MENU_COUNT; i++) {
int iy = item_y(i); int iy = item_y(i);
if (is_separator(i)) { if (is_separator(i)) {
XSetForeground(wm->dpy, wm->gc, color_alloc(wm, 0x2A3040)); XSetForeground(wm->dpy, wm->gc, color_alloc(wm, 0x3C3C3C));
XDrawLine(wm->dpy, wm->menu_win, wm->gc, 8, iy + 4, MENU_W - 8, iy + 4); XDrawLine(wm->dpy, wm->menu_win, wm->gc, 8, iy + 4, MENU_W - 8, iy + 4);
continue; continue;
} }
/* hover highlight */ /* hover highlight — macOS blue selection pill */
if (i == wm->menu_hovered && wm->menu_hovered >= 0) { if (i == wm->menu_hovered && wm->menu_hovered >= 0) {
XSetForeground(wm->dpy, wm->gc, color_alloc(wm, 0x4A6888)); XSetForeground(wm->dpy, wm->gc, color_alloc(wm, 0x4A90D9));
XFillRectangle(wm->dpy, wm->menu_win, wm->gc, XFillRectangle(wm->dpy, wm->menu_win, wm->gc,
2, iy, MENU_W - 4, MENU_ITEM_H); 4, iy + 1, MENU_W - 8, MENU_ITEM_H - 2);
} }
XftColor *tc = (i == wm->menu_hovered) ? &col_hover_text : &col_normal; XftColor *tc = (i == wm->menu_hovered) ? &col_hover_text : &col_normal;
@@ -142,7 +142,7 @@ void menu_draw(WM *wm) {
strlen(MENU_ITEMS[i].label), &ext); strlen(MENU_ITEMS[i].label), &ext);
int ty = iy + (MENU_ITEM_H - ext.height) / 2 + ext.height - ext.y; int ty = iy + (MENU_ITEM_H - ext.height) / 2 + ext.height - ext.y;
XftDrawStringUtf8(xd, tc, wm->font_menu, XftDrawStringUtf8(xd, tc, wm->font_menu,
MENU_PADDING + 4, ty, MENU_PADDING + 10, ty,
(const FcChar8 *)MENU_ITEMS[i].label, (const FcChar8 *)MENU_ITEMS[i].label,
strlen(MENU_ITEMS[i].label)); strlen(MENU_ITEMS[i].label));
} }
+34 -12
View File
@@ -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);
+122 -17
View File
@@ -1,5 +1,67 @@
#include "wm.h" #include "wm.h"
/* ── autostart ───────────────────────────────────────────────────── */
static void wm_run_autostart(void) {
const char *home = getenv("HOME");
if (!home) return;
char path[512];
snprintf(path, sizeof(path), "%s/.config/orionos/autostart", home);
FILE *f = fopen(path, "r");
if (!f) return;
char line[512];
while (fgets(line, sizeof(line), f)) {
size_t len = strlen(line);
while (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r' ||
line[len-1] == ' ' || line[len-1] == '\t'))
line[--len] = '\0';
if (len == 0 || line[0] == '#') continue;
char cmd[560];
snprintf(cmd, sizeof(cmd), "%s &", line);
system(cmd);
}
fclose(f);
}
/* ── hot corners ─────────────────────────────────────────────────── */
static void hot_corner_check(WM *wm) {
Window root_ret, child;
int root_x, root_y, win_x, win_y;
unsigned int mask;
XQueryPointer(wm->dpy, wm->root, &root_ret, &child,
&root_x, &root_y, &win_x, &win_y, &mask);
int sw = DisplayWidth(wm->dpy, wm->screen);
int sh = DisplayHeight(wm->dpy, wm->screen);
int corner = 0;
if (root_x <= 1 && root_y <= 1) corner = 1; /* TL: Exposé */
else if (root_x >= sw - DESKBAR_W - 2 && root_y <= 1) corner = 2; /* TR: show desktop */
else if (root_x <= 1 && root_y >= sh - 2) corner = 3; /* BL: launcher */
else if (root_x >= sw - DESKBAR_W - 2 && root_y >= sh - 2) corner = 4; /* BR: lock */
if (corner == wm->hot_corner_last) return;
wm->hot_corner_last = corner;
if (corner == 0) return; /* just left a corner, no action needed */
switch (corner) {
case 1: /* top-left → Exposé */
if (!wm->expose_active) expose_show(wm);
break;
case 2: /* top-right → show desktop (minimize all on current desktop) */
if (wm->expose_active) { expose_hide(wm, None); break; }
for (Client *c = wm->clients; c; c = c->next)
if (!c->minimized && c->desktop == wm->cur_desktop)
client_minimize(wm, c);
break;
case 3: /* bottom-left → launcher */
system("orionlauncher &");
break;
case 4: /* bottom-right → lock screen */
system("orionlock &");
break;
}
}
static int x_error_handler(Display *dpy, XErrorEvent *e) { static int x_error_handler(Display *dpy, XErrorEvent *e) {
if (e->error_code == BadWindow || e->error_code == BadAccess) if (e->error_code == BadWindow || e->error_code == BadAccess)
return 0; return 0;
@@ -48,6 +110,8 @@ void wm_init(WM *wm) {
wm->net_wm_state_hidden = XInternAtom(wm->dpy, "_NET_WM_STATE_HIDDEN", False); wm->net_wm_state_hidden = XInternAtom(wm->dpy, "_NET_WM_STATE_HIDDEN", False);
wm->net_wm_state_maximized_vert = XInternAtom(wm->dpy, "_NET_WM_STATE_MAXIMIZED_VERT", False); wm->net_wm_state_maximized_vert = XInternAtom(wm->dpy, "_NET_WM_STATE_MAXIMIZED_VERT", False);
wm->net_wm_state_maximized_horz = XInternAtom(wm->dpy, "_NET_WM_STATE_MAXIMIZED_HORZ", False); wm->net_wm_state_maximized_horz = XInternAtom(wm->dpy, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
wm->net_wm_desktop = XInternAtom(wm->dpy, "_NET_WM_DESKTOP", False);
wm->net_current_desktop = XInternAtom(wm->dpy, "_NET_CURRENT_DESKTOP", False);
/* advertise basic EWMH support */ /* advertise basic EWMH support */
Atom net_supported = XInternAtom(wm->dpy, "_NET_SUPPORTED", False); Atom net_supported = XInternAtom(wm->dpy, "_NET_SUPPORTED", False);
@@ -55,10 +119,12 @@ void wm_init(WM *wm) {
wm->net_wm_state, wm->net_wm_state_hidden, wm->net_wm_state, wm->net_wm_state_hidden,
wm->net_wm_state_maximized_vert, wm->net_wm_state_maximized_vert,
wm->net_wm_state_maximized_horz, wm->net_wm_state_maximized_horz,
wm->net_wm_desktop,
wm->net_current_desktop,
XInternAtom(wm->dpy, "_NET_ACTIVE_WINDOW", False), XInternAtom(wm->dpy, "_NET_ACTIVE_WINDOW", False),
XInternAtom(wm->dpy, "_NET_CLIENT_LIST", False), XInternAtom(wm->dpy, "_NET_CLIENT_LIST", False),
XInternAtom(wm->dpy, "_NET_CLOSE_WINDOW", False),
XInternAtom(wm->dpy, "_NET_NUMBER_OF_DESKTOPS", False), XInternAtom(wm->dpy, "_NET_NUMBER_OF_DESKTOPS", False),
XInternAtom(wm->dpy, "_NET_CURRENT_DESKTOP", False),
XInternAtom(wm->dpy, "_NET_WM_NAME", False), XInternAtom(wm->dpy, "_NET_WM_NAME", False),
}; };
XChangeProperty(wm->dpy, wm->root, net_supported, XA_ATOM, 32, XChangeProperty(wm->dpy, wm->root, net_supported, XA_ATOM, 32,
@@ -90,6 +156,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 */
@@ -98,7 +165,29 @@ 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 },
/* XF86 media keys */
{ XF86XK_AudioRaiseVolume, 0 },
{ XF86XK_AudioLowerVolume, 0 },
{ XF86XK_AudioMute, 0 },
{ XF86XK_AudioPlay, 0 },
{ XF86XK_AudioNext, 0 },
{ XF86XK_AudioPrev, 0 },
{ XF86XK_MonBrightnessUp, 0 },
{ XF86XK_MonBrightnessDown, 0 },
/* Super+V: clipboard manager */
{ XK_v, Mod4Mask },
}; };
for (int i = 0; i < (int)(sizeof(grabs)/sizeof(grabs[0])); i++) { for (int i = 0; i < (int)(sizeof(grabs)/sizeof(grabs[0])); i++) {
KeyCode kc = XKeysymToKeycode(wm->dpy, grabs[i].sym); KeyCode kc = XKeysymToKeycode(wm->dpy, grabs[i].sym);
@@ -122,28 +211,44 @@ void wm_init(WM *wm) {
XSync(wm->dpy, False); XSync(wm->dpy, False);
fprintf(stderr, "orionwm: started\n"); fprintf(stderr, "orionwm: started\n");
wm_run_autostart();
} }
void wm_run(WM *wm) { void wm_run(WM *wm) {
int xfd = ConnectionNumber(wm->dpy);
XEvent e; XEvent e;
for (;;) { for (;;) {
XNextEvent(wm->dpy, &e); /* drain all pending X events */
switch (e.type) { while (XPending(wm->dpy)) {
case MapRequest: on_map_request(wm, &e.xmaprequest); break; XNextEvent(wm->dpy, &e);
case UnmapNotify: on_unmap_notify(wm, &e.xunmap); break; switch (e.type) {
case DestroyNotify: on_destroy_notify(wm, &e.xdestroywindow); break; case MapRequest: on_map_request(wm, &e.xmaprequest); break;
case ConfigureRequest: on_configure_request(wm, &e.xconfigurerequest); break; case UnmapNotify: on_unmap_notify(wm, &e.xunmap); break;
case Expose: on_expose(wm, &e.xexpose); break; case DestroyNotify: on_destroy_notify(wm, &e.xdestroywindow); break;
case ButtonPress: on_button_press(wm, &e.xbutton); break; case ConfigureRequest: on_configure_request(wm, &e.xconfigurerequest); break;
case ButtonRelease: on_button_release(wm, &e.xbutton); break; case Expose: on_expose(wm, &e.xexpose); break;
case MotionNotify: on_motion_notify(wm, &e.xmotion); break; case ButtonPress: on_button_press(wm, &e.xbutton); break;
case KeyPress: on_key_press(wm, &e.xkey); break; case ButtonRelease: on_button_release(wm, &e.xbutton); break;
case KeyRelease: on_key_release(wm, &e.xkey); break; case MotionNotify: on_motion_notify(wm, &e.xmotion); break;
case EnterNotify: on_enter_notify(wm, &e.xcrossing); break; case KeyPress: on_key_press(wm, &e.xkey); break;
case ClientMessage: on_client_message(wm, &e.xclient); break; case KeyRelease: on_key_release(wm, &e.xkey); break;
case PropertyNotify: on_property_notify(wm, &e.xproperty); break; case EnterNotify: on_enter_notify(wm, &e.xcrossing); break;
default: break; case ClientMessage: on_client_message(wm, &e.xclient); break;
case PropertyNotify: on_property_notify(wm, &e.xproperty); break;
default: break;
}
} }
/* poll hot corners every 200ms */
hot_corner_check(wm);
fd_set fds;
FD_ZERO(&fds);
FD_SET(xfd, &fds);
struct timeval tv = { 0, 200000 };
select(xfd + 1, &fds, NULL, NULL, &tv);
} }
} }