Compare commits
17 Commits
copilot/fi
...
copilot/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2afa8fd66 | ||
|
|
4da4c9e267 | ||
|
|
02c1596ff4 | ||
|
|
597f983c92 | ||
|
|
0e0117b19b | ||
|
|
4e0cc1c0da | ||
|
|
f77e92d634 | ||
|
|
f25cdb3431 | ||
|
|
604dd2dbde | ||
|
|
25065ba98f | ||
|
|
7b16ec62bb | ||
|
|
d8f31592b9 | ||
|
|
80bb0f476d | ||
|
|
b7222ac85c | ||
|
|
241bca0828 | ||
|
|
90d86b10a3 | ||
|
|
4130c6670f |
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@@ -739,12 +739,18 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- name: Free disk space
|
||||
run: |
|
||||
df -h
|
||||
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc
|
||||
sudo apt-get clean
|
||||
df -h
|
||||
- name: Build FreeBSD
|
||||
id: build-freebsd
|
||||
uses: vmactions/freebsd-vm@v1.3.0
|
||||
uses: vmactions/freebsd-vm@v1.3.2
|
||||
with:
|
||||
usesh: true
|
||||
mem: 4096
|
||||
mem: 8192
|
||||
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 sparsehash rapidjson
|
||||
run: |
|
||||
set -e
|
||||
@@ -766,7 +772,7 @@ jobs:
|
||||
submodules: recursive
|
||||
- name: Build OpenBSD
|
||||
id: build-openbsd
|
||||
uses: vmactions/openbsd-vm@v1.2.5
|
||||
uses: vmactions/openbsd-vm@v1.2.9
|
||||
with:
|
||||
usesh: true
|
||||
mem: 4096
|
||||
|
||||
@@ -295,6 +295,12 @@ if(UNIX AND NOT APPLE)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
optional_component(WINDOWS_MEDIA_CONTROLS ON "Windows Media Transport Controls"
|
||||
DEPENDS "MSVC compiler" MSVC
|
||||
)
|
||||
endif()
|
||||
|
||||
optional_component(SONGFINGERPRINTING ON "Song fingerprinting and tracking"
|
||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||
)
|
||||
@@ -1294,6 +1300,7 @@ endif()
|
||||
optional_source(HAVE_ALSA SOURCES src/engine/alsadevicefinder.cpp src/engine/alsapcmdevicefinder.cpp)
|
||||
optional_source(HAVE_PULSE SOURCES src/engine/pulsedevicefinder.cpp)
|
||||
optional_source(MSVC SOURCES src/engine/uwpdevicefinder.cpp src/engine/asiodevicefinder.cpp)
|
||||
optional_source(HAVE_WINDOWS_MEDIA_CONTROLS SOURCES src/core/windowsmediacontroller.cpp HEADERS src/core/windowsmediacontroller.h)
|
||||
optional_source(HAVE_CHROMAPRINT SOURCES src/engine/chromaprinter.cpp)
|
||||
|
||||
optional_source(HAVE_MUSICBRAINZ
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, 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
|
||||
@@ -189,6 +189,26 @@ void CollectionLibrary::ReloadSettings() {
|
||||
|
||||
}
|
||||
|
||||
void CollectionLibrary::CurrentSongChanged(const Song &song) {
|
||||
|
||||
current_song_url_ = song.url();
|
||||
|
||||
if (!pending_song_saves_.isEmpty()) {
|
||||
SavePendingPlaycountsAndRatings();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionLibrary::Stopped() {
|
||||
|
||||
current_song_url_ = QUrl();
|
||||
|
||||
if (!pending_song_saves_.isEmpty()) {
|
||||
SavePendingPlaycountsAndRatings();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionLibrary::SyncPlaycountAndRatingToFilesAsync() {
|
||||
|
||||
(void)QtConcurrent::run(&CollectionLibrary::SyncPlaycountAndRatingToFiles, this);
|
||||
@@ -212,18 +232,85 @@ void CollectionLibrary::SyncPlaycountAndRatingToFiles() {
|
||||
|
||||
}
|
||||
|
||||
void CollectionLibrary::SongsPlaycountChanged(const SongList &songs, const bool save_tags) const {
|
||||
void CollectionLibrary::SongsPlaycountChanged(const SongList &songs, const bool save_tags) {
|
||||
|
||||
if (save_tags || save_playcounts_to_files_) {
|
||||
tagreader_client_->SaveSongsPlaycountAsync(songs);
|
||||
SongList songs_to_save_now;
|
||||
for (const Song &song : songs) {
|
||||
if (song.url().isLocalFile() && song.url() == current_song_url_ &&
|
||||
(song.filetype() == Song::FileType::OggFlac || song.filetype() == Song::FileType::OggVorbis || song.filetype() == Song::FileType::OggOpus)) {
|
||||
qLog(Debug) << "Deferring playcount save for currently playing file" << song.url().toLocalFile();
|
||||
if (pending_song_saves_.contains(song.url())) {
|
||||
SharedPtr<PendingSongSave> pending_song_save = pending_song_saves_[song.url()];
|
||||
pending_song_save->save_playcount = true;
|
||||
pending_song_save->song.set_playcount(song.playcount());
|
||||
}
|
||||
else {
|
||||
SharedPtr<PendingSongSave> pending_song_save = make_shared<PendingSongSave>();
|
||||
pending_song_save->save_playcount = true;
|
||||
pending_song_save->song = song;
|
||||
pending_song_saves_.insert(song.url(), pending_song_save);
|
||||
}
|
||||
}
|
||||
else {
|
||||
songs_to_save_now << song;
|
||||
}
|
||||
}
|
||||
if (!songs_to_save_now.isEmpty()) {
|
||||
tagreader_client_->SaveSongsPlaycountAsync(songs_to_save_now);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionLibrary::SongsRatingChanged(const SongList &songs, const bool save_tags) const {
|
||||
void CollectionLibrary::SongsRatingChanged(const SongList &songs, const bool save_tags) {
|
||||
|
||||
if (save_tags || save_ratings_to_files_) {
|
||||
tagreader_client_->SaveSongsRatingAsync(songs);
|
||||
SongList songs_to_save_now;
|
||||
for (const Song &song : songs) {
|
||||
if (song.url().isLocalFile() && song.url() == current_song_url_ &&
|
||||
(song.filetype() == Song::FileType::OggFlac || song.filetype() == Song::FileType::OggVorbis || song.filetype() == Song::FileType::OggOpus)) {
|
||||
qLog(Debug) << "Deferring rating save for currently playing file" << song.url().toLocalFile();
|
||||
if (pending_song_saves_.contains(song.url())) {
|
||||
SharedPtr<PendingSongSave> pending_song_save = pending_song_saves_[song.url()];
|
||||
pending_song_save->save_rating = true;
|
||||
pending_song_save->song.set_rating(song.rating());
|
||||
}
|
||||
else {
|
||||
SharedPtr<PendingSongSave> pending_song_save = make_shared<PendingSongSave>();
|
||||
pending_song_save->save_rating = true;
|
||||
pending_song_save->song = song;
|
||||
pending_song_saves_.insert(song.url(), pending_song_save);
|
||||
}
|
||||
}
|
||||
else {
|
||||
songs_to_save_now << song;
|
||||
}
|
||||
}
|
||||
if (!songs_to_save_now.isEmpty()) {
|
||||
tagreader_client_->SaveSongsRatingAsync(songs_to_save_now);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionLibrary::SavePendingPlaycountsAndRatings() {
|
||||
|
||||
for (auto it = pending_song_saves_.constBegin(); it != pending_song_saves_.constEnd();) {
|
||||
const QUrl url = it.key();
|
||||
SharedPtr<PendingSongSave> pending_song_save = it.value();
|
||||
if (url == current_song_url_) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
qLog(Debug) << "Saving deferred playcount/rating for" << url.toLocalFile();
|
||||
if (pending_song_save->save_playcount) {
|
||||
tagreader_client_->SaveSongsPlaycountAsync(SongList() << pending_song_save->song);
|
||||
}
|
||||
if (pending_song_save->save_rating) {
|
||||
tagreader_client_->SaveSongsRatingAsync(SongList() << pending_song_save->song);
|
||||
}
|
||||
it = pending_song_saves_.erase(it);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, 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 <QList>
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
@@ -71,6 +72,7 @@ class CollectionLibrary : public QObject {
|
||||
|
||||
private:
|
||||
void SyncPlaycountAndRatingToFiles();
|
||||
void SavePendingPlaycountsAndRatings();
|
||||
|
||||
public Q_SLOTS:
|
||||
void ReloadSettings();
|
||||
@@ -84,16 +86,26 @@ class CollectionLibrary : public QObject {
|
||||
|
||||
void IncrementalScan();
|
||||
|
||||
void CurrentSongChanged(const Song &song);
|
||||
void Stopped();
|
||||
|
||||
private Q_SLOTS:
|
||||
void ExitReceived();
|
||||
void SongsPlaycountChanged(const SongList &songs, const bool save_tags = false) const;
|
||||
void SongsRatingChanged(const SongList &songs, const bool save_tags = false) const;
|
||||
void SongsPlaycountChanged(const SongList &songs, const bool save_tags = false);
|
||||
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
|
||||
|
||||
Q_SIGNALS:
|
||||
void Error(const QString &error);
|
||||
void ExitFinished();
|
||||
|
||||
private:
|
||||
class PendingSongSave {
|
||||
public:
|
||||
Song song;
|
||||
bool save_playcount = false;
|
||||
bool save_rating = false;
|
||||
};
|
||||
|
||||
const SharedPtr<TaskManager> task_manager_;
|
||||
const SharedPtr<TagReaderClient> tagreader_client_;
|
||||
|
||||
@@ -111,6 +123,10 @@ class CollectionLibrary : public QObject {
|
||||
|
||||
bool save_playcounts_to_files_;
|
||||
bool save_ratings_to_files_;
|
||||
|
||||
QUrl current_song_url_;
|
||||
|
||||
QMap<QUrl, SharedPtr<PendingSongSave>> pending_song_saves_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1209,49 +1209,41 @@ QString CollectionModel::ContainerKey(const GroupBy group_by, const Song &song,
|
||||
switch (group_by) {
|
||||
case GroupBy::AlbumArtist:
|
||||
key = TextOrUnknown(song.effective_albumartist());
|
||||
if (!song.effective_albumartistsort().isEmpty() && song.effective_albumartistsort() != song.effective_albumartist()) key.append(QLatin1Char('-') + TextOrUnknown(song.effective_albumartistsort()));
|
||||
has_unique_album_identifier = true;
|
||||
break;
|
||||
case GroupBy::Artist:
|
||||
key = TextOrUnknown(song.artist());
|
||||
if (!song.artistsort().isEmpty() && song.artistsort() != song.artist()) key.append(QLatin1Char('-') + TextOrUnknown(song.artistsort()));
|
||||
has_unique_album_identifier = true;
|
||||
break;
|
||||
case GroupBy::Album:
|
||||
key = TextOrUnknown(song.album());
|
||||
if (!song.albumsort().isEmpty() && song.albumsort() != song.album()) key.append(QLatin1Char('-') + TextOrUnknown(song.albumsort()));
|
||||
if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
|
||||
if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
|
||||
break;
|
||||
case GroupBy::AlbumDisc:
|
||||
key = TextOrUnknown(song.album());
|
||||
if (!song.albumsort().isEmpty() && song.albumsort() != song.album()) key.append(QLatin1Char('-') + TextOrUnknown(song.albumsort()));
|
||||
key.append(QLatin1Char('-') + SortTextForNumber(song.disc()));
|
||||
if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
|
||||
if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
|
||||
break;
|
||||
case GroupBy::YearAlbum:
|
||||
key = SortTextForYear(song.year()) + QLatin1Char('-') + TextOrUnknown(song.album());
|
||||
if (!song.albumsort().isEmpty() && song.albumsort() != song.album()) key.append(QLatin1Char('-') + TextOrUnknown(song.albumsort()));
|
||||
if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
|
||||
if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
|
||||
break;
|
||||
case GroupBy::YearAlbumDisc:
|
||||
key = SortTextForYear(song.year()) + QLatin1Char('-') + TextOrUnknown(song.album());
|
||||
if (!song.albumsort().isEmpty() && song.albumsort() != song.album()) key.append(QLatin1Char('-') + TextOrUnknown(song.albumsort()));
|
||||
key.append(QLatin1Char('-') + SortTextForNumber(song.disc()));
|
||||
if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
|
||||
if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
|
||||
break;
|
||||
case GroupBy::OriginalYearAlbum:
|
||||
key = SortTextForYear(song.effective_originalyear()) + QLatin1Char('-') + TextOrUnknown(song.album());
|
||||
if (!song.albumsort().isEmpty() && song.albumsort() != song.album()) key.append(QLatin1Char('-') + TextOrUnknown(song.albumsort()));
|
||||
if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
|
||||
if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
|
||||
break;
|
||||
case GroupBy::OriginalYearAlbumDisc:
|
||||
key = SortTextForYear(song.effective_originalyear()) + QLatin1Char('-') + TextOrUnknown(song.album());
|
||||
if (!song.albumsort().isEmpty() && song.albumsort() != song.album()) key.append(QLatin1Char('-') + TextOrUnknown(song.albumsort()));
|
||||
key.append(QLatin1Char('-') + SortTextForNumber(song.disc()));
|
||||
if (!song.album_id().isEmpty()) key.append(QLatin1Char('-') + song.album_id());
|
||||
if (options_active_.separate_albums_by_grouping && !song.grouping().isEmpty()) key.append(QLatin1Char('-') + song.grouping());
|
||||
@@ -1270,12 +1262,10 @@ QString CollectionModel::ContainerKey(const GroupBy group_by, const Song &song,
|
||||
break;
|
||||
case GroupBy::Composer:
|
||||
key = TextOrUnknown(song.composer());
|
||||
if (!song.composersort().isEmpty() && song.composersort() != song.composer()) key.append(QLatin1Char('-') + song.composersort());
|
||||
has_unique_album_identifier = true;
|
||||
break;
|
||||
case GroupBy::Performer:
|
||||
key = TextOrUnknown(song.performer());
|
||||
if (!song.performersort().isEmpty() && song.performersort() != song.performer()) key.append(QLatin1Char('-') + song.performersort());
|
||||
has_unique_album_identifier = true;
|
||||
break;
|
||||
case GroupBy::Grouping:
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#cmakedefine HAVE_GIO_UNIX
|
||||
#cmakedefine HAVE_DBUS
|
||||
#cmakedefine HAVE_MPRIS2
|
||||
#cmakedefine HAVE_WINDOWS_MEDIA_CONTROLS
|
||||
#cmakedefine HAVE_UDISKS2
|
||||
#cmakedefine HAVE_AUDIOCD
|
||||
#cmakedefine HAVE_MTP
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
#include <QShortcut>
|
||||
#include <QMessageBox>
|
||||
#include <QErrorMessage>
|
||||
#include <QSettings>
|
||||
#include <QColor>
|
||||
#include <QFrame>
|
||||
#include <QItemSelectionModel>
|
||||
@@ -697,6 +696,9 @@ MainWindow::MainWindow(Application *app,
|
||||
QObject::connect(&*app_->task_manager(), &TaskManager::PauseCollectionWatchers, &*app_->collection(), &CollectionLibrary::PauseWatcher);
|
||||
QObject::connect(&*app_->task_manager(), &TaskManager::ResumeCollectionWatchers, &*app_->collection(), &CollectionLibrary::ResumeWatcher);
|
||||
|
||||
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, &*app_->collection(), &CollectionLibrary::CurrentSongChanged);
|
||||
QObject::connect(&*app_->player(), &Player::Stopped, &*app_->collection(), &CollectionLibrary::Stopped);
|
||||
|
||||
QObject::connect(&*app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, &*app_->current_albumcover_loader(), &CurrentAlbumCoverLoader::LoadAlbumCover);
|
||||
QObject::connect(&*app_->current_albumcover_loader(), &CurrentAlbumCoverLoader::AlbumCoverLoaded, this, &MainWindow::AlbumCoverLoaded);
|
||||
QObject::connect(album_cover_choice_controller_, &AlbumCoverChoiceController::Error, this, &MainWindow::ShowErrorDialog);
|
||||
@@ -977,27 +979,28 @@ MainWindow::MainWindow(Application *app,
|
||||
|
||||
// Load settings
|
||||
qLog(Debug) << "Loading settings";
|
||||
settings_.beginGroup(MainWindowSettings::kSettingsGroup);
|
||||
Settings settings;
|
||||
settings.beginGroup(MainWindowSettings::kSettingsGroup);
|
||||
|
||||
// Set last used geometry to position window on the correct monitor
|
||||
// Set window state only if the window was last maximized
|
||||
if (settings_.contains("geometry")) {
|
||||
restoreGeometry(settings_.value("geometry").toByteArray());
|
||||
if (settings.contains("geometry")) {
|
||||
restoreGeometry(settings.value("geometry").toByteArray());
|
||||
}
|
||||
|
||||
if (!settings_.contains(MainWindowSettings::kSplitterState) || !ui_->splitter->restoreState(settings_.value(MainWindowSettings::kSplitterState).toByteArray())) {
|
||||
if (!settings.contains(MainWindowSettings::kSplitterState) || !ui_->splitter->restoreState(settings.value(MainWindowSettings::kSplitterState).toByteArray())) {
|
||||
ui_->splitter->setSizes(QList<int>() << 20 << (width() - 20));
|
||||
}
|
||||
|
||||
ui_->tabs->setCurrentIndex(settings_.value("current_tab", 1).toInt());
|
||||
ui_->tabs->setCurrentIndex(settings.value("current_tab", 1).toInt());
|
||||
FancyTabWidget::Mode default_mode = FancyTabWidget::Mode::LargeSidebar;
|
||||
FancyTabWidget::Mode tab_mode = static_cast<FancyTabWidget::Mode>(settings_.value("tab_mode", static_cast<int>(default_mode)).toInt());
|
||||
FancyTabWidget::Mode tab_mode = static_cast<FancyTabWidget::Mode>(settings.value("tab_mode", static_cast<int>(default_mode)).toInt());
|
||||
if (tab_mode == FancyTabWidget::Mode::None) tab_mode = default_mode;
|
||||
ui_->tabs->SetMode(tab_mode);
|
||||
|
||||
TabSwitched();
|
||||
|
||||
file_view_->SetPath(settings_.value("file_path", QDir::homePath()).toString());
|
||||
file_view_->SetPath(settings.value("file_path", QDir::homePath()).toString());
|
||||
|
||||
// Users often collapse one side of the splitter by mistake and don't know how to restore it. This must be set after the state is restored above.
|
||||
ui_->splitter->setChildrenCollapsible(false);
|
||||
@@ -1040,13 +1043,13 @@ MainWindow::MainWindow(Application *app,
|
||||
case BehaviourSettings::StartupBehaviour::Remember:
|
||||
default:{
|
||||
|
||||
was_maximized_ = settings_.value(MainWindowSettings::kMaximized, true).toBool();
|
||||
was_maximized_ = settings.value(MainWindowSettings::kMaximized, true).toBool();
|
||||
if (was_maximized_) setWindowState(windowState() | Qt::WindowMaximized);
|
||||
|
||||
was_minimized_ = settings_.value(MainWindowSettings::kMinimized, false).toBool();
|
||||
was_minimized_ = settings.value(MainWindowSettings::kMinimized, false).toBool();
|
||||
if (was_minimized_) setWindowState(windowState() | Qt::WindowMinimized);
|
||||
|
||||
if (!systemtrayicon_->IsSystemTrayAvailable() || !systemtrayicon_->isVisible() || !settings_.value(MainWindowSettings::kHidden, false).toBool()) {
|
||||
if (!systemtrayicon_->IsSystemTrayAvailable() || !systemtrayicon_->isVisible() || !settings.value(MainWindowSettings::kHidden, false).toBool()) {
|
||||
show();
|
||||
}
|
||||
break;
|
||||
@@ -1054,7 +1057,7 @@ MainWindow::MainWindow(Application *app,
|
||||
}
|
||||
#endif
|
||||
|
||||
bool show_sidebar = settings_.value(MainWindowSettings::kShowSidebar, true).toBool();
|
||||
bool show_sidebar = settings.value(MainWindowSettings::kShowSidebar, true).toBool();
|
||||
ui_->sidebar_layout->setVisible(show_sidebar);
|
||||
ui_->action_toggle_show_sidebar->setChecked(show_sidebar);
|
||||
|
||||
@@ -1225,7 +1228,9 @@ void MainWindow::ReloadSettings() {
|
||||
|
||||
osd_->ReloadSettings();
|
||||
|
||||
album_cover_choice_controller_->search_cover_auto_action()->setChecked(settings_.value(MainWindowSettings::kSearchForCoverAuto, true).toBool());
|
||||
s.beginGroup(MainWindowSettings::kSettingsGroup);
|
||||
album_cover_choice_controller_->search_cover_auto_action()->setChecked(s.value(MainWindowSettings::kSearchForCoverAuto, true).toBool());
|
||||
s.endGroup();
|
||||
|
||||
#ifdef HAVE_SUBSONIC
|
||||
s.beginGroup(SubsonicSettings::kSettingsGroup);
|
||||
@@ -1342,8 +1347,11 @@ void MainWindow::SaveSettings() {
|
||||
ui_->playlist->view()->SaveSettings();
|
||||
app_->scrobbler()->WriteCache();
|
||||
|
||||
settings_.setValue(MainWindowSettings::kShowSidebar, ui_->action_toggle_show_sidebar->isChecked());
|
||||
settings_.setValue(MainWindowSettings::kSearchForCoverAuto, album_cover_choice_controller_->search_cover_auto_action()->isChecked());
|
||||
Settings s;
|
||||
s.beginGroup(MainWindowSettings::kSettingsGroup);
|
||||
s.setValue(MainWindowSettings::kShowSidebar, ui_->action_toggle_show_sidebar->isChecked());
|
||||
s.setValue(MainWindowSettings::kSearchForCoverAuto, album_cover_choice_controller_->search_cover_auto_action()->isChecked());
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
@@ -1584,23 +1592,35 @@ void MainWindow::ToggleSidebar(const bool checked) {
|
||||
|
||||
ui_->sidebar_layout->setVisible(checked);
|
||||
TabSwitched();
|
||||
settings_.setValue(MainWindowSettings::kShowSidebar, checked);
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(MainWindowSettings::kSettingsGroup);
|
||||
s.setValue(MainWindowSettings::kShowSidebar, checked);
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::ToggleSearchCoverAuto(const bool checked) {
|
||||
settings_.setValue(MainWindowSettings::kSearchForCoverAuto, checked);
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(MainWindowSettings::kSettingsGroup);
|
||||
s.setValue(MainWindowSettings::kSearchForCoverAuto, checked);
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::SaveGeometry() {
|
||||
|
||||
if (!initialized_) return;
|
||||
|
||||
settings_.setValue(MainWindowSettings::kMaximized, isMaximized());
|
||||
settings_.setValue(MainWindowSettings::kMinimized, isMinimized());
|
||||
settings_.setValue(MainWindowSettings::kHidden, isHidden());
|
||||
settings_.setValue(MainWindowSettings::kGeometry, saveGeometry());
|
||||
settings_.setValue(MainWindowSettings::kSplitterState, ui_->splitter->saveState());
|
||||
Settings s;
|
||||
s.beginGroup(MainWindowSettings::kSettingsGroup);
|
||||
s.setValue(MainWindowSettings::kMaximized, isMaximized());
|
||||
s.setValue(MainWindowSettings::kMinimized, isMinimized());
|
||||
s.setValue(MainWindowSettings::kHidden, isHidden());
|
||||
s.setValue(MainWindowSettings::kGeometry, saveGeometry());
|
||||
s.setValue(MainWindowSettings::kSplitterState, ui_->splitter->saveState());
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
@@ -1742,7 +1762,12 @@ void MainWindow::SetHiddenInTray(const bool hidden) {
|
||||
}
|
||||
|
||||
void MainWindow::FilePathChanged(const QString &path) {
|
||||
settings_.setValue("file_path", path);
|
||||
|
||||
Settings s;
|
||||
s.beginGroup(MainWindowSettings::kSettingsGroup);
|
||||
s.setValue("file_path", path);
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::Seeked(const qint64 microseconds) {
|
||||
@@ -2324,7 +2349,9 @@ void MainWindow::EditValue() {
|
||||
void MainWindow::AddFile() {
|
||||
|
||||
// Last used directory
|
||||
QString directory = settings_.value("add_media_path", QDir::currentPath()).toString();
|
||||
Settings s;
|
||||
s.beginGroup(MainWindowSettings::kSettingsGroup);
|
||||
QString directory = s.value("add_media_path", QDir::currentPath()).toString();
|
||||
|
||||
PlaylistParser parser(app_->tagreader_client(), app_->collection_backend());
|
||||
|
||||
@@ -2334,7 +2361,7 @@ void MainWindow::AddFile() {
|
||||
if (filenames.isEmpty()) return;
|
||||
|
||||
// Save last used directory
|
||||
settings_.setValue("add_media_path", filenames[0]);
|
||||
s.setValue("add_media_path", filenames[0]);
|
||||
|
||||
// Convert to URLs
|
||||
QList<QUrl> urls;
|
||||
@@ -2352,14 +2379,16 @@ void MainWindow::AddFile() {
|
||||
void MainWindow::AddFolder() {
|
||||
|
||||
// Last used directory
|
||||
QString directory = settings_.value("add_folder_path", QDir::currentPath()).toString();
|
||||
Settings s;
|
||||
s.beginGroup(MainWindowSettings::kSettingsGroup);
|
||||
QString directory = s.value("add_folder_path", QDir::currentPath()).toString();
|
||||
|
||||
// Show dialog
|
||||
directory = QFileDialog::getExistingDirectory(this, tr("Add folder"), directory);
|
||||
if (directory.isEmpty()) return;
|
||||
|
||||
// Save last used directory
|
||||
settings_.setValue("add_folder_path", directory);
|
||||
s.setValue("add_folder_path", directory);
|
||||
|
||||
// Add media
|
||||
MimeData *mimedata = new MimeData;
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <QTimer>
|
||||
#include <QSettings>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "includes/scoped_ptr.h"
|
||||
@@ -53,7 +52,6 @@
|
||||
#include "includes/lazy.h"
|
||||
#include "core/platforminterface.h"
|
||||
#include "core/song.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/commandlineoptions.h"
|
||||
#include "tagreader/tagreaderclient.h"
|
||||
#include "osd/osdbase.h"
|
||||
@@ -390,7 +388,6 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
|
||||
QTimer *track_position_timer_;
|
||||
QTimer *track_slider_timer_;
|
||||
Settings settings_;
|
||||
|
||||
bool keep_running_;
|
||||
bool playing_widget_;
|
||||
|
||||
@@ -805,19 +805,19 @@ bool Song::lyrics_supported() const {
|
||||
}
|
||||
|
||||
bool Song::albumartistsort_supported() const {
|
||||
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
|
||||
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::OggOpus || d->filetype_ == FileType::MPEG;
|
||||
}
|
||||
|
||||
bool Song::albumsort_supported() const {
|
||||
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
|
||||
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::OggOpus || d->filetype_ == FileType::MPEG;
|
||||
}
|
||||
|
||||
bool Song::artistsort_supported() const {
|
||||
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
|
||||
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::OggOpus || d->filetype_ == FileType::MPEG;
|
||||
}
|
||||
|
||||
bool Song::composersort_supported() const {
|
||||
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
|
||||
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::OggOpus || d->filetype_ == FileType::MPEG;
|
||||
}
|
||||
|
||||
bool Song::performersort_supported() const {
|
||||
@@ -826,7 +826,7 @@ bool Song::performersort_supported() const {
|
||||
}
|
||||
|
||||
bool Song::titlesort_supported() const {
|
||||
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
|
||||
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::OggOpus || d->filetype_ == FileType::MPEG;
|
||||
}
|
||||
|
||||
bool Song::save_embedded_cover_supported(const FileType filetype) {
|
||||
|
||||
303
src/core/windowsmediacontroller.cpp
Normal file
303
src/core/windowsmediacontroller.cpp
Normal file
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* 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 "config.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
// Undefine 'interface' macro from windows.h before including WinRT headers
|
||||
#pragma push_macro("interface")
|
||||
#undef interface
|
||||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Media.h>
|
||||
#include <winrt/Windows.Storage.h>
|
||||
#include <winrt/Windows.Storage.Streams.h>
|
||||
|
||||
#pragma pop_macro("interface")
|
||||
|
||||
// Include the interop header for ISystemMediaTransportControlsInterop
|
||||
#include <systemmediatransportcontrolsinterop.h>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "windowsmediacontroller.h"
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/player.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "covermanager/currentalbumcoverloader.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Media;
|
||||
using namespace Windows::Storage;
|
||||
using namespace Windows::Storage::Streams;
|
||||
|
||||
// Helper struct to hold the WinRT object
|
||||
struct WindowsMediaControllerPrivate {
|
||||
SystemMediaTransportControls smtc{nullptr};
|
||||
};
|
||||
|
||||
WindowsMediaController::WindowsMediaController(HWND hwnd,
|
||||
const SharedPtr<Player> player,
|
||||
const SharedPtr<PlaylistManager> playlist_manager,
|
||||
const SharedPtr<CurrentAlbumCoverLoader> current_albumcover_loader,
|
||||
QObject *parent)
|
||||
: QObject(parent),
|
||||
player_(player),
|
||||
playlist_manager_(playlist_manager),
|
||||
current_albumcover_loader_(current_albumcover_loader),
|
||||
smtc_(nullptr),
|
||||
apartment_initialized_(false) {
|
||||
|
||||
try {
|
||||
// Initialize WinRT apartment if not already initialized
|
||||
// Qt or other components may have already initialized it
|
||||
try {
|
||||
winrt::init_apartment(winrt::apartment_type::single_threaded);
|
||||
apartment_initialized_ = true;
|
||||
}
|
||||
catch (const hresult_error &e) {
|
||||
// Apartment already initialized - this is fine, continue
|
||||
if (e.code() != RPC_E_CHANGED_MODE) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Create private implementation
|
||||
auto *priv = new WindowsMediaControllerPrivate();
|
||||
smtc_ = priv;
|
||||
|
||||
// Get the SystemMediaTransportControls instance for this window
|
||||
// Use the interop interface
|
||||
auto interop = winrt::get_activation_factory<SystemMediaTransportControls, ISystemMediaTransportControlsInterop>();
|
||||
|
||||
if (!interop) {
|
||||
qLog(Warning) << "Failed to get ISystemMediaTransportControlsInterop";
|
||||
delete priv;
|
||||
smtc_ = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get SMTC for the window
|
||||
winrt::com_ptr<IInspectable> inspectable;
|
||||
HRESULT hr = interop->GetForWindow(hwnd, winrt::guid_of<SystemMediaTransportControls>(), inspectable.put_void());
|
||||
|
||||
if (FAILED(hr) || !inspectable) {
|
||||
qLog(Warning) << "Failed to get SystemMediaTransportControls for window, HRESULT:" << Qt::hex << static_cast<unsigned int>(hr);
|
||||
delete priv;
|
||||
smtc_ = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to SystemMediaTransportControls
|
||||
priv->smtc = inspectable.as<SystemMediaTransportControls>();
|
||||
|
||||
if (!priv->smtc) {
|
||||
qLog(Warning) << "Failed to cast to SystemMediaTransportControls";
|
||||
delete priv;
|
||||
smtc_ = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable the controls
|
||||
priv->smtc.IsEnabled(true);
|
||||
priv->smtc.IsPlayEnabled(true);
|
||||
priv->smtc.IsPauseEnabled(true);
|
||||
priv->smtc.IsStopEnabled(true);
|
||||
priv->smtc.IsNextEnabled(true);
|
||||
priv->smtc.IsPreviousEnabled(true);
|
||||
|
||||
// Setup button handlers
|
||||
SetupButtonHandlers();
|
||||
|
||||
// Connect signals from Player
|
||||
QObject::connect(&*player_->engine(), &EngineBase::StateChanged, this, &WindowsMediaController::EngineStateChanged);
|
||||
QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentSongChanged, this, &WindowsMediaController::CurrentSongChanged);
|
||||
QObject::connect(&*current_albumcover_loader_, &CurrentAlbumCoverLoader::AlbumCoverLoaded, this, &WindowsMediaController::AlbumCoverLoaded);
|
||||
|
||||
qLog(Info) << "Windows Media Transport Controls initialized successfully";
|
||||
}
|
||||
catch (const hresult_error &e) {
|
||||
qLog(Warning) << "Failed to initialize Windows Media Transport Controls:" << QString::fromWCharArray(e.message().c_str());
|
||||
if (smtc_) {
|
||||
delete static_cast<WindowsMediaControllerPrivate*>(smtc_);
|
||||
smtc_ = nullptr;
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
qLog(Warning) << "Failed to initialize Windows Media Transport Controls: unknown error";
|
||||
if (smtc_) {
|
||||
delete static_cast<WindowsMediaControllerPrivate*>(smtc_);
|
||||
smtc_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WindowsMediaController::~WindowsMediaController() {
|
||||
if (smtc_) {
|
||||
auto *priv = static_cast<WindowsMediaControllerPrivate*>(smtc_);
|
||||
if (priv->smtc) {
|
||||
priv->smtc.IsEnabled(false);
|
||||
}
|
||||
delete priv;
|
||||
smtc_ = nullptr;
|
||||
}
|
||||
// Only uninit if we initialized the apartment
|
||||
if (apartment_initialized_) {
|
||||
winrt::uninit_apartment();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowsMediaController::SetupButtonHandlers() {
|
||||
if (!smtc_) return;
|
||||
|
||||
auto *priv = static_cast<WindowsMediaControllerPrivate*>(smtc_);
|
||||
if (!priv->smtc) return;
|
||||
|
||||
// Handle button pressed events
|
||||
priv->smtc.ButtonPressed([this](const SystemMediaTransportControls &, const SystemMediaTransportControlsButtonPressedEventArgs &args) {
|
||||
switch (args.Button()) {
|
||||
case SystemMediaTransportControlsButton::Play:
|
||||
player_->Play();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton::Pause:
|
||||
player_->Pause();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton::Stop:
|
||||
player_->Stop();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton::Next:
|
||||
player_->Next();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton::Previous:
|
||||
player_->Previous();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WindowsMediaController::EngineStateChanged(EngineBase::State newState) {
|
||||
UpdatePlaybackStatus(newState);
|
||||
}
|
||||
|
||||
void WindowsMediaController::UpdatePlaybackStatus(EngineBase::State state) {
|
||||
if (!smtc_) return;
|
||||
|
||||
auto *priv = static_cast<WindowsMediaControllerPrivate*>(smtc_);
|
||||
if (!priv->smtc) return;
|
||||
|
||||
try {
|
||||
switch (state) {
|
||||
case EngineBase::State::Playing:
|
||||
priv->smtc.PlaybackStatus(MediaPlaybackStatus::Playing);
|
||||
break;
|
||||
case EngineBase::State::Paused:
|
||||
priv->smtc.PlaybackStatus(MediaPlaybackStatus::Paused);
|
||||
break;
|
||||
case EngineBase::State::Empty:
|
||||
case EngineBase::State::Idle:
|
||||
priv->smtc.PlaybackStatus(MediaPlaybackStatus::Stopped);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const hresult_error &e) {
|
||||
qLog(Warning) << "Failed to update playback status:" << QString::fromWCharArray(e.message().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void WindowsMediaController::CurrentSongChanged(const Song &song) {
|
||||
if (!song.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update metadata immediately with what we have
|
||||
UpdateMetadata(song, QUrl());
|
||||
|
||||
// Album cover will be updated via AlbumCoverLoaded signal
|
||||
}
|
||||
|
||||
void WindowsMediaController::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
|
||||
if (!song.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update metadata with album cover
|
||||
UpdateMetadata(song, result.temp_cover_url.isEmpty() ? result.album_cover.cover_url : result.temp_cover_url);
|
||||
}
|
||||
|
||||
void WindowsMediaController::UpdateMetadata(const Song &song, const QUrl &art_url) {
|
||||
if (!smtc_) return;
|
||||
|
||||
auto *priv = static_cast<WindowsMediaControllerPrivate*>(smtc_);
|
||||
if (!priv->smtc) return;
|
||||
|
||||
try {
|
||||
// Get the updater
|
||||
SystemMediaTransportControlsDisplayUpdater updater = priv->smtc.DisplayUpdater();
|
||||
updater.Type(MediaPlaybackType::Music);
|
||||
|
||||
// Get the music properties
|
||||
auto musicProperties = updater.MusicProperties();
|
||||
|
||||
// Set basic metadata
|
||||
if (!song.title().isEmpty()) {
|
||||
musicProperties.Title(winrt::hstring(song.title().toStdWString()));
|
||||
}
|
||||
if (!song.artist().isEmpty()) {
|
||||
musicProperties.Artist(winrt::hstring(song.artist().toStdWString()));
|
||||
}
|
||||
if (!song.album().isEmpty()) {
|
||||
musicProperties.AlbumTitle(winrt::hstring(song.album().toStdWString()));
|
||||
}
|
||||
|
||||
// Set album art if available
|
||||
if (art_url.isValid() && art_url.isLocalFile()) {
|
||||
QString artPath = art_url.toLocalFile();
|
||||
if (!artPath.isEmpty()) {
|
||||
try {
|
||||
// Use file:// URI to avoid async blocking in STA thread
|
||||
QString fileUri = QUrl::fromLocalFile(artPath).toString();
|
||||
auto thumbnailStream = RandomAccessStreamReference::CreateFromUri(
|
||||
winrt::Windows::Foundation::Uri(winrt::hstring(fileUri.toStdWString()))
|
||||
);
|
||||
updater.Thumbnail(thumbnailStream);
|
||||
current_song_art_url_ = artPath;
|
||||
}
|
||||
catch (const hresult_error &e) {
|
||||
qLog(Debug) << "Failed to set album art:" << QString::fromWCharArray(e.message().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the display
|
||||
updater.Update();
|
||||
}
|
||||
catch (const hresult_error &e) {
|
||||
qLog(Warning) << "Failed to update metadata:" << QString::fromWCharArray(e.message().c_str());
|
||||
}
|
||||
}
|
||||
69
src/core/windowsmediacontroller.h
Normal file
69
src/core/windowsmediacontroller.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 WINDOWSMEDIACONTROLLER_H
|
||||
#define WINDOWSMEDIACONTROLLER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
|
||||
class Player;
|
||||
class PlaylistManager;
|
||||
class CurrentAlbumCoverLoader;
|
||||
class Song;
|
||||
|
||||
class WindowsMediaController : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WindowsMediaController(HWND hwnd,
|
||||
const SharedPtr<Player> player,
|
||||
const SharedPtr<PlaylistManager> playlist_manager,
|
||||
const SharedPtr<CurrentAlbumCoverLoader> current_albumcover_loader,
|
||||
QObject *parent = nullptr);
|
||||
~WindowsMediaController() override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result = AlbumCoverLoaderResult());
|
||||
void EngineStateChanged(EngineBase::State newState);
|
||||
void CurrentSongChanged(const Song &song);
|
||||
|
||||
private:
|
||||
void UpdatePlaybackStatus(EngineBase::State state);
|
||||
void UpdateMetadata(const Song &song, const QUrl &art_url);
|
||||
void SetupButtonHandlers();
|
||||
|
||||
private:
|
||||
const SharedPtr<Player> player_;
|
||||
const SharedPtr<PlaylistManager> playlist_manager_;
|
||||
const SharedPtr<CurrentAlbumCoverLoader> current_albumcover_loader_;
|
||||
void *smtc_; // Pointer to SystemMediaTransportControls (opaque to avoid WinRT headers in public header)
|
||||
QString current_song_art_url_;
|
||||
bool apartment_initialized_; // Track if we initialized the WinRT apartment
|
||||
};
|
||||
|
||||
#endif // WINDOWSMEDIACONTROLLER_H
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <QKeySequence>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#include "globalshortcutsmanager.h"
|
||||
#include "globalshortcutsbackend.h"
|
||||
@@ -58,29 +59,32 @@ using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
GlobalShortcutsManager::GlobalShortcutsManager(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
settings_.beginGroup(GlobalShortcutsSettings::kSettingsGroup);
|
||||
Settings s;
|
||||
s.beginGroup(GlobalShortcutsSettings::kSettingsGroup);
|
||||
|
||||
// Create actions
|
||||
AddShortcut(u"play"_s, tr("Play"), std::bind(&GlobalShortcutsManager::Play, this));
|
||||
AddShortcut(u"pause"_s, tr("Pause"), std::bind(&GlobalShortcutsManager::Pause, this));
|
||||
AddShortcut(u"play_pause"_s, tr("Play/Pause"), std::bind(&GlobalShortcutsManager::PlayPause, this), QKeySequence(Qt::Key_MediaPlay));
|
||||
AddShortcut(u"stop"_s, tr("Stop"), std::bind(&GlobalShortcutsManager::Stop, this), QKeySequence(Qt::Key_MediaStop));
|
||||
AddShortcut(u"stop_after"_s, tr("Stop playing after current track"), std::bind(&GlobalShortcutsManager::StopAfter, this));
|
||||
AddShortcut(u"next_track"_s, tr("Next track"), std::bind(&GlobalShortcutsManager::Next, this), QKeySequence(Qt::Key_MediaNext));
|
||||
AddShortcut(u"prev_track"_s, tr("Previous track"), std::bind(&GlobalShortcutsManager::Previous, this), QKeySequence(Qt::Key_MediaPrevious));
|
||||
AddShortcut(u"restart_or_prev_track"_s, tr("Restart or previous track"), std::bind(&GlobalShortcutsManager::RestartOrPrevious, this));
|
||||
AddShortcut(u"inc_volume"_s, tr("Increase volume"), std::bind(&GlobalShortcutsManager::IncVolume, this));
|
||||
AddShortcut(u"dec_volume"_s, tr("Decrease volume"), std::bind(&GlobalShortcutsManager::DecVolume, this));
|
||||
AddShortcut(u"mute"_s, tr("Mute"), std::bind(&GlobalShortcutsManager::Mute, this));
|
||||
AddShortcut(u"seek_forward"_s, tr("Seek forward"), std::bind(&GlobalShortcutsManager::SeekForward, this));
|
||||
AddShortcut(u"seek_backward"_s, tr("Seek backward"), std::bind(&GlobalShortcutsManager::SeekBackward, this));
|
||||
AddShortcut(u"show_hide"_s, tr("Show/Hide"), std::bind(&GlobalShortcutsManager::ShowHide, this));
|
||||
AddShortcut(u"show_osd"_s, tr("Show OSD"), std::bind(&GlobalShortcutsManager::ShowOSD, this));
|
||||
AddShortcut(u"toggle_pretty_osd"_s, tr("Toggle Pretty OSD"), std::bind(&GlobalShortcutsManager::TogglePrettyOSD, this)); // Toggling possible only for pretty OSD
|
||||
AddShortcut(u"shuffle_mode"_s, tr("Change shuffle mode"), std::bind(&GlobalShortcutsManager::CycleShuffleMode, this));
|
||||
AddShortcut(u"repeat_mode"_s, tr("Change repeat mode"), std::bind(&GlobalShortcutsManager::CycleRepeatMode, this));
|
||||
AddShortcut(u"toggle_scrobbling"_s, tr("Enable/disable scrobbling"), std::bind(&GlobalShortcutsManager::ToggleScrobbling, this));
|
||||
AddShortcut(u"love"_s, tr("Love"), std::bind(&GlobalShortcutsManager::Love, this));
|
||||
AddShortcut(s, u"play"_s, tr("Play"), std::bind(&GlobalShortcutsManager::Play, this));
|
||||
AddShortcut(s, u"pause"_s, tr("Pause"), std::bind(&GlobalShortcutsManager::Pause, this));
|
||||
AddShortcut(s, u"play_pause"_s, tr("Play/Pause"), std::bind(&GlobalShortcutsManager::PlayPause, this), QKeySequence(Qt::Key_MediaPlay));
|
||||
AddShortcut(s, u"stop"_s, tr("Stop"), std::bind(&GlobalShortcutsManager::Stop, this), QKeySequence(Qt::Key_MediaStop));
|
||||
AddShortcut(s, u"stop_after"_s, tr("Stop playing after current track"), std::bind(&GlobalShortcutsManager::StopAfter, this));
|
||||
AddShortcut(s, u"next_track"_s, tr("Next track"), std::bind(&GlobalShortcutsManager::Next, this), QKeySequence(Qt::Key_MediaNext));
|
||||
AddShortcut(s, u"prev_track"_s, tr("Previous track"), std::bind(&GlobalShortcutsManager::Previous, this), QKeySequence(Qt::Key_MediaPrevious));
|
||||
AddShortcut(s, u"restart_or_prev_track"_s, tr("Restart or previous track"), std::bind(&GlobalShortcutsManager::RestartOrPrevious, this));
|
||||
AddShortcut(s, u"inc_volume"_s, tr("Increase volume"), std::bind(&GlobalShortcutsManager::IncVolume, this));
|
||||
AddShortcut(s, u"dec_volume"_s, tr("Decrease volume"), std::bind(&GlobalShortcutsManager::DecVolume, this));
|
||||
AddShortcut(s, u"mute"_s, tr("Mute"), std::bind(&GlobalShortcutsManager::Mute, this));
|
||||
AddShortcut(s, u"seek_forward"_s, tr("Seek forward"), std::bind(&GlobalShortcutsManager::SeekForward, this));
|
||||
AddShortcut(s, u"seek_backward"_s, tr("Seek backward"), std::bind(&GlobalShortcutsManager::SeekBackward, this));
|
||||
AddShortcut(s, u"show_hide"_s, tr("Show/Hide"), std::bind(&GlobalShortcutsManager::ShowHide, this));
|
||||
AddShortcut(s, u"show_osd"_s, tr("Show OSD"), std::bind(&GlobalShortcutsManager::ShowOSD, this));
|
||||
AddShortcut(s, u"toggle_pretty_osd"_s, tr("Toggle Pretty OSD"), std::bind(&GlobalShortcutsManager::TogglePrettyOSD, this)); // Toggling possible only for pretty OSD
|
||||
AddShortcut(s, u"shuffle_mode"_s, tr("Change shuffle mode"), std::bind(&GlobalShortcutsManager::CycleShuffleMode, this));
|
||||
AddShortcut(s, u"repeat_mode"_s, tr("Change repeat mode"), std::bind(&GlobalShortcutsManager::CycleRepeatMode, this));
|
||||
AddShortcut(s, u"toggle_scrobbling"_s, tr("Enable/disable scrobbling"), std::bind(&GlobalShortcutsManager::ToggleScrobbling, this));
|
||||
AddShortcut(s, u"love"_s, tr("Love"), std::bind(&GlobalShortcutsManager::Love, this));
|
||||
|
||||
s.endGroup();
|
||||
|
||||
// Create backends - these do the actual shortcut registration
|
||||
|
||||
@@ -116,35 +120,39 @@ void GlobalShortcutsManager::ReloadSettings() {
|
||||
backends_enabled_ << GlobalShortcutsBackend::Type::Win;
|
||||
#endif
|
||||
|
||||
{
|
||||
Settings s;
|
||||
s.beginGroup(GlobalShortcutsSettings::kSettingsGroup);
|
||||
#ifdef HAVE_KGLOBALACCEL_GLOBALSHORTCUTS
|
||||
if (settings_.value(GlobalShortcutsSettings::kUseKGlobalAccel, true).toBool()) {
|
||||
backends_enabled_ << GlobalShortcutsBackend::Type::KGlobalAccel;
|
||||
}
|
||||
if (s.value(GlobalShortcutsSettings::kUseKGlobalAccel, true).toBool()) {
|
||||
backends_enabled_ << GlobalShortcutsBackend::Type::KGlobalAccel;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_X11_GLOBALSHORTCUTS
|
||||
if (settings_.value(GlobalShortcutsSettings::kUseX11, false).toBool()) {
|
||||
backends_enabled_ << GlobalShortcutsBackend::Type::X11;
|
||||
}
|
||||
if (s.value(GlobalShortcutsSettings::kUseX11, false).toBool()) {
|
||||
backends_enabled_ << GlobalShortcutsBackend::Type::X11;
|
||||
}
|
||||
#endif
|
||||
s.endGroup();
|
||||
}
|
||||
|
||||
Unregister();
|
||||
Register();
|
||||
|
||||
}
|
||||
|
||||
void GlobalShortcutsManager::AddShortcut(const QString &id, const QString &name, std::function<void()> signal, const QKeySequence &default_key) { // clazy:exclude=function-args-by-ref
|
||||
void GlobalShortcutsManager::AddShortcut(Settings &s, const QString &id, const QString &name, std::function<void()> signal, const QKeySequence &default_key) { // clazy:exclude=function-args-by-ref
|
||||
|
||||
Shortcut shortcut = AddShortcut(id, name, default_key);
|
||||
Shortcut shortcut = AddShortcut(s, id, name, default_key);
|
||||
QObject::connect(shortcut.action, &QAction::triggered, this, signal);
|
||||
|
||||
}
|
||||
|
||||
GlobalShortcutsManager::Shortcut GlobalShortcutsManager::AddShortcut(const QString &id, const QString &name, const QKeySequence &default_key) {
|
||||
GlobalShortcutsManager::Shortcut GlobalShortcutsManager::AddShortcut(Settings &s, const QString &id, const QString &name, const QKeySequence &default_key) {
|
||||
|
||||
Shortcut shortcut;
|
||||
shortcut.action = new QAction(name, this);
|
||||
QKeySequence key_sequence = QKeySequence::fromString(settings_.value(id, default_key.toString()).toString());
|
||||
QKeySequence key_sequence = QKeySequence::fromString(s.value(id, default_key.toString()).toString());
|
||||
shortcut.action->setShortcut(key_sequence);
|
||||
shortcut.id = id;
|
||||
shortcut.default_key = default_key;
|
||||
|
||||
@@ -32,15 +32,14 @@
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QKeySequence>
|
||||
#include <QSettings>
|
||||
|
||||
#include "globalshortcutsbackend.h"
|
||||
|
||||
#include "core/settings.h"
|
||||
|
||||
class QShortcut;
|
||||
class QAction;
|
||||
|
||||
class Settings;
|
||||
|
||||
class GlobalShortcutsManager : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -99,12 +98,11 @@ class GlobalShortcutsManager : public QWidget {
|
||||
void Love();
|
||||
|
||||
private:
|
||||
void AddShortcut(const QString &id, const QString &name, std::function<void()> signal, const QKeySequence &default_key = QKeySequence(0));
|
||||
Shortcut AddShortcut(const QString &id, const QString &name, const QKeySequence &default_key);
|
||||
void AddShortcut(Settings &s, const QString &id, const QString &name, std::function<void()> signal, const QKeySequence &default_key = QKeySequence(0));
|
||||
Shortcut AddShortcut(Settings &s, const QString &id, const QString &name, const QKeySequence &default_key);
|
||||
|
||||
private:
|
||||
QList<GlobalShortcutsBackend*> backends_;
|
||||
Settings settings_;
|
||||
QList<GlobalShortcutsBackend::Type> backends_enabled_;
|
||||
QMap<QString, Shortcut> shortcuts_;
|
||||
};
|
||||
|
||||
@@ -93,6 +93,10 @@
|
||||
# include "discord/richpresence.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_WINDOWS_MEDIA_CONTROLS
|
||||
# include "core/windowsmediacontroller.h"
|
||||
#endif
|
||||
|
||||
#include "core/iconloader.h"
|
||||
#include "core/commandlineoptions.h"
|
||||
#include "core/networkproxyfactory.h"
|
||||
@@ -365,6 +369,11 @@ int main(int argc, char *argv[]) {
|
||||
#endif
|
||||
options);
|
||||
|
||||
#ifdef HAVE_WINDOWS_MEDIA_CONTROLS
|
||||
// Initialize Windows Media Transport Controls
|
||||
WindowsMediaController windows_media_controller(reinterpret_cast<HWND>(w.winId()), app.player(), app.playlist_manager(), app.current_albumcover_loader());
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
mac::EnableFullScreen(w);
|
||||
#endif // Q_OS_MACOS
|
||||
|
||||
@@ -106,8 +106,6 @@ PlaylistContainer::PlaylistContainer(QWidget *parent)
|
||||
no_matches_font.setBold(true);
|
||||
no_matches_label_->setFont(no_matches_font);
|
||||
|
||||
settings_.beginGroup(kSettingsGroup);
|
||||
|
||||
// Tab bar
|
||||
ui_->tab_bar->setExpanding(false);
|
||||
ui_->tab_bar->setMovable(true);
|
||||
@@ -257,7 +255,11 @@ void PlaylistContainer::ReloadSettings() {
|
||||
ui_->redo->setIconSize(QSize(iconsize, iconsize));
|
||||
ui_->search_field->setIconSize(iconsize);
|
||||
|
||||
bool playlist_clear = settings_.value("playlist_clear", true).toBool();
|
||||
s.beginGroup(kSettingsGroup);
|
||||
const bool playlist_clear = s.value("playlist_clear", true).toBool();
|
||||
const bool show_toolbar = s.value("show_toolbar", true).toBool();
|
||||
s.endGroup();
|
||||
|
||||
if (playlist_clear) {
|
||||
ui_->clear->show();
|
||||
}
|
||||
@@ -265,7 +267,6 @@ void PlaylistContainer::ReloadSettings() {
|
||||
ui_->clear->hide();
|
||||
}
|
||||
|
||||
bool show_toolbar = settings_.value("show_toolbar", true).toBool();
|
||||
ui_->toolbar->setVisible(show_toolbar);
|
||||
|
||||
if (!show_toolbar) ui_->search_field->clear();
|
||||
@@ -308,7 +309,12 @@ void PlaylistContainer::PlaylistAdded(const int id, const QString &name, const b
|
||||
ui_->tab_bar->InsertTab(id, index, name, favorite);
|
||||
|
||||
// Are we start up, should we select this tab?
|
||||
if (starting_up_ && settings_.value("current_playlist", 1).toInt() == id) {
|
||||
Settings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
const int current_playlist = s.value("current_playlist", 1).toInt();
|
||||
s.endGroup();
|
||||
|
||||
if (starting_up_ && current_playlist == id) {
|
||||
starting_up_ = false;
|
||||
ui_->tab_bar->set_current_id(id);
|
||||
}
|
||||
@@ -347,12 +353,14 @@ void PlaylistContainer::NewPlaylist() { manager_->New(tr("Playlist")); }
|
||||
|
||||
void PlaylistContainer::LoadPlaylist() {
|
||||
|
||||
QString filename = settings_.value("last_load_playlist").toString();
|
||||
Settings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
QString filename = s.value("last_load_playlist").toString();
|
||||
filename = QFileDialog::getOpenFileName(this, tr("Load playlist"), filename, manager_->parser()->filters(PlaylistParser::Type::Load));
|
||||
|
||||
if (filename.isNull()) return;
|
||||
|
||||
settings_.setValue("last_load_playlist", filename);
|
||||
s.setValue("last_load_playlist", filename);
|
||||
|
||||
manager_->Load(filename);
|
||||
|
||||
@@ -391,7 +399,10 @@ void PlaylistContainer::Save() {
|
||||
|
||||
if (starting_up_) return;
|
||||
|
||||
settings_.setValue("current_playlist", ui_->tab_bar->current_id());
|
||||
Settings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("current_playlist", ui_->tab_bar->current_id());
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
#include <QIcon>
|
||||
#include <QSettings>
|
||||
|
||||
class QTimer;
|
||||
class QTimeLine;
|
||||
@@ -45,7 +44,6 @@ class PlaylistView;
|
||||
class Ui_PlaylistContainer;
|
||||
|
||||
#include "includes/shared_ptr.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
class PlaylistContainer : public QWidget {
|
||||
Q_OBJECT
|
||||
@@ -118,7 +116,6 @@ class PlaylistContainer : public QWidget {
|
||||
QAction *redo_;
|
||||
Playlist *playlist_;
|
||||
|
||||
Settings settings_;
|
||||
bool starting_up_;
|
||||
|
||||
bool tab_bar_visible_;
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
#include <QLinearGradient>
|
||||
#include <QScrollBar>
|
||||
#include <QtEvents>
|
||||
#include <QSettings>
|
||||
#include <QDrag>
|
||||
|
||||
#include "includes/qt_blurimage.h"
|
||||
|
||||
Reference in New Issue
Block a user