From 5857a2f8c41dd82b433112230d3c4f470cf9b2a7 Mon Sep 17 00:00:00 2001
From: Robert Xiao <brx@cs.cmu.edu>
Date: Tue, 6 Mar 2012 22:53:29 -0500
Subject: [PATCH 1/6] Add new "Proxynect" substitute library.

Proxynect passes all requests through a persistent daemon process.
This allows the Kinect to be used by more than one process at a time,
and also solves some issues relating to repeated reinitialization
(e.g. gradually shifting depth values).

Signed-off-by: Robert Xiao <brx@cs.cmu.edu>
---
 CMakeLists.txt            |   5 +
 proxynect/CMakeLists.txt  |  25 +++
 proxynect/README          |  29 +++
 proxynect/proxydaemon.c   | 189 ++++++++++++++++
 proxynect/proxynect.c     | 457 ++++++++++++++++++++++++++++++++++++++
 proxynect/proxynect.h     |  44 ++++
 proxynect/proxynect.sh.in |  46 ++++
 proxynect/proxyutils.c    |  69 ++++++
 8 files changed, 864 insertions(+)
 create mode 100644 proxynect/CMakeLists.txt
 create mode 100644 proxynect/README
 create mode 100644 proxynect/proxydaemon.c
 create mode 100644 proxynect/proxynect.c
 create mode 100644 proxynect/proxynect.h
 create mode 100755 proxynect/proxynect.sh.in
 create mode 100644 proxynect/proxyutils.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 441e66fc..2e5f9248 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -57,6 +57,7 @@ set (PROJECT_APIVER
 OPTION(BUILD_AUDIO "Build audio support" OFF)
 OPTION(BUILD_REDIST_PACKAGE "Build libfreenect in a legally-redistributable manner (only affects audio)" OFF)
 OPTION(BUILD_EXAMPLES "Build example programs" ON)
+OPTION(BUILD_PROXYNECT "Build proxynect persistent-connection library" ON)
 OPTION(BUILD_FAKENECT "Build fakenect mock library" ON)
 OPTION(BUILD_C_SYNC "Build c synchronous library" ON)
 OPTION(BUILD_CPP "Build C++ Library (currently header only)" ON)
@@ -145,6 +146,10 @@ IF(BUILD_FAKENECT)
   add_subdirectory (fakenect)
 ENDIF()
 
+IF(BUILD_PROXYNECT)
+  add_subdirectory (proxynect)
+ENDIF()
+
 IF(BUILD_C_SYNC)
   add_subdirectory (wrappers/c_sync)
 ENDIF()
diff --git a/proxynect/CMakeLists.txt b/proxynect/CMakeLists.txt
new file mode 100644
index 00000000..a7e1acc5
--- /dev/null
+++ b/proxynect/CMakeLists.txt
@@ -0,0 +1,25 @@
+######################################################################################
+# Proxynect Persistent Kinect Library
+######################################################################################
+SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
+SET(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib/proxynect)
+add_library (proxynect SHARED proxynect.c proxyutils.c)
+set_target_properties (proxynect PROPERTIES
+  VERSION ${PROJECT_VER}
+  SOVERSION ${PROJECT_APIVER}
+  OUTPUT_NAME freenect)
+
+install (TARGETS proxynect
+  DESTINATION "${PROJECT_LIBRARY_INSTALL_DIR}/proxynect")
+
+add_executable(proxydaemon proxydaemon.c proxyutils.c)
+target_link_libraries(proxydaemon freenect m)
+install (TARGETS proxydaemon
+  DESTINATION bin)
+
+CONFIGURE_FILE("proxynect.sh.in"
+  "proxynect.sh"
+  IMMEDIATE @ONLY)
+install (PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/proxynect.sh
+  DESTINATION bin
+  RENAME proxynect)
diff --git a/proxynect/README b/proxynect/README
new file mode 100644
index 00000000..df01cd12
--- /dev/null
+++ b/proxynect/README
@@ -0,0 +1,29 @@
+=Proxynect=
+© 2012 Robert Xiao <brx@cs.cmu.edu>
+
+==Description==
+Proxynect runs a persistent daemon which controls a single device. Multiple subscribers using the proxynect library can connect to and control the device.
+
+Proxynect allows multiple processes to use the same Kinect, and avoids certain issues related to repeated reinitialization of the Kinect.
+
+==Daemon==
+Run the daemon with
+./proxydaemon [deviceindex]
+If deviceindex is not specified, 0 is assumed. Press Ctrl+C to quit.
+
+The proxydaemon tries to avoid running if another instance seems to be active. Add "--force" to the command-line if you want to override this (e.g. if the last instance did not shutdown properly).
+
+==Library==
+Use the resulting proxynect .so dynamically instead of libfreenect.
+
+==Build==
+This is built with the main cmake script.
+
+This gives you a build/lib/proxynect/libfreenect.so that you dynamically link in instead of libfreenect.so.
+
+You can call proxynect in front of your application to automatically force the proxynect library to be used, e.g.
+
+proxynect glview
+
+==Credits==
+This library is based on the fakenect library by Brandyn White.
diff --git a/proxynect/proxydaemon.c b/proxynect/proxydaemon.c
new file mode 100644
index 00000000..1cc7a5c7
--- /dev/null
+++ b/proxynect/proxydaemon.c
@@ -0,0 +1,189 @@
+/*
+ * This file is part of the OpenKinect Project. http://www.openkinect.org
+ *
+ * Copyright (c) 2012 Robert Xiao <brx@cs.cmu.edu>
+ *
+ * This code is licensed to you under the terms of the Apache License, version
+ * 2.0, or, at your option, the terms of the GNU General Public License,
+ * version 2.0. See the APACHE20 and GPL2 files for the text of the licenses,
+ * or the following URLs:
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * If you redistribute this file in source form, modified or unmodified, you
+ * may:
+ *   1) Leave this header intact and distribute it under the same terms,
+ *      accompanying it with the APACHE20 and GPL20 files, or
+ *   2) Delete the Apache 2.0 clause and accompany it with the GPL2 file, or
+ *   3) Delete the GPL v2 clause and accompany it with the APACHE20 file
+ * In all cases you must keep the copyright notice intact and include a copy
+ * of the CONTRIB file.
+ *
+ * Binary distributions must follow the binary distribution requirements of
+ * either License.
+ */
+
+#include "proxynect.h"
+#include <libfreenect.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+volatile sig_atomic_t running = 1;
+
+struct proxynect_device *device;
+
+void depth_cb(freenect_device *dev, void *depth, uint32_t timestamp)
+{
+    int bufsel = !device->depth.bufsel;
+
+    device->depth.frame_mode[bufsel] = freenect_get_current_depth_mode(dev);
+    memcpy(device->depth.data[bufsel], depth, device->depth.frame_mode[bufsel].bytes);
+    device->depth.bufsel = bufsel;
+
+    device->depth.timestamp = timestamp;
+}
+
+
+void video_cb(freenect_device *dev, void *video, uint32_t timestamp)
+{
+    int bufsel = !device->video.bufsel;
+
+    device->video.frame_mode[bufsel] = freenect_get_current_video_mode(dev);
+    memcpy(device->video.data[bufsel], video, device->video.frame_mode[bufsel].bytes);
+    device->video.bufsel = bufsel;
+
+    device->video.timestamp = timestamp;
+}
+
+int run()
+{
+    freenect_context *ctx;
+	freenect_device *dev;
+
+	if(freenect_init(&ctx, 0)) {
+	    fprintf(stderr, "Error: Failed to get context\n");
+	    return 1;
+	}
+
+    freenect_set_log_level(ctx, device->loglevel);
+
+    /* TODO: audio */
+	freenect_select_subdevices(ctx, (freenect_device_flags)(FREENECT_DEVICE_MOTOR | FREENECT_DEVICE_CAMERA));
+
+    device->timestamp = 0;
+    device->video.bufsel = 0;
+    device->video.timestamp = 0;
+    device->depth.bufsel = 0;
+    device->depth.timestamp = 0;
+
+	if(freenect_open_device(ctx, &dev, device->index)) {
+	    fprintf(stderr, "Error: Failed to open device %d\n", device->index);
+	    return 2;
+	}
+
+	freenect_set_depth_mode(dev, freenect_find_depth_mode(FREENECT_RESOLUTION_MEDIUM, FREENECT_DEPTH_11BIT));
+	freenect_start_depth(dev);
+	freenect_set_video_mode(dev, freenect_find_video_mode(FREENECT_RESOLUTION_MEDIUM, FREENECT_VIDEO_RGB));
+	freenect_start_video(dev);
+
+	freenect_set_depth_callback(dev, depth_cb);
+	freenect_set_video_callback(dev, video_cb);
+
+	while (running && freenect_process_events(ctx) >= 0) {
+	    if(device->settings.tilt_degs_changed) {
+	        freenect_set_tilt_degs(dev, device->settings.tilt_degs);
+	        device->settings.tilt_degs_changed = 0;
+	    }
+
+        if(device->settings.led_changed) {
+	        freenect_set_led(dev, device->settings.led);
+	        device->settings.led_changed = 0;
+	    }
+
+        if(device->settings.video_mode_changed) {
+	        freenect_set_video_mode(dev, device->settings.video_mode);
+	        device->settings.video_mode_changed = 0;
+	    }
+
+        if(device->settings.depth_mode_changed) {
+	        freenect_set_depth_mode(dev, device->settings.depth_mode);
+	        device->settings.depth_mode_changed = 0;
+	    }
+        
+	    freenect_update_tilt_state(dev);
+	    device->raw_state = *freenect_get_tilt_state(dev);
+	    device->timestamp++;
+	}
+
+	freenect_stop_depth(dev);
+	freenect_stop_video(dev);
+	freenect_close_device(dev);
+	freenect_shutdown(ctx);
+
+    return 0;
+}
+
+void signal_cleanup(int num)
+{
+	running = 0;
+	fprintf(stderr, "Caught signal, cleaning up\n");
+	signal(SIGINT, signal_cleanup);
+}
+
+void usage()
+{
+	fprintf(stderr, "Opens a device for proxynect usage.\n");
+	fprintf(stderr, "Usage:\n");
+	fprintf(stderr, "  proxynect [--help] [--force] [--loglevel n] [device-index]\n");
+	exit(1);
+}
+
+int main(int argc, char **argv)
+{
+    int c = 1;
+    int force = 0;
+    int loglevel = 0;
+    int index = 0;
+    device = NULL;
+
+    while(c < argc) {
+        if(!strcmp(argv[c], "--help")) {
+            usage();
+        } else if(!strcmp(argv[c], "--force")) {
+            force = 1;
+        } else if(!strcmp(argv[c], "--loglevel")) {
+            loglevel = atoi(argv[c+1]);
+            c++;
+        } else if(argv[c][0] == '-') {
+            fprintf(stderr, "Unrecognized option %s; use --help for help.", argv[c]);
+            exit(1);
+        } else {
+            index = atoi(argv[c]);
+        }
+        c++;
+    }
+
+    if(index < 0) {
+        fprintf(stderr, "Error: Invalid device index %d\n", index);
+        return 1;
+    }
+
+    device = create_device(index, force);
+    if(device == NULL) {
+        perror("Failed to create shared device");
+        return 1;
+    }
+
+    device->index = index;
+    device->loglevel = loglevel;
+
+	signal(SIGINT, signal_cleanup);
+
+    int ret = run();
+
+    destroy_device(device);
+
+    return ret;
+}
diff --git a/proxynect/proxynect.c b/proxynect/proxynect.c
new file mode 100644
index 00000000..7bf1370b
--- /dev/null
+++ b/proxynect/proxynect.c
@@ -0,0 +1,457 @@
+/*
+ * This file is part of the OpenKinect Project. http://www.openkinect.org
+ *
+ * Copyright (c) 2012 Robert Xiao <brx@cs.cmu.edu>
+ *
+ * This code is licensed to you under the terms of the Apache License, version
+ * 2.0, or, at your option, the terms of the GNU General Public License,
+ * version 2.0. See the APACHE20 and GPL2 files for the text of the licenses,
+ * or the following URLs:
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.gnu.org/licenses/gpl-2.0.txt
+ *
+ * If you redistribute this file in source form, modified or unmodified, you
+ * may:
+ *   1) Leave this header intact and distribute it under the same terms,
+ *      accompanying it with the APACHE20 and GPL20 files, or
+ *   2) Delete the Apache 2.0 clause and accompany it with the GPL2 file, or
+ *   3) Delete the GPL v2 clause and accompany it with the APACHE20 file
+ * In all cases you must keep the copyright notice intact and include a copy
+ * of the CONTRIB file.
+ *
+ * Binary distributions must follow the binary distribution requirements of
+ * either License.
+ */
+
+#include "proxynect.h"
+#include <libfreenect.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#define GRAVITY 9.80665
+
+#define MAX_DEVICES 100
+
+/* copied from cameras.c */
+#define MAKE_RESERVED(res, fmt) (uint32_t)(((res & 0xff) << 8) | (((fmt & 0xff))))
+#define RESERVED_TO_RESOLUTION(reserved) (freenect_resolution)((reserved >> 8) & 0xff)
+#define RESERVED_TO_FORMAT(reserved) ((reserved) & 0xff)
+
+#define video_mode_count 12
+static freenect_frame_mode supported_video_modes[video_mode_count] = {
+	// reserved, resolution, format, bytes, width, height, data_bits_per_pixel, padding_bits_per_pixel, framerate, is_valid
+	{MAKE_RESERVED(FREENECT_RESOLUTION_HIGH,   FREENECT_VIDEO_RGB), FREENECT_RESOLUTION_HIGH, {FREENECT_VIDEO_RGB}, 1280*1024*3, 1280, 1024, 24, 0, 10, 1 },
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_VIDEO_RGB), FREENECT_RESOLUTION_MEDIUM, {FREENECT_VIDEO_RGB}, 640*480*3, 640,  480, 24, 0, 30, 1 },
+
+	{MAKE_RESERVED(FREENECT_RESOLUTION_HIGH,   FREENECT_VIDEO_BAYER), FREENECT_RESOLUTION_HIGH, {FREENECT_VIDEO_BAYER}, 1280*1024, 1280, 1024, 8, 0, 10, 1 },
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_VIDEO_BAYER), FREENECT_RESOLUTION_MEDIUM, {FREENECT_VIDEO_BAYER}, 640*480, 640, 480, 8, 0, 30, 1 },
+
+	{MAKE_RESERVED(FREENECT_RESOLUTION_HIGH,   FREENECT_VIDEO_IR_8BIT), FREENECT_RESOLUTION_HIGH, {FREENECT_VIDEO_IR_8BIT}, 1280*1024, 1280, 1024, 8, 0, 10, 1 },
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_VIDEO_IR_8BIT), FREENECT_RESOLUTION_MEDIUM, {FREENECT_VIDEO_IR_8BIT}, 640*488, 640, 488, 8, 0, 30, 1 },
+
+	{MAKE_RESERVED(FREENECT_RESOLUTION_HIGH,   FREENECT_VIDEO_IR_10BIT), FREENECT_RESOLUTION_HIGH, {FREENECT_VIDEO_IR_10BIT}, 1280*1024*2, 1280, 1024, 10, 6, 10, 1 },
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_VIDEO_IR_10BIT), FREENECT_RESOLUTION_MEDIUM, {FREENECT_VIDEO_IR_10BIT}, 640*488*2, 640, 488, 10, 6, 30, 1 },
+
+	{MAKE_RESERVED(FREENECT_RESOLUTION_HIGH,   FREENECT_VIDEO_IR_10BIT_PACKED), FREENECT_RESOLUTION_HIGH, {FREENECT_VIDEO_IR_10BIT_PACKED}, 1280*1024*10/8, 1280, 1024, 10, 0, 10, 1 },
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_VIDEO_IR_10BIT_PACKED), FREENECT_RESOLUTION_MEDIUM, {FREENECT_VIDEO_IR_10BIT_PACKED}, 640*488*10/8, 640, 488, 10, 0, 30, 1 },
+
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_VIDEO_YUV_RGB), FREENECT_RESOLUTION_MEDIUM, {FREENECT_VIDEO_YUV_RGB}, 640*480*3, 640, 480, 24, 0, 15, 1 },
+
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_VIDEO_YUV_RAW), FREENECT_RESOLUTION_MEDIUM, {FREENECT_VIDEO_YUV_RAW}, 640*480*2, 640, 480, 16, 0, 15, 1 },
+};
+
+#define depth_mode_count 6
+static freenect_frame_mode supported_depth_modes[depth_mode_count] = {
+	// reserved, resolution, format, bytes, width, height, data_bits_per_pixel, padding_bits_per_pixel, framerate, is_valid
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_DEPTH_11BIT), FREENECT_RESOLUTION_MEDIUM, {FREENECT_DEPTH_11BIT}, 640*480*2, 640, 480, 11, 5, 30, 1},
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_DEPTH_10BIT), FREENECT_RESOLUTION_MEDIUM, {FREENECT_DEPTH_10BIT}, 640*480*2, 640, 480, 10, 6, 30, 1},
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_DEPTH_11BIT_PACKED), FREENECT_RESOLUTION_MEDIUM, {FREENECT_DEPTH_11BIT_PACKED}, 640*480*11/8, 640, 480, 11, 0, 30, 1},
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_DEPTH_10BIT_PACKED), FREENECT_RESOLUTION_MEDIUM, {FREENECT_DEPTH_10BIT_PACKED}, 640*480*10/8, 640, 480, 10, 0, 30, 1},
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_DEPTH_REGISTERED), FREENECT_RESOLUTION_MEDIUM, {FREENECT_DEPTH_REGISTERED}, 640*480*2, 640, 480, 16, 0, 30, 1},
+	{MAKE_RESERVED(FREENECT_RESOLUTION_MEDIUM, FREENECT_DEPTH_MM), FREENECT_RESOLUTION_MEDIUM, {FREENECT_DEPTH_MM}, 640*480*2, 640, 480, 16, 0, 30, 1},
+};
+
+struct _freenect_context {
+    int num_devices;
+    freenect_device *first;
+};
+
+struct _freenect_device {
+    freenect_context *parent;
+    freenect_device *prev;
+    freenect_device *next;
+
+    struct proxynect_device *device;
+
+    void *user_data;
+
+    uint32_t last_timestamp;
+
+    freenect_video_cb video_cb;
+    uint8_t *video_buf;
+    uint32_t last_video;
+    int video_running;
+
+    freenect_depth_cb depth_cb;
+    uint8_t *depth_buf;
+    uint32_t last_depth;
+    int depth_running;
+};
+
+
+int freenect_init(freenect_context **ctx, freenect_usb_context *usb_ctx) {
+    freenect_context *newctx = (freenect_context *)malloc(sizeof(freenect_context));
+    if(newctx == NULL)
+        return -1;
+
+    newctx->num_devices = 0;
+    newctx->first = NULL;
+
+    *ctx = newctx;
+
+    return 0;
+}
+
+int freenect_shutdown(freenect_context *ctx) {
+    while(ctx->first != NULL) {
+        freenect_close_device(ctx->first);
+    }
+
+    free(ctx);
+    return 0;
+}
+
+/* Logging not supported here (daemon does all the logging) */
+void freenect_set_log_level(freenect_context *ctx, freenect_loglevel level) {}
+void freenect_set_log_callback(freenect_context *ctx, freenect_log_cb cb) {}
+
+int freenect_process_events(freenect_context *ctx) {
+    struct timeval timeout;
+    timeout.tv_sec = 60;
+    timeout.tv_usec = 0;
+
+    return freenect_process_events_timeout(ctx, &timeout);
+}
+
+void timeval_add(struct timeval *t1, struct timeval *t2) {
+    /* Add t2 to t1 */
+    t1->tv_usec += t2->tv_usec;
+    if(t1->tv_usec >= 1000000) {
+        t1->tv_usec -= 1000000;
+        t1->tv_sec += 1;
+    }
+    t1->tv_sec += t2->tv_sec;
+}
+
+int timeval_cmp(struct timeval *t1, struct timeval *t2) {
+    if(t1->tv_sec > t2->tv_sec)
+        return 1;
+    else if(t1->tv_sec < t2->tv_sec)
+        return -1;
+    else
+        return t1->tv_usec - t2->tv_usec;
+}
+
+static void update_device(freenect_device *dev) {
+    if(dev->last_video != dev->device->video.timestamp) {
+        int bufsel = dev->device->video.bufsel;
+        uint8_t *video_buf = dev->device->video.data[bufsel];
+        freenect_frame_mode *video_format = &dev->device->video.frame_mode[bufsel];
+
+        if(dev->video_buf != NULL) {
+            memcpy(dev->video_buf, video_buf, video_format->bytes);
+            video_buf = dev->video_buf;
+        }
+
+        dev->last_video = dev->device->video.timestamp;
+
+        if(dev->video_cb != NULL) {
+            dev->video_cb(dev, video_buf, dev->last_video);
+        }
+    }
+
+    if(dev->last_depth != dev->device->depth.timestamp) {
+        int bufsel = dev->device->depth.bufsel;
+        uint8_t *depth_buf = dev->device->depth.data[bufsel];
+        freenect_frame_mode *depth_format = &dev->device->depth.frame_mode[bufsel];
+
+        if(dev->depth_buf != NULL) {
+            memcpy(dev->depth_buf, depth_buf, depth_format->bytes);
+            depth_buf = dev->depth_buf;
+        }
+
+        dev->last_depth = dev->device->depth.timestamp;
+
+        if(dev->depth_cb != NULL) {
+            dev->depth_cb(dev, depth_buf, dev->last_depth);
+        }
+    }
+}
+
+int freenect_process_events_timeout(freenect_context *ctx, struct timeval* timeout) {
+    struct timeval end, cur;
+    gettimeofday(&end, NULL);
+    timeval_add(&end, timeout);
+
+    while(1) {
+        gettimeofday(&cur, NULL);
+        if(timeval_cmp(&cur, &end) >= 0)
+            break;
+
+        freenect_device *dev;
+        int updated = 0;
+        for(dev = ctx->first; dev != NULL; dev = dev->next) {            
+            if(dev->last_timestamp != dev->device->timestamp) {
+                updated = 1;
+                update_device(dev);
+                dev->last_timestamp = dev->device->timestamp;
+            }
+        }
+        if(updated)
+            return 0;
+        usleep(1000);
+    }
+    return 0;
+}
+
+int freenect_num_devices(freenect_context *ctx) {
+    int i;
+    for(i=0; i<MAX_DEVICES; i++) {
+        struct proxynect_device *device = open_device(i);
+        if(device == NULL)
+            break;
+        close_device(device);
+    }
+
+    return i;
+}
+
+int freenect_list_device_attributes(freenect_context *ctx, struct freenect_device_attributes** attribute_list) {
+    /* XXX unsupported; use freenect_num_devices instead */
+    return -1;
+}
+
+void freenect_free_device_attributes(struct freenect_device_attributes* attribute_list) {
+    /* nothing */
+}
+
+int freenect_supported_subdevices(void) {
+    /* TODO: audio */
+    return FREENECT_DEVICE_MOTOR | FREENECT_DEVICE_CAMERA;
+}
+
+void freenect_select_subdevices(freenect_context *ctx, freenect_device_flags subdevs) {
+    /* Do nothing: the device has already been opened by the daemon anyway */
+}
+
+int freenect_open_device(freenect_context *ctx, freenect_device **dev, int index) {
+    freenect_device *newdev = (freenect_device *)malloc(sizeof(freenect_device));
+    newdev->parent = ctx;
+
+    newdev->device = open_device(index);
+    if(newdev->device == NULL) {
+        perror("opening device");
+        free(newdev);
+        return -1;
+    }
+
+    newdev->next = ctx->first;
+    newdev->prev = NULL;
+    if(ctx->first != NULL) {
+        ctx->first->prev = newdev;
+    }
+    ctx->first = newdev;
+    newdev->user_data = NULL;
+
+    newdev->last_timestamp = 0;
+
+    newdev->video_cb = NULL;
+    newdev->video_buf = NULL;
+    newdev->last_video = 0;
+    newdev->video_running = 0;
+
+    newdev->depth_cb = NULL;
+    newdev->depth_buf = NULL;
+    newdev->last_depth = 0;
+    newdev->depth_running = 0;
+
+    *dev = newdev;
+    return 0;
+}
+
+int freenect_open_device_by_camera_serial(freenect_context *ctx, freenect_device **dev, const char* camera_serial) {
+    /* XXX unsupported */
+    return -1;
+}
+
+int freenect_close_device(freenect_device *dev) {
+    close_device(dev->device);
+    if(dev->next != NULL) {
+        dev->next->prev = dev->prev;
+    }
+
+    if(dev->prev != NULL) {
+        dev->prev->next = dev->next;
+    } else {
+        dev->parent->first = dev->next;
+    }
+
+    free(dev);
+    return 0;
+}
+
+void freenect_set_user(freenect_device *dev, void *user) {
+    dev->user_data = user;
+}
+
+void *freenect_get_user(freenect_device *dev) {
+    return dev->user_data;
+}
+
+void freenect_set_depth_callback(freenect_device *dev, freenect_depth_cb cb) {
+    dev->depth_cb = cb;
+}
+
+void freenect_set_video_callback(freenect_device *dev, freenect_video_cb cb) {
+    dev->video_cb = cb;
+}
+
+int freenect_set_depth_buffer(freenect_device *dev, void *buf) {
+    dev->depth_buf = buf;
+    return 0;
+}
+
+int freenect_set_video_buffer(freenect_device *dev, void *buf) {
+    dev->video_buf = buf;
+    return 0;
+}
+
+int freenect_start_depth(freenect_device *dev) {
+    dev->depth_running = 1;
+    return 0;
+}
+
+int freenect_start_video(freenect_device *dev) {
+    dev->video_running = 1;
+    return 0;
+}
+
+int freenect_stop_depth(freenect_device *dev) {
+    dev->depth_running = 0;
+    return 0;
+}
+
+int freenect_stop_video(freenect_device *dev) {
+    dev->video_running = 0;
+    return 0;
+}
+
+int freenect_update_tilt_state(freenect_device *dev) {
+    /* Accelerometer data is always considered up-to-date */
+    return 0;
+}
+
+freenect_raw_tilt_state* freenect_get_tilt_state(freenect_device *dev) {
+    return &dev->device->raw_state;
+}
+
+double freenect_get_tilt_degs(freenect_raw_tilt_state *state) {
+    return ((double)state->tilt_angle) / 2.0;
+}
+
+int freenect_set_tilt_degs(freenect_device *dev, double angle) {
+    dev->device->settings.tilt_degs = angle;
+    dev->device->settings.tilt_degs_changed = 1;
+    return 0;
+}
+
+freenect_tilt_status_code freenect_get_tilt_status(freenect_raw_tilt_state *state) {
+    return state->tilt_status;
+}
+
+int freenect_set_led(freenect_device *dev, freenect_led_options option) {
+    dev->device->settings.led = option;
+    dev->device->settings.led_changed = 1;
+    return 0;
+}
+
+void freenect_get_mks_accel(freenect_raw_tilt_state *state, double* x, double* y, double* z) {
+	*x = (double)state->accelerometer_x/FREENECT_COUNTS_PER_G*GRAVITY;
+	*y = (double)state->accelerometer_y/FREENECT_COUNTS_PER_G*GRAVITY;
+	*z = (double)state->accelerometer_z/FREENECT_COUNTS_PER_G*GRAVITY;
+}
+
+int freenect_get_video_mode_count() {
+	return video_mode_count;
+}
+
+freenect_frame_mode freenect_get_video_mode(int mode_num) {
+	if (mode_num >= 0 && mode_num < video_mode_count)
+		return supported_video_modes[mode_num];
+	freenect_frame_mode retval;
+	retval.is_valid = 0;
+	return retval;
+}
+
+freenect_frame_mode freenect_get_current_video_mode(freenect_device *dev) {
+    return dev->device->video.frame_mode[dev->device->video.bufsel];
+}
+
+freenect_frame_mode freenect_find_video_mode(freenect_resolution res, freenect_video_format fmt) {
+	uint32_t unique_id = MAKE_RESERVED(res, fmt);
+	int i;
+	for(i = 0 ; i < video_mode_count; i++) {
+		if (supported_video_modes[i].reserved == unique_id)
+			return supported_video_modes[i];
+	}
+	freenect_frame_mode retval;
+	retval.is_valid = 0;
+	return retval;
+}
+
+int freenect_set_video_mode(freenect_device* dev, freenect_frame_mode mode) {
+    dev->device->settings.video_mode = mode;
+    dev->device->settings.video_mode_changed = 1;
+    return 0;
+}
+
+int freenect_get_depth_mode_count() {
+	return depth_mode_count;
+}
+
+freenect_frame_mode freenect_get_depth_mode(int mode_num) {
+	if (mode_num >= 0 && mode_num < depth_mode_count)
+		return supported_depth_modes[mode_num];
+	freenect_frame_mode retval;
+	retval.is_valid = 0;
+	return retval;
+}
+
+freenect_frame_mode freenect_get_current_depth_mode(freenect_device *dev) {
+    return dev->device->depth.frame_mode[dev->device->depth.bufsel];
+}
+
+freenect_frame_mode freenect_find_depth_mode(freenect_resolution res, freenect_depth_format fmt) {
+	uint32_t unique_id = MAKE_RESERVED(res, fmt);
+	int i;
+	for(i = 0 ; i < depth_mode_count; i++) {
+		if (supported_depth_modes[i].reserved == unique_id)
+			return supported_depth_modes[i];
+	}
+	freenect_frame_mode retval;
+	retval.is_valid = 0;
+	return retval;
+}
+
+int freenect_set_depth_mode(freenect_device* dev, const freenect_frame_mode mode) {
+    dev->device->settings.depth_mode = mode;
+    dev->device->settings.depth_mode_changed = 1;
+    return 0;
+}
diff --git a/proxynect/proxynect.h b/proxynect/proxynect.h
new file mode 100644
index 00000000..6b70b069
--- /dev/null
+++ b/proxynect/proxynect.h
@@ -0,0 +1,44 @@
+#include <sys/types.h>
+#include <libfreenect.h>
+
+struct proxynect_device {
+    int index;
+    freenect_loglevel loglevel;
+
+    uint32_t timestamp;
+
+    struct {
+        int bufsel;
+        uint32_t timestamp;
+        freenect_frame_mode frame_mode[2];
+        uint8_t data[2][1280*1024*3];
+    } video;
+
+    struct {
+        int bufsel;
+        uint32_t timestamp;
+        freenect_frame_mode frame_mode[2];
+        uint8_t data[2][640*480*2];
+    } depth;
+
+    freenect_raw_tilt_state raw_state;
+
+    struct {
+        double tilt_degs;
+        int tilt_degs_changed;
+
+        freenect_led_options led;
+        int led_changed;
+
+        freenect_frame_mode video_mode;
+        int video_mode_changed;
+
+        freenect_frame_mode depth_mode;
+        int depth_mode_changed;
+    } settings;
+};
+
+struct proxynect_device *create_device(int index, int force);
+struct proxynect_device *open_device(int index);
+void close_device(struct proxynect_device *device);
+void destroy_device(struct proxynect_device *device);
diff --git a/proxynect/proxynect.sh.in b/proxynect/proxynect.sh.in
new file mode 100755
index 00000000..697c4401
--- /dev/null
+++ b/proxynect/proxynect.sh.in
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+# This file is part of the OpenKinect Project. http://www.openkinect.org
+#
+# Copyright (c) 2012 individual OpenKinect contributors. See the CONTRIB file
+# for details.
+#
+# This code is licensed to you under the terms of the Apache License, version
+# 2.0, or, at your option, the terms of the GNU General Public License,
+# version 2.0. See the APACHE20 and GPL20 files for the text of the licenses,
+# or the following URLs:
+# http://www.apache.org/licenses/LICENSE-2.0
+# http://www.gnu.org/licenses/gpl-2.0.txt
+#
+# If you redistribute this file in source form, modified or unmodified,
+# you may:
+# 1) Leave this header intact and distribute it under the same terms,
+# accompanying it with the APACHE20 and GPL20 files, or
+# 2) Delete the Apache 2.0 clause and accompany it with the GPL20 file, or
+# 3) Delete the GPL v2.0 clause and accompany it with the APACHE20 file
+# In all cases you must keep the copyright notice intact and include a copy
+# of the CONTRIB file.
+# Binary distributions must follow the binary distribution requirements of
+# either License.
+
+# proxynect wrapper script:
+# simplifies calling libfreenect applications under proxynect
+# usage: proxynect app --arg=value
+
+# catch bad args
+if [ $# -lt 1 ]
+then
+  echo "Usage: $0 <application> [args...]"
+  exit -1
+fi
+
+# set link path (LD_LIBRARY_PATH for Linux, DYLIB_LIBRARY_PATH for OS X)
+if [ `uname` == "Darwin" ];
+then
+  export DYLD_LIBRARY_PATH="@CMAKE_INSTALL_PREFIX@/@PROJECT_LIBRARY_INSTALL_DIR@/proxynect/:${LD_LIBRARY_PATH}"
+else
+  export LD_LIBRARY_PATH="@CMAKE_INSTALL_PREFIX@/@PROJECT_LIBRARY_INSTALL_DIR@/proxynect/:${LD_LIBRARY_PATH}"
+fi
+
+# run desired app w/ args, now that environment is configured
+"${@:1}"
diff --git a/proxynect/proxyutils.c b/proxynect/proxyutils.c
new file mode 100644
index 00000000..70e9d5ab
--- /dev/null
+++ b/proxynect/proxyutils.c
@@ -0,0 +1,69 @@
+#include "proxynect.h"
+
+#include <stdio.h>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+static struct proxynect_device *map_device(int index, int flags) {
+    char fn[80];
+    sprintf(fn, "/proxynect_%d", index);
+
+    int fd = shm_open(fn, flags, 0777);
+
+    if(fd < 0) {
+        goto fail;
+    }
+
+    if(flags & O_CREAT) {
+        if(ftruncate(fd, sizeof(struct proxynect_device)) < 0) {
+            goto fail;
+        }
+    }
+
+    struct proxynect_device *device = (struct proxynect_device *)mmap(
+        NULL, sizeof(struct proxynect_device), PROT_READ | PROT_WRITE,
+        MAP_SHARED, fd, 0);
+
+    if(device == MAP_FAILED) {
+        goto fail;
+    }
+
+    close(fd);
+
+    return device;
+
+fail:
+    if(fd >= 0) {
+        close(fd);
+        if(flags & O_CREAT)
+            shm_unlink(fn);
+    }
+    return NULL;
+}
+
+struct proxynect_device *create_device(int index, int force) {
+    if(force) {
+        return map_device(index, O_RDWR | O_CREAT);
+    } else {
+        return map_device(index, O_RDWR | O_CREAT | O_EXCL);
+    }
+}
+
+struct proxynect_device *open_device(int index) {
+    return map_device(index, O_RDWR);
+}
+
+void close_device(struct proxynect_device *device) {
+    munmap(device, sizeof(struct proxynect_device));
+}
+
+void destroy_device(struct proxynect_device *device) {
+    char fn[80];
+    sprintf(fn, "/proxynect_%d", device->index);
+    shm_unlink(fn);
+
+    munmap(device, sizeof(struct proxynect_device));
+}

From 51f9af2eaf1e73b9e2dabf03de1b0bed52b44a52 Mon Sep 17 00:00:00 2001
From: Robert Xiao <brx@cs.cmu.edu>
Date: Tue, 6 Mar 2012 23:22:38 -0500
Subject: [PATCH 2/6] proxynect: Make sure to stop & start when setting modes.

We also avoid setting the mode if there's no change; this prevents
problems with repeated depth or video start/stops.

Signed-off-by: Robert Xiao <brx@cs.cmu.edu>
---
 proxynect/proxydaemon.c | 30 +++++++++++++++++++++++++-----
 1 file changed, 25 insertions(+), 5 deletions(-)

diff --git a/proxynect/proxydaemon.c b/proxynect/proxydaemon.c
index 1cc7a5c7..f68ca17f 100644
--- a/proxynect/proxydaemon.c
+++ b/proxynect/proxydaemon.c
@@ -30,6 +30,8 @@
 #include <stdlib.h>
 #include <string.h>
 
+#define log(level, ...) do { if(device->loglevel >= FREENECT_LOG_##level) { fprintf(stderr, __VA_ARGS__); } } while(0)
+
 volatile sig_atomic_t running = 1;
 
 struct proxynect_device *device;
@@ -63,7 +65,7 @@ int run()
 	freenect_device *dev;
 
 	if(freenect_init(&ctx, 0)) {
-	    fprintf(stderr, "Error: Failed to get context\n");
+	    log(FATAL, "Error: Failed to get context\n");
 	    return 1;
 	}
 
@@ -79,7 +81,7 @@ int run()
     device->depth.timestamp = 0;
 
 	if(freenect_open_device(ctx, &dev, device->index)) {
-	    fprintf(stderr, "Error: Failed to open device %d\n", device->index);
+	    log(FATAL, "Error: Failed to open device %d\n", device->index);
 	    return 2;
 	}
 
@@ -93,22 +95,40 @@ int run()
 
 	while (running && freenect_process_events(ctx) >= 0) {
 	    if(device->settings.tilt_degs_changed) {
+	        log(INFO, "Updating tilt degrees to %.1f", device->settings.tilt_degs);
 	        freenect_set_tilt_degs(dev, device->settings.tilt_degs);
 	        device->settings.tilt_degs_changed = 0;
 	    }
 
         if(device->settings.led_changed) {
+	        log(INFO, "Updating LED to %d", device->settings.led);
 	        freenect_set_led(dev, device->settings.led);
 	        device->settings.led_changed = 0;
 	    }
 
         if(device->settings.video_mode_changed) {
-	        freenect_set_video_mode(dev, device->settings.video_mode);
+            freenect_frame_mode curmode = freenect_get_current_video_mode(dev);
+            if(!memcmp(&device->settings.video_mode, &curmode, sizeof(freenect_frame_mode))) {
+                log(DEBUG, "Ignoring video format update request for %d\n", device->settings.video_mode.video_format);
+            } else {
+                log(INFO, "Updating video format to %d\n", device->settings.video_mode.video_format);
+                freenect_stop_video(dev);
+                freenect_set_video_mode(dev, device->settings.video_mode);
+                freenect_start_video(dev);
+            }
 	        device->settings.video_mode_changed = 0;
 	    }
 
         if(device->settings.depth_mode_changed) {
-	        freenect_set_depth_mode(dev, device->settings.depth_mode);
+	        freenect_frame_mode curmode = freenect_get_current_depth_mode(dev);
+            if(!memcmp(&device->settings.depth_mode, &curmode, sizeof(freenect_frame_mode))) {
+                log(DEBUG, "Ignoring depth format update request for %d\n", device->settings.depth_mode.depth_format);
+            } else {
+                log(INFO, "Updating depth format to %d\n", device->settings.depth_mode.depth_format);
+                freenect_stop_depth(dev);
+                freenect_set_depth_mode(dev, device->settings.depth_mode);
+                freenect_start_depth(dev);
+            }
 	        device->settings.depth_mode_changed = 0;
 	    }
         
@@ -128,7 +148,7 @@ int run()
 void signal_cleanup(int num)
 {
 	running = 0;
-	fprintf(stderr, "Caught signal, cleaning up\n");
+	log(NOTICE, "Caught signal, cleaning up\n");
 	signal(SIGINT, signal_cleanup);
 }
 

From d315a7e2354301bc9adf81dac061244e51bd6931 Mon Sep 17 00:00:00 2001
From: Robert Xiao <brx@cs.cmu.edu>
Date: Tue, 6 Mar 2012 23:24:07 -0500
Subject: [PATCH 3/6] Take depth/video_running into account

Signed-off-by: Robert Xiao <brx@cs.cmu.edu>
---
 proxynect/proxynect.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/proxynect/proxynect.c b/proxynect/proxynect.c
index 7bf1370b..4886a890 100644
--- a/proxynect/proxynect.c
+++ b/proxynect/proxynect.c
@@ -160,7 +160,7 @@ int timeval_cmp(struct timeval *t1, struct timeval *t2) {
 }
 
 static void update_device(freenect_device *dev) {
-    if(dev->last_video != dev->device->video.timestamp) {
+    if(dev->last_video != dev->device->video.timestamp && dev->video_running) {
         int bufsel = dev->device->video.bufsel;
         uint8_t *video_buf = dev->device->video.data[bufsel];
         freenect_frame_mode *video_format = &dev->device->video.frame_mode[bufsel];
@@ -177,7 +177,7 @@ static void update_device(freenect_device *dev) {
         }
     }
 
-    if(dev->last_depth != dev->device->depth.timestamp) {
+    if(dev->last_depth != dev->device->depth.timestamp && dev->depth_running) {
         int bufsel = dev->device->depth.bufsel;
         uint8_t *depth_buf = dev->device->depth.data[bufsel];
         freenect_frame_mode *depth_format = &dev->device->depth.frame_mode[bufsel];

From b180705fdf168607085a0212f6bf854c7ba116b2 Mon Sep 17 00:00:00 2001
From: Robert Xiao <brx@cs.cmu.edu>
Date: Wed, 7 Mar 2012 15:03:45 -0500
Subject: [PATCH 4/6] Add missing newline

Signed-off-by: Robert Xiao <brx@cs.cmu.edu>
---
 proxynect/proxydaemon.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/proxynect/proxydaemon.c b/proxynect/proxydaemon.c
index f68ca17f..ae5762ea 100644
--- a/proxynect/proxydaemon.c
+++ b/proxynect/proxydaemon.c
@@ -177,7 +177,7 @@ int main(int argc, char **argv)
             loglevel = atoi(argv[c+1]);
             c++;
         } else if(argv[c][0] == '-') {
-            fprintf(stderr, "Unrecognized option %s; use --help for help.", argv[c]);
+            fprintf(stderr, "Unrecognized option %s; use --help for help.\n", argv[c]);
             exit(1);
         } else {
             index = atoi(argv[c]);

From cb114766bce51dbabc7463fb5ab53be6e983ed54 Mon Sep 17 00:00:00 2001
From: Robert Xiao <brx@cs.cmu.edu>
Date: Sat, 17 Mar 2012 22:54:47 -0400
Subject: [PATCH 5/6] Add newlines to log messages

Signed-off-by: Robert Xiao <brx@cs.cmu.edu>
---
 proxynect/proxydaemon.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/proxynect/proxydaemon.c b/proxynect/proxydaemon.c
index ae5762ea..17adf553 100644
--- a/proxynect/proxydaemon.c
+++ b/proxynect/proxydaemon.c
@@ -95,13 +95,13 @@ int run()
 
 	while (running && freenect_process_events(ctx) >= 0) {
 	    if(device->settings.tilt_degs_changed) {
-	        log(INFO, "Updating tilt degrees to %.1f", device->settings.tilt_degs);
+	        log(INFO, "Updating tilt degrees to %.1f\n", device->settings.tilt_degs);
 	        freenect_set_tilt_degs(dev, device->settings.tilt_degs);
 	        device->settings.tilt_degs_changed = 0;
 	    }
 
         if(device->settings.led_changed) {
-	        log(INFO, "Updating LED to %d", device->settings.led);
+	        log(INFO, "Updating LED to %d\n", device->settings.led);
 	        freenect_set_led(dev, device->settings.led);
 	        device->settings.led_changed = 0;
 	    }

From d9a84003a65ea6c3b6ecb2aa87cdecdd651fdd7e Mon Sep 17 00:00:00 2001
From: Robert Xiao <brx@cs.cmu.edu>
Date: Sun, 15 Sep 2013 12:03:34 -0400
Subject: [PATCH 6/6] Implement freenect_enabled_subdevices and
 freenect_set_flag in proxynect

Signed-off-by: Robert Xiao <brx@cs.cmu.edu>
---
 proxynect/proxydaemon.c | 13 ++++++++++++-
 proxynect/proxynect.c   | 14 +++++++++++++-
 proxynect/proxynect.h   |  3 +++
 3 files changed, 28 insertions(+), 2 deletions(-)

diff --git a/proxynect/proxydaemon.c b/proxynect/proxydaemon.c
index 17adf553..f0c1b36a 100644
--- a/proxynect/proxydaemon.c
+++ b/proxynect/proxydaemon.c
@@ -131,7 +131,18 @@ int run()
             }
 	        device->settings.depth_mode_changed = 0;
 	    }
-        
+
+        if(device->settings.flags_changed) {
+            int i;
+            for(i=0; i<sizeof(int)*8; i++) {
+                int flag = 1<<i;
+                if(device->settings.flags_changed & flag) {
+                    device->settings.flags_changed &= ~flag;
+                    freenect_set_flag(dev, flag, (device->settings.flags & flag) ? FREENECT_ON : FREENECT_OFF);
+                }
+            }
+        }
+
 	    freenect_update_tilt_state(dev);
 	    device->raw_state = *freenect_get_tilt_state(dev);
 	    device->timestamp++;
diff --git a/proxynect/proxynect.c b/proxynect/proxynect.c
index 4886a890..db1730f4 100644
--- a/proxynect/proxynect.c
+++ b/proxynect/proxynect.c
@@ -207,7 +207,7 @@ int freenect_process_events_timeout(freenect_context *ctx, struct timeval* timeo
 
         freenect_device *dev;
         int updated = 0;
-        for(dev = ctx->first; dev != NULL; dev = dev->next) {            
+        for(dev = ctx->first; dev != NULL; dev = dev->next) {
             if(dev->last_timestamp != dev->device->timestamp) {
                 updated = 1;
                 update_device(dev);
@@ -247,6 +247,18 @@ int freenect_supported_subdevices(void) {
     return FREENECT_DEVICE_MOTOR | FREENECT_DEVICE_CAMERA;
 }
 
+freenect_device_flags freenect_enabled_subdevices(freenect_context *ctx) {
+    /* TODO: audio */
+    return FREENECT_DEVICE_MOTOR | FREENECT_DEVICE_CAMERA;
+}
+
+int freenect_set_flag(freenect_device *dev, freenect_flag flag, freenect_flag_value value) {
+    dev->device->settings.flags = (dev->device->settings.flags & ~flag)
+        | ((value == FREENECT_ON) ? flag : 0);
+    dev->device->settings.flags_changed |= flag;
+    return 0;
+}
+
 void freenect_select_subdevices(freenect_context *ctx, freenect_device_flags subdevs) {
     /* Do nothing: the device has already been opened by the daemon anyway */
 }
diff --git a/proxynect/proxynect.h b/proxynect/proxynect.h
index 6b70b069..41d9ff1f 100644
--- a/proxynect/proxynect.h
+++ b/proxynect/proxynect.h
@@ -35,6 +35,9 @@ struct proxynect_device {
 
         freenect_frame_mode depth_mode;
         int depth_mode_changed;
+
+        freenect_flag flags;
+        freenect_flag flags_changed; // bitmask
     } settings;
 };