diff --git a/extension.js b/extension.js
index c528cad6..8d396d90 100644
--- a/extension.js
+++ b/extension.js
@@ -39,6 +39,7 @@ var VitalsMenuButton = GObject.registerClass({
'network' : { 'icon': 'network-symbolic.svg',
'icon-rx': 'network-download-symbolic.svg',
'icon-tx': 'network-upload-symbolic.svg',
+ 'icon-ping': 'network-ping-symbolic.svg',
'icon-ad': '../flags/1x1/ad.svg',
'icon-ae': '../flags/1x1/ae.svg',
'icon-af': '../flags/1x1/af.svg',
@@ -337,7 +338,8 @@ var VitalsMenuButton = GObject.registerClass({
'fixed-widths', 'hide-icons', 'unit',
'memory-measurement', 'include-public-ip', 'network-public-ip-interval',
'network-public-ip-show-flag', 'network-speed-format', 'storage-measurement',
- 'include-static-info', 'include-static-gpu-info' ];
+ 'include-static-info', 'include-static-gpu-info',
+ 'network-ping-host' ];
for (let setting of Object.values(settings))
this._addSettingChangedSignal(setting, this._redrawMenu.bind(this));
diff --git a/icons/gnome/network-ping-symbolic.svg b/icons/gnome/network-ping-symbolic.svg
new file mode 100644
index 00000000..b798bef5
--- /dev/null
+++ b/icons/gnome/network-ping-symbolic.svg
@@ -0,0 +1,8 @@
+
+
diff --git a/icons/original/network-ping-symbolic.svg b/icons/original/network-ping-symbolic.svg
new file mode 100644
index 00000000..04834170
--- /dev/null
+++ b/icons/original/network-ping-symbolic.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/prefs.js b/prefs.js
index bdeb9a80..eee7a2e0 100644
--- a/prefs.js
+++ b/prefs.js
@@ -78,6 +78,24 @@ const Settings = new GObject.Class({
this._settings.bind('network-public-ip-interval', this.builder.get_object('network-public-ip-interval'),
'value', Gio.SettingsBindFlags.DEFAULT);
+ let pingHostEntry = this.builder.get_object('network-ping-host');
+ let pingHostSave = this.builder.get_object('network-ping-host-save');
+ let getSavedPingHost = () => this._settings.get_string('network-ping-host');
+ let savePingHost = () => {
+ if (pingHostEntry.get_text() === getSavedPingHost())
+ return;
+
+ this._settings.set_string('network-ping-host', pingHostEntry.get_text());
+ pingHostSave.set_sensitive(false);
+ };
+
+ pingHostEntry.set_text(getSavedPingHost());
+ pingHostEntry.connect('changed', (widget) => {
+ pingHostSave.set_sensitive(widget.get_text() !== getSavedPingHost());
+ });
+ pingHostEntry.connect('activate', () => savePingHost());
+ pingHostSave.connect('clicked', () => savePingHost());
+
// process individual text entry sensor preferences
sensors = [ 'storage-path', 'monitor-cmd' ];
for (let key in sensors) {
diff --git a/prefs.ui b/prefs.ui
index b3c8c4b2..01a10278 100644
--- a/prefs.ui
+++ b/prefs.ui
@@ -147,6 +147,47 @@
+
+
+
diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled
index b84716f2..30155af9 100644
Binary files a/schemas/gschemas.compiled and b/schemas/gschemas.compiled differ
diff --git a/schemas/org.gnome.shell.extensions.vitals.gschema.xml b/schemas/org.gnome.shell.extensions.vitals.gschema.xml
index fef102db..34cb02b7 100644
--- a/schemas/org.gnome.shell.extensions.vitals.gschema.xml
+++ b/schemas/org.gnome.shell.extensions.vitals.gschema.xml
@@ -94,6 +94,11 @@
Network speed format
Should speed display in bits or bytes?
+
+ ""
+ Ping target host
+ Hostname or IP address to ping for latency measurement
+
"/"
Storage path
diff --git a/sensors.js b/sensors.js
index d61b0909..54a2bc19 100644
--- a/sensors.js
+++ b/sensors.js
@@ -27,6 +27,7 @@
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import * as SubProcessModule from './helpers/subprocess.js';
+import Gio from 'gi://Gio';
import * as FileModule from './helpers/file.js';
import { gettext as _ } from 'resource:///org/gnome/shell/extensions/extension.js';
import NM from 'gi://NM';
@@ -46,6 +47,8 @@ export const Sensors = GObject.registerClass({
this._settings = settings;
this._sensorIcons = sensorIcons;
+ this._pingCancellable = null;
+
this.resetHistory();
this._last_processor = { 'core': {}, 'speed': [] };
@@ -54,6 +57,7 @@ export const Sensors = GObject.registerClass({
this._addSettingChangedSignal('show-gpu', this._reconfigureNvidiaSmiProcess.bind(this));
this._addSettingChangedSignal('update-time', this._reconfigureNvidiaSmiProcess.bind(this));
this._addSettingChangedSignal('network-public-ip-interval', () => {this._lastPublicIPCheck = 0;});
+ this._addSettingChangedSignal('network-ping-host', () => this._pingCancellable?.cancel());
//this._addSettingChangedSignal('include-static-gpu-info', this._reconfigureNvidiaSmiProcess.bind(this));
this._gpu_drm_vendors = null;
@@ -338,6 +342,63 @@ export const Sensors = GObject.registerClass({
this._returnValue(callback, 'WiFi Signal Level', signal, 'network', 'string');
}
}).catch(err => { });
+
+ this._pollPing(value => {
+ this._returnValue(callback, 'Ping', value, 'network-ping', 'string');
+ });
+ }
+
+ _pollPing(callback) {
+ let host = this._settings.get_string('network-ping-host').trim();
+ if (!host) return;
+
+ this._pingCancellable?.cancel();
+ this._pingCancellable = new Gio.Cancellable();
+ let cancellable = this._pingCancellable;
+
+ let subprocess;
+ try {
+ subprocess = new Gio.Subprocess({
+ argv: ['ping', '-c', '1', '-W', '5', host],
+ flags: Gio.SubprocessFlags.STDOUT_PIPE |
+ Gio.SubprocessFlags.STDERR_PIPE,
+ });
+ subprocess.init(null);
+ } catch (e) {
+ callback('?? ms');
+ return;
+ }
+
+ cancellable.connect(() => subprocess.force_exit());
+
+ subprocess.communicate_utf8_async(null, cancellable, (proc, result) => {
+ let value = this._parsePingResult(proc, result, cancellable);
+ callback(value);
+ });
+ }
+
+ _parsePingResult(proc, result, cancellable) {
+ let stdout, exitStatus;
+ try {
+ [, stdout] = proc.communicate_utf8_finish(result);
+ exitStatus = proc.get_exit_status();
+ } catch (e) {
+ let cancelled = cancellable.is_cancelled();
+ return cancelled ? this._pingTimeoutLabel() : '?? ms';
+ }
+
+ if (exitStatus === 0 && stdout) {
+ let match = /time[=<]([\d.]+)\s*ms/.exec(stdout);
+ if (match)
+ return `${parseFloat(match[1]).toFixed(1)} ms`;
+ }
+
+ return exitStatus === 1 ? this._pingTimeoutLabel() : '?? ms';
+ }
+
+ _pingTimeoutLabel() {
+ let timeoutMs = Math.min(Math.max(this._settings.get_int('update-time'), 1), 5) * 1000;
+ return `${timeoutMs}+ ms`;
}
_queryStorage(callback, dwell) {
@@ -1066,11 +1127,13 @@ export const Sensors = GObject.registerClass({
this._frameMonitorLastTime = 0;
this._frameMonitorFrameCount = 0;
this._frameMonitorAccTime = 0;
+ this._pingCancellable?.cancel();
}
destroy() {
this._destroyFrameMonitor();
this._terminateNvidiaSmiProcess();
+ this._pingCancellable?.cancel();
for (let signal of Object.values(this._settingChangedSignals))
this._settings.disconnect(signal);