greatly simplify it to report idle seconds to a script

这个提交包含在:
Albert S. 2018-10-06 18:19:21 +02:00
父节点 acf64db071
当前提交 70b3e7eaa6
共有 4 个文件被更改,包括 80 次插入398 次删除

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
查看文件

@ -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 可执行文件
查看文件

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