Woah. It was actually not too difficult to get that working: I’ve now bundled Chromium Embedded Framework with required dependencies into an info-beamer package. It’s only about 170MB in size total. It can either run as a standalone, like the existing full screen browser, or be added as a plugin to the HD player version 2 package that will be out soon. The latter then allows you to freely mix browser content with images and videos, all running fully inside info-beamer.
The HD player V2 also has video wall and synced playback support that that also works with the browser plugin too. Assuming a configured website produces the same results when fetched from different devices at roughly the same time, the content can be spread across multiple displays.
About the content process API: It now works as follows:
local p = resource.create_content_process{
file = "browser.exe",
width = 800,
height = 800,
args = {"https://info-beamer.com"}
}
By convention, info-beamer OS will now mark content with the .exe
extension as executable. The above code will fork a background process, do a few sandboxing steps (the plugins run as their own user ID) and optionally overlay mount an package-provided overlay.squashfs
file system that can be shared with a package service. It then spawns the process with file descriptor 3 being a DMA handle, fd 2 being debug output, \n
separated lines written to fd 1 ending up in p:status()
and fd 0 being fed by what’s provided via p:command("foo")
. Everything in args
is provided as normal command line arguments in argv
.
p:state()
works similarlay to how images or videos work. It’s first in the "loading"
state and once the first new line separated string is send to fd 1, the state switches to "loaded"
. This allows the background process to signal when its ready and at best already filled a first frame with content. Exiting with exit status 0
results in the state switching to "finished"
while any other exit status result in "error"
.
Explicitly calling p:dispose()
or a garbage collection sends a SIGKILL to the process. There is no nice way to end content processes. They are expected to handle this. The content process currently has no way to write any persistent files: Everything is fully disposed once the content process object is garbage collected. The advantage is that there is never any leaked state across multiple invocations of the same executable.
Here’s an example C program that produces fully red output:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/dma-buf.h>
#define DMA_FD 3
static void die(const char *msg) {
fprintf(stderr, "fail: %s\n", msg);
exit(1);
}
int main(int argc, const char **argv) {
int width = atoi(getenv("WIDTH"));
int height = atoi(getenv("HEIGHT"));
char *mem = mmap(NULL, width * height * 4, PROT_WRITE, MAP_SHARED, DMA_FD, 0);
if (mem == MAP_FAILED)
die("cannot map DMA-buf");
fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) | O_NONBLOCK);
while (1) {
struct dma_buf_sync sync;
char buf[200];
int ret = read(0, buf, 200);
if (ret >= 0)
fprintf(stderr, "COMMAND: '%.*s'\n", ret, buf);
sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE;
ret = ioctl(DMA_FD, DMA_BUF_IOCTL_SYNC, &sync);
if (ret)
die("sync failed");
char bgra[4] = {0, 0, 255, 255}; // Currently BGRA
for (int i = 0; i < width * height * 4; i+=4) {
memcpy(mem+i, bgra, 4);
}
sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE;
ret = ioctl(DMA_FD, DMA_BUF_IOCTL_SYNC, &sync);
if (ret)
die("sync failed");
if (i == 0)
write(1, "ready\n", 6); // signal being ready: switches to "loaded"
usleep(16600);
}
return 0;
}
Similar to a browser, it actually shouldn’t be too difficult to use something like mupdf and its C API to render PDF files the same way.
While that’s all pretty exciting, one downside is that this will only work on the Pi4 and Pi5. Theoretically the Pi3 should be able to run the required KMS/DRM based version of info-beamer as well, but in my experiments, that’s quite slow compared to the broadcom APIs.