Skip to content

Instantly share code, notes, and snippets.

@Scavanger
Last active February 15, 2024 18:43
Show Gist options
  • Save Scavanger/40be235292e44377b4eaec8218c33d83 to your computer and use it in GitHub Desktop.
Save Scavanger/40be235292e44377b4eaec8218c33d83 to your computer and use it in GitHub Desktop.
Bluetooth Device Chooser for Electron
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: black;
margin: 10px;
}
h1 {
font-size: 20px;
font-weight: bold;
color: white;
}
#id {
margin: 5px;
}
#list {
color: white;
}
.item {
margin: 5px;
padding: 5px;
height: 40px;
line-height: 40px;
width: 345px;
display: inline-block;
vertical-align: middle;
border: 1px solid whitesmoke;
}
.item:hover {
background-color: rgb(88, 88, 192);
}
#cancel {
text-align: center;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<link type="text/css" rel="stylesheet" href="./bluetoothDeviceChooser.css" media="all" />
<script src="./bluetoothDeviceChooserRenderer.js"></script>
<title>Bluetooth Device Chooser</title>
</head>
<body>
<h1>Select Bluetooth device:</h1>
<div id="list">
<div id="cancel" class="item">
Cancel
</div>
</div
</body>
</html>
document.addEventListener("DOMContentLoaded", () => {
window.electronAPI.bleScan(data => {
console.log(data);
data.forEach(device => {
var dev = document.getElementById(device.deviceId)
if (dev) {
dev.parentElement.removeChild(dev);
}
var item = document.createElement('div');
item.className = 'item'
item.id = device.deviceId;
item.addEventListener('click', () => {
window.electronAPI.deviceSelected(item.id);
window.close();
});
item.appendChild(document.createTextNode(device.deviceName + ' (' + device.deviceId + ')'));
document.getElementById('list').prepend(item);
});
});
document.getElementById('cancel').addEventListener('click', () => {
window.close();
});
});
const { contextBridge, ipcRenderer } = require('electron/renderer');
contextBridge.exposeInMainWorld('electronAPI', {
bleScan: (callback) => ipcRenderer.on('ble-scan', (_event, data) => callback(data)),
deviceSelected: (deviceId) => ipcRenderer.send('deviceSelected', deviceId)
});
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Bluetooth Device Chooser</title>
</head>
<body>
<h1>Bluetooth Device Chooser</h1>
<button id="clickme">Test Bluetooth</button>
<button id="cancel">Cancel Bluetooth Request</button>
<p>Currently selected bluetooth device: <strong id="device-name""></strong></p>
<script src="./renderer.js"></script>
</body>
</html>
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')
let bluetoothPinCallback;
let selectBluetoothCallback;
let mainWindow;
let bluetoothDeviceChooser = null;
let btDeviceList = null;
function createDeviceChooser() {
bluetoothDeviceChooser = new BrowserWindow({
parent: mainWindow,
width: 400,
height: 400,
webPreferences: {
preload: path.join(__dirname, 'bluetoothDeviceChoserPreload.js')
}
});
bluetoothDeviceChooser.removeMenu();
bluetoothDeviceChooser.loadFile('bluetoothDeviceChooser.html');
bluetoothDeviceChooser.webContents.openDevTools();
bluetoothDeviceChooser.on('closed', () => {
btDeviceList = null;
if (selectBluetoothCallback) {
selectBluetoothCallback('');
selectBluetoothCallback = null;
}
bluetoothDeviceChooser = null;
});
ipcMain.on('deviceSelected', (_event, deviceID) => {
if (selectBluetoothCallback) {
selectBluetoothCallback(deviceID);
selectBluetoothCallback = null;
}
});
}
function createWindow () {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault();
selectBluetoothCallback = callback;
console.log(deviceList);
const compare= (a, b) => {
if (a.length !== b.length) {
return false;
}
a.every((element, index) => {
if (element.deviceId !== b[index].deviceId) {
return false;
}
})
return true;
}
if (!btDeviceList || !compare(btDeviceList, deviceList)) {
btDeviceList = [...deviceList];
//console.log(btDeviceList);
if (!bluetoothDeviceChooser) {
createDeviceChooser();
}
bluetoothDeviceChooser.webContents.send('ble-scan', btDeviceList);
}
});
ipcMain.on('cancel-bluetooth-request', (event) => {
if (selectBluetoothCallback) {
selectBluetoothCallback('')
selectBluetoothCallback = null;
}
})
// Listen for a message from the renderer to get the response for the Bluetooth pairing.
ipcMain.on('bluetooth-pairing-response', (event, response) => {
bluetoothPinCallback(response)
})
mainWindow.webContents.session.setBluetoothPairingHandler((details, callback) => {
bluetoothPinCallback = callback
// Send a message to the renderer to prompt the user to confirm the pairing.
mainWindow.webContents.send('bluetooth-pairing-request', details)
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
{
"name": "Bluetooth Demo",
"productName": "Bluetooth Device Chooser Demo",
"description": "Bluetooth Device Chooser for Electron",
"keywords": [],
"main": "./main.js",
"version": "1.0.0",
"author": "Scavanger",
"scripts": {
"start": "electron ."
},
"dependencies": {},
"devDependencies": {
"electron": "28.2.2"
}
}
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
cancelBluetoothRequest: () => ipcRenderer.send('cancel-bluetooth-request'),
bluetoothPairingRequest: (callback) => ipcRenderer.on('bluetooth-pairing-request', () => callback()),
bluetoothPairingResponse: (response) => ipcRenderer.send('bluetooth-pairing-response', response)
})
async function testIt () {
try {
const device = await navigator.bluetooth.requestDevice({
acceptAllDevices: true
})
document.getElementById('device-name').innerHTML = device.name || `ID: ${device.id}`
} catch {
document.getElementById('device-name').innerHTML = '';
}
}
document.getElementById('clickme').addEventListener('click', testIt)
function cancelRequest () {
window.electronAPI.cancelBluetoothRequest()
}
document.getElementById('cancel').addEventListener('click', cancelRequest)
window.electronAPI.bluetoothPairingRequest((event, details) => {
const response = {}
switch (details.pairingKind) {
case 'confirm': {
response.confirmed = window.confirm(`Do you want to connect to device ${details.deviceId}?`)
break
}
case 'confirmPin': {
response.confirmed = window.confirm(`Does the pin ${details.pin} match the pin displayed on device ${details.deviceId}?`)
break
}
case 'providePin': {
const pin = window.prompt(`Please provide a pin for ${details.deviceId}.`)
if (pin) {
response.pin = pin
response.confirmed = true
} else {
response.confirmed = false
}
}
}
window.electronAPI.bluetoothPairingResponse(response)
})
/* styles.css */
/* Add styles here to customize the appearance of your app */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment