Saturday 5 February 2011

Using the 3DConnexion mouse with a Qt Application

This note describes my experiences getting a 3D mouse to work in a Qt application on Windows.

The 3DConnexion 3D mouse is a 6-DOF controller often used for CAD and engineering applications. 3DConnexion provide an SDK for windows which provides integration with MFC applications but not for Qt.


RAWINPUT support for Qt

The recommended method of supporting the 3DConnexion 3D mouse is to use Windows RAWINPUT message to receive the data from the mouse and then process those message to get the motion data.

There are 2 problems here

  • Qt has no native support for RAWINPUT - it is a windows only system and Qt has no corresponding Qt events for the 3D mouse
  • The "out-of-the-box" version of Qt does not forward RAWINPUT messages as they are received - at least up to Qt 4.6.2. On windows 7 Qt4.7.1 seemed to work with no modification.

The Raw Input API provides a "stable and robust way for applications to accept raw input from any HID, including the keyboard and mouse." [msdn] This enables the application to get the actual data sent by the HID device without any processing being done by windows first.

The application first registers to receive Raw Input from a particular device type, then when that device has data the application receives a WM_INPUT message in its event queue and the queue status flag QS_RAWINPUT is set. The application can then retrieve the data using the Raw Input API.

Raw Input was added in Windows XP, so is not available in Windows 2000
(Which is important -- see below.)

Raw Input and Qt

The problem with Qt (at least in 4.6.2) is that the version from the installer does not process Raw Input messages correctly. The way of collecting these the Raw Input messages is to set an event filter on the QApplication which gets the native windows messages. These can then be processed by the application using the Windows Raw Input API. However, the WM_INPUT messages are not being received by window/event filter the until some other event such as a mouse or timer event occurrs. Moving the 3D mouse causes no events to be received by the event filter but then if the normal mouse is moved (or key pressed etc)
all(?) the 3D mouse events arrive.

The problem can be traced to QEventDispatcherWin32?, which maps the windows messages onto Qt Events. Specifically, the line

   MsgWaitForMultipleObjectsEX(nCount, pHandles, INFINITE, QS_ALLINPUT,MWMO_ALERTABLE);

in the processEvents function of QEventDispatcherWin32. This is blocking and not waking on WM_INPUT events.

The windows documentation for this notes that QS_ALLINPUT, which includes QS_INPUT, “does not include QS_RAWINPUT in Windows2000.” Therefore, the problem is that the binary Qt installer is built to work on all windows platforms, so during the compile _WIN32_WINNT is defined to something less
that WinXP? (0×501) so that raw-input messages are not handled.

The fix is to re-compile Qt passing -D _WIN32_WINNT=0×501 to configure so that the WM_INPUT messages are correctly passed to the event filter when the 3D mouse is moved.

So this is not really a Qt bug, but rather that Qt is compiled for an "earlier" Windows platform which does not support Raw Input.

Sample Code for Qt and the 3D Mouse

I updated the 3DConennexion code for MFC to work with Qt. It uses an event filter to pick up the raw windows events then extracts the 3D Mouse information. This is then available as a Qt signal.

The zip file below contains a very simple application which displays the motion data in a window. This was tested with Qt 4.7.1.

3DMouse.zip

17 comments:

  1. Thanks David, it worked right out of the box. I didn't find any copyright message on it, assuming that it's OK for people to use, could you post a sentence explicitly stating there are no restrictions on using the code?

    ReplyDelete
  2. Hi Ben,
    I am glad that it was useful. I am happy for you to do whatever you like with the code. However, it was heavily based on the original 3D Connexion SDK which just supported MFC/ATL applications. That SDK is freely available (http://www.3dconnexion.com/service/software-developer.html) but has its own license agreement.

    ReplyDelete
  3. Hi David, first of all, thanks very much for posting the 3D Mouse code with Qt. I got this to work out of the box on my Win 7 x64 PC using Qt 4.8.1 for Desktop using MSVC2010 Release version as well as using Qt 4.8.0 (with MSVC 2010). I did get some errors using Mingw as a GCC for Windows on all Qt versions I tried it with (4.7.4, 4.8.0, 4.8.1), such as "'NEXTRAWINPUTBLOCK' was not declared in this scope" in Mouse3DInput.cpp - I'm trying to dig into a fix, but if you have experienced something similar, I'm all ears. :-) Again, thanks so much for posting this code.

    ReplyDelete
  4. Hi Tom, I am afraid that I only tried compiling this with MSVC. I did not try with Mingw, however, since the code uses some Microsoft specific headers I am not surprised that you get some errors from Mingw.

    ReplyDelete
    Replies
    1. David, first of all thank you for sharing!

      I just got it going under QT5.15.1 and MinGW8. Runs like a dream and I can sleep again ;)

      Cheers!

      Delete
    2. Might I trouble you to drop the updated 5.15.1 files into a repo or place to grab? Thanks!

      Delete
  5. Hi,

    I would like to thank you for this. I have managed to implement this in a FOSS project called FreeCAD. It is released under LGPL, and each file has license info, so maybe you can help us out with this files. We can add you as original author (and/or your email and/or this address), but we need your permission to do that.

    Also, maybe you can generally help us out with license on this files, since I have no idea what to do:

    https://sourceforge.net/apps/phpbb/free-cad/viewtopic.php?f=10&t=2785

    Thanks again and Best Regards,
    Petar

    ReplyDelete
  6. Hi David,

    Thanks a lot for your post.

    Although it's been a while since you posted it, I'm trying to make it work for Qt5, but since I'm a Qt newbie I cannot get it to work.

    I found out that QCoreApplication::setEventFilter() is replaced in Qt5 by QCoreApplication::installNativeEventFilter().

    So I created the class MyRawInputEventFilter and there I reimplemented the method nativeEventFilter with the same code in your Mouse3DInput::RawInputEventFilter. Is it that a correct approach?

    However the program always crashes delivering the following output:

    ASSERT: "!eventDispatcher" in file kernel\qguiapplication.cpp, line 1191
    Invalid parameter passed to C runtime function.

    Any help would be much appreciated.

    Thanks again and regards,
    Francisco

    ReplyDelete
    Replies
    1. When we updated to Qt5 some time ago I don't remember any particular problems. You are right that you need to change to a native event filter.

      The event filter looks something like below. The gMouseInput is a static variable for handling the input.

      I think this is the same as the previous event filter. As you said this post is from some time ago and I haven't looked at this code in some time.

      bool 3DMouseInput::RawInputEventFilter(void* msg, long* result)

      {

      if (gMouseInput == 0) return false;
      MSG* message = (MSG*)(msg);

      if (message->message == WM_INPUT) {
      HRAWINPUT hRawInput = reinterpret_cast(message->lParam);
      gMouseInput->OnRawInput(RIM_INPUT,hRawInput);
      if (result != 0) {
      *result = 0;
      }
      return true;
      }
      return false;
      }



      bool 3DMouseInput::nativeEventFilter(const QByteArray& eventType, void* message, long* result)

      {
      return RawInputEventFilter(message, result);
      }

      Delete
    2. Hey David do I understand correctly that this will work only under win? I'd be interested in any pointers/ideas how to have this run crossplatform

      Delete
    3. I have not got around to doing it yet but it should be possible on Linux using the spacenav driver (http://spacenav.sourceforge.net/).

      FreeCAD seems to have implemented Linux support in this way (https://www.freecadweb.org/wiki/3Dconnexion_input_devices)

      I am not sure it is possible to have a single cross platform solution that works on all platforms, although I haven't looked at the official drivers/SDK in a long time.

      Delete
  7. Thank you David for your work. I could make a few minor adjustments to make it work under QT5 and also with newer 3D-Connexion mice, as these no longer have the Logitech Vendor-ID (046D), but a new one (256F).
    I extended the e3dconnexion_pid type and also derived the whole Mouse3DInput class directly from QAbstractNativeEventFilter, thus the registration / unregistration work simply from the constructor and destructor, respectively:

    Mouse3DInput::Mouse3DInput(QWidget* widget) :
    QObject(widget)
    {
    fLast3dmouseInputTime = 0;

    InitializeRawInput((HWND)widget->winId());
    QApplication::instance()->installNativeEventFilter(this);
    }


    Mouse3DInput::~Mouse3DInput()
    {
    QApplication::instance()->removeNativeEventFilter(this);
    }


    The nativeEventFilter is identical to your static RawInputEventFilter function:

    bool Mouse3DInput::nativeEventFilter(const QByteArray &eventType, void* msg, long* result)
    {
    MSG* message = (MSG*)(msg);

    if (message->message == WM_INPUT)
    {
    HRAWINPUT hRawInput = reinterpret_cast(message->lParam);
    OnRawInput(RIM_INPUT, hRawInput);
    if (result != 0)
    {
    result = 0;
    }
    return true;
    }

    return false;
    }


    I can share the modified Mouse3DInput.h and Mouse3DInput.cpp here, if you allow me to upload them somehow.

    ReplyDelete
  8. I forgot to include the nem enum values:

    enum e3dconnexion_pid
    {
    // Logitech 3Dconnexion devices (old)
    eSpacemousePlusXT = 0xc603,
    eCADman = 0xc605,
    eSpacemouseClassic = 0xc606,
    eSpaceball5000 = 0xc621,
    eSpaceTraveller = 0xc623,
    eSpacePilot = 0xc625,
    eSpaceNavigator = 0xc626,
    eSpaceExplorer = 0xc627,
    eSpaceNavigatorforNotebooks = 0xc628,
    eSpacePilotPro = 0xc629,
    eSpaceMousePro = 0xc62b,

    // 3Dconnexion devices (new)
    eSpaceMouseWirelessCabled = 0xc62e,
    eSpaceMouseWirelessReceiver = 0xc62f,
    eSpaceMouseProWirelessCabled = 0xc631,
    eSpaceMouseProWirelessReceiver = 0xc632,
    eSpaceMouseEnterprise = 0xc633,
    eSpaceMouseCompact = 0xc635,
    eCadMouseWireless = 0xc651,
    eUniversalReceiver = 0xc652,
    eCadMouseProWireless = 0xc654,
    eCadMouseProWirelessLeft = 0xc657
    };

    Bye: Tamas

    ReplyDelete
  9. Hello!

    I am using now a wireless version of the SpaceMouse (2021) and now have a problem with the evaluation of keystate-change input signals. Within TranslateRawInputData I receive the 0x03 message telling that a button was pressed, but the read value of dwKeyState is always 0 here:

    unsigned long dwKeystate = *reinterpret_cast(&pRawInput->data.hid.bRawData[1]);

    Do you have an idea what causes this problem?
    The same code works well using a SpaceNavigator (the predecessor of SpaceMouse).

    Thanks,

    NewtoM

    ReplyDelete
  10. Hi,

    How do I get which buttons are available for which device model for SpaceMouse?
    How do I know what model the device is connected and how or where can i find information on which buttons are available for what model?

    ReplyDelete
  11. hi @David Dibben
    in the source you keep mentioning about "see Programming for the 3D Mouse" but I cant seem to find this book, article or anything with that title, do you have a copy of it and can you share?

    ReplyDelete
  12. Hi David,

    I see you've mentioned it previously to someone else but I was wondering about licensing on this code.

    I know a part was taken from the 3DConnexion SDK and I have permission to use this in my product but I wanted to know if there were any other restrictions on using your code.

    Thank you.

    ReplyDelete