Ejecutar script después de un período de inactividad

Ejecutar script después de un período de inactividad

Estoy buscando una manera de ejecutar cierto script después de un período de inactividad y después de completar ese período. Por "inactividad" me refiero a la falta de eventos de mouse y teclado. Por ejemplo, quiero:

  1. ejecutar el primer script después de X min. de inactividad;

  2. ejecutar el segundo script cuando ese período sea interrumpido por el mouse o el teclado.

Será mejor si ese método no estará vinculado al sistema X y también funcionará en la terminal (cuando X no esté iniciado).

Respuesta1

Podrías hacer esto con un poco de magia en C (cualquier otro lenguaje de programación que soporte las funciones necesarias también estará bien). Lo que tendrás que hacer es:

  • abrir todos inputlos dispositivos de eventos (todos los que coincidan /dev/input/event[0-9]*)
  • ha select(2)llamado a esos dispositivos, esperando entrada, con un tiempo de espera apropiado (su período de inactividad)
    • en el tiempo de espera: no pasó nada: inicie su programa
    • si la entrada está lista: algo sucedió: finalice su programa, si se está ejecutando
  • read(2)entrada desde todos los dispositivos, por lo que la siguiente select(2)llamada no volverá inmediatamente

Un ejemplo rápido en C se vería así:

#include <sys/select.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <glob.h>
#include <stdlib.h>
#include <stdio.h>

int
main(int argc, char *argv[])
{
    int *fds, ret, i;
    glob_t glob_result;

    /* find all devices matching /dev/input/event[0-9]* */
    ret = glob("/dev/input/event[0-9]*", GLOB_ERR|GLOB_NOSORT|GLOB_NOESCAPE, NULL, &glob_result);
    if (ret)
        err(EXIT_FAILURE, "glob");

    /* allocate array for opened file descriptors */
    fds = malloc(sizeof(*fds) * (glob_result.gl_pathc+1));
    if (fds == NULL)
        err(EXIT_FAILURE, "malloc");

    /* open devices */
    for (i = 0; i < glob_result.gl_pathc; i++) {
        fds[i] = open(glob_result.gl_pathv[i], O_RDONLY|O_NONBLOCK);
        if (fds[i] == -1)
            err(EXIT_FAILURE, "open `%s'", glob_result.gl_pathv[i]);
    }

    fds[i] = -1; /* end of array */

    for (;;) {
        char buf[512];
        struct timeval timeout;
        fd_set readfds;
        int nfds = -1;

        FD_ZERO(&readfds);

        /* select(2) might alter the fdset, thus freshly set it
           on every iteration */
        for (i = 0; fds[i] != -1; i++) {
            FD_SET(fds[i], &readfds);
            nfds = fds[i] >= nfds ? fds[i] + 1 : nfds;

            /* read everything what's available on this fd */
            while ((ret = read(fds[i], buf, sizeof(buf))) > 0)
                continue; /* read away input */
            if (ret == -1 && errno != EAGAIN)
                err(EXIT_FAILURE, "read");
        }

        /* same for timeout, 5 seconds here */
        timeout.tv_sec = 5;    /* FIXME */
        timeout.tv_usec = 0;

        ret = select(nfds, &readfds, NULL, NULL, &timeout);
        if (ret == -1)
            err(EXIT_FAILURE, "select");
        if (ret == 0)
            printf("Timeout: start first script\n");
        } else {
            printf("No timeout: start second script\n");
        }
    }

    return 0;
}

Este ejemplo esperaría indefinidamente la entrada. Si después de 5 segundos no se recibe ninguna entrada, imprimirá "Tiempo de espera:...", si se recibe la entrada "Sin tiempo de espera:..."

Para obtener más información (aunque desea ejecutar y finalizar procesos), consulte fork(2)y exec(2)respectivamente kill(2). Como se mencionó anteriormente, cualquier lenguaje que permita ejecutar select(2)archivos sería suficiente, por lo que también podría hacerlo en Python o Ruby o algo similar.

Nota: Este es sólo un ejemplo, habría cosas adicionales de las que ocuparse. Por ejemplo, esto imprime "Tiempo de espera".cada5 segundos, no solo una vez hasta que se recibe la entrada, de manera similar se enciende "Sin tiempo de espera"cadapulsación de tecla/movimiento del ratón.

Además, esto debería ejecutarse como root, ya que inputnadie puede leer los dispositivos de eventos por razones obvias.

Respuesta2

Aquí hay una aplicación C que encontré y que puedes compilar.

$ more xidle.c 
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/extensions/scrnsaver.h>

/* Report amount of X server idle time. */
/* Build with- */
/* cc xidle.c -o xidle -lX11 -lXext -lXss */


int main(int argc, char *argv[])
{
    Display *display;
    int event_base, error_base;
    XScreenSaverInfo info;
    float seconds;

    display = XOpenDisplay("");

    if (XScreenSaverQueryExtension(display, &event_base, &error_base)) {
    XScreenSaverQueryInfo(display, DefaultRootWindow(display), &info);

    seconds = (float)info.idle/1000.0f;
    printf("%f\n",seconds);
    return(0);
    }
    else {
    fprintf(stderr,"Error: XScreenSaver Extension not present\n");
    return(1);
    }
}

Necesita un par de bibliotecas para construir. En mi sistema Fedora 19 necesitaba las siguientes bibliotecas:

$ rpm -qf /lib64/libX11.so.6 /lib64/libXext.so.6 /lib64/libXss.so.1
libX11-1.6.0-1.fc19.x86_64
libXext-1.3.2-1.fc19.x86_64
libXScrnSaver-1.2.2-5.fc19.x86_64

Una vez instalados, compilé lo anterior de esta manera:

$ gcc xidle.c -o xidle -lX11 -lXext -lXss

Puede ver que es capaz de informar la cantidad de segundos que X detecta como tiempo de inactividad ejecutándolo así:

$ while [ 1 ]; do ./xidle ; sleep 2;done
0.005000
1.948000
3.954000
5.959000
7.965000
0.073000   <--- moved the mouse here which resets it
0.035000

Con este ejecutable, podría crear un script que ahora pueda saber cuánto tiempo de inactividad ha transcurrido. Si es igual a la ventana de tiempo en el n.° 1, ejecute su primer script. Si esa ventana inactiva se interrumpe (punto 2 de su pregunta), ejecute su segundo script.

un pequeño ejemplo

A continuación se muestra cómo podría construir al menos la primera parte de su cheque, si pasan, digamos, 5 segundos.

$ while [ 1 ]; do 
  idle=$(./xidle); 
  [ $( echo "$idle > 5" | bc ) -eq 0 ] && echo "still < 5" || echo "now > 5";
  sleep 2;
done
still < 5
still < 5
still < 5
now > 5
now > 5
still < 5
still < 5

En lo anterior no moví el mouse ni presioné una tecla durante más de 5 segundos. Luego presioné la Shifttecla y el bucle volvió a cambiar still < 5.

Referencias

información relacionada