What Selkies 2.2.1 brings
Selkies 2.2.1 ships with pixelflux 2.2.3 and pcmflux 2.2.1.
- A stalled connection comes back at its rate. A path that holds the stream for seconds and then delivers it at once, a Wi-Fi radio retrying or a lossy hop, used to cut a still desktop to a blur until the screen moved. The rate now returns to what it was within two seconds of the stall clearing.
- A still desktop climbs back after a slow spell. Once a slower path or another flow's download has cut the rate, the stream is padded up to its target while the screen sits still, so the path is tested and the rate climbs back, 1.3x a second, where it used to stay cut until something moved.
- Safari's engine streams behind the login. WebKit browsers send no login with the stream's socket, which the server refused, so behind the default Basic login they showed nothing; the page load now gives the socket a cookie in its place.
- A link that logs in with
user:pass@works. The page drops the credentials from its address before anything runs, so the mode probe, the WebSockets/WebRTC switch and the file manager no longer fail on them;?token=and#token=are kept. - WebRTC keeps moving through a sharp bandwidth drop. After a 5 to 0.5 Mbit/s step, a 720p30 stream stalled for 20 s and more, its round trip at 2.3 s (#503). It now keeps a few frames a second through the step, its longest pause 2-4 s, at a round trip of 0.1-0.4 s, with audio playing or not.
- A press after switching tabs works. A mouse button or a touch whose release went to another tab or app stayed held: every motion dragged, and on a touch screen every tap after it counted as a second finger and scrolled instead of clicking. Hiding the page, leaving its window or touching it again now releases what the page no longer holds.
- A SealSkin room's viewers stay viewers. In secure mode the token, not the Basic login a proxy adds, decides what an API request may do.
- Sound survives a reconnect in a background tab. A short network drop while the tab was hidden, or the video switched off, left the session silent until a reload; the sound now plays on, and the picture comes back when the tab is shown (#509).
- KDE Plasma on Wayland runs on a GPU the compositor cannot render on. On a virtual GPU the compositor fell back to software while still offering GPU buffers, and a nested KWin crashed on them (#504).
- No frozen screen at a login. When the browser stopped sending its saved login, the page's background requests waited behind a login prompt a fullscreen session hid, and the screen froze. The page now reloads into the browser's login.
pixelflux2.2.3 captures an NVIDIA X server through DRI3 first, holds NvFBC at 240 frames a second, keeps the paced capture paths at their rate under load, and hands a still host's frame back to the portal stream.
Running this build
Container: the desktop, a browser, and an audio stack, with nothing to install:
docker run --name selkies -it -d --rm --shm-size=2g -p 8080:8080 \
ghcr.io/selkies-project/selkies/desktop:2.2.1-ubuntu26.04Open https://localhost:8080, accept the container's self-signed certificate, and log in as ubuntu with the password mypasswd. Set another with -e PASSWD= before anyone else can reach the session. debiantrixie is the other flavor, and latest now points at this release. The flags for NVIDIA, Intel, and AMD GPUs are in Getting Started.
PyPI: the wheel and the sdist, with pixelflux 2.2.3 and pcmflux 2.2.1 resolved from PyPI as well, on Python 3.9 through 3.15:
pip install selkiesAppImage: runs from wherever you put it, on any distribution with glibc 2.28 or newer. Given session as its first argument, it starts a whole session with its own display and sound server:
APP="selkies-2.2.1-$(uname -m).AppImage"
curl -O -fsSL "https://github.com/selkies-project/selkies/releases/download/2.2.1/${APP}"
chmod +x "./${APP}"
"./${APP}" --public --port=8080 --basic-auth-user=user --basic-auth-password=mypasswdNative packages: install a private environment at /opt/selkies and put selkies on PATH:
. /etc/os-release
DISTRO="${ID}$([ "${ID}" = "ubuntu" ] && echo "${VERSION_ID}" || echo "${VERSION_CODENAME}")"
PKG="selkies-2.2.1-${DISTRO}-$(dpkg --print-architecture).deb"
curl -O -fsSL "https://github.com/selkies-project/selkies/releases/download/2.2.1/${PKG}"
sudo apt-get install -y "./${PKG}"Native Install has the .rpm, .apk, and .pkg.tar.zst lines. Jupyter, Coder, and Open OnDemand covers selkies session and the three integrations.
Checksums and provenance: every file here is listed with its SHA-256 sum in SHA256SUMS, and GitHub attests the build provenance of each. You can check a download with either:
sha256sum --check --ignore-missing SHA256SUMSgh attestation verify "./${PKG}" --repo selkies-project/selkies
Changes since 2.2.0
Congestion control
-
A stall's cut is restored once round trips are back at the floor. A hold of a second or more, read as a deep queue that arrived at once or over a trickle of the target, keeps the rate the path was carrying before it, and the second clean round trip in a row restores it. A path that really slowed backs off as before. On a rig whose path holds the page for 3.5 s every 40 s, 6 of 8 cuts on 2.2.0 never came back within the run; now each comes back within 2 s.
-
A still screen whose rate was cut pads its stream to test the path (both transports): WebSockets sends padding a page drops unread, WebRTC resends its last packets on its RTX stream behind any frame. A probe that meets a queue waits 15 s before the next, longer each time. After a 15 s narrowing, a still desktop was back at 3/4 of its rate in 15-21 s, where 2.2.0 stayed at about an eighth of it.
-
A padded probe climbs 1.3x a second instead of 1.15x: after a narrowing to 1000 kbit/s, a still text desktop over WebSockets is sharp again 8-16 s after the path widens, where it took 24-26 s; over WebRTC 17-20 s against 20-21. On a path that stays slow, the last step of each probe can lift the round trip briefly (a p95 of 410 ms in one WebRTC run of three).
-
WebRTC on a full path (#503): repairs take at most the steered rate while a queue the path showed cut it (a random loss's repairs stay unbounded), and a key frame on its way answers the requests behind it, where every NACK for a packet gone from the history asked for one; the bridge drops at most 6 frames in a row from an encoder without long-term references, and the capture rate falls to what the budget lets through, to 2 fps at least, while most frames are dropped; the steer leaves the audio's share of the path out of the video's, and RED's redundancy steps down while audio takes over half of a cut path. A page switching every 15 s over a path narrowed to 500 kbit/s drew 2300-2700 key-frame requests and stood at a 560-610 ms round trip; now 34-54 and 149-279 ms. On #503's step, 21-26 frames decoded in 25 s became 81-86 with audio off and 59-66 with a tone playing, the longest pause 19-22 s became 2-4 s.
Several pages, one display
- A page beside a hidden owner gets the capture that owner stopped, where a page that cannot take the display over showed no video until the owner came back.
- Over WebRTC, a hidden owner that never paused its own feed is paused when the display goes to the page in view (a phone that freezes a page on hide), and resumed when it is shown.
- A shared page's relay takes a queue standing on its path for the path, as the owner's steer does, instead of thinning its stream for ten minutes: behind a standing +150 ms, a shared page now keeps 60 fps where it fell to 25.
Login and the dashboard
- A WebKit page streams behind Basic auth: every Basic-authenticated request is answered with a
selkies_socketcookie (HttpOnly, SameSite=Strict, the login's role, signed for a day by the server process) that the socket's handshake presents in place of the Basic credentials WebKit does not attach to it. It authenticates nothing else. A proxy with a Basic login of its own in front of Selkies still challenges the handshake. - A page opened at a
user:pass@address drops the credentials before its scripts run, where they made every relative request fail in Chrome, Firefox and WebKit (the mode probe, the mode switch, the file manager, the manifest). - Background requests are answered without the Basic prompt, so a stale login reloads the page into the browser's login instead of freezing it.
- In secure mode, a Basic login without a session token counts as a viewer on the API. It was a controller there, so where a proxy sends the session's own login for every participant (SealSkin's rooms), a viewer that left its token off could list and download files, upload, print, switch the transport and start a recording. The token decides the role, as the secure mode route table says; a script that used the Basic login alone for those routes needs a session token or the master token. Without a master token nothing changes.
- The dashboard no longer fails to load a layout's lazy chunk on a slow network or a quick reconnect with the login in the URL (by @thelamer).
Settings
SELKIES_ENABLE_CLIPBOARD=0turns the clipboard off (and1on), as every other switch reads 0 and 1, where0fell through to the default and enabled it in both directions (#507, by @RaphaelFakhri).
Server and sessions
- A remote description parses in time linear in its length (WebRTC): the feedback bound counted
a=rtcp-fblines, but a*line lands on every codec and a repeateda=rtpmapmade another codec each time, so a description at the length bound parsed into 2.2 million feedback entries in 1-1.5 s on the event loop every session shares; it now counts entries, keeps the first codec of a payload type, and parses the same description in 31-37 ms (by @thelamer). selkies sessionsays which backend a desktop has a session for: asked for one backend on a desktop that installs only the other's session (KDE Plasma 6.8 ships Wayland alone), it names the session it found and the--waylandchoice that starts it, where it failed without saying why (#504).
Reconnects
- A reconnect whose new socket is up before any frame keeps its playback: the notice timer closed the audio decoder the new socket had already joined whenever no frame had come in 1.5 s, which a paused video (a hidden tab's, or one switched off) never sends, so the sound stayed off until a reload. A visible page whose first frame is late still shows the notice until the picture comes (#509, by @Qusic).
- A reconnect waits for the new socket's own first frame in Firefox and Safari: the video worker's once-a-second count could report the last socket's frames after the new one opened, which ended the wait at once, so a late picture showed no notice and the key frame requests a lost first frame needs stopped.
Input
- Hiding the page, blurring its window or leaving it releases held buttons, touches and gestures, not only keys, and a touch landing forgets tracked touches no longer on the surface: a release that went to another tab or app (a tab switched mid-press, an edge swipe the system took) had left a button down, so every motion dragged, or a finger tracked, so every tap after it was a two-finger scroll.
The capture stack
pixelflux 2.2.3 brings:
- DRI3 tried before NvFBC on an NVIDIA X server, at 55-65% of NvFBC's CPU and sooner to the encoder.
- NvFBC held at 240 frames a second, following a resize from the X server instead of asking the driver every 30 frames.
- The paced capture paths kept at their rate when an encode or a timer tick runs late, and the Wayland zero-copy session encoded on its own thread.
- A still host's frame handed back to the portal stream, so KDE's cursor updates reach the client, and the host pointer placed in Selkies' layout (selkies-project/pixelflux#61).
- A compositor that falls back to software rendering offers no GPU buffers, where a nested kwin_wayland crashed on them (#504).
- A still screen's cleanup on NVENC no longer crashes the capture when the encoder reports a band it took back late, which froze the stream.
pcmflux 2.2.1 is the release Selkies 2.2.0 shipped with.
Known limitations
- WebRTC behind NAT may still need a STUN/TURN server; see WebRTC and Firewall Issues. The default WebSocket transport does not.
- AV1 in WebKit: WebKit answers that it decodes AV1 and then refuses every AV1 key frame. The client treats that as a refusal and steps to H.264 without a reload.
- WebRTC in Firefox: some Firefox releases paint WebRTC AV1 with the wrong color matrix; the FAQ says which.
- 10-bit in Chromium: Chromium-based browsers decode AV1 over WebRTC at 8 bits only.
- Gaming mode holds the keyboard in Chromium-based browsers, Firefox 156 and later, and Safari 26.4 and later. Brave's Shields block the Keyboard Lock API by default.
- Hosts are Linux only. macOS and Windows support is not implemented.
Reporting
Issues go to https://github.com/selkies-project/selkies/issues, and most of the testing conversation happens on Discord. We can act on a report right away when it names:
- the transport (
websocketsorwebrtc); - the backend (X11 or Wayland);
- the browser;
- the GPU.
We are in need of maintainers and community contributors. Please consider stepping up, as we can never have too much help!
Contributors
There are 24 commits, none of them merges, between 2.2.0 (2026-11-02) and d6b935142 (2026-11-04), the commit this release was built from. Commits are counted by the GitHub account that authored each.
| Handle | Name | Commits |
|---|---|---|
| @ehfd | Seungmin Kim | 20 |
| @thelamer | Ryan Kuba | 2 |
| @RaphaelFakhri | Raphael Fakhri | 1 |
| @Qusic | Bang Lee | 1 |