Programming Whitefox keyboard with QMK

This week I've finally received my order of Whitefox keyboard:
What's so special about this keyboard you ask? First of all, it is a mechanical keyboard. But more importantly is that keyboard has a programmable microcontroller. So the layout doesn't depend on the OS or drivers or 3rd party utilities anymore!
In fact, Whitefox was the only programmable keyboard that you can buy fully assembled in November 2017, when I did the research.
A common setup for programmable keyboards is to have several dedicated buttons for switching the layout, for example, CAPSLOCK or right CTRL. My idea was to use a spacebar: if spacebar is pressed along with another character then it will act as layer selector, otherwise, it will emit space character when the button is released. That allows using vim-like navigation without hands leaving the home row or stretching a pinky.

Kiibohd configurator


InputClub officially recommends this program (or the web version which is essentially the same app) to configure Whitefox. If all you need is to create a couple of virtual layers activated when a user presses or holds dedicated button - look no further. Unfortunately, the configurator doesn't allow you to easily go beyond the basics.
I have looked at how it represents and compiles a firmware and the path looks like:
config ui  kll file  kll compiler  binary  dfu-util  keyboard
KLL is short for "Keyboard Layout Language". It seems that this project tries to create some standard for defining keyboard layouts. At this moment kll-compiler supports version 0.3 of spec which is quite plain doesn't allow assigning "two roles" to one button. Maybe after a couple of years, this project will cover 95% of user needs. From my personal point of view, the need to maintain such niche domain-specific language only makes progress slower. And there is only one maintainer...
Fortunately for me, there is an alternative:

QMK


QMK is a successor/fork of pretty popular TMK firmware. It takes much straightforward approach: you modify several files in well-structured C-project and then compile them directly to binary.
As this project IS as a firmware, it's really easy to get direct access lower levels, down to matrix scan method. That's why there are tons of contributed features you can employ in your layout right away. And look, there is a one that covers my case:
LT(layer, kc) - momentary switch to layer when held, and kc when tapped.
If you're interested in my full layout build check my fork of QMK (make target is "whitefox:gem"). I would argue that one doesn't really need a UI if it's so easy to define it in a text file.
I can't commend QMK project enough: it's well maintained, builds smoothly, has awesome documentation and many contributors. I hope that InputClub will invest more in that project and will make it officially supported by QMK.

In conclusion


I like this new toy and really enjoy typing with it! As it's 65% keyboard, it will take some time to get used to missing buttons: enter is smaller, grave, F-keys, backspace or numpad are not where my hands look for them. These are things to consider before buying a non-standard keyboard. And don't forget to check if yours is supported by QMK ;)
Good luck!


Logging in programming contests

More often then not programs we created don't do what we want them to: runtime errors or invalid results appear, and we have to figure out what is wrong. It's especially intimidating if you have only 10 minutes left to find and fix the bug before the end of the programming contest.

There are several approaches to that. The best one is to check that the general idea is correct and carefully read through the code. Reading and thinking are faster than anything else, plus you can't really get around them anyway.

On the opposite side sits "debugger". Ability to pause at the key moments and carefully inspect the program state sounds awesome. In practice though, it's a very time-consuming process of stepping through the code, adding breakpoints and watch expressions for the data you are interested in. Multiple people advocates using debugger only as a last resort. I found debugger useful from time to time to understand something non-trivial while I have a luxury of time after a contest.

Logging and asserts seem like lightweight and robust tools to get some intel about what's going on in a program. Here I am going to talk about logging.

For the sake of the example let's use "find k-th smallest element" problem: you are given $n$ numbers and want to find $k$-th smallest ($0<k <= n$) in $O(n)$.

#include <bits/stdc++.h>
using namespace std;

int find_kh(auto a, auto b, int k) {
  auto pivot = *(a + (b - a) / 2);
  auto lower = partition(a, b, [pivot](auto const& x) {
    return x < pivot; });
  auto upper = partition(lower, b, [pivot](auto const& x) {
    return x == pivot; });
  if ((upper - a) <= k) return find_kh(upper, b, k - (upper - a));
  if ((lower - a) > k) return find_kh(a, lower, k);
  return pivot;
}

int main() {
  int n, k;
  cin >> n >> k;
  vector<int> v(n);
  while (n--) cin >> v[n];
  cout << find_kh(v.begin(), v.end(), k - 1) << endl;
}

Let's add a detailed log of function internals:

int find_k(auto a, auto b, int k) {
  auto pivot = *(a + (b - a) / 2);
  cerr << "pivot " << pivot << endl;
  auto lower = partition(a, b, [pivot](auto const& x) {
      return x < pivot; });
  auto upper = partition(lower, b, [pivot](auto const& x) {
      return x == pivot; });
  cerr << "partitioned: ";
  auto i = a;
  while (i != b) cerr << *(i++) << ", ";
  cerr << endl;
  cerr << "size < : " << (lower - a) << endl;
  if ((lower - a) > k) {
    cerr << "left" << endl;
    return find_k(a, lower, k);
  }
  cerr << "size <= : " << (upper - a) << endl;
  if ((upper - a) <= k) {
    auto new_k = k - (upper - a);
    cerr << "right, new_k =" << new_k << endl;
    return find_k(upper, b, new_k);
  }
  return pivot;
}

yikes! Look how much noise we've added to the function and now it's much harder to comprehend. Moreover, it's not possible to submit such code to judge as it might fail due to timeout as we are doing additional vector traversals and sync on stderr. Of course, there are workarounds for that, like commenting out logging or guarding it with #ifndef ONLINE_JUDGE. That works but requires more typing (aka time).

I have used to log like this. But from time to time stumbled into some cases when logging requires introducing intermediate variables or new sections (see the printing of th updated value of k above) and that wasn't seem to be the most productive way to go.

After one of the Codeforces contests, I have decided to sort this thing out. First of all, I've listed "requirements":

  • don't take too much space in solution's template file (mine was already 20 lines long).
  • conditional header files are fine. Would be nice if the size is small and it doesn't significantly increase compilation time.
  • less typing, these << ' ' << are not fun.
  • do something with the logging of return and intermediate expressions, e.g. conditions of if or function arguments.
  • if I am going to include header then lets add some common print routines for vectors and tuples!

I have experimented with several non-header approaches but they took too much real estate in the template, plus there is no need to send this code. And the ability to print std containers looks so appealing. Let's create a header!

There is a really good header-only library for printing std containers by Louis Delacroix. It's included through the new header and works great with things like vectors, maps or tuples.

Let's look at the logging function itself:

template<typename T, typename... O>
decltype(auto) debug(T&& arg, O&&... args) {...}

it takes one or more arguments, prints them and returns the first one. This allows debugging data "in-flight":

if (debug(a < b, a, b)) {
  return debug(a, "<", b);
}

For a = 3, b = 4 that will print

1 3 4
3 < 4

For a time I have tried to make this function return the value of the last argument so it would make debug(a, b, a < b) behave like (a, b, a < b) (given that additional arguments don't have side effects) and one can simply remove the name of the function to stop logging while preserving semantics. And.. I have not found an easy way to do that. Moreover, what I really wanted is to stop calculating all arguments but the "main one" on judge, as additional expressions can be really expensive.

Now it's time to leave this function alone and write some macro:

#if defined(LOCAL)
#include "logging.h"
#define L(x...) (debug(x, #x))
#else
#define L(x, ...) (x)
#endif

What's happening here? First of all, if LOCAL is defined we include the header and L() will call debug with everything passed to macro as is, plus #x. This "hash" is a really neat feature of the preprocessor that puts the a string literal of macro argument. If LOCAL is not defined (as it will be on a judge) then the preprocessor will throw away everything but first macro parameter thus removing any side effects of logging.

A short note about the choice of identifier. For some period I have relied on the absence of ONLINE_JUDGE but later discovered that Yandex.contest does not specify it; so I resorted to the presence of LOCAL identifier instead that is under my control.

It's time to look how this changes the impact of logging on our function:

int find_k(auto a, auto b, int k) {
  auto pivot = L(*(a + (b - a) / 2), "pivot");
  auto lower = partition(a, b, [pivot](auto const& x) {
      return x < pivot; });
  auto upper = partition(lower, b, [pivot](auto const& x) {
      return x == pivot; });
  L("partitioned", vector<int>(a, b));
  if (L(lower - a) > k)
    return find_k(a, lower, L(k, "left"));
  if (L(upper - a) <= k)
    return find_k(upper, b, L(k - (upper - a), "right"));
  return pivot;
}

If there is only one expression to log then you can stop printing it out by deleting a single letter L while keeping everything else intact (e.g. logging of lower - a in the condition above).

Note that as log will automatically print expression we are interested in, it's no longer necessary to provide additional string hints. On the other hand, when logging statement is an expression by itself, like the output of a range after partition above, I found it useful to do add some constant as a first argument. It's true that compiler will likely optimize out even very complex expressions if they have no side effects. I do that to be 100% sure. Maybe for you, it will make more sense to introduce another macro for standalone logging that will be completely removed on judge.

While it will definitely require one to spend some time to get used to a new way of printf-ing and learn how to deal with not so obvious cases, the speed and ease of use will massively offset that.

You can find this tiny header source in gist (don't forget to download prettyprint as well).

Maybe you know a better way to log program state? Please share your thoughts in the comments below!

P.S. while this post was in the draft, Jonathan Boccara published very similar idea of "tee" function. I invite you to check his implementation and inspiration for this method.

Programming Whitefox keyboard with QMK

This week I've finally received my order of Whitefox keyboard: What's so special about this keyboard you ask? First of all, it ...