25

I want to run a command when the user becomes inactive (the system is idle). For example:

echo "You started to be inactive."

Also, when the user becomes active again (the system is not idle anymore):

echo "You started to be active, again."

I need a shell script that will do this. Is this possible without a timer/interval? Maybe some system events?

  • 2
    Can you go into a little bit deeper detail about what you actually want to achieve? – Nils Mar 22 '14 at 21:31
  • 3
    How do you measure idleness? Is a user watching a movie idle (because he isn't interacting with the computer)? Is a user who is logged in remotely active? – Gilles 'SO- stop being evil' Mar 22 '14 at 23:30
  • From what I know, the system has this built-in concept: idle, active etc. Is this right? – Ionică Bizău Mar 23 '14 at 05:33
  • @IonicăBizău: Not really. Idle in the context of the system would only mean that all processes are sleeping, that is 'waiting for something'. Although this may happen quite often on a modern system and may even be the case most of the time, it does not stay that way for long as there is always something to be done and if it is updating the clock. Idleness, especially in the context of your question, is more a function of the user than the system and even than it is usually linked to a specific session. – Adaephon Mar 25 '14 at 08:32
  • @Adaephon Ok. In my concept, a system is idle when the user doesn't do any action (mouse move, click, key press) at the computer for x minutes. The system becomes active when the user make the first action: clicks a mouse button, moves it, presses a key and so on. Is it possible to detect these idle/active states using a shell script? – Ionică Bizău Mar 25 '14 at 11:33

5 Answers5

23

This thread on the ArchLinux forums contains a short C program that queries the xscreensaver for information how long the user has been idle, this seems to be quite close to your requirements:

#include <X11/extensions/scrnsaver.h>
#include <stdio.h>

int main(void) {
    Display *dpy = XOpenDisplay(NULL);

    if (!dpy) {
        return(1);
    }

    XScreenSaverInfo *info = XScreenSaverAllocInfo();
    XScreenSaverQueryInfo(dpy, DefaultRootWindow(dpy), info);
    printf("%u\n", info->idle);

      return(0);
}

Save this as getIdle.c and compile with

gcc -o getIdle getIdle.c -lXss -lX11

to get an executable file getIdle. This program prints the "idle time" (user does not move/click with mouse, does not use keyboard) in milliseconds, so a bash script that builds upon this could looke like this:

#!/bin/bash

idle=false
idleAfter=3000     # consider idle after 3000 ms

while true; do
  idleTimeMillis=$(./getIdle)
  echo $idleTimeMillis  # just for debug purposes.
  if [[ $idle = false && $idleTimeMillis -gt $idleAfter ]] ; then
    echo "start idle"   # or whatever command(s) you want to run...
    idle=true
  fi

  if [[ $idle = true && $idleTimeMillis -lt $idleAfter ]] ; then
    echo "end idle"     # same here.
    idle=false
  fi
  sleep 1      # polling interval

done

This still needs regular polling, but it does everything you need...

ioflx
  • 3
Jasper
  • 3,628
  • 6
    You could save yourself compiling your own program and just use xprintidle, which is available in most distributions and does exactly the same thing. – Riot Jul 07 '16 at 20:36
  • Deserves a Github repo: https://github.com/ceremcem/on-idle – ceremcem May 05 '20 at 23:24
  • This code sometimes works okay, sometimes doesn't. I'm using this code for a long long time and it behaves unpredictable. I still couldn't figure out what prevents the computer from identified as "idle". – ceremcem Aug 21 '20 at 21:48
  • 1
    It turns out that VLC player sends some kind of "tick" to the system in every 30 seconds while playing a video. As a solution, I discarded very small "not idle" signals: here – ceremcem Aug 21 '20 at 23:51
5

This is not quite what you asked for, but there is always the batch-command (usually a special invocation of the at-command and using the atd-daemon).

batch lets you cue-up a command to be run when the load-average drop below a certain limit (usually 1.5, but this can be set when starting atd). With at it's also possible to cue a job in such a way that rather than being run at a certain time; the job is just delivered to batchat that time, and first run when the load-average drops (eg. it's run as soon as the load-average drops under 1.5 sometime after 2:00am).

Unfortunately a batch-job will then run to it's end, it will not stop if the computer is no-longer idle.

+++

If you have to go the programming-route (which it looks like from the other answers), I think I'd try to make something similar to the atd or crond daemons, that monitored logged-in users and/or load-average. This daemon could then run scripts/programs from a certain directory, and start/continue or stop them (with signals) as needed be.

4

TMOUT in bash will terminate an interactive session after the set number of seconds. You may use that mechanism.

You migth capture the logout by setting an according trap (I did not test that), or by using the bash-logout-scripts (~/.bash_logout).

Here is a good superuser-answer into that direction.

Nils
  • 18,492
3

This thread has a couple of solutions based on detecting keyboard and mouse activity. I run xprintidle from a cron job which starts every few minutes. But as they point out xprintidle is unmaintained, so you may want to install the Perl module and use that instead:

$ cpanm X11::IdleTime
...
$ sleep 10; perl -MX11::IdleTime -le 'print GetIdleTime()'
9

I would use either solution by polling from cron, in a script which exits at the top if the UI hasn't been sufficiently idle. Of course the script would need an export DISPLAY=:0.0 in order to talk to X11.

Remember that your keyboard and mouse may be inactive when watching a movie, or running a CPU-heavy task.

Metamorphic
  • 1,179
2

I don't know of a way to do this without polling some sort of system stats, like the other answers use a screensaver or bash idle timer, or running from .bash_logout, but here's an idea to check CPU usage.

This would still involve polling every n seconds, and if your CPU usage is under whatever amount you choose then you can script whatever you want to run. However, whatever you run could raise the CPU usage, but you could use nice on your "stuff" to not count it.

Here's a test script using top, but you could use mpstat instead, or check load averages instead?

while true
do
idle=$(top -bn2 | grep "Cpu(s)"|tail -n 1|sed "s/.*, *\([0-9.]*\)%* id.*/\1/")
echo "idle is $idle"
if [[ $idle > 90 ]]
then
    echo "idle above 90%"
    echo "Do stuff now"
else
    echo "idle below 90%"
    echo "Stop doing stuff now"
fi
sleep 1
done

That's just a script I threw together to test out reading the idle from top. You could parse /proc/stat but I think it only shows total times, and you'd need to compare results over an interval. Top has it's own problem for me (linux mint 16), on the first run it seems to never change cpustats, as if it has to wait to parse /proc/stat itself, hence the top -bn2 but in theory top -bn1 should work.

Xen2050
  • 2,354