From ed81f9a07aa503c9951a49db992f9874aef61055 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Mon, 26 Oct 2026 09:35:54 +0100 Subject: [PATCH] Schcom is gone; the desktop path is Xorg now, Wayland groundwork next SchOS will not write its own compositor. projects/schcom, its test and its make targets are deleted. roadmap.md section 5 is now the road to Wayland (udev/eudev, a /sys/devices tree and uevent netlink, libinput and libseat, a DRM device via the GPU plan) and 5b is the boot manager rework (Niveus/skeuomorphic UEFI GUI). XLibre, Xorg or a Wayland compositor from Alpine, whichever suits; Xorg for now. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao --- .gitignore | 2 - Makefile | 13 +- README.md | 1 - docs/Website/index.html | 1 - docs/architecture/README.md | 1 - docs/architecture/desktop.md | 5 +- docs/architecture/graphics.md | 2 +- docs/guides/testing.md | 8 +- docs/plan/1.0.md | 6 +- docs/plan/README.md | 14 +- docs/plan/roadmap.md | 50 ++- docs/plan/schapi.md | 4 +- projects/README.md | 17 +- projects/schcom/Makefile | 32 -- projects/schcom/README.md | 110 ------ projects/schcom/proto.h | 128 ------ projects/schcom/schcom | Bin 45416 -> 0 bytes projects/schcom/schcom-demo | Bin 40928 -> 0 bytes projects/schcom/schcom-demo.c | 149 ------- projects/schcom/schcom.c | 715 ---------------------------------- tools/posix/schcom_test.sh | 154 -------- 21 files changed, 59 insertions(+), 1353 deletions(-) delete mode 100644 projects/schcom/Makefile delete mode 100644 projects/schcom/README.md delete mode 100644 projects/schcom/proto.h delete mode 100644 projects/schcom/schcom delete mode 100644 projects/schcom/schcom-demo delete mode 100644 projects/schcom/schcom-demo.c delete mode 100644 projects/schcom/schcom.c delete mode 100644 tools/posix/schcom_test.sh diff --git a/.gitignore b/.gitignore index 8b12f57..683ea4e 100644 --- a/.gitignore +++ b/.gitignore @@ -36,8 +36,6 @@ dist/ # Build output under projects/ -- these are programs that run on SchOS, and # their binaries were tracked by accident. -projects/schcom/schcom -projects/schcom/schcom-demo # A built ISO dropped next to the site for download.html. 62 MB; not source. docs/Website/releases/ diff --git a/Makefile b/Makefile index 9504c30..70dc3e3 100644 --- a/Makefile +++ b/Makefile @@ -779,7 +779,7 @@ ports: # # Staged into $(SYSROOT) so the next image build picks them up; run # `make projects && make` to get them onto a disk. -PROJECTS ?= schcom +PROJECTS ?= projects: @test -d $(MUSLPREFIX)/include || { \ @@ -1624,15 +1624,6 @@ $(BUILD)/fs.img: $(MKFS) $(BUILD)/sysroot.stamp # it gets worse -- because stage2 loads the kernel by PIO one sector at a time, # and under KVM every one of those port accesses is a VM exit, where under TCG # it is cheap. -# Schcom, end to end, checked from outside the machine. -# -# Boots, starts the compositor and a client, and then screendumps the -# framebuffer and asserts the pixels on the host. That last part is the whole -# value: the compositor saying "I composited it" proves nothing, and a -# screenshot nobody inspects proves less. Needs `make projects` first. -schcom-test: esp $(BUILD)/schfs.img - sh tools/posix/schcom_test.sh - # The firmware and the disks, in one place. Every run target below boots the # same way, because there is only one way to boot now: OVMF, the staged ESP as # the first disk, the SchFS volume as the second. Keeping the argument list in @@ -1829,7 +1820,7 @@ qrtest: $(QRTEST) qrcheck: $(QRTEST) $(PYTHON) tools/python/qrcheck.py -.PHONY: disk all kernel ramroot cmd ports projects sysroot-install qemu qemu-console qemu-kvm schcom-test \ +.PHONY: disk all kernel ramroot cmd ports projects sysroot-install qemu qemu-console qemu-kvm \ schfs-test schfs-objtest schfs-image-test schfs-check kmem-test mkschfs \ schfs-fsck crashshot qemu-schfs iso qemu-iso clean distclean \ logo qrtest qrcheck crashqr usb-ids pci-ids hwdata ovmf esp uefi diff --git a/README.md b/README.md index 14fc3d0..7150da7 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,6 @@ features. It boots from a USB stick on three different real machines. | Image format | **SCFF** | The SchOS Compressed File Format the live root ships in | | Packages | **apk** | Alpine's package manager manages the root; SchOS itself is the package `schos-base` | | Desktop | **Xorg + Window Maker** | Alpine's, on the framebuffer | -| Compositor | **Schcom** | SchOS's own compositor — v0 runs; the installer's GUI first, a desktop (**SchDE**) later | | Crash screen | **PSOGM / GMMOS** | The Purple Screen of Guru Meditation, and the micro OS that runs under it | | API | **SchAPI** | The long-term native interface — *designed, not built* | diff --git a/docs/Website/index.html b/docs/Website/index.html index 9897695..4d5e6f9 100644 --- a/docs/Website/index.html +++ b/docs/Website/index.html @@ -405,7 +405,6 @@ Xorg on the framebuffer with Window Maker, xterm and a few X clients; NetSurf for the web. No acceleration yet — the GPU is a plan, and the plan is Linux's own amdgpu through LDLS. - Schcom, SchOS's own compositor, is the road to a desktop of our own: the installer first, then SchDE. diff --git a/docs/architecture/README.md b/docs/architecture/README.md index a861df7..025fa1f 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -16,7 +16,6 @@ its distribution. | Kernel | **SchIFT** | Multi-core x86-64 kernel with a modular VFS | | Filesystem | **SchFS** | Transactional, copy-on-write, the root filesystem | | API | **SchAPI** | The native programming interface — *designed, not built* | -| Compositor | **Schcom** | SchOS's own compositor — v0 runs ([../plan/roadmap.md](../plan/roadmap.md) §5) | | Packages | **apk** | Alpine's package manager manages the root; SchOS itself is the package `schos-base` | Twelve virtual terminals sit under all of it -- `/dev/tty1`..`tty12`, Ctrl+Alt+F1..F12 diff --git a/docs/architecture/desktop.md b/docs/architecture/desktop.md index 8be437c..92b80c8 100644 --- a/docs/architecture/desktop.md +++ b/docs/architecture/desktop.md @@ -5,8 +5,9 @@ driver and the `evdev` input driver, Window Maker as the window manager, and `xt `startx` runs `wmaker`. Nothing here is written by SchOS except the kernel interfaces it stands on and four small files in `tools/posix/mkaroot.sh`. -**Xorg is the desktop for now, not for ever.** SchOS's own compositor, Schcom, is the road to a desktop of its own — the installer's -GUI first, SchDE on top later ([../plan/roadmap.md](../plan/roadmap.md) §5). +**Xorg is the desktop for now, not for ever.** SchOS will not write its own compositor (the one it had, Schcom, was deleted). +What comes next is whichever of XLibre (the Xorg fork), Xorg or a Wayland compositor from Alpine suits the machine; the groundwork +Wayland needs — udev, `/sys/devices`, libinput, a DRM device — is in [../plan/roadmap.md](../plan/roadmap.md) §5. ## Why X and not Wayland diff --git a/docs/architecture/graphics.md b/docs/architecture/graphics.md index 0526912..6676a1f 100644 --- a/docs/architecture/graphics.md +++ b/docs/architecture/graphics.md @@ -17,7 +17,7 @@ first thing here. | **About** | Pixels and text | *Frames* | | **Operations** | `draw_pixel`, `fill_rect`, `putc`, `text_at` | `acquire`, `present`, `wait_vblank` | | **Buffering** | None. Direct writes to the scanout. | Double-buffered | -| **Users** | The console, PSOGM, GMMOS | Schcom (v0 and its successors); Xorg draws through `/dev/fb0` instead | +| **Users** | The console, PSOGM, GMMOS | None today — Xorg draws through `/dev/fb0` | | **Must work when** | **Nothing else in the system does** | Userspace is healthy | That last row is the reason they are not one interface. The console and the diff --git a/docs/guides/testing.md b/docs/guides/testing.md index 9e4651b..dbd9579 100644 --- a/docs/guides/testing.md +++ b/docs/guides/testing.md @@ -14,7 +14,7 @@ reporting that it started. |---|---|---| | `make schfs-check`, `make kmem-test`, `ldls-inspect`/`ldls-run` | Host-native unit suites and the Linux-module loader run on the build machine | The filesystem's logic, the slab allocator's bookkeeping, and a `.ko`'s layout, in about a second | | `tools/windows/bootcheck.ps1` | A real boot, driven over the serial console | That the system answers correctly | -| `make crashqr`, `make schcom-test` | A boot, then pixel assertions from the host | That the *screen* is right | +| `make crashqr` | A boot, then pixel assertions from the host | That the *screen* is right | The levels exist because each catches what the one below cannot. A unit test cannot tell you the kernel fails to boot; a boot test cannot tell you the screen @@ -97,17 +97,11 @@ can; the rest are run by hand or by a script: ```sh make crashshot && make crashqr # crash the kernel, decode the QR out of the framebuffer -make schcom-test # boot, run the compositor and a client, check the pixels ``` `tools/windows/splashshot.ps1` counts colours. **That exists because a log-only harness once passed on a blank screen.** -`schcom-test` asserts **from the host** — the compositor's own report that it -composited something proves nothing. It runs two overlapping clients and checks -that the newer one wins in the overlap, because a compositor that cannot order -two surfaces cannot host a window manager. - Other capture tools, all in `tools/windows/`: `dispshot`, `fbshot`, `splashshot`, `textshot`, `ttyshot`. diff --git a/docs/plan/1.0.md b/docs/plan/1.0.md index ed2eb0d..ee7ab5c 100644 --- a/docs/plan/1.0.md +++ b/docs/plan/1.0.md @@ -60,8 +60,7 @@ boot on real hardware from a USB stick The test used to end with *reboot — the file is still there*. That clause is **dropped**, deliberately: 1.0 is a **live** system. The root lives in RAM and nothing written to it survives a reboot, and that is what the project is calling finished. -Persistence (an installer, or a persistent partition) is after 1.0, and its GUI is Schcom's job — see -[roadmap.md](roadmap.md) §5. +Persistence (an installer, or a persistent partition) is after 1.0 — see [roadmap.md](roadmap.md) §5. --- @@ -90,7 +89,6 @@ directory that has said "to be deleted" for months is the corner, not the cure. | | Where it stopped | |---|---| -| **`projects/schcom/`** | A compositor v0 (about 700 lines) with no client but its own demo: the desktop is Xorg. Kept on purpose — it is to draw the installer, and later SchDE ([roadmap.md](roadmap.md) §5) — but until that starts it is built and connected to nothing. | | **The in-kernel LDLS window** | `vm.c` maps the kernel image a second time in the top 2 GB at every boot, and `include/ldls/ldls.h` describes a `/dev/ldls` loader — the start of loading Linux modules *into the kernel*, which [ldls.md](../architecture/ldls.md) rule 2 says is not how it is done. Nothing calls either. **Delete it**, or decide. | | **`display_ops`** | `acquire` and `wait_vblank` are in the vtable, implemented by one driver at most, with no consumer: Xorg draws through `/dev/fb0`. Kept for the GPU work ([gpu.md](gpu.md)); that is the reason it stays. | | **Socket activation** | `schinitd` parses `[Socket name]` and remembers the path. Nothing holds the socket. This is the entire reason for copying launchd, and it is the half that was not built. | @@ -129,7 +127,7 @@ Neither cancelled nor in progress. **Do not open these.** | | | |---|---| -| `projects/schinst/` | the installer, the installation environment, `.sip` — drawn by Schcom | +| `projects/schinst/` | the installer, the installation environment, `.sip` | | `projects/schfs-linux/` | SchFS as a filesystem for Linux | | [gpu.md](gpu.md) | amdgpu and nouveau through LDLS — measured, with the work it needs written down | | [finch.md](finch.md) | Finch inside SchOS (the language itself is finished and lives in its own repository) | diff --git a/docs/plan/README.md b/docs/plan/README.md index d474a7e..81178ba 100644 --- a/docs/plan/README.md +++ b/docs/plan/README.md @@ -40,8 +40,8 @@ Plans leave this directory when they are done. These did, and what replaced them And one that was **decided against**: `wayland.md`. Wayland needs DRM/KMS, libinput, udev and libseat, and DRM/KMS alone is a kernel graphics subsystem. The desktop is X. The reasoning moved to [desktop.md](../architecture/desktop.md). -`SchWM` and `SchDE` were removed from the tree as READMEs with no code: Schcom is its own window manager, and SchDE returns as -ordinary clients of Schcom when there is a Schcom to build them on. Meanwhile the window manager is Window Maker. +`SchWM`, `SchDE` and the compositor `Schcom` are gone from the tree: SchOS uses other people's window manager and desktop — Window Maker +now; XLibre, Xorg or a Wayland compositor later — and puts its work into what they need underneath (udev, DRM). --- @@ -50,11 +50,11 @@ ordinary clients of Schcom when there is a Schcom to build them on. Meanwhile th Set by [1.0.md](1.0.md), which is the authority. **1.0 is reached** (a live system; persistence is not part of it). What is left before anything new starts: -1. **Empty the register**: decide the unused in-kernel LDLS window (delete it, or use it), build socket activation - or remove the key, and give `projects/schcom/` its first client. -2. **Then**, whatever [roadmap.md](roadmap.md) says is next. The two things the owner wants most are the installer - drawn by Schcom — SchOS's own compositor, and the road to SchDE ([roadmap.md](roadmap.md) §5) — and GPU - acceleration on the laptop ([gpu.md](gpu.md)). Finch inside SchOS ([finch.md](finch.md)) comes after. +1. **Empty the register**: decide the unused in-kernel LDLS window (delete it, or use it), and build socket activation or + remove the key. +2. **The owner's list** ([roadmap.md](roadmap.md)): the Wayland groundwork (udev first, §5), making LDLS good enough that the GPU + driver can run ([gpu.md](gpu.md)), a boot manager worth looking at (§5b), then the installer, then Finch inside SchOS + ([finch.md](finch.md)). ## What this plan does not address diff --git a/docs/plan/roadmap.md b/docs/plan/roadmap.md index 05bc8e3..deb7a14 100644 --- a/docs/plan/roadmap.md +++ b/docs/plan/roadmap.md @@ -54,22 +54,44 @@ what is present ([boot.md](../architecture/boot.md)). - **udev's job** — a uevent socket and `/sys/devices` — so X and others are told about devices instead of being given a list. -## 5. Schcom, the installer, and SchDE +## 5. The desktop, and the road to Wayland -**Schcom** (`projects/schcom/`) is SchOS's own compositor. v0 is 715 lines that composite clients over a wire format of its -own, and everything it was blocked on in the kernel now exists: Unix sockets, descriptor passing, `memfd`, evdev, framebuffer -`mmap`. The plan is in three steps, each useful on its own: +SchOS does not write its own compositor. (Schcom, a 715-line v0, was deleted: its wire format was a dead end, and a +compositor worth running is years of work that Alpine already packages.) The desktop is **Xorg with Window Maker** now +([desktop.md](../architecture/desktop.md)); **XLibre**, the Xorg fork, is a drop-in alternative if Alpine carries it, and a +Wayland compositor (labwc, sway, weston, a GNOME or KDE session) is the larger step. What Wayland — and a modern X — need +from the system, in the order they are useful: -1. **The installer's GUI is Schcom's first real client.** An installer wants a window, a list, a progress bar and a handful of - input events — small enough to be the program that forces Schcom's protocol to be real, and independent of Xorg, which is not in - the installation environment. It needs a wire format for clients that is designed (Wayland-shaped: surfaces, buffers shared by - descriptor, input events), not the v0 one. -2. **If that works, Schcom becomes useful in its own right**: window management as a module of the compositor (a compositor *is* - its window manager), multiple clients with focus and stacking, damage tracking. -3. **SchDE on top of it**: a panel, a launcher, a wallpaper and settings, as ordinary clients — only once there is a Schcom worth - putting them on. Until then the desktop is Xorg with Window Maker ([desktop.md](../architecture/desktop.md)). +1. **udev.** Today Xorg is *told* its input devices (`xorg.conf.d/10-schos.conf`) because nothing announces them. The fix is + Alpine's `eudev` (or `udev`) running on SchOS, which wants: a **uevent netlink socket** (`NETLINK_KOBJECT_UEVENT`, with the + `add`/`remove`/`change` messages and their environment), a real **`/sys/devices` tree** with `/sys/bus/*/devices/*` and + `/sys/class/*/*` as symlinks into it, `uevent` files that can be written to replay events (`udevadm trigger`), and devtmpfs + that udev can create nodes in. libinput then enumerates evdev devices through it. +2. **libinput and libseat** (seatd is in Alpine): evdev with the properties libinput reads (`ID_INPUT_*` from udev), and a way + for a non-root compositor to take the keyboard and the display from the kernel — the `VT_PROCESS` handshake exists. +3. **A DRM device.** Wayland compositors want `/dev/dri/card0` with KMS, and Mesa wants `renderD128`. SchOS has neither: it has + `/dev/fb0`. This is the GPU plan ([gpu.md](gpu.md)) — Linux's own `amdgpu` run by LDLS and `/dev/dri` served by that + process — and, for machines without a real GPU driver, a *simple-framebuffer* DRM shim (Linux's `simpledrm`, which is a + `.ko` LDLS could also host). +4. **dma-buf, `epoll`-able sync files and the rest of the Linux graphics ABI**, as the compositors turn out to need them. -Nothing here starts before [1.0.md](1.0.md)'s register is empty. +None of this is a kernel rewrite: it is the Linux interfaces that already exist half-built (`/sys`, netlink, evdev, `SCM_RIGHTS`, +`memfd`), finished until real programs stop asking for what is missing. The method is the one that has worked all along: `apk +add` the program, run it, and fix what it names. + +Nothing here starts before [1.0.md](1.0.md)'s register is empty, except where the owner says otherwise. + +## 5b. The boot manager + +The loader ([boot.md](../architecture/boot.md)) is a text menu: a splash, "Press F8", a list of options. UEFI gives much more: +a graphics-output framebuffer at the panel's native mode, a pointer protocol, file systems to read themes from. The plan is a +boot manager that is worth looking at — in the **Niveus / skeuomorphic** style the site and the wallpapers already use (the +Shift mark, real materials, depth) — and usable: a mouse and keyboard, a list of what can be booted (this ISO, a SchFS +partition, other operating systems' loaders), the options that live in the F8 menu now (resolution with its "keep this?" +confirmation, verbose boot, drivers off), and an honest failure screen. It is its own piece of work in `boot/uefi/` +(a small text and image renderer in the loader — the kernel's TrueType rasteriser is not available yet at that point) and it +replaces the boot-time display handling that grew by accretion (GOP mode choice, the "keep this resolution" probe, the font scaling +in the kernel) with one design. ### The installer and `.sip` @@ -96,7 +118,7 @@ Open questions, all wanting a decision before code: now, or not? - **Can the kernel mount a `.sip` read-only directly?** If it can, the installation environment's root can simply *be* the `.sip`. -The GUI for this is a Schcom client (above). +Its GUI is the boot manager's (5b) — the installation environment starts in it — or an X client. ## 6. SchFS beyond SchOS diff --git a/docs/plan/schapi.md b/docs/plan/schapi.md index 1ec4a0f..b2f5329 100644 --- a/docs/plan/schapi.md +++ b/docs/plan/schapi.md @@ -3,7 +3,9 @@ Status: **direction agreed, not yet implemented**, and frozen until 1.0 ships ([1.0.md](1.0.md)). Read it as a direction and not as a schedule: written when the plan was a compositor of SchOS's own, it has not been revisited since the desktop became Xorg and the -packages became Alpine's, and §7 (what happens now) in particular predates both. Nothing in this document +packages became Alpine's, and §7 (what happens now) in particular predates both. It also assumed SchOS would write its +own compositor (Schcom) for the API to be designed *from*; that compositor was deleted, and SchOS's desktop +is other people's, so read every mention of Schcom below as "the desktop". Nothing in this document changes what is being built this week; it changes what everything is being built *towards*. See §7 for what actually happens now. diff --git a/projects/README.md b/projects/README.md index 9faf146..2163149 100644 --- a/projects/README.md +++ b/projects/README.md @@ -7,8 +7,7 @@ Everything above this directory — `kernel/`, `boot/`, `lib/`, `cmd/`, is one program that either works or does not. Everything in here is an ordinary program that talks to it through syscalls, exactly as `toybox` does. -The split exists because the alternative gets bad quickly. A compositor, -a window manager and a desktop are each larger than the kernel will be for a +The split exists because the alternative gets bad quickly. A desktop environment is larger than the kernel will be for a long time. Grown inside the OS tree they would blur into it: a compositor would start reaching into kernel headers because they are right there, the build would grow a dependency from the kernel to a GUI, and "does SchOS @@ -27,14 +26,12 @@ Concretely, code in here: | Directory | What it is | |------------|-----------------------------------------------------------------| -| `schcom/` | SchOS Compositor. v0 runs; the plan is for it to draw the installer, and later a desktop (SchDE) on top. | | `schinst/` | The installer, the installation environment, and `.sip` tooling. | | `schfs-linux/` | SchFS as a filesystem for Linux. FUSE first, a kernel module later. | -`schwm/` and `schde/` used to be here and were **deleted**: both held a README and no code. The window manager -is Schcom's own business now (a compositor is its window manager), and **SchDE**, a panel, launcher and the rest, -comes back as ordinary clients of Schcom once there is something for them to run on. Until then the desktop is -Xorg with Window Maker ([`docs/architecture/desktop.md`](../docs/architecture/desktop.md)). +`schcom/`, `schwm/` and `schde/` used to be here and were **deleted**: SchOS does not write its own compositor, window +manager or desktop. The desktop is Xorg with Window Maker ([`docs/architecture/desktop.md`](../docs/architecture/desktop.md)), +and the road to Wayland is Alpine's compositors on top of udev and a DRM device ([`docs/plan/roadmap.md`](../docs/plan/roadmap.md) §5). **`schfs-linux/` is written to stand on its own.** It does not mention SchOS anywhere, and that is deliberate rather than an oversight: it is intended to be @@ -51,11 +48,5 @@ by anything above it. ## Status -`schcom/` is a compositor v0, 715 lines that run, over a wire format of its own. It is **kept, with a job**: the -installer's GUI ([`docs/plan/roadmap.md`](../docs/plan/roadmap.md) §5) is its first real client, and if it grows -into something actually useful it becomes SchOS's own desktop, SchDE on top. Everything it was blocked on in the kernel -exists (Unix sockets, descriptor passing, `memfd`, evdev, framebuffer `mmap`); what it lacks is a protocol designed for -real clients and the clients themselves. - `schinst/` and `schfs-linux/` are scoped and unstarted, and are **frozen** -- see [`docs/plan/1.0.md`](../docs/plan/1.0.md). diff --git a/projects/schcom/Makefile b/projects/schcom/Makefile deleted file mode 100644 index 90cbd2e..0000000 --- a/projects/schcom/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# -# Schcom builds like any other program that runs *on* SchOS: against the -# sysroot's libc, never against kernel headers. The top-level Makefile passes -# CC, CFLAGS and DESTDIR in; nothing here reaches back up the tree. -# -# It is not part of the OS build. `make` at the top level never descends here -- -# `make projects` does. - -CC ?= cc -CFLAGS ?= -O2 -CFLAGS += -Wall -Wextra -std=c11 -DESTDIR ?= ../../sysroot - -BINS = schcom schcom-demo - -all: $(BINS) - -schcom: schcom.c proto.h - $(CC) $(CFLAGS) -o $@ schcom.c $(LDFLAGS) - -schcom-demo: schcom-demo.c proto.h - $(CC) $(CFLAGS) -o $@ schcom-demo.c $(LDFLAGS) - -install: $(BINS) - mkdir -p $(DESTDIR)/bin - cp $(BINS) $(DESTDIR)/bin/ - -clean: - rm -f $(BINS) - -.PHONY: all install clean diff --git a/projects/schcom/README.md b/projects/schcom/README.md deleted file mode 100644 index 86fc93c..0000000 --- a/projects/schcom/README.md +++ /dev/null @@ -1,110 +0,0 @@ -# Schcom — the SchOS Compositor - -Owns the display. Every pixel that reaches the screen goes through it. - -Clients do not draw to the framebuffer; they draw into their own buffers and -hand them over. Schcom decides what is on top of what, composites the result, -and presents it. It also owns input: it reads the raw event devices and sends -each event to whichever client should have it. - -That is the whole job. Window *placement* is not part of it — that is SchWM's, -and Schcom exposes a protocol for it rather than implementing policy itself. - -## Status - -**v0 runs.** A client connects, is given a shared buffer, draws into it and -commits; the result is composited and presented. Verified end to end by -`make schcom-test`, which boots SchOS, runs the compositor and a client, then -screendumps the framebuffer and asserts the pixels **from the host** — the -compositor's own report that it composited something proves nothing. It runs -two overlapping clients and checks that the newer one wins in the overlap: a -compositor that cannot order two surfaces cannot host a window manager. - -What exists: - -- framebuffer output through `/dev/fb0` and the fbdev ioctls, double-buffered -- a versioned protocol over a Unix socket, with a handshake that must come first -- shared-memory buffers, one per surface, owned by the compositor -- surface / attach / **commit** semantics: nothing is shown mid-draw -- input from `/dev/input/eventN`, routed to the topmost surface -- clients that die are cleaned up, including their shm segments -- **multiple clients**, composited bottom-to-top in creation order - -What does not exist yet: damage tracking beyond "repaint everything", multiple -outputs, subsurfaces, and any focus model beyond "topmost". Those are additions -rather than rewrites, which is the reason for committing to the surface/commit -model now instead of growing into it later. - -## Building - -Not part of the OS build. Needs `make ports` first, for the sysroot's libc: - -```sh -make projects # builds and stages into sysroot/bin -make # rebuilds the image so the binaries are on it -make schcom-test # boots it and checks the pixels -``` - -## What it needs from the kernel - -| Need | State | -|---|---| -| Shared memory (`shmget`/`shmat`/`shmdt`) | done | -| `mmap` of `/dev/fb0` | done | -| Framebuffer geometry via the fbdev ioctls | done | -| Unix domain sockets | done | -| Raw input events — key down/up with modifiers | done | -| `SCM_RIGHTS` | done — `sendmsg`/`recvmsg`, see [`docs/architecture/networking.md`](../../docs/architecture/networking.md#descriptor-passing) | -| A shareable memory object | **missing.** A Wayland buffer is a descriptor for memory; a System V segment id is not one. Item 2 of [`docs/plan/foundations.md`](../../docs/plan/foundations.md), and the one thing now standing between this directory and a Wayland compositor. | -| Mode setting and double buffering | partial — virtio-gpu binds, no modeset API yet | - -> **The v0 protocol below is superseded and the code is frozen.** It runs, and -> it is kept because it is the only thing in the tree that has ever composited -> anything — but nothing more will be added to it. SchWM and SchDE are referred -> to throughout and both have been deleted; read those mentions as "the policy -> module" and "a layer-shell client". See -> [`docs/plan/wayland.md`](../../docs/plan/wayland.md). - -## Design notes - -- **Buffers are shared memory, not pipes.** A pipe means copying every frame; at - 800×600×32 that is 1.9 MB per frame per client. -- **The compositor owns the segment, not the client.** It has to survive a - client crashing mid-frame, and it must not be something a client can resize - underneath the compositor. -- **One protocol, versioned from the start.** SchWM and SchDE are separate - programs, so this is a real compatibility surface on day one. Every message - carries its own length, so an unknown message is skipped rather than - desynchronising the stream — that is what lets a v0 compositor survive a v1 - client. -- **Policy belongs to SchWM.** If Schcom starts deciding where windows go, the - split has already failed. `CREATE_SURFACE` carries x/y today only because - there is no SchWM yet to ask. - -## On Wayland - -> **Decided: Schcom moves onto Wayland**, replacing the protocol below while -> keeping the architecture. See [`docs/plan/wayland.md`](../../docs/plan/wayland.md). - - -The protocol is deliberately Wayland-shaped — surfaces rather than windows, -buffers attached and made visible only on commit, and the compositor telling -clients when to draw. Wayland is **MIT-licensed**, so it can be linked against -without any conflict with GPL-2.0-or-later (see `docs/licensing.md`), and -supporting it later is intended. - -The one place v0 knowingly diverges: a buffer handle is a System V shared memory -id passed as an integer, where Wayland passes a file descriptor over -`SCM_RIGHTS`. `struct schcom_surface` carries a `handle_kind` field precisely so -that switching would be a new value rather than a protocol break. - -**That field could not have saved us.** Wayland's `wl_shm_create_pool` passes a -descriptor with no alternative form, so *our* protocol being the flexible one -was never the flexibility that was needed — and for a long time this README -claimed `SCM_RIGHTS` was done when there was no `sendmsg`/`recvmsg` in the -kernel at all. An entire GUI plan was drawn on top of that line. - -It is true now. Item 1 of -[`docs/plan/foundations.md`](../../docs/plan/foundations.md) is built and -tested; what a buffer still needs is item 2, a shareable memory object, because -a descriptor for a System V segment is not a thing that exists. diff --git a/projects/schcom/proto.h b/projects/schcom/proto.h deleted file mode 100644 index f7ebf15..0000000 --- a/projects/schcom/proto.h +++ /dev/null @@ -1,128 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * The Schcom wire protocol. - * - * Versioned from the first commit, because SchWM and SchDE are separate - * programs: the protocol is a real compatibility surface on day one, not after - * the fact. A client sends SCHCOM_OP_HELLO with the version it speaks and the - * compositor either acknowledges it or closes the connection. Nothing else is - * accepted before the handshake. - * - * The shape is deliberately Wayland's, because Wayland support is intended - * later and it is MIT-licensed, so it can be linked (see the licensing note in - * the README). Borrowed, specifically: - * - * - a surface is not a window. It is a rectangle of pixels with no policy - * attached; placement belongs to SchWM. - * - a buffer is attached to a surface and nothing appears until COMMIT. - * Atomicity is the whole point: a client that is halfway through drawing - * must never be shown. - * - the compositor tells the client when to draw again (FRAME), rather than - * the client drawing whenever it likes. That is what stops a fast client - * from burning the machine rendering frames nobody sees. - * - * Where it deliberately differs, for now: a buffer is a System V shared memory - * id passed as an integer, not a file descriptor passed over SCM_RIGHTS. - * SCM_RIGHTS is the Wayland-compatible route and the kernel supports it, but an - * integer needs no ancillary-data handling on either side and this is v0. The - * message carries a `handle_kind` field so the switch does not become a - * protocol break. - */ -#ifndef SCHCOM_PROTO_H -#define SCHCOM_PROTO_H - -#include - -#define SCHCOM_SOCKET_PATH "/tmp/schcom.sock" - -/* Bumped on any incompatible change. v0 is provisional in the SchAPI sense: - * nothing here is frozen until 1.0. */ -#define SCHCOM_VERSION 0u - -enum schcom_op { - /* client -> compositor */ - SCHCOM_OP_HELLO = 1, - SCHCOM_OP_CREATE_SURFACE = 2, - SCHCOM_OP_COMMIT = 3, - SCHCOM_OP_DESTROY = 4, - - /* compositor -> client */ - SCHCOM_OP_HELLO_ACK = 128, - SCHCOM_OP_SURFACE = 129, /* the buffer for a CREATE_SURFACE */ - SCHCOM_OP_FRAME = 130, /* draw again now */ - SCHCOM_OP_INPUT = 131, - SCHCOM_OP_ERROR = 132, -}; - -/* How the pixels are shared. Only SHMID exists today; the field exists so that - * adding SCM_RIGHTS later is a new value rather than a new protocol. */ -enum schcom_handle_kind { - SCHCOM_HANDLE_SHMID = 1, - SCHCOM_HANDLE_FD = 2, /* reserved: passed over SCM_RIGHTS */ -}; - -/* Every message begins with this. `length` counts the header itself, so a - * reader can skip a message it does not understand instead of losing sync -- - * which is what lets an old compositor talk to a newer client at all. */ -struct schcom_header { - uint32_t op; - uint32_t length; - uint32_t serial; /* echoed in the reply, 0 for events */ - uint32_t surface; /* surface id, 0 where not applicable */ -}; - -struct schcom_hello { - struct schcom_header hdr; - uint32_t version; - uint32_t pad; -}; - -struct schcom_create_surface { - struct schcom_header hdr; - uint32_t width; - uint32_t height; - int32_t x; /* v0: the client asks. SchWM will own this later. */ - int32_t y; -}; - -/* The compositor's answer: here is your surface id and where its pixels live. - * Always 32-bit XRGB8888, stride in bytes, because a client that has to - * negotiate a format before it can draw a rectangle is a client that cannot be - * written in an afternoon. Format negotiation is a v1 problem. */ -struct schcom_surface { - struct schcom_header hdr; - uint32_t handle_kind; - uint32_t handle; /* shm id when handle_kind == SHMID */ - uint32_t width; - uint32_t height; - uint32_t stride; - uint32_t size; -}; - -/* Nothing is shown until this arrives. The damage rectangle is advisory: the - * compositor may repaint more, never less. */ -struct schcom_commit { - struct schcom_header hdr; - int32_t x, y; - uint32_t width, height; -}; - -/* Raw-ish input, already routed to the surface that should have it. Codes are - * the Linux input event codes the kernel's /dev/input/eventN already speaks -- - * translating them here would mean inventing a second table to disagree with - * the first. */ -struct schcom_input { - struct schcom_header hdr; - uint32_t type; /* EV_KEY, EV_REL, ... */ - uint32_t code; - int32_t value; - int32_t x, y; /* pointer position at the time of the event */ -}; - -struct schcom_error { - struct schcom_header hdr; - uint32_t code; - uint32_t pad; -}; - -#endif /* SCHCOM_PROTO_H */ diff --git a/projects/schcom/schcom b/projects/schcom/schcom deleted file mode 100644 index 92d09d6ba0b07bbc0596d5d68bc5f8080e275fa5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45416 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%0AQ-r|$**2GH--=pTTnT#rox-I3-clR)-*WC#GL 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 diff --git a/projects/schcom/schcom-demo b/projects/schcom/schcom-demo deleted file mode 100644 index d6cdb49bd89f2bfb3473b7cec3e6bc1279af1482..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40928 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 diff --git a/projects/schcom/schcom-demo.c b/projects/schcom/schcom-demo.c deleted file mode 100644 index dc4bdeb..0000000 --- a/projects/schcom/schcom-demo.c +++ /dev/null @@ -1,149 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * A Schcom client, and the test for the compositor. - * - * Draws a flat rectangle of a known colour at a known position and commits it. - * Both of those are chosen so the result can be checked from outside the - * machine: `make schcom-shot` screendumps the framebuffer and a script on the - * host asserts the pixel at a given coordinate. That is the same oracle - * pattern the SchFS round-trip uses -- something with no connection to the code - * under test has to agree that it worked. - * - * schcom-demo [--colour 0xRRGGBB] [--at X Y] [--size W H] [--wait SECONDS] - */ - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include - -#include "proto.h" - -static int -read_all(int fd, void *buf, size_t n) -{ - unsigned char *p = buf; - while (n) { - ssize_t r = read(fd, p, n); - if (r == 0) return -1; - if (r < 0) { if (errno == EINTR) continue; return -1; } - p += r; n -= (size_t)r; - } - return 0; -} - -int -main(int argc, char **argv) -{ - uint32_t colour = 0x00C04020; - int32_t at_x = 100, at_y = 80; - uint32_t w = 240, h = 160; - int wait_s = 3; - - for (int i = 1; i < argc; i++) { - if (!strcmp(argv[i], "--colour") && i + 1 < argc) - colour = (uint32_t)strtoul(argv[++i], NULL, 0); - else if (!strcmp(argv[i], "--at") && i + 2 < argc) { - at_x = atoi(argv[++i]); at_y = atoi(argv[++i]); - } else if (!strcmp(argv[i], "--size") && i + 2 < argc) { - w = (uint32_t)atoi(argv[++i]); h = (uint32_t)atoi(argv[++i]); - } else if (!strcmp(argv[i], "--wait") && i + 1 < argc) - wait_s = atoi(argv[++i]); - } - - int fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) { perror("socket"); return 1; } - - struct sockaddr_un addr; - memset(&addr, 0, sizeof addr); - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, SCHCOM_SOCKET_PATH, sizeof addr.sun_path - 1); - if (connect(fd, (struct sockaddr *)&addr, sizeof addr) < 0) { - fprintf(stderr, "schcom-demo: connect %s: %s\n", - SCHCOM_SOCKET_PATH, strerror(errno)); - return 1; - } - - /* Handshake first: the compositor accepts nothing else before it. */ - struct schcom_hello hello; - memset(&hello, 0, sizeof hello); - hello.hdr.op = SCHCOM_OP_HELLO; - hello.hdr.length = sizeof hello; - hello.hdr.serial = 1; - hello.version = SCHCOM_VERSION; - if (write(fd, &hello, sizeof hello) != (ssize_t)sizeof hello) { - perror("write hello"); return 1; - } - - struct schcom_hello ack; - if (read_all(fd, &ack, sizeof ack) < 0 || ack.hdr.op != SCHCOM_OP_HELLO_ACK) { - fprintf(stderr, "schcom-demo: no hello ack\n"); - return 1; - } - printf("schcom-demo: connected, protocol v%u\n", ack.version); - - struct schcom_create_surface req; - memset(&req, 0, sizeof req); - req.hdr.op = SCHCOM_OP_CREATE_SURFACE; - req.hdr.length = sizeof req; - req.hdr.serial = 2; - req.width = w; req.height = h; - req.x = at_x; req.y = at_y; - if (write(fd, &req, sizeof req) != (ssize_t)sizeof req) { - perror("write create"); return 1; - } - - struct schcom_surface surf; - if (read_all(fd, &surf, sizeof surf) < 0) { - fprintf(stderr, "schcom-demo: no reply to create\n"); - return 1; - } - if (surf.hdr.op != SCHCOM_OP_SURFACE) { - fprintf(stderr, "schcom-demo: create refused (op %u)\n", surf.hdr.op); - return 1; - } - if (surf.handle_kind != SCHCOM_HANDLE_SHMID) { - fprintf(stderr, "schcom-demo: unexpected handle kind %u\n", surf.handle_kind); - return 1; - } - - void *pix = shmat((int)surf.handle, NULL, 0); - if (pix == (void *)-1) { perror("shmat"); return 1; } - - printf("schcom-demo: surface %u, %ux%u stride %u\n", - surf.hdr.surface, surf.width, surf.height, surf.stride); - - /* Draw: a solid block with a one-pixel border, so a test can tell "the - * buffer arrived" from "the buffer arrived at the wrong offset". */ - for (uint32_t y = 0; y < surf.height; y++) { - uint32_t *row = (uint32_t *)((unsigned char *)pix + (size_t)y * surf.stride); - for (uint32_t x = 0; x < surf.width; x++) { - int edge = (x == 0 || y == 0 || x == surf.width - 1 || y == surf.height - 1); - row[x] = edge ? 0x00FFFFFF : colour; - } - } - - struct schcom_commit commit; - memset(&commit, 0, sizeof commit); - commit.hdr.op = SCHCOM_OP_COMMIT; - commit.hdr.length = sizeof commit; - commit.hdr.serial = 3; - commit.hdr.surface = surf.hdr.surface; - commit.width = surf.width; commit.height = surf.height; - if (write(fd, &commit, sizeof commit) != (ssize_t)sizeof commit) { - perror("write commit"); return 1; - } - printf("schcom-demo: committed 0x%06X at %d,%d\n", colour, at_x, at_y); - fflush(stdout); - - /* Stay alive: the surface belongs to this connection, and the compositor - * drops it when the client goes. Exiting immediately would erase the very - * thing the screenshot is meant to catch. */ - sleep(wait_s); - return 0; -} diff --git a/projects/schcom/schcom.c b/projects/schcom/schcom.c deleted file mode 100644 index 7a45438..0000000 --- a/projects/schcom/schcom.c +++ /dev/null @@ -1,715 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Schcom -- the SchOS compositor. - * - * Owns the display and the input devices. Clients draw into shared buffers and - * hand them over; Schcom decides what is on top of what, composites, and - * presents. It does not decide where windows go: that is SchWM's job, and the - * moment this file starts making placement decisions the split has failed. - * - * v0, deliberately small but not a toy: - * - * - real framebuffer output through /dev/fb0 and the fbdev ioctls - * - real clients over a Unix socket, with a versioned handshake - * - real shared-memory buffers, one per surface - * - a back buffer, so a half-composited frame never reaches the screen - * - input read from /dev/input/eventN and routed to the top surface - * - * What it does not do yet: damage tracking beyond "repaint everything", - * multiple outputs, subsurfaces, or any notion of focus beyond "topmost". - * Those are additions, not rewrites -- which is the point of committing to the - * surface/commit model now rather than growing into it. - */ - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "proto.h" - -#define MAX_CLIENTS 16 -#define MAX_SURFACES 32 - -struct surface { - int used; - uint32_t id; - int client; /* index into clients[] */ - int shmid; - void *pixels; - uint32_t w, h, stride, size; - int32_t x, y; -}; - -struct client { - int fd; /* -1 when the slot is free */ - int hello_done; -}; - -static struct client clients[MAX_CLIENTS]; -static struct surface surfaces[MAX_SURFACES]; -static uint32_t next_surface_id = 1; - -/* The screen. */ -static uint8_t *fb; -static uint32_t fb_w, fb_h, fb_stride, fb_bpp; -static size_t fb_size; -static uint8_t *back; /* composited here, then copied out in one go */ - -/* Pointer position: set outright by EV_ABS from a tablet, nudged by EV_REL - * from a plain mouse. */ -static int32_t ptr_x, ptr_y; - -static void -die(const char *what) -{ - fprintf(stderr, "schcom: %s: %s\n", what, strerror(errno)); - exit(1); -} - -/* ---------------------------------------------------------------- output */ - -static int -open_framebuffer(void) -{ - int fd = open("/dev/fb0", O_RDWR); - if (fd < 0) - die("open /dev/fb0"); - - struct fb_var_screeninfo var; - struct fb_fix_screeninfo fix; - if (ioctl(fd, FBIOGET_VSCREENINFO, &var) < 0) - die("FBIOGET_VSCREENINFO"); - if (ioctl(fd, FBIOGET_FSCREENINFO, &fix) < 0) - die("FBIOGET_FSCREENINFO"); - - fb_w = var.xres; - fb_h = var.yres; - fb_bpp = var.bits_per_pixel; - /* The stride comes from the driver, never from xres*bpp: the two differ on - * real hardware and assuming otherwise shears the image. */ - fb_stride = fix.line_length; - fb_size = (size_t)fb_stride * fb_h; - - if (fb_bpp != 32) { - fprintf(stderr, "schcom: need 32bpp, got %u\n", fb_bpp); - exit(1); - } - - fb = mmap(NULL, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (fb == MAP_FAILED) - die("mmap /dev/fb0"); - - back = malloc(fb_size); - if (!back) - die("malloc back buffer"); - - printf("schcom: %ux%u, %u bpp, stride %u\n", fb_w, fb_h, fb_bpp, fb_stride); - fflush(stdout); - return fd; -} - -/* Damage: the rectangle that has to be redrawn and copied out. - * - * Without this the compositor repainted the whole screen for every event -- - * at 1280x800 that is a million pixels of desktop fill plus a 4 MB copy to the - * framebuffer, per mouse movement. Under emulation the pointer lagged behind - * the mouse by whole seconds, and it was not the emulation's fault: moving a - * 10x10 cursor does not require touching four megabytes. - * - * One rectangle rather than a list. Two distant small changes then repaint the - * span between them, which is wasteful in theory; in practice the changes are - * a pointer and the window under it, and a list costs more bookkeeping than it - * saves. - */ -static int dmg_x0, dmg_y0, dmg_x1, dmg_y1; /* x1/y1 exclusive */ -static int dirty; - -static void -damage(int x, int y, int w, int h) -{ - int x0 = x, y0 = y, x1 = x + w, y1 = y + h; - - if (x0 < 0) x0 = 0; - if (y0 < 0) y0 = 0; - if (x1 > (int)fb_w) x1 = (int)fb_w; - if (y1 > (int)fb_h) y1 = (int)fb_h; - if (x0 >= x1 || y0 >= y1) - return; - - if (!dirty) { - dmg_x0 = x0; dmg_y0 = y0; dmg_x1 = x1; dmg_y1 = y1; - dirty = 1; - return; - } - if (x0 < dmg_x0) dmg_x0 = x0; - if (y0 < dmg_y0) dmg_y0 = y0; - if (x1 > dmg_x1) dmg_x1 = x1; - if (y1 > dmg_y1) dmg_y1 = y1; -} - -static void -damage_all(void) -{ - damage(0, 0, (int)fb_w, (int)fb_h); -} - -#define PTR_SIZE 10 - -static void -composite(void) -{ - if (!dirty) - return; - - const int rx0 = dmg_x0, ry0 = dmg_y0, rx1 = dmg_x1, ry1 = dmg_y1; - - /* Desktop. A flat colour rather than black, so that "nothing was drawn" - * and "the compositor is not running" cannot look the same. */ - for (int y = ry0; y < ry1; y++) { - uint32_t *row = (uint32_t *)(back + (size_t)y * fb_stride); - for (int x = rx0; x < rx1; x++) - row[x] = 0x00203040; - } - - /* Surfaces, bottom to top. Creation order is stacking order in v0; SchWM - * will reorder later through a protocol request rather than by reaching in - * here. */ - for (int i = 0; i < MAX_SURFACES; i++) { - struct surface *s = &surfaces[i]; - if (!s->used || !s->pixels) - continue; - - /* Only the part of this surface inside the damage rectangle. */ - for (uint32_t sy = 0; sy < s->h; sy++) { - int dy = s->y + (int)sy; - if (dy < ry0 || dy >= ry1) - continue; - - uint32_t *dst = (uint32_t *)(back + (size_t)dy * fb_stride); - uint32_t *src = (uint32_t *)((uint8_t *)s->pixels + (size_t)sy * s->stride); - - for (uint32_t sx = 0; sx < s->w; sx++) { - int dx = s->x + (int)sx; - if (dx < rx0 || dx >= rx1) - continue; - dst[dx] = src[sx]; - } - } - } - - /* A crude pointer, drawn last so it is never under a window. */ - for (int py = 0; py < PTR_SIZE; py++) { - int dy = ptr_y + py; - if (dy < ry0 || dy >= ry1) - continue; - uint32_t *dst = (uint32_t *)(back + (size_t)dy * fb_stride); - for (int px = 0; px < PTR_SIZE - py; px++) { - int dx = ptr_x + px; - if (dx >= rx0 && dx < rx1) - dst[dx] = 0x00FFFFFF; - } - } - - /* Copy out only what changed, row by row. Compositing straight into the - * framebuffer would show every intermediate state -- the flicker would be - * the whole desktop being repainted under the user's eyes -- but copying - * the whole thing to move a cursor is what made the pointer lag. */ - size_t bytes = (size_t)(rx1 - rx0) * 4; - for (int y = ry0; y < ry1; y++) { - size_t off = (size_t)y * fb_stride + (size_t)rx0 * 4; - memcpy(fb + off, back + off, bytes); - } - - dirty = 0; -} - -/* ---------------------------------------------------------------- clients */ - -static int -send_all(int fd, const void *buf, size_t n) -{ - const uint8_t *p = buf; - while (n) { - ssize_t w = write(fd, p, n); - if (w < 0) { - if (errno == EINTR) - continue; - return -1; - } - p += w; - n -= (size_t)w; - } - return 0; -} - -static int -read_all(int fd, void *buf, size_t n) -{ - uint8_t *p = buf; - while (n) { - ssize_t r = read(fd, p, n); - if (r == 0) - return -1; /* client went away */ - if (r < 0) { - if (errno == EINTR) - continue; - return -1; - } - p += r; - n -= (size_t)r; - } - return 0; -} - -static void -send_error(int fd, uint32_t serial, uint32_t code) -{ - struct schcom_error e; - memset(&e, 0, sizeof e); - e.hdr.op = SCHCOM_OP_ERROR; - e.hdr.length = sizeof e; - e.hdr.serial = serial; - e.code = code; - send_all(fd, &e, sizeof e); -} - -static void -drop_client(int ci) -{ - if (clients[ci].fd < 0) - return; - - for (int i = 0; i < MAX_SURFACES; i++) { - struct surface *s = &surfaces[i]; - if (s->used && s->client == ci) { - damage(s->x, s->y, (int)s->w, (int)s->h); - if (s->pixels) - shmdt(s->pixels); - /* IPC_RMID once, here: the segment goes when the last attach does, - * so a client that died mid-frame does not leak it. */ - shmctl(s->shmid, IPC_RMID, NULL); - memset(s, 0, sizeof *s); - } - } - - close(clients[ci].fd); - clients[ci].fd = -1; - clients[ci].hello_done = 0; - printf("schcom: client %d gone\n", ci); - fflush(stdout); -} - -static void -handle_create_surface(int ci, const struct schcom_create_surface *req) -{ - int fd = clients[ci].fd; - - int slot = -1; - for (int i = 0; i < MAX_SURFACES; i++) - if (!surfaces[i].used) { slot = i; break; } - if (slot < 0) { - send_error(fd, req->hdr.serial, ENOSPC); - return; - } - - uint32_t w = req->width, h = req->height; - if (w == 0 || h == 0 || w > fb_w || h > fb_h) { - send_error(fd, req->hdr.serial, EINVAL); - return; - } - - uint32_t stride = w * 4; - uint32_t size = stride * h; - - /* The compositor owns the segment, not the client: it has to survive the - * client crashing mid-frame, and it must not be something a client can - * quietly resize underneath us. */ - int shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600); - if (shmid < 0) { - send_error(fd, req->hdr.serial, errno); - return; - } - void *pix = shmat(shmid, NULL, 0); - if (pix == (void *)-1) { - shmctl(shmid, IPC_RMID, NULL); - send_error(fd, req->hdr.serial, errno); - return; - } - memset(pix, 0, size); - - struct surface *s = &surfaces[slot]; - s->used = 1; - s->id = next_surface_id++; - s->client = ci; - s->shmid = shmid; - s->pixels = pix; - s->w = w; - s->h = h; - s->stride = stride; - s->size = size; - s->x = req->x; - s->y = req->y; - - struct schcom_surface rep; - memset(&rep, 0, sizeof rep); - rep.hdr.op = SCHCOM_OP_SURFACE; - rep.hdr.length = sizeof rep; - rep.hdr.serial = req->hdr.serial; - rep.hdr.surface = s->id; - rep.handle_kind = SCHCOM_HANDLE_SHMID; - rep.handle = (uint32_t)shmid; - rep.width = w; - rep.height = h; - rep.stride = stride; - rep.size = size; - - if (send_all(fd, &rep, sizeof rep) < 0) { - drop_client(ci); - return; - } - damage(s->x, s->y, (int)w, (int)h); - printf("schcom: surface %u %ux%u at %d,%d for client %d (shm %d)\n", - s->id, w, h, s->x, s->y, ci, shmid); - fflush(stdout); -} - -static struct surface * -find_surface(uint32_t id) -{ - for (int i = 0; i < MAX_SURFACES; i++) - if (surfaces[i].used && surfaces[i].id == id) - return &surfaces[i]; - return NULL; -} - -/* Returns 1 if the screen needs repainting. */ -static int -handle_message(int ci) -{ - int fd = clients[ci].fd; - struct schcom_header hdr; - - if (read_all(fd, &hdr, sizeof hdr) < 0) { - drop_client(ci); - return 0; - } - if (hdr.length < sizeof hdr || hdr.length > 4096) { - send_error(fd, hdr.serial, EPROTO); - drop_client(ci); - return 0; - } - - uint8_t body[4096]; - memcpy(body, &hdr, sizeof hdr); - size_t rest = hdr.length - sizeof hdr; - if (rest && read_all(fd, body + sizeof hdr, rest) < 0) { - drop_client(ci); - return 0; - } - - if (!clients[ci].hello_done && hdr.op != SCHCOM_OP_HELLO) { - /* Nothing before the handshake. A client that gets this wrong is a - * client built against a protocol we do not speak. */ - send_error(fd, hdr.serial, EPROTO); - drop_client(ci); - return 0; - } - - switch (hdr.op) { - case SCHCOM_OP_HELLO: { - const struct schcom_hello *h = (const void *)body; - if (hdr.length < sizeof *h || h->version != SCHCOM_VERSION) { - send_error(fd, hdr.serial, EPROTONOSUPPORT); - drop_client(ci); - return 0; - } - struct schcom_hello ack; - memset(&ack, 0, sizeof ack); - ack.hdr.op = SCHCOM_OP_HELLO_ACK; - ack.hdr.length = sizeof ack; - ack.hdr.serial = hdr.serial; - ack.version = SCHCOM_VERSION; - if (send_all(fd, &ack, sizeof ack) < 0) { - drop_client(ci); - return 0; - } - clients[ci].hello_done = 1; - printf("schcom: client %d speaks v%u\n", ci, SCHCOM_VERSION); - fflush(stdout); - return 0; - } - - case SCHCOM_OP_CREATE_SURFACE: - if (hdr.length < sizeof(struct schcom_create_surface)) { - send_error(fd, hdr.serial, EINVAL); - return 0; - } - handle_create_surface(ci, (const void *)body); - return 1; - - case SCHCOM_OP_COMMIT: { - struct surface *s = find_surface(hdr.surface); - if (!s || s->client != ci) { - send_error(fd, hdr.serial, ENOENT); - return 0; - } - /* The commit is the whole point: the pixels were already there, but - * they were not allowed to be shown until now. The damage rectangle in - * the message is advisory -- repaint the whole surface, which is never - * wrong, only sometimes more than asked. */ - damage(s->x, s->y, (int)s->w, (int)s->h); - struct schcom_header fr; - memset(&fr, 0, sizeof fr); - fr.op = SCHCOM_OP_FRAME; - fr.length = sizeof fr; - fr.surface = s->id; - if (send_all(fd, &fr, sizeof fr) < 0) { - drop_client(ci); - return 1; - } - return 1; - } - - case SCHCOM_OP_DESTROY: { - struct surface *s = find_surface(hdr.surface); - if (s && s->client == ci) { - damage(s->x, s->y, (int)s->w, (int)s->h); - if (s->pixels) - shmdt(s->pixels); - shmctl(s->shmid, IPC_RMID, NULL); - memset(s, 0, sizeof *s); - return 1; - } - return 0; - } - - default: - /* Unknown but well-formed: skipped, not fatal. That is what the length - * field is for, and it is how a v0 compositor survives a v1 client. */ - return 0; - } -} - -/* ----------------------------------------------------------------- input */ - -/* 1 if the screen needs repainting, 0 if the event changed nothing, -1 to - * quit, -2 when the queue is empty. The last is separate from 0 on purpose: - * every burst of movement ends in an EV_SYN that changes nothing, and a - * drain loop that stopped there would leave the rest of the burst queued. */ -static int -handle_input(int fd) -{ - struct input_event ev; - if (read(fd, &ev, sizeof ev) != (ssize_t)sizeof ev) - return -2; - - int repaint = 0; - - /* An absolute pointer says where it is; a relative one says how far it - * moved. Taking the absolute value as authoritative is the whole point of - * usb-tablet: two pointers that accumulate deltas independently drift - * apart, which inside a VM shows up as the guest cursor sitting somewhere - * the host's is not. */ - if (ev.type == EV_ABS) { - int old_x = ptr_x, old_y = ptr_y; - if (ev.code == ABS_X) { ptr_x = ev.value; repaint = 1; } - if (ev.code == ABS_Y) { ptr_y = ev.value; repaint = 1; } - if (ptr_x < 0) ptr_x = 0; - if (ptr_y < 0) ptr_y = 0; - if (ptr_x >= (int32_t)fb_w) ptr_x = (int32_t)fb_w - 1; - if (ptr_y >= (int32_t)fb_h) ptr_y = (int32_t)fb_h - 1; - if (repaint) { - damage(old_x, old_y, PTR_SIZE, PTR_SIZE); - damage(ptr_x, ptr_y, PTR_SIZE, PTR_SIZE); - } - } - - if (ev.type == EV_REL) { - int old_x = ptr_x, old_y = ptr_y; - if (ev.code == REL_X) { ptr_x += ev.value; repaint = 1; } - if (ev.code == REL_Y) { ptr_y += ev.value; repaint = 1; } - if (ptr_x < 0) ptr_x = 0; - if (ptr_y < 0) ptr_y = 0; - if (ptr_x >= (int32_t)fb_w) ptr_x = (int32_t)fb_w - 1; - if (ptr_y >= (int32_t)fb_h) ptr_y = (int32_t)fb_h - 1; - if (repaint) { - /* Where it was and where it is. Everything between is untouched. */ - damage(old_x, old_y, PTR_SIZE, PTR_SIZE); - damage(ptr_x, ptr_y, PTR_SIZE, PTR_SIZE); - } - } - - if (ev.type == EV_KEY && ev.value == 1 && ev.code == KEY_ESC) - return -1; - - /* Route to the topmost surface. "Topmost" is the whole focus model in v0; - * anything cleverer is policy and belongs to SchWM. */ - struct surface *top = NULL; - for (int i = 0; i < MAX_SURFACES; i++) - if (surfaces[i].used) - top = &surfaces[i]; - - if (top && (ev.type == EV_KEY || ev.type == EV_REL || - ev.type == EV_ABS)) { - struct schcom_input msg; - memset(&msg, 0, sizeof msg); - msg.hdr.op = SCHCOM_OP_INPUT; - msg.hdr.length = sizeof msg; - msg.hdr.surface = top->id; - msg.type = ev.type; - msg.code = ev.code; - msg.value = ev.value; - msg.x = ptr_x; - msg.y = ptr_y; - if (clients[top->client].fd >= 0) - send_all(clients[top->client].fd, &msg, sizeof msg); - } - - return repaint; -} - -/* ------------------------------------------------------------------ main */ - -int -main(int argc, char **argv) -{ - int frames_then_exit = 0; /* --frames N: for automated testing */ - - for (int i = 1; i < argc; i++) - if (!strcmp(argv[i], "--frames") && i + 1 < argc) - frames_then_exit = atoi(argv[++i]); - - for (int i = 0; i < MAX_CLIENTS; i++) - clients[i].fd = -1; - - open_framebuffer(); - ptr_x = (int32_t)fb_w / 2; - ptr_y = (int32_t)fb_h / 2; - - unlink(SCHCOM_SOCKET_PATH); - int lfd = socket(AF_UNIX, SOCK_STREAM, 0); - if (lfd < 0) - die("socket"); - - struct sockaddr_un addr; - memset(&addr, 0, sizeof addr); - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, SCHCOM_SOCKET_PATH, sizeof addr.sun_path - 1); - if (bind(lfd, (struct sockaddr *)&addr, sizeof addr) < 0) - die("bind " SCHCOM_SOCKET_PATH); - if (listen(lfd, 8) < 0) - die("listen"); - - int kbd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); - int mouse = open("/dev/input/event1", O_RDONLY | O_NONBLOCK); - - printf("schcom: listening on %s\n", SCHCOM_SOCKET_PATH); - fflush(stdout); - - damage_all(); - composite(); - int frames = 1; - - for (;;) { - struct pollfd pfd[MAX_CLIENTS + 3]; - int n = 0; - - pfd[n].fd = lfd; pfd[n].events = POLLIN; n++; - int kbd_slot = -1, mouse_slot = -1; - if (kbd >= 0) { kbd_slot = n; pfd[n].fd = kbd; pfd[n].events = POLLIN; n++; } - if (mouse >= 0) { mouse_slot = n; pfd[n].fd = mouse; pfd[n].events = POLLIN; n++; } - - int first_client = n; - for (int i = 0; i < MAX_CLIENTS; i++) - if (clients[i].fd >= 0) { - pfd[n].fd = clients[i].fd; - pfd[n].events = POLLIN; - n++; - } - - if (poll(pfd, n, 100) < 0) { - if (errno == EINTR) - continue; - die("poll"); - } - - int repaint = 0; - - if (pfd[0].revents & POLLIN) { - int cfd = accept(lfd, NULL, NULL); - if (cfd >= 0) { - int slot = -1; - for (int i = 0; i < MAX_CLIENTS; i++) - if (clients[i].fd < 0) { slot = i; break; } - if (slot < 0) { - close(cfd); - } else { - clients[slot].fd = cfd; - clients[slot].hello_done = 0; - printf("schcom: client %d connected\n", slot); - fflush(stdout); - } - } - } - - /* Drain both devices before painting anything. A single mouse - * movement produces a burst of EV_REL followed by EV_SYN, and - * repainting per event meant the compositor rendered every - * intermediate position of a cursor nobody saw at those positions -- - * which is most of what "the mouse lags" was. */ - int quit = 0; - if (kbd_slot >= 0 && (pfd[kbd_slot].revents & POLLIN)) { - for (int n = 0; n < 256; n++) { - int r = handle_input(kbd); - if (r == -2) break; - if (r < 0) { quit = 1; break; } - if (r > 0) repaint = 1; - } - } - if (!quit && mouse_slot >= 0 && (pfd[mouse_slot].revents & POLLIN)) { - for (int n = 0; n < 256; n++) { - int r = handle_input(mouse); - if (r == -2) break; - if (r < 0) { quit = 1; break; } - if (r > 0) repaint = 1; - } - } - if (quit) break; - - /* Walk the client slots in the same order they were added to pfd. */ - int k = first_client; - for (int i = 0; i < MAX_CLIENTS; i++) { - if (clients[i].fd < 0) - continue; - if (pfd[k].revents & (POLLIN | POLLHUP)) - repaint |= handle_message(i); - k++; - } - - if (repaint) { - composite(); - frames++; - if (frames_then_exit && frames >= frames_then_exit) { - printf("schcom: %d frames, exiting as asked\n", frames); - fflush(stdout); - break; - } - } - } - - unlink(SCHCOM_SOCKET_PATH); - return 0; -} diff --git a/tools/posix/schcom_test.sh b/tools/posix/schcom_test.sh deleted file mode 100644 index 13986f8..0000000 --- a/tools/posix/schcom_test.sh +++ /dev/null @@ -1,154 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-2.0-or-later -# -# Run Schcom and a client inside SchOS, then check the actual pixels from the -# host with QEMU's screendump. The host has no connection to the compositor, so -# it is a real oracle: if the block is the right colour in the right place, the -# buffer really was shared, composited and presented. -# -# One display device on purpose (-vga std, no virtio-gpu): with two, screendump -# captures whichever QEMU considers active, which need not be the one /dev/fb0 -# maps. -set -e -# Run from anywhere: resolve the repo root from this script's own location -# rather than trusting the caller's working directory. Everything below is -# relative to it, and running from tools/posix/ used to fail with "missing -# build/kernel/kernel -- run make first" on a tree that was perfectly built. -script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -repo=$(CDPATH= cd -- "$script_dir/../.." && pwd) -cd "$repo" - -BUILD=${BUILD:-build} -mkdir -p "$BUILD" - -LOG=$BUILD/schcom-test.log -PPM=${1:-$BUILD/schcom.ppm} -rm -f "$LOG" "$PPM" - -# The firmware varstore is written by OVMF, so this run gets its own copy -# rather than sharing the pristine one every other run starts from. -cp -f build/OVMF_VARS.fd build/schcom-OVMF_VARS.fd - -PORT=$(python3 - <<'EOF' -import socket -s=socket.socket(); s.bind(('127.0.0.1',0)); print(s.getsockname()[1]); s.close() -EOF -) - -( - sleep 26 - printf 'schcom --frames 24 &\n'; sleep 6 - printf 'schcom-demo --colour 0x00C04020 --at 100 80 --size 240 160 --wait 40 &\n'; sleep 10 - printf 'schcom-demo --colour 0x002060C0 --at 260 180 --size 240 160 --wait 30 &\n'; sleep 12 - printf '\n' - sleep 40 -) | timeout -k 5 120 qemu-system-x86_64 \ - -machine pc-i440fx-9.2 -cpu qemu64,+rdtscp \ - -drive if=pflash,format=raw,unit=0,readonly=on,file=build/OVMF_CODE.fd \ - -drive if=pflash,format=raw,unit=1,file=build/schcom-OVMF_VARS.fd \ - -drive file=fat:rw:build/esp,index=0,media=disk,format=raw \ - -drive file=build/schfs.img,format=raw,index=1,media=disk \ - -m 1G -smp 1 \ - -display none -vga std \ - -monitor tcp:127.0.0.1:$PORT,server,nowait \ - -chardev stdio,id=c0,logfile="$LOG" -serial chardev:c0 \ - >/dev/null 2>&1 & -QPID=$! - -# Wait until *both* clients have committed, then photograph. Breaking on the -# first one screenshots a desktop the second client has not reached yet, which -# looks exactly like a compositor that cannot handle two clients. -for i in $(seq 1 90); do - [ "$(grep -ac 'committed' "$LOG" 2>/dev/null)" -ge 2 ] && break - sleep 1 -done -sleep 2 - -python3 - "$PORT" "$PPM" <<'EOF' -import socket, sys, time -port, ppm = int(sys.argv[1]), sys.argv[2] -s = socket.create_connection(('127.0.0.1', port), timeout=10) -time.sleep(0.5) -s.sendall(b'screendump ' + ppm.encode() + b'\n') -time.sleep(2.0) -s.close() -EOF - -sleep 1 -kill -9 $QPID 2>/dev/null || true -wait 2>/dev/null || true - -echo "=== guest output ===" -sed 's/\x1b\[[0-9;=]*[A-Za-z]//g' "$LOG" | tr -d '\r' | grep -aE 'schcom|surface|committed|listening' | head -12 - -echo "=== pixel check (host oracle) ===" -python3 - "$PPM" <<'EOF' -import sys -path = sys.argv[1] -try: - data = open(path, 'rb').read() -except FileNotFoundError: - print("FAIL: no screendump produced"); sys.exit(1) - -# Binary PPM: P6, whitespace-separated width height maxval, then RGB triples. -def token(buf, i): - while buf[i:i+1].isspace(): i += 1 - j = i - while not buf[j:j+1].isspace(): j += 1 - return buf[i:j], j - -if data[:2] != b'P6': - print("FAIL: not a P6 PPM"); sys.exit(1) -i = 2 -w, i = token(data, i); h, i = token(data, i); mx, i = token(data, i) -w, h = int(w), int(h) -px = data[i+1:] -print(f" screendump {w}x{h}") - -def at(x, y): - o = (y*w + x)*3 - return px[o], px[o+1], px[o+2] - -def near(got, want, tol=8): - return all(abs(a-b) <= tol for a, b in zip(got, want)) - -fails = 0 -inside = at(150, 120) # middle of the 240x160 block at 100,80 -if near(inside, (0xC0, 0x40, 0x20)): - print(f" [ok] surface pixel at 150,120 is {inside} -- the client's colour") -else: - print(f" FAIL: 150,120 is {inside}, expected (192, 64, 32)"); fails += 1 - -desktop = at(20, 20) # compositor background 0x203040 -if near(desktop, (0x20, 0x30, 0x40)): - print(f" [ok] desktop pixel at 20,20 is {desktop} -- compositor background") -else: - print(f" FAIL: 20,20 is {desktop}, expected (32, 48, 64)"); fails += 1 - -border = at(100, 80) # top-left of the first surface -if near(border, (0xFF, 0xFF, 0xFF)): - print(f" [ok] border pixel at 100,80 is {border} -- surface landed exactly") -else: - print(f" FAIL: 100,80 is {border}, expected white border"); fails += 1 - -# Second client, created later, so it is above the first where they overlap. -second = at(460, 320) # inside surface 2, clear of the pointer -if near(second, (0x20, 0x60, 0xC0)): - print(f" [ok] second surface at 460,320 is {second} -- two clients composited") -else: - print(f" FAIL: 460,320 is {second}, expected (32, 96, 192)"); fails += 1 - -# 260,180..340,240 is where the two overlap. The later surface must win: if -# stacking were ignored the first client's colour would show through here, and -# a compositor that cannot order two windows cannot host a window manager. -overlap = at(300, 220) -if near(overlap, (0x20, 0x60, 0xC0)): - print(f" [ok] overlap at 300,220 is {overlap} -- newer surface on top") -elif near(overlap, (0xC0, 0x40, 0x20)): - print(f" FAIL: overlap shows the older surface -- stacking order ignored"); fails += 1 -else: - print(f" FAIL: overlap at 300,220 is {overlap}, expected the newer surface"); fails += 1 - -print("schcom end-to-end: PASSED" if not fails else f"schcom end-to-end: {fails} FAILED") -sys.exit(1 if fails else 0) -EOF