WhenIdle

Keith Vetter 2008-05-06 : One feature I've been wanting for a while is way to execute some background code when the user is really and truly idle, say 2 seconds after the last user event. after idle doesn't quite work because it can fire when the user is not really idle, such as when scrolling through a document. What I really want is something like after idle 2000 myCmd In tcl 8.5, Tip 245 Discover User Inactivity Time [1 ] provides a way of doing this.

WhenIdle below is a function which after the user has been idle for $delay ms executes the command $cmd. It works by scheduling an after script for the desired delay, and checking when that event fires, just how long the user has been idle. If long enough then it executes the specified command, otherwise it reschedules another after script.

There's some demo code that shows the code in action. Fiddle with the delay amount and then press Start; click randomly to see how it compensates for your activity. After the specified amount of user idle time, the screen will flash red.


##+##########################################################################
#
# whenidle.tcl -- Fires after a certain amount of user idle time has passed
# by Keith Vetter, May 2008
#
package require Tk



##+##########################################################################
# 
# WhenIdle -- after $delay amount of user idle time, execute $cmd
# 
proc WhenIdle {delay cmd} {
    set t [tk inactive]
    set diff [expr {$delay - $t}]
    if {$diff <= 0} {
	if {$::demo} {set ::S(msg) "idle long enough"}
	eval $cmd
    } else {
	if {$::demo} {
	    if {$t == 0} {
		set ::S(msg) "idling for $delay ms"
	    } else {
		set ::S(msg) "need more idling ($diff ms)"
	    }
	}
	after $diff [list WhenIdle $delay $cmd]
    }
}



proc DoDisplay {} {
    label .l1 -text Status:
    label .msg -textvariable ::S(msg) -bd 2 -relief sunken -width 20
    label .l2 -text "Idle Time:"
    scale .s -from 10 -to 3000 -orient h -variable ::S(delay)
    button .start -text Start -command [list WhenIdle $::S(delay) {myCmd red}]
    .start config -command Start ;# Use this for demo

    grid .l1 .msg -sticky ew
    grid .l2 .s
    grid .start - -pady 1m
    grid columnconfigure . 1 -weight 1
    update idletasks
    wm geom . [wm geom .]
}

proc Start {} {
    foreach aid [after info] { after cancel $aid }
    WhenIdle $::S(delay) {myCmd red}
}

proc myCmd {bg} {
    set bg2 [lindex [. config -bg] 3]
    foreach clr [list $bg $bg2 $bg $bg2 $bg] {
	. config -bg $clr
	update idletasks
	after 100
    }
    . config -bg $bg2
}

set demo 1
set S(delay) 2000
set S(msg) "press Start to begin"

DoDisplay
return