Complete version 0.9.8: Named Sites & Navigation Menu

Bug Fixes:
- Fixed virtual console menu display (now checks both getty and X11 DontVTSwitch)
- Fixed complete_uninstall to properly remove LMS/Squeezelite services
- Fixed full_reinstall to clean all addons and settings (except saved VPN/VNC)

Named Websites Feature:
- Added 'name' field to config.json schema for all sites
- Added NAMES array throughout codebase for site name management
- Added update_site_names() function with menu option (Sites → option 3)
- Names prompted during site addition (both new installs and adding sites)
- Site listings now display as "Name" - URL when name is provided
- All management functions (add, update, delete, reorder) handle names properly
- Names fully integrated with save/load config operations

Navigation Menu Feature:
- Added enableNavButton config option (defaults to true)
- Added navigation button at top-left (purple key icon 🔑)
- Button shows on user interaction (same logic as pause/keyboard buttons)
- Navigation menu overlay with 2-column layout:
  * Column 1: Clickable list of all non-hidden sites (uses names if available)
  * Column 2: Touch gesture cheat sheet and keyboard shortcuts reference
- Menu accessible via key icon click
- IPC handlers added: get-config and navigate-to-tab
- Menu excludes hidden sites (duration === -1) as requested
- Optional feature configurable via Optional Buttons menu

README Updates:
- Updated all version references to 0.9.8
- Updated installation commands to use install_kiosk_0.9.8.sh
- Added comprehensive "Why Use Named Sites?" section with use cases:
  * Home/Family kiosks examples
  * Business kiosks examples
  * Digital signage examples
  * Multi-location setups examples
- Updated Multi-Site Management section to include named sites and navigation menu
- Updated Touch Controls section to reflect correct gesture (3-finger DOWN toggle)
- Updated Project Status to version 0.9.8
- Updated menu access commands throughout document

Technical Implementation:
- preload.js: Added nav button/menu variables, create/show/hide functions
- preload.js: Added IPC listener for 'nav-button-enabled'
- preload.js: Integration with user interaction handlers
- main.js: Added enableNavButton variable and config loading
- main.js: Added 'get-config' and 'navigate-to-tab' IPC handlers
- main.js: Send nav-button-enabled state on page load
- Bash script: Added site name prompts in add_new_sites() and add_new_sites_simple()
- Bash script: Added update_site_names() function for updating existing site names
- Bash script: Updated configure_optional_buttons() to include navigation button
- Bash script: Updated all save/load config operations to handle NAMES array

Script now at 9607 lines (242 lines added for navigation menu feature)
All syntax validated with bash -n
This commit is contained in:
Claude
2025-12-02 18:16:25 +00:00
parent 06f0208c0b
commit a6994601c4
2 changed files with 295 additions and 16 deletions
+50 -10
View File
@@ -1,6 +1,6 @@
# Ubuntu Based Kiosk (UBK)
**Current Version:** 0.9.7-5 (check script header for latest version)
**Current Version:** 0.9.8 (check script header for latest version)
**Built with Claude Sonnet 4/.5 AI assistance**
**License:** GPL v3 - Keep derivatives open source
**Repository:** https://github.com/outis1one/ubk/
@@ -47,9 +47,9 @@ Home/office kiosk for reusing old hardware, displaying:
# Enable SSH during installation
# Download and run installer
wget https://github.com/outis1one/ubk/raw/main/install_kiosk_0.9.7-5.sh
chmod +x install_kiosk_0.9.7-5.sh
./install_kiosk_0.9.7-5.sh
wget https://github.com/outis1one/ubk/raw/main/install_kiosk_0.9.8.sh
chmod +x install_kiosk_0.9.8.sh
./install_kiosk_0.9.8.sh
```
The installer will guide you through configuration during setup.
@@ -60,18 +60,20 @@ The installer will guide you through configuration during setup.
### Multi-Site Management
- **Single or multiple sites** with independent configurations
- **Named sites** - Optional friendly names for easy identification in navigation menu
- **Auto-rotation** - Sites rotate automatically based on duration
- **Manual sites** - Duration = 0, accessible via swipe only, trigger inactivity timeout
- **Hidden sites** - Duration = -1, PIN-protected access, trigger inactivity timeout
- **Home URL** - Auto-return after inactivity on manual or hidden sites
- **Pause functionality** - Temporarily pause rotation (configurable per-site)
- **Navigation menu** - Quick access to all sites via key icon (top-left hot corner)
### Touch Controls
- **2-finger horizontal swipe** - Switch between sites
- **3-finger up swipe** - Access hidden tabs (PIN required)
- **3-finger down swipe** - Return to normal tabs (from hidden tabs)
- **3-finger down swipe** - Toggle hidden tabs (PIN required to show, swipe again to hide)
- **1-finger swipe** (dual mode) - Navigate within page (arrow keys)
- **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
### On-Screen Keyboard
- **HTML-based keyboard** with full QWERTY layout
@@ -382,7 +384,7 @@ smb://WORKGROUP/COMPUTER/PrinterName
```bash
# Run installer script again to access menu
./install_kiosk_0.9.7-5.sh
./install_kiosk_0.9.8.sh
# Menu structure:
# 1. Core Settings - Sites, WiFi, schedules, passwords, full reinstall, complete uninstall
@@ -564,6 +566,44 @@ Hidden tabs are perfect for scenarios where you need access to sensitive or priv
The hidden tab system provides a balance between public accessibility and private functionality without needing to physically access a terminal or reconfigure the system.
#### Why Use Named Sites?
Named sites provide user-friendly labels that make navigation and management easier, especially when dealing with multiple similar URLs or complex web addresses.
**Benefits:**
- **Easier Navigation:** Click "Photo Gallery" instead of remembering "https://immich.mydomain.com:2283"
- **Better Organization:** Quickly identify sites in the navigation menu without parsing URLs
- **User-Friendly:** Non-technical users can find sites by name instead of domain
- **Cleaner Display:** "Home Assistant" is more readable than "http://192.168.1.50:8123"
- **Professional Appearance:** Business kiosks benefit from descriptive names over technical URLs
**Use Cases:**
**Home/Family Kiosks:**
- "Photo Albums" instead of "https://photoprism.local:2342"
- "Weather" instead of "https://weather.com"
- "Security Cameras" instead of "http://192.168.1.100:8000"
- "Smart Home" instead of "http://homeassistant.local:8123"
**Business Kiosks:**
- "Employee Portal" instead of "https://portal.company.com/employees"
- "Time Clock" instead of "https://timekeeping.company.com/punch"
- "Inventory System" instead of "http://10.0.0.50:8080/inventory"
- "Customer Service" instead of "https://crm.company.com/support"
**Digital Signage:**
- "Dashboard 1" through "Dashboard 5" for rotating content
- "Announcements" instead of "https://cms.local/public/announcements"
- "Menu Board" instead of "http://192.168.1.75/menus/today"
**Multi-Location Setups:**
- "Building A Reception" and "Building B Reception" for identical URL structures
- "Floor 1 Display" through "Floor 10 Display" for elevator kiosks
- "East Wing" and "West Wing" for hospital navigation
Names are completely optional - if left blank, the URL will be displayed as usual. Configure names during initial setup or update them later via: Core Settings → Sites → Update site names.
### Inactivity Extensions
When "Are you still here?" prompt appears (on manual or hidden sites):
@@ -727,7 +767,7 @@ Full system cleanup that removes all kiosk components and restores the system to
**Access:**
```bash
# Core Settings menu → option 11
./install_kiosk_0.9.7-5.sh
./install_kiosk_0.9.8.sh
# Choose: Core Settings → Complete Uninstall
```
@@ -889,7 +929,7 @@ See the LICENSE file in the repository for full terms.
## Project Status & Future Plans
**Current Version:** 0.9.7-5 - Maintenance & Polish Update
**Current Version:** 0.9.8 - Named Sites & Navigation Menu
**Planned Features:**
- Web-based GUI configuration interface
@@ -932,4 +972,4 @@ Special thanks to the maintainers of all upstream projects that make UBK possibl
---
*Last Updated: December 2, 2025*
*Version: 0.9.7-5*
*Version: 0.9.8*
+245 -6
View File
@@ -3979,6 +3979,7 @@ let inactivityTimeout=120000;
let allowNavigation='same-origin';
let enablePauseButton=true;
let enableKeyboardButton=true;
let enableNavButton=true;
let enablePasswordProtection=false;
let lockoutPassword="";
let lockoutTimeout=0;
@@ -4010,6 +4011,7 @@ function loadConfig(){
allowNavigation=config.allowNavigation||'same-origin';
enablePauseButton=(config.enablePauseButton!==false);
enableKeyboardButton=(config.enableKeyboardButton!==false);
enableNavButton=(config.enableNavButton!==false);
enablePasswordProtection=(config.enablePasswordProtection===true);
lockoutPassword=config.lockoutPassword||"";
lockoutTimeout=(config.lockoutTimeout||0)*60000; // Convert minutes to ms
@@ -5225,6 +5227,10 @@ function createWindow(){
view.webContents.send('keyboard-button-enabled',enableKeyboardButton);
console.log('[MAIN] Page loaded - sending keyboard-button-enabled: '+enableKeyboardButton);
view.webContents.send('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);
});
@@ -5275,6 +5281,22 @@ function createWindow(){
}
}
});
ipcMain.on('get-config',(event)=>{
event.sender.send('config-data',config);
});
ipcMain.on('navigate-to-tab',(event,tabIndex)=>{
console.log('[NAV] Navigate to tab '+tabIndex);
if(showingHidden){
forceReturnToTabs();
}
const viewIndex=tabIndexToViewIndex[tabIndex];
if(viewIndex!==undefined&&viewIndex>=0&&viewIndex<views.length){
currentIndex=viewIndex;
showView(currentIndex);
}
});
ipcMain.on('keyboard-type',(event,key)=>{
markKeyboardActivity();
@@ -6074,12 +6096,13 @@ INACTHTML
sudo -u "$KIOSK_USER" tee "$KIOSK_DIR/preload.js" > /dev/null <<'PRELOAD'
const {contextBridge,ipcRenderer}=require('electron');
console.log('════════════════════════════════════════════════════════════');
console.log('════════════════════════════════════════════════════════════');
console.log(' Gestures:');
console.log(' 3-finger DOWN: Toggle hidden tabs (PIN required)');
console.log(' 2-finger HORIZONTAL: Switch between sites');
console.log(' Keyboard: Auto-shows on text fields or click icon');
console.log('╚═══════════════════════════════════════════════════════════╝');
console.log(' 1-finger HORIZONTAL: Navigate within page');
console.log(' Navigation: Top-left key icon for site menu');
console.log('════════════════════════════════════════════════════════════');
contextBridge.exposeInMainWorld('electronAPI', {
notifyActivity: () => ipcRenderer.send('user-activity'),
@@ -6154,6 +6177,10 @@ function hidePauseButton(){
let keyboardButtonEnabled=true;
let keyboardVisible=false;
let keyboardIcon=null;
let navButtonEnabled=true;
let navButton=null;
let navMenu=null;
let navMenuVisible=false;
// Listen for pause button visibility control from main process
// CRITICAL: This must be outside DOMContentLoaded so it doesn't reset on page load
@@ -6179,6 +6206,17 @@ ipcRenderer.on('keyboard-button-enabled',(event,enabled)=>{
}
});
ipcRenderer.on('nav-button-enabled',(event,enabled)=>{
navButtonEnabled=enabled;
console.log('[NAV-BTN] Navigation button enabled: '+enabled);
if(navButton&&!enabled){
navButton.style.display='none';
}
if(navMenu&&!enabled){
navMenu.style.display='none';
}
});
window.addEventListener('DOMContentLoaded',()=>{
document.addEventListener('contextmenu',e=>e.preventDefault());
@@ -6275,7 +6313,200 @@ window.addEventListener('DOMContentLoaded',()=>{
function hideKeyboardIcon(){
if(keyboardIcon)keyboardIcon.style.display='none';
}
function createNavButton(){
if(navButton||!navButtonEnabled)return;
navButton=document.createElement('div');
navButton.id='electron-nav-button';
navButton.innerHTML='🔑';
navButton.title='Navigation Menu';
navButton.style.cssText=`
position:fixed;top:20px;left:20px;width:60px;height:60px;
background:rgba(155,89,182,0.95);border:3px solid rgba(255,255,255,0.9);
border-radius:50%;display:none;align-items:center;justify-content:center;
font-size:32px;cursor:pointer;z-index:999999;
box-shadow:0 4px 12px rgba(0,0,0,0.4);user-select:none;
`;
navButton.addEventListener('click',(e)=>{
e.preventDefault();
e.stopPropagation();
toggleNavMenu();
});
document.body.appendChild(navButton);
}
function showNavButton(){
if(!navButtonEnabled)return;
if(!navButton)createNavButton();
if(navButton)navButton.style.display='flex';
}
function hideNavButton(){
if(navButton)navButton.style.display='none';
}
function createNavMenu(){
if(navMenu)return;
navMenu=document.createElement('div');
navMenu.id='electron-nav-menu';
navMenu.style.cssText=`
position:fixed;top:0;left:0;width:100%;height:100%;
background:rgba(0,0,0,0.9);display:none;align-items:center;justify-content:center;
z-index:999998;
`;
const content=document.createElement('div');
content.style.cssText=`
background:rgba(44,62,80,0.98);border-radius:20px;padding:40px;
max-width:90%;max-height:90%;overflow-y:auto;
box-shadow:0 10px 40px rgba(0,0,0,0.5);
`;
const closeBtn=document.createElement('div');
closeBtn.innerHTML='✕';
closeBtn.style.cssText=`
position:absolute;top:20px;right:20px;font-size:32px;color:white;
cursor:pointer;width:40px;height:40px;display:flex;align-items:center;
justify-content:center;border-radius:50%;background:rgba(231,76,60,0.8);
`;
closeBtn.addEventListener('click',hideNavMenu);
content.appendChild(closeBtn);
const columns=document.createElement('div');
columns.style.cssText='display:flex;gap:40px;margin-top:20px;';
// Column 1: Sites
const sitesCol=document.createElement('div');
sitesCol.style.cssText='flex:1;min-width:300px;';
sitesCol.innerHTML='<h2 style="color:white;margin-bottom:20px;">Sites</h2>';
const sitesList=document.createElement('div');
sitesList.id='nav-sites-list';
sitesList.style.cssText='display:flex;flex-direction:column;gap:10px;';
sitesCol.appendChild(sitesList);
// Column 2: Gesture Cheat Sheet
const cheatCol=document.createElement('div');
cheatCol.style.cssText='flex:1;min-width:300px;';
cheatCol.innerHTML=`
<h2 style="color:white;margin-bottom:20px;">Touch Gestures</h2>
<div style="color:#ecf0f1;line-height:1.8;font-size:16px;">
<div style="margin-bottom:15px;">
<div style="font-weight:bold;color:#3498db;">2-Finger Horizontal Swipe</div>
<div style="padding-left:15px;">Switch between sites</div>
</div>
<div style="margin-bottom:15px;">
<div style="font-weight:bold;color:#3498db;">1-Finger Horizontal Swipe</div>
<div style="padding-left:15px;">Navigate within page (arrow keys)</div>
</div>
<div style="margin-bottom:15px;">
<div style="font-weight:bold;color:#9b59b6;">3-Finger Down Swipe</div>
<div style="padding-left:15px;">Toggle hidden tabs (PIN required)</div>
</div>
<div style="margin-bottom:25px;padding-top:15px;border-top:1px solid rgba(255,255,255,0.2);">
<div style="font-weight:bold;color:#e74c3c;">Keyboard Shortcuts</div>
</div>
<div style="margin-bottom:10px;">
<div style="padding-left:15px;"><kbd style="background:rgba(255,255,255,0.2);padding:2px 8px;border-radius:3px;">Ctrl+Tab</kbd> or <kbd style="background:rgba(255,255,255,0.2);padding:2px 8px;border-radius:3px;">Ctrl+]</kbd> Next tab</div>
</div>
<div style="margin-bottom:10px;">
<div style="padding-left:15px;"><kbd style="background:rgba(255,255,255,0.2);padding:2px 8px;border-radius:3px;">Ctrl+Shift+Tab</kbd> or <kbd style="background:rgba(255,255,255,0.2);padding:2px 8px;border-radius:3px;">Ctrl+[</kbd> Previous tab</div>
</div>
<div style="margin-bottom:10px;">
<div style="padding-left:15px;"><kbd style="background:rgba(255,255,255,0.2);padding:2px 8px;border-radius:3px;">F10</kbd> or <kbd style="background:rgba(255,255,255,0.2);padding:2px 8px;border-radius:3px;">Ctrl+H</kbd> Toggle hidden tabs</div>
</div>
<div style="margin-bottom:10px;">
<div style="padding-left:15px;"><kbd style="background:rgba(255,255,255,0.2);padding:2px 8px;border-radius:3px;">Escape</kbd> Return to normal tabs</div>
</div>
<div style="margin-bottom:10px;">
<div style="padding-left:15px;"><kbd style="background:rgba(255,255,255,0.2);padding:2px 8px;border-radius:3px;">Ctrl+Alt+Delete</kbd> or <kbd style="background:rgba(255,255,255,0.2);padding:2px 8px;border-radius:3px;">Ctrl+Alt+P</kbd> Power menu</div>
</div>
<div style="margin-bottom:10px;">
<div style="padding-left:15px;"><kbd style="background:rgba(255,255,255,0.2);padding:2px 8px;border-radius:3px;">Ctrl+K</kbd> Toggle keyboard</div>
</div>
</div>
`;
columns.appendChild(sitesCol);
columns.appendChild(cheatCol);
content.appendChild(columns);
navMenu.appendChild(content);
navMenu.addEventListener('click',(e)=>{
if(e.target===navMenu){
hideNavMenu();
}
});
document.body.appendChild(navMenu);
}
function toggleNavMenu(){
if(navMenuVisible){
hideNavMenu();
}else{
showNavMenu();
}
}
function showNavMenu(){
if(!navMenu)createNavMenu();
loadSitesIntoNav();
navMenu.style.display='flex';
navMenuVisible=true;
}
function hideNavMenu(){
if(navMenu)navMenu.style.display='none';
navMenuVisible=false;
}
function loadSitesIntoNav(){
const sitesList=document.getElementById('nav-sites-list');
if(!sitesList)return;
// Request config from main process
ipcRenderer.send('get-config');
}
ipcRenderer.on('config-data',(event,config)=>{
const sitesList=document.getElementById('nav-sites-list');
if(!sitesList||!config||!config.tabs)return;
sitesList.innerHTML='';
config.tabs.forEach((tab,index)=>{
// Skip hidden tabs (duration === -1)
if(tab.duration===-1)return;
const siteBtn=document.createElement('div');
const displayName=tab.name||tab.url;
siteBtn.innerHTML=displayName;
siteBtn.style.cssText=`
padding:15px 20px;background:rgba(52,152,219,0.8);color:white;
border-radius:10px;cursor:pointer;font-size:18px;
transition:all 0.2s;border:2px solid transparent;
`;
siteBtn.addEventListener('mouseenter',()=>{
siteBtn.style.background='rgba(52,152,219,1)';
siteBtn.style.borderColor='rgba(255,255,255,0.5)';
});
siteBtn.addEventListener('mouseleave',()=>{
siteBtn.style.background='rgba(52,152,219,0.8)';
siteBtn.style.borderColor='transparent';
});
siteBtn.addEventListener('click',()=>{
ipcRenderer.send('navigate-to-tab',index);
hideNavMenu();
});
sitesList.appendChild(siteBtn);
});
});
function isTextInput(el){
if(!el)return false;
const tag=(el.tagName||'').toLowerCase();
@@ -6380,6 +6611,11 @@ window.addEventListener('DOMContentLoaded',()=>{
}
showPauseButton(); // This will reset the hide timer
}
// Always show navigation button on user interaction (if enabled)
if(navButtonEnabled){
showNavButton();
}
}
// Show pause button on any user interaction
@@ -8314,8 +8550,11 @@ disable_keyboard_autoshow() {
const {contextBridge,ipcRenderer}=require('electron');
console.log('════════════════════════════════════════════════════════════');
console.log(' Hidden Tab: 3-finger DOWN swipe (PIN protected)');
console.log(' Keyboard: 2-finger DOWN swipe (always available)');
console.log(' Gestures:');
console.log(' 3-finger DOWN: Toggle hidden tabs (PIN required)');
console.log(' 2-finger HORIZONTAL: Switch between sites');
console.log(' 1-finger HORIZONTAL: Navigate within page');
console.log(' Navigation: Top-left key icon for site menu');
console.log('════════════════════════════════════════════════════════════');
contextBridge.exposeInMainWorld('electronAPI', {