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:
parent
d042dd73aa
commit
e006b9b400
@ -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 \
|
||||||
|
@ -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>
|
||||||
|
28
data/org.gnome.Shell.PadOsd.xml
Normal file
28
data/org.gnome.Shell.PadOsd.xml
Normal 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>
|
@ -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
30
data/theme/pad-osd.css
Normal 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;
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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
761
js/ui/padOsd.js
Normal 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);
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user