diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 82af9d6..f1e1017 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,8 +1,11 @@
-# Builds the Windows installer and publishes a draft GitHub Release
+# Builds the desktop installers and publishes a draft GitHub Release
# whenever a `v*` tag is pushed. Publish flow:
# git tag v0.1.0 && git push origin v0.1.0
# → wait for this workflow → review the draft on the Releases page → publish.
#
+# Windows produces the NSIS installer; Linux produces .deb, .rpm and an
+# AppImage (see src-tauri/tauri.linux.conf.json for the bundle targets).
+#
# The TAURI_SIGNING_* secrets are for tauri-plugin-updater artifact
# signatures (latest.json + .sig). Until the updater is wired up and the
# secrets are set, they resolve to empty strings and the build simply
@@ -16,11 +19,29 @@ on:
jobs:
build:
- runs-on: windows-latest
permissions:
contents: write
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - platform: windows-latest
+ args: ""
+ - platform: ubuntu-22.04
+ args: ""
+ runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
+ - name: Install Linux system dependencies
+ if: matrix.platform == 'ubuntu-22.04'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ libwebkit2gtk-4.1-dev \
+ libappindicator3-dev \
+ librsvg2-dev \
+ patchelf \
+ libgtk-3-dev
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
@@ -42,3 +63,4 @@ jobs:
releaseBody: "See the assets below to download and install this version."
releaseDraft: true
prerelease: false
+ args: ${{ matrix.args }}
diff --git a/README.md b/README.md
index aa2ccd2..6a51b16 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
[](LICENSE)
[](https://ko-fi.com/nuberr)
-A fast, responsive YouTube Music desktop client for Windows.
+A fast, responsive YouTube Music desktop client for Windows and Linux.
Built as a reaction to the sluggish webview-wrapper experience — YTubic talks to YouTube's InnerTube API directly, renders its own UI, and caches aggressively, so navigation and playback feel instant.
@@ -19,7 +19,7 @@ Built as a reaction to the sluggish webview-wrapper experience — YTubic talks
- **Synced lyrics** — line-by-line synced lyrics from multiple providers (LRCLIB, Musixmatch, Genius)
- **Hi-res cover art** — upgrades album covers to high-resolution studio art when available
- **Full library support** — your playlists, likes, albums and artists; search with filters; radio/autoplay queues
-- **Windows integration** — media keys, System Media Transport Controls, tray icon, single instance
+- **Desktop integration** — media keys and Now Playing controls (SMTC on Windows, MPRIS on Linux), tray icon, single instance
- **Auto-updates** — the app updates itself from GitHub Releases, and keeps its yt-dlp copy fresh automatically
> **Disclaimer:** YTubic is an unofficial client. It is not affiliated with,
@@ -32,7 +32,14 @@ Built as a reaction to the sluggish webview-wrapper experience — YTubic talks
Download the latest installer from the [Releases](../../releases) page and run it.
-- **Windows 10/11 only** for now.
+- **Windows 10/11**: run the `.exe` NSIS installer.
+- **Linux**: install the `.deb` (Debian/Ubuntu), `.rpm` (Fedora/openSUSE), or
+ run the `AppImage` directly. Requires WebKitGTK 4.1 + GTK 3, plus the
+ GStreamer "good" and "base" plugins (and `gst-libav`) — WebKitGTK decodes
+ audio through GStreamer, so without `autoaudiosink` + the WebM/Opus/AAC
+ demuxers there is no sound. These are pulled in automatically by the
+ `.deb`/`.rpm`; for the AppImage install them yourself, e.g. on Arch:
+ `sudo pacman -S webkit2gtk-4.1 gst-plugins-base gst-plugins-good gst-libav`.
- On first launch the app downloads its own copy of yt-dlp (~12 MB) into its
data folder and keeps it updated automatically.
- Signing in is optional: browse and playback work anonymously; sign in to get
diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json
index a013ac7..2707f19 100644
--- a/src-tauri/capabilities/default.json
+++ b/src-tauri/capabilities/default.json
@@ -33,6 +33,7 @@
"core:window:allow-toggle-maximize",
"core:window:allow-close",
"core:window:allow-start-dragging",
+ "core:window:allow-start-resize-dragging",
"core:window:allow-outer-position",
"core:window:allow-outer-size",
"core:window:allow-set-always-on-top"
diff --git a/src-tauri/tauri.linux.conf.json b/src-tauri/tauri.linux.conf.json
new file mode 100644
index 0000000..ae889ab
--- /dev/null
+++ b/src-tauri/tauri.linux.conf.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://schema.tauri.app/config/2",
+ "bundle": {
+ "targets": ["deb", "rpm", "appimage"],
+ "category": "Music",
+ "linux": {
+ "deb": {
+ "depends": [
+ "libwebkit2gtk-4.1-0",
+ "libgtk-3-0",
+ "gstreamer1.0-plugins-base",
+ "gstreamer1.0-plugins-good",
+ "gstreamer1.0-libav"
+ ]
+ },
+ "rpm": {
+ "depends": [
+ "webkit2gtk4.1",
+ "gtk3",
+ "gstreamer1-plugins-base",
+ "gstreamer1-plugins-good"
+ ]
+ }
+ }
+ }
+}
diff --git a/src/components/layout/app-shell.tsx b/src/components/layout/app-shell.tsx
index 5ce0a64..c94029e 100644
--- a/src/components/layout/app-shell.tsx
+++ b/src/components/layout/app-shell.tsx
@@ -10,6 +10,7 @@ import { PlayerBar } from "@/components/layout/player-bar";
import { PlayerBarBottom } from "@/components/layout/player-bar-bottom";
import { FloatingPlayerSync } from "@/components/layout/floating-player-sync";
import { DragSnapOverlay } from "@/components/layout/drag-snap-overlay";
+import { WindowResizeHandles } from "@/components/layout/window-resize-handles";
import { EntityPageHeader } from "@/components/layout/entity-page-header";
import { SettingsDialog } from "@/components/settings/settings-dialog";
import { PremiumGateDialog } from "@/components/layout/premium-gate-dialog";
@@ -234,6 +235,7 @@ export function AppShell({ children }: { children: ReactNode }) {
{mode === "floating" && hasTrack &&