Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fd29c6dcc | ||
|
|
4429e9973f | ||
|
|
1572d241d5 | ||
|
|
eae7e2a9e6 | ||
|
|
251e5b379b | ||
|
|
0db082fca0 | ||
|
|
2799e55076 | ||
|
|
440ee43a91 | ||
|
|
98c72ec1f8 | ||
|
|
759488ae1a | ||
|
|
055cb413c9 | ||
|
|
f760b87b58 | ||
|
|
39f228f862 | ||
|
|
a683a279f5 | ||
|
|
e800926f57 | ||
|
|
dd9f80d539 | ||
|
|
8484cac4ed | ||
|
|
e24097582f | ||
|
|
58fc8c82bb | ||
|
|
02bb875bb3 | ||
|
|
5db01482eb | ||
|
|
719fa6ffb3 | ||
|
|
159be5d79e | ||
|
|
911237e281 | ||
|
|
ae89ca8123 | ||
|
|
b832675893 | ||
|
|
b4cfe636c9 | ||
|
|
e6a0945dfa | ||
|
|
726c105ed6 | ||
|
|
d73cbc3a1d | ||
|
|
121f45d3b6 | ||
|
|
3a9ea81929 | ||
|
|
b919472241 | ||
|
|
e8c8b39410 | ||
|
|
6904efef47 | ||
|
|
fafa89baff | ||
|
|
d9062446f5 | ||
|
|
9256b92d8f | ||
|
|
f66459f3cb | ||
|
|
f8ea9631ca | ||
|
|
ab558f87b5 | ||
|
|
ab73eda2be | ||
|
|
92f34ff36e | ||
|
|
d0bf2d7a9c | ||
|
|
b2cd3afe55 | ||
|
|
decd0a1dc6 | ||
|
|
3e0a9fa388 | ||
|
|
e5b6c5959f | ||
|
|
8a9db5440d | ||
|
|
28a25c5763 | ||
|
|
ea49fbcbee | ||
|
|
d9807b358e | ||
|
|
3e53d2b237 | ||
|
|
9122881f74 | ||
|
|
9c36d7fd43 | ||
|
|
a4a365cbee | ||
|
|
00f06b22b8 | ||
|
|
e2d8838fca | ||
|
|
9427691f39 | ||
|
|
bebdcc4e7f | ||
|
|
041f761921 | ||
|
|
1435ae6dc0 | ||
|
|
33ae53a90f | ||
|
|
01c28867b7 | ||
|
|
08fb2ae331 | ||
|
|
99970f9e52 | ||
|
|
bc206f43b4 | ||
|
|
018448159c | ||
|
|
81fe90bdef | ||
|
|
319558c535 | ||
|
|
9095b0d6b2 | ||
|
|
558eae1ca1 | ||
|
|
2c64f05cea | ||
|
|
c80e7071a1 | ||
|
|
038e679000 | ||
|
|
72447fecfb | ||
|
|
c7830f6f05 | ||
|
|
af525e42b6 | ||
|
|
eb83f23125 | ||
|
|
7527d2ea9a | ||
|
|
69d38879d2 | ||
|
|
cbce9f7191 | ||
|
|
f938129d81 | ||
|
|
415a40ea04 | ||
|
|
6e7aaed4ee | ||
|
|
7afae70bb0 | ||
|
|
be8097919b | ||
|
|
1990a42e1d | ||
|
|
8fcee4511d | ||
|
|
24af1be666 | ||
|
|
8302a95bc1 | ||
|
|
36a8ab49a0 | ||
|
|
5c64dc9c4d | ||
|
|
c271743208 | ||
|
|
ee49b1ddc8 | ||
|
|
bf98633f16 | ||
|
|
e2a928f2dc | ||
|
|
47d3312a6b | ||
|
|
4f97325953 | ||
|
|
91e8fe0943 | ||
|
|
dc5894b38a | ||
|
|
52ee50a2a4 | ||
|
|
f545b028ee | ||
|
|
a96627c5a9 | ||
|
|
a13fc31f83 | ||
|
|
9ee5c8dc17 | ||
|
|
82cd425ece | ||
|
|
3b02d364ba | ||
|
|
73e7947487 | ||
|
|
cfc9a43b88 | ||
|
|
969500023a | ||
|
|
53c72d4f8e | ||
|
|
f971c92f32 | ||
|
|
82156e8a13 | ||
|
|
6a6285861e | ||
|
|
dae8c8730b | ||
|
|
20b47eae8f | ||
|
|
2ce8220d88 | ||
|
|
35b0b5df57 | ||
|
|
63631d6b0c | ||
|
|
21ab2ef1a7 | ||
|
|
a3f96d2b85 | ||
|
|
e2c1cb0116 | ||
|
|
07e295776b | ||
|
|
8604a39d94 | ||
|
|
315240fec7 | ||
|
|
fbc6d326f8 | ||
|
|
c082377e7a | ||
|
|
448445a38a | ||
|
|
18f835d7e5 | ||
|
|
fd427dac29 | ||
|
|
e1afe03d51 | ||
|
|
1b49653974 | ||
|
|
d66126f998 | ||
|
|
0fff5f672a | ||
|
|
2726f01fb3 | ||
|
|
9a74fce53d | ||
|
|
b3be8387f1 | ||
|
|
d396cb515d | ||
|
|
2548b4648e | ||
|
|
df0ec6b709 | ||
|
|
376af26f0e | ||
|
|
9bff55e1ee | ||
|
|
8f7e29f503 | ||
|
|
c3aa885a0f | ||
|
|
77ea5729c3 |
156
.github/workflows/build.yml
vendored
156
.github/workflows/build.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
|
||||
build-opensuse:
|
||||
name: Build openSUSE
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -21,18 +21,18 @@ jobs:
|
||||
steps:
|
||||
- name: Refresh repositories
|
||||
run: zypper -n --gpg-auto-import-keys ref
|
||||
- name: Upgrade packages
|
||||
- name: Upgrade packages (Tumbleweed)
|
||||
if: matrix.opensuse_version == 'tumbleweed'
|
||||
run: zypper -n --gpg-auto-import-keys dup
|
||||
- name: Upgrade packages
|
||||
- name: Upgrade packages (Leap)
|
||||
if: matrix.opensuse_version != 'tumbleweed'
|
||||
run: zypper -n --gpg-auto-import-keys up
|
||||
- name: Install gcc
|
||||
- name: Install gcc (Tumbleweed)
|
||||
if: matrix.opensuse_version == 'tumbleweed'
|
||||
run: zypper -n --gpg-auto-import-keys in gcc gcc-c++
|
||||
- name: Install gcc 13
|
||||
- name: Install gcc (Leap)
|
||||
if: matrix.opensuse_version != 'tumbleweed'
|
||||
run: zypper -n --gpg-auto-import-keys in gcc13 gcc13-c++
|
||||
run: zypper -n --gpg-auto-import-keys in gcc14 gcc14-c++
|
||||
- name: Install packages
|
||||
run: >
|
||||
zypper -n --gpg-auto-import-keys in
|
||||
@@ -68,6 +68,7 @@ jobs:
|
||||
hicolor-icon-theme
|
||||
qt6-core-devel
|
||||
qt6-gui-devel
|
||||
qt6-gui-private-devel
|
||||
qt6-widgets-devel
|
||||
qt6-concurrent-devel
|
||||
qt6-network-devel
|
||||
@@ -77,6 +78,8 @@ jobs:
|
||||
qt6-base-common-devel
|
||||
qt6-sql-sqlite
|
||||
qt6-linguist-devel
|
||||
gtest
|
||||
gmock
|
||||
- name: Install kdsingleapplication-qt6-devel
|
||||
if: matrix.opensuse_version == 'tumbleweed'
|
||||
run: zypper -n --gpg-auto-import-keys in kdsingleapplication-qt6-devel
|
||||
@@ -101,15 +104,18 @@ jobs:
|
||||
run: cp strawberry-*.tar.xz /usr/src/packages/SOURCES/
|
||||
- name: Build RPM (Tumbleweed)
|
||||
if: matrix.opensuse_version == 'tumbleweed'
|
||||
env:
|
||||
RPM_BUILD_NCPUS: 4
|
||||
working-directory: build
|
||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
||||
run: rpmbuild -ba strawberry.spec
|
||||
- name: Build RPM (Leap)
|
||||
if: matrix.opensuse_version != 'tumbleweed'
|
||||
working-directory: build
|
||||
env:
|
||||
CC: gcc-13
|
||||
CXX: g++-13
|
||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
||||
RPM_BUILD_NCPUS: 4
|
||||
CC: gcc-14
|
||||
CXX: g++-14
|
||||
working-directory: build
|
||||
run: rpmbuild -ba strawberry.spec
|
||||
- name: Set subdir
|
||||
id: set-subdir
|
||||
run: echo "subdir=$(echo ${{matrix.opensuse_version}} | sed 's/leap:/lp/g' | sed 's/\.//g')" > $GITHUB_OUTPUT
|
||||
@@ -132,7 +138,7 @@ jobs:
|
||||
|
||||
build-fedora:
|
||||
name: Build Fedora
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -149,7 +155,7 @@ jobs:
|
||||
run: >
|
||||
dnf -y install
|
||||
@development-tools
|
||||
redhat-lsb-core
|
||||
lsb_release
|
||||
which
|
||||
git
|
||||
glibc
|
||||
@@ -185,6 +191,8 @@ jobs:
|
||||
libappstream-glib
|
||||
hicolor-icon-theme
|
||||
kdsingleapplication-qt6-devel
|
||||
gtest-devel
|
||||
gmock-devel
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -207,9 +215,9 @@ jobs:
|
||||
run: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
|
||||
- name: Build RPM
|
||||
env:
|
||||
RPM_BUILD_NCPUS: "2"
|
||||
RPM_BUILD_NCPUS: 4
|
||||
working-directory: build
|
||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
||||
run: rpmbuild -ba strawberry.spec
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -221,7 +229,7 @@ jobs:
|
||||
|
||||
build-openmandriva:
|
||||
name: Build OpenMandriva
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && false
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master' && false
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -300,9 +308,9 @@ jobs:
|
||||
run: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
|
||||
- name: Build RPM
|
||||
env:
|
||||
RPM_BUILD_NCPUS: "2"
|
||||
RPM_BUILD_NCPUS: 4
|
||||
working-directory: build
|
||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
||||
run: rpmbuild -ba strawberry.spec
|
||||
- name: Upload artifacts
|
||||
if: matrix.openmandriva_version != 'cooker'
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -315,7 +323,7 @@ jobs:
|
||||
|
||||
build-mageia:
|
||||
name: Build Mageia
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -374,6 +382,7 @@ jobs:
|
||||
desktop-file-utils
|
||||
appstream-util
|
||||
hicolor-icon-theme
|
||||
gtest
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -396,9 +405,9 @@ jobs:
|
||||
run: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
|
||||
- name: Build RPM
|
||||
env:
|
||||
RPM_BUILD_NCPUS: "2"
|
||||
RPM_BUILD_NCPUS: 4
|
||||
working-directory: build
|
||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
||||
run: rpmbuild -ba strawberry.spec
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -410,7 +419,7 @@ jobs:
|
||||
|
||||
build-debian:
|
||||
name: Build Debian
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -456,7 +465,9 @@ jobs:
|
||||
libcdio-dev
|
||||
libmtp-dev
|
||||
libgpod-dev
|
||||
libxkbcommon-dev
|
||||
qt6-base-dev
|
||||
qt6-base-private-dev
|
||||
qt6-base-dev-tools
|
||||
qt6-tools-dev
|
||||
qt6-tools-dev-tools
|
||||
@@ -471,21 +482,24 @@ jobs:
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory build
|
||||
- name: Configure CMake
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_WERROR=ON
|
||||
- name: Delete build directory
|
||||
run: rm -rf build
|
||||
- name: make deb
|
||||
run: dpkg-buildpackage -b -d -uc -us -nc -j2
|
||||
run: dpkg-buildpackage -b -d -uc -us -nc -j4
|
||||
- name: Copy deb
|
||||
run: cp ../*.deb .
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: debian-${{matrix.debian_version}}
|
||||
path: "*.deb"
|
||||
path: |
|
||||
*.deb
|
||||
|
||||
|
||||
build-ubuntu:
|
||||
name: Build Ubuntu
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -534,7 +548,9 @@ jobs:
|
||||
libcdio-dev
|
||||
libmtp-dev
|
||||
libgpod-dev
|
||||
libxkbcommon-dev
|
||||
qt6-base-dev
|
||||
qt6-base-private-dev
|
||||
qt6-base-dev-tools
|
||||
qt6-tools-dev
|
||||
qt6-tools-dev-tools
|
||||
@@ -549,9 +565,11 @@ jobs:
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory build
|
||||
- name: Configure CMake
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_WERROR=ON
|
||||
- name: Delete build directory
|
||||
run: rm -rf build
|
||||
- name: make deb
|
||||
run: dpkg-buildpackage -b -d -uc -us -nc -j2
|
||||
run: dpkg-buildpackage -b -d -uc -us -nc -j4
|
||||
- name: Copy deb
|
||||
run: cp ../*.deb ../*.ddeb .
|
||||
- name: Upload artifacts
|
||||
@@ -565,7 +583,7 @@ jobs:
|
||||
|
||||
upload-ubuntu-ppa:
|
||||
name: Upload Ubuntu PPA
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.event_name == 'release' || (github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/1.1'))) && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.event_name == 'release' || (github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/1.1')))
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -612,7 +630,9 @@ jobs:
|
||||
libcdio-dev
|
||||
libmtp-dev
|
||||
libgpod-dev
|
||||
libxkbcommon-dev
|
||||
qt6-base-dev
|
||||
qt6-base-private-dev
|
||||
qt6-base-dev-tools
|
||||
qt6-tools-dev
|
||||
qt6-tools-dev-tools
|
||||
@@ -634,7 +654,7 @@ jobs:
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory build
|
||||
- name: Configure CMake
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_WERROR=ON
|
||||
- name: Delete build directory
|
||||
run: rm -rf build
|
||||
- name: Import Ubuntu PPA GPG private key
|
||||
@@ -651,9 +671,60 @@ jobs:
|
||||
run: dput ppa:jonaski/strawberry ../*_source.changes
|
||||
|
||||
|
||||
build-freebsd:
|
||||
name: Build FreeBSD
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- name: Build FreeBSD
|
||||
id: build-freebsd
|
||||
uses: vmactions/freebsd-vm@v1.1.8
|
||||
with:
|
||||
usesh: true
|
||||
mem: 4096
|
||||
prepare: pkg install -y git cmake pkgconf boost-libs alsa-lib glib qt6-base qt6-tools sqlite gstreamer1 gstreamer1-plugins chromaprint libebur128 taglib libcdio libmtp gdk-pixbuf2 libgpod fftw3 icu kdsingleapplication googletest pulseaudio
|
||||
run: |
|
||||
set -e
|
||||
git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
cmake -E make_directory build
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE="Debug"
|
||||
cmake --build build --config Debug --parallel 4
|
||||
|
||||
|
||||
build-openbsd:
|
||||
name: Build OpenBSD
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- name: Build OpenBSD
|
||||
id: build-openbsd
|
||||
uses: vmactions/openbsd-vm@v1.1.6
|
||||
with:
|
||||
usesh: true
|
||||
mem: 4096
|
||||
prepare: pkg_add git cmake pkgconf boost glib2 qt6-qtbase qt6-qttools sqlite gstreamer1 gstreamer1-plugins-base chromaprint libebur128 taglib libcdio libmtp gdk-pixbuf libgpod fftw3 icu4c kdsingleapplication pulseaudio
|
||||
run: |
|
||||
set -e
|
||||
export LDFLAGS="-L/usr/local/lib"
|
||||
git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
cmake -E make_directory build
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE="Debug" -DENABLE_ALSA=OFF
|
||||
cmake --build build --config Debug --parallel 4
|
||||
|
||||
|
||||
build-macos-public:
|
||||
name: Build macOS Public
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -737,10 +808,12 @@ jobs:
|
||||
-DCMAKE_PREFIX_PATH="${{env.prefix_path}}/lib/cmake"
|
||||
-DBUILD_WERROR=ON
|
||||
-DUSE_BUNDLE=ON
|
||||
-DENABLE_DBUS=OFF
|
||||
-DICU_ROOT="${{env.prefix_path}}"
|
||||
-DAPPLE_DEVELOPER_ID=$(test '${{github.repository}}' = 'strawberrymusicplayer/strawberry' && test '${{github.event.pull_request.base.repo.full_name}}' = '${{github.event.pull_request.head.repo.full_name}}' && echo "383J84DVB6" || echo "")
|
||||
-DARCH="${{env.arch}}"
|
||||
-DENABLE_SPOTIFY=$(test -f "${{env.prefix_path}}/lib/gstreamer-1.0/libgstspotify.dylib" && echo "ON" || echo "OFF")
|
||||
-DENABLE_SPARKLE=ON
|
||||
-DENABLE_QTSPARKLE=OFF
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --config Release --parallel 4
|
||||
@@ -876,10 +949,12 @@ jobs:
|
||||
-DCMAKE_PREFIX_PATH="${{env.prefix_path}}/lib/cmake"
|
||||
-DBUILD_WERROR=ON
|
||||
-DUSE_BUNDLE=ON
|
||||
-DENABLE_DBUS=OFF
|
||||
-DICU_ROOT="${{env.prefix_path}}"
|
||||
-DAPPLE_DEVELOPER_ID="383J84DVB6"
|
||||
-DARCH="${{env.arch}}"
|
||||
-DENABLE_SPOTIFY=$(test -f "${{env.prefix_path}}/lib/gstreamer-1.0/libgstspotify.dylib" && echo "ON" || echo "OFF")
|
||||
-DENABLE_SPARKLE=ON
|
||||
-DENABLE_QTSPARKLE=OFF
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --config Release --parallel 4
|
||||
@@ -931,7 +1006,7 @@ jobs:
|
||||
|
||||
build-windows-mingw:
|
||||
name: Build Windows MinGW
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -974,7 +1049,7 @@ jobs:
|
||||
-DBUILD_WERROR=ON
|
||||
-DARCH="${{matrix.arch}}"
|
||||
-DENABLE_WIN32_CONSOLE=$(test "${{matrix.buildtype}}" = "debug" && echo "ON" || echo "OFF")
|
||||
-DENABLE_DBUS=OFF
|
||||
-DENABLE_GIO=OFF
|
||||
-DENABLE_AUDIOCD=OFF
|
||||
-DENABLE_MTP=OFF
|
||||
-DENABLE_GPOD=OFF
|
||||
@@ -1120,7 +1195,7 @@ jobs:
|
||||
|
||||
build-windows-msvc:
|
||||
name: Build Windows MSVC
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && (!(github.event.pusher.name == 'strawbsbot' && contains(github.event.head_commit.message, 'New translations')))
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -1262,6 +1337,10 @@ jobs:
|
||||
-DENABLE_WIN32_CONSOLE=${{env.win32_console}}
|
||||
-DPKG_CONFIG_EXECUTABLE="${{env.prefix_path_forwardslash}}/bin/pkg-config.exe"
|
||||
-DICU_ROOT="${{env.prefix_path_forwardslash}}"
|
||||
-DENABLE_GIO=OFF
|
||||
-DENABLE_AUDIOCD=OFF
|
||||
-DENABLE_MTP=OFF
|
||||
-DENABLE_GPOD=OFF
|
||||
-DENABLE_SPOTIFY=ON
|
||||
|
||||
- name: Run Make
|
||||
@@ -1396,6 +1475,11 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download MSVC runtime
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: curl -f -O -L https://aka.ms/vs/17/release/vc_redist.$(test "${{matrix.arch}}" = "x86_64" && echo "x64" || echo "${{matrix.arch}}").exe
|
||||
|
||||
- name: Create nsis installer
|
||||
shell: cmd
|
||||
working-directory: build
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,6 +11,4 @@
|
||||
/out
|
||||
/CMakeSettings.json
|
||||
/dist/scripts/maketarball.sh
|
||||
/dist/unix/strawberry.spec
|
||||
/debian/changelog
|
||||
/dist/macos/Info.plist
|
||||
|
||||
178
CMakeLists.txt
178
CMakeLists.txt
@@ -18,24 +18,19 @@ include(CheckCXXSourceRuns)
|
||||
include(CheckIncludeFiles)
|
||||
include(FindPkgConfig)
|
||||
include(cmake/Version.cmake)
|
||||
include(cmake/Summary.cmake)
|
||||
include(cmake/OptionalComponent.cmake)
|
||||
include(cmake/OptionalSource.cmake)
|
||||
include(cmake/ParseArguments.cmake)
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(LINUX ON)
|
||||
endif()
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||
set(FREEBSD ON)
|
||||
endif()
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
||||
set(OPENBSD ON)
|
||||
endif()
|
||||
|
||||
if(LINUX)
|
||||
include(cmake/Rpm.cmake)
|
||||
include(cmake/Deb.cmake)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
include(cmake/Dmg.cmake)
|
||||
endif()
|
||||
@@ -72,7 +67,6 @@ option(USE_BUNDLE "Bundle dependencies" ${USE_BUNDLE_DEFAULT})
|
||||
|
||||
if(WIN32)
|
||||
option(ENABLE_WIN32_CONSOLE "Show the windows console even outside Debug mode" ${ENABLE_WIN32_CONSOLE_DEFAULT})
|
||||
option(USE_QTSPARKLE "Use Qt Sparkle updater" ON)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
@@ -134,7 +128,10 @@ add_definitions(
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-DUNICODE)
|
||||
add_definitions(
|
||||
-DUNICODE
|
||||
-DNOMINMAX
|
||||
)
|
||||
endif()
|
||||
|
||||
if(BUILD_WERROR)
|
||||
@@ -167,17 +164,17 @@ if(NOT Boost_FOUND)
|
||||
find_package(Boost REQUIRED)
|
||||
endif()
|
||||
find_package(ICU COMPONENTS uc i18n REQUIRED)
|
||||
if(LINUX)
|
||||
find_package(ALSA REQUIRED)
|
||||
else()
|
||||
find_package(ALSA)
|
||||
endif()
|
||||
if(UNIX AND NOT APPLE)
|
||||
if(LINUX)
|
||||
find_package(ALSA REQUIRED)
|
||||
else()
|
||||
find_package(ALSA)
|
||||
endif()
|
||||
find_package(X11 COMPONENTS X11_xcb)
|
||||
endif()
|
||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||
pkg_check_modules(GOBJECT REQUIRED IMPORTED_TARGET gobject-2.0)
|
||||
if(UNIX AND NOT APPLE)
|
||||
if(NOT APPLE)
|
||||
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
|
||||
if(GIO_FOUND AND UNIX)
|
||||
pkg_check_modules(GIO_UNIX IMPORTED_TARGET gio-unix-2.0)
|
||||
@@ -191,7 +188,9 @@ pkg_check_modules(GSTREAMER_APP REQUIRED IMPORTED_TARGET gstreamer-app-1.0)
|
||||
pkg_check_modules(GSTREAMER_TAG REQUIRED IMPORTED_TARGET gstreamer-tag-1.0)
|
||||
pkg_check_modules(GSTREAMER_PBUTILS REQUIRED IMPORTED_TARGET gstreamer-pbutils-1.0)
|
||||
pkg_check_modules(SQLITE REQUIRED IMPORTED_TARGET sqlite3>=3.9)
|
||||
pkg_check_modules(LIBPULSE IMPORTED_TARGET libpulse)
|
||||
if(UNIX AND NOT APPLE)
|
||||
pkg_check_modules(LIBPULSE IMPORTED_TARGET libpulse)
|
||||
endif()
|
||||
pkg_check_modules(CHROMAPRINT IMPORTED_TARGET libchromaprint>=1.4)
|
||||
pkg_check_modules(FFTW3 IMPORTED_TARGET fftw3)
|
||||
pkg_check_modules(LIBEBUR128 IMPORTED_TARGET libebur128)
|
||||
@@ -215,30 +214,23 @@ set(QT_VERSION_MAJOR 6)
|
||||
set(QT_MIN_VERSION 6.4.0)
|
||||
set(QT_DEFAULT_MAJOR_VERSION ${QT_VERSION_MAJOR})
|
||||
set(QT_COMPONENTS Core Concurrent Gui Widgets Network Sql)
|
||||
set(QT_OPTIONAL_COMPONENTS LinguistTools Test)
|
||||
set(QT_OPTIONAL_COMPONENTS GuiPrivate LinguistTools Test)
|
||||
if(UNIX AND NOT APPLE)
|
||||
list(APPEND QT_OPTIONAL_COMPONENTS DBus)
|
||||
endif()
|
||||
set(QT_NO_PRIVATE_MODULE_WARNING ON)
|
||||
|
||||
find_package(Qt${QT_VERSION_MAJOR} ${QT_MIN_VERSION} COMPONENTS ${QT_COMPONENTS} REQUIRED OPTIONAL_COMPONENTS ${QT_OPTIONAL_COMPONENTS})
|
||||
|
||||
if(TARGET "Qt${QT_VERSION_MAJOR}::GuiPrivate")
|
||||
set(QT_GUI_PRIVATE_FOUND ON)
|
||||
endif()
|
||||
|
||||
if(Qt${QT_VERSION_MAJOR}DBus_FOUND)
|
||||
set(DBUS_FOUND ON)
|
||||
endif()
|
||||
|
||||
if(X11_FOUND)
|
||||
|
||||
find_path(QPA_QPLATFORMNATIVEINTERFACE_H qpa/qplatformnativeinterface.h PATHS ${Qt${QT_VERSION_MAJOR}Gui_INCLUDE_DIRS} ${Qt${QT_VERSION_MAJOR}Gui_PRIVATE_INCLUDE_DIRS})
|
||||
if(NOT QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||
find_path(QPA_QPLATFORMNATIVEINTERFACE_H ${Qt${QT_VERSION_MAJOR}Gui_VERSION}/QtGui/qpa/qplatformnativeinterface.h PATHS ${Qt${QT_VERSION_MAJOR}Gui_INCLUDE_DIRS} ${Qt${QT_VERSION_MAJOR}Gui_PRIVATE_INCLUDE_DIRS})
|
||||
endif()
|
||||
if(QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||
set(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H ON)
|
||||
message(STATUS "Have qpa/qplatformnativeinterface.h header.")
|
||||
else()
|
||||
message(STATUS "Missing qpa/qplatformnativeinterface.h header.")
|
||||
endif()
|
||||
|
||||
# Check for QX11Application (Qt 6 compiled with XCB).
|
||||
set(CMAKE_REQUIRED_FLAGS "-std=c++17")
|
||||
set(CMAKE_REQUIRED_LIBRARIES Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui)
|
||||
@@ -253,7 +245,6 @@ if(X11_FOUND)
|
||||
)
|
||||
unset(CMAKE_REQUIRED_FLAGS)
|
||||
unset(CMAKE_REQUIRED_LIBRARIES)
|
||||
|
||||
endif()
|
||||
|
||||
# SingleApplication
|
||||
@@ -271,33 +262,36 @@ else()
|
||||
add_definitions(-DKDSINGLEAPPLICATION_STATIC_BUILD)
|
||||
endif()
|
||||
|
||||
# if(APPLE)
|
||||
# find_package(SPMediaKeyTap REQUIRED)
|
||||
# endif()
|
||||
if(APPLE)
|
||||
find_library(SPARKLE Sparkle)
|
||||
#find_package(SPMediaKeyTap REQUIRED)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
find_package(getopt-win REQUIRED)
|
||||
if(USE_QTSPARKLE)
|
||||
pkg_check_modules(QTSPARKLE REQUIRED IMPORTED_TARGET qtsparkle-qt${QT_VERSION_MAJOR})
|
||||
set(HAVE_QTSPARKLE ON)
|
||||
endif()
|
||||
|
||||
if(APPLE OR WIN32)
|
||||
find_package(qtsparkle-qt${QT_VERSION_MAJOR})
|
||||
if(TARGET "qtsparkle-qt${QT_VERSION_MAJOR}::qtsparkle")
|
||||
set(QTSPARKLE_FOUND ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
optional_component(ALSA ON "ALSA integration"
|
||||
DEPENDS "alsa" ALSA_FOUND
|
||||
)
|
||||
|
||||
optional_component(PULSE ON "PulseAudio integration"
|
||||
DEPENDS "libpulse" LIBPULSE_FOUND
|
||||
)
|
||||
|
||||
optional_component(DBUS ON "D-Bus support"
|
||||
DEPENDS "Qt D-Bus" DBUS_FOUND
|
||||
)
|
||||
|
||||
optional_component(MPRIS2 ON "MPRIS2 D-Bus Interface"
|
||||
DEPENDS "D-Bus support" HAVE_DBUS
|
||||
)
|
||||
if(UNIX AND NOT APPLE)
|
||||
optional_component(ALSA ON "ALSA integration"
|
||||
DEPENDS "alsa" ALSA_FOUND
|
||||
)
|
||||
optional_component(PULSE ON "PulseAudio integration"
|
||||
DEPENDS "libpulse" LIBPULSE_FOUND
|
||||
)
|
||||
optional_component(DBUS ON "D-Bus support"
|
||||
DEPENDS "Qt D-Bus" DBUS_FOUND
|
||||
)
|
||||
optional_component(MPRIS2 ON "MPRIS2 D-Bus Interface"
|
||||
DEPENDS "D-Bus support" HAVE_DBUS
|
||||
)
|
||||
endif()
|
||||
|
||||
optional_component(SONGFINGERPRINTING ON "Song fingerprinting and tracking"
|
||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||
@@ -307,26 +301,29 @@ optional_component(MUSICBRAINZ ON "MusicBrainz integration"
|
||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||
)
|
||||
|
||||
optional_component(X11_GLOBALSHORTCUTS ON "X11 global shortcuts"
|
||||
DEPENDS "X11" X11_FOUND
|
||||
DEPENDS "QX11Application" HAVE_QX11APPLICATION
|
||||
)
|
||||
if(UNIX AND NOT APPLE)
|
||||
optional_component(X11_GLOBALSHORTCUTS ON "X11 global shortcuts"
|
||||
DEPENDS "X11" X11_FOUND
|
||||
DEPENDS "QX11Application" HAVE_QX11APPLICATION
|
||||
)
|
||||
optional_component(KGLOBALACCEL_GLOBALSHORTCUTS ON "KGlobalAccel global shortcuts"
|
||||
DEPENDS "D-Bus support" HAVE_DBUS
|
||||
)
|
||||
optional_component(UDISKS2 ON "Devices: UDisks2 backend"
|
||||
DEPENDS "D-Bus support" HAVE_DBUS
|
||||
)
|
||||
endif()
|
||||
|
||||
optional_component(KGLOBALACCEL_GLOBALSHORTCUTS ON "KGlobalAccel global shortcuts"
|
||||
DEPENDS "D-Bus support" HAVE_DBUS
|
||||
)
|
||||
|
||||
optional_component(UDISKS2 ON "Devices: UDisks2 backend"
|
||||
DEPENDS "D-Bus support" HAVE_DBUS
|
||||
)
|
||||
|
||||
optional_component(GIO ON "Devices: GIO device backend"
|
||||
DEPENDS "libgio" GIO_FOUND
|
||||
)
|
||||
|
||||
optional_component(GIO_UNIX ON "Devices: GIO device backend (Unix support)"
|
||||
DEPENDS "libgio-unix" GIO_UNIX_FOUND
|
||||
)
|
||||
if(NOT APPLE)
|
||||
optional_component(GIO ON "Devices: GIO device backend"
|
||||
DEPENDS "libgio" GIO_FOUND
|
||||
)
|
||||
if(UNIX)
|
||||
optional_component(GIO_UNIX ON "Devices: GIO device backend (Unix support)"
|
||||
DEPENDS "libgio-unix" GIO_UNIX_FOUND
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
optional_component(AUDIOCD ON "Devices: Audio CD support"
|
||||
DEPENDS "libcdio" LIBCDIO_FOUND
|
||||
@@ -358,6 +355,22 @@ optional_component(EBUR128 ON "EBU R 128 loudness normalization"
|
||||
DEPENDS "libebur128" LIBEBUR128_FOUND
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
optional_component(SPARKLE ON "Sparkle integration"
|
||||
DEPENDS "Sparkle" SPARKLE
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE OR WIN32)
|
||||
optional_component(QTSPARKLE ON "QtSparkle integration"
|
||||
DEPENDS "QtSparkle" QTSPARKLE_FOUND
|
||||
)
|
||||
endif()
|
||||
|
||||
optional_component(QPA_QPLATFORMNATIVEINTERFACE ON "QPA Platform Native Interface"
|
||||
DEPENDS "Qt Gui Private" QT_GUI_PRIVATE_FOUND
|
||||
)
|
||||
|
||||
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
|
||||
set(HAVE_CHROMAPRINT ON)
|
||||
endif()
|
||||
@@ -392,7 +405,7 @@ add_executable(strawberry)
|
||||
|
||||
if(APPLE)
|
||||
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/dist/macos/Info.plist")
|
||||
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/dist/macos/Info.plist")
|
||||
endif()
|
||||
|
||||
if(WIN32 AND NOT ENABLE_WIN32_CONSOLE)
|
||||
@@ -443,6 +456,7 @@ set(SOURCES
|
||||
src/core/enginemetadata.cpp
|
||||
src/core/songmimedata.cpp
|
||||
src/core/platforminterface.cpp
|
||||
src/core/standardpaths.cpp
|
||||
|
||||
src/utilities/strutils.cpp
|
||||
src/utilities/envutils.cpp
|
||||
@@ -1202,6 +1216,7 @@ if(APPLE)
|
||||
src/osd/osdmac.h
|
||||
src/device/macosdevicelister.h
|
||||
)
|
||||
optional_source(HAVE_SPARKLE SOURCES src/core/sparkleupdater.mm HEADERS src/core/sparkleupdater.h)
|
||||
else()
|
||||
list(APPEND SOURCES src/systemtrayicon/qtsystemtrayicon.cpp src/widgets/searchfield_qt.cpp src/widgets/searchfield_qt_private.cpp)
|
||||
list(APPEND HEADERS src/systemtrayicon/qtsystemtrayicon.h src/widgets/searchfield_qt_private.h)
|
||||
@@ -1306,9 +1321,7 @@ optional_source(HAVE_MOODBAR
|
||||
src/settings/moodbarsettingspage.ui
|
||||
)
|
||||
|
||||
if(UNIX)
|
||||
optional_source(HAVE_GIO SOURCES src/device/giolister.cpp HEADERS src/device/giolister.h)
|
||||
endif()
|
||||
optional_source(HAVE_GIO SOURCES src/device/giolister.cpp HEADERS src/device/giolister.h)
|
||||
|
||||
if(HAVE_UDISKS2)
|
||||
optional_source(HAVE_UDISKS2 SOURCES src/device/udisks2lister.cpp HEADERS src/device/udisks2lister.h)
|
||||
@@ -1456,7 +1469,7 @@ endif()
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(dist)
|
||||
|
||||
if(TARGET GTest::GTest AND Qt${QT_VERSION_MAJOR}Test_FOUND)
|
||||
if(TARGET GTest::gtest AND TARGET GTest::gmock AND Qt${QT_VERSION_MAJOR}Test_FOUND)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
@@ -1485,12 +1498,9 @@ if(SINGLEAPPLICATION_INCLUDE_DIRS)
|
||||
target_include_directories(strawberry_lib SYSTEM PUBLIC ${SINGLEAPPLICATION_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||
target_include_directories(strawberry_lib SYSTEM PUBLIC ${Qt${QT_VERSION_MAJOR}Gui_PRIVATE_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
target_link_libraries(strawberry_lib PUBLIC
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
$<$<BOOL:${HAVE_BACKTRACE}>:${Backtrace_LIBRARIES}>
|
||||
PkgConfig::GLIB
|
||||
PkgConfig::GOBJECT
|
||||
PkgConfig::SQLITE
|
||||
@@ -1508,6 +1518,7 @@ target_link_libraries(strawberry_lib PUBLIC
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
Qt${QT_VERSION_MAJOR}::Sql
|
||||
$<$<BOOL:${HAVE_DBUS}>:Qt${QT_VERSION_MAJOR}::DBus>
|
||||
$<$<BOOL:${HAVE_QPA_QPLATFORMNATIVEINTERFACE}>:Qt${QT_VERSION_MAJOR}::GuiPrivate>
|
||||
ICU::uc
|
||||
ICU::i18n
|
||||
$<$<BOOL:${HAVE_ALSA}>:ALSA::ALSA>
|
||||
@@ -1521,8 +1532,7 @@ target_link_libraries(strawberry_lib PUBLIC
|
||||
$<$<BOOL:${HAVE_AUDIOCD}>:PkgConfig::LIBCDIO>
|
||||
$<$<BOOL:${HAVE_MTP}>:PkgConfig::LIBMTP>
|
||||
$<$<BOOL:${HAVE_GPOD}>:PkgConfig::LIBGPOD PkgConfig::GDK_PIXBUF>
|
||||
$<$<BOOL:${HAVE_QTSPARKLE}>:PkgConfig::QTSPARKLE>
|
||||
$<$<BOOL:${FREEBSD}>:execinfo>
|
||||
$<$<BOOL:${HAVE_QTSPARKLE}>:qtsparkle-qt${QT_VERSION_MAJOR}::qtsparkle>
|
||||
$<$<BOOL:${WIN32}>:dsound dwmapi getopt-win::getopt>
|
||||
$<$<BOOL:${MSVC}>:WindowsApp>
|
||||
${SINGLEAPPLICATION_LIBRARIES}
|
||||
@@ -1538,6 +1548,10 @@ if(APPLE)
|
||||
"-framework IOKit"
|
||||
"-framework ScriptingBridge"
|
||||
)
|
||||
if(HAVE_SPARKLE)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${SPARKLE}/Headers)
|
||||
target_link_libraries(strawberry_lib PRIVATE ${SPARKLE})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_link_libraries(strawberry PUBLIC strawberry_lib)
|
||||
@@ -1553,7 +1567,7 @@ endif()
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY)
|
||||
add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
|
||||
|
||||
summary_show()
|
||||
optional_component_summary_show()
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING AND NOT QT_SQLITE_TEST)
|
||||
message(WARNING "The Qt sqlite driver test failed.")
|
||||
|
||||
59
Changelog
59
Changelog
@@ -2,6 +2,65 @@ Strawberry Music Player
|
||||
=======================
|
||||
ChangeLog
|
||||
|
||||
Version 1.2.7 (2025.01.31):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed strawberry exiting when clicking tray icon.
|
||||
* Fixed Clementine import script errors.
|
||||
* Disabled OSD Pretty on Wayland since it's not working properly.
|
||||
|
||||
Enhancements:
|
||||
* Only maximize error dialog if Strawberry is the active window (#1627).
|
||||
* Added QPA Platform Native Interface as optional component.
|
||||
|
||||
Version 1.2.6 (2025.01.17):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed dragging songs from playlist to queue.
|
||||
|
||||
Version 1.2.5 (2025.01.17):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed crash when saving playcount or rating to file (#1633).
|
||||
* Fixed QFile::open failing in unit tests.
|
||||
* Fixed playlist sequence settings saved to wrong configuration file (#1649).
|
||||
|
||||
Enhancements:
|
||||
* Fixed use of deprecated GIO functions with GLib 2.84 and newer.
|
||||
* (macOS) Added back Sparkle updater to check for new releases.
|
||||
|
||||
Version 1.2.4 (2025.01.10):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed Spotify songs not being available for scrobbling.
|
||||
* Fixed leading "A" and "The" articles being skipped for album sort text.
|
||||
* Fixed thread safety issue when validating playlist songs on startup.
|
||||
* Fixed filter search not ignoring space after colon when using column based search.
|
||||
* Fixed KGlobalAccel to use capitalized application name.
|
||||
* Fixed slash not properly handled when saving a playlist (#1624).
|
||||
* (Unix) Fixed collection scanner so it ignores special filesystem paths (/sys, /proc, /run, etc) (#1615).
|
||||
* (Windows) Fixed smart playlist wizard not respecting dark mode with Windows 11 style (#1639).
|
||||
|
||||
Enhancements:
|
||||
* Use XSPF "title" as playlist name when loading and saving playlists (#1624).
|
||||
* Added support for using album ID when receving album covers for Subsonic songs (#1636).
|
||||
* Added option for preserving directory structure when trascoding songs (#1637).
|
||||
* (Windows) Always run MSVC runtime installer to possible fix issues when there is an older runtime installed.
|
||||
|
||||
Version 1.2.3 (2024.12.08):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed libcdio NULL related compilation error on FreeBSD (#1610).
|
||||
* Fixed missing seek when starting playback of a CUE song (#1568).
|
||||
* Fixed "QDBusObjectPath: invalid path" error.
|
||||
|
||||
Version 1.2.2 (2024.11.23):
|
||||
|
||||
Bugfixes:
|
||||
|
||||
* Fixed crash when creating a new smart playlist (#1609).
|
||||
* Fixed last playlist column being added when dragging a song and switching playlists.
|
||||
|
||||
Version 1.2.1 (2024.11.21):
|
||||
|
||||
This release features major restructuring of the codebase, moving source files,
|
||||
|
||||
@@ -2,5 +2,5 @@ find_program(LSB_RELEASE_EXEC lsb_release)
|
||||
find_program(DPKG_BUILDPACKAGE dpkg-buildpackage)
|
||||
|
||||
if (LSB_RELEASE_EXEC AND DPKG_BUILDPACKAGE)
|
||||
add_custom_target(deb WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${DPKG_BUILDPACKAGE} -b -d -uc -us)
|
||||
add_custom_target(deb WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${DPKG_BUILDPACKAGE} -b -d -uc -us -nc -j4)
|
||||
endif()
|
||||
|
||||
@@ -31,7 +31,7 @@ if(MACDEPLOYQT_EXECUTABLE)
|
||||
|
||||
add_custom_target(deploy
|
||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/strawberry.app/Contents/{Frameworks,Resources}
|
||||
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/Info.plist ${CMAKE_BINARY_DIR}/strawberry.app/Contents/
|
||||
COMMAND cp -v ${CMAKE_BINARY_DIR}/dist/macos/Info.plist ${CMAKE_BINARY_DIR}/strawberry.app/Contents/
|
||||
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/strawberry.icns ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources/
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macgstcopy.sh ${CMAKE_BINARY_DIR}/strawberry.app
|
||||
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gst-plugin-scanner ${MACDEPLOYQT_CODESIGN}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
set(summary_willbuild "")
|
||||
set(summary_willnotbuild "")
|
||||
|
||||
macro(summary_add name test)
|
||||
macro(optional_component_summary_add name test)
|
||||
if (${test})
|
||||
list(APPEND summary_willbuild ${name})
|
||||
else (${test})
|
||||
list(APPEND summary_willnotbuild "${name}")
|
||||
endif (${test})
|
||||
endmacro(summary_add)
|
||||
endmacro(optional_component_summary_add)
|
||||
|
||||
macro(summary_show_part variable title)
|
||||
macro(optional_component_summary_show_part variable title)
|
||||
list(LENGTH ${variable} _len)
|
||||
if (_len)
|
||||
message("")
|
||||
@@ -18,19 +18,20 @@ macro(summary_show_part variable title)
|
||||
message(" ${_item}")
|
||||
endforeach (_item)
|
||||
endif (_len)
|
||||
endmacro(summary_show_part)
|
||||
endmacro(optional_component_summary_show_part)
|
||||
|
||||
macro(summary_show)
|
||||
macro(optional_component_summary_show)
|
||||
list(SORT summary_willbuild)
|
||||
list(SORT summary_willnotbuild)
|
||||
message("")
|
||||
message("Building strawberry version: ${STRAWBERRY_VERSION_DISPLAY}, Qt version ${Qt${QT_VERSION_MAJOR}Core_VERSION}")
|
||||
summary_show_part(summary_willbuild "The following components will be built:")
|
||||
summary_show_part(summary_willnotbuild "The following components WILL NOT be built:")
|
||||
optional_component_summary_show_part(summary_willbuild "The following components will be built:")
|
||||
optional_component_summary_show_part(summary_willnotbuild "The following components WILL NOT be built:")
|
||||
message("")
|
||||
endmacro(summary_show)
|
||||
endmacro(optional_component_summary_show)
|
||||
|
||||
function(optional_component name default description)
|
||||
|
||||
set(option_variable "ENABLE_${name}")
|
||||
set(have_variable "HAVE_${name}")
|
||||
set(${have_variable} OFF)
|
||||
@@ -79,6 +80,9 @@ function(optional_component name default description)
|
||||
set(text "${description} (missing ${deplist_text})")
|
||||
|
||||
set(summary_willnotbuild "${summary_willnotbuild};${text}" PARENT_SCOPE)
|
||||
|
||||
message(FATAL_ERROR "${text}, to disable this optional feature, pass -D${option_variable}=OFF to CMake")
|
||||
|
||||
else()
|
||||
set(${have_variable} ON PARENT_SCOPE)
|
||||
set(summary_willbuild "${summary_willbuild};${description}" PARENT_SCOPE)
|
||||
@@ -64,7 +64,7 @@ if (LSB_RELEASE_EXEC AND RPMBUILD_EXEC)
|
||||
add_custom_target(rpm
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/dist/scripts/maketarball.sh
|
||||
COMMAND ${CMAKE_COMMAND} -E copy strawberry-${STRAWBERRY_VERSION_PACKAGE}.tar.xz ${RPMBUILD_DIR}/SOURCES/
|
||||
COMMAND ${RPMBUILD_EXEC} -ba ${CMAKE_SOURCE_DIR}/dist/unix/strawberry.spec
|
||||
COMMAND ${RPMBUILD_EXEC} -ba ${CMAKE_BINARY_DIR}/strawberry.spec
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||
set(STRAWBERRY_VERSION_MINOR 2)
|
||||
set(STRAWBERRY_VERSION_PATCH 1)
|
||||
set(STRAWBERRY_VERSION_PATCH 7)
|
||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||
|
||||
set(INCLUDE_GIT_REVISION OFF)
|
||||
|
||||
3
debian/clean
vendored
Normal file
3
debian/clean
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
dist/scripts/maketarball.sh
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
1
debian/compat
vendored
1
debian/compat
vendored
@@ -1 +0,0 @@
|
||||
11
|
||||
6
debian/control
vendored
6
debian/control
vendored
@@ -2,7 +2,7 @@ Source: strawberry
|
||||
Section: sound
|
||||
Priority: optional
|
||||
Maintainer: Jonas Kvinge <jonas@jkvinge.net>
|
||||
Build-Depends: debhelper (>= 11),
|
||||
Build-Depends: debhelper-compat (= 12),
|
||||
git,
|
||||
make,
|
||||
cmake,
|
||||
@@ -15,7 +15,9 @@ Build-Depends: debhelper (>= 11),
|
||||
libpulse-dev,
|
||||
libtag1-dev,
|
||||
libicu-dev,
|
||||
libxkbcommon-dev,
|
||||
qt6-base-dev,
|
||||
qt6-base-private-dev,
|
||||
qt6-base-dev-tools,
|
||||
qt6-tools-dev,
|
||||
qt6-tools-dev-tools,
|
||||
@@ -28,7 +30,7 @@ Build-Depends: debhelper (>= 11),
|
||||
libchromaprint-dev,
|
||||
libfftw3-dev,
|
||||
libebur128-dev
|
||||
Standards-Version: 4.6.1
|
||||
Standards-Version: 4.7.0
|
||||
|
||||
Package: strawberry
|
||||
Architecture: any
|
||||
|
||||
15
debian/rules
vendored
15
debian/rules
vendored
@@ -1,17 +1,10 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
%:
|
||||
dh $@ --buildsystem=cmake -builddirectory=build
|
||||
|
||||
override_dh_auto_clean:
|
||||
rm -f dist/macos/Info.plist
|
||||
rm -f dist/unix/strawberry.spec
|
||||
rm -f dist/scripts/maketarball.sh
|
||||
rm -f dist/windows/strawberry.nsi
|
||||
rm -f src/translations/translations.pot
|
||||
dh_auto_clean
|
||||
export DH_VERBOSE=1
|
||||
export DEB_BUILD_MAINT_OPTIONS=hardening=+all
|
||||
|
||||
override_dh_installchangelogs:
|
||||
dh_installchangelogs Changelog
|
||||
|
||||
override_dh_auto_test:
|
||||
%:
|
||||
dh $@
|
||||
|
||||
14
dist/CMakeLists.txt
vendored
14
dist/CMakeLists.txt
vendored
@@ -1,7 +1,7 @@
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/scripts/maketarball.sh.in ${CMAKE_CURRENT_SOURCE_DIR}/scripts/maketarball.sh @ONLY)
|
||||
if(RPM_DISTRO AND RPM_DATE)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec.in ${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec @ONLY)
|
||||
endif(RPM_DISTRO AND RPM_DATE)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec.in ${CMAKE_BINARY_DIR}/strawberry.spec @ONLY)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
if(DEFINED ENV{MACOSX_DEPLOYMENT_TARGET})
|
||||
@@ -9,13 +9,13 @@ if(APPLE)
|
||||
else()
|
||||
set(LSMinimumSystemVersion 12.0)
|
||||
endif()
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist)
|
||||
endif(APPLE)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/macos/Info.plist)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/windres.rc.in ${CMAKE_BINARY_DIR}/windres.rc)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi.in ${CMAKE_BINARY_DIR}/strawberry.nsi @ONLY)
|
||||
endif(WIN32)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
install(FILES ../data/icons/48x48/strawberry.png DESTINATION share/icons/hicolor/48x48/apps/)
|
||||
@@ -24,9 +24,9 @@ if(UNIX AND NOT APPLE)
|
||||
install(FILES unix/org.strawberrymusicplayer.strawberry.desktop DESTINATION share/applications)
|
||||
install(FILES unix/org.strawberrymusicplayer.strawberry.appdata.xml DESTINATION share/metainfo)
|
||||
install(FILES unix/strawberry.1 DESTINATION share/man/man1)
|
||||
endif(UNIX AND NOT APPLE)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/macos/Info.plist" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents")
|
||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/macos/strawberry.icns" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources")
|
||||
endif()
|
||||
|
||||
4
dist/macos/Info.plist.in
vendored
4
dist/macos/Info.plist.in
vendored
@@ -35,9 +35,9 @@
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>@LSMinimumSystemVersion@</string>
|
||||
<key>SUFeedURL</key>
|
||||
<string>https://www.strawberrymusicplayer.org/sparkle-macos</string>
|
||||
<string>https://www.strawberrymusicplayer.org/sparkle-macos-@ARCH@</string>
|
||||
<key>SUPublicEDKey</key>
|
||||
<string>3IRScV8YtNVnx7zoeJAXvg28Kh1gN/Pyl2iPM467pG8=</string>
|
||||
<string>/OydhYVfypuO2Mf7G6DUqVZWW9G19eFV74qaDCBTOUk=</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
||||
29
dist/scripts/import-from-clementine.sh
vendored
29
dist/scripts/import-from-clementine.sh
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
# Strawberry Music Player
|
||||
# Copyright 2020, Jonas Kvinge <jonas@jkvinge.net>
|
||||
# 2021 Alexey Vazhnov
|
||||
# Copyright 2021, Alexey Vazhnov
|
||||
#
|
||||
# Strawberry is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -19,7 +19,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
# Based on https://github.com/strawberrymusicplayer/strawberry/wiki/Import-collection-library-and-playlists-data-from-Clementine
|
||||
# Based on https://wiki.strawberrymusicplayer.org/wiki/Import_collection_library_and_playlists_from_Clementine
|
||||
|
||||
set -o nounset
|
||||
set -o errexit
|
||||
@@ -35,8 +35,8 @@ test -f "$FILE_DST" || { echo "No such file: $FILE_DST"; exit 1; }
|
||||
|
||||
echo "Will try to copy information from $FILE_SRC to $FILE_DST."
|
||||
echo
|
||||
echo 'This script will **delete all information** from Strawberry database!'
|
||||
read -r -p 'Do you want to continue? (the only YES is accepted) ' answer
|
||||
echo 'This script will **delete all data** from the Strawberry database!'
|
||||
read -r -p 'Do you want to continue? (Only YES is accepted) ' answer
|
||||
if [ "$answer" != "YES" ]; then exit 1; fi
|
||||
|
||||
# 'heredoc' with substitution of variables, see `man bash`, "Here Documents":
|
||||
@@ -62,9 +62,9 @@ INSERT INTO strawberry.subdirectories (directory_id, path, mtime) SELECT directo
|
||||
INSERT INTO strawberry.songs (ROWID, title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, beginning, length, bitrate, samplerate, directory_id, url, filetype, filesize, mtime, ctime, unavailable, playcount, skipcount, lastplayed, compilation_detected, compilation_on, compilation_off, compilation_effective, art_automatic, art_manual, effective_albumartist, effective_originalyear, cue_path, rating)
|
||||
SELECT ROWID, title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, beginning, length, bitrate, samplerate, directory, filename, filetype, filesize, mtime, ctime, unavailable, playcount, skipcount, lastplayed, sampler, forced_compilation_on, forced_compilation_off, effective_compilation, art_automatic, art_manual, effective_albumartist, effective_originalyear, cue_path, rating FROM clementine.songs WHERE unavailable = 0;
|
||||
UPDATE strawberry.songs SET source = 2;
|
||||
UPDATE strawberry.songs SET artist_id = "";
|
||||
UPDATE strawberry.songs SET album_id = "";
|
||||
UPDATE strawberry.songs SET song_id = "";
|
||||
UPDATE strawberry.songs SET artist_id = '';
|
||||
UPDATE strawberry.songs SET album_id = '';
|
||||
UPDATE strawberry.songs SET song_id = '';
|
||||
|
||||
/* Import playlists */
|
||||
|
||||
@@ -140,7 +140,7 @@ SELECT ROWID,
|
||||
bitrate,
|
||||
samplerate,
|
||||
directory,
|
||||
filename,
|
||||
CASE WHEN filename IS NULL THEN '' ELSE filename END,
|
||||
filetype,
|
||||
filesize,
|
||||
mtime,
|
||||
@@ -162,16 +162,9 @@ SELECT ROWID,
|
||||
|
||||
UPDATE strawberry.playlist_items SET source = 2;
|
||||
UPDATE strawberry.playlist_items SET type = 2;
|
||||
UPDATE strawberry.playlist_items SET artist_id = "";
|
||||
UPDATE strawberry.playlist_items SET album_id = "";
|
||||
UPDATE strawberry.playlist_items SET song_id = "";
|
||||
|
||||
/* Recreate the FTS tables */
|
||||
|
||||
DELETE FROM strawberry.songs_fts;
|
||||
INSERT INTO strawberry.songs_fts (ROWID, ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment)
|
||||
SELECT ROWID, title, album, artist, albumartist, composer, performer, grouping, genre, comment
|
||||
FROM strawberry.songs;
|
||||
UPDATE strawberry.playlist_items SET artist_id = '';
|
||||
UPDATE strawberry.playlist_items SET album_id = '';
|
||||
UPDATE strawberry.playlist_items SET song_id = '';
|
||||
|
||||
EOF
|
||||
|
||||
|
||||
@@ -51,6 +51,12 @@
|
||||
</screenshots>
|
||||
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
||||
<releases>
|
||||
<release version="1.2.7" date="2025-01-31"/>
|
||||
<release version="1.2.6" date="2025-01-17"/>
|
||||
<release version="1.2.5" date="2025-01-17"/>
|
||||
<release version="1.2.4" date="2025-01-10"/>
|
||||
<release version="1.2.3" date="2024-12-08"/>
|
||||
<release version="1.2.2" date="2024-11-23"/>
|
||||
<release version="1.2.1" date="2024-11-21"/>
|
||||
<release version="1.1.3" date="2024-09-21"/>
|
||||
<release version="1.1.2" date="2024-09-12"/>
|
||||
|
||||
18
dist/unix/strawberry.spec.in
vendored
18
dist/unix/strawberry.spec.in
vendored
@@ -63,6 +63,8 @@ BuildRequires: pkgconfig(libcdio)
|
||||
BuildRequires: pkgconfig(libebur128)
|
||||
BuildRequires: pkgconfig(libgpod-1.0)
|
||||
BuildRequires: pkgconfig(libmtp)
|
||||
BuildRequires: cmake(GTest)
|
||||
BuildRequires: pkgconfig(gmock)
|
||||
|
||||
%if 0%{?suse_version}
|
||||
Requires: qt6-sql-sqlite
|
||||
@@ -103,13 +105,13 @@ Features:
|
||||
|
||||
%build
|
||||
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
|
||||
export CXXFLAGS="-fPIC $RPM_OPT_FLAGS"
|
||||
export CXXFLAGS="-fPIC -Wno-maybe-uninitialized $RPM_OPT_FLAGS"
|
||||
%endif
|
||||
%if "%{?_vendor}" == "openmandriva"
|
||||
%{cmake} -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@ -DENABLE_TRANSLATIONS=OFF
|
||||
%{cmake} -DBUILD_WERROR=ON
|
||||
%make_build
|
||||
%else
|
||||
%{cmake} -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@
|
||||
%{cmake} -DBUILD_WERROR=ON
|
||||
%cmake_build
|
||||
%endif
|
||||
|
||||
@@ -120,11 +122,13 @@ Features:
|
||||
%cmake_install
|
||||
%endif
|
||||
|
||||
%if 0%{?suse_version}
|
||||
%suse_update_desktop_file org.strawberrymusicplayer.strawberry Qt AudioVideo Audio Player
|
||||
%endif
|
||||
|
||||
%check
|
||||
export QT_QPA_PLATFORM="offscreen"
|
||||
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
|
||||
%{cmake_build} -t strawberry_tests
|
||||
%else
|
||||
%{make_build} -j $(nproc) -C build strawberry_tests
|
||||
%endif
|
||||
desktop-file-validate %{buildroot}%{_datadir}/applications/org.strawberrymusicplayer.strawberry.desktop
|
||||
%if 0%{?suse_version}
|
||||
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/org.strawberrymusicplayer.strawberry.appdata.xml
|
||||
|
||||
10
dist/windows/strawberry.nsi.in
vendored
10
dist/windows/strawberry.nsi.in
vendored
@@ -208,14 +208,16 @@ FunctionEnd
|
||||
!ifdef msvc
|
||||
!define vc_redist_file "vc_redist.${arch}.exe"
|
||||
Function InstallMSVCRuntime
|
||||
${registry::Read} "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\${arch}" "Version" $R0 $R1
|
||||
${If} $R0 == ""
|
||||
SetOutPath "$TEMP"
|
||||
File "${vc_redist_file}"
|
||||
; ${registry::Read} "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\${arch}" "Version" $R0 $R1
|
||||
; ${If} $R0 == ""
|
||||
SetDetailsView hide
|
||||
inetc::get /caption "Downloading..." "https://aka.ms/vs/17/release/${vc_redist_file}" "$TEMP\${vc_redist_file}" /end
|
||||
; inetc::get /caption "Downloading..." "https://aka.ms/vs/17/release/${vc_redist_file}" "$TEMP\${vc_redist_file}" /end
|
||||
ExecWait '"$TEMP\${vc_redist_file}" /install /passive'
|
||||
Delete "$TEMP\${vc_redist_file}"
|
||||
SetDetailsView show
|
||||
${EndIf}
|
||||
; ${EndIf}
|
||||
FunctionEnd
|
||||
!endif
|
||||
|
||||
|
||||
@@ -50,9 +50,9 @@
|
||||
// Make an INSTRUCTIONS file
|
||||
// can't mod scope in analyze you have to use transform for 2D use setErasePixmap Qt function insetead of m_background
|
||||
|
||||
AnalyzerBase::AnalyzerBase(QWidget *parent, const uint scopeSize)
|
||||
AnalyzerBase::AnalyzerBase(QWidget *parent, const uint scope_size)
|
||||
: QWidget(parent),
|
||||
fht_(new FHT(scopeSize)),
|
||||
fht_(new FHT(scope_size)),
|
||||
engine_(nullptr),
|
||||
lastscope_(512),
|
||||
new_frame_(false),
|
||||
@@ -211,28 +211,28 @@ void AnalyzerBase::demo(QPainter &p) {
|
||||
|
||||
}
|
||||
|
||||
void AnalyzerBase::interpolate(const Scope &inVec, Scope &outVec) {
|
||||
void AnalyzerBase::interpolate(const Scope &in_scope, Scope &out_scope) {
|
||||
|
||||
double pos = 0.0;
|
||||
const double step = static_cast<double>(inVec.size()) / static_cast<double>(outVec.size());
|
||||
const double step = static_cast<double>(in_scope.size()) / static_cast<double>(out_scope.size());
|
||||
|
||||
for (uint i = 0; i < outVec.size(); ++i, pos += step) {
|
||||
for (uint i = 0; i < out_scope.size(); ++i, pos += step) {
|
||||
const double error = pos - std::floor(pos);
|
||||
const uint64_t offset = static_cast<uint64_t>(pos);
|
||||
|
||||
uint64_t indexLeft = offset + 0;
|
||||
|
||||
if (indexLeft >= inVec.size()) {
|
||||
indexLeft = inVec.size() - 1;
|
||||
if (indexLeft >= in_scope.size()) {
|
||||
indexLeft = in_scope.size() - 1;
|
||||
}
|
||||
|
||||
uint64_t indexRight = offset + 1;
|
||||
|
||||
if (indexRight >= inVec.size()) {
|
||||
indexRight = inVec.size() - 1;
|
||||
if (indexRight >= in_scope.size()) {
|
||||
indexRight = in_scope.size() - 1;
|
||||
}
|
||||
|
||||
outVec[i] = inVec[indexLeft] * (1.0F - static_cast<float>(error)) + inVec[indexRight] * static_cast<float>(error);
|
||||
out_scope[i] = in_scope[indexLeft] * (1.0F - static_cast<float>(error)) + in_scope[indexRight] * static_cast<float>(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class AnalyzerBase : public QWidget {
|
||||
|
||||
protected:
|
||||
using Scope = std::vector<float>;
|
||||
explicit AnalyzerBase(QWidget*, const uint scopeSize = 7);
|
||||
explicit AnalyzerBase(QWidget *parent, const uint scope_size = 7);
|
||||
|
||||
void hideEvent(QHideEvent *e) override;
|
||||
void showEvent(QShowEvent *e) override;
|
||||
@@ -71,12 +71,12 @@ class AnalyzerBase : public QWidget {
|
||||
int resizeExponent(int exp);
|
||||
int resizeForBands(const int bands);
|
||||
virtual void init() {}
|
||||
virtual void transform(Scope&);
|
||||
virtual void analyze(QPainter &p, const Scope&, const bool new_frame) = 0;
|
||||
virtual void transform(Scope &scope);
|
||||
virtual void analyze(QPainter &p, const Scope &s, const bool new_frame) = 0;
|
||||
virtual void demo(QPainter &p);
|
||||
|
||||
void interpolate(const Scope&, Scope&);
|
||||
void initSin(Scope&, const uint = 6000);
|
||||
void interpolate(const Scope &in_scope, Scope &out_scope);
|
||||
void initSin(Scope &v, const uint size = 6000);
|
||||
|
||||
protected:
|
||||
QBasicTimer timer_;
|
||||
|
||||
@@ -78,7 +78,7 @@ CollectionBackend::~CollectionBackend() {
|
||||
|
||||
void CollectionBackend::Init(SharedPtr<Database> db, SharedPtr<TaskManager> task_manager, const Song::Source source, const QString &songs_table, const QString &dirs_table, const QString &subdirs_table) {
|
||||
|
||||
setObjectName(source == Song::Source::Collection ? QLatin1String(metaObject()->className()) : QStringLiteral("%1%2").arg(Song::DescriptionForSource(source), QLatin1String(metaObject()->className())));
|
||||
setObjectName(source == Song::Source::Collection ? QLatin1String(QObject::metaObject()->className()) : QStringLiteral("%1%2").arg(Song::DescriptionForSource(source), QLatin1String(QObject::metaObject()->className())));
|
||||
|
||||
db_ = db;
|
||||
task_manager_ = task_manager;
|
||||
|
||||
@@ -66,7 +66,7 @@ CollectionLibrary::CollectionLibrary(const SharedPtr<Database> database,
|
||||
save_playcounts_to_files_(false),
|
||||
save_ratings_to_files_(false) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
|
||||
original_thread_ = thread();
|
||||
|
||||
|
||||
@@ -50,14 +50,14 @@
|
||||
#include <QPixmapCache>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
|
||||
#include "includes/scoped_ptr.h"
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/standardpaths.h"
|
||||
#include "core/database.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/songmimedata.h"
|
||||
#include "collectionfilteroptions.h"
|
||||
@@ -98,7 +98,7 @@ CollectionModel::CollectionModel(const SharedPtr<CollectionBackend> backend, con
|
||||
loading_(false),
|
||||
icon_disk_cache_(new QNetworkDiskCache(this)) {
|
||||
|
||||
setObjectName(backend_->source() == Song::Source::Collection ? QLatin1String(metaObject()->className()) : QStringLiteral("%1%2").arg(Song::DescriptionForSource(backend_->source()), QLatin1String(metaObject()->className())));
|
||||
setObjectName(backend_->source() == Song::Source::Collection ? QLatin1String(QObject::metaObject()->className()) : QStringLiteral("%1%2").arg(Song::DescriptionForSource(backend_->source()), QLatin1String(QObject::metaObject()->className())));
|
||||
|
||||
filter_->setSourceModel(this);
|
||||
filter_->setSortRole(Role_SortText);
|
||||
@@ -114,7 +114,7 @@ CollectionModel::CollectionModel(const SharedPtr<CollectionBackend> backend, con
|
||||
pixmap_no_cover_ = nocover.pixmap(nocover_sizes.last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
icon_disk_cache_->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/' + QLatin1String(kPixmapDiskCacheDir) + u'-' + Song::TextForSource(backend_->source()));
|
||||
icon_disk_cache_->setCacheDirectory(StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + u'/' + QLatin1String(kPixmapDiskCacheDir) + u'-' + Song::TextForSource(backend_->source()));
|
||||
|
||||
QObject::connect(&*backend_, &CollectionBackend::SongsAdded, this, &CollectionModel::AddReAddOrUpdate);
|
||||
QObject::connect(&*backend_, &CollectionBackend::SongsChanged, this, &CollectionModel::AddReAddOrUpdate);
|
||||
@@ -288,29 +288,11 @@ void CollectionModel::SetFilterMaxAge(const int filter_max_age) {
|
||||
|
||||
QVariant CollectionModel::data(const QModelIndex &idx, const int role) const {
|
||||
|
||||
const CollectionItem *item = IndexToItem(idx);
|
||||
|
||||
// Handle a special case for returning album artwork instead of a generic CD icon.
|
||||
// this is here instead of in the other data() function to let us use the
|
||||
// QModelIndex& version of GetChildSongs, which satisfies const-ness, instead
|
||||
// of the CollectionItem *version, which doesn't.
|
||||
if (options_active_.show_pretty_covers) {
|
||||
bool is_album_node = false;
|
||||
if (role == Qt::DecorationRole && item->type == CollectionItem::Type::Container) {
|
||||
GroupBy container_group_by = options_active_.group_by[item->container_level];
|
||||
is_album_node = IsAlbumGroupBy(container_group_by);
|
||||
}
|
||||
if (is_album_node) {
|
||||
// It has const behaviour some of the time - that's ok right?
|
||||
return const_cast<CollectionModel*>(this)->AlbumIcon(idx);
|
||||
}
|
||||
}
|
||||
|
||||
return data(item, role);
|
||||
return data(IndexToItem(idx), role);
|
||||
|
||||
}
|
||||
|
||||
QVariant CollectionModel::data(const CollectionItem *item, const int role) const {
|
||||
QVariant CollectionModel::data(CollectionItem *item, const int role) const {
|
||||
|
||||
GroupBy container_group_by = item->type == CollectionItem::Type::Container ? options_active_.group_by[item->container_level] : GroupBy::None;
|
||||
|
||||
@@ -329,7 +311,7 @@ QVariant CollectionModel::data(const CollectionItem *item, const int role) const
|
||||
case GroupBy::YearAlbumDisc:
|
||||
case GroupBy::OriginalYearAlbum:
|
||||
case GroupBy::OriginalYearAlbumDisc:
|
||||
return QVariant();
|
||||
return options_active_.show_pretty_covers ? const_cast<CollectionModel*>(this)->AlbumIcon(item) : QVariant();
|
||||
case GroupBy::Artist:
|
||||
case GroupBy::AlbumArtist:
|
||||
return icon_artist_;
|
||||
@@ -408,17 +390,17 @@ QMimeData *CollectionModel::mimeData(const QModelIndexList &indexes) const {
|
||||
|
||||
if (indexes.isEmpty()) return nullptr;
|
||||
|
||||
SongMimeData *data = new SongMimeData;
|
||||
QList<QUrl> urls;
|
||||
SongList songs;
|
||||
QSet<int> song_ids;
|
||||
|
||||
data->backend = backend_;
|
||||
|
||||
QList<QUrl> urls;
|
||||
for (const QModelIndex &idx : indexes) {
|
||||
GetChildSongs(IndexToItem(idx), &urls, &data->songs, &song_ids);
|
||||
GetChildSongs(IndexToItem(idx), songs, song_ids, urls);
|
||||
}
|
||||
|
||||
SongMimeData *data = new SongMimeData;
|
||||
data->setUrls(urls);
|
||||
data->backend = backend_;
|
||||
data->songs = songs;
|
||||
data->name_for_new_playlist_ = Song::GetNameForNewPlaylist(data->songs);
|
||||
|
||||
return data;
|
||||
@@ -854,9 +836,9 @@ void CollectionModel::LoadSongsFromSqlAsyncFinished() {
|
||||
|
||||
}
|
||||
|
||||
QString CollectionModel::AlbumIconPixmapCacheKey(const QModelIndex &idx) const {
|
||||
QString CollectionModel::AlbumIconPixmapCacheKey(const CollectionItem *item) const {
|
||||
|
||||
return Song::TextForSource(backend_->source()) + QLatin1Char('/') + idx.data(Role_ContainerKey).toString();
|
||||
return Song::TextForSource(backend_->source()) + QLatin1Char('/') + item->container_key;
|
||||
|
||||
}
|
||||
|
||||
@@ -869,7 +851,7 @@ QUrl CollectionModel::AlbumIconPixmapDiskCacheKey(const QString &cache_key) {
|
||||
void CollectionModel::ClearItemPixmapCache(CollectionItem *item) {
|
||||
|
||||
// Remove from pixmap cache
|
||||
const QString cache_key = AlbumIconPixmapCacheKey(ItemToIndex(item));
|
||||
const QString cache_key = AlbumIconPixmapCacheKey(item);
|
||||
QPixmapCache::remove(cache_key);
|
||||
if (use_disk_cache_ && icon_disk_cache_) icon_disk_cache_->remove(AlbumIconPixmapDiskCacheKey(cache_key));
|
||||
if (pending_cache_keys_.contains(cache_key)) {
|
||||
@@ -888,13 +870,12 @@ void CollectionModel::ClearItemPixmapCache(CollectionItem *item) {
|
||||
|
||||
}
|
||||
|
||||
QVariant CollectionModel::AlbumIcon(const QModelIndex &idx) {
|
||||
QVariant CollectionModel::AlbumIcon(CollectionItem *item) {
|
||||
|
||||
CollectionItem *item = IndexToItem(idx);
|
||||
if (!item) return pixmap_no_cover_;
|
||||
|
||||
// Check the cache for a pixmap we already loaded.
|
||||
const QString cache_key = AlbumIconPixmapCacheKey(idx);
|
||||
const QString cache_key = AlbumIconPixmapCacheKey(item);
|
||||
|
||||
QPixmap cached_pixmap;
|
||||
if (QPixmapCache::find(cache_key, &cached_pixmap)) {
|
||||
@@ -919,7 +900,7 @@ QVariant CollectionModel::AlbumIcon(const QModelIndex &idx) {
|
||||
}
|
||||
|
||||
// No art is cached and we're not loading it already. Load art for the first song in the album.
|
||||
SongList songs = GetChildSongs(idx);
|
||||
const SongList songs = GetChildSongs(item);
|
||||
if (!songs.isEmpty()) {
|
||||
AlbumCoverLoaderOptions cover_loader_options(AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage);
|
||||
cover_loader_options.desired_scaled_size = QSize(kPrettyCoverSize, kPrettyCoverSize);
|
||||
@@ -1095,7 +1076,7 @@ QString CollectionModel::SortText(const GroupBy group_by, const Song &song, cons
|
||||
case GroupBy::Artist:
|
||||
return SortTextForArtist(song.artist(), sort_skips_articles);
|
||||
case GroupBy::Album:
|
||||
return SortTextForArtist(song.album(), sort_skips_articles);
|
||||
return SortText(song.album());
|
||||
case GroupBy::AlbumDisc:
|
||||
return song.album() + SortTextForNumber(std::max(0, song.disc()));
|
||||
case GroupBy::YearAlbum:
|
||||
@@ -1412,7 +1393,7 @@ QString CollectionModel::DividerDisplayText(const GroupBy group_by, const QStrin
|
||||
|
||||
}
|
||||
|
||||
bool CollectionModel::CompareItems(const CollectionItem *a, const CollectionItem *b) const {
|
||||
bool CollectionModel::CompareItems(CollectionItem *a, CollectionItem *b) const {
|
||||
|
||||
QVariant left = data(a, CollectionModel::Role_SortText);
|
||||
QVariant right = data(b, CollectionModel::Role_SortText);
|
||||
@@ -1453,24 +1434,23 @@ qint64 CollectionModel::MaximumCacheSize(Settings *s, const char *size_id, const
|
||||
|
||||
}
|
||||
|
||||
void CollectionModel::GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const {
|
||||
void CollectionModel::GetChildSongs(CollectionItem *item, SongList &songs, QSet<int> &song_ids, QList<QUrl> &urls) const {
|
||||
|
||||
switch (item->type) {
|
||||
case CollectionItem::Type::Container: {
|
||||
QList<CollectionItem*> children = item->children;
|
||||
std::sort(children.begin(), children.end(), std::bind(&CollectionModel::CompareItems, this, std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
for (CollectionItem *child : children) {
|
||||
GetChildSongs(child, urls, songs, song_ids);
|
||||
GetChildSongs(child, songs, song_ids, urls);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CollectionItem::Type::Song:
|
||||
urls->append(item->metadata.url());
|
||||
if (!song_ids->contains(item->metadata.id())) {
|
||||
songs->append(item->metadata);
|
||||
song_ids->insert(item->metadata.id());
|
||||
urls << item->metadata.url();
|
||||
if (!song_ids.contains(item->metadata.id())) {
|
||||
songs << item->metadata;
|
||||
song_ids << item->metadata.id();
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1480,16 +1460,33 @@ void CollectionModel::GetChildSongs(CollectionItem *item, QList<QUrl> *urls, Son
|
||||
|
||||
}
|
||||
|
||||
SongList CollectionModel::GetChildSongs(const QList<CollectionItem*> items) const {
|
||||
|
||||
SongList songs;
|
||||
QSet<int> song_ids;
|
||||
QList<QUrl> urls;
|
||||
for (CollectionItem *item : items) {
|
||||
GetChildSongs(item, songs, song_ids, urls);
|
||||
}
|
||||
|
||||
return songs;
|
||||
|
||||
}
|
||||
|
||||
SongList CollectionModel::GetChildSongs(CollectionItem *item) const {
|
||||
return GetChildSongs(QList<CollectionItem*>() << item);
|
||||
}
|
||||
|
||||
SongList CollectionModel::GetChildSongs(const QModelIndexList &indexes) const {
|
||||
|
||||
QList<QUrl> dontcare;
|
||||
SongList ret;
|
||||
SongList songs;
|
||||
QSet<int> song_ids;
|
||||
|
||||
QList<QUrl> urls;
|
||||
for (const QModelIndex &idx : indexes) {
|
||||
GetChildSongs(IndexToItem(idx), &dontcare, &ret, &song_ids);
|
||||
GetChildSongs(IndexToItem(idx), songs, song_ids, urls);
|
||||
}
|
||||
return ret;
|
||||
|
||||
return songs;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -194,11 +194,13 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
QString ContainerKey(const GroupBy group_by, const Song &song, bool &has_unique_album_identifier) const;
|
||||
|
||||
// Get information about the collection
|
||||
void GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const;
|
||||
void GetChildSongs(CollectionItem *item, SongList &songs, QSet<int> &song_ids, QList<QUrl> &urls) const;
|
||||
SongList GetChildSongs(const QList<CollectionItem*> items) const;
|
||||
SongList GetChildSongs(CollectionItem *item) const;
|
||||
SongList GetChildSongs(const QModelIndex &idx) const;
|
||||
SongList GetChildSongs(const QModelIndexList &indexes) const;
|
||||
|
||||
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
||||
bool CompareItems(CollectionItem *a, CollectionItem *b) const;
|
||||
|
||||
bool HasParentAlbumGroupBy(CollectionItem *item) const;
|
||||
|
||||
@@ -224,7 +226,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
void BeginReset();
|
||||
void EndReset();
|
||||
|
||||
QVariant data(const CollectionItem *item, const int role) const;
|
||||
QVariant data(CollectionItem *item, const int role) const;
|
||||
|
||||
void ScheduleUpdate(const CollectionModelUpdate::Type type, const SongList &songs);
|
||||
void ScheduleAddSongs(const SongList &songs);
|
||||
@@ -250,9 +252,9 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
|
||||
// Helpers
|
||||
static bool IsCompilationArtistNode(const CollectionItem *node) { return node == node->parent->compilation_artist_node_; }
|
||||
QString AlbumIconPixmapCacheKey(const QModelIndex &idx) const;
|
||||
QString AlbumIconPixmapCacheKey(const CollectionItem *item) const;
|
||||
static QUrl AlbumIconPixmapDiskCacheKey(const QString &cache_key);
|
||||
QVariant AlbumIcon(const QModelIndex &idx);
|
||||
QVariant AlbumIcon(CollectionItem *item);
|
||||
void ClearItemPixmapCache(CollectionItem *item);
|
||||
static qint64 MaximumCacheSize(Settings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default);
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
#include "collectionitem.h"
|
||||
#include "collectionitemdelegate.h"
|
||||
#include "collectionview.h"
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
# include "device/devicemanager.h"
|
||||
# include "device/devicestatefiltermodel.h"
|
||||
#endif
|
||||
@@ -95,7 +95,7 @@ CollectionView::CollectionView(QWidget *parent)
|
||||
action_open_in_new_playlist_(nullptr),
|
||||
action_organize_(nullptr),
|
||||
action_search_for_this_(nullptr),
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
action_copy_to_device_(nullptr),
|
||||
#endif
|
||||
action_edit_track_(nullptr),
|
||||
@@ -108,7 +108,7 @@ CollectionView::CollectionView(QWidget *parent)
|
||||
is_in_keyboard_search_(false),
|
||||
delete_files_(false) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
|
||||
setItemDelegate(new CollectionItemDelegate(this));
|
||||
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
@@ -417,7 +417,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
|
||||
context_menu_->addSeparator();
|
||||
action_organize_ = context_menu_->addAction(IconLoader::Load(u"edit-copy"_s), tr("Organize files..."), this, &CollectionView::Organize);
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
action_copy_to_device_ = context_menu_->addAction(IconLoader::Load(u"device"_s), tr("Copy to device..."), this, &CollectionView::CopyToDevice);
|
||||
#endif
|
||||
action_delete_files_ = context_menu_->addAction(IconLoader::Load(u"edit-delete"_s), tr("Delete from disk..."), this, &CollectionView::Delete);
|
||||
@@ -439,7 +439,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
|
||||
context_menu_->addMenu(filter_widget_->menu());
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
action_copy_to_device_->setDisabled(device_manager_->connected_devices_model()->rowCount() == 0);
|
||||
QObject::connect(device_manager_->connected_devices_model(), &DeviceStateFilterModel::IsEmptyChanged, action_copy_to_device_, &QAction::setDisabled);
|
||||
#endif
|
||||
@@ -481,7 +481,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
action_rescan_songs_->setEnabled(regular_editable > 0);
|
||||
|
||||
action_organize_->setVisible(regular_elements == regular_editable);
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
action_copy_to_device_->setVisible(regular_elements == regular_editable);
|
||||
#endif
|
||||
|
||||
@@ -492,7 +492,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
|
||||
// only when all selected items are editable
|
||||
action_organize_->setEnabled(regular_elements == regular_editable);
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
action_copy_to_device_->setEnabled(regular_elements == regular_editable);
|
||||
#endif
|
||||
|
||||
@@ -759,7 +759,7 @@ void CollectionView::RescanSongs() {
|
||||
|
||||
void CollectionView::CopyToDevice() {
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
if (!organize_dialog_) {
|
||||
organize_dialog_ = make_unique<OrganizeDialog>(task_manager_, tagreader_client_, nullptr, this);
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ class CollectionView : public AutoExpandingTreeView {
|
||||
QAction *action_organize_;
|
||||
QAction *action_search_for_this_;
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
QAction *action_copy_to_device_;
|
||||
#endif
|
||||
QAction *action_edit_track_;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QIODevice>
|
||||
#include <QStorageInfo>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
@@ -51,6 +52,7 @@
|
||||
#include "core/settings.h"
|
||||
#include "utilities/imageutils.h"
|
||||
#include "constants/timeconstants.h"
|
||||
#include "constants/filesystemconstants.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "collectiondirectory.h"
|
||||
#include "collectionbackend.h"
|
||||
@@ -104,7 +106,7 @@ CollectionWatcher::CollectionWatcher(const Song::Source source,
|
||||
cue_parser_(new CueParser(tagreader_client, backend, this)),
|
||||
last_scan_time_(0) {
|
||||
|
||||
setObjectName(source_ == Song::Source::Collection ? QLatin1String(metaObject()->className()) : QStringLiteral("%1%2").arg(Song::DescriptionForSource(source_), QLatin1String(metaObject()->className())));
|
||||
setObjectName(source_ == Song::Source::Collection ? QLatin1String(QObject::metaObject()->className()) : QStringLiteral("%1%2").arg(Song::DescriptionForSource(source_), QLatin1String(QObject::metaObject()->className())));
|
||||
|
||||
original_thread_ = thread();
|
||||
|
||||
@@ -464,6 +466,24 @@ CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
||||
|
||||
void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs) {
|
||||
|
||||
{
|
||||
const QFileInfo path_info(dir.path);
|
||||
if (path_info.isSymbolicLink()) {
|
||||
const QStorageInfo storage_info(path_info.symLinkTarget());
|
||||
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||
qLog(Warning) << "Ignoring collection directory path" << dir.path << "which is a symbolic link to path" << path_info.symLinkTarget() << "with rejected filesystem type" << storage_info.fileSystemType();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const QStorageInfo storage_info(dir.path);
|
||||
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||
qLog(Warning) << "Ignoring collection directory path" << dir.path << "with rejected filesystem type" << storage_info.fileSystemType();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CancelStop();
|
||||
|
||||
watched_dirs_[dir.id] = dir;
|
||||
@@ -506,17 +526,29 @@ void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const Colle
|
||||
|
||||
void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSubdirectory &subdir, const quint64 files_count, ScanTransaction *t, const bool force_noincremental) {
|
||||
|
||||
QFileInfo path_info(path);
|
||||
const QFileInfo path_info(path);
|
||||
|
||||
// Do not scan symlinked dirs that are already in collection
|
||||
if (path_info.isSymLink()) {
|
||||
QString real_path = path_info.symLinkTarget();
|
||||
const QString real_path = path_info.symLinkTarget();
|
||||
const QStorageInfo storage_info(real_path);
|
||||
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||
qLog(Warning) << "Ignoring symbolic link" << path << "which links to" << real_path << "with rejected filesystem type" << storage_info.fileSystemType();
|
||||
return;
|
||||
}
|
||||
// Do not scan symlinked dirs that are already in collection
|
||||
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||
if (real_path.startsWith(dir.path)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const QStorageInfo storage_info(path);
|
||||
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||
qLog(Warning) << "Ignoring path" << path << "with rejected filesystem type" << storage_info.fileSystemType();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool songs_missing_fingerprint = false;
|
||||
bool songs_missing_loudness_characteristics = false;
|
||||
@@ -556,32 +588,40 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
|
||||
if (stop_or_abort_requested()) return;
|
||||
|
||||
QString child(it.next());
|
||||
QFileInfo child_info(child);
|
||||
const QString child_filepath = it.next();
|
||||
const QFileInfo child_fileinfo(child_filepath);
|
||||
|
||||
if (child_info.isDir()) {
|
||||
if (!t->HasSeenSubdir(child)) {
|
||||
if (child_fileinfo.isSymLink()) {
|
||||
QStorageInfo storage_info(child_fileinfo.symLinkTarget());
|
||||
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||
qLog(Warning) << "Ignoring symbolic link" << child_filepath << "which links to" << child_fileinfo.symLinkTarget() << "with rejected filesystem type" << storage_info.fileSystemType();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (child_fileinfo.isDir()) {
|
||||
if (!t->HasSeenSubdir(child_filepath)) {
|
||||
// We haven't seen this subdirectory before - add it to a list, and later we'll tell the backend about it and scan it.
|
||||
CollectionSubdirectory new_subdir;
|
||||
new_subdir.directory_id = -1;
|
||||
new_subdir.path = child;
|
||||
new_subdir.mtime = child_info.lastModified().toSecsSinceEpoch();
|
||||
new_subdir.path = child_filepath;
|
||||
new_subdir.mtime = child_fileinfo.lastModified().toSecsSinceEpoch();
|
||||
my_new_subdirs << new_subdir;
|
||||
}
|
||||
t->AddToProgress(1);
|
||||
}
|
||||
else {
|
||||
QString ext_part(ExtensionPart(child));
|
||||
QString dir_part(DirectoryPart(child));
|
||||
if (Song::kRejectedExtensions.contains(child_info.suffix(), Qt::CaseInsensitive) || child_info.baseName() == "qt_temp"_L1) {
|
||||
QString ext_part(ExtensionPart(child_filepath));
|
||||
QString dir_part(DirectoryPart(child_filepath));
|
||||
if (Song::kRejectedExtensions.contains(child_fileinfo.suffix(), Qt::CaseInsensitive) || child_fileinfo.baseName() == "qt_temp"_L1) {
|
||||
t->AddToProgress(1);
|
||||
}
|
||||
else if (sValidImages.contains(ext_part)) {
|
||||
album_art[dir_part] << child;
|
||||
album_art[dir_part] << child_filepath;
|
||||
t->AddToProgress(1);
|
||||
}
|
||||
else if (tagreader_client_->IsMediaFileBlocking(child)) {
|
||||
files_on_disk << child;
|
||||
else if (tagreader_client_->IsMediaFileBlocking(child_filepath)) {
|
||||
files_on_disk << child_filepath;
|
||||
}
|
||||
else {
|
||||
t->AddToProgress(1);
|
||||
@@ -828,7 +868,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
||||
qLog(Error) << "Could not open CUE file" << matching_cue << "for reading:" << cue_file.errorString();
|
||||
return;
|
||||
}
|
||||
const SongList songs = cue_parser_->Load(&cue_file, matching_cue, path, false);
|
||||
const SongList songs = cue_parser_->Load(&cue_file, matching_cue, path, false).songs;
|
||||
cue_file.close();
|
||||
|
||||
// Update every song that's in the CUE and collection
|
||||
@@ -915,7 +955,7 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
|
||||
// Also, watch out for incorrect media files.
|
||||
// Playlist parser for CUEs considers every entry in sheet valid, and we don't want invalid media getting into collection!
|
||||
QString file_nfd = file.normalized(QString::NormalizationForm_D);
|
||||
SongList cue_songs = cue_parser_->Load(&cue_file, matching_cue, path, false);
|
||||
SongList cue_songs = cue_parser_->Load(&cue_file, matching_cue, path, false).songs;
|
||||
cue_file.close();
|
||||
songs.reserve(cue_songs.count());
|
||||
for (Song &cue_song : cue_songs) {
|
||||
@@ -1153,7 +1193,7 @@ void CollectionWatcher::RescanPathsNow() {
|
||||
|
||||
QMap<QString, quint64> subdir_files_count;
|
||||
for (const QString &path : paths) {
|
||||
quint64 files_count = FilesCountForPath(&transaction, path);
|
||||
const quint64 files_count = FilesCountForPath(&transaction, path);
|
||||
subdir_files_count[path] = files_count;
|
||||
transaction.AddToProgressMax(files_count);
|
||||
}
|
||||
@@ -1316,18 +1356,45 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
|
||||
|
||||
quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &path) {
|
||||
|
||||
const QFileInfo path_info(path);
|
||||
if (path_info.isSymLink()) {
|
||||
const QString real_path = path_info.symLinkTarget();
|
||||
const QStorageInfo storage_info(real_path);
|
||||
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||
return 0;
|
||||
}
|
||||
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||
if (real_path.startsWith(dir.path)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const QStorageInfo storage_info(path);
|
||||
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
quint64 i = 0;
|
||||
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||
while (it.hasNext()) {
|
||||
|
||||
if (stop_or_abort_requested()) break;
|
||||
|
||||
QString child = it.next();
|
||||
QFileInfo path_info(child);
|
||||
const QString child_filepath = it.next();
|
||||
const QFileInfo child_fileinfo(child_filepath);
|
||||
|
||||
if (child_fileinfo.isDir()) {
|
||||
if (child_fileinfo.isSymLink()) {
|
||||
|
||||
const QString real_path = child_fileinfo.symLinkTarget();
|
||||
|
||||
QStorageInfo storage_info(real_path);
|
||||
if (kRejectedFileSystems.contains(storage_info.fileSystemType())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path_info.isDir()) {
|
||||
if (path_info.isSymLink()) {
|
||||
QString real_path = path_info.symLinkTarget();
|
||||
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||
if (real_path.startsWith(dir.path)) {
|
||||
continue;
|
||||
@@ -1335,9 +1402,9 @@ quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &
|
||||
}
|
||||
}
|
||||
|
||||
if (!t->HasSeenSubdir(child) && !path_info.isHidden()) {
|
||||
if (!t->HasSeenSubdir(child_filepath) && !child_fileinfo.isHidden()) {
|
||||
// We haven't seen this subdirectory before, so we need to include the file count for this directory too.
|
||||
i += FilesCountForPath(t, child);
|
||||
i += FilesCountForPath(t, child_filepath);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1385,7 +1452,7 @@ void CollectionWatcher::RescanSongs(const SongList &songs) {
|
||||
if (stop_or_abort_requested()) break;
|
||||
if (subdir.path != song_path) continue;
|
||||
qLog(Debug) << "Rescan for directory ID" << song.directory_id() << "directory" << subdir.path;
|
||||
quint64 files_count = FilesCountForPath(&transaction, subdir.path);
|
||||
const quint64 files_count = FilesCountForPath(&transaction, subdir.path);
|
||||
ScanSubdirectory(song_path, subdir, files_count, &transaction);
|
||||
scanned_paths << subdir.path;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#cmakedefine HAVE_AUDIOCD
|
||||
#cmakedefine HAVE_MTP
|
||||
#cmakedefine HAVE_GPOD
|
||||
#cmakedefine HAVE_SPARKLE
|
||||
#cmakedefine HAVE_QTSPARKLE
|
||||
#cmakedefine HAVE_SONGFINGERPRINTING
|
||||
#cmakedefine HAVE_MUSICBRAINZ
|
||||
@@ -40,8 +41,8 @@
|
||||
#cmakedefine INSTALL_TRANSLATIONS
|
||||
#define TRANSLATIONS_DIR "${CMAKE_INSTALL_PREFIX}/share/strawberry/translations"
|
||||
|
||||
#cmakedefine HAVE_QPA_QPLATFORMNATIVEINTERFACE
|
||||
#cmakedefine HAVE_QX11APPLICATION
|
||||
#cmakedefine HAVE_QPA_QPLATFORMNATIVEINTERFACE_H
|
||||
|
||||
#cmakedefine ENABLE_WIN32_CONSOLE
|
||||
|
||||
|
||||
31
src/constants/filesystemconstants.h
Normal file
31
src/constants/filesystemconstants.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef FILESYSTEMCONSTANTS_H
|
||||
#define FILESYSTEMCONSTANTS_H
|
||||
|
||||
#include <QByteArrayList>
|
||||
|
||||
const QByteArrayList kRejectedFileSystems = QByteArrayList() << "proc"
|
||||
<< "sysfs"
|
||||
<< "tmpfs"
|
||||
<< "devtmpfs";
|
||||
|
||||
|
||||
#endif // FILESYSTEMCONSTANTS_H
|
||||
@@ -37,6 +37,7 @@ constexpr char kPassword[] = "password";
|
||||
constexpr char kHTTP2[] = "http2";
|
||||
constexpr char kVerifyCertificate[] = "verifycertificate";
|
||||
constexpr char kDownloadAlbumCovers[] = "downloadalbumcovers";
|
||||
constexpr char kUseAlbumIdForAlbumCovers[] = "usealbumidforalbumcovers";
|
||||
constexpr char kServerSideScrobbling[] = "serversidescrobbling";
|
||||
constexpr char kAuthMethod[] = "authmethod";
|
||||
|
||||
|
||||
@@ -250,7 +250,7 @@ Application::Application(QObject *parent)
|
||||
p_(new ApplicationImpl(this)),
|
||||
g_thread_(nullptr) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
|
||||
const QMetaObject *mo = QAbstractEventDispatcher::instance(QCoreApplication::instance()->thread())->metaObject();
|
||||
if (mo && strcmp(mo->className(), "QEventDispatcherGlib") != 0 && strcmp(mo->superClass()->className(), "QEventDispatcherGlib") != 0) {
|
||||
|
||||
@@ -39,10 +39,10 @@
|
||||
#include <QSqlDriver>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlError>
|
||||
#include <QStandardPaths>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "logging.h"
|
||||
#include "standardpaths.h"
|
||||
#include "taskmanager.h"
|
||||
#include "database.h"
|
||||
#include "sqlquery.h"
|
||||
@@ -69,7 +69,7 @@ Database::Database(SharedPtr<TaskManager> task_manager, QObject *parent, const Q
|
||||
startup_schema_version_(-1),
|
||||
original_thread_(nullptr) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
|
||||
original_thread_ = thread();
|
||||
|
||||
@@ -78,7 +78,7 @@ Database::Database(SharedPtr<TaskManager> task_manager, QObject *parent, const Q
|
||||
connection_id_ = sNextConnectionId++;
|
||||
}
|
||||
|
||||
directory_ = QDir::toNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
|
||||
directory_ = QDir::toNativeSeparators(StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation)).replace(u"Strawberry"_s, u"strawberry"_s);
|
||||
|
||||
QMutexLocker l(&mutex_);
|
||||
Connect();
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
#include <QString>
|
||||
#include <QIcon>
|
||||
#include <QSize>
|
||||
#include <QStandardPaths>
|
||||
#include <QSettings>
|
||||
|
||||
#include "logging.h"
|
||||
#include "standardpaths.h"
|
||||
#include "settings.h"
|
||||
#include "includes/iconmapper.h"
|
||||
#include "iconloader.h"
|
||||
@@ -43,7 +43,7 @@ bool IconLoader::custom_icons_ = false;
|
||||
|
||||
void IconLoader::Init() {
|
||||
|
||||
#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN)
|
||||
#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN32)
|
||||
Settings s;
|
||||
s.beginGroup(AppearanceSettings::kSettingsGroup);
|
||||
system_icons_ = s.value("system_icons", false).toBool();
|
||||
@@ -51,7 +51,7 @@ void IconLoader::Init() {
|
||||
#endif
|
||||
|
||||
QDir dir;
|
||||
if (dir.exists(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/icons"_s)) {
|
||||
if (dir.exists(StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/icons"_s)) {
|
||||
custom_icons_ = true;
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ QIcon IconLoader::Load(const QString &name, const bool system_icon, const int fi
|
||||
}
|
||||
|
||||
if (custom_icons_) {
|
||||
QString custom_icon_path = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/icons/%1x%2/%3.png"_s;
|
||||
QString custom_icon_path = StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/icons/%1x%2/%3.png"_s;
|
||||
for (int s : std::as_const(sizes)) {
|
||||
QString filename(custom_icon_path.arg(s).arg(s).arg(name));
|
||||
if (QFile::exists(filename)) ret.addFile(filename, QSize(s, s));
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
#include "lyrics/lyricsproviders.h"
|
||||
#include "device/devicemanager.h"
|
||||
#include "device/devicestatefiltermodel.h"
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
# include "device/deviceview.h"
|
||||
# include "device/deviceviewcontainer.h"
|
||||
#endif
|
||||
@@ -208,7 +208,7 @@
|
||||
|
||||
#include "organize/organizeerrordialog.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN32
|
||||
# include "core/windows7thumbbar.h"
|
||||
#endif
|
||||
|
||||
@@ -220,6 +220,10 @@
|
||||
# include "systemtrayicon/qtsystemtrayicon.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SPARKLE
|
||||
#include "core/sparkleupdater.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_QTSPARKLE
|
||||
# include <qtsparkle-qt6/Updater>
|
||||
#endif // HAVE_QTSPARKLE
|
||||
@@ -235,25 +239,46 @@ const int kTrackPositionUpdateTimeMs = 1000;
|
||||
} // namespace
|
||||
|
||||
#ifdef HAVE_QTSPARKLE
|
||||
# ifdef _MSC_VER
|
||||
# ifdef _M_X64
|
||||
constexpr char QTSPARKLE_URL[] = "https://www.strawberrymusicplayer.org/sparkle-windows-msvc-x64";
|
||||
namespace {
|
||||
|
||||
# if defined(__APPLE__)
|
||||
# if defined(__x86_64__)
|
||||
constexpr char QTSPARKLE_URL[] = "https://www.strawberrymusicplayer.org/sparkle-macos-x86_64";
|
||||
# elif defined(__aarch64__)
|
||||
constexpr char QTSPARKLE_URL[] = "https://www.strawberrymusicplayer.org/sparkle-macos-arm64";
|
||||
# else
|
||||
constexpr char QTSPARKLE_URL[] = "https://www.strawberrymusicplayer.org/sparkle-windows-msvc-x86";
|
||||
# error "Unsupported macOS arch for QtSparkle"
|
||||
# endif
|
||||
# else
|
||||
# ifdef __x86_64__
|
||||
|
||||
# elif defined(__MINGW32__)
|
||||
# if defined(__x86_64__)
|
||||
constexpr char QTSPARKLE_URL[] = "https://www.strawberrymusicplayer.org/sparkle-windows-mingw-x64";
|
||||
# else
|
||||
# elif defined(__i686__)
|
||||
constexpr char QTSPARKLE_URL[] = "https://www.strawberrymusicplayer.org/sparkle-windows-mingw-x86";
|
||||
# else
|
||||
# error "Unsupported MinGW arch for QtSparkle"
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
# elif defined(_MSC_VER)
|
||||
# if defined(_WIN64)
|
||||
constexpr char QTSPARKLE_URL[] = "https://www.strawberrymusicplayer.org/sparkle-windows-msvc-x64";
|
||||
# elif defined(_WIN32)
|
||||
constexpr char QTSPARKLE_URL[] = "https://www.strawberrymusicplayer.org/sparkle-windows-msvc-x86";
|
||||
# else
|
||||
# error "Unsupported MSVC arch for QtSparkle"
|
||||
# endif
|
||||
|
||||
# else
|
||||
# error "Unsupported OS for QtSparkle"
|
||||
# endif // OS
|
||||
|
||||
} // namespace
|
||||
#endif // HAVE_QTSPARKLE
|
||||
|
||||
MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OSDBase *osd, const CommandlineOptions &options, QWidget *parent)
|
||||
: QMainWindow(parent),
|
||||
ui_(new Ui_MainWindow),
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN32
|
||||
thumbbar_(new Windows7ThumbBar(this)),
|
||||
#endif
|
||||
app_(app),
|
||||
@@ -272,7 +297,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
context_view_(new ContextView(this)),
|
||||
collection_view_(new CollectionViewContainer(this)),
|
||||
file_view_(new FileView(this)),
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
device_view_(new DeviceViewContainer(this)),
|
||||
#endif
|
||||
playlist_list_(new PlaylistListContainer(this)),
|
||||
@@ -337,7 +362,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
playlist_move_to_collection_(nullptr),
|
||||
playlist_open_in_browser_(nullptr),
|
||||
playlist_organize_(nullptr),
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
playlist_copy_to_device_(nullptr),
|
||||
#endif
|
||||
playlist_delete_(nullptr),
|
||||
@@ -391,7 +416,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
ui_->tabs->AddTab(smartplaylists_view_, u"smartplaylists"_s, IconLoader::Load(u"view-media-playlist"_s, true, 0, 32), tr("Smart playlists"));
|
||||
ui_->tabs->AddTab(file_view_, u"files"_s, IconLoader::Load(u"document-open"_s, true, 0, 32), tr("Files"));
|
||||
ui_->tabs->AddTab(radio_view_, u"radios"_s, IconLoader::Load(u"radio"_s, true, 0, 32), tr("Radios"));
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
ui_->tabs->AddTab(device_view_, u"devices"_s, IconLoader::Load(u"device"_s, true, 0, 32), tr("Devices"));
|
||||
#endif
|
||||
#ifdef HAVE_SUBSONIC
|
||||
@@ -441,7 +466,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
|
||||
collection_view_->view()->setModel(app_->collection()->model()->filter());
|
||||
collection_view_->view()->Init(app->task_manager(), app->tagreader_client(), app->network(), app->albumcover_loader(), app->current_albumcover_loader(), app->cover_providers(), app->lyrics_providers(), app->collection(), app->device_manager(), app->streaming_services());
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
device_view_->view()->Init(app->task_manager(), app->tagreader_client(), app->device_manager(), app->collection_model()->directory_model());
|
||||
#endif
|
||||
playlist_list_->Init(app_->task_manager(), app->tagreader_client(), app_->playlist_manager(), app_->playlist_backend(), app_->device_manager());
|
||||
@@ -515,7 +540,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
QObject::connect(file_view_, &FileView::CopyToCollection, this, &MainWindow::CopyFilesToCollection);
|
||||
QObject::connect(file_view_, &FileView::MoveToCollection, this, &MainWindow::MoveFilesToCollection);
|
||||
QObject::connect(file_view_, &FileView::EditTags, this, &MainWindow::EditFileTags);
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
QObject::connect(file_view_, &FileView::CopyToDevice, this, &MainWindow::CopyFilesToDevice);
|
||||
#endif
|
||||
file_view_->SetTaskManager(app_->task_manager());
|
||||
@@ -679,7 +704,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
QObject::connect(album_cover_choice_controller_->search_cover_auto_action(), &QAction::triggered, this, &MainWindow::SearchCoverAutomatically);
|
||||
QObject::connect(album_cover_choice_controller_->search_cover_auto_action(), &QAction::toggled, this, &MainWindow::ToggleSearchCoverAuto);
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
// Devices connections
|
||||
QObject::connect(device_view_->view(), &DeviceView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist);
|
||||
#endif
|
||||
@@ -785,7 +810,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
playlist_organize_ = playlist_menu_->addAction(IconLoader::Load(u"edit-copy"_s), tr("Organize files..."), this, &MainWindow::PlaylistMoveToCollection);
|
||||
playlist_copy_to_collection_ = playlist_menu_->addAction(IconLoader::Load(u"edit-copy"_s), tr("Copy to collection..."), this, &MainWindow::PlaylistCopyToCollection);
|
||||
playlist_move_to_collection_ = playlist_menu_->addAction(IconLoader::Load(u"go-jump"_s), tr("Move to collection..."), this, &MainWindow::PlaylistMoveToCollection);
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
playlist_copy_to_device_ = playlist_menu_->addAction(IconLoader::Load(u"device"_s), tr("Copy to device..."), this, &MainWindow::PlaylistCopyToDevice);
|
||||
#endif
|
||||
playlist_delete_ = playlist_menu_->addAction(IconLoader::Load(u"edit-delete"_s), tr("Delete from disk..."), this, &MainWindow::PlaylistDelete);
|
||||
@@ -829,13 +854,13 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
QObject::connect(&*tray_icon_, &SystemTrayIcon::ChangeVolume, this, &MainWindow::VolumeWheelEvent);
|
||||
|
||||
// Windows 7 thumbbar buttons
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN32
|
||||
thumbbar_->SetActions(QList<QAction*>() << ui_->action_previous_track << ui_->action_play_pause << ui_->action_stop << ui_->action_next_track << nullptr << ui_->action_love);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_QTSPARKLE)
|
||||
QAction *check_updates = ui_->menu_tools->addAction(tr("Check for updates..."));
|
||||
check_updates->setMenuRole(QAction::ApplicationSpecificRole);
|
||||
#if defined(HAVE_SPARKLE) || defined(HAVE_QTSPARKLE)
|
||||
QAction *action_check_updates = ui_->menu_tools->addAction(tr("Check for updates..."));
|
||||
action_check_updates->setMenuRole(QAction::ApplicationSpecificRole);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GLOBALSHORTCUTS
|
||||
@@ -941,7 +966,7 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
QObject::connect(&*app_->lastfm_import(), &LastFMImport::UpdateLastPlayed, &*app_->collection_backend(), &CollectionBackend::UpdateLastPlayed);
|
||||
QObject::connect(&*app_->lastfm_import(), &LastFMImport::UpdatePlayCount, &*app_->collection_backend(), &CollectionBackend::UpdatePlayCount);
|
||||
|
||||
#if !defined(HAVE_AUDIOCD) || defined(Q_OS_WIN)
|
||||
#if !defined(HAVE_AUDIOCD) || defined(Q_OS_WIN32)
|
||||
ui_->action_open_cd->setEnabled(false);
|
||||
ui_->action_open_cd->setVisible(false);
|
||||
#endif
|
||||
@@ -1046,13 +1071,18 @@ MainWindow::MainWindow(Application *app, SharedPtr<SystemTrayIcon> tray_icon, OS
|
||||
app_->scrobbler()->Submit();
|
||||
}
|
||||
|
||||
#ifdef HAVE_SPARKLE
|
||||
SparkleUpdater *sparkle_updater = new SparkleUpdater(action_check_updates, this);
|
||||
QObject::connect(action_check_updates, &QAction::triggered, sparkle_updater, &SparkleUpdater::CheckForUpdates);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_QTSPARKLE
|
||||
QUrl sparkle_url(QString::fromLatin1(QTSPARKLE_URL));
|
||||
if (!sparkle_url.isEmpty()) {
|
||||
qLog(Debug) << "Creating Qt Sparkle updater";
|
||||
qtsparkle::Updater *updater = new qtsparkle::Updater(sparkle_url, this);
|
||||
updater->SetVersion(QStringLiteral(STRAWBERRY_VERSION_PACKAGE));
|
||||
QObject::connect(check_updates, &QAction::triggered, updater, &qtsparkle::Updater::CheckNow);
|
||||
QObject::connect(action_check_updates, &QAction::triggered, updater, &qtsparkle::Updater::CheckNow);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1636,6 +1666,11 @@ void MainWindow::StopAfterCurrent() {
|
||||
|
||||
void MainWindow::showEvent(QShowEvent *e) {
|
||||
|
||||
if (error_dialog_ && error_dialog_->isVisible() && error_dialog_->isMinimized()) {
|
||||
error_dialog_->raise();
|
||||
error_dialog_->activateWindow();
|
||||
}
|
||||
|
||||
QMainWindow::showEvent(e);
|
||||
|
||||
}
|
||||
@@ -1665,7 +1700,12 @@ void MainWindow::closeEvent(QCloseEvent *e) {
|
||||
void MainWindow::SetHiddenInTray(const bool hidden) {
|
||||
|
||||
if (hidden && isVisible()) {
|
||||
close();
|
||||
if (tray_icon_->IsSystemTrayAvailable() && tray_icon_->isVisible() && keep_running_) {
|
||||
close();
|
||||
}
|
||||
else {
|
||||
showMinimized();
|
||||
}
|
||||
}
|
||||
else if (!hidden && isHidden()) {
|
||||
if (was_minimized_) {
|
||||
@@ -1962,7 +2002,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
||||
playlist_show_in_collection_->setVisible(false);
|
||||
playlist_copy_to_collection_->setVisible(false);
|
||||
playlist_move_to_collection_->setVisible(false);
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
playlist_copy_to_device_->setVisible(false);
|
||||
#endif
|
||||
playlist_organize_->setVisible(false);
|
||||
@@ -2037,7 +2077,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
||||
playlist_move_to_collection_->setVisible(local_songs > 0);
|
||||
}
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
playlist_copy_to_device_->setVisible(local_songs > 0);
|
||||
#endif
|
||||
|
||||
@@ -2676,7 +2716,7 @@ void MainWindow::MoveFilesToCollection(const QList<QUrl> &urls) {
|
||||
|
||||
void MainWindow::CopyFilesToDevice(const QList<QUrl> &urls) {
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
|
||||
organize_dialog_->SetCopy(true);
|
||||
if (organize_dialog_->SetUrls(urls)) {
|
||||
@@ -2816,7 +2856,7 @@ void MainWindow::PlaylistSkip() {
|
||||
|
||||
void MainWindow::PlaylistCopyToDevice() {
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
|
||||
SongList songs;
|
||||
|
||||
@@ -2992,7 +3032,7 @@ void MainWindow::Raise() {
|
||||
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN32
|
||||
bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) {
|
||||
|
||||
if (exit_count_ == 0 && message) {
|
||||
@@ -3001,7 +3041,7 @@ bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, qintptr
|
||||
}
|
||||
return QMainWindow::nativeEvent(eventType, message, result);
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
#endif // Q_OS_WIN32
|
||||
|
||||
void MainWindow::AutoCompleteTags() {
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ class CollectionViewContainer;
|
||||
class CollectionFilter;
|
||||
class AlbumCoverChoiceController;
|
||||
class CommandlineOptions;
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
class DeviceViewContainer;
|
||||
#endif
|
||||
class EditTagDialog;
|
||||
@@ -92,7 +92,7 @@ class Ui_MainWindow;
|
||||
class StreamingSongsView;
|
||||
class StreamingTabsView;
|
||||
class SmartPlaylistsViewContainer;
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN32
|
||||
class Windows7ThumbBar;
|
||||
#endif
|
||||
class AddStreamDialog;
|
||||
@@ -114,7 +114,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
void hideEvent(QHideEvent *e) override;
|
||||
void closeEvent(QCloseEvent *e) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN32
|
||||
bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override;
|
||||
#endif
|
||||
|
||||
@@ -289,7 +289,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
|
||||
private:
|
||||
Ui_MainWindow *ui_;
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN32
|
||||
Windows7ThumbBar *thumbbar_;
|
||||
#endif
|
||||
|
||||
@@ -306,7 +306,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
ContextView *context_view_;
|
||||
CollectionViewContainer *collection_view_;
|
||||
FileView *file_view_;
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
DeviceViewContainer *device_view_;
|
||||
#endif
|
||||
PlaylistListContainer *playlist_list_;
|
||||
@@ -359,7 +359,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
QAction *playlist_move_to_collection_;
|
||||
QAction *playlist_open_in_browser_;
|
||||
QAction *playlist_organize_;
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef Q_OS_WIN32
|
||||
QAction *playlist_copy_to_device_;
|
||||
#endif
|
||||
QAction *playlist_delete_;
|
||||
|
||||
@@ -99,7 +99,7 @@ Player::Player(const SharedPtr<TaskManager> task_manager, const SharedPtr<UrlHan
|
||||
volume_increment_(5),
|
||||
play_offset_nanosec_(0) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
|
||||
timer_save_volume_->setSingleShot(true);
|
||||
timer_save_volume_->setInterval(5s);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2024-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -18,13 +18,26 @@
|
||||
*/
|
||||
|
||||
#include <QSettings>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "settings.h"
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
Settings::Settings(QObject *parent)
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||
: QSettings(QCoreApplication::organizationName().toLower(), QCoreApplication::applicationName().toLower(), parent) {}
|
||||
#else
|
||||
: QSettings(parent) {}
|
||||
#endif
|
||||
|
||||
Settings::Settings(const QSettings::Scope scope, QObject *parent)
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||
: QSettings(scope, QCoreApplication::organizationName().toLower(), QCoreApplication::applicationName().toLower(), parent) {}
|
||||
#else
|
||||
: QSettings(scope, parent) {}
|
||||
#endif
|
||||
|
||||
Settings::Settings(const QString &filename, const Format format, QObject *parent)
|
||||
: QSettings(filename, format, parent) {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2024-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -21,14 +21,14 @@
|
||||
#define SETTINGS_H
|
||||
|
||||
#include <QSettings>
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
|
||||
class Settings : public QSettings {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Settings(QObject *parent = nullptr);
|
||||
explicit Settings(const QSettings::Scope scope, QObject *parent = nullptr);
|
||||
explicit Settings(const QString &filename, const Format format, QObject *parent = nullptr);
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/settings.h"
|
||||
|
||||
class SettingsProvider {
|
||||
public:
|
||||
@@ -60,7 +61,7 @@ class DefaultSettingsProvider : public SettingsProvider {
|
||||
void endArray() override;
|
||||
|
||||
private:
|
||||
QSettings backend_;
|
||||
Settings backend_;
|
||||
|
||||
Q_DISABLE_COPY(DefaultSettingsProvider)
|
||||
};
|
||||
|
||||
@@ -45,11 +45,11 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QUrl>
|
||||
#include <QIcon>
|
||||
#include <QStandardPaths>
|
||||
#include <QSqlRecord>
|
||||
|
||||
#include <taglib/tstring.h>
|
||||
|
||||
#include "core/standardpaths.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/enginemetadata.h"
|
||||
#include "utilities/strutils.h"
|
||||
@@ -249,7 +249,8 @@ const QStringList Song::kRejectedExtensions = QStringList() << u"tmp"_s
|
||||
<< u"z"_s
|
||||
<< u"zip"_s
|
||||
<< u"rar"_s
|
||||
<< u"wvc"_s;
|
||||
<< u"wvc"_s
|
||||
<< u"zst"_s;
|
||||
|
||||
struct Song::Private : public QSharedData {
|
||||
|
||||
@@ -1334,24 +1335,24 @@ QString Song::ImageCacheDir(const Source source) {
|
||||
|
||||
switch (source) {
|
||||
case Source::Collection:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/collectionalbumcovers"_s;
|
||||
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/collectionalbumcovers"_s;
|
||||
case Source::Subsonic:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/subsonicalbumcovers"_s;
|
||||
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/subsonicalbumcovers"_s;
|
||||
case Source::Tidal:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/tidalalbumcovers"_s;
|
||||
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/tidalalbumcovers"_s;
|
||||
case Source::Spotify:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/spotifyalbumcovers"_s;
|
||||
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/spotifyalbumcovers"_s;
|
||||
case Source::Qobuz:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/qobuzalbumcovers"_s;
|
||||
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/qobuzalbumcovers"_s;
|
||||
case Source::Device:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/devicealbumcovers"_s;
|
||||
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/devicealbumcovers"_s;
|
||||
case Source::LocalFile:
|
||||
case Source::CDDA:
|
||||
case Source::Stream:
|
||||
case Source::SomaFM:
|
||||
case Source::RadioParadise:
|
||||
case Source::Unknown:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + u"/albumcovers"_s;
|
||||
return StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + u"/albumcovers"_s;
|
||||
}
|
||||
|
||||
return QString();
|
||||
|
||||
@@ -281,7 +281,7 @@ SongLoader::Result SongLoader::LoadLocalAsync(const QString &filename) {
|
||||
// It's a CUE - create virtual tracks
|
||||
QFile cue(matching_cue);
|
||||
if (cue.open(QIODevice::ReadOnly)) {
|
||||
const SongList songs = cue_parser_->Load(&cue, matching_cue, QDir(filename.section(u'/', 0, -2)));
|
||||
const SongList songs = cue_parser_->Load(&cue, matching_cue, QDir(filename.section(u'/', 0, -2))).songs;
|
||||
cue.close();
|
||||
for (const Song &song : songs) {
|
||||
if (song.is_valid()) songs_ << song;
|
||||
@@ -348,7 +348,9 @@ void SongLoader::LoadPlaylist(ParserBase *parser, const QString &filename) {
|
||||
|
||||
QFile file(filename);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
songs_ = parser->Load(&file, filename, QFileInfo(filename).path());
|
||||
const ParserBase::LoadResult result = parser->Load(&file, filename, QFileInfo(filename).path());
|
||||
songs_ = result.songs;
|
||||
playlist_name_ = result.playlist_name;
|
||||
file.close();
|
||||
}
|
||||
else {
|
||||
@@ -424,7 +426,9 @@ void SongLoader::StopTypefind() {
|
||||
// Parse the playlist
|
||||
QBuffer buf(&buffer_);
|
||||
if (buf.open(QIODevice::ReadOnly)) {
|
||||
songs_ = parser_->Load(&buf);
|
||||
const ParserBase::LoadResult result = parser_->Load(&buf);
|
||||
songs_ = result.songs;
|
||||
playlist_name_ = result.playlist_name;
|
||||
buf.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ class SongLoader : public QObject {
|
||||
|
||||
const QUrl &url() const { return url_; }
|
||||
const SongList &songs() const { return songs_; }
|
||||
const QString &playlist_name() const { return playlist_name_; }
|
||||
|
||||
int timeout() const { return timeout_; }
|
||||
void set_timeout(int msec) { timeout_ = msec; }
|
||||
@@ -141,6 +142,7 @@ class SongLoader : public QObject {
|
||||
|
||||
QUrl url_;
|
||||
SongList songs_;
|
||||
QString playlist_name_;
|
||||
|
||||
const SharedPtr<UrlHandlers> url_handlers_;
|
||||
const SharedPtr<CollectionBackendInterface> collection_backend_;
|
||||
|
||||
48
src/core/sparkleupdater.h
Normal file
48
src/core/sparkleupdater.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SPARKLEUPDATER_H
|
||||
#define SPARKLEUPDATER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QAction;
|
||||
|
||||
#ifdef __OBJC__
|
||||
@class AppUpdaterDelegate;
|
||||
#endif
|
||||
|
||||
class SparkleUpdater : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SparkleUpdater(QAction *action_check_updates, QObject *parent = nullptr);
|
||||
|
||||
public Q_SLOTS:
|
||||
void CheckForUpdates();
|
||||
|
||||
private:
|
||||
#ifdef __OBJC__
|
||||
AppUpdaterDelegate *updater_delegate_;
|
||||
#else
|
||||
void *updater_delegate_;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // SPARKLEUPDATER_H
|
||||
79
src/core/sparkleupdater.mm
Normal file
79
src/core/sparkleupdater.mm
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <Sparkle/Sparkle.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QAction>
|
||||
|
||||
#include "sparkleupdater.h"
|
||||
|
||||
@interface AppUpdaterDelegate : NSObject <SPUUpdaterDelegate>
|
||||
|
||||
@property(nonatomic, assign) SPUStandardUpdaterController *updater_controller;
|
||||
|
||||
@end
|
||||
|
||||
@implementation AppUpdaterDelegate
|
||||
|
||||
- (void)observeCanCheckForUpdatesWithAction:(QAction*)action_check_updates {
|
||||
[_updater_controller.updater addObserver:self forKeyPath:NSStringFromSelector(@selector(canCheckForUpdates)) options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) context:(void*)action_check_updates];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id>*)change context:(void*)context {
|
||||
|
||||
if ([keyPath isEqualToString:NSStringFromSelector(@selector(canCheckForUpdates))]) {
|
||||
QAction *action = reinterpret_cast<QAction*>(context);
|
||||
action->setEnabled(_updater_controller.updater.canCheckForUpdates);
|
||||
}
|
||||
else {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
|
||||
@autoreleasepool {
|
||||
[_updater_controller.updater removeObserver:self forKeyPath:NSStringFromSelector(@selector(canCheckForUpdates))];
|
||||
}
|
||||
|
||||
[super dealloc];
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
SparkleUpdater::SparkleUpdater(QAction *action_check_updates, QObject *parent) : QObject(parent) {
|
||||
|
||||
@autoreleasepool {
|
||||
updater_delegate_ = [[AppUpdaterDelegate alloc] init];
|
||||
updater_delegate_.updater_controller = [[SPUStandardUpdaterController alloc] initWithStartingUpdater:YES updaterDelegate:updater_delegate_ userDriverDelegate:nil];
|
||||
[updater_delegate_ observeCanCheckForUpdatesWithAction:action_check_updates];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SparkleUpdater::CheckForUpdates() {
|
||||
|
||||
@autoreleasepool {
|
||||
[updater_delegate_.updater_controller checkForUpdates:nil];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,7 +55,7 @@ void StandardItemIconLoader::SetModel(QAbstractItemModel *model) {
|
||||
|
||||
}
|
||||
|
||||
void StandardItemIconLoader::LoadIcon(const QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item) {
|
||||
void StandardItemIconLoader::LoadAlbumCover(const QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item) {
|
||||
|
||||
AlbumCoverLoaderOptions cover_options(AlbumCoverLoaderOptions::Option::ScaledImage);
|
||||
cover_options.desired_scaled_size = QSize(16, 16);
|
||||
@@ -64,7 +64,7 @@ void StandardItemIconLoader::LoadIcon(const QUrl &art_automatic, const QUrl &art
|
||||
|
||||
}
|
||||
|
||||
void StandardItemIconLoader::LoadIcon(const Song &song, QStandardItem *for_item) {
|
||||
void StandardItemIconLoader::LoadAlbumCover(const Song &song, QStandardItem *for_item) {
|
||||
|
||||
AlbumCoverLoaderOptions cover_options(AlbumCoverLoaderOptions::Option::ScaledImage);
|
||||
cover_options.desired_scaled_size = QSize(16, 16);
|
||||
|
||||
@@ -46,8 +46,8 @@ class StandardItemIconLoader : public QObject {
|
||||
|
||||
void SetModel(QAbstractItemModel *model);
|
||||
|
||||
void LoadIcon(const QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item);
|
||||
void LoadIcon(const Song &song, QStandardItem *for_item);
|
||||
void LoadAlbumCover(const QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item);
|
||||
void LoadAlbumCover(const Song &song, QStandardItem *for_item);
|
||||
|
||||
private Q_SLOTS:
|
||||
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
||||
|
||||
82
src/core/standardpaths.cpp
Normal file
82
src/core/standardpaths.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "standardpaths.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
void StandardPaths::AppendOrganizationAndApplication(QString &path) {
|
||||
|
||||
const QString organization_name = QCoreApplication::organizationName().toLower();
|
||||
if (!organization_name.isEmpty()) {
|
||||
path += u'/' + organization_name;
|
||||
}
|
||||
const QString application_name = QCoreApplication::applicationName().toLower();
|
||||
if (!application_name.isEmpty()) {
|
||||
path += u'/' + application_name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString StandardPaths::WritableLocation(const StandardLocation type) {
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||
switch (type) {
|
||||
case StandardLocation::CacheLocation:
|
||||
case StandardLocation::GenericCacheLocation:{
|
||||
QString cache_location = qEnvironmentVariable("XDG_CACHE_HOME");
|
||||
if (!cache_location.startsWith(u'/')) {
|
||||
cache_location.clear();
|
||||
}
|
||||
if (cache_location.isEmpty()) {
|
||||
cache_location = QDir::homePath() + "/.cache"_L1;
|
||||
}
|
||||
if (type == QStandardPaths::CacheLocation) {
|
||||
AppendOrganizationAndApplication(cache_location);
|
||||
}
|
||||
return cache_location;
|
||||
}
|
||||
case StandardLocation::AppDataLocation:
|
||||
case StandardLocation::AppLocalDataLocation:
|
||||
case StandardLocation::GenericDataLocation:{
|
||||
QString data_location = qEnvironmentVariable("XDG_DATA_HOME");
|
||||
if (!data_location.startsWith(u'/')) {
|
||||
data_location.clear();
|
||||
}
|
||||
if (data_location.isEmpty()) {
|
||||
data_location = QDir::homePath() + "/.local/share"_L1;
|
||||
}
|
||||
if (type == StandardLocation::AppDataLocation || type == StandardLocation::AppLocalDataLocation) {
|
||||
AppendOrganizationAndApplication(data_location);
|
||||
}
|
||||
return data_location;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
return QStandardPaths::writableLocation(type);
|
||||
|
||||
}
|
||||
36
src/core/standardpaths.h
Normal file
36
src/core/standardpaths.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STANDARDPATHS_H
|
||||
#define STANDARDPATHS_H
|
||||
|
||||
#include <QStandardPaths>
|
||||
|
||||
class StandardPaths {
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
using StandardLocation = QStandardPaths::StandardLocation;
|
||||
static QString WritableLocation(const StandardLocation type);
|
||||
|
||||
private:
|
||||
static void AppendOrganizationAndApplication(QString &path);
|
||||
};
|
||||
|
||||
#endif // STANDARDPATHS_H
|
||||
@@ -34,7 +34,7 @@ using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
TaskManager::TaskManager(QObject *parent) : QObject(parent), next_task_id_(1) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QCoreApplication>
|
||||
#include <QStandardPaths>
|
||||
#include <QIODevice>
|
||||
#include <QMutex>
|
||||
#include <QNetworkDiskCache>
|
||||
@@ -32,6 +31,7 @@
|
||||
#include <QAbstractNetworkCache>
|
||||
#include <QUrl>
|
||||
|
||||
#include "standardpaths.h"
|
||||
#include "threadsafenetworkdiskcache.h"
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
@@ -48,9 +48,9 @@ ThreadSafeNetworkDiskCache::ThreadSafeNetworkDiskCache(QObject *parent) : QAbstr
|
||||
if (!sCache) {
|
||||
sCache = new QNetworkDiskCache;
|
||||
#ifdef Q_OS_WIN32
|
||||
sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + u"/strawberry/networkcache"_s);
|
||||
sCache->setCacheDirectory(StandardPaths::WritableLocation(StandardPaths::StandardLocation::TempLocation) + u"/strawberry/networkcache"_s);
|
||||
#else
|
||||
sCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u"/networkcache"_s);
|
||||
sCache->setCacheDirectory(StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + u"/networkcache"_s);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ AlbumCoverLoader::AlbumCoverLoader(const SharedPtr<TagReaderClient> tagreader_cl
|
||||
load_image_async_id_(1),
|
||||
original_thread_(nullptr) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
|
||||
original_thread_ = thread();
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
#include <QObject>
|
||||
#include <QDir>
|
||||
#include <QImage>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/standardpaths.h"
|
||||
#include "core/song.h"
|
||||
#include "core/temporaryfile.h"
|
||||
#include "albumcoverloader.h"
|
||||
@@ -42,10 +42,10 @@ using namespace Qt::Literals::StringLiterals;
|
||||
CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(const SharedPtr<AlbumCoverLoader> albumcover_loader, QObject *parent)
|
||||
: QObject(parent),
|
||||
albumcover_loader_(albumcover_loader),
|
||||
temp_file_pattern_(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + u"/strawberry-cover-XXXXXX.jpg"_s),
|
||||
temp_file_pattern_(StandardPaths::WritableLocation(StandardPaths::StandardLocation::TempLocation) + u"/strawberry-cover-XXXXXX.jpg"_s),
|
||||
id_(0) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
|
||||
options_.options = AlbumCoverLoaderOptions::Option::RawImageData | AlbumCoverLoaderOptions::Option::OriginalImage | AlbumCoverLoaderOptions::Option::ScaledImage;
|
||||
options_.desired_scaled_size = QSize(120, 120);
|
||||
|
||||
@@ -52,10 +52,12 @@
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
using std::make_shared;
|
||||
|
||||
const char *DiscogsCoverProvider::kUrlSearch = "https://api.discogs.com/database/search";
|
||||
const char *DiscogsCoverProvider::kAccessKeyB64 = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk=";
|
||||
const char *DiscogsCoverProvider::kSecretKeyB64 = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI=";
|
||||
const int DiscogsCoverProvider::kRequestsDelay = 1000;
|
||||
namespace {
|
||||
constexpr char kUrlSearch[] = "https://api.discogs.com/database/search";
|
||||
constexpr char kAccessKeyB64[] = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk=";
|
||||
constexpr char kSecretKeyB64[] = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI=";
|
||||
constexpr int kRequestsDelay = 1000;
|
||||
} // namespace
|
||||
|
||||
DiscogsCoverProvider::DiscogsCoverProvider(const SharedPtr<NetworkAccessManager> network, QObject *parent)
|
||||
: JsonCoverProvider(u"Discogs"_s, false, false, 0.0, false, false, network, parent),
|
||||
|
||||
@@ -89,11 +89,6 @@ class DiscogsCoverProvider : public JsonCoverProvider {
|
||||
void HandleReleaseReply(QNetworkReply *reply, const int search_id, const quint64 release_id);
|
||||
|
||||
private:
|
||||
static const char *kUrlSearch;
|
||||
static const char *kAccessKeyB64;
|
||||
static const char *kSecretKeyB64;
|
||||
static const int kRequestsDelay;
|
||||
|
||||
QTimer *timer_flush_requests_;
|
||||
QQueue<SharedPtr<DiscogsCoverSearchContext>> queue_search_requests_;
|
||||
QQueue<DiscogsCoverReleaseContext> queue_release_requests_;
|
||||
|
||||
@@ -24,9 +24,6 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cdio/cdio.h>
|
||||
#include <gst/audio/gstaudiocdsrc.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <cdio/cdio.h>
|
||||
#include <cdio/device.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QFileInfo>
|
||||
#include <QByteArray>
|
||||
@@ -30,10 +35,6 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QUrl>
|
||||
|
||||
// This must come after Qt includes
|
||||
#include <cdio/cdio.h>
|
||||
#include <cdio/device.h>
|
||||
|
||||
#include "cddalister.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
|
||||
@@ -24,10 +24,16 @@
|
||||
#include <memory>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gtypes.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include <cdio/cdio.h>
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/tag/tag.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
@@ -35,10 +41,6 @@
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include <cdio/cdio.h>
|
||||
#include <gst/gst.h>
|
||||
#include <gst/tag/tag.h>
|
||||
|
||||
#include "cddasongloader.h"
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
@@ -24,17 +24,19 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <cdio/types.h>
|
||||
#include <cdio/cdio.h>
|
||||
|
||||
#include <gst/gstelement.h>
|
||||
#include <gst/audio/gstaudiocdsrc.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
// These must come after Qt includes
|
||||
#include <cdio/types.h>
|
||||
#include <cdio/cdio.h>
|
||||
#include <gst/gstelement.h>
|
||||
#include <gst/audio/gstaudiocdsrc.h>
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#ifdef HAVE_MUSICBRAINZ
|
||||
|
||||
@@ -49,7 +49,7 @@ DeviceDatabaseBackend::DeviceDatabaseBackend(QObject *parent)
|
||||
db_(nullptr),
|
||||
original_thread_(nullptr) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
|
||||
original_thread_ = thread();
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ const DeviceInfo::Backend *DeviceInfo::BestBackend() const {
|
||||
|
||||
}
|
||||
|
||||
void DeviceInfo::LoadIcon(const QVariantList &icons, const QString &name_hint) {
|
||||
void DeviceInfo::SetIcon(const QVariantList &icons, const QString &name_hint) {
|
||||
|
||||
icon_name_ = "device"_L1;
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ class DeviceInfo : public SimpleTreeItem<DeviceInfo> {
|
||||
DeviceDatabaseBackend::Device SaveToDb() const;
|
||||
|
||||
// Tries to load a good icon for the device. Sets icon_name_ and icon_.
|
||||
void LoadIcon(const QVariantList &icons, const QString &name_hint);
|
||||
void SetIcon(const QVariantList &icons, const QString &name_hint);
|
||||
|
||||
// Gets the best backend available (the one with the highest priority)
|
||||
const Backend *BestBackend() const;
|
||||
|
||||
@@ -45,7 +45,7 @@ DeviceLister::DeviceLister(QObject *parent)
|
||||
original_thread_(nullptr),
|
||||
next_mount_request_id_(0) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
|
||||
original_thread_ = thread();
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ DeviceManager::DeviceManager(const SharedPtr<TaskManager> task_manager,
|
||||
albumcover_loader_(albumcover_loader),
|
||||
not_connected_overlay_(IconLoader::Load(u"edit-delete"_s)) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
|
||||
thread_pool_.setMaxThreadCount(1);
|
||||
QObject::connect(&*task_manager, &TaskManager::TasksChanged, this, &DeviceManager::TasksChanged);
|
||||
@@ -256,7 +256,7 @@ void DeviceManager::AddDeviceFromDB(DeviceInfo *info) {
|
||||
for (const QString &icon_name : icon_names) {
|
||||
icons << icon_name;
|
||||
}
|
||||
info->LoadIcon(icons, info->friendly_name_);
|
||||
info->SetIcon(icons, info->friendly_name_);
|
||||
|
||||
DeviceInfo *existing = FindEquivalentDevice(info);
|
||||
if (existing) {
|
||||
@@ -477,7 +477,7 @@ void DeviceManager::PhysicalDeviceAdded(const QString &id) {
|
||||
if (info->database_id_ == -1 && info->BestBackend() && info->BestBackend()->lister_ == lister) {
|
||||
info->friendly_name_ = lister->MakeFriendlyName(id);
|
||||
info->size_ = lister->DeviceCapacity(id);
|
||||
info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_);
|
||||
info->SetIcon(lister->DeviceIcons(id), info->friendly_name_);
|
||||
}
|
||||
QModelIndex idx = ItemToIndex(info);
|
||||
if (idx.isValid()) Q_EMIT dataChanged(idx, idx);
|
||||
@@ -488,7 +488,7 @@ void DeviceManager::PhysicalDeviceAdded(const QString &id) {
|
||||
info->backends_ << DeviceInfo::Backend(lister, id);
|
||||
info->friendly_name_ = lister->MakeFriendlyName(id);
|
||||
info->size_ = lister->DeviceCapacity(id);
|
||||
info->LoadIcon(lister->DeviceIcons(id), info->friendly_name_);
|
||||
info->SetIcon(lister->DeviceIcons(id), info->friendly_name_);
|
||||
beginInsertRows(ItemToIndex(root_), static_cast<int>(devices_.count()), static_cast<int>(devices_.count()));
|
||||
devices_ << info;
|
||||
endInsertRows();
|
||||
@@ -808,7 +808,7 @@ void DeviceManager::RemoveFromDB(DeviceInfo *info, const QModelIndex &idx) {
|
||||
const QString id = info->BestBackend()->unique_id_;
|
||||
|
||||
info->friendly_name_ = info->BestBackend()->lister_->MakeFriendlyName(id);
|
||||
info->LoadIcon(info->BestBackend()->lister_->DeviceIcons(id), info->friendly_name_);
|
||||
info->SetIcon(info->BestBackend()->lister_->DeviceIcons(id), info->friendly_name_);
|
||||
Q_EMIT dataChanged(idx, idx);
|
||||
}
|
||||
|
||||
@@ -822,7 +822,7 @@ void DeviceManager::SetDeviceOptions(const QModelIndex &idx, const QString &frie
|
||||
if (!info) return;
|
||||
|
||||
info->friendly_name_ = friendly_name;
|
||||
info->LoadIcon(QVariantList() << icon_name, friendly_name);
|
||||
info->SetIcon(QVariantList() << icon_name, friendly_name);
|
||||
info->transcode_mode_ = mode;
|
||||
info->transcode_format_ = format;
|
||||
|
||||
|
||||
@@ -484,16 +484,22 @@ void GioLister::DeviceInfo::ReadMountInfo(GMount *mount) {
|
||||
}
|
||||
|
||||
#ifdef HAVE_GIO_UNIX
|
||||
#ifdef GLIB_VERSION_2_84
|
||||
GUnixMountEntry *unix_mount = g_unix_mount_entry_for(g_file_get_path(root), nullptr);
|
||||
#else
|
||||
GUnixMountEntry *unix_mount = g_unix_mount_for(g_file_get_path(root), nullptr);
|
||||
#endif
|
||||
if (unix_mount) {
|
||||
// the GIO's definition of system internal mounts include filesystems like
|
||||
// autofs, tmpfs, sysfs, etc, and various system directories, including the root,
|
||||
// /boot, /var, /home, etc.
|
||||
// The GIO's definition of system internal mounts include filesystems like autofs, tmpfs, sysfs, etc,
|
||||
// and various system directories, including the root, /boot, /var, /home, etc.
|
||||
#ifdef GLIB_VERSION_2_84
|
||||
is_system_internal = g_unix_mount_entry_is_system_internal(unix_mount);
|
||||
g_unix_mount_entry_free(unix_mount);
|
||||
#else
|
||||
is_system_internal = g_unix_mount_is_system_internal(unix_mount);
|
||||
g_unix_mount_free(unix_mount);
|
||||
// Although checking most of the internal mounts is safe,
|
||||
// we really don't want to touch autofs filesystems, as that would
|
||||
// trigger automounting.
|
||||
#endif
|
||||
// Although checking most of the internal mounts is safe, we really don't want to touch autofs filesystems, as that would trigger automounting.
|
||||
if (is_system_internal) return;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/standardpaths.h"
|
||||
#include "core/temporaryfile.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/database.h"
|
||||
@@ -211,9 +211,9 @@ bool GPodDevice::CopyToStorage(const CopyJob &job, QString &error_text) {
|
||||
bool result = false;
|
||||
if (!job.cover_image_.isNull()) {
|
||||
#ifdef Q_OS_LINUX
|
||||
QString temp_path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u"/organize"_s;
|
||||
QString temp_path = StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + u"/organize"_s;
|
||||
#else
|
||||
QString temp_path = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
|
||||
QString temp_path = StandardPaths::WritableLocation(StandardPaths::StandardLocation::TempLocation);
|
||||
#endif
|
||||
if (!QDir(temp_path).exists()) QDir().mkpath(temp_path);
|
||||
SharedPtr<TemporaryFile> cover_file = make_shared<TemporaryFile>(temp_path + u"/track-albumcover-XXXXXX.jpg"_s);
|
||||
|
||||
@@ -39,6 +39,7 @@ using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
ErrorDialog::ErrorDialog(QWidget *parent)
|
||||
: QDialog(parent),
|
||||
parent_(parent),
|
||||
ui_(new Ui_ErrorDialog) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
@@ -66,8 +67,11 @@ void ErrorDialog::ShowMessage(const QString &message) {
|
||||
UpdateContent();
|
||||
|
||||
show();
|
||||
raise();
|
||||
activateWindow();
|
||||
|
||||
if (parent_ && parent_->isMaximized()) {
|
||||
raise();
|
||||
activateWindow();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ class ErrorDialog : public QDialog {
|
||||
private:
|
||||
void UpdateContent();
|
||||
|
||||
QWidget *parent_;
|
||||
Ui_ErrorDialog *ui_;
|
||||
|
||||
QStringList current_messages_;
|
||||
|
||||
@@ -53,7 +53,7 @@ using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
DeviceFinders::DeviceFinders(QObject *parent) : QObject(parent) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -49,8 +49,8 @@ EngineBase::EngineBase(QObject *parent)
|
||||
exclusive_mode_(false),
|
||||
volume_control_(true),
|
||||
volume_(100),
|
||||
beginning_nanosec_(0),
|
||||
end_nanosec_(0),
|
||||
beginning_offset_nanosec_(0),
|
||||
end_offset_nanosec_(0),
|
||||
ebur128_loudness_normalizing_gain_db_(0.0),
|
||||
scope_(kScopeSize),
|
||||
buffering_(false),
|
||||
@@ -84,15 +84,15 @@ EngineBase::EngineBase(QObject *parent)
|
||||
|
||||
EngineBase::~EngineBase() = default;
|
||||
|
||||
bool EngineBase::Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags track_change_flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||
bool EngineBase::Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags track_change_flags, const bool force_stop_at_end, const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||
|
||||
Q_UNUSED(track_change_flags)
|
||||
Q_UNUSED(force_stop_at_end);
|
||||
|
||||
media_url_ = media_url;
|
||||
stream_url_ = stream_url;
|
||||
beginning_nanosec_ = beginning_nanosec;
|
||||
end_nanosec_ = end_nanosec;
|
||||
beginning_offset_nanosec_ = beginning_offset_nanosec;
|
||||
end_offset_nanosec_ = end_offset_nanosec;
|
||||
|
||||
ebur128_loudness_normalizing_gain_db_ = 0.0;
|
||||
if (ebur128_loudness_normalization_ && ebur128_integrated_loudness_lufs) {
|
||||
@@ -112,9 +112,9 @@ bool EngineBase::Load(const QUrl &media_url, const QUrl &stream_url, const Track
|
||||
|
||||
}
|
||||
|
||||
bool EngineBase::Play(const QUrl &media_url, const QUrl &stream_url, const bool pause, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const quint64 offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||
bool EngineBase::Play(const QUrl &media_url, const QUrl &stream_url, const bool pause, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const quint64 offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||
|
||||
if (!Load(media_url, stream_url, flags, force_stop_at_end, beginning_nanosec, end_nanosec, ebur128_integrated_loudness_lufs)) {
|
||||
if (!Load(media_url, stream_url, flags, force_stop_at_end, beginning_offset_nanosec, end_offset_nanosec, ebur128_integrated_loudness_lufs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ class EngineBase : public QObject {
|
||||
virtual bool Init() = 0;
|
||||
virtual State state() const = 0;
|
||||
virtual void StartPreloading(const QUrl&, const QUrl&, const bool, const qint64, const qint64) {}
|
||||
virtual bool Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags track_change_flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs);
|
||||
virtual bool Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags track_change_flags, const bool force_stop_at_end, const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs);
|
||||
virtual bool Play(const bool pause, const quint64 offset_nanosec) = 0;
|
||||
virtual void Stop(const bool stop_after = false) = 0;
|
||||
virtual void Pause() = 0;
|
||||
@@ -106,9 +106,9 @@ class EngineBase : public QObject {
|
||||
|
||||
// Sets new values for the beginning and end markers of the currently playing song.
|
||||
// This doesn't change the state of engine or the stream's current position.
|
||||
virtual void RefreshMarkers(const quint64 beginning_nanosec, const qint64 end_nanosec) {
|
||||
beginning_nanosec_ = beginning_nanosec;
|
||||
end_nanosec_ = end_nanosec;
|
||||
virtual void RefreshMarkers(const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec) {
|
||||
beginning_offset_nanosec_ = beginning_offset_nanosec;
|
||||
end_offset_nanosec_ = end_offset_nanosec;
|
||||
}
|
||||
|
||||
virtual OutputDetailsList GetOutputsList() const = 0;
|
||||
@@ -120,7 +120,7 @@ class EngineBase : public QObject {
|
||||
|
||||
// Plays a media stream represented with the URL 'u' from the given 'beginning' to the given 'end' (usually from 0 to a song's length).
|
||||
// Both markers should be passed in nanoseconds. 'end' can be negative, indicating that the real length of 'u' stream is unknown.
|
||||
bool Play(const QUrl &media_url, const QUrl &stream_url, const bool pause, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const quint64 offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs);
|
||||
bool Play(const QUrl &media_url, const QUrl &stream_url, const bool pause, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const quint64 offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs);
|
||||
void SetVolume(const uint volume);
|
||||
|
||||
public Q_SLOTS:
|
||||
@@ -179,8 +179,8 @@ class EngineBase : public QObject {
|
||||
bool exclusive_mode_;
|
||||
bool volume_control_;
|
||||
uint volume_;
|
||||
quint64 beginning_nanosec_;
|
||||
qint64 end_nanosec_;
|
||||
quint64 beginning_offset_nanosec_;
|
||||
qint64 end_offset_nanosec_;
|
||||
QUrl media_url_;
|
||||
QUrl stream_url_;
|
||||
double ebur128_loudness_normalizing_gain_db_;
|
||||
|
||||
@@ -62,7 +62,6 @@
|
||||
#include "gstbufferconsumer.h"
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
using std::make_shared;
|
||||
|
||||
#ifdef __clang__
|
||||
# pragma clang diagnostic push
|
||||
@@ -177,13 +176,13 @@ EngineBase::State GstEngine::state() const {
|
||||
|
||||
}
|
||||
|
||||
void GstEngine::StartPreloading(const QUrl &media_url, const QUrl &stream_url, const bool force_stop_at_end, const qint64 beginning_nanosec, const qint64 end_nanosec) {
|
||||
void GstEngine::StartPreloading(const QUrl &media_url, const QUrl &stream_url, const bool force_stop_at_end, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec) {
|
||||
|
||||
const QByteArray gst_url = FixupUrl(stream_url);
|
||||
|
||||
// No crossfading, so we can just queue the new URL in the existing pipeline and get gapless playback (hopefully)
|
||||
if (current_pipeline_) {
|
||||
current_pipeline_->PrepareNextUrl(media_url, stream_url, gst_url, beginning_nanosec, force_stop_at_end ? end_nanosec : 0);
|
||||
current_pipeline_->PrepareNextUrl(media_url, stream_url, gst_url, beginning_offset_nanosec, force_stop_at_end ? end_offset_nanosec : 0);
|
||||
// Add request to discover the stream
|
||||
if (discoverer_ && media_url.scheme() != u"spotify"_s) {
|
||||
if (!gst_discoverer_discover_uri_async(discoverer_, gst_url.constData())) {
|
||||
@@ -194,9 +193,9 @@ void GstEngine::StartPreloading(const QUrl &media_url, const QUrl &stream_url, c
|
||||
|
||||
}
|
||||
|
||||
bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||
bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||
|
||||
EngineBase::Load(media_url, stream_url, change, force_stop_at_end, beginning_nanosec, end_nanosec, ebur128_integrated_loudness_lufs);
|
||||
EngineBase::Load(media_url, stream_url, change, force_stop_at_end, beginning_offset_nanosec, end_offset_nanosec, ebur128_integrated_loudness_lufs);
|
||||
|
||||
const QByteArray gst_url = FixupUrl(stream_url);
|
||||
|
||||
@@ -215,7 +214,7 @@ bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const Engine
|
||||
}
|
||||
}
|
||||
|
||||
GstEnginePipelinePtr pipeline = CreatePipeline(media_url, stream_url, gst_url, force_stop_at_end ? end_nanosec : 0, ebur128_loudness_normalizing_gain_db_);
|
||||
GstEnginePipelinePtr pipeline = CreatePipeline(media_url, stream_url, gst_url, static_cast<qint64>(beginning_offset_nanosec), force_stop_at_end ? end_offset_nanosec : 0, ebur128_loudness_normalizing_gain_db_);
|
||||
if (!pipeline) return false;
|
||||
|
||||
GstEnginePipelinePtr old_pipeline = current_pipeline_;
|
||||
@@ -264,7 +263,17 @@ bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const Engine
|
||||
|
||||
bool GstEngine::Play(const bool pause, const quint64 offset_nanosec) {
|
||||
|
||||
if (!current_pipeline_ || current_pipeline_->is_buffering() || current_pipeline_->state() == GstState::GST_STATE_PLAYING) return false;
|
||||
if (!current_pipeline_ || current_pipeline_->is_buffering()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (current_pipeline_->state() == GstState::GST_STATE_PLAYING) {
|
||||
if (offset_nanosec != 0 || beginning_offset_nanosec_ != 0) {
|
||||
Seek(offset_nanosec);
|
||||
PlayDone(GST_STATE_CHANGE_SUCCESS, false, offset_nanosec, current_pipeline_->id());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (OldExclusivePipelineActive()) {
|
||||
qLog(Debug) << "Delaying play because a exclusive pipeline is already active...";
|
||||
@@ -289,7 +298,7 @@ bool GstEngine::Play(const bool pause, const quint64 offset_nanosec) {
|
||||
watcher->deleteLater();
|
||||
PlayDone(ret, pause, offset_nanosec, pipeline_id);
|
||||
});
|
||||
QFuture<GstStateChangeReturn> future = current_pipeline_->Play(pause, beginning_nanosec_ + offset_nanosec);
|
||||
QFuture<GstStateChangeReturn> future = current_pipeline_->Play(pause, beginning_offset_nanosec_ + offset_nanosec);
|
||||
watcher->setFuture(future);
|
||||
|
||||
return true;
|
||||
@@ -306,7 +315,7 @@ void GstEngine::Stop(const bool stop_after) {
|
||||
|
||||
media_url_.clear();
|
||||
stream_url_.clear(); // To ensure we return Empty from state()
|
||||
beginning_nanosec_ = end_nanosec_ = 0;
|
||||
beginning_offset_nanosec_ = end_offset_nanosec_ = 0;
|
||||
|
||||
// Check if we started a fade out. If it isn't finished yet and the user pressed stop, we cancel the fader and just stop the playback.
|
||||
if (fadeout_pause_pipeline_) {
|
||||
@@ -384,7 +393,7 @@ void GstEngine::Seek(const quint64 offset_nanosec) {
|
||||
|
||||
if (!current_pipeline_) return;
|
||||
|
||||
seek_pos_ = beginning_nanosec_ + offset_nanosec;
|
||||
seek_pos_ = beginning_offset_nanosec_ + offset_nanosec;
|
||||
waiting_to_seek_ = true;
|
||||
|
||||
if (!seek_timer_->isActive()) {
|
||||
@@ -402,7 +411,7 @@ qint64 GstEngine::position_nanosec() const {
|
||||
|
||||
if (!current_pipeline_) return 0;
|
||||
|
||||
const qint64 result = current_pipeline_->position() - static_cast<qint64>(beginning_nanosec_);
|
||||
const qint64 result = current_pipeline_->position() - static_cast<qint64>(beginning_offset_nanosec_);
|
||||
return std::max(0LL, result);
|
||||
|
||||
}
|
||||
@@ -411,7 +420,7 @@ qint64 GstEngine::length_nanosec() const {
|
||||
|
||||
if (!current_pipeline_) return 0;
|
||||
|
||||
const qint64 result = end_nanosec_ - static_cast<qint64>(beginning_nanosec_);
|
||||
const qint64 result = end_offset_nanosec_ - static_cast<qint64>(beginning_offset_nanosec_);
|
||||
|
||||
if (result > 0) {
|
||||
return result;
|
||||
@@ -742,7 +751,7 @@ void GstEngine::PlayDone(const GstStateChangeReturn ret, const bool pause, const
|
||||
stream_url = old_pipeline->stream_url();
|
||||
stream_url.detach();
|
||||
}
|
||||
current_pipeline_ = CreatePipeline(media_url, stream_url, redirect_url, end_nanosec_, old_pipeline->ebur128_loudness_normalizing_gain_db());
|
||||
current_pipeline_ = CreatePipeline(media_url, stream_url, redirect_url, beginning_offset_nanosec_, end_offset_nanosec_, old_pipeline->ebur128_loudness_normalizing_gain_db());
|
||||
FinishPipeline(old_pipeline);
|
||||
Play(pause, offset_nanosec);
|
||||
return;
|
||||
@@ -761,7 +770,7 @@ void GstEngine::PlayDone(const GstStateChangeReturn ret, const bool pause, const
|
||||
|
||||
Q_EMIT StateChanged(pause ? State::Paused : State::Playing);
|
||||
|
||||
// We've successfully started playing a media stream with this url
|
||||
// We've successfully started playing a media stream with this URL
|
||||
Q_EMIT ValidSongRequested(stream_url_);
|
||||
|
||||
}
|
||||
@@ -887,7 +896,7 @@ void GstEngine::StopTimers() {
|
||||
|
||||
GstEnginePipelinePtr GstEngine::CreatePipeline() {
|
||||
|
||||
GstEnginePipelinePtr pipeline = make_shared<GstEnginePipeline>();
|
||||
GstEnginePipelinePtr pipeline = GstEnginePipelinePtr(new GstEnginePipeline);
|
||||
pipeline->set_output_device(output_, device_);
|
||||
pipeline->set_exclusive_mode(exclusive_mode_);
|
||||
pipeline->set_volume_enabled(volume_control_);
|
||||
@@ -926,11 +935,11 @@ GstEnginePipelinePtr GstEngine::CreatePipeline() {
|
||||
|
||||
}
|
||||
|
||||
GstEnginePipelinePtr GstEngine::CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db) {
|
||||
GstEnginePipelinePtr GstEngine::CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const double ebur128_loudness_normalizing_gain_db) {
|
||||
|
||||
GstEnginePipelinePtr ret = CreatePipeline();
|
||||
QString error;
|
||||
if (!ret->InitFromUrl(media_url, stream_url, gst_url, end_nanosec, ebur128_loudness_normalizing_gain_db, error)) {
|
||||
if (!ret->InitFromUrl(media_url, stream_url, gst_url, beginning_offset_nanosec, end_offset_nanosec, ebur128_loudness_normalizing_gain_db, error)) {
|
||||
ret.reset();
|
||||
Q_EMIT Error(error);
|
||||
Q_EMIT StateChanged(State::Error);
|
||||
|
||||
@@ -60,8 +60,8 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
|
||||
|
||||
bool Init() override;
|
||||
State state() const override;
|
||||
void StartPreloading(const QUrl &media_url, const QUrl &stream_url, const bool force_stop_at_end, const qint64 beginning_nanosec, const qint64 end_nanosec) override;
|
||||
bool Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) override;
|
||||
void StartPreloading(const QUrl &media_url, const QUrl &stream_url, const bool force_stop_at_end, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec) override;
|
||||
bool Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) override;
|
||||
bool Play(const bool pause, const quint64 offset_nanosec) override;
|
||||
void Stop(const bool stop_after = false) override;
|
||||
void Pause() override;
|
||||
@@ -133,7 +133,7 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
|
||||
void StopTimers();
|
||||
|
||||
GstEnginePipelinePtr CreatePipeline();
|
||||
GstEnginePipelinePtr CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db);
|
||||
GstEnginePipelinePtr CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const double ebur128_loudness_normalizing_gain_db);
|
||||
|
||||
void FinishPipeline(GstEnginePipelinePtr pipeline);
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
|
||||
ebur128_loudness_normalizing_gain_db_(0.0),
|
||||
segment_start_(0),
|
||||
segment_start_received_(false),
|
||||
beginning_offset_nanosec_(-1),
|
||||
end_offset_nanosec_(-1),
|
||||
next_beginning_offset_nanosec_(-1),
|
||||
next_end_offset_nanosec_(-1),
|
||||
@@ -173,7 +174,11 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
|
||||
logged_unsupported_analyzer_format_(false),
|
||||
about_to_finish_(false),
|
||||
finish_requested_(false),
|
||||
finished_(false) {
|
||||
finished_(false),
|
||||
set_state_in_progress_(0),
|
||||
set_state_async_in_progress_(0),
|
||||
last_set_state_in_progress_(GST_STATE_VOID_PENDING),
|
||||
last_set_state_async_in_progress_(GST_STATE_VOID_PENDING) {
|
||||
|
||||
eq_band_gains_.reserve(kEqBandCount);
|
||||
for (int i = 0; i < kEqBandCount; ++i) eq_band_gains_ << 0;
|
||||
@@ -418,18 +423,23 @@ bool GstEnginePipeline::Finish() {
|
||||
|
||||
Disconnect();
|
||||
|
||||
if (IsStateNull()) {
|
||||
if (IsStateNull() && set_state_async_in_progress_ == 0 && set_state_in_progress_ == 0) {
|
||||
finished_ = true;
|
||||
}
|
||||
else {
|
||||
SetState(GST_STATE_NULL);
|
||||
if (set_state_async_in_progress_ > 0 && last_set_state_async_in_progress_ != GST_STATE_NULL) {
|
||||
SetStateAsync(GST_STATE_NULL);
|
||||
}
|
||||
else if ((!IsStateNull() || set_state_in_progress_ > 0) && last_set_state_in_progress_ != GST_STATE_NULL) {
|
||||
SetState(GST_STATE_NULL);
|
||||
}
|
||||
}
|
||||
|
||||
return finished_.value();
|
||||
|
||||
}
|
||||
|
||||
bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error) {
|
||||
bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error) {
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_url_);
|
||||
@@ -438,7 +448,8 @@ bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_ur
|
||||
gst_url_ = gst_url;
|
||||
}
|
||||
|
||||
end_offset_nanosec_ = end_nanosec;
|
||||
beginning_offset_nanosec_ = beginning_offset_nanosec;
|
||||
end_offset_nanosec_ = end_offset_nanosec;
|
||||
ebur128_loudness_normalizing_gain_db_ = ebur128_loudness_normalizing_gain_db;
|
||||
|
||||
guint version_major = 0, version_minor = 0, version_micro = 0, version_nano = 0;
|
||||
@@ -1328,6 +1339,7 @@ GstPadProbeReturn GstEnginePipeline::BufferProbeCallback(GstPad *pad, GstPadProb
|
||||
if (instance->end_offset_nanosec_.value() > 0 && end_time > instance->end_offset_nanosec_.value()) {
|
||||
if (instance->HasMatchingNextUrl() && instance->next_beginning_offset_nanosec_.value() == instance->end_offset_nanosec_.value()) {
|
||||
// The "next" song is actually the next segment of this file - so cheat and keep on playing, but just tell the Engine we've moved on.
|
||||
instance->beginning_offset_nanosec_ = instance->next_beginning_offset_nanosec_;
|
||||
instance->end_offset_nanosec_ = instance->next_end_offset_nanosec_;
|
||||
instance->next_media_url_.clear();
|
||||
instance->next_stream_url_.clear();
|
||||
@@ -1477,6 +1489,7 @@ void GstEnginePipeline::StreamStartMessageReceived() {
|
||||
next_media_url_.clear();
|
||||
next_gst_url_.clear();
|
||||
}
|
||||
beginning_offset_nanosec_ = next_beginning_offset_nanosec_;
|
||||
end_offset_nanosec_ = next_end_offset_nanosec_;
|
||||
next_beginning_offset_nanosec_ = 0;
|
||||
next_end_offset_nanosec_ = 0;
|
||||
@@ -1554,7 +1567,7 @@ void GstEnginePipeline::ErrorMessageReceived(GstMessage *msg) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN32
|
||||
// Ignore non-error received for directsoundsink: "IDirectSoundBuffer_GetStatus The operation completed successfully"
|
||||
if (code == GST_RESOURCE_ERROR_OPEN_WRITE && message.contains(QLatin1String("IDirectSoundBuffer_GetStatus The operation completed successfully."))) {
|
||||
return;
|
||||
@@ -1788,7 +1801,19 @@ bool GstEnginePipeline::IsStateNull() const {
|
||||
|
||||
void GstEnginePipeline::SetStateAsync(const GstState state) {
|
||||
|
||||
QMetaObject::invokeMethod(this, "SetState", Qt::QueuedConnection, Q_ARG(GstState, state));
|
||||
last_set_state_async_in_progress_ = state;
|
||||
++set_state_async_in_progress_;
|
||||
|
||||
QMetaObject::invokeMethod(this, "SetStateAsyncSlot", Qt::QueuedConnection, Q_ARG(GstState, state));
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::SetStateAsyncSlot(const GstState state) {
|
||||
|
||||
last_set_state_async_in_progress_ = GST_STATE_VOID_PENDING;
|
||||
--set_state_async_in_progress_;
|
||||
|
||||
SetState(state);
|
||||
|
||||
}
|
||||
|
||||
@@ -1796,6 +1821,9 @@ QFuture<GstStateChangeReturn> GstEnginePipeline::SetState(const GstState state)
|
||||
|
||||
qLog(Debug) << "Setting pipeline" << id() << "state to" << GstStateText(state);
|
||||
|
||||
last_set_state_in_progress_ = state;
|
||||
++set_state_in_progress_;
|
||||
|
||||
QFutureWatcher<GstStateChangeReturn> *watcher = new QFutureWatcher<GstStateChangeReturn>();
|
||||
QObject::connect(watcher, &QFutureWatcher<GstStateChangeReturn>::finished, this, [this, watcher, state]() {
|
||||
const GstStateChangeReturn state_change_return = watcher->result();
|
||||
@@ -1811,13 +1839,16 @@ QFuture<GstStateChangeReturn> GstEnginePipeline::SetState(const GstState state)
|
||||
|
||||
void GstEnginePipeline::SetStateFinishedSlot(const GstState state, const GstStateChangeReturn state_change_return) {
|
||||
|
||||
last_set_state_in_progress_ = GST_STATE_VOID_PENDING;
|
||||
--set_state_in_progress_;
|
||||
|
||||
switch (state_change_return) {
|
||||
case GST_STATE_CHANGE_SUCCESS:
|
||||
case GST_STATE_CHANGE_ASYNC:
|
||||
case GST_STATE_CHANGE_NO_PREROLL:
|
||||
qLog(Debug) << "Pipeline" << id() << "state successfully set to" << GstStateText(state);
|
||||
Q_EMIT SetStateFinished(state_change_return);
|
||||
if (!finished_.value() && finish_requested_.value()) {
|
||||
if (!finished_.value() && finish_requested_.value() && set_state_async_in_progress_ == 0 && set_state_in_progress_ == 0) {
|
||||
finished_ = true;
|
||||
Q_EMIT Finished();
|
||||
}
|
||||
@@ -2137,7 +2168,7 @@ bool GstEnginePipeline::HasMatchingNextUrl() const {
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::PrepareNextUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_nanosec, const qint64 end_nanosec) {
|
||||
void GstEnginePipeline::PrepareNextUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec) {
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_next_url_);
|
||||
@@ -2146,8 +2177,8 @@ void GstEnginePipeline::PrepareNextUrl(const QUrl &media_url, const QUrl &stream
|
||||
next_gst_url_ = gst_url;
|
||||
}
|
||||
|
||||
next_beginning_offset_nanosec_ = beginning_nanosec;
|
||||
next_end_offset_nanosec_ = end_nanosec;
|
||||
next_beginning_offset_nanosec_ = beginning_offset_nanosec;
|
||||
next_end_offset_nanosec_ = end_offset_nanosec;
|
||||
|
||||
if (about_to_finish_.value()) {
|
||||
SetNextUrl();
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "includes/mutex_protected.h"
|
||||
@@ -83,7 +84,7 @@ class GstEnginePipeline : public QObject {
|
||||
bool Finish();
|
||||
|
||||
// Creates the pipeline, returns false on error
|
||||
bool InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error);
|
||||
bool InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error);
|
||||
|
||||
// GstBufferConsumers get fed audio data. Thread-safe.
|
||||
void AddBufferConsumer(GstBufferConsumer *consumer);
|
||||
@@ -105,7 +106,7 @@ class GstEnginePipeline : public QObject {
|
||||
// If this is set then it will be loaded automatically when playback finishes for gapless playback
|
||||
bool HasNextUrl() const;
|
||||
bool HasMatchingNextUrl() const;
|
||||
void PrepareNextUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_nanosec, const qint64 end_nanosec);
|
||||
void PrepareNextUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 beginning_offset_nanosec, const qint64 end_offset_nanosec);
|
||||
void SetNextUrl();
|
||||
|
||||
void SetSourceDevice(const QString &device);
|
||||
@@ -197,6 +198,7 @@ class GstEnginePipeline : public QObject {
|
||||
void ProcessPendingSeek(const GstState state);
|
||||
|
||||
private Q_SLOTS:
|
||||
void SetStateAsyncSlot(const GstState state);
|
||||
void SetStateFinishedSlot(const GstState state, const GstStateChangeReturn state_change_return);
|
||||
void SetFaderVolume(const qreal volume);
|
||||
void FaderTimelineStateChanged(const QTimeLine::State state);
|
||||
@@ -287,6 +289,7 @@ class GstEnginePipeline : public QObject {
|
||||
mutex_protected<bool> segment_start_received_;
|
||||
GstSegment last_playbin_segment_{};
|
||||
|
||||
mutex_protected<qint64> beginning_offset_nanosec_;
|
||||
// If this is > 0 then the pipeline will be forced to stop when playback goes past this position.
|
||||
mutex_protected<qint64> end_offset_nanosec_;
|
||||
|
||||
@@ -367,8 +370,14 @@ class GstEnginePipeline : public QObject {
|
||||
mutex_protected<bool> about_to_finish_;
|
||||
mutex_protected<bool> finish_requested_;
|
||||
mutex_protected<bool> finished_;
|
||||
|
||||
mutex_protected<int> set_state_in_progress_;
|
||||
mutex_protected<int> set_state_async_in_progress_;
|
||||
|
||||
mutex_protected<GstState> last_set_state_in_progress_;
|
||||
mutex_protected<GstState> last_set_state_async_in_progress_;
|
||||
};
|
||||
|
||||
using GstEnginePipelinePtr = SharedPtr<GstEnginePipeline>;
|
||||
using GstEnginePipelinePtr = QSharedPointer<GstEnginePipeline>;
|
||||
|
||||
#endif // GSTENGINEPIPELINE_H
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
#include <gst/pbutils/pbutils.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QStandardPaths>
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/standardpaths.h"
|
||||
#include "utilities/envutils.h"
|
||||
|
||||
#ifdef HAVE_MOODBAR
|
||||
@@ -145,7 +145,7 @@ void SetEnvironment() {
|
||||
#endif // USE_BUNDLE
|
||||
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_MACOS)
|
||||
QString gst_registry_filename = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + QStringLiteral("/gst-registry-%1-bin").arg(QCoreApplication::applicationVersion());
|
||||
QString gst_registry_filename = StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppLocalDataLocation) + QStringLiteral("/gst-registry-%1-bin").arg(QCoreApplication::applicationVersion());
|
||||
qLog(Debug) << "Setting GStreamer registry file to" << gst_registry_filename;
|
||||
Utilities::SetEnv("GST_REGISTRY", gst_registry_filename);
|
||||
#endif
|
||||
|
||||
@@ -188,8 +188,15 @@ FilterTree *FilterParser::parseSearchTerm() {
|
||||
QString value;
|
||||
|
||||
bool in_quotes = false;
|
||||
bool previous_char_operator = false;
|
||||
|
||||
for (; iter_ != end_; ++iter_) {
|
||||
if (previous_char_operator) {
|
||||
if (iter_->isSpace()) {
|
||||
continue;
|
||||
}
|
||||
previous_char_operator = false;
|
||||
}
|
||||
if (in_quotes) {
|
||||
if (*iter_ == u'"') {
|
||||
in_quotes = false;
|
||||
@@ -206,6 +213,7 @@ FilterTree *FilterParser::parseSearchTerm() {
|
||||
column = buf_.toLower();
|
||||
buf_.clear();
|
||||
prefix.clear(); // Prefix isn't allowed here - let's ignore it
|
||||
previous_char_operator = true;
|
||||
}
|
||||
else if (iter_->isSpace() || *iter_ == u'(' || *iter_ == u')' || *iter_ == u'-') {
|
||||
break;
|
||||
@@ -214,9 +222,11 @@ FilterTree *FilterParser::parseSearchTerm() {
|
||||
// We don't know whether there is a column part in this search term thus we assume the latter and just try and read a prefix
|
||||
if (prefix.isEmpty() && (*iter_ == u'>' || *iter_ == u'<' || *iter_ == u'=' || *iter_ == u'!')) {
|
||||
prefix += *iter_;
|
||||
previous_char_operator = true;
|
||||
}
|
||||
else if (prefix != u'=' && *iter_ == u'=') {
|
||||
prefix += *iter_;
|
||||
previous_char_operator = true;
|
||||
}
|
||||
else {
|
||||
buf_ += *iter_;
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
# include "globalshortcutsbackend-x11.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN32
|
||||
# include "globalshortcutsbackend-win.h"
|
||||
#endif
|
||||
|
||||
@@ -92,7 +92,7 @@ GlobalShortcutsManager::GlobalShortcutsManager(QWidget *parent) : QWidget(parent
|
||||
backends_ << new GlobalShortcutsBackendMacOS(this, this);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN32
|
||||
backends_ << new GlobalShortcutsBackendWin(this, this);
|
||||
#endif
|
||||
|
||||
@@ -112,7 +112,7 @@ void GlobalShortcutsManager::ReloadSettings() {
|
||||
backends_enabled_ << GlobalShortcutsBackend::Type::macOS;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN32
|
||||
backends_enabled_ << GlobalShortcutsBackend::Type::Win;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -54,8 +54,8 @@ class Lazy {
|
||||
|
||||
T* operator->() const { return get(); }
|
||||
|
||||
// Returns true if the object is not yet initialized.
|
||||
explicit operator bool() const { return ptr_; }
|
||||
// Returns true if the object is initialized.
|
||||
explicit operator bool() const { return ptr_ != nullptr; }
|
||||
|
||||
// Deletes the underlying object and will re-run the initialization function if the object is requested again.
|
||||
void reset() { ptr_.reset(); }
|
||||
|
||||
@@ -47,6 +47,56 @@ class mutex_protected : public boost::noncopyable {
|
||||
return value == value_;
|
||||
}
|
||||
|
||||
bool operator!=(const mutex_protected &value) const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value.value() != value_;
|
||||
}
|
||||
|
||||
bool operator!=(const T value) const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value != value_;
|
||||
}
|
||||
|
||||
bool operator>(const mutex_protected &value) const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value_ > value.value();
|
||||
}
|
||||
|
||||
bool operator>(const T value) const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value_ > value;
|
||||
}
|
||||
|
||||
bool operator>=(const mutex_protected &value) const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value_ >= value.value();
|
||||
}
|
||||
|
||||
bool operator>=(const T value) const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value_ >= value;
|
||||
}
|
||||
|
||||
bool operator<(const mutex_protected &value) const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value_ < value.value();
|
||||
}
|
||||
|
||||
bool operator<(const T value) const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value_ < value;
|
||||
}
|
||||
|
||||
bool operator<=(const mutex_protected &value) const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value_ <= value.value();
|
||||
}
|
||||
|
||||
bool operator<=(const T value) const {
|
||||
QMutexLocker l(&mutex_);
|
||||
return value_ <= value;
|
||||
}
|
||||
|
||||
void operator=(const mutex_protected &value) {
|
||||
QMutexLocker l(&mutex_);
|
||||
value_ = value.value();
|
||||
@@ -57,6 +107,36 @@ class mutex_protected : public boost::noncopyable {
|
||||
value_ = value;
|
||||
}
|
||||
|
||||
void operator++() {
|
||||
QMutexLocker l(&mutex_);
|
||||
++value_;
|
||||
}
|
||||
|
||||
void operator+=(const T value) {
|
||||
QMutexLocker l(&mutex_);
|
||||
value_ += value;
|
||||
}
|
||||
|
||||
void operator+=(const mutex_protected value) {
|
||||
QMutexLocker l(&mutex_);
|
||||
value_ += value.value();
|
||||
}
|
||||
|
||||
void operator--() {
|
||||
QMutexLocker l(&mutex_);
|
||||
--value_;
|
||||
}
|
||||
|
||||
void operator-=(const T value) {
|
||||
QMutexLocker l(&mutex_);
|
||||
value_ -= value;
|
||||
}
|
||||
|
||||
void operator-=(const mutex_protected value) {
|
||||
QMutexLocker l(&mutex_);
|
||||
value_ -= value.value();
|
||||
}
|
||||
|
||||
private:
|
||||
T value_;
|
||||
mutable QMutex mutex_;
|
||||
|
||||
@@ -44,7 +44,7 @@ using std::make_shared;
|
||||
|
||||
LyricsProviders::LyricsProviders(QObject *parent) : QObject(parent), thread_(new QThread(this)), network_(make_shared<NetworkAccessManager>()) {
|
||||
|
||||
setObjectName(QLatin1String(metaObject()->className()));
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
thread_->setObjectName(objectName());
|
||||
network_->moveToThread(thread_);
|
||||
thread_->start();
|
||||
|
||||
15
src/main.cpp
15
src/main.cpp
@@ -48,7 +48,6 @@
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QSysInfo>
|
||||
#include <QStandardPaths>
|
||||
#include <QLibraryInfo>
|
||||
#include <QFileDevice>
|
||||
#include <QIODevice>
|
||||
@@ -70,6 +69,7 @@
|
||||
#include "includes/shared_ptr.h"
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/standardpaths.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#include "utilities/envutils.h"
|
||||
@@ -131,13 +131,8 @@ int main(int argc, char *argv[]) {
|
||||
mac::MacMain();
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_MACOS)
|
||||
QCoreApplication::setApplicationName(u"Strawberry"_s);
|
||||
QCoreApplication::setOrganizationName(u"Strawberry"_s);
|
||||
#else
|
||||
QCoreApplication::setApplicationName(u"strawberry"_s);
|
||||
QCoreApplication::setOrganizationName(u"strawberry"_s);
|
||||
#endif
|
||||
QCoreApplication::setApplicationVersion(QStringLiteral(STRAWBERRY_VERSION_DISPLAY));
|
||||
QCoreApplication::setOrganizationDomain(u"strawberrymusicplayer.org"_s);
|
||||
|
||||
@@ -157,7 +152,7 @@ int main(int argc, char *argv[]) {
|
||||
// Only start a core application now, so we can check if there's another instance without requiring an X server.
|
||||
// This MUST be done before parsing the commandline options so QTextCodec gets the right system locale for filenames.
|
||||
QCoreApplication core_app(argc, argv);
|
||||
KDSingleApplication single_app(QCoreApplication::applicationName(), KDSingleApplication::Option::IncludeUsernameInSocketName);
|
||||
KDSingleApplication single_app(QCoreApplication::applicationName().toLower(), KDSingleApplication::Option::IncludeUsernameInSocketName);
|
||||
// Parse commandline options - need to do this before starting the full QApplication, so it works without an X server
|
||||
if (!options.Parse()) return 1;
|
||||
logging::SetLevels(options.log_levels());
|
||||
@@ -174,7 +169,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
// Must happen after QCoreApplication::setOrganizationName().
|
||||
Utilities::SetEnv("XDG_CONFIG_HOME", QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
|
||||
Utilities::SetEnv("XDG_CONFIG_HOME", StandardPaths::WritableLocation(StandardPaths::StandardLocation::AppConfigLocation));
|
||||
#endif
|
||||
|
||||
// Output the version, so when people attach log output to bug reports they don't have to tell us which version they're using.
|
||||
@@ -194,7 +189,7 @@ int main(int argc, char *argv[]) {
|
||||
QGuiApplication::setQuitOnLastWindowClosed(false);
|
||||
|
||||
QApplication a(argc, argv);
|
||||
KDSingleApplication single_app(QCoreApplication::applicationName(), KDSingleApplication::Option::IncludeUsernameInSocketName);
|
||||
KDSingleApplication single_app(QCoreApplication::applicationName().toLower(), KDSingleApplication::Option::IncludeUsernameInSocketName);
|
||||
if (!single_app.isPrimaryInstance()) {
|
||||
if (options.is_empty()) {
|
||||
qLog(Info) << "Strawberry is already running - activating existing window (2)";
|
||||
@@ -295,7 +290,7 @@ int main(int argc, char *argv[]) {
|
||||
translations->LoadTranslation(u"strawberry"_s, QDir::currentPath(), language);
|
||||
|
||||
# ifdef HAVE_QTSPARKLE
|
||||
//qtsparkle::LoadTranslations(language);
|
||||
qtsparkle::LoadTranslations(language);
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <QByteArray>
|
||||
#include <QUrl>
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/player.h"
|
||||
@@ -33,6 +34,8 @@
|
||||
#include "moodbarloader.h"
|
||||
#include "moodbarpipeline.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
MoodbarController::MoodbarController(const SharedPtr<Player> player, const SharedPtr<MoodbarLoader> moodbar_loader, QObject *parent)
|
||||
: QObject(parent),
|
||||
player_(player),
|
||||
@@ -56,25 +59,27 @@ void MoodbarController::CurrentSongChanged(const Song &song) {
|
||||
|
||||
if (!enabled_) return;
|
||||
|
||||
QByteArray data;
|
||||
MoodbarPipeline *pipeline = nullptr;
|
||||
const MoodbarLoader::Result result = moodbar_loader_->Load(song.url(), song.has_cue(), &data, &pipeline);
|
||||
|
||||
switch (result) {
|
||||
case MoodbarLoader::Result::CannotLoad:
|
||||
Q_EMIT CurrentMoodbarDataChanged(QByteArray());
|
||||
const MoodbarLoader::LoadResult load_result = moodbar_loader_->Load(song.url(), song.has_cue());
|
||||
switch (load_result.status) {
|
||||
case MoodbarLoader::LoadStatus::CannotLoad:
|
||||
Q_EMIT CurrentMoodbarDataChanged();
|
||||
break;
|
||||
|
||||
case MoodbarLoader::Result::Loaded:
|
||||
Q_EMIT CurrentMoodbarDataChanged(data);
|
||||
case MoodbarLoader::LoadStatus::Loaded:
|
||||
Q_EMIT CurrentMoodbarDataChanged(load_result.data);
|
||||
break;
|
||||
|
||||
case MoodbarLoader::Result::WillLoadAsync:
|
||||
// Emit an empty array for now so the GUI reverts to a normal progress
|
||||
// bar. Our slot will be called when the data is actually loaded.
|
||||
Q_EMIT CurrentMoodbarDataChanged(QByteArray());
|
||||
case MoodbarLoader::LoadStatus::WillLoadAsync:
|
||||
// Emit an empty array for now so the GUI reverts to a normal progressbar. Our slot will be called when the data is actually loaded.
|
||||
Q_EMIT CurrentMoodbarDataChanged();
|
||||
|
||||
QObject::connect(pipeline, &MoodbarPipeline::Finished, this, [this, pipeline, song]() { AsyncLoadComplete(pipeline, song.url()); });
|
||||
MoodbarPipelinePtr pipeline = load_result.pipeline;
|
||||
Q_ASSERT(pipeline);
|
||||
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
|
||||
*connection = QObject::connect(&*pipeline, &MoodbarPipeline::Finished, this, [this, connection, pipeline, song]() {
|
||||
AsyncLoadComplete(pipeline, song.url());
|
||||
QObject::disconnect(*connection);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -83,12 +88,12 @@ void MoodbarController::CurrentSongChanged(const Song &song) {
|
||||
void MoodbarController::PlaybackStopped() {
|
||||
|
||||
if (enabled_) {
|
||||
Q_EMIT CurrentMoodbarDataChanged(QByteArray());
|
||||
Q_EMIT CurrentMoodbarDataChanged();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MoodbarController::AsyncLoadComplete(MoodbarPipeline *pipeline, const QUrl &url) {
|
||||
void MoodbarController::AsyncLoadComplete(MoodbarPipelinePtr pipeline, const QUrl &url) {
|
||||
|
||||
// Is this song still playing?
|
||||
PlaylistItemPtr current_item = player_->GetCurrentItem();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -28,9 +28,9 @@
|
||||
#include <QUrl>
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "moodbarpipeline.h"
|
||||
|
||||
class MoodbarLoader;
|
||||
class MoodbarPipeline;
|
||||
class Song;
|
||||
class Player;
|
||||
|
||||
@@ -43,15 +43,15 @@ class MoodbarController : public QObject {
|
||||
void ReloadSettings();
|
||||
|
||||
Q_SIGNALS:
|
||||
void CurrentMoodbarDataChanged(const QByteArray &data);
|
||||
void CurrentMoodbarDataChanged(const QByteArray &data = QByteArray());
|
||||
void StyleChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
void CurrentSongChanged(const Song &song);
|
||||
void PlaybackStopped();
|
||||
|
||||
private Q_SLOTS:
|
||||
void AsyncLoadComplete(MoodbarPipeline *pipeline, const QUrl &url);
|
||||
private:
|
||||
void AsyncLoadComplete(MoodbarPipelinePtr pipeline, const QUrl &url);
|
||||
|
||||
private:
|
||||
const SharedPtr<Player> player_;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -36,6 +36,8 @@
|
||||
#include <QPainter>
|
||||
#include <QRect>
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/settings.h"
|
||||
#include "playlist/playlist.h"
|
||||
#include "playlist/playlistview.h"
|
||||
@@ -48,6 +50,8 @@
|
||||
|
||||
#include "constants/moodbarsettings.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
MoodbarItemDelegate::Data::Data() : state_(State::None) {}
|
||||
|
||||
MoodbarItemDelegate::MoodbarItemDelegate(const SharedPtr<MoodbarLoader> moodbar_loader, PlaylistView *playlist_view, QObject *parent)
|
||||
@@ -113,7 +117,10 @@ QPixmap MoodbarItemDelegate::PixmapForIndex(const QModelIndex &idx, const QSize
|
||||
}
|
||||
else {
|
||||
data = new Data;
|
||||
if (!data_.insert(url, data)) return QPixmap();
|
||||
if (!data_.insert(url, data)) {
|
||||
qLog(Error) << "Could not insert moodbar data for URL" << url << "into cache";
|
||||
return QPixmap();
|
||||
}
|
||||
}
|
||||
|
||||
data->indexes_.insert(idx);
|
||||
@@ -150,21 +157,24 @@ void MoodbarItemDelegate::StartLoadingData(const QUrl &url, const bool has_cue,
|
||||
data->state_ = Data::State::LoadingData;
|
||||
|
||||
// Load a mood file for this song and generate some colors from it
|
||||
QByteArray bytes;
|
||||
MoodbarPipeline *pipeline = nullptr;
|
||||
switch (moodbar_loader_->Load(url, has_cue, &bytes, &pipeline)) {
|
||||
case MoodbarLoader::Result::CannotLoad:
|
||||
const MoodbarLoader::LoadResult load_result = moodbar_loader_->Load(url, has_cue);
|
||||
switch (load_result.status) {
|
||||
case MoodbarLoader::LoadStatus::CannotLoad:
|
||||
data->state_ = Data::State::CannotLoad;
|
||||
break;
|
||||
|
||||
case MoodbarLoader::Result::Loaded:
|
||||
// We got the data immediately.
|
||||
StartLoadingColors(url, bytes, data);
|
||||
case MoodbarLoader::LoadStatus::Loaded:
|
||||
StartLoadingColors(url, load_result.data, data);
|
||||
break;
|
||||
|
||||
case MoodbarLoader::Result::WillLoadAsync:
|
||||
// Maybe in a little while.
|
||||
QObject::connect(pipeline, &MoodbarPipeline::Finished, this, [this, url, pipeline]() { DataLoaded(url, pipeline); });
|
||||
case MoodbarLoader::LoadStatus::WillLoadAsync:
|
||||
MoodbarPipelinePtr pipeline = load_result.pipeline;
|
||||
Q_ASSERT(pipeline);
|
||||
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
|
||||
*connection = QObject::connect(&*pipeline, &MoodbarPipeline::Finished, this, [this, connection, url, pipeline]() {
|
||||
DataLoaded(url, pipeline);
|
||||
QObject::disconnect(*connection);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -194,7 +204,7 @@ void MoodbarItemDelegate::ReloadAllColors() {
|
||||
|
||||
}
|
||||
|
||||
void MoodbarItemDelegate::DataLoaded(const QUrl &url, MoodbarPipeline *pipeline) {
|
||||
void MoodbarItemDelegate::DataLoaded(const QUrl &url, MoodbarPipelinePtr pipeline) {
|
||||
|
||||
if (!data_.contains(url)) return;
|
||||
|
||||
@@ -291,8 +301,7 @@ void MoodbarItemDelegate::ImageLoaded(const QUrl &url, const QImage &image) {
|
||||
}
|
||||
|
||||
if (source_index.model() != playlist) {
|
||||
// The pixmap was for an index in a different playlist, maybe the user
|
||||
// switched to a different one.
|
||||
// The pixmap was for an index in a different playlist, maybe the user switched to a different one.
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -38,10 +38,10 @@
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "constants/moodbarsettings.h"
|
||||
#include "moodbarpipeline.h"
|
||||
|
||||
class QPainter;
|
||||
class MoodbarLoader;
|
||||
class MoodbarPipeline;
|
||||
class PlaylistView;
|
||||
|
||||
class MoodbarItemDelegate : public QItemDelegate {
|
||||
@@ -55,13 +55,14 @@ class MoodbarItemDelegate : public QItemDelegate {
|
||||
private Q_SLOTS:
|
||||
void ReloadSettings();
|
||||
|
||||
void DataLoaded(const QUrl &url, MoodbarPipeline *pipeline);
|
||||
void ColorsLoaded(const QUrl &url, const ColorVector &colors);
|
||||
void ImageLoaded(const QUrl &url, const QImage &image);
|
||||
|
||||
Q_SIGNALS:
|
||||
void StyleChanged();
|
||||
|
||||
private:
|
||||
void DataLoaded(const QUrl &url, MoodbarPipelinePtr pipeline);
|
||||
void ColorsLoaded(const QUrl &url, const ColorVector &colors);
|
||||
void ImageLoaded(const QUrl &url, const QImage &image);
|
||||
|
||||
private:
|
||||
struct Data {
|
||||
Data();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -28,21 +28,21 @@
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QCoreApplication>
|
||||
#include <QStandardPaths>
|
||||
#include <QIODevice>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QAbstractNetworkCache>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QTimer>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QSettings>
|
||||
|
||||
#include "includes/scoped_ptr.h"
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/standardpaths.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#include "moodbarpipeline.h"
|
||||
@@ -51,6 +51,7 @@
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
using std::make_shared;
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
# include <windows.h>
|
||||
@@ -63,7 +64,10 @@ MoodbarLoader::MoodbarLoader(QObject *parent)
|
||||
kMaxActiveRequests(qMax(1, QThread::idealThreadCount() / 2)),
|
||||
save_(false) {
|
||||
|
||||
cache_->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u"/moodbar"_s);
|
||||
setObjectName(QLatin1String(QObject::metaObject()->className()));
|
||||
thread_->setObjectName(objectName());
|
||||
|
||||
cache_->setCacheDirectory(StandardPaths::WritableLocation(StandardPaths::StandardLocation::CacheLocation) + u"/moodbar"_s);
|
||||
cache_->setMaximumCacheSize(60LL * 1024LL * 1024LL); // 60MB - enough for 20,000 moodbars
|
||||
|
||||
ReloadSettings();
|
||||
@@ -104,16 +108,15 @@ QUrl MoodbarLoader::CacheUrlEntry(const QString &filename) {
|
||||
|
||||
}
|
||||
|
||||
MoodbarLoader::Result MoodbarLoader::Load(const QUrl &url, const bool has_cue, QByteArray *data, MoodbarPipeline **async_pipeline) {
|
||||
MoodbarLoader::LoadResult MoodbarLoader::Load(const QUrl &url, const bool has_cue) {
|
||||
|
||||
if (!url.isLocalFile() || has_cue) {
|
||||
return Result::CannotLoad;
|
||||
return LoadStatus::CannotLoad;
|
||||
}
|
||||
|
||||
// Are we in the middle of loading this moodbar already?
|
||||
if (requests_.contains(url)) {
|
||||
*async_pipeline = requests_.value(url);
|
||||
return Result::WillLoadAsync;
|
||||
return LoadResult(LoadStatus::WillLoadAsync, requests_.value(url));
|
||||
}
|
||||
|
||||
// Check if a mood file exists for this file already
|
||||
@@ -121,16 +124,18 @@ MoodbarLoader::Result MoodbarLoader::Load(const QUrl &url, const bool has_cue, Q
|
||||
|
||||
const QStringList possible_mood_files = MoodFilenames(filename);
|
||||
for (const QString &possible_mood_file : possible_mood_files) {
|
||||
QFile f(possible_mood_file);
|
||||
if (f.exists()) {
|
||||
if (f.open(QIODevice::ReadOnly)) {
|
||||
QFile file(possible_mood_file);
|
||||
if (file.exists()) {
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
qLog(Info) << "Loading moodbar data from" << possible_mood_file;
|
||||
*data = f.readAll();
|
||||
f.close();
|
||||
return Result::Loaded;
|
||||
const QByteArray data = file.readAll();
|
||||
file.close();
|
||||
if (!data.isEmpty()) {
|
||||
return LoadResult(LoadStatus::Loaded, data);
|
||||
}
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Failed to load moodbar data from" << possible_mood_file << f.errorString();
|
||||
qLog(Error) << "Failed to load moodbar data from" << possible_mood_file << file.errorString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,9 +147,9 @@ MoodbarLoader::Result MoodbarLoader::Load(const QUrl &url, const bool has_cue, Q
|
||||
ScopedPtr<QIODevice> device_cache_file(cache_->data(disk_cache_metadata.url()));
|
||||
if (device_cache_file) {
|
||||
qLog(Info) << "Loading cached moodbar data for" << filename;
|
||||
*data = device_cache_file->readAll();
|
||||
if (!data->isEmpty()) {
|
||||
return Result::Loaded;
|
||||
const QByteArray data = device_cache_file->readAll();
|
||||
if (!data.isEmpty()) {
|
||||
return LoadResult(LoadStatus::Loaded, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,17 +157,20 @@ MoodbarLoader::Result MoodbarLoader::Load(const QUrl &url, const bool has_cue, Q
|
||||
if (!thread_->isRunning()) thread_->start(QThread::IdlePriority);
|
||||
|
||||
// There was no existing file, analyze the audio file and create one.
|
||||
MoodbarPipeline *pipeline = new MoodbarPipeline(url);
|
||||
MoodbarPipelinePtr pipeline = MoodbarPipelinePtr(new MoodbarPipeline(url));
|
||||
pipeline->moveToThread(thread_);
|
||||
QObject::connect(pipeline, &MoodbarPipeline::Finished, this, [this, pipeline, url]() { RequestFinished(pipeline, url); });
|
||||
SharedPtr<QMetaObject::Connection> connection = make_shared<QMetaObject::Connection>();
|
||||
*connection = QObject::connect(&*pipeline, &MoodbarPipeline::Finished, this, [this, connection, pipeline, url]() {
|
||||
RequestFinished(pipeline, url);
|
||||
QObject::disconnect(*connection);
|
||||
});
|
||||
|
||||
requests_[url] = pipeline;
|
||||
queued_requests_ << url;
|
||||
|
||||
MaybeTakeNextRequest();
|
||||
|
||||
*async_pipeline = pipeline;
|
||||
return Result::WillLoadAsync;
|
||||
return LoadResult(LoadStatus::WillLoadAsync, pipeline);
|
||||
|
||||
}
|
||||
|
||||
@@ -178,15 +186,17 @@ void MoodbarLoader::MaybeTakeNextRequest() {
|
||||
active_requests_ << url;
|
||||
|
||||
qLog(Info) << "Creating moodbar data for" << url.toLocalFile();
|
||||
QMetaObject::invokeMethod(requests_.value(url), &MoodbarPipeline::Start, Qt::QueuedConnection);
|
||||
|
||||
MoodbarPipelinePtr pipeline = requests_.value(url);
|
||||
QMetaObject::invokeMethod(&*pipeline, &MoodbarPipeline::Start, Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
void MoodbarLoader::RequestFinished(MoodbarPipeline *request, const QUrl &url) {
|
||||
void MoodbarLoader::RequestFinished(MoodbarPipelinePtr pipeline, const QUrl &url) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
|
||||
if (request->success()) {
|
||||
if (pipeline->success()) {
|
||||
|
||||
const QString filename = url.toLocalFile();
|
||||
|
||||
@@ -201,7 +211,7 @@ void MoodbarLoader::RequestFinished(MoodbarPipeline *request, const QUrl &url) {
|
||||
|
||||
QIODevice *device_cache_file = cache_->prepare(disk_cache_metadata);
|
||||
if (device_cache_file) {
|
||||
const qint64 data_written = device_cache_file->write(request->data());
|
||||
const qint64 data_written = device_cache_file->write(pipeline->data());
|
||||
if (data_written > 0) {
|
||||
cache_->insert(device_cache_file);
|
||||
}
|
||||
@@ -213,7 +223,7 @@ void MoodbarLoader::RequestFinished(MoodbarPipeline *request, const QUrl &url) {
|
||||
const QString mood_filename(mood_filenames[0]);
|
||||
QFile mood_file(mood_filename);
|
||||
if (mood_file.open(QIODevice::WriteOnly)) {
|
||||
if (mood_file.write(request->data()) <= 0) {
|
||||
if (mood_file.write(pipeline->data()) <= 0) {
|
||||
qLog(Error) << "Error writing to mood file" << mood_filename << mood_file.errorString();
|
||||
}
|
||||
mood_file.close();
|
||||
@@ -233,8 +243,6 @@ void MoodbarLoader::RequestFinished(MoodbarPipeline *request, const QUrl &url) {
|
||||
requests_.remove(url);
|
||||
active_requests_.remove(url);
|
||||
|
||||
QTimer::singleShot(1s, request, &MoodbarLoader::deleteLater);
|
||||
|
||||
MaybeTakeNextRequest();
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -30,10 +30,11 @@
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
|
||||
#include "moodbarpipeline.h"
|
||||
|
||||
class QThread;
|
||||
class QByteArray;
|
||||
class QNetworkDiskCache;
|
||||
class MoodbarPipeline;
|
||||
|
||||
class MoodbarLoader : public QObject {
|
||||
Q_OBJECT
|
||||
@@ -42,7 +43,7 @@ class MoodbarLoader : public QObject {
|
||||
explicit MoodbarLoader(QObject *parent = nullptr);
|
||||
~MoodbarLoader() override;
|
||||
|
||||
enum class Result {
|
||||
enum class LoadStatus {
|
||||
// The URL isn't a local file or the moodbar plugin was not available -
|
||||
// moodbar data can never be loaded.
|
||||
CannotLoad,
|
||||
@@ -55,17 +56,25 @@ class MoodbarLoader : public QObject {
|
||||
WillLoadAsync
|
||||
};
|
||||
|
||||
class LoadResult {
|
||||
public:
|
||||
LoadResult(const LoadStatus _status) : status(_status) {}
|
||||
LoadResult(const LoadStatus _status, MoodbarPipelinePtr _pipeline) : status(_status), pipeline(_pipeline) {}
|
||||
LoadResult(const LoadStatus _status, const QByteArray &_data) : status(_status), data(_data) {}
|
||||
LoadStatus status;
|
||||
MoodbarPipelinePtr pipeline;
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
Result Load(const QUrl &url, const bool has_cue, QByteArray *data, MoodbarPipeline **async_pipeline);
|
||||
|
||||
private Q_SLOTS:
|
||||
void RequestFinished(MoodbarPipeline *request, const QUrl &url);
|
||||
void MaybeTakeNextRequest();
|
||||
LoadResult Load(const QUrl &url, const bool has_cue);
|
||||
|
||||
private:
|
||||
static QStringList MoodFilenames(const QString &song_filename);
|
||||
static QUrl CacheUrlEntry(const QString &filename);
|
||||
void RequestFinished(MoodbarPipelinePtr pipeline, const QUrl &url);
|
||||
void MaybeTakeNextRequest();
|
||||
|
||||
Q_SIGNALS:
|
||||
void MoodbarEnabled(const bool enabled);
|
||||
@@ -78,7 +87,7 @@ class MoodbarLoader : public QObject {
|
||||
|
||||
const int kMaxActiveRequests;
|
||||
|
||||
QMap<QUrl, MoodbarPipeline*> requests_;
|
||||
QMap<QUrl, MoodbarPipelinePtr> requests_;
|
||||
QList<QUrl> queued_requests_;
|
||||
QSet<QUrl> active_requests_;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -56,20 +56,24 @@ MoodbarPipeline::MoodbarPipeline(const QUrl &url, QObject *parent)
|
||||
success_(false),
|
||||
running_(false) {}
|
||||
|
||||
MoodbarPipeline::~MoodbarPipeline() { Cleanup(); }
|
||||
MoodbarPipeline::~MoodbarPipeline() {
|
||||
|
||||
GstElement *MoodbarPipeline::CreateElement(const QString &factory_name) {
|
||||
Cleanup();
|
||||
|
||||
GstElement *ret = gst_element_factory_make(factory_name.toLatin1().constData(), nullptr);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
gst_bin_add(GST_BIN(pipeline_), ret);
|
||||
GstElement *MoodbarPipeline::CreateElement(const QByteArray &factory_name) {
|
||||
|
||||
GstElement *element = gst_element_factory_make(factory_name.constData(), nullptr);
|
||||
|
||||
if (element) {
|
||||
gst_bin_add(GST_BIN(pipeline_), element);
|
||||
}
|
||||
else {
|
||||
qLog(Warning) << "Unable to create gstreamer element" << factory_name;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return element;
|
||||
|
||||
}
|
||||
|
||||
@@ -86,6 +90,7 @@ QByteArray MoodbarPipeline::ToGstUrl(const QUrl &url) {
|
||||
|
||||
void MoodbarPipeline::Start() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
Q_ASSERT(QThread::currentThread() != qApp->thread());
|
||||
|
||||
Utilities::SetThreadIOPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
|
||||
@@ -96,10 +101,10 @@ void MoodbarPipeline::Start() {
|
||||
|
||||
pipeline_ = gst_pipeline_new("moodbar-pipeline");
|
||||
|
||||
GstElement *decodebin = CreateElement(u"uridecodebin"_s);
|
||||
convert_element_ = CreateElement(u"audioconvert"_s);
|
||||
GstElement *spectrum = CreateElement(u"strawberry-fastspectrum"_s);
|
||||
GstElement *fakesink = CreateElement(u"fakesink"_s);
|
||||
GstElement *decodebin = CreateElement("uridecodebin");
|
||||
convert_element_ = CreateElement("audioconvert");
|
||||
GstElement *spectrum = CreateElement("strawberry-fastspectrum");
|
||||
GstElement *fakesink = CreateElement("fakesink");
|
||||
|
||||
if (!decodebin || !convert_element_ || !spectrum || !fakesink) {
|
||||
gst_object_unref(GST_OBJECT(pipeline_));
|
||||
@@ -204,6 +209,8 @@ GstBusSyncReply MoodbarPipeline::BusCallbackSync(GstBus *bus, GstMessage *messag
|
||||
|
||||
MoodbarPipeline *instance = reinterpret_cast<MoodbarPipeline*>(self);
|
||||
|
||||
if (!instance->running_) return GST_BUS_PASS;
|
||||
|
||||
switch (GST_MESSAGE_TYPE(message)) {
|
||||
case GST_MESSAGE_EOS:
|
||||
instance->Stop(true);
|
||||
@@ -224,23 +231,34 @@ GstBusSyncReply MoodbarPipeline::BusCallbackSync(GstBus *bus, GstMessage *messag
|
||||
|
||||
void MoodbarPipeline::Stop(const bool success) {
|
||||
|
||||
success_ = success;
|
||||
running_ = false;
|
||||
|
||||
QMetaObject::invokeMethod(this, "Finish", Qt::QueuedConnection, Q_ARG(bool, success));
|
||||
|
||||
}
|
||||
|
||||
void MoodbarPipeline::Finish(const bool success) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
Q_ASSERT(QThread::currentThread() != qApp->thread());
|
||||
|
||||
success_ = success;
|
||||
|
||||
if (builder_) {
|
||||
data_ = builder_->Finish(1000);
|
||||
builder_.reset();
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
|
||||
Q_EMIT Finished(success);
|
||||
|
||||
}
|
||||
|
||||
void MoodbarPipeline::Cleanup() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
Q_ASSERT(QThread::currentThread() != qApp->thread());
|
||||
|
||||
running_ = false;
|
||||
|
||||
if (pipeline_) {
|
||||
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline_));
|
||||
if (bus) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2019-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
@@ -46,18 +47,18 @@ class MoodbarPipeline : public QObject {
|
||||
bool success() const { return success_; }
|
||||
const QByteArray &data() const { return data_; }
|
||||
|
||||
public Q_SLOTS:
|
||||
void Start();
|
||||
Q_INVOKABLE void Start();
|
||||
|
||||
Q_SIGNALS:
|
||||
void Finished(const bool success);
|
||||
|
||||
private:
|
||||
GstElement *CreateElement(const QString &factory_name);
|
||||
GstElement *CreateElement(const QByteArray &factory_name);
|
||||
|
||||
QByteArray ToGstUrl(const QUrl &url);
|
||||
void ReportError(GstMessage *msg);
|
||||
void Stop(const bool success);
|
||||
Q_INVOKABLE void Finish(const bool success);
|
||||
void Cleanup();
|
||||
|
||||
static void NewPadCallback(GstElement *element, GstPad *pad, gpointer self);
|
||||
@@ -75,4 +76,6 @@ class MoodbarPipeline : public QObject {
|
||||
QByteArray data_;
|
||||
};
|
||||
|
||||
using MoodbarPipelinePtr = QSharedPointer<MoodbarPipeline>;
|
||||
|
||||
#endif // MOODBARPIPELINE_H
|
||||
|
||||
@@ -201,7 +201,7 @@ void MoodbarProxyStyle::drawComplexControl(ComplexControl control, const QStyleO
|
||||
|
||||
}
|
||||
|
||||
void MoodbarProxyStyle::Render(ComplexControl control, const QStyleOptionSlider *option, QPainter *painter, const QWidget *widget) {
|
||||
void MoodbarProxyStyle::Render(const ComplexControl control, const QStyleOptionSlider *option, QPainter *painter, const QWidget *widget) {
|
||||
|
||||
const qreal fade_value = fade_timeline_->currentValue();
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class MoodbarProxyStyle : public QProxyStyle {
|
||||
private:
|
||||
void NextState();
|
||||
|
||||
void Render(ComplexControl control, const QStyleOptionSlider *option, QPainter *painter, const QWidget *widget);
|
||||
void Render(const ComplexControl control, const QStyleOptionSlider *option, QPainter *painter, const QWidget *widget);
|
||||
void EnsureMoodbarRendered(const QStyleOptionSlider *opt);
|
||||
void DrawArrow(const QStyleOptionSlider *option, QPainter *painter) const;
|
||||
void ShowContextMenu(const QPoint pos);
|
||||
@@ -92,7 +92,7 @@ class MoodbarProxyStyle : public QProxyStyle {
|
||||
static QPixmap MoodbarPixmap(const ColorVector &colors, const QSize size, const QPalette &palette, const QStyleOptionSlider *opt);
|
||||
|
||||
private Q_SLOTS:
|
||||
void FaderValueChanged(qreal value);
|
||||
void FaderValueChanged(const qreal value);
|
||||
void SetStyle(QAction *action);
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
@@ -106,8 +106,7 @@ Mpris2::Mpris2(const SharedPtr<Player> player,
|
||||
: QObject(parent),
|
||||
player_(player),
|
||||
playlist_manager_(playlist_manager),
|
||||
current_albumcover_loader_(current_albumcover_loader),
|
||||
app_name_(QCoreApplication::applicationName()) {
|
||||
current_albumcover_loader_(current_albumcover_loader) {
|
||||
|
||||
new Mpris2Root(this);
|
||||
new Mpris2TrackList(this);
|
||||
@@ -135,8 +134,6 @@ Mpris2::Mpris2(const SharedPtr<Player> player,
|
||||
QObject::connect(&*playlist_manager_, &PlaylistManager::PlaylistChanged, this, &Mpris2::PlaylistChangedSlot);
|
||||
QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentChanged, this, &Mpris2::PlaylistCollectionChanged);
|
||||
|
||||
app_name_[0] = app_name_[0].toUpper();
|
||||
|
||||
QStringList data_dirs = QString::fromUtf8(qgetenv("XDG_DATA_DIRS")).split(u':');
|
||||
|
||||
if (!data_dirs.contains("/usr/local/share"_L1)) {
|
||||
@@ -238,7 +235,7 @@ bool Mpris2::CanRaise() const { return true; }
|
||||
|
||||
bool Mpris2::HasTrackList() const { return true; }
|
||||
|
||||
QString Mpris2::Identity() const { return app_name_; }
|
||||
QString Mpris2::Identity() const { return QCoreApplication::applicationName(); }
|
||||
|
||||
QString Mpris2::DesktopEntryAbsolutePath() const {
|
||||
|
||||
@@ -386,8 +383,12 @@ void Mpris2::SetRating(double rating) {
|
||||
|
||||
}
|
||||
|
||||
QDBusObjectPath Mpris2::current_track_id() const {
|
||||
return QDBusObjectPath(QStringLiteral("/org/strawberrymusicplayer/strawberry/Track/%1").arg(QString::number(playlist_manager_->active()->current_row())));
|
||||
int Mpris2::current_playlist_row() const {
|
||||
return playlist_manager_->active()->current_row();
|
||||
}
|
||||
|
||||
QDBusObjectPath Mpris2::current_track_id(const int current_row) const {
|
||||
return QDBusObjectPath(QStringLiteral("/org/strawberrymusicplayer/strawberry/Track/%1").arg(current_row));
|
||||
}
|
||||
|
||||
// We send Metadata change notification as soon as the process of changing song starts...
|
||||
@@ -405,11 +406,14 @@ void Mpris2::CurrentSongChanged(const Song &song) {
|
||||
// ... and we add the cover information later, when it's available.
|
||||
void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
|
||||
|
||||
const int current_row = current_playlist_row();
|
||||
if (current_row == -1) return;
|
||||
|
||||
last_metadata_ = QVariantMap();
|
||||
song.ToXesam(&last_metadata_);
|
||||
|
||||
using mpris::AddMetadata;
|
||||
AddMetadata(u"mpris:trackid"_s, current_track_id(), &last_metadata_);
|
||||
AddMetadata(u"mpris:trackid"_s, current_track_id(current_row), &last_metadata_);
|
||||
|
||||
QUrl cover_url;
|
||||
if (result.album_cover.cover_url.isValid() && result.album_cover.cover_url.isLocalFile() && QFile(result.album_cover.cover_url.toLocalFile()).exists()) {
|
||||
@@ -519,7 +523,11 @@ void Mpris2::Seek(qint64 offset) {
|
||||
|
||||
void Mpris2::SetPosition(const QDBusObjectPath &trackId, qint64 offset) {
|
||||
|
||||
if (CanSeek() && trackId == current_track_id() && offset >= 0) {
|
||||
const int current_row = current_playlist_row();
|
||||
|
||||
if (current_row == -1) return;
|
||||
|
||||
if (CanSeek() && trackId == current_track_id(current_row) && offset >= 0) {
|
||||
offset *= kNsecPerUsec;
|
||||
|
||||
if (offset < player_->GetCurrentItem()->Metadata().length_nanosec()) {
|
||||
|
||||
@@ -230,7 +230,8 @@ class Mpris2 : public QObject {
|
||||
|
||||
QString PlaybackStatus(EngineBase::State state) const;
|
||||
|
||||
QDBusObjectPath current_track_id() const;
|
||||
int current_playlist_row() const;
|
||||
QDBusObjectPath current_track_id(const int current_row) const;
|
||||
|
||||
bool CanSeek(EngineBase::State state) const;
|
||||
|
||||
@@ -241,7 +242,6 @@ class Mpris2 : public QObject {
|
||||
const SharedPtr<PlaylistManager> playlist_manager_;
|
||||
const SharedPtr<CurrentAlbumCoverLoader> current_albumcover_loader_;
|
||||
|
||||
QString app_name_;
|
||||
QString desktopfilepath_;
|
||||
QVariantMap last_metadata_;
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ void Organize::ProcessSomeFiles() {
|
||||
|
||||
}
|
||||
|
||||
Song::FileType Organize::CheckTranscode(Song::FileType original_type) const {
|
||||
Song::FileType Organize::CheckTranscode(const Song::FileType original_type) const {
|
||||
|
||||
if (original_type == Song::FileType::Stream) return Song::FileType::Unknown;
|
||||
|
||||
@@ -319,7 +319,7 @@ Song::FileType Organize::CheckTranscode(Song::FileType original_type) const {
|
||||
|
||||
}
|
||||
|
||||
void Organize::SetSongProgress(float progress, bool transcoded) {
|
||||
void Organize::SetSongProgress(const float progress, const bool transcoded) {
|
||||
|
||||
const int max = transcoded ? 50 : 100;
|
||||
current_copy_progress_ = (transcoded ? 50 : 0) + qBound(0, static_cast<int>(progress * static_cast<float>(max)), max - 1);
|
||||
@@ -359,7 +359,7 @@ void Organize::UpdateProgress() {
|
||||
|
||||
}
|
||||
|
||||
void Organize::FileTranscoded(const QString &input, const QString &output, bool success) {
|
||||
void Organize::FileTranscoded(const QString &input, const QString &output, const bool success) {
|
||||
|
||||
Q_UNUSED(output);
|
||||
|
||||
|
||||
@@ -87,13 +87,13 @@ class Organize : public QObject {
|
||||
|
||||
private Q_SLOTS:
|
||||
void ProcessSomeFiles();
|
||||
void FileTranscoded(const QString &input, const QString &output, bool success);
|
||||
void FileTranscoded(const QString &input, const QString &output, const bool success);
|
||||
void LogLine(const QString &message);
|
||||
|
||||
private:
|
||||
void SetSongProgress(float progress, bool transcoded = false);
|
||||
void SetSongProgress(const float progress, const bool transcoded = false);
|
||||
void UpdateProgress();
|
||||
Song::FileType CheckTranscode(Song::FileType original_type) const;
|
||||
Song::FileType CheckTranscode(const Song::FileType original_type) const;
|
||||
|
||||
private:
|
||||
struct Task {
|
||||
|
||||
@@ -411,7 +411,6 @@ void OrganizeDialog::SetLoadingSongs(const bool loading) {
|
||||
SongList OrganizeDialog::LoadSongsBlocking(const QStringList &filenames) const {
|
||||
|
||||
SongList songs;
|
||||
Song song;
|
||||
|
||||
QStringList filenames_copy = filenames;
|
||||
while (!filenames_copy.isEmpty()) {
|
||||
@@ -427,6 +426,7 @@ SongList OrganizeDialog::LoadSongsBlocking(const QStringList &filenames) const {
|
||||
continue;
|
||||
}
|
||||
|
||||
Song song;
|
||||
const TagReaderResult result = tagreader_client_->ReadFileBlocking(filename, &song);
|
||||
if (result.success() && song.is_valid()) {
|
||||
songs << song;
|
||||
@@ -476,6 +476,7 @@ Organize::NewSongInfoList OrganizeDialog::ComputeNewSongsFilenames(const SongLis
|
||||
}
|
||||
new_songs_info << Organize::NewSongInfo(song, result.filename, result.unique_filename);
|
||||
}
|
||||
|
||||
return new_songs_info;
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QCoreApplication>
|
||||
#include <QGuiApplication>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QImage>
|
||||
@@ -48,7 +49,6 @@ OSDBase::OSDBase(const SharedPtr<SystemTrayIcon> tray_icon, QObject *parent)
|
||||
: QObject(parent),
|
||||
tray_icon_(tray_icon),
|
||||
pretty_popup_(new OSDPretty(OSDPretty::Mode::Popup)),
|
||||
app_name_(QCoreApplication::applicationName()),
|
||||
timeout_msec_(5000),
|
||||
type_(OSDSettings::Type::Native),
|
||||
show_on_volume_change_(false),
|
||||
@@ -59,11 +59,7 @@ OSDBase::OSDBase(const SharedPtr<SystemTrayIcon> tray_icon, QObject *parent)
|
||||
use_custom_text_(false),
|
||||
force_show_next_(false),
|
||||
ignore_next_stopped_(false),
|
||||
playing_(false) {
|
||||
|
||||
app_name_[0] = app_name_[0].toUpper();
|
||||
|
||||
}
|
||||
playing_(false) {}
|
||||
|
||||
OSDBase::~OSDBase() {
|
||||
delete pretty_popup_;
|
||||
@@ -85,22 +81,48 @@ void OSDBase::ReloadSettings() {
|
||||
custom_text2_ = s.value("CustomText2").toString();
|
||||
s.endGroup();
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
if (!SupportsNativeNotifications() && !SupportsTrayPopups() && type_ == OSDSettings::Type::Native) {
|
||||
#else
|
||||
if (!SupportsNativeNotifications() && type_ == OSDSettings::Type::Native) {
|
||||
#endif
|
||||
type_ = OSDSettings::Type::Pretty;
|
||||
}
|
||||
|
||||
if (!SupportsTrayPopups() && type_ == OSDSettings::Type::TrayPopup) {
|
||||
type_ = OSDSettings::Type::Disabled;
|
||||
if (!IsTypeSupported(type_)) {
|
||||
type_ = GetSupportedType();
|
||||
}
|
||||
|
||||
ReloadPrettyOSDSettings();
|
||||
|
||||
}
|
||||
|
||||
OSDSettings::Type OSDBase::GetSupportedType() const {
|
||||
|
||||
if (SupportsNativeNotifications()) {
|
||||
return OSDSettings::Type::Native;
|
||||
}
|
||||
if (SupportsOSDPretty()) {
|
||||
return OSDSettings::Type::Pretty;
|
||||
}
|
||||
if (SupportsTrayPopups()) {
|
||||
return OSDSettings::Type::TrayPopup;
|
||||
}
|
||||
|
||||
return OSDSettings::Type::Disabled;
|
||||
|
||||
}
|
||||
|
||||
bool OSDBase::IsTypeSupported(const OSDSettings::Type type) const {
|
||||
|
||||
switch (type) {
|
||||
case OSDSettings::Type::Native:
|
||||
return SupportsNativeNotifications();
|
||||
case OSDSettings::Type::TrayPopup:
|
||||
return SupportsTrayPopups();
|
||||
case OSDSettings::Type::Pretty:
|
||||
return SupportsOSDPretty();
|
||||
break;
|
||||
case OSDSettings::Type::Disabled:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// Reload just Pretty OSD settings, not everything
|
||||
void OSDBase::ReloadPrettyOSDSettings() {
|
||||
|
||||
@@ -264,7 +286,7 @@ void OSDBase::Stopped() {
|
||||
}
|
||||
|
||||
void OSDBase::StopAfterToggle(const bool stop) {
|
||||
ShowMessage(app_name_, tr("Stop playing after track: %1").arg(stop ? tr("On") : tr("Off")));
|
||||
ShowMessage(QCoreApplication::applicationName(), tr("Stop playing after track: %1").arg(stop ? tr("On") : tr("Off")));
|
||||
}
|
||||
|
||||
void OSDBase::PlaylistFinished() {
|
||||
@@ -272,7 +294,7 @@ void OSDBase::PlaylistFinished() {
|
||||
// We get a PlaylistFinished followed by a Stopped from the player
|
||||
ignore_next_stopped_ = true;
|
||||
|
||||
ShowMessage(app_name_, tr("Playlist finished"));
|
||||
ShowMessage(QCoreApplication::applicationName(), tr("Playlist finished"));
|
||||
|
||||
}
|
||||
|
||||
@@ -290,7 +312,7 @@ void OSDBase::VolumeChanged(const uint value) {
|
||||
}
|
||||
#endif
|
||||
|
||||
ShowMessage(app_name_, message);
|
||||
ShowMessage(QCoreApplication::applicationName(), message);
|
||||
|
||||
}
|
||||
|
||||
@@ -346,7 +368,7 @@ void OSDBase::ShuffleModeChanged(const PlaylistSequence::ShuffleMode mode) {
|
||||
case PlaylistSequence::ShuffleMode::InsideAlbum: current_mode = tr("Shuffle tracks in this album"); break;
|
||||
case PlaylistSequence::ShuffleMode::Albums: current_mode = tr("Shuffle albums"); break;
|
||||
}
|
||||
ShowMessage(app_name_, current_mode);
|
||||
ShowMessage(QCoreApplication::applicationName(), current_mode);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -363,7 +385,7 @@ void OSDBase::RepeatModeChanged(const PlaylistSequence::RepeatMode mode) {
|
||||
case PlaylistSequence::RepeatMode::OneByOne: current_mode = tr("Stop after every track"); break;
|
||||
case PlaylistSequence::RepeatMode::Intro: current_mode = tr("Intro tracks"); break;
|
||||
}
|
||||
ShowMessage(app_name_, current_mode);
|
||||
ShowMessage(QCoreApplication::applicationName(), current_mode);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -437,14 +459,24 @@ void OSDBase::SetPrettyOSDToggleMode(const bool toggle) {
|
||||
pretty_popup_->set_toggle_mode(toggle);
|
||||
}
|
||||
|
||||
bool OSDBase::SupportsNativeNotifications() {
|
||||
bool OSDBase::SupportsNativeNotifications() const {
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
return SupportsTrayPopups();
|
||||
#endif
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
bool OSDBase::SupportsTrayPopups() {
|
||||
bool OSDBase::SupportsTrayPopups() const {
|
||||
return tray_icon_->IsSystemTrayAvailable();
|
||||
}
|
||||
|
||||
bool OSDBase::SupportsOSDPretty() {
|
||||
return QGuiApplication::platformName() != "wayland"_L1;
|
||||
}
|
||||
|
||||
void OSDBase::ShowMessageNative(const QString &summary, const QString &message, const QString &icon, const QImage &image) {
|
||||
|
||||
Q_UNUSED(summary)
|
||||
|
||||
@@ -50,10 +50,11 @@ class OSDBase : public QObject {
|
||||
void ReloadPrettyOSDSettings();
|
||||
void SetPrettyOSDToggleMode(bool toggle);
|
||||
|
||||
virtual bool SupportsNativeNotifications();
|
||||
virtual bool SupportsTrayPopups();
|
||||
|
||||
QString app_name() { return app_name_; }
|
||||
OSDSettings::Type GetSupportedType() const;
|
||||
bool IsTypeSupported(const OSDSettings::Type type) const;
|
||||
virtual bool SupportsNativeNotifications() const;
|
||||
virtual bool SupportsTrayPopups() const;
|
||||
static bool SupportsOSDPretty();
|
||||
|
||||
public Q_SLOTS:
|
||||
void ReloadSettings();
|
||||
@@ -88,7 +89,6 @@ class OSDBase : public QObject {
|
||||
const SharedPtr<SystemTrayIcon> tray_icon_;
|
||||
OSDPretty *pretty_popup_;
|
||||
|
||||
QString app_name_;
|
||||
int timeout_msec_;
|
||||
OSDSettings::Type type_;
|
||||
bool show_on_volume_change_;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user