--- a/trunk/src/gx_head/engine/jsonrpc.cpp
+++ b/trunk/src/gx_head/engine/jsonrpc.cpp
@@ -31,63 +31,102 @@
 
 
 class JsonValue {
+protected:
+    JsonValue() {}
+    virtual ~JsonValue() {}
+    friend class JsonArray;
 public:
-    static boost::shared_ptr<JsonValue> create(gx_system::JsonParser& jp);
-    virtual void writeJSON(gx_system::JsonWriter& jw) = 0;
+    virtual double getFloat() const;
+    virtual int getInt() const;
+    virtual const Glib::ustring& getString() const;
 };
 
-typedef std::vector<boost::shared_ptr<JsonValue> > jsonarray;
-
 class JsonString: public JsonValue {
-public:
+private:
     Glib::ustring string;
     JsonString(Glib::ustring s): JsonValue(), string(s) {}
-    virtual void writeJSON(gx_system::JsonWriter& jw);
+    ~JsonString() {}
+    friend class JsonArray;
+    virtual const Glib::ustring& getString() const;
 };
 
 class JsonFloat: public JsonValue {
-public:
+private:
     double value;
     JsonFloat(double v): value(v) {}
-    virtual void writeJSON(gx_system::JsonWriter& jw);
+    ~JsonFloat() {}
+    friend class JsonArray;
+    virtual double getFloat() const;
 };
 
 class JsonInt: public JsonValue {
-public:
+private:
     int value;
     JsonInt(int v): value(v) {}
-    virtual void writeJSON(gx_system::JsonWriter& jw);
+    ~JsonInt() {}
+    friend class JsonArray;
+    virtual double getFloat() const;
+    virtual int getInt() const;
 };
 
-boost::shared_ptr<JsonValue> JsonValue::create(gx_system::JsonParser& jp) {
+class JsonArray: public std::vector<JsonValue*> {
+public:
+    JsonArray():std::vector<JsonValue*>() {}
+    ~JsonArray();
+    void append(gx_system::JsonParser& jp);
+};
+
+JsonArray::~JsonArray() {
+    for (iterator i = begin(); i != end(); ++i) {
+	delete *i;
+    }
+}
+
+void JsonArray::append(gx_system::JsonParser& jp) {
     if (jp.peek() == gx_system::JsonParser::value_string) {
 	jp.next();
-	return boost::shared_ptr<JsonValue>(new JsonString(jp.current_value()));
+	push_back(new JsonString(jp.current_value()));
     } else if (jp.peek() == gx_system::JsonParser::value_number) {
 	jp.next();
 	const char *str = jp.current_value().c_str();
 	char *endptr;
 	int n = strtol(str, &endptr, 10);
 	if (*endptr == '\0') {
-	    return boost::shared_ptr<JsonValue>(new JsonInt(n));
+	    push_back(new JsonInt(n));
 	} else {
-	    return boost::shared_ptr<JsonValue>(new JsonFloat(atof(str)));
+	    push_back(new JsonFloat(atof(str)));
 	}
     } else {
 	throw gx_system::JsonException("unexpected token");
     }
 }
 
-void JsonString::writeJSON(gx_system::JsonWriter& jw) {
-    jw.write(string);
-}
-
-void JsonFloat::writeJSON(gx_system::JsonWriter& jw) {
-    jw.write(value);
-}
-
-void JsonInt::writeJSON(gx_system::JsonWriter& jw) {
-    jw.write(value);
+double JsonValue::getFloat() const {
+    throw RpcError(-32602, "Invalid param -- float expected");
+}
+
+int JsonValue::getInt() const {
+    throw RpcError(-32602, "Invalid param -- int expected");
+}
+
+const Glib::ustring& JsonValue::getString() const {
+    throw RpcError(-32602, "Invalid param -- string expected");
+}
+
+double JsonFloat::getFloat() const {
+    return value;
+}
+
+double JsonInt::getFloat() const {
+    return value;
+}
+
+int JsonInt::getInt() const {
+    return value;
+}
+
+const Glib::ustring& JsonString::getString() const {
+    return string;
 }
 
 /****************************************************************
@@ -101,15 +140,22 @@
 private:
     MyService& serv;
     Glib::RefPtr<Gio::SocketConnection> connection;
-    __gnu_cxx::stdio_filebuf<char> filebuf;
-    istream is;
+    std::stringbuf inbuf;
     gx_system::JsonParser jp;
     __gnu_cxx::stdio_filebuf<char> writebuf;
     ostream os;
     gx_system::JsonWriter jw;
+    sigc::connection conn_preset_changed;
+    sigc::connection conn_state_changed;
+    sigc::connection conn_freq_changed;
+    sigc::connection conn_display;
+    sigc::connection conn_display_state;
+    sigc::connection conn_selection_done;
+    sigc::connection conn_log_message;
+private:
     void exec(Glib::ustring cmd);
-    void call(Glib::ustring& method, jsonarray& params);
-    void notify(Glib::ustring& method, jsonarray& params);
+    void call(Glib::ustring& method, JsonArray& params);
+    void notify(Glib::ustring& method, JsonArray& params);
     bool request(bool batch_start);
     void write_error(int code, const char *message);
     void write_error(int code, Glib::ustring& message) { write_error(code, message.c_str()); }
@@ -125,6 +171,9 @@
     void set_display_state(TunerSwitcher::SwitcherState newstate);
     void on_selection_done();
     void on_log_message(const string& msg, gx_system::GxMsgType tp, bool plugged);
+    void listen(const Glib::ustring& tp);
+    void unlisten(const Glib::ustring& tp);
+    void process(istringstream& is);
 
 public:
     CmdConnection(MyService& serv, const Glib::RefPtr<Gio::SocketConnection>& connection_);
@@ -135,30 +184,77 @@
 CmdConnection::CmdConnection(MyService& serv_, const Glib::RefPtr<Gio::SocketConnection>& connection_)
     : serv(serv_),
       connection(connection_),
-      filebuf(connection->get_socket()->get_fd(), std::ios::in),
-      is(&filebuf),
-      jp(&is),
+      jp(),
       writebuf(connection->get_socket()->get_fd(), std::ios::out),
       os(&writebuf),
-      jw(&os) {
-    serv.settings.signal_selection_changed().connect(
-	sigc::mem_fun(*this, &CmdConnection::preset_changed));
-    serv.jack.get_engine().signal_state_change().connect(
-	sigc::mem_fun(*this, &CmdConnection::on_engine_state_change));
-    serv.jack.get_engine().tuner.signal_freq_changed().connect(
+      jw(&os),
+      conn_preset_changed(),
+      conn_state_changed(),
+      conn_freq_changed(),
+      conn_display(),
+      conn_display_state(),
+      conn_selection_done(),
+      conn_log_message() {
+}
+
+void CmdConnection::listen(const Glib::ustring& tp) {
+    bool all = (tp == "all");
+    if (all || tp == "preset") {
+	conn_preset_changed = serv.settings.signal_selection_changed().connect(
+	    sigc::mem_fun(*this, &CmdConnection::preset_changed));
+    }
+    if (all || tp == "state") {
+	conn_state_changed = serv.jack.get_engine().signal_state_change().connect(
+	    sigc::mem_fun(*this, &CmdConnection::on_engine_state_change));
+    }
+    if (all || tp == "freq") {
+	conn_freq_changed = serv.jack.get_engine().tuner.signal_freq_changed().connect(
 	    sigc::mem_fun(this, &CmdConnection::on_tuner_freq_changed));
-    serv.tuner_switcher.signal_display().connect(sigc::mem_fun(this, &CmdConnection::display));
-    serv.tuner_switcher.signal_set_state().connect(sigc::mem_fun(this, &CmdConnection::set_display_state));
-    serv.tuner_switcher.signal_selection_done().connect(sigc::mem_fun(this, &CmdConnection::on_selection_done));
-    gx_system::Logger::get_logger().signal_message().connect(
-	sigc::mem_fun(this, &CmdConnection::on_log_message));
-    gx_system::Logger::get_logger().unplug_queue();
+    }
+    if (all || tp == "display") {
+	conn_display = serv.tuner_switcher.signal_display().connect(
+	    sigc::mem_fun(this, &CmdConnection::display));
+	conn_display_state = serv.tuner_switcher.signal_set_state().connect(
+	    sigc::mem_fun(this, &CmdConnection::set_display_state));
+    }
+    if (all || tp == "tuner") {
+	conn_selection_done = serv.tuner_switcher.signal_selection_done().connect(
+	    sigc::mem_fun(this, &CmdConnection::on_selection_done));
+    }
+    if (all || tp == "logger") {
+	conn_log_message = gx_system::Logger::get_logger().signal_message().connect(
+	    sigc::mem_fun(this, &CmdConnection::on_log_message));
+	gx_system::Logger::get_logger().unplug_queue();
+    }
+}
+
+void CmdConnection::unlisten(const Glib::ustring& tp) {
+    bool all = (tp == "all");
+    if (all || tp == "preset") {
+	conn_preset_changed.disconnect();
+    }
+    if (all || tp == "state") {
+	conn_state_changed.disconnect();
+    }
+    if (all || tp == "freq") {
+	conn_freq_changed.disconnect();
+    }
+    if (all || tp == "display") {
+	conn_display.disconnect();
+	conn_display_state.disconnect();
+    }
+    if (all || tp == "tuner") {
+	conn_selection_done.disconnect();
+    }
+    if (all || tp == "logger") {
+	conn_log_message.disconnect();
+    }
 }
 
 void CmdConnection::on_log_message(const string& msg, gx_system::GxMsgType tp, bool plugged) {
     switch (tp) {
-    case gx_system::kInfo:    return;
-    case gx_system::kWarning: return;
+    case gx_system::kInfo:    break;
+    case gx_system::kWarning: break;
     case gx_system::kError:   break;
     default:       break;
     }
@@ -251,19 +347,126 @@
 
 static bool do_reset = true;
 
-void CmdConnection::call(Glib::ustring& method, jsonarray& params) {
+static void write_plugin_state(gx_system::JsonWriter& jw, gx_engine::Plugin *i) {
+    jw.begin_object();
+    jw.write_key("id");
+    jw.write(i->pdef->id);
+    jw.write_key("on_off");
+    jw.write(i->on_off);
+    jw.write_key("box_visible");
+    jw.write(i->box_visible);
+    jw.write_key("plug_visible");
+    jw.write(i->plug_visible);
+    jw.write_key("position");
+    jw.write(i->position);
+    jw.write_key("post_pre");
+    jw.write(i->effect_post_pre);
+    jw.write_key("stereo");
+    jw.write((i->pdef->flags & PGN_STEREO) == PGN_STEREO);
+    const char *p;
+    p = i->pdef->category;
+    if (p) {
+	jw.write_key("category");
+	jw.write(p);
+    }
+    p = i->pdef->name;
+    if (p) {
+	jw.write_key("name");
+	jw.write(p);
+    }
+    p = i->pdef->shortname;
+    if (p) {
+	jw.write_key("shortname");
+	jw.write(p);
+    }
+    p = i->pdef->description;
+    if (p) {
+	jw.write_key("description");
+	jw.write(p);
+    }
+    jw.end_object();
+}
+
+static void write_parameter_state(gx_system::JsonWriter& jw, const gx_engine::Parameter& p) {
+    jw.begin_object();
+    if (p.hasRange()) {
+	jw.write_key("lower_bound");
+	jw.write(p.getLowerAsFloat());
+	jw.write_key("upper_bound");
+	jw.write(p.getUpperAsFloat());
+	jw.write_key("step");
+	jw.write(p.getStepAsFloat());
+    }
+    const value_pair *pairs = p.getValueNames();
+    if (pairs) {
+	jw.write_key("value_names");
+	jw.begin_array();
+	for (; pairs->value_id; pairs++) {
+	    jw.begin_array();
+	    jw.write(pairs->value_id);
+	    jw.write(p.value_label(*pairs));
+	    jw.end_array();
+	}
+	jw.end_array();
+    }
+    jw.write_key("name");
+    jw.write(p.l_name());
+    jw.write_key("group");
+    jw.write(p.l_group());
+    jw.write_key("type");
+    jw.write(p.get_typename());
+    gx_engine::Parameter::ctrl_type t = p.getControlType();
+    if (t == gx_engine::Parameter::Continuous) {
+	jw.write_key("ctl_continous");
+	jw.write(1);
+    } else if (t == gx_engine::Parameter::Switch) {
+	jw.write_key("ctl_switch");
+	jw.write(1);
+    } else if (t == gx_engine::Parameter::Enum) {
+	jw.write_key("ctl_enum");
+	jw.write(1);
+    }
+    jw.write_key("value");
+    jw.begin_object();
+    p.writeJSON(jw);
+    jw.end_object();
+    jw.end_object();
+}
+
+static inline bool unit_match(const Glib::ustring& id, const Glib::ustring& prefix, const char** gl) {
+    if (id.compare(0, prefix.size(), prefix) == 0) {
+	return true;
+    }
+    if (!gl) {
+	return false;
+    }
+    while (*gl) {
+	const char *p = *gl;
+	if (p[0] == '.') {
+	    p += 1;
+	    int n = strlen(p);
+	    if (strncmp(id.c_str(), p, n) == 0 && id[n] == '.') {
+		return true;
+	    }
+	}
+	gl += 2;
+    }
+    return false;
+}
+
+void CmdConnection::call(Glib::ustring& method, JsonArray& params) {
     static const char *cmds[] = {
 	"get","set","banks","presets","setpreset","getstate","setstate",
 	"switch_tuner","get_tuning","get_max_input_level","get_max_output_level",
-	"shutdown","stop","rtload","desc","list","help",
+	"shutdown","stop","rtload","desc","list","get_parameter","help",
 	0
     };
     if (method == "get") {
 	gx_engine::ParamMap& param = serv.settings.get_param();
 	jw.write_key("result");
 	jw.begin_object();
-	for (jsonarray::iterator i = params.begin(); i != params.end(); ++i) {
-	    Glib::ustring& attr = boost::dynamic_pointer_cast<JsonString>(*i)->string;
+	for (JsonArray::iterator i = params.begin(); i != params.end(); ++i) {
+	    const Glib::ustring& attr = (*i)->getString();
 	    if (!param.hasId(attr)) {
 		jw.write_key(attr);
 		if (attr == "sys.active_mono_plugins") {
@@ -271,7 +474,7 @@
 		    serv.jack.get_engine().pluginlist.ordered_mono_list(l, PGN_MODE_NORMAL);
 		    jw.begin_array();
 		    for (list<gx_engine::Plugin*>::iterator i = l.begin(); i != l.end(); ++i) {
-			jw.write((*i)->pdef->id);
+			write_plugin_state(jw, *i);
 		    }
 		    jw.end_array();
 		} else if (attr == "sys.active_stereo_plugins") {
@@ -279,7 +482,25 @@
 		    serv.jack.get_engine().pluginlist.ordered_stereo_list(l, PGN_MODE_NORMAL);
 		    jw.begin_array();
 		    for (list<gx_engine::Plugin*>::iterator i = l.begin(); i != l.end(); ++i) {
-			jw.write((*i)->pdef->id);
+			write_plugin_state(jw, *i);
+		    }
+		    jw.end_array();
+		} else if (attr == "sys.visible_mono_plugins") {
+		    list<gx_engine::Plugin*> l;
+		    const int bits = (PGN_GUI|gx_engine::PGNI_DYN_POSITION);
+		    serv.jack.get_engine().pluginlist.ordered_list(l, false, bits, bits);
+		    jw.begin_array();
+		    for (list<gx_engine::Plugin*>::iterator i = l.begin(); i != l.end(); ++i) {
+			write_plugin_state(jw, *i);
+		    }
+		    jw.end_array();
+		} else if (attr == "sys.visible_stereo_plugins") {
+		    list<gx_engine::Plugin*> l;
+		    const int bits = (PGN_GUI|gx_engine::PGNI_DYN_POSITION);
+		    serv.jack.get_engine().pluginlist.ordered_list(l, true, bits, bits);
+		    jw.begin_array();
+		    for (list<gx_engine::Plugin*>::iterator i = l.begin(); i != l.end(); ++i) {
+			write_plugin_state(jw, *i);
 		    }
 		    jw.end_array();
 		} else {
@@ -290,6 +511,18 @@
 	    param[attr].writeJSON(jw);
 	}
 	jw.end_object();
+    } else if (method == "get_parameter") {
+	gx_engine::ParamMap& param = serv.settings.get_param();
+	jw.write_key("result");
+	jw.begin_object();
+	for (JsonArray::iterator i = params.begin(); i != params.end(); ++i) {
+	    const Glib::ustring& attr = (*i)->getString();
+	    if (param.hasId(attr)) {
+		jw.write_key(attr);
+		write_parameter_state(jw, param[attr]);
+	    }
+	}
+	jw.end_object();
     } else if (method == "banks") {
 	gx_system::PresetBanks& banks = serv.settings.banks;
 	jw.write_key("result");
@@ -299,7 +532,10 @@
 	}
 	jw.end_array();
     } else if (method == "presets") {
-	gx_system::PresetFile* pf = serv.settings.banks.get_file(boost::dynamic_pointer_cast<JsonString>(params[0])->string);
+	gx_system::PresetFile* pf = serv.settings.banks.get_file(params[0]->getString());
+	if (!pf) {
+	    throw RpcError(-32602, "Invalid params");
+	}
 	jw.write_key("result");
 	jw.begin_array();
 	for (gx_system::PresetFile::iterator i = pf->begin(); i != pf->end(); ++i) {
@@ -339,36 +575,43 @@
     } else if (method == "jack_cpu_load") {
 	jw.write_key("result");
 	jw.write(serv.jack.get_jcpu_load());
+    } else if (method == "queryunit") {
+	if (params.size() != 1) {
+	    throw RpcError(-32602, "Invalid params -- 1 parameter expected");
+	}
+	gx_engine::Plugin *p = serv.jack.get_engine().pluginlist.find_plugin(params[0]->getString().c_str());
+	if (!p) {
+	    throw RpcError(-32602, "Invalid params -- plugin not found");
+	}
+	Glib::ustring unitprefix = p->pdef->id;
+	unitprefix += ".";
+	const char **gl = p->pdef->groups;
+	gx_engine::ParamMap& param = serv.settings.get_param();
+	jw.write_key("result");
+	jw.begin_object();
+	for (gx_engine::ParamMap::iterator i = param.begin(); i != param.end(); ++i) {
+	    if (unit_match(i->first, unitprefix, gl)) {
+		jw.write_key(i->first);
+		write_parameter_state(jw, *i->second);
+	    }
+	}
+	jw.end_object();
     } else if (method == "desc") {
 	gx_engine::ParamMap& param = serv.settings.get_param();
 	jw.write_key("result");
 	jw.begin_object();
-	for (jsonarray::iterator i = params.begin(); i != params.end(); ++i) {
-	    Glib::ustring& attr = boost::dynamic_pointer_cast<JsonString>(*i)->string;
+	for (JsonArray::iterator i = params.begin(); i != params.end(); ++i) {
+	    const Glib::ustring& attr = (*i)->getString();
 	    jw.write_key(attr);
 	    if (!param.hasId(attr)) {
 		jw.write("unknown");
 		continue;
 	    }
-	    const gx_engine::Parameter& p = param[attr];
-	    jw.begin_object();
-	    if (p.hasRange()) {
-		jw.write_key("lower_bound");
-		jw.write(p.getLowerAsFloat());
-		jw.write_key("upper_bound");
-		jw.write(p.getUpperAsFloat());
-	    }
-	    jw.write_key("name");
-	    jw.write(p.l_name());
-	    jw.write_key("group");
-	    jw.write(p.l_group());
-	    jw.write_key("type");
-	    jw.write(p.get_typename());
-	    jw.end_object();
+	    write_parameter_state(jw, param[attr]);
 	}
 	jw.end_object();
     } else if (method == "list") {
-	Glib::ustring prefix = boost::dynamic_pointer_cast<JsonString>(params[0])->string;
+	const Glib::ustring& prefix = params[0]->getString();
 	gx_engine::ParamMap& param = serv.settings.get_param();
 	jw.write_key("result");
 	jw.begin_array();
@@ -390,34 +633,57 @@
     }
 }
 
-void CmdConnection::notify(Glib::ustring& method, jsonarray& params) {
+void CmdConnection::notify(Glib::ustring& method, JsonArray& params) {
     if (method == "set") {
+	if (params.size() & 1) {
+	    throw RpcError(-32602, "Invalid param -- array length must be even");
+	}
 	gx_engine::ParamMap& param = serv.settings.get_param();
-	jp.next(gx_system::JsonParser::begin_object);
-	Glib::ustring& attr = boost::dynamic_pointer_cast<JsonString>(params[0])->string;
-	if (param.hasId(attr)) {
-	    gx_engine::Parameter& p = param[attr];
-	    if (p.isFloat()) {
-		p.getFloat().set(boost::dynamic_pointer_cast<JsonFloat>(params[1])->value);
-	    } else if (p.isInt()) {
-		p.getInt().set(boost::dynamic_pointer_cast<JsonInt>(params[1])->value);
-	    } else if (p.isUInt()) {
-		p.getInt().set(boost::dynamic_pointer_cast<JsonInt>(params[1])->value);
-	    } else if (p.isBool()) {
-		p.getInt().set(boost::dynamic_pointer_cast<JsonInt>(params[1])->value);
-	    } else if (p.isSwitch()) {
-		p.getInt().set(boost::dynamic_pointer_cast<JsonInt>(params[1])->value);
-	    } else if (p.isFile()) {
-		p.getInt().set(boost::dynamic_pointer_cast<JsonInt>(params[1])->value);
-	    } else if (p.isString()) {
-		p.getInt().set(boost::dynamic_pointer_cast<JsonInt>(params[1])->value);
-	    }
+	for (unsigned int i = 0; i < params.size(); i += 2) {
+	    const Glib::ustring& attr = params[i]->getString();
+	    if (param.hasId(attr)) {
+		gx_engine::Parameter& p = param[attr];
+		JsonValue *v = params[i+1];
+		if (p.isFloat()) {
+		    p.getFloat().set(v->getFloat());
+		} else if (p.isInt()) {
+		    gx_engine::IntParameter& pi = p.getInt();
+		    int i;
+		    if (p.getControlType() == gx_engine::Parameter::Enum) {
+			i = pi.idx_from_id(v->getString());
+		    } else {
+			i = v->getInt();
+		    }
+		    pi.set(i);
+		} else if (p.isUInt()) {
+		    gx_engine::UIntParameter& pi = p.getUInt();
+		    int i;
+		    if (p.getControlType() == gx_engine::Parameter::Enum) {
+			i = pi.idx_from_id(v->getString());
+		    } else {
+			i = v->getInt();
+		    }
+		    pi.set(i);
+		} else if (p.isBool()) {
+		    p.getBool().set(v->getInt());
+		} else if (p.isSwitch()) {
+		    p.getSwitch().set(v->getInt());
+		} else if (p.isFile()) {
+		    p.getFile().set(Gio::File::create_for_path(v->getString()));
+		} else if (p.isString()) {
+		    p.getString().set(v->getString());
+		} else {
+		    throw RpcError(-32602, "Invalid param -- unknown variable");
+		}
+	    }
+	    serv.save_state();
 	}
     } else if (method == "setpreset") {
-	gx_system::PresetFile* pf = serv.settings.banks.get_file(boost::dynamic_pointer_cast<JsonString>(params[0])->string);
-	serv.settings.load_preset(pf, boost::dynamic_pointer_cast<JsonString>(params[1])->string);
+	gx_system::PresetFile* pf = serv.settings.banks.get_file(params[0]->getString());
+	serv.settings.load_preset(pf, params[1]->getString());
+	serv.save_state();
     } else if (method == "setstate") {
-	Glib::ustring &p = boost::dynamic_pointer_cast<JsonString>(params[0])->string;
+	const Glib::ustring &p = params[0]->getString();
 	if (p == "stopped") {
 	    serv.jack.get_engine().set_state(gx_engine::kEngineOff);
 	} else if (p == "running") {
@@ -429,19 +695,23 @@
 	}
 	serv.jack.get_engine().check_module_lists();
     } else if (method == "switch_tuner") {
-	boost::shared_ptr<JsonInt> p = boost::dynamic_pointer_cast<JsonInt>(params[0]);
-	if (!p) {
-	    throw RpcError(-32602, "Invalid params");
-	}
-	serv.jack.get_engine().tuner.used_for_livedisplay(p->value);
+	serv.jack.get_engine().tuner.used_for_livedisplay(params[0]->getInt());
 	serv.jack.get_engine().check_module_lists();
     } else if (method == "switch") {
 	serv.on_switcher_toggled(true);
     } else if (method == "shutdown") {
 	connection->close();
-	serv.loop->quit();
+	serv.quit_mainloop();
     } else if (method == "stop") {
 	do_reset = false;
+    } else if (method == "listen") {
+	for (JsonArray::iterator i = params.begin(); i != params.end(); ++i) {
+	    CmdConnection::listen((*i)->getString());
+	}
+    } else if (method == "unlisten") {
+	for (JsonArray::iterator i = params.begin(); i != params.end(); ++i) {
+	    CmdConnection::unlisten((*i)->getString());
+	}
     } else {
 	throw RpcError(-32601, "Method not found");
     }
@@ -459,7 +729,7 @@
 
 bool CmdConnection::request(bool batch_start) {
     Glib::ustring method;
-    jsonarray params;
+    JsonArray params;
     Glib::ustring id;
     jp.next(gx_system::JsonParser::begin_object);
     while (jp.peek() != gx_system::JsonParser::end_object) {
@@ -476,7 +746,7 @@
 	    if (jp.peek() == gx_system::JsonParser::begin_array) {
 		jp.next(gx_system::JsonParser::begin_array);
 		while (jp.peek() != gx_system::JsonParser::end_array) {
-		    params.push_back(JsonValue::create(jp));
+		    params.append(jp);
 		}
 		jp.next(gx_system::JsonParser::end_array);
 	    } else if (jp.peek() == gx_system::JsonParser::begin_object) {
@@ -502,7 +772,8 @@
     if (id.empty()) {
 	try {
 	    notify(method, params);
-	} catch(RpcError&) {
+	} catch(RpcError& e) {
+	    error_response(e.code, e.message);
 	}
 	return false;
     } else {
@@ -537,7 +808,43 @@
 }
 
 bool CmdConnection::on_data(Glib::IOCondition cond) {
+    if (cond != Glib::IO_IN) {
+	delete this;
+	return false;
+    }
+    Glib::RefPtr<Gio::Socket> sock = connection->get_socket();
+    char buf[1000];
+    while (true) {
+	int n;
+	try {
+	    n = sock->receive(buf, sizeof(buf));
+	} catch(Glib::Error e) {
+	    if (e.code() == Gio::Error::WOULD_BLOCK) {
+		return true;
+	    }
+	    delete this;
+	    return false;
+	}
+	if (n <= 0) {
+	    delete this;
+	    return false;
+	}
+	char *p = buf;
+	while (n-- > 0) {
+	    inbuf.sputc(*p);
+	    if (*p == '\n') {
+		istringstream is;
+		is.ios::rdbuf(&inbuf);
+		process(is);
+	    }
+	    p++;
+	}
+    }
+}
+
+void CmdConnection::process(istringstream& is) {
     try {
+	jp.set_stream(&is);
 	bool resp = false;
 	is >> ws; // jp.peek() doesn't work at start of stream
 	if (is.peek() == '[') {
@@ -557,24 +864,21 @@
 	    os << endl;
 	    jw.reset();
 	}
-	return true;
-    } catch (gx_system::JsonExceptionEOF& e) {
-	delete this;
-	return false;
+	return;
     } catch (gx_system::JsonException& e) {
 	error_response(-32700, "Parse Error");
     } catch (RpcError& e) {
 	error_response(e.code, e.message);
     }
     char buf[101];
-    gsize n = connection->get_input_stream()->read(buf, sizeof(buf)-1);
+    is.seekg(0);
+    is.read(buf, sizeof(buf)-1);
+    gsize n = is.gcount();
     if (n == 0) {
-	delete this;
-	return false;
+	return;
     }
     buf[n] = '\0';
     gx_system::gx_print_error("JSON-RPC", Glib::ustring("error: skipped text: ")+buf);
-    return true;
 }
 
 
@@ -583,24 +887,55 @@
  */
 
 MyService::MyService(gx_preset::GxSettings& settings_, gx_jack::GxJack& jack_,
-		     Glib::RefPtr<Glib::MainLoop>& loop_)
+		     sigc::slot<void> quit_mainloop_, int port)
     : Gio::SocketService(),
       settings(settings_),
       jack(jack_),
-      loop(loop_),
+      quit_mainloop(quit_mainloop_),
       tuner_switcher(settings_, jack_.get_engine()),
       switcher_signal(&jack_.get_engine().get_ui(),
-		      &gx_engine::parameter_map["ui.live_play_switcher"].getBool().get_value()) { //FIXME
+		      &gx_engine::parameter_map["ui.live_play_switcher"].getBool().get_value()), //FIXME
+      oldest_unsaved(0),
+      last_change(0),
+      save_conn() {
     switcher_signal.changed.connect(sigc::mem_fun(this, &MyService::on_switcher_toggled));
     tuner_switcher.signal_selection_done().connect(sigc::mem_fun(this, &MyService::on_selection_done));
-    add_inet_port(7000);
+    add_inet_port(port);
+}
+
+//FIXME: this belongs into GxSettings
+void MyService::save_state() {
+    static const int min_idle = 2;   // seconds; after this idle time save changed state
+    static const int max_delay = 15; // seconds; maximum delay for save changed state
+    time_t now = time(NULL);
+    if (oldest_unsaved == 0) {
+	oldest_unsaved = last_change = now;
+	save_conn = Glib::signal_timeout().connect(sigc::bind_return(sigc::mem_fun(this, &MyService::save_state),false), 1000*min_idle);
+	return;
+    }
+    if (now - oldest_unsaved >= max_delay || now - last_change >= min_idle) {
+	settings.save_to_state();
+	sync();
+	oldest_unsaved = 0;
+	save_conn.disconnect();
+    } else {
+	last_change = now;
+	if (oldest_unsaved == 0) {
+	    oldest_unsaved = now;
+	}
+	save_conn.disconnect();
+	save_conn = Glib::signal_timeout().connect(sigc::bind_return(sigc::mem_fun(this, &MyService::save_state),false), 1000*min_idle);
+    }
 }
 
 bool MyService::on_incoming(const Glib::RefPtr<Gio::SocketConnection>& connection,
 			    const Glib::RefPtr<Glib::Object>& source_object) {
     CmdConnection *cc = new CmdConnection(*this, connection);
-    Glib::signal_io().connect(sigc::mem_fun(cc, &CmdConnection::on_data),
-			      connection->get_socket()->get_fd(), Glib::IO_IN);
+    Glib::RefPtr<Gio::Socket> sock = connection->get_socket();
+    sock->set_blocking(false);
+    Glib::signal_io().connect(
+	sigc::mem_fun(cc, &CmdConnection::on_data),
+	sock->get_fd(), Glib::IO_IN|Glib::IO_HUP);
     return true;
 }