From 5ff2e1557199595eda91218284e6ae2c75d588ce Mon Sep 17 00:00:00 2001 From: Robert Chery Date: Wed, 8 Jul 2026 21:28:21 -0400 Subject: [PATCH] Add Linux support with application mute handling and startup script --- README.md | 36 ++++++++++++++++++++++++++++++++--- server.js | 48 +++++++++++++++++++++++++++++++++++++++++++---- start-macropad.sh | 22 ++++++++++++++++++++++ 3 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 start-macropad.sh diff --git a/README.md b/README.md index ffd9ae6..071f040 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,40 @@ The architecture consists of three main components: ## Starting the Server -There are a couple of ways to launch the software depending on your needs: +There are a few ways to launch the software depending on your needs: -- **Interactive Mode (`Start-Macropad.bat`)**: Starts the Node.js server, displays the console output, and automatically opens your default web browser to the `http://localhost:3001` configuration page. -- **Background Mode (`Run-Hidden.vbs`)**: Silently launches the Node server in the background without opening a visible command prompt window. This is ideal for adding to your Windows Startup folder for seamless background operation. +- **Interactive Mode (Windows) (`Start-Macropad.bat`)**: Starts the Node.js server, displays the console output, and automatically opens your default web browser to the `http://localhost:3001` configuration page. +- **Background Mode (Windows) (`Run-Hidden.vbs`)**: Silently launches the Node server in the background without opening a visible command prompt window. This is ideal for adding to your Windows Startup folder for seamless background operation. +- **Linux Mode (`start-macropad.sh`)**: Starts the Node.js server and automatically opens the configuration page in your default browser. + +--- + +## Linux Installation & Prerequisites + +To run the application on Linux (e.g., Ubuntu/Debian), perform the following configuration steps: + +### 1. Serial Port Permissions +By default, Linux limits access to serial devices (like `/dev/ttyACM0`). Give your user account permission to access the Pico serial port by running: +```bash +sudo usermod -a -G dialout $USER +``` +*Note: You must log out and log back in (or reboot) for this change to take effect.* + +### 2. Keyboard Simulation Dependencies (`nut-js`) +The keyboard simulation library requires the development header for `libXtst` and requires an **X11 / XWayland** environment (native Wayland will block synthetic input): +```bash +sudo apt-get update +sudo apt-get install libxtst-dev +``` + +### 3. Launching +Make the script executable and run it: +```bash +chmod +x start-macropad.sh +./start-macropad.sh +``` + +--- ## Configuration diff --git a/server.js b/server.js index 0485687..6601043 100644 --- a/server.js +++ b/server.js @@ -148,6 +148,10 @@ async function connectToPico() { }, 1000); }); + serialPort.on('error', (err) => { + console.error('Serial port error: ', err.message); + }); + parser = serialPort.pipe(new ReadlineParser({ delimiter: '\n' })); parser.on('data', async (line) => { @@ -193,10 +197,46 @@ async function connectToPico() { }); } else if (btn.actionType === "mute_app" && btn.cmd) { console.log(`Toggling mute for: ${btn.cmd}`); - const svvPath = path.join(__dirname, 'SoundVolumeView.exe'); - exec(`"${svvPath}" /Switch "${btn.cmd}"`, (error, stdout, stderr) => { - if (error) console.error(`Mute error: ${error}\n${stderr}`); - }); + if (process.platform === 'win32') { + const svvPath = path.join(__dirname, 'SoundVolumeView.exe'); + exec(`"${svvPath}" /Switch "${btn.cmd}"`, (error, stdout, stderr) => { + if (error) console.error(`Mute error: ${error}\n${stderr}`); + }); + } else if (process.platform === 'linux') { + const cleanAppName = btn.cmd.replace(/\.exe$/i, '').toLowerCase(); + exec('pactl list sink-inputs', (error, stdout, stderr) => { + if (error) { + console.error(`pactl error: ${error}\n${stderr}`); + return; + } + const blocks = stdout.split(/Sink Input #/i); + let found = false; + for (const block of blocks) { + if (!block.trim()) continue; + const lines = block.split('\n'); + const index = parseInt(lines[0].trim()); + if (isNaN(index)) continue; + const blockLower = block.toLowerCase(); + const hasAppName = blockLower.includes(`application.name = "${cleanAppName}"`) || + blockLower.includes(`application.name = '${cleanAppName}'`) || + blockLower.includes(`application.process.binary = "${cleanAppName}"`) || + blockLower.includes(`application.process.binary = '${cleanAppName}'`) || + blockLower.includes(`media.name = "${cleanAppName}"`); + if (hasAppName) { + found = true; + console.log(`Found stream index ${index} for ${cleanAppName}, toggling mute...`); + exec(`pactl set-sink-input-mute ${index} toggle`, (err) => { + if (err) console.error(`Failed to mute sink input ${index}: ${err}`); + }); + } + } + if (!found) { + console.log(`Application '${cleanAppName}' not found in active audio streams.`); + } + }); + } else { + console.warn(`mute_app is not supported on platform: ${process.platform}`); + } } } else if (line.startsWith("R,")) { diff --git a/start-macropad.sh b/start-macropad.sh new file mode 100644 index 0000000..a569fe7 --- /dev/null +++ b/start-macropad.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Move to the script's directory +cd "$(dirname "$0")" + +echo "Starting Macropad Control Center..." +node server.js & +SERVER_PID=$! + +# Wait 2 seconds for server to start +sleep 2 + +# Open local config page in default browser +if command -v xdg-open > /dev/null; then + xdg-open http://localhost:3001 +elif command -v gnome-open > /dev/null; then + gnome-open http://localhost:3001 +else + echo "Please open http://localhost:3001 in your browser" +fi + +# Keep the script running to allow stopping the server with Ctrl+C +wait $SERVER_PID