Files
OrionOS/installer/orioninstall-gui.c
T
nmemmertandClaude Sonnet 4.6 61fa1194db Phase 7: rename SkyOS → OrionOS across entire codebase
- Rename all binaries: skywm→orionwm, skydeskbar→oriondeskbar,
  skylauncher→orionlauncher, skyinstall/skyinstall-gui→orioninstall*
- Rename theme directory: themes/SkyOS/ → themes/OrionOS/
- Update all string literals, window titles, menu labels, and comments
- Update ISO build: hostname→orionos, user→orion, all paths/labels
- Add Plymouth boot splash theme (orionos) with Navy+Blue palette
- Update GTK2/GTK3 theme names, wallpaper output name
- Update .gitignore, root Makefile, build-iso.sh for new binary names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 13:42:18 -04:00

1125 lines
41 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* OrionOS GUI Installer — Xlib/Xft wizard
*
* Steps:
* 0 Welcome
* 1 Select disk
* 2 User & hostname
* 3 Review
* 4 Installing (live log + progress bar)
* 5 Done / reboot
*/
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <X11/Xft/Xft.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
/* ── window geometry ──────────────────────────────────────────────── */
#define WIN_W 660
#define WIN_H 520
#define HDR_H 70 /* title header height */
#define FOOT_H 56 /* button footer height */
#define BODY_Y HDR_H
#define BODY_H (WIN_H - HDR_H - FOOT_H)
/* ── colors ──────────────────────────────────────────────────────── */
#define C_BG 0x12161F /* window background */
#define C_HDR 0x0E1828 /* header panel */
#define C_HDR_ACCENT 0x1C4A80 /* header accent stripe */
#define C_BODY 0x161A26 /* body background */
#define C_FOOT 0x0E1220 /* footer */
#define C_PANEL 0x1A2030 /* card / panel bg */
#define C_INPUT_BG 0x0C1018 /* text field bg */
#define C_INPUT_BD 0x263040 /* text field border */
#define C_INPUT_ACT 0x2060A0 /* active field border */
#define C_BTN 0x1C3A60 /* button fill */
#define C_BTN_HOV 0x2060A0 /* button hover */
#define C_BTN_PRI 0x2060A0 /* primary button */
#define C_BTN_PRI_H 0x2878C0 /* primary button hover */
#define C_BTN_DNG 0x6A1818 /* danger button */
#define C_BTN_DNG_H 0x9A2020 /* danger hover */
#define C_RADIO 0x1C3A60 /* radio fill */
#define C_RADIO_SEL 0x2060A0 /* radio selected */
#define C_SEP 0x1E2840 /* separator */
#define C_TXT 0xC8D4E8 /* primary text */
#define C_TXT_DIM 0x5A7090 /* dim text */
#define C_TXT_INP 0xD8E4F8 /* input text */
#define C_TXT_HDR 0xE8F0FF /* header text */
#define C_TXT_LOGO 0x88AADD /* "OrionOS" accent */
#define C_WARN 0xFFCC44 /* warning text */
#define C_ERR 0xFF5555 /* error text */
#define C_OK 0x44CC77 /* success text */
#define C_BAR_BG 0x1A2030 /* progress bar bg */
#define C_BAR_FG 0x2060A0 /* progress bar fill */
#define C_BAR_SHINE 0x2878C0 /* bar highlight */
#define C_BORDER 0x1E2840 /* outer border */
/* ── fonts ───────────────────────────────────────────────────────── */
#define F_LOGO "Sans:size=18:weight=bold"
#define F_HEAD "Sans:size=11:weight=bold"
#define F_BODY "Sans:size=10"
#define F_SMALL "Sans:size=9"
#define F_MONO "DejaVu Sans Mono:size=9"
#define F_BTN "Sans:size=10:weight=bold"
/* ── limits ──────────────────────────────────────────────────────── */
#define MAX_DISKS 8
#define MAX_LOG 200
#define LOG_COLS 80
#define TF_MAX 128
/* ── steps ───────────────────────────────────────────────────────── */
typedef enum {
STEP_WELCOME = 0,
STEP_DISK,
STEP_USER,
STEP_REVIEW,
STEP_INSTALL,
STEP_DONE,
} Step;
/* ── disk info ───────────────────────────────────────────────────── */
typedef struct {
char name[32]; /* sda, nvme0n1, … */
char path[40]; /* /dev/sda */
char model[64];
char size_str[16]; /* "256 GB" */
long long size_mb;
} Disk;
/* ── text field ──────────────────────────────────────────────────── */
typedef struct {
char buf[TF_MAX];
int len;
int password; /* show bullets */
int focused;
} TF;
/* ── log buffer ──────────────────────────────────────────────────── */
typedef struct {
char lines[MAX_LOG][LOG_COLS + 1];
int head; /* next write index (circular) */
int count; /* total lines written */
} LogBuf;
static void log_push(LogBuf *lb, const char *line) {
snprintf(lb->lines[lb->head], LOG_COLS, "%s", line);
lb->head = (lb->head + 1) % MAX_LOG;
if (lb->count < MAX_LOG) lb->count++;
}
/* ── main state ──────────────────────────────────────────────────── */
typedef struct {
Display *dpy;
int scr;
Window win;
GC gc;
Colormap cmap;
Visual *vis;
int depth;
XftFont *f_logo, *f_head, *f_body, *f_small, *f_mono, *f_btn;
Step step;
int prev_step;
/* disk selection */
Disk disks[MAX_DISKS];
int ndisks;
int sel_disk;
/* user fields */
TF tf_host, tf_user, tf_pass, tf_pass2;
TF *focused_tf;
/* button hover */
int hov_back, hov_next;
/* install progress */
int progress; /* 0–100 */
char prog_msg[128];
LogBuf log;
int install_fd; /* pipe fd to installer subprocess */
int install_done;
char install_err[256];
/* error display */
char err_msg[256];
} Ins;
/* ══════════════════════════════════════════════════════════════════ */
/* X helpers */
/* ══════════════════════════════════════════════════════════════════ */
static unsigned long mkcolor(Ins *s, 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(s->dpy, s->cmap, &c);
return c.pixel;
}
static void xftcol(Ins *s, unsigned long rgb, XftColor *out) {
XRenderColor rc = {
((rgb >> 16) & 0xFF) * 257,
((rgb >> 8) & 0xFF) * 257,
((rgb) & 0xFF) * 257,
0xFFFF
};
XftColorAllocValue(s->dpy, s->vis, s->cmap, &rc, out);
}
static void fill_rect(Ins *s, int x, int y, int w, int h, unsigned long rgb) {
XSetForeground(s->dpy, s->gc, mkcolor(s, rgb));
XFillRectangle(s->dpy, s->win, s->gc, x, y, w, h);
}
static void draw_line(Ins *s, int x1, int y1, int x2, int y2, unsigned long rgb) {
XSetForeground(s->dpy, s->gc, mkcolor(s, rgb));
XDrawLine(s->dpy, s->win, s->gc, x1, y1, x2, y2);
}
/* Draw text and return pixel width */
static int xft_text(Ins *s, XftDraw *xd, XftFont *f, unsigned long rgb,
int x, int y, const char *str) {
if (!str || !str[0]) return 0;
XftColor col; xftcol(s, rgb, &col);
XGlyphInfo ext;
XftTextExtentsUtf8(s->dpy, f, (const FcChar8 *)str, strlen(str), &ext);
XftDrawStringUtf8(xd, &col, f, x, y + ext.height - ext.y,
(const FcChar8 *)str, strlen(str));
XftColorFree(s->dpy, s->vis, s->cmap, &col);
return ext.width;
}
static int text_w(Ins *s, XftFont *f, const char *str) {
XGlyphInfo e;
XftTextExtentsUtf8(s->dpy, f, (const FcChar8 *)str, strlen(str), &e);
return e.width;
}
static int text_h(Ins *s, XftFont *f) {
XGlyphInfo e;
XftTextExtentsUtf8(s->dpy, f, (const FcChar8 *)"Ag", 2, &e);
return e.height;
}
/* ══════════════════════════════════════════════════════════════════ */
/* Widget drawing */
/* ══════════════════════════════════════════════════════════════════ */
/* Button: returns 1 if (mx,my) is inside */
static void draw_button(Ins *s, XftDraw *xd, int x, int y, int w, int h,
const char *label, unsigned long bg, int hov) {
unsigned long face = hov ? (bg + 0x101828) : bg;
/* clamp */
if ((face & 0xFF) > 0xFF) face = (face & 0xFFFF00) | 0xFF;
fill_rect(s, x, y, w, h, face);
/* top highlight */
unsigned long hi = (face < 0xEFEFEF) ? face + 0x101010 : 0xFFFFFF;
draw_line(s, x, y, x+w-1, y, hi);
draw_line(s, x, y, x, y+h-1, hi);
/* bottom shadow */
draw_line(s, x, y+h-1, x+w-1, y+h-1, C_BORDER);
draw_line(s, x+w-1, y, x+w-1, y+h-1, C_BORDER);
int tw = text_w(s, s->f_btn, label);
int th = text_h(s, s->f_btn);
xft_text(s, xd, s->f_btn, C_TXT_HDR,
x + (w - tw) / 2,
y + (h - th) / 2,
label);
}
static void draw_textfield(Ins *s, XftDraw *xd, int x, int y, int w,
TF *tf, const char *placeholder) {
int focused = tf->focused;
unsigned long bord = focused ? C_INPUT_ACT : C_INPUT_BD;
fill_rect(s, x, y, w, 32, C_INPUT_BG);
/* border */
XSetForeground(s->dpy, s->gc, mkcolor(s, bord));
XDrawRectangle(s->dpy, s->win, s->gc, x, y, w - 1, 31);
/* content */
if (tf->len == 0 && !focused && placeholder) {
XRectangle clip = { (short)(x+6), (short)(y+2),
(unsigned short)(w-12), 28 };
XftDraw *xd2 = xd;
XftDrawSetClipRectangles(xd2, 0, 0, &clip, 1);
xft_text(s, xd, s->f_body, C_TXT_DIM, x+6, y+6, placeholder);
XftDrawSetClip(xd2, None);
} else {
/* build display string */
char display[TF_MAX * 3 + 4];
if (tf->password) {
int i; for (i = 0; i < tf->len; i++) {
/* UTF-8 bullet U+2022 */
display[i*3+0] = (char)0xE2;
display[i*3+1] = (char)0x80;
display[i*3+2] = (char)0xA2;
}
display[i*3] = '\0';
} else {
memcpy(display, tf->buf, tf->len);
display[tf->len] = '\0';
}
XRectangle clip = { (short)(x+6), (short)(y+2),
(unsigned short)(w-12), 28 };
XftDrawSetClipRectangles(xd, 0, 0, &clip, 1);
xft_text(s, xd, s->f_body, C_TXT_INP, x+6, y+6, display);
XftDrawSetClip(xd, None);
/* cursor */
if (focused) {
int cw = text_w(s, s->f_body, display);
int cx = x + 6 + cw;
if (cx > x + w - 8) cx = x + w - 8;
fill_rect(s, cx, y + 6, 2, 20, C_INPUT_ACT);
}
}
}
/* Radio button row */
static void draw_radio(Ins *s, XftDraw *xd, int x, int y, int w,
const char *label, const char *sub,
int selected, int hovered) {
unsigned long bg = selected ? C_RADIO_SEL :
hovered ? C_RADIO + 0x080808 : C_RADIO;
fill_rect(s, x, y, w, 44, bg);
/* top border */
draw_line(s, x, y, x+w-1, y,
selected ? C_BTN_PRI : C_SEP);
/* radio circle */
int cx = x + 20, cy = y + 22;
XSetForeground(s->dpy, s->gc, mkcolor(s, C_SEP));
XDrawArc(s->dpy, s->win, s->gc, cx-8, cy-8, 16, 16, 0, 360*64);
if (selected) {
XSetForeground(s->dpy, s->gc, mkcolor(s, C_BTN_PRI));
XFillArc(s->dpy, s->win, s->gc, cx-5, cy-5, 10, 10, 0, 360*64);
}
xft_text(s, xd, s->f_body, C_TXT, x+38, y+8, label);
xft_text(s, xd, s->f_small, C_TXT_DIM, x+38, y+24, sub);
}
/* Progress bar */
static void draw_progress(Ins *s, int x, int y, int w, int h, int pct) {
fill_rect(s, x, y, w, h, C_BAR_BG);
XSetForeground(s->dpy, s->gc, mkcolor(s, C_BORDER));
XDrawRectangle(s->dpy, s->win, s->gc, x, y, w-1, h-1);
if (pct > 0) {
int fw = (w - 2) * pct / 100;
if (fw > 0) {
fill_rect(s, x+1, y+1, fw, h-2, C_BAR_FG);
/* shine on top half */
fill_rect(s, x+1, y+1, fw, (h-2)/2, C_BAR_SHINE);
}
}
}
/* ══════════════════════════════════════════════════════════════════ */
/* Disk detection */
/* ══════════════════════════════════════════════════════════════════ */
static void detect_disks(Ins *s) {
s->ndisks = 0;
DIR *d = opendir("/sys/block");
if (!d) return;
struct dirent *de;
while ((de = readdir(d)) && s->ndisks < MAX_DISKS) {
const char *n = de->d_name;
if (n[0] == '.') continue;
/* skip loop, ram, sr devices */
if (strncmp(n, "loop", 4) == 0) continue;
if (strncmp(n, "ram", 3) == 0) continue;
if (strncmp(n, "sr", 2) == 0) continue;
if (strncmp(n, "zram", 4) == 0) continue;
Disk *dk = &s->disks[s->ndisks];
snprintf(dk->name, sizeof(dk->name), "%s", n);
snprintf(dk->path, sizeof(dk->path), "/dev/%s", n);
/* size from /sys/block/<name>/size (512-byte sectors) */
char spath[128];
snprintf(spath, sizeof(spath), "/sys/block/%s/size", n);
FILE *f = fopen(spath, "r");
long long sectors = 0;
if (f) { fscanf(f, "%lld", &sectors); fclose(f); }
dk->size_mb = sectors / 2048; /* sectors → MB */
if (dk->size_mb < 4000) continue; /* skip tiny devices <4GB */
if (dk->size_mb >= 1024*1024)
snprintf(dk->size_str, sizeof(dk->size_str),
"%.1f TB", dk->size_mb / 1024.0 / 1024.0);
else
snprintf(dk->size_str, sizeof(dk->size_str),
"%lld GB", dk->size_mb / 1024);
/* model */
snprintf(spath, sizeof(spath), "/sys/block/%s/device/model", n);
f = fopen(spath, "r");
dk->model[0] = '\0';
if (f) {
fgets(dk->model, sizeof(dk->model), f);
fclose(f);
/* trim trailing newline/spaces */
int l = strlen(dk->model);
while (l > 0 && (dk->model[l-1] == '\n' ||
dk->model[l-1] == ' '))
dk->model[--l] = '\0';
}
if (!dk->model[0]) {
/* NVMe — try nvme/model */
snprintf(spath, sizeof(spath), "/sys/block/%s/device/model", n);
snprintf(dk->model, sizeof(dk->model), "Disk");
}
s->ndisks++;
}
closedir(d);
}
/* ══════════════════════════════════════════════════════════════════ */
/* Layout helpers */
/* ══════════════════════════════════════════════════════════════════ */
static void draw_header(Ins *s, XftDraw *xd, const char *title) {
fill_rect(s, 0, 0, WIN_W, HDR_H, C_HDR);
fill_rect(s, 0, 0, WIN_W, 3, C_HDR_ACCENT);
draw_line(s, 0, HDR_H-1, WIN_W, HDR_H-1, C_BORDER);
xft_text(s, xd, s->f_logo, C_TXT_LOGO, 20, 12, "OrionOS");
int lw = text_w(s, s->f_logo, "OrionOS");
draw_line(s, 20 + lw + 12, HDR_H/2, WIN_W - 20, HDR_H/2, C_SEP);
xft_text(s, xd, s->f_head, C_TXT_HDR, 20 + lw + 20, 18, title);
}
static void draw_footer(Ins *s, XftDraw *xd,
const char *back_lbl, const char *next_lbl,
unsigned long next_col) {
int fy = WIN_H - FOOT_H;
fill_rect(s, 0, fy, WIN_W, FOOT_H, C_FOOT);
draw_line(s, 0, fy, WIN_W, fy, C_BORDER);
/* step dots */
int ndots = 5; /* steps 0-4 shown as dots (5 is done) */
int dot_step = (int)s->step < ndots ? (int)s->step : ndots-1;
for (int i = 0; i < ndots; i++) {
int dx = WIN_W/2 - (ndots*14)/2 + i*14 + 7;
int dy = fy + FOOT_H/2;
unsigned long dc = (i == dot_step) ? C_BTN_PRI : C_SEP;
XSetForeground(s->dpy, s->gc, mkcolor(s, dc));
XFillArc(s->dpy, s->win, s->gc, dx-4, dy-4, 8, 8, 0, 360*64);
}
int bw = 120, bh = 36, by = fy + (FOOT_H - bh) / 2;
if (back_lbl)
draw_button(s, xd, 20, by, bw, bh, back_lbl,
C_BTN, s->hov_back);
if (next_lbl)
draw_button(s, xd, WIN_W - bw - 20, by, bw, bh, next_lbl,
next_col, s->hov_next);
}
static int in_rect(int mx, int my, int x, int y, int w, int h) {
return mx>=x && mx<x+w && my>=y && my<y+h;
}
static int back_rect(int *x, int *y, int *w, int *h) {
*x=20; *y=WIN_H-FOOT_H+(FOOT_H-36)/2; *w=120; *h=36; return 1;
}
static int next_rect(int *x, int *y, int *w, int *h) {
*x=WIN_W-140; *y=WIN_H-FOOT_H+(FOOT_H-36)/2; *w=120; *h=36; return 1;
}
/* ══════════════════════════════════════════════════════════════════ */
/* Validation */
/* ══════════════════════════════════════════════════════════════════ */
static int validate_step(Ins *s) {
s->err_msg[0] = '\0';
switch (s->step) {
case STEP_DISK:
if (s->sel_disk < 0) {
snprintf(s->err_msg, sizeof(s->err_msg), "Please select a disk.");
return 0;
}
break;
case STEP_USER:
if (s->tf_host.len < 1) {
snprintf(s->err_msg, sizeof(s->err_msg), "Hostname cannot be empty.");
return 0;
}
if (s->tf_user.len < 1) {
snprintf(s->err_msg, sizeof(s->err_msg), "Username cannot be empty.");
return 0;
}
if (s->tf_pass.len < 6) {
snprintf(s->err_msg, sizeof(s->err_msg),
"Password must be at least 6 characters.");
return 0;
}
if (strcmp(s->tf_pass.buf, s->tf_pass2.buf) != 0) {
snprintf(s->err_msg, sizeof(s->err_msg), "Passwords do not match.");
return 0;
}
break;
default: break;
}
return 1;
}
/* ══════════════════════════════════════════════════════════════════ */
/* Step rendering */
/* ══════════════════════════════════════════════════════════════════ */
static void draw_welcome(Ins *s, XftDraw *xd) {
draw_header(s, xd, "Welcome");
fill_rect(s, 0, BODY_Y, WIN_W, BODY_H, C_BODY);
int y = BODY_Y + 30;
xft_text(s, xd, s->f_head, C_TXT_HDR, 40, y,
"Welcome to the OrionOS Installer");
y += 36;
const char *lines[] = {
"This wizard will guide you through installing OrionOS",
"onto your computer.",
"",
"What you will need:",
" • A target disk of at least 16 GB",
" • About 10–20 minutes",
"",
"The target disk will be completely erased.",
"Make sure you have backed up any important data",
"before continuing.",
};
for (int i = 0; i < (int)(sizeof(lines)/sizeof(lines[0])); i++) {
unsigned long col = (lines[i][0] == '\0') ? C_TXT :
(lines[i][0] == ' ') ? C_TXT :
C_TXT;
xft_text(s, xd, s->f_body, col, 40, y, lines[i]);
y += 22;
}
/* EFI/BIOS detection note */
y += 10;
int uefi = (access("/sys/firmware/efi", F_OK) == 0);
char boot_note[80];
snprintf(boot_note, sizeof(boot_note),
"Boot mode detected: %s", uefi ? "UEFI" : "Legacy BIOS");
xft_text(s, xd, s->f_small, C_TXT_DIM, 40, y, boot_note);
draw_footer(s, xd, NULL, "Next →", C_BTN_PRI);
}
static void draw_disk(Ins *s, XftDraw *xd) {
draw_header(s, xd, "Select Installation Disk");
fill_rect(s, 0, BODY_Y, WIN_W, BODY_H, C_BODY);
xft_text(s, xd, s->f_body, C_TXT_DIM, 30, BODY_Y + 16,
"Choose the disk where OrionOS will be installed. It will be erased.");
int y = BODY_Y + 42;
int dw = WIN_W - 60;
if (s->ndisks == 0) {
xft_text(s, xd, s->f_body, C_ERR, 30, y,
"No suitable disks found (≥4 GB).");
} else {
for (int i = 0; i < s->ndisks; i++) {
char label[80], sub[80];
snprintf(label, sizeof(label), "/dev/%s — %s",
s->disks[i].name, s->disks[i].size_str);
snprintf(sub, sizeof(sub), "%s", s->disks[i].model[0]
? s->disks[i].model : "Unknown model");
draw_radio(s, xd, 30, y, dw, label, sub,
s->sel_disk == i, 0);
y += 46;
}
}
if (s->err_msg[0])
xft_text(s, xd, s->f_body, C_ERR, 30, WIN_H - FOOT_H - 24,
s->err_msg);
draw_footer(s, xd, "← Back", "Next →", C_BTN_PRI);
}
static void draw_user(Ins *s, XftDraw *xd) {
draw_header(s, xd, "Create Your Account");
fill_rect(s, 0, BODY_Y, WIN_W, BODY_H, C_BODY);
int lx = 30, fx = 200, fw = WIN_W - fx - 30;
int y = BODY_Y + 24;
int fh = 32, gap = 48;
struct { const char *label; TF *tf; const char *ph; } fields[] = {
{ "Hostname:", &s->tf_host, "orionos" },
{ "Username:", &s->tf_user, "orion" },
{ "Password:", &s->tf_pass, "••••••••" },
{ "Confirm password:", &s->tf_pass2, "••••••••" },
};
for (int i = 0; i < 4; i++) {
xft_text(s, xd, s->f_body, C_TXT_DIM, lx,
y + (fh - text_h(s, s->f_body)) / 2, fields[i].label);
draw_textfield(s, xd, fx, y, fw, fields[i].tf, fields[i].ph);
y += gap;
}
/* hints */
xft_text(s, xd, s->f_small, C_TXT_DIM, lx, y + 8,
"Password must be at least 6 characters.");
xft_text(s, xd, s->f_small, C_TXT_DIM, lx, y + 24,
"You can change these settings after installation.");
if (s->err_msg[0])
xft_text(s, xd, s->f_body, C_ERR, lx, WIN_H - FOOT_H - 24,
s->err_msg);
draw_footer(s, xd, "← Back", "Next →", C_BTN_PRI);
}
static void draw_review(Ins *s, XftDraw *xd) {
draw_header(s, xd, "Ready to Install");
fill_rect(s, 0, BODY_Y, WIN_W, BODY_H, C_BODY);
/* summary card */
int cx = 30, cy = BODY_Y + 20, cw = WIN_W - 60, ch = 150;
fill_rect(s, cx, cy, cw, ch, C_PANEL);
draw_line(s, cx, cy, cx+cw, cy, C_SEP);
int lx = cx + 24, rx = cx + 180;
int y = cy + 18;
int lh = 26;
xft_text(s, xd, s->f_small, C_TXT_DIM, lx, y, "Disk:");
{
char v[80];
snprintf(v, sizeof(v), "/dev/%s (%s — %s)",
s->disks[s->sel_disk].name,
s->disks[s->sel_disk].size_str,
s->disks[s->sel_disk].model[0]
? s->disks[s->sel_disk].model : "?");
xft_text(s, xd, s->f_body, C_TXT, rx, y, v);
}
y += lh;
xft_text(s, xd, s->f_small, C_TXT_DIM, lx, y, "Hostname:");
xft_text(s, xd, s->f_body, C_TXT, rx, y, s->tf_host.buf);
y += lh;
xft_text(s, xd, s->f_small, C_TXT_DIM, lx, y, "Username:");
xft_text(s, xd, s->f_body, C_TXT, rx, y, s->tf_user.buf);
y += lh;
xft_text(s, xd, s->f_small, C_TXT_DIM, lx, y, "Boot mode:");
xft_text(s, xd, s->f_body, C_TXT, rx, y,
(access("/sys/firmware/efi", F_OK) == 0) ? "UEFI (systemd-boot)"
: "BIOS (GRUB)");
/* warning */
int wy = cy + ch + 20;
fill_rect(s, cx, wy, cw, 54, 0x2A1010);
draw_line(s, cx, wy, cx+cw, wy, 0x6A1818);
xft_text(s, xd, s->f_head, C_WARN, cx+16, wy+8,
"⚠ This will erase all data on the selected disk!");
char wline[128];
snprintf(wline, sizeof(wline),
" /dev/%s (%s) will be permanently reformatted.",
s->disks[s->sel_disk].name,
s->disks[s->sel_disk].size_str);
xft_text(s, xd, s->f_body, C_ERR, cx+16, wy+30, wline);
draw_footer(s, xd, "← Back", "Install", C_BTN_DNG);
}
/* ── install step ────────────────────────────────────────────────── */
#define LOG_LINES_VISIBLE 12
#define LOG_LINE_H 17
static void draw_install(Ins *s, XftDraw *xd) {
draw_header(s, xd, s->install_done ? "Installation Complete" :
"Installing OrionOS…");
fill_rect(s, 0, BODY_Y, WIN_W, BODY_H, C_BODY);
int y = BODY_Y + 16;
/* progress bar */
draw_progress(s, 30, y, WIN_W - 60, 26, s->progress);
y += 30;
/* progress percentage + message */
char pct_str[16];
snprintf(pct_str, sizeof(pct_str), "%d%%", s->progress);
xft_text(s, xd, s->f_head, C_TXT, 30, y, pct_str);
xft_text(s, xd, s->f_body, C_TXT_DIM,
30 + text_w(s, s->f_head, pct_str) + 12, y, s->prog_msg);
y += 28;
/* log panel */
fill_rect(s, 30, y, WIN_W - 60,
LOG_LINES_VISIBLE * LOG_LINE_H + 8, C_PANEL);
draw_line(s, 30, y, WIN_W-30, y, C_BORDER);
int logy = y + 6;
int start = (s->log.count > LOG_LINES_VISIBLE)
? (s->log.head - LOG_LINES_VISIBLE + MAX_LOG) % MAX_LOG
: (s->log.head - s->log.count + MAX_LOG) % MAX_LOG;
int show = s->log.count < LOG_LINES_VISIBLE ? s->log.count : LOG_LINES_VISIBLE;
for (int i = 0; i < show; i++) {
int idx = (start + i) % MAX_LOG;
const char *line = s->log.lines[idx];
unsigned long col = C_TXT_DIM;
if (strncmp(line, ">>>", 3) == 0) col = C_TXT;
if (strncmp(line, " ✓", 5) == 0) col = C_OK;
if (strncmp(line, "ERROR", 5) == 0 ||
strncmp(line, "error", 5) == 0) col = C_ERR;
XRectangle clip = { 32, (short)logy,
(unsigned short)(WIN_W - 64),
(unsigned short)LOG_LINE_H };
XftDrawSetClipRectangles(xd, 0, 0, &clip, 1);
xft_text(s, xd, s->f_mono, col, 36, logy, line);
XftDrawSetClip(xd, None);
logy += LOG_LINE_H;
}
if (s->install_done) {
if (s->install_err[0]) {
xft_text(s, xd, s->f_body, C_ERR, 30, WIN_H - FOOT_H - 24,
s->install_err);
}
draw_footer(s, xd, NULL, "Reboot", C_OK);
}
/* else: no buttons while installing */
}
static void draw_done(Ins *s, XftDraw *xd) {
draw_header(s, xd, "Installation Complete");
fill_rect(s, 0, BODY_Y, WIN_W, BODY_H, C_BODY);
int cy = BODY_Y + BODY_H / 2 - 60;
xft_text(s, xd, s->f_logo, C_OK,
WIN_W/2 - text_w(s, s->f_logo, "✓ OrionOS Installed!")/2,
cy, "✓ OrionOS Installed!");
cy += 48;
const char *l1 = "OrionOS has been installed successfully.";
const char *l2 = "Remove the USB drive and restart your computer.";
xft_text(s, xd, s->f_body, C_TXT,
WIN_W/2 - text_w(s, s->f_body, l1)/2, cy, l1);
cy += 26;
xft_text(s, xd, s->f_body, C_TXT_DIM,
WIN_W/2 - text_w(s, s->f_body, l2)/2, cy, l2);
draw_footer(s, xd, NULL, "Reboot Now", C_BTN_PRI);
}
/* ══════════════════════════════════════════════════════════════════ */
/* Main draw dispatch */
/* ══════════════════════════════════════════════════════════════════ */
static void redraw(Ins *s) {
XftDraw *xd = XftDrawCreate(s->dpy, s->win, s->vis, s->cmap);
fill_rect(s, 0, 0, WIN_W, WIN_H, C_BG);
switch (s->step) {
case STEP_WELCOME: draw_welcome(s, xd); break;
case STEP_DISK: draw_disk(s, xd); break;
case STEP_USER: draw_user(s, xd); break;
case STEP_REVIEW: draw_review(s, xd); break;
case STEP_INSTALL: draw_install(s, xd); break;
case STEP_DONE: draw_done(s, xd); break;
}
/* outer border */
XSetForeground(s->dpy, s->gc, mkcolor(s, C_BORDER));
XDrawRectangle(s->dpy, s->win, s->gc, 0, 0, WIN_W-1, WIN_H-1);
XftDrawDestroy(xd);
XFlush(s->dpy);
}
/* ══════════════════════════════════════════════════════════════════ */
/* Installation runner */
/* ══════════════════════════════════════════════════════════════════ */
static void start_install(Ins *s) {
int pipefd[2];
if (pipe(pipefd) < 0) {
snprintf(s->install_err, sizeof(s->install_err),
"pipe() failed: %s", strerror(errno));
s->install_done = 1;
return;
}
pid_t pid = fork();
if (pid < 0) {
snprintf(s->install_err, sizeof(s->install_err),
"fork() failed: %s", strerror(errno));
s->install_done = 1;
return;
}
if (pid == 0) {
/* child */
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]);
execl("/usr/local/bin/orioninstall", "orioninstall",
"--gui",
s->disks[s->sel_disk].path,
s->tf_host.buf,
s->tf_user.buf,
s->tf_pass.buf,
NULL);
fprintf(stderr, "exec orioninstall failed: %s\n", strerror(errno));
_exit(1);
}
/* parent */
close(pipefd[1]);
s->install_fd = pipefd[0];
/* non-blocking reads */
fcntl(s->install_fd, F_SETFL, O_NONBLOCK);
}
static void poll_install(Ins *s) {
if (s->install_fd < 0) return;
char buf[512];
static char linebuf[LOG_COLS + 4];
static int linelen = 0;
ssize_t n = read(s->install_fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
for (int i = 0; i < (int)n; i++) {
char c = buf[i];
if (c == '\n' || c == '\r') {
linebuf[linelen] = '\0';
if (linelen > 0) {
/* parse PROGRESS:pct:msg */
if (strncmp(linebuf, "PROGRESS:", 9) == 0) {
int pct = 0;
char msg[128] = {0};
sscanf(linebuf + 9, "%d:%127[^\n]", &pct, msg);
s->progress = pct;
snprintf(s->prog_msg, sizeof(s->prog_msg), "%s", msg);
if (pct == 100) {
s->install_done = 1;
s->step = STEP_DONE;
}
} else {
log_push(&s->log, linebuf);
}
redraw(s);
}
linelen = 0;
} else {
if (linelen < LOG_COLS)
linebuf[linelen++] = c;
}
}
} else if (n == 0 || (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) {
/* EOF */
close(s->install_fd);
s->install_fd = -1;
if (!s->install_done) {
snprintf(s->install_err, sizeof(s->install_err),
"Installer exited unexpectedly.");
s->install_done = 1;
}
redraw(s);
}
}
/* ══════════════════════════════════════════════════════════════════ */
/* Text field input handling */
/* ══════════════════════════════════════════════════════════════════ */
static void tf_key(TF *tf, XKeyEvent *e) {
KeySym sym; char buf[8]; int len;
len = XLookupString(e, buf, sizeof(buf)-1, &sym, NULL);
buf[len] = '\0';
if (sym == XK_BackSpace) {
if (tf->len > 0) tf->buf[--tf->len] = '\0';
} else if (len > 0 && buf[0] >= 0x20 && buf[0] < 0x7F &&
tf->len < TF_MAX - 1) {
tf->buf[tf->len++] = buf[0];
tf->buf[tf->len] = '\0';
}
}
/* ══════════════════════════════════════════════════════════════════ */
/* Event handling */
/* ══════════════════════════════════════════════════════════════════ */
static TF *tf_at(Ins *s, int x, int y) {
int fx = 200, fw = WIN_W - fx - 30, lh = 48;
int ys[] = { BODY_Y+24, BODY_Y+72, BODY_Y+120, BODY_Y+168 };
TF *tfs[] = { &s->tf_host, &s->tf_user, &s->tf_pass, &s->tf_pass2 };
for (int i = 0; i < 4; i++)
if (in_rect(x, y, fx, ys[i], fw, 32)) return tfs[i];
(void)lh;
return NULL;
}
static void handle_click(Ins *s, int x, int y) {
int bx, by, bw, bh;
/* next button */
next_rect(&bx, &by, &bw, &bh);
if (in_rect(x, y, bx, by, bw, bh)) {
if (!validate_step(s)) { redraw(s); return; }
switch (s->step) {
case STEP_WELCOME:
s->step = STEP_DISK;
if (s->ndisks == 0) detect_disks(s);
break;
case STEP_DISK: s->step = STEP_USER; break;
case STEP_USER: s->step = STEP_REVIEW; break;
case STEP_REVIEW:
s->step = STEP_INSTALL;
redraw(s);
start_install(s);
return;
case STEP_INSTALL:
if (s->install_done) {
system("reboot");
}
break;
case STEP_DONE:
system("reboot");
break;
}
redraw(s);
return;
}
/* back button */
back_rect(&bx, &by, &bw, &bh);
if (in_rect(x, y, bx, by, bw, bh) &&
s->step > STEP_WELCOME && s->step < STEP_INSTALL) {
s->step = (Step)((int)s->step - 1);
s->err_msg[0] = '\0';
redraw(s);
return;
}
/* disk list */
if (s->step == STEP_DISK) {
int dy = BODY_Y + 42;
for (int i = 0; i < s->ndisks; i++) {
if (in_rect(x, y, 30, dy, WIN_W-60, 44)) {
s->sel_disk = i;
redraw(s);
return;
}
dy += 46;
}
}
/* text fields */
if (s->step == STEP_USER) {
TF *hit = tf_at(s, x, y);
if (s->focused_tf) s->focused_tf->focused = 0;
s->focused_tf = hit;
if (hit) hit->focused = 1;
redraw(s);
}
}
static void handle_key(Ins *s, XKeyEvent *e) {
KeySym sym = XLookupKeysym(e, 0);
/* Tab cycles text fields */
if (sym == XK_Tab && s->step == STEP_USER) {
TF *order[] = { &s->tf_host, &s->tf_user, &s->tf_pass, &s->tf_pass2 };
int cur = -1;
for (int i = 0; i < 4; i++)
if (order[i] == s->focused_tf) { cur = i; break; }
if (s->focused_tf) s->focused_tf->focused = 0;
cur = (cur + 1) % 4;
s->focused_tf = order[cur];
s->focused_tf->focused = 1;
redraw(s);
return;
}
/* Enter advances */
if (sym == XK_Return) {
int bx, by, bw, bh;
next_rect(&bx, &by, &bw, &bh);
handle_click(s, bx + 1, by + 1);
return;
}
if (s->focused_tf) {
tf_key(s->focused_tf, e);
redraw(s);
}
}
static void handle_motion(Ins *s, int x, int y) {
int bx, by, bw, bh;
int prev_back = s->hov_back, prev_next = s->hov_next;
next_rect(&bx, &by, &bw, &bh);
s->hov_next = in_rect(x, y, bx, by, bw, bh);
back_rect(&bx, &by, &bw, &bh);
s->hov_back = in_rect(x, y, bx, by, bw, bh) &&
s->step > STEP_WELCOME && s->step < STEP_INSTALL;
if (s->hov_back != prev_back || s->hov_next != prev_next)
redraw(s);
}
/* ══════════════════════════════════════════════════════════════════ */
/* Init & main loop */
/* ══════════════════════════════════════════════════════════════════ */
static void init(Ins *s) {
s->dpy = XOpenDisplay(NULL);
if (!s->dpy) { fputs("Cannot open display\n", stderr); exit(1); }
s->scr = DefaultScreen(s->dpy);
s->vis = DefaultVisual(s->dpy, s->scr);
s->cmap = DefaultColormap(s->dpy, s->scr);
s->depth = DefaultDepth(s->dpy, s->scr);
int sw = DisplayWidth(s->dpy, s->scr);
int sh = DisplayHeight(s->dpy, s->scr);
XSetWindowAttributes swa;
swa.background_pixel = mkcolor(s, C_BG);
swa.event_mask = ExposureMask | ButtonPressMask | PointerMotionMask
| KeyPressMask | StructureNotifyMask;
s->win = XCreateWindow(
s->dpy, RootWindow(s->dpy, s->scr),
(sw - WIN_W)/2, (sh - WIN_H)/2, WIN_W, WIN_H,
0, s->depth, InputOutput, s->vis,
CWBackPixel | CWEventMask, &swa);
s->gc = XCreateGC(s->dpy, s->win, 0, NULL);
/* window title + WM hints */
XStoreName(s->dpy, s->win, "OrionOS Installer");
Atom wm_del = XInternAtom(s->dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(s->dpy, s->win, &wm_del, 1);
/* fixed size */
XSizeHints *sh2 = XAllocSizeHints();
sh2->flags = PMinSize | PMaxSize;
sh2->min_width = sh2->max_width = WIN_W;
sh2->min_height = sh2->max_height = WIN_H;
XSetWMNormalHints(s->dpy, s->win, sh2);
XFree(sh2);
/* fonts */
s->f_logo = XftFontOpenName(s->dpy, s->scr, F_LOGO);
s->f_head = XftFontOpenName(s->dpy, s->scr, F_HEAD);
s->f_body = XftFontOpenName(s->dpy, s->scr, F_BODY);
s->f_small = XftFontOpenName(s->dpy, s->scr, F_SMALL);
s->f_mono = XftFontOpenName(s->dpy, s->scr, F_MONO);
s->f_btn = XftFontOpenName(s->dpy, s->scr, F_BTN);
XftFont **ff[] = {&s->f_logo,&s->f_head,&s->f_body,
&s->f_small,&s->f_mono,&s->f_btn};
for (int i = 0; i < 6; i++)
if (!*ff[i])
*ff[i] = XftFontOpenName(s->dpy, s->scr, "fixed:size=10");
/* defaults */
s->step = STEP_WELCOME;
s->sel_disk = -1;
s->install_fd = -1;
s->progress = 0;
snprintf(s->prog_msg, sizeof(s->prog_msg), "Starting…");
snprintf(s->tf_host.buf, TF_MAX, "orionos");
s->tf_host.len = strlen("orionos");
snprintf(s->tf_user.buf, TF_MAX, "orion");
s->tf_user.len = strlen("orion");
s->tf_pass.password = 1;
s->tf_pass2.password = 1;
detect_disks(s);
if (s->ndisks == 1) s->sel_disk = 0;
XMapRaised(s->dpy, s->win);
XSync(s->dpy, False);
}
int main(void) {
Ins s = {0};
init(&s);
redraw(&s);
Atom wm_del = XInternAtom(s.dpy, "WM_DELETE_WINDOW", False);
for (;;) {
/* poll for X events or installer output */
int xfd = ConnectionNumber(s.dpy);
struct pollfd pfds[2];
pfds[0].fd = xfd; pfds[0].events = POLLIN;
pfds[1].fd = s.install_fd;
pfds[1].events = (s.install_fd >= 0) ? POLLIN : 0;
int r = poll(pfds, (s.install_fd >= 0) ? 2 : 1, 100);
(void)r;
/* drain installer output */
if (s.install_fd >= 0 && (pfds[1].revents & (POLLIN|POLLHUP)))
poll_install(&s);
/* also poll on timeout to catch buffered data */
if (s.install_fd >= 0 && r == 0)
poll_install(&s);
/* X events */
while (XPending(s.dpy)) {
XEvent e;
XNextEvent(s.dpy, &e);
switch (e.type) {
case Expose:
if (e.xexpose.count == 0) redraw(&s);
break;
case ButtonPress:
handle_click(&s, e.xbutton.x, e.xbutton.y);
break;
case MotionNotify:
handle_motion(&s, e.xmotion.x, e.xmotion.y);
break;
case KeyPress:
handle_key(&s, &e.xkey);
break;
case ClientMessage:
if ((Atom)e.xclient.data.l[0] == wm_del) return 0;
break;
default: break;
}
}
}
return 0;
}