518 lines
19 KiB
JavaScript
518 lines
19 KiB
JavaScript
|
const St = imports.gi.St;
|
||
|
const Lang = imports.lang;
|
||
|
const PanelMenu = imports.ui.panelMenu;
|
||
|
const PopupMenu = imports.ui.popupMenu;
|
||
|
const Main = imports.ui.main;
|
||
|
const Util = imports.misc.util;
|
||
|
const Mainloop = imports.mainloop;
|
||
|
const Clutter = imports.gi.Clutter;
|
||
|
const Gio = imports.gi.Gio;
|
||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||
|
const Me = ExtensionUtils.getCurrentExtension();
|
||
|
Me.imports.helpers.polyfills;
|
||
|
const Sensors = Me.imports.sensors;
|
||
|
const Convenience = Me.imports.helpers.convenience;
|
||
|
const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']);
|
||
|
const _ = Gettext.gettext;
|
||
|
const MessageTray = imports.ui.messageTray;
|
||
|
const Values = Me.imports.values;
|
||
|
const Config = imports.misc.config;
|
||
|
|
||
|
let MenuItem, vitalsMenu;
|
||
|
|
||
|
const VitalsMenuButton = new Lang.Class({
|
||
|
Name: 'VitalsMenuButton',
|
||
|
Extends: PanelMenu.Button,
|
||
|
|
||
|
_init: function() {
|
||
|
this.parent(St.Align.START);
|
||
|
|
||
|
this._settings = Convenience.getSettings();
|
||
|
|
||
|
this._sensorIcons = {
|
||
|
'temperature' : { 'icon': 'temperature-symbolic.svg',
|
||
|
'alphabetize': true },
|
||
|
'voltage' : { 'icon': 'voltage-symbolic.svg',
|
||
|
'alphabetize': true },
|
||
|
'fan' : { 'icon': 'fan-symbolic.svg',
|
||
|
'alphabetize': true },
|
||
|
'memory' : { 'icon': 'memory-symbolic.svg',
|
||
|
'alphabetize': true },
|
||
|
'processor' : { 'icon': 'cpu-symbolic.svg',
|
||
|
'alphabetize': true },
|
||
|
'system' : { 'icon': 'system-symbolic.svg',
|
||
|
'alphabetize': true },
|
||
|
'network' : { 'icon': 'network-symbolic.svg',
|
||
|
'alphabetize': true,
|
||
|
'icon-download': 'network-download-symbolic.svg',
|
||
|
'icon-upload': 'network-upload-symbolic.svg' },
|
||
|
'storage' : { 'icon': 'storage-symbolic.svg',
|
||
|
'alphabetize': true },
|
||
|
'battery' : { 'icon': 'battery-symbolic.svg',
|
||
|
'alphabetize': true }
|
||
|
}
|
||
|
|
||
|
this._warnings = [];
|
||
|
this._sensorMenuItems = {};
|
||
|
this._hotLabels = {};
|
||
|
this._hotIcons = {};
|
||
|
this._groups = {};
|
||
|
|
||
|
this._update_time = this._settings.get_int('update-time');
|
||
|
|
||
|
this._sensors = new Sensors.Sensors(this._settings, this._sensorIcons);
|
||
|
this._values = new Values.Values(this._settings, this._sensorIcons);
|
||
|
this._menuLayout = new St.BoxLayout({
|
||
|
vertical: false,
|
||
|
clip_to_allocation: true,
|
||
|
x_align: Clutter.ActorAlign.START,
|
||
|
y_align: Clutter.ActorAlign.CENTER,
|
||
|
reactive: true,
|
||
|
x_expand:true,
|
||
|
pack_start: false
|
||
|
});
|
||
|
|
||
|
this._drawMenu();
|
||
|
|
||
|
if (ExtensionUtils.versionCheck(['3.18', '3.20', '3.22', '3.24', '3.26', '3.28', '3.30', '3.32'], Config.PACKAGE_VERSION)) {
|
||
|
this.actor.add_actor(this._menuLayout);
|
||
|
} else {
|
||
|
this.add_actor(this._menuLayout);
|
||
|
}
|
||
|
|
||
|
this._settingChangedSignals = [];
|
||
|
this._addSettingChangedSignal('update-time', Lang.bind(this, this._updateTimeChanged));
|
||
|
this._addSettingChangedSignal('position-in-panel', Lang.bind(this, this._positionInPanelChanged));
|
||
|
this._addSettingChangedSignal('use-higher-precision', Lang.bind(this, this._higherPrecisionChanged));
|
||
|
|
||
|
let settings = [ 'alphabetize', 'include-public-ip', 'hide-zeros', 'unit', 'network-speed-format' ];
|
||
|
for (let setting of Object.values(settings))
|
||
|
this._addSettingChangedSignal(setting, Lang.bind(this, this._redrawMenu));
|
||
|
|
||
|
// add signals for show- preference based categories
|
||
|
for (let sensor in this._sensorIcons)
|
||
|
this._addSettingChangedSignal('show-' + sensor, Lang.bind(this, this._showHideSensorsChanged));
|
||
|
|
||
|
this._initializeMenu();
|
||
|
this._initializeTimer();
|
||
|
},
|
||
|
|
||
|
_initializeMenu: function() {
|
||
|
// display sensor categories
|
||
|
for (let sensor in this._sensorIcons) {
|
||
|
// groups associated sensors under accordion menu
|
||
|
if (typeof this._groups[sensor] != 'undefined') continue;
|
||
|
|
||
|
this._groups[sensor] = new PopupMenu.PopupSubMenuMenuItem(_(this._ucFirst(sensor)), true);
|
||
|
this._groups[sensor].icon.gicon = Gio.icon_new_for_string(Me.path + '/icons/' + this._sensorIcons[sensor]['icon']);
|
||
|
|
||
|
// hide menu items that user has requested to not include
|
||
|
if (!this._settings.get_boolean('show-' + sensor))
|
||
|
this._groups[sensor].actor.hide(); // 3.34?
|
||
|
|
||
|
if (!this._groups[sensor].status) {
|
||
|
this._groups[sensor].status = this._defaultLabel();
|
||
|
this._groups[sensor].actor.insert_child_at_index(this._groups[sensor].status, 4); // 3.34?
|
||
|
this._groups[sensor].status.text = 'No Data';
|
||
|
}
|
||
|
|
||
|
this.menu.addMenuItem(this._groups[sensor]);
|
||
|
}
|
||
|
|
||
|
// add separator
|
||
|
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||
|
|
||
|
// Gnome 3.36 straight up removed round button support. No standard deprecation process. What the heck??
|
||
|
if (ExtensionUtils.versionCheck(['3.18', '3.20', '3.22', '3.24', '3.26', '3.28', '3.30', '3.32'], Config.PACKAGE_VERSION)) {
|
||
|
let panelSystem = Main.panel.statusArea.aggregateMenu._system;
|
||
|
let item = new PopupMenu.PopupBaseMenuItem({
|
||
|
reactive: false,
|
||
|
style_class: 'vitals-menu-button-container'
|
||
|
});
|
||
|
|
||
|
// round preferences button
|
||
|
let prefsButton = panelSystem._createActionButton('preferences-system-symbolic', _("Preferences"));
|
||
|
prefsButton.connect('clicked', function() {
|
||
|
Util.spawn(["gnome-shell-extension-prefs", Me.metadata.uuid]);
|
||
|
});
|
||
|
item.actor.add(prefsButton, { expand: true, x_fill: false }); // 3.34?
|
||
|
|
||
|
// round monitor button
|
||
|
let monitorButton = panelSystem._createActionButton('utilities-system-monitor-symbolic', _("System Monitor"));
|
||
|
monitorButton.connect('clicked', function() {
|
||
|
Util.spawn(["gnome-system-monitor"]);
|
||
|
});
|
||
|
item.actor.add(monitorButton, { expand: true, x_fill: false }); // 3.34?
|
||
|
|
||
|
// round refresh button
|
||
|
let refreshButton = panelSystem._createActionButton('view-refresh-symbolic', _("Refresh"));
|
||
|
refreshButton.connect('clicked', Lang.bind(this, function(self) {
|
||
|
this._sensors.resetHistory();
|
||
|
this._values.resetHistory();
|
||
|
this._updateTimeChanged();
|
||
|
}));
|
||
|
item.actor.add(refreshButton, { expand: true, x_fill: false }); // 3.34?
|
||
|
|
||
|
// add buttons
|
||
|
this.menu.addMenuItem(item);
|
||
|
} else {
|
||
|
// preferences option
|
||
|
let preferences = new PopupMenu.PopupBaseMenuItem();
|
||
|
preferences.actor.add(new St.Label({ text: _("Preferences") }), { expand: false, x_fill: false });
|
||
|
preferences.connect('activate', function () {
|
||
|
Util.spawn(["gnome-shell-extension-prefs", Me.metadata.uuid]);
|
||
|
});
|
||
|
this.menu.addMenuItem(preferences);
|
||
|
|
||
|
// monitor option
|
||
|
let monitor = new PopupMenu.PopupBaseMenuItem();
|
||
|
monitor.actor.add(new St.Label({ text: _("System Monitor") }), { expand: false, x_fill: false });
|
||
|
monitor.connect('activate', function () {
|
||
|
Util.spawn(["gnome-system-monitor"]);
|
||
|
});
|
||
|
this.menu.addMenuItem(monitor);
|
||
|
|
||
|
// refresh option
|
||
|
let refresh = new PopupMenu.PopupBaseMenuItem();
|
||
|
refresh.actor.add(new St.Label({ text: _("Refresh") }), { expand: false, x_fill: false });
|
||
|
refresh.connect('activate', function () {
|
||
|
this._sensors.resetHistory();
|
||
|
this._values.resetHistory();
|
||
|
this._updateTimeChanged();
|
||
|
});
|
||
|
this.menu.addMenuItem(refresh);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_removeMissingHotSensors: function(hotSensors) {
|
||
|
for (let i = hotSensors.length - 1; i >= 0; i--) {
|
||
|
let sensor = hotSensors[i];
|
||
|
|
||
|
// make sure default icon (if any) stays visible
|
||
|
if (sensor == '_default_icon_') continue;
|
||
|
|
||
|
if (!this._sensorMenuItems[sensor]) {
|
||
|
hotSensors.splice(i, 1);
|
||
|
this._removeHotLabel(sensor);
|
||
|
this._removeHotIcon(sensor);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return hotSensors;
|
||
|
},
|
||
|
|
||
|
_saveHotSensors: function(hotSensors) {
|
||
|
this._settings.set_strv('hot-sensors', hotSensors.filter(
|
||
|
function(item, pos) {
|
||
|
return hotSensors.indexOf(item) == pos;
|
||
|
}
|
||
|
));
|
||
|
},
|
||
|
|
||
|
_initializeTimer: function() {
|
||
|
// start off with fresh sensors
|
||
|
this._querySensors();
|
||
|
|
||
|
// used to query sensors and update display
|
||
|
this._refreshTimeoutId = Mainloop.timeout_add_seconds(this._update_time, Lang.bind(this, function() {
|
||
|
this._querySensors();
|
||
|
|
||
|
// keep the timer running
|
||
|
return true;
|
||
|
}));
|
||
|
},
|
||
|
|
||
|
_createHotItem: function(key, gicon, value) {
|
||
|
let icon = this._defaultIcon(gicon);
|
||
|
this._hotIcons[key] = icon;
|
||
|
this._menuLayout.add(icon, { expand: true, x_fill: false });
|
||
|
|
||
|
// don't add a label when no sensors are in the panel
|
||
|
if (key == '_default_icon_') return;
|
||
|
|
||
|
let label = new St.Label({
|
||
|
text: (value)?value:'\u2026', // ...
|
||
|
y_expand: true,
|
||
|
y_align: Clutter.ActorAlign.CENTER
|
||
|
});
|
||
|
|
||
|
this._hotLabels[key] = label;
|
||
|
this._menuLayout.add(label, { expand: true, x_fill: false });
|
||
|
},
|
||
|
|
||
|
_higherPrecisionChanged: function() {
|
||
|
this._sensors.resetHistory();
|
||
|
this._values.resetHistory();
|
||
|
this._querySensors();
|
||
|
},
|
||
|
|
||
|
_showHideSensorsChanged: function(self, sensor) {
|
||
|
this._groups[sensor.substr(5)].visible = this._settings.get_boolean(sensor);
|
||
|
},
|
||
|
|
||
|
_positionInPanelChanged: function() {
|
||
|
this.container.get_parent().remove_actor(this.container);
|
||
|
|
||
|
// small HACK with private boxes :)
|
||
|
let boxes = {
|
||
|
left: Main.panel._leftBox,
|
||
|
center: Main.panel._centerBox,
|
||
|
right: Main.panel._rightBox
|
||
|
};
|
||
|
|
||
|
let p = this.positionInPanel;
|
||
|
boxes[p].insert_child_at_index(this.container, p == 'right' ? 0 : -1)
|
||
|
},
|
||
|
|
||
|
_removeHotLabel: function(key) {
|
||
|
if (typeof this._hotLabels[key] != 'undefined') {
|
||
|
let label = this._hotLabels[key];
|
||
|
delete this._hotLabels[key];
|
||
|
// make sure set_label is not called on non existant actor
|
||
|
label.destroy();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_removeHotLabels: function() {
|
||
|
for (let key in this._hotLabels)
|
||
|
this._removeHotLabel(key);
|
||
|
},
|
||
|
|
||
|
_removeHotIcon: function(key) {
|
||
|
if (typeof this._hotIcons[key] != 'undefined') {
|
||
|
this._hotIcons[key].destroy();
|
||
|
delete this._hotIcons[key];
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_removeHotIcons: function() {
|
||
|
for (let key in this._hotIcons)
|
||
|
this._removeHotIcon(key);
|
||
|
},
|
||
|
|
||
|
_redrawMenu: function() {
|
||
|
this._removeHotIcons();
|
||
|
this._removeHotLabels();
|
||
|
|
||
|
for (let key in this._sensorMenuItems) {
|
||
|
if (key.includes('-group')) continue;
|
||
|
this._sensorMenuItems[key].destroy();
|
||
|
delete this._sensorMenuItems[key];
|
||
|
}
|
||
|
|
||
|
this._drawMenu();
|
||
|
this._sensors.resetHistory();
|
||
|
this._values.resetHistory();
|
||
|
this._querySensors();
|
||
|
},
|
||
|
|
||
|
_drawMenu: function() {
|
||
|
// grab list of selected menubar icons
|
||
|
let hotSensors = this._settings.get_strv('hot-sensors');
|
||
|
for (let key of Object.values(hotSensors))
|
||
|
this._createHotItem(key);
|
||
|
},
|
||
|
|
||
|
_updateTimeChanged: function() {
|
||
|
this._update_time = this._settings.get_int('update-time');
|
||
|
this._sensors.update_time = this._update_time;
|
||
|
|
||
|
// invalidate and reinitialize timer
|
||
|
Mainloop.source_remove(this._refreshTimeoutId);
|
||
|
|
||
|
this._initializeTimer();
|
||
|
},
|
||
|
|
||
|
_addSettingChangedSignal: function(key, callback) {
|
||
|
this._settingChangedSignals.push(this._settings.connect('changed::' + key, callback));
|
||
|
},
|
||
|
|
||
|
_updateDisplay: function(label, value, type, key) {
|
||
|
//global.log('...label=' + label, 'value=' + value, 'type=' + type, 'key=' + key);
|
||
|
|
||
|
// update sensor value in menubar
|
||
|
if (this._hotLabels[key])
|
||
|
this._hotLabels[key].set_text(value);
|
||
|
|
||
|
// have we added this sensor before?
|
||
|
let item = this._sensorMenuItems[key];
|
||
|
if (item) {
|
||
|
// update sensor value in the group
|
||
|
item.value = value;
|
||
|
} else if (type.includes('-group')) {
|
||
|
// update text next to group header
|
||
|
let group = type.split('-')[0];
|
||
|
if (this._groups[group]) {
|
||
|
this._groups[group].status.text = value;
|
||
|
this._sensorMenuItems[type] = this._groups[group];
|
||
|
}
|
||
|
} else {
|
||
|
let sensor = { 'label': label, 'value': value, 'type': type }
|
||
|
this._appendMenuItem(sensor, key);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_appendMenuItem: function(sensor, key) {
|
||
|
let split = sensor.type.split('-');
|
||
|
let type = split[0];
|
||
|
let icon = (typeof split[1] != 'undefined')?'icon-' + split[1]:'icon';
|
||
|
let gicon = Gio.icon_new_for_string(Me.path + '/icons/' + this._sensorIcons[type][icon]);
|
||
|
|
||
|
let item = new MenuItem.MenuItem(gicon, key, sensor.label, sensor.value);
|
||
|
item.connect('activate', Lang.bind(this, function(self) {
|
||
|
let hotSensors = this._settings.get_strv('hot-sensors');
|
||
|
|
||
|
if (self.checked) {
|
||
|
self.checked = false;
|
||
|
|
||
|
// remove selected sensor from panel
|
||
|
hotSensors.splice(hotSensors.indexOf(self.key), 1);
|
||
|
this._removeHotLabel(self.key);
|
||
|
this._removeHotIcon(self.key);
|
||
|
} else {
|
||
|
self.checked = true;
|
||
|
|
||
|
// add selected sensor to panel
|
||
|
hotSensors.push(self.key);
|
||
|
this._createHotItem(self.key, self.gicon, self.value);
|
||
|
}
|
||
|
|
||
|
if (hotSensors.length <= 0) {
|
||
|
// add generic icon to panel when no sensors are selected
|
||
|
hotSensors.push('_default_icon_');
|
||
|
this._createHotItem('_default_icon_');
|
||
|
} else {
|
||
|
let defIconPos = hotSensors.indexOf('_default_icon_');
|
||
|
if (defIconPos >= 0) {
|
||
|
// remove generic icon from panel when sensors are selected
|
||
|
hotSensors.splice(defIconPos, 1);
|
||
|
this._removeHotIcon('_default_icon_');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// removes any sensors that may not currently be available
|
||
|
hotSensors = this._removeMissingHotSensors(hotSensors);
|
||
|
|
||
|
// this code is called asynchronously - make sure to save it for next round
|
||
|
this._saveHotSensors(hotSensors);
|
||
|
|
||
|
//this.emit('activate', self);
|
||
|
//self.disconnect();
|
||
|
return true;
|
||
|
}));
|
||
|
|
||
|
if (this._hotLabels[key]) {
|
||
|
item.checked = true;
|
||
|
if (this._hotIcons[key])
|
||
|
this._hotIcons[key].gicon = item.gicon;
|
||
|
}
|
||
|
|
||
|
this._sensorMenuItems[key] = item;
|
||
|
let i = Object.keys(this._sensorMenuItems[key]).length;
|
||
|
|
||
|
// alphabetize the sensors for these categories
|
||
|
if (this._sensorIcons[type]['alphabetize'] && this._settings.get_boolean('alphabetize')) {
|
||
|
let menuItems = this._groups[type].menu._getMenuItems();
|
||
|
for (i = 0; i < menuItems.length; i++)
|
||
|
// use natural sort order for system load, etc
|
||
|
if (typeof menuItems[i] != 'undefined' && typeof menuItems[i].key != 'undefined' && menuItems[i].key.localeCompare(key, undefined, { numeric: true, sensitivity: 'base' }) > 0)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
this._groups[type].menu.addMenuItem(item, i);
|
||
|
},
|
||
|
|
||
|
_defaultLabel: function() {
|
||
|
return new St.Label({
|
||
|
style_class: 'vitals-status-menu-item',
|
||
|
y_expand: true,
|
||
|
y_align: Clutter.ActorAlign.CENTER
|
||
|
});
|
||
|
},
|
||
|
|
||
|
_defaultIcon: function(gicon) {
|
||
|
let icon = new St.Icon({
|
||
|
icon_name: "utilities-system-monitor-symbolic",
|
||
|
style_class: 'system-status-icon',
|
||
|
reactive: true
|
||
|
});
|
||
|
|
||
|
if (gicon) icon.gicon = gicon;
|
||
|
return icon;
|
||
|
},
|
||
|
|
||
|
_ucFirst: function(string) {
|
||
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||
|
},
|
||
|
|
||
|
get positionInPanel() {
|
||
|
let positions = [ 'left', 'center', 'right' ];
|
||
|
return positions[this._settings.get_int('position-in-panel')];
|
||
|
},
|
||
|
|
||
|
_querySensors: function() {
|
||
|
this._sensors.query(Lang.bind(this, function(label, value, type, format) {
|
||
|
let key = '_' + type.split('-')[0] + '_' + label.replace(' ', '_').toLowerCase() + '_';
|
||
|
|
||
|
/*
|
||
|
if (key == '_temperature_package_id 0_' && value >= 50000)
|
||
|
this._warnings.push(label + ' is ' + value);
|
||
|
|
||
|
if (key == '_system_load_1m_' && value >= 2)
|
||
|
this._warnings.push(label + ' is ' + value);
|
||
|
*/
|
||
|
|
||
|
let items = this._values.returnIfDifferent(label, value, type, format, key);
|
||
|
for (let item of Object.values(items))
|
||
|
this._updateDisplay(_(item[0]), item[1], item[2], item[3]);
|
||
|
}));
|
||
|
|
||
|
if (this._warnings.length > 0) {
|
||
|
this._notify("Vitals", this._warnings.join("\n"), 'folder-symbolic');
|
||
|
this._warnings = [];
|
||
|
}
|
||
|
},
|
||
|
|
||
|
_notify: function(msg, details, icon) {
|
||
|
let source = new MessageTray.Source("MyApp Information", icon);
|
||
|
Main.messageTray.add(source);
|
||
|
let notification = new MessageTray.Notification(source, msg, details);
|
||
|
notification.setTransient(true);
|
||
|
source.notify(notification);
|
||
|
},
|
||
|
|
||
|
destroy: function() {
|
||
|
Mainloop.source_remove(this._refreshTimeoutId);
|
||
|
|
||
|
for (let signal of Object.values(this._settingChangedSignals))
|
||
|
this._settings.disconnect(signal);
|
||
|
|
||
|
// call parent
|
||
|
this.parent();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
function init() {
|
||
|
Convenience.initTranslations();
|
||
|
|
||
|
// load correct menuItem depending on Gnome version
|
||
|
if (ExtensionUtils.versionCheck(['3.18', '3.20', '3.22', '3.24', '3.26', '3.28'], Config.PACKAGE_VERSION)) {
|
||
|
MenuItem = Me.imports.menuItemLegacy;
|
||
|
} else if (ExtensionUtils.versionCheck(['3.30', '3.32'], Config.PACKAGE_VERSION)) {
|
||
|
MenuItem = Me.imports.menuItemOld;
|
||
|
} else {
|
||
|
MenuItem = Me.imports.menuItem;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function enable() {
|
||
|
vitalsMenu = new VitalsMenuButton();
|
||
|
let positionInPanel = vitalsMenu.positionInPanel;
|
||
|
Main.panel.addToStatusArea('vitalsMenu', vitalsMenu, positionInPanel == 'right' ? 1 : -1, positionInPanel);
|
||
|
}
|
||
|
|
||
|
function disable() {
|
||
|
vitalsMenu.destroy();
|
||
|
vitalsMenu = null;
|
||
|
}
|