Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to catch keyboard layout change event and get current new keyboard layout on Linux?

Now, I am working on developing applications on Linux, and want to catch keyboard layout change event (changing keyboard layout through UI/widget/shell/programing, etc.) and get/set the new keyboard layout information to further process. This is not a new questions, I think, but I search from stackoverflow again and again, but no answer. Hope I could get right answer here!

The main solution I want to learn is described here. In windows, WM_INPUTLANGCHANGE windows message could be caught in WinProc, which contains keyboard layout information. And we could use GetKeyboardLayout() API to get current keyboard layout information. Finally, if I want to use my preferred keyboard layout, I could use ActivateKeyboardLayout() to activate keyboard layout.

In summary, I hope to find the notification messages and how to catch the message in code (it is better to show me an example) in Linux, the Get keyboard layout API and the Set keyboard layout API in Linux. The development language is C/C++, also.

Thanks in advance.

like image 994
zhangyang01123 Avatar asked Feb 23 '16 05:02

zhangyang01123


3 Answers

The other answer doesn't work for me. It compiles and runs, but MappingNotify event doesn't happen when I switch layouts. Here is a modification that works for me.

#include <stdio.h>
#include <X11/Xutil.h>
#include <X11/XKBlib.h>

int main(int argc, char **argv)
{
        XEvent e;
        Display *d;

        if (!(d = XOpenDisplay(NULL))) {
                fprintf(stderr, "cannot open display\n");
                return 1;
        }

        XKeysymToKeycode(d, XK_F1);

    int xkbEventType;
    XkbQueryExtension(d, 0, &xkbEventType, 0, 0, 0);
    XkbSelectEvents(d, XkbUseCoreKbd, XkbAllEventsMask, XkbAllEventsMask);

    XSync(d, False);

    while (1) {
        XNextEvent(d, &e);
        if (e.type == xkbEventType) {
            XkbEvent* xkbEvent = (XkbEvent*) &e;
            if (xkbEvent->any.xkb_type == XkbStateNotify) {
                int lang = xkbEvent->state.group;
                if (lang == 1) {
                    fprintf(stdout, "1\n");
                    fflush(stdout);
                } else {
                    fprintf(stdout, "0\n");
                    fflush(stdout);
                }
            }
        }
    }

    return(0);
}

Compile with:

gcc -Wall -O2 xmappingnotify.c -o xmappingnotify -lX11

I use it like this:

xmappingnotify | xargs -I {} my-custom-command {} &

so my-custom-command [number_of_the_layout] runs when I change layouts. I have 2 layouts, xmappingnotify outputs "1" for one layout and "0" for the other.

like image 135
gvlasov Avatar answered Sep 28 '22 06:09

gvlasov


Answer for X11:

Detect changes via MappingNotify event

Change or query the layout via setxkbmap

Here is a basic xlib example for detecting MappingNotify events:

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

int main(int argc, char **argv)
{
        XEvent event;
        Display *dpy;

        if (!(dpy = XOpenDisplay(NULL))) {
                fprintf(stderr, "cannot open display\n");
                return 1;
        }

        /**
         * Note: We might never get a MappingNotify event if the
         * modifier and keymap information was never cached in Xlib.
         * The next line makes sure that this happens initially.
         */
        XKeysymToKeycode(dpy, XK_F1);

        while (1)
        {
                XNextEvent(dpy, &event);
                if (event.type == MappingNotify) {
                        XMappingEvent *e = (XMappingEvent *) &event;
                        if (e->request == MappingKeyboard) {
                                fprintf(stderr, "The keyboard mapping was changed!\n");
                        }
                        XRefreshKeyboardMapping(e);
                }
        }

        return(0);
}

Built command:

gcc -Wall -O2 xmappingnotify.c -o xmappingnotify -lX11
like image 33
gollum Avatar answered Sep 28 '22 07:09

gollum


I used @gvlasov answer as a keyboard layout notification in Suckless dwm bar with per-window keyboard layout patch. It works correctly regarding the result, but it produces too many events (ctrl, mod, shift keys presses-releases and mouse scroll wheel movement outputs the layout; the proper one, yet this is not needed, only when layout is changed should return layout). Probably it's what mentioned by @matanster 's comment. So I made a change which stopped output from mouse wheel movement, although still output from ctrl, mod, shift keys presses-releases is produced. It's not ideal, it's a bit better:

Changed the line:

XkbSelectEvents(d, XkbUseCoreKbd, XkbAllEventsMask, XkbAllEventsMask);

with this:

XkbSelectEventDetails(d, XkbUseCoreKbd, XkbMapNotifyMask, XkbAllEventsMask, XkbAllEventsMask);

If anybody has any idea how to get result/output only on keyboard layout change and not ctrl, mod, shift keys presses-releases, is welcomed.


Update, using this:

XkbSelectEventDetails(d, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask);

I got the expected result. The only event notified is keyboard layout change, neither key-presses nor key-releases. It's only drawback compared to my previous line is that caps changes are not notified, which was possible before.

like image 44
Krackout Avatar answered Sep 28 '22 06:09

Krackout