====== Gnome - Extensions - VPN Status ====== Displays if the system is connected to a VPN or not. ---- ===== Create the directory for the extension ===== mkdir ~/.local/share/gnome-shell/extensions/vpn-status@peterroux.com ---- ===== Create a metadata.json file ===== **metadata.json** is a mandatory file of the extension, containing information about the extension such as its UUID, name and description. { "_generated": "Generated by Peter Terence Roux, 2019", "description": "A VPN Status indicator.", "name": "VPN Status", "shell-version": [ "3.32", "3.34", "3.36", "3.38", "40", "42" ], "url": "", "uuid": "vpn-status@peterroux.com", "version": 2 } ---- ===== Create a extensions.js file ===== **extensions.js** is the core file of the extension and contains the function hooks **init()**, **enable()** and **disable()** used by GNOME Shell to load, enable and disable the extension. /* VPN Status. Displays whether a VPN is running or not in the top bar. Author: Peter Roux */ /* The Main instance our UI elements. */ const Main = imports.ui.main; /* The library that allows you to create UI elements. */ const St = imports.gi.St; /* This supports Canvas and UI elements. */ const Clutter = imports.gi.Clutter; /* For the animations of the UI elements. const Tweener = imports.ui.tweener; const Tweener = imports.tweener.tweener; */ const Util = imports.misc.util; const PanelMenu = imports.ui.panelMenu; const Mainloop = imports.mainloop; const GLib = imports.gi.GLib; /* Initialise global variables to use as button to click and text labels. */ let text, button, vpn_label; /* Hide the info. This function is called when the label is opacity 0%. As the label remains an UI element, but not visible, it has to be deleted explicitily. When the label reaches 0% opacity, it is removed from the Main instance. */ function _hideInfo() { Main.uiGroup.remove_actor(text); text.destroy(); text = null; } /* Show some info. This function is called when the main button is clicked. */ function _showInfo() { /* If the text is not already present, create a new UI element using the St library. REFERENCE: http://developer.gnome.org/st/stable/ */ if (!text) { text = new St.Label({ style: 'background-color: rgba(10,10,10,0.7); color: #ffffff; font-weight: bold; font-size: 250%;', style_class: 'vpn-status-text', text: "VPN Status: Written by Peter Roux!" }); Main.uiGroup.add_actor(text); } /* Set text to be fully visible at first. */ text.opacity = 255; /* Select the monitor to display the info label on. Here, the primary monitor is used. */ let monitor = Main.layoutManager.primaryMonitor; /* Change the position of the text to the center of the monitor. */ text.set_position(Math.floor(monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2)); /* Using tweener for the animations. Set the opacity at 0% after 5 seconds, with the type of transition easeOutQuad. When this animation is completed, execute our function _hideInfo. REFERENCE: http://hosted.zeh.com.br/tweener/docs/en-us/ */ /* Tweener.addTween(text, { opacity: 0, time: 5, transition: 'easeOutQuad', onComplete: _hideInfo }); */ // Tweener` is a deprecated module....so using this... text.ease({ //x: newX, //y: 10, opacity: 0, duration: 2000, mode: Clutter.AnimationMode.EASE_OUT_BOUNCE, onComplete: _hideInfo }); } /* Initialize the extension. */ function init() { } /* This does the actual work of determining and displaying the VPN status. */ function _refresh() { let [res, out, err, exit] = GLib.spawn_sync(null, ["/bin/bash", "-c","ip addr | grep tun0"], null, GLib.SpawnFlags.SEARCH_PATH, null); try { if (vpn_label) { if (exit == 256) vpn_label.set_text("VPN off"); else if (exit == 0) { //let [res2, out2, err2, exit2] = GLib.spawn_sync(null, ["/bin/bash", "-c","ip addr | grep 'state ' -A2 | tail -n1 | awk '{print $2}' | cut -f1 -d'/'"], null, GLib.SpawnFlags.SEARCH_PATH, null); let [res2, out2, err2, exit2] = GLib.spawn_sync(null, ["/bin/bash", "-c","ip addr | grep 'tun0' -A2 | grep inet | awk '{print $2}'"], null, GLib.SpawnFlags.SEARCH_PATH, null); if (exit2 == 0) vpn_label.set_text("VPN on" + " " + out2); else vpn_label.set_text("VPN on"); } else vpn_label.set_text("VPN Error"); } } catch (e) { // logError(e, 'ExtensionError'); } if (this._timeout) { Mainloop.source_remove(this._timeout); this._timeout = null; } // Calls the refresh function every 2 seconds. this._timeout = Mainloop.timeout_add(2, this._refresh); } /* Create any objects, connecting signals etc. */ function enable() { /* Create a button for the top panel. We pass to the constructor a map of properties, properties from St.bin and its parent classes, stWidget. A style class (from the css theming of gnome shell). We make it reactive to mouse clicks. We mark the button as also being able to receive keyboard focus via keyboard navigation. The button will expand the x space, but we do not want to expand the y space. We want the button to be reactive on the hover of a mouse, so we set the value of the track_hover property to true. */ button = new St.Bin({ style_class: 'panel-button', reactive: true, can_focus: true, x_expand: true, y_expand: false, track_hover: true }); /* We create an icon with the "system-status-icon" icon and give it the name "system-run" let vpn_label = new St.Icon({ icon_name: 'vpn-status', style_class: 'vpn-status-icon' }); */ /* Create a vpn label to display the VPN status. */ vpn_label = new St.Label({ text: _("Loading..."), y_align: Clutter.ActorAlign.CENTER }); /* Set the vpn label as a child of the button. In the structure of actors we have the vpn label inside the button that is a container. */ button.set_child(vpn_label); /* Connect the actor signal "button-press-event" of the button to the funcion _showInfo. When the button is pressed, this signal is emitted, we capture it and execute the _showInfo function. */ button.connect('button-press-event', _showInfo); /* Add the button that we created to the right side panel of the top panel (where the sound and wifi settings are). */ Main.panel._rightBox.insert_child_at_index(button, 0); /* Call the refresh function. */ this._refresh(); } /* Revert any changes, disconnect signals and destroy objects. */ function disable() { /* Destroy the timeout object. */ if (this._timeout) { Mainloop.source_remove(this._timeout); this._timeout = null; } /* Destroy the vpn object. */ if (vpn_label !== null) { vpn_label.destroy(); vpn_label = null; } /* Destroy the text object. */ if (text !== null) { text.destroy(); text = null; } /* Remove the button from the right panel. */ Main.panel._rightBox.remove_child(button); /* Destroy the button object. */ if (button !== null) { button.destroy(); button = null; } } **NOTE:** Ensure that the guidelines are followed at https://gjs.guide/extensions/review-guidelines/review-guidelines.html. ---- ===== Reload GNOME to pick up the extension ===== Press **Alt+F2**, and enter **r** and press **ENTER** to restart GNOME Shell. **NOTE:** Check if any error messages are being reported. sudo journalctl /usr/bin/gnome-shell | grep vpn ---- ===== Activate the extension ===== Open the **Gnome Tweak Tool** and activate the extension. ---- ===== References ===== https://extensions.gnome.org/ http://smasue.github.io/gnome-shell-tw https://gjs.guide/extensions/review-guidelines/review-guidelines.html