Cross distribution terminal opening - node.js

In electron, I am registering a shortcut to open a terminal:
globalShortcut.register('CommandOrControl+Alt+Shift+P', () => {
spawn(os.platform() === 'linux' ? 'xterm' : 'cmd');
});
I noticed that xterm don't have the same "style" of the terminal opened from OS menu and I found that the latter is customized by the Desktop Environment (I have an Ubuntu Mate where its terminal is mate-terminal and a RedHat 7.5 with konsole).
I read here that nodejs don't have an API to detect the distribution, so it seems not feasible to know which terminal to run depending on distribution.
Is there any way to open the correct terminal or to style xterm as the OS one?

TL;DR: There's no standardised API for that and you cannot be sure that a distribution indicates the terminal application.
First of all, XTerm, Mate's Terminal, Konsole, etc. are all different applications. And because Ubuntu Mate under the hood reports as Ubuntu, AFAIK, you cannot even be certain that this is the application registered as "the" terminal app. Also, nearly every desktop environment brings its own terminal application, and since you can install multiple terminal applications side by side, it is at most an educated guess to automatically select an "appropriate" terminal application.
However, there are multiple approaches which can be considered to solve this (aesthetic) problem:
Let the user decide what terminal application they want. If your application has a configuration file, you could use that to write a value specified by the user and take that as the terminal app. In theory, they could specify a path to an application which is not a terminal app, but then it's their fault.
Compile a list of known terminal applications and check if they're installed. For example, you could make an (ordered) list of the apps your application should search for and if none of them is found, fall back to XTerm (because that is installed on most if not all desktop systems running Linux). To see if an application is installed and runnable on Linux, you can use the which command. Example:
const { spawnSync } = require ("child_process");
const terms = [ "konsole", "mate-terminal", "gnome-terminal" /* ... */ ];
var terminal = null;
// In your startup method (e.g. before opening a BrowserWindow)
if (os.platform () === "linux") {
for (term in terms) {
which = spawnSync ("which", [term]);
if (which.status !== null && which.status === 0) { // Command found
terminal = term;
break;
}
}
if (terminal === null) terminal = "xterm";
} else {
terminal = "cmd";
}
Of course you could also use spawn in favour of spawnSync but this will get more complex. Also, this only provides you with the first application found to be installed, it does not select the one "appropriate" for the desktop environment. But I believe it is a good step in the right direction if you want your application the be (somewhat) seemlessly integrated into the DE.
As a side note, your code does not take MacOS into account, but it is also possible to open a terminal on MacOS, it's simply called "Terminal" there.

Node.js doesn't provide a method to detect the name of the distribution natively, because there is no standard way to get that information from different Linux distributions.
But you can do it with a library sush as getos:
const getos = require('getos');
getos((err, os) => {
if (err) return console.log(err);
console.log(os.dist, os.codename)
})

There is a package called OpenTerm, it has configurable function which automatically determines terminal to use, and it currently supports windows and linux, but not MacOs.
const { VTexec } = require('open-term')
VTexec('help') // Runs "help" command in determined terminal.
"help" command would work both on bash and cmd. So i use it here as example.

Related

Detect Key Press in System

I am trying to detect a keypress event outside the console tab, I have allready tried input-event (for linux), hotkeys-js (for browser), and iohook (gives error). How can I do it?
I found a way. I used iohook, but the version 0.6.6 since the new version crashed, and called the start method.
iohook.on('keypress', (res) => {
let key = String.fromCharCode(res.rawcode);
console.log(key);
});
iohook.start();

Node JS - Systeminformation, cpuTemperature function does not return temperature

I am working on a project where I want to access and display the temperature of my CPU. I found a tutorial where I via the "systeminformation" package can gain access to mutliple CPU stats. But when I use the cpuTemperature function it does not return the temperature, instead it returns "-1" and empty list and "-1".
si.cpuTemperature(function(data) {
console.log('CPU-Temperature:');
console.log(data);
});
=> { main: -1, cores: [], max: -1 }
Am I doing something wrong or is my CPU not supporting this type of event?
Tutorial I have used: https://github.com/sebhildebrandt/systeminformation
I presume you are trying that on windows. Accoording to docs you need to run wmic with admin privileges, if you don't get any data. For OSX and Linux you also need some extra packages installed like osx-temperature-sensor Node module for OSX, and the sensors package (lm-sensors for Debian based) Linux machines. You can try to run node from an Administrator terminal to check out the privilege issue on Windows.

Electron app installed but doesn't appear in start menu

I've created an app using Electron, and bundled it in an .exe file with electron-builder.
When I run the generated executable, the application starts with the default installation GIF used by electron-builder, as expected.
After the GIF finishes, the app restarts and works properly. It even appears in control panel's programs list.
However, if I look for it in the start menu applications, it isn't there (and searching it by its name only returns the aforementioned .exe installer).
Because of this, once the app is closed, the only way to open it back is running again the installer.
Why does this happen? Is there any way to make it appear with the other programs?
The electron-builder installer (and electron-windows-installer) use Squirrel for handling the installation. Squirrel launches your application on install with arguments that you need to handle. An example can be found on the windows installer github docs
Handling Squirrel Events
Squirrel will spawn your app with command line flags on first run, updates, and uninstalls. it is very important that your app handle these events as early as possible, and quit immediately after handling them. Squirrel will give your app a short amount of time (~15sec) to apply these operations and quit.
The electron-squirrel-startup module will handle the most common events for you, such as managing desktop shortcuts. Just add the following to the top of your main.js and you're good to go:
if (require('electron-squirrel-startup')) return;
You should handle these events in your app's main entry point with something such as:
const app = require('app');
// this should be placed at top of main.js to handle setup events quickly
if (handleSquirrelEvent()) {
// squirrel event handled and app will exit in 1000ms, so don't do anything else
return;
}
function handleSquirrelEvent() {
if (process.argv.length === 1) {
return false;
}
const ChildProcess = require('child_process');
const path = require('path');
const appFolder = path.resolve(process.execPath, '..');
const rootAtomFolder = path.resolve(appFolder, '..');
const updateDotExe = path.resolve(path.join(rootAtomFolder, 'Update.exe'));
const exeName = path.basename(process.execPath);
const spawn = function(command, args) {
let spawnedProcess, error;
try {
spawnedProcess = ChildProcess.spawn(command, args, {detached: true});
} catch (error) {}
return spawnedProcess;
};
const spawnUpdate = function(args) {
return spawn(updateDotExe, args);
};
const squirrelEvent = process.argv[1];
switch (squirrelEvent) {
case '--squirrel-install':
case '--squirrel-updated':
// Optionally do things such as:
// - Add your .exe to the PATH
// - Write to the registry for things like file associations and
// explorer context menus
// Install desktop and start menu shortcuts
spawnUpdate(['--createShortcut', exeName]);
setTimeout(app.quit, 1000);
return true;
case '--squirrel-uninstall':
// Undo anything you did in the --squirrel-install and
// --squirrel-updated handlers
// Remove desktop and start menu shortcuts
spawnUpdate(['--removeShortcut', exeName]);
setTimeout(app.quit, 1000);
return true;
case '--squirrel-obsolete':
// This is called on the outgoing version of your app before
// we update to the new version - it's the opposite of
// --squirrel-updated
app.quit();
return true;
}
};
Notice that the first time the installer launches your app, your app will see a --squirrel-firstrun flag. This allows you to do things like showing up a splash screen or presenting a settings UI. Another thing to be aware of is that, since the app is spawned by squirrel and squirrel acquires a file lock during installation, you won't be able to successfully check for app updates till a few seconds later when squirrel releases the lock.
In this example you can see it run Update.exe (a squirrel executable) with the argument --create-shortcut that adds start menu and desktop shortcuts.
It's 2021 and I am still having a very similar problem.
My app installs correctly and with the script above it also successfully adds a Desktop link to my app. BUT: There is no Shortcut being added to the Windows Start Menu.
With the script above this should also be added to the Start Menu, right?
One comment above says:
// Install desktop and start menu shortcuts
spawnUpdate(['--createShortcut', exeName]);
What am I missing? Any hint highly appreciated...
For me it helped to add icon and setupIcon to the package.json file, where the makers, are configured. Before my app didnt show up in the Start menu, and with the maker config as below, it does. I am not sure why though.
"makers": [
{
"name": "#electron-forge/maker-squirrel",
"config": {
"name": "cellmonitor",
"icon": "favicon.ico",
"setupIcon": "favicon.ico"
}
}
]

Use Node.js as Shell

How might I set up node.js as a shell replacement for bash? For example I should be able to run vi('file') to open a file and cd('location') to change between directories.
Is this even possible?
Sure you can! It will become much less straightforward to use your computer, though.
First off, you will need to know how to set this up. While you could likely set your user shell in Linux to usr/bin/node, this will leave you with only a Node.js REPL with no additional programs set up. What you're going to want to do is write a setup script that can do all of the below setup/convenience steps for you, essentially something that ends with repl.start() to produce a REPL after setting everything up. Of course, since UNIX shell settings can't specify arguments, you will need to write a small C program that executes your shell with those arguments (essentially, exec("/usr/bin/node", "path/to/setup/script.js");) and set that as your UNIX shell.
The main idea here is that any commands that you use beyond the very basics must be require()d into your shell - e.g. to do anything with your filesystem, execute
var fs = require("fs")
and do all of your filesystem calls from the fs object. This is analogous to adding things to your PATH. You can get basic shell commands by using shelljs or similar, and to get at actual executable programs, use Node's built-in child_process.spawnSync for a foreground task or child_process.spawn for a background task.
Since part of your requirement is that you want to call each of your programs like a function, you will need to produce these functions yourself, getting something like:
function ls(path) {
child_process.spawnSync('/bin/ls', [path], { stdio: 'inherit' });
}
for everything you want to run. You can probably do this programmatically by iterating through all the entries in your PATH and using something involving eval() or new Function() to generate execute functions for each, assigning them to the global object so that you don't have to enter any prefixes.
Again, it will become much less straightforward to use your computer, despite having these named functions. Lots of programs that cheat and use bash commands in the background will likely no longer work. But I can certainly see the appeal of being able to leverage JavaScript in your command-line environment.
ADDENDUM: For writing this setup script, the REPLServer object returned by repl.start() has a context object that is the same as the global object accessible to the REPL session it creates. When you write your setup script, you will need to assign everything to the context object:
const context = repl.start('> ').context;
context.ls = function ls(path) { /* . . . */ }
context.cd = function cd(path) { /* . . . */ }
I think it would be an intersting proposition. Create a test account and tell it to use node as it's shell. See 'man useradd' for all options
$ useradd -s /usr/bin/node test
$ su - test
This works on mac and linux.
require('child_process').spawnSync('vi', ['file.txt'], { stdio: 'inherit' })
You could bootstrap a repl session with your own commands, then run the script
#!/bin/bash
node --experimental-repl-await -i -e "$(< scripts/noderc.js)"`
This allows for things like:
> ls()
> run('vi','file.txt')
> await myAsyncFunc()
I think you're looking for something like this https://youtu.be/rcwcigtOwQ0 !
If so.... YES you can!
If you like I can share my code. But I need to fix some bugs first!
tell me if you like.
my .sh function:
const hey = Object.create(null),
sh = Object.create(null);;
hey.shell = Object.create(null);
hey.shell.run = require('child_process').exec;
sh.help = 'Execute an OS command';
sh.action = (...args) => {
// repl_ is the replServer
// the runningExternalProgram property is one way to know if I should
// render the prompt and is not needed. I will create a better
// way to do this (action without if/decision)!
repl_.runningExternalProgram = true;
hey.shell.run(args.join(' '),
(...args) => {
['error', 'log'].forEach((command, idx) => {
if (args[idx]) {
console[command](args[idx]);
}
});
repl_.runningExternalProgram = false;
});
};
PS: to 'cd' into some directory you just need to change the process.cwd (current working directory)
PS2: to avoid need to write .sh for every OS program/command you can use Proxy on the global object.

Get a unique re-useable identifier of node (cli-)app what is running in different terminal windows in parallel by same user?

I like to get an unique reusable identifier of the terminal window my node app (a CLI) is running in. The reason I like to know this is the common use of the app by the same user within multiple terminals at the same time.
It is necessary to get back selected data for each terminal and also getting to know what is selected on different terminal windows the CLI-App is running in.
The best way to save this would be in a hidden log file basically named by a unique terminal identifier.
[x] Working on process.platform === 'win32' each cmd.exe has its own PID.
[ ] Working on process.platform === 'darwin' (Mac OS X) the PPID is the same on all terminal windows.
[ ] Working on process.platform === 'freebsd' || 'linux' || 'sunos' I assume it is the same problem also depending on the GUI you are running.
Executing tty on the terminal gives me different identifiers like /dev/ttys003 what would be useful.
But trying to get this out of the app is not possible because executing this within a child_process ends up in a not a tty!
The PID of the app itself is changing every time it is executed. Thats not helpful in this case. The parent PID is the same (but also in different terminals).
Setting env will not be bound to the terminal but only to the apps runtime.
Is there any idea to get this done ?
I got it myself:
SpoTTY is the solution for posix based systems.
The example on the page has a misleading bug - use this example to get it done:
// A clone of the `tty` command
var spotty = require('spotty');
var tty = spotty.tty(function(err, res) {
if (err) {
console.error("Error found: " + err.toString())
} else {
return res;
}
})
hope this helps someone.

Resources