From f46765e9ad86b91fffdd4848c6eb13e08db443b4 Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Sun, 22 Feb 2026 21:45:44 +0100 Subject: [PATCH 01/11] Try strict snap confinement --- .gitignore | 2 + lib/service/scrcpy_service.dart | 15 ++++- snap/snapcraft-classic.yaml | 50 ++++++++++++++ snap/snapcraft-strict.yaml | 116 -------------------------------- snap/snapcraft.yaml | 89 +++++++++++++++++++----- 5 files changed, 137 insertions(+), 135 deletions(-) create mode 100644 snap/snapcraft-classic.yaml delete mode 100644 snap/snapcraft-strict.yaml diff --git a/.gitignore b/.gitignore index bb9cf3e..50557bc 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ dist/ *.dmg # Snap package *.snap +# Snap debugging +squashfs-root/ diff --git a/lib/service/scrcpy_service.dart b/lib/service/scrcpy_service.dart index 061b2b7..d9cb07e 100644 --- a/lib/service/scrcpy_service.dart +++ b/lib/service/scrcpy_service.dart @@ -90,5 +90,18 @@ class ScrcpyService { Map? _getEnvironment(String adbPath) => adbPath.isNotEmpty ? {'ADB': adbPath} : null; - String _getExecutable(String path) => path.isEmpty ? 'scrcpy' : path; + String _getExecutable(String path) => path.isEmpty ? getScrcpyPath() : path; + + String getScrcpyPath() { + final snapPath = Platform.environment['SNAP']; + if (snapPath != null) { + print("SNAP_PATH: $snapPath"); + final scrcpyPath = '$snapPath/scrcpy-runtime/usr/local/bin/scrcpy'; + if (File(scrcpyPath).existsSync()) { + return scrcpyPath; + } + } + + return 'scrcpy'; + } } diff --git a/snap/snapcraft-classic.yaml b/snap/snapcraft-classic.yaml new file mode 100644 index 0000000..91f4573 --- /dev/null +++ b/snap/snapcraft-classic.yaml @@ -0,0 +1,50 @@ +name: scrcpy-buddy +version: '1.0.0' +summary: GUI wrapper for scrcpy +description: | + A simple, clean, minimalist GUI wrapper for scrcpy (https://github.com/Genymobile/scrcpy). +grade: stable +base: core24 +confinement: classic + +apps: + scrcpy-buddy: + command: scrcpy_buddy + environment: + PATH: /usr/local/bin:/usr/bin:/bin:$PATH + # GTK/GNOME settings for Flutter + GTK_USE_PORTAL: 1 + XDG_DATA_DIRS: $SNAP/usr/share:$XDG_DATA_DIRS + slots: + - dbus-scrcpy-buddy + +slots: + dbus-scrcpy-buddy: + interface: dbus + bus: session + name: dev.codertainment.scrcpy_buddy + +parts: + scrcpy-buddy: + source: . + plugin: flutter + flutter-target: lib/main.dart + build-packages: + - pkg-config + - libgtk-3-dev + - libglib2.0-dev + - libgdk-pixbuf2.0-dev + - libcairo2-dev + - libpango1.0-dev + - libayatana-appindicator3-dev # For tray_manager + stage-packages: + # Add GTK dependencies since we removed gnome extension + - libgtk-3-0 + - libgdk-pixbuf2.0-0 + - libglib2.0-0 + - libcairo2 + - libpango-1.0-0 + - libatk1.0-0 + - libayatana-appindicator3-1 # For tray_manager + organize: + assets/icon_light_256.png: meta/gui/icon_light.png diff --git a/snap/snapcraft-strict.yaml b/snap/snapcraft-strict.yaml deleted file mode 100644 index f2c9349..0000000 --- a/snap/snapcraft-strict.yaml +++ /dev/null @@ -1,116 +0,0 @@ -# This one doesn't work x_x - -name: scrcpy-buddy -version: '1.0.0' -summary: GUI wrapper for scrcpy -description: | - A simple, clean, minimalist GUI wrapper for scrcpy (https://github.com/Genymobile/scrcpy). -grade: stable -base: core24 -confinement: strict - -#plugs: -# system-binaries: -# interface: system-files -# read: -# - /usr/bin/adb -# - /usr/bin/scrcpy - -apps: - scrcpy-buddy: - command: scrcpy_buddy - extensions: [gnome] - environment: - PATH: $SNAP/lib:$SNAP/usr/lib:$SNAP/bin:$SNAP/usr/bin:$SNAP/usr/local/bin:$PATH - LD_LIBRARY_PATH: "$LD_LIBRARY_PATH:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/android:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/pulseaudio:/var/lib/snapd/lib/gl" - #SCRCPY_SERVER_PATH: "$SNAP/usr/local/share/scrcpy/scrcpy-server" - # Point to bundled binaries - SCRCPY_BUDDY_ADB: "$SNAP/command/adb.wrapper" - #SCRCPY_BUDDY_SCRCPY: "$SNAP/usr/local/bin/scrcpy" - plugs: - - adb-support - - desktop - - desktop-legacy - - x11 - - unity7 - - wayland - - opengl - - network - - network-bind - - home - - process-control - - hardware-observe - slots: - - dbus-scrcpy-buddy - - # Expose scrcpy command directly - #scrcpy: - # command: usr/local/bin/scrcpy - # environment: - # LD_LIBRARY_PATH: "$LD_LIBRARY_PATH:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/android:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/pulseaudio:/var/lib/snapd/lib/gl" - # MESA_GLSL_CACHE_DIR: "$SNAP_USER_DATA" - # LIBGL_DRIVERS_PATH: "$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/dri" - # SCRCPY_SERVER_PATH: "$SNAP/usr/local/share/scrcpy/scrcpy-server" - # plugs: - # - adb-support - # - desktop - # - x11 - # - wayland - # - opengl - # - network - - # Expose adb command directly - adb: - command: command/adb.wrapper - environment: - LD_LIBRARY_PATH: "$LD_LIBRARY_PATH:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/android/" - plugs: - - adb-support - - network - -slots: - dbus-scrcpy-buddy: - interface: dbus - bus: session - name: dev.codertainment.scrcpy_buddy - -parts: - scrcpy-buddy: - source: . - plugin: flutter - flutter-target: lib/main.dart - override-build: | - craftctl default - # Create adb wrapper - mkdir -p $CRAFT_PART_INSTALL/command - cat > $CRAFT_PART_INSTALL/command/adb.wrapper << 'EOF' - #!/bin/bash - export LD_LIBRARY_PATH="$SNAP/usr/lib/x86_64-linux-gnu/android:$LD_LIBRARY_PATH" - exec "$SNAP/usr/bin/adb" "$@" - EOF - chmod +x $CRAFT_PART_INSTALL/command/adb.wrapper -# override-prime: | -# # Clean any existing CMake cache -# rm -rf $CRAFT_PART_BUILD/build/linux/x64/release/CMakeCache.txt -# rm -rf $CRAFT_PART_BUILD/build/linux/x64/release/CMakeFiles -# -# craftctl default - stage-packages: - - adb - - android-sdk-platform-tools-common - - libusb-1.0-0 - - scrcpy - organize: - assets/icon_light_256.png: meta/gui/icon_light.png - - #scrcpy: - # plugin: nil - # stage-snaps: - # - scrcpy - # stage: - # # Exclude conflicting libraries - # - -lib/x86_64-linux-gnu/libcom_err.so.2.1 - # - -usr/lib/x86_64-linux-gnu/libsensors.so.4.4.0 - # - -usr/lib/x86_64-linux-gnu/libtiff.so.5.* - # - -usr/lib/x86_64-linux-gnu/libxml2.so.2.* - # - -usr/share/doc/*/changelog.Debian.gz diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 91f4573..1941b8d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,3 +1,5 @@ +# This one doesn't work x_x + name: scrcpy-buddy version: '1.0.0' summary: GUI wrapper for scrcpy @@ -5,16 +7,57 @@ description: | A simple, clean, minimalist GUI wrapper for scrcpy (https://github.com/Genymobile/scrcpy). grade: stable base: core24 -confinement: classic +confinement: strict apps: + adb: + command: usr/bin/adb + environment: + LD_LIBRARY_PATH: ${SNAP}/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}/android:${LD_LIBRARY_PATH} + plugs: + - adb-support + - network + - network-bind + - raw-usb + scrcpy-buddy: command: scrcpy_buddy + extensions: [gnome] environment: - PATH: /usr/local/bin:/usr/bin:/bin:$PATH - # GTK/GNOME settings for Flutter - GTK_USE_PORTAL: 1 - XDG_DATA_DIRS: $SNAP/usr/share:$XDG_DATA_DIRS + LD_LIBRARY_PATH: ${SNAP}/scrcpy-runtime/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}:${SNAP}/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}/android:${LD_LIBRARY_PATH} + PATH: ${SNAP}/scrcpy-runtime/usr/local/bin:${PATH} + + plugs: + # For running the Android Debug Bridge (adb) and + # connecting the Android devices over USB or TCP/IP. + # https://github.com/Genymobile/scrcpy/blob/master/doc/connection.md + - adb-support + - network + - network-bind + - raw-usb + + # For scrcpy audio forwarding functionality. + # https://github.com/Genymobile/scrcpy/blob/master/doc/audio.md + - alsa + - audio-playback + - pulseaudio + + # For scrcpy v4l2 emulation support. + # https://github.com/Genymobile/scrcpy/blob/master/doc/v4l2.md + - camera + + # For scrcpy video & audio recording support. + # https://github.com/Genymobile/scrcpy/blob/master/doc/recording.md + - home + - removable-media + + # For scrcpy gamepad simulation support. + # https://github.com/Genymobile/scrcpy/blob/master/doc/gamepad.md + - joystick + + # Legacy desktop support. + # https://github.com/canonical/snapd/blob/master/interfaces/builtin/unity7.go + - unity7 slots: - dbus-scrcpy-buddy @@ -24,27 +67,37 @@ slots: bus: session name: dev.codertainment.scrcpy_buddy +plugs: + scrcpy-runtime-2404: + interface: content + target: scrcpy-runtime + default-provider: scrcpy-runtime-2404 + parts: + adb: + plugin: nil + stage-packages: + - adb + - android-libbase + - android-libboringssl + - android-libcutils + - android-liblog + - android-libziparchive + - libbrotli1 + - libcap2 + - liblz4-1 + - libprotobuf32t64 + - libudev1 + - libusb-1.0-0 + - libzstd1 + scrcpy-buddy: source: . plugin: flutter flutter-target: lib/main.dart build-packages: - - pkg-config - - libgtk-3-dev - - libglib2.0-dev - - libgdk-pixbuf2.0-dev - - libcairo2-dev - - libpango1.0-dev - libayatana-appindicator3-dev # For tray_manager stage-packages: - # Add GTK dependencies since we removed gnome extension - - libgtk-3-0 - - libgdk-pixbuf2.0-0 - - libglib2.0-0 - - libcairo2 - - libpango-1.0-0 - - libatk1.0-0 - libayatana-appindicator3-1 # For tray_manager organize: assets/icon_light_256.png: meta/gui/icon_light.png From aab79d0a0a5cf9c25a4b6a36c819d4f4d9af7ef4 Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Mon, 23 Feb 2026 21:11:26 +0100 Subject: [PATCH 02/11] Add to plugs - Specify target and content Signed-off-by: Shripal Jain --- snap/snapcraft.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 1941b8d..6c38270 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -28,6 +28,7 @@ apps: PATH: ${SNAP}/scrcpy-runtime/usr/local/bin:${PATH} plugs: + - scrcpy-runtime-2404 # For running the Android Debug Bridge (adb) and # connecting the Android devices over USB or TCP/IP. # https://github.com/Genymobile/scrcpy/blob/master/doc/connection.md @@ -70,7 +71,8 @@ slots: plugs: scrcpy-runtime-2404: interface: content - target: scrcpy-runtime + content: scrcpy-runtime-2404 + target: $SNAP/scrcpy-runtime default-provider: scrcpy-runtime-2404 parts: @@ -91,6 +93,12 @@ parts: - libusb-1.0-0 - libzstd1 + lp-bug-2016358-workaround: + plugin: nil + override-prime: | + craftctl default + mkdir -p "${CRAFT_PRIME}/scrcpy-runtime" + scrcpy-buddy: source: . plugin: flutter From 22c5a4ce894a0efe1ff4b31ed34e8b02cb5a3162 Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Thu, 26 Feb 2026 20:27:17 +0100 Subject: [PATCH 03/11] Add ffmpeg plug --- snap/snapcraft.yaml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 6c38270..5da8bd8 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -9,6 +9,17 @@ grade: stable base: core24 confinement: strict +plugs: + ffmpeg-2404: + interface: content + target: $SNAP/ffmpeg-platform + default-provider: ffmpeg-2404 + + scrcpy-runtime-2404: + interface: content + target: $SNAP/scrcpy-runtime + default-provider: scrcpy-runtime-2404 + apps: adb: command: usr/bin/adb @@ -24,11 +35,10 @@ apps: command: scrcpy_buddy extensions: [gnome] environment: - LD_LIBRARY_PATH: ${SNAP}/scrcpy-runtime/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}:${SNAP}/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}/android:${LD_LIBRARY_PATH} + LD_LIBRARY_PATH: ${SNAP}/scrcpy-runtime/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}:${SNAP}/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}/android:${LD_LIBRARY_PATH}:${SNAP}/ffmpeg-platform/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR} PATH: ${SNAP}/scrcpy-runtime/usr/local/bin:${PATH} plugs: - - scrcpy-runtime-2404 # For running the Android Debug Bridge (adb) and # connecting the Android devices over USB or TCP/IP. # https://github.com/Genymobile/scrcpy/blob/master/doc/connection.md @@ -68,13 +78,6 @@ slots: bus: session name: dev.codertainment.scrcpy_buddy -plugs: - scrcpy-runtime-2404: - interface: content - content: scrcpy-runtime-2404 - target: $SNAP/scrcpy-runtime - default-provider: scrcpy-runtime-2404 - parts: adb: plugin: nil @@ -97,7 +100,7 @@ parts: plugin: nil override-prime: | craftctl default - mkdir -p "${CRAFT_PRIME}/scrcpy-runtime" + mkdir -p "${CRAFT_PRIME}/"{ffmpeg-2404,scrcpy-runtime} scrcpy-buddy: source: . From b604db57e92ab3ffaf45d98900686902a836e5c1 Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Tue, 31 Mar 2026 21:59:45 +0200 Subject: [PATCH 04/11] Finally fix strict snap confinement fully - Running scrcpy works - Icon works - Tray icon works Signed-off-by: Shripal Jain --- lib/init.dart | 20 +++++++++++--- scripts/rebuild_snap.sh | 40 +++++++++++++++++++++++++++ snap/gui/scrcpy-buddy.desktop | 2 +- snap/snapcraft.yaml | 52 ++++++++++++++++++++++++++++++++++- 4 files changed, 108 insertions(+), 6 deletions(-) create mode 100755 scripts/rebuild_snap.sh diff --git a/lib/init.dart b/lib/init.dart index 45f2fb9..22308ed 100644 --- a/lib/init.dart +++ b/lib/init.dart @@ -17,7 +17,9 @@ Future init() async { // window init await flutter_acrylic.Window.initialize(); if (defaultTargetPlatform == TargetPlatform.windows) { - await flutter_acrylic.Window.setEffect(effect: flutter_acrylic.WindowEffect.acrylic); + await flutter_acrylic.Window.setEffect( + effect: flutter_acrylic.WindowEffect.acrylic, + ); } await WindowManager.instance.ensureInitialized(); if (kReleaseMode) { @@ -35,11 +37,21 @@ Future init() async { initTrayIcon(); } -Future initTrayIcon([Brightness platformBrightness = Brightness.light]) async { +Future initTrayIcon([ + Brightness platformBrightness = Brightness.light, +]) async { // tray icon init try { - final iconName = platformBrightness == Brightness.dark ? 'icon_light' : 'icon_dark'; - await trayManager.setIcon(Platform.isWindows ? 'assets/tray/$iconName.ico' : 'assets/tray/$iconName.png'); + final iconName = platformBrightness == Brightness.dark + ? 'icon_light' + : 'icon_dark'; + final snapDir = Platform.environment['SNAP']; + final iconPath = snapDir != null + ? '$snapDir/data/flutter_assets/assets/tray/$iconName.png' + : Platform.isWindows + ? 'assets/tray/$iconName.ico' + : 'assets/tray/$iconName.png'; // fallback for non-snap + await trayManager.setIcon(iconPath); if (Platform.isLinux) { // Don't show title on macOS (takes up more space in menu bar) // Not supported on Windows diff --git a/scripts/rebuild_snap.sh b/scripts/rebuild_snap.sh new file mode 100755 index 0000000..f7fc16c --- /dev/null +++ b/scripts/rebuild_snap.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +SNAP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SNAP_NAME="scrcpy-buddy" + +echo "==> Working directory: $SNAP_DIR" +cd "$SNAP_DIR" + +# 1. Remove installed snaps +echo "" +echo "==> [1/5] Removing installed snaps..." +sudo snap remove --purge "$SNAP_NAME" || true +sudo snap remove --purge scrcpy-runtime-2404 || true + +# 2. Clean snapcraft build artifacts +echo "" +echo "==> [2/5] Cleaning snapcraft build..." +snapcraft clean --use-lxd + +# 3. Build the snap +echo "" +echo "==> [3/5] Building snap with LXD..." +snapcraft pack --use-lxd + +# 4. Install the freshly built snap +SNAP_FILE=$(ls -t "${SNAP_DIR}"/*.snap | head -1) +echo "" +echo "==> [4/5] Installing snap: $SNAP_FILE" +sudo snap install --dangerous "$SNAP_FILE" + +# 5. Connect interfaces +echo "" +echo "==> [5/5] Connecting snap interfaces..." +sudo snap disconnect "$SNAP_NAME:gnome-46-2404" || true +sudo snap connect "$SNAP_NAME:gnome-46-2404" gnome-46-2404:gnome-46-2404 +sudo snap connect "$SNAP_NAME:scrcpy-runtime-2404" scrcpy-runtime-2404:scrcpy-runtime-2404 + +echo "" +echo "==> Done! You can now launch: snap run $SNAP_NAME" diff --git a/snap/gui/scrcpy-buddy.desktop b/snap/gui/scrcpy-buddy.desktop index cda28ba..6a34b3a 100644 --- a/snap/gui/scrcpy-buddy.desktop +++ b/snap/gui/scrcpy-buddy.desktop @@ -2,7 +2,7 @@ Name=scrcpy buddy Comment=GUI wrapper for scrcpy Exec=scrcpy-buddy -Icon=${SNAP}/meta/gui/icon_light.png +Icon=${SNAP}/meta/gui/icon.png Terminal=false Type=Application Categories=Utility; diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 9bb87c3..2622f60 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -78,6 +78,25 @@ slots: bus: session name: dev.codertainment.scrcpy_buddy +layout: + # WORKAROUND: + # libproxy.so.1 (from gnome-46-2404) has a hardcoded RUNPATH of + # /usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}/libproxy for loading + # libpxbackend-1.0.so. Map it so the dynamic linker can resolve it. + # https://github.com/canonical/snapcraft/issues/6054 + /usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}/libproxy: + symlink: $SNAP/gnome-platform/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}/libproxy + # This fixes the "ERROR: Could not open icon image: /usr/local/share/icons/hicolor/256x256/apps/scrcpy.png" + # error at runtime. + /usr/local/share/icons: + symlink: $SNAP/scrcpy-runtime/usr/local/share/icons + # This allows scrcpy to locate the scrcpy-server binary. + # "ERROR: '/usr/local/share/scrcpy/scrcpy-server' does not exist or is not a regular file" + /usr/local/share/scrcpy: + symlink: $SNAP/scrcpy-runtime/usr/local/share/scrcpy + /usr/share/alsa: + bind: $SNAP/usr/share/alsa + parts: adb: plugin: nil @@ -102,6 +121,35 @@ parts: craftctl default mkdir -p "${CRAFT_PRIME}/"{ffmpeg-2404,scrcpy-runtime} + # Avoid buggy mount namespace handling in snapd, which can prevent + # manually connected content interfaces from appearing in the snap + # runtime. + # https://bugs.launchpad.net/snapd/+bug/2144666 + lp-bug-2144666-workaround: + plugin: nil + override-prime: | + set -eu + craftctl default + + content_interface_mount_target_dirs=( + # From the "gnome" extension + "${CRAFT_PRIME}/gpu-2404" + "${CRAFT_PRIME}/gpu-2404-2" # mesa-2404 content in spool mode + "${CRAFT_PRIME}/data-dir/icons" + "${CRAFT_PRIME}/data-dir/sounds" + "${CRAFT_PRIME}/data-dir/themes" + "${CRAFT_PRIME}/gnome-platform" + + # From the "scrcpy-runtime-2404" content interface + "${CRAFT_PRIME}/scrcpy-runtime" + + # From the "ffmpeg-2404" content interface + "${CRAFT_PRIME}/ffmpeg-platform" + ) + for dir in "${content_interface_mount_target_dirs[@]}"; do + mkdir -p "${dir}" + done + scrcpy-buddy: source: . plugin: flutter @@ -110,5 +158,7 @@ parts: - libayatana-appindicator3-dev # For tray_manager stage-packages: - libayatana-appindicator3-1 # For tray_manager + - libdbusmenu-gtk3-4 # Missing transitive dep + - libayatana-ido3-0.4-0 # Missing transitive dep organize: - assets/icon_light_256.png: meta/gui/icon_light.png + data/flutter_assets/assets/icon_light_256.png: meta/gui/icon.png From dcbfc16c126d1a0bf8e6c98c67eb4f6d204461bc Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Sun, 12 Apr 2026 17:02:25 +0200 Subject: [PATCH 05/11] Introduce SnapEnvironment service for snap-specific environment handling --- lib/service/snap_environment.dart | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 lib/service/snap_environment.dart diff --git a/lib/service/snap_environment.dart b/lib/service/snap_environment.dart new file mode 100644 index 0000000..e54c312 --- /dev/null +++ b/lib/service/snap_environment.dart @@ -0,0 +1,40 @@ +import 'dart:developer' as developer; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; + +/// Provides information about the Snap environment the app may be running in. +class SnapEnvironment { + /// Returns `true` if the application is running inside a Snap package. + bool get isRunningInSnap => Platform.environment['SNAP'] != null; + + /// Returns the correct persistent storage directory for the app, + /// respecting the Snap environment variables when running as a Snap. + /// + /// Priority: `SNAP_USER_COMMON` → `SNAP_USER_DATA` → system app support dir. + Future getStoragePath() async { + final snapUserCommon = Platform.environment['SNAP_USER_COMMON']; + final snapUserData = Platform.environment['SNAP_USER_DATA']; + + if (snapUserCommon != null) { + return path.join(snapUserCommon, 'scrcpy_buddy'); + } else if (snapUserData != null) { + return path.join(snapUserData, 'scrcpy_buddy'); + } else { + final dir = await getApplicationSupportDirectory(); + return path.join(dir.path, 'scrcpy_buddy_db'); + } + } + + /// Returns the scrcpy binary path for the Snap bundle, or `null` if the + /// bundle path cannot be determined or the binary does not exist there. + String? getSnapScrcpyPath() { + final snapPath = Platform.environment['SNAP']; + if (snapPath == null) return null; + + developer.log('SNAP_PATH: $snapPath', name: 'scrcpy_buddy.snap'); + final scrcpyPath = '$snapPath/scrcpy-runtime/usr/local/bin/scrcpy'; + return File(scrcpyPath).existsSync() ? scrcpyPath : null; + } +} From 3a1986387db115ccca060bd67e7ac203112b87e4 Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Sun, 12 Apr 2026 17:02:57 +0200 Subject: [PATCH 06/11] Use the SnapEnvironment service in ScrcpyService while getting default executable path --- lib/injector.dart | 7 ++++++- lib/service/scrcpy_service.dart | 19 ++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/injector.dart b/lib/injector.dart index 8cbf550..df23b32 100644 --- a/lib/injector.dart +++ b/lib/injector.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; import 'package:scrcpy_buddy/service/adb_service.dart'; import 'package:scrcpy_buddy/service/running_process_manager.dart'; import 'package:scrcpy_buddy/service/scrcpy_service.dart'; +import 'package:scrcpy_buddy/service/snap_environment.dart'; import 'application/adb_result_parser.dart'; @@ -16,7 +17,11 @@ List get providers => [ create: (context) => AdbService(context.read(), context.read()), ), - Provider(create: (context) => ScrcpyService(context.read())), + Provider(create: (_) => SnapEnvironment()), + + Provider( + create: (context) => ScrcpyService(context.read(), context.read()), + ), Provider(create: (_) => RunningProcessManager()), ]; diff --git a/lib/service/scrcpy_service.dart b/lib/service/scrcpy_service.dart index d9cb07e..7d65d12 100644 --- a/lib/service/scrcpy_service.dart +++ b/lib/service/scrcpy_service.dart @@ -6,11 +6,13 @@ import 'package:process/process.dart'; import 'package:scrcpy_buddy/application/model/scrcpy/device_app.dart'; import 'package:scrcpy_buddy/application/model/scrcpy/scrcpy_error.dart'; import 'package:scrcpy_buddy/application/model/scrcpy/scrcpy_result.dart'; +import 'package:scrcpy_buddy/service/snap_environment.dart'; class ScrcpyService { final ProcessManager _processManager; + final SnapEnvironment _snapEnvironment; - ScrcpyService(this._processManager); + ScrcpyService(this._processManager, this._snapEnvironment); Future start(String deviceSerial, String adbPath, String path, List args) async { try { @@ -92,16 +94,7 @@ class ScrcpyService { String _getExecutable(String path) => path.isEmpty ? getScrcpyPath() : path; - String getScrcpyPath() { - final snapPath = Platform.environment['SNAP']; - if (snapPath != null) { - print("SNAP_PATH: $snapPath"); - final scrcpyPath = '$snapPath/scrcpy-runtime/usr/local/bin/scrcpy'; - if (File(scrcpyPath).existsSync()) { - return scrcpyPath; - } - } - - return 'scrcpy'; - } + /// Returns the resolved scrcpy binary path, preferring the Snap bundle + /// when running inside a Snap package. + String getScrcpyPath() => _snapEnvironment.getSnapScrcpyPath() ?? 'scrcpy'; } From cde9569cf21cab01386edc24a360113a84d43433 Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Sun, 12 Apr 2026 17:03:41 +0200 Subject: [PATCH 07/11] Use the SnapEnvironment service for ObjectBox storage path --- lib/application/objectbox.dart | 27 +++------------------------ lib/main.dart | 3 ++- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/lib/application/objectbox.dart b/lib/application/objectbox.dart index 2084f5c..a8abb82 100644 --- a/lib/application/objectbox.dart +++ b/lib/application/objectbox.dart @@ -1,7 +1,6 @@ import 'dart:io'; -import 'package:path/path.dart' as path; -import 'package:path_provider/path_provider.dart'; +import 'package:scrcpy_buddy/service/snap_environment.dart'; import '../objectbox.g.dart'; // created by `flutter pub run build_runner build` import 'model/profile.dart'; @@ -14,11 +13,9 @@ class ObjectBox { // Add any additional setup code, e.g. build queries. } - static Future create() async { - // Get snap-safe directory - final String dbPath = await _getStoragePath(); + static Future create(SnapEnvironment snapEnvironment) async { + final String dbPath = await snapEnvironment.getStoragePath(); - // Ensure directory exists final dir = Directory(dbPath); if (!dir.existsSync()) { await dir.create(recursive: true); @@ -29,22 +26,4 @@ class ObjectBox { } Box get profileBox => store.box(); - - static Future _getStoragePath() async { - // Check if running in snap - final snapUserCommon = Platform.environment['SNAP_USER_COMMON']; - final snapUserData = Platform.environment['SNAP_USER_DATA']; - - if (snapUserCommon != null) { - // Use SNAP_USER_COMMON for persistent data across snap revisions - return path.join(snapUserCommon, 'scrcpy_buddy'); - } else if (snapUserData != null) { - // Use SNAP_USER_DATA (backed up on snap refresh) - return path.join(snapUserData, 'scrcpy_buddy'); - } else { - // Not running as snap, use default location - final dir = await getApplicationSupportDirectory(); - return path.join(dir.path, "scrcpy_buddy_db"); - } - } } diff --git a/lib/main.dart b/lib/main.dart index b795cae..f6bd6b6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,6 +14,7 @@ import 'package:scrcpy_buddy/init.dart'; import 'package:scrcpy_buddy/presentation/devices/bloc/devices_bloc.dart'; import 'package:scrcpy_buddy/presentation/search/bloc/search_bloc.dart'; import 'package:scrcpy_buddy/routes.dart'; +import 'package:scrcpy_buddy/service/snap_environment.dart'; import 'package:system_theme/system_theme.dart'; import 'package:window_manager/window_manager.dart'; @@ -30,7 +31,7 @@ late ObjectBox _objectBox; void main() async { await init(); await _prefs.initialize(); - _objectBox = await ObjectBox.create(); + _objectBox = await ObjectBox.create(SnapEnvironment()); runApp(const MyApp()); } From 0b7d4ef5910f3ec592a1c87545a01bfacb92ab79 Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Sun, 12 Apr 2026 17:04:07 +0200 Subject: [PATCH 08/11] Don't allow changing executable path settings when running inside snap environment --- assets/i18n/en.json | 3 ++- .../widget/adb_executable_setting.dart | 20 ++++++++++++------- .../widget/scrcpy_executable_setting.dart | 20 ++++++++++++------- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/assets/i18n/en.json b/assets/i18n/en.json index b0af183..4128df3 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -110,6 +110,7 @@ "serialCopied": "Device serial copied to clipboard" }, "settings": { + "snapEnvironmentHint": "Executable paths are preset when running in snap", "adbExecutable": { "title": "adb executable", "dialogTitle": "adb scrcpy executable", @@ -356,4 +357,4 @@ "create": "Create", "setDefault": "Set default" } -} +} \ No newline at end of file diff --git a/lib/presentation/settings/widget/adb_executable_setting.dart b/lib/presentation/settings/widget/adb_executable_setting.dart index 5ac762c..d95cedc 100644 --- a/lib/presentation/settings/widget/adb_executable_setting.dart +++ b/lib/presentation/settings/widget/adb_executable_setting.dart @@ -10,6 +10,7 @@ import 'package:scrcpy_buddy/presentation/devices/bloc/devices_bloc.dart'; import 'package:scrcpy_buddy/presentation/extension/translation_extension.dart'; import 'package:scrcpy_buddy/presentation/widgets/app_widgets.dart'; import 'package:scrcpy_buddy/service/adb_service.dart'; +import 'package:scrcpy_buddy/service/snap_environment.dart'; class AdbExecutableSetting extends StatefulWidget { const AdbExecutableSetting({super.key}); @@ -25,6 +26,7 @@ class _AdbExecutableSettingState extends AppModuleState { late final _executablePreference = context.read().adbExecutable; late final _adbService = context.read(); late final _devicesBloc = context.read(); + late final _snapEnvironment = context.read(); final _infoFlyoutController = FlyoutController(); final _textController = TextEditingController(); @@ -96,26 +98,30 @@ class _AdbExecutableSettingState extends AppModuleState { @override Widget build(BuildContext context) { + final isSnap = _snapEnvironment.isRunningInSnap; return Row( children: [ Expanded( child: TextBox( controller: _textController, - enabled: !_isSaving, + enabled: !_isSaving && !isSnap, textInputAction: TextInputAction.done, + placeholder: isSnap ? context.translatedText(key: 'settings.snapEnvironmentHint') : null, onSubmitted: (value) => _validateAndSave(value), onTapOutside: (_) => _validateAndSave(_textController.text), suffix: _isSaving ? ProgressRing() : IconButton( icon: Icon(WindowsIcons.file_explorer), - onPressed: () async { - final result = await openFile(); + onPressed: isSnap + ? null + : () async { + final result = await openFile(); - if (result != null) { - _validateAndSave(result.path); - } - }, + if (result != null) { + _validateAndSave(result.path); + } + }, ), ), ), diff --git a/lib/presentation/settings/widget/scrcpy_executable_setting.dart b/lib/presentation/settings/widget/scrcpy_executable_setting.dart index 130c1cd..8fb2b29 100644 --- a/lib/presentation/settings/widget/scrcpy_executable_setting.dart +++ b/lib/presentation/settings/widget/scrcpy_executable_setting.dart @@ -8,6 +8,7 @@ import 'package:scrcpy_buddy/application/extension/scrcpy_error_extension.dart'; import 'package:scrcpy_buddy/presentation/extension/translation_extension.dart'; import 'package:scrcpy_buddy/presentation/widgets/app_widgets.dart'; import 'package:scrcpy_buddy/service/scrcpy_service.dart'; +import 'package:scrcpy_buddy/service/snap_environment.dart'; class ScrcpyExecutableSetting extends StatefulWidget { const ScrcpyExecutableSetting({super.key}); @@ -22,6 +23,7 @@ class _ScrcpyExecutableSettingState extends AppModuleState().scrcpyExecutable; late final _scrcpyService = context.read(); + late final _snapEnvironment = context.read(); final _infoFlyoutController = FlyoutController(); final _textController = TextEditingController(); @@ -85,26 +87,30 @@ class _ScrcpyExecutableSettingState extends AppModuleState _validateAndSave(value), onTapOutside: (_) => _validateAndSave(_textController.text), suffix: _isSaving ? ProgressRing() : IconButton( icon: Icon(WindowsIcons.file_explorer), - onPressed: () async { - final result = await openFile(); + onPressed: isSnap + ? null + : () async { + final result = await openFile(); - if (result != null) { - _validateAndSave(result.path); - } - }, + if (result != null) { + _validateAndSave(result.path); + } + }, ), ), ), From c3c67858b28c3ec7afc0b8e2747512ab6d605f43 Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Sun, 12 Apr 2026 17:04:30 +0200 Subject: [PATCH 09/11] Fix snapcraft lint warnings - Add metadata --- snap/snapcraft-classic.yaml | 50 ------------------------------------- snap/snapcraft.yaml | 41 ++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 61 deletions(-) delete mode 100644 snap/snapcraft-classic.yaml diff --git a/snap/snapcraft-classic.yaml b/snap/snapcraft-classic.yaml deleted file mode 100644 index 91f4573..0000000 --- a/snap/snapcraft-classic.yaml +++ /dev/null @@ -1,50 +0,0 @@ -name: scrcpy-buddy -version: '1.0.0' -summary: GUI wrapper for scrcpy -description: | - A simple, clean, minimalist GUI wrapper for scrcpy (https://github.com/Genymobile/scrcpy). -grade: stable -base: core24 -confinement: classic - -apps: - scrcpy-buddy: - command: scrcpy_buddy - environment: - PATH: /usr/local/bin:/usr/bin:/bin:$PATH - # GTK/GNOME settings for Flutter - GTK_USE_PORTAL: 1 - XDG_DATA_DIRS: $SNAP/usr/share:$XDG_DATA_DIRS - slots: - - dbus-scrcpy-buddy - -slots: - dbus-scrcpy-buddy: - interface: dbus - bus: session - name: dev.codertainment.scrcpy_buddy - -parts: - scrcpy-buddy: - source: . - plugin: flutter - flutter-target: lib/main.dart - build-packages: - - pkg-config - - libgtk-3-dev - - libglib2.0-dev - - libgdk-pixbuf2.0-dev - - libcairo2-dev - - libpango1.0-dev - - libayatana-appindicator3-dev # For tray_manager - stage-packages: - # Add GTK dependencies since we removed gnome extension - - libgtk-3-0 - - libgdk-pixbuf2.0-0 - - libglib2.0-0 - - libcairo2 - - libpango-1.0-0 - - libatk1.0-0 - - libayatana-appindicator3-1 # For tray_manager - organize: - assets/icon_light_256.png: meta/gui/icon_light.png diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 2622f60..77fdb0a 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,13 +1,32 @@ -# This one doesn't work x_x - name: scrcpy-buddy -version: '1.0.5' -summary: GUI wrapper for scrcpy -description: | - A simple, clean, minimalist GUI wrapper for scrcpy (https://github.com/Genymobile/scrcpy). +version: "1.0.5" +title: scrcpy buddy grade: stable base: core24 confinement: strict +summary: GUI for scrcpy +description: | + A simple, clean, minimalist GUI for scrcpy (https://github.com/Genymobile/scrcpy). +license: Apache-2.0 +contact: https://github.com/Codertainment/scrcpy_buddy/issues +issues: https://github.com/Codertainment/scrcpy_buddy/issues +source-code: https://github.com/Codertainment/scrcpy_buddy +website: https://github.com/Codertainment/scrcpy_buddy +donation: https://github.com/Codertainment/scrcpy_buddy + +# Suppress lint warnings for libraries that are either dynamically loaded +# at runtime (libobjectbox.so) or pulled in as transitive dependencies. +lint: + ignore: + - library: + - lib/libobjectbox.so + - usr/lib/*/libcolordprivate.so.2* + - usr/lib/*/libdconf.so.1* + - usr/lib/*/libicuio.so.74* + - usr/lib/*/libicutest.so.74* + - usr/lib/*/libpsx.so.2* + - usr/lib/*/libicutu.so.74* + - usr/lib/*/libicui18n.so.74* plugs: ffmpeg-2404: @@ -33,7 +52,7 @@ apps: scrcpy-buddy: command: scrcpy_buddy - extensions: [gnome] + extensions: [ gnome ] environment: LD_LIBRARY_PATH: ${SNAP}/scrcpy-runtime/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}:${SNAP}/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR}/android:${LD_LIBRARY_PATH}:${SNAP}/ffmpeg-platform/usr/lib/${CRAFT_ARCH_TRIPLET_BUILD_FOR} PATH: ${SNAP}/scrcpy-runtime/usr/local/bin:${PATH} @@ -155,10 +174,10 @@ parts: plugin: flutter flutter-target: lib/main.dart build-packages: - - libayatana-appindicator3-dev # For tray_manager + - libayatana-appindicator3-dev # For tray_manager stage-packages: - - libayatana-appindicator3-1 # For tray_manager - - libdbusmenu-gtk3-4 # Missing transitive dep - - libayatana-ido3-0.4-0 # Missing transitive dep + - libayatana-appindicator3-1 # For tray_manager + - libdbusmenu-gtk3-4 # Missing transitive dep + - libayatana-ido3-0.4-0 # Missing transitive dep organize: data/flutter_assets/assets/icon_light_256.png: meta/gui/icon.png From 0c9323ce51376248a1db549e0c8da209604819d9 Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Sun, 12 Apr 2026 17:05:31 +0200 Subject: [PATCH 10/11] Revert empty EOF in translation file --- assets/i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 4128df3..3cc31fa 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -357,4 +357,4 @@ "create": "Create", "setDefault": "Set default" } -} \ No newline at end of file +} From 44fe1767ea2255051884a176e54c367aba8437b6 Mon Sep 17 00:00:00 2001 From: Shripal Jain Date: Sun, 12 Apr 2026 17:07:57 +0200 Subject: [PATCH 11/11] Extract tray icon logic to SnapEnvironment --- lib/init.dart | 23 ++++++++--------------- lib/service/snap_environment.dart | 8 ++++++++ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/init.dart b/lib/init.dart index 22308ed..c7ed4ed 100644 --- a/lib/init.dart +++ b/lib/init.dart @@ -4,6 +4,7 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_acrylic/flutter_acrylic.dart' as flutter_acrylic; import 'package:scrcpy_buddy/main.reflectable.dart'; +import 'package:scrcpy_buddy/service/snap_environment.dart'; import 'package:system_theme/system_theme.dart'; import 'package:tray_manager/tray_manager.dart'; import 'package:window_manager/window_manager.dart'; @@ -17,9 +18,7 @@ Future init() async { // window init await flutter_acrylic.Window.initialize(); if (defaultTargetPlatform == TargetPlatform.windows) { - await flutter_acrylic.Window.setEffect( - effect: flutter_acrylic.WindowEffect.acrylic, - ); + await flutter_acrylic.Window.setEffect(effect: flutter_acrylic.WindowEffect.acrylic); } await WindowManager.instance.ensureInitialized(); if (kReleaseMode) { @@ -37,20 +36,14 @@ Future init() async { initTrayIcon(); } -Future initTrayIcon([ - Brightness platformBrightness = Brightness.light, -]) async { +Future initTrayIcon([Brightness platformBrightness = Brightness.light]) async { // tray icon init try { - final iconName = platformBrightness == Brightness.dark - ? 'icon_light' - : 'icon_dark'; - final snapDir = Platform.environment['SNAP']; - final iconPath = snapDir != null - ? '$snapDir/data/flutter_assets/assets/tray/$iconName.png' - : Platform.isWindows - ? 'assets/tray/$iconName.ico' - : 'assets/tray/$iconName.png'; // fallback for non-snap + final iconName = platformBrightness == Brightness.dark ? 'icon_light' : 'icon_dark'; + final snapIconPath = SnapEnvironment().getSnapTrayIconPath(iconName); + final iconPath = + snapIconPath ?? + (Platform.isWindows ? 'assets/tray/$iconName.ico' : 'assets/tray/$iconName.png'); // fallback for non-snap await trayManager.setIcon(iconPath); if (Platform.isLinux) { // Don't show title on macOS (takes up more space in menu bar) diff --git a/lib/service/snap_environment.dart b/lib/service/snap_environment.dart index e54c312..907f7f5 100644 --- a/lib/service/snap_environment.dart +++ b/lib/service/snap_environment.dart @@ -37,4 +37,12 @@ class SnapEnvironment { final scrcpyPath = '$snapPath/scrcpy-runtime/usr/local/bin/scrcpy'; return File(scrcpyPath).existsSync() ? scrcpyPath : null; } + + /// Returns the path to the tray icon in the Snap bundle, or `null` if the + /// bundle path cannot be determined. + String? getSnapTrayIconPath(String iconName) { + final snapDir = Platform.environment['SNAP']; + if (snapDir == null) return null; + return '$snapDir/data/flutter_assets/assets/tray/$iconName.png'; + } }