Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. How to build WM_KEYDOWN|UP LPARAM from a QKeyEvent?
QtWS25 Last Chance

How to build WM_KEYDOWN|UP LPARAM from a QKeyEvent?

Scheduled Pinned Locked Moved Solved General and Desktop
7 Posts 3 Posters 1.2k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • Y Offline
    Y Offline
    Ylvy
    wrote on 22 Apr 2023, 16:08 last edited by Ylvy
    #1

    I'm trying to replicate QKeyEvents sent on a QWidget to another non Qt Window, for this I'm using the WINAPI PostMessage together WM_KEYDOWN / WM_KEYUP messages

    Based on this stack answer
    and Microsoft documentation of Keystroke Message Flags

    I 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 the nativeEvent is different from the ones i'm building inside the eventFilter, trying to figure out what I'm calculating wrong.

    1 Reply Last reply
    0
    • Y Ylvy
      22 Apr 2023, 23:26

      @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 of WM_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::ControlModifier 0x04000000 while from Microsoft MK_CONTROL is 0x0008

      C Offline
      C Offline
      Chris Kawa
      Lifetime Qt Champion
      wrote on 23 Apr 2023, 07:34 last edited by Chris Kawa
      #7

      @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 as int64_t in modern C++), so when dealing with constants for it you should be using signed 64bit integers, for example 1LL. As @JonB said it stands for long 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 for unsigned __int64, so when dealing with it you should be using unsigned 64bit integers, for example 1uLL, which stands for unsigned long long int.

      The type of (1<<31) expression is int, since all operands are int and that is a 32bit signed integer on Windows. When using it in expression like lParam | (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 get 11111... 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 is long long, since the left operand is a long long constant. It already is a 64bit value with 00000.... 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 and unsigned 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.

      1 Reply Last reply
      1
      • C Offline
        C Offline
        Chris Kawa
        Lifetime Qt Champion
        wrote on 22 Apr 2023, 18:57 last edited by Chris Kawa
        #2

        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.

        Y 1 Reply Last reply 22 Apr 2023, 19:59
        1
        • C Chris Kawa
          22 Apr 2023, 18:57

          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.

          Y Offline
          Y Offline
          Ylvy
          wrote on 22 Apr 2023, 19:59 last edited by Ylvy
          #3

          @Chris-Kawa

          I fixed the things you said, im testing hitting "a" in the QTextEdit, this is the values i get in qDebug

          KEYDOWN 
          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.

          C 1 Reply Last reply 22 Apr 2023, 20:36
          1
          • Y Ylvy
            22 Apr 2023, 19:59

            @Chris-Kawa

            I fixed the things you said, im testing hitting "a" in the QTextEdit, this is the values i get in qDebug

            KEYDOWN 
            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.

            C Offline
            C Offline
            Chris Kawa
            Lifetime Qt Champion
            wrote on 22 Apr 2023, 20:36 last edited by Chris Kawa
            #4

            @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.

            Y 1 Reply Last reply 22 Apr 2023, 23:26
            0
            • C Chris Kawa
              22 Apr 2023, 20:36

              @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.

              Y Offline
              Y Offline
              Ylvy
              wrote on 22 Apr 2023, 23:26 last edited by Ylvy
              #5

              @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 of WM_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::ControlModifier 0x04000000 while from Microsoft MK_CONTROL is 0x0008

              JonBJ C 2 Replies Last reply 23 Apr 2023, 06:49
              0
              • Y Ylvy
                22 Apr 2023, 23:26

                @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 of WM_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::ControlModifier 0x04000000 while from Microsoft MK_CONTROL is 0x0008

                JonBJ Offline
                JonBJ Offline
                JonB
                wrote on 23 Apr 2023, 06:49 last edited by
                #6

                @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 is 1. The ll suffix stands for long-long, which in practice means make it a 64-bit number. (You can also write it with uppercase letter, as 1LL, which personally I prefer.) You could also write it as (long long int) 1 or (int64) 1. So the value written in hex is 0x0000000000000001. @Chris-Kawa explained why a 64-bit rather than a standard 32-bit int is required here for working with LPARAM.

                1 Reply Last reply
                1
                • Y Ylvy
                  22 Apr 2023, 23:26

                  @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 of WM_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::ControlModifier 0x04000000 while from Microsoft MK_CONTROL is 0x0008

                  C Offline
                  C Offline
                  Chris Kawa
                  Lifetime Qt Champion
                  wrote on 23 Apr 2023, 07:34 last edited by Chris Kawa
                  #7

                  @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 as int64_t in modern C++), so when dealing with constants for it you should be using signed 64bit integers, for example 1LL. As @JonB said it stands for long 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 for unsigned __int64, so when dealing with it you should be using unsigned 64bit integers, for example 1uLL, which stands for unsigned long long int.

                  The type of (1<<31) expression is int, since all operands are int and that is a 32bit signed integer on Windows. When using it in expression like lParam | (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 get 11111... 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 is long long, since the left operand is a long long constant. It already is a 64bit value with 00000.... 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 and unsigned 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.

                  1 Reply Last reply
                  1
                  • Y Ylvy has marked this topic as solved on 23 Apr 2023, 17:04

                  1/7

                  22 Apr 2023, 16:08

                  • Login

                  • Login or register to search.
                  1 out of 7
                  • First post
                    1/7
                    Last post
                  0
                  • Categories
                  • Recent
                  • Tags
                  • Popular
                  • Users
                  • Groups
                  • Search
                  • Get Qt Extensions
                  • Unsolved