Initial commit: SkyOS source code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
e641720a3c
30 files changed
+5836
No files matched your search
@@ -0,0 +1,26 @@
|
||||
CC = gcc
|
||||
|
||||
PKG_XFT_C := $(shell pkg-config --cflags xft x11 2>/dev/null)
|
||||
PKG_XFT_L := $(shell pkg-config --libs xft x11 2>/dev/null)
|
||||
ifeq ($(PKG_XFT_C),)
|
||||
PKG_XFT_C := -I/opt/homebrew/include
|
||||
PKG_XFT_L := -L/opt/homebrew/lib -lXft -lfontconfig
|
||||
endif
|
||||
|
||||
CFLAGS = -Wall -Wextra -O2 -Iinclude $(PKG_XFT_C)
|
||||
LDFLAGS = $(PKG_XFT_L)
|
||||
|
||||
SRC = src/deskbar.c
|
||||
OBJ = $(SRC:.c=.o)
|
||||
BIN = skydeskbar
|
||||
|
||||
.PHONY: all clean install
|
||||
all: $(BIN)
|
||||
$(BIN): $(OBJ)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
install: $(BIN)
|
||||
install -Dm755 $(BIN) /usr/local/bin/$(BIN)
|
||||
clean:
|
||||
rm -f $(OBJ) $(BIN)
|
||||
@@ -0,0 +1,155 @@
|
||||
#ifndef DESKBAR_H
|
||||
#define DESKBAR_H
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/Xft/Xft.h>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/*
|
||||
* SkyDeskbar — modern dark panel, docked top-right.
|
||||
*
|
||||
* ┌──────────────────────────┐
|
||||
* │ [Sky ▾] │ 12:00 PM │ <- top strip: menu + clock
|
||||
* │ │ Wed Jul 23 │ <- date line
|
||||
* ├──────────────────────────┤
|
||||
* │ [F] Firefox ▸ │ <- app entries (letter icon + name)
|
||||
* │ [T] Thunar ▸ │
|
||||
* │ [X] [xterm] ▸ │ <- minimized: bracketed + dimmed
|
||||
* ├──────────────────────────┤
|
||||
* │ [1] [2] [3] [4] │ <- workspace strip
|
||||
* ├──────────────────────────┤
|
||||
* │ CPU 12% MEM 45% │ <- status bar
|
||||
* └──────────────────────────┘
|
||||
*/
|
||||
|
||||
/* ── layout ──────────────────────────────────────────────────────────── */
|
||||
#define DB_W 200 /* deskbar total width */
|
||||
#define DB_TOP_H 52 /* top strip height (menu + clock + date) */
|
||||
#define DB_MENU_W 68 /* width of the Sky menu button */
|
||||
#define DB_ITEM_H 26 /* app entry row height */
|
||||
#define DB_ICON_SZ 18 /* app icon square size */
|
||||
#define DB_WS_H 28 /* workspace strip height */
|
||||
#define DB_STATUS_H 22 /* status bar height */
|
||||
#define DB_MAX_APPS 32
|
||||
|
||||
/* ── fly-out menu ────────────────────────────────────────────────────── */
|
||||
#define DB_FLYOUT_W 196 /* fly-out menu panel width */
|
||||
#define DB_FLYOUT_IH 28 /* item row height */
|
||||
#define DB_FLYOUT_SH 8 /* separator height */
|
||||
#define DB_FLYOUT_PAD 4 /* top/bottom outer padding */
|
||||
#define DB_FLYOUT_LPD 13 /* left label padding */
|
||||
|
||||
/* ── modern dark palette ──────────────────────────────────────────────── */
|
||||
#define DB_COL_BG 0x111520 /* dark navy body */
|
||||
#define DB_COL_MENU_BG 0x0E1A32 /* menu button bg */
|
||||
#define DB_COL_LOGO_TEXT 0x90BBEE /* "Sky" text */
|
||||
#define DB_COL_ITEM_BG 0x141828 /* app row bg */
|
||||
#define DB_COL_ITEM_ALT 0x111520 /* alternate row */
|
||||
#define DB_COL_ITEM_HOV 0x1E4878 /* hover — blue */
|
||||
#define DB_COL_ITEM_FOCUS 0x183860 /* focused window row */
|
||||
#define DB_COL_ITEM_TEXT 0xC0D0E8 /* normal app name */
|
||||
#define DB_COL_ITEM_HTXT 0xF0F6FF /* text on hover/focus */
|
||||
#define DB_COL_ITEM_MINI 0x485868 /* dimmed minimized text */
|
||||
#define DB_COL_DIVIDER 0x1C2030 /* row separator */
|
||||
#define DB_COL_WS_ACTIVE 0x1E58A8 /* active workspace */
|
||||
#define DB_COL_WS_INACT 0x202838 /* inactive workspace */
|
||||
#define DB_COL_WS_TEXT 0x8AB0D0 /* workspace number */
|
||||
#define DB_COL_CLOCK_TXT 0xD8E8FF /* clock text */
|
||||
#define DB_COL_DATE_TXT 0x5878A0 /* date text */
|
||||
#define DB_COL_STATUS_TXT 0x486080 /* status text */
|
||||
#define DB_COL_BORDER_L 0x223050 /* bevel light */
|
||||
#define DB_COL_BORDER_D 0x080C14 /* bevel dark */
|
||||
#define DB_COL_ACCENT 0x1E58A8 /* accent stripe */
|
||||
|
||||
/* ── fonts ───────────────────────────────────────────────────────────── */
|
||||
#define DB_FONT_UI "Sans:size=9"
|
||||
#define DB_FONT_MENU "Sans:size=10:weight=bold"
|
||||
#define DB_FONT_CLOCK "Sans:size=11:weight=bold"
|
||||
#define DB_FONT_DATE "Sans:size=8"
|
||||
#define DB_FONT_ICON "Sans:size=8:weight=bold"
|
||||
|
||||
/* ── app icon color palette (cycled by name hash) ────────────────────── */
|
||||
static const unsigned long DB_ICON_COLORS[] = {
|
||||
0x2060A0, /* blue */
|
||||
0x206040, /* forest green */
|
||||
0x804020, /* burnt orange */
|
||||
0x602080, /* purple */
|
||||
0x208060, /* teal */
|
||||
0x802040, /* crimson */
|
||||
0x406020, /* olive */
|
||||
0x204060, /* dark blue */
|
||||
};
|
||||
#define DB_ICON_NCOLORS 8
|
||||
|
||||
typedef struct AppEntry {
|
||||
Window win;
|
||||
char title[64];
|
||||
int focused;
|
||||
int minimized;
|
||||
} AppEntry;
|
||||
|
||||
typedef struct Deskbar {
|
||||
Display *dpy;
|
||||
int screen;
|
||||
Window root;
|
||||
Window win;
|
||||
GC gc;
|
||||
Colormap cmap;
|
||||
Visual *visual;
|
||||
int depth;
|
||||
|
||||
XftFont *font_ui;
|
||||
XftFont *font_menu;
|
||||
XftFont *font_clock;
|
||||
XftFont *font_date;
|
||||
XftFont *font_icon;
|
||||
|
||||
int bar_h;
|
||||
int hovered;
|
||||
int cur_ws;
|
||||
int num_ws;
|
||||
|
||||
AppEntry apps[DB_MAX_APPS];
|
||||
int napps;
|
||||
|
||||
Atom net_client_list;
|
||||
Atom net_active_window;
|
||||
Atom net_current_desktop;
|
||||
Atom net_number_of_desktops;
|
||||
Atom net_wm_name;
|
||||
Atom utf8_string;
|
||||
|
||||
/* fly-out menu state */
|
||||
Window menu_win; /* main fly-out, None when closed */
|
||||
Window sub_win; /* sub-menu panel, None when closed */
|
||||
int menu_hov; /* hovered item index in main menu, -1 = none */
|
||||
int sub_active; /* which sub-menu is open: 0=Apps, 1=Settings, -1=none */
|
||||
int sub_hov; /* hovered item in sub-menu */
|
||||
int sw_cache; /* cached screen width for menu positioning */
|
||||
} Deskbar;
|
||||
|
||||
void db_init(Deskbar *db);
|
||||
void db_run(Deskbar *db);
|
||||
void db_draw(Deskbar *db);
|
||||
void db_update_apps(Deskbar *db);
|
||||
void db_update_workspace(Deskbar *db);
|
||||
void db_resize(Deskbar *db);
|
||||
void db_click(Deskbar *db, int x, int y);
|
||||
void db_hover(Deskbar *db, int x, int y);
|
||||
void db_menu_open(Deskbar *db);
|
||||
void db_menu_close(Deskbar *db);
|
||||
|
||||
unsigned long db_color(Deskbar *db, unsigned long rgb);
|
||||
void db_xft_color(Deskbar *db, unsigned long rgb, XftColor *out);
|
||||
|
||||
int db_apps_y(void);
|
||||
int db_ws_y(Deskbar *db);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,963 @@
|
||||
#include "deskbar.h"
|
||||
|
||||
/* ── 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_status_y(Deskbar *db) {
|
||||
return db_ws_y(db) + DB_WS_H + 1;
|
||||
}
|
||||
|
||||
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 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_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);
|
||||
|
||||
/* Sky 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);
|
||||
}
|
||||
/* "Sky \xe2\x96\xbe" = "Sky ▾" */
|
||||
draw_str_centered(db, xd, db->font_menu, DB_COL_LOGO_TEXT,
|
||||
0, 2, DB_MENU_W, DB_TOP_H - 2, "Sky \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);
|
||||
|
||||
/* ── 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();
|
||||
char status[64];
|
||||
int pos = 0;
|
||||
if (cpu >= 0)
|
||||
pos += snprintf(status + pos, sizeof(status) - pos,
|
||||
"CPU %d%%", 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), "SkyOS");
|
||||
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 &","xterm &","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 SkyOS...", "xterm -e '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 SkyOS", "xterm -e '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);
|
||||
|
||||
/* Sky 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, "skydeskbar: 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->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);
|
||||
|
||||
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_TOP_H + 1 + db->napps * DB_ITEM_H + 1 + DB_WS_H + 1 + 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);
|
||||
|
||||
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)
|
||||
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 PropertyNotify:
|
||||
if (e.xproperty.atom == db->net_client_list) {
|
||||
/* Window list changed — full refresh + resize */
|
||||
db_update_apps(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;
|
||||
}
|
||||
Reference in new issue
Block a user