- Rename all binaries: skywm→orionwm, skydeskbar→oriondeskbar, skylauncher→orionlauncher, skyinstall/skyinstall-gui→orioninstall* - Rename theme directory: themes/SkyOS/ → themes/OrionOS/ - Update all string literals, window titles, menu labels, and comments - Update ISO build: hostname→orionos, user→orion, all paths/labels - Add Plymouth boot splash theme (orionos) with Navy+Blue palette - Update GTK2/GTK3 theme names, wallpaper output name - Update .gitignore, root Makefile, build-iso.sh for new binary names Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1122 lines
43 KiB
C
1122 lines
43 KiB
C
#include "deskbar.h"
|
|
|
|
#define TRAY_REQUEST_DOCK 0
|
|
#define XEMBED_EMBEDDED_NOTIFY 0
|
|
#define XEMBED_VERSION 0
|
|
|
|
/* ── layout ───────────────────────────────────────────────────────────── */
|
|
|
|
int db_apps_y(void) { return DB_TOP_H + 1; }
|
|
|
|
int db_ws_y(Deskbar *db) {
|
|
return db_apps_y() + db->napps * DB_ITEM_H + 1;
|
|
}
|
|
|
|
static int db_tray_h(Deskbar *db) { return db->n_tray_icons > 0 ? DB_TRAY_H : 0; }
|
|
static int db_tray_y(Deskbar *db) { return db_ws_y(db) + DB_WS_H + 1; }
|
|
static int db_status_y(Deskbar *db) {
|
|
int th = db_tray_h(db);
|
|
return db_tray_y(db) + th + (th > 0 ? 1 : 0);
|
|
}
|
|
|
|
void db_resize(Deskbar *db) {
|
|
db->bar_h = db_status_y(db) + DB_STATUS_H + 2;
|
|
int sw = DisplayWidth(db->dpy, db->screen);
|
|
XMoveResizeWindow(db->dpy, db->win, sw - DB_W, 0, DB_W, db->bar_h);
|
|
}
|
|
|
|
/* ── colors ───────────────────────────────────────────────────────────── */
|
|
|
|
unsigned long db_color(Deskbar *db, 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(db->dpy, db->cmap, &c);
|
|
return c.pixel;
|
|
}
|
|
|
|
void db_xft_color(Deskbar *db, 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(db->dpy, db->visual, db->cmap, &rc, out);
|
|
}
|
|
|
|
static unsigned long db_app_icon_color(const char *title) {
|
|
unsigned int h = 0;
|
|
for (const char *p = title; *p; p++) h = h * 31 + (unsigned char)*p;
|
|
return DB_ICON_COLORS[h % DB_ICON_NCOLORS];
|
|
}
|
|
|
|
/* ── EWMH helpers ─────────────────────────────────────────────────────── */
|
|
|
|
static long ewmh_get_long(Deskbar *db, Window win, Atom prop) {
|
|
Atom type; int fmt; unsigned long n, after;
|
|
unsigned char *data = NULL;
|
|
long val = -1;
|
|
if (XGetWindowProperty(db->dpy, win, prop, 0, 1, False,
|
|
XA_CARDINAL, &type, &fmt, &n, &after, &data)
|
|
== Success && data) {
|
|
val = *(long *)data;
|
|
XFree(data);
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static void ewmh_get_title(Deskbar *db, Window win, char *buf, int bufsz) {
|
|
Atom type; int fmt; unsigned long n, after;
|
|
unsigned char *data = NULL;
|
|
buf[0] = '\0';
|
|
if (XGetWindowProperty(db->dpy, win, db->net_wm_name, 0, 256, False,
|
|
db->utf8_string, &type, &fmt, &n, &after, &data)
|
|
== Success && data && n > 0) {
|
|
snprintf(buf, bufsz, "%.*s", (int)n, data);
|
|
XFree(data);
|
|
return;
|
|
}
|
|
if (data) XFree(data);
|
|
char *name = NULL;
|
|
if (XFetchName(db->dpy, win, &name) && name) {
|
|
snprintf(buf, bufsz, "%s", name);
|
|
XFree(name);
|
|
}
|
|
}
|
|
|
|
void db_update_apps(Deskbar *db) {
|
|
Atom type; int fmt; unsigned long n, after;
|
|
unsigned char *data = NULL;
|
|
db->napps = 0;
|
|
|
|
if (XGetWindowProperty(db->dpy, db->root, db->net_client_list,
|
|
0, DB_MAX_APPS, False, XA_WINDOW,
|
|
&type, &fmt, &n, &after, &data)
|
|
!= Success || !data) return;
|
|
|
|
Window *wins = (Window *)data;
|
|
long active_win = -1;
|
|
{
|
|
unsigned char *aw = NULL;
|
|
unsigned long an, aa; Atom at; int af;
|
|
if (XGetWindowProperty(db->dpy, db->root, db->net_active_window,
|
|
0, 1, False, XA_WINDOW, &at, &af, &an, &aa, &aw)
|
|
== Success && aw) {
|
|
active_win = *(long *)aw;
|
|
XFree(aw);
|
|
}
|
|
}
|
|
|
|
Atom net_wm_state = XInternAtom(db->dpy, "_NET_WM_STATE", False);
|
|
Atom net_wm_state_hidden = XInternAtom(db->dpy, "_NET_WM_STATE_HIDDEN", False);
|
|
|
|
for (unsigned long i = 0; i < n && db->napps < DB_MAX_APPS; i++) {
|
|
db->apps[db->napps].win = wins[i];
|
|
db->apps[db->napps].focused = (wins[i] == (Window)active_win);
|
|
db->apps[db->napps].minimized = 0;
|
|
|
|
{
|
|
Atom atype; int afmt; unsigned long an, aa;
|
|
unsigned char *adata = NULL;
|
|
if (XGetWindowProperty(db->dpy, wins[i], net_wm_state,
|
|
0, 32, False, XA_ATOM,
|
|
&atype, &afmt, &an, &aa, &adata)
|
|
== Success && adata) {
|
|
Atom *atoms = (Atom *)adata;
|
|
for (unsigned long j = 0; j < an; j++)
|
|
if (atoms[j] == net_wm_state_hidden)
|
|
db->apps[db->napps].minimized = 1;
|
|
XFree(adata);
|
|
}
|
|
}
|
|
|
|
ewmh_get_title(db, wins[i],
|
|
db->apps[db->napps].title,
|
|
sizeof(db->apps[db->napps].title));
|
|
if (db->apps[db->napps].title[0] != '\0')
|
|
db->napps++;
|
|
}
|
|
XFree(data);
|
|
}
|
|
|
|
void db_update_workspace(Deskbar *db) {
|
|
long cws = ewmh_get_long(db, db->root, db->net_current_desktop);
|
|
long nws = ewmh_get_long(db, db->root, db->net_number_of_desktops);
|
|
db->cur_ws = (cws >= 0) ? (int)cws : 0;
|
|
db->num_ws = (nws > 0) ? (int)nws : 4;
|
|
}
|
|
|
|
/* ── system tray (XEmbed) ─────────────────────────────────────────────── */
|
|
|
|
static void db_tray_reposition(Deskbar *db) {
|
|
int ty = db_tray_y(db);
|
|
int iy = (DB_TRAY_H - DB_TRAY_ICON_SZ) / 2;
|
|
for (int i = 0; i < db->n_tray_icons; i++) {
|
|
int ix = 4 + i * (DB_TRAY_ICON_SZ + 4);
|
|
XMoveWindow(db->dpy, db->tray_icons[i], ix, ty + iy);
|
|
}
|
|
}
|
|
|
|
static void db_tray_dock(Deskbar *db, Window icon_win) {
|
|
if (db->n_tray_icons >= DB_TRAY_MAX) return;
|
|
for (int i = 0; i < db->n_tray_icons; i++)
|
|
if (db->tray_icons[i] == icon_win) return;
|
|
|
|
int ty = db_tray_y(db);
|
|
int iy = (DB_TRAY_H - DB_TRAY_ICON_SZ) / 2;
|
|
int ix = 4 + db->n_tray_icons * (DB_TRAY_ICON_SZ + 4);
|
|
int was_empty = (db->n_tray_icons == 0);
|
|
|
|
XSelectInput(db->dpy, icon_win, StructureNotifyMask);
|
|
XReparentWindow(db->dpy, icon_win, db->win, ix, ty + iy);
|
|
XResizeWindow(db->dpy, icon_win, DB_TRAY_ICON_SZ, DB_TRAY_ICON_SZ);
|
|
XMapWindow(db->dpy, icon_win);
|
|
|
|
/* XEMBED_EMBEDDED_NOTIFY message to the icon */
|
|
XEvent ev = {0};
|
|
ev.xclient.type = ClientMessage;
|
|
ev.xclient.window = icon_win;
|
|
ev.xclient.message_type = db->atom_xembed;
|
|
ev.xclient.format = 32;
|
|
ev.xclient.data.l[0] = CurrentTime;
|
|
ev.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY;
|
|
ev.xclient.data.l[2] = 0;
|
|
ev.xclient.data.l[3] = db->win;
|
|
ev.xclient.data.l[4] = XEMBED_VERSION;
|
|
XSendEvent(db->dpy, icon_win, False, NoEventMask, &ev);
|
|
|
|
db->tray_icons[db->n_tray_icons++] = icon_win;
|
|
if (was_empty) db_resize(db);
|
|
db_draw(db);
|
|
}
|
|
|
|
static void db_tray_remove(Deskbar *db, Window icon_win) {
|
|
int pos = -1;
|
|
for (int i = 0; i < db->n_tray_icons; i++)
|
|
if (db->tray_icons[i] == icon_win) { pos = i; break; }
|
|
if (pos < 0) return;
|
|
|
|
memmove(&db->tray_icons[pos], &db->tray_icons[pos + 1],
|
|
(db->n_tray_icons - pos - 1) * sizeof(Window));
|
|
db->n_tray_icons--;
|
|
|
|
db_tray_reposition(db);
|
|
if (db->n_tray_icons == 0) db_resize(db);
|
|
db_draw(db);
|
|
}
|
|
|
|
/* ── system status ────────────────────────────────────────────────────── */
|
|
|
|
static int read_cpu_percent(void) {
|
|
static long prev_idle = 0, prev_total = 0;
|
|
FILE *f = fopen("/proc/stat", "r");
|
|
if (!f) return -1;
|
|
long u, n, s, idle, iow, irq, sirq;
|
|
int ok = fscanf(f, "cpu %ld %ld %ld %ld %ld %ld %ld",
|
|
&u, &n, &s, &idle, &iow, &irq, &sirq);
|
|
fclose(f);
|
|
if (ok < 7) return -1;
|
|
long total = u + n + s + idle + iow + irq + sirq;
|
|
long d_idle = idle - prev_idle;
|
|
long d_total = total - prev_total;
|
|
prev_idle = idle;
|
|
prev_total = total;
|
|
if (d_total == 0) return 0;
|
|
return (int)(100 * (d_total - d_idle) / d_total);
|
|
}
|
|
|
|
static int read_volume_percent(void) {
|
|
static int cached = -1;
|
|
static int ticks = 0;
|
|
if (ticks-- > 0) return cached;
|
|
ticks = 4; /* re-poll every 5 seconds */
|
|
FILE *f = popen("pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null", "r");
|
|
if (!f) return cached;
|
|
char line[256];
|
|
cached = -1;
|
|
if (fgets(line, sizeof(line), f)) {
|
|
char *p = strstr(line, "/ ");
|
|
if (p) cached = atoi(p + 2);
|
|
}
|
|
pclose(f);
|
|
return cached;
|
|
}
|
|
|
|
static int read_battery_percent(void) {
|
|
FILE *f;
|
|
char path[80];
|
|
for (int i = 0; i <= 1; i++) {
|
|
snprintf(path, sizeof(path),
|
|
"/sys/class/power_supply/BAT%d/capacity", i);
|
|
f = fopen(path, "r");
|
|
if (f) { int v = -1; fscanf(f, "%d", &v); fclose(f); return v; }
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static char read_battery_status(void) {
|
|
FILE *f;
|
|
char path[80], status[16];
|
|
for (int i = 0; i <= 1; i++) {
|
|
snprintf(path, sizeof(path),
|
|
"/sys/class/power_supply/BAT%d/status", i);
|
|
f = fopen(path, "r");
|
|
if (f) {
|
|
status[0] = '\0';
|
|
fscanf(f, "%15s", status);
|
|
fclose(f);
|
|
return status[0]; /* 'C'harging, 'D'ischarging, 'F'ull */
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int read_mem_percent(void) {
|
|
FILE *f = fopen("/proc/meminfo", "r");
|
|
if (!f) return -1;
|
|
long total = 0, available = 0;
|
|
char line[128];
|
|
while (fgets(line, sizeof(line), f)) {
|
|
long v;
|
|
if (sscanf(line, "MemTotal: %ld", &v) == 1) total = v;
|
|
if (sscanf(line, "MemAvailable: %ld", &v) == 1) available = v;
|
|
if (total && available) break;
|
|
}
|
|
fclose(f);
|
|
if (!total) return -1;
|
|
return (int)(100 * (total - available) / total);
|
|
}
|
|
|
|
/* ── drawing ──────────────────────────────────────────────────────────── */
|
|
|
|
static void draw_str(Deskbar *db, XftDraw *xd, XftFont *font,
|
|
unsigned long rgb, int x, int y,
|
|
const char *str, int clip_x, int clip_w) {
|
|
XftColor col;
|
|
db_xft_color(db, rgb, &col);
|
|
if (clip_w > 0) {
|
|
XRectangle r = { (short)clip_x, 0,
|
|
(unsigned short)clip_w,
|
|
(unsigned short)db->bar_h };
|
|
XftDrawSetClipRectangles(xd, 0, 0, &r, 1);
|
|
}
|
|
XGlyphInfo ext;
|
|
XftTextExtentsUtf8(db->dpy, font, (const FcChar8 *)str, strlen(str), &ext);
|
|
XftDrawStringUtf8(xd, &col, font, x, y + ext.height - ext.y,
|
|
(const FcChar8 *)str, strlen(str));
|
|
if (clip_w > 0) XftDrawSetClip(xd, None);
|
|
XftColorFree(db->dpy, db->visual, db->cmap, &col);
|
|
}
|
|
|
|
static void draw_str_centered(Deskbar *db, XftDraw *xd, XftFont *font,
|
|
unsigned long rgb,
|
|
int strip_x, int strip_y, int strip_w, int strip_h,
|
|
const char *str) {
|
|
XGlyphInfo ext;
|
|
XftTextExtentsUtf8(db->dpy, font, (const FcChar8 *)str, strlen(str), &ext);
|
|
int tx = strip_x + (strip_w - (int)ext.width) / 2;
|
|
int ty = strip_y + (strip_h - (int)ext.height) / 2;
|
|
draw_str(db, xd, font, rgb, tx, ty, str, 0, 0);
|
|
}
|
|
|
|
/* Draw a letter icon (first char of title) */
|
|
static void draw_letter_icon(Deskbar *db, XftDraw *xd,
|
|
int ix, int iy, const char *title,
|
|
unsigned long bg, int mini) {
|
|
/* background square */
|
|
unsigned long icon_col = mini ? db_color(db, 0x2A2E3A)
|
|
: db_color(db, bg);
|
|
XSetForeground(db->dpy, db->gc, icon_col);
|
|
XFillRectangle(db->dpy, db->win, db->gc, ix, iy, DB_ICON_SZ, DB_ICON_SZ);
|
|
|
|
/* subtle border */
|
|
unsigned long border_col = mini ? db_color(db, 0x383C4A) : db_color(db, bg + 0x101010);
|
|
XSetForeground(db->dpy, db->gc, border_col);
|
|
XDrawRectangle(db->dpy, db->win, db->gc, ix, iy, DB_ICON_SZ - 1, DB_ICON_SZ - 1);
|
|
|
|
/* letter */
|
|
char letter[2] = { title[0] ? (char)((title[0] & 0x5F) | 0x40) : '?', '\0' };
|
|
/* convert to uppercase */
|
|
if (letter[0] >= 'a' && letter[0] <= 'z') letter[0] -= 32;
|
|
unsigned long txt_col = mini ? DB_COL_ITEM_MINI : DB_COL_ITEM_HTXT;
|
|
XGlyphInfo ext;
|
|
XftTextExtentsUtf8(db->dpy, db->font_icon, (const FcChar8 *)letter, 1, &ext);
|
|
int lx = ix + (DB_ICON_SZ - ext.width) / 2;
|
|
int ly = iy + (DB_ICON_SZ - ext.height) / 2;
|
|
draw_str(db, xd, db->font_icon, txt_col, lx, ly, letter, 0, 0);
|
|
}
|
|
|
|
void db_draw(Deskbar *db) {
|
|
XftDraw *xd = XftDrawCreate(db->dpy, db->win, db->visual, db->cmap);
|
|
|
|
/* ── TOP STRIP ── */
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_BG));
|
|
XFillRectangle(db->dpy, db->win, db->gc, 0, 0, DB_W, DB_TOP_H);
|
|
|
|
/* accent line at very top */
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_ACCENT));
|
|
XFillRectangle(db->dpy, db->win, db->gc, 0, 0, DB_W, 2);
|
|
|
|
/* Orion menu button — highlight when menu is open */
|
|
unsigned long sky_bg = db->menu_win ? 0x1A3868 : DB_COL_MENU_BG;
|
|
XSetForeground(db->dpy, db->gc, db_color(db, sky_bg));
|
|
XFillRectangle(db->dpy, db->win, db->gc, 0, 2, DB_MENU_W, DB_TOP_H - 2);
|
|
/* bottom indicator line when open */
|
|
if (db->menu_win) {
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_ACCENT));
|
|
XFillRectangle(db->dpy, db->win, db->gc, 0, DB_TOP_H - 2, DB_MENU_W, 2);
|
|
}
|
|
/* "Orion ▾" */
|
|
draw_str_centered(db, xd, db->font_menu, DB_COL_LOGO_TEXT,
|
|
0, 2, DB_MENU_W, DB_TOP_H - 2, "Orion \xe2\x96\xbe");
|
|
|
|
/* vertical divider */
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_BORDER_D));
|
|
XDrawLine(db->dpy, db->win, db->gc, DB_MENU_W, 2, DB_MENU_W, DB_TOP_H - 2);
|
|
|
|
/* clock */
|
|
{
|
|
time_t t = time(NULL);
|
|
struct tm *tm_info = localtime(&t);
|
|
char tstr[16], dstr[24];
|
|
strftime(tstr, sizeof(tstr), "%I:%M %p", tm_info);
|
|
strftime(dstr, sizeof(dstr), "%a %b %d", tm_info);
|
|
|
|
int cw = DB_W - DB_MENU_W - 1;
|
|
int cx = DB_MENU_W + 1;
|
|
|
|
/* time on upper part */
|
|
draw_str_centered(db, xd, db->font_clock, DB_COL_CLOCK_TXT,
|
|
cx, 4, cw, DB_TOP_H / 2 - 2, tstr);
|
|
/* date on lower part */
|
|
draw_str_centered(db, xd, db->font_date, DB_COL_DATE_TXT,
|
|
cx, DB_TOP_H / 2 + 2, cw, DB_TOP_H / 2 - 4, dstr);
|
|
}
|
|
|
|
/* separator below top strip */
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_BORDER_D));
|
|
XDrawLine(db->dpy, db->win, db->gc, 0, DB_TOP_H, DB_W, DB_TOP_H);
|
|
|
|
/* ── APP LIST ── */
|
|
int ay = db_apps_y();
|
|
|
|
for (int i = 0; i < db->napps; i++) {
|
|
int iy = ay + i * DB_ITEM_H;
|
|
int hover = (db->hovered == i);
|
|
int focus = db->apps[i].focused;
|
|
int mini = db->apps[i].minimized;
|
|
|
|
/* row background — alternate for readability */
|
|
unsigned long row_bg =
|
|
hover ? db_color(db, DB_COL_ITEM_HOV) :
|
|
focus ? db_color(db, DB_COL_ITEM_FOCUS) :
|
|
(i % 2 == 0) ? db_color(db, DB_COL_ITEM_BG)
|
|
: db_color(db, DB_COL_ITEM_ALT);
|
|
XSetForeground(db->dpy, db->gc, row_bg);
|
|
XFillRectangle(db->dpy, db->win, db->gc, 0, iy, DB_W, DB_ITEM_H);
|
|
|
|
/* letter icon */
|
|
unsigned long icon_bg = db_app_icon_color(db->apps[i].title);
|
|
int icon_x = 5;
|
|
int icon_y = iy + (DB_ITEM_H - DB_ICON_SZ) / 2;
|
|
draw_letter_icon(db, xd, icon_x, icon_y,
|
|
db->apps[i].title, icon_bg, mini);
|
|
|
|
/* app name */
|
|
int name_x = icon_x + DB_ICON_SZ + 6;
|
|
int name_maxw = DB_W - name_x - 14;
|
|
int name_y = iy + (DB_ITEM_H - 10) / 2;
|
|
|
|
unsigned long tc = (hover || focus) ? DB_COL_ITEM_HTXT
|
|
: mini ? DB_COL_ITEM_MINI
|
|
: DB_COL_ITEM_TEXT;
|
|
|
|
if (mini && !hover) {
|
|
char bracketed[70];
|
|
snprintf(bracketed, sizeof(bracketed), "[%s]", db->apps[i].title);
|
|
draw_str(db, xd, db->font_ui, tc, name_x, name_y,
|
|
bracketed, name_x, name_maxw);
|
|
} else {
|
|
draw_str(db, xd, db->font_ui, tc, name_x, name_y,
|
|
db->apps[i].title, name_x, name_maxw);
|
|
}
|
|
|
|
/* expand arrow ▸ */
|
|
unsigned long arrow_col = (hover || focus) ? DB_COL_ITEM_HTXT : 0x303848;
|
|
draw_str(db, xd, db->font_ui, arrow_col,
|
|
DB_W - 13, name_y, "\xe2\x96\xb8", 0, 0);
|
|
|
|
/* row divider */
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_DIVIDER));
|
|
XDrawLine(db->dpy, db->win, db->gc,
|
|
0, iy + DB_ITEM_H - 1, DB_W, iy + DB_ITEM_H - 1);
|
|
}
|
|
|
|
/* separator below app list */
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_BORDER_D));
|
|
XDrawLine(db->dpy, db->win, db->gc,
|
|
0, ay + db->napps * DB_ITEM_H, DB_W, ay + db->napps * DB_ITEM_H);
|
|
|
|
/* ── WORKSPACE STRIP ── */
|
|
int wy = db_ws_y(db);
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_BG));
|
|
XFillRectangle(db->dpy, db->win, db->gc, 0, wy, DB_W, DB_WS_H);
|
|
|
|
int total = db->num_ws < 1 ? 4 : (db->num_ws > 8 ? 8 : db->num_ws);
|
|
int btnw = (DB_W - 8) / total;
|
|
int bth = DB_WS_H - 6;
|
|
|
|
for (int i = 0; i < total; i++) {
|
|
int bx = 4 + i * btnw;
|
|
int by = wy + 3;
|
|
int active = (i == db->cur_ws);
|
|
|
|
XSetForeground(db->dpy, db->gc,
|
|
db_color(db, active ? DB_COL_WS_ACTIVE : DB_COL_WS_INACT));
|
|
XFillRectangle(db->dpy, db->win, db->gc, bx, by, btnw - 2, bth);
|
|
|
|
/* active indicator: accent top edge */
|
|
if (active) {
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_ACCENT));
|
|
XFillRectangle(db->dpy, db->win, db->gc, bx, by, btnw - 2, 2);
|
|
}
|
|
|
|
char label[4];
|
|
snprintf(label, sizeof(label), "%d", i + 1);
|
|
draw_str_centered(db, xd, db->font_ui,
|
|
active ? DB_COL_ITEM_HTXT : DB_COL_WS_TEXT,
|
|
bx, by, btnw - 2, bth, label);
|
|
}
|
|
|
|
/* separator below workspace strip */
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_BORDER_D));
|
|
XDrawLine(db->dpy, db->win, db->gc,
|
|
0, wy + DB_WS_H, DB_W, wy + DB_WS_H);
|
|
|
|
/* ── SYSTEM TRAY ── */
|
|
if (db->n_tray_icons > 0) {
|
|
int ty = db_tray_y(db);
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_BG));
|
|
XFillRectangle(db->dpy, db->win, db->gc, 0, ty, DB_W, DB_TRAY_H);
|
|
|
|
/* separator below tray */
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_BORDER_D));
|
|
XDrawLine(db->dpy, db->win, db->gc, 0, ty + DB_TRAY_H, DB_W, ty + DB_TRAY_H);
|
|
}
|
|
|
|
/* ── STATUS BAR ── */
|
|
int sy = db_status_y(db);
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_BG));
|
|
XFillRectangle(db->dpy, db->win, db->gc, 0, sy, DB_W, DB_STATUS_H);
|
|
|
|
{
|
|
int cpu = read_cpu_percent();
|
|
int mem = read_mem_percent();
|
|
int bat = read_battery_percent();
|
|
char bst = read_battery_status();
|
|
int vol = read_volume_percent();
|
|
char status[80];
|
|
int pos = 0;
|
|
if (vol >= 0)
|
|
pos += snprintf(status + pos, sizeof(status) - pos,
|
|
"VOL %d%%", vol);
|
|
if (cpu >= 0)
|
|
pos += snprintf(status + pos, sizeof(status) - pos,
|
|
"%sCPU %d%%", pos ? " " : "", cpu);
|
|
if (mem >= 0)
|
|
pos += snprintf(status + pos, sizeof(status) - pos,
|
|
" MEM %d%%", mem);
|
|
if (bat >= 0) {
|
|
const char *arrow = (bst == 'C') ? "+" : (bst == 'F') ? "=" : "-";
|
|
pos += snprintf(status + pos, sizeof(status) - pos,
|
|
" BAT %d%%%s", bat, arrow);
|
|
}
|
|
if (pos == 0) snprintf(status, sizeof(status), "OrionOS");
|
|
draw_str_centered(db, xd, db->font_date, DB_COL_STATUS_TXT,
|
|
0, sy, DB_W, DB_STATUS_H, status);
|
|
}
|
|
|
|
XftDrawDestroy(xd);
|
|
}
|
|
|
|
/* ── BeOS-style fly-out menu ─────────────────────────────────────────── */
|
|
|
|
typedef struct { const char *lbl; const char *cmd; int sep; int has_sub; int sub_i; } MI;
|
|
|
|
/* sub-menu 0: Applications */
|
|
static const char *S0_LBL[] = {
|
|
"Firefox","Terminal","Files","Text Editor",
|
|
"Media Player","Image Viewer","OnlyOffice","PDF Viewer"
|
|
};
|
|
static const char *S0_CMD[] = {
|
|
"firefox &","alacritty &","thunar &","mousepad &",
|
|
"mpv --player-operation-mode=pseudo-gui &","viewnior &",
|
|
"onlyoffice-desktopeditors &","evince &"
|
|
};
|
|
#define NS0 8
|
|
|
|
/* sub-menu 1: Settings */
|
|
static const char *S1_LBL[] = {
|
|
"Display","Audio","Network","Bluetooth","Power"
|
|
};
|
|
static const char *S1_CMD[] = {
|
|
"arandr &","pavucontrol &",
|
|
"nm-connection-editor &","blueman-manager &",
|
|
"xfce4-power-manager-settings &"
|
|
};
|
|
#define NS1 5
|
|
|
|
static const MI MENU[] = {
|
|
{ "Install OrionOS...", "alacritty -e bash -c 'sudo skyinstall; exec bash' &", 0, 0,-1 },
|
|
{ NULL, NULL, 1, 0,-1 },
|
|
{ "Applications", NULL, 0, 1, 0 },
|
|
{ "Settings", NULL, 0, 1, 1 },
|
|
{ NULL, NULL, 1, 0,-1 },
|
|
{ "About OrionOS", "alacritty -e bash -c 'fastfetch; read' &", 0, 0,-1 },
|
|
{ "Lock Screen", "xscreensaver-command -lock &", 0, 0,-1 },
|
|
{ NULL, NULL, 1, 0,-1 },
|
|
{ "Restart", "systemctl reboot", 0, 0,-1 },
|
|
{ "Shut Down", "systemctl poweroff", 0, 0,-1 },
|
|
};
|
|
#define N_MENU ((int)(sizeof(MENU)/sizeof(MENU[0])))
|
|
|
|
static int mi_y(int i) {
|
|
int y = DB_FLYOUT_PAD;
|
|
for (int j = 0; j < i; j++)
|
|
y += MENU[j].sep ? DB_FLYOUT_SH : DB_FLYOUT_IH;
|
|
return y;
|
|
}
|
|
static int menu_win_h(void) { return mi_y(N_MENU) + DB_FLYOUT_PAD; }
|
|
static int sub_win_h(int n) { return n * DB_FLYOUT_IH + DB_FLYOUT_PAD * 2; }
|
|
|
|
static int menu_item_at(int y) {
|
|
for (int i = 0; i < N_MENU; i++) {
|
|
if (MENU[i].sep) continue;
|
|
int iy = mi_y(i);
|
|
if (y >= iy && y < iy + DB_FLYOUT_IH) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int sub_item_at(int y, int n) {
|
|
int iy = DB_FLYOUT_PAD;
|
|
for (int i = 0; i < n; i++) {
|
|
if (y >= iy && y < iy + DB_FLYOUT_IH) return i;
|
|
iy += DB_FLYOUT_IH;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void menu_draw_panel(Deskbar *db, Window win, int is_sub,
|
|
const char **lbls, int n, int hov) {
|
|
int ww = DB_FLYOUT_W;
|
|
int wh = is_sub ? sub_win_h(n) : menu_win_h();
|
|
XftDraw *xd = XftDrawCreate(db->dpy, win, db->visual, db->cmap);
|
|
|
|
/* background */
|
|
XSetForeground(db->dpy, db->gc, db_color(db, 0x0C1120));
|
|
XFillRectangle(db->dpy, win, db->gc, 0, 0, ww, wh);
|
|
|
|
/* left accent bar (BeOS-style colored stripe) */
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_ACCENT));
|
|
XFillRectangle(db->dpy, win, db->gc, 0, 0, 3, wh);
|
|
|
|
/* subtle border */
|
|
XSetForeground(db->dpy, db->gc, db_color(db, 0x1A2A48));
|
|
XDrawRectangle(db->dpy, win, db->gc, 0, 0, ww - 1, wh - 1);
|
|
|
|
if (is_sub) {
|
|
for (int i = 0; i < n; i++) {
|
|
int iy = DB_FLYOUT_PAD + i * DB_FLYOUT_IH;
|
|
if (i == hov) {
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_ITEM_HOV));
|
|
XFillRectangle(db->dpy, win, db->gc, 3, iy, ww - 4, DB_FLYOUT_IH);
|
|
}
|
|
unsigned long tc = (i == hov) ? DB_COL_ITEM_HTXT : DB_COL_ITEM_TEXT;
|
|
draw_str(db, xd, db->font_ui, tc,
|
|
DB_FLYOUT_LPD, iy + (DB_FLYOUT_IH - 10) / 2,
|
|
lbls[i], 0, 0);
|
|
}
|
|
} else {
|
|
for (int i = 0; i < N_MENU; i++) {
|
|
if (MENU[i].sep) {
|
|
int sy = mi_y(i) + DB_FLYOUT_SH / 2;
|
|
XSetForeground(db->dpy, db->gc, db_color(db, 0x1C2A44));
|
|
XDrawLine(db->dpy, win, db->gc, 8, sy, ww - 9, sy);
|
|
continue;
|
|
}
|
|
int iy = mi_y(i);
|
|
if (i == hov) {
|
|
XSetForeground(db->dpy, db->gc, db_color(db, DB_COL_ITEM_HOV));
|
|
XFillRectangle(db->dpy, win, db->gc, 3, iy, ww - 4, DB_FLYOUT_IH);
|
|
}
|
|
unsigned long tc = (i == hov) ? DB_COL_ITEM_HTXT : DB_COL_ITEM_TEXT;
|
|
draw_str(db, xd, db->font_ui, tc,
|
|
DB_FLYOUT_LPD, iy + (DB_FLYOUT_IH - 10) / 2,
|
|
MENU[i].lbl, 0, 0);
|
|
if (MENU[i].has_sub) {
|
|
unsigned long ac = (i == hov) ? DB_COL_ITEM_HTXT : 0x30486A;
|
|
/* ▶ U+25B6 */
|
|
draw_str(db, xd, db->font_ui, ac,
|
|
ww - 15, iy + (DB_FLYOUT_IH - 10) / 2,
|
|
"\xe2\x96\xb6", 0, 0);
|
|
}
|
|
}
|
|
}
|
|
XftDrawDestroy(xd);
|
|
XFlush(db->dpy);
|
|
}
|
|
|
|
static void db_sub_close(Deskbar *db) {
|
|
if (!db->sub_win) return;
|
|
XDestroyWindow(db->dpy, db->sub_win);
|
|
db->sub_win = None;
|
|
db->sub_active = -1;
|
|
db->sub_hov = -1;
|
|
}
|
|
|
|
static void db_sub_open(Deskbar *db, int sub_i) {
|
|
if (db->sub_active == sub_i && db->sub_win) return;
|
|
db_sub_close(db);
|
|
|
|
const char **lbls = (sub_i == 0) ? S0_LBL : S1_LBL;
|
|
int n = (sub_i == 0) ? NS0 : NS1;
|
|
int sx = db->sw_cache - DB_W - DB_FLYOUT_W - DB_FLYOUT_W;
|
|
int sy = mi_y(db->menu_hov);
|
|
|
|
XSetWindowAttributes swa;
|
|
swa.override_redirect = True;
|
|
swa.background_pixel = db_color(db, 0x0C1120);
|
|
swa.event_mask = ExposureMask;
|
|
db->sub_win = XCreateWindow(
|
|
db->dpy, db->root, sx, sy, DB_FLYOUT_W, sub_win_h(n), 0,
|
|
db->depth, InputOutput, db->visual,
|
|
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
|
|
db->sub_active = sub_i;
|
|
db->sub_hov = -1;
|
|
XMapRaised(db->dpy, db->sub_win);
|
|
menu_draw_panel(db, db->sub_win, 1, lbls, n, -1);
|
|
}
|
|
|
|
void db_menu_open(Deskbar *db) {
|
|
if (db->menu_win) return;
|
|
db->sw_cache = DisplayWidth(db->dpy, db->screen);
|
|
int wx = db->sw_cache - DB_W - DB_FLYOUT_W;
|
|
|
|
XSetWindowAttributes swa;
|
|
swa.override_redirect = True;
|
|
swa.background_pixel = db_color(db, 0x0C1120);
|
|
swa.event_mask = ExposureMask;
|
|
db->menu_win = XCreateWindow(
|
|
db->dpy, db->root, wx, 0, DB_FLYOUT_W, menu_win_h(), 0,
|
|
db->depth, InputOutput, db->visual,
|
|
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
|
|
db->menu_hov = -1;
|
|
XMapRaised(db->dpy, db->menu_win);
|
|
menu_draw_panel(db, db->menu_win, 0, NULL, 0, -1);
|
|
|
|
/* grab pointer so clicks outside are detected */
|
|
XGrabPointer(db->dpy, db->menu_win, True,
|
|
ButtonPressMask | PointerMotionMask | ButtonReleaseMask,
|
|
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
|
|
|
|
/* redraw deskbar to show Sky button as active */
|
|
db_draw(db);
|
|
}
|
|
|
|
void db_menu_close(Deskbar *db) {
|
|
if (!db->menu_win) return;
|
|
XUngrabPointer(db->dpy, CurrentTime);
|
|
db_sub_close(db);
|
|
XDestroyWindow(db->dpy, db->menu_win);
|
|
db->menu_win = None;
|
|
db->menu_hov = -1;
|
|
db_draw(db);
|
|
}
|
|
|
|
static void handle_menu_motion(Deskbar *db, int rx, int ry) {
|
|
/* is pointer in sub-menu? */
|
|
if (db->sub_win) {
|
|
int sx, sy; unsigned sw, sh, bw, dep; Window sr;
|
|
XGetGeometry(db->dpy, db->sub_win, &sr, &sx, &sy, &sw, &sh, &bw, &dep);
|
|
if (rx >= sx && ry >= sy && rx < sx + (int)sw && ry < sy + (int)sh) {
|
|
int n = (db->sub_active == 0) ? NS0 : NS1;
|
|
const char **lbls = (db->sub_active == 0) ? S0_LBL : S1_LBL;
|
|
int idx = sub_item_at(ry - sy, n);
|
|
if (idx != db->sub_hov) {
|
|
db->sub_hov = idx;
|
|
menu_draw_panel(db, db->sub_win, 1, lbls, n, idx);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* is pointer in main menu? */
|
|
if (db->menu_win) {
|
|
int mx, my; unsigned mw, mh, bw, dep; Window mr;
|
|
XGetGeometry(db->dpy, db->menu_win, &mr, &mx, &my, &mw, &mh, &bw, &dep);
|
|
if (rx >= mx && ry >= my && rx < mx + (int)mw && ry < my + (int)mh) {
|
|
int idx = menu_item_at(ry - my);
|
|
if (idx != db->menu_hov) {
|
|
db->menu_hov = idx;
|
|
menu_draw_panel(db, db->menu_win, 0, NULL, 0, idx);
|
|
if (idx >= 0 && MENU[idx].has_sub)
|
|
db_sub_open(db, MENU[idx].sub_i);
|
|
else
|
|
db_sub_close(db);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void handle_menu_press(Deskbar *db, int rx, int ry) {
|
|
/* click in sub-menu? */
|
|
if (db->sub_win) {
|
|
int sx, sy; unsigned sw, sh, bw, dep; Window sr;
|
|
XGetGeometry(db->dpy, db->sub_win, &sr, &sx, &sy, &sw, &sh, &bw, &dep);
|
|
if (rx >= sx && ry >= sy && rx < sx + (int)sw && ry < sy + (int)sh) {
|
|
int n = (db->sub_active == 0) ? NS0 : NS1;
|
|
const char **cmds = (db->sub_active == 0) ? S0_CMD : S1_CMD;
|
|
int idx = sub_item_at(ry - sy, n);
|
|
if (idx >= 0) system(cmds[idx]);
|
|
db_menu_close(db);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* click in main menu? */
|
|
if (db->menu_win) {
|
|
int mx, my; unsigned mw, mh, bw, dep; Window mr;
|
|
XGetGeometry(db->dpy, db->menu_win, &mr, &mx, &my, &mw, &mh, &bw, &dep);
|
|
if (rx >= mx && ry >= my && rx < mx + (int)mw && ry < my + (int)mh) {
|
|
int idx = menu_item_at(ry - my);
|
|
if (idx >= 0 && !MENU[idx].has_sub && MENU[idx].cmd)
|
|
system(MENU[idx].cmd);
|
|
if (idx >= 0 && !MENU[idx].has_sub)
|
|
db_menu_close(db);
|
|
/* clicking a has_sub item just keeps the sub-menu open */
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* click outside all menu windows → close */
|
|
db_menu_close(db);
|
|
}
|
|
|
|
/* ── input ────────────────────────────────────────────────────────────── */
|
|
|
|
void db_hover(Deskbar *db, int x, int y) {
|
|
(void)x;
|
|
int prev = db->hovered;
|
|
int ay = db_apps_y();
|
|
db->hovered = -1;
|
|
if (y >= ay && y < ay + db->napps * DB_ITEM_H)
|
|
db->hovered = (y - ay) / DB_ITEM_H;
|
|
if (db->hovered != prev) db_draw(db);
|
|
}
|
|
|
|
void db_click(Deskbar *db, int x, int y) {
|
|
int ay = db_apps_y();
|
|
int wy = db_ws_y(db);
|
|
|
|
/* Orion menu button → toggle fly-out menu */
|
|
if (y < DB_TOP_H && x < DB_MENU_W) {
|
|
if (db->menu_win) db_menu_close(db);
|
|
else db_menu_open(db);
|
|
return;
|
|
}
|
|
|
|
/* app row click → focus or restore */
|
|
if (y >= ay && y < ay + db->napps * DB_ITEM_H) {
|
|
int idx = (y - ay) / DB_ITEM_H;
|
|
if (idx < db->napps) {
|
|
XEvent ev = {0};
|
|
ev.xclient.type = ClientMessage;
|
|
ev.xclient.window = db->apps[idx].win;
|
|
ev.xclient.message_type = XInternAtom(db->dpy, "_NET_ACTIVE_WINDOW", False);
|
|
ev.xclient.format = 32;
|
|
ev.xclient.data.l[0] = 2;
|
|
ev.xclient.data.l[1] = CurrentTime;
|
|
XSendEvent(db->dpy, db->root, False,
|
|
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
|
|
XRaiseWindow(db->dpy, db->apps[idx].win);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* workspace button click → switch desktop */
|
|
if (y >= wy && y < wy + DB_WS_H) {
|
|
int total = db->num_ws < 1 ? 4 : (db->num_ws > 8 ? 8 : db->num_ws);
|
|
int btnw = (DB_W - 8) / total;
|
|
int idx = (x - 4) / btnw;
|
|
if (idx >= 0 && idx < db->num_ws) {
|
|
XEvent ev = {0};
|
|
ev.xclient.type = ClientMessage;
|
|
ev.xclient.window = db->root;
|
|
ev.xclient.message_type = XInternAtom(db->dpy, "_NET_CURRENT_DESKTOP", False);
|
|
ev.xclient.format = 32;
|
|
ev.xclient.data.l[0] = idx;
|
|
ev.xclient.data.l[1] = CurrentTime;
|
|
XSendEvent(db->dpy, db->root, False,
|
|
SubstructureRedirectMask | SubstructureNotifyMask, &ev);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ── init & run ───────────────────────────────────────────────────────── */
|
|
|
|
void db_init(Deskbar *db) {
|
|
db->dpy = XOpenDisplay(NULL);
|
|
if (!db->dpy) { fprintf(stderr, "oriondeskbar: cannot open display\n"); exit(1); }
|
|
|
|
db->screen = DefaultScreen(db->dpy);
|
|
db->root = RootWindow(db->dpy, db->screen);
|
|
db->cmap = DefaultColormap(db->dpy, db->screen);
|
|
db->visual = DefaultVisual(db->dpy, db->screen);
|
|
db->depth = DefaultDepth(db->dpy, db->screen);
|
|
db->hovered = -1;
|
|
db->menu_win = None;
|
|
db->sub_win = None;
|
|
db->menu_hov = -1;
|
|
db->sub_active = -1;
|
|
db->sub_hov = -1;
|
|
db->sw_cache = 0;
|
|
db->n_tray_icons = 0;
|
|
|
|
db->net_client_list = XInternAtom(db->dpy, "_NET_CLIENT_LIST", False);
|
|
db->net_active_window = XInternAtom(db->dpy, "_NET_ACTIVE_WINDOW", False);
|
|
db->net_current_desktop = XInternAtom(db->dpy, "_NET_CURRENT_DESKTOP", False);
|
|
db->net_number_of_desktops = XInternAtom(db->dpy, "_NET_NUMBER_OF_DESKTOPS", False);
|
|
db->net_wm_name = XInternAtom(db->dpy, "_NET_WM_NAME", False);
|
|
db->utf8_string = XInternAtom(db->dpy, "UTF8_STRING", False);
|
|
|
|
{
|
|
char sel_name[32];
|
|
snprintf(sel_name, sizeof(sel_name), "_NET_SYSTEM_TRAY_S%d", db->screen);
|
|
db->atom_tray_s0 = XInternAtom(db->dpy, sel_name, False);
|
|
}
|
|
db->atom_tray_opcode = XInternAtom(db->dpy, "_NET_SYSTEM_TRAY_OPCODE", False);
|
|
db->atom_xembed = XInternAtom(db->dpy, "_XEMBED", False);
|
|
db->atom_manager = XInternAtom(db->dpy, "MANAGER", False);
|
|
|
|
db->font_ui = XftFontOpenName(db->dpy, db->screen, DB_FONT_UI);
|
|
db->font_menu = XftFontOpenName(db->dpy, db->screen, DB_FONT_MENU);
|
|
db->font_clock = XftFontOpenName(db->dpy, db->screen, DB_FONT_CLOCK);
|
|
db->font_date = XftFontOpenName(db->dpy, db->screen, DB_FONT_DATE);
|
|
db->font_icon = XftFontOpenName(db->dpy, db->screen, DB_FONT_ICON);
|
|
if (!db->font_ui) db->font_ui = XftFontOpenName(db->dpy, db->screen, "fixed:size=9");
|
|
if (!db->font_menu) db->font_menu = db->font_ui;
|
|
if (!db->font_clock) db->font_clock = db->font_ui;
|
|
if (!db->font_date) db->font_date = db->font_ui;
|
|
if (!db->font_icon) db->font_icon = db->font_ui;
|
|
|
|
db_update_apps(db);
|
|
db_update_workspace(db);
|
|
|
|
db->bar_h = db_status_y(db) + DB_STATUS_H + 2;
|
|
|
|
int sw = DisplayWidth(db->dpy, db->screen);
|
|
|
|
XSetWindowAttributes swa;
|
|
swa.override_redirect = True;
|
|
swa.background_pixel = db_color(db, DB_COL_BG);
|
|
swa.event_mask = ExposureMask | ButtonPressMask
|
|
| PointerMotionMask | LeaveWindowMask
|
|
| VisibilityChangeMask;
|
|
|
|
db->win = XCreateWindow(
|
|
db->dpy, db->root,
|
|
sw - DB_W, 0, DB_W, db->bar_h, 0,
|
|
db->depth, InputOutput, db->visual,
|
|
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
|
|
db->gc = XCreateGC(db->dpy, db->win, 0, NULL);
|
|
|
|
/* Claim _NET_SYSTEM_TRAY_S<screen> selection and announce ourselves */
|
|
XSetSelectionOwner(db->dpy, db->atom_tray_s0, db->win, CurrentTime);
|
|
if (XGetSelectionOwner(db->dpy, db->atom_tray_s0) == db->win) {
|
|
XEvent sev = {0};
|
|
sev.xclient.type = ClientMessage;
|
|
sev.xclient.window = db->root;
|
|
sev.xclient.message_type = db->atom_manager;
|
|
sev.xclient.format = 32;
|
|
sev.xclient.data.l[0] = CurrentTime;
|
|
sev.xclient.data.l[1] = db->atom_tray_s0;
|
|
sev.xclient.data.l[2] = db->win;
|
|
XSendEvent(db->dpy, db->root, False, StructureNotifyMask, &sev);
|
|
}
|
|
|
|
Atom net_wm_window_type = XInternAtom(db->dpy, "_NET_WM_WINDOW_TYPE", False);
|
|
Atom net_wm_type_dock = XInternAtom(db->dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
|
|
XChangeProperty(db->dpy, db->win, net_wm_window_type, XA_ATOM, 32,
|
|
PropModeReplace, (unsigned char *)&net_wm_type_dock, 1);
|
|
|
|
/* strut: reserve right edge for deskbar */
|
|
Atom net_strut_partial = XInternAtom(db->dpy, "_NET_WM_STRUT_PARTIAL", False);
|
|
long strut[12] = { 0, DB_W, 0, 0, 0,0, 0,0, 0,0, 0,0 };
|
|
/* right strut: strut[1]=DB_W, right_start[8]=0, right_end[9]=screen_height */
|
|
int sh = DisplayHeight(db->dpy, db->screen);
|
|
strut[9] = sh;
|
|
XChangeProperty(db->dpy, db->win, net_strut_partial, XA_CARDINAL, 32,
|
|
PropModeReplace, (unsigned char *)strut, 12);
|
|
|
|
XSelectInput(db->dpy, db->root, PropertyChangeMask);
|
|
|
|
db_resize(db);
|
|
XMapRaised(db->dpy, db->win);
|
|
XSync(db->dpy, False);
|
|
}
|
|
|
|
void db_run(Deskbar *db) {
|
|
time_t last_tick = 0;
|
|
int status_tick = 0;
|
|
|
|
for (;;) {
|
|
while (XPending(db->dpy)) {
|
|
XEvent e;
|
|
XNextEvent(db->dpy, &e);
|
|
|
|
/* While the fly-out menu is open, the pointer is grabbed.
|
|
Route all motion and button events through the menu handlers
|
|
(they use root-relative coords to determine which panel was hit). */
|
|
if (db->menu_win) {
|
|
if (e.type == MotionNotify) {
|
|
handle_menu_motion(db, e.xmotion.x_root, e.xmotion.y_root);
|
|
continue;
|
|
}
|
|
if (e.type == ButtonPress) {
|
|
handle_menu_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->menu_win)
|
|
menu_draw_panel(db, db->menu_win, 0, NULL, 0, db->menu_hov);
|
|
else if (db->sub_win && e.xany.window == db->sub_win) {
|
|
int n = (db->sub_active == 0) ? NS0 : NS1;
|
|
const char **lbls = (db->sub_active == 0) ? S0_LBL : S1_LBL;
|
|
menu_draw_panel(db, db->sub_win, 1, lbls, n, db->sub_hov);
|
|
} else if (e.xany.window == db->win)
|
|
db_draw(db);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
switch (e.type) {
|
|
case Expose:
|
|
if (e.xexpose.count == 0) db_draw(db);
|
|
break;
|
|
case ButtonPress:
|
|
if (e.xany.window == db->win) {
|
|
int sy = db_status_y(db);
|
|
if (e.xbutton.button == Button4) {
|
|
/* scroll up on status bar = volume up */
|
|
if (e.xbutton.y >= sy)
|
|
system("pactl set-sink-volume @DEFAULT_SINK@ +5% 2>/dev/null &");
|
|
break;
|
|
}
|
|
if (e.xbutton.button == Button5) {
|
|
if (e.xbutton.y >= sy)
|
|
system("pactl set-sink-volume @DEFAULT_SINK@ -5% 2>/dev/null &");
|
|
break;
|
|
}
|
|
if (e.xbutton.button == Button1 && e.xbutton.y >= sy)
|
|
system("pactl set-sink-mute @DEFAULT_SINK@ toggle 2>/dev/null &");
|
|
else
|
|
db_click(db, e.xbutton.x, e.xbutton.y);
|
|
}
|
|
break;
|
|
case MotionNotify:
|
|
if (e.xany.window == db->win)
|
|
db_hover(db, e.xmotion.x, e.xmotion.y);
|
|
break;
|
|
case LeaveNotify:
|
|
if (e.xany.window == db->win && db->hovered != -1) {
|
|
db->hovered = -1; db_draw(db);
|
|
}
|
|
break;
|
|
case VisibilityNotify:
|
|
/* Another window covered us — fight back to the top */
|
|
if (e.xany.window == db->win &&
|
|
e.xvisibility.state != VisibilityUnobscured)
|
|
XRaiseWindow(db->dpy, db->win);
|
|
break;
|
|
case ClientMessage:
|
|
if (e.xclient.message_type == db->atom_tray_opcode &&
|
|
e.xclient.data.l[1] == TRAY_REQUEST_DOCK)
|
|
db_tray_dock(db, (Window)e.xclient.data.l[2]);
|
|
break;
|
|
case DestroyNotify:
|
|
db_tray_remove(db, e.xdestroywindow.window);
|
|
break;
|
|
case ReparentNotify:
|
|
if (e.xreparent.parent != db->win)
|
|
db_tray_remove(db, e.xreparent.window);
|
|
break;
|
|
case SelectionClear:
|
|
if (e.xselectionclear.selection == db->atom_tray_s0) {
|
|
db->n_tray_icons = 0;
|
|
db_resize(db);
|
|
db_draw(db);
|
|
}
|
|
break;
|
|
case PropertyNotify:
|
|
if (e.xproperty.atom == db->net_client_list) {
|
|
/* Window list changed — full refresh + resize */
|
|
db_update_apps(db);
|
|
db_tray_reposition(db);
|
|
db_resize(db);
|
|
db_draw(db);
|
|
} else if (e.xproperty.atom == db->net_active_window) {
|
|
/* Focus changed — only update focused flag, no resize */
|
|
Atom at; int af; unsigned long an, aa;
|
|
unsigned char *aw = NULL;
|
|
long active_win = -1;
|
|
if (XGetWindowProperty(db->dpy, db->root,
|
|
db->net_active_window, 0, 1, False,
|
|
XA_WINDOW, &at, &af, &an, &aa, &aw)
|
|
== Success && aw) {
|
|
active_win = *(long *)aw;
|
|
XFree(aw);
|
|
}
|
|
for (int i = 0; i < db->napps; i++)
|
|
db->apps[i].focused =
|
|
(db->apps[i].win == (Window)active_win);
|
|
db_draw(db);
|
|
} else if (e.xproperty.atom == db->net_current_desktop) {
|
|
db_update_workspace(db);
|
|
db_draw(db);
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
time_t now = time(NULL);
|
|
if (now != last_tick) {
|
|
last_tick = now;
|
|
status_tick++;
|
|
if (status_tick >= 5) {
|
|
status_tick = 0;
|
|
/* Safety net: re-raise deskbar every 5s in case
|
|
VisibilityNotify was missed while menu was open */
|
|
if (!db->menu_win)
|
|
XRaiseWindow(db->dpy, db->win);
|
|
}
|
|
db_draw(db);
|
|
}
|
|
|
|
usleep(100000);
|
|
}
|
|
}
|
|
|
|
int main(void) {
|
|
Deskbar db = {0};
|
|
db_init(&db);
|
|
db_run(&db);
|
|
return 0;
|
|
}
|