Nexoniarz is the sole author and SchOS was never published under Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text, every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE, README.md, docs/licensing.md and the site say so. projects/schfs-linux follows the rest of the tree. The GPL gives copyleft: a fork has to ship its source. It also lifts the old "Linux drivers, never" rule, since GPL-2.0-only code combines with GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still has to pick one version, so docs/licensing.md now keeps Apache-2.0 and GPLv3 code out of the kernel to keep Linux code possible there. BSD 4-clause code goes from "avoid" to "no", because the advertising clause is GPL-incompatible. LDLS still ships no .ko. That boundary is now project policy rather than a legal requirement, and ldls.md, devices.md and licensing.md section 6 say so. refmod declares license=GPL, and ldls-check passes (27 checks). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
149 lines
5.1 KiB
C
149 lines
5.1 KiB
C
/* 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;
|
|
}
|