Electron Misc Resources

Send message from React <-> Electron - Sync vs Async

In the react app, in the root component:

import { ipcRenderer} from 'electron'

const App = () => {

  useEffect(() => {
  // on message
  ipcRenderer.on('export', () => alert("export ON"));

  // reply
  ipcRenderer.send("export", {message: "done"})
 }, []);

 return (
   ...
 )
}

To handle the messages/replies from the React app, add this listener in the main.ts electron file:

ipcMain.on("export", (event, message) => {
    console.log("answer ", message)
})

NOTE: export is the channel name and it can be anything

Invoke Electron function from React & Exec shell command

// on Electron
ipcMain.handle('exec', async (event, command) => {
  try {
    const stdout = execSync(command, {stdio: 'pipe', encoding: 'utf-8'})
    return {stdout, stderr: ''}
  } catch (stderr) {
    return {stdout: '', stderr}
  }
})

// on React
const result = await electron.ipcRenderer.invoke('exec', 'ls -al /tmp')

Sync vs Async

Requests (in the web client):

const {ipcRenderer} = require('electron')

// Synch message emmiter and handler
console.log(ipcRenderer.sendSync('sync-message', 'sync ping')) 

// Async message handler
ipcRenderer.on('asynchronous-reply', (event, arg) => {
   console.log(arg)
})

// Async message sender
ipcRenderer.send('async-message', 'async ping')

Replies (in the electron app):

ipcMain.on('async-message', (event, arg) => {
   event.sender.send('asynchronous-reply', 'async pong')
})

ipcMain.on('sync-message', (event, arg) => {
   event.returnValue = 'sync pong'
})

Open File Browser Dialog

const electron = window.require('electron');
const remote = electron.remote
const {BrowserWindow,dialog,Menu} = remote

///
 const browseFolder = () => {
 const windowOne = new BrowserWindow()
  windowOne.loadURL('https://www.electronjs.org/')
 }

///
 <button onClick={browseFolder}>Browse...</button>

Get Folder Path (browser):

 const browseFolder = async () => {
 const path = await dialog.showOpenDialog({
  properties: ['openDirectory']
  }) 
  console.log(path.filePaths); // empty array if cancel
 }

Get the operating system name:

const isMac = process.platform === 'darwin'

DevTools

Start dev tools console:

mainWindow.webContents.openDevTools()

Prevent dev tools console to open:

win.webContents.on("devtools-opened", () => { win.webContents.closeDevTools(); });

Config Path - Data Storage

get the path to the config folder:

const {app} = require('electron');

const configDir = app.getPath('userData');

console.log(configDir);

src https://www.electronjs.org/docs/api/app#appgetpathname

home - User's home directory.

appData - Per-user application data directory, which by default points to

userData - The directory for storing your app's configuration files, which by default it is the appData directory appended with your app's name.

temp - Temporary directory.

desktop - The current user's Desktop directory.

documents - Directory for a user's "My Documents".

exe - The current executable file.

Misc BrowserWindow Settings

 mainWindow = new BrowserWindow({
            width: 800,
            height: 600,
            modal: true,
            center: true,
            alwaysOnTop: true,
            closable: false,
            resizable: false,
            minimizable: false,
            maximizable: false,
            skipTaskbar: true,
            frame: false,
            fullscreen: false,
            fullscreenable: false,
            // kiosk: true,
            webPreferences: {
                nodeIntegration: true
            }
        })

Register Global Key Shortcut

src: https://www.electronjs.org/docs/api/global-shortcut

app.whenReady().then(() => {
    const ret = globalShortcut.register('CommandOrControl+Shift+X', () => {
        console.log('CommandOrControl+Shift+X is pressed')
    })

    if (!ret) {
        console.log('registration failed')
    }

    // Check whether a shortcut is registered.
    console.log(globalShortcut.isRegistered('CommandOrControl+Shift+X'))
}

app.on('will-quit', () => {
    // Unregister a shortcut.
    globalShortcut.unregister('CommandOrControl+Shift+X')
  
    // Unregister all shortcuts.
    globalShortcut.unregisterAll()
  })

more ex:

 globalShortcut.register('Escape', () => {
        mainWindow.hide()
    })

Messages

src https://www.electronjs.org/docs/api/ipc-main

in main.js:

// In the main process.
const { ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
  console.log(arg) // prints "ping"
  event.reply('asynchronous-reply', 'pong')
})

ipcMain.on('synchronous-message', (event, arg) => {
  console.log(arg) // prints "ping"
  event.returnValue = 'pong'
})

in index.html:

const { ipcRenderer } = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"

ipcRenderer.on('asynchronous-reply', (event, arg) => {
  console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')

Listen to Clipboard Events

example: https://github.com/arjun-g/electron-clipboard-extended#readme

const {clipboard} = require('electron')
const EventEmitter = require('./EventEmitter')
const clipboardEmitter = new EventEmitter()

let watcherId = null, previousText = clipboard.readText(), previousImage = clipboard.readImage()

clipboard.on = (event, listener) => {
    clipboardEmitter.on(event, listener)
    return clipboard
}

clipboard.once = (event, listener) => {
    clipboardEmitter.once(event, listener)
    return clipboard
}

clipboard.off = (event, listener) => {
    if(listener)
        clipboardEmitter.removeListener(event, listener)
    else
        clipboardEmitter.removeAllListeners(event)
    return clipboard
}

clipboard.startWatching = () => {
    if(!watcherId)
        watcherId = setInterval(() => {
            if(isDiffText(previousText, previousText = clipboard.readText())) clipboardEmitter.emit('text-changed')
            if(isDiffImage(previousImage, previousImage = clipboard.readImage())) clipboardEmitter.emit('image-changed')
        }, 500)
    return clipboard
}

clipboard.stopWatching = () => {
    if(watcherId) clearInterval(watcherId)
    watcherId = null
    return clipboard
}

function isDiffText(str1, str2){
    return str2 && str1 !== str2
}

function isDiffImage(img1, img2){
    return !img2.isEmpty() && img1.toDataURL() !== img2.toDataURL()
}

module.exports = clipboard

Build a Tray App

src https://www.electronjs.org/docs/api/tray

First, create a folder assets and add an icon icon.png

Then, in main.js:

const path = require('path');
const {
    app,
    Menu,
    Tray,
} = require('electron');

let tray = null;

const getIcon = () => {
    // if (process.platform === 'win32') return 'icon-light.ico';
    // if (systemPreferences.isDarkMode()) return 'icon-light.png';
    // return 'icon-dark.png';
};

app.on('ready', () => {
    // hide on Mac
    if (app.dock) app.dock.hide();

    tray = new Tray(path.join(__dirname, './assets/icon.png'));

    if (process.platform === 'win32') {
        tray.on('click', tray.popUpContextMenu);
    }

    const menu = Menu.buildFromTemplate([{
        label: 'Quit',
        click() {
            app.quit();
        }
    }]);

    tray.setToolTip('Tool Tip');
    tray.setContextMenu(menu);
});