215 lines
6.9 KiB
C
215 lines
6.9 KiB
C
#include "protocol.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <spawn.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
extern char **environ;
|
|
|
|
static bool write_exact(int fd, const void *data, size_t size)
|
|
{
|
|
const uint8_t *cursor = data;
|
|
while (size) {
|
|
ssize_t count = write(fd, cursor, size);
|
|
if (count < 0 && errno == EINTR)
|
|
continue;
|
|
if (count <= 0)
|
|
return false;
|
|
cursor += (size_t)count;
|
|
size -= (size_t)count;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool read_frame(int fd, uint8_t *pixels, size_t size, int timeout_ms)
|
|
{
|
|
size_t used = 0;
|
|
while (used < size) {
|
|
struct pollfd pollfd = {.fd = fd, .events = POLLIN};
|
|
int ready = poll(&pollfd, 1, timeout_ms);
|
|
if (ready <= 0)
|
|
return false;
|
|
ssize_t count = read(fd, pixels + used, size - used);
|
|
if (count < 0 && errno == EINTR)
|
|
continue;
|
|
if (count <= 0)
|
|
return false;
|
|
used += (size_t)count;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
if (argc < 2 || argc > 4) {
|
|
fprintf(stderr,
|
|
"usage: %s /path/to/obs-webkit-renderer [opaque | zoom | opaque-url URL]\n",
|
|
argv[0]);
|
|
return 2;
|
|
}
|
|
bool external_url = argc == 4 && strcmp(argv[2], "opaque-url") == 0;
|
|
bool zoom = argc == 3 && strcmp(argv[2], "zoom") == 0;
|
|
bool opaque = external_url || zoom || (argc == 3 && strcmp(argv[2], "opaque") == 0);
|
|
int control[2];
|
|
int frames[2];
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, control) || pipe(frames)) {
|
|
perror("IPC");
|
|
return 2;
|
|
}
|
|
const uint32_t width = external_url ? 1890 : 64;
|
|
const uint32_t height = external_url ? 660 : 64;
|
|
size_t shared_size = owb_shared_size(width, height);
|
|
int shared_fd = memfd_create("renderer-smoke-frames", MFD_CLOEXEC);
|
|
if (shared_fd < 0 || ftruncate(shared_fd, (off_t)shared_size) != 0)
|
|
return 2;
|
|
struct owb_shared_frames *shared = mmap(NULL, shared_size, PROT_READ | PROT_WRITE,
|
|
MAP_SHARED, shared_fd, 0);
|
|
if (shared == MAP_FAILED)
|
|
return 2;
|
|
memset(shared, 0, sizeof(*shared));
|
|
shared->magic = OWB_SHARED_MAGIC;
|
|
shared->version = OWB_PROTOCOL_VERSION;
|
|
shared->width = width;
|
|
shared->height = height;
|
|
shared->stride = width * 4;
|
|
shared->slot_count = OWB_SHARED_FRAME_SLOTS;
|
|
posix_spawn_file_actions_t actions;
|
|
posix_spawn_file_actions_init(&actions);
|
|
posix_spawn_file_actions_adddup2(&actions, control[1], STDIN_FILENO);
|
|
posix_spawn_file_actions_adddup2(&actions, frames[1], STDOUT_FILENO);
|
|
posix_spawn_file_actions_adddup2(&actions, shared_fd, OWB_SHARED_FRAME_FD);
|
|
char *child_argv[] = {argv[1], NULL};
|
|
pid_t child = 0;
|
|
int error = posix_spawn(&child, argv[1], &actions, NULL, child_argv, environ);
|
|
posix_spawn_file_actions_destroy(&actions);
|
|
close(control[1]);
|
|
close(frames[1]);
|
|
if (error) {
|
|
fprintf(stderr, "posix_spawn: %s\n", strerror(error));
|
|
return 2;
|
|
}
|
|
|
|
const char internal_url[] =
|
|
"data:text/html,<body style='background:rgb(3,5,7)'><div id='box'></div></body>";
|
|
const char transparent_css[] = "html,body{margin:0}"
|
|
"#box{position:fixed;inset:16px;background:rgba(255,0,0,.5)}";
|
|
const char opaque_css[] = "html,body{margin:0;background:rgb(7,19,31)!important}"
|
|
"#box{position:fixed;left:16px;top:16px;width:32px;height:32px;"
|
|
"background:rgb(241,23,17);transform:translateZ(0);filter:blur(.1px)}";
|
|
const char zoom_css[] = "html,body{margin:0;background:rgb(7,19,31)!important}"
|
|
"#box{position:fixed;left:0;top:0;width:10px;height:10px;"
|
|
"background:rgb(241,23,17)}";
|
|
const char *url = external_url ? argv[3] : internal_url;
|
|
const char *css = external_url ? "" : zoom ? zoom_css : opaque ? opaque_css : transparent_css;
|
|
struct owb_config_header config = {
|
|
.magic = OWB_CONFIG_MAGIC,
|
|
.version = OWB_PROTOCOL_VERSION,
|
|
.width = width,
|
|
.height = height,
|
|
.fps = 10,
|
|
.zoom_percent = zoom ? 200 : 100,
|
|
.transparent = opaque ? 0 : 1,
|
|
/* GPU rendering must still produce an invisible captured frame. */
|
|
.hardware_acceleration = 1,
|
|
.url_length = (uint32_t)strlen(url),
|
|
.css_length = (uint32_t)strlen(css),
|
|
};
|
|
bool sent = write_exact(control[0], &config, sizeof(config)) &&
|
|
write_exact(control[0], url, strlen(url)) &&
|
|
write_exact(control[0], css, strlen(css));
|
|
uint8_t last_bgra[4] = {0};
|
|
uint8_t last_corner_bgra[4] = {0};
|
|
bool found_red = false;
|
|
int max_attempts = external_url ? 120 : 60;
|
|
for (int attempt = 0; sent && attempt < max_attempts; attempt++) {
|
|
uint8_t slot;
|
|
if (!read_frame(frames[0], &slot, sizeof(slot), 1000))
|
|
continue;
|
|
if (slot >= OWB_SHARED_FRAME_SLOTS)
|
|
continue;
|
|
uint8_t *frame = owb_shared_slot(shared, slot);
|
|
if (external_url) {
|
|
bool nonuniform = false;
|
|
uint32_t first;
|
|
memcpy(&first, frame, sizeof(first));
|
|
for (uint32_t y = 0; y < height && !nonuniform; y += 8) {
|
|
for (uint32_t x = 0; x < width; x += 8) {
|
|
uint32_t sample;
|
|
memcpy(&sample, frame + ((size_t)y * width + x) * 4,
|
|
sizeof(sample));
|
|
if (sample != first) {
|
|
nonuniform = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
found_red = nonuniform;
|
|
if ((attempt + 1) % 10 == 0)
|
|
fprintf(stderr, "external page at %.1fs: %s\n",
|
|
(double)(attempt + 1) / 10.0,
|
|
nonuniform ? "content" : "uniform");
|
|
continue;
|
|
}
|
|
size_t center = zoom ? ((size_t)5 * width + 15) * 4
|
|
: ((size_t)(height / 2) * width + width / 2) * 4;
|
|
memcpy(last_bgra, frame + center, sizeof(last_bgra));
|
|
size_t corner = zoom ? ((size_t)5 * width + 25) * 4 : 0;
|
|
memcpy(last_corner_bgra, frame + corner, sizeof(last_corner_bgra));
|
|
bool expected = zoom
|
|
? last_bgra[0] < 32 && last_bgra[1] < 40 && last_bgra[2] > 210 &&
|
|
last_bgra[3] == 255 && last_corner_bgra[0] > 20 &&
|
|
last_corner_bgra[1] < 32 && last_corner_bgra[2] < 32 &&
|
|
last_corner_bgra[3] == 255
|
|
: opaque
|
|
? last_bgra[0] < 32 && last_bgra[1] < 40 && last_bgra[2] > 210 &&
|
|
last_bgra[3] == 255
|
|
: last_bgra[0] < 8 && last_bgra[1] < 8 && last_bgra[2] > 248 &&
|
|
last_bgra[3] > 120 && last_bgra[3] < 136 &&
|
|
last_corner_bgra[0] == 0 && last_corner_bgra[1] == 0 &&
|
|
last_corner_bgra[2] == 0 && last_corner_bgra[3] == 0;
|
|
if (expected) {
|
|
found_red = true;
|
|
break;
|
|
}
|
|
}
|
|
shutdown(control[0], SHUT_RDWR);
|
|
close(control[0]);
|
|
close(frames[0]);
|
|
munmap(shared, shared_size);
|
|
close(shared_fd);
|
|
kill(child, SIGTERM);
|
|
int child_status = 0;
|
|
while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {
|
|
}
|
|
if (WIFEXITED(child_status) && WEXITSTATUS(child_status) == 5)
|
|
return 77;
|
|
if (!found_red) {
|
|
if (external_url) {
|
|
fprintf(stderr, "renderer kept the external page uniformly colored: %s\n", url);
|
|
return 1;
|
|
}
|
|
fprintf(stderr,
|
|
"renderer did not produce the expected %s red BGRA frame "
|
|
"(center BGRA=%u,%u,%u,%u; corner BGRA=%u,%u,%u,%u)\n",
|
|
zoom ? "200%-zoomed" : opaque ? "opaque" : "transparent",
|
|
last_bgra[0], last_bgra[1],
|
|
last_bgra[2], last_bgra[3], last_corner_bgra[0], last_corner_bgra[1],
|
|
last_corner_bgra[2], last_corner_bgra[3]);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|