Electron app installed but doesn't appear in start menu - node.js

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"
}
}
]

Related

Cross distribution terminal opening

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.

detect if Kofax launched custom module or User

When the custom module gets launched I can use
if (Environment.UserInteractive)
{
// Run as WinForms app
}
else
{
// Run as service
}
to switch between a background service and a WinForms app. But I can also run the .exe file without launching Kofax.
Is it possible to check if Kofax launched the module? My example code would look like
if (Environment.UserInteractive)
{
// Run as WinForms app
if (Application.LaunchedByKofax)
{
// Do something additional
}
}
else
{
// Run as service
}
The only context in which Kofax Capture launches your custom module is when a user tries to process a batch from Batch Manager, and that batch is currently in the queue for your custom module. If you are referring to something other than that, then you'll need to clarify your question.
When that happens, the path registered for your custom module is called with additional parameters, the most notable of which is -B###, where ### is the decimal batch ID. For more details on this see Kofax KB article 1713, which is old but still applicable for current versions.
Thus you can use a function like this to check for the expected parameters.
public bool LaunchedFromBatchManager()
{
var args = Environment.GetCommandLineArgs();
//args[0] will contain the path to your exe, subsquent items are the actual args
if (args.Count() > 1)
{
// When a user tries to process a batch from batch manager,
// it launches the module with -B###, where ### is the decimal batch ID
// see: http://knowledgebase.kofax.com/faqsearch/results.aspx?QAID=1713
if (args[1].StartsWith("-B"))
{
return true;
}
}
return false;
}

Run main process as child_process

I am having troubles with getting child_process working with Atom Electron. First of all, I am using the pre-compiled binary app that you can download from Electron's website:
I am using the usual pre-compiled binary on Mac OS X.
In myapp.app/Contents/Resources I made a folder app as described.
I added a brief package.json inside it, setting index.js as main script.
Now, if I add to index.js the following snippet:
'use strict';
var electron = require('electron');
var app = electron.app;
const BrowserWindow = electron.BrowserWindow;
var mainWindow;
function createWindow () {
mainWindow = new BrowserWindow({width: 800, height: 600});
mainWindow.loadURL('file://' + __dirname + '/index.html');
mainWindow.webContents.openDevTools();
mainWindow.on('closed', function()
{
mainWindow = null;
});
}
app.on('ready', createWindow);
app.on('window-all-closed', function () {
if (process.platform !== 'darwin')
{
app.quit();
}
});
app.on('activate', function () {
if (mainWindow === null)
{
createWindow();
}
});
(that is basically the example code to get things working) everything works fine. I get the window and I can get it to do basically anything.
Now, for reasons related to updates, I am in need to slightly change this paradigm. What I would need is to be able to perform several tasks from index.js without any need to do any gui operation (it should be some sort of daemon) and then to start some child.js script as a child_process from index.js. child.js should be able to open windows and all the rest.
So here was my naive try. I just cut and pasted the example snippet above in child.js, then edited index.js into the following:
var child_process = require('child_process');
var my_child = child_process.fork(__dirname + '/child.js');
Quite minimal, right? Hoped it would work, but it didn't. When I double click on my pretty app, nothing happens. I bet I am doing something wrong in a trivial way, but I wouldn't be able to tell what.
Update 1 I moved this out of my package so that I could get console.logs. child.js dies with an error at require('electron'): it doesn't seem to be able to find it.
Update 2: I listed the environment variables in child.js and noticed a ATOM_SHELL_INTERNAL_RUN_AS_NODE = 1. I thought I should turn that to 0, but nothing changed.
In Electron child_process.fork() will spawn the child with the ATOM_SHELL_INTERNAL_RUN_AS_NODE environment variable set, which means that no Chromium features will be available in the child process (so no Electron built-in modules), all you'll get is the vanilla Node runtime plus a patched fs that can read inside ASAR files. If you look at the code for child_process.fork() you'll see there's no way to override the value of ATOM_SHELL_INTERNAL_RUN_AS_NODE. If you can, use child_process.spawn() instead, failing that I guess you could try opening an issue and asking if the Electron team will be open to modifying the current behavior.

Remove terminal icon in node notifier

I am using the https://github.com/mikaelbr/node-notifier package to show notifications in shell.
This is my code:
var notifier = require('node-notifier');
var path = require('path');
notifier.notify({
title: 'My awesome title',
message: 'Hello from node, Mr. User!',
icon: path.join(__dirname, 'coulson.jpg'), // absolute path (not balloons)
sound: true, // Only Notification Center or Windows Toasters
wait: true // wait with callback until user action is taken on notification
}, function (err, response) {
// response is response from notification
});
notifier.on('click', function (notifierObject, options) {
// Happens if `wait: true` and user clicks notification
});
notifier.on('timeout', function (notifierObject, options) {
// Happens if `wait: true` and notification closes
});
The notification comes like this:
As you can see a terminal icon is coming before the name.
Can you help me how to remove that icon?
It is known issue with node-notifier.
From issue #71:
mikaelbr:
No, I'm afraid that's how the notification work, as it's the terminal that initiates the message. The only way to avoid this is to use your custom terminal-notifier where the Terminal icon is swapped for your own. It's not a big task, and you can easily set customPath for notification center reporter.
kurisubrooks:
This happens because of the way notifications in OS X work. The notification will show the referring app icon, and because we're using terminal-notifier to push notifications, we have the icon of terminal-notifier.
To work around this, you'll need to compile terminal-notifier with your own app.icns. Download the source, change out the AppIcon bundle with your own in Xcode, recompile terminal-notifier and pop it into node-notifier. (/node-notifier/vendor/terminal-notifier.app)
Now that you have your own terminal-notifier inside node-notifier, remove all the icon references from your OS X Notification Center code, and run the notification as if it has no icon. If the old app icon is showing in your notifications, you need to clear your icon cache. (Google how to do this)
Another valuable comment from mikaelb:
That's correct. But keep in mind, node-notifier uses a fork of terminal-notifier (github.com/mikaelbr/terminal-notifier) to add option to wait for notification to finish, so this should be used to add your own icon. A easy way to do it is to copy/paste from the vendor-folder and use customPath to point to your own vendor.
I tried #Aleksandr M's steps but it didn't seem to work for me. Maybe I didn't understand the steps well enough. Here's how it worked for me.
I cloned https://github.com/mikaelbr/terminal-notifier . Then opened the project with xcode and deleted the Terminal.icns file and replaced it with my custom icon Myicon.icns.
Then edited terminal-notifier/Terminal Notifier/Terminal Notifier-Info.plist by setting the key icon file to Myicon.
After doing this, simply building the project did NOT work. I had to change the values of the build version and build identifier (any new value would do) see this.
Afterwards I just built the project with xcode and then copied the built .app file (you can find it by clicking the Products directory of the project from xcode Products > right click the file > show in finder) to my electron project
e.g your final path may look like this. electron-project/vendor/terminal-notifier.app.
Then I set customPath as #Aleksandr M suggested.
Here's what mine looked like
var notifier = new NotificationCenter({
customPath: 'vendor/terminal-notifier.app/Contents/MacOS/terminal-notifier'
});
And THEN it worked! 🙂
This solved my problem and you only need to have your icns file ready:
run this command in terminal after downloading :customise-terminal-notifier
** path/customise-terminal-notifier-master/customise-terminal-notifier -i path/Terminal.icns -b com.bundle.identifier

Execute node command from UI

I am not very much familiar with nodejs but, I need some guidance in my task. Any help would be appreciated.
I have nodejs file which runs from command line.
filename arguments and that do some operation whatever arguments I have passed.
Now, I have html page and different options to select different operation. Based on selection, I can pass my parameters to any file. that can be any local node js file which calls my another nodejs file internally. Is that possible ? I am not sure about what would be my approach !
I always have to run different command from terminal to execute different task. so, my goal is to reduce that overhead. I can select options from UI and do operations through nodejs file.
I was bored so I decided to try to answer this even though I'm not totally sure it's what you're asking. If you mean you just need to run a node script from a node web app and you normally run that script from the terminal, just require your script and run it programmatically.
Let's pretend this script you run looks like this:
// myscript.js
var task = process.argv[2];
if (!task) {
console.log('Please provide a task.');
return;
}
switch (task.toLowerCase()) {
case 'task1':
console.log('Performed Task 1');
break;
case 'task2':
console.log('Performed Task 2');
break;
default:
console.log('Unrecognized task.');
break;
}
With that you'd normally do something like:
$ node myscript task1
Instead you could modify the script to look like this:
// Define our task logic as functions attached to exports.
// This allows our script to be required by other node apps.
exports.task1 = function () {
console.log('Performed Task 1');
};
exports.task2 = function () {
console.log('Performed Task 2');
};
// If process.argv has more than 2 items then we know
// this is running from the terminal and the third item
// is the task we want to run :)
if (process.argv.length > 2) {
var task = process.argv[2];
if (!task) {
console.error('Please provide a task.');
return;
}
// Check the 3rd command line argument. If it matches a
// task name, invoke the related task function.
if (exports.hasOwnProperty(task)) {
exports[task]();
} else {
console.error('Unrecognized task.');
}
}
Now you can run it from the terminal the same way:
$ node myscript task1
Or you can require it from an application, including a web application:
// app.js
var taskScript = require('./myscript.js');
taskScript.task1();
taskScript.task2();
Click the animated gif for a larger smoother version. Just remember that if a user invokes your task script from your web app via a button or something, the script will be running on the web server and not the user's local machine. That should be obvious but I thought I'd remind you anyway :)
EDIT
I already did the video so I'm not going to redo it, but I just discovered module.parent. The parent property is only populated if your script was loaded from another script via require. This is a better way to test if your script is being run directly from the terminal or not. The way I did it might have problems if you pass an argument in when you start your app.js file, such as --debug. It would try to run a task called "--debug" and then print out "Unrecognized task." to the console when you start your app.
I suggest changing this:
if (process.argv.length > 2) {
To this:
if (!module.parent) {
Reference: Can I know, in node.js, if my script is being run directly or being loaded by another script?

Resources