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
+61 -59
View File
@@ -3,17 +3,22 @@
### 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:
# - Return to rotation popup now appears on ANY user interaction
# * Any touch, click, scroll, or keyboard input triggers the popup
# * Exception: popup does NOT appear when media is playing
# * Gives users immediate option to extend time on current URL
# - Time extension options remain the same (15/30/60/120 minutes)
# - Popup pauses rotation while displayed
# - Popup appears once per site load (won't spam on every interaction)
# - Manual navigation and automatic rotation both trigger popup on interaction
# - 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:
# - Reverted to simpler, working logic from v09.9.1_7
@@ -183,7 +188,7 @@ declare -a DURS=()
declare -a USERS=()
declare -a PASSES=()
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_PASSWORD=""
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
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")
if [[ "$tab_count" -gt 0 ]]; then
@@ -969,7 +974,7 @@ configure_sites() {
USERS=()
PASSES=()
HOME_TAB_INDEX=-1
INACTIVITY_TIMEOUT=120
INACTIVITY_TIMEOUT=60
LOCKOUT_ENABLED="false"
LOCKOUT_PASSWORD=""
LOCKOUT_TIMEOUT=1800
@@ -1371,10 +1376,10 @@ configure_home_url() {
if [[ "$site_num" =~ ^[0-9]+$ ]] && [[ "$site_num" -ge 1 ]] && [[ "$site_num" -le "${#URLS[@]}" ]]; then
HOME_TAB_INDEX=$((site_num - 1))
echo
read -r -p "Inactivity timeout in minutes [2]: " timeout_min
timeout_min="${timeout_min:-2}"
read -r -p "Inactivity timeout in minutes [1]: " timeout_min
timeout_min="${timeout_min:-1}"
INACTIVITY_TIMEOUT=$((timeout_min * 60))
log_success "Home URL: Site #${site_num} (${timeout_min} min timeout)"
@@ -1384,7 +1389,7 @@ configure_home_url() {
;;
2)
HOME_TAB_INDEX=-1
INACTIVITY_TIMEOUT=120
INACTIVITY_TIMEOUT=60
log_success "Home URL disabled"
;;
0)
@@ -1495,9 +1500,9 @@ add_new_sites() {
pause
local use_home_url=false
local inactivity_minutes=2
local inactivity_minutes=1
local home_duration=180
echo " ═══ HOME URL FEATURE ═══"
echo
echo "A HOME URL returns the kiosk to a default screen after inactivity."
@@ -1512,8 +1517,8 @@ add_new_sites() {
echo
if ask_yes_no "Enable HOME URL feature?" "n"; then
use_home_url=true
read -r -p "Inactivity timeout in minutes [2]: " inactivity_minutes
inactivity_minutes="${inactivity_minutes:-2}"
read -r -p "Inactivity timeout in minutes [1]: " inactivity_minutes
inactivity_minutes="${inactivity_minutes:-1}"
read -r -p "Home display duration in seconds [180]: " home_duration
home_duration="${home_duration:-180}"
echo "✓ HOME: Returns after ${inactivity_minutes}min, displays ${home_duration}s"
@@ -3636,7 +3641,7 @@ let mediaIsPlaying=false;
let userRecentlyActive=false;
let keyboardIsOpen=false;
let keyboardClosePending=false;
let popupShownForCurrentSite=false;
let userInteractedWithCurrentSite=false;
const USER_ACTIVITY_PAUSE=60000;
const KEYBOARD_AUTO_CLOSE=30000;
@@ -3648,7 +3653,7 @@ const INACTIVITY_PROMPT_TIMEOUT=15000;
let lastMediaStateChange=Date.now();
let homeTabIndex=-1;
let inactivityTimeout=120000;
let inactivityTimeout=60000; // v0.9.15: Changed to 1 minute (was 2 minutes)
let allowNavigation='same-origin';
let lockoutEnabled=false;
let lockoutPassword='';
@@ -3668,7 +3673,7 @@ function loadConfig(){
const config=JSON.parse(data);
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';
lockoutEnabled=(config.lockoutEnabled===true||config.lockoutEnabled==='true');
lockoutPassword=config.lockoutPassword||'';
@@ -3703,7 +3708,13 @@ function markActivity(resetLockoutTimer){
lastUserInteraction=now;
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
if(resetLockoutTimer){
lastLockoutCheck=now;
@@ -3716,14 +3727,6 @@ function markActivity(resetLockoutTimer){
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!
// Extensions should only be cleared when:
// 1. They naturally expire
@@ -3924,7 +3927,7 @@ function startMasterTimer(){
}
// 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){
const currentTabIdx=viewIndexToTabIndex(currentIndex);
@@ -3934,10 +3937,11 @@ function startMasterTimer(){
if(siteDuration>0){
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 userHasInteracted=userInteractedWithCurrentSite;
if(timeOnSite>=siteDuration*1000&&!hasActiveExtension){
if(timeOnSite>=siteDuration*1000&&!hasActiveExtension&&!userHasInteracted){
rotateToNextSite();
return;
}
@@ -3945,16 +3949,12 @@ function startMasterTimer(){
}
}
// 7. INACTIVITY CHECK (works on home page OR manual navigation)
// v0.9.14: Restore simpler logic from v09.9.1_7
// Inactivity prompt ONLY appears on home page or when user manually navigated
if(homeTabIndex>=0&&!showingHidden&&!sessionLocked){
const homeViewIdx=getHomeViewIndex();
const currentTabIdx=viewIndexToTabIndex(currentIndex);
const isOnHomePage=(homeViewIdx>=0&&currentIndex===homeViewIdx);
// Only check if we're in manual mode OR on home page
if(manualNavigationMode||isOnHomePage){
// 7. INACTIVITY CHECK
// v0.9.15: Show popup on ANY site where user has interacted after inactivity timeout
// Exception: Don't show if media is playing
if(!showingHidden&&!sessionLocked&&!mediaIsPlaying){
// Check if user has interacted with current site
if(userInteractedWithCurrentSite){
const idleTime=now-lastUserInteraction;
// CRITICAL FIX: Use absolute time check for extensions
@@ -3969,9 +3969,10 @@ function startMasterTimer(){
}
return; // Skip timeout check during extension
}else if(inactivityExtensionUntil>0&&now>=inactivityExtensionUntil){
// Extension expired - clear it and check timeout
console.log('[INACTIVITY] ⏰ Extension expired - checking timeout');
// Extension expired - clear it and allow rotation to resume
console.log('[INACTIVITY] ⏰ Extension expired - rotation can resume');
inactivityExtensionUntil=0;
userInteractedWithCurrentSite=false; // Clear interaction flag so rotation can resume
}
// Log every 15 seconds
@@ -3980,14 +3981,12 @@ function startMasterTimer(){
const idleSeconds=Math.floor((idleTime%60000)/1000);
const timeoutMinutes=Math.floor(effectiveTimeout/60000);
const timeoutSeconds=Math.floor((effectiveTimeout%60000)/1000);
const location=isOnHomePage?'HOME PAGE':'OTHER PAGE';
console.log('[INACTIVITY] 🕒 '+location+' IDLE: '+idleMinutes+'m '+idleSeconds+'s / '+timeoutMinutes+'m '+timeoutSeconds+'s');
console.log('[INACTIVITY] 🕒 IDLE: '+idleMinutes+'m '+idleSeconds+'s / '+timeoutMinutes+'m '+timeoutSeconds+'s');
}
if(idleTime>=effectiveTimeout){
if(!promptWindow||promptWindow.isDestroyed()){
const location=isOnHomePage?'home page':'other page';
console.log('[INACTIVITY] 🔔 *** SHOWING PROMPT (on '+location+') ***');
console.log('[INACTIVITY] 🔔 *** SHOWING POPUP - user inactive for '+Math.floor(idleTime/1000)+'s ***');
showInactivityPrompt();
}
}
@@ -4076,8 +4075,8 @@ function attachView(i,isAutoRotation){
views[i].webContents.focus();
siteStartTime=Date.now();
// v0.9.15: Reset popup flag when site changes
popupShownForCurrentSite=false;
// v0.9.15: Reset interaction flag when site changes
userInteractedWithCurrentSite=false;
}
function nextTab(){
@@ -4179,9 +4178,10 @@ function showInactivityPrompt(){
// CRITICAL FIX: Store timeout ID so we can cancel it when user responds
const promptTimeoutId=setTimeout(()=>{
if(promptWindow&&!promptWindow.isDestroyed()){
console.log('[PROMPT] No response - returning home');
console.log('[PROMPT] No response - returning to rotation');
promptWindow.close();
promptWindow=null;
userInteractedWithCurrentSite=false; // Clear interaction flag
returnToHome();
}
},INACTIVITY_PROMPT_TIMEOUT);
@@ -4196,21 +4196,23 @@ function showInactivityPrompt(){
promptWindow=null;
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;
userInteractedWithCurrentSite=false; // Clear interaction flag so rotation resumes
returnToHome();
}else if(minutes===0){
// v0.9.14: User chose "I'm still here" - don't change manualNavigationMode
// This is a prompt response, not actual interaction with content
// User chose "I'm still here" - reset timer but keep on current site
console.log('[PROMPT] User chose: I\'m still here');
inactivityExtensionUntil=0;
markActivity(); // Resets inactivity timer but NOT lockout timer
}else{
// 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();
inactivityExtensionUntil=now+(minutes*60*1000);
lastUserInteraction=now;
console.log('[PROMPT] ⏰ Extended until: '+new Date(inactivityExtensionUntil).toLocaleTimeString());
console.log('[PROMPT] ⏰ Extended '+minutes+' minutes until: '+new Date(inactivityExtensionUntil).toLocaleTimeString());
}
});
}