•about 2 years ago
One of the newest and coolest innovations in the tech/hardware world as of late is the touch bar that Apple released on its MacBook Pro last year. As these machines have become more and more popular, more and more applications are utilizing the touch bar to interact with their users in a new dimension. As we watched this trend unfold, we decided that it seemed like a fun challenge to tackle ourselves, since our pet project of the last several months, Winds 2.0, made its debut earlier this month. As we continue to iterate on Winds based on feedback from the community, now seemed like the perfect time to launch support for touch bar control on macOS, in Winds 2.1. Going into it, it seemed like it was going to be a piece of cake. However, we couldn’t have been more wrong. The API communication between macOS and Electron is far from complete. Hopefully, we’ll see some changes in the near future for better support between the macOS and Electron. For now, we’ve come up with a decent solution to the problem that allows us to communicate bi-directionally between Electron and the macOS touch bar. To do this, we heavily relied on three major Electron components:
- The (limited) touch bar API that is provided by Electron
- The ipcMain module, which handles asynchronous and synchronous messages sent from a renderer process (web page)
- The ipcRenderer module, which provides a few methods that allow you to send synchronous and asynchronous messages from the renderer process (web page) to the main process (ipcMain).
In this post, we’ll do a deep dive into how we accomplished this task. Let’s do it.
The ipcMain Module
The ipcMain module is an instance of the EventEmitter class. When used in the main process, it handles asynchronous and synchronous messages sent from a renderer process (web page). Messages sent from a renderer are emitted to this module and picked up by an event handler and then passed off to a function for further processing.
Send & Receive from Electron
In /app/public/electron.js, we initialize the following code once the window is ready to show: https://gist.github.com/nparsons08/1d0fd8512b7e5f698a9f430d7bb3eed4 The event property specifies what happened, whereas the args can be a single value or an object of key-value pairs. For Winds, we chose to go with an object so we could pass along additional metadata (from the frontend), such as the current episode title and podcast name.
The ipcRenderer Module
The ipcRenderer module is an instance of the EventEmitter class. It provides a few methods that allow you to send synchronous and asynchronous messages from the renderer process (web page) to the main process (Electron). Understanding how communication works was the first step in our journey to get media control support in place. To better understand how it works, let’s look at a few short code examples:
Send & Receive from React
In /app/src/components/Player.js, we use window.ipcRenderer, as ipcRenderer is not directly available, thus requiring us to pull it off of the window object: https://gist.github.com/nparsons08/f6e3b1c6219ddfb28c971a055a8767e1 AND https://gist.github.com/nparsons08/f4be5d6e207adc3784e341d751326c68 So, after all is said and done, we can use the player context to differentiate between a playing episode and a paused episode. It looks something like this: https://gist.github.com/nparsons08/a8a58d0eee0b3be7b2fdd1daab5f578d
React Lifecycle Events
On componentDidMount(), we use the following handler to ensure that our incoming events are picked up. Note: We wrap our code in an Electron check via the is-electron Node module to ensure that we only execute this in an Electron environment – this is important because we have web and native versions of the application. https://gist.github.com/nparsons08/3440f66b9aa710af9b00187402a87696 On componentWillUnmount(), we use the following handler to ensure that all listeners are destroyed: https://gist.github.com/nparsons08/b050cc9ae2490be938097198c736ff68
Electron Touch Bar API
As pointed out in the previous portion of this post, we initialize ipcMain in our electron.js file. But wait, there’s more... We also have a portion of code dedicated to handling the incoming (and outbound) messages, in addition to toggling the touch bar images, and handling touch bar events: https://gist.github.com/nparsons08/55173d75c39163142dacaeebb0519c62 This function should go in your main.js file, or in our case, the electron.js file.
All of this put together gives Winds 2.1 an awesome touch bar feature that allows our users to pause and play podcast episodes in Winds, view the current podcast that is playing, and seek both forward and backwards. As we continue build the app and receive feedback from our awesome community, we are hoping to continue to add new ways for the user to interact with the touch bar and leave people feeling pleasantly surprised with each of their interactions with Winds.
I hope that this mini tutorial helped shed some light on communication between the main process (Electron) and the renderer (React). As previously mentioned, the APIs aren’t entirely there yet, so you may run into some hiccups along the way – feel free to post in the comments; I’m happy to help out! If you think that I’ve missed anything, please feel free to drop a line in the comments below or find me on Twitter – @NickParsons.