Clipboard Shortcut/Hotkey binding with Qt outside of application - c++

I've tried googling, and searching on this site about this but to no avail.
I am building a Clipboard related application for Windows using Qt, and one of the requirements for it to work right is to be able to register for keyboard events outside my Qt application, like ctrl + c, ctrl + v. (copy/paste). The only thing that I have found online is using an external plugin for Qt but the entire concept was not explained properly, so I hit a dead end.
Does anyone have an idea how I can do this? Again, I want to register shortcuts to my application that will occur outside the application itself.
Thanks in advance!

Binding clipboard shortcuts and binding shortcuts in general are as I have discovered, two different things. Related to Clipboard events, Qt provides access to a dataChanged() signal through its QClipboard class. Using that, you are able to know when the clipboard data has changed, and act accordingly, and should eliminate the need to perform a system-wide binding of the Copy/Paste shortcuts.
In order to register a global shortcut (in this case the need for ctrl + v), and this is platform specific like in my needs, one can use the RegisterHotKey function under Windows. The HWND requested as the first parameter can be obtained from the winId function that is provided by QWidget.
In order to accept the WM_HOTKEY event, one would have to implement the winEvent virtual protected function under Qt <= 5.0, and nativeEvent on >= 5.0.

This depends on the Desktopenvironment the application runs in and it is OS-specific. If you intend to run the application within KDE you can register global hotkeys easyly by deploying a .desktop File with your Application or by adding things to /usr/share/kde4/apps/khotkeys .
Within Windows, the easiest way would probably be adding a registry key to the registry in order to register a global Hotkey. See MSDN

I think you're just confused about how the clipboard works. You never need to register clipboard-related hotkeys outside of your application. Those are handled by other applications. What those applications do is interact with the system-wide clipboard. Your application needs to interact with the same clipboard and get notifications about new clipboard data being available etc.
You might get more helpful answers if you tell us what you mean by a "clipboard related" application. Is it used to sell wooden clipboards? Or to calibrate clipboard springs? Or to manage inventory of clipboards? Or does it run on a digital clipboard? Sigh.

This should get you up and running in Windows+Qt and HotKeys pretty quickly.
I haven't tried the Qt eXTension library with QxtGlobalShortcut, but it sounds like it may be a more elegant complete solution for more platforms. (like in #TimMeyer's comment to your question)
https://stackoverflow.com/a/3154652/808151
I wrote up this function to listen for a single system wide hotkey in windows.
#ifndef HOTKEYTHREAD_H
#define HOTKEYTHREAD_H
#include <QThread>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
class HotKeyThread : public QThread
{
Q_OBJECT
public:
HotKeyThread(QObject *parent);
~HotKeyThread();
signals:
void hot_key_event(int);
public slots:
void run();
void stop();
private:
volatile bool m_stopped;
DWORD m_thread_id;
};
#endif // HOTKEYTHREAD_H
.cpp file
#include "hotkeythread.h"
#include <QDebug>
#include <process.h>
#define WM_END_THREAD (WM_USER+2)
HotKeyThread::HotKeyThread(QObject *parent)
: QThread(parent)
{
this->m_thread_id = 0;
}
HotKeyThread::~HotKeyThread()
{
}
void HotKeyThread::stop()
{
if(this->m_thread_id != 0)
::PostThreadMessage(this->m_thread_id, WM_END_THREAD, 0, 0);
}
//
void HotKeyThread::run()
{
// store a thread id so we can exit later
m_thread_id = ::GetCurrentThreadId();
qDebug() << "ThreadIDs" << QString::number(m_thread_id, 16) << QString::number((int) this->currentThreadId(), 16);
// register an atom, and a hotkey
BOOL retVal;
int counter = 0;
int magic_num = 1128;
ATOM id = ::GlobalAddAtom(MAKEINTATOM(magic_num + counter++));
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
int modifier = 0x0;// modify this line
int key = VK_NUMPAD0;// modify this line
if(QSysInfo::windowsVersion() > QSysInfo::WV_VISTA)
{
retVal = ::RegisterHotKey(NULL, id, modifier | MOD_NOREPEAT, key);
}
else
{
// No repeat is only supported in 7 and later
retVal = ::RegisterHotKey(NULL, id, modifier, key);
}
if(retVal)
{
qDebug() << "Successfully added a HotKey!";
}
else
{
qDebug() << "Failed to add a hotkey!";
return;
}
// wait on hotkeys
MSG msg = {0};
while (0 < ::GetMessage(&msg, NULL, 0, 0))
{
if(msg.message == WM_HOTKEY)
{
bool control = LOWORD(msg.lParam) & MOD_CONTROL;
bool shift = LOWORD(msg.lParam) & MOD_SHIFT;
bool alt = LOWORD(msg.lParam) & MOD_ALT;
bool win = LOWORD(msg.lParam) & MOD_WIN;
qDebug() << "HotKey!" << (control ? "Ctrl +": "")
<< (alt ? "Alt +": "")
<< (shift ? "Shift +":"")
<< (win ? "Win +":"") << QString::number(HIWORD(msg.lParam),16);
// TODO Notify MainWindow of the event
emit hot_key_event(msg.lParam);
}
else if(msg.message == WM_END_THREAD)
{
// exit
break;
}
}
// Clean up Hotkey
::UnregisterHotKey(NULL, id);
::GlobalDeleteAtom(id);
}
Usage in your GUI
// Start HotKey Thread!
m_hot_key_thread = new HotKeyThread(this);
QObject::connect(m_hot_key_thread, SIGNAL(hot_key_event(int)),
this, SLOT(handle_hot_key_event(int)), Qt::QueuedConnection);
m_hot_key_thread->start();
and when your program is closing use
m_hot_key_thread->stop();
Hope that helps.

Related

Get Capslock state on Linux with Wayland

I am struggling with the following task: for our cross-platform application I want to enable a capslock warning for the user. This works perfectly on Windows and macOS and is a bit unnecessarily complicated but doable on Linux with X11, though I cannot find out how to do it properly on Wayland.
We are using Qt5, so the more Qt APIs I can use for this, the better. I see that Qt has a very extensive Wayland framework, but it seems to be designed primarily for writing your own compositor and not for accessing specifics of the underlying platform plugin.
Here's the code as far as I have it:
#include <QGuiApplication>
#include <qpa/qplatformnativeinterface.h>
// namespace required to avoid name clashes with declarations in XKBlib.h
namespace X11
{
#include <X11/XKBlib.h>
}
void checkCapslockState()
{
// ... Windows and macOS one-liners
// Here starts the Linux mess.
// At least I can query the display with this for both X11 and Wayland.
QPlatformNativeInterface* native = QGuiApplication::platformNativeInterface();
auto* display = native->nativeResourceForWindow("display", nullptr);
if (!display) {
return;
}
const QString platform = QGuiApplication::platformName();
if (platform == "xcb") {
unsigned state = 0;
if (X11::XkbGetIndicatorState(reinterpret_cast<X11::Display*>(display), XkbUseCoreKbd, &state) == Success) {
// works fine
newCapslockState = ((state & 1u) != 0);
}
} else if (platform == "wayland") {
// but how to proceed here?
// struct wl_display* waylandDisplay = reinterpret_cast<struct wl_display*>(display);
}
// ...
}
My understanding is that I have to get hold of the Wayland wl_seat object somehow, which holds information about the wl_keyboard. However, I cannot find a way to access these objects from the wl_display object alone without instantiating all sorts of contexts. The Qt application itself is already running as a Wayland client, so there should be a way to access these objects, I would assume. Unfortunately, the Wayland documentation on this is very sparse and quite opaque for someone not familiar with the whole architecture and Wayland's user base is still too small that things pop up on Google.
I found a solution, but I am far from satisfied with it.
I am using KWayland here, but it is possible to use the plain Wayland C API, of course. Be prepared to write 200-300 additional lines of boilerplate code, though. KWayland abstracts that a bit, but it is still pretty verbose. Even the X11 solution is shorter, let alone the one-liners of Windows and macOS.
I don't think this is a state in which Wayland will be pretty successful in the long-term. It's okay to have that much control on the lowest level, but there need to be proper high-level abstractions in place.
Long story short, here's the full code for all platforms:
#include <QGuiApplication>
#if defined(Q_OS_WIN)
#include <windows.h>
#elif defined(Q_OS_MACOS)
#include <CoreGraphics/CGEventSource.h>
#elif defined(Q_OS_UNIX)
#include <qpa/qplatformnativeinterface.h>
// namespace required to avoid name clashes with declarations in XKBlib.h
namespace X11
{
#include <X11/XKBlib.h>
}
#include <KF5/KWayland/Client/registry.h>
#include <KF5/KWayland/Client/seat.h>
#include <KF5/KWayland/Client/keyboard.h>
#endif
void MyCls::checkCapslockState()
{
const QString platform = QGuiApplication::platformName();
#if defined(Q_OS_WIN)
newCapslockState = (GetKeyState(VK_CAPITAL) == 1);
#elif defined(Q_OS_MACOS)
newCapslockState = ((CGEventSourceFlagsState(kCGEventSourceStateHIDSystemState) & kCGEventFlagMaskAlphaShift) != 0);
#elif defined(Q_OS_UNIX)
// get platform display
QPlatformNativeInterface* native = QGuiApplication::platformNativeInterface();
auto* display = native->nativeResourceForWindow("display", nullptr);
if (!display) {
return;
}
if (platform == "xcb") {
unsigned state = 0;
if (X11::XkbGetIndicatorState(reinterpret_cast<X11::Display*>(display), XkbUseCoreKbd, &state) == Success) {
newCapslockState = ((state & 1u) != 0);
}
} else if (platform == "wayland") {
if (!m_wlRegistry) {
auto* wlDisplay = reinterpret_cast<struct wl_display*>(display);
m_wlRegistry.reset(new KWayland::Client::Registry());
m_wlRegistry->create(wlDisplay);
m_wlRegistry->setup();
// wait for a seat to be announced
connect(m_wlRegistry.data(), &KWayland::Client::Registry::seatAnnounced, [this](quint32 name, quint32 version) {
auto* wlSeat = new KWayland::Client::Seat(m_wlRegistry.data());
wlSeat->setup(m_wlRegistry->bindSeat(name, version));
// wait for a keyboard to become available in the seat
connect(wlSeat, &KWayland::Client::Seat::hasKeyboardChanged, [wlSeat, this](bool hasKeyboard) {
if (hasKeyboard) {
auto* keyboard = wlSeat->createKeyboard(wlSeat);
// listen for a modifier change
connect(keyboard, &KWayland::Client::Keyboard::modifiersChanged,
[this](quint32 depressed, quint32 latched, quint32 locked, quint32 group) {
Q_UNUSED(depressed)
Q_UNUSED(latched)
Q_UNUSED(group)
newCapslockState = (locked & 2u) != 0;
// emit signals etc. here to notify outer non-callback
// context of the new value of newCapslockState
});
}
});
});
}
}
// do something with the newCapslockState state for any
// platform other than Wayland
}
m_wlRegistry is defined as a QScopedPointer<KWayland::Client::Registry> member in the header file.
This solution basically works, but suffers from either a bug in KWin or a weirdness of the protocol (I don't know which). Pressing the Capslock key will trigger the inner lambda callback twice: first time with bit 2 in locked set and a second time on key release with it unset. There is no reliable way of filtering out this second activation based on the other parameters passed (I tried ignoring when depressed != 0, but it's not working as expected). Pressing any other key after that will trigger the callback a third time with the bit set again. So when you are actually typing, the code works, but when you are just pressing Capslock, the behaviour is weird and less reliable than the solutions for other platforms. Since the Capslock indicator in the Plasma tray has the same issue, I would assume this is a bug.
As a KDE-specific solution, there appears to be another interface one could listen to called org_kde_kwin_keystate (https://github.com/KDE/kwayland/blob/master/src/client/protocols/keystate.xml). However, when I was testing it in a KDE Neon VM, the compositor did not announce this protocol extension, so I was unable to use it.

Sleep inside QTConcurrent run method

I'm using QtConcurrent::run to execute some functions in background and not hang the GUI thread. In one function, I read logs from local SQlite database and send them to server by TCP socket.
Now I want to delay the execution after each log so the server has time to save it (TCP response is read in different thread). I'm stuck with Qt4.8 due to implementation limitations (many embeded devices - no chance to upgrade QT on them) and I can't use QThread::sleep(2) because it is protected in 4.8.
Is it possible to somehow pause the execution of thread inside QtConcurrent::run method or should I redesign it to implement my own class inheriting QThread?
void MainWindow::ReportFinishedHUs(bool asyncWork)
{
if(asyncWork == false)
{
QMutexLocker locker(&localDBmutex);
QList<QSqlRecord> HUsToReport = localDB->getHUsForBook();
qDebug() << "HUs to report" << HUsToReport.count();
if(!HUsToReport.isEmpty())
{
Cls_log::WriteDebugLog("HUs to report: " + QString::number(HUsToReport.count()));
foreach (QSqlRecord record, HUsToReport)
{
int _hu = record.indexOf("hu");
int _logTime = record.indexOf("logTime");
QString logTimeString = record.value(_logTime).toString();
QString hu = record.value(_hu).toString();
qDebug() << hu << logTimeString;
// creating message here ...
qDebug() << message;
emit sig_SendTCPMessage(message);
// this is where I need to wait for 2 seconds
QThread::sleep(2);
}
}
}
else
{
QtConcurrent::run(this, &MainWindow::ReportFinishedHUs, false);
}
}
EDIT:
Solved by usleep(2000000) which I somehow discarded for being platform specific... but hey, half of my aplication is platform specific and I only use it in embeded device with constant OS.
Keeping the question open if anyone can suggest more elegand solution using Qt methods. I like to get inspired.

Listen close event of iexplorer in my application

I am writing a win32 application by C++, and I want it to do something when all iexplorer.exe were closed.
I know that SetWindowsHook() may be useful in my case.
But if I have no idea about the process or thread ID of IE, because every time open IE would get a different thread ID.
If I do not use timer to check the process list to get the ID of iexplorer, does there have another approach to listen close event of IE in my win32 application?
The object for IE is called InternetExplorer. TheShellWindows object is a collection of InternetExplorer objects. But here it gets complicated. Not all InternetExplorer objects are what you would call an IE window. Some of them are "Windows Explorer" windows. See About the Browser (Internet Explorer).
The following is a managed C++ console program that lists the existing windows and sets a count of the number of existing windows. Then it uses WindowRegistered and WindowRevoked events to monitor creation and closing of windows. Those event are not documented very well. The sample below uses the Document member of each InternetExplorer object to determine if the window has HTML. However see the comment in c# - Distinguishing IE windows from other windows when using SHDocVw; it is possible for a IE window to not have HTML in it.
Note that the following sample is using an AutoResetEvent to keep the program going since it is a console program.
The following is the header:
#pragma once
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <ShlObj.h>
#include <comdef.h>
#include <vcclr.h>
The following is the program:
#include "stdafx.h"
using namespace System;
using namespace System::Threading;
static int Browsers = 0;
static gcroot<AutoResetEvent^> Event;
bool IsBrowser(SHDocVw::InternetExplorer ^ ie)
{
MSHTML::IHTMLDocument2^ Document;
try { Document = (MSHTML::IHTMLDocument2^)ie->Document; }
catch (Exception^ ex)
{
return false;
}
return Document != nullptr;
}
static void WindowRegistered(int lCookie) {
++Browsers;
Console::WriteLine("WindowRegistered");
}
static void WindowRevoked(int lCookie) {
--Browsers;
Console::WriteLine("WindowRevoked");
if (Browsers <= 0)
Event->Set();
}
int main(array<System::String ^> ^args)
{
SHDocVw::ShellWindows^ swList = gcnew SHDocVw::ShellWindowsClass();
Console::WriteLine(L"{0} instances", swList->Count);
for each (SHDocVw::InternetExplorer ^ ie in swList) {
Console::WriteLine(ie->LocationURL);
if (IsBrowser(ie)) {
Console::WriteLine("HTML document");
++Browsers;
}
else
Console::WriteLine("Not HTML");
}
if (Browsers == 0)
{
Console::WriteLine("No browsers");
return 0;
}
Event = gcnew AutoResetEvent(false);
swList->WindowRegistered += gcnew SHDocVw::DShellWindowsEvents_WindowRegisteredEventHandler(WindowRegistered);
swList->WindowRevoked += gcnew SHDocVw::DShellWindowsEvents_WindowRevokedEventHandler(WindowRevoked);
Event->WaitOne();
Console::WriteLine("No more browsers");
return 0;
}
Now I just realized that there is a problem with the way this works. The WindowRegistered and WindowRevoked handlers are incrementing the Browsers count even if the window is not an IE window. I don't know how to determine what window that the cookie passed to WindowRegistered and WindowRevoked represents. A few years ago I spent a couple of days or more tryinig to figure that out. So what you should do is to somehow re-list all the windows after each WindowRegistered and WindowRevoked event.
You need to add references for "Microsoft Internet Controls" (SHDocVw.dll) and "Microsoft HTML Object Library" (mshtml.dll) to the project. They are COM objects that should be in your "C:\Windows\System32" directory.

My xlib code can listen to keyboard events but it fails for some windows

I have this code to listen to keyboard events of the active window:
#include<X11/Xlib.h>
#include<stdio.h>
#include<stdlib.h>
#include <iostream>
#include <thread>
using namespace std;
#define IR_MODE 8192
#define SHIFT_MODE 16
#define US_MODE 0
static int ErrorHandler (Display *display, XErrorEvent *error)
{
cout<< "An error ocurred. We don't know anything.";
return 0;
}
int main()
{
Display *d = XOpenDisplay(0);
if(d == 0)
{
cout<< "Cannot open display\n";
exit(0);
}
Window root = DefaultRootWindow(d);
XSetErrorHandler(ErrorHandler);
Window current_window;
int rev;
XGetInputFocus(d,&current_window,&rev);
XSelectInput(d,current_window, KeyPressMask | KeyReleaseMask | FocusChangeMask);
while(true)
{
XEvent e;
XNextEvent(d, &e);
switch(e.type)
{
case FocusOut:
XGetInputFocus(d,&current_window,&rev);
if(current_window == PointerRoot || current_window == None)
{
current_window = root;
}
XSelectInput(d,current_window, KeyPressMask | KeyReleaseMask | FocusChangeMask);
break;
case KeyPress:
{
cout<< "key pressed\n";
break;
}
case KeyRelease:
cout<< "key released\n";
break;
}
}
XCloseDisplay(d);//*/
}
It works for many windows. But it fails for some windows, specially for gnome applications, e.g. nautilus. Why does this happen and how can I solve the problem?
The program just tries to listen to keyboard without interfering anything. As if the keyboard is being tapped with a difference: the program doesn't intend to lose language layout info. When a key is pressed, some information including ASCII codes are chosen and attached to the key event according to language layout and then the key event is sent. The program needs the key event with the information attached to it. So The program does not intend to Grab keyboard. It does not care for active or focused window. It just tries to listen to keys even if the program's window is not active. There are programs which check regularly with XQueryKeymap, but I'm not going to use that. Because it uses up CPU and then it will be more than just a fair listener. Also if checking is not frequent some keys may escape.
I guess your program does not work with GTK3 windows which uses xinput2. This is true if GTK3 was built without --disable-xinput.
AFAIK XSelectInput() won't work with xinput2, you need XISelectEvents() for such windows.
Look at meta_core_select_events() function from Mutter sources. It works both for xinput2 and traditional windows. This patch may be also helpful.

C++ Finding Index for Font temporarily added to System Font Table with AddFontResource() to use in Console

I am trying to temporarily install a font to use in the win32 console with
int AddFontResource(LPCTSTR lpszFilename);
and
BOOL WINAPI SetConsoleFont(HANDLE hOutput, DWORD fontIndex)
I got hold of this function from this site.
Although both functions seem to work fine I have no idea how to find the added font index to use with SetConsoleFont.
AddFontResource returns no index value or key to the temporary font.
Here is my relevant code:
#include "Level.h"
#include "ConsoleFont.h" //acquired from above mentioned site
#include <Windows.h>
//-------------------------------------------------------------------------------
void init();
void cleanup();
int main()
{
FileManager *pFileManager = new FileManager(); //unrelated
Level *lvl1 = new Level("filename",pFileManager); //unrelated
///TEMPORARY PLANNING
// using add font resource. how can i get this fonts index value?
int err = AddFontResource(L"Files/gamefont.fnt");
if (err == 0)
{
MessageBox(NULL,L"loading font failed",L"Error",0);
}
else
{
wchar_t message[100];
swprintf_s(message,100,L"AddFontResource returned: %d",err);
MessageBox(NULL,LPTSTR(message),L"error",0);
}
SendMessage(HWND_BROADCAST, WM_FONTCHANGE,0,0);
//acquiring handle to current active screen buffer
HANDLE tempHandle = GetStdHandle(STD_OUTPUT_HANDLE);
if (tempHandle == INVALID_HANDLE_VALUE)
{
MessageBox(NULL,L"Failed to aquire Screen Buffer handle",L"Error",0);
}
//I dont know what to set this to. this is the crux of the problem.
DWORD fontIndex = 1;
if (FALSE == SetConsoleFont(tempHandle,fontIndex))
{
MessageBox(NULL,L"loading console font failed",L"Error",0);
}
//draws a house when in correct font
std::cout<<"!!!!!!!!#\n"
<<"!!!!!!!!!\n"
<<"! !! !! !\n"
<<"!!!!!!!!!\n"
<<"! !! !! !\n"
<<"!!!!!!!!!\n"
<<"! !! !! !\n"
<<"!!!!!!!!!\n"
<<"! !! !! !#\n"
<<"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"<<std::endl;
///PLANNING OVERS
bool quit = false;
while(!quit)
{
//still to be implemented
}
err = RemoveFontResource(L"Files/gamefont.fnt");
if (err==0)
{
MessageBox(NULL,L"removing font failed",L"Error",0);
}
return 0;
}
I don't know how to go about finding my new font's index value or even if this is possible using my current method.
If someone knows or has a better method please help me out.
any help or hints are appreciated. It must possible to use a custom font in the win32 Console without fiddling with the registry. I'm sure of it :)
Unfortunately you entered the dark world on Win APIs. There is no documentation (or atleast I could never find it) for a console font table lookup. You can try the method "GetNumberOfConsoleFonts()" to see what is returned. I think the font at index 10 is Lucida Console. You'll have to play around a little. Also, this may not work for the OS version you have. Worked for me on XP. Never had to try on anything else. And honestly, never got it fully working on XP too.
For the registry,
Fonts registries are here:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts
Console registries are here:
HKEY_CURRENT_USER\Console
If you end up modifying the registry, the changes may not be reflected immediately. You need to either restart the console or send a special WM_* message (sorry don't remember the name).
Will be great if you can find a solution/workaround :)
int err = AddFontResource(L"Files/gamefont.fnt");
if (err == 0)
{
MessageBox(NULL,L"loading font failed",L"Error",0);
}
else
{
wchar_t message[100];
swprintf_s(message,100,L"AddFontResource returned: %d",err);
MessageBox(NULL,LPTSTR(message),L"error",0);
}
this is wrong AddFontResource returns the number of fonts loaded, so the code in the ELSE doesn't make sense.