From 70b3e7eaa6eeea6d1d9f47e9051826211cf485f3 Mon Sep 17 00:00:00 2001 From: Albert S Date: Sat, 6 Oct 2018 18:19:21 +0200 Subject: [PATCH] greatly simplify it to report idle seconds to a script --- README | 24 ++- afkcron.c | 451 ++++++-------------------------------------- afkcron_config.conf | 1 - script.sh | 2 + 4 files changed, 80 insertions(+), 398 deletions(-) delete mode 100644 afkcron_config.conf create mode 100755 script.sh diff --git a/README b/README index d0ef7ee..e8a1acb 100644 --- a/README +++ b/README @@ -1,4 +1,24 @@ afkcron ======= -afkcron reads a configuration which specifies programs to launch after n seconds of afk time. -Once the user is afk for those seconds, it then starts the program. +afkcron simply reports the idle time of the X11 server to a shell script. + +Originally, it was reading config files and also handling the return of the +user by decreasing priority of launched processes or killing them etc. + +Now I considered it's best for it to be rather stupid and delegate this +to a shell script for example. + +Launching afkcron +================= +afkcron [path to script] [how often idle time should be polled, in seconds] + +Example: afkcron script.sh 30 + +Shellscript +=========== +First argument: "active" (if idle time has stopped), "idle": When idling +Second argument: the number of seconds X11 is idling. + +Dependencies +============ +libX11, libXss. Should be a given. diff --git a/afkcron.c b/afkcron.c index a80db92..407957e 100644 --- a/afkcron.c +++ b/afkcron.c @@ -1,7 +1,7 @@ /* - * afkcron launches a program depending on the time the user is not using his machine + * afkcron launches a script depending on the time the user is not using his machine * - * Copyright (c) 2014-2017 Albert S. + * Copyright (c) 2014-2018 Albert S. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -19,137 +19,17 @@ #define _XOPEN_SOURCE 700 #include #include -#include #include #include #include -#include -#include -#include -#include -#include -#include #include #include -#include - -#define CONFIG_DEFAULT_PATH "/etc/afkcron" -#define CONFIG_DELIMER ':' - -//return actions flags -#define FLAG_RETURN_KILL (1 << 0) -#define FLAG_RETURN_PRIO (1 << 1) -#define FLAG_RETURN_STOP (1 << 2) -#define FLAG_RETURN_STAY (1 << 3) -#define FLAG_RETURN_TERM (1 << 4) -#define FLAG_RETURN_TERMKILL (FLAG_RETURN_TERM | FLAG_RETURN_KILL) -//general flags -#define FLAG_SINGLE_SHOT (1 << 0) // fire only once during program lifetime - -//consider merging return action flags and general flags -struct entry -{ - pid_t pid; // 0 = can be ran, -1 single shot entry which finished, pid > 0 = currently running - bool stopped; - const char *path; - const char *args; - int comeback_action; //What to do, when the user returns, starts using the computer again? - int idle_seconds; - int flags; - struct entry *next; -}; - -FILE *logfp = NULL; - -struct entry *head_entry = NULL; -struct entry *tail_entry = NULL; - -void *xmalloc(size_t n) -{ - char *tmp = malloc(n); - if(tmp == NULL) - { - perror("malloc"); - exit(EXIT_FAILURE); - } - return tmp; -} - -void *xstrdup(const char *str) -{ - char *result = strdup(str); - if(result == NULL) - { - perror("strdup"); - exit(EXIT_FAILURE); - } - return result; -} bool strempty(const char *str) { return str == NULL || *str == '\0'; } -int strcnt(const char *str, const char c) -{ - int result = 0; - while(*str) - if(*str++ == c) ++result; - return result; -} - -int split_string(char ***out, const char *str, char delim) -{ - int current = 0; - int items = strcnt(str, delim) + 1; - *out = xmalloc(items * sizeof(char *)); - char *temp = xstrdup(str); - char *portion = temp; - while(*temp) - { - if(*temp == delim) - { - *temp=0; - (*out)[current] = portion; - ++current; - portion = temp+1; - } - ++temp; - } - (*out)[current] = portion; - return ++current; -} - -const char **create_execv_args(const char *name, const char *str, char delim) -{ - if(strempty(name)) - return NULL; - const char **result = NULL; - if(strempty(str)) - { - result = xmalloc(2 * sizeof(char *)); - result[0] = name; - result[1] = NULL; - } - else - { - int items=2; - char **args = NULL; - int n_args = split_string(&args, str, delim); - items += n_args; - result = xmalloc(items * sizeof(char *)); - result[0] = name; - for(int i=0; i < n_args; i++) - result[i+1] = args[i]; - result[items-1] = NULL; - } - return result; - -} - - - void print_err_and_exit(const char *s) { fputs(s, stderr); @@ -163,335 +43,116 @@ void perror_and_exit(const char *s) } -void logit(const char *format, ...) -{ - if(logfp != NULL) - { - va_list args; - va_start(args, format); - time_t now = time(0); - char *timestr = ctime(&now); - size_t len = strlen(timestr); - if(timestr[len-1] == '\n') - { - timestr[len-1] = 0; - } - fprintf(logfp, "%s: ", timestr); - vfprintf(logfp, format, args); - fflush(logfp); - va_end(args); - } -} int get_idle_seconds(Display *display) { XScreenSaverInfo info; if(XScreenSaverQueryInfo(display, DefaultRootWindow(display), &info) == 0) + { return -1; + } return info.idle / 1000; } -void add_entry(struct entry *e) -{ - if(head_entry == NULL) - head_entry = e; - if(tail_entry != NULL) - tail_entry->next = e; - tail_entry = e; -} - - -int comeback_action_from_string(const char *str) -{ - int result = 0; - if(! strempty(str)) - { - if(strcmp(str, "kill") == 0) - result |= FLAG_RETURN_KILL; - else if(strcmp(str, "stop") == 0) - result |= FLAG_RETURN_STOP; - else if(strcmp(str, "prio") == 0) - result |= FLAG_RETURN_PRIO; - else if(strcmp(str, "term") == 0) - result |= FLAG_RETURN_TERM; - else if(strcmp(str, "termkill") == 0) - result |= FLAG_RETURN_TERMKILL; - else - result |= FLAG_RETURN_STAY; - } - return result; -} - -int flags_from_string(const char *str) -{ - int result = 0; - if(strstr(str, "oneshot")) - result |= FLAG_SINGLE_SHOT; - return result; -} -//TODO: check integer overflow -int secs_from_string(char *unitstr) -{ - if(unitstr == NULL) - return -1; - size_t len = strlen(unitstr); - char unit = unitstr[len-1]; - if(! isdigit(unit)) - unitstr[len-1] = '\0'; - - int secs = atoi(unitstr); - if(secs < 0) - return -1; - - switch(unit) - { - case 'd': - secs *= 24; - case 'h': - secs *= 60; - case 'm': - secs *= 60; - } - - return secs; -} - -struct entry *entry_from_line(const char *line) -{ - char *l = xstrdup(line); - - char **fields = NULL; - int n_fields = split_string(&fields, l, CONFIG_DELIMER); - if(n_fields < 5) - return NULL; - - struct entry *result = xmalloc(sizeof(struct entry)); - result->path = fields[0]; - result->args = fields[1]; - result->comeback_action = comeback_action_from_string(fields[2]); - result->idle_seconds = secs_from_string(fields[3]); - result->flags = flags_from_string(fields[4]); - return result; - -} - - -static inline bool check_entry(struct entry *e) -{ - return ( ! strempty(e->path) ) && ( e->idle_seconds > 0 ) && ( e->comeback_action > 0 ); -} - -void read_config(const char *configfile) -{ - FILE *fp = fopen(configfile, "r"); - if(fp == NULL) - { - perror_and_exit("fopen"); - } - char *line; - size_t n = 0; - ssize_t r; - while((r = getline(&line, &n, fp)) != -1 ) - { - if(line[r-1] == '\n') - line[r-1] = '\0'; - - struct entry *e = entry_from_line(line); - if(e == NULL) - print_err_and_exit("error reading from file"); - - if(! check_entry(e)) - print_err_and_exit("Invalid values for entry"); - - add_entry(e); - - - } - - if(ferror(fp)) - perror_and_exit("error reading from config file"); - fclose(fp); -} - -void handle_comeback() -{ - for(struct entry *current = head_entry; current != NULL; current = current->next) - { - if(current->pid > 0) - { - int comeback_action = current->comeback_action; - bool wants_term = (comeback_action & FLAG_RETURN_TERM) == FLAG_RETURN_TERM; - - if(wants_term) - { - logit("Terminating %z\n", current->pid); - kill(current->pid, SIGTERM); - } - - if(comeback_action & FLAG_RETURN_KILL) - { - if(wants_term) - while(sleep(2)); - if(kill(current->pid, SIGKILL) == 0) - { - logit("Killed %z\n", current->pid); - current->pid = 0; - } - //TODO: and if fail?... - } - if(comeback_action & FLAG_RETURN_STOP) - { - if(kill(current->pid, SIGSTOP) == 0) - { - logit("Stopped %z\n", current->pid); - current->stopped = true; - //TODO: ... - } - } - - if(comeback_action & FLAG_RETURN_PRIO) - { - logit("Lowering priority for %z\n", current->pid); - setpriority(PRIO_PROCESS, current->pid, 19); - //TODO: IO prio? - } - } - - } -} - -void handle_finished_pid(pid_t pid) -{ - for(struct entry *current = head_entry; current != NULL; current = current->next) - { - if(current->pid == pid) - { - if( (current->flags & FLAG_SINGLE_SHOT) ) - { - current->pid = -1; - } - else - current->pid = 0; - } - } -} - - void child_handler(int signum, siginfo_t *info, void *context) { if(signum != SIGCHLD) + { return; - - pid_t pid = info->si_pid; - //TODO + } int status; - int x = waitpid(pid, &status, WNOHANG); - if(x == -1) - print_err_and_exit("waitpid failed"); - handle_finished_pid(pid); - - + int ret = waitpid(info->si_pid, &status, WNOHANG); + if(ret == -1) + { + perror_and_exit("waitpid failed"); + } + if(status == EXIT_FAILURE) + { + print_err_and_exit("script did not succeed, exiting"); + } + } + void set_signals() { struct sigaction action; action.sa_flags = SA_NOCLDSTOP | SA_SIGINFO; action.sa_sigaction = &child_handler; if(sigaction(SIGCHLD, &action, NULL) == -1) - print_err_and_exit("sigaction failed"); + { + perror_and_exit("sigaction failed"); + } } - -bool run_entry(struct entry *e) +void run_script(const char *path, const char *type, int sleepseconds) { - const char **args = create_execv_args(e->path, e->args, ' '); pid_t child = fork(); if(child == 0) { - logit("Starting execution of %s\n", e->path); - execv(e->path, (char * const *)args); - fclose(logfp); - } - else if(child > 0) - e->pid = child; - return child != -1; -} + char sleepstr[20]; + snprintf(sleepstr, sizeof(sleepstr), "%i", sleepseconds); + const char *args[4]; + args[0] = path; + args[1] = type; + args[2] = sleepstr; + args[3] = NULL; + execv(path, (char * const *)args); + perror_and_exit("failed launching script"); -void run_entries(int idle_seconds) -{ - for(struct entry *e = head_entry; e != NULL; e = e->next) + } + if(child == -1) { - if(e->idle_seconds > idle_seconds) - continue; - if(e->pid == 0) - run_entry(e); - if(e->pid > 0 && e->stopped) - { - logit("Continuing %z\n", e->pid); - if(kill(e->pid, SIGCONT) == 0) - e->stopped = false; - - } - + perror_and_exit("fork failed"); } } - - int main(int argc, char *argv[]) { + if(argc < 3) + { + printf("Usage: afkcron script.sh interval\n"); + exit(EXIT_FAILURE); + } + + const char *scriptpath = argv[1]; + unsigned int interval = strtol(argv[2], NULL, 10); + Display *display = XOpenDisplay(NULL); if(display == NULL) + { print_err_and_exit("Couldn't open DISPLAY"); - + } int event_base, error_base; if (! XScreenSaverQueryExtension(display, &event_base, &error_base)) + { print_err_and_exit("No XScreenSaver Extension available on this display"); - + } set_signals(); - - int option; - while((option = getopt(argc, argv, "c:l:")) != -1) - { - switch(option) - { - case 'c': - read_config(optarg); - break; - case 'l': - logfp = fopen(optarg, "a"); - if(logfp == NULL) - { - print_err_and_exit("Error opening log file"); - } - break; - - } - } - if(head_entry == NULL) - { - read_config(CONFIG_DEFAULT_PATH); - } int previous_seconds = 0; while(1) { int idle_seconds = get_idle_seconds(display); if(idle_seconds == -1) + { print_err_and_exit("X11 Screen Saver Extension not supported?"); - + } + if(previous_seconds > idle_seconds) - handle_comeback(); + { + run_script(scriptpath, "active", idle_seconds); + } else - run_entries(idle_seconds); - + { + run_script(scriptpath, "idle", idle_seconds); + } previous_seconds = idle_seconds; - while(sleep(10)); // Well this sleep approach is suboptimal, do it with events somehow if possible. + while(sleep(interval)); } } diff --git a/afkcron_config.conf b/afkcron_config.conf deleted file mode 100644 index 4f387bb..0000000 --- a/afkcron_config.conf +++ /dev/null @@ -1 +0,0 @@ -/bin/touch:/tmp/works:kill:5:oneshot diff --git a/script.sh b/script.sh new file mode 100755 index 0000000..1339ef4 --- /dev/null +++ b/script.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo "State is: $1. Been idling for $2"