384 lines
10 KiB
C++
384 lines
10 KiB
C++
// 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"
|