Executar script após período de inatividade

Executar script após período de inatividade

Estou procurando uma maneira de executar determinado script após um período de inatividade e após o término desse período. Por 'inatividade' quero dizer falta de eventos de mouse e teclado. Por exemplo, eu quero:

  1. execute o primeiro script após X min. de inatividade;

  2. execute o segundo script quando esse período for interrompido pelo mouse ou teclado.

Será melhor se esse método não estiver vinculado ao sistema X e também funcionar no terminal (quando o X não for iniciado).

Responder1

Você poderia fazer isso com um pouco de magia C (qualquer outra linguagem de programação que suporte as funções necessárias também servirá). O que você terá que fazer é:

  • abra todos inputos dispositivos de evento (tudo correspondente /dev/input/event[0-9]*)
  • ligou select(2)para esses dispositivos, aguardando entrada, com um tempo limite apropriado (seu período ocioso)
    • no tempo limite: nada aconteceu: inicie seu programa
    • se a entrada ficar pronta: algo aconteceu: elimine seu programa, se estiver em execução
  • read(2)entrada de todos os dispositivos, para que a próxima select(2)chamada não retorne imediatamente

Um exemplo rápido em C ficaria assim:

#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 exemplo esperaria pela entrada indefinidamente. Se após 5 segundos nenhuma entrada for recebida, será impresso "Timeout: …", se a entrada for recebida "No timeout: …"

Para ler mais (no entanto, você deseja executar e encerrar processos), consulte e fork(2), respectivamente. Como mencionado anteriormente, qualquer linguagem que permita a execução de arquivos seria suficiente, então você também pode fazer isso em Python ou Ruby ou algo parecido.exec(2)kill(2)select(2)

Observação: Este é apenas um exemplo, haveria coisas adicionais a serem cuidadas. Por exemplo, isso imprime "Timeout"todo5 segundos, não apenas uma vez até que a entrada seja recebida, da mesma forma, "Sem tempo limite" aparecetodopressionamento de tecla/movimento do mouse.

Além disso, isso precisaria ser executado como root, já que os inputdispositivos de evento não podem ser lidos por ninguém por motivos óbvios.

Responder2

Aqui está um aplicativo C que encontrei e que você pode 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);
    }
}

São necessárias algumas bibliotecas para construir. No meu sistema Fedora 19 eu precisava das seguintes 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

Depois que eles foram instalados, compilei o acima assim:

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

Você pode ver que ele é capaz de relatar o número de segundos que o X está detectando como tempo ocioso executando-o da seguinte forma:

$ 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

Usando este executável você pode montar um script que agora pode saber quanto tempo ocioso passou. Se for igual à janela de tempo em #1, execute seu primeiro script. Se essa janela inativa for interrompida (ponto 2 da sua pergunta), execute seu segundo script.

Um pequeno exemplo

O exemplo abaixo mostra como você pode construir pelo menos a primeira parte do seu cheque, digamos que 5 segundos se passem.

$ 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

Acima, não movi o mouse nem apertei uma tecla por mais de 5 segundos. Em seguida, pressionei a Shifttecla e o loop voltou para still < 5.

Referências

informação relacionada