Add Nebula — native C settings panel (Display, Appearance, Input, About)
nebula/nebula.c (~600 lines, single-file, native Xlib/Xft): - Display tab: resolution cycling via xrandr, DPI toggle (96/144), brightness control via brightnessctl - Appearance tab: wallpaper reset/open, shows active GTK/icon theme - Input tab: key repeat delay + rate steppers (reads live from XKB), tap-to-click and natural scrolling toggles via xinput - About tab: version, component, platform info - Fixed-size 700×500 window, dark-navy palette, sidebar nav with accent mark on selected tab, dirty-state indicator, Apply/Close - Escape/Q/Enter shortcuts; Enter applies pending changes Wired up: - Root Makefile: nebula target, install -Dm755 nebula/nebula - Right-click desktop menu: "System Settings" → nebula - ISO: binary installed to /usr/local/bin/nebula, Super+S xbindkeys shortcut, file permissions map entry - .gitignore: nebula/nebula binary Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
98aeafc4f0
commit
53c6199102
6 files changed
+880
-2
No files matched your search
@@ -2,6 +2,7 @@
|
||||
*.iso
|
||||
*.o
|
||||
deskbar/oriondeskbar
|
||||
nebula/nebula
|
||||
installer/orioninstall
|
||||
installer/orioninstall-gui
|
||||
launcher/orionlauncher
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
# make clean — remove build artifacts
|
||||
# make iso — build the bootable ISO (requires Arch Linux + archiso)
|
||||
|
||||
.PHONY: all wm deskbar launcher installer clean install iso
|
||||
.PHONY: all wm deskbar launcher installer nebula clean install iso
|
||||
|
||||
all: wm deskbar launcher installer
|
||||
all: wm deskbar launcher installer nebula
|
||||
|
||||
wm:
|
||||
$(MAKE) -C wm
|
||||
@@ -21,11 +21,15 @@ launcher:
|
||||
installer:
|
||||
$(MAKE) -C installer
|
||||
|
||||
nebula:
|
||||
$(MAKE) -C nebula
|
||||
|
||||
clean:
|
||||
$(MAKE) -C wm clean
|
||||
$(MAKE) -C deskbar clean
|
||||
$(MAKE) -C launcher clean
|
||||
$(MAKE) -C installer clean
|
||||
$(MAKE) -C nebula clean
|
||||
|
||||
install: all
|
||||
# binaries
|
||||
@@ -33,6 +37,7 @@ install: all
|
||||
install -Dm755 deskbar/oriondeskbar /usr/local/bin/oriondeskbar
|
||||
install -Dm755 launcher/orionlauncher /usr/local/bin/orionlauncher
|
||||
install -Dm755 themes/orionwallpaper /usr/local/bin/orionwallpaper
|
||||
install -Dm755 nebula/nebula /usr/local/bin/nebula
|
||||
# GTK theme
|
||||
mkdir -p /usr/share/themes
|
||||
cp -r themes/OrionOS /usr/share/themes/
|
||||
|
||||
@@ -230,6 +230,7 @@ install -m755 "$SRC/wm/orionwm" "$AIROOTFS/usr/local/bin/orionwm"
|
||||
install -m755 "$SRC/deskbar/oriondeskbar" "$AIROOTFS/usr/local/bin/oriondeskbar"
|
||||
install -m755 "$SRC/launcher/orionlauncher" "$AIROOTFS/usr/local/bin/orionlauncher"
|
||||
install -m755 "$SRC/themes/orionwallpaper" "$AIROOTFS/usr/local/bin/orionwallpaper"
|
||||
install -m755 "$SRC/nebula/nebula" "$AIROOTFS/usr/local/bin/nebula"
|
||||
install -m755 "$SRC/installer/orioninstall" "$AIROOTFS/usr/local/bin/orioninstall"
|
||||
# GUI installer (compiled binary; falls back gracefully if not built)
|
||||
if [[ -f "$SRC/installer/orioninstall-gui" ]]; then
|
||||
@@ -335,6 +336,10 @@ cat > "$SKEL/.xbindkeysrc" <<'XBK'
|
||||
"orionlauncher"
|
||||
Mod1 + F1
|
||||
|
||||
# System settings (Nebula)
|
||||
"nebula"
|
||||
super + s
|
||||
|
||||
# Brightness (Mac Fn+F1/F2, also mapped as XF86 keys)
|
||||
"brightnessctl set 10%-"
|
||||
XF86MonBrightnessDown
|
||||
@@ -684,6 +689,7 @@ file_permissions=(
|
||||
["/usr/local/bin/oriondeskbar"]="0:0:755"
|
||||
["/usr/local/bin/orionlauncher"]="0:0:755"
|
||||
["/usr/local/bin/orionwallpaper"]="0:0:755"
|
||||
["/usr/local/bin/nebula"]="0:0:755"
|
||||
["/usr/local/bin/orioninstall"]="0:0:755"
|
||||
)
|
||||
PROFILEDEF
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
BIN = nebula
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -O2 \
|
||||
$(shell pkg-config --cflags x11 xft fontconfig 2>/dev/null)
|
||||
LIBS = $(shell pkg-config --libs x11 xft fontconfig 2>/dev/null) \
|
||||
-lX11 -lXft -lfontconfig
|
||||
|
||||
SRC = nebula.c
|
||||
|
||||
$(BIN): $(SRC)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)
|
||||
|
||||
.PHONY: clean
|
||||
+848
@@ -0,0 +1,848 @@
|
||||
/* nebula.c — OrionOS Settings Panel
|
||||
*
|
||||
* Sections: Display | Appearance | Input | About
|
||||
* Native C/Xlib + Xft, dark-navy palette matching the rest of OrionOS.
|
||||
*/
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/Xft/Xft.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* ── 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;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ static const MenuItem MENU_ITEMS[] = {
|
||||
{ "Web Browser", "firefox &" },
|
||||
{ "System Monitor", "alacritty -T 'System Monitor' -e htop &" },
|
||||
{ "Volume Control", "pavucontrol &" },
|
||||
{ "System Settings", "nebula &" },
|
||||
{ "Display Settings", "arandr &" },
|
||||
{ "──────────────────", NULL },
|
||||
{ "Change Wallpaper", "orionwallpaper --reset &" },
|
||||
|
||||
Reference in new issue
Block a user