/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* ── 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_LOCALE, /* timezone + keyboard layout */ 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; Pixmap buf; /* double-buffer — all drawing goes here, then blit */ 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; /* locale selection */ int tz_sel; /* index into TZ_LIST */ int tz_scroll; /* first visible row in timezone pane */ char tz_filter[48]; /* live filter text */ int tz_filter_len; int tz_filter_focus; int kb_sel; /* index into KB_LIST */ int kb_scroll; /* 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->buf, 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->buf, 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); /* y is the TOP of the text row; compute baseline from font metrics */ XftDrawStringUtf8(xd, &col, f, x, y + f->ascent, (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->buf, 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->buf, 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->buf, 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->buf, 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//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", §ors); 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 = 6; /* steps 0-5 shown as dots (6 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->buf, 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=y && myerr_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 */ /* ══════════════════════════════════════════════════════════════════ */ /* ── locale data ─────────────────────────────────────────────────── */ static const char *TZ_LIST[] = { "UTC", "America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles", "America/Anchorage", "America/Honolulu", "America/Toronto", "America/Vancouver", "America/Mexico_City", "America/Sao_Paulo", "America/Buenos_Aires", "Europe/London", "Europe/Paris", "Europe/Berlin", "Europe/Rome", "Europe/Madrid", "Europe/Amsterdam", "Europe/Stockholm", "Europe/Warsaw", "Europe/Moscow", "Europe/Istanbul", "Asia/Dubai", "Asia/Kolkata", "Asia/Dhaka", "Asia/Bangkok", "Asia/Shanghai", "Asia/Tokyo", "Asia/Seoul", "Asia/Singapore", "Australia/Sydney", "Australia/Perth", "Pacific/Auckland", "Pacific/Honolulu", "Africa/Cairo", "Africa/Johannesburg", "Africa/Lagos", }; #define NTZ ((int)(sizeof(TZ_LIST)/sizeof(TZ_LIST[0]))) typedef struct { const char *code; const char *label; } KBLayout; static const KBLayout KB_LIST[] = { { "us", "US English" }, { "gb", "UK English" }, { "de", "German" }, { "fr", "French" }, { "es", "Spanish" }, { "it", "Italian" }, { "pt", "Portuguese" }, { "br", "Portuguese (BR)" }, { "ru", "Russian" }, { "pl", "Polish" }, { "nl", "Dutch" }, { "sv", "Swedish" }, { "no", "Norwegian" }, { "da", "Danish" }, { "fi", "Finnish" }, { "tr", "Turkish" }, { "ar", "Arabic" }, { "jp", "Japanese" }, { "kr", "Korean" }, { "cn", "Chinese (Pinyin)" }, }; #define NKB ((int)(sizeof(KB_LIST)/sizeof(KB_LIST[0]))) #define LC_LIST_H 220 /* pixel height of each list pane */ #define LC_ROW_H 22 /* row height in list */ #define LC_ROWS_VIS (LC_LIST_H / LC_ROW_H) #define LC_PANE_W ((WIN_W - 60) / 2 - 6) static void draw_locale(Ins *s, XftDraw *xd) { draw_header(s, xd, "Timezone & Keyboard"); fill_rect(s, 0, BODY_Y, WIN_W, BODY_H, C_BODY); int lx = 30, rx = WIN_W/2 + 4; int ty = BODY_Y + 14; /* ── Timezone pane ── */ xft_text(s, xd, s->f_head, C_TXT, lx, ty, "Timezone"); /* filter input */ int fy = ty + 22; int factive = s->tz_filter_focus; unsigned long fbd = factive ? C_INPUT_ACT : C_INPUT_BD; fill_rect(s, lx, fy, LC_PANE_W, 26, C_INPUT_BG); XSetForeground(s->dpy, s->gc, mkcolor(s, fbd)); XDrawRectangle(s->dpy, s->buf, s->gc, lx, fy, LC_PANE_W-1, 25); const char *fdisp = s->tz_filter_len ? s->tz_filter : ""; xft_text(s, xd, s->f_body, s->tz_filter_len ? C_TXT_INP : C_TXT_DIM, lx + 6, fy + 17, s->tz_filter_len ? fdisp : "Filter…"); if (factive) { int cx = lx + 6 + text_w(s, s->f_body, fdisp); XSetForeground(s->dpy, s->gc, mkcolor(s, C_TXT_INP)); XDrawLine(s->dpy, s->buf, s->gc, cx, fy + 4, cx, fy + 21); } /* timezone list */ int ly = fy + 32; fill_rect(s, lx, ly, LC_PANE_W, LC_LIST_H, C_PANEL); XSetForeground(s->dpy, s->gc, mkcolor(s, C_SEP)); XDrawRectangle(s->dpy, s->buf, s->gc, lx, ly, LC_PANE_W-1, LC_LIST_H-1); int vis = 0, row = 0; for (int i = 0; i < NTZ && vis < LC_ROWS_VIS + s->tz_scroll; i++) { if (s->tz_filter_len && !strstr(TZ_LIST[i], s->tz_filter)) continue; if (row++ < s->tz_scroll) continue; int ry2 = ly + vis * LC_ROW_H; int actual_i = i; /* track real index */ if (actual_i == s->tz_sel) fill_rect(s, lx+1, ry2, LC_PANE_W-2, LC_ROW_H, C_RADIO_SEL); else if (vis % 2 == 1) fill_rect(s, lx+1, ry2, LC_PANE_W-2, LC_ROW_H, 0x141C2C); xft_text(s, xd, s->f_small, actual_i == s->tz_sel ? C_TXT_HDR : C_TXT, lx + 8, ry2 + LC_ROW_H - 5, TZ_LIST[i]); vis++; } /* ── Keyboard pane ── */ xft_text(s, xd, s->f_head, C_TXT, rx, ty, "Keyboard Layout"); int kly = ty + 22 + 32; /* align with timezone list (below filter) */ fill_rect(s, rx, kly, LC_PANE_W, LC_LIST_H + 26, C_PANEL); XSetForeground(s->dpy, s->gc, mkcolor(s, C_SEP)); XDrawRectangle(s->dpy, s->buf, s->gc, rx, kly, LC_PANE_W-1, LC_LIST_H+25); int kvis = NKB < LC_ROWS_VIS + 1 ? NKB : LC_ROWS_VIS + 1; for (int i = s->kb_scroll; i < NKB && (i - s->kb_scroll) < kvis; i++) { int ky = kly + (i - s->kb_scroll) * LC_ROW_H; if (i == s->kb_sel) fill_rect(s, rx+1, ky, LC_PANE_W-2, LC_ROW_H, C_RADIO_SEL); else if ((i - s->kb_scroll) % 2 == 1) fill_rect(s, rx+1, ky, LC_PANE_W-2, LC_ROW_H, 0x141C2C); char kbuf[48]; snprintf(kbuf, sizeof(kbuf), "[%s] %s", KB_LIST[i].code, KB_LIST[i].label); xft_text(s, xd, s->f_small, i == s->kb_sel ? C_TXT_HDR : C_TXT, rx + 8, ky + LC_ROW_H - 5, kbuf); } /* selected values footer */ int sy2 = BODY_Y + BODY_H - FOOT_H - 4; char sel_tz[80], sel_kb[80]; snprintf(sel_tz, sizeof(sel_tz), "Timezone: %s", s->tz_sel >= 0 ? TZ_LIST[s->tz_sel] : "UTC"); snprintf(sel_kb, sizeof(sel_kb), " | Layout: %s", KB_LIST[s->kb_sel].code); xft_text(s, xd, s->f_small, C_TXT_DIM, lx, sy2, sel_tz); xft_text(s, xd, s->f_small, C_TXT_DIM, lx + text_w(s, s->f_small, sel_tz), sy2, sel_kb); draw_footer(s, xd, "← Back", "Next →", C_BTN_PRI); } 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->buf, 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_LOCALE: draw_locale(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->buf, s->gc, 0, 0, WIN_W-1, WIN_H-1); XftDrawDestroy(xd); /* blit double-buffer → window in one shot (eliminates ghosting) */ XCopyArea(s->dpy, s->buf, s->win, s->gc, 0, 0, WIN_W, WIN_H, 0, 0); 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, s->tz_sel >= 0 ? TZ_LIST[s->tz_sel] : "UTC", KB_LIST[s->kb_sel].code, 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_LOCALE; break; case STEP_LOCALE: 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; } } /* locale pane clicks */ if (s->step == STEP_LOCALE) { int lx = 30, rx = WIN_W/2 + 4; int ty = BODY_Y + 14; int fy = ty + 22; /* filter input focus */ if (in_rect(x, y, lx, fy, LC_PANE_W, 26)) { s->tz_filter_focus = 1; redraw(s); return; } else { s->tz_filter_focus = 0; } /* timezone list */ int ly = fy + 32; if (in_rect(x, y, lx, ly, LC_PANE_W, LC_LIST_H)) { int vis = 0, row = 0; int clicked_row = (y - ly) / LC_ROW_H; for (int i = 0; i < NTZ; i++) { if (s->tz_filter_len && !strstr(TZ_LIST[i], s->tz_filter)) continue; if (row++ < s->tz_scroll) continue; if (vis == clicked_row) { s->tz_sel = i; break; } vis++; } redraw(s); return; } /* keyboard list */ int kly = ty + 22 + 32; if (in_rect(x, y, rx, kly, LC_PANE_W, LC_LIST_H + 26)) { int row2 = (y - kly) / LC_ROW_H + s->kb_scroll; if (row2 >= 0 && row2 < NKB) { s->kb_sel = row2; } redraw(s); return; } redraw(s); return; } /* 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); /* Locale step: typing into the timezone filter */ if (s->step == STEP_LOCALE && s->tz_filter_focus) { if (sym == XK_BackSpace && s->tz_filter_len > 0) { s->tz_filter[--s->tz_filter_len] = '\0'; s->tz_scroll = 0; redraw(s); return; } if (sym == XK_Escape) { s->tz_filter_len = 0; s->tz_filter[0] = '\0'; s->tz_scroll = 0; redraw(s); return; } /* scroll list with arrow keys */ if (sym == XK_Down) { s->tz_scroll++; redraw(s); return; } if (sym == XK_Up && s->tz_scroll > 0) { s->tz_scroll--; redraw(s); return; } char buf[8]; int len = XLookupString(e, buf, sizeof(buf)-1, NULL, NULL); if (len > 0 && (unsigned char)buf[0] >= 32 && s->tz_filter_len < (int)sizeof(s->tz_filter) - 2) { buf[len] = '\0'; strcat(s->tz_filter, buf); s->tz_filter_len += len; s->tz_scroll = 0; redraw(s); return; } return; } /* 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); s->buf = XCreatePixmap(s->dpy, s->win, WIN_W, WIN_H, s->depth); /* 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->tz_sel = 0; /* UTC */ s->kb_sel = 0; /* us */ 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; }