├── metadata.json ├── README.md └── extension.js /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Net Speed", 3 | "description": "Show current net speed on panel.", 4 | "uuid": "netspeed@alynx.one", 5 | "url": "https://github.com/AlynxZhou/gnome-shell-extension-net-speed/", 6 | "version": 14, 7 | "shell-version": ["45", "46", "47", "48", "49"] 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Net Speed 2 | ========= 3 | 4 | Show current net speed on panel. 5 | -------------------------------- 6 | 7 | This extension is inspired by which has some annoying flickers existing for a long time so I decide to create my own alternative. I removed all features that I won't use. 8 | 9 | It only shows text like `↓ 777 K/s ↑ 2.33 K/s` on the right part of panel. No font size changing, no total downloaded bytes, no bits. 10 | 11 | # Usage 12 | 13 | ``` 14 | $ git clone https://github.com/AlynxZhou/gnome-shell-extension-net-speed.git ~/.local/share/gnome-shell/extensions/netspeed@alynx.one 15 | ``` 16 | 17 | or install it from . 18 | 19 | Then restart GNOME Shell and enable Net Speed from GNOME Extensions. 20 | -------------------------------------------------------------------------------- /extension.js: -------------------------------------------------------------------------------- 1 | /* extension.js 2 | * 3 | * This program is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 2 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program. If not, see . 15 | * 16 | * SPDX-License-Identifier: GPL-2.0-or-later 17 | */ 18 | 19 | import GObject from "gi://GObject"; 20 | import GLib from "gi://GLib"; 21 | import Gio from "gi://Gio"; 22 | import Clutter from "gi://Clutter"; 23 | import St from "gi://St"; 24 | 25 | import * as Main from "resource:///org/gnome/shell/ui/main.js"; 26 | import * as PanelMenu from "resource:///org/gnome/shell/ui/panelMenu.js"; 27 | import {Extension} from "resource:///org/gnome/shell/extensions/extension.js"; 28 | 29 | const refreshInterval = 3; 30 | const speedUnits = [ 31 | "B/s", "K/s", "M/s", "G/s", "T/s", "P/s", "E/s", "Z/s", "Y/s" 32 | ]; 33 | // `ifb`: Created by python-based bandwidth manager "traffictoll". 34 | // `lxdbr`: Created by lxd container manager. 35 | // Add more virtual interface prefixes here. 36 | const virtualIfacePrefixes = [ 37 | "lo", "ifb", "lxdbr", "virbr", "br", "vnet", "tun", "tap", "docker", "utun", 38 | "wg", "veth" 39 | ]; 40 | 41 | const isVirtualIface = (name) => { 42 | return virtualIfacePrefixes.some((prefix) => { 43 | return name.startsWith(prefix); 44 | }); 45 | }; 46 | 47 | const formatSpeedWithUnit = (amount) => { 48 | let unitIndex = 0; 49 | while (amount >= 1000 && unitIndex < speedUnits.length - 1) { 50 | amount /= 1000; 51 | ++unitIndex; 52 | } 53 | 54 | let digits = 0; 55 | // Instead of showing 0.00123456 as 0.00, show it as 0. 56 | if (amount >= 100 || amount - 0 < 0.01) { 57 | // 100 M/s, 200 K/s, 300 B/s. 58 | digits = 0; 59 | } else if (amount >= 10) { 60 | // 10.1 M/s, 20.2 K/s, 30.3 B/s. 61 | digits = 1; 62 | } else { 63 | // 1.01 M/s, 2.02 K/s, 3.03 B/s. 64 | digits = 2; 65 | } 66 | 67 | // See . 68 | return `${amount.toFixed(digits)} ${speedUnits[unitIndex]}`; 69 | }; 70 | 71 | const toSpeedString = (speed) => { 72 | return `↓ ${formatSpeedWithUnit(speed["down"])} ↑ ${formatSpeedWithUnit(speed["up"])}`; 73 | }; 74 | 75 | const Indicator = GObject.registerClass( 76 | class Indicator extends PanelMenu.Button { 77 | _init() { 78 | // `menuAlignment`, `nameText`, `dontCreateMenu`. 79 | super._init(0.0, "Net Speed", true); 80 | 81 | this._label = new St.Label({ 82 | "y_align": Clutter.ActorAlign.CENTER, 83 | "text": toSpeedString({"down": 0, "up": 0}) 84 | }); 85 | 86 | this.add_child(this._label); 87 | } 88 | 89 | setText(text) { 90 | return this._label.set_text(text); 91 | } 92 | }); 93 | 94 | export default class NetSpeed extends Extension { 95 | constructor(metadata) { 96 | super(metadata); 97 | 98 | this._metadata = metadata; 99 | this._uuid = metadata.uuid; 100 | } 101 | 102 | enable() { 103 | this._textDecoder = new TextDecoder(); 104 | this._lastSum = {"down": 0, "up": 0}; 105 | this._timeout = null; 106 | 107 | this._indicator = new Indicator(); 108 | // `role`, `indicator`, `position`, `box`. 109 | // `-1` is not OK for position, it will show at the right of system menu. 110 | Main.panel.addToStatusArea(this._uuid, this._indicator, 0, "right"); 111 | 112 | this._timeout = GLib.timeout_add_seconds( 113 | GLib.PRIORITY_DEFAULT, refreshInterval, () => { 114 | const speed = this.getCurrentNetSpeed(refreshInterval); 115 | const text = toSpeedString(speed); 116 | // console.log(text); 117 | this._indicator.setText(text); 118 | // Run as loop, not once. 119 | return GLib.SOURCE_CONTINUE; 120 | } 121 | ); 122 | } 123 | 124 | disable() { 125 | if (this._indicator != null) { 126 | this._indicator.destroy(); 127 | this._indicator = null; 128 | } 129 | this._textDecoder = null; 130 | this._lastSum = null; 131 | if (this._timeout != null) { 132 | GLib.source_remove(this._timeout); 133 | this._timeout = null; 134 | } 135 | } 136 | 137 | getCurrentNetSpeed(refreshInterval) { 138 | const speed = {"down": 0, "up": 0}; 139 | 140 | try { 141 | const inputFile = Gio.File.new_for_path("/proc/net/dev"); 142 | const [, content] = inputFile.load_contents(null); 143 | // See . 144 | // 145 | // `ByteArray` is deprecated with ES Module, standard JavaScript 146 | // `TextDecoder` should be used here. 147 | // 148 | // Caculate the sum of all interfaces line by line, skip table head. 149 | const sum = this._textDecoder.decode(content).split("\n").map((line) => { 150 | return line.trim().split(/\W+/); 151 | }).filter((fields) => { 152 | return fields.length > 2; 153 | }).map((fields) => { 154 | return { 155 | "name": fields[0], 156 | "down": Number.parseInt(fields[1]), 157 | "up": Number.parseInt(fields[9]) 158 | }; 159 | }).filter((iface) => { 160 | return !(isNaN(iface["down"]) || isNaN(iface["up"]) || 161 | isVirtualIface(iface["name"])); 162 | }).reduce((sum, iface) => { 163 | return { 164 | "down": sum["down"] + iface["down"], 165 | "up": sum["up"] + iface["up"] 166 | }; 167 | }, {"down": 0, "up": 0}); 168 | 169 | if (this._lastSum["down"] === 0) { 170 | this._lastSum["down"] = sum["down"]; 171 | } 172 | if (this._lastSum["up"] === 0) { 173 | this._lastSum["up"] = sum["up"]; 174 | } 175 | 176 | speed["down"] = (sum["down"] - this._lastSum["down"]) / refreshInterval; 177 | speed["up"] = (sum["up"] - this._lastSum["up"]) / refreshInterval; 178 | 179 | this._lastSum = sum; 180 | } catch (e) { 181 | console.error(e); 182 | } 183 | 184 | return speed; 185 | } 186 | }; 187 | --------------------------------------------------------------------------------