Bringing an electron app to foreground with a global shortcut (like Spotlight/Launchy) - javascript

I'm looking to replicate behavior similar to that of Launchy/Quicksilver/Spotlight.
I want to have an electron app that's always running. When I hit a shortcut key, the electron app is brought to the foreground and to focus.
I understand that the globalShortcut module can be used to bind a shortcut, however I can't figure out how to make that shortcut trigger bringing the app to the foreground.
Any help would be much appreciated...

Let's start with the simplest case and then build our solution to better handle some edge cases.
The simplest possible case is to show a window that is already open whenever the global shortcut we registered is pressed.
const path = require('path');
const { app, BrowserWindow, globalShortcut } = require('electron');
let mainWindow = null;
app.on('ready', () => {
mainWindow = new BrowserWindow();
mainWindow.loadURL(path.join(__dirname, 'index.html'));
const shortcut = globalShortcut.register('Control+Space', () => {
mainWindow.show();
});
if (!shortcut) { console.log('Registration failed.'); }
});
This code has some problems though. The good news is that it still works if the window has been minimized. The bad news is that it will not work if the window has been closed. This is because closing the last window quits the application. Bummer. (Frankly, I was a little surprised by this—but that's what happens. So, let's go with it.)
Let's stop that from happening.
app.on('window-all-closed', (event) => {
event.preventDefault();
});
Okay, our app doesn't quit, it but it crashes.
Uncaught Exception:
Error: Object has been destroyed
Alright, fine. This is because the window is destroyed when it's close. So, let's not close it. Let's hide it, shall we? Within app.on('ready', () => {…}), add the following:
mainWindow.on('close', (event) => {
event.preventDefault();
mainWindow.hide();
});
The end result looks like this:
const path = require('path');
const { app, BrowserWindow, globalShortcut } = require('electron');
let mainWindow = null;
app.on('ready', () => {
mainWindow = new BrowserWindow();
mainWindow.loadURL(path.join(__dirname, 'index.html'));
const shortcut = globalShortcut.register('Control+Space', () => {
mainWindow.show();
});
if (!shortcut) { console.log('Registration failed.'); }
mainWindow.on('close', (event) => {
event.preventDefault();
mainWindow.hide();
});
});
app.on('window-all-closed', (event) => {
event.preventDefault();
});
And with that you should have the basic functionality in place. You press your global shortcut and the window appears. Dismiss it and press the keys and watch it reappear.

Related

ipcRenderer not receiving the message from webContents.send (Electron)

In the main window of my program, I have a button that, if clicked, creates a new, additional window. When this new window finishes loading, I want to send a message to ipcRenderer; however, I have not been able so far to make ipcRenderer able to receive the message, even though the window is created successfully.
Here's a snippet of the code in main.js:
const { ipcMain } = require('electron');
ipcMain.handle('open-window', () => {
const newWindow = createWindow();
newWindow.on('did-finish-load', () => {
newWindow.webContents.send('opened-window');
});
});
Note that createWindow is a function that creates and returns a browser window.
And here's a snippet of the code in preload.js:
const { ipcRenderer } = require('electron');
window.addEventListener('DOMContentLoaded', () => {
document.getElementById('openWindow').addEventListener('click', () => {
ipcRenderer.invoke('open-window');
});
});
ipcRenderer.on('opened-window', () => {
console.log('received message!')
})
As you can see, I would expect to receive in the console the string received message! after the new window finishes loading; however, this is not happening. What am I doing wrong?
You are sending "opened-window" to the new window you created, not the original one in which the button was pressed.
newWindow.webContents.send('opened-window')
instead of newWindow, you need to refer to the window with the button and the opened-window handler

Electron: always returns ontouchstart = true

I'm currently working on a web application with Electron. If I open the app in the normal Chrome browser it works as expected: click as click, touch as touch.
But if I package it with electron it does not work right - it always returns touchstart = true, even though I'm not in the dev tools in the responsive view or on a device that is touchable - so clicks won't work.
This is the beginning of my javascript file:
var selectEvents = (function () {
if ('ontouchstart' in document === true) {
return "touchstart";
} else {
return "click";
}
})();
and this is my main.js for electron
const {app, BrowserWindow} = require('electron');
const path = require('path');
const url = require('url');
// init win
let win;
function createWindow(){
// create browser window
win = new BrowserWindow({width:3840, height: 1080, icon:__dirname+'/images/icons/bosch_logo.jpg', kiosk: true});
// load index.html
win.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}));
// open devtools
win.webContents.openDevTools();
win.on('closed', () => {
win = null;
});
}
// Run create window function
app.on('ready', createWindow);
// quit when all windows are closed
app.on('window-all-closed', () => {
// check for mac osx
if(process.platform !== 'darwin') {
app.quit();
}
})
In my HTML I created a button with the id 'next-button-1'
After an interaction it checks in the script if it's a click or a touchevent normally.
$('#next-button-1').on(selectEvents, function () {
...
}
Searched now quite a while on google but didn't find a working solution. Maybe you could help me
Added "click touchstart" to the first return. Now it works also on Electron. Hope this is reliable.
var selectEvents = (function () {
if ('ontouchstart' in document === true) {
return "click touchstart";
} else {
return "click";
}
})();

Electron - Close initial window but keep child open

I am coding an electron app which is supposed to load a splash screen, and then open a new window. Afterwards the splash screen should be closed.
However I am not able to do that. In my index.js, the startup script, I have the following code:
const { app, BrowserWindow } = require("electron");
app.on("ready", () => {
let win = new BrowserWindow({ /*...*/ });
win.loadURL(`file://${__dirname}/splash/splash.html`);
win.on("ready-to-show", () => { win.show(); });
win.on("closed", () => { app.quit(); });
});
In splash.html I load the splash.js by using
<script>require("./splash");</script>
And in splash.js I tried the following code:
const remote = require("electron").remote;
let tWin = remote.getCurrentWindow();
let next = function(){
let win = new remote.BrowserWindow({ /*...*/ });
win.loadURL(`file://${__dirname}/main/main.html`);
win.on("ready-to-show", () => { win.show(); });
win.on("closed", () => { app.quit(); });
tWin.close();
// I could just use win.hide(); here instead
// of tWin.close(); but that can't really be the right way.
};
The function next() gets called after a timeout. The problem is, if called, the main window shows up for a second but both, slpash and main close instantly.
I tried to fix that by commenting out
win.on("closed", () => { app.quit(); });
in my index.js. But that led to the following error:
Attempting to call a function in a renderer window that has been closed or released.
Uncaught Exception:
Error: Attempting to call a function in a renderer window that has been closed or released. Function provided here: splash.js:38:9.
at BrowserWindow.callIntoRenderer (/usr/lib/node_modules/electron-prebuilt/dist/resources/electron.asar/browser/rpc-server.js:199:19)
at emitOne (events.js:96:13)
at BrowserWindow.emit (events.js:188:7)
Does anyone have an idea on how to prevent the newly created window from closing?
I usually use a diferent approach. Here's how a use do it:
create global var reference for the main window and the splash window, if not it will be self closed by the garbage collector.
load 'splash' browserwindow
on 'show' event I call a function to load 'main' window
on main window 'dom-ready', I close 'splash' and show 'main'
Here's one example of my main.js electron code, feel free to ask:
'use strict';
//generic modules
const { app, BrowserWindow, Menu } = require('electron');
const path = require('path')
const url = require('url')
const config = require('./config'); // => 1: archivo de configuracion
const fileToLoad = config.files.current ? config.files.current : config.files.raw;
const jsonData = require(fileToLoad); // => 2: archivo de datos (json)
const pug = require('electron-pug')({ pretty: true }, jsonData); // => 3: pasamos datos ya tratados a plantillas pug/jade
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win, loading
app.mainWindow = win;
function initApp() {
showLoading(initPresentation)
}
function showLoading(callback) {
loading = new BrowserWindow({ show: false, frame: false })
loading.once('show', callback);
loading.loadURL(url.format({
pathname: path.join(__dirname, '/src/pages/loading.html'),
protocol: 'file:',
slashes: true
}))
loading.show();
}
function initPresentation() {
win = new BrowserWindow({
width: 1280,
height: 920,
show: false,
webPreferences: {
experimentalFeatures: true
}
})
win.webContents.once('dom-ready', () => {
console.log("main loaded!!")
win.setMenu(null);
win.show();
loading.hide();
loading.close();
})
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null
})
win.loadURL(url.format({
//pathname: path.join(__dirname, '/src/pages/home.pug'),
pathname: path.join(__dirname, '/lab/pug/index.pug'),
protocol: 'file:',
slashes: true
}))
win.webContents.openDevTools() // Open the DevTools.
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', initApp)
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
initApp()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.*/

Show and Hide Main Window on Electron

Instead of quit the application, I'd like to hide the main window when the system close button is clicked and show the main window when the application is clicked or activate. I'm using the following code to do this on my Electron app:
'use strict'
import { app, BrowserWindow } from 'electron'
let mainWindow
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:${require('../../../config').port}`
: `file://${__dirname}/index.html`
function createWindow () {
/**
* Initial window options
*/
mainWindow = new BrowserWindow({
height: 620,
width: 350,
resizable: true,
fullscreenable: true
})
mainWindow.loadURL(winURL)
mainWindow.on('closed', () => {
mainWindow.hide()
})
// eslint-disable-next-line no-console
console.log('mainWindow opened')
}
app.on('ready', createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
mainWindow.show()
})
But, hiding and showing the window from the activate and closed event shows the following error and never show the main window when the application is active.
Uncaught Exception:
Error: Object has been destroyed
at BrowserWindow.<anonymous> (/app/src/main/index.js:24:16): mainWindow.on('closed')
Not sure what else to do.
My solution is this:
import { app, BrowserWindow } from 'electron'
let win = null
function createWindow () {
win = new BrowserWindow({width: 1024, height: 768})
win.loadURL('...')
win.webContents.openDevTools()
win.on('close', (event) => {
if (app.quitting) {
win = null
} else {
event.preventDefault()
win.hide()
}
})
}
app.on('ready', createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => { win.show() })
app.on('before-quit', () => app.quitting = true)
In this way on OSX if you close the window, the window simply hide, if you close the app with cmd+Q the app terminate.
You can do this, and it will prevent the window from closing, and will just hide it.
You are listening to the closed event. But you need to listen to the close event. In that event, you can prevent the default action, and just do your hide.
mainWindow.on('close', event=>{
event.preventDefault(); //this prevents it from closing. The `closed` event will not fire now
mainWindow.hide();
})
Once you do this, you won't be able to close your window. So you will want to add a Menu to your app, with an accelerator of CmdOrCtrl+Q. Then in there, you can tell the app to quit.
const {app, Menu} = require('electron');
Menu.setApplicationMenu(Menu.buildFromTemplate([
{
label: "Quit",
accelerator: "CmdOrCtrl+Q",
click() {
app.quit();
}
}
]));
And this will allow you to Cmd+Q to quit your app.

Electron channel messages initiated from the main process not received by the renderer process

I'm building an electron app that has a OS tray menu.
In the main process I want to listen for clicks to the "About" menu item and then notify the renderer process so that it can update the window's view accordingly.
Here's the parts of my main process when I'm rendering the window and the tray menu:
const {app, BrowserWindow, Menu, Tray} = require('electron')
const appConfig = require('./appConfig')
const ipc = require('electron').ipcMain
const path = require('path')
const config = require('../config')
let win, tray
function createWindow(){
win = new BrowserWindow({
height: appConfig.window.height,
width: appConfig.window.width
})
win.loadURL(`file://${__dirname}/client/index.html`)
if(appConfig.debugMode){
win.webContents.openDevTools()
}
win.on('closed', () => {
win = null
})
}
function setTrayIcon(){
tray = new Tray(path.resolve(__dirname, './assets/rocketTemplate.png'))
tray.setToolTip('Launch applications by group')
let menuitems = config.groups.map(group => {
return {label: `Launch group: ${group.name}`}
})
win.webContents.send('ShowAboutPage' , {msg:'hello from main process'});
win.webContents.send('ShowAboutPage')
menuitems.unshift({type: 'separator'})
// Here where I add the "About" menu item that has the click event listener
menuitems.unshift({
label: 'About AppLauncher',
click(menuitem, browserWin, event){
// sending a log to the console to confirm that the click listener did indeed hear the click
console.log('about menu clicked')
win.webContents.send('ShowAboutPage')
}
})
menuitems.push({type: 'separator'})
menuitems.push({label: 'Quit AppLauncher'})
const contextMenu = Menu.buildFromTemplate(menuitems)
tray.setContextMenu(contextMenu)
}
app.on('ready', () => {
createWindow()
setTrayIcon()
})
And here's the renderer script that should be listening for the channel message:
const Vue = require('vue')
const App = require('./App.vue')
const ipc = require('electron').ipcRenderer
ipc.on('ShowAboutPage', () => {
console.log('show about fired in store')
alert('show about fired in store')
this.notificationMessage = 'Show about page'
})
require('./style/base.sass')
new Vue({
el: '#app',
render: h => h(App)
})
When I click the "About" menu in the tray, I get the console.log output from the main process, but I never receive the console.log or alert in the renderer process.
My app only ever has one window (as evidenced by the nullification of the win variable on window close) so it doesn't seem like an issue where I'm sending the message to the wrong renderer process.
I looked through the electron documentation on using webContents to send messages to renderer instances and it seems like my code is structured correctly, but obviously I'm missing something.
Any ideas?
Try rewriting your renderer script as
const { ipcRenderer } = require("electron");
ipcRenderer.on("ShowAboutPage", () => {
//etc
});

Categories

Resources