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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
parent
6e78bb0d5e
commit
ed81f9a07a
21 changed files with 62 additions and 1356 deletions
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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 <stdint.h>
|
||||
|
||||
#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 */
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -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 <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/shm.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -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 <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/shm.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <linux/fb.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
Loading…
Reference in a new issue