diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4221727 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Generated directories +build/ +objects/ + +# Autosave files +*~ +*# diff --git a/Makefile b/Makefile index c7d7cf8..2160fca 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,17 @@ #NEWPROJECT requires refactor.sh main.c # Taken from https://stackoverflow.com/questions/30573481/how-to-write-a-makefile-with-separate-source-and-header-directories +# Always build targets and unit tests together. This avoids the scenario wherein the targets are built, then the source files are edited, then the unit tests are built and, accidentally, outdated targets are packaged and shipped. I say this knowing full well that I never ship my targets to anyone ever. + +MAKEFILE_VERSION := Makefile from battery log v1.0 + +PREFIX := +SUFFIX := + + SRCDIR := sources MAINDIR := $(SRCDIR)/main -INCDIR := include +INCDIR := $(abspath $(SRCDIR)/include) OBJDIR := objects MAINOD := $(OBJDIR)/main BINDIR := build @@ -12,34 +20,57 @@ INSTDIR := /usr/local/bin REFACTOR := refactor.h +TESTCFLAGS:= $(shell pkg-config --cflags criterion) +TESTLIBS := $(shell pkg-config --libs criterion) +TESTDIR := $(SRCDIR)/test +TESTOD := $(OBJDIR)/test + HEADERS := $(wildcard $(INCDIR)/*.h) MAINS := $(wildcard $(MAINDIR)/*.c) MAINOBJS := $(patsubst $(MAINDIR)/%.c, $(MAINOD)/%.o, $(MAINS)) +TESTS := $(wildcard $(TESTDIR)/*.c) +TESTOBJS := $(patsubst $(TESTDIR)/%.c, $(TESTOD)/%.o, $(TESTS)) + +UNITTESTS := $(patsubst $(TESTDIR)/%.c, $(BINDIR)/%, $(TESTS)) + TARGETS := $(patsubst $(MAINDIR)/%.c, $(BINDIR)/%, $(MAINS)) SOURCES := $(wildcard $(SRCDIR)/*.c) OBJECTS := $(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.o, $(SOURCES)) -CPPFLAGS := -Iinclude -MMD -MP -CFLAGS := -Wall -Werror -Wpedantic +CPPFLAGS := -I$(INCDIR) -MMD -MP -DUSE_LIBNOTIFY +CFLAGS := -Wall -Werror -Wpedantic $(shell pkg-config --cflags libnotify) LDFLAGS := #-Lmath or whatever -LDLIBS := #-lm or whatever +LDLIBS := $(shell pkg-config --libs libnotify) #-lm or whatever -.PHONY: all clean +define MANGLE = + $(dir $(1))$(PREFIX)$(notdir $(1))$(SUFFIX) +endef -all: $(TARGETS) - @echo $(TARGETS) +.PHONY: all clean version + +all: | $(TARGETS) $(UNITTESTS) + +version: + @echo $(MAKEFILE_VERSION) refactor: ./refactor.sh -$(TARGETS): $(OBJECTS) $(MAINOBJS) | $(BINDIR) $(OBJDIR) $(MAINOD) - $(CC) $(LDFLAGS) $(OBJECTS) $(patsubst $(BINDIR)/%, $(MAINOD)/%.o, $@) $(LDLIBS) -o $@ +$(UNITTESTS): $(OBJECTS) $(TESTOBJS) | $(BINDIR) $(OBJDIR) $(TESTOD) + @echo "Linking unit tests..." + $(CC) $(LDFLAGS) $(OBJECTS) $(patsubst $(BINDIR)/%, $(TESTOD)/%.o, $@) $(LDLIBS) $(TESTLIBS) -o $(dir $@)$(PREFIX)$(notdir $@)$(SUFFIX) + @echo "...Done linking unit tests" -$(BINDIR) $(OBJDIR) $(MAINOD): +$(TARGETS): $(OBJECTS) $(MAINOBJS) | $(BINDIR) $(OBJDIR) $(MAINOD) + @echo "Linking targets..." + $(CC) $(LDFLAGS) $(OBJECTS) $(patsubst $(BINDIR)/%, $(MAINOD)/%.o, $@) $(LDLIBS) -o $(dir $@)$(PREFIX)$(notdir $@)$(SUFFIX) + @echo "...Done linking targets" + +$(BINDIR) $(OBJDIR) $(MAINOD) $(TESTOD): mkdir --parents $@ $(OBJDIR)/%.o: $(SRCDIR)/%.c $(HEADERS) | $(OBJDIR) @@ -48,6 +79,9 @@ $(OBJDIR)/%.o: $(SRCDIR)/%.c $(HEADERS) | $(OBJDIR) $(MAINOD)/%.o: $(MAINDIR)/%.c $(HEADERS) | $(MAINOD) $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ +$(TESTOD)/%.o: $(TESTDIR)/%.c $(HEADERS) | $(TESTOD) + $(CC) $(CPPFLAGS) $(CFLAGS) $(TESTCFLAGS) $(TESTLIBS) -c $< -o $@ + # $| evaluates to the order-only prerequisites, in this case: $(TARGETS) # The $(foreach ...) function will evaluate to a semicolon-separated list of 'cp $instdir' # Putting this $(foreach ...) into the recipe means that its result @@ -59,15 +93,35 @@ $(MAINOD)/%.o: $(MAINDIR)/%.c $(HEADERS) | $(MAINOD) install: | $(TARGETS) @echo "Installing executables to $(INSTDIR)" - @$(foreach target, $|, cp -v $(target) $(INSTDIR);) + @$(foreach target, $|, cp -v $(call MANGLE, $(target)) $(INSTDIR);) uninstall: | $(TARGETS) @echo "Removing executables from $(INSTDIR)" - $(foreach target, $(notdir $|), rm -v $(INSTDIR)/$(target);) + $(foreach target, $(notdir $(call MANGLE, $|)), rm -v $(INSTDIR)/$(target);) clean: $(RM) -rv $(BINDIR) $(OBJDIR) +# Lazy kludge to run the first executable specified in $(TARGETS) +# Could also be accomplished by running $< but this is easier to read +run: all + $(eval MANGLEDNAME=$(call MANGLE, $(word 1,$(TARGETS)))) + @echo "Running " $(MANGLEDNAME) + @$(MANGLEDNAME) +# @$(dir $(word 1,$(TARGETS)))$(PREFIX)$(notdir $(word 1,$(TARGETS)))$(SUFFIX) + +# unit testing! +test: $(TARGETS) $(UNITTESTS) + @echo "Testing " $(word 1,$(UNITTESTS)) + @$(dir $(word 1,$(UNITTESTS)))$(PREFIX)$(notdir $(word 1,$(UNITTESTS)))$(SUFFIX) + +.PHONY: variable +variable: all + $(eval mangle2=$(dir $(word 1,$(UNITTESTS)))$(PREFIX)$(notdir $(word 1,$(UNITTESTS)))$(SUFFIX)) + $(eval mangle3=$(call MANGLE, $(UNITTESTS))) + @echo "variable declared within a recipe: "$(mangle2) + @echo "variable declared within a recipe using a canned recipe: "$(mangle3) + # Honestly still not sure what this directive does. # It's an include directive, which processes all .d files that correspond to members of $(OBJ), # with the hyphen in front meaning that it suppresses/ignores errors (from nonexistent .d files) diff --git a/sources/include/c.h b/sources/include/c.h new file mode 100644 index 0000000..a8e3e54 --- /dev/null +++ b/sources/include/c.h @@ -0,0 +1 @@ +#define A 4 diff --git a/sources/include/main.h b/sources/include/main.h new file mode 100644 index 0000000..c7de2d9 --- /dev/null +++ b/sources/include/main.h @@ -0,0 +1,26 @@ +/** + * Main.h: contains definitions for battery.c + */ + +#define DEFAULT_LOG_INTERVAL 15 +#define DEFAULT_LOG_INTERVAL_STRING "15" + +#define CHARGE_NOW_PATH "/sys/class/power_supply/BAT0/charge_now" +#define CHARGE_FULL_PATH "/sys/class/power_supply/BAT0/charge_full" +#define VERSION "v0.2" +#define TITLE "Battery Percentage, by Catluck Kettlemerry" +#define PROGNAME "battery" +#define USAGE0 "Usage:" +#define USAGE1 "[OPTION...]" +#define OPTIONS0 "Options:" +#define OPTIONS1 "\t-v run verbosely" +#define OPTIONS2 "\t-h show help and exit" +#define OPTIONS3 "\t-t provide terse output" +#define OPTIONS4 "\t-l Continuously log battery level every " DEFAULT_LOG_INTERVAL_STRING " seconds" +#define OPTIONS5 "\t-i Set log interval to poll every seconds" +#define OPTIONS6 "\t (This argument automatically activates continuous logging)" +#define OPTIONS7 "\t-u (If the interval is less than 15 seconds, " PROGNAME " will refuse to run." +#define OPTIONS8 "\t Specify -u to run anyway.)" +#define OPTIONS9 "\t-w Warn the user if the battery charge drops below percent" +#define OPTIONS10 "\t-p Print the effects of the given options for this invocation" +#define MESGLEN 25 diff --git a/sources/main/battery.c b/sources/main/battery.c index ea90855..ef6faaf 100644 --- a/sources/main/battery.c +++ b/sources/main/battery.c @@ -1,54 +1,44 @@ +#include #include #include #include -#define CHARGE_NOW_PATH "/sys/class/power_supply/BAT0/charge_now" -#define CHARGE_FULL_PATH "/sys/class/power_supply/BAT0/charge_full" -#define VERSION "v0.0" -#define TITLE "Battery Percentage, by Catluck Kettlemerry" -#define PROGNAME "battery_percentage" -#define USAGE0 "Usage: " -#define USAGE1 "[option]" -#define OPTIONS0 "Options:" -#define OPTIONS1 "\t-v run verbosely" -#define OPTIONS2 "\t-h show help and exit" -#define OPTIONS3 "\t-t provide terse output" +#ifdef USE_SYSLOG +#include +#endif + +#include "main.h" + +#ifdef USE_LIBNOTIFY +#include +#endif int run_verbose = 0; int run_terse = 0; +int run_continuously = 0; +int interval = DEFAULT_LOG_INTERVAL; +int run_unsafe = 0; +int force_print_parameters = 0; + +int warn_level = 10; + +int warned = 0; /* Set if already warned */ + +const char * delimiter = "\n"; void print_help(void){ printf("%s %s\n", TITLE, VERSION); printf("%s %s %s\n", USAGE0, PROGNAME, USAGE1); - printf("%s\n%s\n%s\n%s\n", OPTIONS0, OPTIONS1, OPTIONS2, OPTIONS3); + printf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", + OPTIONS0, OPTIONS1, OPTIONS2, OPTIONS3, OPTIONS4, + OPTIONS5, OPTIONS6, OPTIONS7, OPTIONS8, OPTIONS9, + OPTIONS10); } -int main(int argc, char *const argv[]){ - - int opt; - while((opt = getopt(argc, argv, "vht")) != -1) { - switch(opt){ - case 'v': - run_verbose = 1; - break; - case 'h': - print_help(); - return 0; - case 't': - run_terse = 1; - break; - default: - printf("Literally how could you pass an unspecified argument to this program"); - } - } - - FILE * fd_now = fopen(CHARGE_NOW_PATH, "r"); - FILE * fd_full = fopen(CHARGE_FULL_PATH, "r"); - - int c_now; - int c_full; - - int vals_read = fscanf(fd_now, "%d", &c_now); +int get_percentage +(FILE * fd_now, FILE * fd_full, int * c_now, int * c_full, float * frac, int *pct) +{ + int vals_read = fscanf(fd_now, "%d", c_now); if(vals_read != 1){ if(errno != 0){ perror("Error reading full battery charge: "); @@ -59,7 +49,7 @@ int main(int argc, char *const argv[]){ } return 1; } - vals_read = fscanf(fd_full, "%d", &c_full); + vals_read = fscanf(fd_full, "%d", c_full); if(vals_read != 1){ if(errno != 0){ perror("Error reading full battery charge: "); @@ -70,17 +60,159 @@ int main(int argc, char *const argv[]){ } return 1; } - - if(run_verbose){ - printf("Current charge: %d\n microampere-hours", c_now); - printf("Full charge: %d\n microampere-hours", c_full); - } - - if(!run_terse) { - printf("Battery percentage: "); - } - float frac = ((float)c_now) / ((float)c_full); - int pct = (int)(frac * 100); - printf("%d\n", pct); + (*frac) = ((float)(*c_now)) / ((float)(*c_full)); + (*pct) = (int)((*frac) * 100); + return 0; +} + +void warn(int pct){ + if(!warned){ + + printf("Battery Low\n"); + +#ifdef USE_SYSLOG + syslog(LOG_WARNING, "Battery Low"); +#endif + +#ifdef USE_LIBNOTIFY + char warnmesg[MESGLEN]; + snprintf(warnmesg, MESGLEN, "Battery is at %d percent", pct); + notify_init(PROGNAME); + NotifyNotification * levelwarn = notify_notification_new("Battery Low", warnmesg, "dialog-information"); + notify_notification_show(levelwarn, NULL); + g_object_unref(G_OBJECT(levelwarn)); + notify_uninit(); +#endif + } + warned = 1; +} + +int main(int argc, char *const argv[]){ + + int opt; + while((opt = getopt(argc, argv, "vhtli:uw:p")) != -1) { + switch(opt){ + case 'v': + run_verbose = 1; + break; + case 'h': + print_help(); + return 0; + case 't': + run_terse = 1; + break; + case 'l': + run_continuously = 1; + break; + case 'i': + interval = atoi(optarg); + run_continuously = 1; + break; + case 'u': + run_unsafe = 1; + break; + case 'w': + warn_level = atoi(optarg); + break; + case 'p': + force_print_parameters = 1; + break; + default: + printf("Literally how could you pass an unspecified argument to this program"); + } + } + + /** + Print run parameters at startup + */ + if(force_print_parameters) { + if(run_terse && run_verbose){ + printf(PROGNAME " called with arguments to run both tersely and verbosely, the author legitimately cannot tell what you want her to do\n"); + } else if(run_terse) { + printf(PROGNAME " will run tersely\n"); + } else if(run_verbose) { + printf(PROGNAME " will run verbosely\n"); + } + printf(PROGNAME " will poll every %d seconds\n", interval); + if(!run_terse){ + printf(PROGNAME " will warn at %d%% battery\n", warn_level); + } + + + } + + if(warn_level <= 0 && !run_terse){ + fprintf(stderr, "Warn level set to %d. " PROGNAME " will not send warning on low battery.\n", warn_level); + } + + if(interval < 15 && !run_unsafe){ + fprintf(stderr, "Update interval less than 15 seconds, refusing to run. Specify -u to override this check.\n"); + return 1; + } + + +#ifdef USE_SYSLOG + openlog(PROGNAME, 0, LOG_USER); +#endif + + FILE * fd_now = fopen(CHARGE_NOW_PATH, "r"); + FILE * fd_full = fopen(CHARGE_FULL_PATH, "r"); + + int c_now; + int c_full; + float frac; + int pct; + + do{ + int rval = get_percentage(fd_now, fd_full, &c_now, &c_full, &frac, &pct); + if (rval){ + printf("Um, problem\n"); + return 1; + } + if(run_verbose){ + printf("Current charge: %d\n microampere-hours\n", c_now); + printf("Full charge: %d\n microampere-hours\n", c_full); + } + + if(!run_terse) { + printf("Battery percentage: "); + } + + if(pct > warn_level){ + warned = 0; + } + + if(pct <= warn_level && !warned){ + warn(pct); + } + printf("%d%s", pct, delimiter); +#ifdef USE_SYSLOG + syslog(LOG_INFO, "%d%%%s", pct, delimiter); +#endif + if(run_continuously){ + sleep(interval);/* + errno = 0; + rewind(fd_now); + if(errno != 0){ + perror("Error rewinding " CHARGE_NOW_PATH); + return 1; + } + rewind(fd_full); //just for the hecc of it + if(errno != 0){ + perror("Error rewinding " CHARGE_FULL_PATH); + return 1; + }*/ + fclose(fd_now); + fclose(fd_full); + fd_now = fopen(CHARGE_NOW_PATH, "r"); + fd_full = fopen(CHARGE_FULL_PATH, "r"); + } + } while (run_continuously); + + +#ifdef USE_SYSLOG + closelog(); +#endif + return 0; } diff --git a/sources/test/test.c b/sources/test/test.c new file mode 100644 index 0000000..12841fe --- /dev/null +++ b/sources/test/test.c @@ -0,0 +1,14 @@ +#include +#include +#include +#include + +#include + +#ifdef USE_LIBNOTIFY +#include +#endif + +Test(misc, passing){ + cr_assert(1); +}