Compare commits

..

4 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
0df4afe363 Address code review feedback: remove const_cast and document scaling formula
Co-authored-by: jonaski <10343810+jonaski@users.noreply.github.com>
2025-12-28 00:09:12 +00:00
copilot-swe-agent[bot]
350d907e8b Calculate icon size dynamically based on resolution, aspect ratio and DPI scaling
Co-authored-by: jonaski <10343810+jonaski@users.noreply.github.com>
2025-12-28 00:08:01 +00:00
copilot-swe-agent[bot]
eaced733bb Increase shuffle/repeat icon size using appearance settings
Co-authored-by: jonaski <10343810+jonaski@users.noreply.github.com>
2025-12-20 12:54:44 +00:00
copilot-swe-agent[bot]
2abe4576a9 Initial plan 2025-12-20 12:20:12 +00:00
19 changed files with 168 additions and 639 deletions

View File

@@ -739,18 +739,12 @@ 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.2
uses: vmactions/freebsd-vm@v1.3.0
with:
usesh: true
mem: 8192
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 sparsehash rapidjson
run: |
set -e
@@ -772,7 +766,7 @@ jobs:
submodules: recursive
- name: Build OpenBSD
id: build-openbsd
uses: vmactions/openbsd-vm@v1.2.9
uses: vmactions/openbsd-vm@v1.2.5
with:
usesh: true
mem: 4096

View File

@@ -295,12 +295,6 @@ 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
)
@@ -1300,7 +1294,6 @@ 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

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2021, 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,26 +189,6 @@ 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);
@@ -232,85 +212,18 @@ void CollectionLibrary::SyncPlaycountAndRatingToFiles() {
}
void CollectionLibrary::SongsPlaycountChanged(const SongList &songs, const bool save_tags) {
void CollectionLibrary::SongsPlaycountChanged(const SongList &songs, const bool save_tags) const {
if (save_tags || save_playcounts_to_files_) {
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);
}
tagreader_client_->SaveSongsPlaycountAsync(songs);
}
}
void CollectionLibrary::SongsRatingChanged(const SongList &songs, const bool save_tags) {
void CollectionLibrary::SongsRatingChanged(const SongList &songs, const bool save_tags) const {
if (save_tags || save_ratings_to_files_) {
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);
tagreader_client_->SaveSongsRatingAsync(songs);
}
}

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2021, 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,7 +27,6 @@
#include <QObject>
#include <QList>
#include <QHash>
#include <QMap>
#include <QString>
#include "includes/shared_ptr.h"
@@ -72,7 +71,6 @@ class CollectionLibrary : public QObject {
private:
void SyncPlaycountAndRatingToFiles();
void SavePendingPlaycountsAndRatings();
public Q_SLOTS:
void ReloadSettings();
@@ -86,26 +84,16 @@ 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);
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
void SongsPlaycountChanged(const SongList &songs, const bool save_tags = false) const;
void SongsRatingChanged(const SongList &songs, const bool save_tags = false) const;
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_;
@@ -123,10 +111,6 @@ 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

View File

@@ -1209,41 +1209,49 @@ 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());
@@ -1262,10 +1270,12 @@ 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:

View File

@@ -14,7 +14,6 @@
#cmakedefine HAVE_GIO_UNIX
#cmakedefine HAVE_DBUS
#cmakedefine HAVE_MPRIS2
#cmakedefine HAVE_WINDOWS_MEDIA_CONTROLS
#cmakedefine HAVE_UDISKS2
#cmakedefine HAVE_AUDIOCD
#cmakedefine HAVE_MTP

View File

@@ -58,6 +58,7 @@
#include <QShortcut>
#include <QMessageBox>
#include <QErrorMessage>
#include <QSettings>
#include <QColor>
#include <QFrame>
#include <QItemSelectionModel>
@@ -696,9 +697,6 @@ 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);
@@ -979,28 +977,27 @@ MainWindow::MainWindow(Application *app,
// Load settings
qLog(Debug) << "Loading settings";
Settings settings;
settings.beginGroup(MainWindowSettings::kSettingsGroup);
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);
@@ -1043,13 +1040,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;
@@ -1057,7 +1054,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);
@@ -1228,9 +1225,7 @@ void MainWindow::ReloadSettings() {
osd_->ReloadSettings();
s.beginGroup(MainWindowSettings::kSettingsGroup);
album_cover_choice_controller_->search_cover_auto_action()->setChecked(s.value(MainWindowSettings::kSearchForCoverAuto, true).toBool());
s.endGroup();
album_cover_choice_controller_->search_cover_auto_action()->setChecked(settings_.value(MainWindowSettings::kSearchForCoverAuto, true).toBool());
#ifdef HAVE_SUBSONIC
s.beginGroup(SubsonicSettings::kSettingsGroup);
@@ -1347,11 +1342,8 @@ void MainWindow::SaveSettings() {
ui_->playlist->view()->SaveSettings();
app_->scrobbler()->WriteCache();
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();
settings_.setValue(MainWindowSettings::kShowSidebar, ui_->action_toggle_show_sidebar->isChecked());
settings_.setValue(MainWindowSettings::kSearchForCoverAuto, album_cover_choice_controller_->search_cover_auto_action()->isChecked());
}
@@ -1592,35 +1584,23 @@ void MainWindow::ToggleSidebar(const bool checked) {
ui_->sidebar_layout->setVisible(checked);
TabSwitched();
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
s.setValue(MainWindowSettings::kShowSidebar, checked);
s.endGroup();
settings_.setValue(MainWindowSettings::kShowSidebar, checked);
}
void MainWindow::ToggleSearchCoverAuto(const bool checked) {
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
s.setValue(MainWindowSettings::kSearchForCoverAuto, checked);
s.endGroup();
settings_.setValue(MainWindowSettings::kSearchForCoverAuto, checked);
}
void MainWindow::SaveGeometry() {
if (!initialized_) return;
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();
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());
}
@@ -1762,12 +1742,7 @@ void MainWindow::SetHiddenInTray(const bool hidden) {
}
void MainWindow::FilePathChanged(const QString &path) {
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
s.setValue("file_path", path);
s.endGroup();
settings_.setValue("file_path", path);
}
void MainWindow::Seeked(const qint64 microseconds) {
@@ -2349,9 +2324,7 @@ void MainWindow::EditValue() {
void MainWindow::AddFile() {
// Last used directory
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
QString directory = s.value("add_media_path", QDir::currentPath()).toString();
QString directory = settings_.value("add_media_path", QDir::currentPath()).toString();
PlaylistParser parser(app_->tagreader_client(), app_->collection_backend());
@@ -2361,7 +2334,7 @@ void MainWindow::AddFile() {
if (filenames.isEmpty()) return;
// Save last used directory
s.setValue("add_media_path", filenames[0]);
settings_.setValue("add_media_path", filenames[0]);
// Convert to URLs
QList<QUrl> urls;
@@ -2379,16 +2352,14 @@ void MainWindow::AddFile() {
void MainWindow::AddFolder() {
// Last used directory
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
QString directory = s.value("add_folder_path", QDir::currentPath()).toString();
QString directory = settings_.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
s.setValue("add_folder_path", directory);
settings_.setValue("add_folder_path", directory);
// Add media
MimeData *mimedata = new MimeData;

View File

@@ -45,6 +45,7 @@
#include <QImage>
#include <QPixmap>
#include <QTimer>
#include <QSettings>
#include <QtEvents>
#include "includes/scoped_ptr.h"
@@ -52,6 +53,7 @@
#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"
@@ -388,6 +390,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
QTimer *track_position_timer_;
QTimer *track_slider_timer_;
Settings settings_;
bool keep_running_;
bool playing_widget_;

View File

@@ -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::OggOpus || d->filetype_ == FileType::MPEG;
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || 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::OggOpus || d->filetype_ == FileType::MPEG;
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || 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::OggOpus || d->filetype_ == FileType::MPEG;
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || 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::OggOpus || d->filetype_ == FileType::MPEG;
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || 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::OggOpus || d->filetype_ == FileType::MPEG;
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
}
bool Song::save_embedded_cover_supported(const FileType filetype) {

View File

@@ -1,303 +0,0 @@
/*
* 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());
}
}

View File

@@ -1,69 +0,0 @@
/*
* 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

View File

@@ -32,7 +32,6 @@
#include <QKeySequence>
#include "core/logging.h"
#include "core/settings.h"
#include "globalshortcutsmanager.h"
#include "globalshortcutsbackend.h"
@@ -59,32 +58,29 @@ using namespace Qt::Literals::StringLiterals;
GlobalShortcutsManager::GlobalShortcutsManager(QWidget *parent) : QWidget(parent) {
Settings s;
s.beginGroup(GlobalShortcutsSettings::kSettingsGroup);
settings_.beginGroup(GlobalShortcutsSettings::kSettingsGroup);
// Create actions
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();
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));
// Create backends - these do the actual shortcut registration
@@ -120,39 +116,35 @@ void GlobalShortcutsManager::ReloadSettings() {
backends_enabled_ << GlobalShortcutsBackend::Type::Win;
#endif
{
Settings s;
s.beginGroup(GlobalShortcutsSettings::kSettingsGroup);
#ifdef HAVE_KGLOBALACCEL_GLOBALSHORTCUTS
if (s.value(GlobalShortcutsSettings::kUseKGlobalAccel, true).toBool()) {
backends_enabled_ << GlobalShortcutsBackend::Type::KGlobalAccel;
}
#endif
#ifdef HAVE_X11_GLOBALSHORTCUTS
if (s.value(GlobalShortcutsSettings::kUseX11, false).toBool()) {
backends_enabled_ << GlobalShortcutsBackend::Type::X11;
}
#endif
s.endGroup();
if (settings_.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;
}
#endif
Unregister();
Register();
}
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
void GlobalShortcutsManager::AddShortcut(const QString &id, const QString &name, std::function<void()> signal, const QKeySequence &default_key) { // clazy:exclude=function-args-by-ref
Shortcut shortcut = AddShortcut(s, id, name, default_key);
Shortcut shortcut = AddShortcut(id, name, default_key);
QObject::connect(shortcut.action, &QAction::triggered, this, signal);
}
GlobalShortcutsManager::Shortcut GlobalShortcutsManager::AddShortcut(Settings &s, const QString &id, const QString &name, const QKeySequence &default_key) {
GlobalShortcutsManager::Shortcut GlobalShortcutsManager::AddShortcut(const QString &id, const QString &name, const QKeySequence &default_key) {
Shortcut shortcut;
shortcut.action = new QAction(name, this);
QKeySequence key_sequence = QKeySequence::fromString(s.value(id, default_key.toString()).toString());
QKeySequence key_sequence = QKeySequence::fromString(settings_.value(id, default_key.toString()).toString());
shortcut.action->setShortcut(key_sequence);
shortcut.id = id;
shortcut.default_key = default_key;

View File

@@ -32,14 +32,15 @@
#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
@@ -98,11 +99,12 @@ class GlobalShortcutsManager : public QWidget {
void Love();
private:
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);
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);
private:
QList<GlobalShortcutsBackend*> backends_;
Settings settings_;
QList<GlobalShortcutsBackend::Type> backends_enabled_;
QMap<QString, Shortcut> shortcuts_;
};

View File

@@ -93,10 +93,6 @@
# 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"
@@ -369,11 +365,6 @@ 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

View File

@@ -106,6 +106,8 @@ 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);
@@ -255,11 +257,7 @@ void PlaylistContainer::ReloadSettings() {
ui_->redo->setIconSize(QSize(iconsize, iconsize));
ui_->search_field->setIconSize(iconsize);
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();
bool playlist_clear = settings_.value("playlist_clear", true).toBool();
if (playlist_clear) {
ui_->clear->show();
}
@@ -267,6 +265,7 @@ 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();
@@ -309,12 +308,7 @@ 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?
Settings s;
s.beginGroup(kSettingsGroup);
const int current_playlist = s.value("current_playlist", 1).toInt();
s.endGroup();
if (starting_up_ && current_playlist == id) {
if (starting_up_ && settings_.value("current_playlist", 1).toInt() == id) {
starting_up_ = false;
ui_->tab_bar->set_current_id(id);
}
@@ -353,14 +347,12 @@ void PlaylistContainer::NewPlaylist() { manager_->New(tr("Playlist")); }
void PlaylistContainer::LoadPlaylist() {
Settings s;
s.beginGroup(kSettingsGroup);
QString filename = s.value("last_load_playlist").toString();
QString filename = settings_.value("last_load_playlist").toString();
filename = QFileDialog::getOpenFileName(this, tr("Load playlist"), filename, manager_->parser()->filters(PlaylistParser::Type::Load));
if (filename.isNull()) return;
s.setValue("last_load_playlist", filename);
settings_.setValue("last_load_playlist", filename);
manager_->Load(filename);
@@ -399,10 +391,7 @@ void PlaylistContainer::Save() {
if (starting_up_) return;
Settings s;
s.beginGroup(kSettingsGroup);
s.setValue("current_playlist", ui_->tab_bar->current_id());
s.endGroup();
settings_.setValue("current_playlist", ui_->tab_bar->current_id());
}

View File

@@ -28,6 +28,7 @@
#include <QWidget>
#include <QString>
#include <QIcon>
#include <QSettings>
class QTimer;
class QTimeLine;
@@ -44,6 +45,7 @@ class PlaylistView;
class Ui_PlaylistContainer;
#include "includes/shared_ptr.h"
#include "core/settings.h"
class PlaylistContainer : public QWidget {
Q_OBJECT
@@ -116,6 +118,7 @@ class PlaylistContainer : public QWidget {
QAction *redo_;
Playlist *playlist_;
Settings settings_;
bool starting_up_;
bool tab_bar_visible_;

View File

@@ -22,6 +22,7 @@
#include "config.h"
#include <utility>
#include <algorithm>
#include <QWidget>
#include <QVariant>
@@ -33,9 +34,12 @@
#include <QAction>
#include <QActionGroup>
#include <QToolButton>
#include <QScreen>
#include <QGuiApplication>
#include "core/iconloader.h"
#include "core/settingsprovider.h"
#include "utilities/screenutils.h"
#include "playlistsequence.h"
#include "ui_playlistsequence.h"
@@ -43,7 +47,14 @@ using namespace Qt::Literals::StringLiterals;
namespace {
constexpr char kSettingsGroup[] = "PlaylistSequence";
}
// Base icon size for reference resolution (1920x1080 at 1.0 scale)
constexpr int kBaseIconSize = 20;
constexpr int kReferenceWidth = 1920;
constexpr int kReferenceHeight = 1080;
constexpr qreal kReferenceDevicePixelRatio = 1.0;
} // namespace
PlaylistSequence::PlaylistSequence(QWidget *parent, SettingsProvider *settings)
: QWidget(parent),
@@ -60,9 +71,11 @@ PlaylistSequence::PlaylistSequence(QWidget *parent, SettingsProvider *settings)
// Icons
ui_->repeat->setIcon(AddDesaturatedIcon(IconLoader::Load(u"media-playlist-repeat"_s)));
ui_->shuffle->setIcon(AddDesaturatedIcon(IconLoader::Load(u"media-playlist-shuffle"_s)));
const int base_icon_size = static_cast<int>(fontMetrics().height() * 1.2);
ui_->repeat->setIconSize(QSize(base_icon_size, base_icon_size));
ui_->shuffle->setIconSize(QSize(base_icon_size, base_icon_size));
// Calculate icon size dynamically based on screen resolution, aspect ratio, and scaling
const int icon_size = CalculateIconSize();
ui_->repeat->setIconSize(QSize(icon_size, icon_size));
ui_->shuffle->setIconSize(QSize(icon_size, icon_size));
// Remove arrow indicators
ui_->repeat->setStyleSheet(u"QToolButton::menu-indicator { image: none; }"_s);
@@ -99,6 +112,48 @@ PlaylistSequence::~PlaylistSequence() {
delete ui_;
}
int PlaylistSequence::CalculateIconSize() {
// Get screen information for the widget
QScreen *screen = Utilities::GetScreen(this);
if (!screen) {
screen = QGuiApplication::primaryScreen();
}
if (!screen) {
// Fallback to a reasonable default if no screen is available
return kBaseIconSize;
}
// Get screen properties
const QSize screen_size = screen->size();
const qreal device_pixel_ratio = screen->devicePixelRatio();
const int screen_width = screen_size.width();
const int screen_height = screen_size.height();
// Calculate scaling factors based on resolution
// Use the smaller dimension to handle both landscape and portrait orientations
const int min_dimension = std::min(screen_width, screen_height);
const int ref_min_dimension = std::min(kReferenceWidth, kReferenceHeight);
const qreal resolution_factor = static_cast<qreal>(min_dimension) / static_cast<qreal>(ref_min_dimension);
// Apply device pixel ratio (for high-DPI displays)
const qreal dpi_factor = device_pixel_ratio / kReferenceDevicePixelRatio;
// Calculate final icon size with combined scaling
// Formula: 50% from resolution scaling + 50% from DPI scaling + 50% base multiplier
// The 0.5 base ensures icons scale up appropriately across different displays
// Without it, icons would be too small on average displays
const qreal combined_factor = (resolution_factor * 0.5) + (dpi_factor * 0.5) + 0.5;
int calculated_size = static_cast<int>(kBaseIconSize * combined_factor);
// Clamp to reasonable bounds (minimum 16px, maximum 48px)
calculated_size = std::max(16, std::min(48, calculated_size));
return calculated_size;
}
void PlaylistSequence::Load() {
loading_ = true; // Stops these setter functions calling Save()

View File

@@ -83,6 +83,7 @@ class PlaylistSequence : public QWidget {
private:
void Load();
void Save();
int CalculateIconSize();
static QIcon AddDesaturatedIcon(const QIcon &icon);
static QPixmap DesaturatedPixmap(const QPixmap &pixmap);

View File

@@ -57,6 +57,7 @@
#include <QLinearGradient>
#include <QScrollBar>
#include <QtEvents>
#include <QSettings>
#include <QDrag>
#include "includes/qt_blurimage.h"