41

I want to check, inside a bash script, how long the user of a X session has been idle.

The user himself does not have to be using bash, but just X. If the user just moved the mouse, for example, a good answer would be "idle for 0 seconds". If he has not touched the computer in 5 minutes, a good answer would be "idle for 300 seconds"

The reason to not use xautolock straight away is to be able to implement some complex behavior. For example, if the user is idle for 10 minutes, try to suspend, if he is idle for more 5 minutes, shutoff (I know it sounds odd, but suspend does not always work here ...)

josinalvo
  • 6,947
  • 5
  • 37
  • 51

4 Answers4

47

Just found a simple way to do it.

There is a program called xprintidle that does the trick

getting the idle time (in milliseconds) is as simple as

xprintidle

and to install

apt-get install xprintidle

For sysadmins, it also works remotely

From an ssh session:

export DISPLAY=:0 && sudo -u john xprintidle

where john is the user logged into the X session on the remote machine.


Note that some programs such as MPlayer seem to reset the counter.

That might be desirable or not depending on your application. For me I meant to use it to suspend the computer and the MPlayer exception is helpful.

There is another answer (https://askubuntu.com/a/1303464/56440) for those who want no resetting, but I haven't personally tested it

josinalvo
  • 6,947
  • 5
  • 37
  • 51
9

Using the answer by josinalvo might work for some, but it didn't quite work so well for me as some programs seem to be regularly resetting the timer, on which xprintidle relies, unexpectedly. In addition, I also would not want a fullscreen application to reset the idle timer, at least not in my use case.

So I've strung together my own solution in a shell script which doesn't rely on the X11 Screen Saver extension. Instead it dumps all user input like mouse movement and key presses using xinput test-xi2 --root for one second and then compares it with a dump from the last second. If they are the same, the variable $t gets increased by 1 and gets reset in the other case. This is looped and when variable $t gets over the treshold in $idletime, it is printed that the user is idle.

idleloop() {
    touch /tmp/.{,last_}input
    cmd='stat --printf="%s"'
    idletime=120
    a=2
    t=0
    while true
    do
        timeout 1 xinput test-xi2 --root > /tmp/.input
    if [[ `eval $cmd /tmp/.input` == `eval $cmd /tmp/.last_input` ]]
    then
        let t++ # increases $t by 1
    else
        t=0     # resets $t
    fi

    mv /tmp/.{,last_}input -f

    if [ $t -ge $idletime ] && [[ $a == "2" ]]
    then
        echo "user has gone idle"
        a=1
    fi
    if [ $t -lt $idletime ] && [[ $a == "1" ]]
    then
        echo "user has come back from idle"
        a=2
    fi
done

}

idleloop

Feel free to leave any suggestions.

Zanna
  • 70,465
  • I think you are correct some programs do reset the counter – josinalvo Jan 02 '21 at 11:31
  • One such program is MPlayer. – josinalvo Jan 02 '21 at 11:31
  • Upvoting for first time introduction to timeout command which can be quite handy in the toolkit. I've been using xprintidle for years though and will continue to keep it in the toolkit. – WinEunuuchs2Unix Jan 05 '21 at 18:32
  • This doesn't work well for me. The stat %s format sequence checks the size of the file, and the file can be different without the size changing. – Ian D. Allen Apr 04 '22 at 02:44
  • This was a great starting point for me. Instead of using stat and spawning two subshells to compare the files, it's easier to use cmp. The if statement would change to if cmp --silent /tmp/.{,last_}input; then. cmp compares the files byte by byte and stops as soon as it finds a difference. Also, if you're using wayland, xprintidle and xinput probably won't work. So, check this answer for an alternative: https://unix.stackexchange.com/a/492328/232740 – Felipe Apr 28 '22 at 17:39
4

Answer from here:

In bash

w | tr -s " " | cut -d" " -f1,5 | tail -n+3

gives you a username/idletime pair for each shell. So basically you can get the idle information through the command w

Dan
  • 673
  • 1
    how does this count interactions with X that are not through a terminal ? Like, say a mouse movement inside a firefox. If the user is moving the mouse, I'd like to get the answer "not idle", or "idle for 0 seconds" – josinalvo Oct 17 '12 at 17:06
  • @josinalvo w just gives you the idle time in that particular terminal I think. – Dan Oct 17 '12 at 17:16
  • 7
    That idle time is in fact the running time, the uptime of the process (WHAT column of the w output). So it gives no idea about how long the user has been idle in his X session. – rosch Oct 17 '12 at 17:55
1

Here is a complete bash script which detects if a user/system is idle for 5 or more seconds and writes the data to a file. With the help of another bash or Python script, we can do more analysis on that text file. Obviously, we can change these values(idletime_threshold) as per requirements.

#!/bin/bash
# detect-user-idle-busy-time-with-dbus.sh

idletime_threshold=5000 # in milli seconds

idletime_threshold=300000 # in milli seconds

a=2 output=system-idle-busy.txt idleloop() { while true do sleep 1 # seconds now=date "+%Y-%m-%d %H:%M:%S" get_idle_time=dbus-send --print-reply --dest=org.gnome.Mutter.IdleMonitor /org/gnome/Mutter/IdleMonitor/Core org.gnome.Mutter.IdleMonitor.GetIdletime idle_time=$(echo $get_idle_time | awk '{print $NF}') #echo "${idle_time}" # milli seconds

    if [ $idle_time -ge $idletime_threshold ] && [[ $a == "2" ]]
    then
        echo "idle : ${now}" | tee -a $output  
        a=1
    fi

    if [ $idle_time -lt $idletime_threshold ] && [[ $a == "1" ]]
    then
        echo "busy : ${now}" | tee -a $output  
        a=2
    fi
done

} idleloop

output:

u-20:~/Downloads$ tailc -f system-idle-busy.txt
idle : 2022-09-15 14:20:00 
busy : 2022-09-15 14:20:02 
idle : 2022-09-15 14:20:25 
busy : 2022-09-15 14:20:59 
idle : 2022-09-15 14:23:48