# \[PLUGIN\] Touch Display

**URL:** <https://community.volumio.com/t/plugin-touch-display/10647>\
**Category:** Plugins\
**Created:** [July 1, 2019, 9:44pm UTC](https://community.volumio.com/t/plugin-touch-display/10647 "2019-07-01T21:44:01Z")\
**Posts on this page:** 1\
**Showing post:** 331

<div class="post-metadata">

**Author:** ![Schmurtz](https://community.volumio.com/user_avatar/community.volumio.com/schmurtz/32/13676_2.png) [@Schmurtz](https://community.volumio.com/u/Schmurtz)\
**Post date:** [January 28, 2022, 5:59pm UTC](https://community.volumio.com/t/plugin-touch-display/10647/331 "2022-01-28T17:59:58Z")

</div>

Hi, Thanks for this great plugin !  
I have [a cheap 3.5inch screen](https://community.volumio.com/t/volumio-with-3-5-tft-touch-screen-gpio-rpi-3b/11265/263) which probably not support dpms, I discover that the LCD backlight is always on but can be disabled with GPIO :  
`gpio mode 3 out` and then  
`gpio write 3 1` to switch on the backlight  
`gpio write 3 0` to switch off

I just need to ON/OFF the screen so first I found the plugin “[gpio control](https://community.volumio.com/t/plugin-gpio-control-control-gpio-with-volumio2-events/10173)”. Thanks to gvolt it works on Volumio 3 and I’m able to switch off/on my backlight on play/pause, …

But I think that the screensaver is very conveniant so I would like to add these gpio commands next to the existing dpms commands in the Touch Display plugin…

Can I edit the /data/plugins/user\_interface/touch\_display/index.js file directly and restart the plugin to test ?

I’ve added this kind of commands :  
`exec('/usr/bin/gpio write 3 0');`  
just before dpms commands but it doesn’t seems to works.

> **My index.js file, click here to expanse**
>
> ```auto
> 
> 'use strict';
> 
> const libQ = require('kew');
> const fs = require('fs-extra');
> const { exec } = require('child_process');
> const path = require('path');
> const os = require('os');
> const net = require('net');
> const io = require('socket.io-client');
> const unixDomSocket = new net.Socket();
> const socket = io.connect('http://localhost:3000');
> const blInterface = '/sys/devices/platform/rpi_backlight/backlight/rpi_backlight';
> const als = '/etc/als'; // The plugin awaits the current value of an optional ambient light sensor (ALS) as a single number in /etc/als.
> const configTxtGpuMemBanner = '#### Touch Display gpu_mem setting below: do not alter ####' + os.EOL;
> const configTxtRotationBanner = '#### Touch Display rotation setting below: do not alter ####' + os.EOL;
> const id = 'touch_display: ';
> let rpiScreen = false;
> let rpiBacklight = false;
> let maxBrightness = 255;
> const alsProgression = [];
> let autoBrTimeoutCleared = false;
> let currentlyAdjusting = false;
> let uiNeedsUpdate = false;
> let device, displayNumber, autoBrTimer, toggleBrTimer;
> 
> module.exports = TouchDisplay;
> 
> function TouchDisplay (context) {
> const self = this;
> 
> self.context = context;
> self.commandRouter = self.context.coreCommand;
> self.logger = self.context.logger;
> self.configManager = self.context.configManager;
> }
> 
> TouchDisplay.prototype.onVolumioStart = function () {
> const self = this;
> const configFile = self.commandRouter.pluginManager.getConfigurationFile(self.context, 'config.json');
> 
> self.config = new (require('v-conf'))();
> self.config.loadFile(configFile);
> return libQ.resolve();
> };
> 
> TouchDisplay.prototype.onVolumioShutdown = function () {
> const self = this;
> 
> if (rpiBacklight) {
> // in order to have full brightness during the next boot up
> self.setBrightness(maxBrightness);
> }
> return libQ.resolve();
> };
> 
> TouchDisplay.prototype.onVolumioReboot = function () {
> const self = this;
> 
> if (rpiBacklight) {
> // in order to have full brightness during the next boot up
> self.setBrightness(maxBrightness);
> }
> return libQ.resolve();
> };
> 
> TouchDisplay.prototype.onStart = function () {
> const self = this;
> const defer = libQ.defer();
> let lastStateIsPlaying = false;
> let attempts = 0;
> 
> self.commandRouter.loadI18nStrings();
> self.systemctl('daemon-reload')
> .then(self.systemctl.bind(self, 'start volumio-kiosk.service'))
> .then(function () {
> self.logger.info(id + 'Volumio Kiosk started');
> self.commandRouter.executeOnPlugin('system_controller', 'system', 'getSystemVersion', '')
> .then(function (infos) {
> device = infos.hardware;
> if (device === 'pi') {
> fs.readFile('/proc/modules', 'utf8', function (err, data) {
> if (err) {
> self.logger.error(id + 'Error reading /proc/modules: ' + err);
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.ERR_READ') + '/proc/modules: ' + err);
> } else {
> // detect Raspberry Pi Foundation original touch screen
> if (data.match(/^rpi_ft5406\b/gm) === null && data.match(/^raspberrypi_ts\b/gm) === null) {
> self.logger.info(id + 'No Raspberry Pi Foundation touch screen detected.');
> } else {
> rpiScreen = true;
> self.logger.info(id + 'Raspberry Pi Foundation touch screen detected.');
> // check for backlight module of Raspberry Pi Foundation original touch screen
> if (data.match(/^rpi_backlight\b/gm) === null) {
> self.logger.info(id + 'No backlight module of a Raspberry Pi Foundation touch screen detected.');
> } else {
> rpiBacklight = true;
> self.logger.info(id + 'Backlight module of a Raspberry Pi Foundation touch screen detected.');
> // screen brightness
> fs.readFile(blInterface + '/max_brightness', 'utf8', function (err, data) {
> if (err) {
> self.logger.error(id + 'Error reading ' + blInterface + '/max_brightness: ' + err);
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.ERR_READ') + blInterface + '/max_brightness: ' + err);
> } else {
> maxBrightness = parseInt(data, 10);
> exec('/usr/bin/sudo /bin/chmod a+w ' + blInterface + '/brightness', { uid: 1000, gid: 1000 }, function (error, stdout, stderr) {
> if (error !== null) {
> self.logger.error(id + 'Error setting file permissions for backlight brightness control: ' + error);
> } else {
> self.logger.info(id + 'File permissions for backlight brightness control set.');
> if (!self.config.get('autoMode')) {
> if (self.config.get('br2StartTime') !== self.config.get('br1StartTime')) {
> self.toggleBrightness();
> } else {
> self.setBrightness(self.config.get('manualBr'));
> }
> } else {
> self.autoBrightness();
> }
> }
> });
> }
> });
> }
> }
> }
> // screen orientation
> self.setOrientation(self.config.get('angle'));
> // GPU memory size
> if (self.config.get('controlGpuMem')) {
> self.modBootConfig(configTxtGpuMemBanner + 'gpu_mem=.*', configTxtGpuMemBanner + 'gpu_mem=' + self.config.get('gpuMem'))
> .then(self.modBootConfig.bind(self, '^gpu_mem', '#GPU_MEM'))
> .fail(function () {
> self.logger.info(id + 'Writing the touch display plugin\'s gpu_mem setting failed. Previous gpu_mem settings in /boot/config.txt have not been commented.');
> });
> }
> });
> }
> });
> // screensaver
> if (self.commandRouter.volumioGetState().status === 'play') {
> lastStateIsPlaying = true;
> }
> unixDomSocket.connect('/tmp/.X11-unix/X' + self.getDisplaynumber());
> unixDomSocket.on('connect', function () {
> if ((self.config.get('afterPlay') && self.commandRouter.volumioGetState().status === 'play') || self.config.get('timeout') === 0) {
> exec('/usr/bin/gpio write 26 1');
> exec('/usr/bin/xset -display :' + displayNumber + ' s reset dpms force on', { uid: 1000, gid: 1000 }, function (error, stdout, stderr) {
> if (error !== null) {
> self.logger.error(id + 'Error waking up the screen: ' + error);
> }
> });
> self.setScreenTimeout(0, false);
> } else {
> self.setScreenTimeout(self.config.get('timeout'), false);
> }
> attempts = 0;
> });
> unixDomSocket.on('error', function (data) {
> });
> unixDomSocket.on('close', function () {
> if (attempts < 100) {
> setTimeout(function () {
> unixDomSocket.connect('/tmp/.X11-unix/X' + self.getDisplaynumber());
> }, 100);
> attempts++;
> } else {
> self.logger.error(id + 'Connecting to the Xserver failed.');
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.ERR_CON_XSERVER'));
> }
> });
> // catch state related events and react to changes of the playing status
> socket.emit('getState', '');
> socket.on('pushState', function (state) {
> if (state.status === 'play' && !lastStateIsPlaying) {
> if (self.config.get('afterPlay')) {
> exec('/usr/bin/gpio write 26 1');
> exec('/usr/bin/xset -display :' + displayNumber + ' s reset dpms force on', { uid: 1000, gid: 1000 }, function (error, stdout, stderr) {
> if (error !== null) {
> self.logger.error(id + 'Error waking up the screen: ' + error);
> }
> });
> self.setScreenTimeout(0, true);
> }
> lastStateIsPlaying = true;
> } else if (state.status !== 'play' && lastStateIsPlaying) {
> self.setScreenTimeout(self.config.get('timeout'), true);
> lastStateIsPlaying = false;
> }
> });
> defer.resolve();
> })
> .fail(function () {
> defer.reject(new Error());
> });
> return defer.promise;
> };
> 
> TouchDisplay.prototype.onStop = function () {
> const self = this;
> const defer = libQ.defer();
> 
> unixDomSocket.removeAllListeners();
> unixDomSocket.destroy();
> socket.off('pushState');
> if (device === 'pi') {
> self.setOrientation('0');
> if (self.config.get('controlGpuMem')) {
> self.modBootConfig('^#GPU_MEM', 'gpu_mem')
> .then(self.modBootConfig.bind(self, configTxtGpuMemBanner + 'gpu_mem=.*', ''))
> .fail(function () {
> self.logger.info(id + 'Restoring gpu_mem settings in /boot/config.txt failed. The touch display plugin\'s gpu_mem settings have been preserved.');
> });
> }
> clearTimeout(autoBrTimer);
> clearTimeout(toggleBrTimer);
> if (rpiBacklight) {
> self.setBrightness(maxBrightness);
> }
> }
> self.systemctl('stop volumio-kiosk.service')
> .fin(function () {
> defer.resolve();
> });
> return defer.promise;
> };
> 
> // Configuration Methods -----------------------------------------------------------------------------
> 
> TouchDisplay.prototype.getUIConfig = function () {
> const self = this;
> const defer = libQ.defer();
> const langCode = self.commandRouter.sharedVars.get('language_code');
> 
> self.commandRouter.i18nJson(path.join(__dirname, 'i18n', 'strings_' + langCode + '.json'),
> path.join(__dirname, 'i18n', 'strings_en.json'),
> path.join(__dirname, 'UIConfig.json'))
> .then(function (uiconf) {
> uiconf.sections[0].hidden = false;
> uiconf.sections[0].content[0].value = self.config.get('timeout');
> uiconf.sections[0].content[0].attributes = [
> {
> placeholder: 120,
> maxlength: Number.MAX_SAFE_INTEGER.toString().length,
> min: 0,
> max: Number.MAX_SAFE_INTEGER
> }
> ];
> uiconf.sections[0].content[1].value = self.config.get('afterPlay');
> if (rpiBacklight) {
> uiconf.sections[1].hidden = false;
> try {
> if (fs.existsSync(als)) {
> uiconf.sections[1].content[0].hidden = false;
> uiconf.sections[1].content[0].value = self.config.get('autoMode');
> uiconf.sections[1].content[1].value = self.config.get('minBr');
> uiconf.sections[1].content[1].attributes = [
> {
> placeholder: 15,
> maxlength: maxBrightness.toString().length,
> min: 0,
> max: maxBrightness
> }
> ];
> uiconf.sections[1].content[2].value = self.config.get('maxBr');
> uiconf.sections[1].content[2].attributes = [
> {
> placeholder: maxBrightness,
> maxlength: maxBrightness.toString().length,
> min: 0,
> max: maxBrightness
> }
> ];
> uiconf.sections[1].content[4].value = self.config.get('brightnessCurve');
> uiconf.sections[1].content[5].value = self.config.get('midBr');
> uiconf.sections[1].content[5].attributes = [
> {
> placeholder: maxBrightness,
> maxlength: maxBrightness.toString().length,
> min: 0,
> max: maxBrightness
> }
> ];
> }
> } catch (e) {
> self.logger.error(id + 'Error checking the existence of "/etc/als": ' + e);
> }
> uiconf.sections[1].content[7].value = self.config.get('manualBr');
> uiconf.sections[1].content[7].attributes = [
> {
> placeholder: maxBrightness,
> maxlength: maxBrightness.toString().length,
> min: 0,
> max: maxBrightness
> }
> ];
> uiconf.sections[1].content[8].value = self.config.get('br1StartTime');
> uiconf.sections[1].content[8].attributes = [
> {
> placeholder: 'hh:mm',
> maxlength: 5
> }
> ];
> uiconf.sections[1].content[9].value = self.config.get('manualBr2');
> uiconf.sections[1].content[9].attributes = [
> {
> placeholder: maxBrightness,
> maxlength: maxBrightness.toString().length,
> min: 0,
> max: maxBrightness
> }
> ];
> uiconf.sections[1].content[10].value = self.config.get('br2StartTime');
> uiconf.sections[1].content[10].attributes = [
> {
> placeholder: 'hh:mm',
> maxlength: 5
> }
> ];
> }
> if (device === 'pi') {
> uiconf.sections[2].hidden = false;
> uiconf.sections[2].content[0].value.value = self.config.get('angle');
> uiconf.sections[2].content[0].value.label = self.commandRouter.getI18nString('TOUCH_DISPLAY.' + self.config.get('angle'));
> uiconf.sections[3].hidden = false;
> uiconf.sections[3].content[0].value = self.config.get('controlGpuMem');
> uiconf.sections[3].content[1].value = self.config.get('gpuMem');
> uiconf.sections[3].content[1].attributes = [
> {
> placeholder: 32,
> maxlength: 3,
> min: 32,
> max: 128
> }
> ];
> }
> uiconf.sections[4].hidden = false;
> uiconf.sections[4].content[0].value = self.config.get('showPointer');
> uiconf.sections[5].hidden = false;
> uiconf.sections[5].content[0].value = self.config.get('scale');
> uiconf.sections[5].content[0].attributes = [
> {
> placeholder: 100,
> maxlength: 3,
> min: 10,
> max: 200
> }
> ];
> defer.resolve(uiconf);
> })
> .fail(function (e) {
> self.logger.error(id + 'Could not fetch UI configuration: ' + e);
> defer.reject(new Error());
> });
> return defer.promise;
> };
> 
> TouchDisplay.prototype.updateUIConfig = function () {
> const self = this;
> 
> self.commandRouter.getUIConfigOnPlugin('user_interface', 'touch_display', {})
> .then(function (uiconf) {
> self.commandRouter.broadcastMessage('pushUiConfig', uiconf);
> });
> self.commandRouter.broadcastMessage('pushUiConfig');
> uiNeedsUpdate = false;
> };
> 
> TouchDisplay.prototype.getConfigurationFiles = function () {
> return ['config.json'];
> };
> 
> TouchDisplay.prototype.getI18nFile = function (langCode) {
> const self = this;
> const langFile = 'strings_' + langCode + '.json';
> 
> try {
> const i18nFiles = fs.readdirSync(path.join(__dirname, 'i18n'));
> // check for i18n file fitting the system language
> if (i18nFiles.some(function (i18nFile) { return i18nFile === langFile; })) {
> return path.join(__dirname, 'i18n', langFile);
> }
> throw new Error('i18n file complementing the system language not found.');
> } catch (e) {
> self.logger.error(id + 'Fetching language file: ' + e);
> // return default i18n file
> return path.join(__dirname, 'i18n', 'strings_en.json');
> }
> };
> 
> TouchDisplay.prototype.saveScreensaverConf = function (confData) {
> const self = this;
> let noChanges = true;
> 
> if (Number.isNaN(parseInt(confData.timeout, 10)) || !isFinite(confData.timeout)) {
> uiNeedsUpdate = true;
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.TIMEOUT') + self.commandRouter.getI18nString('TOUCH_DISPLAY.NAN'));
> } else {
> confData.timeout = self.checkLimits('timeout', confData.timeout, 0, Number.MAX_SAFE_INTEGER);
> if (self.config.get('timeout') !== confData.timeout || self.config.get('afterPlay') !== confData.afterPlay) {
> self.config.set('timeout', confData.timeout);
> self.config.set('afterPlay', confData.afterPlay);
> if ((confData.afterPlay && self.commandRouter.volumioGetState().status === 'play') || confData.timeout === 0) {
> exec('/usr/bin/gpio write 26 1');
> exec('/usr/bin/xset -display :' + displayNumber + ' s reset dpms force on', { uid: 1000, gid: 1000 }, function (error, stdout, stderr) {
> if (error !== null) {
> self.logger.error(id + 'Error waking up the screen: ' + error);
> }
> });
> self.setScreenTimeout(0, true);
> } else {
> self.setScreenTimeout(confData.timeout, true);
> }
> noChanges = false;
> }
> }
> if (uiNeedsUpdate) {
> self.updateUIConfig();
> } else if (noChanges) {
> self.commandRouter.pushToastMessage('info', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.NO_CHANGES'));
> } else {
> self.commandRouter.pushToastMessage('success', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('COMMON.SETTINGS_SAVED_SUCCESSFULLY'));
> }
> };
> 
> TouchDisplay.prototype.saveBrightnessConf = function (confData) {
> const self = this;
> const responseData = {
> title: self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'),
> message: self.commandRouter.getI18nString('TOUCH_DISPLAY.TEST_MSG'),
> size: 'lg',
> buttons: [
> {
> name: self.commandRouter.getI18nString('TOUCH_DISPLAY.TESTBRIGHTNESS'),
> class: 'btn btn-default',
> emit: 'callMethod',
> payload: { endpoint: 'user_interface/touch_display', method: 'testBrightness', data: Object.assign({}, confData) }
> },
> {
> name: self.commandRouter.getI18nString('COMMON.CONTINUE'),
> class: 'btn btn-info',
> emit: 'callMethod',
> payload: { endpoint: 'user_interface/touch_display', method: 'saveBrightnessConf', data: (function () { const data = Object.assign({}, confData); data.modalResult = true; return data; })() }
> },
> {
> name: self.commandRouter.getI18nString('COMMON.CANCEL'),
> class: 'btn btn-info',
> emit: 'callMethod',
> payload: { endpoint: 'user_interface/touch_display', method: 'saveBrightnessConf', data: (function () { const data = Object.assign({}, confData); data.modalResult = false; return data; })() }
> }
> ]
> };
> const timeValidator = /^([0-9]|0[0-9]|1[0-9]|2[0-3]):([0-5][0-9])$/;
> let noChanges = true;
> 
> self.commandRouter.broadcastMessage('closeAllModals', '');
> if (self.config.get('autoMode') !== confData.autoMode) {
> noChanges = false;
> }
> if (confData.autoMode) {
> if (Number.isNaN(parseInt(confData.minBr, 10)) || !isFinite(confData.minBr)) {
> confData.minBr = self.config.get('minBr');
> uiNeedsUpdate = true;
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.MINBR') + self.commandRouter.getI18nString('TOUCH_DISPLAY.NAN'));
> }
> confData.minBr = self.checkLimits('minBr', confData.minBr, 0, maxBrightness);
> if (confData.modalResult === undefined && confData.minBr < 15 && confData.minBr < self.config.get('minBr')) {
> responseData.message = responseData.message.replace('${}', confData.minBr);
> responseData.buttons[2].payload.data.minBr = self.config.get('minBr');
> self.commandRouter.broadcastMessage('openModal', responseData);
> return;
> } else {
> if (confData.modalResult === false) {
> uiNeedsUpdate = true;
> } else {
> if (self.config.get('minBr') !== confData.minBr) {
> self.config.set('minBr', confData.minBr);
> noChanges = false;
> }
> }
> if (Number.isNaN(parseInt(confData.maxBr, 10)) || !isFinite(confData.maxBr)) {
> confData.maxBr = self.config.get('maxBr');
> uiNeedsUpdate = true;
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.MAXBR') + self.commandRouter.getI18nString('TOUCH_DISPLAY.NAN'));
> }
> confData.maxBr = self.checkLimits('maxBr', confData.maxBr, confData.minBr, maxBrightness);
> if (self.config.get('maxBr') !== confData.maxBr) {
> self.config.set('maxBr', confData.maxBr);
> noChanges = false;
> }
> if (confData.brightnessCurve) {
> if (Number.isNaN(parseInt(confData.midBr, 10)) || !isFinite(confData.midBr)) {
> confData.midBr = self.config.get('midBr');
> uiNeedsUpdate = true;
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.MIDBR') + self.commandRouter.getI18nString('TOUCH_DISPLAY.NAN'));
> }
> confData.midBr = self.checkLimits('midBr', confData.midBr, confData.minBr, confData.maxBr);
> if (self.config.get('midBr') !== confData.midBr) {
> self.config.set('midBr', confData.midBr);
> noChanges = false;
> }
> }
> // minAls and maxAls can only be the same value if the ALS range has not been determined before
> if (self.config.get('maxAls') <= self.config.get('minAls')) {
> if (confData.brightnessCurve) {
> self.getAlsValue({ confData: confData, action: 'minmaxmid' });
> } else {
> self.getAlsValue({ confData: confData, action: 'minmax' });
> }
> } else if (confData.brightnessCurve && (!self.config.has('midAls') || self.config.get('midAls') <= self.config.get('minAls') || self.config.get('midAls') >= self.config.get('maxAls'))) {
> self.getAlsValue({ confData: confData, action: 'mid' });
> } else {
> if (self.config.get('brightnessCurve') !== confData.brightnessCurve) {
> self.config.set('brightnessCurve', confData.brightnessCurve);
> noChanges = false;
> }
> self.config.set('autoMode', confData.autoMode);
> clearTimeout(toggleBrTimer);
> clearTimeout(autoBrTimer);
> autoBrTimeoutCleared = true;
> self.autoBrightness();
> }
> }
> } else {
> self.config.set('autoMode', confData.autoMode);
> clearTimeout(autoBrTimer);
> autoBrTimeoutCleared = true;
> if (Number.isNaN(parseInt(confData.manualBr, 10)) || !isFinite(confData.manualBr)) {
> confData.manualBr = self.config.get('manualBr');
> uiNeedsUpdate = true;
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.MANUALBR') + self.commandRouter.getI18nString('TOUCH_DISPLAY.NAN'));
> }
> confData.manualBr = self.checkLimits('manualBr', confData.manualBr, 0, maxBrightness);
> if (Number.isNaN(parseInt(confData.manualBr2, 10)) || !isFinite(confData.manualBr2)) {
> confData.manualBr2 = self.config.get('manualBr2');
> uiNeedsUpdate = true;
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.MANUALBR2') + self.commandRouter.getI18nString('TOUCH_DISPLAY.NAN'));
> }
> confData.manualBr2 = self.checkLimits('manualBr2', confData.manualBr2, 0, maxBrightness);
> if (confData.modalResult === undefined && ((confData.manualBr < 15 && confData.manualBr < self.config.get('manualBr') && confData.manualBr < self.config.get('manualBr2')) || (confData.manualBr2 < 15 && confData.manualBr2 < self.config.get('manualBr2') && confData.manualBr2 < self.config.get('manualBr')))) {
> if (confData.manualBr <= confData.manualBr2) {
> responseData.message = responseData.message.replace('${}', confData.manualBr);
> responseData.buttons[2].payload.data.manualBr = self.config.get('manualBr');
> if (confData.manualBr === confData.manualBr2) {
> responseData.buttons[2].payload.data.manualBr2 = self.config.get('manualBr2');
> }
> } else {
> responseData.message = responseData.message.replace('${}', confData.manualBr2);
> responseData.buttons[2].payload.data.manualBr2 = self.config.get('manualBr2');
> }
> self.commandRouter.broadcastMessage('openModal', responseData);
> return;
> } else {
> if (confData.modalResult === false) {
> uiNeedsUpdate = true;
> if (confData.manualBr !== confData.manualBr2) {
> if (confData.manualBr < 15 && confData.manualBr < self.config.get('manualBr')) {
> responseData.message = responseData.message.replace('${}', confData.manualBr);
> responseData.buttons[2].payload.data.manualBr = self.config.get('manualBr');
> self.commandRouter.broadcastMessage('openModal', responseData);
> return;
> } else if (confData.manualBr2 < 15 && confData.manualBr2 < self.config.get('manualBr2')) {
> responseData.message = responseData.message.replace('${}', confData.manualBr2);
> responseData.buttons[2].payload.data.manualBr2 = self.config.get('manualBr2');
> self.commandRouter.broadcastMessage('openModal', responseData);
> return;
> }
> } else {
> self.config.set('manualBr', confData.manualBr);
> self.config.set('manualBr2', confData.manualBr2);
> noChanges = false;
> }
> } else {
> self.config.set('manualBr', confData.manualBr);
> self.config.set('manualBr2', confData.manualBr2);
> noChanges = false;
> }
> if (confData.br1StartTime.match(timeValidator) === null) {
> uiNeedsUpdate = true;
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.BR1STARTTIME') + self.commandRouter.getI18nString('TOUCH_DISPLAY.INVALID'));
> } else if (self.config.get('br1StartTime') !== confData.br1StartTime) {
> self.config.set('br1StartTime', confData.br1StartTime);
> noChanges = false;
> }
> if (confData.br2StartTime.match(timeValidator) === null) {
> uiNeedsUpdate = true;
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.BR2STARTTIME') + self.commandRouter.getI18nString('TOUCH_DISPLAY.INVALID'));
> } else if (self.config.get('br2StartTime') !== confData.br2StartTime) {
> self.config.set('br2StartTime', confData.br2StartTime);
> noChanges = false;
> }
> if (self.config.get('br2StartTime') !== self.config.get('br1StartTime') && self.config.get('manualBr') !== self.config.get('manualBr2')) {
> self.toggleBrightness();
> } else {
> self.setBrightness(confData.manualBr);
> clearTimeout(toggleBrTimer);
> }
> }
> }
> if (uiNeedsUpdate) {
> self.updateUIConfig();
> } else if (noChanges) {
> self.commandRouter.pushToastMessage('info', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.NO_CHANGES'));
> } else {
> self.commandRouter.pushToastMessage('success', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('COMMON.SETTINGS_SAVED_SUCCESSFULLY'));
> }
> };
> 
> TouchDisplay.prototype.saveOrientationConf = function (confData) {
> const self = this;
> const responseData = {
> title: self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'),
> message: self.commandRouter.getI18nString('TOUCH_DISPLAY.REBOOT_MSG'),
> size: 'lg',
> buttons: [
> {
> name: self.commandRouter.getI18nString('COMMON.RESTART'),
> class: 'btn btn-default',
> emit: 'reboot',
> payload: ''
> },
> {
> name: self.commandRouter.getI18nString('COMMON.CONTINUE'),
> class: 'btn btn-info',
> emit: 'closeModals',
> payload: ''
> }
> ]
> };
> 
> if (self.config.get('angle') !== confData.angle.value) {
> self.config.set('angle', confData.angle.value);
> self.setOrientation(confData.angle.value)
> .then(function () {
> self.commandRouter.broadcastMessage('openModal', responseData);
> });
> } else {
> self.commandRouter.pushToastMessage('info', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.NO_CHANGES'));
> }
> };
> 
> TouchDisplay.prototype.saveGpuMemConf = function (confData) {
> const self = this;
> const responseData = {
> title: self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'),
> message: self.commandRouter.getI18nString('TOUCH_DISPLAY.REBOOT_MSG'),
> size: 'lg',
> buttons: [
> {
> name: self.commandRouter.getI18nString('COMMON.RESTART'),
> class: 'btn btn-default',
> emit: 'reboot',
> payload: ''
> },
> {
> name: self.commandRouter.getI18nString('COMMON.CONTINUE'),
> class: 'btn btn-info',
> emit: 'closeModals',
> payload: ''
> }
> ]
> };
> 
> if (confData.controlGpuMem) {
> if (Number.isNaN(parseInt(confData.gpuMem, 10)) || !isFinite(confData.gpuMem)) {
> confData.gpuMem = self.config.get('gpuMem');
> uiNeedsUpdate = true;
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.GPUMEM') + self.commandRouter.getI18nString('TOUCH_DISPLAY.NAN'));
> } else {
> confData.gpuMem = self.checkLimits('gpuMem', confData.gpuMem, 32, 128);
> }
> if (self.config.get('gpuMem') !== confData.gpuMem || self.config.get('controlGpuMem') !== confData.controlGpuMem) {
> self.modBootConfig(configTxtGpuMemBanner + 'gpu_mem=.*', configTxtGpuMemBanner + 'gpu_mem=' + confData.gpuMem)
> .then(function () {
> self.config.set('gpuMem', confData.gpuMem);
> if (self.config.get('controlGpuMem') !== confData.controlGpuMem) {
> self.modBootConfig('^gpu_mem', '#GPU_MEM');
> }
> })
> .then(function () {
> self.config.set('controlGpuMem', confData.controlGpuMem);
> self.commandRouter.broadcastMessage('openModal', responseData);
> })
> .fail(function () {
> uiNeedsUpdate = true;
> self.logger.error(id + 'Changing gpu_mem settings failed.');
> })
> .done(function () {
> if (uiNeedsUpdate) {
> self.updateUIConfig();
> }
> });
> } else if (uiNeedsUpdate) {
> self.updateUIConfig();
> } else {
> self.commandRouter.pushToastMessage('info', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.NO_CHANGES'));
> }
> } else if (self.config.get('controlGpuMem') !== confData.controlGpuMem) {
> self.modBootConfig('^#GPU_MEM', 'gpu_mem')
> .then(function () {
> self.modBootConfig(configTxtGpuMemBanner + 'gpu_mem=.*', '');
> })
> .then(function () {
> self.config.set('controlGpuMem', confData.controlGpuMem);
> self.commandRouter.broadcastMessage('openModal', responseData);
> })
> .fail(function () {
> self.updateUIConfig();
> self.logger.error(id + 'Uncommenting gpu_mem settings in /boot/config.txt failed.');
> });
> } else {
> self.commandRouter.pushToastMessage('info', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.NO_CHANGES'));
> }
> };
> 
> TouchDisplay.prototype.savePointerConf = function (confData) {
> const self = this;
> const defer = libQ.defer();
> const execStartLine = 'ExecStart=\\/usr\\/bin\\/startx \\/etc\\/X11\\/Xsession \\/opt\\/volumiokiosk.sh';
> const pointerOpt = confData.showPointer ? "'" : " -- -nocursor'";
> 
> if (self.config.get('showPointer') !== confData.showPointer) {
> fs.stat('/tmp/.X11-unix/X' + displayNumber, function (err, stats) {
> if (err !== null || !stats.isSocket()) {
> self.updateUIConfig();
> self.logger.error(id + 'Pointer config cannot be applied: ' + err); // this can happen if the user applies a pointer setting which leads to a restart of the Xserver and then fastly (before the Xserver has completed its start) tries to apply a new pointer config
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.ERR_SET_POINTER') + err);
> defer.reject(err);
> } else {
> self.config.set('showPointer', confData.showPointer);
> exec("/bin/echo volumio | /usr/bin/sudo -S /bin/sed -i -e '/" + execStartLine + '/c\\' + execStartLine + pointerOpt + ' /lib/systemd/system/volumio-kiosk.service', { uid: 1000, gid: 1000 }, function (error, stdout, stderr) {
> if (error !== null) {
> self.logger.error(id + 'Error modifying /lib/systemd/system/volumio-kiosk.service: ' + error);
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.ERR_MOD') + '/lib/systemd/system/volumio-kiosk.service: ' + error);
> defer.reject(error);
> } else {
> self.systemctl('daemon-reload')
> .then(self.onStop.bind(self))
> .then(self.onStart.bind(self))
> .then(function () {
> self.commandRouter.pushToastMessage('success', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('COMMON.SETTINGS_SAVED_SUCCESSFULLY'));
> defer.resolve();
> })
> .fail(function () {
> defer.reject(new Error());
> });
> }
> });
> }
> });
> } else {
> self.commandRouter.pushToastMessage('info', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.NO_CHANGES'));
> }
> return defer.promise;
> };
> 
> TouchDisplay.prototype.saveScaleConf = function (confData) {
> const self = this;
> const defer = libQ.defer();
> 
> if (Number.isNaN(parseInt(confData.scale, 10)) || !isFinite(confData.scale)) {
> uiNeedsUpdate = true;
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.SCALE') + self.commandRouter.getI18nString('TOUCH_DISPLAY.NAN'));
> } else {
> confData.scale = self.checkLimits('scale', confData.scale, 10, 200);
> if (self.config.get('scale') !== confData.scale) {
> fs.stat('/tmp/.X11-unix/X' + displayNumber, function (err, stats) {
> if (err !== null || !stats.isSocket()) {
> self.updateUIConfig();
> self.logger.error(id + 'Scale config cannot be applied: ' + err); // this can happen if the user applies a scale setting which leads to a restart of the Xserver and then fastly (before the Xserver has completed its start) tries to apply a new scale config
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.ERR_SET_SCALE') + err);
> defer.reject(err);
> } else {
> self.config.set('scale', confData.scale);
> exec('/usr/bin/chromium-browser -version', { uid: 1000, gid: 1000 }, function (error, stdout, stderr) {
> if (error !== null) {
> self.logger.error(id + 'Error requesting browser version.');
> } else {
> if (confData.scale < 100 && stdout.match(/\d*\./).toString().slice(0, -1) < 57) {
> self.commandRouter.pushToastMessage('warning', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.SCALE_WARN'));
> }
> exec("/bin/echo volumio | /usr/bin/sudo -S /bin/sed -i -e 's/factor=.* /factor=" + confData.scale / 100 + " /' /opt/volumiokiosk.sh", { uid: 1000, gid: 1000 }, function (error, stdout, stderr) {
> if (error !== null) {
> self.logger.error(id + 'Error modifying /opt/volumiokiosk.sh: ' + error);
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.ERR_MOD') + '/opt/volumiokiosk.sh: ' + error);
> defer.reject(error);
> } else {
> self.systemctl('daemon-reload')
> .then(self.onStop.bind(self))
> .then(self.onStart.bind(self))
> .then(function () {
> self.commandRouter.pushToastMessage('success', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('COMMON.SETTINGS_SAVED_SUCCESSFULLY'));
> defer.resolve();
> })
> .fail(function () {
> defer.reject(new Error());
> });
> }
> });
> }
> });
> }
> });
> } else {
> self.commandRouter.pushToastMessage('info', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.NO_CHANGES'));
> }
> }
> if (uiNeedsUpdate) {
> self.updateUIConfig();
> }
> return defer.promise;
> };
> 
> // Plugin Methods ------------------------------------------------------------------------------------
> 
> TouchDisplay.prototype.checkLimits = function (item, value, min, max) {
> const self = this;
> 
> if (value < min) {
> if (item !== '') {
> uiNeedsUpdate = true;
> self.commandRouter.pushToastMessage('info', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.' + item.toUpperCase()) + ': ' + self.commandRouter.getI18nString('TOUCH_DISPLAY.INFO_MIN'));
> }
> return min;
> }
> if (value > max) {
> if (item !== '') {
> uiNeedsUpdate = true;
> self.commandRouter.pushToastMessage('info', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.' + item.toUpperCase()) + ': ' + self.commandRouter.getI18nString('TOUCH_DISPLAY.INFO_MAX'));
> }
> return max;
> }
> return parseInt(value, 10);
> };
> 
> TouchDisplay.prototype.setScreenTimeout = function (timeout, showErr) {
> const self = this;
> const defer = libQ.defer();
> 
> fs.stat('/tmp/.X11-unix/X' + displayNumber, function (err, stats) {
> if (err !== null || !stats.isSocket()) {
> self.logger.error(id + 'Error setting screensaver timeout: ' + err);
> if (showErr) {
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.ERR_SET_TIMEOUT') + err);
> }
> defer.reject(err);
> } else {
> exec('/usr/bin/gpio write 26 0');
> exec('/usr/bin/xset -display :' + displayNumber + ' s off +dpms dpms 0 0 ' + timeout, { uid: 1000, gid: 1000 }, function (error, stdout, stderr) {
> if (error !== null) {
> self.logger.error(id + 'Error setting screensaver timeout: ' + error);
> if (showErr) {
> self.commandRouter.pushToastMessage('error', self.commandRouter.getI18nString('TOUCH_DISPLAY.PLUGIN_NAME'), self.commandRouter.getI18nString('TOUCH_DISPLAY.ERR_SET_TIMEOUT') + error);
> }
> defer.reject(error);
> } else {
> self.logger.info(id + 'Setting screensaver timeout to ' + timeout + ' seconds.');
> defer.resolve();
> }
> });
> }
> });
> return defer.promise;
> };
> 
> ........
> code to long, cutted here
> .........
> 
> ```

A great alternative could be to authorise users to run custom scripts from events in settings of “Touch Display” plugin 😛

---

_[View the full topic](https://community.volumio.com/t/plugin-touch-display/10647)._
