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 }