/*
* Copyright (C) 2015-2016 Canonical, Ltd.
*
* 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; version 3.
*
* 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, see .
*/
#include "WindowInputMonitor.h"
#include
#include
namespace UnityUtil {
class FakeElapsedTimer : public AbstractElapsedTimer {
public:
static qint64 msecsSinceEpoch;
FakeElapsedTimer() {}
void start() override { m_msecsSinceReference = msecsSinceEpoch; m_valid = true; }
qint64 msecsSinceReference() const override { return m_msecsSinceReference; }
qint64 elapsed() const override { return m_valid ? msecsSinceEpoch - m_msecsSinceReference : qrand(); }
private:
qint64 m_msecsSinceReference{0};
bool m_valid{false};
};
qint64 FakeElapsedTimer::msecsSinceEpoch = 0;
class FakeTimer : public AbstractTimer
{
Q_OBJECT
public:
FakeTimer(QObject *parent = nullptr);
void update();
qint64 nextTimeoutTime() const { return m_nextTimeoutTime; }
int interval() const override;
void setInterval(int msecs) override;
void start() override;
void stop() override;
bool isRunning() const override;
bool isSingleShot() const override;
void setSingleShot(bool value) override;
private:
int m_interval;
bool m_singleShot;
bool m_isRunning;
qint64 m_nextTimeoutTime;
};
class FakeTimerFactory : public AbstractTimerFactory
{
public:
FakeTimerFactory();
virtual ~FakeTimerFactory();
void updateTime(qint64 targetTime);
AbstractTimer *create(QObject *parent = nullptr) override;
QList> timers;
};
} // namespace UnityUtil
using namespace UnityUtil;
Q_DECLARE_METATYPE(Qt::Key) // because of QTest::addColumn
class WindowInputMonitorTest : public QObject {
Q_OBJECT
private Q_SLOTS:
void init(); // called right before each and every test function is executed
void cleanup(); // called right after each and every test function is executed
void touchTapTouch_data();
void touchTapTouch();
void tapWhileTouching();
void multipleHomeKeys();
void keyComboInvolvingHome();
void repeatedSuperPress();
private:
void passTime(qint64 timeSpanMs);
FakeTimerFactory *m_fakeTimerFactory;
};
void WindowInputMonitorTest::init()
{
m_fakeTimerFactory = new FakeTimerFactory;
}
void WindowInputMonitorTest::cleanup()
{
delete m_fakeTimerFactory;
m_fakeTimerFactory = nullptr;
}
void WindowInputMonitorTest::passTime(qint64 timeSpanMs)
{
qint64 finalTime = FakeElapsedTimer::msecsSinceEpoch + timeSpanMs;
m_fakeTimerFactory->updateTime(finalTime);
}
void WindowInputMonitorTest::touchTapTouch_data()
{
QTest::addColumn("silenceBeforeTap");
QTest::addColumn("tapDuration");
QTest::addColumn("silenceAfterTap");
QTest::addColumn("expectedActivatedCount");
QTest::addColumn("key");
QTest::newRow("tap followed by touch") << 2000 << 50 << 10 << 0 << Qt::Key_Super_L;
QTest::newRow("touch, tap and touch") << 10 << 50 << 10 << 0 << Qt::Key_Super_L;
QTest::newRow("touch followed by tap") << 10 << 50 << 2000 << 0 << Qt::Key_Super_L;
QTest::newRow("touch followed by long tap") << 10 << 500 << 2000 << 0 << Qt::Key_Super_L;
QTest::newRow("isolated tap, Super_L") << 1000 << 50 << 1000 << 1 << Qt::Key_Super_L;
QTest::newRow("isolated tap, HomePage") << 1000 << 50 << 1000 << 1 << Qt::Key_HomePage;
QTest::newRow("isolated long press") << 1000 << 200 << 1000 << 0 << Qt::Key_Super_L;
}
void WindowInputMonitorTest::touchTapTouch()
{
QFETCH(int, silenceBeforeTap);
QFETCH(int, tapDuration);
QFETCH(int, silenceAfterTap);
QFETCH(int, expectedActivatedCount);
QFETCH(Qt::Key, key);
WindowInputMonitor homeKeyWatcher(m_fakeTimerFactory->create(this), new FakeElapsedTimer);
QSignalSpy activatedSpy(&homeKeyWatcher, &WindowInputMonitor::homeKeyActivated);
QVERIFY(activatedSpy.isValid());
{
QTouchEvent touchEvent(QEvent::TouchBegin);
homeKeyWatcher.update(&touchEvent);
}
passTime(100);
{
QTouchEvent touchEvent(QEvent::TouchUpdate);
homeKeyWatcher.update(&touchEvent);
}
passTime(100);
{
QTouchEvent touchEvent(QEvent::TouchEnd);
homeKeyWatcher.update(&touchEvent);
}
passTime(silenceBeforeTap);
{
QKeyEvent keyEvent(QEvent::KeyPress, key, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
int tapTime = 0;
while (tapTime < tapDuration) {
passTime(10);
tapTime += 10;
if (tapTime + 10 >= tapDuration) {
QKeyEvent keyEvent(QEvent::KeyRelease, key, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
} else {
QKeyEvent keyEvent(QEvent::KeyPress, key, Qt::NoModifier, QString(), true /*autorepeat*/);
homeKeyWatcher.update(&keyEvent);
}
}
passTime(silenceAfterTap);
{
QTouchEvent touchEvent(QEvent::TouchBegin);
homeKeyWatcher.update(&touchEvent);
}
passTime(100);
{
QTouchEvent touchEvent(QEvent::TouchUpdate);
homeKeyWatcher.update(&touchEvent);
}
passTime(100);
{
QTouchEvent touchEvent(QEvent::TouchEnd);
homeKeyWatcher.update(&touchEvent);
}
passTime(1000);
QCOMPARE(activatedSpy.count(), expectedActivatedCount);
}
void WindowInputMonitorTest::keyComboInvolvingHome()
{
WindowInputMonitor homeKeyWatcher(m_fakeTimerFactory->create(this), new FakeElapsedTimer);
QSignalSpy activatedSpy(&homeKeyWatcher, &WindowInputMonitor::homeKeyActivated);
QVERIFY(activatedSpy.isValid());
passTime(1000);
{
QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Super_L, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
{
QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
{
QKeyEvent keyEvent(QEvent::KeyRelease, Qt::Key_A, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
{
QKeyEvent keyEvent(QEvent::KeyRelease, Qt::Key_Super_L, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
passTime(1000);
QCOMPARE(activatedSpy.count(), 0);
}
void WindowInputMonitorTest::tapWhileTouching()
{
WindowInputMonitor homeKeyWatcher(m_fakeTimerFactory->create(this), new FakeElapsedTimer);
QSignalSpy activatedSpy(&homeKeyWatcher, &WindowInputMonitor::homeKeyActivated);
QVERIFY(activatedSpy.isValid());
{
QTouchEvent touchEvent(QEvent::TouchBegin);
homeKeyWatcher.update(&touchEvent);
}
passTime(1000);
{
QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Super_L, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
passTime(100);
{
QKeyEvent keyEvent(QEvent::KeyRelease, Qt::Key_Super_L, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
passTime(1000);
{
QTouchEvent touchEvent(QEvent::TouchEnd);
homeKeyWatcher.update(&touchEvent);
}
passTime(1000);
QCOMPARE(activatedSpy.count(), 0);
}
/*
Press multiple of the mapped home keys roughly at the same time.
The first pressed will be recognized and the other ignored
*/
void WindowInputMonitorTest::multipleHomeKeys()
{
WindowInputMonitor homeKeyWatcher(m_fakeTimerFactory->create(this), new FakeElapsedTimer);
QSignalSpy activatedSpy(&homeKeyWatcher, &WindowInputMonitor::homeKeyActivated);
QVERIFY(activatedSpy.isValid());
passTime(1000);
{
QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_HomePage, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
passTime(50);
{
QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Super_L, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
passTime(50);
{
QKeyEvent keyEvent(QEvent::KeyRelease, Qt::Key_HomePage, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
passTime(10);
{
QKeyEvent keyEvent(QEvent::KeyRelease, Qt::Key_Super_L, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
passTime(1000);
QCOMPARE(activatedSpy.count(), 1);
}
// regression test for lp:1607427
void WindowInputMonitorTest::repeatedSuperPress()
{
WindowInputMonitor homeKeyWatcher(m_fakeTimerFactory->create(this), new FakeElapsedTimer);
QSignalSpy activatedSpy(&homeKeyWatcher, &WindowInputMonitor::homeKeyActivated);
QVERIFY(activatedSpy.isValid());
// 1st try
passTime(1000);
{
QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Super_L, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
passTime(50);
{
QKeyEvent keyEvent(QEvent::KeyRelease, Qt::Key_Super_L, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
passTime(1000);
QCOMPARE(activatedSpy.count(), 1);
// a touch event in between
{
QTouchEvent touchEvent(QEvent::TouchBegin);
homeKeyWatcher.update(&touchEvent);
}
{
QTouchEvent touchEvent(QEvent::TouchUpdate);
homeKeyWatcher.update(&touchEvent);
}
{
QTouchEvent touchEvent(QEvent::TouchEnd);
homeKeyWatcher.update(&touchEvent);
}
passTime(1000);
// 2nd try
activatedSpy.clear();
{
QKeyEvent keyEvent(QEvent::KeyPress, Qt::Key_Super_L, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
passTime(50);
{
QKeyEvent keyEvent(QEvent::KeyRelease, Qt::Key_Super_L, Qt::NoModifier);
homeKeyWatcher.update(&keyEvent);
}
passTime(1000);
QCOMPARE(activatedSpy.count(), 1);
}
/////////////////////////////////// FakeTimer //////////////////////////////////
FakeTimer::FakeTimer(QObject *parent)
: UnityUtil::AbstractTimer(parent)
, m_interval(0)
, m_singleShot(true)
, m_isRunning(false)
{
}
void FakeTimer::update()
{
if (!isRunning()) {
return;
}
if (m_nextTimeoutTime <= FakeElapsedTimer::msecsSinceEpoch) {
if (isSingleShot()) {
stop();
} else {
m_nextTimeoutTime += interval();
}
Q_EMIT timeout();
}
}
void FakeTimer::start()
{
m_isRunning = true;
m_nextTimeoutTime = FakeElapsedTimer::msecsSinceEpoch + (qint64)interval();
}
void FakeTimer::stop()
{
m_isRunning = false;
}
bool FakeTimer::isRunning() const
{
return m_isRunning;
}
int FakeTimer::interval() const
{
return m_interval;
}
void FakeTimer::setInterval(int msecs)
{
m_interval = msecs;
}
bool FakeTimer::isSingleShot() const
{
return m_singleShot;
}
void FakeTimer::setSingleShot(bool value)
{
m_singleShot = value;
}
/////////////////////////////////// FakeTimerFactory //////////////////////////////////
FakeTimerFactory::FakeTimerFactory()
{
}
FakeTimerFactory::~FakeTimerFactory()
{
for (int i = 0; i < timers.count(); ++i) {
FakeTimer *timer = timers[i].data();
if (timer) {
delete timer;
}
}
}
void FakeTimerFactory::updateTime(qint64 targetTime)
{
qint64 minTimeoutTime = targetTime;
for (int i = 0; i < timers.count(); ++i) {
FakeTimer *timer = timers[i].data();
if (timer && timer->isRunning() && timer->nextTimeoutTime() < minTimeoutTime) {
minTimeoutTime = timer->nextTimeoutTime();
}
}
FakeElapsedTimer::msecsSinceEpoch = minTimeoutTime;
for (int i = 0; i < timers.count(); ++i) {
FakeTimer *timer = timers[i].data();
if (timer) {
timer->update();
}
}
if (FakeElapsedTimer::msecsSinceEpoch < targetTime) {
updateTime(targetTime);
}
}
AbstractTimer *FakeTimerFactory::create(QObject *parent)
{
FakeTimer *fakeTimer = new FakeTimer(parent);
timers.append(fakeTimer);
return fakeTimer;
}
QTEST_GUILESS_MAIN(WindowInputMonitorTest)
#include "WindowInputMonitorTest.moc"