diff --git a/data/Makefile.am b/data/Makefile.am index f681ab424..7c62aad2a 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -35,6 +35,7 @@ endif introspectiondir = $(datadir)/dbus-1/interfaces introspection_DATA = \ + org.gnome.Shell.PadOsd.xml \ org.gnome.Shell.Screencast.xml \ org.gnome.Shell.Screenshot.xml \ org.gnome.ShellSearchProvider.xml \ @@ -114,6 +115,7 @@ EXTRA_DIST = \ $(convert_DATA) \ $(keys_in_files) \ $(dist_theme_files) \ + pad-osd.css \ perf-background.xml.in \ org.gnome.Shell.PortalHelper.desktop.in \ org.gnome.Shell.PortalHelper.service.in \ diff --git a/data/gnome-shell-theme.gresource.xml b/data/gnome-shell-theme.gresource.xml index 5c8ca6e69..76aeaa579 100644 --- a/data/gnome-shell-theme.gresource.xml +++ b/data/gnome-shell-theme.gresource.xml @@ -22,6 +22,7 @@ no-events.svg no-notifications.svg noise-texture.png + pad-osd.css page-indicator-active.svg page-indicator-inactive.svg page-indicator-checked.svg diff --git a/data/org.gnome.Shell.PadOsd.xml b/data/org.gnome.Shell.PadOsd.xml new file mode 100644 index 000000000..765973c98 --- /dev/null +++ b/data/org.gnome.Shell.PadOsd.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/data/theme/pad-osd.css b/data/theme/pad-osd.css new file mode 100644 index 000000000..31c237722 --- /dev/null +++ b/data/theme/pad-osd.css @@ -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; +} diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index 54f702b4a..58f433c3c 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -72,6 +72,7 @@ ui/osdMonitorLabeler.js ui/overview.js ui/overviewControls.js + ui/padOsd.js ui/panel.js ui/panelMenu.js ui/pointerWatcher.js diff --git a/js/ui/main.js b/js/ui/main.js index bd42960f3..b19c3b2d1 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -26,6 +26,7 @@ const ModalDialog = imports.ui.modalDialog; const OsdWindow = imports.ui.osdWindow; const OsdMonitorLabeler = imports.ui.osdMonitorLabeler; const Overview = imports.ui.overview; +const PadOsd = imports.ui.padOsd; const Panel = imports.ui.panel; const Params = imports.misc.params; const RunDialog = imports.ui.runDialog; @@ -61,6 +62,7 @@ let screenShield = null; let notificationDaemon = null; let windowAttentionHandler = null; let ctrlAltTabManager = null; +let padOsdService = null; let osdWindowManager = null; let osdMonitorLabeler = null; let sessionMode = null; @@ -155,6 +157,7 @@ function _initializeUI() { // working until it's updated. uiGroup = layoutManager.uiGroup; + padOsdService = new PadOsd.PadOsdService(); screencastService = new Screencast.ScreencastService(); xdndHandler = new XdndHandler.XdndHandler(); ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager(); diff --git a/js/ui/padOsd.js b/js/ui/padOsd.js new file mode 100644 index 000000000..3ab3be2b4 --- /dev/null +++ b/js/ui/padOsd.js @@ -0,0 +1,733 @@ +// -*- 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 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...'), + width: 120 }); + this.actor.connect('captured-event', Lang.bind(this, this._onCapturedEvent)); + this.actor.connect('destroy', Lang.bind(this, this.destroy)); + }, + + _onCapturedEvent: function (actor, event) { + if (event.type() == Clutter.EventType.KEY_PRESS) { + if (GLib.unichar_isprint(event.get_key_unicode())) { + 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', str); + } + return Clutter.EVENT_STOP; + } + + return Clutter.EVENT_PROPAGATE; + }, + + destroy: function () { + this.actor.destroy(); + } +}); +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({ width: 150 }); + 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); + + /* Order matches GDesktopPadButtonAction enum */ + this._actions = [_('Application defined'), + _('Show on-screen help'), + _('Switch monitor'), + _('Assign keystroke')]; + + 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); + + for (let i = 0; i < this._actions.length; i++) { + let str = this._actions[i]; + let action = i; + this._editMenu.addAction(str, Lang.bind(this, function() { this._onActionSelected(action) })); + } + + this.setAction(GDesktopEnums.PadButtonAction.NONE); + }, + + _onActionSelected: function (action) { + this.setAction(action); + this.popdown(); + this.emit('action', action); + }, + + setAction: function (action) { + this._label.set_text(this._actions[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', Lang.bind(this, this._onActionSelected)); + this.actor.add_actor(this._actionComboBox.actor); + + this._keybindingEdit = new KeybindingEntry(); + this._keybindingEdit.connect('keybinding', Lang.bind(this, this._onKeybindingEdited)); + this._keybindingEdit.actor.hide(); + this.actor.add_actor(this._keybindingEdit.actor); + + this._doneButton = new St.Button ({ label: _('Done'), + width: 100, + style_class: 'button'}); + this._doneButton.connect('clicked', Lang.bind(this, this._onEditingDone)); + this.actor.add_actor(this._doneButton); + }, + + 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); + + if (this._currentAction == GDesktopEnums.PadButtonAction.KEYBINDING) { + this._keybindingEdit.actor.set_text(this._currentKeybinding); + this._keybindingEdit.actor.show(); + } else { + this._keybindingEdit.actor.hide(); + } + }, + + close: function() { + this._actionComboBox.popdown(); + this.actor.hide(); + }, + + _onKeybindingEdited: function (entry, keybinding) { + this._currentKeybinding = keybinding; + }, + + _onActionSelected: function (menu, action) { + this._currentAction = action; + + if (action == GDesktopEnums.PadButtonAction.KEYBINDING) { + this._keybindingEdit.actor.show(); + this._keybindingEdit.actor.grab_key_focus(); + } else { + this._keybindingEdit.actor.hide(); + } + }, + + _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, + + _init: function (imagePath, leftHanded) { + this.parent(); + + 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; + + let originalHandle = Rsvg.Handle.new_from_file(imagePath); + let dimensions = originalHandle.get_dimensions(); + this._imageWidth = dimensions.width; + this._imageHeight = dimensions.height; + + this._activeButtons = []; + this._imagePath = imagePath; + this._handle = this._composeStyledDiagram(); + this.connect('repaint', Lang.bind(this, this._repaint)); + this.connect('notify::size', Lang.bind(this, this._updateScale)); + this._leftHanded = leftHanded; + }, + + _wrappingSvgHeader: function () { + return ('' + + ' ' + + '' + + '' + + ''); + }, + + _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; + }, + + _updateScale: function () { + let [width, height] = this.get_size(); + let dimensions = this._handle.get_dimensions (); + let scaleX = width / dimensions.width; + let scaleY = height / dimensions.height; + this._scale = Math.min(scaleX, scaleY); + }, + + _repaint: function (area) { + if (this._handle == null) + return; + + let [width, height] = area.get_surface_size(); + let dimensions = this._handle.get_dimensions (); + let cr = this.get_context(); + + if (this._scale == null) + this._updateScale(); + + 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 [width, height] = this.get_size(); + let dimensions = this._handle.get_dimensions (); + x = x * this._scale + width / 2 - dimensions.width / 2 * this._scale; + y = y * this._scale + height / 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); + }, + + _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 (); + } +}); + +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)); + + this.actor = new Shell.GenericContainer({ style_class: 'pad-osd-window', + reactive: true, + x: 0, + y: 0, + width: global.screen_width, + height: global.screen_height }); + this.actor.connect('allocate', Lang.bind(this, this._allocate)); + this.actor.connect('destroy', Lang.bind(this, this.destroy)); + Main.uiGroup.add_actor(this.actor); + + this._monitorIndex = monitorIndex; + let constraint = new Layout.MonitorConstraint({ index: monitorIndex }); + this.actor.add_constraint(constraint); + + this._padDiagram = new PadDiagram(this._imagePath, settings.get_boolean('left-handed')); + this.actor.add_actor(this._padDiagram); + + this._buttonBox = new St.Widget({ layout_manager: new Clutter.BinLayout(), + x_expand: true, + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER }); + this._editButton = new St.Button({ label: _('Edit...'), + style_class: 'button', + can_focus: true, + x_expand: true }); + this._editButton.connect('clicked', Lang.bind(this, function () { this.setEditionMode(true) })); + this._buttonBox.add_actor(this._editButton); + this.actor.add_actor(this._buttonBox); + + let boxLayout = new Clutter.BoxLayout({ orientation: Clutter.Orientation.VERTICAL }); + this._labelBox = new St.Widget({ layout_manager: boxLayout, + x_expand: true, + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER }); + this._titleLabel = new St.Label(); + this._titleLabel.clutter_text.set_markup('' + padDevice.get_device_name() + ''); + this._labelBox.add_actor(this._titleLabel); + + this._tipLabel = new St.Label(); + this._labelBox.add_actor(this._tipLabel); + this.actor.add_actor(this._labelBox); + + this._actionEditor = new ActionEditor(); + this._actionEditor.connect ('done', Lang.bind(this, this._endButtonActionEdition)); + this.actor.add_actor(this._actionEditor.actor); + + this._labels = []; + this._ringLabels = []; + this._stripLabels = []; + + // FIXME: Fix num buttons. + let i = 0; + for (i = 0; i < 50; i++) { + let [found, x, y, direction] = this._padDiagram.getButtonLabelCoords(i); + if (!found) + break; + let label = this._createLabel(i, Meta.PadActionType.BUTTON); + this._labels.push(label); + } + + for (i = 0; i < padDevice.get_n_rings(); i++) { + let [found, x, y, direction] = this._padDiagram.getRingLabelCoords(i, CW); + let [found2, x2, y2, direction2] = this._padDiagram.getRingLabelCoords(i, CCW); + if (!found || !found2) + break; + + let label1 = this._createLabel(i, Meta.PadActionType.RING); + let label2 = this._createLabel(i, Meta.PadActionType.RING); + this._ringLabels.push([label1, label2]); + } + + for (i = 0; i < padDevice.get_n_strips(); i++) { + let [found, x, y, direction] = this._padDiagram.getStripLabelCoords(i, UP); + let [found2, x2, y2, direction2] = this._padDiagram.getStripLabelCoords(i, DOWN); + if (!found || !found2) + break; + + let label1 = this._createLabel(i, Meta.PadActionType.STRIP); + let label2 = this._createLabel(i, Meta.PadActionType.STRIP); + this._stripLabels.push([label1, label2]); + } + + this._syncEditionMode(); + }, + + _createLabel: function (number, type) { + let str = global.display.get_pad_action_label(this.padDevice, type, number); + let label = new St.Label({ text: str ? str : _('None') }); + this.actor.add_actor(label); + + return label; + }, + + _allocateChild: function (child, x, y, direction, box) { + let [prefHeight, natHeight] = child.get_preferred_height (-1); + let [prefWidth, natWidth] = child.get_preferred_width (natHeight); + let childBox = new Clutter.ActorBox(); + + natWidth = Math.min(natWidth, 250); + + if (direction == LTR) { + childBox.x1 = x + box.x1; + childBox.x2 = x + box.x1 + natWidth; + } else { + childBox.x1 = x + box.x1 - natWidth; + childBox.x2 = x + box.x1; + } + + childBox.y1 = y + box.y1 - natHeight / 2; + childBox.y2 = y + box.y1 + natHeight / 2; + child.allocate(childBox, 0); + }, + + _allocate: function (actor, box, flags) { + let [prefLabelHeight, natLabelHeight] = this._labelBox.get_preferred_height(box.x2 - box.x1); + let buttonY = Math.max((box.y2 - box.y1) * 3 / 4 + box.y1, (box.y2 - box.y1) - 100); + let childBox = new Clutter.ActorBox(); + let diagramBox = new Clutter.ActorBox(); + + diagramBox.x1 = box.x1; + diagramBox.x2 = box.x2; + diagramBox.y1 = prefLabelHeight; + diagramBox.y2 = buttonY; + this._padDiagram.allocate(diagramBox, flags); + + childBox.x1 = box.x1; + childBox.x2 = box.x2; + childBox.y1 = buttonY; + childBox.y2 = box.y2; + this._buttonBox.allocate(childBox, flags); + + childBox.y1 = 0; + childBox.y2 = prefLabelHeight; + this._labelBox.allocate(childBox, flags); + + for (let i = 0; i < this._labels.length; i++) { + let label = this._labels[i]; + let [found, x, y, direction] = this._padDiagram.getButtonLabelCoords(i); + this._allocateChild(label, x, y, direction, diagramBox); + } + + for (let i = 0; i < this._ringLabels.length; i++) { + let [label1, label2] = this._ringLabels[i]; + + let [found, x, y, direction] = this._padDiagram.getRingLabelCoords(i, CW); + this._allocateChild(label1, x, y, direction, diagramBox); + + [found, x, y, direction] = this._padDiagram.getRingLabelCoords(i, CCW); + this._allocateChild(label2, x, y, direction, diagramBox); + } + + for (let i = 0; i < this._stripLabels.length; i++) { + let [label1, label2] = this._stripLabels[i]; + + let [found, x, y, direction] = this._padDiagram.getStripLabelCoords(i, UP); + this._allocateChild(label1, x, y, direction, diagramBox); + + [found, x, y, direction] = this._padDiagram.getStripLabelCoords(i, DOWN); + this._allocateChild(label2, x, y, direction, diagramBox); + } + + if (this._editingButtonAction != null) { + let [found, x, y, direction] = this._padDiagram.getButtonLabelCoords(this._editingButtonAction); + this._allocateChild(this._actionEditor.actor, x, y, direction, diagramBox); + } + }, + + _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('' + title + ''); + }, + + _endButtonActionEdition: function () { + this._actionEditor.close(); + + if (this._editingButtonAction != null) { + // Update and show the label + let str = global.display.get_pad_action_label(this.padDevice, + Meta.PadActionType.BUTTON, + this._editingButtonAction); + this._labels[this._editingButtonAction].set_text(str ? str : _('None')); + + this._labels[this._editingButtonAction].show(); + this._editingButtonAction = null; + } + + this._editedButtonSettings = null; + }, + + _startButtonActionEdition: function (button) { + if (this._editingButtonAction == button) + return; + + this._endButtonActionEdition(); + this._editingButtonAction = button; + + this._labels[this._editingButtonAction].hide(); + this._actionEditor.actor.show(); + this.actor.queue_relayout(); + + 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); + }, + + setEditionMode: function (editionMode) { + if (this._editionMode == editionMode) + return; + + this._editionMode = editionMode; + this._syncEditionMode(); + }, + + destroy: function () { + this._actionEditor.close(); + + if (this._capturedEventId != 0) { + global.stage.disconnect(this._capturedEventId); + this._capturedEventId = 0; + } + + if (this.actor) { + let actor = this.actor; + this.actor = null; + actor.destroy(); + this.emit('closed'); + } + } +}); +Signals.addSignalMethods(PadOsd.prototype); + +const PadOsdIface = ' \ + \ + \ + \ + \ + \ + \ +'; + +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); diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js index 51ce482c4..21de603d2 100644 --- a/js/ui/windowManager.js +++ b/js/ui/windowManager.js @@ -17,6 +17,7 @@ const Main = imports.ui.main; const ModalDialog = imports.ui.modalDialog; const Tweener = imports.ui.tweener; const WindowMenu = imports.ui.windowMenu; +const PadOsd = imports.ui.padOsd; const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings'; const MINIMIZE_WINDOW_ANIMATION_TIME = 0.2; @@ -915,6 +916,7 @@ const WindowManager = new Lang.Class({ Lang.bind(this, this._toggleCalendar)); 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() { for (let i = 0; i < this._dimmedWindows.length; i++) @@ -944,7 +946,19 @@ const WindowManager = new Lang.Class({ gesture = new AppSwitchAction(); gesture.connect('activated', Lang.bind(this, this._switchApp)); global.stage.add_action(gesture); + }, + _showPadOsd: function (display, device, settings, imagePath, editionMode, monitorIndex) { + if (this._currentPadOsd != null) { + if (this._currentPadOsd.padDevice == device) + this._currentPadOsd.destroy(); + return null; + } + + 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) {