UI(Windows): add task icon progress and buttons

This commit is contained in:
2025-07-14 21:26:53 +08:00
parent dc4b9fc047
commit 91c6672f5c
6 changed files with 666 additions and 2 deletions

View File

@ -58,6 +58,7 @@ set (PMUSIC_HEADER_FILES
lyricsmanager.h
fftspectrum.h
playbackprogressindicator.h
taskbarmanager.h
)
set (PMUSIC_UI_FILES
@ -84,6 +85,9 @@ TS_FILES
if (WIN32)
target_sources(${EXE_NAME} PRIVATE assets/pineapple-music.rc)
target_sources(${EXE_NAME} PRIVATE taskbarmanager.cpp)
else ()
target_sources(${EXE_NAME} PRIVATE taskbarmanager_dummy.cpp)
endif ()
if (NOT TagLib_FOUND)

View File

@ -8,6 +8,7 @@
#include "playlistmanager.h"
#include "fftspectrum.h"
#include "lrcbar.h"
#include "taskbarmanager.h"
// taglib
#ifndef NO_TAGLIB
@ -34,6 +35,7 @@
#include <QStringBuilder>
#include <QSettings>
#include <QGraphicsDropShadowEffect>
#include <QTimer>
constexpr QSize miniSize(490, 160);
constexpr QSize fullSize(490, 420);
@ -47,6 +49,7 @@ MainWindow::MainWindow(QWidget *parent)
, m_fftSpectrum(new FFTSpectrum(this))
, m_lrcbar(new LrcBar(nullptr))
, m_playlistManager(new PlaylistManager(this))
, m_taskbarManager(new TaskBarManager(this))
{
ui->setupUi(this);
m_playlistManager->setAutoLoadFilterSuffixes({
@ -70,12 +73,21 @@ MainWindow::MainWindow(QWidget *parent)
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinimizeButtonHint);
setAttribute(Qt::WA_TranslucentBackground, true);
m_taskbarManager->setCanTogglePlayback(true);
m_taskbarManager->setCanSkipBackward(true);
m_taskbarManager->setCanSkipForward(true);
m_taskbarManager->setShowProgress(true);
loadConfig();
loadSkinData();
initConnections();
initUiAndAnimation();
centerWindow();
QTimer::singleShot(1000, [this](){
m_taskbarManager->setWinId(window()->winId());
});
}
MainWindow::~MainWindow()
@ -492,6 +504,7 @@ void MainWindow::initConnections()
ui->nowTimeLabel->setText(ms2str(pos));
if (m_mediaPlayer->duration() != 0) {
ui->playbackProgressIndicator->setPosition(pos);
m_taskbarManager->setProgressValue(pos);
}
m_lrcbar->playbackPositionChanged(pos, m_mediaPlayer->duration());
});
@ -506,6 +519,7 @@ void MainWindow::initConnections()
connect(m_mediaPlayer, &QMediaPlayer::durationChanged, this, [=](qint64 dua) {
ui->playbackProgressIndicator->setDuration(dua);
m_taskbarManager->setProgressMaximum(dua);
ui->totalTimeLabel->setText(ms2str(dua));
});
@ -519,6 +533,7 @@ void MainWindow::initConnections()
ui->playBtn->setIcon(QIcon(":/icons/icons/media-playback-start.png"));
break;
}
m_taskbarManager->setPlaybackState(newState);
});
connect(m_audioOutput, &QAudioOutput::volumeChanged, this, [=](float vol) {
@ -559,9 +574,21 @@ void MainWindow::initConnections()
}
});
connect(m_mediaPlayer, &QMediaPlayer::errorOccurred, this, [=](QMediaPlayer::Error error, const QString &errorString) {
connect(m_taskbarManager, &TaskBarManager::togglePlayback, this, [this](){
on_playBtn_clicked();
});
connect(m_taskbarManager, &TaskBarManager::skipBackward, this, [this](){
on_prevBtn_clicked();
});
connect(m_taskbarManager, &TaskBarManager::skipForward, this, [this](){
on_nextBtn_clicked();
});
connect(m_mediaPlayer, &QMediaPlayer::errorOccurred, this, [=](QMediaPlayer::Error error, const QString &errorString) {
qDebug() << error << errorString;
});
});
}
void MainWindow::loadConfig()

View File

@ -22,6 +22,7 @@ QT_END_NAMESPACE
class FFTSpectrum;
class LrcBar;
class PlaylistManager;
class TaskBarManager;
class MainWindow : public QMainWindow
{
Q_OBJECT
@ -103,6 +104,7 @@ private:
LrcBar *m_lrcbar;
QPropertyAnimation *m_fadeOutAnimation;
PlaylistManager *m_playlistManager;
TaskBarManager *m_taskbarManager;
void initUiAndAnimation();
void initConnections();

383
taskbarmanager.cpp Normal file
View File

@ -0,0 +1,383 @@
// SPDX-FileCopyrightText: 2024 (c) Jack Hill <jackhill3103@gmail.com>
// SPDX-FileCopyrightText: 2025 (c) Gary Wang <opensource@blumia.net>
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// This file is modified based on Elisa's taskmanager.cpp implementation,
// with its Qt Quick usage removed.
#include "taskbarmanager.h"
#include <QAbstractEventDispatcher>
#include <QAbstractNativeEventFilter>
#include <QDebug>
#include <Shobjidl.h>
#include <WinDef.h>
#include <Windows.h>
constexpr int numOfButtons = 3;
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
using namespace Qt::Literals::StringLiterals;
static HICON hiconFromTheme(const QString &iconName)
#else
static HICON hiconFromTheme(const QIcon::ThemeIcon iconName)
#endif
{
const auto width = GetSystemMetrics(SM_CXSMICON);
const auto height = GetSystemMetrics(SM_CYSMICON);
return QIcon::fromTheme(iconName).pixmap(width, height).toImage().toHICON();
}
class TaskBarManagerPrivate
{
public:
ITaskbarList3 *taskBar = nullptr;
HWND hwnd = nullptr;
/**
* Whether the toolbar has been added to the taskbar
*/
bool addedThumbButtons = false;
QMediaPlayer::PlaybackState playbackState = QMediaPlayer::StoppedState;
bool showProgress = false;
qlonglong progressMaximum = 0;
qlonglong progressValue = 0;
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
HICON skipBackwardIcon = hiconFromTheme(u"media-skip-backward"_s);
HICON skipForwardIcon = hiconFromTheme(u"media-skip-forward"_s);
HICON playIcon = hiconFromTheme(u"media-playback-start"_s);
HICON pauseIcon = hiconFromTheme(u"media-playback-pause"_s);
#else
HICON skipBackwardIcon = hiconFromTheme(QIcon::ThemeIcon::MediaSkipBackward);
HICON skipForwardIcon = hiconFromTheme(QIcon::ThemeIcon::MediaSkipForward);
HICON playIcon = hiconFromTheme(QIcon::ThemeIcon::MediaPlaybackStart);
HICON pauseIcon = hiconFromTheme(QIcon::ThemeIcon::MediaPlaybackPause);
#endif
/**
* Contains the current state of each button
*/
struct ButtonData {
HICON icon = nullptr;
bool enabled = false;
};
std::array<ButtonData, numOfButtons> buttonData = {{
{skipBackwardIcon, false},
{playIcon, false},
{skipForwardIcon, false}
}};
/**
* For convenience when reading code
*/
enum ButtonID {
SkipBackward = 0,
TogglePlayback = 1,
SkipForward = 2,
};
TaskBarManagerPrivate()
{
HRESULT result = CoCreateInstance(CLSID_TaskbarList,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&taskBar));
if (FAILED(result)) {
taskBar = nullptr;
qWarning() << "Failed to create Windows taskbar instance";
return;
}
}
~TaskBarManagerPrivate()
{
if (taskBar) {
taskBar->Release();
}
DestroyIcon(skipForwardIcon);
DestroyIcon(skipBackwardIcon);
DestroyIcon(pauseIcon);
DestroyIcon(playIcon);
}
/**
* Update the native window handle for the taskbar instance
*/
void setHWND(HWND newHwnd)
{
if (hwnd == newHwnd) {
return;
}
hwnd = newHwnd;
addedThumbButtons = false;
if (hwnd) {
updateProgressFlags();
updateProgressValue();
setupTaskBarManagerButtons();
}
}
/**
* Update the taskbar progress flags
*/
void updateProgressFlags()
{
if (taskBar && hwnd) {
taskBar->SetProgressState(hwnd, progressFlags());
}
}
/**
* Update the taskbar progress value, only if the taskbar is showing progress
*/
void updateProgressValue()
{
if (taskBar && hwnd && showProgress && playbackState != QMediaPlayer::StoppedState) {
taskBar->SetProgressValue(hwnd, progressValue, progressMaximum);
}
}
/**
* Update the state of a single toolbutton
*/
void updateButton(const ButtonID buttonId)
{
if (!taskBar || !hwnd || !addedThumbButtons) {
return;
}
THUMBBUTTON thumbButtons[1];
initThumbButton(thumbButtons, buttonId);
taskBar->ThumbBarUpdateButtons(hwnd, 1, thumbButtons);
}
private:
/**
* Convert playbackState into windows taskbar flags
*/
TBPFLAG progressFlags() const
{
if (!showProgress) {
return TBPF_NOPROGRESS;
}
switch (playbackState) {
case QMediaPlayer::StoppedState:
return TBPF_NOPROGRESS;
case QMediaPlayer::PlayingState:
return TBPF_NORMAL;
case QMediaPlayer::PausedState:
return TBPF_PAUSED;
default:
return TBPF_NOPROGRESS;
}
}
/**
* Initialize a single toolbutton
*/
void initThumbButton(THUMBBUTTON *button, const ButtonID id)
{
button->dwMask = THB_ICON | THB_FLAGS;
button->iId = id;
button->hIcon = buttonData.at(id).icon;
button->dwFlags = buttonData.at(id).enabled ? THBF_ENABLED : THBF_DISABLED;
}
/**
* Initialize the toolbar
*/
void setupTaskBarManagerButtons()
{
if (!taskBar || !hwnd || addedThumbButtons) {
return;
}
THUMBBUTTON thumbButtons[numOfButtons];
THUMBBUTTON *currButton = thumbButtons;
for (int i = 0; i < numOfButtons; ++i) {
initThumbButton(currButton++, static_cast<ButtonID>(i));
}
// win32 api reference says we should pass &thumbButtons but this doesn't compile
HRESULT result = taskBar->ThumbBarAddButtons(hwnd, numOfButtons, thumbButtons);
if (FAILED(result)) {
qWarning() << "Failed to create Windows taskbar buttons";
} else {
addedThumbButtons = true;
}
}
};
TaskBarManager::TaskBarManager(QObject *parent)
: QObject(parent)
, d(std::make_unique<TaskBarManagerPrivate>())
{
QAbstractEventDispatcher::instance()->installNativeEventFilter(this);
}
TaskBarManager::~TaskBarManager() = default;
bool TaskBarManager::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *)
{
if (eventType == "windows_generic_MSG") {
MSG *msg = static_cast<MSG *>(message);
if (msg->hwnd != d->hwnd || msg->message != WM_COMMAND || HIWORD(msg->wParam) != THBN_CLICKED) {
return false;
}
switch (LOWORD(msg->wParam)) {
case TaskBarManagerPrivate::SkipBackward:
Q_EMIT skipBackward();
return true;
case TaskBarManagerPrivate::TogglePlayback:
Q_EMIT togglePlayback();
return true;
case TaskBarManagerPrivate::SkipForward:
Q_EMIT skipForward();
return true;
}
}
return false;
}
QMediaPlayer::PlaybackState TaskBarManager::playbackState() const
{
return d->playbackState;
}
bool TaskBarManager::showProgress() const
{
return d->showProgress;
}
qulonglong TaskBarManager::progressMaximum() const
{
return d->progressMaximum;
}
qulonglong TaskBarManager::progressValue() const
{
return d->progressValue;
}
bool TaskBarManager::canSkipBackward() const
{
return d->buttonData.at(TaskBarManagerPrivate::SkipBackward).enabled;
}
bool TaskBarManager::canSkipForward() const
{
return d->buttonData.at(TaskBarManagerPrivate::SkipForward).enabled;
}
bool TaskBarManager::canTogglePlayback() const
{
return d->buttonData.at(TaskBarManagerPrivate::TogglePlayback).enabled;
}
void TaskBarManager::setWinId(WId winId)
{
d->setHWND(reinterpret_cast<HWND>(winId));
}
void TaskBarManager::setPlaybackState(const QMediaPlayer::PlaybackState newPlaybackState)
{
if (d->playbackState == newPlaybackState) {
return;
}
d->playbackState = newPlaybackState;
Q_EMIT playbackStateChanged();
d->buttonData.at(TaskBarManagerPrivate::TogglePlayback).icon =
d->playbackState == QMediaPlayer::PlayingState ? d->pauseIcon : d->playIcon;
d->updateButton(TaskBarManagerPrivate::TogglePlayback);
d->updateProgressFlags();
}
void TaskBarManager::setShowProgress(const bool showProgress)
{
if (d->showProgress == showProgress) {
return;
}
d->showProgress = showProgress;
Q_EMIT showProgressChanged();
d->updateProgressFlags();
}
void TaskBarManager::setProgressMaximum(const qlonglong newMaximum)
{
if (d->progressMaximum == newMaximum) {
return;
}
d->progressMaximum = newMaximum;
Q_EMIT progressMaximumChanged();
if (d->progressValue > d->progressMaximum) {
d->progressValue = d->progressMaximum;
Q_EMIT progressValueChanged();
}
d->updateProgressValue();
}
void TaskBarManager::setProgressValue(const qlonglong newValue)
{
if (d->progressValue == newValue) {
return;
}
d->progressValue = newValue < d->progressMaximum ? newValue : d->progressMaximum;
Q_EMIT progressValueChanged();
d->updateProgressValue();
}
void TaskBarManager::setCanSkipBackward(const bool canSkip)
{
if (d->buttonData[TaskBarManagerPrivate::SkipBackward].enabled == canSkip) {
return;
}
d->buttonData[TaskBarManagerPrivate::SkipBackward].enabled = canSkip;
Q_EMIT canSkipBackwardChanged();
d->updateButton(TaskBarManagerPrivate::SkipBackward);
}
void TaskBarManager::setCanSkipForward(const bool canSkip)
{
if (d->buttonData[TaskBarManagerPrivate::SkipForward].enabled == canSkip) {
return;
}
d->buttonData[TaskBarManagerPrivate::SkipForward].enabled = canSkip;
Q_EMIT canSkipForwardChanged();
d->updateButton(TaskBarManagerPrivate::SkipForward);
}
void TaskBarManager::setCanTogglePlayback(const bool canToggle)
{
if (d->buttonData[TaskBarManagerPrivate::TogglePlayback].enabled == canToggle) {
return;
}
d->buttonData[TaskBarManagerPrivate::TogglePlayback].enabled = canToggle;
Q_EMIT canTogglePlaybackChanged();
d->updateButton(TaskBarManagerPrivate::TogglePlayback);
}
#include "moc_taskbarmanager.cpp"

151
taskbarmanager.h Normal file
View File

@ -0,0 +1,151 @@
// SPDX-FileCopyrightText: 2024 (c) Jack Hill <jackhill3103@gmail.com>
// SPDX-FileCopyrightText: 2025 (c) Gary Wang <opensource@blumia.net>
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// This file is modified based on Elisa's taskmanager.cpp implementation,
// with its Qt Quick usage removed.
#ifndef TASKBAR_H
#define TASKBAR_H
#include <QAbstractNativeEventFilter>
#include <QMediaPlayer>
#include <QWindow>
#include <memory>
class TaskBarManagerPrivate;
/**
* Windows taskbar
*/
class TaskBarManager : public QObject, public QAbstractNativeEventFilter
{
Q_OBJECT
/**
* Whether the media is playing, paused, or stopped
*/
Q_PROPERTY(QMediaPlayer::PlaybackState playbackState
READ playbackState
WRITE setPlaybackState
NOTIFY playbackStateChanged)
/**
* Whether to show track progress on the taskbar
*/
Q_PROPERTY(bool showProgress
READ showProgress
WRITE setShowProgress
NOTIFY showProgressChanged)
/**
* Maximum possible progress
*/
Q_PROPERTY(qulonglong progressMaximum
READ progressMaximum
WRITE setProgressMaximum
NOTIFY progressMaximumChanged)
/**
* Current progress; this is always clamped to be in the range [0, progressMaximum]
*/
Q_PROPERTY(qulonglong progressValue
READ progressValue
WRITE setProgressValue
NOTIFY progressValueChanged)
/**
* Whether the "Skip Backward" button is enabled
*/
Q_PROPERTY(bool canSkipBackward
READ canSkipBackward
WRITE setCanSkipBackward
NOTIFY canSkipBackwardChanged)
/**
* Whether the "Skip Forward" button is enabled
*/
Q_PROPERTY(bool canSkipForward
READ canSkipForward
WRITE setCanSkipForward
NOTIFY canSkipForwardChanged)
/**
* Whether the "Toggle Playback" button is enabled
*/
Q_PROPERTY(bool canTogglePlayback
READ canTogglePlayback
WRITE setCanTogglePlayback
NOTIFY canTogglePlaybackChanged)
public:
explicit TaskBarManager(QObject *parent = nullptr);
~TaskBarManager() override;
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override;
[[nodiscard]] QMediaPlayer::PlaybackState playbackState() const;
[[nodiscard]] bool showProgress() const;
[[nodiscard]] qulonglong progressMaximum() const;
[[nodiscard]] qulonglong progressValue() const;
[[nodiscard]] bool canSkipBackward() const;
[[nodiscard]] bool canSkipForward() const;
[[nodiscard]] bool canTogglePlayback() const;
Q_SIGNALS:
void playbackStateChanged();
void showProgressChanged();
void progressMaximumChanged();
void progressValueChanged();
void canSkipBackwardChanged();
void canSkipForwardChanged();
void canTogglePlaybackChanged();
void skipBackward();
void skipForward();
void togglePlayback();
public Q_SLOTS:
void setWinId(WId win);
void setPlaybackState(QMediaPlayer::PlaybackState newPlaybackState);
void setShowProgress(bool showProgress);
void setProgressMaximum(qlonglong newMaximum);
void setProgressValue(qlonglong newValue);
void setCanSkipBackward(bool canSkip);
void setCanSkipForward(bool canSkip);
void setCanTogglePlayback(bool canToggle);
private:
std::unique_ptr<TaskBarManagerPrivate> d;
};
#endif // TASKBAR_H

97
taskbarmanager_dummy.cpp Normal file
View File

@ -0,0 +1,97 @@
// SPDX-FileCopyrightText: 2025 Gary Wang <opensource@blumia.net>
//
// SPDX-License-Identifier: MIT
#include "taskbarmanager.h"
#include <QDebug>
class TaskBarManagerPrivate {};
TaskBarManager::TaskBarManager(QObject *parent)
: QObject(parent)
{
}
TaskBarManager::~TaskBarManager() = default;
bool TaskBarManager::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *)
{
return false;
}
QMediaPlayer::PlaybackState TaskBarManager::playbackState() const
{
return QMediaPlayer::StoppedState;
}
bool TaskBarManager::showProgress() const
{
return false;
}
qulonglong TaskBarManager::progressMaximum() const
{
return 100;
}
qulonglong TaskBarManager::progressValue() const
{
return 50;
}
bool TaskBarManager::canSkipBackward() const
{
return false;
}
bool TaskBarManager::canSkipForward() const
{
return false;
}
bool TaskBarManager::canTogglePlayback() const
{
return false;
}
void TaskBarManager::setWinId(WId winId)
{
}
void TaskBarManager::setPlaybackState(const QMediaPlayer::PlaybackState newPlaybackState)
{
Q_UNUSED(newPlaybackState);
}
void TaskBarManager::setShowProgress(const bool showProgress)
{
Q_UNUSED(showProgress);
}
void TaskBarManager::setProgressMaximum(const qlonglong newMaximum)
{
Q_UNUSED(newMaximum);
}
void TaskBarManager::setProgressValue(const qlonglong newValue)
{
Q_UNUSED(newValue);
}
void TaskBarManager::setCanSkipBackward(const bool canSkip)
{
Q_UNUSED(canSkip);
}
void TaskBarManager::setCanSkipForward(const bool canSkip)
{
Q_UNUSED(canSkip);
}
void TaskBarManager::setCanTogglePlayback(const bool canToggle)
{
Q_UNUSED(canToggle);
}
#include "moc_taskbarmanager.cpp"