1

I created an application using Qt in GNU/Linux and I run in the background. I want to execute certain application functionalities when user presses some key combinations, for example Ctrl+Alt+A...

I know it is possible, Gnome Pie does it but I don't know how I can capture the keys. I tried using the examples provided in this question but none of them worked...also I wouldn't want to run my application as root...

Can anyone point me some resources or give me some hints on that?

EDIT:

@iharob suggested I should use libkeybinder. I found it, tried it but it uses GTK and GTK doesn't play well with Qt...I'm not even a GTK beginner, never worked with it but I think the GTK event loop conflicts with the Qt event loop; when I emit a Qt signal from the callback which gets called after the key was pressed(which is also after gtk_init was called) the application crashes.

What would be great is if I could create a class that emits a signal whenever a keyboard key combination was pressed(e.g. Ctrl+Alt+A).

Community
  • 1
  • 1
Jacob Krieg
  • 2,834
  • 15
  • 68
  • 140
  • I think there are some libraries that could help you, like `libkeybinder`. – Iharob Al Asimi Apr 28 '15 at 20:39
  • It would be helpful if I'd be told why the question was downvoted... – Jacob Krieg Apr 28 '15 at 20:41
  • No, that wouldn't be helpful at all. – Iharob Al Asimi Apr 28 '15 at 20:45
  • Because that would not help you solve your problem, but what you really need is google. – Iharob Al Asimi Apr 28 '15 at 20:47
  • @iharob It wouldn't but maybe if I did a mistake it is useful to know what I did wrong so I won't repeat it. Also I did google it for 2 or 3 days and couldn't find a single working example...that's why I'm asking for help, I hope I didn't ask the dumbest question... – Jacob Krieg Apr 28 '15 at 20:50
  • 1
    Can't be done with Qt. Only widgets, which are in focus can receive key events. A background application has no focus. – Greenflow Apr 28 '15 at 21:27
  • @Greenflow `libkeybinder` works ok, I just did a small test...do you know of any other `C++` libraries for accomplishing this task? – Jacob Krieg Apr 28 '15 at 21:29
  • Works ok? Interesting. I thought libkeybinder works only with gtk apps. And no, I don't know other such libs. Never had a need for them. – Greenflow Apr 28 '15 at 21:38
  • @Greenflow Yes, it works only with `gtk` apps, because it uses `gtk`...unfortunately I had to bind the app with `gtk` but I don't think I had other option...maybe guard everything in some `#ifdef`s and use `winapi` on `Windows` for the same task... – Jacob Krieg Apr 28 '15 at 21:48
  • AFAIK this would be something desktop related, not Linux kernel related. What desktop(s) are you targeting? – MrEricSir Apr 28 '15 at 22:49
  • @MrEricSir I don't think this is desktop environment related, are you sure? I would like something that works across all `GNU/Linux` distributions...is this possible? – Jacob Krieg May 03 '15 at 00:01

3 Answers3

2

As far as I see and as @SamVarshavchik pointed out libkeybinder uses libx11 in the background so you could just use libx11 in order to get rid of the GTK event loop which is not very Qt friendly. AFAIK KDE's KAction uses the same technique for their global short keys so I think this technique will play well with Qt's event loop.

These things being said, you can use a hot-key example as presented here:

x11_hot_key.pro:

#-------------------------------------------------
#
# Project created by QtCreator 2015-05-04T01:47:22
#
#-------------------------------------------------

QT       += core

QT       -= gui

TARGET = x11_hot_key
CONFIG   += console
CONFIG   -= app_bundle

TEMPLATE = app


SOURCES += main.cpp

CONFIG  += link_pkgconfig
PKGCONFIG += x11

main.cpp:

#include <QCoreApplication>

#include <iostream>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

using namespace std;

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Display*    dpy     = XOpenDisplay(0);
    Window      root    = DefaultRootWindow(dpy);
    XEvent      ev;

    unsigned int    modifiers       = ControlMask | ShiftMask;
    int             keycode         = XKeysymToKeycode(dpy,XK_Y);
    Window          grab_window     =  root;
    Bool            owner_events    = False;
    int             pointer_mode    = GrabModeAsync;
    int             keyboard_mode   = GrabModeAsync;

    XGrabKey(dpy, keycode, modifiers, grab_window, owner_events, pointer_mode,
             keyboard_mode);

    XSelectInput(dpy, root, KeyPressMask );
    while(true)
    {
        bool shouldQuit = false;
        XNextEvent(dpy, &ev);
        switch(ev.type)
        {
        case KeyPress:
            cout << "Hot key pressed!" << endl;
            XUngrabKey(dpy,keycode,modifiers,grab_window);
            shouldQuit = true;

        default:
            break;
        }

        if(shouldQuit)
            break;
    }

    XCloseDisplay(dpy);

    return a.exec();
}

or you could just use this simple library as presented here which also has some simple examples together with a handy Makefile for you to get along with.

As I don't have knowledge of an asynchronous correspondent to XGrabKey, a problem you will have is that the while(true) loop never returns and blocks the main thread thus the application so what you want is to move that in a separate thread and connect it to the main thread using signals and slots. This shouldn't be a big issue though and won't affect your application's performance because AFAIK XNextEvent blocks until your key is hit so the processor won't be uselessly processing...

Hope this helps.

Community
  • 1
  • 1
Iuliu
  • 4,001
  • 19
  • 31
1

A brief look at libkeybinder's very small source indicates that all it does in install a keygrab on the X display's root window.

This should be doable, but it won't be easy, and requires some knowledge and understanding of the low level X Window System protocol. It should be possible for both Qt and libxcb to coexist peacefully in one process. The way I would try to implement something like this would be as follows:

  1. Start a separate thread.
  2. The thread would open a separate connection to the X server, enumerate all screens on the display, obtain each screen's root window, install a key grab on each root window, then enter a loop reading X events from the xcb_connection_t handle.
  3. Upon receipt of a key event (the only key events I expect to process in this loop would be the ones corresponding to the grabbed key), immediately ungrab the keyboard so that the X server can proceed on its merry way, then notify your application's main thread, in some form or fashion, that the key has been pressed.
  4. Your application will have to have some means of stopping this thread, when it's time to quit.
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
1

Possible solution would be to simulate this behavior - have a small standalone application that sends signal to your background process (there are many variants doing this, signal() would be probably the simplest). Then attach that application for desired key binding in window manager for particular environment. It may require to learn how to do that for various window managers, but result could be cleaner and faster to implement.

Slava
  • 43,454
  • 1
  • 47
  • 90