Reputation: 70000
Here the requirement is just to check if any key was pressed or any mouse click or movement has happened. Capturing other specific details is not required.
I am fine to do every 1 second polling as well to see if any mouse or key event happened.
Will QAbstractNativeEventFilter
be of any help?
Any other platform independent C++ library also will be useful.
Below is a sample code to capture mouse & keyboard events only when the app is in focus:
#include<QApplication>
#include<QDebug>
#include<QKeyEvent>
#include<QWidget>
struct Widget : public QWidget
{
Widget ()
{
installEventFilter(this);
grabKeyboard();
grabMouse();
setMouseTracking(true);
}
~Widget () { qDebug() << "~Event()"; }
bool eventFilter (QObject* const pObject,
QEvent* const pEvent) override
{
qDebug() << "Event: " << pEvent->type();
if(pEvent->type() == QEvent::KeyPress)
{
QKeyEvent* const pKeyEvent = static_cast<QKeyEvent*>(pEvent);
qDebug() << "Key event: " << pKeyEvent->key();
}
return false; //QObject::eventFilter(pObject, pEvent);
}
};
int main (int argc, char *argv[])
{
QApplication application(argc, argv);
Widget widget;
widget.show();
return application.exec();
}
Upvotes: 1
Views: 2983
Reputation: 70000
Have written minimal working code for the 3 platforms below. Using Qt is optional.
#include<iostream>
#include<thread>
#include<chrono>
struct Activity
{
Activity ();
};
// You may need to put Qt equivalent part here; The core logic is after main()
#include<QGuiApplication>
int
main (int argc, char *argv[])
{ // Qt specific; but it can be anything here
QGuiApplication application(argc, argv);
Activity activity;
return application.exec();
}
#ifdef __linux__ // add in .pro file "linux: QT += x11extras" and "linux: LIBS += -lX11 -lXtst"
#include<X11/Xlib.h>
#include<X11/extensions/record.h>
#define CHECK(EVENT) if(*pDatum == EVENT) std::cout << #EVENT
void Handle (XPointer, XRecordInterceptData *pRecord)
{
using XRecordDatum = char;
std::cout << pRecord->category << "---" << pRecord->data;
if(auto* const pDatum = reinterpret_cast<XRecordDatum*>(pRecord->data))
{ CHECK(KeyPress); else CHECK(KeyRelease); else CHECK(ButtonPress); else CHECK(ButtonRelease); }
::XRecordFreeData(pRecord);
}
Activity::Activity ()
{
if(auto* const pDisplay = XOpenDisplay(nullptr))
{
XRecordClientSpec clients = XRecordAllClients;
auto* pRange = ::XRecordAllocRange();
pRange->device_events = XRecordRange8{KeyPress, ButtonRelease};
auto context = ::XRecordCreateContext(pDisplay, 0, &clients, 1, &pRange, 1);
::XRecordEnableContextAsync(pDisplay, context, Handle, nullptr); // use with/without `...Async()`
// ::XRecordProcessReplies(pDisplay);
::XFlush(pDisplay);
::XSync(pDisplay, true);
}
// Use below functions by putting variables in global scope to stop capturing
// Also refer: https://stackoverflow.com/questions/69711608/why-xrecorddisablecontext-is-not-working
// ::XRecordDisableContext(pDisplay, context);
// ::XRecordFreeContext(pDisplay, context);
// ::XFree(pRange);
}
#endif
#ifdef WIN32
#include<Windows.h> // add in .pro file "win32: LIBS += -luser32"
HHOOK sHookKeyboard, sHookMouse;
Activity::Activity ()
{
sHookKeyboard = ::SetWindowsHookExA(WH_KEYBOARD_LL,
[] (int i, WPARAM w, LPARAM l)
{ std::cout << i << w << l; return ::CallNextHookEx(sHookKeyboard, i, w, l); },
0, 0);
sHookMouse = ::SetWindowsHookExA(WH_MOUSE_LL,
[] (int i, WPARAM w, LPARAM l)
{ std::cout << i << w << l; return ::CallNextHookEx(sHookMouse, i, w, l); },
0, 0);
}
#endif
#ifdef __APPLE__ // add in .pro file "mac: LIBS += -framework ApplicationServices"
#include<ApplicationServices/ApplicationServices.h>
// Add binary (& if you are it via running Qt, then that too) into the "privacy" settings of the ...
// ... Mac system; System Preferences > Privacy settings; Without this the code will not work
// https://developer.apple.com/forums/thread/109283
// https://stackoverflow.com/questions/4556278/cgeventtapcreates-watching-keyboard-input-in-cocoa
Activity::Activity ()
{
CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventScrollWheel) |
CGEventMaskBit(kCGEventLeftMouseDown) | CGEventMaskBit(kCGEventRightMouseDown);
auto eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,
kCGEventTapOptionDefault, mask,
[] (CGEventTapProxy, CGEventType type, CGEventRef event, void*)
{ std::cout << "captured: " << type; return event; }, this);
if(eventTap == nullptr)
return;
CFRunLoopAddSource(CFRunLoopGetCurrent(),
CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0),
kCFRunLoopCommonModes);
// Enable the event tap.
CGEventTapEnable(eventTap, true);
}
#endif
Upvotes: 6
Reputation: 2502
Have written minimal code for the 3 platforms with below concerns:
Linux: Very simplistic. It certainly captures ctrl, alt, shift etc special keys and mouse clicks. The other keyboard inputs are captures sometimes and sometimes they don't. It's inconsistent. If use XGrabKeyboard()
API, then all the keys are captured. However I don't know how to pass on those keys to the visible application as it is.
Windows: Works fine & straightforward!
Mac OSX: Doesn't work yet. There might be something missing. I will update, if I find something.
#include<QAbstractEventDispatcher>
#include<QAbstractNativeEventFilter>
#include<QGuiApplication>
#include<QDebug>
struct EventFilter : QAbstractNativeEventFilter
{
EventFilter ();
bool nativeEventFilter (const QByteArray& eventType, void* pMessage, long*) override;
};
int main (int argc, char *argv[])
{
QGuiApplication application(argc, argv);
EventFilter m_Filter;
return application.exec();
}
#ifdef __linux__ // add in .pro file "linux: QT += x11extras" and "linux: LIBS += -lX11"
#include<QX11Info>
EventFilter::EventFilter () { qApp->eventDispatcher()->installNativeEventFilter(this); }
bool EventFilter::nativeEventFilter (const QByteArray& eventType, void* pMessage, long*)
{
auto* const pEvent = static_cast<xcb_generic_event_t*>(pMessage);
switch(pEvent->response_type)
{
case 28: case 37: qDebug() << eventType << ": Keypress (special)" << pEvent->response_type; break;
case 2: case 3: case 35: qDebug() << eventType << ": Keypress (general)" << pEvent->response_type; break;
case 85: qDebug() << eventType << ": Mouse Click"; break;
default: qDebug() << eventType << ": <others> " << pEvent->response_type; break;
}
return false;
}
#endif
#ifdef WIN32
#include<Windows.h> // add in .pro file "win32: LIBS += -luser32"
HHOOK sHookKeyboard, sHookMouse;
EventFilter::EventFilter ()
{
sHookKeyboard = ::SetWindowsHookExA(WH_KEYBOARD_LL,
[] (int i, WPARAM w, LPARAM l)
{ qDebug() << i << w << l; return ::CallNextHookEx(sHookKeyboard, i, w, l); },
0, 0);
sHookMouse = ::SetWindowsHookExA(WH_MOUSE_LL,
[] (int i, WPARAM w, LPARAM l)
{ qDebug() << i << w << l; return ::CallNextHookEx(sHookMouse, i, w, l); },
0, 0);
}
bool nativeEventFilter (const QByteArray& eventType, void* pMessage, long*) {}
#endif
#ifdef __APPLE__ // add in .pro file "mac: LIBS += -framework Carbon"
#include<Carbon/Carbon.h> // give required permission to the binary from "Application" settings
OSStatus HandleKeyboard (EventHandlerCallRef nextHandler, EventRef event, void* data)
{ qDebug() << "KeyPress event raised: "; return noErr; }
OSStatus HandleMouse (EventHandlerCallRef nextHandler, EventRef event, void* data)
{ qDebug() << "MouseClick event raised: "; return noErr; }
EventFilter::EventFilter ()
{
EventTypeSpec eventKeyPress;
eventKeyPress.eventClass = kEventClassKeyboard;
eventKeyPress.eventKind = kEventRawKeyDown;
InstallApplicationEventHandler(HandleKeyboard, 1, &eventKeyPress, nullptr, nullptr);
EventTypeSpec eventMouseClick;
eventMouseClick.eventClass = kEventClassMouse;
eventMouseClick.eventKind = kEventMouseDown;
InstallApplicationEventHandler(HandleMouse, 1, &eventMouseClick, nullptr, nullptr);
}
bool nativeEventFilter (const QByteArray& eventType, void* pMessage, long*) {}
#endif
Upvotes: 0
Reputation: 415
you need system-wide mouse hook and keyb hook. It should be in stand-alone dll
which is registered in windows. Then you should add it to qt . use installnativeeventfilter and within the filter where you can use plain windows C code (from msvc) do SetWindowsHookEx()
twice. Ofc hook stuff from dll should be visible in this filter module. All in all your chances to do it aren't that much . i think you agree with me haha
forget about platform-independent something
some tips:
for hook's callback function use CALLBACK
type
dll should be placed in windows\system or so. In windows 7 it's enough
my bet is that you can do #pragma comment(lib, <hook_lib>)
in native filter's .cpp
. i mean .lib
you'll have after making .dll
use .def
for export. https://learn.microsoft.com/en-us/cpp/build/exporting-from-a-dll-using-def-files?view=msvc-160
Upvotes: 1