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; +}