status/powerProfiles: Add power mode selection
Settings' power panel gained support for switchable power profiles in GNOME 40. It's useful to have that functionality more readily available, so expose it in the system status menu as well. https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3944 Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1907>
This commit is contained in:
parent
770f15207c
commit
f1320bd250
76
data/dbus-interfaces/net.hadess.PowerProfiles.xml
Normal file
76
data/dbus-interfaces/net.hadess.PowerProfiles.xml
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||||
|
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||||
|
|
||||||
|
<node>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
net.hadess.PowerProfiles:
|
||||||
|
@short_description: Power Profiles daemon
|
||||||
|
|
||||||
|
The power-profiles-daemon API is meant to be used by parts of the OS or
|
||||||
|
desktop environment to switch system power profiles based on user choice,
|
||||||
|
or user intent.
|
||||||
|
|
||||||
|
OS components would typically use the "Profiles" property to construct
|
||||||
|
their UI (2 or 3 profiles available), and monitor the "ActiveProfile"
|
||||||
|
and the "PerformanceInhibited" properties to update that UI. The UI
|
||||||
|
would try to set the "ActiveProfile" property if the user selected
|
||||||
|
a different one.
|
||||||
|
|
||||||
|
Note that the reason why the project exists and how it is different from
|
||||||
|
existing projects is explained <ulink href=" https://gitlab.freedesktop.org/hadess/power-profiles-daemon/-/blob/master/README.md">
|
||||||
|
in the project's README file</ulink>.
|
||||||
|
|
||||||
|
The object path will be "/net/hadess/PowerProfiles".
|
||||||
|
-->
|
||||||
|
<interface name="net.hadess.PowerProfiles">
|
||||||
|
<!--
|
||||||
|
ActiveProfile:
|
||||||
|
|
||||||
|
The type of the currently active profile. It might change automatically
|
||||||
|
if the "performance" profile was selected but it got inhibited, in which
|
||||||
|
case the "PerformanceInhibited" property will reflect the reason.
|
||||||
|
-->
|
||||||
|
<property name="ActiveProfile" type="s" access="readwrite"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
PerformanceInhibited:
|
||||||
|
|
||||||
|
This will be set if the performance power profile is unavailable, with
|
||||||
|
the value being used to identify the reason for unavailability. As new
|
||||||
|
reasons can be added, it is recommended that front-ends show a generic
|
||||||
|
reason if they do not recognise the value. Possible values are:
|
||||||
|
- "lap-detected" (the computer is sitting on the user's lap)
|
||||||
|
- "high-operating-temperature" (the computer is close to overheating)
|
||||||
|
- "" (the empty string, if not inhibited)
|
||||||
|
-->
|
||||||
|
<property name="PerformanceInhibited" type="s" access="read"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Profiles:
|
||||||
|
|
||||||
|
An array of key-pair values representing each profile. The key named
|
||||||
|
"Driver" (s) identifies the power-profiles-daemon backend code used to
|
||||||
|
implement the profile.
|
||||||
|
|
||||||
|
The key named "Profile" (s) will be one of:
|
||||||
|
- "power-saver" (battery saving profile)
|
||||||
|
- "balanced" (the default profile)
|
||||||
|
- "performance" (a profile that does not care about noise or battery consumption)
|
||||||
|
|
||||||
|
Only one of each type of profile will be listed, with the daemon choosing the
|
||||||
|
more appropriate "driver" for each profile type.
|
||||||
|
-->
|
||||||
|
<property name="Profiles" type="aa{sv}" access="read"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Actions:
|
||||||
|
|
||||||
|
An array of strings listing each one of the "actions" implemented in
|
||||||
|
the running daemon. This is used by API users to figure out whether
|
||||||
|
particular functionality is available in a version of the daemon.
|
||||||
|
-->
|
||||||
|
<property name="Actions" type="as" access="read"/>
|
||||||
|
|
||||||
|
</interface>
|
||||||
|
</node>
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<gresources>
|
<gresources>
|
||||||
<gresource prefix="/org/gnome/shell/dbus-interfaces">
|
<gresource prefix="/org/gnome/shell/dbus-interfaces">
|
||||||
|
<file preprocess="xml-stripblanks">net.hadess.PowerProfiles.xml</file>
|
||||||
<file preprocess="xml-stripblanks">net.hadess.SensorProxy.xml</file>
|
<file preprocess="xml-stripblanks">net.hadess.SensorProxy.xml</file>
|
||||||
<file preprocess="xml-stripblanks">net.reactivated.Fprint.Device.xml</file>
|
<file preprocess="xml-stripblanks">net.reactivated.Fprint.Device.xml</file>
|
||||||
<file preprocess="xml-stripblanks">net.reactivated.Fprint.Manager.xml</file>
|
<file preprocess="xml-stripblanks">net.reactivated.Fprint.Manager.xml</file>
|
||||||
|
@ -133,6 +133,7 @@
|
|||||||
<file>ui/status/nightLight.js</file>
|
<file>ui/status/nightLight.js</file>
|
||||||
<file>ui/status/network.js</file>
|
<file>ui/status/network.js</file>
|
||||||
<file>ui/status/power.js</file>
|
<file>ui/status/power.js</file>
|
||||||
|
<file>ui/status/powerProfiles.js</file>
|
||||||
<file>ui/status/rfkill.js</file>
|
<file>ui/status/rfkill.js</file>
|
||||||
<file>ui/status/volume.js</file>
|
<file>ui/status/volume.js</file>
|
||||||
<file>ui/status/bluetooth.js</file>
|
<file>ui/status/bluetooth.js</file>
|
||||||
|
@ -684,6 +684,7 @@ class AggregateMenu extends PanelMenu.Button {
|
|||||||
|
|
||||||
this._remoteAccess = new imports.ui.status.remoteAccess.RemoteAccessApplet();
|
this._remoteAccess = new imports.ui.status.remoteAccess.RemoteAccessApplet();
|
||||||
this._power = new imports.ui.status.power.Indicator();
|
this._power = new imports.ui.status.power.Indicator();
|
||||||
|
this._powerProfiles = new imports.ui.status.powerProfiles.Indicator();
|
||||||
this._rfkill = new imports.ui.status.rfkill.Indicator();
|
this._rfkill = new imports.ui.status.rfkill.Indicator();
|
||||||
this._volume = new imports.ui.status.volume.Indicator();
|
this._volume = new imports.ui.status.volume.Indicator();
|
||||||
this._brightness = new imports.ui.status.brightness.Indicator();
|
this._brightness = new imports.ui.status.brightness.Indicator();
|
||||||
@ -703,6 +704,7 @@ class AggregateMenu extends PanelMenu.Button {
|
|||||||
this._indicators.add_child(this._rfkill);
|
this._indicators.add_child(this._rfkill);
|
||||||
this._indicators.add_child(this._volume);
|
this._indicators.add_child(this._volume);
|
||||||
this._indicators.add_child(this._power);
|
this._indicators.add_child(this._power);
|
||||||
|
this._indicators.add_child(this._powerProfiles);
|
||||||
|
|
||||||
this.menu.addMenuItem(this._volume.menu);
|
this.menu.addMenuItem(this._volume.menu);
|
||||||
this.menu.addMenuItem(this._brightness.menu);
|
this.menu.addMenuItem(this._brightness.menu);
|
||||||
@ -717,6 +719,7 @@ class AggregateMenu extends PanelMenu.Button {
|
|||||||
this.menu.addMenuItem(this._location.menu);
|
this.menu.addMenuItem(this._location.menu);
|
||||||
this.menu.addMenuItem(this._rfkill.menu);
|
this.menu.addMenuItem(this._rfkill.menu);
|
||||||
this.menu.addMenuItem(this._power.menu);
|
this.menu.addMenuItem(this._power.menu);
|
||||||
|
this.menu.addMenuItem(this._powerProfiles.menu);
|
||||||
this.menu.addMenuItem(this._nightLight.menu);
|
this.menu.addMenuItem(this._nightLight.menu);
|
||||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||||
this.menu.addMenuItem(this._system.menu);
|
this.menu.addMenuItem(this._system.menu);
|
||||||
@ -724,6 +727,7 @@ class AggregateMenu extends PanelMenu.Button {
|
|||||||
menuLayout.addSizeChild(this._location.menu.actor);
|
menuLayout.addSizeChild(this._location.menu.actor);
|
||||||
menuLayout.addSizeChild(this._rfkill.menu.actor);
|
menuLayout.addSizeChild(this._rfkill.menu.actor);
|
||||||
menuLayout.addSizeChild(this._power.menu.actor);
|
menuLayout.addSizeChild(this._power.menu.actor);
|
||||||
|
menuLayout.addSizeChild(this._powerProfiles.menu.actor);
|
||||||
menuLayout.addSizeChild(this._system.menu.actor);
|
menuLayout.addSizeChild(this._system.menu.actor);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
111
js/ui/status/powerProfiles.js
Normal file
111
js/ui/status/powerProfiles.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
/* exported Indicator */
|
||||||
|
|
||||||
|
const { Gio, GObject } = imports.gi;
|
||||||
|
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
|
|
||||||
|
const { loadInterfaceXML } = imports.misc.fileUtils;
|
||||||
|
|
||||||
|
const BUS_NAME = 'net.hadess.PowerProfiles';
|
||||||
|
const OBJECT_PATH = '/net/hadess/PowerProfiles';
|
||||||
|
|
||||||
|
const PowerProfilesIface = loadInterfaceXML('net.hadess.PowerProfiles');
|
||||||
|
const PowerProfilesProxy = Gio.DBusProxy.makeProxyWrapper(PowerProfilesIface);
|
||||||
|
|
||||||
|
const PROFILE_LABELS = {
|
||||||
|
'performance': _('Performance Mode'),
|
||||||
|
'balanced': _('Balanced Power'),
|
||||||
|
'power-saver': _('Power Saver'),
|
||||||
|
};
|
||||||
|
const PROFILE_ICONS = {
|
||||||
|
'performance': 'power-profile-performance-symbolic',
|
||||||
|
'balanced': 'power-profile-balanced-symbolic',
|
||||||
|
'power-saver': 'power-profile-power-saver-symbolic',
|
||||||
|
};
|
||||||
|
|
||||||
|
var Indicator = GObject.registerClass(
|
||||||
|
class Indicator extends PanelMenu.SystemIndicator {
|
||||||
|
_init() {
|
||||||
|
super._init();
|
||||||
|
|
||||||
|
this._profileItems = new Map();
|
||||||
|
this._updateProfiles = true;
|
||||||
|
|
||||||
|
this._proxy = new PowerProfilesProxy(Gio.DBus.system, BUS_NAME, OBJECT_PATH,
|
||||||
|
(proxy, error) => {
|
||||||
|
if (error) {
|
||||||
|
log(error.message);
|
||||||
|
} else {
|
||||||
|
this._proxy.connect('g-properties-changed',
|
||||||
|
(p, properties) => {
|
||||||
|
const propertyNames = properties.deep_unpack();
|
||||||
|
this._updateProfiles = 'Profiles' in propertyNames;
|
||||||
|
this._sync();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._sync();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._item = new PopupMenu.PopupSubMenuMenuItem('', true);
|
||||||
|
|
||||||
|
this._profileSection = new PopupMenu.PopupMenuSection();
|
||||||
|
this._item.menu.addMenuItem(this._profileSection);
|
||||||
|
this._item.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||||
|
this._item.menu.addSettingsAction(_('Power Settings'),
|
||||||
|
'gnome-power-panel.desktop');
|
||||||
|
this.menu.addMenuItem(this._item);
|
||||||
|
|
||||||
|
Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
|
||||||
|
this._sessionUpdated();
|
||||||
|
this._sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
_sessionUpdated() {
|
||||||
|
const sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
|
||||||
|
this.menu.setSensitive(sensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sync() {
|
||||||
|
this._item.visible = this._proxy.g_name_owner !== null;
|
||||||
|
|
||||||
|
if (!this._item.visible)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this._updateProfiles) {
|
||||||
|
this._profileSection.removeAll();
|
||||||
|
this._profileItems.clear();
|
||||||
|
|
||||||
|
const profiles = this._proxy.Profiles
|
||||||
|
.map(p => p.Profile.unpack())
|
||||||
|
.reverse();
|
||||||
|
for (const profile of profiles) {
|
||||||
|
const label = PROFILE_LABELS[profile];
|
||||||
|
if (!label)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const item = new PopupMenu.PopupMenuItem(label);
|
||||||
|
item.connect('activate',
|
||||||
|
() => (this._proxy.ActiveProfile = profile));
|
||||||
|
this._profileItems.set(profile, item);
|
||||||
|
this._profileSection.addMenuItem(item);
|
||||||
|
}
|
||||||
|
this._updateProfiles = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [profile, item] of this._profileItems) {
|
||||||
|
item.setOrnament(profile === this._proxy.ActiveProfile
|
||||||
|
? PopupMenu.Ornament.DOT
|
||||||
|
: PopupMenu.Ornament.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
const perfItem = this._profileItems.get('performance');
|
||||||
|
if (perfItem)
|
||||||
|
perfItem.sensitive = this._proxy.PerformanceInhibited === '';
|
||||||
|
|
||||||
|
this._item.label.text = PROFILE_LABELS[this._proxy.ActiveProfile];
|
||||||
|
this._item.icon.icon_name = PROFILE_ICONS[this._proxy.ActiveProfile];
|
||||||
|
}
|
||||||
|
});
|
@ -62,6 +62,7 @@ js/ui/status/location.js
|
|||||||
js/ui/status/network.js
|
js/ui/status/network.js
|
||||||
js/ui/status/nightLight.js
|
js/ui/status/nightLight.js
|
||||||
js/ui/status/power.js
|
js/ui/status/power.js
|
||||||
|
js/ui/status/powerProfiles.js
|
||||||
js/ui/status/remoteAccess.js
|
js/ui/status/remoteAccess.js
|
||||||
js/ui/status/rfkill.js
|
js/ui/status/rfkill.js
|
||||||
js/ui/status/system.js
|
js/ui/status/system.js
|
||||||
|
Loading…
Reference in New Issue
Block a user