/* nebula.c — OrionOS Settings Panel * * Sections: Display | Appearance | Input | About * Native C/Xlib + Xft, dark-navy palette matching the rest of OrionOS. */ #include #include #include #include #include #include #include #include #include #include /* ── layout ─────────────────────────────────────────────────────────── */ #define WIN_W 700 #define WIN_H 500 #define SIDEBAR_W 160 #define HDR_H 50 #define FOOTER_H 50 #define DIV_X SIDEBAR_W #define CONTENT_X (SIDEBAR_W + 1) #define CONTENT_Y HDR_H #define CONTENT_W (WIN_W - SIDEBAR_W - 1) #define CONTENT_H (WIN_H - HDR_H - FOOTER_H) #define TAB_H 42 #define PAD 20 #define ROW_H 44 #define BTN_W 90 #define BTN_H 28 #define STEP_SZ 22 /* +/- stepper button size */ /* ── palette ─────────────────────────────────────────────────────────── */ #define C_BG 0x0E1220 #define C_SIDEBAR 0x090D1A #define C_HDR 0x080B16 #define C_CONTENT 0x0D1624 #define C_DIVIDER 0x182030 #define C_ACCENT 0x1E58A8 #define C_ACCENT_HOV 0x2A70CC #define C_TAB_HOV 0x101828 #define C_TAB_SEL_BG 0x0D1624 #define C_TAB_MARK 0x1E58A8 /* left edge mark on selected tab */ #define C_BORDER 0x1A2840 #define C_TEXT 0xA0BCD8 #define C_TEXT_DIM 0x3A5068 #define C_TEXT_BRIGHT 0xE0EEFF #define C_TAB_TXT 0x3E5870 #define C_TAB_SEL_TXT 0x90BBEE #define C_BTN_BG 0x182848 #define C_BTN_HOV 0x1E3860 #define C_BTN_APPLY 0x1A4888 #define C_BTN_APPLY_H 0x2060AA #define C_TOGGLE_ON 0x1E58A8 #define C_TOGGLE_OFF 0x141E30 #define C_TOGGLE_KNOB 0xD0E4F8 #define C_ROW_RULE 0x111A2A #define C_STEP_BG 0x141E30 #define C_STEP_HOV 0x1C2A40 #define C_VALUE_BG 0x0C1520 #define C_ABOUT_ACCENT 0x1E4D88 #define FONT_UI "Sans:size=9" #define FONT_HDR "Sans:size=10:weight=bold" #define FONT_TAB "Sans:size=9" #define FONT_SECT "Sans:size=8:weight=bold" /* ── sections ────────────────────────────────────────────────────────── */ typedef enum { SEC_DISPLAY, SEC_APPEARANCE, SEC_INPUT, SEC_ABOUT, SEC_COUNT } Section; static const char *SEC_LABELS[SEC_COUNT] = { "Display", "Appearance", "Input", "About" }; /* ── settings state ──────────────────────────────────────────────────── */ #define MAX_RES 24 typedef struct { char label[24]; int w, h; } Resolution; typedef struct { /* display */ Resolution res[MAX_RES]; int nres; int res_idx; int dpi; /* 96 or 144 */ int brightness; /* 0–100, -1 = not available */ /* input */ int key_delay; /* ms */ int key_rate; /* repeats/s */ int tap_click; /* bool */ int nat_scroll; /* bool */ } Settings; /* ── app struct ──────────────────────────────────────────────────────── */ typedef struct { Display *dpy; int screen; Window root, win; GC gc; Colormap cmap; Visual *visual; int depth; XftFont *font_ui; XftFont *font_hdr; XftFont *font_tab; XftFont *font_sect; Section section; Settings cur; /* current (applied) state */ Settings pend; /* pending (edited, not yet applied) */ int hover_tab; /* -1 = none */ int hover_id; /* widget hover id, -1 = none */ int dirty; /* pending != cur */ } Nebula; /* ── color helpers ───────────────────────────────────────────────────── */ static unsigned long col(Nebula *n, 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(n->dpy, n->cmap, &c); return c.pixel; } static void xcol(Nebula *n, unsigned long rgb, XftColor *out) { XRenderColor rc = { .red = ((rgb >> 16) & 0xFF) * 257, .green = ((rgb >> 8) & 0xFF) * 257, .blue = ( rgb & 0xFF) * 257, .alpha = 0xFFFF }; XftColorAllocValue(n->dpy, n->visual, n->cmap, &rc, out); } /* ── draw primitives ─────────────────────────────────────────────────── */ static void fill(Nebula *n, unsigned long rgb, int x, int y, int w, int h) { XSetForeground(n->dpy, n->gc, col(n, rgb)); XFillRectangle(n->dpy, n->win, n->gc, x, y, w, h); } static void rect(Nebula *n, unsigned long rgb, int x, int y, int w, int h) { XSetForeground(n->dpy, n->gc, col(n, rgb)); XDrawRectangle(n->dpy, n->win, n->gc, x, y, w - 1, h - 1); } static void text(Nebula *n, XftFont *f, unsigned long rgb, int x, int y, const char *s) { XftDraw *xd = XftDrawCreate(n->dpy, n->win, n->visual, n->cmap); XftColor c; xcol(n, rgb, &c); XftDrawStringUtf8(xd, &c, f, x, y, (const FcChar8 *)s, strlen(s)); XftColorFree(n->dpy, n->visual, n->cmap, &c); XftDrawDestroy(xd); } static int text_w(Nebula *n, XftFont *f, const char *s) { XGlyphInfo ext; XftTextExtentsUtf8(n->dpy, f, (const FcChar8 *)s, strlen(s), &ext); return ext.xOff; } /* center text vertically in a box; returns baseline y */ static int baseline_in(XftFont *f, int box_y, int box_h) { return box_y + (box_h + f->ascent - f->descent) / 2; } /* ── sidebar ─────────────────────────────────────────────────────────── */ static void draw_sidebar(Nebula *n) { fill(n, C_SIDEBAR, 0, 0, SIDEBAR_W, WIN_H); /* header area background */ fill(n, C_HDR, 0, 0, SIDEBAR_W, HDR_H); /* app title */ int tx = PAD; int ty = baseline_in(n->font_hdr, 0, HDR_H); text(n, n->font_hdr, C_TEXT_BRIGHT, tx, ty, "Nebula"); text(n, n->font_ui, C_TEXT_DIM, tx, ty + 14, "System Settings"); /* divider line between header and tabs */ fill(n, C_DIVIDER, 0, HDR_H, SIDEBAR_W, 1); /* tabs */ for (int i = 0; i < SEC_COUNT; i++) { int ty2 = HDR_H + 8 + i * TAB_H; int sel = (n->section == (Section)i); int hov = (n->hover_tab == i && !sel); unsigned long bg = sel ? C_TAB_SEL_BG : (hov ? C_TAB_HOV : C_SIDEBAR); fill(n, bg, 0, ty2, SIDEBAR_W, TAB_H); if (sel) { fill(n, C_TAB_MARK, 0, ty2, 3, TAB_H); } int lby = baseline_in(n->font_tab, ty2, TAB_H); unsigned long tc = sel ? C_TAB_SEL_TXT : C_TAB_TXT; text(n, n->font_tab, tc, 14, lby, SEC_LABELS[i]); } /* right border */ fill(n, C_DIVIDER, SIDEBAR_W, 0, 1, WIN_H); } /* ── header ──────────────────────────────────────────────────────────── */ static void draw_header(Nebula *n) { fill(n, C_HDR, CONTENT_X, 0, CONTENT_W, HDR_H); fill(n, C_DIVIDER, CONTENT_X, HDR_H, CONTENT_W, 1); int ty = baseline_in(n->font_hdr, 0, HDR_H); text(n, n->font_hdr, C_TEXT_BRIGHT, CONTENT_X + PAD, ty, SEC_LABELS[n->section]); } /* ── footer ──────────────────────────────────────────────────────────── */ #define HOV_APPLY 100 #define HOV_CLOSE 101 #define HOV_RESET 102 static void draw_footer(Nebula *n) { int fy = WIN_H - FOOTER_H; fill(n, C_HDR, 0, fy, WIN_W, FOOTER_H); fill(n, C_DIVIDER, 0, fy, WIN_W, 1); /* Apply button */ int ax = WIN_W - BTN_W - PAD; int ay = fy + (FOOTER_H - BTN_H) / 2; unsigned long abg = (n->hover_id == HOV_APPLY) ? C_BTN_APPLY_H : C_BTN_APPLY; fill(n, abg, ax, ay, BTN_W, BTN_H); rect(n, C_ACCENT, ax, ay, BTN_W, BTN_H); int atx = ax + (BTN_W - text_w(n, n->font_ui, "Apply")) / 2; text(n, n->font_ui, C_TEXT_BRIGHT, atx, baseline_in(n->font_ui, ay, BTN_H), "Apply"); /* Close button */ int cx = ax - BTN_W - 8; unsigned long cbg = (n->hover_id == HOV_CLOSE) ? C_BTN_HOV : C_BTN_BG; fill(n, cbg, cx, ay, BTN_W, BTN_H); rect(n, C_BORDER, cx, ay, BTN_W, BTN_H); int ctx = cx + (BTN_W - text_w(n, n->font_ui, "Close")) / 2; text(n, n->font_ui, C_TEXT, ctx, baseline_in(n->font_ui, ay, BTN_H), "Close"); /* dirty indicator */ if (n->dirty) text(n, n->font_ui, C_TEXT_DIM, CONTENT_X + PAD, baseline_in(n->font_ui, fy, FOOTER_H), "Unsaved changes"); } /* ── row drawing helpers ─────────────────────────────────────────────── */ static int row_y(int idx) { return CONTENT_Y + PAD + idx * ROW_H; } static void draw_row_label(Nebula *n, int idx, const char *label) { int ry = row_y(idx); if (idx % 2 == 1) fill(n, C_ROW_RULE, CONTENT_X, ry, CONTENT_W, ROW_H); int ty = baseline_in(n->font_ui, ry, ROW_H); text(n, n->font_ui, C_TEXT, CONTENT_X + PAD, ty, label); } /* Draw a [−] VALUE [+] stepper; returns widget base x */ static void draw_stepper(Nebula *n, int hov_dec, int hov_inc, int x, int ry, const char *value) { int sy = ry + (ROW_H - STEP_SZ) / 2; /* [−] */ unsigned long bg_d = (n->hover_id == hov_dec) ? C_STEP_HOV : C_STEP_BG; fill(n, bg_d, x, sy, STEP_SZ, STEP_SZ); rect(n, C_BORDER, x, sy, STEP_SZ, STEP_SZ); text(n, n->font_ui, C_TEXT, x + 6, baseline_in(n->font_ui, sy, STEP_SZ), "−"); /* value box */ int vw = 70; fill(n, C_VALUE_BG, x + STEP_SZ + 2, sy, vw, STEP_SZ); rect(n, C_BORDER, x + STEP_SZ + 2, sy, vw, STEP_SZ); int vtx = x + STEP_SZ + 2 + (vw - text_w(n, n->font_ui, value)) / 2; text(n, n->font_ui, C_TEXT_BRIGHT, vtx, baseline_in(n->font_ui, sy, STEP_SZ), value); /* [+] */ int px = x + STEP_SZ + 2 + vw + 2; unsigned long bg_i = (n->hover_id == hov_inc) ? C_STEP_HOV : C_STEP_BG; fill(n, bg_i, px, sy, STEP_SZ, STEP_SZ); rect(n, C_BORDER, px, sy, STEP_SZ, STEP_SZ); text(n, n->font_ui, C_TEXT, px + 5, baseline_in(n->font_ui, sy, STEP_SZ), "+"); } /* Draw a pill toggle; toggle_id used for hover */ static void draw_toggle(Nebula *n, int toggle_id, int x, int ry, int on) { int tw = 46, th = 22; int ty = ry + (ROW_H - th) / 2; unsigned long bg = on ? C_TOGGLE_ON : C_TOGGLE_OFF; if (n->hover_id == toggle_id) bg = on ? C_ACCENT_HOV : C_TAB_HOV; fill(n, bg, x, ty, tw, th); rect(n, C_BORDER, x, ty, tw, th); /* knob */ int kx = on ? x + tw - th + 2 : x + 2; fill(n, C_TOGGLE_KNOB, kx, ty + 2, th - 4, th - 4); /* label */ const char *lbl = on ? "On" : "Off"; int lx = on ? x + 6 : x + th - 2; text(n, n->font_ui, on ? C_TEXT_BRIGHT : C_TEXT_DIM, lx, baseline_in(n->font_ui, ty, th), lbl); } /* ── DISPLAY section ─────────────────────────────────────────────────── */ #define HOV_RES_DEC 0 #define HOV_RES_INC 1 #define HOV_DPI_DEC 2 #define HOV_DPI_INC 3 #define HOV_BRT_DEC 4 #define HOV_BRT_INC 5 static void draw_display(Nebula *n) { char buf[32]; /* row 0: Resolution */ draw_row_label(n, 0, "Resolution"); snprintf(buf, sizeof(buf), "%s", n->pend.nres > 0 ? n->pend.res[n->pend.res_idx].label : "—"); draw_stepper(n, HOV_RES_DEC, HOV_RES_INC, CONTENT_X + PAD + 140, row_y(0), buf); /* row 1: DPI */ draw_row_label(n, 1, "DPI / HiDPI"); snprintf(buf, sizeof(buf), "%d dpi", n->pend.dpi); draw_stepper(n, HOV_DPI_DEC, HOV_DPI_INC, CONTENT_X + PAD + 140, row_y(1), buf); /* row 2: Brightness */ draw_row_label(n, 2, "Brightness"); if (n->pend.brightness >= 0) { snprintf(buf, sizeof(buf), "%d%%", n->pend.brightness); draw_stepper(n, HOV_BRT_DEC, HOV_BRT_INC, CONTENT_X + PAD + 140, row_y(2), buf); } else { int ty = baseline_in(n->font_ui, row_y(2), ROW_H); text(n, n->font_ui, C_TEXT_DIM, CONTENT_X + PAD + 140, ty, "Not available"); } /* hint */ int hy = CONTENT_Y + PAD + 3 * ROW_H + 8; text(n, n->font_ui, C_TEXT_DIM, CONTENT_X + PAD, hy, "Changes apply immediately. Resolution requires a connected display."); } /* ── APPEARANCE section ──────────────────────────────────────────────── */ #define HOV_WP_RESET 10 #define HOV_WP_OPEN 11 static void draw_appearance(Nebula *n) { /* row 0: Wallpaper */ draw_row_label(n, 0, "Wallpaper"); int ry = row_y(0); int bx = CONTENT_X + PAD + 140; int by = ry + (ROW_H - BTN_H) / 2; unsigned long r_bg = (n->hover_id == HOV_WP_RESET) ? C_BTN_HOV : C_BTN_BG; fill(n, r_bg, bx, by, BTN_W + 10, BTN_H); rect(n, C_BORDER, bx, by, BTN_W + 10, BTN_H); text(n, n->font_ui, C_TEXT, bx + 8, baseline_in(n->font_ui, by, BTN_H), "Reset Default"); unsigned long o_bg = (n->hover_id == HOV_WP_OPEN) ? C_BTN_HOV : C_BTN_BG; int ox = bx + BTN_W + 10 + 8; fill(n, o_bg, ox, by, BTN_W + 20, BTN_H); rect(n, C_BORDER, ox, by, BTN_W + 20, BTN_H); text(n, n->font_ui, C_TEXT, ox + 8, baseline_in(n->font_ui, by, BTN_H), "Open Pictures"); /* row 1: Icon Theme (informational) */ draw_row_label(n, 1, "Icon Theme"); int ty = baseline_in(n->font_ui, row_y(1), ROW_H); text(n, n->font_ui, C_TEXT, CONTENT_X + PAD + 140, ty, "Papirus Dark"); /* row 2: GTK Theme (informational) */ draw_row_label(n, 2, "GTK Theme"); ty = baseline_in(n->font_ui, row_y(2), ROW_H); text(n, n->font_ui, C_TEXT, CONTENT_X + PAD + 140, ty, "OrionOS"); /* row 3: Font */ draw_row_label(n, 3, "UI Font"); ty = baseline_in(n->font_ui, row_y(3), ROW_H); text(n, n->font_ui, C_TEXT, CONTENT_X + PAD + 140, ty, "Sans 10"); } /* ── INPUT section ───────────────────────────────────────────────────── */ #define HOV_KD_DEC 20 #define HOV_KD_INC 21 #define HOV_KR_DEC 22 #define HOV_KR_INC 23 #define HOV_TAP 24 #define HOV_NSCROLL 25 static void draw_input(Nebula *n) { char buf[32]; /* row 0: Key delay */ draw_row_label(n, 0, "Key Repeat Delay"); snprintf(buf, sizeof(buf), "%d ms", n->pend.key_delay); draw_stepper(n, HOV_KD_DEC, HOV_KD_INC, CONTENT_X + PAD + 160, row_y(0), buf); /* row 1: Key rate */ draw_row_label(n, 1, "Key Repeat Rate"); snprintf(buf, sizeof(buf), "%d /s", n->pend.key_rate); draw_stepper(n, HOV_KR_DEC, HOV_KR_INC, CONTENT_X + PAD + 160, row_y(1), buf); /* row 2: Tap-to-click */ draw_row_label(n, 2, "Tap-to-Click"); draw_toggle(n, HOV_TAP, CONTENT_X + PAD + 160, row_y(2), n->pend.tap_click); /* row 3: Natural scroll */ draw_row_label(n, 3, "Natural Scrolling"); draw_toggle(n, HOV_NSCROLL, CONTENT_X + PAD + 160, row_y(3), n->pend.nat_scroll); int hy = CONTENT_Y + PAD + 4 * ROW_H + 8; text(n, n->font_ui, C_TEXT_DIM, CONTENT_X + PAD, hy, "Touchpad settings apply to libinput devices."); } /* ── ABOUT section ───────────────────────────────────────────────────── */ static void draw_about(Nebula *n) { int cx = CONTENT_X + PAD; int cy = CONTENT_Y + PAD + 10; /* big name */ XftColor ac; xcol(n, C_ACCENT, &ac); XftDraw *xd = XftDrawCreate(n->dpy, n->win, n->visual, n->cmap); XftDrawStringUtf8(xd, &ac, n->font_hdr, cx, cy + n->font_hdr->ascent, "OrionOS", 7); XftColorFree(n->dpy, n->visual, n->cmap, &ac); XftDrawDestroy(xd); static const char *INFO[] = { "Version: 0.1 (Mintaka)", "Shell: OrionWM + OrionDeskbar", "Toolkit: Xlib / Xft (native C)", "Platform: Arch Linux", "", "Nebula — System Settings", "Part of the OrionOS desktop environment.", }; int n_info = (int)(sizeof(INFO) / sizeof(INFO[0])); int ly = cy + n->font_hdr->ascent + 24; for (int i = 0; i < n_info; i++) { unsigned long c2 = (i >= 5) ? C_TEXT_DIM : C_TEXT; text(n, n->font_ui, c2, cx, ly, INFO[i]); ly += 20; } /* colored divider */ fill(n, C_ABOUT_ACCENT, cx, cy + n->font_hdr->ascent + 8, 280, 1); } /* ── full redraw ─────────────────────────────────────────────────────── */ static void draw(Nebula *n) { fill(n, C_CONTENT, CONTENT_X, 0, CONTENT_W, WIN_H); draw_sidebar(n); draw_header(n); switch (n->section) { case SEC_DISPLAY: draw_display(n); break; case SEC_APPEARANCE: draw_appearance(n); break; case SEC_INPUT: draw_input(n); break; case SEC_ABOUT: draw_about(n); break; default: break; } draw_footer(n); } /* ── hit testing ─────────────────────────────────────────────────────── */ static int stepper_hit(int mx, int my, int sx, int ry, int hov_dec, int hov_inc) { int sy = ry + (ROW_H - STEP_SZ) / 2; if (my < sy || my >= sy + STEP_SZ) return -1; if (mx >= sx && mx < sx + STEP_SZ) return hov_dec; int px = sx + STEP_SZ + 2 + 70 + 2; if (mx >= px && mx < px + STEP_SZ) return hov_inc; return -1; } static int toggle_hit(int mx, int my, int tx, int ry) { int tw = 46, th = 22; int ty = ry + (ROW_H - th) / 2; return (mx >= tx && mx < tx + tw && my >= ty && my < ty + th); } static int hit_widget(Nebula *n, int mx, int my) { /* footer buttons */ int fy = WIN_H - FOOTER_H; int ay = fy + (FOOTER_H - BTN_H) / 2; int ax = WIN_W - BTN_W - PAD; int cx = ax - BTN_W - 8; if (my >= ay && my < ay + BTN_H) { if (mx >= ax && mx < ax + BTN_W) return HOV_APPLY; if (mx >= cx && mx < cx + BTN_W) return HOV_CLOSE; } /* sidebar tabs */ if (mx < SIDEBAR_W) { for (int i = 0; i < SEC_COUNT; i++) { int ty = HDR_H + 8 + i * TAB_H; if (my >= ty && my < ty + TAB_H) return 200 + i; } return -1; } /* content area widgets */ switch (n->section) { case SEC_DISPLAY: { int sx = CONTENT_X + PAD + 140; int h = stepper_hit(mx, my, sx, row_y(0), HOV_RES_DEC, HOV_RES_INC); if (h >= 0) return h; h = stepper_hit(mx, my, sx, row_y(1), HOV_DPI_DEC, HOV_DPI_INC); if (h >= 0) return h; if (n->pend.brightness >= 0) { h = stepper_hit(mx, my, sx, row_y(2), HOV_BRT_DEC, HOV_BRT_INC); if (h >= 0) return h; } break; } case SEC_APPEARANCE: { int bx = CONTENT_X + PAD + 140; int ry0 = row_y(0); int by = ry0 + (ROW_H - BTN_H) / 2; if (my >= by && my < by + BTN_H) { if (mx >= bx && mx < bx + BTN_W + 10) return HOV_WP_RESET; int ox = bx + BTN_W + 10 + 8; if (mx >= ox && mx < ox + BTN_W + 20) return HOV_WP_OPEN; } break; } case SEC_INPUT: { int sx = CONTENT_X + PAD + 160; int h = stepper_hit(mx, my, sx, row_y(0), HOV_KD_DEC, HOV_KD_INC); if (h >= 0) return h; h = stepper_hit(mx, my, sx, row_y(1), HOV_KR_DEC, HOV_KR_INC); if (h >= 0) return h; if (toggle_hit(mx, my, sx, row_y(2))) return HOV_TAP; if (toggle_hit(mx, my, sx, row_y(3))) return HOV_NSCROLL; break; } default: break; } return -1; } /* ── apply settings ──────────────────────────────────────────────────── */ static void apply_display(Nebula *n) { if (n->pend.nres > 0 && n->pend.res_idx != n->cur.res_idx) { char cmd[256]; snprintf(cmd, sizeof(cmd), "xrandr --output $(xrandr | awk '/ connected/{print $1;exit}')" " --mode %dx%d &", n->pend.res[n->pend.res_idx].w, n->pend.res[n->pend.res_idx].h); system(cmd); } if (n->pend.dpi != n->cur.dpi) { char cmd[64]; snprintf(cmd, sizeof(cmd), "xrandr --dpi %d &", n->pend.dpi); system(cmd); } if (n->pend.brightness >= 0 && n->pend.brightness != n->cur.brightness) { char cmd[64]; snprintf(cmd, sizeof(cmd), "brightnessctl set %d%% &", n->pend.brightness); system(cmd); } } static void apply_input(Nebula *n) { if (n->pend.key_delay != n->cur.key_delay || n->pend.key_rate != n->cur.key_rate) { char cmd[64]; snprintf(cmd, sizeof(cmd), "xset r rate %d %d", n->pend.key_delay, n->pend.key_rate); system(cmd); } if (n->pend.tap_click != n->cur.tap_click) { char cmd[128]; snprintf(cmd, sizeof(cmd), "xinput set-prop \"$(xinput list --name-only" " | grep -i touchpad | head -1)\"" " 'libinput Tapping Enabled' %d 2>/dev/null || true", n->pend.tap_click); system(cmd); } if (n->pend.nat_scroll != n->cur.nat_scroll) { char cmd[128]; snprintf(cmd, sizeof(cmd), "xinput set-prop \"$(xinput list --name-only" " | grep -i touchpad | head -1)\"" " 'libinput Natural Scrolling Enabled' %d 2>/dev/null || true", n->pend.nat_scroll); system(cmd); } } static void apply_all(Nebula *n) { apply_display(n); apply_input(n); n->cur = n->pend; n->dirty = 0; } /* ── click handler ───────────────────────────────────────────────────── */ static void on_click(Nebula *n, int mx, int my) { int h = hit_widget(n, mx, my); /* tab select */ if (h >= 200 && h < 200 + SEC_COUNT) { n->section = (Section)(h - 200); draw(n); return; } switch (h) { case HOV_APPLY: apply_all(n); draw(n); return; case HOV_CLOSE: XDestroyWindow(n->dpy, n->win); return; /* Display */ case HOV_RES_DEC: if (n->pend.res_idx > 0) { n->pend.res_idx--; n->dirty = 1; } break; case HOV_RES_INC: if (n->pend.res_idx < n->pend.nres - 1) { n->pend.res_idx++; n->dirty = 1; } break; case HOV_DPI_DEC: if (n->pend.dpi > 72) { n->pend.dpi = (n->pend.dpi == 144) ? 96 : n->pend.dpi - 24; n->dirty = 1; } break; case HOV_DPI_INC: if (n->pend.dpi < 192) { n->pend.dpi = (n->pend.dpi == 96) ? 144 : n->pend.dpi + 24; n->dirty = 1; } break; case HOV_BRT_DEC: if (n->pend.brightness > 5) { n->pend.brightness -= 5; n->dirty = 1; } break; case HOV_BRT_INC: if (n->pend.brightness < 100) { n->pend.brightness += 5; n->dirty = 1; } break; /* Appearance */ case HOV_WP_RESET: system("orionwallpaper --reset &"); return; case HOV_WP_OPEN: system("xdg-open ~/Pictures 2>/dev/null || thunar ~/Pictures &"); return; /* Input */ case HOV_KD_DEC: if (n->pend.key_delay > 150) { n->pend.key_delay -= 50; n->dirty = 1; } break; case HOV_KD_INC: if (n->pend.key_delay < 1000) { n->pend.key_delay += 50; n->dirty = 1; } break; case HOV_KR_DEC: if (n->pend.key_rate > 5) { n->pend.key_rate -= 5; n->dirty = 1; } break; case HOV_KR_INC: if (n->pend.key_rate < 60) { n->pend.key_rate += 5; n->dirty = 1; } break; case HOV_TAP: n->pend.tap_click ^= 1; n->dirty = 1; break; case HOV_NSCROLL: n->pend.nat_scroll ^= 1; n->dirty = 1; break; default: return; } draw(n); } /* ── motion / hover ──────────────────────────────────────────────────── */ static void on_motion(Nebula *n, int mx, int my) { int h = hit_widget(n, mx, my); int ht = (mx < SIDEBAR_W && my >= HDR_H + 8) ? (my - HDR_H - 8) / TAB_H : -1; int ht2 = (ht >= 0 && ht < SEC_COUNT) ? ht : -1; if (h != n->hover_id || ht2 != n->hover_tab) { n->hover_id = h; n->hover_tab = ht2; draw(n); } } /* ── init helpers ────────────────────────────────────────────────────── */ static void query_resolutions(Nebula *n) { FILE *f = popen("xrandr 2>/dev/null", "r"); if (!f) return; char line[256]; while (fgets(line, sizeof(line), f) && n->cur.nres < MAX_RES) { int w, h; char star[4] = {0}; if (sscanf(line, " %dx%d%3s", &w, &h, star) >= 2) { int dup = 0; for (int i = 0; i < n->cur.nres; i++) if (n->cur.res[i].w == w && n->cur.res[i].h == h) { dup = 1; break; } if (!dup) { snprintf(n->cur.res[n->cur.nres].label, 24, "%d×%d", w, h); n->cur.res[n->cur.nres].w = w; n->cur.res[n->cur.nres].h = h; if (strchr(star, '*')) n->cur.res_idx = n->cur.nres; n->cur.nres++; } } } pclose(f); if (n->cur.nres == 0) { strcpy(n->cur.res[0].label, "1920×1080"); n->cur.res[0].w = 1920; n->cur.res[0].h = 1080; n->cur.nres = 1; } } static int query_brightness(void) { FILE *f = popen("brightnessctl g 2>/dev/null", "r"); if (!f) return -1; long cur = 0; fscanf(f, "%ld", &cur); pclose(f); f = popen("brightnessctl m 2>/dev/null", "r"); if (!f) return -1; long max = 0; fscanf(f, "%ld", &max); pclose(f); if (max <= 0) return -1; return (int)(cur * 100 / max); } static void read_key_repeat(Nebula *n) { XkbDescPtr xkb = XkbGetKeyboard(n->dpy, XkbAllComponentsMask, XkbUseCoreKbd); if (xkb && xkb->ctrls) { n->cur.key_delay = xkb->ctrls->repeat_delay; int interval = xkb->ctrls->repeat_interval; n->cur.key_rate = interval > 0 ? (1000 / interval) : 30; XkbFreeKeyboard(xkb, 0, True); } else { n->cur.key_delay = 250; n->cur.key_rate = 30; } } static void nebula_init(Nebula *n) { n->dpy = XOpenDisplay(NULL); if (!n->dpy) { fputs("nebula: cannot open display\n", stderr); exit(1); } n->screen = DefaultScreen(n->dpy); n->root = RootWindow(n->dpy, n->screen); n->cmap = DefaultColormap(n->dpy, n->screen); n->visual = DefaultVisual(n->dpy, n->screen); n->depth = DefaultDepth(n->dpy, n->screen); n->font_ui = XftFontOpenName(n->dpy, n->screen, FONT_UI); n->font_hdr = XftFontOpenName(n->dpy, n->screen, FONT_HDR); n->font_tab = XftFontOpenName(n->dpy, n->screen, FONT_TAB); n->font_sect = XftFontOpenName(n->dpy, n->screen, FONT_SECT); if (!n->font_ui || !n->font_hdr) { fputs("nebula: cannot load fonts\n", stderr); exit(1); } /* defaults */ n->section = SEC_DISPLAY; n->hover_tab = -1; n->hover_id = -1; n->dirty = 0; n->cur.dpi = 96; n->cur.brightness = query_brightness(); n->cur.tap_click = 1; n->cur.nat_scroll = 1; query_resolutions(n); read_key_repeat(n); n->pend = n->cur; /* create window */ XSetWindowAttributes swa; swa.background_pixel = col(n, C_BG); swa.event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | KeyPressMask | StructureNotifyMask; n->win = XCreateWindow(n->dpy, n->root, 100, 100, WIN_W, WIN_H, 0, n->depth, InputOutput, n->visual, CWBackPixel | CWEventMask, &swa); /* GC */ n->gc = XCreateGC(n->dpy, n->win, 0, NULL); /* window title & class */ XStoreName(n->dpy, n->win, "Nebula — System Settings"); XSetIconName(n->dpy, n->win, "Nebula"); XClassHint *ch = XAllocClassHint(); if (ch) { ch->res_name = (char *)"nebula"; ch->res_class = (char *)"Nebula"; XSetClassHint(n->dpy, n->win, ch); XFree(ch); } /* size hints (fixed size) */ XSizeHints *sh = XAllocSizeHints(); if (sh) { sh->flags = PMinSize | PMaxSize; sh->min_width = sh->max_width = WIN_W; sh->min_height = sh->max_height = WIN_H; XSetWMNormalHints(n->dpy, n->win, sh); XFree(sh); } /* WM_DELETE_WINDOW */ Atom wm_delete = XInternAtom(n->dpy, "WM_DELETE_WINDOW", False); Atom wm_protos = XInternAtom(n->dpy, "WM_PROTOCOLS", False); XChangeProperty(n->dpy, n->win, wm_protos, XA_ATOM, 32, PropModeReplace, (unsigned char *)&wm_delete, 1); XMapWindow(n->dpy, n->win); XFlush(n->dpy); } /* ── event loop ──────────────────────────────────────────────────────── */ static void nebula_run(Nebula *n) { Atom wm_delete = XInternAtom(n->dpy, "WM_DELETE_WINDOW", False); XEvent ev; for (;;) { XNextEvent(n->dpy, &ev); switch (ev.type) { case Expose: if (ev.xexpose.count == 0) draw(n); break; case ButtonPress: if (ev.xbutton.button == Button1) on_click(n, ev.xbutton.x, ev.xbutton.y); break; case MotionNotify: on_motion(n, ev.xmotion.x, ev.xmotion.y); break; case KeyPress: { KeySym sym = XLookupKeysym(&ev.xkey, 0); if (sym == XK_Escape || sym == XK_q) return; if (sym == XK_Return && n->dirty) { apply_all(n); draw(n); } break; } case ClientMessage: if ((Atom)ev.xclient.data.l[0] == wm_delete) return; break; case DestroyNotify: return; } } } int main(void) { Nebula n = {0}; nebula_init(&n); nebula_run(&n); return 0; }