shellinabox/service.h \
shellinabox/session.c \
shellinabox/session.h \
+ shellinabox/usercss.c \
+ shellinabox/usercss.h \
shellinabox/cgi_root.html \
shellinabox/root_page.html \
shellinabox/vt100.jspp \
am__dirstamp = $(am__leading_dot)dirstamp
am_shellinaboxd_OBJECTS = shellinaboxd.$(OBJEXT) \
externalfile.$(OBJEXT) launcher.$(OBJEXT) privileges.$(OBJEXT) \
- service.$(OBJEXT) session.$(OBJEXT) \
+ service.$(OBJEXT) session.$(OBJEXT) usercss.$(OBJEXT) \
shellinabox/cgi_root.$(OBJEXT) shellinabox/root_page.$(OBJEXT) \
shellinabox/vt100.$(OBJEXT) \
shellinabox/shell_in_a_box.$(OBJEXT) \
shellinabox/service.h \
shellinabox/session.c \
shellinabox/session.h \
+ shellinabox/usercss.c \
+ shellinabox/usercss.h \
shellinabox/cgi_root.html \
shellinabox/root_page.html \
shellinabox/vt100.jspp \
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ssl.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trie.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/url.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/usercss.Po@am__quote@
.c.o:
@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o session.obj `if test -f 'shellinabox/session.c'; then $(CYGPATH_W) 'shellinabox/session.c'; else $(CYGPATH_W) '$(srcdir)/shellinabox/session.c'; fi`
+usercss.o: shellinabox/usercss.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT usercss.o -MD -MP -MF $(DEPDIR)/usercss.Tpo -c -o usercss.o `test -f 'shellinabox/usercss.c' || echo '$(srcdir)/'`shellinabox/usercss.c
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/usercss.Tpo $(DEPDIR)/usercss.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='shellinabox/usercss.c' object='usercss.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o usercss.o `test -f 'shellinabox/usercss.c' || echo '$(srcdir)/'`shellinabox/usercss.c
+
+usercss.obj: shellinabox/usercss.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT usercss.obj -MD -MP -MF $(DEPDIR)/usercss.Tpo -c -o usercss.obj `if test -f 'shellinabox/usercss.c'; then $(CYGPATH_W) 'shellinabox/usercss.c'; else $(CYGPATH_W) '$(srcdir)/shellinabox/usercss.c'; fi`
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/usercss.Tpo $(DEPDIR)/usercss.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='shellinabox/usercss.c' object='usercss.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o usercss.obj `if test -f 'shellinabox/usercss.c'; then $(CYGPATH_W) 'shellinabox/usercss.c'; else $(CYGPATH_W) '$(srcdir)/shellinabox/usercss.c'; fi`
+
mostlyclean-libtool:
-rm -f *.lo
#define STDC_HEADERS 1
/* Most recent revision number in the version control system */
-#define VCS_REVISION "164"
+#define VCS_REVISION "165"
/* Version number of package */
#define VERSION "2.9"
ac_compiler_gnu=$ac_cv_c_compiler_gnu
-VCS_REVISION=164
+VCS_REVISION=165
cat >>confdefs.h <<_ACEOF
dnl This is the one location where the authoritative version number is stored
AC_INIT(shellinabox, 2.9, markus@shellinabox.com)
-VCS_REVISION=164
+VCS_REVISION=165
AC_SUBST(VCS_REVISION)
AC_DEFINE_UNQUOTED(VCS_REVISION, "${VCS_REVISION}",
[Most recent revision number in the version control system])
}
};
+VT100.prototype.initializeUserCSSStyles = function() {
+ this.usercssActions = [];
+ if (typeof userCSSList != 'undefined') {
+ var menu = '';
+ var group = '';
+ var wasSingleSel = 1;
+ var beginOfGroup = 0;
+ for (var i = 0; i <= userCSSList.length; ++i) {
+ if (i < userCSSList.length) {
+ var label = userCSSList[i][0];
+ var newGroup = userCSSList[i][1];
+ var enabled = userCSSList[i][2];
+
+ // Add user style sheet to document
+ var style = document.createElement('link');
+ var id = document.createAttribute('id');
+ id.nodeValue = 'usercss-' + i;
+ style.setAttributeNode(id);
+ var rel = document.createAttribute('rel');
+ rel.nodeValue = 'stylesheet';
+ style.setAttributeNode(rel);
+ var href = document.createAttribute('href');
+ href.nodeValue = 'usercss-' + i + '.css';
+ style.setAttributeNode(href);
+ var type = document.createAttribute('type');
+ type.nodeValue = 'text/css';
+ style.setAttributeNode(type);
+ document.getElementsByTagName('head')[0].appendChild(style);
+ style.disabled = !enabled;
+ }
+
+ // Add entry to menu
+ if (newGroup || i == userCSSList.length) {
+ if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
+ // The last group had multiple entries that are mutually exclusive;
+ // or the previous to last group did. In either case, we need to
+ // append a "<hr />" before we can add the last group to the menu.
+ menu += '<hr />';
+ }
+ wasSingleSel = i - beginOfGroup < 1;
+ menu += group;
+ group = '';
+
+ for (var j = beginOfGroup; j < i; ++j) {
+ this.usercssActions[this.usercssActions.length] =
+ function(vt100, current, begin, count) {
+
+ // Deselect all other entries in the group, then either select
+ // (for multiple entries in group) or toggle (for on/off entry)
+ // the current entry.
+ return function() {
+ var entry = vt100.getChildById(vt100.menu,
+ 'beginusercss');
+ var i = -1;
+ var j = -1;
+ for (var c = count; c > 0; ++j) {
+ if (entry.tagName == 'LI') {
+ if (++i >= begin) {
+ --c;
+ var label = vt100.usercss.childNodes[j];
+ label.innerHTML =
+ label.innerHTML.replace(/^\u2714 /, '');
+ var sheet = document.getElementById(
+ 'usercss-' + i);
+ if (i == current) {
+ if (count == 1) {
+ sheet.disabled = !sheet.disabled;
+ } else {
+ sheet.disabled = false;
+ }
+ if (!sheet.disabled) {
+ label.innerHTML= '✔ ' + label.innerHTML;
+ }
+ } else {
+ sheet.disabled = true;
+ }
+ }
+ }
+ entry = entry.nextSibling;
+ }
+ };
+ }(this, j, beginOfGroup, i - beginOfGroup);
+ }
+
+ if (i == userCSSList.length) {
+ break;
+ }
+
+ beginOfGroup = i;
+ }
+ // Collect all entries in a group, before attaching them to the menu.
+ // This is necessary as we don't know whether this is a group of
+ // mutually exclusive options (which should be separated by "<hr />" on
+ // both ends), or whether this is a on/off toggle, which can be grouped
+ // together with other on/off options.
+ group +=
+ '<li>' + (enabled ? '✔ ' : '') + label + '</li>';
+ }
+ this.usercss.innerHTML = menu;
+ }
+};
+
VT100.prototype.initializeElements = function(container) {
// If the necessary objects have not already been defined in the HTML
// page, create them now.
!this.getChildById(this.container, 'padding') ||
!this.getChildById(this.container, 'cursor') ||
!this.getChildById(this.container, 'lineheight') ||
+ !this.getChildById(this.container, 'usercss') ||
!this.getChildById(this.container, 'space') ||
!this.getChildById(this.container, 'input') ||
!this.getChildById(this.container, 'cliphelper') ||
'<pre id="cursor"> </pre>' +
'</div>' +
'<div class="hidden">' +
+ '<div id="usercss"></div>' +
'<pre><div><span id="space"></span></div></pre>' +
'<input type="textfield" id="input" />' +
'<input type="textfield" id="cliphelper" />' +
var ieProbe = this.getChildById(this.container, 'ieprobe');
this.padding = this.getChildById(this.container, 'padding');
this.cursor = this.getChildById(this.container, 'cursor');
+ this.usercss = this.getChildById(this.container, 'usercss');
this.space = this.getChildById(this.container, 'space');
this.input = this.getChildById(this.container, 'input');
this.cliphelper = this.getChildById(this.container,
'cliphelper');
this.attributeHelper = this.getChildById(this.container, 'attrib');
+ // Add any user selectable style sheets to the menu
+ this.initializeUserCSSStyles();
+
// Remember the dimensions of a standard character glyph. We would
// expect that we could just check cursor.clientWidth/Height at any time,
// but it turns out that browsers sometimes invalidate these values
};
VT100.prototype.about = function() {
- alert("VT100 Terminal Emulator " + "2.9 (revision 164)" +
+ alert("VT100 Terminal Emulator " + "2.9 (revision 165)" +
"\nCopyright 2008-2009 by Markus Gutschke\n" +
"For more information check http://shellinabox.com");
};
(this.utfEnabled ? '✔ ' : '') + 'Unicode</li>' +
'<li id="endconfig">' +
(this.visualBell ? '✔ ' : '') + 'Visual Bell</li>'+
- '<hr />' +
+ (this.usercss.firstChild ?
+ '<hr id="beginusercss" />' +
+ this.usercss.innerHTML +
+ '<hr id="endusercss" />' :
+ '<hr />') +
'<li id="about">About...</li>' +
'</ul>' +
'</td></tr>' +
menuentries.childNodes[1].className
= 'disabled';
}
+
+ // Actions for default items
var actions = [ this.copyLast, p, this.reset,
- this.toggleUTF, this.toggleBell,
- this.about ];
+ this.toggleUTF, this.toggleBell ];
+
+ // Actions for user CSS styles (if any)
+ for (var i = 0; i < this.usercssActions.length; ++i) {
+ actions[actions.length] = this.usercssActions[i];
+ }
+ actions[actions.length] = this.about;
// Allow subclasses to dynamically add entries to the context menu
this.extendContextMenu(menuentries, actions);
--></script>
<link rel="stylesheet" href="styles.css" type="text/css">
<script type="text/javascript"><!--
-
- // We would like to hide overflowing lines as this can lead to
- // visually jarring results if the browser substitutes oversized
- // Unicode characters from different fonts. Unfortunately, a bug
- // in Firefox prevents it from allowing multi-line text
- // selections whenever we change the "overflow" style. So, only
- // do so for non-Netscape browsers.
- if (typeof navigator.appName == 'undefined' ||
- navigator.appName != 'Netscape') {
- document.write('<style type="text/css">' +
- '#vt100 #console div, #vt100 #alt_console div {' +
- ' overflow: hidden;' +
- '}' +
- '</style>');
- }
+ (function() {
+ // We would like to hide overflowing lines as this can lead to
+ // visually jarring results if the browser substitutes oversized
+ // Unicode characters from different fonts. Unfortunately, a bug
+ // in Firefox prevents it from allowing multi-line text
+ // selections whenever we change the "overflow" style. So, only
+ // do so for non-Netscape browsers.
+ if (typeof navigator.appName == 'undefined' ||
+ navigator.appName != 'Netscape') {
+ document.write('<style type="text/css">' +
+ '#vt100 #console div, #vt100 #alt_console div {' +
+ ' overflow: hidden;' +
+ '}' +
+ '</style>');
+ }
+ })();
--></script>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<script type="text/javascript" src="ShellInABox.js"></script>
#include <sys/types.h>
#include <unistd.h>
-#include "shellinabox/service.h"
+#include "logging/logging.h"
#include "shellinabox/launcher.h"
#include "shellinabox/privileges.h"
-#include "logging/logging.h"
+#include "shellinabox/service.h"
struct Service **services;
};
ShellInABox.prototype.about = function() {
- alert("Shell In A Box version " + "2.9 (revision 164)" +
+ alert("Shell In A Box version " + "2.9 (revision 165)" +
"\nCopyright 2008-2009 by Markus Gutschke\n" +
"For more information check http://shellinabox.com" +
(typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?
#include "shellinabox/privileges.h"
#include "shellinabox/service.h"
#include "shellinabox/session.h"
+#include "shellinabox/usercss.h"
#define PORTNUM 4200
#define MAX_RESPONSE 2048
-static int port;
-static int portMin;
-static int portMax;
-static int localhostOnly = 0;
-static int noBeep = 0;
-static int numericHosts = 0;
-static int enableSSL = 1;
-static int enableSSLMenu = 1;
-static int linkifyURLs = 1;
-static char *certificateDir;
-static int certificateFd = -1;
-static HashMap *externalFiles;
-static Server *cgiServer;
-static char *cgiSessionKey;
-static int cgiSessions;
-static char *cssStyleSheet;
+static int port;
+static int portMin;
+static int portMax;
+static int localhostOnly = 0;
+static int noBeep = 0;
+static int numericHosts = 0;
+static int enableSSL = 1;
+static int enableSSLMenu = 1;
+static int linkifyURLs = 1;
+static char *certificateDir;
+static int certificateFd = -1;
+static HashMap *externalFiles;
+static Server *cgiServer;
+static char *cgiSessionKey;
+static int cgiSessions;
+static char *cssStyleSheet;
+static struct UserCSS *userCSSList;
static char *jsonEscape(const char *buf, int len) {
static const char *hexDigit = "0123456789ABCDEF";
extern char vt100End[];
extern char shellInABoxStart[];
extern char shellInABoxEnd[];
+ char *userCSSString = getUserCSSString(userCSSList);
char *stateVars = stringPrintf(NULL,
"serverSupportsSSL = %s;\n"
"disableSSLMenu = %s;\n"
"suppressAllAudio = %s;\n"
- "linkifyURLs = %d;\n\n",
+ "linkifyURLs = %d;\n"
+ "userCSSList = %s;\n\n",
enableSSL ? "true" : "false",
!enableSSLMenu ? "true" : "false",
noBeep ? "true" : "false",
- linkifyURLs);
+ linkifyURLs, userCSSString);
+ free(userCSSString);
int stateVarsLength = strlen(stateVars);
int contentLength = stateVarsLength +
(addr(vt100End) - addr(vt100Start)) +
// Serve the style sheet.
serveStaticFile(http, "text/css; charset=utf-8",
cssStyleSheet, strrchr(cssStyleSheet, '\000'));
+ } else if (pathInfoLength > 8 && !memcmp(pathInfo, "usercss-", 8)) {
+ // Server user style sheets (if any)
+ struct UserCSS *css = userCSSList;
+ for (int idx = atoi(pathInfo + 8);
+ idx-- > 0 && css; css = css->next ) {
+ }
+ if (css) {
+ serveStaticFile(http, "text/css; charset=utf-8",
+ css->style, css->style + css->styleLen);
+ } else {
+ httpSendReply(http, 404, "File not found", NO_MSG);
+ }
} else {
httpSendReply(http, 404, "File not found", NO_MSG);
}
"%s"
" -q, --quiet turn off all messages\n"
" -u, --user=UID switch to this user (default: %s)\n"
+ " --user-css=STYLES defines user-selectable CSS options\n"
" -v, --verbose enable logging messages\n"
" --version prints version information\n"
"\n"
"Debug, quiet, and verbose are mutually exclusive.\n"
"\n"
"One or more --service arguments define services that should "
- "be made available \n"
+ "be made available\n"
"through the web interface:\n"
" SERVICE := <url-path> ':' APP\n"
" APP := 'LOGIN' | 'SSH' [ : <host> ] | "
" ${lines} - number of rows\n"
" ${peer} - name of remote peer\n"
" ${uid} - user id\n"
- " ${user} - user name",
+ " ${user} - user name\n"
+ "\n"
+ "One or more --user-css arguments define optional user-selectable "
+ "CSS options.\n"
+ "These options show up in the right-click context menu:\n"
+ " STYLES := GROUP { ';' GROUP }*\n"
+ " GROUP := OPTION { ',' OPTION }*\n"
+ " OPTION := <label> ':' [ '-' | '+' ] <css-file>\n"
+ "\n"
+ "OPTIONs that make up a GROUP are mutually exclusive. But "
+ "individual GROUPs are\n"
+ "independent of each other.\n",
!serverSupportsSSL() ? "" :
" -c, --cert=CERTDIR set certificate dir "
"(default: $PWD)\n"
{ "disable-ssl-menu", 0, 0, 0 },
{ "quiet", 0, 0, 'q' },
{ "user", 1, 0, 'u' },
+ { "user-css", 1, 0, 0 },
{ "verbose", 0, 0, 'v' },
{ "version", 0, 0, 0 },
{ 0, 0, 0, 0 } };
if (idx-- <= 0) {
// Help (or invalid argument)
usage();
- if (idx == -1) {
+ if (idx < -1) {
fatal("Failed to parse command line");
}
exit(0);
fatal("\"--user\" expects a user name.");
}
runAsUser = parseUser(optarg, NULL);
+ } else if (!idx--) {
+ // User CSS
+ if (!optarg || !*optarg) {
+ fatal("\"--user-css\" expects a list of styles sheets and labels");
+ }
+ parseUserCSS(&userCSSList, optarg);
} else if (!idx--) {
// Verbose
if (!logIsDefault() && (!logIsInfo() || logIsDebug())) {
--- /dev/null
+// usercss.c -- Defines user-selectable CSS options
+// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// In addition to these license terms, the author grants the following
+// additional rights:
+//
+// If you modify this program, or any covered work, by linking or
+// combining it with the OpenSSL project's OpenSSL library (or a
+// modified version of that library), containing parts covered by the
+// terms of the OpenSSL or SSLeay licenses, the author
+// grants you additional permission to convey the resulting work.
+// Corresponding Source for a non-source form of such a combination
+// shall include the source code for the parts of OpenSSL used as well
+// as that of the covered work.
+//
+// You may at your option choose to remove this additional permission from
+// the work, or from any part of it.
+//
+// It is possible to build this program in a way that it loads OpenSSL
+// libraries at run-time. If doing so, the following notices are required
+// by the OpenSSL and SSLeay licenses:
+//
+// This product includes software developed by the OpenSSL Project
+// for use in the OpenSSL Toolkit. (http://www.openssl.org/)
+//
+// This product includes cryptographic software written by Eric Young
+// (eay@cryptsoft.com)
+//
+//
+// The most up-to-date version of this program is always available from
+// http://shellinabox.com
+
+#include "config.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "logging/logging.h"
+#include "shellinabox/usercss.h"
+
+
+static void readStylesheet(const char *filename, char **style, size_t *len) {
+ int fd = open(filename, O_RDONLY);
+ struct stat st;
+ if (fd < 0 || fstat(fd, &st)) {
+ fatal("Cannot access style sheet \"%s\"", filename);
+ }
+ FILE *fp;
+ check(fp = fdopen(fd, "r"));
+ check(*style = malloc(st.st_size + 1));
+ check(fread(*style, 1, st.st_size, fp) == st.st_size);
+ (*style)[st.st_size] = '\000';
+ *len = st.st_size;
+ fclose(fp);
+}
+
+void initUserCSS(struct UserCSS *userCSS, const char *arg) {
+ userCSS->newGroup = 1;
+
+ int numMembers = 1;
+ int hasActiveMember = 0;
+ for (;;) {
+ const char *colon = strchr(arg, ':');
+ if (!colon) {
+ fatal("Incomplete user CSS definition: \"%s\"", arg);
+ }
+
+ check(userCSS->label = malloc(6*(colon - arg) + 1));
+ for (const char *src = arg, *dst = userCSS->label;;) {
+ if (src == colon) {
+ *(char *)dst = '\000';
+ break;
+ }
+ char ch = *src++;
+ if (ch == '<') {
+ memcpy((char *)dst, "<", 4);
+ dst += 4;
+ } else if (ch == '&') {
+ memcpy((char *)dst, "&", 5);
+ dst += 5;
+ } else if (ch == '\'') {
+ memcpy((char *)dst, "'", 6);
+ dst += 6;
+ } else if (ch == '"') {
+ memcpy((char *)dst, """, 6);
+ dst += 6;
+ } else {
+ *(char *)dst++ = ch;
+ }
+ }
+
+ int filenameLen = strcspn(colon + 1, ",;");
+ char *filename;
+ check(filename = malloc(filenameLen + 1));
+ memcpy(filename, colon + 1, filenameLen);
+ filename[filenameLen] = '\000';
+
+ switch (*filename) {
+ case '-':
+ userCSS->isActivated = 0;
+ break;
+ case '+':
+ if (hasActiveMember) {
+ fatal("There can only be one active style option per group. Maybe "
+ "use ';' instead of ',' to start a new group.");
+ }
+ hasActiveMember = 1;
+ userCSS->isActivated = 1;
+ break;
+ default:
+ fatal("Must indicate with '+' or '-' whether the style option is "
+ "active by default");
+ }
+
+ readStylesheet(filename + 1, (char **)&userCSS->style, &userCSS->styleLen);
+ free(filename);
+
+ arg = colon + 1 + filenameLen;
+ if (!*arg) {
+ userCSS->next = NULL;
+ break;
+ }
+ check(userCSS->next = malloc(sizeof(struct UserCSS)));
+ userCSS = userCSS->next;
+ userCSS->newGroup = *arg++ == ';';
+ if (userCSS->newGroup) {
+ if (!hasActiveMember && numMembers > 1) {
+ // Print error message
+ break;
+ }
+ numMembers = 1;
+ hasActiveMember = 0;
+ } else {
+ ++numMembers;
+ }
+ }
+ if (!hasActiveMember && numMembers > 1) {
+ fatal("Each group of style options must have exactly one style that is "
+ "active by\n"
+ "default.");
+ }
+}
+
+struct UserCSS *newUserCSS(const char *arg) {
+ struct UserCSS *userCSS;
+ check(userCSS = malloc(sizeof(struct UserCSS)));
+ initUserCSS(userCSS, arg);
+ return userCSS;
+}
+
+void parseUserCSS(struct UserCSS **userCSSList, const char *arg) {
+ while (*userCSSList) {
+ userCSSList = &(*userCSSList)->next;
+ }
+ *userCSSList = newUserCSS(arg);
+}
+
+void destroyUserCSS(struct UserCSS *userCSS) {
+ if (userCSS) {
+ free((void *)userCSS->label);
+ userCSS->label = NULL;
+ free((void *)userCSS->style);
+ userCSS->style = NULL;
+ userCSS->styleLen = -1;
+ for (struct UserCSS *child = userCSS->next; child; ) {
+ struct UserCSS *next = child->next;
+ free((void *)child->label);
+ free((void *)child->style);
+ free(child);
+ child = next;
+ }
+ userCSS->next = NULL;
+ }
+}
+
+void deleteUserCSS(struct UserCSS *userCSS) {
+ destroyUserCSS(userCSS);
+ free(userCSS);
+}
+
+char *getUserCSSString(struct UserCSS *userCSS) {
+ char *s = stringPrintf(NULL, "[ ");
+ while (userCSS) {
+ s = stringPrintf(s, "[ '%s', %s, %s ]%s",
+ userCSS->label,
+ userCSS->newGroup ? "true" : "false",
+ userCSS->isActivated ? "true" : "false",
+ userCSS->next ? ", " : "");
+ userCSS = userCSS->next;
+ }
+ return stringPrintf(s, " ]");
+}
--- /dev/null
+// usercss.h -- Defines user-selectable CSS options
+// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// In addition to these license terms, the author grants the following
+// additional rights:
+//
+// If you modify this program, or any covered work, by linking or
+// combining it with the OpenSSL project's OpenSSL library (or a
+// modified version of that library), containing parts covered by the
+// terms of the OpenSSL or SSLeay licenses, the author
+// grants you additional permission to convey the resulting work.
+// Corresponding Source for a non-source form of such a combination
+// shall include the source code for the parts of OpenSSL used as well
+// as that of the covered work.
+//
+// You may at your option choose to remove this additional permission from
+// the work, or from any part of it.
+//
+// It is possible to build this program in a way that it loads OpenSSL
+// libraries at run-time. If doing so, the following notices are required
+// by the OpenSSL and SSLeay licenses:
+//
+// This product includes software developed by the OpenSSL Project
+// for use in the OpenSSL Toolkit. (http://www.openssl.org/)
+//
+// This product includes cryptographic software written by Eric Young
+// (eay@cryptsoft.com)
+//
+//
+// The most up-to-date version of this program is always available from
+// http://shellinabox.com
+
+#ifndef USERCSS_H__
+#define USERCSS_H__
+
+struct UserCSS {
+ struct UserCSS *next;
+ int newGroup;
+ int isActivated;
+ const char *label;
+ const char *style;
+ size_t styleLen;
+};
+
+void initUserCSS(struct UserCSS *userCSS, const char *arg);
+struct UserCSS *newUserCSS(const char *arg);
+void parseUserCSS(struct UserCSS **userCSSList, const char *arg);
+void destroyUserCSS(struct UserCSS *userCSS);
+void deleteUserCSS(struct UserCSS *userCSS);
+char *getUserCSSString(struct UserCSS *userCSS);
+
+#endif
}
};
+VT100.prototype.initializeUserCSSStyles = function() {
+ this.usercssActions = [];
+ if (typeof userCSSList != 'undefined') {
+ var menu = '';
+ var group = '';
+ var wasSingleSel = 1;
+ var beginOfGroup = 0;
+ for (var i = 0; i <= userCSSList.length; ++i) {
+ if (i < userCSSList.length) {
+ var label = userCSSList[i][0];
+ var newGroup = userCSSList[i][1];
+ var enabled = userCSSList[i][2];
+
+ // Add user style sheet to document
+ var style = document.createElement('link');
+ var id = document.createAttribute('id');
+ id.nodeValue = 'usercss-' + i;
+ style.setAttributeNode(id);
+ var rel = document.createAttribute('rel');
+ rel.nodeValue = 'stylesheet';
+ style.setAttributeNode(rel);
+ var href = document.createAttribute('href');
+ href.nodeValue = 'usercss-' + i + '.css';
+ style.setAttributeNode(href);
+ var type = document.createAttribute('type');
+ type.nodeValue = 'text/css';
+ style.setAttributeNode(type);
+ document.getElementsByTagName('head')[0].appendChild(style);
+ style.disabled = !enabled;
+ }
+
+ // Add entry to menu
+ if (newGroup || i == userCSSList.length) {
+ if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
+ // The last group had multiple entries that are mutually exclusive;
+ // or the previous to last group did. In either case, we need to
+ // append a "<hr />" before we can add the last group to the menu.
+ menu += '<hr />';
+ }
+ wasSingleSel = i - beginOfGroup < 1;
+ menu += group;
+ group = '';
+
+ for (var j = beginOfGroup; j < i; ++j) {
+ this.usercssActions[this.usercssActions.length] =
+ function(vt100, current, begin, count) {
+
+ // Deselect all other entries in the group, then either select
+ // (for multiple entries in group) or toggle (for on/off entry)
+ // the current entry.
+ return function() {
+ var entry = vt100.getChildById(vt100.menu,
+ 'beginusercss');
+ var i = -1;
+ var j = -1;
+ for (var c = count; c > 0; ++j) {
+ if (entry.tagName == 'LI') {
+ if (++i >= begin) {
+ --c;
+ var label = vt100.usercss.childNodes[j];
+ label.innerHTML =
+ label.innerHTML.replace(/^\u2714 /, '');
+ var sheet = document.getElementById(
+ 'usercss-' + i);
+ if (i == current) {
+ if (count == 1) {
+ sheet.disabled = !sheet.disabled;
+ } else {
+ sheet.disabled = false;
+ }
+ if (!sheet.disabled) {
+ label.innerHTML= '✔ ' + label.innerHTML;
+ }
+ } else {
+ sheet.disabled = true;
+ }
+ }
+ }
+ entry = entry.nextSibling;
+ }
+ };
+ }(this, j, beginOfGroup, i - beginOfGroup);
+ }
+
+ if (i == userCSSList.length) {
+ break;
+ }
+
+ beginOfGroup = i;
+ }
+ // Collect all entries in a group, before attaching them to the menu.
+ // This is necessary as we don't know whether this is a group of
+ // mutually exclusive options (which should be separated by "<hr />" on
+ // both ends), or whether this is a on/off toggle, which can be grouped
+ // together with other on/off options.
+ group +=
+ '<li>' + (enabled ? '✔ ' : '') + label + '</li>';
+ }
+ this.usercss.innerHTML = menu;
+ }
+};
+
VT100.prototype.initializeElements = function(container) {
// If the necessary objects have not already been defined in the HTML
// page, create them now.
!this.getChildById(this.container, 'padding') ||
!this.getChildById(this.container, 'cursor') ||
!this.getChildById(this.container, 'lineheight') ||
+ !this.getChildById(this.container, 'usercss') ||
!this.getChildById(this.container, 'space') ||
!this.getChildById(this.container, 'input') ||
!this.getChildById(this.container, 'cliphelper') ||
'<pre id="cursor"> </pre>' +
'</div>' +
'<div class="hidden">' +
+ '<div id="usercss"></div>' +
'<pre><div><span id="space"></span></div></pre>' +
'<input type="textfield" id="input" />' +
'<input type="textfield" id="cliphelper" />' +
var ieProbe = this.getChildById(this.container, 'ieprobe');
this.padding = this.getChildById(this.container, 'padding');
this.cursor = this.getChildById(this.container, 'cursor');
+ this.usercss = this.getChildById(this.container, 'usercss');
this.space = this.getChildById(this.container, 'space');
this.input = this.getChildById(this.container, 'input');
this.cliphelper = this.getChildById(this.container,
'cliphelper');
this.attributeHelper = this.getChildById(this.container, 'attrib');
+ // Add any user selectable style sheets to the menu
+ this.initializeUserCSSStyles();
+
// Remember the dimensions of a standard character glyph. We would
// expect that we could just check cursor.clientWidth/Height at any time,
// but it turns out that browsers sometimes invalidate these values
};
VT100.prototype.about = function() {
- alert("VT100 Terminal Emulator " + "2.9 (revision 164)" +
+ alert("VT100 Terminal Emulator " + "2.9 (revision 165)" +
"\nCopyright 2008-2009 by Markus Gutschke\n" +
"For more information check http://shellinabox.com");
};
(this.utfEnabled ? '✔ ' : '') + 'Unicode</li>' +
'<li id="endconfig">' +
(this.visualBell ? '✔ ' : '') + 'Visual Bell</li>'+
- '<hr />' +
+ (this.usercss.firstChild ?
+ '<hr id="beginusercss" />' +
+ this.usercss.innerHTML +
+ '<hr id="endusercss" />' :
+ '<hr />') +
'<li id="about">About...</li>' +
'</ul>' +
'</td></tr>' +
menuentries.childNodes[1].className
= 'disabled';
}
+
+ // Actions for default items
var actions = [ this.copyLast, p, this.reset,
- this.toggleUTF, this.toggleBell,
- this.about ];
+ this.toggleUTF, this.toggleBell ];
+
+ // Actions for user CSS styles (if any)
+ for (var i = 0; i < this.usercssActions.length; ++i) {
+ actions[actions.length] = this.usercssActions[i];
+ }
+ actions[actions.length] = this.about;
// Allow subclasses to dynamically add entries to the context menu
this.extendContextMenu(menuentries, actions);
}
};
+VT100.prototype.initializeUserCSSStyles = function() {
+ this.usercssActions = [];
+ if (typeof userCSSList != 'undefined') {
+ var menu = '';
+ var group = '';
+ var wasSingleSel = 1;
+ var beginOfGroup = 0;
+ for (var i = 0; i <= userCSSList.length; ++i) {
+ if (i < userCSSList.length) {
+ var label = userCSSList[i][0];
+ var newGroup = userCSSList[i][1];
+ var enabled = userCSSList[i][2];
+
+ // Add user style sheet to document
+ var style = document.createElement('link');
+ var id = document.createAttribute('id');
+ id.nodeValue = 'usercss-' + i;
+ style.setAttributeNode(id);
+ var rel = document.createAttribute('rel');
+ rel.nodeValue = 'stylesheet';
+ style.setAttributeNode(rel);
+ var href = document.createAttribute('href');
+ href.nodeValue = 'usercss-' + i + '.css';
+ style.setAttributeNode(href);
+ var type = document.createAttribute('type');
+ type.nodeValue = 'text/css';
+ style.setAttributeNode(type);
+ document.getElementsByTagName('head')[0].appendChild(style);
+ style.disabled = !enabled;
+ }
+
+ // Add entry to menu
+ if (newGroup || i == userCSSList.length) {
+ if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
+ // The last group had multiple entries that are mutually exclusive;
+ // or the previous to last group did. In either case, we need to
+ // append a "<hr />" before we can add the last group to the menu.
+ menu += '<hr />';
+ }
+ wasSingleSel = i - beginOfGroup < 1;
+ menu += group;
+ group = '';
+
+ for (var j = beginOfGroup; j < i; ++j) {
+ this.usercssActions[this.usercssActions.length] =
+ function(vt100, current, begin, count) {
+
+ // Deselect all other entries in the group, then either select
+ // (for multiple entries in group) or toggle (for on/off entry)
+ // the current entry.
+ return function() {
+ var entry = vt100.getChildById(vt100.menu,
+ 'beginusercss');
+ var i = -1;
+ var j = -1;
+ for (var c = count; c > 0; ++j) {
+ if (entry.tagName == 'LI') {
+ if (++i >= begin) {
+ --c;
+ var label = vt100.usercss.childNodes[j];
+ label.innerHTML =
+ label.innerHTML.replace(/^\u2714 /, '');
+ var sheet = document.getElementById(
+ 'usercss-' + i);
+ if (i == current) {
+ if (count == 1) {
+ sheet.disabled = !sheet.disabled;
+ } else {
+ sheet.disabled = false;
+ }
+ if (!sheet.disabled) {
+ label.innerHTML= '✔ ' + label.innerHTML;
+ }
+ } else {
+ sheet.disabled = true;
+ }
+ }
+ }
+ entry = entry.nextSibling;
+ }
+ };
+ }(this, j, beginOfGroup, i - beginOfGroup);
+ }
+
+ if (i == userCSSList.length) {
+ break;
+ }
+
+ beginOfGroup = i;
+ }
+ // Collect all entries in a group, before attaching them to the menu.
+ // This is necessary as we don't know whether this is a group of
+ // mutually exclusive options (which should be separated by "<hr />" on
+ // both ends), or whether this is a on/off toggle, which can be grouped
+ // together with other on/off options.
+ group +=
+ '<li>' + (enabled ? '✔ ' : '') + label + '</li>';
+ }
+ this.usercss.innerHTML = menu;
+ }
+};
+
VT100.prototype.initializeElements = function(container) {
// If the necessary objects have not already been defined in the HTML
// page, create them now.
!this.getChildById(this.container, 'padding') ||
!this.getChildById(this.container, 'cursor') ||
!this.getChildById(this.container, 'lineheight') ||
+ !this.getChildById(this.container, 'usercss') ||
!this.getChildById(this.container, 'space') ||
!this.getChildById(this.container, 'input') ||
!this.getChildById(this.container, 'cliphelper') ||
'<pre id="cursor"> </pre>' +
'</div>' +
'<div class="hidden">' +
+ '<div id="usercss"></div>' +
'<pre><div><span id="space"></span></div></pre>' +
'<input type="textfield" id="input" />' +
'<input type="textfield" id="cliphelper" />' +
var ieProbe = this.getChildById(this.container, 'ieprobe');
this.padding = this.getChildById(this.container, 'padding');
this.cursor = this.getChildById(this.container, 'cursor');
+ this.usercss = this.getChildById(this.container, 'usercss');
this.space = this.getChildById(this.container, 'space');
this.input = this.getChildById(this.container, 'input');
this.cliphelper = this.getChildById(this.container,
'cliphelper');
this.attributeHelper = this.getChildById(this.container, 'attrib');
+ // Add any user selectable style sheets to the menu
+ this.initializeUserCSSStyles();
+
// Remember the dimensions of a standard character glyph. We would
// expect that we could just check cursor.clientWidth/Height at any time,
// but it turns out that browsers sometimes invalidate these values
(this.utfEnabled ? '✔ ' : '') + 'Unicode</li>' +
'<li id="endconfig">' +
(this.visualBell ? '✔ ' : '') + 'Visual Bell</li>'+
- '<hr />' +
+ (this.usercss.firstChild ?
+ '<hr id="beginusercss" />' +
+ this.usercss.innerHTML +
+ '<hr id="endusercss" />' :
+ '<hr />') +
'<li id="about">About...</li>' +
'</ul>' +
'</td></tr>' +
menuentries.childNodes[1].className
= 'disabled';
}
+
+ // Actions for default items
var actions = [ this.copyLast, p, this.reset,
- this.toggleUTF, this.toggleBell,
- this.about ];
+ this.toggleUTF, this.toggleBell ];
+
+ // Actions for user CSS styles (if any)
+ for (var i = 0; i < this.usercssActions.length; ++i) {
+ actions[actions.length] = this.usercssActions[i];
+ }
+ actions[actions.length] = this.about;
// Allow subclasses to dynamically add entries to the context menu
this.extendContextMenu(menuentries, actions);