Initial commit: SkyOS source code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-07-23 12:14:34 -04:00
commit e641720a3c
30 files changed
+5836

No files matched your search

+26
View File
@@ -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)
+155
View File
@@ -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
+963
View File
@@ -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;
}