Show HN: 1-FPS encrypted screen sharing for introverts (1fps.video)
I tired of sharing screen via Google Meet with 1-hour limitation, with Zoom and 40-minute limitation, etc. With paid Slack subscription. And often times I just needed to screenshare with no audio.
So I ended up with my own solution - no registration, low memory, low CPU, low tek 1 fps encrypted screen sharing. Currently sharing only the main screen (good for laptop users).
It's very raw in terms of infrastructure, since I'm not counting bytes (yikes!), everything works on my own dedicated server. But the service itself has been tested, we've been sharing screens for countless hours. All sessions last for 48 hours, then it gets removed with all remaining info.
Every new frame replaces the other, and everything is end-to-end encrypted so even server owners and operators won't be able to see what are you sharing.
There is also no tracking, except the main page - and I use my own analytics. Sessions are not getting tracked and never will be, and observability currently is not in place.
Again, this is a true one-person side hacking project I hope (but I have serious doubts) I might need to scale if it's getting traction to support more users.
132 comments
[ 2.7 ms ] story [ 208 ms ] threadhttps://github.com/go-vgo/robotgo?tab=readme-ov-file#require...
There are some prereqs not listed your page. On Mint 22 I had to install the libxtst-dev package.
> 1fps.video is perfect for introverts and remote workers who prefer sharing their screen without the pressure of audio or video calls. It's a versatile solution that works alongside any team chat application you're already using.
It seems closer to "text chat while sending screenshots" than "share screen in a voice call." I can see why some would prefer this.
It seems like it would be better to just send a screenshot and then discuss, so the other person doesn't have to watch you typing messages to them instead of looking at the actual thing you want to share.
> we use WebSocket-based cursor tracking, providing smooth, near 30 FPS pointer movement for precise demonstrations.
This part does not seem to support that use case, you don't need 30 FPS pointer tracking for text chat.. Moreover, it'd be actively bad, as the cursor is likely to be pointing to the text chat window.
Another use case if when you have a long-running meeting, and still need to share. I found I sometimes just do not sit and listen for those 1-2 hours meetings, but prefer to code.
And for those of us who just don't like audio, like myself. I have many students who I am willing to help, but I don't wanna get audio-involved. My voice chat is not a very parallelable resource.
What could be going on when you're not home?
If this is meant as a security tool, the fact you have to look at it is a non-starter.
If this is meant as anything else, why wouldn't you use VNC?
I've been wanting to create something WebRTC based, I'm not happy with either VNC or RDP.
On Linux, I've used both kinds of VNC server. One does start a new X instance, while the other one shares your main X instance. At the time I tried it, it was "TightVNCServer" to get a new X instance, and "X11vnc" to share the existing session.
Couple it with novnc if you want it in the web browser. Currently WebSockets but WebCodecs support looks to be around the corner[1].
> Terrible product design.
Which "product" are you even talking about here? VNC is a protocol with several different implementations.
[0]: https://tigervnc.org/doc/x0vncserver.html
[1]: https://github.com/novnc/noVNC/pull/1876
On what, your primary desktop?
Maybe you are just a bit Windows-centric but I would guess many of us run virtual desktops and/or other means of remote access such as ssh.
For general monitoring, maybe have a look at state of the art solutions, like https://prometheus.io/
Doesn't this require leaving the computer unlocked?
Our workflow is built around removing the need for an office and technical infrastructure. We have live streams of our timeline output (video editing) and an open comms channel. Most of our team is pretty introverted, so it's a push to talk system. We mostly just leave notes in the chat if it doesn't warrant a full discussion.
Crude solutions are often the ones that get adopted.
It definitely sounds like a great idea and an interesting problem.
This seems like it could get into the area of smartphone "cameras" that do so much computation on the output of the light sensors that it can hardly be called photography [0]. It's a cool idea (in chess I've heard a similar idea called "quiescence search"[1]), but probably not worth the trouble.
[0] https://old.reddit.com/r/Android/comments/11nzrb0/samsung_sp...
[1] https://en.wikipedia.org/wiki/Quiescence_search
Here's a pic, the ring is where my cursor actually is https://i.imgur.com/TvzskjS.png
1) You generate a random key [0] and then feed it into PBKDF2 [1] to generate a 32-byte AES-GCM key. If you can generate 32 random bytes instead of 10 reduced-ASCII characters and a key stretch, just do that. PBKDF2 is for turning a password into a key, and it's far from the recommended algorithm nowadays; prefer scrypt if you need to do this sort of thing.
2) AES-GCM with random 12-byte nonces. Never use random IVs with GCM; this breaks the authentication [2] [3]. Given the pitfalls of AES-GCM with respect to random nonces, you might prefer switching to XSalsa20+Poly1305. The advantage of XSalsa is it has an extended nonce length, so you can use random nonces without fear.
3) Random key derivation with a restricted character set can make brute force attacks easier. You should have a 256-bit random key, and if you want that key to be within a certain character set, then encode the byte output from the CSPRNG using that character set.
4) 1fps achieves symmetric key distribution via a URL with a fragment identifier ("#") which IIRC is not sent to the server. Therefore it assumes you have a secure key distribution channel - the link contains the key, so it's important that only the intended recipient can view the part after the "#". If the server is truly malicious, it can deploy client-side Javascript to send the fragment to the server, allowing the server to access the key (and thus cleartext communication).
[0]: https://github.com/1fpsvideo/1fps/blob/main/1fps.go#L99
[1]: https://github.com/1fpsvideo/1fps/blob/main/1fps.go#L287
[2]: https://eprint.iacr.org/2016/475.pdf
[3]: https://soatok.blog/2020/05/13/why-aes-gcm-sucks/
[0]: https://pkg.go.dev/golang.org/x/crypto/nacl/secretbox
But otherwise big +1 not to reimplement crypto when the are alternatives. Another option for secret key stuff might be ssh?
You still need to figure out how you handle trust and key authentication somehow, but that is true of all cryptographic protocols.
(a) you can't share one TLS connection to the host between multiple clients; if you wanted multi-client support while preserving end-to-end TLS, the host would need to maintain a TLS connection with each client and waste bandwidth re-uploading the same image
(b) there is no client software requirement, so you would have to do the TLS decryption clientside in the browser (maybe via WASM) unless you're OK with having viewers download software
Agree. When people hear the adage "don't roll your own crypto", they often think it refers to crypto primitives only. In reality, it's also hard to design a secure crypto protocol, even if the underlying crypto primitives are secure.
With TLS you need pubkeys you can trust (the certificate authority hierarchy provides that trust for the open Internet) or you're vulnerable to MITM. You could potentially share pubkeys using a similar out-of-band mechanism to that currently used for symmetric key distriubtion, and tunnel that TLS connection through the server's shared comms channel. That would work OK for two parties, but it becomes significantly more cumbersome if you want three or more, since each TLS session is a pairwise key exchange. Notably, however, this would not transit secret keys through server-controlled web pages where they could be available to Javascript. Something like Noise [0] might also be useful for a similar pubkey model.
Unfortunately, this kind of cryptography engineering is hard. Key distribution and exchange is hard. There isn't much of a way around learning the underlying material well enough to find this sort of issue yourself, but misuse-resistant libraries can help. Google's Tink [1] is misuse-resistant and provides a handful of blessed ways to do things such as key generation, but I'm not sure if it's suitable outside of cloud deployments with KMS solutions. nacl/secretbox handles straight encryption/decryption with sound primitives, but it still requires a correct means of key generation [2] and distribution.
[0]: http://www.noiseprotocol.org/noise.html
[1]: https://github.com/tink-crypto/tink-go
[2]: https://pkg.go.dev/golang.org/x/crypto/nacl/secretbox
Those papers are a bit over my head. Could you please explain what's wrong with using random IVs here? What should we do instead (assuming we can only use GCM, and not switch to chacha)
1. It is necessary for nonces to never be re-used for a given key lest you open yourself to a certain class of attacks that can decode all messages using that key. This is specific to AES-GCM due to how it internally reuses nonces.
2. AES-GCM uses very small nonces, making the probability of randomly using the same nonce unacceptably as the number of messages encoded with a given key increases (as it would with each frame sent on 1fps).
You can avoid all this by using a different primitive with a longer nonce such as XSalsa (a version of Salsa with a 192-bit nonce)
Background: the key+IV define a keystream which is xor-ed against the message. The same key+IV generate the same keystream. Thus you can XOR two cipher texts and reveal information from the two plaintext.
AES-GCM is authenticated encryption. To combat known-ciphertext-attacks, you want to have authenticated cipher texts. AES-GCM specifically is vulnerable to an attack with a reused IV to recover the authentication key. Allowing you to forge authentication tags and employ a KCA.
The solution, if you're stuck with aes, is to switch to XAES-GCM or better AES-GCM-SIV. Alternatively you must use a counter or checkes system to not reuse IV. Since this is in the context of 1fps, you could use unix timestamp + random bytes to reduce the chance of collisions.
If an attacker intercepts 2 messages that were encrypted with the same key and the same nonce, they can reveal the authentication key used for the generation of authentication tags (auth tags), and they can then forge auth tags for any message. These auth tags are how a recipient verifies that the message was created by someone who knows the symmetric key that was used to encrypt the plaintext, and that it was not altered in transit.
More simply, it allows an attacker to alter the ciphertext of an encrypted message, and then forge an authentication tag so that the modification of the ciphertext could not be detected. It does not reveal the symmetric key that allows decryption of the ciphertext, or encryption of arbitrary plaintext.
If a random nonce is generated, there is a chance that it is the same as a random nonce that was generated earlier in the session. Since the nonce is 12 bytes, this chance is very small for any 2 random nonces (1 in 2^96), but the chance of a collision increases rapidly with the number of encrypted messages sent in a session (see the birthday problem). It still requires a large number of messages to be sent before the chance of a collision becomes significant: "after 2^28 encryptions the probability of a nonce collision will be around 0,2 % ... After 2^33 [encryptions] the probability will be more than 80 %"[0]
If this program is sending 1 message per second (1 FPS), it would take 8 years for 2^28 messages to be sent. I haven't looked at the code, it may well be sending many more messages than that.
The alternative to a random nonce is a "counter" nonce, which starts at 1 and increments with each message. The potential pitfall of counter nonces is that they can be harder to implement, as they require tracking and updating state. If the program ever fails to track or update this state correctly, nonce reuse will occur. A different counter must be used for each symmetric key (which should be randomly generated for each session).
EXTRA CREDIT: There is also information revealed about the plaintext of the 2 messages that used the same key and nonce - specifically, the XOR of the 2 plaintexts. While this doesn't directly reveal the plaintexts, if some information about one of the plaintexts is known, that can be used to reveal information about the other plaintext.
I learned most of this information from David Wong's Real-World Cryptography.
[0]: https://eprint.iacr.org/2016/475.pdf, section 3.3
Out of curiosity, is the primary reason you don't recommend fixing the nonce issue in this specific case due primarily to the pitfalls in doing so or is it more nuanced and related to the general issues mentioned in the articles above?
A naive perspective could be that one uses AES-GCM because it is used in so many places, such as TLS or SRTP, and someone who is not very well versed in cryptography assumes it can be the way to go.
In general, the move in modern cryptography engineering is to assume the end user does not know what they are doing. For GCM, you have to get the nonces right and you need the right tag length, and the design uses lookup tables so it's prone to timing attacks in many implementations.
Later on I didn't just recommend an algorithm but a specific implementation (at least if we can find a better method of symmetric key distribution): nacl/secretbox [1]. This is a cryptographic library designed to be misuse-resistant, a property of cryptographic designs that makes implementation errors more difficult. nacl is a few years behind the curve inasmuch as it arguably gives the end-user too much control over key generation, but it permits random nonces (being based upon XSalsa) and provides a simple API that is difficult to mess up.
AES-GCM is secure with a correct implementation, but to build a correct implementation you often need to know the specific library inputs and configuration settings to produce your desired outcome. Something like secretbox doesn't give you those options: you get one relatively secure configuration ... and that's it!
[0]: https://csrc.nist.gov/csrc/media/projects/block-cipher-techn...
[1]: https://pkg.go.dev/golang.org/x/crypto/nacl/secretbox
The technical explanation for our issue is that the client-side Javascript in our webapp is trusted. To quote the late Ross Anderson [0, pg. 13], "a trusted system or component is one whose failure can break the security policy." In this case, our security policy is that the server must not be capable of viewing our screenshots. Our goal is to make that trusted Javascript more trustworthy: that is, closer to a system that can't fail.
We're at an advantage in this case: there's an open-source application on GitHub with eyeballs[1] on it that users must run on their endpoint machines. Given that we already have source-available local code running, we could instead serve the UI from the local Go application and use CORS[2] to permit access to the remote server. If the local application is trustworthy, and we're only sending data (not fetching remote Javascript), then the local client UI is trustworthy and won't steal your keys. If users run binaries directly from 1fps (as opposed to building from source), then you would want some multi-party verification that those binaries correspond directly to the associated source [3].
Protonmail is almost surprising: it's supposed to be end-to-end encrypted, but it's not end-to-end encrypted in the presence of a malicious server. If, say, a government order compelled Protonmail to deploy a backdoor only when a particular client visited the site, most users would be unaffected and the likelihood of discovery would be low.
[0]: https://www.cl.cam.ac.uk/~rja14/book.html
[1]: https://en.wikipedia.org/wiki/Linus%27s_law
[2]: https://stackoverflow.com/a/45910902
[3]: https://en.wikipedia.org/wiki/Reproducible_builds
Another attempt at compression: use a native app to serve Javascript for the web app so you don't have to trust any server
I don't mean to skip anything, it's just not clear to me how related some of it is, and if it's lengthy just because it is, or because I'm missing something thats very significant (ex. cite + name + page # to help say "something we trust is something we rely on", link to Wikipedia as source for "eyeballs make bugs shallow"
it takes the form of a browser extension the user downloads that will tell the user if the javascript code is what it is expected to be. it checks this by verifying the code's expected hash with an endpoint hosted by Cloudflare. Whatsapp can publish new versions to Cloudflare but they can't modify them.
In this case it makes it so that you are trusting Cloudflare instead of just WhatsApp, but (as an amateur), I don't see why this couldn't be adapted into a standard that works with something like a blockchain or certificate authorities (or even something like a git host to go along with public source code auditing?). I think something like this should become a standard and be built into browsers, but currently not a lot of companies are using any solution at all.
The only other implementation of a solution to this that I found, which I think is pretty similar, is Etesync's pgp signed webpages library + browser extension (https://stosb.com/blog/signed-web-pages/), which allows the developer to PGP sign web pages so you know the code has not been modified by a malicious server without the developers approval. So maybe you can use that in your project I guess, or there are probably some other solutions that I haven't found
I think this problem might be called "Code Verification" in cryptography, if you want to look more into it
A "lighter" /alternative to a local binary is to a have a local index.html and use SRI when linking to the remote scripts [0]. But seems clunky as well...
[0]: https://developer.mozilla.org/en-US/docs/Web/Security/Subres...
footnote: there are client side certs, however because there is no tooling for them built into the browser usability sucks, I want to try to get public key auth working on my toy js application and the browser tooling for user generated keys sucks. I am tempted to use ssh keys(I like ssh keys), but will probably see if I can get hoba working. https://datatracker.ietf.org/doc/html/rfc7486 I got all excited about hoba when I first read about it, but am now a bit bitter when as found out that there is zero internal browser support.
Why could one not use Encrypt-then-HMAC and HMAC-then-Decrypt with a random IV ?
(Serious question. It definitely sounds like you know what you are talking about, I just can't see what I am missing here)
Was Rustdesk on-radar?
So you are either offering a slow and very limited free service, or you need to pay hand over fist and burn venture capital to basically compete with Zoom and WebRTC. Slowing the video stream to very low FPS does help somewhat with scaling, but makes for a niche product.
If you can crack P2P multiplexing and offer an unlimited free service, and tack on some fremium model on that, that this thing can take off like a rocketship, if for no other reason that every team leader in the world wants a continuous feed of their remote worker's desktop. A free and capable screen sharing app can become THE tool for collaboration, disrupting things like Slack if the right features are there.
I'm seriously interested to cofound something like that, let me know if anything I've said makes sense to you.
"every" -- why?
Do high-performance teams have low or high trust?
It's a real problem real companies face, look at r/overemployed for a taste.
ahem GDPR?
Besides, that's absolutely the sort of enterpriseware that should be charged for.
You can charge for the enterprise features (and get the resources to develop them in the first place) after you reach a critical mass of users.
Of course, as any tool can be used for bad things, so if, say, instead of the default sharing of just the development apps, the employee shares his browser, email client or instant messenger which he uses for personal purposes, you could argue it crosses the line into unlawful workplace surveillance, so it becomes a matter of setting correct policies. Sounds to me like an enterprise feature set you could charge for, as complement to the free tier.
..talk about micromanaging
I recorded a one minute video clip of me editing some code in VS Code (1440p 10fps, using AV1 encoding) and it was about half the size of 60 JPEG screenshots of the same screen. I would be curious to see your numbers if you've done any tests.
But could easily do both if you wanted to - though I’m not sure it’s worth the hassle. I agree that this might struggle if used at scale on the same IP
JPEG either makes "smeared" screenshots or low-compression screenshots. PNG often works better.
A proper video codec mostly sends the small changes between frames (including shifts,like scrolling), and relatively rare key frames. It could give both a better visual quality and better bandwidth usage.
What's interesting in the "screenshot per second" solution is that it can be hacked together from common existing pieces, like imagemagic, netcat, and bash; no need to install anything. (Imagine you've got privilege-limited access to a remote box, and maybe cannot even write to disk! Oh wait...)
Just open any website and you'll see text right beside photos, or text against a photographic backdrop, often in the middle of being moved around with hardware-accelerated CSS animations.
I think we need an image container format that can use different compression algorithms for different regions or "layers" of the image, and an encoder that quickly detects how to slice up a screenshot into arbitrary layers. Both should be possible with modern tech. I just hope the resulting format isn't patent-encumbered.
I suppose than the more modern video compression algorithms apply such image analysis already, to an extent. I don't know how e.g. VNC or RDP work, but it would be naural for them to have provisions like that co save bandwidth / latency, which is often in a shorter supply than computing power.
Of existing still image codecs, JPEG XL seems to have the right properties[1]: the ability to split image to areas and / or layers, and the ability to encode different areas either with DCT or losslessly. But these are capabilities of the format; I don't know how well existing encoder implementations can use them.
[1]: https://en.wikipedia.org/wiki/JPEG_XL#Technical_details
Uses a combination of different tech [0]. MS-RDPBCGR is at the base of it all, sort of like the main event loop [1]. MS-RDPEGDI looks into the actual drawing commands and optimizes them on the fly [2]. Then there's the MS-RDPEDC for desktop composition optimizations [3]. Also a bunch of other bits and pieces, like MS-RDPRFX which uses lossy compression optimization [4].
In RDP you don't get to play only with the bitmap or image stream data, but the actual interactions that are happening on the screen. You could say for example that the user right clicked a desktop item. Now send and render only the pop-up menu for this, and track and draw the mouse actions inside that "region" only.
[0] https://learn.microsoft.com/en-us/openspecs/windows_protocol... [1] https://learn.microsoft.com/en-us/openspecs/windows_protocol... [2] https://learn.microsoft.com/en-us/openspecs/windows_protocol... [3] https://learn.microsoft.com/en-us/openspecs/windows_protocol... [4] https://learn.microsoft.com/en-us/openspecs/windows_protocol...
[0]: https://support.apple.com/guide/mac-help/screen-sharing-type...
So, with this, you can have high-quality static text in one region of the frame while there is lossy motion encoding (e.g. for an animating UI element) in another region of the frame.
modern codec do motion prediction and don't work with with low frame rate out of the box.
So from on side you have CPU utilization, from the other - network. Network is cheap, but encoding is expensive. This is my thinking at least. I don't have proof - only local experiments, but it's a really good idea to start measuring this.
I also have other ideas in mind on how to scan the screen and send only parts of the screen that have been updated. Probably if I send only a half of the screen, it will beat the video encoding in terms of network. The diff algo should be very fast though, since we're dealing (in case of 1280x720) with 1280x720=914400, 914400*4 = 3.49 MB info processing in 1 second.
Also, curious to hear about video encoding efficiency vs 60x JPEG creation. Is it comparable?
Did you try to use the hardware encoder? Modern computers have chips to accelerate/offload video encode/decode. Your 2019 Mac has Intel GPU for H.264 and HEVC hw encoder, also it has an T2 co-processor that can also encode HEVC video. If you don't supply specific encoders (with _videotoolbox suffix on Mac) via -c:v then ffmpeg will default to sw encoder, which consumes CPU.
> how to scan the screen and send only parts of the screen that have been updated
You'll be reinventing video codecs with interframe compression.
> Also, curious to hear about video encoding efficiency vs 60x JPEG creation. Is it comparable?
I see that you are comparing pixel by pixel for each image to dedupe and also resizing the image to 1280px. Also the image has to be encoded to JPEG. All of the above are done in CPU. In essense you implemented Motion JPEG. Below is a command to allow you to evaluate a more effecient ffmpeg setup.
ffmpeg \
-f avfoundation -i "<screen device index>:<audio device index>" \ # specific to mac
-an \ # no audio
-c:v h264_videotoolbox \ # macos h.264 hardware encoder -r 1 \ # 1fps
-vf scale=1920:-1 \ # 1920px wide
-b:v 2M \ # 2Mbps bitrate for clear and legible text
out.mp4 # you may want to setup a RTMP server so that ffmpeg can transmit the encoded video stream to and allow visitors to view the video
The second version tried to use the VNC approach developed years ago by AT&T and open sourced. The open source implementation glitched just a bit much for my liking. Various companies white labelled VNC in those days; not sure they fed back all their fixes. But the raw VNC protocol has a lot of good compression ideas specific to the screen sharing domain and documented in papers. People also tunneled VNC over SSH. I jerryrigged an HTTPS tunnel of sorts.
After a while I started to suspect if I wanted to get higher frame rates I should use a more modern screen sharing oriented Microsoft-based/specific codec. But it wasn't my skillet so I never actually went down that route. But I'd recommend researching using others screen-optimized lossless codecs, open or closed, so you don't reinvent the wheel on that side of things if you're serious about reducing bandwidth.
Font quality with lossy codecs generally looked too lousy back when I tried it at various settings. Screensharing unlike videos is very text centric.
I'd be willing to do the right kind of lossy but jpeg discrete cosines back then wasn't it.
The screenshots were taking up a lot of disk space. I noticed that very little changed between pictures, so I started thinking of an algorithm that would make use of this characteristic: store only the changes between subsequent images. A few minutes in, I realized I was reinventing video compression!
So, I just used ffmpeg to turn the image sequence into a mp4. It was something like 95% reduction in file size. (I think I used ImageMagick to embed the timestamp filename into the images themselves, thus recreating basically all the features of TimeSnapper Classic with 2 commands.)
Seems like this is a really good minimal interface - if I’m feeling wonky I might extend it with chat persistence somehow
I am assuming any additional synchronized text or voice is done elsewhere like calling someone on the phone or clarification via text on slack right?
Earlier today you were thinking you specifically needed exactly 1 fps?
I really appreciate the discussion about the tech involved, especially non-go lang info and advice. peak HN imo
https://malware.opendns.com/main?url=1fps.video&server=nyc4&... - not sure if that's viewable elsewhere, but if so there's a report link there.
Please understand the Windows toolchain is often broken, and binaries are preferable. I will roll out binaries soon for all platforms. I also noticed some cursor coordinates issues on Windows machine (I have high resolution). I'm wondering if you have any issues with that as well.
Good news is that there are steps to workaround and we're aware of these issues :) I guess check back later, I hope we gonna fix it soon!
What is going on? The golang windows toolchain is absolutely not "often broken".
The following was just inspired by the idea of yours "1fps screen sharing":
If you do 1FPS screen sharing - then create a private gallery on imgur.com - and have a thing update screenshots in a single gallery with a garbage collecting thread deleting a screenshot every 30 seconds/interval...
then have user have hidden gallery url and auto refresh browser tab.
This might work well actually if you have a small data connector and a device that can upload like only when event occurs.
Just upload that to replace the file in the imgur gallery - and you have a free cloud cam.
FWIW, jitsi[0] is an open source[1] WebRTC based, full featured, video conference/meeting alternative to zoom, google-meet, slack, etc.
You can use it via the main site, or self-host it if you like.
[0]: https://meet.jit.si/ [1]: https://github.com/jitsi
[1] https://github.com/strukturag/nextcloud-spreed-signaling
What makes all these newfangled language ecosystems think this is an acceptable way to distribute your software. No, I don't want to install yet another giant tree of language specific crap. Learn to distribute self-contained binaries please.
That said, certainly there does seem to be a weird glib assumption by some hobby/OSS projects that of course I already have the same developer ecosystem/toolchain installed on my system as they do - of course I have a Rust toolchain or can install dependencies with Homebrew or am using Linux with systemd/Wayland. This project at least kindly asks me to install Go first if I haven't already.