FunctionalColor Plugin - Automatically label keys with colors

This is my first Kaleidoscope plugin that automatically assigns colors to your keys in groups based on their function. When the function layer is active, the LEDs will change to indicate the functions of the keys on that layer.

This should make it pretty easy for anyone to color their keys in such a way that it not only looks the way you want it to, but makes it easy to remember the functionality of each key, particularly useful when you’re reconfiguring your function layer and experimenting with different layouts.

You can download it here:

I am a total noob at both C++ programming and Arduino development, so be warned that I barely know what I’m doing… but so far it seems to work as intended. If anyone can have a look at my code and offer some tips for how I could optimize my plugin to be more efficient, use less memory/cpu, or otherwise work better, I’d be appreciative.

If you use this but want support for keys that I didn’t include (ie for letters in languages other than English), let me know and I’ll try to add them.

The backstory here is that I found it extremely frustrating and difficult to configure per-key colors on the Model 01, and there seemed to be no default means of helping a noobie user do this. I felt like Kaleidoscope could use a few helper functions to make it easier to tweak and easily set key colors.

Since I couldn’t find what I wanted, I wrote my own. Yay for open source!

If you want a fairly simple color scheme with a few keys highlighted, this plugin should help. If you want almost every single key to be a different color based on its function, but most “normal” alphanumeric keys in groups, this makes it easy to create a scheme like that.

To use the plugin, include the header, declare an effect using the
LEDFunctionalColor class, and tell the firmware to use the new effect.
Configure your own colors for groups of keys inside setup():

// Automatically sets key LEDs on active layer based on the function of the key
#include <Kaleidoscope-LEDEffect-FunctionalColor.h>

// You can make multiple variations of the theme.
// Warning: having several versions consumes a lot of memory!
kaleidoscope::LEDFunctionalColor FunColor;
kaleidoscope::LEDFunctionalColor FunColorMedium;
kaleidoscope::LEDFunctionalColor FunColorLow;

void setup() {
  Kaleidoscope.use(&FunColor, &FunColorMedium, &FunColorLow);

  Kaleidoscope.setup(

    // Optionally Make things more human readable by naming your colors
    cRGB antiquewhite = CRGB(250, 235, 215);
    cRGB blue = CRGB(0, 0, 255);
    cRGB cyan = CRGB(0, 255, 255);
    cRGB green = CRGB(0, 128, 0);
    cRGB lightskyblue = CRGB(135, 206, 250);
    cRGB lime = CRGB(0, 255, 0);
    cRGB mintcream = CRGB(245, 255, 250);
    cRGB orange = CRGB(255, 165, 0);
    cRGB orangered = CRGB(255, 100, 0);
    cRGB palegreen = CRGB(152, 251, 152);
    cRGB pink = CRGB(255, 192, 203);
    cRGB red = CRGB(255, 0, 0);
    cRGB skyblue = CRGB(135, 206, 235);
    cRGB slateblue = CRGB(106, 90, 205);
    cRGB violet = CRGB(238, 130, 238);
    cRGB white = CRGB(255, 255, 255);
    cRGB yellow = CRGB(255, 255, 0);
    cRGB yellowgreen = CRGB(154, 205, 50);

    // If your FUNCTION layer is not the default, you must set it here
    FunColor.functionLayer = FUNCTION;

    // Here we can set custom colors for your FunctionalColor instance.
    // You can optionally specify a brightness value, 0-255 to dim your lights.

    // Set this first to provide a "default" color for all keys, then override with the other settings.
    FunColor.all(CRGB(250, 235, 215));

    // Set this second to change all modifiers (non-alphabet/numeric/punctuation keys)
    FunColor.allModifiers(CRGB(250, 235, 215));

    // Set this before individual mouse settings to change all mouse-related keys
    FunColor.allMouse(CRGB(0, 200, 200)); 

    //Set individual groups of colors. You may delete any lines you don't need.
    FunColor.escape(red, 170);
    FunColor.numbers(white, 160);
    FunColor.letters(antiquewhite, 100);
    FunColor.punctuation(antiquewhite, 170);
    FunColor.brackets(antiquewhite, 200);
    FunColor.backslash(antiquewhite, 170);
    FunColor.pipe(antiquewhite, 170);
    FunColor.tab(white, 170);
    FunColor.backspace(red, 170);
    FunColor.del(red, 170);
    FunColor.enter(white, 170);
    FunColor.arrows(white, 170);
    FunColor.nav(yellow, 170);
    FunColor.insert(yellow, 170);
    FunColor.shift(palegreen, 170);
    FunColor.ctrl(skyblue, 170);
    FunColor.alt(green, 170);
    FunColor.cmd(CRGB(250, 235, 215));
    FunColor.app(CRGB(250, 235, 215));
    FunColor.printscreen(CRGB(250, 235, 215));
    FunColor.pause(CRGB(250, 235, 215));
    FunColor.scrolllock(CRGB(250, 235, 215));
    FunColor.capslock(CRGB(250, 235, 215));
    FunColor.fkeys(red, 170);
    FunColor.fn(CRGB(250, 235, 215));
    FunColor.media(CRGB(250, 235, 215));
    FunColor.led(blue, 190);
    FunColor.mousemove(cyan, 170);
    FunColor.mousebuttons(lightskyblue, 170);
    FunColor.mousewarp(cyan, 100);
    FunColor.mousescroll(lightskyblue, 100);

    //Copy new settings to the dimmed versions
    FunColorMedium = FunColor;
    FunColorLow = FunColor;

    // You could make adjustments to your other versions' groups here, if desired.

    // Adjust the brightness of dimmed versions here from 0-255
    FunColorMedium.brightness(210);
    FunColorLow.brightness(170);


  );
}

Simple Easy Config for Noobies
If you are not an experienced programmer and/or don’t know your way around Kaleidoscope, here is a very pared down example of a configuration that you can just paste wholesale over the default firmware to get you started.

// -*- mode: c++ -*-
#include "Kaleidoscope.h"
#include "Kaleidoscope-MouseKeys.h"
#include "Kaleidoscope-LEDControl.h"
#include "Kaleidoscope-NumPad.h"

// Automatically sets key LEDs on active layer based on the function of the key
#include "Kaleidoscope-LEDEffect-FunctionalColor.h"

// You can make multiple variations of the theme.
// Warning: having several versions consumes a lot of memory!
kaleidoscope::LEDFunctionalColor FunColor;
kaleidoscope::LEDFunctionalColor FunColorMedium;
kaleidoscope::LEDFunctionalColor FunColorLow;
  

enum { QWERTY, NUMPAD, FUNCTION }; // layers

const Key keymaps[][ROWS][COLS] PROGMEM = {

  [QWERTY] = KEYMAP_STACKED
  (___,          Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext,
   Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
   Key_PageUp,   Key_A, Key_S, Key_D, Key_F, Key_G,
   Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
   Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
   ShiftToLayer(FUNCTION),

   ___,  Key_6, Key_7, Key_8,     Key_9,         Key_0,         LockLayer(NUMPAD),
   Key_Enter,     Key_Y, Key_U, Key_I,     Key_O,         Key_P,         Key_Equals,
                  Key_H, Key_J, Key_K,     Key_L,         Key_Semicolon, Key_Quote,
   Key_RightAlt,  Key_N, Key_M, Key_Comma, Key_Period,    Key_Slash,     Key_Minus,
   Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl,
   ShiftToLayer(FUNCTION)),


  [NUMPAD] =  KEYMAP_STACKED
  (___, ___, ___, ___, ___, ___, ___,
   ___, ___, ___, ___, ___, ___, ___,
   ___, ___, ___, ___, ___, ___,
   ___, ___, ___, ___, ___, ___, ___,
   ___, ___, ___, ___,
   ___,

   ___,  ___, Key_Keypad7, Key_Keypad8,   Key_Keypad9,        Key_KeypadSubtract, ___,
   ___,                    ___, Key_Keypad4, Key_Keypad5,   Key_Keypad6,        Key_KeypadAdd,      ___,
                           ___, Key_Keypad1, Key_Keypad2,   Key_Keypad3,        Key_Equals,         Key_Quote,
   ___,                    ___, Key_Keypad0, Key_KeypadDot, Key_KeypadMultiply, Key_KeypadDivide,   Key_Enter,
   ___, ___, ___, ___,
   ___),

  [FUNCTION] =  KEYMAP_STACKED
  (___,      Key_F1,           Key_F2,      Key_F3,     Key_F4,        Key_F5,           XXX,
   Key_Tab,  ___,              Key_mouseUp, ___,        Key_mouseBtnR, Key_mouseWarpEnd, Key_mouseWarpNE,
   Key_Home, Key_mouseL,       Key_mouseDn, Key_mouseR, Key_mouseBtnL, Key_mouseWarpNW,
   Key_End,  Key_PrintScreen,  Key_Insert,  ___,        Key_mouseBtnM, Key_mouseWarpSW,  Key_mouseWarpSE,
   ___, Key_Delete, ___, ___,
   ___,

   Consumer_ScanPreviousTrack, Key_F6,                 Key_F7,                   Key_F8,                   Key_F9,          Key_F10,          Key_F11,
   Consumer_PlaySlashPause,    Consumer_ScanNextTrack, Key_LeftCurlyBracket,     Key_RightCurlyBracket,    Key_LeftBracket, Key_RightBracket, Key_F12,
                               Key_LeftArrow,          Key_DownArrow,            Key_UpArrow,              Key_RightArrow,  ___,              ___,
   Key_PcApplication,          Consumer_Mute,          Consumer_VolumeDecrement, Consumer_VolumeIncrement, ___,             Key_Backslash,    Key_Pipe,
   ___, ___, Key_Enter, ___,
   ___)

};


/** The 'setup' function is one of the two standard Arduino sketch functions.
  * It's called when your keyboard first powers up. This is where you set up
  * Kaleidoscope and any plugins.
  */

void setup() {
  // First, call Kaleidoscope's internal setup function
  Kaleidoscope.setup();

  // Next, tell Kaleidoscope which plugins you want to use.
  // The order can be important. For example, LED effects are
  // added in the order they're listed here.
  Kaleidoscope.use(
    // The MouseKeys plugin lets you add keys to your keymap which move the mouse.
    &MouseKeys,
    &FunColor,&FunColorMedium,&FunColorLow 
  );

  // While we hope to improve this in the future, the NumPad plugin
  // needs to be explicitly told which keymap layer is your numpad layer
  NumPad.numPadLayer = NUMPAD;
  
  // Optionally Make things more human readable by naming your colors
  cRGB antiquewhite = CRGB(250, 235, 215);
  cRGB blue = CRGB(0, 0, 255);
  cRGB cyan = CRGB(0, 255, 255);
  cRGB green = CRGB(0, 128, 0);
  cRGB lightskyblue = CRGB(135, 206, 250);
  cRGB lime = CRGB(0, 255, 0);
  cRGB mintcream = CRGB(245, 255, 250);
  cRGB orange = CRGB(255, 165, 0);
  cRGB orangered = CRGB(255, 100, 0);
  cRGB palegreen = CRGB(152, 251, 152);
  cRGB pink = CRGB(255, 192, 203);
  cRGB red = CRGB(255, 0, 0);
  cRGB skyblue = CRGB(135, 206, 235);
  cRGB slateblue = CRGB(106, 90, 205);
  cRGB violet = CRGB(238, 130, 238);
  cRGB white = CRGB(255, 255, 255);
  cRGB yellow = CRGB(255, 255, 0);
  cRGB yellowgreen = CRGB(154, 205, 50);
  
  
  // If your FUNCTION layer is not the default, you must set it here
  FunColor.functionLayer = FUNCTION;
  
  // Here we can set custom colors for your FunctionalColor instance.
  // You can optionally specify a brightness value, 0-255 to dim your lights.
  
  // Set this first to provide a "default" color for all keys, then override with the other settings.
  FunColor.all(CRGB(250, 235, 215));
  
  // Set this second to change all modifiers (non-alphabet/numeric/punctuation keys)
  FunColor.allModifiers(CRGB(250, 235, 215));
  
  // Set this before individual mouse settings to change all mouse-related keys
  FunColor.allMouse(CRGB(0, 200, 200)); 
  
  //Set individual groups of colors. You may delete any lines you don't need.
  FunColor.escape(red, 170);
  FunColor.numbers(white, 160);
  FunColor.letters(antiquewhite, 100);
  FunColor.punctuation(antiquewhite, 170);
  FunColor.brackets(antiquewhite, 200);
  FunColor.backslash(antiquewhite, 170);
  FunColor.pipe(antiquewhite, 170);
  FunColor.tab(white, 170);
  FunColor.backspace(red, 170);
  FunColor.del(red, 170);
  FunColor.enter(white, 170);
  FunColor.arrows(white, 170);
  FunColor.nav(yellow, 170);
  FunColor.insert(yellow, 170);
  FunColor.shift(palegreen, 170);
  FunColor.ctrl(skyblue, 170);
  FunColor.alt(green, 170);
  FunColor.cmd(CRGB(250, 235, 215));
  FunColor.app(CRGB(250, 235, 215));
  FunColor.printscreen(CRGB(250, 235, 215));
  FunColor.pause(CRGB(250, 235, 215));
  FunColor.scrolllock(CRGB(250, 235, 215));
  FunColor.capslock(CRGB(250, 235, 215));
  FunColor.fkeys(red, 170);
  FunColor.fn(CRGB(250, 235, 215));
  FunColor.media(CRGB(250, 235, 215));
  FunColor.led(blue, 190);
  FunColor.mousemove(cyan, 170);
  FunColor.mousebuttons(lightskyblue, 170);
  FunColor.mousewarp(cyan, 100);
  FunColor.mousescroll(lightskyblue, 100);
  
  //Copy new settings to the dimmed versions
  FunColorMedium = FunColor;
  FunColorLow = FunColor;
  
  // You could make adjustments to your other versions' groups here, if desired.
  
  // Adjust the brightness of dimmed versions here from 0-255
  FunColorMedium.brightness(210);
  FunColorLow.brightness(170);

} //end setup - all FunctionalColor object methods must be called before this point


void loop() {
  Kaleidoscope.loop();
}
3 Likes

Looks cool, I will have to try it at some point.

Thank you.

Cool, I’ll have a look!

For future reference, Chrysalis has an LED editor that lets you individually set LED colours for each key on a per-layer basis (so you can have different colours on different layers).

Oh, cool. I’ll have to have a look at Chrysalis because that sounds pretty handy. I thought it was far from being usable, though…

It sounds like the main difference with my plugin is that mine keeps the color bound not to the key’s location, but to the key’s function, so if you frequently remap and want, say, your enter key to be white and your shift keys green, you can change your layout without manually changing the LEDs.

Yeah, your method is definitely much less manual – to be honest, it’s a little painful to individually set each LED with Chrysalis.

As for usable, it’s actually fairly usable at this point, although you have to build it yourself, I think, as we don’t yet have pre-built binaries for all platforms (I should point out that I’m one of the devs working on Chysalis too, so feel free to let me know if you have further questions :smile:).

Awesome, good to meet you James.

The main questions I have at this very moment relate to how can I reduce the memory usage of my plugin, heheh… if you add it to the default firmware the Arduino IDE complains that the keyboard is low on memory - but as far as I can tell it works.

I’m a web developer, so I’m not used to having to be super-duper efficient and worry about memory all the time, so this is a new world… a few too many variables or one inefficient method, and BAM, you have a brick instead of a keyboard. It’s a challenge for me!

I should probably get rid of the word “set” from all the functions that allow you to change the colors too… that looks a little messy now that I think about it.

Hah, I’m mainly a web developer too, I haven’t done this sort of low-level thing since school.

I think the general way of reducing memory usage is to store things in PROGMEM instead of RAM…in this case, I guess you could store all the colours with the progmem macro & read them from there instead of having them as instance variables, as that makes the plugin object a lot longer. I’ve only used PROGMEM for storing things that are set outside the plugin (e.g. like PrefixLayer), so I’m not sure how to make that work for variables defined in the class…

I have made a fairly significant change and changed all the public methods responsible for setting the color variables… Originally I was using the style I learned in school but ultimately decided that it looked messier and was maybe harder to use… One potential issue, though is that it’s more likely to cause a namespace collision, especially with the “delete” method which I had to rename to del().

@james.nvc Thanks for the tip. I’ll play with PROGMEM and see if that helps. I actually made a huge list of colors by using all the CSS color names, but found that I quickly ran out of memory when doing that. I actually wished that the default firmware had those color names baked in (maybe not all of them but at least the obvious colors). If your suggestion works, perhaps I can create another plugin to give people all the color names.

I also wrote my own simple dimming method, and I wish that the stock firmware had something like this as well. I feel like things this basic should be built in so people don’t need to reinvent the wheel, but I’m certainly no expert, so maybe I just don’t know what I’m talking about, haha.

If anyone is interested, here’s the dimming method I wrote… it’s simple and very handy.

// dims the specified color from 0 (off) to 255 (full)
cRGB LEDFunctionalColor::dim(cRGB color, byte brightness) {
  return CRGB(color.r*brightness/255, color.g*brightness/255, color.b*brightness/255);
}
1 Like

Weird. I tried using PROGMEM with the huge list of colors I have, and it seemed that for some reason all the colors turned into this dark shade off blue…

  const cRGB yellow PROGMEM = CRGB(255, 255, 0);
  const cRGB yellowgreen PROGMEM = CRGB(154, 205, 50);

In addition, it seemed to make no difference in the amount of memory used either way… but it sure should have when I had 150 colors defined. Do you have to access the variables in a different way or something? I feel like I need to read a book on this or something.

Yeah, I think you need to use pgm_read_word to look up the value, so I think code using the variables would become pgm_read_word(yellow) (or pgm_read_word(&yellow)? I’m not completely sure, I’ve just been reading other code & experimenting to find what works).

Not sure why it isn’t reducing the memory usage either, but I am definitely not an expert in the matter…maybe @algernon could shed some light.

1 Like

I have an update that now offers brightness control of the entire configuration along with more granular mouse controls… I didn’t really think about the fact that there are so many mouse controls available, and that one would want the different kinds of mouse functionality colored differently.

With the brightness control, I’ve revised my example in the README.md to show how you could have slight variations of a theme to simulate brightness control… one setting for daytime, one for night, for example. Unfortunately I found out the hard way that the Model 01 doesn’t like having much more than 3 different versions on the default firmware… I think my FunctionalColor object takes way too much memory :frowning:

I loved this so much, I forked your repo and made a version that let me specify a color for each row of letters. Thanks for making this @jdlien!!

2 Likes

Cool! Feel free to do a PR if you think this adds value to my project without compromising its other functionality.

Hi seems cool your plugin. For the memory solution. Why don’t you define the colors with #define ? Instead of with variables. With #define the compiler does the translation and no memory is used for variables.

Whether the value is stored as a variable or a literal doesn’t change the fact that the program needs to store the value somewhere in order to use it. Once it’s compiled, there really isn’t a difference.

ok thx for the reply
maybe I’ll come with a better solution I have a small idea
I’ll try it out tomorrow and keep you informed

hi there , im a total noob, i want to set specific color to keys, and it seems this plugin does just that.
problem is im having a hard time to make it work.
I put the code you shared in the firmware using arduino, but im obviously having difficulty getting it to be recognized …
i added the include at the begining,
#include “Kaleidoscope-LEDEffect-FunctionalColor.h”

but teh part where you have to declare it and tell teh firmware to use the effect is super vage and cant understand how you do that…
any chance of sharing a fully functional firmware with this code included so i can reverse learn it?
:slight_smile:

Abe, have you got the Kaleidoscope-LEDEffect-FunctionalColor directory in your Arduino libraries directory? For me that is the most difficult part because it’s thoroughly buried…

In my case, on a Mac, it’s in
/Users/jdlien/Library/Arduino15/packages/keyboardio/hardware/avr/1.22.0/libraries/

Copy the Kaleidoscope-LEDEffect-FunctionalColor into there.

Using it is pretty simple but to understand my instructions you have to sort of “get” the main part of the firmware…

You’ve got the include, which is a good start. Next, you need to create (at least) one instance of it, which I’ve labeled FunColor in my example, and this can go right after the include if you want:
kaleidoscope::LEDFunctionalColor FunColor;

In the setup(){ section you can see how the default firmware has things like &LEDRainbowWaveEffect and other effects? Add the instance(s) you’ve created in there alongside the other effects.

&LEDControl,
&StalkerEffect,
// JD’s automatic color-coding based on your key layout. Shows the function of the fn layer when active
&FunColor,

That should get you started but you can customize your colors to change from the defaults in the setup section at the end of the file as illustrated in the readme. In the default firmware this will be near the end of the file, where the LEDRainbowWaveEffect.brightness(170) is:

LEDRainbowWaveEffect.brightness(170);
// If your FUNCTION layer is not the default, you must set it here
FunColor.functionLayer = FUNCTION;

// Set this before individual mouse settings to change all mouse-related keys
FunColor.allMouse(CRGB(0, 200, 200));

I hope this helps. Let me know how it goes.

I tried what you suggested, but still get an error (at least the directory is recognized :slight_smile:
still not compiling though!
im basically trying to get your example to work, then i would change whatever i want bit by bit, learning how it works.
so teh include works, the setup section seems ok, but maybe its the function not working…not sure i understand how to make it default…
how can i post the code here?

1 Like
Click the "code" icon that looks like </> and paste and it will be preformatted like this.

If you paste your config I can hopefully determine what’s going wrong.