Algernon's Syster implementation not working with zeros

It seems like hardly anyone is talking about programming anymore, but I am doing some lately. I tried using algernon’s implementation of Syster plugin lately. It’s worked for me in the past but I didn’t really use it so I got rid of it. I’ve changed my workflow lately and I’m going to use it again, but with some changes because it only partially working for me. It behaves really weirdly.
Here’s (part of) algernon’s code:
/* -- mode: c++ --

  • Model01-Sketch – algernon’s Model01 Sketch
  • Copyright (C) 2016, 2017, 2019 Gergely Nagy
  • This program is free software: you can redistribute it and/or modify
  • it under the terms of the GNU General Public License as published by
  • the Free Software Foundation, either version 3 of the License, or
  • (at your option) any later version.
  • This program is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • GNU General Public License for more details.
  • You should have received a copy of the GNU General Public License
  • along with this program. If not, see http://www.gnu.org/licenses/.
    */

#include “SymUnI.h”

#include <Kaleidoscope.h>
#include <Kaleidoscope-Unicode.h>

namespace algernon {
namespace SymUnI {

static const struct {
const char *symbol;
uint32_t code;
} symbol_map[] PROGMEM = {
{“coffee”, 0x2615},
{“lambda”, 0x03bb},
{“poop”, 0x1f4a9},
{“rofl”, 0x1f923},
{“kiss”, 0x1f619},
{“snowman”, 0x2603},
{“heart”, 0x2764},
{“bolt”, 0x26a1},
{“pi”, 0x03c0},
{“mouse”, 0x1f401},
{“micro”, 0x00b5},
{“tm”, 0x2122},
{“family”, 0x1f46a},
{“child”, 0x1f476},
{“ok”, 0x2713},
{“joy”, 0x1F602},
};

void input(const char *symbol) {
uint32_t code = 0;

for (uint8_t i = 0; i < sizeof(symbol_map) / sizeof(symbol_map[0]); i++) {
const char *map_symbol = (const char *)pgm_read_word(&symbol_map[i].symbol);
if (strcmp(symbol, map_symbol) == 0) {
code = pgm_read_dword(&symbol_map[i].code);
break;
}
}

if (code)
Unicode.type(code);
else
typeString(symbol);
}

void typeString(const char *str) {
Unicode.start();

for (uint8_t i = 0; str[i]; i++) {
const char c = str[i];
Key key = Key_NoKey;

switch (c) {
case 'a' ... 'z':
  key.setKeyCode(Key_A.getKeyCode() + (c - 'a'));
  break;
case 'A' ... 'Z':
  key.setKeyCode(Key_A.getKeyCode() + (c - 'A'));
  break;
case '1' ... '9':
  key.setKeyCode(Key_1.getKeyCode() + (c - '1'));
  break;
case '0':
  key.setKeyCode(Key_0.getKeyCode());
  break;
}

Unicode.input();
handleKeyswitchEvent(key, UnknownKeyswitchLocation, IS_PRESSED | INJECTED);
Keyboard.sendReport();
Unicode.input();
handleKeyswitchEvent(key, UnknownKeyswitchLocation, WAS_PRESSED | INJECTED);
Keyboard.sendReport();

}
Unicode.end();
}

}
}

So the way it works is that if you type a symbol that is in the table it will delete what you typed and replace it with the corresponding symbol. If you type something that is not in the table it will treat what you typed as a hex unicode.

This is the problem. When I type any code that has a zero it, it won’t work. So for the coffee symbol, I can type “coffee” or “2615” (see table) and get the coffee symbol. But for lambda, I can type “lambda” and it will work, but if I type “03bb”, which has a zero in it, it doesn’t type anything. It seems very strange. I did come up with a workaround which works ok for me and which I like:

/* -- mode: c++ --

  • Model01-Sketch – algernon’s Model01 Sketch
  • Copyright (C) 2016, 2017, 2019 Gergely Nagy
    • Modifications Copyright (C) 2021 Stephen Polson
  • This program is free software: you can redistribute it and/or modify
  • it under the terms of the GNU General Public License as published by
  • the Free Software Foundation, either version 3 of the License, or
  • (at your option) any later version.
  • This program is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • GNU General Public License for more details.
  • You should have received a copy of the GNU General Public License
  • along with this program. If not, see http://www.gnu.org/licenses/.
    */

#include “SymUnI.h”

#include <Kaleidoscope.h>
#include <Kaleidoscope-Macros.h>
#include <Kaleidoscope-Unicode.h>

namespace zerchi {
namespace SymUnI {

static const struct {
const char *symbol;
uint32_t code;
} symbol_map[] PROGMEM = {
{“coffee”, 0x2615},
{“lambda”, 0x03bb},
{“poop”, 0x1f4a9},
{“rofl”, 0x1f923},
{“kiss”, 0x1f619},
{“snowman”, 0x2603},
{“heart”, 0x2764},
{“bolt”, 0x26a1},
{“pi”, 0x03c0},
{“mouse”, 0x1f401},
{“micro”, 0x00b5},
{“tm”, 0x2122},
{“family”, 0x1f46a},
{“child”, 0x1f476},
{“ok”, 0x2713},
{“joy”, 0x1F602},
};

void input(const char *symbol) {
uint32_t code = 0;

for (uint8_t i = 0; i < sizeof(symbol_map) / sizeof(symbol_map[0]); i++) {
const char *map_symbol = (const char *)pgm_read_word(&symbol_map[i].symbol);
if (strcmp(symbol, map_symbol) == 0) {
code = pgm_read_dword(&symbol_map[i].code);
break;
}// if
}// for

// If no match from symbol map, treat symbol as a four-character unicode and compute hex code from it.
if (!code) {
for(uint8_t i = 0; i < 4; i++) {
const char c = symbol[i];
switch (c) {
case ‘a’ … ‘f’:
// use bitwise OR to load up variable ‘code’ with 4 hexadigits
code = code | (0xa + (c - ‘a’)) << (12 - 4 * i);
break;
case ‘A’ … ‘F’:
code = code | (0xa + (c - ‘A’)) << (12 - 4 * i);
break;
case ‘1’ … ‘9’:
code = code | (0x1 + (c - ‘1’)) << (12 - 4 * i);
break;
default:
// Do nothing. We are treating hex zero itself as well as anything other than
// a proper hexadigit as zero.
break;
} // switch (c)
} // for
}// if (!code)

Unicode.type(code);
}// input()

}// namespace SymUnI
}// namespace zerchi

It will be much easier to examine the code if you mark the beginning and end with triple backticks to mark the whole section as preformatted text.

I’ve had a look at the Syster code, and a quick comparison to the ASCII table reveals the error with 0. The USB HID keyboard values have 0 as the highest-value digit—above 9, whereas the ASCII value has it as the lowest—below 1. Adding a special case for 0 in keyToChar() should fix the problem.

Incidentally, it also looks to me like there’s a problem with this code:

void input(const char *symbol) {
  uint32_t code = 0;

  for (uint8_t i = 0; i < sizeof(symbol_map) / sizeof(symbol_map[0]); i++) {
    const char *map_symbol = (const char *)pgm_read_word(&symbol_map[i].symbol);
    if (strcmp(symbol, map_symbol) == 0) {
      code = pgm_read_dword(&symbol_map[i].code);
      break;
    }
  }

  if (code)
    Unicode.type(code);
  else
    typeString(symbol);
}

The variable map_symbol is passed to strcmp(), but it’s populated by pgm_read_word(), which only reads the first two bytes of the symbol_map[] entry, not the whole string. If symbol has more than two characters, it ends up reading past the end of an array.

Oh thank you. I’m going to fix the symbol map thing in my SymUni.cpp right away.

As for the zero problem your explanation makes sense and I guess that must be the problem, but to me it looks like in Algernon’s code zero IS treated as a special case. I don’t understand why it doesn’t work. First we look for 1 to 9 to compute a key code, and then if still no bingo we at last deal with zero as a special case. “case ‘1’ … ‘9’:” doesn’t include zero does it?

case '1' ... '9':
  key.setKeyCode(Key_1.getKeyCode() + (c - '1'));
  break;
case '0':
  key.setKeyCode(Key_0.getKeyCode());
  break;

In Syster.cpp, there’s a function named keyToChar() that translates USB HID keycode values to ASCII. That function needs to special-case Key_0, but currently does not. It is defined as a weak function, however, so you should be able to override it by including the following in your sketch:

const char keyToChar(Key key) {
  if (key.getFlags() != 0)
    return 0;

  switch (key.getKeyCode()) {
  case Key_A.getKeyCode() ... Key_Z.getKeyCode():
    return 'a' + (key.getKeyCode() - Key_A.getKeyCode());
  case Key_1.getKeyCode() ... Key_9.getKeyCode():
    return '1' + (key.getKeyCode() - Key_1.getKeyCode());
  case Key_0.getKeyCode():
    return '0';
  }

  return 0;
}

I understand. I will fix that too, although in my implementation zeros don’t create a problem anymore for some reason. It’s sloppy to consider anything that isn’t 1 through F a zero I guess.

Thank you.

Maybe I’ll have the keyboard type a rofl emoji whenever I give it something that isn’t in the symbol map and also has things that aren’t hexadigits. Like “you are so funny you type so poorly.”

I’ve submitted a bug report and a fix for this to Kaleidoscope.

1 Like