Phase 4 — native apps: - quill/: text editor (monospace, double-buffered, find bar, undo, open/save) - lens/: image viewer using imlib2 (zoom, pan, rotate, directory nav) - wm/src/menu.c: launch quill instead of mousepad - iso/docker/entrypoint.sh: add imlib2 package; install quill + lens binaries - skykit/template/: developer app template demonstrating the SkyKit API Phase 5 — file system and search: - rigel/rigel.c: right-click context menu (open, open in quill, copy path, rename, trash, properties); Ctrl+F search/filter bar with name highlighting; F2 rename-in-place overlay; Delete key moves to trash Phase 6 — distribution: - .github/workflows/build-iso.yml: GitHub Actions CI that compiles all components and builds a bootable ISO on every tag push; creates a GitHub release with sha256 checksums Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
147 lines
5.1 KiB
C
147 lines
5.1 KiB
C
/* hello.c — minimal SkyKit application template
|
|
*
|
|
* Build:
|
|
* cd skykit && make (builds libskykit.a)
|
|
* cd template
|
|
* gcc -O2 hello.c -I.. -L.. -lskykit \
|
|
* $(pkg-config --libs --cflags x11 xft fontconfig) -o hello
|
|
*
|
|
* This template shows the minimum boilerplate for a native OrionOS app:
|
|
* - sk_app_init() opens the display and loads fonts
|
|
* - Create a window and a Pixmap back-buffer
|
|
* - Wrap them in an SKPainter
|
|
* - Draw to the Pixmap, then XCopyArea to the window
|
|
* - Respond to Expose, KeyPress, ButtonPress, ClientMessage
|
|
*/
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/Xatom.h>
|
|
#include <X11/keysym.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "skykit.h"
|
|
|
|
#define WIN_W 640
|
|
#define WIN_H 400
|
|
|
|
int main(void) {
|
|
SKApp app;
|
|
sk_app_init(&app);
|
|
|
|
/* ── Window ──────────────────────────────────────────── */
|
|
XSetWindowAttributes swa = {
|
|
.background_pixel = sk_color(&app, SK_BG),
|
|
.event_mask = ExposureMask | KeyPressMask
|
|
| ButtonPressMask | StructureNotifyMask,
|
|
};
|
|
Window win = XCreateWindow(
|
|
app.dpy, app.root,
|
|
200, 100, WIN_W, WIN_H, 0,
|
|
app.depth, InputOutput, app.vis,
|
|
CWBackPixel | CWEventMask, &swa);
|
|
|
|
/* Back-buffer for flicker-free rendering */
|
|
GC gc = XCreateGC(app.dpy, win, 0, NULL);
|
|
Pixmap buf = XCreatePixmap(app.dpy, win, WIN_W, WIN_H, app.depth);
|
|
|
|
/* SKPainter wraps the back-buffer */
|
|
SKPainter p = { .app = &app, .drw = buf, .gc = gc };
|
|
|
|
/* WM_DELETE_WINDOW protocol */
|
|
Atom wm_del = XInternAtom(app.dpy, "WM_DELETE_WINDOW", False);
|
|
XSetWMProtocols(app.dpy, win, &wm_del, 1);
|
|
|
|
XStoreName(app.dpy, win, "Hello OrionOS");
|
|
XClassHint *ch = XAllocClassHint();
|
|
if (ch) { ch->res_name = (char*)"hello"; ch->res_class = (char*)"Hello"; XSetClassHint(app.dpy, win, ch); XFree(ch); }
|
|
|
|
XMapWindow(app.dpy, win);
|
|
XFlush(app.dpy);
|
|
|
|
/* Click counter for demo */
|
|
int clicks = 0;
|
|
|
|
/* ── Draw function ───────────────────────────────────── */
|
|
/* We define a lambda-style local redraw — in a real app this would be
|
|
* a proper function taking your app struct. */
|
|
#define REDRAW() do { \
|
|
sk_fill(&p, SK_BG, 0, 0, WIN_W, WIN_H); \
|
|
\
|
|
/* Accent bar at top */ \
|
|
sk_fill(&p, SK_ACCENT, 0, 0, WIN_W, 3); \
|
|
\
|
|
/* Centred heading */ \
|
|
const char *title = "Hello, OrionOS"; \
|
|
int tw = sk_textw(&app, app.font_hdr, title); \
|
|
sk_text(&p, app.font_hdr, SK_TEXT_HDR, \
|
|
(WIN_W - tw) / 2, sk_baseline(app.font_hdr, 60, 32), title); \
|
|
\
|
|
/* Subtitle */ \
|
|
const char *sub = "A SkyKit native application"; \
|
|
int sw = sk_textw(&app, app.font_ui, sub); \
|
|
sk_text(&p, app.font_ui, SK_TEXT_DIM, \
|
|
(WIN_W - sw) / 2, sk_baseline(app.font_ui, 100, 24), sub); \
|
|
\
|
|
/* Click counter button */ \
|
|
SKBtn btn = { (WIN_W - 160) / 2, 160, 160, 36, "Click me", 1 }; \
|
|
sk_draw_btn(&p, &btn, 0); \
|
|
\
|
|
/* Counter label */ \
|
|
char cnt[64]; snprintf(cnt, sizeof(cnt), "Clicked %d time%s", clicks, clicks==1?"":"s"); \
|
|
int cw = sk_textw(&app, app.font_ui, cnt); \
|
|
sk_text(&p, app.font_ui, SK_TEXT, (WIN_W - cw) / 2, \
|
|
sk_baseline(app.font_ui, 215, 24), cnt); \
|
|
\
|
|
/* Hint */ \
|
|
const char *hint = "Press Q or Esc to quit"; \
|
|
int hw = sk_textw(&app, app.font_sm, hint); \
|
|
sk_text(&p, app.font_sm, SK_TEXT_DIM, (WIN_W - hw) / 2, \
|
|
sk_baseline(app.font_sm, WIN_H - 36, 24), hint); \
|
|
\
|
|
/* Blit to window */ \
|
|
XCopyArea(app.dpy, buf, win, gc, 0, 0, WIN_W, WIN_H, 0, 0); \
|
|
XFlush(app.dpy); \
|
|
} while (0)
|
|
|
|
REDRAW();
|
|
|
|
/* ── Event loop ──────────────────────────────────────── */
|
|
int running = 1;
|
|
while (running) {
|
|
XEvent ev;
|
|
XNextEvent(app.dpy, &ev);
|
|
switch (ev.type) {
|
|
case Expose:
|
|
if (ev.xexpose.count == 0) REDRAW();
|
|
break;
|
|
case KeyPress: {
|
|
KeySym ks = XLookupKeysym(&ev.xkey, 0);
|
|
if (ks == XK_q || ks == XK_Q || ks == XK_Escape) running = 0;
|
|
break;
|
|
}
|
|
case ButtonPress:
|
|
if (ev.xbutton.button == Button1) {
|
|
/* check if inside the button */
|
|
int bx = (WIN_W - 160) / 2, by = 160;
|
|
if (sk_in_rect(ev.xbutton.x, ev.xbutton.y, bx, by, 160, 36)) {
|
|
clicks++;
|
|
REDRAW();
|
|
}
|
|
}
|
|
break;
|
|
case ClientMessage:
|
|
if ((Atom)ev.xclient.data.l[0] == wm_del) running = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#undef REDRAW
|
|
|
|
sk_app_free(&app);
|
|
XFreePixmap(app.dpy, buf);
|
|
XFreeGC(app.dpy, gc);
|
|
XDestroyWindow(app.dpy, win);
|
|
XCloseDisplay(app.dpy);
|
|
return 0;
|
|
}
|