diff --git a/.gitignore b/.gitignore index 02fdd3f..ca49fe6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ runwhenidle package-build/ +*.o \ No newline at end of file diff --git a/Makefile b/Makefile index 7494f14..2710777 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,8 @@ CC=gcc ifeq ($(PREFIX),) PREFIX := /usr endif +SOURCES = sleep_utils.c main.c +OBJECTS = $(SOURCES:.c=.o) all: executable @@ -13,15 +15,18 @@ release: executable debug: CCFLAGS += -DDEBUG -ggdb debug: executable -executable: - $(CC) main.c -o $(TARGET_EXEC) $(CCFLAGS) $(LDFLAGS) $(LDLIBS) +%.o: %.c + $(CC) $(CCFLAGS) -c $< -o $@ $(LDFLAGS) $(LDLIBS) + +executable: $(OBJECTS) + $(CC) $(CCFLAGS) $(OBJECTS) -o $(TARGET_EXEC) $(LDFLAGS) $(LDLIBS) install: release install -d $(DESTDIR)$(PREFIX)/bin/ install -m 755 $(TARGET_EXEC) $(DESTDIR)$(PREFIX)/bin/ clean: - rm -f runwhenidle + rm -f $(OBJECTS) $(TARGET_EXEC) debian-package: docker build --build-arg HOST_UID=`id -u` --tag runwhenidle-ubuntu2204-build distro-packages/ubuntu22.04 diff --git a/main.c b/main.c index 83628bc..47a7b5e 100644 --- a/main.c +++ b/main.c @@ -7,6 +7,8 @@ #include #include +#include "sleep_utils.h" + int verbose; int quiet; @@ -68,6 +70,17 @@ pid_t run_shell_command(const char *shell_command_to_run, pid_t pid) { return pid; } +void exit_if_pid_has_finished(pid_t pid) { + int status; + if (waitpid(pid, &status, WNOHANG + WUNTRACED) == pid && WIFEXITED(status)) { + int exit_code = WEXITSTATUS(status); + if (verbose) { + fprintf(stderr, "PID %i has finished with exit code %u\n", pid, exit_code); + } + exit(exit_code); + } +} + int main(int argc, char *argv[]) { pid_t pid; int user_idle_timeout_ms = 300000; @@ -120,14 +133,23 @@ int main(int argc, char *argv[]) { pid = run_shell_command(argv[optind], pid); + // Let command run for 300ms to give it a chance to error-out or provide initial output. + // 300ms is chosen to avoid giving user a noticeable delay while giving most quick commands a chance to finish. + sleep_for_milliseconds(300); + int polling_interval_seconds = 1; int sleep_time_seconds = polling_interval_seconds; int command_paused = 0; + // Monitor user activity while (1) { - sleep(sleep_time_seconds); XScreenSaverQueryInfo(dpy, DefaultRootWindow(dpy), info); + // Checking this after querying the screensaver timer so that the command is still running while + // we're querying the screensaver and has a chance to do some work and finish, + // but before potentially pausing the command to avoid trying to pause it if it completed. + exit_if_pid_has_finished(pid); + if (info->idle > user_idle_timeout_ms) { // User is inactive if (command_paused) { @@ -159,15 +181,6 @@ int main(int argc, char *argv[]) { sleep_time_seconds); } } - - // Check if the command has finished - int status; - if (waitpid(pid, &status, WNOHANG + WUNTRACED) == pid && WIFEXITED(status)) { - int exit_code = WEXITSTATUS(status); - if (verbose) { - fprintf(stderr, "Command has finished with exit code %u\n", exit_code); - } - return exit_code; - } + sleep(sleep_time_seconds); } } diff --git a/sleep_utils.c b/sleep_utils.c new file mode 100644 index 0000000..b3d6b7f --- /dev/null +++ b/sleep_utils.c @@ -0,0 +1,20 @@ +#include +#include + +int sleep_for_milliseconds(long milliseconds) +{ + struct timespec ts; + + if (milliseconds < 0) + { + errno = EINVAL; + return -1; + } + + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = (milliseconds % 1000) * 1000000; + + //We don't care about sleeping for exact amount in case sleep is interrupted by a signal, + //which is why NULL is used for the second argument + return nanosleep(&ts, NULL); +} diff --git a/sleep_utils.h b/sleep_utils.h new file mode 100644 index 0000000..4eeaf59 --- /dev/null +++ b/sleep_utils.h @@ -0,0 +1,15 @@ +#ifndef SLEEP_UTILS_H +#define SLEEP_UTILS_H + +#include +#include + +/** + * Sleeps for the specified number of milliseconds unless interrupted by a signal. + * + * @param milliseconds The number of milliseconds to sleep. + * @return 0 on success, -1 on failure or being interrupted by a signal (sets errno to EINVAL if milliseconds is negative). + */ +int sleep_for_milliseconds(long milliseconds); + +#endif /* SLEEP_UTILS_H */