I've created a custom GJS extension to connect into VPN. Basically it's a wrapper around a shell script, which is controlled from taskbar. There is a one issue, that after PC goes into suspend mode and back, extension is still displaying, that is disconnected, while it's still connected.
const GObject = imports.gi.GObject;
const St = imports.gi.St;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Gettext = imports.gettext;
const _ = Gettext.gettext;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const MainLoop = imports.mainloop;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
let close_connection = `
pkexec kill -SIGINT $(pidof openconnect) 2>&1
`;
let create_connection = `
trap clean SIGINT
clean() {
pkexec kill -SIGINT $(pidof openconnect) 2>&1
}
vpn-ura-pke &
wait
`;
let _icon;
let _connectionSwitch;
let _last_connection = false;
let _already_running = false;
let _proc = null;
const iconsState = [
'network-vpn-acquiring-symbolic', // disconnected
'network-vpn-symbolic' // connected
];
function setConnectionState(connected) {
// prevent same notification changing
if (_last_connection == connected) return;
_icon.icon_name = iconsState[connected ? 1 : 0];
Main.notify('VPN URA', (connected ? 'connected' : 'disconnected'));
_last_connection = connected;
}
// read line callback
function onProcLine(stream, result) {
try {
let line = stream.read_line_finish_utf8(result)[0];
if (line !== null) {
// process read line
log("onProcLine:" + line);
// check connection status
if (line.includes('Connected as ')) setConnectionState(true);
else if(line.includes('Logout successful')) setConnectionState(false);
stream.read_line_async(0, null, onProcLine.bind(this));
}
} catch (ex) {
logError(ex);
}
}
// exec async process
async function execCheck(argv) {
_proc = new Gio.Subprocess({
argv: argv,
flags: (Gio.SubprocessFlags.STDIN_PIPE |
Gio.SubprocessFlags.STDOUT_PIPE |
Gio.SubprocessFlags.STDERR_PIPE)
});
_proc.init(null);
try {
let stdoutStream = new Gio.DataInputStream({
base_stream: _proc.get_stdout_pipe()
});
stdoutStream.read_line_async(
GLib.PRIORITY_DEFAULT,
null,
onProcLine.bind(this)
);
_proc.wait_check_async(null, (_proc, res) => {
try {
if (!_proc.wait_check_finish(res)) {
let status = _proc.get_exit_status();
setConnectionState(false);
if (status != 0) {
throw new Gio.IOErrorEnum({
code: Gio.io_error_from_errno(status),
message: GLib.strerror(status)
});
}
}
} catch (ex) {
setConnectionState(false);
logError(ex);
} finally {
_connectionSwitch.setToggleState(false);
}
});
} catch (ex) {
logError(ex);
}
}
const Indicator = GObject.registerClass(
class Indicator extends PanelMenu.Button {
toggleConnection(enabled) {
if (enabled) {
log("enable connection");
// start process
execCheck([
'bash',
'-c',
create_connection]
);
} else {
log("disable conenction");
// close running process
if (_proc) _proc.send_signal(2);
else if (_already_running) {
// kill process
Gio.Subprocess.new([
'bash',
'-c',
close_connection],
Gio.SubprocessFlags.STDOUT_PIPE
);
_already_running = false;
setConnectionState(false);
}
}
}
_init() {
super._init(0.0, _('VPN URA'));
// set icon
_icon = new St.Icon({
icon_name: iconsState[0],
style_class: 'system-status-icon'
});
this.add_child(_icon);
// toggle connection
_connectionSwitch = new PopupMenu.PopupSwitchMenuItem('Connection', false);
_connectionSwitch.connect('toggled', (_item, state) => {
this.toggleConnection(state);
});
this.menu.addMenuItem(_connectionSwitch);
// check if process is not already running
let [, , , status] = GLib.spawn_command_line_sync('pidof openconnect');
if (status == 0) {
_already_running = true;
_connectionSwitch.setToggleState(true);
setConnectionState(true);
}
}
});
class Extension {
constructor(uuid) {
this._uuid = uuid;
}
enable() {
_already_running = false;
this._indicator = new Indicator();
Main.panel.addToStatusArea(this._uuid, this._indicator, 1);
}
disable() {
_proc = null;
_connectionSwitch = null;
_last_connection = false;
_icon.destroy();
_icon = null;
this._indicator.destroy();
this._indicator = null;
}
}
function init(meta) {
return new Extension(meta.uuid);
}
Even if I added a case, where I was trying to use pidof to detect if process is running already. It caught only the case, when process was started outside from extension, but not the case, which I wanted.
How to bind into callback, which is fired up when session is restored? Or is there any another way to handle this?
Thanks, Andy
I do also attempt to write a basic gnome extension for the same purpose. Being very new to gjs coding, I found those answers regarding connection updating, in my research. Hope some of it might help you :
Util.spawnCommandLine does not work on GNOME Shell extension
How to get OS name while writing gnome-extensions
GLib run command with root privileges
Running an asynchronous function in a GNOME extension