diff --git a/src/server/portal/portal.py b/src/server/portal/portal.py
index c40bf5dedc..366da88009 100644
--- a/src/server/portal/portal.py
+++ b/src/server/portal/portal.py
@@ -262,6 +262,12 @@ if WEBSERVER_ENABLED:
web_root.putChild("webclientdata", webclient)
webclientstr = "/client"
+ #from src.server.portal import websocket
+ #factory = protocol.ServerFactory()
+ #websocketclient = websocket.WebSocketProtocol()
+ #websocketclient.sessionhandler = PORTAL_SESSIONS
+ #web_root.putChild("websocket", websocketclient)
+
web_root = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
proxy_service = internet.TCPServer(proxyport,
web_root,
@@ -270,7 +276,6 @@ if WEBSERVER_ENABLED:
PORTAL.services.addService(proxy_service)
print " webproxy%s%s:%s (<-> %s)" % (webclientstr, ifacestr, proxyport, serverport)
-
if WEBSOCKET_ENABLED:
# websocket support is experimental!
diff --git a/src/server/portal/websocket.py b/src/server/portal/websocket.py
index 0c3b3f5304..0040921993 100644
--- a/src/server/portal/websocket.py
+++ b/src/server/portal/websocket.py
@@ -37,6 +37,7 @@ class WebSocketProtocol(Protocol, Session):
"""
This is called when the connection is first established
"""
+
def connectionMade(self):
"""
This is called when the connection is first established.
diff --git a/src/settings_default.py b/src/settings_default.py
index 68ed71c333..f03b3544a0 100644
--- a/src/settings_default.py
+++ b/src/settings_default.py
@@ -61,6 +61,14 @@ WEBSERVER_THREADPOOL_LIMITS = (1, 20)
# Start the evennia ajax client on /webclient
# (the webserver must also be running)
WEBCLIENT_ENABLED = True
+# Activate Websocket support. If this is on, the default webclient will use this
+# before going for the ajax implementation
+WEBSOCKET_ENABLED = True
+# Ports to use for Websockets. If this is changed, you must also update
+# src/web/media/javascript/evennia_websocket_webclient.js to match.
+WEBSOCKET_PORTS = [8001]
+# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
+WEBSOCKET_INTERFACES = ['0.0.0.0']
# Activate SSH protocol (SecureShell)
SSH_ENABLED = False
# Ports to use for SSH
@@ -73,12 +81,6 @@ SSL_ENABLED = False
SSL_PORTS = [4001]
# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
SSL_INTERFACES = ['0.0.0.0']
-# Activate Websocket support
-WEBSOCKET_ENABLED = False
-# Ports to use for Websockets
-WEBSOCKET_PORTS = [8021]
-# Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6.
-WEBSOCKET_INTERFACES = ['0.0.0.0']
# The path that contains this settings.py file (no trailing slash).
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Path to the src directory containing the bulk of the codebase's code.
diff --git a/src/web/media/javascript/evennia_webclient.js b/src/web/media/javascript/evennia_ajax_webclient.js
similarity index 100%
rename from src/web/media/javascript/evennia_webclient.js
rename to src/web/media/javascript/evennia_ajax_webclient.js
diff --git a/src/web/media/javascript/evennia_websocket_webclient.js b/src/web/media/javascript/evennia_websocket_webclient.js
new file mode 100644
index 0000000000..b58c605923
--- /dev/null
+++ b/src/web/media/javascript/evennia_websocket_webclient.js
@@ -0,0 +1,266 @@
+/*
+
+Evennia websocket webclient (javascript component)
+
+The client is composed of several parts:
+ templates/webclient.html - the main page
+ webclient/views.py - the django view serving the template (based on urls.py pattern)
+ src/server/portal/websockets.py - the server component talking to the client
+ this file - the javascript component handling dynamic content
+
+This implements an mud client for use with Evennia, using jQuery
+for simplicity.
+
+messages sent to the client is one of three modes:
+ OOB(func,(args), func,(args), ...) - OOB command executions
+ text - any other text is considered a normal text to echo
+
+*/
+
+// jQuery must be imported by the calling html page before this script
+// There are plenty of help on using the jQuery library on http://jquery.com/
+
+// Server communications
+// Set this to the value matching settings.WEBSOCKET_PORTS
+var wsurl = "ws://localhost:8001";
+
+function webclient_init(){
+ websocket = new WebSocket(wsurl);
+ websocket.onopen = function(evt) { onOpen(evt) };
+ websocket.onclose = function(evt) { onClose(evt) };
+ websocket.onmessage = function(evt) { onMessage(evt) };
+ websocket.onerror = function(evt) { onError(evt) };
+}
+
+function onOpen(evt) {
+ $("#connecting").remove(); // remove the "connecting ..." message
+ msg_display("sys", "Using websockets - connected to " + wsurl + ".")
+
+ setTimeout(function () {
+ $("#playercount").fadeOut('slow', webclient_set_sizes);
+ }, 10000);
+}
+
+function onClose(evt) {
+ CLIENT_HASH = 0;
+ alert("Mud client connection was closed cleanly.");
+}
+
+function onMessage(evt) {
+ var inmsg = evt.data
+ if (inmsg.length > 3 && inmsg.substr(0, 3) == "OOB") {
+ // dynamically call oob methods, if available
+ try {var oobtuples = JSON.parse(inmsg.slice(3));} // everything after OOB }
+ catch(err) {
+ // not JSON packed - a normal text
+ msg_display('out', inmsg);
+ return;
+ }
+
+ for (var oobtuple in oobtuples) {
+ try { window[oobtuple[0]](oobtuple[1]) }
+ catch(err) { msg_display("err", "Could not execute OOB function " + oobtuple[0] + "!") }
+ }
+ }
+ else {
+ // normal message
+ msg_display('out', inmsg); }
+}
+
+function onError(evt) {
+ msg_display('err', "Error: Server returned an error. Try reloading the page.");
+}
+
+function doSend(){
+ outmsg = $("#inputfield").val();
+ history_add(outmsg);
+ HISTORY_POS = 0;
+ $('#inputform')[0].reset(); // clear input field
+ websocket.send(outmsg);
+}
+
+function doOOB(ooblist){
+ // Takes an array on form [funcname, [args], funcname, [args], ... ]
+ var oobmsg = JSON.stringify(ooblist);
+ websocket.send("OOB" + oobmsg);
+}
+
+//
+// Display messages
+
+function msg_display(type, msg){
+ // Add a div to the message window.
+ // type gives the class of div to use.
+ $("#messagewindow").append(
+ "
"+ msg +"
");
+ // scroll message window to bottom
+ $('#messagewindow').animate({scrollTop: $('#messagewindow')[0].scrollHeight});
+}
+
+// Input history mechanism
+
+var HISTORY_MAX_LENGTH = 21
+var HISTORY = new Array();
+HISTORY[0] = '';
+var HISTORY_POS = 0;
+
+function history_step_back() {
+ // step backwards in history stack
+ HISTORY_POS = Math.min(++HISTORY_POS, HISTORY.length-1);
+ return HISTORY[HISTORY.length-1 - HISTORY_POS];
+}
+function history_step_fwd() {
+ // step forward in history stack
+ HISTORY_POS = Math.max(--HISTORY_POS, 0);
+ return HISTORY[HISTORY.length-1 - HISTORY_POS];
+}
+function history_add(input) {
+ // add an entry to history
+ if (input != HISTORY[HISTORY.length-1]) {
+ if (HISTORY.length >= HISTORY_MAX_LENGTH) {
+ HISTORY.shift(); // kill oldest history entry
+ }
+ HISTORY[HISTORY.length-1] = input;
+ HISTORY[HISTORY.length] = '';
+ }
+}
+
+// Catching keyboard shortcuts
+
+$.fn.appendCaret = function() {
+ /* jQuery extension that will forward the caret to the end of the input, and
+ won't harm other elements (although calling this on multiple inputs might
+ not have the expected consequences).
+
+ Thanks to
+ http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
+ for the good starting point. */
+ return this.each(function() {
+ var range,
+ // Index at where to place the caret.
+ end,
+ self = this;
+
+ if (self.setSelectionRange) {
+ // other browsers
+ end = self.value.length;
+ self.focus();
+ // NOTE: Need to delay the caret movement until after the callstack.
+ setTimeout(function() {
+ self.setSelectionRange(end, end);
+ }, 0);
+ }
+ else if (self.createTextRange) {
+ // IE
+ end = self.value.length - 1;
+ range = self.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', end);
+ range.moveStart('character', end);
+ // NOTE: I haven't tested to see if IE has the same problem as
+ // W3C browsers seem to have in this context (needing to fire
+ // select after callstack).
+ range.select();
+ }
+ });
+};
+$.fn.appendCaret = function() {
+ /* jQuery extension that will forward the caret to the end of the input, and
+ won't harm other elements (although calling this on multiple inputs might
+ not have the expected consequences).
+
+ Thanks to
+ http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
+ for the good starting point. */
+ return this.each(function() {
+ var range,
+ // Index at where to place the caret.
+ end,
+ self = this;
+
+ if (self.setSelectionRange) {
+ // other browsers
+ end = self.value.length;
+ self.focus();
+ // NOTE: Need to delay the caret movement until after the callstack.
+ setTimeout(function() {
+ self.setSelectionRange(end, end);
+ }, 0);
+ }
+ else if (self.createTextRange) {
+ // IE
+ end = self.value.length - 1;
+ range = self.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', end);
+ range.moveStart('character', end);
+ // NOTE: I haven't tested to see if IE has the same problem as
+ // W3C browsers seem to have in this context (needing to fire
+ // select after callstack).
+ range.select();
+ }
+ });
+};
+
+$(document).keydown( function(event) {
+ // Get the pressed key (normalized by jQuery)
+ var code = event.which,
+ inputField = $("#inputfield");
+
+ // always focus input field no matter which key is pressed
+ inputField.focus();
+
+ // Special keys recognized by client
+
+ //msg_display("out", "key code pressed: " + code); // debug
+
+ if (code == 13) { // Enter Key
+ doSend();
+ event.preventDefault();
+ }
+ else {
+ if (code == 38) { // arrow up 38
+ inputField.val(history_step_back()).appendCaret();
+ }
+ else if (code == 40) { // arrow down 40
+ inputField.val(history_step_fwd()).appendCaret();
+ }
+ }
+});
+
+// handler to avoid double-clicks until the ajax request finishes
+//$("#inputsend").one("click", webclient_input)
+
+function webclient_set_sizes() {
+ // Sets the size of the message window
+ var win_h = $(document).height();
+ //var win_w = $('#wrapper').width();
+ var inp_h = $('#inputform').outerHeight(true);
+ //var inp_w = $('#inputsend').outerWidth(true);
+
+ $("#messagewindow").css({'height': win_h - inp_h - 1});
+ //$("#inputfield").css({'width': win_w - inp_w - 20});
+}
+
+
+// Callback function - called when page has finished loading (gets things going)
+$(document).ready(function(){
+ // remove the "no javascript" warning, since we obviously have javascript
+ $('#noscript').remove();
+ // set sizes of elements and reposition them
+ webclient_set_sizes();
+ // a small timeout to stop 'loading' indicator in Chrome
+ setTimeout(function () {
+ webclient_init();
+ }, 500);
+ // set an idle timer to avoid proxy servers to time out on us (every 3 minutes)
+ setInterval(function() {
+ webclient_input("idle", true);
+ }, 60000*3);
+});
+
+// Callback function - called when the browser window resizes
+$(window).resize(webclient_set_sizes);
+
+// Callback function - called when page is closed or moved away from.
+//$(window).bind("beforeunload", webclient_close);
diff --git a/src/web/templates/admin/index.html b/src/web/templates/admin/index.html
index a7487cd49d..7a4f0a5295 100644
--- a/src/web/templates/admin/index.html
+++ b/src/web/templates/admin/index.html
@@ -14,12 +14,12 @@
{% if app_list %}
- {% for app in app_list %}
+ {% for app in app_list %}
{% if app.name in evennia_userapps %}
{% if app.name == 'Players' %}
-
Admin
+
Admin
Players are the out-of-character representation of a
game account. A Player can potentially control any number of
in-game character Objects (depending on game).
- {% for app in app_list %}
+ {% for app in app_list %}
{% if app.name in evennia_entityapps %}
- {% if app.name == 'Comms' %}
+ {% if app.name == 'Comms' %}
This defines entities that has an in-game precense or
- effect of some kind.
diff --git a/src/web/urls.py b/src/web/urls.py
index 5ca3bf8a41..9d9d1844c3 100755
--- a/src/web/urls.py
+++ b/src/web/urls.py
@@ -44,8 +44,8 @@ urlpatterns = patterns('',
# favicon
url(r'^favicon\.ico$', RedirectView.as_view(url='/media/images/favicon.ico')),
- # ajax stuff
- url(r'^webclient/',include('src.web.webclient.urls')),
+ # webclient stuff
+ url(r'^webclient/', include('src.web.webclient.urls')),
)
# This sets up the server if the user want to run the Django
diff --git a/src/web/utils/general_context.py b/src/web/utils/general_context.py
index 2b9304e94a..421bf9587a 100644
--- a/src/web/utils/general_context.py
+++ b/src/web/utils/general_context.py
@@ -1,9 +1,9 @@
#
-# This file defines global variables that will always be
+# This file defines global variables that will always be
# available in a view context without having to repeatedly
-# include it. For this to work, this file is included in
-# the settings file, in the TEMPLATE_CONTEXT_PROCESSORS
-# tuple.
+# include it. For this to work, this file is included in
+# the settings file, in the TEMPLATE_CONTEXT_PROCESSORS
+# tuple.
#
from django.db import models
@@ -19,8 +19,8 @@ except AttributeError:
SERVER_VERSION = get_evennia_version()
-# Setup lists of the most relevant apps so
-# the adminsite becomes more readable.
+# Setup lists of the most relevant apps so
+# the adminsite becomes more readable.
PLAYER_RELATED = ['Players']
GAME_ENTITIES = ['Objects', 'Scripts', 'Comms', 'Help']
@@ -44,5 +44,6 @@ def general_context(request):
'evennia_setupapps': GAME_SETUP,
'evennia_connectapps': CONNECTIONS,
'evennia_websiteapps':WEBSITE,
- "webclient_enabled" : settings.WEBCLIENT_ENABLED
+ "webclient_enabled" : settings.WEBCLIENT_ENABLED,
+ "websocket_enabled" : settings.WEBSOCKET_ENABLED
}
diff --git a/src/web/webclient/views.py b/src/web/webclient/views.py
index 3d097b6935..d74ff4f5e2 100644
--- a/src/web/webclient/views.py
+++ b/src/web/webclient/views.py
@@ -8,7 +8,7 @@ page and serve it eventual static content.
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext
from django.conf import settings
-from src.server.sessionhandler import SESSIONS
+from src.players.models import PlayerDB
def webclient(request):
"""
@@ -21,8 +21,9 @@ def webclient(request):
print "Called from port 8000!"
#return redirect("http://localhost:8001/webclient/", permanent=True)
+ nsess = len(PlayerDB.objects.get_connected_players()) or "none"
# as an example we send the number of connected players to the template
- pagevars = {'num_players_connected': SESSIONS.player_count()}
+ pagevars = {'num_players_connected': nsess}
context_instance = RequestContext(request)
return render_to_response('webclient.html', pagevars, context_instance)
diff --git a/src/web/website/views.py b/src/web/website/views.py
index a0af399ddc..2ab73c3d19 100644
--- a/src/web/website/views.py
+++ b/src/web/website/views.py
@@ -33,7 +33,7 @@ def page_index(request):
nplyrs_conn_recent = len(recent_users) or "none"
nplyrs = PlayerDB.objects.num_total_players() or "none"
nplyrs_reg_recent = len(PlayerDB.objects.get_recently_created_players()) or "none"
- nsess = len(PlayerDB.objects.get_connected_players()) or "noone"
+ nsess = len(PlayerDB.objects.get_connected_players()) or "none"
nobjs = ObjectDB.objects.all().count()
nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=_BASE_CHAR_TYPECLASS).count()