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);
});