greatly simplify it to report idle seconds to a script

This commit is contained in:
Albert S. 2018-10-06 18:19:21 +02:00
parent acf64db071
commit 70b3e7eaa6
4 changed files with 80 additions and 398 deletions

24
README
View File

@ -1,4 +1,24 @@
afkcron afkcron
======= =======
afkcron reads a configuration which specifies programs to launch after n seconds of afk time. afkcron simply reports the idle time of the X11 server to a shell script.
Once the user is afk for those seconds, it then starts the program.
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.

451
afkcron.c
View File

@ -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. <launchutils at quitesimple dot org> * Copyright (c) 2014-2018 Albert S. <launchutils at quitesimple dot org>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -19,137 +19,17 @@
#define _XOPEN_SOURCE 700 #define _XOPEN_SOURCE 700
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <unistd.h> #include <unistd.h>
#include <signal.h> #include <signal.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdarg.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/extensions/scrnsaver.h> #include <X11/extensions/scrnsaver.h>
#include <ctype.h>
#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) bool strempty(const char *str)
{ {
return str == NULL || *str == '\0'; 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) void print_err_and_exit(const char *s)
{ {
fputs(s, stderr); 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) int get_idle_seconds(Display *display)
{ {
XScreenSaverInfo info; XScreenSaverInfo info;
if(XScreenSaverQueryInfo(display, DefaultRootWindow(display), &info) == 0) if(XScreenSaverQueryInfo(display, DefaultRootWindow(display), &info) == 0)
{
return -1; return -1;
}
return info.idle / 1000; 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) void child_handler(int signum, siginfo_t *info, void *context)
{ {
if(signum != SIGCHLD) if(signum != SIGCHLD)
{
return; return;
}
pid_t pid = info->si_pid;
//TODO
int status; int status;
int x = waitpid(pid, &status, WNOHANG); int ret = waitpid(info->si_pid, &status, WNOHANG);
if(x == -1) if(ret == -1)
print_err_and_exit("waitpid failed"); {
handle_finished_pid(pid); perror_and_exit("waitpid failed");
}
if(status == EXIT_FAILURE)
{
print_err_and_exit("script did not succeed, exiting");
}
} }
void set_signals() void set_signals()
{ {
struct sigaction action; struct sigaction action;
action.sa_flags = SA_NOCLDSTOP | SA_SIGINFO; action.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
action.sa_sigaction = &child_handler; action.sa_sigaction = &child_handler;
if(sigaction(SIGCHLD, &action, NULL) == -1) if(sigaction(SIGCHLD, &action, NULL) == -1)
print_err_and_exit("sigaction failed"); {
perror_and_exit("sigaction failed");
}
} }
void run_script(const char *path, const char *type, int sleepseconds)
bool run_entry(struct entry *e)
{ {
const char **args = create_execv_args(e->path, e->args, ' ');
pid_t child = fork(); pid_t child = fork();
if(child == 0) if(child == 0)
{ {
logit("Starting execution of %s\n", e->path); char sleepstr[20];
execv(e->path, (char * const *)args); snprintf(sleepstr, sizeof(sleepstr), "%i", sleepseconds);
fclose(logfp); const char *args[4];
} args[0] = path;
else if(child > 0) args[1] = type;
e->pid = child; args[2] = sleepstr;
return child != -1; args[3] = NULL;
} execv(path, (char * const *)args);
perror_and_exit("failed launching script");
void run_entries(int idle_seconds) }
{ if(child == -1)
for(struct entry *e = head_entry; e != NULL; e = e->next)
{ {
if(e->idle_seconds > idle_seconds) perror_and_exit("fork failed");
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;
}
} }
} }
int main(int argc, char *argv[]) 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); Display *display = XOpenDisplay(NULL);
if(display == NULL) if(display == NULL)
{
print_err_and_exit("Couldn't open DISPLAY"); print_err_and_exit("Couldn't open DISPLAY");
}
int event_base, error_base; int event_base, error_base;
if (! XScreenSaverQueryExtension(display, &event_base, &error_base)) if (! XScreenSaverQueryExtension(display, &event_base, &error_base))
{
print_err_and_exit("No XScreenSaver Extension available on this display"); print_err_and_exit("No XScreenSaver Extension available on this display");
}
set_signals(); 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; int previous_seconds = 0;
while(1) while(1)
{ {
int idle_seconds = get_idle_seconds(display); int idle_seconds = get_idle_seconds(display);
if(idle_seconds == -1) if(idle_seconds == -1)
{
print_err_and_exit("X11 Screen Saver Extension not supported?"); print_err_and_exit("X11 Screen Saver Extension not supported?");
}
if(previous_seconds > idle_seconds) if(previous_seconds > idle_seconds)
handle_comeback(); {
run_script(scriptpath, "active", idle_seconds);
}
else else
run_entries(idle_seconds); {
run_script(scriptpath, "idle", idle_seconds);
}
previous_seconds = 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));
} }
} }

View File

@ -1 +0,0 @@
/bin/touch:/tmp/works:kill:5:oneshot

2
script.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
echo "State is: $1. Been idling for $2"