设备访问

类似基于 Chromium 的浏览器一样, Electron 也提供了通过 web API 访问设备硬件的方法。 大部分接口就像在浏览器调用的 API 一样,但有一些差异需要考虑到。 Electron和浏览器之间的主要区别是请求访问设备时发生的情况。 在浏览器中,用户可以在弹出窗口中允许访问单独的设备。 在 Electron API中,提供了可供开发者自动选择设备或提示用户通过开发者创建的接口选择设备。

Web Bluetooth API

Web Bluetooth API 可以被用来连接蓝牙设备。 为了在 Electron 中使用此 API , 开发者将需要在 webContent 处理 select-bluetooth-device 事件 ,从而与设备请求相关联。

此外, ses.setBluetoothPairingHandler(handler) 方法可以被用来处理蓝牙设备配对, 这在 Windows 或 Linux 下进行额外的引脚校验时很有效.

示例

这个示例演示了一个 Electron 应用程序,当点击了 Test Bluetooth 按钮时,它会自动选择第一个可用的蓝牙设备。

docs/fiddles/features/web-bluetooth (23.0.0)Open in Fiddle

  • main.js
  • preload.js
  • index.html
  • renderer.js
  1. const {app, BrowserWindow, ipcMain} = require('electron')
  2. const path = require('path')
  3. function createWindow () {
  4. const mainWindow = new BrowserWindow({
  5. width: 800,
  6. height: 600,
  7. webPreferences: {
  8. preload: path.join(__dirname, 'preload.js')
  9. }
  10. })
  11. mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
  12. event.preventDefault()
  13. if (deviceList && deviceList.length > 0) {
  14. callback(deviceList[0].deviceId)
  15. }
  16. })
  17. // Listen for a message from the renderer to get the response for the Bluetooth pairing.
  18. ipcMain.on('bluetooth-pairing-response', (event, response) => {
  19. bluetoothPinCallback(response)
  20. })
  21. mainWindow.webContents.session.setBluetoothPairingHandler((details, callback) => {
  22. bluetoothPinCallback = callback
  23. // Send a message to the renderer to prompt the user to confirm the pairing.
  24. mainWindow.webContents.send('bluetooth-pairing-request', details)
  25. })
  26. mainWindow.loadFile('index.html')
  27. }
  28. app.whenReady().then(() => {
  29. createWindow()
  30. app.on('activate', function () {
  31. if (BrowserWindow.getAllWindows().length === 0) createWindow()
  32. })
  33. })
  34. app.on('window-all-closed', function () {
  35. if (process.platform !== 'darwin') app.quit()
  36. })
  1. const { contextBridge, ipcRenderer } = require('electron')
  2. contextBridge.exposeInMainWorld('electronAPI', {
  3. bluetoothPairingRequest: (callback) => ipcRenderer.on('bluetooth-pairing-request', callback),
  4. bluetoothPairingResponse: (response) => ipcRenderer.send('bluetooth-pairing-response', response)
  5. })
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
  6. <title>Web Bluetooth API</title>
  7. </head>
  8. <body>
  9. <h1>Web Bluetooth API</h1>
  10. <button id="clickme">Test Bluetooth</button>
  11. <p>Currently selected bluetooth device: <strong id="device-name""></strong></p>
  12. <script src="./renderer.js"></script>
  13. </body>
  14. </html>
  1. async function testIt() {
  2. const device = await navigator.bluetooth.requestDevice({
  3. acceptAllDevices: true
  4. })
  5. document.getElementById('device-name').innerHTML = device.name || `ID: ${device.id}`
  6. }
  7. document.getElementById('clickme').addEventListener('click',testIt)
  8. window.electronAPI.bluetoothPairingRequest((event, details) => {
  9. const response = {}
  10. switch (details.pairingKind) {
  11. case 'confirm': {
  12. response.confirmed = confirm(`Do you want to connect to device ${details.deviceId}?`)
  13. break
  14. }
  15. case 'confirmPin': {
  16. response.confirmed = confirm(`Does the pin ${details.pin} match the pin displayed on device ${details.deviceId}?`)
  17. break
  18. }
  19. case 'providePin': {
  20. const pin = prompt(`Please provide a pin for ${details.deviceId}.`)
  21. if (pin) {
  22. response.pin = pin
  23. response.confirmed = true
  24. } else {
  25. response.confirmed = false
  26. }
  27. }
  28. }
  29. window.electronAPI.bluetoothPairingResponse(response)
  30. })

WebHID API

WebHID API 可以用于访问HID 设备,例如 键盘和游戏机。 Electron 提供了几个使用 WebHID API的接口:

  • 调用 navigator.hid.requestDevice 并选择高清设备,将触发会话内的 select-hid-device 事件 在处理 select-hid-device 事件期间, hid-device-addedhid-device-removed 两种 Session 事件可以被用来处理设备插拔. Note: These events only fire until the callback from select-hid-device is called. They are not intended to be used as a generic hid device listener.
  • 在第一次调用 navigator.hid.requestDevice 前, 可以通过 ses.setDevicePermissionHandler(handler) 给予设备默认权限, Additionally, the default behavior of Electron is to store granted device permission through the lifetime of the corresponding WebContents. 如果需要更长期的存储,开发人员可以存储设备许可信息(比如: 在处理 select-hid-device 事件时), 然后通过 setDevicePermissionHandler 从存储的信息中读取
  • ses.setPermissionCheckHandler(handler) 可以用于禁用特定来源的 HID 访问。

阻止列表

默认情况下,Electron 使用与 Chromium 相同的 blocklist 如果您想要覆盖此行为,您可以通过设置 disable-hid-blocklist 标志来做到这一点:

  1. app.commandLine.appendSwitch('disable-hid-blocklist')

示例

这个示例演示了,当 Test WebHID 按钮被点击后,一个Electron 应用将通过 ses.setDevicePermissionHandler(handler)select-hid-device 会话事件 自动选择 HID 设备

docs/fiddles/features/web-hid (23.0.0)Open in Fiddle

  • main.js
  • index.html
  • renderer.js
  1. const {app, BrowserWindow} = require('electron')
  2. const path = require('path')
  3. function createWindow () {
  4. const mainWindow = new BrowserWindow({
  5. width: 800,
  6. height: 600
  7. })
  8. mainWindow.webContents.session.on('select-hid-device', (event, details, callback) => {
  9. //Add events to handle devices being added or removed before the callback on
  10. //`select-hid-device` is called.
  11. mainWindow.webContents.session.on('hid-device-added', (event, device) => {
  12. console.log('hid-device-added FIRED WITH', device)
  13. //Optionally update details.deviceList
  14. })
  15. mainWindow.webContents.session.on('hid-device-removed', (event, device) => {
  16. console.log('hid-device-removed FIRED WITH', device)
  17. //Optionally update details.deviceList
  18. })
  19. event.preventDefault()
  20. if (details.deviceList && details.deviceList.length > 0) {
  21. callback(details.deviceList[0].deviceId)
  22. }
  23. })
  24. mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
  25. if (permission === 'hid' && details.securityOrigin === 'file:///') {
  26. return true
  27. }
  28. })
  29. mainWindow.webContents.session.setDevicePermissionHandler((details) => {
  30. if (details.deviceType === 'hid' && details.origin === 'file://') {
  31. return true
  32. }
  33. })
  34. mainWindow.loadFile('index.html')
  35. }
  36. app.whenReady().then(() => {
  37. createWindow()
  38. app.on('activate', function () {
  39. if (BrowserWindow.getAllWindows().length === 0) createWindow()
  40. })
  41. })
  42. app.on('window-all-closed', function () {
  43. if (process.platform !== 'darwin') app.quit()
  44. })
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
  6. <title>WebHID API</title>
  7. </head>
  8. <body>
  9. <h1>WebHID API</h1>
  10. <button id="clickme">Test WebHID</button>
  11. <h3>HID devices automatically granted access via <i>setDevicePermissionHandler</i></h3>
  12. <div id="granted-devices"></div>
  13. <h3>HID devices automatically granted access via <i>select-hid-device</i></h3>
  14. <div id="granted-devices2"></div>
  15. <script src="./renderer.js"></script>
  16. </body>
  17. </html>
  1. async function testIt() {
  2. const grantedDevices = await navigator.hid.getDevices()
  3. let grantedDeviceList = ''
  4. grantedDevices.forEach(device => {
  5. grantedDeviceList += `<hr>${device.productName}</hr>`
  6. })
  7. document.getElementById('granted-devices').innerHTML = grantedDeviceList
  8. const grantedDevices2 = await navigator.hid.requestDevice({
  9. filters: []
  10. })
  11. grantedDeviceList = ''
  12. grantedDevices2.forEach(device => {
  13. grantedDeviceList += `<hr>${device.productName}</hr>`
  14. })
  15. document.getElementById('granted-devices2').innerHTML = grantedDeviceList
  16. }
  17. document.getElementById('clickme').addEventListener('click',testIt)

Web Serial API

Web Serial API 可以被用来访问串口设备比如 USB 或蓝牙。 为了在 Electron 中使用这个 API, 开发者需要先定义关联在串口请求中的 select-serial-port 会话事件.

有几个额外的 API 用于与 Web Serial API 合作:

  • The serial-port-added and serial-port-removed events on the Session can be used to handle devices being plugged in or unplugged when handling the select-serial-port event. Note: These events only fire until the callback from select-serial-port is called. They are not intended to be used as a generic serial port listener.
  • 在第一次调用 navigator.serial.requestPort 前, 可以通过 ses.setDevicePermissionHandler(handler) 给予设备默认权限, Additionally, the default behavior of Electron is to store granted device permission through the lifetime of the corresponding WebContents. 如果需要更长期的存储,开发人员可以存储设备许可信息(比如: 在处理 select-serial-port 事件时), 然后通过 setDevicePermissionHandler 从存储的信息中读取
  • ses.setPermissionCheckHandler(handler) 可以用于禁用特定来源的串口访问。

示例

这个示例项目既演示了一个 Electron 应用通过 ses.setDevicePermissionHandler(handler) 自动选择串口设备,又演示了当 Test Web Serial 按钮被点击后,通过 select-serial-port event on the Session 选择第一个可用的(若已连接) Arduino Uno 串口设备的流程。

docs/fiddles/features/web-serial (23.0.0)Open in Fiddle

  • main.js
  • index.html
  • renderer.js
  1. const {app, BrowserWindow} = require('electron')
  2. const path = require('path')
  3. function createWindow () {
  4. const mainWindow = new BrowserWindow({
  5. width: 800,
  6. height: 600
  7. })
  8. mainWindow.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => {
  9. //Add listeners to handle ports being added or removed before the callback for `select-serial-port`
  10. //is called.
  11. mainWindow.webContents.session.on('serial-port-added', (event, port) => {
  12. console.log('serial-port-added FIRED WITH', port)
  13. //Optionally update portList to add the new port
  14. })
  15. mainWindow.webContents.session.on('serial-port-removed', (event, port) => {
  16. console.log('serial-port-removed FIRED WITH', port)
  17. //Optionally update portList to remove the port
  18. })
  19. event.preventDefault()
  20. if (portList && portList.length > 0) {
  21. callback(portList[0].portId)
  22. } else {
  23. callback('') //Could not find any matching devices
  24. }
  25. })
  26. mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
  27. if (permission === 'serial' && details.securityOrigin === 'file:///') {
  28. return true
  29. }
  30. return false
  31. })
  32. mainWindow.webContents.session.setDevicePermissionHandler((details) => {
  33. if (details.deviceType === 'serial' && details.origin === 'file://') {
  34. return true
  35. }
  36. return false
  37. })
  38. mainWindow.loadFile('index.html')
  39. mainWindow.webContents.openDevTools()
  40. }
  41. app.whenReady().then(() => {
  42. createWindow()
  43. app.on('activate', function () {
  44. if (BrowserWindow.getAllWindows().length === 0) createWindow()
  45. })
  46. })
  47. app.on('window-all-closed', function () {
  48. if (process.platform !== 'darwin') app.quit()
  49. })
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
  6. <title>Web Serial API</title>
  7. <body>
  8. <h1>Web Serial API</h1>
  9. <button id="clickme">Test Web Serial API</button>
  10. <p>Matching Arduino Uno device: <strong id="device-name""></strong></p>
  11. <script src="./renderer.js"></script>
  12. </body>
  13. </html>
  1. async function testIt() {
  2. const filters = [
  3. { usbVendorId: 0x2341, usbProductId: 0x0043 },
  4. { usbVendorId: 0x2341, usbProductId: 0x0001 }
  5. ];
  6. try {
  7. const port = await navigator.serial.requestPort({filters});
  8. const portInfo = port.getInfo();
  9. document.getElementById('device-name').innerHTML = `vendorId: ${portInfo.usbVendorId} | productId: ${portInfo.usbProductId} `
  10. } catch (ex) {
  11. if (ex.name === 'NotFoundError') {
  12. document.getElementById('device-name').innerHTML = 'Device NOT found'
  13. } else {
  14. document.getElementById('device-name').innerHTML = ex
  15. }
  16. }
  17. }
  18. document.getElementById('clickme').addEventListener('click',testIt)

WebUSB API

The WebUSB API can be used to access USB devices. Electron provides several APIs for working with the WebUSB API:

  • The select-usb-device event on the Session can be used to select a USB device when a call to navigator.usb.requestDevice is made. Additionally the usb-device-added and usb-device-removed events on the Session can be used to handle devices being plugged in or unplugged when handling the select-usb-device event. Note: These two events only fire until the callback from select-usb-device is called. They are not intended to be used as a generic usb device listener.
  • The `usb-device-revoked’ event on the Session can be used to respond when device.forget() is called on a USB device.
  • ses.setDevicePermissionHandler(handler) can be used to provide default permissioning to devices without first calling for permission to devices via navigator.usb.requestDevice. Additionally, the default behavior of Electron is to store granted device permission through the lifetime of the corresponding WebContents. If longer term storage is needed, a developer can store granted device permissions (eg when handling the select-usb-device event) and then read from that storage with setDevicePermissionHandler.
  • ses.setPermissionCheckHandler(handler) can be used to disable USB access for specific origins.

示例

This example demonstrates an Electron application that automatically selects USB devices (if they are attached) through ses.setDevicePermissionHandler(handler) and through select-usb-device event on the Session when the Test WebUSB button is clicked.

docs/fiddles/features/web-usb (23.0.0)Open in Fiddle

  • main.js
  • index.html
  • renderer.js
  1. const {app, BrowserWindow} = require('electron')
  2. const e = require('express')
  3. const path = require('path')
  4. function createWindow () {
  5. const mainWindow = new BrowserWindow({
  6. width: 800,
  7. height: 600
  8. })
  9. let grantedDeviceThroughPermHandler
  10. mainWindow.webContents.session.on('select-usb-device', (event, details, callback) => {
  11. //Add events to handle devices being added or removed before the callback on
  12. //`select-usb-device` is called.
  13. mainWindow.webContents.session.on('usb-device-added', (event, device) => {
  14. console.log('usb-device-added FIRED WITH', device)
  15. //Optionally update details.deviceList
  16. })
  17. mainWindow.webContents.session.on('usb-device-removed', (event, device) => {
  18. console.log('usb-device-removed FIRED WITH', device)
  19. //Optionally update details.deviceList
  20. })
  21. event.preventDefault()
  22. if (details.deviceList && details.deviceList.length > 0) {
  23. const deviceToReturn = details.deviceList.find((device) => {
  24. if (!grantedDeviceThroughPermHandler || (device.deviceId != grantedDeviceThroughPermHandler.deviceId)) {
  25. return true
  26. }
  27. })
  28. if (deviceToReturn) {
  29. callback(deviceToReturn.deviceId)
  30. } else {
  31. callback()
  32. }
  33. }
  34. })
  35. mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
  36. if (permission === 'usb' && details.securityOrigin === 'file:///') {
  37. return true
  38. }
  39. })
  40. mainWindow.webContents.session.setDevicePermissionHandler((details) => {
  41. if (details.deviceType === 'usb' && details.origin === 'file://') {
  42. if (!grantedDeviceThroughPermHandler) {
  43. grantedDeviceThroughPermHandler = details.device
  44. return true
  45. } else {
  46. return false
  47. }
  48. }
  49. })
  50. mainWindow.loadFile('index.html')
  51. }
  52. app.whenReady().then(() => {
  53. createWindow()
  54. app.on('activate', function () {
  55. if (BrowserWindow.getAllWindows().length === 0) createWindow()
  56. })
  57. })
  58. app.on('window-all-closed', function () {
  59. if (process.platform !== 'darwin') app.quit()
  60. })
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
  6. <title>WebUSB API</title>
  7. </head>
  8. <body>
  9. <h1>WebUSB API</h1>
  10. <button id="clickme">Test WebUSB</button>
  11. <h3>USB devices automatically granted access via <i>setDevicePermissionHandler</i></h3>
  12. <div id="granted-devices"></div>
  13. <h3>USB devices automatically granted access via <i>select-usb-device</i></h3>
  14. <div id="granted-devices2"></div>
  15. <script src="./renderer.js"></script>
  16. </body>
  17. </html>
  1. function getDeviceDetails(device) {
  2. return grantedDevice.productName || `Unknown device ${grantedDevice.deviceId}`
  3. }
  4. async function testIt() {
  5. const noDevicesFoundMsg = 'No devices found'
  6. const grantedDevices = await navigator.usb.getDevices()
  7. let grantedDeviceList = ''
  8. if (grantedDevices.length > 0) {
  9. grantedDevices.forEach(device => {
  10. grantedDeviceList += `<hr>${getDeviceDetails(device)}</hr>`
  11. })
  12. } else {
  13. grantedDeviceList = noDevicesFoundMsg
  14. }
  15. document.getElementById('granted-devices').innerHTML = grantedDeviceList
  16. grantedDeviceList = ''
  17. try {
  18. const grantedDevice = await navigator.usb.requestDevice({
  19. filters: []
  20. })
  21. grantedDeviceList += `<hr>${getDeviceDetails(device)}</hr>`
  22. } catch (ex) {
  23. if (ex.name === 'NotFoundError') {
  24. grantedDeviceList = noDevicesFoundMsg
  25. }
  26. }
  27. document.getElementById('granted-devices2').innerHTML = grantedDeviceList
  28. }
  29. document.getElementById('clickme').addEventListener('click',testIt)