Category Archives: Uncategorized

Wayfire on WSLg

You heard right, wayfire and other wlroots compositors can now run with vulkan or gles renderers on WSLg (Windows Subsystem for Linux GUI)! Before getting too excited, there is a catch.. it runs entirely in software without a render node, on wlroots headless backend. It is viewable with wayvnc (VNC) or wf-ogon plugin (RDP). This is possible with the advent of the udmabuf allocator in wlroots. It is used in conjunction with llvmpipe to render directly into RAM. The udmabuf allocator is new in latest wlroots, so it has yet to be released. However, I have taken the liberty of backporting the code to wlroots 0.18.2. I have uploaded the code to the udmabuf branch on github here. Without any modifications to wayfire GL code, wayfire runs in software. To do this, the WSL kernel must be compiled with `CONFIG_UDMABUF=y`, since this option is not default. This gives us /dev/udmabuf which key for the functionality needed for userspace dynamically allocated buffers. To get this working, latest mesa 25.x.x is required. Build wayfire with the following:

meson setup -Duse_system_wlroots=disabled -Dwlroots:c_std=c11 -Dwlroots:allocators=gbm,udmabuf --prefix=/usr build
ninja -C build
sudo -E ninja -C build install

and run it with

LP_NUM_THREADS=0 WLR_RENDERER_FORCE_SOFTWARE=1 WLR_BACKENDS=headless wayfire

Don’t forget to load wayvnc in [autostart] section to connect with a vnc client or load the wf-ogon plugin and connect with an rdp client. I didn’t actually try this on WSLg, but I helped those that did get it working. So I don’t have a video of my own, but this is from a user that did run it:

Cutting Corners

At this point, wayfire has been gaining momentum and the pixdecor plugin is becoming more popular. One unsung hero has been lurking however, and that is the filters plugin. The filters plugin allows a glsl shader to be applied from a file on disk to a window, or the entire screen. It comes with a few shaders to do basic things like CRT effect, color inversion and borders. The borders shader is interesting, since it renders a border of color and width on a window surface. However, with pixdecor shadows, the border doesn’t wrap the decoration, it wraps the outermost part of the shadow region. This is not what we want to happen. Meanwhile, in the CSD (client side decorations) case, the shadow area must be computed to know where to render the border. At first, this task seemed nearly impossible. But for the SSD (server side decorations) case of pixdecor, I was able to side band the shadow margin size to the filters plugin using a globally scoped class, shared between the two out-of-tree plugins. This is as if the class were defined once in a common header. Since no such header exists, we just copy and share it. This happens to work very well and allows filters to get the shadow margin size that pixdecor set. For CSD windows, the filter shader can find the shadow margins easily, with a couple of calls using wayfire’s robust plugin API. After all the rest was ironed out, the results are quite awesome. Here is a basic video to showcase the two plugins working simultaneously.

Here is the pixdecor shade effect with filters plugin:

More Wayfire Animations

In this post, we introduce two more animations, shatter and vortex. These two build on the concepts and infrastructure found in the previous animation code. I will focus more on the shatter animation, as it is more technically involving.

Shatter, as you might have guessed from the name, is an animation that fragments the surface into polygons and explodes them into the scene, when closing a window. When opening a window, the reverse implosion is in order. To do this, a way was needed to fragment the rectangular surface into irregular convex polygons. After searching around a bit on the web, I noticed the Voronoi diagrams concept. After learning that boost library offered some useful functions, it was off to test them.

At first, creating a Voronoi diagram was confusing. But after rendering it visually, it started to make sense. Input random points over the surface geometry and get output of convex polygons, suitable for rendering each as a triangle fan. With this in hand, the next step was obvious: rotate the polygons and translate them into an exploding fashion. However there was one small problem. The library doesn’t offer the center point of the polygon, required for rotation. After thinking a bit, I found a simple solution: compute the bounding box of each polygon and take the center of that. This worked remarkably well as it was fast. The only caveat is that the code walk the cells twice, once to get all the points required to know the bounding box and thus center point, and another to use this information when writing the vertices that the shader will use. The first iteration probably can be done once up front at animation creation instead of each frame, but this is yet to be implemented.

Vortex is a fragment shader that sucks the pixels into the center on close and the opposite on open. Now let’s watch some videos!

Streaming RTSP directly to browsers

Let’s not sugar coat it, streaming video is a complex topic. If every browser had the same video player, it might make things a bit easier. But of course, they don’t. So, I’ll try to dumb this down, keep it short and make it as simple as possible.

If you have some RTSP H.264 streams that you want to stream to Firefox or Chrome with minimal latency, you might be in for a treat. Using nodejs and ffmpeg, we can serve up the streams while convincing the browser to play the stream immediately while buffering with minimal latency. How? As I found, it is not easy. But let’s act like it was, shall we?

In a nutshell, a browser first makes an initial GET for the url and the server replies with a video/mp4 mime type header with 200 status. We start up ffmpeg and have it write to stdout but we don’t send any of the data on this request. This behooves the client to make a range request. Next, on the range request, the client is expecting a file so it asks for portions of it. Here, we always tell it the start is 0 and do some math to tell it what range and length to expect, based on the bytes we have so far, and send 206 status. Then, we start sending the data. Getting this right was only half the battle though..

TL;DR, I checked the agent string to see if it’s chrome or not. If it’s chrome, we use `ffmpeg -f matroska` but for firefox, we have to use `ffmpeg -f mp4` since it doesn’t have matroska support. The confusing thing was that the mime type could not be video/x-matroska because that caused the browser to attempt downloading the file. Now on to the code.

The following is a snippet to stream RTSP directly to firefox or chrome browsers:

function serve_rtsp(req, res, ip, user, pass) {
    user_agent_string = req.get('user-agent');
    const range = req.headers.range;
    if (range)
    {
        if (!running)
        {
            res.end();
            return;
        }
        const start = 0;
        const end = Math.max(total_length, 1024 * 1024 * 32);
        const chunk_size = end + 1;
        res.writeHead(206, {'Accept-Ranges': 'bytes', 'Content-Range': 'bytes ' + start + '-' + end + '/' + chunk_size, 'Content-length': chunk_size, 'Content-Type': 'video/mp4', 'Connection': 'keep-alive'});
        ffmpeg.stdout.unpipe();
        ffmpeg.stdout.pipe(res);
        return;
    } else
    {
        res.writeHead(200, {'Accept-Ranges': 'bytes', 'Content-Type': 'video/mp4', 'Connection': 'keep-alive'});
    }
    if (running)
    {
        ffmpeg.kill("SIGINT");
        total_length = 0;
        clearTimeout(ffmpeg_timer);
    }
    if (user_agent_string.includes("Chrome"))
    {
        ffmpeg = child_process.spawn("ffmpeg",[
        "-i", 'rtsp://' + user + ':' + pass + '@' + ip + ':80',
        "-f", "matroska",  // File format
        "-c", "copy",      // No transcoding
        "-t", "60",        // Timeout
        "-"                // Output (to stdout)
        ]);
    }
    else
    {
        ffmpeg = child_process.spawn("ffmpeg",[
        "-i", 'rtsp://' + user + ':' + pass + '@' + ip + ':80',
        "-f", "mp4",       // File format
        "-movflags", "empty_moov+frag_keyframe",
        "-c", "copy",      // No transcoding
        "-t", "60",        // Timeout
        "-"                // Output (to stdout)
        ]);
    }
    running = true;
    res.end();
    ffmpeg.stdout.on('data', ffmpeg_handle_data);
    ffmpeg_timer = setTimeout(function() {
        running = false;
    }, 60000);
}

I hope this is helpful to someone out there. If you do find it useful and want to say thanks, you can donate a coffee on northfield.ws.