dot/.local/share/gnome-shell/extensions/Vitals@CoreCoding.com/extension.js

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;
}