Manipulate modifiers while keeping OS key repeat ability

I have a feeling I am going into well-discussed territory here.
There exists a number of posts that discuss how to change modifiers. I think I have read them all by now. You have the ShapeShifter plugin that changes the key typed while holding down shift. Someone made a ModifierLayers plugin that maps a whole layer to e.g. Shift. (I think. It uses the old plugin system, and I am too much a noob to know how to get it to work).

The exact problem I am trying to solve is that, depending on the key, I want AltGr/Right Alt to be held instead of shift.

I want to, when I press Shift + 2, create @ instead of ".

I know this has also been discussed on the forums, but the solution in those cases seemed to be to switch keyboard layouts on the OS level from (in my case) danish to US.

I don’t want to that.

Currently I am using US layout because I like that layout better. But the rest of my family and my coworkers are pretty annoyed every time they need to use my keyboard (which has danish layout). And with my first 100% programmable keyboard (the Keyboardio, duh) I want to switch all my PCs to Danish, but configure the Keyboardio to use the US layout. But that means I have to be able to switch modifier from shift to altgr.

And before you ask: Yes, I will keep both keyboards attached. The normal keyboard for all the normal people, and the Keyboardio for me.

With macros I was able to put one on e.g. the “2” key that produced a @ even with danish keyboard layout when shift was held. But it wouldn’t repeat. Do I need that repeat? Probably not, but out of sheer principle I want the keyboard to behave exactly like normal. Curse my OCD!

I have so far come up with this:

EventHandlerResult MyAwesomePlugin::onKeyswitchEvent(Key &mapped_key, byte row,
                                              byte col, uint8_t key_state) {
  uint8_t modifiers = Keyboard.lastKeyReport.modifiers;
  
  if(mapped_key == Key_2) {
    set_key_ = false;

    // Check if only shifts are pressed
    if(!(modifiers & ~SHIFT_KEYS) && (modifiers & SHIFT_KEYS)) {
      if(keyToggledOn(key_state)) {
        // If key was just pressed
        // and set the modifiers to AltGr
        set_key_ = true;
      } else if(keyIsPressed(key_state)) {
        // If key is pressed

        // Have non-shift modifiers been pressed?
        if(modifiers & ~SHIFT_KEYS) {
          return EventHandlerResult::OK;
        }

        // Set modifiers to AltGr
        set_key_ = true;
      } else if(keyToggledOff(key_state)) {
        // Some debugging message I removed.
      }
    }
  }
  return EventHandlerResult::OK;
}

At first I had everything in onKeyswitchEvent, but then I read about how the report could be incomplete at just about any stage until it was sent over the wire. So I tried to make the changes as late as possible and used beforeReportingState:

EventHandlerResult MyAwesomePlugin::beforeReportingState() {
  if(set_key_) {
    Keyboard.keyReport.modifiers = RIGHT_ALT_KEY;
  }
  if(!memcmp(Keyboard.lastKeyReport.allkeys, 
             Keyboard.keyReport.allkeys, 
             sizeof(Keyboard.keyReport))) {
    // Here we end up if the reports are the same. At this point the keyboard should 
    // not send a new report. That logic is elsewhere in the firmware. When I hold 
    // down shift I never end up here, although the report modifiers have been set to
    // RIGHT_ALT_KEY. I think there is something else going on in the firmware, but I
    // have no idea how to find out.
  }
  return EventHandlerResult::OK;
}

I cannot figure out what is happening since the reports are not the same. What goes into allkeys?
If I just press any other key than 2 and shift I get key repeat, but not with 2 and shift.
Without knowing anything really about Kaleidoscope my guess is that because I hold down shift the whole time, and I change the modifier to not have shift but right alt instead, the firmware still sees that shift at a time when it needs to figure out if a report should be sent.

Do I make myself clear, or am I just sounding like the noob I am?

So you want to get an @ when you hit Shift+2, even though you’re using the Danish layout? Would something like this work?

EventHandlerResult MyAwesomePlugin::onKeyswitchEvent(Key &mapped_key, byte row,
                                              byte col, uint8_t key_state) {
  if(mapped_key == Key_2) {
    if(mapped_key.flags == SHIFT_HELD) {
      if(keyIsPressed(key_state)) {
        hid::pressKey(RALT(Key_2));
        return EventHandlerResult::EVENT_CONSUMED;
      }
  }
  return EventHandlerResult::OK;
}
1 Like

Unfortunately not.
It seems

mapped_key.flags == SHIFT_HELD

doesn’t evaluate to true.

Then swap in your modifiers check?

With this:

if(mapped_key == Key_2) {
  Serial.println("Key_2 event");
  if(!(modifiers & ~SHIFT_KEYS) && (modifiers & SHIFT_KEYS)) {
    Serial.println("SHIFT_HELD");
    if(keyIsPressed(key_state)) {
      Serial.println("keyIsPressed");
      hid::pressKey(RALT(Key_2));
      return EventHandlerResult::EVENT_CONSUMED;
    }
  }
}

I now get “keyIsPressed” in the console, but nothing is printing with shift down. Neither 2, " nor @.

My guess is that, from Kaleidoscopes point of view, shift is still down (because I am pressing it down physically) and by default shift+right-alt+2 doesn’t produce any characters.

Per my understanding, that shouldn’t be the case. Try replacing the call to pressKey with hid::pressKey(Key_2);. You should get a 2, not ".

What happens if you manually type AltGr+2?

If I press Shift+2 I still get ".

But I do notice something funny.

If I first press 2 and keep it down, I can see 2s printed with Windows’ typical speed. But if I then press down shift the "s goes crazy. I think this is because Kaleidoscope is generating the hid::pressKey(Key_2) events as fast as it can and to Windows they are a bunch of fast incoming keydowns and keyups so it’s Kaleidoscope that determines the speed and not Windows.

If I press AltGr+2 I get @ (I use the AltGr from my normal keyboard)
If I then also press down Shift I get nothing.

1 Like

I was dissatisfied with the lack of ability to completely dissociate shifted symbols from unshifted symbols in the keyboard firmware, and ended up writing a completely independent firmware as a result. I did this because, at the time it was impossible to implement my Unshifter plugin for Kaleidoscope with its system of tracking keyboard state in the HID report. Unshifter lets me arbitrarily rearrange symbols such that holding shift in combination with a key can produce any arbitrary key value, even ones that aren’t shift-modified in the HID report. For example, I have a key in my keymap that’s , without shift, but ; with it.

There have been some changes to Kaleidoscope since I first wrote it, so I might be able to port Unshifter now. I’m not very optimistic, but it’s possible that I could make it work. If so, I’m pretty sure it would solve your problem.

2 Likes

Whoops, I should have had you use something other than Key_2, but it doesn’t matter; looking in hid.cpp, it seems that hid::pressKey() uses the modifiers currently pressed, as well as any modifiers explicitly set in the Key's flags.

I did, however, notice another function that might be useful, namely releaseModifiers(). In your if block, try this:

hid::releaseModifiers(SHIFT_HELD);
hid::pressKey(RALT(Key_2));
hid::pressModifiers(SHIFT_HELD);

I don’t know how to help with the repeat speed, unfortunately. I encountered the same thing with another plugin I wrote, but I was able to skirt around the issue because the plugin called for me to disable repeat altogether.

One important thing to understand is that the keyboard doesn’t send key press and release events to the host. Instead, it sends a HID report that contains (effectively) a list of all the keys that are currently pressed at the time. Kaleidoscope starts every scan cycle with an empty report, and adds keycodes as it goes, on key at a time. Once it has processed all the keys, it then sends the complete report to the host.

One problem you may have is that the modifier key you’re testing for might have been pressed before the 2 key, but it might be scanned after it in the scan order. If a modifier needs to be unset, it should be done in the beforeReportingState() hook, not in onKeyswitchEvent().

Also, the hid::press*() and hid::release*() functions don’t send HID reports, they only set and unset keycodes in the report, so calling hid::releaseModifiers() followed by hid::pressModifiers() with the same argument will only have an effect if a report is sent in between those two calls. And sending extra reports like that tends to cause rapid key repeats, so it’s far better to ensure that the normal end-of-cycle report contains only what it should.

1 Like

Hmmm… If I try to use hid::releaseModifiers (in beforeReportingState as per Michaels comment) I get this error in the Arduino IDE:

<snip>\MyAwesomePlugin.cpp:108:5: error: 'releaseModifiers'
                                    is not a member of 'kaleidoscope::hid'

It’s kaleidoscope::hid::pressKey() & kaleidoscope::hid::releaseKey(). Sorry about not checking the function names first.

Ah, it turns out that releaseModifiers and pressModifiers are both in an anonymous namespace, and so are unaccessible. I didn’t notice earlier, because they share the same level of indentation as the rest of the kaleidoscope::hid namespace. My bad.

Right. I put those functions in that namespace because they’re intended to be helper functions, and shouldn’t be called by plugins directly. Similarly, it’s unwise to directly call functions in KeyboardioHID.

I was about to ask about the *Modifiers functions not being accessible, but you cleared it up for me. :slightly_smiling_face:

Noted. If there are other ways to accomplish this I am all ears.

With this:

EventHandlerResult MyAwesomePlugin::beforeReportingState() {
  if(set_key_) { 
    Serial.println("Press AltGr");
    kaleidoscope::hid::releaseKey(Key_LeftShift);
    kaleidoscope::hid::pressKey(Key_RightAlt);
  }
  return EventHandlerResult::OK;
}

I, of course, get “Press AltGr” all the time after the first press. I need some way to tell the system to unpress it again. I haven’t found the correct place for “set_key_ = false;” yet.
Is beforeReportingState called before afterEachCycle? And how do the two functions relate?
I need to somehow check if anything other than “2” and SHIFT is pressed and in that case set set_key_ to false - or something equivalent to that. But I need to do it as late in the process (cycle?) as possible.

I think you should use three hooks and two boolean variables (two_pressed_ & shift_pressed_):

afterEachCycle() — Set both member variables to false. This hook gets called after the report is sent. I don’t recall if it’s before or after the report is cleared, but that doesn’t matter in this case, since we’re doing the equivalent operation.

onKeyswitchEvent() — Test each pressed key. If mappedKey is Key_2, set two_pressed_ to true. Likewise if a shift key is pressed, set the other state variable. This hook is called during the scan cycle for each key that’s not idle.

beforeReportingState() — If both state variables are true, call kaleidoscope::hid::releaseKey() on both shift keys, and pressKey() on Key_RightAlt. This function gets called after the scan cycle is finished, but before the report is sent.

Note that it’s never necessary to call releaseKey(KeyRightAlt), because all keycodes get cleared from the report after the report is sent each cycle.

1 Like

Yes! That is exactly the behavior I want. Thank you :smiley:

And I even learned a lot at the same time.

1 Like