commit ab90291f930c962204727527edb8a7aa7fb466f6 Author: Patrick Neff Date: Sat Apr 13 22:27:25 2024 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3af788c --- /dev/null +++ b/.gitignore @@ -0,0 +1,63 @@ +/compile_commands.json +/sleeptimer + +# Created by https://www.toptal.com/developers/gitignore/api/c +# Edit at https://www.toptal.com/developers/gitignore?templates=c + +### C ### +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# End of https://www.toptal.com/developers/gitignore/api/c + +/.cache diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..60cd870 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +PROGRAM = sleeptimer + +LDFLAGS = $(shell pkg-config --libs dbus-1 x11 xscrnsaver) +CFLAGS = --std=gnu17 -Wall -Werror -Wextra -Wpedantic -g $(shell pkg-config --cflags dbus-1 x11 xscrnsaver) + +objs = idle.o main.o dbus_wrapper.o suspend.o echo.o sleeptimer.o duration.o actions.o + +sleeptimer: $(objs) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(PROGRAM) $(objs) + +$(objs): + +clean: + rm $(PROGRAM) $(objs) diff --git a/actions.c b/actions.c new file mode 100644 index 0000000..585f762 --- /dev/null +++ b/actions.c @@ -0,0 +1,39 @@ +#include +#include + +#include "echo.h" +#include "stdio.h" +#include "suspend.h" + +#include "actions.h" + +const char *ACTION_ECHO = "echo"; +const char *ACTION_SUSPEND = "suspend"; + +enum Action parseAction(const char *action) { + enum Action a = ActionSuspend; + if (strcmp(action, ACTION_ECHO) == 0) { + a = ActionEcho; + } else if (strcmp(action, ACTION_SUSPEND) == 0) { + a = ActionSuspend; + } else { + a = ActionUnknown; + } + return a; +} + +actionCallback getActionCallback(enum Action action) { + actionCallback callback = suspend; + switch (action) { + case ActionSuspend: + callback = suspend; + break; + case ActionEcho: + callback = echo; + break; + default: + fprintf(stderr, "Unknown method\n"); + return NULL; + } + return callback; +} diff --git a/actions.h b/actions.h new file mode 100644 index 0000000..22d9e80 --- /dev/null +++ b/actions.h @@ -0,0 +1,11 @@ +#ifndef _ACTIONS_H +#define _ACTIONS_H + +#include + +typedef bool (*actionCallback)(void); +enum Action { ActionUnknown, ActionSuspend, ActionEcho }; +actionCallback getActionCallback(enum Action action); +enum Action parseAction(const char *action); + +#endif diff --git a/dbus_wrapper.c b/dbus_wrapper.c new file mode 100644 index 0000000..29dc92a --- /dev/null +++ b/dbus_wrapper.c @@ -0,0 +1,70 @@ +#include +#include +#include + +#include "dbus_wrapper.h" + +DBusError dbusError; + +DBusConnection *conn; + +void handleDbusError(DBusError *err, const char *message) { + if (dbus_error_is_set(err)) { + fprintf(stderr, "DBus Error - %s - %s\n", message, err->message); + dbus_error_free(err); + } +} + +DBusConnection *dbusConnect(void) { + dbus_error_init(&dbusError); + + if (NULL == conn) { + return conn; + } + + conn = dbus_bus_get(DBUS_BUS_SYSTEM, &dbusError); + if (dbus_error_is_set(&dbusError)) { + handleDbusError(&dbusError, "Could not connect"); + dbus_error_free(&dbusError); + } + if (NULL == conn) { + return NULL; + } + return conn; +} + +bool dbusCallMethod(DBusConnection *conn, const char *bus_name, + const char *path, const char *iface, const char *method) { + DBusMessage *msg; + DBusMessageIter args; + + msg = dbus_message_new_method_call(bus_name, path, iface, method); + if (NULL == msg) { + handleDbusError(&dbusError, "Could not initialize message"); + return false; + } + + // append arguments + dbus_message_iter_init_append(msg, &args); + int val = 0; + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_BOOLEAN, &val)) { + handleDbusError(&dbusError, "Could not append argument to method call"); + return false; + } + + if (!dbus_connection_send_with_reply_and_block( + conn, msg, -1, &dbusError)) { // -1 is default timeout + handleDbusError(&dbusError, "Could not send message"); + dbus_message_unref(msg); + return false; + } + + dbus_connection_flush(conn); + + // free message + dbus_message_unref(msg); + + dbus_connection_close(conn); + + return true; +} diff --git a/dbus_wrapper.h b/dbus_wrapper.h new file mode 100644 index 0000000..e81f89b --- /dev/null +++ b/dbus_wrapper.h @@ -0,0 +1,13 @@ +#ifndef _DBUS_WRAPPER_H +#define _DBUS_WRAPPER_H + +#include + +#include + +void handleDbusError(DBusError *err, const char *message); +DBusConnection *dbusConnect(void); +bool dbusCallMethod(DBusConnection *conn, const char *bus_name, const char *path, + const char *iface, const char *method); + +#endif // !_DBUS_WRAPPER_H diff --git a/duration.c b/duration.c new file mode 100644 index 0000000..0bceb2c --- /dev/null +++ b/duration.c @@ -0,0 +1,75 @@ +#include "duration.h" +#include "stdlib.h" +#include "string.h" +#include + +enum DurationParserState { + DURATION_PARSER_STATE_INITIAL, + DURATION_PARSER_STATE_NUMBER, + DURATION_PARSER_STATE_SECOND, + DURATION_PARSER_STATE_MINUTE, + DURATION_PARSER_STATE_HOUR, + DURATION_PARSER_STATE_EOS, +}; + +Duration parseDuration(const char *duration) { + enum DurationParserState state = DURATION_PARSER_STATE_INITIAL; + Duration d; + d.seconds = 0; + d.hours = 0; + d.minutes = 0; + char numBuf[32] = "0"; + for (const char *c = duration; *c; c++) { + if (state == DURATION_PARSER_STATE_INITIAL) { + if (*c >= '0' && *c <= '9') { + state = DURATION_PARSER_STATE_NUMBER; + } else if (*c == 's' || *c == 'S') { + state = DURATION_PARSER_STATE_SECOND; + } else if (*c == 'm' || *c == 'M') { + state = DURATION_PARSER_STATE_MINUTE; + } else if (*c == 'h' || *c == 'H') { + state = DURATION_PARSER_STATE_HOUR; + } + } + if (state == DURATION_PARSER_STATE_NUMBER) { + const char *start = c; + while (*c >= '0' && *c <= '9') { + numBuf[c - start] = *c; + c++; + } + c--; + state = DURATION_PARSER_STATE_INITIAL; + } else if (state == DURATION_PARSER_STATE_SECOND) { + d.seconds = atoi(numBuf); + strncpy(numBuf, "0", 32); + state = DURATION_PARSER_STATE_INITIAL; + } else if (state == DURATION_PARSER_STATE_MINUTE) { + d.minutes = atoi(numBuf); + strncpy(numBuf, "0", 32); + state = DURATION_PARSER_STATE_INITIAL; + } else if (state == DURATION_PARSER_STATE_HOUR) { + d.hours = atoi(numBuf); + strncpy(numBuf, "0", 32); + state = DURATION_PARSER_STATE_INITIAL; + } + } + return d; +} + +unsigned long durationToSeconds(const Duration *d) { + return d->hours * 3600 + d->minutes * 60 + d->seconds; +} + +Duration secondsToDuration(unsigned long seconds) { + Duration d; + d.hours = seconds / 3600; + d.minutes = (seconds % 3600) / 60; + d.seconds = seconds % 60; + return d; +} + +char* formatDuration(const Duration *d) { + char buf[32]; + sprintf(buf, "%02lu:%02lu:%02lu", d->hours, d->minutes, d->seconds); + return strdup(buf); +} diff --git a/duration.h b/duration.h new file mode 100644 index 0000000..9b3e873 --- /dev/null +++ b/duration.h @@ -0,0 +1,14 @@ +#ifndef _DURATION_H +#define _DURATION_H +typedef struct { + unsigned long seconds; + unsigned long minutes; + unsigned long hours; +} Duration; + +Duration parseDuration(const char *duration); +unsigned long durationToSeconds(const Duration *duration); +Duration secondsToDuration(unsigned long seconds); +char* formatDuration(const Duration *duration); + +#endif diff --git a/echo.c b/echo.c new file mode 100644 index 0000000..5cda73a --- /dev/null +++ b/echo.c @@ -0,0 +1,7 @@ +#include +#include + +bool echo(void) { + printf("Timer has ran out!\n"); + return true; +} diff --git a/echo.h b/echo.h new file mode 100644 index 0000000..790c2ed --- /dev/null +++ b/echo.h @@ -0,0 +1,8 @@ +#ifndef _ECHO_H +#define _ECHO_H + +#include + +bool echo(void); + +#endif diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..5647657 --- /dev/null +++ b/flake.lock @@ -0,0 +1,64 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1712791164, + "narHash": "sha256-3sbWO1mbpWsLepZGbWaMovSO7ndZeFqDSdX0hZ9nVyw=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "1042fd8b148a9105f3c0aca3a6177fd1d9360ba5", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1680978846, + "narHash": "sha256-Gtqg8b/v49BFDpDetjclCYXm8mAnTrUzR0JnE2nv5aw=", + "owner": "nix-systems", + "repo": "x86_64-linux", + "rev": "2ecfcac5e15790ba6ce360ceccddb15ad16d08a8", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "x86_64-linux", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c1f6f9a --- /dev/null +++ b/flake.nix @@ -0,0 +1,57 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + systems.url = "github:nix-systems/x86_64-linux"; + flake-utils = { + url = "github:numtide/flake-utils"; + inputs.systems.follows = "systems"; + }; + }; + outputs = { + nixpkgs, + flake-utils, + ... + }: let + drv = { + stdenv, + lib, + libX11, + libXScrnSaver, + dbus, + pkg-config, + ... + }: + stdenv.mkDerivation { + pname = "sleeptimer"; + version = "0.0.1"; + src = lib.cleanSource ./.; + nativeBuildInputs = [pkg-config]; + buildInputs = [libX11 libXScrnSaver dbus.dev]; + installPhase = '' + mkdir -p $out/bin + cp sleeptimer $out/bin + ''; + }; + in + flake-utils.lib.eachDefaultSystem (system: let + pkgs = import nixpkgs { + inherit system; + }; + llvm = pkgs.llvmPackages_latest; + package = pkgs.callPackage drv {inherit (llvm) stdenv;}; + in { + devShells.default = pkgs.mkShell.override {inherit (llvm) stdenv;} { + inputsFrom = [package]; + packages = with pkgs; [ + bear + clang-tools + zlib + lzma + icu74 + ]; + }; + packages = { + default = package; + }; + }); +} diff --git a/idle.c b/idle.c new file mode 100644 index 0000000..7c28b5d --- /dev/null +++ b/idle.c @@ -0,0 +1,32 @@ +#include + +#include +#include + +/* Get the idle time from the X Screen Saver extionsion. + * + * Returns: + * long: Idle time in milliseconds + */ +Display *xorgDisplay; +XScreenSaverInfo *xorgScreensaverInfo; + +unsigned long getIdleTime(void) { + unsigned long idle = 0; + if (NULL == xorgDisplay) { + xorgDisplay = XOpenDisplay(NULL); + } + + if (!xorgDisplay) { + return idle; + } + + if (NULL == xorgScreensaverInfo) { + xorgScreensaverInfo = XScreenSaverAllocInfo(); + } + XScreenSaverQueryInfo(xorgDisplay, DefaultRootWindow(xorgDisplay), xorgScreensaverInfo); + + idle = xorgScreensaverInfo->idle; + + return idle; +} diff --git a/idle.h b/idle.h new file mode 100644 index 0000000..83e18e6 --- /dev/null +++ b/idle.h @@ -0,0 +1,6 @@ +#ifndef _IDLE_H +#define _IDLE_H + +long getIdleTime(void); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..8233891 --- /dev/null +++ b/main.c @@ -0,0 +1,32 @@ +#include +#include + +#include "actions.h" +#include "duration.h" +#include "sleeptimer.h" +#include "stdio.h" + +int main(int argc, char **argv) { + time_t start = time(NULL); + + Duration duration = {.minutes = 30}; + if (argc >= 2) { + duration = parseDuration(argv[1]); + } + + enum Action action = ActionEcho; + if (argc >= 3) { + action = parseAction(argv[2]); + } + + printf("Sleep timer set for \"%s\"\n", formatDuration(&duration)); + + unsigned long seconds = durationToSeconds(&duration); + actionCallback callback = getActionCallback(action); + if (!sleeptimer(seconds, callback)) { + return 1; + } + time_t end = time(NULL); + Duration runtime = secondsToDuration(end - start); + printf("Program ran for \"%s\"\n", formatDuration(&runtime)); +} diff --git a/sleeptimer.c b/sleeptimer.c new file mode 100644 index 0000000..363efa9 --- /dev/null +++ b/sleeptimer.c @@ -0,0 +1,21 @@ +#include "actions.h" +#include "idle.h" +#include +#include +#include + +bool sleeptimer(unsigned long seconds, actionCallback callback) { + unsigned long millis = seconds * 1000; + unsigned long idleTime = getIdleTime(); + + struct timespec sleepTime = {.tv_sec = 0, .tv_nsec = 100 * 1000000}; + while (idleTime < millis) { + idleTime = getIdleTime(); + nanosleep(&sleepTime, NULL); + } + + if (!(*callback)()) { + return false; + } + return true; +} diff --git a/sleeptimer.h b/sleeptimer.h new file mode 100644 index 0000000..0a9b8f2 --- /dev/null +++ b/sleeptimer.h @@ -0,0 +1,9 @@ +#ifndef __SLEEPTIMER_H +#define __SLEEPTIMER_H + +#include "actions.h" +#include + +bool sleeptimer(unsigned long seconds, actionCallback callback); + +#endif diff --git a/suspend.c b/suspend.c new file mode 100644 index 0000000..2ffa0c6 --- /dev/null +++ b/suspend.c @@ -0,0 +1,16 @@ +#include "dbus_wrapper.h" + +bool suspend(void) { + const char *bus_name = "org.freedesktop.login1"; + const char *path = "/org/freedesktop/login1"; + const char *iface = "org.freedesktop.login1.Manager"; + const char *method = "Suspend"; + + DBusConnection *conn = dbusConnect(); + if (NULL == conn) { + return false; + } + + dbusCallMethod(conn, bus_name, path, iface, method); + return true; +} diff --git a/suspend.h b/suspend.h new file mode 100644 index 0000000..6c4f7bc --- /dev/null +++ b/suspend.h @@ -0,0 +1,5 @@ +#ifndef _SUSPEND_H +#define _SUSPEND_H +#include +bool suspend(void); +#endif