Handle global keybindings in the shell
Handling global keybindings, such as volume and brightness keys but also custom keybindings, directly in the compositor is the only way to deal with grabs and modal operations that could be active (primarily the overview, for which special policy was introduced in the last commit) https://bugzilla.gnome.org/show_bug.cgi?id=613543
This commit is contained in:
parent
d886dc17e1
commit
60d87ef4ba
@ -2325,3 +2325,17 @@ StScrollBar StButton#vhandle:active {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.osd-window {
|
||||
color: #ededed;
|
||||
background-color: rgba(33, 37, 38, 0.80);
|
||||
border-radius: 15px;
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0.75);
|
||||
|
||||
padding: 40px;
|
||||
spacing: 5px;
|
||||
}
|
||||
|
||||
.osd-progress-bar {
|
||||
height: 0.8em;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
@ -100,6 +100,7 @@ nobase_dist_js_DATA = \
|
||||
ui/components/__init__.js \
|
||||
ui/components/autorunManager.js \
|
||||
ui/components/automountManager.js \
|
||||
ui/components/mediaKeysManager.js \
|
||||
ui/components/networkAgent.js \
|
||||
ui/components/polkitAgent.js \
|
||||
ui/components/recorder.js \
|
||||
|
@ -17,6 +17,9 @@ const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager
|
||||
<method name='Suspend'>
|
||||
<arg type='b' direction='in'/>
|
||||
</method>
|
||||
<method name='Hibernate'>
|
||||
<arg type='b' direction='in'/>
|
||||
</method>
|
||||
<method name='CanPowerOff'>
|
||||
<arg type='s' direction='out'/>
|
||||
</method>
|
||||
@ -26,6 +29,9 @@ const SystemdLoginManagerIface = <interface name='org.freedesktop.login1.Manager
|
||||
<method name='CanSuspend'>
|
||||
<arg type='s' direction='out'/>
|
||||
</method>
|
||||
<method name='CanHibernate'>
|
||||
<arg type='s' direction='out'/>
|
||||
</method>
|
||||
</interface>;
|
||||
|
||||
const SystemdLoginSessionIface = <interface name='org.freedesktop.login1.Session'>
|
||||
@ -140,6 +146,15 @@ const LoginManagerSystemd = new Lang.Class({
|
||||
});
|
||||
},
|
||||
|
||||
canHibernate: function(asyncCallback) {
|
||||
this._proxy.CanSuspendRemote(function(result, error) {
|
||||
if (error)
|
||||
asyncCallback(false);
|
||||
else
|
||||
asyncCallback(result[0] != 'no');
|
||||
});
|
||||
},
|
||||
|
||||
powerOff: function() {
|
||||
this._proxy.PowerOffRemote(true);
|
||||
},
|
||||
@ -150,6 +165,10 @@ const LoginManagerSystemd = new Lang.Class({
|
||||
|
||||
suspend: function() {
|
||||
this._proxy.SuspendRemote(true);
|
||||
},
|
||||
|
||||
hibernate: function() {
|
||||
this._proxy.HibernateRemote(true);
|
||||
}
|
||||
});
|
||||
|
||||
@ -215,6 +234,13 @@ const LoginManagerConsoleKit = new Lang.Class({
|
||||
}));
|
||||
},
|
||||
|
||||
canHibernate: function(asyncCallback) {
|
||||
Mainloop.idle_add(Lang.bind(this, function() {
|
||||
asyncCallback(this._upClient.get_can_hibernate());
|
||||
return false;
|
||||
}));
|
||||
},
|
||||
|
||||
powerOff: function() {
|
||||
this._proxy.StopRemote();
|
||||
},
|
||||
@ -225,5 +251,9 @@ const LoginManagerConsoleKit = new Lang.Class({
|
||||
|
||||
suspend: function() {
|
||||
this._upClient.suspend_sync(null);
|
||||
},
|
||||
|
||||
hibernate: function() {
|
||||
this._upClient.hibernate_sync(null);
|
||||
}
|
||||
});
|
||||
|
647
js/ui/components/mediaKeysManager.js
Normal file
647
js/ui/components/mediaKeysManager.js
Normal file
@ -0,0 +1,647 @@
|
||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const ShellMountOperation = imports.ui.shellMountOperation;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const INTERFACE_SETTINGS = 'org.gnome.desktop.interface';
|
||||
const POWER_SETTINGS = 'org.gnome.settings-daemon.plugins.power';
|
||||
const XSETTINGS_SETTINGS = 'org.gnome.settings-daemon.plugins.xsettings';
|
||||
const TOUCHPAD_SETTINGS = 'org.gnome.settings-daemon.peripherals.touchpad';
|
||||
const KEYBINDING_SETTINGS = 'org.gnome.settings-daemon.plugins.media-keys';
|
||||
const CUSTOM_KEYBINDING_SETTINGS = 'org.gnome.settings-daemon.plugins.media-keys.custom-keybinding';
|
||||
const A11Y_SETTINGS = 'org.gnome.desktop.a11y.applications';
|
||||
const MAGNIFIER_SETTINGS = 'org.gnome.desktop.a11y.magnifier';
|
||||
const INPUT_SOURCE_SETTINGS = 'org.gnome.desktop.input-sources';
|
||||
|
||||
const MediaKeysInterface = <interface name='org.gnome.SettingsDaemon.MediaKeys'>
|
||||
<method name='GrabMediaPlayerKeys'>
|
||||
<arg name='application' direction='in' type='s'/>
|
||||
<arg name='time' direction='in' type='u'/>
|
||||
</method>
|
||||
<method name='ReleaseMediaPlayerKeys'>
|
||||
<arg name='application' direction='in' type='s'/>
|
||||
</method>
|
||||
<signal name='MediaPlayerKeyPressed'>
|
||||
<arg name='application' type='s'/>
|
||||
<arg name='key' type='s'/>
|
||||
</signal>
|
||||
</interface>;
|
||||
|
||||
/* [ actionName, setting, hardcodedKeysym, overviewOnly, args ] */
|
||||
/* (overviewOnly means that the keybinding is handled when the shell is not
|
||||
modal, or when the overview is active, but not when other modal operations
|
||||
are active; otherwise the keybinding is always handled) */
|
||||
const DEFAULT_KEYBINDINGS = [
|
||||
[ 'doTouchpadToggle', null, 'XF86TouchpadToggle', false ],
|
||||
[ 'doTouchpadSet', null, 'XF86TouchpadOn', false, [ true ] ],
|
||||
[ 'doTouchpadSet', null, 'XF86TouchpadOff', false, [ false ] ],
|
||||
[ 'doMute', 'volume-mute', null, false, [ false ] ],
|
||||
[ 'doVolumeAdjust', 'volume-down', null, false, [ Clutter.ScrollDirection.DOWN, false ] ],
|
||||
[ 'doVolumeAdjust', 'volume-up', null, false, [ Clutter.ScrollDirection.UP, false ] ],
|
||||
[ 'doMute', null, '<Alt>XF86AudioMute', false, [ true ] ],
|
||||
[ 'doVolumeAdjust', null, '<Alt>XF86AudioLowerVolume', false, [ Clutter.ScrollDirection.DOWN, true ] ],
|
||||
[ 'doVolumeAdjust', null, '<Alt>XF86AudioRaiseVolume', false, [ Clutter.ScrollDirection.UP, true ] ],
|
||||
[ 'doLogout', 'logout', null, true ],
|
||||
[ 'doEject', 'eject', null, false ],
|
||||
[ 'doHome', 'home', null, true ],
|
||||
[ 'doLaunchMimeHandler', 'media', null, true, [ 'application/x-vorbis+ogg' ] ],
|
||||
[ 'doLaunchApp', 'calculator', null, true, [ 'gcalcltool.desktop' ] ],
|
||||
[ 'doLaunchApp', 'search', null, true, [ 'tracker-needle.desktop' ] ],
|
||||
[ 'doLaunchMimeHandler', 'email', null, true, [ 'x-scheme-handler/mailto' ] ],
|
||||
[ 'doScreensaver', 'screensaver', null, true ],
|
||||
[ 'doScreensaver', null, 'XF86ScreenSaver', true ],
|
||||
[ 'doLaunchApp', 'help', null, true, [ 'yelp.desktop' ] ],
|
||||
[ 'doSpawn', 'screenshot', null, true, [ ['gnome-screenshot'] ] ],
|
||||
[ 'doSpawn', 'window-screenshot', null, true, [ ['gnome-screenshot', '--window'] ] ],
|
||||
[ 'doSpawn', 'area-screenshot', null, true, [ ['gnome-screenshot', '--area'] ] ],
|
||||
[ 'doSpawn', 'screenshot-clip', null, true, [ ['gnome-screenshot', '--clipboard'] ] ],
|
||||
[ 'doSpawn', 'window-screenshot-clip', null, true, [ ['gnome-screenshot', '--window', '--clipboard'] ] ],
|
||||
[ 'doSpawn', 'area-screenshot-clip', null, true, [ ['gnome-screenshot', '--area', '--clipboard'] ] ],
|
||||
[ 'doLaunchMimeHandler', 'www', null, true, [ 'x-scheme-handler/http' ] ],
|
||||
[ 'doMediaKey', 'play', null, true, [ 'Play' ] ],
|
||||
[ 'doMediaKey', 'pause', null, true, [ 'Pause' ] ],
|
||||
[ 'doMediaKey', 'stop', null, true, [ 'Stop' ] ],
|
||||
[ 'doMediaKey', 'previous', null, true, [ 'Previous' ] ],
|
||||
[ 'doMediaKey', 'next', null, true, [ 'Next' ] ],
|
||||
[ 'doMediaKey', null, 'XF86AudioRewind', true, [ 'Rewind' ] ],
|
||||
[ 'doMediaKey', null, 'XF86AudioForward', true, [ 'FastForward' ] ],
|
||||
[ 'doMediaKey', null, 'XF86AudioRepeat', true, [ 'Repeat' ] ],
|
||||
[ 'doMediaKey', null, 'XF86AudioRandomPlay', true, [ 'Shuffle' ] ],
|
||||
[ 'doXRandRAction', null, '<Super>p', false, [ 'VideoModeSwitch' ] ],
|
||||
/* Key code of the XF86Display key (Fn-F7 on Thinkpads, Fn-F4 on HP machines, etc.) */
|
||||
[ 'doXRandRAction', null, 'XF86Display', false, [ 'VideoModeSwitch' ] ],
|
||||
/* Key code of the XF86RotateWindows key (present on some tablets) */
|
||||
[ 'doXRandRAction', null, 'XF86RotateWindows', false, [ 'Rotate' ] ],
|
||||
[ 'doA11yAction', 'magnifier', null, true, [ 'screen-magnifier-enabled' ] ],
|
||||
[ 'doA11yAction', 'screenreader', null, true, [ 'screen-reader-enabled' ] ],
|
||||
[ 'doA11yAction', 'on-screen-keyboard', null, true, [ 'screen-keyboard-enabled' ] ],
|
||||
[ 'doTextSize', 'increase-text-size', null, true, [ 1 ] ],
|
||||
[ 'doTextSize', 'decrease-text-size', null, true, [ -1 ] ],
|
||||
[ 'doToggleContrast', 'toggle-contrast', null, true ],
|
||||
[ 'doMagnifierZoom', 'magnifier-zoom-in', null, true, [ 1 ] ],
|
||||
[ 'doMagnifierZoom', 'magnifier-zoom-out', null, true, [ -1 ] ],
|
||||
[ 'doPowerAction', null, 'XF86PowerOff', true, [ 'button-power' ] ],
|
||||
/* the kernel / Xorg names really are like this... */
|
||||
[ 'doPowerAction', null, 'XF86Suspend', false, [ 'button-sleep' ] ],
|
||||
[ 'doPowerAction', null, 'XF86Sleep', false, [ 'button-suspend' ] ],
|
||||
[ 'doPowerAction', null, 'XF86Hibernate', false, [ 'button-hibernate' ] ],
|
||||
[ 'doBrightness', null, 'XF86MonBrightnessUp', false, [ 'Screen', 'StepUp' ] ],
|
||||
[ 'doBrightness', null, 'XF86MonBrightnessDown', false, [ 'Screen', 'StepDown' ] ],
|
||||
[ 'doBrightness', null, 'XF86KbdBrightnessUp', false, [ 'Keyboard', 'StepUp' ] ],
|
||||
[ 'doBrightness', null, 'XF86KbdBrightnessDown', false, [ 'Keyboard', 'StepDown' ] ],
|
||||
[ 'doBrightnessToggle', null, 'XF86KbdLightOnOff', false, ],
|
||||
[ 'doInputSource', 'switch-input-source', null, false, [ +1 ] ],
|
||||
[ 'doInputSource', 'switch-input-source-backward', null, false, [ -1 ] ],
|
||||
[ 'doLaunchApp', null, 'XF86Battery', true, [ 'gnome-power-statistics.desktop' ] ]
|
||||
];
|
||||
|
||||
var osdWin;
|
||||
const OSDWindow = new Lang.Class({
|
||||
Name: 'OSDWindow',
|
||||
|
||||
FADE_TIMEOUT: 1500,
|
||||
FADE_DURATION: 100,
|
||||
|
||||
_init: function(iconName, value) {
|
||||
/* assume 130x130 on a 640x480 display and scale from there */
|
||||
let monitor = Main.layoutManager.primaryMonitor;
|
||||
let scalew = monitor.width / 640.0;
|
||||
let scaleh = monitor.height / 480.0;
|
||||
let scale = Math.min(scalew, scaleh);
|
||||
let size = 130 * Math.max(1, scale);
|
||||
|
||||
this.actor = new St.BoxLayout({ style_class: 'osd-window',
|
||||
vertical: true,
|
||||
reactive: false,
|
||||
visible: false,
|
||||
width: size,
|
||||
height: size,
|
||||
});
|
||||
|
||||
this._icon = new St.Icon({ icon_name: iconName,
|
||||
icon_size: size / 2,
|
||||
});
|
||||
this.actor.add(this._icon, { expand: true,
|
||||
x_align: St.Align.MIDDLE,
|
||||
y_align: St.Align.MIDDLE });
|
||||
|
||||
this._value = value;
|
||||
this._progressBar = new St.DrawingArea({ style_class: 'osd-progress-bar' });
|
||||
this._progressBar.connect('repaint', Lang.bind(this, this._drawProgress));
|
||||
this.actor.add(this._progressBar, { expand: true, x_fill: true, y_fill: false });
|
||||
this._progressBar.visible = value !== undefined;
|
||||
|
||||
Main.layoutManager.addChrome(this.actor);
|
||||
|
||||
/* Position in the middle of primary monitor */
|
||||
let [width, height] = this.actor.get_size();
|
||||
this.actor.x = ((monitor.width - width) / 2) + monitor.x;
|
||||
this.actor.y = monitor.y + (monitor.height / 2) + (monitor.height / 2 - height) / 2;
|
||||
},
|
||||
|
||||
show: function() {
|
||||
this.actor.show();
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 255,
|
||||
time: this.FADE_DURATION / 1000,
|
||||
transition: 'easeInQuad' });
|
||||
|
||||
if (this._timeoutId)
|
||||
GLib.source_remove(this._timeoutId);
|
||||
|
||||
this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this.FADE_TIMEOUT, Lang.bind(this, this.hide));
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: this.FADE_DURATION / 1000,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: function() {
|
||||
this.actor.destroy();
|
||||
this.actor = null;
|
||||
osdWin = null;
|
||||
},
|
||||
onCompleteScope: this });
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
setIcon: function(name) {
|
||||
this._icon.icon_name = name;
|
||||
},
|
||||
|
||||
setValue: function(value) {
|
||||
if (value == this._value)
|
||||
return;
|
||||
|
||||
this._value = value;
|
||||
this._progressBar.visible = value !== undefined;
|
||||
this._progressBar.queue_repaint();
|
||||
},
|
||||
|
||||
_drawProgress: function(area) {
|
||||
let cr = area.get_context();
|
||||
|
||||
let themeNode = this.actor.get_theme_node();
|
||||
let color = themeNode.get_foreground_color();
|
||||
Clutter.cairo_set_source_color(cr, color);
|
||||
|
||||
let [width, height] = area.get_surface_size();
|
||||
width = width * this._value;
|
||||
|
||||
cr.moveTo(0,0);
|
||||
cr.lineTo(width, 0);
|
||||
cr.lineTo(width, height);
|
||||
cr.lineTo(0, height);
|
||||
cr.fill();
|
||||
}
|
||||
});
|
||||
|
||||
function showOSD(icon, value) {
|
||||
if (osdWin) {
|
||||
osdWin.setIcon(icon);
|
||||
osdWin.setValue(value);
|
||||
} else {
|
||||
osdWin = new OSDWindow(icon, value);
|
||||
}
|
||||
|
||||
osdWin.show();
|
||||
}
|
||||
|
||||
const MediaKeysGrabber = new Lang.Class({
|
||||
Name: 'MediaKeysGrabber',
|
||||
|
||||
_init: function() {
|
||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(MediaKeysInterface, this);
|
||||
this._apps = [];
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/SettingsDaemon/MediaKeys');
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
this._dbusImpl.unexport();
|
||||
},
|
||||
|
||||
GrabMediaPlayerKeysAsync: function(parameters, invocation) {
|
||||
let [appName, time] = parameters;
|
||||
|
||||
/* I'm not sure of this code, but it is in gnome-settings-daemon
|
||||
(letting alone that the introspection is wrong in glib...)
|
||||
*/
|
||||
if (time == Gdk.CURRENT_TIME) {
|
||||
let tv = new GLib.TimeVal;
|
||||
GLib.get_current_time(tv);
|
||||
time = tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||
}
|
||||
|
||||
let pos = -1;
|
||||
for (let i = 0; i < this._apps.length; i++) {
|
||||
if (this._apps[i].appName == appName) {
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos != -1)
|
||||
this._freeMediaPlayer(pos);
|
||||
|
||||
let app = {
|
||||
appName: appName,
|
||||
name: invocation.get_sender(),
|
||||
time: time,
|
||||
watchId: Gio.DBus.session.watch_name(invocation.get_sender(),
|
||||
Gio.BusNameWatcherFlags.NONE,
|
||||
null,
|
||||
Lang.bind(this, this._onNameVanished)),
|
||||
};
|
||||
Util.insertSorted(this._apps, app, function(a, b) {
|
||||
return b.time-a.time;
|
||||
});
|
||||
|
||||
invocation.return_value(GLib.Variant.new('()', []));
|
||||
},
|
||||
|
||||
ReleaseMediaPlayerAsync: function(parameters, invocation) {
|
||||
let name = invocation.get_sender();
|
||||
let [appName] = parameters;
|
||||
|
||||
let pos = -1;
|
||||
for (let i = 0; i < this._apps.length; i++) {
|
||||
if (this._apps[i].appName == appName) {
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos == -1) {
|
||||
for (let i = 0; i < this._apps.length; i++) {
|
||||
if (this._apps[i].name == name) {
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pos != -1)
|
||||
this._freeMediaPlayer(pos);
|
||||
|
||||
invocation.return_value(GLib.Variant.new('()', []));
|
||||
},
|
||||
|
||||
_freeMediaPlayer: function(pos) {
|
||||
let app = this._apps[pos];
|
||||
Gio.bus_unwatch_name(app.watchId)
|
||||
|
||||
this._apps.splice(pos, 1);
|
||||
},
|
||||
|
||||
mediaKeyPressed: function(key) {
|
||||
if (this._apps.length == 0) {
|
||||
showOSD('action-unavailable-symbolic');
|
||||
return;
|
||||
}
|
||||
|
||||
let app = this._apps[0];
|
||||
Gio.DBus.session.emit_signal(app.name,
|
||||
'/org/gnome/SettingsDaemon/MediaKeys',
|
||||
'org.gnome.SettingsDaemon.MediaKeys',
|
||||
'MediaPlayerKeyPressed',
|
||||
GLib.Variant.new('(ss)', [app.appName || '',
|
||||
key]));
|
||||
},
|
||||
});
|
||||
|
||||
const MediaKeysManager = new Lang.Class({
|
||||
Name: 'MediaKeysManager',
|
||||
|
||||
_init: function() {
|
||||
this._a11yControl = Main.panel.statusArea.a11y;
|
||||
this._volumeControl = Main.panel.statusArea.volume;
|
||||
this._userMenu = Main.panel.statusArea.userMenu;
|
||||
this._mediaPlayerKeys = new MediaKeysGrabber();
|
||||
|
||||
this._keybindingSettings = new Gio.Settings({ schema: KEYBINDING_SETTINGS });
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
for (let i = 0; i < DEFAULT_KEYBINDINGS.length; i++) {
|
||||
let [action, setting, keyval, overviewOnly, args] = DEFAULT_KEYBINDINGS[i];
|
||||
let func = this[action];
|
||||
if (!func) {
|
||||
log('Keybinding action %s is missing'.format(action));
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = setting ? setting : 'media-keys-keybindings-%d'.format(i);
|
||||
let ok;
|
||||
func = Util.wrapKeybinding(Lang.bind.apply(null, [this, func].concat(args)), overviewOnly);
|
||||
if (setting)
|
||||
ok = global.display.add_keybinding(setting, this._keybindingSettings,
|
||||
Meta.KeyBindingFlags.BUILTIN |
|
||||
Meta.KeyBindingFlags.IS_SINGLE |
|
||||
Meta.KeyBindingFlags.HANDLE_WHEN_GRABBED, func);
|
||||
else
|
||||
ok = global.display.add_grabbed_key(name, keyval,
|
||||
Meta.KeyBindingFlags.HANDLE_WHEN_GRABBED, func);
|
||||
|
||||
if (!ok)
|
||||
log('Installing keybinding %s failed'.format(name));
|
||||
}
|
||||
|
||||
this._customKeybindings = [];
|
||||
this._changedId = this._keybindingSettings.connect('changed::custom-keybindings',
|
||||
Lang.bind(this, this._reloadCustomKeybindings));
|
||||
this._reloadCustomKeybindings();
|
||||
|
||||
this._mediaPlayerKeys.enable();
|
||||
},
|
||||
|
||||
disable: function() {
|
||||
for (let i = 0; i < DEFAULT_KEYBINDINGS.length; i++) {
|
||||
let [action, setting, keyval, overviewOnly, args] = DEFAULT_KEYBINDINGS[i];
|
||||
|
||||
let name = setting ? setting : 'media-keys-keybindings-%d'.format(i);
|
||||
if (setting)
|
||||
global.display.remove_keybinding(setting, this._keybindingSettings);
|
||||
else
|
||||
global.display.remove_grabbed_key(name);
|
||||
}
|
||||
|
||||
this._clearCustomKeybindings();
|
||||
this._keybindingSettings.disconnect(this._changedId);
|
||||
|
||||
this._mediaPlayerKeys.disable();
|
||||
},
|
||||
|
||||
_clearCustomKeybindings: function() {
|
||||
for (let i = 0; i < this._customKeybindings.length; i++)
|
||||
global.display.remove_keybinding('binding', this._customKeybindings[i]);
|
||||
|
||||
this._customKeybindings = [];
|
||||
},
|
||||
|
||||
_reloadCustomKeybindings: function() {
|
||||
this._clearCustomKeybindings();
|
||||
|
||||
let paths = this._keybindingSettings.get_strv('custom-keybindings');
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
let setting = new Gio.Settings({ schema: CUSTOM_KEYBINDING_SETTINGS,
|
||||
path: paths[i] });
|
||||
let func = Util.wrapKeybinding(Lang.bind(this, this.doCustom, setting), true);
|
||||
|
||||
global.display.add_keybinding('binding', setting,
|
||||
Meta.KeyBindingFlags.IS_SINGLE |
|
||||
Meta.KeyBindingFlags.HANDLE_WHEN_GRABBED, func);
|
||||
this._customKeybindings.push(setting);
|
||||
}
|
||||
},
|
||||
|
||||
doCustom: function(display, screen, window, binding, settings) {
|
||||
let command = settings.get_string('command');
|
||||
Util.spawnCommandLine(command);
|
||||
},
|
||||
|
||||
doTouchpadToggle: function(display, screen, window, binding) {
|
||||
let settings = new Gio.Settings({ schema: TOUCHPAD_SETTINGS });
|
||||
let enabled = settings.get_boolean('touchpad-enabled');
|
||||
|
||||
this.doTouchpadSet(display, screen, window, binding, !enabled);
|
||||
settings.set_boolean(!enabled);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
doTouchpadSet: function(display, screen, window, binding, enabled) {
|
||||
showOSD(enabled ? 'input-touchpad-symbolic' : 'touchpad-disabled-symbolic');
|
||||
return true;
|
||||
},
|
||||
|
||||
doMute: function(display, screen, window, binding, quiet) {
|
||||
let [icon, value] = this._volumeControl.volumeMenu.toggleMute(quiet);
|
||||
showOSD(icon, value);
|
||||
return true;
|
||||
},
|
||||
|
||||
doVolumeAdjust: function(display, screen, window, binding, direction, quiet) {
|
||||
let [icon, value] = this._volumeControl.volumeMenu.scroll(direction, quiet);
|
||||
showOSD(icon, value);
|
||||
return true;
|
||||
},
|
||||
|
||||
doLogout: function(display, screen, window, binding) {
|
||||
this._userMenu.logOut();
|
||||
return true;
|
||||
},
|
||||
|
||||
doEject: function(display, screen, window, binding) {
|
||||
let volumeMonitor = Gio.VolumeMonitor.get();
|
||||
|
||||
let drives = volumeMonitor.get_connected_drives();
|
||||
let score = 0, drive;
|
||||
for (let i = 0; i < drives.length; i++) {
|
||||
if (!drives[i].can_eject())
|
||||
continue;
|
||||
if (!drives[i].is_media_removable())
|
||||
continue;
|
||||
if (score < 1) {
|
||||
drive = drives[i];
|
||||
score = 1;
|
||||
}
|
||||
if (!drives[i].has_media())
|
||||
continue;
|
||||
if (score < 2) {
|
||||
drive = drives[i];
|
||||
score = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
showOSD('media-eject-custom-symbolic');
|
||||
|
||||
if (!drive)
|
||||
return true;
|
||||
|
||||
let mountOp = new ShellMountOperation.ShellMountOperation(drive);
|
||||
drive.eject_with_operation(Gio.MountUnmountFlags.FORCE,
|
||||
mountOp.mountOp, null, null);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
doHome: function() {
|
||||
let homeFile = Gio.file_new_for_path (GLib.get_home_dir());
|
||||
let homeUri = homeFile.get_uri();
|
||||
Gio.app_info_launch_default_for_uri(homeUri, null);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
doLaunchMimeHandler: function(display, screen, window, binding, mimeType) {
|
||||
let gioApp = Gio.AppInfo.get_default_for_type(mimeType, false);
|
||||
if (gioApp != null) {
|
||||
let app = Shell.AppSystem.get_default().lookup_app(gioApp.get_id());
|
||||
app.open_new_window(-1);
|
||||
} else {
|
||||
log('Could not find default application for \'%s\' mime-type'.format(mimeType));
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
doLaunchApp: function(display, screen, window, binding, appId) {
|
||||
let app = Shell.AppSystem.get_default().lookup_app(appId);
|
||||
app.open_new_window(-1);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
doScreensaver: function() {
|
||||
// FIXME: handled in house, to the screenshield!
|
||||
return true;
|
||||
},
|
||||
|
||||
doSpawn: function(display, screen, window, binding, argv) {
|
||||
Util.spawn(argv);
|
||||
return true;
|
||||
},
|
||||
|
||||
doMediaKey: function(display, screen, window, binding, key) {
|
||||
this._mediaPlayerKeys.mediaKeyPressed(key);
|
||||
},
|
||||
|
||||
_onXRandRFinished: function(connection, result) {
|
||||
connection.call_finish(result);
|
||||
this._XRandRCancellable = null;
|
||||
},
|
||||
|
||||
doXRandRAction: function(display, screen, window, binding, action) {
|
||||
if (this._XRandRCancellable)
|
||||
this._XRandRCancellable.cancel();
|
||||
|
||||
this._XRandRCancellable = new Gio.Cancellable();
|
||||
Gio.DBus.session.call('org.gnome.SettingsDaemon',
|
||||
'/org/gnome/SettingsDaemon/XRANDR',
|
||||
'org.gnome.SettingsDaemon.XRANDR_2',
|
||||
action,
|
||||
GLib.Variant.new('(x)', [global.get_current_time()]),
|
||||
null, /* reply type */
|
||||
Gio.DBusCallFlags.NONE,
|
||||
-1,
|
||||
this._XRandRCancellable,
|
||||
Lang.bind(this, this._onXRandRFinished));
|
||||
},
|
||||
|
||||
doA11yAction: function(display, screen, window, binding, key) {
|
||||
let settings = new Gio.Settings({ schema: A11Y_SETTINGS });
|
||||
let enabled = settings.get_boolean(key);
|
||||
settings.set_boolean(key, !enabled);
|
||||
},
|
||||
|
||||
doTextSize: function(display, screen, window, binding, multiplier) {
|
||||
// Same values used in the Seeing tab of the Universal Access panel
|
||||
const FACTORS = [ 0.75, 1.0, 1.25, 1.5 ];
|
||||
|
||||
// Figure out the current DPI scaling factor
|
||||
let settings = new Gio.Settings({ schema: INTERFACE_SETTINGS });
|
||||
let factor = settings.get_double('text-scaling-factor');
|
||||
factor += multiplier * 0.25;
|
||||
|
||||
/* Try to find a matching value */
|
||||
let distance = 1e6;
|
||||
let best = 1.0;
|
||||
for (let i = 0; i < FACTORS.length; i++) {
|
||||
let d = Math.abs(factor - FACTORS[i]);
|
||||
if (d < distance) {
|
||||
best = factors[i];
|
||||
distance = d;
|
||||
}
|
||||
}
|
||||
|
||||
if (best == 1.0)
|
||||
settings.reset('text-scaling-factor');
|
||||
else
|
||||
settings.set_double('text-scaling-factor', best);
|
||||
},
|
||||
|
||||
doToggleContrast: function(display, screen, window, binding) {
|
||||
this._a11yControl.toggleHighContrast();
|
||||
},
|
||||
|
||||
doMagnifierZoom: function(display, screen, window, binding, offset) {
|
||||
let settings = new Gio.Settings({ schema: MAGNIFIER_SETTINGS });
|
||||
|
||||
let value = settings.get_value('mag-factor');
|
||||
value = Math.round(value + offset);
|
||||
settings.set_value('mag-factor', value);
|
||||
},
|
||||
|
||||
doPowerAction: function(display, screen, window, binding, action) {
|
||||
let settings = new Gio.Settings({ schema: POWER_SETTINGS });
|
||||
switch (settings.get_string(action)) {
|
||||
case 'suspend':
|
||||
this._userMenu.suspend();
|
||||
break;
|
||||
case 'interactive':
|
||||
case 'shutdown':
|
||||
this._userMenu.shutdown();
|
||||
break;
|
||||
case 'hibernate':
|
||||
this._userMenu.hibernate();
|
||||
break;
|
||||
case 'blank':
|
||||
case 'default':
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_onBrightnessFinished: function(connection, result, kind) {
|
||||
let [percentage] = connection.call_finish(result).deep_unpack();
|
||||
|
||||
let icon = kind == 'Keyboard' ? 'keyboard-brightness-symbolic' : 'display-brightness-symbolic';
|
||||
showOSD(icon, percentage / 100);
|
||||
},
|
||||
|
||||
doBrightness: function(display, screen, window, binding, kind, action) {
|
||||
let iface = 'org.gnome.SettingsDaemon.Power.' + kind;
|
||||
let objectPath = '/org/gnome/SettingsDaemon/Power';
|
||||
|
||||
Gio.DBus.session.call('org.gnome.SettingsDaemon',
|
||||
objectPath, iface, action,
|
||||
null, null, /* parameters, reply type */
|
||||
Gio.DBusCallFlags.NONE, -1, null,
|
||||
Lang.bind(this, this._onBrightnessFinished, kind));
|
||||
},
|
||||
|
||||
doInputSource: function(display, screen, window, binding, offset) {
|
||||
let settings = new Gio.Settings({ schema: INPUT_SOURCE_SETTINGS });
|
||||
|
||||
let current = settings.get_uint('current');
|
||||
let max = settings.get_strv('sources').length - 1;
|
||||
|
||||
current += offset;
|
||||
if (current < 0)
|
||||
current = 0;
|
||||
else if (current > max)
|
||||
current = max;
|
||||
|
||||
settings.set_uint('current', current);
|
||||
},
|
||||
});
|
||||
|
||||
const Component = MediaKeysManager;
|
@ -37,7 +37,7 @@ const _modes = {
|
||||
isGreeter: true,
|
||||
isPrimary: true,
|
||||
unlockDialog: imports.gdm.loginDialog.LoginDialog,
|
||||
components: ['polkitAgent'],
|
||||
components: ['polkitAgent', 'mediaKeysManager'],
|
||||
panel: {
|
||||
left: ['logo'],
|
||||
center: ['dateMenu'],
|
||||
@ -50,7 +50,7 @@ const _modes = {
|
||||
isLocked: true,
|
||||
isGreeter: undefined,
|
||||
unlockDialog: undefined,
|
||||
components: ['polkitAgent', 'telepathyClient'],
|
||||
components: ['polkitAgent', 'telepathyClient', 'mediaKeysManager'],
|
||||
panel: {
|
||||
left: ['userMenu'],
|
||||
center: [],
|
||||
@ -61,7 +61,7 @@ const _modes = {
|
||||
'unlock-dialog': {
|
||||
isLocked: true,
|
||||
unlockDialog: undefined,
|
||||
components: ['polkitAgent', 'telepathyClient'],
|
||||
components: ['polkitAgent', 'telepathyClient', 'mediaKeysManager'],
|
||||
panel: {
|
||||
left: ['userMenu'],
|
||||
center: [],
|
||||
@ -71,7 +71,7 @@ const _modes = {
|
||||
|
||||
'initial-setup': {
|
||||
isPrimary: true,
|
||||
components: ['keyring'],
|
||||
components: ['keyring', 'mediaKeysManager'],
|
||||
panel: {
|
||||
left: [],
|
||||
center: ['dateMenu'],
|
||||
@ -91,8 +91,9 @@ const _modes = {
|
||||
isLocked: false,
|
||||
isPrimary: true,
|
||||
unlockDialog: imports.ui.unlockDialog.UnlockDialog,
|
||||
components: ['networkAgent', 'polkitAgent', 'telepathyClient',
|
||||
'keyring', 'recorder', 'autorunManager', 'automountManager'],
|
||||
components: ['networkAgent', 'polkitAgent', 'telepathyClient', 'keyring',
|
||||
'recorder', 'autorunManager', 'automountManager',
|
||||
'mediaKeysManager'],
|
||||
panel: {
|
||||
left: ['activities', 'appMenu'],
|
||||
center: ['dateMenu'],
|
||||
|
@ -38,8 +38,8 @@ const ATIndicator = new Lang.Class({
|
||||
_init: function() {
|
||||
this.parent('preferences-desktop-accessibility-symbolic', _("Accessibility"));
|
||||
|
||||
let highContrast = this._buildHCItem();
|
||||
this.menu.addMenuItem(highContrast);
|
||||
this._highContrast = this._buildHCItem();
|
||||
this.menu.addMenuItem(this._highContrast);
|
||||
|
||||
let magnifier = this._buildItem(_("Zoom"), APPLICATIONS_SCHEMA,
|
||||
'screen-magnifier-enabled');
|
||||
@ -159,5 +159,9 @@ const ATIndicator = new Lang.Class({
|
||||
widget.setToggleState(active);
|
||||
});
|
||||
return widget;
|
||||
}
|
||||
},
|
||||
|
||||
toggleHighContrast: function() {
|
||||
this._highContrast.toggle();
|
||||
},
|
||||
});
|
||||
|
@ -66,7 +66,21 @@ const VolumeMenu = new Lang.Class({
|
||||
this._onControlStateChanged();
|
||||
},
|
||||
|
||||
scroll: function(direction) {
|
||||
toggleMute: function(quiet) {
|
||||
let muted = this._output.is_muted;
|
||||
this._output.change_is_muted(!muted);
|
||||
|
||||
if (muted && !quiet)
|
||||
this._notifyVolumeChange();
|
||||
|
||||
if (!muted)
|
||||
return ['audio-volume-muted-symbolic', 0];
|
||||
else
|
||||
return [this._volumeToIcon(this._output.volume),
|
||||
this._output.volume / this._volumeMax];
|
||||
},
|
||||
|
||||
scroll: function(direction, quiet) {
|
||||
let currentVolume = this._output.volume;
|
||||
|
||||
if (direction == Clutter.ScrollDirection.DOWN) {
|
||||
@ -85,7 +99,14 @@ const VolumeMenu = new Lang.Class({
|
||||
this._output.push_volume();
|
||||
}
|
||||
|
||||
this._notifyVolumeChange();
|
||||
if (!quiet)
|
||||
this._notifyVolumeChange();
|
||||
|
||||
if (this._output.is_muted)
|
||||
return ['audio-volume-muted-symbolic', 0];
|
||||
else
|
||||
return [this._volumeToIcon(this._output.volume),
|
||||
this._output.volume / this._volumeMax];
|
||||
},
|
||||
|
||||
_onControlStateChanged: function() {
|
||||
@ -221,14 +242,14 @@ const Indicator = new Lang.Class({
|
||||
this.parent('audio-volume-muted-symbolic', _("Volume"));
|
||||
|
||||
this._control = getMixerControl();
|
||||
this._volumeMenu = new VolumeMenu(this._control);
|
||||
this._volumeMenu.connect('icon-changed', Lang.bind(this, function(menu, icon) {
|
||||
this.volumeMenu = new VolumeMenu(this._control);
|
||||
this.volumeMenu.connect('icon-changed', Lang.bind(this, function(menu, icon) {
|
||||
this._hasPulseAudio = (icon != null);
|
||||
this.setIcon(icon);
|
||||
this._syncVisibility();
|
||||
}));
|
||||
|
||||
this.menu.addMenuItem(this._volumeMenu);
|
||||
this.menu.addMenuItem(this.volumeMenu);
|
||||
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop');
|
||||
@ -242,6 +263,6 @@ const Indicator = new Lang.Class({
|
||||
},
|
||||
|
||||
_onScrollEvent: function(actor, event) {
|
||||
this._volumeMenu.scroll(event.get_scroll_direction());
|
||||
}
|
||||
this.volumeMenu.scroll(event.get_scroll_direction(), false);
|
||||
},
|
||||
});
|
||||
|
@ -572,6 +572,7 @@ const UserMenuButton = new Lang.Class({
|
||||
|
||||
this._updateHaveShutdown();
|
||||
this._updateHaveSuspend();
|
||||
this._updateHaveHibernate();
|
||||
}));
|
||||
this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
|
||||
Lang.bind(this, this._updateHaveShutdown));
|
||||
@ -655,6 +656,13 @@ const UserMenuButton = new Lang.Class({
|
||||
}));
|
||||
},
|
||||
|
||||
_updateHaveHibernate: function() {
|
||||
this._loginManager.canHibernate(Lang.bind(this,
|
||||
function(result) {
|
||||
this._haveHibernate = result;
|
||||
}));
|
||||
},
|
||||
|
||||
_updateSuspendOrPowerOff: function() {
|
||||
if (!this._suspendOrPowerOffItem)
|
||||
return;
|
||||
@ -766,7 +774,7 @@ const UserMenuButton = new Lang.Class({
|
||||
this._loginScreenItem = item;
|
||||
|
||||
item = new PopupMenu.PopupMenuItem(_("Log Out"));
|
||||
item.connect('activate', Lang.bind(this, this._onQuitSessionActivate));
|
||||
item.connect('activate', Lang.bind(this, this.logOut));
|
||||
this.menu.addMenuItem(item);
|
||||
this._logoutItem = item;
|
||||
|
||||
@ -835,7 +843,7 @@ const UserMenuButton = new Lang.Class({
|
||||
Gdm.goto_login_session_sync(null);
|
||||
},
|
||||
|
||||
_onQuitSessionActivate: function() {
|
||||
logOut: function() {
|
||||
Main.overview.hide();
|
||||
this._session.LogoutRemote(0);
|
||||
},
|
||||
@ -847,25 +855,60 @@ const UserMenuButton = new Lang.Class({
|
||||
this._session.RebootRemote();
|
||||
},
|
||||
|
||||
shutdown: function() {
|
||||
this._session.ShutdownRemote();
|
||||
},
|
||||
|
||||
suspend: function() {
|
||||
if (!this._haveSuspend)
|
||||
return false;
|
||||
|
||||
// Ensure we only suspend after locking the screen
|
||||
if (this._screenSaverSettings.get_boolean(LOCK_ENABLED_KEY)) {
|
||||
let tmpId = Main.screenShield.connect('lock-screen-shown', Lang.bind(this, function() {
|
||||
Main.screenShield.disconnect(tmpId);
|
||||
|
||||
this._loginManager.suspend();
|
||||
}));
|
||||
|
||||
this.menu.close(BoxPointer.PopupAnimation.NONE);
|
||||
Main.screenShield.lock(true);
|
||||
} else {
|
||||
this._loginManager.suspend();
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
hibernate: function() {
|
||||
if (!this._haveHibernate)
|
||||
return false;
|
||||
|
||||
// Ensure we only suspend after locking the screen
|
||||
if (this._screenSaverSettings.get_boolean(LOCK_ENABLED_KEY)) {
|
||||
let tmpId = Main.screenShield.connect('lock-screen-shown', Lang.bind(this, function() {
|
||||
Main.screenShield.disconnect(tmpId);
|
||||
|
||||
this._loginManager.hibernate();
|
||||
}));
|
||||
|
||||
this.menu.close(BoxPointer.PopupAnimation.NONE);
|
||||
Main.screenShield.lock(true);
|
||||
} else {
|
||||
this._loginManager.hibernate();
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_onSuspendOrPowerOffActivate: function() {
|
||||
Main.overview.hide();
|
||||
|
||||
if (this._haveShutdown &&
|
||||
this._suspendOrPowerOffItem.state == PopupMenu.PopupAlternatingMenuItemState.DEFAULT) {
|
||||
this._session.ShutdownRemote();
|
||||
this.shutdown();
|
||||
} else {
|
||||
if (this._screenSaverSettings.get_boolean(LOCK_ENABLED_KEY)) {
|
||||
let tmpId = Main.screenShield.connect('lock-screen-shown', Lang.bind(this, function() {
|
||||
Main.screenShield.disconnect(tmpId);
|
||||
|
||||
this._loginManager.suspend();
|
||||
}));
|
||||
|
||||
this.menu.close(BoxPointer.PopupAnimation.NONE);
|
||||
Main.screenShield.lock(true);
|
||||
} else {
|
||||
this._loginManager.suspend();
|
||||
}
|
||||
this.suspend();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user