diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss
index 64cc612aa3eddac761864fcfd77a8f1e420af202..c8c53d557ff0e5d652f3c702794eff5662438307 100644
--- a/data/theme/gnome-shell-sass/_common.scss
+++ b/data/theme/gnome-shell-sass/_common.scss
@@ -1159,6 +1159,15 @@ StScrollBar {
border: 1px solid $selected_bg_color;
}
+// Pointer location
+.ripple-pointer-location {
+ width: 50px;
+ height: 50px;
+ border-radius: 25px 25px 25px 25px; // radius the size of the box give us the curve
+ background-color: lighten(transparentize($selected_bg_color, 0.7), 30%);
+ box-shadow: 0 0 2px 2px lighten($selected_bg_color, 20%);
+}
+
// not really top bar only
.popup-menu-arrow { icon-size: 1.09em; }
.popup-menu-icon { icon-size: 1.09em; }
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index 836d1c6746f35a55a77a82affb90abc3a8ba7c54..7807cce9db5350e2339187eedfc111051700f245 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -64,6 +64,7 @@
ui/keyboard.js
ui/layout.js
ui/lightbox.js
+ ui/locatePointer.js
ui/lookingGlass.js
ui/magnifier.js
ui/magnifierDBus.js
@@ -84,6 +85,7 @@
ui/pointerWatcher.js
ui/popupMenu.js
ui/remoteSearch.js
+ ui/ripples.js
ui/runDialog.js
ui/screenShield.js
ui/screencast.js
diff --git a/js/ui/layout.js b/js/ui/layout.js
index 6f6677d642aa2e6062b63bf5860a4da2d252ef61..90385e82cb2c751b52094b138f9cc5185abccf62 100644
--- a/js/ui/layout.js
+++ b/js/ui/layout.js
@@ -11,6 +11,7 @@ const DND = imports.ui.dnd;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
+const Ripples = imports.ui.ripples;
var STARTUP_ANIMATION_TIME = 0.5;
var KEYBOARD_ANIMATION_TIME = 0.15;
@@ -1117,14 +1118,15 @@ var HotCorner = class HotCorner {
Shell.ActionMode.OVERVIEW);
this._pressureBarrier.connect('trigger', this._toggleOverview.bind(this));
- // Cache the three ripples instead of dynamically creating and destroying them.
- this._ripple1 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });
- this._ripple2 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });
- this._ripple3 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });
+ let px = 0.0;
+ let py = 0.0;
+ if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
+ px = 1.0;
+ py = 0.0;
+ }
- layoutManager.uiGroup.add_actor(this._ripple1);
- layoutManager.uiGroup.add_actor(this._ripple2);
- layoutManager.uiGroup.add_actor(this._ripple3);
+ this._ripples = new Ripples.Ripples(px, py, 'ripple-box');
+ this._ripples.addTo(layoutManager.uiGroup);
}
setBarrierSize(size) {
@@ -1206,53 +1208,12 @@ var HotCorner = class HotCorner {
this.actor.destroy();
}
- _animRipple(ripple, delay, time, startScale, startOpacity, finalScale) {
- // We draw a ripple by using a source image and animating it scaling
- // outwards and fading away. We want the ripples to move linearly
- // or it looks unrealistic, but if the opacity of the ripple goes
- // linearly to zero it fades away too quickly, so we use Tweener's
- // 'onUpdate' to give a non-linear curve to the fade-away and make
- // it more visible in the middle section.
-
- ripple._opacity = startOpacity;
-
- if (ripple.get_text_direction() == Clutter.TextDirection.RTL)
- ripple.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
-
- ripple.visible = true;
- ripple.opacity = 255 * Math.sqrt(startOpacity);
- ripple.scale_x = ripple.scale_y = startScale;
-
- ripple.x = this._x;
- ripple.y = this._y;
-
- Tweener.addTween(ripple, { _opacity: 0,
- scale_x: finalScale,
- scale_y: finalScale,
- delay: delay,
- time: time,
- transition: 'linear',
- onUpdate() { ripple.opacity = 255 * Math.sqrt(ripple._opacity); },
- onComplete() { ripple.visible = false; } });
- }
-
- _rippleAnimation() {
- // Show three concentric ripples expanding outwards; the exact
- // parameters were found by trial and error, so don't look
- // for them to make perfect sense mathematically
-
- // delay time scale opacity => scale
- this._animRipple(this._ripple1, 0.0, 0.83, 0.25, 1.0, 1.5);
- this._animRipple(this._ripple2, 0.05, 1.0, 0.0, 0.7, 1.25);
- this._animRipple(this._ripple3, 0.35, 1.0, 0.0, 0.3, 1);
- }
-
_toggleOverview() {
if (this._monitor.inFullscreen && !Main.overview.visible)
return;
if (Main.overview.shouldToggleByCornerOrButton()) {
- this._rippleAnimation();
+ this._ripples.playAnimation(this._x, this._y);
Main.overview.toggle();
}
}
diff --git a/js/ui/locatePointer.js b/js/ui/locatePointer.js
new file mode 100644
index 0000000000000000000000000000000000000000..52a0bc48657d62c3d025789682fab4f070750c17
--- /dev/null
+++ b/js/ui/locatePointer.js
@@ -0,0 +1,24 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const { Clutter, Gio, GLib, St } = imports.gi;
+const Ripples = imports.ui.ripples;
+const Main = imports.ui.main;
+
+const LOCATE_POINTER_KEY = "locate-pointer";
+const LOCATE_POINTER_SCHEMA = "org.gnome.desktop.interface"
+
+var locatePointer = class {
+ constructor() {
+ this._settings = new Gio.Settings({schema_id: LOCATE_POINTER_SCHEMA});
+ this._ripples = new Ripples.Ripples(0.5, 0.5, 'ripple-pointer-location');
+ this._ripples.addTo(Main.uiGroup);
+ }
+
+ show() {
+ if (!this._settings.get_boolean("locate-pointer"))
+ return;
+
+ let [x, y, mods] = global.get_pointer();
+ this._ripples.playAnimation(x, y);
+ }
+};
diff --git a/js/ui/main.js b/js/ui/main.js
index d9f287cd9ded262a38ac40ca5d362340b223023a..b3cd69283a7a1b11afd832acd01ea8c555c7b811 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -37,6 +37,7 @@ const WindowManager = imports.ui.windowManager;
const Magnifier = imports.ui.magnifier;
const XdndHandler = imports.ui.xdndHandler;
const KbdA11yDialog = imports.ui.kbdA11yDialog;
+const LocatePointer = imports.ui.locatePointer;
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
const STICKY_KEYS_ENABLE = 'stickykeys-enable';
@@ -74,6 +75,7 @@ var layoutManager = null;
var kbdA11yDialog = null;
var inputMethod = null;
var introspectService = null;
+var locatePointer = null;
let _startDate;
let _defaultCssStylesheet = null;
let _cssStylesheet = null;
@@ -92,6 +94,8 @@ function _sessionUpdated() {
wm.allowKeybinding('overlay-key', Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW);
+ wm.allowKeybinding('locate-pointer-key', Shell.ActionMode.ALL);
+
wm.setCustomKeybindingHandler('panel-run-dialog',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
@@ -165,6 +169,8 @@ function _initializeUI() {
kbdA11yDialog = new KbdA11yDialog.KbdA11yDialog();
wm = new WindowManager.WindowManager();
magnifier = new Magnifier.Magnifier();
+ locatePointer = new LocatePointer.locatePointer();
+
if (LoginManager.canLock())
screenShield = new ScreenShield.ScreenShield();
@@ -190,6 +196,10 @@ function _initializeUI() {
overview.toggle();
});
+ global.connect('locate-pointer', () => {
+ locatePointer.show();
+ });
+
global.display.connect('show-restart-message', (display, message) => {
showRestartMessage(message);
return true;
diff --git a/js/ui/ripples.js b/js/ui/ripples.js
new file mode 100644
index 0000000000000000000000000000000000000000..37d7362deef788dcf1326750141164ca89f92dca
--- /dev/null
+++ b/js/ui/ripples.js
@@ -0,0 +1,97 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const { Clutter, St } = imports.gi;
+const Tweener = imports.ui.tweener;
+
+// Shamelessly copied from the layout "hotcorner" ripples implementation
+var Ripples = class Ripples {
+ constructor(px, py, styleClass) {
+ this._x = 0;
+ this._y = 0;
+
+ this._px = px;
+ this._py = py;
+
+ this._ripple1 = new St.BoxLayout({ style_class: styleClass,
+ opacity: 0,
+ can_focus: false,
+ reactive: false,
+ visible: false });
+ this._ripple1.set_pivot_point(px, py);
+
+ this._ripple2 = new St.BoxLayout({ style_class: styleClass,
+ opacity: 0,
+ can_focus: false,
+ reactive: false,
+ visible: false });
+ this._ripple2.set_pivot_point(px, py);
+
+ this._ripple3 = new St.BoxLayout({ style_class: styleClass,
+ opacity: 0,
+ can_focus: false,
+ reactive: false,
+ visible: false });
+ this._ripple3.set_pivot_point(px, py);
+ }
+
+ _animRipple(ripple, delay, time, startScale, startOpacity, finalScale) {
+ // We draw a ripple by using a source image and animating it scaling
+ // outwards and fading away. We want the ripples to move linearly
+ // or it looks unrealistic, but if the opacity of the ripple goes
+ // linearly to zero it fades away too quickly, so we use Tweener's
+ // 'onUpdate' to give a non-linear curve to the fade-away and make
+ // it more visible in the middle section.
+
+ ripple.x = this._x;
+ ripple.y = this._y;
+ ripple._opacity = startOpacity;
+ ripple.visible = true;
+ ripple.opacity = 255 * Math.sqrt(startOpacity);
+ ripple.scale_x = ripple.scale_y = startScale;
+ ripple.set_translation( - this._px * ripple.width, - this._py * ripple.height, 0.0);
+
+ Tweener.addTween(ripple, { _opacity: 0,
+ scale_x: finalScale,
+ scale_y: finalScale,
+ delay: delay,
+ time: time,
+ transition: 'linear',
+ onUpdate() { ripple.opacity = 255 * Math.sqrt(ripple._opacity); },
+ onComplete() { ripple.visible = false; } });
+ }
+
+ addTo(stage) {
+ if (this._stage !== undefined) {
+ throw new Error('Ripples already added');
+ return;
+ }
+
+ this._stage = stage;
+ this._stage.add_actor(this._ripple1);
+ this._stage.add_actor(this._ripple2);
+ this._stage.add_actor(this._ripple3);
+ }
+
+ playAnimation(x, y) {
+ if (this._stage === undefined) {
+ throw new Error('Ripples not added');
+ return;
+ }
+
+ this._x = x;
+ this._y = y;
+
+ this._stage.set_child_above_sibling(this._ripple1, null);
+ this._stage.set_child_above_sibling(this._ripple2, this._ripple1);
+ this._stage.set_child_above_sibling(this._ripple3, this._ripple2);
+
+ // Show three concentric ripples expanding outwards; the exact
+ // parameters were found by trial and error, so don't look
+ // for them to make perfect sense mathematically
+
+ // delay time scale opacity => scale
+ this._animRipple(this._ripple1, 0.0, 0.83, 0.25, 1.0, 1.5);
+ this._animRipple(this._ripple2, 0.05, 1.0, 0.0, 0.7, 1.25);
+ this._animRipple(this._ripple3, 0.35, 1.0, 0.0, 0.3, 1);
+ }
+};
diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c
index 35a9a8bcb44f567f22b6d40ba7c1fd4f50a9aba6..2e491646caa7111d493d6fca1decf2cd0ad3790d 100644
--- a/src/gnome-shell-plugin.c
+++ b/src/gnome-shell-plugin.c
@@ -346,6 +346,13 @@ gnome_shell_plugin_create_inhibit_shortcuts_dialog (MetaPlugin *plugin,
return _shell_wm_create_inhibit_shortcuts_dialog (get_shell_wm (), window);
}
+static void
+gnome_shell_plugin_locate_pointer (MetaPlugin *plugin)
+{
+ GnomeShellPlugin *shell_plugin = GNOME_SHELL_PLUGIN (plugin);
+ _shell_global_locate_pointer (shell_plugin->global);
+}
+
static void
gnome_shell_plugin_class_init (GnomeShellPluginClass *klass)
{
@@ -378,6 +385,8 @@ gnome_shell_plugin_class_init (GnomeShellPluginClass *klass)
plugin_class->create_close_dialog = gnome_shell_plugin_create_close_dialog;
plugin_class->create_inhibit_shortcuts_dialog = gnome_shell_plugin_create_inhibit_shortcuts_dialog;
+
+ plugin_class->locate_pointer = gnome_shell_plugin_locate_pointer;
}
static void
diff --git a/src/shell-global-private.h b/src/shell-global-private.h
index df53236aac54ea01718916891ff87aa13f793c7e..9969691cb47db09ae5c81f043cc609e80f4aa645 100644
--- a/src/shell-global-private.h
+++ b/src/shell-global-private.h
@@ -18,4 +18,6 @@ GjsContext *_shell_global_get_gjs_context (ShellGlobal *global);
gboolean _shell_global_check_xdnd_event (ShellGlobal *global,
XEvent *xev);
+void _shell_global_locate_pointer (ShellGlobal *global);
+
#endif /* __SHELL_GLOBAL_PRIVATE_H__ */
diff --git a/src/shell-global.c b/src/shell-global.c
index 293053d0b58446125b27db9848a10bf8509ede46..70120c2da834ebc0680925af873f9a84560e3472 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -115,6 +115,7 @@ enum {
enum
{
NOTIFY_ERROR,
+ LOCATE_POINTER,
LAST_SIGNAL
};
@@ -357,6 +358,13 @@ shell_global_class_init (ShellGlobalClass *klass)
G_TYPE_NONE, 2,
G_TYPE_STRING,
G_TYPE_STRING);
+ shell_global_signals[LOCATE_POINTER] =
+ g_signal_new ("locate-pointer",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
g_object_class_install_property (gobject_class,
PROP_SESSION_MODE,
@@ -1732,3 +1740,9 @@ shell_global_get_persistent_state (ShellGlobal *global,
{
return load_variant (global->userdatadir_path, property_type, property_name);
}
+
+void
+_shell_global_locate_pointer (ShellGlobal *global)
+{
+ g_signal_emit (global, shell_global_signals[LOCATE_POINTER], 0);
+}