Hi, Thanks for this great plugin !
I have a cheap 3.5inch screen 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”. 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
'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