diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 707bc72..891b8b8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,7 @@ set(SOURCES main.cpp powermanagementd.cpp trayicon.cpp + trayiconbattery.cpp iconproducer.cpp powerbutton.cpp ../config/powermanagementsettings.cpp diff --git a/src/batterywatcher.cpp b/src/batterywatcher.cpp index a13afe5..2c1863d 100644 --- a/src/batterywatcher.cpp +++ b/src/batterywatcher.cpp @@ -69,11 +69,6 @@ BatteryWatcher::BatteryWatcher(QObject *parent) : Watcher(parent) settingsChanged(); batteryChanged(); - - // pause timer - mPauseTimer.setSingleShot(true); - mPauseTimer.setTimerType(Qt::VeryCoarseTimer); - connect(&mPauseTimer, &QTimer::timeout, this, &BatteryWatcher::onPauseTimeout); } BatteryWatcher::~BatteryWatcher() @@ -177,31 +172,9 @@ void BatteryWatcher::settingsChanged() { for (Solid::Battery *battery : std::as_const(mBatteries)) { - mTrayIcons.append(new TrayIcon(battery, this)); + mTrayIcons.append(new TrayIconBattery(battery, this)); connect(mTrayIcons.last(), &TrayIcon::toggleShowInfo, mBatteryInfoDialog, &BatteryInfoDialog::toggleShow); - connect(mTrayIcons.last(), &TrayIcon::pauseChanged, this, &BatteryWatcher::setPause); mTrayIcons.last()->show(); } } } - -void BatteryWatcher::onPauseTimeout() -{ - for (const auto &trayIcon : std::as_const(mTrayIcons)) - trayIcon->setPause(TrayIcon::PAUSE::None); -} - -void BatteryWatcher::setPause(TrayIcon::PAUSE duration) -{ - if (duration == TrayIcon::PAUSE::None) - { - onPauseTimeout(); - mPauseTimer.stop(); - } - else - { - for (const auto &trayIcon : std::as_const(mTrayIcons)) - trayIcon->setPause(duration); - mPauseTimer.start(TrayIcon::getPauseInterval(duration)); - } -} diff --git a/src/batterywatcher.h b/src/batterywatcher.h index 60b4bfc..9262891 100644 --- a/src/batterywatcher.h +++ b/src/batterywatcher.h @@ -28,7 +28,7 @@ #define BATTERYWATCHER_H #include "watcher.h" -#include "trayicon.h" +#include "trayiconbattery.h" #include "batteryinfodialog.h" #include "../config/powermanagementsettings.h" @@ -47,13 +47,10 @@ class BatteryWatcher : public Watcher private slots: void batteryChanged(); void settingsChanged(); - void onPauseTimeout(); - void setPause(TrayIcon::PAUSE duration); private: QList mBatteries; - QList mTrayIcons; - QTimer mPauseTimer; + QList mTrayIcons; PowerManagementSettings mSettings; BatteryInfoDialog *mBatteryInfoDialog; diff --git a/src/powermanagementd.cpp b/src/powermanagementd.cpp index c318ca4..0fc1a24 100644 --- a/src/powermanagementd.cpp +++ b/src/powermanagementd.cpp @@ -43,6 +43,8 @@ PowerManagementd::PowerManagementd() : mBatterywatcherd(nullptr), mLidwatcherd(nullptr), mIdlenesswatcherd(nullptr), + mPowerButton(nullptr), + mTrayIcon(nullptr), mSettings() { connect(&mSettings, &PowerManagementSettings::settingsChanged, this, &PowerManagementd::settingsChanged); @@ -70,6 +72,16 @@ void PowerManagementd::settingsChanged() mBatterywatcherd->deleteLater(); mBatterywatcherd = nullptr; } + if (!mTrayIcon && (!mBatterywatcherd || !mSettings.isShowIcon())) + { + mTrayIcon = new TrayIcon(this); + mTrayIcon->show(); + } else if (mBatterywatcherd && mSettings.isShowIcon() && mTrayIcon) + { + mTrayIcon->hide(); + mTrayIcon->deleteLater(); + mTrayIcon = nullptr; + } if (mSettings.isLidWatcherEnabled() && !mLidwatcherd) { diff --git a/src/powermanagementd.h b/src/powermanagementd.h index 95450eb..66f5b48 100644 --- a/src/powermanagementd.h +++ b/src/powermanagementd.h @@ -17,6 +17,7 @@ class BatteryWatcher; class LidWatcher; class IdlenessWatcher; class PowerButton; +class TrayIcon; class PowerManagementd : public QObject { @@ -36,7 +37,8 @@ private slots: BatteryWatcher* mBatterywatcherd; LidWatcher* mLidwatcherd; IdlenessWatcher* mIdlenesswatcherd; - PowerButton *mPowerButton; + PowerButton* mPowerButton; + TrayIcon* mTrayIcon; PowerManagementSettings mSettings; LXQt::Notification mNotification; diff --git a/src/trayicon.cpp b/src/trayicon.cpp index d5a03c6..b2c5719 100644 --- a/src/trayicon.cpp +++ b/src/trayicon.cpp @@ -5,8 +5,10 @@ * https://lxqt.org * * Copyright: 2011 Razor team + * 2025~ LXQt team * Authors: * Christian Surlykke + * Palo Kisa * * This program or library is free software; you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public @@ -25,100 +27,109 @@ * * END_COMMON_COPYRIGHT_HEADER */ -#include -#include #include #include #include -#include -#include #include #include #include -#include -#include -#include #include -#include #include "trayicon.h" -#include "batteryhelper.h" #include "../config/powermanagementsettings.h" #include #include #include "PowerProfiles.h" +#include -TrayIcon::TrayIcon(Solid::Battery *battery, QObject *parent) +QList TrayIcon::msInstances; +std::unique_ptr TrayIcon::msContextMenu{nullptr}; +std::unique_ptr TrayIcon::msPauseTimer{nullptr}; +std::unique_ptr TrayIcon::msPauseActions{nullptr}; + +TrayIcon::TrayIcon(QObject *parent) : QSystemTrayIcon(parent), - mBattery(battery), - mIconProducer(battery), - mContextMenu(), + mBaseIcon(QIcon::fromTheme(QL1S("preferences-system-power-management"))), mHasPauseEmblem(false) { - connect(mBattery, &Solid::Battery::chargePercentChanged, this, &TrayIcon::updateTooltip); - connect(mBattery, &Solid::Battery::chargeStateChanged, this, &TrayIcon::updateTooltip); - connect(mBattery, &Solid::Battery::timeToEmptyChanged, this, &TrayIcon::updateTooltip); - connect(mBattery, &Solid::Battery::timeToFullChanged, this, &TrayIcon::updateTooltip); - updateTooltip(); - connect(&mIconProducer, &IconProducer::iconChanged, this, &TrayIcon::iconChanged); iconChanged(); connect(this, &TrayIcon::activated, this, &TrayIcon::onActivated); - mContextMenu.addAction(QIcon::fromTheme(QStringLiteral("configure")), tr("Configure"), - this, &TrayIcon::onConfigureTriggered); + if (!msPauseTimer) + { + msPauseTimer.reset(new QTimer); + msPauseTimer->setSingleShot(true); + msPauseTimer->setTimerType(Qt::VeryCoarseTimer); + connect(msPauseTimer.get(), &QTimer::timeout, [] { onPauseTimeout(); }); + } + if (!msContextMenu) { + msContextMenu.reset(new QMenu); + connect(msContextMenu->addAction(QIcon::fromTheme(QStringLiteral("configure")), tr("Configure")), &QAction::triggered, [] { onConfigureTriggered(); }); - // pause actions - mPauseActions = new QActionGroup(this); - mPauseActions->setExclusionPolicy(QActionGroup::ExclusionPolicy::ExclusiveOptional); - connect(mPauseActions, &QActionGroup::triggered, this, &TrayIcon::onPauseTriggered); + // pause actions + msPauseActions.reset(new QActionGroup{nullptr}); + msPauseActions->setExclusionPolicy(QActionGroup::ExclusionPolicy::ExclusiveOptional); + connect(msPauseActions.get(), &QActionGroup::triggered, [] (QAction * action) { onPauseTriggered(action); }); - QAction *a = new QAction(tr("30 minutes"), mPauseActions); - a->setCheckable(true); - a->setData(PAUSE::Half); + QAction *a = new QAction(tr("30 minutes"), msPauseActions.get()); + a->setCheckable(true); + a->setData(PAUSE::Half); - a = new QAction(tr("1 hour"), mPauseActions); - a->setCheckable(true); - a->setData(PAUSE::One); + a = new QAction(tr("1 hour"), msPauseActions.get()); + a->setCheckable(true); + a->setData(PAUSE::One); - a = new QAction(tr("2 hours"), mPauseActions); - a->setCheckable(true); - a->setData(PAUSE::Two); + a = new QAction(tr("2 hours"), msPauseActions.get()); + a->setCheckable(true); + a->setData(PAUSE::Two); - a = new QAction(tr("3 hours"), mPauseActions); - a->setCheckable(true); - a->setData(PAUSE::Three); + a = new QAction(tr("3 hours"), msPauseActions.get()); + a->setCheckable(true); + a->setData(PAUSE::Three); - a = new QAction(tr("4 hours"), mPauseActions); - a->setCheckable(true); - a->setData(PAUSE::Four); + a = new QAction(tr("4 hours"), msPauseActions.get()); + a->setCheckable(true); + a->setData(PAUSE::Four); - QMenu *pauseMenu = mContextMenu.addMenu(QIcon::fromTheme(QStringLiteral("media-playback-pause")), - tr("Pause idleness checks")); - pauseMenu->addActions(mPauseActions->actions()); + QMenu *pauseMenu = msContextMenu->addMenu(QIcon::fromTheme(QStringLiteral("media-playback-pause")), + tr("Pause idleness checks")); + pauseMenu->addActions(msPauseActions->actions()); - // power-profiles actions - mContextMenu.addAction(LXQt::PowerProfiles::instance().menuAction()); + // power-profiles actions + msContextMenu->addAction(LXQt::PowerProfiles::instance().menuAction()); - mContextMenu.addSeparator(); + msContextMenu->addSeparator(); - mContextMenu.addAction(QIcon::fromTheme(QStringLiteral("help-about")), tr("About"), - this, &TrayIcon::onAboutTriggered); - mContextMenu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), tr("Disable icon"), - this, &TrayIcon::onDisableIconTriggered); - setContextMenu(&mContextMenu); + connect(msContextMenu->addAction(QIcon::fromTheme(QStringLiteral("help-about")), tr("About")), &QAction::triggered, [] { TrayIcon::onAboutTriggered(); }); + connect(msContextMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), tr("Disable icon")), &QAction::triggered, [] { TrayIcon::onDisableIconTriggered(); }); + } + setContextMenu(msContextMenu.get()); + msInstances.push_back(this); } TrayIcon::~TrayIcon() { + msInstances.removeOne(this); + if (msInstances.empty()) + { + msPauseTimer.reset(nullptr); + msPauseActions.reset(nullptr); + msContextMenu.reset(nullptr); + } +} + +/*virtual*/ const QIcon & TrayIcon::getIcon() const +{ + return mBaseIcon; } void TrayIcon::iconChanged() { mHasPauseEmblem = PowerManagementSettings().isIdlenessWatcherPaused(); - setIcon(mHasPauseEmblem ? emblemizedIcon() : mIconProducer.mIcon); + setIcon(mHasPauseEmblem ? emblemizedIcon() : getIcon()); } QIcon TrayIcon::emblemizedIcon() @@ -131,7 +142,7 @@ QIcon TrayIcon::emblemizedIcon() ); const QSize icnSize(200, 200); // NOTE: QSystemTrayIcon::geometry() gives an empty rectangle - QPixmap icnPix = mIconProducer.mIcon.pixmap(icnSize); + QPixmap icnPix = getIcon().pixmap(icnSize); const qreal pixelRatio = icnPix.devicePixelRatio(); const QSize icnPixSize((QSizeF(icnSize) * pixelRatio).toSize()); @@ -162,24 +173,10 @@ QIcon TrayIcon::emblemizedIcon() return QIcon(pix); } -void TrayIcon::updateTooltip() +void TrayIcon::onPauseTimeout() { - QString stateStr = mBattery->chargePercent() <= 0 && mBattery->chargeState() == Solid::Battery::NoCharge ? - tr("Empty") : BatteryHelper::stateToString(mBattery->chargeState()); - QString tooltip = stateStr % QString::fromLatin1(" (%1%)").arg(mBattery->chargePercent()); - switch (mBattery->chargeState()) - { - case Solid::Battery::Charging: - tooltip += QL1S(", ") % BatteryHelper::timeToFullString(mBattery->timeToFull()); - break; - case Solid::Battery::Discharging: - tooltip += QL1S(", ") % BatteryHelper::timeToEmptyString(mBattery->timeToEmpty()); - break; - default: - break; - } - - setToolTip(tooltip); + for (const auto &trayIcon : std::as_const(msInstances)) + trayIcon->setPause(TrayIcon::PAUSE::None); } void TrayIcon::onConfigureTriggered() @@ -189,8 +186,18 @@ void TrayIcon::onConfigureTriggered() void TrayIcon::onPauseTriggered(QAction *action) { - emit pauseChanged(!action->isChecked() ? PAUSE::None - : static_cast(action->data().toInt())); + const PAUSE duration = !action->isChecked() ? None : static_cast(action->data().toInt()); + if (duration == None) + { + onPauseTimeout(); + msPauseTimer->stop(); + } + else + { + for (const auto &trayIcon : std::as_const(msInstances)) + trayIcon->setPause(duration); + msPauseTimer->start(getPauseInterval(duration)); + } } void TrayIcon::onAboutTriggered() @@ -236,7 +243,7 @@ void TrayIcon::onActivated(QSystemTrayIcon::ActivationReason reason) void TrayIcon::setPause(PAUSE duration) { // add/remove the pause emblem and correct the checked action if needed - QAction *checked = mPauseActions->checkedAction(); + QAction *checked = msPauseActions->checkedAction(); if (duration == PAUSE::None) { PowerManagementSettings().setIdlenessWatcherPaused(false); @@ -252,7 +259,7 @@ void TrayIcon::setPause(PAUSE duration) iconChanged(); // adds the pause emblem if (checked == nullptr || checked->data().toInt() != duration) { - const auto actions = mPauseActions->actions(); + const auto actions = msPauseActions->actions(); for (const auto &a : actions) { if (a->data().toInt() == duration) diff --git a/src/trayicon.h b/src/trayicon.h index cb4dffc..8c5eaee 100644 --- a/src/trayicon.h +++ b/src/trayicon.h @@ -5,8 +5,10 @@ * https://lxqt.org * * Copyright: 2011 Razor team + * 2025~ LXQt team * Authors: * Christian Surlykke + * Palo Kisa * * This program or library is free software; you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public @@ -25,15 +27,11 @@ * * END_COMMON_COPYRIGHT_HEADER */ -#ifndef TRAYICON_H -#define TRAYICON_H +#pragma once #include #include -#include -#include "iconproducer.h" -#include "../config/powermanagementsettings.h" class TrayIcon : public QSystemTrayIcon { @@ -49,7 +47,7 @@ class TrayIcon : public QSystemTrayIcon Four = 8 }; - TrayIcon(Solid::Battery *battery, QObject *parent = nullptr); + TrayIcon(QObject *parent = nullptr); ~TrayIcon() override; static int getPauseInterval(PAUSE duration); @@ -62,23 +60,27 @@ class TrayIcon : public QSystemTrayIcon public slots: void iconChanged(); - void updateTooltip(); private slots: - void onConfigureTriggered(); - void onPauseTriggered(QAction *action); - void onAboutTriggered(); - void onDisableIconTriggered(); void onActivated(QSystemTrayIcon::ActivationReason reason); private: + static void onPauseTriggered(QAction *action); + static void onPauseTimeout(); + static void onConfigureTriggered(); + static void onAboutTriggered(); + static void onDisableIconTriggered(); + +private: + virtual const QIcon & getIcon() const; QIcon emblemizedIcon(); - Solid::Battery *mBattery; - IconProducer mIconProducer; - QMenu mContextMenu; - QActionGroup *mPauseActions; +private: + static QList msInstances; + static std::unique_ptr msContextMenu; + static std::unique_ptr msPauseTimer; + static std::unique_ptr msPauseActions; + + QIcon mBaseIcon; bool mHasPauseEmblem; }; - -#endif // TRAYICON_H diff --git a/src/trayiconbattery.cpp b/src/trayiconbattery.cpp new file mode 100644 index 0000000..46d5241 --- /dev/null +++ b/src/trayiconbattery.cpp @@ -0,0 +1,80 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2011 Razor team + * 2025~ LXQt team + * Authors: + * Christian Surlykke + * Palo Kisa + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + +#include +#include +#include + +#include "trayiconbattery.h" +#include "batteryhelper.h" + +TrayIconBattery::TrayIconBattery(Solid::Battery *battery, QObject *parent) + : TrayIcon(parent), + mBattery(battery), + mIconProducer(battery) +{ + connect(mBattery, &Solid::Battery::chargePercentChanged, this, &TrayIconBattery::updateTooltip); + connect(mBattery, &Solid::Battery::chargeStateChanged, this, &TrayIconBattery::updateTooltip); + connect(mBattery, &Solid::Battery::timeToEmptyChanged, this, &::TrayIconBattery::updateTooltip); + connect(mBattery, &Solid::Battery::timeToFullChanged, this, &::TrayIconBattery::updateTooltip); + updateTooltip(); + + connect(&mIconProducer, &IconProducer::iconChanged, this, &TrayIcon::iconChanged); + iconChanged(); + +} + +TrayIconBattery::~TrayIconBattery() +{ +} + +/*virtual*/ const QIcon & TrayIconBattery::getIcon() const +{ + return mIconProducer.mIcon; +} + +void TrayIconBattery::updateTooltip() +{ + QString stateStr = mBattery->chargePercent() <= 0 && mBattery->chargeState() == Solid::Battery::NoCharge ? + tr("Empty") : BatteryHelper::stateToString(mBattery->chargeState()); + QString tooltip = stateStr % QString::fromLatin1(" (%1%)").arg(mBattery->chargePercent()); + switch (mBattery->chargeState()) + { + case Solid::Battery::Charging: + tooltip += QL1S(", ") % BatteryHelper::timeToFullString(mBattery->timeToFull()); + break; + case Solid::Battery::Discharging: + tooltip += QL1S(", ") % BatteryHelper::timeToEmptyString(mBattery->timeToEmpty()); + break; + default: + break; + } + + setToolTip(tooltip); +} diff --git a/src/trayiconbattery.h b/src/trayiconbattery.h new file mode 100644 index 0000000..890a943 --- /dev/null +++ b/src/trayiconbattery.h @@ -0,0 +1,53 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2011 Razor team + * 2025~ LXQt team + * Authors: + * Christian Surlykke + * Palo Kisa + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + +#pragma once + +#include "trayicon.h" +#include + +#include "iconproducer.h" + +class TrayIconBattery : public TrayIcon +{ + Q_OBJECT + +public: + TrayIconBattery(Solid::Battery *battery, QObject *parent = nullptr); + ~TrayIconBattery() override; + +public slots: + void updateTooltip(); + +private: + virtual const QIcon & getIcon() const override; + + Solid::Battery *mBattery; + IconProducer mIconProducer; +};