From 3b6a61953bcd319a6df55d66116ce92f7525ec00 Mon Sep 17 00:00:00 2001 From: Honglei Zhang Date: Sun, 3 Jul 2011 20:22:41 +0300 Subject: Enable key capture and RemCon interfaces for Qt apps on Symbian The volume and other multimedia keys in Symbian are not delivered through the normal key events, but through a seperate API called CRemConCoreApiTarget. The commit implements the feature that multimedia key events are delivered via normal key events to Qt Application, if the Qt::AA_CaptureMultimediaKeys is defined. Task-number: QTBUG-4415 Reviewed-by: Sami Merila Reviewed-by: Miikka Heikkinen --- src/corelib/global/qnamespace.h | 1 + src/corelib/global/qnamespace.qdoc | 9 + src/gui/kernel/qkeymapper_p.h | 2 + src/gui/kernel/qkeymapper_s60.cpp | 195 +++++++------ src/gui/s60framework/qs60keycapture.cpp | 308 +++++++++++++++++++++ src/gui/s60framework/qs60keycapture_p.h | 116 ++++++++ src/gui/s60framework/qs60mainappui.cpp | 17 ++ src/gui/s60framework/s60framework.pri | 21 +- .../qs60mainapplication/qs60mainapplication.pro | 1 + .../tst_qs60mainapplication.cpp | 259 +++++++++++++++++ 10 files changed, 838 insertions(+), 91 deletions(-) create mode 100644 src/gui/s60framework/qs60keycapture.cpp create mode 100644 src/gui/s60framework/qs60keycapture_p.h 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..7d3b7f6 100644 --- a/src/corelib/global/qnamespace.qdoc +++ b/src/corelib/global/qnamespace.qdoc @@ -167,6 +167,15 @@ construction in order to make Xlib calls thread-safe. This attribute must be set before QApplication is constructed. + \value AA_CaptureMultimediaKeys Enables application to receive multimedia key events + (play, next, previous etc). This includes also external sources such as headsets. + Application can not use Remote Control framework on Symbian if this attribute is + set. On Symbian, multimedia key event routing may vary between different devices. + For example, application on background may receive multimedia key events only if + it has active audio stream i.e. it is playing music or video. This attribute must + be set before QApplication is constructed. This attribute is only supported in Symbian + platform. + \omitvalue AA_AttributeCount */ diff --git a/src/gui/kernel/qkeymapper_p.h b/src/gui/kernel/qkeymapper_p.h index 2607c2a..1d624f9 100644 --- a/src/gui/kernel/qkeymapper_p.h +++ b/src/gui/kernel/qkeymapper_p.h @@ -213,6 +213,8 @@ public: int mapS60ScanCodesToQt(TUint s60key); int mapQtToS60Key(int qtKey); int mapQtToS60ScanCodes(int qtKey); + int mapS60RemConIdToS60Key(int s60RemConId); + int mapS60RemConIdToS60ScanCodes(int s60RemConId); void updateInputLanguage(); #endif }; diff --git a/src/gui/kernel/qkeymapper_s60.cpp b/src/gui/kernel/qkeymapper_s60.cpp index 1113b77..2a7083e 100644 --- a/src/gui/kernel/qkeymapper_s60.cpp +++ b/src/gui/kernel/qkeymapper_s60.cpp @@ -87,97 +87,104 @@ QString QKeyMapperPrivate::translateKeyEvent(int keySym, Qt::KeyboardModifiers / } #include +#include struct KeyMapping{ TKeyCode s60KeyCode; TStdScanCode s60ScanCode; + TRemConCoreApiOperationId s60RemConId; Qt::Key qtKey; }; using namespace Qt; +// key mapping table in the format of +// {S60 key code, S60 scan code, S60 RemCon operation Id, Qt key code} static const KeyMapping keyMapping[] = { - {EKeyBackspace, EStdKeyBackspace, Key_Backspace}, - {EKeyTab, EStdKeyTab, Key_Tab}, - {EKeyEnter, EStdKeyEnter, Key_Enter}, - {EKeyEscape, EStdKeyEscape, Key_Escape}, - {EKeySpace, EStdKeySpace, Key_Space}, - {EKeyDelete, EStdKeyDelete, Key_Delete}, - {EKeyPrintScreen, EStdKeyPrintScreen, Key_SysReq}, - {EKeyPause, EStdKeyPause, Key_Pause}, - {EKeyHome, EStdKeyHome, Key_Home}, - {EKeyEnd, EStdKeyEnd, Key_End}, - {EKeyPageUp, EStdKeyPageUp, Key_PageUp}, - {EKeyPageDown, EStdKeyPageDown, Key_PageDown}, - {EKeyInsert, EStdKeyInsert, Key_Insert}, - {EKeyLeftArrow, EStdKeyLeftArrow, Key_Left}, - {EKeyRightArrow, EStdKeyRightArrow, Key_Right}, - {EKeyUpArrow, EStdKeyUpArrow, Key_Up}, - {EKeyDownArrow, EStdKeyDownArrow, Key_Down}, - {EKeyLeftShift, EStdKeyLeftShift, Key_Shift}, - {EKeyRightShift, EStdKeyRightShift, Key_Shift}, - {EKeyLeftAlt, EStdKeyLeftAlt, Key_Alt}, - {EKeyRightAlt, EStdKeyRightAlt, Key_AltGr}, - {EKeyLeftCtrl, EStdKeyLeftCtrl, Key_Control}, - {EKeyRightCtrl, EStdKeyRightCtrl, Key_Control}, - {EKeyLeftFunc, EStdKeyLeftFunc, Key_Super_L}, - {EKeyRightFunc, EStdKeyRightFunc, Key_Super_R}, - {EKeyCapsLock, EStdKeyCapsLock, Key_CapsLock}, - {EKeyNumLock, EStdKeyNumLock, Key_NumLock}, - {EKeyScrollLock, EStdKeyScrollLock, Key_ScrollLock}, - {EKeyF1, EStdKeyF1, Key_F1}, - {EKeyF2, EStdKeyF2, Key_F2}, - {EKeyF3, EStdKeyF3, Key_F3}, - {EKeyF4, EStdKeyF4, Key_F4}, - {EKeyF5, EStdKeyF5, Key_F5}, - {EKeyF6, EStdKeyF6, Key_F6}, - {EKeyF7, EStdKeyF7, Key_F7}, - {EKeyF8, EStdKeyF8, Key_F8}, - {EKeyF9, EStdKeyF9, Key_F9}, - {EKeyF10, EStdKeyF10, Key_F10}, - {EKeyF11, EStdKeyF11, Key_F11}, - {EKeyF12, EStdKeyF12, Key_F12}, - {EKeyF13, EStdKeyF13, Key_F13}, - {EKeyF14, EStdKeyF14, Key_F14}, - {EKeyF15, EStdKeyF15, Key_F15}, - {EKeyF16, EStdKeyF16, Key_F16}, - {EKeyF17, EStdKeyF17, Key_F17}, - {EKeyF18, EStdKeyF18, Key_F18}, - {EKeyF19, EStdKeyF19, Key_F19}, - {EKeyF20, EStdKeyF20, Key_F20}, - {EKeyF21, EStdKeyF21, Key_F21}, - {EKeyF22, EStdKeyF22, Key_F22}, - {EKeyF23, EStdKeyF23, Key_F23}, - {EKeyF24, EStdKeyF24, Key_F24}, - {EKeyOff, EStdKeyOff, Key_PowerOff}, -// {EKeyMenu, EStdKeyMenu, Key_Menu}, // Menu is EKeyApplication0 - {EKeyHelp, EStdKeyHelp, Key_Help}, - {EKeyDial, EStdKeyDial, Key_Call}, - {EKeyIncVolume, EStdKeyIncVolume, Key_VolumeUp}, - {EKeyDecVolume, EStdKeyDecVolume, Key_VolumeDown}, - {EKeyDevice0, EStdKeyDevice0, Key_Context1}, // Found by manual testing. - {EKeyDevice1, EStdKeyDevice1, Key_Context2}, // Found by manual testing. - {EKeyDevice3, EStdKeyDevice3, Key_Select}, - {EKeyDevice7, EStdKeyDevice7, Key_Camera}, - {EKeyApplication0, EStdKeyApplication0, Key_Menu}, // Found by manual testing. - {EKeyApplication1, EStdKeyApplication1, Key_Launch1}, // Found by manual testing. - {EKeyApplication2, EStdKeyApplication2, Key_MediaPlay}, // Found by manual testing. - {EKeyApplication3, EStdKeyApplication3, Key_MediaStop}, // Found by manual testing. - {EKeyApplication4, EStdKeyApplication4, Key_MediaNext}, // Found by manual testing. - {EKeyApplication5, EStdKeyApplication5, Key_MediaPrevious}, // Found by manual testing. - {EKeyApplication6, EStdKeyApplication6, Key_Launch6}, - {EKeyApplication7, EStdKeyApplication7, Key_Launch7}, - {EKeyApplication8, EStdKeyApplication8, Key_Launch8}, - {EKeyApplication9, EStdKeyApplication9, Key_Launch9}, - {EKeyApplicationA, EStdKeyApplicationA, Key_LaunchA}, - {EKeyApplicationB, EStdKeyApplicationB, Key_LaunchB}, - {EKeyApplicationC, EStdKeyApplicationC, Key_LaunchC}, - {EKeyApplicationD, EStdKeyApplicationD, Key_LaunchD}, - {EKeyApplicationE, EStdKeyApplicationE, Key_LaunchE}, - {EKeyApplicationF, EStdKeyApplicationF, Key_LaunchF}, - {EKeyApplication19, EStdKeyApplication19, Key_CameraFocus}, - {EKeyYes, EStdKeyYes, Key_Yes}, - {EKeyNo, EStdKeyNo, Key_No}, - {TKeyCode(0), TStdScanCode(0), Qt::Key(0)} + {EKeyBackspace, EStdKeyBackspace, ENop, Key_Backspace}, + {EKeyTab, EStdKeyTab, ENop, Key_Tab}, + {EKeyEnter, EStdKeyEnter, ERemConCoreApiEnter, Key_Enter}, + {EKeyEscape, EStdKeyEscape, ENop, Key_Escape}, + {EKeySpace, EStdKeySpace, ENop, Key_Space}, + {EKeyDelete, EStdKeyDelete, ENop, Key_Delete}, + {EKeyPrintScreen, EStdKeyPrintScreen, ENop, Key_SysReq}, + {EKeyPause, EStdKeyPause, ENop, Key_Pause}, + {EKeyHome, EStdKeyHome, ENop, Key_Home}, + {EKeyEnd, EStdKeyEnd, ENop, Key_End}, + {EKeyPageUp, EStdKeyPageUp, ERemConCoreApiPageUp, Key_PageUp}, + {EKeyPageDown, EStdKeyPageDown, ERemConCoreApiPageDown, Key_PageDown}, + {EKeyInsert, EStdKeyInsert, ENop, Key_Insert}, + {EKeyLeftArrow, EStdKeyLeftArrow, ERemConCoreApiLeft, Key_Left}, + {EKeyRightArrow, EStdKeyRightArrow, ERemConCoreApiRight, Key_Right}, + {EKeyUpArrow, EStdKeyUpArrow, ERemConCoreApiUp, Key_Up}, + {EKeyDownArrow, EStdKeyDownArrow, ERemConCoreApiDown, Key_Down}, + {EKeyLeftShift, EStdKeyLeftShift, ENop, Key_Shift}, + {EKeyRightShift, EStdKeyRightShift, ENop, Key_Shift}, + {EKeyLeftAlt, EStdKeyLeftAlt, ENop, Key_Alt}, + {EKeyRightAlt, EStdKeyRightAlt, ENop, Key_AltGr}, + {EKeyLeftCtrl, EStdKeyLeftCtrl, ENop, Key_Control}, + {EKeyRightCtrl, EStdKeyRightCtrl, ENop, Key_Control}, + {EKeyLeftFunc, EStdKeyLeftFunc, ENop, Key_Super_L}, + {EKeyRightFunc, EStdKeyRightFunc, ENop, Key_Super_R}, + {EKeyCapsLock, EStdKeyCapsLock, ENop, Key_CapsLock}, + {EKeyNumLock, EStdKeyNumLock, ENop, Key_NumLock}, + {EKeyScrollLock, EStdKeyScrollLock, ENop, Key_ScrollLock}, + {EKeyF1, EStdKeyF1, ERemConCoreApiF1, Key_F1}, + {EKeyF2, EStdKeyF2, ERemConCoreApiF2, Key_F2}, + {EKeyF3, EStdKeyF3, ERemConCoreApiF3, Key_F3}, + {EKeyF4, EStdKeyF4, ERemConCoreApiF4, Key_F4}, + {EKeyF5, EStdKeyF5, ERemConCoreApiF5, Key_F5}, + {EKeyF6, EStdKeyF6, ENop, Key_F6}, + {EKeyF7, EStdKeyF7, ENop, Key_F7}, + {EKeyF8, EStdKeyF8, ENop, Key_F8}, + {EKeyF9, EStdKeyF9, ENop, Key_F9}, + {EKeyF10, EStdKeyF10, ENop, Key_F10}, + {EKeyF11, EStdKeyF11, ENop, Key_F11}, + {EKeyF12, EStdKeyF12, ENop, Key_F12}, + {EKeyF13, EStdKeyF13, ENop, Key_F13}, + {EKeyF14, EStdKeyF14, ENop, Key_F14}, + {EKeyF15, EStdKeyF15, ENop, Key_F15}, + {EKeyF16, EStdKeyF16, ENop, Key_F16}, + {EKeyF17, EStdKeyF17, ENop, Key_F17}, + {EKeyF18, EStdKeyF18, ENop, Key_F18}, + {EKeyF19, EStdKeyF19, ENop, Key_F19}, + {EKeyF20, EStdKeyF20, ENop, Key_F20}, + {EKeyF21, EStdKeyF21, ENop, Key_F21}, + {EKeyF22, EStdKeyF22, ENop, Key_F22}, + {EKeyF23, EStdKeyF23, ENop, Key_F23}, + {EKeyF24, EStdKeyF24, ENop, Key_F24}, + {EKeyOff, EStdKeyOff, ENop, Key_PowerOff}, +// {EKeyMenu, EStdKeyMenu, ENop, Key_Menu}, // Menu is EKeyApplication0 + {EKeyHelp, EStdKeyHelp, ERemConCoreApiHelp, Key_Help}, + {EKeyDial, EStdKeyDial, ENop, Key_Call}, + {EKeyIncVolume, EStdKeyIncVolume, ERemConCoreApiVolumeUp, Key_VolumeUp}, + {EKeyDecVolume, EStdKeyDecVolume, ERemConCoreApiVolumeDown, Key_VolumeDown}, + {EKeyDevice0, EStdKeyDevice0, ENop, Key_Context1}, // Found by manual testing. + {EKeyDevice1, EStdKeyDevice1, ENop, Key_Context2}, // Found by manual testing. + {EKeyDevice3, EStdKeyDevice3, ERemConCoreApiSelect, Key_Select}, + {EKeyDevice7, EStdKeyDevice7, ENop, Key_Camera}, + {EKeyApplication0, EStdKeyApplication0, ENop, Key_Menu}, // Found by manual testing. + {EKeyApplication1, EStdKeyApplication1, ENop, Key_Launch1}, // Found by manual testing. + {EKeyApplication2, EStdKeyApplication2, ERemConCoreApiPlay, Key_MediaPlay}, // Found by manual testing. + {EKeyApplication3, EStdKeyApplication3, ERemConCoreApiStop, Key_MediaStop}, // Found by manual testing. + {EKeyApplication4, EStdKeyApplication4, ERemConCoreApiForward, Key_MediaNext}, // Found by manual testing. + {EKeyApplication5, EStdKeyApplication5, ERemConCoreApiBackward, Key_MediaPrevious}, // Found by manual testing. + {EKeyApplication6, EStdKeyApplication6, ENop, Key_Launch6}, + {EKeyApplication7, EStdKeyApplication7, ENop, Key_Launch7}, + {EKeyApplication8, EStdKeyApplication8, ENop, Key_Launch8}, + {EKeyApplication9, EStdKeyApplication9, ENop, Key_Launch9}, + {EKeyApplicationA, EStdKeyApplicationA, ENop, Key_LaunchA}, + {EKeyApplicationB, EStdKeyApplicationB, ENop, Key_LaunchB}, + {EKeyApplicationC, EStdKeyApplicationC, ENop, Key_LaunchC}, + {EKeyApplicationD, EStdKeyApplicationD, ENop, Key_LaunchD}, + {EKeyApplicationE, EStdKeyApplicationE, ENop, Key_LaunchE}, + {EKeyApplicationF, EStdKeyApplicationF, ENop, Key_LaunchF}, + {EKeyApplication19, EStdKeyApplication19, ENop, Key_CameraFocus}, + {EKeyYes, EStdKeyYes, ENop, Key_Yes}, + {EKeyNo, EStdKeyNo, ENop, Key_No}, + {EKeyDevice20, EStdKeyDevice20, ERemConCoreApiPausePlayFunction, Key_MediaTogglePlayPause}, + {EKeyDevice21, EStdKeyDevice21, ERemConCoreApiRewind, Key_AudioRewind}, + {EKeyDevice22, EStdKeyDevice22, ERemConCoreApiFastForward, Key_AudioForward}, + {TKeyCode(0), TStdScanCode(0), ENop, Qt::Key(0)} }; int QKeyMapperPrivate::mapS60KeyToQt(TUint s60key) @@ -228,6 +235,30 @@ int QKeyMapperPrivate::mapQtToS60ScanCodes(int qtKey) return res; } +int QKeyMapperPrivate::mapS60RemConIdToS60Key(int s60RemConId) +{ + int res = KErrUnknown; + for (int i = 0; keyMapping[i].s60KeyCode != 0; i++) { + if (keyMapping[i].s60RemConId == s60RemConId) { + res = keyMapping[i].s60KeyCode; + break; + } + } + return res; +} + +int QKeyMapperPrivate::mapS60RemConIdToS60ScanCodes(int s60RemConId) +{ + int res = KErrUnknown; + for (int i = 0; keyMapping[i].s60KeyCode != 0; i++) { + if (keyMapping[i].s60RemConId == s60RemConId) { + res = keyMapping[i].s60ScanCode; + break; + } + } + return res; +} + void QKeyMapperPrivate::updateInputLanguage() { #ifdef Q_WS_S60 diff --git a/src/gui/s60framework/qs60keycapture.cpp b/src/gui/s60framework/qs60keycapture.cpp new file mode 100644 index 0000000..db4cc0e --- /dev/null +++ b/src/gui/s60framework/qs60keycapture.cpp @@ -0,0 +1,308 @@ +/**************************************************************************** +** +** 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 QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include "qkeymapper_p.h" +#include "qs60keycapture_p.h" + +QT_BEGIN_NAMESPACE + +/* + * Helper class for sending key handling responses to RemCon. +*/ +class CResponseHandler : public CActive +{ +public: + static CResponseHandler *NewL(CRemConCoreApiTarget &remConCoreApiTarget); + virtual ~CResponseHandler(); + void CompleteAnyKey(TRemConCoreApiOperationId operationId); + +private: + CResponseHandler(CRemConCoreApiTarget &remConCoreApiTarget); + + void RunL(); + void DoCancel(); + +private: + RArray iResponseArray; + CRemConCoreApiTarget &iRemConCoreApiTarget; +}; + +CResponseHandler::CResponseHandler(CRemConCoreApiTarget &remConCoreApiTarget) + : CActive(CActive::EPriorityStandard), + iRemConCoreApiTarget(remConCoreApiTarget) +{ + CActiveScheduler::Add(this); +} + +CResponseHandler *CResponseHandler::NewL(CRemConCoreApiTarget &remConCoreApiTarget) +{ + CResponseHandler *self = + new (ELeave) CResponseHandler(remConCoreApiTarget); + return self; +} + +CResponseHandler::~CResponseHandler() +{ + Cancel(); + iResponseArray.Close(); +} + +void CResponseHandler::CompleteAnyKey(TRemConCoreApiOperationId operationId) +{ + if (!IsActive()) { + TInt error = KErrNone; + iRemConCoreApiTarget.SendResponse(iStatus, operationId, error); + SetActive(); + } else { + // already active. Append to array and complete later. + iResponseArray.Append(operationId); + } +} + +void CResponseHandler::DoCancel() +{ + iRemConCoreApiTarget.Cancel(); +} + +void CResponseHandler::RunL() +{ + // if any existing -> Send response + if (iResponseArray.Count()) { + CompleteAnyKey(iResponseArray[0]); + // Remove already completed key + iResponseArray.Remove(0); + iResponseArray.Compress(); + } +} + + +/* + * QS60KeyCapture provides media key handling using services from RemCon. + */ +QS60KeyCapture::QS60KeyCapture(CCoeEnv *env, QObject *parent): + QObject(parent), coeEnv(env), selector(0), target(0), handler(0) +{ + initRemCon(); + + TTimeIntervalMicroSeconds32 initialTime; + TTimeIntervalMicroSeconds32 time; + coeEnv->WsSession().GetKeyboardRepeatRate(initialTime, time); + initialRepeatTime = (initialTime.Int() / 1000); // msecs + repeatTime = (time.Int() / 1000); // msecs + + int clickTimeout = initialRepeatTime + repeatTime; + + volumeUpClickTimer.setSingleShot(true); + volumeDownClickTimer.setSingleShot(true); + repeatTimer.setSingleShot(true); + + volumeUpClickTimer.setInterval(clickTimeout); + volumeDownClickTimer.setInterval(clickTimeout); + repeatTimer.setInterval(initialRepeatTime); + + connect(&volumeUpClickTimer, SIGNAL(timeout()), this, SLOT(volumeUpClickTimerExpired())); + connect(&volumeDownClickTimer, SIGNAL(timeout()), this, SLOT(volumeDownClickTimerExpired())); + connect(&repeatTimer, SIGNAL(timeout()), this, SLOT(repeatTimerExpired())); +} + +/* + * Initializes RemCon connection for receiving key events + */ +void QS60KeyCapture::initRemCon() +{ + try { + QT_TRAP_THROWING( + selector = CRemConInterfaceSelector::NewL(); + target = CRemConCoreApiTarget::NewL(*selector, *this); + handler = CResponseHandler::NewL(*target); + selector->OpenTargetL()); + } catch (const std::exception &e) { + delete handler; + delete selector; + selector = 0; + target = 0; + handler = 0; + } +} + +QS60KeyCapture::~QS60KeyCapture() +{ + delete handler; + delete selector; +} + +void QS60KeyCapture::MrccatoCommand(TRemConCoreApiOperationId operationId, + TRemConCoreApiButtonAction buttonAction) +{ + if (!target) + return; + + switch (operationId) { + // Volume up/down keys auto repeat if user hold the key long enough + case ERemConCoreApiVolumeUp: + case ERemConCoreApiVolumeDown: + { + // Side key events are sent using following scheme: + // - ButtonClick is sent first + // - if nothing happens before repeat timer expires, no further events are generated + // - if user holds the button longer, ButtonPress is sent and + // when button is finally released ButtonRelease is sent. + + QTimer &timer = (operationId == ERemConCoreApiVolumeUp) ? volumeUpClickTimer : volumeDownClickTimer; + + switch (buttonAction) { + case ERemConCoreApiButtonPress: + if (timer.isActive()) { + timer.stop(); + // force repeat event + repeatKeyEvent = mapToKeyEvent(operationId); + repeatTimerExpired(); + } else { + sendKey(operationId, QEvent::KeyPress); + repeatTimer.start(initialRepeatTime); + } + break; + case ERemConCoreApiButtonRelease: + timer.stop(); + sendKey(operationId, QEvent::KeyRelease); + repeatTimer.stop(); + break; + case ERemConCoreApiButtonClick: + if (timer.isActive()) { + timer.stop(); + sendKey(operationId, QEvent::KeyRelease); + } + sendKey(operationId, QEvent::KeyPress); + timer.start(); + repeatTimer.stop(); + break; + default: + break; + } + } + break; + default: + switch (buttonAction) { + case ERemConCoreApiButtonPress: + sendKey(operationId, QEvent::KeyPress); + repeatTimer.start(initialRepeatTime); + break; + case ERemConCoreApiButtonRelease: + sendKey(operationId, QEvent::KeyRelease); + repeatTimer.stop(); + break; + case ERemConCoreApiButtonClick: + sendKey(operationId, QEvent::KeyPress); + sendKey(operationId, QEvent::KeyRelease); + repeatTimer.stop(); + break; + default: + break; + } + break; + } + + if (handler) + handler->CompleteAnyKey(operationId); +} + +/* + * Sends volume up KeyRelease event + */ +void QS60KeyCapture::volumeUpClickTimerExpired() +{ + sendKey(ERemConCoreApiVolumeUp, QEvent::KeyRelease); +} + +/* + * Sends volume down KeyRelease event + */ +void QS60KeyCapture::volumeDownClickTimerExpired() +{ + sendKey(ERemConCoreApiVolumeDown, QEvent::KeyRelease); +} + +/* + * Repeats last key event + */ +void QS60KeyCapture::repeatTimerExpired() +{ + // set auto repeat flag on + repeatKeyEvent.iRepeats = 1; + coeEnv->SimulateKeyEventL(repeatKeyEvent, EEventKey); + repeatTimer.start(repeatTime); +} + +/* + * Maps RemCon operation id to key event (includes key code and scan codes) + */ +TKeyEvent QS60KeyCapture::mapToKeyEvent(TRemConCoreApiOperationId operationId) +{ + TKeyEvent keyEvent; + keyEvent.iModifiers = 0; + keyEvent.iRepeats = 0; + keyEvent.iCode = qt_keymapper_private()->mapS60RemConIdToS60Key(operationId); + keyEvent.iScanCode = qt_keymapper_private()->mapS60RemConIdToS60ScanCodes(operationId); + return keyEvent; +} + +/* + * Delivers key events to the application. + * RemCon operations are converted to simulated key events + */ +void QS60KeyCapture::sendKey(TRemConCoreApiOperationId operationId, QEvent::Type type) +{ + TKeyEvent keyEvent = mapToKeyEvent(operationId); + + if (type == QEvent::KeyPress) { + coeEnv->SimulateKeyEventL(keyEvent, EEventKeyDown); + coeEnv->SimulateKeyEventL(keyEvent, EEventKey); + // save the key press event for repeats + repeatKeyEvent = keyEvent; + } else { + coeEnv->SimulateKeyEventL(keyEvent, EEventKeyUp); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/s60framework/qs60keycapture_p.h b/src/gui/s60framework/qs60keycapture_p.h new file mode 100644 index 0000000..41cb765 --- /dev/null +++ b/src/gui/s60framework/qs60keycapture_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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 QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QS60KEYCAPTURE_P_H +#define QS60KEYCAPTURE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#ifdef Q_OS_SYMBIAN + +#include +#include +#include +#include +#include + +class CRemConInterfaceSelector; +class CRemConCoreApiTarget; +class CResponseHandler; +class CCoeEnv; + +QT_BEGIN_NAMESPACE + +class QS60KeyCapture: public QObject, + public MRemConCoreApiTargetObserver +{ + Q_OBJECT +public: + QS60KeyCapture(CCoeEnv *env, QObject *parent = 0); + ~QS60KeyCapture(); + + void MrccatoCommand(TRemConCoreApiOperationId operationId, + TRemConCoreApiButtonAction buttonAct); + +private slots: + void volumeUpClickTimerExpired(); + void volumeDownClickTimerExpired(); + void repeatTimerExpired(); + +private: + void sendKey(TRemConCoreApiOperationId operationId, QEvent::Type type); + TKeyEvent mapToKeyEvent(TRemConCoreApiOperationId operationId); + void initRemCon(); + +private: + QS60KeyCapture(); + Q_DISABLE_COPY(QS60KeyCapture) + + CCoeEnv *coeEnv; + + CRemConInterfaceSelector *selector; + CRemConCoreApiTarget *target; + CResponseHandler *handler; + + QTimer volumeUpClickTimer; + QTimer volumeDownClickTimer; + + TKeyEvent repeatKeyEvent; + int initialRepeatTime; // time before first repeat key event + int repeatTime; // time between subsequent repeat key events + QTimer repeatTimer; +}; + +QT_END_NAMESPACE + +#endif // Q_OS_SYMBIAN +#endif // QS60KEYCAPTURE_P_H diff --git a/src/gui/s60framework/qs60mainappui.cpp b/src/gui/s60framework/qs60mainappui.cpp index bfd1825..f120287 100644 --- a/src/gui/s60framework/qs60mainappui.cpp +++ b/src/gui/s60framework/qs60mainappui.cpp @@ -59,6 +59,7 @@ #include #include #include +#include "qs60keycapture_p.h" //Animated wallpapers in Qt applications are not supported. const TInt KAknDisableAnimationBackground = 0x02000000; @@ -66,6 +67,20 @@ const TInt KAknSingleClickCompatible = 0x01000000; QT_BEGIN_NAMESPACE +static QS60KeyCapture *qt_S60KeyCapture = 0; + +static void installS60KeyCapture(CCoeEnv *env) +{ + if (QApplication::testAttribute(Qt::AA_CaptureMultimediaKeys)) + qt_S60KeyCapture = new QS60KeyCapture(env); +} + +static void removeS60KeyCapture() +{ + delete qt_S60KeyCapture; + qt_S60KeyCapture = 0; +} + /*! \class QS60MainAppUi \since 4.6 @@ -127,6 +142,7 @@ void QS60MainAppUi::ConstructL() } #endif BaseConstructL(flags); + installS60KeyCapture(iCoeEnv); } /*! @@ -142,6 +158,7 @@ QS60MainAppUi::QS60MainAppUi() */ QS60MainAppUi::~QS60MainAppUi() { + removeS60KeyCapture(); } /*! diff --git a/src/gui/s60framework/s60framework.pri b/src/gui/s60framework/s60framework.pri index 19525b7..e30d2c0 100644 --- a/src/gui/s60framework/s60framework.pri +++ b/src/gui/s60framework/s60framework.pri @@ -1,10 +1,13 @@ SOURCES += s60framework/qs60mainapplication.cpp \ - s60framework/qs60mainappui.cpp \ - s60framework/qs60maindocument.cpp - -HEADERS += s60framework/qs60mainapplication_p.h \ - s60framework/qs60mainapplication.h \ - s60framework/qs60mainappui.h \ - s60framework/qs60maindocument.h - -!isEmpty(QT_LIBINFIX): DEFINES += QT_LIBINFIX_UNQUOTED=$$QT_LIBINFIX \ No newline at end of file + s60framework/qs60mainappui.cpp \ + s60framework/qs60maindocument.cpp \ + s60framework/qs60keycapture.cpp +HEADERS += qs60keycapture_p.h \ + s60framework/qs60mainapplication_p.h \ + s60framework/qs60mainapplication.h \ + s60framework/qs60mainappui.h \ + s60framework/qs60maindocument.h \ + s60framework/qs60keycapture_p.h +LIBS += -lremconcoreapi \ + -lremconinterfacebase +!isEmpty(QT_LIBINFIX):DEFINES += QT_LIBINFIX_UNQUOTED=$$QT_LIBINFIX diff --git a/tests/auto/qs60mainapplication/qs60mainapplication.pro b/tests/auto/qs60mainapplication/qs60mainapplication.pro index bbd6c30..de3f59d 100644 --- a/tests/auto/qs60mainapplication/qs60mainapplication.pro +++ b/tests/auto/qs60mainapplication/qs60mainapplication.pro @@ -2,3 +2,4 @@ load(qttest_p4) SOURCES += tst_qs60mainapplication.cpp symbian:LIBS += -lapparc -leikcore -lcone -lavkon +symbian:LIBS += -lremconcoreapi -lremconinterfacebase \ No newline at end of file diff --git a/tests/auto/qs60mainapplication/tst_qs60mainapplication.cpp b/tests/auto/qs60mainapplication/tst_qs60mainapplication.cpp index 069fd14..967ce4e 100644 --- a/tests/auto/qs60mainapplication/tst_qs60mainapplication.cpp +++ b/tests/auto/qs60mainapplication/tst_qs60mainapplication.cpp @@ -59,6 +59,8 @@ public slots: void cleanup(); private slots: void customQS60MainApplication(); + void testMultimediaKeys_data(); + void testMultimediaKeys(); }; void tst_QS60MainApplication::initTestCase() @@ -115,6 +117,201 @@ CApaApplication *factory() { return new (ELeave) CustomMainApplication; } + +#include +#include +#include +#include +#include + +class KeyGenerator : public QObject, + public MRemConCoreApiControllerObserver +{ + Q_OBJECT +public: + KeyGenerator(QObject *parent = 0); + ~KeyGenerator(); + void MrccacoResponse(TRemConCoreApiOperationId operationId, TInt error); + + void simulateKey(int qtKey); + +private: + void init(); + void cleanup(); + + CRemConInterfaceSelector *interfaceSelector; + CRemConCoreApiController *coreController; +}; + +KeyGenerator::KeyGenerator(QObject *parent) : QObject(parent) +{ + init(); +} + +KeyGenerator::~KeyGenerator() +{ + cleanup(); +} + +void KeyGenerator::MrccacoResponse(TRemConCoreApiOperationId operationId, TInt error) +{ + Q_UNUSED(operationId); + Q_UNUSED(error); +} + +/* + * Generates keyPress and keyRelease events for given key + */ +void KeyGenerator::simulateKey(int qtKey) +{ + if (!coreController) + return; + + TRemConCoreApiButtonAction action = ERemConCoreApiButtonClick; + TUint numRemotes = 0; + TRequestStatus status; + bool wait = true; + + switch (qtKey) { + // media keys + case Qt::Key_VolumeUp: + coreController->VolumeUp(status, numRemotes, action); + break; + case Qt::Key_VolumeDown: + coreController->VolumeDown(status, numRemotes, action); + break; + case Qt::Key_MediaStop: + coreController->Stop(status, numRemotes, action); + break; + case Qt::Key_MediaTogglePlayPause: + coreController->PausePlayFunction(status, numRemotes, action); + break; + case Qt::Key_MediaNext: + coreController->Forward(status, numRemotes, action); + break; + case Qt::Key_MediaPrevious: + coreController->Backward(status, numRemotes, action); + break; + case Qt::Key_AudioForward: + coreController->FastForward(status, numRemotes, action); + break; + case Qt::Key_AudioRewind: + coreController->Rewind(status, numRemotes, action); + break; + // accessory keys + case Qt::Key_Select: + coreController->Select(status, numRemotes, action); + break; + case Qt::Key_Enter: + coreController->Enter(status, numRemotes, action); + break; + case Qt::Key_PageUp: + coreController->PageUp(status, numRemotes, action); + break; + case Qt::Key_PageDown: + coreController->PageDown(status, numRemotes, action); + break; + case Qt::Key_Left: + coreController->Left(status, numRemotes, action); + break; + case Qt::Key_Right: + coreController->Right(status, numRemotes, action); + break; + case Qt::Key_Up: + coreController->Up(status, numRemotes, action); + break; + case Qt::Key_Down: + coreController->Down(status, numRemotes, action); + break; + case Qt::Key_Help: + coreController->Help(status, numRemotes, action); + break; + case Qt::Key_F1: + coreController->F1(status, numRemotes, action); + break; + case Qt::Key_F2: + coreController->F2(status, numRemotes, action); + break; + case Qt::Key_F3: + coreController->F3(status, numRemotes, action); + break; + case Qt::Key_F4: + coreController->F4(status, numRemotes, action); + break; + case Qt::Key_F5: + coreController->F5(status, numRemotes, action); + break; + default: + wait = false; + break; + } + + if (wait) + User::WaitForRequest(status); +} + +void KeyGenerator::init() +{ + try { + QT_TRAP_THROWING(interfaceSelector = CRemConInterfaceSelector::NewL()); + QT_TRAP_THROWING(coreController = CRemConCoreApiController::NewL(*interfaceSelector, *this)); + QT_TRAP_THROWING(interfaceSelector->OpenControllerL()); + } catch (const std::exception &e) { + cleanup(); + } +} + +void KeyGenerator::cleanup() +{ + delete interfaceSelector; + interfaceSelector = 0; + coreController = 0; +} + +const int keyEventTimeout = 2000; // 2secs + +class TestWidget : public QWidget +{ + Q_OBJECT +public: + TestWidget(QWidget *parent = 0); + ~TestWidget(); + +signals: + void keyPress(int key); + void keyRelease(int key); + +protected: + void keyPressEvent(QKeyEvent *event); + void keyReleaseEvent(QKeyEvent *event); + +private: + QTimer exitTimer; +}; + +TestWidget::TestWidget(QWidget *parent) : QWidget(parent) +{ + // quit if no events are received + exitTimer.setSingleShot(true); + exitTimer.start(keyEventTimeout); + connect(&exitTimer, SIGNAL(timeout()), qApp, SLOT(quit())); +} + +TestWidget::~TestWidget() +{ +} + +void TestWidget::keyPressEvent(QKeyEvent *event) +{ + emit keyPress(event->key()); +} + +void TestWidget::keyReleaseEvent(QKeyEvent *event) +{ + emit keyRelease(event->key()); + qApp->quit(); // test is done so quit immediately +} + #endif // Q_WS_S60 void tst_QS60MainApplication::customQS60MainApplication() @@ -129,5 +326,67 @@ void tst_QS60MainApplication::customQS60MainApplication() #endif } +void tst_QS60MainApplication::testMultimediaKeys_data() +{ + QTest::addColumn("key"); + + QTest::newRow("Key_VolumeUp") << (int)Qt::Key_VolumeUp; + QTest::newRow("Key_VolumeDown") << (int)Qt::Key_VolumeDown; + QTest::newRow("Key_MediaStop") << (int)Qt::Key_MediaStop; + QTest::newRow("Key_MediaTogglePlayPause") << (int)Qt::Key_MediaTogglePlayPause; + QTest::newRow("Key_MediaNext") << (int)Qt::Key_MediaNext; + QTest::newRow("Key_MediaPrevious") << (int)Qt::Key_MediaPrevious; + QTest::newRow("Key_AudioForward") << (int)Qt::Key_AudioForward; + QTest::newRow("Key_AudioRewind") << (int)Qt::Key_AudioRewind; + + QTest::newRow("Key_Select") << (int)Qt::Key_Select; + QTest::newRow("Key_Enter") << (int)Qt::Key_Enter; + QTest::newRow("Key_PageUp") << (int)Qt::Key_PageUp; + QTest::newRow("Key_PageDown") << (int)Qt::Key_PageDown; + QTest::newRow("Key_Left") << (int)Qt::Key_Left; + QTest::newRow("Key_Right") << (int)Qt::Key_Right; + QTest::newRow("Key_Up") << (int)Qt::Key_Up; + QTest::newRow("Key_Down") << (int)Qt::Key_Down; + QTest::newRow("Key_Help") << (int)Qt::Key_Help; + QTest::newRow("Key_F1") << (int)Qt::Key_F1; + QTest::newRow("Key_F2") << (int)Qt::Key_F2; + QTest::newRow("Key_F3") << (int)Qt::Key_F3; + QTest::newRow("Key_F4") << (int)Qt::Key_F4; + QTest::newRow("Key_F5") << (int)Qt::Key_F5; +} + +void tst_QS60MainApplication::testMultimediaKeys() +{ +#ifndef Q_WS_S60 + QSKIP("This is an S60-only test", SkipAll); +#elif __WINS__ + QSKIP("S60 emulator not supported", SkipAll); +#else + QApplication::setAttribute(Qt::AA_CaptureMultimediaKeys); + int argc = 1; + char *argv = "tst_qs60mainapplication"; + QApplication app(argc, &argv); + + QFETCH(int, key); + KeyGenerator keyGen; + keyGen.simulateKey(key); + + TestWidget widget; + QSignalSpy keyPressSpy(&widget, SIGNAL(keyPress(int))); + QSignalSpy keyReleaseSpy(&widget, SIGNAL(keyRelease(int))); + + widget.show(); + app.exec(); + + QCOMPARE(keyPressSpy.count(), 1); + QList arguments = keyPressSpy.takeFirst(); + QVERIFY(arguments.at(0).toInt() == key); + + QCOMPARE(keyReleaseSpy.count(), 1); + arguments = keyReleaseSpy.takeFirst(); + QVERIFY(arguments.at(0).toInt() == key); +#endif +} + QTEST_APPLESS_MAIN(tst_QS60MainApplication) #include "tst_qs60mainapplication.moc" -- cgit v0.12