[gnome-shell] accessibility: Add pointer accessibility support



commit 5ace4682bf325e62f03b2f94600e281c622c67a3
Author: Olivier Fourdan <ofourdan redhat com>
Date:   Wed Mar 20 17:46:12 2019 +0100

    accessibility: Add pointer accessibility support
    
    Adds the UI part for the pointer accessibility features.
    
    The various timeouts running are notified using a pie-timer showing
    under the pointer.
    
    For dwell-click type selection, we use a drop-down menu. Users can
    use the dwell-click to select the next type of dwell click to be
    emitted.
    
    https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/474

 data/theme/gnome-shell-sass/_common.scss |   9 +++
 js/js-resources.gresource.xml            |   2 +
 js/ui/main.js                            |   4 ++
 js/ui/panel.js                           |   1 +
 js/ui/pointerA11yTimeout.js              | 104 +++++++++++++++++++++++++++++++
 js/ui/sessionMode.js                     |   6 +-
 js/ui/status/dwellClick.js               |  86 +++++++++++++++++++++++++
 po/POTFILES.in                           |   1 +
 8 files changed, 210 insertions(+), 3 deletions(-)
---
diff --git a/data/theme/gnome-shell-sass/_common.scss b/data/theme/gnome-shell-sass/_common.scss
index c8c53d557..50bfc6650 100644
--- a/data/theme/gnome-shell-sass/_common.scss
+++ b/data/theme/gnome-shell-sass/_common.scss
@@ -1195,6 +1195,15 @@ StScrollBar {
   }
 }
 
+// Pointer accessibility notifications
+.pie-timer {
+  width: 60px;
+  height: 60px;
+  -pie-border-width: 3px;
+  -pie-border-color: $selected_bg_color;
+  -pie-background-color: lighten(transparentize($selected_bg_color, 0.7), 40%);
+}
+
 /* NETWORK DIALOGS */
 
 .nm-dialog {
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index 7807cce9d..b5348ddcb 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -82,6 +82,7 @@
     <file>ui/pageIndicators.js</file>
     <file>ui/panel.js</file>
     <file>ui/panelMenu.js</file>
+    <file>ui/pointerA11yTimeout.js</file>
     <file>ui/pointerWatcher.js</file>
     <file>ui/popupMenu.js</file>
     <file>ui/remoteSearch.js</file>
@@ -122,6 +123,7 @@
 
     <file>ui/status/accessibility.js</file>
     <file>ui/status/brightness.js</file>
+    <file>ui/status/dwellClick.js</file>
     <file>ui/status/location.js</file>
     <file>ui/status/keyboard.js</file>
     <file>ui/status/nightLight.js</file>
diff --git a/js/ui/main.js b/js/ui/main.js
index b3cd69283..c01be9882 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -38,6 +38,7 @@ const Magnifier = imports.ui.magnifier;
 const XdndHandler = imports.ui.xdndHandler;
 const KbdA11yDialog = imports.ui.kbdA11yDialog;
 const LocatePointer = imports.ui.locatePointer;
+const PointerA11yTimeout = imports.ui.pointerA11yTimeout;
 
 const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
 const STICKY_KEYS_ENABLE = 'stickykeys-enable';
@@ -82,6 +83,7 @@ let _cssStylesheet = null;
 let _a11ySettings = null;
 let _themeResource = null;
 let _oskResource = null;
+let pointerA11yTimeout = null;
 
 function _sessionUpdated() {
     if (sessionMode.isPrimary)
@@ -189,6 +191,8 @@ function _initializeUI() {
     layoutManager.init();
     overview.init();
 
+    pointerA11yTimeout = new PointerA11yTimeout.PointerA11yTimeout();
+
     _a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA });
 
     global.display.connect('overlay-key', () => {
diff --git a/js/ui/panel.js b/js/ui/panel.js
index 6522e9099..ef08b1aad 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -817,6 +817,7 @@ const PANEL_ITEM_IMPLEMENTATIONS = {
     'dateMenu': imports.ui.dateMenu.DateMenuButton,
     'a11y': imports.ui.status.accessibility.ATIndicator,
     'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
+    'dwellClick': imports.ui.status.dwellClick.DwellClickIndicator,
 };
 
 var Panel = GObject.registerClass(
diff --git a/js/ui/pointerA11yTimeout.js b/js/ui/pointerA11yTimeout.js
new file mode 100644
index 000000000..d9309415b
--- /dev/null
+++ b/js/ui/pointerA11yTimeout.js
@@ -0,0 +1,104 @@
+const { Clutter, GLib, GObject, Meta, St } = imports.gi;
+const Tweener = imports.ui.tweener;
+const Main = imports.ui.main;
+const Cairo = imports.cairo;
+
+const ANIMATION_STEPS = 36.;
+
+var PieTimer = GObject.registerClass(
+class PieTimer extends St.DrawingArea {
+    _init() {
+        this._x = 0;
+        this._y = 0;
+        this._startTime = 0;
+        this._duration = 0;
+        super._init( { style_class: 'pie-timer',
+                       visible: false,
+                       can_focus: false,
+                       reactive: false });
+    }
+
+    vfunc_repaint() {
+        let node = this.get_theme_node();
+        let backgroundColor = node.get_color('-pie-background-color');
+        let borderColor = node.get_color('-pie-border-color');
+        let borderWidth = node.get_length('-pie-border-width');
+        let [width, height] = this.get_surface_size();
+        let radius = Math.min(width / 2, height / 2);
+
+        let currentTime = GLib.get_monotonic_time() / 1000.0;
+        let ellapsed = currentTime - this._startTime;
+        let angle = (ellapsed / this._duration) * 2 * Math.PI;
+        let startAngle = 3 * Math.PI / 2;
+        let endAngle = startAngle + angle;
+
+        let cr = this.get_context();
+        cr.setLineCap(Cairo.LineCap.ROUND);
+        cr.setLineJoin(Cairo.LineJoin.ROUND);
+        cr.translate(width / 2, height / 2);
+
+        cr.moveTo(0, 0);
+        cr.arc(0, 0, radius - borderWidth, startAngle, endAngle);
+        cr.lineTo(0, 0);
+        cr.closePath();
+
+        cr.setLineWidth(0);
+        Clutter.cairo_set_source_color(cr, backgroundColor);
+        cr.fillPreserve();
+
+        cr.setLineWidth(borderWidth);
+        Clutter.cairo_set_source_color(cr, borderColor);
+        cr.stroke();
+
+        cr.$dispose();
+    }
+
+    start(x, y, duration) {
+        Tweener.removeTweens(this);
+
+        this.x = x - this.width / 2;
+        this.y = y - this.height / 2;
+        this.show();
+        Main.uiGroup.set_child_above_sibling(this, null);
+
+        this._startTime = GLib.get_monotonic_time() / 1000.0;
+        this._duration = duration;
+
+        Tweener.addTween(this,
+                         { opacity: 255,
+                           time: duration / 1000,
+                           transition: 'easeOutQuad',
+                           onUpdateScope: this,
+                           onUpdate() { this.queue_repaint() },
+                           onCompleteScope: this,
+                           onComplete() { this.stop(); }
+                          });
+    }
+
+    stop() {
+        Tweener.removeTweens(this);
+        this.hide();
+    }
+});
+
+var PointerA11yTimeout = class PointerA11yTimeout {
+    constructor() {
+        let manager = Clutter.DeviceManager.get_default();
+        let pieTimer = new PieTimer();
+
+        Main.uiGroup.add_actor(pieTimer);
+
+        manager.connect('ptr-a11y-timeout-started', (manager, device, type, timeout) => {
+            let [x, y, mods] = global.get_pointer();
+            pieTimer.start(x, y, timeout);
+            if (type == Clutter.PointerA11yTimeoutType.GESTURE)
+              global.display.set_cursor(Meta.Cursor.CROSSHAIR);
+        });
+
+        manager.connect('ptr-a11y-timeout-stopped', (manager, device, type) => {
+            pieTimer.stop();
+            if (type == Clutter.PointerA11yTimeoutType.GESTURE)
+              global.display.set_cursor(Meta.Cursor.DEFAULT);
+        });
+    }
+};
diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js
index 1f3e23b7a..ae6c64dd3 100644
--- a/js/ui/sessionMode.js
+++ b/js/ui/sessionMode.js
@@ -48,7 +48,7 @@ const _modes = {
         panel: {
             left: [],
             center: ['dateMenu'],
-            right: ['a11y', 'keyboard', 'aggregateMenu']
+            right: ['dwellClick', 'a11y', 'keyboard', 'aggregateMenu']
         },
         panelStyle: 'login-screen'
     },
@@ -73,7 +73,7 @@ const _modes = {
         panel: {
             left: [],
             center: [],
-            right: ['a11y', 'keyboard', 'aggregateMenu']
+            right: ['dwellClick', 'a11y', 'keyboard', 'aggregateMenu']
         },
         panelStyle: 'unlock-screen'
     },
@@ -101,7 +101,7 @@ const _modes = {
         panel: {
             left: ['activities', 'appMenu'],
             center: ['dateMenu'],
-            right: ['a11y', 'keyboard', 'aggregateMenu']
+            right: ['dwellClick', 'a11y', 'keyboard', 'aggregateMenu']
         }
     }
 };
diff --git a/js/ui/status/dwellClick.js b/js/ui/status/dwellClick.js
new file mode 100644
index 000000000..cb9f331f3
--- /dev/null
+++ b/js/ui/status/dwellClick.js
@@ -0,0 +1,86 @@
+const { Clutter, Gio, GLib, GObject, St } = imports.gi;
+const Mainloop = imports.mainloop;
+
+const PanelMenu = imports.ui.panelMenu;
+const PopupMenu = imports.ui.popupMenu;
+
+const MOUSE_A11Y_SCHEMA       = 'org.gnome.desktop.a11y.mouse';
+const KEY_DWELL_CLICK_ENABLED = 'dwell-click-enabled';
+const KEY_DWELL_MODE          = 'dwell-mode';
+const DWELL_MODE_WINDOW       = 'window';
+const DWELL_CLICK_MODES = {
+    primary:   {
+                 name: _("Single Click"),
+                 icon: 'pointer-primary-click-symbolic',
+                 type: Clutter.PointerA11yDwellClickType.PRIMARY
+               },
+    double:    {
+                 name: _("Double Click"),
+                 icon: 'pointer-double-click-symbolic',
+                 type: Clutter.PointerA11yDwellClickType.DOUBLE
+               },
+    drag:      {
+                 name: _("Drag"),
+                 icon: 'pointer-drag-symbolic',
+                 type: Clutter.PointerA11yDwellClickType.DRAG
+               },
+    secondary: {
+                 name: _("Secondary Click"),
+                 icon: 'pointer-secondary-click-symbolic',
+                 type: Clutter.PointerA11yDwellClickType.SECONDARY
+               },
+};
+
+var DwellClickIndicator = GObject.registerClass(
+class DwellClickIndicator extends PanelMenu.Button {
+    _init() {
+        super._init(0.0, _("Dwell Click"));
+
+        this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
+        this._icon = new St.Icon({ style_class: 'system-status-icon',
+                                   icon_name: 'pointer-primary-click-symbolic' });
+        this._hbox.add_child(this._icon);
+        this._hbox.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM));
+
+        this.add_child(this._hbox);
+
+        this._a11ySettings = new Gio.Settings({ schema_id: MOUSE_A11Y_SCHEMA });
+        this._a11ySettings.connect('changed::' + KEY_DWELL_CLICK_ENABLED, 
this._syncMenuVisibility.bind(this));
+        this._a11ySettings.connect('changed::' + KEY_DWELL_MODE, this._syncMenuVisibility.bind(this));
+
+        this._deviceManager = Clutter.DeviceManager.get_default();
+        this._deviceManager.connect('ptr-a11y-dwell-click-type-changed', this._updateClickType.bind(this));
+
+        this._addDwellAction(DWELL_CLICK_MODES.primary);
+        this._addDwellAction(DWELL_CLICK_MODES.double);
+        this._addDwellAction(DWELL_CLICK_MODES.drag);
+        this._addDwellAction(DWELL_CLICK_MODES.secondary);
+
+        this._setClickType(DWELL_CLICK_MODES.primary);
+        this._syncMenuVisibility();
+    }
+
+    _syncMenuVisibility() {
+        this.visible =
+          (this._a11ySettings.get_boolean(KEY_DWELL_CLICK_ENABLED) &&
+           this._a11ySettings.get_string(KEY_DWELL_MODE) == DWELL_MODE_WINDOW);
+
+        return GLib.SOURCE_REMOVE;
+    }
+
+    _addDwellAction(mode) {
+        this.menu.addAction(mode.name, this._setClickType.bind(this, mode), mode.icon);
+    }
+
+    _updateClickType(manager, click_type) {
+        for (let mode in DWELL_CLICK_MODES) {
+            if (DWELL_CLICK_MODES[mode].type == click_type)
+                this._icon.icon_name = DWELL_CLICK_MODES[mode].icon;
+        }
+    }
+
+    _setClickType(mode) {
+        this._deviceManager.set_pointer_a11y_dwell_click_type(mode.type);
+        this._icon.icon_name = mode.icon;
+    }
+});
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 43ea408ac..a7b216710 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -54,6 +54,7 @@ js/ui/shellMountOperation.js
 js/ui/status/accessibility.js
 js/ui/status/bluetooth.js
 js/ui/status/brightness.js
+js/ui/status/dwellClick.js
 js/ui/status/keyboard.js
 js/ui/status/location.js
 js/ui/status/network.js


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]