- idle tracking - initial implementation

This commit is contained in:
Dmytro Bogovych 2022-05-12 19:48:18 +03:00
parent 18ac04ec18
commit d2d899280d
10 changed files with 105 additions and 5 deletions

View File

@ -4,7 +4,7 @@
// App version // App version
#define QBREAK_VERSION_MAJOR 0 #define QBREAK_VERSION_MAJOR 0
#define QBREAK_VERSION_MINOR 1 #define QBREAK_VERSION_MINOR 1
#define QBREAK_VERSION_SUFFIX 0 #define QBREAK_VERSION_SUFFIX 1
// How often UI is updated - interval in seconds // How often UI is updated - interval in seconds
#define INTERVAL_UPDATE_UI (60) #define INTERVAL_UPDATE_UI (60)

37
app/idle_tracking.cpp Normal file
View File

@ -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 <time.h>
#include <stdio.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/scrnsaver.h>
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

7
app/idle_tracking.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef __IDLE_TRACKING_H
#define __IDLE_TRACKING_H
// Returns the idle time in minutes
extern int get_idle_time();
#endif

View File

@ -6,6 +6,7 @@
#include "aboutdlg.h" #include "aboutdlg.h"
#include "config.h" #include "config.h"
#include "audio_support.h" #include "audio_support.h"
#include "idle_tracking.h"
#include <QMenu> #include <QMenu>
#include <QAction> #include <QAction>
@ -78,18 +79,19 @@ void MainWindow::init()
// No postpone attempts yet // No postpone attempts yet
mPostponeCount = 0; mPostponeCount = 0;
// Timer // Timer to start break
mTimer = new QTimer(this); mTimer = new QTimer(this);
mTimer->setTimerType(Qt::TimerType::CoarseTimer); mTimer->setTimerType(Qt::TimerType::CoarseTimer);
mTimer->setSingleShot(true); mTimer->setSingleShot(true);
connect(mTimer, SIGNAL(timeout()), this, SLOT(onLongBreakStart())); connect(mTimer, SIGNAL(timeout()), this, SLOT(onLongBreakStart()));
// Timer to run notification about upcoming break
mNotifyTimer = new QTimer(this); mNotifyTimer = new QTimer(this);
mNotifyTimer->setTimerType(Qt::TimerType::CoarseTimer); mNotifyTimer->setTimerType(Qt::TimerType::CoarseTimer);
mNotifyTimer->setSingleShot(true); mNotifyTimer->setSingleShot(true);
connect(mNotifyTimer, SIGNAL(timeout()), this, SLOT(onLongBreakNotify())); connect(mNotifyTimer, SIGNAL(timeout()), this, SLOT(onLongBreakNotify()));
// Just update UI once per minute
mUpdateUITimer = new QTimer(this); mUpdateUITimer = new QTimer(this);
mUpdateUITimer->setTimerType(Qt::TimerType::CoarseTimer); mUpdateUITimer->setTimerType(Qt::TimerType::CoarseTimer);
mUpdateUITimer->setSingleShot(false); mUpdateUITimer->setSingleShot(false);
@ -97,6 +99,7 @@ void MainWindow::init()
connect(mUpdateUITimer, SIGNAL(timeout()), this, SLOT(onUpdateUI())); connect(mUpdateUITimer, SIGNAL(timeout()), this, SLOT(onUpdateUI()));
mUpdateUITimer->start(); mUpdateUITimer->start();
// Timer to draw progress bar during the break
mProgressTimer = new QTimer(this); mProgressTimer = new QTimer(this);
mProgressTimer->setInterval(std::chrono::milliseconds(INTERVAL_UPDATE_PROGRESS)); mProgressTimer->setInterval(std::chrono::milliseconds(INTERVAL_UPDATE_PROGRESS));
mProgressTimer->setSingleShot(false); mProgressTimer->setSingleShot(false);
@ -269,6 +272,25 @@ void MainWindow::onUpdateUI()
} }
ui->mSkipButton->setVisible(mPostponeCount > 0); 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() void MainWindow::onLongBreakNotify()

View File

@ -39,6 +39,7 @@ private:
std::chrono::steady_clock::time_point mBreakStartTime; std::chrono::steady_clock::time_point mBreakStartTime;
app_settings::config mAppConfig; app_settings::config mAppConfig;
int mPostponeCount; int mPostponeCount;
std::chrono::steady_clock::time_point mIdleStart;
void init(); void init();
void loadConfig(); void loadConfig();

View File

@ -16,7 +16,8 @@ SOURCES += \
settings.cpp \ settings.cpp \
settingsdialog.cpp \ settingsdialog.cpp \
runguard.cpp \ runguard.cpp \
audio_support.cpp audio_support.cpp \
idle_tracking.cpp
HEADERS += \ HEADERS += \
@ -27,7 +28,8 @@ HEADERS += \
settings.h \ settings.h \
settingsdialog.h \ settingsdialog.h \
runguard.h \ runguard.h \
audio_support.h audio_support.h \
idle_tracking.h
FORMS += \ FORMS += \
@ -45,3 +47,7 @@ unix:!macx: DEFINES += TARGET_LINUX
qnx: target.path = /tmp/$${TARGET}/bin qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target !isEmpty(target.path): INSTALLS += target
unix:LIBS += -L/usr/X11R6/lib/ -lX11 -lXext -lXss

View File

@ -13,6 +13,7 @@ const QString Key_PreferredMonitor = "Preferred_Monitor";
const QString Key_Audio_Name = "Audio_Name"; const QString Key_Audio_Name = "Audio_Name";
const QString Key_Audio_Path = "Audio_Path"; const QString Key_Audio_Path = "Audio_Path";
const QString Key_Script = "Script"; const QString Key_Script = "Script";
const QString Key_Idle_Timeout = "Idle_Timeout";
void app_settings::save(const config &cfg) 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_Name, cfg.play_audio.name);
s.setValue(Key_Audio_Path, cfg.play_audio.path); s.setValue(Key_Audio_Path, cfg.play_audio.path);
s.setValue(Key_Script, cfg.script_on_break_finish); s.setValue(Key_Script, cfg.script_on_break_finish);
s.setValue(Key_Idle_Timeout, cfg.idle_timeout);
} }
app_settings::config app_settings::load() 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.name = s.value(Key_Audio_Name, Audio_Empty).toString();
r.play_audio.path = s.value(Key_Audio_Path, QString()).toString(); r.play_audio.path = s.value(Key_Audio_Path, QString()).toString();
r.script_on_break_finish = s.value(Key_Script, 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; return r;
} }

View File

@ -19,6 +19,9 @@ const bool Default_Verbose = false;
// Default autostart // Default autostart
const bool Default_Autostart = true; const bool Default_Autostart = true;
// Default idle timeout
const int Default_Idle_Timeout = 0;
const QString Default_Monitor = ""; const QString Default_Monitor = "";
const QString Primary_Monitor = "[Primary]"; const QString Primary_Monitor = "[Primary]";
@ -67,6 +70,9 @@ public:
// This value can be path to audio file or empty or [embedded] string // This value can be path to audio file or empty or [embedded] string
selected_audio play_audio; selected_audio play_audio;
QString script_on_break_finish; QString script_on_break_finish;
// Zero means "idle is not tracked"
int idle_timeout = Default_Idle_Timeout;
}; };
static void save(const config& cfg); static void save(const config& cfg);

View File

@ -69,6 +69,9 @@ void SettingsDialog::init()
ui->mScriptEdit->setText(c.script_on_break_finish); ui->mScriptEdit->setText(c.script_on_break_finish);
connect(ui->mAudioComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onAudioIndexChanged(int))); connect(ui->mAudioComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onAudioIndexChanged(int)));
// Idle timeout
ui->mIdleTimeoutSpinbox->setValue(c.idle_timeout);
mSkipAudioChangeEvent = false; mSkipAudioChangeEvent = false;
} }
@ -87,6 +90,7 @@ void SettingsDialog::accept()
if (c.play_audio.name == Audio_Custom) if (c.play_audio.name == Audio_Custom)
c.play_audio.path = mCustomAudioPath; c.play_audio.path = mCustomAudioPath;
c.idle_timeout = ui->mIdleTimeoutSpinbox->value();
app_settings::save(c); app_settings::save(c);
emit accepted(); emit accepted();

View File

@ -138,6 +138,20 @@
<item row="9" column="1"> <item row="9" column="1">
<widget class="QLineEdit" name="mScriptEdit"/> <widget class="QLineEdit" name="mScriptEdit"/>
</item> </item>
<item row="10" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Idle timeout in minutes (zero means no idle tracking)</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QSpinBox" name="mIdleTimeoutSpinbox">
<property name="suffix">
<string/>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>