Fix v0.9.15: Properly implement interaction-based rotation pause

This commit fixes the initial v0.9.15 implementation to match the
actual vision:

CORRECTED BEHAVIOR:
- User interaction PAUSES rotation on that site
- After 1 minute of inactivity → popup appears
- Popup gives options: extend time or return to rotation
- No response (15 sec) → auto-return to rotation
- Works on ALL sites including home URL
- Media playback blocks popup and rotation

KEY FIXES:
1. Removed wrong "popup on first interaction" logic
2. Restored userInteractedWithCurrentSite flag (removed in v0.9.14)
3. markActivity() now sets interaction flag instead of showing popup
4. Rotation blocked when userInteractedWithCurrentSite=true
5. Inactivity check works on ANY site where user interacted
   (not just home or manualNavigationMode)
6. Changed default timeout from 2 minutes to 1 minute
7. Prompt response clears interaction flag on "return to rotation"
8. Extension expiry clears interaction flag to resume rotation
9. Updated all bash script defaults from 120s to 60s

TECHNICAL CHANGES:
- Line 3639: Changed popupShownForCurrentSite → userInteractedWithCurrentSite
- Line 3706-3711: Set flag in markActivity() instead of showing popup
- Line 3937: Block rotation if userInteractedWithCurrentSite
- Line 3947-3989: Simplified inactivity check - works on ANY interacted site
- Line 3651: Default timeout 60000ms (was 120000ms)
- Line 3671: Config default 60s (was 120s)
- Line 3970: Clear flag when extension expires
- Line 4078: Reset flag when site changes
- Line 4179, 4197: Clear flag on return to rotation
- Bash section: Updated all 120s→60s, 2min→1min defaults

This matches the original vision: rotation pauses on interaction,
popup after inactivity gives control, auto-return prevents stuck kiosk.
This commit is contained in:
Claude
2025-11-17 23:50:55 +00:00
parent 10652e9d09
commit 5ba854dd5a
+59 -57
View File
@@ -3,17 +3,22 @@
### Ubuntu Based Kiosk (UBK) v0.9.15 ### ### Ubuntu Based Kiosk (UBK) v0.9.15 ###
################################################################################ ################################################################################
# #
# RELEASE v0.9.15 - Interaction-Based Return to Rotation Popup # RELEASE v0.9.15 - Proper Interaction-Based Rotation Pause
# #
# What's in v0.9.15: # What's in v0.9.15:
# - Return to rotation popup now appears on ANY user interaction # - User interaction PAUSES rotation on that site
# * Any touch, click, scroll, or keyboard input triggers the popup # * Any touch, click, scroll, or keyboard input pauses rotation
# * Exception: popup does NOT appear when media is playing # * After 1 minute of inactivity, "Return to Rotation" popup appears
# * Gives users immediate option to extend time on current URL # * If no response to popup (15 sec), auto-returns to rotation (prevents stuck kiosk)
# - Time extension options remain the same (15/30/60/120 minutes) # * User can extend time with 15/30/60/120 minute options
# - Popup pauses rotation while displayed # - Works on ALL sites including home URL
# - Popup appears once per site load (won't spam on every interaction) # * Even if all sites have 0 time, popup still works (returns to home)
# - Manual navigation and automatic rotation both trigger popup on interaction # * 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
@@ -183,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
@@ -858,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
@@ -969,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
@@ -1373,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)"
@@ -1384,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)
@@ -1495,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 ═══"
@@ -1512,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"
@@ -3636,7 +3641,7 @@ let mediaIsPlaying=false;
let userRecentlyActive=false; let userRecentlyActive=false;
let keyboardIsOpen=false; let keyboardIsOpen=false;
let keyboardClosePending=false; let keyboardClosePending=false;
let popupShownForCurrentSite=false; let userInteractedWithCurrentSite=false;
const USER_ACTIVITY_PAUSE=60000; const USER_ACTIVITY_PAUSE=60000;
const KEYBOARD_AUTO_CLOSE=30000; const KEYBOARD_AUTO_CLOSE=30000;
@@ -3648,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='';
@@ -3668,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||'';
@@ -3703,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;
@@ -3716,14 +3727,6 @@ function markActivity(resetLockoutTimer){
promptWindow=null; promptWindow=null;
} }
// v0.9.15: Show return to rotation popup on first interaction with site
// Exception: Don't show if media is playing or session is locked
if(!popupShownForCurrentSite&&!mediaIsPlaying&&!sessionLocked&&timeSinceLastActivity>5000){
console.log('[ACTIVITY] 🔔 First interaction with site - showing return to rotation popup');
popupShownForCurrentSite=true;
showInactivityPrompt();
}
// CRITICAL FIX: Don't clear time extensions on user activity! // CRITICAL FIX: Don't clear time extensions on user activity!
// Extensions should only be cleared when: // Extensions should only be cleared when:
// 1. They naturally expire // 1. They naturally expire
@@ -3924,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);
@@ -3934,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;
} }
@@ -3945,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&&currentIndex===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
@@ -3969,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
@@ -3980,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();
} }
} }
@@ -4076,8 +4075,8 @@ function attachView(i,isAutoRotation){
views[i].webContents.focus(); views[i].webContents.focus();
siteStartTime=Date.now(); siteStartTime=Date.now();
// v0.9.15: Reset popup flag when site changes // v0.9.15: Reset interaction flag when site changes
popupShownForCurrentSite=false; userInteractedWithCurrentSite=false;
} }
function nextTab(){ function nextTab(){
@@ -4179,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);
@@ -4196,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());
} }
}); });
} }