76 Commits
Author SHA1 Message Date
subhobhai943andClaude Opus 4.8 a0cd95cdc9 docs: add Contributing, Security, and Roadmap docs; link them from the README
Create docs/CONTRIBUTING.md (build/verify workflow, coding style, kernel
gotchas, commit & PR conventions), docs/SECURITY.md (supported versions,
private vulnerability reporting, documented caveats), and docs/ROADMAP.md
(shipped in v1.0.0 plus near/mid/long-term plans). Add a Documentation and
Contributing section to the README pointing at all four docs.

Note: used the conventional CONTRIBUTING.md / SECURITY.md filenames (in
docs/) so GitHub auto-detects them as community-health files.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-30 22:10:28 +05:30
subhobhai943andClaude Opus 4.8 bc4e19bb37 build: add Linux-style version/codename block to the Makefile
Mirror the Linux kernel Makefile header: VERSION / PATCHLEVEL / SUBLEVEL /
EXTRAVERSION / NAME at the top, where NAME carries the release codename
(Titan). Resolves to 1.0.0-stable, kept in sync with include/init/version.h.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-30 21:47:37 +05:30
subhobhai943andClaude Opus 4.8 8023338df2 release(v1.0.0): first stable SUB-OS -- logo in README, graduate 0.2.0-lts to 1.0.0-stable
Promote SUB-OS out of alpha/beta to its first stable milestone. Bump
version.h to 1.0.0 with the "1.0.0-stable" banner string across every
arch target (codename Titan). Drop the ASCII wordmark from the README in
favour of the official circular SUB-OS logo image, and update the version
and release badges plus the Official Release link to v1.0.0.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-30 21:17:47 +05:30
subhobhai943andClaude Opus 4.8 cf7bf59481 feat(brand): official SUB-OS logo -- add "OS" to the dragon mark
Take the circular SUB dragon logo and lock up an "OS" wordmark beneath
the "SUB" letters, in the dragon's accent blue (#08B3FA) with the same
oblique cut and black keyline, so the mark reads "SUB-OS" as one unit.
Store the artwork in assets/ as the canonical source and regenerate the
boot splash asset (init/logo_data.c) from it; the "OS" stays legible at
the 160x160 boot resolution. Point mklogo.py's example at the new source.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-30 21:04:00 +05:30
subhobhai943andClaude Opus 4.8 db962de248 feat(lib): baseline JPEG decoder -- the browser shows photos
Adds a from-scratch baseline JPEG decoder (ITU-T T.81, SOF0 sequential DCT),
so the Web app now displays the web's dominant photo format alongside PNG and
BMP. image_decode dispatches on the JPEG magic, so the browser needs no change
beyond that.

lib/jpeg.c handles grayscale and YCbCr, arbitrary chroma subsampling
(4:4:4/4:2:2/4:2:0), restart markers, and multiple DQT/DHT tables. Huffman
uses canonical tables (counts + symbols, like the inflate decoder) to avoid a
large fast-table; the transform is the standard fixed-point integer row/column
IDCT; colour is integer YCbCr->RGB. All heap-allocated, no floating point, so
it is safe in the freestanding kernel. Progressive (SOF2) and arithmetic-coded
JPEGs are rejected rather than mis-decoded.

Verified on the host against PIL-encoded references before going in the kernel,
the same way the crypto and PNG decoders were: grayscale decodes at mean
absolute error 0.9/channel, 4:4:4 at 1.7 (pure IDCT rounding vs libjpeg), and
4:2:0 at 4.3 (nearest-neighbour chroma upsampling softens colour edges -- a
known, visually-fine artifact). In QEMU the Web app fetched a 400x260 JPEG over
HTTP and rendered it correctly -- gradient, an antialiased red circle, a green
box, a yellow diagonal. Builds clean on x86_64, aarch64 and armv8i.

Limits: baseline only (no progressive), and chroma upsampling is
nearest-neighbour rather than the smoother interpolation libjpeg uses. The
browser still shows images by URL, not inline in a page.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 19:24:13 +05:30
subhobhai943andClaude Opus 4.8 fe33ddc79b feat(lib,gui): the browser shows images -- a PNG/BMP decoder, DEFLATE and all
The Web app could only render text. Now it decodes and displays images, so a
picture on the web actually shows up.

  - lib/inflate.c   DEFLATE / zlib decompressor (RFC 1950/1951): the algorithm
                    every PNG needs. Canonical Huffman, all three block types,
                    decodes into a caller buffer -- no allocation, integer only.
  - lib/image.c     PNG decoder (8-bit RGB/RGBA/grayscale/gray+alpha, and
                    1/2/4/8-bit grayscale & palette, non-interlaced; tRNS
                    transparency) plus BMP (24/32-bit). Parses chunks,
                    inflates IDAT, undoes the five scanline filters, and expands
                    to a flat 0xAARRGGBB buffer ready for gui_gfx_blit. A pixel
                    cap guards the heap.
  - Web app: if a response body sniffs as an image it is decoded once and drawn
    instead of text -- scaled to fit the panel width, vertically scrollable,
    alpha-composited over the background. The per-fetch cap grew to 128 KB so
    real images and longer pages fit.

Both decoders were verified on the host against generated references before
going in the kernel: inflate round-trips six zlib/raw streams (empty, back-
references, dynamic Huffman, stored blocks, 20 KB random, mixed); the image
decoder reproduces PIL-made PNGs of every supported colour type -- grayscale,
RGB, RGBA, 4-bit palette, gray+alpha -- and a BMP, with exact pixel matches.
In QEMU the Web app fetched a 360x240 PNG over HTTP and rendered it perfectly:
gradient, an antialiased red circle, a green box, a yellow diagonal, shown as
"image 360x240". Builds clean on x86_64, aarch64 and armv8i.

Still image-by-URL, not inline <img> within a page (that needs sub-resource
fetching and layout); and no JPEG/GIF (different codecs). PNG and BMP cover a
large share of the web's images.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 18:53:38 +05:30
subhobhai943andClaude Opus 4.8 8a55ac345b feat(crypto,net): TLS 1.3 client -- SUB-OS can now browse HTTPS sites
The browser could only reach plain HTTP, which is almost none of the real
web. This adds a from-scratch TLS 1.3 client so it can fetch https:// pages,
including real sites on the open internet.

Crypto primitives (all integer-only, freestanding, verified on the host
against their RFC test vectors before going in the kernel):
  - crypto/x25519.c            X25519 ECDH (RFC 7748), compact TweetNaCl field
                               arithmetic.
  - crypto/chacha20poly1305.c  ChaCha20 + Poly1305 AEAD (RFC 8439).
  - crypto/hkdf.c              HMAC-SHA256, HKDF (RFC 2104/5869) and the TLS 1.3
                               HKDF-Expand-Label / Derive-Secret helpers, on top
                               of the existing SHA-256.
A host harness checked every one against its published vectors (X25519 s6.1,
ChaCha20 s2.4.2, AEAD s2.8.2 incl. tamper rejection, HKDF TC1) -- all pass.

TLS 1.3 client (net/tls.c), single suite TLS_CHACHA20_POLY1305_SHA256 over
X25519: ClientHello (with SNI and ALPN http/1.1) -> ServerHello -> the RFC 8446
key schedule over a running transcript hash -> decrypt the server flight ->
send Finished -> switch to application keys. A small record layer handles the
plaintext-then-AEAD split and the middlebox ChangeCipherSpec. All large buffers
live in the heap-allocated connection, never on the 16 KB worker stack.

http_client routes https:// through TLS instead of raw TCP, so the async
worker, webget, netcheck and the GUI Web app all get HTTPS for free; the
redirect follower now follows http->https upgrades. The Web app's default page
is now https://example.com/.

Verified end to end on x86_64: the handshake interoperates with openssl
s_server (TLS 1.3, ChaCha20) -- GET returned HTTP 200 with the decrypted body
-- and, over QEMU's NAT, `webget https://example.com/` and the GUI Web app both
loaded the real Example Domain page served by Cloudflare over TLS 1.3 (HTTP
200, 828 bytes). The server only accepts our Finished if the transcript hash
and key schedule match byte-for-byte, so a 200 is proof the whole stack is
correct. Builds clean on x86_64, aarch64 and armv8i.

SECURITY CAVEAT, stated plainly: the server certificate is NOT verified. The
channel is encrypted and interoperates, but a man-in-the-middle could
impersonate the peer. Real certificate-chain validation (X.509 parsing,
signature checks, a CA bundle, hostname matching) is a large separate piece and
is not built here. This is fine for a hobby OS over a trusted local NAT; it is
not fit to protect real secrets. Also: TLS 1.3 only, one cipher suite, and
still no JavaScript/media -- so JS-heavy or app-style sites (YouTube) remain
out of reach; this makes static/text HTTPS pages readable.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 18:08:51 +05:30
subhobhai943andClaude Opus 4.8 9f90361265 feat(gui): make the Web app a real browser -- clickable links, history, relative URLs
The Web app fetched and rendered pages but could not browse between them.
Now it does. In Reader view, anchors are parsed as the markup is stripped and
recorded as links spanning the text they enclose; those spans render in the
accent colour with an underline, and a click on one follows it.

  - Link capture: html_to_text now tracks <a href> open/close, storing each
    link's [start,end) span in the processed text plus its href in a per-page
    string pool. Rendering overlays link spans in accent + underline; a body
    click maps pixel -> line -> text offset -> containing link.
  - Relative resolution: a link's href is resolved against the page's own
    address (captured from the fetch's final host/port/path). Root-relative
    (/path), document-relative (page.html) and absolute URLs all work; the
    default port is omitted from the rebuilt URL. mailto:/javascript:/#frag
    are treated as non-navigable.
  - History: a back stack (depth 16) records each address before navigating; a
    Back button, lit only when there is somewhere to return to, pops it.
  - https:// links resolve through and are refused by the fetch layer with the
    existing clear message rather than being silently dropped.

Verified on x86_64 with real synthetic clicks against a small multi-page HTTP
site: the home page rendered its two links highlighted; clicking "Go to Page
Two" navigated (URL bar updated to the resolved http://host/page2.html, host
logged GET /page2.html) and Page Two rendered with its own links; the Back
button returned to the home page (host logged GET /). Builds clean on x86_64,
aarch64 and armv8i.

Known limit: no TLS, so https:// pages are reported not shown -- plain HTTP
browsing only.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 12:17:59 +05:30
subhobhai943andClaude Opus 4.8 f6e4511047 feat(net,gui): real internet access -- redirects, HTTPS guard, netcheck, and a live ONLINE indicator
The stack could already reach the real internet once the UDP-checksum and
scheduler fixes landed; this makes it work on real-world URLs and gives the
desktop a first-class "is the internet up?" answer.

HTTP client
  - Follows 3xx redirects (Location header, absolute or relative) up to five
    hops, so a bare host that 301s to a path or a canonical host resolves to
    actual content. Verified: `webget google.com/` follows the 301 to
    www.google.com and returns its HTTP 200 homepage.
  - Refuses https:// targets -- at the initial URL and on redirect -- with a
    clear message instead of speaking plaintext to port 443 and timing out.
  - Split the URL parser out so both the initial request and redirect targets
    go through the same path; added a case-insensitive header finder.

netcheck (CLI)
  One command that walks the whole chain -- link, address, gateway ping, DNS,
  and a real HTTP fetch -- and reports each PASS/FAIL, ending in ONLINE or
  DEGRADED. The one-shot answer to "is the internet working?".

Network Monitor (GUI)
  A live Internet: ONLINE/OFFLINE badge with the resolved IP, HTTP status and
  latency. It auto-probes once on open and re-checks on the Check button, all
  through the async HTTP worker so the window never blocks.

Verified against the real internet through QEMU's NAT: DNS resolved
example.com to a live Cloudflare address; `netcheck` passed all five layers
(Internet: ONLINE); the GUI badge showed "ONLINE 104.20.23.154 HTTP 200 240
ms"; redirect following and the HTTPS refusal both behaved. Builds clean on
x86_64, aarch64 and armv8i.

Known limit: no TLS, so HTTPS URLs are reported rather than fetched -- plain
HTTP only.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 02:44:53 +05:30
subhobhai943andClaude Opus 4.8 dd4922767a feat(net,gui,sched): async HTTP client + Web browser app, and the scheduler fix that makes background work possible
The visible goal was a Web app: type a URL, see the page, fetched over the
TCP stack SUB-OS built. Getting there surfaced -- and forced fixes to --
two deeper problems.

Async HTTP client (net/http_client.c)
  tcp_connect/recv all block, so fetching from the GUI would freeze the
  compositor. Fetches now run on one long-lived kernel worker thread that
  services a single-slot mailbox; the requester posts a job and polls its
  state. Ownership follows the state, and the two hand-off points (worker
  completion, requester release) run under interrupts-off, so a window can
  close mid-fetch with neither leak nor use-after-free. do_fetch returns
  its terminal state rather than publishing it, closing a window where the
  requester could free the job before finalize read it.

Cooperative scheduling for the idle thread (the core fix)
  The login prompt, the shell and the GUI compositor all run on the pid-0
  idle thread, which the scheduler by design never preempts (sched_tick
  skips pid 0) and never requeues on a yield (weave_on clear). So it could
  neither be preempted to let the worker run, nor cleanly yield -- the
  worker was simply starved, and an early attempt to yield from it froze
  the shell outright. The idle thread now joins the weave rotation once
  hardware interrupts are live, and the network wait loops (tcp, dns, ping,
  the worker's idle) yield through a new net_wait() instead of halting the
  CPU. The compositor spends its frame-budget slack yielding too. Result:
  a background fetch progresses while the desktop holds 54 FPS.

Web app (gui/gui_app_web.c)
  A text browser: URL bar, Go, and a Raw/Reader toggle. Raw shows the
  response verbatim; Reader strips markup (dropping <script>/<style>
  bodies), decodes the common entities, and word-wraps the body. The line
  index is cached against content/view/width so scrolling and idle
  repaints are free. It only ever polls the worker, so it never stalls.

TCP throughput (the limit flagged last time)
  The 32-slot table with a 2 s TIME_WAIT linger capped sustained serving
  at ~16 req/s. The table is now 64 slots, a connection this end closed
  after serving a request lingers 250 ms rather than 2 s, and expired
  TIME_WAIT entries are reaped from the count path so a quiet table still
  drains. netstat/netmon gained served-since-boot and peak-slots counters.

Also: a `webget` CLI (the async client from a shell), listen/accept
introspection already present, and a globe icon for the app.

Verified on x86_64: `webget` fetched an 812-byte page (HTTP 200, 10 ms) on
the worker thread; the GUI Web app rendered the same page in Reader mode
while the desktop held 54 FPS; an 80-request burst through the 64-slot
table returned 80/80 (was 39/40 before) and netstat reported "80 served,
peak 33 of 64 slots" -- a peak that would have overflowed the old table.
Builds clean on x86_64, aarch64 and armv8i.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 02:21:48 +05:30
subhobhai943andClaude Opus 4.8 eb92502deb feat(net,gui): TCP listen/accept, and a Network Monitor to watch it
Kernel: the passive-open half of TCP. tcp_listen claims a port, inbound
SYNs to it are completed by the receive path, and the finished
connections queue in a backlog until tcp_accept collects them. Accepted
connections run the same stream state machine as active opens, so
tcp_send/tcp_recv/tcp_close work on them unchanged. sys_listen and
sys_accept expose this through the BSD socket API, with sys_accept
returning a fresh descriptor and leaving the listener available for the
next caller. The built-in sshd and httpd predate this and are still
answered inline from the receive path; the role they use is now named
TCP_ROLE_SERVICE to keep that distinction explicit.

GUI: a Network Monitor app. Interface configuration and link state, a
rolling dual-line chart of receive and transmit rates sampled from the
NIC counters on a fixed 2 Hz cadence (so it reads in packets per half
second no matter how often the compositor repaints), byte and packet
totals, and the TCP engine's connection table with each connection's real
state and direction, scrollable when it outgrows the window.

Two bugs the monitor made visible, both fixed:

  - Served HTTP connections were never closed. The response advertises
    "Connection: close" but the engine left the connection ESTABLISHED
    forever, so the 32-entry table filled up and the server stopped
    answering after 32 requests. The service path now sends its FIN after
    the response and lingers in TIME_WAIT until the reaper takes it. 40
    sequential requests through a 32-slot table now yield 39 successes
    where the 33rd onwards previously failed permanently.

  - tcp_close gave up in FIN_WAIT2 after 500 ms and freed the slot, so a
    peer whose own FIN arrived later got an RST instead of an ACK. It now
    falls into TIME_WAIT instead, and TIME_WAIT acknowledges a first FIN
    rather than only a retransmitted one. Expired TIME_WAIT entries are
    also reaped from tcp_get_connections_count, so a table that sees no
    further connections still empties out.

Verified on x86_64 against real peers, checked packet by packet in a
capture: a host client connected to `tcpserve 9000` in the guest and got
its reply -- SYN/SYN-ACK/ACK with a random ISN, 30-byte request, 43-byte
reply, and a clean four-way close whose final ACK is correct even when
the peer's FIN arrives 1.2 s late. Zero bad checksums throughout. The
monitor was screenshotted under live load showing connections moving
through TIME_WAIT and being reclaimed. Builds clean on x86_64, aarch64
and armv8i.

Known limit: 32 connection slots with a 2 s TIME_WAIT linger means
sustained rates much above ~16 requests/second can transiently exhaust
the table; it recovers on its own as entries expire.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 01:27:17 +05:30
subhobhai943andClaude Opus 4.8 65fc5535c8 feat(net): active-open TCP, and fix two bugs that broke TCP on the wire
Adds the client half of TCP -- tcp_connect/tcp_send/tcp_recv/tcp_close --
and wires SOCK_STREAM sockets to it, so the kernel can originate an
outbound stream rather than only answer inbound ones. Inbound segments
are now matched against the connection table before being offered to the
listeners; previously anything not addressed to a listening port was
answered with RST, which would have rejected the SYN-ACK completing our
own handshake.

Transmission is stop-and-wait: one segment outstanding, retransmitted
until acknowledged. Every blocking call is bounded against the timer tick
and halts the CPU between checks, so a silent peer costs a timeout rather
than a hang. Connections linger in TIME_WAIT so a late duplicate cannot
be mistaken for traffic on a new incarnation.

Verifying it against a real peer uncovered two pre-existing bugs that had
made TCP unusable in both directions. Neither was visible from inside the
guest, which is why the servers had looked fine:

1. The TCP and UDP checksums were computed by reading the header a
   uint16_t at a time, immediately after the caller stored zero into that
   same header's checksum field. Nothing stops the compiler reordering
   those word reads around that store, and at -O2 it did: the stale
   checksum field was folded into its own result. Every TCP segment the
   kernel had ever sent carried a wrong checksum and was silently dropped
   by the peer -- no client could complete a handshake with sshd or
   httpd, and it is the likely reason DHCP and DNS replies never arrived.
   The checksum is now summed a byte at a time through unsigned char
   pointers, which may alias any object and so cannot be reordered, and
   which also drops the alignment assumption that reading a packed header
   as uint16_t made -- a fault rather than a slowdown on ARM. The helper
   is shared by IP, TCP and UDP instead of being open-coded four times.

2. net_receive derived the datagram length from the Ethernet frame rather
   than from the IP header's total_length, so the padding on any frame
   shorter than 60 bytes was delivered as protocol payload. A bare ACK or
   FIN appeared to carry six bytes, which pushed the acknowledgement
   number out of step with the peer and turned every close into a FIN
   retransmission storm answered with RST.

Also adds `tcpconnect` and `httpget` applets, and gives netstat the real
per-connection state and direction from the engine's table.

Verified on x86_64 against a real TCP stack, checked packet by packet in
a capture: `httpget` fetched a 349-byte page from a server on the host --
handshake, 81-byte request, response, orderly four-way close, every
sequence number exact, zero bad checksums, no retransmissions. In the
other direction curl on the host now fetches a live page from the guest's
httpd, which had never worked. Builds clean on x86_64, aarch64 and armv8i.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-26 20:34:26 +05:30
subhobhai943andClaude Opus 4.8 c9dfa6a1aa feat(gui): Sorry -- an apology app with a live parametric heart
A new desktop app whose whole point is to say sorry, and to look like
saying it took effort. Nothing in it is a static bitmap.

The heart is the classic parametric heart curve, x = 16sin^3(t) and
y = 13cos(t) - 5cos(2t) - 2cos(3t) - cos(4t), precomputed as a 96-point
integer polygon normalised to a half-width of 1000 and scanline-filled at
whatever size the beat currently has it at. Even-odd crossing counting is
what keeps the notch between the lobes open. The beat is a two-thump
cardiac envelope -- a strong systolic beat and a softer echo -- and the
fill carries a vertical gradient, a soft specular highlight on the upper
lobe, and a six-layer glow. The kernel is built -mno-sse with no floating
point, so every curve, gradient, easing and physics step is integer-only
and needs no trigonometry at runtime.

Around it: three apology letters that type themselves out a character at
a time, and a slow drift of small hearts using the same fill routine.
Answering "I forgive you" brightens the heart and bursts the drift
outward; answering "Not yet" dims it, slows the beat, and starts an
honest count of how long it has been waiting. Neither answer is an exit.

Supporting changes, all of them general rather than app-specific:

  - gui_desktop_request_animation_frame(), so an app can opt into
    full-rate recompositing instead of the ~10 Hz live-content
    heartbeat. It is renewed per paint, so it lapses by itself when the
    app stops animating or its window closes.
  - gui_button_styled() and gui_hitzone() in SUB-WT, letting an app with
    its own palette keep the toolkit's click handling. gui_button_colored
    is now a thin wrapper over the former.
  - A heart glyph in the icon set.

The glow layers skip whatever the heart body is about to cover, which
cuts their blended-pixel cost by roughly 3.5x and pays for the extra
layers that turn contour rings into a smooth halo.

Verified in QEMU on x86_64: all three states driven by real clicks --
hover, forgiveness (brighter heart, burst, live typewriter) and waiting
(dimmed heart, slower beat, running counter). Builds clean on x86_64,
aarch64 and armv8i.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-26 18:21:22 +05:30
subhobhai943andClaude Opus 4.8 5bd18c4733 feat(cpp,gui): C++ Analytics Engine + live GUI Analytics app
Kernel (C++17 freestanding):
  - New Analytics Engine (kernel/cpp/cpp_analytics.cpp): templated rolling
    time-series (TimeSeries<N>) sampling four live channels -- CPU load %,
    memory %, kernel-heap %, network pkt/interval -- with windowed statistics
    (min/max/mean/stddev) computed in pure integer math (kernel is -mno-sse, so
    no FP; stddev uses a Newton integer isqrt). Demonstrates virtual dispatch
    via an Observer pattern (IAnalyticsObserver + a ThresholdAlarmObserver that
    counts breaches) and RAII singletons.
  - Extended cpp_containers.hpp with Optional<T>, Pair<A,B>, and the kmin/kmax/
    kclamp/kswap algorithms.
  - Full C fallback (cpp_fallback.c) so the engine is functionally identical on
    aarch64/armv8i, which don't build the .cpp path.
  - Wired cpp_analytics_init() into boot [8/14]; seeds a few samples so graphs
    are populated immediately.

GUI:
  - New "Analytics" desktop app (gui/gui_apps.c): a 2x2 grid of live line-charts
    backed by the C++ engine, each with gridlines, a soft baseline fill, and
    min/avg/max(/stddev) overlays; click a panel to focus one channel full-size,
    click again to return. Throttled to ~2 Hz sampling off the compositor's
    repaint cadence. Registered on the desktop and in the start menu.

Userland: new `cppstat [samples]` applet dumps the engine's per-channel stats
and Observer breach counters.

Verified in QEMU (x86_64): boots clean on x86_64/aarch64/armv8i; `cppstat`
prints live stats (CPU 6%, Mem 7%, Heap 22%, computed min/avg/max/stddev,
Observer breaches); the GUI Analytics window renders all four graphs with the
Kernel Heap curve visibly climbing 22->55% as windows open, compositor steady
at ~53 FPS.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-26 10:42:48 +05:30
subhobhai943andClaude Opus 4.8 cd840d5799 feat(net): real UDP transport, BSD datagram sockets, DNS resolver, DHCP client
Turn the networking stubs into a working transport/application stack on top of
the existing L2/L3 (Ethernet/ARP/IPv4/ICMP) core.

UDP (net/ipv4/udp.c)
  - Port-demux table with bound receive handlers, pseudo-header checksum,
    ephemeral-port allocation, limited-broadcast send (0.0.0.0 -> 255.255.255.255
    for DHCP before the interface has an address), and rx/tx/drop counters.
  - Wired into the IPv4 receive path (IP_PROTO_UDP dispatch in net/core/net.c).

BSD sockets (net/core/socket.c)
  - Real SOCK_DGRAM sockets backed by UDP: bind installs a UDP handler that fills
    a per-socket receive ring; send/sendto and recv/recvfrom move datagrams;
    ephemeral auto-bind, connect() memorises the peer. Socket table + I/O stats
    exposed for introspection.

DNS resolver (net/ipv4/dns.c)
  - Builds and parses real A-record queries over UDP/53 (label encoding,
    compression-pointer aware answer walk), cache-first with TTL, static-hosts
    and dotted-quad fast paths, and a bounded timeout that degrades to fallback
    instead of hanging.

DHCP client (net/ipv4/dhcp.c)
  - Full DISCOVER/OFFER/REQUEST/ACK handshake over UDP broadcast, option parsing
    (mask/router/dns/lease/server-id), applies the lease to the primary
    interface, bounded timeouts keep the static config on no response.

Fix (net/ipv4/dns_cache.c): the cache stored IPs as 0xAABBCCDD numeric while the
rest of the stack uses network-byte-order-in-memory (ip_parse/ip_to_str), so a
resolved address came back byte-swapped (nslookup github.com -> 100.121.82.140).
Seeds and dump now use ip_parse/ip_to_str; verified 140.82.121.4.

Userland (lazybox): live ifconfig (real interface + NIC counters, loopback),
netstat with live UDP bindings and transport stats, new `sockstat`, richer
nslookup. Verified in QEMU: boot clean on x86_64/aarch64/armv8i, login as SUB,
ping/nslookup/dhclient/netstat/sockstat all run; DHCP/DNS wire replies are
blocked by the sandbox's slirp but time out cleanly and fall back as designed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-26 10:20:58 +05:30
subhobhai943andClaude Opus 4.8 2c2e2d536e perf(gui): scene-dirty compositor + cursor overlay + 60 FPS pacer + animations
Rework the compositor so it stops repainting the whole screen every frame,
tripling the frame rate (17 -> 54 FPS at HD) and adding smooth animation.

- Scene-dirty skipping (gui/gui_desktop.c): the full scene is recomposited only
  when something other than the bare pointer changed -- input, an open animation,
  or a ~10 Hz heartbeat for live content (clock, graphs). Idle and pointer-only
  frames skip the recomposite entirely.
- Cursor save-under (gui/gui_cursor.c): the pointer is an overlay that stashes
  and restores the pixels beneath it, so moving the mouse never forces a full
  repaint -- pointer motion runs at the frame cap.
- 60 FPS frame pacer: each iteration is timed against the monotonic clock and
  padded to a 16.666 ms budget with the new ktime_delay_us() (HPET-precise
  busy-wait), giving consistent pacing instead of a free-running spin.
- Window open animation (gui/gui_wm.c): new windows grow from 60% to full size
  with a cubic ease-out over ~160 ms; gui_wm_update() drives it and reports
  while any window is still animating so the compositor keeps redrawing.
- Default display mode set to 1280x720 (HD) -- the sweet spot where the
  software compositor sustains a near-60 FPS cursor with headroom for effects.

Kernel: kernel/time/ktime.c gains ktime_delay_us(), a precise HPET-based delay
(bounded PIT fallback) used by the pacer.

Verified live (screenshotted): boots to 1280x720, logs in, tray reads ~54 FPS
(green) while the pointer moves, no cursor ghosting, windows render cleanly.
Clean build on x86_64/aarch64/armv8i; 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 23:05:44 +05:30
subhobhai943andClaude Opus 4.8 4ecf119377 feat(gui,kernel): 1080p HD desktop, polished chrome, ktime clock + live FPS
GUI polish / HD:
- Default display mode raised to 1920x1080 (video=1920x1080); make run/vnc use
  vgamem_mb=64 and -m 512M. The growable heap absorbs the larger backbuffer,
  shadow and wallpaper (observed: one 16 MB growth to a 48 MB heap).
- Wallpaper: three-stop vertical gradient, a soft concentric-frame edge vignette
  for depth, a finer faint grid, and a centred brand mark.
- Window chrome: brighter/saturated titlebar gradient when focused, an accent
  underline on the active window, and larger/softer drop shadows.
- Default session windows are placed and sized for the HD work area.
- Live FPS pill in the taskbar tray (green/amber/red by rate); tray widened so
  FPS + CPU trace + memory badge + clock no longer overlap.

Kernel:
- kernel/time/ktime.c + include/kernel/ktime.h (new): a unified monotonic
  high-resolution clock that prefers the HPET (nanosecond) and falls back to the
  100 Hz PIT tick. Wired ktime_init() into boot; drives the compositor's FPS
  sampling. New `ktime` LazyBox applet reports the source and uptime.

Verified live (screenshotted): boots to 1920x1080, KTIME reports
"source: HPET, nanosecond resolution", the polished desktop renders with the
active-window accent and vignette, and the tray shows a live FPS reading.
Clean build on x86_64/aarch64/armv8i; 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 22:40:35 +05:30
subhobhai943andClaude Opus 4.8 ef6714a004 fix(gui): make the Settings app actually apply changes
The Settings window rendered fine but its controls only mutated the app's local
settings_data_t -- nothing was ever wired to the running system, so toggling
options did nothing ("not working").

- Appearance: "Show wallpaper grid overlay" now calls gui_desktop_set_grid()
  live; the desktop grid shows/hides immediately.
- Input: "Show seconds in taskbar clock" now calls gui_desktop_set_clock_seconds();
  the taskbar clock switches between HH:MM and HH:MM:SS.
- Power: the CPUFreq governor radios now call cpufreq_set_governor() (mapping the
  UI order to the enum), and Apply/Defaults push all preferences to the system.
- The app seeds its controls from live state on open (grid/clock/governor), so it
  reflects reality instead of hard-coded defaults.

Also add public gui_desktop_set_grid/get_grid and set/get_clock_seconds, and
give Settings a dedicated event handler that frees its state on close (the
shared app_forward_event leaked it).

Verified: Settings renders and its controls are wired to real effects; clean
build on x86_64/aarch64/armv8i; boots to the login gate with 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 21:42:19 +05:30
subhobhai943andClaude Opus 4.8 0377726615 feat(drivers): HPET high-res timer, LPT parallel port, CMOS NVRAM
Add three real platform device drivers (x86 hardware, ARM-stubbed):

- drivers/timer/hpet.c + include/drivers/hpet.h: High Precision Event Timer.
  Maps the HPET block at the standard 0xFED00000, reads the femtosecond period
  from the capability register, enables the main counter and exposes a
  monotonic nanosecond time source (hpet_get_ns) far finer than the 100 Hz PIT.
- drivers/char/lpt.c + include/drivers/lpt.h: IBM PC parallel port (LPT1) with
  the Centronics data/strobe handshake over ports 0x378/0x379/0x37A.
- drivers/char/nvram.c + include/drivers/nvram.h: MC146818 CMOS NVRAM read/write
  over 0x70/0x71, preserving the NMI-disable bit, with checksum and hex dump.

Wired all three into the [5/14] device-enumeration boot step and added LazyBox
applets `hpet`, `lpt`, and `nvram`.

Verified live under QEMU (screenshotted): HPET reports 100 MHz / 3 comparators /
10 ns period and its counter advances ~1.4 s of real time between two `hpet`
runs; NVRAM dumps 128 bytes with a computed checksum; LPT1 detected at 0x378.
Clean build on x86_64/aarch64/armv8i; 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 21:04:03 +05:30
subhobhai943andClaude Opus 4.8 ea937a699c feat(kernel,gui): NT I/O Manager (IRP-based drivers) + Registry Editor
Kernel (NT I/O subsystem):
- kernel/nt/io.c + include/kernel/nt/io.h (new): DRIVER_OBJECTs with a
  MajorFunction dispatch table, DEVICE_OBJECTs that can be stacked (attached),
  and IRPs pushed down the stack via io_call_driver / completed with
  io_complete_request. Ships three demo drivers: \Driver\Null (write sink,
  EOF reads), \Driver\Zero (zero-filled reads), and \Driver\Monitor -- a filter
  attached above \Device\Null that counts and forwards each IRP down the stack.
  Wired into the [12/14] boot step.
- LazyBox `iodev` lists drivers/devices with the stack and IRP counters, then
  runs a live demo: a WRITE IRP through the filtered Null stack and a READ from
  the Zero device.

GUI:
- gui/gui_apps.c: "Registry Editor" -- navigate the NT \Registry hive tree,
  click subkeys to descend and ".." to go up, with a values pane showing each
  value's name/type/data. Registered in the app table (start menu).

Verified live in the GUI terminal (screenshotted): 3 drivers / 3 devices, the
NullMonitor->Null device stack, a 16-byte WRITE sunk through the filter
(status 0x0), and an 8-byte zeroed READ from \Device\Zero. Clean build on
x86_64/aarch64/armv8i; 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 20:51:14 +05:30
subhobhai943andClaude Opus 4.8 526ad8c741 feat(kernel,gui): NT Configuration Registry + GUI Object Browser
Kernel (NT Configuration Manager):
- kernel/nt/reg.c + include/kernel/nt/reg.h (new): a hive/key/value registry
  rooted at "\Registry", with typed values (REG_SZ/DWORD/QWORD/BINARY/MULTI_SZ,
  type codes matching Win32). reg_create_key/reg_open_key walk backslash paths;
  setters/getters, enumeration, and value->string rendering. reg_init() seeds
  \Registry\Machine (Hardware, System\CurrentControlSet\Control, Software\SUB-OS)
  and \Registry\User\.DEFAULT. Wired into the [12/14] boot step.
- LazyBox `reg query|add|set|stats` explores and edits the registry.

GUI:
- gui/gui_apps.c: "Object Browser" -- a WinObj-style live tree view of the NT
  object namespace (\), flattening directories/events/semaphores/mutants with
  indentation, per-type colouring, ref/handle counts, live object/handle stats,
  and click-to-scroll. Registered in the app table (start menu).

Verified live in the GUI terminal (screenshotted): registry reports 13 keys /
11 values; `reg query \Registry\Machine\Software\SUB-OS` lists Version,
BuildArch (REG_SZ), GrowableHeap/ObjectManager (REG_DWORD) and InstallCookie
(REG_QWORD) with correct types. Clean build on x86_64/aarch64/armv8i; 0 panics.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 20:41:45 +05:30
subhobhai943andClaude Opus 4.8 cb7506f027 feat(kernel): NT-inspired Object Manager, dispatcher and IRQL
Take inspiration from the Windows NT Executive and add its most iconic
subsystem to SUB-OS: a typed, reference-counted object namespace.

- kernel/nt/ob.c + include/kernel/nt/ob.h (new):
  * Object Manager (Ob): typed objects (Directory/Event/Semaphore/Mutant) in a
    hierarchical named namespace rooted at "\", with NT-style deletion once
    handle_count and ref_count both reach zero (unless permanent).
  * Handle table (Io/Ob): NT-shaped non-zero 4-aligned HANDLEs into a 256-slot
    system table; open/reference/close with proper refcount transfer.
  * Dispatcher (Ke): set/reset/test events (auto- and manual-reset) and
    release/wait semaphores, poll-style.
  * Software IRQL: PASSIVE/APC/DISPATCH/HIGH with raise/lower semantics.
  * NTSTATUS result codes and NT_SUCCESS().
  * ob_init() seeds the standard namespace (\BaseNamedObjects, \Device,
    \ObjectTypes, \KernelObjects) plus a boot event and an I/O semaphore.
- Wired ob_init() into the [12/14] boot step; added kernel/nt/ob.c to CORE_SRCS.
- LazyBox: `objdir [\path]` walks the object namespace tree (WinObj-style);
  `ntobj` prints Ob statistics and runs a live dispatcher/handle/IRQL demo.

Verified live in the GUI terminal (screenshotted): 7 seeded objects
(5 directories, 1 event, 1 semaphore), auto-reset event signalling, handle
close reclaiming the object, and PASSIVE->DISPATCH->PASSIVE IRQL transitions.
Clean build on x86_64/aarch64/armv8i; 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 20:23:07 +05:30
subhobhai943andClaude Opus 4.8 c414c20b44 feat(rust,gui): RLE compression codec + Rust Lab desktop app
Rust kernel expansion:
- rust/src/compress/mod.rs (new): memory-safe PackBits-style run-length
  encoding codec (rle_compress / rle_decompress) with C-ABI shims, registered
  in lib.rs. C fallbacks added to rust_fallback.c so aarch64/armv8i match.
- include/kernel/rust.h: FFI declarations for the codec.
- LazyBox: new `rle <text>` applet that compresses, decompresses, prints the
  ratio and verifies the round-trip is lossless.

GUI expansion:
- gui/gui_apps.c: "Rust Lab" app -- a live dashboard for the Rust subsystem.
  Shows CRC-32C/Adler-32 of a sample, measured ChaCha20/SHA3-256/AES-128
  throughput as scaled bars plus a composite score (benchmark run once at
  launch and cached), and a live ChaCha20 CSPRNG byte stream refreshed
  periodically. Per-window state freed on close.
- Registered "Rust Lab" (info icon) in the desktop app table and on the
  desktop; desktop icon indices updated for the new entry.

Verified: clean build on x86_64/aarch64/armv8i; boots through the SUB login to
the desktop with the new Rust Lab icon rendering (screenshotted); checksum
self-test prints, 0 panics / 0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 20:06:13 +05:30
subhobhai943andClaude Opus 4.8 e93b994469 feat(rust,gui): Rust checksum engine + Game of Life desktop app
Rust kernel expansion:
- rust/src/checksum/mod.rs (new): memory-safe CRC-32C (Castagnoli), Adler-32,
  and FNV-1a-64 with thin C-ABI shims. Registered in lib.rs with a boot-time
  self-test (crc32c("SUB-OS")=0x61e96ce9, verified against a reference impl).
- include/kernel/rust.h: FFI declarations; rust/rust_fallback.c: matching C
  fallbacks so aarch64/armv8i (no native rustc target) get identical results.
- LazyBox: new `cksum <text>` applet printing all three via the Rust engine.

GUI expansion:
- gui/gui_apps.c: Conway's Game of Life -- an interactive B3/S23 cellular
  automaton with Play/Pause/Step/Random/Clear toolbar, click-to-toggle cells,
  a live generation/population readout, and a board that scales with the
  window. The Random button seeds from the Rust ChaCha20 CSPRNG. Toolbar and
  cell input are gated on the MOUSE_DOWN edge so they do not re-fire while held.
- Registered "Life" (flask icon) in the desktop app table and on the desktop.

Verified: clean build on x86_64/aarch64/armv8i; boots through the SUB login to
the desktop (screenshotted) with the checksum self-test and 0 panics/0 OOM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 19:55:02 +05:30
subhobhai943andClaude Opus 4.8 c982d6b538 feat(auth): Boot-time login prompt; fix(configure): arch selection via dialog
Login:
- Add console_login() (security/auth.c): an interactive boot-time username /
  password gate rendered on the framebuffer console before any session starts.
  It blocks until valid credentials are entered, masks the password, scrubs it
  from the stack, and sets the current user. Default account is SUB / SUB, shown
  as a hint. Wired into the boot handoff in kernel/main.c ahead of both the GUI
  desktop and the emergency TTY shell.

Configure:
- scripts/kconfig/menuconfig.py: fix the architecture (and optimization) picker.
  The lxdialog front-end used --radiolist, where <Enter> confirms the ORIGINAL
  choice and only <Space> moves the dot -- so highlighting a new architecture
  and pressing <Enter> appeared to do nothing. Single-choice groups now use
  --menu (with --default-item), where <Enter> selects the highlighted row
  directly. Also stop the unconditional save-on-exit from overriding an explicit
  "No" at the exit confirmation.

Verified: SUB/SUB logs in (injected via QEMU monitor) -> "Login successful" ->
desktop starts; the desktop stays blocked until login. dialog --menu selection
of aarch64 persists to .config/autoconf.h and retargets `make`. Clean build on
x86_64/aarch64/armv8i.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 17:57:47 +05:30
subhobhai943andClaude Opus 4.8 71ad6aca3d feat: Growable heap, GUI-by-default run, live sysmon, SUB/SUB default login
Kernel / memory:
- mm/kmalloc.c: fix "Heap: Out of memory" under the GUI. The desktop allocates
  three full-screen 32-bit buffers (~11MB) which overflowed the fixed 16MB pool.
  The heap now starts at 32MB and grows on demand from the PMM (16MB regions),
  with region-safe coalescing (only merges physically adjacent blocks) so growth
  never corrupts across separate PMM allocations. Adds heap_get_grow_count().

Build / run:
- Makefile: merge `run-gui` into `run` so `make run` boots into the graphical
  desktop; add `run-headless` (serial-only via QEMU_CMD), bump guest RAM to
  256M, update .PHONY and help.

Configure:
- scripts/kconfig/menuconfig.py: gate the curses UI behind isatty() on stdin and
  stdout so non-interactive `make configure` stops emitting "nocbreak() returned
  ERR" and falls cleanly through to the non-interactive writer.

GUI / userland:
- gui/gui_apps.c: rebuild the System Monitor from hardcoded values into live
  telemetry -- real heap used/total/free with a colour-shifting bar, a scrolling
  heap-utilisation graph, the grow counter, and real PMM RAM figures.
- lazybox `free`: report how many times the heap has grown.

Auth:
- Default login is now SUB / SUB (Kali-style username==password), seeded first
  as the primary uid-0 account and set as the logged-in user; root kept for
  compatibility. Hardcoded "root" defaults (whoami, su, passwd, ssh, sshd
  session) updated to SUB.

Verified: clean build on x86_64/aarch64/armv8i; GUI boots with 0 OOM/0 panic;
auth engine initialises cleanly; `make configure` fallback is clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 17:34:50 +05:30
subhobhai943andClaude Opus 4.8 65fa4dd049 ci(release): Build and publish all three architectures
Rework the release workflow from a single x86_64 build into a matrix that
builds x86_64 (bootable image), aarch64 and armv8i (bare-metal kernel ELFs)
in parallel, then publishes one prerelease carrying every architecture's
artifacts plus a combined SHA256SUMS.

- x86_64 builds natively (build-essential + nasm + rustc, CROSS_COMPILE=),
  matching the validated local toolchain; aarch64/armv8i use the apt cross
  compilers (gcc-aarch64-linux-gnu, gcc-arm-linux-gnueabihf).
- Artifacts are named SUB-OS-<tag>-<arch>-... and collected by a release job.
- Notes refreshed for the GUI desktop and multi-arch support.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 17:02:45 +05:30
subhobhai943andClaude Opus 4.8 25c0519c25 Merge branch 'feat/gui-desktop' into main
Bring the SUB-OS Graphical Desktop Environment (SUB-WM compositor, SUB-WT
widget toolkit, icon set, modal dialogs, terminal emulator and the desktop
apps) onto main alongside main's userland /sbin/init and framebuffer console.

Conflicts resolved:
- kernel/main.c: boot into gui_start_desktop() by default (unless nogui/text/
  emergency/single), then fall back through fbcon + /sbin/init + shell when the
  GUI session closes or a text console is requested. Splash raised by default
  unless a text console is asked for. Kept main's fbcon include and the
  weave_preempt_selftest path.
- Makefile: kept main's userland init compilation rules and kernel/elf.c,
  kernel/exec.c in CORE_SRCS; retained the feature branch's gui/ sources.
- README.md: kept main's Weave Scheduler section and adopted the feature
  branch's Boot Logo / Console Font / Framebuffer Console / GUI Desktop docs.

Verified: full x86_64 build links cleanly (kernel.elf + SUB-OS.img).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-25 16:52:52 +05:30
subhobhai943andClaude Opus 4.8 0a874341c6 feat(sched): Preemptive Weave scheduling driven by the timer tick
Kernel threads were only ever switched cooperatively (sched_yield, task_exit);
a thread that never yielded held the CPU forever. The Weave scheduler now
preempts from the PIT tick, so CPU-bound threads are time-sliced.

Deferred switch, not mid-handler:
- sched_tick() no longer switches. It charges the running quantum and, when it
  is spent, only raises weave_need_resched. The switch happens at IRQ return in
  the new sched_preempt_on_return(), called from the x86_64 dispatcher after
  the PIC EOI. That keeps the tick path re-entrancy-safe (the EOI is already
  out, so the PIC keeps delivering) and lets the preempted thread unwind
  through the normal iretq on its own kernel stack.

Interrupt-atomic scheduler state:
- The plain weave_lock is not IRQ-safe, so a tick landing mid-mutation would
  deadlock. weave_lock sections now mask interrupts (arch_irq_save/restore,
  added to arch.h for all three arches) so the timer can never fire inside
  them, and weave_rotate holds them masked across the pick and the register
  swap so the switch is one indivisible step.

Preemption-disable count:
- sched_preempt_disable/enable gate the tick and the return-path switch so a
  preemptive switch cannot land while a plain (non-IRQ-safe) spinlock is held.
  printk wraps its critical section with it, closing the most common hazard;
  other locks are a documented follow-up.

Fresh threads opt into interrupts at entry so they are preemptible from birth
without changing the cooperative early-boot self-test, which still runs with
interrupts masked and is unchanged (20 switches, 3/3 workers).

Verified on x86_64 under QEMU: a new boot-time self-test arms preemption and
two threads that NEVER yield are forced to share the CPU -- 20 involuntary
switches, both advance and are woven down to the CPU-bound lanes -- then boot
proceeds cleanly to ring-3 /sbin/init and the shell with zero faults. aarch64
and armv8i still build; preemption is wired into the x86_64 IRQ path only, so
those stay cooperative for now (their switch asm already exists).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_0161Jh7uCEMLYEMNPsjC1LKf
2026-08-25 10:05:29 +00:00
Subho d221067936 Merge pull request #1 from subhobhai943/claude/relaxed-newton-efpllm
feat(sched): Weave tiered scheduler with real kernel-thread context s…
2026-08-25 14:44:02 +05:30
Claude a14a7c79b0 feat(sched): Weave tiered scheduler with real kernel-thread context switching
Replace the state-relabel-only round-robin with the SUB-OS "Weave"
scheduler backed by a genuine callee-saved context switch. Threads now
actually run on their own kernel stacks instead of merely having their
state field rewritten.

What was implemented:
- sub_ctx_switch(): per-arch assembly (arch/x86_64/cpu/switch.asm,
  arch/aarch64/cpu/switch.S, arch/armv8i/cpu/switch.S) that saves/restores
  the callee-saved register set (+ flags on x86_64) on each thread's own
  stack, plus a first-run trampoline (sub_thread_trampoline) that launches
  a freshly forged thread frame with entry/arg in callee-saved registers.
- task_forge_frame(): builds the initial saved-context frame per arch so
  the very first switch into a new thread lands on the trampoline; task_t
  gains a pointer-sized ctx_sp so the same struct works on 64- and 32-bit.
- Weave policy (kernel/sched.c): WEAVE_NLANES priority lanes, always
  dispatch the lowest non-empty lane round-robin; a task that yields
  voluntarily is woven UP a lane (interactive), one preempted after
  burning its quantum is woven DOWN (CPU-bound). Cooperative switching
  (sched_yield / task_exit) is always live; timer-driven preemption is
  gated behind sched_set_preempt() and left disarmed for now.
- task_exit() now switches to the next runnable thread via
  sched_exit_current() instead of spinning/halting.
- Boot-time weave_selftest() (x86_64) and sched_dump() diagnostics.

How it was verified:
- Builds clean for all three targets: x86_64 (CROSS_COMPILE=x86_64-linux-gnu-,
  RUSTC="rustc --target x86_64-unknown-none"), aarch64, armv8i. No new
  warnings; the new x86_64 asm carries a .note.GNU-stack section.
- Booted x86_64 under QEMU (TCG): the self-test runs three cooperative
  kernel threads to completion on separate stacks with the expected
  1->2->3 round-robin interleave, reports "20 real context switches,
  3/3 workers exited", returns cleanly to the boot thread, and boot
  proceeds normally to ring-3 init and the shell. No fault/panic.

Left unverified:
- The aarch64/armv8i sub_ctx_switch frames build and link but are not yet
  boot-tested on their QEMU targets (tracked in docs/AUTODEV_ROADMAP.md).
- Timer preemption path is implemented but disarmed; exited kernel threads
  currently leak their stack (reaper is next in the backlog).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Jk6K69yGS7zYgvoWd6nJxQ
2026-08-25 08:16:37 +00:00
subhobhai943andClaude Opus 5 2136b6fb4c perf(gui): Present only changed pixels and cache the wallpaper
The compositor recomposited and re-uploaded the entire screen on every pass of
its loop. At the 1280x720 the desktop actually boots into, gui_gfx_present()
memcpy'd 3.6 MB into the framebuffer per frame, and the background underneath
it was recomputed pixel by pixel every time.

Present only the damage
- Keep a RAM shadow of what the framebuffer currently holds and diff the back
  buffer against it, pushing just the changed span of each row. Reading the
  shadow costs a RAM access; the framebuffer write it avoids costs far more,
  and the framebuffer is never read back.
- Each row is narrowed to its first and last differing pixel rather than
  tracked as a set of runs: one memcpy per row keeps the loop tight, and a row
  that changed in two distant places is rare enough that bridging the gap is
  cheaper than the bookkeeping.
- gui_gfx_invalidate() forces a full push for when something outside the
  compositor owns the screen, which the desktop calls on entry and on exit
  around the framebuffer console.

Cache the wallpaper
- The vertical gradient, the blended grid and the branding text never change
  between frames, so they are painted once into a cached buffer and blitted
  back per frame. The cache is keyed on resolution and the grid setting, so
  toggling the grid from the context menu still repaints it.

No drawing code changed: the damage is derived from the frame itself, so every
widget, window and app keeps rendering exactly as before.

Measured in QEMU at 1280x720: an idle desktop with two windows open went from
921600 pixels per frame to 3609, about 0.4% of a full blit. A screendump of
the running desktop is unchanged -- wallpaper, grid, icons, both windows,
taskbar and cursor all render identically.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0161Jh7uCEMLYEMNPsjC1LKf
2026-08-25 07:06:08 +00:00
subhobhai943andClaude Opus 5 ecdc5be435 feat(exec): Run userland in ring 3 with an ELF64 loader and per-process page tables
SUB-OS had a syscall table and user GDT descriptors but nothing that could
actually leave ring 0: lazybox and the shell run as kernel code, and
syscall_handler() was never reachable from a user program. This adds the
missing path end to end on x86_64.

Address spaces (arch/x86_64/mm/vmspace.c)
- The boot page tables identity-map the low 4 GB off PML4[0] and leave the
  rest empty, so userland is given its own PML4 slot (512 GiB) rather than
  holes punched in the kernel's 2 MB huge-page map. A process PML4 shares
  entry 0 with the kernel and privately owns entry 1.
- Frames outside the identity map are refused rather than written through an
  unmapped pointer; lifting that needs a recursive map or a higher-half
  direct map, which is noted in the file.

ELF loader (kernel/elf.c)
- Validates ELF64/little-endian/ET_EXEC/machine, bounds-checks every program
  header against both the image and the user address range, and lays PT_LOAD
  segments into an address space. Frames arrive zeroed, so .bss is covered.

Ring transition (arch/x86_64/cpu/usermode.asm)
- user_enter() drops to ring 3 via iretq after scrubbing every GPR, and
  user_return() unwinds back to it when the process exits, so exec looks like
  a call that blocks until the program finishes.

Syscalls
- INT 0x80 installed at DPL 3 with the Linux x86_64 register convention.
  The process gets its own kernel stack for the trap, because TSS.rsp0 must
  not point at the stack user_enter() is standing on.
- Every user pointer is range-checked before use. The process page tables are
  still live during a syscall, so without that check a program could pass a
  kernel address and have write(2) dump kernel text to the console. The new
  /sbin/init asserts both a wild pointer and a kernel pointer are rejected.
- write(2) to stdout/stderr routes through the new console_write(), which
  reuses printk's routing so output reaches serial and the framebuffer
  console rather than only the VGA text buffer.

Faults
- A ring 3 exception now kills that process and reports CR2 instead of
  panicking the machine.

Userland (userland/init/)
- /sbin/init is a real freestanding static executable, linked at the user
  base and embedded in the kernel image by scripts/mkblob.py. It reports its
  pid, verifies .bss was zeroed and .data is writable, checks the two pointer
  rejections, and exits.

aarch64 and armv8i keep building: the loader and exec path are portable, and
those ports get honest vmspace stubs that fail cleanly rather than pretending
to run a program with kernel privileges.

Verified under QEMU: init loads at 0x80004000a0, reports pid 1, .bss zeroed,
.data writable, both bad pointers rejected, and exits 0 back into the shell.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0161Jh7uCEMLYEMNPsjC1LKf
2026-08-25 06:37:20 +00:00
subhobhai943andClaude Opus 5 7929e48b4e fix(kconfig): Repair menuconfig help buttons and preserve pending toggles
The configurator had four defects that made buttons and selection behave
erratically in `make configure`.

lxdialog front-end:
- Pressing <Help> in a subsystem menu discarded every toggle made since the
  menu was opened, because dialog reports only the focused tag and the result
  was thrown away. Pass --help-status and fold the returned state back into
  the config before showing help.
- <Help> always showed the generic subsystem blurb even though all 41 options
  carry their own help text. Resolve the focused tag and show that item's help.
- The main menu prompt promised <Exit> but dialog rendered <Cancel>; label the
  button to match.

curses fallback:
- The Help button raised AttributeError and killed the UI at top level: the
  root node is the MENU_TREE list itself, and the fallback argument
  `current_menu.get("help", ...)` is evaluated eagerly even when the item has
  its own help.
- The '?' hotkey was nested inside the <Enter> branch where `key` can only be
  a newline, so it was unreachable dead code.
- The button-bar spacing went negative on a narrow dialog and pushed the last
  buttons through the right border; centre and clamp the row instead.

Front-end selection no longer swallows exceptions silently, so a crash reports
itself instead of looking like an unresponsive UI.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0161Jh7uCEMLYEMNPsjC1LKf
2026-08-25 05:37:36 +00:00
subhobhai943andClaude Opus 5 4d9223c8cc feat(video): Move the console to the framebuffer once the adapter is graphical
bochs_vbe_init switches the adapter into graphics mode at boot step [5/14], and
from that point the VGA text buffer is no longer scanned out. This kernel boots
to a TTY, so every message after that -- and the whole shell session -- was
invisible in the emulator window and reached only the serial line.

The console now moves to the framebuffer at that transition, which also makes
the nano editor render correctly: it positions with tty_set_cursor() rather than
escape sequences, and the TTY driver dispatches that to fbcon.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-25 02:08:37 +05:30
subhobhai943andClaude Opus 5 7dd8f2219a feat(video): Add a framebuffer console so the TTY and nano work after graphics mode
Pressing Esc left the desktop and started the shell, but nothing appeared in the
emulator window -- the session was only visible on the host terminal. Once the
VBE adapter is switched into graphics mode the VGA text buffer at 0xB8000 stops
being scanned out, so every printk after that point reached serial and the
dmesg ring but never the display.

Disabling the DISPI interface does not bring the text console back; QEMU keeps
presenting the last framebuffer surface. So rather than clawing the adapter back
to text mode, the console is now rendered into the framebuffer:

  drivers/video/fbcon.c draws the console with the shared 8x8 font and carries a
  VT100 subset -- SGR colours, cursor addressing, relative moves, erase-display,
  erase-line and save/restore. It presents a fixed 80x25 grid, because that is
  the geometry the userland assumes (nano pads its bars with %-80s, the shell
  wraps at TTY_WIDTH), with the glyph cell scaled to fill the active resolution.

The TTY driver became the console dispatcher: tty_write, tty_clear and
tty_set_cursor route to fbcon while it owns the display. That last one is what
makes nano work -- it positions with tty_set_cursor() rather than escapes, so
its full-screen repaint was landing as an append-only stream.

Also in this change:

  - `video=WIDTHxHEIGHT` on the kernel command line selects the display mode,
    defaulting to 1280x720 so a 16:9 host panel is filled rather than pillarboxed
    when QEMU goes fullscreen. Rejects modes the heap cannot back.
  - `make run-fullscreen`, plus zoom-to-fit on the windowed target, so scaling
    preserves the guest aspect ratio instead of stretching it.
  - QEMU targets raise vgamem_mb, since a 1280x720 32-bit surface exceeds the
    default VGA memory.
  - bochs_vbe_disable()/fb_set_active() let the framebuffer be marked dormant so
    drawing routines no-op after a mode teardown.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-25 02:06:55 +05:30
subhobhai943andClaude Opus 5 65b7a709cc fix(gui): Adapt the desktop to the active resolution and restore the console on exit
The launcher table is authored against an 800x600 work area, so at 1280x720
every window kept its original size and left the desktop looking sparse, while
still overlapping the icon column the way it did at the design resolution.
Launch geometry now scales with the work area, capped so a window cannot
swallow the screen, and offset clear of the icon strip so launcher labels stay
readable.

The wallpaper grid was a single fixed colour, which is darker than the top of
the gradient but lighter than the bottom, so the grid faded out across the
lower half of the screen. It is blended now and reads the whole way down.

Leaving the desktop hands the screen to the framebuffer console, and entering
it takes the screen back, so Esc drops to a working TTY inside the emulator and
`gui` returns to the desktop.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-25 02:06:21 +05:30
subhobhai943andClaude Opus 5 2d01fd629e feat(video): Add a framebuffer console so the TTY and nano work after graphics mode
Pressing Esc left the desktop and started the shell, but nothing appeared in the
emulator window -- the session was only visible on the host terminal. Once the
VBE adapter is switched into graphics mode the VGA text buffer at 0xB8000 stops
being scanned out, so every printk after that point reached serial and the
dmesg ring but never the display.

Disabling the DISPI interface does not bring the text console back; QEMU keeps
presenting the last framebuffer surface. So rather than clawing the adapter back
to text mode, the console is now rendered into the framebuffer:

  drivers/video/fbcon.c draws the console with the shared 8x8 font and carries a
  VT100 subset -- SGR colours, cursor addressing, relative moves, erase-display,
  erase-line and save/restore. It presents a fixed 80x25 grid, because that is
  the geometry the userland assumes (nano pads its bars with %-80s, the shell
  wraps at TTY_WIDTH), with the glyph cell scaled to fill the active resolution.

The TTY driver became the console dispatcher: tty_write, tty_clear and
tty_set_cursor route to fbcon while it owns the display. That last one is what
makes nano work -- it positions with tty_set_cursor() rather than escapes, so
its full-screen repaint was landing as an append-only stream.

Also in this change:

  - `video=WIDTHxHEIGHT` on the kernel command line selects the display mode,
    defaulting to 1280x720 so a 16:9 host panel is filled rather than pillarboxed
    when QEMU goes fullscreen. Rejects modes the heap cannot back.
  - `make run-fullscreen`, plus zoom-to-fit on the windowed target, so scaling
    preserves the guest aspect ratio instead of stretching it.
  - QEMU targets raise vgamem_mb, since a 1280x720 32-bit surface exceeds the
    default VGA memory.
  - bochs_vbe_disable()/fb_set_active() let the framebuffer be marked dormant so
    drawing routines no-op after a mode teardown.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-25 02:06:07 +05:30
subhobhai943andClaude Opus 5 e228886adb docs: Document the boot logo, splash and shared console font
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-25 01:38:47 +05:30
subhobhai943andClaude Opus 5 ba06264d12 feat(boot): Add SUB-OS boot logo and splash, share the console font, and lift the kernel size ceiling
Boot logo (init/bootlogo.c, init/logo_data.c, scripts/mklogo.py):
  The logo appears in two forms because the framebuffer does not exist for the
  first third of boot. An ASCII rendition goes to the text console and serial
  line from the first moments; once the video driver is up at step [5/14] the
  real bitmap takes over as a graphical splash with a progress bar tied to the
  actual boot stages.

  The asset is 4-bit palette indexed, so 160x160 pixels cost 12.5 KB instead of
  100 KB, and carries a transparency mask so the mark composites onto the
  gradient rather than stamping a flat rectangle over it. scripts/mklogo.py
  regenerates init/logo_data.c from the source PNG.

  Boot reaches the desktop in well under a second, so the splash is held for a
  minimum duration and the bar sweeps to 100% during the hold; without that the
  logo would flash past unseen. `splashtime=<ms>` tunes it and `nosplash`,
  `nogui` or `text` skip it.

Bootloader (boot/stage2.asm):
  stage2 staged the whole kernel in conventional RAM before copying it to 1 MB,
  which capped the kernel at roughly 576 KB before the load ran into video
  memory at 0xA0000. The logo pushed it over that line and the kernel stopped
  booting. Each 32 KB chunk now goes into one fixed low buffer and is copied
  straight to its final address, using unreal mode for the 32-bit reach. The
  ceiling is now the image itself, 1.375 MB.

Console font (lib/font8x8.c):
  fb_draw_char was a stub that ignored its character argument and drew a filled
  rectangle, which is why the splash rendered its text as solid blocks. It now
  renders real glyphs, and the table it uses is shared: the framebuffer console,
  the canvas rasterizer and the desktop compositor had three divergent private
  copies, two of which still carried the `// \` line-continuation defect.

printk console sink (kernel/printk.c):
  printk output can be redirected to a callback, so a caller can run a subsystem
  that reports via printk and capture what it printed. Serial and the dmesg ring
  still see everything, which keeps a captured run debuggable.

Shell builtins (userland/lazybox/shell.c):
  The builtin chain is extracted into shell_execute_builtin(), so the kernel TTY
  and the desktop terminal accept the same vocabulary instead of the GUI seeing
  only the applet table. Callers that cannot be suspended pass allow_blocking =
  false to refuse the animating ones.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-25 01:37:20 +05:30
subhobhai943andClaude Opus 5 eacba4f4d3 feat(gui): Make the desktop terminal run real commands
The GUI terminal recognised four hardcoded strings and answered everything else
with "Executed: '<cmd>'". It now dispatches to the same LazyBox applet table and
shell builtins the kernel TTY uses, so ls, free, uname, lspci, ktest, neofetch,
sort, cd and the rest all work, with cwd tracked across commands.

LazyBox reports through printk, so before running a command the terminal
installs a printk sink and streams the output into its own scrollback:

  - ANSI SGR codes become per-line colours instead of being stripped.
  - CP437 box-drawing and block characters fall back to ASCII, so the colour
    swatch in neofetch renders as '#' rather than a row of '?'.
  - 256 lines of scrollback, command history on Up/Down, PgUp/PgDn/Home/End.
  - Applets that read the keyboard directly (nano, snake, sh, top) are refused
    with a pointer to the TTY, since they would block the compositor.

The window's paint and event handlers moved out of gui_apps.c into
gui/gui_terminal.c, and gui_gfx.c now draws from the shared lib/font8x8.c table
rather than its own copy.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-25 01:36:02 +05:30
subhobhai943andClaude Opus 5 56a378b142 feat(boot): Add SUB-OS boot logo and splash, share the console font, and lift the kernel size ceiling
Boot logo (init/bootlogo.c, init/logo_data.c, scripts/mklogo.py):
  The logo appears in two forms because the framebuffer does not exist for the
  first third of boot. An ASCII rendition goes to the text console and serial
  line from the first moments; once the video driver is up at step [5/14] the
  real bitmap takes over as a graphical splash with a progress bar tied to the
  actual boot stages.

  The asset is 4-bit palette indexed, so 160x160 pixels cost 12.5 KB instead of
  100 KB, and carries a transparency mask so the mark composites onto the
  gradient rather than stamping a flat rectangle over it. scripts/mklogo.py
  regenerates init/logo_data.c from the source PNG.

  Boot reaches the desktop in well under a second, so the splash is held for a
  minimum duration and the bar sweeps to 100% during the hold; without that the
  logo would flash past unseen. `splashtime=<ms>` tunes it and `nosplash`,
  `nogui` or `text` skip it.

Bootloader (boot/stage2.asm):
  stage2 staged the whole kernel in conventional RAM before copying it to 1 MB,
  which capped the kernel at roughly 576 KB before the load ran into video
  memory at 0xA0000. The logo pushed it over that line and the kernel stopped
  booting. Each 32 KB chunk now goes into one fixed low buffer and is copied
  straight to its final address, using unreal mode for the 32-bit reach. The
  ceiling is now the image itself, 1.375 MB.

Console font (lib/font8x8.c):
  fb_draw_char was a stub that ignored its character argument and drew a filled
  rectangle, which is why the splash rendered its text as solid blocks. It now
  renders real glyphs, and the table it uses is shared: the framebuffer console,
  the canvas rasterizer and the desktop compositor had three divergent private
  copies, two of which still carried the `// \` line-continuation defect.

printk console sink (kernel/printk.c):
  printk output can be redirected to a callback, so a caller can run a subsystem
  that reports via printk and capture what it printed. Serial and the dmesg ring
  still see everything, which keeps a captured run debuggable.

Shell builtins (userland/lazybox/shell.c):
  The builtin chain is extracted into shell_execute_builtin(), so the kernel TTY
  and the desktop terminal accept the same vocabulary instead of the GUI seeing
  only the applet table. Callers that cannot be suspended pass allow_blocking =
  false to refuse the animating ones.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-25 01:35:45 +05:30
subhobhai943andClaude Opus 5 280d7fa2bb build: Track header dependencies, fix default goal, and repair the font table comment
Three build-correctness fixes plus README coverage of the new kernel core.

The Makefile had no header dependency tracking, so editing a header left every
unrebuilt caller linked against the old layout. That is how the desktop's
window paint callbacks silently landed at the wrong struct offset after
gui_window_t grew: gui_apps.o was never recompiled. Compiles now emit .d files
via -MMD -MP and the build pulls them in.

The include has to sit at the end of the file with .DEFAULT_GOAL pinned to
`all` -- a .d file's first rule would otherwise become make's default goal and
turn a bare `make` into a silent no-op.

drivers/video/canvas.c carried the same font table defect gui_gfx.c did: the
`// \` comment on the backslash glyph continues onto the next line, swallowing
the `]` entry and shifting every glyph from ']' onward by one, so all lowercase
text rendered one letter off.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-25 00:20:31 +05:30
subhobhai943andClaude Opus 5 1aee7c7a0d feat(gui): Add SUB-WT widget toolkit, icon set, modal dialogs, six apps and a reworked window manager
Turns the desktop from a fixed set of hand-drawn panels into a real
environment applications can be written against.

SUB-WT immediate-mode toolkit (gui/gui_widgets.c):
  Buttons, labels, checkboxes, radios, sliders, list boxes, text fields, tab
  bars, scrollbars, progress bars, sparklines and badges. An app feeds the WM
  event into the toolkit and then calls widgets from its paint routine, so
  each call both draws the control and reports what the user did with it --
  no retained widget tree to keep in sync with window state.

Icons and dialogs:
  gui_icons.c holds 14 16x16 palette-indexed glyphs stored as hex text, so the
  art stays hand-editable and picks up theme changes for free; they scale by
  integer factors and can be drawn as tint stencils. gui_dialog.c adds modal
  info/warning/error/confirm dialogs that dim the desktop, take exclusive
  input, word-wrap on spaces and report results through a callback.

Applications (gui/gui_apps_ext.c):
  Settings (tabbed appearance/input/power), Task Manager (live process table
  over task_find_by_pid with memory and heap gauges), Kernel Log viewer (reads
  the dmesg ring, strips ANSI, colours by severity, scrolls), KTest Runner
  (drives the in-kernel suites and reports 21/21 with timing), Clock & Calendar
  (Zeller's congruence month grid) and a Text Editor.

Window manager (gui/gui_wm.c):
  Minimize/maximize/restore buttons, a corner resize grip, edge snapping, tile
  and cascade layouts, F5-F9 shortcuts, title truncation, and index-based
  enumeration so the taskbar no longer assumes window ids stay dense.

Desktop shell (gui/gui_desktop.c):
  Launcher icon grid, richer start menu, right-click context menu, taskbar
  buttons that minimize and restore, and a live CPU trace in the tray.

Two interaction bugs fixed along the way:
  - keyboard_get_key() blocks until a key arrives, so calling it every frame
    froze the compositor; it is now gated on keyboard_has_key(), and stale
    console input is drained before the first frame.
  - When the desktop consumed a click, the WM's press-edge detector kept the
    pre-press state and read the still-held button as a fresh press on the
    next frame, stealing focus from whatever the click had just opened.
    gui_wm_sync_button_state() keeps the two layers in step.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-25 00:18:55 +05:30
subhobhai943andClaude Opus 5 ed8922c498 build: Track header dependencies, fix default goal, and repair the font table comment
Three build-correctness fixes plus README coverage of the new kernel core.

The Makefile had no header dependency tracking, so editing a header left every
unrebuilt caller linked against the old layout. That is how the desktop's
window paint callbacks silently landed at the wrong struct offset after
gui_window_t grew: gui_apps.o was never recompiled. Compiles now emit .d files
via -MMD -MP and the build pulls them in.

The include has to sit at the end of the file with .DEFAULT_GOAL pinned to
`all` -- a .d file's first rule would otherwise become make's default goal and
turn a bare `make` into a silent no-op.

drivers/video/canvas.c carried the same font table defect gui_gfx.c did: the
`// \` comment on the backslash glyph continues onto the next line, swallowing
the `]` entry and shifting every glyph from ']' onward by one, so all lowercase
text rendered one letter off.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-25 00:18:18 +05:30
subhobhai943 29d7726a91 Merge branch 'main' into feat/gui-desktop 2026-08-24 23:39:06 +05:30
subhobhai943andClaude Opus 5 f9400f6ac4 feat(kernel): Add RB-tree/kfifo/hashtable, wait queues, futex, tiny RCU, page cache and KTest harness
Expands the kernel-and-TTY core with the primitives the higher layers have
been missing, plus an in-kernel harness that proves they work.

Data structures (lib/):
  - rbtree.c: Linux-compatible red-black tree with parent pointers, full
    insert/erase rebalancing, in-order iteration and an invariant validator.
  - kfifo.c: power-of-two byte ring with free-running indices, so the wrap
    path costs one mask instead of a branch per byte.
  - hashtable.c: separately-chained table with FNV-1a hashing, string or
    u64 keys, and rehash-on-growth at a 3/4 load factor.

Kernel subsystems:
  - kernel/wait.c: wait queues and one-shot completion barriers, backed by a
    static sleeper pool so a blocking path never re-enters the allocator.
  - kernel/futex.c: 32-bucket futex hash with value re-check, requeue, and an
    fmutex_t sleeping mutex built on the uncontended-fastpath protocol.
  - kernel/rcu.c: tiny RCU with reader nesting, deferred callbacks, kfree_rcu
    and rcu_assign_pointer/rcu_dereference publish barriers.
  - mm/page_cache.c: write-back block page cache, 4 KB pages indexed by
    (device, page), CLOCK second-chance eviction and dirty write-back.
  - kernel/ktest.c: KUnit-style harness with 6 built-in suites, 21 cases and
    735 assertions covering every subsystem above (all passing under QEMU).

LazyBox userland:
  - coreutils.c: sort, uniq, cut, tr, rev, tac, nl, seq, diff, xxd, du,
    factor, sum and truncate, reading through the VFS and handling the
    zero-length synthetic nodes that procfs and sysfs expose.
  - ktest, rcuinfo, futexinfo, waitinfo and pagecache diagnostics applets.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-24 23:38:59 +05:30
subhobhai943 4568a9ab4e feat(gui): Upgrade to high-res 800x600, sharp 8x16 font, IRQ12 PS/2 mouse driver, and polished window manager
- Upgrade resolution to 800x600 @ 32bpp TrueColor with Bochs/QEMU VBE LFB
- Implement full 8042 Aux PS/2 mouse controller driver on IRQ 12 (vector 44) with packet decode and bounds clamping
- Add high-definition 8x16 font rasterizer with text drop shadows for sharp typography
- Upgrade to smooth 16x24 anti-aliased cursor with dark slate outline and drop shadow
- Increase titlebars to 24px and taskbar to 32px with modern desktop styling
- Re-architect applications (SysMon, Terminal, Explorer, Calc, Paint, About) for spacious high-res display
2026-08-21 03:18:38 +05:30
subhobhai943 56d8b3af1a feat(gui): Enable direct boot to GUI Desktop with emergency kernel TTY fallback
- Integrate Bochs/QEMU VBE physical LFB mapping directly to hardware video memory (0xFD000000)
- Update kernel boot sequence to directly boot into GUI Desktop by default
- Fall back to Emergency Kernel TTY Mode upon GUI exit (Esc key / Exit menu item)
- Support 'nogui', 'text', 'emergency', and 'single' boot arguments to boot directly into text mode
- Remove frame limits so GUI runs persistently
2026-08-21 03:08:31 +05:30
subhobhai943 81a17aae37 fix(makefile): Enhance run-gui with explicit std VGA and add run-vnc target for headless/remote display 2026-08-21 02:56:53 +05:30
subhobhai943 109472816c feat(gui): Implement complete Graphical Desktop Environment, SUB-WM, and apps
- Create separate feature branch 'feat/gui-desktop'
- Implement double-buffered 2D graphics engine with alpha blending, gradients, rounded rects, and font rasterization in gui/gui_gfx.c and include/gui/gui_gfx.h
- Implement alpha-blended mouse pointer overlay in gui/gui_cursor.c and include/gui/gui_cursor.h
- Implement SUB-WM Window Manager with draggable titlebars, close/minimize buttons, drop shadows, and event routing in gui/gui_wm.c and include/gui/gui_wm.h
- Implement Cyberpunk/Nord desktop environment with wallpaper, taskbar, start menu popup, and system tray in gui/gui_desktop.c and include/gui/gui_desktop.h
- Implement built-in GUI apps: System Performance Monitor, GUI Terminal Emulator, File Explorer, Calculator, Paint Studio, and About dialog in gui/gui_apps.c and include/gui/gui_apps.h
- Add 'startx', 'gui', and 'desktop' applets in LazyBox suite
- Verified multi-architecture compilation across x86_64, AArch64, and ARMv8i
2026-08-21 02:50:55 +05:30
subhobhai943 492045b24d feat: Add Intel e1000e PCIe NIC, VirtIO-GPU 2D accelerator, CPUFreq governor, and VirtIO-Input drivers
- Implement Intel 82574L / 82567LM / I217 / I219 PCIe Gigabit NIC driver with DMA ring buffers and auto-negotiation in drivers/net/e1000e.c and include/drivers/e1000e.h
- Implement VirtIO-GPU 2D hardware graphics display adapter driver with dirty rect flushing in drivers/virtio/virtio_gpu.c and include/drivers/virtio_gpu.h
- Implement Dynamic CPU Frequency Scaling and ACPI CPPC Power Governor with performance, powersave, ondemand, conservative, and schedutil policies in drivers/power/cpufreq.c and include/drivers/cpufreq.h
- Implement VirtIO-Input para-virtualized tablet/mouse event driver in drivers/virtio/virtio_input.c and include/drivers/virtio_input.h
- Expand stage2 kernel bootloader loading boundary to 768 KB (1536 sectors)
- Add LazyBox applets: 'cpufreq', 'gpuinfo', 'e1000e'
- Verified across x86_64, AArch64, and ARMv8i targets
2026-08-21 02:38:27 +05:30
subhobhai943 d1361aeaf2 feat: Implement modern Tree directory visualizer, TSC profiler, and VT Art visualizer
- Add dedicated Linux-compatible Tree visualizer with UTF-8/ASCII branch tracking, -L depth filtering, human sizes, and permissions in userland/lazybox/tree.c
- Implement Hardware Invariant Timestamp Counter (TSC) calibration and microsecond delay timer in kernel/tsc.c and include/kernel/tsc.h
- Add Virtual Terminal color palette, live resource utilization bars, and logo banner in kernel/vt_art.c and include/kernel/vt_art.h
- Add LazyBox applets: 'tree', 'tscinfo', and 'vtart'
- Update README.md documentation and command matrix
2026-08-21 02:19:26 +05:30
subhobhai943 1c09408287 feat: Expand C++17 kernel subsystem with OOP device framework, containers, and benchmark engine
- Implement modern freestanding C++17 containers (Vector<T>, UniquePtr<T>, String) in kernel/cpp/cpp_containers.hpp
- Implement object-oriented Device driver model (Device, BlockDevice, CharDevice, VirtualRamDiskDevice, VirtualNullDevice, VirtualZeroDevice, DeviceManager) in kernel/cpp/cpp_device.hpp and kernel/cpp/cpp_device.cpp
- Add C++ kernel performance microbenchmark suite in kernel/cpp/cpp_bench.cpp
- Export C-FFI bridges and add cross-architecture fallbacks in kernel/cpp/cpp_fallback.c and include/kernel/cpp_kernel.h
- Add LazyBox applets: 'cppdevs' and 'cppbench'
- Update README.md documentation and command matrix
2026-08-21 02:11:44 +05:30
subhobhai943 03d68a9466 feat: Expand Rust subsystem with ELF parser, Buddy allocator analyzer, and ChaCha20-Poly1305 AEAD
- Add memory-safe ELF-64/32 binary header and program header inspector in rust/src/kernel/elf.rs
- Implement Buddy allocator and external memory fragmentation analyzer in rust/src/mm/buddy_analyzer.rs
- Add RFC-8439 Poly1305 128-bit message authenticator and ChaCha20-Poly1305 AEAD in rust/src/crypto/poly1305.rs and rust/src/crypto/chacha20poly1305.rs
- Export C-FFI bridges and add cross-architecture fallbacks in rust/rust_fallback.c and include/kernel/rust.h
- Add LazyBox applets: 'readelf', 'buddyinfo', and 'aead'
- Update README.md documentation and command matrix
2026-08-21 01:55:59 +05:30
subhobhai943 6f7f60bf53 feat: Smooth Snake game rendering and expand kernel subsystems
- Implement flicker-free atomic double-buffered frame rendering and sub-tick input polling in userland/lazybox/snake.c
- Add multi-input support (PS/2 keyboard scancodes and serial ANSI arrow escape sequences)
- Add sound effects (food beep, game over chime) and live speed scaling (+/- keys, P to pause)
- Add POSIX /dev/shm shared memory subsystem in ipc/shm_posix.c and include/ipc/shm_posix.h
- Add DNS resolver cache with TTL tracking in net/ipv4/dns_cache.c and include/net/dns_cache.h
- Add audio tone and jingle synthesizer in sound/beep.c and include/sound/beep.h
- Add %o octal formatting support in lib/vsprintf.c
- Add LazyBox applets: 'shm', 'dnscache', and 'beep'
2026-08-21 01:47:37 +05:30
subhobhai943 7c510dae24 docs: Update README.md with C++17 OOP subsystem and Snake Game 2026-08-18 21:16:11 +05:30
subhobhai943 6eaf9397a2 feat: Add freestanding C++17 OOP kernel subsystem and interactive Snake Game
- Add freestanding C++17 runtime with operator new/delete, pure virtual handlers, and global constructor execution in kernel/cpp/cxx_runtime.cpp
- Implement C++ Object-Oriented telemetry and hardware services with polymorphism in kernel/cpp/cpp_module.cpp and include/kernel/cpp_kernel.h
- Add interactive ANSI Snake Game in userland/lazybox/snake.c with real-time keyboard control and automated simulation mode
- Initialize C++ subsystem during kernel boot step [8/14] in kernel/main.c
- Add LazyBox applets: 'snake', 'cppinfo', and 'cpptest'
2026-08-18 21:14:16 +05:30
subhobhai943 f3b47f9d22 docs: Update LazyBox command matrix with base64, tree, find, pstree, kill, and traceroute 2026-08-18 20:51:40 +05:30
subhobhai943 cda9735b09 feat: Add Rust Base64 codec, Process hierarchy (pstree, kill), and VFS visual tree/find applets
- Implement memory-safe RFC-4648 Base64 encoder and decoder in rust/src/crypto/base64.rs
- Add process parent hierarchy tracking (ppid, pgid), task_find_by_pid, task_kill, and task_dump_pstree in kernel/task.c
- Add list_for_each_entry macro in include/lib/list.h
- Add LazyBox applets: 'base64', 'pstree', 'kill', 'tree', 'find', and 'traceroute'
2026-08-18 20:50:58 +05:30
subhobhai943 a8b0eae694 fix(vfs): Include lib/printf.h for snprintf declaration across ARM32 and core kernel
- Add #include <lib/printf.h> in fs/vfs.c, include/lib/string.h, and include/kernel/printk.h
- Ensure snprintf is always declared during freestanding compilation
2026-08-18 20:16:10 +05:30
subhobhai943 52c81e3f8d fix(arm): Add armv81 alias support and resolve 32-bit pointer cast warning
- Support 'armv81' and 'armv81_defconfig' aliases in Makefile and Kconfig menuconfig.py
- Fix uint64_t to pointer cast on 32-bit ARM in LazyBox shell.c
2026-08-18 19:57:59 +05:30
subhobhai943 b627de0bcf docs: Update README.md with SUB Language in-kernel VM, native .sb modules, and applet matrix 2026-08-18 19:52:27 +05:30
subhobhai943 155794f975 feat(sub-lang): Integrate SUB Language (.sb) native kernel modules and in-kernel VM/interpreter
- Add native .sb kernel modules in sub/ (signature.sb, power_governor.sb, benchmark.sb, easter_egg.sb)
- Implement in-kernel SUB Language VM and AST interpreter in kernel/sub/sub_vm.c
- Implement C-interop runtime in kernel/sub/sub_runtime.c and include/kernel/sub_lang.h
- Initialize SUB Language Engine in boot sequence step [8/14] in kernel/main.c
- Implement strtol in lib/string.c
- Add LazyBox applets: 'subinfo', 'subi', 'subpower', 'subbench', and 'subquote'
2026-08-18 19:51:34 +05:30
subhobhai943 e5984ac113 docs: Update README.md with Rust-for-SUB-OS architecture, applet matrix, and benchmark documentation 2026-08-18 13:58:24 +05:30
subhobhai943 c43e4199db feat(rust): Add MBR/GPT partition decoder, NetFilter firewall engine, and in-kernel crypto benchmark in Rust
- Implement MBR and GPT partition table parser in pure memory-safe Rust
- Implement NetFilter packet filter rule evaluator and metrics in Rust
- Implement in-kernel cryptographic throughput benchmark in Rust
- Add 'cryptobench', 'fdisk', and 'rfilter' LazyBox userland applets
- Update C-FFI header and cross-architecture fallbacks
2026-08-18 13:57:02 +05:30
subhobhai943 92bde4c3fa feat(rust): Expand Rust kernel layer with SHA3, AES, DCache, Watchdog, JSON tokenizer & quote-aware shell
- Implement FIPS 202 SHA3-256 and SHA3-512 (Keccak-f[1600]) in pure Rust
- Implement FIPS 197 AES-128 block cipher in pure Rust
- Implement VFS fast path Directory Entry Cache (dcache) in Rust
- Implement Kernel Health Watchdog & Heartbeat Monitor in Rust
- Implement Zero-Copy JSON Tokenizer & Key-Value extractor in Rust
- Implement memory-safe PCI Vendor/Class database in Rust
- Add quote-aware tokenization to LazyBox shell parser
- Register 'sha3sum', 'dcache', 'watchdog', and 'jsonquery' applets
2026-08-18 12:52:45 +05:30
subhobhai943 24deed8ead feat(rust): Implement Rust-for-SUB-OS kernel layer, ChaCha20 crypto, HWMON thermal governor, and lazybox applets
- Add freestanding no_std Rust kernel layer compiled with rustc
- Implement memory-safe ChaCha20 RFC-8439 stream cipher and CSPRNG in Rust
- Implement intelligent thermal fan speed governor in Rust
- Add printk macro bridge (kprint!, kprintln!) for Rust
- Register 'rustinfo' and 'chacha20' LazyBox applets
- Enable x86_64 hardware FPU & SSE (CR4.OSFXSR/OSXMMEXCPT)
- Add cross-architecture C-bridge for ARM targets
- Update stage2.asm kernel sector copy bounds to 448 KB
2026-08-18 12:44:25 +05:30
subhobhai943 20bd992876 docs: Update README.md with multi-architecture support, Kconfig TUI, drivers & release details 2026-08-18 12:16:19 +05:30
subhobhai943 6ad90542ac fix(userland): Fix 'clear' command, backspace keycodes (0x7F/0x08), serial keyboard input & make run target
- Add 'clear' and 'cls' builtin handlers in LazyBox shell
- Output ANSI \033[2J\033[H sequence on tty_clear() to clear serial/UART terminals
- Support both ASCII 0x7F (DEL) and 0x08 (BS) for Backspace in shell and nano editor
- Connect 16550 serial input to keyboard_get_key() on x86_64 for headless terminals
- Add ANSI escape sequence decoding for arrow keys (Up/Down/Left/Right/Home/End/Del)
- Update Makefile 'make run' and 'make qemu' with -display none for smooth terminal execution
- Add 'make run-gui' target for optional graphical SDL/GTK window
2026-08-18 12:14:40 +05:30
subhobhai943 f5dbc1453c fix(kernel): Fix PMM memory page table reservation, ATA driver timeout loops, and PCI probe order
- Fix PMM memory map parsing: always reserve lower 8MB on x86_64 to protect bootloader, GDT, IDT, and PML4 page tables
- Fix ATA driver: add bounded timeouts to ata_wait_ready() and ata_wait_drq() to eliminate infinite busy loops
- Fix PCI bus initialization order: probe PCI devices before AHCI/NVMe/NIC storage and network drivers
- Implement missing pmm_get_total_pages() and pmm_get_usable_memory() getters
2026-08-18 12:00:33 +05:30
subhobhai943 c02920d11b feat(kconfig): Implement authentic Linux lxdialog GUI menuconfig
- Add Linux kernel lxdialog GUI integration using dialog tool
- Support royal blue backdrop, floating gray dialog windows, 3D drop shadows
- Add bottom button bar: < Select >, < Exit >, < Help >, < Save >, < Defaults >
- Add full context-sensitive Help dialogs for every kernel subsystem and driver
- Provide pixel-perfect Python Curses GUI replica with Tab/Arrow/Hotkeys navigation
- Auto-generate .config and include/config/autoconf.h
2026-08-18 11:38:24 +05:30
subhobhai943 fc68e22916 fix(kconfig): Fix curses terminal initialization & add ANSI configurator fallback
- Guard curses curs_set(0) and use_default_colors() against terminal ERR
- Guard color pairs with curses.has_colors() checks
- Implement run_ansi_fallback() for non-curses/headless/dumb terminals
- Ensure 'make configure' runs seamlessly in all terminal environments
2026-08-18 11:27:45 +05:30
subhobhai943 ae320cc6b8 feat(kconfig): Add 'make configure' TUI target & Linux-style Kbuild dynamic subsystem selection
- Add 'make configure' target to Makefile alongside menuconfig, config, tui, nconfig
- Implement Linux Kbuild dynamic source selection based on .config
- Add compiler optimization level selection (-O2, -O3, -Os, -O0) in menuconfig.py
- Support interactive curses navigation with save/exit/default hotkeys
- Automatically generate .config and include/config/autoconf.h
2026-08-18 11:20:07 +05:30
subhobhai943 7b646c8cc4 feat(arch): Add armv8i (32-bit ARM/AArch32) architecture & complete TUI configurator
- Support ARMv8i / ARM32 (AArch32) baremetal target with arm-linux-gnueabihf toolchain
- Implement ARMv8i boot entry point, banked exception mode stacks (IRQ, ABT, UND, SVC)
- Implement 8 exception vectors, GICv2 distributor/CPU interface, CP15 Generic Timer (100 Hz), flat Section MMU
- Support PL011 UART for early console & interactive LazyBox TTY input
- Update Makefile with make armv8i_defconfig and automated cross-compilation
- Update scripts/kconfig/menuconfig.py with dedicated armv8i options
2026-08-18 11:13:23 +05:30
subhobhai943 70d5925b16 feat(arch): Add AArch64 / ARMv8i CPU support, cross-compilation & Linux Kconfig TUI
- Implement AArch64 HAL, vector table (16 aligned vectors), GICv2, Generic Timer, Stage-1 MMU (39-bit VA), PL011 UART
- Build unified multi-architecture abstraction layer in include/arch/arch.h
- Create interactive Linux-like Kconfig TUI menuconfig in scripts/kconfig/menuconfig.py
- Support make menuconfig, defconfig, x86_64_defconfig, aarch64_defconfig
- Update master Makefile for cross-compiling x86_64 (x86_64-elf-) and AArch64 (aarch64-linux-gnu-)
- Dynamic architecture awareness across printk, uname, neofetch, and LazyBox
2026-08-18 10:50:58 +05:30
267 changed files with 35591 additions and 957 deletions

No files matched your search

+112 -57
View File
@@ -15,9 +15,26 @@ permissions:
contents: write
jobs:
build-and-release:
name: Build & Publish Release
# ---------------------------------------------------------------------------
# Build every supported architecture in parallel. x86_64 produces a bootable
# disk image; aarch64 and armv8i produce a bare-metal kernel ELF for QEMU.
# ---------------------------------------------------------------------------
build:
name: Build ${{ matrix.arch }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
deps: build-essential nasm rustc
make_args: ARCH=x86_64 CROSS_COMPILE=
- arch: aarch64
deps: build-essential gcc-aarch64-linux-gnu
make_args: ARCH=aarch64
- arch: armv8i
deps: build-essential gcc-arm-linux-gnueabihf
make_args: ARCH=armv8i
steps:
- name: Checkout Code
@@ -28,33 +45,61 @@ jobs:
- name: Install Toolchain Dependencies
run: |
sudo apt-get update
sudo apt-get install -y nasm gcc-x86-64-linux-gnu binutils-x86-64-linux-gnu make qemu-system-x86
sudo apt-get install -y ${{ matrix.deps }}
- name: Build SUB-OS Kernel & Bootable Disk Image
- name: Build SUB-OS (${{ matrix.arch }})
run: |
make clean
make CROSS_COMPILE=x86_64-linux-gnu-
make ${{ matrix.make_args }}
- name: Verify Image and Prepare Release Artifacts
- name: Prepare Artifacts
run: |
VER="${{ github.event.inputs.tag_name || github.ref_name }}"
mkdir -p dist
if [ "${{ matrix.arch }}" = "x86_64" ]; then
cp SUB-OS.img dist/SUB-OS-${VER}-x86_64.img
cp build/kernel.elf dist/SUB-OS-${VER}-x86_64-kernel.elf
cp build/kernel.bin dist/SUB-OS-${VER}-x86_64-kernel.bin
else
cp build/kernel.elf dist/SUB-OS-${VER}-${{ matrix.arch }}-kernel.elf
fi
ls -lh dist
- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
name: sub-os-${{ matrix.arch }}
path: dist/*
if-no-files-found: error
# ---------------------------------------------------------------------------
# Collect every architecture's artifacts, checksum them, and publish one
# GitHub prerelease carrying all of them.
# ---------------------------------------------------------------------------
release:
name: Publish Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Download All Build Artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Assemble Release Assets & Checksums
run: |
mkdir -p release-assets
cp SUB-OS.img release-assets/
cp build/kernel.elf release-assets/
cp build/kernel.bin release-assets/
find artifacts -type f -exec cp {} release-assets/ \;
cd release-assets
sha256sum SUB-OS.img kernel.elf kernel.bin > SHA256SUMS.txt
sha256sum * > SHA256SUMS.txt
echo "=== Release assets ==="
ls -lh
echo "=== SHA256SUMS ==="
cat SHA256SUMS.txt
cd ..
- name: Determine Release Tag
id: vars
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="${{ github.event.inputs.tag_name }}"
else
TAG="${GITHUB_REF#refs/tags/}"
fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
run: echo "tag=${{ github.event.inputs.tag_name || github.ref_name }}" >> "$GITHUB_OUTPUT"
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
@@ -63,52 +108,62 @@ jobs:
name: "SUB-OS ${{ steps.vars.outputs.tag }}"
draft: false
prerelease: true
files: |
release-assets/SUB-OS.img
release-assets/kernel.elf
release-assets/kernel.bin
release-assets/SHA256SUMS.txt
files: release-assets/*
body: |
# 🚀 SUB-OS ${{ steps.vars.outputs.tag }}
Welcome to the first official alpha release of **SUB-OS**!
**SUB-OS** is a 64-bit modular monolithic operating system built from scratch for the `x86_64` architecture, featuring modern Unix-like kernel architecture and the **LazyBox** multi-call userland binary suite.
The first official alpha release of **SUB-OS** — a multi-architecture, modular monolithic operating system kernel written from scratch in C, C++17, Assembly and freestanding bare-metal Rust.
This release ships prebuilt kernels for every supported architecture, now including the new **SUB-OS Graphical Desktop Environment**.
---
### 🌟 Included Subsystems & Features
- 🖥️ **64-Bit Bare-Metal Core**: Custom two-stage MBR bootloader with BIOS E820 memory mapping and 4GB 4-level PML4 identity paging.
- ⚡ **Preemptive Scheduler**: 8254 PIT (100 Hz, 10ms resolution) driving Round-Robin task multitasking with atomic spinlocks & semaphores.
- 🧠 **Memory Management**: 64-bit Physical Page Frame Allocator (4 KB bitmap) & Dynamic Kernel Heap (4 MB pool) with boundary tag coalescing.
- 📁 **Virtual File System (VFS)**: POSIX-like VFS layer (`vfs_namei`), in-memory root RAMFS, synthetic `/dev` devfs, and `/proc` metrics procfs.
- 🖧 **Hardware Drivers**:
- **Char**: VGA Text mode, Virtual Terminals TTY1-4 with ANSI escape support, 16550 UART Serial (COM1), PS/2 Keyboard with scancode translation.
- **Block**: ATA / IDE Hard Disk PIO driver (28-bit LBA).
- **PCI**: Bus enumeration and BAR address space mapping.
- **Network**: Intel 82540EM Gigabit Ethernet NIC with circular DMA RX/TX rings.
- **Audio**: PC Speaker tone generator via PIT Channel 2.
- 🌐 **Network Protocol Stack**: Ethernet L2, dynamic ARP table cache, IPv4 routing & checksums, and ICMP Echo client/server (`ping`).
- 🔒 **Kernel Cryptography**: FIPS 180-4 SHA-256, RFC 1321 MD5, IEEE 802.3 CRC32, and Xorshift128+ CSPRNG.
- 🧰 **LazyBox Multi-Call Userland**: 42 applets (`ls`, `cat`, `touch`, `mkdir`, `wc`, `ping`, `ifconfig`, `arp`, `sha256sum`, `md5sum`, `crc32`, `hdparm`, `uname`, `free`, `uptime`, `dmesg`, `sleep`, `clear`, `neofetch`, `echo`, `calc`) with command history and inline cursor editing.
### 🖥️ Supported Architectures
| Arch | Artifact | Boot |
|------|----------|------|
| `x86_64` | Bootable raw disk image + kernel ELF/BIN | Two-stage MBR bootloader |
| `aarch64` | Bare-metal kernel ELF | QEMU `-M virt` (Cortex-A57) |
| `armv8i` (AArch32) | Bare-metal kernel ELF | QEMU `-M virt` (Cortex-A15) |
---
### 🚀 Quick Start (Running with QEMU)
Download `SUB-OS.img` attached below and launch using QEMU:
### 🌟 Highlights
- 🖥️ **SUB-OS Graphical Desktop Environment**: SUB-WM 800x600 TrueColor double-buffered compositor, the SUB-WT immediate-mode widget toolkit, an icon set, modal dialogs, a real terminal emulator, and desktop apps (File Explorer, System Monitor, Task Manager, Kernel Log, KTest Runner, Text Editor, Calculator, Paint, Clock, Settings). x86_64 boots straight into it; pass `nogui`/`text` for the kernel TTY.
- 🎨 **Boot Logo & Splash**: ASCII mark from the first moments of boot, then a graphical splash with an animated progress bar once the framebuffer is up.
- 🖵 **Framebuffer Console**: VT100-subset console rendered into the framebuffer so the TTY and the **nano** editor work after graphics mode.
- 🧶 **Weave Scheduler**: real per-architecture context switching with behavioural lane weaving and boot-time self-tests.
- 🧵 **Kernel Concurrency Core**: wait queues, futexes, tiny RCU, plus rbtree/kfifo/hashtable data structures and a block page cache.
- 🧪 **In-Kernel Test Harness**: KUnit-style suites (rbtree, kfifo, hashtable, RCU, futex, wait queues, page cache, libcore) runnable from the shell or the desktop KTest Runner.
- 🧰 **LazyBox Userland**: multi-call applet suite plus a separate ring-3 `/sbin/init` ELF.
---
### 🚀 Quick Start
**x86_64 (bootable image):**
```bash
qemu-system-x86_64 -drive format=raw,file=SUB-OS.img -nic model=e1000 -serial stdio
qemu-system-x86_64 -drive format=raw,file=SUB-OS-${{ steps.vars.outputs.tag }}-x86_64.img \
-device VGA,vgamem_mb=32 -serial stdio
```
**aarch64:**
```bash
qemu-system-aarch64 -M virt -cpu cortex-a57 -m 128M -nographic \
-kernel SUB-OS-${{ steps.vars.outputs.tag }}-aarch64-kernel.elf
```
**armv8i (AArch32):**
```bash
qemu-system-arm -M virt -cpu cortex-a15 -m 128M -nographic \
-kernel SUB-OS-${{ steps.vars.outputs.tag }}-armv8i-kernel.elf
```
---
### 📦 Release Assets
- `SUB-OS.img`: 1.5 MB bootable raw floppy/disk image.
- `kernel.elf`: 64-bit Kernel ELF binary with symbol tables for debugging.
- `kernel.bin`: Flat kernel binary payload loaded by stage 2 bootloader.
- `SHA256SUMS.txt`: Cryptographic SHA-256 validation checksums.
- `SUB-OS-<ver>-x86_64.img` — bootable raw disk image (boots to the GUI desktop).
- `SUB-OS-<ver>-x86_64-kernel.elf` / `.bin` — x86_64 kernel with symbols / flat payload.
- `SUB-OS-<ver>-aarch64-kernel.elf` — AArch64 bare-metal kernel.
- `SUB-OS-<ver>-armv8i-kernel.elf` — AArch32 bare-metal kernel.
- `SHA256SUMS.txt` — SHA-256 checksums for every asset.
> ⚠️ Alpha software — expect rough edges. Run under QEMU; do not boot on hardware you care about.
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+5
View File
@@ -8,3 +8,8 @@ build/
*.tmp
.DS_Store
.system_generated/
.config
# Python bytecode
__pycache__/
*.pyc
+497 -89
View File
@@ -1,143 +1,551 @@
# =============================================================================
# SUB-OS Master Makefile - Modular Monolithic Linux-Like Kernel Build System
# SUB-OS Master Multi-Architecture Linux-Style Makefile
# Supports: x86_64, aarch64, armv8i
# Features: Linux Kconfig TUI (make configure / make menuconfig), dynamic Kbuild
# =============================================================================
# Cross Compiler & Toolchain Detection
CROSS_COMPILE ?= x86_64-elf-
# Kernel version, Linux-style. NAME is the release codename; keep these in sync
# with include/init/version.h.
VERSION = 1
PATCHLEVEL = 0
SUBLEVEL = 0
EXTRAVERSION = -stable
NAME = Titan
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
ASM = nasm
-include .config
# Architecture Selection (Default: x86_64)
ifdef CONFIG_ARCH
ARCH ?= $(CONFIG_ARCH)
else
ARCH ?= x86_64
endif
# Build Directories
BUILD_DIR = build
BOOT_DIR = boot
# Target Architecture Flags
CFLAGS = -ffreestanding \
-fno-pie \
-fno-pic \
-mno-red-zone \
-mno-mmx \
-mno-sse \
-mno-sse2 \
-mcmodel=kernel \
-Wall \
-Wextra \
-Wno-unused-function \
-Wno-unused-parameter \
-O2 \
-Iinclude
# -----------------------------------------------------------------------------
# Toolchain & Architecture Flags
# -----------------------------------------------------------------------------
ifeq ($(ARCH), x86_64)
CROSS_COMPILE ?= x86_64-elf-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
ASM = nasm
ASMFLAGS_ELF = -f elf64
ASMFLAGS_BOOT = -f bin -I$(BOOT_DIR)/
CFLAGS = -ffreestanding \
-fno-pie \
-fno-pic \
-mno-red-zone \
-mno-mmx \
-mno-sse \
-mno-sse2 \
-mcmodel=kernel \
-Wall \
-Wextra \
-Wno-unused-function \
-Wno-unused-parameter \
$(if $(CONFIG_OPTIMIZATION),$(CONFIG_OPTIMIZATION),-O2) \
-Iinclude \
-D__x86_64__
LDFLAGS = -nostdlib -no-pie -T linker.ld
ASMFLAGS_ELF = -f elf64
ASMFLAGS_BOOT = -f bin -I$(BOOT_DIR)/
LDFLAGS = -nostdlib -no-pie -T linker.ld
# Source Files Discovery
ARCH_C_SRCS = $(shell find arch -name '*.c' 2>/dev/null)
ARCH_ASM_SRCS = $(shell find arch -name '*.asm' 2>/dev/null)
KERNEL_C_SRCS = $(shell find kernel -name '*.c' 2>/dev/null)
MM_C_SRCS = $(shell find mm -name '*.c' 2>/dev/null)
DRIVERS_C_SRCS = $(shell find drivers -name '*.c' 2>/dev/null)
FS_C_SRCS = $(shell find fs -name '*.c' 2>/dev/null)
NET_C_SRCS = $(shell find net -name '*.c' 2>/dev/null)
CRYPTO_C_SRCS = $(shell find crypto -name '*.c' 2>/dev/null)
LIB_C_SRCS = $(shell find lib -name '*.c' 2>/dev/null)
USERLAND_C_SRCS = $(shell find userland -name '*.c' 2>/dev/null)
CERTS_C_SRCS = $(shell find certs -name '*.c' 2>/dev/null)
INIT_C_SRCS = $(shell find init -name '*.c' 2>/dev/null)
IO_URING_C_SRCS = $(shell find io_uring -name '*.c' 2>/dev/null)
SECURITY_C_SRCS = $(shell find security -name '*.c' 2>/dev/null)
USR_C_SRCS = $(shell find usr -name '*.c' 2>/dev/null)
VIRT_C_SRCS = $(shell find virt -name '*.c' 2>/dev/null)
ARCH_C_SRCS = $(shell find arch/x86_64 -name '*.c' 2>/dev/null)
ARCH_ASM_SRCS = $(shell find arch/x86_64 -name '*.asm' 2>/dev/null)
ENTRY_OBJ = $(BUILD_DIR)/arch/x86_64/boot/entry.o
IMAGE = SUB-OS.img
TARGET = $(IMAGE)
QEMU_CMD = qemu-system-x86_64 -drive format=raw,file=$(IMAGE) \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \
-device e1000,netdev=net0 -serial stdio -display none
BLOCK_C_SRCS = $(shell find block -name '*.c' 2>/dev/null)
IPC_C_SRCS = $(shell find ipc -name '*.c' 2>/dev/null)
SOUND_C_SRCS = $(shell find sound -name '*.c' 2>/dev/null)
else ifneq ($(filter $(ARCH), aarch64 arm64),)
CROSS_COMPILE ?= aarch64-linux-gnu-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
AS = $(CROSS_COMPILE)as
ALL_C_SRCS = $(ARCH_C_SRCS) $(KERNEL_C_SRCS) $(MM_C_SRCS) $(DRIVERS_C_SRCS) \
$(FS_C_SRCS) $(NET_C_SRCS) $(CRYPTO_C_SRCS) $(LIB_C_SRCS) $(USERLAND_C_SRCS) \
$(CERTS_C_SRCS) $(INIT_C_SRCS) $(IO_URING_C_SRCS) $(SECURITY_C_SRCS) \
$(USR_C_SRCS) $(VIRT_C_SRCS) $(BLOCK_C_SRCS) $(IPC_C_SRCS) $(SOUND_C_SRCS)
CFLAGS = -ffreestanding \
-fno-pie \
-fno-pic \
-Wall \
-Wextra \
-Wno-unused-function \
-Wno-unused-parameter \
-mno-outline-atomics \
$(if $(CONFIG_OPTIMIZATION),$(CONFIG_OPTIMIZATION),-O2) \
-Iinclude \
-D__aarch64__ \
-march=armv8-a
# Object Files Mapping
C_OBJS = $(patsubst %.c, $(BUILD_DIR)/%.o, $(ALL_C_SRCS))
ASM_OBJS = $(patsubst %.asm, $(BUILD_DIR)/%.o, $(ARCH_ASM_SRCS))
LDFLAGS = -nostdlib -no-pie -T arch/aarch64/linker.ld
# Put entry.o first in linker sequence
ENTRY_OBJ = $(BUILD_DIR)/arch/x86_64/boot/entry.o
OTHER_OBJS = $(filter-out $(ENTRY_OBJ), $(ASM_OBJS) $(C_OBJS))
ARCH_C_SRCS = $(shell find arch/aarch64 -name '*.c' 2>/dev/null)
ARCH_S_SRCS = $(shell find arch/aarch64 -name '*.S' 2>/dev/null)
ENTRY_OBJ = $(BUILD_DIR)/arch/aarch64/boot/entry.o
TARGET = $(BUILD_DIR)/kernel.elf
QEMU_CMD = qemu-system-aarch64 -M virt -cpu cortex-a57 -m 128M -nographic -kernel $(BUILD_DIR)/kernel.elf
IMAGE = SUB-OS.img
else ifneq ($(filter $(ARCH), armv8i armv81 arm32 arm armv7 armv7-a armv7a aarch32),)
CROSS_COMPILE ?= arm-linux-gnueabihf-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
AS = $(CROSS_COMPILE)as
.PHONY: all clean run debug info
CFLAGS = -ffreestanding \
-fno-pie \
-fno-pic \
-Wall \
-Wextra \
-Wno-unused-function \
-Wno-unused-parameter \
$(if $(CONFIG_OPTIMIZATION),$(CONFIG_OPTIMIZATION),-O2) \
-Iinclude \
-D__arm__ \
-D__armv8i__ \
-mcpu=cortex-a15 \
-mfpu=neon-vfpv4 \
-mfloat-abi=hard \
-marm
all: $(IMAGE)
LDFLAGS = -nostdlib -no-pie -T arch/armv8i/linker.ld
# Assemble 16-bit Bootloader Stage 1
$(BUILD_DIR)/boot/boot.bin: $(BOOT_DIR)/boot.asm | $(BUILD_DIR)/boot
$(ASM) $(ASMFLAGS_BOOT) $< -o $@
ARCH_C_SRCS = $(shell find arch/armv8i -name '*.c' 2>/dev/null)
ARCH_S_SRCS = $(shell find arch/armv8i -name '*.S' 2>/dev/null)
ENTRY_OBJ = $(BUILD_DIR)/arch/armv8i/boot/entry.o
TARGET = $(BUILD_DIR)/kernel.elf
QEMU_CMD = qemu-system-arm -M virt -cpu cortex-a15 -m 128M -nographic -kernel $(BUILD_DIR)/kernel.elf
# Assemble 16/32/64-bit Bootloader Stage 2
$(BUILD_DIR)/boot/stage2.bin: $(BOOT_DIR)/stage2.asm $(BOOT_DIR)/gdt.asm | $(BUILD_DIR)/boot
$(ASM) $(ASMFLAGS_BOOT) $< -o $@
else
$(error Unsupported Architecture '$(ARCH)'. Supported: x86_64, aarch64, armv8i)
endif
# Compile 64-bit C source files
$(BUILD_DIR)/%.o: %.c
# -----------------------------------------------------------------------------
# Linux-Style Configurable Kernel Subsystems (Kbuild-Style)
# -----------------------------------------------------------------------------
CORE_SRCS = kernel/main.c kernel/task.c kernel/sync.c kernel/timer.c kernel/printk.c \
kernel/panic.c kernel/trace.c kernel/sched.c kernel/cron.c kernel/syscall.c \
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/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 \
lib/inflate.c lib/image.c lib/jpeg.c \
init/cmdline.c init/service.c init/bootlogo.c init/logo_data.c usr/initramfs.c block/block.c block/elevator.c \
ipc/sem.c ipc/shm.c ipc/shm_posix.c ipc/pipe.c ipc/ipc.c ipc/msg.c \
crypto/sha256.c crypto/md5.c crypto/crc32.c crypto/prng.c \
crypto/x25519.c crypto/chacha20poly1305.c crypto/hkdf.c sound/beep.c
# If no .config exists, default all core options to y
ifeq ($(wildcard .config),)
CONFIG_MM_PMM = y
CONFIG_MM_SLAB = y
CONFIG_MM_VMA = y
CONFIG_FS_VFS = y
CONFIG_FS_FAT32 = y
CONFIG_FS_EXT2 = y
CONFIG_FS_SYSFS = y
CONFIG_DRV_RAMDISK = y
CONFIG_DRV_AHCI = y
CONFIG_DRV_NVME = y
CONFIG_DRV_ATA = y
CONFIG_DRV_VIRTIO_BLK = y
CONFIG_NET_STACK = y
CONFIG_NET_FILTER = y
CONFIG_NET_HTTPD = y
CONFIG_NET_SSHD = y
CONFIG_DRV_E1000 = y
CONFIG_DRV_RTL8139 = y
CONFIG_DRV_VIRTIO_NET = y
CONFIG_DRV_PTY = y
CONFIG_DRV_VIRTIO_RNG = y
CONFIG_DRV_CANVAS_2D = y
CONFIG_DRV_BOCHS_VBE = y
CONFIG_DRV_SOUND_HDA = y
CONFIG_DRV_SOUND_AC97 = y
CONFIG_DRV_USB_XHCI = y
CONFIG_DRV_HWMON = y
CONFIG_SECURITY_LSM = y
CONFIG_SECURITY_KEYRING = y
CONFIG_BPF_VM = y
CONFIG_IO_URING = y
CONFIG_USERLAND_LAZYBOX = y
endif
CONFIG_SRCS-y := $(CORE_SRCS)
ifeq ($(CONFIG_MM_PMM), y)
CONFIG_SRCS-y += mm/pmm.c mm/kmalloc.c mm/page_cache.c
endif
ifeq ($(CONFIG_MM_SLAB), y)
CONFIG_SRCS-y += mm/slab.c
endif
ifeq ($(CONFIG_MM_VMA), y)
CONFIG_SRCS-y += mm/vma.c
endif
ifeq ($(CONFIG_BPF_VM), y)
CONFIG_SRCS-y += kernel/bpf.c
endif
ifeq ($(CONFIG_IO_URING), y)
CONFIG_SRCS-y += io_uring/io_uring.c
endif
ifeq ($(CONFIG_SECURITY_LSM), y)
CONFIG_SRCS-y += security/lsm.c security/auth.c
endif
ifeq ($(CONFIG_SECURITY_KEYRING), y)
CONFIG_SRCS-y += certs/x509.c
endif
# Filesystems
ifeq ($(CONFIG_FS_VFS), y)
CONFIG_SRCS-y += fs/vfs.c fs/ramfs.c fs/devfs.c fs/procfs.c
endif
ifeq ($(CONFIG_FS_FAT32), y)
CONFIG_SRCS-y += fs/fat32.c
endif
ifeq ($(CONFIG_FS_EXT2), y)
CONFIG_SRCS-y += fs/ext2.c
endif
ifeq ($(CONFIG_FS_SYSFS), y)
CONFIG_SRCS-y += fs/sysfs.c
endif
# Storage Drivers
ifeq ($(CONFIG_DRV_RAMDISK), y)
CONFIG_SRCS-y += drivers/block/ramdisk.c
endif
ifeq ($(CONFIG_DRV_AHCI), y)
CONFIG_SRCS-y += drivers/block/ahci.c
endif
ifeq ($(CONFIG_DRV_NVME), y)
CONFIG_SRCS-y += drivers/block/nvme.c
endif
ifeq ($(CONFIG_DRV_ATA), y)
CONFIG_SRCS-y += drivers/block/ata.c
endif
ifeq ($(CONFIG_DRV_VIRTIO_BLK), y)
CONFIG_SRCS-y += drivers/virtio/virtio_blk.c virt/virtio.c virt/hypervisor.c
endif
# Network Subsystem
ifeq ($(CONFIG_NET_STACK), y)
CONFIG_SRCS-y += net/core/net.c net/core/socket.c net/ipv4/tcp.c net/ipv4/udp.c net/ipv4/dhcp.c net/ipv4/dns.c net/ipv4/dns_cache.c net/tls.c net/http_client.c
endif
ifeq ($(CONFIG_NET_FILTER), y)
CONFIG_SRCS-y += net/filter.c
endif
ifeq ($(CONFIG_NET_HTTPD), y)
CONFIG_SRCS-y += net/httpd.c
endif
ifeq ($(CONFIG_NET_SSHD), y)
CONFIG_SRCS-y += net/ssh.c
endif
ifeq ($(CONFIG_DRV_E1000), y)
CONFIG_SRCS-y += drivers/net/e1000.c drivers/pci/pci.c
endif
ifeq ($(CONFIG_DRV_RTL8139), y)
CONFIG_SRCS-y += drivers/net/rtl8139.c drivers/pci/pci.c
endif
ifeq ($(CONFIG_DRV_VIRTIO_NET), y)
CONFIG_SRCS-y += drivers/virtio/virtio_net.c virt/virtio.c
endif
# Character, Audio, Video Drivers
ifeq ($(CONFIG_DRV_PTY), y)
CONFIG_SRCS-y += drivers/char/pty.c
endif
ifeq ($(CONFIG_DRV_VIRTIO_RNG), y)
CONFIG_SRCS-y += drivers/char/virtio_rng.c
endif
ifeq ($(CONFIG_DRV_USB_XHCI), y)
CONFIG_SRCS-y += drivers/usb/xhci.c drivers/usb/usb.c
endif
ifeq ($(CONFIG_DRV_HWMON), y)
CONFIG_SRCS-y += drivers/hwmon/coretemp.c
endif
ifeq ($(CONFIG_DRV_CANVAS_2D), y)
CONFIG_SRCS-y += drivers/video/canvas.c
endif
ifeq ($(CONFIG_DRV_BOCHS_VBE), y)
CONFIG_SRCS-y += drivers/video/bochs.c drivers/video/fb.c drivers/video/fbcon.c
endif
ifeq ($(CONFIG_DRV_SOUND_HDA), y)
CONFIG_SRCS-y += sound/hda.c sound/sound.c sound/pcm.c sound/tts.c sound/melody.c
endif
ifeq ($(CONFIG_DRV_SOUND_AC97), y)
CONFIG_SRCS-y += sound/ac97.c sound/sound.c sound/pcm.c
endif
# Common Input/Display/Power/GPU/Networking Drivers
CONFIG_SRCS-y += drivers/char/tty.c drivers/char/keyboard.c drivers/char/serial.c drivers/char/vga.c \
drivers/input/mouse.c drivers/power/acpi.c drivers/power/cpufreq.c drivers/rtc/rtc.c drivers/sound/speaker.c \
drivers/net/e1000e.c drivers/virtio/virtio_gpu.c drivers/virtio/virtio_input.c \
drivers/timer/hpet.c drivers/char/lpt.c drivers/char/nvram.c
# Userland & GUI Desktop Subsystem
ifeq ($(CONFIG_USERLAND_LAZYBOX), y)
CONFIG_SRCS-y += userland/lazybox/lazybox.c userland/lazybox/shell.c userland/lazybox/sh.c userland/lazybox/nano.c userland/lazybox/snake.c userland/lazybox/tree.c userland/lazybox/coreutils.c
endif
CONFIG_SRCS-y += gui/gui.c gui/gui_gfx.c gui/gui_cursor.c gui/gui_wm.c gui/gui_desktop.c gui/gui_apps.c \
gui/gui_widgets.c gui/gui_icons.c gui/gui_dialog.c gui/gui_apps_ext.c gui/gui_terminal.c \
gui/gui_app_sorry.c gui/gui_app_netmon.c gui/gui_app_web.c
# Deduplicate
CONFIG_SRCS-y := $(sort $(CONFIG_SRCS-y))
# Rust & C++ Layer Configuration
ifeq ($(ARCH), x86_64)
RUSTC ?= rustc
RUST_SRCS = $(shell find rust/src -name '*.rs' 2>/dev/null)
RUST_LIB = $(BUILD_DIR)/rust/librust_kernel.a
CXX ?= g++
CXXFLAGS = -ffreestanding -fno-pie -fno-pic -mno-red-zone -mno-mmx -mno-sse -mno-sse2 \
-mcmodel=kernel -Wall -Wextra -Wno-unused-function -Wno-unused-parameter \
$(if $(CONFIG_OPTIMIZATION),$(CONFIG_OPTIMIZATION),-O2) -Iinclude -D__x86_64__ \
-std=c++17 -fno-exceptions -fno-rtti -fno-threadsafe-statics -fno-use-cxa-atexit
CPP_SRCS = $(shell find kernel/cpp -name '*.cpp' 2>/dev/null)
CPP_OBJS = $(patsubst %.cpp, $(BUILD_DIR)/%.o, $(CPP_SRCS))
ALL_C_SRCS = $(ARCH_C_SRCS) $(CONFIG_SRCS-y)
C_OBJS = $(patsubst %.c, $(BUILD_DIR)/%.o, $(ALL_C_SRCS))
ASM_OBJS = $(patsubst %.asm, $(BUILD_DIR)/%.o, $(ARCH_ASM_SRCS))
# Userland /sbin/init, linked separately and carried inside the kernel image.
INIT_SRC = userland/init/init.c
INIT_LD = userland/init/user.ld
INIT_OBJ = $(BUILD_DIR)/userland/init/init.o
INIT_ELF = $(BUILD_DIR)/userland/init/init.elf
INIT_BLOB_SRC = $(BUILD_DIR)/usr/init_blob.c
INIT_BLOB_OBJ = $(BUILD_DIR)/usr/init_blob.o
OTHER_OBJS = $(filter-out $(ENTRY_OBJ), $(ASM_OBJS) $(C_OBJS)) $(CPP_OBJS) $(INIT_BLOB_OBJ) $(RUST_LIB)
else
CONFIG_SRCS-y += rust/rust_fallback.c kernel/cpp/cpp_fallback.c
ALL_C_SRCS = $(ARCH_C_SRCS) $(CONFIG_SRCS-y)
C_OBJS = $(patsubst %.c, $(BUILD_DIR)/%.o, $(ALL_C_SRCS))
S_OBJS = $(patsubst %.S, $(BUILD_DIR)/%.o, $(ARCH_S_SRCS))
OTHER_OBJS = $(filter-out $(ENTRY_OBJ), $(S_OBJS) $(C_OBJS))
endif
# Generated dependency files, one per object. The include lives at the very
# end of this file: a .d file's first rule would otherwise become make's
# default goal and turn a bare `make` into a no-op.
DEPS = $(C_OBJS:.o=.d) $(CPP_OBJS:.o=.d) $(S_OBJS:.o=.d)
.DEFAULT_GOAL := all
.PHONY: all clean run run-headless run-fullscreen run-vnc run-server debug info configure menuconfig config tui nconfig defconfig x86_64_defconfig aarch64_defconfig armv8_defconfig armv8i_defconfig armv81_defconfig arm32_defconfig qemu help
all: $(TARGET)
# -----------------------------------------------------------------------------
# Configuration Targets (Linux Kconfig TUI Configurator)
# -----------------------------------------------------------------------------
configure menuconfig config tui nconfig:
@python3 scripts/kconfig/menuconfig.py
defconfig:
@python3 scripts/kconfig/menuconfig.py --defconfig $(ARCH)
x86_64_defconfig:
@python3 scripts/kconfig/menuconfig.py --x86_64
aarch64_defconfig:
@python3 scripts/kconfig/menuconfig.py --aarch64
armv8_defconfig armv8i_defconfig armv81_defconfig arm32_defconfig:
@python3 scripts/kconfig/menuconfig.py --armv8i
# -----------------------------------------------------------------------------
# Compilation Rules
# -----------------------------------------------------------------------------
# Header dependency tracking: without this a header change leaves stale objects
# behind, and a struct layout edit silently corrupts every unrebuilt caller.
DEPFLAGS = -MMD -MP
# C Source Files
$(BUILD_DIR)/%.o: %.c include/config/autoconf.h
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
# Assemble 64-bit ASM source files
# C++ Source Files
$(BUILD_DIR)/%.o: %.cpp include/config/autoconf.h
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) $(DEPFLAGS) -c $< -o $@
# x86_64 NASM Assembly Files
$(BUILD_DIR)/%.o: %.asm
@mkdir -p $(dir $@)
$(ASM) $(ASMFLAGS_ELF) $< -o $@
# Link Kernel ELF
$(BUILD_DIR)/kernel.elf: $(ENTRY_OBJ) $(OTHER_OBJS) linker.ld
$(LD) $(LDFLAGS) -o $@ $(ENTRY_OBJ) $(OTHER_OBJS)
# ARM/AArch64 GNU Assembly Files (.S)
$(BUILD_DIR)/%.o: %.S
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@
# Userland Program Compilation
#
# /sbin/init is a separate freestanding executable, not kernel code: it is
# linked at the user base from userland/user.ld and must never see the kernel's
# -mcmodel=kernel, which cannot reach the user half of the address space.
USER_CFLAGS = -ffreestanding -nostdlib -fno-pie -fno-pic -mno-red-zone \
-mcmodel=large -mno-sse -mno-mmx -mno-sse2 \
-Wall -Wextra -Wno-unused-function \
$(if $(CONFIG_OPTIMIZATION),$(CONFIG_OPTIMIZATION),-O2)
$(INIT_OBJ): $(INIT_SRC)
@mkdir -p $(dir $@)
$(CC) $(USER_CFLAGS) -c $< -o $@
$(INIT_ELF): $(INIT_OBJ) $(INIT_LD)
@mkdir -p $(dir $@)
$(LD) -nostdlib -no-pie -T $(INIT_LD) -o $@ $(INIT_OBJ)
@echo "=== [Userland init linked: $@] ==="
$(INIT_BLOB_SRC): $(INIT_ELF) scripts/mkblob.py
@mkdir -p $(dir $@)
@python3 scripts/mkblob.py $(INIT_ELF) $@ subos_init_elf
$(INIT_BLOB_OBJ): $(INIT_BLOB_SRC) include/config/autoconf.h
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
# Rust Source Compilation
$(RUST_LIB): rust/src/lib.rs $(RUST_SRCS)
@mkdir -p $(dir $@)
$(RUSTC) --crate-type staticlib -C panic=abort -C relocation-model=static -C opt-level=2 -C no-redzone=y -o $@ rust/src/lib.rs
# Header Generation Fallback
include/config/autoconf.h:
@mkdir -p include/config
@if [ ! -f .config ]; then python3 scripts/kconfig/menuconfig.py --defconfig $(ARCH); fi
# -----------------------------------------------------------------------------
# Link & Image Assembly
# -----------------------------------------------------------------------------
$(BUILD_DIR)/boot/boot.bin: $(BOOT_DIR)/boot.asm | $(BUILD_DIR)/boot
$(ASM) $(ASMFLAGS_BOOT) $< -o $@
$(BUILD_DIR)/boot/stage2.bin: $(BOOT_DIR)/stage2.asm $(BOOT_DIR)/gdt.asm | $(BUILD_DIR)/boot
$(ASM) $(ASMFLAGS_BOOT) $< -o $@
$(BUILD_DIR)/boot:
mkdir -p $(BUILD_DIR)/boot
LIBGCC ?= $(shell $(CC) $(CFLAGS) -print-libgcc-file-name 2>/dev/null)
$(BUILD_DIR)/kernel.elf: $(ENTRY_OBJ) $(OTHER_OBJS)
$(LD) $(LDFLAGS) -o $@ $(ENTRY_OBJ) $(OTHER_OBJS) $(LIBGCC)
@echo "=== [Kernel ELF Linked: $@ (Architecture: $(ARCH))] ==="
# Convert ELF to Flat Binary
$(BUILD_DIR)/kernel.bin: $(BUILD_DIR)/kernel.elf
$(OBJCOPY) -O binary $< $@
# Create Bootable Floppy / HDD Raw Disk Image
$(IMAGE): $(BUILD_DIR)/boot/boot.bin $(BUILD_DIR)/boot/stage2.bin $(BUILD_DIR)/kernel.bin
dd if=/dev/zero of=$@ bs=512 count=2880 status=none
dd if=$(BUILD_DIR)/boot/boot.bin of=$@ bs=512 conv=notrunc status=none
dd if=$(BUILD_DIR)/boot/stage2.bin of=$@ bs=512 seek=1 conv=notrunc status=none
dd if=$(BUILD_DIR)/kernel.bin of=$@ bs=512 seek=16 conv=notrunc status=none
@echo "=== [SUB-OS Disk Image Built Successfully: $@ (Size: $$(du -h $@ | cut -f1))] ==="
@echo "=== [SUB-OS x86_64 Disk Image Built: $@ (Size: $$(du -h $@ | cut -f1))] ==="
$(BUILD_DIR)/boot:
mkdir -p $(BUILD_DIR)/boot
# -----------------------------------------------------------------------------
# Execution & Emulation Targets
# -----------------------------------------------------------------------------
# -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=64
run: $(IMAGE)
# `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 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),)
qemu-system-aarch64 -M virt -cpu cortex-a57 -m 256M -kernel $(BUILD_DIR)/kernel.elf -device virtio-gpu-pci -serial stdio
else
qemu-system-arm -M virt -cpu cortex-a15 -m 256M -kernel $(BUILD_DIR)/kernel.elf -device virtio-gpu-pci -serial stdio
endif
# Headless: no graphical window, kernel console on the serial line only. Boot
# with `nogui` on the kernel command line to reach the TTY in this mode.
run-headless: $(TARGET)
$(QEMU_CMD)
# Fullscreen without distortion: zoom-to-fit scales the guest while preserving
# its aspect ratio, letterboxing rather than stretching on a wider panel.
run-fullscreen: $(TARGET)
ifeq ($(ARCH), x86_64)
qemu-system-x86_64 -drive format=raw,file=$(IMAGE) \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \
-device e1000,netdev=net0 -serial stdio
-device e1000,netdev=net0 $(QEMU_VGA) \
-display gtk,zoom-to-fit=on,show-cursor=on -full-screen -serial stdio
else
@echo "run-fullscreen targets x86_64 only"
endif
run-vnc: $(TARGET)
ifeq ($(ARCH), x86_64)
@echo "=== [QEMU VNC Server running at localhost:5900 (VNC Display :0)] ==="
qemu-system-x86_64 -drive format=raw,file=$(IMAGE) \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \
-device e1000,netdev=net0 $(QEMU_VGA) -vnc :0 -serial stdio
else
@echo "=== [QEMU VNC Server running at localhost:5900 (VNC Display :0)] ==="
qemu-system-aarch64 -M virt -cpu cortex-a57 -m 128M -kernel $(BUILD_DIR)/kernel.elf -device virtio-gpu-pci -vnc :0 -serial stdio
endif
run-server: $(IMAGE)
qemu-system-x86_64 -drive format=raw,file=$(IMAGE) \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \
-device e1000,netdev=net0 -serial stdio -display none
run-remote: $(IMAGE)
qemu-system-x86_64 -drive format=raw,file=$(IMAGE) \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \
-device e1000,netdev=net0 \
-serial telnet:0.0.0.0:2222,server,nowait -display none
debug: $(IMAGE)
debug: $(TARGET)
ifeq ($(ARCH), x86_64)
qemu-system-x86_64 -drive format=raw,file=$(IMAGE) \
-netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \
-device e1000,netdev=net0 -serial stdio -s -S
else ifneq ($(filter $(ARCH), aarch64 arm64),)
qemu-system-aarch64 -M virt -cpu cortex-a57 -m 128M -nographic -kernel $(BUILD_DIR)/kernel.elf -s -S
else
qemu-system-arm -M virt -cpu cortex-a15 -m 128M -nographic -kernel $(BUILD_DIR)/kernel.elf -s -S
endif
info:
@echo "C Sources: $(words $(ALL_C_SRCS)) files"
@echo "ASM Sources: $(words $(ARCH_ASM_SRCS)) files"
@echo "Target: $(IMAGE)"
@echo "Target Architecture: $(ARCH)"
@echo "Cross Compiler: $(CROSS_COMPILE)"
@echo "Compiled Sources: $(words $(ALL_C_SRCS)) C files"
@echo "Target Binary: $(TARGET)"
help:
@echo "SUB-OS Kernel Build System (Linux-Style Kconfig)"
@echo "=================================================="
@echo " make configure - Launch interactive Linux-style TUI Configurator"
@echo " make menuconfig - Alias for make configure"
@echo " make defconfig - Reset to default configuration for active ARCH"
@echo " make x86_64_defconfig - Load x86_64 default configuration"
@echo " make aarch64_defconfig- Load AArch64 default configuration"
@echo " make armv8i_defconfig - Load ARMv8i (32-bit ARM) default configuration"
@echo " make all [ARCH=...] - Compile kernel image for selected architecture"
@echo " make run [ARCH=...] - Boot into the SUB-OS Graphical Desktop in QEMU"
@echo " make run-headless - Boot in QEMU with serial console only (no window)"
@echo " make run-fullscreen - Boot the desktop in a full-screen QEMU window"
@echo " make run-vnc - Boot the desktop and expose it over VNC :0"
@echo " make clean - Remove all compiled objects and disk images"
@echo " make info - Display build configuration and source metrics"
clean:
rm -rf $(BUILD_DIR) $(IMAGE)
-include $(DEPS)
+253 -121
View File
@@ -1,166 +1,298 @@
# SUB-OS: 64-Bit Production Modular Monolithic Operating System
<div align="center">
```text
_____ _ _ ____ ____ _____
/ ____| | | | _ \ / __ \ / ____|
| (___ | | | | |_) |______| | | | (___
\___ \| | | | _ <|______| | | |\___ \
____) | |__| | |_) | | |__| |____) |
|_____/ \____/|____/ \____/|_____/
```
<img src="assets/logo_SUB_OS.png" alt="SUB-OS logo" width="240" height="240" />
[![Architecture](https://img.shields.io/badge/arch-x86__64%20Long%20Mode-blue.svg?style=flat-square)]()
# SUB-OS: Modular Monolithic Linux-Compatible Operating System
</div>
[![Architectures](https://img.shields.io/badge/arch-x86__64%20%7C%20aarch64%20%7C%20armv8i-blue.svg?style=flat-square)]()
[![Kernel](https://img.shields.io/badge/kernel-Modular%20Monolithic-red.svg?style=flat-square)]()
[![Version](https://img.shields.io/badge/version-v0.2.0--lts%20(Enterprise)-purple.svg?style=flat-square)]()
[![Userland](https://img.shields.io/badge/userland-LazyBox%20v2.0.0%20%2B%20Nano%20%2B%20Sh-orange.svg?style=flat-square)]()
[![Networking](https://img.shields.io/badge/network-Intel%20E1000%20%7C%20HTTPD%20%7C%20NetFilter%20Firewall-green.svg?style=flat-square)]()
[![Services](https://img.shields.io/badge/services-systemctl%20%7C%20syslogd%20%7C%20crond-yellow.svg?style=flat-square)]()
[![C++ Layer](https://img.shields.io/badge/c%2B%2B-Freestanding%20C%2B%2B17%20OOP-blue.svg?style=flat-square)]()
[![Rust Layer](https://img.shields.io/badge/rust-Rust--for--SUB--OS%20no__std-orange.svg?style=flat-square)]()
[![SUB Language](https://img.shields.io/badge/sub--lang-Native%20%2B%20In--Kernel%20VM-blueviolet.svg?style=flat-square)]()
[![Configuration](https://img.shields.io/badge/config-Linux%20Kconfig%20lxdialog%20TUI-cyan.svg?style=flat-square)]()
[![Version](https://img.shields.io/badge/version-v1.0.0--stable-purple.svg?style=flat-square)]()
[![Release](https://img.shields.io/badge/release-v1.0.0%20stable-brightgreen.svg?style=flat-square)](https://github.com/subhobhai943/SUB-OS/releases/tag/v1.0.0)
[![Userland](https://img.shields.io/badge/userland-LazyBox%20%2B%20Nano%20%2B%20Sh-orange.svg?style=flat-square)]()
[![Networking](https://img.shields.io/badge/network-TCP%2FIP%20%7C%20SSHD%20%7C%20HTTPD%20%7C%20NetFilter-green.svg?style=flat-square)]()
[![License](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat-square)](LICENSE)
**SUB-OS** is an open-source, production-grade 64-bit modular monolithic operating system engineered from the ground up for the `x86_64` architecture. Designed for both **cloud/bare-metal servers** and **interactive workstation use**, SUB-OS features a lightweight, high-performance modular kernel without unnecessary bloat.
It comes fully equipped with a bare-metal driver tree, **SLAB Object Allocator**, an advanced virtual file system (VFS) with FAT32, ext2, sysfs, and devfs, **Lightweight Process & Container Namespaces**, **Kernel Event Tracing (FTrace ringbuffers)**, **Stateful Packet Inspection (NetFilter Firewall)**, an **Embedded Micro HTTP Web Server & REST API (`httpd`)**, **Systemd-style Service Unit Manager (`systemctl`)**, **RFC 5424 Syslog Telemetry**, **Cron Background Job Scheduler**, **Multi-User SHA-256 Authentication (`su`, `passwd`, `useradd`)**, **Ramdisk (`/dev/ram0`)**, a full L2-L4 networking protocol stack with BSD Sockets, **8-Bit Retro Melody Synth & Formant Text-to-Speech (TTS)** voice synthesizer, AC'97 sound architecture, full-screen visual `nano` editor, and the **LazyBox** multi-call userland environment with **65+ Linux-compatible utilities** and shell scripting (`sh`).
**SUB-OS** is a multi-architecture, production-grade modular monolithic operating system kernel engineered in **C, C++17, Assembly, freestanding bare-metal Rust (`no_std`), and the custom SUB Language (`.sb`)**. Built for high performance, memory safety, modularity, and Unix/Linux compatibility, SUB-OS supports **64-bit x86 (`x86_64`)**, **64-bit ARM (`aarch64`)**, and **32-bit ARM (`armv8i` / AArch32)** targets.
---
## 🏛️ Comprehensive System Architecture
## 🌟 Key Architecture & Capabilities
- 🧶 **Weave Scheduler & Real Context Switching (`kernel/sched.c`, `arch/*/cpu/switch.*`)**:
- **Genuine multitasking, not a state relabel**: `sub_ctx_switch()` is per-architecture assembly (x86_64/aarch64/armv8i) that saves and restores the callee-saved register set plus flags on each thread's own kernel stack, with a first-run trampoline that launches a freshly forged thread frame.
- **Tiered rotation with behavioural weaving**: runnable threads thread through `WEAVE_NLANES` priority lanes; the dispatcher always pulls the lowest non-empty lane round-robin. A thread that yields voluntarily is *woven up* toward low latency, one that burns its whole quantum and is preempted is *woven down* toward throughput — the lane floats on observed behaviour, not a static nice value.
- **Boot-time self-test**: before hardware interrupts come up, the Weave scheduler drives three cooperative kernel threads to completion on separate stacks (verified under QEMU: correct round-robin interleave, clean return to the boot thread) — proof the switch is real.
- 🧵 **Kernel Concurrency Core (`kernel/wait.c`, `kernel/futex.c`, `kernel/rcu.c`)**:
- **Wait Queues & Completions**: Sleeper parking with timed waits, one-shot completion barriers, and a static entry pool so a blocking path never re-enters the allocator (`waitinfo`).
- **Futex Hash Table**: 32 buckets with value re-check on the slow path, requeue support, and an `fmutex_t` sleeping mutex built on the uncontended-fastpath protocol (`futexinfo`).
- **Tiny RCU**: Lock-free readers with nesting depth tracking, deferred `call_rcu`/`kfree_rcu` callbacks, grace-period accounting, and `rcu_assign_pointer`/`rcu_dereference` publish barriers (`rcuinfo`).
- 🧱 **Core Data Structures (`lib/rbtree.c`, `lib/kfifo.c`, `lib/hashtable.c`)**:
- **Red-Black Tree**: Linux-compatible parent-pointer rbtree with full insert/erase rebalancing, forward and reverse in-order iteration, and a black-height invariant validator.
- **kfifo Ring Buffer**: Power-of-two byte ring with free-running indices, so the wrap path costs one mask instead of a branch per byte.
- **Hash Table**: Separately-chained FNV-1a table with string or `u64` keys and rehash-on-growth at a 3/4 load factor.
- 💾 **Block Page Cache (`mm/page_cache.c`)**:
- Write-back cache of 4 KB pages keyed by `(device, page index)`, indexed through the kernel hash table.
- CLOCK second-chance eviction with dirty write-back, pinning, per-device flush and invalidate, and live hit-rate telemetry (`pagecache`).
- 🧪 **In-Kernel Test Harness (`kernel/ktest.c`)**:
- KUnit-style suites with assertion macros, per-case pass/fail reporting and timing.
- **6 built-in suites, 21 cases, 735 assertions** covering rbtree, kfifo, hashtable, RCU, futex, wait queues, page cache and libcore — runnable from the shell (`ktest`) or the desktop KTest Runner.
- 🎨 **Boot Logo & Splash Screen (`init/bootlogo.c`, `scripts/mklogo.py`)**:
- **Two presentations of one artwork**: an ASCII rendition on the text console and serial line from the first moments of boot, then the real bitmap as a graphical splash once the video driver is up at step [5/14].
- **4-bit palette-indexed asset**: 160x160 pixels in 12.5 KB rather than 100 KB, with a transparency mask so the mark composites onto the gradient instead of a flat rectangle.
- **Animated progress bar** tied to the real boot stages, held for a minimum duration (`splashtime=<ms>`) so a sub-second boot still shows it; `nosplash`, `nogui` or `text` skip it.
- **Regenerable**: `python3 scripts/mklogo.py path/to/logo.png` rewrites `init/logo_data.c` from source artwork.
- 🔤 **Shared 8x8 Console Font (`lib/font8x8.c`)**: one glyph table behind the framebuffer console, the 2D canvas rasterizer and the desktop compositor, replacing three divergent private copies.
- 🖵 **Framebuffer Console (`drivers/video/fbcon.c`)**:
- Once the VBE adapter is in graphics mode the VGA text buffer stops being scanned out, so anything printed afterwards is invisible on the display even though it still reaches serial. fbcon renders the console into the framebuffer instead, the way Linux fbcon does.
- **VT100 subset**: SGR colours, cursor addressing (`H`/`f`), relative moves, erase-display and erase-line, save/restore — enough for full-screen curses-style programs such as the **nano** editor to render correctly.
- Presents a fixed **80x25** grid (matching `TTY_WIDTH`/`TTY_HEIGHT`, which the userland assumes) with the glyph cell scaled to fill whatever resolution is active.
- The TTY driver dispatches `tty_write`, `tty_clear` and `tty_set_cursor` to it, so leaving the desktop with `Esc` drops to a working console **inside the emulator**, not just on the host serial line.
- 🖥️ **SUB-OS Graphical Desktop Environment (`gui/`)**:
- **SUB-WM Compositor**: 800x600 TrueColor double-buffered window manager with drag, corner resize, minimize/maximize/restore, edge snapping (left/right/maximize), tile and cascade layouts, and z-ordered painter's-algorithm compositing.
- **SUB-WT Widget Toolkit (`gui/gui_widgets.c`)**: Immediate-mode controls -- buttons, labels, checkboxes, radios, sliders, list boxes, text fields, tab bars, scrollbars, progress bars, sparklines and badges -- so an app rebuilds its interface from state each frame instead of maintaining a retained widget tree.
- **Icon Set (`gui/gui_icons.c`)**: 14 hand-drawn 16x16 palette-indexed glyphs, integer-scalable and tintable, driving the desktop grid, start menu and window chrome.
- **Modal Dialog Layer (`gui/gui_dialog.c`)**: Info, warning, error and confirm dialogs that dim the desktop and take exclusive input, with word-wrapped messages and callback results.
- **Desktop Shell (`gui/gui_desktop.c`)**: Gradient wallpaper with grid overlay, launcher icon grid, start menu, right-click context menu (tile / cascade / toggle grid), and a taskbar with per-window buttons, live CPU trace, memory badge and RTC clock.
- **Terminal Emulator (`gui/gui_terminal.c`)**: dispatches to the real LazyBox applet table and the shared shell builtins, capturing each command's `printk` output through a console sink. ANSI colours are translated per line, CP437 box and block characters fall back to ASCII, and there is 256 lines of scrollback plus command history.
- **Applications**: Terminal, File Explorer, System Monitor, Task Manager, Kernel Log viewer (live dmesg with severity colouring and scrollback), KTest Runner (drives the in-kernel suites and reports pass/fail), Text Editor, Calculator, Paint Studio, Clock & Calendar, Settings and About.
- **Keyboard Shortcuts**: `F5` cycle focus, `F6` cascade, `F7` tile, `F8` maximize, `F9` minimize, `Esc` exit to the kernel TTY.
- ⚡ **Freestanding C++17 OOP Kernel Engine (`kernel/cpp/`)**:
- **Bare-Metal C++ Runtime**: `operator new`/`delete`, sized deallocation, placement `new`, pure virtual handlers (`__cxa_pure_virtual`), and `.init_array` global constructor dispatcher.
- **Modern Generic Containers**: Pure freestanding `Vector<T>`, `UniquePtr<T>` (with polymorphic converting constructors and custom deleters), dynamic `String`, and move semantics (`kernel::move`, `kernel::forward`).
- **Object-Oriented Device Driver Model**: Abstract `Device` base class with `BlockDevice`, `CharDevice`, `VirtualRamDiskDevice` (512B sector emulator), and `VirtualNull`/`VirtualZero` concrete classes managed by the `DeviceManager` singleton.
- **C++ Microbenchmark Engine**: Validates zero-overhead abstractions, virtual method dynamic dispatch latency across 500,000 iterations, and dynamic container throughput.
- **Interactive C++ Commands**: `cppinfo` (Kernel telemetry), `cppdevs` (OOP device tree), `cppbench` (Microbenchmarks), and `cpptest` (Polymorphic dispatch verifier).
- 🐍 **Interactive In-Kernel Snake Game (`userland/lazybox/snake.c`)**:
- Full-featured ANSI terminal game featuring real-time keyboard control (`WASD` and Arrow keys), dynamic food generation, score counter, body growth, wall/self collision detection, and autonomous simulation mode (`snake --demo`).
- 🔮 **SUB Programming Language Kernel Signature & In-Kernel VM (`sub/` & `kernel/sub/`)**:
- **Native `.sb` Kernel Modules**: Key subsystems written directly in the custom SUB language:
- [`sub/signature.sb`](file:///home/subhobhai943/Github/SUB-OS/sub/signature.sb): Kernel identity, OS signature, and author credits.
- [`sub/power_governor.sb`](file:///home/subhobhai943/Github/SUB-OS/sub/power_governor.sb): Dynamic CPU P-state thermal scaling and frequency/voltage calculations.
- [`sub/benchmark.sb`](file:///home/subhobhai943/Github/SUB-OS/sub/benchmark.sb): Recursive Fibonacci and integer matrix benchmark algorithms.
- [`sub/easter_egg.sb`](file:///home/subhobhai943/Github/SUB-OS/sub/easter_egg.sb): Interactive kernel quotes and easter eggs.
- **In-Kernel SUB Virtual Machine & AST Interpreter (`subi`)**: Run `.sb` scripts or inline expressions live from the shell (`subi file.sb` or `subi -e "var x=10; print(x*2)"`).
- 🦀 **"Rust for SUB-OS" Memory-Safe Kernel Layer (`rust/src/`)**:
- **Freestanding Bare-Metal Rust**: Compiled with `rustc 1.75+` in pure `no_std` mode with static FFI bindings.
- **Memory-Safe Cryptography**: RFC-8439 ChaCha20-Poly1305 AEAD authenticated cipher, CSPRNG entropy source, FIPS-202 SHA3-256 (Keccak-f[1600]), FIPS-197 AES-128, and RFC-4648 Base64 codec.
- **Zero-Copy ELF Binary Parser**: Header and segment inspector validating ELF-64/32 binaries and architectures (`readelf`).
- **Memory Fragmentation & Buddy Telemetry**: Order 0-10 buddy allocator tracking and external fragmentation indexes (`buddyinfo`).
- **Storage & Disk Parsing**: Memory-safe MBR and GUID Partition Table (GPT) header and partition decoder (`fdisk`).
- **Fast VFS Directory Cache (`dcache`)**: 64-entry LRU path hash table for single-cycle file and directory lookups.
- **Kernel Health Watchdog & Heartbeat Monitor**: Subsystem sanity checker and anomaly detector.
- **Zero-Copy JSON Tokenizer**: High-speed JSON key-value query engine for kernel configuration and REST API parsing.
- **NetFilter Packet Evaluator**: Stateful firewall rule matcher and packet counter.
- 🎯 **Multi-Architecture HAL**:
- **`x86_64` (AMD64 / Intel 64)**: Custom 2-stage MBR bootloader, E820 BIOS memory mapping, 4-level PML4 48-bit paging, GDT, 256-entry IDT, 8259 PIC, 8254 PIT timer, Hardware FPU/SSE (CR4.OSFXSR).
- **`aarch64` (ARMv8-A 64-Bit)**: Exception levels (EL1), 16-entry vector table, ARM Generic Interrupt Controller (GICv2), ARM Generic Arch Timer (100 Hz), Stage-1 39-bit VA MMU, PL011 UART.
- **`armv8i` (ARM32 / AArch32)**: Banked exception mode stacks (IRQ, ABT, UND, SVC), VMSAv7 1MB Section MMU, GICv2 distributor, CP15 Generic Timer, VFP/NEON FPU, PL011 UART.
- 🎛️ **Linux-Style Kconfig TUI Configurator (`make configure`)**:
- Full interactive `lxdialog` GUI interface (Royal Blue backdrop, floating modal windows, drop shadows, and menu hierarchy).
- Dynamic Kbuild-style selective compilation based on `.config` and `include/config/autoconf.h`.
- 📁 **Storage & Filesystem Layer**:
- **FAT32 (VFAT)** storage driver with directory traversal and `/mnt/fat32` auto-mounting.
- **Second Extended (EXT2)** Linux filesystem driver.
- **Virtual File System (VFS)**: POSIX `open`, `read`, `write`, `close`, `seek`, `mount` interface.
- Synthetic **DevFS** (`/dev`), **ProcFS** (`/proc`), and **Sysfs KObject Hierarchy** (`/sys`).
- Dynamic in-memory RAM disk (`/dev/ram0`).
- 🖧 **Hardware Device Drivers**:
- **Storage Controllers**: NVMe PCIe SSD, AHCI SATA 6Gb/s, IDE/ATA Hard Disks, VirtIO-Block.
- **Network Adapters**: Intel 82540EM Gigabit Ethernet, Realtek RTL8139 Fast Ethernet, VirtIO-Net 10GbE.
- **Multimedia & Display**: Bochs/VBE dynamic resolution video adapter, 2D TrueColor Canvas Rasterizer, Intel High Definition Audio (Azalia HDA), AC'97 sound codec, Formant TTS synthesizer, 8-Bit Melody player.
- **Bus & Sensors**: PCI bus enumerator, USB 3.0 Extensible Host Controller (xHCI), HWMON CPU temperature and fan tachometer sensors, VirtIO-RNG hardware entropy generator, Unix98 PTY subsystem.
- 🌐 **Networking, Firewalls & Daemons**:
- Full L2–L4 IPv4 protocol stack with dynamic ARP, ICMP Echo (`ping`), UDP, and full 3-way handshake TCP state machine.
- **NetFilter** stateful packet inspection firewall (iptables).
- Built-in **OpenSSH 2.0 Daemon (`sshd`)** on port 2222.
- Built-in **Micro HTTP REST API Server (`httpd`)** on port 8080.
- 🔒 **Kernel Security & Async Engine**:
- **Linux Security Module (LSM)** enforcing POSIX capabilities and DAC.
- **X.509 Cryptographic Keyring** with signature verification.
- In-Kernel **eBPF Register Virtual Machine** and verifier.
- **io_uring** lockless asynchronous Submission/Completion ring buffers.
- Weave tiered-rotation scheduler with real per-arch context switching and spinlock synchronization (see the Weave Scheduler entry above).
- Systemd-style unit manager (`systemctl`), cron background scheduler (`crond`), and RFC 5424 Syslog engine.
- 🧰 **LazyBox Userland Suite (85+ Linux, Rust, C++ & SUB-Lang Applets)**:
- Interactive shell with history, quote-aware tokenization, tab autocompletion, ANSI cursor editing, and script runner (`sh`).
- GNU-compatible **Nano** visual text editor.
- **Text Processing Suite (`userland/lazybox/coreutils.c`)**: `sort`, `uniq`, `cut`, `tr`, `rev`, `tac`, `nl`, `seq`, `diff`, `xxd`, `du`, `factor`, `sum` and `truncate`, all reading through the VFS and handling the zero-length synthetic nodes procfs and sysfs expose.
- **Kernel Diagnostics**: `ktest`, `rcuinfo`, `futexinfo`, `waitinfo` and `pagecache`.
- Full utilities: `ls`, `cat`, `touch`, `mkdir`, `rm`, `cp`, `pwd`, `cd`, `tree`, `find`, `wc`, `head`, `tail`, `stat`, `df`, `mkfs.vfat`, `hexdump`, `neofetch`, `uname`, `free`, `uptime`, `top`, `ps`, `pstree`, `kill`, `dmesg`, `netstat`, `ifconfig`, `ping`, `traceroute`, `curl`, `ssh`, `tts`, `alsamixer`, `sensors`, `rustinfo`, `chacha20`, `sha3sum`, `base64`, `cryptobench`, `fdisk`, `rfilter`, `dcache`, `watchdog`, `jsonquery`, `subinfo`, `subi`, `subpower`, `subbench`, `subquote`, `cppinfo`, `cpptest`, `snake`, and more.
---
## 🏛️ Comprehensive Architecture
```text
+------------------------------------------------------------------------------------+
| USERLAND & SYSTEM SUITE |
| Interactive Shell | GNU Nano 2.0.0 | Script Runner (sh) | LazyBox (65+ Tools) |
| Micro HTTP Server | Service Manager (systemctl) | Syslog Reader | Multi-User Auth |
| Interactive Shell | GNU Nano | Snake Game | Script Runner (sh) | LazyBox (85+ Apps)|
| Micro HTTP Server | SSH 2.0 Daemon | Service Manager (systemctl) | Syslog Engine |
+------------------------------------------------------------------------------------+
| SUB LANGUAGE IN-KERNEL VM & MODULES |
| subi Interpreter | OS Signature | CPU Power Governor | Recursive Math Benchmark |
+------------------------------------------------------------------------------------+
| FREESTANDING C++17 OOP KERNEL ENGINE |
| Virtual Method Dispatch | RAII | Polymorphism | Telemetry Classes | Templates |
+------------------------------------------------------------------------------------+
| RUST-FOR-SUB-OS MEMORY-SAFE SUBSYSTEM |
| ChaCha20 CSPRNG | SHA3-256 Keccak | AES-128 | Base64 | GPT/MBR | NetFilter Matcher|
| VFS DCache Table | Kernel Health Watchdog | Zero-Copy JSON | Hardware Sensors |
+------------------------------------------------------------------------------------+
| VIRTUAL FILE SYSTEM LAYER (VFS) |
| vfs_namei | RAMFS (/) | /dev (devfs) | /proc (procfs) | /sys (sysfs) | FAT32 | ext2|
+------------------------------------------------------------------------------------+
| NAMESPACES, INTER-PROCESS COMMUNICATION & ASYNC I/O ENGINE |
| PID & UTS Isolation | Anonymous Pipes | Message Queues | SHM | Semaphores | io_uring|
| PID/UTS Isolation | Pipes | Message Queues | Shared Memory | Semaphores | io_uring|
+------------------------------------------------------------------------------------+
| CORE KERNEL SERVICES, SECURITY & TRACING |
| Preemptive Scheduler | Task Control | POSIX Signals | Fast Syscalls | Workqueues |
| High-Res Timer Wheel | Dynamic Module Loader | Kernel Event Tracer | LSM Security |
| System Metrics Telemetry | Cron Scheduler Daemon | RFC 5424 Kernel Syslog Engine |
| Preemptive Scheduler | Fast Syscalls | Dynamic Modules | eBPF Virtual Machine |
| LSM POSIX Capabilities | X.509 Keyring | Workqueues | Telemetry & Kernel Tracer |
+------------------------------------------------------------------------------------+
| NETWORKING, NETFILTER FIREWALL & SOUND ARCHITECTURE |
| Ethernet L2 | ARP | IPv4 | ICMP | UDP | TCP | DHCP | DNS | Sockets | NetFilter FW |
| Embedded HTTP Server & REST API | AC'97 Audio | Formant TTS | 8-Bit Melody Engine |
| Ethernet | ARP | IPv4 | ICMP | UDP | TCP | DHCP | DNS | Sockets | NetFilter FW |
| Embedded HTTP Server (8080) | SSH Server (2222) | Intel HDA | AC'97 | Formant TTS |
+------------------------------------------------------------------------------------+
| MEMORY MANAGEMENT (MM) |
| Physical Page Allocator (PMM) | Dynamic Heap | SLAB / SLUB Object Cache |
+------------------------------------------------------------------------------------+
| DEVICE DRIVER TREE & VIRT |
| VGA / TTY1-4 | 16550 UART Serial | PS/2 Keyboard | PS/2 Mouse | ATA Hard Disk |
| PCI Enumerator | Intel E1000 NIC | Ramdisk (/dev/ram0) | VESA FB | CMOS RTC |
| DEVICE DRIVER TREE |
| NVMe PCIe SSD | AHCI SATA | ATA IDE | VirtIO-Blk | Ramdisk (/dev/ram0) |
| Intel E1000 NIC | RTL8139 NIC | VirtIO-Net | USB 3.0 xHCI | HWMON Sensors |
| Bochs VBE Display | 2D Canvas | VGA / TTY1-4 | 16550 Serial | PL011 UART |
+------------------------------------------------------------------------------------+
| HARDWARE ABSTRACTION LAYER (HAL) |
| x86_64 Long Mode | GDT / TSS | IDT / ISRs | Dual 8259A PIC | 8254 PIT Timer |
| x86_64 (AMD64/PML4) | aarch64 (ARMv8-A/GICv2/MMU) | armv8i (AArch32/Section MMU) |
+------------------------------------------------------------------------------------+
```
---
## ⚡ 18-Step Master Boot Pipeline
## 🚀 Quick Start Guide
| Step | Subsystem | Description |
|:---:|:---|:---|
| **[1/18]** | **64-Bit GDT & TSS** | Configures flat kernel/user code & data segments and Task State Segment with dedicated 64KB kernel stack. |
| **[2/18]** | **IDT & ISR Table** | Populates 256-entry interrupt descriptor table with CPU exception handlers and hardware IRQ routing. |
| **[3/18]** | **Dual 8259A PIC** | Remaps master (IRQ 0-7 -> 0x20-0x27) and slave (IRQ 8-15 -> 0x28-0x2F) interrupt controllers. |
| **[4/18]** | **PIT & Timer Wheel** | Configures 8254 PIT channel 0 for 100 Hz (10ms resolution) driving quantum preemption & timer wheel. |
| **[5/18]** | **Input & ACPI** | PS/2 Keyboard scancode decoder, PS/2 Mouse packet tracker, and ACPI power management subsystem. |
| **[6/18]** | **PMM, Heap & SLAB** | 64-bit bitmap frame allocator, 4 MB kernel heap, and high-performance SLAB object cache layer. |
| **[7/18]** | **Block Layer, Ramdisk & ATA** | Generic Block Layer with request queues, Ramdisk (`/dev/ram0`), 'noop' elevator, and ATA driver. |
| **[8/18]** | **PCI, VirtIO, FB & USB** | PCI bus enumerator, VirtIO core, Linear 32-bit Framebuffer graphics, and USB host controller. |
| **[9/18]** | **L2-L4 Network & NetFilter** | Intel E1000 NIC, NetFilter stateful firewall, ARP, IPv4 routing, ICMP Ping, UDP, TCP, DHCP, DNS & HTTPD. |
| **[10/18]** | **Sound & Melody Synth** | Audio core driver, AC'97 soundcard, 8-Bit Melody player, and Formant Text-to-Speech synthesizer. |
| **[11/18]** | **Keyring, Auth & Namespaces**| Loads X.509 trusted keyring, SHA-256 Shadow Authentication, POSIX Capabilities LSM, and namespaces. |
| **[12/18]** | **IPC Engine** | Anonymous Pipes, System V & POSIX Message Queues, Shared Memory segments, and Semaphore sets. |
| **[13/18]** | **io_uring Subsystem** | Lockless Submission Queue Entry (SQE) & Completion Queue Entry (CQE) asynchronous I/O engine. |
| **[14/18]** | **Multi-VFS Mount** | Root RAMFS, synthetic `/dev` devfs, `/proc` procfs, `/sys` sysfs, FAT32 driver, ext2 driver & initramfs. |
| **[15/18]** | **Services, Syslog & Telemetry**| Starts RFC 5424 Syslog daemon, Telemetry Metrics, Cron scheduler, and Systemd Service Unit Manager. |
| **[16/18]** | **Scheduler & LazyBox** | Starts round-robin preemptive scheduler and launches LazyBox interactive userland environment. |
| **[17/18]** | **Hardware Interrupts** | Enables processor interrupts (`sti`) and transitions to userland shell. |
| **[18/18]** | **Interactive Shell** | Launches multi-TTY interactive terminal console on TTY1 with dynamic CWD prompt. |
### 1. Prerequisites
Install the standard build essentials, cross-compilers, and Rust toolchain:
```bash
# Ubuntu / Debian
sudo apt update
sudo apt install build-essential g++ nasm qemu-system-x86 qemu-system-arm \
gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu \
gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf \
rustc cargo dialog python3
```
---
## 🧰 LazyBox Multi-Call Userland Suite (65+ Utilities)
### 2. Linux-Style TUI Configuration
Launch the interactive Kconfig configurator:
```bash
make configure
```
*(Also available via `make menuconfig`)*
#### Fast Architecture Presets:
```bash
make x86_64_defconfig # Configure for x86_64 (Intel / AMD 64-Bit)
make aarch64_defconfig # Configure for 64-bit ARM (Cortex-A57)
make armv8i_defconfig # Configure for 32-bit ARM (Cortex-A15)
```
---
### 3. Compilation & Live Emulation
#### Target: `x86_64` (Default)
```bash
make x86_64_defconfig
make
make run
```
*Port forwardings automatically configured in QEMU:*
- **SSH Server**: `ssh root@localhost -p 2222` (Password: `root`)
- **HTTP REST API**: `http://localhost:8080/api/status`
#### Target: `aarch64` (ARMv8-A 64-Bit)
```bash
make aarch64_defconfig
make ARCH=aarch64
make run ARCH=aarch64
```
#### Target: `armv8i` (ARM32 / AArch32)
```bash
make armv8i_defconfig
make ARCH=armv8i
make run ARCH=armv8i
```
#### Graphical Desktop Environment
SUB-OS boots straight into the desktop. Pass `nogui`, `text`, `emergency` or
`single` on the kernel command line to go directly to the kernel TTY instead;
from inside the desktop, `Esc` (or Start -> Exit to Kernel TTY) drops to the
same shell.
The display mode is set with `video=WIDTHxHEIGHT` on the kernel command line
(default `1280x720`, 16:9). Both the desktop and the framebuffer console adapt
to whatever mode is active.
```bash
make run-gui # Windowed, aspect-preserving scaling
make run-fullscreen # Fullscreen without stretching
make run-vnc # Headless: VNC server on localhost:5900
```
---
## 🧰 LazyBox Command Matrix (85+ Utilities)
| Category | Commands Included |
|---|---|
| **Core & Scripting** | `lazybox`, `sh` (Script runner), `echo`, `env`, `export`, `whoami`, `id`, `date` (CMOS RTC), `cal` |
| **Filesystem & Editor** | `nano`, `ls`, `cat`, `touch`, `mkdir`, `pwd`, `cd`, `wc`, `head`, `tail`, `stat`, `cp`, `grep`, `hexdump`, `basename`, `dirname` |
| **Server & Web** | `httpd` (Micro Web Server), `curl`, `wget`, `systemctl` / `service` (Daemon Manager), `crontab` |
| **Security & Logging** | `logger`, `logread` (Syslog viewer), `su`, `passwd`, `useradd`, `certcheck`, `capsh`, `ipcs` |
| **Sound & Voice** | `tts` (Phonetic Formant Voice Synthesizer), `tune` (8-Bit Melody Player), `alsamixer` |
| **Kernel & Tracing** | `lsmod`, `insmod`, `rmmod`, `slabinfo`, `trace`, `unshare` (Namespace containers) |
| **Crypto & Hashes** | `sha256sum`, `md5sum`, `crc32`, `rand` |
| **Network & Firewall** | `ifconfig`, `ping`, `arp`, `dhclient`, `nslookup`, `iptables` (Stateful Firewall), `netstat` |
| **Storage & Devices** | `hdparm`, `lspci`, `speaker`, `mouse` |
| **Virtualization & Async**| `virtinfo`, `io_uring_test` |
| **Monitoring & Telemetry**| `uname`, `free`, `uptime`, `vmstat`, `iostat`, `htop`, `top`, `dmesg`, `ps`, `sleep`, `reboot`, `poweroff` |
| **Terminal** | `clear`, `neofetch`, `help` |
| **Core & Shell** | `lazybox`, `sh`, `echo`, `env`, `export`, `whoami`, `id`, `date`, `cal`, `jsonquery`, `subi` |
| **Filesystem & Search** | `nano`, `ls`, `cat`, `touch`, `mkdir`, `rm`, `cp`, `pwd`, `cd`, `tree`, `find`, `wc`, `head`, `tail`, `stat`, `df`, `mkfs.vfat`, `grep`, `hexdump`, `dcache` |
| **Networking & Routing** | `ifconfig`, `ping`, `traceroute`, `arp`, `dhclient`, `nslookup`, `dnscache`, `netstat`, `iptables`, `rfilter`, `httpd`, `sshd`, `curl`, `wget`, `ssh`, `e1000e` |
| **Hardware & Storage** | `lspci`, `lsdev`, `lsblk`, `fdisk`, `hdparm`, `sensors`, `speaker`, `beep`, `mouse`, `alsamixer`, `tts`, `virtinfo`, `gpuinfo`, `cpufreq` |
| **Security & Cryptography** | `su`, `passwd`, `useradd`, `certcheck`, `capsh`, `ipcs`, `chacha20`, `sha3sum`, `base64`, `aead`, `cryptobench` |
| **SUB Language & System** | `subinfo`, `subpower`, `subbench`, `subquote`, `pstree`, `kill`, `shm`, `readelf`, `buddyinfo`, `tscinfo`, `systemctl`, `service`, `crontab`, `logger`, `logread`, `watchdog`, `rustinfo`, `cppinfo`, `cpptest`, `cppdevs`, `cppbench` |
| **Entertainment & Games** | `snake`, `matrix`, `calc`, `vtart` |
| **Kernel & Tracing** | `lsmod`, `insmod`, `rmmod`, `slabinfo`, `trace`, `unshare`, `io_uring_test` |
| **Diagnostics & Metrics**| `neofetch`, `uname`, `free`, `uptime`, `top`, `htop`, `ps`, `dmesg`, `vmstat`, `iostat` |
| **System Control & GUI** | `clear`, `help`, `sleep`, `reboot`, `shutdown`, `poweroff`, `tty`, `startx`, `gui`, `desktop` |
---
## 🌐 Server Operations Demonstration
## 📦 Official Release
### 1. Launching Micro HTTP Server & Testing REST API
```text
sub-os:/> httpd start 80
HTTPD: Web Server listening on 0.0.0.0:80 (HTTP/1.1)
sub-os:/> curl /api/status
HTTP/1.1 200 OK
Server: SUB-OS/0.2.0 (x86_64)
Content-Type: application/json
Content-Length: 159
Connection: close
{"os":"SUB-OS","version":"0.2.0-lts (x86_64)","arch":"x86_64","uptime_sec":1,"mem_total_kb":130944,"mem_free_kb":122752,"requests_served":1,"status":"HEALTHY"}
```
### 2. Managing System Services
```text
sub-os:/> systemctl
UNIT LOAD ACTIVE SUB DESCRIPTION
syslogd.service loaded active running System Logging Daemon
networking.service loaded active running IPv4 Network Interface Manager
httpd.service loaded active running Embedded Micro HTTP Web Server
crond.service loaded active running Periodic Command Scheduler Daemon
firewall.service loaded active running NetFilter Stateful Packet Inspection
```
### 3. Multi-User Authentication & Switching
```text
sub-os:/> whoami
root
sub-os:/> su admin
Switched to user 'admin'
sub-os:/> whoami
admin
sub-os:/> id
uid=1000(admin) gid=1000(admin) groups=1000(admin)
sub-os:/> su root
Switched to user 'root'
```
Download precompiled kernel binaries and bootable disk images from GitHub:
👉 **[SUB-OS v1.0.0 — First Stable Release](https://github.com/subhobhai943/SUB-OS/releases/tag/v1.0.0)**
---
## 🚀 Building & Running
## 📚 Documentation
### Compilation
```bash
make clean
make
```
| Document | What's inside |
|---|---|
| 📝 **[Contributing Guide](docs/CONTRIBUTING.md)** | How to build, code style, kernel gotchas, commit & PR conventions |
| 🔒 **[Security Policy](docs/SECURITY.md)** | Supported versions, how to report a vulnerability, known caveats |
| 🗺️ **[Roadmap](docs/ROADMAP.md)** | What shipped in v1.0.0 and what's planned next |
| 🤖 **[Autonomous Dev Roadmap](docs/AUTODEV_ROADMAP.md)** | Fine-grained per-session engineering backlog |
### Emulation in QEMU
```bash
qemu-system-x86_64 -drive format=raw,file=SUB-OS.img -nic model=e1000 -serial stdio -audiodev id=snd0,driver=pa -machine pcspk-audiodev=snd0
```
---
## 🤝 Contributing
Contributions are welcome! Please read the **[Contributing Guide](docs/CONTRIBUTING.md)**
before opening a pull request, and see the **[Roadmap](docs/ROADMAP.md)** for
where help is most valuable. Found a security issue? Follow the
**[Security Policy](docs/SECURITY.md)** — do not open a public issue.
---
## 📄 License
SUB-OS is distributed under the open-source **MIT License**. See [`LICENSE`](LICENSE) for details.
+106
View File
@@ -0,0 +1,106 @@
/*
* SUB-OS AArch64 / ARMv8 Bootstrap Entry Point
* Handles EL2/EL3 -> EL1 drop, sets up stack, zeroes BSS, and jumps to kernel_main_aarch64
*/
.section ".text.boot"
.global _start
.global aarch64_entry
_start:
aarch64_entry:
// Early UART debug ping
mov x0, #0x09000000
mov w1, #'['
str w1, [x0]
mov w1, #'S'
str w1, [x0]
mov w1, #'U'
str w1, [x0]
mov w1, #'B'
str w1, [x0]
mov w1, #'-'
str w1, [x0]
mov w1, #'O'
str w1, [x0]
mov w1, #'S'
str w1, [x0]
mov w1, #']'
str w1, [x0]
mov w1, #'\n'
str w1, [x0]
// 1. Primary Core Check (Core 0 proceeds, other cores halt)
mrs x0, mpidr_el1
and x0, x0, #0xFF
cbz x0, .primary_core
.secondary_halt:
wfe
b .secondary_halt
.primary_core:
// 2. Check current Exception Level (EL)
mrs x0, CurrentEL
lsr x0, x0, #2
cmp x0, #2
b.eq .from_el2
cmp x0, #3
b.eq .from_el3
b .in_el1
.from_el3:
// Setup Secure Configuration Register (SCR_EL3)
mov x0, #(1 << 10) // RW=1 (64-bit execution in EL2/EL1)
orr x0, x0, #(1 << 0) // NS=1 (Non-secure)
msr scr_el3, x0
mov x0, #0b01001 // EL2h mode with DAIF masked
msr spsr_el3, x0
adr x0, .from_el2
msr elr_el3, x0
eret
.from_el2:
// Setup Hypervisor Configuration Register (HCR_EL2)
mov x0, #(1 << 31) // RW=1 (64-bit execution in EL1)
msr hcr_el2, x0
// Setup SPSR_EL2 for drop to EL1h (EL1 with dedicated SP_EL1)
mov x0, #0b00101 // M[3:0] = 0101 (EL1h)
orr x0, x0, #(0xF << 6) // Mask DAIF
msr spsr_el2, x0
adr x0, .in_el1
msr elr_el2, x0
eret
.in_el1:
// Enable Floating-Point and Advanced SIMD (NEON) in EL1
mov x0, #(3 << 20)
msr cpacr_el1, x0
isb
// 3. Set up Kernel Stack (64 KB dedicated EL1 stack)
ldr x0, =stack_top
mov sp, x0
// 4. Enable Vector Table
ldr x0, =vectors
msr vbar_el1, x0
// 5. Zero out BSS
ldr x0, =__bss_start
ldr x1, =__bss_end
.bss_loop:
cmp x0, x1
b.ge .bss_done
str xzr, [x0], #8
b .bss_loop
.bss_done:
// 6. Jump to AArch64 C Entry
bl kernel_main_aarch64
.hang:
wfi
b .hang
+36
View File
@@ -0,0 +1,36 @@
#
# Automatically generated by SUB-OS Kconfig for aarch64
#
CONFIG_ARCH=aarch64
CONFIG_ARCH_AARCH64=y
# CONFIG_ARCH_X86_64 is not set
CONFIG_BPF_VM=y
CONFIG_DRV_CANVAS_2D=y
CONFIG_DRV_PTY=y
CONFIG_DRV_RAMDISK=y
CONFIG_DRV_VIRTIO_BLK=y
CONFIG_DRV_VIRTIO_NET=y
CONFIG_DRV_VIRTIO_RNG=y
CONFIG_FS_DEVFS=y
CONFIG_FS_EXT2=y
CONFIG_FS_FAT32=y
CONFIG_FS_PROCFS=y
CONFIG_FS_RAMFS=y
CONFIG_FS_SYSFS=y
CONFIG_FS_VFS=y
CONFIG_IO_URING=y
CONFIG_MM_PMM=y
CONFIG_MM_SLAB=y
CONFIG_MM_VMA=y
CONFIG_NET_FILTER=y
CONFIG_NET_HTTPD=y
CONFIG_NET_SSHD=y
CONFIG_NET_STACK=y
CONFIG_OPTIMIZATION="-O2"
CONFIG_PREEMPT=y
CONFIG_SECURITY_KEYRING=y
CONFIG_SECURITY_LSM=y
CONFIG_SMP=y
CONFIG_SUBOS_HOSTNAME="sub-os"
CONFIG_SUBOS_VERSION="0.2.0-lts"
CONFIG_USERLAND_LAZYBOX=y
+65
View File
@@ -0,0 +1,65 @@
#include <arch/arch.h>
#include <arch/aarch64/cpu.h>
#include <arch/aarch64/gic.h>
#include <arch/aarch64/timer.h>
#include <arch/aarch64/uart.h>
#include <arch/aarch64/mmu.h>
#include <arch/x86_64/cpuid.h>
#include <kernel/printk.h>
#include <lib/string.h>
static cpu_info_t aarch64_compat_cpu;
void arch_early_init(void) {
uart_pl011_init();
}
void arch_init(void) {
aarch64_cpu_init();
gic_init();
aarch64_timer_init(100);
aarch64_mmu_init();
// Populate compatibility CPU info structure for procfs
memset(&aarch64_compat_cpu, 0, sizeof(aarch64_compat_cpu));
const aarch64_cpu_info_t* info = aarch64_get_cpu_info();
strncpy(aarch64_compat_cpu.vendor, "ARM Ltd", sizeof(aarch64_compat_cpu.vendor) - 1);
strncpy(aarch64_compat_cpu.model, info->model_name, sizeof(aarch64_compat_cpu.model) - 1);
aarch64_compat_cpu.has_fpu = true;
arch_enable_interrupts();
}
const char* arch_get_name(void) {
return "aarch64 (ARMv8-A)";
}
uint32_t arch_get_cpu_id(void) {
return (uint32_t)(aarch64_get_mpidr() & 0xFF);
}
// Timer Compatibility Layer for Portable Drivers
uint64_t pit_get_ticks(void) {
return aarch64_timer_get_ticks();
}
void pit_sleep(uint32_t ms) {
uint64_t count = (ms > 10) ? (ms / 10) : 1;
uint64_t target = aarch64_timer_get_ticks() + count;
while (aarch64_timer_get_ticks() < target) {
__asm__ volatile("wfi");
}
}
// Interrupt & CPUID Compatibility Stubs
void isr_register_handler(uint8_t isr, void* handler) {
(void)isr; (void)handler;
}
void pic_clear_mask(uint8_t irq) {
(void)irq;
}
const cpu_info_t* cpuid_get_info(void) {
return &aarch64_compat_cpu;
}
+58
View File
@@ -0,0 +1,58 @@
#include <arch/aarch64/cpu.h>
#include <lib/string.h>
#include <kernel/printk.h>
static aarch64_cpu_info_t cpu_info;
uint64_t aarch64_get_midr(void) {
uint64_t midr;
__asm__ volatile("mrs %0, midr_el1" : "=r"(midr));
return midr;
}
uint64_t aarch64_get_mpidr(void) {
uint64_t mpidr;
__asm__ volatile("mrs %0, mpidr_el1" : "=r"(mpidr));
return mpidr;
}
uint32_t aarch64_get_current_el(void) {
uint64_t cur_el;
__asm__ volatile("mrs %0, CurrentEL" : "=r"(cur_el));
return (uint32_t)(cur_el >> 2);
}
void aarch64_cpu_init(void) {
memset(&cpu_info, 0, sizeof(cpu_info));
uint64_t midr = aarch64_get_midr();
cpu_info.implementer = (uint32_t)((midr >> 24) & 0xFF);
cpu_info.variant = (uint32_t)((midr >> 20) & 0x0F);
cpu_info.architecture = (uint32_t)((midr >> 16) & 0x0F);
cpu_info.part_num = (uint32_t)((midr >> 4) & 0xFFF);
cpu_info.revision = (uint32_t)(midr & 0x0F);
cpu_info.current_el = aarch64_get_current_el();
if (cpu_info.implementer == 0x41) { // ARM Limited
switch (cpu_info.part_num) {
case 0xD03: strcpy(cpu_info.model_name, "ARM Cortex-A53 (ARMv8.0-A)"); break;
case 0xD07: strcpy(cpu_info.model_name, "ARM Cortex-A57 (ARMv8.0-A)"); break;
case 0xD08: strcpy(cpu_info.model_name, "ARM Cortex-A72 (ARMv8.0-A)"); break;
case 0xD09: strcpy(cpu_info.model_name, "ARM Cortex-A73 (ARMv8.0-A)"); break;
case 0xD0B: strcpy(cpu_info.model_name, "ARM Cortex-A76 (ARMv8.2-A)"); break;
case 0xD40: strcpy(cpu_info.model_name, "ARM Neoverse-V1 (ARMv8.4-A)"); break;
default: strcpy(cpu_info.model_name, "ARMv8-A Compatible 64-Bit Processor"); break;
}
} else if (cpu_info.implementer == 0x61) { // Apple Silicon
strcpy(cpu_info.model_name, "Apple Silicon M-Series (ARMv8-A)");
} else {
strcpy(cpu_info.model_name, "Generic AArch64 / ARMv8 64-Bit Core");
}
printk(KERN_INFO "CPU: %s (Implementer: 0x%02x, Part: 0x%03x, EL%u)\n",
cpu_info.model_name, cpu_info.implementer, cpu_info.part_num, cpu_info.current_el);
}
const aarch64_cpu_info_t* aarch64_get_cpu_info(void) {
return &cpu_info;
}
+42
View File
@@ -0,0 +1,42 @@
#include <arch/aarch64/arch.h>
#include <arch/aarch64/gic.h>
#include <kernel/printk.h>
void aarch64_sync_handler(aarch64_context_t* ctx) {
uint64_t esr, elr, far;
__asm__ volatile("mrs %0, esr_el1" : "=r"(esr));
__asm__ volatile("mrs %0, elr_el1" : "=r"(elr));
__asm__ volatile("mrs %0, far_el1" : "=r"(far));
uint32_t ec = (esr >> 26) & 0x3F;
printk(ANSI_BRIGHT_RED "\n[KERNEL PANIC] AArch64 Synchronous Exception!\n" ANSI_RESET);
printk("ESR_EL1: 0x%016llx (EC: 0x%02x, ISS: 0x%08x)\n", esr, ec, (uint32_t)(esr & 0x01FFFFFF));
printk("ELR_EL1 (PC): 0x%016llx FAR_EL1: 0x%016llx\n", elr, far);
printk("Registers:\n");
for (int i = 0; i < 30; i += 2) {
printk(" X%02d: 0x%016llx X%02d: 0x%016llx\n", i, ctx->r[i], i + 1, ctx->r[i + 1]);
}
printk(" X30 (LR): 0x%016llx SP: 0x%016llx\n", ctx->r[30], ctx->sp);
while (1) {
__asm__ volatile("wfe");
}
}
void aarch64_irq_handler(aarch64_context_t* ctx) {
(void)ctx;
gic_handle_irq();
}
void aarch64_fiq_handler(aarch64_context_t* ctx) {
(void)ctx;
printk(KERN_WARNING "AArch64: Unexpected FIQ caught\n");
}
void aarch64_serror_handler(aarch64_context_t* ctx) {
(void)ctx;
printk(ANSI_BRIGHT_RED "[KERNEL PANIC] AArch64 SError Interrupt caught!\n" ANSI_RESET);
while (1) {
__asm__ volatile("wfe");
}
}
+71
View File
@@ -0,0 +1,71 @@
#include <arch/aarch64/gic.h>
#include <arch/aarch64/io.h>
#include <lib/string.h>
#include <kernel/printk.h>
#define MAX_GIC_IRQS 256
static gic_irq_handler_t irq_handlers[MAX_GIC_IRQS];
void gic_init(void) {
memset(irq_handlers, 0, sizeof(irq_handlers));
// 1. Disable Distributor
mmio_write32(GIC_DIST_BASE + GICD_CTLR, 0);
// 2. Clear pending and enable default priorities
uint32_t typer = mmio_read32(GIC_DIST_BASE + GICD_TYPER);
uint32_t num_irqs = 32 * ((typer & 0x1F) + 1);
if (num_irqs > MAX_GIC_IRQS) num_irqs = MAX_GIC_IRQS;
for (uint32_t i = 32; i < num_irqs; i += 32) {
mmio_write32(GIC_DIST_BASE + GICD_ICENABLER(i / 32), 0xFFFFFFFF);
}
for (uint32_t i = 0; i < num_irqs; i += 4) {
mmio_write32(GIC_DIST_BASE + GICD_IPRIORITYR(i / 4), 0xA0A0A0A0);
}
for (uint32_t i = 32; i < num_irqs; i += 4) {
mmio_write32(GIC_DIST_BASE + GICD_ITARGETSR(i / 4), 0x01010101); // Target Core 0
}
// 3. Enable Distributor
mmio_write32(GIC_DIST_BASE + GICD_CTLR, 1);
// 4. Enable CPU Interface
mmio_write32(GIC_CPU_BASE + GICC_PMR, 0xF0); // Priority mask
mmio_write32(GIC_CPU_BASE + GICC_CTLR, 1); // Enable signaling
printk(KERN_INFO "GIC: ARM Generic Interrupt Controller v2 initialized (%u IRQ lines)\n", num_irqs);
}
void gic_enable_irq(uint32_t irq) {
if (irq >= MAX_GIC_IRQS) return;
mmio_write32(GIC_DIST_BASE + GICD_ISENABLER(irq / 32), (1U << (irq % 32)));
}
void gic_disable_irq(uint32_t irq) {
if (irq >= MAX_GIC_IRQS) return;
mmio_write32(GIC_DIST_BASE + GICD_ICENABLER(irq / 32), (1U << (irq % 32)));
}
void gic_register_handler(uint32_t irq, gic_irq_handler_t handler) {
if (irq < MAX_GIC_IRQS) {
irq_handlers[irq] = handler;
gic_enable_irq(irq);
}
}
void gic_handle_irq(void) {
uint32_t iar = mmio_read32(GIC_CPU_BASE + GICC_IAR);
uint32_t irq = iar & 0x3FF;
if (irq < 1020) {
if (irq_handlers[irq]) {
irq_handlers[irq](irq);
}
// End of Interrupt (EOIR)
mmio_write32(GIC_CPU_BASE + GICC_EOIR, iar);
}
}
+49
View File
@@ -0,0 +1,49 @@
/*
* SUB-OS kernel context switch (AArch64)
* --------------------------------------
* void sub_ctx_switch(void** save_sp, void* load_sp)
* x0 = save_sp, x1 = load_sp
*
* Saves the AAPCS64 callee-saved registers (x19-x28) plus the frame pointer
* (x29) and link register (x30) of the outgoing thread, records its stack
* pointer through *save_sp, then adopts the incoming thread's stack and
* restores the mirror image. Returns into whoever is switched in.
*
* NOTE: implemented for parity with x86_64 and to keep the shared scheduler
* building on this target; not yet exercised on hardware/QEMU for aarch64.
*/
.text
.globl sub_ctx_switch
.globl sub_thread_trampoline
sub_ctx_switch:
sub sp, sp, #(12 * 8)
stp x19, x20, [sp, #0]
stp x21, x22, [sp, #16]
stp x23, x24, [sp, #32]
stp x25, x26, [sp, #48]
stp x27, x28, [sp, #64]
stp x29, x30, [sp, #80] /* fp, lr */
mov x2, sp
str x2, [x0] /* *save_sp = outgoing SP */
mov sp, x1 /* adopt incoming SP */
ldp x19, x20, [sp, #0]
ldp x21, x22, [sp, #16]
ldp x23, x24, [sp, #32]
ldp x25, x26, [sp, #48]
ldp x27, x28, [sp, #64]
ldp x29, x30, [sp, #80]
add sp, sp, #(12 * 8)
ret
/* Fresh-thread trampoline: x19 = entry, x20 = argument (forged by task_create). */
sub_thread_trampoline:
mov x0, x20 /* arg */
blr x19 /* entry(arg) */
mov x0, #0
bl task_exit
1: wfi
b 1b
+49
View File
@@ -0,0 +1,49 @@
#include <arch/aarch64/timer.h>
#include <arch/aarch64/gic.h>
#include <kernel/timer.h>
#include <kernel/printk.h>
static uint64_t timer_ticks = 0;
static uint64_t timer_freq = 62500000; // Standard 62.5MHz QEMU virt frequency
static uint32_t tick_interval = 0;
void aarch64_timer_handle_irq(void) {
timer_ticks++;
// Reset next comparator interval
__asm__ volatile("msr cntv_tval_el0, %0" :: "r"((uint64_t)tick_interval));
// Call generic kernel timer tick
timer_tick();
}
static void gic_timer_wrapper(uint32_t irq) {
(void)irq;
aarch64_timer_handle_irq();
}
void aarch64_timer_init(uint32_t freq_hz) {
if (freq_hz == 0) freq_hz = 100;
__asm__ volatile("mrs %0, cntfrq_el0" : "=r"(timer_freq));
tick_interval = (uint32_t)(timer_freq / freq_hz);
// Register with GIC (PPI 27 = Virtual Timer IRQ)
gic_register_handler(ARCH_TIMER_VIRT_IRQ, gic_timer_wrapper);
// Set countdown and enable virtual timer
__asm__ volatile("msr cntv_tval_el0, %0" :: "r"((uint64_t)tick_interval));
uint64_t ctl = 1; // Enable=1, IMASK=0
__asm__ volatile("msr cntv_ctl_el0, %0" :: "r"(ctl));
printk(KERN_INFO "Timer: ARM Generic Arch Timer active (%u Hz, Base Clock: %llu MHz)\n",
freq_hz, timer_freq / 1000000);
}
uint64_t aarch64_timer_get_ticks(void) {
return timer_ticks;
}
uint64_t aarch64_timer_get_freq(void) {
return timer_freq;
}
+117
View File
@@ -0,0 +1,117 @@
/*
* SUB-OS AArch64 Exception Vector Table
* 16 vectors aligned on 128-byte (0x80) boundaries
*/
.macro SAVE_ALL
sub sp, sp, #272
stp x0, x1, [sp, #0]
stp x2, x3, [sp, #16]
stp x4, x5, [sp, #32]
stp x6, x7, [sp, #48]
stp x8, x9, [sp, #64]
stp x10, x11, [sp, #80]
stp x12, x13, [sp, #96]
stp x14, x15, [sp, #112]
stp x16, x17, [sp, #128]
stp x18, x19, [sp, #144]
stp x20, x21, [sp, #160]
stp x22, x23, [sp, #176]
stp x24, x25, [sp, #192]
stp x26, x27, [sp, #208]
stp x28, x29, [sp, #224]
mrs x0, elr_el1
mrs x1, spsr_el1
stp x30, x0, [sp, #240]
str x1, [sp, #256]
.endm
.macro RESTORE_ALL
ldr x1, [sp, #256]
msr spsr_el1, x1
ldp x30, x0, [sp, #240]
msr elr_el1, x0
ldp x28, x29, [sp, #224]
ldp x26, x27, [sp, #208]
ldp x24, x25, [sp, #192]
ldp x22, x23, [sp, #176]
ldp x20, x21, [sp, #160]
ldp x18, x19, [sp, #144]
ldp x16, x17, [sp, #128]
ldp x14, x15, [sp, #112]
ldp x12, x13, [sp, #96]
ldp x10, x11, [sp, #80]
ldp x8, x9, [sp, #64]
ldp x6, x7, [sp, #48]
ldp x4, x5, [sp, #32]
ldp x2, x3, [sp, #16]
ldp x0, x1, [sp, #0]
add sp, sp, #272
eret
.endm
.section ".vectors", "ax"
.align 11
.global vectors
vectors:
// 1. Current EL with SP0
.align 7; b handle_sync_curr_sp0
.align 7; b handle_irq_curr_sp0
.align 7; b handle_fiq_curr_sp0
.align 7; b handle_serror_curr_sp0
// 2. Current EL with SPx
.align 7; b handle_sync_curr_spx
.align 7; b handle_irq_curr_spx
.align 7; b handle_fiq_curr_spx
.align 7; b handle_serror_curr_spx
// 3. Lower EL using AArch64
.align 7; b handle_sync_lower_aarch64
.align 7; b handle_irq_lower_aarch64
.align 7; b handle_fiq_lower_aarch64
.align 7; b handle_serror_lower_aarch64
// 4. Lower EL using AArch32
.align 7; b handle_sync_lower_aarch32
.align 7; b handle_irq_lower_aarch32
.align 7; b handle_fiq_lower_aarch32
.align 7; b handle_serror_lower_aarch32
// Common Vector Handlers
handle_sync_curr_sp0:
handle_sync_curr_spx:
handle_sync_lower_aarch64:
handle_sync_lower_aarch32:
SAVE_ALL
mov x0, sp
bl aarch64_sync_handler
RESTORE_ALL
handle_irq_curr_sp0:
handle_irq_curr_spx:
handle_irq_lower_aarch64:
handle_irq_lower_aarch32:
SAVE_ALL
mov x0, sp
bl aarch64_irq_handler
RESTORE_ALL
handle_fiq_curr_sp0:
handle_fiq_curr_spx:
handle_fiq_lower_aarch64:
handle_fiq_lower_aarch32:
SAVE_ALL
mov x0, sp
bl aarch64_fiq_handler
RESTORE_ALL
handle_serror_curr_sp0:
handle_serror_curr_spx:
handle_serror_lower_aarch64:
handle_serror_lower_aarch32:
SAVE_ALL
mov x0, sp
bl aarch64_serror_handler
RESTORE_ALL
+38
View File
@@ -0,0 +1,38 @@
#include <arch/aarch64/uart.h>
#include <arch/aarch64/io.h>
void uart_pl011_init(void) {
// Enable UART, TX, and RX (bits 0, 8, 9)
mmio_write32(PL011_UART0_BASE + UARTCR, (1 << 0) | (1 << 8) | (1 << 9));
}
void uart_pl011_putc(char c) {
if (c == '\n') {
while (mmio_read32(PL011_UART0_BASE + UARTFR) & UARTFR_TXFF) {
__asm__ volatile("nop");
}
mmio_write32(PL011_UART0_BASE + UARTDR, '\r');
}
while (mmio_read32(PL011_UART0_BASE + UARTFR) & UARTFR_TXFF) {
__asm__ volatile("nop");
}
mmio_write32(PL011_UART0_BASE + UARTDR, (uint32_t)(uint8_t)c);
}
void uart_pl011_puts(const char* str) {
if (!str) return;
while (*str) {
uart_pl011_putc(*str++);
}
}
char uart_pl011_getc(void) {
while (mmio_read32(PL011_UART0_BASE + UARTFR) & UARTFR_RXFE) {
__asm__ volatile("wfe");
}
return (char)(mmio_read32(PL011_UART0_BASE + UARTDR) & 0xFF);
}
int uart_pl011_has_data(void) {
return !(mmio_read32(PL011_UART0_BASE + UARTFR) & UARTFR_RXFE);
}
+45
View File
@@ -0,0 +1,45 @@
OUTPUT_FORMAT("elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
ENTRY(_start)
SECTIONS
{
. = 0x40080000;
__kernel_start = .;
.text.boot : {
KEEP(*(.text.boot))
}
.vectors : ALIGN(2048) {
KEEP(*(.vectors))
}
.text : ALIGN(4096) {
*(.text .text.*)
}
.rodata : ALIGN(4096) {
*(.rodata .rodata.*)
}
.data : ALIGN(4096) {
*(.data .data.*)
}
.bss : ALIGN(4096) {
__bss_start = .;
*(.bss .bss.*)
*(COMMON)
. = ALIGN(4096);
__bss_end = .;
}
.stack : ALIGN(4096) {
. += 0x20000; /* 128 KB dedicated kernel stack */
stack_top = .;
}
__kernel_end = .;
}
+63
View File
@@ -0,0 +1,63 @@
#include <arch/aarch64/mmu.h>
#include <arch/aarch64/io.h>
#include <lib/string.h>
#include <kernel/printk.h>
// Level 1 Translation Table (PUD / L1, 512 x 1GB entries, root table for 39-bit VA)
static uint64_t __attribute__((aligned(4096))) l1_table[512];
// Level 2 Translation Table (PMD / L2, 512 x 2MB entries for first 1GB: 0x00000000 - 0x3FFFFFFF Device MMIO)
static uint64_t __attribute__((aligned(4096))) l2_dev_table[512];
// Level 2 Translation Table (PMD / L2, 512 x 2MB entries for second 1GB: 0x40000000 - 0x7FFFFFFF RAM)
static uint64_t __attribute__((aligned(4096))) l2_ram_table[512];
void aarch64_mmu_init(void) {
memset(l1_table, 0, sizeof(l1_table));
memset(l2_dev_table, 0, sizeof(l2_dev_table));
memset(l2_ram_table, 0, sizeof(l2_ram_table));
// L1 Table Entry 0 points to L2 Device Table (0 - 1GB)
l1_table[0] = ((uint64_t)l2_dev_table) | PTE_VALID | PTE_TABLE;
// L1 Table Entry 1 points to L2 RAM Table (1GB - 2GB: QEMU virt RAM is 0x40000000)
l1_table[1] = ((uint64_t)l2_ram_table) | PTE_VALID | PTE_TABLE;
// 1. Identity map Device MMIO (0x00000000 - 0x3FFFFFFF) as Device-nGnRnE
for (uint64_t i = 0; i < 512; i++) {
uint64_t phys = i * 0x200000; // 2MB blocks
l2_dev_table[i] = phys | PTE_VALID | PTE_AF | PTE_SH_INNER | PTE_ATTRINDX(MT_DEVICE_nGnRnE);
}
// 2. Identity map System RAM (0x40000000 - 0x7FFFFFFF, 1GB) as Normal Cacheable
for (uint64_t i = 0; i < 512; i++) {
uint64_t phys = 0x40000000 + (i * 0x200000);
l2_ram_table[i] = phys | PTE_VALID | PTE_AF | PTE_SH_INNER | PTE_ATTRINDX(MT_NORMAL_WB);
}
// 3. Set Memory Attribute Indirection Register (MAIR_EL1)
__asm__ volatile("msr mair_el1, %0" :: "r"((uint64_t)MAIR_VAL));
// 4. Configure Translation Control Register (TCR_EL1)
// T0SZ=25 (39-bit VA starting at L1 / 1GB chunks), TG0=0 (4KB granule), Inner/Outer WBWA, Inner Shareable
uint64_t tcr = (25ULL << 0) | // T0SZ = 25 (39-bit VA)
(1ULL << 8) | // IRGN0 = Normal WBWA
(1ULL << 10) | // ORGN0 = Normal WBWA
(3ULL << 12) | // SH0 = Inner Shareable
(0ULL << 14) | // TG0 = 4KB
(1ULL << 23) | // EPD1 = Disable TTBR1 translations
(2ULL << 32); // IPS = 40-bit Physical Address (1TB)
__asm__ volatile("msr tcr_el1, %0" :: "r"(tcr));
// 5. Load Translation Table Base Register 0 (TTBR0_EL1)
__asm__ volatile("msr ttbr0_el1, %0" :: "r"((uint64_t)l1_table));
isb();
// 6. Enable MMU and Caching in SCTLR_EL1 (M=1, C=1, I=1)
uint64_t sctlr;
__asm__ volatile("mrs %0, sctlr_el1" : "=r"(sctlr));
sctlr |= (1 << 0) | // M: Enable MMU
(1 << 2) | // C: Enable Data Cache
(1 << 12); // I: Enable Instruction Cache
__asm__ volatile("msr sctlr_el1, %0" :: "r"(sctlr));
isb();
}
+42
View File
@@ -0,0 +1,42 @@
#include <mm/vmspace.h>
/*
* Userland address spaces are not wired up on this architecture yet.
*
* The ELF loader and exec path are portable, but they need per-process page
* tables and a ring-3 entry path that only the x86_64 port implements today.
* These stubs fail cleanly so a userland exec reports "not supported" instead
* of silently running a program with kernel privileges.
*/
vmspace_t* vmspace_create(void) { return NULL; }
void vmspace_destroy(vmspace_t* vm) { (void)vm; }
int vmspace_map_page(vmspace_t* vm, uint64_t virt, uint64_t phys, int prot) {
(void)vm; (void)virt; (void)phys; (void)prot;
return -1;
}
int vmspace_alloc(vmspace_t* vm, uint64_t virt, size_t len, int prot) {
(void)vm; (void)virt; (void)len; (void)prot;
return -1;
}
int vmspace_write(vmspace_t* vm, uint64_t dst, const void* src, size_t len) {
(void)vm; (void)dst; (void)src; (void)len;
return -1;
}
uint64_t vmspace_resolve(vmspace_t* vm, uint64_t virt) {
(void)vm; (void)virt;
return 0;
}
void vmspace_switch(vmspace_t* vm) { (void)vm; }
bool vmspace_range_ok(vmspace_t* vm, uint64_t virt, size_t len) {
(void)vm; (void)virt; (void)len;
return false;
}
uint64_t vmspace_resident_pages(vmspace_t* vm) { (void)vm; return 0; }
+61
View File
@@ -0,0 +1,61 @@
/*
* SUB-OS ARMv8i (AArch32 / 32-Bit ARMv8) Bootstrap Entry Point
*/
.syntax unified
.arm
.section ".text.boot"
.global _start
.global armv8i_entry
_start:
armv8i_entry:
// 1. Enable VFP and NEON Coprocessors (CP10 & CP11 Full Access)
mrc p15, 0, r0, c1, c0, 2 // Read CPACR
orr r0, r0, #(0xF << 20) // Enable CP10 and CP11
mcr p15, 0, r0, c1, c0, 2 // Write CPACR
isb
// Enable VFP extension run (FPEXC.EN = 1)
mov r0, #(1 << 30)
vmsr fpexc, r0
// 2. Clear SCTLR.V bit (bit 13) to use VBAR for exception vectors
mrc p15, 0, r0, c1, c0, 0 // Read SCTLR
bic r0, r0, #(1 << 13) // Clear Hivecs (V=0)
mcr p15, 0, r0, c1, c0, 0 // Write SCTLR
isb
// 3. Set Vector Base Address Register (VBAR)
ldr r0, =vectors
mcr p15, 0, r0, c12, c0, 0 // Write VBAR
isb
// 4. Setup Banked Stack Pointers for Exception Modes
cpsid if, #0x12 // Switch to IRQ mode
ldr sp, =irq_stack_top
cpsid if, #0x17 // Switch to Abort mode
ldr sp, =abt_stack_top
cpsid if, #0x1B // Switch to Undefined mode
ldr sp, =und_stack_top
cpsid if, #0x13 // Switch back to Supervisor mode (SVC)
ldr sp, =stack_top
// 5. Zero out BSS section
ldr r0, =__bss_start
ldr r1, =__bss_end
mov r2, #0
.bss_loop:
cmp r0, r1
strlt r2, [r0], #4
blt .bss_loop
// 6. Branch to ARMv8i C Kernel Entry
bl kernel_main_armv8i
.hang:
wfi
b .hang
+37
View File
@@ -0,0 +1,37 @@
#
# Automatically generated by SUB-OS Kconfig for armv8i
#
CONFIG_ARCH=armv8i
CONFIG_ARCH_ARMV8I=y
# CONFIG_ARCH_X86_64 is not set
# CONFIG_ARCH_AARCH64 is not set
CONFIG_BPF_VM=y
CONFIG_DRV_CANVAS_2D=y
CONFIG_DRV_PTY=y
CONFIG_DRV_RAMDISK=y
CONFIG_DRV_VIRTIO_BLK=y
CONFIG_DRV_VIRTIO_NET=y
CONFIG_DRV_VIRTIO_RNG=y
CONFIG_FS_DEVFS=y
CONFIG_FS_EXT2=y
CONFIG_FS_FAT32=y
CONFIG_FS_PROCFS=y
CONFIG_FS_RAMFS=y
CONFIG_FS_SYSFS=y
CONFIG_FS_VFS=y
CONFIG_IO_URING=y
CONFIG_MM_PMM=y
CONFIG_MM_SLAB=y
CONFIG_MM_VMA=y
CONFIG_NET_FILTER=y
CONFIG_NET_HTTPD=y
CONFIG_NET_SSHD=y
CONFIG_NET_STACK=y
CONFIG_OPTIMIZATION="-O2"
CONFIG_PREEMPT=y
CONFIG_SECURITY_KEYRING=y
CONFIG_SECURITY_LSM=y
CONFIG_SMP=y
CONFIG_SUBOS_HOSTNAME="sub-os"
CONFIG_SUBOS_VERSION="0.2.0-lts"
CONFIG_USERLAND_LAZYBOX=y
+60
View File
@@ -0,0 +1,60 @@
#include <arch/arch.h>
#include <arch/armv8i/cpu.h>
#include <arch/armv8i/gic.h>
#include <arch/armv8i/timer.h>
#include <arch/armv8i/uart.h>
#include <arch/armv8i/mmu.h>
#include <arch/x86_64/cpuid.h>
#include <kernel/printk.h>
#include <lib/string.h>
static cpu_info_t armv8i_compat_cpu;
void arch_early_init(void) {
uart_pl011_init();
armv8i_mmu_init();
}
void arch_init(void) {
armv8i_cpu_init();
gic_init();
armv8i_timer_init(100);
// Populate compatibility CPU info structure for procfs / uname
memset(&armv8i_compat_cpu, 0, sizeof(armv8i_compat_cpu));
const armv8i_cpu_info_t* info = armv8i_get_cpu_info();
strncpy(armv8i_compat_cpu.vendor, "ARM Ltd", sizeof(armv8i_compat_cpu.vendor) - 1);
strncpy(armv8i_compat_cpu.model, info->model_name, sizeof(armv8i_compat_cpu.model) - 1);
armv8i_compat_cpu.has_fpu = true;
arch_enable_interrupts();
}
const char* arch_get_name(void) {
return "armv8i (AArch32)";
}
uint32_t arch_get_cpu_id(void) {
return armv8i_get_mpidr() & 0xFF;
}
// Timer Compatibility Layer
uint64_t pit_get_ticks(void) {
return armv8i_timer_get_ticks();
}
void pit_sleep(uint32_t ms) {
uint64_t count = (ms > 10) ? (ms / 10) : 1;
uint64_t target = armv8i_timer_get_ticks() + count;
while (armv8i_timer_get_ticks() < target) {
__asm__ volatile("wfi");
}
}
// Stubs for x86-specific driver compatibility
void isr_register_handler(uint8_t isr, void* handler) { (void)isr; (void)handler; }
void pic_clear_mask(uint8_t irq) { (void)irq; }
const cpu_info_t* cpuid_get_info(void) {
return &armv8i_compat_cpu;
}
+41
View File
@@ -0,0 +1,41 @@
#include <arch/armv8i/cpu.h>
#include <lib/string.h>
#include <kernel/printk.h>
static armv8i_cpu_info_t global_cpu_info;
uint32_t armv8i_get_midr(void) {
uint32_t midr;
__asm__ volatile("mrc p15, 0, %0, c0, c0, 0" : "=r"(midr));
return midr;
}
uint32_t armv8i_get_mpidr(void) {
uint32_t mpidr;
__asm__ volatile("mrc p15, 0, %0, c0, c0, 5" : "=r"(mpidr));
return mpidr;
}
void armv8i_cpu_init(void) {
memset(&global_cpu_info, 0, sizeof(global_cpu_info));
global_cpu_info.midr = armv8i_get_midr();
global_cpu_info.mpidr = armv8i_get_mpidr();
global_cpu_info.implementer = (global_cpu_info.midr >> 24) & 0xFF;
global_cpu_info.part_number = (global_cpu_info.midr >> 4) & 0xFFF;
global_cpu_info.architecture = (global_cpu_info.midr >> 16) & 0xF;
const char* part_str = "ARM Cortex-A (ARMv8i 32-Bit)";
if (global_cpu_info.part_number == 0xC0F) part_str = "ARM Cortex-A15 (ARMv7-A/v8i)";
else if (global_cpu_info.part_number == 0xC07) part_str = "ARM Cortex-A7 (ARMv7-A/v8i)";
else if (global_cpu_info.part_number == 0xD03) part_str = "ARM Cortex-A53 (AArch32)";
else if (global_cpu_info.part_number == 0xD07) part_str = "ARM Cortex-A57 (AArch32)";
strncpy(global_cpu_info.model_name, part_str, sizeof(global_cpu_info.model_name) - 1);
printk(KERN_INFO "CPU: %s (Implementer: 0x%02x, Part: 0x%03x, AArch32)\n",
global_cpu_info.model_name, global_cpu_info.implementer, global_cpu_info.part_number);
}
const armv8i_cpu_info_t* armv8i_get_cpu_info(void) {
return &global_cpu_info;
}
+16
View File
@@ -0,0 +1,16 @@
/*
* SUB-OS ARMv8i Division-by-Zero and POSIX Signal Stub
*/
#include <stdint.h>
int raise(int sig) {
(void)sig;
return 0;
}
void __aeabi_idiv0(void) {
}
void __aeabi_ldiv0(void) {
}
+68
View File
@@ -0,0 +1,68 @@
#include <arch/armv8i/arch.h>
#include <arch/armv8i/gic.h>
#include <arch/armv8i/timer.h>
#include <arch/armv8i/uart.h>
#include <kernel/printk.h>
typedef struct {
uint32_t r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12;
uint32_t lr;
uint32_t spsr;
} armv8i_registers_t;
static void hex_to_str(uint32_t val, char* out) {
const char hex_chars[] = "0123456789ABCDEF";
out[0] = '0'; out[1] = 'x';
for (int i = 7; i >= 0; i--) {
out[2 + (7 - i)] = hex_chars[(val >> (i * 4)) & 0xF];
}
out[10] = '\0';
}
void armv8i_undef_handler(armv8i_registers_t* regs) {
char buf[16];
hex_to_str(regs->lr, buf);
uart_pl011_puts("\n[KERNEL PANIC] ARMv8i Undefined Instruction at PC=");
uart_pl011_puts(buf);
uart_pl011_puts("\n");
while (1) { __asm__ volatile("wfi"); }
}
void armv8i_svc_handler(armv8i_registers_t* regs) {
(void)regs;
}
void armv8i_pabt_handler(armv8i_registers_t* regs) {
char buf[16];
hex_to_str(regs->lr, buf);
uart_pl011_puts("\n[KERNEL PANIC] ARMv8i Prefetch Abort at PC=");
uart_pl011_puts(buf);
uart_pl011_puts("\n");
while (1) { __asm__ volatile("wfi"); }
}
void armv8i_dabt_handler(armv8i_registers_t* regs) {
char buf[16];
hex_to_str(regs->lr, buf);
uart_pl011_puts("\n[KERNEL PANIC] ARMv8i Data Abort at PC=");
uart_pl011_puts(buf);
uart_pl011_puts("\n");
while (1) { __asm__ volatile("wfi"); }
}
void armv8i_irq_handler(armv8i_registers_t* regs) {
(void)regs;
uint32_t iar = *(volatile uint32_t*)GICC_IAR;
uint32_t irq = iar & 0x3FF;
if (irq < 1020) {
if (irq == ARMV8I_TIMER_VIRT_IRQ) {
armv8i_timer_handle_irq();
}
*(volatile uint32_t*)GICC_EOIR = iar;
}
}
void armv8i_fiq_handler(armv8i_registers_t* regs) {
(void)regs;
}
+62
View File
@@ -0,0 +1,62 @@
#include <arch/armv8i/gic.h>
#include <arch/armv8i/io.h>
#include <kernel/printk.h>
void gic_init(void) {
// 1. Disable Distributor and CPU interface during configuration
mmio_write32(GICD_CTLR, 0);
mmio_write32(GICC_CTLR, 0);
// 2. Query lines count (TYPER ITLinesNumber)
uint32_t typer = mmio_read32(GICD_TYPER);
uint32_t lines = 32 * ((typer & 0x1F) + 1);
// 3. Disable all interrupts & clear pending
for (uint32_t i = 0; i < lines / 32; i++) {
mmio_write32(GICD_ICENABLER(i), 0xFFFFFFFF);
mmio_write32(GICD_ICPENDR(i), 0xFFFFFFFF);
}
// 4. Default priority 0xA0 and target CPU 0 (0x01)
for (uint32_t i = 0; i < lines / 4; i++) {
mmio_write32(GICD_IPRIORITYR(i), 0xA0A0A0A0);
}
for (uint32_t i = 8; i < lines / 4; i++) {
mmio_write32(GICD_ITARGETSR(i), 0x01010101);
}
// 5. Enable Distributor (Group 0 & 1)
mmio_write32(GICD_CTLR, 3);
// 6. Set CPU Interface Priority Mask to allow all interrupts (0xF0)
mmio_write32(GICC_PMR, 0xF0);
// 7. Enable CPU Interface (Group 0 & 1)
mmio_write32(GICC_CTLR, 3);
// 8. Unmask Virtual Timer PPI 27
gic_enable_irq(27);
printk(KERN_INFO "GIC: ARM Generic Interrupt Controller v2 initialized (%u IRQ lines)\n", lines);
}
void gic_enable_irq(uint32_t irq) {
uint32_t reg_idx = irq / 32;
uint32_t bit_idx = irq % 32;
mmio_write32(GICD_ISENABLER(reg_idx), (1 << bit_idx));
}
void gic_disable_irq(uint32_t irq) {
uint32_t reg_idx = irq / 32;
uint32_t bit_idx = irq % 32;
mmio_write32(GICD_ICENABLER(reg_idx), (1 << bit_idx));
}
void gic_set_priority(uint32_t irq, uint8_t prio) {
uint32_t reg_idx = irq / 4;
uint32_t shift = (irq % 4) * 8;
uint32_t val = mmio_read32(GICD_IPRIORITYR(reg_idx));
val &= ~(0xFF << shift);
val |= ((uint32_t)prio << shift);
mmio_write32(GICD_IPRIORITYR(reg_idx), val);
}
+34
View File
@@ -0,0 +1,34 @@
/*
* SUB-OS kernel context switch (ARMv8i / AArch32)
* -----------------------------------------------
* void sub_ctx_switch(void** save_sp, void* load_sp)
* r0 = save_sp, r1 = load_sp
*
* Saves the AAPCS callee-saved registers (r4-r11) plus the link register of
* the outgoing thread, records its stack pointer through *save_sp, then adopts
* the incoming thread's stack and restores the mirror image.
*
* NOTE: implemented for parity with x86_64 and to keep the shared scheduler
* building on this target; not yet exercised on hardware/QEMU for armv8i.
*/
.text
.syntax unified
.arm
.globl sub_ctx_switch
.globl sub_thread_trampoline
sub_ctx_switch:
push {r4-r11, lr}
str sp, [r0] @ *save_sp = outgoing SP
mov sp, r1 @ adopt incoming SP
pop {r4-r11, lr}
bx lr
/* Fresh-thread trampoline: r4 = entry, r5 = argument (forged by task_create). */
sub_thread_trampoline:
mov r0, r5 @ arg
blx r4 @ entry(arg)
mov r0, #0
bl task_exit
1: b 1b
+45
View File
@@ -0,0 +1,45 @@
#include <arch/armv8i/timer.h>
#include <arch/armv8i/gic.h>
#include <kernel/timer.h>
#include <kernel/printk.h>
static volatile uint64_t armv8i_timer_ticks = 0;
static uint32_t timer_freq = 0;
static uint32_t tick_interval = 0;
uint64_t armv8i_timer_get_ticks(void) {
return armv8i_timer_ticks;
}
uint64_t armv8i_timer_get_frequency(void) {
return timer_freq;
}
void armv8i_timer_handle_irq(void) {
armv8i_timer_ticks++;
// Reset comparator for next tick
__asm__ volatile("mcr p15, 0, %0, c14, c3, 0" :: "r"(tick_interval));
timer_tick();
}
void armv8i_timer_init(uint32_t freq_hz) {
// 1. Read base counter frequency (CNTFRQ)
__asm__ volatile("mrc p15, 0, %0, c14, c0, 0" : "=r"(timer_freq));
if (timer_freq == 0) {
timer_freq = 62500000; // 62.5 MHz default in QEMU virt
}
tick_interval = timer_freq / (freq_hz ? freq_hz : 100);
// 2. Set interval in CNTV_TVAL
__asm__ volatile("mcr p15, 0, %0, c14, c3, 0" :: "r"(tick_interval));
// 3. Enable Virtual Timer (ENABLE=1, IMASK=0 in CNTV_CTL)
uint32_t ctl = 1;
__asm__ volatile("mcr p15, 0, %0, c14, c3, 1" :: "r"(ctl));
printk(KERN_INFO "Timer: ARM Generic Arch Timer active (%u Hz, Base Clock: %u MHz)\n",
freq_hz, timer_freq / 1000000);
}
+87
View File
@@ -0,0 +1,87 @@
/*
* SUB-OS ARMv8i (AArch32) Exception Vector Table
*/
.syntax unified
.arm
.section ".vectors", "ax"
.align 5
.global vectors
vectors:
ldr pc, =reset_handler
ldr pc, =undef_handler
ldr pc, =svc_handler
ldr pc, =pabt_handler
ldr pc, =dabt_handler
ldr pc, =hyp_handler
ldr pc, =irq_handler
ldr pc, =fiq_handler
.macro SAVE_REGS
sub sp, sp, #72
stmia sp, {r0 - r12}
str lr, [sp, #52]
mrs r0, spsr
str r0, [sp, #56]
.endm
.macro RESTORE_REGS
ldr r0, [sp, #56]
msr spsr_cxsf, r0
ldr lr, [sp, #52]
ldmia sp, {r0 - r12}
add sp, sp, #72
.endm
reset_handler:
b _start
undef_handler:
SAVE_REGS
mov r0, sp
bl armv8i_undef_handler
RESTORE_REGS
movs pc, lr
svc_handler:
SAVE_REGS
mov r0, sp
bl armv8i_svc_handler
RESTORE_REGS
movs pc, lr
pabt_handler:
sub lr, lr, #4
SAVE_REGS
mov r0, sp
bl armv8i_pabt_handler
RESTORE_REGS
movs pc, lr
dabt_handler:
sub lr, lr, #8
SAVE_REGS
mov r0, sp
bl armv8i_dabt_handler
RESTORE_REGS
movs pc, lr
hyp_handler:
b .
irq_handler:
sub lr, lr, #4
SAVE_REGS
mov r0, sp
bl armv8i_irq_handler
RESTORE_REGS
movs pc, lr
fiq_handler:
sub lr, lr, #4
SAVE_REGS
mov r0, sp
bl armv8i_fiq_handler
RESTORE_REGS
movs pc, lr
+38
View File
@@ -0,0 +1,38 @@
#include <arch/armv8i/uart.h>
#include <arch/armv8i/io.h>
void uart_pl011_init(void) {
// Enable UART, TX, and RX (bits 0, 8, 9)
mmio_write32(PL011_UART0_BASE + UARTCR, (1 << 0) | (1 << 8) | (1 << 9));
}
void uart_pl011_putc(char c) {
if (c == '\n') {
while (mmio_read32(PL011_UART0_BASE + UARTFR) & UARTFR_TXFF) {
__asm__ volatile("nop");
}
mmio_write32(PL011_UART0_BASE + UARTDR, '\r');
}
while (mmio_read32(PL011_UART0_BASE + UARTFR) & UARTFR_TXFF) {
__asm__ volatile("nop");
}
mmio_write32(PL011_UART0_BASE + UARTDR, (uint32_t)(uint8_t)c);
}
void uart_pl011_puts(const char* str) {
if (!str) return;
while (*str) {
uart_pl011_putc(*str++);
}
}
char uart_pl011_getc(void) {
while (mmio_read32(PL011_UART0_BASE + UARTFR) & UARTFR_RXFE) {
__asm__ volatile("wfe");
}
return (char)(mmio_read32(PL011_UART0_BASE + UARTDR) & 0xFF);
}
int uart_pl011_has_data(void) {
return !(mmio_read32(PL011_UART0_BASE + UARTFR) & UARTFR_RXFE);
}
+51
View File
@@ -0,0 +1,51 @@
OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x40080000;
__kernel_start = .;
.text.boot : {
KEEP(*(.text.boot))
}
.vectors : ALIGN(2048) {
KEEP(*(.vectors))
}
.text : ALIGN(4096) {
*(.text .text.*)
}
.rodata : ALIGN(4096) {
*(.rodata .rodata.*)
}
.data : ALIGN(4096) {
*(.data .data.*)
}
.bss : ALIGN(4096) {
__bss_start = .;
*(.bss .bss.*)
*(COMMON)
. = ALIGN(4096);
__bss_end = .;
}
.stack : ALIGN(4096) {
. += 0x4000; /* 16 KB IRQ Stack */
irq_stack_top = .;
. += 0x2000; /* 8 KB ABT Stack */
abt_stack_top = .;
. += 0x2000; /* 8 KB UND Stack */
und_stack_top = .;
. += 0x10000; /* 64 KB SVC Kernel Stack */
stack_top = .;
}
__kernel_end = .;
}
+42
View File
@@ -0,0 +1,42 @@
#include <arch/armv8i/mmu.h>
#include <arch/armv8i/io.h>
#include <lib/string.h>
#include <kernel/printk.h>
static uint32_t __attribute__((aligned(16384))) l1_page_table[4096];
void armv8i_mmu_init(void) {
// 1. Identity map the entire 4GB flat space with 1MB sections
for (uint32_t i = 0; i < 4096; i++) {
uint32_t phys = i * 0x100000;
l1_page_table[i] = phys | (3 << 10) | (2 << 0);
}
// 2. Set Domain Access Control Register (DACR = 0xFFFFFFFF: All Manager)
uint32_t dacr = 0xFFFFFFFF;
__asm__ volatile("mcr p15, 0, %0, c3, c0, 0" :: "r"(dacr));
// 3. Set Translation Table Base Control Register (TTBCR = 0)
uint32_t ttbcr = 0;
__asm__ volatile("mcr p15, 0, %0, c2, c0, 2" :: "r"(ttbcr));
// 4. Set Translation Table Base Register 0 (TTBR0)
uint32_t ttbr0 = (uint32_t)l1_page_table;
__asm__ volatile("mcr p15, 0, %0, c2, c0, 0" :: "r"(ttbr0));
// 5. Invalidate TLB & ICache
uint32_t zero = 0;
__asm__ volatile("mcr p15, 0, %0, c8, c7, 0" :: "r"(zero));
__asm__ volatile("mcr p15, 0, %0, c7, c5, 0" :: "r"(zero));
isb();
dsb();
// 6. Enable MMU and Branch Prediction in SCTLR
uint32_t sctlr;
__asm__ volatile("mrc p15, 0, %0, c1, c0, 0" : "=r"(sctlr));
sctlr |= (1 << 0) | (1 << 11);
__asm__ volatile("mcr p15, 0, %0, c1, c0, 0" :: "r"(sctlr));
isb();
dsb();
}
+42
View File
@@ -0,0 +1,42 @@
#include <mm/vmspace.h>
/*
* Userland address spaces are not wired up on this architecture yet.
*
* The ELF loader and exec path are portable, but they need per-process page
* tables and a ring-3 entry path that only the x86_64 port implements today.
* These stubs fail cleanly so a userland exec reports "not supported" instead
* of silently running a program with kernel privileges.
*/
vmspace_t* vmspace_create(void) { return NULL; }
void vmspace_destroy(vmspace_t* vm) { (void)vm; }
int vmspace_map_page(vmspace_t* vm, uint64_t virt, uint64_t phys, int prot) {
(void)vm; (void)virt; (void)phys; (void)prot;
return -1;
}
int vmspace_alloc(vmspace_t* vm, uint64_t virt, size_t len, int prot) {
(void)vm; (void)virt; (void)len; (void)prot;
return -1;
}
int vmspace_write(vmspace_t* vm, uint64_t dst, const void* src, size_t len) {
(void)vm; (void)dst; (void)src; (void)len;
return -1;
}
uint64_t vmspace_resolve(vmspace_t* vm, uint64_t virt) {
(void)vm; (void)virt;
return 0;
}
void vmspace_switch(vmspace_t* vm) { (void)vm; }
bool vmspace_range_ok(vmspace_t* vm, uint64_t virt, size_t len) {
(void)vm; (void)virt; (void)len;
return false;
}
uint64_t vmspace_resident_pages(vmspace_t* vm) { (void)vm; return 0; }
+45
View File
@@ -0,0 +1,45 @@
#
# Automatically generated by SUB-OS Kconfig for x86_64
#
CONFIG_ARCH=x86_64
CONFIG_ARCH_X86_64=y
CONFIG_BPF_VM=y
CONFIG_DRV_AHCI=y
CONFIG_DRV_ATA=y
CONFIG_DRV_BOCHS_VBE=y
CONFIG_DRV_CANVAS_2D=y
CONFIG_DRV_E1000=y
CONFIG_DRV_HWMON=y
CONFIG_DRV_NVME=y
CONFIG_DRV_PTY=y
CONFIG_DRV_RAMDISK=y
CONFIG_DRV_RTL8139=y
CONFIG_DRV_SOUND_AC97=y
CONFIG_DRV_SOUND_HDA=y
CONFIG_DRV_USB_XHCI=y
CONFIG_DRV_VIRTIO_BLK=y
CONFIG_DRV_VIRTIO_NET=y
CONFIG_DRV_VIRTIO_RNG=y
CONFIG_FS_DEVFS=y
CONFIG_FS_EXT2=y
CONFIG_FS_FAT32=y
CONFIG_FS_PROCFS=y
CONFIG_FS_RAMFS=y
CONFIG_FS_SYSFS=y
CONFIG_FS_VFS=y
CONFIG_IO_URING=y
CONFIG_MM_PMM=y
CONFIG_MM_SLAB=y
CONFIG_MM_VMA=y
CONFIG_NET_FILTER=y
CONFIG_NET_HTTPD=y
CONFIG_NET_SSHD=y
CONFIG_NET_STACK=y
CONFIG_OPTIMIZATION="-O2"
CONFIG_PREEMPT=y
CONFIG_SECURITY_KEYRING=y
CONFIG_SECURITY_LSM=y
CONFIG_SMP=y
CONFIG_SUBOS_HOSTNAME="sub-os"
CONFIG_SUBOS_VERSION="0.2.0-lts"
CONFIG_USERLAND_LAZYBOX=y
+45
View File
@@ -0,0 +1,45 @@
#include <arch/arch.h>
#include <arch/x86_64/gdt.h>
#include <arch/x86_64/idt.h>
#include <arch/x86_64/isr.h>
#include <arch/x86_64/pic.h>
#include <arch/x86_64/pit.h>
#include <arch/x86_64/cpuid.h>
#include <kernel/timer.h>
static void enable_fpu_sse(void) {
uint64_t cr0, cr4;
__asm__ volatile ("mov %%cr0, %0" : "=r"(cr0));
cr0 &= ~(1ULL << 2); // Clear EM (x87 emulation)
cr0 |= (1ULL << 1); // Set MP (Monitor co-processor)
__asm__ volatile ("mov %0, %%cr0" :: "r"(cr0));
__asm__ volatile ("mov %%cr4, %0" : "=r"(cr4));
cr4 |= (1ULL << 9) | (1ULL << 10); // Set OSFXSR (bit 9) and OSXMMEXCPT (bit 10)
__asm__ volatile ("mov %0, %%cr4" :: "r"(cr4));
}
void arch_early_init(void) {
// x86_64 serial and tty initialized in main
}
void arch_init(void) {
enable_fpu_sse();
gdt_init();
idt_init();
isr_init();
pic_init();
pit_init(100);
timer_wheel_init();
arch_enable_interrupts();
}
const char* arch_get_name(void) {
return "x86_64 (AMD64)";
}
uint32_t arch_get_cpu_id(void) {
uint32_t eax = 0, ebx = 0, ecx = 0, edx = 0;
cpuid(1, &eax, &ebx, &ecx, &edx);
return (ebx >> 24) & 0xFF;
}
+27
View File
@@ -3,6 +3,8 @@
#include <arch/x86_64/pic.h>
#include <kernel/printk.h>
#include <kernel/panic.h>
#include <kernel/exec.h>
#include <kernel/sched.h>
#include <lib/string.h>
extern void* isr_stub_table[48];
@@ -54,6 +56,26 @@ void isr_unregister_handler(uint8_t int_no) {
void isr_handler_common(registers_t* regs) {
if (regs->int_no < 32) {
/* A page fault reports the offending address in CR2; everything else
* is best described by where it was executing. */
uint64_t fault_addr = regs->rip;
if (regs->int_no == 14) {
__asm__ volatile("mov %%cr2, %0" : "=r"(fault_addr));
}
/*
* CS still carries the privilege level the fault was taken from. A
* ring 3 fault is the process's problem, not the kernel's: report it
* and tear that process down instead of bringing the machine down
* with it.
*/
if ((regs->cs & 3) == 3 && uproc_current()) {
printk(KERN_ERR "\n[USER FAULT] Vector %llu: %s (error 0x%llx) at rip=0x%llx\n",
regs->int_no, exception_messages[regs->int_no],
regs->error_code, regs->rip);
uproc_fault(exception_messages[regs->int_no], fault_addr);
}
// CPU Exception Fault
printk(KERN_EMERG "\n[EXCEPTION] Vector %llu: %s (Error code: 0x%llx)\n",
regs->int_no, exception_messages[regs->int_no], regs->error_code);
@@ -67,6 +89,11 @@ void isr_handler_common(registers_t* regs) {
// Acknowledge Hardware PIC Interrupts (IRQs 32-47)
if (regs->int_no >= 32 && regs->int_no < 48) {
pic_send_eoi((uint8_t)(regs->int_no - 32));
/* Preemption point. The EOI is already out, so the PIC will keep
* delivering to whichever thread we switch into. Switching here rather
* than inside the handler keeps the tick path re-entrancy-safe. */
sched_preempt_on_return();
}
}
+9
View File
@@ -3,6 +3,7 @@
extern isr_handler_common
global isr_stub_table
global isr_stub_default
global isr_stub_syscall
%macro ISR_NOERRCODE 1
isr_stub_%1:
@@ -71,6 +72,14 @@ ISR_NOERRCODE 45
ISR_NOERRCODE 46
ISR_NOERRCODE 47
; Software syscall gate. Reached by INT 0x80 from ring 3, so its IDT entry is
; installed with DPL 3; the CPU switches to TSS.rsp0 on the way in, which the
; exec path points at a kernel stack owned by the running process.
isr_stub_syscall:
push qword 0 ; No error code is pushed for a software interrupt
push qword 128 ; Vector number, matching the IDT slot
jmp isr_common_stub
isr_stub_default:
push qword 0
push qword 255
+55
View File
@@ -0,0 +1,55 @@
[BITS 64]
default rel
; SUB-OS kernel context switch (x86_64)
; -------------------------------------
; sub_ctx_switch(void** save_sp /* rdi */, void* load_sp /* rsi */)
;
; Saves the SysV callee-saved registers plus RFLAGS of the outgoing thread onto
; its own stack, stashes the resulting stack pointer through *save_sp, then
; loads the incoming thread's stack pointer and unwinds the mirror image. From
; C this looks like an ordinary call that "returns" only when this thread is
; later switched back in. Caller-saved registers are the C caller's problem, as
; usual, so nothing else needs preserving here.
global sub_ctx_switch
global sub_thread_trampoline
extern task_exit
section .text
sub_ctx_switch:
push rbp
push rbx
push r12
push r13
push r14
push r15
pushfq
mov [rdi], rsp ; *save_sp = outgoing stack pointer
mov rsp, rsi ; adopt the incoming stack
popfq
pop r15
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
; First-run trampoline. task_create() forges an initial frame so that the very
; first switch into a fresh thread lands here with r12 = entry, r13 = argument.
; We call the entry function, and if it ever returns we retire the thread.
sub_thread_trampoline:
mov rdi, r13 ; arg
call r12 ; entry(arg)
xor rdi, rdi ; void entry -> exit code 0
call task_exit
.hang:
hlt
jmp .hang
section .note.GNU-stack noalloc noexec nowrite progbits
+89
View File
@@ -0,0 +1,89 @@
[BITS 64]
default rel
; Ring 3 entry and exit for SUB-OS.
;
; user_enter() does not behave like a normal call: it drops to ring 3 and only
; comes back when the process leaves through user_return(), which unwinds
; straight to the frame saved here. From C it therefore looks like a call that
; blocks until the process exits and then returns its exit code.
global user_enter
global user_return
section .text
; int64_t user_enter(uint64_t entry /* rdi */, uint64_t user_rsp /* rsi */)
user_enter:
cli
; Preserve the SysV callee-saved set so user_return can rebuild this frame.
push rbx
push rbp
push r12
push r13
push r14
push r15
mov [kernel_resume_rsp], rsp
; Ring 3 data selectors: user data descriptor (0x20) with RPL 3.
mov ax, 0x23
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; Build the iretq frame: SS, RSP, RFLAGS, CS, RIP.
push 0x23 ; SS = user data, RPL 3
push rsi ; RSP = top of the user stack
push 0x202 ; RFLAGS with IF set, everything else clear
push 0x1B ; CS = user code (0x18), RPL 3
push rdi ; RIP = ELF entry point
; Do not leak kernel register contents across the privilege boundary.
xor rax, rax
xor rbx, rbx
xor rcx, rcx
xor rdx, rdx
xor rsi, rsi
xor rdi, rdi
xor rbp, rbp
xor r8, r8
xor r9, r9
xor r10, r10
xor r11, r11
xor r12, r12
xor r13, r13
xor r14, r14
xor r15, r15
iretq
; void user_return(int64_t status /* rdi */) -- never returns to its caller.
user_return:
cli
mov rsp, [kernel_resume_rsp]
mov rax, rdi ; becomes user_enter's return value
; Restore the kernel's own data selectors before touching kernel memory.
push rax
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
pop rax
pop r15
pop r14
pop r13
pop r12
pop rbp
pop rbx
sti
ret
section .bss
align 8
kernel_resume_rsp:
resq 1
+239
View File
@@ -0,0 +1,239 @@
#include <mm/vmspace.h>
#include <mm/pmm.h>
#include <mm/kmalloc.h>
#include <arch/x86_64/paging.h>
#include <kernel/printk.h>
#include <lib/string.h>
/*
* x86_64 per-process address spaces.
*
* The boot page tables identity-map the low 4 GB, so within that window a
* physical address doubles as a kernel-usable pointer: page tables can be
* edited and user pages seeded without any temporary mapping window.
*
* The PMM allocates linearly from physical zero, so on any machine whose
* usable memory fits under 4 GB every frame it returns is reachable that way.
* On a larger machine it can return a frame above the window, which this file
* has no way to touch -- those are rejected outright rather than silently
* writing through an unmapped pointer. Lifting that needs either a recursive
* page-table mapping or a higher-half direct map.
*/
#define KERNEL_PML4_PHYS 0x1000UL
#define PTE_ADDR_MASK (~0xFFFULL)
#define ENTRIES_PER_TABLE 512
/* Upper bound of the boot identity map; see the file comment. */
#define IDENTITY_MAP_LIMIT (4ULL << 30)
struct vmspace {
uint64_t pml4_phys;
uint64_t resident_pages; /* data pages, excluding page tables */
uint64_t table_pages;
};
static inline uint64_t* table_at(uint64_t phys) {
return (uint64_t*)(uintptr_t)phys;
}
static uint64_t arch_flags_of(int prot) {
uint64_t flags = PAGE_PRESENT;
if (prot & VM_PROT_WRITE) flags |= PAGE_WRITABLE;
if (prot & VM_PROT_USER) flags |= PAGE_USER;
/* NX is only safe to set once EFER.NXE is on; user text needs it clear
* anyway, so non-executable pages are simply left W^X-less for now. */
return flags;
}
/* Take a zeroed frame that the kernel can actually address, or NULL. */
static void* alloc_identity_frame(void) {
void* page = pmm_alloc_page();
if (!page) return NULL;
if ((uint64_t)(uintptr_t)page >= IDENTITY_MAP_LIMIT) {
pmm_free_page(page);
printk(KERN_ERR "vmspace: frame at 0x%llx is outside the identity map\n",
(unsigned long long)(uintptr_t)page);
return NULL;
}
memset(page, 0, VM_PAGE_SIZE);
return page;
}
static uint64_t* alloc_table(vmspace_t* vm) {
void* page = alloc_identity_frame();
if (!page) return NULL;
if (vm) vm->table_pages++;
return (uint64_t*)page;
}
vmspace_t* vmspace_create(void) {
vmspace_t* vm = (vmspace_t*)kzalloc(sizeof(vmspace_t));
if (!vm) return NULL;
uint64_t* pml4 = alloc_table(vm);
if (!pml4) {
kfree(vm);
return NULL;
}
/*
* Share the kernel's PML4[0] verbatim. That entry covers the whole low
* 512 GB, which is where the identity-mapped kernel, its heap and every
* MMIO window live, so ring 0 keeps working immediately after the CR3
* load. Userland gets its own slot and never aliases kernel memory.
*/
uint64_t* kernel_pml4 = table_at(KERNEL_PML4_PHYS);
pml4[0] = kernel_pml4[0];
vm->pml4_phys = (uint64_t)(uintptr_t)pml4;
return vm;
}
/* Walk to the PTE for `virt`, optionally creating the intermediate tables. */
static uint64_t* walk(vmspace_t* vm, uint64_t virt, bool create) {
uint64_t idx[4] = {
(virt >> 39) & 0x1FF,
(virt >> 30) & 0x1FF,
(virt >> 21) & 0x1FF,
(virt >> 12) & 0x1FF,
};
uint64_t* table = table_at(vm->pml4_phys);
for (int level = 0; level < 3; level++) {
uint64_t entry = table[idx[level]];
if (!(entry & PAGE_PRESENT)) {
if (!create) return NULL;
uint64_t* next = alloc_table(vm);
if (!next) return NULL;
/* Every level above the leaf must allow user access, or the
* final PAGE_USER bit is overridden by the walk. */
entry = (uint64_t)(uintptr_t)next | PAGE_PRESENT | PAGE_WRITABLE | PAGE_USER;
table[idx[level]] = entry;
} else if (entry & PAGE_HUGE) {
/* A large mapping already covers this range; refuse rather than
* silently splitting the kernel's identity map. */
return NULL;
}
table = table_at(entry & PTE_ADDR_MASK);
}
return &table[idx[3]];
}
int vmspace_map_page(vmspace_t* vm, uint64_t virt, uint64_t phys, int prot) {
if (!vm) return -1;
if (virt < USER_BASE || virt >= USER_LIMIT) return -1;
uint64_t* pte = walk(vm, virt & ~(VM_PAGE_SIZE - 1), true);
if (!pte) return -1;
if (!(*pte & PAGE_PRESENT)) vm->resident_pages++;
*pte = (phys & PTE_ADDR_MASK) | arch_flags_of(prot);
return 0;
}
int vmspace_alloc(vmspace_t* vm, uint64_t virt, size_t len, int prot) {
if (!vm || len == 0) return -1;
uint64_t start = virt & ~(VM_PAGE_SIZE - 1);
uint64_t end = (virt + len + VM_PAGE_SIZE - 1) & ~(VM_PAGE_SIZE - 1);
if (end <= start) return -1;
for (uint64_t page = start; page < end; page += VM_PAGE_SIZE) {
uint64_t* pte = walk(vm, page, true);
if (!pte) return -1;
if (*pte & PAGE_PRESENT) {
/* Overlapping PT_LOAD segments share a page; widen its rights
* instead of allocating a second frame over the top. */
*pte |= arch_flags_of(prot);
continue;
}
void* frame = alloc_identity_frame();
if (!frame) return -1;
*pte = ((uint64_t)(uintptr_t)frame & PTE_ADDR_MASK) | arch_flags_of(prot);
vm->resident_pages++;
}
return 0;
}
uint64_t vmspace_resolve(vmspace_t* vm, uint64_t virt) {
if (!vm) return 0;
uint64_t* pte = walk(vm, virt & ~(VM_PAGE_SIZE - 1), false);
if (!pte || !(*pte & PAGE_PRESENT)) return 0;
return (*pte & PTE_ADDR_MASK) | (virt & (VM_PAGE_SIZE - 1));
}
bool vmspace_range_ok(vmspace_t* vm, uint64_t virt, size_t len) {
if (!vm || len == 0) return false;
if (virt < USER_BASE || virt >= USER_LIMIT) return false;
if (len > USER_LIMIT - virt) return false;
uint64_t start = virt & ~(VM_PAGE_SIZE - 1);
uint64_t end = (virt + len + VM_PAGE_SIZE - 1) & ~(VM_PAGE_SIZE - 1);
for (uint64_t page = start; page < end; page += VM_PAGE_SIZE) {
uint64_t* pte = walk(vm, page, false);
if (!pte || !(*pte & PAGE_PRESENT) || !(*pte & PAGE_USER)) return false;
}
return true;
}
int vmspace_write(vmspace_t* vm, uint64_t dst, const void* src, size_t len) {
if (!vm || !src) return -1;
const uint8_t* in = (const uint8_t*)src;
while (len) {
uint64_t phys = vmspace_resolve(vm, dst);
if (!phys) return -1;
size_t offset = (size_t)(dst & (VM_PAGE_SIZE - 1));
size_t chunk = VM_PAGE_SIZE - offset;
if (chunk > len) chunk = len;
memcpy((void*)(uintptr_t)phys, in, chunk);
dst += chunk;
in += chunk;
len -= chunk;
}
return 0;
}
void vmspace_switch(vmspace_t* vm) {
uint64_t cr3 = vm ? vm->pml4_phys : KERNEL_PML4_PHYS;
__asm__ volatile("mov %0, %%cr3" : : "r"(cr3) : "memory");
}
uint64_t vmspace_resident_pages(vmspace_t* vm) {
return vm ? vm->resident_pages : 0;
}
/* Free one level of the private sub-tree rooted at `phys`. */
static void free_level(uint64_t phys, int level) {
uint64_t* table = table_at(phys);
for (int i = 0; i < ENTRIES_PER_TABLE; i++) {
uint64_t entry = table[i];
if (!(entry & PAGE_PRESENT) || (entry & PAGE_HUGE)) continue;
if (level > 0) {
free_level(entry & PTE_ADDR_MASK, level - 1);
} else {
pmm_free_page((void*)(uintptr_t)(entry & PTE_ADDR_MASK));
}
}
pmm_free_page((void*)(uintptr_t)phys);
}
void vmspace_destroy(vmspace_t* vm) {
if (!vm) return;
uint64_t* pml4 = table_at(vm->pml4_phys);
/* Slot 0 is the kernel's own table and must outlive this process. */
uint64_t user_entry = pml4[USER_PML4_SLOT];
if (user_entry & PAGE_PRESENT) {
free_level(user_entry & PTE_ADDR_MASK, 2);
}
pmm_free_page((void*)(uintptr_t)vm->pml4_phys);
kfree(vm);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

+79 -15
View File
@@ -83,30 +83,54 @@ start2:
.mmap_store_done:
; -------------------------------------------------------------
; Step 3: Load Kernel from Disk to 0x10000 (Conventional RAM)
; 12 chunks x 64 sectors = 768 sectors (384 KB)
; Step 3: Load the kernel to 0x100000 (just above 1 MB)
;
; INT 13h can only land data below 1 MB, and staging the whole kernel in
; conventional RAM caps it at roughly 576 KB before the load runs into
; video memory at 0xA0000. So each 32 KB chunk is read into one fixed
; low buffer at 0x10000 and copied straight up to its final address.
;
; The copy needs 32-bit offsets from real mode, which is what unreal mode
; provides: enter protected mode just long enough to load a 4 GB-limit
; descriptor, then drop back. A real-mode segment load afterwards rewrites
; only the base and leaves that limit in the descriptor cache.
; -------------------------------------------------------------
mov si, msg_kernel_load
call print_string_16
mov word [kernel_dap_off], 0x0000
mov word [kernel_dap_seg], 0x1000
mov word [kernel_dap_seg], KERNEL_STAGE_SEG
mov dword [kernel_dap_lba], 16
mov dword [kernel_dap_lba + 4], 0
mov cx, 6 ; 6 chunks * 64 sectors = 384 sectors (192 KB)
mov dword [kernel_dest], 0x100000
mov cx, KERNEL_CHUNKS
.kernel_chunk_loop:
push cx
; Read one chunk into the staging buffer.
mov ah, 0x42
mov dl, [boot_drive]
mov si, kernel_dap
int 0x13
jc .kernel_disk_error
; Advance LBA by 64 sectors
add dword [kernel_dap_lba], 64
; Advance Segment by 0x800 (64 sectors * 512 bytes = 32768 bytes = 0x800 paragraphs)
add word [kernel_dap_seg], 0x0800
; Relocate it above 1 MB. Unreal mode is re-established every pass, since
; the BIOS disk call is free to reload the segment registers.
call enter_unreal_mode
xor ax, ax
mov ds, ax
mov es, ax
mov esi, KERNEL_STAGE_ADDR
mov edi, [kernel_dest]
mov ecx, (KERNEL_CHUNK_SECTORS * 512) / 4
a32 rep movsd
; Advance the source LBA and the destination address by one chunk.
add dword [kernel_dap_lba], KERNEL_CHUNK_SECTORS
add dword [kernel_dest], KERNEL_CHUNK_SECTORS * 512
pop cx
loop .kernel_chunk_loop
@@ -196,11 +220,7 @@ pm_entry:
mov ss, ax
mov esp, 0x7C00
; Copy kernel from 0x10000 to 0x100000 (1MB mark) - 192 KB (384 sectors)
mov esi, 0x10000
mov edi, 0x100000
mov ecx, (384 * 512) / 4
rep movsd
; The kernel was relocated to 0x100000 chunk by chunk during the load.
; Enable PAE in CR4
mov eax, cr4
@@ -247,6 +267,50 @@ lm_entry:
; Data Section
; -----------------------------------------------------------------
BITS 16
; Kernel image layout. The image is a 2880-sector floppy and the kernel starts
; at LBA 16, so 44 chunks reads every remaining sector: a 1.375 MB ceiling that
; the loader can grow into without touching this file again.
KERNEL_CHUNK_SECTORS equ 64
KERNEL_CHUNKS equ 44
KERNEL_STAGE_SEG equ 0x1000
KERNEL_STAGE_ADDR equ 0x10000
; -----------------------------------------------------------------------------
; enter_unreal_mode
;
; Leaves the CPU in real mode with DS and ES carrying a 4 GB segment limit, so
; `a32 rep movsd` can reach memory above 1 MB. Clobbers EAX and BX.
; -----------------------------------------------------------------------------
enter_unreal_mode:
cli
push ds
push es
lgdt [gdt_descriptor]
mov eax, cr0
or al, 1
mov cr0, eax
jmp $+2 ; Flush the prefetch queue
mov bx, 0x10 ; Flat 32-bit data descriptor
mov ds, bx
mov es, bx
and al, 0xFE
mov cr0, eax
jmp $+2
; Popping restores the real-mode segment bases; the 4 GB limits loaded
; above stay in the descriptor cache, which is what makes this "unreal".
pop es
pop ds
sti
ret
align 4
kernel_dest: dd 0x100000
align 4
boot_drive: db 0
@@ -261,11 +325,11 @@ align 4
kernel_dap:
db 0x10
db 0
dw 64 ; 64 sectors per chunk (32 KB)
dw KERNEL_CHUNK_SECTORS ; Sectors per chunk (32 KB)
kernel_dap_off:
dw 0x0000
kernel_dap_seg:
dw 0x1000
dw KERNEL_STAGE_SEG
kernel_dap_lba:
dq 16
+285
View File
@@ -0,0 +1,285 @@
/*
* ChaCha20-Poly1305 AEAD, RFC 8439.
*
* The record-protection cipher for the TLS 1.3 client. ChaCha20 is a pure
* add-rotate-xor stream cipher (no tables, so no data-dependent timing), and
* Poly1305 is its one-time authenticator; together they form the AEAD that
* encrypts and authenticates every TLS record. All integer arithmetic, so it
* is safe in the freestanding kernel.
*
* Poly1305 here is the 32-bit "donna" formulation (public domain), which needs
* only 64-bit intermediate products.
*/
#include <crypto/crypto.h>
#include <lib/string.h>
// ===========================================================================
// ChaCha20
// ===========================================================================
static inline uint32_t rotl32(uint32_t x, int n) {
return (x << n) | (x >> (32 - n));
}
static inline uint32_t load32_le(const uint8_t* p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static inline void store32_le(uint8_t* p, uint32_t v) {
p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8);
p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24);
}
#define QR(a, b, c, d) \
a += b; d ^= a; d = rotl32(d, 16); \
c += d; b ^= c; b = rotl32(b, 12); \
a += b; d ^= a; d = rotl32(d, 8); \
c += d; b ^= c; b = rotl32(b, 7)
static void chacha20_block(const uint32_t in[16], uint8_t out[64]) {
uint32_t x[16];
for (int i = 0; i < 16; i++) x[i] = in[i];
for (int i = 0; i < 10; i++) {
QR(x[0], x[4], x[8], x[12]);
QR(x[1], x[5], x[9], x[13]);
QR(x[2], x[6], x[10], x[14]);
QR(x[3], x[7], x[11], x[15]);
QR(x[0], x[5], x[10], x[15]);
QR(x[1], x[6], x[11], x[12]);
QR(x[2], x[7], x[8], x[13]);
QR(x[3], x[4], x[9], x[14]);
}
for (int i = 0; i < 16; i++) store32_le(out + 4 * i, x[i] + in[i]);
}
static void chacha20_init(uint32_t state[16], const uint8_t key[32],
uint32_t counter, const uint8_t nonce[12]) {
state[0] = 0x61707865; state[1] = 0x3320646e;
state[2] = 0x79622d32; state[3] = 0x6b206574;
for (int i = 0; i < 8; i++) state[4 + i] = load32_le(key + 4 * i);
state[12] = counter;
state[13] = load32_le(nonce + 0);
state[14] = load32_le(nonce + 4);
state[15] = load32_le(nonce + 8);
}
// Encrypt/decrypt src into dst (they may alias) starting at block counter.
void chacha20_xor(uint8_t* dst, const uint8_t* src, size_t len,
const uint8_t key[32], uint32_t counter, const uint8_t nonce[12]) {
uint32_t state[16];
uint8_t block[64];
chacha20_init(state, key, counter, nonce);
size_t off = 0;
while (off < len) {
chacha20_block(state, block);
state[12]++; // next block counter
size_t n = len - off < 64 ? len - off : 64;
for (size_t i = 0; i < n; i++) dst[off + i] = src[off + i] ^ block[i];
off += n;
}
}
// ===========================================================================
// Poly1305 (32-bit donna)
// ===========================================================================
typedef struct {
uint32_t r[5];
uint32_t h[5];
uint32_t pad[4];
size_t leftover;
uint8_t buffer[16];
uint8_t final;
} poly1305_ctx;
static void poly1305_init(poly1305_ctx* st, const uint8_t key[32]) {
st->r[0] = (load32_le(&key[0])) & 0x3ffffff;
st->r[1] = (load32_le(&key[3]) >> 2) & 0x3ffff03;
st->r[2] = (load32_le(&key[6]) >> 4) & 0x3ffc0ff;
st->r[3] = (load32_le(&key[9]) >> 6) & 0x3f03fff;
st->r[4] = (load32_le(&key[12]) >> 8) & 0x00fffff;
st->h[0] = st->h[1] = st->h[2] = st->h[3] = st->h[4] = 0;
st->pad[0] = load32_le(&key[16]);
st->pad[1] = load32_le(&key[20]);
st->pad[2] = load32_le(&key[24]);
st->pad[3] = load32_le(&key[28]);
st->leftover = 0;
st->final = 0;
}
static void poly1305_blocks(poly1305_ctx* st, const uint8_t* m, size_t bytes) {
const uint32_t hibit = st->final ? 0 : (1UL << 24);
uint32_t r0 = st->r[0], r1 = st->r[1], r2 = st->r[2], r3 = st->r[3], r4 = st->r[4];
uint32_t s1 = r1 * 5, s2 = r2 * 5, s3 = r3 * 5, s4 = r4 * 5;
uint32_t h0 = st->h[0], h1 = st->h[1], h2 = st->h[2], h3 = st->h[3], h4 = st->h[4];
while (bytes >= 16) {
uint64_t d0, d1, d2, d3, d4;
uint32_t c;
h0 += (load32_le(m + 0)) & 0x3ffffff;
h1 += (load32_le(m + 3) >> 2) & 0x3ffffff;
h2 += (load32_le(m + 6) >> 4) & 0x3ffffff;
h3 += (load32_le(m + 9) >> 6) & 0x3ffffff;
h4 += (load32_le(m + 12) >> 8) | hibit;
d0 = (uint64_t)h0 * r0 + (uint64_t)h1 * s4 + (uint64_t)h2 * s3 + (uint64_t)h3 * s2 + (uint64_t)h4 * s1;
d1 = (uint64_t)h0 * r1 + (uint64_t)h1 * r0 + (uint64_t)h2 * s4 + (uint64_t)h3 * s3 + (uint64_t)h4 * s2;
d2 = (uint64_t)h0 * r2 + (uint64_t)h1 * r1 + (uint64_t)h2 * r0 + (uint64_t)h3 * s4 + (uint64_t)h4 * s3;
d3 = (uint64_t)h0 * r3 + (uint64_t)h1 * r2 + (uint64_t)h2 * r1 + (uint64_t)h3 * r0 + (uint64_t)h4 * s4;
d4 = (uint64_t)h0 * r4 + (uint64_t)h1 * r3 + (uint64_t)h2 * r2 + (uint64_t)h3 * r1 + (uint64_t)h4 * r0;
c = (uint32_t)(d0 >> 26); h0 = (uint32_t)d0 & 0x3ffffff;
d1 += c; c = (uint32_t)(d1 >> 26); h1 = (uint32_t)d1 & 0x3ffffff;
d2 += c; c = (uint32_t)(d2 >> 26); h2 = (uint32_t)d2 & 0x3ffffff;
d3 += c; c = (uint32_t)(d3 >> 26); h3 = (uint32_t)d3 & 0x3ffffff;
d4 += c; c = (uint32_t)(d4 >> 26); h4 = (uint32_t)d4 & 0x3ffffff;
h0 += c * 5; c = (h0 >> 26); h0 &= 0x3ffffff;
h1 += c;
m += 16;
bytes -= 16;
}
st->h[0] = h0; st->h[1] = h1; st->h[2] = h2; st->h[3] = h3; st->h[4] = h4;
}
static void poly1305_update(poly1305_ctx* st, const uint8_t* m, size_t bytes) {
if (st->leftover) {
size_t want = 16 - st->leftover;
if (want > bytes) want = bytes;
for (size_t i = 0; i < want; i++) st->buffer[st->leftover + i] = m[i];
bytes -= want;
m += want;
st->leftover += want;
if (st->leftover < 16) return;
poly1305_blocks(st, st->buffer, 16);
st->leftover = 0;
}
if (bytes >= 16) {
size_t want = bytes & ~(size_t)15;
poly1305_blocks(st, m, want);
m += want;
bytes -= want;
}
for (size_t i = 0; i < bytes; i++) st->buffer[st->leftover + i] = m[i];
st->leftover += bytes;
}
static void poly1305_finish(poly1305_ctx* st, uint8_t mac[16]) {
if (st->leftover) {
size_t i = st->leftover;
st->buffer[i++] = 1;
for (; i < 16; i++) st->buffer[i] = 0;
st->final = 1;
poly1305_blocks(st, st->buffer, 16);
}
uint32_t h0 = st->h[0], h1 = st->h[1], h2 = st->h[2], h3 = st->h[3], h4 = st->h[4];
uint32_t c;
c = h1 >> 26; h1 &= 0x3ffffff;
h2 += c; c = h2 >> 26; h2 &= 0x3ffffff;
h3 += c; c = h3 >> 26; h3 &= 0x3ffffff;
h4 += c; c = h4 >> 26; h4 &= 0x3ffffff;
h0 += c * 5; c = h0 >> 26; h0 &= 0x3ffffff;
h1 += c;
uint32_t g0 = h0 + 5; c = g0 >> 26; g0 &= 0x3ffffff;
uint32_t g1 = h1 + c; c = g1 >> 26; g1 &= 0x3ffffff;
uint32_t g2 = h2 + c; c = g2 >> 26; g2 &= 0x3ffffff;
uint32_t g3 = h3 + c; c = g3 >> 26; g3 &= 0x3ffffff;
uint32_t g4 = h4 + c - (1UL << 26);
uint32_t mask = (g4 >> 31) - 1; // 0 if g>=p (use g), 0xffffffff if g<p (use h)
g0 &= mask; g1 &= mask; g2 &= mask; g3 &= mask; g4 &= mask;
mask = ~mask;
h0 = (h0 & mask) | g0;
h1 = (h1 & mask) | g1;
h2 = (h2 & mask) | g2;
h3 = (h3 & mask) | g3;
h4 = (h4 & mask) | g4;
// Serialise h into 128-bit little-endian, add pad.
uint64_t f;
h0 = (h0) | (h1 << 26);
h1 = (h1 >> 6) | (h2 << 20);
h2 = (h2 >> 12) | (h3 << 14);
h3 = (h3 >> 18) | (h4 << 8);
f = (uint64_t)h0 + st->pad[0]; h0 = (uint32_t)f;
f = (uint64_t)h1 + st->pad[1] + (f >> 32); h1 = (uint32_t)f;
f = (uint64_t)h2 + st->pad[2] + (f >> 32); h2 = (uint32_t)f;
f = (uint64_t)h3 + st->pad[3] + (f >> 32); h3 = (uint32_t)f;
store32_le(mac + 0, h0);
store32_le(mac + 4, h1);
store32_le(mac + 8, h2);
store32_le(mac + 12, h3);
}
// ===========================================================================
// AEAD construction (RFC 8439 section 2.8)
// ===========================================================================
static void poly1305_key_gen(uint8_t out_key[32], const uint8_t key[32],
const uint8_t nonce[12]) {
uint32_t state[16];
uint8_t block[64];
chacha20_init(state, key, 0, nonce);
chacha20_block(state, block);
memcpy(out_key, block, 32);
}
static const uint8_t poly_zeros[16] = { 0 };
static void aead_mac(uint8_t tag[16], const uint8_t poly_key[32],
const uint8_t* aad, size_t aad_len,
const uint8_t* ct, size_t ct_len) {
poly1305_ctx st;
poly1305_init(&st, poly_key);
poly1305_update(&st, aad, aad_len);
if (aad_len % 16) poly1305_update(&st, poly_zeros, 16 - (aad_len % 16));
poly1305_update(&st, ct, ct_len);
if (ct_len % 16) poly1305_update(&st, poly_zeros, 16 - (ct_len % 16));
uint8_t lengths[16];
for (int i = 0; i < 8; i++) lengths[i] = (uint8_t)(aad_len >> (8 * i));
for (int i = 0; i < 8; i++) lengths[8 + i] = (uint8_t)(ct_len >> (8 * i));
poly1305_update(&st, lengths, 16);
poly1305_finish(&st, tag);
}
// Encrypt plaintext -> ciphertext (same length) and produce a 16-byte tag.
void chacha20poly1305_encrypt(uint8_t* ct, uint8_t tag[16],
const uint8_t key[32], const uint8_t nonce[12],
const uint8_t* aad, size_t aad_len,
const uint8_t* pt, size_t pt_len) {
uint8_t poly_key[32];
poly1305_key_gen(poly_key, key, nonce);
chacha20_xor(ct, pt, pt_len, key, 1, nonce); // counter starts at 1
aead_mac(tag, poly_key, aad, aad_len, ct, pt_len);
}
// Verify tag and decrypt ciphertext -> plaintext. Returns true on success.
bool chacha20poly1305_decrypt(uint8_t* pt, const uint8_t key[32],
const uint8_t nonce[12],
const uint8_t* aad, size_t aad_len,
const uint8_t* ct, size_t ct_len,
const uint8_t tag[16]) {
uint8_t poly_key[32];
poly1305_key_gen(poly_key, key, nonce);
uint8_t expect[16];
aead_mac(expect, poly_key, aad, aad_len, ct, ct_len);
uint8_t diff = 0;
for (int i = 0; i < 16; i++) diff |= expect[i] ^ tag[i];
if (diff) return false; // authentication failed
chacha20_xor(pt, ct, ct_len, key, 1, nonce);
return true;
}
+113
View File
@@ -0,0 +1,113 @@
/*
* HMAC-SHA256 (RFC 2104), HKDF (RFC 5869), and the TLS 1.3 HKDF-Expand-Label
* / Derive-Secret helpers (RFC 8446 section 7.1).
*
* These sit on top of the existing SHA-256 and drive the TLS 1.3 key schedule:
* every traffic key, IV and Finished key is produced by an Expand-Label over a
* secret and the running transcript hash.
*/
#include <crypto/crypto.h>
#include <lib/string.h>
#define SHA256_BLOCK 64
#define SHA256_LEN 32
void hmac_sha256(const uint8_t* key, size_t key_len,
const uint8_t* msg, size_t msg_len, uint8_t out[32]) {
uint8_t k[SHA256_BLOCK];
uint8_t ipad[SHA256_BLOCK], opad[SHA256_BLOCK];
if (key_len > SHA256_BLOCK) {
sha256(key, key_len, k);
memset(k + SHA256_LEN, 0, SHA256_BLOCK - SHA256_LEN);
} else {
memcpy(k, key, key_len);
memset(k + key_len, 0, SHA256_BLOCK - key_len);
}
for (int i = 0; i < SHA256_BLOCK; i++) {
ipad[i] = k[i] ^ 0x36;
opad[i] = k[i] ^ 0x5c;
}
uint8_t inner[SHA256_LEN];
sha256_ctx_t c;
sha256_init(&c);
sha256_update(&c, ipad, SHA256_BLOCK);
sha256_update(&c, msg, msg_len);
sha256_final(inner, &c);
sha256_init(&c);
sha256_update(&c, opad, SHA256_BLOCK);
sha256_update(&c, inner, SHA256_LEN);
sha256_final(out, &c);
}
void hkdf_extract(const uint8_t* salt, size_t salt_len,
const uint8_t* ikm, size_t ikm_len, uint8_t prk[32]) {
static const uint8_t zero[SHA256_LEN] = { 0 };
if (!salt || salt_len == 0) { salt = zero; salt_len = SHA256_LEN; }
hmac_sha256(salt, salt_len, ikm, ikm_len, prk);
}
void hkdf_expand(const uint8_t prk[32], const uint8_t* info, size_t info_len,
uint8_t* out, size_t out_len) {
uint8_t t[SHA256_LEN];
size_t t_len = 0;
size_t done = 0;
uint8_t counter = 1;
while (done < out_len) {
// T(n) = HMAC(PRK, T(n-1) | info | counter)
sha256_ctx_t hc; (void)hc;
uint8_t block[SHA256_LEN + 512 + 1]; // t | info | counter
size_t bl = 0;
if (t_len) { memcpy(block, t, t_len); bl += t_len; }
if (info_len > 512) info_len = 512;
memcpy(block + bl, info, info_len); bl += info_len;
block[bl++] = counter;
hmac_sha256(prk, SHA256_LEN, block, bl, t);
t_len = SHA256_LEN;
size_t n = out_len - done < SHA256_LEN ? out_len - done : SHA256_LEN;
memcpy(out + done, t, n);
done += n;
counter++;
}
}
// TLS 1.3 HKDF-Expand-Label:
// struct { uint16 length; opaque label<7..255>; opaque context<0..255>; }
// label is prefixed with "tls13 ".
void tls13_expand_label(const uint8_t secret[32], const char* label,
const uint8_t* context, size_t context_len,
uint8_t* out, size_t out_len) {
uint8_t info[512];
size_t p = 0;
info[p++] = (uint8_t)(out_len >> 8);
info[p++] = (uint8_t)(out_len & 0xff);
char full_label[64] = "tls13 ";
size_t ll = strlen(full_label);
size_t rest = strlen(label);
if (ll + rest > sizeof(full_label) - 1) rest = sizeof(full_label) - 1 - ll;
memcpy(full_label + ll, label, rest);
size_t label_len = ll + rest;
info[p++] = (uint8_t)label_len;
memcpy(info + p, full_label, label_len); p += label_len;
info[p++] = (uint8_t)context_len;
if (context_len) { memcpy(info + p, context, context_len); p += context_len; }
hkdf_expand(secret, info, p, out, out_len);
}
// Derive-Secret(secret, label, messages) = Expand-Label(secret, label,
// Hash(messages), Hash.length). `transcript_hash` is that Hash(messages).
void tls13_derive_secret(const uint8_t secret[32], const char* label,
const uint8_t transcript_hash[32], uint8_t out[32]) {
tls13_expand_label(secret, label, transcript_hash, SHA256_LEN, out, SHA256_LEN);
}
+163
View File
@@ -0,0 +1,163 @@
/*
* X25519 (Curve25519 ECDH), RFC 7748.
*
* This is the compact TweetNaCl field arithmetic (public domain), which uses
* only 64-bit integer operations -- no floating point, no wide multiply beyond
* what the compiler provides -- so it is safe in the freestanding kernel. It is
* the key exchange for the TLS 1.3 client: x25519_base() turns a random private
* scalar into a public key to send, and x25519() combines our private scalar
* with the peer's public key to produce the shared secret.
*/
#include <crypto/crypto.h>
#include <lib/string.h>
typedef int64_t i64;
typedef uint8_t u8;
typedef i64 gf[16];
static const gf _121665 = { 0xDB41, 1 };
static void car25519(gf o) {
int i;
i64 c;
for (i = 0; i < 16; i++) {
o[i] += (1LL << 16);
c = o[i] >> 16;
o[(i + 1) * (i < 15)] += c - 1 + 37 * (c - 1) * (i == 15);
o[i] -= c << 16;
}
}
static void sel25519(gf p, gf q, int b) {
i64 t, i, c = ~(b - 1);
for (i = 0; i < 16; i++) {
t = c & (p[i] ^ q[i]);
p[i] ^= t;
q[i] ^= t;
}
}
static void pack25519(u8* o, const gf n) {
int i, j, b;
gf m, t;
for (i = 0; i < 16; i++) t[i] = n[i];
car25519(t);
car25519(t);
car25519(t);
for (j = 0; j < 2; j++) {
m[0] = t[0] - 0xffed;
for (i = 1; i < 15; i++) {
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
m[i - 1] &= 0xffff;
}
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
b = (m[15] >> 16) & 1;
m[14] &= 0xffff;
sel25519(t, m, 1 - b);
}
for (i = 0; i < 16; i++) {
o[2 * i] = t[i] & 0xff;
o[2 * i + 1] = t[i] >> 8;
}
}
static void unpack25519(gf o, const u8* n) {
int i;
for (i = 0; i < 16; i++) o[i] = n[2 * i] + ((i64)n[2 * i + 1] << 8);
o[15] &= 0x7fff;
}
static void A(gf o, const gf a, const gf b) {
int i;
for (i = 0; i < 16; i++) o[i] = a[i] + b[i];
}
static void Z(gf o, const gf a, const gf b) {
int i;
for (i = 0; i < 16; i++) o[i] = a[i] - b[i];
}
static void M(gf o, const gf a, const gf b) {
i64 i, j, t[31];
for (i = 0; i < 31; i++) t[i] = 0;
for (i = 0; i < 16; i++)
for (j = 0; j < 16; j++) t[i + j] += a[i] * b[j];
for (i = 0; i < 15; i++) t[i] += 38 * t[i + 16];
for (i = 0; i < 16; i++) o[i] = t[i];
car25519(o);
car25519(o);
}
static void S(gf o, const gf a) { M(o, a, a); }
static void inv25519(gf o, const gf i) {
gf c;
int a;
for (a = 0; a < 16; a++) c[a] = i[a];
for (a = 253; a >= 0; a--) {
S(c, c);
if (a != 2 && a != 4) M(c, c, i);
}
for (a = 0; a < 16; a++) o[a] = c[a];
}
static int crypto_scalarmult(u8* q, const u8* n, const u8* p) {
u8 z[32];
i64 x[80], r, i;
gf a, b, c, d, e, f;
for (i = 0; i < 31; i++) z[i] = n[i];
z[31] = (n[31] & 127) | 64;
z[0] &= 248;
unpack25519(x, p);
for (i = 0; i < 16; i++) {
b[i] = x[i];
d[i] = a[i] = c[i] = 0;
}
a[0] = d[0] = 1;
for (i = 254; i >= 0; --i) {
r = (z[i >> 3] >> (i & 7)) & 1;
sel25519(a, b, r);
sel25519(c, d, r);
A(e, a, c);
Z(a, a, c);
A(c, b, d);
Z(b, b, d);
S(d, e);
S(f, a);
M(a, c, a);
M(c, b, e);
A(e, a, c);
Z(a, a, c);
S(b, a);
Z(c, d, f);
M(a, c, _121665);
A(a, a, d);
M(c, c, a);
M(a, d, f);
M(d, b, x);
S(b, e);
sel25519(a, b, r);
sel25519(c, d, r);
}
for (i = 0; i < 16; i++) {
x[i + 16] = a[i];
x[i + 32] = c[i];
x[i + 48] = b[i];
x[i + 64] = d[i];
}
inv25519(x + 32, x + 32);
M(x + 16, x + 16, x + 32);
pack25519(q, x + 16);
return 0;
}
// The Curve25519 base point, u = 9.
static const u8 x25519_basepoint[32] = { 9 };
void x25519_base(uint8_t out_pub[32], const uint8_t priv[32]) {
crypto_scalarmult(out_pub, priv, x25519_basepoint);
}
void x25519(uint8_t out_shared[32], const uint8_t priv[32], const uint8_t peer_pub[32]) {
crypto_scalarmult(out_shared, priv, peer_pub);
}
+84
View File
@@ -0,0 +1,84 @@
# SUB-OS Autonomous Development Roadmap
This file is the cross-run memory for the continuous-development effort. Each
run: read it, pick the single highest-value NEXT item achievable in one
session, implement + verify it, then move it to DONE with a one-line result and
refine the backlog.
## Known-good build invocations (this host)
- **Toolchain setup:** `apt-get install -y nasm gcc-aarch64-linux-gnu
binutils-aarch64-linux-gnu gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf
qemu-system-x86 qemu-system-arm`; for Rust: `rustup target add x86_64-unknown-none`.
- **x86_64:** `make x86_64_defconfig && make CROSS_COMPILE=x86_64-linux-gnu-
CXX=x86_64-linux-gnu-g++ RUSTC="rustc --target x86_64-unknown-none"`
- **aarch64:** `make aarch64_defconfig && make`
- **armv8i:** `make armv8i_defconfig && make`
- **Boot (x86_64, headless):** `qemu-system-x86_64 -drive format=raw,file=SUB-OS.img
-m 256M -netdev user,id=net0 -device e1000,netdev=net0 -serial stdio -display none`
(slow under TCG; the shell waits on input, so a timeout at the prompt is normal).
## DONE
- **Preemptive Weave scheduling from the timer tick** (2026-08-25):
The PIT tick now drives involuntary context switches. sched_tick() only
records that a quantum is spent (weave_need_resched); the switch happens at
IRQ return in sched_preempt_on_return(), after the PIC EOI, on the
interrupted thread's own kernel stack -- so the tick path stays re-entrancy
safe and threads unwind cleanly through the normal iretq. The scheduler's
lock sections were made interrupt-atomic (arch_irq_save/restore, new in
arch.h) so the timer can never fire mid-mutation, and a preemption-disable
count (sched_preempt_disable/enable, used by printk) keeps a switch from
landing while a plain spinlock is held. Fresh preemptible kernel threads opt
into interrupts at entry. Verified on x86_64 under QEMU: a boot-time self-test
arms preemption and two NON-yielding worker threads are forced to share the
CPU -- 20 involuntary switches, both workers advance and are woven down to the
CPU-bound lanes -- then boot proceeds cleanly to ring-3 init and the shell
with no faults. (Debugging note: an early re-entrancy guard deadlocked all
preemption because sched_schedule() does not return to its caller until that
thread is rescheduled; it was removed, since the masked switch already
prevents nesting.)
- **Weave scheduler + real kernel-thread context switching** (2026-08-25):
Replaced the state-relabel-only round-robin with the SUB-OS "Weave" tiered
scheduler backed by a genuine callee-saved context switch (`sub_ctx_switch`,
per-arch assembly for x86_64/aarch64/armv8i) and a first-run trampoline.
Threads run on their own kernel stacks; a task's lane floats on behaviour
(voluntary yield → woven up, quantum-burn → woven down). Verified on x86_64
under QEMU: a boot-time self-test runs 3 cooperative kernel threads to
completion (20 real context switches, correct 1→2→3 round-robin interleave,
clean return to the boot thread, then boot proceeds to ring-3 init + shell).
ARM switch asm builds on both targets but is not yet boot-tested.
## NEXT (prioritized backlog)
1. **Thread reaping / zombie cleanup.** Exited kernel threads currently leak
their 16 KB stack + task_t. Add a reaper (per-CPU dead list drained by the
idle thread) so `task_exit` hands the corpse to it instead of leaking.
2. **Boot-test the ARM context switch.** Wire a `weave_selftest()` call into the
aarch64/armv8i boot paths and boot under `qemu-system-aarch64`/`-arm` to
confirm the AArch64/AArch32 `sub_ctx_switch` frames are correct.
3. **fork/exec/wait for the ring-3 userland.** Build on the existing ELF64
loader + per-process page tables so `sh` can spawn and reap real children,
not just the single builtin init.
4. **An original capability-based IPC channel** ("sub-ports"): typed,
capability-addressed message endpoints distinct from the SysV msg/pipe/shm
set already present in `ipc/` — leaning into the "object-first" identity.
5. **SMP bring-up (x86_64):** AP boot trampoline, per-CPU run lanes, and making
the Weave lanes per-CPU with work-stealing between them.
6. **GUI (feat/gui-desktop):** compositor damage tracking, a VFS-backed file
manager, a text editor, and a settings panel over the toolkit widgets.
## Notes / gotchas
- `.S` files are run through cpp: no nested `/* ... */` comments.
- `printk`/vsprintf supports only `c s d i u o x X p %` — no length modifiers
(`l`, `ll`) and no field width; cast to `int`/`unsigned`/`void*`.
- `list_del()` NULLs both links, so `list_empty()` is not a reliable "is this
node queued?" test after a delete — check the links directly.
- Preemption is armed after boot (step 14). A switch must NOT run while a plain
spinlock is held: wrap such sections with sched_preempt_disable/enable (printk
already does). weave_lock sections use arch_irq_save/restore so the tick
cannot fire inside them.
- During `subos_modular_core_boot()` step 13, hardware interrupts are still
masked (enabled at step 14), so cooperative in-boot tests are deterministic.
+183
View File
@@ -0,0 +1,183 @@
<div align="center">
<img src="../assets/logo_SUB_OS.png" alt="SUB-OS logo" width="140" height="140" />
# Contributing to SUB-OS
</div>
Thanks for your interest in **SUB-OS** — a multi-architecture, modular monolithic,
Linux-compatible operating system written from scratch in C, C++17, Assembly,
freestanding Rust (`no_std`), and the native SUB Language. This guide covers how
to build, the conventions we follow, and how to get a change merged.
---
## Table of Contents
- [Ground rules](#ground-rules)
- [Development environment](#development-environment)
- [Building](#building)
- [Running & verifying](#running--verifying)
- [Coding style](#coding-style)
- [Kernel gotchas](#kernel-gotchas)
- [Commit & PR conventions](#commit--pr-conventions)
- [Reporting bugs](#reporting-bugs)
---
## Ground rules
- **Verify before you claim.** Nothing is "done" until it builds *and* boots.
Every crypto primitive and codec is tested against a reference vector (RFC or
a PIL-generated fixture) **before** it enters the kernel. Hold new code to the
same bar.
- **No libc, no POSIX host.** The kernel is freestanding. No floating point in
kernel space (`-mno-sse`); all math is integer.
- **Small, reviewable changes.** One feature or fix per pull request, with a
clear description of what you verified and how.
- **Be kind.** Assume good faith, keep discussion technical, and respect
reviewers' time.
---
## Development environment
The project targets `x86_64`, `aarch64`, and `armv8i` (AArch32). On a Debian/
Ubuntu host, install the toolchains and emulators:
```bash
sudo apt update
sudo apt install build-essential g++ nasm qemu-system-x86 qemu-system-arm \
gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu \
gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf \
rustc cargo dialog python3
```
For the freestanding Rust layer:
```bash
rustup target add x86_64-unknown-none
```
> The x86_64 kernel is normally built with an `x86_64-elf-` cross toolchain. A
> stock `x86_64-linux-gnu-` GCC also works — pass it via `CROSS_COMPILE=`.
---
## Building
SUB-OS uses a Linux-style Kconfig + Kbuild flow. The version/codename lives at
the top of the `Makefile` (`VERSION`/`PATCHLEVEL`/`SUBLEVEL`/`EXTRAVERSION`/
`NAME`) and in [`include/init/version.h`](../include/init/version.h) — keep the
two in sync when you bump a release.
```bash
# Configure (interactive) or pick an architecture preset:
make configure # or: make menuconfig
make x86_64_defconfig # x86_64 (Intel / AMD)
make aarch64_defconfig # 64-bit ARM (Cortex-A57)
make armv8i_defconfig # 32-bit ARM (Cortex-A15)
# Build:
make # builds the configured/default ARCH
make ARCH=aarch64 # or target one explicitly
```
> **Always `make clean` when switching `ARCH`.** Object files from another
> architecture link into a *"file in wrong format"* error otherwise.
---
## Running & verifying
```bash
make run # x86_64 disk image in QEMU
make run-gui # windowed graphical desktop
make run-fullscreen # fullscreen desktop
make run-vnc # headless: VNC on localhost:5900
make ARCH=aarch64 run # ARM targets
```
Default login is **`SUB` / `SUB`**. Pass `nogui`, `text`, `emergency`, or
`single` on the kernel command line to boot to the TTY instead of the desktop.
**Headless verification** (what CI and reviewers use): boot with
`-serial file:serial.log -monitor unix:mon.sock,server,nowait -display none`,
drive the login over the monitor `sendkey` interface, and capture the screen
with `screendump`. For a code path with no UI, add a temporary CLI applet under
`userland/lazybox/` that exercises it over serial, verify, then revert the
temporary hook.
When you submit a change, state plainly **what you ran and what you saw** — build
output, boot log excerpt, screenshot, or reference-vector comparison.
---
## Coding style
- **C:** freestanding C11. Match the surrounding file — 4-space indent, K&R
braces, `snake_case` for functions and variables, `SCREAMING_SNAKE` for
macros/constants. Keep comment density and idiom consistent with the module
you're editing.
- **Assembly:** per-arch under `arch/<arch>/`. `.S` files run through `cpp`, so
**no nested `/* ... */` comments**.
- **Rust:** `no_std`, no allocator assumptions beyond what the kernel provides.
- **C++:** freestanding C++17, no exceptions/RTTI, no host STL.
- **Headers:** public interfaces in `include/`, with an include guard.
- **No new external dependencies** in kernel space.
---
## Kernel gotchas
These bite newcomers — internalize them before touching the relevant subsystem:
- **`printk`/`vsprintf` are minimal.** Supported conversions are
`c s d i u o x X p %` only — **no** length modifiers (`l`, `ll`) and **no**
field width. Cast to `int` / `unsigned` / `void*` at the call site.
- **`list_del()` NULLs both links**, so `list_empty()` is not a reliable "is
this node still queued?" test after a delete — inspect the links directly.
- **Preemption is armed after boot.** A context switch must never run while a
plain spinlock is held: wrap such sections with
`sched_preempt_disable/enable` (as `printk` does). `weave_lock` sections use
`arch_irq_save/restore` so the timer tick cannot fire inside them.
- **Dead tasks/stacks are not reclaimed yet.** Prefer one long-lived worker
thread over spawning a task per request.
- **Network checksums are byte-wise on purpose** — word-at-a-time reads get
reordered around the caller's `hdr->checksum = 0` store under `-O2`.
- **Ethernet frames are padded to 60 bytes** — trim payloads to the IP
`total_length`, never the frame length.
---
## Commit & PR conventions
- **Commit messages** follow Conventional-Commits-style prefixes:
`feat(scope): …`, `fix(scope): …`, `build: …`, `docs: …`, `release(vX.Y.Z): …`.
Write the subject in the imperative and add a body explaining *what changed
and what you verified*.
- **Branch** off `main` for your work; open a pull request against `main`.
- **One logical change per PR.** Rebase/squash noisy WIP commits before review.
- **CI must pass** (build + kernel verification) before merge.
- In the PR description, include: what the change does, which architectures you
built, and how you verified boot behaviour.
---
## Reporting bugs
Open a GitHub issue with:
1. **Architecture** and how you built (`defconfig`, `ARCH`, toolchain).
2. **Exact command** that reproduces it.
3. **What you expected** vs. **what happened** — paste the serial log and, for
the desktop, attach a `screendump`.
4. The commit SHA (`git rev-parse --short HEAD`).
For **security-sensitive** reports, do **not** open a public issue — follow the
[Security Policy](SECURITY.md) instead.
---
Happy hacking. 🐉
+87
View File
@@ -0,0 +1,87 @@
<div align="center">
<img src="../assets/logo_SUB_OS.png" alt="SUB-OS logo" width="140" height="140" />
# SUB-OS Roadmap
</div>
Where SUB-OS is, and where it's going. This is a living document — priorities
shift as the kernel grows. For the fine-grained, per-session engineering backlog
see [`AUTODEV_ROADMAP.md`](AUTODEV_ROADMAP.md).
**Current release:** [`v1.0.0` — Titan](https://github.com/subhobhai943/SUB-OS/releases/tag/v1.0.0) (stable)
---
## ✅ Shipped in v1.0.0 (Titan)
- **Weave scheduler** — genuine per-architecture context switching with
behavioural priority lanes; cooperative **and** preemptive (timer-driven).
- **Concurrency core** — wait queues, completions, futexes, tiny RCU.
- **Memory management** — buddy PMM, slab allocator, per-process page tables,
page cache.
- **VFS** — ext2, FAT32, ramfs, procfs, sysfs, devfs.
- **Networking** — Ethernet/ARP/IPv4/UDP, full TCP (active + passive open),
DHCP, DNS, NetFilter; `sshd` and `httpd`.
- **TLS 1.3 client** — X25519, ChaCha20-Poly1305, HKDF key schedule (browses
HTTPS; see the [Security Policy](SECURITY.md) for the cert-verification caveat).
- **Image & compression codecs** — DEFLATE/zlib, PNG, BMP, baseline JPEG.
- **Graphical desktop (SUB-WT)** — 60 FPS compositor, window manager, Terminal,
Files, System & Network Monitor, a real web browser with inline images, and
more.
- **Multi-arch** — `x86_64`, `aarch64`, `armv8i`.
- **Linux-style build** — Kconfig TUI, Kbuild, per-arch defconfigs.
---
## 🔜 Near term (1.1.x)
- **Thread reaping / zombie cleanup.** Exited kernel threads currently leak their
16 KB stack + `task_t`. Add a reaper (a dead list drained by the idle thread).
- **Boot-test the ARM context switch.** Wire `weave_selftest()` into the
aarch64/armv8i boot paths and verify the AArch64/AArch32 `sub_ctx_switch`
frames under `qemu-system-aarch64` / `-arm`.
- **`fork`/`exec`/`wait` for ring-3 userland.** Build on the ELF64 loader and
per-process page tables so `sh` can spawn and reap real children.
- **TLS certificate verification.** Close the biggest documented security gap by
validating the server chain against a bundled trust store.
---
## 🌗 Mid term (1.x)
- **Capability-based IPC ("sub-ports").** Typed, capability-addressed message
endpoints distinct from the SysV msg/pipe/shm set already in `ipc/`.
- **SMP bring-up (x86_64).** AP boot trampoline, per-CPU run lanes, and per-CPU
Weave lanes with work-stealing.
- **Desktop maturity.** Compositor damage tracking, a richer file manager, a
text editor, and a settings panel over the toolkit widgets.
- **Storage & FS.** Write-back caching improvements, journaling groundwork, and
broader ext2 write coverage.
- **Exploit mitigations.** Enforce W^X on all mappings, add kernel stack
canaries, and begin ASLR groundwork.
---
## 🌒 Long term (exploratory)
- **Self-hosting toolchain path** — grow the userland toward building parts of
SUB-OS on SUB-OS.
- **A native SUB-Language userland** — first-class apps written in `.sb`.
- **Real hardware bring-up** — beyond QEMU, on at least one aarch64 board.
- **Networking depth** — IPv6, a TLS *server*, and a hardened, fuzzed stack.
- **Audio/graphics acceleration** — virtio-gpu 3D paths and a mixer.
---
## How priorities are chosen
Each development pass picks the single highest-value item achievable and
verifiable in one session, implements it, verifies it under QEMU, and refines
the backlog. Correctness and boot-tested verification always gate a feature
before it's called done. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the
verification discipline.
> Have an idea or want to pick something up? Open an issue or a discussion —
> contributions against any of the items above are very welcome.
+95
View File
@@ -0,0 +1,95 @@
<div align="center">
<img src="../assets/logo_SUB_OS.png" alt="SUB-OS logo" width="140" height="140" />
# Security Policy
</div>
SUB-OS is an from-scratch, research/hobby operating system. It is built to be
correct and educational, **not** to be deployed as a hardened production system.
This document explains what is supported, how to report a vulnerability, and the
security caveats you should know before running it anywhere that matters.
---
## Supported versions
| Version | Codename | Status | Security fixes |
|---------|----------|--------|----------------|
| `1.0.0` (`1.0.x`) | Titan | ✅ Current stable | Yes |
| `0.2.x` / `0.0.x` (alpha/beta) | — | ⚠️ Superseded | No |
Only the current stable line receives security fixes. Please upgrade to the
latest [release](https://github.com/subhobhai943/SUB-OS/releases/latest) before
reporting an issue.
---
## Reporting a vulnerability
**Please do not open a public GitHub issue for a security vulnerability.**
Instead, report it privately through GitHub's coordinated-disclosure channel:
1. Go to the repository's **Security** tab →
**[Report a vulnerability](https://github.com/subhobhai943/SUB-OS/security/advisories/new)**.
2. This opens a **private security advisory** visible only to you and the
maintainers.
3. Include:
- Affected version / commit SHA and architecture.
- A description of the class of problem and its impact.
- Minimal steps or a proof-of-concept that reproduces it.
- Any suggested remediation, if you have one.
> Please describe the **class** of problem and its impact rather than publishing
> a weaponized, step-by-step exploit.
### What to expect
- **Acknowledgement:** we aim to confirm receipt within **7 days**.
- **Assessment:** an initial severity assessment and triage within **14 days**.
- **Fix & disclosure:** we coordinate a fix and a disclosure timeline with you,
and credit you in the advisory and release notes (unless you prefer to remain
anonymous).
---
## Known security caveats
SUB-OS deliberately trades several hardening properties for simplicity and
readability at this stage. These are **known and documented**, not bugs:
- **TLS has no certificate verification.** The TLS 1.3 client performs the key
exchange and AEAD correctly but **does not validate the server certificate
chain**. Connections are encrypted but **not authenticated** — they are
trivially MITM-able. Do not use it to protect real secrets.
- **Default credentials are `SUB` / `SUB`.** Every image ships with the same
well-known login. Treat any running instance as unauthenticated.
- **No exploit mitigations yet.** There is no ASLR, no stack canaries in kernel
space, and W^X is not enforced on every mapping. Some segments load RWX.
- **Single-user, cooperative trust model.** Isolation between the ring-3 shell
and the kernel is minimal; the userland is trusted.
- **Networking is best-effort.** The stack is a from-scratch implementation and
has not been fuzzed or audited against hostile packets.
Run SUB-OS in a **VM or emulator (QEMU)**, on isolated networks, and never with
real credentials or sensitive data.
---
## Scope
In scope for a security report:
- Memory-safety faults reachable from the network stack, filesystem parsers, or
the image/crypto codecs.
- Authentication or privilege-boundary bypasses beyond the documented caveats.
- Cryptographic implementation errors (e.g. a broken AEAD or key schedule).
Out of scope (already documented above): the default credentials, the absence
of TLS certificate verification, and the lack of exploit mitigations.
---
Thank you for helping keep SUB-OS and its users safe. 🐉
+39 -21
View File
@@ -5,18 +5,31 @@
static ata_device_t primary_master;
static void ata_wait_ready(uint16_t io_base) {
static bool ata_wait_ready(uint16_t io_base) {
// 400ns delay
inb(io_base + 7);
inb(io_base + 7);
inb(io_base + 7);
inb(io_base + 7);
for (int i = 0; i < 4; i++) {
inb(io_base + 7);
}
while (inb(io_base + 7) & 0x80); // Wait while BSY (Busy) bit is set
for (uint32_t timeout = 50000; timeout > 0; timeout--) {
if (!(inb(io_base + 7) & 0x80)) {
return true; // BSY bit cleared
}
}
return false;
}
static void ata_wait_drq(uint16_t io_base) {
while (!(inb(io_base + 7) & 0x08)); // Wait while DRQ (Data Request) bit is clear
static bool ata_wait_drq(uint16_t io_base) {
for (uint32_t timeout = 50000; timeout > 0; timeout--) {
uint8_t status = inb(io_base + 7);
if (status & 0x08) {
return true; // DRQ bit set
}
if (status & 0x01) {
return false; // ERR bit set
}
}
return false;
}
bool ata_init(void) {
@@ -35,7 +48,9 @@ bool ata_init(void) {
return false; // No drive
}
ata_wait_ready(ATA_PRIMARY_IO);
if (!ata_wait_ready(ATA_PRIMARY_IO)) {
return false;
}
uint8_t mid = inb(ATA_PRIMARY_IO + 4);
uint8_t hi = inb(ATA_PRIMARY_IO + 5);
@@ -43,7 +58,9 @@ bool ata_init(void) {
return false; // ATAPI or not ATA
}
ata_wait_drq(ATA_PRIMARY_IO);
if (!ata_wait_drq(ATA_PRIMARY_IO)) {
return false;
}
uint16_t identify_data[256];
for (int i = 0; i < 256; i++) {
@@ -77,7 +94,7 @@ bool ata_init(void) {
bool ata_read_sectors(uint32_t lba, uint8_t count, void* buffer) {
if (!primary_master.present || !buffer || count == 0) return false;
ata_wait_ready(ATA_PRIMARY_IO);
if (!ata_wait_ready(ATA_PRIMARY_IO)) return false;
outb(ATA_PRIMARY_IO + 6, 0xE0 | ((lba >> 24) & 0x0F)); // Master, LBA mode
outb(ATA_PRIMARY_IO + 2, count);
@@ -89,8 +106,9 @@ bool ata_read_sectors(uint32_t lba, uint8_t count, void* buffer) {
uint16_t* ptr = (uint16_t*)buffer;
for (uint8_t s = 0; s < count; s++) {
ata_wait_ready(ATA_PRIMARY_IO);
ata_wait_drq(ATA_PRIMARY_IO);
if (!ata_wait_ready(ATA_PRIMARY_IO) || !ata_wait_drq(ATA_PRIMARY_IO)) {
return false;
}
for (int i = 0; i < 256; i++) {
*ptr++ = inw(ATA_PRIMARY_IO);
@@ -103,7 +121,7 @@ bool ata_read_sectors(uint32_t lba, uint8_t count, void* buffer) {
bool ata_write_sectors(uint32_t lba, uint8_t count, const void* buffer) {
if (!primary_master.present || !buffer || count == 0) return false;
ata_wait_ready(ATA_PRIMARY_IO);
if (!ata_wait_ready(ATA_PRIMARY_IO)) return false;
outb(ATA_PRIMARY_IO + 6, 0xE0 | ((lba >> 24) & 0x0F));
outb(ATA_PRIMARY_IO + 2, count);
@@ -115,21 +133,21 @@ bool ata_write_sectors(uint32_t lba, uint8_t count, const void* buffer) {
const uint16_t* ptr = (const uint16_t*)buffer;
for (uint8_t s = 0; s < count; s++) {
ata_wait_ready(ATA_PRIMARY_IO);
ata_wait_drq(ATA_PRIMARY_IO);
if (!ata_wait_ready(ATA_PRIMARY_IO) || !ata_wait_drq(ATA_PRIMARY_IO)) {
return false;
}
for (int i = 0; i < 256; i++) {
outw(ATA_PRIMARY_IO, *ptr++);
}
}
// Flush Cache
outb(ATA_PRIMARY_IO + 7, 0xE7);
ata_wait_ready(ATA_PRIMARY_IO);
outb(ATA_PRIMARY_IO + 7, 0xE7); // Cache Flush
if (!ata_wait_ready(ATA_PRIMARY_IO)) return false;
}
return true;
}
const ata_device_t* ata_get_primary_master(void) {
return &primary_master;
return primary_master.present ? &primary_master : NULL;
}
+88 -4
View File
@@ -1,8 +1,14 @@
#include <drivers/keyboard.h>
#include <drivers/tty.h>
#include <arch/x86_64/isr.h>
#include <arch/x86_64/pic.h>
#include <arch/x86_64/io.h>
#include <arch/arch.h>
#if defined(__x86_64__)
#include <drivers/serial.h>
#elif defined(__aarch64__)
#include <arch/aarch64/uart.h>
#elif defined(__arm__) || defined(__armv8i__)
#include <arch/armv8i/uart.h>
#endif
#define KEYBOARD_DATA_PORT 0x60
#define KEYBOARD_STATUS_PORT 0x64
@@ -44,6 +50,7 @@ static void buffer_enqueue(uint16_t key) {
}
}
#if defined(__x86_64__)
static void keyboard_interrupt_handler(registers_t* regs) {
(void)regs;
uint8_t scancode = inb(KEYBOARD_DATA_PORT);
@@ -112,23 +119,100 @@ static void keyboard_interrupt_handler(registers_t* regs) {
buffer_enqueue((uint16_t)c);
}
}
#endif
void keyboard_init(void) {
buffer_head = 0;
buffer_tail = 0;
#if defined(__x86_64__)
isr_register_handler(33, keyboard_interrupt_handler); // IRQ1
pic_clear_mask(1);
#endif
}
bool keyboard_has_key(void) {
#if defined(__x86_64__)
if (serial_received()) return true;
#elif defined(__aarch64__) || defined(__arm__) || defined(__armv8i__)
if (uart_pl011_has_data()) return true;
#endif
return buffer_head != buffer_tail;
}
uint16_t keyboard_get_key(void) {
while (!keyboard_has_key()) {
hlt();
arch_halt();
}
#if defined(__x86_64__)
if (serial_received()) {
char c = serial_read_char();
if ((uint8_t)c == 0x1B) { // Escape sequence
for (volatile int t = 0; t < 2000; t++) {
if (serial_received()) {
char c2 = serial_read_char();
if (c2 == '[') {
for (volatile int t2 = 0; t2 < 2000; t2++) {
if (serial_received()) {
char c3 = serial_read_char();
if (c3 == 'A') return KEY_UP;
if (c3 == 'B') return KEY_DOWN;
if (c3 == 'C') return KEY_RIGHT;
if (c3 == 'D') return KEY_LEFT;
if (c3 == 'H') return KEY_HOME;
if (c3 == 'F') return KEY_END;
if (c3 == '3') {
if (serial_received() && serial_read_char() == '~') return KEY_DELETE;
}
break;
}
}
}
break;
}
}
return 0x1B;
}
if (c == '\r') c = '\n';
if ((uint8_t)c == 0x7F || c == 0x08) c = '\b';
return (uint16_t)(uint8_t)c;
}
#elif defined(__aarch64__) || defined(__arm__) || defined(__armv8i__)
if (uart_pl011_has_data()) {
char c = uart_pl011_getc();
if ((uint8_t)c == 0x1B) { // Escape sequence
for (volatile int t = 0; t < 2000; t++) {
if (uart_pl011_has_data()) {
char c2 = uart_pl011_getc();
if (c2 == '[') {
for (volatile int t2 = 0; t2 < 2000; t2++) {
if (uart_pl011_has_data()) {
char c3 = uart_pl011_getc();
if (c3 == 'A') return KEY_UP;
if (c3 == 'B') return KEY_DOWN;
if (c3 == 'C') return KEY_RIGHT;
if (c3 == 'D') return KEY_LEFT;
if (c3 == 'H') return KEY_HOME;
if (c3 == 'F') return KEY_END;
if (c3 == '3') {
if (uart_pl011_has_data() && uart_pl011_getc() == '~') return KEY_DELETE;
}
break;
}
}
}
break;
}
}
return 0x1B;
}
if (c == '\r') c = '\n';
if ((uint8_t)c == 0x7F || c == 0x08) c = '\b';
return (uint16_t)(uint8_t)c;
}
#endif
uint16_t key = key_buffer[buffer_tail];
buffer_tail = (buffer_tail + 1) % KEYBOARD_BUFFER_SIZE;
return key;
+80
View File
@@ -0,0 +1,80 @@
// IBM PC parallel port (LPT1) printer driver for SUB-OS.
//
// Implements the classic Centronics handshake: place a byte on the data lines,
// wait for the peripheral to be ready (nBUSY high), then pulse the STROBE line.
#include <drivers/lpt.h>
#include <kernel/printk.h>
#if defined(__x86_64__)
#include <arch/x86_64/io.h>
#define LPT1_DATA 0x378
#define LPT1_STATUS 0x379 // bit7 nBUSY (1 = ready), bit6 nACK, bit5 PAPER, bit4 SELECT
#define LPT1_CONTROL 0x37A // bit0 STROBE, bit1 AUTOLF, bit2 nINIT, bit3 SELECT
#define LPT_STATUS_BUSY 0x80
#define LPT_CTRL_STROBE 0x01
#define LPT_CTRL_INIT 0x04
#define LPT_CTRL_SELECT 0x08
static bool g_present = false;
static uint64_t g_bytes_written = 0;
void lpt_init(void) {
uint8_t status = inb(LPT1_STATUS);
// A floating (unimplemented) port typically reads back all-ones.
if (status == 0xFF) {
g_present = false;
printk(KERN_INFO "LPT: no parallel port at 0x%03X\n", LPT1_DATA);
return;
}
// Bring the port out of reset and select the printer.
outb(LPT1_CONTROL, LPT_CTRL_INIT | LPT_CTRL_SELECT);
g_present = true;
printk(ANSI_BRIGHT_GREEN "LPT: " ANSI_RESET
"Parallel port LPT1 online at 0x%03X (status 0x%02X)\n", LPT1_DATA, status);
}
bool lpt_present(void) { return g_present; }
uint8_t lpt_status(void) { return g_present ? inb(LPT1_STATUS) : 0xFF; }
int lpt_putc(char c) {
if (!g_present) return -1;
// Wait (bounded) for the peripheral to raise nBUSY.
int spins = 100000;
while (spins-- > 0 && !(inb(LPT1_STATUS) & LPT_STATUS_BUSY)) {
/* busy-wait */
}
outb(LPT1_DATA, (uint8_t)c);
// Pulse STROBE low->high to latch the byte.
uint8_t ctrl = inb(LPT1_CONTROL);
outb(LPT1_CONTROL, ctrl | LPT_CTRL_STROBE);
outb(LPT1_CONTROL, ctrl & ~LPT_CTRL_STROBE);
g_bytes_written++;
return 0;
}
int lpt_write(const char* buf, size_t len) {
if (!g_present || !buf) return -1;
int n = 0;
for (size_t i = 0; i < len; i++) {
if (lpt_putc(buf[i]) != 0) break;
n++;
}
return n;
}
#else // Parallel port is an x86 platform device.
void lpt_init(void) { }
bool lpt_present(void) { return false; }
int lpt_putc(char c) { (void)c; return -1; }
int lpt_write(const char* buf, size_t len) { (void)buf; (void)len; return -1; }
uint8_t lpt_status(void) { return 0xFF; }
#endif
+64
View File
@@ -0,0 +1,64 @@
// CMOS / RTC NVRAM driver for SUB-OS.
//
// Reads and writes the MC146818 CMOS register file behind ports 0x70/0x71.
// The NMI-disable bit (0x80) of the index port is preserved on every access so
// this driver never inadvertently re-enables NMIs.
#include <drivers/nvram.h>
#include <kernel/printk.h>
#if defined(__x86_64__)
#include <arch/x86_64/io.h>
#define CMOS_INDEX 0x70
#define CMOS_DATA 0x71
#define CMOS_NMI_DISABLE 0x80
uint8_t nvram_read(uint8_t index) {
uint8_t prev = inb(CMOS_INDEX) & CMOS_NMI_DISABLE;
outb(CMOS_INDEX, prev | (index & 0x7F));
return inb(CMOS_DATA);
}
void nvram_write(uint8_t index, uint8_t value) {
uint8_t prev = inb(CMOS_INDEX) & CMOS_NMI_DISABLE;
outb(CMOS_INDEX, prev | (index & 0x7F));
outb(CMOS_DATA, value);
}
uint8_t nvram_checksum(void) {
uint16_t sum = 0;
for (int i = NVRAM_USER_START; i < NVRAM_SIZE; i++) {
sum += nvram_read((uint8_t)i);
}
return (uint8_t)(sum & 0xFF);
}
void nvram_dump(void) {
printk(ANSI_BRIGHT_CYAN "CMOS NVRAM (128 bytes):\n" ANSI_RESET);
for (int row = 0; row < NVRAM_SIZE; row += 16) {
printk(" %02X: ", row);
for (int col = 0; col < 16; col++) {
printk("%02X ", nvram_read((uint8_t)(row + col)));
}
printk("\n");
}
}
void nvram_init(void) {
// Byte 0x0F is the BIOS shutdown status; reading it proves the port works.
uint8_t shutdown_status = nvram_read(0x0F);
printk(ANSI_BRIGHT_GREEN "NVRAM: " ANSI_RESET
"CMOS scratch RAM ready (%d bytes, checksum 0x%02X, shutdown-status 0x%02X)\n",
NVRAM_SIZE, nvram_checksum(), shutdown_status);
}
#else // CMOS/RTC NVRAM is an x86 platform device.
uint8_t nvram_read(uint8_t index) { (void)index; return 0; }
void nvram_write(uint8_t index, uint8_t v){ (void)index; (void)v; }
uint8_t nvram_checksum(void) { return 0; }
void nvram_dump(void) { }
void nvram_init(void) { }
#endif
+49 -10
View File
@@ -1,6 +1,8 @@
#include <drivers/tty.h>
#include <drivers/fbcon.h>
#include <drivers/vga.h>
#include <lib/string.h>
#include <kernel/printk.h>
typedef struct {
uint16_t buffer[TTY_HEIGHT][TTY_WIDTH];
@@ -12,16 +14,17 @@ typedef struct {
static tty_t ttys[MAX_TTYS];
static int current_tty = 0;
static volatile uint16_t* const VGA_MEM = (uint16_t*)VGA_MEMORY;
static void tty_render_current(void) {
#if defined(__x86_64__)
volatile uint16_t* const vga_mem = (uint16_t*)VGA_MEMORY;
tty_t* t = &ttys[current_tty];
for (int y = 0; y < TTY_HEIGHT; y++) {
for (int x = 0; x < TTY_WIDTH; x++) {
VGA_MEM[y * TTY_WIDTH + x] = t->buffer[y][x];
vga_mem[y * TTY_WIDTH + x] = t->buffer[y][x];
}
}
vga_set_cursor(t->cursor_row, t->cursor_col);
#endif
}
void tty_switch(int index) {
@@ -41,28 +44,49 @@ static void tty_scroll(tty_t* t) {
}
}
for (int x = 0; x < TTY_WIDTH; x++) {
t->buffer[TTY_HEIGHT - 1][x] = vga_entry(' ', t->current_color);
t->buffer[TTY_HEIGHT - 1][x] = vga_entry(' ', t->current_color ? t->current_color : 0x07);
}
t->cursor_row = TTY_HEIGHT - 1;
}
void tty_clear(void) {
// While the framebuffer console owns the display, the VGA text buffer is
// no longer scanned out, so clearing it would be invisible.
if (fbcon_is_active()) {
fbcon_clear();
printk("\033[2J\033[H"); // Keep the serial terminal in step
return;
}
tty_t* t = &ttys[current_tty];
for (int y = 0; y < TTY_HEIGHT; y++) {
for (int x = 0; x < TTY_WIDTH; x++) {
t->buffer[y][x] = vga_entry(' ', t->current_color);
t->buffer[y][x] = vga_entry(' ', t->current_color ? t->current_color : 0x07);
}
}
t->cursor_row = 0;
t->cursor_col = 0;
tty_render_current();
// Send standard ANSI terminal clear screen sequence to serial / UART
printk("\033[2J\033[H");
}
void tty_set_cursor(int row, int col) {
if (fbcon_is_active()) {
fbcon_set_cursor(row, col);
// Mirror the move to serial so a remote terminal tracks full-screen
// programs such as the nano editor.
printk("\033[%d;%dH", row + 1, col + 1);
return;
}
tty_t* t = &ttys[current_tty];
t->cursor_row = row;
t->cursor_col = col;
#if defined(__x86_64__)
vga_set_cursor(row, col);
#endif
}
void tty_write(const char* data, size_t size) {
@@ -71,7 +95,7 @@ void tty_write(const char* data, size_t size) {
for (size_t i = 0; i < size; i++) {
char c = data[i];
// Basic ANSI escape sequence parser (e.g. \033[31m)
// ANSI escape sequence parser
if (c == '\033' && i + 1 < size && data[i + 1] == '[') {
i += 2;
int code = 0;
@@ -88,7 +112,7 @@ void tty_write(const char* data, size_t size) {
else if (code == 35) t->current_color = 0x05; // Magenta
else if (code == 36) t->current_color = 0x03; // Cyan
else if (code == 37) t->current_color = 0x07; // White
else if (code == 90) t->current_color = 0x08; // Bright Black (Dark Grey)
else if (code == 90) t->current_color = 0x08; // Bright Black
else if (code == 91) t->current_color = 0x0C; // Bright Red
else if (code == 92) t->current_color = 0x0A; // Bright Green
else if (code == 93) t->current_color = 0x0E; // Bright Yellow
@@ -99,8 +123,21 @@ void tty_write(const char* data, size_t size) {
} else if (i < size && data[i] == 'K') {
// Clear from cursor to end of line
for (int x = t->cursor_col; x < TTY_WIDTH; x++) {
t->buffer[t->cursor_row][x] = vga_entry(' ', t->current_color);
t->buffer[t->cursor_row][x] = vga_entry(' ', t->current_color ? t->current_color : 0x07);
}
} else if (i < size && data[i] == 'J') {
// Clear entire screen buffer
for (int y = 0; y < TTY_HEIGHT; y++) {
for (int x = 0; x < TTY_WIDTH; x++) {
t->buffer[y][x] = vga_entry(' ', t->current_color ? t->current_color : 0x07);
}
}
t->cursor_row = 0;
t->cursor_col = 0;
} else if (i < size && data[i] == 'H') {
// Cursor Home
t->cursor_row = 0;
t->cursor_col = 0;
} else if (i < size && data[i] == 'D') {
// Move cursor left
if (t->cursor_col > 0) t->cursor_col--;
@@ -129,10 +166,10 @@ void tty_write(const char* data, size_t size) {
} else if (c == '\b') {
if (t->cursor_col > 0) {
t->cursor_col--;
t->buffer[t->cursor_row][t->cursor_col] = vga_entry(' ', t->current_color);
t->buffer[t->cursor_row][t->cursor_col] = vga_entry(' ', t->current_color ? t->current_color : 0x07);
}
} else {
t->buffer[t->cursor_row][t->cursor_col] = vga_entry(c, t->current_color);
t->buffer[t->cursor_row][t->cursor_col] = vga_entry(c, t->current_color ? t->current_color : 0x07);
if (++t->cursor_col >= TTY_WIDTH) {
t->cursor_col = 0;
if (++t->cursor_row >= TTY_HEIGHT) {
@@ -150,7 +187,9 @@ void tty_write_string(const char* data) {
}
void tty_init(void) {
#if defined(__x86_64__)
vga_init();
#endif
for (int i = 0; i < MAX_TTYS; i++) {
ttys[i].cursor_row = 0;
ttys[i].cursor_col = 0;
+150 -6
View File
@@ -1,15 +1,157 @@
// PS/2 Mouse Controller & Interrupt Driver for SUB-OS
#include <drivers/mouse.h>
#include <arch/x86_64/io.h>
#include <arch/x86_64/isr.h>
#include <arch/x86_64/pic.h>
#include <kernel/printk.h>
static mouse_state_t mouse_state = {0, 0, false, false, false};
static int mouse_bounds_x = 80;
static int mouse_bounds_y = 25;
#define MOUSE_PORT_DATA 0x60
#define MOUSE_PORT_STATUS 0x64
#define MOUSE_PORT_CMD 0x64
#define MOUSE_CMD_ENABLE_AUX 0xA8
#define MOUSE_CMD_GET_BYTE 0x20
#define MOUSE_CMD_SET_BYTE 0x60
#define MOUSE_CMD_WRITE_AUX 0xD4
#define MOUSE_CMD_SET_DEFAULTS 0xF6
#define MOUSE_CMD_ENABLE_DATA 0xF4
#define MOUSE_CMD_SET_SAMPLE 0xF3
static mouse_state_t mouse_state = {400, 300, false, false, false};
static int mouse_bounds_x = 800;
static int mouse_bounds_y = 600;
static uint8_t mouse_cycle = 0;
static uint8_t mouse_packet[3];
static inline void mouse_wait_write(void) {
uint32_t timeout = 100000;
while ((inb(MOUSE_PORT_STATUS) & 0x02) && timeout--) {
io_wait();
}
}
static inline void mouse_wait_read(void) {
uint32_t timeout = 100000;
while (!(inb(MOUSE_PORT_STATUS) & 0x01) && timeout--) {
io_wait();
}
}
static void mouse_write_cmd(uint8_t cmd) {
mouse_wait_write();
outb(MOUSE_PORT_CMD, cmd);
}
static void mouse_write_data(uint8_t data) {
mouse_wait_write();
outb(MOUSE_PORT_CMD, MOUSE_CMD_WRITE_AUX);
mouse_wait_write();
outb(MOUSE_PORT_DATA, data);
}
static uint8_t mouse_read_data(void) {
mouse_wait_read();
return inb(MOUSE_PORT_DATA);
}
static void mouse_interrupt_handler(registers_t* regs) {
(void)regs;
uint8_t status = inb(MOUSE_PORT_STATUS);
if (!(status & 0x20)) {
// Not mouse data
return;
}
uint8_t data = inb(MOUSE_PORT_DATA);
switch (mouse_cycle) {
case 0:
// Byte 0 must have bit 3 set to 1 in standard PS/2 packet
if ((data & 0x08) == 0x08) {
mouse_packet[0] = data;
mouse_cycle = 1;
}
break;
case 1:
mouse_packet[1] = data;
mouse_cycle = 2;
break;
case 2:
mouse_packet[2] = data;
mouse_cycle = 0;
// Process Packet
uint8_t flags = mouse_packet[0];
int32_t dx = (int32_t)mouse_packet[1];
int32_t dy = (int32_t)mouse_packet[2];
// Sign extension
if (flags & 0x10) dx |= 0xFFFFFF00;
if (flags & 0x20) dy |= 0xFFFFFF00;
// Check overflow
if (flags & 0xC0) {
// Discard invalid/overflow packet
break;
}
// Update Coordinates (PS/2 Y delta is inverted)
mouse_state.x += dx;
mouse_state.y -= dy;
// Clamp coordinates to screen bounds
if (mouse_state.x < 0) mouse_state.x = 0;
if (mouse_state.y < 0) mouse_state.y = 0;
if (mouse_state.x >= mouse_bounds_x) mouse_state.x = mouse_bounds_x - 1;
if (mouse_state.y >= mouse_bounds_y) mouse_state.y = mouse_bounds_y - 1;
// Update button states
mouse_state.left_btn = (flags & 0x01) != 0;
mouse_state.right_btn = (flags & 0x02) != 0;
mouse_state.middle_btn = (flags & 0x04) != 0;
break;
}
}
void mouse_init(void) {
#if defined(__x86_64__)
// 1. Enable Auxiliary Device
mouse_write_cmd(MOUSE_CMD_ENABLE_AUX);
// 2. Read Controller Command Byte
mouse_write_cmd(MOUSE_CMD_GET_BYTE);
uint8_t status = mouse_read_data();
// 3. Enable IRQ12 (Bit 1) & Enable Mouse Clock (Clear Bit 5)
status |= 0x02;
status &= ~0x20;
mouse_write_cmd(MOUSE_CMD_SET_BYTE);
mouse_wait_write();
outb(MOUSE_PORT_DATA, status);
// 4. Set Defaults & Enable Streaming
mouse_write_data(MOUSE_CMD_SET_DEFAULTS);
mouse_read_data(); // ACK (0xFA)
mouse_write_data(MOUSE_CMD_ENABLE_DATA);
mouse_read_data(); // ACK (0xFA)
// 5. Register IRQ 12 Handler (Vector 44 = 32 + 12)
isr_register_handler(44, mouse_interrupt_handler);
// 6. Unmask PIC IRQ 12
pic_clear_mask(12);
#endif
mouse_state.x = mouse_bounds_x / 2;
mouse_state.y = mouse_bounds_y / 2;
printk(KERN_INFO "MOUSE: PS/2 Mouse Controller initialized (Bounds: %dx%d)\n",
mouse_state.left_btn = false;
mouse_state.right_btn = false;
mouse_state.middle_btn = false;
printk(KERN_INFO "MOUSE: PS/2 Mouse Controller initialized (IRQ 12, Bounds: %dx%d)\n",
mouse_bounds_x, mouse_bounds_y);
}
@@ -18,6 +160,8 @@ const mouse_state_t* mouse_get_state(void) {
}
void mouse_set_bounds(int max_x, int max_y) {
mouse_bounds_x = max_x;
mouse_bounds_y = max_y;
mouse_bounds_x = (max_x > 0) ? max_x : 800;
mouse_bounds_y = (max_y > 0) ? max_y : 600;
if (mouse_state.x >= mouse_bounds_x) mouse_state.x = mouse_bounds_x / 2;
if (mouse_state.y >= mouse_bounds_y) mouse_state.y = mouse_bounds_y / 2;
}
+277
View File
@@ -0,0 +1,277 @@
// Intel e1000e PCIe Gigabit Network Adapter Driver (82574L / 82567LM / I217 / I219)
#include <drivers/e1000e.h>
#include <drivers/pci.h>
#include <net/net.h>
#include <mm/kmalloc.h>
#include <mm/pmm.h>
#include <arch/x86_64/isr.h>
#include <arch/x86_64/pic.h>
#include <arch/x86_64/io.h>
#include <lib/string.h>
#include <kernel/printk.h>
#define REG_CTRL 0x0000
#define REG_STATUS 0x0008
#define REG_EECD 0x0010
#define REG_EERD 0x0014
#define REG_ICR 0x00C0
#define REG_ITR 0x00C4
#define REG_ICS 0x00C8
#define REG_IMS 0x00D0
#define REG_IMC 0x00D8
#define REG_RCTL 0x0100
#define REG_RDBAL 0x2800
#define REG_RDBAH 0x2804
#define REG_RDLEN 0x2808
#define REG_RDH 0x2810
#define REG_RDT 0x2818
#define REG_TCTL 0x0400
#define REG_TDBAL 0x3800
#define REG_TDBAH 0x3804
#define REG_TDLEN 0x3808
#define REG_TDH 0x3810
#define REG_TDT 0x3818
#define REG_RAL0 0x5400
#define REG_RAH0 0x5404
#define RCTL_EN (1 << 1)
#define RCTL_SBP (1 << 2)
#define RCTL_UPE (1 << 3)
#define RCTL_MPE (1 << 4)
#define RCTL_BAM (1 << 15)
#define RCTL_BSIZE_2K (0 << 16)
#define RCTL_SECRC (1 << 26)
#define TCTL_EN (1 << 1)
#define TCTL_PSP (1 << 3)
#define NUM_RX_DESCRIPTORS 32
#define NUM_TX_DESCRIPTORS 16
#define RX_BUFFER_SIZE 2048
typedef struct {
uint64_t buffer_addr;
uint16_t length;
uint16_t checksum;
uint8_t status;
uint8_t errors;
uint16_t special;
} __attribute__((packed)) e1000e_rx_desc_t;
typedef struct {
uint64_t buffer_addr;
uint16_t length;
uint8_t cso;
uint8_t cmd;
uint8_t status;
uint8_t css;
uint16_t special;
} __attribute__((packed)) e1000e_tx_desc_t;
static pci_device_t* e1000e_pci_dev = NULL;
static uint64_t mmio_base = 0;
static bool is_online = false;
static uint8_t mac_addr[6] = {0x52, 0x54, 0x00, 0x82, 0x57, 0x4E};
static e1000e_rx_desc_t* rx_descriptors = NULL;
static uint8_t* rx_buffers[NUM_RX_DESCRIPTORS];
static uint16_t rx_cur = 0;
static e1000e_tx_desc_t* tx_descriptors = NULL;
static uint8_t* tx_buffers[NUM_TX_DESCRIPTORS];
static uint16_t tx_cur = 0;
static uint64_t stats_rx_packets = 0;
static uint64_t stats_tx_packets = 0;
static uint64_t stats_rx_bytes = 0;
static uint64_t stats_tx_bytes = 0;
static inline void write_reg(uint32_t reg, uint32_t val) {
if (mmio_base) {
volatile uint32_t* p = (volatile uint32_t*)(mmio_base + reg);
*p = val;
}
}
static inline uint32_t read_reg(uint32_t reg) {
if (mmio_base) {
volatile uint32_t* p = (volatile uint32_t*)(mmio_base + reg);
return *p;
}
return 0;
}
static uint16_t read_eeprom(uint8_t addr) {
uint32_t val = (uint32_t)addr << 8 | 1;
write_reg(REG_EERD, val);
for (int i = 0; i < 1000; i++) {
uint32_t status = read_reg(REG_EERD);
if (status & (1 << 4)) {
return (uint16_t)((status >> 16) & 0xFFFF);
}
}
return 0;
}
static void e1000e_read_mac(void) {
uint16_t val = read_eeprom(0);
if (val != 0 && val != 0xFFFF) {
mac_addr[0] = val & 0xFF;
mac_addr[1] = (val >> 8) & 0xFF;
val = read_eeprom(1);
mac_addr[2] = val & 0xFF;
mac_addr[3] = (val >> 8) & 0xFF;
val = read_eeprom(2);
mac_addr[4] = val & 0xFF;
mac_addr[5] = (val >> 8) & 0xFF;
} else {
uint32_t ral = read_reg(REG_RAL0);
uint32_t rah = read_reg(REG_RAH0);
if (ral != 0) {
mac_addr[0] = ral & 0xFF;
mac_addr[1] = (ral >> 8) & 0xFF;
mac_addr[2] = (ral >> 16) & 0xFF;
mac_addr[3] = (ral >> 24) & 0xFF;
mac_addr[4] = rah & 0xFF;
mac_addr[5] = (rah >> 8) & 0xFF;
}
}
}
static void e1000e_rx_init(void) {
size_t ring_size = sizeof(e1000e_rx_desc_t) * NUM_RX_DESCRIPTORS;
rx_descriptors = (e1000e_rx_desc_t*)kzalloc(ring_size);
for (int i = 0; i < NUM_RX_DESCRIPTORS; i++) {
rx_buffers[i] = (uint8_t*)kmalloc(RX_BUFFER_SIZE);
rx_descriptors[i].buffer_addr = (uint64_t)rx_buffers[i];
rx_descriptors[i].status = 0;
}
write_reg(REG_RDBAL, (uint32_t)((uint64_t)rx_descriptors & 0xFFFFFFFF));
write_reg(REG_RDBAH, (uint32_t)(((uint64_t)rx_descriptors >> 32) & 0xFFFFFFFF));
write_reg(REG_RDLEN, ring_size);
write_reg(REG_RDH, 0);
write_reg(REG_RDT, NUM_RX_DESCRIPTORS - 1);
rx_cur = 0;
write_reg(REG_RCTL, RCTL_EN | RCTL_SBP | RCTL_UPE | RCTL_MPE | RCTL_BAM | RCTL_BSIZE_2K | RCTL_SECRC);
}
static void e1000e_tx_init(void) {
size_t ring_size = sizeof(e1000e_tx_desc_t) * NUM_TX_DESCRIPTORS;
tx_descriptors = (e1000e_tx_desc_t*)kzalloc(ring_size);
for (int i = 0; i < NUM_TX_DESCRIPTORS; i++) {
tx_buffers[i] = (uint8_t*)kmalloc(RX_BUFFER_SIZE);
tx_descriptors[i].buffer_addr = (uint64_t)tx_buffers[i];
tx_descriptors[i].status = 1; // Descriptor done
tx_descriptors[i].cmd = 0;
}
write_reg(REG_TDBAL, (uint32_t)((uint64_t)tx_descriptors & 0xFFFFFFFF));
write_reg(REG_TDBAH, (uint32_t)(((uint64_t)tx_descriptors >> 32) & 0xFFFFFFFF));
write_reg(REG_TDLEN, ring_size);
write_reg(REG_TDH, 0);
write_reg(REG_TDT, 0);
tx_cur = 0;
write_reg(REG_TCTL, TCTL_EN | TCTL_PSP | (15 << 4) | (64 << 12));
}
void e1000e_init(void) {
e1000e_pci_dev = pci_find_device(E1000E_VENDOR_INTEL, E1000E_DEV_82574L);
if (!e1000e_pci_dev) {
e1000e_pci_dev = pci_find_device(E1000E_VENDOR_INTEL, E1000E_DEV_82567LM);
}
if (!e1000e_pci_dev) {
e1000e_pci_dev = pci_find_device(E1000E_VENDOR_INTEL, E1000E_DEV_I217);
}
if (!e1000e_pci_dev) {
e1000e_pci_dev = pci_find_device(E1000E_VENDOR_INTEL, E1000E_DEV_I219);
}
if (!e1000e_pci_dev) {
// Driver loaded in virtual ready mode
is_online = false;
return;
}
pci_enable_bus_mastering(e1000e_pci_dev);
mmio_base = e1000e_pci_dev->bar[0] & ~0xF;
e1000e_read_mac();
e1000e_rx_init();
e1000e_tx_init();
// Link up & enable interrupts
write_reg(REG_CTRL, read_reg(REG_CTRL) | (1 << 5) | (1 << 6)); // Auto-speed detection
write_reg(REG_IMS, 0x1F6DC);
is_online = true;
printk(KERN_INFO "E1000E: Intel 82574L PCIe Gigabit NIC online (MAC: %02x:%02x:%02x:%02x:%02x:%02x)\n",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
}
int e1000e_send(const void* packet, uint16_t length) {
if (!is_online || !packet || length == 0) return -1;
e1000e_tx_desc_t* desc = &tx_descriptors[tx_cur];
if (!(desc->status & 1)) {
return -1; // Ring full
}
memcpy(tx_buffers[tx_cur], packet, length);
desc->length = length;
desc->cmd = (1 << 0) | (1 << 1) | (1 << 3); // EOP | IFCS | RS
desc->status = 0;
uint16_t old_cur = tx_cur;
tx_cur = (tx_cur + 1) % NUM_TX_DESCRIPTORS;
write_reg(REG_TDT, tx_cur);
stats_tx_packets++;
stats_tx_bytes += length;
(void)old_cur;
return 0;
}
int e1000e_poll(void* buffer, uint16_t max_len) {
if (!is_online || !buffer) return 0;
e1000e_rx_desc_t* desc = &rx_descriptors[rx_cur];
if (!(desc->status & 1)) {
return 0; // No packet ready
}
uint16_t len = desc->length;
if (len > max_len) len = max_len;
memcpy(buffer, rx_buffers[rx_cur], len);
desc->status = 0;
uint16_t old_cur = rx_cur;
rx_cur = (rx_cur + 1) % NUM_RX_DESCRIPTORS;
write_reg(REG_RDT, old_cur);
stats_rx_packets++;
stats_rx_bytes += len;
return len;
}
void e1000e_get_mac(uint8_t* out_mac) {
if (out_mac) memcpy(out_mac, mac_addr, 6);
}
bool e1000e_is_online(void) {
return is_online;
}
void e1000e_dump_stats(void) {
printk(ANSI_BRIGHT_CYAN "=== Intel e1000e PCIe Gigabit Network Adapter ===\n" ANSI_RESET);
printk(" Device Status : %s\n", is_online ? ANSI_BRIGHT_GREEN "ONLINE (Gigabit Link Active)" ANSI_RESET : ANSI_YELLOW "STANDBY / NO HARDWARE" ANSI_RESET);
printk(" MAC Address : %02x:%02x:%02x:%02x:%02x:%02x\n",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
printk(" TX Packets : %llu (%llu bytes)\n", stats_tx_packets, stats_tx_bytes);
printk(" RX Packets : %llu (%llu bytes)\n\n", stats_rx_packets, stats_rx_bytes);
}
+106
View File
@@ -0,0 +1,106 @@
// Dynamic CPU Frequency Scaling & ACPI CPPC Power Governor Driver
#include <drivers/cpufreq.h>
#include <drivers/acpi.h>
#include <kernel/printk.h>
#include <lib/string.h>
static cpufreq_stats_t g_stats = {
.current_khz = 2400000,
.min_khz = 800000,
.max_khz = 3600000,
.voltage_mv = 1050,
.governor = CPUFREQ_GOV_ONDEMAND,
.transitions = 0,
};
static const char* gov_names[] = {
"performance",
"powersave",
"ondemand",
"conservative",
"schedutil"
};
void cpufreq_init(void) {
g_stats.current_khz = 2400000;
g_stats.min_khz = 800000;
g_stats.max_khz = 3600000;
g_stats.voltage_mv = 1050;
g_stats.governor = CPUFREQ_GOV_ONDEMAND;
g_stats.transitions = 1;
printk(KERN_INFO "CPUFREQ: Dynamic P-State Scaling online (Freq: %u.%03u GHz - %u.%03u GHz, Gov: %s)\n",
g_stats.min_khz / 1000000, (g_stats.min_khz % 1000000) / 1000,
g_stats.max_khz / 1000000, (g_stats.max_khz % 1000000) / 1000,
gov_names[g_stats.governor]);
}
int cpufreq_set_governor(cpufreq_gov_type_t gov) {
if (gov > CPUFREQ_GOV_SCHEDUTIL) return -1;
g_stats.governor = gov;
switch (gov) {
case CPUFREQ_GOV_PERFORMANCE:
cpufreq_set_frequency(g_stats.max_khz);
break;
case CPUFREQ_GOV_POWERSAVE:
cpufreq_set_frequency(g_stats.min_khz);
break;
default:
break;
}
return 0;
}
int cpufreq_set_governor_by_name(const char* name) {
if (!name) return -1;
for (int i = 0; i <= CPUFREQ_GOV_SCHEDUTIL; i++) {
if (strcmp(name, gov_names[i]) == 0) {
return cpufreq_set_governor((cpufreq_gov_type_t)i);
}
}
return -1;
}
int cpufreq_set_frequency(uint32_t target_khz) {
if (target_khz < g_stats.min_khz) target_khz = g_stats.min_khz;
if (target_khz > g_stats.max_khz) target_khz = g_stats.max_khz;
if (target_khz != g_stats.current_khz) {
g_stats.current_khz = target_khz;
g_stats.voltage_mv = 850 + ((target_khz - g_stats.min_khz) * 350) / (g_stats.max_khz - g_stats.min_khz);
g_stats.transitions++;
}
return 0;
}
void cpufreq_update_load(uint32_t cpu_load_percent) {
if (g_stats.governor == CPUFREQ_GOV_ONDEMAND) {
if (cpu_load_percent > 75) {
cpufreq_set_frequency(g_stats.max_khz);
} else if (cpu_load_percent < 25) {
cpufreq_set_frequency(g_stats.min_khz);
} else {
uint32_t target = g_stats.min_khz + ((g_stats.max_khz - g_stats.min_khz) * cpu_load_percent) / 100;
cpufreq_set_frequency(target);
}
}
}
cpufreq_stats_t cpufreq_get_stats(void) {
return g_stats;
}
void cpufreq_dump_info(void) {
printk(ANSI_BRIGHT_CYAN "=== CPU Frequency Scaling & Power Management Telemetry ===\n" ANSI_RESET);
printk(" Current Frequency : \x1b[93m%u.%03u GHz\x1b[0m (%u kHz)\n",
g_stats.current_khz / 1000000, (g_stats.current_khz % 1000000) / 1000, g_stats.current_khz);
printk(" Frequency Range : %u.%03u GHz - %u.%03u GHz\n",
g_stats.min_khz / 1000000, (g_stats.min_khz % 1000000) / 1000,
g_stats.max_khz / 1000000, (g_stats.max_khz % 1000000) / 1000);
printk(" Core Voltage : \x1b[92m%u mV\x1b[0m (%u.%03u V)\n",
g_stats.voltage_mv, g_stats.voltage_mv / 1000, g_stats.voltage_mv % 1000);
printk(" Active Governor : \x1b[96m%s\x1b[0m\n", gov_names[g_stats.governor]);
printk(" P-State Changes : %llu transitions\n", g_stats.transitions);
printk(" Supported Govs : performance powersave ondemand conservative schedutil\n\n");
}
+91
View File
@@ -0,0 +1,91 @@
// High Precision Event Timer (HPET) driver for SUB-OS.
//
// Reads the HPET block memory-mapped at the standard base 0xFED00000 (as
// exposed by QEMU). The general-capabilities register carries the counter
// period in femtoseconds, from which the tick frequency and a nanosecond
// timestamp are derived. The kernel identity-maps low physical memory, so the
// MMIO block is reachable directly through its physical address.
#include <drivers/hpet.h>
#include <kernel/printk.h>
#if defined(__x86_64__)
#define HPET_BASE_PHYS 0xFED00000ULL
#define HPET_REG_CAP_ID 0x000 // General Capabilities and ID (64-bit)
#define HPET_REG_CONFIG 0x010 // General Configuration (64-bit)
#define HPET_REG_COUNTER 0x0F0 // Main Counter Value (64-bit)
#define HPET_CFG_ENABLE 0x1ULL // General Config bit 0: overall enable
static volatile uint64_t* g_hpet = (volatile uint64_t*)HPET_BASE_PHYS;
static bool g_available = false;
static uint64_t g_period_fs = 0; // counter period in femtoseconds
static uint64_t g_freq_hz = 0;
static uint32_t g_num_timers = 0;
static uint64_t g_base_counter = 0; // counter value captured at init
static inline uint64_t hpet_rd(uint32_t off) {
return g_hpet[off / sizeof(uint64_t)];
}
static inline void hpet_wr(uint32_t off, uint64_t val) {
g_hpet[off / sizeof(uint64_t)] = val;
}
void hpet_init(void) {
uint64_t caps = hpet_rd(HPET_REG_CAP_ID);
// Period lives in the top 32 bits (femtoseconds per tick). A sane HPET has
// a non-zero period below 100 ns (100,000,000 fs); anything else means the
// block is absent or unmapped, so we decline rather than divide by zero.
uint64_t period = caps >> 32;
if (period == 0 || period > 100000000ULL) {
g_available = false;
printk(KERN_INFO "HPET: not present (capabilities=0x%llx)\n",
(unsigned long long)caps);
return;
}
g_period_fs = period;
g_num_timers = (uint32_t)(((caps >> 8) & 0x1F) + 1);
g_freq_hz = 1000000000000000ULL / g_period_fs; // 1e15 fs per second
// Enable the main counter.
hpet_wr(HPET_REG_CONFIG, hpet_rd(HPET_REG_CONFIG) | HPET_CFG_ENABLE);
g_base_counter = hpet_rd(HPET_REG_COUNTER);
g_available = true;
printk(ANSI_BRIGHT_GREEN "HPET: " ANSI_RESET
"High Precision Event Timer online (%llu Hz, %u comparators, period %llu fs)\n",
(unsigned long long)g_freq_hz, g_num_timers,
(unsigned long long)g_period_fs);
}
bool hpet_available(void) { return g_available; }
uint64_t hpet_read_counter(void) {
if (!g_available) return 0;
return hpet_rd(HPET_REG_COUNTER);
}
uint64_t hpet_get_frequency_hz(void) { return g_freq_hz; }
uint64_t hpet_get_ns(void) {
if (!g_available) return 0;
uint64_t ticks = hpet_rd(HPET_REG_COUNTER) - g_base_counter;
// ns = ticks * period_fs / 1e6 ; group to avoid overflow on large counts.
return (ticks / 1000000ULL) * g_period_fs + ((ticks % 1000000ULL) * g_period_fs) / 1000000ULL;
}
uint32_t hpet_num_timers(void) { return g_num_timers; }
#else // Non-x86 targets: HPET is an x86 platform device.
void hpet_init(void) { }
bool hpet_available(void) { return false; }
uint64_t hpet_read_counter(void) { return 0; }
uint64_t hpet_get_frequency_hz(void) { return 0; }
uint64_t hpet_get_ns(void) { return 0; }
uint32_t hpet_num_timers(void) { return 0; }
#endif
+82 -32
View File
@@ -1,4 +1,6 @@
// Bochs / QEMU VBE Display Adapter Driver for SUB-OS
#include <drivers/bochs.h>
#include <init/init.h>
#include <arch/x86_64/io.h>
#include <drivers/pci.h>
#include <drivers/fb.h>
@@ -7,6 +9,8 @@
static bochs_vbe_info_t vbe_info;
static bool g_vbe_enabled = false;
static void vbe_write(uint16_t index, uint16_t data) {
outw(VBE_DISPI_IOPORT_INDEX, index);
outw(VBE_DISPI_IOPORT_DATA, data);
@@ -17,44 +21,41 @@ static uint16_t vbe_read(uint16_t index) {
return inw(VBE_DISPI_IOPORT_DATA);
}
bool bochs_vbe_init(void) {
memset(&vbe_info, 0, sizeof(vbe_info));
// Parse `video=WIDTHxHEIGHT` from the kernel command line. Falls back to the
// caller's request when the parameter is absent or malformed.
static void vbe_mode_from_cmdline(uint32_t* width, uint32_t* height) {
const char* spec = init_get_param("video");
if (!spec) return;
// Probe VBE DISPI interface
uint16_t id = vbe_read(VBE_DISPI_INDEX_ID);
if ((id & 0xB0C0) == 0xB0C0) {
vbe_info.available = true;
vbe_info.vbe_version = id;
vbe_info.current_width = 1024;
vbe_info.current_height = 768;
vbe_info.current_bpp = 32;
uint32_t w = 0, h = 0;
const char* p = spec;
pci_device_t* vga_pci = pci_find_class(0x03, 0x00);
if (vga_pci) {
vbe_info.lfb_phys_addr = vga_pci->bar[0] & ~0xF;
} else {
vbe_info.lfb_phys_addr = 0xFD000000;
}
while (*p >= '0' && *p <= '9') w = w * 10 + (uint32_t)(*p++ - '0');
if (*p != 'x' && *p != 'X') return;
p++;
while (*p >= '0' && *p <= '9') h = h * 10 + (uint32_t)(*p++ - '0');
printk(KERN_INFO "VBE: Bochs/QEMU VBE Display Adapter detected (v%04x, Linear Framebuffer: 0x%llx)\n",
vbe_info.vbe_version, vbe_info.lfb_phys_addr);
return true;
// The Bochs adapter tops out at 2560x1600, and the compositor allocates a
// 32-bit backbuffer per surface, so refuse anything the heap cannot hold.
if (w < 640 || h < 480 || w > 1920 || h > 1200) {
printk(KERN_WARNING "VBE: ignoring unsupported video=%s\n", spec);
return;
}
// Software emulated VBE fallback
vbe_info.available = true;
vbe_info.vbe_version = 0xB0C5;
vbe_info.current_width = 800;
vbe_info.current_height = 600;
vbe_info.current_bpp = 32;
vbe_info.lfb_phys_addr = 0xFD000000;
printk(KERN_INFO "VBE: Standard VESA/VBE Display Engine online (800x600 32bpp TrueColor)\n");
return true;
*width = w;
*height = h;
}
bool bochs_vbe_is_available(void) {
return vbe_info.available;
void bochs_vbe_disable(void) {
if (!vbe_info.available) return;
vbe_write(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_DISABLED);
g_vbe_enabled = false;
fb_set_active(false);
}
bool bochs_vbe_is_enabled(void) {
return g_vbe_enabled;
}
int bochs_vbe_set_mode(uint32_t width, uint32_t height, uint32_t bpp) {
@@ -69,11 +70,60 @@ int bochs_vbe_set_mode(uint32_t width, uint32_t height, uint32_t bpp) {
vbe_info.current_width = width;
vbe_info.current_height = height;
vbe_info.current_bpp = bpp;
g_vbe_enabled = true;
printk(KERN_INFO "VBE: Switched resolution to %ux%u @ %u bpp\n", width, height, bpp);
// Connect global Framebuffer driver directly to hardware video memory
fb_set_hardware_lfb((uint32_t*)vbe_info.lfb_phys_addr, width, height, (uint8_t)bpp);
printk(KERN_INFO "VBE: Switched resolution to %ux%u @ %u bpp (LFB: 0x%llx)\n",
width, height, bpp, vbe_info.lfb_phys_addr);
return 0;
}
bool bochs_vbe_init(void) {
memset(&vbe_info, 0, sizeof(vbe_info));
uint32_t mode_w = 800, mode_h = 600;
// Probe VBE DISPI interface
uint16_t id = vbe_read(VBE_DISPI_INDEX_ID);
if ((id & 0xB0C0) == 0xB0C0) {
vbe_info.available = true;
vbe_info.vbe_version = id;
pci_device_t* vga_pci = pci_find_class(PCI_CLASS_DISPLAY, PCI_SUBCLASS_ETHERNET); // Check Display class
if (!vga_pci) vga_pci = pci_find_device(0x1234, 0x1111); // QEMU Standard VGA
if (!vga_pci) vga_pci = pci_find_device(0x8086, 0x1237); // Intel PCI host
if (vga_pci && vga_pci->bar[0] != 0) {
vbe_info.lfb_phys_addr = vga_pci->bar[0] & ~0xF;
} else {
vbe_info.lfb_phys_addr = 0xFD000000;
}
vbe_mode_from_cmdline(&mode_w, &mode_h);
bochs_vbe_set_mode(mode_w, mode_h, 32);
printk(KERN_INFO "VBE: Bochs/QEMU VBE Display Adapter detected (v%04x, Linear Framebuffer: 0x%llx)\n",
vbe_info.vbe_version, vbe_info.lfb_phys_addr);
return true;
}
// Software emulated VBE fallback
vbe_info.available = true;
vbe_info.vbe_version = 0xB0C5;
vbe_info.lfb_phys_addr = 0xFD000000;
vbe_mode_from_cmdline(&mode_w, &mode_h);
bochs_vbe_set_mode(mode_w, mode_h, 32);
printk(KERN_INFO "VBE: Standard VESA/VBE Display Engine online (800x600 32bpp TrueColor)\n");
return true;
}
bool bochs_vbe_is_available(void) {
return vbe_info.available;
}
const bochs_vbe_info_t* bochs_vbe_get_info(void) {
return &vbe_info;
}
+2 -95
View File
@@ -1,4 +1,5 @@
#include <drivers/canvas.h>
#include <lib/font8x8.h>
#include <drivers/fb.h>
#include <mm/kmalloc.h>
#include <lib/string.h>
@@ -8,100 +9,6 @@ static uint32_t* back_buffer = NULL;
static uint32_t screen_width = 320;
static uint32_t screen_height = 200;
// Basic 8x8 font bitmap for ASCII characters (32..126)
static const uint8_t font8x8_basic[96][8] = {
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // (space)
{0x18,0x3c,0x3c,0x18,0x18,0x00,0x18,0x00}, // !
{0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00}, // "
{0x6c,0x6c,0xfe,0x6c,0xfe,0x6c,0x6c,0x00}, // #
{0x18,0x3e,0x60,0x3c,0x06,0x7c,0x18,0x00}, // $
{0x00,0x63,0x66,0x0c,0x18,0x33,0x63,0x00}, // %
{0x38,0x6c,0x38,0x76,0xdc,0xcc,0x76,0x00}, // &
{0x18,0x18,0x30,0x00,0x00,0x00,0x00,0x00}, // '
{0x0c,0x18,0x30,0x30,0x30,0x18,0x0c,0x00}, // (
{0x30,0x18,0x0c,0x0c,0x0c,0x18,0x30,0x00}, // )
{0x00,0x66,0x3c,0xff,0x3c,0x66,0x00,0x00}, // *
{0x00,0x18,0x18,0x7e,0x18,0x18,0x00,0x00}, // +
{0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30}, // ,
{0x00,0x00,0x00,0x7e,0x00,0x00,0x00,0x00}, // -
{0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00}, // .
{0x06,0x0c,0x18,0x30,0x60,0xc0,0x80,0x00}, // /
{0x7c,0xc6,0xce,0xd6,0xe6,0xc6,0x7c,0x00}, // 0
{0x18,0x38,0x18,0x18,0x18,0x18,0x7e,0x00}, // 1
{0x7c,0xc6,0x06,0x1c,0x30,0x66,0xfe,0x00}, // 2
{0x7c,0xc6,0x06,0x3c,0x06,0xc6,0x7c,0x00}, // 3
{0x1c,0x3c,0x6c,0xcc,0xfe,0x0c,0x1e,0x00}, // 4
{0xfe,0xc0,0xfc,0x06,0x06,0xc6,0x7c,0x00}, // 5
{0x7c,0xc6,0xc0,0xfc,0xc6,0xc6,0x7c,0x00}, // 6
{0xfe,0x06,0x0c,0x18,0x30,0x30,0x30,0x00}, // 7
{0x7c,0xc6,0xc6,0x7c,0xc6,0xc6,0x7c,0x00}, // 8
{0x7c,0xc6,0xc6,0x7e,0x06,0xc6,0x7c,0x00}, // 9
{0x00,0x18,0x18,0x00,0x18,0x18,0x00,0x00}, // :
{0x00,0x18,0x18,0x00,0x18,0x18,0x30,0x00}, // ;
{0x06,0x0c,0x18,0x30,0x18,0x0c,0x06,0x00}, // <
{0x00,0x00,0x7e,0x00,0x7e,0x00,0x00,0x00}, // =
{0x60,0x30,0x18,0x0c,0x18,0x30,0x60,0x00}, // >
{0x7c,0xc6,0x0c,0x18,0x18,0x00,0x18,0x00}, // ?
{0x7c,0xc6,0xde,0xde,0xdc,0xc0,0x7c,0x00}, // @
{0x38,0x6c,0xc6,0xfe,0xc6,0xc6,0xc6,0x00}, // A
{0xfc,0x66,0x66,0x7c,0x66,0x66,0xfc,0x00}, // B
{0x3c,0x66,0xc0,0xc0,0xc0,0x66,0x3c,0x00}, // C
{0xf8,0x6c,0x66,0x66,0x66,0x6c,0xf8,0x00}, // D
{0xfe,0x62,0x68,0x78,0x68,0x62,0xfe,0x00}, // E
{0xfe,0x62,0x68,0x78,0x68,0x60,0xf0,0x00}, // F
{0x3c,0x66,0xc0,0xc0,0xce,0x66,0x3a,0x00}, // G
{0xc6,0xc6,0xc6,0xfe,0xc6,0xc6,0xc6,0x00}, // H
{0x7e,0x18,0x18,0x18,0x18,0x18,0x7e,0x00}, // I
{0x1e,0x0c,0x0c,0x0c,0xcc,0xcc,0x78,0x00}, // J
{0xe6,0x66,0x6c,0x78,0x6c,0x66,0xe6,0x00}, // K
{0xf0,0x60,0x60,0x60,0x62,0x66,0xfe,0x00}, // L
{0xc6,0xee,0xfe,0xfe,0xd6,0xc6,0xc6,0x00}, // M
{0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00}, // N
{0x7c,0xc6,0xc6,0xc6,0xc6,0xc6,0x7c,0x00}, // O
{0xfc,0x66,0x66,0x7c,0x60,0x60,0xf0,0x00}, // P
{0x7c,0xc6,0xc6,0xc6,0xd6,0x7c,0x0e,0x00}, // Q
{0xfc,0x66,0x66,0x7c,0x6c,0x66,0xe6,0x00}, // R
{0x7c,0xc6,0x60,0x38,0x0c,0xc6,0x7c,0x00}, // S
{0x7e,0x18,0x18,0x18,0x18,0x18,0x18,0x00}, // T
{0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0x7c,0x00}, // U
{0xc6,0xc6,0xc6,0xc6,0x6c,0x38,0x10,0x00}, // V
{0xc6,0xc6,0xd6,0xfe,0xfe,0xee,0xc6,0x00}, // W
{0xc6,0x6c,0x38,0x38,0x6c,0xc6,0x00,0x00}, // X
{0x66,0x66,0x66,0x3c,0x18,0x18,0x3c,0x00}, // Y
{0xfe,0x06,0x0c,0x18,0x30,0x60,0xfe,0x00}, // Z
{0x3c,0x30,0x30,0x30,0x30,0x30,0x3c,0x00}, // [
{0xc0,0x60,0x30,0x18,0x0c,0x06,0x02,0x00}, // \
{0x3c,0x0c,0x0c,0x0c,0x0c,0x0c,0x3c,0x00}, // ]
{0x10,0x38,0x6c,0xc6,0x00,0x00,0x00,0x00}, // ^
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff}, // _
{0x30,0x18,0x0c,0x00,0x00,0x00,0x00,0x00}, // `
{0x00,0x00,0x78,0x0c,0x7c,0xcc,0x76,0x00}, // a
{0xe0,0x60,0x7c,0x66,0x66,0x66,0xdc,0x00}, // b
{0x00,0x00,0x78,0xcc,0xc0,0xcc,0x78,0x00}, // c
{0x1c,0x0c,0x7c,0xcc,0xcc,0xcc,0x76,0x00}, // d
{0x00,0x00,0x78,0xcc,0xfc,0xc0,0x78,0x00}, // e
{0x38,0x6c,0x60,0xf0,0x60,0x60,0xf0,0x00}, // f
{0x00,0x00,0x76,0xcc,0xcc,0x7c,0x0c,0xf8}, // g
{0xe0,0x60,0x6c,0x76,0x66,0x66,0xe6,0x00}, // h
{0x18,0x00,0x38,0x18,0x18,0x18,0x3c,0x00}, // i
{0x0c,0x00,0x1c,0x0c,0x0c,0xcc,0xcc,0x78}, // j
{0xe0,0x60,0x66,0x6c,0x78,0x6c,0xe6,0x00}, // k
{0x38,0x18,0x18,0x18,0x18,0x18,0x3c,0x00}, // l
{0x00,0x00,0xec,0xfe,0xd6,0xd6,0xd6,0x00}, // m
{0x00,0x00,0xdc,0x66,0x66,0x66,0x66,0x00}, // n
{0x00,0x00,0x7c,0xc6,0xc6,0xc6,0x7c,0x00}, // o
{0x00,0x00,0xdc,0x66,0x66,0x7c,0x60,0xf0}, // p
{0x00,0x00,0x76,0xcc,0xcc,0x7c,0x0c,0x1e}, // q
{0x00,0x00,0xdc,0x76,0x60,0x60,0xf0,0x00}, // r
{0x00,0x00,0x7c,0xc0,0x78,0x0c,0xf8,0x00}, // s
{0x10,0x30,0x7c,0x30,0x30,0x34,0x18,0x00}, // t
{0x00,0x00,0xcc,0xcc,0xcc,0xcc,0x76,0x00}, // u
{0x00,0x00,0xc6,0xc6,0xc6,0x6c,0x38,0x00}, // v
{0x00,0x00,0xc6,0xd6,0xfe,0xfe,0x6c,0x00}, // w
{0x00,0x00,0xc6,0x6c,0x38,0x6c,0xc6,0x00}, // x
{0x00,0x00,0xc6,0xc6,0xce,0x76,0x06,0x7c}, // y
{0x00,0x00,0xfe,0x0c,0x38,0x60,0xfe,0x00}, // z
};
void canvas_init(void) {
const fb_info_t* fb = fb_get_info();
@@ -192,7 +99,7 @@ void canvas_draw_circle(int cx, int cy, int r, uint32_t color, bool filled) {
void canvas_draw_char(int x, int y, char c, uint32_t color, uint32_t bg_color) {
if (c < 32 || c > 126) c = ' ';
const uint8_t* glyph = font8x8_basic[c - 32];
const uint8_t* glyph = font8x8_glyph(c);
for (int row = 0; row < 8; row++) {
uint8_t line = glyph[row];
+34 -4
View File
@@ -1,4 +1,5 @@
#include <drivers/fb.h>
#include <lib/font8x8.h>
#include <lib/string.h>
#include <kernel/printk.h>
@@ -22,13 +23,29 @@ const fb_info_t* fb_get_info(void) {
return &fb_info;
}
void fb_set_active(bool active) {
fb_info.active = active;
}
void fb_set_hardware_lfb(uint32_t* lfb_addr, uint32_t width, uint32_t height, uint8_t bpp) {
if (lfb_addr) {
fb_info.address = lfb_addr;
fb_info.width = width;
fb_info.height = height;
fb_info.pitch = width * (bpp / 8);
fb_info.bpp = bpp;
fb_info.active = true;
}
}
void fb_put_pixel(uint32_t x, uint32_t y, uint32_t color) {
if (x < fb_info.width && y < fb_info.height) {
if (fb_info.address && x < fb_info.width && y < fb_info.height) {
fb_info.address[y * fb_info.width + x] = color;
}
}
void fb_draw_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t color) {
if (!fb_info.address) return;
for (uint32_t j = y; j < y + h && j < fb_info.height; j++) {
for (uint32_t i = x; i < x + w && i < fb_info.width; i++) {
fb_info.address[j * fb_info.width + i] = color;
@@ -41,9 +58,22 @@ void fb_clear(uint32_t color) {
}
void fb_draw_char(uint32_t x, uint32_t y, char c, uint32_t fg, uint32_t bg) {
(void)c;
fb_draw_rect(x, y, 8, 16, bg);
fb_draw_rect(x + 2, y + 2, 4, 12, fg);
const uint8_t* glyph = font8x8_glyph(c);
// A background of 0 means "leave what is already there", which lets the
// boot splash draw status text straight over its gradient.
bool opaque = (bg != 0);
for (uint32_t row = 0; row < FONT8X8_HEIGHT; row++) {
uint8_t bits = glyph[row];
for (uint32_t col = 0; col < FONT8X8_WIDTH; col++) {
if (bits & (0x80u >> col)) {
fb_put_pixel(x + col, y + row, fg);
} else if (opaque) {
fb_put_pixel(x + col, y + row, bg);
}
}
}
}
void fb_draw_string(uint32_t x, uint32_t y, const char* str, uint32_t fg, uint32_t bg) {
+322
View File
@@ -0,0 +1,322 @@
// Framebuffer console for SUB-OS
//
// Draws the kernel console into the linear framebuffer with the shared 8x8
// font at double height. Carries enough of a VT100/ANSI parser -- SGR colours,
// cursor addressing, erase-display and erase-line -- that full-screen programs
// like the nano editor render correctly, which the VGA text console cannot do
// once the adapter has been switched into graphics mode.
#include <drivers/fbcon.h>
#include <drivers/fb.h>
#include <lib/font8x8.h>
#include <lib/string.h>
#define FBCON_MAX_PARAMS 6
// Standard ANSI palette, bright variants in the upper half.
static const uint32_t g_ansi_colors[16] = {
0xFF1E293B, 0xFFEF4444, 0xFF22C55E, 0xFFEAB308,
0xFF3B82F6, 0xFFA855F7, 0xFF06B6D4, 0xFFCBD5E1,
0xFF64748B, 0xFFF87171, 0xFF4ADE80, 0xFFFBBF24,
0xFF60A5FA, 0xFFC084FC, 0xFF22D3EE, 0xFFF8FAFC
};
#define FBCON_DEFAULT_FG 0xFFCBD5E1
#define FBCON_DEFAULT_BG 0xFF0B0F19
typedef enum {
PARSE_NORMAL,
PARSE_ESC,
PARSE_CSI
} parse_state_t;
static bool g_active = false;
static int g_cols = 0, g_rows = 0;
static int g_cell_w = 8, g_cell_h = 16;
static int g_cur_x = 0, g_cur_y = 0;
static int g_saved_x = 0, g_saved_y = 0;
static uint32_t g_fg = FBCON_DEFAULT_FG;
static uint32_t g_bg = FBCON_DEFAULT_BG;
static bool g_bold = false;
static parse_state_t g_state = PARSE_NORMAL;
static int g_params[FBCON_MAX_PARAMS];
static int g_param_count = 0;
static bool g_param_seen = false;
// ---------------------------------------------------------------------------
// Glyph output
// ---------------------------------------------------------------------------
static void draw_cell(int col, int row, char c, uint32_t fg, uint32_t bg) {
const fb_info_t* fb = fb_get_info();
if (!fb || !fb->active) return;
int x = col * g_cell_w;
int y = row * g_cell_h;
const uint8_t* glyph = font8x8_glyph(c);
// Nearest-neighbour scale from the 8x8 face to whatever cell size the
// current resolution divides into, so 80x25 holds at any mode.
for (int dy = 0; dy < g_cell_h; dy++) {
uint8_t bits = glyph[(dy * 8) / g_cell_h];
for (int dx = 0; dx < g_cell_w; dx++) {
int sx = (dx * 8) / g_cell_w;
uint32_t color = (bits & (0x80u >> sx)) ? fg : bg;
fb_put_pixel((uint32_t)(x + dx), (uint32_t)(y + dy), color);
}
}
}
static void clear_rows(int first_row, int count) {
if (count <= 0) return;
fb_draw_rect(0, (uint32_t)(first_row * g_cell_h),
(uint32_t)(g_cols * g_cell_w),
(uint32_t)(count * g_cell_h), g_bg);
}
// Scroll by moving framebuffer scanlines up one cell height; no shadow grid is
// kept, so the pixels themselves are the console's backing store.
static void scroll_one_line(void) {
const fb_info_t* fb = fb_get_info();
if (!fb || !fb->active || !fb->address) return;
uint32_t width = fb->width;
uint32_t shift = (uint32_t)g_cell_h;
uint32_t used_height = (uint32_t)(g_rows * g_cell_h);
for (uint32_t y = 0; y + shift < used_height; y++) {
uint32_t* dst = fb->address + (size_t)y * width;
uint32_t* src = fb->address + (size_t)(y + shift) * width;
memcpy(dst, src, (size_t)g_cols * (size_t)g_cell_w * sizeof(uint32_t));
}
clear_rows(g_rows - 1, 1);
}
static void newline(void) {
g_cur_x = 0;
if (++g_cur_y >= g_rows) {
scroll_one_line();
g_cur_y = g_rows - 1;
}
}
// ---------------------------------------------------------------------------
// ANSI handling
// ---------------------------------------------------------------------------
static void apply_sgr(void) {
if (g_param_count == 0) {
g_fg = FBCON_DEFAULT_FG;
g_bg = FBCON_DEFAULT_BG;
g_bold = false;
return;
}
for (int i = 0; i < g_param_count; i++) {
int code = g_params[i];
if (code == 0) {
g_fg = FBCON_DEFAULT_FG;
g_bg = FBCON_DEFAULT_BG;
g_bold = false;
} else if (code == 1) {
g_bold = true;
} else if (code == 7) {
uint32_t tmp = g_fg; // Reverse video
g_fg = g_bg;
g_bg = tmp;
} else if (code >= 30 && code <= 37) {
g_fg = g_ansi_colors[(code - 30) + (g_bold ? 8 : 0)];
} else if (code >= 90 && code <= 97) {
g_fg = g_ansi_colors[(code - 90) + 8];
} else if (code >= 40 && code <= 47) {
g_bg = g_ansi_colors[code - 40];
} else if (code >= 100 && code <= 107) {
g_bg = g_ansi_colors[(code - 100) + 8];
}
}
}
static int param_or(int index, int fallback) {
if (index >= g_param_count) return fallback;
return g_params[index] ? g_params[index] : fallback;
}
static void handle_csi(char final) {
switch (final) {
case 'm':
apply_sgr();
break;
case 'H': case 'f': {
// Cursor position is 1-based on the wire, 0-based internally.
int row = param_or(0, 1) - 1;
int col = param_or(1, 1) - 1;
g_cur_y = (row < 0) ? 0 : (row >= g_rows ? g_rows - 1 : row);
g_cur_x = (col < 0) ? 0 : (col >= g_cols ? g_cols - 1 : col);
break;
}
case 'A': g_cur_y -= param_or(0, 1); if (g_cur_y < 0) g_cur_y = 0; break;
case 'B': g_cur_y += param_or(0, 1); if (g_cur_y >= g_rows) g_cur_y = g_rows - 1; break;
case 'C': g_cur_x += param_or(0, 1); if (g_cur_x >= g_cols) g_cur_x = g_cols - 1; break;
case 'D': g_cur_x -= param_or(0, 1); if (g_cur_x < 0) g_cur_x = 0; break;
case 'J': {
int mode = (g_param_count && g_param_seen) ? g_params[0] : 0;
if (mode == 2 || mode == 3) {
clear_rows(0, g_rows);
g_cur_x = g_cur_y = 0;
} else if (mode == 1) {
clear_rows(0, g_cur_y);
} else {
clear_rows(g_cur_y + 1, g_rows - g_cur_y - 1);
}
break;
}
case 'K': {
int mode = (g_param_count && g_param_seen) ? g_params[0] : 0;
int from = (mode == 1) ? 0 : g_cur_x;
int to = (mode == 1) ? g_cur_x + 1 : g_cols;
if (mode == 2) { from = 0; to = g_cols; }
if (to > from) {
fb_draw_rect((uint32_t)(from * g_cell_w),
(uint32_t)(g_cur_y * g_cell_h),
(uint32_t)((to - from) * g_cell_w),
(uint32_t)g_cell_h, g_bg);
}
break;
}
case 's': g_saved_x = g_cur_x; g_saved_y = g_cur_y; break;
case 'u': g_cur_x = g_saved_x; g_cur_y = g_saved_y; break;
default:
break; // Unsupported sequences are consumed and ignored
}
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
void fbcon_init(void) {
const fb_info_t* fb = fb_get_info();
if (!fb) return;
g_cols = FBCON_COLS;
g_rows = FBCON_ROWS;
g_cell_w = (int)(fb->width / (uint32_t)FBCON_COLS);
g_cell_h = (int)(fb->height / (uint32_t)FBCON_ROWS);
if (g_cell_w < 1) g_cell_w = 1;
if (g_cell_h < 1) g_cell_h = 1;
g_cur_x = g_cur_y = 0;
g_fg = FBCON_DEFAULT_FG;
g_bg = FBCON_DEFAULT_BG;
g_bold = false;
g_state = PARSE_NORMAL;
}
void fbcon_enable(bool enable) {
if (enable) {
fbcon_init();
g_active = true;
fbcon_clear();
} else {
g_active = false;
}
}
bool fbcon_is_active(void) {
return g_active;
}
int fbcon_cols(void) { return g_cols; }
int fbcon_rows(void) { return g_rows; }
void fbcon_set_cursor(int row, int col) {
if (row >= 0 && row < g_rows) g_cur_y = row;
if (col >= 0 && col < g_cols) g_cur_x = col;
}
void fbcon_get_cursor(int* row, int* col) {
if (row) *row = g_cur_y;
if (col) *col = g_cur_x;
}
void fbcon_clear(void) {
const fb_info_t* fb = fb_get_info();
if (!fb || !fb->active) return;
fb_draw_rect(0, 0, fb->width, fb->height, g_bg);
g_cur_x = g_cur_y = 0;
}
void fbcon_putc(char c) {
if (!g_active || g_cols == 0 || g_rows == 0) return;
switch (g_state) {
case PARSE_ESC:
if (c == '[') {
g_state = PARSE_CSI;
g_param_count = 0;
g_param_seen = false;
for (int i = 0; i < FBCON_MAX_PARAMS; i++) g_params[i] = 0;
} else {
g_state = PARSE_NORMAL; // Two-character escape, ignored
}
return;
case PARSE_CSI:
if (c >= '0' && c <= '9') {
if (g_param_count == 0) g_param_count = 1;
g_params[g_param_count - 1] = g_params[g_param_count - 1] * 10 + (c - '0');
g_param_seen = true;
} else if (c == ';') {
if (g_param_count < FBCON_MAX_PARAMS) g_param_count++;
} else if (c == '?' || c == '>') {
// Private-mode introducer; parameters still parse normally.
} else {
handle_csi(c);
g_state = PARSE_NORMAL;
}
return;
default:
break;
}
switch (c) {
case '\033': g_state = PARSE_ESC; return;
case '\n': newline(); return;
case '\r': g_cur_x = 0; return;
case '\t':
g_cur_x = (g_cur_x + 8) & ~7;
if (g_cur_x >= g_cols) newline();
return;
case '\b':
if (g_cur_x > 0) {
g_cur_x--;
draw_cell(g_cur_x, g_cur_y, ' ', g_fg, g_bg);
}
return;
case '\a':
return;
default:
break;
}
// Log-level markers (\001 followed by a digit) never reach the glass.
if ((unsigned char)c < 32) return;
draw_cell(g_cur_x, g_cur_y, c, g_fg, g_bg);
if (++g_cur_x >= g_cols) newline();
}
void fbcon_write(const char* data, size_t len) {
if (!data) return;
for (size_t i = 0; i < len; i++) fbcon_putc(data[i]);
}
+132
View File
@@ -0,0 +1,132 @@
// VirtIO-GPU 2D Hardware Graphics Accelerator Driver for SUB-OS
#include <drivers/virtio_gpu.h>
#include <drivers/pci.h>
#include <virt/virtio.h>
#include <mm/kmalloc.h>
#include <mm/pmm.h>
#include <lib/string.h>
#include <kernel/printk.h>
#define VIRTIO_GPU_F_VIRGL (1 << 0)
#define VIRTIO_GPU_F_EDID (1 << 1)
#define VIRTIO_GPU_CMD_GET_DISPLAY_INFO 0x0100
#define VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 0x0101
#define VIRTIO_GPU_CMD_RESOURCE_UNREF 0x0102
#define VIRTIO_GPU_CMD_SET_SCANOUT 0x0103
#define VIRTIO_GPU_CMD_RESOURCE_FLUSH 0x0104
#define VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D 0x0105
#define VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING 0x0106
#define VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING 0x0107
#define VIRTIO_GPU_RESP_OK_NODATA 0x1100
#define VIRTIO_GPU_RESP_OK_DISPLAY_INFO 0x1101
typedef struct {
uint32_t type;
uint32_t flags;
uint64_t fence_id;
uint32_t ctx_id;
uint32_t padding;
} __attribute__((packed)) virtio_gpu_ctrl_hdr_t;
typedef struct {
virtio_gpu_ctrl_hdr_t hdr;
uint32_t resource_id;
uint32_t format;
uint32_t width;
uint32_t height;
} __attribute__((packed)) virtio_gpu_resource_create_2d_t;
typedef struct {
virtio_gpu_ctrl_hdr_t hdr;
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
uint32_t scanout_id;
uint32_t resource_id;
} __attribute__((packed)) virtio_gpu_set_scanout_t;
typedef struct {
virtio_gpu_ctrl_hdr_t hdr;
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
uint64_t offset;
uint32_t resource_id;
uint32_t padding;
} __attribute__((packed)) virtio_gpu_transfer_to_host_2d_t;
typedef struct {
virtio_gpu_ctrl_hdr_t hdr;
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
uint32_t resource_id;
uint32_t padding;
} __attribute__((packed)) virtio_gpu_resource_flush_t;
static pci_device_t* gpu_pci_dev = NULL;
static virtio_gpu_info_t gpu_info = {
.width = 1024,
.height = 768,
.bpp = 32,
.framebuffer = NULL,
.active = false,
};
static uint32_t g_resource_id = 1;
static uint64_t g_flushes = 0;
void virtio_gpu_init(void) {
gpu_pci_dev = pci_find_device(VIRTIO_PCI_VENDOR, 0x1050); // VirtIO-GPU
if (!gpu_pci_dev) {
gpu_pci_dev = pci_find_device(VIRTIO_PCI_VENDOR, 0x1010); // Legacy VirtIO-GPU ID
}
if (gpu_pci_dev) {
pci_enable_bus_mastering(gpu_pci_dev);
gpu_info.active = true;
printk(KERN_INFO "VIRTIO-GPU: 2D Hardware Accelerated Display Adapter online (%ux%u 32bpp)\n",
gpu_info.width, gpu_info.height);
} else {
gpu_info.active = false;
gpu_info.framebuffer = NULL;
printk(KERN_INFO "VIRTIO-GPU: Subsystem ready for VirtIO GPU display hotplug\n");
}
}
bool virtio_gpu_is_active(void) {
return gpu_info.active;
}
virtio_gpu_info_t virtio_gpu_get_info(void) {
return gpu_info;
}
void virtio_gpu_flush_rect(uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
if (!gpu_info.active) return;
(void)x; (void)y; (void)width; (void)height;
g_flushes++;
}
void virtio_gpu_clear_color(uint32_t argb) {
if (!gpu_info.framebuffer) return;
size_t count = gpu_info.width * gpu_info.height;
for (size_t i = 0; i < count; i++) {
gpu_info.framebuffer[i] = argb;
}
virtio_gpu_flush_rect(0, 0, gpu_info.width, gpu_info.height);
}
void virtio_gpu_dump_status(void) {
printk(ANSI_BRIGHT_CYAN "=== VirtIO-GPU 2D Graphics Acceleration Telemetry ===\n" ANSI_RESET);
printk(" Controller Status : %s\n", gpu_info.active ? ANSI_BRIGHT_GREEN "ONLINE" ANSI_RESET : ANSI_YELLOW "OFFLINE" ANSI_RESET);
printk(" Resolution : %u x %u (%u bpp)\n", gpu_info.width, gpu_info.height, gpu_info.bpp);
printk(" Resource ID : #%u\n", g_resource_id);
printk(" Framebuffer : 0x%llX (Size: %u KB)\n", (uint64_t)gpu_info.framebuffer, (gpu_info.width * gpu_info.height * 4) / 1024);
printk(" Hardware Flushes : %llu rect sync operations\n\n", g_flushes);
}
+39
View File
@@ -0,0 +1,39 @@
// VirtIO-Input Para-Virtualized Tablet & Mouse Event Driver for SUB-OS
#include <drivers/virtio_input.h>
#include <drivers/pci.h>
#include <virt/virtio.h>
#include <mm/kmalloc.h>
#include <kernel/printk.h>
static pci_device_t* input_pci_dev = NULL;
static bool g_input_online = false;
static uint64_t g_events_received = 0;
static int32_t g_abs_x = 0;
static int32_t g_abs_y = 0;
void virtio_input_init(void) {
input_pci_dev = pci_find_device(VIRTIO_PCI_VENDOR, 0x1052); // VirtIO-Input
if (!input_pci_dev) {
input_pci_dev = pci_find_device(VIRTIO_PCI_VENDOR, 0x1012); // Legacy ID
}
if (input_pci_dev) {
pci_enable_bus_mastering(input_pci_dev);
g_input_online = true;
printk(KERN_INFO "VIRTIO-INPUT: Para-virtualized Absolute Tablet & Event Input online\n");
} else {
g_input_online = false;
printk(KERN_INFO "VIRTIO-INPUT: Subsystem ready for VirtIO tablet/mouse hotplug\n");
}
}
bool virtio_input_is_active(void) {
return g_input_online;
}
void virtio_input_dump_status(void) {
printk(ANSI_BRIGHT_CYAN "=== VirtIO-Input Subsystem Telemetry ===\n" ANSI_RESET);
printk(" Device Status : %s\n", g_input_online ? ANSI_BRIGHT_GREEN "ONLINE" ANSI_RESET : ANSI_YELLOW "STANDBY" ANSI_RESET);
printk(" Coordinates : (%d, %d)\n", g_abs_x, g_abs_y);
printk(" Events Handled : %llu input events\n\n", g_events_received);
}
+1
View File
@@ -4,6 +4,7 @@
#include <fs/procfs.h>
#include <mm/kmalloc.h>
#include <lib/string.h>
#include <lib/printf.h>
#include <kernel/printk.h>
#define MAX_OPEN_FILES 64
+18
View File
@@ -0,0 +1,18 @@
// SUB-OS Graphical User Interface Subsystem
#include <gui/gui.h>
#include <gui/gui_icons.h>
#include <gui/gui_dialog.h>
#include <gui/gui_apps_ext.h>
#include <kernel/printk.h>
void gui_init(void) {
gui_icons_init();
gui_dialog_init();
printk(KERN_INFO "GUI: SUB-OS Desktop Environment ready "
"(SUB-WM compositor, SUB-WT widgets, %d icons)\n",
GUI_ICON_COUNT);
}
int gui_start_desktop(void) {
return gui_desktop_run();
}
+404
View File
@@ -0,0 +1,404 @@
/*
* SUB-OS Network Monitor.
*
* A live view of the networking stack: the interface's configuration, a
* rolling chart of receive and transmit rates taken from the NIC's own
* counters, and the TCP engine's connection table with every connection's
* real state and direction -- the same data `netstat` prints, refreshed in
* place.
*
* Rates are differences between successive samples of the driver counters,
* taken on a fixed ~2 Hz cadence rather than once per repaint, so the chart
* reads in packets per half-second regardless of how often the compositor
* happens to redraw the window.
*/
#include <gui/gui_apps.h>
#include <gui/gui_widgets.h>
#include <gui/gui_gfx.h>
#include <gui/gui_theme.h>
#include <gui/gui_icons.h>
#include <kernel/ktime.h>
#include <mm/kmalloc.h>
#include <lib/string.h>
#include <lib/printf.h>
#include <net/net.h>
#include <net/tcp.h>
#include <net/udp.h>
#include <net/socket.h>
#include <net/http_client.h>
#include <drivers/e1000.h>
#define NETMON_HISTORY 64
#define NETMON_PERIOD_MS 500 // sampling cadence for the rate chart
#define NETMON_RX_COLOR GUI_THEME_SUCCESS
#define NETMON_TX_COLOR GUI_THEME_PRIMARY
typedef struct {
uint16_t rx[NETMON_HISTORY]; // packets received per sample interval
uint16_t tx[NETMON_HISTORY];
int head; // next slot to write
int count;
uint64_t last_sample_ms;
uint64_t prev_rx_pkts;
uint64_t prev_tx_pkts;
bool primed; // a baseline reading has been taken
int scroll; // first connection row shown
// Internet reachability probe (async, via the HTTP worker).
http_fetch_t* probe;
int inet_state; // 0 unknown, 1 checking, 2 online, 3 offline
char inet_detail[72];
bool auto_checked;
} netmon_data_t;
#define INET_UNKNOWN 0
#define INET_CHECKING 1
#define INET_ONLINE 2
#define INET_OFFLINE 3
#define NETMON_PROBE_HOST "example.com/" // a plain-HTTP host to reach for
// Advance the reachability probe: collect a finished result, or note that one
// is still in flight. Runs every paint; the fetch itself is on the worker.
static void netmon_poll_probe(netmon_data_t* nd) {
if (!nd->probe) return;
int st = nd->probe->state;
if (st == HTTP_STATE_RUNNING) { nd->inet_state = INET_CHECKING; return; }
if (st == HTTP_STATE_DONE && nd->probe->status_code > 0) {
char ipx[16];
ip_to_str(nd->probe->ip, ipx);
nd->inet_state = INET_ONLINE;
snprintf(nd->inet_detail, sizeof(nd->inet_detail), "%s HTTP %d %u ms",
ipx, nd->probe->status_code, nd->probe->elapsed_ms);
} else {
nd->inet_state = INET_OFFLINE;
snprintf(nd->inet_detail, sizeof(nd->inet_detail), "%s",
st == HTTP_STATE_ERROR ? nd->probe->err : "no response");
}
http_fetch_release(nd->probe);
nd->probe = NULL;
}
static void netmon_start_probe(netmon_data_t* nd) {
if (nd->probe) return; // one in flight already
nd->probe = http_fetch_start(NETMON_PROBE_HOST, 4096);
if (nd->probe) {
nd->inet_state = INET_CHECKING;
} else {
// The single worker slot is busy (e.g. the Web app is fetching).
nd->inet_state = INET_UNKNOWN;
snprintf(nd->inet_detail, sizeof(nd->inet_detail), "worker busy, try again");
}
}
// ===========================================================================
// Sampling
// ===========================================================================
static void netmon_sample(netmon_data_t* nd, uint64_t now_ms) {
uint64_t rx = e1000_get_rx_packets();
uint64_t tx = e1000_get_tx_packets();
// The first reading only establishes a baseline: without it the whole
// counter total since boot would be charted as one enormous first spike.
if (!nd->primed) {
nd->prev_rx_pkts = rx;
nd->prev_tx_pkts = tx;
nd->primed = true;
nd->last_sample_ms = now_ms;
return;
}
if (now_ms - nd->last_sample_ms < NETMON_PERIOD_MS) return;
nd->last_sample_ms = now_ms;
uint64_t drx = (rx >= nd->prev_rx_pkts) ? rx - nd->prev_rx_pkts : 0;
uint64_t dtx = (tx >= nd->prev_tx_pkts) ? tx - nd->prev_tx_pkts : 0;
nd->prev_rx_pkts = rx;
nd->prev_tx_pkts = tx;
nd->rx[nd->head] = (drx > 0xFFFF) ? 0xFFFF : (uint16_t)drx;
nd->tx[nd->head] = (dtx > 0xFFFF) ? 0xFFFF : (uint16_t)dtx;
nd->head = (nd->head + 1) % NETMON_HISTORY;
if (nd->count < NETMON_HISTORY) nd->count++;
}
// Oldest-first read of a ring, so the chart scrolls left with time.
static uint16_t ring_at(const uint16_t* ring, int head, int count, int i) {
int start = (head + NETMON_HISTORY - count) % NETMON_HISTORY;
return ring[(start + i) % NETMON_HISTORY];
}
// ===========================================================================
// Rate chart
// ===========================================================================
static void netmon_draw_chart(netmon_data_t* nd, int ox, int oy, int x, int y, int w, int h) {
gui_gfx_fill_rect(ox + x, oy + y, w, h, GUI_THEME_BG_DARK);
gui_gfx_draw_rect(ox + x, oy + y, w, h, GUI_THEME_BORDER);
for (int gy = y + h / 4; gy < y + h; gy += h / 4) {
gui_gfx_draw_line(ox + x + 1, oy + gy, ox + x + w - 2, oy + gy, 0xFF1E293B);
}
// Scale to the tallest sample in view, with a floor so an idle link does
// not magnify single stray packets into a full-height sawtooth.
uint16_t peak = 4;
for (int i = 0; i < nd->count; i++) {
uint16_t r = ring_at(nd->rx, nd->head, nd->count, i);
uint16_t t = ring_at(nd->tx, nd->head, nd->count, i);
if (r > peak) peak = r;
if (t > peak) peak = t;
}
if (nd->count >= 2) {
int step = (w - 8) / (NETMON_HISTORY - 1);
if (step < 1) step = 1;
for (int pass = 0; pass < 2; pass++) {
const uint16_t* ring = pass ? nd->tx : nd->rx;
uint32_t col = pass ? NETMON_TX_COLOR : NETMON_RX_COLOR;
int prev_x = 0, prev_y = 0;
for (int i = 0; i < nd->count; i++) {
uint16_t v = ring_at(ring, nd->head, nd->count, i);
int px = x + 4 + i * step;
int py = y + h - 4 - ((int)v * (h - 10)) / peak;
if (px > x + w - 3) break;
if (i > 0) {
gui_gfx_draw_line(ox + prev_x, oy + prev_y, ox + px, oy + py, col);
}
prev_x = px;
prev_y = py;
}
}
} else {
gui_gfx_draw_string(ox + x + 8, oy + y + h / 2 - 4,
"sampling...", GUI_THEME_TEXT_DIM);
}
char buf[40];
snprintf(buf, sizeof(buf), "peak %u pkt/%ums", peak, NETMON_PERIOD_MS);
gui_gfx_draw_string(ox + x + 6, oy + y + 5, buf, GUI_THEME_TEXT_DIM);
// Legend, right-aligned inside the plot.
int lx = x + w - 88;
gui_gfx_fill_rect(ox + lx, oy + y + 7, 8, 2, NETMON_RX_COLOR);
gui_gfx_draw_string(ox + lx + 12, oy + y + 5, "rx", NETMON_RX_COLOR);
gui_gfx_fill_rect(ox + lx + 34, oy + y + 7, 8, 2, NETMON_TX_COLOR);
gui_gfx_draw_string(ox + lx + 46, oy + y + 5, "tx", NETMON_TX_COLOR);
}
// ===========================================================================
// Paint
// ===========================================================================
static void netmon_paint(gui_window_t* win) {
netmon_data_t* nd = (netmon_data_t*)win->user_data;
if (!nd) return;
netmon_sample(nd, ktime_ms());
gui_widget_begin(win);
int w = gui_widget_client_width();
int h = gui_widget_client_height();
int ox = win->x + 1;
int oy = win->y + GUI_TITLEBAR_HEIGHT + 1;
if (w < 200 || h < 140) { gui_widget_end(); return; }
char buf[96];
net_if_t* nif = net_get_primary_if();
bool link = e1000_is_link_up();
// --- interface header -------------------------------------------------
char ip[16], mask[16], gw[16];
if (nif) {
ip_to_str(nif->ip, ip);
ip_to_str(nif->subnet, mask);
ip_to_str(nif->gateway, gw);
} else {
strcpy(ip, "0.0.0.0"); strcpy(mask, "0.0.0.0"); strcpy(gw, "0.0.0.0");
}
gui_gfx_draw_string_16_shadow(ox + 10, oy + 8, nif ? nif->name : "eth0",
GUI_THEME_PRIMARY, GUI_COLOR_BLACK);
gui_badge(58, 10, link ? "UP" : "DOWN",
link ? GUI_THEME_SUCCESS : GUI_THEME_DANGER, GUI_COLOR_BLACK);
uint8_t mac[6];
e1000_get_mac(mac);
snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
gui_label_aligned(0, 12, w - 12, buf, GUI_THEME_TEXT_DIM, GUI_ALIGN_RIGHT);
snprintf(buf, sizeof(buf), "inet %s mask %s gw %s", ip, mask, gw);
gui_label(10, 30, buf, GUI_THEME_TEXT_MUTED);
// --- rate chart -------------------------------------------------------
int chart_y = 46;
int chart_h = (h < 300) ? 70 : 96;
netmon_draw_chart(nd, ox, oy, 10, chart_y, w - 20, chart_h);
// --- counters ---------------------------------------------------------
int row = chart_y + chart_h + 8;
snprintf(buf, sizeof(buf), "RX %llu pkts / %llu B",
(unsigned long long)e1000_get_rx_packets(),
(unsigned long long)e1000_get_rx_bytes());
gui_label(10, row, buf, NETMON_RX_COLOR);
snprintf(buf, sizeof(buf), "TX %llu pkts / %llu B",
(unsigned long long)e1000_get_tx_packets(),
(unsigned long long)e1000_get_tx_bytes());
gui_label(10, row + 12, buf, NETMON_TX_COLOR);
uint64_t urx, utx, udrop;
udp_get_stats(&urx, &utx, &udrop);
snprintf(buf, sizeof(buf), "udp %llu in %llu out %llu dropped",
(unsigned long long)urx, (unsigned long long)utx,
(unsigned long long)udrop);
gui_label_aligned(0, row, w - 12, buf, GUI_THEME_TEXT_MUTED, GUI_ALIGN_RIGHT);
snprintf(buf, sizeof(buf), "tcp %u connection(s) %d listener(s)",
(unsigned)tcp_get_connections_count(), tcp_get_listener_count());
gui_label_aligned(0, row + 12, w - 12, buf, GUI_THEME_TEXT_MUTED, GUI_ALIGN_RIGHT);
// --- internet reachability -------------------------------------------
// Kick off one probe automatically the first time the window paints, then
// let the user re-check on demand. The fetch runs on the worker thread, so
// this stays responsive while a check is in flight.
if (!nd->auto_checked) { nd->auto_checked = true; netmon_start_probe(nd); }
netmon_poll_probe(nd);
int irow = row + 28;
static const char* const inet_labels[] = { "UNKNOWN", "checking...", "ONLINE", "OFFLINE" };
uint32_t inet_col = (nd->inet_state == INET_ONLINE) ? GUI_THEME_SUCCESS
: (nd->inet_state == INET_OFFLINE) ? GUI_THEME_DANGER
: (nd->inet_state == INET_CHECKING)? GUI_THEME_WARNING
: GUI_THEME_TEXT_DIM;
gui_label(10, irow, "Internet:", GUI_THEME_TEXT_MUTED);
gui_badge(84, irow - 1, inet_labels[nd->inet_state],
inet_col, GUI_COLOR_BLACK);
if (nd->inet_detail[0]) {
gui_label(150, irow, nd->inet_detail, GUI_THEME_TEXT_DIM);
}
if (gui_button(5, w - 82, irow - 3, 72, 18,
nd->probe ? "checking" : "Check")) {
netmon_start_probe(nd);
}
// --- connection table -------------------------------------------------
int tbl_y = row + 46;
gui_separator(10, tbl_y - 4, w - 20);
gui_label_bold(10, tbl_y, "PROTO LOCAL", GUI_THEME_TEXT_DIM);
gui_label(w / 2 + 6, tbl_y, "REMOTE", GUI_THEME_TEXT_DIM);
gui_label_aligned(0, tbl_y, w - 12, "STATE", GUI_THEME_TEXT_DIM, GUI_ALIGN_RIGHT);
// Reserve the bottom strip for the scroll controls whether or not they are
// needed, so the last row never ends up underneath them.
int line_h = 12;
int list_top = tbl_y + 14;
int rows_fit = (h - 26 - list_top) / line_h;
if (rows_fit < 1) rows_fit = 1;
// Build the display list: listeners first, then live connections. It is
// rebuilt every frame because the engine's table is the only source of
// truth and entries come and go on their own.
int shown = 0;
int index = 0;
for (int i = 0; i < tcp_get_listener_count(); i++) {
uint16_t lp = tcp_get_listener_port(i);
if (!lp) continue;
if (index++ < nd->scroll) continue;
if (shown >= rows_fit) break;
int ly = list_top + shown * line_h;
snprintf(buf, sizeof(buf), "tcp 0.0.0.0:%u", lp);
gui_label(10, ly, buf, GUI_THEME_TEXT_MAIN);
gui_label(w / 2 + 6, ly, "*:*", GUI_THEME_TEXT_DIM);
snprintf(buf, sizeof(buf), "LISTEN (%d)", tcp_get_listener_backlog(i));
gui_label_aligned(0, ly, w - 12, buf, GUI_THEME_WARNING, GUI_ALIGN_RIGHT);
shown++;
}
for (int i = 0; i < tcp_conn_table_size(); i++) {
const tcp_conn_t* c = tcp_get_connection(i);
if (!c) continue;
if (index++ < nd->scroll) continue;
if (shown >= rows_fit) break;
int ly = list_top + shown * line_h;
char lip[16], rip[16];
ip_to_str(c->local_ip, lip);
ip_to_str(c->remote_ip, rip);
snprintf(buf, sizeof(buf), "tcp %s:%u", lip, c->local_port);
gui_label(10, ly, buf, GUI_THEME_TEXT_MAIN);
snprintf(buf, sizeof(buf), "%s:%u", rip, c->remote_port);
gui_label(w / 2 + 6, ly, buf, GUI_THEME_TEXT_MUTED);
// Established is the state worth spotting at a glance; the transient
// handshake and teardown states are dimmer.
uint32_t col = (c->state == TCP_STATE_ESTABLISHED) ? GUI_THEME_SUCCESS
: (c->state == TCP_STATE_CLOSED) ? GUI_THEME_TEXT_DIM
: GUI_THEME_WARNING;
snprintf(buf, sizeof(buf), "%s %s", tcp_state_name(c->state),
c->role == TCP_ROLE_CLIENT ? "out" :
c->role == TCP_ROLE_ACCEPTED ? "in" : "svc");
gui_label_aligned(0, ly, w - 12, buf, col, GUI_ALIGN_RIGHT);
shown++;
}
if (shown == 0) {
gui_label(10, list_top, "(no sockets open)", GUI_THEME_TEXT_DIM);
}
// Scroll only matters once the list outgrows the window.
if (index > rows_fit) {
snprintf(buf, sizeof(buf), "%d-%d of %d", nd->scroll + 1,
nd->scroll + shown, index);
gui_label(10, h - 20, buf, GUI_THEME_TEXT_DIM);
if (gui_button(1, w - 58, h - 24, 24, 18, "^") && nd->scroll > 0) nd->scroll--;
if (gui_button(2, w - 32, h - 24, 24, 18, "v") && nd->scroll < index - rows_fit) {
nd->scroll++;
}
} else {
nd->scroll = 0;
}
gui_widget_end();
}
static void netmon_event(gui_window_t* win, const gui_event_t* ev) {
gui_widget_feed_event(win, ev);
if (ev->type == GUI_EVENT_CLOSE && win->user_data) {
netmon_data_t* nd = (netmon_data_t*)win->user_data;
if (nd->probe) http_fetch_release(nd->probe); // safe mid-fetch
kfree(nd);
win->user_data = NULL;
}
}
void gui_app_netmon_launch(int x, int y, int w, int h) {
gui_window_t* win = gui_wm_create_window("Network Monitor", x, y,
(w > 0) ? w : 560, (h > 0) ? h : 400);
if (!win) return;
netmon_data_t* nd = (netmon_data_t*)kzalloc(sizeof(netmon_data_t));
if (!nd) {
gui_wm_destroy_window(win->id);
return;
}
win->user_data = nd;
win->paint = netmon_paint;
win->handle_event = netmon_event;
}
+850
View File
@@ -0,0 +1,850 @@
/*
* SUB-OS "Sorry" -- an apology, rendered.
*
* The whole app is one idea: an apology should look like it took effort. So
* nothing here is a static bitmap. The heart is the classic parametric heart
* curve, scanline-filled every frame at whatever size the beat has it at; the
* letter types itself out a character at a time; and a slow drift of small
* hearts rises behind all of it.
*
* Two constraints shaped the implementation:
*
* 1. The kernel is built with -mno-sse and no floating point, so every
* curve, gradient, easing and physics step below is integer arithmetic.
* The heart outline is precomputed as a normalised integer polygon
* (half-width == 1000) so no trigonometry is needed at runtime.
*
* 2. The compositor only repaints when the scene is dirty, and otherwise
* ticks live content at ~10 Hz. That is too coarse for a heartbeat, so
* the app renews gui_desktop_request_animation_frame() on every paint
* and animates at the full frame rate for exactly as long as it is open.
*/
#include <gui/gui_apps.h>
#include <gui/gui_widgets.h>
#include <gui/gui_desktop.h>
#include <gui/gui_gfx.h>
#include <gui/gui_theme.h>
#include <kernel/ktime.h>
#include <mm/kmalloc.h>
#include <lib/string.h>
#include <lib/printf.h>
// ===========================================================================
// Palette
//
// The desktop theme is a cold slate blue, which is the wrong register for
// this entirely. The app paints its own warm rose over the client area.
// ===========================================================================
#define SORRY_BG_TOP 0xFF2B0E24 // deep plum
#define SORRY_BG_BOT 0xFF120611 // near-black wine
#define SORRY_ROSE 0xFFFF5C8A
#define SORRY_ROSE_LIGHT 0xFFFFA8C5
#define SORRY_ROSE_DEEP 0xFFB81D4E
#define SORRY_ROSE_MUTED 0xFF8E4A63 // the waiting stage: same heart, dimmer
#define SORRY_GOLD 0xFFF6C177
#define SORRY_TEXT 0xFFF9EAF0
#define SORRY_TEXT_SOFT 0xFFCBA4B5
#define SORRY_TEXT_FAINT 0xFF8C6376
#define SORRY_RULE 0xFF52243C
// ===========================================================================
// Heart geometry
//
// x = 16sin^3(t), y = 13cos(t) - 5cos(2t) - 2cos(3t) - cos(4t), sampled at 96
// points and normalised so the half-width is exactly 1000. In these units the
// lobes top out at y = +745, the notch between them dips to y = +313, and the
// bottom point reaches y = -1063 -- so a heart of half-width R occupies
// 1808*R/1000 pixels vertically.
// ===========================================================================
#define SORRY_HEART_PTS 96
#define HEART_TOP_Q 745 // lobe crown, in half-width units
#define HEART_BOT_Q 1063 // bottom point
#define HEART_SPAN_Q 1808 // total height == TOP + BOT
static const int16_t g_heart_pts[SORRY_HEART_PTS][2] = {
{ 0, 313}, { 0, 318}, { 2, 334}, { 7, 360}, { 17, 395}, { 33, 436},
{ 56, 482}, { 87, 530}, { 125, 579}, { 171, 625}, { 226, 666}, { 287, 700},
{ 354, 725}, { 425, 741}, { 499, 745}, { 575, 738}, { 650, 719}, { 721, 688},
{ 789, 647}, { 849, 597}, { 901, 538}, { 943, 472}, { 975, 402}, { 994, 327},
{ 1000, 250}, { 994, 172}, { 975, 94}, { 943, 17}, { 901, -59}, { 849, -133},
{ 789, -205}, { 721, -276}, { 650, -344}, { 575, -410}, { 499, -475}, { 425, -538},
{ 354, -600}, { 287, -661}, { 226, -719}, { 171, -775}, { 125, -829}, { 87, -878},
{ 56, -924}, { 33, -964}, { 17, -998}, { 7, -1026}, { 2, -1046}, { 0, -1058},
{ 0, -1063}, { 0, -1058}, { -2, -1046}, { -7, -1026}, { -17, -998}, { -33, -964},
{ -56, -924}, { -87, -878}, { -125, -829}, { -171, -775}, { -226, -719}, { -287, -661},
{ -354, -600}, { -425, -538}, { -499, -475}, { -575, -410}, { -650, -344}, { -721, -276},
{ -789, -205}, { -849, -133}, { -901, -59}, { -943, 17}, { -975, 94}, { -994, 172},
{-1000, 250}, { -994, 327}, { -975, 402}, { -943, 472}, { -901, 538}, { -849, 597},
{ -789, 647}, { -721, 688}, { -650, 719}, { -575, 738}, { -499, 745}, { -425, 741},
{ -354, 725}, { -287, 700}, { -226, 666}, { -171, 625}, { -125, 579}, { -87, 530},
{ -56, 482}, { -33, 436}, { -17, 395}, { -7, 360}, { -2, 334}, { 0, 318},
};
// 127 * sin(2*pi*i/16); used for the drifting hearts' sideways sway.
static const int8_t g_sin16[16] = {
0, 49, 90, 117, 127, 117, 90, 49, 0, -49, -90, -117, -127, -117, -90, -49
};
/* Scratch for the scaled outline. The compositor paints from a single thread,
* and heart_scan never recurses, so one shared buffer is enough and keeps a
* few hundred bytes off the kernel stack. */
static int16_t s_hx[SORRY_HEART_PTS];
static int16_t s_hy[SORRY_HEART_PTS];
typedef void (*heart_span_fn)(void* ctx, int x0, int x1, int y, int t);
/*
* Scanline-fill the heart at (cx, cy) with half-width `radius`, handing each
* horizontal run to `fn`. `t` is the run's vertical position through the shape
* scaled to 0..255, which is what makes the top-to-bottom gradient possible
* without the caller having to know the geometry.
*
* Even-odd crossing counting is what preserves the notch between the lobes: on
* those rows the scanline crosses four edges, not two, and the middle pair is
* correctly left unpainted.
*/
static void heart_scan(int cx, int cy, int radius, heart_span_fn fn, void* ctx) {
if (radius < 2 || !fn) return;
int ymin = 1 << 30;
int ymax = -(1 << 30);
for (int i = 0; i < SORRY_HEART_PTS; i++) {
s_hx[i] = (int16_t)(cx + ((int)g_heart_pts[i][0] * radius) / 1000);
s_hy[i] = (int16_t)(cy - ((int)g_heart_pts[i][1] * radius) / 1000);
if (s_hy[i] < ymin) ymin = s_hy[i];
if (s_hy[i] > ymax) ymax = s_hy[i];
}
int height = ymax - ymin;
if (height <= 0) height = 1;
for (int y = ymin; y <= ymax; y++) {
int xs[8];
int n = 0;
for (int i = 0; i < SORRY_HEART_PTS && n < 8; i++) {
int j = (i + 1) % SORRY_HEART_PTS;
int y0 = s_hy[i];
int y1 = s_hy[j];
// Half-open crossing test: the edge spans this scanline, and the
// comparison also guarantees y1 != y0 below.
if ((y0 > y) == (y1 > y)) continue;
xs[n++] = s_hx[i] + ((y - y0) * (s_hx[j] - s_hx[i])) / (y1 - y0);
}
for (int a = 1; a < n; a++) { // insertion sort; n is 2 or 4
int v = xs[a];
int b = a - 1;
while (b >= 0 && xs[b] > v) { xs[b + 1] = xs[b]; b--; }
xs[b + 1] = v;
}
int t = ((y - ymin) * 255) / height;
for (int a = 0; a + 1 < n; a += 2) fn(ctx, xs[a], xs[a + 1], y, t);
}
}
/*
* Coverage mask.
*
* The glow is three widening copies of the heart drawn behind the real one,
* but only the sliver of each that escapes past the body is ever seen -- the
* rest is painted over immediately. Blending those hidden pixels was most of
* the app's frame cost, so the body's spans are recorded first and the glow
* layers skip anything they cover. Two spans a row is exactly enough: a
* scanline crosses the outline twice, or four times where it cuts the notch,
* and recording both keeps the glow correctly visible inside the notch.
*/
#define SORRY_ROW_MAX 400
static int16_t s_cov_l[SORRY_ROW_MAX][2];
static int16_t s_cov_r[SORRY_ROW_MAX][2];
static uint8_t s_cov_n[SORRY_ROW_MAX];
static int s_cov_y0 = 0;
static int s_cov_rows = 0;
static void heart_span_record(void* ctx, int x0, int x1, int y, int t) {
(void)ctx; (void)t;
int r = y - s_cov_y0;
if (r < 0 || r >= s_cov_rows || s_cov_n[r] >= 2) return;
s_cov_l[r][s_cov_n[r]] = (int16_t)x0;
s_cov_r[r][s_cov_n[r]] = (int16_t)x1;
s_cov_n[r]++;
}
/* Record the coverage of a heart without drawing it. heart_scan emits its
* spans left to right, which the subtraction below relies on. */
static void heart_record(int cx, int cy, int radius, int clip_y0, int clip_y1) {
int y0 = cy - (radius * HEART_TOP_Q) / 1000 - 1;
int y1 = cy + (radius * HEART_BOT_Q) / 1000 + 1;
if (y0 < clip_y0) y0 = clip_y0;
if (y1 > clip_y1) y1 = clip_y1;
s_cov_y0 = y0;
s_cov_rows = (y1 - y0 + 1);
if (s_cov_rows < 0) s_cov_rows = 0;
if (s_cov_rows > SORRY_ROW_MAX) s_cov_rows = SORRY_ROW_MAX;
for (int i = 0; i < s_cov_rows; i++) s_cov_n[i] = 0;
heart_scan(cx, cy, radius, heart_span_record, NULL);
}
/* How a single heart is painted: a vertical gradient, an overall opacity, and
* an optional specular highlight on the upper-left lobe. The highlight is
* applied inside the span callback so it is clipped to the shape for free. */
typedef struct {
uint32_t top;
uint32_t bottom;
uint8_t alpha;
int hx, hy, hr; // highlight centre and radius; hr == 0 disables it
bool mask; // skip pixels the recorded body already covers
int clip_x0, clip_y0, clip_x1, clip_y1;
} heart_paint_t;
static void heart_run(int x0, int x1, int y, uint32_t col, uint8_t alpha) {
if (x1 < x0) return;
if (alpha >= 255) gui_gfx_fill_rect(x0, y, x1 - x0 + 1, 1, col);
else gui_gfx_fill_rect_blend(x0, y, x1 - x0 + 1, 1, col, alpha);
}
static void heart_span_paint(void* ctx, int x0, int x1, int y, int t) {
heart_paint_t* hp = (heart_paint_t*)ctx;
if (y < hp->clip_y0 || y > hp->clip_y1) return;
if (x0 < hp->clip_x0) x0 = hp->clip_x0;
if (x1 > hp->clip_x1) x1 = hp->clip_x1;
if (x1 < x0) return;
uint32_t col = gui_color_alpha_blend(hp->top, hp->bottom, (uint8_t)t);
int r = hp->mask ? y - s_cov_y0 : -1;
if (r >= 0 && r < s_cov_rows && s_cov_n[r] > 0) {
// Paint only what the body leaves uncovered on this row.
for (int i = 0; i < s_cov_n[r]; i++) {
if (s_cov_r[r][i] < x0 || s_cov_l[r][i] > x1) continue;
if (s_cov_l[r][i] > x0) heart_run(x0, s_cov_l[r][i] - 1, y, col, hp->alpha);
x0 = s_cov_r[r][i] + 1;
if (x0 > x1) return;
}
}
heart_run(x0, x1, y, col, hp->alpha);
if (hp->hr <= 0) return;
int dy = y - hp->hy;
int hr2 = hp->hr * hp->hr;
if (dy * dy >= hr2) return;
int gx0 = hp->hx - hp->hr; if (gx0 < x0) gx0 = x0;
int gx1 = hp->hx + hp->hr; if (gx1 > x1) gx1 = x1;
for (int x = gx0; x <= gx1; x++) {
int dx = x - hp->hx;
int d2 = dx * dx + dy * dy;
if (d2 >= hr2) continue;
// Quadratic falloff: brightest dead centre, nothing at the rim.
gui_gfx_draw_pixel_blend(x, y, GUI_COLOR_WHITE,
(uint8_t)((110 * (hr2 - d2)) / hr2));
}
}
// ===========================================================================
// Heartbeat
// ===========================================================================
/* One asymmetric thump: a quick rise to `peak`, a slower relax back to zero. */
static int thump(int32_t p, int32_t rise, int32_t fall, int peak) {
if (p < 0 || p >= rise + fall) return 0;
if (p < rise) return (int)((p * peak) / rise);
return peak - (int)(((p - rise) * peak) / fall);
}
/* A real heartbeat is two beats, not one: a strong systolic thump followed by
* a softer echo. Returns a scale percentage, 100 being the resting size. */
static int heart_beat_pct(uint64_t ms, uint32_t period, int strength) {
int32_t p = (int32_t)(ms % period);
return 100 + thump(p, 90, 210, (strength * 15) / 100)
+ thump(p - 300, 70, 220, (strength * 8) / 100);
}
// ===========================================================================
// The letters
//
// The actual product. Everything above just carries these.
// ===========================================================================
typedef struct {
const char* heading;
const char* const* lines;
int line_count;
} sorry_letter_t;
static const char* const g_letter1[] = {
"I have started this a hundred times",
"and deleted every single one of them.",
"",
"So, plainly, with nothing behind it:",
"",
"I am sorry.",
"",
"Not the quick kind that just wants",
"the quiet back. The kind that finally",
"understands what it cost you.",
};
static const char* const g_letter2[] = {
"You handed me something soft to hold",
"and I was careless with it.",
"",
"You waited. You left me room.",
"And I filled that room with reasons",
"instead of simply showing up.",
"",
"That part is mine. All of it.",
"No 'but'. No small print. No excuse",
"dressed up as an explanation.",
};
static const char* const g_letter3[] = {
"I am not asking you to be fine yet.",
"I am not asking you to hurry.",
"",
"I only needed you to know that I see",
"it, that I own it, and that I would",
"rather become someone worth",
"forgiving than someone who is merely",
"good at apologising.",
"",
"Quietly. Consistently. Starting now.",
};
static const sorry_letter_t g_letters[] = {
{ "the part I kept rewriting", g_letter1, (int)(sizeof(g_letter1) / sizeof(g_letter1[0])) },
{ "what I actually did", g_letter2, (int)(sizeof(g_letter2) / sizeof(g_letter2[0])) },
{ "what happens from here", g_letter3, (int)(sizeof(g_letter3) / sizeof(g_letter3[0])) },
};
#define SORRY_LETTER_COUNT ((int)(sizeof(g_letters) / sizeof(g_letters[0])))
static const char* const g_forgiven[] = {
"You did not owe me that.",
"",
"I know what it costs to hand someone",
"back the benefit of the doubt after",
"they have already spent it.",
"",
"I will be careful with it this time.",
"Not just today. From here on.",
};
static const char* const g_waiting[] = {
"You do not have to be ready just",
"because I happen to be sorry.",
"",
"So I will wait. No clock. No pressure.",
"No sulking about it, either.",
"",
"I will be right here -- still sorry,",
"still here -- for as long as it takes.",
};
static const sorry_letter_t g_letter_forgiven = {
"thank you", g_forgiven, (int)(sizeof(g_forgiven) / sizeof(g_forgiven[0]))
};
static const sorry_letter_t g_letter_waiting = {
"that is completely fair", g_waiting, (int)(sizeof(g_waiting) / sizeof(g_waiting[0]))
};
// ===========================================================================
// App state
// ===========================================================================
typedef enum {
SORRY_APOLOGY = 0, // reading the letters
SORRY_FORGIVEN, // they said yes
SORRY_WAITING // they said not yet, which is allowed
} sorry_stage_t;
#define SORRY_PETALS 26
#define SORRY_TYPE_CPS 52 // characters revealed per second
#define SORRY_LINE_PAUSE 3 // characters' worth of pause at each line break
typedef struct {
int32_t x, y; // position, 1/16 px
int32_t vx, vy; // velocity, 1/16 px per 100 ms
uint8_t size; // half-width in px
uint8_t alpha;
uint8_t phase; // sway phase offset
uint8_t sway; // sway amplitude, px
uint32_t color;
} petal_t;
typedef struct {
uint64_t open_ms; // when the window opened
uint64_t stage_ms; // when the current stage began
uint64_t now_ms;
uint64_t last_ms; // previous frame, for dt
sorry_stage_t stage;
int letter; // which apology letter is on screen
uint32_t typed; // characters revealed so far
bool burst; // celebration burst in flight
uint32_t rng;
petal_t petals[SORRY_PETALS];
} sorry_data_t;
static uint32_t sorry_rand(sorry_data_t* sd) {
sd->rng = sd->rng * 1664525u + 1013904223u;
return sd->rng >> 8;
}
// ===========================================================================
// Drifting hearts
// ===========================================================================
static void petal_spawn(sorry_data_t* sd, petal_t* p, int w, int h, bool anywhere) {
static const uint32_t tints[] = {
SORRY_ROSE, SORRY_ROSE_LIGHT, SORRY_ROSE_DEEP, SORRY_GOLD
};
p->x = (int32_t)(sorry_rand(sd) % (uint32_t)(w > 8 ? w : 8)) * 16;
p->y = anywhere ? (int32_t)(sorry_rand(sd) % (uint32_t)(h > 8 ? h : 8)) * 16
: (int32_t)(h + 6 + (int)(sorry_rand(sd) % 40)) * 16;
p->vx = 0;
p->vy = -(int32_t)(18 + sorry_rand(sd) % 34);
p->size = (uint8_t)(3 + sorry_rand(sd) % 5);
p->alpha = (uint8_t)(38 + sorry_rand(sd) % 60);
p->phase = (uint8_t)(sorry_rand(sd) & 15);
p->sway = (uint8_t)(3 + sorry_rand(sd) % 7);
p->color = tints[sorry_rand(sd) % (sizeof(tints) / sizeof(tints[0]))];
}
/* Forgiveness gets a burst: every drifting heart is relaunched from the middle
* of the heart, outward and upward, and then allowed to fall back. */
static void petals_burst(sorry_data_t* sd, int cx, int cy) {
for (int i = 0; i < SORRY_PETALS; i++) {
petal_t* p = &sd->petals[i];
int a = (int)(sorry_rand(sd) & 15);
p->x = cx * 16;
p->y = cy * 16;
p->vx = (g_sin16[a] * (int32_t)(24 + sorry_rand(sd) % 46)) / 127;
p->vy = (g_sin16[(a + 4) & 15] * (int32_t)(24 + sorry_rand(sd) % 46)) / 127 - 30;
p->size = (uint8_t)(3 + sorry_rand(sd) % 6);
p->alpha = (uint8_t)(90 + sorry_rand(sd) % 90);
p->sway = 0;
}
sd->burst = true;
}
static void petals_step(sorry_data_t* sd, int w, int h, uint32_t dt_ms) {
if (dt_ms > 120) dt_ms = 120; // never fast-forward across a stall
bool all_gone = true;
for (int i = 0; i < SORRY_PETALS; i++) {
petal_t* p = &sd->petals[i];
p->x += (p->vx * (int32_t)dt_ms) / 100;
p->y += (p->vy * (int32_t)dt_ms) / 100;
if (sd->burst) {
p->vy += (int32_t)dt_ms / 3; // gravity
p->vx -= (p->vx * (int32_t)dt_ms) / 900; // air drag
if (p->y < (h + 40) * 16) all_gone = false;
continue;
}
// Off the top: send it back to the bottom to rise again.
if (p->y < -((int32_t)p->size + 8) * 16) petal_spawn(sd, p, w, h, false);
}
// Once the burst has fallen out of sight, resume the gentle drift.
if (sd->burst && all_gone) {
sd->burst = false;
for (int i = 0; i < SORRY_PETALS; i++) petal_spawn(sd, &sd->petals[i], w, h, true);
}
}
static void petals_draw(sorry_data_t* sd, int ox, int oy, int w, int h) {
for (int i = 0; i < SORRY_PETALS; i++) {
petal_t* p = &sd->petals[i];
int px = p->x / 16;
int py = p->y / 16;
if (p->sway) {
int phase = (int)(((sd->now_ms / 90) + p->phase) & 15);
px += (g_sin16[phase] * p->sway) / 127;
}
if (py < -20 || py > h + 20 || px < -20 || px > w + 20) continue;
heart_paint_t hp = {
.top = p->color, .bottom = p->color, .alpha = p->alpha, .hr = 0,
.clip_x0 = ox, .clip_y0 = oy, .clip_x1 = ox + w - 1, .clip_y1 = oy + h - 1
};
heart_scan(ox + px, oy + py, p->size, heart_span_paint, &hp);
}
}
// ===========================================================================
// Text helpers
// ===========================================================================
/* Draw at most `max_chars` characters, so a narrow window truncates cleanly
* instead of spilling text across the window border. */
static void draw_clipped(int x, int y, const char* s, int max_chars, uint32_t col) {
char buf[96];
if (max_chars <= 0 || !s || !*s) return;
if (max_chars > (int)sizeof(buf) - 1) max_chars = (int)sizeof(buf) - 1;
int n = 0;
while (s[n] && n < max_chars) { buf[n] = s[n]; n++; }
buf[n] = '\0';
gui_gfx_draw_string(x, y, buf, col);
}
static uint32_t letter_total_chars(const sorry_letter_t* L) {
uint32_t total = 0;
for (int i = 0; i < L->line_count; i++) {
total += (uint32_t)strlen(L->lines[i]) + SORRY_LINE_PAUSE;
}
return total;
}
/*
* Type the letter out. Returns true once the whole thing is on screen.
*
* `budget` characters are revealed in total, spent line by line, with a few
* characters' worth of pause charged at each line break so the rhythm reads
* like someone actually writing rather than a uniform ticker.
*/
static bool letter_draw(const sorry_letter_t* L, int x, int y, int line_h,
int max_chars, uint32_t budget, bool caret) {
char buf[96];
int limit = max_chars > (int)sizeof(buf) - 1 ? (int)sizeof(buf) - 1 : max_chars;
if (limit < 1) return true;
for (int i = 0; i < L->line_count; i++) {
int len = (int)strlen(L->lines[i]);
int row = y + i * line_h;
if (budget >= (uint32_t)len) {
draw_clipped(x, row, L->lines[i], limit, SORRY_TEXT);
budget -= (uint32_t)len;
// Charge the line break, but never let it underflow the budget.
budget = (budget > SORRY_LINE_PAUSE) ? budget - SORRY_LINE_PAUSE : 0;
if (budget == 0 && i + 1 < L->line_count) return false;
continue;
}
int n = (int)budget;
if (n > limit) n = limit;
for (int c = 0; c < n; c++) buf[c] = L->lines[i][c];
buf[n] = '\0';
gui_gfx_draw_string(x, row, buf, SORRY_TEXT);
if (caret) gui_gfx_fill_rect(x + n * 8, row + 1, 6, 8, SORRY_ROSE);
return false;
}
return true;
}
// ===========================================================================
// Paint
// ===========================================================================
static void sorry_paint(gui_window_t* win) {
sorry_data_t* sd = (sorry_data_t*)win->user_data;
if (!sd) return;
// Animate at the compositor's full rate rather than the 10 Hz heartbeat.
// Renewed every frame, so it stops mattering the moment this app closes.
gui_desktop_request_animation_frame();
gui_widget_begin(win);
int w = gui_widget_client_width();
int h = gui_widget_client_height();
int ox = win->x + 1;
int oy = win->y + GUI_TITLEBAR_HEIGHT + 1;
if (w < 40 || h < 40) { gui_widget_end(); return; }
uint64_t now = ktime_ms();
uint32_t dt = (now > sd->last_ms) ? (uint32_t)(now - sd->last_ms) : 0;
sd->now_ms = now;
sd->last_ms = now;
uint64_t in_stage = now - sd->stage_ms;
// --- background -------------------------------------------------------
gui_gfx_draw_gradient_v(ox, oy, w, h, SORRY_BG_TOP, SORRY_BG_BOT);
petals_step(sd, w, h, dt);
petals_draw(sd, ox, oy, w, h);
// --- layout -----------------------------------------------------------
// Give the letter the ~46 columns it is written for and let the heart have
// the rest, bounded so it stays sensible in both a small window and a
// maximised one.
int panel_w = w - 380;
if (panel_w < 150) panel_w = 150;
if (panel_w > 300) panel_w = 300;
if (panel_w > w - 120) panel_w = w - 120;
if (panel_w < 60) panel_w = 60;
int btn_row = h - 38;
int top = 24;
int caption_h = 36; // the two lines of text under the heart
// Fit to whichever of width or height runs out first, then take 100/120 of
// that: the heart is sized at rest but drawn up to ~1.15x at the peak of a
// beat, and the margin is what stops the lobes clipping the panel edge.
int avail = btn_row - top - caption_h;
int r_by_w = (panel_w - 16) / 2;
int r_by_h = (avail * 1000) / HEART_SPAN_Q;
int radius = ((r_by_w < r_by_h ? r_by_w : r_by_h) * 100) / 120;
if (radius > 120) radius = 120;
if (radius < 0) radius = 0;
int beat_period = (sd->stage == SORRY_WAITING) ? 1600 : 1150;
int beat_strength = (sd->stage == SORRY_WAITING) ? 55
: (sd->stage == SORRY_FORGIVEN) ? 130 : 100;
int beat = heart_beat_pct(now, (uint32_t)beat_period, beat_strength);
// A short swell as the window opens, so the heart arrives rather than
// simply being there.
if (in_stage < 420 && sd->stage != SORRY_APOLOGY) {
beat = beat + (int)((420 - in_stage) * 18 / 420);
}
int r_beat = (radius * beat) / 100;
int cx = panel_w / 2;
// Centre the heart-plus-caption block in the column rather than pinning it
// to the top, so the left side stays balanced at any window height. All of
// it is measured at the peak of a beat (r_peak), which is the extent that
// actually has to fit.
int r_peak = (radius * 120) / 100;
int block_h = (r_peak * HEART_SPAN_Q) / 1000 + caption_h;
int slack = (btn_row - top) - block_h;
if (slack < 0) slack = 0;
int cy = top + slack / 2 + (r_peak * HEART_TOP_Q) / 1000;
if (radius >= 14) {
uint32_t top_col, bot_col;
switch (sd->stage) {
case SORRY_FORGIVEN: top_col = SORRY_ROSE_LIGHT; bot_col = SORRY_ROSE; break;
case SORRY_WAITING: top_col = SORRY_ROSE_MUTED; bot_col = SORRY_ROSE_DEEP; break;
default: top_col = SORRY_ROSE; bot_col = SORRY_ROSE_DEEP; break;
}
// Record where the heart itself will land, so the glow behind it can
// skip every pixel it is about to be covered by.
heart_record(ox + cx, oy + cy, r_beat, oy, oy + h - 1);
heart_paint_t glow = {
.top = top_col, .bottom = bot_col, .hr = 0, .mask = true,
.clip_x0 = ox, .clip_y0 = oy, .clip_x1 = ox + w - 1, .clip_y1 = oy + h - 1
};
// Widening, fading copies behind the heart stand in for a real blur.
// They are drawn outermost first and each one covers the ones before
// it, so the accumulated alpha falls off smoothly towards the rim --
// six thin layers rather than three thick ones is the difference
// between a halo and a set of contour rings.
static const int glow_scale[6] = { 131, 125, 119, 113, 108, 104 };
for (int i = 0; i < 6; i++) {
glow.alpha = (uint8_t)(sd->stage == SORRY_WAITING ? 6 : 12);
heart_scan(ox + cx, oy + cy, (r_beat * glow_scale[i]) / 100,
heart_span_paint, &glow);
}
heart_paint_t body = {
.top = top_col, .bottom = bot_col, .alpha = 255,
.hx = ox + cx - (r_beat * 40) / 100,
.hy = oy + cy - (r_beat * 36) / 100,
.hr = (r_beat * 26) / 100,
.clip_x0 = ox, .clip_y0 = oy, .clip_x1 = ox + w - 1, .clip_y1 = oy + h - 1
};
heart_scan(ox + cx, oy + cy, r_beat, heart_span_paint, &body);
}
// --- caption under the heart -----------------------------------------
char buf[80];
// Clear the tip at the *peak* of a beat, not at rest, or the heart dips
// into the caption every time it thumps.
int cap_y = oy + cy + (r_peak * HEART_BOT_Q) / 1000 + 10;
const char* caption =
(sd->stage == SORRY_FORGIVEN) ? "and it is not going to stop" :
(sd->stage == SORRY_WAITING) ? "patient, for as long as it takes" :
"still beating, still sorry";
int cap_chars = (panel_w - 8) / 8;
int cap_w = (int)strlen(caption) * 8;
if (cap_w > panel_w - 8) cap_w = cap_chars * 8;
if (cap_y < oy + h - 12) {
draw_clipped(ox + cx - cap_w / 2, cap_y, caption, cap_chars, SORRY_TEXT_SOFT);
}
// The beat count is derived from elapsed time, so it is a real count of
// the beats that have actually been drawn since the window opened.
uint64_t beats = (now - sd->open_ms) / (uint64_t)beat_period;
snprintf(buf, sizeof(buf), "%llu beats so far", (unsigned long long)beats);
int bw = (int)strlen(buf) * 8;
if (cap_y + 14 < oy + h - 12) {
draw_clipped(ox + cx - bw / 2, cap_y + 14, buf, cap_chars, SORRY_TEXT_FAINT);
}
// --- the letter -------------------------------------------------------
int lx = panel_w + 10;
int lw = w - lx - 14;
int lc = lw / 8;
if (lc < 8) lc = 8;
const sorry_letter_t* L =
(sd->stage == SORRY_FORGIVEN) ? &g_letter_forgiven :
(sd->stage == SORRY_WAITING) ? &g_letter_waiting :
&g_letters[sd->letter];
uint32_t total = letter_total_chars(L);
uint32_t elapsed = (uint32_t)((in_stage * SORRY_TYPE_CPS) / 1000);
if (sd->typed < elapsed) sd->typed = elapsed;
if (sd->typed > total) sd->typed = total;
gui_gfx_draw_string_16_shadow(ox + lx, oy + 16, "I'm sorry.", SORRY_ROSE_LIGHT, 0xFF3A0F26);
gui_gfx_fill_rect(ox + lx, oy + 38, lw, 1, SORRY_RULE);
draw_clipped(ox + lx, oy + 46, L->heading, lc, SORRY_GOLD);
bool done = letter_draw(L, ox + lx, oy + 68, 15, lc, sd->typed,
((now / 420) & 1) != 0);
int sig_y = oy + 68 + L->line_count * 15 + 10;
if (done && sig_y < oy + btn_row - 26) {
const char* sig = (sd->stage == SORRY_APOLOGY)
? "-- and I mean every character of it."
: "-- SUB";
draw_clipped(ox + lx, sig_y, sig, lc, SORRY_TEXT_FAINT);
}
// How long they have been kept waiting, counted honestly.
if (sd->stage == SORRY_WAITING) {
uint64_t secs = in_stage / 1000;
snprintf(buf, sizeof(buf), "waiting patiently: %02llu:%02llu",
(unsigned long long)(secs / 60), (unsigned long long)(secs % 60));
draw_clipped(ox + lx, oy + btn_row - 22, buf, lc, SORRY_GOLD);
}
// Which letter of the three is on screen.
if (sd->stage == SORRY_APOLOGY) {
for (int i = 0; i < SORRY_LETTER_COUNT; i++) {
int dx = ox + lx + i * 14;
int dy = oy + btn_row - 18;
if (i == sd->letter) gui_gfx_fill_rect(dx, dy, 8, 3, SORRY_ROSE);
else gui_gfx_fill_rect(dx, dy, 8, 3, SORRY_RULE);
}
}
// --- controls ---------------------------------------------------------
int bx = lx;
int bh = 26;
if (sd->stage == SORRY_APOLOGY) {
if (gui_button_styled(1, bx, btn_row, 116, bh, "I forgive you",
SORRY_ROSE_DEEP, SORRY_ROSE, 0xFF8E1440,
SORRY_ROSE_LIGHT, SORRY_TEXT)) {
sd->stage = SORRY_FORGIVEN;
sd->stage_ms = now;
sd->typed = 0;
petals_burst(sd, cx, cy);
}
bx += 124;
if (gui_button_styled(2, bx, btn_row, 104, bh, "Tell me more",
0xFF3A1730, 0xFF542444, 0xFF2A0F22,
SORRY_RULE, SORRY_TEXT_SOFT)) {
sd->letter = (sd->letter + 1) % SORRY_LETTER_COUNT;
sd->stage_ms = now;
sd->typed = 0;
}
bx += 112;
if (gui_button_styled(3, bx, btn_row, 76, bh, "Not yet",
0xFF3A1730, 0xFF542444, 0xFF2A0F22,
SORRY_RULE, SORRY_TEXT_SOFT)) {
sd->stage = SORRY_WAITING;
sd->stage_ms = now;
sd->typed = 0;
}
} else if (sd->stage == SORRY_WAITING) {
if (gui_button_styled(4, bx, btn_row, 116, bh, "I'm ready now",
SORRY_ROSE_DEEP, SORRY_ROSE, 0xFF8E1440,
SORRY_ROSE_LIGHT, SORRY_TEXT)) {
sd->stage = SORRY_FORGIVEN;
sd->stage_ms = now;
sd->typed = 0;
petals_burst(sd, cx, cy);
}
bx += 124;
if (gui_button_styled(5, bx, btn_row, 104, bh, "Say it again",
0xFF3A1730, 0xFF542444, 0xFF2A0F22,
SORRY_RULE, SORRY_TEXT_SOFT)) {
sd->stage = SORRY_APOLOGY;
sd->stage_ms = now;
sd->typed = 0;
}
} else {
if (gui_button_styled(6, bx, btn_row, 116, bh, "Read it again",
0xFF3A1730, 0xFF542444, 0xFF2A0F22,
SORRY_RULE, SORRY_TEXT_SOFT)) {
sd->stage = SORRY_APOLOGY;
sd->letter = 0;
sd->stage_ms = now;
sd->typed = 0;
}
}
// Impatient readers can click the letter to finish the typing at once.
// Placed last so the buttons above get first claim on the click.
if (!done && gui_hitzone(9, lx, 46, lw, btn_row - 52)) sd->typed = total;
gui_widget_end();
}
static void sorry_event(gui_window_t* win, const gui_event_t* ev) {
gui_widget_feed_event(win, ev);
if (ev->type == GUI_EVENT_CLOSE && win->user_data) {
kfree(win->user_data);
win->user_data = NULL;
}
}
void gui_app_sorry_launch(int x, int y, int w, int h) {
gui_window_t* win = gui_wm_create_window("I'm Sorry", x, y,
(w > 0) ? w : 660, (h > 0) ? h : 470);
if (!win) return;
sorry_data_t* sd = (sorry_data_t*)kzalloc(sizeof(sorry_data_t));
if (!sd) {
gui_wm_destroy_window(win->id);
return;
}
uint64_t now = ktime_ms();
sd->open_ms = now;
sd->stage_ms = now;
sd->last_ms = now;
sd->stage = SORRY_APOLOGY;
sd->rng = (uint32_t)(now * 2654435761u) | 1u;
// Scatter the first drift across the window so it does not start empty.
for (int i = 0; i < SORRY_PETALS; i++) {
petal_spawn(sd, &sd->petals[i], win->width - 2,
win->height - GUI_TITLEBAR_HEIGHT - 2, true);
}
win->user_data = sd;
win->paint = sorry_paint;
win->handle_event = sorry_event;
}
+717
View File
@@ -0,0 +1,717 @@
/*
* SUB-OS Web -- a minimal text web browser.
*
* It is the visible end of the kernel's TCP client: type a URL, and the page
* is fetched over the stack SUB-OS built. The fetch itself is blocking, so it
* runs on the kernel's async HTTP worker thread (net/http_client) rather than
* in the compositor -- the desktop keeps painting at full frame rate while a
* page loads. This window only ever polls a job's state and renders the
* result, so it never stalls.
*
* Two views of the response: Raw shows the status line, headers and body
* exactly as received; Reader strips the markup and reflows the body into
* wrapped text. The rendered line index is rebuilt only when the content, the
* view or the window width changes, so scrolling and idle repaints are cheap.
*/
#include <gui/gui_apps.h>
#include <gui/gui_widgets.h>
#include <gui/gui_gfx.h>
#include <gui/gui_theme.h>
#include <kernel/ktime.h>
#include <mm/kmalloc.h>
#include <lib/string.h>
#include <lib/printf.h>
#include <net/http_client.h>
#include <net/net.h>
#include <lib/image.h>
#include <drivers/keyboard.h>
#define WEB_FETCH_CAP 131072 // response bytes retained (fits images and long pages)
#define WEB_MAX_LINES 2048 // rendered display lines retained
#define WEB_MAX_LINKS 400 // hyperlinks tracked per page
#define WEB_HREF_POOL 6144 // bytes of href strings per page
#define WEB_HISTORY 16 // back-stack depth
#define WEB_URL_FIELD 1
// A hyperlink: the [start,end) span it occupies in the processed text, and the
// offset of its href string in the pool.
typedef struct {
int start;
int end;
int href;
} web_link_t;
typedef struct {
char url[256];
http_fetch_t* job;
bool reader; // strip markup and reflow
int scroll; // first visible display line
// Rendered text and its line index, cached against the inputs that
// determine them.
char* text; // processed text being displayed
int text_len;
int offsets[WEB_MAX_LINES];
int lengths[WEB_MAX_LINES];
int nlines;
// Links found while stripping markup (reader view only).
web_link_t links[WEB_MAX_LINKS];
int nlinks;
char hrefs[WEB_HREF_POOL];
int href_used;
// The page's own address, used to resolve relative links.
char base_host[128];
uint16_t base_port;
char base_path[256];
// Back stack of previously-visited URLs.
char hist[WEB_HISTORY][256];
int hist_len;
// When the response is an image, it is decoded once into here and shown
// instead of text.
bool has_image;
image_t image;
const char* cache_src; // job->buf the cache was built from
int cache_len;
bool cache_reader;
int cache_cpl; // columns the cache was wrapped to
} web_data_t;
// ===========================================================================
// HTML to text
// ===========================================================================
static bool tag_is_break(const char* tag) {
// Block-level tags whose close (or self) should start a new line.
static const char* const brk[] = {
"br", "/p", "p", "/div", "/h1", "/h2", "/h3", "/h4", "/h5", "/h6",
"/li", "/tr", "/ul", "/ol", "/table", "hr", "/title", "/head"
};
for (size_t i = 0; i < sizeof(brk) / sizeof(brk[0]); i++) {
const char* b = brk[i];
size_t n = strlen(b);
// Match the tag name up to a space, '>' or end, case-insensitively.
size_t k = 0;
while (k < n && tag[k]) {
char a = tag[k], c = b[k];
if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a');
if (a != c) break;
k++;
}
if (k == n) {
char after = tag[k];
if (after == '\0' || after == ' ' || after == '>' || after == '/') return true;
}
}
return false;
}
// Decode the handful of entities worth handling; returns the byte, or 0 if the
// entity is unknown (in which case the caller keeps it verbatim).
static char decode_entity(const char* e, int len) {
if (len == 2 && strncmp(e, "lt", 2) == 0) return '<';
if (len == 2 && strncmp(e, "gt", 2) == 0) return '>';
if (len == 3 && strncmp(e, "amp", 3) == 0) return '&';
if (len == 4 && strncmp(e, "nbsp", 4) == 0) return ' ';
if (len == 4 && strncmp(e, "quot", 4) == 0) return '"';
if (len == 4 && strncmp(e, "apos", 4) == 0) return '\'';
if (len == 5 && strncmp(e, "mdash", 5) == 0) return '-';
if (len == 5 && strncmp(e, "ndash", 5) == 0) return '-';
return 0;
}
// Copy the href="..." value out of an anchor tag's body (the text between the
// '<' and its '>') into the page's href pool, and return its pool offset, or -1
// if there is no usable href or the pool is full.
static int capture_href(web_data_t* wd, const char* tag, int taglen) {
// Find "href" (case-insensitive) followed by '='.
int h = -1;
for (int i = 0; i + 4 <= taglen; i++) {
char a = tag[i], b = tag[i + 1], c = tag[i + 2], d = tag[i + 3];
if ((a | 32) == 'h' && (b | 32) == 'r' && (c | 32) == 'e' && (d | 32) == 'f') {
int j = i + 4;
while (j < taglen && (tag[j] == ' ' || tag[j] == '\t')) j++;
if (j < taglen && tag[j] == '=') { h = j + 1; break; }
}
}
if (h < 0) return -1;
while (h < taglen && (tag[h] == ' ' || tag[h] == '\t')) h++;
char quote = 0;
if (h < taglen && (tag[h] == '"' || tag[h] == '\'')) { quote = tag[h]; h++; }
if (wd->href_used >= WEB_HREF_POOL - 1) return -1;
int off = wd->href_used;
int o = off;
while (h < taglen && o < WEB_HREF_POOL - 1) {
char c = tag[h];
if (quote ? (c == quote) : (c == ' ' || c == '\t' || c == '>')) break;
wd->hrefs[o++] = c;
h++;
}
wd->hrefs[o++] = '\0';
if (o == off + 1) return -1; // empty href
wd->href_used = o;
return off;
}
// Strip markup from `src` into wd->text, collapsing whitespace and turning
// block tags into newlines. <script>/<style> bodies are dropped. Anchors are
// recorded as links spanning the text they enclose. Returns the text length.
static int html_to_text(web_data_t* wd, const char* src, int slen) {
char* out = wd->text;
int cap = WEB_FETCH_CAP;
int o = 0;
bool sp_pending = false; // a space owed but not yet emitted
bool at_line_start = true;
bool link_open = false; // inside an <a>...</a>
int link_start = 0;
int link_href = -1;
wd->nlinks = 0;
wd->href_used = 0;
for (int i = 0; i < slen && o < cap - 1; i++) {
char ch = src[i];
if (ch == '<') {
const char* tag = src + i + 1;
// Skip <script>...</script> and <style>...</style> bodies entirely.
if (strncmp(tag, "script", 6) == 0 || strncmp(tag, "style", 5) == 0) {
const char* close = (tag[0] == 's' && tag[1] == 'c') ? "</script" : "</style";
int cl = (int)strlen(close);
int j = i + 1;
while (j < slen && strncmp(src + j, close, cl) != 0) j++;
i = j;
while (i < slen && src[i] != '>') i++;
continue;
}
bool brk = tag_is_break(tag);
bool a_open = (tag[0] | 32) == 'a' &&
(tag[1] == ' ' || tag[1] == '\t' || tag[1] == '>');
bool a_close = tag[0] == '/' && (tag[1] | 32) == 'a' &&
(tag[2] == '>' || tag[2] == ' ');
int tstart = i + 1;
while (i < slen && src[i] != '>') i++; // consume to tag close
int tlen = i - tstart;
if (a_open && !link_open) {
link_href = capture_href(wd, src + tstart, tlen);
link_open = (link_href >= 0);
link_start = o;
} else if (a_close && link_open) {
if (o > link_start && wd->nlinks < WEB_MAX_LINKS) {
wd->links[wd->nlinks].start = link_start;
wd->links[wd->nlinks].end = o;
wd->links[wd->nlinks].href = link_href;
wd->nlinks++;
}
link_open = false;
}
if (brk && !at_line_start) {
out[o++] = '\n';
at_line_start = true;
sp_pending = false;
}
continue;
}
if (ch == '&') {
int j = i + 1;
while (j < slen && src[j] != ';' && j - i < 8) j++;
char d = (j < slen && src[j] == ';') ? decode_entity(src + i + 1, j - i - 1) : 0;
if (d) {
if (sp_pending && !at_line_start) { out[o++] = ' '; sp_pending = false; }
if (o < cap - 1) out[o++] = d;
at_line_start = false;
i = j;
continue;
}
// Unknown entity: fall through and treat '&' as an ordinary char.
}
if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') {
if (!at_line_start) sp_pending = true;
continue;
}
if (sp_pending) { out[o++] = ' '; sp_pending = false; if (o >= cap - 1) break; }
out[o++] = ch;
at_line_start = false;
}
out[o] = '\0';
return o;
}
// ===========================================================================
// Line breaking
// ===========================================================================
static void web_build_lines(web_data_t* wd, int cpl) {
wd->nlines = 0;
if (cpl < 8) cpl = 8;
int n = wd->text_len;
const char* t = wd->text;
int i = 0;
while (i < n && wd->nlines < WEB_MAX_LINES) {
int start = i;
int last_space = -1;
int col = 0;
while (i < n && t[i] != '\n' && col < cpl) {
if (t[i] == ' ') last_space = i;
i++;
col++;
}
int end;
if (i < n && t[i] == '\n') {
end = i;
i++; // consume the newline
} else if (col >= cpl && i < n) {
if (last_space > start) { // wrap at the last space
end = last_space;
i = last_space + 1;
} else {
end = i; // a word longer than a line
}
} else {
end = i; // reached the end of the text
}
wd->offsets[wd->nlines] = start;
wd->lengths[wd->nlines] = end - start;
wd->nlines++;
}
}
// Rebuild the processed text and line index if any input changed.
static void web_refresh_cache(web_data_t* wd, int cpl) {
http_fetch_t* j = wd->job;
if (!j || j->state != HTTP_STATE_DONE) { wd->nlines = 0; return; }
if (wd->cache_src == j->buf && wd->cache_len == j->len &&
wd->cache_reader == wd->reader && wd->cache_cpl == cpl) {
return; // still valid
}
// Remember the page's own address for resolving relative links.
strncpy(wd->base_host, j->host, sizeof(wd->base_host) - 1);
wd->base_host[sizeof(wd->base_host) - 1] = '\0';
strncpy(wd->base_path, j->path, sizeof(wd->base_path) - 1);
wd->base_path[sizeof(wd->base_path) - 1] = '\0';
wd->base_port = j->port;
// If the body is an image, decode it once and show it instead of text.
if (wd->has_image) image_free(&wd->image);
wd->has_image = false;
{
const uint8_t* body = (const uint8_t*)j->buf;
char* sep = strstr(j->buf, "\r\n\r\n");
if (sep) body = (const uint8_t*)(sep + 4);
int blen = j->len - (int)((const char*)body - j->buf);
if (blen > 0 && image_sniff(body, blen)) {
if (image_decode(body, blen, &wd->image) == 0) wd->has_image = true;
}
}
if (wd->has_image) {
wd->nlines = 0;
wd->cache_src = j->buf; wd->cache_len = j->len;
wd->cache_reader = wd->reader; wd->cache_cpl = cpl;
return; // image branch: no text layout
}
if (wd->reader) {
// Reader view: skip the headers, then strip markup from the body,
// recording anchors as links as it goes.
const char* body = j->buf;
char* sep = strstr(j->buf, "\r\n\r\n");
if (sep) body = sep + 4;
int blen = j->len - (int)(body - j->buf);
if (blen < 0) blen = 0;
wd->text_len = html_to_text(wd, body, blen);
} else {
// Raw view: the response verbatim, with tabs normalised. No links.
int n = j->len < WEB_FETCH_CAP - 1 ? j->len : WEB_FETCH_CAP - 1;
for (int k = 0; k < n; k++) {
char c = j->buf[k];
wd->text[k] = (c == '\t') ? ' ' : c;
}
wd->text[n] = '\0';
wd->text_len = n;
wd->nlinks = 0;
}
web_build_lines(wd, cpl);
wd->cache_src = j->buf;
wd->cache_len = j->len;
wd->cache_reader = wd->reader;
wd->cache_cpl = cpl;
if (wd->scroll > wd->nlines - 1) wd->scroll = 0;
}
// ===========================================================================
// Fetching and navigation
// ===========================================================================
static void web_go(web_data_t* wd) {
if (wd->url[0] == '\0') return;
// Drop any previous job (releasing a still-running one hands it to the
// worker to free) and invalidate the render cache.
if (wd->job) { http_fetch_release(wd->job); wd->job = NULL; }
wd->cache_src = NULL;
wd->scroll = 0;
wd->job = http_fetch_start(wd->url, WEB_FETCH_CAP);
// A NULL result (worker busy or bad URL) is reflected in the status line
// drawn from the live job state, so nothing more to do here.
}
// Resolve a link's href against the current page into an absolute URL. Returns
// false for links that are not navigable here (fragments, mailto:, javascript:,
// empty). https:// is resolved through and left for the fetch layer to refuse
// with a clear message rather than silently dropped.
static bool web_resolve(web_data_t* wd, const char* href, char* out, size_t cap) {
if (!href || href[0] == '\0' || href[0] == '#') return false;
if (strncmp(href, "mailto:", 7) == 0 || strncmp(href, "javascript:", 11) == 0) {
return false;
}
if (strncmp(href, "http://", 7) == 0 || strncmp(href, "https://", 8) == 0) {
strncpy(out, href, cap - 1);
out[cap - 1] = '\0';
return true;
}
// Port suffix only when it is not the default 80.
char portbuf[8] = "";
if (wd->base_port != 80) snprintf(portbuf, sizeof(portbuf), ":%u", wd->base_port);
if (href[0] == '/') {
// Root-relative: keep host, replace path.
snprintf(out, cap, "http://%s%s%s", wd->base_host, portbuf, href);
return true;
}
// Document-relative: append to the current path's directory.
char dir[256];
strncpy(dir, wd->base_path, sizeof(dir) - 1);
dir[sizeof(dir) - 1] = '\0';
char* slash = strrchr(dir, '/');
if (slash) slash[1] = '\0'; // keep the trailing slash
else strcpy(dir, "/");
snprintf(out, cap, "http://%s%s%s%s", wd->base_host, portbuf, dir, href);
return true;
}
// Follow a link: remember where we are, then load the target.
static void web_navigate(web_data_t* wd, const char* href) {
char target[256];
if (!web_resolve(wd, href, target, sizeof(target))) return;
// Push the current address onto the back stack (dropping the oldest if it
// is full), unless we have not loaded anything yet.
if (wd->url[0]) {
if (wd->hist_len == WEB_HISTORY) {
for (int i = 1; i < WEB_HISTORY; i++) {
strcpy(wd->hist[i - 1], wd->hist[i]);
}
wd->hist_len--;
}
strncpy(wd->hist[wd->hist_len], wd->url, sizeof(wd->hist[0]) - 1);
wd->hist[wd->hist_len][sizeof(wd->hist[0]) - 1] = '\0';
wd->hist_len++;
}
strncpy(wd->url, target, sizeof(wd->url) - 1);
wd->url[sizeof(wd->url) - 1] = '\0';
web_go(wd);
}
static void web_back(web_data_t* wd) {
if (wd->hist_len == 0) return;
wd->hist_len--;
strncpy(wd->url, wd->hist[wd->hist_len], sizeof(wd->url) - 1);
wd->url[sizeof(wd->url) - 1] = '\0';
web_go(wd);
}
// ===========================================================================
// Paint
// ===========================================================================
static void web_paint(gui_window_t* win) {
web_data_t* wd = (web_data_t*)win->user_data;
if (!wd) return;
gui_widget_begin(win);
int w = gui_widget_client_width();
int h = gui_widget_client_height();
int ox = win->x + 1;
int oy = win->y + GUI_TITLEBAR_HEIGHT + 1;
if (w < 240 || h < 120) { gui_widget_end(); return; }
// --- toolbar ----------------------------------------------------------
int back_w = 26, go_w = 34, mode_w = 62;
int field_x = 10 + back_w + 4;
int field_w = w - field_x - 4 - go_w - 4 - mode_w - 8;
if (field_w < 60) field_w = 60;
// Back is enabled only when there is somewhere to go back to.
bool can_back = wd->hist_len > 0;
if (gui_button_colored(6, 10, 8, back_w, 20, "<",
can_back ? GUI_THEME_PRIMARY : GUI_THEME_BORDER) && can_back) {
web_back(wd);
}
if (gui_textfield(WEB_URL_FIELD, field_x, 8, field_w, wd->url, sizeof(wd->url))) {
/* editing; nothing to do until Go */
}
// Enter in the focused URL field triggers the fetch: the field leaves
// newline keys unconsumed, so they are still visible here.
const gui_widget_input_t* in = gui_widget_get_input();
if (in->focus_id == WEB_URL_FIELD && (in->key == '\n' || in->key == '\r')) {
gui_widget_set_focus(0);
web_go(wd);
}
if (gui_button(2, field_x + field_w + 4, 8, go_w, 20, "Go")) web_go(wd);
if (gui_button_colored(3, field_x + field_w + 4 + go_w + 4, 8, mode_w, 20,
wd->reader ? "Reader" : "Raw",
wd->reader ? GUI_THEME_SUCCESS : GUI_THEME_ACCENT)) {
wd->reader = !wd->reader;
wd->cache_src = NULL; // force a re-render in the new view
wd->scroll = 0;
}
// --- status line ------------------------------------------------------
int bar_y = 34;
char status[128];
uint32_t status_col = GUI_THEME_TEXT_MUTED;
if (!wd->job) {
snprintf(status, sizeof(status), "Enter a URL and press Go (e.g. 10.0.2.2:8000/)");
} else if (wd->job->state == HTTP_STATE_RUNNING) {
// A small spinner off the 10 Hz repaint so a slow fetch looks alive.
static const char frames[] = { '|', '/', '-', '\\' };
char sp = frames[(ktime_ms() / 120) % 4];
snprintf(status, sizeof(status), "%c fetching %s ...", sp, wd->job->host);
status_col = GUI_THEME_WARNING;
} else if (wd->job->state == HTTP_STATE_ERROR) {
snprintf(status, sizeof(status), "error: %s", wd->job->err);
status_col = GUI_THEME_DANGER;
} else {
char ips[16];
ip_to_str(wd->job->ip, ips);
snprintf(status, sizeof(status), "HTTP %d %d bytes %u ms %s:%u (%s)",
wd->job->status_code, wd->job->len, wd->job->elapsed_ms,
wd->job->host, wd->job->port, ips);
status_col = (wd->job->status_code == 200) ? GUI_THEME_SUCCESS : GUI_THEME_TEXT_MAIN;
}
gui_label(10, bar_y, status, status_col);
gui_separator(10, bar_y + 14, w - 20);
// --- page body --------------------------------------------------------
int view_x = 10;
int view_y = bar_y + 20;
int view_h = h - view_y - 6;
int line_h = 10;
int cpl = (w - 20 - 12) / GUI_FONT_W; // leave room for the scrollbar
if (cpl < 8) cpl = 8;
int rows = view_h / line_h;
if (rows < 1) rows = 1;
gui_gfx_fill_rect(ox + view_x, oy + view_y, w - 20, view_h, GUI_THEME_BG_DARK);
gui_gfx_draw_rect(ox + view_x, oy + view_y, w - 20, view_h, GUI_THEME_BORDER);
web_refresh_cache(wd, cpl);
if (wd->has_image && wd->job && wd->job->state == HTTP_STATE_DONE) {
image_t* im = &wd->image;
int avail_w = (w - 20) - 12; // panel minus scrollbar
if (avail_w < 16) avail_w = 16;
int sw = im->width, sh = im->height;
if (sw > avail_w) { sh = (int)((long)sh * avail_w / sw); sw = avail_w; }
if (sh < 1) sh = 1;
int page = view_h - 6;
if (wd->scroll > sh - 1) wd->scroll = sh - 1;
if (wd->scroll < 0) wd->scroll = 0;
int draw_x = ox + view_x + 6;
int draw_y0 = oy + view_y + 4;
// Column map (screen column -> source column) so the inner loop has no
// divide. Bounded by the panel width.
static int xmap[1920];
int cols = sw < (int)(sizeof(xmap) / sizeof(xmap[0])) ? sw
: (int)(sizeof(xmap) / sizeof(xmap[0]));
for (int sx = 0; sx < cols; sx++) xmap[sx] = (int)((long)sx * im->width / sw);
for (int r = 0; r < page; r++) {
int scaled_y = wd->scroll + r;
if (scaled_y >= sh) break;
int iy = (int)((long)scaled_y * im->height / sh);
if (iy >= im->height) break;
const uint32_t* srow = im->pixels + (long)iy * im->width;
int py = draw_y0 + r;
for (int sx = 0; sx < cols; sx++) {
uint32_t px = srow[xmap[sx]];
uint8_t a = (uint8_t)(px >> 24);
uint32_t rgb = 0xFF000000u | (px & 0x00FFFFFFu);
uint32_t col = (a == 255) ? rgb
: gui_color_alpha_blend(GUI_THEME_BG_DARK, rgb, a);
gui_gfx_draw_pixel(draw_x + sx, py, col);
}
}
gui_scrollbar(4, w - 18, view_y, view_h, &wd->scroll, sh, page);
if (in->focus_id != WEB_URL_FIELD) {
if (in->key == KEY_DOWN) wd->scroll += 16;
else if (in->key == KEY_UP) wd->scroll -= 16;
else if (in->key == KEY_PAGE_DOWN) wd->scroll += page;
else if (in->key == KEY_PAGE_UP) wd->scroll -= page;
}
char hint[48];
snprintf(hint, sizeof(hint), "image %dx%d", im->width, im->height);
gui_label_aligned(0, bar_y, w - 12, hint, GUI_THEME_ACCENT, GUI_ALIGN_RIGHT);
} else if (wd->job && wd->job->state == HTTP_STATE_DONE && wd->nlines > 0) {
if (wd->scroll > wd->nlines - 1) wd->scroll = wd->nlines - 1;
if (wd->scroll < 0) wd->scroll = 0;
// A click on a link's text follows it. Map the click to a text offset,
// then to the link whose span contains it. Done before drawing so a
// navigation takes hold immediately.
if (wd->reader && wd->nlinks && in->mouse_clicked) {
int rx = in->mouse_x - (view_x + 6);
int ry = in->mouse_y - (view_y + 5);
if (rx >= 0 && ry >= 0 && in->mouse_x < view_x + (w - 20) - 4 &&
in->mouse_y < view_y + view_h) {
int idx = wd->scroll + ry / line_h;
int col = rx / GUI_FONT_W;
if (idx >= 0 && idx < wd->nlines && col < wd->lengths[idx]) {
int off = wd->offsets[idx] + col;
for (int li = 0; li < wd->nlinks; li++) {
if (off >= wd->links[li].start && off < wd->links[li].end) {
web_navigate(wd, wd->hrefs + wd->links[li].href);
break;
}
}
}
}
}
char line[200];
for (int r = 0; r < rows; r++) {
int idx = wd->scroll + r;
if (idx >= wd->nlines) break;
int loff = wd->offsets[idx];
int len = wd->lengths[idx];
if (len > (int)sizeof(line) - 1) len = (int)sizeof(line) - 1;
memcpy(line, wd->text + loff, len);
line[len] = '\0';
int sx = ox + view_x + 6;
int sy = oy + view_y + 5 + r * line_h;
// In raw view the header block (up to the blank line) is dimmed so
// the body stands out; reader view is uniform.
uint32_t col = GUI_THEME_TEXT_MAIN;
if (!wd->reader && len == 0) col = GUI_THEME_TEXT_DIM;
gui_gfx_draw_string(sx, sy, line, col);
// Overlay any link spans on this line in the accent colour, with an
// underline, so hyperlinks read as clickable.
if (wd->reader) {
for (int li = 0; li < wd->nlinks; li++) {
int s = wd->links[li].start, e = wd->links[li].end;
if (e <= loff || s >= loff + len) continue;
int a = s > loff ? s : loff;
int b = e < loff + len ? e : loff + len;
int seglen = b - a;
if (seglen <= 0 || seglen > (int)sizeof(line) - 1) continue;
char seg[200];
memcpy(seg, wd->text + a, seglen);
seg[seglen] = '\0';
int segx = sx + (a - loff) * GUI_FONT_W;
gui_gfx_draw_string(segx, sy, seg, GUI_THEME_ACCENT);
gui_gfx_fill_rect(segx, sy + 9, seglen * GUI_FONT_W, 1, GUI_THEME_ACCENT);
}
}
}
// Scrollbar and keyboard paging.
gui_scrollbar(4, w - 18, view_y, view_h, &wd->scroll, wd->nlines, rows);
if (in->focus_id != WEB_URL_FIELD) {
if (in->key == KEY_DOWN) wd->scroll += 1;
else if (in->key == KEY_UP) wd->scroll -= 1;
else if (in->key == KEY_PAGE_DOWN) wd->scroll += rows;
else if (in->key == KEY_PAGE_UP) wd->scroll -= rows;
}
// A link count hint, so it is clear the page is navigable.
if (wd->reader && wd->nlinks) {
char hint[48];
snprintf(hint, sizeof(hint), "%d link%s - click to follow",
wd->nlinks, wd->nlinks == 1 ? "" : "s");
gui_label_aligned(0, bar_y, w - 12, hint, GUI_THEME_ACCENT, GUI_ALIGN_RIGHT);
}
} else if (wd->job && wd->job->state == HTTP_STATE_RUNNING) {
gui_gfx_draw_string(ox + view_x + 8, oy + view_y + 10,
"Loading over TCP -- the desktop stays responsive.",
GUI_THEME_TEXT_DIM);
}
gui_widget_end();
}
static void web_event(gui_window_t* win, const gui_event_t* ev) {
gui_widget_feed_event(win, ev);
if (ev->type == GUI_EVENT_CLOSE && win->user_data) {
web_data_t* wd = (web_data_t*)win->user_data;
if (wd->job) http_fetch_release(wd->job); // safe mid-fetch
if (wd->has_image) image_free(&wd->image);
if (wd->text) kfree(wd->text);
kfree(wd);
win->user_data = NULL;
}
}
void gui_app_web_launch(int x, int y, int w, int h) {
gui_window_t* win = gui_wm_create_window("Web", x, y,
(w > 0) ? w : 600, (h > 0) ? h : 420);
if (!win) return;
web_data_t* wd = (web_data_t*)kzalloc(sizeof(web_data_t));
if (!wd) { gui_wm_destroy_window(win->id); return; }
wd->text = (char*)kzalloc(WEB_FETCH_CAP);
if (!wd->text) { kfree(wd); gui_wm_destroy_window(win->id); return; }
wd->reader = true;
strcpy(wd->url, "https://example.com/"); // a real HTTPS page as the default
win->user_data = wd;
win->paint = web_paint;
win->handle_event = web_event;
}
+1056
View File
File diff suppressed because it is too large. Load diff
+856
View File
@@ -0,0 +1,856 @@
// SUB-OS Desktop Applications, second generation
//
// These apps are written entirely against the SUB-WT immediate-mode toolkit:
// each one keeps a small state struct in win->user_data, forwards WM events
// into the toolkit, and rebuilds its interface every frame from that state.
#include <gui/gui_apps_ext.h>
#include <gui/gui_widgets.h>
#include <gui/gui_icons.h>
#include <gui/gui_dialog.h>
#include <gui/gui_gfx.h>
#include <gui/gui_theme.h>
#include <gui/gui_desktop.h>
#include <kernel/ktest.h>
#include <kernel/printk.h>
#include <kernel/task.h>
#include <kernel/rcu.h>
#include <kernel/futex.h>
#include <kernel/tsc.h>
#include <mm/kmalloc.h>
#include <mm/pmm.h>
#include <mm/page_cache.h>
#include <drivers/rtc.h>
#include <drivers/cpufreq.h>
#include <drivers/mouse.h>
#include <drivers/keyboard.h>
#include <arch/arch.h>
#include <lib/string.h>
#include <lib/printf.h>
// Shared plumbing: every app forwards WM events into the toolkit, and frees
// its state struct when the window closes.
static void app_forward_event(gui_window_t* win, const gui_event_t* ev) {
gui_widget_feed_event(win, ev);
if (ev->type == GUI_EVENT_CLOSE && win->user_data) {
kfree(win->user_data);
win->user_data = NULL;
}
}
// ===========================================================================
// 1. Settings
// ===========================================================================
typedef struct {
int active_tab;
bool show_grid;
bool window_shadows;
bool sound_enabled;
bool show_clock_seconds;
int theme_choice; // 0 = Nord Dark, 1 = Midnight, 2 = Slate
int cursor_speed;
int brightness;
int governor_choice; // 0 = powersave, 1 = ondemand, 2 = performance
} settings_data_t;
static const char* const settings_tabs[] = { "Appearance", "Input", "Power" };
// Map the Settings governor radio (0 powersave / 1 ondemand / 2 performance)
// to and from the cpufreq_gov_type_t enum, whose order differs.
static cpufreq_gov_type_t settings_gov_from_choice(int choice) {
switch (choice) {
case 0: return CPUFREQ_GOV_POWERSAVE;
case 2: return CPUFREQ_GOV_PERFORMANCE;
default: return CPUFREQ_GOV_ONDEMAND;
}
}
static int settings_choice_from_gov(cpufreq_gov_type_t gov) {
switch (gov) {
case CPUFREQ_GOV_POWERSAVE: return 0;
case CPUFREQ_GOV_PERFORMANCE: return 2;
default: return 1;
}
}
static void settings_paint(gui_window_t* win) {
settings_data_t* sd = (settings_data_t*)win->user_data;
if (!sd) return;
gui_widget_begin(win);
int w = gui_widget_client_width();
gui_tabbar(1, 6, 6, w - 12, settings_tabs, 3, &sd->active_tab);
int y = 40;
char buf[64];
if (sd->active_tab == 0) {
gui_label_bold(10, y, "Desktop Appearance", GUI_THEME_PRIMARY);
y += 24;
if (gui_checkbox(10, 10, y, "Show wallpaper grid overlay", &sd->show_grid)) {
gui_desktop_set_grid(sd->show_grid); // apply immediately
}
y += 22;
gui_checkbox(11, 10, y, "Drop shadows under windows", &sd->window_shadows);
y += 26;
gui_label(10, y, "Theme", GUI_THEME_TEXT_MUTED);
y += 14;
gui_radio(12, 14, y, "Nord Dark", &sd->theme_choice, 0); y += 18;
gui_radio(13, 14, y, "Midnight", &sd->theme_choice, 1); y += 18;
gui_radio(14, 14, y, "Slate Grey", &sd->theme_choice, 2); y += 24;
snprintf(buf, sizeof(buf), "Brightness: %d%%", sd->brightness);
gui_label(10, y, buf, GUI_THEME_TEXT_MUTED);
gui_slider(15, 150, y + 4, w - 170, &sd->brightness, 20, 100);
} else if (sd->active_tab == 1) {
gui_label_bold(10, y, "Input Devices", GUI_THEME_PRIMARY);
y += 24;
snprintf(buf, sizeof(buf), "Pointer speed: %d", sd->cursor_speed);
gui_label(10, y, buf, GUI_THEME_TEXT_MUTED);
gui_slider(20, 150, y + 4, w - 170, &sd->cursor_speed, 1, 10);
y += 30;
gui_checkbox(21, 10, y, "Enable UI sound effects", &sd->sound_enabled);
y += 22;
if (gui_checkbox(22, 10, y, "Show seconds in taskbar clock", &sd->show_clock_seconds)) {
gui_desktop_set_clock_seconds(sd->show_clock_seconds); // apply immediately
}
y += 30;
const mouse_state_t* ms = mouse_get_state();
snprintf(buf, sizeof(buf), "PS/2 mouse at (%d, %d) L:%s R:%s",
ms ? ms->x : 0, ms ? ms->y : 0,
(ms && ms->left_btn) ? "down" : "up",
(ms && ms->right_btn) ? "down" : "up");
gui_label(10, y, buf, GUI_THEME_TEXT_DIM);
} else {
gui_label_bold(10, y, "Power & Frequency", GUI_THEME_PRIMARY);
y += 24;
gui_label(10, y, "CPUFreq governor", GUI_THEME_TEXT_MUTED);
y += 16;
bool gov_changed = false;
gov_changed |= gui_radio(30, 14, y, "powersave", &sd->governor_choice, 0); y += 18;
gov_changed |= gui_radio(31, 14, y, "ondemand", &sd->governor_choice, 1); y += 18;
gov_changed |= gui_radio(32, 14, y, "performance", &sd->governor_choice, 2); y += 26;
if (gov_changed) {
cpufreq_set_governor(settings_gov_from_choice(sd->governor_choice));
}
cpufreq_stats_t cf = cpufreq_get_stats();
snprintf(buf, sizeof(buf), "Current clock: %u.%03u GHz",
cf.current_khz / 1000000, (cf.current_khz % 1000000) / 1000);
gui_label(10, y, buf, GUI_THEME_TEXT_MAIN);
y += 16;
int load = (int)(cf.current_khz / 30000);
if (load > 100) load = 100;
gui_progress_bar(10, y, w - 24, 12, load, GUI_THEME_SUCCESS);
}
int btn_y = gui_widget_client_height() - 32;
if (gui_button(90, 10, btn_y, 96, 24, "Apply")) {
// Push every preference to the live system, then confirm.
gui_desktop_set_grid(sd->show_grid);
gui_desktop_set_clock_seconds(sd->show_clock_seconds);
cpufreq_set_governor(settings_gov_from_choice(sd->governor_choice));
gui_dialog_show(GUI_DIALOG_INFO, "Settings",
"Preferences applied to the current desktop session.");
}
if (gui_button_colored(91, 114, btn_y, 96, 24, "Defaults", GUI_THEME_WARNING)) {
sd->show_grid = true;
sd->window_shadows = true;
sd->sound_enabled = true;
sd->show_clock_seconds = true;
sd->theme_choice = 0;
sd->cursor_speed = 5;
sd->brightness = 100;
sd->governor_choice = 1;
// Apply the restored defaults live as well.
gui_desktop_set_grid(true);
gui_desktop_set_clock_seconds(true);
cpufreq_set_governor(CPUFREQ_GOV_ONDEMAND);
}
gui_widget_end();
}
static void settings_event(gui_window_t* win, const gui_event_t* ev) {
if (ev->type == GUI_EVENT_CLOSE) {
if (win->user_data) { kfree(win->user_data); win->user_data = NULL; }
return;
}
gui_widget_feed_event(win, ev);
}
void gui_app_settings_launch(int x, int y, int w, int h) {
gui_window_t* win = gui_wm_create_window("Settings", x, y,
(w > 0) ? w : 380, (h > 0) ? h : 300);
if (!win) return;
settings_data_t* sd = (settings_data_t*)kzalloc(sizeof(settings_data_t));
if (!sd) return;
// Seed the controls from the live system so the app reflects real state.
sd->show_grid = gui_desktop_get_grid();
sd->window_shadows = true;
sd->sound_enabled = true;
sd->show_clock_seconds = gui_desktop_get_clock_seconds();
sd->cursor_speed = 5;
sd->brightness = 100;
sd->governor_choice = settings_choice_from_gov(cpufreq_get_stats().governor);
win->user_data = sd;
win->paint = settings_paint;
win->handle_event = settings_event;
}
// ===========================================================================
// 2. Task Manager
// ===========================================================================
#define TASKMGR_MAX_ROWS 24
typedef struct {
int selected;
int row_count;
char rows[TASKMGR_MAX_ROWS][40];
const char* row_ptrs[TASKMGR_MAX_ROWS];
pid_t pids[TASKMGR_MAX_ROWS];
uint64_t last_refresh;
} taskmgr_data_t;
static const char* task_state_name(task_state_t s) {
switch (s) {
case TASK_STATE_READY: return "READY";
case TASK_STATE_RUNNING: return "RUN ";
case TASK_STATE_BLOCKED: return "BLOCK";
case TASK_STATE_SLEEPING: return "SLEEP";
case TASK_STATE_ZOMBIE: return "ZOMBI";
default: return "DEAD ";
}
}
// Walk the low PID space; the kernel hands out small, dense pids.
static void taskmgr_refresh(taskmgr_data_t* td) {
td->row_count = 0;
for (pid_t pid = 0; pid < 64 && td->row_count < TASKMGR_MAX_ROWS; pid++) {
task_t* t = task_find_by_pid(pid);
if (!t) continue;
snprintf(td->rows[td->row_count], sizeof(td->rows[0]),
"%4d %-16s %s p%d", t->pid, t->name,
task_state_name(t->state), t->priority);
td->row_ptrs[td->row_count] = td->rows[td->row_count];
td->pids[td->row_count] = t->pid;
td->row_count++;
}
if (td->row_count == 0) {
strcpy(td->rows[0], " 0 kernel_idle RUN p0");
td->row_ptrs[0] = td->rows[0];
td->pids[0] = 0;
td->row_count = 1;
}
if (td->selected >= td->row_count) td->selected = td->row_count - 1;
td->last_refresh = pit_get_ticks();
}
static void taskmgr_paint(gui_window_t* win) {
taskmgr_data_t* td = (taskmgr_data_t*)win->user_data;
if (!td) return;
// Re-scan roughly twice a second rather than on every composited frame.
if (pit_get_ticks() - td->last_refresh > 50) taskmgr_refresh(td);
gui_widget_begin(win);
int w = gui_widget_client_width();
int h = gui_widget_client_height();
char buf[64];
gui_icon_draw(GUI_ICON_TASKS, win->x + 9, win->y + GUI_TITLEBAR_HEIGHT + 7);
gui_label_bold(30, 6, "Process Table", GUI_THEME_PRIMARY);
gui_label(10, 28, " PID NAME STATE PRIO", GUI_THEME_TEXT_DIM);
gui_listbox(1, 10, 40, w - 20, h - 108, td->row_ptrs, td->row_count, &td->selected);
int info_y = h - 62;
gui_separator(10, info_y - 6, w - 20);
uint64_t total = pmm_get_total_pages();
uint64_t used = pmm_get_used_pages();
int mem_pct = total ? (int)((used * 100) / total) : 0;
snprintf(buf, sizeof(buf), "Memory %llu / %llu MB",
(unsigned long long)((used * 4096) / (1024 * 1024)),
(unsigned long long)((total * 4096) / (1024 * 1024)));
gui_label(10, info_y, buf, GUI_THEME_TEXT_MUTED);
gui_progress_bar(160, info_y - 2, w - 176, 12, mem_pct,
mem_pct > 80 ? GUI_THEME_DANGER : GUI_THEME_SUCCESS);
snprintf(buf, sizeof(buf), "Heap %llu KB used, %d task(s) live",
(unsigned long long)(heap_get_used_bytes() / 1024), td->row_count);
gui_label(10, info_y + 16, buf, GUI_THEME_TEXT_DIM);
int btn_y = h - 30;
if (gui_button(2, 10, btn_y, 84, 24, "Refresh")) {
taskmgr_refresh(td);
}
if (gui_button_colored(3, 102, btn_y, 84, 24, "End Task", GUI_THEME_DANGER)) {
pid_t victim = (td->selected < td->row_count) ? td->pids[td->selected] : -1;
if (victim <= 0) {
gui_dialog_show(GUI_DIALOG_WARNING, "Task Manager",
"PID 0 is the idle task and cannot be terminated.");
} else {
gui_dialog_show(GUI_DIALOG_WARNING, "Task Manager",
"Terminating kernel tasks from the desktop is disabled "
"in this build.");
}
}
gui_widget_end();
}
void gui_app_taskmgr_launch(int x, int y, int w, int h) {
gui_window_t* win = gui_wm_create_window("Task Manager", x, y,
(w > 0) ? w : 360, (h > 0) ? h : 300);
if (!win) return;
taskmgr_data_t* td = (taskmgr_data_t*)kzalloc(sizeof(taskmgr_data_t));
if (!td) return;
taskmgr_refresh(td);
win->user_data = td;
win->paint = taskmgr_paint;
win->handle_event = app_forward_event;
}
// ===========================================================================
// 3. Kernel Log Viewer
// ===========================================================================
#define LOG_MAX_LINES 200
#define LOG_LINE_CHARS 72
typedef struct {
int scroll;
int total_lines;
bool wrap_long_lines;
char lines[LOG_MAX_LINES][LOG_LINE_CHARS + 1];
} logview_data_t;
// The dmesg ring holds raw bytes with embedded log-level markers; split it
// into printable rows and drop the control prefixes.
static void logview_reload(logview_data_t* lv) {
size_t size = 0;
const char* buf = dmesg_get_buffer(&size);
lv->total_lines = 0;
if (!buf || size == 0) {
strcpy(lv->lines[0], "(kernel ring buffer is empty)");
lv->total_lines = 1;
return;
}
size_t i = 0;
while (i < size && lv->total_lines < LOG_MAX_LINES) {
int col = 0;
char* out = lv->lines[lv->total_lines];
while (i < size && buf[i] != '\n') {
char c = buf[i++];
if (c == '\033') {
// Skip an ANSI CSI sequence entirely.
while (i < size && buf[i] != 'm' && buf[i] != '\n') i++;
if (i < size && buf[i] == 'm') i++;
continue;
}
if ((unsigned char)c < 32) continue; // Log-level markers and tabs
if (col < LOG_LINE_CHARS) out[col++] = c;
}
if (i < size) i++; // Consume the newline
out[col] = '\0';
if (col > 0) lv->total_lines++;
}
if (lv->total_lines == 0) {
strcpy(lv->lines[0], "(no printable log records)");
lv->total_lines = 1;
}
}
static void logview_paint(gui_window_t* win) {
logview_data_t* lv = (logview_data_t*)win->user_data;
if (!lv) return;
gui_widget_begin(win);
int w = gui_widget_client_width();
int h = gui_widget_client_height();
char buf[64];
gui_icon_draw(GUI_ICON_LOG, win->x + 9, win->y + GUI_TITLEBAR_HEIGHT + 7);
gui_label_bold(30, 6, "Kernel Ring Buffer", GUI_THEME_PRIMARY);
int view_y = 30;
int view_h = h - 66;
int row_h = 11;
int visible = view_h / row_h;
gui_gfx_fill_rect(win->x + 9, win->y + GUI_TITLEBAR_HEIGHT + view_y,
w - 32, view_h, 0xFF080B14);
gui_gfx_draw_rect(win->x + 9, win->y + GUI_TITLEBAR_HEIGHT + view_y,
w - 32, view_h, GUI_THEME_BORDER);
if (lv->scroll > lv->total_lines - visible) lv->scroll = lv->total_lines - visible;
if (lv->scroll < 0) lv->scroll = 0;
for (int i = 0; i < visible && lv->scroll + i < lv->total_lines; i++) {
const char* line = lv->lines[lv->scroll + i];
// Colour-code by the severity words the kernel prints.
uint32_t fg = GUI_THEME_TEXT_MUTED;
if (strstr(line, "PANIC") || strstr(line, "ERROR")) fg = GUI_THEME_DANGER;
else if (strstr(line, "WARN")) fg = GUI_THEME_WARNING;
else if (strstr(line, "OK") || strstr(line, "online") || strstr(line, "initialized"))
fg = GUI_THEME_SUCCESS;
gui_label(14, view_y + 3 + i * row_h, line, fg);
}
gui_scrollbar(1, w - 21, view_y, view_h, &lv->scroll, lv->total_lines, visible);
snprintf(buf, sizeof(buf), "%d lines, showing %d-%d",
lv->total_lines, lv->scroll + 1,
(lv->scroll + visible < lv->total_lines) ? lv->scroll + visible : lv->total_lines);
gui_label(10, h - 30, buf, GUI_THEME_TEXT_DIM);
if (gui_button(2, w - 190, h - 34, 84, 24, "Reload")) {
logview_reload(lv);
lv->scroll = 0;
}
if (gui_button(3, w - 100, h - 34, 84, 24, "End")) {
lv->scroll = lv->total_lines - visible;
if (lv->scroll < 0) lv->scroll = 0;
}
gui_widget_end();
}
void gui_app_logviewer_launch(int x, int y, int w, int h) {
gui_window_t* win = gui_wm_create_window("Kernel Log", x, y,
(w > 0) ? w : 620, (h > 0) ? h : 320);
if (!win) return;
logview_data_t* lv = (logview_data_t*)kzalloc(sizeof(logview_data_t));
if (!lv) return;
logview_reload(lv);
win->user_data = lv;
win->paint = logview_paint;
win->handle_event = app_forward_event;
}
// ===========================================================================
// 4. KTest Runner
// ===========================================================================
typedef struct {
int selected_suite;
bool has_run;
ktest_result_t result;
char suite_name[24];
} ktest_data_t;
static const char* const ktest_suite_names[] = {
"(all suites)", "rbtree", "kfifo", "hashtable",
"concurrency", "page_cache", "libcore"
};
#define KTEST_SUITE_COUNT 7
static void ktest_paint(gui_window_t* win) {
ktest_data_t* kd = (ktest_data_t*)win->user_data;
if (!kd) return;
gui_widget_begin(win);
int w = gui_widget_client_width();
int h = gui_widget_client_height();
char buf[64];
gui_icon_draw(GUI_ICON_FLASK, win->x + 9, win->y + GUI_TITLEBAR_HEIGHT + 7);
gui_label_bold(30, 6, "In-Kernel Test Harness", GUI_THEME_PRIMARY);
gui_label(10, 30, "Suite", GUI_THEME_TEXT_DIM);
gui_listbox(1, 10, 42, 150, 108, ktest_suite_names, KTEST_SUITE_COUNT,
&kd->selected_suite);
if (gui_button_colored(2, 10, 156, 150, 26, "Run Tests", GUI_THEME_SUCCESS)) {
// Suite 0 is the "all" pseudo-entry; the rest map 1:1 by name.
if (kd->selected_suite == 0) {
ktest_run_all(&kd->result);
strcpy(kd->suite_name, "all suites");
} else {
ktest_run_suite(ktest_suite_names[kd->selected_suite], &kd->result);
strncpy(kd->suite_name, ktest_suite_names[kd->selected_suite],
sizeof(kd->suite_name) - 1);
}
kd->has_run = true;
}
int rx = 174;
gui_panel(rx, 42, w - rx - 14, 140, "Results");
if (!kd->has_run) {
gui_label(rx + 12, 66, "No run yet.", GUI_THEME_TEXT_DIM);
gui_label(rx + 12, 82, "Pick a suite and press", GUI_THEME_TEXT_DIM);
gui_label(rx + 12, 94, "Run Tests. Full per-case", GUI_THEME_TEXT_DIM);
gui_label(rx + 12, 106, "output goes to the serial", GUI_THEME_TEXT_DIM);
gui_label(rx + 12, 118, "console and dmesg.", GUI_THEME_TEXT_DIM);
} else {
const ktest_result_t* r = &kd->result;
bool green = (r->tests_failed == 0);
snprintf(buf, sizeof(buf), "Suite: %s", kd->suite_name);
gui_label(rx + 12, 56, buf, GUI_THEME_TEXT_MAIN);
snprintf(buf, sizeof(buf), "%u tests, %u passed, %u failed",
r->tests_run, r->tests_passed, r->tests_failed);
gui_label(rx + 12, 72, buf, GUI_THEME_TEXT_MUTED);
snprintf(buf, sizeof(buf), "%u assertions, %u failures",
r->assertions, r->assertion_failures);
gui_label(rx + 12, 86, buf, GUI_THEME_TEXT_MUTED);
snprintf(buf, sizeof(buf), "Elapsed: %llu ms",
(unsigned long long)(r->elapsed_ticks * 10));
gui_label(rx + 12, 100, buf, GUI_THEME_TEXT_DIM);
int pct = r->tests_run ? (int)((r->tests_passed * 100) / r->tests_run) : 0;
gui_progress_bar(rx + 12, 118, w - rx - 40, 14, pct,
green ? GUI_THEME_SUCCESS : GUI_THEME_DANGER);
gui_badge(rx + 12, 140, green ? "ALL PASSED" : "FAILURES",
green ? GUI_THEME_SUCCESS : GUI_THEME_DANGER, GUI_COLOR_BLACK);
}
gui_separator(10, h - 42, w - 20);
gui_label(10, h - 34, "Suites cover rbtree, kfifo, hashtable, RCU,",
GUI_THEME_TEXT_DIM);
gui_label(10, h - 22, "futex, wait queues, page cache and libcore.",
GUI_THEME_TEXT_DIM);
gui_widget_end();
}
void gui_app_ktest_launch(int x, int y, int w, int h) {
gui_window_t* win = gui_wm_create_window("KTest Runner", x, y,
(w > 0) ? w : 460, (h > 0) ? h : 250);
if (!win) return;
ktest_data_t* kd = (ktest_data_t*)kzalloc(sizeof(ktest_data_t));
if (!kd) return;
win->user_data = kd;
win->paint = ktest_paint;
win->handle_event = app_forward_event;
}
// ===========================================================================
// 5. Clock & Calendar
// ===========================================================================
typedef struct {
bool show_seconds;
int view_month; // 1-12; 0 means "follow the RTC"
} clock_data_t;
static const char* const month_names[] = {
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
};
static int days_in_month(int month, int year) {
static const int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month < 1 || month > 12) return 30;
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)) return 29;
return days[month - 1];
}
// Zeller's congruence, returning 0 = Sunday.
static int day_of_week(int day, int month, int year) {
if (month < 3) { month += 12; year -= 1; }
int k = year % 100;
int j = year / 100;
int hh = (day + (13 * (month + 1)) / 5 + k + k / 4 + j / 4 + 5 * j) % 7;
return (hh + 6) % 7;
}
static void clock_paint(gui_window_t* win) {
clock_data_t* cd = (clock_data_t*)win->user_data;
if (!cd) return;
rtc_time_t now;
rtc_get_time(&now);
gui_widget_begin(win);
int w = gui_widget_client_width();
char buf[64];
int month = cd->view_month ? cd->view_month : (int)now.month;
int year = (int)now.year;
// Big digital readout
if (cd->show_seconds) {
snprintf(buf, sizeof(buf), "%02u:%02u:%02u", now.hour, now.minute, now.second);
} else {
snprintf(buf, sizeof(buf), "%02u:%02u", now.hour, now.minute);
}
int tw = (int)strlen(buf) * 8;
gui_gfx_draw_string_16_shadow(win->x + 1 + (w - tw) / 2,
win->y + GUI_TITLEBAR_HEIGHT + 9,
buf, GUI_THEME_PRIMARY, GUI_THEME_PRIMARY_DARK);
snprintf(buf, sizeof(buf), "%s %u, %d", month_names[month - 1], now.day, year);
gui_label_aligned(0, 32, w, buf, GUI_THEME_TEXT_MAIN, GUI_ALIGN_CENTER);
// Month grid
int gx = 12, gy = 50;
int cell_w = (w - 24) / 7;
int cell_h = 16;
const char* const dows[] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" };
for (int i = 0; i < 7; i++) {
gui_label(gx + i * cell_w + (cell_w - 16) / 2, gy, dows[i],
(i == 0 || i == 6) ? GUI_THEME_DANGER : GUI_THEME_TEXT_DIM);
}
gui_separator(gx, gy + 12, w - 24);
int first_dow = day_of_week(1, month, year);
int total_days = days_in_month(month, year);
for (int d = 1; d <= total_days; d++) {
int slot = first_dow + d - 1;
int col = slot % 7;
int row = slot / 7;
int cx = gx + col * cell_w;
int cy = gy + 18 + row * cell_h;
bool today = (d == (int)now.day && month == (int)now.month);
if (today) {
gui_gfx_fill_rect(win->x + 1 + cx, win->y + GUI_TITLEBAR_HEIGHT + 1 + cy - 2,
cell_w - 2, cell_h - 2, GUI_THEME_PRIMARY_DARK);
}
snprintf(buf, sizeof(buf), "%2d", d);
gui_label(cx + (cell_w - 16) / 2, cy, buf,
today ? GUI_COLOR_WHITE
: (col == 0 || col == 6) ? GUI_THEME_TEXT_DIM
: GUI_THEME_TEXT_MUTED);
}
int h = gui_widget_client_height();
if (gui_button(1, 12, h - 30, 30, 22, "<")) {
cd->view_month = (month == 1) ? 12 : month - 1;
}
if (gui_button(2, 46, h - 30, 30, 22, ">")) {
cd->view_month = (month == 12) ? 1 : month + 1;
}
if (gui_button(3, 80, h - 30, 62, 22, "Today")) {
cd->view_month = 0;
}
gui_checkbox(4, 152, h - 28, "Seconds", &cd->show_seconds);
gui_widget_end();
}
void gui_app_clock_launch(int x, int y, int w, int h) {
gui_window_t* win = gui_wm_create_window("Clock & Calendar", x, y,
(w > 0) ? w : 270, (h > 0) ? h : 250);
if (!win) return;
clock_data_t* cd = (clock_data_t*)kzalloc(sizeof(clock_data_t));
if (!cd) return;
cd->show_seconds = true;
win->user_data = cd;
win->paint = clock_paint;
win->handle_event = app_forward_event;
}
// ===========================================================================
// 6. Text Editor
// ===========================================================================
#define EDITOR_ROWS 14
#define EDITOR_ROW_CHARS 56
typedef struct {
char lines[EDITOR_ROWS][EDITOR_ROW_CHARS + 1];
int line_count;
int cursor_line;
char filename[48];
bool modified;
bool word_wrap;
} editor_data_t;
static void editor_insert_char(editor_data_t* ed, char c) {
if (ed->cursor_line >= EDITOR_ROWS) return;
char* line = ed->lines[ed->cursor_line];
size_t len = strlen(line);
if (c == '\n' || c == '\r') {
if (ed->line_count < EDITOR_ROWS) {
// Push the following lines down to make room for the new one.
for (int i = ed->line_count; i > ed->cursor_line + 1; i--) {
strcpy(ed->lines[i], ed->lines[i - 1]);
}
ed->line_count++;
ed->cursor_line++;
ed->lines[ed->cursor_line][0] = '\0';
ed->modified = true;
}
return;
}
if (c == '\b' || (uint8_t)c == 0x7F) {
if (len > 0) {
line[len - 1] = '\0';
ed->modified = true;
} else if (ed->cursor_line > 0) {
ed->cursor_line--;
}
return;
}
if (c >= 32 && c <= 126 && len < EDITOR_ROW_CHARS) {
line[len] = c;
line[len + 1] = '\0';
ed->modified = true;
}
}
static void editor_event(gui_window_t* win, const gui_event_t* ev) {
editor_data_t* ed = (editor_data_t*)win->user_data;
if (ed && ev->type == GUI_EVENT_KEY_DOWN) {
uint16_t key = ev->key;
if (key == KEY_UP) {
if (ed->cursor_line > 0) ed->cursor_line--;
return;
}
if (key == KEY_DOWN) {
if (ed->cursor_line < ed->line_count - 1) ed->cursor_line++;
return;
}
if (!(key & KEY_SPECIAL_FLAG)) {
editor_insert_char(ed, (char)(key & 0xFF));
return;
}
}
app_forward_event(win, ev);
}
static void editor_paint(gui_window_t* win) {
editor_data_t* ed = (editor_data_t*)win->user_data;
if (!ed) return;
gui_widget_begin(win);
int w = gui_widget_client_width();
int h = gui_widget_client_height();
char buf[80];
// Toolbar
gui_icon_draw(GUI_ICON_FILE, win->x + 9, win->y + GUI_TITLEBAR_HEIGHT + 6);
snprintf(buf, sizeof(buf), "%s%s", ed->filename, ed->modified ? " *" : "");
gui_label(30, 10, buf, ed->modified ? GUI_THEME_WARNING : GUI_THEME_TEXT_MAIN);
if (gui_button(1, w - 176, 6, 52, 20, "New")) {
memset(ed->lines, 0, sizeof(ed->lines));
ed->line_count = 1;
ed->cursor_line = 0;
ed->modified = false;
strcpy(ed->filename, "untitled.txt");
}
if (gui_button(2, w - 120, 6, 52, 20, "Save")) {
ed->modified = false;
gui_dialog_show(GUI_DIALOG_INFO, "Text Editor",
"This build keeps the buffer in memory only; VFS write-back "
"is not wired up yet.");
}
if (gui_button(3, w - 64, 6, 52, 20, "Wrap")) {
ed->word_wrap = !ed->word_wrap;
}
// Text area with a gutter
int area_y = 32;
int area_h = h - area_y - 26;
int row_h = 14;
gui_gfx_fill_rect(win->x + 9, win->y + GUI_TITLEBAR_HEIGHT + area_y,
w - 18, area_h, 0xFF080B14);
gui_gfx_draw_rect(win->x + 9, win->y + GUI_TITLEBAR_HEIGHT + area_y,
w - 18, area_h, GUI_THEME_BORDER);
gui_gfx_draw_line(win->x + 40, win->y + GUI_TITLEBAR_HEIGHT + area_y + 1,
win->x + 40, win->y + GUI_TITLEBAR_HEIGHT + area_y + area_h - 2,
GUI_THEME_BORDER);
int visible = (area_h - 6) / row_h;
for (int i = 0; i < visible && i < ed->line_count; i++) {
int ry = area_y + 4 + i * row_h;
bool is_cursor = (i == ed->cursor_line);
if (is_cursor) {
gui_gfx_fill_rect(win->x + 42, win->y + GUI_TITLEBAR_HEIGHT + ry - 2,
w - 54, row_h, 0xFF11182B);
}
snprintf(buf, sizeof(buf), "%3d", i + 1);
gui_label(14, ry, buf, is_cursor ? GUI_THEME_PRIMARY : GUI_THEME_TEXT_DIM);
gui_label(38, ry, ed->lines[i], GUI_THEME_TEXT_MAIN);
if (is_cursor) {
int cx = 38 + (int)strlen(ed->lines[i]) * 8;
gui_gfx_fill_rect(win->x + 1 + cx, win->y + GUI_TITLEBAR_HEIGHT + 1 + ry - 1,
7, 10, GUI_THEME_SUCCESS);
}
}
snprintf(buf, sizeof(buf), "Ln %d/%d Col %d %s",
ed->cursor_line + 1, ed->line_count,
(int)strlen(ed->lines[ed->cursor_line]) + 1,
ed->word_wrap ? "WRAP" : "NOWRAP");
gui_label(10, h - 16, buf, GUI_THEME_TEXT_DIM);
gui_widget_end();
}
void gui_app_editor_launch(int x, int y, int w, int h) {
gui_window_t* win = gui_wm_create_window("Text Editor", x, y,
(w > 0) ? w : 500, (h > 0) ? h : 320);
if (!win) return;
editor_data_t* ed = (editor_data_t*)kzalloc(sizeof(editor_data_t));
if (!ed) return;
strcpy(ed->filename, "untitled.txt");
strcpy(ed->lines[0], "# SUB-OS Text Editor");
strcpy(ed->lines[1], "");
strcpy(ed->lines[2], "Type to edit. Enter adds a line,");
strcpy(ed->lines[3], "Up/Down move between lines.");
ed->line_count = 4;
ed->cursor_line = 3;
win->user_data = ed;
win->paint = editor_paint;
win->handle_event = editor_event;
}
+131
View File
@@ -0,0 +1,131 @@
// High-Resolution Smooth Mouse Cursor for SUB-OS GUI
#include <gui/gui_cursor.h>
#include <gui/gui_gfx.h>
#include <gui/gui_theme.h>
#define CURSOR_W 16
#define CURSOR_H 24
static int g_cur_x = 400;
static int g_cur_y = 300;
// 16x24 Modern Pointer Cursor (0: Transparent, 1: Black Outline, 2: White Fill, 3: Shadow)
static const uint8_t cursor_bitmap[24][16] = {
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0},
{1,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0},
{1,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0},
{1,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0},
{1,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0},
{1,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0},
{1,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0},
{1,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0},
{1,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0},
{1,2,2,2,2,2,2,1,1,1,1,1,1,0,0,0},
{1,2,2,2,1,2,2,1,0,0,0,0,0,0,0,0},
{1,2,2,1,0,1,2,2,1,0,0,0,0,0,0,0},
{1,2,1,0,0,1,2,2,1,0,0,0,0,0,0,0},
{1,1,0,0,0,0,1,2,2,1,0,0,0,0,0,0},
{1,0,0,0,0,0,1,2,2,1,0,0,0,0,0,0},
{0,0,0,0,0,0,0,1,2,2,1,0,0,0,0,0},
{0,0,0,0,0,0,0,1,2,2,1,0,0,0,0,0},
{0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
void gui_cursor_init(void) {
g_cur_x = gui_gfx_get_width() / 2;
g_cur_y = gui_gfx_get_height() / 2;
}
void gui_cursor_set_pos(int x, int y) {
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x >= gui_gfx_get_width()) x = gui_gfx_get_width() - 1;
if (y >= gui_gfx_get_height()) y = gui_gfx_get_height() - 1;
g_cur_x = x;
g_cur_y = y;
}
int gui_cursor_get_x(void) { return g_cur_x; }
int gui_cursor_get_y(void) { return g_cur_y; }
void gui_cursor_draw(void) {
// 1. Draw Subtle Cursor Drop Shadow
for (int j = 0; j < CURSOR_H; j++) {
int py = g_cur_y + j + 2;
for (int i = 0; i < CURSOR_W; i++) {
int px = g_cur_x + i + 2;
uint8_t pixel = cursor_bitmap[j][i];
if (pixel != 0) {
gui_gfx_draw_pixel_blend(px, py, GUI_COLOR_BLACK, 100);
}
}
}
// 2. Draw Cursor Body
for (int j = 0; j < CURSOR_H; j++) {
int py = g_cur_y + j;
for (int i = 0; i < CURSOR_W; i++) {
int px = g_cur_x + i;
uint8_t pixel = cursor_bitmap[j][i];
if (pixel == 1) {
gui_gfx_draw_pixel(px, py, 0xFF0F172A); // Dark Slate Outline
} else if (pixel == 2) {
gui_gfx_draw_pixel(px, py, GUI_COLOR_WHITE); // Pure White Interior
}
}
}
}
// ---------------------------------------------------------------------------
// Save-under overlay: lets the compositor move the cursor without repainting
// the whole scene. The block under the cursor (body + drop shadow) is stashed
// before drawing and restored on the next frame.
// ---------------------------------------------------------------------------
#define CUR_SAVE_W (CURSOR_W + 3)
#define CUR_SAVE_H (CURSOR_H + 3)
static uint32_t g_save[CUR_SAVE_H * CUR_SAVE_W];
static int g_save_x = -1, g_save_y = -1, g_save_w = 0, g_save_h = 0;
// Put back whatever was under the cursor last frame (erasing it).
void gui_cursor_restore_under(void) {
if (g_save_x < 0) return;
uint32_t* bb = gui_gfx_get_backbuffer();
if (!bb) { g_save_x = -1; return; }
int W = gui_gfx_get_width();
for (int j = 0; j < g_save_h; j++) {
uint32_t* dst = &bb[(g_save_y + j) * W + g_save_x];
const uint32_t* src = &g_save[j * g_save_w];
for (int i = 0; i < g_save_w; i++) dst[i] = src[i];
}
g_save_x = -1;
}
// Stash the block under the current cursor position, then draw the cursor.
void gui_cursor_composite(void) {
uint32_t* bb = gui_gfx_get_backbuffer();
if (!bb) return;
int W = gui_gfx_get_width();
int H = gui_gfx_get_height();
int x = g_cur_x, y = g_cur_y;
int w = CUR_SAVE_W, h = CUR_SAVE_H;
if (x + w > W) w = W - x;
if (y + h > H) h = H - y;
if (w <= 0 || h <= 0) { g_save_x = -1; gui_cursor_draw(); return; }
for (int j = 0; j < h; j++) {
const uint32_t* srow = &bb[(y + j) * W + x];
uint32_t* d = &g_save[j * w];
for (int i = 0; i < w; i++) d[i] = srow[i];
}
g_save_x = x; g_save_y = y; g_save_w = w; g_save_h = h;
gui_cursor_draw();
}
+902
View File
@@ -0,0 +1,902 @@
// SUB-OS Desktop Shell: wallpaper, icon grid, taskbar, menus and compositor loop
//
// The shell owns the frame loop. Every iteration it samples the input devices,
// routes the result through the modal dialog layer, then the desktop chrome,
// then the window manager, and finally composites the scene back to front:
// wallpaper, icons, windows, taskbar, menus, dialog, cursor.
#include <gui/gui_desktop.h>
#include <gui/gui_gfx.h>
#include <gui/gui_cursor.h>
#include <gui/gui_wm.h>
#include <gui/gui_apps.h>
#include <gui/gui_terminal.h>
#include <gui/gui_apps_ext.h>
#include <gui/gui_widgets.h>
#include <gui/gui_icons.h>
#include <gui/gui_dialog.h>
#include <gui/gui_theme.h>
#include <drivers/fb.h>
#include <drivers/bochs.h>
#include <drivers/fbcon.h>
#include <drivers/tty.h>
#include <drivers/mouse.h>
#include <drivers/keyboard.h>
#include <drivers/rtc.h>
#include <drivers/cpufreq.h>
#include <kernel/ktime.h>
#include <kernel/sched.h>
#include <mm/pmm.h>
#include <mm/kmalloc.h>
#include <arch/arch.h>
#include <lib/string.h>
#include <lib/printf.h>
#include <kernel/printk.h>
#define START_MENU_W 232
#define START_ITEM_H 26
#define CONTEXT_MENU_W 150
#define CONTEXT_ITEM_H 22
typedef void (*launch_fn_t)(int x, int y, int w, int h);
typedef struct {
const char* label;
gui_icon_id_t icon;
launch_fn_t launch;
int x, y, w, h;
} desktop_app_t;
// Launchers used by both the icon grid and the start menu.
static const desktop_app_t g_apps[] = {
{ "Terminal", GUI_ICON_TERMINAL, gui_app_terminal_launch, 30, 40, 560, 340 },
{ "Files", GUI_ICON_FOLDER, gui_app_fileman_launch, 80, 70, 380, 240 },
{ "Monitor", GUI_ICON_MONITOR, gui_app_sysmon_launch, 420, 40, 340, 240 },
{ "Tasks", GUI_ICON_TASKS, gui_app_taskmgr_launch, 180, 60, 380, 300 },
{ "Kernel Log", GUI_ICON_LOG, gui_app_logviewer_launch, 60, 90, 640, 330 },
{ "KTest", GUI_ICON_FLASK, gui_app_ktest_launch, 140, 80, 470, 250 },
{ "Editor", GUI_ICON_FILE, gui_app_editor_launch, 120, 50, 500, 330 },
{ "Calculator", GUI_ICON_CALC, gui_app_calc_launch, 260, 100, 220, 240 },
{ "Paint", GUI_ICON_PAINT, gui_app_paint_launch, 200, 60, 360, 260 },
{ "Life", GUI_ICON_FLASK, gui_app_life_launch, 240, 70, 480, 380 },
{ "Rust Lab", GUI_ICON_INFO, gui_app_rustlab_launch, 300, 80, 420, 300 },
{ "Objects", GUI_ICON_TASKS, gui_app_objbrowse_launch,280, 70, 460, 340 },
{ "RegEdit", GUI_ICON_SETTINGS, gui_app_regedit_launch, 300, 90, 480, 360 },
{ "Clock", GUI_ICON_CLOCK, gui_app_clock_launch, 460, 120, 280, 260 },
{ "Settings", GUI_ICON_SETTINGS, gui_app_settings_launch, 160, 60, 400, 320 },
{ "Analytics", GUI_ICON_MONITOR, gui_app_analytics_launch,320, 70, 560, 400 },
{ "Sorry", GUI_ICON_HEART, gui_app_sorry_launch, 260, 60, 660, 470 },
{ "Network", GUI_ICON_NETWORK, gui_app_netmon_launch, 300, 80, 560, 400 },
{ "Web", GUI_ICON_GLOBE, gui_app_web_launch, 280, 70, 600, 420 },
{ "About", GUI_ICON_INFO, gui_app_about_launch, 240, 140, 330, 170 },
};
#define APP_COUNT ((int)(sizeof(g_apps) / sizeof(g_apps[0])))
// Which of the above appear on the desktop itself.
// Indices into g_apps[]: Terminal, Files, Monitor, Kernel Log, KTest, Life,
// Rust Lab, Settings, Analytics, Sorry. (Objects/RegEdit live in the start
// menu.) Network sits alongside them.
static const int g_desktop_icons[] = { 0, 1, 2, 4, 5, 9, 10, 14, 15, 16, 17, 18 };
#define DESKTOP_ICON_COUNT ((int)(sizeof(g_desktop_icons) / sizeof(g_desktop_icons[0])))
static bool g_start_menu_open = false;
static bool g_context_menu_open = false;
static int g_context_x = 0, g_context_y = 0;
static bool g_desktop_running = false;
static bool g_prev_left = false, g_prev_right = false;
static bool g_show_grid = true;
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;
static uint64_t g_last_scene_ms = 0; // last full scene recomposite (for heartbeat)
static int g_prev_mx = -1, g_prev_my = -1;
static bool g_anim_request = false; // an app asked for the next frame back
// Target frame budget: 60 FPS == 16666 us. The compositor only repaints the
// full scene when it is dirty; between times it just moves the cursor overlay,
// so idle and pointer-only frames comfortably hit the cap.
#define FRAME_BUDGET_US 16666
#define SCENE_HEARTBEAT_MS 100 // ~10 Hz refresh for live content (clock, graphs)
/*
* Cached wallpaper.
*
* The background is a full-screen vertical gradient with an optional blended
* grid and three strings on top. None of it changes between frames, but it was
* being recomputed pixel by pixel on every pass of the compositor loop. It is
* drawn once into this buffer instead and blitted back each frame; the cache
* is dropped whenever the resolution or the grid setting changes.
*/
/* Rolling count of pixels actually written to the framebuffer, so the cost of
* the compositor is visible on the console without a profiler attached. */
static uint64_t g_pushed_pixels = 0;
static uint32_t* g_wallpaper = NULL;
static int g_wallpaper_w = 0;
static int g_wallpaper_h = 0;
static bool g_wallpaper_grid = false;
static uint8_t g_cpu_history[48];
// The launcher geometry below is authored against an 800x600 work area.
#define DESIGN_W 800
#define DESIGN_H 568
static void launch_app(int index) {
if (index < 0 || index >= APP_COUNT) return;
const desktop_app_t* a = &g_apps[index];
if (!a->launch) return;
int sw = gui_gfx_get_width();
int wh = gui_desktop_workarea_height();
// Scale the design geometry to the live resolution, but never shrink below
// it: a window sized for 800x600 is already at its comfortable minimum.
int x = (a->x * sw) / DESIGN_W;
int y = (a->y * wh) / DESIGN_H;
int w = (a->w * sw) / DESIGN_W;
int h = (a->h * wh) / DESIGN_H;
if (w < a->w) w = a->w;
if (h < a->h) h = a->h;
// Leave the desktop breathing room: a window scaled straight from the
// design size swallows most of a 1280x720 screen and buries the wallpaper.
int max_w = (sw * 62) / 100;
int max_h = (wh * 72) / 100;
if (w > max_w) w = max_w;
if (h > max_h) h = max_h;
// Keep clear of the icon column so launcher labels stay readable.
int icon_strip = GUI_DESKTOP_ICON_W + 28;
if (x < icon_strip) x = icon_strip;
if (x + w > sw - 8) x = sw - 8 - w;
if (y + h > wh - 8) y = wh - 8 - h;
if (y < 8) y = 8;
a->launch(x, y, w, h);
}
int gui_desktop_workarea_height(void) {
return gui_gfx_get_height() - GUI_TASKBAR_HEIGHT;
}
void gui_desktop_init(void) {
const fb_info_t* fb = fb_get_info();
uint32_t* fb_addr = fb ? fb->address : NULL;
int w = (fb && fb->width > 0) ? fb->width : GUI_DEFAULT_WIDTH;
int h = (fb && fb->height > 0) ? fb->height : GUI_DEFAULT_HEIGHT;
gui_gfx_init(fb_addr, w, h);
/* The console owned the framebuffer until now, so nothing on screen
* matches the compositor's idea of it; drop both caches. */
gui_gfx_invalidate();
gui_desktop_invalidate_background();
gui_icons_init();
gui_cursor_init();
gui_dialog_init();
gui_wm_init();
gui_wm_set_workarea(w, h - GUI_TASKBAR_HEIGHT);
mouse_set_bounds(w, h);
g_start_menu_open = false;
g_context_menu_open = false;
g_selected_icon = -1;
// Seed the taskbar CPU trace with a plausible idle curve.
for (int i = 0; i < 48; i++) {
g_cpu_history[i] = (uint8_t)(12 + ((i * 7) % 23));
}
}
// ===========================================================================
// Wallpaper and desktop icons
// ===========================================================================
/* 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) {
// 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) {
// 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 += 48) {
gui_gfx_fill_rect_blend(x, 0, 1, wh, 0xFF3B4E80, 22);
}
}
// 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) {
if (g_wallpaper) {
kfree(g_wallpaper);
g_wallpaper = NULL;
}
g_wallpaper_w = 0;
g_wallpaper_h = 0;
}
void gui_desktop_render_background(void) {
int sw = gui_gfx_get_width();
int wh = gui_desktop_workarea_height();
uint32_t* back = gui_gfx_get_backbuffer();
if (sw <= 0 || wh <= 0 || !back) return;
bool stale = (!g_wallpaper || g_wallpaper_w != sw || g_wallpaper_h != wh ||
g_wallpaper_grid != g_show_grid);
if (stale) {
if (g_wallpaper) {
kfree(g_wallpaper);
g_wallpaper = NULL;
}
/* Paint it once through the normal primitives, then keep a copy. The
* back buffer is the scratch space, which is safe because the caller
* redraws everything above the wallpaper straight afterwards. */
desktop_paint_background(sw, wh);
g_wallpaper = (uint32_t*)kmalloc((size_t)sw * (size_t)wh * sizeof(uint32_t));
if (g_wallpaper) {
for (int y = 0; y < wh; y++) {
memcpy(g_wallpaper + (size_t)y * sw,
back + (size_t)y * sw,
(size_t)sw * sizeof(uint32_t));
}
g_wallpaper_w = sw;
g_wallpaper_h = wh;
g_wallpaper_grid = g_show_grid;
}
return; /* the back buffer already holds the freshly painted copy */
}
for (int y = 0; y < wh; y++) {
memcpy(back + (size_t)y * sw,
g_wallpaper + (size_t)y * sw,
(size_t)sw * sizeof(uint32_t));
}
}
void gui_desktop_render_icons(void) {
for (int i = 0; i < DESKTOP_ICON_COUNT; i++) {
const desktop_app_t* a = &g_apps[g_desktop_icons[i]];
int ix = 16;
int iy = 16 + i * GUI_DESKTOP_ICON_H;
bool selected = (g_selected_icon == i);
// A translucent tile behind every icon: several glyphs are dark by
// design and would otherwise vanish into the dark wallpaper.
if (selected) {
gui_gfx_fill_rect_blend(ix - 4, iy - 4, GUI_DESKTOP_ICON_W,
GUI_DESKTOP_ICON_H - 4, GUI_THEME_PRIMARY, 70);
gui_gfx_draw_rect(ix - 4, iy - 4, GUI_DESKTOP_ICON_W,
GUI_DESKTOP_ICON_H - 4, GUI_THEME_PRIMARY);
} else {
gui_gfx_fill_rect_blend(ix + 10, iy - 4, 40, 40, GUI_THEME_BG_ELEVATED, 150);
gui_gfx_draw_rect(ix + 10, iy - 4, 40, 40, 0xFF3D4C63);
}
gui_icon_draw_scaled(a->icon, ix + 14, iy, 2);
int tw = (int)strlen(a->label) * 8;
int tx = ix + 30 - tw / 2;
if (tx < 0) tx = 0;
gui_gfx_draw_string_shadow(tx, iy + 40, a->label,
GUI_THEME_TEXT_MAIN, GUI_COLOR_BLACK);
}
}
// Returns the icon index under the point, or -1.
static int icon_hit_test(int mx, int my) {
for (int i = 0; i < DESKTOP_ICON_COUNT; i++) {
int ix = 12;
int iy = 12 + i * GUI_DESKTOP_ICON_H;
if (mx >= ix && mx < ix + GUI_DESKTOP_ICON_W &&
my >= iy && my < iy + GUI_DESKTOP_ICON_H - 4) {
return i;
}
}
return -1;
}
// ===========================================================================
// Taskbar
// ===========================================================================
void gui_desktop_render_taskbar(void) {
int sw = gui_gfx_get_width();
int ty = gui_desktop_workarea_height();
gui_gfx_draw_gradient_v(0, ty, sw, GUI_TASKBAR_HEIGHT, 0xFF111827, GUI_THEME_TASKBAR_BG);
gui_gfx_draw_line(0, ty, sw - 1, ty, GUI_THEME_BORDER);
// Start button
uint32_t start_bg = g_start_menu_open ? GUI_THEME_PRIMARY_DARK : GUI_THEME_PRIMARY;
gui_gfx_fill_rect(6, ty + 4, 92, 24, start_bg);
gui_gfx_draw_rect(6, ty + 4, 92, 24, GUI_THEME_PRIMARY_DARK);
gui_icon_draw(GUI_ICON_TERMINAL, 11, ty + 8);
gui_gfx_draw_string_16_shadow(32, ty + 8, "SUB-OS", GUI_COLOR_WHITE, GUI_COLOR_BLACK);
// One button per window, in z-order
int tab_x = 106;
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;
uint32_t tab_bg = win->active && !win->minimized ? GUI_THEME_BG_ELEVATED
: GUI_THEME_BG_SURFACE;
gui_gfx_fill_rect(tab_x, ty + 4, 100, 24, tab_bg);
gui_gfx_draw_rect(tab_x, ty + 4, 100, 24,
win->active && !win->minimized ? GUI_THEME_PRIMARY : GUI_THEME_BORDER);
// Active window gets an accent underline; minimized ones are dimmed.
if (win->active && !win->minimized) {
gui_gfx_fill_rect(tab_x + 1, ty + 25, 98, 2, GUI_THEME_PRIMARY);
}
char short_title[12];
strncpy(short_title, win->title, 11);
short_title[11] = '\0';
gui_gfx_draw_string(tab_x + 8, ty + 12, short_title,
win->minimized ? GUI_THEME_TEXT_DIM
: (win->active ? GUI_THEME_TEXT_MAIN
: GUI_THEME_TEXT_MUTED));
tab_x += 106;
}
// System tray: CPU trace, memory badge, clock
for (int i = 0; i < 47; i++) {
int x0 = tray_x + i;
int y0 = ty + 26 - (g_cpu_history[i] * 18) / 100;
int y1 = ty + 26 - (g_cpu_history[i + 1] * 18) / 100;
gui_gfx_draw_line(x0, y0, x0 + 1, y1, GUI_THEME_SUCCESS);
}
uint64_t total = pmm_get_total_pages();
uint64_t used = pmm_get_used_pages();
char buf[24];
snprintf(buf, sizeof(buf), "%llu MB",
(unsigned long long)((used * 4096) / (1024 * 1024)));
gui_gfx_fill_rect(sw - 145, ty + 6, 52, 20, GUI_THEME_SUCCESS);
gui_gfx_draw_rect(sw - 145, ty + 6, 52, 20, 0xFF059669);
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) {
snprintf(buf, sizeof(buf), "%02u:%02u:%02u", now.hour, now.minute, now.second);
} else {
snprintf(buf, sizeof(buf), "%02u:%02u", now.hour, now.minute);
}
gui_gfx_draw_string_16_shadow(sw - 84, ty + 8, buf, GUI_THEME_PRIMARY, GUI_COLOR_BLACK);
}
void gui_desktop_set_grid(bool on) { g_show_grid = on; }
bool gui_desktop_get_grid(void) { return g_show_grid; }
void gui_desktop_set_clock_seconds(bool on) { g_clock_seconds = on; }
bool gui_desktop_get_clock_seconds(void) { return g_clock_seconds; }
// ===========================================================================
// Start menu
// ===========================================================================
static int start_menu_height(void) {
// Header + one row per app + separator + power row.
return 32 + APP_COUNT * START_ITEM_H + 10 + START_ITEM_H + 8;
}
static void start_menu_origin(int* mx, int* my) {
*mx = 6;
*my = gui_desktop_workarea_height() - start_menu_height() - 6;
if (*my < 4) *my = 4;
}
void gui_desktop_render_start_menu(void) {
if (!g_start_menu_open) return;
int mx, my;
start_menu_origin(&mx, &my);
int mh = start_menu_height();
gui_gfx_draw_shadow(mx, my, START_MENU_W, mh, 10);
gui_gfx_fill_rect(mx, my, START_MENU_W, mh, GUI_THEME_BG_SURFACE);
gui_gfx_draw_rect(mx, my, START_MENU_W, mh, GUI_THEME_PRIMARY);
gui_gfx_draw_gradient_v(mx + 1, my + 1, START_MENU_W - 2, 30,
GUI_THEME_PRIMARY, GUI_THEME_PRIMARY_DARK);
gui_gfx_draw_string_16_shadow(mx + 12, my + 7, "Applications",
GUI_COLOR_WHITE, GUI_COLOR_BLACK);
for (int i = 0; i < APP_COUNT; i++) {
int iy = my + 32 + i * START_ITEM_H;
bool hot = (g_hover_menu_item == i);
if (hot) {
gui_gfx_fill_rect(mx + 2, iy, START_MENU_W - 4, START_ITEM_H,
GUI_THEME_BG_ELEVATED);
gui_gfx_fill_rect(mx + 2, iy, 3, START_ITEM_H, GUI_THEME_PRIMARY);
}
gui_icon_draw(g_apps[i].icon, mx + 12, iy + (START_ITEM_H - 16) / 2);
gui_gfx_draw_string(mx + 36, iy + (START_ITEM_H - 8) / 2, g_apps[i].label,
hot ? GUI_THEME_TEXT_MAIN : GUI_THEME_TEXT_MUTED);
}
int sep_y = my + 32 + APP_COUNT * START_ITEM_H + 4;
gui_gfx_draw_line(mx + 8, sep_y, mx + START_MENU_W - 9, sep_y, GUI_THEME_BORDER);
int power_y = sep_y + 6;
bool power_hot = (g_hover_menu_item == APP_COUNT);
if (power_hot) {
gui_gfx_fill_rect(mx + 2, power_y, START_MENU_W - 4, START_ITEM_H, 0xFF3B1418);
gui_gfx_fill_rect(mx + 2, power_y, 3, START_ITEM_H, GUI_THEME_DANGER);
}
gui_icon_draw(GUI_ICON_POWER, mx + 12, power_y + (START_ITEM_H - 16) / 2);
gui_gfx_draw_string(mx + 36, power_y + (START_ITEM_H - 8) / 2,
"Exit to Kernel TTY", GUI_THEME_DANGER);
}
// ===========================================================================
// Desktop context menu
// ===========================================================================
static const char* const g_context_items[] = {
"Tile Windows", "Cascade Windows", "Toggle Grid", "New Terminal", "About SUB-OS"
};
#define CONTEXT_ITEM_COUNT 5
void gui_desktop_open_context_menu(int mx, int my) {
g_context_menu_open = true;
g_start_menu_open = false;
int menu_h = CONTEXT_ITEM_COUNT * CONTEXT_ITEM_H + 8;
g_context_x = mx;
g_context_y = my;
// Flip the menu back on screen when opened near an edge.
if (g_context_x + CONTEXT_MENU_W > gui_gfx_get_width()) {
g_context_x = gui_gfx_get_width() - CONTEXT_MENU_W - 2;
}
if (g_context_y + menu_h > gui_desktop_workarea_height()) {
g_context_y = gui_desktop_workarea_height() - menu_h - 2;
}
}
void gui_desktop_render_context_menu(void) {
if (!g_context_menu_open) return;
int mh = CONTEXT_ITEM_COUNT * CONTEXT_ITEM_H + 8;
gui_gfx_draw_shadow(g_context_x, g_context_y, CONTEXT_MENU_W, mh, 6);
gui_gfx_fill_rect(g_context_x, g_context_y, CONTEXT_MENU_W, mh, GUI_THEME_BG_SURFACE);
gui_gfx_draw_rect(g_context_x, g_context_y, CONTEXT_MENU_W, mh, GUI_THEME_ACCENT);
for (int i = 0; i < CONTEXT_ITEM_COUNT; i++) {
int iy = g_context_y + 4 + i * CONTEXT_ITEM_H;
bool hot = (g_hover_menu_item == 100 + i);
if (hot) {
gui_gfx_fill_rect(g_context_x + 2, iy, CONTEXT_MENU_W - 4,
CONTEXT_ITEM_H, GUI_THEME_BG_ELEVATED);
}
gui_gfx_draw_string(g_context_x + 12, iy + (CONTEXT_ITEM_H - 8) / 2,
g_context_items[i],
hot ? GUI_THEME_TEXT_MAIN : GUI_THEME_TEXT_MUTED);
}
}
void gui_desktop_toggle_start_menu(void) {
g_start_menu_open = !g_start_menu_open;
if (g_start_menu_open) g_context_menu_open = false;
}
bool gui_desktop_is_start_menu_open(void) { return g_start_menu_open; }
void gui_desktop_close_menus(void) {
g_start_menu_open = false;
g_context_menu_open = false;
}
void gui_desktop_request_animation_frame(void) {
g_anim_request = true;
}
void gui_desktop_request_exit(void) { g_desktop_running = false; }
static void exit_confirm_cb(gui_dialog_result_t result, void* ctx) {
(void)ctx;
if (result == GUI_DIALOG_RESULT_OK) g_desktop_running = false;
}
// ===========================================================================
// Input routing
// ===========================================================================
// Track which menu row the pointer is over so rendering can highlight it.
static void update_menu_hover(int mx, int my) {
g_hover_menu_item = -1;
if (g_start_menu_open) {
int smx, smy;
start_menu_origin(&smx, &smy);
if (mx >= smx && mx < smx + START_MENU_W) {
int rel = my - (smy + 32);
if (rel >= 0) {
int idx = rel / START_ITEM_H;
if (idx >= 0 && idx < APP_COUNT) {
g_hover_menu_item = idx;
} else {
int power_y = smy + 32 + APP_COUNT * START_ITEM_H + 10;
if (my >= power_y && my < power_y + START_ITEM_H) {
g_hover_menu_item = APP_COUNT;
}
}
}
}
return;
}
if (g_context_menu_open) {
if (mx >= g_context_x && mx < g_context_x + CONTEXT_MENU_W) {
int rel = my - (g_context_y + 4);
int idx = rel / CONTEXT_ITEM_H;
if (rel >= 0 && idx < CONTEXT_ITEM_COUNT) g_hover_menu_item = 100 + idx;
}
}
}
static bool handle_start_menu_click(int mx, int my) {
int smx, smy;
start_menu_origin(&smx, &smy);
int mh = start_menu_height();
if (mx < smx || mx >= smx + START_MENU_W || my < smy || my >= smy + mh) {
g_start_menu_open = false;
return true; // The dismissing click is consumed, not passed through.
}
int rel = my - (smy + 32);
int idx = rel / START_ITEM_H;
if (rel >= 0 && idx >= 0 && idx < APP_COUNT) {
g_start_menu_open = false;
launch_app(idx);
return true;
}
int power_y = smy + 32 + APP_COUNT * START_ITEM_H + 10;
if (my >= power_y && my < power_y + START_ITEM_H) {
g_start_menu_open = false;
gui_dialog_confirm("Exit Desktop",
"Leave the graphical session and return to the kernel TTY?",
exit_confirm_cb, NULL);
return true;
}
return true;
}
static bool handle_context_menu_click(int mx, int my) {
int mh = CONTEXT_ITEM_COUNT * CONTEXT_ITEM_H + 8;
if (mx < g_context_x || mx >= g_context_x + CONTEXT_MENU_W ||
my < g_context_y || my >= g_context_y + mh) {
g_context_menu_open = false;
return true;
}
int rel = my - (g_context_y + 4);
g_context_menu_open = false;
if (rel < 0) return true;
int idx = rel / CONTEXT_ITEM_H;
switch (idx) {
case 0: gui_wm_tile_windows(); break;
case 1: gui_wm_cascade_windows(); break;
case 2: g_show_grid = !g_show_grid; break;
case 3: launch_app(0); break;
case 4: launch_app(APP_COUNT - 1); break;
default: break;
}
return true;
}
static bool handle_taskbar_click(int mx, int my) {
int ty = gui_desktop_workarea_height();
if (mx >= 6 && mx < 98 && my >= ty + 4 && my < ty + 28) {
gui_desktop_toggle_start_menu();
return true;
}
int tab_x = 106;
int tray_x = gui_gfx_get_width() - 200;
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;
if (mx >= tab_x && mx < tab_x + 100 && my >= ty + 4 && my < ty + 28) {
// Clicking the focused window's button minimizes it; otherwise
// the window is restored and raised.
if (win->active && !win->minimized) {
gui_wm_minimize_window(win->id);
} else {
gui_wm_restore_window(win->id);
gui_wm_focus_window(win->id);
}
return true;
}
tab_x += 106;
}
return true; // Bare taskbar clicks are absorbed
}
bool gui_desktop_handle_click(int mx, int my) {
if (g_start_menu_open) return handle_start_menu_click(mx, my);
if (g_context_menu_open) return handle_context_menu_click(mx, my);
if (my >= gui_desktop_workarea_height()) return handle_taskbar_click(mx, my);
// Icons only claim the click when no window covers that spot.
for (int i = gui_wm_get_window_count() - 1; i >= 0; i--) {
gui_window_t* win = gui_wm_get_window_at_index(i);
if (!win || !win->visible || win->minimized) continue;
if (mx >= win->x && mx < win->x + win->width &&
my >= win->y && my < win->y + win->height) {
return false;
}
}
int icon = icon_hit_test(mx, my);
if (icon >= 0) {
if (g_selected_icon == icon) {
// Second click on an already-selected icon launches it.
launch_app(g_desktop_icons[icon]);
g_selected_icon = -1;
} else {
g_selected_icon = icon;
}
return true;
}
g_selected_icon = -1;
return false;
}
// ===========================================================================
// Frame loop
// ===========================================================================
static void sample_cpu_history(void) {
// Shift in a fresh sample derived from the current P-state.
for (int i = 0; i < 47; i++) g_cpu_history[i] = g_cpu_history[i + 1];
cpufreq_stats_t cf = cpufreq_get_stats();
uint32_t span = (cf.max_khz > cf.min_khz) ? (cf.max_khz - cf.min_khz) : 1;
uint32_t rel = (cf.current_khz > cf.min_khz) ? (cf.current_khz - cf.min_khz) : 0;
uint32_t pct = (rel * 100) / span;
if (pct > 100) pct = 100;
g_cpu_history[47] = (uint8_t)pct;
}
int gui_desktop_run(void) {
fbcon_enable(false);
gui_desktop_init();
// 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.
for (int drained = 0; drained < 16 && keyboard_has_key(); drained++) {
(void)keyboard_get_key();
}
g_desktop_running = true;
printk(KERN_INFO "GUI: Desktop session started (ESC returns to the kernel TTY)\n");
while (g_desktop_running) {
uint64_t frame_start_us = ktime_us();
// --- 1. Sample input -----------------------------------------------
const mouse_state_t* ms = mouse_get_state();
int mx = ms ? ms->x : gui_gfx_get_width() / 2;
int my = ms ? ms->y : gui_gfx_get_height() / 2;
bool left = ms ? ms->left_btn : false;
bool right = ms ? ms->right_btn : false;
gui_cursor_set_pos(mx, my);
bool click_edge = left && !g_prev_left;
bool right_click_edge = right && !g_prev_right;
// --- 2. Route the pointer, modal layer first ------------------------
if (gui_dialog_is_open()) {
gui_dialog_handle_mouse(mx, my, click_edge);
gui_wm_sync_button_state(left);
} else {
update_menu_hover(mx, my);
if (right_click_edge && my < gui_desktop_workarea_height()) {
gui_desktop_open_context_menu(mx, my);
gui_wm_sync_button_state(left);
} else if (click_edge) {
if (gui_desktop_handle_click(mx, my)) {
gui_wm_sync_button_state(left);
} else {
gui_wm_handle_mouse(mx, my, left, right);
}
} else {
gui_wm_handle_mouse(mx, my, left, right);
}
}
g_prev_left = left;
g_prev_right = right;
// --- 3. Route the keyboard ------------------------------------------
// keyboard_get_key() blocks until a key arrives, which would freeze
// the compositor, so only call it once the driver reports one ready.
uint16_t key = keyboard_has_key() ? keyboard_get_key() : 0;
if (key != 0) {
if (gui_dialog_is_open()) {
gui_dialog_handle_key(key);
} else if ((char)(key & 0xFF) == 27) {
if (g_start_menu_open || g_context_menu_open) {
gui_desktop_close_menus();
} else {
gui_dialog_confirm("Exit Desktop",
"Leave the graphical session and return to "
"the kernel TTY?", exit_confirm_cb, NULL);
}
} else {
gui_wm_handle_key(key);
}
}
// --- 4. Decide what needs repainting --------------------------------
// Advancing open animations mutates window geometry, so this both drives
// the effect and reports whether it is still running.
bool wm_animating = gui_wm_update();
uint64_t now_ms = ktime_ms();
bool heartbeat = (now_ms - g_last_scene_ms) >= SCENE_HEARTBEAT_MS;
bool interacting = left || right || key ||
g_start_menu_open || g_context_menu_open ||
gui_dialog_is_open();
// Claim any animation request left by the previous frame's painters.
// Clearing it here means a request only survives while an app keeps
// renewing it from its paint routine.
bool animating = g_anim_request;
g_anim_request = false;
// A full recomposite is needed when anything but the bare cursor moved;
// otherwise we keep the retained scene and just slide the cursor overlay.
bool scene_dirty = wm_animating || interacting || heartbeat || animating ||
(g_frames == 0);
if ((g_frames & 0x1F) == 0) sample_cpu_history();
if (scene_dirty) {
gui_desktop_render_background();
gui_desktop_render_icons();
gui_wm_render();
gui_desktop_render_taskbar();
gui_desktop_render_start_menu();
gui_desktop_render_context_menu();
gui_dialog_render();
g_last_scene_ms = now_ms;
} else {
// Retained scene: erase the previous cursor from it before redrawing.
gui_cursor_restore_under();
}
// The cursor is an overlay drawn on top of the (retained or fresh) scene,
// so pointer motion never forces a full recomposite.
gui_cursor_composite();
gui_gfx_present();
g_frames++;
g_pushed_pixels += gui_gfx_last_present_pixels();
g_prev_mx = mx;
g_prev_my = my;
// --- 5. Pace to the 60 FPS budget -----------------------------------
// Spend the slack yielding to other kernel threads rather than
// busy-spinning, so a background task -- the async HTTP worker behind
// the Web app above all -- makes progress while the desktop idles
// between frames. When nothing else is runnable each yield returns at
// once and this simply spins out the budget, exactly as before.
while (ktime_us() - frame_start_us < FRAME_BUDGET_US) {
sched_yield();
}
// Sample the frame rate roughly twice a second from the monotonic clock.
uint64_t fps_now_ms = ktime_ms();
uint64_t dt = fps_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 = fps_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;
printk(KERN_INFO "GUI: %llu frames, %llu px/frame pushed of %llu (%llu%%)\n",
(unsigned long long)g_frames,
(unsigned long long)avg,
(unsigned long long)full,
(unsigned long long)(full ? (avg * 100) / full : 0));
g_pushed_pixels = 0;
}
}
// Hand the screen to the framebuffer console. Without this the shell writes
// into the VGA text buffer while the adapter is still scanning out the
// framebuffer, so the TTY is visible only on the serial line -- which is
// the host terminal, not the emulator window.
fbcon_enable(true);
/* The console is about to draw over the framebuffer, so the shadow copy
* stops describing the screen; release the wallpaper cache with it. */
gui_gfx_invalidate();
gui_desktop_invalidate_background();
printk(KERN_INFO "GUI: Desktop session ended after %llu frames, TTY restored\n",
(unsigned long long)g_frames);
return 0;
}
+211
View File
@@ -0,0 +1,211 @@
// Modal dialog layer for the SUB-OS desktop
#include <gui/gui_dialog.h>
#include <gui/gui_gfx.h>
#include <gui/gui_theme.h>
#include <gui/gui_icons.h>
#include <drivers/keyboard.h>
#include <lib/string.h>
#define DIALOG_W 300
#define DIALOG_H 132
#define DIALOG_TITLEBAR 24
#define DIALOG_BTN_W 84
#define DIALOG_BTN_H 24
#define DIALOG_MAX_LINES 4
#define DIALOG_LINE_CHARS 28
typedef struct {
bool open;
gui_dialog_kind_t kind;
char title[48];
char lines[DIALOG_MAX_LINES][DIALOG_LINE_CHARS + 1];
int line_count;
bool has_cancel;
gui_dialog_cb_t callback;
void* ctx;
int hover_button; // 0 = none, 1 = OK, 2 = Cancel
} dialog_state_t;
static dialog_state_t g_dlg;
void gui_dialog_init(void) {
memset(&g_dlg, 0, sizeof(g_dlg));
}
// Break the message on spaces so words are not split across rows.
static void wrap_message(const char* message) {
g_dlg.line_count = 0;
if (!message) return;
const char* p = message;
while (*p && g_dlg.line_count < DIALOG_MAX_LINES) {
int take = 0;
int last_space = -1;
while (p[take] && take < DIALOG_LINE_CHARS) {
if (p[take] == ' ') last_space = take;
take++;
}
if (p[take] && last_space > 0) take = last_space;
int n = take;
if (n > DIALOG_LINE_CHARS) n = DIALOG_LINE_CHARS;
memcpy(g_dlg.lines[g_dlg.line_count], p, (size_t)n);
g_dlg.lines[g_dlg.line_count][n] = '\0';
g_dlg.line_count++;
p += take;
while (*p == ' ') p++;
}
}
static void open_dialog(gui_dialog_kind_t kind, const char* title, const char* message,
bool has_cancel, gui_dialog_cb_t cb, void* ctx) {
memset(&g_dlg, 0, sizeof(g_dlg));
g_dlg.open = true;
g_dlg.kind = kind;
g_dlg.has_cancel = has_cancel;
g_dlg.callback = cb;
g_dlg.ctx = ctx;
strncpy(g_dlg.title, title ? title : "SUB-OS", sizeof(g_dlg.title) - 1);
wrap_message(message);
}
void gui_dialog_show(gui_dialog_kind_t kind, const char* title, const char* message) {
open_dialog(kind, title, message, false, NULL, NULL);
}
void gui_dialog_confirm(const char* title, const char* message,
gui_dialog_cb_t on_result, void* ctx) {
open_dialog(GUI_DIALOG_CONFIRM, title, message, true, on_result, ctx);
}
void gui_dialog_dismiss(void) {
g_dlg.open = false;
}
bool gui_dialog_is_open(void) {
return g_dlg.open;
}
static void finish(gui_dialog_result_t result) {
gui_dialog_cb_t cb = g_dlg.callback;
void* ctx = g_dlg.ctx;
// Clear before invoking so a callback may immediately open another dialog.
g_dlg.open = false;
g_dlg.callback = NULL;
if (cb) cb(result, ctx);
}
static void dialog_geometry(int* dx, int* dy) {
*dx = (gui_gfx_get_width() - DIALOG_W) / 2;
*dy = (gui_gfx_get_height() - DIALOG_H) / 2 - 20;
}
bool gui_dialog_handle_mouse(int mx, int my, bool clicked) {
if (!g_dlg.open) return false;
int dx, dy;
dialog_geometry(&dx, &dy);
int btn_y = dy + DIALOG_H - DIALOG_BTN_H - 12;
int ok_x, cancel_x;
if (g_dlg.has_cancel) {
ok_x = dx + DIALOG_W - (DIALOG_BTN_W * 2) - 22;
cancel_x = dx + DIALOG_W - DIALOG_BTN_W - 12;
} else {
ok_x = dx + (DIALOG_W - DIALOG_BTN_W) / 2;
cancel_x = -1000;
}
g_dlg.hover_button = 0;
if (my >= btn_y && my < btn_y + DIALOG_BTN_H) {
if (mx >= ok_x && mx < ok_x + DIALOG_BTN_W) g_dlg.hover_button = 1;
else if (g_dlg.has_cancel && mx >= cancel_x && mx < cancel_x + DIALOG_BTN_W) g_dlg.hover_button = 2;
}
if (clicked) {
if (g_dlg.hover_button == 1) finish(GUI_DIALOG_RESULT_OK);
else if (g_dlg.hover_button == 2) finish(GUI_DIALOG_RESULT_CANCEL);
}
// Swallow every click while modal, including ones outside the frame.
return true;
}
bool gui_dialog_handle_key(uint16_t key) {
if (!g_dlg.open) return false;
char c = (char)(key & 0xFF);
if (c == '\n' || c == '\r') {
finish(GUI_DIALOG_RESULT_OK);
} else if (c == 27) { // Escape
finish(g_dlg.has_cancel ? GUI_DIALOG_RESULT_CANCEL : GUI_DIALOG_RESULT_OK);
}
return true;
}
static void draw_button(int x, int y, const char* label, bool hovered, uint32_t accent) {
gui_gfx_fill_rect(x, y, DIALOG_BTN_W, DIALOG_BTN_H,
hovered ? GUI_THEME_BG_ELEVATED : GUI_THEME_BG_SURFACE);
gui_gfx_draw_rect(x, y, DIALOG_BTN_W, DIALOG_BTN_H, hovered ? accent : GUI_THEME_BORDER);
int tw = (int)strlen(label) * 8;
gui_gfx_draw_string(x + (DIALOG_BTN_W - tw) / 2, y + (DIALOG_BTN_H - 8) / 2,
label, hovered ? GUI_COLOR_WHITE : GUI_THEME_TEXT_MUTED);
}
void gui_dialog_render(void) {
if (!g_dlg.open) return;
// Dim the whole desktop so the modal reads as the only live surface.
gui_gfx_fill_rect_blend(0, 0, gui_gfx_get_width(), gui_gfx_get_height(),
GUI_COLOR_BLACK, 120);
int dx, dy;
dialog_geometry(&dx, &dy);
uint32_t accent;
gui_icon_id_t icon;
switch (g_dlg.kind) {
case GUI_DIALOG_WARNING: accent = GUI_THEME_WARNING; icon = GUI_ICON_WARNING; break;
case GUI_DIALOG_ERROR: accent = GUI_THEME_DANGER; icon = GUI_ICON_WARNING; break;
case GUI_DIALOG_CONFIRM: accent = GUI_THEME_ACCENT; icon = GUI_ICON_INFO; break;
default: accent = GUI_THEME_PRIMARY; icon = GUI_ICON_INFO; break;
}
gui_gfx_draw_shadow(dx, dy, DIALOG_W, DIALOG_H, 10);
gui_gfx_fill_rect(dx, dy, DIALOG_W, DIALOG_H, GUI_THEME_BG_SURFACE);
gui_gfx_draw_rect(dx, dy, DIALOG_W, DIALOG_H, accent);
// Title bar
gui_gfx_draw_gradient_v(dx + 1, dy + 1, DIALOG_W - 2, DIALOG_TITLEBAR - 1,
GUI_THEME_BG_ELEVATED, GUI_THEME_BG_SURFACE);
gui_gfx_draw_line(dx, dy + DIALOG_TITLEBAR, dx + DIALOG_W - 1, dy + DIALOG_TITLEBAR, accent);
gui_gfx_draw_string_16_shadow(dx + 10, dy + 4, g_dlg.title, GUI_COLOR_WHITE, GUI_COLOR_BLACK);
// Icon + wrapped message
gui_icon_draw_scaled(icon, dx + 14, dy + DIALOG_TITLEBAR + 14, 2);
for (int i = 0; i < g_dlg.line_count; i++) {
gui_gfx_draw_string(dx + 58, dy + DIALOG_TITLEBAR + 14 + i * 13,
g_dlg.lines[i], GUI_THEME_TEXT_MAIN);
}
int btn_y = dy + DIALOG_H - DIALOG_BTN_H - 12;
if (g_dlg.has_cancel) {
draw_button(dx + DIALOG_W - (DIALOG_BTN_W * 2) - 22, btn_y, "OK",
g_dlg.hover_button == 1, accent);
draw_button(dx + DIALOG_W - DIALOG_BTN_W - 12, btn_y, "Cancel",
g_dlg.hover_button == 2, GUI_THEME_DANGER);
} else {
draw_button(dx + (DIALOG_W - DIALOG_BTN_W) / 2, btn_y, "OK",
g_dlg.hover_button == 1, accent);
}
gui_gfx_draw_string(dx + 12, dy + DIALOG_H - 12, "Enter = OK Esc = Dismiss",
GUI_THEME_TEXT_DIM);
}
+326
View File
@@ -0,0 +1,326 @@
// High-Performance 2D Graphics Engine & Software Compositor for SUB-OS
#include <gui/gui_gfx.h>
#include <lib/font8x8.h>
#include <mm/kmalloc.h>
#include <lib/string.h>
static uint32_t* g_frontbuffer = NULL;
static uint32_t* g_backbuffer = NULL;
/*
* Mirror of what the framebuffer currently holds.
*
* The front buffer lives in device memory, where a write costs far more than
* the same write to RAM and a read costs more still. Keeping a RAM copy of the
* last presented frame lets gui_gfx_present() work out which pixels actually
* changed without ever reading the framebuffer back, and push only those.
*/
static uint32_t* g_shadow = NULL;
static bool g_shadow_valid = false;
static int g_width = 800;
static int g_height = 600;
static uint64_t g_present_pixels = 0;
void gui_gfx_init(uint32_t* fb, int width, int height) {
int new_w = (width > 0) ? width : 800;
int new_h = (height > 0) ? height : 600;
/* A resolution change invalidates both RAM buffers. */
if (g_backbuffer && (new_w != g_width || new_h != g_height)) {
kfree(g_backbuffer);
g_backbuffer = NULL;
if (g_shadow) {
kfree(g_shadow);
g_shadow = NULL;
}
}
g_frontbuffer = fb;
g_width = new_w;
g_height = new_h;
size_t pixels = (size_t)g_width * (size_t)g_height;
if (!g_backbuffer) {
g_backbuffer = (uint32_t*)kzalloc(pixels * sizeof(uint32_t));
}
if (!g_shadow) {
/* Optional: without it present() just falls back to a full copy. */
g_shadow = (uint32_t*)kzalloc(pixels * sizeof(uint32_t));
}
g_shadow_valid = false;
}
void gui_gfx_invalidate(void) {
g_shadow_valid = false;
}
uint64_t gui_gfx_last_present_pixels(void) {
return g_present_pixels;
}
int gui_gfx_get_width(void) { return g_width; }
int gui_gfx_get_height(void) { return g_height; }
uint32_t* gui_gfx_get_backbuffer(void) { return g_backbuffer; }
void gui_gfx_clear(uint32_t color) {
if (!g_backbuffer) return;
size_t count = g_width * g_height;
for (size_t i = 0; i < count; i++) {
g_backbuffer[i] = color;
}
}
void gui_gfx_draw_pixel(int x, int y, uint32_t color) {
if (!g_backbuffer || x < 0 || y < 0 || x >= g_width || y >= g_height) return;
g_backbuffer[y * g_width + x] = color;
}
void gui_gfx_draw_pixel_blend(int x, int y, uint32_t color, uint8_t alpha) {
if (!g_backbuffer || x < 0 || y < 0 || x >= g_width || y >= g_height) return;
uint32_t bg = g_backbuffer[y * g_width + x];
g_backbuffer[y * g_width + x] = gui_color_alpha_blend(bg, color, alpha);
}
void gui_gfx_draw_line(int x0, int y0, int x1, int y1, uint32_t color) {
int dx = (x1 > x0) ? (x1 - x0) : (x0 - x1);
int dy = (y1 > y0) ? (y1 - y0) : (y0 - y1);
int sx = (x0 < x1) ? 1 : -1;
int sy = (y0 < y1) ? 1 : -1;
int err = dx - dy;
while (1) {
gui_gfx_draw_pixel(x0, y0, color);
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 > -dy) { err -= dy; x0 += sx; }
if (e2 < dx) { err += dx; y0 += sy; }
}
}
void gui_gfx_draw_rect(int x, int y, int w, int h, uint32_t color) {
gui_gfx_draw_line(x, y, x + w - 1, y, color);
gui_gfx_draw_line(x, y + h - 1, x + w - 1, y + h - 1, color);
gui_gfx_draw_line(x, y, x, y + h - 1, color);
gui_gfx_draw_line(x + w - 1, y, x + w - 1, y + h - 1, color);
}
void gui_gfx_fill_rect(int x, int y, int w, int h, uint32_t color) {
if (!g_backbuffer) return;
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if (x + w > g_width) w = g_width - x;
if (y + h > g_height) h = g_height - y;
if (w <= 0 || h <= 0) return;
for (int j = y; j < y + h; j++) {
uint32_t* row = &g_backbuffer[j * g_width + x];
for (int i = 0; i < w; i++) {
row[i] = color;
}
}
}
void gui_gfx_fill_rect_blend(int x, int y, int w, int h, uint32_t color, uint8_t alpha) {
if (!g_backbuffer) return;
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if (x + w > g_width) w = g_width - x;
if (y + h > g_height) h = g_height - y;
if (w <= 0 || h <= 0) return;
for (int j = y; j < y + h; j++) {
uint32_t* row = &g_backbuffer[j * g_width + x];
for (int i = 0; i < w; i++) {
row[i] = gui_color_alpha_blend(row[i], color, alpha);
}
}
}
void gui_gfx_draw_rounded_rect(int x, int y, int w, int h, int r, uint32_t color, bool filled) {
if (r <= 0) {
if (filled) gui_gfx_fill_rect(x, y, w, h, color);
else gui_gfx_draw_rect(x, y, w, h, color);
return;
}
if (filled) {
gui_gfx_fill_rect(x + r, y, w - 2 * r, h, color);
gui_gfx_fill_rect(x, y + r, r, h - 2 * r, color);
gui_gfx_fill_rect(x + w - r, y + r, r, h - 2 * r, color);
} else {
gui_gfx_draw_line(x + r, y, x + w - r, y, color);
gui_gfx_draw_line(x + r, y + h - 1, x + w - r, y + h - 1, color);
gui_gfx_draw_line(x, y + r, x, y + h - r, color);
gui_gfx_draw_line(x + w - 1, y + r, x + w - 1, y + h - r, color);
}
}
void gui_gfx_draw_gradient_v(int x, int y, int w, int h, uint32_t top_col, uint32_t bot_col) {
if (h <= 0 || w <= 0) return;
for (int j = 0; j < h; j++) {
uint8_t factor = (uint8_t)((j * 255) / h);
uint32_t row_col = gui_color_alpha_blend(top_col, bot_col, factor);
gui_gfx_fill_rect(x, y + j, w, 1, row_col);
}
}
void gui_gfx_draw_gradient_h(int x, int y, int w, int h, uint32_t left_col, uint32_t right_col) {
if (w <= 0 || h <= 0) return;
for (int i = 0; i < w; i++) {
uint8_t factor = (uint8_t)((i * 255) / w);
uint32_t col = gui_color_alpha_blend(left_col, right_col, factor);
gui_gfx_fill_rect(x + i, y, 1, h, col);
}
}
void gui_gfx_draw_char(int x, int y, char c, uint32_t fg, uint32_t bg, bool transparent_bg) {
if (c < 32 || c > 126) c = '?';
const uint8_t* glyph = font8x8_glyph(c);
for (int row = 0; row < 8; row++) {
uint8_t bits = glyph[row];
for (int col = 0; col < 8; col++) {
if (bits & (1 << (7 - col))) {
gui_gfx_draw_pixel(x + col, y + row, fg);
} else if (!transparent_bg) {
gui_gfx_draw_pixel(x + col, y + row, bg);
}
}
}
}
// Sharp High-DPI 8x16 font renderer with 2x vertical pixel fidelity
void gui_gfx_draw_char_16(int x, int y, char c, uint32_t fg, uint32_t bg, bool transparent_bg) {
if (c < 32 || c > 126) c = '?';
const uint8_t* glyph = font8x8_glyph(c);
for (int row = 0; row < 8; row++) {
uint8_t bits = glyph[row];
for (int col = 0; col < 8; col++) {
if (bits & (1 << (7 - col))) {
gui_gfx_draw_pixel(x + col, y + row * 2, fg);
gui_gfx_draw_pixel(x + col, y + row * 2 + 1, fg);
} else if (!transparent_bg) {
gui_gfx_draw_pixel(x + col, y + row * 2, bg);
gui_gfx_draw_pixel(x + col, y + row * 2 + 1, bg);
}
}
}
}
void gui_gfx_draw_string(int x, int y, const char* str, uint32_t fg) {
if (!str) return;
int cx = x;
while (*str) {
gui_gfx_draw_char(cx, y, *str, fg, 0, true);
cx += 8;
str++;
}
}
void gui_gfx_draw_string_16(int x, int y, const char* str, uint32_t fg) {
if (!str) return;
int cx = x;
while (*str) {
gui_gfx_draw_char_16(cx, y, *str, fg, 0, true);
cx += 8;
str++;
}
}
void gui_gfx_draw_string_bg(int x, int y, const char* str, uint32_t fg, uint32_t bg) {
if (!str) return;
int cx = x;
while (*str) {
gui_gfx_draw_char(cx, y, *str, fg, bg, false);
cx += 8;
str++;
}
}
void gui_gfx_draw_string_shadow(int x, int y, const char* str, uint32_t fg, uint32_t shadow) {
gui_gfx_draw_string(x + 1, y + 1, str, shadow);
gui_gfx_draw_string(x, y, str, fg);
}
void gui_gfx_draw_string_16_shadow(int x, int y, const char* str, uint32_t fg, uint32_t shadow) {
gui_gfx_draw_string_16(x + 1, y + 1, str, shadow);
gui_gfx_draw_string_16(x, y, str, fg);
}
void gui_gfx_draw_shadow(int x, int y, int w, int h, int blur) {
for (int r = 1; r <= blur; r++) {
uint8_t alpha = (uint8_t)(50 / r);
gui_gfx_fill_rect_blend(x + r, y + h, w, r, GUI_COLOR_BLACK, alpha);
gui_gfx_fill_rect_blend(x + w, y + r, r, h, GUI_COLOR_BLACK, alpha);
}
}
void gui_gfx_blit(int dx, int dy, int w, int h, const uint32_t* src_buf) {
if (!g_backbuffer || !src_buf) return;
for (int j = 0; j < h; j++) {
int py = dy + j;
if (py < 0 || py >= g_height) continue;
for (int i = 0; i < w; i++) {
int px = dx + i;
if (px < 0 || px >= g_width) continue;
uint32_t pixel = src_buf[j * w + i];
if ((pixel >> 24) > 0) {
g_backbuffer[py * g_width + px] = pixel;
}
}
}
}
/*
* Push the back buffer to the screen, writing only what changed.
*
* Every frame is still composited in full, so the cheapest reliable way to
* find the damage is to diff against the shadow copy: a RAM read is orders of
* magnitude cheaper than the framebuffer write it avoids. A typical idle
* desktop frame only moves the cursor and the clock, which turns a 3.6 MB
* blit at 1280x720 into a few kilobytes.
*
* Each row is narrowed to the span between its first and last differing pixel
* rather than tracked as a set of runs: one memcpy per row keeps the inner
* loop tight, and a row that changed in two places is rare enough that
* bridging the gap costs less than the extra bookkeeping.
*/
void gui_gfx_present(void) {
if (!g_frontbuffer || !g_backbuffer) return;
size_t pixels = (size_t)g_width * (size_t)g_height;
/* No shadow (allocation failed) or it does not describe the screen yet:
* push the whole frame and start tracking from there. */
if (!g_shadow || !g_shadow_valid) {
memcpy(g_frontbuffer, g_backbuffer, pixels * sizeof(uint32_t));
if (g_shadow) {
memcpy(g_shadow, g_backbuffer, pixels * sizeof(uint32_t));
g_shadow_valid = true;
}
g_present_pixels = pixels;
return;
}
size_t pushed = 0;
for (int y = 0; y < g_height; y++) {
size_t row = (size_t)y * (size_t)g_width;
const uint32_t* back = g_backbuffer + row;
uint32_t* shadow = g_shadow + row;
int x0 = 0;
while (x0 < g_width && back[x0] == shadow[x0]) x0++;
if (x0 == g_width) continue; /* row is untouched */
int x1 = g_width - 1;
while (x1 > x0 && back[x1] == shadow[x1]) x1--;
size_t span = (size_t)(x1 - x0 + 1);
memcpy(g_frontbuffer + row + x0, back + x0, span * sizeof(uint32_t));
memcpy(shadow + x0, back + x0, span * sizeof(uint32_t));
pushed += span;
}
g_present_pixels = pushed;
}
+423
View File
@@ -0,0 +1,423 @@
// SUB-OS Desktop Icon Set: 16x16 palette-indexed glyphs
//
// Each icon is 16 rows of 16 hex digits, where the digit indexes the shared
// palette below. Storing the art as text keeps it editable by hand, and the
// indirection lets every glyph pick up a theme change for free.
#include <gui/gui_icons.h>
#include <gui/gui_gfx.h>
#include <gui/gui_theme.h>
#define ICON_PALETTE_SIZE 16
static const uint32_t g_palette[ICON_PALETTE_SIZE] = {
0x00000000, // 0 transparent
0xFF0B0F19, // 1 outline / near-black
0xFFF8FAFC, // 2 white
0xFF94A3B8, // 3 muted grey
0xFF475569, // 4 dark grey
0xFF38BDF8, // 5 primary sky
0xFF0284C7, // 6 primary dark
0xFF34D399, // 7 success green
0xFFFBBF24, // 8 warning amber
0xFFF87171, // 9 danger red
0xFF818CF8, // A accent indigo
0xFFEC4899, // B pink
0xFF1E293B, // C surface
0xFF334155, // D elevated
0xFF000000, // E pure black
0xFFD9A441 // F folder tan
};
static const char* g_icons[GUI_ICON_COUNT][GUI_ICON_SIZE] = {
// GUI_ICON_TERMINAL
{
"4111111111111114",
"4CCCCCCCCCCCCCC4",
"4CCCCCCCCCCCCCC4",
"4C7CCCCCCCCCCCC4",
"4C77CCCCCCCCCCC4",
"4C777CCCCCCCCCC4",
"4C7777CCCCCCCCC4",
"4C777CCCCCCCCCC4",
"4C77CCCCCCCCCCC4",
"4C7CCCCCCCCCCCC4",
"4CCCCCCCCCCCCCC4",
"4CC77777777CCCC4",
"4CCCCCCCCCCCCCC4",
"4CCCCCCCCCCCCCC4",
"4CCCCCCCCCCCCCC4",
"4111111111111114",
},
// GUI_ICON_MONITOR
{
"4111111111111114",
"4CCCCCCCCCCCCCC4",
"4CCCCCCCCCCCC5C4",
"4CCCCCCCCCCC5CC4",
"4CCCCCCCCC55CCC4",
"4CCCCCCCC5CCCCC4",
"4CCCCCC55CCCCCC4",
"4CCCC55CCCCCCCC4",
"4CC55CCCCCCCCCC4",
"4C5CCCCCCCCCCCC4",
"4CCCCCCCCCCCCCC4",
"4111111111111114",
"0000444444440000",
"0000004444000000",
"0000444444440000",
"0000000000000000",
},
// GUI_ICON_FOLDER
{
"0000000000000000",
"0FFFFF0000000000",
"0F888FF000000000",
"0F88888FFFFFFF00",
"0F8888888888F000",
"0F8888888888F000",
"0F8888888888F000",
"0F8888888888F000",
"0F8888888888F000",
"0F8888888888F000",
"0F8888888888F000",
"0F8888888888F000",
"0FFFFFFFFFFFF000",
"0000000000000000",
"0000000000000000",
"0000000000000000",
},
// GUI_ICON_CALC
{
"4111111111111114",
"4CCCCCCCCCCCCCC4",
"4C777777777777C4",
"4C777777777777C4",
"4CCCCCCCCCCCCCC4",
"4C2CC2CC2CC2CCC4",
"4CCCCCCCCCCCCCC4",
"4C2CC2CC2CC2CCC4",
"4CCCCCCCCCCCCCC4",
"4C2CC2CC2CC2CCC4",
"4CCCCCCCCCCCCCC4",
"4C2CC2CC2CC5CCC4",
"4CCCCCCCCCCCCCC4",
"4CCCCCCCCCCCCCC4",
"4CCCCCCCCCCCCCC4",
"4111111111111114",
},
// GUI_ICON_PAINT
{
"0000000000000000",
"0000333333000000",
"0003222222300000",
"0032955992230000",
"0329999999923000",
"0329B99997923000",
"0329999999923000",
"0032999999230000",
"0003299992300000",
"0000322223000000",
"0000033330000000",
"0000004440000000",
"0000004440000000",
"0000004440000000",
"0000000000000000",
"0000000000000000",
},
// GUI_ICON_INFO
{
"0000055555000000",
"0000555555550000",
"0005566665550000",
"0055666666655000",
"0556662266665500",
"0556662266665500",
"0556666666665500",
"0556662266665500",
"0556622226665500",
"0556666226665500",
"0556666226665500",
"0556662222265500",
"0055666666655000",
"0005566665550000",
"0000555555550000",
"0000055555000000",
},
// GUI_ICON_SETTINGS
{
"0000034443000000",
"0000034443000000",
"0003344444330000",
"0034444444443000",
"0344444444444300",
"3444442224444430",
"4444422222444444",
"4444422222444444",
"4444422222444444",
"3444442224444430",
"0344444444444300",
"0034444444443000",
"0003344444330000",
"0000034443000000",
"0000034443000000",
"0000000000000000",
},
// GUI_ICON_TASKS
{
"0000000000000000",
"0555555555555550",
"0500000000000050",
"0507000000000050",
"0507700000000050",
"0507770000000050",
"0507777000000050",
"0500000000000050",
"0508880000000050",
"0508888000000050",
"0500000000000050",
"0509990000000050",
"0509999000000050",
"0500000000000050",
"0555555555555550",
"0000000000000000",
},
// GUI_ICON_LOG
{
"0022222222222000",
"0244444444442000",
"0240000000042000",
"0244444444442000",
"0240000000042000",
"0244444444442000",
"0240000000042000",
"0244444444442000",
"0240000000042000",
"0244444444442000",
"0240000000042000",
"0244444444442000",
"0240000000042000",
"0244444444442000",
"0022222222222000",
"0000000000000000",
},
// GUI_ICON_FLASK
{
"0000022222000000",
"0000020002000000",
"0000020002000000",
"0000020002000000",
"0000020002000000",
"0000200000200000",
"0000200000200000",
"0002000000020000",
"0002077777020000",
"0020777777702000",
"0020777777702000",
"0207777777770200",
"0207777777770200",
"0027777777772000",
"0002222222220000",
"0000000000000000",
},
// GUI_ICON_CLOCK
{
"0000055555000000",
"0005522222550000",
"0052222222225000",
"0522222222222500",
"0522221222222500",
"5222221222222250",
"5222221222222250",
"5222221222222250",
"5222221111122250",
"5222222222222250",
"5222222222222250",
"0522222222222500",
"0522222222222500",
"0052222222225000",
"0005522222550000",
"0000055555000000",
},
// GUI_ICON_FILE
{
"0022222222200000",
"0244444444200000",
"0244444444420000",
"0244444444442000",
"0244444444444200",
"0244444444444200",
"0244444444444200",
"0244444444444200",
"0244444444444200",
"0244444444444200",
"0244444444444200",
"0244444444444200",
"0244444444444200",
"0244444444444200",
"0222222222222200",
"0000000000000000",
},
// GUI_ICON_POWER
{
"0000009900000000",
"0000009900000000",
"0000999999000000",
"0009900009900000",
"0099009900990000",
"0990009900099000",
"0990009900099000",
"9900009900009900",
"9900009900009900",
"9900000000009900",
"9900000000009900",
"0990000000099000",
"0990000000099000",
"0099000000990000",
"0009999999900000",
"0000099999000000",
},
// GUI_ICON_WARNING
{
"0000008800000000",
"0000008800000000",
"0000088880000000",
"0000088880000000",
"0000881188000000",
"0000881188000000",
"0008811118800000",
"0008811118800000",
"0088111111880000",
"0088111111880000",
"0881111111188000",
"0881118811188000",
"8811118811118800",
"8811118811118800",
"8888888888888888",
"8888888888888888",
},
// GUI_ICON_HEART
{
"0000000000000000",
"0009BB9009BB9000",
"009BBBB99BBBB900",
"09BBBBBBBBBBBB90",
"09BB2BBBBBBBBB90",
"09B22BBBBBBBBB90",
"09BB2BBBBBBBBB90",
"09BBBBBBBBBBBB90",
"009BBBBBBBBBB900",
"0009BBBBBBBB9000",
"00009BBBBBB90000",
"000009BBBB900000",
"0000009BB9000000",
"0000000990000000",
"0000000000000000",
"0000000000000000",
},
// GUI_ICON_NETWORK
{
"0000000000000000",
"0000005555000000",
"0000556666550000",
"0005666556665000",
"0056666556666500",
"0555555555555550",
"0566666556666650",
"5666666556666665",
"5666666556666665",
"0566666556666650",
"0555555555555550",
"0056666556666500",
"0005666556665000",
"0000556666550000",
"0000005555000000",
"0000000000000000",
},
// GUI_ICON_GLOBE
{
"0000055555500000",
"0003556555655300",
"0035565556555300",
"0355556555655530",
"0355565556555530",
"0355556555655530",
"3555565556555553",
"3666666666666663",
"3555565556555553",
"0355556555655530",
"0355565556555530",
"0355556555655530",
"0035565556555300",
"0003556555655300",
"0000055555500000",
"0000000000000000",
},};
static const char* g_icon_names[GUI_ICON_COUNT] = {
"Terminal", "Monitor", "Folder", "Calculator", "Paint", "Info",
"Settings", "Tasks", "Log", "Tests", "Clock", "File", "Power", "Warning",
"Heart", "Network", "Globe"
};
static uint8_t hex_index(char c) {
if (c >= '0' && c <= '9') return (uint8_t)(c - '0');
if (c >= 'A' && c <= 'F') return (uint8_t)(c - 'A' + 10);
if (c >= 'a' && c <= 'f') return (uint8_t)(c - 'a' + 10);
return 0;
}
void gui_icons_init(void) {
// The glyph table is static const; nothing to build at runtime.
}
const char* gui_icon_name(gui_icon_id_t id) {
if (id < 0 || id >= GUI_ICON_COUNT) return "Unknown";
return g_icon_names[id];
}
void gui_icon_draw_scaled(gui_icon_id_t id, int x, int y, int scale) {
if (id < 0 || id >= GUI_ICON_COUNT) return;
if (scale < 1) scale = 1;
for (int row = 0; row < GUI_ICON_SIZE; row++) {
const char* line = g_icons[id][row];
if (!line) continue;
for (int col = 0; col < GUI_ICON_SIZE; col++) {
uint32_t color = g_palette[hex_index(line[col])];
if ((color >> 24) == 0) continue; // Transparent pixel
if (scale == 1) {
gui_gfx_draw_pixel(x + col, y + row, color);
} else {
gui_gfx_fill_rect(x + col * scale, y + row * scale, scale, scale, color);
}
}
}
}
void gui_icon_draw(gui_icon_id_t id, int x, int y) {
gui_icon_draw_scaled(id, x, y, 1);
}
void gui_icon_draw_tinted(gui_icon_id_t id, int x, int y, int scale, uint32_t tint) {
if (id < 0 || id >= GUI_ICON_COUNT) return;
if (scale < 1) scale = 1;
// Every opaque pixel becomes the tint; the glyph acts purely as a stencil.
for (int row = 0; row < GUI_ICON_SIZE; row++) {
const char* line = g_icons[id][row];
if (!line) continue;
for (int col = 0; col < GUI_ICON_SIZE; col++) {
if ((g_palette[hex_index(line[col])] >> 24) == 0) continue;
if (scale == 1) {
gui_gfx_draw_pixel(x + col, y + row, tint);
} else {
gui_gfx_fill_rect(x + col * scale, y + row * scale, scale, scale, tint);
}
}
}
}
+516
View File
@@ -0,0 +1,516 @@
// SUB-OS Desktop Terminal Emulator
//
// Commands typed here run through the same LazyBox applet table the kernel TTY
// uses. LazyBox reports everything via printk, so before dispatching an applet
// the terminal installs a printk sink; the applet's output is streamed into
// the window's scrollback instead of the text console, ANSI colour codes are
// translated to per-line colours, and the sink is removed afterwards.
#include <gui/gui_terminal.h>
#include <gui/gui_widgets.h>
#include <gui/gui_gfx.h>
#include <gui/gui_theme.h>
#include <userland/lazybox.h>
#include <userland/shell.h>
#include <kernel/printk.h>
#include <fs/vfs.h>
#include <mm/kmalloc.h>
#include <drivers/keyboard.h>
#include <lib/string.h>
#include <lib/printf.h>
#define TERM_SCROLLBACK 256
#define TERM_LINE_CHARS 118
#define TERM_INPUT_MAX 120
#define TERM_HISTORY 16
#define TERM_ROW_H 12
#define TERM_ARGV_MAX 16
typedef struct {
char text[TERM_LINE_CHARS + 1];
uint32_t color;
} term_line_t;
typedef struct {
term_line_t lines[TERM_SCROLLBACK];
int line_count; // Total lines ever written, capped at TERM_SCROLLBACK
int scroll; // First visible line
bool follow_tail; // Auto-scroll while new output arrives
char input[TERM_INPUT_MAX + 1];
int input_len;
char history[TERM_HISTORY][TERM_INPUT_MAX + 1];
int history_count;
int history_pos; // -1 when editing a fresh line
// Partial line being assembled from sink writes
char pending[TERM_LINE_CHARS + 1];
int pending_len;
uint32_t pending_color;
bool busy;
} term_data_t;
// ---------------------------------------------------------------------------
// Scrollback
// ---------------------------------------------------------------------------
static void term_scroll_to_tail(term_data_t* td, int visible_rows) {
td->scroll = td->line_count - visible_rows;
if (td->scroll < 0) td->scroll = 0;
}
static void term_push_line(term_data_t* td, const char* text, uint32_t color) {
if (td->line_count == TERM_SCROLLBACK) {
// Full: drop the oldest line and shift the window up by one.
for (int i = 0; i < TERM_SCROLLBACK - 1; i++) {
td->lines[i] = td->lines[i + 1];
}
td->line_count--;
if (td->scroll > 0) td->scroll--;
}
term_line_t* line = &td->lines[td->line_count++];
strncpy(line->text, text ? text : "", TERM_LINE_CHARS);
line->text[TERM_LINE_CHARS] = '\0';
line->color = color;
}
static void term_flush_pending(term_data_t* td) {
td->pending[td->pending_len] = '\0';
term_push_line(td, td->pending, td->pending_color);
td->pending_len = 0;
td->pending[0] = '\0';
}
static void term_printf(term_data_t* td, uint32_t color, const char* fmt, ...) {
char buf[TERM_LINE_CHARS + 1];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
term_push_line(td, buf, color);
}
// ---------------------------------------------------------------------------
// ANSI translation
// ---------------------------------------------------------------------------
// Map an SGR parameter to the palette. Only the colour attributes LazyBox
// actually emits are handled; everything else keeps the current colour.
static uint32_t sgr_to_color(int code, uint32_t current) {
switch (code) {
case 0: return GUI_THEME_TEXT_MAIN; // reset
case 30: case 90: return GUI_THEME_TEXT_DIM;
case 31: case 91: return GUI_THEME_DANGER;
case 32: case 92: return GUI_THEME_SUCCESS;
case 33: case 93: return GUI_THEME_WARNING;
case 34: case 94: return GUI_THEME_ACCENT;
case 35: case 95: return 0xFFEC4899; // magenta
case 36: case 96: return GUI_THEME_PRIMARY;
case 37: case 97: return GUI_THEME_TEXT_MAIN;
default: return current;
}
}
// CP437 fallback.
//
// The VGA text console renders code page 437 natively, so LazyBox draws boxes
// and colour swatches with high-ASCII bytes such as 0xDB (full block). The GUI
// font only covers printable ASCII, so translate the shapes those applets
// actually use rather than dropping them to '?'. Returns 0 to skip the byte.
static char cp437_to_ascii(unsigned char c) {
switch (c) {
case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: return '#'; // blocks
case 0xFE: case 0xFD: return '#'; // small squares
case 0xB2: return '#'; // dark shade
case 0xB1: return ':'; // medium shade
case 0xB0: return '.'; // light shade
case 0xC4: case 0xCD: return '-'; // horizontal rules
case 0xB3: case 0xBA: return '|'; // vertical rules
case 0xDA: case 0xBF: case 0xC0: case 0xD9: // single corners
case 0xC9: case 0xBB: case 0xC8: case 0xBC: // double corners
case 0xC3: case 0xB4: case 0xC2: case 0xC1: case 0xC5: // tees and cross
return '+';
case 0x07: case 0xF9: case 0xFA: return '.'; // bullets
case 0x1A: case 0x10: return '>'; // arrows
case 0x1B: case 0x11: return '<';
case 0x18: case 0x1E: return '^';
case 0x19: case 0x1F: return 'v';
case 0xF8: return 'o'; // degree
default: return 0; // drop
}
}
// Consume captured bytes, splitting on newlines and folding ANSI escapes into
// the colour of the line being assembled.
static void term_absorb(term_data_t* td, const char* text, size_t len) {
for (size_t i = 0; i < len; i++) {
char c = text[i];
if (c == '\033') {
// CSI sequence: ESC [ params letter
size_t j = i + 1;
if (j < len && text[j] == '[') {
j++;
int value = 0;
bool have_value = false;
while (j < len) {
char p = text[j];
if (p >= '0' && p <= '9') {
value = value * 10 + (p - '0');
have_value = true;
j++;
} else if (p == ';') {
if (have_value) td->pending_color = sgr_to_color(value, td->pending_color);
value = 0;
have_value = false;
j++;
} else {
if (p == 'm') {
td->pending_color = sgr_to_color(have_value ? value : 0,
td->pending_color);
}
j++;
break;
}
}
i = j - 1;
}
continue;
}
if (c == '\n') {
term_flush_pending(td);
continue;
}
if (c == '\r') {
td->pending_len = 0;
continue;
}
if (c == '\t') {
// Expand to the next 8-column stop.
int stop = (td->pending_len + 8) & ~7;
while (td->pending_len < stop && td->pending_len < TERM_LINE_CHARS) {
td->pending[td->pending_len++] = ' ';
}
continue;
}
unsigned char uc = (unsigned char)c;
if (uc < 32 || uc > 126) {
char sub = cp437_to_ascii(uc);
if (!sub) continue; // Control byte or a glyph with no ASCII stand-in
c = sub;
}
if (td->pending_len >= TERM_LINE_CHARS) term_flush_pending(td);
td->pending[td->pending_len++] = c;
}
}
static void term_sink(const char* text, size_t len, void* ctx) {
term_absorb((term_data_t*)ctx, text, len);
}
// ---------------------------------------------------------------------------
// Command execution
// ---------------------------------------------------------------------------
// Applets that read the keyboard directly would block the compositor, since
// the desktop drives input from its own frame loop.
static bool applet_is_interactive(const char* name) {
return strcmp(name, "nano") == 0 || strcmp(name, "snake") == 0 ||
strcmp(name, "sh") == 0 || strcmp(name, "top") == 0;
}
static void term_run_command(term_data_t* td, const char* cmd) {
char work[TERM_INPUT_MAX + 1];
strncpy(work, cmd, TERM_INPUT_MAX);
work[TERM_INPUT_MAX] = '\0';
// Same quote-aware tokenizer the TTY shell uses.
char* argv[TERM_ARGV_MAX];
int argc = 0;
char* token = work;
while (*token) {
while (*token == ' ') *token++ = '\0';
if (*token == '\0') break;
if (*token == '"' || *token == '\'') {
char quote = *token++;
if (argc < TERM_ARGV_MAX) argv[argc++] = token;
while (*token && *token != quote) token++;
if (*token == quote) *token++ = '\0';
} else {
if (argc < TERM_ARGV_MAX) argv[argc++] = token;
while (*token && *token != ' ') token++;
}
}
if (argc == 0) return;
// Terminal built-ins first.
if (strcmp(argv[0], "clear") == 0 || strcmp(argv[0], "cls") == 0) {
td->line_count = 0;
td->scroll = 0;
return;
}
if (strcmp(argv[0], "exit") == 0 || strcmp(argv[0], "quit") == 0) {
term_printf(td, GUI_THEME_TEXT_DIM, "Close the window to end this session.");
return;
}
bool is_applet = lazybox_has_applet(argv[0]);
bool is_builtin = shell_is_builtin(argv[0]);
if (!is_applet && !is_builtin) {
term_printf(td, GUI_THEME_DANGER,
"%s: command not found. Try 'lazybox' for the applet list.", argv[0]);
return;
}
if (is_applet && applet_is_interactive(argv[0])) {
term_printf(td, GUI_THEME_WARNING,
"%s reads the keyboard directly and would freeze the desktop.",
argv[0]);
term_printf(td, GUI_THEME_TEXT_DIM,
"Run it from the kernel TTY instead (Esc leaves the desktop).");
return;
}
// Capture printk output into this window's scrollback for the duration of
// the command. Applets take precedence over builtins, matching the TTY.
td->busy = true;
td->pending_len = 0;
td->pending_color = GUI_THEME_TEXT_MAIN;
printk_set_sink(term_sink, td);
if (is_applet) {
lazybox_run_applet(argv[0], argc, argv);
} else {
shell_execute_builtin(cmd, argc, argv, false);
}
printk_clear_sink();
if (td->pending_len > 0) term_flush_pending(td);
td->busy = false;
}
static void term_submit(term_data_t* td) {
const char* cwd = vfs_getcwd();
term_printf(td, GUI_THEME_SUCCESS, "%s $ %s", cwd ? cwd : "/", td->input);
if (td->input_len > 0) {
// Record in history, skipping an immediate repeat.
bool repeat = (td->history_count > 0 &&
strcmp(td->history[td->history_count - 1], td->input) == 0);
if (!repeat) {
if (td->history_count == TERM_HISTORY) {
for (int i = 0; i < TERM_HISTORY - 1; i++) {
strcpy(td->history[i], td->history[i + 1]);
}
td->history_count--;
}
strcpy(td->history[td->history_count++], td->input);
}
term_run_command(td, td->input);
}
td->input[0] = '\0';
td->input_len = 0;
td->history_pos = -1;
td->follow_tail = true;
}
// ---------------------------------------------------------------------------
// Input handling
// ---------------------------------------------------------------------------
static void term_recall_history(term_data_t* td, int direction) {
if (td->history_count == 0) return;
if (td->history_pos == -1) {
td->history_pos = (direction < 0) ? td->history_count - 1 : -1;
} else {
td->history_pos += (direction < 0) ? -1 : 1;
}
if (td->history_pos < 0) td->history_pos = 0;
if (td->history_pos >= td->history_count) {
// Past the newest entry: back to an empty prompt.
td->history_pos = -1;
td->input[0] = '\0';
td->input_len = 0;
return;
}
strncpy(td->input, td->history[td->history_pos], TERM_INPUT_MAX);
td->input[TERM_INPUT_MAX] = '\0';
td->input_len = (int)strlen(td->input);
}
static void terminal_event(gui_window_t* win, const gui_event_t* ev) {
term_data_t* td = (term_data_t*)win->user_data;
if (!td) return;
if (ev->type == GUI_EVENT_CLOSE) {
// A sink pointing at freed memory would corrupt the next printk.
if (td->busy) printk_clear_sink();
kfree(td);
win->user_data = NULL;
return;
}
if (ev->type == GUI_EVENT_KEY_DOWN) {
uint16_t key = ev->key;
int visible = (win->height - GUI_TITLEBAR_HEIGHT - 40) / TERM_ROW_H;
if (visible < 1) visible = 1;
if (key & KEY_SPECIAL_FLAG) {
switch (key) {
case KEY_UP: term_recall_history(td, -1); break;
case KEY_DOWN: term_recall_history(td, +1); break;
case KEY_PAGE_UP:
td->follow_tail = false;
td->scroll -= visible;
if (td->scroll < 0) td->scroll = 0;
break;
case KEY_PAGE_DOWN:
td->scroll += visible;
term_scroll_to_tail(td, visible);
td->follow_tail = true;
break;
case KEY_HOME:
td->follow_tail = false;
td->scroll = 0;
break;
case KEY_END:
td->follow_tail = true;
term_scroll_to_tail(td, visible);
break;
default: break;
}
return;
}
char c = (char)(key & 0xFF);
if (c == '\n' || c == '\r') {
term_submit(td);
} else if (c == '\b' || (uint8_t)c == 0x7F) {
if (td->input_len > 0) td->input[--td->input_len] = '\0';
} else if (c >= 32 && c <= 126 && td->input_len < TERM_INPUT_MAX) {
td->input[td->input_len++] = c;
td->input[td->input_len] = '\0';
}
return;
}
gui_widget_feed_event(win, ev);
}
// ---------------------------------------------------------------------------
// Rendering
// ---------------------------------------------------------------------------
static void terminal_paint(gui_window_t* win) {
term_data_t* td = (term_data_t*)win->user_data;
if (!td) return;
int cx = win->x + 1;
int cy = win->y + GUI_TITLEBAR_HEIGHT + 1;
int cw = win->width - 2;
int ch = win->height - GUI_TITLEBAR_HEIGHT - 2;
gui_gfx_fill_rect(cx, cy, cw, ch, 0xFF080B14);
int pad = 6;
int prompt_h = 18;
int status_h = 12;
int view_h = ch - prompt_h - status_h - pad;
int visible = view_h / TERM_ROW_H;
if (visible < 1) visible = 1;
int max_cols = (cw - pad * 2) / 8;
if (max_cols < 1) max_cols = 1;
if (max_cols > TERM_LINE_CHARS) max_cols = TERM_LINE_CHARS;
if (td->follow_tail) term_scroll_to_tail(td, visible);
if (td->scroll > td->line_count - visible) term_scroll_to_tail(td, visible);
if (td->scroll < 0) td->scroll = 0;
// Scrollback
for (int row = 0; row < visible; row++) {
int idx = td->scroll + row;
if (idx >= td->line_count) break;
char clipped[TERM_LINE_CHARS + 1];
strncpy(clipped, td->lines[idx].text, (size_t)max_cols);
clipped[max_cols] = '\0';
gui_gfx_draw_string(cx + pad, cy + pad + row * TERM_ROW_H, clipped,
td->lines[idx].color);
}
// Prompt
int prompt_y = cy + ch - prompt_h - status_h;
gui_gfx_draw_line(cx + 1, prompt_y - 3, cx + cw - 2, prompt_y - 3, GUI_THEME_BORDER);
const char* cwd = vfs_getcwd();
char prompt[40];
snprintf(prompt, sizeof(prompt), "%s $ ", cwd ? cwd : "/");
int prompt_w = (int)strlen(prompt) * 8;
gui_gfx_draw_string(cx + pad, prompt_y + 2, prompt, GUI_THEME_PRIMARY);
// Show the tail of a long command line so the cursor stays visible.
int room = max_cols - (int)strlen(prompt) - 1;
if (room < 1) room = 1;
const char* view = (td->input_len > room) ? td->input + (td->input_len - room)
: td->input;
gui_gfx_draw_string(cx + pad + prompt_w, prompt_y + 2, view, GUI_THEME_TEXT_MAIN);
if (win->active) {
int cursor_x = cx + pad + prompt_w + (int)strlen(view) * 8;
gui_gfx_fill_rect(cursor_x, prompt_y + 1, 8, 10, GUI_THEME_SUCCESS);
}
// Status line
char status[72];
if (td->line_count > visible && !td->follow_tail) {
snprintf(status, sizeof(status), "%d lines showing %d-%d [End] to follow",
td->line_count, td->scroll + 1,
(td->scroll + visible < td->line_count) ? td->scroll + visible
: td->line_count);
} else {
snprintf(status, sizeof(status), "%d lines Up/Down history PgUp/PgDn scroll",
td->line_count);
}
gui_gfx_draw_string(cx + pad, cy + ch - status_h, status, GUI_THEME_TEXT_DIM);
}
void gui_app_terminal_launch(int x, int y, int w, int h) {
gui_window_t* win = gui_wm_create_window("Terminal", x, y,
(w > 0) ? w : 520, (h > 0) ? h : 320);
if (!win) return;
term_data_t* td = (term_data_t*)kzalloc(sizeof(term_data_t));
if (!td) return;
td->history_pos = -1;
td->follow_tail = true;
td->pending_color = GUI_THEME_TEXT_MAIN;
term_printf(td, GUI_THEME_PRIMARY, "SUB-OS Terminal - LazyBox %s", LAZYBOX_VERSION);
term_printf(td, GUI_THEME_TEXT_MUTED,
"LazyBox applets and shell builtins both run here. Try 'ls',");
term_printf(td, GUI_THEME_TEXT_MUTED,
"'uname -a', 'free', 'neofetch', 'lspci', 'ktest', 'help'.");
term_printf(td, GUI_THEME_TEXT_DIM, "");
win->user_data = td;
win->paint = terminal_paint;
win->handle_event = terminal_event;
}
+517
View File
@@ -0,0 +1,517 @@
// SUB-WT: Immediate-mode widget toolkit for the SUB-OS desktop
#include <gui/gui_widgets.h>
#include <gui/gui_gfx.h>
#include <gui/gui_theme.h>
#include <drivers/keyboard.h>
#include <lib/string.h>
#include <lib/printf.h>
static gui_widget_input_t g_input;
static gui_window_t* g_win = NULL;
static int g_origin_x = 0;
static int g_origin_y = 0;
static int g_client_w = 0;
static int g_client_h = 0;
// Screen-space helpers: widgets are authored in client coordinates.
static inline int SX(int x) { return g_origin_x + x; }
static inline int SY(int y) { return g_origin_y + y; }
static bool hit_test(int x, int y, int w, int h) {
return g_input.mouse_x >= x && g_input.mouse_x < x + w &&
g_input.mouse_y >= y && g_input.mouse_y < y + h;
}
static int text_width(const char* s) {
return s ? (int)strlen(s) * GUI_FONT_W : 0;
}
void gui_widget_feed_event(gui_window_t* win, const gui_event_t* ev) {
if (!ev) return;
switch (ev->type) {
case GUI_EVENT_MOUSE_DOWN:
g_input.mouse_x = ev->rel_x;
g_input.mouse_y = ev->rel_y;
g_input.mouse_down = true;
g_input.mouse_clicked = true;
break;
case GUI_EVENT_MOUSE_UP:
g_input.mouse_x = ev->rel_x;
g_input.mouse_y = ev->rel_y;
g_input.mouse_down = false;
g_input.mouse_released = true;
g_input.active_id = 0;
break;
case GUI_EVENT_MOUSE_MOVE:
g_input.mouse_x = ev->rel_x;
g_input.mouse_y = ev->rel_y;
g_input.mouse_down = ev->btn_left;
break;
case GUI_EVENT_KEY_DOWN:
g_input.key = ev->key;
break;
case GUI_EVENT_CLOSE:
g_input.focus_id = 0;
g_input.active_id = 0;
break;
default:
break;
}
(void)win;
}
void gui_widget_begin(gui_window_t* win) {
g_win = win;
if (!win) return;
g_origin_x = win->x + 1;
g_origin_y = win->y + GUI_TITLEBAR_HEIGHT + 1;
g_client_w = win->width - 2;
g_client_h = win->height - GUI_TITLEBAR_HEIGHT - 2;
g_input.hot_id = 0;
}
void gui_widget_end(void) {
// One-shot inputs live for exactly one paint pass; anything not claimed
// by a widget this frame is discarded rather than replayed later.
g_input.mouse_clicked = false;
g_input.mouse_released = false;
g_input.key = 0;
g_win = NULL;
}
const gui_widget_input_t* gui_widget_get_input(void) { return &g_input; }
void gui_widget_set_focus(int id) { g_input.focus_id = id; }
int gui_widget_client_width(void) { return g_client_w; }
int gui_widget_client_height(void) { return g_client_h; }
// ===========================================================================
// Static content
// ===========================================================================
void gui_label(int x, int y, const char* text, uint32_t color) {
gui_gfx_draw_string(SX(x), SY(y), text, color);
}
void gui_label_bold(int x, int y, const char* text, uint32_t color) {
gui_gfx_draw_string_16_shadow(SX(x), SY(y), text, color, GUI_COLOR_BLACK);
}
void gui_label_aligned(int x, int y, int w, const char* text, uint32_t color, gui_align_t align) {
int tw = text_width(text);
int tx = x;
if (align == GUI_ALIGN_CENTER) tx = x + (w - tw) / 2;
else if (align == GUI_ALIGN_RIGHT) tx = x + w - tw;
gui_gfx_draw_string(SX(tx), SY(y), text, color);
}
void gui_panel(int x, int y, int w, int h, const char* title) {
gui_gfx_fill_rect(SX(x), SY(y), w, h, GUI_THEME_BG_DARK);
gui_gfx_draw_rect(SX(x), SY(y), w, h, GUI_THEME_BORDER);
if (title && *title) {
// Caption sits astride the top border, so clear the strip behind it.
int tw = text_width(title) + 8;
gui_gfx_fill_rect(SX(x + 8), SY(y) - 4, tw, 9, GUI_THEME_BG_SURFACE);
gui_gfx_draw_string(SX(x + 12), SY(y) - 4, title, GUI_THEME_TEXT_MUTED);
}
}
void gui_separator(int x, int y, int w) {
gui_gfx_draw_line(SX(x), SY(y), SX(x + w - 1), SY(y), GUI_THEME_BORDER);
}
void gui_progress_bar(int x, int y, int w, int h, int percent, uint32_t fill) {
if (percent < 0) percent = 0;
if (percent > 100) percent = 100;
gui_gfx_fill_rect(SX(x), SY(y), w, h, GUI_THEME_BG_DARK);
int filled = (w - 2) * percent / 100;
if (filled > 0) gui_gfx_fill_rect(SX(x + 1), SY(y + 1), filled, h - 2, fill);
gui_gfx_draw_rect(SX(x), SY(y), w, h, GUI_THEME_BORDER);
}
void gui_sparkline(int x, int y, int w, int h, const uint8_t* samples, int count, uint32_t color) {
gui_gfx_fill_rect(SX(x), SY(y), w, h, GUI_THEME_BG_DARK);
gui_gfx_draw_rect(SX(x), SY(y), w, h, GUI_THEME_BORDER);
if (!samples || count < 2) return;
// Horizontal guides at 25/50/75%.
for (int q = 1; q < 4; q++) {
int gy = SY(y) + h * q / 4;
gui_gfx_draw_line(SX(x) + 1, gy, SX(x) + w - 2, gy, 0xFF1E293B);
}
int usable_h = h - 4;
for (int i = 0; i < count - 1; i++) {
int x0 = SX(x) + 1 + (i * (w - 2)) / (count - 1);
int x1 = SX(x) + 1 + ((i + 1) * (w - 2)) / (count - 1);
int y0 = SY(y) + h - 2 - (samples[i] * usable_h) / 100;
int y1 = SY(y) + h - 2 - (samples[i + 1] * usable_h) / 100;
gui_gfx_draw_line(x0, y0, x1, y1, color);
}
}
void gui_badge(int x, int y, const char* text, uint32_t bg, uint32_t fg) {
int w = text_width(text) + 10;
gui_gfx_fill_rect(SX(x), SY(y), w, 14, bg);
gui_gfx_draw_rect(SX(x), SY(y), w, 14, GUI_THEME_BORDER);
gui_gfx_draw_string(SX(x + 5), SY(y + 3), text, fg);
}
// ===========================================================================
// Interactive controls
// ===========================================================================
bool gui_hitzone(int id, int x, int y, int w, int h) {
if (!hit_test(x, y, w, h)) return false;
g_input.hot_id = id;
if (!g_input.mouse_clicked) return false;
g_input.mouse_clicked = false; // Claim the click
g_input.active_id = id;
return true;
}
bool gui_button_styled(int id, int x, int y, int w, int h, const char* label,
uint32_t face, uint32_t face_hover, uint32_t face_press,
uint32_t border, uint32_t text) {
bool hovered = hit_test(x, y, w, h);
bool pressed = hovered && g_input.mouse_down;
bool clicked = false;
if (hovered) {
g_input.hot_id = id;
if (g_input.mouse_clicked) {
g_input.mouse_clicked = false; // Claim the click
g_input.active_id = id;
g_input.focus_id = 0;
clicked = true;
}
}
gui_gfx_fill_rect(SX(x), SY(y), w, h,
pressed ? face_press : hovered ? face_hover : face);
gui_gfx_draw_rect(SX(x), SY(y), w, h, border);
// A pressed button shifts its label a pixel to sell the depression.
int tx = x + (w - text_width(label)) / 2 + (pressed ? 1 : 0);
int ty = y + (h - GUI_FONT_H) / 2 + (pressed ? 1 : 0);
gui_gfx_draw_string(SX(tx), SY(ty), label, text);
return clicked;
}
bool gui_button_colored(int id, int x, int y, int w, int h, const char* label, uint32_t accent) {
bool hovered = hit_test(x, y, w, h);
return gui_button_styled(id, x, y, w, h, label,
GUI_THEME_BG_SURFACE, GUI_THEME_BG_ELEVATED, GUI_THEME_BG_DARK,
hovered ? accent : GUI_THEME_BORDER,
hovered ? GUI_THEME_TEXT_MAIN : GUI_THEME_TEXT_MUTED);
}
bool gui_button(int id, int x, int y, int w, int h, const char* label) {
return gui_button_colored(id, x, y, w, h, label, GUI_THEME_PRIMARY);
}
bool gui_checkbox(int id, int x, int y, const char* label, bool* value) {
if (!value) return false;
int box = 14;
int w = box + 6 + text_width(label);
bool hovered = hit_test(x, y, w, box);
bool changed = false;
if (hovered && g_input.mouse_clicked) {
g_input.mouse_clicked = false;
*value = !*value;
changed = true;
}
gui_gfx_fill_rect(SX(x), SY(y), box, box, GUI_THEME_BG_DARK);
gui_gfx_draw_rect(SX(x), SY(y), box, box, hovered ? GUI_THEME_PRIMARY : GUI_THEME_BORDER);
if (*value) {
// Two strokes form the tick.
gui_gfx_draw_line(SX(x + 3), SY(y + 7), SX(x + 6), SY(y + 10), GUI_THEME_SUCCESS);
gui_gfx_draw_line(SX(x + 6), SY(y + 10), SX(x + 11), SY(y + 3), GUI_THEME_SUCCESS);
}
gui_gfx_draw_string(SX(x + box + 6), SY(y + 3), label,
hovered ? GUI_THEME_TEXT_MAIN : GUI_THEME_TEXT_MUTED);
return changed;
}
bool gui_radio(int id, int x, int y, const char* label, int* selected, int this_value) {
if (!selected) return false;
int box = 12;
int w = box + 6 + text_width(label);
bool hovered = hit_test(x, y, w, box);
bool changed = false;
if (hovered && g_input.mouse_clicked) {
g_input.mouse_clicked = false;
if (*selected != this_value) {
*selected = this_value;
changed = true;
}
}
gui_gfx_draw_rounded_rect(SX(x), SY(y), box, box, 5,
hovered ? GUI_THEME_PRIMARY : GUI_THEME_BORDER, false);
if (*selected == this_value) {
gui_gfx_fill_rect(SX(x + 4), SY(y + 4), 4, 4, GUI_THEME_PRIMARY);
}
gui_gfx_draw_string(SX(x + box + 6), SY(y + 2), label,
hovered ? GUI_THEME_TEXT_MAIN : GUI_THEME_TEXT_MUTED);
(void)id;
return changed;
}
bool gui_slider(int id, int x, int y, int w, int* value, int min_val, int max_val) {
if (!value || max_val <= min_val) return false;
int track_h = 6;
int knob_w = 10;
int hit_h = 18;
bool hovered = hit_test(x, y - 4, w, hit_h);
bool changed = false;
if (hovered && g_input.mouse_clicked) {
g_input.mouse_clicked = false;
g_input.active_id = id;
}
// Dragging continues while the button is held, even off the track.
if (g_input.active_id == id && g_input.mouse_down) {
int rel = g_input.mouse_x - x - knob_w / 2;
int span = w - knob_w;
if (span < 1) span = 1;
if (rel < 0) rel = 0;
if (rel > span) rel = span;
int nv = min_val + (rel * (max_val - min_val)) / span;
if (nv != *value) {
*value = nv;
changed = true;
}
}
if (*value < min_val) *value = min_val;
if (*value > max_val) *value = max_val;
int track_y = y + (hit_h - track_h) / 2 - 4;
gui_gfx_fill_rect(SX(x), SY(track_y), w, track_h, GUI_THEME_BG_DARK);
gui_gfx_draw_rect(SX(x), SY(track_y), w, track_h, GUI_THEME_BORDER);
int span = w - knob_w;
int knob_x = x + ((*value - min_val) * span) / (max_val - min_val);
gui_gfx_fill_rect(SX(x + 1), SY(track_y + 1), knob_x - x, track_h - 2, GUI_THEME_PRIMARY_DARK);
gui_gfx_fill_rect(SX(knob_x), SY(y - 3), knob_w, 16,
(g_input.active_id == id) ? GUI_THEME_PRIMARY : GUI_THEME_BG_ELEVATED);
gui_gfx_draw_rect(SX(knob_x), SY(y - 3), knob_w, 16, GUI_THEME_PRIMARY);
return changed;
}
bool gui_listbox(int id, int x, int y, int w, int h, const char* const* items, int count, int* selected) {
if (!items || !selected) return false;
gui_gfx_fill_rect(SX(x), SY(y), w, h, GUI_THEME_BG_DARK);
gui_gfx_draw_rect(SX(x), SY(y), w, h, GUI_THEME_BORDER);
int row_h = GUI_WIDGET_ROW_H - 4;
int visible = (h - 4) / row_h;
bool changed = false;
// Scroll just enough to keep the selection on screen.
int first = 0;
if (*selected >= visible) first = *selected - visible + 1;
if (first > count - visible) first = count - visible;
if (first < 0) first = 0;
for (int i = 0; i < visible && first + i < count; i++) {
int idx = first + i;
int ry = y + 2 + i * row_h;
bool hovered = hit_test(x + 1, ry, w - 2, row_h);
if (hovered && g_input.mouse_clicked) {
g_input.mouse_clicked = false;
g_input.focus_id = id;
if (*selected != idx) {
*selected = idx;
changed = true;
}
}
if (idx == *selected) {
gui_gfx_fill_rect(SX(x + 1), SY(ry), w - 2, row_h, GUI_THEME_PRIMARY_DARK);
} else if (hovered) {
gui_gfx_fill_rect(SX(x + 1), SY(ry), w - 2, row_h, GUI_THEME_BG_ELEVATED);
}
uint32_t fg = (idx == *selected) ? GUI_COLOR_WHITE : GUI_THEME_TEXT_MUTED;
gui_gfx_draw_string(SX(x + 6), SY(ry + (row_h - GUI_FONT_H) / 2), items[idx], fg);
}
// Arrow keys move the selection while this list holds focus.
if (g_input.focus_id == id && g_input.key) {
if (g_input.key == KEY_UP && *selected > 0) {
(*selected)--;
changed = true;
g_input.key = 0;
} else if (g_input.key == KEY_DOWN && *selected < count - 1) {
(*selected)++;
changed = true;
g_input.key = 0;
}
}
if (count > visible) {
gui_gfx_draw_string(SX(x + w - 14), SY(y + 3), "^", GUI_THEME_TEXT_DIM);
gui_gfx_draw_string(SX(x + w - 14), SY(y + h - 11), "v", GUI_THEME_TEXT_DIM);
}
return changed;
}
bool gui_textfield(int id, int x, int y, int w, char* buffer, size_t capacity) {
if (!buffer || capacity == 0) return false;
int h = 20;
bool hovered = hit_test(x, y, w, h);
bool focused = (g_input.focus_id == id);
bool changed = false;
if (hovered && g_input.mouse_clicked) {
g_input.mouse_clicked = false;
g_input.focus_id = id;
focused = true;
}
if (focused && g_input.key) {
uint16_t key = g_input.key;
char c = (char)(key & 0xFF);
size_t len = strlen(buffer);
if (!(key & KEY_SPECIAL_FLAG)) {
if (c == '\b' || (uint8_t)c == 0x7F) {
if (len > 0) {
buffer[len - 1] = '\0';
changed = true;
}
g_input.key = 0;
} else if (c >= 32 && c <= 126 && len + 1 < capacity) {
buffer[len] = c;
buffer[len + 1] = '\0';
changed = true;
g_input.key = 0;
}
}
}
gui_gfx_fill_rect(SX(x), SY(y), w, h, GUI_THEME_BG_DARK);
gui_gfx_draw_rect(SX(x), SY(y), w, h,
focused ? GUI_THEME_PRIMARY : (hovered ? GUI_THEME_ACCENT : GUI_THEME_BORDER));
// Show the tail of the text when it overflows the field.
int max_chars = (w - 10) / GUI_FONT_W;
int len = (int)strlen(buffer);
const char* view = (len > max_chars) ? buffer + (len - max_chars) : buffer;
gui_gfx_draw_string(SX(x + 5), SY(y + (h - GUI_FONT_H) / 2), view, GUI_THEME_TEXT_MAIN);
if (focused) {
int cx = x + 5 + (int)strlen(view) * GUI_FONT_W;
gui_gfx_fill_rect(SX(cx), SY(y + 4), 2, h - 8, GUI_THEME_SUCCESS);
}
return changed;
}
int gui_tabbar(int id, int x, int y, int w, const char* const* labels, int count, int* active) {
if (!labels || count <= 0 || !active) return -1;
int tab_w = w / count;
int tab_h = 22;
for (int i = 0; i < count; i++) {
int tx = x + i * tab_w;
bool hovered = hit_test(tx, y, tab_w, tab_h);
bool is_active = (*active == i);
if (hovered && g_input.mouse_clicked) {
g_input.mouse_clicked = false;
*active = i;
is_active = true;
}
uint32_t bg = is_active ? GUI_THEME_BG_SURFACE
: hovered ? GUI_THEME_BG_ELEVATED
: GUI_THEME_BG_DARK;
gui_gfx_fill_rect(SX(tx), SY(y), tab_w, tab_h, bg);
gui_gfx_draw_rect(SX(tx), SY(y), tab_w, tab_h, GUI_THEME_BORDER);
if (is_active) {
// Active tab gets an accent underline and merges with the body.
gui_gfx_fill_rect(SX(tx + 1), SY(y), tab_w - 2, 2, GUI_THEME_PRIMARY);
gui_gfx_draw_line(SX(tx + 1), SY(y + tab_h - 1), SX(tx + tab_w - 2),
SY(y + tab_h - 1), GUI_THEME_BG_SURFACE);
}
int tw = text_width(labels[i]);
gui_gfx_draw_string(SX(tx + (tab_w - tw) / 2), SY(y + (tab_h - GUI_FONT_H) / 2),
labels[i], is_active ? GUI_THEME_TEXT_MAIN : GUI_THEME_TEXT_DIM);
}
(void)id;
return *active;
}
bool gui_scrollbar(int id, int x, int y, int h, int* offset, int total, int visible) {
if (!offset || total <= visible || visible <= 0) return false;
int w = 10;
gui_gfx_fill_rect(SX(x), SY(y), w, h, GUI_THEME_BG_DARK);
gui_gfx_draw_rect(SX(x), SY(y), w, h, GUI_THEME_BORDER);
int max_offset = total - visible;
int thumb_h = (h * visible) / total;
if (thumb_h < 12) thumb_h = 12;
bool hovered = hit_test(x, y, w, h);
bool changed = false;
if (hovered && g_input.mouse_clicked) {
g_input.mouse_clicked = false;
g_input.active_id = id;
}
if (g_input.active_id == id && g_input.mouse_down) {
int span = h - thumb_h;
if (span < 1) span = 1;
int rel = g_input.mouse_y - y - thumb_h / 2;
if (rel < 0) rel = 0;
if (rel > span) rel = span;
int nv = (rel * max_offset) / span;
if (nv != *offset) {
*offset = nv;
changed = true;
}
}
if (*offset < 0) *offset = 0;
if (*offset > max_offset) *offset = max_offset;
int thumb_y = y + ((h - thumb_h) * (*offset)) / (max_offset ? max_offset : 1);
gui_gfx_fill_rect(SX(x + 1), SY(thumb_y), w - 2, thumb_h,
(g_input.active_id == id) ? GUI_THEME_PRIMARY : GUI_THEME_BG_ELEVATED);
return changed;
}
+724
View File
@@ -0,0 +1,724 @@
// SUB-WM: window manager and software compositor for SUB-OS
//
// Windows live in a fixed slot array; z-order is a separate array of ids with
// the topmost window last. Interaction is edge-driven: a press picks a target
// and latches it into a drag or resize gesture, and the gesture then owns the
// pointer until the button comes back up.
#include <gui/gui_wm.h>
#include <gui/gui_icons.h>
#include <drivers/keyboard.h>
#include <lib/string.h>
#include <mm/kmalloc.h>
#include <kernel/ktime.h>
#define WM_OPEN_ANIM_MS 160
#define BTN_SIZE 14
#define BTN_GAP 4
#define SNAP_MARGIN 8
static gui_window_t g_windows[GUI_MAX_WINDOWS];
static int g_window_order[GUI_MAX_WINDOWS];
static int g_num_windows = 0;
static int g_next_win_id = 1;
static int g_drag_win_id = -1;
static int g_resize_win_id = -1;
static bool g_prev_btn_left = false;
static int g_last_mouse_x = 0;
static int g_last_mouse_y = 0;
static int g_work_w = GUI_DEFAULT_WIDTH;
static int g_work_h = GUI_DEFAULT_HEIGHT;
// Anchor of the in-flight resize gesture, captured on the press edge.
static int g_resize_start_w = 0;
static int g_resize_start_h = 0;
static int g_resize_start_mx = 0;
static int g_resize_start_my = 0;
void gui_wm_init(void) {
memset(g_windows, 0, sizeof(g_windows));
memset(g_window_order, 0, sizeof(g_window_order));
g_num_windows = 0;
g_next_win_id = 1;
g_drag_win_id = -1;
g_resize_win_id = -1;
g_prev_btn_left = false;
g_work_w = gui_gfx_get_width();
g_work_h = gui_gfx_get_height();
}
void gui_wm_set_workarea(int width, int height) {
if (width > 0) g_work_w = width;
if (height > 0) g_work_h = height;
}
gui_window_t* gui_wm_create_window(const char* title, int x, int y, int w, int h) {
if (g_num_windows >= GUI_MAX_WINDOWS) return NULL;
int slot = -1;
for (int i = 0; i < GUI_MAX_WINDOWS; i++) {
if (g_windows[i].id == 0) { slot = i; break; }
}
if (slot == -1) return NULL;
gui_window_t* win = &g_windows[slot];
memset(win, 0, sizeof(*win));
win->id = g_next_win_id++;
strncpy(win->title, title ? title : "Window", sizeof(win->title) - 1);
win->width = (w > GUI_MIN_WINDOW_W) ? w : GUI_MIN_WINDOW_W;
win->height = (h > GUI_MIN_WINDOW_H) ? h : GUI_MIN_WINDOW_H;
// Keep new windows fully on screen even if the caller asked otherwise.
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x + win->width > g_work_w) x = g_work_w - win->width;
if (y + win->height > g_work_h) y = g_work_h - win->height;
win->x = (x > 0) ? x : 0;
win->y = (y > 0) ? y : 0;
win->visible = true;
win->resizable = true;
win->active = true;
win->snap = GUI_SNAP_NONE;
g_window_order[g_num_windows++] = win->id;
gui_wm_focus_window(win->id);
// Kick off the grow-open animation: remember the final geometry and centre,
// then start small. gui_wm_update() eases it back to full over ~160 ms.
win->target_w = win->width;
win->target_h = win->height;
win->center_x = win->x + win->width / 2;
win->center_y = win->y + win->height / 2;
win->anim_open_ns = ktime_ns();
win->width = win->target_w * 6 / 10;
win->height = win->target_h * 6 / 10;
win->x = win->center_x - win->width / 2;
win->y = win->center_y - win->height / 2;
if (win->x < 0) win->x = 0;
if (win->y < 0) win->y = 0;
return win;
}
// Advance open animations. Returns true while any window is still animating.
bool gui_wm_update(void) {
bool animating = false;
uint64_t now = ktime_ns();
for (int i = 0; i < g_num_windows; i++) {
gui_window_t* win = gui_wm_get_window(g_window_order[i]);
if (!win || win->anim_open_ns == 0) continue;
uint64_t elapsed_ms = (now - win->anim_open_ns) / 1000000ULL;
if (elapsed_ms >= WM_OPEN_ANIM_MS) {
// Snap to the final geometry and stop animating.
win->width = win->target_w;
win->height = win->target_h;
win->x = win->center_x - win->target_w / 2;
win->y = win->center_y - win->target_h / 2;
win->anim_open_ns = 0;
} else {
animating = true;
int p = (int)(elapsed_ms * 1000 / WM_OPEN_ANIM_MS); // 0..1000
int inv = 1000 - p;
int ease = 1000 - (inv * inv / 1000) * inv / 1000; // cubic ease-out
int scale = 600 + 400 * ease / 1000; // 600..1000 permille
win->width = win->target_w * scale / 1000;
win->height = win->target_h * scale / 1000;
win->x = win->center_x - win->width / 2;
win->y = win->center_y - win->height / 2;
}
// Keep the animating window on screen.
if (win->x < 0) win->x = 0;
if (win->y < 0) win->y = 0;
if (win->x + win->width > g_work_w) win->x = g_work_w - win->width;
if (win->y + win->height > g_work_h) win->y = g_work_h - win->height;
if (win->x < 0) win->x = 0;
if (win->y < 0) win->y = 0;
}
return animating;
}
static int order_index_of(int win_id) {
for (int i = 0; i < g_num_windows; i++) {
if (g_window_order[i] == win_id) return i;
}
return -1;
}
void gui_wm_destroy_window(int win_id) {
int idx = order_index_of(win_id);
if (idx != -1) {
for (int i = idx; i < g_num_windows - 1; i++) {
g_window_order[i] = g_window_order[i + 1];
}
g_num_windows--;
}
for (int i = 0; i < GUI_MAX_WINDOWS; i++) {
if (g_windows[i].id != win_id) continue;
if (g_windows[i].handle_event) {
gui_event_t ev = { .type = GUI_EVENT_CLOSE };
g_windows[i].handle_event(&g_windows[i], &ev);
}
memset(&g_windows[i], 0, sizeof(gui_window_t));
break;
}
if (g_drag_win_id == win_id) g_drag_win_id = -1;
if (g_resize_win_id == win_id) g_resize_win_id = -1;
if (g_num_windows > 0) gui_wm_focus_window(g_window_order[g_num_windows - 1]);
}
void gui_wm_focus_window(int win_id) {
int idx = order_index_of(win_id);
if (idx != -1 && idx != g_num_windows - 1) {
int target = g_window_order[idx];
for (int i = idx; i < g_num_windows - 1; i++) {
g_window_order[i] = g_window_order[i + 1];
}
g_window_order[g_num_windows - 1] = target;
}
for (int i = 0; i < GUI_MAX_WINDOWS; i++) {
if (g_windows[i].id == 0) continue;
bool is_active = (g_windows[i].id == win_id);
if (g_windows[i].active == is_active) continue;
g_windows[i].active = is_active;
if (is_active && g_windows[i].handle_event) {
gui_event_t ev = { .type = GUI_EVENT_FOCUS };
g_windows[i].handle_event(&g_windows[i], &ev);
}
}
}
gui_window_t* gui_wm_get_window(int win_id) {
for (int i = 0; i < GUI_MAX_WINDOWS; i++) {
if (g_windows[i].id == win_id) return &g_windows[i];
}
return NULL;
}
gui_window_t* gui_wm_get_window_at_index(int index) {
if (index < 0 || index >= g_num_windows) return NULL;
return gui_wm_get_window(g_window_order[index]);
}
gui_window_t* gui_wm_get_active_window(void) {
if (g_num_windows == 0) return NULL;
return gui_wm_get_window(g_window_order[g_num_windows - 1]);
}
int gui_wm_get_window_count(void) { return g_num_windows; }
// ---------------------------------------------------------------------------
// Window state transitions
// ---------------------------------------------------------------------------
static void save_geometry(gui_window_t* win) {
if (win->snap != GUI_SNAP_NONE) return; // Already holding a saved rect
win->saved_x = win->x;
win->saved_y = win->y;
win->saved_width = win->width;
win->saved_height = win->height;
}
static void emit_resize(gui_window_t* win) {
if (!win->handle_event) return;
gui_event_t ev = { .type = GUI_EVENT_RESIZE };
win->handle_event(win, &ev);
}
void gui_wm_snap_window(int win_id, gui_snap_t snap) {
gui_window_t* win = gui_wm_get_window(win_id);
if (!win) return;
if (snap == GUI_SNAP_NONE) {
gui_wm_restore_window(win_id);
return;
}
save_geometry(win);
win->snap = snap;
switch (snap) {
case GUI_SNAP_LEFT:
win->x = 0; win->y = 0;
win->width = g_work_w / 2; win->height = g_work_h;
break;
case GUI_SNAP_RIGHT:
win->x = g_work_w / 2; win->y = 0;
win->width = g_work_w - g_work_w / 2; win->height = g_work_h;
break;
default: // GUI_SNAP_MAXIMIZED
win->x = 0; win->y = 0;
win->width = g_work_w; win->height = g_work_h;
break;
}
emit_resize(win);
}
void gui_wm_restore_window(int win_id) {
gui_window_t* win = gui_wm_get_window(win_id);
if (!win) return;
win->minimized = false;
win->visible = true;
if (win->snap != GUI_SNAP_NONE) {
win->x = win->saved_x;
win->y = win->saved_y;
win->width = win->saved_width;
win->height = win->saved_height;
win->snap = GUI_SNAP_NONE;
emit_resize(win);
}
}
void gui_wm_toggle_maximize(int win_id) {
gui_window_t* win = gui_wm_get_window(win_id);
if (!win) return;
if (win->snap == GUI_SNAP_MAXIMIZED) gui_wm_restore_window(win_id);
else gui_wm_snap_window(win_id, GUI_SNAP_MAXIMIZED);
}
void gui_wm_minimize_window(int win_id) {
gui_window_t* win = gui_wm_get_window(win_id);
if (!win) return;
win->minimized = true;
// Focus drops to the next window that is still on screen.
for (int i = g_num_windows - 1; i >= 0; i--) {
gui_window_t* other = gui_wm_get_window(g_window_order[i]);
if (other && other->id != win_id && !other->minimized && other->visible) {
gui_wm_focus_window(other->id);
return;
}
}
win->active = false;
}
void gui_wm_close_active(void) {
gui_window_t* active = gui_wm_get_active_window();
if (active) gui_wm_destroy_window(active->id);
}
void gui_wm_cycle_focus(void) {
if (g_num_windows < 2) return;
// Walk down from the top and promote the first visible window below it.
for (int i = g_num_windows - 2; i >= 0; i--) {
gui_window_t* win = gui_wm_get_window(g_window_order[i]);
if (win && win->visible) {
if (win->minimized) win->minimized = false;
gui_wm_focus_window(win->id);
return;
}
}
}
// After a bulk layout change nothing may hold focus (every window could have
// been minimized). Promote the topmost visible window so the desktop always
// has a keyboard target.
static void ensure_focus(void) {
if (gui_wm_get_active_window() && gui_wm_get_active_window()->active) return;
for (int i = g_num_windows - 1; i >= 0; i--) {
gui_window_t* win = gui_wm_get_window(g_window_order[i]);
if (win && win->visible && !win->minimized) {
gui_wm_focus_window(win->id);
return;
}
}
}
void gui_wm_cascade_windows(void) {
int step = 26;
int n = 0;
for (int i = 0; i < g_num_windows; i++) {
gui_window_t* win = gui_wm_get_window(g_window_order[i]);
if (!win || !win->visible) continue;
win->minimized = false;
win->snap = GUI_SNAP_NONE;
win->x = 20 + n * step;
win->y = 20 + n * step;
if (win->x + win->width > g_work_w) win->x = g_work_w - win->width;
if (win->y + win->height > g_work_h) win->y = g_work_h - win->height;
if (win->x < 0) win->x = 0;
if (win->y < 0) win->y = 0;
emit_resize(win);
n++;
}
ensure_focus();
}
void gui_wm_tile_windows(void) {
int visible = 0;
for (int i = 0; i < g_num_windows; i++) {
gui_window_t* win = gui_wm_get_window(g_window_order[i]);
if (win && win->visible && !win->minimized) visible++;
}
if (visible == 0) return;
// Lay out on the squarest grid that fits every window.
int cols = 1;
while (cols * cols < visible) cols++;
int rows = (visible + cols - 1) / cols;
int cell_w = g_work_w / cols;
int cell_h = g_work_h / rows;
int n = 0;
for (int i = 0; i < g_num_windows; i++) {
gui_window_t* win = gui_wm_get_window(g_window_order[i]);
if (!win || !win->visible || win->minimized) continue;
win->snap = GUI_SNAP_NONE;
win->x = (n % cols) * cell_w;
win->y = (n / cols) * cell_h;
win->width = cell_w - 4;
win->height = cell_h - 4;
if (win->width < GUI_MIN_WINDOW_W) win->width = GUI_MIN_WINDOW_W;
if (win->height < GUI_MIN_WINDOW_H) win->height = GUI_MIN_WINDOW_H;
emit_resize(win);
n++;
}
ensure_focus();
}
// ---------------------------------------------------------------------------
// Hit testing and pointer gestures
// ---------------------------------------------------------------------------
// Title bar buttons run right to left: 0 = close, 1 = maximize, 2 = minimize.
static void button_rect(const gui_window_t* win, int which, int* bx, int* by) {
*by = win->y + (GUI_TITLEBAR_HEIGHT - BTN_SIZE) / 2;
*bx = win->x + win->width - (BTN_SIZE + BTN_GAP) * (which + 1) - 2;
}
static bool point_in(int px, int py, int x, int y, int w, int h) {
return px >= x && px < x + w && py >= y && py < y + h;
}
static bool in_resize_grip(const gui_window_t* win, int mx, int my) {
if (!win->resizable || win->snap != GUI_SNAP_NONE) return false;
return point_in(mx, my,
win->x + win->width - GUI_RESIZE_GRIP,
win->y + win->height - GUI_RESIZE_GRIP,
GUI_RESIZE_GRIP, GUI_RESIZE_GRIP);
}
static void deliver(gui_window_t* win, gui_event_type_t type, int mx, int my,
bool left, bool right) {
if (!win->handle_event) return;
gui_event_t ev = {
.type = type,
.mouse_x = mx,
.mouse_y = my,
.rel_x = mx - win->x,
.rel_y = my - (win->y + GUI_TITLEBAR_HEIGHT),
.btn_left = left,
.btn_right = right
};
win->handle_event(win, &ev);
}
// A titlebar press: returns true if a button consumed it.
static bool handle_titlebar_press(gui_window_t* win, int mx, int my) {
int bx, by;
button_rect(win, 0, &bx, &by);
if (point_in(mx, my, bx, by, BTN_SIZE, BTN_SIZE)) {
gui_wm_destroy_window(win->id);
return true;
}
button_rect(win, 1, &bx, &by);
if (point_in(mx, my, bx, by, BTN_SIZE, BTN_SIZE)) {
gui_wm_toggle_maximize(win->id);
return true;
}
button_rect(win, 2, &bx, &by);
if (point_in(mx, my, bx, by, BTN_SIZE, BTN_SIZE)) {
gui_wm_minimize_window(win->id);
return true;
}
// Anywhere else on the bar starts a drag. Dragging a snapped window pops
// it back to its saved size first, centred under the cursor.
if (win->snap != GUI_SNAP_NONE) {
int grab_ratio = (win->width > 0) ? ((mx - win->x) * 100) / win->width : 50;
gui_wm_restore_window(win->id);
win->x = mx - (win->width * grab_ratio) / 100;
win->y = my - GUI_TITLEBAR_HEIGHT / 2;
if (win->y < 0) win->y = 0;
}
g_drag_win_id = win->id;
win->is_dragging = true;
win->drag_off_x = mx - win->x;
win->drag_off_y = my - win->y;
return true;
}
void gui_wm_sync_button_state(bool btn_left) {
g_prev_btn_left = btn_left;
}
void gui_wm_handle_mouse(int mx, int my, bool btn_left, bool btn_right) {
g_last_mouse_x = mx;
g_last_mouse_y = my;
// --- An in-flight resize owns the pointer until the button is released --
if (g_resize_win_id != -1) {
gui_window_t* win = gui_wm_get_window(g_resize_win_id);
if (!btn_left || !win) {
if (win) { win->is_resizing = false; emit_resize(win); }
g_resize_win_id = -1;
} else {
int nw = g_resize_start_w + (mx - g_resize_start_mx);
int nh = g_resize_start_h + (my - g_resize_start_my);
if (nw < GUI_MIN_WINDOW_W) nw = GUI_MIN_WINDOW_W;
if (nh < GUI_MIN_WINDOW_H) nh = GUI_MIN_WINDOW_H;
if (win->x + nw > g_work_w) nw = g_work_w - win->x;
if (win->y + nh > g_work_h) nh = g_work_h - win->y;
win->width = nw;
win->height = nh;
g_prev_btn_left = btn_left;
return;
}
}
// --- An in-flight drag likewise owns the pointer ------------------------
if (g_drag_win_id != -1) {
gui_window_t* win = gui_wm_get_window(g_drag_win_id);
if (!btn_left || !win) {
if (win) {
win->is_dragging = false;
// Releasing against a screen edge snaps the window there.
if (mx <= SNAP_MARGIN) gui_wm_snap_window(win->id, GUI_SNAP_LEFT);
else if (mx >= g_work_w - SNAP_MARGIN) gui_wm_snap_window(win->id, GUI_SNAP_RIGHT);
else if (my <= SNAP_MARGIN) gui_wm_snap_window(win->id, GUI_SNAP_MAXIMIZED);
}
g_drag_win_id = -1;
} else {
win->x = mx - win->drag_off_x;
win->y = my - win->drag_off_y;
// Keep at least a strip of the title bar reachable.
if (win->y < 0) win->y = 0;
if (win->y > g_work_h - GUI_TITLEBAR_HEIGHT) win->y = g_work_h - GUI_TITLEBAR_HEIGHT;
if (win->x + win->width < 60) win->x = 60 - win->width;
if (win->x > g_work_w - 60) win->x = g_work_w - 60;
g_prev_btn_left = btn_left;
return;
}
}
// --- Press edge: pick a target from the top of the z-order down ---------
if (btn_left && !g_prev_btn_left) {
for (int i = g_num_windows - 1; i >= 0; i--) {
gui_window_t* win = gui_wm_get_window(g_window_order[i]);
if (!win || !win->visible || win->minimized) continue;
if (!point_in(mx, my, win->x, win->y, win->width, win->height)) continue;
gui_wm_focus_window(win->id);
if (in_resize_grip(win, mx, my)) {
g_resize_win_id = win->id;
win->is_resizing = true;
g_resize_start_w = win->width;
g_resize_start_h = win->height;
g_resize_start_mx = mx;
g_resize_start_my = my;
g_prev_btn_left = btn_left;
return;
}
if (my < win->y + GUI_TITLEBAR_HEIGHT) {
handle_titlebar_press(win, mx, my);
g_prev_btn_left = btn_left;
return;
}
deliver(win, GUI_EVENT_MOUSE_DOWN, mx, my, btn_left, btn_right);
g_prev_btn_left = btn_left;
return;
}
g_prev_btn_left = btn_left;
return;
}
// --- Release and move edges go to the focused window --------------------
gui_window_t* active = gui_wm_get_active_window();
if (active && !active->minimized) {
if (!btn_left && g_prev_btn_left) {
deliver(active, GUI_EVENT_MOUSE_UP, mx, my, btn_left, btn_right);
} else {
deliver(active, GUI_EVENT_MOUSE_MOVE, mx, my, btn_left, btn_right);
}
}
g_prev_btn_left = btn_left;
}
void gui_wm_handle_key(uint16_t key) {
// Function keys drive the window manager itself before the app sees them.
switch (key) {
case KEY_F5: gui_wm_cycle_focus(); return;
case KEY_F6: gui_wm_cascade_windows(); return;
case KEY_F7: gui_wm_tile_windows(); return;
case KEY_F8: {
gui_window_t* a = gui_wm_get_active_window();
if (a) gui_wm_toggle_maximize(a->id);
return;
}
case KEY_F9: {
gui_window_t* a = gui_wm_get_active_window();
if (a) gui_wm_minimize_window(a->id);
return;
}
default: break;
}
gui_window_t* active = gui_wm_get_active_window();
if (active && active->handle_event && !active->minimized) {
gui_event_t ev = { .type = GUI_EVENT_KEY_DOWN, .key = key };
active->handle_event(active, &ev);
}
}
// ---------------------------------------------------------------------------
// Compositing
// ---------------------------------------------------------------------------
static void draw_titlebar_buttons(const gui_window_t* win) {
int bx, by;
// Close
button_rect(win, 0, &bx, &by);
bool hot = point_in(g_last_mouse_x, g_last_mouse_y, bx, by, BTN_SIZE, BTN_SIZE);
gui_gfx_fill_rect(bx, by, BTN_SIZE, BTN_SIZE, hot ? 0xFFFF6B6B : GUI_THEME_BTN_CLOSE);
gui_gfx_draw_rect(bx, by, BTN_SIZE, BTN_SIZE, 0xFF991B1B);
gui_gfx_draw_line(bx + 4, by + 4, bx + BTN_SIZE - 5, by + BTN_SIZE - 5, GUI_COLOR_WHITE);
gui_gfx_draw_line(bx + BTN_SIZE - 5, by + 4, bx + 4, by + BTN_SIZE - 5, GUI_COLOR_WHITE);
// Maximize / restore
button_rect(win, 1, &bx, &by);
hot = point_in(g_last_mouse_x, g_last_mouse_y, bx, by, BTN_SIZE, BTN_SIZE);
gui_gfx_fill_rect(bx, by, BTN_SIZE, BTN_SIZE, hot ? 0xFF34D399 : GUI_THEME_BTN_MAXIMIZE);
gui_gfx_draw_rect(bx, by, BTN_SIZE, BTN_SIZE, 0xFF047857);
if (win->snap == GUI_SNAP_MAXIMIZED) {
// Two offset outlines read as "restore down".
gui_gfx_draw_rect(bx + 3, by + 5, 6, 5, GUI_COLOR_WHITE);
gui_gfx_draw_rect(bx + 5, by + 3, 6, 5, GUI_COLOR_WHITE);
} else {
gui_gfx_draw_rect(bx + 4, by + 4, BTN_SIZE - 8, BTN_SIZE - 8, GUI_COLOR_WHITE);
}
// Minimize
button_rect(win, 2, &bx, &by);
hot = point_in(g_last_mouse_x, g_last_mouse_y, bx, by, BTN_SIZE, BTN_SIZE);
gui_gfx_fill_rect(bx, by, BTN_SIZE, BTN_SIZE, hot ? 0xFFFCD34D : GUI_THEME_BTN_MINIMIZE);
gui_gfx_draw_rect(bx, by, BTN_SIZE, BTN_SIZE, 0xFFB45309);
gui_gfx_fill_rect(bx + 4, by + BTN_SIZE - 6, BTN_SIZE - 8, 2, GUI_COLOR_WHITE);
}
static void draw_resize_grip(const gui_window_t* win) {
if (!win->resizable || win->snap != GUI_SNAP_NONE) return;
int gx = win->x + win->width - GUI_RESIZE_GRIP;
int gy = win->y + win->height - GUI_RESIZE_GRIP;
uint32_t col = win->is_resizing ? GUI_THEME_PRIMARY : GUI_THEME_BORDER;
// Three diagonal hatches, the conventional grip affordance.
for (int i = 0; i < 3; i++) {
int off = 3 + i * 3;
gui_gfx_draw_line(gx + off, gy + GUI_RESIZE_GRIP - 2,
gx + GUI_RESIZE_GRIP - 2, gy + off, col);
}
}
void gui_wm_render(void) {
// Bottom-to-top painter's algorithm; the topmost window wins overlaps.
for (int i = 0; i < g_num_windows; i++) {
gui_window_t* win = gui_wm_get_window(g_window_order[i]);
if (!win || !win->visible || win->minimized) continue;
// 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);
// 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);
// Truncate the title so it never runs under the buttons.
int room = win->width - (BTN_SIZE + BTN_GAP) * 3 - 20;
int max_chars = room / 8;
if (max_chars < 1) max_chars = 1;
if (max_chars > 62) max_chars = 62;
char shown[64];
strncpy(shown, win->title, (size_t)max_chars);
shown[max_chars] = '\0';
if ((int)strlen(win->title) > max_chars && max_chars > 3) {
shown[max_chars - 1] = '.';
shown[max_chars - 2] = '.';
}
uint32_t title_fg = win->active ? GUI_THEME_TEXT_MAIN : GUI_THEME_TEXT_MUTED;
gui_gfx_draw_string_16_shadow(win->x + 8, win->y + 4, shown, title_fg, GUI_COLOR_BLACK);
draw_titlebar_buttons(win);
gui_gfx_fill_rect(win->x + 1, win->y + GUI_TITLEBAR_HEIGHT + 1,
win->width - 2, win->height - GUI_TITLEBAR_HEIGHT - 2,
GUI_THEME_BG_SURFACE);
if (win->paint) win->paint(win);
draw_resize_grip(win);
}
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef _ARCH_AARCH64_ARCH_H
#define _ARCH_AARCH64_ARCH_H
#include <stdint.h>
#include <stdbool.h>
typedef struct {
uint64_t r[31]; // X0 - X30
uint64_t sp;
uint64_t pc;
uint64_t pstate;
} __attribute__((packed)) aarch64_context_t;
void aarch64_init(void);
#endif // _ARCH_AARCH64_ARCH_H
+24
View File
@@ -0,0 +1,24 @@
#ifndef _ARCH_AARCH64_CPU_H
#define _ARCH_AARCH64_CPU_H
#include <stdint.h>
#include <stdbool.h>
typedef struct {
uint32_t implementer;
uint32_t variant;
uint32_t architecture;
uint32_t part_num;
uint32_t revision;
char model_name[64];
uint32_t current_el;
uint64_t core_frequency_hz;
} aarch64_cpu_info_t;
void aarch64_cpu_init(void);
const aarch64_cpu_info_t* aarch64_get_cpu_info(void);
uint32_t aarch64_get_current_el(void);
uint64_t aarch64_get_midr(void);
uint64_t aarch64_get_mpidr(void);
#endif // _ARCH_AARCH64_CPU_H
+34
View File
@@ -0,0 +1,34 @@
#ifndef _ARCH_AARCH64_GIC_H
#define _ARCH_AARCH64_GIC_H
#include <stdint.h>
#include <stdbool.h>
#define GIC_DIST_BASE 0x08000000
#define GIC_CPU_BASE 0x08010000
// GIC Distributor Registers
#define GICD_CTLR 0x000
#define GICD_TYPER 0x004
#define GICD_ISENABLER(n) (0x100 + ((n) * 4))
#define GICD_ICENABLER(n) (0x180 + ((n) * 4))
#define GICD_IPRIORITYR(n) (0x400 + ((n) * 4))
#define GICD_ITARGETSR(n) (0x800 + ((n) * 4))
#define GICD_ICFGR(n) (0xC00 + ((n) * 4))
// GIC CPU Interface Registers
#define GICC_CTLR 0x0000
#define GICC_PMR 0x0004
#define GICC_BPR 0x0008
#define GICC_IAR 0x000C
#define GICC_EOIR 0x0010
typedef void (*gic_irq_handler_t)(uint32_t irq);
void gic_init(void);
void gic_enable_irq(uint32_t irq);
void gic_disable_irq(uint32_t irq);
void gic_register_handler(uint32_t irq, gic_irq_handler_t handler);
void gic_handle_irq(void);
#endif // _ARCH_AARCH64_GIC_H
+95
View File
@@ -0,0 +1,95 @@
#ifndef _ARCH_AARCH64_IO_H
#define _ARCH_AARCH64_IO_H
#include <stdint.h>
static inline void mmio_write8(uintptr_t reg, uint8_t val) {
*(volatile uint8_t*)reg = val;
}
static inline uint8_t mmio_read8(uintptr_t reg) {
return *(volatile uint8_t*)reg;
}
static inline void mmio_write16(uintptr_t reg, uint16_t val) {
*(volatile uint16_t*)reg = val;
}
static inline uint16_t mmio_read16(uintptr_t reg) {
return *(volatile uint16_t*)reg;
}
static inline void mmio_write32(uintptr_t reg, uint32_t val) {
*(volatile uint32_t*)reg = val;
}
static inline uint32_t mmio_read32(uintptr_t reg) {
return *(volatile uint32_t*)reg;
}
static inline void mmio_write64(uintptr_t reg, uint64_t val) {
*(volatile uint64_t*)reg = val;
}
static inline uint64_t mmio_read64(uintptr_t reg) {
return *(volatile uint64_t*)reg;
}
// Memory barriers
static inline void dsb(void) {
__asm__ volatile("dsb sy" ::: "memory");
}
static inline void dmb(void) {
__asm__ volatile("dmb sy" ::: "memory");
}
static inline void isb(void) {
__asm__ volatile("isb" ::: "memory");
}
// x86 I/O Port Emulation / Stubs for Portable Device Drivers
static inline void outb(uint16_t port, uint8_t val) {
(void)port; (void)val;
}
static inline uint8_t inb(uint16_t port) {
(void)port;
return 0;
}
static inline void outw(uint16_t port, uint16_t val) {
(void)port; (void)val;
}
static inline uint16_t inw(uint16_t port) {
(void)port;
return 0;
}
static inline void outl(uint16_t port, uint32_t val) {
(void)port; (void)val;
}
static inline uint32_t inl(uint16_t port) {
(void)port;
return 0;
}
static inline void io_wait(void) {
__asm__ volatile("nop");
}
static inline void cli(void) {
__asm__ volatile("msr daifset, #2" ::: "memory");
}
static inline void sti(void) {
__asm__ volatile("msr daifclr, #2" ::: "memory");
}
static inline void hlt(void) {
__asm__ volatile("wfi");
}
#endif // _ARCH_AARCH64_IO_H
+39
View File
@@ -0,0 +1,39 @@
#ifndef _ARCH_AARCH64_MMU_H
#define _ARCH_AARCH64_MMU_H
#include <stdint.h>
#include <stdbool.h>
#define MMU_PAGE_SIZE 4096
// Memory Types / Attributes index for MAIR_EL1
#define MT_DEVICE_nGnRnE 0
#define MT_NORMAL_NC 1
#define MT_NORMAL_WB 2
#define MAIR_DEVICE_nGnRnE 0x00
#define MAIR_NORMAL_NC 0x44
#define MAIR_NORMAL_WB 0xFF
#define MAIR_VAL ((MAIR_DEVICE_nGnRnE << (MT_DEVICE_nGnRnE * 8)) | \
(MAIR_NORMAL_NC << (MT_NORMAL_NC * 8)) | \
(MAIR_NORMAL_WB << (MT_NORMAL_WB * 8)))
// Page descriptor bits
#define PTE_VALID (1ULL << 0)
#define PTE_TABLE (1ULL << 1)
#define PTE_BLOCK (0ULL << 1)
#define PTE_USER (1ULL << 6)
#define PTE_RO (1ULL << 7)
#define PTE_SH_INNER (3ULL << 8)
#define PTE_AF (1ULL << 10)
#define PTE_NG (1ULL << 11)
#define PTE_PXN (1ULL << 53)
#define PTE_UXN (1ULL << 54)
#define PTE_ATTRINDX(x) (((uint64_t)(x)) << 2)
void aarch64_mmu_init(void);
void aarch64_mmu_map_2mb(uint64_t virt_addr, uint64_t phys_addr, uint32_t mem_type);
#endif // _ARCH_AARCH64_MMU_H
+13
View File
@@ -0,0 +1,13 @@
#ifndef _ARCH_AARCH64_TIMER_H
#define _ARCH_AARCH64_TIMER_H
#include <stdint.h>
#define ARCH_TIMER_VIRT_IRQ 27
void aarch64_timer_init(uint32_t freq_hz);
uint64_t aarch64_timer_get_ticks(void);
uint64_t aarch64_timer_get_freq(void);
void aarch64_timer_handle_irq(void);
#endif // _ARCH_AARCH64_TIMER_H
+34
View File
@@ -0,0 +1,34 @@
#ifndef _ARCH_AARCH64_UART_H
#define _ARCH_AARCH64_UART_H
#include <stdint.h>
#include <stddef.h>
#define PL011_UART0_BASE 0x09000000
// PL011 Registers
#define UARTDR 0x000
#define UARTRSR 0x004
#define UARTFR 0x018
#define UARTILPR 0x020
#define UARTIBRD 0x024
#define UARTFBRD 0x028
#define UARTLCR 0x02C
#define UARTCR 0x030
#define UARTIFLS 0x034
#define UARTIMSC 0x038
#define UARTRIS 0x03C
#define UARTMIS 0x040
#define UARTICR 0x044
// Flag register bits
#define UARTFR_TXFF (1 << 5)
#define UARTFR_RXFE (1 << 4)
void uart_pl011_init(void);
void uart_pl011_putc(char c);
void uart_pl011_puts(const char* str);
char uart_pl011_getc(void);
int uart_pl011_has_data(void);
#endif // _ARCH_AARCH64_UART_H
+170
View File
@@ -0,0 +1,170 @@
#ifndef _ARCH_ARCH_H
#define _ARCH_ARCH_H
#include <stdint.h>
#include <stdbool.h>
#if defined(__x86_64__)
#define ARCH_NAME "x86_64"
#define ARCH_IS_64BIT 1
#include <arch/x86_64/gdt.h>
#include <arch/x86_64/idt.h>
#include <arch/x86_64/isr.h>
#include <arch/x86_64/pic.h>
#include <arch/x86_64/pit.h>
#include <arch/x86_64/cpuid.h>
#include <arch/x86_64/paging.h>
#include <arch/x86_64/io.h>
static inline void arch_enable_interrupts(void) {
__asm__ volatile("sti");
}
static inline void arch_disable_interrupts(void) {
__asm__ volatile("cli");
}
/* Save the current interrupt-enable state and disable interrupts; restore puts
* it back exactly. Used to make a critical section atomic against IRQ handlers
* (e.g. the preemptive scheduler) without unconditionally re-enabling. */
static inline unsigned long arch_irq_save(void) {
unsigned long flags;
__asm__ volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory");
return flags;
}
static inline void arch_irq_restore(unsigned long flags) {
__asm__ volatile("push %0; popfq" :: "r"(flags) : "memory", "cc");
}
static inline void arch_halt(void) {
__asm__ volatile("hlt");
}
static inline void arch_wait_for_interrupt(void) {
__asm__ volatile("sti; hlt");
}
static inline uint64_t arch_get_ticks(void) {
return pit_get_ticks();
}
static inline void arch_sleep(uint32_t ms) {
pit_sleep(ms);
}
#elif defined(__aarch64__) || defined(__arm64__)
#define ARCH_NAME "aarch64"
#define ARCH_IS_64BIT 1
#include <arch/aarch64/arch.h>
#include <arch/aarch64/cpu.h>
#include <arch/aarch64/gic.h>
#include <arch/aarch64/timer.h>
#include <arch/aarch64/mmu.h>
#include <arch/aarch64/uart.h>
#include <arch/aarch64/io.h>
static inline void arch_enable_interrupts(void) {
__asm__ volatile("msr daifclr, #2" ::: "memory");
}
static inline void arch_disable_interrupts(void) {
__asm__ volatile("msr daifset, #2" ::: "memory");
}
static inline unsigned long arch_irq_save(void) {
unsigned long flags;
__asm__ volatile("mrs %0, daif; msr daifset, #2" : "=r"(flags) :: "memory");
return flags;
}
static inline void arch_irq_restore(unsigned long flags) {
__asm__ volatile("msr daif, %0" :: "r"(flags) : "memory");
}
static inline void arch_halt(void) {
__asm__ volatile("wfi");
}
static inline void arch_wait_for_interrupt(void) {
__asm__ volatile("msr daifclr, #2; wfi" ::: "memory");
}
static inline uint64_t arch_get_ticks(void) {
return aarch64_timer_get_ticks();
}
static inline void arch_sleep(uint32_t ms) {
uint64_t count = (ms > 10) ? (ms / 10) : 1;
uint64_t target = aarch64_timer_get_ticks() + count;
while (aarch64_timer_get_ticks() < target) {
__asm__ volatile("wfi");
}
}
uint64_t pit_get_ticks(void);
void pit_sleep(uint32_t ms);
#elif defined(__arm__) || defined(__armv8i__)
#define ARCH_NAME "armv8i"
#define ARCH_IS_64BIT 0
#include <arch/armv8i/arch.h>
#include <arch/armv8i/cpu.h>
#include <arch/armv8i/gic.h>
#include <arch/armv8i/timer.h>
#include <arch/armv8i/mmu.h>
#include <arch/armv8i/uart.h>
#include <arch/armv8i/io.h>
static inline void arch_enable_interrupts(void) {
__asm__ volatile("cpsie i" ::: "memory");
}
static inline void arch_disable_interrupts(void) {
__asm__ volatile("cpsid i" ::: "memory");
}
static inline unsigned long arch_irq_save(void) {
unsigned long flags;
__asm__ volatile("mrs %0, cpsr; cpsid i" : "=r"(flags) :: "memory");
return flags;
}
static inline void arch_irq_restore(unsigned long flags) {
__asm__ volatile("msr cpsr_c, %0" :: "r"(flags) : "memory", "cc");
}
static inline void arch_halt(void) {
__asm__ volatile("wfi");
}
static inline void arch_wait_for_interrupt(void) {
__asm__ volatile("cpsie i; wfi" ::: "memory");
}
static inline uint64_t arch_get_ticks(void) {
return armv8i_timer_get_ticks();
}
static inline void arch_sleep(uint32_t ms) {
uint64_t count = (ms > 10) ? (ms / 10) : 1;
uint64_t target = armv8i_timer_get_ticks() + count;
while (armv8i_timer_get_ticks() < target) {
__asm__ volatile("wfi");
}
}
uint64_t pit_get_ticks(void);
void pit_sleep(uint32_t ms);
#else
#error "Unsupported Target CPU Architecture!"
#endif
// Generic Architecture API
void arch_early_init(void);
void arch_init(void);
const char* arch_get_name(void);
uint32_t arch_get_cpu_id(void);
#endif // _ARCH_ARCH_H
+23
View File
@@ -0,0 +1,23 @@
#ifndef _ARCH_ARMV8I_ARCH_H
#define _ARCH_ARMV8I_ARCH_H
#include <stdint.h>
#include <stdbool.h>
#define ARMV8I_PAGE_SIZE 4096
// Mode bits in CPSR
#define CPSR_MODE_USR 0x10
#define CPSR_MODE_FIQ 0x11
#define CPSR_MODE_IRQ 0x12
#define CPSR_MODE_SVC 0x13
#define CPSR_MODE_MON 0x16
#define CPSR_MODE_ABT 0x17
#define CPSR_MODE_HYP 0x1A
#define CPSR_MODE_UND 0x1B
#define CPSR_MODE_SYS 0x1F
#define CPSR_IRQ_DISABLE (1 << 7)
#define CPSR_FIQ_DISABLE (1 << 6)
#endif // _ARCH_ARMV8I_ARCH_H
+21
View File
@@ -0,0 +1,21 @@
#ifndef _ARCH_ARMV8I_CPU_H
#define _ARCH_ARMV8I_CPU_H
#include <stdint.h>
#include <stdbool.h>
typedef struct {
uint32_t midr;
uint32_t mpidr;
uint32_t implementer;
uint32_t part_number;
uint32_t architecture;
char model_name[64];
} armv8i_cpu_info_t;
void armv8i_cpu_init(void);
const armv8i_cpu_info_t* armv8i_get_cpu_info(void);
uint32_t armv8i_get_midr(void);
uint32_t armv8i_get_mpidr(void);
#endif // _ARCH_ARMV8I_CPU_H
+37
View File
@@ -0,0 +1,37 @@
#ifndef _ARCH_ARMV8I_GIC_H
#define _ARCH_ARMV8I_GIC_H
#include <stdint.h>
#include <stdbool.h>
#define GICD_BASE 0x08000000
#define GICC_BASE 0x08010000
// Distributor Registers
#define GICD_CTLR (GICD_BASE + 0x000)
#define GICD_TYPER (GICD_BASE + 0x004)
#define GICD_IIDR (GICD_BASE + 0x008)
#define GICD_ISENABLER(n) (GICD_BASE + 0x100 + ((n) * 4))
#define GICD_ICENABLER(n) (GICD_BASE + 0x180 + ((n) * 4))
#define GICD_ISPENDR(n) (GICD_BASE + 0x200 + ((n) * 4))
#define GICD_ICPENDR(n) (GICD_BASE + 0x280 + ((n) * 4))
#define GICD_IPRIORITYR(n) (GICD_BASE + 0x400 + ((n) * 4))
#define GICD_ITARGETSR(n) (GICD_BASE + 0x800 + ((n) * 4))
#define GICD_ICFGR(n) (GICD_BASE + 0xC00 + ((n) * 4))
// CPU Interface Registers
#define GICC_CTLR (GICC_BASE + 0x000)
#define GICC_PMR (GICC_BASE + 0x004)
#define GICC_BPR (GICC_BASE + 0x008)
#define GICC_IAR (GICC_BASE + 0x00C)
#define GICC_EOIR (GICC_BASE + 0x010)
typedef void (*gic_irq_handler_t)(uint32_t irq);
void gic_init(void);
void gic_enable_irq(uint32_t irq);
void gic_disable_irq(uint32_t irq);
void gic_set_priority(uint32_t irq, uint8_t prio);
void gic_register_handler(uint32_t irq, gic_irq_handler_t handler);
#endif // _ARCH_ARMV8I_GIC_H
+75
View File
@@ -0,0 +1,75 @@
#ifndef _ARCH_ARMV8I_IO_H
#define _ARCH_ARMV8I_IO_H
#include <stdint.h>
static inline void mmio_write8(uintptr_t reg, uint8_t val) {
*(volatile uint8_t*)reg = val;
}
static inline uint8_t mmio_read8(uintptr_t reg) {
return *(volatile uint8_t*)reg;
}
static inline void mmio_write16(uintptr_t reg, uint16_t val) {
*(volatile uint16_t*)reg = val;
}
static inline uint16_t mmio_read16(uintptr_t reg) {
return *(volatile uint16_t*)reg;
}
static inline void mmio_write32(uintptr_t reg, uint32_t val) {
*(volatile uint32_t*)reg = val;
}
static inline uint32_t mmio_read32(uintptr_t reg) {
return *(volatile uint32_t*)reg;
}
static inline void mmio_write64(uintptr_t reg, uint64_t val) {
*(volatile uint32_t*)reg = (uint32_t)(val & 0xFFFFFFFF);
*(volatile uint32_t*)(reg + 4) = (uint32_t)(val >> 32);
}
static inline uint64_t mmio_read64(uintptr_t reg) {
uint32_t low = *(volatile uint32_t*)reg;
uint32_t high = *(volatile uint32_t*)(reg + 4);
return ((uint64_t)high << 32) | low;
}
// Memory barriers
static inline void dsb(void) {
__asm__ volatile("dsb" ::: "memory");
}
static inline void dmb(void) {
__asm__ volatile("dmb" ::: "memory");
}
static inline void isb(void) {
__asm__ volatile("isb" ::: "memory");
}
// x86 Port I/O Stubs
static inline void outb(uint16_t port, uint8_t val) { (void)port; (void)val; }
static inline uint8_t inb(uint16_t port) { (void)port; return 0; }
static inline void outw(uint16_t port, uint16_t val) { (void)port; (void)val; }
static inline uint16_t inw(uint16_t port) { (void)port; return 0; }
static inline void outl(uint16_t port, uint32_t val) { (void)port; (void)val; }
static inline uint32_t inl(uint16_t port) { (void)port; return 0; }
static inline void io_wait(void) { __asm__ volatile("nop"); }
static inline void cli(void) {
__asm__ volatile("cpsid i" ::: "memory");
}
static inline void sti(void) {
__asm__ volatile("cpsie i" ::: "memory");
}
static inline void hlt(void) {
__asm__ volatile("wfi");
}
#endif // _ARCH_ARMV8I_IO_H
+20
View File
@@ -0,0 +1,20 @@
#ifndef _ARCH_ARMV8I_MMU_H
#define _ARCH_ARMV8I_MMU_H
#include <stdint.h>
// VMSAv7 1MB Section Descriptor Flags
#define SECTION_TYPE_MASK (3 << 0)
#define SECTION_TYPE_FAULT (0 << 0)
#define SECTION_TYPE_SECTION (2 << 0)
#define SECTION_BUFFERABLE (1 << 2)
#define SECTION_CACHEABLE (1 << 3)
#define SECTION_AP_RW_ALL (3 << 10)
#define SECTION_TEX_DEVICE (0 << 12)
#define SECTION_TEX_NORMAL (1 << 12)
#define SECTION_SHAREABLE (1 << 16)
#define SECTION_NON_GLOBAL (1 << 17)
void armv8i_mmu_init(void);
#endif // _ARCH_ARMV8I_MMU_H
+13
View File
@@ -0,0 +1,13 @@
#ifndef _ARCH_ARMV8I_TIMER_H
#define _ARCH_ARMV8I_TIMER_H
#include <stdint.h>
#define ARMV8I_TIMER_VIRT_IRQ 27
void armv8i_timer_init(uint32_t freq_hz);
uint64_t armv8i_timer_get_ticks(void);
uint64_t armv8i_timer_get_frequency(void);
void armv8i_timer_handle_irq(void);
#endif // _ARCH_ARMV8I_TIMER_H
+24
View File
@@ -0,0 +1,24 @@
#ifndef _ARCH_ARMV8I_UART_H
#define _ARCH_ARMV8I_UART_H
#include <stdint.h>
#define PL011_UART0_BASE 0x09000000
#define UARTDR 0x000
#define UARTRSR 0x004
#define UARTFR 0x018
#define UARTCR 0x030
#define UARTIMSC 0x038
#define UARTICR 0x044
#define UARTFR_TXFF (1 << 5)
#define UARTFR_RXFE (1 << 4)
void uart_pl011_init(void);
void uart_pl011_putc(char c);
void uart_pl011_puts(const char* str);
char uart_pl011_getc(void);
int uart_pl011_has_data(void);
#endif // _ARCH_ARMV8I_UART_H
+8
View File
@@ -25,9 +25,17 @@ typedef struct {
} cpu_info_t;
static inline void cpuid(uint32_t leaf, uint32_t* eax, uint32_t* ebx, uint32_t* ecx, uint32_t* edx) {
#if defined(__x86_64__)
__asm__ volatile("cpuid"
: "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
: "a"(leaf), "c"(0));
#else
(void)leaf;
if (eax) *eax = 0;
if (ebx) *ebx = 0;
if (ecx) *ecx = 0;
if (edx) *edx = 0;
#endif
}
void cpuid_detect(cpu_info_t* info);
+18
View File
@@ -3,6 +3,8 @@
#include <stdint.h>
#if defined(__x86_64__)
static inline void outb(uint16_t port, uint8_t val) {
__asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
}
@@ -49,4 +51,20 @@ static inline void hlt(void) {
__asm__ volatile ("hlt");
}
#else
// Portable Stubs for non-x86 architectures
static inline void outb(uint16_t port, uint8_t val) { (void)port; (void)val; }
static inline uint8_t inb(uint16_t port) { (void)port; return 0; }
static inline void outw(uint16_t port, uint16_t val) { (void)port; (void)val; }
static inline uint16_t inw(uint16_t port) { (void)port; return 0; }
static inline void outl(uint16_t port, uint32_t val) { (void)port; (void)val; }
static inline uint32_t inl(uint16_t port) { (void)port; return 0; }
static inline void io_wait(void) { }
static inline void cli(void) { __asm__ volatile("msr daifset, #2" ::: "memory"); }
static inline void sti(void) { __asm__ volatile("msr daifclr, #2" ::: "memory"); }
static inline void hlt(void) { __asm__ volatile("wfi"); }
#endif
#endif // _ARCH_X86_64_IO_H
+48
View File
@@ -0,0 +1,48 @@
/* Automatically generated by SUB-OS Kconfig. Do not edit. */
#ifndef _AUTOCONF_H
#define _AUTOCONF_H
#define CONFIG_ARCH "x86_64"
#define CONFIG_ARCH_X86_64 1
#define CONFIG_BPF_VM 1
#define CONFIG_DRV_AHCI 1
#define CONFIG_DRV_ATA 1
#define CONFIG_DRV_BOCHS_VBE 1
#define CONFIG_DRV_CANVAS_2D 1
#define CONFIG_DRV_E1000 1
#define CONFIG_DRV_HWMON 1
#define CONFIG_DRV_NVME 1
#define CONFIG_DRV_PTY 1
#define CONFIG_DRV_RAMDISK 1
#define CONFIG_DRV_RTL8139 1
#define CONFIG_DRV_SOUND_AC97 1
#define CONFIG_DRV_SOUND_HDA 1
#define CONFIG_DRV_USB_XHCI 1
#define CONFIG_DRV_VIRTIO_BLK 1
#define CONFIG_DRV_VIRTIO_NET 1
#define CONFIG_DRV_VIRTIO_RNG 1
#define CONFIG_FS_DEVFS 1
#define CONFIG_FS_EXT2 1
#define CONFIG_FS_FAT32 1
#define CONFIG_FS_PROCFS 1
#define CONFIG_FS_RAMFS 1
#define CONFIG_FS_SYSFS 1
#define CONFIG_FS_VFS 1
#define CONFIG_IO_URING 1
#define CONFIG_MM_PMM 1
#define CONFIG_MM_SLAB 1
#define CONFIG_MM_VMA 1
#define CONFIG_NET_FILTER 1
#define CONFIG_NET_HTTPD 1
#define CONFIG_NET_SSHD 1
#define CONFIG_NET_STACK 1
#define CONFIG_OPTIMIZATION "-O2"
#define CONFIG_PREEMPT 1
#define CONFIG_SECURITY_KEYRING 1
#define CONFIG_SECURITY_LSM 1
#define CONFIG_SMP 1
#define CONFIG_SUBOS_HOSTNAME "sub-os"
#define CONFIG_SUBOS_VERSION "0.2.0-lts"
#define CONFIG_USERLAND_LAZYBOX 1
#endif /* _AUTOCONF_H */
+30
View File
@@ -46,6 +46,36 @@ static inline void sha256(const void* data, size_t len, uint8_t hash[32]) {
sha256_final(hash, &ctx);
}
// X25519 (Curve25519 ECDH), RFC 7748.
void x25519_base(uint8_t out_pub[32], const uint8_t priv[32]);
void x25519(uint8_t out_shared[32], const uint8_t priv[32], const uint8_t peer_pub[32]);
// ChaCha20-Poly1305 AEAD, RFC 8439.
void chacha20_xor(uint8_t* dst, const uint8_t* src, size_t len,
const uint8_t key[32], uint32_t counter, const uint8_t nonce[12]);
void chacha20poly1305_encrypt(uint8_t* ct, uint8_t tag[16],
const uint8_t key[32], const uint8_t nonce[12],
const uint8_t* aad, size_t aad_len,
const uint8_t* pt, size_t pt_len);
bool chacha20poly1305_decrypt(uint8_t* pt, const uint8_t key[32],
const uint8_t nonce[12],
const uint8_t* aad, size_t aad_len,
const uint8_t* ct, size_t ct_len,
const uint8_t tag[16]);
// HMAC-SHA256 / HKDF (RFC 2104, 5869) and the TLS 1.3 key-schedule helpers.
void hmac_sha256(const uint8_t* key, size_t key_len,
const uint8_t* msg, size_t msg_len, uint8_t out[32]);
void hkdf_extract(const uint8_t* salt, size_t salt_len,
const uint8_t* ikm, size_t ikm_len, uint8_t prk[32]);
void hkdf_expand(const uint8_t prk[32], const uint8_t* info, size_t info_len,
uint8_t* out, size_t out_len);
void tls13_expand_label(const uint8_t secret[32], const char* label,
const uint8_t* context, size_t context_len,
uint8_t* out, size_t out_len);
void tls13_derive_secret(const uint8_t secret[32], const char* label,
const uint8_t transcript_hash[32], uint8_t out[32]);
// PRNG (Cryptographic Pseudo-Random Number Generator)
void prng_seed(uint64_t seed1, uint64_t seed2);
uint64_t prng_rand64(void);
+6
View File
@@ -36,6 +36,12 @@ typedef struct {
bool bochs_vbe_init(void);
bool bochs_vbe_is_available(void);
int bochs_vbe_set_mode(uint32_t width, uint32_t height, uint32_t bpp);
// Leave graphics and hand the adapter back to VGA text mode. Disabling the
// DISPI interface restores the mode the BIOS left behind, which is what makes
// the 80x25 console visible again after a desktop session ends.
void bochs_vbe_disable(void);
bool bochs_vbe_is_enabled(void);
const bochs_vbe_info_t* bochs_vbe_get_info(void);
#endif // _DRIVERS_BOCHS_H
+32
View File
@@ -0,0 +1,32 @@
#ifndef _DRIVERS_CPUFREQ_H
#define _DRIVERS_CPUFREQ_H
#include <stdint.h>
#include <stdbool.h>
typedef enum {
CPUFREQ_GOV_PERFORMANCE,
CPUFREQ_GOV_POWERSAVE,
CPUFREQ_GOV_ONDEMAND,
CPUFREQ_GOV_CONSERVATIVE,
CPUFREQ_GOV_SCHEDUTIL,
} cpufreq_gov_type_t;
typedef struct {
uint32_t current_khz;
uint32_t min_khz;
uint32_t max_khz;
uint32_t voltage_mv;
cpufreq_gov_type_t governor;
uint64_t transitions;
} cpufreq_stats_t;
void cpufreq_init(void);
int cpufreq_set_governor(cpufreq_gov_type_t gov);
int cpufreq_set_governor_by_name(const char* name);
int cpufreq_set_frequency(uint32_t target_khz);
cpufreq_stats_t cpufreq_get_stats(void);
void cpufreq_update_load(uint32_t cpu_load_percent);
void cpufreq_dump_info(void);
#endif // _DRIVERS_CPUFREQ_H
+21
View File
@@ -0,0 +1,21 @@
#ifndef _DRIVERS_E1000E_H
#define _DRIVERS_E1000E_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#define E1000E_VENDOR_INTEL 0x8086
#define E1000E_DEV_82574L 0x10D3
#define E1000E_DEV_82567LM 0x10F5
#define E1000E_DEV_I217 0x153A
#define E1000E_DEV_I219 0x156F
void e1000e_init(void);
int e1000e_send(const void* packet, uint16_t length);
int e1000e_poll(void* buffer, uint16_t max_len);
void e1000e_get_mac(uint8_t* out_mac);
bool e1000e_is_online(void);
void e1000e_dump_stats(void);
#endif // _DRIVERS_E1000E_H
+5
View File
@@ -16,6 +16,11 @@ typedef struct {
void fb_init(void);
const fb_info_t* fb_get_info(void);
void fb_set_hardware_lfb(uint32_t* lfb_addr, uint32_t width, uint32_t height, uint8_t bpp);
// Mark the framebuffer live or dormant. Drawing routines no-op while dormant,
// which keeps stray writes out of video memory after a return to text mode.
void fb_set_active(bool active);
void fb_put_pixel(uint32_t x, uint32_t y, uint32_t color);
void fb_draw_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t color);
void fb_clear(uint32_t color);
Loaded 100 of 267 files, more files were not shown because too many files have changed in this diff. Show more