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 92d09d6..0000000
Binary files a/projects/schcom/schcom and /dev/null differ
diff --git a/projects/schcom/schcom-demo b/projects/schcom/schcom-demo
deleted file mode 100644
index d6cdb49..0000000
Binary files a/projects/schcom/schcom-demo and /dev/null differ
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