18

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

  • Errr... There appears to be a large gap when it comes to this... There is a thing to beep when a modifier key is pressed though. – Wilf Jan 8 '14 at 17:20
  • @wilf If I'm not mistaken there's a Accessibility icon in Unity (same as Gnome 3) but not a "extra" icon to inform the user about the status. – Braiam Jan 9 '14 at 1:46
  • 4
    @Takkat: Thank you for the suggestion. indicator-keylock only displays the state of those keys which traditionally have state indicators on the keyboard itself: CapsLock, ScrollLock, NumLock. I need an indicator which shows the state of the standard modifier keys: Shift, Ctrl, Tux, Alt. All major desktops (KDE, Windows, Mac OSX) have this indicator applet available. – dotancohen Jan 9 '14 at 8:45
  • 1
    I believe the packet name of the KDE tool you are referring too is plasma-widget-kbstate and a quick search in the software center does indeed not heed any equivalent results – Daniel W. Mar 7 '14 at 20:39
  • 2
    @shengy: I'm using the KB State plasmoid. If you are on Kubuntu, then install it with sudo apt-get install plasma-widget-kbstate. – dotancohen May 3 '15 at 10:05
7

This is an outstanding issue in Unity:

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:

  • Nice, thank you! I will give this a try and get back to you. Great work, Sneetsher! – dotancohen Mar 31 '14 at 5:59
  • That you sneetsher! – dotancohen Apr 11 '14 at 12:16
  • yep, thank you @dotancohen. So at least you can use Unity, may be it's ugly without fixed position icons? – user.dz Apr 11 '14 at 13:40
  • 1
    I don't mind ugly, you should see a picture of me! I've mentioned this issue on the Ubuntu bugtracker. – dotancohen Apr 11 '14 at 13:44
  • 1
    Sneetsher, I wanted to say thank you again. I've now moved to Unity and the move would have been impossible without this terrific application. I've also learned quite a bit from the code. Thank you! – dotancohen Jan 13 '16 at 8:51
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

1

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.

  • 1
    Nice find, however key-mon shows which button was pressed and not the state of the modifier keys. The distinction is that the key-mon display returns to the un-pressed state 1 second after the button was pressed. A keyboard modifier state applet would return the display to the unpressed state when the next keypress would occur and therefore disable the 'pressed' state. – dotancohen Mar 12 '14 at 8:27
  • re-edited answer. – Elder Geek Mar 12 '14 at 15:23
  • Sorry for the confusion. I've edited the answer (again) to provide exactly what you need. – Elder Geek Mar 12 '14 at 16:03
  • 1
    Thank you Elder Geek. The --sticky option seems to guess the state of the modifier keys by watching the state other keys, instead of querying the proper interface as do proper keyboard modifier state applets. This can be seen by pressing a modifier key twice: the first time enables the indicator in the applet, but the second time does not release it. Thus, the state is wrongly reported as being active when it is not. This related bug addresses the issue partially, I'll fill in the details there and in additional bugs. Thank you. – dotancohen Mar 13 '14 at 8:06
  • 1
    @dotancohen I tested based on your statement that " A keyboard modifier state applet would return the display to the unpressed state when the next keypress would occur and therefore disable the 'pressed' state." and it never dawned on me that anyone would hit the modifier key twice in succession on purpose. My apologies for the oversight. – Elder Geek Mar 13 '14 at 20:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.