I am trying to implement Drag'n'Drop functionality for users to easier upload their images. In browser the drop area works perfectly but Electron is preventing files to be even dropped into the app. Every time I try to drop a file cursor changes to the one shown below and nothing happens.
I thought maybe Electron had configs to disable dropping files by default due to popular complain but I couldn't find any solution.
The picture shows the drop area, but the same issue is all across the app.
Main.js:
const { app, BrowserWindow } = require('electron')
let win
function createWindow () {
win = new BrowserWindow({ width: 1000, height: 600, minWidth: 690, minHeight: 340 })
win.loadFile('index.html')
win.webContents.openDevTools()
win.on('closed', () => {
win = null
})
}
app.on('ready', createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (win === null) {
createWindow()
}
})
Ok. That wasn't fun. Looks like something actually broke. But after I restarted the console, Windows then reinstalled the project, Electron, and NPM it finally worked. The problem is now solved.
Related
I'm trying to develop an application which communicates with Bluetooth Low Energy Devices. I established a working "website" with the Web Bluetooth API. Everything works fine, so I used the Electron framework, to build an application.
The issue is known - if you start navigator.bluetooth.requestDevice(), you get this error message:
User cancelled the requestDevice() chooser..
This causes due to the missing device chooser in Chromium. There are several topics about workarounds I found, but no examples. This is my first Electron project. Maybe somebody solved this problem and can give me a hint :-)
thank you very much for your support. According to your suggestions and some research, I developed a working solution and like to share it with you.
These two links helped me a lot:
https://github.com/electron/electron/issues/11865
https://github.com/electron/electron/issues/10764
Especially this post from MarshallOfSound – well described:
Hook event in main process
Send message to renderer process with device list
Show UI in renderer process
Send selected device to main process
Call callback
To get more Information about the main process and renderer process, events and their API, read this:
https://www.electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes
https://www.electronjs.org/docs/api/ipc-main
https://www.electronjs.org/docs/api/web-contents#contentssendchannel-args
https://www.electronjs.org/docs/api/ipc-renderer
https://electronjs.org/docs/api/web-contents#event-select-bluetooth-device (already posted by Gerrit)
https://www.electronjs.org/docs/api/structures/bluetooth-device
For my application I wanted a device picker, as seen in Chrome. The sequence I wanted to implement is:
Start application
Search for a device
Devicepicker pops up
Select a device
Devicepicker closes
See data in application
A reference to the code for the processes of the tutorial and the code snippet:
electron application: main.js (main process) renderer.js (render process) devicepicker GUI: devicepicker.js (renderer process) devicepicker.html & layout.css (GUI)
1) Create devicepicker with a GUI (I used and two ) and a script
2) In your main.js create a select-bluetooth-device event inside the 'ready' event of your application object (docs in links above) When you start navigator.bluetooth.requestDevice() in your renderer.js, the event get’s fired and the devicelist is in the main process. With console.log(deviceList) it's visible in the shell. To handle it you need to send it to the renderer process (your application window).
3) To achieve this, we implement webContents.send of our BrowserWindow object inside the webContents.on event. Now the main process sends a devicelist every time he found new devices through the channel channelForBluetoothDeviceList
4) Create in renderer.js startDevicePicker(). devicePicker() must be started in the same function as navigator.bluetooth.requestDevice(). startDevicePicker() instantiates a new BrowserWindow() object which loads devicepicker.html
5) To get the list from the main process a ipcRenderer.on() listener must be implemented in startDevicePicker() that listens to the channelForBluetoothDeviceList channel of our main process. Now we can get the list in our electron application (renderer prcoess). To send it to the devicepicker UI, we need to forward it from our electron application (renderer process) to the devicepicker (also a renderer process)
6) To achieve this, we need the ipcRenderer.sendTo() sender in devicePicker(), which forwards messages between from a renderer process to a specific other renderer process. Additional to the channel bluetoothDeviceDiscoverList we need the BrowserWindow.id of the devicepicker. Since we just instantiated it, we can use our devicepicker object. I had a device which sended only once, the main process was faster than the build of the devicepicker and my list was never sent to the devicepicker. So I used a Promise() to wait with ipcRenderer.sendTo() until the devicepicker was ready to use.
7) To receive the devicelist at our devicepicker GUI, we need to listen to the bluetoothDeviceDiscoverList with ipcRenderer.on() (devicepicker.js). I inserted the devicelist now to the <option> of the devicepicker, you can use of course other elements (devicepicker.html). Please note: implement a query that compares the sended list to the current. Otherwise you get multiple devices and your selection gets loooong. I still need to do that, it's not finished yet :-)
8) To select a device that navigator.bluetooth.requestDevice() (renderer.js) gets resolved, we need to send back the BluetoothDevice.deviceId of our selected device to the main process where we callback ‚callback‘ with deviceId as callback parameter (main.js).
9) Now we can use ipcRenderer.sendTo() send the selected BluetoothDevice.deviceId to the mainprocess (devicepicker.js).
10) In the main process (main.js) of our electron application we listen to the channel channelForSelectingDevice with ipcMain.on() and callback with the received BluetoothDevice.deviceId. The device discovery gets stopped, navigator.bluetooth.requestDevice() gets resolved and we receive data from our device in our application (renderer.js). To cancel the discovery of devices, listen with ipcMain.on() in another channel channelForTerminationSignal just a signal to the main process (main.js), for example after a click (devicepicker.js) and call the callback with an empty string (as written in the docs)
I admit it could be done much simpler without a devicepicker. Then just send the devicelist from the main process (main.js) to your application (renderer process). But this helped me a lot to understand the processes in electron. I hope this tutorial is useful for you :-) !
Snippet:
main.js
const { ipcMain, app, BrowserWindow } = require('electron')
let win = null;
var callbackForBluetoothEvent = null;
// Create the browser window.
function createWindow () {
win = new BrowserWindow({
webPreferences: {
nodeIntegration: true //to activate require()
}
})
win.maximize()
win.show()
//This sender sends the devicelist from the main process to all renderer processes
win.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault(); //important, otherwise first available device will be selected
console.log(deviceList); //if you want to see the devices in the shell
let bluetoothDeviceList = deviceList;
callbackForBluetoothEvent = callback; //to make it accessible outside createWindow()
win.webContents.send('channelForBluetoothDeviceList', bluetoothDeviceList);
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
//cancels Discovery
ipcMain.on('channelForTerminationSignal', _ => {
callbackForBluetoothEvent(''); //reference to callback of win.webContents.on('select-bluetooth-device'...)
console.log("Discovery cancelled");
});
//resolves navigator.bluetooth.requestDevice() and stops device discovery
ipcMain.on('channelForSelectingDevice', (event, DeviceId) => {
callbackForBluetoothEvent(sentDeviceId); //reference to callback of win.webContents.on('select-bluetooth-device'...)
console.log("Device selected, discovery finished");
})
renderer.js
function discoverDevice() {
navigator.bluetooth.requestDevice()
startDevicepicker()
}
function startDevicepicker(){
let devicepicker = null;
let mainProcessDeviceList = null;
devicepicker = new BrowserWindow({
width: 350,
height: 270,
show: false, //needed to resolve promise devicepickerStarted()
webPreferences: {
nodeIntegration: true
}
})
devicepicker.loadFile('devicePicker.html');
//electron application listens for the devicelist from main process
ipcRenderer.on('channelForBluetoothDeviceList', (event, list) => {
mainProcessDeviceList = list;
devicepickerStarted.then(_=> {
console.log("Promise resolved!");
ipcRenderer.sendTo(devicepicker.webContents.id, 'bluetoothDeviceDiscoverList', mainProcessDeviceList);
})
})
//Promise that ensures that devicepicker GUI gets the list if the device only sends once
var devicepickerStarted = new Promise(
function (resolve, reject) {
console.log("Promise started");
devicepicker.once('ready-to-show', () => {
devicepicker.show();
resolve();
console.log("Devicepicker is ready!")
})
}
)
//remove listeners after closing devicepicker
devicepicker.on('closed', _ => {
devicepicker = null;
ipcRenderer.removeAllListeners('channelForBluetoothDeviceList');
ipcRenderer.removeAllListeners('currentWindowId');
ipcRenderer.removeAllListeners('receivedDeviceList');
})
}
devicepicker.js
//save received list here
var myDeviceList = new Array();
//Html elements
const devicePickerSelection = document.getElementById("devicePickerSelection");
const buttonSelect = document.getElementById("Select");
const buttonCancel = document.getElementById("Cancel");
//eventListeners for buttons
buttonSelect.addEventListener('click', selectFromDevicePicker);
buttonCancel.addEventListener('click', cancelDevicePicker);
//listens for deviceList
ipcRenderer.on('receivedDeviceList', (event, bluetoothDeviceDiscoverList) => {
console.log("list arrived!")
//code: add list to html element
});
function selectFromDevicePicker() {
let selectedDevice = devicePickerSelection.value;
let deviceId = //depends on where you save the BluetoothDevice.deviceId values
//sends deviceId to main process for callback to resolve navigator.bluetooth.requestDevice()
ipcRenderer.send('channelForSelectingDevice', deviceId);
ipcRenderer.removeAllListeners('receivedDeviceList');
closeDevicePicker();
}
function cancelDevicePicker() {
ipcRenderer.send('channelForTerminationSignal');
closeDevicePicker();
}
function closeDevicePicker() {
myDevicePicker.close();
}}
In your main.js Add this code snippet
if (process.platform === "linux"){
app.commandLine.appendSwitch("enable-experimental-web-platform-features", true);
} else {
app.commandLine.appendSwitch("enable-web-bluetooth", true);
}
This will enable the bluetooth at your Electron app.
And use this as reference
https://github.com/electron/electron/issues/11865
https://github.com/electron/electron/issues/7367
https://github.com/aalhaimi/electron-web-bluetooth
But I'd suggest you to consider your Electron version.
Here is a code sample that will just return the first device instead of having to implement a device chooser:
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault();
console.log('Device list:', deviceList);
let result = deviceList[0];
if (!result) {
callback('');
} else {
callback(result.deviceId);
}
});
Source: https://electronjs.org/docs/api/web-contents#event-select-bluetooth-device
How do I create a functioning electron app with multiple instances of the mainWindow? Here's a very simple app with a mainWindow that just has two buttons. One to create a new mainWindow instance, and one to close the current window.
// main.js
const { app, ipcMain, BrowserWindow } = require('electron')
let mainWindow;
function main () {
mainWindow = new BrowserWindow({
width: 500,
height: 400,
tabbingIdentifier: 'todoTab',
show: false,
webPreferences: {
nodeIntegration: true
}
})
mainWindow.loadFile('renderer/index.html');
mainWindow.once('ready-to-show', () => {
mainWindow.setTitle("Todo-" + mainWindow.id)
mainWindow.show();
})
mainWindow.mergeAllWindows();
}
app.on('ready', main);
ipcMain.on('newListWindow', main);
ipcMain.on('closeWindow', function(event){
mainWindow.close();
});
In the above file I set mainWindow as a global variable.
Adding a tabbingIdentifier property and chaining the mergeAllWindows() method will automatically create multiple tabs in the display if more than one window is opened.
Each mainWindow instance is assigned an id by Electron. If only one instance is open the id is 1. For simplicity I set the mainWindow title to be "Todo-" + the mainWindow.id (so Todo-1 for the first window, Todo-2 if I open a second).
When the newListWindow button is clicked the "main" function gets called creating a new instances of mainWindow.
When the closeWindow button is pressed the mainWindow instance is closed.
The HTML file with the two buttons (abbreviated to just the body element)
// renderer/index.html
<body>
<h1 class="text-center">Todo List</h1>
<button id="new-list-btn">New Todo List</button>
<button id="close-btn">Close List</button>
<script src="./index.js"></script>
</body>
The ipcRenderer. Listens for the button clicks and sends a message to main.js.
// renderer/index.js
const { ipcRenderer } = require('electron')
document.getElementById('new-list-btn').addEventListener('click', () => {
ipcRenderer.send('newListWindow');
});
document.getElementById('close-btn').addEventListener('click', () => {
ipcRenderer.send('closeWindow');
});
The above code will create multiple Todo windows and display them on different tabs. Each list title (Todo-1, Todo-2, etc) is displayed. The problem is, the last one opened is the only active one. So if I open three todos, then go to any one of them and click the close button, only the third window will close, regardless of which one I was in. Then the other two will throw an error if I try to close them saying the object was destroyed. Which makes sense. So how do I code this so if that whichever instance tab I am in is the one that I close. And when I close it the next tab I am in becomes the valid mainWindow object?
You may want to deal only with the focused BrowserWindow in 'closeWindow' callback
Use BrowserWindow.getFocusedWindow static method
ipcMain.on('closeWindow', function(event) {
const current = BrowserWindow.getFocusedWindow()
if (current) current.close()
})
Pergy's answer works as requested but I'll post this answer as well since it's what I'm actually using, and there's no other documentation on how to do this that I could find. The difference is small but this seems more direct:
ipcMain.on('closeWindow', function(event) {
mainWindow = BrowserWindow.getFocusedWindow();
mainWindow.close();
})
I know this has been asked but previous answers aren't working. This is my first electron app.
Here is my main.js
const { app, BrowserWindow } = require('electron')
// 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 win
function createWindow () {
// Create the browser window.
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
// and load the index.html of the app.
win.loadFile('index.html')
// Open the DevTools.
//win.webContents.openDevTools()
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS 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', () => {
// On macOS 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 (win === null) {
createWindow()
}
win.setMenu(null);
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
I thought win.setMenu(null);would remove the menu bar but it does not. What do I need to do to completely remove this?
--The only think I've found that is sort of close is making the app frameless.
win = new BrowserWindow({
width: 800,
height: 600,
frame: false,
webPreferences: {
nodeIntegration: true
}
})
For Windows and Linux:
require('electron').Menu.setApplicationMenu(null);
Refer to https://electronjs.org/docs/api/menu#menusetapplicationmenumenu
I want to make my Electron app toggle developer tools in response to F12.
In the renderer page, I added:
const currentWebContents = require("electron").remote.getCurrentWebContents();
window.addEventListener("keydown", (e: KeyboardEvent) => {
if (e.keyCode === 123) { // F12
currentWebContents.toggleDevTools();
}
});
This works when I'm focused on the main page. However, immediately after the dev tools opens up, focus goes to the dev tools, so F12 is no longer detected.
I tried fixing this by adding a listener to the devtools webcontents right after calling toggleDevTools() like so:
if (currentWebContents.devToolsWebContents) {
currentWebContents.devToolsWebContents.on("before-input-event", (event: Electron.Event, input: Electron.Input) => {
if (input.type === "keyDown" && input.key === "F12") {
currentWebContents.toggleDevTools();
}
});
}
However, currentWebContents.devToolsWebContents is null right after opening it. My first question is how to ensure that it isn't null - is there a way to wait until it's fully opened?
I worked around this by putting the if (currentWebContents.devToolsWebContents) code into a setTimeout(..., 1000);
However, upon doing that, my before-input-event handler does not get triggered when pressing keys while focused on the devtools.
Does anybody know why that is?
There is no easy way to do this.
As per this issue, you can't detect input from devtools.
An Electron developer posted a comment here:
I think this is because the toggleDevTools menu role doesn't properly check for the 'parent' window of a devtools window. it would probably be possible to have the toggleDevTools menu role check to see if the currently focused window is a devtools window, and if so, call toggleDevTools on the webcontents for which the devtools is opened, instead of on the devtools window itself.
In any case, this requires Electron development to solve.
Update: Someone here suggested this workaround - I haven't tried it myself:
mainWindow.webContents.on("before-input-event", (e, input) => {
if (input.type === "keyDown" && input.key === "F12") {
mainWindow.webContents.toggleDevTools();
mainWindow.webContents.on('devtools-opened', () => {
// Can't use mainWindow.webContents.devToolsWebContents.on("before-input-event") - it just doesn't intercept any events.
mainWindow.webContents.devToolsWebContents.executeJavaScript(`
new Promise((resolve)=> {
addEventListener("keydown", (event) => {
if (event.key === "F12") {
resolve();
}
}, { once: true });
})
`)
.then(() => {
mainWindow.webContents.toggleDevTools();
});
});
}
});
I use for the first time electron and webchimera. I see this demo, but I don't want open in main window the player. I do not know good use electron so I try with a server that renders a index page and, after a button click, the player-page.
Client loads from server the index page:
//this is for webchimera
if (process.platform == 'win32')
process.env['VLC_PLUGIN_PATH'] = require('path').join(__dirname, 'node_modules/wcjs-prebuilt/bin/plugins');
const {app, BrowserWindow} = require('electron')
const path = require('path')
const url = require('url')
// 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 win
function createWindow () {
// Create the browser window.
win = new BrowserWindow({width: 800, height: 600})
// and load the index.html of the app.
win.loadURL("http://localhost:8888/")
// Open the DevTools.
win.webContents.openDevTools()
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS 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', () => {
// On macOS 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 (win === null) {
createWindow()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
The index page contains a link to player page, if it clicked the server responds with the player page.
The server is in nodejs.
Server run in localhost:8888 and it sends this html page:
<html>
<head>
<style>
body,html{ width: 100%; height: 100%; padding: 0px; margin:0px }
#player { width: 100%; height: 100% }
</style>
</head>
<body>
<div id="player"></div>
<script>
var wjs = require("wcjs-player");
var player = new wjs("#player").addPlayer({
autoplay: true,
wcjs: require('wcjs-prebuilt')
});
player.addPlaylist("http://archive.org/download/CartoonClassics/Krazy_Kat_- _Keeping_Up_With_Krazy.mp4");
</script>
</body>
</html>
But I obtain this error:
cannot find module wcjs-player
How can I fix this?
PS:
I use a server only because I wrote it for a webapp before I decided to use electron. It not necessary. I can remove the server and use only the client.
That means the module wcjs-player is not installed. Run npm install wcjs-player in your console/terminal to install it, that must help and also, you might wanna take a look at this