commit d8110ce23c368126deaa9cda5516abf4d469db46 Author: Honglei Zhang Date: Mon Jun 20 11:24:12 2011 +0300 Implement multimedia key event capture for Windows platform. Normally a media application would need to receive multimedia key events even it is running at background. The normally used multimedia keys include play/pause, volume up, volume down, next track, previous track and mute keys. To receive keyevents when an application is running at background, Qt installs a windows keyboard hook. After a key event is received, the hook function check if it's a multimedia key event and then forward the keyevents to the receiving application's active window. Task-number: QTBUG-19064 Reviewed-by: Trust Me diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h index 99479d0..e247533 100644 --- a/src/corelib/global/qnamespace.h +++ b/src/corelib/global/qnamespace.h @@ -546,6 +546,7 @@ public: AA_S60DontConstructApplicationPanes = 8, AA_S60DisablePartialScreenInputMode = 9, AA_X11InitThreads = 10, + AA_CaptureMultimediaKeys = 11, // Add new attributes before this line AA_AttributeCount diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc index 9f59c6e..17d1a2f 100644 --- a/src/corelib/global/qnamespace.qdoc +++ b/src/corelib/global/qnamespace.qdoc @@ -167,6 +167,20 @@ construction in order to make Xlib calls thread-safe. This attribute must be set before QApplication is constructed. + \value AA_CaptureMultimediaKeys Ensures that application receives multimedia key events. + On windows platform, multimedia key events from keyboard are received even when + application is running at background. Following keys are treated as multimedia keys + Qt::Key_VolumeDown, Qt::Key_VolumeMute, Qt::Key_VolumeUp, Qt::Key_MediaStop, + Qt::Key_MediaPlay, Qt::Key_MediaPrevious, Qt::Key_MediaNext and Qt::Key_LaunchMedia. + On Symbian platform, multimedia key events are + also received from external sources like headset. Symbian platform has a limitation that + there can be only one listener per process for media key events. In practice, + this means that when this attribute is set then application can not use Symbian + remote control API simultaneously (i.e. CRemConInterfaceSelector class). + This attribute must be set before QApplication is constructed. This is only + supported in Windows and Symbian^3. + + \omitvalue AA_AttributeCount */ diff --git a/src/corelib/kernel/kernel.pri b/src/corelib/kernel/kernel.pri index a3628b1..2489ddd 100644 --- a/src/corelib/kernel/kernel.pri +++ b/src/corelib/kernel/kernel.pri @@ -161,4 +161,3 @@ integrity { contains(QT_CONFIG, clock-gettime):include($$QT_SOURCE_TREE/config.tests/unix/clock-gettime/clock-gettime.pri) } - diff --git a/src/gui/kernel/kernel.pri b/src/gui/kernel/kernel.pri index 3c57368..e61cb89 100644 --- a/src/gui/kernel/kernel.pri +++ b/src/gui/kernel/kernel.pri @@ -7,7 +7,7 @@ PRECOMPILED_HEADER = kernel/qt_gui_pch.h KERNEL_P= kernel HEADERS += \ kernel/qaction.h \ - kernel/qaction_p.h \ + kernel/qaction_p.h \ kernel/qactiongroup.h \ kernel/qapplication.h \ kernel/qapplication_p.h \ @@ -35,8 +35,8 @@ HEADERS += \ kernel/qstackedlayout.h \ kernel/qtooltip.h \ kernel/qwhatsthis.h \ - kernel/qwidget.h \ - kernel/qwidget_p.h \ + kernel/qwidget.h \ + kernel/qwidget_p.h \ kernel/qwidgetaction.h \ kernel/qwidgetaction_p.h \ kernel/qwindowdefs.h \ @@ -47,7 +47,7 @@ HEADERS += \ kernel/qgesturerecognizer.h \ kernel/qgesturemanager_p.h \ kernel/qsoftkeymanager_p.h \ - kernel/qsoftkeymanager_common_p.h \ + kernel/qsoftkeymanager_common_p.h \ kernel/qguiplatformplugin_p.h \ SOURCES += \ @@ -82,7 +82,7 @@ SOURCES += \ kernel/qgesturerecognizer.cpp \ kernel/qgesturemanager.cpp \ kernel/qsoftkeymanager.cpp \ - kernel/qdesktopwidget.cpp \ + kernel/qdesktopwidget.cpp \ kernel/qguiplatformplugin.cpp win32 { @@ -101,8 +101,8 @@ win32 { kernel/qsound_win.cpp \ kernel/qwidget_win.cpp \ kernel/qole_win.cpp \ - kernel/qkeymapper_win.cpp \ - kernel/qwinnativepangesturerecognizer_win.cpp + kernel/qkeymapper_win.cpp \ + kernel/qwinnativepangesturerecognizer_win.cpp !contains(DEFINES, QT_NO_DIRECTDRAW):LIBS += ddraw.lib } diff --git a/src/gui/kernel/qapplication_win.cpp b/src/gui/kernel/qapplication_win.cpp index c34e75f..ce82c97 100644 --- a/src/gui/kernel/qapplication_win.cpp +++ b/src/gui/kernel/qapplication_win.cpp @@ -404,6 +404,13 @@ QT_END_INCLUDE_NAMESPACE static int translateButtonState(int s, int type, int button); +// handle of gui library instance +HINSTANCE hGuiLibInstance = 0; + +// keyboard hook function +LRESULT QT_WIN_CALLBACK qt_LowLevelKeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam); +HHOOK hKeyboardHook = NULL; + // ##### get rid of this! QRgb qt_colorref2qrgb(COLORREF col) { @@ -761,7 +768,6 @@ static BOOL WINAPI qt_updateLayeredWindowIndirect(HWND hwnd, const Q_UPDATELAYER void qt_init(QApplicationPrivate *priv, int) { - int argc = priv->argc; char **argv = priv->argv; int i, j; @@ -911,6 +917,11 @@ void qt_init(QApplicationPrivate *priv, int) (PtrEndPanningFeedback)libTheme.resolve("EndPanningFeedback"); #endif #endif // QT_NO_GESTURES + + // setup keyboard hook to receive multimedia key events when application is at background + if(QCoreApplication::testAttribute(Qt::AA_CaptureMultimediaKeys)){ + hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL,(HOOKPROC) qt_LowLevelKeyboardHookProc, hGuiLibInstance, 0); + } } /***************************************************************************** @@ -919,6 +930,12 @@ void qt_init(QApplicationPrivate *priv, int) void qt_cleanup() { + // clean up keyboard hook + if(hKeyboardHook){ + UnhookWindowsHookEx(hKeyboardHook); + hKeyboardHook = NULL; + } + unregWinClasses(); QPixmapCache::clear(); @@ -4245,4 +4262,53 @@ bool QApplicationPrivate::translateTouchEvent(const MSG &msg) return true; } +LRESULT QT_WIN_CALLBACK qt_LowLevelKeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + LPKBDLLHOOKSTRUCT kbHookStruct = reinterpret_cast(lParam); + + switch(kbHookStruct->vkCode){ + case VK_VOLUME_MUTE: + case VK_VOLUME_DOWN: + case VK_VOLUME_UP: + case VK_MEDIA_NEXT_TRACK: + case VK_MEDIA_PREV_TRACK: + case VK_MEDIA_STOP: + case VK_MEDIA_PLAY_PAUSE: + case VK_LAUNCH_MEDIA_SELECT: + // send message + { + HWND hWnd = NULL; + foreach (QWidget *widget, QApplication::topLevelWidgets()) { + // relay message to each top level widgets(window) + // if the window has focus, we don't send a duplicate message + if(QApplicationPrivate::active_window == widget){ + continue; + } + + hWnd = widget->winId(); + + // generate message and post it to the message queue + LPKBDLLHOOKSTRUCT pKeyboardHookStruct = reinterpret_cast(lParam); + WPARAM _wParam = pKeyboardHookStruct->vkCode; + LPARAM _lParam = MAKELPARAM(pKeyboardHookStruct->scanCode, pKeyboardHookStruct->flags); + PostMessage(hWnd, wParam, _wParam, _lParam); + } + } + break; + default: + break; + } + + return CallNextHookEx(0, nCode, wParam, lParam); +} + +EXTERN_C BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpvReserved) +{ + hGuiLibInstance = hInstance; + Q_UNUSED(dwReason); + Q_UNUSED(lpvReserved); + + return true; +} + QT_END_NAMESPACE diff --git a/tests/auto/qapplication/multimediakeys/main.cpp b/tests/auto/qapplication/multimediakeys/main.cpp new file mode 100644 index 0000000..f9a1381 --- /dev/null +++ b/tests/auto/qapplication/multimediakeys/main.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include +#include +#include +#include +#include +#include + +class MainWindow : public QMainWindow +{ + //Q_OBJECT +public: + explicit MainWindow(QWidget *parent=0) : QMainWindow(parent){}; + //~MainWindow(); + +protected: + virtual void keyPressEvent(QKeyEvent *); +}; + +/* +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent) +{ +} + +MainWindow::~MainWindow() +{ +} +*/ + +void MainWindow::keyPressEvent(QKeyEvent *e) +{ + QString msg; + fprintf(stderr, "%x", e->key()); + fflush(stderr); + qApp->quit(); +} + +int main(int argc, char *argv[]) +{ + QApplication::setAttribute(Qt::AA_CaptureMultimediaKeys); + QApplication app(argc, argv); + MainWindow w; + w.showMinimized(); + // 5 seconds suicide timer + QTimer::singleShot(5000, &app, SLOT(quit())); + return app.exec(); +} diff --git a/tests/auto/qapplication/multimediakeys/multimediakeys.pro b/tests/auto/qapplication/multimediakeys/multimediakeys.pro new file mode 100644 index 0000000..9e2715c --- /dev/null +++ b/tests/auto/qapplication/multimediakeys/multimediakeys.pro @@ -0,0 +1,5 @@ +TEMPLATE = app +TARGET = +DEPENDPATH += . +INCLUDEPATH += . +SOURCES += main.cpp diff --git a/tests/auto/qapplication/qapplication.pro b/tests/auto/qapplication/qapplication.pro index becc6c6..1e5b960 100644 --- a/tests/auto/qapplication/qapplication.pro +++ b/tests/auto/qapplication/qapplication.pro @@ -2,6 +2,7 @@ TEMPLATE = subdirs SUBDIRS = test \ desktopsettingsaware \ modal \ - wincmdline + wincmdline \ + multimediakeys diff --git a/tests/auto/qapplication/tst_qapplication.cpp b/tests/auto/qapplication/tst_qapplication.cpp index 79cb362..991b92b 100644 --- a/tests/auto/qapplication/tst_qapplication.cpp +++ b/tests/auto/qapplication/tst_qapplication.cpp @@ -50,6 +50,7 @@ #include "private/qapplication_p.h" #include "private/qstylesheetstyle_p.h" +#include #ifdef Q_OS_WINCE #include #endif @@ -88,6 +89,7 @@ public slots: void cleanup(); private slots: void sendEventsOnProcessEvents(); // this must be the first test + void getSetCheck(); void staticSetup(); @@ -148,7 +150,8 @@ private slots: void symbianLeaveThroughMain(); void qtbug_12673(); - + void multimediaKeyEvents_data(); + void multimediaKeyEvents(); void globalStaticObjectDestruction(); // run this last }; @@ -2261,6 +2264,66 @@ void tst_QApplication::qtbug_12673() #endif // Q_OS_SYMBIAN } +void tst_QApplication::multimediaKeyEvents_data() +{ +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + QTest::addColumn("sent"); + QTest::addColumn("expected"); + + QTest::newRow("Volume Mute") << VK_VOLUME_MUTE << (int)Qt::Key_VolumeMute; + QTest::newRow("Volume Down") << VK_VOLUME_DOWN << (int)Qt::Key_VolumeDown; + QTest::newRow("Volume Up") << VK_VOLUME_UP << (int)Qt::Key_VolumeUp; + QTest::newRow("Next Track") << VK_MEDIA_NEXT_TRACK << (int)Qt::Key_MediaNext; + QTest::newRow("Prev Track") << VK_MEDIA_PREV_TRACK << (int)Qt::Key_MediaPrevious; + QTest::newRow("Media Stop") << VK_MEDIA_STOP << (int)Qt::Key_MediaStop; + QTest::newRow("Play Pause") << VK_MEDIA_PLAY_PAUSE << (int)Qt::Key_MediaPlay; + QTest::newRow("Media Select") << VK_LAUNCH_MEDIA_SELECT << (int)Qt::Key_LaunchMedia; + QTest::newRow("A") << 0x41 << 0; + QTest::newRow("Left Arrow") << VK_LEFT << 0; +#endif +} + +void tst_QApplication::multimediaKeyEvents() +{ +#ifdef Q_OS_SYMBIAN + // symbian testing +#else +#if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) + QProcess testProcess; + QFETCH(int, sent); + QFETCH(int, expected); + +#ifdef QT_DEBUG + testProcess.start("multimediakeys/debug/multimediakeys.exe"); +#else + testProcess.start("multimediakeys/release/multimediakeys.exe"); +#endif + testProcess.waitForStarted(); + QTest::qSleep(500); + + // sythesize keystrokes with WIN32 API + KEYBDINPUT kb={0}; + INPUT input={0}; + kb.wVk = sent; + input.type = INPUT_KEYBOARD; + input.ki = kb; + SendInput(1, &input, sizeof(input)); + + //send keyup event + input.ki.dwFlags = KEYEVENTF_KEYUP; + SendInput(1, &input, sizeof(input)); + QVERIFY(testProcess.waitForFinished(10000)); + QByteArray error = testProcess.readAllStandardError(); + bool ok; + int received = QString(error).toInt(&ok, 16); + QCOMPARE(received, expected); +#else + QSKIP("This feature is only supported in Symbian and Windows", SkipAll); +#endif +#endif +} + + /* This test is meant to ensure that certain objects (public & commonly used) can safely be used in a Q_GLOBAL_STATIC such that their destructors are diff --git a/tests/auto/qtextdocumentlayout/expected.png b/tests/auto/qtextdocumentlayout/expected.png new file mode 100644 index 0000000..42ee311 Binary files /dev/null and b/tests/auto/qtextdocumentlayout/expected.png differ diff --git a/tests/auto/qtextdocumentlayout/img.png b/tests/auto/qtextdocumentlayout/img.png new file mode 100644 index 0000000..42ee311 Binary files /dev/null and b/tests/auto/qtextdocumentlayout/img.png differ