diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..7f44e8b2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,29 @@
+*.a
+*.la
+*.lo
+*.log
+*.o
+*.stamp
+*.swp
+*.trs
+*~
+.config.args
+.deps/
+.dirstamp
+.libs/
+/*.tar.xz
+Makefile
+Makefile.in
+autom4te.cache/
+aclocal.m4
+build-aux/
+config.h
+config.h.in
+config.log
+config.status
+configure
+libtool
+m4/
+ratbagctl
+ratbagd
+stamp-*
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 00000000..e721ab8e
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,93 @@
+# ------------------------------------------------------------------------------
+# main
+
+ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
+AM_MAKEFLAGS = --no-print-directory
+AUTOMAKE_OPTIONS = color-tests parallel-tests
+
+GCC_COLORS ?= 'ooh, shiny!'
+export GCC_COLORS
+
+SUBDIRS = .
+
+# remove targets if the command fails
+.DELETE_ON_ERROR:
+
+# keep intermediate files
+.SECONDARY:
+
+# Keep the test-suite.log
+.PRECIOUS: $(TEST_SUITE_LOG) Makefile
+
+CLEANFILES = $(BUILT_SOURCES)
+DISTCLEANFILES =
+EXTRA_DIST =
+BUILT_SOURCES =
+lib_LTLIBRARIES =
+noinst_LTLIBRARIES =
+bin_PROGRAMS =
+noinst_PROGRAMS =
+
+AM_CPPFLAGS = \
+ -include $(top_builddir)/config.h \
+ -I $(top_srcdir)/src \
+ -I $(top_builddir)/src \
+ $(OUR_CPPFLAGS)
+
+AM_CFLAGS = $(OUR_CFLAGS)
+AM_LDFLAGS = $(OUR_LDFLAGS)
+
+# ------------------------------------------------------------------------------
+# libshared
+
+noinst_LTLIBRARIES += \
+ libshared.la
+
+libshared_la_SOURCES = \
+ src/shared-macro.h \
+ src/shared-rbtree.h \
+ src/shared-rbtree.c
+
+libshared_la_CFLAGS = \
+ $(AM_CFLAGS)
+
+libshared_la_LIBADD = \
+ -lm
+
+# ------------------------------------------------------------------------------
+# ratbagctl
+
+bin_PROGRAMS += \
+ ratbagctl
+
+ratbagctl_SOURCES = \
+ src/ratbagctl.c
+
+ratbagctl_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(LIBSYSTEMD_CFLAGS)
+
+ratbagctl_LDADD = \
+ libshared.la \
+ $(LIBSYSTEMD_LIBS)
+
+# ------------------------------------------------------------------------------
+# ratbagd
+
+bin_PROGRAMS += \
+ ratbagd
+
+ratbagd_SOURCES = \
+ src/ratbagd.h \
+ src/ratbagd.c \
+ src/ratbagd-device.c
+
+ratbagd_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(UDEV_CFLAGS) \
+ $(LIBSYSTEMD_CFLAGS)
+
+ratbagd_LDADD = \
+ libshared.la \
+ $(UDEV_LIBS) \
+ $(LIBSYSTEMD_LIBS)
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 00000000..dbb5141d
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# autogen.sh: prepare build system
+# Run this script to prepare the build-system for your local checkout. It
+# requires the usual autotools dependencies to be installed.
+#
+# Prepare build system, but don't configure:
+# $ ./autogen.sh
+#
+# Prepare build system and run ./configure with debugging enabled:
+# $ ./autogen.sh c
+#
+# Prepare build system and run ./configure with llvm over gcc:
+# $ ./autogen.sh l
+#
+# You can put additional arguments to ./configure into ".config.args". They
+# will be appended to the arguments used by this script.
+#
+
+set -e
+
+oldpwd=$(pwd)
+topdir=$(dirname $0)
+cd $topdir
+
+autoreconf --force --install --symlink
+
+if [ -f "$topdir/.config.args" ]; then
+ args="$args $(cat $topdir/.config.args)"
+fi
+
+cd $oldpwd
+
+if [ "x$1" = "xc" ]; then
+ $topdir/configure --enable-debug $args
+ make clean
+elif [ "x$1" = "xl" ]; then
+ $topdir/configure CC=clang $args
+ make clean
+else
+ echo
+ echo "----------------------------------------------------------------"
+ echo "Initialized build system. For a common configuration please run:"
+ echo "----------------------------------------------------------------"
+ echo
+ echo "$topdir/configure $args"
+ echo
+fi
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 00000000..2d40488d
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,151 @@
+# ------------------------------------------------------------------------------
+# main
+
+AC_PREREQ([2.64])
+
+AC_INIT([ratbagd],
+ [1],
+ [http://www.github.com/libratbag/ratbagd],
+ [ratbagd],
+ [http://www.github.com/libratbag/ratbagd])
+
+AC_CONFIG_SRCDIR([src/ratbagd.h])
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_AUX_DIR([build-aux])
+
+AC_USE_SYSTEM_EXTENSIONS
+AC_SYS_LARGEFILE
+AM_MAINTAINER_MODE([enable])
+AM_INIT_AUTOMAKE([foreign 1.11 -Wall -Wno-portability silent-rules tar-pax no-dist-gzip dist-xz subdir-objects parallel-tests])
+AM_SILENT_RULES([yes])
+AC_CANONICAL_HOST
+AC_DEFINE_UNQUOTED([CANONICAL_HOST], "$host", [Canonical host string.])
+
+LT_PREREQ(2.2)
+LT_INIT([disable-static])
+
+AC_PROG_MKDIR_P
+AC_PROG_LN_S
+AC_PROG_SED
+AC_PROG_GREP
+AC_PROG_AWK
+AC_PROG_CC_C99
+
+AC_PATH_PROG([M4], [m4])
+AC_PATH_PROG([XSLTPROC], [xsltproc])
+
+AS_IF([! ln --relative --help > /dev/null 2>&1], [AC_MSG_ERROR([*** ln doesn't support --relative ***])])
+
+# ------------------------------------------------------------------------------
+# arguments
+
+AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [enable debug options]))
+
+# ------------------------------------------------------------------------------
+# toolchain
+
+CC_CHECK_FLAGS_APPEND([with_cflags], [CFLAGS], [\
+ -pipe \
+ -Wall \
+ -Wextra \
+ -Wno-inline \
+ -Wundef \
+ "-Wformat=2 -Wformat-security -Wformat-nonliteral" \
+ -Wlogical-op \
+ -Wsign-compare \
+ -Wmissing-include-dirs \
+ -Wold-style-definition \
+ -Wpointer-arith \
+ -Winit-self \
+ -Wdeclaration-after-statement \
+ -Wfloat-equal \
+ -Wsuggest-attribute=noreturn \
+ -Wmissing-prototypes \
+ -Wstrict-prototypes \
+ -Wredundant-decls \
+ -Wmissing-declarations \
+ -Wmissing-noreturn \
+ -Wendif-labels \
+ -Wstrict-aliasing=2 \
+ -Wwrite-strings \
+ -Wno-long-long \
+ -Wno-overlength-strings \
+ -Wno-unused-parameter \
+ -Wno-missing-field-initializers \
+ -Wno-unused-result \
+ -Werror=overflow \
+ -Wdate-time \
+ -Wnested-externs \
+ -ffast-math \
+ -fno-common \
+ -fdiagnostics-show-option \
+ -fno-strict-aliasing \
+ -fvisibility=hidden \
+ -ffunction-sections \
+ -fdata-sections \
+ -fstack-protector \
+ -fstack-protector-strong \
+ -fPIE \
+ --param=ssp-buffer-size=4])
+
+AS_CASE([$CC], [*clang*],
+ [CC_CHECK_FLAGS_APPEND([with_cppflags], [CPPFLAGS], [ \
+ -Wno-typedef-redefinition \
+ -Wno-gnu-variable-sized-type-not-at-end \
+ ])])
+
+CC_CHECK_FLAGS_APPEND([with_ldflags], [LDFLAGS], [\
+ -Wl,--as-needed \
+ -Wl,--no-undefined \
+ -Wl,--gc-sections \
+ -Wl,-z,relro \
+ -Wl,-z,now \
+ -pie])
+
+AS_IF([test "x$enable_debug" = "xyes"],
+ [
+ CC_CHECK_FLAGS_APPEND([with_cflags], [CFLAGS], [ \
+ -g \
+ -O0 \
+ -ftrapv])
+ ], [
+ CC_CHECK_FLAGS_APPEND([with_cppflags], [CPPFLAGS], [\
+ -Wp,-D_FORTIFY_SOURCE=2])
+ ])
+
+AC_SUBST([OUR_CFLAGS], "$with_cflags")
+AC_SUBST([OUR_CPPFLAGS], "$with_cppflags")
+AC_SUBST([OUR_LDFLAGS], "$with_ldflags")
+
+# ------------------------------------------------------------------------------
+# system features
+
+# This makes sure pkg.m4 is available.
+m4_pattern_forbid([^_?PKG_[A-Z_]+$],[*** pkg.m4 missing, please install pkg-config])
+
+# ------------------------------------------------------------------------------
+# dependencies
+
+PKG_CHECK_MODULES([UDEV], [libudev])
+PKG_CHECK_MODULES([LIBSYSTEMD], [libsystemd >= 225])
+
+# ------------------------------------------------------------------------------
+# report
+
+AC_CONFIG_FILES([Makefile])
+
+AC_OUTPUT
+AC_MSG_RESULT([
+ $PACKAGE_NAME $VERSION
+ debug: ${enable_debug}
+
+ prefix: ${prefix}
+ exec_prefix: ${exec_prefix}
+ includedir: ${includedir}
+ libdir: ${libdir}
+
+ CFLAGS: ${OUR_CFLAGS} ${CFLAGS}
+ CPPFLAGS: ${OUR_CPPFLAGS} ${CPPFLAGS}
+ LDFLAGS: ${OUR_LDFLAGS} ${LDFLAGS}
+])
diff --git a/src/org.freedesktop.ratbag1.conf b/src/org.freedesktop.ratbag1.conf
new file mode 100644
index 00000000..cd7b8843
--- /dev/null
+++ b/src/org.freedesktop.ratbag1.conf
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ratbagctl.c b/src/ratbagctl.c
new file mode 100644
index 00000000..929c174c
--- /dev/null
+++ b/src/ratbagctl.c
@@ -0,0 +1,213 @@
+/***
+ This file is part of ratbagd.
+
+ Copyright 2015 David Herrmann
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+***/
+
+#include
+#include
+#include
+#include
+#include
+#include "shared-macro.h"
+
+struct ratbagctl {
+ int unused;
+};
+
+static int verb_help(struct ratbagctl *ctl, int argc, char **argv);
+
+static struct ratbagctl *ratbagctl_free(struct ratbagctl *ctl)
+{
+ if (!ctl)
+ return NULL;
+
+ free(ctl);
+
+ return NULL;
+}
+
+static int ratbagctl_new(struct ratbagctl **out)
+{
+ struct ratbagctl *ctl;
+
+ ctl = calloc(1, sizeof(*ctl));
+ if (!ctl)
+ return -ENOMEM;
+
+ *out = ctl;
+ return 0;
+}
+
+static int verb_list(struct ratbagctl *ctl, int argc, char **argv)
+{
+ return 0;
+}
+
+static const struct {
+ const char *verb;
+ const char *help;
+ int (*dispatch) (struct ratbagctl *ctl, int argc, char **argv);
+ void (*long_help) (void);
+} verbs[] = {
+ { "list", "List available configurable mice", verb_list, NULL },
+ { "help", "Show help for a command", verb_help, NULL },
+ {},
+};
+
+static void help(void)
+{
+ unsigned int i;
+
+ printf("%s [OPTIONS..] COMMAND [OPTIONS..]\n\n"
+ "Query or modify configurable mice.\n\n"
+ "Commands:\n",
+ program_invocation_short_name);
+
+ for (i = 0; i < ELEMENTSOF(verbs); ++i) {
+ if (verbs[i].help != NULL)
+ printf(" %-12s %s\n", verbs[i].verb, verbs[i].help);
+ }
+}
+
+static int verb_help(struct ratbagctl *ctl, int argc, char **argv)
+{
+ static const struct option options[] = {
+ {},
+ };
+ const char *verb;
+ unsigned int i;
+ int c;
+
+ while ((c = getopt_long(argc, argv, "+", options, NULL)) >= 0) {
+ switch (c) {
+ default:
+ return -EINVAL;
+ }
+ }
+
+ verb = argv[optind];
+ if (!verb) {
+ help();
+ return 1;
+ } else {
+ for (i = 0; i < ELEMENTSOF(verbs); ++i) {
+ if (!streq_ptr(verb, verbs[i].verb))
+ continue;
+
+ if (verbs[i].long_help)
+ verbs[i].long_help();
+ else
+ printf("%s: No help available for '%s'\n",
+ program_invocation_short_name, verb);
+
+ return 1;
+ }
+ }
+
+ fprintf(stderr, "%s: Unknown verb '%s'\n", program_invocation_short_name, verb);
+ return 1;
+}
+
+static void version(void)
+{
+ printf("%s\n", VERSION);
+}
+
+static int ratbagctl_dispatch(struct ratbagctl *ctl, int argc, char **argv)
+{
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ {},
+ };
+ char *fake[] = { (char*)verbs[0].verb, NULL };
+ const char *verb;
+ unsigned int i;
+ int c;
+
+ /* '+' makes getopt_long() stop at the first non-argument */
+ while ((c = getopt_long(argc, argv, "+hV", options, NULL)) >= 0) {
+ switch (c) {
+ case 'h':
+ help();
+ return 1;
+ case 'V':
+ version();
+ return 1;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ verb = argv[optind];
+ if (!verb) {
+ assert(argc - optind == 0);
+
+ argc = ELEMENTSOF(fake) - 1;
+ argv = fake;
+ verb = fake[0];
+ optind = 0;
+
+ assert(verb);
+ }
+
+ for (i = 0; i < ELEMENTSOF(verbs); ++i) {
+ if (!streq_ptr(verb, verbs[i].verb))
+ continue;
+
+ argc -= optind;
+ argv += optind;
+ optind = 0;
+
+ return verbs[i].dispatch(ctl, argc, argv);
+ }
+
+ fprintf(stderr, "%s: Missing or unknown command\n",
+ program_invocation_short_name);
+
+ return -EINVAL;
+}
+
+int main(int argc, char **argv)
+{
+ struct ratbagctl *ctl = NULL;
+ int r;
+
+ r = ratbagctl_new(&ctl);
+ if (r < 0)
+ goto exit;
+
+ r = ratbagctl_dispatch(ctl, argc, argv);
+
+exit:
+ ratbagctl_free(ctl);
+
+ if (r < 0) {
+ errno = -r;
+ fprintf(stderr, "%s: Failed: %m\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/ratbagd-device.c b/src/ratbagd-device.c
new file mode 100644
index 00000000..8c54951f
--- /dev/null
+++ b/src/ratbagd-device.c
@@ -0,0 +1,288 @@
+/***
+ This file is part of ratbagd.
+
+ Copyright 2015 David Herrmann
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+***/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "ratbagd.h"
+#include "shared-macro.h"
+#include "shared-rbtree.h"
+
+struct ratbagd_device {
+ struct ratbagd *ctx;
+ RBNode node;
+ char *name;
+};
+
+#define ratbagd_device_from_node(_ptr) \
+ rbnode_of((_ptr), struct ratbagd_device, node)
+
+static int ratbagd_device_find(sd_bus *bus,
+ const char *path,
+ const char *interface,
+ void *userdata,
+ void **found,
+ sd_bus_error *error)
+{
+ _cleanup_(freep) char *name = NULL;
+ struct ratbagd *ctx = userdata;
+ struct ratbagd_device *device;
+ int r;
+
+ /* decodes the suffix into @name, returns 1 if valid */
+ r = sd_bus_path_decode(path, "/org/freedesktop/ratbag1/device", &name);
+ if (r <= 0)
+ return r;
+
+ device = ratbagd_find_device(ctx, name);
+ if (!device)
+ return 0;
+
+ *found = device;
+ return 1;
+}
+
+static int ratbagd_device_list(sd_bus *bus,
+ const char *path,
+ void *userdata,
+ char ***nodes,
+ sd_bus_error *error)
+{
+ struct ratbagd *ctx = userdata;
+ struct ratbagd_device *device;
+ char **devices, **pos;
+ RBNode *node;
+ int r;
+
+ devices = calloc(ctx->n_devices + 1, sizeof(char *));
+ if (!devices)
+ return -ENOMEM;
+
+ pos = devices;
+
+ for (node = rbtree_first(&ctx->device_map);
+ node;
+ node = rbnode_next(node)) {
+ device = ratbagd_device_from_node(node);
+
+ r = sd_bus_path_encode("/org/freedesktop/ratbag1/device",
+ device->name,
+ pos++);
+ if (r < 0)
+ goto error;
+ }
+
+ *pos = NULL;
+
+ *nodes = devices;
+ return 1;
+
+error:
+ for (pos = devices; *pos; ++pos)
+ free(*pos);
+ free(devices);
+ return r;
+}
+
+static const sd_bus_vtable ratbagd_device_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_VTABLE_END,
+};
+
+int ratbagd_device_new(struct ratbagd_device **out,
+ struct ratbagd *ctx,
+ const char *name)
+{
+ _cleanup_(ratbagd_device_freep) struct ratbagd_device *device = NULL;
+
+ assert(out);
+ assert(ctx);
+ assert(name);
+
+ device = calloc(1, sizeof(*device));
+ if (!device)
+ return -ENOMEM;
+
+ device->ctx = ctx;
+ rbnode_init(&device->node);
+
+ device->name = strdup(name);
+ if (!device->name)
+ return -ENOMEM;
+
+ *out = device;
+ device = NULL;
+ return 0;
+}
+
+struct ratbagd_device *ratbagd_device_free(struct ratbagd_device *device)
+{
+ if (!device)
+ return NULL;
+
+ assert(!ratbagd_device_linked(device));
+
+ device->name = mfree(device->name);
+ return mfree(device);
+}
+
+bool ratbagd_device_linked(struct ratbagd_device *device)
+{
+ return device && rbnode_linked(&device->node);
+}
+
+void ratbagd_device_link(struct ratbagd_device *device)
+{
+ _cleanup_(freep) char *path = NULL;
+ struct ratbagd_device *iter;
+ RBNode **node, *parent;
+ int r, v;
+
+ assert(device);
+ assert(!ratbagd_device_linked(device));
+
+ /* find place to link it to */
+ parent = NULL;
+ node = &device->ctx->device_map.root;
+ while (*node) {
+ parent = *node;
+ iter = ratbagd_device_from_node(parent);
+ v = strcmp(device->name, iter->name);
+
+ /* if there's a duplicate, the caller screwed up */
+ assert(v != 0);
+
+ if (v < 0)
+ node = &parent->left;
+ else /* if (v > 0) */
+ node = &parent->right;
+ }
+
+ /* link into context */
+ rbtree_add(&device->ctx->device_map, parent, node, &device->node);
+ ++device->ctx->n_devices;
+
+ /* send out object-manager notification */
+ r = sd_bus_path_encode("/org/freedesktop/ratbag1/device",
+ device->name,
+ &path);
+ if (r >= 0)
+ r = sd_bus_emit_object_added(device->ctx->bus, path);
+ if (r < 0) {
+ errno = -r;
+ fprintf(stderr,
+ "Cannot send device notification for '%s': %m\n",
+ device->name);
+ }
+}
+
+void ratbagd_device_unlink(struct ratbagd_device *device)
+{
+ _cleanup_(freep) char *path = NULL;
+ int r;
+
+ if (!ratbagd_device_linked(device))
+ return;
+
+ /* send out object-manager notification */
+ r = sd_bus_path_encode("/org/freedesktop/ratbag1/device",
+ device->name,
+ &path);
+ if (r >= 0)
+ r = sd_bus_emit_object_removed(device->ctx->bus, path);
+ if (r < 0) {
+ errno = -r;
+ fprintf(stderr,
+ "Cannot send device notification for '%s': %m\n",
+ device->name);
+ }
+
+ /* unlink from context */
+ --device->ctx->n_devices;
+ rbtree_remove(&device->ctx->device_map, &device->node);
+ rbnode_init(&device->node);
+}
+
+int ratbagd_init_device(struct ratbagd *ctx)
+{
+ int r;
+
+ assert(ctx);
+
+ /*
+ * Called by the context initializer after the context was prepared but
+ * not run, yet. No need to cleanup anything if an error is returned,
+ * as long it is teared down automatically on destruction.
+ */
+
+ r = sd_bus_add_fallback_vtable(ctx->bus,
+ NULL,
+ "/org/freedesktop/ratbag1/device",
+ "org.freedesktop.ratbag1.Device",
+ ratbagd_device_vtable,
+ ratbagd_device_find,
+ ctx);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_add_node_enumerator(ctx->bus,
+ NULL,
+ "/org/freedesktop/ratbag1/device",
+ ratbagd_device_list,
+ ctx);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+struct ratbagd_device *ratbagd_find_device(struct ratbagd *ctx,
+ const char *name)
+{
+ struct ratbagd_device *device;
+ RBNode *node;
+ int v;
+
+ assert(ctx);
+ assert(name);
+
+ node = ctx->device_map.root;
+ while (node) {
+ device = ratbagd_device_from_node(node);
+ v = strcmp(name, device->name);
+ if (!v)
+ return device;
+ else if (v < 0)
+ node = node->left;
+ else /* if (v > 0) */
+ node = node->right;
+ }
+
+ return NULL;
+}
diff --git a/src/ratbagd.c b/src/ratbagd.c
new file mode 100644
index 00000000..86c8cb88
--- /dev/null
+++ b/src/ratbagd.c
@@ -0,0 +1,303 @@
+/***
+ This file is part of ratbagd.
+
+ Copyright 2015 David Herrmann
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+***/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "ratbagd.h"
+#include "shared-macro.h"
+
+static void ratbagd_process_device(struct ratbagd *ctx,
+ struct udev_device *udevice)
+{
+ struct ratbagd_device *device;
+ const char *name;
+ int r;
+
+ /*
+ * TODO: So far we just enumerate udev input devices and add a new
+ * ratbag device for each one we find. That's a bit easy. We
+ * should really try to create a libratbag device for the udev
+ * device and only add devices that are actually accepted.
+ * Furthermore, the libratbag object should really be kept as
+ * context in ratbagd_device.
+ *
+ * TODO: libratbag should provide some mechanism to allow
+ * device-grouping, just like libinput does. If multiple input
+ * devices belong to the same virtual device, we should not add
+ * it multiple times. Instead, libratbag should group them and
+ * provide *us* a unique name that identifies the group, rather
+ * than taking a random input-device as tag.
+ */
+
+ name = udev_device_get_sysname(udevice);
+ if (!name)
+ return;
+
+ device = ratbagd_find_device(ctx, name);
+
+ if (streq_ptr("remove", udev_device_get_action(udevice))) {
+ /* device was removed, unlink it and destroy our context */
+ if (device) {
+ ratbagd_device_unlink(device);
+ ratbagd_device_free(device);
+ }
+ } else if (device) {
+ /* device already known, refresh our view of the device */
+ } else {
+ /* device unknown, create new one and link it */
+ r = ratbagd_device_new(&device, ctx, name);
+ if (r < 0) {
+ fprintf(stderr, "Cannot track device '%s'\n", name);
+ return;
+ }
+
+ ratbagd_device_link(device);
+ }
+}
+
+static int ratbagd_monitor_event(sd_event_source *source,
+ int fd,
+ uint32_t mask,
+ void *userdata)
+{
+ struct ratbagd *ctx = userdata;
+ struct udev_device *udevice;
+
+ udevice = udev_monitor_receive_device(ctx->monitor);
+ if (!udevice)
+ return 0;
+
+ ratbagd_process_device(ctx, udevice);
+ udev_device_unref(udevice);
+
+ return 0;
+}
+
+static struct ratbagd *ratbagd_free(struct ratbagd *ctx)
+{
+ if (!ctx)
+ return NULL;
+
+ ctx->bus = sd_bus_flush_close_unref(ctx->bus);
+ ctx->monitor_source = sd_event_source_unref(ctx->monitor_source);
+ ctx->monitor = udev_monitor_unref(ctx->monitor);
+ ctx->event = sd_event_unref(ctx->event);
+
+ assert(!ctx->device_map.root);
+
+ return mfree(ctx);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd *, ratbagd_free);
+
+static int ratbagd_init_monitor(struct ratbagd *ctx)
+{
+ struct udev *udev;
+ int r;
+
+ udev = udev_new();
+ if (!udev)
+ return -ENOMEM;
+
+ ctx->monitor = udev_monitor_new_from_netlink(udev, "udev");
+
+ /* we don't need the context to stay around; drop it */
+ udev_unref(udev);
+
+ if (!ctx->monitor)
+ return -ENOMEM;
+
+ r = udev_monitor_filter_add_match_subsystem_devtype(ctx->monitor,
+ "input",
+ NULL);
+ if (r < 0)
+ return r;
+
+ r = udev_monitor_enable_receiving(ctx->monitor);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_io(ctx->event,
+ &ctx->monitor_source,
+ udev_monitor_get_fd(ctx->monitor),
+ EPOLLIN,
+ ratbagd_monitor_event,
+ ctx);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int ratbagd_new(struct ratbagd **out)
+{
+ _cleanup_(ratbagd_freep) struct ratbagd *ctx = NULL;
+ int r;
+
+ ctx = calloc(1, sizeof(*ctx));
+ if (!ctx)
+ return -ENOMEM;
+
+ r = sd_event_default(&ctx->event);
+ if (r < 0)
+ return r;
+
+ r = sd_event_set_watchdog(ctx->event, true);
+ if (r < 0)
+ return r;
+
+ r = ratbagd_init_monitor(ctx);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_open_system(&ctx->bus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_add_object_manager(ctx->bus,
+ NULL,
+ "/org/freedesktop/ratbag1");
+ if (r < 0)
+ return r;
+
+ r = ratbagd_init_device(ctx);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_request_name(ctx->bus, "org.freedesktop.ratbag1", 0);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_attach_event(ctx->bus, ctx->event, 0);
+ if (r < 0)
+ return r;
+
+ *out = ctx;
+ ctx = NULL;
+ return 0;
+}
+
+static int ratbagd_run_enumerate(struct ratbagd *ctx)
+{
+ struct udev_list_entry *list, *iter;
+ struct udev_enumerate *e;
+ struct udev *udev;
+ int r;
+
+ udev = udev_monitor_get_udev(ctx->monitor);
+
+ e = udev_enumerate_new(udev);
+ if (!e)
+ return -ENOMEM;
+
+ r = udev_enumerate_add_match_subsystem(e, "input");
+ if (r < 0)
+ goto exit;
+
+ r = udev_enumerate_add_match_is_initialized(e);
+ if (r < 0)
+ goto exit;
+
+ r = udev_enumerate_scan_devices(e);
+ if (r < 0)
+ goto exit;
+
+ list = udev_enumerate_get_list_entry(e);
+ udev_list_entry_foreach(iter, list) {
+ struct udev_device *udevice;
+ const char *p;
+
+ p = udev_list_entry_get_name(iter);
+ udevice = udev_device_new_from_syspath(udev, p);
+ if (udevice)
+ ratbagd_process_device(ctx, udevice);
+ udev_device_unref(udevice);
+ }
+
+ r = 0;
+
+exit:
+ udev_enumerate_unref(e);
+ return r;
+}
+
+static int ratbagd_run(struct ratbagd *ctx)
+{
+ int r;
+
+ /*
+ * TODO: We should support exit-on-idle and bus-activation. Note that
+ * we don't store any state on our own, hence, all we have to do
+ * is to make sure we advertise device add/remove events via the
+ * bus (in case there is a listener).
+ * To track such events, we should store devices we already
+ * advertised in /run/ratbagd/devices/ and read it out on
+ * activation.
+ * Note that this feature requires udev-activation, which might
+ * not be possible, yet.
+ */
+
+ r = ratbagd_run_enumerate(ctx);
+ if (r < 0)
+ return r;
+
+ return sd_event_loop(ctx->event);
+}
+
+int main(int argc, char *argv[])
+{
+ struct ratbagd *ctx = NULL;
+ int r;
+
+ if (argc != 1) {
+ fprintf(stderr, "%s: This program takes no arguments\n",
+ program_invocation_short_name);
+ r = -EINVAL;
+ goto exit;
+ }
+
+ r = ratbagd_new(&ctx);
+ if (r < 0)
+ goto exit;
+
+ r = ratbagd_run(ctx);
+
+exit:
+ ratbagd_free(ctx);
+
+ if (r < 0) {
+ errno = -r;
+ fprintf(stderr, "%s: Failed: %m\n",
+ program_invocation_short_name);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/ratbagd.h b/src/ratbagd.h
new file mode 100644
index 00000000..84235f09
--- /dev/null
+++ b/src/ratbagd.h
@@ -0,0 +1,71 @@
+#pragma once
+
+/***
+ This file is part of ratbagd.
+
+ Copyright 2015 David Herrmann
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+***/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "shared-macro.h"
+#include "shared-rbtree.h"
+
+struct ratbagd;
+struct ratbagd_device;
+
+/*
+ * Devices
+ */
+
+int ratbagd_device_new(struct ratbagd_device **out,
+ struct ratbagd *ctx,
+ const char *name);
+struct ratbagd_device *ratbagd_device_free(struct ratbagd_device *device);
+bool ratbagd_device_linked(struct ratbagd_device *device);
+void ratbagd_device_link(struct ratbagd_device *device);
+void ratbagd_device_unlink(struct ratbagd_device *device);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd_device *, ratbagd_device_free);
+
+/*
+ * Context
+ */
+
+struct ratbagd {
+ sd_event *event;
+ struct udev_monitor *monitor;
+ sd_event_source *monitor_source;
+ sd_bus *bus;
+
+ RBTree device_map;
+ size_t n_devices;
+};
+
+int ratbagd_init_device(struct ratbagd *ctx);
+struct ratbagd_device *ratbagd_find_device(struct ratbagd *ctx,
+ const char *name);
diff --git a/src/shared-macro.h b/src/shared-macro.h
new file mode 100644
index 00000000..c542b75f
--- /dev/null
+++ b/src/shared-macro.h
@@ -0,0 +1,165 @@
+#pragma once
+
+/***
+ This file is part of ratbagd.
+
+ Copyright 2015 David Herrmann
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+***/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/*
+ * We require:
+ * sizeof(void*) == sizeof(long)
+ * sizeof(long) == 4 || sizeof(long) == 8
+ * sizeof(int) == 4
+ * The linux kernel requires the same from the toolchain, so this should work
+ * just fine.
+ */
+#if __SIZEOF_POINTER__ != __SIZEOF_LONG__
+# error "sizeof(void*) != sizeof(long)"
+#elif __SIZEOF_LONG__ != 4 && __SIZEOF_LONG__ != 8
+# error "sizeof(long) != 4 && sizeof(long) != 8"
+#elif __SIZEOF_INT__ != 4
+# error "sizeof(int) != 4"
+#endif
+
+/*
+ * Shortcuts for gcc attributes. See GCC manual for details.
+ */
+#define _alignas_(_x) __attribute__((aligned(__alignof(_x))))
+#define _alloc_(...) __attribute__((alloc_size(__VA_ARGS__)))
+#define _cleanup_(_x) __attribute__((cleanup(_x)))
+#define _const_ __attribute__((const))
+#define _deprecated_ __attribute__((deprecated))
+#define _hidden_ __attribute__((visibility("hidden")))
+#define _likely_(_x) (__builtin_expect(!!(_x), 1))
+#define _malloc_ __attribute__((malloc))
+#define _packed_ __attribute__((packed))
+#define _printf_(_a, _b) __attribute__((format (printf, _a, _b)))
+#define _public_ __attribute__((visibility("default")))
+#define _pure_ __attribute__((pure))
+#define _sentinel_ __attribute__((sentinel))
+#define _unlikely_(_x) (__builtin_expect(!!(_x), 0))
+#define _unused_ __attribute__((unused))
+#define _weak_ __attribute__((weak))
+#define _weakref_(_x) __attribute__((weakref(#_x)))
+
+/*
+ * DEFINE_TRIVIAL_CLEANUP_FUNC() - define helper suitable for _cleanup_()
+ * @_type: type of object to cleanup
+ * @_func: function to call on cleanup
+ */
+#define DEFINE_TRIVIAL_CLEANUP_FUNC(_type, _func) \
+ static inline void _func##p(_type *_p) \
+ { \
+ if (*_p) \
+ _func(*_p); \
+ } \
+ struct __useless_struct_to_allow_trailing_semicolon__
+
+/*
+ * ELEMENTSOF() - number of array elements
+ * @_array: C array
+ *
+ * This calculates the number of elements (compared to the byte size) of
+ * the given C array. This returns (void) if the passed argument is not
+ * actually a valid C array (decided at compile time).
+ */
+#define ELEMENTSOF(_array) \
+ __extension__ (__builtin_choose_expr( \
+ !__builtin_types_compatible_p(typeof(_array), \
+ typeof(&*(_array))), \
+ sizeof(_array) / sizeof((_array)[0]), \
+ (void)0))
+
+/*
+ * negative_errno() - return negative errno
+ *
+ * This helper should be used to shut up gcc if you know 'errno' is
+ * negative. Instead of "return -errno;", use "return negative_errno();"
+ * It will suppress bogus gcc warnings in case it assumes 'errno' might
+ * be 0 and thus the caller's error-handling might not be triggered.
+ */
+static inline int negative_errno(void)
+{
+ return (errno <= 0) ? -EINVAL : -errno;
+}
+
+/*
+ * mfree() - free memory
+ * @mem: memory to free
+ *
+ * This is basically the same as free(), but returns NULL. This makes free()
+ * follow the same style as all our other destructors.
+ */
+static inline void *mfree(void *mem)
+{
+ free(mem);
+ return NULL;
+}
+
+/*
+ * Miscellaneous cleanup helpers
+ * All these helpers are suitable for use with _cleanup_(). They call the helper
+ * they're named after on the variable marked for cleanup.
+ */
+static inline void freep(void *p)
+{
+ free(*(void**)p);
+}
+
+/*
+ * streq() - test whether two strings are equal
+ * @_a: string A
+ * @_b: string B
+ */
+#define streq(_a, _b) (!strcmp((_a), (_b)))
+
+/*
+ * streq_ptr() - test whether two strings are equal, considering NULL valid
+ * @a: string A or NULL
+ * @b: string B or NULL
+ */
+static inline bool streq_ptr(const char *a, const char *b)
+{
+ return (a && b) ? streq(a, b) : (!a && !b);
+}
+
+/*
+ * now() - returns current time in nano-seconds
+ * @clock: clock to use
+ */
+static inline uint64_t now(clockid_t clock)
+{
+ struct timespec spec = {};
+ clock_gettime(clock, &spec);
+ return spec.tv_sec * 1000ULL * 1000ULL * 1000ULL + spec.tv_nsec;
+}
diff --git a/src/shared-rbtree.c b/src/shared-rbtree.c
new file mode 100644
index 00000000..53866241
--- /dev/null
+++ b/src/shared-rbtree.c
@@ -0,0 +1,450 @@
+/***
+ This file is part of rbtree.
+
+ Copyright 2015 David Herrmann
+
+ Based on the rbtree implementation of the linux-kernel:
+ Copyright 1999 Andrea Arcangeli
+
+ rbtree is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ rbtree 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with rbtree; If not, see .
+***/
+
+/*
+ * Open Red-Black-Tree Implementation
+ * You're highly recommended to read a paper on rb-trees before reading this
+ * code. The reader is expected to be familiar with the different cases that
+ * might occur during insertion and removal of elements. The comments in this
+ * file do not contain a full prove for correctness.
+ */
+
+#include "shared-rbtree.h"
+
+RBNode *rbnode_leftmost(RBNode *n) {
+ if (n)
+ while (n->left)
+ n = n->left;
+ return n;
+}
+
+RBNode *rbnode_rightmost(RBNode *n) {
+ if (n)
+ while (n->right)
+ n = n->right;
+ return n;
+}
+
+RBNode *rbtree_first(RBTree *t) {
+ return rbnode_leftmost(t->root);
+}
+
+RBNode *rbtree_last(RBTree *t) {
+ return rbnode_rightmost(t->root);
+}
+
+RBNode *rbnode_next(RBNode *n) {
+ RBNode *p;
+
+ if (!rbnode_linked(n))
+ return NULL;
+
+ if (n->right)
+ return rbnode_leftmost(n->right);
+
+ while ((p = rbnode_parent(n)) && n == p->right)
+ n = p;
+
+ return p;
+}
+
+RBNode *rbnode_prev(RBNode *n) {
+ RBNode *p;
+
+ if (!rbnode_linked(n))
+ return NULL;
+
+ if (n->left)
+ return rbnode_rightmost(n->left);
+
+ while ((p = rbnode_parent(n)) && n == p->left)
+ n = p;
+
+ return p;
+}
+
+static inline void rbnode_reparent(RBNode *n, RBNode *p, unsigned long c) {
+ /* change color and/or parent of a node */
+ rbtree_assert(!((unsigned long)p & 1));
+ rbtree_assert(c < 2);
+ n->__parent_and_color = (RBNode*)((unsigned long)p | c);
+}
+
+static inline void rbtree_reparent(RBTree *t, RBNode *p, RBNode *old, RBNode *new) {
+ /* change previous parent/root from old to new node */
+ if (p) {
+ if (p->left == old)
+ p->left = new;
+ else
+ p->right = new;
+ } else {
+ t->root = new;
+ }
+}
+
+static inline RBNode *rbtree_paint_one(RBTree *t, RBNode *n) {
+ RBNode *p, *g, *gg, *u, *x;
+
+ /*
+ * Paint a single node according to RB-Tree rules. The node must
+ * already be linked into the tree and painted red.
+ * We repaint the node or rotate the tree, if required. In case a
+ * recursive repaint is required, the next node to be re-painted
+ * is returned.
+ * p: parent
+ * g: grandparent
+ * gg: grandgrandparent
+ * u: uncle
+ * x: temporary
+ */
+
+ /* node is red, so we can access the parent directly */
+ p = n->__parent_and_color;
+
+ if (!p) {
+ /* Case 1:
+ * We reached the root. Mark it black and be done. As all
+ * leaf-paths share the root, the ratio of black nodes on each
+ * path stays the same. */
+ rbnode_reparent(n, p, RBNODE_BLACK);
+ n = NULL;
+ } else if (rbnode_black(p)) {
+ /* Case 2:
+ * The parent is already black. As our node is red, we did not
+ * change the number of black nodes on any path, nor do we have
+ * multiple consecutive red nodes. */
+ n = NULL;
+ } else if (p == p->__parent_and_color->left) { /* parent is red, so grandparent exists */
+ g = p->__parent_and_color;
+ gg = rbnode_parent(g);
+ u = g->right;
+
+ if (u && rbnode_red(u)) {
+ /* Case 3:
+ * Parent and uncle are both red. We know the
+ * grandparent must be black then. Repaint parent and
+ * uncle black, the grandparent red and recurse into
+ * the grandparent. */
+ rbnode_reparent(p, g, RBNODE_BLACK);
+ rbnode_reparent(u, g, RBNODE_BLACK);
+ rbnode_reparent(g, gg, RBNODE_RED);
+ n = g;
+ } else {
+ /* parent is red, uncle is black */
+
+ if (n == p->right) {
+ /* Case 4:
+ * We're the right child. Rotate on parent to
+ * become left child, so we can handle it the
+ * same as case 5. */
+ x = n->left;
+ p->right = n->left;
+ n->left = p;
+ if (x)
+ rbnode_reparent(x, p, RBNODE_BLACK);
+ rbnode_reparent(p, n, RBNODE_RED);
+ p = n;
+ }
+
+ /* 'n' is invalid from here on! */
+ n = NULL;
+
+ /* Case 5:
+ * We're the red left child or a red parent, black
+ * grandparent and uncle. Rotate on grandparent and
+ * switch color with parent. Number of black nodes on
+ * each path stays the same, but we got rid of the
+ * double red path. As the grandparent is still black,
+ * we're done. */
+ x = p->right;
+ g->left = x;
+ p->right = g;
+ if (x)
+ rbnode_reparent(x, g, RBNODE_BLACK);
+ rbnode_reparent(p, gg, RBNODE_BLACK);
+ rbnode_reparent(g, p, RBNODE_RED);
+ rbtree_reparent(t, gg, g, p);
+ }
+ } else /* if (p == p->__parent_and_color->left) */ { /* same as above, but mirrored */
+ g = p->__parent_and_color;
+ gg = rbnode_parent(g);
+ u = g->left;
+
+ if (u && rbnode_red(u)) {
+ rbnode_reparent(p, g, RBNODE_BLACK);
+ rbnode_reparent(u, g, RBNODE_BLACK);
+ rbnode_reparent(g, gg, RBNODE_RED);
+ n = g;
+ } else {
+ if (n == p->left) {
+ x = n->right;
+ p->left = n->right;
+ n->right = p;
+ if (x)
+ rbnode_reparent(x, p, RBNODE_BLACK);
+ rbnode_reparent(p, n, RBNODE_RED);
+ p = n;
+ }
+
+ n = NULL;
+
+ x = p->left;
+ g->right = x;
+ p->left = g;
+ if (x)
+ rbnode_reparent(x, g, RBNODE_BLACK);
+ rbnode_reparent(p, gg, RBNODE_BLACK);
+ rbnode_reparent(g, p, RBNODE_RED);
+ rbtree_reparent(t, gg, g, p);
+ }
+ }
+
+ return n;
+}
+
+static inline void rbtree_paint(RBTree *t, RBNode *n) {
+ while (n)
+ n = rbtree_paint_one(t, n);
+}
+
+void rbtree_add(RBTree *t, RBNode *p, RBNode **l, RBNode *n) {
+ n->__parent_and_color = p;
+ n->left = n->right = NULL;
+ *l = n;
+
+ rbtree_paint(t, n);
+}
+
+static inline RBNode *rbtree_rebalance_one(RBTree *t, RBNode *p, RBNode *n) {
+ RBNode *s, *x, *y, *g;
+
+ /*
+ * Rebalance tree after a node was removed. This happens only if you
+ * remove a black node and one path is now left with an unbalanced
+ * number or black nodes.
+ * This function assumes all paths through p and n have one black node
+ * less than all other paths. If recursive fixup is required, the
+ * current node is returned.
+ */
+
+ if (n == p->left) {
+ s = p->right;
+ if (rbnode_red(s)) {
+ /* Case 3:
+ * We have a red node as sibling. Rotate it onto our
+ * side so we can later on turn it black. This way, we
+ * gain the additional black node in our path. */
+ g = rbnode_parent(p);
+ x = s->left;
+ p->right = x;
+ s->left = p;
+ rbnode_reparent(x, p, RBNODE_BLACK);
+ rbnode_reparent(s, g, rbnode_color(p));
+ rbnode_reparent(p, s, RBNODE_RED);
+ rbtree_reparent(t, g, p, s);
+ s = x;
+ }
+
+ x = s->right;
+ if (!x || rbnode_black(x)) {
+ y = s->left;
+ if (!y || rbnode_black(y)) {
+ /* Case 4:
+ * Our sibling is black and has only black
+ * children. Flip it red and turn parent black.
+ * This way we gained a black node in our path,
+ * or we fix it recursively one layer up, which
+ * will rotate the red sibling as parent. */
+ rbnode_reparent(s, p, RBNODE_RED);
+ if (rbnode_black(p))
+ return p;
+
+ rbnode_reparent(p, rbnode_parent(p), RBNODE_BLACK);
+ return NULL;
+ }
+
+ /* Case 5:
+ * Left child of our sibling is red, right one is black.
+ * Rotate on parent so the right child of our sibling is
+ * now red, and we can fall through to case 6. */
+ x = y->right;
+ s->left = y->right;
+ y->right = s;
+ p->right = y;
+ if (x)
+ rbnode_reparent(x, s, RBNODE_BLACK);
+ x = s;
+ s = y;
+ }
+
+ /* Case 6:
+ * The right child of our sibling is red. Rotate left and flip
+ * colors, which gains us an additional black node in our path,
+ * that was previously on our sibling. */
+ g = rbnode_parent(p);
+ y = s->left;
+ p->right = y;
+ s->left = p;
+ rbnode_reparent(x, s, RBNODE_BLACK);
+ if (y)
+ rbnode_reparent(y, p, rbnode_color(y));
+ rbnode_reparent(s, g, rbnode_color(p));
+ rbnode_reparent(p, s, RBNODE_BLACK);
+ rbtree_reparent(t, g, p, s);
+ } else /* if (!n || n == p->right) */ { /* same as above, but mirrored */
+ s = p->left;
+ if (rbnode_red(s)) {
+ g = rbnode_parent(p);
+ x = s->right;
+ p->left = x;
+ s->right = p;
+ rbnode_reparent(x, p, RBNODE_BLACK);
+ rbnode_reparent(s, g, RBNODE_BLACK);
+ rbnode_reparent(p, s, RBNODE_RED);
+ rbtree_reparent(t, g, p, s);
+ s = x;
+ }
+
+ x = s->left;
+ if (!x || rbnode_black(x)) {
+ y = s->right;
+ if (!y || rbnode_black(y)) {
+ rbnode_reparent(s, p, RBNODE_RED);
+ if (rbnode_black(p))
+ return p;
+
+ rbnode_reparent(p, rbnode_parent(p), RBNODE_BLACK);
+ return NULL;
+ }
+
+ x = y->left;
+ s->right = y->left;
+ y->left = s;
+ p->left = y;
+ if (x)
+ rbnode_reparent(x, s, RBNODE_BLACK);
+ x = s;
+ s = y;
+ }
+
+ g = rbnode_parent(p);
+ y = s->right;
+ p->left = y;
+ s->right = p;
+ rbnode_reparent(x, s, RBNODE_BLACK);
+ if (y)
+ rbnode_reparent(y, p, rbnode_color(y));
+ rbnode_reparent(s, g, rbnode_color(p));
+ rbnode_reparent(p, s, RBNODE_BLACK);
+ rbtree_reparent(t, g, p, s);
+ }
+
+ return NULL;
+}
+
+static inline void rbtree_rebalance(RBTree *t, RBNode *p) {
+ RBNode *n = NULL;
+
+ while (p) {
+ n = rbtree_rebalance_one(t, p, n);
+ p = n ? rbnode_parent(n) : NULL;
+ }
+}
+
+void rbtree_remove(RBTree *t, RBNode *n) {
+ RBNode *p, *s, *gc, *x, *next = NULL;
+ unsigned long c;
+
+ /*
+ * To remove an interior node from a binary tree, we simply find its
+ * successor, swap both nodes and then remove the node. Therefore, the
+ * only interesting case is were the node to be removed has at most one
+ * child.
+ * p: parent
+ * s: successor
+ * gc: grand-...-child
+ * x: temporary
+ * next: next node to rebalance on
+ */
+
+ if (!n->left) {
+ /* Case 1:
+ * We have at most one child, which must be red. We're
+ * guaranteed to be black then. Replace our node with the child
+ * (in case it exists), but turn it black.
+ * If no child exists, we need to rebalance, in case we were
+ * black as our path now lost a black node. */
+ p = rbnode_parent(n);
+ c = rbnode_color(n);
+ rbtree_reparent(t, p, n, n->right);
+ if (n->right)
+ rbnode_reparent(n->right, p, c);
+ else
+ next = (c == RBNODE_BLACK) ? p : NULL;
+ } else if (!n->right) {
+ /* case 1 mirrored (but n->left guaranteed non-NULL) */
+ p = rbnode_parent(n);
+ c = rbnode_color(n);
+ rbnode_reparent(n->left, p, c);
+ rbtree_reparent(t, p, n, n->left);
+ } else {
+ /* Case 2:
+ * We're an interior node. Find our successor and swap it with
+ * our node. Then remove our node. For performance reasons we
+ * don't perform the full swap, but skip links that are about to
+ * be removed, anyway. */
+ s = n->right;
+ if (!s->left) {
+ /* right child is next, no need to touch grandchild */
+ p = s;
+ gc = s->right;
+ } else {
+ /* find successor and swap partially */
+ s = rbnode_leftmost(s);
+ p = rbnode_parent(s);
+
+ gc = s->right;
+ p->left = s->right;
+ s->right = n->right;
+ rbnode_reparent(n->right, s, rbnode_color(n->right));
+ }
+
+ /* node is partially swapped, now remove as in case 1 */
+
+ s->left = n->left;
+ rbnode_reparent(n->left, s, rbnode_color(n->left));
+
+ x = rbnode_parent(n);
+ c = rbnode_color(n);
+ rbtree_reparent(t, x, n, s);
+ if (gc) {
+ rbnode_reparent(s, x, c);
+ rbnode_reparent(gc, p, RBNODE_BLACK);
+ } else {
+ next = rbnode_black(s) ? p : NULL;
+ rbnode_reparent(s, x, c);
+ }
+ }
+
+ if (next)
+ rbtree_rebalance(t, next);
+}
diff --git a/src/shared-rbtree.h b/src/shared-rbtree.h
new file mode 100644
index 00000000..ffb0296d
--- /dev/null
+++ b/src/shared-rbtree.h
@@ -0,0 +1,99 @@
+#pragma once
+
+/***
+ This file is part of rbtree.
+
+ Copyright 2015 David Herrmann
+
+ rbtree is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ rbtree 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with rbtree; If not, see .
+***/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+
+#ifdef RBTREE_DEBUG
+# include
+# define rbtree_assert(_x) assert(_x)
+#else
+# define rbtree_assert(_x)
+#endif
+
+typedef struct RBTree RBTree;
+typedef struct RBNode RBNode;
+
+enum {
+ RBNODE_RED = 0,
+ RBNODE_BLACK = 1,
+};
+
+struct RBTree {
+ RBNode *root;
+};
+
+struct RBNode {
+ RBNode *__parent_and_color;
+ RBNode *left;
+ RBNode *right;
+};
+
+#define rbnode_of(_ptr, _type, _member) \
+ __extension__ ({ \
+ const typeof( ((_type*)0)->_member ) *__ptr = (_ptr); \
+ __ptr ? (_type*)( (char*)__ptr - offsetof(_type, _member) ) : NULL; \
+ })
+
+#define RBNODE_INIT(var) ((RBNode){ .__parent_and_color = (&var) })
+
+static inline RBNode *rbnode_init(RBNode *n) {
+ *n = RBNODE_INIT(*n);
+ return n;
+}
+
+static inline RBNode *rbnode_parent(RBNode *n) {
+ return (RBNode*)((unsigned long)n->__parent_and_color & ~1UL);
+}
+
+static inline int rbnode_linked(RBNode *n) {
+ return n && n->__parent_and_color != n;
+}
+
+static inline unsigned long rbnode_color(RBNode *n) {
+ return (unsigned long)n->__parent_and_color & 1UL;
+}
+
+static inline int rbnode_red(RBNode *n) {
+ return rbnode_color(n) == RBNODE_RED;
+}
+
+static inline int rbnode_black(RBNode *n) {
+ return rbnode_color(n) == RBNODE_BLACK;
+}
+
+RBNode *rbnode_leftmost(RBNode *n);
+RBNode *rbnode_rightmost(RBNode *n);
+
+RBNode *rbtree_first(RBTree *t);
+RBNode *rbtree_last(RBTree *t);
+RBNode *rbnode_next(RBNode *n);
+RBNode *rbnode_prev(RBNode *n);
+
+void rbtree_add(RBTree *t, RBNode *p, RBNode **l, RBNode *n);
+void rbtree_remove(RBTree *t, RBNode *n);
+
+#ifdef __cplusplus
+}
+#endif