From 4734685358e877ff0b35d84db0bef6cf5af4834f Mon Sep 17 00:00:00 2001 From: Sander Dijkhuis Date: Wed, 10 Sep 2008 23:45:12 +0200 Subject: [PATCH] Initial commit --- Makefile | 16 +++ TODO | 36 +++++ atlantis-server-glue.h | 124 ++++++++++++++++ atlantis-server.c | 349 +++++++++++++++++++++++++++++++++++++++++++++ atlantis-server.h | 59 ++++++++ atlantis-server.xml | 13 ++ create-dbus-glue.sh | 1 + default-services/markup.js | 31 ++++ directory-provider.py | 58 ++++++++ interface.css | 81 +++++++++++ interface.js | 294 ++++++++++++++++++++++++++++++++++++++ main.c | 22 +++ 12 files changed, 1084 insertions(+) create mode 100644 Makefile create mode 100644 TODO create mode 100644 atlantis-server-glue.h create mode 100644 atlantis-server.c create mode 100644 atlantis-server.h create mode 100644 atlantis-server.xml create mode 100644 create-dbus-glue.sh create mode 100644 default-services/markup.js create mode 100644 directory-provider.py create mode 100644 interface.css create mode 100644 interface.js create mode 100644 main.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1516b46 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +PROGNAME=atlantis-server + +CC=cc +CFLAGS=-g -Wall -pedantic -ansi `pkg-config --cflags glib-2.0 dbus-glib-1` -DG_LOG_DOMAIN=\"Atlantis\" +LFLAGS=-lm `pkg-config --libs glib-2.0 dbus-glib-1` +RM=rm -rf +OFILES=main.o atlantis-server.o + +$(PROGNAME): $(OFILES) + $(CC) $(CFLAGS) $(OFILES) $(LFLAGS) -o $(PROGNAME) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + $(RM) *.o $(PROGNAME) diff --git a/TODO b/TODO new file mode 100644 index 0000000..36c01a6 --- /dev/null +++ b/TODO @@ -0,0 +1,36 @@ +A T L A N T I S T O D O & P L A N S ============== Sander Dijkhuis 2008 + +- ATLANTIS 1D --------------------------------------------------------------- + + Atlantis 1D is a one-dimensional environment for using the computer. It + consists of what you could call one large document in which the user can + view, create and manipulate all kinds of stuff. The user interacts can + run commands. + + What follows are lists with things that need to be implemented before we + can release specific versions. The lists are by no means complete. + +--- Atlantis 1D0.1 ---------------------------------------------------------- + + - Add autosave. + - Make sure the service system works well - don't allow for duplicate + services (or commands), allow for service removal. + - Make sure only localhost can reach the server, don't worry too much + about security. + - Make sure pasting stuff doesn't mess up the whole document. + - Regularly ask the server for news. Let services hook into this. + +--- Atlantis 1D1.0 ---------------------------------------------------------- + + - Save undo history. + - Allow for logging in from other computers. + - Have a better name than Atlantis. + - Solve search (by leap?). + +- ATLANTIS 2D --------------------------------------------------------------- + + Atlantis 2D will have a zooming user interface. + +- ATLANTIS 3D --------------------------------------------------------------- + + Research whether 3D would work well for user interfaces. diff --git a/atlantis-server-glue.h b/atlantis-server-glue.h new file mode 100644 index 0000000..116edb6 --- /dev/null +++ b/atlantis-server-glue.h @@ -0,0 +1,124 @@ +/* Generated by dbus-binding-tool; do not edit! */ + + +#ifndef __dbus_glib_marshal_atlantis_server_MARSHAL_H__ +#define __dbus_glib_marshal_atlantis_server_MARSHAL_H__ + +#include + +G_BEGIN_DECLS + +#ifdef G_ENABLE_DEBUG +#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) +#define g_marshal_value_peek_char(v) g_value_get_char (v) +#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) +#define g_marshal_value_peek_int(v) g_value_get_int (v) +#define g_marshal_value_peek_uint(v) g_value_get_uint (v) +#define g_marshal_value_peek_long(v) g_value_get_long (v) +#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) +#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) +#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) +#define g_marshal_value_peek_enum(v) g_value_get_enum (v) +#define g_marshal_value_peek_flags(v) g_value_get_flags (v) +#define g_marshal_value_peek_float(v) g_value_get_float (v) +#define g_marshal_value_peek_double(v) g_value_get_double (v) +#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) +#define g_marshal_value_peek_param(v) g_value_get_param (v) +#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) +#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) +#define g_marshal_value_peek_object(v) g_value_get_object (v) +#else /* !G_ENABLE_DEBUG */ +/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. + * Do not access GValues directly in your code. Instead, use the + * g_value_get_*() functions + */ +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int +#define g_marshal_value_peek_char(v) (v)->data[0].v_int +#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint +#define g_marshal_value_peek_long(v) (v)->data[0].v_long +#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 +#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 +#define g_marshal_value_peek_enum(v) (v)->data[0].v_long +#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong +#define g_marshal_value_peek_float(v) (v)->data[0].v_float +#define g_marshal_value_peek_double(v) (v)->data[0].v_double +#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer +#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer +#endif /* !G_ENABLE_DEBUG */ + + +/* BOOLEAN:STRING,STRING,STRING,POINTER (/tmp/dbus-binding-tool-c-marshallers.3XSHFU:1) */ +extern void dbus_glib_marshal_atlantis_server_BOOLEAN__STRING_STRING_STRING_POINTER (GClosure *closure, + GValue *return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data); +void +dbus_glib_marshal_atlantis_server_BOOLEAN__STRING_STRING_STRING_POINTER (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef gboolean (*GMarshalFunc_BOOLEAN__STRING_STRING_STRING_POINTER) (gpointer data1, + gpointer arg_1, + gpointer arg_2, + gpointer arg_3, + gpointer arg_4, + gpointer data2); + register GMarshalFunc_BOOLEAN__STRING_STRING_STRING_POINTER callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + gboolean v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 5); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_BOOLEAN__STRING_STRING_STRING_POINTER) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, + g_marshal_value_peek_string (param_values + 1), + g_marshal_value_peek_string (param_values + 2), + g_marshal_value_peek_string (param_values + 3), + g_marshal_value_peek_pointer (param_values + 4), + data2); + + g_value_set_boolean (return_value, v_return); +} + +G_END_DECLS + +#endif /* __dbus_glib_marshal_atlantis_server_MARSHAL_H__ */ + +#include +static const DBusGMethodInfo dbus_glib_atlantis_server_methods[] = { + { (GCallback) atlantis_server_register_service, dbus_glib_marshal_atlantis_server_BOOLEAN__STRING_STRING_STRING_POINTER, 0 }, +}; + +const DBusGObjectInfo dbus_glib_atlantis_server_object_info = { + 0, + dbus_glib_atlantis_server_methods, + 1, +"nl.momple.Atlantis.Server\0RegisterService\0S\0bus_name\0I\0s\0object_path\0I\0s\0service_id\0I\0s\0\0\0", +"\0", +"\0" +}; + diff --git a/atlantis-server.c b/atlantis-server.c new file mode 100644 index 0000000..cabfa2f --- /dev/null +++ b/atlantis-server.c @@ -0,0 +1,349 @@ +/* + * atlantis-server.c - The Atlantis server + * (c) 2008 Sander Dijkhuis + * Still need to decide about licensing. Ask if interested. + */ + +#include +#include +#include +#include +#include +#include +#include "atlantis-server.h" +#include "atlantis-server-glue.h" + +#define ATLANTIS_SERVER_PORT 8000 + +struct _AtlantisServerPrivate +{ + DBusGConnection *dbus_connection; + GData **services; +}; + +typedef struct _AtlantisService AtlantisService; + +struct _AtlantisService +{ + gchar *bus_name; + gchar *object_path; +}; + +/* FIXME: Why should we do this? */ +extern void +close (int); + +extern DBusConnection * +dbus_g_connection_get_connection (DBusGConnection *gconnection); +/* end of FIXME */ + +void +web_respond (int cfd, gchar *resp) +{ + send (cfd, resp, strlen (resp), 0); +} + +gchar * +get_response_from_service (AtlantisServer *server, + gchar *url) +{ + GRegex *regex = g_regex_new ("^/-/(?P[^/]+)(?P/.*)$", + 0, 0, NULL); + GMatchInfo *match_info; + gchar *service_id; + GError *error; + DBusGProxy *proxy; + AtlantisService *service; + gchar *content; + gchar *path; + + if (!g_regex_match (regex, url, 0, &match_info)) + { + g_regex_unref (regex); + g_match_info_free (match_info); + return NULL; + } + + service_id = g_match_info_fetch_named (match_info, "service"); + path = g_match_info_fetch_named (match_info, "path"); + + g_regex_unref (regex); + g_match_info_free (match_info); + + service = g_datalist_get_data (server->private->services, service_id); + if (service == NULL) + return NULL; + + proxy = dbus_g_proxy_new_for_name (server->private->dbus_connection, + service->bus_name, + service->object_path, + "nl.momple.Atlantis.Service"); + + error = NULL; + if (!dbus_g_proxy_call (proxy, "GetContent", &error, + G_TYPE_STRING, service_id, + G_TYPE_STRING, path, + G_TYPE_INVALID, + G_TYPE_STRING, &content, + G_TYPE_INVALID)) + { + g_message ("D-Bus error: %s", error->message); + return NULL; + } + + g_free (service_id); + + return content; +} + +void +add_service_init (GQuark key_id, gpointer data, gpointer user_data) +{ + int *cfd = (int *)user_data; + gchar *service_id = (gchar *)(g_quark_to_string (key_id)); + web_respond (*cfd, g_strconcat ("\n", NULL)); +} + +void +add_service_script (GQuark key_id, gpointer data, gpointer user_data) +{ + struct DataStruct + { + int cfd; + AtlantisServer *server; + }; + struct DataStruct *ds = (struct DataStruct *)user_data; + gchar *service_id = (gchar *)(g_quark_to_string (key_id)); + web_respond (ds->cfd, g_strconcat (" loadService('", service_id, "');\n", + NULL)); +} + +void +send_web_response (AtlantisServer *self, char *url, int cfd) +{ + GIOChannel *file; + gchar *content; + gsize content_length; + GError **error = NULL; + + if (!strcmp (url, "/")) + { + web_respond (cfd, + "HTTP/1.1 200 OK\n" + "Content-Type: text/html; charset=UTF-8\n" + "\n" + "\n" + "Atlantis\n" + "\n" + "\n" + "\n" + "
\n" + "


\n" + "
    \n" + "
    \n" + "

    \n" + "
    \n" + "

    Test

    \n" + "

    Hello, world!\n" + "


    \n" + "

    Bye, world!\n" + "

    " + ); + } + else if (!strcmp (url, "/interface.js")) + { + web_respond (cfd, + "HTTP/1.1 200 OK\n" + "Content-Type: text/javascript\n\n"); + + file = g_io_channel_new_file ("interface.js", "r", error); + g_io_channel_read_to_end (file, &content, &content_length, error); + + web_respond (cfd, content); + + g_free (content); + g_io_channel_shutdown (file, FALSE, error); + } + else if (!strcmp (url, "/interface.css")) + { + web_respond (cfd, + "HTTP/1.1 200 OK\n" + "Content-Type: text/css\n\n"); + + file = g_io_channel_new_file ("interface.css", "r", error); + g_io_channel_read_to_end (file, &content, &content_length, error); + + web_respond (cfd, content); + + g_free (content); + g_io_channel_shutdown (file, FALSE, error); + } + else if (!strcmp (url, "/services.js")) + { + struct + { + int cfd; + AtlantisServer *server; + } data; + data.cfd = cfd; + data.server = self; + + web_respond (cfd, + "HTTP/1.1 200 OK\n" + "Content-Type: text/javascript\n\n"); + + web_respond (cfd, "with (Atlantis) {\n" + " addInitFunction(function() {\n"); + g_datalist_foreach (self->private->services, add_service_script, &data); + web_respond (cfd, " });\n}"); + } + else if ((content = get_response_from_service (self, url)) != NULL) + { + web_respond (cfd, content); + } + else + { + web_respond (cfd, "HTTP/1.1 404 Not Found\n\n404, not found."); + } +} + +gboolean +incoming_cb (GIOChannel *source, GIOCondition condition, gpointer data) +{ + int cfd; + struct sockaddr_in sockaddr; + unsigned int socklen = sizeof (sockaddr); + char buf[256]; + char url[256]; + int i, j; + int fd = g_io_channel_unix_get_fd (source); + if (condition != G_IO_IN) + return TRUE; + + if ((cfd = accept (fd, (struct sockaddr *)&sockaddr, &socklen)) < 0) + return TRUE; + + recv (cfd, buf, sizeof (buf), 0); + + /* Put the requested URL in url. */ + for (i = 0; i < 256 && buf[i] != ' '; i++); + for (i++, j = 0; i < 256 && buf[i] != ' '; i++, j++) + url[j] = buf[i]; + url[j] = '\0'; + + send_web_response ((AtlantisServer *)data, url, cfd); + + close (cfd); + + return TRUE; +} + +void +atlantis_server_class_init (gpointer g_class, + gpointer g_class_data) +{ + dbus_g_object_type_install_info (ATLANTIS_TYPE_SERVER, + &dbus_glib_atlantis_server_object_info); +} + +void +atlantis_server_instance_init (GTypeInstance *instance, + gpointer g_class) +{ + AtlantisServer *self = (AtlantisServer *)instance; + GError *error = NULL; + DBusError *dbus_error = NULL; + struct sockaddr_in sockaddr; + int fd; + + self->private = g_new0 (AtlantisServerPrivate, 1); + + self->private->services = g_malloc (sizeof (GData *)); + *(self->private->services) = NULL; + g_datalist_init (self->private->services); + + if ((self->private->dbus_connection = + dbus_g_bus_get (DBUS_BUS_SESSION, &error)) == NULL) + { + g_error ("Failed to open D-Bus connection: %s\n", error->message); + } + + dbus_bus_request_name (dbus_g_connection_get_connection + (self->private->dbus_connection), + "nl.momple.Atlantis.Server", 0, dbus_error); + + dbus_g_connection_register_g_object (self->private->dbus_connection, + "/nl/momple/Atlantis/Server", + G_OBJECT (self)); + + if ((fd = socket (PF_INET, SOCK_STREAM, 0)) < 0) + { + g_error ("Couldn't open the server socket."); + } + + sockaddr.sin_family = AF_INET; + sockaddr.sin_addr.s_addr = INADDR_ANY; + sockaddr.sin_port = htons (ATLANTIS_SERVER_PORT); + + if ((bind (fd, (struct sockaddr *)&sockaddr, sizeof (sockaddr))) < 0) + { + g_error ("Couldn't bind to the server socket."); + } + + listen (fd, 1); + g_io_add_watch (g_io_channel_unix_new (fd), G_IO_IN, incoming_cb, self); + + g_message ("Started the server at http://localhost:%d/", + ATLANTIS_SERVER_PORT); +} + +AtlantisServer * +atlantis_server_new (void) +{ + return g_object_new (ATLANTIS_TYPE_SERVER, NULL); +} + +gboolean +atlantis_server_register_service (AtlantisServer *self, + gchar *bus_name, + gchar *object_path, + gchar *service_id, + GError **error) +{ + AtlantisService *service = g_malloc (sizeof (AtlantisService)); + + service->bus_name = g_strdup (bus_name); + service->object_path = g_strdup (object_path); + g_datalist_set_data (self->private->services, service_id, service); + + g_message ("Service %s registered", service_id); + + return TRUE; +} + +GType +atlantis_server_get_type (void) +{ + static GType type = 0; + if (!type) + { + static const GTypeInfo info = + { + sizeof (AtlantisServerClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + &atlantis_server_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (AtlantisServer), + 0, /* n_preallocs */ + &atlantis_server_instance_init + }; + + type = g_type_register_static (G_TYPE_OBJECT, + "AtlantisServerType", + &info, 0); + } + return type; +} diff --git a/atlantis-server.h b/atlantis-server.h new file mode 100644 index 0000000..5b130ca --- /dev/null +++ b/atlantis-server.h @@ -0,0 +1,59 @@ +/* + * atlantis-server.h - The Atlantis server + * (c) 2008 Sander Dijkhuis + * Still need to decide about licensing. Ask if interested. + */ + +#ifndef ATLANTIS_SERVER_H +#define ATLANTIS_SERVER_H + +G_BEGIN_DECLS + +#include + +#define ATLANTIS_TYPE_SERVER (atlantis_server_get_type ()) +#define ATLANTIS_SERVER(obj) (G_TYPE_CHECK_CLASS_CAST \ + ((obj), ATLANTIS_TYPE_SERVER, AtlantisServer)) +#define ATLANTIS_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST \ + ((klass), ATLANTIS_TYPE_SERVER, \ + AtlantisServerClass)) +#define ATLANTIS_IS_SERVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), ATLANTIS_TYPE_SERVER)) +#define ATLANTIS_SERVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), ATLANTIS_TYPE_SERVER, \ + AtlantisServerClass)) + +typedef struct _AtlantisServer AtlantisServer; +typedef struct _AtlantisServerClass AtlantisServerClass; +typedef struct _AtlantisServerPrivate AtlantisServerPrivate; + +struct _AtlantisServer +{ + GObject parent; + + /*< private >*/ + AtlantisServerPrivate *private; +}; + +struct _AtlantisServerClass +{ + GObjectClass parent; + /* class members go here */ +}; + +GType +atlantis_server_get_type (void); + +AtlantisServer * +atlantis_server_new (void); + +gboolean +atlantis_server_register_service (AtlantisServer *server, + gchar *bus_name, + gchar *object_path, + gchar *service_id, + GError **error); + +G_END_DECLS + +#endif diff --git a/atlantis-server.xml b/atlantis-server.xml new file mode 100644 index 0000000..c3d582e --- /dev/null +++ b/atlantis-server.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/create-dbus-glue.sh b/create-dbus-glue.sh new file mode 100644 index 0000000..67bc861 --- /dev/null +++ b/create-dbus-glue.sh @@ -0,0 +1 @@ +dbus-binding-tool atlantis-server.xml --mode=glib-server --prefix=atlantis_server > atlantis-server-glue.h diff --git a/default-services/markup.js b/default-services/markup.js new file mode 100644 index 0000000..f73732d --- /dev/null +++ b/default-services/markup.js @@ -0,0 +1,31 @@ +var i; +var cmd; +var cmds = [ + ['bold', 'Toggles the bold font.', function() { + document.execCommand('bold', undefined, undefined); + }, null], + ['italic', 'Toggles the italic font.', function() { + document.execCommand('italic', undefined, undefined); + }, null], + ['unordered list', '', function() { + document.execCommand('insertUnorderedList', undefined, undefined); + }, null], + ['ordered list', '', function() { + document.execCommand('insertOrderedList', undefined, undefined); + }, null], + ['paragraph', '', function() { + document.execCommand('insertParagraph', undefined, undefined); + }, null], + ['heading', '', function() { + document.execCommand('formatBlock', undefined, 'h1'); + }, null], +]; + +for (i in cmds) { + cmd = new Atlantis.Command(); + cmd.name = cmds[i][0]; + cmd.doc = cmds[i][1]; + cmd.run = cmds[i][2]; + cmd.data = cmds[i][3]; + Atlantis.addCommand(cmd); +} diff --git a/directory-provider.py b/directory-provider.py new file mode 100644 index 0000000..4847b6b --- /dev/null +++ b/directory-provider.py @@ -0,0 +1,58 @@ +# directory-provider.py - Registers and runs single-JavaScript-file services +# (c) 2008 Sander Dijkhuis + +from __future__ import with_statement +import dbus +import dbus.mainloop.glib +import dbus.service +import gobject +import os +import sys + +USAGE = """Usage: python directory-provider.py /PATH/TO/SERVICE_DIRECTORY +Provides a service X for each file X.js in the specified directory.""" + +class DirectoryProvider(dbus.service.Object): + def __init__(self, path): + self.path = path + + object_path = '/nl/momple/Atlantis/DirectoryProvider' + dbus.service.Object.__init__(self, dbus.SessionBus(), object_path) + + bus_name = 'nl.momple.Atlantis.DirectoryProvider' + self.connection.request_name(bus_name) + + server = self.connection.get_object('nl.momple.Atlantis.Server', + '/nl/momple/Atlantis/Server') + + for file in os.listdir(path): + if file[-3:] == '.js': + server.RegisterService(bus_name, object_path, file[:-3]) + + @dbus.service.method(dbus_interface='nl.momple.Atlantis.Service', + in_signature='ss', out_signature='s') + def GetContent(self, service, url_path): + if url_path == '/main.js' \ + and os.path.exists('%s/%s.js' % (self.path, service)): + print '200: /-/%s/main.js' % service + with open('%s/%s.js' % (self.path, service)) as f: + return 'HTTP/1.1 200 OK\nContent-Type: text/javascript\n\n' \ + + ''.join(f.readlines()) + else: + print '404: %s' % url_path + return 'HTTP/1.1 404 Not Found\n\nNot found' + +if __name__ == '__main__': + if len(sys.argv) != 2: + sys.exit(USAGE) + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + ts = DirectoryProvider(sys.argv[1]) + + loop = gobject.MainLoop() + try: + loop.run() + except KeyboardInterrupt: + print 'Goodbye' + loop.quit() diff --git a/interface.css b/interface.css new file mode 100644 index 0000000..e178dfc --- /dev/null +++ b/interface.css @@ -0,0 +1,81 @@ +body { + margin: 0; + font: 16px 'DejaVu Sans', sans-serif; +} +a { + color: #05561a; + text-decoration: underline; +} +#content { + padding: 32px; + outline: none; + background: white; + color: black; +} + +#command-block { + position: fixed; + top: 0; + left: 0; + margin: 0; +} +#command-string { + margin: 0; + background: rgba(0, 0, 0, 0.7); + color: #fff; + padding: 8px; + font-size: 24px; + -moz-border-radius: 0 0 8px 0; + display: inline-block; +} +#command-alternatives { + margin: 0; + padding: 8px; + background: rgba(0, 0, 0, 0.7); + color: #1c1; + padding: 8px; + font-size: 18px; + -moz-border-radius: 0 8px 8px 0; + display: inline-block; + clear: both; +} +#command-alternatives b { + color: #fff; + font-weight: normal; +} + +.document-separator { + background: #afdaf9; + padding: 8px; + font-size: 12px; +} +.document-separator p { + margin: 0; +} + +.doc-char { + background: #afdaf9; + height: 3px; + border: none; + margin: 2px 0; +} + +#message { + position: fixed; + top: 32px; + text-align: center; + left: 0; + right: 0; +} +#message p { + margin: 0; + display: inline-block; + background: rgba(0, 0, 0, 0.7); + color: white; + font-size: 24px; + padding: 8px 16px; + -moz-border-radius: 8px; + -moz-user-focus: ignore; + -moz-user-select: -moz-none; + cursor: default; +} \ No newline at end of file diff --git a/interface.js b/interface.js new file mode 100644 index 0000000..02aa710 --- /dev/null +++ b/interface.js @@ -0,0 +1,294 @@ +/* + * interface.js - The Atlantis user interface + * (c) 2008 Sander Dijkhuis + * Still need to decide about licensing. Ask if interested. + */ + +var Atlantis = { + DEBUG: true, + + CTRL_KEY_CODE: 17, + BACKSP_KEY_CODE: 8, + DOC_KEY_CODE: 19, + contentElement: null, + commanding: false, + commandElts: { // very bad probably + block: null, + string: null, + alternatives: null + }, + commandString: '', + commandMatches: null, + message: '', + messageElt: null, + messageShown: false, + commands: new Array(), + initFunctions: new Array(), + initialized: false, + + init: function() { + this.contentElement = document.getElementById('content'); + + this.commandElts.block = document.getElementById('command-block'); + this.commandElts.string = document.getElementById('command-string'); + this.commandElts.alternatives = + document.getElementById('command-alternatives'); + + this.messageElt = document.getElementById('message'); + + this.addDefaultCommands(); + + this.toggleCommandMode(false); + this.hideMessage(); + + this.contentElement.focus(); + + for (i in this.initFunctions) + this.initFunctions[i](); + + this.initialized = true; + }, + + addInitFunction: function(f) { + if (this.initalized) + f(); + else + this.initFunctions.push(f); + }, + + toggleCommandMode: function(on) { + this.commanding = on; + this.commandElts.block.style.visibility = on? 'visible' : 'hidden'; + if (!on) { + this.setCommandAlternatives(''); + this.setCommandString(''); + } + }, + + addCommandChar: function(c) { + this.setCommandString(this.commandString + c); + this.updateCommandMatches(); + }, + + updateCommandMatches: function() { + this.commandMatches = new Array(); + var s = ''; + var match; + for (i in this.commands) { + match = this.matchCommand(this.commands[i].name, + this.commandString); + if (match != null) { + s += '
  • ' + match; + this.commandMatches.push(this.commands[i].name); + } + } + + this.setCommandAlternatives(s); + }, + + matchCommand: function(command, input) { + var i = 0; + var html = ''; + var j; + var c; + var foundIt = false; + for (j in command) { + c = command[j]; + if (input[i] == c && !foundIt) { + i++; + if (i == input.length) + foundIt = true; + html += '' + c + ''; + } else { + html += c; + } + } + if (foundIt) { + return html; + } + else + return null; + }, + + deleteCommandChar: function() { + this.setCommandString(this.commandString.substring(0, + this.commandString. + length - 1)); + this.updateCommandMatches(); + }, + + setCommandString: function(s) { + this.commandString = this.commandElts.string.innerHTML = s; + }, + + setCommandAlternatives: function(s) { + this.commandElts.alternatives.style.visibility = + s? 'visible' : 'hidden'; + this.commandElts.alternatives.innerHTML = s; + }, + + execute: function(command) { + var available; + for (i in this.commands) + if (command == this.commands[i].name) + return this.commands[i].run(); + }, + + showUnknownCommand: function(command) { + this.showMessage('Unknown command: ' + command + ''); + }, + + showMessage: function(s) { + this.messageShown = true; + this.messageElt.style.visibility = 'visible'; + this.messageElt.childNodes[0].innerHTML = s; + }, + + hideMessage: function() { + this.messageShown = false; + this.messageElt.style.visibility = 'hidden'; + }, + + insertHTML: function(s) { + document.execCommand('insertHTML', undefined, s); + }, + + addCommand: function(command) { + this.commands.push(command); + this.commands.sort(); + }, + + getXMLHttpRequestObject: function() { + var obj = null; + + if (window.XMLHttpRequest) + obj = new XMLHttpRequest(); + else if (window.ActiveXObject) + obj = new ActiveXObject('Microsoft.XMLHTTP'); + + return obj; + }, + + loadService: function(serviceId) { + this.loadScript('/-/' + serviceId + '/main.js'); + }, + + loadScript: function(url) { + var xhr = this.getXMLHttpRequestObject(); + xhr.onreadystatechange = function() { + if (xhr.readyState != 4) + return; + if (xhr.status == 200) + (function() { eval(xhr.responseText); })(); + }; + xhr.open('GET', url, true); + xhr.send(null); + } +}; + +/* temporary function for adding temporary commands quickly and uglily */ +Atlantis.addDefaultCommands = function() { + var i; + var cmd; + var cmds = + [ + ['undo', '', function() { + document.execCommand('undo', undefined, undefined); + }, null], + ['redo', '', function() { + document.execCommand('redo', undefined, undefined); + }, null], + ['new document', '', function() { + // doesn't work if in
      + Atlantis.insertHTML('
      '); + }, null], + ['commands', '', function() { + var i; + var command; + s = '

      Commands

        '; + for (i in Atlantis.commands) { + command = Atlantis.commands[i]; + s += '
      • ' + command.name.replace(/_/g, ' ') + ''; + if (command.doc) + s += ' - ' + command.doc; + } + s += '
      '; + Atlantis.insertHTML(s); + }, null], + ['hello world', '', function() { + Atlantis.insertHTML('Hello, World!'); + }, null] + ]; + + for (i in cmds) { + cmd = new Atlantis.Command(); + cmd.name = cmds[i][0]; + cmd.doc = cmds[i][1]; + cmd.run = cmds[i][2]; + cmd.data = cmds[i][3]; + Atlantis.addCommand(cmd); + } + + cmd = new Atlantis.Command(); + cmd.name = 'data test'; + cmd.doc = 'Testing data.'; + cmd.data = { i: 0 }; + cmd.run = function() { + Atlantis.insertHTML(cmd.data.i++); + }; + Atlantis.addCommand(cmd); +}; + +Atlantis.Command = function() { +}; + +Atlantis.Command.prototype = { + name: '', + doc: '', + run: null, + data: null, + toString: function() { + return this.name; + }, +}; + +function onkeydown(evt) { + if (Atlantis.messageShown) + Atlantis.hideMessage(); + + if (evt.keyCode == Atlantis.CTRL_KEY_CODE) { + Atlantis.toggleCommandMode(true); + return false; + } +} + +function onkeyup(evt) { + if (evt.keyCode == Atlantis.CTRL_KEY_CODE) { + if (Atlantis.commandString) { + if (Atlantis.commandMatches.length > 0) + Atlantis.execute(Atlantis.commandMatches[0]); + else + Atlantis.showUnknownCommand(Atlantis.commandString); + } + Atlantis.toggleCommandMode(false); + } +} + +function onkeypress(evt) { + if (Atlantis.commanding) { + if (evt.which == Atlantis.BACKSP_KEY_CODE) + Atlantis.deleteCommandChar(); + else + Atlantis.addCommandChar(String.fromCharCode(evt.charCode)); + return false; + } else if (evt.keyCode == Atlantis.DOC_KEY_CODE) + Atlantis.execute('new document'); +} + +function oncontextmenu(evt) { + return false; +} + +function onload(evt) { + Atlantis.init(); +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..c8523f1 --- /dev/null +++ b/main.c @@ -0,0 +1,22 @@ +/* + * main.c - Runs the Atlantis server + * (c) 2008 Sander Dijkhuis + * Still need to decide about licensing. Ask if interested. + */ + +#include +#include +#include "atlantis-server.h" + +int +main (int argc, char *argv[]) +{ + AtlantisServer *server; + GMainLoop *loop = g_main_loop_new (NULL, TRUE); + + g_type_init (); + + server = atlantis_server_new (); + + g_main_loop_run (loop); +} -- 2.11.4.GIT