gnome-shell/js/ui/padOsd.js
Carlos Garnacho 3ad2baede0 padOsd: Apply specific CSS to Button/Leader SVG classes
Applying a fill operation on the Leader line path seems to close
it, resulting in filled polygon. Bug or not this is not the intended
result here, we can do less ambiguously by not specifying the fill
CSS property to the Leader class.

Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/2570

https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1290
2020-05-29 20:26:57 +02:00

994 lines
34 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported PadOsd, PadOsdService */
const { Atk, Clutter, GDesktopEnums, Gio,
GLib, GObject, Gtk, Meta, Pango, Rsvg, St } = imports.gi;
const Signals = imports.signals;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const Layout = imports.ui.layout;
const { loadInterfaceXML } = imports.misc.fileUtils;
const ACTIVE_COLOR = "#729fcf";
const LTR = 0;
const RTL = 1;
const CW = 0;
const CCW = 1;
const UP = 0;
const DOWN = 1;
var PadChooser = GObject.registerClass({
Signals: { 'pad-selected': { param_types: [Clutter.InputDevice.$gtype] } },
}, class PadChooser extends St.Button {
_init(device, groupDevices) {
super._init({
style_class: 'pad-chooser-button',
toggle_mode: true,
});
this.currentDevice = device;
this._padChooserMenu = null;
let arrow = new St.Icon({
style_class: 'popup-menu-arrow',
icon_name: 'pan-down-symbolic',
accessible_role: Atk.Role.ARROW,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
});
this.set_child(arrow);
this._ensureMenu(groupDevices);
this.connect('destroy', this._onDestroy.bind(this));
}
vfunc_clicked() {
if (this.get_checked()) {
if (this._padChooserMenu != null)
this._padChooserMenu.open(true);
else
this.set_checked(false);
} else {
this._padChooserMenu.close(true);
}
}
_ensureMenu(devices) {
this._padChooserMenu = new PopupMenu.PopupMenu(this, 0.5, St.Side.TOP);
this._padChooserMenu.connect('menu-closed', () => {
this.set_checked(false);
});
this._padChooserMenu.actor.hide();
Main.uiGroup.add_actor(this._padChooserMenu.actor);
for (let i = 0; i < devices.length; i++) {
let device = devices[i];
if (device == this.currentDevice)
continue;
this._padChooserMenu.addAction(device.get_device_name(), () => {
this.emit('pad-selected', device);
});
}
}
_onDestroy() {
this._padChooserMenu.destroy();
}
update(devices) {
if (this._padChooserMenu)
this._padChooserMenu.actor.destroy();
this.set_checked(false);
this._ensureMenu(devices);
}
});
var KeybindingEntry = GObject.registerClass({
Signals: { 'keybinding-edited': { param_types: [GObject.TYPE_STRING] } },
}, class KeybindingEntry extends St.Entry {
_init() {
super._init({ hint_text: _("New shortcut…"), style: 'width: 10em' });
}
vfunc_captured_event(event) {
if (event.type() != Clutter.EventType.KEY_PRESS)
return Clutter.EVENT_PROPAGATE;
let str = Gtk.accelerator_name_with_keycode(null,
event.get_key_symbol(),
event.get_key_code(),
event.get_state());
this.set_text(str);
this.emit('keybinding-edited', str);
return Clutter.EVENT_STOP;
}
});
var ActionComboBox = GObject.registerClass({
Signals: { 'action-selected': { param_types: [GObject.TYPE_INT] } },
}, class ActionComboBox extends St.Button {
_init() {
super._init({ style_class: 'button' });
this.set_toggle_mode(true);
let boxLayout = new Clutter.BoxLayout({ orientation: Clutter.Orientation.HORIZONTAL,
spacing: 6 });
let box = new St.Widget({ layout_manager: boxLayout });
this.set_child(box);
this._label = new St.Label({ style_class: 'combo-box-label' });
box.add_child(this._label);
let arrow = new St.Icon({ style_class: 'popup-menu-arrow',
icon_name: 'pan-down-symbolic',
accessible_role: Atk.Role.ARROW,
y_expand: true,
y_align: Clutter.ActorAlign.CENTER });
box.add_child(arrow);
this._editMenu = new PopupMenu.PopupMenu(this, 0, St.Side.TOP);
this._editMenu.connect('menu-closed', () => {
this.set_checked(false);
});
this._editMenu.actor.hide();
Main.uiGroup.add_actor(this._editMenu.actor);
this._actionLabels = new Map();
this._actionLabels.set(GDesktopEnums.PadButtonAction.NONE, _("Application defined"));
this._actionLabels.set(GDesktopEnums.PadButtonAction.HELP, _("Show on-screen help"));
this._actionLabels.set(GDesktopEnums.PadButtonAction.SWITCH_MONITOR, _("Switch monitor"));
this._actionLabels.set(GDesktopEnums.PadButtonAction.KEYBINDING, _("Assign keystroke"));
this._buttonItems = [];
for (let [action, label] of this._actionLabels.entries()) {
let selectedAction = action;
let item = this._editMenu.addAction(label, () => {
this._onActionSelected(selectedAction);
});
/* These actions only apply to pad buttons */
if (selectedAction == GDesktopEnums.PadButtonAction.HELP ||
selectedAction == GDesktopEnums.PadButtonAction.SWITCH_MONITOR)
this._buttonItems.push(item);
}
this.setAction(GDesktopEnums.PadButtonAction.NONE);
}
_onActionSelected(action) {
this.setAction(action);
this.popdown();
this.emit('action-selected', action);
}
setAction(action) {
this._label.set_text(this._actionLabels.get(action));
}
popup() {
this._editMenu.open(true);
}
popdown() {
this._editMenu.close(true);
}
vfunc_clicked() {
if (this.get_checked())
this.popup();
else
this.popdown();
}
setButtonActionsActive(active) {
this._buttonItems.forEach(item => item.setSensitive(active));
}
});
var ActionEditor = GObject.registerClass({
Signals: { 'done': {} },
}, class ActionEditor extends St.Widget {
_init() {
let boxLayout = new Clutter.BoxLayout({ orientation: Clutter.Orientation.HORIZONTAL,
spacing: 12 });
super._init({ layout_manager: boxLayout });
this._actionComboBox = new ActionComboBox();
this._actionComboBox.connect('action-selected', this._onActionSelected.bind(this));
this.add_actor(this._actionComboBox);
this._keybindingEdit = new KeybindingEntry();
this._keybindingEdit.connect('keybinding-edited', this._onKeybindingEdited.bind(this));
this.add_actor(this._keybindingEdit);
this._doneButton = new St.Button({ label: _("Done"),
style_class: 'button',
x_expand: false });
this._doneButton.connect('clicked', this._onEditingDone.bind(this));
this.add_actor(this._doneButton);
}
_updateKeybindingEntryState() {
if (this._currentAction == GDesktopEnums.PadButtonAction.KEYBINDING) {
this._keybindingEdit.set_text(this._currentKeybinding);
this._keybindingEdit.show();
this._keybindingEdit.grab_key_focus();
} else {
this._keybindingEdit.hide();
}
}
setSettings(settings, action) {
this._buttonSettings = settings;
this._currentAction = this._buttonSettings.get_enum('action');
this._currentKeybinding = this._buttonSettings.get_string('keybinding');
this._actionComboBox.setAction(this._currentAction);
this._updateKeybindingEntryState();
let isButton = action == Meta.PadActionType.BUTTON;
this._actionComboBox.setButtonActionsActive(isButton);
}
close() {
this._actionComboBox.popdown();
this.hide();
}
_onKeybindingEdited(entry, keybinding) {
this._currentKeybinding = keybinding;
}
_onActionSelected(menu, action) {
this._currentAction = action;
this._updateKeybindingEntryState();
}
_storeSettings() {
if (!this._buttonSettings)
return;
let keybinding = null;
if (this._currentAction == GDesktopEnums.PadButtonAction.KEYBINDING)
keybinding = this._currentKeybinding;
this._buttonSettings.set_enum('action', this._currentAction);
if (keybinding)
this._buttonSettings.set_string('keybinding', keybinding);
else
this._buttonSettings.reset('keybinding');
}
_onEditingDone() {
this._storeSettings();
this.close();
this.emit('done');
}
});
var PadDiagram = GObject.registerClass({
Properties: {
'left-handed': GObject.ParamSpec.boolean('left-handed',
'left-handed', 'Left handed',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT_ONLY,
false),
'image': GObject.ParamSpec.string('image', 'image', 'Image',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT_ONLY,
null),
'editor-actor': GObject.ParamSpec.object('editor-actor',
'editor-actor',
'Editor actor',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT_ONLY,
Clutter.Actor.$gtype),
},
}, class PadDiagram extends St.DrawingArea {
_init(params) {
let file = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/pad-osd.css');
let [success_, css] = file.load_contents(null);
if (css instanceof Uint8Array)
css = imports.byteArray.toString(css);
this._curEdited = null;
this._prevEdited = null;
this._css = css;
this._labels = [];
this._activeButtons = [];
super._init(params);
}
// eslint-disable-next-line camelcase
get left_handed() {
return this._leftHanded;
}
// eslint-disable-next-line camelcase
set left_handed(leftHanded) {
this._leftHanded = leftHanded;
}
get image() {
return this._imagePath;
}
set image(imagePath) {
let originalHandle = Rsvg.Handle.new_from_file(imagePath);
let dimensions = originalHandle.get_dimensions();
this._imageWidth = dimensions.width;
this._imageHeight = dimensions.height;
this._imagePath = imagePath;
this._handle = this._composeStyledDiagram();
this._initLabels();
}
// eslint-disable-next-line camelcase
get editor_actor() {
return this._editorActor;
}
// eslint-disable-next-line camelcase
set editor_actor(actor) {
actor.hide();
this._editorActor = actor;
this.add_actor(actor);
}
_initLabels() {
let i = 0;
for (i = 0; ; i++) {
if (!this._addLabel(Meta.PadActionType.BUTTON, i))
break;
}
for (i = 0; ; i++) {
if (!this._addLabel(Meta.PadActionType.RING, i, CW) ||
!this._addLabel(Meta.PadActionType.RING, i, CCW))
break;
}
for (i = 0; ; i++) {
if (!this._addLabel(Meta.PadActionType.STRIP, i, UP) ||
!this._addLabel(Meta.PadActionType.STRIP, i, DOWN))
break;
}
}
_wrappingSvgHeader() {
return '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' +
'<svg version="1.1" xmlns="http://www.w3.org/2000/svg" ' +
'xmlns:xi="http://www.w3.org/2001/XInclude" ' +
'width="%d" height="%d">'.format(this._imageWidth, this._imageHeight) +
'<style type="text/css">';
}
_wrappingSvgFooter() {
return '</style>' +
'<xi:include href="' + this._imagePath + '" />' +
'</svg>';
}
_cssString() {
let css = this._css;
for (let i = 0; i < this._activeButtons.length; i++) {
let ch = String.fromCharCode('A'.charCodeAt() + this._activeButtons[i]);
css += '.%s.Leader { stroke: %s !important; }'.format(ch, ACTIVE_COLOR);
css += '.%s.Button { stroke: %s !important; fill: %s !important; }'.format(ch, ACTIVE_COLOR, ACTIVE_COLOR);
}
return css;
}
_composeStyledDiagram() {
let svgData = '';
if (!GLib.file_test(this._imagePath, GLib.FileTest.EXISTS))
return null;
svgData += this._wrappingSvgHeader();
svgData += this._cssString();
svgData += this._wrappingSvgFooter();
let istream = new Gio.MemoryInputStream();
istream.add_bytes(new GLib.Bytes(svgData));
return Rsvg.Handle.new_from_stream_sync(istream,
Gio.File.new_for_path(this._imagePath),
0, null);
}
_updateDiagramScale() {
[this._actorWidth, this._actorHeight] = this.get_size();
let dimensions = this._handle.get_dimensions();
let scaleX = this._actorWidth / dimensions.width;
let scaleY = this._actorHeight / dimensions.height;
this._scale = Math.min(scaleX, scaleY);
}
_allocateChild(child, x, y, direction) {
let [, natHeight] = child.get_preferred_height(-1);
let [, natWidth] = child.get_preferred_width(natHeight);
let childBox = new Clutter.ActorBox();
// I miss Cairo.Matrix
let dimensions = this._handle.get_dimensions();
x = x * this._scale + this._actorWidth / 2 - dimensions.width / 2 * this._scale;
y = y * this._scale + this._actorHeight / 2 - dimensions.height / 2 * this._scale;
if (direction == LTR) {
childBox.x1 = x;
childBox.x2 = x + natWidth;
} else {
childBox.x1 = x - natWidth;
childBox.x2 = x;
}
childBox.y1 = y - natHeight / 2;
childBox.y2 = y + natHeight / 2;
child.allocate(childBox, 0);
}
vfunc_allocate(box, flags) {
super.vfunc_allocate(box, flags);
if (this._handle === null)
return;
this._updateDiagramScale();
for (let i = 0; i < this._labels.length; i++) {
const { label, x, y, arrangement } = this._labels[i];
this._allocateChild(label, x, y, arrangement);
}
if (this._editorActor && this._curEdited) {
const { x, y, arrangement } = this._curEdited;
this._allocateChild(this._editorActor, x, y, arrangement);
}
}
vfunc_repaint() {
if (this._handle == null)
return;
if (this._scale == null)
this._updateDiagramScale();
let [width, height] = this.get_surface_size();
let dimensions = this._handle.get_dimensions();
let cr = this.get_context();
cr.save();
cr.translate(width / 2, height / 2);
cr.scale(this._scale, this._scale);
if (this._leftHanded)
cr.rotate(Math.PI);
cr.translate(-dimensions.width / 2, -dimensions.height / 2);
this._handle.render_cairo(cr);
cr.restore();
cr.$dispose();
}
_getItemLabelCoords(labelName, leaderName) {
if (this._handle == null)
return [false];
let leaderPos, leaderSize, pos;
let found, direction;
[found, pos] = this._handle.get_position_sub('#%s'.format(labelName));
if (!found)
return [false];
[found, leaderPos] = this._handle.get_position_sub('#%s'.format(leaderName));
[found, leaderSize] = this._handle.get_dimensions_sub('#%s'.format(leaderName));
if (!found)
return [false];
if (pos.x > leaderPos.x + leaderSize.width)
direction = LTR;
else
direction = RTL;
if (this._leftHanded) {
direction = 1 - direction;
pos.x = this._imageWidth - pos.x;
pos.y = this._imageHeight - pos.y;
}
return [true, pos.x, pos.y, direction];
}
_getButtonLabels(button) {
let ch = String.fromCharCode('A'.charCodeAt() + button);
let labelName = 'Label%s'.format(ch);
let leaderName = 'Leader%s'.format(ch);
return [labelName, leaderName];
}
_getRingLabels(number, dir) {
let numStr = number > 0 ? (number + 1).toString() : '';
let dirStr = dir == CW ? 'CW' : 'CCW';
let labelName = 'LabelRing%s%s'.format(numStr, dirStr);
let leaderName = 'LeaderRing%s%s'.format(numStr, dirStr);
return [labelName, leaderName];
}
_getStripLabels(number, dir) {
let numStr = number > 0 ? (number + 1).toString() : '';
let dirStr = dir == UP ? 'Up' : 'Down';
let labelName = 'LabelStrip%s%s'.format(numStr, dirStr);
let leaderName = 'LeaderStrip%s%s'.format(numStr, dirStr);
return [labelName, leaderName];
}
_getLabelCoords(action, idx, dir) {
if (action == Meta.PadActionType.BUTTON)
return this._getItemLabelCoords(...this._getButtonLabels(idx));
else if (action == Meta.PadActionType.RING)
return this._getItemLabelCoords(...this._getRingLabels(idx, dir));
else if (action == Meta.PadActionType.STRIP)
return this._getItemLabelCoords(...this._getStripLabels(idx, dir));
return [false];
}
_invalidateSvg() {
if (this._handle == null)
return;
this._handle = this._composeStyledDiagram();
this.queue_repaint();
}
activateButton(button) {
this._activeButtons.push(button);
this._invalidateSvg();
}
deactivateButton(button) {
for (let i = 0; i < this._activeButtons.length; i++) {
if (this._activeButtons[i] == button)
this._activeButtons.splice(i, 1);
}
this._invalidateSvg();
}
_addLabel(action, idx, dir) {
let [found, x, y, arrangement] = this._getLabelCoords(action, idx, dir);
if (!found)
return false;
let label = new St.Label();
this._labels.push({ label, action, idx, dir, x, y, arrangement });
this.add_actor(label);
return true;
}
updateLabels(getText) {
for (let i = 0; i < this._labels.length; i++) {
const { label, action, idx, dir } = this._labels[i];
let str = getText(action, idx, dir);
label.set_text(str);
}
this.queue_relayout();
}
_applyLabel(label, action, idx, dir, str) {
if (str !== null)
label.set_text(str);
label.show();
}
stopEdition(continues, str) {
this._editorActor.hide();
if (this._prevEdited) {
const { label, action, idx, dir } = this._prevEdited;
this._applyLabel(label, action, idx, dir, str);
this._prevEdited = null;
}
if (this._curEdited) {
const { label, action, idx, dir } = this._curEdited;
this._applyLabel(label, action, idx, dir, str);
if (continues)
this._prevEdited = this._curEdited;
this._curEdited = null;
}
this.queue_relayout();
}
startEdition(action, idx, dir) {
let editedLabel;
if (this._curEdited)
return;
for (let i = 0; i < this._labels.length; i++) {
if (action == this._labels[i].action &&
idx == this._labels[i].idx && dir == this._labels[i].dir) {
this._curEdited = this._labels[i];
editedLabel = this._curEdited.label;
break;
}
}
if (this._curEdited == null)
return;
this._editorActor.show();
editedLabel.hide();
this.queue_relayout();
}
});
var PadOsd = GObject.registerClass({
Signals: {
'pad-selected': { param_types: [Clutter.InputDevice.$gtype] },
'closed': {},
},
}, class PadOsd extends St.BoxLayout {
_init(padDevice, settings, imagePath, editionMode, monitorIndex) {
super._init({
style_class: 'pad-osd-window',
vertical: true,
x_expand: true,
y_expand: true,
reactive: true,
});
this.padDevice = padDevice;
this._groupPads = [padDevice];
this._settings = settings;
this._imagePath = imagePath;
this._editionMode = editionMode;
this._capturedEventId = global.stage.connect('captured-event', this._onCapturedEvent.bind(this));
this._padChooser = null;
let seat = Clutter.get_default_backend().get_default_seat();
this._deviceAddedId = seat.connect('device-added', (_seat, device) => {
if (device.get_device_type() == Clutter.InputDeviceType.PAD_DEVICE &&
this.padDevice.is_grouped(device)) {
this._groupPads.push(device);
this._updatePadChooser();
}
});
this._deviceRemovedId = seat.connect('device-removed', (_seat, device) => {
// If the device is being removed, destroy the padOsd.
if (device == this.padDevice) {
this.destroy();
} else if (this._groupPads.includes(device)) {
// Or update the pad chooser if the device belongs to
// the same group.
this._groupPads.splice(this._groupPads.indexOf(device), 1);
this._updatePadChooser();
}
});
seat.list_devices().forEach(device => {
if (device != this.padDevice &&
device.get_device_type() == Clutter.InputDeviceType.PAD_DEVICE &&
this.padDevice.is_grouped(device))
this._groupPads.push(device);
});
this.connect('destroy', this._onDestroy.bind(this));
Main.uiGroup.add_actor(this);
this._monitorIndex = monitorIndex;
let constraint = new Layout.MonitorConstraint({ index: monitorIndex });
this.add_constraint(constraint);
this._titleBox = new St.BoxLayout({ style_class: 'pad-osd-title-box',
vertical: false,
x_expand: false,
x_align: Clutter.ActorAlign.CENTER });
this.add_actor(this._titleBox);
let labelBox = new St.BoxLayout({ style_class: 'pad-osd-title-menu-box',
vertical: true });
this._titleBox.add_actor(labelBox);
this._titleLabel = new St.Label({ style: 'font-side: larger; font-weight: bold;',
x_align: Clutter.ActorAlign.CENTER });
this._titleLabel.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE);
this._titleLabel.clutter_text.set_text(padDevice.get_device_name());
labelBox.add_actor(this._titleLabel);
this._tipLabel = new St.Label({ x_align: Clutter.ActorAlign.CENTER });
labelBox.add_actor(this._tipLabel);
this._updatePadChooser();
this._actionEditor = new ActionEditor();
this._actionEditor.connect('done', this._endActionEdition.bind(this));
this._padDiagram = new PadDiagram({ image: this._imagePath,
left_handed: settings.get_boolean('left-handed'),
editor_actor: this._actionEditor,
x_expand: true,
y_expand: true });
this.add_actor(this._padDiagram);
this._updateActionLabels();
let buttonBox = new St.Widget({ layout_manager: new Clutter.BinLayout(),
x_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER });
this.add_actor(buttonBox);
this._editButton = new St.Button({
label: _('Edit…'),
style_class: 'button',
can_focus: true,
x_align: Clutter.ActorAlign.CENTER,
});
this._editButton.connect('clicked', () => {
this.setEditionMode(true);
});
buttonBox.add_actor(this._editButton);
this._syncEditionMode();
Main.pushModal(this);
}
_updatePadChooser() {
if (this._groupPads.length > 1) {
if (this._padChooser == null) {
this._padChooser = new PadChooser(this.padDevice, this._groupPads);
this._padChooser.connect('pad-selected', (chooser, pad) => {
this._requestForOtherPad(pad);
});
this._titleBox.add_child(this._padChooser);
} else {
this._padChooser.update(this._groupPads);
}
} else if (this._padChooser != null) {
this._padChooser.destroy();
this._padChooser = null;
}
}
_requestForOtherPad(pad) {
if (pad == this.padDevice || !this._groupPads.includes(pad))
return;
let editionMode = this._editionMode;
this.destroy();
global.display.request_pad_osd(pad, editionMode);
}
_getActionText(type, number) {
let str = global.display.get_pad_action_label(this.padDevice, type, number);
return str ? str : _("None");
}
_updateActionLabels() {
this._padDiagram.updateLabels(this._getActionText.bind(this));
}
_onCapturedEvent(actor, event) {
let isModeSwitch =
(event.type() == Clutter.EventType.PAD_BUTTON_PRESS ||
event.type() == Clutter.EventType.PAD_BUTTON_RELEASE) &&
this.padDevice.get_mode_switch_button_group(event.get_button()) >= 0;
if (event.type() == Clutter.EventType.PAD_BUTTON_PRESS &&
event.get_source_device() == this.padDevice) {
this._padDiagram.activateButton(event.get_button());
/* Buttons that switch between modes cannot be edited */
if (this._editionMode && !isModeSwitch)
this._startButtonActionEdition(event.get_button());
return Clutter.EVENT_STOP;
} else if (event.type() == Clutter.EventType.PAD_BUTTON_RELEASE &&
event.get_source_device() == this.padDevice) {
this._padDiagram.deactivateButton(event.get_button());
if (isModeSwitch) {
this._endActionEdition();
this._updateActionLabels();
}
return Clutter.EVENT_STOP;
} else if (event.type() == Clutter.EventType.KEY_PRESS &&
(!this._editionMode || event.get_key_symbol() === Clutter.KEY_Escape)) {
if (this._editedAction != null)
this._endActionEdition();
else
this.destroy();
return Clutter.EVENT_STOP;
} else if (event.get_source_device() == this.padDevice &&
event.type() == Clutter.EventType.PAD_STRIP) {
if (this._editionMode) {
let [retval_, number, mode] = event.get_pad_event_details();
this._startStripActionEdition(number, UP, mode);
}
} else if (event.get_source_device() == this.padDevice &&
event.type() == Clutter.EventType.PAD_RING) {
if (this._editionMode) {
let [retval_, number, mode] = event.get_pad_event_details();
this._startRingActionEdition(number, CCW, mode);
}
}
// If the event comes from another pad in the same group,
// show the OSD for it.
if (this._groupPads.includes(event.get_source_device())) {
this._requestForOtherPad(event.get_source_device());
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
}
_syncEditionMode() {
this._editButton.set_reactive(!this._editionMode);
this._editButton.save_easing_state();
this._editButton.set_easing_duration(200);
this._editButton.set_opacity(this._editionMode ? 128 : 255);
this._editButton.restore_easing_state();
let title;
if (this._editionMode) {
title = _("Press a button to configure");
this._tipLabel.set_text(_("Press Esc to exit"));
} else {
title = this.padDevice.get_device_name();
this._tipLabel.set_text(_("Press any key to exit"));
}
this._titleLabel.set_text(title);
}
_isEditedAction(type, number, dir) {
if (!this._editedAction)
return false;
return this._editedAction.type == type &&
this._editedAction.number == number &&
this._editedAction.dir == dir;
}
_followUpActionEdition(str) {
let { type, dir, number, mode } = this._editedAction;
let hasNextAction = type == Meta.PadActionType.RING && dir == CCW ||
type == Meta.PadActionType.STRIP && dir == UP;
if (!hasNextAction)
return false;
this._padDiagram.stopEdition(true, str);
this._editedAction = null;
if (type == Meta.PadActionType.RING)
this._startRingActionEdition(number, CW, mode);
else
this._startStripActionEdition(number, DOWN, mode);
return true;
}
_endActionEdition() {
this._actionEditor.close();
if (this._editedAction != null) {
let str = global.display.get_pad_action_label(this.padDevice,
this._editedAction.type,
this._editedAction.number);
if (this._followUpActionEdition(str))
return;
this._padDiagram.stopEdition(false, str ? str : _("None"));
this._editedAction = null;
}
this._editedActionSettings = null;
}
_startActionEdition(key, type, number, dir, mode) {
if (this._isEditedAction(type, number, dir))
return;
this._endActionEdition();
this._editedAction = { type, number, dir, mode };
let settingsPath = this._settings.path + key + '/';
this._editedActionSettings = Gio.Settings.new_with_path('org.gnome.desktop.peripherals.tablet.pad-button',
settingsPath);
this._actionEditor.setSettings(this._editedActionSettings, type);
this._padDiagram.startEdition(type, number, dir);
}
_startButtonActionEdition(button) {
let ch = String.fromCharCode('A'.charCodeAt() + button);
let key = 'button%s'.format(ch);
this._startActionEdition(key, Meta.PadActionType.BUTTON, button);
}
_startRingActionEdition(ring, dir, mode) {
let ch = String.fromCharCode('A'.charCodeAt() + ring);
let key = 'ring%s-%s-mode-%d'.format(ch, dir == CCW ? 'ccw' : 'cw', mode);
this._startActionEdition(key, Meta.PadActionType.RING, ring, dir, mode);
}
_startStripActionEdition(strip, dir, mode) {
let ch = String.fromCharCode('A'.charCodeAt() + strip);
let key = 'strip%s-%s-mode-%d'.format(ch, dir == UP ? 'up' : 'down', mode);
this._startActionEdition(key, Meta.PadActionType.STRIP, strip, dir, mode);
}
setEditionMode(editionMode) {
if (this._editionMode == editionMode)
return;
this._editionMode = editionMode;
this._syncEditionMode();
}
_onDestroy() {
Main.popModal(this);
this._actionEditor.close();
let seat = Clutter.get_default_backend().get_default_seat();
if (this._deviceRemovedId != 0) {
seat.disconnect(this._deviceRemovedId);
this._deviceRemovedId = 0;
}
if (this._deviceAddedId != 0) {
seat.disconnect(this._deviceAddedId);
this._deviceAddedId = 0;
}
if (this._capturedEventId != 0) {
global.stage.disconnect(this._capturedEventId);
this._capturedEventId = 0;
}
this.emit('closed');
}
});
const PadOsdIface = loadInterfaceXML('org.gnome.Shell.Wacom.PadOsd');
var PadOsdService = class {
constructor() {
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(PadOsdIface, this);
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Wacom');
Gio.DBus.session.own_name('org.gnome.Shell.Wacom.PadOsd', Gio.BusNameOwnerFlags.REPLACE, null, null);
}
ShowAsync(params, invocation) {
let [deviceNode, editionMode] = params;
let seat = Clutter.get_default_backend().get_default_seat();
let devices = seat.list_devices();
let padDevice = null;
devices.forEach(device => {
if (deviceNode == device.get_device_node() &&
device.get_device_type() == Clutter.InputDeviceType.PAD_DEVICE)
padDevice = device;
});
if (padDevice == null) {
invocation.return_error_literal(Gio.IOErrorEnum,
Gio.IOErrorEnum.CANCELLED,
"Invalid params");
return;
}
global.display.request_pad_osd(padDevice, editionMode);
invocation.return_value(null);
}
};
Signals.addSignalMethods(PadOsdService.prototype);