greatly simplify it to report idle seconds to a script
This commit is contained in:
rodzic
acf64db071
commit
70b3e7eaa6
24
README
24
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.
|
||||
|
451
afkcron.c
451
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. <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
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
@ -19,137 +19,17 @@
|
||||
#define _XOPEN_SOURCE 700
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.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/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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
/bin/touch:/tmp/works:kill:5:oneshot
|
2
script.sh
Executable file
2
script.sh
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
echo "State is: $1. Been idling for $2"
|
Ładowanie…
Reference in New Issue
Block a user