How to add a keyboard modifier state applet to Unity panel?

I am a KDE user thinking about moving to Unity. Due to manual disability, I use sticky keys and in KDE I have an applet in the system panel which shows which modifier keys are active. I remember that Gnome had this feature as well, so do Windows and OS X.

How does one ad the keyboard modifier state applet to the panel in Unity?

Clarification: I have already enabled sticky keys. I am asking how to add an applet which indicates the state of the modifier keys. This indicator would show when the Shift key is depressed, when the Alt key is depressed, when the Tux key is depressed, and when the Ctrl key is depressed. This applet exists in all major desktop environments (KDE, Windows, Mac OSX, and Gnome). It is necessary for accessibility of the desktop.

Here is an image of the keyboard modifier state applet, next to the keyboard layout indicator applet. The modifiers represented are, from left to right, Shift, Ctrl, Alt, I-dont-know-this-one, Tux/Win, NumLock, and CapsLock. It can be seen that the NumLock key is active.

enter image description here


Solution 1:

This is an outstanding issue in Unity:

  • lp#773078 Should display the StickyKeys status in some way (a11y)
  • lp#1306584 No keyboard state applet in Unity (Thanks to @dotancohen)

The code below has been updated, now it can use icons to show state. But it may get slow sometime as I has to update icon file on hard drive then reload it again. (See notes about this issue/limitation in libappindicator)

A well packaged release was made available on webupd8 ppa (Thanks goes to Alin Andrei /Andrew/)

sudo add-apt-repository ppa:nilarimogard/webupd8
sudo apt-get update
sudo apt-get install indicator-xkbmod

Reference: Keyboard Modifiers State indicator For Ubuntu: Xkbmod Indicator


Original Answer:

This is not attended to be a canonical answer to the question. It could be counted as a work around. Hopping someone writes sophisticated solution for it.

This a simple prototype keyboard modifiers indicator for Unity.

Image starting from left: Icon, Shift, Locked Caps, Ctrl, Alt, Super, Locked AltGr (Small circle to indicate locked state)

screenshot of unity-xkbmod prototype

Source file unity-xkbmod.c:

/*
 * unity-xkbmod.c
 *
 * Copyright 2014 Sneetsher <sneetsher@localhost>
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 *
 *
 */

#include <string.h>

#include <X11/XKBlib.h>

#include <glib/gprintf.h>
#include <gtk/gtk.h>
#include <libappindicator/app-indicator.h>

//callback data structure
typedef struct _AppData {
  Display *_display;
  int *_deviceId;
  AppIndicator *indicator;
} AppData;

//menu ui
static GtkActionEntry entries[] = {
  { "Quit",     "application-exit", "_Quit", "<control>Q",
    "Exit the application", G_CALLBACK (gtk_main_quit) },
};

static guint n_entries = G_N_ELEMENTS (entries);

static const gchar *ui_info =
"<ui>"
"  <popup name='IndicatorPopup'>"
"    <menuitem action='Quit' />"
"  </popup>"
"</ui>";

//callback function, get xkb state, update indicator label (icon have limitation)
static gboolean update_xkb_state (gpointer data)
{
  //get xkb state
  XkbStateRec xkbState;
  XkbGetState(((AppData*) data)->_display, *(((AppData*) data)->_deviceId), &xkbState);

  //construct label
  GString *label = g_string_new("");

  register int i;
  unsigned bit;

  //loop taken from xkbwatch source
  for (i = XkbNumModifiers - 1, bit = 0x80; i >= 0; i--, bit >>= 1)
  {
    //printf("base%d %s  ", i, (xkbState.base_mods & bit) ? "on " : "off");
    //printf("latched%d %s  ", i, (xkbState.latched_mods & bit) ? "on " : "off");
    //printf("locked%d %s  ", i, (xkbState.locked_mods & bit) ? "on " : "off");
    //printf("effective%d %s  ", i, (xkbState.mods & bit) ? "on " : "off");
    //printf("compat%d %s\n", i, (xkbState.compat_state & bit) ? "on " : "off");

    //todo: change constant with xkb modifier constant (defined in the headers)
    // show effective modifier stat
    switch (i)
    {
      case 7:
        g_string_prepend (label,  ((xkbState.mods & bit) ? "⎇" : ""));
        break;
      case 6:
        g_string_prepend (label,  ((xkbState.mods & bit) ? "⌘" : ""));
        break;
      case 5:
        g_string_prepend (label,  ((xkbState.mods & bit) ? "5" : ""));
        break;
      case 4:
        g_string_prepend (label,  ((xkbState.mods & bit) ? "①" : ""));
        break;
      case 3:
        g_string_prepend (label,  ((xkbState.mods & bit) ? "⌥" : ""));
        break;
      case 2:
        g_string_prepend (label,  ((xkbState.mods & bit) ? "⋀" : ""));
        break;
      case 1:
        g_string_prepend (label,  ((xkbState.mods & bit) ? "⇬" : ""));
        break;
      case 0:
        g_string_prepend (label,  ((xkbState.mods & bit) ? "⇧" : ""));
        break;
      default:
        break;
    };

    // show if modifier is locked
    g_string_prepend (label,  ((xkbState.locked_mods & bit) ? " ˳" : " "));
  }

  //g_string_prepend (label,  "");
  app_indicator_set_label (((AppData*) data)->indicator, label->str, NULL);

  //g_free(label);
  return TRUE;
}


int main (int argc, char **argv)
{
  AppData appdata;
  Display *_display;
  int _deviceId;

  char* displayName = strdup("");
  int eventCode;
  int errorReturn;
  int major = XkbMajorVersion;
  int minor = XkbMinorVersion;;
  int reasonReturn;


  AppIndicator *indicator;
  GtkWidget *indicator_menu;
  GtkUIManager *uim;
  GtkActionGroup *action_group;
  GError *error = NULL;

  gtk_init (&argc, &argv);


  XkbIgnoreExtension(False);

  g_printf("Xkb client lib ver: %d.%d\n" , major , minor );
  _display = XkbOpenDisplay(displayName, &eventCode, &errorReturn,
                            &major, &minor, &reasonReturn);
  g_printf("Xkb server lib ver: %d.%d\n" , major , minor );

  if (reasonReturn != XkbOD_Success)
  {
    g_printf("Unable to open display!\n");
    return 1;
  }

  XkbDescRec* kbdDescPtr = XkbAllocKeyboard();
  if (kbdDescPtr == NULL)
  {
    g_printf ("Failed to get keyboard description.\n");
    return 2;
  }
  kbdDescPtr->dpy = _display;
  _deviceId = kbdDescPtr->device_spec;

  /*
  //no need for event listener, used gtk_timeout timer
  XkbSelectEventDetails(_display, XkbUseCoreKbd, XkbStateNotify,
                     XkbAllStateComponentsMask, XkbGroupStateMask);
  */


  action_group = gtk_action_group_new ("AppActions");
  gtk_action_group_add_actions (action_group, entries, n_entries, NULL);

  indicator = app_indicator_new_with_path (
                                        "Simple XKB Modifier Indicator",
                                        "icon",
                                        APP_INDICATOR_CATEGORY_HARDWARE,
                                        g_get_current_dir());

  uim = gtk_ui_manager_new ();
  gtk_ui_manager_insert_action_group (uim, action_group, 0);
  if (!gtk_ui_manager_add_ui_from_string (uim, ui_info, -1, &error))
  {
    g_printf ("Failed to build menus: %s\n", error->message);
    g_error_free (error);
    error = NULL;
    return 3;
  }

  indicator_menu = gtk_ui_manager_get_widget (uim, "/ui/IndicatorPopup");
  app_indicator_set_menu (indicator, GTK_MENU (indicator_menu));
  app_indicator_set_status (indicator, APP_INDICATOR_STATUS_ACTIVE);

  //app_indicator_set_label (indicator, " ⇧ ⋀ ⌥ ⎇  ⌘ ", NULL);
  //symbols: shift U21E7 ctrl U22C0 alt/altgr U2325 U2387  cmd U2318
  //from font: DejaVu Sans

  appdata._display = _display;
  appdata._deviceId = &_deviceId;
  appdata.indicator = indicator;
  gtk_timeout_add (120, (GtkFunction) update_xkb_state, &appdata);
  //nice for realtime tasks, to replace gtk_timeout
  //gtk_idle_add ((GtkFunction) idle_func, &appdata);

  gtk_main ();

  XCloseDisplay (_display);
  return 0;
}
  1. Installing needed headers/libs: (Not sure if I miss any)

    sudo apt-get install libx11-dev libappindicator-dev libgtk2.0-dev
    
  2. Compiling:

    gcc -Wall unity-xkbmod.c -o unity-xkbmod `pkg-config --cflags --libs appindicator-0.1` -lX11
    
  3. Run:

    ./unity-xkbmod
    

Note:

  • libappindicator used for Unity indicators lack an important feature which make easy to ports other desktops indicators. See Bug #812067 API needed: pixbuf icon setting support

    Without this feature, let's say we need (Shift, Ctrl, Alt, AltGr, Super) with sticky keys active; we have 3 main status for each (Off, On/Latched, Locked). So 3^5 combined icons should be generated. (Where normal case just 3x5 single icons)

    That's why I used indicator label with symbols from DejaVu Sans font.

  • To put an icon, place it in same folder and name it icon.*. Accepted formats: png, svg, ico, xpm ...

    If you don't like any icon, just create a 1x1 px image instead.

References:

  • The X Keyboard Extension
  • Application Panel Indicators
  • Source of xkbwatch & plasma-widget-kbstate

Solution 2:

Another solution which is not perfect, but some may find it useful as it is possible to have full functionality as in KDE like activating a mod with a click.

  1. install kbstate applet

    sudo apt-get install plasma-widget-kbstate kde-workspace-bin
    
  2. Run it in plasma-windowed player

    • Regular window

      plasma-windowed kbstate
      

      screenshot of plasma-widget-kbstate in Xubuntu

    • Borderless window

      plasma-windowed --noborder kbstate
      

      screenshot of borderless plasma-widget-kbstate in Unity

I didn't have much time to play with, but wmctrl may help to position, resize and make it on top at launch.

Reference: What command to launch a kde plasmoid and the kickstart menu

Solution 3:

I did a search for Ubuntu sticky keys monitor and found something which may be helpful here: http://code.google.com/p/key-mon/

show case screenshot

Try running

key-mon --sticky for Support for sticky keys.

Reference: http://code.google.com/p/key-mon/

Note that the version available through the software center is 1.6-0ubuntu1. Released June of 2011 which does not support the --sticky switch. If the indicator looks exactly like the above you have the older version Try the latest version at http://code.google.com/p/key-mon/ as of this writing it is keymon_1.17-1_all.deb 229 KB Released Jan 3 2014. Support for --sticky switch tested and confirmed.