diff --git a/nixos/modules/services/desktop-managers/lomiri.nix b/nixos/modules/services/desktop-managers/lomiri.nix index 8f04e33291ac..638c1ef9bfcc 100644 --- a/nixos/modules/services/desktop-managers/lomiri.nix +++ b/nixos/modules/services/desktop-managers/lomiri.nix @@ -95,6 +95,7 @@ in lomiri-gallery-app lomiri-history-service lomiri-mediaplayer-app + lomiri-music-app lomiri-polkit-agent lomiri-schemas # exposes some required dbus interfaces lomiri-session # wrappers to properly launch the session @@ -198,37 +199,54 @@ in "/share/sounds" ]; - systemd.user.services = { - # Unconditionally run service that collects system-installed URL handlers before LUD - # TODO also run user-installed one? - "lomiri-url-dispatcher-update-system-dir" = { - description = "Lomiri URL dispatcher system directory updater"; - wantedBy = [ "lomiri-url-dispatcher.service" ]; - before = [ "lomiri-url-dispatcher.service" ]; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${pkgs.lomiri.lomiri-url-dispatcher}/libexec/lomiri-url-dispatcher/lomiri-update-directory /run/current-system/sw/share/lomiri-url-dispatcher/urls/"; - }; - }; - - "lomiri-polkit-agent" = rec { - description = "Lomiri Polkit agent"; - wantedBy = [ + systemd.user.services = + let + lomiriServiceNames = [ "lomiri.service" "lomiri-full-greeter.service" "lomiri-full-shell.service" "lomiri-greeter.service" "lomiri-shell.service" ]; - after = [ "graphical-session.target" ]; - partOf = wantedBy; - serviceConfig = { - Type = "simple"; - Restart = "always"; - ExecStart = "${pkgs.lomiri.lomiri-polkit-agent}/libexec/lomiri-polkit-agent/policykit-agent"; + in + { + # Unconditionally run service that collects system-installed URL handlers before LUD + # TODO also run user-installed one? + "lomiri-url-dispatcher-update-system-dir" = { + description = "Lomiri URL dispatcher system directory updater"; + wantedBy = [ "lomiri-url-dispatcher.service" ]; + before = [ "lomiri-url-dispatcher.service" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.lomiri.lomiri-url-dispatcher}/libexec/lomiri-url-dispatcher/lomiri-update-directory /run/current-system/sw/share/lomiri-url-dispatcher/urls/"; + }; + }; + + "lomiri-polkit-agent" = { + description = "Lomiri Polkit agent"; + wantedBy = lomiriServiceNames; + after = [ "graphical-session.target" ]; + partOf = lomiriServiceNames; + serviceConfig = { + Type = "simple"; + Restart = "always"; + ExecStart = "${pkgs.lomiri.lomiri-polkit-agent}/libexec/lomiri-polkit-agent/policykit-agent"; + }; + }; + + "mediascanner-2.0" = { + description = "Media Scanner"; + wantedBy = lomiriServiceNames; + before = lomiriServiceNames; + partOf = lomiriServiceNames; + serviceConfig = { + Type = "dbus"; + BusName = "com.lomiri.MediaScanner2.Daemon"; + Restart = "on-failure"; + ExecStart = "${lib.getExe pkgs.lomiri.mediascanner2}"; + }; }; }; - }; systemd.services = { "dbus-com.lomiri.UserMetrics" = { diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index cb7f599eb6ea..24d6e0725f2a 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -569,6 +569,7 @@ in { lomiri-docviewer-app = runTest ./lomiri-docviewer-app.nix; lomiri-filemanager-app = runTest ./lomiri-filemanager-app.nix; lomiri-mediaplayer-app = runTest ./lomiri-mediaplayer-app.nix; + lomiri-music-app = runTest ./lomiri-music-app.nix; lomiri-gallery-app = runTest ./lomiri-gallery-app.nix; lomiri-system-settings = handleTest ./lomiri-system-settings.nix {}; lorri = handleTest ./lorri/default.nix {}; diff --git a/nixos/tests/lomiri-music-app.nix b/nixos/tests/lomiri-music-app.nix new file mode 100644 index 000000000000..93a3889c9127 --- /dev/null +++ b/nixos/tests/lomiri-music-app.nix @@ -0,0 +1,193 @@ +{ lib, ... }: +let + ocrContent = "Music Test"; + musicFile = "test.mp3"; + + ocrPauseColor = "#FF00FF"; + ocrStartColor = "#00FFFF"; +in +{ + name = "lomiri-music-app-standalone"; + meta = { + maintainers = lib.teams.lomiri.members; + # This needs a Linux VM + platforms = lib.platforms.linux; + }; + + nodes.machine = + { config, pkgs, ... }: + { + imports = [ + ./common/auto.nix + ./common/user-account.nix + ./common/x11.nix + ]; + + services.xserver.enable = true; + + environment = { + # Setup video + etc."${musicFile}".source = + pkgs.runCommand musicFile + { + nativeBuildInputs = with pkgs; [ + ffmpeg # produce music + (imagemagick.override { ghostscriptSupport = true; }) # produce OCR-able cover image + ]; + } + '' + magick -size 400x400 canvas:white -pointsize 40 -fill black -annotate +100+100 '${ocrContent}' output.png + ffmpeg -re \ + -f lavfi -i anullsrc=channel_layout=mono:sample_rate=44100 \ + -i output.png \ + -map 0:0 -map 1:0 \ + -id3v2_version 3 \ + -metadata:s:v title="Album cover" \ + -metadata:s:v comment="Cover (front)" \ + -t 120 \ + $out -loglevel fatal + ''; + + systemPackages = + with pkgs; + [ + xdg-user-dirs # generate XDG dirs + xdotool # mouse movement + ] + ++ (with pkgs.lomiri; [ + mediascanner2 + lomiri-music-app + lomiri-thumbnailer + # To check if playback actually works, or is broken due to missing codecs, we need to make the app's icons more OCR-able + (lib.meta.hiPrio ( + suru-icon-theme.overrideAttrs (oa: { + # Colour the background in special colours, which we can OCR for + postPatch = + (oa.postPatch or "") + + '' + substituteInPlace suru/actions/scalable/media-playback-pause.svg \ + --replace-fail 'fill:none' 'fill:${ocrPauseColor}' + + substituteInPlace suru/actions/scalable/media-playback-start.svg \ + --replace-fail 'fill:none' 'fill:${ocrStartColor}' + ''; + }) + )) + ]); + + variables = { + UITK_ICON_THEME = "suru"; + }; + }; + + # Get mediascanner-2.0.service + services.desktopManager.lomiri.enable = lib.mkForce true; + + # ...but stick with icewm + services.displayManager.defaultSession = lib.mkForce "none+icewm"; + + systemd.tmpfiles.settings = { + "10-lomiri-music-app-test-setup" = { + "/root/Music".d = { + mode = "0755"; + user = "root"; + group = "root"; + }; + "/root/Music/${musicFile}"."C+".argument = "/etc/${musicFile}"; + }; + }; + + i18n.supportedLocales = [ "all" ]; + }; + + enableOCR = true; + + testScript = '' + from collections.abc import Callable + import tempfile + import subprocess + + pauseColor: str = "${ocrPauseColor}" + startColor: str = "${ocrStartColor}" + + # Based on terminal-emulators.nix' check_for_pink + def check_for_color(color: str) -> Callable[[bool], bool]: + def check_for_color_retry(final=False) -> bool: + with tempfile.NamedTemporaryFile() as tmpin: + machine.send_monitor_command("screendump {}".format(tmpin.name)) + + cmd = 'convert {} -define histogram:unique-colors=true -format "%c" histogram:info:'.format( + tmpin.name + ) + ret = subprocess.run(cmd, shell=True, capture_output=True) + if ret.returncode != 0: + raise Exception( + "image analysis failed with exit code {}".format(ret.returncode) + ) + + text = ret.stdout.decode("utf-8") + return color in text + + return check_for_color_retry + + machine.wait_for_x() + + # mediascanner2 needs XDG dirs to exist + machine.succeed("xdg-user-dirs-update") + + # mediascanner2 needs to have run, is only started automatically by Lomiri + machine.systemctl("start mediascanner-2.0.service", "root") + + with subtest("lomiri music launches"): + machine.succeed("lomiri-music-app >&2 &") + machine.wait_for_text("favorite music") + machine.send_key("alt-f10") + machine.screenshot("lomiri-music") + + with subtest("lomiri music plays music"): + machine.succeed("xdotool mousemove 30 720 click 1") # Skip intro + machine.wait_for_text("Albums") + machine.succeed("xdotool mousemove 25 45 click 1") # Open categories + machine.wait_for_text("Tracks") + machine.succeed("xdotool mousemove 25 240 click 1") # Switch to Tracks category + machine.wait_for_text("test") # the test file + machine.screenshot("lomiri-music_listing") + + # Ensure pause colours isn't present already + assert ( + check_for_color(pauseColor)(True) == False + ), "pauseColor {} was present on the screen before we selected anything!".format(pauseColor) + + machine.succeed("xdotool mousemove 25 120 click 1") # Select the track + + # Waiting for pause icon to be displayed + with machine.nested("Waiting for the screen to have pauseColor {} on it:".format(pauseColor)): + retry(check_for_color(pauseColor)) + + machine.screenshot("lomiri-music_playback") + + # Ensure play colours isn't present already + assert ( + check_for_color(startColor)(True) == False + ), "startColor {} was present on the screen before we were expecting it to be!".format(startColor) + + machine.succeed("xdotool mousemove 860 480 click 1") # Pause track (only works if app can actually decode the file) + + # Waiting for play icon to be displayed + with machine.nested("Waiting for the screen to have startColor {} on it:".format(startColor)): + retry(check_for_color(startColor)) + + machine.screenshot("lomiri-music_paused") + + # Lastly, check if generated cover image got extracted properly + # if not, indicates an issue with mediascanner / lomiri-thumbnailer + machine.wait_for_text("${ocrContent}") + + machine.succeed("pkill -f lomiri-music-app") + + with subtest("lomiri music localisation works"): + machine.succeed("env LANG=de_DE.UTF-8 lomiri-music-app .mp4 >&2 &") + machine.wait_for_text("Titel") + machine.screenshot("lomiri-music_localised") + ''; +} diff --git a/pkgs/desktops/lomiri/applications/lomiri-music-app/default.nix b/pkgs/desktops/lomiri/applications/lomiri-music-app/default.nix new file mode 100644 index 000000000000..f3688b33d9e3 --- /dev/null +++ b/pkgs/desktops/lomiri/applications/lomiri-music-app/default.nix @@ -0,0 +1,124 @@ +{ + stdenv, + lib, + fetchFromGitLab, + fetchpatch, + gitUpdater, + nixosTests, + cmake, + gettext, + gst_all_1, + libusermetrics, + lomiri-content-hub, + lomiri-thumbnailer, + lomiri-ui-toolkit, + qtbase, + qtdeclarative, + qtmultimedia, + qtsystems, + wrapGAppsHook3, + wrapQtAppsHook, +}: + +stdenv.mkDerivation (finalAttrs: { + pname = "lomiri-music-app"; + version = "3.2.2"; + + src = fetchFromGitLab { + owner = "ubports"; + repo = "development/apps/lomiri-music-app"; + rev = "refs/tags/v${finalAttrs.version}"; + hash = "sha256-tHCbZF+7i/gYs8WqM5jDBhhKmM4ZeUbG3DYBdQAiUT8="; + }; + + patches = [ + # Remove when version > 3.2.2 + (fetchpatch { + name = "0001-lomiri-music-app-Fix-GNUInstallDirs-usage.patch"; + url = "https://gitlab.com/ubports/development/apps/lomiri-music-app/-/commit/32591f2332aa204b9ee2857992e50594db0e6ff2.patch"; + hash = "sha256-SXn+7jItzi1Q0xK0iK57+W3SpEdSCx1dKSfeghOCePg="; + }) + + # Remove when version > 3.2.2 + (fetchpatch { + name = "0002-lomiri-music-app-bindtextdomain.patch"; + url = "https://gitlab.com/ubports/development/apps/lomiri-music-app/-/commit/4e950521a67e201f3d02b3b71c6bb1ddce8ef2b2.patch"; + hash = "sha256-HgGKk44FU+IXRVx2NK3iGSo/wPJce1T2k/vP8nZtewQ="; + }) + ]; + + postPatch = '' + # We don't want absolute paths in desktop files + substituteInPlace CMakeLists.txt \ + --replace-fail 'ICON ''${DATA_DIR}/''${ICON_FILE}' 'ICON lomiri-music-app' \ + --replace-fail 'SPLASH ''${DATA_DIR}/''${SPLASH_FILE}' 'SPLASH lomiri-app-launch/splash/lomiri-music-app.svg' + ''; + + strictDeps = true; + + nativeBuildInputs = [ + cmake + gettext + wrapGAppsHook3 + wrapQtAppsHook + ]; + + buildInputs = + [ + qtbase + qtdeclarative + + # QML + libusermetrics + lomiri-content-hub + lomiri-thumbnailer + lomiri-ui-toolkit + qtmultimedia + qtsystems + ] + # QtMultimedia playback support + ++ (with gst_all_1; [ + gstreamer + gst-plugins-base + gst-plugins-good + gst-plugins-bad + ]); + + dontWrapGApps = true; + + cmakeFlags = [ + (lib.cmakeBool "CLICK_MODE" false) + (lib.cmakeBool "INSTALL_TESTS" false) + ]; + + # Only autopilot tests + doCheck = false; + + postInstall = '' + mkdir -p $out/share/{icons/hicolor/scalable/apps,lomiri-app-launch/splash} + + ln -s $out/share/{lomiri-music-app/app/graphics/music-app.svg,icons/hicolor/scalable/apps/lomiri-music-app.svg} + ln -s $out/share/{lomiri-music-app/app/graphics/music-app-splash.svg,lomiri-app-launch/splash/lomiri-music-app.svg} + ''; + + preFixup = '' + qtWrapperArgs+=("''${gappsWrapperArgs[@]}") + ''; + + passthru = { + tests.vm = nixosTests.lomiri-music-app; + updateScript = gitUpdater { }; + }; + + meta = { + description = "Default Music application for Ubuntu devices"; + homepage = "https://gitlab.com/ubports/development/apps/lomiri-music-app"; + changelog = "https://gitlab.com/ubports/development/apps/lomiri-music-app/-/blob/v${finalAttrs.version}/ChangeLog"; + license = with lib.licenses; [ + gpl3Only + ]; + mainProgram = "lomiri-music-app"; + maintainers = lib.teams.lomiri.members; + platforms = lib.platforms.linux; + }; +}) diff --git a/pkgs/desktops/lomiri/data/lomiri-session/default.nix b/pkgs/desktops/lomiri/data/lomiri-session/default.nix index 960fb2794c84..fdc7b90ad875 100644 --- a/pkgs/desktops/lomiri/data/lomiri-session/default.nix +++ b/pkgs/desktops/lomiri/data/lomiri-session/default.nix @@ -13,6 +13,7 @@ makeWrapper, pkg-config, systemd, + xdg-user-dirs, }: stdenvNoCC.mkDerivation (finalAttrs: { @@ -34,6 +35,10 @@ stdenvNoCC.mkDerivation (finalAttrs: { substituteInPlace systemd/CMakeLists.txt \ --replace-fail 'pkg_get_variable(SYSTEMD_USER_DIR systemd systemduserunitdir)' 'pkg_get_variable(SYSTEMD_USER_DIR systemd systemduserunitdir DEFINE_VARIABLES prefix=''${CMAKE_INSTALL_PREFIX})' + + # Inject a call to xdg-user-dirs-update, so when mediascanner2 launches, it can actually scan for files + substituteInPlace desktop/dm-lomiri-session.in \ + --replace-fail '@CMAKE_INSTALL_FULL_LIBEXECDIR@/lomiri-session/run-systemd-session' '${lib.getExe' xdg-user-dirs "xdg-user-dirs-update"} && @CMAKE_INSTALL_FULL_LIBEXECDIR@/lomiri-session/run-systemd-session' ''; nativeBuildInputs = [ diff --git a/pkgs/desktops/lomiri/default.nix b/pkgs/desktops/lomiri/default.nix index bd26af310969..483eacaeb58a 100644 --- a/pkgs/desktops/lomiri/default.nix +++ b/pkgs/desktops/lomiri/default.nix @@ -21,6 +21,7 @@ let lomiri-filemanager-app = callPackage ./applications/lomiri-filemanager-app { }; lomiri-gallery-app = callPackage ./applications/lomiri-gallery-app { }; lomiri-mediaplayer-app = callPackage ./applications/lomiri-mediaplayer-app { }; + lomiri-music-app = callPackage ./applications/lomiri-music-app { }; lomiri-system-settings-unwrapped = callPackage ./applications/lomiri-system-settings { }; lomiri-system-settings = callPackage ./applications/lomiri-system-settings/wrapper.nix { }; lomiri-terminal-app = callPackage ./applications/lomiri-terminal-app { }; diff --git a/pkgs/desktops/lomiri/services/lomiri-thumbnailer/default.nix b/pkgs/desktops/lomiri/services/lomiri-thumbnailer/default.nix index 925f5592c34d..a9cb0b7da1d9 100644 --- a/pkgs/desktops/lomiri/services/lomiri-thumbnailer/default.nix +++ b/pkgs/desktops/lomiri/services/lomiri-thumbnailer/default.nix @@ -176,7 +176,11 @@ stdenv.mkDerivation (finalAttrs: { passthru = { tests = { # gallery app delegates to thumbnailer, tests various formats - vm = nixosTests.lomiri-gallery-app; + gallery-app = nixosTests.lomiri-gallery-app; + + # music app relies on thumbnailer to extract embedded cover art + music-app = nixosTests.lomiri-music-app; + pkg-config = testers.testMetaPkgConfig finalAttrs.finalPackage; }; updateScript = gitUpdater { }; diff --git a/pkgs/desktops/lomiri/services/mediascanner2/default.nix b/pkgs/desktops/lomiri/services/mediascanner2/default.nix index 0401e42ca78a..86172e2774c6 100644 --- a/pkgs/desktops/lomiri/services/mediascanner2/default.nix +++ b/pkgs/desktops/lomiri/services/mediascanner2/default.nix @@ -2,7 +2,9 @@ stdenv, lib, fetchFromGitLab, + fetchpatch, gitUpdater, + nixosTests, testers, # dbus-cpp not compatible with Boost 1.87 # https://gitlab.com/ubports/development/core/lib-cpp/dbus-cpp/-/issues/8 @@ -44,6 +46,14 @@ stdenv.mkDerivation (finalAttrs: { "dev" ]; + patches = [ + (fetchpatch { + name = "0001-mediascanner2-scannerdaemon-Drop-desktop-and-MEDIASCANNER_RUN-check.patch"; + url = "https://gitlab.com/ubports/development/core/mediascanner2/-/commit/1e65b32e32a0536b9e2f283ba563fa78b6ef6d61.patch"; + hash = "sha256-Xhm5+/E/pP+mn+4enqdsor1oRqfYTzabg1ODVfIhra4="; + }) + ]; + postPatch = '' substituteInPlace src/qml/MediaScanner.*/CMakeLists.txt \ --replace-fail "\''${CMAKE_INSTALL_LIBDIR}/qt5/qml" "\''${CMAKE_INSTALL_PREFIX}/${qtbase.qtQmlPrefix}" @@ -101,7 +111,12 @@ stdenv.mkDerivation (finalAttrs: { ''; passthru = { - tests.pkg-config = testers.testMetaPkgConfig finalAttrs.finalPackage; + tests = { + # music app needs mediascanner to work properly, so it can find files + music-app = nixosTests.lomiri-music-app; + + pkg-config = testers.testMetaPkgConfig finalAttrs.finalPackage; + }; updateScript = gitUpdater { }; };