Sunday, 23 January 2011

Detecting Application Idle in Qt 4

The Patient Management System we are writing required a timeout for logins after a certain period had elapsed. So we needed to detect application idle. A couple of examples exist on the the web, but seemed incomplete and not thread safe. I also wanted a singleton class model so that I could have notifications of idle in any of my QObjects. So here is my implementation. Comments and improvements will be appreciated.


Header File:


#ifndef IDLETIMER_H
#define IDLETIMER_H

#include <qobject>
#include <qmutex>
#include <qevent>
#include <qtimer>

class IdleTimer : public QObject
{
Q_OBJECT
public:
// Singleton class stuff
static IdleTimer* instance(QObject *parent=0, int seconds=0) {
static QMutex mutex;
if (!m_Instance) {
mutex.lock();
m_Instance = new IdleTimer(parent, seconds);
mutex.unlock();
}
return m_Instance;
}

static void drop() {
qDebug("IdleTimer dropped ...");
static QMutex mutex;
mutex.lock();

m_timer->stop();
if(m_timer) delete m_timer;

if (m_Instance)
delete m_Instance;
m_Instance = 0;
mutex.unlock();
}

void start(int seconds = 0);
void stop();

private:
explicit IdleTimer(QObject *parent, int seconds);
explicit IdleTimer() {}
~IdleTimer() {}

IdleTimer(const IdleTimer &); // hide copy constructor
IdleTimer& operator=(const IdleTimer &); // hide assign op

static IdleTimer *m_Instance;
static QTimer *m_timer;

static QMutex m_timeoutMutex;
static int m_timeout;

signals:
void idle();

private slots:
void idleTimeout();

protected:
bool eventFilter(QObject *obj, QEvent *ev);
};

#endif // IDLETIMER_H


And the implementation


/*!
* Small class for detecting idle in the applcation
*/

#include "IdleTimer.h"

// Static initialisers
IdleTimer *IdleTimer::m_Instance = 0;
QTimer *IdleTimer::m_timer = new QTimer();
int IdleTimer::m_timeout = 0;
QMutex IdleTimer::m_timeoutMutex;

/*!
* Class constructor
*/
IdleTimer::IdleTimer(QObject *parent, int seconds) :
QObject(parent)
{
m_timeoutMutex.lock();

if (seconds)
m_timeout = seconds;

Q_ASSERT(parent);
Q_ASSERT_X(m_timeout, "IdleTimer Constructor", "The timeout must be specified in the first call to instance.");

parent->installEventFilter(this);
m_timer->singleShot(m_timeout*1000, this, SLOT(idleTimeout()));

m_timeoutMutex.unlock();
}

/*!
* Either reset the timeout to a different value to restart the timer
*/
void IdleTimer::start(int seconds/*=0*/) {
m_timeoutMutex.lock();

if(seconds)
m_timeout = seconds;

Q_ASSERT_X(m_timeout, "IdleTimer reset", "A timeout must be specified either in this call or in the first call to instance.");

m_timer->start(m_timeout*1000);

m_timeoutMutex.unlock();
}

void IdleTimer::stop() {
m_timeoutMutex.lock();
m_timer->stop();
m_timeoutMutex.unlock();
}

/*!
* The vent filter
*/
bool IdleTimer::eventFilter(QObject *obj, QEvent *ev)
{
if(ev->type() == QEvent::KeyPress ||
ev->type() == QEvent::MouseMove)
// now reset your timer, for example
IdleTimer::m_timer->start();

// Must return to allow further processing
return QObject::eventFilter(obj, ev);
}

/*slot*/
void IdleTimer::idleTimeout() {
qDebug("Application has been idle, emitting idle signal ...");
emit idle();
}

4 comments:

drpclind said...

Looks very useful, and I would like to adopt this class for use in my software library. I will give you full credit, of course.

The source code listing for the header file contains four #include statements that appear to be incomplete. Is this a glitch or an omission?

Neville Dastur said...

Sorry, classic HTML problem with less than / great than sign. Corrected now ...

AttitudeMonger said...

This doesn't work. Even when I move the mouse, it still prints out the alert after 20 seconds. This is how I am using it:

IdleTimer* t = IdleTimer::instance(this,20);
connect(t,SIGNAL(idle()),this,SLOT(print()));

Neville Dastur said...

Hi @AttitudeMonger. I am not sure from your code what this points to but we use QApplication so that application wide events are captured by the idle timer. By allowing a parent to be passed though idle on any particular window for example can be detected.

I hope this helps