Compare commits

1 Commits

Author SHA1 Message Date
Robert Chery
5ff2e15571 Add Linux support with application mute handling and startup script 2026-07-08 21:28:21 -04:00
3 changed files with 99 additions and 7 deletions

View File

@@ -27,10 +27,40 @@ The architecture consists of three main components:
## Starting the Server ## 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. - **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 (`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. - **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 ## Configuration

View File

@@ -148,6 +148,10 @@ async function connectToPico() {
}, 1000); }, 1000);
}); });
serialPort.on('error', (err) => {
console.error('Serial port error: ', err.message);
});
parser = serialPort.pipe(new ReadlineParser({ delimiter: '\n' })); parser = serialPort.pipe(new ReadlineParser({ delimiter: '\n' }));
parser.on('data', async (line) => { parser.on('data', async (line) => {
@@ -193,10 +197,46 @@ async function connectToPico() {
}); });
} else if (btn.actionType === "mute_app" && btn.cmd) { } else if (btn.actionType === "mute_app" && btn.cmd) {
console.log(`Toggling mute for: ${btn.cmd}`); console.log(`Toggling mute for: ${btn.cmd}`);
if (process.platform === 'win32') {
const svvPath = path.join(__dirname, 'SoundVolumeView.exe'); const svvPath = path.join(__dirname, 'SoundVolumeView.exe');
exec(`"${svvPath}" /Switch "${btn.cmd}"`, (error, stdout, stderr) => { exec(`"${svvPath}" /Switch "${btn.cmd}"`, (error, stdout, stderr) => {
if (error) console.error(`Mute error: ${error}\n${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,")) { } else if (line.startsWith("R,")) {

22
start-macropad.sh Normal file
View File

@@ -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