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,23 @@
|
||||
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 $(PKG_XFT_C)
|
||||
LDFLAGS = $(PKG_XFT_L)
|
||||
|
||||
SRC = src/launcher.c
|
||||
BIN = skylauncher
|
||||
|
||||
.PHONY: all clean install
|
||||
all: $(BIN)
|
||||
$(BIN): $(SRC)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
install: $(BIN)
|
||||
install -Dm755 $(BIN) /usr/local/bin/$(BIN)
|
||||
clean:
|
||||
rm -f $(BIN)
|
||||
@@ -0,0 +1,407 @@
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/Xft/Xft.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* ── dimensions ─────────────────────────────────────────────────────── */
|
||||
#define WIN_W 580
|
||||
#define WIN_H 420
|
||||
#define HDR_H 50 /* search header */
|
||||
#define TAB_H 34 /* category tab strip */
|
||||
#define CONTENT_Y (HDR_H + TAB_H)
|
||||
#define PAD 12 /* grid outer margin */
|
||||
#define CARD_W 96
|
||||
#define CARD_H 84
|
||||
#define CARD_GAP 8
|
||||
#define COLS 5
|
||||
#define ICON_SZ 38
|
||||
|
||||
/* grid left edge */
|
||||
#define GX0 ((WIN_W - (COLS * CARD_W + (COLS-1) * CARD_GAP)) / 2)
|
||||
#define GY0 (CONTENT_Y + PAD)
|
||||
|
||||
/* ── palette ─────────────────────────────────────────────────────────── */
|
||||
#define C_BG 0x0E1220
|
||||
#define C_HDR 0x090C18
|
||||
#define C_BORDER 0x192236
|
||||
#define C_ACCENT 0x2258B0
|
||||
#define C_TAB_SEL 0x141E30
|
||||
#define C_CARD_HOV 0x131C2E
|
||||
#define C_CARD_BD 0x1E2E48
|
||||
#define C_SEARCH 0x131A28
|
||||
#define C_SEARCH_BD 0x1C2C44
|
||||
#define C_TEXT 0x8AAAC8
|
||||
#define C_TEXT_HOV 0xE8F0FF
|
||||
#define C_TEXT_DIM 0x344A62
|
||||
#define C_TAB_TXT 0x486070
|
||||
#define C_TAB_TSEL 0xA8C4E8
|
||||
|
||||
static const unsigned long ICON_BG[] = {
|
||||
0x1A4D88, 0x166C3C, 0x6C3018,
|
||||
0x4A1A80, 0x126070, 0x6C1A38,
|
||||
0x365218, 0x183460,
|
||||
};
|
||||
#define NICON 8
|
||||
|
||||
#define FONT_UI "Sans:size=9"
|
||||
#define FONT_LOGO "Sans:size=11:weight=bold"
|
||||
#define FONT_ICON "Sans:size=13:weight=bold"
|
||||
#define MAX_FLT 64
|
||||
|
||||
typedef struct { const char *name, *cmd, *cat; } App;
|
||||
static const App APPS[] = {
|
||||
{ "Firefox", "firefox &", "Internet" },
|
||||
{ "Email", "firefox https://mail.google.com &", "Internet" },
|
||||
{ "Media Player", "mpv --player-operation-mode=pseudo-gui &", "Media" },
|
||||
{ "Images", "viewnior &", "Media" },
|
||||
{ "OnlyOffice", "onlyoffice-desktopeditors &", "Office" },
|
||||
{ "PDF Viewer", "evince &", "Office" },
|
||||
{ "Terminal", "xterm &", "System" },
|
||||
{ "Files", "thunar &", "System" },
|
||||
{ "Text Editor", "mousepad &", "System" },
|
||||
{ "System Info", "xterm -e 'fastfetch; read' &", "System" },
|
||||
{ "Screen Lock", "xscreensaver-command -lock &", "System" },
|
||||
{ "Install", "xterm -e 'sudo skyinstall' &", "System" },
|
||||
{ "Display", "arandr &", "Settings" },
|
||||
{ "Audio", "pavucontrol &", "Settings" },
|
||||
{ "Network", "nm-connection-editor &", "Settings" },
|
||||
{ "Bluetooth", "blueman-manager &", "Settings" },
|
||||
};
|
||||
#define NAPP ((int)(sizeof(APPS)/sizeof(APPS[0])))
|
||||
|
||||
static const char *CATS[] = {
|
||||
"All", "Internet", "Media", "Office", "System", "Settings"
|
||||
};
|
||||
#define NCAT ((int)(sizeof(CATS)/sizeof(CATS[0])))
|
||||
|
||||
typedef struct {
|
||||
Display *dpy; int screen;
|
||||
Window root, win; GC gc;
|
||||
Colormap cmap; Visual *visual;
|
||||
XftFont *font_ui, *font_logo, *font_icon;
|
||||
int sel_cat, hov;
|
||||
char filter[MAX_FLT];
|
||||
int filter_len;
|
||||
} L;
|
||||
|
||||
/* ── helpers ─────────────────────────────────────────────────────────── */
|
||||
static unsigned long mkc(L *l, 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(l->dpy, l->cmap, &c);
|
||||
return c.pixel;
|
||||
}
|
||||
static void xftc(L *l, unsigned long rgb, XftColor *out) {
|
||||
XRenderColor r = {
|
||||
((rgb>>16)&0xFF)*257, ((rgb>>8)&0xFF)*257,
|
||||
((rgb) &0xFF)*257, 0xFFFF
|
||||
};
|
||||
XftColorAllocValue(l->dpy, l->visual, l->cmap, &r, out);
|
||||
}
|
||||
static void ds(L *l, XftDraw *xd, XftFont *f, unsigned long rgb,
|
||||
int x, int y, const char *s) {
|
||||
XftColor c; xftc(l, rgb, &c);
|
||||
XGlyphInfo e;
|
||||
XftTextExtentsUtf8(l->dpy, f, (FcChar8*)s, strlen(s), &e);
|
||||
XftDrawStringUtf8(xd, &c, f, x, y + e.height - e.y, (FcChar8*)s, strlen(s));
|
||||
XftColorFree(l->dpy, l->visual, l->cmap, &c);
|
||||
}
|
||||
static void ds_ctr(L *l, XftDraw *xd, XftFont *f, unsigned long rgb,
|
||||
int bx, int by, int bw, int bh, const char *s) {
|
||||
XGlyphInfo e;
|
||||
XftTextExtentsUtf8(l->dpy, f, (FcChar8*)s, strlen(s), &e);
|
||||
int tx = bx + (bw - (int)e.width) / 2;
|
||||
int ty = by + (bh - (int)e.height) / 2;
|
||||
ds(l, xd, f, rgb, tx, ty, s);
|
||||
}
|
||||
|
||||
/* ── query ───────────────────────────────────────────────────────────── */
|
||||
static int nvis(L *l, int out[NAPP]) {
|
||||
int n = 0;
|
||||
const char *cat = CATS[l->sel_cat];
|
||||
for (int i = 0; i < NAPP; i++) {
|
||||
if (strcmp(cat,"All")!=0 && strcmp(APPS[i].cat,cat)!=0) continue;
|
||||
if (l->filter_len > 0) {
|
||||
char nm[64], fl[MAX_FLT];
|
||||
snprintf(nm, sizeof(nm), "%s", APPS[i].name);
|
||||
snprintf(fl, sizeof(fl), "%s", l->filter);
|
||||
for (char *p=nm; *p; p++) if (*p>='A'&&*p<='Z') *p+=32;
|
||||
for (char *p=fl; *p; p++) if (*p>='A'&&*p<='Z') *p+=32;
|
||||
if (!strstr(nm, fl)) continue;
|
||||
}
|
||||
out[n++] = i;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* tab widths — measured once per draw via font metrics */
|
||||
static void tab_layout(L *l, int widths[NCAT], int *total) {
|
||||
*total = 0;
|
||||
for (int i = 0; i < NCAT; i++) {
|
||||
XGlyphInfo e;
|
||||
XftTextExtentsUtf8(l->dpy, l->font_ui, (FcChar8*)CATS[i], strlen(CATS[i]), &e);
|
||||
widths[i] = (int)e.width + 22;
|
||||
*total += widths[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* hit: which category tab contains point (x,y)? -1 = none */
|
||||
static int tab_at(L *l, int x, int y) {
|
||||
if (y < HDR_H || y >= CONTENT_Y) return -1;
|
||||
int w[NCAT], total; tab_layout(l, w, &total);
|
||||
int tx = (WIN_W - total) / 2;
|
||||
for (int i = 0; i < NCAT; i++) {
|
||||
if (x >= tx && x < tx + w[i]) return i;
|
||||
tx += w[i];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* hit: which card index (into vis[]) is at point? -1 = none */
|
||||
static int card_at(int x, int y) {
|
||||
if (y < CONTENT_Y) return -1;
|
||||
int lx = x - GX0;
|
||||
int ly = y - GY0;
|
||||
if (lx < 0 || ly < 0) return -1;
|
||||
int col = lx / (CARD_W + CARD_GAP);
|
||||
int row = ly / (CARD_H + CARD_GAP);
|
||||
if (col < 0 || col >= COLS || row < 0) return -1;
|
||||
/* reject click in gap between cards */
|
||||
int cx = col * (CARD_W + CARD_GAP);
|
||||
int cy = row * (CARD_H + CARD_GAP);
|
||||
if (lx - cx >= CARD_W || ly - cy >= CARD_H) return -1;
|
||||
return row * COLS + col;
|
||||
}
|
||||
|
||||
/* ── draw ─────────────────────────────────────────────────────────────── */
|
||||
static void draw(L *l) {
|
||||
XftDraw *xd = XftDrawCreate(l->dpy, l->win, l->visual, l->cmap);
|
||||
|
||||
/* background */
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, C_BG));
|
||||
XFillRectangle(l->dpy, l->win, l->gc, 0, 0, WIN_W, WIN_H);
|
||||
|
||||
/* header */
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, C_HDR));
|
||||
XFillRectangle(l->dpy, l->win, l->gc, 0, 0, WIN_W, HDR_H);
|
||||
|
||||
/* accent top stripe */
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, C_ACCENT));
|
||||
XFillRectangle(l->dpy, l->win, l->gc, 0, 0, WIN_W, 2);
|
||||
|
||||
/* logo */
|
||||
ds(l, xd, l->font_logo, C_ACCENT, 14, 13, "SkyOS");
|
||||
|
||||
/* search box */
|
||||
{
|
||||
int sx = 86, sy = 10, sw = WIN_W - 86 - 14, sh = 30;
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, C_SEARCH));
|
||||
XFillRectangle(l->dpy, l->win, l->gc, sx, sy, sw, sh);
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, C_SEARCH_BD));
|
||||
XDrawRectangle(l->dpy, l->win, l->gc, sx, sy, sw, sh);
|
||||
|
||||
if (l->filter_len == 0) {
|
||||
ds(l, xd, l->font_ui, C_TEXT_DIM, sx+8, sy+7, "Search...");
|
||||
} else {
|
||||
char buf[MAX_FLT+2];
|
||||
snprintf(buf, sizeof(buf), "%s|", l->filter);
|
||||
ds(l, xd, l->font_ui, C_TEXT_HOV, sx+8, sy+7, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* separator below header */
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, C_BORDER));
|
||||
XDrawLine(l->dpy, l->win, l->gc, 0, HDR_H, WIN_W, HDR_H);
|
||||
|
||||
/* category tabs */
|
||||
int tw[NCAT], ttotal; tab_layout(l, tw, &ttotal);
|
||||
int tx = (WIN_W - ttotal) / 2;
|
||||
for (int i = 0; i < NCAT; i++) {
|
||||
if (i == l->sel_cat) {
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, C_TAB_SEL));
|
||||
XFillRectangle(l->dpy, l->win, l->gc, tx, HDR_H+1, tw[i], TAB_H-1);
|
||||
/* accent underline */
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, C_ACCENT));
|
||||
XFillRectangle(l->dpy, l->win, l->gc, tx+5, HDR_H+TAB_H-2, tw[i]-10, 2);
|
||||
}
|
||||
unsigned long tc = (i == l->sel_cat) ? C_TAB_TSEL : C_TAB_TXT;
|
||||
ds_ctr(l, xd, l->font_ui, tc, tx, HDR_H, tw[i], TAB_H, CATS[i]);
|
||||
tx += tw[i];
|
||||
}
|
||||
|
||||
/* separator below tabs */
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, C_BORDER));
|
||||
XDrawLine(l->dpy, l->win, l->gc, 0, CONTENT_Y, WIN_W, CONTENT_Y);
|
||||
|
||||
/* app grid */
|
||||
int vis[NAPP];
|
||||
int nv = nvis(l, vis);
|
||||
|
||||
if (nv == 0) {
|
||||
ds_ctr(l, xd, l->font_ui, C_TEXT_DIM,
|
||||
0, CONTENT_Y, WIN_W, WIN_H - CONTENT_Y, "No apps found");
|
||||
} else {
|
||||
for (int i = 0; i < nv; i++) {
|
||||
int col = i % COLS;
|
||||
int row = i / COLS;
|
||||
int cx = GX0 + col * (CARD_W + CARD_GAP);
|
||||
int cy = GY0 + row * (CARD_H + CARD_GAP);
|
||||
if (cy + CARD_H > WIN_H) break; /* clip to window */
|
||||
int hover = (i == l->hov);
|
||||
|
||||
/* card hover highlight */
|
||||
if (hover) {
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, C_CARD_HOV));
|
||||
XFillRectangle(l->dpy, l->win, l->gc, cx, cy, CARD_W, CARD_H);
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, C_CARD_BD));
|
||||
XDrawRectangle(l->dpy, l->win, l->gc, cx, cy, CARD_W-1, CARD_H-1);
|
||||
}
|
||||
|
||||
/* icon square */
|
||||
int app_i = vis[i];
|
||||
unsigned int h = 0;
|
||||
for (const char *p = APPS[app_i].name; *p; p++) h = h*31 + (unsigned char)*p;
|
||||
unsigned long ibg = ICON_BG[h % NICON];
|
||||
int ix = cx + (CARD_W - ICON_SZ) / 2;
|
||||
int iy = cy + 10;
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, ibg));
|
||||
XFillRectangle(l->dpy, l->win, l->gc, ix, iy, ICON_SZ, ICON_SZ);
|
||||
|
||||
/* icon letter */
|
||||
char letter[2] = { APPS[app_i].name[0], '\0' };
|
||||
if (letter[0] >= 'a' && letter[0] <= 'z') letter[0] -= 32;
|
||||
ds_ctr(l, xd, l->font_icon, 0xEAF0FF, ix, iy, ICON_SZ, ICON_SZ, letter);
|
||||
|
||||
/* label */
|
||||
char lbl[14];
|
||||
snprintf(lbl, sizeof(lbl), "%.12s", APPS[app_i].name);
|
||||
unsigned long tc = hover ? C_TEXT_HOV : C_TEXT;
|
||||
ds_ctr(l, xd, l->font_ui, tc,
|
||||
cx, iy + ICON_SZ + 4, CARD_W, CARD_H - ICON_SZ - 18, lbl);
|
||||
}
|
||||
}
|
||||
|
||||
/* outer border */
|
||||
XSetForeground(l->dpy, l->gc, mkc(l, C_BORDER));
|
||||
XDrawRectangle(l->dpy, l->win, l->gc, 0, 0, WIN_W-1, WIN_H-1);
|
||||
|
||||
XftDrawDestroy(xd);
|
||||
}
|
||||
|
||||
/* ── event handlers ──────────────────────────────────────────────────── */
|
||||
static void launch(L *l, int hov) {
|
||||
int vis[NAPP];
|
||||
int nv = nvis(l, vis);
|
||||
if (hov >= 0 && hov < nv) {
|
||||
system(APPS[vis[hov]].cmd);
|
||||
XDestroyWindow(l->dpy, l->win);
|
||||
XCloseDisplay(l->dpy);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_key(L *l, XKeyEvent *e) {
|
||||
KeySym sym = XLookupKeysym(e, 0);
|
||||
if (sym == XK_Escape) {
|
||||
XDestroyWindow(l->dpy, l->win);
|
||||
XCloseDisplay(l->dpy);
|
||||
exit(0);
|
||||
}
|
||||
if (sym == XK_Return) { launch(l, l->hov); return; }
|
||||
if (sym == XK_BackSpace && l->filter_len > 0) {
|
||||
l->filter[--l->filter_len] = '\0';
|
||||
l->hov = -1; draw(l); return;
|
||||
}
|
||||
char buf[8] = {0};
|
||||
int len = XLookupString(e, buf, sizeof(buf)-1, &sym, NULL);
|
||||
if (len > 0 && buf[0] >= 0x20 && buf[0] < 0x7F && l->filter_len < MAX_FLT-1) {
|
||||
l->filter[l->filter_len++] = buf[0];
|
||||
l->filter[l->filter_len] = '\0';
|
||||
l->sel_cat = 0; l->hov = -1;
|
||||
draw(l);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── main ─────────────────────────────────────────────────────────────── */
|
||||
int main(void) {
|
||||
L l = {0}; l.sel_cat = 0; l.hov = -1;
|
||||
|
||||
l.dpy = XOpenDisplay(NULL);
|
||||
if (!l.dpy) { fprintf(stderr, "skylauncher: cannot open display\n"); return 1; }
|
||||
l.screen = DefaultScreen(l.dpy);
|
||||
l.root = RootWindow(l.dpy, l.screen);
|
||||
l.cmap = DefaultColormap(l.dpy, l.screen);
|
||||
l.visual = DefaultVisual(l.dpy, l.screen);
|
||||
|
||||
l.font_ui = XftFontOpenName(l.dpy, l.screen, FONT_UI);
|
||||
l.font_logo = XftFontOpenName(l.dpy, l.screen, FONT_LOGO);
|
||||
l.font_icon = XftFontOpenName(l.dpy, l.screen, FONT_ICON);
|
||||
if (!l.font_ui) l.font_ui = XftFontOpenName(l.dpy, l.screen, "fixed:size=9");
|
||||
if (!l.font_logo) l.font_logo = l.font_ui;
|
||||
if (!l.font_icon) l.font_icon = l.font_ui;
|
||||
|
||||
int sw = DisplayWidth(l.dpy, l.screen);
|
||||
int sh = DisplayHeight(l.dpy, l.screen);
|
||||
|
||||
XSetWindowAttributes swa;
|
||||
swa.background_pixel = mkc(&l, C_BG);
|
||||
swa.override_redirect = True;
|
||||
swa.event_mask = ExposureMask | ButtonPressMask
|
||||
| PointerMotionMask | LeaveWindowMask | KeyPressMask;
|
||||
|
||||
l.win = XCreateWindow(
|
||||
l.dpy, l.root,
|
||||
(sw - WIN_W) / 2, (sh - WIN_H) / 2, WIN_W, WIN_H, 0,
|
||||
DefaultDepth(l.dpy, l.screen), InputOutput, l.visual,
|
||||
CWBackPixel | CWOverrideRedirect | CWEventMask, &swa);
|
||||
l.gc = XCreateGC(l.dpy, l.win, 0, NULL);
|
||||
|
||||
XMapRaised(l.dpy, l.win);
|
||||
XGrabKeyboard(l.dpy, l.win, True, GrabModeAsync, GrabModeAsync, CurrentTime);
|
||||
XSync(l.dpy, False);
|
||||
|
||||
for (;;) {
|
||||
XEvent e;
|
||||
XNextEvent(l.dpy, &e);
|
||||
switch (e.type) {
|
||||
case Expose:
|
||||
if (e.xexpose.count == 0) draw(&l);
|
||||
break;
|
||||
case ButtonPress:
|
||||
{
|
||||
int t = tab_at(&l, e.xbutton.x, e.xbutton.y);
|
||||
if (t >= 0) {
|
||||
l.sel_cat = t; l.hov = -1; draw(&l);
|
||||
} else {
|
||||
int idx = card_at(e.xbutton.x, e.xbutton.y);
|
||||
launch(&l, idx);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionNotify:
|
||||
{
|
||||
int idx = card_at(e.xmotion.x, e.xmotion.y);
|
||||
int vis[NAPP]; int nv = nvis(&l, vis);
|
||||
int prev = l.hov;
|
||||
l.hov = (idx >= 0 && idx < nv) ? idx : -1;
|
||||
if (l.hov != prev) draw(&l);
|
||||
}
|
||||
break;
|
||||
case LeaveNotify:
|
||||
if (l.hov != -1) { l.hov = -1; draw(&l); }
|
||||
break;
|
||||
case KeyPress:
|
||||
on_key(&l, &e.xkey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user