diff --git a/.github/workflows/linux-arm64-build-and-test.yml b/.github/workflows/linux-arm64-build-and-test.yml
index 2349260b..ade00a39 100644
--- a/.github/workflows/linux-arm64-build-and-test.yml
+++ b/.github/workflows/linux-arm64-build-and-test.yml
@@ -52,6 +52,12 @@ jobs:
- name: Install test-msgs on Linux
run: |
sudo apt install ros-${{ matrix.ros_distribution }}-test-msgs
+ # Adjust dependencies based on Ubuntu version
+ LIBASOUND_PKG="libasound2"
+ if grep -q "24.04" /etc/os-release; then
+ LIBASOUND_PKG="libasound2t64"
+ fi
+ sudo apt install -y xvfb libgtk-3-0 libnss3 $LIBASOUND_PKG libgbm-dev
- uses: actions/checkout@v6
diff --git a/.github/workflows/linux-x64-build-and-test.yml b/.github/workflows/linux-x64-build-and-test.yml
index 81bed305..433a9023 100644
--- a/.github/workflows/linux-x64-build-and-test.yml
+++ b/.github/workflows/linux-x64-build-and-test.yml
@@ -56,6 +56,12 @@ jobs:
- name: Install test-msgs on Linux
run: |
sudo apt install ros-${{ matrix.ros_distribution }}-test-msgs
+ # Adjust dependencies based on Ubuntu version
+ LIBASOUND_PKG="libasound2"
+ if grep -q "24.04" /etc/os-release; then
+ LIBASOUND_PKG="libasound2t64"
+ fi
+ sudo apt install -y xvfb libgtk-3-0 libnss3 $LIBASOUND_PKG libgbm-dev
- uses: actions/checkout@v6
diff --git a/electron_demo/car/package.json b/electron_demo/car/package.json
index 94bdb278..a2c1f1db 100644
--- a/electron_demo/car/package.json
+++ b/electron_demo/car/package.json
@@ -21,6 +21,6 @@
},
"devDependencies": {
"@electron/rebuild": "^3.6.0",
- "electron": "^31.0.0"
+ "electron": "^40.0.0"
}
}
diff --git a/electron_demo/car/renderer.js b/electron_demo/car/renderer.js
index 2ddcbcfe..f7bb7211 100644
--- a/electron_demo/car/renderer.js
+++ b/electron_demo/car/renderer.js
@@ -11,6 +11,7 @@
// limitations under the License.
const { ipcRenderer } = require('electron');
+const process = require('process');
// DOM elements
let currentCommandEl, linearXEl, angularZEl, topicNameEl;
@@ -27,6 +28,13 @@ document.addEventListener('DOMContentLoaded', function () {
initializeElements();
setupEventListeners();
setupROSListeners();
+
+ const versionDiv = document.createElement('div');
+ versionDiv.style.textAlign = 'center';
+ versionDiv.style.padding = '10px';
+ versionDiv.style.marginTop = '20px';
+ versionDiv.innerText = 'Electron version: ' + process.versions.electron;
+ document.querySelector('.container').appendChild(versionDiv);
});
function initializeElements() {
diff --git a/electron_demo/manipulator/index.html b/electron_demo/manipulator/index.html
index 8de52b26..83e47a86 100644
--- a/electron_demo/manipulator/index.html
+++ b/electron_demo/manipulator/index.html
@@ -153,6 +153,11 @@
Two-Joint Manipulator Demo
JointState - ROS2 sensor_msgs
Use the sliders to control joint angles or start automatic animation.
+
+ We are using Node.js ,
+ Chromium ,
+ and Electron .
+
🎯 What to Look For:
diff --git a/electron_demo/manipulator/package.json b/electron_demo/manipulator/package.json
index f46413da..0b470c4d 100644
--- a/electron_demo/manipulator/package.json
+++ b/electron_demo/manipulator/package.json
@@ -24,6 +24,6 @@
},
"devDependencies": {
"@electron/rebuild": "^3.7.2",
- "electron": "^31.7.7"
+ "electron": "^40.0.0"
}
}
diff --git a/electron_demo/manipulator/renderer.js b/electron_demo/manipulator/renderer.js
index 5f2da319..d112ef46 100644
--- a/electron_demo/manipulator/renderer.js
+++ b/electron_demo/manipulator/renderer.js
@@ -11,6 +11,7 @@
// limitations under the License.
const { ipcRenderer } = require('electron');
+const process = require('process');
// Three.js scene components
let scene, camera, renderer, controls;
@@ -28,6 +29,16 @@ let jointAngles = {
// Initialize the 3D scene
function initScene() {
+ const versionDiv = document.createElement('div');
+ versionDiv.style.position = 'absolute';
+ versionDiv.style.bottom = '10px';
+ versionDiv.style.right = '10px';
+ versionDiv.style.color = 'white';
+ versionDiv.style.fontFamily = 'Arial, sans-serif';
+ versionDiv.style.zIndex = '1000';
+ versionDiv.innerText = 'Electron version: ' + process.versions.electron;
+ document.body.appendChild(versionDiv);
+
const container = document.getElementById('canvas-container');
// Scene setup
@@ -358,6 +369,15 @@ window.addEventListener('resize', () => {
// UI event handlers
document.addEventListener('DOMContentLoaded', () => {
+ const replaceText = (selector, text) => {
+ const element = document.getElementById(selector);
+ if (element) element.innerText = text;
+ };
+
+ for (const type of ['chrome', 'node', 'electron']) {
+ replaceText(`${type}-version`, process.versions[type] || 'Unavailable');
+ }
+
initScene();
setupUIEventHandlers();
startFrequencyMeasurement();
diff --git a/electron_demo/topics/package.json b/electron_demo/topics/package.json
index 2eb69a9a..28c6b5c4 100644
--- a/electron_demo/topics/package.json
+++ b/electron_demo/topics/package.json
@@ -17,6 +17,6 @@
},
"devDependencies": {
"@electron/rebuild": "^3.6.0",
- "electron": "^31.0.0"
+ "electron": "^40.0.0"
}
}
diff --git a/electron_demo/topics/renderer.js b/electron_demo/topics/renderer.js
index 10933271..a7bd861d 100644
--- a/electron_demo/topics/renderer.js
+++ b/electron_demo/topics/renderer.js
@@ -11,6 +11,16 @@
// limitations under the License.
const { ipcRenderer } = require('electron');
+const process = require('process');
+
+document.addEventListener('DOMContentLoaded', () => {
+ const versionDiv = document.createElement('div');
+ versionDiv.style.position = 'fixed';
+ versionDiv.style.bottom = '10px';
+ versionDiv.style.left = '10px';
+ versionDiv.innerText = 'Electron version: ' + process.versions.electron;
+ document.body.appendChild(versionDiv);
+});
ipcRenderer.on('topic-received', function (event, response) {
document.getElementById('received-topic').innerText = response;
diff --git a/electron_demo/turtle_tf2/main.js b/electron_demo/turtle_tf2/main.js
index fe15bcdf..3a272184 100644
--- a/electron_demo/turtle_tf2/main.js
+++ b/electron_demo/turtle_tf2/main.js
@@ -335,7 +335,7 @@ async function createTurtleTf2Listener() {
);
// Timer to check for transforms and control turtle2
- const timer = node.createTimer(1000, () => {
+ node.createTimer(1000n, () => {
// Wrap the async logic in a try-catch to handle promise rejections
(async () => {
try {
@@ -442,7 +442,7 @@ async function createDynamicFrameTf2Broadcaster() {
const tfBroadcaster = node.createPublisher('tf2_msgs/msg/TFMessage', '/tf');
// Timer to broadcast dynamic transform
- const timer = node.createTimer(100, () => {
+ node.createTimer(100n, () => {
const now = node.now();
// Use a more stable time calculation to avoid NaN
@@ -535,7 +535,7 @@ async function createFixedFrameTf2Broadcaster() {
const tfBroadcaster = node.createPublisher('tf2_msgs/msg/TFMessage', '/tf');
// Timer to broadcast fixed transform
- const timer = node.createTimer(100, () => {
+ node.createTimer(100n, () => {
const now = node.now();
const fixedTransform = {
header: {
diff --git a/electron_demo/turtle_tf2/package.json b/electron_demo/turtle_tf2/package.json
index 3fc38989..c57dc59d 100644
--- a/electron_demo/turtle_tf2/package.json
+++ b/electron_demo/turtle_tf2/package.json
@@ -24,6 +24,6 @@
},
"devDependencies": {
"@electron/rebuild": "^3.7.2",
- "electron": "^31.7.7"
+ "electron": "^40.0.0"
}
}
diff --git a/electron_demo/turtle_tf2/renderer.js b/electron_demo/turtle_tf2/renderer.js
index 2b2becab..0b9c440d 100644
--- a/electron_demo/turtle_tf2/renderer.js
+++ b/electron_demo/turtle_tf2/renderer.js
@@ -11,6 +11,7 @@
// limitations under the License.
const { ipcRenderer } = require('electron');
+const process = require('process');
// 3D Scene variables
let scene, camera, renderer, controls;
@@ -41,10 +42,24 @@ let keyState = {
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', function () {
+ // Display Electron Version
+ const versionDiv = document.createElement('div');
+ versionDiv.style.position = 'fixed';
+ versionDiv.style.top = '25px';
+ versionDiv.style.right = '20px';
+ versionDiv.style.color = 'rgba(255, 255, 255, 0.9)';
+ versionDiv.style.fontSize = '14px';
+ versionDiv.style.fontWeight = 'bold';
+ versionDiv.style.zIndex = '999999';
+ versionDiv.style.pointerEvents = 'none';
+ versionDiv.innerText = 'Electron: ' + process.versions.electron;
+ document.body.appendChild(versionDiv);
+
initializeScene();
setupEventListeners();
setupKeyboardControls();
setupROSListeners();
+
updateStatus();
// Don't automatically hide loading screen - wait for ROS2 initialization
diff --git a/package.json b/package.json
index a9d4b027..5a17f0c9 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"install": "node scripts/install.js",
"postinstall": "npm run generate-messages",
"docs": "cd docs && make",
- "test": "nyc node --expose-gc ./scripts/run_test.js && tsd",
+ "test": "nyc node --expose-gc ./scripts/run_test.js && tsd && npm install --no-save electron && node test/electron/run_test.js",
"test-idl": "nyc node --expose-gc ./scripts/run_test.js --idl",
"lint": "eslint && node ./scripts/cpplint.js",
"format": "clang-format -i -style=file ./src/*.cpp ./src/*.h && npx --yes prettier --write \"{lib,rosidl_gen,rostsd_gen,rosidl_parser,types,example,test,scripts,benchmark,rostsd_gen}/**/*.{js,md,ts}\" ./*.{js,md,ts}",
diff --git a/test/electron/run_test.js b/test/electron/run_test.js
new file mode 100644
index 00000000..d847bf4f
--- /dev/null
+++ b/test/electron/run_test.js
@@ -0,0 +1,47 @@
+'use strict';
+
+const path = require('path');
+const { spawn } = require('child_process');
+
+let electron;
+try {
+ electron = require('electron');
+} catch (e) {
+ console.error(
+ 'Electron module not found. Please install electron to run this test.'
+ );
+ process.exit(1);
+}
+
+let command = electron;
+let args = [path.join(__dirname, 'test_usability.js'), '--no-sandbox'];
+
+// Handle headless Linux environments (like CI) by using xvfb-run
+if (process.platform === 'linux' && !process.env.DISPLAY) {
+ console.log('No DISPLAY detected. Attempting to use xvfb-run...');
+ command = 'xvfb-run';
+ // -a: --auto-servernum (use explicit server number to avoid conflicts)
+ args = ['-a', electron, ...args];
+}
+
+console.log('Launching Electron to run test_usability.js...');
+const child = spawn(command, args, {
+ stdio: 'inherit',
+ env: { ...process.env, ELECTRON_ENABLE_LOGGING: true },
+});
+
+child.on('close', (code) => {
+ console.log(`Electron process exited with code ${code}`);
+ if (code === 0) {
+ console.log('Test Passed!');
+ process.exit(0);
+ } else {
+ console.error('Test Failed!');
+ process.exit(1);
+ }
+});
+
+child.on('error', (err) => {
+ console.error('Failed to start electron:', err);
+ process.exit(1);
+});
diff --git a/test/electron/test_usability.js b/test/electron/test_usability.js
new file mode 100644
index 00000000..8afe2428
--- /dev/null
+++ b/test/electron/test_usability.js
@@ -0,0 +1,57 @@
+'use strict';
+
+const rclnodejs = require('../../index.js');
+const { app } = require('electron');
+
+app.on('ready', () => {
+ console.log('Electron version:', process.versions.electron);
+ rclnodejs
+ .init()
+ .then(() => {
+ console.log('rclnodejs initialized successfully.');
+ const node = rclnodejs.createNode('test_electron_node');
+ const publisher = node.createPublisher(
+ 'std_msgs/msg/String',
+ 'electron_test_topic'
+ );
+
+ const subscription = node.createSubscription(
+ 'std_msgs/msg/String',
+ 'electron_test_topic',
+ (msg) => {
+ if (msg.data === 'Hello from Electron') {
+ console.log(
+ 'Successfully received message in Electron environment.'
+ );
+ rclnodejs.shutdown();
+ app.quit();
+ process.exit(0);
+ }
+ }
+ );
+
+ console.log('Publisher and Subscriber created.');
+
+ // Publish repeatedly until received
+ const interval = setInterval(() => {
+ publisher.publish('Hello from Electron');
+ console.log('Published message...');
+ }, 100);
+
+ // Set a timeout to fail the test
+ setTimeout(() => {
+ console.error('Test Failed: Timeout waiting for message.');
+ clearInterval(interval);
+ rclnodejs.shutdown();
+ app.quit();
+ process.exit(1);
+ }, 10000);
+
+ rclnodejs.spin(node);
+ })
+ .catch((e) => {
+ console.error('Initialization failed:', e);
+ app.quit();
+ process.exit(1);
+ });
+});