How to build WM_KEYDOWN|UP LPARAM from a QKeyEvent?
-
I'm trying to replicate
QKeyEvents
sent on aQWidget
to another non Qt Window, for this I'm using the WINAPIPostMessage
togetherWM_KEYDOWN / WM_KEYUP
messagesBased on this stack answer
and Microsoft documentation of Keystroke Message FlagsI wrote this small compilable example:
// mainwindow.h QT_BEGIN_NAMESPACE namespace Ui { class MainWindowClass; }; QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); bool eventFilter(QObject* obj, QEvent* event); protected: virtual bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override; private: Ui::MainWindowClass *ui; }; // mainwindow.cpp #include "MainWindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindowClass()) { ui->setupUi(this); QTextEdit* textEdit = new QTextEdit(this); textEdit->installEventFilter(this); textEdit->setGeometry(50, 50, 200, 50); } bool MainWindow::eventFilter(QObject* obj, QEvent* event) { switch (event->type()) { case QEvent::KeyPress: case QEvent::KeyRelease: { QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); WPARAM wParam = keyEvent->nativeVirtualKey(); LPARAM lParam = 0; lParam |= (keyEvent->type() == QEvent::KeyPress) ? (0) : (1 << 31); // Transition state lParam |= (keyEvent->isAutoRepeat()) ? (1 << 30) : 0; // Previous key state lParam |= (keyEvent->modifiers() & Qt::ShiftModifier) ? (1 << 24) : 0; lParam |= (keyEvent->modifiers() & Qt::ControlModifier) ? (1 << 25) : 0; lParam |= (keyEvent->modifiers() & Qt::AltModifier) ? (1 << 26) : 0; lParam |= (keyEvent->modifiers() & Qt::MetaModifier) ? (1 << 27) : 0; bool isExtendedKey = (keyEvent->nativeScanCode() & 0x0100) != 0; // Extended key lParam |= (isExtendedKey ? (1 << 24) : 0); lParam |= (MapVirtualKey(wParam, MAPVK_VK_TO_VSC) << 16); // Scan code lParam |= (keyEvent->type() == QEvent::KeyPress) ? (1) : 0; // Repeat count qDebug() << (keyEvent->type() == QEvent::KeyPress ? "[[KEYDOWN]]" : "[[KEYUP]]") << "\nwParam" << wParam << "\nlParam" << lParam << "\n"; //PostMessage(hwnd, keyEvent->type() == QEvent::KeyPress ? WM_KEYDOWN : WM_KEYUP, wParam, lParam); } } return false; } bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr* result) { MSG* msg = static_cast<MSG*>(message); switch (msg->message) { case WM_KEYDOWN: case WM_KEYUP: { qDebug() << (msg->message == WM_KEYDOWN ? "KEYDOWN" : "KEYUP") << "\nwParam:" << msg->wParam << "\nlParam:" << msg->lParam << "\n";; break; } default: break; } return QWidget::nativeEvent(eventType, message, result); }
The
lParam
from thenativeEvent
is different from the ones i'm building inside theeventFilter
, trying to figure out what I'm calculating wrong. -
@Chris-Kawa Thank you, now the values are equal, i was getting the same issue for the
QWheelEvent
the value when delta.y < 0 was being different from the wParam ofWM_MOUSEWHEEL
case QEvent::Wheel: { QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event); // https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel int stateFlags = 0; if (wheelEvent->buttons() & Qt::LeftButton) stateFlags |= MK_LBUTTON; if (wheelEvent->buttons() & Qt::RightButton) stateFlags |= MK_RBUTTON; if (wheelEvent->modifiers() & Qt::ShiftModifier) stateFlags |= MK_SHIFT; if (wheelEvent->modifiers() & Qt::ControlModifier) stateFlags |= MK_CONTROL; qDebug() << "modifier:" << wheelEvent->modifiers(); qDebug() << "stateFlags:" << stateFlags; WPARAM wParam = 0; if (wheelEvent->angleDelta().y() > 0) wParam = (120 << 16) | (stateFlags & 0xffff); else if (wheelEvent->angleDelta().y() < 0) wParam = (-120ll << 16) | (stateFlags & 0xffff);
then i changed
wParam = (-120 << 16) | (stateFlags & 0xffff);
to
wParam = (-120ll << 16) | (stateFlags & 0xffff);
now the wParam match.I didn't understand this
ll
thing, based on these two examples i gave (wParam of wheel and lParam of KeyEvent) is it still possible to it construct wrong messages at sometime or is it correct now?A side question, why Qt use a different value for the key modifiers
Qt::ControlModifier
, etc, than Microsoft? it took a lot of time to figure out it.
Qt::ControlModifier0x04000000
while from MicrosoftMK_CONTROL
is0x0008
@Ylvy said:
I didn't understand this ll thing, based on these two examples i gave (wParam of wheel and lParam of KeyEvent) is it still possible to it construct wrong messages at sometime or is it correct now?
LPARAM
is a typedef for__int64
, which is Microsoft's extension type name for 64bit signed integers (same asint64_t
in modern C++), so when dealing with constants for it you should be using signed 64bit integers, for example1LL
. As @JonB said it stands forlong long int
, which is the old way of standard C++ to mean "at least 64 bit" (it is always 64 bit on 64bit Windows).WPARAM
is a typedef forunsigned __int64
, so when dealing with it you should be using unsigned 64bit integers, for example1uLL
, which stands forunsigned long long int
.The type of
(1<<31)
expression isint
, since all operands are int and that is a 32bit signed integer on Windows. When using it in expression likelParam | (1 << 31)
it gets upcasted to 64bit type, because the left operand is a signed 64bit value.(1<<31)
is a negative 32bit signed value, so when upcasted to 64bit you get11111...
at the extended part of the 64bit value (C++ uses Two's complement binary representation), which caused your problem. A type of(1LL << 31)
expression islong long
, since the left operand is along long
constant. It already is a 64bit value with00000....
on the first 32 bits, so there's no upcasing needed when you | it with lParam.Btw. keep in mind that what I said is about 64bit Windows. If you target 32bit Windows too then LPARAM and WPARAM are typedefs for 32bit types
long
andunsigned int
, so use 32bit constants there.A side question, why Qt use a different value for the key modifiers Qt::ControlModifier, etc, than Microsoft?
Why should it be the same as Microsoft? Qt is a cross-platform framework with roots in Linux. I don't see any reason why it should use the same values for private implementation as any particular vendor, not just Microsoft.
-
I see a couple problems:
-
Repeat count should always be 1 for key releases and >=1 for key presses. It's more than 1 when app is slow to respond and Windows compresses auto repeat messages into single message. For purposes of synthesizing this message you should always put 1 there and Windows will compress them if it feels like it.
-
Previous key state should be 1 for autorepeat and key release.
-
ALT key should be encoded in the (1 << 29) bit (Context Code).
-
You've got two flags in bit (1<<24). It should only hold the extended key flag.
-
I don't see where you got these bit numbers 24-27 from, but that's not the right way to handle these modifiers. The state of the SHIFT, ALT and CTRL keys is a bit tricky, as they can get incorporated into the scan code. Also there's left and right keys for those, which Qt unfortunately doesn't differentiate. You'll have to work them out from the scan code and extended key flag.
-
-
I see a couple problems:
-
Repeat count should always be 1 for key releases and >=1 for key presses. It's more than 1 when app is slow to respond and Windows compresses auto repeat messages into single message. For purposes of synthesizing this message you should always put 1 there and Windows will compress them if it feels like it.
-
Previous key state should be 1 for autorepeat and key release.
-
ALT key should be encoded in the (1 << 29) bit (Context Code).
-
You've got two flags in bit (1<<24). It should only hold the extended key flag.
-
I don't see where you got these bit numbers 24-27 from, but that's not the right way to handle these modifiers. The state of the SHIFT, ALT and CTRL keys is a bit tricky, as they can get incorporated into the scan code. Also there's left and right keys for those, which Qt unfortunately doesn't differentiate. You'll have to work them out from the scan code and extended key flag.
I fixed the things you said, im testing hitting "a" in the
QTextEdit
, this is the values i get in qDebugKEYDOWN wParam 65 lParam 1966081 scanCode: 30 [[KEYDOWN]] wParam 65 lParam 1966081 scanCode 30 KEYUP wParam 65 lParam 3223191553 scanCode: 30 [[KEYUP]] wParam 65 lParam -1071775743 scanCode 30
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); WPARAM wParam = keyEvent->nativeVirtualKey(); bool keyRelease = keyEvent->type() == QEvent::KeyRelease; LPARAM lParam = 0; lParam |= (keyRelease) ? (1 << 31): (0); // Transition state lParam |= (keyEvent->isAutoRepeat() || keyRelease) ? (1 << 30) : 0; // Previous key state lParam |= (keyEvent->modifiers() & Qt::AltModifier) ? (1 << 29) : 0; // Context code bool isExtendedKey = (keyEvent->nativeScanCode() & 0x0100) != 0; // Extended key lParam |= (isExtendedKey ? (1 << 24) : 0); lParam |= (keyEvent->nativeScanCode() << 16); // Scan code lParam |= 1; // Repeat count // Just for debugging: WORD keyFlags = HIWORD(lParam); WORD scanCode = LOBYTE(keyFlags); // scan code qDebug() << (keyRelease ? "[[KEYUP]]" : "[[KEYDOWN]]") << "\nwParam" << wParam << "lParam" << lParam << "\nscanCode" << scanCode << "\n";
Apparently now, its different only when its the keyRelease.
-
-
I fixed the things you said, im testing hitting "a" in the
QTextEdit
, this is the values i get in qDebugKEYDOWN wParam 65 lParam 1966081 scanCode: 30 [[KEYDOWN]] wParam 65 lParam 1966081 scanCode 30 KEYUP wParam 65 lParam 3223191553 scanCode: 30 [[KEYUP]] wParam 65 lParam -1071775743 scanCode 30
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); WPARAM wParam = keyEvent->nativeVirtualKey(); bool keyRelease = keyEvent->type() == QEvent::KeyRelease; LPARAM lParam = 0; lParam |= (keyRelease) ? (1 << 31): (0); // Transition state lParam |= (keyEvent->isAutoRepeat() || keyRelease) ? (1 << 30) : 0; // Previous key state lParam |= (keyEvent->modifiers() & Qt::AltModifier) ? (1 << 29) : 0; // Context code bool isExtendedKey = (keyEvent->nativeScanCode() & 0x0100) != 0; // Extended key lParam |= (isExtendedKey ? (1 << 24) : 0); lParam |= (keyEvent->nativeScanCode() << 16); // Scan code lParam |= 1; // Repeat count // Just for debugging: WORD keyFlags = HIWORD(lParam); WORD scanCode = LOBYTE(keyFlags); // scan code qDebug() << (keyRelease ? "[[KEYUP]]" : "[[KEYDOWN]]") << "\nwParam" << wParam << "lParam" << lParam << "\nscanCode" << scanCode << "\n";
Apparently now, its different only when its the keyRelease.
@Ylvy Oh, right. It's just a silly casting issue. It's because LPARAM is int64 and (1 << 31) is int32 value of -1, so it gets sign extended to 64 bits when converting to LPARAM for the | operator. Simply use the 64 bit type for 1, so
lParam |= (keyRelease) ? (1ll << 31): (0);
that's 1 followed by two lowercase L.
-
@Ylvy Oh, right. It's just a silly casting issue. It's because LPARAM is int64 and (1 << 31) is int32 value of -1, so it gets sign extended to 64 bits when converting to LPARAM for the | operator. Simply use the 64 bit type for 1, so
lParam |= (keyRelease) ? (1ll << 31): (0);
that's 1 followed by two lowercase L.
@Chris-Kawa Thank you, now the values are equal, i was getting the same issue for the
QWheelEvent
the value when delta.y < 0 was being different from the wParam ofWM_MOUSEWHEEL
case QEvent::Wheel: { QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event); // https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel int stateFlags = 0; if (wheelEvent->buttons() & Qt::LeftButton) stateFlags |= MK_LBUTTON; if (wheelEvent->buttons() & Qt::RightButton) stateFlags |= MK_RBUTTON; if (wheelEvent->modifiers() & Qt::ShiftModifier) stateFlags |= MK_SHIFT; if (wheelEvent->modifiers() & Qt::ControlModifier) stateFlags |= MK_CONTROL; qDebug() << "modifier:" << wheelEvent->modifiers(); qDebug() << "stateFlags:" << stateFlags; WPARAM wParam = 0; if (wheelEvent->angleDelta().y() > 0) wParam = (120 << 16) | (stateFlags & 0xffff); else if (wheelEvent->angleDelta().y() < 0) wParam = (-120ll << 16) | (stateFlags & 0xffff);
then i changed
wParam = (-120 << 16) | (stateFlags & 0xffff);
to
wParam = (-120ll << 16) | (stateFlags & 0xffff);
now the wParam match.I didn't understand this
ll
thing, based on these two examples i gave (wParam of wheel and lParam of KeyEvent) is it still possible to it construct wrong messages at sometime or is it correct now?A side question, why Qt use a different value for the key modifiers
Qt::ControlModifier
, etc, than Microsoft? it took a lot of time to figure out it.
Qt::ControlModifier0x04000000
while from MicrosoftMK_CONTROL
is0x0008
-
@Chris-Kawa Thank you, now the values are equal, i was getting the same issue for the
QWheelEvent
the value when delta.y < 0 was being different from the wParam ofWM_MOUSEWHEEL
case QEvent::Wheel: { QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event); // https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel int stateFlags = 0; if (wheelEvent->buttons() & Qt::LeftButton) stateFlags |= MK_LBUTTON; if (wheelEvent->buttons() & Qt::RightButton) stateFlags |= MK_RBUTTON; if (wheelEvent->modifiers() & Qt::ShiftModifier) stateFlags |= MK_SHIFT; if (wheelEvent->modifiers() & Qt::ControlModifier) stateFlags |= MK_CONTROL; qDebug() << "modifier:" << wheelEvent->modifiers(); qDebug() << "stateFlags:" << stateFlags; WPARAM wParam = 0; if (wheelEvent->angleDelta().y() > 0) wParam = (120 << 16) | (stateFlags & 0xffff); else if (wheelEvent->angleDelta().y() < 0) wParam = (-120ll << 16) | (stateFlags & 0xffff);
then i changed
wParam = (-120 << 16) | (stateFlags & 0xffff);
to
wParam = (-120ll << 16) | (stateFlags & 0xffff);
now the wParam match.I didn't understand this
ll
thing, based on these two examples i gave (wParam of wheel and lParam of KeyEvent) is it still possible to it construct wrong messages at sometime or is it correct now?A side question, why Qt use a different value for the key modifiers
Qt::ControlModifier
, etc, than Microsoft? it took a lot of time to figure out it.
Qt::ControlModifier0x04000000
while from MicrosoftMK_CONTROL
is0x0008
@Ylvy said in How to build WM_KEYDOWN|UP LPARAM from a QKeyEvent?:
I didn't understand this ll thing
1ll
is just a way of writing a constant in C++. The constant's value is1
. Thell
suffix stands forlong-long
, which in practice means make it a 64-bit number. (You can also write it with uppercase letter, as1LL
, which personally I prefer.) You could also write it as(long long int) 1
or(int64) 1
. So the value written in hex is0x0000000000000001
. @Chris-Kawa explained why a 64-bit rather than a standard 32-bitint
is required here for working withLPARAM
. -
@Chris-Kawa Thank you, now the values are equal, i was getting the same issue for the
QWheelEvent
the value when delta.y < 0 was being different from the wParam ofWM_MOUSEWHEEL
case QEvent::Wheel: { QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event); // https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel int stateFlags = 0; if (wheelEvent->buttons() & Qt::LeftButton) stateFlags |= MK_LBUTTON; if (wheelEvent->buttons() & Qt::RightButton) stateFlags |= MK_RBUTTON; if (wheelEvent->modifiers() & Qt::ShiftModifier) stateFlags |= MK_SHIFT; if (wheelEvent->modifiers() & Qt::ControlModifier) stateFlags |= MK_CONTROL; qDebug() << "modifier:" << wheelEvent->modifiers(); qDebug() << "stateFlags:" << stateFlags; WPARAM wParam = 0; if (wheelEvent->angleDelta().y() > 0) wParam = (120 << 16) | (stateFlags & 0xffff); else if (wheelEvent->angleDelta().y() < 0) wParam = (-120ll << 16) | (stateFlags & 0xffff);
then i changed
wParam = (-120 << 16) | (stateFlags & 0xffff);
to
wParam = (-120ll << 16) | (stateFlags & 0xffff);
now the wParam match.I didn't understand this
ll
thing, based on these two examples i gave (wParam of wheel and lParam of KeyEvent) is it still possible to it construct wrong messages at sometime or is it correct now?A side question, why Qt use a different value for the key modifiers
Qt::ControlModifier
, etc, than Microsoft? it took a lot of time to figure out it.
Qt::ControlModifier0x04000000
while from MicrosoftMK_CONTROL
is0x0008
@Ylvy said:
I didn't understand this ll thing, based on these two examples i gave (wParam of wheel and lParam of KeyEvent) is it still possible to it construct wrong messages at sometime or is it correct now?
LPARAM
is a typedef for__int64
, which is Microsoft's extension type name for 64bit signed integers (same asint64_t
in modern C++), so when dealing with constants for it you should be using signed 64bit integers, for example1LL
. As @JonB said it stands forlong long int
, which is the old way of standard C++ to mean "at least 64 bit" (it is always 64 bit on 64bit Windows).WPARAM
is a typedef forunsigned __int64
, so when dealing with it you should be using unsigned 64bit integers, for example1uLL
, which stands forunsigned long long int
.The type of
(1<<31)
expression isint
, since all operands are int and that is a 32bit signed integer on Windows. When using it in expression likelParam | (1 << 31)
it gets upcasted to 64bit type, because the left operand is a signed 64bit value.(1<<31)
is a negative 32bit signed value, so when upcasted to 64bit you get11111...
at the extended part of the 64bit value (C++ uses Two's complement binary representation), which caused your problem. A type of(1LL << 31)
expression islong long
, since the left operand is along long
constant. It already is a 64bit value with00000....
on the first 32 bits, so there's no upcasing needed when you | it with lParam.Btw. keep in mind that what I said is about 64bit Windows. If you target 32bit Windows too then LPARAM and WPARAM are typedefs for 32bit types
long
andunsigned int
, so use 32bit constants there.A side question, why Qt use a different value for the key modifiers Qt::ControlModifier, etc, than Microsoft?
Why should it be the same as Microsoft? Qt is a cross-platform framework with roots in Linux. I don't see any reason why it should use the same values for private implementation as any particular vendor, not just Microsoft.
-