62

I want my Electron.js application to live on system tray and whenever the user wants to do something they can restore from the system tray do something and minimize/close it back to system tray. How do i do that?

I've seen the tray section from the documentation but doesn't help much to achieve what i want.

Here is what i got so far on the main.js file

var application = require('app'),
    BrowserWindow = require('browser-window'),
    Menu = require('menu'), 
    Tray = require('tray'); 
application.on('ready', function () {
    var mainWindow = new BrowserWindow({
        width: 650,
        height: 450,
        'min-width': 500,
        'min-height': 200,
        'accept-first-mouse': true,
        // 'title-bar-style': 'hidden',
        icon:'./icon.png'
    });
    mainWindow.loadUrl('file://' + __dirname + '/src/index.html');
    mainWindow.on('closed', function () {
        mainWindow = null;
    });
    mainWindow.setMenu(null);

    var appIcon = null;
    appIcon = new Tray('./icon-resized.png');
    var contextMenu = Menu.buildFromTemplate([
        { label: 'Restore', type: 'radio' }
    ]);
    appIcon.setToolTip('Electron.js App');
    appIcon.setContextMenu(contextMenu);
});

UPDATE:

I found this menubar repo, but it won't work as expected on linux.

ShegaMike
  • 2,729
  • 1
  • 23
  • 34

7 Answers7

90

I actually figured it out a long time ago but for folks who encounter the same problem here is one way you could achieve minimizing to tray and restoring from tray. The trick is to catch the close and minimize events.

var BrowserWindow = require('browser-window'),

var mainWindow = new BrowserWindow({
    width: 850,
    height: 450,
    title: "TEST",
    icon:'./icon.png'
});

mainWindow.on('minimize',function(event){
    event.preventDefault();
    mainWindow.hide();
});

mainWindow.on('close', function (event) {
    if(!application.isQuiting){
        event.preventDefault();
        mainWindow.hide();
    }

    return false;
});

and restoring from Tray

var contextMenu = Menu.buildFromTemplate([
    { label: 'Show App', click:  function(){
        mainWindow.show();
    } },
    { label: 'Quit', click:  function(){
        application.isQuiting = true;
        application.quit();
    } }
]);
Oskar
  • 1,702
  • 1
  • 14
  • 32
ShegaMike
  • 2,729
  • 1
  • 23
  • 34
  • 3
    What is "application" means here? – Anand Vaidya Nov 10 '17 at 12:34
  • 3
    I am getting error Cannot set property 'isQuiting' of undefined – Anand Vaidya Nov 10 '17 at 13:14
  • 4
    @AnandVaidya: `var { application } = require('electron');` – Aniruddha Shevle Aug 20 '19 at 04:46
  • 1
    Two things, A) this can create a serious bug with electron updater and electron builder. electron updater has a function called quitAndInstall(), which as you guess, quits and install your update for your electron app upon publishing. The quitAndInstall function apparently uses electron's quit() function which is pretty much blocked here, so whoever uses this, if you don't want to release a broken update for your clients, make your isQuiting value truthy before using quitAndInstall(). – Anatsu Jan 12 '20 at 14:59
  • B) I really don't think it's a good practice to attach your key and value to electron's object. why not using a variable on the global scope? – Anatsu Jan 12 '20 at 15:00
  • @ShegaMike is there a method to avoid so many icons on system tray? Every time electron app hides on system tray theirs so many icons but goes away when you hover it. – Sarotobi Feb 10 '20 at 14:32
  • This works but the app never exit from the dock. I need the app to be quitable from the dock but the tray still running. – Ali123 May 29 '20 at 00:14
18

I updated the code with a scenario if you want to show icon on your system tray all the time until you don't quit the application

var { app, BrowserWindow, Tray, Menu } = require('electron')
var path = require('path')
var url = require('url')
var iconpath = path.join(__dirname, 'user.ico') // path of y
var win
function createWindow() {
    win = new BrowserWindow({ width: 600, height: 600, icon: iconpath })

    win.loadURL(url.format({
        pathname: path.join(__dirname, 'index.html'),
    }))

    var appIcon = new Tray(iconpath)

    var contextMenu = Menu.buildFromTemplate([
        {
            label: 'Show App', click: function () {
                win.show()
            }
        },
        {
            label: 'Quit', click: function () {
                app.isQuiting = true
                app.quit()
            }
        }
    ])

    appIcon.setContextMenu(contextMenu)

    win.on('close', function (event) {
        win = null
    })

    win.on('minimize', function (event) {
        event.preventDefault()
        win.hide()
    })

    win.on('show', function () {
        appIcon.setHighlightMode('always')
    })

}

app.on('ready', createWindow)
Oskar
  • 1,702
  • 1
  • 14
  • 32
Vishal Shori
  • 324
  • 2
  • 4
  • 1
    `// Deprecated tray.setHighlightMode(mode) // API will be removed in v7.0 without replacement.` – Alex G Apr 27 '20 at 18:33
18

Addition to above answers - isQuiting flag is worth setting at app' before-quit callback, too. This way the application will be closed properly if requested by the OS or user some other way, e.g. via Macos Dock taskbar' quit command. Complete Typescript-friendly snippet:

import {app, BrowserWindow, Tray, Menu} from 'electron';
import * as path from 'path';

let window;
let isQuiting;
let tray;

app.on('before-quit', function () {
  isQuiting = true;
});

app.on('ready', () => {
  tray = new Tray(path.join(__dirname, 'tray.png'));

  tray.setContextMenu(Menu.buildFromTemplate([
    {
      label: 'Show App', click: function () {
        window.show();
      }
    },
    {
      label: 'Quit', click: function () {
        isQuiting = true;
        app.quit();
      }
    }
  ]));

  window = new BrowserWindow({
    width: 850,
    height: 450,
    show: false,
  });

  window.on('close', function (event) {
    if (!isQuiting) {
      event.preventDefault();
      window.hide();
      event.returnValue = false;
    }
  });
});
Artem Vasiliev
  • 1,585
  • 1
  • 17
  • 19
11

A better way than using flags and for those who do not want to change the minimize behavior :

just normally hide the window on close event using mainWindow.hide()

mainWindow.on('close', function (event) {
    event.preventDefault();
    mainWindow.hide();
});

then call mainWIndow.destroy() to force close the window. It also guarantees to execute the closed event handler.

From the documentation:

Force closing the window, the unload and beforeunload event won't be emitted for the web page, and close event will also not be emitted for this window, but it guarantees the closed event will be emitted.

var contextMenu = Menu.buildFromTemplate([
    { label: 'Show App', click:  function(){
        mainWindow.show();
    } },
    { label: 'Quit', click:  function(){
        mainWindow.destroy();
        app.quit();
    } }
]);
Jalal Mostafa
  • 882
  • 8
  • 22
0

I share my Sketch, on minimize hide on taskbar icon, menu options rigth-click icon to restore/close. using websocket/http server..

//const {app, BrowserWindow} = require('electron');
const {app, BrowserWindow, Tray, Menu} = require('electron');
const myip = require('quick-local-ip');
const express = require('express');
const WebSocket = require('ws');
const bodyParser = require('body-parser');
const path = require('path')
// Config
const Config = {
    http_port: '8080',
    socket_port: '3030'
};

var iconpath = path.join(__dirname, 'rfid.png') // path of y

// Http server
const _app = express();
const server = require('http').Server(_app);
server.listen(Config.http_port);

// WSS server
const wss = new WebSocket.Server({port: Config.socket_port});

// Console print
console.log('[SERVER]: WebSocket on: ' + myip.getLocalIP4() + ':' + Config.socket_port); // print websocket ip address
console.log('[SERVER]: HTTP on: ' + myip.getLocalIP4() + ':' + Config.http_port); // print web server ip address

// 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 mainWindow;

let window;
let isQuiting;
let tray;

function createWindow() {
    mainWindow = new BrowserWindow({
        width: 1200,
        height: 800,
        acceptFirstMouse: true,
        autoHideMenuBar: false,
        useContentSize: true,
    });

    var appIcon = new Tray(iconpath);

    // mainWindow.loadURL('index.html')
    mainWindow.loadURL('http://localhost:8080');
    mainWindow.focus();
    // mainWindow.setFullScreen(true);

    // Open the DevTools.
    mainWindow.webContents.openDevTools();

    var contextMenu = Menu.buildFromTemplate([
        {
            label: 'Show App', click: function () {
                mainWindow.show()
            }
        },
        {
            label: 'Quit', click: function () {
                app.isQuiting = true
                app.quit()
            }
        }
    ])

    appIcon.setContextMenu(contextMenu)

    // Emitted when the window is closed.
    mainWindow.on('close', function (event) {
        mainWindow = null
    });

    mainWindow.on('minimize', function (event) {
        event.preventDefault()
        mainWindow.hide()
    });

    mainWindow.on('show', function () {
        appIcon.setHighlightMode('always')
    })
}

app.on('ready', createWindow)

// Quit when all windows are closed.
app.on('window-all-closed', function () {
    // On OS X 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', function () {
    // On OS X 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 (mainWindow === null) {
        createWindow()
    }
})

/**
 * EXPRESS
 */
_app.use(bodyParser.urlencoded({
    extended: false
}));

_app.use('/assets', express.static(__dirname + '/www/assets'))

_app.get('/', function (req, res) {
    res.sendFile(__dirname + '/www/index.html');
});

/**
 * WEBSOCKET
 */
wss.getUniqueID = function () {
    function s4() {
        return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
    }

    return s4() + s4() + '-' + s4();
};

wss.on('connection', function connection(ws, req) {
    ws.id = wss.getUniqueID();
    console.log('[SERVER]: Client Connected. ID=', ws.id);

    ws.on('close', function close() {
        console.log('[SERVER]: Client disconnected.');
    });

    ws.on('message', function incoming(recieveData) {
        console.log('[SERVER] Message:', recieveData);

        // Example use
        // send(recieveData);

        sendAll(recieveData);
    });

    // Send back to client
    function send(data) {
        data = JSON.stringify(data);
        ws.send(data);
    }

    // Send to all clients
    function sendAll(data) {
        data = JSON.stringify(data);

        wss.clients.forEach(function each(client) {
            client.send(data);
        });
    }
});
Navas Ema
  • 61
  • 3
0

This was tagged with NW.js, and since all the other answers are for Electron I thought I'd show off how much easier everything always is in NW.js. There is a simple demo repo set up right here:

Just download that and run npm install && npm start.

Jaredcheeda
  • 742
  • 7
  • 9
-1

Try minimize event instead of hide.

var BrowserWindow = require('browser-window'),

var mainWindow = new BrowserWindow({
    width: 850,
    height: 450,
    title: "TEST",
    icon:'./icon.png'
});

mainWindow.on('minimize',function(event){
    event.preventDefault();
    mainWindow.minimize();
});

mainWindow.on('close', function (event) {

  event.preventDefault();
  mainWindow.minimize();
    return false;
});

This worked for me. hide() was closing the window.

manish kumar
  • 3,629
  • 2
  • 30
  • 44