#include "protocol.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern char **environ; static bool transfer(int fd, void *data, size_t size, bool writing) { uint8_t *cursor = data; while (size) { if (!writing) { struct pollfd item = {.fd = fd, .events = POLLIN}; if (poll(&item, 1, 5000) <= 0) return false; } ssize_t count = writing ? write(fd, cursor, size) : read(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 double seconds_since(const struct timespec *start) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return (double)(now.tv_sec - start->tv_sec) + (double)(now.tv_nsec - start->tv_nsec) / 1e9; } int main(int argc, char **argv) { if (argc != 2 && argc != 5 && argc != 6) return 2; int control[2], frames[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, control) || pipe(frames)) return 2; const uint32_t width = argc > 2 ? (uint32_t)strtoul(argv[2], NULL, 10) : 1280; const uint32_t height = argc > 3 ? (uint32_t)strtoul(argv[3], NULL, 10) : 720; const uint32_t requested_fps = argc > 4 ? (uint32_t)strtoul(argv[4], NULL, 10) : 30; size_t shared_size = owb_shared_size(width, height); int shared_fd = memfd_create("renderer-fps-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) return 2; const char url[] = "data:text/html,
"; const char css_blur[] = "html,body{margin:0;background:transparent;overflow:hidden}" "body:after{content:'';position:fixed;left:0;top:0;width:32px;height:32px;" "animation:probe .73s linear infinite alternate}" "#card{position:absolute;left:15%;top:20%;width:45%;height:55%;" "background:rgba(30,150,255,.75);will-change:transform,filter;" "animation:fx 1s ease-in-out infinite alternate}" "@keyframes fx{from{transform:translateX(0) scale(.92);filter:blur(2px)}" "to{transform:translateX(45%) scale(1.08);filter:blur(18px)}}" "@keyframes probe{from{background:#000}to{background:#fff}}"; const char css_plain[] = "html,body{margin:0;background:transparent;overflow:hidden}" "body:after{content:'';position:fixed;left:0;top:0;width:32px;height:32px;" "animation:probe .73s linear infinite alternate}" "#card{position:absolute;left:15%;top:20%;width:45%;height:55%;" "background:rgba(30,150,255,.75);animation:fx 1s ease-in-out infinite alternate}" "@keyframes fx{from{transform:translateX(0) scale(.92)}" "to{transform:translateX(45%) scale(1.08)}}" "@keyframes probe{from{background:#000}to{background:#fff}}"; const char *css = argc == 6 && strstr(argv[5], "plain") != NULL ? css_plain : css_blur; size_t css_length = strlen(css); struct owb_config_header config = { .magic = OWB_CONFIG_MAGIC, .version = OWB_PROTOCOL_VERSION, .width = width, .height = height, .fps = requested_fps, .transparent = 1, .zoom_percent = 100, .hardware_acceleration = 1, .url_length = sizeof(url) - 1, .css_length = (uint32_t)css_length, }; bool okay = transfer(control[0], &config, sizeof(config), true) && transfer(control[0], (void *)url, sizeof(url) - 1, true) && transfer(control[0], (void *)css, css_length, true); size_t frame_size = (size_t)width * height * 4; uint8_t *pixels = malloc(frame_size); unsigned frames_read = 0; unsigned changed_frames = 0; uint64_t previous_hash = 0; struct timespec start = {0}; while (okay) { uint8_t slot; if (!transfer(frames[0], &slot, sizeof(slot), false) || slot >= OWB_SHARED_FRAME_SLOTS) break; bool stable = false; for (unsigned attempt = 0; attempt < 3 && !stable; attempt++) { uint64_t before = __atomic_load_n(&shared->sequence[slot], __ATOMIC_ACQUIRE); if (before & 1u) continue; memcpy(pixels, owb_shared_slot(shared, slot), frame_size); uint64_t after = __atomic_load_n(&shared->sequence[slot], __ATOMIC_ACQUIRE); stable = before == after && !(after & 1u); } if (!stable) continue; uint64_t hash = 1469598103934665603ull; for (uint32_t y = 0; y < height; y += 16) { for (uint32_t x = 0; x < width; x += 16) { size_t offset = ((size_t)y * width + x) * 4; uint32_t sample; memcpy(&sample, pixels + offset, sizeof(sample)); hash ^= sample; hash *= 1099511628211ull; } } if (!frames_read || hash != previous_hash) changed_frames++; previous_hash = hash; if (!frames_read) clock_gettime(CLOCK_MONOTONIC, &start); frames_read++; if (seconds_since(&start) >= 4.0) break; } double elapsed = frames_read ? seconds_since(&start) : 0.0; double fps = elapsed > 0.0 ? (double)(frames_read - 1) / elapsed : 0.0; double changed_fps = elapsed > 0.0 ? (double)(changed_frames - 1) / elapsed : 0.0; fprintf(stderr, "blur-transition: %u frames in %.3f s = %.2f FPS; %u changed = %.2f FPS " "(requested %u)\n", frames_read, elapsed, fps, changed_frames, changed_fps, requested_fps); free(pixels); munmap(shared, shared_size); close(shared_fd); shutdown(control[0], SHUT_RDWR); close(control[0]); close(frames[0]); 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; return frames_read && fps >= requested_fps * 0.80 && changed_fps >= requested_fps * 0.50 ? 0 : 1; }