Skip to content
misc-dePublic

About

Cast your screen, YouTube and media to Chromecast, DLNA & LG webOS TVs on Linux

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

Schwupp

Schwupp

Cast your screen, YouTube and media to Chromecast & LG webOS TVs on Linux, with native screen mirroring.

License: MIT Python 3.11+ GTK4 / libadwaita


⚠️ AI-assisted project


Schwupp is a self-contained casting app for Linux desktops and Linux phones (Phosh, e.g. FuriPhone FLX1). It streams your screen, YouTube and local media to the TV and supports both Chromecast / Google TV and LG webOS from a single adaptive GTK4/libadwaita interface — without depending on external CLI tools like catt.

The interface follows your system language (English and German included; English is the fallback for unsupported locales).

Features per device

Feature Chromecast / Google TV LG webOS (2024+) AirPlay 2 (Hisense, Samsung …)
Automatic discovery ✅ ✅ ✅
Music files ✅ ✅ ✅
Video files ✅ ✅ †
YouTube ✅ (TV's own app) ✅ (TV's own app) † (as a video stream)
Web videos ✅ ✅ †
Play / pause / stop, volume ✅ ✅ —
Screen mirroring (native, < 1 s) ✅ ✅ —
Screen mirroring (AirPlay, with sound) — — ✅
Screen mirroring (HLS) ✅ ✅ †
Sound while mirroring ✅ ✅ ✅

† depends on the individual TV. Many AirPlay sets take only audio and refuse every video request — including YouTube and HLS mirroring, which both arrive as a video URL. Schwupp finds this out on the first attempt and then stops offering what that device won't do, so you are not left guessing. Playback controls are not offered at all on AirPlay: these TVs acknowledge the commands and ignore them.

Most TVs are found and connected automatically. Newer LG webOS TVs (≈2024 and later) work just like a Chromecast, including screen mirroring.

AirPlay 2 TVs (Hisense/VIDAA, Samsung Tizen, Sony, older non-Cast models …) pair with the code shown on the TV the first time you connect; the pairing is stored, so this happens only once. Screen mirroring uses a separate protocol and therefore asks for its own code once — see Screen mirroring below.

Do not trust what these TVs advertise: the tested Hisense claims AirPlay video and refuses every playback request — yet mirroring to it works, with sound. On such a set, mirroring is how you get any moving picture there, YouTube included.

Other TVs

Samsung, Sony, Panasonic, Philips and many Hisense TVs without AirPlay are also detected via DLNA and can play your media, web videos and YouTube, with playback and volume control. (Screen mirroring on these needs a Chromecast.)

Screen mirroring

Mirror your desktop to the TV. (Curious how it works? See docs/MIRRORING.md.)

On AirPlay TVs this uses the AirPlay mirroring protocol by way of doubletake, which the Flatpak bundles. Two things are worth knowing: the TV shows a one-time code the first time, and the receiver probes a UDP timing port on your machine during setup. If a firewall drops that, the session comes up but no picture ever appears — so allow the range Schwupp uses:

sudo ufw allow from <TV-IP> proto udp to any port 60000:60010 comment 'AirPlay Mirroring'

Not every AirPlay TV accepts video at all: some only take audio. Schwupp says so plainly instead of leaving you guessing — and on such a TV, mirroring is the way to get a picture there, including for YouTube (there is no startable YouTube app over AirPlay, unlike on LG webOS or Chromecast).

Architecture

app/
  discovery.py     unified discovery (Cast, webOS, AirPlay, DLNA)
  receivers/       device backends behind one interface:
    base.py          Receiver interface + feature gating
    chromecast.py    pychromecast
    webos.py         pywebostv (control/YouTube) + DLNA (media)
    airplay.py       pyatv (AirPlay 2: PIN pairing + play_url)
    dlna.py          generic UPnP media renderers
  dlna.py          minimal UPnP-AVTransport client
  server/          local HTTP server (files w/ Range, HLS, live stream)
  sources/         YouTube / web video (yt-dlp)
  mirror/          pluggable mirror engines (native, hls, dlnats)
  service.py       D-Bus interface for shell quick settings (no window)
  speaker/         all sound of the device to Cast, Sonos, DLNA, AirPlay speakers
  paths.py         locates VERSION/lang (git checkout, prefix, Flatpak)
  updater.py       self-update (git pull or verified ZIP staging)
  ui/              GTK4/libadwaita interface (adaptive desktop/phone)
tests/             protocol, server and config tests — no TV required

The GUI only talks to the Receiver interface; which actions appear is driven by receiver.supports(...). Mirror engines share one base class that handles screen capture selection, the worker thread and error reporting back into the window.

Sound to speakers

Send all sound of this device to a speaker — music apps, videos, calls to the browser, everything. The device's own speakers go quiet meanwhile; the sound arrives 2–4 seconds later. Tap "Send sound here" on a device (or the speaker in the shell's list, see below).

Speaker How the sound gets there
Chromecast, Nest and other Cast speakers, Cast groups live MP3 stream (320 kbit/s) the speaker fetches
Sonos (room groups as one, e.g. "Kitchen +1") the same stream under Sonos' radio scheme x-rincon-mp3radio://
DLNA/UPnP renderers the same stream, announced as a broadcast
AirPlay speakers (_raop._tcp) PipeWire's RAOP sink (pipewire-zeroconf)

A virtual output becomes the default while the sound is sent; its name shows where the sound goes ("Schwupp → Kitchen"), and the volume keys set its level. Needs PipeWire (or PulseAudio) with pactl; AirPlay speakers need PipeWire's RAOP module and Avahi. The Sonos and DLNA handling comes from the author's Emilia.

Shell integration

A shell can offer mirroring and sound without opening Schwupp — Phosh's cast quick setting on the Plinius does this: tap a TV in the list to mirror, a speaker to send the sound there, tap it again to stop. Schwupp exports de.cais.Schwupp.Cast1 at /de/cais/Schwupp/Cast on its application bus name de.cais.Schwupp; when Schwupp is not running, D-Bus starts it without a window (schwupp --service), and it quits by itself 30 s after the last caller is done.

Member
StartDiscovery() / StopDiscovery() search while a caller holds it (ends when the caller leaves the bus)
Connect(s id) / Disconnect() mirror to a TV or send the sound to a speaker from Devices, stop
SubmitPin(s pin) the code an AirPlay TV shows the first time (State is pin)
OpenSettings(s id) open Schwupp with that device's settings (resolution, bitrate, sound …)
GetSettings(s id) → a{sv} the values of a TV ("": the defaults for all TVs) with the choices: engines a(ssbs), encoders as, resolutions ai; per TV also name, kind, custom
SetSetting(s id, s key, v value) change one value (mirror_height, mirror_bitrate_kbps, mirror_fps, mirror_audio, mirror_encoder, per TV also mirror_engine, airplay_video); checked, InvalidArgs otherwise
ResetDevice(s id) / ForgetDevice(s id) back to the defaults / also drop it from KnownDevices
Devices a(ssss) id, name, description, icon name — TVs that can mirror and speakers (audio-speakers-symbolic)
KnownDevices a(ssss) TVs with own settings or mirrored before, also while they are not found
ActiveDevice s, State s, Error s, Discovering b State: idle, connecting, pin, starting, streaming

The settings stay per device in Schwupp: the gear next to a device in the list opens them without connecting first. A settings app of the desktop can show the same values through GetSettings/SetSetting — on the Plinius they are on the "Streaming" page of GNOME Settings. The config stays in Schwupp's process this way; nothing else writes the file while the service holds it.

Installation

Flatpak (recommended)

Pre-built, GPG-signed bundle for x86_64 and aarch64 — ideal for the phone, no build tools needed:

flatpak remote-add --if-not-exists schwupp https://misc-de.github.io/Schwupp/de.cais.Schwupp.flatpakrepo
flatpak install schwupp de.cais.Schwupp
flatpak run de.cais.Schwupp

Update later with flatpak update de.cais.Schwupp. The signing key is already embedded in the .flatpakrepo file — nothing needs to be imported separately.

The Flatpak brings everything along, including the X11 screen capture element and wf-recorder for wlroots compositors without a screen-cast portal.

Prefer to run it from a Git checkout, or compile it yourself? See Building from source & project layout.

From a Git checkout

git clone https://github.com/misc-de/Schwupp.git
cd Schwupp
make venv     # Python environment (uses the system GTK4/GStreamer)
make run

The system libraries needed for this route are listed in BUILDING.md. To get an app icon and menu entry, run make install PREFIX=$HOME/.local (no root needed).

First use

Open Schwupp from your app menu. The computer and TV must be on the same network and the TV must be on. The first time you connect to an LG TV, a pairing dialog appears on the TV — confirm it with the remote (the key is then stored). On AirPlay TVs (e.g. Hisense) the TV shows a code instead — type it into the dialog in Schwupp (also stored, one-time).

On startup Schwupp checks that everything it needs is installed: if something required is missing it tells you and stops; if only optional parts are missing it lists them and lets you continue anyway.

Updates

Installed as a Flatpak: flatpak update de.cais.Schwupp (the settings page says so as well).

Running from a Git checkout: go to Settings → App → "Check for updates". Schwupp checks for a newer version and updates itself in place — your settings are kept — and then offers to restart.

Credits

Schwupp stands on the shoulders of these projects:

Project Used for License
pychromecast Google Cast protocol LGPL-2.1
casttube YouTube playback on Cast devices (bundled with pychromecast) MIT
pywebostv LG webOS SSAP control MIT
pyatv AirPlay 2 pairing & playback (Hisense, Samsung …) MIT
yt-dlp web video extraction (fallback) Unlicense
python-zeroconf mDNS service discovery LGPL-2.1
cryptography AES-128-CTR for the native mirror Apache-2.0 / BSD
requests HTTP for the updater Apache-2.0
PyGObject · GTK4 · libadwaita the user interface LGPL
GStreamer screen capture & H.264 encoding LGPL
PipeWire · xdg-desktop-portal Wayland screen capture (ScreenCast portal) MIT / LGPL-2.1
x264 · OpenH264 H.264 encoders used through GStreamer / wf-recorder GPL-2.0 / BSD-2-Clause
doubletake AirPlay screen mirroring (the protocol pyatv does not speak) LGPL-3.0
wf-recorder Wayland capture where no screen-cast portal exists MIT
LAME MP3 encoding of the sound for speakers (through GStreamer) LGPL-2.0
Emilia how Sonos, DLNA and Cast speakers take a live stream (ported to Python by the same author) GPL-3.0-or-later

The native Cast-streaming wire format was derived from the public Chromium Open Screen Library / media/cast sources (no code copied). The self-update flow is modeled on the author's DrivePulse app.

doubletake and wf-recorder are invoked as separate programs — Schwupp does not link against them — and the Flatpak ships them as standalone binaries alongside the app. They keep their own licenses, as do all dependencies; the table above is informational.

A word of thanks is due for the AirPlay work in particular: mirroring from Linux was long considered impossible without FairPlay, Apple's DRM. That it works here rests on years of reverse engineering by the UxPlay and RPiPlay projects, which doubletake builds on.

License

Released under the MIT License © 2026 misc-de.

About

Cast your screen, YouTube and media to Chromecast, DLNA & LG webOS TVs on Linux

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages