Back/forward buttons: adjustable opacity; Upgrade refreshes the Web UI copy

New historyButtonsOpacity setting (10-100 percent, default 50) in the
terminal menu (Display & Interaction) and the Web UI. Upgrade now also
copies the Web UI app to /opt/kiosk-webui and restarts it, so new Web UI
settings appear after an upgrade (it previously kept its old copy).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZQveYdjfcrEsyazq32
This commit is contained in:
Claude
2026-11-06 18:21:50 +00:00
parent e7cf15a802
commit fa0d602505
11 changed files with 70 additions and 13 deletions
+1 -1
View File
@@ -249,7 +249,7 @@ The kiosk machine still needs a working internet connection (ethernet, or WiFi c
- **1-finger swipe** (dual mode) - Navigate within page (arrow keys) - **1-finger swipe** (dual mode) - Navigate within page (arrow keys)
- **On-screen keyboard** - Auto-shows on text fields or click keyboard icon - **On-screen keyboard** - Auto-shows on text fields or click keyboard icon
- **Navigation menu** - Click key icon (top-left) to access site list and gesture cheat sheet - **Navigation menu** - Click key icon (top-left) to access site list and gesture cheat sheet
- **Back/forward buttons** (optional, off by default) - two half-transparent ◀ ▶ buttons at the top middle of the screen, for kiosks with no keyboard or mouse side buttons. They appear on a tap, click or mouse movement and fade after a few seconds. Turn on in Core Settings → Display & Interaction, or the Web UI. - **Back/forward buttons** (optional, off by default) - two half-transparent ◀ ▶ buttons at the top middle of the screen, for kiosks with no keyboard or mouse side buttons. They appear on a tap, click or mouse movement and fade after a few seconds. Turn on in Core Settings → Display & Interaction, or the Web UI, where you can also set how solid they look (opacity 10–100%, default 50).
### On-Screen Keyboard ### On-Screen Keyboard
- **HTML-based keyboard** with full QWERTY layout - **HTML-based keyboard** with full QWERTY layout
+7 -2
View File
@@ -61,6 +61,7 @@ let enablePauseButton=true;
let enableKeyboardButton=true; let enableKeyboardButton=true;
let enableNavButton=true; let enableNavButton=true;
let enableHistoryButtons=false; let enableHistoryButtons=false;
let historyButtonsOpacity=50;
let enablePasswordProtection=false; let enablePasswordProtection=false;
let lockoutPassword=""; let lockoutPassword="";
let lockoutTimeout=0; let lockoutTimeout=0;
@@ -104,6 +105,10 @@ function loadConfig(){
enableKeyboardButton=(config.enableKeyboardButton!==false); enableKeyboardButton=(config.enableKeyboardButton!==false);
enableNavButton=(config.enableNavButton!==false); enableNavButton=(config.enableNavButton!==false);
enableHistoryButtons=(config.enableHistoryButtons===true); enableHistoryButtons=(config.enableHistoryButtons===true);
{
const pct=Number(config.historyButtonsOpacity);
historyButtonsOpacity=(Number.isInteger(pct)&&pct>=10&&pct<=100)?pct:50;
}
enablePasswordProtection=(config.enablePasswordProtection===true); enablePasswordProtection=(config.enablePasswordProtection===true);
lockoutPassword=config.lockoutPassword||""; lockoutPassword=config.lockoutPassword||"";
lockoutTimeout=(config.lockoutTimeout||0)*60000; // Convert minutes to ms lockoutTimeout=(config.lockoutTimeout||0)*60000; // Convert minutes to ms
@@ -124,7 +129,7 @@ function loadConfig(){
console.log('[CONFIG] Navigation:',allowNavigation); console.log('[CONFIG] Navigation:',allowNavigation);
console.log('[CONFIG] Pause button:',enablePauseButton); console.log('[CONFIG] Pause button:',enablePauseButton);
console.log('[CONFIG] Keyboard button:',enableKeyboardButton); console.log('[CONFIG] Keyboard button:',enableKeyboardButton);
console.log('[CONFIG] Back/forward buttons:',enableHistoryButtons); console.log('[CONFIG] Back/forward buttons:',enableHistoryButtons,'opacity',historyButtonsOpacity+'%');
console.log('[CONFIG] Password protection:',enablePasswordProtection); console.log('[CONFIG] Password protection:',enablePasswordProtection);
console.log('[CONFIG] Lockout timeout:',lockoutTimeout/60000,'minutes'); console.log('[CONFIG] Lockout timeout:',lockoutTimeout/60000,'minutes');
if(lockoutAtTime)console.log('[CONFIG] Lock at time:',lockoutAtTime); if(lockoutAtTime)console.log('[CONFIG] Lock at time:',lockoutAtTime);
@@ -1640,7 +1645,7 @@ async function createWindow(){
console.log('[MAIN] Page loaded - sending keyboard-button-enabled: '+enableKeyboardButton); console.log('[MAIN] Page loaded - sending keyboard-button-enabled: '+enableKeyboardButton);
view.webContents.send('nav-button-enabled',enableNavButton); view.webContents.send('nav-button-enabled',enableNavButton);
view.webContents.send('history-buttons-enabled',enableHistoryButtons); view.webContents.send('history-buttons-config',{enabled:enableHistoryButtons,opacity:historyButtonsOpacity});
console.log('[MAIN] Page loaded - sending nav-button-enabled: '+enableNavButton); console.log('[MAIN] Page loaded - sending nav-button-enabled: '+enableNavButton);
console.log('[MAIN] Page loaded - resending pause-button-visibility: '+shouldShow+' for '+t.url); console.log('[MAIN] Page loaded - resending pause-button-visibility: '+shouldShow+' for '+t.url);
+13 -8
View File
@@ -120,11 +120,16 @@ ipcRenderer.on('keyboard-button-enabled',(event,enabled)=>{
let historyButtonsEnabled=false; let historyButtonsEnabled=false;
let historyBar=null; let historyBar=null;
let historyBarHideTimer=null; let historyBarHideTimer=null;
ipcRenderer.on('history-buttons-enabled',(event,enabled)=>{ let historyOpacity=0.5; // 0.1 - 1.0, from the "opacity" setting (percent)
historyButtonsEnabled=enabled===true; const historyHoverOpacity=()=>Math.min(1,historyOpacity+0.4);
console.log('[HISTORY-BTN] Back/forward buttons enabled: '+historyButtonsEnabled); ipcRenderer.on('history-buttons-config',(event,cfg)=>{
if(historyBar&&!historyButtonsEnabled){ historyButtonsEnabled=!!(cfg&&cfg.enabled===true);
historyBar.style.display='none'; const pct=Number(cfg&&cfg.opacity);
historyOpacity=(pct>=10&&pct<=100)?pct/100:0.5;
console.log('[HISTORY-BTN] Back/forward buttons enabled: '+historyButtonsEnabled+', opacity '+historyOpacity);
if(historyBar){
historyBar.style.opacity=String(historyOpacity);
if(!historyButtonsEnabled)historyBar.style.display='none';
} }
}); });
@@ -275,7 +280,7 @@ window.addEventListener('DOMContentLoaded',()=>{
historyBar.id='electron-history-bar'; historyBar.id='electron-history-bar';
historyBar.style.cssText=` historyBar.style.cssText=`
position:fixed;top:12px;left:50%;transform:translateX(-50%); position:fixed;top:12px;left:50%;transform:translateX(-50%);
display:none;gap:14px;z-index:999999;opacity:0.5; display:none;gap:14px;z-index:999999;opacity:${historyOpacity};
transition:opacity 0.15s;user-select:none; transition:opacity 0.15s;user-select:none;
`; `;
const makeButton=(label,direction,path)=>{ const makeButton=(label,direction,path)=>{
@@ -299,8 +304,8 @@ window.addEventListener('DOMContentLoaded',()=>{
makeButton('Back','back','M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'), makeButton('Back','back','M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'),
makeButton('Forward','forward','M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z') makeButton('Forward','forward','M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z')
); );
historyBar.addEventListener('mouseenter',()=>{historyBar.style.opacity='0.9';}); historyBar.addEventListener('mouseenter',()=>{historyBar.style.opacity=String(historyHoverOpacity());});
historyBar.addEventListener('mouseleave',()=>{historyBar.style.opacity='0.5';}); historyBar.addEventListener('mouseleave',()=>{historyBar.style.opacity=String(historyOpacity);});
document.body.appendChild(historyBar); document.body.appendChild(historyBar);
} }
+6 -1
View File
@@ -80,6 +80,7 @@ ENABLE_PAUSE_BUTTON="true"
ENABLE_KEYBOARD_BUTTON="true" ENABLE_KEYBOARD_BUTTON="true"
ENABLE_NAV_BUTTON="true" ENABLE_NAV_BUTTON="true"
ENABLE_HISTORY_BUTTONS="false" ENABLE_HISTORY_BUTTONS="false"
HISTORY_BUTTONS_OPACITY=50
ENABLE_PASSWORD_PROTECTION="false" ENABLE_PASSWORD_PROTECTION="false"
LOCKOUT_PASSWORD="" LOCKOUT_PASSWORD=""
LOCKOUT_TIMEOUT=0 LOCKOUT_TIMEOUT=0
@@ -217,6 +218,9 @@ load_existing_config() {
history_btn=$(sudo -u "$KIOSK_USER" jq -r '.enableHistoryButtons // false' "$CONFIG_PATH" 2>/dev/null) history_btn=$(sudo -u "$KIOSK_USER" jq -r '.enableHistoryButtons // false' "$CONFIG_PATH" 2>/dev/null)
[[ "$history_btn" == "true" ]] && ENABLE_HISTORY_BUTTONS="true" || ENABLE_HISTORY_BUTTONS="false" [[ "$history_btn" == "true" ]] && ENABLE_HISTORY_BUTTONS="true" || ENABLE_HISTORY_BUTTONS="false"
HISTORY_BUTTONS_OPACITY=$(sudo -u "$KIOSK_USER" jq -r '.historyButtonsOpacity // 50' "$CONFIG_PATH" 2>/dev/null || echo "50")
[[ "$HISTORY_BUTTONS_OPACITY" =~ ^[0-9]+$ ]] && (( HISTORY_BUTTONS_OPACITY >= 10 && HISTORY_BUTTONS_OPACITY <= 100 )) || HISTORY_BUTTONS_OPACITY=50
password_enabled=$(sudo -u "$KIOSK_USER" jq -r '.enablePasswordProtection // false' "$CONFIG_PATH" 2>/dev/null) password_enabled=$(sudo -u "$KIOSK_USER" jq -r '.enablePasswordProtection // false' "$CONFIG_PATH" 2>/dev/null)
[[ "$password_enabled" == "true" ]] && ENABLE_PASSWORD_PROTECTION="true" || ENABLE_PASSWORD_PROTECTION="false" [[ "$password_enabled" == "true" ]] && ENABLE_PASSWORD_PROTECTION="true" || ENABLE_PASSWORD_PROTECTION="false"
@@ -309,6 +313,7 @@ save_config() {
--argjson enableKeyboardButton "$keyboard_btn_json" \ --argjson enableKeyboardButton "$keyboard_btn_json" \
--argjson enableNavButton "$nav_btn_json" \ --argjson enableNavButton "$nav_btn_json" \
--argjson enableHistoryButtons "$history_btn_json" \ --argjson enableHistoryButtons "$history_btn_json" \
--argjson historyButtonsOpacity "${HISTORY_BUTTONS_OPACITY:-50}" \
--argjson enablePasswordProtection "$password_json" \ --argjson enablePasswordProtection "$password_json" \
--arg lockoutPassword "${LOCKOUT_PASSWORD:-}" \ --arg lockoutPassword "${LOCKOUT_PASSWORD:-}" \
--argjson lockoutTimeout "${LOCKOUT_TIMEOUT:-0}" \ --argjson lockoutTimeout "${LOCKOUT_TIMEOUT:-0}" \
@@ -322,7 +327,7 @@ save_config() {
--arg autheliaURL "${AUTHELIA_URL:-}" \ --arg autheliaURL "${AUTHELIA_URL:-}" \
--arg autheliaUsername "${AUTHELIA_USERNAME:-}" \ --arg autheliaUsername "${AUTHELIA_USERNAME:-}" \
--arg autheliaEncryptedPassword "${AUTHELIA_ENCRYPTED_PASSWORD:-}" \ --arg autheliaEncryptedPassword "${AUTHELIA_ENCRYPTED_PASSWORD:-}" \
'. + {autoswitch:$autoswitch,enableTouch:$enableTouch,dualSwipe:$dualSwipe,swipeMode:$swipeMode,allowNavigation:$allowNavigation,homeTabIndex:$homeTabIndex,inactivityTimeout:$inactivityTimeout,enablePauseButton:$enablePauseButton,enableKeyboardButton:$enableKeyboardButton,enableNavButton:$enableNavButton,enableHistoryButtons:$enableHistoryButtons,enablePasswordProtection:$enablePasswordProtection,lockoutPassword:$lockoutPassword,lockoutTimeout:$lockoutTimeout,lockoutAtTime:$lockoutAtTime,lockoutActiveStart:$lockoutActiveStart,lockoutActiveEnd:$lockoutActiveEnd,requirePasswordOnBoot:$requirePasswordOnBoot,lockoutCodeType:$lockoutCodeType,bootPassword:$bootPassword,bootCodeType:$bootCodeType,autheliaURL:$autheliaURL,autheliaUsername:$autheliaUsername,autheliaEncryptedPassword:$autheliaEncryptedPassword,tabs:[]}' > "$tmp" '. + {autoswitch:$autoswitch,enableTouch:$enableTouch,dualSwipe:$dualSwipe,swipeMode:$swipeMode,allowNavigation:$allowNavigation,homeTabIndex:$homeTabIndex,inactivityTimeout:$inactivityTimeout,enablePauseButton:$enablePauseButton,enableKeyboardButton:$enableKeyboardButton,enableNavButton:$enableNavButton,enableHistoryButtons:$enableHistoryButtons,historyButtonsOpacity:$historyButtonsOpacity,enablePasswordProtection:$enablePasswordProtection,lockoutPassword:$lockoutPassword,lockoutTimeout:$lockoutTimeout,lockoutAtTime:$lockoutAtTime,lockoutActiveStart:$lockoutActiveStart,lockoutActiveEnd:$lockoutActiveEnd,requirePasswordOnBoot:$requirePasswordOnBoot,lockoutCodeType:$lockoutCodeType,bootPassword:$bootPassword,bootCodeType:$bootCodeType,autheliaURL:$autheliaURL,autheliaUsername:$autheliaUsername,autheliaEncryptedPassword:$autheliaEncryptedPassword,tabs:[]}' > "$tmp"
if [[ ${#URLS[@]} -gt 0 ]]; then if [[ ${#URLS[@]} -gt 0 ]]; then
for idx in "${!URLS[@]}"; do for idx in "${!URLS[@]}"; do
+12
View File
@@ -121,6 +121,18 @@ action_upgrade() {
provision_configure_display provision_configure_display
provision_configure_firewall provision_configure_firewall
provision_configure_power_management provision_configure_power_management
# The Web UI runs from its own copy (/opt/kiosk-webui), not from
# this checkout - refresh it too, or new settings never show up
# there. Port, service and sudo rules are left as they are.
if [[ -f "$SYSTEMD_DIR/kiosk-webui.service" ]]; then
log_info "Refreshing the Web UI..."
if webui_install_app_files; then
sudo systemctl restart kiosk-webui || log_warning "Web UI copied but did not restart - run: sudo systemctl restart kiosk-webui"
else
log_warning "Couldn't refresh the Web UI - the Web UI keeps its old version"
fi
fi
log_success "Setup refreshed" log_success "Setup refreshed"
echo echo
+16 -1
View File
@@ -20,7 +20,7 @@ display_status() {
echo "Pause button: $(onoff "$ENABLE_PAUSE_BUTTON")" echo "Pause button: $(onoff "$ENABLE_PAUSE_BUTTON")"
echo "Keyboard button: $(onoff "$ENABLE_KEYBOARD_BUTTON")" echo "Keyboard button: $(onoff "$ENABLE_KEYBOARD_BUTTON")"
echo "Navigation button: $(onoff "$ENABLE_NAV_BUTTON")" echo "Navigation button: $(onoff "$ENABLE_NAV_BUTTON")"
echo "Back/forward btns: $(onoff "$ENABLE_HISTORY_BUTTONS")" echo "Back/forward btns: $(onoff "$ENABLE_HISTORY_BUTTONS") (opacity ${HISTORY_BUTTONS_OPACITY}%)"
} }
display_menu_builder() { display_menu_builder() {
@@ -31,6 +31,7 @@ display_menu_builder() {
"Toggle on-screen keyboard button (currently: $(onoff "$ENABLE_KEYBOARD_BUTTON"))" "Toggle on-screen keyboard button (currently: $(onoff "$ENABLE_KEYBOARD_BUTTON"))"
"Toggle navigation/help button (currently: $(onoff "$ENABLE_NAV_BUTTON"))" "Toggle navigation/help button (currently: $(onoff "$ENABLE_NAV_BUTTON"))"
"Toggle on-screen back/forward buttons (currently: $(onoff "$ENABLE_HISTORY_BUTTONS"))" "Toggle on-screen back/forward buttons (currently: $(onoff "$ENABLE_HISTORY_BUTTONS"))"
"Back/forward button opacity (currently: ${HISTORY_BUTTONS_OPACITY}%)"
) )
MENU_HANDLERS=( MENU_HANDLERS=(
action_set_touch_mode action_set_touch_mode
@@ -39,6 +40,7 @@ display_menu_builder() {
action_toggle_keyboard_button action_toggle_keyboard_button
action_toggle_nav_button action_toggle_nav_button
action_toggle_history_buttons action_toggle_history_buttons
action_set_history_opacity
) )
} }
@@ -149,3 +151,16 @@ action_toggle_history_buttons() {
fi fi
save_config save_config
} }
# How see-through the back/forward buttons are: 100 = solid, 10 = faint.
# They get a little more solid while the mouse is over them.
action_set_history_opacity() {
echo
echo "How solid the on-screen back/forward buttons look (10 = faint, 100 = solid)."
HISTORY_BUTTONS_OPACITY=$(ask_integer "Opacity percent" "$HISTORY_BUTTONS_OPACITY" 10 100)
save_config
log_success "Back/forward button opacity: ${HISTORY_BUTTONS_OPACITY}%"
if [[ "$ENABLE_HISTORY_BUTTONS" != "true" ]]; then
log_info "The buttons are off - turn them on with the entry above."
fi
}
+1
View File
@@ -41,6 +41,7 @@ const SCALAR_DEFAULTS = {
enableKeyboardButton: true, enableKeyboardButton: true,
enableNavButton: true, enableNavButton: true,
enableHistoryButtons: false, enableHistoryButtons: false,
historyButtonsOpacity: 50,
enablePasswordProtection: false, enablePasswordProtection: false,
lockoutTimeout: 0, lockoutTimeout: 0,
lockoutAtTime: '', lockoutAtTime: '',
+2
View File
@@ -174,6 +174,7 @@ displayForm.addEventListener('submit', async (e) => {
enableKeyboardButton: fd.get('enableKeyboardButton') === 'on', enableKeyboardButton: fd.get('enableKeyboardButton') === 'on',
enableNavButton: fd.get('enableNavButton') === 'on', enableNavButton: fd.get('enableNavButton') === 'on',
enableHistoryButtons: fd.get('enableHistoryButtons') === 'on', enableHistoryButtons: fd.get('enableHistoryButtons') === 'on',
historyButtonsOpacity: parseInt(fd.get('historyButtonsOpacity'), 10),
homeTabIndex: parseInt(fd.get('homeTabIndex'), 10), homeTabIndex: parseInt(fd.get('homeTabIndex'), 10),
inactivityTimeoutMinutes: parseInt(fd.get('inactivityTimeoutMinutes'), 10), inactivityTimeoutMinutes: parseInt(fd.get('inactivityTimeoutMinutes'), 10),
}; };
@@ -284,6 +285,7 @@ function populateAll(config) {
displayForm.elements.enableKeyboardButton.checked = !!config.enableKeyboardButton; displayForm.elements.enableKeyboardButton.checked = !!config.enableKeyboardButton;
displayForm.elements.enableNavButton.checked = !!config.enableNavButton; displayForm.elements.enableNavButton.checked = !!config.enableNavButton;
displayForm.elements.enableHistoryButtons.checked = !!config.enableHistoryButtons; displayForm.elements.enableHistoryButtons.checked = !!config.enableHistoryButtons;
displayForm.elements.historyButtonsOpacity.value = config.historyButtonsOpacity;
updateHomeTabOptions(config.tabs); updateHomeTabOptions(config.tabs);
homeTabSelect.value = String(config.homeTabIndex); homeTabSelect.value = String(config.homeTabIndex);
displayForm.elements.inactivityTimeoutMinutes.value = Math.round(config.inactivityTimeout / 60); displayForm.elements.inactivityTimeoutMinutes.value = Math.round(config.inactivityTimeout / 60);
+4
View File
@@ -105,6 +105,10 @@
<option value="-1">Disabled</option> <option value="-1">Disabled</option>
</select> </select>
</label> </label>
<label>
Back/forward button opacity (10 = faint, 100 = solid)
<input type="number" name="historyButtonsOpacity" min="10" max="100" step="5">
</label>
<label> <label>
Inactivity timeout (minutes) Inactivity timeout (minutes)
<input type="number" name="inactivityTimeoutMinutes" min="1" max="240" step="1"> <input type="number" name="inactivityTimeoutMinutes" min="1" max="240" step="1">
+5
View File
@@ -71,6 +71,11 @@ app.put('/api/config', (req, res) => {
if (body.enableKeyboardButton !== undefined) patch.enableKeyboardButton = !!body.enableKeyboardButton; if (body.enableKeyboardButton !== undefined) patch.enableKeyboardButton = !!body.enableKeyboardButton;
if (body.enableNavButton !== undefined) patch.enableNavButton = !!body.enableNavButton; if (body.enableNavButton !== undefined) patch.enableNavButton = !!body.enableNavButton;
if (body.enableHistoryButtons !== undefined) patch.enableHistoryButtons = !!body.enableHistoryButtons; if (body.enableHistoryButtons !== undefined) patch.enableHistoryButtons = !!body.enableHistoryButtons;
if (body.historyButtonsOpacity !== undefined) {
const pct = Number(body.historyButtonsOpacity);
if (!Number.isInteger(pct) || pct < 10 || pct > 100) return badRequest(res, 'Button opacity must be 10-100 percent');
patch.historyButtonsOpacity = pct;
}
let tabCount; let tabCount;
if (body.tabs !== undefined) { if (body.tabs !== undefined) {
+3
View File
@@ -81,6 +81,9 @@ check('enableHistoryButtons defaults off and round-trips', () => {
saveConfig({ enableHistoryButtons: true }); saveConfig({ enableHistoryButtons: true });
assert.strictEqual(JSON.parse(fs.readFileSync(process.env.CONFIG_PATH, 'utf8')).enableHistoryButtons, true); assert.strictEqual(JSON.parse(fs.readFileSync(process.env.CONFIG_PATH, 'utf8')).enableHistoryButtons, true);
assert.strictEqual(loadConfig().enableHistoryButtons, true); assert.strictEqual(loadConfig().enableHistoryButtons, true);
assert.strictEqual(loadConfig().historyButtonsOpacity, 50);
saveConfig({ historyButtonsOpacity: 80 });
assert.strictEqual(loadConfig().historyButtonsOpacity, 80);
}); });
check('saveConfig tabs: new password gets hashed, never stored/returned as plaintext', () => { check('saveConfig tabs: new password gets hashed, never stored/returned as plaintext', () => {