QMK in the New Model F Keyboard

elcano

26 Oct 2020, 04:39

I received my new F77 keyboard last week and soon noticed that what I wanted to do wasn't possible with the QMK Online Configurator. Therefore, I have been using the local compilation.

My priority is programming characters with diacritics for us in Spanish in Windows and Linux (á, ñ, ü, etc.) with as minimal OS modification as possible. The other was enabling Dynamic Macros.

The first objective was a partial success. I created 2 base layouts (1 for Windows and another for Linux) and switch them with programmed keys. Linux works perfectly. However, one of my diacritics is not working in Windows. While á, é, í, ó, ú and ü work fine, when I send to ñ in a browser it also triggers a Browser Back Key. Therefore, the entry is lost. I don't understand what is happening.

This is the code for ñ:

Code: Select all

        case NHAT:
            if (record->event.pressed) {
                // when keycode NHAT pressed
                SEND_STRING(SS_DOWN(X_LALT) SS_TAP(X_P1) SS_TAP(X_P6) SS_TAP(X_P4) SS_UP(X_LALT));
            } else {
                // when keycode NHAT is released
            }
            break;
In contrast, this is the code for á, which works perfectly in Windows:

Code: Select all

        case AHAT:
            if (record->event.pressed) {
                // when keycode AHAT pressed
                SEND_STRING(SS_DOWN(X_LALT) SS_TAP(X_P1) SS_TAP(X_P6) SS_TAP(X_P0) SS_UP(X_LALT)); 
            } else {
                // when keycode AHAT is released
            }
            break;
I don't have explanation for this problem. Any idea is welcomed.

The other objective that I had was dynamic macros. Whenever I enable this, the keyboard becomes non-responsive. I even got to disassemble it to put it in booloader mode. Has anybody experienced this? Anybody knows why is this happening or what I could be doing wrong?

Got bless the LSHIFT+RSHIFT+B combination that places it in bootloader mode even it your don't assign a key for it. BTW, how is this happening? Where in the directory structure is the file that is being included into the compile so that this happens? I'm curious about the INCLUDE that is pulling this magic.

The other problem that I'm facing is that sometimes when I press the Backspace twice the keyboard generates a print screen. It is not always, however. I wondered if like the LSHIFT+RSHIFT+B combination, there was a Tap Dance program activated by a sequence that I don't understand. However, while I type this I realize that the print screen key is next to the backspace. What if the problem is not software? I'm going to deactivate that key from the base layer to see if the problem disappears. Anyway, I'm curious about what happens in the back that I don't see in my keymap.c.

Thanks

zzxx53

03 Nov 2020, 00:53

I get a suspicion that this has something to do with your Numlock status. If numlock is off, numpad 4 becomes left arrow, and alt+left arrow will trigger a browser back.

elcano

02 Dec 2020, 03:51

Thank you. I gave up and installed WinCompose. Now it is working perfectly. I wish I didn't have to install anything this app works nice. Thanks for the tip anyway. I will check it later.

cakeanalytics

02 Dec 2020, 19:11

elcano wrote:
02 Dec 2020, 03:51
Thank you. I gave up and installed WinCompose. Now it is working perfectly. I wish I didn't have to install anything this app works nice. Thanks for the tip anyway. I will check it later.
It's generally a pain to get a keyboard to output non-ASCII characters. Keyboards can only output scan codes, and it's up to the OS to interpret them & display the correct characters.
Depending on your selected keyboard layout, you might have better luck using a AltGr+Letter combination. If you are on US-International keyboard layout, AltGr+N (or Ctrl+Alt+N) will get you ñ.

elcano

24 Jan 2021, 04:21

The problem that I'm trying to solve has to do with eliminating the use of the US-International layout.
When using that layout you must confirm your single and double quotes with the space bar. That totally destroys my flow when programming. I have used that layout for years, but when remote programming on a peer's laptop I enter the space after each ' or " by instinct.

The Microsoft PowerToys team have logged some some related functionality as issues in GitHub. Hopefully they develop a solution. It's not as good as OS native solution, but the next best alternative.

User avatar
darkcruix

24 Jan 2021, 11:13

I have played around with the same as I am running into exactly the same problem. For me the way to go was to have both US International and US in the OS (e.g. Alt+Shift in Windows / Ctrl+Alt+Space in Mac OS). Still, it is not ideal, as it requires the back and forth. Normally I stay within US layout and only when I type non-US texts, I switch over.
Sorry for not having a good solution to try.

elcano

08 Feb 2021, 03:56

I think that I found the error in my code. My newly installed Num Lock LEDs proved to be useful.

I was missing that the in the Model F Keyboard the Alt+Numpad codes only work when the keyboard has the Num Lock mode is activated. Therefore, When I was trying to enter Alt+160 (for the á character) using a macro in QMK, if Num Lock was not activated it was just sending Alt+END+{RIGHT_ARROW}+INSERT instead.

A very unrefined macro that works for the same character would be like this:

Code: Select all

        case AHAT:
            if (record->event.pressed) {
                // when keycode AHAT pressed
				if (host_keyboard_led_state().num_lock) {
                    SEND_STRING(SS_DOWN(X_LALT) SS_TAP(X_P1) SS_TAP(X_P6) SS_TAP(X_P0) SS_UP(X_LALT)); 
				} else {
                    SEND_STRING(SS_TAP(X_NLCK) SS_DOWN(X_LALT) SS_TAP(X_P1) SS_TAP(X_P6) SS_TAP(X_P0) SS_UP(X_LALT) SS_TAP(X_NLCK));
				}
            } else {
                // when keycode AHAT is released
            }
            break;
The code above works perfectly in Windows with or without WinCompose activated and regardless of whether the keyboard is in UNICODE_SELECTED_MODES = UC_WIN or UC_WINC. If the Num Lock is not active, it will be turned on for a fraction of a second (the LED will be seen flashing in my case). These are great news because I have not been able to use this keyboard with my company laptop because I'm not allowed to install WinCompose there.

However, I would like to create a structure were I define my Alt sequences like this:

Code: Select all

AHAT = [X_P1, X_P6, X_P0]
EHAT = [X_P1, X_P3, X_P0]
and define a function that process the with Num Lock, Alt modifier and everything, so that when I call the function the proper sequence is sent:

Code: Select all

send_alt_code(AHAT);
However, I don't know how to do that. So, I will have to do repetitive code while it fits in the EEPROM memory.

The other limitation in comparison to the use of Unicode is that I have to define Layers for the shifted versions of these characters. I wish that we could apply macros for shifted keys in all layers as easily as we do for Unicode with XP().

pandrew

09 Feb 2021, 02:51

Hey, I've implemented something for you, try this:

Code: Select all

void send_alt_code_P(const PGM_P strlo, const PGM_P strhi) {
    bool num_lock = host_keyboard_led_state().num_lock;
    uint8_t lshift = keyboard_report->mods & MOD_BIT(KC_LSHIFT);
    uint8_t rshift = keyboard_report->mods & MOD_BIT(KC_RSHIFT);
    const PGM_P str = strlo;
    if (lshift || rshift) str = strhi;
    if (lshift) unregister_code(KC_LSHIFT);
    if (rshift) unregister_code(KC_RSHIFT);
    if (!num_lock) SEND_STRING(SS_TAP(X_NLCK));
    SEND_STRING(SS_DOWN(X_LALT));
    while (1) {
        char ch = pgm_read_byte(str++);
        if (!ch) break;
        if ((ch < '0') || (ch > '9')) break;
        uint16_t code = KC_KP_0;
        if (ch >= '1') code = ch - '1' + KC_KP_1; // KC_KP_1..9 are continuous, but KC_KP_0 is not before 1.
        register_code(code);
        unregister_code(code);
    }
    SEND_STRING(SS_UP(X_LALT));
    if (!num_lock) {
        SEND_STRING(SS_TAP(X_NLCK));
    }
    if (lshift) register_code(KC_LSHIFT);
    if (rshift) register_code(KC_RSHIFT);
    wait_ms(15); // seems to be necessary when sending two alt-codes one next to the other. 9ms was sufficient on my system
}

#define SEND_ALT_CODE(stringlow, stringhigh) send_alt_code_P(PSTR(stringlow), PSTR(stringhigh))
The send_alt_code_P function takes two strings in program memory only. I didn't implement the non-program-memory version because it would be wasteful. You can give string literals to SEND_ALT_CODE, and it will put them in program memory automatically.

So use like this, to send é (or É when shift is pressed):

Code: Select all

SEND_ALT_CODE("130", "0201");
You could also store them separately if you whish,
Unfortunately you must declare the strings separately from the array, I don't think there's a way to tell the compiler to put a full array of strings including the strings in program memory without being very explicit like this:

Code: Select all

const char AHATlo[] PROGMEM = "160";
const char AHAThi[] PROGMEM = "0193";
const char EHATlo[] PROGMEM = "130";
const char EHAThi[] PROGMEM = "0201";

const PGM_P const codes[][2] PROGMEM = {
    {AHATlo, AHAThi,},
    {EHATlo, EHAThi,},
};
Then you can use like:

Code: Select all

int index = 0;
send_alt_code_P((PGM_P)pgm_read_word(&codes[index][0]), (PGM_P)pgm_read_word(&codes[index][1]));
And you could do something clever, like reserving a range of custom keycodes for your custom altcoded characters, and then instead of a massive switch statement in process_record_user, you could just check if the keycode is in your range, and if yes, then substract the starting keycode from the keycode, and use that as an index.

Note: I only tested this on a linux machine, and a windows VM. On a real system you may need to introduce some more wait_ms() calls, but if your previous code examples worked, then I think this will most likely also work.
Last edited by pandrew on 09 Feb 2021, 05:56, edited 1 time in total.

elcano

09 Feb 2021, 04:36

Wow. Ohh, wow. Thanks!

This is impressive. It is too late to test today and might not be able to test until late tomorrow, but I'm already salivating. :-)

I cannot believe how much C Language has evolved since the mid '90s. I used to say that C was my second language, after Spanish (much ahead of English). I used to program microprocessors and even 68HC11 microcontrollers in C (using a primitive C to assembly cross compiler). Now I can barely follow it. But I will continue tweaking and reading until I get up to speed again.

Thanks again!

elcano

10 Feb 2021, 01:27

Pandrew, I tested the first half of it and it words flawlesly. This is solving my primary need from the programmable features of this keyboard. I don't have much time today (will work until very late), but wanted to document this for other people reading this in the future,

This is implemented as a QMK macro per character. Therefore, it is necessary to define the name of each macro first. Here I'm defining 7 additional macro names, in addition to the ones that come by default:

Step 1:

Code: Select all

// Defines the keycodes used by our macros in process_record_user
enum custom_keycodes {
// Demo code
    QMKBEST = SAFE_RANGE,
    QMKURL,
    AHAT,
    EHAT,
    IHAT,
    OHAT,
    UHAT,
    UEYES,
    NHAT,
};
Step 2:
Add the macro in the alternate layers of your keyboard layout directly. Here is a portion for the u, i, o keys:

Code: Select all

KC_NO, UHAT, IHAT, OHAT, KC_NO,
Step 3:
Define your macros. Here I'm including only the macro for the EHAT (é, É):

Code: Select all

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
    switch (keycode) {
        case EHAT:
            if (record->event.pressed) {
                // when keycode EHAT pressed
				SEND_ALT_CODE("130", "0201");
            } else {
                // when keycode EHAT is released
            }
            break;
    }
    return true;
Step 4:
Include the function and preprocessor macro that makes everything work together:

Code: Select all

void send_alt_code_P(const PGM_P strlo, const PGM_P strhi) {
    bool num_lock = host_keyboard_led_state().num_lock;
    uint8_t lshift = keyboard_report->mods & MOD_BIT(KC_LSHIFT);
    uint8_t rshift = keyboard_report->mods & MOD_BIT(KC_RSHIFT);
    const PGM_P str = strlo;
    if (lshift || rshift) str = strhi;
    if (lshift) unregister_code(KC_LSHIFT);
    if (rshift) unregister_code(KC_RSHIFT);
    if (!num_lock) SEND_STRING(SS_TAP(X_NLCK));
    SEND_STRING(SS_DOWN(X_LALT));
    while (1) {
        char ch = pgm_read_byte(str++);
        if (!ch) break;
        if ((ch < '0') || (ch > '9')) break;
        uint16_t code = KC_KP_0;
        if (ch >= '1') code = ch - '1' + KC_KP_1; // KC_KP_1..9 are continuous, but KC_KP_0 is not before 1.
        register_code(code);
        unregister_code(code);
    }
    SEND_STRING(SS_UP(X_LALT));
    if (!num_lock) {
        SEND_STRING(SS_TAP(X_NLCK));
    }
    if (lshift) register_code(KC_LSHIFT);
    if (rshift) register_code(KC_RSHIFT);
    wait_ms(15); // seems to be necessary when sending two alt-codes one next to the other. 9ms was sufficient on my system
}

#define SEND_ALT_CODE(stringlow, stringhigh) send_alt_code_P(PSTR(stringlow), PSTR(stringhigh))

Post Reply

Return to “Keyboards”