Gnome shell extension login callback - gnome

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

Related

Where do I execute my on-first-start functions in my Electron app?

I am using Electron 9 and I have a main process and a single render process. On the first start of my application I would like to execute some code which is not executed on the second run.
Does Electron have a dedicated location where I should do this? Any help is highly appreciated!
Use app.getPath('userData') - it's dedicated location for your apps data for current user (eg. in windows it will point to something like AppData/Roaming/app-name/)
At startup use:
app.on('ready', () => {
const firstTimeFilePath = path.resolve(app.getPath('userData'), '.first-time-huh');
let isFirstTime;
try {
fs.closeSync(fs.openSync(firstTimeFilePath, 'wx'));
isFirstTime = true;
} catch(e) {
if (e.code === 'EEXIST') {
isFirstTime = false;
} else {
// something gone wrong
throw e;
}
}
// ...
});
Profit!
https://nodejs.org/api/fs.html#fs_file_system_flags - why use wx flag
https://nodejs.org/api/fs.html#fs_fs_opensync_path_flags_mode - fs.openSync()
https://www.electronjs.org/docs/api/app#appgetpathname - app.getPath()
If you want to write out default preferences in the first run and read them in the next runs, try this:
import defaults from './default_preferences.json'; // will work for plain js objects too
let prefs = defaultPrefs;
app.on('ready', () => {
const prefsPath = path.resolve(app.getPath('userData'), 'prefs.json');
let isFirstTime;
try {
fs.writeFileSync(prefsPath, JSON.stringify(defaultPrefs), { flag: 'wx' });
isFirstTime = true;
} catch (e) {
if (e.code === 'EEXIST') {
// slight posibility of races, you can eleminate it by using `singleInstanceLock` or waiting loop for `write` flag
prefs = require(prefsPath);
isFirstTime = false;
} else {
// something gone wrong
throw e;
}
}
...
});

How to run a function in caller context?

I have a function in a module that simulates shell.
function shell() {
while(1) {
let code = readline.question(">> ");
if(code == "") continue;
if(code == "exit") break;
try {
console.log(eval(code));
} catch (e) {
console.log(e.message);
}
}
}
module.exports = shell;
I'm calling this shell function inside another js file in hope of accessing all the variables and functions defined inside that caller function. Like below:
const shell = require('./shell.js');
var EXPIRY_DATES = ["28MAY2020"];
shell();
function parse_data() {
return "somedata";
}
But I'm not able to access EXPIRY_DATES and parse_data() from inside the shell. How to do this?
(I tried call and bind but not successful.)
Consider passing an object containing the properties (variables) you want the other script to be able to access, then reference that object when evaling:
function shell(vars) {
const result = eval('vars.EXPIRY_DATES');
console.log(result);
// other code in shell
}
(() => {
var EXPIRY_DATES = ["28MAY2020"];
shell({ EXPIRY_DATES });
module.exports = shell;
})();
Logged result:
[ '28MAY2020' ]

NodeJS: call func from inside another func in same file

I have NodeJS program.
In one class, I have various utility methods. One function, safeGithubPush, calls safeString, another func in the same class
module.exports = {
safeString(stringToCheck) {
console.log(validator.isAscii(stringToCheck), validator.matches(stringToCheck, /^((\w)*[-.]?(\w)*)*$/))
return (
validator.isAscii(stringToCheck) &&
validator.matches(stringToCheck, /^((\w)*[-.]?(\w)*)*$/)
);
},
safeGithubPush(currentJob) {
if (
!currentJob ||
!currentJob.payload ||
!currentJob.payload.repoName ||
!currentJob.payload.repoOwner ||
!currentJob.payload.branchName
) {
this.logIn(
currentJob,
`${' (sanitize)'.padEnd(15)}failed due to insufficient job definition`
);
throw invalidJobDef;
}
if (
this.safeString(currentJob.payload.repoName) &&
this.safeString(currentJob.payload.repoOwner) &&
this.safeString(currentJob.payload.branchName)
) {
return true;
}
throw invalidJobDef;
},
}
While this.logIn(), another func in the utility class, works just fine, I get the error for safeString:
Error caught by first catch: TypeError: this.safeString is not a function
I followed a solution offer by another SO post:
safeString: function(stringToCheck){
...
}
safeGithubPush(currentJob) {
...
if (
this.safeString(currentJob.payload.repoName) &&
this.safeString(currentJob.payload.repoOwner) &&
this.safeString(currentJob.payload.branchName)
) {
return true;
}
}
But this also gets a, TypeError: this.safeString is not a function.
I'm not using arrow functions, which is the explanation for this error on a different SO post
I don't think the reason is determinable with the code you are currently presenting. It likely has something to do with how you are calling safeGithubPush. If you do something that would change the this binding the this.safeString is going to fail.
const foo = {
fizz() {
console.log("fizz");
},
buzz() {
this.fizz();
}
};
// "this" is correct
foo.buzz();
// "this" has no fizz to call
const myFizz = foo.buzz;
myFizz();
Considering you are attaching these to module.exports I am going to guess that you pull these functions off in a require call and then try to use them bare which makes the problem obvious after looking at my example above:
// Ignore these 2 lines, they let this look like node
const module = {};
const require = () => module.exports;
// Ignore above 2 lines, they let this look like node
// Your module "some.js"
module.exports = {
safeString(str) {
return true;
},
safeGithubPush(currentJob) {
if (!this.safeString("some")) {
throw new Error("Not safe");
}
return true;
}
};
try {
// Some consumer module that doesn't work
const {safeGithubPush} = require("./some.js");
const isItSafe = safeGithubPush();
console.log(`Safe? ${isItSafe}`);
} catch (err) {
console.error("Didn't bind right \"this\"");
}
try {
// Some consumer module that DOES work
const someModule = require("./some.js");
const isItSafe = someModule.safeGithubPush();
console.log(`Safe? ${isItSafe}`);
} catch (err) {
console.error(err);
}
I would restructure this code. You say these are utility functions which makes me think you don't really want to have to structure them with this in mind.
Instead of attaching them all to module.exports at their definition, define them outside and directly reference the functions you want to use, then attach them to exports so other modules can use the functions:
function safeString(stringToCheck) {
return true;
}
function safeGithubPush(currentJob) {
if (!safeString("some")) {
throw new Error("Not safe");
}
return true;
}
module.exports = {
safeString,
safeGithubPush
};

Cast Application Framework receiver app not working on external cast device 2nd Geneation

I'm trying to develop CAF v3 receiver app and it's working with in-built and setup boxes cast devices but not working on external cast devices.
External cast devices NC2-A65 throws PIPELINE_INITIALIZATION_ERROR or VIDEO_ERROR with shaka error code 3016
After debugging, observation is drm License Url doesn't get called when useLegacyDashSupport is true
Any help is appreciated
Here is the code,
<script>
const context = cast.framework.CastReceiverContext.getInstance();
context.setLoggerLevel(cast.framework.LoggerLevel.DEBUG);
const options = new cast.framework.CastReceiverOptions();
const castDebugLogger = cast.debug.CastDebugLogger.getInstance();
const playerManager = context.getPlayerManager();
const playbackConfig = (Object.assign(new cast.framework.PlaybackConfig(), playerManager.getPlaybackConfig()));
options.maxInactivity = 3600;
options.supportedCommands = cast.framework.messages.Command.ALL_BASIC_MEDIA;
castDebugLogger.setEnabled(true);
// Show debug overlay
castDebugLogger.showDebugLogs(true);
let useLegacyDashSupport = false;
if (context.canDisplayType('video/mp4; codecs="avc1.640028"; width=3840; height=2160')) {
// The device and display can both do 4k. Assume a 4k limit.
castDebugLogger.info("Hardware Resolution: ", '3840x2160');;
options.touchScreenOptimizedApp = true;
} else {
// Chromecast has always been able to do 1080p. Assume a 1080p limit.
castDebugLogger.info("Hardware Resolution: ", '1920x1080');
useLegacyDashSupport = true;
}
options.useLegacyDashSupport = useLegacyDashSupport;
context.loadPlayerLibraries(useLegacyDashSupport);
context.addEventListener(cast.framework.system.EventType.ERROR, event => {
castDebugLogger.info('Context Error - ', JSON.stringify(event));
});
playerManager.addEventListener(cast.framework.events.EventType.ERROR, event => {
castDebugLogger.info('Error - ', "ERROR event: " + JSON.stringify(event));
});
playerManager.addEventListener(cast.framework.events.EventType.MEDIA_STATUS, (event) => {
castDebugLogger.info('Player State - ', event.mediaStatus.playerState);
});
// Intercept the LOAD request to be able to read in a contentId and get data.
playerManager.setMessageInterceptor(cast.framework.messages.MessageType.LOAD, loadRequestData => {
castDebugLogger.info('LoadRequest Data - ', JSON.stringify(loadRequestData));
const error = new cast.framework.messages.ErrorData(cast.framework.messages.ErrorType.LOAD_CANCELLED);
if (!loadRequestData.media) {
error.reason = cast.framework.messages.ErrorReason.INVALID_PARAM;
return error;
}
if (!loadRequestData.media.contentId) {
error.reason = cast.framework.messages.ErrorReason.INVALID_PARAM;
return error;
}
loadRequestData.autoplay = true;
let url = loadRequestData.media.contentId;
castDebugLogger.info('Content Id - ', url);
const ext = url.substring(url.lastIndexOf('.'), url.length);
loadRequestData.media.contentType = 'video/mp4';
if (ext.includes('mpd')) {
loadRequestData.media.contentType = 'application/dash+xml';
} else if (ext.includes('m3u8')) {
loadRequestData.media.contentType = 'application/vnd.apple.mpegurl';
// TODO: Create option to set hlsSegmentFormat option.
loadRequestData.media.hlsSegmentFormat = cast.framework.messages.HlsSegmentFormat.TS;
} else if (ext.includes('ism')) {
loadRequestData.media.contentType = 'application/vnd.ms-sstr+xml';
}
if (loadRequestData.media.customData && loadRequestData.media.customData.drm) {
playerManager.setMediaPlaybackInfoHandler((loadRequest, playbackConfigData) => {
playbackConfigData.licenseUrl = loadRequest.media.customData.drm.widevine.url;
playbackConfigData.protectionSystem = cast.framework.ContentProtection.WIDEVINE;
castDebugLogger.info('PlaybackConfig Data - ', JSON.stringify(playbackConfigData));
return playbackConfigData;
});
}
return loadRequestData;
});
options.playbackConfig = playbackConfig;
context.start(options);
</script>

Terminate node.js script from another node.js script

I have a node.js script that runs another node.js script named 'app.js' using child_process.exec(). I need something that can terminate 'app.js'. Either a command that I can use in command line or something else in node.js.
I'm not trying to kill all node processes, only the process of 'app.js'. My computer is running Windows 10.
Right now the code looks like this:
let isLaunched = false;
const exec = require('child_process').exec;
function launch() {
if (isLaunched) {
isLaunched = false;
// Something that terminates app.js
} else {
isLaunched = true;
exec('node app.js');
}
}
I call the function from a button in HTML.
<button onclick="launch()">Launch</button>
How can I do this in node.js?
var isLaunched = false;
var { exec } = require('child_process');
var myProcess; //use whatever name
function launch() {
if (isLaunched) {
isLaunched = false;
myProcess.kill('SIGKILL');
} else {
isLaunched = true;
myProcess = exec('node app.js');
}
}

Resources