├── 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 |
--------------------------------------------------------------------------------