diff --git a/g13d/CMakeLists.txt b/g13d/CMakeLists.txt
index 8538535..0a40dae 100644
--- a/g13d/CMakeLists.txt
+++ b/g13d/CMakeLists.txt
@@ -11,8 +11,8 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
 
 add_executable(pbm2lpbm pbm2lpbm.cc)
 add_executable(g13d
+  action.cc
   device.cc
-  g13.cc
   g13_fonts.cc
   g13_keys.cc
   g13_lcd.cc
diff --git a/g13d/action.cc b/g13d/action.cc
new file mode 100644
index 0000000..ec0cadd
--- /dev/null
+++ b/g13d/action.cc
@@ -0,0 +1,81 @@
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/preprocessor/seq/for_each.hpp>
+#include <boost/foreach.hpp>
+
+#include "action.h"
+#include "manager.h"
+#include "repr.h"
+
+namespace G13 {
+
+G13_Action::~G13_Action() {}
+
+G13_Action_Keys::G13_Action_Keys(G13_Device &keypad,
+                                 const std::string &keys_string)
+    : G13_Action(keypad) {
+  std::vector<std::string> keys;
+  boost::split(keys, keys_string, boost::is_any_of("+"));
+
+  BOOST_FOREACH (std::string const &key, keys) {
+    auto kval = manager().find_input_key_value(key);
+    if (kval == BAD_KEY_VALUE) {
+      throw G13_CommandException("create action unknown key : " + key);
+    }
+    _keys.push_back(kval);
+  }
+
+  std::vector<int> _keys;
+}
+
+G13_Action_Keys::~G13_Action_Keys() {}
+
+void G13_Action_Keys::act(G13_Device &g13, bool is_down) {
+  for (auto key : _keys) {
+    g13.send_event(EV_KEY, key, is_down);
+    G13_LOG(trace, "sending KEY " << (is_down ? "DOWN " : "UP ") << key);
+  }
+}
+
+void G13_Action_Keys::dump(std::ostream &out) const {
+  out << " SEND KEYS: ";
+
+  for (size_t i = 0; i < _keys.size(); i++) {
+    if (i)
+      out << " + ";
+    out << manager().find_input_key_name(_keys[i]);
+  }
+}
+
+G13_Action_PipeOut::G13_Action_PipeOut(G13_Device &keypad,
+                                       const std::string &out)
+    : G13_Action(keypad), _out(out + "\n") {}
+G13_Action_PipeOut::~G13_Action_PipeOut() {}
+
+void G13_Action_PipeOut::act(G13_Device &kp, bool is_down) {
+  if (is_down) {
+    kp.write_output_pipe(_out);
+  }
+}
+
+void G13_Action_PipeOut::dump(std::ostream &o) const {
+  o << "WRITE PIPE : " << repr(_out);
+}
+
+G13_Action_Command::G13_Action_Command(G13_Device &keypad,
+                                       const std::string &cmd)
+    : G13_Action(keypad), _cmd(cmd) {}
+G13_Action_Command::~G13_Action_Command() {}
+
+void G13_Action_Command::act(G13_Device &kp, bool is_down) {
+  if (is_down) {
+    keypad().command(_cmd.c_str());
+  }
+}
+
+void G13_Action_Command::dump(std::ostream &o) const {
+  o << "COMMAND : " << repr(_cmd);
+}
+
+}
diff --git a/g13d/device.cc b/g13d/device.cc
index 8acc4d3..e75855f 100644
--- a/g13d/device.cc
+++ b/g13d/device.cc
@@ -1,9 +1,111 @@
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <signal.h>
 
+#include <libusb-1.0/libusb.h>
+#include <linux/uinput.h>
 
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/preprocessor/seq/for_each.hpp>
+#include <boost/foreach.hpp>
+
+#include <iostream>
+#include <fstream>
+#include <string>
+
+#include "logo.h"
+#include "manager.h"
+#include "repr.h"
 #include "device.h"
 
 namespace G13 {
 
+static std::string describe_libusb_error_code(int code) {
+
+#define TEST_libusb_error(r, data, elem)                                       \
+  case BOOST_PP_CAT(LIBUSB_, elem):                                            \
+    return BOOST_PP_STRINGIZE(elem);
+
+  switch (code) {
+    BOOST_PP_SEQ_FOR_EACH(
+        TEST_libusb_error, _,
+        (SUCCESS)(ERROR_IO)(ERROR_INVALID_PARAM)(ERROR_ACCESS)(ERROR_NO_DEVICE)(
+            ERROR_NOT_FOUND)(ERROR_BUSY)(ERROR_TIMEOUT)(ERROR_OVERFLOW)(
+            ERROR_PIPE)(ERROR_INTERRUPTED)(ERROR_NO_MEM)(ERROR_NOT_SUPPORTED)(
+            ERROR_OTHER))
+  }
+  return "unknown error";
+}
+
+int g13_create_fifo(const char *fifo_name) {
+  mkfifo(fifo_name, 0666);
+  chmod(fifo_name, 0660);
+  return open(fifo_name, O_RDWR | O_NONBLOCK);
+}
+
+int g13_create_uinput(void) {
+  struct uinput_user_dev uinp;
+  const char *dev_uinput_fname =
+      access("/dev/input/uinput", F_OK) == 0
+          ? "/dev/input/uinput"
+          : access("/dev/uinput", F_OK) == 0 ? "/dev/uinput" : 0;
+  if (!dev_uinput_fname) {
+    G13_LOG(error, "Could not find an uinput device");
+    return -1;
+  }
+  if (access(dev_uinput_fname, W_OK) != 0) {
+    G13_LOG(error, dev_uinput_fname << " doesn't grant write permissions");
+    return -1;
+  }
+  int ufile = open(dev_uinput_fname, O_WRONLY | O_NDELAY);
+  if (ufile <= 0) {
+    G13_LOG(error, "Could not open uinput");
+    return -1;
+  }
+  memset(&uinp, 0, sizeof(uinp));
+  char name[] = "G13";
+  strncpy(uinp.name, name, sizeof(uinp.name));
+  uinp.id.version = 1;
+  uinp.id.bustype = BUS_USB;
+  uinp.id.product = G13_PRODUCT_ID;
+  uinp.id.vendor = G13_VENDOR_ID;
+  uinp.absmin[ABS_X] = 0;
+  uinp.absmin[ABS_Y] = 0;
+  uinp.absmax[ABS_X] = 0xff;
+  uinp.absmax[ABS_Y] = 0xff;
+  //  uinp.absfuzz[ABS_X] = 4;
+  //  uinp.absfuzz[ABS_Y] = 4;
+  //  uinp.absflat[ABS_X] = 0x80;
+  //  uinp.absflat[ABS_Y] = 0x80;
+
+  ioctl(ufile, UI_SET_EVBIT, EV_KEY);
+  ioctl(ufile, UI_SET_EVBIT, EV_ABS);
+  /*  ioctl(ufile, UI_SET_EVBIT, EV_REL);*/
+  ioctl(ufile, UI_SET_MSCBIT, MSC_SCAN);
+  ioctl(ufile, UI_SET_ABSBIT, ABS_X);
+  ioctl(ufile, UI_SET_ABSBIT, ABS_Y);
+  /*  ioctl(ufile, UI_SET_RELBIT, REL_X);
+   ioctl(ufile, UI_SET_RELBIT, REL_Y);*/
+  for (int i = 0; i < 256; i++)
+    ioctl(ufile, UI_SET_KEYBIT, i);
+  ioctl(ufile, UI_SET_KEYBIT, BTN_THUMB);
+
+  int retcode = write(ufile, &uinp, sizeof(uinp));
+  if (retcode < 0) {
+    G13_LOG(error, "Could not write to uinput device (" << retcode << ")");
+    return -1;
+  }
+  retcode = ioctl(ufile, UI_DEV_CREATE);
+  if (retcode) {
+    G13_LOG(error, "Error creating uinput device for G13");
+    return -1;
+  }
+  return ufile;
+}
+
 void G13_Device::send_event(int type, int code, int val) {
   memset(&_event, 0, sizeof(_event));
   gettimeofday(&_event.time, 0);
@@ -57,4 +159,391 @@ void G13_Device::set_key_color(int red, int green, int blue) {
   }
 }
 
+void G13_Device::register_context(libusb_context *_ctx) {
+  ctx = _ctx;
+
+  int leds = 0;
+  int red = 0;
+  int green = 0;
+  int blue = 255;
+  init_lcd();
+
+  set_mode_leds(leds);
+  set_key_color(red, green, blue);
+
+  write_lcd(g13_logo, sizeof(g13_logo));
+
+  _uinput_fid = g13_create_uinput();
+
+  _input_pipe_name = _manager.make_pipe_name(this, true);
+  _input_pipe_fid = g13_create_fifo(_input_pipe_name.c_str());
+  _output_pipe_name = _manager.make_pipe_name(this, false);
+  _output_pipe_fid = g13_create_fifo(_output_pipe_name.c_str());
+
+  if (_input_pipe_fid == -1) {
+    G13_LOG(error, "failed opening pipe");
+  }
+}
+
+void G13_Device::cleanup() {
+  remove(_input_pipe_name.c_str());
+  remove(_output_pipe_name.c_str());
+  ioctl(_uinput_fid, UI_DEV_DESTROY);
+  close(_uinput_fid);
+  libusb_release_interface(handle, 0);
+  libusb_close(handle);
+}
+
+
+/*! reads and processes key state report from G13
+ *
+ */
+int G13_Device::read_keys() {
+  unsigned char buffer[G13_REPORT_SIZE];
+  int size;
+  int error =
+      libusb_interrupt_transfer(handle, LIBUSB_ENDPOINT_IN | G13_KEY_ENDPOINT,
+                                buffer, G13_REPORT_SIZE, &size, 100);
+
+  if (error && error != LIBUSB_ERROR_TIMEOUT) {
+
+    G13_LOG(error, "Error while reading keys: "
+                       << error << " (" << describe_libusb_error_code(error)
+                       << ")");
+    //    G13_LOG( error, "Stopping daemon" );
+    //    return -1;
+  }
+  if (size == G13_REPORT_SIZE) {
+    parse_joystick(buffer);
+    _current_profile->parse_keys(buffer);
+    send_event(EV_SYN, SYN_REPORT, 0);
+  }
+  return 0;
+}
+
+void G13_Device::read_config_file(const std::string &filename) {
+  std::ifstream s(filename);
+
+  G13_LOG(info, "reading configuration from " << filename);
+  while (s.good()) {
+
+    // grab a line
+    char buf[1024];
+    buf[0] = 0;
+    buf[sizeof(buf) - 1] = 0;
+    s.getline(buf, sizeof(buf) - 1);
+
+    // strip comment
+    char *comment = strchr(buf, '#');
+    if (comment) {
+      comment--;
+      while (comment > buf && isspace(*comment))
+        comment--;
+      *comment = 0;
+    }
+
+    // send it
+    if (buf[0]) {
+      G13_LOG(info, "  cfg: " << buf);
+      command(buf);
+    }
+  }
+}
+
+void G13_Device::read_commands() {
+
+  fd_set set;
+  FD_ZERO(&set);
+  FD_SET(_input_pipe_fid, &set);
+  struct timeval tv;
+  tv.tv_sec = 0;
+  tv.tv_usec = 0;
+  int ret = select(_input_pipe_fid + 1, &set, 0, 0, &tv);
+  if (ret > 0) {
+    unsigned char buf[1024 * 1024];
+    memset(buf, 0, 1024 * 1024);
+    ret = read(_input_pipe_fid, buf, 1024 * 1024);
+    G13_LOG(trace, "read " << ret << " characters");
+
+    if (ret ==
+        960) { // TODO probably image, for now, don't test, just assume image
+      lcd().image(buf, ret);
+    } else {
+      std::string buffer = reinterpret_cast<const char *>(buf);
+      std::vector<std::string> lines;
+      boost::split(lines, buffer, boost::is_any_of("\n\r"));
+
+      BOOST_FOREACH (std::string const &cmd, lines) {
+        std::vector<std::string> command_comment;
+        boost::split(command_comment, cmd, boost::is_any_of("#"));
+
+        if (command_comment.size() > 0 &&
+            command_comment[0] != std::string("")) {
+          G13_LOG(info, "command: " << command_comment[0]);
+          command(command_comment[0].c_str());
+        }
+      }
+    }
+  }
+}
+
+G13_Device::G13_Device(G13_Manager &manager, libusb_device_handle *handle,
+                       int _id)
+    : _id_within_manager(_id), handle(handle), ctx(0), _uinput_fid(-1),
+      _manager(manager), _lcd(*this), _stick(*this) {
+  _current_profile = ProfilePtr(new G13_Profile(*this, "default"));
+  _profiles["default"] = _current_profile;
+
+  for (unsigned int i = 0; i < sizeof(keys); i++) {
+    keys[i] = false;
+  }
+
+  lcd().image_clear();
+
+  _init_fonts();
+  _init_commands();
+}
+
+FontPtr G13_Device::switch_to_font(const std::string &name) {
+  FontPtr rv = _fonts[name];
+  if (rv) {
+    _current_font = rv;
+  }
+  return rv;
+}
+
+void G13_Device::switch_to_profile(const std::string &name) {
+  _current_profile = profile(name);
+}
+
+ProfilePtr G13_Device::profile(const std::string &name) {
+  ProfilePtr rv = _profiles[name];
+  if (!rv) {
+    rv = ProfilePtr(new G13_Profile(*_current_profile, name));
+    _profiles[name] = rv;
+  }
+  return rv;
+}
+
+
+G13_ActionPtr G13_Device::make_action(const std::string &action) {
+  if (!action.size()) {
+    throw G13_CommandException("empty action string");
+  }
+  if (action[0] == '>') {
+    return G13_ActionPtr(new G13_Action_PipeOut(*this, &action[1]));
+  } else if (action[0] == '!') {
+    return G13_ActionPtr(new G13_Action_Command(*this, &action[1]));
+  } else {
+    return G13_ActionPtr(new G13_Action_Keys(*this, action));
+  }
+  throw G13_CommandException("can't create action for " + action);
+}
+
+// *************************************************************************
+
+void G13_Device::dump(std::ostream &o, int detail) {
+  o << "G13 id=" << id_within_manager() << std::endl
+    << "   input_pipe_name=" << repr(_input_pipe_name) << std::endl
+    << "   output_pipe_name=" << repr(_output_pipe_name) << std::endl
+    << "   current_profile=" << _current_profile->name() << std::endl
+    << "   current_font=" << _current_font->name() << std::endl;
+
+  if (detail > 0) {
+    o << "STICK" << std::endl;
+
+    stick().dump(o);
+
+    if (detail == 1) {
+      _current_profile->dump(o);
+    } else {
+      for (auto i = _profiles.begin(); i != _profiles.end(); i++) {
+        i->second->dump(o);
+      }
+    }
+  }
+}
+
+inline const char *advance_ws(const char* &source, std::string &dest) {
+  const char *space = source ? strchr(source, ' ') : 0;
+
+  if (space) {
+    dest = std::string(source, space - source);
+    source = space + 1;
+  } else {
+    dest = source;
+    source = 0;
+  }
+
+  return source;
+};
+
+struct command_adder {
+  command_adder(G13_Device::CommandFunctionTable &t, const char *name)
+      : _t(t), _name(name) {}
+
+  G13_Device::CommandFunctionTable &_t;
+  std::string _name;
+  command_adder &operator+=(G13_Device::COMMAND_FUNCTION f) {
+    _t[_name] = f;
+    return *this;
+  };
+};
+
+#define RETURN_FAIL(message)                                                   \
+  {                                                                            \
+    G13_LOG(error, message);                                                   \
+    return;                                                                    \
+  }
+
+#define G13_DEVICE_COMMAND(name)                                               \
+  ;                                                                            \
+  command_adder BOOST_PP_CAT(add_, name)(_command_table,                       \
+                                         BOOST_PP_STRINGIZE(name));            \
+  BOOST_PP_CAT(add_, name) += [this](const char *remainder)
+
+void G13_Device::_init_commands() {
+  G13_DEVICE_COMMAND(out) {
+    lcd().write_string(remainder);
+  }
+
+  G13_DEVICE_COMMAND(pos) {
+    int row, col;
+    if (sscanf(remainder, "%i %i", &row, &col) == 2) {
+      lcd().write_pos(row, col);
+    } else {
+      RETURN_FAIL("bad pos : " << remainder);
+    }
+  }
+
+  G13_DEVICE_COMMAND(bind) {
+    std::string keyname;
+    advance_ws(remainder, keyname);
+    std::string action = remainder;
+    try {
+      if (auto key = _current_profile->find_key(keyname)) {
+        key->set_action(make_action(action));
+      } else if (auto stick_key = _stick.zone(keyname)) {
+        stick_key->set_action(make_action(action));
+      } else {
+        RETURN_FAIL("bind key " << keyname << " unknown");
+      }
+      G13_LOG(trace, "bind " << keyname << " [" << action << "]");
+    } catch (const std::exception &ex) {
+      RETURN_FAIL("bind " << keyname << " " << action
+                          << " failed : " << ex.what());
+    }
+  }
+
+  G13_DEVICE_COMMAND(profile) { switch_to_profile(remainder); }
+  G13_DEVICE_COMMAND(font) { switch_to_font(remainder); }
+  G13_DEVICE_COMMAND(mod) { set_mode_leds(atoi(remainder)); }
+  G13_DEVICE_COMMAND(textmode) { lcd().text_mode = atoi(remainder); }
+
+  G13_DEVICE_COMMAND(rgb) {
+    int red, green, blue;
+    if (sscanf(remainder, "%i %i %i", &red, &green, &blue) == 3) {
+      set_key_color(red, green, blue);
+    } else {
+      RETURN_FAIL("rgb bad format: <" << remainder << ">");
+    }
+  }
+
+#define STICKMODE_TEST(r, data, elem)                                          \
+  if (mode == BOOST_PP_STRINGIZE(elem)) {                                      \
+    _stick.set_mode(BOOST_PP_CAT(STICK_, elem));                               \
+    return;                                                                    \
+  } else
+
+  G13_DEVICE_COMMAND(stickmode) {
+    std::string mode = remainder;
+
+    BOOST_PP_SEQ_FOR_EACH(
+        STICKMODE_TEST, _,
+        (ABSOLUTE)(RELATIVE)(KEYS)(CALCENTER)(CALBOUNDS)(CALNORTH)) {
+      RETURN_FAIL("unknown stick mode : <" << mode << ">");
+    }
+  }
+
+  G13_DEVICE_COMMAND(stickzone) {
+    std::string operation, zonename;
+    advance_ws(remainder, operation);
+    advance_ws(remainder, zonename);
+
+    if (operation != "add") {
+      G13_StickZone *zone = _stick.zone(zonename);
+
+      if (!zone) {
+        throw G13_CommandException("unknown stick zone");
+      }
+
+      if (operation == "action") {
+        zone->set_action(make_action(remainder));
+      } else if (operation == "bounds") {
+        double x1, y1, x2, y2;
+
+        if (sscanf(remainder, "%lf %lf %lf %lf", &x1, &y1, &x2, &y2) != 4) {
+          throw G13_CommandException("bad bounds format");
+        }
+
+        zone->set_bounds(G13_ZoneBounds(x1, y1, x2, y2));
+      } else if (operation == "del") {
+        _stick.remove_zone(*zone);
+      } else {
+        RETURN_FAIL("unknown stickzone operation: <" << operation << ">");
+      }
+    }
+  }
+
+  G13_DEVICE_COMMAND(dump) {
+    std::string target;
+    advance_ws(remainder, target);
+    if (target == "all") {
+      dump(std::cout, 3);
+    } else if (target == "current") {
+      dump(std::cout, 1);
+    } else if (target == "summary") {
+      dump(std::cout, 0);
+    } else {
+      RETURN_FAIL("unknown dump target: <" << target << ">");
+    }
+  }
+
+  G13_DEVICE_COMMAND(log_level) {
+    std::string level;
+    advance_ws(remainder, level);
+    manager().set_log_level(level);
+  }
+
+  G13_DEVICE_COMMAND(refresh) {
+    lcd().image_send();
+  }
+
+  G13_DEVICE_COMMAND(clear) {
+    lcd().image_clear();
+    lcd().image_send();
+  };
+}
+
+void G13_Device::command(char const *str) {
+  const char *remainder = str;
+
+  try {
+    std::string cmd;
+    advance_ws(remainder, cmd);
+
+    auto i = _command_table.find(cmd);
+    if (i == _command_table.end()) {
+      RETURN_FAIL("unknown command : " << cmd)
+    }
+
+    COMMAND_FUNCTION f = i->second;
+    f(remainder);
+
+    return;
+  } catch (const std::exception &ex) {
+    RETURN_FAIL("command failed : " << ex.what());
+  }
+}
+
 } // namespace G13
diff --git a/g13d/g13.cc b/g13d/g13.cc
deleted file mode 100644
index b3fbd3d..0000000
--- a/g13d/g13.cc
+++ /dev/null
@@ -1,700 +0,0 @@
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <signal.h>
-
-#include <libusb-1.0/libusb.h>
-#include <linux/uinput.h>
-
-#include <boost/lexical_cast.hpp>
-#include <boost/algorithm/string/split.hpp>
-#include <boost/algorithm/string/classification.hpp>
-#include <boost/preprocessor/seq/for_each.hpp>
-#include <boost/foreach.hpp>
-
-#include <iostream>
-#include <fstream>
-#include <string>
-
-#include "device.h"
-#include "g13.h"
-#include "logo.h"
-#include "manager.h"
-#include "repr.h"
-#include "find_or_throw.h"
-
-#define CONTROL_DIR std::string("/tmp/")
-
-namespace G13 {
-
-int g13_create_fifo(const char *fifo_name) {
-  mkfifo(fifo_name, 0666);
-  chmod(fifo_name, 0660);
-  return open(fifo_name, O_RDWR | O_NONBLOCK);
-}
-
-int g13_create_uinput(void) {
-  struct uinput_user_dev uinp;
-  const char *dev_uinput_fname =
-      access("/dev/input/uinput", F_OK) == 0
-          ? "/dev/input/uinput"
-          : access("/dev/uinput", F_OK) == 0 ? "/dev/uinput" : 0;
-  if (!dev_uinput_fname) {
-    G13_LOG(error, "Could not find an uinput device");
-    return -1;
-  }
-  if (access(dev_uinput_fname, W_OK) != 0) {
-    G13_LOG(error, dev_uinput_fname << " doesn't grant write permissions");
-    return -1;
-  }
-  int ufile = open(dev_uinput_fname, O_WRONLY | O_NDELAY);
-  if (ufile <= 0) {
-    G13_LOG(error, "Could not open uinput");
-    return -1;
-  }
-  memset(&uinp, 0, sizeof(uinp));
-  char name[] = "G13";
-  strncpy(uinp.name, name, sizeof(uinp.name));
-  uinp.id.version = 1;
-  uinp.id.bustype = BUS_USB;
-  uinp.id.product = G13_PRODUCT_ID;
-  uinp.id.vendor = G13_VENDOR_ID;
-  uinp.absmin[ABS_X] = 0;
-  uinp.absmin[ABS_Y] = 0;
-  uinp.absmax[ABS_X] = 0xff;
-  uinp.absmax[ABS_Y] = 0xff;
-  //  uinp.absfuzz[ABS_X] = 4;
-  //  uinp.absfuzz[ABS_Y] = 4;
-  //  uinp.absflat[ABS_X] = 0x80;
-  //  uinp.absflat[ABS_Y] = 0x80;
-
-  ioctl(ufile, UI_SET_EVBIT, EV_KEY);
-  ioctl(ufile, UI_SET_EVBIT, EV_ABS);
-  /*  ioctl(ufile, UI_SET_EVBIT, EV_REL);*/
-  ioctl(ufile, UI_SET_MSCBIT, MSC_SCAN);
-  ioctl(ufile, UI_SET_ABSBIT, ABS_X);
-  ioctl(ufile, UI_SET_ABSBIT, ABS_Y);
-  /*  ioctl(ufile, UI_SET_RELBIT, REL_X);
-   ioctl(ufile, UI_SET_RELBIT, REL_Y);*/
-  for (int i = 0; i < 256; i++)
-    ioctl(ufile, UI_SET_KEYBIT, i);
-  ioctl(ufile, UI_SET_KEYBIT, BTN_THUMB);
-
-  int retcode = write(ufile, &uinp, sizeof(uinp));
-  if (retcode < 0) {
-    G13_LOG(error, "Could not write to uinput device (" << retcode << ")");
-    return -1;
-  }
-  retcode = ioctl(ufile, UI_DEV_CREATE);
-  if (retcode) {
-    G13_LOG(error, "Error creating uinput device for G13");
-    return -1;
-  }
-  return ufile;
-}
-
-void G13_Device::register_context(libusb_context *_ctx) {
-  ctx = _ctx;
-
-  int leds = 0;
-  int red = 0;
-  int green = 0;
-  int blue = 255;
-  init_lcd();
-
-  set_mode_leds(leds);
-  set_key_color(red, green, blue);
-
-  write_lcd(g13_logo, sizeof(g13_logo));
-
-  _uinput_fid = g13_create_uinput();
-
-  _input_pipe_name = _manager.make_pipe_name(this, true);
-  _input_pipe_fid = g13_create_fifo(_input_pipe_name.c_str());
-  _output_pipe_name = _manager.make_pipe_name(this, false);
-  _output_pipe_fid = g13_create_fifo(_output_pipe_name.c_str());
-
-  if (_input_pipe_fid == -1) {
-    G13_LOG(error, "failed opening pipe");
-  }
-}
-
-void G13_Device::cleanup() {
-  remove(_input_pipe_name.c_str());
-  remove(_output_pipe_name.c_str());
-  ioctl(_uinput_fid, UI_DEV_DESTROY);
-  close(_uinput_fid);
-  libusb_release_interface(handle, 0);
-  libusb_close(handle);
-}
-
-void G13_Manager::cleanup() {
-  G13_LOG(info, "cleaning up");
-
-  for (auto device : g13s) {
-    device->cleanup();
-    delete device;
-  }
-
-  libusb_exit(ctx);
-}
-
-// *************************************************************************
-
-static std::string describe_libusb_error_code(int code) {
-
-#define TEST_libusb_error(r, data, elem)                                       \
-  case BOOST_PP_CAT(LIBUSB_, elem):                                            \
-    return BOOST_PP_STRINGIZE(elem);
-
-  switch (code) {
-    BOOST_PP_SEQ_FOR_EACH(
-        TEST_libusb_error, _,
-        (SUCCESS)(ERROR_IO)(ERROR_INVALID_PARAM)(ERROR_ACCESS)(ERROR_NO_DEVICE)(
-            ERROR_NOT_FOUND)(ERROR_BUSY)(ERROR_TIMEOUT)(ERROR_OVERFLOW)(
-            ERROR_PIPE)(ERROR_INTERRUPTED)(ERROR_NO_MEM)(ERROR_NOT_SUPPORTED)(
-            ERROR_OTHER))
-  }
-  return "unknown error";
-}
-
-// *************************************************************************
-
-/*! reads and processes key state report from G13
- *
- */
-int G13_Device::read_keys() {
-  unsigned char buffer[G13_REPORT_SIZE];
-  int size;
-  int error =
-      libusb_interrupt_transfer(handle, LIBUSB_ENDPOINT_IN | G13_KEY_ENDPOINT,
-                                buffer, G13_REPORT_SIZE, &size, 100);
-
-  if (error && error != LIBUSB_ERROR_TIMEOUT) {
-
-    G13_LOG(error, "Error while reading keys: "
-                       << error << " (" << describe_libusb_error_code(error)
-                       << ")");
-    //    G13_LOG( error, "Stopping daemon" );
-    //    return -1;
-  }
-  if (size == G13_REPORT_SIZE) {
-    parse_joystick(buffer);
-    _current_profile->parse_keys(buffer);
-    send_event(EV_SYN, SYN_REPORT, 0);
-  }
-  return 0;
-}
-
-void G13_Device::read_config_file(const std::string &filename) {
-  std::ifstream s(filename);
-
-  G13_LOG(info, "reading configuration from " << filename);
-  while (s.good()) {
-
-    // grab a line
-    char buf[1024];
-    buf[0] = 0;
-    buf[sizeof(buf) - 1] = 0;
-    s.getline(buf, sizeof(buf) - 1);
-
-    // strip comment
-    char *comment = strchr(buf, '#');
-    if (comment) {
-      comment--;
-      while (comment > buf && isspace(*comment))
-        comment--;
-      *comment = 0;
-    }
-
-    // send it
-    if (buf[0]) {
-      G13_LOG(info, "  cfg: " << buf);
-      command(buf);
-    }
-  }
-}
-
-void G13_Device::read_commands() {
-
-  fd_set set;
-  FD_ZERO(&set);
-  FD_SET(_input_pipe_fid, &set);
-  struct timeval tv;
-  tv.tv_sec = 0;
-  tv.tv_usec = 0;
-  int ret = select(_input_pipe_fid + 1, &set, 0, 0, &tv);
-  if (ret > 0) {
-    unsigned char buf[1024 * 1024];
-    memset(buf, 0, 1024 * 1024);
-    ret = read(_input_pipe_fid, buf, 1024 * 1024);
-    G13_LOG(trace, "read " << ret << " characters");
-
-    if (ret ==
-        960) { // TODO probably image, for now, don't test, just assume image
-      lcd().image(buf, ret);
-    } else {
-      std::string buffer = reinterpret_cast<const char *>(buf);
-      std::vector<std::string> lines;
-      boost::split(lines, buffer, boost::is_any_of("\n\r"));
-
-      BOOST_FOREACH (std::string const &cmd, lines) {
-        std::vector<std::string> command_comment;
-        boost::split(command_comment, cmd, boost::is_any_of("#"));
-
-        if (command_comment.size() > 0 &&
-            command_comment[0] != std::string("")) {
-          G13_LOG(info, "command: " << command_comment[0]);
-          command(command_comment[0].c_str());
-        }
-      }
-    }
-  }
-}
-
-G13_Device::G13_Device(G13_Manager &manager, libusb_device_handle *handle,
-                       int _id)
-    : _id_within_manager(_id), handle(handle), ctx(0), _uinput_fid(-1),
-      _manager(manager), _lcd(*this), _stick(*this) {
-  _current_profile = ProfilePtr(new G13_Profile(*this, "default"));
-  _profiles["default"] = _current_profile;
-
-  for (unsigned int i = 0; i < sizeof(keys); i++) {
-    keys[i] = false;
-  }
-
-  lcd().image_clear();
-
-  _init_fonts();
-  _init_commands();
-}
-
-FontPtr G13_Device::switch_to_font(const std::string &name) {
-  FontPtr rv = _fonts[name];
-  if (rv) {
-    _current_font = rv;
-  }
-  return rv;
-}
-
-void G13_Device::switch_to_profile(const std::string &name) {
-  _current_profile = profile(name);
-}
-
-ProfilePtr G13_Device::profile(const std::string &name) {
-  ProfilePtr rv = _profiles[name];
-  if (!rv) {
-    rv = ProfilePtr(new G13_Profile(*_current_profile, name));
-    _profiles[name] = rv;
-  }
-  return rv;
-}
-
-// *************************************************************************
-
-G13_Action::~G13_Action() {}
-
-G13_Action_Keys::G13_Action_Keys(G13_Device &keypad,
-                                 const std::string &keys_string)
-    : G13_Action(keypad) {
-  std::vector<std::string> keys;
-  boost::split(keys, keys_string, boost::is_any_of("+"));
-
-  BOOST_FOREACH (std::string const &key, keys) {
-    auto kval = manager().find_input_key_value(key);
-    if (kval == BAD_KEY_VALUE) {
-      throw G13_CommandException("create action unknown key : " + key);
-    }
-    _keys.push_back(kval);
-  }
-
-  std::vector<int> _keys;
-}
-
-G13_Action_Keys::~G13_Action_Keys() {}
-
-void G13_Action_Keys::act(G13_Device &g13, bool is_down) {
-  for (auto key : _keys) {
-    g13.send_event(EV_KEY, key, is_down);
-    G13_LOG(trace, "sending KEY " << (is_down ? "DOWN " : "UP ") << key);
-  }
-}
-
-void G13_Action_Keys::dump(std::ostream &out) const {
-  out << " SEND KEYS: ";
-
-  for (size_t i = 0; i < _keys.size(); i++) {
-    if (i)
-      out << " + ";
-    out << manager().find_input_key_name(_keys[i]);
-  }
-}
-
-G13_Action_PipeOut::G13_Action_PipeOut(G13_Device &keypad,
-                                       const std::string &out)
-    : G13_Action(keypad), _out(out + "\n") {}
-G13_Action_PipeOut::~G13_Action_PipeOut() {}
-
-void G13_Action_PipeOut::act(G13_Device &kp, bool is_down) {
-  if (is_down) {
-    kp.write_output_pipe(_out);
-  }
-}
-
-void G13_Action_PipeOut::dump(std::ostream &o) const {
-  o << "WRITE PIPE : " << repr(_out);
-}
-
-G13_Action_Command::G13_Action_Command(G13_Device &keypad,
-                                       const std::string &cmd)
-    : G13_Action(keypad), _cmd(cmd) {}
-G13_Action_Command::~G13_Action_Command() {}
-
-void G13_Action_Command::act(G13_Device &kp, bool is_down) {
-  if (is_down) {
-    keypad().command(_cmd.c_str());
-  }
-}
-
-void G13_Action_Command::dump(std::ostream &o) const {
-  o << "COMMAND : " << repr(_cmd);
-}
-
-G13_ActionPtr G13_Device::make_action(const std::string &action) {
-  if (!action.size()) {
-    throw G13_CommandException("empty action string");
-  }
-  if (action[0] == '>') {
-    return G13_ActionPtr(new G13_Action_PipeOut(*this, &action[1]));
-  } else if (action[0] == '!') {
-    return G13_ActionPtr(new G13_Action_Command(*this, &action[1]));
-  } else {
-    return G13_ActionPtr(new G13_Action_Keys(*this, action));
-  }
-  throw G13_CommandException("can't create action for " + action);
-}
-
-// *************************************************************************
-
-void G13_Device::dump(std::ostream &o, int detail) {
-  o << "G13 id=" << id_within_manager() << std::endl
-    << "   input_pipe_name=" << repr(_input_pipe_name) << std::endl
-    << "   output_pipe_name=" << repr(_output_pipe_name) << std::endl
-    << "   current_profile=" << _current_profile->name() << std::endl
-    << "   current_font=" << _current_font->name() << std::endl;
-
-  if (detail > 0) {
-    o << "STICK" << std::endl;
-
-    stick().dump(o);
-
-    if (detail == 1) {
-      _current_profile->dump(o);
-    } else {
-      for (auto i = _profiles.begin(); i != _profiles.end(); i++) {
-        i->second->dump(o);
-      }
-    }
-  }
-}
-
-// *************************************************************************
-
-#define RETURN_FAIL(message)                                                   \
-  {                                                                            \
-    G13_LOG(error, message);                                                   \
-    return;                                                                    \
-  }
-
-struct command_adder {
-  command_adder(G13_Device::CommandFunctionTable &t, const char *name)
-      : _t(t), _name(name) {}
-
-  G13_Device::CommandFunctionTable &_t;
-  std::string _name;
-  command_adder &operator+=(G13_Device::COMMAND_FUNCTION f) {
-    _t[_name] = f;
-    return *this;
-  };
-};
-
-#define G13_DEVICE_COMMAND(name)                                               \
-  ;                                                                            \
-  command_adder BOOST_PP_CAT(add_, name)(_command_table,                       \
-                                         BOOST_PP_STRINGIZE(name));            \
-  BOOST_PP_CAT(add_, name) += [this](const char *remainder)
-
-inline const char *advance_ws(const char* &source, std::string &dest) {
-  const char *space = source ? strchr(source, ' ') : 0;
-
-  if (space) {
-    dest = std::string(source, space - source);
-    source = space + 1;
-  } else {
-    dest = source;
-    source = 0;
-  }
-
-  return source;
-};
-
-void G13_Device::_init_commands() {
-  G13_DEVICE_COMMAND(out) {
-    lcd().write_string(remainder);
-  }
-
-  G13_DEVICE_COMMAND(pos) {
-    int row, col;
-    if (sscanf(remainder, "%i %i", &row, &col) == 2) {
-      lcd().write_pos(row, col);
-    } else {
-      RETURN_FAIL("bad pos : " << remainder);
-    }
-  }
-
-  G13_DEVICE_COMMAND(bind) {
-    std::string keyname;
-    advance_ws(remainder, keyname);
-    std::string action = remainder;
-    try {
-      if (auto key = _current_profile->find_key(keyname)) {
-        key->set_action(make_action(action));
-      } else if (auto stick_key = _stick.zone(keyname)) {
-        stick_key->set_action(make_action(action));
-      } else {
-        RETURN_FAIL("bind key " << keyname << " unknown");
-      }
-      G13_LOG(trace, "bind " << keyname << " [" << action << "]");
-    } catch (const std::exception &ex) {
-      RETURN_FAIL("bind " << keyname << " " << action
-                          << " failed : " << ex.what());
-    }
-  }
-
-  G13_DEVICE_COMMAND(profile) { switch_to_profile(remainder); }
-  G13_DEVICE_COMMAND(font) { switch_to_font(remainder); }
-  G13_DEVICE_COMMAND(mod) { set_mode_leds(atoi(remainder)); }
-  G13_DEVICE_COMMAND(textmode) { lcd().text_mode = atoi(remainder); }
-
-  G13_DEVICE_COMMAND(rgb) {
-    int red, green, blue;
-    if (sscanf(remainder, "%i %i %i", &red, &green, &blue) == 3) {
-      set_key_color(red, green, blue);
-    } else {
-      RETURN_FAIL("rgb bad format: <" << remainder << ">");
-    }
-  }
-
-#define STICKMODE_TEST(r, data, elem)                                          \
-  if (mode == BOOST_PP_STRINGIZE(elem)) {                                      \
-    _stick.set_mode(BOOST_PP_CAT(STICK_, elem));                               \
-    return;                                                                    \
-  } else
-
-  G13_DEVICE_COMMAND(stickmode) {
-    std::string mode = remainder;
-
-    BOOST_PP_SEQ_FOR_EACH(
-        STICKMODE_TEST, _,
-        (ABSOLUTE)(RELATIVE)(KEYS)(CALCENTER)(CALBOUNDS)(CALNORTH)) {
-      RETURN_FAIL("unknown stick mode : <" << mode << ">");
-    }
-  }
-
-  G13_DEVICE_COMMAND(stickzone) {
-    std::string operation, zonename;
-    advance_ws(remainder, operation);
-    advance_ws(remainder, zonename);
-
-    if (operation != "add") {
-      G13_StickZone *zone = _stick.zone(zonename);
-
-      if (!zone) {
-        throw G13_CommandException("unknown stick zone");
-      }
-
-      if (operation == "action") {
-        zone->set_action(make_action(remainder));
-      } else if (operation == "bounds") {
-        double x1, y1, x2, y2;
-
-        if (sscanf(remainder, "%lf %lf %lf %lf", &x1, &y1, &x2, &y2) != 4) {
-          throw G13_CommandException("bad bounds format");
-        }
-
-        zone->set_bounds(G13_ZoneBounds(x1, y1, x2, y2));
-      } else if (operation == "del") {
-        _stick.remove_zone(*zone);
-      } else {
-        RETURN_FAIL("unknown stickzone operation: <" << operation << ">");
-      }
-    }
-  }
-
-  G13_DEVICE_COMMAND(dump) {
-    std::string target;
-    advance_ws(remainder, target);
-    if (target == "all") {
-      dump(std::cout, 3);
-    } else if (target == "current") {
-      dump(std::cout, 1);
-    } else if (target == "summary") {
-      dump(std::cout, 0);
-    } else {
-      RETURN_FAIL("unknown dump target: <" << target << ">");
-    }
-  }
-
-  G13_DEVICE_COMMAND(log_level) {
-    std::string level;
-    advance_ws(remainder, level);
-    manager().set_log_level(level);
-  }
-
-  G13_DEVICE_COMMAND(refresh) {
-    lcd().image_send();
-  }
-
-  G13_DEVICE_COMMAND(clear) {
-    lcd().image_clear();
-    lcd().image_send();
-  };
-}
-
-void G13_Device::command(char const *str) {
-  const char *remainder = str;
-
-  try {
-    std::string cmd;
-    advance_ws(remainder, cmd);
-
-    auto i = _command_table.find(cmd);
-    if (i == _command_table.end()) {
-      RETURN_FAIL("unknown command : " << cmd)
-    }
-
-    COMMAND_FUNCTION f = i->second;
-    f(remainder);
-
-    return;
-  } catch (const std::exception &ex) {
-    RETURN_FAIL("command failed : " << ex.what());
-  }
-}
-
-G13_Manager::G13_Manager()
-    : devs(0), ctx(0) {
-}
-
-// *************************************************************************
-
-bool G13_Manager::running = true;
-void G13_Manager::set_stop(int) { running = false; }
-
-std::string G13_Manager::string_config_value(const std::string &name) const {
-  try {
-    return find_or_throw(_string_config_values, name);
-  } catch (...) {
-    return "";
-  }
-}
-
-void G13_Manager::set_string_config_value(const std::string &name,
-                                          const std::string &value) {
-  G13_LOG(info, "set_string_config_value " << name << " = " << repr(value));
-  _string_config_values[name] = value;
-}
-
-std::string G13_Manager::make_pipe_name(G13_Device *d, bool is_input) {
-  if (is_input) {
-    std::string config_base = string_config_value("pipe_in");
-    if (config_base.size()) {
-      if (d->id_within_manager() == 0) {
-        return config_base;
-      } else {
-        return config_base + "-" +
-               boost::lexical_cast<std::string>(d->id_within_manager());
-      }
-    }
-    return CONTROL_DIR + "g13-" +
-           boost::lexical_cast<std::string>(d->id_within_manager());
-  } else {
-    std::string config_base = string_config_value("pipe_out");
-    if (config_base.size()) {
-      if (d->id_within_manager() == 0) {
-        return config_base;
-      } else {
-        return config_base + "-" +
-               boost::lexical_cast<std::string>(d->id_within_manager());
-      }
-    }
-
-    return CONTROL_DIR + "g13-" +
-           boost::lexical_cast<std::string>(d->id_within_manager()) + "_out";
-  }
-}
-
-int G13_Manager::run() {
-  init_keynames();
-  display_keys();
-
-  ssize_t cnt;
-  int ret;
-
-  ret = libusb_init(&ctx);
-  if (ret < 0) {
-    G13_LOG(error, "Initialization error: " << ret);
-    return 1;
-  }
-
-  libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, 3);
-  cnt = libusb_get_device_list(ctx, &devs);
-  if (cnt < 0) {
-    G13_LOG(error, "Error while getting device list");
-    return 1;
-  }
-
-  discover_g13s(devs, cnt, g13s);
-  libusb_free_device_list(devs, 1);
-  G13_LOG(info, "Found " << g13s.size() << " G13s");
-  if (g13s.size() == 0) {
-    return 1;
-  }
-
-  for (auto device : g13s) {
-    device->register_context(ctx);
-  }
-
-  signal(SIGINT, set_stop);
-
-  if (g13s.size() > 0 && logo_filename.size()) {
-    g13s[0]->write_lcd_file(logo_filename);
-  }
-
-  G13_LOG(info, "Active Stick zones ");
-  g13s[0]->stick().dump(std::cout);
-
-  std::string config_fn = string_config_value("config");
-  if (config_fn.size()) {
-    G13_LOG(info, "config_fn = " << config_fn);
-    g13s[0]->read_config_file(config_fn);
-  }
-
-  do {
-    if (g13s.size() > 0)
-      for (auto device : g13s) {
-        int status = device->read_keys();
-        device->read_commands();
-
-        if (status < 0) {
-          running = false;
-        }
-      }
-  } while (running && (g13s.size() > 0));
-
-  cleanup();
-
-  return 0;
-}
-
-} // namespace G13
diff --git a/g13d/manager.cc b/g13d/manager.cc
index 66a7e48..6ed48fb 100644
--- a/g13d/manager.cc
+++ b/g13d/manager.cc
@@ -1,3 +1,5 @@
+#include <signal.h>
+
 #include <boost/log/attributes.hpp>
 #include <boost/log/core/core.hpp>
 #include <boost/log/expressions.hpp>
@@ -14,6 +16,8 @@
 
 #include "device.h"
 #include "manager.h"
+#include "find_or_throw.h"
+#include "repr.h"
 
 namespace G13 {
 
@@ -69,4 +73,134 @@ void G13_Manager::set_log_level(const std::string &level) {
 
   G13_LOG(error, "unknown log level" << level);
 }
+
+void G13_Manager::cleanup() {
+  G13_LOG(info, "cleaning up");
+
+  for (auto device : g13s) {
+    device->cleanup();
+    delete device;
+  }
+
+  libusb_exit(ctx);
+}
+
+
+G13_Manager::G13_Manager()
+    : devs(0), ctx(0) {
+}
+
+// *************************************************************************
+
+bool G13_Manager::running = true;
+void G13_Manager::set_stop(int) { running = false; }
+
+std::string G13_Manager::string_config_value(const std::string &name) const {
+  try {
+    return find_or_throw(_string_config_values, name);
+  } catch (...) {
+    return "";
+  }
+}
+
+void G13_Manager::set_string_config_value(const std::string &name,
+                                          const std::string &value) {
+  G13_LOG(info, "set_string_config_value " << name << " = " << repr(value));
+  _string_config_values[name] = value;
+}
+
+#define CONTROL_DIR std::string("/tmp/")
+
+std::string G13_Manager::make_pipe_name(G13_Device *d, bool is_input) {
+  if (is_input) {
+    std::string config_base = string_config_value("pipe_in");
+    if (config_base.size()) {
+      if (d->id_within_manager() == 0) {
+        return config_base;
+      } else {
+        return config_base + "-" +
+               boost::lexical_cast<std::string>(d->id_within_manager());
+      }
+    }
+    return CONTROL_DIR + "g13-" +
+           boost::lexical_cast<std::string>(d->id_within_manager());
+  } else {
+    std::string config_base = string_config_value("pipe_out");
+    if (config_base.size()) {
+      if (d->id_within_manager() == 0) {
+        return config_base;
+      } else {
+        return config_base + "-" +
+               boost::lexical_cast<std::string>(d->id_within_manager());
+      }
+    }
+
+    return CONTROL_DIR + "g13-" +
+           boost::lexical_cast<std::string>(d->id_within_manager()) + "_out";
+  }
+}
+
+int G13_Manager::run() {
+  init_keynames();
+  display_keys();
+
+  ssize_t cnt;
+  int ret;
+
+  ret = libusb_init(&ctx);
+  if (ret < 0) {
+    G13_LOG(error, "Initialization error: " << ret);
+    return 1;
+  }
+
+  libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, 3);
+  cnt = libusb_get_device_list(ctx, &devs);
+  if (cnt < 0) {
+    G13_LOG(error, "Error while getting device list");
+    return 1;
+  }
+
+  discover_g13s(devs, cnt, g13s);
+  libusb_free_device_list(devs, 1);
+  G13_LOG(info, "Found " << g13s.size() << " G13s");
+  if (g13s.size() == 0) {
+    return 1;
+  }
+
+  for (auto device : g13s) {
+    device->register_context(ctx);
+  }
+
+  signal(SIGINT, set_stop);
+
+  if (g13s.size() > 0 && logo_filename.size()) {
+    g13s[0]->write_lcd_file(logo_filename);
+  }
+
+  G13_LOG(info, "Active Stick zones ");
+  g13s[0]->stick().dump(std::cout);
+
+  std::string config_fn = string_config_value("config");
+  if (config_fn.size()) {
+    G13_LOG(info, "config_fn = " << config_fn);
+    g13s[0]->read_config_file(config_fn);
+  }
+
+  do {
+    if (g13s.size() > 0)
+      for (auto device : g13s) {
+        int status = device->read_keys();
+        device->read_commands();
+
+        if (status < 0) {
+          running = false;
+        }
+      }
+  } while (running && (g13s.size() > 0));
+
+  cleanup();
+
+  return 0;
+}
+
 } // namespace G13