From d8de0002950ac9ba8d45a94e682f574e7d4207f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Gro=C3=9F?= Date: Fri, 1 Nov 2024 01:58:58 +0100 Subject: [PATCH 1/4] added commands to enable/disable via management API --- .vscode/extensions.json | 5 ++ browser-management-api-requests.http | 94 ++++++++++++++++++++++++++++ readme.md | 19 ++++++ src/server.js | 69 ++++++++++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 .vscode/extensions.json create mode 100644 browser-management-api-requests.http diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..d24a18f --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "humao.rest-client" + ] +} \ No newline at end of file diff --git a/browser-management-api-requests.http b/browser-management-api-requests.http new file mode 100644 index 0000000..58570cb --- /dev/null +++ b/browser-management-api-requests.http @@ -0,0 +1,94 @@ +# This file can be used with the VSCode Rest Client extension (https://marketplace.visualstudio.com/items?itemName=humao.rest-client) to play with the browser block API. +@baseUrl=http://your-device-hostname:5011 + +### Ping the browser block +GET {{baseUrl}}/ping + +### Refresh the currently displayed page +POST {{baseUrl}}/refresh + +### Automatically refresh the browser window every 30 seconds +POST {{baseUrl}}/autorefresh/30 + +### Disable automatic refresh +POST {{baseUrl}}/autorefresh/0 + +### Re-scan for local HTTP or HTTPS services +POST {{baseUrl}}/scan + +### Get the URL currently being displayed +GET {{baseUrl}}/url + +### Set the URL to be displayed +PUT {{baseUrl}}/url +Content-Type: application/x-www-form-urlencoded + +url=http://www.balena.io + +### Set the URL with GPU and Kiosk settings +PUT {{baseUrl}}/url +Content-Type: application/x-www-form-urlencoded + +url=http://www.balena.io&gpu=1&kiosk=1 + +### Get the status of the GPU +GET {{baseUrl}}/gpu + +### Enable the GPU +PUT {{baseUrl}}/gpu/1 + +### Disable the GPU +PUT {{baseUrl}}/gpu/0 + +### Get the status of Kiosk mode +GET {{baseUrl}}/kiosk + +### Enable Kiosk mode +POST {{baseUrl}}/kiosk/1 + +### Disable Kiosk mode +POST {{baseUrl}}/kiosk/0 + +### Get the flags Chromium was started with +GET {{baseUrl}}/flags + +### Get the version of Chromium +GET {{baseUrl}}/version + +### Take a screenshot of the Chromium window +GET {{baseUrl}}/screenshot + +### Get the display state +GET {{baseUrl}}/display + +### Set the display state to on +POST {{baseUrl}}/display +Content-Type: application/json + +{ + "state": "on" +} + +### Set the display state to off +POST {{baseUrl}}/display +Content-Type: application/json + +{ + "state": "off" +} + +### Set the display state to on using PUT +PUT {{baseUrl}}/display +Content-Type: application/json + +{ + "state": "on" +} + +### Set the display state to off using PUT +PUT {{baseUrl}}/display +Content-Type: application/json + +{ + "state": "off" +} \ No newline at end of file diff --git a/readme.md b/readme.md index 831e3bb..b556d3e 100644 --- a/readme.md +++ b/readme.md @@ -14,6 +14,7 @@ The block provides an API for dynamic configuration, and also exposes the Chromi - Automatically displays local HTTP (port 80 or 8080) or HTTPS (443) service endpoints. - API for remote configuration and management - Chromium remote debugging port +- Remotely enable/disable display --- ## Usage @@ -221,6 +222,24 @@ Returns the version of Chromium that `browser` is running Uses [scrot](https://opensource.com/article/17/11/taking-screen-captures-linux-command-line-scrot) to take a screenshot of the chromium window. The screenshot will be saved as a temporary file in the container. + +#### **GET** /display +Returns `on` or `off` depending on the display status + +#### **PUT** or **POST** /display +Expects json payload with `state` key. + +Turn display on: +```bash +curl -X PUT -H "Content-Type: application/json" -d '{"state": "on"}' http://localhost:5011/display +``` + +Turn display off: +```bash +curl -X PUT -H "Content-Type: application/json" -d '{"state": "off"}' http://localhost:5011/display +``` + + --- ## Supported devices diff --git a/src/server.js b/src/server.js index 79c8031..2c9642b 100644 --- a/src/server.js +++ b/src/server.js @@ -10,6 +10,7 @@ const { } = require('set-interval-async/dynamic') const { spawn } = require('child_process'); const { readFile, unlink } = require('fs').promises; +const { exec } = require('child_process'); const path = require('path'); const os = require('os'); @@ -35,6 +36,8 @@ let flags = []; // Refresh timer object let timer = {}; +// store the display state +let displayState = 'on'; // Returns the URL to display, adhering to the hieracrchy: // 1) the configured LAUNCH_URL // 2) a discovered HTTP service on the device @@ -388,6 +391,72 @@ app.post('/scan', (req, res) => { return res.status(200).send('ok'); }); +// Function to enable the display +function enableDisplay() { + exec('xset dpms force on', (error, stdout, stderr) => { + if (error) { + console.error(`Error enabling display: ${error}`); + return; + } + console.log('Display enabled'); + }); +} + +// Function to disable the display +function disableDisplay() { + exec('xset dpms force off', (error, stdout, stderr) => { + if (error) { + console.error(`Error disabling display: ${error}`); + return; + } + console.log('Display disabled'); + }); +} + +app.post('/display', (req, res) => { + if (!req.body.state) { + return res.status(400).send('Bad request: missing state in the body element'); + } + + const state = req.body.state.toLowerCase(); + if (state !== 'on' && state !== 'off') { + return res.status(400).send('Bad request: state must be "on" or "off"'); + } + + displayState = state; + if (displayState === 'on') { + enableDisplay(); + } else { + disableDisplay(); + } + + return res.status(204).send(`Display state set to ${displayState}`); +}); + +app.put('/display', (req, res) => { + if (!req.body.state) { + return res.status(400).send('Bad request: missing state in the body element'); + } + + const state = req.body.state.toLowerCase(); + if (state !== 'on' && state !== 'off') { + return res.status(400).send('Bad request: state must be "on" or "off"'); + } + + displayState = state; + if (displayState === 'on') { + enableDisplay(); + } else { + disableDisplay(); + } + + return res.status(204).send(`Display state set to ${displayState}`); +}); + +app.get('/display', (req, res) => { + return res.status(200).send(displayState); +}); + app.listen(API_PORT, () => { console.log('Browser API running on port: ' + API_PORT); }); From a69c2ca92cea25624e2446b250972b892dff42c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Gro=C3=9F?= Date: Fri, 1 Nov 2024 01:58:58 +0100 Subject: [PATCH 2/4] Add commands to enable/disable via management API --- .vscode/extensions.json | 5 ++ browser-management-api-requests.http | 94 ++++++++++++++++++++++++++++ readme.md | 19 ++++++ src/server.js | 69 ++++++++++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 .vscode/extensions.json create mode 100644 browser-management-api-requests.http diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..d24a18f --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "humao.rest-client" + ] +} \ No newline at end of file diff --git a/browser-management-api-requests.http b/browser-management-api-requests.http new file mode 100644 index 0000000..58570cb --- /dev/null +++ b/browser-management-api-requests.http @@ -0,0 +1,94 @@ +# This file can be used with the VSCode Rest Client extension (https://marketplace.visualstudio.com/items?itemName=humao.rest-client) to play with the browser block API. +@baseUrl=http://your-device-hostname:5011 + +### Ping the browser block +GET {{baseUrl}}/ping + +### Refresh the currently displayed page +POST {{baseUrl}}/refresh + +### Automatically refresh the browser window every 30 seconds +POST {{baseUrl}}/autorefresh/30 + +### Disable automatic refresh +POST {{baseUrl}}/autorefresh/0 + +### Re-scan for local HTTP or HTTPS services +POST {{baseUrl}}/scan + +### Get the URL currently being displayed +GET {{baseUrl}}/url + +### Set the URL to be displayed +PUT {{baseUrl}}/url +Content-Type: application/x-www-form-urlencoded + +url=http://www.balena.io + +### Set the URL with GPU and Kiosk settings +PUT {{baseUrl}}/url +Content-Type: application/x-www-form-urlencoded + +url=http://www.balena.io&gpu=1&kiosk=1 + +### Get the status of the GPU +GET {{baseUrl}}/gpu + +### Enable the GPU +PUT {{baseUrl}}/gpu/1 + +### Disable the GPU +PUT {{baseUrl}}/gpu/0 + +### Get the status of Kiosk mode +GET {{baseUrl}}/kiosk + +### Enable Kiosk mode +POST {{baseUrl}}/kiosk/1 + +### Disable Kiosk mode +POST {{baseUrl}}/kiosk/0 + +### Get the flags Chromium was started with +GET {{baseUrl}}/flags + +### Get the version of Chromium +GET {{baseUrl}}/version + +### Take a screenshot of the Chromium window +GET {{baseUrl}}/screenshot + +### Get the display state +GET {{baseUrl}}/display + +### Set the display state to on +POST {{baseUrl}}/display +Content-Type: application/json + +{ + "state": "on" +} + +### Set the display state to off +POST {{baseUrl}}/display +Content-Type: application/json + +{ + "state": "off" +} + +### Set the display state to on using PUT +PUT {{baseUrl}}/display +Content-Type: application/json + +{ + "state": "on" +} + +### Set the display state to off using PUT +PUT {{baseUrl}}/display +Content-Type: application/json + +{ + "state": "off" +} \ No newline at end of file diff --git a/readme.md b/readme.md index 831e3bb..b556d3e 100644 --- a/readme.md +++ b/readme.md @@ -14,6 +14,7 @@ The block provides an API for dynamic configuration, and also exposes the Chromi - Automatically displays local HTTP (port 80 or 8080) or HTTPS (443) service endpoints. - API for remote configuration and management - Chromium remote debugging port +- Remotely enable/disable display --- ## Usage @@ -221,6 +222,24 @@ Returns the version of Chromium that `browser` is running Uses [scrot](https://opensource.com/article/17/11/taking-screen-captures-linux-command-line-scrot) to take a screenshot of the chromium window. The screenshot will be saved as a temporary file in the container. + +#### **GET** /display +Returns `on` or `off` depending on the display status + +#### **PUT** or **POST** /display +Expects json payload with `state` key. + +Turn display on: +```bash +curl -X PUT -H "Content-Type: application/json" -d '{"state": "on"}' http://localhost:5011/display +``` + +Turn display off: +```bash +curl -X PUT -H "Content-Type: application/json" -d '{"state": "off"}' http://localhost:5011/display +``` + + --- ## Supported devices diff --git a/src/server.js b/src/server.js index 79c8031..2c9642b 100644 --- a/src/server.js +++ b/src/server.js @@ -10,6 +10,7 @@ const { } = require('set-interval-async/dynamic') const { spawn } = require('child_process'); const { readFile, unlink } = require('fs').promises; +const { exec } = require('child_process'); const path = require('path'); const os = require('os'); @@ -35,6 +36,8 @@ let flags = []; // Refresh timer object let timer = {}; +// store the display state +let displayState = 'on'; // Returns the URL to display, adhering to the hieracrchy: // 1) the configured LAUNCH_URL // 2) a discovered HTTP service on the device @@ -388,6 +391,72 @@ app.post('/scan', (req, res) => { return res.status(200).send('ok'); }); +// Function to enable the display +function enableDisplay() { + exec('xset dpms force on', (error, stdout, stderr) => { + if (error) { + console.error(`Error enabling display: ${error}`); + return; + } + console.log('Display enabled'); + }); +} + +// Function to disable the display +function disableDisplay() { + exec('xset dpms force off', (error, stdout, stderr) => { + if (error) { + console.error(`Error disabling display: ${error}`); + return; + } + console.log('Display disabled'); + }); +} + +app.post('/display', (req, res) => { + if (!req.body.state) { + return res.status(400).send('Bad request: missing state in the body element'); + } + + const state = req.body.state.toLowerCase(); + if (state !== 'on' && state !== 'off') { + return res.status(400).send('Bad request: state must be "on" or "off"'); + } + + displayState = state; + if (displayState === 'on') { + enableDisplay(); + } else { + disableDisplay(); + } + + return res.status(204).send(`Display state set to ${displayState}`); +}); + +app.put('/display', (req, res) => { + if (!req.body.state) { + return res.status(400).send('Bad request: missing state in the body element'); + } + + const state = req.body.state.toLowerCase(); + if (state !== 'on' && state !== 'off') { + return res.status(400).send('Bad request: state must be "on" or "off"'); + } + + displayState = state; + if (displayState === 'on') { + enableDisplay(); + } else { + disableDisplay(); + } + + return res.status(204).send(`Display state set to ${displayState}`); +}); + +app.get('/display', (req, res) => { + return res.status(200).send(displayState); +}); + app.listen(API_PORT, () => { console.log('Browser API running on port: ' + API_PORT); }); From 21215986f95e589fe5fbd54bec7bb9053dfe0c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Gro=C3=9F?= Date: Sat, 9 Nov 2024 21:26:56 +0100 Subject: [PATCH 3/4] allow remote debugging again --- Dockerfile.template | 3 +++ readme.md | 2 +- src/start.sh | 9 +++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Dockerfile.template b/Dockerfile.template index ec28f91..5f13458 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -38,6 +38,9 @@ RUN usermod -a -G audio,video,tty chromium RUN curl -skL https://raw.githubusercontent.com/balena-labs-projects/audio/master/scripts/alsa-bridge/debian-setup.sh| sh ENV PULSE_SERVER=tcp:audio:4317 +# install socat to forard chromiums debug port +RUN install_packages socat + COPY VERSION . # Start app diff --git a/readme.md b/readme.md index b556d3e..997d779 100644 --- a/readme.md +++ b/readme.md @@ -92,7 +92,7 @@ The following environment variables allow configuration of the `browser` block: |`WINDOW_SIZE`|`x,y`|Detected screen resolution|Sets the browser window size, such as `800,600`.
**Note:** Reverse the dimensions if you also rotate the display to `left` or `right` | |`WINDOW_POSITION`|`x,y`|`0,0`|Specifies the browser window position on the screen| |`API_PORT`|port number|5011|Specifies the port number the API runs on| -|`REMOTE_DEBUG_PORT`|port number|35173|Specifies the port number the chrome remote debugger runs on| +|`REMOTE_DEBUG_PORT`|port number|35173|Specifies the port number the chrome remote debugger runs on. The actual port a client (like chrome) might connect to it will be REMOTE_DEBUG_PORT + 1 due to a deprecation of [this chromium feature](https://issues.chromium.org/issues/41487252).| |`AUTO_REFRESH`|interval|0 (disabled)|Specifies the number of seconds before the page automatically refreshes| --- diff --git a/src/start.sh b/src/start.sh index 6e89c01..1610f4f 100644 --- a/src/start.sh +++ b/src/start.sh @@ -68,4 +68,13 @@ environment="${environment::-1}" # launch Chromium and whitelist the enVars so that they pass through to the su session su -w $environment -c "export DISPLAY=:$DISPLAY_NUM && startx /usr/src/app/startx.sh $CURSOR" - chromium + +# forward the chromium remote debugging port from 0.0.0.0 to localhost to deal with the dropped support to listen on all interfaces (https://issues.chromium.org/issues/41487252) +REMOTE_DEBUG_PORT=${REMOTE_DEBUG_PORT:-35173} +REMOTE_DEBUG_FORWARD_PORT=$(($REMOTE_DEBUG_PORT+1)) +echo "Forwarding remote debugging port 0.0.0.0:$REMOTE_DEBUG_FORWARD_PORT to (internal) localhost:$REMOTE_DEBUG_PORT" +socat TCP-LISTEN:${REMOTE_DEBUG_FORWARD_PORT},fork,reuseaddr TCP:localhost:${REMOTE_DEBUG_PORT} & + +# TODO: We should use REMOTE_DEBUG_PORT as the main env var, and handle the forwarding internally instead of this workaround-convention. The main api to a user should remain REMOTE_DEBUG_PORT + balena-idle From e1d1fd617d635bb8c8963ef372749f918fc1aa5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henning=20Gro=C3=9F?= Date: Sat, 9 Nov 2024 21:32:11 +0100 Subject: [PATCH 4/4] start socat before chromium --- src/start.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/start.sh b/src/start.sh index 1610f4f..b503c9d 100644 --- a/src/start.sh +++ b/src/start.sh @@ -66,15 +66,13 @@ environment=$(env | grep -v -w '_' | awk -F= '{ st = index($0,"=");print substr( # remove the last comma environment="${environment::-1}" -# launch Chromium and whitelist the enVars so that they pass through to the su session -su -w $environment -c "export DISPLAY=:$DISPLAY_NUM && startx /usr/src/app/startx.sh $CURSOR" - chromium - # forward the chromium remote debugging port from 0.0.0.0 to localhost to deal with the dropped support to listen on all interfaces (https://issues.chromium.org/issues/41487252) REMOTE_DEBUG_PORT=${REMOTE_DEBUG_PORT:-35173} REMOTE_DEBUG_FORWARD_PORT=$(($REMOTE_DEBUG_PORT+1)) echo "Forwarding remote debugging port 0.0.0.0:$REMOTE_DEBUG_FORWARD_PORT to (internal) localhost:$REMOTE_DEBUG_PORT" socat TCP-LISTEN:${REMOTE_DEBUG_FORWARD_PORT},fork,reuseaddr TCP:localhost:${REMOTE_DEBUG_PORT} & - # TODO: We should use REMOTE_DEBUG_PORT as the main env var, and handle the forwarding internally instead of this workaround-convention. The main api to a user should remain REMOTE_DEBUG_PORT +# launch Chromium and whitelist the enVars so that they pass through to the su session +su -w $environment -c "export DISPLAY=:$DISPLAY_NUM && startx /usr/src/app/startx.sh $CURSOR" - chromium balena-idle