Compare commits

...

146 Commits
1.2.1 ... 1.2.7

Author SHA1 Message Date
Jonas Kvinge
3fd29c6dcc Release 1.2.7 2025-01-31 16:35:19 +01:00
Jonas Kvinge
4429e9973f StandardItemIconLoader : Rename LoadIcon
Avoids conflict on Windows where windows headers define LoadIcon to LoadIconW with unicode.
2025-01-31 16:20:03 +01:00
Jonas Kvinge
1572d241d5 Replace Windows conflicting "LoadIcon" with "SetIcon"
Windows headers defines LoadIcon to LoadIconW when UNICODE is defined.
2025-01-31 16:10:23 +01:00
Jonas Kvinge
eae7e2a9e6 Update Changelog 2025-01-31 13:09:38 +01:00
Jonas Kvinge
251e5b379b Disable OSD Pretty on Wayland 2025-01-29 22:12:29 +01:00
Jonas Kvinge
0db082fca0 Replace Q_OS_WIN with Q_OS_WIN32 2025-01-28 20:30:43 +01:00
Jonas Kvinge
2799e55076 OSDPretty: Set window title 2025-01-28 20:24:27 +01:00
Jonas Kvinge
440ee43a91 Update Changelog 2025-01-27 14:56:06 +01:00
Jonas Kvinge
98c72ec1f8 import-from-clementine: Minor tidy 2025-01-27 14:55:51 +01:00
Jonas Kvinge
759488ae1a CMake: Reword QPlatformNativeInterface option 2025-01-27 14:55:30 +01:00
Jonas Kvinge
055cb413c9 import-from-clementine: Handle NULL filename and remove FTS
Fixes #1660
2025-01-27 14:41:14 +01:00
Jonas Kvinge
f760b87b58 CI: Add missing dependencies for PPA 2025-01-25 19:37:58 +01:00
Jonas Kvinge
39f228f862 CMake: Add QPA Platform interface as optional component 2025-01-25 18:31:22 +01:00
Jonas Kvinge
a683a279f5 CMake: Define NOMINMAX on Windows 2025-01-24 12:46:49 +01:00
Jonas Kvinge
e800926f57 Ignore -Wcpp for gtest.h/gmock.h 2025-01-24 12:46:48 +01:00
Strawberry Bot
dd9f80d539 New translations 2025-01-24 10:58:02 +01:00
Jonas Kvinge
8484cac4ed Add strawberry_fr_BE.ts 2025-01-24 10:42:40 +01:00
Strawberry Bot
e24097582f New translations 2025-01-24 10:40:12 +01:00
Jonas Kvinge
58fc8c82bb MainWindow: Maximize error dialog when window is shown 2025-01-22 17:51:16 +01:00
Jonas Kvinge
02bb875bb3 ErrorDialog: Only raise window if parent is maximized
Fixes #1627
2025-01-22 17:50:41 +01:00
Jonas Kvinge
5db01482eb Lazy: Fix bool 2025-01-22 17:49:52 +01:00
Jonas Kvinge
719fa6ffb3 MainWindow: Only hide window when system tray and keep running is enabled 2025-01-22 17:26:08 +01:00
Jonas Kvinge
159be5d79e MainWindow: Change close() to hide() in SetHiddenInTray
Otherwise close event is triggered causing Strawberry to quit.
2025-01-19 09:45:13 +01:00
Jonas Kvinge
911237e281 AnalyzerBase: Add missing parameter names 2025-01-19 09:42:00 +01:00
Jonas Kvinge
ae89ca8123 Turn on git revision 2025-01-17 12:34:43 +01:00
Jonas Kvinge
b832675893 Release 1.2.6 2025-01-17 10:27:48 +01:00
Jonas Kvinge
b4cfe636c9 TranscodeDialog: Fix mismatched definition 2025-01-17 09:15:55 +01:00
Jonas Kvinge
e6a0945dfa Call QObject::metaObject 2025-01-17 09:08:59 +01:00
Jonas Kvinge
726c105ed6 MoodbarItemDelegate: Remove delete of data
Memory is deleted in QCache::insert
2025-01-17 08:29:17 +01:00
Jonas Kvinge
d73cbc3a1d EngineBase: Fix mismatched definition 2025-01-17 08:26:11 +01:00
Jonas Kvinge
121f45d3b6 Playlist: Use sizeof playlist pointer to pointer 2025-01-17 07:22:49 +01:00
Jonas Kvinge
3a9ea81929 Queue: Fix sizeof, should be the pointer not the class 2025-01-17 07:12:25 +01:00
Jonas Kvinge
b919472241 Playlist: Correct sizeof 2025-01-17 06:56:08 +01:00
Jonas Kvinge
e8c8b39410 Turn on git revision 2025-01-17 04:29:05 +01:00
Jonas Kvinge
6904efef47 Release 1.2.5 2025-01-17 01:40:38 +01:00
Jonas Kvinge
fafa89baff CI: Disable GIO on Windows 2025-01-16 07:18:31 +01:00
Strawberry Bot
d9062446f5 New translations 2025-01-16 06:39:23 +01:00
Jonas Kvinge
9256b92d8f Update Changelog 2025-01-16 06:32:35 +01:00
Jonas Kvinge
f66459f3cb Make optional feature required unless disabled, add QtSparkle for macOS 2025-01-16 06:22:13 +01:00
Jonas Kvinge
f8ea9631ca Add sparkle 2025-01-15 23:03:40 +01:00
Jonas Kvinge
ab558f87b5 GstEnginePipeline: Use SetStateAsync in finish if needed 2025-01-15 07:01:43 +01:00
Jonas Kvinge
ab73eda2be Add mutex_protected_test 2025-01-15 07:00:28 +01:00
Jonas Kvinge
92f34ff36e mutex_protected: Add more operators 2025-01-15 07:00:16 +01:00
Strawberry Bot
d0bf2d7a9c New translations 2025-01-14 07:10:07 +01:00
Jonas Kvinge
b2cd3afe55 TagReaderClient: Add [[nodiscard]] 2025-01-14 06:36:15 +01:00
Jonas Kvinge
decd0a1dc6 TagReaderClient: Connect TagReaderReplyPtr
Makes sure TagReaderReplyPtr is not deleted too early.

Partial fix for #1633
2025-01-14 06:35:51 +01:00
Jonas Kvinge
3e0a9fa388 SettingsProvider: Use Settings
Fixes #1649
2025-01-13 12:22:31 +01:00
Jonas Kvinge
e5b6c5959f CMake: Use find_package for qtsparkle 2025-01-12 04:16:17 +01:00
Jonas Kvinge
8a9db5440d rpm: Enable unit tests on Fedora 2025-01-11 01:39:26 +01:00
Jonas Kvinge
28a25c5763 CMake: Fix add_test 2025-01-11 01:37:06 +01:00
dependabot[bot]
ea49fbcbee Bump vmactions/openbsd-vm from 1.1.5 to 1.1.6
Bumps [vmactions/openbsd-vm](https://github.com/vmactions/openbsd-vm) from 1.1.5 to 1.1.6.
- [Release notes](https://github.com/vmactions/openbsd-vm/releases)
- [Commits](https://github.com/vmactions/openbsd-vm/compare/v1.1.5...v1.1.6)

---
updated-dependencies:
- dependency-name: vmactions/openbsd-vm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-11 00:37:52 +01:00
Jonas Kvinge
d9807b358e GioLister: Fix use of deprecated functions 2025-01-11 00:35:24 +01:00
Jonas Kvinge
3e53d2b237 CI: Add -Wno-maybe-uninitialized for Fedora 2025-01-11 00:35:24 +01:00
Jonas Kvinge
9122881f74 CI: Bump GCC on openSUSE Leap 2025-01-11 00:35:24 +01:00
Jonas Kvinge
9c36d7fd43 rpm: Add BUILD_WERROR 2025-01-11 00:35:24 +01:00
Jonas Kvinge
a4a365cbee CI: Set RPM_BUILD_NCPUS to 4 2025-01-11 00:35:24 +01:00
Jonas Kvinge
00f06b22b8 CMake: Check for gmock 2025-01-11 00:35:24 +01:00
Jonas Kvinge
e2d8838fca rpm: Run unit tests 2025-01-11 00:35:24 +01:00
Jonas Kvinge
9427691f39 CI: Install gtest and gmock for openSUSE, Fedora and Mageia 2025-01-11 00:35:24 +01:00
Jonas Kvinge
bebdcc4e7f CollectionModel: Simply data function 2025-01-10 17:47:30 +01:00
Jonas Kvinge
041f761921 test_utils: Fix Q_ASSERT 2025-01-10 15:35:15 +01:00
Jonas Kvinge
1435ae6dc0 Turn on git revision 2025-01-10 15:08:04 +01:00
Jonas Kvinge
33ae53a90f Release 1.2.4 2025-01-10 02:26:41 +01:00
Strawberry Bot
01c28867b7 New translations 2025-01-10 02:10:44 +01:00
Jonas Kvinge
08fb2ae331 Update Changelog 2025-01-10 01:58:19 +01:00
Jonas Kvinge
99970f9e52 Update strawberry_en_US.ts 2025-01-10 01:58:19 +01:00
Jonas Kvinge
bc206f43b4 TranscodeDialog: Minor adjustments 2025-01-10 01:58:19 +01:00
dependabot[bot]
018448159c Bump vmactions/freebsd-vm from 1.1.7 to 1.1.8
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.1.7 to 1.1.8.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.1.7...v1.1.8)

---
updated-dependencies:
- dependency-name: vmactions/freebsd-vm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-10 01:27:57 +01:00
Jonas Kvinge
81fe90bdef Update Changelog 2025-01-10 01:07:32 +01:00
Jonas Kvinge
319558c535 tests: Fixed ignored return value 2025-01-10 00:58:41 +01:00
Jonas Kvinge
9095b0d6b2 Always run MSVC runtime installer 2025-01-08 19:34:27 +01:00
Jonas Kvinge
558eae1ca1 Remove unneeded slots 2025-01-08 04:57:17 +01:00
Jonas Kvinge
2c64f05cea MoodbarProxyStyle: Add const 2025-01-08 04:54:45 +01:00
Jonas Kvinge
c80e7071a1 StandardPaths: Fix namespace 2025-01-07 22:13:24 +01:00
Jonas Kvinge
038e679000 SmartPlaylistWizard: Apply dark mode workaround for all styles except vista
Fixes #1639
2025-01-07 22:04:28 +01:00
Jonas Kvinge
72447fecfb StandardPaths: Remove inheritance 2025-01-07 21:40:06 +01:00
Jonas Kvinge
c7830f6f05 MoodbarPipeline: Cleanup on finish 2025-01-07 01:09:49 +01:00
Jonas Kvinge
af525e42b6 MoodbarPipeline: Remove Q_ASSERT 2025-01-06 21:28:44 +01:00
Jonas Kvinge
eb83f23125 MoodbarPipeline: Use bytearray directly 2025-01-06 21:28:33 +01:00
Jonas Kvinge
7527d2ea9a MoodbarLoader: Set object name 2025-01-06 18:11:38 +01:00
Jonas Kvinge
69d38879d2 Use QCoreApplication::applicationName() directly 2025-01-06 00:38:51 +01:00
Jonas Kvinge
cbce9f7191 Override config, data and cache location 2025-01-05 23:45:29 +01:00
Jonas Kvinge
f938129d81 CI: Fix deb copy command 2025-01-05 21:22:08 +01:00
Jonas Kvinge
415a40ea04 FilterParser: Ignore space after operator 2025-01-05 20:25:48 +01:00
Jonas Kvinge
6e7aaed4ee Use QSharedPointer for GstEnginePipeline 2025-01-05 19:03:16 +01:00
Jonas Kvinge
7afae70bb0 GstEnginePipeline: Make sure all set states are finished before finishing pipeline 2025-01-05 18:58:03 +01:00
Jonas Kvinge
be8097919b mutex_protected: Add operator ++ and -- 2025-01-05 18:56:22 +01:00
Jonas Kvinge
1990a42e1d CI: Don't run build on l10n_master branch 2025-01-05 18:53:19 +01:00
Kyle Hopkins
8fcee4511d TranscodeDialog: update to optionally preserve directory structure 2025-01-05 18:34:14 +01:00
Leandro Matheus
24af1be666 Subsonic: Add support for using album id to retrieve album covers 2025-01-05 18:30:24 +01:00
Jonas Kvinge
8302a95bc1 Use shared pointers for moodbar pipelines
Possible fix for #1633
2025-01-05 18:28:41 +01:00
Jonas Kvinge
36a8ab49a0 MoodbarItemDelegate: Format comment 2025-01-05 03:51:00 +01:00
Jonas Kvinge
5c64dc9c4d MoodbarItemDelegate: Delete data if it fails to insert to cache 2025-01-05 03:50:51 +01:00
Strawberry Bot
c271743208 New translations 2025-01-05 00:22:50 +01:00
Jonas Kvinge
ee49b1ddc8 Update strawberry_en_US.ts 2025-01-04 04:56:08 +01:00
Jonas Kvinge
bf98633f16 Load XSPF title as playlist name 2025-01-04 04:52:17 +01:00
Jonas Kvinge
e2a928f2dc Save XSPF playlist with title
Fixes #1624
2025-01-04 03:48:53 +01:00
Jonas Kvinge
47d3312a6b PlaylistManager: Remove slash from playlist filename 2025-01-04 03:46:03 +01:00
Jonas Kvinge
4f97325953 CollectionSettingsPage: Fix string conversion 2025-01-04 03:32:50 +01:00
Jonas Kvinge
91e8fe0943 ScrobblerSettingsPage: Add tooltip 2025-01-04 03:15:38 +01:00
Jonas Kvinge
dc5894b38a CollectionWatcher: Ignore special filesystem paths
Fixes #1615
2025-01-04 03:06:46 +01:00
Jonas Kvinge
52ee50a2a4 CollectionSettingsPage: Add check for filesystem type 2025-01-04 02:58:59 +01:00
Jonas Kvinge
f545b028ee Add filesystem constants 2025-01-04 02:58:17 +01:00
Jonas Kvinge
a96627c5a9 Playlist: Invalidate deleted songs in main thread
Fixes #1625
2025-01-04 00:16:36 +01:00
Jonas Kvinge
a13fc31f83 PlaylistManager: Remove unused InvalidateDeletedSongs function 2025-01-04 00:09:26 +01:00
Jonas Kvinge
9ee5c8dc17 Playlist: Update copyright 2025-01-03 23:41:43 +01:00
Jonas Kvinge
82cd425ece Playlist: Add const 2025-01-03 23:41:30 +01:00
Jonas Kvinge
3b02d364ba Playlist: Rename some variables 2025-01-03 23:41:15 +01:00
dependabot[bot]
73e7947487 Bump vmactions/openbsd-vm from 1.1.4 to 1.1.5
Bumps [vmactions/openbsd-vm](https://github.com/vmactions/openbsd-vm) from 1.1.4 to 1.1.5.
- [Release notes](https://github.com/vmactions/openbsd-vm/releases)
- [Commits](https://github.com/vmactions/openbsd-vm/compare/v1.1.4...v1.1.5)

---
updated-dependencies:
- dependency-name: vmactions/openbsd-vm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-30 17:38:02 +01:00
dependabot[bot]
cfc9a43b88 Bump vmactions/freebsd-vm from 1.1.6 to 1.1.7
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.1.6 to 1.1.7.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.1.6...v1.1.7)

---
updated-dependencies:
- dependency-name: vmactions/freebsd-vm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-30 17:37:32 +01:00
dependabot[bot]
969500023a Bump vmactions/freebsd-vm from 1.1.5 to 1.1.6
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.1.5 to 1.1.6.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.1.5...v1.1.6)

---
updated-dependencies:
- dependency-name: vmactions/freebsd-vm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-26 00:44:32 +01:00
Jonas Kvinge
53c72d4f8e CollectionModel: Don't use artist sort text for album 2024-12-17 22:38:04 +01:00
Jonas Kvinge
f971c92f32 Move debian back to source dir
PPA failed to build
2024-12-17 21:57:37 +01:00
Strawberry Bot
82156e8a13 New translations 2024-12-17 00:01:04 +01:00
dependabot[bot]
6a6285861e Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 23:59:25 +01:00
Jonas Kvinge
dae8c8730b debian: Remove whitespaces 2024-12-14 18:33:40 +01:00
Jonas Kvinge
20b47eae8f CI: Use -j 4 for Ubuntu 2024-12-14 18:33:29 +01:00
Jonas Kvinge
2ce8220d88 Move generated files to binary directory 2024-12-14 17:59:26 +01:00
Jonas Kvinge
35b0b5df57 CMake: Fix backtrace linking 2024-12-14 15:47:45 +01:00
Jonas Kvinge
63631d6b0c CMake: Fix backtrace linking 2024-12-14 05:30:31 +01:00
Jonas Kvinge
21ab2ef1a7 CI: Add FreeBSD and OpenBSD 2024-12-14 05:01:40 +01:00
Jonas Kvinge
a3f96d2b85 CMake: Link Backtrace 2024-12-14 05:01:14 +01:00
Jonas Kvinge
e2c1cb0116 Formatting 2024-12-14 00:55:53 +01:00
Jonas Kvinge
07e295776b Add Spotify to scrobbler 2024-12-10 01:29:57 +01:00
Jonas Kvinge
8604a39d94 Turn on git revision 2024-12-08 17:29:14 +01:00
Jonas Kvinge
315240fec7 Release 1.2.3 2024-12-08 16:21:28 +01:00
Jonas Kvinge
fbc6d326f8 Update Changelog 2024-12-08 16:15:24 +01:00
Strawberry Bot
c082377e7a New translations 2024-12-07 19:21:29 +01:00
Jonas Kvinge
448445a38a CI: Replace redhat-lsb-core with lsb_release 2024-12-07 14:42:15 +01:00
Jonas Kvinge
18f835d7e5 Mpris2: Check for valid current row 2024-12-07 14:36:44 +01:00
Jonas Kvinge
fd427dac29 Handle missing HTTP status code 2024-12-07 14:02:59 +01:00
Jonas Kvinge
e1afe03d51 Check for valid http status code 2024-12-07 00:32:06 +01:00
Jonas Kvinge
1b49653974 Update Changelog 2024-12-06 23:55:25 +01:00
Jonas Kvinge
d66126f998 GstEngine: Add missing seek
Fixes #1568
2024-12-06 23:44:27 +01:00
Jonas Kvinge
0fff5f672a Rename variables 2024-12-06 23:43:44 +01:00
Strawberry Bot
2726f01fb3 New translations 2024-12-04 22:41:27 +01:00
Jonas Kvinge
9a74fce53d DiscogsCoverProvider: Use anonymous namespace for constants 2024-12-02 22:01:00 +01:00
Jonas Kvinge
b3be8387f1 Song: Add .zst to rejected file extensions
Fixes #1612
2024-11-29 23:37:49 +01:00
Jonas Kvinge
d396cb515d Include cstddef before libcdio includes
Fixes #1610
2024-11-29 22:50:49 +01:00
Jonas Kvinge
2548b4648e Turn on git revision 2024-11-23 19:34:13 +01:00
Jonas Kvinge
df0ec6b709 Release 1.2.2 2024-11-23 17:25:42 +01:00
Jonas Kvinge
376af26f0e Playlist: Move new QMimeData 2024-11-23 15:19:31 +01:00
Jonas Kvinge
9bff55e1ee PlaylistView: Ignore invalid QHeaderView::sectionResized
Workaround a possible Qt bug: moving a song in the playlist triggers `QHeaderView::sectionResized` with the state reset for each column, this causes an an invalid state for the last column since SetHeaderState is called before the last column state is restored, which again is used when restoring the playlist columns after switching to different playlist. To workaround this, only call SetHeaderState when the new column size is not zero.
2024-11-23 11:07:15 +01:00
Jonas Kvinge
8f7e29f503 PlaylistView: Use constants 2024-11-23 10:56:29 +01:00
Jonas Kvinge
c3aa885a0f SmartPlaylistSearchPreview: Remove early SetItemDelegates
It's called too early before MoodbarLoader is set, Init() already calls SetItemDelegates.

Fixes #1609
2024-11-22 17:04:42 +01:00
Jonas Kvinge
77ea5729c3 Turn on git revision 2024-11-21 18:34:29 +01:00
219 changed files with 13348 additions and 5780 deletions

View File

@@ -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
View File

@@ -11,6 +11,4 @@
/out
/CMakeSettings.json
/dist/scripts/maketarball.sh
/dist/unix/strawberry.spec
/debian/changelog
/dist/macos/Info.plist

View File

@@ -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.")

View File

@@ -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,

View File

@@ -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()

View File

@@ -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}

View File

@@ -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)

View File

@@ -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()

View File

@@ -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
View File

@@ -0,0 +1,3 @@
dist/scripts/maketarball.sh
CMakeCache.txt
CMakeFiles/

1
debian/compat vendored
View File

@@ -1 +0,0 @@
11

6
debian/control vendored
View File

@@ -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
View File

@@ -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
View File

@@ -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()

View File

@@ -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>

View File

@@ -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

View File

@@ -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"/>

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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_;

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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_;

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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";

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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));

View File

@@ -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() {

View File

@@ -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_;

View File

@@ -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);

View File

@@ -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) {}

View File

@@ -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);
};

View File

@@ -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)
};

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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
View 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

View 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];
}
}

View File

@@ -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);

View File

@@ -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);

View 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
View 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

View File

@@ -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()));
}

View File

@@ -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
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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),

View File

@@ -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_;

View File

@@ -24,9 +24,6 @@
#include "config.h"
#include <cdio/cdio.h>
#include <gst/audio/gstaudiocdsrc.h>
#include <QObject>
#include <QString>
#include <QStringList>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -48,6 +48,7 @@ class ErrorDialog : public QDialog {
private:
void UpdateContent();
QWidget *parent_;
Ui_ErrorDialog *ui_;
QStringList current_messages_;

View File

@@ -53,7 +53,7 @@ using namespace Qt::Literals::StringLiterals;
DeviceFinders::DeviceFinders(QObject *parent) : QObject(parent) {
setObjectName(QLatin1String(metaObject()->className()));
setObjectName(QLatin1String(QObject::metaObject()->className()));
}

View File

@@ -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;
}

View File

@@ -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_;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -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_;

View File

@@ -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

View File

@@ -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(); }

View File

@@ -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_;

View File

@@ -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();

View File

@@ -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

View File

@@ -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();

View File

@@ -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_;

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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_;

View File

@@ -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) {

View File

@@ -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

View File

@@ -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();

View File

@@ -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:

View File

@@ -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()) {

View File

@@ -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_;

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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