From ab90291f930c962204727527edb8a7aa7fb466f6 Mon Sep 17 00:00:00 2001 From: Patrick Neff Date: Sat, 13 Apr 2024 22:27:25 +0200 Subject: [PATCH] initial commit --- .gitignore | 63 ++++++++++++++++++++++++++++++++++++++++++ Makefile | 14 ++++++++++ actions.c | 39 ++++++++++++++++++++++++++ actions.h | 11 ++++++++ dbus_wrapper.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++ dbus_wrapper.h | 13 +++++++++ duration.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ duration.h | 14 ++++++++++ echo.c | 7 +++++ echo.h | 8 ++++++ flake.lock | 64 ++++++++++++++++++++++++++++++++++++++++++ flake.nix | 57 ++++++++++++++++++++++++++++++++++++++ idle.c | 32 +++++++++++++++++++++ idle.h | 6 ++++ main.c | 32 +++++++++++++++++++++ sleeptimer.c | 21 ++++++++++++++ sleeptimer.h | 9 ++++++ suspend.c | 16 +++++++++++ suspend.h | 5 ++++ 19 files changed, 556 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 actions.c create mode 100644 actions.h create mode 100644 dbus_wrapper.c create mode 100644 dbus_wrapper.h create mode 100644 duration.c create mode 100644 duration.h create mode 100644 echo.c create mode 100644 echo.h create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 idle.c create mode 100644 idle.h create mode 100644 main.c create mode 100644 sleeptimer.c create mode 100644 sleeptimer.h create mode 100644 suspend.c create mode 100644 suspend.h 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