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
This commit is contained in:
parent
14d9839ed3
commit
5ace4682bf
@ -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 */
|
/* NETWORK DIALOGS */
|
||||||
|
|
||||||
.nm-dialog {
|
.nm-dialog {
|
||||||
|
@ -82,6 +82,7 @@
|
|||||||
<file>ui/pageIndicators.js</file>
|
<file>ui/pageIndicators.js</file>
|
||||||
<file>ui/panel.js</file>
|
<file>ui/panel.js</file>
|
||||||
<file>ui/panelMenu.js</file>
|
<file>ui/panelMenu.js</file>
|
||||||
|
<file>ui/pointerA11yTimeout.js</file>
|
||||||
<file>ui/pointerWatcher.js</file>
|
<file>ui/pointerWatcher.js</file>
|
||||||
<file>ui/popupMenu.js</file>
|
<file>ui/popupMenu.js</file>
|
||||||
<file>ui/remoteSearch.js</file>
|
<file>ui/remoteSearch.js</file>
|
||||||
@ -122,6 +123,7 @@
|
|||||||
|
|
||||||
<file>ui/status/accessibility.js</file>
|
<file>ui/status/accessibility.js</file>
|
||||||
<file>ui/status/brightness.js</file>
|
<file>ui/status/brightness.js</file>
|
||||||
|
<file>ui/status/dwellClick.js</file>
|
||||||
<file>ui/status/location.js</file>
|
<file>ui/status/location.js</file>
|
||||||
<file>ui/status/keyboard.js</file>
|
<file>ui/status/keyboard.js</file>
|
||||||
<file>ui/status/nightLight.js</file>
|
<file>ui/status/nightLight.js</file>
|
||||||
|
@ -38,6 +38,7 @@ const Magnifier = imports.ui.magnifier;
|
|||||||
const XdndHandler = imports.ui.xdndHandler;
|
const XdndHandler = imports.ui.xdndHandler;
|
||||||
const KbdA11yDialog = imports.ui.kbdA11yDialog;
|
const KbdA11yDialog = imports.ui.kbdA11yDialog;
|
||||||
const LocatePointer = imports.ui.locatePointer;
|
const LocatePointer = imports.ui.locatePointer;
|
||||||
|
const PointerA11yTimeout = imports.ui.pointerA11yTimeout;
|
||||||
|
|
||||||
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
|
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
|
||||||
const STICKY_KEYS_ENABLE = 'stickykeys-enable';
|
const STICKY_KEYS_ENABLE = 'stickykeys-enable';
|
||||||
@ -82,6 +83,7 @@ let _cssStylesheet = null;
|
|||||||
let _a11ySettings = null;
|
let _a11ySettings = null;
|
||||||
let _themeResource = null;
|
let _themeResource = null;
|
||||||
let _oskResource = null;
|
let _oskResource = null;
|
||||||
|
let pointerA11yTimeout = null;
|
||||||
|
|
||||||
function _sessionUpdated() {
|
function _sessionUpdated() {
|
||||||
if (sessionMode.isPrimary)
|
if (sessionMode.isPrimary)
|
||||||
@ -189,6 +191,8 @@ function _initializeUI() {
|
|||||||
layoutManager.init();
|
layoutManager.init();
|
||||||
overview.init();
|
overview.init();
|
||||||
|
|
||||||
|
pointerA11yTimeout = new PointerA11yTimeout.PointerA11yTimeout();
|
||||||
|
|
||||||
_a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA });
|
_a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA });
|
||||||
|
|
||||||
global.display.connect('overlay-key', () => {
|
global.display.connect('overlay-key', () => {
|
||||||
|
@ -817,6 +817,7 @@ const PANEL_ITEM_IMPLEMENTATIONS = {
|
|||||||
'dateMenu': imports.ui.dateMenu.DateMenuButton,
|
'dateMenu': imports.ui.dateMenu.DateMenuButton,
|
||||||
'a11y': imports.ui.status.accessibility.ATIndicator,
|
'a11y': imports.ui.status.accessibility.ATIndicator,
|
||||||
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
|
'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
|
||||||
|
'dwellClick': imports.ui.status.dwellClick.DwellClickIndicator,
|
||||||
};
|
};
|
||||||
|
|
||||||
var Panel = GObject.registerClass(
|
var Panel = GObject.registerClass(
|
||||||
|
104
js/ui/pointerA11yTimeout.js
Normal file
104
js/ui/pointerA11yTimeout.js
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -48,7 +48,7 @@ const _modes = {
|
|||||||
panel: {
|
panel: {
|
||||||
left: [],
|
left: [],
|
||||||
center: ['dateMenu'],
|
center: ['dateMenu'],
|
||||||
right: ['a11y', 'keyboard', 'aggregateMenu']
|
right: ['dwellClick', 'a11y', 'keyboard', 'aggregateMenu']
|
||||||
},
|
},
|
||||||
panelStyle: 'login-screen'
|
panelStyle: 'login-screen'
|
||||||
},
|
},
|
||||||
@ -73,7 +73,7 @@ const _modes = {
|
|||||||
panel: {
|
panel: {
|
||||||
left: [],
|
left: [],
|
||||||
center: [],
|
center: [],
|
||||||
right: ['a11y', 'keyboard', 'aggregateMenu']
|
right: ['dwellClick', 'a11y', 'keyboard', 'aggregateMenu']
|
||||||
},
|
},
|
||||||
panelStyle: 'unlock-screen'
|
panelStyle: 'unlock-screen'
|
||||||
},
|
},
|
||||||
@ -101,7 +101,7 @@ const _modes = {
|
|||||||
panel: {
|
panel: {
|
||||||
left: ['activities', 'appMenu'],
|
left: ['activities', 'appMenu'],
|
||||||
center: ['dateMenu'],
|
center: ['dateMenu'],
|
||||||
right: ['a11y', 'keyboard', 'aggregateMenu']
|
right: ['dwellClick', 'a11y', 'keyboard', 'aggregateMenu']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
86
js/ui/status/dwellClick.js
Normal file
86
js/ui/status/dwellClick.js
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
});
|
@ -54,6 +54,7 @@ js/ui/shellMountOperation.js
|
|||||||
js/ui/status/accessibility.js
|
js/ui/status/accessibility.js
|
||||||
js/ui/status/bluetooth.js
|
js/ui/status/bluetooth.js
|
||||||
js/ui/status/brightness.js
|
js/ui/status/brightness.js
|
||||||
|
js/ui/status/dwellClick.js
|
||||||
js/ui/status/keyboard.js
|
js/ui/status/keyboard.js
|
||||||
js/ui/status/location.js
|
js/ui/status/location.js
|
||||||
js/ui/status/network.js
|
js/ui/status/network.js
|
||||||
|
Loading…
Reference in New Issue
Block a user