Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ var VitalsMenuButton = GObject.registerClass({
this._warnings = [];
this._isDestroyed = false;
this._sensorMenuItems = {};
this._availableSensors = {};
this._availableSensorsSig = '';
this._hotLabels = {};
this._hotItems = {};
this._groups = {};
Expand Down Expand Up @@ -343,6 +345,7 @@ var VitalsMenuButton = GObject.registerClass({
'changed::position-in-panel', this._positionInPanelChanged.bind(this),
'changed::menu-centered', this._positionInPanelChanged.bind(this),
'changed::icon-style', this._iconStyleChanged.bind(this),
'changed::hot-sensors', this._hotSensorsChanged.bind(this),
this);

let settings = [ 'use-higher-precision', 'alphabetize', 'hide-zeros',
Expand Down Expand Up @@ -498,6 +501,40 @@ var VitalsMenuButton = GObject.registerClass({
));
}

_hotSensorsChanged() {
// 'hot-sensors' changed. When the change came from the dropdown the
// panel already matches, so only redraw when it actually differs from
// what is currently shown (e.g. the change came from the preferences
// sensor selector, in this same or another process). Comparing sets is
// timing-independent, unlike a flag (the 'changed' signal is async).
let desired = this._settings.get_strv('hot-sensors');
let current = Object.keys(this._hotItems);
if (desired.length === current.length && desired.every(k => current.includes(k)))
return;

this._redrawMenu();
}

// publishes the list of sensors discovered so far into a setting the
// preferences window (a separate process) can read to build its selector
_publishAvailableSensors() {
let list = [];
for (let key in this._availableSensors) {
let sensor = this._availableSensors[key];
list.push({ key: key, label: sensor.label, group: sensor.group });
}

// stable ordering so the signature only changes when the set changes
list.sort((a, b) =>
a.group.localeCompare(b.group) ||
a.label.localeCompare(b.label, undefined, { numeric: true, sensitivity: 'base' }));

let json = JSON.stringify(list);
if (json === this._availableSensorsSig) return;
this._availableSensorsSig = json;
this._settings.set_string('available-sensors', json);
}

_initializeTimer() {
// used to query sensors and update display
let update_time = this._settings.get_int('update-time');
Expand Down Expand Up @@ -699,6 +736,10 @@ var VitalsMenuButton = GObject.registerClass({
let icon = (split.length == 2)?'icon-' + split[1]:'icon';
let gicon = Gio.icon_new_for_string(this._sensorIconPath(type, icon));

// remember this sensor so the preferences selector can list it
this._availableSensors[key] = { label: sensor.label, group: type };
this._publishAvailableSensors();

let item = new MenuItem.MenuItem(gicon, key, sensor.label, sensor.value, this._hotLabels[key]);
item.connect('toggle', (self) => {
let hotSensors = this._settings.get_strv('hot-sensors');
Expand Down
131 changes: 130 additions & 1 deletion prefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,141 @@ export default class VitalsPrefs extends ExtensionPreferences {
let settings = new Settings(this);
let widget = settings.widget;

const page = new Adw.PreferencesPage();
const page = new Adw.PreferencesPage({
title: _('General'),
icon_name: 'preferences-system-symbolic'
});
const group = new Adw.PreferencesGroup({});
group.add(widget);
page.add(group);
window.add(page);
window.set_default_size(widget.width, widget.height);
widget.show();

// second page: pick exactly which sensors show in the top bar
this._buildPanelSensorsPage(window, window._settings);
}

// Builds a page that lists every sensor the extension has discovered,
// grouped by category, with a switch per sensor. Toggling a switch adds or
// removes that sensor from 'hot-sensors' (the sensors shown in the panel).
_buildPanelSensorsPage(window, settings) {
const page = new Adw.PreferencesPage({
title: _('Panel'),
icon_name: 'utilities-system-monitor-symbolic'
});
window.add(page);

// groups we add dynamically, so we can rebuild when the sensor list changes
let dynamicGroups = [];
let keyToSwitch = {};
// guards against feedback loops between our writes and the change signals
let updating = false;

const rebuild = () => {
for (let g of dynamicGroups) page.remove(g);
dynamicGroups = [];
keyToSwitch = {};

let available = [];
try {
available = JSON.parse(settings.get_string('available-sensors'));
} catch (e) {
available = [];
}

if (!available.length) {
let empty = new Adw.PreferencesGroup({
title: _('Sensors in the panel'),
description: _('Open the Vitals menu in the top bar once so your sensors can be detected. They will then appear here to be toggled on or off.')
});
page.add(empty);
dynamicGroups.push(empty);
return;
}

let intro = new Adw.PreferencesGroup({
title: _('Sensors in the panel'),
description: _('Choose which sensors are shown in the top bar.')
});
page.add(intro);
dynamicGroups.push(intro);

// bucket sensors by their category
let byGroup = {};
for (let sensor of available) {
if (!byGroup[sensor.group]) byGroup[sensor.group] = [];
byGroup[sensor.group].push(sensor);
}

let hot = settings.get_strv('hot-sensors');

for (let groupName of Object.keys(byGroup).sort()) {
let pg = new Adw.PreferencesGroup({ title: this._groupDisplayName(groupName) });

for (let sensor of byGroup[groupName]) {
let row = new Adw.SwitchRow({
title: sensor.label,
active: hot.indexOf(sensor.key) >= 0
});
keyToSwitch[sensor.key] = row;

row.connect('notify::active', () => {
if (updating) return;

let list = settings.get_strv('hot-sensors');
let idx = list.indexOf(sensor.key);
if (row.active && idx < 0) list.push(sensor.key);
else if (!row.active && idx >= 0) list.splice(idx, 1);
else return;

// same invariant the panel uses: show the generic
// placeholder icon only when no real sensor is selected
let defIdx = list.indexOf('_default_icon_');
if (defIdx >= 0) list.splice(defIdx, 1);
if (list.length === 0) list.push('_default_icon_');

updating = true;
settings.set_strv('hot-sensors', list);
updating = false;
});

pg.add(row);
}

page.add(pg);
dynamicGroups.push(pg);
}
};

rebuild();

// extension discovered new sensors (or the app just started) -> rebuild
let availId = settings.connect('changed::available-sensors', () => rebuild());

// hot-sensors changed elsewhere (e.g. the dropdown) -> reflect in switches
let hotId = settings.connect('changed::hot-sensors', () => {
if (updating) return;
let hot = settings.get_strv('hot-sensors');
updating = true;
for (let key in keyToSwitch)
keyToSwitch[key].active = hot.indexOf(key) >= 0;
updating = false;
});

// avoid leaking the signal handlers when the window is closed
window.connect('close-request', () => {
settings.disconnect(availId);
settings.disconnect(hotId);
return false;
});
}

_groupDisplayName(group) {
if (group.startsWith('gpu')) {
let n = group.split('#')[1];
return n ? _('Graphics') + ' ' + n : _('Graphics');
}
return group.charAt(0).toUpperCase() + group.slice(1);
}
}
5 changes: 5 additions & 0 deletions schemas/org.gnome.shell.extensions.vitals.gschema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
<summary>Sensors to show in panel</summary>
<description>List of sensors to be shown in the panel</description>
</key>
<key name="available-sensors" type="s">
<default>"[]"</default>
<summary>Discovered sensors</summary>
<description>JSON list of sensors discovered by the extension, published so the preferences window can offer a selector for which sensors appear in the panel</description>
</key>
<key type="i" name="update-time">
<default>5</default>
<summary>Seconds between updates</summary>
Expand Down