Files
OrionOS/orionlock/orionlock.c
T
Claude 2a42b826fb Add 6 desktop features: workspace shortcuts, Exposé, deskbar icons, notifications, screen lock, move-to-desktop
- Super+1-4: switch virtual desktop; Super+Shift+1-4: move focused window to desktop N
- Super+Space: Exposé window overview (tiled grid), click to select, Escape to cancel
- wm/src/expose.c: expose_show/expose_hide using ceil(sqrt) grid layout with saved/restored geometry
- client_move_to_desktop(): moves window between desktops, fires _NET_WM_DESKTOP update
- on_client_message: handles _NET_WM_DESKTOP to move windows from external requests
- deskbar: optional imlib2 app icon rendering (Papirus-Dark → Papirus → hicolor → pixmaps fallback)
- deskbar: XGetClassHint populates cls field for better icon lookup
- deskbar: context menu extended with separator + "→ Desktop 1-4" items via _NET_WM_DESKTOP
- orionnotify/: freedesktop.org notification daemon (libdbus-1), stacks up to 5 popups top-right
- orionlock/: PAM-authenticated fullscreen screen lock with clock, date, password dots
- Lock Screen menu item wired to orionlock
- Root Makefile includes orionnotify and orionlock targets

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GHiBQKPfUoaJgCKuA9op1H
2026-08-30 20:14:45 +00:00

272 lines
9.1 KiB
C

/*
* 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;
}