diff --git a/unix/README.md b/unix/README.md new file mode 100644 index 0000000..9ee002b --- /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. + +Run with: + + ./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 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. +- 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..7b1f6f9 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 @@ -29,62 +30,9 @@ // (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 #include #include #include @@ -98,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. @@ -128,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]; @@ -145,12 +95,149 @@ void InitKeycodeMapping() { } } +#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[XDQ_MODIFIER_LIMIT]; +static int modifier_count; + +static int WhineOverModifierCount(unsigned int count) { + fprintf(stderr, "Error: Too many modifiers. (current limit is %u)\n", + XDQ_MODIFIER_LIMIT); + fprintf(stderr, "Please increase the modifier limit:\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 > XDQ_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; +} + +static int ParseArgs(int argc, char* argv[]) { + // Waste the first argument, since it's just the program name. + argv++; + argc--; + + if (argc == 0) { + return InitializeDefaultModifiers(); + } + if (argc > XDQ_MODIFIER_LIMIT) { + return WhineOverModifierCount(argc); + } + + for (int i = 0; i < argc; i++) { + modifiers[i] = 0; + char* token = strtok(argv[i], "+"); + Bool first_done = False; + + 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; + } + + printf("%s", token); + first_done = True; + token = strtok(NULL, "+"); + } + printf("'.\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; +} + +static void WaitUntilLayoutIsDvorak(Display* display) { + printf("Layout is not Dvorak. Waiting...\n"); + Bool done = False; + + do { + XEvent event; + XNextEvent(display, &event); + + 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. -int failed_grab_count = 0; -int (*original_error_handler)(Display* display, XErrorEvent* error); +static int failed_grab_count; +static int (*original_error_handler)(Display* display, XErrorEvent* error); -int HandleError(Display* display, XErrorEvent* error) { +static int HandleError(Display* display, XErrorEvent* error) { if (error->error_code == BadAccess) { ++failed_grab_count; return 0; @@ -159,21 +246,14 @@ int HandleError(Display* display, XErrorEvent* error) { } } -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; - } +#define GRAB_METHOD 2 - Window window = DefaultRootWindow(display); +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 @@ -195,53 +275,31 @@ 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 - // 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. - 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); + 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. @@ -253,6 +311,54 @@ int main(int argc, char* argv[]) { "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; @@ -340,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;