--- /dev/null
+# Note: 99+ required for for-loop initial declaration (CentOS 6)
+# Note: gnu99 (instead of c99) required for libcutils-net
+
+CFLAGS += -Wall -pedantic -I./ -std=gnu99
+CXXFLAGS += -Wall -pedantic -I./
+PREFIX = /usr/local
+
+ifdef DEBUG
+CFLAGS += -ggdb -O0
+CXXFLAGS += -ggdb -O0
+endif
+
+.PHONY: all cutils check net install uninstall clean mrpropre mrpropre debug
+
+all: utils check net
+
+utils: ../../bin/libcutils.o
+
+check: ../../bin/libcutils-check.o
+
+net: ../../bin/libcutils-net.o
+
+SOURCES=$(wildcard *.c)
+HEADERS=$(wildcard *.h)
+OBJECTS=$(SOURCES:%.c=%.o)
+
+array.o: array.[ch]
+desktop.o: desktop.[ch] array.h
+
+../../bin/libcutils.o: $(OBJECTS)
+ mkdir -p ../../bin
+ # note: -r = --relocatable, but former also works with Clang
+ $(LD) -r $(OBJECTS) -o $@
+
+../../bin/libcutils-check.o: check/launcher.o
+ mkdir -p ../../bin
+ # note: -r = --relocatable, but former also works with Clang
+ $(LD) -r check/launcher.o -o $@
+
+../../bin/libcutils-net.o: net/net.o
+ mkdir -p ../../bin
+ # note: -r = --relocatable, but former also works with Clang
+ $(LD) -r net/net.o -o $@
+
+debug:
+ $(MAKE) -f makefile.d DEBUG=1
+
+clean:
+ rm -f *.o check/*.o net/*.o
+
+mrproper: mrpropre
+
+mrpropre: clean
+ rm -f ../../bin/libcutils.o
+ rm -f ../../bin/libcutils-check.o
+ rm -f ../../bin/lubcutils-net.o
+ rmdir ../../bin 2>/dev/null || true
+
+install:
+ @echo "installing cutils to $(PREFIX)..."
+ mkdir -p "$(PREFIX)/lib/cutils/"
+ cp ../../bin/libcutils.o "$(PREFIX)/lib/cutils/"
+ cp ../../bin/libcutils-check.o "$(PREFIX)/lib/cutils/"
+ cp ../../bin/libcutils-net.o "$(PREFIX)/lib/cutils/"
+ mkdir -p "$(PREFIX)/include/cutils/check/"
+ mkdir -p "$(PREFIX)/include/cutils/net/"
+ cp *.h "$(PREFIX)/include/cutils/"
+ cp check/*.h "$(PREFIX)/include/cutils/check/"
+ cp net/*.h "$(PREFIX)/include/cutils/net/"
+
+uninstall:
+ @echo "uninstalling utils from $(PREFIX)..."
+ rm -f "$(PREFIX)/lib/cutils/libcutils.o"
+ rm -f "$(PREFIX)/lib/cutils/libcutils-check.o"
+ rm -f "$(PREFIX)/lib/cutils/libcutils-net.o"
+ rmdir "$(PREFIX)/lib/cutils/" 2>/dev/null || true
+ rm -f "$(PREFIX)/include/cutils/net/"*.h
+ rm -f "$(PREFIX)/include/cutils/check/"*.h
+ rm -f "$(PREFIX)/include/cutils/"*.h
+ rmdir "$(PREFIX)/include/cutils/net" 2>/dev/null || true
+ rmdir "$(PREFIX)/include/cutils/check" 2>/dev/null || true
+ rmdir "$(PREFIX)/include/cutils" 2>/dev/null || true
+
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2020 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "array.h"
+
+typedef struct {
+ size_t elem_size;
+ size_t buffer;
+ void *data;
+} priv_t;
+
+/* make sure we have at least n+1 elements in the buffer, grow if needed */
+/* +1 is so we can always end with a NULL value for array_data/convert() */
+static int array_assure(array_t *me, size_t nb_elem);
+
+/* for qsort operations */
+static int array_qsorts_func(const void *a, const void *b);
+static int array_qsorti_func(const void *a, const void *b);
+static int array_qsortl_func(const void *a, const void *b);
+static int array_qsortf_func(const void *a, const void *b);
+static int array_qsorts_rfunc(const void *a, const void *b);
+static int array_qsorti_rfunc(const void *a, const void *b);
+static int array_qsortl_rfunc(const void *a, const void *b);
+static int array_qsortf_rfunc(const void *a, const void *b);
+
+array_t *new_array(size_t elem_size, size_t initial) {
+ if (!initial)
+ return NULL;
+
+ array_t *me = malloc(sizeof(array_t));
+ if (!init_array(me, elem_size, initial)) {
+ free(me);
+ me = NULL;
+ }
+
+ return me;
+}
+
+int init_array(array_t *me, size_t elem_size, size_t initial) {
+ if (!initial)
+ return 0;
+
+ strcpy(me->CNAME, "[CArray ]");
+
+ me->priv = malloc(sizeof(priv_t));
+ if (!me->priv)
+ return 0;
+
+ priv_t *priv = (priv_t *) me->priv;
+ strcpy(me->CNAME, "[CArray ]");
+ priv->elem_size = elem_size;
+ me->count = 0;
+ priv->buffer = initial;
+ priv->data = malloc(elem_size * initial);
+ if (!priv->data) {
+ free(me->priv);
+ return 0;
+ }
+
+ return 1;
+}
+
+void free_array(array_t *me) {
+ if (me)
+ uninit_array(me);
+
+ free(me);
+}
+
+void uninit_array(array_t *me) {
+ priv_t *priv = (priv_t *) me->priv;
+ me->count = 0;
+ priv->buffer = 0;
+ free(priv->data);
+ priv->data = NULL;
+ free(priv);
+ me->priv = NULL;
+ me->CNAME[0] = '!';
+}
+
+void array_clear(array_t *me) {
+ me->count = 0;
+}
+
+// convert to void * data (free the rest)
+void *array_convert(array_t *me) {
+ priv_t *priv = (priv_t *) me->priv;
+ void *data = array_data(me);
+ free(priv);
+ free(me);
+ return data;
+}
+
+void *array_data(array_t *me) {
+ priv_t *priv = (priv_t *) me->priv;
+
+ // Note: this should be impossible
+ if (me->count >= priv->buffer)
+ array_assure(me, me->count + 1);
+
+ // cast to (char *) because we want 'byte' arithmetic
+ void *after_end = (void *) (((char *) priv->data)
+ + (me->count * priv->elem_size));
+
+ // last item is always NULL
+ memset(after_end, '\0', priv->elem_size);
+
+ return priv->data;
+}
+
+size_t array_count(array_t *me) {
+ return me->count;
+}
+
+void *array_new(array_t *me) {
+ return array_newn(me, 1);
+}
+
+void *array_newn(array_t *me, size_t how_many) {
+ if (!array_assure(me, me->count + how_many))
+ return 0;
+
+ me->count += how_many;
+ return array_get(me, me->count - how_many);
+}
+
+void *array_first(array_t *me) {
+ if (!me->count)
+ return NULL;
+
+ priv_t *priv = (priv_t *) me->priv;
+ return priv->data;
+}
+
+void *array_last(array_t *me) {
+ if (!me->count)
+ return NULL;
+
+ priv_t *priv = (priv_t *) me->priv;
+ // cast to (char *) because we want 'byte' arithmetic
+ return (void *) (((char *) priv->data) + ((me->count - 1) * priv->elem_size));
+}
+
+void *array_prev(array_t *me, void *ptr) {
+ priv_t *priv = (priv_t *) me->priv;
+
+ // cast to (char *) because we want 'byte' arithmetic
+ char *cptr = (char *) ptr;
+ char *cdata = (char*) priv->data;
+
+ if (cptr) {
+ cptr -= priv->elem_size;
+ if (cptr >= cdata) {
+ return cptr;
+ }
+ }
+
+ return NULL;
+}
+
+void *array_next(array_t *me, void *ptr) {
+ priv_t *priv = (priv_t *) me->priv;
+
+ // cast to (char *) because we want 'byte' arithmetic
+ char *cptr = (char *) ptr;
+ char *cdata = (char*) priv->data;
+
+ if (cptr) {
+ cptr += priv->elem_size;
+ char *last = cdata + ((me->count - 1) * priv->elem_size);
+ if (cptr <= last) {
+ return cptr;
+ }
+ }
+
+ return NULL;
+}
+
+void *array_get(array_t *me, size_t i) {
+ priv_t *priv = (priv_t *) me->priv;
+
+ // cast to (char *) because we want 'byte' arithmetic
+ return (void *) (((char *) priv->data) + (i * priv->elem_size));
+}
+
+void *array_pop(array_t *me) {
+ return array_cut_at(me, me->count - 1);
+}
+
+void *array_cut_at(array_t *me, size_t n) {
+ if (n < me->count) {
+ void *item = array_get(me, n);
+ me->count = n;
+ return item;
+ }
+
+ return NULL;
+}
+
+void array_compact(array_t *me) {
+ priv_t *priv = (priv_t *) me->priv;
+
+ int c = me->count ? me->count : 1;
+ priv->data = realloc(priv->data, c * priv->elem_size);
+ priv->buffer = c;
+}
+
+void array_qsort(array_t *me, int (*compar)(const void *, const void *)) {
+ priv_t *priv = (priv_t *) me->priv;
+ qsort(priv->data, me->count, priv->elem_size, compar);
+}
+
+void array_qsorts(array_t *me, int rev) {
+ array_qsort(me, rev ? array_qsorts_rfunc : array_qsorts_func);
+}
+static int array_qsorts_func(const void *a, const void *b) {
+ char *stra = ((char **) a)[0];
+ char *strb = ((char **) b)[0];
+ return strcmp(stra, strb);
+}
+static int array_qsorts_rfunc(const void *a, const void *b) {
+ char *stra = ((char **) a)[0];
+ char *strb = ((char **) b)[0];
+ return strcmp(strb, stra);
+}
+
+void array_qsorti(array_t *me, int rev) {
+ array_qsort(me, rev ? array_qsorti_rfunc : array_qsorti_func);
+}
+static int array_qsorti_func(const void *a, const void *b) {
+ long ia, ib;
+ ia = ((int *) a)[0];
+ ib = ((int *) b)[0];
+ if (ia < ib)
+ return -1;
+ return !(ia == ib);
+}
+static int array_qsorti_rfunc(const void *a, const void *b) {
+ long ia, ib;
+ ia = ((int *) a)[0];
+ ib = ((int *) b)[0];
+ if (ia > ib)
+ return -1;
+ return !(ia == ib);
+}
+
+void array_qsortl(array_t *me, int rev) {
+ array_qsort(me, rev ? array_qsortl_rfunc : array_qsortl_func);
+}
+static int array_qsortl_func(const void *a, const void *b) {
+ long la, lb;
+ la = ((long *) a)[0];
+ lb = ((long *) b)[0];
+ if (la < lb)
+ return -1;
+ return !(la == lb);
+}
+static int array_qsortl_rfunc(const void *a, const void *b) {
+ long la, lb;
+ la = ((long *) a)[0];
+ lb = ((long *) b)[0];
+ if (la > lb)
+ return -1;
+ return !(la == lb);
+}
+
+void array_qsortf(array_t *me, int rev) {
+ array_qsort(me, rev ? array_qsortf_rfunc : array_qsortf_func);
+}
+static int array_qsortf_func(const void *a, const void *b) {
+ float fa, fb;
+ fa = ((float *) a)[0];
+ fb = ((float *) b)[0];
+ // Also works:
+ //memcpy(&fa, a, sizeof(float));
+ //memcpy(&fb, b, sizeof(float));
+ if (fa < fb)
+ return -1;
+ return !(fa == fb);
+}
+static int array_qsortf_rfunc(const void *a, const void *b) {
+ float fa, fb;
+ fa = ((float *) a)[0];
+ fb = ((float *) b)[0];
+ // Also works:
+ //memcpy(&fa, a, sizeof(float));
+ //memcpy(&fb, b, sizeof(float));
+ if (fa > fb)
+ return -1;
+ return !(fa == fb);
+}
+
+int array_push(array_t *me, void *data) {
+ return array_setn(me, me->count, data, 1);
+}
+
+int array_pushn(array_t *me, void *data, size_t n) {
+ return array_setn(me, me->count, data, n);
+}
+
+int array_copy(array_t *me, void *target, size_t i) {
+ return array_copyn(me, target, i, 1);
+}
+
+int array_copyn(array_t *me, void *target, size_t i, size_t n) {
+ priv_t *priv = (priv_t *) me->priv;
+
+ if (i + n < me->count) {
+ // cast to (char *) because we want 'byte' arithmetic
+ memcpy(target, ((char *) (priv->data)) + (i * priv->elem_size),
+ n * priv->elem_size);
+
+ return 1;
+ }
+
+ return 0;
+}
+
+int array_set(array_t *me, size_t i, void *data) {
+ return array_setn(me, i, data, 1);
+}
+
+int array_setn(array_t *me, size_t i, void *data, size_t n) {
+ // allow new items BUT NOT holes in the array
+ if (i > me->count)
+ return 0;
+
+ if (!array_assure(me, i + n))
+ return 0;
+
+ priv_t *priv = (priv_t *) me->priv;
+
+ // cast to (char *) because we want 'byte' arithmetic
+ memcpy(((char *) (priv->data)) + (i * priv->elem_size), data,
+ n * priv->elem_size);
+ if ((i + n) > me->count)
+ me->count = i + n;
+
+ return 1;
+}
+
+/* Privates functions */
+
+static int array_assure(array_t *me, size_t nb_elem) {
+ priv_t *priv = (priv_t *) me->priv;
+
+ if (priv->buffer <= nb_elem) {
+ priv->buffer *= 2;
+ if (priv->buffer < nb_elem) {
+ priv->buffer = nb_elem;
+ }
+
+ void *tmp = realloc(priv->data, priv->elem_size * priv->buffer);
+ if (!tmp)
+ return 0;
+
+ priv->data = tmp;
+ }
+
+ return 1;
+}
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2020 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file array.h
+ * @author Niki
+ * @date 2020 - 2022
+ *
+ * @brief A simple auto-growing array-list
+ *
+ * A simple auto-growing array-list, with a minimum buffer of 1.
+ *
+ * It has a link to `qsort`, too, so you don't need to retype all
+ * the parameters you already passed to the array.
+ *
+ * It is created with `new_array()` and must be freed with :
+ * - `free_array()` for normal operations
+ * - `array_convert()` if you want to free the array but keep the data
+ * (the data is now your responsibility to free or not, it was allocated
+ * with `malloc`/`realloc`)
+ *
+ * Example usage:
+ * ```C
+ * array lines = new_array(sizeof(char *), 100);
+ *
+ * const char *l1 = "2. À l'arrière";
+ * const char *l2 = "3. En réserve";
+ * const char *l3 = "1. Première ligne";
+ *
+ * // push mode (usually used for int, char, long...)
+ * array_push(lines, &l1);
+ * array_push(lines, &l2);
+ *
+ * // new mode (usually used for structures)
+ * char **tmp = array_new(lines);
+ * *tmp = l3;
+ *
+ * // sort as Strings (also possible with int, long and custom functions)
+ * array_qsorts(lines, 0);
+ *
+ * char **last_line = array_get(lines, array_count(lines) - 1);
+ * printf("Last line is now: %s\n", *last_line);
+ * // -> last_line is now: 3. En réserve
+ *
+ * array_loop(lines, line, char) {
+ * printf("Line: %s\n", line);
+ * }
+ * // -> Line: 1. Première ligne
+ * // -> Line: 2. En réserve
+ * // -> Line: 3. À l'arrière
+ *
+ * free_array(lines);
+ * ```
+ */
+
+#ifndef ARRAY_H
+#define ARRAY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+
+/**
+ * Declare a new <tt>TYPE *</tt> pointer and loop through the array with it.
+ *
+ * How to use:
+ * ```C
+ * array_loop(me, line, char) {
+ * printf("Item: %s\n", line);
+ * }
+ * ```
+ */
+#define array_loop(me, ptr, TYPE) \
+ for (TYPE *ptr = array_first(me); ptr; ptr = array_next(me, ptr))
+
+/**
+ * Similar to <tt>array_loop</tt>, but add a counter <tt>i</tt> starting at 0.
+ *
+ * @see array_loop
+ *
+ * How to use:
+ * ```C
+ * array_loop_i(me, line, char, i) {
+ * printf("Item n°%d: %s\n", i, line);
+ * }
+ * ```
+ *
+ * @note this macro does expand as 2 separate lines, surround with { } if needed
+ */
+#define array_loop_i(me, ptr, TYPE, i) \
+ size_t i = 0; \
+ for (TYPE *ptr = array_first(me); ptr; ptr = array_next(me, ptr), i++)
+
+/**
+ * @brief A simple auto-growing array-list
+ *
+ * The structure contains a private field (which you should not use) and the
+ * current count of how many items were added. You should probably not modify
+ * the count either (setting it higher is a bad idea and while it should be
+ * possible to set it lower, you are strongly advised to use
+ * <tt>array_cut_at</tt> instead).
+ *
+ * @see array_cut_at
+ */
+typedef struct {
+ char CNAME[10];
+ size_t count;
+ void *priv;
+} array_t;
+
+/**
+ * Create a new array.
+ *
+ * @note always identical to <tt>malloc</tt> + <tt>init_array</tt>
+ *
+ * @param elem_size the size of one element
+ * @param initial the initial number of items the buffer can hold (<b>not</b> 0)
+ *
+ * @see malloc()
+ * @see init_array(array_t *self)
+ *
+ * @return a new array (you must later call `free_array()` or `array_convert()`)
+ */
+array_t *new_array(size_t elem_size, size_t initial);
+
+/**
+ * Initialise a new array.
+ *
+ * @param elem_size the size of one element
+ * @param initial the initial number of items the buffer can hold (<b>not</b> 0)
+ */
+int init_array(array_t *self, size_t elem_size, size_t initial);
+
+/**
+ * Free the resources held for the given array: you must not use it any more.
+ * Note that if you use pointers and not direct data, you may want to free
+ * those yourself first.
+ *
+ * @note always equivalent to <tt>uninit_array</tt> + <tt>free</tt>
+ *
+ * @see uninit_array(array_t *self)
+ * @see free(void *data)
+ * @see array_clear
+ * @see array_loop
+ */
+void free_array(array_t *me);
+
+/**
+ * Free the resources held for the given array: you must not use it any more.
+ * Note that if you use pointers and not direct data, you may want to free
+ * those yourself first.
+ *
+ * @see free_array(array_t *self)
+ * @see array_clear
+ * @see array_loop
+ */
+void uninit_array(array_t *me);
+
+/**
+ * Clear the array, that is, resets its current size to 0 (buffer unchanged).
+ *
+ * @note if you hold custom structures with owned resources in the array, you
+ * should deallocate them properly before
+ */
+void array_clear(array_t *me);
+
+/**
+ * Convert the array to a block of memory where all values are adjacent.
+ *
+ * @note an extra NULL value is assured to be present as last element
+ *
+ * @return the data (you must later call `free()` on it)
+ */
+void *array_convert(array_t *me);
+
+/**
+ * Return a pointer to the internal storage used by this array.
+ * This is the same value as would return `array_convert()`,
+ * but the array is still valid.
+ *
+ * Be careful if you change the content (you should not).
+ *
+ * @note an extra NULL value is assured to be present as last element
+ *
+ * @return the internal storage area
+ */
+void *array_data(array_t *me);
+
+/**
+ * Return the current number of elements in the array.
+ *
+ * @return the number of elements in the array
+ */
+size_t array_count(array_t *me);
+
+/**
+ * Create a new element in the array and return a pointer to it.
+ *
+ * @return a pointer to the (newly allocated) last element of the array
+ */
+void *array_new(array_t *me);
+
+/**
+ * Create <tt>n</tt> elements in the array and return a pointer to the
+ * first one ({see array_next(void *)} to get the next ones).
+ *
+ * @param n how many elements to add
+ *
+ * @return a pointer to the (newly allocated) first new element of the array
+ */
+void *array_newn(array_t *me, size_t n);
+
+/**
+ * Return a pointer to the first element of the array (for instance, if you
+ * store integers, it will be <tt>(int *)</tt>; if you store strings, it will
+ * be <tt>char **</tt>).
+ *
+ * @return a <i>pointer</i> to the first element, or NULL if no elements are
+ * present
+ */
+void *array_first(array_t *me);
+
+/**
+ * Return a pointer to the last element of the array (for instance, if you
+ * store integers, it will be <tt>(int *)</tt>; if you store strings, it will
+ * be <tt>char **</tt>).
+ *
+ * @return a <i>pointer</i> to the last element, or NULL if no elements are
+ * present
+ */
+void *array_last(array_t *me);
+
+/**
+ * Return the pointer to the previous element, or NULL if it was the first.
+ *
+ * @param ptr a pointer from an array (the array must be valid)
+ *
+ * @return the previous element, or NULL
+ */
+void *array_prev(array_t *me, void *ptr);
+
+/**
+ * Return the pointer to the next element, or NULL if it was the last.
+ *
+ * @param ptr a pointer from an array (the array must be valid)
+ *
+ * @return the next element, or NULL
+ */
+void *array_next(array_t *me, void *ptr);
+
+/**
+ * Retrieve the the pointer of an item.
+ * The address of the item will be returned.
+ *
+ * @param i the index of the element to retrieve
+ *
+ * @note if the index is out of bounds, you will get invalid data
+ *
+ * @return the pointer to the i'th element
+ */
+void *array_get(array_t *me, size_t i);
+
+/**
+ * Return a pointer to the last element of this array and remove it from the
+ * array, if the array is not empty.
+ *
+ * @note be careful, the memory pointed to by the element will be reused the
+ * next time we add an element -- you should not use it after this; in
+ * short, the return value is mainly so you can call <tt>free</tt> on
+ * value pointed to by this pointer (<b>not</b> the pointer itself) if it
+ * is a pointer to memory you own, or use it locally before continuing to
+ * use the array
+ * @note in case this was not clear, do <b>not</b> call <tt>free</tt> on the
+ * returned value
+ *
+ * @return a pointer to the last (now removed) item, or NULL if no element
+ */
+void *array_pop(array_t *me);
+
+/**
+ * Cut the array at the given size and return a pointer to the first element
+ * that was removed if any.
+ *
+ * @note be careful, the memory pointed to by the element(s) will be reused the
+ * next time we add an element -- you should not use it after this; in
+ * short, the return value is mainly so you can call <tt>free</tt> on
+ * value(s) pointed to by this pointer (<b>not</b> the pointer itself) if
+ * it is a pointer to memory you own, or use it locally before continuing
+ * to use the array
+ * @note in case this was not clear, do <b>not</b> call <tt>free</tt> on the
+ * returned value(s)
+ *
+ * @return a pointer to the first removed element, or NULL
+ */
+void *array_cut_at(array_t *me, size_t n);
+
+/**
+ * Compact the array (resize the buffer so it is equals to the current number
+ * of items in the array or size 1 if there are no items in the array).
+ */
+void array_compact(array_t *me);
+
+/**
+ * Sort the array with a call to `qsort()`.
+ * All the appropriate parameters are passed, except the sorting function.
+ *
+ * @param compar a custom comparison function
+ * @param (parameters)
+ * * `itm1`: a **pointer** to one of your data element (for instance,
+ * if you store floats, it will be a `(float *)`,
+ * so you would need to cast it via `((float *)itm1)[0]`
+ * * `itm2`: another **pointer** to compare with the first one
+ * @param (returns)
+ * * `-1`: if element A is less than element B
+ * * `0`: if both elements are equals
+ * * `1`: if element A is more than element B
+ */
+void array_qsort(array_t *me, int (*compar)(const void *itm1, const void *itm2));
+
+/**
+ * Sort the array with `qsort()`, data is `char *`.
+ *
+ * @param rev FALSE for normal order, TRUE for reverse order
+ *
+ * @see array_qsort
+ */
+void array_qsorts(array_t *me, int rev);
+
+/**
+ * Sort the array with `qsort()`, data is `int`.
+ *
+ * @param rev FALSE for normal order, TRUE for reverse order
+ *
+ * @see array_qsort
+ */
+void array_qsorti(array_t *me, int rev);
+
+/**
+ * Sort the array with `qsort()`, data is `long`.
+ *
+ * @param rev FALSE for normal order, TRUE for reverse order
+ *
+ * @see array_qsort
+ */
+void array_qsortl(array_t *me, int rev);
+
+/**
+ * Sort the array with `qsort()`, data is `float`.
+ *
+ * @param rev FALSE for normal order, TRUE for reverse order
+ *
+ * @see array_qsort
+ */
+void array_qsortf(array_t *me, int rev);
+
+/**
+ * Add an element to the array (will create a new item in the array and copy the
+ * data from the given element in it).
+ *
+ * @param data the memory position of the element to add
+ *
+ * @return FALSE if the array is too short and we cannot allocate enough
+ * contiguous memory for its needs
+ */
+int array_push(array_t *me, void *data);
+
+/**
+ * Add multiple elements to the array (will create new items in the array and
+ * copy the data from the given elements in them).
+ *
+ * @param data the memory position of the elements to add, adjacent to each
+ * other
+ * @param n the number of elements to copy from `data`
+ *
+ * @return FALSE if the array is too short and we cannot allocate enough
+ * contiguous memory for its needs
+ */
+int array_pushn(array_t *me, void *data, size_t n);
+
+/**
+ * Retrieve the content of an item.
+ * The item will be copied to the given address location if it exists.
+ *
+ * @param target an address where to write a copy of the item
+ * @param i the index of the element to retrieve
+ *
+ * @return TRUE if the item exists (if <tt>i</tt> is an element of the array)
+ */
+int array_copy(array_t *me, void *target, size_t i);
+
+/**
+ * Retrieve the content of multiple items if they exist.
+ * The items will be copied in a sequence to the given address location.
+ *
+ * @param target an address where to write a copy of the items
+ * @param i the index of the first element to retrieve
+ * @param n the number of elements to retrieve
+ *
+ * @return TRUE if the item exists (if <tt>i</tt> to <tt>n</tt> are elements
+ * of the array)
+ */
+int array_copyn(array_t *me, void *target, size_t i, size_t n);
+
+/**
+ * Set an element of the array to the given value.
+ * Can also append an element at the end of the array (i.e., <tt>i</tt> can be
+ * the size of the array and this will result in an array with one more
+ * element).
+ * Memory will be copied from the given data to the array.
+ *
+ * @param i the element index
+ * @param data the data that will replace the current value, or new data
+ * to append after the current elements (you can add items just at
+ * the end of the array (index = count), but it is not allowed to
+ * set items after that index, so not to create holes)
+ *
+ * @return FALSE if the array is too short and we cannot allocate enough
+ * contiguous memory for its needs, or if the index is out of bounds
+ */
+int array_set(array_t *me, size_t i, void *data);
+
+/**
+ * Set elements of the array to the given value.
+ * Can also append elements at the end of the array (i.e., <tt>i</tt> can be
+ * the size of the array and this will result in an array with <tt>n</tt> more
+ * elements).
+ * Memory will be copied from the given data to the array.
+ *
+ * @param i the element index to start the insertion at
+ * @param data the data that will replace the current values, or new data
+ * to append after the current elements (you can add items just at
+ * the end of the array (index = count), but it is not allowed to
+ * set items with `i` after that index, so not to create holes)
+ * @param n the number of elements to copy from `data` and to insert at `i`
+ *
+ * @return FALSE if the array is too short and we cannot allocate enough
+ * contiguous memory for its needs, or if the index is out of bounds
+ */
+int array_setn(array_t *me, size_t i, void *data, size_t n);
+
+#endif /* ARRAY_H */
+
+#ifdef __cplusplus
+}
+#endif
+
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2013 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "base64.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+static char encoding_table[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
+ 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
+ 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
+ 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
+ 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
+
+static int decoding_table_flg = 0;
+static char decoding_table[256];
+
+static void init_dtable() {
+ if (!decoding_table_flg) {
+ for (int i = 0; i < 64; i++)
+ decoding_table[(unsigned char) encoding_table[i]] = i;
+ decoding_table_flg = 1;
+ }
+}
+
+char *base64_encode(const char *data) {
+ size_t input_length = strlen(data);
+ size_t output_length = 4 * ((input_length + 2) / 3);
+
+ char *encoded_data = malloc(output_length + 1);
+ if (!encoded_data)
+ return NULL;
+
+ for (unsigned int i = 0, j = 0; i < input_length;) {
+ unsigned int octet_a = i < input_length ? (unsigned char) data[i++] : 0;
+ unsigned int octet_b = i < input_length ? (unsigned char) data[i++] : 0;
+ unsigned int octet_c = i < input_length ? (unsigned char) data[i++] : 0;
+
+ unsigned int triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
+
+ encoded_data[j++] = (char) encoding_table[(triple >> 3 * 6) & 0x3F];
+ encoded_data[j++] = (char) encoding_table[(triple >> 2 * 6) & 0x3F];
+ encoded_data[j++] = (char) encoding_table[(triple >> 1 * 6) & 0x3F];
+ encoded_data[j++] = (char) encoding_table[(triple >> 0 * 6) & 0x3F];
+ }
+
+ if ((input_length % 3) > 0)
+ encoded_data[output_length - 1] = '=';
+ if ((input_length % 3) == 1)
+ encoded_data[output_length - 2] = '=';
+
+ encoded_data[output_length] = '\0';
+
+ return encoded_data;
+}
+
+char *base64_decode(const char *data) {
+ init_dtable();
+
+ size_t input_length = strlen(data);
+ if (input_length % 4 != 0)
+ return NULL;
+
+ size_t output_length = ((input_length / 4) * 3);
+ if (data[input_length - 1] == '=')
+ output_length--;
+ if (data[input_length - 2] == '=')
+ output_length--;
+
+ char *decoded_data = malloc(output_length + 1);
+ if (!decoded_data)
+ return NULL;
+
+ for (unsigned int i = 0, j = 0; i < input_length; i += 4) {
+ unsigned int sextet_a = 0;
+ unsigned int sextet_b = 0;
+ unsigned int sextet_c = 0;
+ unsigned int sextet_d = 0;
+
+ if (data[i] != '=') {
+ sextet_a = decoding_table[(unsigned char) data[i + 0]];
+ sextet_b = decoding_table[(unsigned char) data[i + 1]];
+ sextet_c = decoding_table[(unsigned char) data[i + 2]];
+ sextet_d = decoding_table[(unsigned char) data[i + 3]];
+ }
+
+ unsigned int triple = (sextet_a << 3 * 6) + (sextet_b << 2 * 6)
+ + (sextet_c << 1 * 6) + (sextet_d << 0 * 6);
+
+ if (j < output_length)
+ decoded_data[j++] = (char) ((triple >> 2 * 8) & 0xFF);
+ if (j < output_length)
+ decoded_data[j++] = (char) ((triple >> 1 * 8) & 0xFF);
+ if (j < output_length)
+ decoded_data[j++] = (char) ((triple >> 0 * 8) & 0xFF);
+ }
+
+ decoded_data[output_length] = '\0';
+ return decoded_data;
+}
+
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2013 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file base64.h
+ * @author Niki
+ * @date 2013 - 2022
+ *
+ * @brief Base64 encode and decode
+ *
+ * This file only provides 2 functions, <tt>base64_encode</tt> and
+ * <tt>base64_decode</tt>, which works on NUL-terminated strings and do what
+ * you expect them to.
+ *
+ * @see base64_encode(const char data[])
+ * @see base64_decode(const char data[])
+ */
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef BASE64_H
+#define BASE64_H
+
+#include <stdlib.h>
+
+/**
+ * Encode the given data to Base64.
+ *
+ * @note can return NULL if there is not enough memory to allocated the answer
+ *
+ * @param data the data to encode
+ *
+ * @return a newly-allocated string for which you are responsible, or NULL
+ */
+char *base64_encode(const char data[]);
+
+/**
+ * Decode the given data to Base64.
+ *
+ * @note can return NULL if there is not enough memory to allocated the answer
+ *
+ * @param data the data to decode
+ *
+ * @return a newly-allocated string for which you are responsible, or NULL
+ */
+char *base64_decode(const char data[]);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2022 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "launcher.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+// Shared globals
+int launcher_color = -1;
+
+// Private functions
+static int has_colour();
+static char *waiting_color();
+static char *passed_color();
+static char *failed_color();
+static char *stop_color();
+
+// Private vars
+// static char BLEU[] = { (char) 27, '[', '3', '4', 'm', '\0' };
+// static char TEAL[] = { (char) 27, '[', '3', '6', 'm', '\0' };
+// static char GRIS[] = { (char) 27, '[', '3', '7', 'm', '\0' };
+static char VERT[] = { (char) 27, '[', '3', '2', 'm', '\0' };
+static char ROUGE[] = { (char) 27, '[', '3', '1', 'm', '\0' };
+static char ORANGE[] = { (char) 27, '[', '3', '3', 'm', '\0' };
+static char STOP[] = { (char) 27, '[', '0', 'm', '\0' };
+static char bs9[] = { 8, 8, 8, 8, 8, 8, 8, 8, 8, '\0' };
+
+// COLORTERM : if it exists and is not empty, the system will default to
+// colour mode (--color) as opposed to --no-color
+
+// CK_ENV : Gets the print mode from the environment variable CK_VERBOSITY,
+// which can have the values "silent", "minimal", "normal", "verbose". If the
+// variable is not found or the value is not recognised, the print mode is set
+// to CK_NORMAL.
+
+// How to start the program:
+// ========================
+//
+// $0
+// test the code (normal tests only)
+// $0 --more
+// run extra tests (if available; usually, they are specified as extra if they
+// take long to process)
+//
+//
+// $0 --name NAME
+// do not test, just format the name as if it was tested
+// $0 --passed
+// format the line as if it was passes
+// $0 --failed
+// format the line as if it was failed
+
+char *tests_name = NULL;
+
+void test_init(const char name[]) {
+ int i;
+ int cols;
+ struct winsize ws;
+
+ ioctl(1, TIOCGWINSZ, &ws);
+ cols = ws.ws_col;
+
+ if (!tests_name) {
+ tests_name = malloc(sizeof(char) * (cols + 1));
+ }
+
+ for (i = 0; i < (cols + 1); i++) {
+ if (i < 4)
+ tests_name[i] = ' ';
+ else
+ tests_name[i] = '.';
+ }
+
+ strcpy(tests_name + 4, name);
+ tests_name[strlen(name) + 4] = ' ';
+ tests_name[cols - 6 - 1 - 4] = '\0';
+
+ fprintf(stderr, "%s", tests_name);
+ fprintf(stderr, "%s[%s%s%s] ", " ", waiting_color(), " ?? ",
+ stop_color());
+}
+
+void test_success() {
+ fprintf(stderr, "%s[%s%s%s] ", bs9, passed_color(), " OK ",
+ stop_color());
+}
+
+void test_failure() {
+ fprintf(stderr, "%s[%s%s%s] ", bs9, failed_color(), "FAIL",
+ stop_color());
+}
+
+int test_start(int more) {
+ int failed;
+ SRunner *runner;
+
+ runner = get_tests(more);
+
+ failed = 0;
+ if (runner) {
+ srunner_run_all(runner, CK_ENV);
+
+ failed = srunner_ntests_failed(runner);
+ srunner_free(runner);
+ } else {
+ printf(">>> No tests have been found <<<\n");
+ }
+
+ return failed;
+}
+
+int main(int argc, char **argv) {
+ int more = 0;
+ int cont = 1;
+
+ for (int i = 1; i < argc; i++) {
+ if (!strcmp("--name", argv[i])) {
+ if ((i + 1) >= argc)
+ return 1;
+ test_init(argv[++i]);
+ cont = 0;
+ } else if (!strcmp("--passed", argv[i])) {
+ test_success();
+ cont = 0;
+ } else if (!strcmp("--failed", argv[i])) {
+ test_failure();
+ cont = 0;
+ } else if (!strcmp("--more", argv[i])) {
+ more = 1;
+ } else if (!strcmp("--color", argv[i])) {
+ launcher_color = 1;
+ } else if (!strcmp("--no-color", argv[i])) {
+ launcher_color = 0;
+ }
+ }
+
+ if (!cont)
+ return 0;
+
+ return test_start(more);
+}
+
+static int has_colour() {
+ if (launcher_color == -1) {
+ // TODO: could we do better?
+ const char *cterm = getenv("COLORTERM");
+ if (cterm && cterm[0])
+ launcher_color = 1;
+ else
+ launcher_color = 0;
+ }
+
+ return launcher_color;
+}
+
+static char *waiting_color() {
+ if (has_colour())
+ return ORANGE;
+ return "";
+}
+
+static char *passed_color() {
+ if (has_colour())
+ return VERT;
+ return "";
+}
+
+static char *failed_color() {
+ if (has_colour())
+ return ROUGE;
+ return "";
+}
+
+static char *stop_color() {
+ if (has_colour())
+ return STOP;
+ return "";
+}
--- /dev/null
+#ifndef _LAUNCHER_H
+#define _LAUNCHER_H
+
+#include <check.h>
+
+#define START(name) \
+START_TEST(name) {\
+ test_init(#name);\
+
+#define END \
+ test_success();\
+}\
+END_TEST\
+
+#define FAIL(...) \
+ck_abort_msg(__VA_ARGS__)\
+
+#define ASSERT_EQUALS_STR(title, expected, received) \
+ do { if (strcmp(expected, received)) { \
+ck_abort_msg("%s\n\tExpected: <%s>\n\tReceived: <%s>", title, expected, received); \
+}} while(0)
+
+#define ASSERT_EQUALS_INT(title, expected, received) \
+ do { if (expected != received) { \
+ck_abort_msg("%s\n\tExpected: %lld\n\tReceived: %lld", title, (long long)expected, (long long)received); \
+}} while(0)
+
+#define ASSERT_EQUALS_SIZE(title, expected, received) \
+ do { if (expected != received) { \
+ck_abort_msg("%s\n\tExpected: %zu\n\tReceived: %zu", title, expected, received); \
+}} while(0)
+
+extern int launcher_color;
+
+SRunner *get_tests(int more);
+
+void test_init(const char name[]);
+
+int test_start(int more);
+
+void test_success();
+
+void test_failure();
+
+#endif
+
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2011 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+ Name: cstring.c
+ Copyright: niki (gpl3 or later) 2011
+ Author: niki
+ Date: 2011-06-16
+ Description: cstring is a collection of helper functions to manipulate text
+ */
+
+#include "cstring.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+#include <wctype.h>
+
+// For upper/lowercase
+#include <locale.h>
+
+#ifndef BUFFER_SIZE
+#define BUFFER_SIZE 81
+#endif
+
+#ifdef WIN32
+#define CSTRING_SEP '\\'
+#else
+#define CSTRING_SEP '/'
+#endif
+
+// Private functions
+
+typedef struct {
+ size_t buffer_length;
+} priv_t;
+
+/** Swap the data */
+static void cstring_swap(cstring_t *a, cstring_t *b);
+/** Change the case to upper -or- lower case (UTF8-compatible) */
+static void cstring_change_case(cstring_t *self, int up);
+/** For path-related functions */
+static void normalize_path(cstring_t *self);
+
+// Private variables
+
+static char *locale = NULL;
+
+// end of privates
+
+cstring_t *new_cstring() {
+ cstring_t *self = malloc(sizeof(cstring_t));
+ if (!init_cstring(self)) {
+ free(self);
+ self = NULL;
+ }
+
+ return self;
+}
+
+int init_cstring(cstring_t *self) {
+ strcpy(self->CNAME, "[CString]");
+
+ self->priv = malloc(sizeof(priv_t));
+ if (!self->priv)
+ return 0;
+ self->string = malloc(sizeof(char) * BUFFER_SIZE);
+ if (!self->string) {
+ free(self->priv);
+ return 0;
+ }
+
+ self->length = 0;
+ ((priv_t *) self->priv)->buffer_length = BUFFER_SIZE;
+ self->string[0] = '\0';
+
+ return 1;
+}
+
+void free_cstring(cstring_t *self) {
+ if (self)
+ uninit_cstring(self);
+
+ free(self);
+}
+
+void uninit_cstring(cstring_t *self) {
+ free(self->priv);
+ free(self->string);
+ self->priv = NULL;
+ self->string = NULL;
+ self->length = 0;
+ self->CNAME[0] = '!';
+}
+
+void cstring_swap(cstring_t *a, cstring_t *b) {
+ void *tmp_p;
+ char *tmp_s;
+ size_t tmp_l;
+
+ tmp_s = a->string;
+ tmp_l = a->length;
+ tmp_p = a->priv;
+
+ a->string = b->string;
+ a->length = b->length;
+ a->priv = b->priv;
+
+ b->string = tmp_s;
+ b->length = tmp_l;
+ b->priv = tmp_p;
+}
+
+int cstring_grow(cstring_t *self, int min_extra) {
+ priv_t *priv = ((priv_t *) self->priv);
+
+ size_t sz = priv->buffer_length;
+ size_t req = self->length + min_extra;
+
+ if (req > sz) {
+ if (sz > BUFFER_SIZE)
+ sz *= 2;
+ else
+ sz += BUFFER_SIZE;
+
+ if (req > sz)
+ sz = req;
+
+ return cstring_grow_to(self, sz);
+ }
+
+ return 1;
+}
+
+int cstring_grow_to(cstring_t *self, int min_buffer) {
+ priv_t *priv = ((priv_t *) self->priv);
+
+ if (min_buffer > priv->buffer_length) {
+ priv->buffer_length = min_buffer;
+ void *mem = realloc(self->string, sizeof(char) * priv->buffer_length);
+
+ if (mem)
+ self->string = (char *) mem;
+ else
+ return 0;
+ }
+
+ return 1;
+}
+
+void cstring_compact(cstring_t *self) {
+ if (self != NULL) {
+ priv_t *priv = ((priv_t *) self->priv);
+
+ priv->buffer_length = self->length + 1;
+ self->string = (char *) realloc(self->string, self->length + 1);
+ }
+}
+
+int cstring_add_car(cstring_t *self, char source) {
+ if (!cstring_grow(self, 1))
+ return 0;
+
+ self->string[self->length] = source;
+ self->length++;
+ self->string[self->length] = '\0';
+
+ return 1;
+}
+
+int cstring_add(cstring_t *self, const char source[]) {
+ return cstring_addf(self, source, 0);
+}
+
+int cstring_addf(cstring_t *self, const char source[], size_t idx) {
+ return cstring_addfn(self, source, idx, 0);
+}
+
+int cstring_addn(cstring_t *self, const char source[], size_t n) {
+ return cstring_addfn(self, source, 0, n);
+}
+
+int cstring_addfn(cstring_t *self, const char source[], size_t idx, size_t n) {
+ size_t ss;
+
+ ss = strlen(source);
+ if (source && ss > idx && idx >= 0) {
+ ss -= idx;
+
+ if (n && n < ss)
+ ss = n;
+
+ if (ss) {
+ // "+1" for the added '\0'
+ if (!cstring_grow(self, ss + 1))
+ return 0;
+
+ memcpy(self->string + self->length, source + idx, ss);
+ self->length += ss;
+ self->string[self->length] = '\0';
+ }
+ }
+
+ return 1;
+}
+
+int cstring_addp(cstring_t *self, const char *fmt, ...) {
+ va_list ap;
+ char empty = '\0';
+
+ va_start(ap, fmt);
+ int sz = vsnprintf(&empty, 0, fmt, ap);
+ va_end(ap);
+
+ char *tmp = malloc((sz + 1) * sizeof(char));
+ if (!tmp)
+ return 0;
+
+ va_start(ap, fmt);
+ sz = vsnprintf(tmp, sz + 1, fmt, ap);
+ va_end(ap);
+
+ int ok = cstring_add(self, tmp);
+ free(tmp);
+
+ return ok;
+}
+
+void cstring_cut_at(cstring_t *self, size_t size) {
+ if (self->length > size) {
+ self->string[size] = '\0';
+ self->length = size;
+ }
+}
+
+cstring_t *cstring_substring(const char self[], size_t start, size_t length) {
+ size_t sz = strlen(self);
+ cstring_t * sub = new_cstring();
+
+ if (start <= sz) {
+ const char *source = (self + start);
+
+ if (!length)
+ cstring_add(sub, source);
+ else
+ cstring_addn(sub, source, length);
+ }
+
+ return sub;
+}
+
+/*
+ clist *cstring_splitc(cstring *self, char delim, char quote) {
+ clist *list;
+ cstring *d, *q;
+
+ d = new_cstring();
+ q = new_cstring();
+
+ if (delim)
+ cstring_add_car(d, delim);
+ if (quote)
+ cstring_add_car(q, quote);
+
+ list = cstring_split(self, d, q);
+
+ free_cstring(d);
+ free_cstring(q);
+
+ return list;
+ }
+
+ clist *cstring_splits(cstring *self, const char delim[], const char quote[]) {
+ clist *list;
+ cstring *d, *q;
+
+ d = new_cstring();
+ q = new_cstring();
+
+ if (delim)
+ cstring_add(d, delim);
+ if (quote)
+ cstring_add(q, quote);
+
+ list = cstring_split(self, d, q);
+
+ free_cstring(d);
+ free_cstring(q);
+
+ return list;
+ }
+ clist *cstring_split(cstring *self, cstring *delim, cstring *quote) {
+ clist *list;
+ cstring *elem;
+ clist_node *node;
+ size_t i;
+ int in_quote;
+ int hasdelim;
+
+ hasdelim = delim && delim->length > 0;
+
+ list = clist_new();
+ in_quote = 0;
+ elem = NULL;
+ for (i = 0; i < self->length; i++) {
+ if (quote->length > 0 && cstring_starts_with(self, quote, i)) {
+ in_quote = !in_quote;
+ i += quote->length - 1;
+ } else {
+ if (elem == NULL) {
+ elem = new_cstring();
+ node = clist_node_new();
+ node->data = elem;
+ node->free_data = free_cstring;
+ clist_add(list, node);
+ }
+ if (!in_quote && hasdelim && cstring_starts_with(self, delim, i)) {
+ elem = new_cstring();
+ node = clist_node_new();
+ node->data = elem;
+ node->free_data = free_cstring;
+ clist_add(list, node);
+ i += delim->length - 1;
+ } else {
+ cstring_add_car(elem, self->string[i]);
+ }
+ }
+ }
+
+ return list;
+ }
+ */
+
+void cstring_reverse(char *self) {
+ size_t i;
+ size_t last;
+ char tmp;
+
+ size_t sz = strlen(self);
+ if (sz) {
+ last = sz - 1;
+ for (i = 0; i <= (last / 2); i++) {
+ tmp = self[i];
+ self[i] = self[last - i];
+ self[last - i] = tmp;
+ }
+ }
+}
+
+int cstring_replace(cstring_t *self, const char from[], const char to[]) {
+ cstring_t *buffer;
+ size_t i;
+ size_t step;
+ int occur;
+
+ // easy optimisation:
+ if (!from || !from[0])
+ return 0;
+ if (from && to && from[0] && to[0] && !from[1] && !to[1])
+ return cstring_replace_car(self->string, from[0], to[0]);
+
+ // optimise for same-size strings?
+
+ step = strlen(from) - 1;
+ buffer = new_cstring();
+ occur = 0;
+ for (i = 0; i < self->length; i++) {
+ if (cstring_starts_with(self->string, from, i)) {
+ cstring_add(buffer, to);
+ i += step;
+ occur++;
+ } else {
+ cstring_add_car(buffer, self->string[i]);
+ }
+ }
+
+ cstring_swap(self, buffer);
+ free_cstring(buffer);
+ return occur;
+}
+
+int cstring_replace_car(char *self, char from, char to) {
+ size_t i;
+ int occur = 0;
+
+ for (i = 0; self[i]; i++) {
+ if (self[i] == from) {
+ self[i] = to;
+ occur++;
+ }
+ }
+
+ return occur;
+}
+
+int cstring_starts_with(const char string[], const char find[],
+ size_t start_idx) {
+ size_t i;
+
+ for (i = 0;
+ string[start_idx + i] == find[i] && string[start_idx + i] != '\0'
+ && find[i] != '\0'; i++)
+ ;
+
+ return find[i] == '\0';
+}
+
+int cstring_ends_with(const char self[], const char find[]) {
+ size_t sz = strlen(self);
+ size_t sz_needle = strlen(find);
+ if (sz_needle <= sz) {
+ if (!strcmp(self + (sz - sz_needle), find))
+ return 1;
+ }
+
+ return 0;
+}
+
+long cstring_find(const char self[], const char find[], size_t start_index) {
+ size_t sz = strlen(self);
+ if (sz > start_index) {
+ char *found = strstr(self + start_index, find);
+ if (found) {
+ return (long) (found - self);
+ }
+ }
+
+ return -1;
+}
+
+long cstring_rfind(char self[], const char find[], long rstart_index) {
+ size_t sz = strlen(self);
+ size_t sz_needle = strlen(find);
+
+ if (rstart_index <= 0)
+ rstart_index += (sz - 1);
+
+ if (sz > rstart_index && sz_needle <= sz) {
+ for (size_t i = rstart_index;; i--) {
+ if (cstring_starts_with(self, find, i))
+ return i;
+
+ if (!i)
+ break;
+ }
+ }
+
+ return -1;
+}
+
+void cstring_clear(cstring_t *self) {
+ self->length = 0;
+ self->string[0] = '\0';
+}
+
+char *cstring_convert(cstring_t *self) {
+ char *string;
+
+ if (!self)
+ return NULL;
+
+ // Note: this could be skipped.
+ cstring_compact(self);
+
+ string = (self->string);
+ self->string = NULL;
+
+ free_cstring(self);
+
+ return string;
+}
+
+cstring_t *cstring_clone(const char self[]) {
+ if (self == NULL)
+ return NULL;
+
+ cstring_t *clone = new_cstring();
+ cstring_add(clone, self);
+
+ return clone;
+}
+
+void cstring_rtrim(cstring_t *self, char car) {
+ for (size_t i = self->length - 1; i >= 0; i--) {
+ if (self->string[i] != car)
+ break;
+ self->string[i] = '\0';
+ self->length--;
+ }
+}
+
+void cstring_trim(cstring_t *self, char car) {
+ if (car == '\0')
+ return;
+
+ cstring_rtrim(self, car);
+
+ int i = 0;
+ while (self->string[i] == car)
+ i++;
+
+ if (i) {
+ cstring_t *tmp = new_cstring();
+ cstring_add(tmp, self->string + i);
+
+ cstring_swap(self, tmp);
+ free_cstring(tmp);
+ }
+}
+
+size_t cstring_remove_crlf(char *self) {
+ size_t sz = strlen(self);
+ if (sz && self[sz - 1] == '\n')
+ sz--;
+ if (sz && self[sz - 1] == '\r')
+ sz--;
+
+ self[sz] = '\0';
+
+ return sz;
+}
+
+void cstring_toupper(cstring_t *self) {
+ cstring_change_case(self, 1);
+}
+
+void cstring_tolower(cstring_t *self) {
+ cstring_change_case(self, 0);
+}
+
+void cstring_change_case(cstring_t *self, int up) {
+ // Change LC_ALL to LANG if not found
+ // TODO: only take part we need (also, this is still bad practise)
+ if (!locale) {
+ locale = setlocale(LC_ALL, NULL);
+ if (!locale || !locale[0] || !strcmp("C", locale)) {
+ char *lang = getenv("LANG");
+ if (lang && lang[0]) {
+ locale = setlocale(LC_ALL, lang);
+ if (!locale)
+ locale = "";
+ }
+ }
+ }
+
+ cstring_t *rep;
+ mbstate_t state_from, state_to;
+ wchar_t wide;
+ char tmp[10];
+ size_t count;
+
+ // init the state (NULL = internal hidden state, not thread-safe)
+ memset(&state_from, '\0', sizeof(mbstate_t));
+ memset(&state_to, '\0', sizeof(mbstate_t));
+
+ rep = new_cstring();
+
+ size_t i = 0;
+ while (i < self->length) {
+ count = mbrtowc(&wide, self->string + i, self->length - i, &state_from);
+
+ //incomplete (should not happen)
+ if (count == (size_t) -2) {
+ // return;
+ cstring_add_car(rep, '_');
+ i++;
+ continue;
+ }
+ // invalid multibyte sequence
+ if (count == (size_t) -1) {
+ // return;
+ cstring_add_car(rep, '_');
+ i++;
+ continue;
+ }
+
+ // End of String (should not happen, see WHILE condition)
+ if (!count)
+ break;
+
+ // char is ok
+ i += count;
+
+ if (up)
+ wide = (wchar_t) towupper((wint_t) wide);
+ else
+ wide = (wchar_t) towlower((wint_t) wide);
+
+ count = wcrtomb(tmp, wide, &state_to);
+ if (count == (size_t) -1) {
+ // failed to convert :(
+ cstring_add_car(rep, '_');
+ } else {
+ tmp[count] = '\0';
+ cstring_add(rep, tmp);
+ }
+ }
+
+ cstring_swap(self, rep);
+ free_cstring(rep);
+}
+
+static char buffer[BUFFER_SIZE];
+int cstring_readline(cstring_t *self, FILE *file) {
+ size_t size = 0;
+ int full_line;
+
+ // sanity check:
+ if (!file)
+ return 0;
+
+ buffer[BUFFER_SIZE - 1] = '\0'; // just in case
+
+ if (!feof(file)) {
+ cstring_clear(self);
+ buffer[0] = '\0';
+
+ // Note: fgets() could return NULL if EOF is reached
+ if (!fgets(buffer, (int) BUFFER_SIZE - 1, file))
+ return 0;
+
+ size = strlen(buffer);
+ full_line = ((file && feof(file)) || size == 0
+ || buffer[size - 1] == '\n');
+ size = cstring_remove_crlf(buffer);
+ cstring_add(self, buffer);
+
+ // No luck, we need to continue getting data
+ while (!full_line) {
+ if (!fgets(buffer, (int) BUFFER_SIZE - 1, file))
+ break;
+
+ size = strlen(buffer);
+ full_line = ((file && feof(file)) || size == 0
+ || buffer[size - 1] == '\n');
+ size = cstring_remove_crlf(buffer);
+ cstring_add(self, buffer);
+ }
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static void normalize_path(cstring_t *self) {
+ while (self->length && self->string[self->length - 1] == CSTRING_SEP)
+ self->length--;
+ self->string[self->length] = '\0';
+}
+
+void cstring_add_path(cstring_t *self, const char subpath[]) {
+ while (self->length && self->string[self->length - 1] == CSTRING_SEP)
+ self->length--;
+ cstring_add_car(self, CSTRING_SEP);
+ if (subpath && subpath[0]) {
+ cstring_add(self, subpath);
+ }
+
+ normalize_path(self);
+}
+
+int cstring_pop_path(cstring_t *self, int how_many) {
+ int count = 0;
+ size_t tmp;
+ char first = '\0';
+
+ if (self->length)
+ first = self->string[0];
+
+ normalize_path(self);
+ for (int i = 0; i < how_many; i++) {
+ tmp = self->length;
+ while (self->length && self->string[self->length - 1] != CSTRING_SEP)
+ self->length--;
+ while (self->length && self->string[self->length - 1] == CSTRING_SEP)
+ self->length--;
+ if (self->length != tmp)
+ count++;
+ }
+ normalize_path(self);
+
+ // Root is root of root
+ if (first == CSTRING_SEP && !self->length)
+ cstring_add_car(self, CSTRING_SEP);
+
+ return count;
+}
+
+char *cstring_basename(const char path[], const char ext[]) {
+ size_t i;
+ size_t sz = strlen(path);
+
+ i = sz;
+ while (i && path[i] != CSTRING_SEP)
+ i--;
+
+ cstring_t *rep;
+ if (path[i] != CSTRING_SEP) {
+ rep = cstring_clone(path);
+ } else {
+ rep = new_cstring();
+ cstring_addf(rep, path, i + 1);
+ }
+
+ if (ext && ext[0] && cstring_ends_with(rep->string, ext)) {
+ cstring_cut_at(rep, rep->length - strlen(ext));
+ }
+
+ return cstring_convert(rep);
+}
+
+char *cstring_dirname(const char path[]) {
+ cstring_t *rep = cstring_clone(path);
+ cstring_pop_path(rep, 1);
+ return cstring_convert(rep);
+}
+
+int cstring_is_utf8(cstring_t *self) {
+ size_t rep = mbstowcs(NULL, self->string, 0);
+ // -2 = invalid, -1 = not whole
+ return (rep != (size_t) -2) && (rep != (size_t) -1);
+}
+
+char *cstring_concat(const char str1[], ...) {
+ if (!str1)
+ return NULL;
+
+ va_list args;
+ size_t total;
+ size_t prec;
+ char *arg;
+ char *ptr;
+ char *rep;
+
+ total = strlen(str1);
+ va_start(args, str1);
+ while ((arg = va_arg(args, char *))) {
+ total += strlen(arg);
+ }
+ va_end(args);
+
+ rep = malloc(total * sizeof(char) + 1);
+ ptr = rep;
+ ptr = strcpy(ptr, str1);
+ prec = strlen(str1);
+
+ va_start(args, str1);
+ while ((arg = va_arg(args, char *))) {
+ ptr = strcpy(ptr + prec, arg);
+ prec = strlen(arg);
+ }
+ va_end(args);
+
+ return rep;
+}
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2013 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file cstring.h
+ * @author Niki
+ * @date 2013 - 2022
+ *
+ * @brief Some string utility functions
+ *
+ * This file implements some basic functions of a string, most often by working
+ * directly with <tt>char *</tt> (but when needed with the provided
+ * <tt>cstring</tt> object).
+ */
+
+#ifndef CSTRING_H
+#define CSTRING_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+/**
+ * @brief A NUL-byte terminated string, with a known length
+ *
+ * The structure contains a suite of characters terminated by a NUL byte
+ * (or just a NUL byte), and a length.
+ * It is advised NOT to modify either of those directly.
+ * You can use <tt>cstring_convert</tt> to get a <tt>char *</tt>, though
+ * (in this case, the cstring <b>MUST NOT</b> be used again, and you are
+ * responsible for freeing said <tt>char *</tt>).
+ *
+ * @see cstring_convert
+ */
+typedef struct {
+ char CNAME[10];
+ char *string;
+ size_t length;
+ void *priv;
+} cstring_t;
+
+/**
+ * Instantiate a new cstring.
+ *
+ * @note always identical to <tt>malloc</tt> + <tt>init_cstring</tt>
+ *
+ * Create (and allocate the memory for) a new cstring.
+ * Do not forget to call cstring_free(cstring) when done.
+ *
+ * @see malloc()
+ * @see init_cstring(cstring_t *self)
+ */
+cstring_t *new_cstring();
+
+/**
+ * Instantiate a new cstring.
+ *
+ * Create (and allocate the memory for) a new cstring.
+ * Do not forget to call uninit_cstring(cstring_t *self) when done.
+ *
+ * @see new_cstring()
+ * @see uninit_cstring(cstring_t *self)
+ */
+int init_cstring(cstring_t *self);
+
+/**
+ * Free the given cstring.
+ *
+ * Free all the resources allocated for this cstring.
+ *
+ * @note always equivalent to <tt>uninit_cstring</tt> + <tt>free</tt>
+ *
+ * @see uninit_cstring(cstring_t *self)
+ * @see free(void *data)
+ */
+void free_cstring(cstring_t *self);
+
+/**
+ * Free the given cstring.
+ *
+ * Free all the resources allocated for this cstring.
+ *
+ * @param self the cstring to free, which MUST NOT be used again afterward
+ */
+void uninit_cstring(cstring_t *self);
+
+/**
+ * Grow the cstring to accommodate that many characters in addition to those
+ * already held, if needed.
+ *
+ * @param self the string to grow
+ * @param min the minimum number of extra characters it should be able to hold
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ * pressure)
+ */
+int cstring_grow(cstring_t *self, int min_extra);
+
+/**
+ * Grow the cstring to accommodate that many characters in total, if needed.
+ *
+ * @param self the string to grow
+ * @param min the minimum number of characters it should be able to hold
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ * pressure)
+ */
+int cstring_grow_to(cstring_t *self, int min_buffer);
+
+/**
+ * Compact the memory used by this string.
+ *
+ * @param self the string to work on
+ */
+void cstring_compact(cstring_t *self);
+
+/**
+ * Add a char at the end of the given cstring.
+ *
+ * @param self the cstring to work on
+ * @param source the character to add
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ * pressure)
+ */
+int cstring_add_car(cstring_t *self, char source);
+
+/**
+ * Add a string (a sequence of char that MUST end with '\0') at the end of the
+ * given cstring.
+ *
+ * @param self the string to work on
+ * @param source the string to add
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ * pressure)
+ */
+int cstring_add(cstring_t *self, const char source[]);
+
+/**
+ * Add a string (a sequence of char that MUST end with '\0') at the end of the
+ * given cstring, starting from index.
+ *
+ * @param self the cstring to work on
+ * @param source the string to add
+ * @param idx the starting index at which to copy from the source
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ * pressure)
+ */
+int cstring_addf(cstring_t *self, const char source[], size_t idx);
+
+/**
+ * Add a string (a sequence of char that MAY end with '\0') at the end of the
+ * given string, up to N chars long.
+ *
+ * @param self the string to work on
+ * @param source the string to add
+ * @param n the maximum number of chars to add (excluding the NUL byte), or 0
+ * to add the whole source (which MUST then end with a '\0')
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ * pressure)
+ */
+int cstring_addn(cstring_t *self, const char source[], size_t n);
+
+/**
+ * Add a string (a sequence of char that MAY end with '\0') at the end of the
+ * given cstring, starting from index, up to N chars long.
+ *
+ * @param self the string to work on
+ * @param source the string to add
+ * @param idx the starting index at which to copy from the source
+ * @param n the maximum number of chars to add (excluding the NUL byte) or 0
+ * to add the whole source (which MUST then end with a '\0')
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ * pressure)
+ */
+int cstring_addfn(cstring_t *self, const char source[], size_t idx, size_t n);
+
+/**
+ * Add a string via the usual <tt>printf</tt> formatters.
+ *
+ * @param self the string to add to
+ * @param fmt the required format specifiers (@{see printf})
+ * @param ... the printf-format parameters
+ *
+ * @return TRUE if success (FALSE means it was unable to grow due to memory
+ * pressure)
+ */
+int cstring_addp(cstring_t *self, const char *fmt, ...);
+
+/**
+ * Cut the string at the given size if it is greater.
+ *
+ * E.g.: it will have (at most) this many characters (without counting NULL) in
+ * it after.
+ *
+ * @param self the string to work on
+ * @param size the size to cut at (the maximum size of the cstring after this
+ * operation, NUL excepted)
+ */
+void cstring_cut_at(cstring_t *self, size_t size);
+
+/**
+ * Create a substring of this one.
+ *
+ * @param self the string to work on
+ * @param start the index to start at
+ * @param length the number of characters to copy, 0 for 'up to the end'
+ *
+ * @return a newly allocated cstring
+ */
+cstring_t *cstring_substring(const char self[], size_t start, size_t length);
+
+/**
+ * Split a cstring into "smaller" cstrings every time the given separator is found.
+ * Will also allow empty fields, ie: "a,b,,c" will return 4 cstrings, the third being empty).
+ *
+ * @param self the cstring to work on
+ * @param delim the separator, which can be longer than one character
+ *
+ * @return a list of cstring
+ */
+//TODO: use a []
+//clist *cstring_split(cstring *self, cstring *delim, cstring *quote);
+/**
+ * Split a cstring into "smaller" cstrings every time the given separator (which MUST end in \0) is found.
+ * Will also allow empty fields, ie: "a,b,,c" will return 4 cstrings, the third being empty).
+ *
+ * @param self the cstring to work on
+ * @param delim the separator, which can be longer than one character and MUST end with \0
+ *
+ * @return a list of cstring
+ */
+//TODO: use a []
+//clist *cstring_splits(cstring *self, const char delim[], const char quote[]);
+/**
+ * Split a cstring into "smaller" cstrings every time the given separator is found.
+ * Will also allow empty fields, ie: "a,b,,c" will return 4 cstrings, the third being empty).
+ *
+ * @param self the cstring to work on
+ * @param delim the separator
+ *
+ * @return a list of cstring
+ */
+//TODO: use a []
+//clist *cstring_splitc(cstring *self, char delim, char quote);
+/**
+ * Reverse the given string.
+ *
+ * @param self the string to work on
+ */
+void cstring_reverse(char *self);
+
+/**
+ * Replace all occurrences of a string inside the given cstring by another.
+ *
+ * @param self the string to work on
+ * @param from the string to replace
+ * @param to the replacement string
+ *
+ * @return the number of occurrences changed
+ */
+int cstring_replace(cstring_t *self, const char from[], const char to[]);
+
+/**
+ * Replace all occurrences of a char inside the given string by another.
+ *
+ * @param self the string to work on
+ * @param from the char to replace
+ * @param to the replacement char
+ *
+ * @return the number of occurrences changed
+ */
+int cstring_replace_car(char *self, char from, char to);
+
+/**
+ * Check if the string starts with the given pattern.
+ *
+ * @param self the string to work on
+ * @param find the string to find
+ * @param start_idx the index at which to start the comparison
+ *
+ * @return 1 if it does
+ */
+int cstring_starts_with(const char self[], const char find[], size_t start_idx);
+
+/**
+ * Check if the string ends with the given pattern.
+ *
+ * @param self the string to work on
+ * @param find the string to find (if empty, will always be found)
+ * @param start_index the index at which to start the comparison
+ *
+ * @return 1 if it does
+ */
+int cstring_ends_with(const char self[], const char find[]);
+
+/**
+ * Find the given substring in this one.
+ *
+ * @param self the string to work on
+ * @param find the string to find
+ * @param start_index the index at which to start the search
+ *
+ * @return the start index of the found string if found, or a negative value
+ * if not
+ */
+long cstring_find(const char self[], const char find[], size_t rstart_index);
+
+/**
+ * Find the given substring in this one, but search in the reverse direction.
+ *
+ * @param self the string to work on
+ * @param find the string to find
+ * @param rstart_index the index at which to start the search, or 0 for
+ * "end of string" (remember that it is reverse, you would then never
+ * find anything with a real rstart_index of 0), or a negative value
+ * to count from the end of the string (-2 means 2 character before the
+ * end)
+ *
+ * @return the start index of the found string if found, or a negative value
+ * if not
+ */
+long cstring_rfind(char self[], const char find[], long rstart_index);
+
+/**
+ * Clear (truncate its size to 0) the given string.
+ *
+ * @param self the string to work on
+ */
+void cstring_clear(cstring_t *self);
+
+/**
+ * Convert this cstring into a string
+ * This means that you MUST NOT call cstring_free nor use the cstring anymore.
+ * NULL will return NULL.
+ *
+ * @param self the cstring to work on
+ */
+char *cstring_convert(cstring_t *self);
+
+/**
+ * Clone this string.
+ * NULL will return NULL.
+ *
+ * @param self the string to clone
+ */
+cstring_t *cstring_clone(const char self[]);
+
+/**
+ * Trim this cstring of all trailing 'car' instances.
+ *
+ * @param self the cstring to work on
+ * @param car the character to trim (right only)
+ *
+ * @return a right trimmed cstring
+ */
+void cstring_rtrim(cstring_t *self, char car);
+
+/**
+ * Trim this cstring of all 'car' instances from the start and/or the
+ * end of the string.
+ *
+ * @param self the cstring to work on
+ * @param car the character to trim
+ *
+ * @return a trimmed cstring
+ */
+void cstring_trim(cstring_t *self, char car);
+
+/**
+ * Remove the \r and \n sequence (or one OR the other) at the end of the string.
+ *
+ * @param self the string to change
+ *
+ * @return the new length of the string
+ */
+size_t cstring_remove_crlf(char *self);
+
+/**
+ * Change the case to upper-case (UTF-8 compatible, but the string MUST be
+ * whole).
+ *
+ * @note: if LC_ALL is not set or is set to C and a viable $LANG exists, it will
+ * set LC_ALL to $LANG
+ *
+ * @param self the cstring to work on
+ */
+void cstring_toupper(cstring_t *self);
+
+/**
+ * Change the case to lower-case (UTF-8 compatible, but the string MUST be
+ * whole).
+ *
+ * @note: if LC_ALL is not set or is set to C and a viable $LANG exists, it will
+ * set LC_ALL to $LANG
+ *
+ * @param self the cstring to work on
+ */
+void cstring_tolower(cstring_t *self);
+
+/**
+ * Read a whole line (CR, LN or CR+LN terminated) from the given file stream.
+ *
+ * @param self the cstring to read into
+ * @param file the file to read
+ *
+ * @return 1 if a line was read, 0 if not
+ */
+int cstring_readline(cstring_t *self, FILE *file);
+
+/**
+ * Add a path to the given cstring (if it is currently empty, it
+ * will result in a root path).
+ *
+ * Will be separated by a forward '/' except on non-standard systems
+ * that uses reverse slash (i.e., Windows).
+ *
+ * @param self the base cstring (empty for a root path)
+ * @param subpath the path component to add
+ */
+void cstring_add_path(cstring_t *self, const char subpath[]);
+
+/**
+ * Remove the <tt>how_many</tt> components of the path described by this
+ * cstring. Will ignore extra path separators and always trim it from the final
+ * result (i.e., <tt>some//path/</tt> is identical to <tt>some/path</tt>).
+ *
+ * @note popping "0" path will simply make sure the string does not end in "/"
+ *
+ * @param how_many how many path components to remove (for instance, to go from
+ * <tt>/some/path/to/file</tt> to <tt>/some/path</tt> you would need 2)
+ */
+int cstring_pop_path(cstring_t *self, int how_many);
+
+/**
+ * Return the basename component of this path (for instance,
+ * '/home/user/file.ext' becomes 'file.ext').
+ *
+ * @param path the path to get the dir of (it can be a dir itself)
+ * @param ext the extension to remove if any (can be empty or NULL for none)
+ *
+ * @note the extension should include the "." if any
+ *
+ * @return a new string representing the parent directory
+ */
+char *cstring_basename(const char path[], const char ext[]);
+
+/**
+ * Return the dirname of this path (for instance,
+ * '/home/user/file.ext' becomes '/home/user').
+ *
+ * @param path the path to get the dir of (it can be a dir itself)
+ *
+ * @return a new string representing the parent directory
+ */
+char *cstring_dirname(const char path[]);
+
+/**
+ * Check if the string is a correct and whole UTF-8 string (i.e., it is indeed
+ * an UTF-8 string and doesn't contain incomplete UTF-8 sequences).
+ *
+ * @return TRUE if it is UTF-8
+ */
+int cstring_is_utf8(cstring_t *self);
+
+/**
+ * Concat all the given string and return the concatenation as a newly allocated
+ * string that you now own.
+ *
+ * @note the last parameter <b>must</b> be NULL
+ *
+ * @note if NULL is passed as first parameter, NULL will be returned
+ *
+ * @param str1 the first string
+ *
+ * @return the concatenated string or NULL if str1 is NULL
+ */
+char *cstring_concat(const char str1[], ...);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2020 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "cutils.h"
+#include "cstring.h"
+
+#ifndef strnlen
+size_t strnlen(const char *s, size_t maxlen) {
+ size_t i;
+ for (i = 0; s[i]; i++) {
+ if (i >= maxlen)
+ return maxlen;
+ }
+
+ return i;
+}
+#endif
+
+#ifndef strdup
+char *strdup(const char *source) {
+ size_t sz = strlen(source);
+ char *new = malloc((sz + 1) * sizeof(char));
+ strcpy(new, source);
+ return new;
+}
+#endif
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2020 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file cutils.h
+ * @author Niki
+ * @date 2020 - 2022
+ *
+ * @brief Include all the other .h as well as C99-compatible
+ * <tt>strdup</tt>/<tt>strnlen</tt> functions if they are not already defined
+ */
+#ifndef CUTILS_H
+#define CUTILS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "cstring.h"
+#include "array.h"
+#include "desktop.h"
+#include "print.h"
+#include "timing.h"
+
+/* Helps with C99 compatibility for code that is not */
+
+#if _POSIX_C_SOURCE < 200809L && _XOPEN_SOURCE < 500
+#ifndef _GNU_SOURCE
+/**
+ * The strnlen() function returns the number of bytes in the string pointed to
+ * by s, excluding the terminating null byte ('\0'), but at most maxlen.
+ * In doing this, strnlen() looks only at the first maxlen characters in the
+ * string pointed to by s and never beyond s[maxlen-1].
+ *
+ * @return The strnlen() function returns strlen(s), if that is less than
+ * maxlen, or maxlen if there is no null terminating ('\0') among the first
+ * maxlen characters pointed to by s
+ */
+size_t strnlen(const char *s, size_t maxlen);
+#endif
+#endif
+#if _POSIX_C_SOURCE < 200809L && _XOPEN_SOURCE < 500
+/**
+ * The strdup() function returns a pointer to a new string which is a
+ * duplicate of the string s. Memory for the new string is obtained with
+ * malloc(3), and can be freed with free(3).
+ *
+ *
+ * @return On success, the strdup() function returns a pointer to the duplicated
+ * string. It returns NULL if insufficient memory was available, with
+ * errno set to indicate the error.
+ */
+char *strdup(const char *source);
+#endif
+
+/* */
+
+#endif // CUTILS_H
+
+#ifdef __cplusplus
+}
+#endif
+
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2021 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <dirent.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+#include "cutils.h"
+
+#define EXT "desktop"
+
+/* Private functions */
+static int desktop_compare(const void *a, const void* b);
+static int desktop_test_file(const char filename[]);
+/* */
+
+desktop_t *new_desktop(const char filename[], int best_size) {
+ desktop_t *me = malloc(sizeof(desktop_t));
+ if (!init_desktop(me, filename, best_size)) {
+ free(me);
+ me = NULL;
+ }
+
+ return me;
+}
+
+int init_desktop(desktop_t *me, const char filename[], int best_size) {
+ strcpy(me->CNAME, "[Desktop]");
+
+ me->name = NULL;
+ me->exec = NULL;
+ me->icon = NULL;
+ me->icon_file = NULL;
+ me->children = NULL;
+ me->id = 0;
+
+ // Copy name
+ me->name = strdup(filename);
+
+ // Get extension an remove ".desktop" from name
+ char *ext = rindex(me->name, '.');
+ if (ext) {
+ size_t idot = (ext - me->name);
+ ext++;
+ if (!strcmp(ext, EXT))
+ me->name[idot] = '\0';
+ }
+ if (ext)
+ ext = strdup(ext);
+
+ // If PNG of the same name, use as default icon
+ me->icon_file = desktop_find_icon(me->name, best_size);
+
+ // Pretify default name (remove dir part)
+ char *slash = rindex(me->name, '/');
+ if (slash && !slash[1]) {
+ slash[0] = '\0';
+ slash = rindex(me->name, '/');
+ }
+ if (slash) {
+ char *copy = strdup(slash + 1);
+ free(me->name);
+ me->name = copy;
+ }
+
+ // Try with the base name, too
+ if (!me->icon_file) {
+ me->icon_file = desktop_find_icon(me->name, best_size);
+ }
+
+ DIR *dp = opendir(filename);
+ if (dp) {
+ // Try to get the default folder icon if no icon yet
+ if (!me->icon_file) {
+ me->icon_file = desktop_find_icon("folder", best_size);
+ }
+
+ me->children = new_array(sizeof(desktop_t), 32);
+ for (struct dirent *ep = readdir(dp); ep; ep = readdir(dp)) {
+ if (!strcmp(ep->d_name, "."))
+ continue;
+ if (!strcmp(ep->d_name, ".."))
+ continue;
+
+ desktop_t *child = array_new(me->children);
+ char *childname = cstring_concat(filename, "/", ep->d_name, NULL);
+ if (!init_desktop(child, childname, best_size))
+ array_pop(me->children);
+ free(childname);
+ }
+
+ array_qsort(me->children, desktop_compare);
+
+ closedir(dp);
+ free(ext);
+ return 1;
+ }
+
+ // Only process ".desktop" files
+ if (!ext || strcmp(ext, EXT)) {
+ uninit_desktop(me);
+ free(ext);
+ return 0;
+ }
+
+ FILE *file;
+ cstring_t *line;
+ char *startsWith;
+
+ file = fopen(filename, "r");
+ if (file) {
+ line = new_cstring();
+ while (cstring_readline(line, file)) {
+ startsWith = "Name=";
+ if (cstring_starts_with(line->string, startsWith, 0)) {
+ free(me->name);
+ me->name = strdup(line->string + strlen(startsWith));
+ }
+
+ startsWith = "Exec=";
+ if (cstring_starts_with(line->string, startsWith, 0)) {
+ free(me->exec);
+ me->exec = strdup(line->string + strlen(startsWith));
+ // TODO: %f %F %u %U %i %c %k: inject values instead
+ char *cars = "ifFuUck";
+ for (char *ptr = index(me->exec, '%'); ptr;
+ ptr = index(ptr, '%')) {
+ if (index(cars, ptr[1])) {
+ ptr[0] = ' ';
+ ptr[1] = ' ';
+ }
+ ptr++;
+ }
+ }
+
+ startsWith = "Icon=";
+ if (cstring_starts_with(line->string, startsWith, 0)) {
+ free(me->icon);
+ me->icon = strdup(line->string + strlen(startsWith));
+ }
+ }
+ free_cstring(line);
+ fclose(file);
+ }
+
+ // Find icon file linked to icon
+ if (me->icon && !me->icon_file) {
+ me->icon_file = desktop_find_icon(me->icon, best_size);
+ }
+ // ...or any we can find, actually
+ if (!me->icon_file) {
+ me->icon_file = desktop_find_icon(me->name, best_size);
+ }
+
+ free(ext);
+ return 1;
+}
+
+void free_desktop(desktop_t *me) {
+ if (me)
+ uninit_desktop(me);
+
+ free(me);
+}
+
+void uninit_desktop(desktop_t *me) {
+ free(me->name);
+ free(me->exec);
+ free(me->icon);
+ free(me->icon_file);
+ me->name = NULL;
+ me->exec = NULL;
+ me->icon = NULL;
+ me->icon_file = NULL;
+ free_array(me->children);
+ me->children = NULL;
+ me->CNAME[0] = '!';
+}
+
+desktop_t *desktop_find_id(array_t *children, int id) {
+ desktop_t *found = NULL;
+
+ array_loop(children, child, desktop_t)
+ {
+ if (child->id == id) {
+ found = child;
+ break;
+ }
+
+ if (child->children) {
+ found = desktop_find_id(child->children, id);
+ break;
+ }
+ }
+
+ return found;
+}
+
+/* Private functions */
+
+static int desktop_compare(const void *a, const void* b) {
+ desktop_t *me1 = (desktop_t *)a;
+ desktop_t *me2 = (desktop_t *)b;
+
+ if (me1->children && !(me2->children))
+ return -1;
+ if (!(me1->children) && me2->children)
+ return 1;
+
+ return strcmp(me1->name, me2->name);
+}
+
+static int desktop_test_file(const char filename[]) {
+ FILE *test;
+ DIR *test_dir;
+
+ test = fopen(filename, "r");
+ if (test) {
+ fclose(test);
+ test_dir = opendir(filename);
+ if (test_dir) {
+ closedir(test_dir);
+ } else {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+#define TRY_DIR(a,b,c) \
+ do { \
+ tmp = cstring_concat(a, b, c, basename, ".png", NULL); \
+ if(desktop_test_file(tmp)) \
+ return tmp; \
+ free(tmp); \
+ } while(0)
+
+static char *theme = NULL;
+static char *ltheme = NULL;
+char *desktop_find_icon(const char basename[], int icon_size) {
+ char *tmp = NULL;
+ char *home = getenv("HOME");
+ char icon_size_str[100];
+ sprintf(icon_size_str, "%dx%d", icon_size, icon_size);
+
+ if (!theme) {
+ tmp = cstring_concat(home, "/.gtkrc-2.0", NULL);
+ FILE *file = fopen(tmp, "r");
+ free(tmp);
+ if (file) {
+ const char *startsWith = "gtk-icon-theme-name=";
+ size_t n = strlen(startsWith);
+
+ cstring_t *line = new_cstring();
+ while (cstring_readline(line, file)) {
+ if (cstring_starts_with(line->string, startsWith, 0)) {
+ free(theme);
+ if (line->string[n] == '"') {
+ theme = strdup(line->string + n + 1);
+ theme[strlen(theme) - 1] = '\0';
+ } else {
+ theme = strdup(line->string + n);
+ }
+ }
+ }
+ free_cstring(line);
+ fclose(file);
+
+ if (!theme || !theme[0]) {
+ theme = strdup("");
+ ltheme = strdup("");
+ } else {
+ tmp = theme;
+ theme = cstring_concat("/usr/share/icons/", tmp, "/", NULL);
+ ltheme = cstring_concat(home, "/", ".icons/", tmp, "/", NULL);
+ free(tmp);
+ }
+
+ }
+ }
+
+ // Allow NULL
+ if (!basename || !basename[0])
+ return NULL;
+
+ // exact match
+ tmp = strdup(basename);
+ if (desktop_test_file(tmp))
+ return tmp;
+ free(tmp);
+
+ // same name, with ".png"
+ TRY_DIR("", "", "");
+
+ // local icons
+ TRY_DIR(home, "/.local/share/icons/", "");
+
+ // theme icons
+ if (theme && theme[0]) {
+ // exact size (apps, places)
+ TRY_DIR(theme, icon_size_str, "/apps/");
+ TRY_DIR(theme, icon_size_str, "/places/");
+
+ // scalable (apps, places)
+ TRY_DIR(ltheme, "scalable", "/apps/");
+ TRY_DIR(ltheme, "scalable", "/places/");
+ }
+
+ // shared icons, exact size (apps, places)
+ TRY_DIR("/usr/share/icons/hicolors/", icon_size_str, "/apps/");
+ TRY_DIR("/usr/share/icons/hicolors/", icon_size_str, "/places/");
+
+ // shared icons, scalable (apps, places)
+ TRY_DIR("/usr/share/icons/hicolors/scalable/apps/", "", "");
+ TRY_DIR("/usr/share/icons/hicolors/scalable/places/", "", "");
+
+ return NULL;
+}
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2021 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file desktop.h
+ * @author Niki
+ * @date 2021 - 2022
+ *
+ * @brief Manipulate <tt>.desktop</tt> files (as described by
+ * <a href='https://freedesktop.org/'>FreeDesktop.org</a>)
+ *
+ * This structure helps you manipulate <tt>.desktop</tt> files (as described by
+ * <a href='https://freedesktop.org/'>FreeDesktop.org</a>).
+ *
+ * @note the desktop object can use icons; for the selection of those, an exact
+ * match will first be tried (same name as the <tt>desktop</tt> file, with
+ * a <tt>.png</tt> extension), then we will look into the local
+ * <tt>.local/share/icons</tt> and if we still haven't found an icon, into
+ * the theme (first by looking for a <tt>best_size</tt> sized icon and if
+ * not in <tt>scalable</tt>)
+ *
+ * @note we support the use of desktop objects for menu, too, and that includes
+ * submenu items support
+ */
+#ifndef DESKTOP_H
+#define DESKTOP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+
+#include "array.h"
+
+/**
+ * The structure used to represent desktop objects.
+ */
+struct {
+ char CNAME[10];
+ /** The user name of the desktop object. */
+ char *name;
+ /** The icon name, if any. */
+ char *icon;
+ /** The icon file that corresponds, if any. */
+ char *icon_file;
+ /** The EXEC command to start. */
+ char *exec;
+ /** The submenu items of this desktop object (for a menu/submenu). */
+ array_t *children;
+ /** A custom external ID for this desktop object, for your own use. */
+ int id;
+}typedef desktop_t;
+
+/**
+ * Create a new desktop object from the given <tt>.desktop</tt> file.
+ *
+ * @note always identical to <tt>malloc</tt> + <tt>init_desktop</tt>
+ *
+ * @param filename the path to the actual <tt>.desktop</tt> file
+ * @param best_size the default size for the icon (see icon selection in the
+ * description of the {@see desktop} object
+ *
+ * @see malloc()
+ * @see init_desktop(desktop_t *self, const char filename[], int best_size)
+ * @see free_desktop(desktop_t *self)
+ *
+ * @return the desktop object
+ */
+desktop_t *new_desktop(const char filename[], int best_size);
+
+/**
+ * Create a new desktop object from the given <tt>.desktop</tt> file.
+ *
+ * @param filename the path to the actual <tt>.desktop</tt> file
+ * @param best_size the default size for the icon (see icon selection in the
+ * description of the {@see desktop} object
+ *
+ * @see new_desktop(const char filename[], int best_size)
+ * @see uninit_desktop(desktop_t *self)
+ *
+ * @return TRUE if success (could fail if the target is not a <tt>.desktop</tt>
+ * file
+ */
+int init_desktop(desktop_t *self, const char filename[], int best_size);
+
+/**
+ * Free the given desktop object.
+ *
+ * @note always equivalent to <tt>uninit_desktop</tt> + <tt>free</tt>
+ *
+ * @see uninit_desktop(desktop_t *self)
+ * @see free(void *data)
+ */
+void free_desktop(desktop_t *self);
+
+/**
+ * Free the resources used by the given desktop object -- do not use it anymore
+ * after this call.
+ */
+void uninit_desktop(desktop_t *self);
+
+/**
+ * Find a submenu item by the given ID ({@see desktop_set_id(desktop *, int)}).
+ *
+ * TODO: use full objects instead
+ * @param children the array of pointers to desktop objects to look through
+ * @param menu_id the ID of the submenu we want to find
+ *
+ * @return the given submenu if found, or NULL
+ */
+desktop_t *desktop_find_id(array_t *children, int menu_id);
+
+/**
+ * Look for the icon file related to this basename.
+ *
+ * @param basename the base name of the icon we want to look for
+ * @param icon_size the best_size to use for the icon (see the description of
+ * the {@desktop} object)
+ *
+ * @return the path to the best related icon we found (you own it), or NULL
+ */
+char *desktop_find_icon(const char basename[], int icon_size);
+
+#endif /* DESKTOP_H */
+
+#ifdef __cplusplus
+}
+#endif
+
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2012 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <signal.h>
+
+#ifndef WIN32
+ #include <fcntl.h>
+ #include <netdb.h>
+#else
+ #include <ws2tcpip.h>
+#endif
+
+#include "net.h"
+
+#define bool int
+#define true 1
+#define false 0
+
+/**
+ * Get the sockaddr, IPv4 or IPv6.
+ * @param sa the socket address is in this structure
+ * @return the sockaddr_in or sockaddr_in6 inside this socketaddr,
+ * depending if it is IPv4 or IPv6
+ */
+void *get_in_addr(struct sockaddr *sa);
+
+#ifndef WIN32
+/**
+ * Reap all zombie processes.
+ * This function will be called when a child process terminates, and
+ * will loop on all zombie processes to properly acknowledge them
+ * so they can die.
+ *
+ * @param s
+ */
+void sigchld_handler(int pid);
+
+void sigchld_handler(int pid) {
+ if (pid > 0 || pid < 0) pid = 0;
+
+ // Reap all zombie processes
+ while (waitpid(-1, NULL, WNOHANG) > 0);
+}
+#endif
+
+int net_init(){
+#if defined (WIN32)
+ WSADATA WSAData;
+ return !WSAStartup(MAKEWORD(2,2), &WSAData);
+#endif
+ return 1;
+}
+
+void net_cleanup(){
+#if defined (WIN32)
+ WSACleanup();
+#endif
+}
+
+int net_set_non_blocking(int fd) {
+ return net_set_blocking(fd, 0);
+}
+
+int net_set_blocking(int fd, int block) {
+ int flags;
+
+/* If they have O_NONBLOCK, use the POSIX way to do it */
+#if defined (O_NONBLOCK)
+ /* O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */
+ if ((flags = fcntl(fd, F_GETFL, 0)) == -1) {
+ flags = 0;
+ }
+ if (block) {
+ return fcntl(fd, F_SETFL, flags ^ O_NONBLOCK);
+ }
+ else {
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ }
+#else
+ flags = block?0:1;
+ return ioctl(fd, FIONBIO, (int)(&flags));
+#endif
+}
+
+int net_connect(const char server[], int port) {
+ int sockfd;
+ struct addrinfo hints, *servinfo, *p;
+ int rv;
+ char string[10];
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ // convert the port number to a string
+ sprintf(string, "%i%c", port, '\0');
+ rv = getaddrinfo(server, string, &hints, &servinfo);
+ //
+
+ if (rv != 0) {
+ // DO NOT dirty the stderr
+ //fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
+ return -1;
+ }
+
+ // loop through all the results and connect to the first we can
+ for (p = servinfo; p != NULL; p = p->ai_next) {
+ if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))
+ == -1) {
+ //perror("client: socket");
+ continue;
+ }
+
+ if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
+ close(sockfd);
+ //perror("client: connect");
+ continue;
+ }
+
+ break;
+ }
+
+ if (p == NULL) {
+ //fprintf(stderr, "client: failed to connect\n");
+ return -1;
+ }
+
+ freeaddrinfo(servinfo);
+
+ return sockfd;
+}
+
+int net_accept(int ssocketd) {
+ struct sockaddr_storage their_addr;
+ socklen_t sin_size;
+
+ sin_size = sizeof(their_addr);
+ return accept(ssocketd, (struct sockaddr *) &their_addr, &sin_size);
+}
+
+int net_listen(int port, int backlog) {
+#ifndef WIN32
+ struct sigaction sa;
+#endif
+ int sockfd;
+ struct addrinfo hints, *servinfo, *p;
+ char yes = 1;
+ int rv;
+ char string[10];
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE; // use my IP
+
+ // convert the port number to a string
+ sprintf(string, "%i%c", port, '\0');
+ rv = getaddrinfo(NULL, string, &hints, &servinfo);
+ //
+
+ if (rv != 0) {
+ // DO NOT dirty the stderr
+ //fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
+ return -1;
+ }
+
+ // loop through all the results and bind to the first we can
+ for (p = servinfo; p != NULL; p = p->ai_next) {
+ if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
+ //perror("server: socket");
+ continue;
+ }
+
+ if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
+ //perror("setsockopt");
+ return -1;
+ }
+
+ if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
+ close(sockfd);
+ //perror("server: bind");
+ continue;
+ }
+
+ break;
+ }
+
+ if (p == NULL) {
+ //fprintf(stderr, "server: failed to bind\n");
+ return -1;
+ }
+
+ // all done with this structure
+ freeaddrinfo(servinfo);
+
+ if (listen(sockfd, backlog) == -1) {
+ //perror("listen");
+ return -1;
+ }
+
+#ifndef WIN32
+ sa.sa_handler = sigchld_handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ if (sigaction(SIGCHLD, &sa, NULL) == -1) {
+ //perror("sigaction");
+ return -1;
+ }
+#endif
+
+ return sockfd;
+}
+
+void net_close_socketd(int socketd) {
+ close(socketd);
+}
+
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+ssize_t net_write(int fd, const void *buf, size_t n) {
+ // In UNIX: send() with flag set to '0' == write()
+ // In WIN32: cannot write() to a socket
+ return send(fd, (char *)buf, n, MSG_NOSIGNAL);
+}
+
+ssize_t net_read(int fd, void *buf, size_t nbytes) {
+ // In UNIX: recv() with flag set to '0' == send()
+ // In WIN32: cannot read() from a socket
+ return recv(fd, (char *)buf, nbytes, 0);
+}
+
+void *get_in_addr(struct sockaddr *sa) {
+ if (sa->sa_family == AF_INET) {
+ return &(((struct sockaddr_in*) sa)->sin_addr);
+ }
+ else {
+ return &(((struct sockaddr_in6*) sa)->sin6_addr);
+ }
+}
+
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2012 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file net.h
+ * @author niki
+ * @date 2011 - 2022
+ *
+ * @brief Send/receive data from the network
+ *
+ * Allows you to make connections to/from a server, and to send/receive data
+ * through those connections.
+ */
+
+#ifndef NET_H
+#define NET_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// WHY ??
+#ifdef _WIN32
+#ifndef WIN32
+#define WIN32
+#endif
+#endif
+
+#ifdef WIN32
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x501
+#endif
+#include <winsock2.h>
+#include <ws2tcpip.h>
+typedef SSIZE_T ssize_t;
+#define close(s) closesocket(s)
+#define ioctl(a,b,c) ioctlsocket(a,b,c)
+#pragma comment(lib, "wsock32.lib");
+#pragma comment(lib, "ws2_32.lib");
+#else
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <sys/wait.h>
+#endif
+
+/* for ssize_t */
+#include <sys/types.h>
+
+/**
+ * You must call this function before doing any network related operation,
+ * because of some legacy WIN32 rule.
+ * (It is a noop on all other platforms.)
+ *
+ * @return FALSE when the Windows host does not support WinSock and thus,
+ * no network for you (you still need to call net_cleanup)
+ */
+int net_init();
+
+/**
+ * You must call this function after you are done using network related
+ * operations within this DLL, because of some legacy WIN32 rule.
+ * (It is a noop on all other platforms.)
+ */
+void net_cleanup();
+
+/**
+ * Set the given socket to (non-)blocking I/O mode.
+ * This function can work with file sockets, too.
+ *
+ * @param fd the file descriptor or socket to change
+ * @param block TRUE to block, FALSE not to block
+ *
+ * @return TRUE if success
+ */
+int net_set_blocking(int fd, int block);
+
+/**
+ * Connect to this server on this port, and return a socket descriptor
+ * to write to or read from it.
+ *
+ * @param server the server to connect to
+ * @param port the port to connect on
+ *
+ * @return the server socket or a negative value on error
+ */
+int net_connect(const char server[], int port);
+
+/**
+ * Open a port and returns a (server) socket descriptor from which you can
+ * accept connections.
+ *
+ * @param port the port to connect on
+ * @param backlog the maximum number of client connections we will queue for
+ * this socket until you handle them
+ *
+ * @return the server socket, or a negative value on error
+ */
+int net_listen(int port, int backlog);
+
+/**
+ * Block (or not) and wait for a client to connect on this socket.
+ * When this is done, return a socket to this specific client/server
+ * connection. It can takes the connections from a queue,
+ * as defined in net_listen.
+ *
+ * @param ssocketd the server socket on which to accept a connection
+ *
+ * @return the socket, or a negative value on error
+ */
+int net_accept(int ssocketd);
+
+/**
+ * Close a socket (or a server socket).
+ *
+ * @param socketd the (server) socket to close
+ */
+void net_close_socketd(int socketd);
+
+/**
+ * Write to this socket, as you would with a file.
+ *
+ * @param fd the socket to write to
+ * @param buf the buffer to read from
+ * @param n the number of bytes to write
+ *
+ * @return the actual number of bytes written or a negative number if error
+ */
+ssize_t net_write(int fd, const void *buf, size_t n);
+
+/**
+ * Read from this socket, as you would with a file.
+ *
+ * @param fd the socket to read from
+ * @param buf the buffer to write to
+ * @param nbytes the number of bytes to read
+ *
+ * @return the actual number of bytes read
+ */
+ssize_t net_read(int fd, void *buf, size_t nbytes);
+
+#endif
+
+#ifdef __cplusplus
+extern}
+#endif
--- /dev/null
+/**
+ * @file print.h
+ * @author Samantaz Fox
+ * @date 2019
+ *
+ * @brief File name and line numbers
+ *
+ * Small utility header designed to print file name and line number
+ * along with debug/info messages.
+ *
+ * Usage example:
+ * ERROR("program exited with code %d\n", getErrorCode() );
+ *
+ * Known issues:
+ * - Must take at least one argument in addition to format string
+ *
+ *
+ * Copyright 2019 (C) Samantaz Fox
+ * https://github.com/SamantazFox/Micro-projects/raw/master/library/print.h
+ *
+ * This file is in the public domain.
+ * Feel free to copy, modify or redistribute it!
+*/
+
+#ifndef __PRINT_H__
+#define __PRINT_H__
+
+
+#ifdef __cplusplus
+ #include <cstdio>
+#else
+ #include <stdio.h>
+#endif
+
+
+#define TRACE(_format, ...) \
+ do { \
+ fprintf(stdout, "[Trace] %s:%d\t" _format, __FILE__, __LINE__, __VA_ARGS__); \
+ } while (0)
+
+#define DEBUG(_format, ...) \
+ do { \
+ fprintf(stdout, "[Debug] %s:%d\t" _format, __FILE__, __LINE__, __VA_ARGS__); \
+ } while (0)
+
+#define INFO(_format, ...) \
+ do { \
+ fprintf(stdout, "[Info ] %s:%d\t" _format, __FILE__, __LINE__, __VA_ARGS__); \
+ } while (0)
+
+#define WARN(_format, ...) \
+ do { \
+ fprintf(stderr, "[Warn ] %s:%d\t" _format, __FILE__, __LINE__, __VA_ARGS__); \
+ } while (0)
+
+#define ERROR(_format, ...) \
+ do { \
+ fprintf(stderr, "[Error] %s:%d\t" _format, __FILE__, __LINE__, __VA_ARGS__); \
+ } while (0)
+
+
+#endif /* !__PRINT_H__ */
--- /dev/null
+/*
+ * CUtils: some small C utilities
+ *
+ * Copyright (C) 2020 Niki Roo
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file timing.h
+ * @author Niki
+ * @date 2020 - 2024
+ *
+ * @brief Timing macros START and STOP
+ *
+ * 2 macro are provided to print the elapsed time between the 2 to stdout.
+ */
+
+#ifndef TIMING_H
+#define TIMING_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/time.h>
+
+/**
+ * Start the timer.
+ */
+#define START struct timeval TIMING_start, TIMING_stop; \
+ /* 1 usec = 0.000001 s */ \
+ char cusec[7]; \
+ gettimeofday(&TIMING_start, NULL);
+
+/**
+ * Stop the timer and print the elapsed time to stdout.
+ */
+#define STOP gettimeofday(&TIMING_stop, NULL); \
+ TIMING_stop.tv_sec = TIMING_stop.tv_sec - TIMING_start.tv_sec; \
+ TIMING_stop.tv_usec = TIMING_stop.tv_usec - TIMING_start.tv_usec; \
+ sprintf(cusec, "%0.6d", TIMING_stop.tv_usec); \
+ printf("TIME: %d.%s sec\n", TIMING_stop.tv_sec, cusec); \
+ gettimeofday(&TIMING_start, NULL);
+
+#endif // TIMING_H
+
+#ifdef __cplusplus
+}
+#endif
+