diff --git a/app/config.h b/app/config.h index 2f0451d..c8aa070 100644 --- a/app/config.h +++ b/app/config.h @@ -4,7 +4,7 @@ // App version #define QBREAK_VERSION_MAJOR 0 #define QBREAK_VERSION_MINOR 1 -#define QBREAK_VERSION_SUFFIX 0 +#define QBREAK_VERSION_SUFFIX 1 // How often UI is updated - interval in seconds #define INTERVAL_UPDATE_UI (60) diff --git a/app/idle_tracking.cpp b/app/idle_tracking.cpp new file mode 100644 index 0000000..73b8373 --- /dev/null +++ b/app/idle_tracking.cpp @@ -0,0 +1,37 @@ +#include "idle_tracking.h" +#include "settings.h" +#if defined(TARGET_LINUX) + +// Thanks to https://stackoverflow.com/questions/222606/detecting-keyboard-mouse-activity-in-linux + +#include +#include +#include +#include +#include +#include + + +int get_idle_time() +{ + time_t idle_time; + static XScreenSaverInfo *mit_info; + Display *display; + int screen; + + mit_info = XScreenSaverAllocInfo(); + if ((display = XOpenDisplay(NULL)) == NULL) { + return -1; + } + + screen = DefaultScreen(display); + XScreenSaverQueryInfo(display, RootWindow(display, screen), mit_info); + idle_time = (mit_info->idle) / 1000 / 60; + XFree(mit_info); + XCloseDisplay(display); + + return idle_time; +} + + +#endif diff --git a/app/idle_tracking.h b/app/idle_tracking.h new file mode 100644 index 0000000..fa6dcfc --- /dev/null +++ b/app/idle_tracking.h @@ -0,0 +1,7 @@ +#ifndef __IDLE_TRACKING_H +#define __IDLE_TRACKING_H + +// Returns the idle time in minutes +extern int get_idle_time(); + +#endif diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index 64da77d..5a4fafb 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -6,6 +6,7 @@ #include "aboutdlg.h" #include "config.h" #include "audio_support.h" +#include "idle_tracking.h" #include #include @@ -78,18 +79,19 @@ void MainWindow::init() // No postpone attempts yet mPostponeCount = 0; - // Timer + // Timer to start break mTimer = new QTimer(this); mTimer->setTimerType(Qt::TimerType::CoarseTimer); mTimer->setSingleShot(true); connect(mTimer, SIGNAL(timeout()), this, SLOT(onLongBreakStart())); + // Timer to run notification about upcoming break mNotifyTimer = new QTimer(this); mNotifyTimer->setTimerType(Qt::TimerType::CoarseTimer); mNotifyTimer->setSingleShot(true); connect(mNotifyTimer, SIGNAL(timeout()), this, SLOT(onLongBreakNotify())); - + // Just update UI once per minute mUpdateUITimer = new QTimer(this); mUpdateUITimer->setTimerType(Qt::TimerType::CoarseTimer); mUpdateUITimer->setSingleShot(false); @@ -97,6 +99,7 @@ void MainWindow::init() connect(mUpdateUITimer, SIGNAL(timeout()), this, SLOT(onUpdateUI())); mUpdateUITimer->start(); + // Timer to draw progress bar during the break mProgressTimer = new QTimer(this); mProgressTimer->setInterval(std::chrono::milliseconds(INTERVAL_UPDATE_PROGRESS)); mProgressTimer->setSingleShot(false); @@ -269,6 +272,25 @@ void MainWindow::onUpdateUI() } ui->mSkipButton->setVisible(mPostponeCount > 0); + + if (mAppConfig.idle_timeout != 0) + { + int idle_minutes = get_idle_time(); + if (idle_minutes >= mAppConfig.idle_timeout) + { + // Idle mode is active. Increase the timer interval + mIdleStart = std::chrono::steady_clock::now() - std::chrono::minutes(idle_minutes); + + int remaining_minutes = mTimer->remainingTime() / 1000 / 60; + + // Change the time + mTimer->stop(); + mTimer->start(std::chrono::minutes(remaining_minutes + idle_minutes)); + + qDebug() << "Idle detected for " << idle_minutes << " minutes. " + << "Remaining " << mTimer->remainingTime() / 1000 / 60 << " until the next break."; + } + } } void MainWindow::onLongBreakNotify() diff --git a/app/mainwindow.h b/app/mainwindow.h index 7240d2a..23c6d59 100644 --- a/app/mainwindow.h +++ b/app/mainwindow.h @@ -39,6 +39,7 @@ private: std::chrono::steady_clock::time_point mBreakStartTime; app_settings::config mAppConfig; int mPostponeCount; + std::chrono::steady_clock::time_point mIdleStart; void init(); void loadConfig(); diff --git a/app/qbreak.pro b/app/qbreak.pro index 8ae7d30..c6ace5e 100644 --- a/app/qbreak.pro +++ b/app/qbreak.pro @@ -16,7 +16,8 @@ SOURCES += \ settings.cpp \ settingsdialog.cpp \ runguard.cpp \ - audio_support.cpp + audio_support.cpp \ + idle_tracking.cpp HEADERS += \ @@ -27,7 +28,8 @@ HEADERS += \ settings.h \ settingsdialog.h \ runguard.h \ - audio_support.h + audio_support.h \ + idle_tracking.h FORMS += \ @@ -45,3 +47,7 @@ unix:!macx: DEFINES += TARGET_LINUX qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target + +unix:LIBS += -L/usr/X11R6/lib/ -lX11 -lXext -lXss + + diff --git a/app/settings.cpp b/app/settings.cpp index 8c68fcb..e183c85 100644 --- a/app/settings.cpp +++ b/app/settings.cpp @@ -13,6 +13,7 @@ const QString Key_PreferredMonitor = "Preferred_Monitor"; const QString Key_Audio_Name = "Audio_Name"; const QString Key_Audio_Path = "Audio_Path"; const QString Key_Script = "Script"; +const QString Key_Idle_Timeout = "Idle_Timeout"; void app_settings::save(const config &cfg) { @@ -28,6 +29,7 @@ void app_settings::save(const config &cfg) s.setValue(Key_Audio_Name, cfg.play_audio.name); s.setValue(Key_Audio_Path, cfg.play_audio.path); s.setValue(Key_Script, cfg.script_on_break_finish); + s.setValue(Key_Idle_Timeout, cfg.idle_timeout); } app_settings::config app_settings::load() @@ -45,6 +47,7 @@ app_settings::config app_settings::load() r.play_audio.name = s.value(Key_Audio_Name, Audio_Empty).toString(); r.play_audio.path = s.value(Key_Audio_Path, QString()).toString(); r.script_on_break_finish = s.value(Key_Script, QString()).toString(); + r.idle_timeout = s.value(Key_Idle_Timeout, Default_Idle_Timeout).toInt(); return r; } diff --git a/app/settings.h b/app/settings.h index 96e28c9..01eaeee 100644 --- a/app/settings.h +++ b/app/settings.h @@ -19,6 +19,9 @@ const bool Default_Verbose = false; // Default autostart const bool Default_Autostart = true; +// Default idle timeout +const int Default_Idle_Timeout = 0; + const QString Default_Monitor = ""; const QString Primary_Monitor = "[Primary]"; @@ -67,6 +70,9 @@ public: // This value can be path to audio file or empty or [embedded] string selected_audio play_audio; QString script_on_break_finish; + + // Zero means "idle is not tracked" + int idle_timeout = Default_Idle_Timeout; }; static void save(const config& cfg); diff --git a/app/settingsdialog.cpp b/app/settingsdialog.cpp index b33a1ae..ed458d8 100644 --- a/app/settingsdialog.cpp +++ b/app/settingsdialog.cpp @@ -69,6 +69,9 @@ void SettingsDialog::init() ui->mScriptEdit->setText(c.script_on_break_finish); connect(ui->mAudioComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onAudioIndexChanged(int))); + // Idle timeout + ui->mIdleTimeoutSpinbox->setValue(c.idle_timeout); + mSkipAudioChangeEvent = false; } @@ -87,6 +90,7 @@ void SettingsDialog::accept() if (c.play_audio.name == Audio_Custom) c.play_audio.path = mCustomAudioPath; + c.idle_timeout = ui->mIdleTimeoutSpinbox->value(); app_settings::save(c); emit accepted(); diff --git a/app/settingsdialog.ui b/app/settingsdialog.ui index 9e08ebd..d5045ff 100644 --- a/app/settingsdialog.ui +++ b/app/settingsdialog.ui @@ -138,6 +138,20 @@ + + + + Idle timeout in minutes (zero means no idle tracking) + + + + + + + + + +