Merge pull request #12 from outis1one/claude/add-rotation-popup-01VFWmdhzT1JRHrRe9vH6fEP
Claude/add rotation popup 01 vf wmdhz t1 jr hr re9v h6f ep
This commit is contained in:
@@ -1,9 +1,24 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
################################################################################
|
################################################################################
|
||||||
### Ubuntu Based Kiosk (UBK) v0.9.14 ###
|
### Ubuntu Based Kiosk (UBK) v0.9.15 ###
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# RELEASE v0.9.14 - Restore Simpler Working Logic
|
# RELEASE v0.9.15 - Proper Interaction-Based Rotation Pause
|
||||||
|
#
|
||||||
|
# What's in v0.9.15:
|
||||||
|
# - User interaction PAUSES rotation on that site
|
||||||
|
# * Any touch, click, scroll, or keyboard input pauses rotation
|
||||||
|
# * After 1 minute of inactivity, "Return to Rotation" popup appears
|
||||||
|
# * If no response to popup (15 sec), auto-returns to rotation (prevents stuck kiosk)
|
||||||
|
# * User can extend time with 15/30/60/120 minute options
|
||||||
|
# - Works on ALL sites including home URL
|
||||||
|
# * Even if all sites have 0 time, popup still works (returns to home)
|
||||||
|
# * Ensures rotation will restart even on home URL after interaction
|
||||||
|
# - Exception: Media playback prevents popup and rotation
|
||||||
|
# * Media check every 3 seconds (not every second - performance)
|
||||||
|
# - Changed default inactivity timeout from 2 minutes to 1 minute
|
||||||
|
# - Restored userInteractedWithCurrentSite flag (removed in v0.9.14)
|
||||||
|
# - Rotation blocked while user has interacted until timeout/extension expires
|
||||||
#
|
#
|
||||||
# What's in v0.9.14:
|
# What's in v0.9.14:
|
||||||
# - Reverted to simpler, working logic from v09.9.1_7
|
# - Reverted to simpler, working logic from v09.9.1_7
|
||||||
@@ -157,7 +172,7 @@ set -euo pipefail
|
|||||||
### SECTION 1: CONSTANTS & GLOBALS
|
### SECTION 1: CONSTANTS & GLOBALS
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
SCRIPT_VERSION="0.9.10"
|
SCRIPT_VERSION="0.9.15"
|
||||||
KIOSK_USER="kiosk"
|
KIOSK_USER="kiosk"
|
||||||
BUILD_USER="${SUDO_USER:-$(whoami)}"
|
BUILD_USER="${SUDO_USER:-$(whoami)}"
|
||||||
KIOSK_HOME="/home/${KIOSK_USER}"
|
KIOSK_HOME="/home/${KIOSK_USER}"
|
||||||
@@ -173,7 +188,7 @@ declare -a DURS=()
|
|||||||
declare -a USERS=()
|
declare -a USERS=()
|
||||||
declare -a PASSES=()
|
declare -a PASSES=()
|
||||||
HOME_TAB_INDEX=-1
|
HOME_TAB_INDEX=-1
|
||||||
INACTIVITY_TIMEOUT=60
|
INACTIVITY_TIMEOUT=60 # v0.9.15: Changed from 120 to 60 seconds (1 minute)
|
||||||
LOCKOUT_ENABLED="false"
|
LOCKOUT_ENABLED="false"
|
||||||
LOCKOUT_PASSWORD=""
|
LOCKOUT_PASSWORD=""
|
||||||
LOCKOUT_TIMEOUT=1800 # 30 minutes in seconds
|
LOCKOUT_TIMEOUT=1800 # 30 minutes in seconds
|
||||||
@@ -848,7 +863,7 @@ configure_sites() {
|
|||||||
|
|
||||||
if sudo -u "$KIOSK_USER" test -f "$CONFIG_PATH" 2>/dev/null; then
|
if sudo -u "$KIOSK_USER" test -f "$CONFIG_PATH" 2>/dev/null; then
|
||||||
HOME_TAB_INDEX=$(sudo -u "$KIOSK_USER" jq -r '.homeTabIndex // -1' "$CONFIG_PATH" 2>/dev/null)
|
HOME_TAB_INDEX=$(sudo -u "$KIOSK_USER" jq -r '.homeTabIndex // -1' "$CONFIG_PATH" 2>/dev/null)
|
||||||
INACTIVITY_TIMEOUT=$(sudo -u "$KIOSK_USER" jq -r '.inactivityTimeout // 120' "$CONFIG_PATH" 2>/dev/null)
|
INACTIVITY_TIMEOUT=$(sudo -u "$KIOSK_USER" jq -r '.inactivityTimeout // 60' "$CONFIG_PATH" 2>/dev/null)
|
||||||
|
|
||||||
local tab_count=$(sudo -u "$KIOSK_USER" jq -r '.tabs | length' "$CONFIG_PATH" 2>/dev/null || echo "0")
|
local tab_count=$(sudo -u "$KIOSK_USER" jq -r '.tabs | length' "$CONFIG_PATH" 2>/dev/null || echo "0")
|
||||||
if [[ "$tab_count" -gt 0 ]]; then
|
if [[ "$tab_count" -gt 0 ]]; then
|
||||||
@@ -959,7 +974,7 @@ configure_sites() {
|
|||||||
USERS=()
|
USERS=()
|
||||||
PASSES=()
|
PASSES=()
|
||||||
HOME_TAB_INDEX=-1
|
HOME_TAB_INDEX=-1
|
||||||
INACTIVITY_TIMEOUT=120
|
INACTIVITY_TIMEOUT=60
|
||||||
LOCKOUT_ENABLED="false"
|
LOCKOUT_ENABLED="false"
|
||||||
LOCKOUT_PASSWORD=""
|
LOCKOUT_PASSWORD=""
|
||||||
LOCKOUT_TIMEOUT=1800
|
LOCKOUT_TIMEOUT=1800
|
||||||
@@ -1363,8 +1378,8 @@ configure_home_url() {
|
|||||||
HOME_TAB_INDEX=$((site_num - 1))
|
HOME_TAB_INDEX=$((site_num - 1))
|
||||||
|
|
||||||
echo
|
echo
|
||||||
read -r -p "Inactivity timeout in minutes [2]: " timeout_min
|
read -r -p "Inactivity timeout in minutes [1]: " timeout_min
|
||||||
timeout_min="${timeout_min:-2}"
|
timeout_min="${timeout_min:-1}"
|
||||||
INACTIVITY_TIMEOUT=$((timeout_min * 60))
|
INACTIVITY_TIMEOUT=$((timeout_min * 60))
|
||||||
|
|
||||||
log_success "Home URL: Site #${site_num} (${timeout_min} min timeout)"
|
log_success "Home URL: Site #${site_num} (${timeout_min} min timeout)"
|
||||||
@@ -1374,7 +1389,7 @@ configure_home_url() {
|
|||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
HOME_TAB_INDEX=-1
|
HOME_TAB_INDEX=-1
|
||||||
INACTIVITY_TIMEOUT=120
|
INACTIVITY_TIMEOUT=60
|
||||||
log_success "Home URL disabled"
|
log_success "Home URL disabled"
|
||||||
;;
|
;;
|
||||||
0)
|
0)
|
||||||
@@ -1485,7 +1500,7 @@ add_new_sites() {
|
|||||||
pause
|
pause
|
||||||
|
|
||||||
local use_home_url=false
|
local use_home_url=false
|
||||||
local inactivity_minutes=2
|
local inactivity_minutes=1
|
||||||
local home_duration=180
|
local home_duration=180
|
||||||
|
|
||||||
echo " ═══ HOME URL FEATURE ═══"
|
echo " ═══ HOME URL FEATURE ═══"
|
||||||
@@ -1502,8 +1517,8 @@ add_new_sites() {
|
|||||||
echo
|
echo
|
||||||
if ask_yes_no "Enable HOME URL feature?" "n"; then
|
if ask_yes_no "Enable HOME URL feature?" "n"; then
|
||||||
use_home_url=true
|
use_home_url=true
|
||||||
read -r -p "Inactivity timeout in minutes [2]: " inactivity_minutes
|
read -r -p "Inactivity timeout in minutes [1]: " inactivity_minutes
|
||||||
inactivity_minutes="${inactivity_minutes:-2}"
|
inactivity_minutes="${inactivity_minutes:-1}"
|
||||||
read -r -p "Home display duration in seconds [180]: " home_duration
|
read -r -p "Home display duration in seconds [180]: " home_duration
|
||||||
home_duration="${home_duration:-180}"
|
home_duration="${home_duration:-180}"
|
||||||
echo "✓ HOME: Returns after ${inactivity_minutes}min, displays ${home_duration}s"
|
echo "✓ HOME: Returns after ${inactivity_minutes}min, displays ${home_duration}s"
|
||||||
@@ -3605,7 +3620,7 @@ const path=require('path');
|
|||||||
const os=require('os');
|
const os=require('os');
|
||||||
|
|
||||||
const CONFIG_FILE=path.join(__dirname,'config.json');
|
const CONFIG_FILE=path.join(__dirname,'config.json');
|
||||||
const VERSION='0.9.14';
|
const VERSION='0.9.15';
|
||||||
|
|
||||||
let mainWindow,views=[],hiddenViews=[],tabs=[],currentIndex=0,showingHidden=false;
|
let mainWindow,views=[],hiddenViews=[],tabs=[],currentIndex=0,showingHidden=false;
|
||||||
let pinWindow=null,promptWindow=null,htmlKeyboardWindow=null;
|
let pinWindow=null,promptWindow=null,htmlKeyboardWindow=null;
|
||||||
@@ -3626,6 +3641,7 @@ let mediaIsPlaying=false;
|
|||||||
let userRecentlyActive=false;
|
let userRecentlyActive=false;
|
||||||
let keyboardIsOpen=false;
|
let keyboardIsOpen=false;
|
||||||
let keyboardClosePending=false;
|
let keyboardClosePending=false;
|
||||||
|
let userInteractedWithCurrentSite=false;
|
||||||
|
|
||||||
const USER_ACTIVITY_PAUSE=60000;
|
const USER_ACTIVITY_PAUSE=60000;
|
||||||
const KEYBOARD_AUTO_CLOSE=30000;
|
const KEYBOARD_AUTO_CLOSE=30000;
|
||||||
@@ -3637,7 +3653,7 @@ const INACTIVITY_PROMPT_TIMEOUT=15000;
|
|||||||
let lastMediaStateChange=Date.now();
|
let lastMediaStateChange=Date.now();
|
||||||
|
|
||||||
let homeTabIndex=-1;
|
let homeTabIndex=-1;
|
||||||
let inactivityTimeout=120000;
|
let inactivityTimeout=60000; // v0.9.15: Changed to 1 minute (was 2 minutes)
|
||||||
let allowNavigation='same-origin';
|
let allowNavigation='same-origin';
|
||||||
let lockoutEnabled=false;
|
let lockoutEnabled=false;
|
||||||
let lockoutPassword='';
|
let lockoutPassword='';
|
||||||
@@ -3657,7 +3673,7 @@ function loadConfig(){
|
|||||||
const config=JSON.parse(data);
|
const config=JSON.parse(data);
|
||||||
|
|
||||||
homeTabIndex=(config.homeTabIndex!=null)?config.homeTabIndex:-1;
|
homeTabIndex=(config.homeTabIndex!=null)?config.homeTabIndex:-1;
|
||||||
inactivityTimeout=(config.inactivityTimeout||120)*1000;
|
inactivityTimeout=(config.inactivityTimeout||60)*1000; // v0.9.15: Default 60 seconds
|
||||||
allowNavigation=config.allowNavigation||'same-origin';
|
allowNavigation=config.allowNavigation||'same-origin';
|
||||||
lockoutEnabled=(config.lockoutEnabled===true||config.lockoutEnabled==='true');
|
lockoutEnabled=(config.lockoutEnabled===true||config.lockoutEnabled==='true');
|
||||||
lockoutPassword=config.lockoutPassword||'';
|
lockoutPassword=config.lockoutPassword||'';
|
||||||
@@ -3692,7 +3708,13 @@ function markActivity(resetLockoutTimer){
|
|||||||
lastUserInteraction=now;
|
lastUserInteraction=now;
|
||||||
userRecentlyActive=true;
|
userRecentlyActive=true;
|
||||||
|
|
||||||
// v0.9.14: Restore simpler logic from v09.9.1_7
|
// v0.9.15: Track that user has interacted with current site
|
||||||
|
// This will trigger inactivity popup after timeout
|
||||||
|
if(!userInteractedWithCurrentSite){
|
||||||
|
console.log('[ACTIVITY] User interacted with site - will show popup after inactivity');
|
||||||
|
}
|
||||||
|
userInteractedWithCurrentSite=true;
|
||||||
|
|
||||||
// Reset lockout timer if requested
|
// Reset lockout timer if requested
|
||||||
if(resetLockoutTimer){
|
if(resetLockoutTimer){
|
||||||
lastLockoutCheck=now;
|
lastLockoutCheck=now;
|
||||||
@@ -3905,7 +3927,7 @@ function startMasterTimer(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 6. SITE ROTATION
|
// 6. SITE ROTATION
|
||||||
// v0.9.14: Restore simple rotation from v09.9.1_7 - just rotate based on time
|
// v0.9.15: Block rotation if user interacted, unless extension expired or media stopped
|
||||||
if(!showingHidden&&views.length>1){
|
if(!showingHidden&&views.length>1){
|
||||||
const currentTabIdx=viewIndexToTabIndex(currentIndex);
|
const currentTabIdx=viewIndexToTabIndex(currentIndex);
|
||||||
|
|
||||||
@@ -3915,10 +3937,11 @@ function startMasterTimer(){
|
|||||||
if(siteDuration>0){
|
if(siteDuration>0){
|
||||||
const timeOnSite=now-siteStartTime;
|
const timeOnSite=now-siteStartTime;
|
||||||
|
|
||||||
// CRITICAL FIX: Don't auto-rotate if time extension is active
|
// Block rotation if user interacted with site (until timeout or they return to rotation)
|
||||||
const hasActiveExtension=(inactivityExtensionUntil>0&&now<inactivityExtensionUntil);
|
const hasActiveExtension=(inactivityExtensionUntil>0&&now<inactivityExtensionUntil);
|
||||||
|
const userHasInteracted=userInteractedWithCurrentSite;
|
||||||
|
|
||||||
if(timeOnSite>=siteDuration*1000&&!hasActiveExtension){
|
if(timeOnSite>=siteDuration*1000&&!hasActiveExtension&&!userHasInteracted){
|
||||||
rotateToNextSite();
|
rotateToNextSite();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3926,16 +3949,12 @@ function startMasterTimer(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. INACTIVITY CHECK (works on home page OR manual navigation)
|
// 7. INACTIVITY CHECK
|
||||||
// v0.9.14: Restore simpler logic from v09.9.1_7
|
// v0.9.15: Show popup on ANY site where user has interacted after inactivity timeout
|
||||||
// Inactivity prompt ONLY appears on home page or when user manually navigated
|
// Exception: Don't show if media is playing
|
||||||
if(homeTabIndex>=0&&!showingHidden&&!sessionLocked){
|
if(!showingHidden&&!sessionLocked&&!mediaIsPlaying){
|
||||||
const homeViewIdx=getHomeViewIndex();
|
// Check if user has interacted with current site
|
||||||
const currentTabIdx=viewIndexToTabIndex(currentIndex);
|
if(userInteractedWithCurrentSite){
|
||||||
const isOnHomePage=(homeViewIdx>=0&¤tIndex===homeViewIdx);
|
|
||||||
|
|
||||||
// Only check if we're in manual mode OR on home page
|
|
||||||
if(manualNavigationMode||isOnHomePage){
|
|
||||||
const idleTime=now-lastUserInteraction;
|
const idleTime=now-lastUserInteraction;
|
||||||
|
|
||||||
// CRITICAL FIX: Use absolute time check for extensions
|
// CRITICAL FIX: Use absolute time check for extensions
|
||||||
@@ -3950,9 +3969,10 @@ function startMasterTimer(){
|
|||||||
}
|
}
|
||||||
return; // Skip timeout check during extension
|
return; // Skip timeout check during extension
|
||||||
}else if(inactivityExtensionUntil>0&&now>=inactivityExtensionUntil){
|
}else if(inactivityExtensionUntil>0&&now>=inactivityExtensionUntil){
|
||||||
// Extension expired - clear it and check timeout
|
// Extension expired - clear it and allow rotation to resume
|
||||||
console.log('[INACTIVITY] ⏰ Extension expired - checking timeout');
|
console.log('[INACTIVITY] ⏰ Extension expired - rotation can resume');
|
||||||
inactivityExtensionUntil=0;
|
inactivityExtensionUntil=0;
|
||||||
|
userInteractedWithCurrentSite=false; // Clear interaction flag so rotation can resume
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log every 15 seconds
|
// Log every 15 seconds
|
||||||
@@ -3961,14 +3981,12 @@ function startMasterTimer(){
|
|||||||
const idleSeconds=Math.floor((idleTime%60000)/1000);
|
const idleSeconds=Math.floor((idleTime%60000)/1000);
|
||||||
const timeoutMinutes=Math.floor(effectiveTimeout/60000);
|
const timeoutMinutes=Math.floor(effectiveTimeout/60000);
|
||||||
const timeoutSeconds=Math.floor((effectiveTimeout%60000)/1000);
|
const timeoutSeconds=Math.floor((effectiveTimeout%60000)/1000);
|
||||||
const location=isOnHomePage?'HOME PAGE':'OTHER PAGE';
|
console.log('[INACTIVITY] 🕒 IDLE: '+idleMinutes+'m '+idleSeconds+'s / '+timeoutMinutes+'m '+timeoutSeconds+'s');
|
||||||
console.log('[INACTIVITY] 🕒 '+location+' IDLE: '+idleMinutes+'m '+idleSeconds+'s / '+timeoutMinutes+'m '+timeoutSeconds+'s');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(idleTime>=effectiveTimeout){
|
if(idleTime>=effectiveTimeout){
|
||||||
if(!promptWindow||promptWindow.isDestroyed()){
|
if(!promptWindow||promptWindow.isDestroyed()){
|
||||||
const location=isOnHomePage?'home page':'other page';
|
console.log('[INACTIVITY] 🔔 *** SHOWING POPUP - user inactive for '+Math.floor(idleTime/1000)+'s ***');
|
||||||
console.log('[INACTIVITY] 🔔 *** SHOWING PROMPT (on '+location+') ***');
|
|
||||||
showInactivityPrompt();
|
showInactivityPrompt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4056,6 +4074,9 @@ function attachView(i,isAutoRotation){
|
|||||||
|
|
||||||
views[i].webContents.focus();
|
views[i].webContents.focus();
|
||||||
siteStartTime=Date.now();
|
siteStartTime=Date.now();
|
||||||
|
|
||||||
|
// v0.9.15: Reset interaction flag when site changes
|
||||||
|
userInteractedWithCurrentSite=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextTab(){
|
function nextTab(){
|
||||||
@@ -4157,9 +4178,10 @@ function showInactivityPrompt(){
|
|||||||
// CRITICAL FIX: Store timeout ID so we can cancel it when user responds
|
// CRITICAL FIX: Store timeout ID so we can cancel it when user responds
|
||||||
const promptTimeoutId=setTimeout(()=>{
|
const promptTimeoutId=setTimeout(()=>{
|
||||||
if(promptWindow&&!promptWindow.isDestroyed()){
|
if(promptWindow&&!promptWindow.isDestroyed()){
|
||||||
console.log('[PROMPT] No response - returning home');
|
console.log('[PROMPT] No response - returning to rotation');
|
||||||
promptWindow.close();
|
promptWindow.close();
|
||||||
promptWindow=null;
|
promptWindow=null;
|
||||||
|
userInteractedWithCurrentSite=false; // Clear interaction flag
|
||||||
returnToHome();
|
returnToHome();
|
||||||
}
|
}
|
||||||
},INACTIVITY_PROMPT_TIMEOUT);
|
},INACTIVITY_PROMPT_TIMEOUT);
|
||||||
@@ -4174,21 +4196,23 @@ function showInactivityPrompt(){
|
|||||||
promptWindow=null;
|
promptWindow=null;
|
||||||
|
|
||||||
if(minutes===-1){
|
if(minutes===-1){
|
||||||
// User chose "Return to Rotation" - clear extension and restart rotation
|
// User chose "Return to Rotation" - clear flags and restart rotation
|
||||||
|
console.log('[PROMPT] User chose return to rotation');
|
||||||
inactivityExtensionUntil=0;
|
inactivityExtensionUntil=0;
|
||||||
|
userInteractedWithCurrentSite=false; // Clear interaction flag so rotation resumes
|
||||||
returnToHome();
|
returnToHome();
|
||||||
}else if(minutes===0){
|
}else if(minutes===0){
|
||||||
// v0.9.14: User chose "I'm still here" - don't change manualNavigationMode
|
// User chose "I'm still here" - reset timer but keep on current site
|
||||||
// This is a prompt response, not actual interaction with content
|
console.log('[PROMPT] User chose: I\'m still here');
|
||||||
inactivityExtensionUntil=0;
|
inactivityExtensionUntil=0;
|
||||||
markActivity(); // Resets inactivity timer but NOT lockout timer
|
markActivity(); // Resets inactivity timer but NOT lockout timer
|
||||||
}else{
|
}else{
|
||||||
// User chose a time extension - grant it!
|
// User chose a time extension - grant it!
|
||||||
// Don't reset lockout timer - they're just buying more time
|
// Keep userInteractedWithCurrentSite=true so rotation stays paused
|
||||||
const now=Date.now();
|
const now=Date.now();
|
||||||
inactivityExtensionUntil=now+(minutes*60*1000);
|
inactivityExtensionUntil=now+(minutes*60*1000);
|
||||||
lastUserInteraction=now;
|
lastUserInteraction=now;
|
||||||
console.log('[PROMPT] ⏰ Extended until: '+new Date(inactivityExtensionUntil).toLocaleTimeString());
|
console.log('[PROMPT] ⏰ Extended '+minutes+' minutes until: '+new Date(inactivityExtensionUntil).toLocaleTimeString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -5248,9 +5272,9 @@ echo "[15/27] Creating inactivity prompt..."
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>👋 Are you still here?</h2>
|
<h2>⏸️ Pause Rotation</h2>
|
||||||
<div class="message">
|
<div class="message">
|
||||||
No activity detected. Choose an option:
|
Would you like to stay on this page?
|
||||||
</div>
|
</div>
|
||||||
<div class="countdown" id="countdown">15</div>
|
<div class="countdown" id="countdown">15</div>
|
||||||
|
|
||||||
@@ -5281,9 +5305,9 @@ echo "[15/27] Creating inactivity prompt..."
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info">
|
<div class="info">
|
||||||
ℹ️ Extensions pause the inactivity timer<br>
|
ℹ️ Choose how long to stay on this page<br>
|
||||||
Media playback (video/audio) automatically pauses the timer<br>
|
Media playback (video/audio) won't trigger this popup<br>
|
||||||
Maximum extension: 4 hours (safety timeout)
|
Select "Return to rotation" to resume automatic rotation
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
Reference in New Issue
Block a user