MessageTray.actor now has ClutterBinLayout layout manager, so we need to tell it esplicitly where to place us.
361 lines
12 KiB
JavaScript
361 lines
12 KiB
JavaScript
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
const GTop = imports.gi.GTop;
|
|
const Lang = imports.lang;
|
|
const Mainloop = imports.mainloop;
|
|
const St = imports.gi.St;
|
|
const Shell = imports.gi.Shell;
|
|
|
|
const Main = imports.ui.main;
|
|
const Tweener = imports.ui.tweener;
|
|
|
|
const Gettext = imports.gettext.domain('gnome-shell-extensions');
|
|
const _ = Gettext.gettext;
|
|
|
|
const ExtensionUtils = imports.misc.extensionUtils;
|
|
const Me = ExtensionUtils.getCurrentExtension();
|
|
const Convenience = Me.imports.convenience;
|
|
|
|
const INDICATOR_UPDATE_INTERVAL = 500;
|
|
const INDICATOR_NUM_GRID_LINES = 3;
|
|
|
|
const ITEM_LABEL_SHOW_TIME = 0.15;
|
|
const ITEM_LABEL_HIDE_TIME = 0.1;
|
|
const ITEM_HOVER_TIMEOUT = 300;
|
|
|
|
const Indicator = new Lang.Class({
|
|
Name: 'SystemMonitor.Indicator',
|
|
|
|
_init: function() {
|
|
this._initValues();
|
|
this.drawing_area = new St.DrawingArea({ reactive: true });
|
|
this.drawing_area.connect('repaint', Lang.bind(this, this._draw));
|
|
this.drawing_area.connect('button-press-event', function() {
|
|
let app = Shell.AppSystem.get_default().lookup_app('gnome-system-monitor.desktop');
|
|
app.open_new_window(-1);
|
|
});
|
|
|
|
this.actor = new St.Bin({ style_class: "extension-systemMonitor-indicator-area",
|
|
reactive: true, track_hover: true,
|
|
x_fill: true, y_fill: true });
|
|
this.actor.add_actor(this.drawing_area);
|
|
|
|
this._timeout = Mainloop.timeout_add(INDICATOR_UPDATE_INTERVAL, Lang.bind(this, function () {
|
|
this._updateValues();
|
|
this.drawing_area.queue_repaint();
|
|
return true;
|
|
}));
|
|
},
|
|
|
|
showLabel: function() {
|
|
if (this.label == null)
|
|
return;
|
|
|
|
this.label.opacity = 0;
|
|
this.label.show();
|
|
|
|
let [stageX, stageY] = this.actor.get_transformed_position();
|
|
|
|
let itemWidth = this.actor.allocation.x2 - this.actor.allocation.x1;
|
|
let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1;
|
|
|
|
let labelWidth = this.label.width;
|
|
let labelHeight = this.label.height;
|
|
let xOffset = Math.floor((itemWidth - labelWidth) / 2)
|
|
|
|
let x = stageX + xOffset;
|
|
|
|
let node = this.label.get_theme_node();
|
|
let yOffset = node.get_length('-y-offset');
|
|
|
|
let y = stageY - this.label.get_height() - yOffset;
|
|
|
|
this.label.set_position(x, y);
|
|
Tweener.addTween(this.label,
|
|
{ opacity: 255,
|
|
time: ITEM_LABEL_SHOW_TIME,
|
|
transition: 'easeOutQuad',
|
|
});
|
|
},
|
|
|
|
setLabelText: function(text) {
|
|
if (this.label == null)
|
|
this.label = new St.Label({ style_class: 'extension-systemMonitor-indicator-label'});
|
|
|
|
this.label.set_text(text);
|
|
Main.layoutManager.addChrome(this.label);
|
|
this.label.hide();
|
|
},
|
|
|
|
hideLabel: function () {
|
|
Tweener.addTween(this.label,
|
|
{ opacity: 0,
|
|
time: ITEM_LABEL_HIDE_TIME,
|
|
transition: 'easeOutQuad',
|
|
onComplete: Lang.bind(this, function() {
|
|
this.label.hide();
|
|
})
|
|
});
|
|
},
|
|
|
|
destroy: function() {
|
|
Mainloop.source_remove(this._timeout);
|
|
|
|
this.actor.destroy();
|
|
if (this.label)
|
|
this.label.destroy();
|
|
},
|
|
|
|
_initValues: function() {
|
|
},
|
|
|
|
_updateValues: function() {
|
|
},
|
|
|
|
_draw: function(area) {
|
|
let [width, height] = area.get_surface_size();
|
|
let themeNode = this.actor.get_theme_node();
|
|
let cr = area.get_context();
|
|
|
|
//draw the background grid
|
|
let color = themeNode.get_color(this.gridColor);
|
|
let gridOffset = Math.floor(height / (INDICATOR_NUM_GRID_LINES + 1));
|
|
for (let i = 1; i <= INDICATOR_NUM_GRID_LINES; ++i) {
|
|
cr.moveTo(0, i * gridOffset + .5);
|
|
cr.lineTo(width, i * gridOffset + .5);
|
|
}
|
|
Clutter.cairo_set_source_color(cr, color);
|
|
cr.setLineWidth(1);
|
|
cr.setDash([4,1], 0);
|
|
cr.stroke();
|
|
|
|
//draw the foreground
|
|
|
|
function makePath(values, reverse, nudge) {
|
|
if (nudge == null) {
|
|
nudge = 0;
|
|
}
|
|
//if we are going in reverse, we are completing the bottom of a chart, so use lineTo
|
|
if (reverse) {
|
|
cr.lineTo(values.length - 1, (1 - values[values.length - 1]) * height + nudge);
|
|
for (let k = values.length - 2; k >= 0; --k) {
|
|
cr.lineTo(k, (1 - values[k]) * height + nudge);
|
|
}
|
|
} else {
|
|
cr.moveTo(0, (1 - values[0]) * height + nudge);
|
|
for (let k = 1; k < values.length; ++k) {
|
|
cr.lineTo(k, (1 - values[k]) * height + nudge);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
let renderStats = this.renderStats;
|
|
|
|
// Make sure we don't have more sample points than pixels
|
|
renderStats.map(Lang.bind(this, function(k){
|
|
let stat = this.stats[k];
|
|
if (stat.values.length > width) {
|
|
stat.values = stat.values.slice(stat.values.length - width, stat.values.length);
|
|
}
|
|
}));
|
|
|
|
for (let i = 0; i < renderStats.length; ++i) {
|
|
let stat = this.stats[renderStats[i]];
|
|
// We outline at full opacity and fill with 40% opacity
|
|
let outlineColor = themeNode.get_color(stat.color);
|
|
let color = new Clutter.Color(outlineColor);
|
|
color.alpha = color.alpha * .4;
|
|
|
|
// Render the background between us and the next level
|
|
makePath(stat.values, false);
|
|
// If there is a process below us, render the cpu between us and it, otherwise,
|
|
// render to the bottom of the chart
|
|
if (i == renderStats.length - 1) {
|
|
cr.lineTo(stat.values.length - 1, height);
|
|
cr.lineTo(0, height);
|
|
cr.closePath();
|
|
} else {
|
|
let nextStat = this.stats[renderStats[i+1]];
|
|
makePath(nextStat.values, true);
|
|
}
|
|
cr.closePath()
|
|
Clutter.cairo_set_source_color(cr, color);
|
|
cr.fill();
|
|
|
|
// Render the outline of this level
|
|
makePath(stat.values, false, .5);
|
|
Clutter.cairo_set_source_color(cr, outlineColor);
|
|
cr.setLineWidth(1.0);
|
|
cr.setDash([], 0);
|
|
cr.stroke();
|
|
}
|
|
}
|
|
});
|
|
|
|
const CpuIndicator = new Lang.Class({
|
|
Name: 'SystemMonitor.CpuIndicator',
|
|
Extends: Indicator,
|
|
|
|
_init: function() {
|
|
this.parent();
|
|
|
|
this.gridColor = '-grid-color';
|
|
this.renderStats = [ 'cpu-user', 'cpu-sys', 'cpu-iowait' ];
|
|
|
|
// Make sure renderStats is sorted as necessary for rendering
|
|
let renderStatOrder = {'cpu-total': 0, 'cpu-user': 1, 'cpu-sys': 2, 'cpu-iowait': 3};
|
|
this.renderStats = this.renderStats.sort(function(a,b) {
|
|
return renderStatOrder[a] - renderStatOrder[b];
|
|
});
|
|
|
|
this.setLabelText(_("CPU"));
|
|
},
|
|
|
|
_initValues: function() {
|
|
this._prev = new GTop.glibtop_cpu;
|
|
GTop.glibtop_get_cpu(this._prev);
|
|
|
|
this.stats = {
|
|
'cpu-user': {color: '-cpu-user-color', values: []},
|
|
'cpu-sys': {color: '-cpu-sys-color', values: []},
|
|
'cpu-iowait': {color: '-cpu-iowait-color', values: []},
|
|
'cpu-total': {color: '-cpu-total-color', values: []}
|
|
};
|
|
},
|
|
|
|
_updateValues: function() {
|
|
let cpu = new GTop.glibtop_cpu;
|
|
let t = 0.0;
|
|
GTop.glibtop_get_cpu(cpu);
|
|
let total = cpu.total - this._prev.total;
|
|
let user = cpu.user - this._prev.user;
|
|
let sys = cpu.sys - this._prev.sys;
|
|
let iowait = cpu.iowait - this._prev.iowait;
|
|
let idle = cpu.idle - this._prev.idle;
|
|
|
|
t += iowait / total;
|
|
this.stats['cpu-iowait'].values.push(t);
|
|
t += sys / total;
|
|
this.stats['cpu-sys'].values.push(t);
|
|
t += user / total;
|
|
this.stats['cpu-user'].values.push(t);
|
|
this.stats['cpu-total'].values.push(1 - idle / total);
|
|
|
|
this._prev = cpu;
|
|
}
|
|
});
|
|
|
|
const MemoryIndicator = new Lang.Class({
|
|
Name: 'SystemMonitor.MemoryIndicator',
|
|
Extends: Indicator,
|
|
|
|
_init: function() {
|
|
this.parent();
|
|
|
|
this.gridColor = '-grid-color';
|
|
this.renderStats = [ 'mem-user', 'mem-other', 'mem-cached' ];
|
|
|
|
// Make sure renderStats is sorted as necessary for rendering
|
|
let renderStatOrder = { 'mem-cached': 0, 'mem-other': 1, 'mem-user': 2 };
|
|
this.renderStats = this.renderStats.sort(function(a,b) {
|
|
return renderStatOrder[a] - renderStatOrder[b];
|
|
});
|
|
|
|
this.setLabelText(_("Memory"));
|
|
},
|
|
|
|
_initValues: function() {
|
|
this.mem = new GTop.glibtop_mem;
|
|
this.stats = {
|
|
'mem-user': { color: "-mem-user-color", values: [] },
|
|
'mem-other': { color: "-mem-other-color", values: [] },
|
|
'mem-cached': { color: "-mem-cached-color", values: [] }
|
|
};
|
|
},
|
|
|
|
_updateValues: function() {
|
|
GTop.glibtop_get_mem(this.mem);
|
|
|
|
let t = this.mem.user / this.mem.total;
|
|
this.stats['mem-user'].values.push(t);
|
|
t += (this.mem.used - this.mem.user - this.mem.cached) / this.mem.total;
|
|
this.stats['mem-other'].values.push(t);
|
|
t += this.mem.cached / this.mem.total;
|
|
this.stats['mem-cached'].values.push(t);
|
|
}
|
|
});
|
|
|
|
const INDICATORS = [CpuIndicator, MemoryIndicator];
|
|
|
|
const Extension = new Lang.Class({
|
|
Name: 'SystemMonitor.Extension',
|
|
|
|
_init: function() {
|
|
Convenience.initTranslations();
|
|
|
|
this._showLabelTimeoutId = 0;
|
|
this._resetHoverTimeoutId = 0;
|
|
this._labelShowing = false;
|
|
},
|
|
|
|
enable: function() {
|
|
this._box = new St.BoxLayout({ style_class: 'extension-systemMonitor-container',
|
|
x_align: Clutter.ActorAlign.START,
|
|
x_expand: true });
|
|
this._indicators = [ ];
|
|
|
|
for (let i = 0; i < INDICATORS.length; i++) {
|
|
let indicator = new (INDICATORS[i])();
|
|
|
|
indicator.actor.connect('notify::hover', Lang.bind(this, function() {
|
|
this._onHover(indicator);
|
|
}));
|
|
this._box.add_actor(indicator.actor);
|
|
this._indicators.push(indicator);
|
|
}
|
|
|
|
Main.messageTray.actor.add_actor(this._box);
|
|
},
|
|
|
|
disable: function() {
|
|
this._indicators.forEach(function(i) { i.destroy(); });
|
|
this._box.destroy();
|
|
},
|
|
|
|
_onHover: function (item) {
|
|
if (item.actor.get_hover()) {
|
|
if (this._showLabelTimeoutId == 0) {
|
|
let timeout = this._labelShowing ? 0 : ITEM_HOVER_TIMEOUT;
|
|
this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
|
|
Lang.bind(this, function() {
|
|
this._labelShowing = true;
|
|
item.showLabel();
|
|
return false;
|
|
}));
|
|
if (this._resetHoverTimeoutId > 0) {
|
|
Mainloop.source_remove(this._resetHoverTimeoutId);
|
|
this._resetHoverTimeoutId = 0;
|
|
}
|
|
}
|
|
} else {
|
|
if (this._showLabelTimeoutId > 0)
|
|
Mainloop.source_remove(this._showLabelTimeoutId);
|
|
this._showLabelTimeoutId = 0;
|
|
item.hideLabel();
|
|
if (this._labelShowing) {
|
|
this._resetHoverTimeoutId = Mainloop.timeout_add(ITEM_HOVER_TIMEOUT,
|
|
Lang.bind(this, function() {
|
|
this._labelShowing = false;
|
|
return false;
|
|
}));
|
|
}
|
|
}
|
|
},
|
|
});
|
|
|
|
function init() {
|
|
return new Extension();
|
|
}
|