From 645b3b50863076f0865d924d2b3aeb2d44460df2 Mon Sep 17 00:00:00 2001 From: Alberto Leiva Popper Date: Mon, 9 May 2016 10:53:27 -0500 Subject: [PATCH 1/4] Make modifier grabs configurable The original code only grabs Control, Alt, Control+Shift and Alt+Shift. While he lack of versatility is slightly upsetting, the real problem is that NumLock is considered a modifier so turning it on breaks the grabbing, confusing some people. This, for example, is a likely victim: http://askubuntu.com/questions/143573 This commit allows the user to specify the modifiers they want to grab when the program is invoked. This fix only applies to the unix binary! I also moved the in-code header documentation to a README since it was getting a little long. --- unix/README.md | 101 ++++++++++++++++++++++++++++++ unix/xdq.c | 165 ++++++++++++++++++++----------------------------- 2 files changed, 168 insertions(+), 98 deletions(-) create mode 100644 unix/README.md diff --git a/unix/README.md b/unix/README.md new file mode 100644 index 0000000..9ac7f92 --- /dev/null +++ b/unix/README.md @@ -0,0 +1,101 @@ +# xdq + +## How to Use + +Compile with: + + gcc xdq.c -o xdq -std=gnu99 -O2 -lX11 + +If you don't have GCC, and the C compiler you do have is not C99-compliant, +try compiling with a C++ compiler instead. + +Once compiled, make sure your keyboard layout is set to Dvorak, all locks +are disabled (NumLock, CapsLock, etc) and then run the `xdq` binary. +While running, keys you press while holding control or alt (but not both +together) will be remapped to Qwerty. To stop, just kill `xdq`. + +If you want to remap other key combinations, see the "ARGUMENTS" section +below. + +## Background + +This file implements the "Dvorak-Qwerty" keyboard layout, in which the layout +is normally Dvorak but switches to Qwerty when control or alt is held. There +are two reasons why I prefer this layout over straight Dvorak: + +- The common copy/paste hotkeys X, C, and V remain on the left hand, and so + can be used while the right hand is on the mouse. +- Holding the control key with my pinky tends to make it hard for me to + remember where many keys are located, because my hands are no longer + positioned as they would be when touch-typing. Meanwhile, the labels on + my keyboard are Qwerty, because I no longer bother reconfiguring them + physically. With the Dvorak-Qwerty layout, I can look at the keyboard to + find the key I want. + +The layout is available by default on Mac OSX. Unfortunately, it is not +typically shipped with Linux distributions. Even more unfortunately, +although it is possible to define an XKB layout which implements +Dvorak-Qwerty, doing so exposes a depressing number of bugs across the board +in X apps. Since it is the responsibility of each X app to interpret the +keyboard layout itself, rather than having the X server do the work, +different GUI frameworks actually tend to have different bugs that kick in +when using such a layout. Fixing them all would be infeasible. + +This program instead works by passively grabbing (with XGrabKey()) all +relevant combinations, rewriting the event, and then using XSendEvent() to +send it to the focused window. + +## Arguments + + ./xdq [Modifiers] + +`[Modifiers]` expands to a list of modifier combinations. These are the valid +modifiers: + +| Name | Usual binding | +|----------|---------------------------| +| Shift | Both Shifts (L and R) | +| CapsLock | Caps Lock | +| Control | Both Controls (L and R) | +| Mod1 | Alt (Alt Gr not included) | +| Mod2 | Num Lock | +| Mod3 | Scroll Lock | +| Mod4 | Super ("Windows") | +| Mod5 | ? | + +(I computed the second column by trial and error. There is a small chance that +you will get different bindings, so you might need to experiment.) + +So if you want to remap only Control, run + + ./xdq Control + +If you want to remap Control and Alt, but *not* Control+Alt: + + ./xdq Control Mod1 + +If you want to remap Control, Alt and Control+Alt: + + ./xdq Control Mod1 Control+Mod1 + +et cetera. + +Notice that locks are considered modifiers, so if you want Control to be +remapped regardless of NumLock, you have to bind both combinations: + + ./xdq Control Control+Mod2 + +For backwards compatibility reasons, by default `xdq` remaps Control, +Control+Shift, Alt and Alt+Shift if you include no `[Modifiers]`. + +`xdq` can only remap program-level hotkeys, not system-level hotkeys, as +system-level hotkeys are typically themselves implemented using XGrabKey(). +Keep this in mind if you want to remap combinations such as Super and/or +Control+Alt; you might need additional, system-specific configuration to get +them to work. + +## If you like it + +If you find this useful, consider sending me a note at temporal@gmail.com to +say so. Otherwise people only contact me when things don't work and that's +depressing. :) diff --git a/unix/xdq.c b/unix/xdq.c index 8ef28c8..9b4bada 100644 --- a/unix/xdq.c +++ b/unix/xdq.c @@ -29,61 +29,6 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// HOW TO USE -// -// Compile with: -// -// gcc xdq.c -o xdq -std=gnu99 -O2 -lX11 -// -// If you don't have GCC, and the C compiler you do have is not C99-compliant, -// try compiling with a C++ compiler instead. -// -// Once compiled, make sure your keyboard layout is set to Dvorak and then run -// the "xdq" binary. While running, keys you press while holding control or -// alt (but not both together) will be remapped to Qwerty. To stop, just kill -// xdq. - -// BACKGROUND -// -// This file implements the "Dvorak-Qwerty" keyboard layout, in which the layout -// is normally Dvorak but switches to Qwerty when control or alt is held. There -// are two reasons why I prefer this layout over straight Dvorak: -// - The common copy/paste hotkeys X, C, and V remain on the left hand, and so -// can be used while the right hand is on the mouse. -// - Holding the control key with my pinky tends to make it hard for me to -// remember where many keys are located, because my hands are no longer -// positioned as they would be when touch-typing. Meanwhile, the labels on -// my keyboard are Qwerty, because I no longer bother reconfiguring them -// physically. With the Dvorak-Qwerty layout, I can look at the keyboard to -// find the key I want. -// -// The layout is available by default on Mac OSX. Unfortunately, it is not -// typically shipped with Linux distributions. Even more unfortunately, -// although it is possible to define an XKB layout which implements -// Dvorak-Qwerty, doing so exposes a depressing number of bugs across the board -// in X apps. Since it is the responsibility of each X app to interpret the -// keyboard layout itself, rather than having the X server do the work, -// different GUI frameworks actually tend to have different bugs that kick in -// when using such a layout. Fixing them all would be infeasible. -// -// This program instead works by passively grabbing (with XGrabKey()) all -// relevant combinations, rewriting the event, and then using XSendEvent() to -// send it to the focused window. -// -// xdq can only remap program-level hotkeys, not system-level hotkeys, as -// system-level hotkeys are typically themselves implemented using XGrabKey(). -// To avoid conflicts with system-level hotkeys, xdq only grabs key combinations -// involving holding control *or* alt, not both together. xdq also does NOT -// try to grab anything involving the "windows" key. If you would like xdq to -// grab all these keys as well, system hotkeys be damned, then compile with -// -DXDQ_GREEDY. - -// IF YOU LIKE IT -// -// If you find this useful, consider sending me a note at temporal@gmail.com to -// say so. Otherwise people only contact me when things don't work and that's -// depressing. :) - #include #include #include @@ -159,6 +104,72 @@ int HandleError(Display* display, XErrorEvent* error) { } } +void GrabEachKey(Display* display, Window window, int argc, char* argv[]) { + // Waste the first argument, since it's just the program name. + argv++; + argc--; + + char *defaultArgv[] = {"Control", "Control+Shift", "Mod1", "Mod1+Śhift"}; + int defaultArgc = arraysize(defaultArgv); + if (argc == 0) { + argc = defaultArgc; + argv = defaultArgv; + } + + // Often, some keys are already grabbed, e.g. by the desktop environment. + // Set an error handler so that we can ignore those. + original_error_handler = XSetErrorHandler(&HandleError); + + for (int i = 0; i < argc; i++) { + unsigned int modifiers = 0; + + if (strstr(argv[i], "Shift")) { + modifiers |= ShiftMask; + } + if (strstr(argv[i], "CapsLock")) { + modifiers |= LockMask; + } + if (strstr(argv[i], "Control")) { + modifiers |= ControlMask; + } + if (strstr(argv[i], "Mod1")) { + modifiers |= Mod1Mask; + } + if (strstr(argv[i], "Mod2")) { + modifiers |= Mod2Mask; + } + if (strstr(argv[i], "Mod3")) { + modifiers |= Mod3Mask; + } + if (strstr(argv[i], "Mod4")) { + modifiers |= Mod4Mask; + } + if (strstr(argv[i], "Mod5")) { + modifiers |= Mod5Mask; + } + if (modifiers == 0) { + fprintf(stderr, "Could not recognize modifiers in '%s'; ignoring...\n", + argv[i]); + continue; + } + + for (int j = 0; j < arraysize(kKeycodes); j++) { + XGrabKey(display, kKeycodes[j], modifiers, window, True, + GrabModeAsync, GrabModeAsync); + } + } + + // Make sure all errors have been reported, then print how many errors we saw. + XSync(display, False); + if (failed_grab_count != 0) { + fprintf(stderr, "Failed to grab %d key combinations.\n", failed_grab_count); + fprintf(stderr, + "This is probably because some hotkeys are already grabbed by the system.\n" + "Unfortunately, these system-wide hotkeys cannot be automatically remapped by\n" + "this tool. However, you can usually configure them manually.\n"); + } +} + int main(int argc, char* argv[]) { InitKeycodeMapping(); @@ -209,49 +220,7 @@ int main(int argc, char* argv[]) { // you really want to grab everything. // We will try to grab all of these modifier combinations. - unsigned int modifiers[] = { - // Control. - ControlMask, - ControlMask | ShiftMask, - - // Alt. - Mod1Mask, - Mod1Mask | ShiftMask, - -#ifdef XQD_GREEDY - // Command/"Windows" key. This is usually used for system-level hotkeys, - // so only grab it in greedy mode. - Mod4Mask, - Mod4Mask | ShiftMask, - - // Control + Alt. Also typically used for system-level hotkeys. - ControlMask | Mod1Mask, - ControlMask | Mod1Mask | ShiftMask, -#endif - - // TODO(kenton): Other combinations? - }; - - // Often, some keys are already grabbed, e.g. by the desktop environment. - // Set an error handler so that we can ignore those. - original_error_handler = XSetErrorHandler(&HandleError); - - for (int i = 0; i < arraysize(kKeycodes); i++) { - for (int j = 0; j < arraysize(modifiers); j++) { - XGrabKey(display, kKeycodes[i], modifiers[j], window, True, - GrabModeAsync, GrabModeAsync); - } - } - } - - // Make sure all errors have been reported, then print how many errors we saw. - XSync(display, False); - if (failed_grab_count != 0) { - fprintf(stderr, "Failed to grab %d key combinations.\n", failed_grab_count); - fprintf(stderr, - "This is probably because some hotkeys are already grabbed by the system.\n" - "Unfortunately, these system-wide hotkeys cannot be automatically remapped by\n" - "this tool. However, you can usually configure them manually.\n"); + GrabEachKey(display, window, argc, argv); } // Event loop. From 5d3df9b232d98867850e731eb82fecd9144e5189 Mon Sep 17 00:00:00 2001 From: Alberto Leiva Popper Date: Mon, 9 May 2016 11:34:47 -0500 Subject: [PATCH 2/4] Fix default modifier value. There was a bogus accent getting in the way of Alt+Shift working. Also updated a comment referring to -DXDQ_GREEDY, a compilation flag that was obsoleted in the previous commit. --- unix/xdq.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/unix/xdq.c b/unix/xdq.c index 9b4bada..5d26788 100644 --- a/unix/xdq.c +++ b/unix/xdq.c @@ -109,7 +109,7 @@ void GrabEachKey(Display* display, Window window, int argc, char* argv[]) { argv++; argc--; - char *defaultArgv[] = {"Control", "Control+Shift", "Mod1", "Mod1+Śhift"}; + char *defaultArgv[] = {"Control", "Control+Shift", "Mod1", "Mod1+Shift"}; int defaultArgc = arraysize(defaultArgv); if (argc == 0) { argc = defaultArgc; @@ -215,9 +215,8 @@ int main(int argc, char* argv[]) { // Method 2: Grab each individual key combination. // // This solves the cursor-disappearing problem with method 1. We can also - // avoid interfering with system hotkeys by only grabbing ctrl and alt - // individually but not when used together. Compile with -DXDQ_GREEDY if - // you really want to grab everything. + // avoid interfering with system hotkeys by letting the user decide which + // modifier combinations are grabbed and which are not. // We will try to grab all of these modifier combinations. GrabEachKey(display, window, argc, argv); From b52f7e25ce69b2e8f73456a8c216115f2b378767 Mon Sep 17 00:00:00 2001 From: Alberto Leiva Popper Date: Sun, 17 Jul 2016 21:10:58 -0500 Subject: [PATCH 3/4] Listen to keyboard layout changes. The Unix binary now automatically realizes whether the current layout is Dvorak and grabs/ungrabs keys as appropriate. This prevents the binary from shuffling the shortcuts all over the place when the user reverts to Qwerty, for example. This is part of kentonv/dvorak-qwerty#2. (It's missing the Windows implementation) The code also validates argvs better. It now complains if the user sends unknown modifiers as program arguments. These bogus strings used to be simply ignored. --- unix/README.md | 16 +-- unix/xdq.c | 303 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 234 insertions(+), 85 deletions(-) diff --git a/unix/README.md b/unix/README.md index 9ac7f92..9ee002b 100644 --- a/unix/README.md +++ b/unix/README.md @@ -9,19 +9,19 @@ Compile with: If you don't have GCC, and the C compiler you do have is not C99-compliant, try compiling with a C++ compiler instead. -Once compiled, make sure your keyboard layout is set to Dvorak, all locks -are disabled (NumLock, CapsLock, etc) and then run the `xdq` binary. -While running, keys you press while holding control or alt (but not both -together) will be remapped to Qwerty. To stop, just kill `xdq`. +Run with: -If you want to remap other key combinations, see the "ARGUMENTS" section -below. + ./xdq [Modifiers] + +Once active, and whenever your keyboard layout is set to Dvorak, keys you press +while holding `[Modifiers]` (see "Arguments" below) will be remapped to +Qwerty. To stop, just kill `xdq`. ## Background This file implements the "Dvorak-Qwerty" keyboard layout, in which the layout -is normally Dvorak but switches to Qwerty when control or alt is held. There -are two reasons why I prefer this layout over straight Dvorak: +is normally Dvorak but switches to Qwerty when certain modifiers are held. +There are two reasons why I prefer this layout over straight Dvorak: - The common copy/paste hotkeys X, C, and V remain on the left hand, and so can be used while the right hand is on the mouse. diff --git a/unix/xdq.c b/unix/xdq.c index 5d26788..7bf7013 100644 --- a/unix/xdq.c +++ b/unix/xdq.c @@ -2,6 +2,7 @@ // Copyright 2010 Google Inc. All rights reserved. // http://dvorak-qwerty.googlecode.com // Author: Kenton Varda (temporal@gmail.com; formerly kenton@google.com) +// Alberto Leiva (ydahhrk@gmail.com) // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -30,6 +31,8 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#include +#include #include #include #include @@ -43,6 +46,8 @@ unsigned int kModifierKeycodes[] = { 37, 105, // ctrl (L, R) 64, 108, // alt (L, R) 50, 62, // shift (L, R) + 66, 77, 78, // lock (caps, num, scroll) + 133, // Super }; // X keycodes corresponding to keys, regardless of layout. @@ -73,7 +78,7 @@ const char kDvorak[] = // keycode --qwerty--> letter --reverse-dvorak--> new keycode int keycode_mapping[256]; -void InitKeycodeMapping() { +static void InitKeycodeMapping() { int size = arraysize(kKeycodes); int dvorak_to_keycode[128]; @@ -90,101 +95,165 @@ void InitKeycodeMapping() { } } -// We receive X errors if we grab keys that are already grabbed. This is not -// really fatal so we catch them. -int failed_grab_count = 0; -int (*original_error_handler)(Display* display, XErrorEvent* error); +#ifndef MODIFIER_LIMIT +#define MODIFIER_LIMIT 16 +#endif -int HandleError(Display* display, XErrorEvent* error) { - if (error->error_code == BadAccess) { - ++failed_grab_count; - return 0; - } else { - return original_error_handler(display, error); +// These are the enabled modifiers, defined by the user as program arguments. +static unsigned int modifiers[MODIFIER_LIMIT]; +static int modifier_count; + +static int WhineOverModifierCount(unsigned int count) { + fprintf(stderr, "Error: Too many modifiers. (current limit is %u)\n", + MODIFIER_LIMIT); + fprintf(stderr, "Please increase the modifier limit:\n"); + fprintf(stderr, "gcc xdq.c -o xdq -std=gnu99 -O2 -lX11 -DMODIFIER_LIMIT=%u\n", + count); + return 1; +} + +static int InitializeDefaultModifiers(void) { + modifier_count = 4; + if (modifier_count > MODIFIER_LIMIT) { + return WhineOverModifierCount(modifier_count); } + + modifiers[0] = ControlMask; + modifiers[1] = ControlMask | ShiftMask; + modifiers[2] = Mod1Mask; + modifiers[3] = Mod1Mask | ShiftMask; + + printf("Using default modifiers: Ctrl, Ctrl+Shift, Mod1 and Mod1+Shift.\n"); + return 0; } -void GrabEachKey(Display* display, Window window, int argc, char* argv[]) { +static int ParseArgs(int argc, char* argv[]) { // Waste the first argument, since it's just the program name. argv++; argc--; - char *defaultArgv[] = {"Control", "Control+Shift", "Mod1", "Mod1+Shift"}; - int defaultArgc = arraysize(defaultArgv); if (argc == 0) { - argc = defaultArgc; - argv = defaultArgv; + return InitializeDefaultModifiers(); + } + if (argc > MODIFIER_LIMIT) { + return WhineOverModifierCount(argc); } - - // Often, some keys are already grabbed, e.g. by the desktop environment. - // Set an error handler so that we can ignore those. - original_error_handler = XSetErrorHandler(&HandleError); for (int i = 0; i < argc; i++) { - unsigned int modifiers = 0; + modifiers[i] = 0; + char* token = strtok(argv[i], "+"); + Bool first_done = False; - if (strstr(argv[i], "Shift")) { - modifiers |= ShiftMask; - } - if (strstr(argv[i], "CapsLock")) { - modifiers |= LockMask; - } - if (strstr(argv[i], "Control")) { - modifiers |= ControlMask; - } - if (strstr(argv[i], "Mod1")) { - modifiers |= Mod1Mask; - } - if (strstr(argv[i], "Mod2")) { - modifiers |= Mod2Mask; - } - if (strstr(argv[i], "Mod3")) { - modifiers |= Mod3Mask; - } - if (strstr(argv[i], "Mod4")) { - modifiers |= Mod4Mask; - } - if (strstr(argv[i], "Mod5")) { - modifiers |= Mod5Mask; - } - if (modifiers == 0) { - fprintf(stderr, "Could not recognize modifiers in '%s'; ignoring...\n", - argv[i]); - continue; - } + printf("Using modifier '"); + while (token) { + if (first_done) { + printf("+"); + } + + if (strcasecmp(token, "Shift") == 0) { + modifiers[i] |= ShiftMask; + } else if (strcasecmp(token, "CapsLock") == 0) { + modifiers[i] |= LockMask; + } else if (strcasecmp(token, "Control") == 0) { + modifiers[i] |= ControlMask; + } else if (strcasecmp(token, "Mod1") == 0) { + modifiers[i] |= Mod1Mask; + } else if (strcasecmp(token, "Mod2") == 0) { + modifiers[i] |= Mod2Mask; + } else if (strcasecmp(token, "Mod3") == 0) { + modifiers[i] |= Mod3Mask; + } else if (strcasecmp(token, "Mod4") == 0) { + modifiers[i] |= Mod4Mask; + } else if (strcasecmp(token, "Mod5") == 0) { + modifiers[i] |= Mod5Mask; + } else { + printf("\n"); + fprintf(stderr, "I don't know what '%s' is; time to panic.\n", token); + return 1; + } - for (int j = 0; j < arraysize(kKeycodes); j++) { - XGrabKey(display, kKeycodes[j], modifiers, window, True, - GrabModeAsync, GrabModeAsync); + printf("%s", token); + first_done = True; + token = strtok(NULL, "+"); } + printf("'.\n"); } - // Make sure all errors have been reported, then print how many errors we saw. - XSync(display, False); - if (failed_grab_count != 0) { - fprintf(stderr, "Failed to grab %d key combinations.\n", failed_grab_count); - fprintf(stderr, - "This is probably because some hotkeys are already grabbed by the system.\n" - "Unfortunately, these system-wide hotkeys cannot be automatically remapped by\n" - "this tool. However, you can usually configure them manually.\n"); + modifier_count = argc; + return 0; +} + +static Bool IsLayoutDvorak(Display* display) { + // If the alphabetic characters of the "main" row are Dvorak, we will assume + // the whole layout is Dvorak. + // This is because other variants such as Svorak and british Dvorak are + // similar enough to original Dvorak that this program might also be used to + // map their keys. It'll be rough around the edges, but at least the + // alphabetic characters will work. + // (I don't seem to have access to the name of the layout...) + // I'm hoping the middle row is the most representative, even if I have often + // been tempted to create a custom layout that swaps "e" and "o" :/ + const char mainCharas[] = "aoeuidhtns"; + const KeySym mainKeycodes[] = {38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + Bool isDvorak = True; + + // Notice: arraysize(mainKeycodes) is better than arraysize(mainCharas) + // because of the null chara. + for (unsigned int c = 0; c < arraysize(mainKeycodes); c++) { + KeySym sym = XkbKeycodeToKeysym(display, mainKeycodes[c], 0, 0); + /* printf(" Testing keycode %lu (index %u). Dvorak:%u (%c) Current:%lu\n", + mainKeycodes[c], c, mainCharas[c], mainCharas[c], sym); */ + if (sym != mainCharas[c]) { + isDvorak = False; + break; + } } + + return isDvorak; } -int main(int argc, char* argv[]) { - InitKeycodeMapping(); +static void WaitUntilLayoutIsDvorak(Display* display) { + printf("Layout is not Dvorak. Waiting...\n"); + Bool done = False; - // Open the display and get the root window. - Display* display = XOpenDisplay(NULL); + do { + XEvent event; + XNextEvent(display, &event); - if (display == NULL) { - fprintf(stderr, "Couldn't open display.\n"); - return 1; + if (event.type == MappingNotify) { + XMappingEvent *e = (XMappingEvent *) &event; + if (e->request == MappingKeyboard && IsLayoutDvorak(display)) { + done = True; + } + XRefreshKeyboardMapping(e); + } else { + fprintf(stderr, "Unknown event: %d\n", event.type); + } + } while (!done); +} + +// We receive X errors if we grab keys that are already grabbed. This is not +// really fatal so we catch them. +static int failed_grab_count; +static int (*original_error_handler)(Display* display, XErrorEvent* error); + +static int HandleError(Display* display, XErrorEvent* error) { + if (error->error_code == BadAccess) { + ++failed_grab_count; + return 0; + } else { + return original_error_handler(display, error); } +} - Window window = DefaultRootWindow(display); +#define GRAB_METHOD 2 + +static void GrabKeys(Display* display, Window window) { + printf("Layout is Dvorak; grabbing keyboard.\n"); + failed_grab_count = 0; // Establish grabs to intercept the events we want. - if (0) { + if (GRAB_METHOD == 1) { // Method 1: Grab the actual modifier keys. // // The keycodes here are for left control, right control, left alt, and @@ -206,12 +275,16 @@ int main(int argc, char* argv[]) { // it get the event, or will we? If we get the event, we'll forward it // to the focused window, which means whatever the system wanted to do // with it won't happen, which would be bad. - int keycodes[] = {37, 64, 109, 115}; - for (int i = 0; i < arraysize(keycodes); i++) { - XGrabKey(display, keycodes[i], 0, window, True, - GrabModeAsync, GrabModeAsync); - } - } else { + // + // Note(ydahhrk): I commented this out because it doesn't work anymore after + // the modifier refactor. + + // int keycodes[] = {37, 64, 109, 115}; + // for (int i = 0; i < arraysize(keycodes); i++) { + // XGrabKey(display, keycodes[i], 0, window, True, + // GrabModeAsync, GrabModeAsync); + // } + } else if (GRAB_METHOD == 2) { // Method 2: Grab each individual key combination. // // This solves the cursor-disappearing problem with method 1. We can also @@ -219,8 +292,73 @@ int main(int argc, char* argv[]) { // modifier combinations are grabbed and which are not. // We will try to grab all of these modifier combinations. - GrabEachKey(display, window, argc, argv); + for (int i = 0; i < modifier_count; i++) { + for (int j = 0; j < arraysize(kKeycodes); j++) { + XGrabKey(display, kKeycodes[j], modifiers[i], window, True, + GrabModeAsync, GrabModeAsync); + } + } + } else { + fprintf(stderr, "Please fix GRAB_METHOD...\n"); + } + + // Make sure all errors have been reported, then print how many errors we saw. + XSync(display, False); + if (failed_grab_count != 0) { + fprintf(stderr, "Failed to grab %d key combinations.\n", failed_grab_count); + fprintf(stderr, + "This is probably because some hotkeys are already grabbed by the system.\n" + "Unfortunately, these system-wide hotkeys cannot be automatically remapped by\n" + "this tool. However, you can usually configure them manually.\n"); + } +} + +static void UngrabKeys(Display* display, Window window) { + printf("Ungrabbing keyboard.\n"); + + if (GRAB_METHOD == 1) { + // int keycodes[] = {37, 64, 109, 115}; + // for (int i = 0; i < arraysize(keycodes); i++) { + // XUngrabKey(display, keycodes[i], 0, window); + // } + } else if (GRAB_METHOD == 2) { + for (int i = 0; i < modifier_count; i++) { + for (int j = 0; j < arraysize(kKeycodes); j++) { + XUngrabKey(display, kKeycodes[j], modifiers[i], window); + } + } + } else { + fprintf(stderr, "Please fix GRAB_METHOD...\n"); + } +} + +int main(int argc, char* argv[]) { + InitKeycodeMapping(); + + // Open the display and get the root window. + Display* display = XOpenDisplay(NULL); + if (display == NULL) { + fprintf(stderr, "Couldn't open display.\n"); + return 1; + } + Window window = DefaultRootWindow(display); + // 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. + // http://stackoverflow.com/questions/35569562 :> + XKeysymToKeycode(display, XK_F1); + + if (ParseArgs(argc, argv)) { + return 1; + } + + if (!IsLayoutDvorak(display)) { + WaitUntilLayoutIsDvorak(display); } + // Often, some keys are already grabbed, e.g. by the desktop environment. + // Set an error handler so that we can ignore those. + original_error_handler = XSetErrorHandler(&HandleError); + GrabKeys(display, window); // Event loop. XEvent down, up; @@ -308,6 +446,17 @@ int main(int argc, char* argv[]) { break; } + case MappingNotify: { + // This is what happens when the user switches keyboard layout. + XMappingEvent *e = (XMappingEvent *) &event; + if (e->request == MappingKeyboard && !IsLayoutDvorak(display)) { + UngrabKeys(display, window); + WaitUntilLayoutIsDvorak(display); + GrabKeys(display, window); + } + XRefreshKeyboardMapping(e); + break; + } default: fprintf(stderr, "Unknown event: %d\n", event.type); break; From 75069aa982ae0399c7dcd1a85dbc52189a160fa2 Mon Sep 17 00:00:00 2001 From: Alberto Leiva Popper Date: Sun, 17 Jul 2016 23:29:23 -0500 Subject: [PATCH 4/4] Follow naming convention for macros. I forgot that macros meant to be editable during compilation are supposed to have "XDQ_" as prefix. Fixed for XDQ_MODIFIER_LIMIT. --- unix/xdq.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/unix/xdq.c b/unix/xdq.c index 7bf7013..7b1f6f9 100644 --- a/unix/xdq.c +++ b/unix/xdq.c @@ -95,26 +95,26 @@ static void InitKeycodeMapping() { } } -#ifndef MODIFIER_LIMIT -#define MODIFIER_LIMIT 16 +#ifndef XDQ_MODIFIER_LIMIT +#define XDQ_MODIFIER_LIMIT 16 #endif // These are the enabled modifiers, defined by the user as program arguments. -static unsigned int modifiers[MODIFIER_LIMIT]; +static unsigned int modifiers[XDQ_MODIFIER_LIMIT]; static int modifier_count; static int WhineOverModifierCount(unsigned int count) { fprintf(stderr, "Error: Too many modifiers. (current limit is %u)\n", - MODIFIER_LIMIT); + XDQ_MODIFIER_LIMIT); fprintf(stderr, "Please increase the modifier limit:\n"); - fprintf(stderr, "gcc xdq.c -o xdq -std=gnu99 -O2 -lX11 -DMODIFIER_LIMIT=%u\n", + fprintf(stderr, "gcc xdq.c -o xdq -std=gnu99 -O2 -lX11 -DXDQ_MODIFIER_LIMIT=%u\n", count); return 1; } static int InitializeDefaultModifiers(void) { modifier_count = 4; - if (modifier_count > MODIFIER_LIMIT) { + if (modifier_count > XDQ_MODIFIER_LIMIT) { return WhineOverModifierCount(modifier_count); } @@ -135,7 +135,7 @@ static int ParseArgs(int argc, char* argv[]) { if (argc == 0) { return InitializeDefaultModifiers(); } - if (argc > MODIFIER_LIMIT) { + if (argc > XDQ_MODIFIER_LIMIT) { return WhineOverModifierCount(argc); }