Wednesday, October 3, 2012

Screensaver blocking on Symbian

In my latest game the players spend significant amount of time just watching the screen and trying to figure the puzzle out. The first few levels are obvious and most players will sole them in a few seconds,m but as the difficulty of the puzzles increases the players stare at the screen longer and longer. At some point the screensaver would kick in and piss the player off (fortunately my ragtag QA/betatester team found this issue before launch).

Google search results are (as usual) helpful, but the most promising lead, the QSystemScreenSaver class is not a solution. There are three problems with it:

  1. The API of the class itself is terrible.
  2. The API of the related QML element is even worse.
  3. Last, but not least, it doesn't work (at least not in the Qt Mobility version shipped with the Qt SDK).

(BTW: these three points sums up pretty much every experience with Qt Mobility package I had. Qt devs should either kill this festering boil with fire or fix it and rename it, because I learned to dread everything remotely related to Qt Mobility, and I suspect I'm not the only one).

Anyways, let's get back to the core of the problem, i.e. "how to block the screensaver". Qt Mobility failed, but the task doesn't seems like a rocket science to me. Slightly different Google search suggested using native Symbian's User::ResetInactivityTime() method. Few minutes and one QTimer later, everything worked:
#ifndef SCREENSAVERBLOCKER_H
#define SCREENSAVERBLOCKER_H

#include <QObject>
#include <QApplication>
#include <QTimer>

class ScreenSaverBlocker : public QObject
{
    Q_OBJECT

public:
    explicit ScreenSaverBlocker(QObject *parent = 0) : QObject(parent) {
        mTimer.setInterval(1000);
        connect(&mTimer, SIGNAL(timeout()), this, SLOT(blockScreenSaver()));
        changeScreenSaverState(QApplication::activeWindow() != 0);
        if (qApp) {
            qApp->installEventFilter(this);
        }
    }

    void changeScreenSaverState(bool blockScreenSaver) {
        if (blockScreenSaver && !mTimer.isActive()) {
            mTimer.start();
        } else {
            mTimer.stop();
        }
    }

protected:
    bool eventFilter(QObject *obj, QEvent *event) {
        Q_UNUSED(obj)
        if (event->type() == QEvent::ApplicationActivate
         || event->type() == QEvent::ApplicationDeactivate) {
            changeScreenSaverState(event->type() == QEvent::ApplicationActivate);
        }
        return false;
    }

private slots:
    void blockScreenSaver() {
#ifdef Q_OS_SYMBIAN
        User::ResetInactivityTime();
#endif
    }

private:
    QTimer mTimer;
};

#endif // SCREENSAVERBLOCKER_H
The important thing in the code above is watching the ApplicationActivate and ApplicationDeactivate events - after all, when your app is in background, you shouldn't affect the phone behavior. I'm not sure if the app would fail the Nokia's QA process without this feature, but it seemed prudent to write the code this way.

If you want to use this object in your QML UI just register it with qmlRegisterType and add the registered import and QML element to your root element.