File indexing completed on 2024-04-21 05:54:04

0001 /*
0002     SPDX-FileCopyrightText: 2005-2006, 2008-2010 Tom Albers <toma@kde.org>
0003     SPDX-FileCopyrightText: 2005-2006 Bram Schoenmakers <bramschoenmakers@kde.nl>
0004     SPDX-FileCopyrightText: 2010 Juan Luis Baptiste <juan.baptiste@gmail.com>
0005 
0006     The parts for idle detection is based on
0007     kdepim's karm idletimedetector.cpp/.h
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "rsitimer.h"
0013 
0014 #include <QDebug>
0015 #include <QTimer>
0016 
0017 #include <kconfig.h>
0018 #include <kconfiggroup.h>
0019 #include <ksharedconfig.h>
0020 #include <kwindowsystem.h>
0021 
0022 #include "rsiglobals.h"
0023 #include "rsistats.h"
0024 
0025 RSITimer::RSITimer(QObject *parent)
0026     : QObject(parent)
0027     , m_idleTimeInstance(new RSIIdleTimeImpl())
0028     , m_intervals(RSIGlobals::instance()->intervals())
0029     , m_state(TimerState::Monitoring)
0030 {
0031     updateConfig(true);
0032     run();
0033 }
0034 
0035 RSITimer::RSITimer(std::unique_ptr<RSIIdleTime> &&_idleTime, const QVector<int> _intervals, const bool _usePopup, const bool _useIdleTimers)
0036     : QObject(nullptr)
0037     , m_idleTimeInstance(std::move(_idleTime))
0038     , m_usePopup(_usePopup)
0039     , m_useIdleTimers(_useIdleTimers)
0040     , m_intervals(_intervals)
0041     , m_state(TimerState::Monitoring)
0042 {
0043     createTimers();
0044     run();
0045 }
0046 
0047 void RSITimer::createTimers()
0048 {
0049     int bigThreshold = m_useIdleTimers ? m_intervals[BIG_BREAK_THRESHOLD] : INT_MAX;
0050     int tinyThreshold = m_useIdleTimers ? m_intervals[TINY_BREAK_THRESHOLD] : INT_MAX;
0051 
0052     m_bigBreakCounter = std::unique_ptr<RSITimerCounter>{new RSITimerCounter(m_intervals[BIG_BREAK_INTERVAL], m_intervals[BIG_BREAK_DURATION], bigThreshold)};
0053     m_tinyBreakCounter = !m_intervals[TINY_BREAK_INTERVAL]
0054         ? nullptr
0055         : std::unique_ptr<RSITimerCounter>{new RSITimerCounter(m_intervals[TINY_BREAK_INTERVAL], m_intervals[TINY_BREAK_DURATION], tinyThreshold)};
0056 }
0057 
0058 void RSITimer::run()
0059 {
0060     auto timer = new QTimer(this);
0061     connect(timer, &QTimer::timeout, this, &RSITimer::timeout);
0062     timer->setTimerType(Qt::TimerType::CoarseTimer);
0063     timer->start(1000);
0064 }
0065 
0066 bool RSITimer::suppressionDetector()
0067 {
0068     for (WId win : KWindowSystem::windows()) {
0069         KWindowInfo info(win, NET::WMDesktop | NET::WMState | NET::XAWMState);
0070         if ((info.state() & NET::FullScreen) && !info.isMinimized() && info.isOnCurrentDesktop()) {
0071             return true;
0072         }
0073     }
0074     return false;
0075 }
0076 
0077 void RSITimer::hibernationDetector(const int totalIdle)
0078 {
0079     // poor mans hibernation detector....
0080     static QDateTime last = QDateTime::currentDateTime();
0081     QDateTime current = QDateTime::currentDateTime();
0082     if (last.secsTo(current) > 60) {
0083         qDebug() << "Not been checking idleTime for more than 60 seconds, "
0084                  << "assuming the computer hibernated, resetting timers"
0085                  << "Last: " << last << "Current: " << current << "Idle, s: " << totalIdle;
0086         m_bigBreakCounter->reset();
0087         if (m_tinyBreakCounter) {
0088             m_tinyBreakCounter->reset();
0089         }
0090         resetAfterBreak();
0091     }
0092     last = current;
0093 }
0094 
0095 int RSITimer::idleTime()
0096 {
0097     int totalIdle = m_idleTimeInstance->getIdleTime() / 1000;
0098     hibernationDetector(totalIdle);
0099     return totalIdle;
0100 }
0101 
0102 void RSITimer::doBreakNow(const int breakTime, const bool nextBreakIsBig)
0103 {
0104     m_state = TimerState::Resting;
0105     m_pauseCounter = std::unique_ptr<RSITimerCounter>{new RSITimerCounter(breakTime, breakTime, INT_MAX)};
0106     m_popupCounter = nullptr;
0107     m_shortInputCounter = std::unique_ptr<RSITimerCounter>{new RSITimerCounter(m_intervals[SHORT_INPUT_INTERVAL], 1, 1)};
0108     if (nextBreakIsBig) {
0109         emit startLongBreak();
0110     } else {
0111         emit startShortBreak();
0112     }
0113     emit updateWidget(breakTime);
0114     emit breakNow();
0115 }
0116 
0117 void RSITimer::resetAfterBreak()
0118 {
0119     m_state = TimerState::Monitoring;
0120     m_pauseCounter = nullptr;
0121     m_popupCounter = nullptr;
0122     m_shortInputCounter = nullptr;
0123     defaultUpdateToolTip();
0124     emit updateIdleAvg(0.0);
0125     emit relax(-1, false);
0126     emit minimize();
0127     if (m_bigBreakCounter->isReset()) {
0128         emit endLongBreak();
0129     } else {
0130         emit endShortBreak();
0131     }
0132 }
0133 
0134 // -------------------------- SLOTS ------------------------//
0135 
0136 void RSITimer::slotStart()
0137 {
0138     m_state = TimerState::Monitoring;
0139 }
0140 
0141 void RSITimer::slotStop()
0142 {
0143     m_state = TimerState::Suspended;
0144     emit updateIdleAvg(0.0);
0145     emit updateToolTip(0, 0);
0146 }
0147 
0148 void RSITimer::slotSuspended(bool suspend)
0149 {
0150     suspend ? slotStop() : slotStart();
0151 }
0152 
0153 void RSITimer::slotLock()
0154 {
0155     resetAfterBreak();
0156 }
0157 
0158 void RSITimer::skipBreak()
0159 {
0160     if (m_bigBreakCounter->isReset()) {
0161         RSIGlobals::instance()->stats()->increaseStat(BIG_BREAKS_SKIPPED);
0162         emit bigBreakSkipped();
0163     } else {
0164         RSIGlobals::instance()->stats()->increaseStat(TINY_BREAKS_SKIPPED);
0165         emit tinyBreakSkipped();
0166     }
0167     resetAfterBreak();
0168 }
0169 
0170 void RSITimer::postponeBreak()
0171 {
0172     if (m_bigBreakCounter->isReset()) {
0173         m_bigBreakCounter->postpone(m_intervals[POSTPONE_BREAK_INTERVAL]);
0174         RSIGlobals::instance()->stats()->increaseStat(BIG_BREAKS_POSTPONED);
0175     } else {
0176         m_tinyBreakCounter->postpone(m_intervals[POSTPONE_BREAK_INTERVAL]);
0177         RSIGlobals::instance()->stats()->increaseStat(TINY_BREAKS_POSTPONED);
0178     }
0179     resetAfterBreak();
0180 }
0181 
0182 void RSITimer::updateConfig(bool doRestart)
0183 {
0184     KConfigGroup popupConfig = KSharedConfig::openConfig()->group("Popup Settings");
0185     m_usePopup = popupConfig.readEntry("UsePopup", true);
0186 
0187     bool oldUseIdleTimers = m_useIdleTimers;
0188     KConfigGroup generalConfig = KSharedConfig::openConfig()->group("General Settings");
0189     m_suppressable = generalConfig.readEntry("SuppressIfPresenting", true);
0190     m_useIdleTimers = !(generalConfig.readEntry("UseNoIdleTimer", false));
0191     doRestart = doRestart || (oldUseIdleTimers != m_useIdleTimers);
0192 
0193     const QVector<int> oldIntervals = m_intervals;
0194     m_intervals = RSIGlobals::instance()->intervals();
0195     doRestart = doRestart || (m_intervals != oldIntervals);
0196 
0197     if (doRestart) {
0198         qDebug() << "Timeout parameters have changed, counters were reset.";
0199         createTimers();
0200     }
0201 }
0202 
0203 // ----------------------------- EVENTS -----------------------//
0204 
0205 void RSITimer::timeout()
0206 {
0207     // Don't change the tray icon when suspended, or evaluate a possible break.
0208     if (m_state == TimerState::Suspended) {
0209         return;
0210     }
0211 
0212     const int idleSeconds = idleTime(); // idleSeconds == 0 means activity
0213 
0214     RSIGlobals::instance()->stats()->increaseStat(TOTAL_TIME);
0215     RSIGlobals::instance()->stats()->setStat(CURRENT_IDLE_TIME, idleSeconds);
0216     if (idleSeconds == 0) {
0217         RSIGlobals::instance()->stats()->increaseStat(ACTIVITY);
0218     } else {
0219         RSIGlobals::instance()->stats()->setStat(MAX_IDLENESS, idleSeconds, true);
0220     }
0221 
0222     switch (m_state) {
0223     case TimerState::Monitoring: {
0224         // This is a weird thing to track as now when user was away, they will get back to zero counters,
0225         // not to an arbitrary time elapsed since last "idleness-skip-break".
0226         bool bigWasReset = m_bigBreakCounter->isReset();
0227         bool tinyWasReset = !m_tinyBreakCounter || m_tinyBreakCounter->isReset();
0228 
0229         int breakTime = m_bigBreakCounter->tick(idleSeconds);
0230         if (m_tinyBreakCounter) {
0231             breakTime = std::max(breakTime, m_tinyBreakCounter->tick(idleSeconds));
0232         }
0233         if (breakTime > 0) {
0234             suggestBreak(breakTime);
0235         } else {
0236             // Not a time for break yet, but if one of the counters got reset, that means we were idle enough to skip.
0237             if (!bigWasReset && m_bigBreakCounter->isReset()) {
0238                 RSIGlobals::instance()->stats()->increaseStat(BIG_BREAKS);
0239                 RSIGlobals::instance()->stats()->increaseStat(IDLENESS_CAUSED_SKIP_BIG);
0240             }
0241             if (!tinyWasReset && m_tinyBreakCounter && m_tinyBreakCounter->isReset()) {
0242                 RSIGlobals::instance()->stats()->increaseStat(TINY_BREAKS);
0243                 RSIGlobals::instance()->stats()->increaseStat(IDLENESS_CAUSED_SKIP_TINY);
0244             }
0245         }
0246         const double rawvalue = m_tinyBreakCounter ? m_tinyBreakCounter->counterLeft() / (double)m_intervals[TINY_BREAK_INTERVAL]
0247                                                    : m_bigBreakCounter->counterLeft() / (double)m_intervals[BIG_BREAK_INTERVAL];
0248         const double value = 100.0 - (rawvalue * 100.0);
0249         emit updateIdleAvg(value);
0250         break;
0251     }
0252     case TimerState::Suggesting: {
0253         // Using popupCounter to count down our patience here.
0254         int breakTime = m_popupCounter->tick(idleSeconds);
0255         if (breakTime > 0) {
0256             // User kept working throw the suggestion timeout. Well, their loss.
0257             emit relax(-1, false);
0258             breakTime = m_pauseCounter->counterLeft();
0259             doBreakNow(breakTime, false);
0260             break;
0261         }
0262 
0263         bool isInputLong = (m_shortInputCounter->tick(idleSeconds) > 0);
0264         int inverseTick = (idleSeconds == 0 && isInputLong) ? 1 : 0; // inverting as we account idle seconds here.
0265         breakTime = m_pauseCounter->tick(inverseTick);
0266         if (breakTime > 0) {
0267             // User has waited out the pause, back to monitoring.
0268             resetAfterBreak();
0269             break;
0270         }
0271         emit relax(m_pauseCounter->counterLeft(), false);
0272         emit updateWidget(m_pauseCounter->counterLeft());
0273         break;
0274     }
0275     case TimerState::Resting: {
0276         bool isInputLong = (m_shortInputCounter->tick(idleSeconds) > 0);
0277         int inverseTick = (idleSeconds == 0 && isInputLong) ? 1 : 0; // inverting as we account idle seconds here.
0278         int breakTime = m_pauseCounter->tick(inverseTick);
0279         if (breakTime > 0) {
0280             resetAfterBreak();
0281         } else {
0282             emit updateWidget(m_pauseCounter->counterLeft());
0283         }
0284         break;
0285     }
0286     default:
0287         qDebug() << "Reached unexpected state";
0288     }
0289     defaultUpdateToolTip();
0290 }
0291 
0292 void RSITimer::suggestBreak(const int breakTime)
0293 {
0294     if (m_suppressable && suppressionDetector()) {
0295         return;
0296     }
0297 
0298     if (m_bigBreakCounter->isReset()) {
0299         RSIGlobals::instance()->stats()->increaseStat(BIG_BREAKS);
0300         RSIGlobals::instance()->stats()->setStat(LAST_BIG_BREAK, QVariant(QDateTime::currentDateTime()));
0301     } else {
0302         RSIGlobals::instance()->stats()->increaseStat(TINY_BREAKS);
0303         RSIGlobals::instance()->stats()->setStat(LAST_TINY_BREAK, QVariant(QDateTime::currentDateTime()));
0304     }
0305 
0306     bool nextOneIsBig = !m_tinyBreakCounter || m_bigBreakCounter->counterLeft() <= m_tinyBreakCounter->getDelayTicks();
0307     if (!m_usePopup) {
0308         doBreakNow(breakTime, nextOneIsBig);
0309         return;
0310     }
0311 
0312     m_state = TimerState::Suggesting;
0313 
0314     // When pause is longer than patience, we need to reset patience timer so that we don't flip to break now in
0315     // mid-pause. Patience / 2 is a good alternative to it by extending patience if user was idle long enough.
0316     m_popupCounter = std::unique_ptr<RSITimerCounter>{new RSITimerCounter(m_intervals[PATIENCE_INTERVAL], breakTime, m_intervals[PATIENCE_INTERVAL] / 2)};
0317     // Threshold of one means the timer is reset on every non-zero tick.
0318     m_pauseCounter = std::unique_ptr<RSITimerCounter>{new RSITimerCounter(breakTime, breakTime, 1)};
0319 
0320     // For measuring input duration in order to limit influence of short inputs on resetting pause counter.
0321     // Example of short input is: mouse sent input due to accidental touch or desk vibration.
0322     m_shortInputCounter = std::unique_ptr<RSITimerCounter>{new RSITimerCounter(m_intervals[SHORT_INPUT_INTERVAL], 1, 1)};
0323 
0324     emit relax(breakTime, nextOneIsBig);
0325 }
0326 
0327 void RSITimer::defaultUpdateToolTip()
0328 {
0329     emit updateToolTip(m_tinyBreakCounter ? m_tinyBreakCounter->counterLeft() : 0, m_bigBreakCounter->counterLeft());
0330 }