diff --git a/src/rsitimer.cpp b/src/rsitimer.cpp index 43a4dc4..548bc4a 100644 --- a/src/rsitimer.cpp +++ b/src/rsitimer.cpp @@ -1,318 +1,325 @@ /* Copyright (C) 2005-2006,2008-2010 Tom Albers Copyright (C) 2005-2006 Bram Schoenmakers Copyright (C) 2010 Juan Luis Baptiste The parts for idle detection is based on kdepim's karm idletimedetector.cpp/.h This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "rsitimer.h" #include #include #include #include #include #include "rsiglobals.h" #include "rsistats.h" RSITimer::RSITimer( QObject *parent ) : QThread( parent ) , m_idleTimeInstance( new RSIIdleTimeImpl() ) , m_intervals( RSIGlobals::instance()->intervals() ) , m_state ( TimerState::Monitoring ) { updateConfig( true ); } RSITimer::RSITimer( std::unique_ptr &&_idleTime, const QVector _intervals, const bool _usePopup, const bool _useIdleTimers ) : QThread( nullptr ) , m_idleTimeInstance( std::move(_idleTime) ) , m_usePopup( _usePopup ) , m_useIdleTimers( _useIdleTimers ) , m_intervals( _intervals ) , m_state( TimerState::Monitoring ) { createTimers(); } void RSITimer::createTimers() { int bigThreshold = m_useIdleTimers ? m_intervals[BIG_BREAK_THRESHOLD] : INT_MAX; int tinyThreshold = m_useIdleTimers ? m_intervals[TINY_BREAK_THRESHOLD] : INT_MAX; m_bigBreakCounter = std::unique_ptr { new RSITimerCounter( m_intervals[BIG_BREAK_INTERVAL], m_intervals[BIG_BREAK_DURATION], bigThreshold ) }; m_tinyBreakCounter = std::unique_ptr { new RSITimerCounter( m_intervals[TINY_BREAK_INTERVAL], m_intervals[TINY_BREAK_DURATION], tinyThreshold ) }; } void RSITimer::run() { QTimer timer; connect( &timer, &QTimer::timeout, this, &RSITimer::timeout ); timer.setTimerType( Qt::TimerType::CoarseTimer ); timer.start( 1000 ); exec(); // start event loop to make timers work. } void RSITimer::hibernationDetector( const int totalIdle ) { // poor mans hibernation detector.... static QDateTime last = QDateTime::currentDateTime(); QDateTime current = QDateTime::currentDateTime(); if ( last.secsTo( current ) > 60 ) { qDebug() << "Not been checking idleTime for more than 60 seconds, " << "assuming the computer hibernated, resetting timers" << "Last: " << last << "Current: " << current << "Idle, s: " << totalIdle; resetAfterBreak(); } last = current; } int RSITimer::idleTime() { int totalIdle = m_idleTimeInstance->getIdleTime() / 1000; hibernationDetector( totalIdle ); // TODO Find a modern-desktop way to check if the screensaver is inhibited // and disable the timer because we assume you're doing for example a presentation and // don't want rsibreak to annoy you return totalIdle; } void RSITimer::doBreakNow( const int breakTime, const bool nextBreakIsBig ) { m_state = TimerState::Resting; m_pauseCounter = std::unique_ptr { new RSITimerCounter( breakTime, breakTime, INT_MAX ) }; m_popupCounter = nullptr; if ( nextBreakIsBig ) { emit startLongBreak(); } else { emit startShortBreak(); } emit updateWidget( breakTime ); emit breakNow(); } void RSITimer::resetAfterBreak() { m_state = TimerState::Monitoring; m_pauseCounter = nullptr; m_popupCounter = nullptr; + m_shortInputCounter = nullptr; defaultUpdateToolTip(); emit updateIdleAvg( 0.0 ); emit relax( -1, false ); emit minimize(); if ( m_bigBreakCounter->isReset() ) { emit endLongBreak(); } else { emit endShortBreak(); } } // -------------------------- SLOTS ------------------------// void RSITimer::slotStart() { m_state = TimerState::Monitoring; } void RSITimer::slotStop() { m_state = TimerState::Suspended; emit updateIdleAvg( 0.0 ); emit updateToolTip( 0, 0 ); } void RSITimer::slotSuspended( bool suspend ) { suspend ? slotStop() : slotStart(); } void RSITimer::slotLock() { resetAfterBreak(); } void RSITimer::skipBreak() { if ( m_bigBreakCounter->isReset() ) { RSIGlobals::instance()->stats()->increaseStat( BIG_BREAKS_SKIPPED ); emit bigBreakSkipped(); } else { RSIGlobals::instance()->stats()->increaseStat( TINY_BREAKS_SKIPPED ); emit tinyBreakSkipped(); } resetAfterBreak(); } void RSITimer::postponeBreak() { if ( m_bigBreakCounter->isReset() ) { m_bigBreakCounter->postpone( m_intervals[POSTPONE_BREAK_INTERVAL] ); RSIGlobals::instance()->stats()->increaseStat( BIG_BREAKS_POSTPONED ); } else { m_tinyBreakCounter->postpone( m_intervals[POSTPONE_BREAK_INTERVAL] ); RSIGlobals::instance()->stats()->increaseStat( TINY_BREAKS_POSTPONED ); } resetAfterBreak(); } void RSITimer::updateConfig( bool doRestart ) { KConfigGroup popupConfig = KSharedConfig::openConfig()->group( "Popup Settings" ); m_usePopup = popupConfig.readEntry( "UsePopup", true ); bool oldUseIdleTimers = m_useIdleTimers; KConfigGroup generalConfig = KSharedConfig::openConfig()->group( "General Settings" ); m_useIdleTimers = !( generalConfig.readEntry( "UseNoIdleTimer", false ) ); doRestart = doRestart || ( oldUseIdleTimers != m_useIdleTimers ); const QVector oldIntervals = m_intervals; m_intervals = RSIGlobals::instance()->intervals(); doRestart = doRestart || ( m_intervals != oldIntervals ); if ( doRestart ) { qDebug() << "Timeout parameters have changed, counters were reset."; createTimers(); } } // ----------------------------- EVENTS -----------------------// void RSITimer::timeout() { // Don't change the tray icon when suspended, or evaluate a possible break. if ( m_state == TimerState::Suspended ) { return; } const int idleSeconds = idleTime(); // idleSeconds == 0 means activity RSIGlobals::instance()->stats()->increaseStat( TOTAL_TIME ); RSIGlobals::instance()->stats()->setStat( CURRENT_IDLE_TIME, idleSeconds ); if ( idleSeconds == 0 ) { RSIGlobals::instance()->stats()->increaseStat( ACTIVITY ); } else { RSIGlobals::instance()->stats()->setStat( MAX_IDLENESS, idleSeconds, true ); } switch ( m_state ) { case TimerState::Monitoring: { // This is a weird thing to track as now when user was away, they will get back to zero counters, // not to an arbitrary time elapsed since last "idleness-skip-break". bool bigWasReset = m_bigBreakCounter->isReset(); bool tinyWasReset = m_tinyBreakCounter->isReset(); int breakTime = std::max( m_bigBreakCounter->tick( idleSeconds ), m_tinyBreakCounter->tick( idleSeconds ) ); if ( breakTime > 0 ) { suggestBreak( breakTime ); } else { // Not a time for break yet, but if one of the counters got reset, that means we were idle enough to skip. if ( !bigWasReset && m_bigBreakCounter->isReset() ) { RSIGlobals::instance()->stats()->increaseStat( BIG_BREAKS ); RSIGlobals::instance()->stats()->increaseStat( IDLENESS_CAUSED_SKIP_BIG ); } if ( !tinyWasReset && m_tinyBreakCounter->isReset() ) { RSIGlobals::instance()->stats()->increaseStat( TINY_BREAKS ); RSIGlobals::instance()->stats()->increaseStat( IDLENESS_CAUSED_SKIP_TINY ); } } const double value = 100.0 - ( ( m_tinyBreakCounter->counterLeft() / ( double ) m_intervals[TINY_BREAK_INTERVAL] ) * 100.0 ); emit updateIdleAvg( value ); break; } case TimerState::Suggesting: { // Using popupCounter to count down our patience here. int breakTime = m_popupCounter->tick( idleSeconds ); if ( breakTime > 0 ) { // User kept working throw the suggestion timeout. Well, their loss. emit relax( -1, false ); breakTime = m_pauseCounter->counterLeft(); doBreakNow( breakTime, false ); break; } - int inverseTick = ( idleSeconds == 0 ) ? 1 : 0; // inverting as we account idle seconds here. + bool isInputLong = (m_shortInputCounter->tick(idleSeconds) > 0); + int inverseTick = ( idleSeconds == 0 && isInputLong) ? 1 : 0; // inverting as we account idle seconds here. breakTime = m_pauseCounter->tick( inverseTick ); if ( breakTime > 0 ) { // User has waited out the pause, back to monitoring. resetAfterBreak(); break; } emit relax( m_pauseCounter->counterLeft(), false ); emit updateWidget( m_pauseCounter->counterLeft() ); break; } case TimerState::Resting: { - int inverseTick = ( idleSeconds == 0 ) ? 1 : 0; // inverting as we account idle seconds here. + bool isInputLong = (m_shortInputCounter->tick(idleSeconds) > 0); + int inverseTick = ( idleSeconds == 0 && isInputLong > 0 ) ? 1 : 0; // inverting as we account idle seconds here. int breakTime = m_pauseCounter->tick( inverseTick ); if ( breakTime > 0 ) { resetAfterBreak(); } else { emit updateWidget( m_pauseCounter->counterLeft() ); } break; } default: qDebug() << "Reached unexpected state"; } defaultUpdateToolTip(); } void RSITimer::suggestBreak( const int breakTime ) { if ( m_bigBreakCounter->isReset() ) { RSIGlobals::instance()->stats()->increaseStat( BIG_BREAKS ); RSIGlobals::instance()->stats()->setStat( LAST_BIG_BREAK, QVariant( QDateTime::currentDateTime() ) ); } else { RSIGlobals::instance()->stats()->increaseStat( TINY_BREAKS ); RSIGlobals::instance()->stats()->setStat( LAST_TINY_BREAK, QVariant( QDateTime::currentDateTime() ) ); } bool nextOneIsBig = m_bigBreakCounter->counterLeft() <= m_tinyBreakCounter->getDelayTicks(); if ( !m_usePopup ) { doBreakNow( breakTime, nextOneIsBig ); return; } m_state = TimerState::Suggesting; // When pause is longer than patience, we need to reset patience timer so that we don't flip to break now in // mid-pause. Patience / 2 is a good alternative to it by extending patience if user was idle long enough. m_popupCounter = std::unique_ptr { new RSITimerCounter( m_intervals[PATIENCE_INTERVAL], breakTime, m_intervals[PATIENCE_INTERVAL] / 2 ) }; // Threshold of one means the timer is reset on every non-zero tick. m_pauseCounter = std::unique_ptr { new RSITimerCounter( breakTime, breakTime, 1 ) }; + // For measuring input duration in order to limit influence of short inputs on resetting pause counter. + // Example of short input is: mouse sent input due to accidental touch or desk vibration. + m_shortInputCounter = std::unique_ptr { new RSITimerCounter( 2, 2, 1 ) }; + emit relax( breakTime, nextOneIsBig ); } void RSITimer::defaultUpdateToolTip() { emit updateToolTip( m_tinyBreakCounter->counterLeft(), m_bigBreakCounter->counterLeft() ); } diff --git a/src/rsitimer.h b/src/rsitimer.h index 5c83a39..ea102ce 100644 --- a/src/rsitimer.h +++ b/src/rsitimer.h @@ -1,212 +1,213 @@ /* Copyright (C) 2005-2006,2008-2010 Tom Albers Copyright (C) 2005-2006 Bram Schoenmakers Copyright (C) 2010 Juan Luis Baptiste This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef RSITimer_H #define RSITimer_H #include #include #include #include "rsitimercounter.h" #include "rsiidletime.h" /** * @class RSITimer * This class controls the timings and arranges the maximizing * and minimizing of the widget. * @author Tom Albers */ class RSITimer : public QThread { Q_OBJECT friend class RSITimerTest; public: /** * Constructor * @param parent Parent Widget * @param name Name */ explicit RSITimer( QObject *parent = 0 ); // Check whether the timer is suspended. bool isSuspended() const { return m_state == TimerState::Suspended; } int tinyLeft() const { return m_tinyBreakCounter->counterLeft(); }; int bigLeft() const { return m_bigBreakCounter->counterLeft(); }; public slots: /** Reads the configuration and restarts the timer with slotRestart. */ void updateConfig( bool doRestart = false ); /** Stops the timer activity. This does not imply resetting counters. */ void slotStop(); /** Called when the user suspends RSIBreak from the docker. @param suspend If true the timer will suspend, if false the timer will unsuspend. */ void slotSuspended( bool suspend ); /** Prepares the timer so that it can start/continue. */ void slotStart(); /** Called when user locks the screen for pause. Resets current timers if currently suggesting. */ void slotLock(); /** When the user presses the Skip button during a break, this function will be called. It will act like a break has just passed. */ void skipBreak(); /** When the user presses the postpone break button during a break, this function will be called. It will postpone the break for the configured amount of seconds. */ void postponeBreak(); /** Queries X how many seconds the user has been idle. A value of 0 means there was activity during the last second. @returns The amount of seconds of idling. */ int idleTime(); private slots: /** The pumping heart of the timer. This will evaluate user's activity and decide what to do (wait, popup a relax notification or a fullscreen break. */ virtual void timeout(); signals: /** Enforce a fullscreen big break. */ void breakNow(); /** Update counters in tooltip. @param tinyLeft If <=0 a tiny break is active, else it defines how much time is left until the next tiny break. @param bigLeft If <=0 a big break is active, else it defines how much time is left until the next big break. */ void updateToolTip( const int tinyLeft, const int bigLeft ); /** Update the time shown on the fullscreen widget. @param secondsLeft Shows the user how many seconds are remaining. */ void updateWidget( int secondsLeft ); /** Update the systray icon. @param v How much time has passed until a tiny break (relative) Varies from 0 to 100. */ void updateIdleAvg( double v ); /** A request to minimize the fullscreen widget, for example when the break is over. @param newImage Load a new image */ void minimize(); /** Pop up a relax notification to the user for @p sec seconds. @param sec The amount of seconds the user should relax to make the popup disappear. A value of -1 will hide the relax popup. @param nextBreakIsBig True if the break after the next break is a big break. We can warn the user in advance. */ void relax( int sec, bool nextBreakIsBig ); /** Indicates a tinyBreak is skipped because user was enough idle */ void tinyBreakSkipped(); /** Indicates a bigBreak is skipped because user was enough idle */ void bigBreakSkipped(); void startLongBreak(); void endLongBreak(); void startShortBreak(); void endShortBreak(); private: std::unique_ptr m_idleTimeInstance; bool m_usePopup; bool m_useIdleTimers; QVector m_intervals; enum class TimerState { Suspended = 0, // user has suspended either via dbus or tray. Monitoring, // normal cycle, waiting for break to trigger. Suggesting, // politely suggest to take a break with some patience. Resting // suggestion ignored, waiting out the break. } m_state; std::unique_ptr m_bigBreakCounter; std::unique_ptr m_tinyBreakCounter; std::unique_ptr m_pauseCounter; std::unique_ptr m_popupCounter; + std::unique_ptr m_shortInputCounter; void hibernationDetector( const int totalIdle ); void suggestBreak( const int time ); void defaultUpdateToolTip(); void createTimers(); // This function is called when a break has passed. void resetAfterBreak(); // Start this thread. void run() override; /** Some internal preparations for a fullscreen break window. @param breakTime The amount of seconds to break. @param nextBreakIsBig Whether the next break will be big. */ void doBreakNow( const int breakTime, const bool nextBreakIsBig ); // Constructor for tests. RSITimer( std::unique_ptr &&_idleTime, const QVector _intervals, const bool _usePopup, const bool _useIdleTimers ); }; #endif