qmk / qmk_firmware

Open-source keyboard firmware for Atmel AVR and Arm USB families
https://qmk.fm
GNU General Public License v2.0
18.31k stars 39.43k forks source link

inverting a key #401

Closed guni77 closed 8 years ago

guni77 commented 8 years ago

I have a simple question

. I use the the compose key to create german umlauts on my american layout.

For example: compose + " + u creates ü.

I would like to invert the function of the KC_QUOT Key. KEY gives " LSHT(KEY) gives '

I did not find a way to do that. At this time a use xmodmap in linux to do that.

Any suggestions?

algernon commented 8 years ago

To achieve a similar thing, I used macros. If you do not have one-shot modifiers, it becomes even simpler:

Is shift pressed? No? Register a Shift + ', netting you a ". If pressed, unregister it, register ', and register shift back.

sergei-dyshel commented 8 years ago

@algernon Would that work for swapping colon <-> semicolon?

  case A_COLN:
    if (keyboard_report->mods & MOD_BIT(KC_LSFT)) {
      if (record->event.pressed) {
        unregister_code(KC_LSFT);
        register_code(KC_SCLN);
        unregister_code(KC_SCLN);
        register_code(KC_LSFT);
      }
    } else {
        register_code(KC_LSFT);
        register_code(KC_SCLN);
        unregister_code(KC_SCLN);
        unregister_code(KC_LSFT);
    }
    break;

Does the order of registering/unregistering shift and colon matter?

fredizzimo commented 8 years ago

I'm actually facing the same issue right now, when making my keymap, so I will try to find a more generic solution, that could easily work with all keymaps.

The first requirement that I have is that shift should be reported as shift when pressed alone, because some applications could speed up mouse operations for example, when shift is held.

The second requirement is that it should not only handle shift. There are actually four different states for each physical key.

  1. Pressed alone
  2. Pressed with shift
  3. Pressed with alt-gr
  4. Pressed with shift and alt-gr

So this generic solution should handle all those combinations, and let you define them exactly like you want, no matter how they are mapped in the operating system keymap.

I'm still a bit torn between if I should implement this as separate layers, or have a macro/function which lets you specify the complete behavior in the same layer. I'm leaning more towards the later, as it will probably be easier to write keymaps that way.

Any thoughts?

And @sergei-dyshel, I can't really answer that question yet. But at the first glance it appears like it would work. Why don't you try it?

fredizzimo commented 8 years ago

After some more thought and investigation, it turns out that the QMK macros, almost supports what we want. But the problem is that they don't support clearing of modifiers.

But if they did we could probably write something like this

  case A_COLN:
    if (record->event.pressed) {
        if (is_shifted(keyboard_report->mods)) {
            return MACRO(R_SHIFT(), T(KC_SCLN));
        } else {
            return MACRO( T(S(KC_SCLN)));
        }
    }

Where R_SHIFT, removes the shift modifier for the duration of the MACRO. The is_shifted is another helper that checks if either right or left shift is pressed.

And of course since it's a macro, we could do just about anything. But perhaps a common thing to do would be to turn a dead key into a normal one, by sending two keystrokes.

Things get a little bit more complicated if you want to support holding the keys down. Especially if layers are switched in the middle. Or if the shifted and unshifted keys represent different physical keys. But the basic idea would be like this (different physical keys used)

  case A_COLN_QUOTE:
   static bool a_coln_quote_was_shift = is_shifted(keyboard_report->mods);
    if (record->event.pressed) {
        if (a_coln_quote_was_shift) {
            return MACRO(R_SHIFT(), D(KC_SCLN));
        } else {
            return MACRO( D(KC_QUOTE));
        }
    } else {
        if (a_coln_quote_was_shift) {
            return MACRO(U(KC_SCLN));
        } else {
            return MACRO( U(KC_QUOTE));
        }
   }

To support all four states normal, shifted, alt-gr, shifted-alt-gr, we could use an uint8_t variable instead of bool, representing the level from 0-3.

One thing to note, is that since the only way to support this in the general case, is to modify the shift and alt-gr states, pressing multiple keys at the same time, might not work as expected.

It would also probably be possible to add some helper functions or macros for the above code snippets. But it should still be possible to write it manually fully customized, if needed.

And at @sergei-dyshel, you need to check the pressed state in both of your branches, so move your ifs around like my example.

piotr-dobrogost commented 8 years ago

One thing to note, is that since the only way to support this in the general case, is to modify the shift and alt-gr states, pressing multiple keys at the same time, might not work as expected.

Could you elaborate?

fredizzimo commented 8 years ago

I was thinking about a case where you press two keys at exactly the same time, and one of them needs shift and the other one doesn't. This kind of combination would not be possible in the same USB report. To support that there would need to be a slight delay, so that the keys are sent in separate reports.

Another case is, if you hold down a key, to repeat it. If that character needs shift, then you can't type unshifted characters at the same time and the other way around.

Some games that reads the keyboard state might also have problems.

However for normal typing, this would not be a problem.

fredizzimo commented 8 years ago

I ended up doing this for now, until we have proper support for this in the library code itself.

void press_key_with_level_mods(uint16_t key) {
    const uint8_t interesting_mods = MOD_BIT(KC_LSHIFT) | MOD_BIT(KC_RSHIFT) | MOD_BIT(KC_RALT);

    // Save the state
    const uint8_t real_mods = get_mods();
    const uint8_t weak_mods = get_weak_mods();
    const uint8_t macro_mods = get_macro_mods();

    uint8_t target_mods = (key >> 8) & (QK_MODS_MAX >> 8);
    // The 5th bit indicates that it's a right hand mod,
    // which needs some fixup
    if (target_mods & 0x10) {
        target_mods &= 0xF;
        target_mods <<= 4;
    }

    // Clear the mods that we are potentially going to modify,
    del_mods(interesting_mods);
    del_weak_mods(interesting_mods);
    del_macro_mods(interesting_mods);

    // Enable the mods that we need
    add_mods(target_mods & interesting_mods);

    // Press and release the key
    register_code(key & 0xFF);
    unregister_code(key & 0xFF);

    // Restore the previous state
    set_mods(real_mods);
    set_weak_mods(weak_mods);
    set_macro_mods(macro_mods);
    send_keyboard_report();
}

void override_key(keyrecord_t* record, uint16_t normal, uint16_t shifted) {
    const uint8_t shift = MOD_BIT(KC_LSFT) | MOD_BIT(KC_RSFT);
    if (record->event.pressed) {
        bool shift_pressed = keyboard_report->mods & shift;
        const uint16_t target = shift_pressed ? shifted : normal;
        uint8_t keycode = target & 0xFF;
        if (keycode == KC_NO) {
            return;
        }
        press_key_with_level_mods(target);
    }
}

const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) {
    switch(id) {
        case LEFT_TAB:
            override_key(record, LSFT(KC_TAB), KC_NO);
            break;
        case RIGHT_TAB:
            override_key(record, KC_TAB, KC_NO);
            break;
        case QUES_EXLM:
            override_key(record, NO_QUES, KC_EXLM);
            break;
        case EURO_PND:
            override_key(record, NO_EURO, NO_PND);
            break;
        case DIAE_TILD:
            override_key(record, SV_DIAE, NO_TILD);
            break;
        case HALF_SECT:
            override_key(record, SV_HALF, SV_SECT);
            break;
        case APOS_QUOT:
            override_key(record, NO_APOS, SV_QUOT);
    }
    return MACRO_NONE;
}

This allows me to map both shifted and unshifted keys like I want. I didn't have any need for the third or fourth level (alt-gr and shift-alt-gr), so that is not handled by my approach. but the ´press_key_with_levelmods` would handle it. It also supports outputting keys with alt-gr, which I'm doing here, as some of the NO, and SV_ keys are alt-gr combinations.

The press_key_with_level_mods`, will temporarily "lift" the shift or alt-gr key if it's needed to output a character. But as soon as the character is typed, it will return the modifiers to it's previous state.

This approach works correctly, if other modfiers(like ctr) are pressed, and won't change the state of them.

piotr-dobrogost commented 8 years ago

@fredizzimo Please use official altgr abbreviation instead of alt-gr so that one (me :)) could find issues like this one easier :) Thanks in advance and sorry for nitpicking.

fredizzimo commented 8 years ago

@piotr-dobrogost,

Ok, I will use AltGr in the future. But now I have to start nitpicking :stuck_out_tongue:. I wouldn't call altgr the official abbreviation, especially not when written in small caps.

  1. All the IBM references that I could find use AltGr
  2. Wikipedia uses AltGr, but with some debate (without real arguments) in the talk section
  3. Most keyboards that I have seen use Alt Gr, with a space
  4. The ISO/IEC 9995, doesn't really specify anything, but says this:
    "For keyboards with characters allocated at level 3, at least one key for the function Level 3 select (frequently marked "Alt Gr") shall be provided."
  5. Alt-Gr is used, but not nearly as much as the other two alternatives.
  6. I would prefer Alt Gr, but have almost always been using the Alt-Gr form to more clearly indicate it's a single key

Searching for Alt Gr, will find both Alt Gr and Alt-Gr, while searching for AltGr will find only AltGr.

ezuk commented 8 years ago

So... since we're all the way to discussing punctuation, can I safely assume the original issue is closed? Closing this for now. Please reopen if not actually closed.

aLTgR, Erez

piotr-dobrogost commented 8 years ago

@fredizzimo

Thanks for taking a look at spelling issue in so much detail :) I'm well aware it should be spelled AltGr or Alt Gr, yet I suggested altgr based on the fact you've already been using lowercase alt-gr. I just wanted to minimize a chance you would stay with alt-gr just not to have to use capital letters :) I agree calling it official is totally wrong of course :)