changeset 21052:b702efa70fb5 draft default tip shane

Adding --json-sock option; publishes octave_link events to a UNIX socket.
author Shane F. Carr <shane.carr@wustl.edu>
date Mon, 04 Jan 2016 08:51:31 +0000
parents 5c5af828c248
children
files libgui/src/octave-qt-link.cc libgui/src/octave-qt-link.h libinterp/corefcn/input.cc libinterp/corefcn/json-main.cc libinterp/corefcn/json-main.h libinterp/corefcn/json-util.cc libinterp/corefcn/json-util.h libinterp/corefcn/module.mk libinterp/corefcn/octave-json-link.cc libinterp/corefcn/octave-json-link.h libinterp/corefcn/octave-link.cc libinterp/corefcn/octave-link.h libinterp/link-deps.mk libinterp/octave.cc libinterp/options-usage.h liboctave/util/oct-mutex.cc liboctave/util/oct-mutex.h scripts/plot/util/__gnuplot_drawnow__.m
diffstat 18 files changed, 1077 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/libgui/src/octave-qt-link.cc
+++ b/libgui/src/octave-qt-link.cc
@@ -646,6 +646,14 @@
 }
 
 void
+octave_qt_link::do_show_static_plot (const std::string& term,
+                                     const std::string& content)
+{
+  // Print to the console (ignore term)
+  octave_stdout << content;
+}
+
+void
 octave_qt_link::terminal_interrupt (void)
 {
   command_interpreter->interrupt ();
--- a/libgui/src/octave-qt-link.h
+++ b/libgui/src/octave-qt-link.h
@@ -135,6 +135,9 @@
 
   void do_show_doc (const std::string& file);
 
+  void do_show_static_plot (const std::string& term,
+                            const std::string& content);
+
   QMutex mutex;
   QWaitCondition waitcondition;
   void shutdown_confirmation (bool sd) {_shutdown_confirm_result = sd;}
--- a/libinterp/corefcn/input.cc
+++ b/libinterp/corefcn/input.cc
@@ -184,7 +184,11 @@
 
   eof = false;
 
-  std::string retval = command_editor::readline (s, eof);
+  std::string retval;
+  if (octave_link::request_input_enabled ())
+    retval = octave_link::request_input (s);
+  else
+    retval = command_editor::readline (s, eof);
 
   if (! eof && retval.empty ())
     retval = "\n";
new file mode 100644
--- /dev/null
+++ b/libinterp/corefcn/json-main.cc
@@ -0,0 +1,79 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "json-main.h"
+
+#include <iostream>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+
+// Analog of main-window.cc
+// TODO: Think more about concurrency and null pointer exceptions
+
+void* run_loop_pthread(void* arg) {
+  json_main* _json_main = static_cast<json_main*>(arg);
+  _json_main->run_loop();
+  return NULL;
+}
+
+void json_object_cb(std::string name, JSON_OBJECT_T jobj, void* arg) {
+  json_main* _json_main = static_cast<json_main*>(arg);
+  _json_main->process_json_object(name, jobj);
+}
+
+json_main::json_main(const std::string& json_sock_path)
+  : _json_sock_path (json_sock_path),
+    _loop_thread_active (false),
+    _octave_json_link (this)
+{
+  // Enable octave_json_link instance
+	octave_link::connect_link(&_octave_json_link);
+
+  // Open UNIX socket file descriptor
+  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+  struct sockaddr_un addr;
+  memset(&addr, 0, sizeof(addr));
+  addr.sun_family = AF_UNIX;
+  memcpy(&addr.sun_path, _json_sock_path.c_str(), sizeof(addr.sun_path)-1);
+  connect(
+    sockfd,
+    reinterpret_cast<struct sockaddr*>(&addr),
+    sizeof(addr));
+}
+
+json_main::~json_main(void) {
+  close(sockfd);
+
+  // TODO: Stop the _loop_thread
+}
+
+void json_main::publish_event(const std::string& name, JSON_OBJECT_T jobj) {
+  std::string jstr = json_util::to_message(name, jobj);
+  send(sockfd, jstr.c_str(), jstr.length(), 0);
+}
+
+void json_main::run_loop_on_new_thread(void) {
+  if (_loop_thread_active)
+    perror("won't run JSON socket loop multiple times");
+  _loop_thread_active = true;
+
+  pthread_create(
+    &_loop_thread,
+    NULL,
+    run_loop_pthread,
+    static_cast<void*>(this));
+}
+
+void json_main::run_loop(void) {
+  json_util::read_stream(
+    sockfd,
+    json_object_cb,
+    static_cast<void*>(this));
+}
+
+void json_main::process_json_object(std::string name, JSON_OBJECT_T jobj) {
+  _octave_json_link.receive_message(name, jobj);
+}
new file mode 100644
--- /dev/null
+++ b/libinterp/corefcn/json-main.h
@@ -0,0 +1,29 @@
+#ifndef json_main_h
+#define json_main_h
+
+#include <queue>
+#include <pthread.h>
+#include <stdio.h>
+
+#include "octave-json-link.h"
+#include "json-util.h"
+
+class json_main {
+public:
+	json_main(const std::string& json_sock_path);
+	~json_main(void);
+
+	void publish_event(const std::string& name, JSON_OBJECT_T jobj);
+	void run_loop_on_new_thread(void);
+	void run_loop(void);
+	void process_json_object(std::string name, JSON_OBJECT_T jobj);
+
+private:
+	octave_json_link _octave_json_link;
+	int sockfd;
+	std::string _json_sock_path;
+	pthread_t _loop_thread;
+	bool _loop_thread_active;
+};
+
+#endif
new file mode 100644
--- /dev/null
+++ b/libinterp/corefcn/json-util.cc
@@ -0,0 +1,226 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <cstdlib>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <json-c/arraylist.h>
+
+#include "str-vec.h"
+
+#include "json-util.h"
+
+JSON_OBJECT_T json_util::from_string(const std::string& str) {
+	return json_object_new_string(str.c_str());
+}
+
+JSON_OBJECT_T json_util::from_int(int i) {
+	return json_object_new_int(i);
+}
+
+JSON_OBJECT_T json_util::from_float(float flt) {
+	return json_object_new_double(flt);
+}
+
+JSON_OBJECT_T json_util::from_boolean(bool b) {
+	return json_object_new_boolean(b);
+}
+
+JSON_OBJECT_T json_util::empty() {
+	return json_object_new_object();
+}
+
+template<typename T>
+JSON_OBJECT_T json_object_from_list(const std::list<T>& list, JSON_OBJECT_T (*convert)(T)) {
+	JSON_OBJECT_T jobj = json_object_new_array();
+	for (
+		typename std::list<T>::const_iterator it = list.begin();
+		it != list.end();
+		++it
+	){
+		json_object_array_add(jobj, convert(*it));
+	}
+	return jobj;
+}
+
+JSON_OBJECT_T json_util::from_string_list(const std::list<std::string>& list) {
+	return json_object_from_list(list, json_util::from_value_string);
+}
+
+JSON_OBJECT_T json_util::from_string_vector(const string_vector& vect) {
+	// TODO: Make sure this function does what it's supposed to do
+	std::list<std::string> list;
+	for (int i = 0; i < vect.numel(); ++i) {
+		list.push_back(vect[i]);
+	}
+
+	return json_object_from_list(list, json_util::from_value_string);
+}
+
+JSON_OBJECT_T json_util::from_int_list(const std::list<int>& list) {
+	return json_object_from_list(list, json_util::from_int);
+}
+
+JSON_OBJECT_T json_util::from_float_list(const std::list<float>& list) {
+	return json_object_from_list(list, json_util::from_float);
+}
+
+JSON_OBJECT_T json_util::from_workspace_list(const std::list<workspace_element>& list) {
+	return json_object_from_list(list, json_util::from_workspace_element);
+}
+
+JSON_OBJECT_T json_util::from_filter_list(const octave_link::filter_list& list) {
+	return json_object_from_list(list, json_util::from_pair);
+}
+
+JSON_OBJECT_T json_util::from_value_string(const std::string str) {
+	return json_util::from_string(str);
+}
+
+JSON_OBJECT_T json_util::from_workspace_element(workspace_element element) {
+	JSON_MAP_T m;
+	m["scope"] = json_util::from_int(element.scope());
+	m["symbol"] = json_util::from_string(element.symbol());
+	m["class_name"] = json_util::from_string(element.class_name());
+	m["dimension"] = json_util::from_string(element.dimension());
+	m["value"] = json_util::from_string(element.value());
+	m["complex_flag"] = json_util::from_boolean(element.complex_flag());
+	return json_util::from_map(m);
+}
+
+JSON_OBJECT_T json_util::from_pair(std::pair<std::string, std::string> pair) {
+	JSON_OBJECT_T jobj = json_object_new_array();
+	json_object_array_add(jobj, json_object_new_string(pair.first.c_str()));
+	json_object_array_add(jobj, json_object_new_string(pair.second.c_str()));
+	return jobj;
+}
+
+JSON_OBJECT_T json_util::from_map(JSON_MAP_T m) {
+	JSON_OBJECT_T jobj = json_object_new_object();
+	for(
+		std::map<std::string, JSON_OBJECT_T>::iterator it = m.begin();
+		it != m.end();
+		++it
+	){
+		json_object_object_add(jobj, it->first.c_str(), it->second);
+	}
+	return jobj;
+}
+
+std::string json_util::to_message(const std::string& name, JSON_OBJECT_T jobj) {
+  JSON_OBJECT_T jmsg = json_object_new_array();
+  json_object_array_add(jmsg, json_util::from_string(name));
+  json_object_array_add(jmsg, jobj);
+  std::string str (json_object_to_json_string(jmsg));
+  return str;
+}
+
+std::string json_util::to_string(JSON_OBJECT_T jobj) {
+  return std::string(json_object_get_string(jobj));
+}
+
+template<typename T>
+std::list<T> json_object_to_list(JSON_OBJECT_T jobj, T (*convert)(JSON_OBJECT_T)) {
+	std::list<T> ret;
+
+	struct array_list* arr = json_object_get_array(jobj);
+	if (arr == NULL)
+		return ret;
+
+	for (int i = 0; i < array_list_length(arr); ++i) {
+		JSON_OBJECT_T jsub = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, i));
+		ret.push_back(convert(jsub));
+	}
+	return ret;
+}
+
+std::pair<std::list<int>, int> json_util::to_int_list_int_pair(JSON_OBJECT_T jobj) {
+	std::pair<std::list<int>, int> ret;
+
+	struct array_list* arr = json_object_get_array(jobj);
+	if (arr == NULL)
+		return ret;
+
+	JSON_OBJECT_T first = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 0));
+	JSON_OBJECT_T second = static_cast<JSON_OBJECT_T> (array_list_get_idx(arr, 1));
+
+	ret.first = json_object_to_list<int>(first, json_util::to_int);
+	ret.second = json_object_get_int(second);
+
+	return ret;
+}
+
+std::list<std::string> json_util::to_string_list(JSON_OBJECT_T jobj) {
+	return json_object_to_list<std::string>(jobj, json_util::to_string);
+}
+
+int json_util::to_int(JSON_OBJECT_T jobj) {
+	return json_object_get_int(jobj);
+}
+
+bool json_util::to_boolean(JSON_OBJECT_T jobj) {
+	return json_object_get_boolean(jobj);
+}
+
+void json_util::read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {
+
+	// Make some local variables
+	int BUF_LEN = 24;
+	char* buf = new char[BUF_LEN];  // buffer for socket read
+	int buf_len;  // length of new bytes in the buffer
+	int buf_offset;  // offset of the JSON parser in the buffer
+	JSON_OBJECT_T jobj;  // pointer to parsed JSON object
+	json_tokener* tok = json_tokener_new();  // JSON tokenizer instance
+	enum json_tokener_error jerr;  // status of JSON tokenizer
+
+	// Start the blocking I/O loop
+	while( (buf_len=recv(sockfd, buf, BUF_LEN, 0)) > 0) {
+		buf_offset = 0;
+		while(buf_offset < buf_len){
+			jobj = json_tokener_parse_ex(tok, buf + buf_offset, buf_len - buf_offset);
+			jerr = json_tokener_get_error(tok);
+			buf_offset += tok->char_offset;
+
+			// Do we need more material in order to make JSON?
+			if (jerr == json_tokener_continue) {
+				continue;
+			}
+
+			// Make a new tokenizer
+			json_tokener_free(tok);
+			tok = json_tokener_new();
+
+			// Did we encounter a malformed JSON object?
+			if (jerr != json_tokener_success) {
+				fprintf(stderr,
+					"JSON parse error: %s: '%.*s'\n",
+					json_tokener_error_desc(jerr),
+					1,
+					buf + buf_offset);
+				fflush(stderr);
+				break;
+			}
+
+			// Our object is ready
+			process_message(jobj, cb, arg);
+		}
+	}
+
+	json_tokener_free(tok);
+	free(buf);
+}
+
+void json_util::process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg) {
+  if (!json_object_is_type(jobj, json_type_array))
+    return;
+  if (json_object_array_length(jobj) != 2)
+    return;
+
+  cb(
+  	json_util::to_string(json_object_array_get_idx(jobj, 0)),
+  	json_object_array_get_idx(jobj, 1),
+  	arg
+  );
+}
new file mode 100644
--- /dev/null
+++ b/libinterp/corefcn/json-util.h
@@ -0,0 +1,55 @@
+#ifndef json_util_h
+#define json_util_h
+
+#include <json-c/json.h>
+#include <map>
+#include <list>
+
+#include "workspace-element.h"
+#include "octave-link.h"
+
+class string_vector;
+
+// All of the code interacting with the external JSON library should be in
+// the json-util.h and json-util.cc files.  This way, if we want to change
+// the external JSON library, we can do it all in one place.
+
+#define JSON_OBJECT_T json_object*
+#define JSON_MAP_T std::map<std::string, JSON_OBJECT_T>
+
+class json_util {
+public:
+	static JSON_OBJECT_T from_string(const std::string& str);
+	static JSON_OBJECT_T from_int(int i);
+	static JSON_OBJECT_T from_float(float flt);
+	static JSON_OBJECT_T from_boolean(bool b);
+	static JSON_OBJECT_T empty();
+
+	static JSON_OBJECT_T from_string_list(const std::list<std::string>& list);
+	static JSON_OBJECT_T from_string_vector(const string_vector& list);
+	static JSON_OBJECT_T from_int_list(const std::list<int>& list);
+	static JSON_OBJECT_T from_float_list(const std::list<float>& list);
+	static JSON_OBJECT_T from_workspace_list(const std::list<workspace_element>& list);
+	static JSON_OBJECT_T from_filter_list(const octave_link::filter_list& list);
+
+	static JSON_OBJECT_T from_value_string(const std::string str);
+	static JSON_OBJECT_T from_workspace_element(workspace_element element);
+	static JSON_OBJECT_T from_pair(std::pair<std::string, std::string> pair);
+
+	static JSON_OBJECT_T from_map(JSON_MAP_T m);
+
+	static std::string to_message(const std::string& name, JSON_OBJECT_T jobj);
+
+	static std::string to_string(JSON_OBJECT_T jobj);
+	static std::pair<std::list<int>, int> to_int_list_int_pair(JSON_OBJECT_T jobj);
+	static std::list<std::string> to_string_list(JSON_OBJECT_T jobj);
+	static int to_int(JSON_OBJECT_T jobj);
+	static bool to_boolean(JSON_OBJECT_T jobj);
+
+	static void read_stream(int sockfd, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);
+
+private:
+	static void process_message(JSON_OBJECT_T jobj, void (*cb)(std::string, JSON_OBJECT_T, void*), void* arg);
+};
+
+#endif
--- a/libinterp/corefcn/module.mk
+++ b/libinterp/corefcn/module.mk
@@ -42,6 +42,8 @@
   libinterp/corefcn/help.h \
   libinterp/corefcn/hook-fcn.h \
   libinterp/corefcn/input.h \
+  libinterp/corefcn/json-main.h \
+  libinterp/corefcn/json-util.h \
   libinterp/corefcn/load-path.h \
   libinterp/corefcn/load-save.h \
   libinterp/corefcn/ls-ascii-helper.h \
@@ -72,6 +74,7 @@
   libinterp/corefcn/oct-tex-parser.h \
   libinterp/corefcn/oct.h \
   libinterp/corefcn/octave-default-image.h \
+  libinterp/corefcn/octave-json-link.h \
   libinterp/corefcn/octave-link.h \
   libinterp/corefcn/octave-preserve-stream-state.h \
   libinterp/corefcn/pager.h \
@@ -166,6 +169,8 @@
   libinterp/corefcn/hook-fcn.cc \
   libinterp/corefcn/input.cc \
   libinterp/corefcn/inv.cc \
+  libinterp/corefcn/json-main.cc \
+  libinterp/corefcn/json-util.cc \
   libinterp/corefcn/kron.cc \
   libinterp/corefcn/load-path.cc \
   libinterp/corefcn/load-save.cc \
@@ -199,6 +204,7 @@
   libinterp/corefcn/oct-strstrm.cc \
   libinterp/corefcn/oct-tex-lexer.ll \
   libinterp/corefcn/oct-tex-parser.yy \
+  libinterp/corefcn/octave-json-link.cc \
   libinterp/corefcn/octave-link.cc \
   libinterp/corefcn/ordschur.cc \
   libinterp/corefcn/pager.cc \
new file mode 100644
--- /dev/null
+++ b/libinterp/corefcn/octave-json-link.cc
@@ -0,0 +1,332 @@
+/*
+
+Copyright (C) 2015-2016 Shane Carr
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+Octave is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <iostream>
+#include "octave-json-link.h"
+#include "workspace-element.h"
+#include "json-main.h"
+
+// MAP_SET(m, foo, string)
+//   => m["foo"] = json_util::from_string(foo);
+#define MAP_SET(M, FIELD, TYPE){ \
+	m[#FIELD] = json_util::from_##TYPE (FIELD); \
+}
+
+octave_json_link::octave_json_link(json_main* __json_main)
+	: octave_link (),
+		_json_main (__json_main)
+{
+	_request_input_enabled = true;
+}
+
+octave_json_link::~octave_json_link(void) { }
+
+std::string octave_json_link::do_request_input(const std::string& prompt) {
+	// Triggered whenever the console prompts for user input
+	_publish_event("request-input", json_util::from_string(prompt));
+
+	return request_input_queue.dequeue();
+}
+
+bool octave_json_link::do_confirm_shutdown(void) {
+	// Triggered when the kernel tries to exit
+	_publish_event("confirm-shutdown", json_util::empty());
+
+	return confirm_shutdown_queue.dequeue();
+}
+
+bool octave_json_link::do_exit(int status) {
+	JSON_MAP_T m;
+	MAP_SET(m, status, int);
+	_publish_event("exit", json_util::from_map(m));
+
+	// It is our responsibility in octave_link to call exit. If we don't, then
+	// the kernel waits for 24 hours expecting us to do something.
+	::exit(status);
+
+	return true;
+}
+
+bool octave_json_link::do_copy_image_to_clipboard(const std::string& file) {
+	// This endpoint might be unused?  (References appear only in libgui)
+	JSON_MAP_T m;
+	MAP_SET(m, file, string);
+	_publish_event("copy-image-to-clipboard", json_util::from_map(m));
+
+	return true;
+}
+
+bool octave_json_link::do_edit_file(const std::string& file) {
+	// Triggered in "edit" for existing files
+	JSON_MAP_T m;
+	MAP_SET(m, file, string);
+	_publish_event("edit-file", json_util::from_map(m));
+
+	return true;
+}
+
+bool octave_json_link::do_prompt_new_edit_file(const std::string& file) {
+	// Triggered in "edit" for new files
+	JSON_MAP_T m;
+	MAP_SET(m, file, string);
+	_publish_event("prompt-new-edit-file", json_util::from_map(m));
+
+	return prompt_new_edit_file_queue.dequeue();
+}
+
+int octave_json_link::do_message_dialog(const std::string& dlg, const std::string& msg, const std::string& title) {
+	// Triggered in "msgbox", "helpdlg", and "errordlg", among others
+	JSON_MAP_T m;
+	MAP_SET(m, dlg, string); // i.e., m["dlg"] = json_util::from_string(dlg);
+	MAP_SET(m, msg, string);
+	MAP_SET(m, title, string);
+	_publish_event("message-dialog", json_util::from_map(m));
+
+	return message_dialog_queue.dequeue();
+}
+
+std::string octave_json_link::do_question_dialog(const std::string& msg, const std::string& title, const std::string& btn1, const std::string& btn2, const std::string& btn3, const std::string& btndef) {
+	// Triggered in "questdlg"
+	JSON_MAP_T m;
+	MAP_SET(m, msg, string);
+	MAP_SET(m, title, string);
+	MAP_SET(m, btn1, string);
+	MAP_SET(m, btn2, string);
+	MAP_SET(m, btn3, string);
+	MAP_SET(m, btndef, string);
+	_publish_event("question-dialog", json_util::from_map(m));
+
+	return question_dialog_queue.dequeue();
+}
+
+std::pair<std::list<int>, int> octave_json_link::do_list_dialog(const std::list<std::string>& list, const std::string& mode, int width, int height, const std::list<int>& initial_value, const std::string& name, const std::list<std::string>& prompt, const std::string& ok_string, const std::string& cancel_string) {
+	// Triggered in "listdlg"
+	JSON_MAP_T m;
+	MAP_SET(m, list, string_list);
+	MAP_SET(m, mode, string);
+	MAP_SET(m, width, int);
+	MAP_SET(m, height, int);
+	MAP_SET(m, initial_value, int_list);
+	MAP_SET(m, name, string);
+	MAP_SET(m, prompt, string_list);
+	MAP_SET(m, ok_string, string);
+	MAP_SET(m, cancel_string, string);
+	_publish_event("list-dialog", json_util::from_map(m));
+
+	return list_dialog_queue.dequeue();
+}
+
+std::list<std::string> octave_json_link::do_input_dialog(const std::list<std::string>& prompt, const std::string& title, const std::list<float>& nr, const std::list<float>& nc, const std::list<std::string>& defaults) {
+	// Triggered in "inputdlg"
+	JSON_MAP_T m;
+	MAP_SET(m, prompt, string_list);
+	MAP_SET(m, title, string);
+	MAP_SET(m, nr, float_list);
+	MAP_SET(m, nc, float_list);
+	MAP_SET(m, defaults, string_list);
+	_publish_event("input-dialog", json_util::from_map(m));
+
+	return input_dialog_queue.dequeue();
+}
+
+std::list<std::string> octave_json_link::do_file_dialog(const filter_list& filter, const std::string& title, const std::string &filename, const std::string &pathname, const std::string& multimode) {
+	// Triggered in "uiputfile", "uigetfile", and "uigetdir"
+	JSON_MAP_T m;
+	MAP_SET(m, filter, filter_list);
+	MAP_SET(m, title, string);
+	MAP_SET(m, filename, string);
+	MAP_SET(m, pathname, string);
+	MAP_SET(m, multimode, string);
+	_publish_event("file-dialog", json_util::from_map(m));
+	
+	return file_dialog_queue.dequeue();
+}
+
+int octave_json_link::do_debug_cd_or_addpath_error(const std::string& file, const std::string& dir, bool addpath_option) {
+	// This endpoint might be unused?  (No references)
+	JSON_MAP_T m;
+	MAP_SET(m, file, string);
+	MAP_SET(m, dir, string);
+	MAP_SET(m, addpath_option, boolean);
+	_publish_event("debug-cd-or-addpath-error", json_util::from_map(m));
+
+	return debug_cd_or_addpath_error_queue.dequeue();
+}
+
+void octave_json_link::do_change_directory(const std::string& dir) {
+	// This endpoint might be unused?  (References appear only in libgui)
+	JSON_MAP_T m;
+	MAP_SET(m, dir, string);
+	_publish_event("change-directory", json_util::from_map(m));
+}
+
+void octave_json_link::do_execute_command_in_terminal(const std::string& command) {
+	// This endpoint might be unused?  (References appear only in libgui)
+	JSON_MAP_T m;
+	MAP_SET(m, command, string);
+	_publish_event("execute-command-in-terminal", json_util::from_map(m));
+}
+
+void octave_json_link::do_set_workspace(bool top_level, bool debug, const std::list<workspace_element>& ws) {
+	// Triggered on every new line entry
+	JSON_MAP_T m;
+	MAP_SET(m, top_level, boolean);
+	MAP_SET(m, debug, boolean);
+	MAP_SET(m, ws, workspace_list);
+	_publish_event("set-workspace", json_util::from_map(m));
+}
+
+void octave_json_link::do_clear_workspace(void) {
+	// Triggered on "clear" command (but not "clear all" or "clear foo")
+	_publish_event("clear-workspace", json_util::empty());
+}
+
+void octave_json_link::do_set_history(const string_vector& hist) {
+	// Called at startup, possibly more?
+	JSON_MAP_T m;
+	MAP_SET(m, hist, string_vector);
+	_publish_event("set-history", json_util::from_map(m));
+}
+
+void octave_json_link::do_append_history(const std::string& hist_entry) {
+	// Appears to be tied to readline, if available
+	JSON_MAP_T m;
+	MAP_SET(m, hist_entry, string);
+	_publish_event("append-history", json_util::from_map(m));
+}
+
+void octave_json_link::do_clear_history(void) {
+	// Appears to be tied to readline, if available
+	_publish_event("clear-history", json_util::empty());
+}
+
+void octave_json_link::do_pre_input_event(void) {
+	// noop
+}
+
+void octave_json_link::do_post_input_event(void) {
+	// noop
+}
+
+void octave_json_link::do_enter_debugger_event(const std::string& file, int line) {
+	JSON_MAP_T m;
+	MAP_SET(m, file, string);
+	MAP_SET(m, line, int);
+	_publish_event("enter-debugger-event", json_util::from_map(m));
+}
+
+void octave_json_link::do_execute_in_debugger_event(const std::string& file, int line) {
+	JSON_MAP_T m;
+	MAP_SET(m, file, string);
+	MAP_SET(m, line, int);
+	_publish_event("execute-in-debugger-event", json_util::from_map(m));
+}
+
+void octave_json_link::do_exit_debugger_event(void) {
+	_publish_event("exit-debugger-event", json_util::empty());
+}
+
+void octave_json_link::do_update_breakpoint(bool insert, const std::string& file, int line) {
+	JSON_MAP_T m;
+	MAP_SET(m, insert, boolean);
+	MAP_SET(m, file, string);
+	MAP_SET(m, line, int);
+	_publish_event("update-breakpoint", json_util::from_map(m));
+}
+
+void octave_json_link::do_set_default_prompts(std::string& ps1, std::string& ps2, std::string& ps4) {
+	// Triggered upon interpreter startup
+	JSON_MAP_T m;
+	MAP_SET(m, ps1, string);
+	MAP_SET(m, ps2, string);
+	MAP_SET(m, ps4, string);
+	_publish_event("set-default-prompts", json_util::from_map(m));
+}
+
+void octave_json_link::do_show_preferences(void) {
+	// Triggered on "preferences" command
+	_publish_event("show-preferences", json_util::empty());
+}
+
+void octave_json_link::do_show_doc(const std::string& file) {
+	// Triggered on "doc" command
+	_publish_event("show-doc", json_util::from_string(file));
+}
+
+void octave_json_link::do_show_static_plot(const std::string& term, const std::string& content) {
+	// Triggered on all plot commands with setenv("GNUTERM","svg")
+	JSON_MAP_T m;
+	MAP_SET(m, term, string);
+	MAP_SET(m, content, string);
+	_publish_event("show-static-plot", json_util::from_map(m));
+}
+
+void octave_json_link::receive_message (const std::string& name, JSON_OBJECT_T jobj) {
+	if (name == "cmd" || name == "request-input-answer") {
+		std::string answer = json_util::to_string(jobj);
+		request_input_queue.enqueue(answer);
+	}
+	else if (name == "confirm-shutdown-answer"){
+		bool answer = json_util::to_boolean(jobj);
+		confirm_shutdown_queue.enqueue(answer);
+	}
+	else if (name == "prompt-new-edit-file-answer"){
+		bool answer = json_util::to_boolean(jobj);
+		prompt_new_edit_file_queue.enqueue(answer);
+	}
+	else if (name == "message-dialog-answer"){
+		int answer = json_util::to_int(jobj);
+		message_dialog_queue.enqueue(answer);
+	}
+	else if (name == "question-dialog-answer") {
+		std::string answer = json_util::to_string(jobj);
+		question_dialog_queue.enqueue(answer);
+	}
+	else if (name == "list-dialog-answer") {
+		std::pair<std::list<int>, int> answer = json_util::to_int_list_int_pair(jobj);
+		list_dialog_queue.enqueue(answer);
+	}
+	else if (name == "input-dialog-answer") {
+		std::list<std::string> answer = json_util::to_string_list(jobj);
+		input_dialog_queue.enqueue(answer);
+	}
+	else if (name == "file-dialog-answer") {
+		std::list<std::string> answer = json_util::to_string_list(jobj);
+		file_dialog_queue.enqueue(answer);
+	}
+	else if (name == "debug-cd-or-addpath-error-answer") {
+		int answer = json_util::to_int(jobj);
+		debug_cd_or_addpath_error_queue.enqueue(answer);
+	}
+	else {
+		std::cerr << "warning: received unknown message: " << name << std::endl;
+	}
+}
+
+void octave_json_link::_publish_event(const std::string& name, JSON_OBJECT_T jobj) {
+	_json_main->publish_event(name, jobj);
+}
+
new file mode 100644
--- /dev/null
+++ b/libinterp/corefcn/octave-json-link.h
@@ -0,0 +1,183 @@
+/*
+
+Copyright (C) 2015-2016 Shane Carr
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+Octave is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef octave_json_link_h
+#define octave_json_link_h
+
+#include <list>
+#include <string>
+
+#include "octave-link.h"
+#include "json-util.h"
+#include "oct-mutex.h"
+
+// Circular reference
+class json_main;
+
+// Thread-safe queue
+template<typename T> class json_queue {
+public:
+  json_queue();
+  ~json_queue();
+
+  void enqueue(const T& value);
+  T dequeue();
+
+private:
+  std::queue<T> _queue;
+  octave_mutex _mutex;
+};
+
+class octave_json_link : public octave_link
+{
+
+public:
+
+  octave_json_link (json_main* __json_main);
+
+  ~octave_json_link (void);
+
+  std::string do_request_input (const std::string& prompt);
+
+  bool do_confirm_shutdown (void);
+  bool do_exit (int status);
+
+  bool do_copy_image_to_clipboard (const std::string& file);
+
+  bool do_edit_file (const std::string& file);
+  bool do_prompt_new_edit_file (const std::string& file);
+
+  int do_message_dialog (const std::string& dlg, const std::string& msg,
+                         const std::string& title);
+
+  std::string
+  do_question_dialog (const std::string& msg, const std::string& title,
+                      const std::string& btn1, const std::string& btn2,
+                      const std::string& btn3, const std::string& btndef);
+
+  std::pair<std::list<int>, int>
+  do_list_dialog (const std::list<std::string>& list,
+                  const std::string& mode,
+                  int width, int height,
+                  const std::list<int>& initial_value,
+                  const std::string& name,
+                  const std::list<std::string>& prompt,
+                  const std::string& ok_string,
+                  const std::string& cancel_string);
+
+  std::list<std::string>
+  do_input_dialog (const std::list<std::string>& prompt,
+                   const std::string& title,
+                   const std::list<float>& nr,
+                   const std::list<float>& nc,
+                   const std::list<std::string>& defaults);
+
+  std::list<std::string>
+  do_file_dialog (const filter_list& filter, const std::string& title,
+                  const std::string &filename, const std::string &pathname,
+                  const std::string& multimode);
+
+  int
+  do_debug_cd_or_addpath_error (const std::string& file,
+                                const std::string& dir,
+                                bool addpath_option);
+
+  void do_change_directory (const std::string& dir);
+
+  void do_execute_command_in_terminal (const std::string& command);
+
+  void do_set_workspace (bool top_level, bool debug,
+                         const std::list<workspace_element>& ws);
+
+  void do_clear_workspace (void);
+
+  void do_set_history (const string_vector& hist);
+  void do_append_history (const std::string& hist_entry);
+  void do_clear_history (void);
+
+  void do_pre_input_event (void);
+  void do_post_input_event (void);
+
+  void do_enter_debugger_event (const std::string& file, int line);
+  void do_execute_in_debugger_event (const std::string& file, int line);
+  void do_exit_debugger_event (void);
+
+  void do_update_breakpoint (bool insert, const std::string& file, int line);
+
+  void do_set_default_prompts (std::string& ps1, std::string& ps2,
+                               std::string& ps4);
+
+  void do_show_preferences (void);
+
+  void do_show_doc (const std::string& file);
+
+  void do_show_static_plot (const std::string& term,
+                            const std::string& content);
+
+  // Custom methods
+  void receive_message (const std::string& name, JSON_OBJECT_T jobj);
+
+private:
+  json_main* _json_main;
+  void _publish_event (const std::string& name, JSON_OBJECT_T jobj);
+
+  // Queues
+  json_queue<std::string> request_input_queue;
+  json_queue<bool> confirm_shutdown_queue;
+  json_queue<bool> prompt_new_edit_file_queue;
+  json_queue<int> message_dialog_queue;
+  json_queue<std::string> question_dialog_queue;
+  json_queue<std::pair<std::list<int>, int> > list_dialog_queue;
+  json_queue<std::list<std::string> > input_dialog_queue;
+  json_queue<std::list<std::string> > file_dialog_queue;
+  json_queue<int> debug_cd_or_addpath_error_queue;
+};
+
+// Template classes require definitions in the header file...
+
+template<typename T>
+json_queue<T>::json_queue() { }
+
+template<typename T>
+json_queue<T>::~json_queue() { }
+
+template<typename T>
+void json_queue<T>::enqueue(const T& value) {
+  _mutex.lock();
+  _queue.push(value);
+  _mutex.cond_signal();
+  _mutex.unlock();
+}
+
+template<typename T>
+T json_queue<T>::dequeue() {
+  _mutex.lock();
+  while (_queue.empty()) {
+    _mutex.cond_wait();
+  }
+  T value = _queue.front();
+  _queue.pop();
+  _mutex.unlock();
+  return value;
+}
+
+#endif
--- a/libinterp/corefcn/octave-link.cc
+++ b/libinterp/corefcn/octave-link.cc
@@ -50,7 +50,7 @@
 
 octave_link::octave_link (void)
   : event_queue_mutex (new octave_mutex ()), gui_event_queue (),
-    debugging (false), link_enabled (true)
+    debugging (false), link_enabled (true), _request_input_enabled (false)
 {
   command_editor::add_event_hook (octave_readline_hook);
 }
@@ -392,3 +392,18 @@
   return octave_value (octave_link::show_doc (file));
 }
 
+DEFUN (__octave_link_show_static_plot__, args, ,
+       "-*- texinfo -*-\n\
+@deftypefn {} {} __octave_link_show_static_plot__ (@var{term}, @var{content})\n\
+Undocumented internal function.\n\
+@end deftypefn")
+{
+  if (args.length () != 2)
+    return ovl ();
+
+  std::string term = args(0).string_value();
+  std::string content = args(1).string_value();
+
+  return octave_value (octave_link::show_static_plot (term, content));
+}
+
--- a/libinterp/corefcn/octave-link.h
+++ b/libinterp/corefcn/octave-link.h
@@ -276,6 +276,13 @@
       instance->do_post_input_event ();
   }
 
+  static std::string request_input (const std::string& prompt)
+  {
+    return request_input_enabled ()
+      ? instance->do_request_input (prompt)
+      : std::string ();
+  }
+
   static void enter_debugger_event (const std::string& file, int line)
   {
     if (enabled ())
@@ -323,6 +330,11 @@
     return instance_ok () ? instance->link_enabled : false;
   }
 
+  static bool request_input_enabled (void)
+  {
+    return enabled () ? instance->_request_input_enabled : false;
+  }
+
   static bool
   show_preferences ()
   {
@@ -336,7 +348,7 @@
   }
 
   static bool
-  show_doc (const std::string & file)
+  show_doc (const std::string& file)
   {
     if (enabled ())
       {
@@ -345,7 +357,18 @@
       }
     else
       return false;
+  }
 
+  static bool
+  show_static_plot (const std::string& term, const std::string& content)
+  {
+    if (enabled ())
+      {
+        instance->do_show_static_plot (term, content);
+        return true;
+      }
+    else
+      return false;
   }
 
 private:
@@ -396,6 +419,9 @@
   void do_entered_readline_hook (void) { }
   void do_finished_readline_hook (void) { }
 
+  bool _request_input_enabled;
+  virtual std::string do_request_input (const std::string&) = 0;
+
   virtual bool do_confirm_shutdown (void) = 0;
   virtual bool do_exit (int status) = 0;
 
@@ -473,7 +499,10 @@
 
   virtual void do_show_preferences (void) = 0;
 
-  virtual void do_show_doc (const std::string &file) = 0;
+  virtual void do_show_doc (const std::string& file) = 0;
+
+  virtual void do_show_static_plot (const std::string& term,
+                                    const std::string& content) = 0;
 };
 
 #endif
--- a/libinterp/link-deps.mk
+++ b/libinterp/link-deps.mk
@@ -15,7 +15,8 @@
   $(GL2PS_LIBS) \
   $(LLVM_LIBS) \
   $(JAVA_LIBS) \
-  $(LAPACK_LIBS)
+  $(LAPACK_LIBS) \
+  -ljson-c
 
 LIBOCTINTERP_LINK_OPTS = \
   $(FT2_LDFLAGS) \
--- a/libinterp/octave.cc
+++ b/libinterp/octave.cc
@@ -56,6 +56,7 @@
 #include "file-io.h"
 #include "help.h"
 #include "input.h"
+#include "json-main.h"
 #include "lex.h"
 #include "load-path.h"
 #include "load-save.h"
@@ -91,6 +92,7 @@
 int octave_cmdline_argc;
 char **octave_cmdline_argv;
 int octave_embedded;
+json_main* _json_main;
 
 // The command-line options.
 static string_vector octave_argv;
@@ -153,6 +155,10 @@
 // (--image-path)
 static std::string image_path;
 
+// The value for "JSON_SOCK" specified on the command line.
+// (--json-sock)
+static std::string json_sock_path;
+
 // If TRUE, ignore the window system even if it is available.
 // (--no-window-system, -W)
 static bool no_window_system = false;
@@ -659,6 +665,11 @@
           Fjit_enable (octave_value (true));
           break;
 
+        case JSON_SOCK_OPTION:
+          if (optarg)
+            json_sock_path = optarg;
+          break;
+
         case LINE_EDITING_OPTION:
           forced_line_editing = true;
           break;
@@ -838,6 +849,11 @@
 
   initialize_version_info ();
 
+  if (!json_sock_path.empty ()) {
+    _json_main = new json_main(json_sock_path);
+    _json_main->run_loop_on_new_thread();
+  }
+
   // Make all command-line arguments available to startup files,
   // including PKG_ADD files.
 
@@ -1018,7 +1034,7 @@
   if (args.length () != 0)
     print_usage ();
 
-  return ovl (start_gui);
+  return ovl (octave_link::enabled());
 }
 
 /*
--- a/libinterp/options-usage.h
+++ b/libinterp/options-usage.h
@@ -33,8 +33,8 @@
        [--echo-commands] [--eval CODE] [--exec-path path]\n\
        [--force-gui] [--help] [--image-path path]\n\
        [--info-file file] [--info-program prog] [--interactive]\n\
-       [--jit-compiler] [--line-editing] [--no-gui] [--no-history]\n\
-       [--no-init-file] [--no-init-path] [--no-line-editing]\n\
+       [--jit-compiler] [--json-sock] [--line-editing] [--no-gui]\n\
+       [--no-history][--no-init-file] [--no-init-path] [--no-line-editing]\n\
        [--no-site-file] [--no-window-system] [--norc] [-p path]\n\
        [--path path] [--persist] [--silent] [--traditional]\n\
        [--verbose] [--version] [file]";
@@ -56,15 +56,16 @@
 #define INFO_PROG_OPTION 8
 #define DEBUG_JIT_OPTION 9
 #define JIT_COMPILER_OPTION 10
-#define LINE_EDITING_OPTION 11
-#define NO_GUI_OPTION 12
-#define NO_INIT_FILE_OPTION 13
-#define NO_INIT_PATH_OPTION 14
-#define NO_LINE_EDITING_OPTION 15
-#define NO_SITE_FILE_OPTION 16
-#define PERSIST_OPTION 17
-#define TEXI_MACROS_FILE_OPTION 18
-#define TRADITIONAL_OPTION 19
+#define JSON_SOCK_OPTION 11
+#define LINE_EDITING_OPTION 12
+#define NO_GUI_OPTION 13
+#define NO_INIT_FILE_OPTION 14
+#define NO_INIT_PATH_OPTION 15
+#define NO_LINE_EDITING_OPTION 16
+#define NO_SITE_FILE_OPTION 17
+#define PERSIST_OPTION 18
+#define TEXI_MACROS_FILE_OPTION 19
+#define TRADITIONAL_OPTION 20
 struct option long_opts[] =
 {
   { "braindead",                no_argument,       0, TRADITIONAL_OPTION },
@@ -82,6 +83,7 @@
   { "info-program",             required_argument, 0, INFO_PROG_OPTION },
   { "interactive",              no_argument,       0, 'i' },
   { "jit-compiler",             no_argument,       0, JIT_COMPILER_OPTION },
+  { "json-sock",                required_argument, 0, JSON_SOCK_OPTION },
   { "line-editing",             no_argument,       0, LINE_EDITING_OPTION },
   { "no-gui",                   no_argument,       0, NO_GUI_OPTION },
   { "no-history",               no_argument,       0, 'H' },
@@ -128,6 +130,7 @@
   --info-program PROGRAM  Use PROGRAM for reading info files.\n\
   --interactive, -i       Force interactive behavior.\n\
   --jit-compiler          Enable the JIT compiler.\n\
+  --json-sock PATH        Listen to and publish events on this UNIX socket.\n\
   --line-editing          Force readline use for command-line editing.\n\
   --no-gui                Disable the graphical user interface.\n\
   --no-history, -H        Don't save commands to the history list\n\
--- a/liboctave/util/oct-mutex.cc
+++ b/liboctave/util/oct-mutex.cc
@@ -53,6 +53,18 @@
   return false;
 }
 
+void
+octave_base_mutex::cond_wait (void)
+{
+  (*current_liboctave_error_handler) ("mutex not supported on this platform");
+}
+
+void
+octave_base_mutex::cond_signal (void)
+{
+  (*current_liboctave_error_handler) ("mutex not supported on this platform");
+}
+
 #if defined (__WIN32__) && ! defined (__CYGWIN__)
 
 class
@@ -63,11 +75,13 @@
     : octave_base_mutex ()
   {
     InitializeCriticalSection (&cs);
+    InitializeConditionVariable (&cv);
   }
 
   ~octave_w32_mutex (void)
   {
     DeleteCriticalSection (&cs);
+    // no need to delete cv: http://stackoverflow.com/a/28981408/1407170
   }
 
   void lock (void)
@@ -85,8 +99,21 @@
     return (TryEnterCriticalSection (&cs) != 0);
   }
 
+  void cond_wait (void)
+  {
+    SleepConditionVariableCS (&cv, &cs, INFINITE);
+  }
+
+  void cond_signal (void)
+  {
+    WakeConditionVariable (&cv);
+  }
+
+  void 
+
 private:
   CRITICAL_SECTION cs;
+  CONDITION_VARIABLE cv;
 };
 
 static DWORD octave_thread_id = 0;
@@ -118,11 +145,18 @@
     pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
     pthread_mutex_init (&pm, &attr);
     pthread_mutexattr_destroy (&attr);
+
+    pthread_condattr_t condattr;
+
+    pthread_condattr_init (&condattr);
+    pthread_cond_init (&condv, &condattr);
+    pthread_condattr_destroy (&condattr);
   }
 
   ~octave_pthread_mutex (void)
   {
     pthread_mutex_destroy (&pm);
+    pthread_cond_destroy (&condv);
   }
 
   void lock (void)
@@ -140,8 +174,20 @@
     return (pthread_mutex_trylock (&pm) == 0);
   }
 
+  void cond_wait (void)
+  {
+    pthread_cond_wait (&condv, &pm);
+  }
+
+  void cond_signal (void)
+  {
+    pthread_cond_signal (&condv);
+  }
+
 private:
   pthread_mutex_t pm;
+  pthread_cond_t condv;
+
 };
 
 static pthread_t octave_thread_id = 0;
--- a/liboctave/util/oct-mutex.h
+++ b/liboctave/util/oct-mutex.h
@@ -43,6 +43,10 @@
 
   virtual bool try_lock (void);
 
+  virtual void cond_wait (void);
+
+  virtual void cond_signal (void);
+
 private:
   octave_refcount<int> count;
 };
@@ -95,6 +99,16 @@
     return rep->try_lock ();
   }
 
+  void cond_wait (void)
+  {
+    rep->cond_wait ();
+  }
+
+  void cond_signal (void)
+  {
+    rep->cond_signal ();
+  }
+
 protected:
   octave_base_mutex *rep;
 };
--- a/scripts/plot/util/__gnuplot_drawnow__.m
+++ b/scripts/plot/util/__gnuplot_drawnow__.m
@@ -75,7 +75,9 @@
       new_stream = false;
     endif
     term = gnuplot_default_term (plot_stream);
-    if (strcmp (term, "dumb"))
+    ## FIXME: add more terminals to the following line
+    print_to_tmp_file = any (strcmp (term, {"dumb", "svg"}));
+    if (print_to_tmp_file)
       ## popen2 eats stdout of gnuplot, use temporary file instead
       dumb_tmp_file = tempname ();
       enhanced = gnuplot_set_term (plot_stream(1), new_stream, h,
@@ -85,10 +87,11 @@
     endif
     __gnuplot_draw_figure__ (h, plot_stream(1), enhanced, mono);
     fflush (plot_stream(1));
-    if (strcmp (term, "dumb"))
+    if (print_to_tmp_file)
       fid = -1;
       while (fid < 0)
-        pause (0.1);
+        ## FIXME: Is there a better way to read this file?
+        pause (0.01);
         fid = fopen (dumb_tmp_file, 'r');
       endwhile
       ## reprint the plot on screen
@@ -98,7 +101,11 @@
         if (a(1) == 12)
           a = a(2:end);  # avoid ^L at the beginning
         endif
-        puts (a);
+        if isguirunning ()
+          __octave_link_show_static_plot__ (term, a);
+        else
+          puts (a);
+        endif
       endif
       unlink (dumb_tmp_file);
     endif