ui: Add PadOsd

This is an implementation of the pad OSD that's been previously
present in gnome-settings-daemon. Since things are moving closer
to the compositor, it makes sense to have this implemented as shell
UI.

https://bugzilla.gnome.org/show_bug.cgi?id=771067
This commit is contained in:
Carlos Garnacho 2016-06-22 19:57:12 +02:00
parent d042dd73aa
commit e006b9b400
10 changed files with 845 additions and 0 deletions

View File

@ -38,6 +38,7 @@ endif
introspectiondir = $(datadir)/dbus-1/interfaces introspectiondir = $(datadir)/dbus-1/interfaces
introspection_DATA = \ introspection_DATA = \
org.gnome.Shell.PadOsd.xml \
org.gnome.Shell.Screencast.xml \ org.gnome.Shell.Screencast.xml \
org.gnome.Shell.Screenshot.xml \ org.gnome.Shell.Screenshot.xml \
org.gnome.ShellSearchProvider.xml \ org.gnome.ShellSearchProvider.xml \
@ -115,6 +116,7 @@ EXTRA_DIST = \
$(convert_DATA) \ $(convert_DATA) \
$(keys_DATA) \ $(keys_DATA) \
$(dist_theme_files) \ $(dist_theme_files) \
pad-osd.css \
perf-background.xml.in \ perf-background.xml.in \
org.gnome.Shell.PortalHelper.desktop.in.in \ org.gnome.Shell.PortalHelper.desktop.in.in \
org.gnome.Shell.PortalHelper.service.in \ org.gnome.Shell.PortalHelper.service.in \

View File

@ -22,6 +22,7 @@
<file>no-events.svg</file> <file>no-events.svg</file>
<file>no-notifications.svg</file> <file>no-notifications.svg</file>
<file>noise-texture.png</file> <file>noise-texture.png</file>
<file>pad-osd.css</file>
<file>page-indicator-active.svg</file> <file>page-indicator-active.svg</file>
<file>page-indicator-inactive.svg</file> <file>page-indicator-inactive.svg</file>
<file>page-indicator-checked.svg</file> <file>page-indicator-checked.svg</file>

View File

@ -0,0 +1,28 @@
<!DOCTYPE node PUBLIC
'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
<node>
<!--
org.gnome.Shell.PadOSD:
@short_description: Pad OSD interface
The interface used to show button map OSD on pad devices.
-->
<interface name='org.gnome.Shell.Wacom.PadOsd'>
<!--
Show:
@device_node: device node file, usually in /dev/input/...
@edition_mode: whether toggling edition mode on when showing
Shows the pad button map OSD for the requested device, the OSD
will be shown according the current device settings (output
mapping, left handed mode, ...)
-->
<method name='Show'>
<arg name='device_node' direction='in' type='o'/>
<arg name='edition_mode' direction='in' type='b'/>
</method>
</interface>
</node>

View File

@ -562,6 +562,16 @@ StScrollBar {
background-color: #eeeeec; background-color: #eeeeec;
border-radius: 0.3em; } border-radius: 0.3em; }
/* Pad OSD */
.pad-osd-window {
padding: 32px;
background-color: rgba(0, 0, 0, 0.8);
}
.combo-box-label {
width: 15em;
}
/* App Switcher */ /* App Switcher */
.switcher-popup { .switcher-popup {
padding: 8px; padding: 8px;

30
data/theme/pad-osd.css Normal file
View File

@ -0,0 +1,30 @@
.Leader {
stroke-width: .5 !important;
stroke: #535353;
fill: none !important;
}
.Button {
stroke-width: .25;
stroke: #ededed;
fill: #ededed;
}
.Ring {
stroke-width: .5 !important;
stroke: #535353 !important;
fill: none !important;
}
.Label {
stroke: none !important;
stroke-width: .1 !important;
font-size: .1 !important;
fill: transparent !important;
}
.TouchStrip, .TouchRing {
stroke-width: .1 !important;
stroke: #ededed !important;
fill: #535353 !important;
}

View File

@ -72,6 +72,7 @@
<file>ui/osdMonitorLabeler.js</file> <file>ui/osdMonitorLabeler.js</file>
<file>ui/overview.js</file> <file>ui/overview.js</file>
<file>ui/overviewControls.js</file> <file>ui/overviewControls.js</file>
<file>ui/padOsd.js</file>
<file>ui/panel.js</file> <file>ui/panel.js</file>
<file>ui/panelMenu.js</file> <file>ui/panelMenu.js</file>
<file>ui/pointerWatcher.js</file> <file>ui/pointerWatcher.js</file>

View File

@ -26,6 +26,7 @@ const ModalDialog = imports.ui.modalDialog;
const OsdWindow = imports.ui.osdWindow; const OsdWindow = imports.ui.osdWindow;
const OsdMonitorLabeler = imports.ui.osdMonitorLabeler; const OsdMonitorLabeler = imports.ui.osdMonitorLabeler;
const Overview = imports.ui.overview; const Overview = imports.ui.overview;
const PadOsd = imports.ui.padOsd;
const Panel = imports.ui.panel; const Panel = imports.ui.panel;
const Params = imports.misc.params; const Params = imports.misc.params;
const RunDialog = imports.ui.runDialog; const RunDialog = imports.ui.runDialog;
@ -61,6 +62,7 @@ let screenShield = null;
let notificationDaemon = null; let notificationDaemon = null;
let windowAttentionHandler = null; let windowAttentionHandler = null;
let ctrlAltTabManager = null; let ctrlAltTabManager = null;
let padOsdService = null;
let osdWindowManager = null; let osdWindowManager = null;
let osdMonitorLabeler = null; let osdMonitorLabeler = null;
let sessionMode = null; let sessionMode = null;
@ -155,6 +157,7 @@ function _initializeUI() {
// working until it's updated. // working until it's updated.
uiGroup = layoutManager.uiGroup; uiGroup = layoutManager.uiGroup;
padOsdService = new PadOsd.PadOsdService();
screencastService = new Screencast.ScreencastService(); screencastService = new Screencast.ScreencastService();
xdndHandler = new XdndHandler.XdndHandler(); xdndHandler = new XdndHandler.XdndHandler();
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager(); ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();

761
js/ui/padOsd.js Normal file
View File

@ -0,0 +1,761 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const Clutter = imports.gi.Clutter;
const St = imports.gi.St;
const Rsvg = imports.gi.Rsvg;
const GObject = imports.gi.GObject;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Gio = imports.gi.Gio;
const GDesktopEnums = imports.gi.GDesktopEnums;
const Atk = imports.gi.Atk;
const Cairo = imports.cairo;
const Signals = imports.signals;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const Layout = imports.ui.layout;
const ACTIVE_COLOR = "#729fcf";
const LTR = 0;
const RTL = 1;
const CW = 0;
const CCW = 1;
const UP = 0;
const DOWN = 1;
const KeybindingEntry = new Lang.Class({
Name: 'KeybindingEntry',
_init: function () {
this.actor = new St.Entry({ hint_text: _("New shortcut…"),
style: 'width: 10em' });
this.actor.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
},
_onCapturedEvent: function (actor, 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.actor.set_text(str);
this.emit('keybinding-edited', str);
return Clutter.EVENT_STOP;
}
});
Signals.addSignalMethods(KeybindingEntry.prototype);
const ActionComboBox = new Lang.Class({
Name: 'ActionComboBox',
_init: function () {
this.actor = new St.Button({ style_class: 'button' });
this.actor.connect('clicked', Lang.bind(this, this._onButtonClicked));
this.actor.set_toggle_mode(true);
let boxLayout = new Clutter.BoxLayout({ orientation: Clutter.Orientation.HORIZONTAL,
spacing: 6 });
let box = new St.Widget({ layout_manager: boxLayout });
this.actor.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.actor, 0, St.Side.TOP);
this._editMenu.connect('menu-closed', Lang.bind(this, function() { this.actor.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"));
for (let [action, label] of this._actionLabels.entries()) {
let selectedAction = action;
this._editMenu.addAction(label, Lang.bind(this, function() { this._onActionSelected(selectedAction) }));
}
this.setAction(GDesktopEnums.PadButtonAction.NONE);
},
_onActionSelected: function (action) {
this.setAction(action);
this.popdown();
this.emit('action-selected', action);
},
setAction: function (action) {
this._label.set_text(this._actionLabels.get(action));
},
popup: function () {
this._editMenu.open(true);
},
popdown: function () {
this._editMenu.close(true);
},
_onButtonClicked: function () {
if (this.actor.get_checked())
this.popup();
else
this.popdown();
}
});
Signals.addSignalMethods(ActionComboBox.prototype);
const ActionEditor = new Lang.Class({
Name: 'ActionEditor',
_init: function () {
let boxLayout = new Clutter.BoxLayout({ orientation: Clutter.Orientation.HORIZONTAL,
spacing: 12 });
this.actor = new St.Widget({ layout_manager: boxLayout });
this._actionComboBox = new ActionComboBox();
this._actionComboBox.connect('action-selected', Lang.bind(this, this._onActionSelected));
this.actor.add_actor(this._actionComboBox.actor);
this._keybindingEdit = new KeybindingEntry();
this._keybindingEdit.connect('keybinding-edited', Lang.bind(this, this._onKeybindingEdited));
this.actor.add_actor(this._keybindingEdit.actor);
this._doneButton = new St.Button({ label: _("Done"),
style_class: 'button',
x_expand: false});
this._doneButton.connect('clicked', Lang.bind(this, this._onEditingDone));
this.actor.add_actor(this._doneButton);
},
_updateKeybindingEntryState: function () {
if (this._currentAction == GDesktopEnums.PadButtonAction.KEYBINDING) {
this._keybindingEdit.actor.set_text(this._currentKeybinding);
this._keybindingEdit.actor.show();
this._keybindingEdit.actor.grab_key_focus();
} else {
this._keybindingEdit.actor.hide();
}
},
setSettings: function (settings) {
this._buttonSettings = settings;
this._currentAction = this._buttonSettings.get_enum('action');
this._currentKeybinding = this._buttonSettings.get_string('keybinding');
this._actionComboBox.setAction(this._currentAction);
this._updateKeybindingEntryState();
},
close: function() {
this._actionComboBox.popdown();
this.actor.hide();
},
_onKeybindingEdited: function (entry, keybinding) {
this._currentKeybinding = keybinding;
},
_onActionSelected: function (menu, action) {
this._currentAction = action;
this._updateKeybindingEntryState();
},
_storeSettings: function () {
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: function () {
this._storeSettings();
this.close();
this.emit('done');
}
});
Signals.addSignalMethods(ActionEditor.prototype);
const PadDiagram = new Lang.Class({
Name: 'PadDiagram',
Extends: St.DrawingArea,
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) },
_init: function (params) {
let file = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/pad-osd.css');
let [success, css, etag] = file.load_contents(null);
this._css = css;
this._labels = [];
this._activeButtons = [];
this.parent(params);
},
get left_handed() {
return this._leftHanded;
},
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();
},
get editor_actor() {
return this._editorActor;
},
set editor_actor(actor) {
actor.hide();
this._editorActor = actor;
this.add_actor(actor);
},
_wrappingSvgHeader: function () {
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="' + this._imageWidth + '" height="' + this._imageHeight + '"> ' +
'<style type="text/css">');
},
_wrappingSvgFooter: function () {
return ('</style>' +
'<xi:include href="' + this._imagePath + '" />' +
'</svg>');
},
_cssString: function () {
let css = this._css;
for (let i = 0; i < this._activeButtons.length; i++) {
let ch = String.fromCharCode('A'.charCodeAt() + this._activeButtons[i]);
css += ('.' + ch + ' { ' +
' stroke: ' + ACTIVE_COLOR + ' !important; ' +
' fill: ' + ACTIVE_COLOR + ' !important; ' +
'} ');
}
return css;
},
_composeStyledDiagram: function () {
let svgData = '';
if (!GLib.file_test(this._imagePath, GLib.FileTest.EXISTS))
return null;
svgData += this._wrappingSvgHeader();
svgData += this._cssString();
svgData += this._wrappingSvgFooter();
let handle = new Rsvg.Handle();
handle.set_base_uri(GLib.path_get_dirname(this._imagePath));
handle.write(svgData);
handle.close();
return handle;
},
_updateDiagramScale: function () {
if (this._handle == null)
return;
[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: function (child, x, y, direction) {
let [prefHeight, natHeight] = child.get_preferred_height(-1);
let [prefWidth, natWidth] = child.get_preferred_width(natHeight);
let childBox = new Clutter.ActorBox();
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: function (box, flags) {
this.parent(box, flags);
this._updateDiagramScale();
for (let i = 0; i < this._labels.length; i++) {
let [label, action, idx, dir] = this._labels[i];
let [found, x, y, arrangement] = this.getLabelCoords(action, idx, dir);
this._allocateChild(label, x, y, arrangement);
}
if (this._editorActor && this._curEdited) {
let [label, action, idx, dir] = this._curEdited;
let [found, x, y, arrangement] = this.getLabelCoords(action, idx, dir);
this._allocateChild(this._editorActor, x, y, arrangement);
}
},
vfunc_repaint: function () {
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();
},
_transformPoint: function (x, y) {
if (this._handle == null || this._scale == null)
return [x, y];
// 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;;
return [Math.round(x), Math.round(y)];
},
_getItemLabelCoords: function (labelName, leaderName) {
if (this._handle == null)
return [false];
let leaderPos, leaderSize, pos;
let found, direction;
[found, pos] = this._handle.get_position_sub('#' + labelName);
if (!found)
return [false];
[found, leaderPos] = this._handle.get_position_sub('#' + leaderName);
[found, leaderSize] = this._handle.get_dimensions_sub('#' + 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;
}
let [x, y] = this._transformPoint(pos.x, pos.y)
return [true, x, y, direction];
},
getButtonLabelCoords: function (button) {
let ch = String.fromCharCode('A'.charCodeAt() + button);
let labelName = 'Label' + ch;
let leaderName = 'Leader' + ch;
return this._getItemLabelCoords(labelName, leaderName);
},
getRingLabelCoords: function (number, dir) {
let numStr = number > 0 ? number.toString() : '';
let dirStr = dir == CW ? 'CW' : 'CCW';
let labelName = 'LabelRing' + numStr + dirStr;
let leaderName = 'LeaderRing' + numStr + dirStr;
return this._getItemLabelCoords(labelName, leaderName);
},
getStripLabelCoords: function (number, dir) {
let numStr = number > 0 ? (number + 1).toString() : '';
let dirStr = dir == UP ? 'Up' : 'Down';
let labelName = 'LabelStrip' + numStr + dirStr;
let leaderName = 'LeaderStrip' + numStr + dirStr;
return this._getItemLabelCoords(labelName, leaderName);
},
getLabelCoords: function (action, idx, dir) {
if (action == Meta.PadActionType.BUTTON)
return this.getButtonLabelCoords(idx);
else if (action == Meta.PadActionType.RING)
return this.getRingLabelCoords(idx, dir);
else if (action == Meta.PadActionType.STRIP)
return this.getStripLabelCoords(idx, dir);
return [false];
},
_invalidateSvg: function () {
if (this._handle == null)
return;
this._handle = this._composeStyledDiagram();
this.queue_repaint();
},
activateButton: function (button) {
this._activeButtons.push(button);
this._invalidateSvg();
},
deactivateButton: function (button) {
for (let i = 0; i < this._activeButtons.length; i++) {
if (this._activeButtons[i] == button)
this._activeButtons.splice(i, 1);
}
this._invalidateSvg();
},
addLabel: function (label, type, idx, dir) {
this._labels.push([label, type, idx, dir]);
this.add_actor(label);
},
stopEdition: function (str) {
this._editorActor.hide();
if (this._curEdited) {
let [label, action, idx, dir] = this._curEdited;
if (str != null) {
label.set_text(str);
let [found, x, y, arrangement] = this.getLabelCoords(action, idx, dir);
this._allocateChild(label, x, y, arrangement);
}
label.show();
this._curEdited = null;
}
},
startEdition: function(action, idx, dir) {
let editedLabel;
this.stopEdition();
for (let i = 0; i < this._labels.length; i++) {
let [label, itemAction, itemIdx, itemDir] = this._labels[i];
if (action == itemAction && idx == itemIdx && dir == itemDir) {
this._curEdited = this._labels[i];
editedLabel = label;
break;
}
}
if (this._curEdited == null)
return;
let [found] = this.getLabelCoords(action, idx, dir);
if (!found)
return;
this._editorActor.show();
editedLabel.hide();
}
});
const PadOsd = new Lang.Class({
Name: 'PadOsd',
_init: function (padDevice, settings, imagePath, editionMode, monitorIndex) {
this.padDevice = padDevice;
this._settings = settings;
this._imagePath = imagePath;
this._editionMode = editionMode;
this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
let deviceManager = Clutter.DeviceManager.get_default();
this._deviceRemovedId = deviceManager.connect('device-removed', Lang.bind(this, function (manager, device) {
// If the device is being removed, destroy the padOsd.
if (device == this.padDevice)
this.destroy();
}));
this.actor = new St.BoxLayout({ style_class: 'pad-osd-window',
x_expand: true,
y_expand: true,
vertical: true,
reactive: true });
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
Main.uiGroup.add_actor(this.actor);
this._monitorIndex = monitorIndex;
let constraint = new Layout.MonitorConstraint({ index: monitorIndex });
this.actor.add_constraint(constraint);
this._titleLabel = new St.Label({ style: 'font-side: larger; font-weight: bold;',
x_align: Clutter.ActorAlign.CENTER });
this._titleLabel.clutter_text.set_text(padDevice.get_device_name());
this.actor.add_actor(this._titleLabel);
this._tipLabel = new St.Label({ x_align: Clutter.ActorAlign.CENTER });
this.actor.add_actor(this._tipLabel);
this._actionEditor = new ActionEditor();
this._actionEditor.connect('done', Lang.bind(this, this._endButtonActionEdition));
this._padDiagram = new PadDiagram({ image: this._imagePath,
left_handed: settings.get_boolean('left-handed'),
editor_actor: this._actionEditor.actor,
x_expand: true,
y_expand: true });
this.actor.add_actor(this._padDiagram);
// FIXME: Fix num buttons.
let i = 0;
for (i = 0; i < 50; i++) {
let [found] = this._padDiagram.getButtonLabelCoords(i);
if (!found)
break;
this._createLabel(Meta.PadActionType.BUTTON, i);
}
for (i = 0; i < padDevice.get_n_rings(); i++) {
this._createLabel(Meta.PadActionType.RING, i, CW);
this._createLabel(Meta.PadActionType.RING, i, CCW);
}
for (i = 0; i < padDevice.get_n_strips(); i++) {
this._createLabel(Meta.PadActionType.STRIP, i, UP);
this._createLabel(Meta.PadActionType.STRIP, i, DOWN);
}
let buttonBox = new St.Widget({ layout_manager: new Clutter.BinLayout(),
x_expand: true,
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER });
this.actor.add_actor(buttonBox);
this._editButton = new St.Button({ label: _("Edit…"),
style_class: 'button',
x_align: Clutter.ActorAlign.CENTER,
can_focus: true });
this._editButton.connect('clicked', Lang.bind(this, function () { this.setEditionMode(true) }));
buttonBox.add_actor(this._editButton);
this._syncEditionMode();
Main.pushModal(this.actor);
},
_createLabel: function (type, number, dir) {
let str = global.display.get_pad_action_label(this.padDevice, type, number);
let label = new St.Label({ text: str ? str : _("None") });
this._padDiagram.addLabel(label, type, number, dir);
},
_onCapturedEvent : function (actor, event) {
if (event.type() == Clutter.EventType.PAD_BUTTON_PRESS &&
event.get_source_device() == this.padDevice) {
this._padDiagram.activateButton(event.get_button());
if (this._editionMode)
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());
return Clutter.EVENT_STOP;
} else if (event.type() == Clutter.EventType.KEY_PRESS &&
(!this._editionMode || event.get_key_symbol() == Clutter.Escape)) {
if (this._editingButtonAction != null)
this._endButtonActionEdition();
else
this.destroy();
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
},
_syncEditionMode: function () {
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.clutter_text.set_markup('<span size="larger"><b>' + title + '</b></span>');
},
_endButtonActionEdition: function () {
this._actionEditor.close();
if (this._editingButtonAction != null) {
let str = global.display.get_pad_action_label(this.padDevice,
Meta.PadActionType.BUTTON,
this._editingButtonAction);
this._padDiagram.stopEdition(str ? str : _("None"))
this._editingButtonAction = null;
}
this._editedButtonSettings = null;
},
_startButtonActionEdition: function (button) {
if (this._editingButtonAction == button)
return;
this._endButtonActionEdition();
this._editingButtonAction = button;
let ch = String.fromCharCode('A'.charCodeAt() + button);
let settingsPath = this._settings.path + "button" + ch + '/';
this._editedButtonSettings = Gio.Settings.new_with_path('org.gnome.desktop.peripherals.tablet.pad-button',
settingsPath);
this._actionEditor.setSettings(this._editedButtonSettings);
this._padDiagram.startEdition(Meta.PadActionType.BUTTON, button);
},
setEditionMode: function (editionMode) {
if (this._editionMode == editionMode)
return;
this._editionMode = editionMode;
this._syncEditionMode();
},
destroy: function () {
this.actor.destroy();
},
_onDestroy: function () {
Main.popModal(this.actor);
this._actionEditor.close();
if (this._deviceRemovedId != 0) {
let deviceManager = Clutter.DeviceManager.get_default();
deviceManager.disconnect(this._deviceRemovedId);
this._deviceRemovedId = 0;
}
if (this._capturedEventId != 0) {
global.stage.disconnect(this._capturedEventId);
this._capturedEventId = 0;
}
this.actor = null;
this.emit('closed');
}
});
Signals.addSignalMethods(PadOsd.prototype);
const PadOsdIface = '<node> \
<interface name="org.gnome.Shell.Wacom.PadOsd"> \
<method name="Show"> \
<arg name="device_node" direction="in" type="o"/> \
<arg name="edition_mode" direction="in" type="b"/> \
</method> \
</interface> \
</node>';
const PadOsdService = new Lang.Class({
Name: 'PadOsdService',
_init: function() {
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: function(params, invocation) {
let [deviceNode, editionMode] = params;
let deviceManager = Clutter.DeviceManager.get_default();
let devices = deviceManager.list_devices();
let padDevice = null;
devices.forEach(Lang.bind(this, function(device) {
if (deviceNode == device.get_device_node())
padDevice = device;
}));
if (padDevice == null ||
padDevice.get_device_type() != Clutter.InputDeviceType.PAD_DEVICE) {
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);

View File

@ -17,6 +17,7 @@ const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog; const ModalDialog = imports.ui.modalDialog;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const WindowMenu = imports.ui.windowMenu; const WindowMenu = imports.ui.windowMenu;
const PadOsd = imports.ui.padOsd;
const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings'; const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
const MINIMIZE_WINDOW_ANIMATION_TIME = 0.2; const MINIMIZE_WINDOW_ANIMATION_TIME = 0.2;
@ -917,6 +918,7 @@ const WindowManager = new Lang.Class({
Lang.bind(this, this._toggleCalendar)); Lang.bind(this, this._toggleCalendar));
global.display.connect('show-resize-popup', Lang.bind(this, this._showResizePopup)); global.display.connect('show-resize-popup', Lang.bind(this, this._showResizePopup));
global.display.connect('show-pad-osd', Lang.bind(this, this._showPadOsd));
Main.overview.connect('showing', Lang.bind(this, function() { Main.overview.connect('showing', Lang.bind(this, function() {
for (let i = 0; i < this._dimmedWindows.length; i++) for (let i = 0; i < this._dimmedWindows.length; i++)
@ -946,7 +948,13 @@ const WindowManager = new Lang.Class({
gesture = new AppSwitchAction(); gesture = new AppSwitchAction();
gesture.connect('activated', Lang.bind(this, this._switchApp)); gesture.connect('activated', Lang.bind(this, this._switchApp));
global.stage.add_action(gesture); global.stage.add_action(gesture);
},
_showPadOsd: function (display, device, settings, imagePath, editionMode, monitorIndex) {
this._currentPadOsd = new PadOsd.PadOsd(device, settings, imagePath, editionMode, monitorIndex);
this._currentPadOsd.connect('closed', Lang.bind(this, function() { this._currentPadOsd = null }));
return this._currentPadOsd.actor;
}, },
_actionSwitchWorkspace: function(action, direction) { _actionSwitchWorkspace: function(action, direction) {

View File

@ -39,6 +39,7 @@ js/ui/mpris.js
js/ui/notificationDaemon.js js/ui/notificationDaemon.js
js/ui/overviewControls.js js/ui/overviewControls.js
js/ui/overview.js js/ui/overview.js
js/ui/padOsd.js
js/ui/panel.js js/ui/panel.js
js/ui/popupMenu.js js/ui/popupMenu.js
js/ui/runDialog.js js/ui/runDialog.js