diff --git a/Makefile b/Makefile index cd443a3..9bca5f6 100644 --- a/Makefile +++ b/Makefile @@ -126,7 +126,7 @@ CORE_SRCS = kernel/main.c kernel/task.c kernel/sync.c kernel/timer.c kernel/prin kernel/syslog.c kernel/module.c kernel/kobject.c kernel/metrics.c kernel/namespace.c \ kernel/signal.c kernel/workqueue.c kernel/tsc.c kernel/vt_art.c \ kernel/sub/sub_runtime.c kernel/sub/sub_vm.c kernel/nt/ob.c kernel/nt/reg.c kernel/nt/io.c \ - kernel/wait.c kernel/futex.c kernel/rcu.c kernel/ktest.c \ + kernel/wait.c kernel/futex.c kernel/rcu.c kernel/ktest.c kernel/time/ktime.c \ kernel/elf.c kernel/exec.c \ lib/string.c lib/vsprintf.c lib/bitmap.c lib/rbtree.c lib/kfifo.c lib/hashtable.c lib/font8x8.c \ init/cmdline.c init/service.c init/bootlogo.c init/logo_data.c usr/initramfs.c block/block.c block/elevator.c \ @@ -451,14 +451,14 @@ $(IMAGE): $(BUILD_DIR)/boot/boot.bin $(BUILD_DIR)/boot/stage2.bin $(BUILD_DIR)/k # ----------------------------------------------------------------------------- # -vga std gives the Bochs VBE adapter the kernel drives. vgamem_mb is raised # because a 1280x720 32-bit framebuffer needs more than the 8 MB default. -QEMU_VGA = -device VGA,vgamem_mb=32 +QEMU_VGA = -device VGA,vgamem_mb=64 # `make run` boots straight into the SUB-OS Graphical Desktop Environment. The # guest window scales with zoom-to-fit; pass `nogui`/`text` on the kernel # command line for the emergency TTY. Use `make run-headless` for serial only. run qemu: $(TARGET) ifeq ($(ARCH), x86_64) - qemu-system-x86_64 -drive format=raw,file=$(IMAGE) -m 256M \ + qemu-system-x86_64 -drive format=raw,file=$(IMAGE) -m 512M \ -netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \ -device e1000,netdev=net0 $(QEMU_VGA) -display gtk,zoom-to-fit=on -serial stdio else ifneq ($(filter $(ARCH), aarch64 arm64),) diff --git a/gui/gui_desktop.c b/gui/gui_desktop.c index 6de110d..e79c1e7 100644 --- a/gui/gui_desktop.c +++ b/gui/gui_desktop.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,9 @@ static bool g_clock_seconds = true; static int g_selected_icon = -1; static int g_hover_menu_item = -1; static uint64_t g_frames = 0; +static uint32_t g_fps = 0; // frames per second, sampled from ktime +static uint64_t g_fps_last_ms = 0; +static uint64_t g_fps_last_frames = 0; /* * Cached wallpaper. @@ -178,28 +182,50 @@ void gui_desktop_init(void) { // Wallpaper and desktop icons // =========================================================================== -/* Paint the wallpaper straight into the back buffer. */ +/* Paint the wallpaper straight into the back buffer. A three-stop vertical + * gradient gives the sky depth, a soft edge vignette frames it, and an optional + * fine grid adds texture without banding. Painted once and cached. */ static void desktop_paint_background(int sw, int wh) { - gui_gfx_draw_gradient_v(0, 0, sw, wh, 0xFF0B132B, 0xFF1C2541); + // Three-stop vertical gradient: deep space blue -> indigo -> a touch lighter. + int mid = wh * 55 / 100; + gui_gfx_draw_gradient_v(0, 0, sw, mid, 0xFF070B18, 0xFF141C38); + gui_gfx_draw_gradient_v(0, mid, sw, wh - mid, 0xFF141C38, 0xFF1D2A52); if (g_show_grid) { - // Blend rather than stamp a fixed colour: the gradient's lower half is - // lighter than any single grid tone, so a constant colour disappears - // there and only shows near the top of the screen. - for (int y = 0; y < wh; y += 40) { - gui_gfx_fill_rect_blend(0, y, sw, 1, GUI_COLOR_BLACK, 40); + // A finer, fainter grid than before reads as texture rather than lines. + for (int y = 0; y < wh; y += 48) { + gui_gfx_fill_rect_blend(0, y, sw, 1, 0xFF3B4E80, 22); } - for (int x = 0; x < sw; x += 40) { - gui_gfx_fill_rect_blend(x, 0, 1, wh, GUI_COLOR_BLACK, 40); + for (int x = 0; x < sw; x += 48) { + gui_gfx_fill_rect_blend(x, 0, 1, wh, 0xFF3B4E80, 22); } } - // Branding, offset to the right so it clears the icon column. - int bx = sw / 2 + 40; - int by = wh / 2 - 30; - gui_gfx_draw_string_16_shadow(bx - 60, by, "SUB-OS DESKTOP", 0xFF38BDF8, 0xFF0284C7); - gui_gfx_draw_string(bx - 76, by + 22, "Modular Monolithic Kernel v0.2.0-LTS", 0xFF64748B); - gui_gfx_draw_string(bx - 76, by + 36, "F5 cycle F6 cascade F7 tile F8 max", 0xFF475569); + // Soft vignette: concentric 1px frames darkening toward the edges. The + // alpha falls off with depth, so the corners recede smoothly. + int depth = (wh < sw ? wh : sw) / 6; + for (int i = 0; i < depth; i++) { + uint8_t a = (uint8_t)(((depth - i) * 42) / depth); + if (a == 0) continue; + gui_gfx_fill_rect_blend(i, i, sw - 2 * i, 1, GUI_COLOR_BLACK, a); // top + gui_gfx_fill_rect_blend(i, wh - 1 - i, sw - 2 * i, 1, GUI_COLOR_BLACK, a); // bottom + gui_gfx_fill_rect_blend(i, i, 1, wh - 2 * i, GUI_COLOR_BLACK, a); // left + gui_gfx_fill_rect_blend(sw - 1 - i, i, 1, wh - 2 * i, GUI_COLOR_BLACK, a); // right + } + + // Centred brand mark with a subtle glow (drawn as a shadowed 16px title). + const char* brand = "SUB-OS"; + int glyph_w = 16; // draw_string_16 advances ~16px per glyph + int bw = (int)strlen(brand) * glyph_w; + int bx = (sw - bw) / 2; + int by = wh / 2 - 40; + gui_gfx_draw_string_16_shadow(bx, by, brand, 0xFF7DD3FC, 0xFF0C4A6E); + const char* sub = "Modular Monolithic Kernel - v0.2.0-LTS"; + int sw2 = (int)strlen(sub) * 8; + gui_gfx_draw_string((sw - sw2) / 2, by + 24, sub, 0xFF64748B); + const char* hint = "F5 cycle F6 cascade F7 tile F8 maximize"; + int hw = (int)strlen(hint) * 8; + gui_gfx_draw_string((sw - hw) / 2, by + 40, hint, 0xFF3E4C6B); } void gui_desktop_invalidate_background(void) { @@ -314,7 +340,7 @@ void gui_desktop_render_taskbar(void) { // One button per window, in z-order int tab_x = 106; - int tray_x = sw - 200; + int tray_x = sw - 260; // widened tray: FPS pill + CPU trace + mem + clock for (int i = 0; i < gui_wm_get_window_count() && tab_x < tray_x - 106; i++) { gui_window_t* win = gui_wm_get_window_at_index(i); if (!win || !win->visible) continue; @@ -358,6 +384,14 @@ void gui_desktop_render_taskbar(void) { gui_gfx_draw_string(sw - 140, ty + 12, buf, GUI_COLOR_BLACK); (void)total; + // Live FPS pill, tinted by how smooth the compositor is running. + char fps[16]; + snprintf(fps, sizeof(fps), "%u FPS", g_fps); + uint32_t fps_col = (g_fps >= 45) ? GUI_THEME_SUCCESS + : (g_fps >= 20) ? GUI_THEME_WARNING + : GUI_THEME_DANGER; + gui_gfx_draw_string(sw - 205, ty + 12, fps, fps_col); + rtc_time_t now; rtc_get_time(&now); if (g_clock_seconds) { @@ -679,9 +713,12 @@ int gui_desktop_run(void) { fbcon_enable(false); gui_desktop_init(); - // Open a default session so the desktop is not empty on first boot. - launch_app(2); // System Monitor - launch_app(0); // Terminal + // Open a default session so the desktop is not empty on first boot. Placed + // and sized for the HD work area rather than the old 800x600 defaults. + int sw = gui_gfx_get_width(); + int wh = gui_desktop_workarea_height(); + gui_app_sysmon_launch(sw - 620, 60, 560, 360); + gui_app_terminal_launch(120, 90, sw - 820, wh - 200); // Discard anything the console left in the keyboard queue; a stale key // arriving on frame 0 would otherwise act as a desktop shortcut. @@ -764,6 +801,15 @@ int gui_desktop_run(void) { g_frames++; g_pushed_pixels += gui_gfx_last_present_pixels(); + // Sample the frame rate roughly twice a second from the monotonic clock. + uint64_t now_ms = ktime_ms(); + uint64_t dt = now_ms - g_fps_last_ms; + if (dt >= 500) { + g_fps = (uint32_t)(((g_frames - g_fps_last_frames) * 1000ULL) / dt); + g_fps_last_ms = now_ms; + g_fps_last_frames = g_frames; + } + if ((g_frames & 0xFF) == 0) { uint64_t full = (uint64_t)gui_gfx_get_width() * (uint64_t)gui_gfx_get_height(); uint64_t avg = g_pushed_pixels / 256; diff --git a/gui/gui_wm.c b/gui/gui_wm.c index 6b332c7..7f4a12b 100644 --- a/gui/gui_wm.c +++ b/gui/gui_wm.c @@ -616,15 +616,24 @@ void gui_wm_render(void) { gui_window_t* win = gui_wm_get_window(g_window_order[i]); if (!win || !win->visible || win->minimized) continue; - gui_gfx_draw_shadow(win->x, win->y, win->width, win->height, 6); + // A larger, softer drop shadow reads as real depth on the HD desktop. + gui_gfx_draw_shadow(win->x, win->y, win->width, win->height, + win->active ? 12 : 7); uint32_t border = win->active ? GUI_THEME_PRIMARY : GUI_THEME_BORDER; gui_gfx_draw_rect(win->x, win->y, win->width, win->height, border); - uint32_t top_bg = win->active ? 0xFF1E293B : 0xFF0F172A; - uint32_t bot_bg = win->active ? 0xFF0F172A : 0xFF0A0E1A; + // Brighter, more saturated titlebar when focused; muted when not. + uint32_t top_bg = win->active ? 0xFF24345A : 0xFF141C2E; + uint32_t bot_bg = win->active ? 0xFF141E38 : 0xFF0B1120; gui_gfx_draw_gradient_v(win->x + 1, win->y + 1, win->width - 2, GUI_TITLEBAR_HEIGHT - 1, top_bg, bot_bg); + + // Accent underline on the active window's titlebar; plain divider otherwise. + if (win->active) { + gui_gfx_fill_rect(win->x + 1, win->y + GUI_TITLEBAR_HEIGHT - 1, + win->width - 2, 2, GUI_THEME_PRIMARY); + } gui_gfx_draw_line(win->x, win->y + GUI_TITLEBAR_HEIGHT, win->x + win->width - 1, win->y + GUI_TITLEBAR_HEIGHT, border); diff --git a/include/kernel/ktime.h b/include/kernel/ktime.h new file mode 100644 index 0000000..a41cc29 --- /dev/null +++ b/include/kernel/ktime.h @@ -0,0 +1,17 @@ +#ifndef _KERNEL_KTIME_H +#define _KERNEL_KTIME_H + +// Unified monotonic high-resolution clock. Prefers the HPET (nanosecond grade) +// and falls back to the 100 Hz timer tick when no HPET is present. + +#include +#include + +void ktime_init(void); +uint64_t ktime_ns(void); // nanoseconds since ktime_init +uint64_t ktime_us(void); +uint64_t ktime_ms(void); +const char* ktime_source(void); // "HPET" or "PIT" +bool ktime_is_highres(void); // true when backed by the HPET + +#endif // _KERNEL_KTIME_H diff --git a/kernel/main.c b/kernel/main.c index 47d937c..5f0a1df 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -76,6 +76,7 @@ #include #include #include +#include #include #include #include @@ -135,6 +136,7 @@ static void subos_modular_core_boot(void) { nvram_init(); lpt_init(); #endif + ktime_init(); canvas_init(); pty_init(); virtio_rng_init(); @@ -330,7 +332,7 @@ void kernel_main(void* memory_map, uint64_t memory_map_count) { printk(ANSI_BRIGHT_CYAN " SUB-OS 64-Bit [%s] Modular Monolithic Kernel %s\n" ANSI_RESET, arch_get_name(), kernel_get_version()); printk(ANSI_BRIGHT_CYAN "=================================================================\n\n" ANSI_RESET); - init_early("root=/dev/sda console=tty1 init=/bin/lazybox video=1280x720 quiet"); + init_early("root=/dev/sda console=tty1 init=/bin/lazybox video=1920x1080 quiet"); rtc_init(); printk(KERN_INFO "[1/14] Initializing 64-Bit GDT, IDT, PIC & PIT Timer... "); diff --git a/kernel/time/ktime.c b/kernel/time/ktime.c new file mode 100644 index 0000000..1cb3028 --- /dev/null +++ b/kernel/time/ktime.c @@ -0,0 +1,39 @@ +// Unified monotonic high-resolution clock for SUB-OS. +// +// When the HPET is available it provides true nanosecond timing; otherwise the +// clock degrades gracefully to the 100 Hz kernel timer tick (10 ms resolution). +// Either way the epoch is the moment ktime_init() ran. + +#include +#include +#include +#include + +#define TICK_HZ 100ULL +#define NS_PER_TICK (1000000000ULL / TICK_HZ) // 10,000,000 ns at 100 Hz + +static bool g_use_hpet = false; +static uint64_t g_base_tick = 0; + +void ktime_init(void) { + g_use_hpet = hpet_available(); + g_base_tick = pit_get_ticks(); + printk(ANSI_BRIGHT_GREEN "KTIME: " ANSI_RESET + "Monotonic clock online (source: %s%s)\n", + g_use_hpet ? "HPET" : "PIT 100 Hz", + g_use_hpet ? ", nanosecond resolution" : ""); +} + +uint64_t ktime_ns(void) { + if (g_use_hpet) { + return hpet_get_ns(); + } + uint64_t ticks = pit_get_ticks() - g_base_tick; + return ticks * NS_PER_TICK; +} + +uint64_t ktime_us(void) { return ktime_ns() / 1000ULL; } +uint64_t ktime_ms(void) { return ktime_ns() / 1000000ULL; } + +const char* ktime_source(void) { return g_use_hpet ? "HPET" : "PIT"; } +bool ktime_is_highres(void) { return g_use_hpet; } diff --git a/userland/lazybox/lazybox.c b/userland/lazybox/lazybox.c index d9c5698..f2f95f6 100644 --- a/userland/lazybox/lazybox.c +++ b/userland/lazybox/lazybox.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -718,6 +719,19 @@ static int applet_iodev(int argc, char** argv) { return 0; } +static int applet_ktime(int argc, char** argv) { + (void)argc; (void)argv; + uint64_t ns = ktime_ns(); + printk(ANSI_BRIGHT_CYAN "Monotonic clock (ktime):\n" ANSI_RESET); + printk(" Source : %s%s\n", ktime_source(), + ktime_is_highres() ? " (nanosecond resolution)" : " (10 ms tick)"); + printk(" Uptime : %llu.%03llu s (%llu ns)\n", + (unsigned long long)(ns / 1000000000ULL), + (unsigned long long)((ns / 1000000ULL) % 1000ULL), + (unsigned long long)ns); + return 0; +} + static int applet_hpet(int argc, char** argv) { (void)argc; (void)argv; if (!hpet_available()) { @@ -2465,6 +2479,7 @@ static const lazybox_applet_t applets[] = { {"reg", applet_reg, "reg query|add|set|stats", "NT configuration registry", "Kernel"}, {"iodev", applet_iodev, "iodev", "NT I/O Manager devices & IRP","Kernel"}, {"hpet", applet_hpet, "hpet", "High Precision Event Timer", "Hardware"}, + {"ktime", applet_ktime, "ktime", "Monotonic high-res clock", "Kernel"}, {"nvram", applet_nvram, "nvram", "Dump CMOS NVRAM bytes", "Hardware"}, {"lpt", applet_lpt, "lpt [text]", "Send text to LPT1 parallel", "Hardware"}, {"rand", applet_rand, "rand [count]", "Generate random numbers", "Crypto"},