diff --git a/js/Makefile.am b/js/Makefile.am
index 62c8323ad..b294dbe02 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -105,6 +105,7 @@ nobase_dist_js_DATA = \
ui/workspacesView.js \
ui/workspaceSwitcherPopup.js \
ui/xdndHandler.js \
+ ui/xkbHandler.js \
ui/components/__init__.js \
ui/components/autorunManager.js \
ui/components/automountManager.js \
diff --git a/js/ui/main.js b/js/ui/main.js
index f43811414..3ca09014f 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -36,6 +36,7 @@ const ShellMountOperation = imports.ui.shellMountOperation;
const WindowManager = imports.ui.windowManager;
const Magnifier = imports.ui.magnifier;
const XdndHandler = imports.ui.xdndHandler;
+const XkbHandler = imports.ui.xkbHandler;
const Util = imports.misc.util;
const OVERRIDES_SCHEMA = 'org.gnome.shell.overrides';
@@ -63,6 +64,7 @@ let modalActorFocusStack = [];
let uiGroup = null;
let magnifier = null;
let xdndHandler = null;
+let xkbHandler = null;
let keyboard = null;
let layoutManager = null;
let _startDate;
@@ -139,6 +141,7 @@ function _initializeUI() {
uiGroup = layoutManager.uiGroup;
xdndHandler = new XdndHandler.XdndHandler();
+ xkbHandler = new XkbHandler.XkbHandler();
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
osdWindow = new OsdWindow.OsdWindow();
overview = new Overview.Overview();
diff --git a/js/ui/xkbHandler.js b/js/ui/xkbHandler.js
new file mode 100644
index 000000000..82d9e2349
--- /dev/null
+++ b/js/ui/xkbHandler.js
@@ -0,0 +1,72 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const Clutter = imports.gi.Clutter;
+const St = imports.gi.St;
+
+const Lang = imports.lang;
+const Layout = imports.ui.layout;
+const Main = imports.ui.main;
+const Shell = imports.gi.Shell;
+const Tweener = imports.ui.tweener;
+
+const FADE_TIME = 0.1;
+
+const XkbHandler = new Lang.Class({
+ Name: 'XkbHandler',
+
+ _init: function() {
+
+ global.connect('xkb-state-changed', Lang.bind(this, this._onStateChange));
+ this.actor = new St.Widget({ x_expand: true,
+ y_expand: true,
+ x_align: Clutter.ActorAlign.END,
+ y_align: Clutter.ActorAlign.END,
+ margin_left: 100,
+ margin_right: 100,
+ margin_bottom: 100});
+ this.actor.add_constraint(new Layout.MonitorConstraint({ primary: true }));
+ this._box = new St.BoxLayout({ style_class: 'osd-window',
+ vertical: true });
+ this.actor.add_actor(this._box);
+
+ this._label = new St.Label();
+ this._box.add(this._label);
+
+ Main.layoutManager.addChrome(this.actor, { affectsInputRegion: false });
+
+ this.actor.hide();
+ },
+
+ _onStateChange: function(obj, latched, locked) {
+ mods = [ 'Shift', 'Caps', 'Ctrl', 'Alt', 'Num Lock', '', 'Super', '' ];
+ markup = '';
+ for (let i = 0; i < 8; i++) {
+ if (locked & (1 << i))
+ {
+ if (markup != '') markup += ' ';
+ markup += '' + mods[i] + '';
+ }
+ else if (latched & (1 << i))
+ {
+ if (markup != '') markup += ' ';
+ markup += mods[i];
+ }
+ }
+
+ this._label.clutter_text.set_markup (markup);
+ if (latched != 0 || locked != 0)
+ {
+ this.actor.show();
+ this.actor.opacity = 0;
+ Tweener.addTween(this.actor,
+ { opacity: 255,
+ time: FADE_TIME,
+ transition: 'easeOutQuad' });
+ }
+ else
+ {
+ this.actor.hide();
+ }
+ log ('xkb state changed, latched: ' + latched + ' locked: ' + locked);
+ }
+});
diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c
index 3470637fa..96ac9ccde 100644
--- a/src/gnome-shell-plugin.c
+++ b/src/gnome-shell-plugin.c
@@ -35,6 +35,7 @@
#include
#include
#include
+#include
#include "shell-global-private.h"
#include "shell-perf-log.h"
@@ -101,6 +102,9 @@ struct _GnomeShellPlugin
guint have_swap_event : 1;
CoglContext *cogl_context;
+ int xkb_event_base;
+ XkbDescPtr xkb;
+
ShellGlobal *global;
};
@@ -187,6 +191,10 @@ gnome_shell_plugin_start (MetaPlugin *plugin)
int status;
GjsContext *gjs_context;
ClutterBackend *backend;
+ MetaScreen *screen;
+ MetaDisplay *display;
+ Display *xdisplay;
+ int xkb_base_error_type, xkb_opcode;
backend = clutter_get_default_backend ();
shell_plugin->cogl_context = clutter_backend_get_cogl_context (backend);
@@ -199,6 +207,26 @@ gnome_shell_plugin_start (MetaPlugin *plugin)
"GL buffer swap complete event received (with timestamp of completion)",
"x");
+ screen = meta_plugin_get_screen (META_PLUGIN (shell_plugin));
+ display = meta_screen_get_display (screen);
+ xdisplay = meta_display_get_xdisplay (display);
+ if (!XkbQueryExtension (xdisplay, &xkb_opcode,
+ &shell_plugin->xkb_event_base,
+ &xkb_base_error_type,
+ NULL, NULL))
+ {
+ shell_plugin->xkb_event_base = -1;
+ g_message ("could not find XKB extension.");
+ }
+ else
+ {
+ shell_plugin->xkb = XkbGetMap (xdisplay,
+ XkbAllComponentsMask,
+ XkbUseCoreKbd);
+ XkbSelectEvents (xdisplay, XkbUseCoreKbd,
+ XkbAllEventsMask, XkbAllEventsMask);
+ }
+
shell_plugin->global = shell_global_get ();
_shell_global_set_plugin (shell_plugin->global, META_PLUGIN (shell_plugin));
@@ -389,6 +417,10 @@ gnome_shell_plugin_xevent_filter (MetaPlugin *plugin,
if (_shell_global_check_xdnd_event (shell_plugin->global, xev))
return TRUE;
+ if (xev->type == shell_plugin->xkb_event_base &&
+ _shell_global_check_xkb_event (shell_plugin->global, xev))
+ return TRUE;
+
return clutter_x11_handle_event (xev) != CLUTTER_X11_FILTER_CONTINUE;
}
diff --git a/src/shell-global-private.h b/src/shell-global-private.h
index 786719f5b..f68122834 100644
--- a/src/shell-global-private.h
+++ b/src/shell-global-private.h
@@ -16,4 +16,7 @@ GjsContext *_shell_global_get_gjs_context (ShellGlobal *global);
gboolean _shell_global_check_xdnd_event (ShellGlobal *global,
XEvent *xev);
+gboolean _shell_global_check_xkb_event (ShellGlobal *global,
+ XEvent *xev);
+
#endif /* __SHELL_GLOBAL_PRIVATE_H__ */
diff --git a/src/shell-global.c b/src/shell-global.c
index 889e14612..c80df4f22 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -16,6 +16,7 @@
#include
#include
+#include
#include
#include
#include
@@ -122,6 +123,7 @@ enum
XDND_POSITION_CHANGED,
XDND_LEAVE,
XDND_ENTER,
+ XKB_STATE_CHANGED,
NOTIFY_ERROR,
LAST_SIGNAL
};
@@ -338,6 +340,15 @@ shell_global_class_init (ShellGlobalClass *klass)
NULL, NULL, NULL,
G_TYPE_NONE, 0);
+ shell_global_signals[XKB_STATE_CHANGED] =
+ g_signal_new ("xkb-state-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_UINT, G_TYPE_UINT);
+
shell_global_signals[NOTIFY_ERROR] =
g_signal_new ("notify-error",
G_TYPE_FROM_CLASS (klass),
@@ -1779,3 +1790,30 @@ shell_global_get_session_mode (ShellGlobal *global)
return global->session_mode;
}
+
+static void
+notify_xkb_state (ShellGlobal *global,
+ XkbStateNotifyEvent *event)
+{
+ if (event->changed & (XkbModifierLatchMask | XkbModifierLockMask))
+ g_signal_emit_by_name (G_OBJECT (global), "xkb-state-changed",
+ event->latched_mods, event->locked_mods);
+}
+
+gboolean
+_shell_global_check_xkb_event (ShellGlobal *global,
+ XEvent *event)
+{
+ XkbEvent *xkb_event = (XkbEvent *)event;
+
+ switch (xkb_event->any.xkb_type)
+ {
+ case XkbStateNotify:
+ notify_xkb_state (global, &xkb_event->state);
+ break;
+ default:
+ break;
+ }
+
+ return FALSE;
+}