bc3989
From 57bb099db30703a474a023122f1106e199ff79ed Mon Sep 17 00:00:00 2001
689e29
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
689e29
Date: Wed, 17 May 2017 19:13:50 +0200
689e29
Subject: [PATCH 1/4] extensions: Resurrect systemMonitor extension
689e29
689e29
The extension was removed upstream because:
689e29
 - it hooks into the message tray that was removed
689e29
 - it was known to have performance issues
689e29
 - there are plenty of alternatives
689e29
689e29
Those aren't good enough reasons for dropping it downstream
689e29
as well though, so we need to bring it back ...
689e29
689e29
This reverts commit c9a6421f362cd156cf731289eadc11f44f6970ac.
689e29
---
bc3989
 extensions/systemMonitor/extension.js     | 376 ++++++++++++++++++++++
bc3989
 extensions/systemMonitor/meson.build      |   5 +
689e29
 extensions/systemMonitor/metadata.json.in |  11 +
bc3989
 extensions/systemMonitor/stylesheet.css   |  35 ++
bc3989
 meson.build                               |   1 +
bc3989
 5 files changed, 428 insertions(+)
689e29
 create mode 100644 extensions/systemMonitor/extension.js
bc3989
 create mode 100644 extensions/systemMonitor/meson.build
689e29
 create mode 100644 extensions/systemMonitor/metadata.json.in
689e29
 create mode 100644 extensions/systemMonitor/stylesheet.css
689e29
689e29
diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js
689e29
new file mode 100644
bc3989
index 0000000..7b09df0
689e29
--- /dev/null
689e29
+++ b/extensions/systemMonitor/extension.js
689e29
@@ -0,0 +1,376 @@
689e29
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
689e29
+
689e29
+const Clutter = imports.gi.Clutter;
689e29
+const GTop = imports.gi.GTop;
689e29
+const Lang = imports.lang;
689e29
+const Mainloop = imports.mainloop;
689e29
+const St = imports.gi.St;
689e29
+const Shell = imports.gi.Shell;
689e29
+
689e29
+const Main = imports.ui.main;
689e29
+const Tweener = imports.ui.tweener;
689e29
+
689e29
+const Gettext = imports.gettext.domain('gnome-shell-extensions');
689e29
+const _ = Gettext.gettext;
689e29
+
689e29
+const ExtensionUtils = imports.misc.extensionUtils;
689e29
+const Me = ExtensionUtils.getCurrentExtension();
689e29
+const Convenience = Me.imports.convenience;
689e29
+
689e29
+const INDICATOR_UPDATE_INTERVAL = 500;
689e29
+const INDICATOR_NUM_GRID_LINES = 3;
689e29
+
689e29
+const ITEM_LABEL_SHOW_TIME = 0.15;
689e29
+const ITEM_LABEL_HIDE_TIME = 0.1;
689e29
+const ITEM_HOVER_TIMEOUT = 300;
689e29
+
689e29
+const Indicator = new Lang.Class({
689e29
+    Name: 'SystemMonitor.Indicator',
689e29
+
689e29
+    _init: function() {
689e29
+        this._initValues();
689e29
+        this.drawing_area = new St.DrawingArea({ reactive: true });
689e29
+        this.drawing_area.connect('repaint', Lang.bind(this, this._draw));
689e29
+        this.drawing_area.connect('button-press-event', function() {
689e29
+            let app = Shell.AppSystem.get_default().lookup_app('gnome-system-monitor.desktop');
689e29
+            app.open_new_window(-1);
689e29
+            return true;
689e29
+        });
689e29
+
689e29
+        this.actor = new St.Bin({ style_class: "extension-systemMonitor-indicator-area",
689e29
+                                  reactive: true, track_hover: true,
689e29
+				  x_fill: true, y_fill: true });
689e29
+        this.actor.add_actor(this.drawing_area);
689e29
+
689e29
+        this._timeout = Mainloop.timeout_add(INDICATOR_UPDATE_INTERVAL, Lang.bind(this, function () {
689e29
+            this._updateValues();
689e29
+            this.drawing_area.queue_repaint();
689e29
+            return true;
689e29
+        }));
689e29
+    },
689e29
+
689e29
+    showLabel: function() {
689e29
+        if (this.label == null)
689e29
+            return;
689e29
+
689e29
+        this.label.opacity = 0;
689e29
+        this.label.show();
689e29
+
689e29
+        let [stageX, stageY] = this.actor.get_transformed_position();
689e29
+
689e29
+	let itemWidth = this.actor.allocation.x2 - this.actor.allocation.x1;
689e29
+        let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1;
689e29
+
689e29
+	let labelWidth = this.label.width;
689e29
+        let labelHeight = this.label.height;
689e29
+        let xOffset = Math.floor((itemWidth - labelWidth) / 2)
689e29
+
689e29
+        let x = stageX + xOffset;
689e29
+
689e29
+        let node = this.label.get_theme_node();
689e29
+        let yOffset = node.get_length('-y-offset');
689e29
+
689e29
+        let y = stageY - this.label.get_height() - yOffset;
689e29
+
689e29
+        this.label.set_position(x, y);
689e29
+        Tweener.addTween(this.label,
689e29
+                         { opacity: 255,
689e29
+                           time: ITEM_LABEL_SHOW_TIME,
689e29
+                           transition: 'easeOutQuad',
689e29
+                         });
689e29
+    },
689e29
+
689e29
+    setLabelText: function(text) {
689e29
+        if (this.label == null)
689e29
+            this.label = new St.Label({ style_class: 'extension-systemMonitor-indicator-label'});
689e29
+
689e29
+        this.label.set_text(text);
689e29
+        Main.layoutManager.addChrome(this.label);
689e29
+        this.label.hide();
689e29
+    },
689e29
+
689e29
+    hideLabel: function () {
689e29
+        Tweener.addTween(this.label,
689e29
+                         { opacity: 0,
689e29
+                           time: ITEM_LABEL_HIDE_TIME,
689e29
+                           transition: 'easeOutQuad',
689e29
+                           onComplete: Lang.bind(this, function() {
689e29
+                               this.label.hide();
689e29
+                           })
689e29
+                         });
689e29
+    },
689e29
+
689e29
+    destroy: function() {
689e29
+        Mainloop.source_remove(this._timeout);
689e29
+
689e29
+        this.actor.destroy();
689e29
+	if (this.label)
689e29
+	    this.label.destroy();
689e29
+    },
689e29
+
689e29
+    _initValues: function() {
689e29
+    },
689e29
+
689e29
+    _updateValues: function() {
689e29
+    },
689e29
+
689e29
+    _draw: function(area) {
689e29
+        let [width, height] = area.get_surface_size();
689e29
+        let themeNode = this.actor.get_theme_node();
689e29
+        let cr = area.get_context();
689e29
+
689e29
+        //draw the background grid
689e29
+        let color = themeNode.get_color(this.gridColor);
689e29
+        let gridOffset = Math.floor(height / (INDICATOR_NUM_GRID_LINES + 1));
689e29
+        for (let i = 1; i <= INDICATOR_NUM_GRID_LINES; ++i) {
689e29
+                cr.moveTo(0, i * gridOffset + .5);
689e29
+                cr.lineTo(width, i * gridOffset + .5);
689e29
+        }
689e29
+        Clutter.cairo_set_source_color(cr, color);
689e29
+        cr.setLineWidth(1);
689e29
+        cr.setDash([4,1], 0);
689e29
+        cr.stroke();
bc3989
+
689e29
+        //draw the foreground
689e29
+
689e29
+        function makePath(values, reverse, nudge) {
689e29
+            if (nudge == null) {
689e29
+                nudge = 0;
689e29
+            }
689e29
+            //if we are going in reverse, we are completing the bottom of a chart, so use lineTo
689e29
+            if (reverse) {
689e29
+                cr.lineTo(values.length - 1, (1 - values[values.length - 1]) * height + nudge);
689e29
+                for (let k = values.length - 2; k >= 0; --k) {
689e29
+                    cr.lineTo(k, (1 - values[k]) * height + nudge);
689e29
+                }
689e29
+            } else {
689e29
+                cr.moveTo(0, (1 - values[0]) * height + nudge);
689e29
+                for (let k = 1; k < values.length; ++k) {
689e29
+                    cr.lineTo(k, (1 - values[k]) * height + nudge);
689e29
+                }
689e29
+
689e29
+            }
689e29
+        }
bc3989
+
689e29
+        let renderStats = this.renderStats;
689e29
+
689e29
+        // Make sure we don't have more sample points than pixels
689e29
+        renderStats.map(Lang.bind(this, function(k){
689e29
+            let stat = this.stats[k];
689e29
+            if (stat.values.length > width) {
689e29
+                stat.values = stat.values.slice(stat.values.length - width, stat.values.length);
689e29
+            }
689e29
+        }));
689e29
+
689e29
+        for (let i = 0; i < renderStats.length; ++i) {
689e29
+            let stat = this.stats[renderStats[i]];
689e29
+            // We outline at full opacity and fill with 40% opacity
689e29
+            let outlineColor = themeNode.get_color(stat.color);
689e29
+            let color = new Clutter.Color(outlineColor);
689e29
+            color.alpha = color.alpha * .4;
bc3989
+
689e29
+            // Render the background between us and the next level
689e29
+            makePath(stat.values, false);
bc3989
+            // If there is a process below us, render the cpu between us and it, otherwise,
689e29
+            // render to the bottom of the chart
689e29
+            if (i == renderStats.length - 1) {
689e29
+                cr.lineTo(stat.values.length - 1, height);
689e29
+                cr.lineTo(0, height);
689e29
+                cr.closePath();
689e29
+            } else {
689e29
+                let nextStat = this.stats[renderStats[i+1]];
689e29
+                makePath(nextStat.values, true);
689e29
+            }
689e29
+            cr.closePath()
689e29
+            Clutter.cairo_set_source_color(cr, color);
689e29
+            cr.fill();
bc3989
+
689e29
+            // Render the outline of this level
689e29
+            makePath(stat.values, false, .5);
689e29
+            Clutter.cairo_set_source_color(cr, outlineColor);
689e29
+            cr.setLineWidth(1.0);
689e29
+            cr.setDash([], 0);
689e29
+            cr.stroke();
689e29
+        }
689e29
+    }
689e29
+});
689e29
+
689e29
+const CpuIndicator = new Lang.Class({
689e29
+    Name: 'SystemMonitor.CpuIndicator',
689e29
+    Extends: Indicator,
689e29
+
689e29
+    _init: function() {
689e29
+        this.parent();
689e29
+
689e29
+        this.gridColor = '-grid-color';
689e29
+        this.renderStats = [ 'cpu-user', 'cpu-sys', 'cpu-iowait' ];
bc3989
+
689e29
+        // Make sure renderStats is sorted as necessary for rendering
689e29
+        let renderStatOrder = {'cpu-total': 0, 'cpu-user': 1, 'cpu-sys': 2, 'cpu-iowait': 3};
689e29
+        this.renderStats = this.renderStats.sort(function(a,b) {
689e29
+            return renderStatOrder[a] - renderStatOrder[b];
689e29
+        });
689e29
+
689e29
+	this.setLabelText(_("CPU"));
689e29
+    },
689e29
+
689e29
+    _initValues: function() {
689e29
+        this._prev = new GTop.glibtop_cpu;
689e29
+        GTop.glibtop_get_cpu(this._prev);
689e29
+
bc3989
+        this.stats = {
689e29
+                       'cpu-user': {color: '-cpu-user-color', values: []},
689e29
+                       'cpu-sys': {color: '-cpu-sys-color', values: []},
689e29
+                       'cpu-iowait': {color: '-cpu-iowait-color', values: []},
bc3989
+                       'cpu-total': {color: '-cpu-total-color', values: []}
689e29
+                     };
689e29
+    },
689e29
+
689e29
+    _updateValues: function() {
689e29
+        let cpu = new GTop.glibtop_cpu;
689e29
+        let t = 0.0;
689e29
+        GTop.glibtop_get_cpu(cpu);
689e29
+        let total = cpu.total - this._prev.total;
689e29
+        let user = cpu.user - this._prev.user;
689e29
+        let sys = cpu.sys - this._prev.sys;
689e29
+        let iowait = cpu.iowait - this._prev.iowait;
689e29
+        let idle = cpu.idle - this._prev.idle;
689e29
+
689e29
+        t += iowait / total;
689e29
+        this.stats['cpu-iowait'].values.push(t);
689e29
+        t += sys / total;
689e29
+        this.stats['cpu-sys'].values.push(t);
689e29
+        t += user / total;
689e29
+        this.stats['cpu-user'].values.push(t);
689e29
+        this.stats['cpu-total'].values.push(1 - idle / total);
bc3989
+
689e29
+        this._prev = cpu;
689e29
+    }
689e29
+});
689e29
+
689e29
+const MemoryIndicator = new Lang.Class({
689e29
+    Name: 'SystemMonitor.MemoryIndicator',
689e29
+    Extends: Indicator,
bc3989
+
689e29
+    _init: function() {
689e29
+        this.parent();
689e29
+
689e29
+        this.gridColor = '-grid-color';
689e29
+        this.renderStats = [ 'mem-user', 'mem-other', 'mem-cached' ];
bc3989
+
689e29
+        // Make sure renderStats is sorted as necessary for rendering
689e29
+        let renderStatOrder = { 'mem-cached': 0, 'mem-other': 1, 'mem-user': 2 };
689e29
+        this.renderStats = this.renderStats.sort(function(a,b) {
689e29
+            return renderStatOrder[a] - renderStatOrder[b];
689e29
+        });
689e29
+
689e29
+	this.setLabelText(_("Memory"));
689e29
+    },
689e29
+
689e29
+    _initValues: function() {
689e29
+        this.mem = new GTop.glibtop_mem;
689e29
+        this.stats = {
689e29
+                        'mem-user': { color: "-mem-user-color", values: [] },
689e29
+                        'mem-other': { color: "-mem-other-color", values: [] },
689e29
+                        'mem-cached': { color: "-mem-cached-color", values: [] }
689e29
+                     };
689e29
+    },
689e29
+
689e29
+    _updateValues: function() {
689e29
+        GTop.glibtop_get_mem(this.mem);
689e29
+
689e29
+        let t = this.mem.user / this.mem.total;
689e29
+        this.stats['mem-user'].values.push(t);
689e29
+        t += (this.mem.used - this.mem.user - this.mem.cached) / this.mem.total;
689e29
+        this.stats['mem-other'].values.push(t);
689e29
+        t += this.mem.cached / this.mem.total;
689e29
+        this.stats['mem-cached'].values.push(t);
689e29
+    }
689e29
+});
689e29
+
689e29
+const INDICATORS = [CpuIndicator, MemoryIndicator];
689e29
+
689e29
+const Extension = new Lang.Class({
689e29
+    Name: 'SystemMonitor.Extension',
689e29
+
689e29
+    _init: function() {
689e29
+	Convenience.initTranslations();
689e29
+
689e29
+	this._showLabelTimeoutId = 0;
689e29
+	this._resetHoverTimeoutId = 0;
689e29
+	this._labelShowing = false;
689e29
+    },
689e29
+
689e29
+    enable: function() {
689e29
+	this._box = new St.BoxLayout({ style_class: 'extension-systemMonitor-container',
689e29
+				       x_align: Clutter.ActorAlign.START,
689e29
+				       x_expand: true });
689e29
+	this._indicators = [ ];
689e29
+
689e29
+	for (let i = 0; i < INDICATORS.length; i++) {
689e29
+	    let indicator = new (INDICATORS[i])();
689e29
+
689e29
+            indicator.actor.connect('notify::hover', Lang.bind(this, function() {
689e29
+		this._onHover(indicator);
689e29
+	    }));
689e29
+	    this._box.add_actor(indicator.actor);
689e29
+	    this._indicators.push(indicator);
689e29
+	}
689e29
+
689e29
+	this._boxHolder = new St.BoxLayout({ x_expand: true,
689e29
+					     y_expand: true,
689e29
+					     x_align: Clutter.ActorAlign.START,
689e29
+					   });
689e29
+	let menuButton = Main.messageTray._messageTrayMenuButton.actor;
689e29
+	Main.messageTray.actor.remove_child(menuButton);
689e29
+	Main.messageTray.actor.add_child(this._boxHolder);
689e29
+
689e29
+	this._boxHolder.add_child(this._box);
689e29
+	this._boxHolder.add_child(menuButton);
689e29
+    },
689e29
+
689e29
+    disable: function() {
689e29
+	this._indicators.forEach(function(i) { i.destroy(); });
689e29
+
689e29
+	let menuButton = Main.messageTray._messageTrayMenuButton.actor;
689e29
+	this._boxHolder.remove_child(menuButton);
689e29
+	Main.messageTray.actor.add_child(menuButton);
689e29
+
689e29
+	this._box.destroy();
689e29
+	this._boxHolder.destroy();
689e29
+    },
689e29
+
689e29
+    _onHover: function (item) {
689e29
+        if (item.actor.get_hover()) {
689e29
+            if (this._showLabelTimeoutId == 0) {
689e29
+                let timeout = this._labelShowing ? 0 : ITEM_HOVER_TIMEOUT;
689e29
+                this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
689e29
+                    Lang.bind(this, function() {
689e29
+                        this._labelShowing = true;
689e29
+                        item.showLabel();
689e29
+                        return false;
689e29
+                    }));
689e29
+                if (this._resetHoverTimeoutId > 0) {
689e29
+                    Mainloop.source_remove(this._resetHoverTimeoutId);
689e29
+                    this._resetHoverTimeoutId = 0;
689e29
+                }
689e29
+            }
689e29
+        } else {
689e29
+            if (this._showLabelTimeoutId > 0)
689e29
+                Mainloop.source_remove(this._showLabelTimeoutId);
689e29
+            this._showLabelTimeoutId = 0;
689e29
+            item.hideLabel();
689e29
+            if (this._labelShowing) {
689e29
+                this._resetHoverTimeoutId = Mainloop.timeout_add(ITEM_HOVER_TIMEOUT,
689e29
+                    Lang.bind(this, function() {
689e29
+                        this._labelShowing = false;
689e29
+                        return false;
689e29
+                    }));
689e29
+            }
689e29
+        }
689e29
+    },
689e29
+});
689e29
+
689e29
+function init() {
689e29
+    return new Extension();
689e29
+}
bc3989
diff --git a/extensions/systemMonitor/meson.build b/extensions/systemMonitor/meson.build
bc3989
new file mode 100644
bc3989
index 0000000..48504f6
bc3989
--- /dev/null
bc3989
+++ b/extensions/systemMonitor/meson.build
bc3989
@@ -0,0 +1,5 @@
bc3989
+extension_data += configure_file(
bc3989
+  input: metadata_name + '.in',
bc3989
+  output: metadata_name,
bc3989
+  configuration: metadata_conf
bc3989
+)
689e29
diff --git a/extensions/systemMonitor/metadata.json.in b/extensions/systemMonitor/metadata.json.in
689e29
new file mode 100644
689e29
index 0000000..fa75007
689e29
--- /dev/null
689e29
+++ b/extensions/systemMonitor/metadata.json.in
689e29
@@ -0,0 +1,11 @@
689e29
+{
689e29
+    "shell-version": ["@shell_current@" ],
689e29
+    "uuid": "@uuid@",
689e29
+    "extension-id": "@extension_id@",
689e29
+    "settings-schema": "@gschemaname@",
689e29
+    "gettext-domain": "@gettext_domain@",
689e29
+    "original-author": "zaspire@rambler.ru",
689e29
+    "name": "SystemMonitor",
689e29
+    "description": "System monitor showing CPU and memory usage in the message tray.",
689e29
+    "url": "@url@"
689e29
+}
689e29
diff --git a/extensions/systemMonitor/stylesheet.css b/extensions/systemMonitor/stylesheet.css
689e29
new file mode 100644
689e29
index 0000000..13f95ec
689e29
--- /dev/null
689e29
+++ b/extensions/systemMonitor/stylesheet.css
689e29
@@ -0,0 +1,35 @@
689e29
+.extension-systemMonitor-container {
689e29
+    spacing: 5px;
689e29
+    padding-left: 5px;
689e29
+    padding-right: 5px;
689e29
+    padding-bottom: 10px;
689e29
+    padding-top: 10px;
689e29
+}
689e29
+
689e29
+.extension-systemMonitor-indicator-area {
689e29
+    border: 1px solid #8d8d8d;
689e29
+    border-radius: 3px;
689e29
+    width: 100px;
689e29
+    /* message tray is 72px, so 20px padding of the container,
689e29
+       2px of border, makes it 50px */
689e29
+    height: 50px;
689e29
+    -grid-color: #575757;
689e29
+    -cpu-total-color: rgb(0,154,62);
689e29
+    -cpu-user-color: rgb(69,154,0);
689e29
+    -cpu-sys-color: rgb(255,253,81);
689e29
+    -cpu-iowait-color: rgb(210,148,0);
689e29
+    -mem-user-color: rgb(210,148,0);
689e29
+    -mem-cached-color: rgb(90,90,90);
689e29
+    -mem-other-color: rgb(205,203,41);
689e29
+    background-color: #1e1e1e;
689e29
+}
689e29
+
689e29
+.extension-systemMonitor-indicator-label {
689e29
+    border-radius: 7px;
689e29
+    padding: 4px 12px;
689e29
+    background-color: rgba(0,0,0,0.9);
689e29
+    text-align: center;
689e29
+    -y-offset: 8px;
689e29
+    font-size: 9pt;
689e29
+    font-weight: bold;
689e29
+}
bc3989
diff --git a/meson.build b/meson.build
bc3989
index 201c484..cde2d34 100644
bc3989
--- a/meson.build
bc3989
+++ b/meson.build
bc3989
@@ -30,60 +30,61 @@ if ver_arr[1].to_int().is_even()
bc3989
 else
bc3989
   shell_version = '.'.join(ver_arr)
bc3989
 endif
bc3989
 
bc3989
 uuid_suffix = '@gnome-shell-extensions.gcampax.github.com'
bc3989
 
bc3989
 classic_extensions = [
bc3989
   'alternate-tab',
bc3989
   'apps-menu',
bc3989
   'places-menu',
bc3989
   'launch-new-instance',
bc3989
   'window-list'
bc3989
 ]
bc3989
 
bc3989
 default_extensions = classic_extensions
bc3989
 default_extensions += [
bc3989
   'drive-menu',
bc3989
   'screenshot-window-sizer',
bc3989
   'windowsNavigator',
bc3989
   'workspace-indicator'
bc3989
 ]
bc3989
 
bc3989
 all_extensions = default_extensions
bc3989
 all_extensions += [
bc3989
   'auto-move-windows',
bc3989
   'dash-to-dock',
bc3989
   'example',
bc3989
   'native-window-placement',
bc3989
   'no-hot-corner',
bc3989
   'panel-favorites',
bc3989
+  'systemMonitor',
bc3989
   'top-icons',
bc3989
   'updates-dialog',
bc3989
   'user-theme'
bc3989
 ]
bc3989
 
bc3989
 enabled_extensions = get_option('enable_extensions')
bc3989
 
bc3989
 if enabled_extensions.length() == 0
bc3989
   set = get_option('extension_set')
bc3989
 
bc3989
   if set == 'classic'
bc3989
     enabled_extensions += classic_extensions
bc3989
   elif set == 'default'
bc3989
     enabled_extensions += default_extensions
bc3989
   elif set == 'all'
bc3989
     enabled_extensions += all_extensions
bc3989
   endif
bc3989
 endif
bc3989
 
bc3989
 classic_mode_enabled = get_option('classic_mode')
bc3989
 
bc3989
 if classic_mode_enabled
bc3989
   # Sanity check: Make sure all classic extensions are enabled
bc3989
   foreach e : classic_extensions
bc3989
     if not enabled_extensions.contains(e)
bc3989
       error('Classic mode is enabled, ' +
bc3989
             'but the required extension @0@ is not.'.format(e))
bc3989
     endif
bc3989
   endforeach
bc3989
 endif
689e29
-- 
bc3989
2.17.1
689e29
689e29
bc3989
From 8ffea72d040e165c73b1b2eba82e6c4e106aee7f Mon Sep 17 00:00:00 2001
689e29
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
689e29
Date: Wed, 17 May 2017 19:31:58 +0200
689e29
Subject: [PATCH 2/4] systemMonitor: Move indicators to calendar
689e29
689e29
The message tray joined the invisible choir, so we have to find
689e29
a new home for the extension UI. The message list in the calendar
689e29
drop-down looks like the best option, given that it replaced the
689e29
old tray (and also took over the old keyboard shortcut to bring
689e29
it up quickly).
689e29
---
bc3989
 extensions/systemMonitor/extension.js   | 56 ++++++++++++-------------
bc3989
 extensions/systemMonitor/stylesheet.css | 14 -------
689e29
 2 files changed, 28 insertions(+), 42 deletions(-)
689e29
689e29
diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js
bc3989
index 7b09df0..1388a1f 100644
689e29
--- a/extensions/systemMonitor/extension.js
689e29
+++ b/extensions/systemMonitor/extension.js
bc3989
@@ -1,132 +1,146 @@
bc3989
 /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
bc3989
 
bc3989
 const Clutter = imports.gi.Clutter;
689e29
 const GTop = imports.gi.GTop;
689e29
 const Lang = imports.lang;
689e29
 const Mainloop = imports.mainloop;
689e29
+const Signals = imports.signals;
689e29
 const St = imports.gi.St;
689e29
 const Shell = imports.gi.Shell;
689e29
 
689e29
 const Main = imports.ui.main;
689e29
+const MessageList = imports.ui.messageList;
689e29
 const Tweener = imports.ui.tweener;
689e29
 
689e29
 const Gettext = imports.gettext.domain('gnome-shell-extensions');
bc3989
 const _ = Gettext.gettext;
bc3989
 
bc3989
 const ExtensionUtils = imports.misc.extensionUtils;
bc3989
 const Me = ExtensionUtils.getCurrentExtension();
bc3989
 const Convenience = Me.imports.convenience;
bc3989
 
bc3989
 const INDICATOR_UPDATE_INTERVAL = 500;
bc3989
 const INDICATOR_NUM_GRID_LINES = 3;
bc3989
 
bc3989
 const ITEM_LABEL_SHOW_TIME = 0.15;
bc3989
 const ITEM_LABEL_HIDE_TIME = 0.1;
bc3989
 const ITEM_HOVER_TIMEOUT = 300;
bc3989
 
bc3989
 const Indicator = new Lang.Class({
bc3989
     Name: 'SystemMonitor.Indicator',
689e29
 
689e29
     _init: function() {
689e29
         this._initValues();
689e29
-        this.drawing_area = new St.DrawingArea({ reactive: true });
689e29
+        this.drawing_area = new St.DrawingArea();
689e29
         this.drawing_area.connect('repaint', Lang.bind(this, this._draw));
689e29
-        this.drawing_area.connect('button-press-event', function() {
689e29
+
689e29
+        this.actor = new St.Button({ style_class: "message message-content extension-systemMonitor-indicator-area",
689e29
+				      x_expand: true, x_fill: true,
689e29
+                                      y_fill: true, can_focus: true });
689e29
+        this.actor.add_actor(this.drawing_area);
689e29
+
689e29
+        this.actor.connect('clicked', function() {
689e29
             let app = Shell.AppSystem.get_default().lookup_app('gnome-system-monitor.desktop');
689e29
             app.open_new_window(-1);
689e29
-            return true;
689e29
-        });
689e29
 
689e29
-        this.actor = new St.Bin({ style_class: "extension-systemMonitor-indicator-area",
689e29
-                                  reactive: true, track_hover: true,
689e29
-				  x_fill: true, y_fill: true });
689e29
-        this.actor.add_actor(this.drawing_area);
689e29
+            Main.overview.hide();
689e29
+            Main.panel.closeCalendar();
689e29
+        });
689e29
 
689e29
         this._timeout = Mainloop.timeout_add(INDICATOR_UPDATE_INTERVAL, Lang.bind(this, function () {
689e29
             this._updateValues();
bc3989
             this.drawing_area.queue_repaint();
bc3989
             return true;
bc3989
         }));
bc3989
     },
bc3989
 
bc3989
     showLabel: function() {
bc3989
         if (this.label == null)
bc3989
             return;
bc3989
 
bc3989
         this.label.opacity = 0;
bc3989
         this.label.show();
bc3989
 
bc3989
         let [stageX, stageY] = this.actor.get_transformed_position();
bc3989
 
bc3989
 	let itemWidth = this.actor.allocation.x2 - this.actor.allocation.x1;
bc3989
         let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1;
bc3989
 
bc3989
 	let labelWidth = this.label.width;
bc3989
         let labelHeight = this.label.height;
bc3989
         let xOffset = Math.floor((itemWidth - labelWidth) / 2)
bc3989
 
bc3989
         let x = stageX + xOffset;
bc3989
 
bc3989
         let node = this.label.get_theme_node();
bc3989
         let yOffset = node.get_length('-y-offset');
bc3989
 
689e29
         let y = stageY - this.label.get_height() - yOffset;
689e29
 
689e29
         this.label.set_position(x, y);
689e29
+        this.label.get_parent().set_child_above_sibling(this.label, null);
689e29
         Tweener.addTween(this.label,
689e29
                          { opacity: 255,
689e29
                            time: ITEM_LABEL_SHOW_TIME,
bc3989
                            transition: 'easeOutQuad',
bc3989
                          });
bc3989
     },
bc3989
 
bc3989
     setLabelText: function(text) {
bc3989
         if (this.label == null)
bc3989
             this.label = new St.Label({ style_class: 'extension-systemMonitor-indicator-label'});
bc3989
 
bc3989
         this.label.set_text(text);
bc3989
         Main.layoutManager.addChrome(this.label);
bc3989
         this.label.hide();
bc3989
     },
bc3989
 
bc3989
     hideLabel: function () {
bc3989
         Tweener.addTween(this.label,
bc3989
                          { opacity: 0,
bc3989
                            time: ITEM_LABEL_HIDE_TIME,
bc3989
                            transition: 'easeOutQuad',
bc3989
                            onComplete: Lang.bind(this, function() {
bc3989
                                this.label.hide();
bc3989
                            })
689e29
                          });
689e29
     },
689e29
 
689e29
+    /* MessageList.Message boilerplate */
689e29
+    canClose: function() {
689e29
+        return false;
689e29
+    },
689e29
+
689e29
+    clear: function() {
689e29
+    },
689e29
+
689e29
     destroy: function() {
689e29
         Mainloop.source_remove(this._timeout);
689e29
 
bc3989
         this.actor.destroy();
bc3989
 	if (this.label)
bc3989
 	    this.label.destroy();
bc3989
     },
bc3989
 
bc3989
     _initValues: function() {
bc3989
     },
bc3989
 
bc3989
     _updateValues: function() {
bc3989
     },
bc3989
 
bc3989
     _draw: function(area) {
bc3989
         let [width, height] = area.get_surface_size();
bc3989
         let themeNode = this.actor.get_theme_node();
bc3989
         let cr = area.get_context();
bc3989
 
bc3989
         //draw the background grid
bc3989
         let color = themeNode.get_color(this.gridColor);
bc3989
         let gridOffset = Math.floor(height / (INDICATOR_NUM_GRID_LINES + 1));
bc3989
         for (let i = 1; i <= INDICATOR_NUM_GRID_LINES; ++i) {
bc3989
                 cr.moveTo(0, i * gridOffset + .5);
bc3989
                 cr.lineTo(width, i * gridOffset + .5);
bc3989
         }
bc3989
         Clutter.cairo_set_source_color(cr, color);
bc3989
         cr.setLineWidth(1);
bc3989
         cr.setDash([4,1], 0);
bc3989
         cr.stroke();
bc3989
@@ -167,60 +181,61 @@ const Indicator = new Lang.Class({
bc3989
             // We outline at full opacity and fill with 40% opacity
bc3989
             let outlineColor = themeNode.get_color(stat.color);
bc3989
             let color = new Clutter.Color(outlineColor);
bc3989
             color.alpha = color.alpha * .4;
bc3989
 
bc3989
             // Render the background between us and the next level
bc3989
             makePath(stat.values, false);
bc3989
             // If there is a process below us, render the cpu between us and it, otherwise,
bc3989
             // render to the bottom of the chart
bc3989
             if (i == renderStats.length - 1) {
bc3989
                 cr.lineTo(stat.values.length - 1, height);
bc3989
                 cr.lineTo(0, height);
bc3989
                 cr.closePath();
bc3989
             } else {
bc3989
                 let nextStat = this.stats[renderStats[i+1]];
bc3989
                 makePath(nextStat.values, true);
bc3989
             }
bc3989
             cr.closePath()
bc3989
             Clutter.cairo_set_source_color(cr, color);
bc3989
             cr.fill();
bc3989
 
bc3989
             // Render the outline of this level
bc3989
             makePath(stat.values, false, .5);
bc3989
             Clutter.cairo_set_source_color(cr, outlineColor);
bc3989
             cr.setLineWidth(1.0);
bc3989
             cr.setDash([], 0);
bc3989
             cr.stroke();
689e29
         }
689e29
     }
689e29
 });
689e29
+Signals.addSignalMethods(Indicator.prototype); // For MessageList.Message compat
689e29
 
689e29
 const CpuIndicator = new Lang.Class({
689e29
     Name: 'SystemMonitor.CpuIndicator',
bc3989
     Extends: Indicator,
bc3989
 
bc3989
     _init: function() {
bc3989
         this.parent();
bc3989
 
bc3989
         this.gridColor = '-grid-color';
bc3989
         this.renderStats = [ 'cpu-user', 'cpu-sys', 'cpu-iowait' ];
bc3989
 
bc3989
         // Make sure renderStats is sorted as necessary for rendering
bc3989
         let renderStatOrder = {'cpu-total': 0, 'cpu-user': 1, 'cpu-sys': 2, 'cpu-iowait': 3};
bc3989
         this.renderStats = this.renderStats.sort(function(a,b) {
bc3989
             return renderStatOrder[a] - renderStatOrder[b];
bc3989
         });
bc3989
 
bc3989
 	this.setLabelText(_("CPU"));
bc3989
     },
bc3989
 
bc3989
     _initValues: function() {
bc3989
         this._prev = new GTop.glibtop_cpu;
bc3989
         GTop.glibtop_get_cpu(this._prev);
bc3989
 
bc3989
         this.stats = {
bc3989
                        'cpu-user': {color: '-cpu-user-color', values: []},
bc3989
                        'cpu-sys': {color: '-cpu-sys-color', values: []},
bc3989
                        'cpu-iowait': {color: '-cpu-iowait-color', values: []},
bc3989
                        'cpu-total': {color: '-cpu-total-color', values: []}
bc3989
                      };
bc3989
@@ -275,96 +290,81 @@ const MemoryIndicator = new Lang.Class({
bc3989
                         'mem-cached': { color: "-mem-cached-color", values: [] }
bc3989
                      };
bc3989
     },
bc3989
 
bc3989
     _updateValues: function() {
bc3989
         GTop.glibtop_get_mem(this.mem);
bc3989
 
bc3989
         let t = this.mem.user / this.mem.total;
bc3989
         this.stats['mem-user'].values.push(t);
bc3989
         t += (this.mem.used - this.mem.user - this.mem.cached) / this.mem.total;
bc3989
         this.stats['mem-other'].values.push(t);
bc3989
         t += this.mem.cached / this.mem.total;
bc3989
         this.stats['mem-cached'].values.push(t);
bc3989
     }
bc3989
 });
bc3989
 
bc3989
 const INDICATORS = [CpuIndicator, MemoryIndicator];
bc3989
 
bc3989
 const Extension = new Lang.Class({
bc3989
     Name: 'SystemMonitor.Extension',
bc3989
 
bc3989
     _init: function() {
bc3989
 	Convenience.initTranslations();
bc3989
 
bc3989
 	this._showLabelTimeoutId = 0;
bc3989
 	this._resetHoverTimeoutId = 0;
bc3989
 	this._labelShowing = false;
689e29
     },
689e29
 
689e29
     enable: function() {
689e29
-	this._box = new St.BoxLayout({ style_class: 'extension-systemMonitor-container',
689e29
-				       x_align: Clutter.ActorAlign.START,
689e29
-				       x_expand: true });
689e29
+	this._section = new MessageList.MessageListSection(_("System Monitor"));
689e29
 	this._indicators = [ ];
689e29
 
689e29
 	for (let i = 0; i < INDICATORS.length; i++) {
bc3989
 	    let indicator = new (INDICATORS[i])();
bc3989
 
689e29
             indicator.actor.connect('notify::hover', Lang.bind(this, function() {
689e29
 		this._onHover(indicator);
689e29
 	    }));
689e29
-	    this._box.add_actor(indicator.actor);
689e29
+	    this._section.addMessage(indicator, false);
689e29
 	    this._indicators.push(indicator);
689e29
 	}
689e29
 
689e29
-	this._boxHolder = new St.BoxLayout({ x_expand: true,
689e29
-					     y_expand: true,
689e29
-					     x_align: Clutter.ActorAlign.START,
689e29
-					   });
689e29
-	let menuButton = Main.messageTray._messageTrayMenuButton.actor;
689e29
-	Main.messageTray.actor.remove_child(menuButton);
689e29
-	Main.messageTray.actor.add_child(this._boxHolder);
689e29
-
689e29
-	this._boxHolder.add_child(this._box);
689e29
-	this._boxHolder.add_child(menuButton);
689e29
+        Main.panel.statusArea.dateMenu._messageList._addSection(this._section);
689e29
+        this._section.actor.get_parent().set_child_at_index(this._section.actor, 0);
689e29
     },
689e29
 
689e29
     disable: function() {
689e29
 	this._indicators.forEach(function(i) { i.destroy(); });
689e29
 
689e29
-	let menuButton = Main.messageTray._messageTrayMenuButton.actor;
689e29
-	this._boxHolder.remove_child(menuButton);
689e29
-	Main.messageTray.actor.add_child(menuButton);
689e29
-
689e29
-	this._box.destroy();
689e29
-	this._boxHolder.destroy();
689e29
+        Main.panel.statusArea.dateMenu._messageList._removeSection(this._section);
689e29
     },
689e29
 
689e29
     _onHover: function (item) {
bc3989
         if (item.actor.get_hover()) {
bc3989
             if (this._showLabelTimeoutId == 0) {
bc3989
                 let timeout = this._labelShowing ? 0 : ITEM_HOVER_TIMEOUT;
bc3989
                 this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
bc3989
                     Lang.bind(this, function() {
bc3989
                         this._labelShowing = true;
bc3989
                         item.showLabel();
bc3989
                         return false;
bc3989
                     }));
bc3989
                 if (this._resetHoverTimeoutId > 0) {
bc3989
                     Mainloop.source_remove(this._resetHoverTimeoutId);
bc3989
                     this._resetHoverTimeoutId = 0;
bc3989
                 }
bc3989
             }
bc3989
         } else {
bc3989
             if (this._showLabelTimeoutId > 0)
bc3989
                 Mainloop.source_remove(this._showLabelTimeoutId);
bc3989
             this._showLabelTimeoutId = 0;
bc3989
             item.hideLabel();
bc3989
             if (this._labelShowing) {
bc3989
                 this._resetHoverTimeoutId = Mainloop.timeout_add(ITEM_HOVER_TIMEOUT,
bc3989
                     Lang.bind(this, function() {
bc3989
                         this._labelShowing = false;
bc3989
                         return false;
bc3989
                     }));
bc3989
             }
bc3989
         }
689e29
diff --git a/extensions/systemMonitor/stylesheet.css b/extensions/systemMonitor/stylesheet.css
689e29
index 13f95ec..978ac12 100644
689e29
--- a/extensions/systemMonitor/stylesheet.css
689e29
+++ b/extensions/systemMonitor/stylesheet.css
bc3989
@@ -1,35 +1,21 @@
689e29
-.extension-systemMonitor-container {
689e29
-    spacing: 5px;
689e29
-    padding-left: 5px;
689e29
-    padding-right: 5px;
689e29
-    padding-bottom: 10px;
689e29
-    padding-top: 10px;
689e29
-}
689e29
-
689e29
 .extension-systemMonitor-indicator-area {
689e29
-    border: 1px solid #8d8d8d;
689e29
-    border-radius: 3px;
689e29
-    width: 100px;
689e29
-    /* message tray is 72px, so 20px padding of the container,
689e29
-       2px of border, makes it 50px */
689e29
     height: 50px;
689e29
     -grid-color: #575757;
689e29
     -cpu-total-color: rgb(0,154,62);
bc3989
     -cpu-user-color: rgb(69,154,0);
bc3989
     -cpu-sys-color: rgb(255,253,81);
bc3989
     -cpu-iowait-color: rgb(210,148,0);
689e29
     -mem-user-color: rgb(210,148,0);
689e29
     -mem-cached-color: rgb(90,90,90);
689e29
     -mem-other-color: rgb(205,203,41);
689e29
-    background-color: #1e1e1e;
689e29
 }
689e29
 
689e29
 .extension-systemMonitor-indicator-label {
bc3989
     border-radius: 7px;
bc3989
     padding: 4px 12px;
bc3989
     background-color: rgba(0,0,0,0.9);
bc3989
     text-align: center;
bc3989
     -y-offset: 8px;
bc3989
     font-size: 9pt;
bc3989
     font-weight: bold;
bc3989
 }
689e29
-- 
bc3989
2.17.1
689e29
689e29
bc3989
From e1133a8a92c49a90e02f8d2f1e66c7aae9d19519 Mon Sep 17 00:00:00 2001
689e29
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
689e29
Date: Thu, 18 May 2017 16:20:07 +0200
689e29
Subject: [PATCH 3/4] systemMonitor: Handle clicks on section title
689e29
689e29
While on 3.24.x only the event section still has a clickable title,
689e29
it's a generic message list feature in previous versions. It's easy
689e29
enough to support with a small subclass, so use that instead of
689e29
the generic baseclass.
689e29
689e29
Fixes: #3
689e29
---
689e29
 extensions/systemMonitor/extension.js | 20 +++++++++++++++++++-
689e29
 1 file changed, 19 insertions(+), 1 deletion(-)
689e29
689e29
diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js
bc3989
index 1388a1f..9c010d8 100644
689e29
--- a/extensions/systemMonitor/extension.js
689e29
+++ b/extensions/systemMonitor/extension.js
bc3989
@@ -276,75 +276,93 @@ const MemoryIndicator = new Lang.Class({
bc3989
         // Make sure renderStats is sorted as necessary for rendering
bc3989
         let renderStatOrder = { 'mem-cached': 0, 'mem-other': 1, 'mem-user': 2 };
bc3989
         this.renderStats = this.renderStats.sort(function(a,b) {
bc3989
             return renderStatOrder[a] - renderStatOrder[b];
bc3989
         });
bc3989
 
bc3989
 	this.setLabelText(_("Memory"));
bc3989
     },
bc3989
 
bc3989
     _initValues: function() {
bc3989
         this.mem = new GTop.glibtop_mem;
bc3989
         this.stats = {
bc3989
                         'mem-user': { color: "-mem-user-color", values: [] },
bc3989
                         'mem-other': { color: "-mem-other-color", values: [] },
bc3989
                         'mem-cached': { color: "-mem-cached-color", values: [] }
bc3989
                      };
bc3989
     },
bc3989
 
bc3989
     _updateValues: function() {
bc3989
         GTop.glibtop_get_mem(this.mem);
bc3989
 
bc3989
         let t = this.mem.user / this.mem.total;
bc3989
         this.stats['mem-user'].values.push(t);
bc3989
         t += (this.mem.used - this.mem.user - this.mem.cached) / this.mem.total;
bc3989
         this.stats['mem-other'].values.push(t);
bc3989
         t += this.mem.cached / this.mem.total;
bc3989
         this.stats['mem-cached'].values.push(t);
689e29
     }
689e29
 });
689e29
 
689e29
+const SystemMonitorSection = new Lang.Class({
689e29
+    Name: 'SystemMonitorSection',
689e29
+    Extends: MessageList.MessageListSection,
689e29
+
689e29
+    _init: function() {
689e29
+        this.parent(_("System Monitor"));
689e29
+    },
689e29
+
689e29
+    _onTitleClicked: function() {
689e29
+        this.parent();
689e29
+
689e29
+        let appSys = Shell.AppSystem.get_default();
689e29
+        let app = appSys.lookup_app('gnome-system-monitor.desktop');
689e29
+        if (app)
689e29
+            app.open_new_window(-1);
689e29
+    }
689e29
+});
689e29
+
689e29
 const INDICATORS = [CpuIndicator, MemoryIndicator];
689e29
 
689e29
 const Extension = new Lang.Class({
bc3989
     Name: 'SystemMonitor.Extension',
bc3989
 
bc3989
     _init: function() {
bc3989
 	Convenience.initTranslations();
bc3989
 
bc3989
 	this._showLabelTimeoutId = 0;
bc3989
 	this._resetHoverTimeoutId = 0;
bc3989
 	this._labelShowing = false;
689e29
     },
689e29
 
689e29
     enable: function() {
689e29
-	this._section = new MessageList.MessageListSection(_("System Monitor"));
689e29
+	this._section = new SystemMonitorSection();
689e29
 	this._indicators = [ ];
689e29
 
689e29
 	for (let i = 0; i < INDICATORS.length; i++) {
bc3989
 	    let indicator = new (INDICATORS[i])();
bc3989
 
bc3989
             indicator.actor.connect('notify::hover', Lang.bind(this, function() {
bc3989
 		this._onHover(indicator);
bc3989
 	    }));
bc3989
 	    this._section.addMessage(indicator, false);
bc3989
 	    this._indicators.push(indicator);
bc3989
 	}
bc3989
 
bc3989
         Main.panel.statusArea.dateMenu._messageList._addSection(this._section);
bc3989
         this._section.actor.get_parent().set_child_at_index(this._section.actor, 0);
bc3989
     },
bc3989
 
bc3989
     disable: function() {
bc3989
 	this._indicators.forEach(function(i) { i.destroy(); });
bc3989
 
bc3989
         Main.panel.statusArea.dateMenu._messageList._removeSection(this._section);
bc3989
     },
bc3989
 
bc3989
     _onHover: function (item) {
bc3989
         if (item.actor.get_hover()) {
bc3989
             if (this._showLabelTimeoutId == 0) {
bc3989
                 let timeout = this._labelShowing ? 0 : ITEM_HOVER_TIMEOUT;
bc3989
                 this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
bc3989
                     Lang.bind(this, function() {
bc3989
                         this._labelShowing = true;
bc3989
                         item.showLabel();
689e29
-- 
bc3989
2.17.1
689e29
689e29
bc3989
From d2a0c7bfdb3fedf56021b6fd64628e4cda1aa294 Mon Sep 17 00:00:00 2001
689e29
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
689e29
Date: Thu, 18 May 2017 18:00:17 +0200
689e29
Subject: [PATCH 4/4] systemMonitor: Provide classic styling
689e29
689e29
The indicator tooltips currently don't work out in classic mode
689e29
(dark text on dark background), so provide some mode-specific
689e29
style.
689e29
689e29
Fixes: #4
689e29
---
689e29
 extensions/systemMonitor/classic.css | 6 ++++++
bc3989
 extensions/systemMonitor/meson.build | 4 ++++
bc3989
 2 files changed, 10 insertions(+)
689e29
 create mode 100644 extensions/systemMonitor/classic.css
689e29
689e29
diff --git a/extensions/systemMonitor/classic.css b/extensions/systemMonitor/classic.css
689e29
new file mode 100644
689e29
index 0000000..946863d
689e29
--- /dev/null
689e29
+++ b/extensions/systemMonitor/classic.css
689e29
@@ -0,0 +1,6 @@
689e29
+@import url("stylesheet.css");
689e29
+
689e29
+.extension-systemMonitor-indicator-label {
689e29
+    background-color: rgba(237,237,237,0.9);
689e29
+    border: 1px solid #a1a1a1;
689e29
+}
bc3989
diff --git a/extensions/systemMonitor/meson.build b/extensions/systemMonitor/meson.build
bc3989
index 48504f6..b6548b1 100644
bc3989
--- a/extensions/systemMonitor/meson.build
bc3989
+++ b/extensions/systemMonitor/meson.build
bc3989
@@ -1,5 +1,9 @@
bc3989
 extension_data += configure_file(
bc3989
   input: metadata_name + '.in',
bc3989
   output: metadata_name,
bc3989
   configuration: metadata_conf
bc3989
 )
bc3989
+
bc3989
+if classic_mode_enabled
bc3989
+  extension_data += files('classic.css')
bc3989
+endif
689e29
-- 
bc3989
2.17.1
689e29