diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d80f912
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
+Net Speed
+=========
+
+Show current net speed on panel.
+--------------------------------
+
+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.
+
+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.
diff --git a/extension.js b/extension.js
new file mode 100644
index 0000000..2c95df7
--- /dev/null
+++ b/extension.js
@@ -0,0 +1,211 @@
+/* extension.js
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/* exported init */
+
+// const GETTEXT_DOMAIN = "net-speed";
+
+const {GObject, GLib, Gio, Clutter, St} = imports.gi;
+
+// const Gettext = imports.gettext.domain(GETTEXT_DOMAIN);
+// const _ = Gettext.gettext;
+
+const ByteArray = imports.byteArray;
+const ExtensionUtils = imports.misc.extensionUtils;
+const Main = imports.ui.main;
+const PanelMenu = imports.ui.panelMenu;
+// const PopupMenu = imports.ui.popupMenu;
+
+const refreshInterval = 3;
+const speedUnits = [
+ "B/s", "K/s", "M/s", "G/s", "T/s", "P/s", "E/s", "Z/s", "Y/s"
+];
+let lastTotalDownBytes = 0;
+let lastTotalUpBytes = 0;
+
+const getCurrentNetSpeed = (refreshInterval) => {
+ const speed = {"down": 0, "up": 0};
+
+ try {
+ const inputFile = Gio.File.new_for_path("/proc/net/dev");
+ const fileInputStream = inputFile.read(null);
+ // See .
+ // If we want new operator, we need to pass params in object.
+ // Short param is only used for static constructor.
+ const dataInputStream = new Gio.DataInputStream({
+ "base_stream": fileInputStream
+ });
+
+ // Caculate the sum of all interfaces' traffic line by line.
+ let totalDownBytes = 0;
+ let totalUpBytes = 0;
+ let line = null;
+ // See .
+ while ((line = dataInputStream.read_line(null)) != null) {
+ // See .
+ // It seems Uint8Array is only returned at the first time.
+ if (line instanceof Uint8Array) {
+ line = ByteArray.toString(line).trim();
+ } else {
+ line = line.toString().trim();
+ }
+ const fields = line.split(/\W+/);
+ if (fields.length <= 2) {
+ break;
+ }
+
+ // Skip virtual interfaces.
+ const interface = fields[0];
+ const currentInterfaceDownBytes = Number.parseInt(fields[1]);
+ const currentInterfaceUpBytes = Number.parseInt(fields[9]);
+ if (interface == "lo" ||
+ // Created by python-based bandwidth manager "traffictoll".
+ interface.match(/^ifb[0-9]+/) ||
+ // Created by lxd container manager.
+ interface.match(/^lxdbr[0-9]+/) ||
+ interface.match(/^virbr[0-9]+/) ||
+ interface.match(/^br[0-9]+/) ||
+ interface.match(/^vnet[0-9]+/) ||
+ interface.match(/^tun[0-9]+/) ||
+ interface.match(/^tap[0-9]+/) ||
+ isNaN(currentInterfaceDownBytes) ||
+ isNaN(currentInterfaceUpBytes)) {
+ continue;
+ }
+
+ totalDownBytes += currentInterfaceDownBytes;
+ totalUpBytes += currentInterfaceUpBytes;
+ }
+
+ fileInputStream.close(null);
+
+ if (lastTotalDownBytes === 0) {
+ lastTotalDownBytes = totalDownBytes;
+ }
+ if (lastTotalUpBytes === 0) {
+ lastTotalUpBytes = totalUpBytes;
+ }
+
+ speed["down"] = (totalDownBytes - lastTotalDownBytes) / refreshInterval;
+ speed["up"] = (totalUpBytes - lastTotalUpBytes) / refreshInterval;
+
+ lastTotalDownBytes = totalDownBytes;
+ lastTotalUpBytes = totalUpBytes;
+ } catch (e) {
+ logError(e);
+ }
+
+ return speed;
+};
+
+const formatSpeedWithUnit = (amount) => {
+ let unitIndex = 0;
+ while (amount >= 1000 && unitIndex < speedUnits.length - 1) {
+ amount /= 1000;
+ ++unitIndex;
+ }
+
+ let digits = 0;
+ // Instead of showing 0.00123456 as 0.00, show it as 0.
+ if (amount >= 100 || amount - 0 < 0.01) {
+ // 100 M/s, 200 K/s, 300 B/s.
+ digits = 0;
+ } else if (amount >= 10) {
+ // 10.1 M/s, 20.2 K/s, 30.3 B/s.
+ digits = 1;
+ } else {
+ // 1.01 M/s, 2.02 K/s, 3.03 B/s.
+ digits = 2;
+ }
+
+ // See .
+ return `${amount.toFixed(digits)} ${speedUnits[unitIndex]}`;
+};
+
+const toSpeedString = (speed) => {
+ return `↓ ${formatSpeedWithUnit(speed["down"])} ↑ ${formatSpeedWithUnit(speed["up"])}`;
+};
+
+const Indicator = GObject.registerClass(
+class Indicator extends PanelMenu.Button {
+ _init() {
+ // menuAlignment, nameText, dontCreateMenu.
+ super._init(0.0, "Net Speed", true);
+
+ this._label = new St.Label({
+ "y_align": Clutter.ActorAlign.CENTER,
+ "text": "---"
+ });
+
+ this.add_child(this._label);
+
+ // let item = new PopupMenu.PopupMenuItem(_("Show Notification"));
+ // item.connect("activate", () => {
+ // Main.notify(_("Whatʼs up, folks?"));
+ // });
+ // this.menu.addMenuItem(item);
+ }
+
+ setText(text) {
+ return this._label.set_text(text);
+ }
+});
+
+class Extension {
+ constructor(uuid) {
+ this._uuid = uuid;
+
+ // ExtensionUtils.initTranslations(GETTEXT_DOMAIN);
+ }
+
+ enable() {
+ lastTotalDownBytes = 0;
+ lastTotalUpBytes = 0;
+
+ this._indicator = new Indicator();
+ // role, indicator, position, box.
+ // -1 is not OK, because it will show in the right side of system menu.
+ Main.panel.addToStatusArea(this._uuid, this._indicator, 0, "right");
+
+ this._timeout = GLib.timeout_add_seconds(
+ GLib.PRIORITY_DEFAULT, refreshInterval, () => {
+ const speed = getCurrentNetSpeed(refreshInterval);
+ const text = toSpeedString(speed);
+ // log(text);
+ this._indicator.setText(text);
+ // Run as loop, not once.
+ return GLib.SOURCE_CONTINUE;
+ }
+ )
+ }
+
+ disable() {
+ if (this._indicator != null) {
+ this._indicator.destroy();
+ this._indicator = null;
+ }
+ if (this._timeout != null) {
+ GLib.source_remove(this._timeout);
+ this._timeout = null;
+ }
+ }
+}
+
+function init(meta) {
+ return new Extension(meta.uuid);
+}
diff --git a/metadata.json b/metadata.json
new file mode 100644
index 0000000..052b081
--- /dev/null
+++ b/metadata.json
@@ -0,0 +1,9 @@
+{
+ "name": "Net Speed",
+ "description": "Show current net speed on panel.",
+ "uuid": "netspeed@alynx.one",
+ "version": 1,
+ "shell-version": [
+ "40"
+ ]
+}
diff --git a/stylesheet.css b/stylesheet.css
new file mode 100644
index 0000000..37b93f2
--- /dev/null
+++ b/stylesheet.css
@@ -0,0 +1 @@
+/* Add your custom extension styling here */