Patrick Passarella

How to position the Electron tray window on all platforms

Creating an Electron tray app and correctly positioning it on Windows, Linux, and Mac

Patrick Passarella - 7th Nov, 2020

Photo by Aziz Acharki on Unsplash

I've had a hard time trying to have the tray window appear in the correct spot for all platforms, since in Linux the tray.getBounds() doesn't work. To solve that, we can use the mouse position for Linux, and the tray.getBounds() for windows and mac.

First, let's create an Electron app (I will not enter in details here, just a quick step-by-step list). You can also jump directly to the important part, here.

  1. $ yarn init to create an empty project.
  2. Change the main property in the package.json to main.js.
  3. Create a main.js file in the root of your project.
  4. $ yarn add electron --dev.
  5. Add a start script to your package.json, "start": "electron ."
  6. Create an index.html in your project root.

In the main.js file, I will be using the Electron default code, found on their documentation. With just some adjustments.

1const { app, BrowserWindow, Tray } = require('electron');
2const path = require('path');
3
4// Keep a global reference of the window object, if you don't, the window will
5// be closed automatically when the JavaScript object is garbage collected.
6let tray = null;
7let window = null;
8
9function createWindow() {
10 // Create the browser window.
11 window = new BrowserWindow({
12 width: 800,
13 height: 600,
14 webPreferences: {
15 nodeIntegration: true,
16 },
17 });
18
19 // and load the index.html of the app.
20 window.loadFile('index.html');
21
22 // Open the DevTools.
23 window.webContents.openDevTools({ mode: 'detach' });
24}
25
26app.on('ready', () => {
27 createWindow();
28});
29
30app.on('window-all-closed', () => {
31 if (process.platform !== 'darwin') {
32 app.quit();
33 }
34});
35
36app.on('activate', () => {
37 // On macOS it's common to re-create a window in the app when the
38 // dock icon is clicked and there are no other windows open.
39 if (BrowserWindow.getAllWindows().length === 0) {
40 createWindow();
41 }
42});

In the index.html, I will also be using the same code as the Electron documentation (just a "Hello World").

1<!DOCTYPE html>
2<html>
3 <head>
4 <meta charset="UTF-8" />
5 <title>Hello World!</title>
6 <meta
7 http-equiv="Content-Security-Policy"
8 content="script-src 'self' 'unsafe-inline';"
9 />
10 </head>
11 <body>
12 <h1>Hello World!</h1>
13 </body>
14</html>

Creating the tray icon

Now, let's create a tray icon. In the main.js file, create a function createTray. Remember to import Tray from 'electron'. This function will create the tray with the icon image provided in the assets folder, so you need to create that folder and put the image inside.

1const createTray = () => {
2 tray = new Tray(path.join(__dirname, 'assets', 'icon.png'));
3};

Call this function only after the app is ready.

1app.on('ready', () => {
2 createWindow();
3 createTray();
4});

Run the app again with $ yarn start, you will see that the tray will appear with your icon.

Positioning the tray window

Now for the main part, clicking the tray icon to open the window in the correct position.
Change the show option to false in the BrowserWindow, to not show the window after created.

1const window = new BrowserWindow({
2 width: 800,
3 height: 600,
4 show: false, // <- here
5 webPreferences: {
6 nodeIntegration: true,
7 },
8});

Create a function to toggle the window. So whenever we click the tray icon, it will show the window if it's hidden, and hide if it's not.

1const toggleWindow = () => {
2 if (window.isVisible()) return window.hide();
3 return showWindow();
4};

That showWindow function, which is being used inside the toggleWindow, is the function that will correctly position the window before showing it. For that, we will be using the library electron-traywindow-positioner, which has everything built in to position the tray icon for all platforms, using the mouse position for Linux, and the tray bounds for windows and mac.

Install the library $ yarn add electron-traywindow-positioner and import it. After calling the positioner.position, it will change the position of the window, so you can just show it using window.show().

1const positioner = require('electron-traywindow-positioner');
2
3const showWindow = () => {
4 positioner.position(window, tray.getBounds());
5 window.show();
6};

Then, add a click handler to the tray, which will call that function.

1const createTray = () => {
2 tray = new Tray(path.join(__dirname, 'assets', 'icon.png'));
3 tray.on('click', () => {
4 toggleWindow();
5 });
6};

Now, every time you click the tray icon, it will toggle the window, and with the correct position!
Here is the full code in the main.js file.

1const { app, BrowserWindow, Tray } = require('electron');
2const positioner = require('electron-traywindow-positioner');
3const path = require('path');
4
5let window = null;
6let tray = null;
7
8const showWindow = () => {
9 positioner.position(window, tray.getBounds());
10 window.show();
11};
12
13const toggleWindow = () => {
14 if (window.isVisible()) return window.hide();
15 return showWindow();
16};
17
18const createTray = () => {
19 tray = new Tray(path.join(__dirname, 'assets', 'drop.png'));
20 tray.on('click', () => {
21 toggleWindow();
22 });
23};
24
25const createWindow = () => {
26 // Create the browser window.
27 window = new BrowserWindow({
28 width: 800,
29 height: 600,
30 show: false,
31 webPreferences: {
32 nodeIntegration: true,
33 },
34 });
35
36 // and load the index.html of the app.
37 window.loadFile('index.html');
38
39 // Open the DevTools.
40 window.webContents.openDevTools({ mode: 'detach' });
41};
42
43app.on('ready', () => {
44 createWindow();
45 createTray();
46});
47
48app.on('window-all-closed', () => {
49 if (process.platform !== 'darwin') {
50 app.quit();
51 }
52});
53
54app.on('activate', () => {
55 // On macOS it's common to re-create a window in the app when the
56 // dock icon is clicked and there are no other windows open.
57 if (BrowserWindow.getAllWindows().length === 0) {
58 createWindow();
59 }
60});

That's it, very simple, but it's something that took me some time to find a great and simple solution.

Thanks for reading!Enjoyed the content and want to read more? Subscribe to my newsletter!