Harmonic Part 2 - Adding global keyboard shortcuts to an Electron Web App

electron tutorial

This post is the second in a series of how to use Electron (formerly atom-shell) to turn a Web Application into a Desktop Application. For this tutorial, We’ll be working on a cross-platform desktop client for Google Play Music. This second post will focus on adding global shortcut keys for controlling the application. It builds on the code from the first post. You can download the code from GitHub.

NOTE: I have decided to put this series on hold until the 1.0 release of Electron, at which point I will update the original posts and continue with the series.

Understanding Processes (and how to talk between them)

When an Electron app is started, the main process is created, which loads and runs the code in the file specified in the package.json file (in our case, index.js). Any windows that are created are spawned in a new renderer process. Because of this, the main process isn’t able to directly interact with the renderer processes. Instead, the main process sends messages to the renderer process via the IPC Message module. To receive these messages, the renderer process must register handlers to listen on the correct channel, using the IPC Renderer Message module.

The IPC Message module provides methods for sending both synchronous and asynchronous messages, in both directions.

To send a message from the main process to a renderer process, in this case window, use the method WebContents.send(channel[, args…])

window.webContents.send('ping', 'Hi';)

This will send a message to channel ‘ping’ in the renderer process, with the value ‘Hi’.

Registering the Global Shortcuts

The first step is to register the global shortcuts in the main process. These global shortcuts don’t require the window to be focused, as they are registered with the OS.

So, open up the index.js file, and load the module at the top where the other modules are loaded, by adding the line

globalShortcut = require('global-shortcut')

Then, once the mainWindow has been created, register the shortcuts with

var register_play = globalShortcut.register('ctrl+space', function(){
  mainWindow.webContents.send('ping', 'play-pause');
});

var register_rewind = globalShortcut.register('ctrl+left', function(){
  mainWindow.webContents.send('ping', 'rewind');
});

var register_next = globalShortcut.register('ctrl+right', function(){
  mainWindow.webContents.send('ping', 'forward');
});

Note that, for now, the global shortcut keybindings are hard coded. You can change them to any combination you like, defined in the Accelerator documentation. I’ll be covering how to make these configurable in a future post.

What we are doing here is registering 3 separate global shortcuts, one for each of the main functions of the media player. Now, we need to setup the renderer process to receive and process these messages.

Loading JavaScript into the renderer process

Loading JavaScript into the window is very simple. It works exactly the same as you would with a web page.

Create a folder in your app called js, and a file called app.js in that folder

mkdir js
touch ./js/app.js

Now, open up the index.html file from the previous post, and add the following script tag

<script src="./js/app.js"></script>

Now, when the browser window is created, it will load the app.js file into the browser window, along with the Google Play Music webpage.

NOTE: You can also load your CSS into the page the same way, using a <link> tag, if you prefer to not us inline CSS.

Register the message handler

Open up the app.js file, and add the following code to it

var ipc = require('ipc');

ipc.on('ping', function(arg){
  var webView = document.querySelector('webview#gpm-player');
  webView.executeJavaScript("document.querySelector('sj-icon-button[data-id=\"" + arg + "\"]').click()");
});

So what are we doing here? First, we load in the IPC module. Then, we register a message handler to listen for new messages on the ‘ping’ channel. The argument sent via the message is the not only the name of the message, but also the data-id value that Google Play Music uses to identify the 3 playback buttons. So, we use the data-id attribute in a querySelector to find the button, and then fire the click() event on it.

The tricky part here is that the HTML loaded into the webview cannot be accessed directly from the app.js file. Instead, we find the webview using a querySelector, and then use the executeJavaScript() method to find the button and fire off the click event.

That’s it. Now, when you start your app with npm start, Google Play Music is loaded into the window. You can select your playlist and then use the global shortcuts to control the basic playback functions.

Next time

In the next tutorial, we’ll look at how to let the user configure the global shortcut keys.

You can view the code from this tutorial on GitHub