Initial version of code that allows users to interactively select from

different style sheet options. This code is still incomplete and subject to
change (e.g. the command line syntax might still change). But it is good
enough to demonstrate the concept on simple style sheets (such as selecting
between normal and reverse video).


git-svn-id: https://shellinabox.googlecode.com/svn/trunk@165 0da03de8-d603-11dd-86c2-0f8696b7b6f9
This commit is contained in:
zodiac 2009-08-11 07:21:51 +00:00
parent e78b94961c
commit 7ab3b32465
14 changed files with 742 additions and 54 deletions

View file

@ -76,6 +76,8 @@ shellinaboxd_SOURCES = shellinabox/shellinaboxd.c \
shellinabox/service.h \ shellinabox/service.h \
shellinabox/session.c \ shellinabox/session.c \
shellinabox/session.h \ shellinabox/session.h \
shellinabox/usercss.c \
shellinabox/usercss.h \
shellinabox/cgi_root.html \ shellinabox/cgi_root.html \
shellinabox/root_page.html \ shellinabox/root_page.html \
shellinabox/vt100.jspp \ shellinabox/vt100.jspp \

View file

@ -73,7 +73,7 @@ PROGRAMS = $(bin_PROGRAMS)
am__dirstamp = $(am__leading_dot)dirstamp am__dirstamp = $(am__leading_dot)dirstamp
am_shellinaboxd_OBJECTS = shellinaboxd.$(OBJEXT) \ am_shellinaboxd_OBJECTS = shellinaboxd.$(OBJEXT) \
externalfile.$(OBJEXT) launcher.$(OBJEXT) privileges.$(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/cgi_root.$(OBJEXT) shellinabox/root_page.$(OBJEXT) \
shellinabox/vt100.$(OBJEXT) \ shellinabox/vt100.$(OBJEXT) \
shellinabox/shell_in_a_box.$(OBJEXT) \ shellinabox/shell_in_a_box.$(OBJEXT) \
@ -320,6 +320,8 @@ shellinaboxd_SOURCES = shellinabox/shellinaboxd.c \
shellinabox/service.h \ shellinabox/service.h \
shellinabox/session.c \ shellinabox/session.c \
shellinabox/session.h \ shellinabox/session.h \
shellinabox/usercss.c \
shellinabox/usercss.h \
shellinabox/cgi_root.html \ shellinabox/cgi_root.html \
shellinabox/root_page.html \ shellinabox/root_page.html \
shellinabox/vt100.jspp \ shellinabox/vt100.jspp \
@ -503,6 +505,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ssl.Plo@am__quote@ @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)/trie.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/url.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: .c.o:
@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
@ -658,6 +661,20 @@ session.obj: shellinabox/session.c
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @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 session.obj `if test -f 'shellinabox/session.c'; then $(CYGPATH_W) 'shellinabox/session.c'; else $(CYGPATH_W) '$(srcdir)/shellinabox/session.c'; fi` @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: mostlyclean-libtool:
-rm -f *.lo -rm -f *.lo

View file

@ -138,7 +138,7 @@
#define STDC_HEADERS 1 #define STDC_HEADERS 1
/* Most recent revision number in the version control system */ /* Most recent revision number in the version control system */
#define VCS_REVISION "164" #define VCS_REVISION "165"
/* Version number of package */ /* Version number of package */
#define VERSION "2.9" #define VERSION "2.9"

2
configure vendored
View file

@ -2317,7 +2317,7 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_compiler_gnu=$ac_cv_c_compiler_gnu
VCS_REVISION=164 VCS_REVISION=165
cat >>confdefs.h <<_ACEOF cat >>confdefs.h <<_ACEOF

View file

@ -2,7 +2,7 @@ AC_PREREQ(2.57)
dnl This is the one location where the authoritative version number is stored dnl This is the one location where the authoritative version number is stored
AC_INIT(shellinabox, 2.9, markus@shellinabox.com) AC_INIT(shellinabox, 2.9, markus@shellinabox.com)
VCS_REVISION=164 VCS_REVISION=165
AC_SUBST(VCS_REVISION) AC_SUBST(VCS_REVISION)
AC_DEFINE_UNQUOTED(VCS_REVISION, "${VCS_REVISION}", AC_DEFINE_UNQUOTED(VCS_REVISION, "${VCS_REVISION}",
[Most recent revision number in the version control system]) [Most recent revision number in the version control system])

View file

@ -259,6 +259,108 @@ VT100.prototype.addListener = function(elem, event, listener) {
} }
}; };
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= '&#10004; ' + 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 ? '&#10004; ' : '') + label + '</li>';
}
this.usercss.innerHTML = menu;
}
};
VT100.prototype.initializeElements = function(container) { VT100.prototype.initializeElements = function(container) {
// If the necessary objects have not already been defined in the HTML // If the necessary objects have not already been defined in the HTML
// page, create them now. // page, create them now.
@ -279,6 +381,7 @@ VT100.prototype.initializeElements = function(container) {
!this.getChildById(this.container, 'padding') || !this.getChildById(this.container, 'padding') ||
!this.getChildById(this.container, 'cursor') || !this.getChildById(this.container, 'cursor') ||
!this.getChildById(this.container, 'lineheight') || !this.getChildById(this.container, 'lineheight') ||
!this.getChildById(this.container, 'usercss') ||
!this.getChildById(this.container, 'space') || !this.getChildById(this.container, 'space') ||
!this.getChildById(this.container, 'input') || !this.getChildById(this.container, 'input') ||
!this.getChildById(this.container, 'cliphelper') || !this.getChildById(this.container, 'cliphelper') ||
@ -325,6 +428,7 @@ VT100.prototype.initializeElements = function(container) {
'<pre id="cursor">&nbsp;</pre>' + '<pre id="cursor">&nbsp;</pre>' +
'</div>' + '</div>' +
'<div class="hidden">' + '<div class="hidden">' +
'<div id="usercss"></div>' +
'<pre><div><span id="space"></span></div></pre>' + '<pre><div><span id="space"></span></div></pre>' +
'<input type="textfield" id="input" />' + '<input type="textfield" id="input" />' +
'<input type="textfield" id="cliphelper" />' + '<input type="textfield" id="cliphelper" />' +
@ -365,12 +469,16 @@ VT100.prototype.initializeElements = function(container) {
var ieProbe = this.getChildById(this.container, 'ieprobe'); var ieProbe = this.getChildById(this.container, 'ieprobe');
this.padding = this.getChildById(this.container, 'padding'); this.padding = this.getChildById(this.container, 'padding');
this.cursor = this.getChildById(this.container, 'cursor'); this.cursor = this.getChildById(this.container, 'cursor');
this.usercss = this.getChildById(this.container, 'usercss');
this.space = this.getChildById(this.container, 'space'); this.space = this.getChildById(this.container, 'space');
this.input = this.getChildById(this.container, 'input'); this.input = this.getChildById(this.container, 'input');
this.cliphelper = this.getChildById(this.container, this.cliphelper = this.getChildById(this.container,
'cliphelper'); 'cliphelper');
this.attributeHelper = this.getChildById(this.container, 'attrib'); 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 // Remember the dimensions of a standard character glyph. We would
// expect that we could just check cursor.clientWidth/Height at any time, // expect that we could just check cursor.clientWidth/Height at any time,
// but it turns out that browsers sometimes invalidate these values // but it turns out that browsers sometimes invalidate these values
@ -1693,7 +1801,7 @@ VT100.prototype.toggleBell = function() {
}; };
VT100.prototype.about = function() { 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" + "\nCopyright 2008-2009 by Markus Gutschke\n" +
"For more information check http://shellinabox.com"); "For more information check http://shellinabox.com");
}; };
@ -1724,7 +1832,11 @@ VT100.prototype.showContextMenu = function(x, y) {
(this.utfEnabled ? '&#10004; ' : '') + 'Unicode</li>' + (this.utfEnabled ? '&#10004; ' : '') + 'Unicode</li>' +
'<li id="endconfig">' + '<li id="endconfig">' +
(this.visualBell ? '&#10004; ' : '') + 'Visual Bell</li>'+ (this.visualBell ? '&#10004; ' : '') + 'Visual Bell</li>'+
'<hr />' + (this.usercss.firstChild ?
'<hr id="beginusercss" />' +
this.usercss.innerHTML +
'<hr id="endusercss" />' :
'<hr />') +
'<li id="about">About...</li>' + '<li id="about">About...</li>' +
'</ul>' + '</ul>' +
'</td></tr>' + '</td></tr>' +
@ -1744,9 +1856,16 @@ VT100.prototype.showContextMenu = function(x, y) {
menuentries.childNodes[1].className menuentries.childNodes[1].className
= 'disabled'; = 'disabled';
} }
// Actions for default items
var actions = [ this.copyLast, p, this.reset, var actions = [ this.copyLast, p, this.reset,
this.toggleUTF, this.toggleBell, this.toggleUTF, this.toggleBell ];
this.about ];
// 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 // Allow subclasses to dynamically add entries to the context menu
this.extendContextMenu(menuentries, actions); this.extendContextMenu(menuentries, actions);

View file

@ -78,7 +78,7 @@
--></script> --></script>
<link rel="stylesheet" href="styles.css" type="text/css"> <link rel="stylesheet" href="styles.css" type="text/css">
<script type="text/javascript"><!-- <script type="text/javascript"><!--
(function() {
// We would like to hide overflowing lines as this can lead to // We would like to hide overflowing lines as this can lead to
// visually jarring results if the browser substitutes oversized // visually jarring results if the browser substitutes oversized
// Unicode characters from different fonts. Unfortunately, a bug // Unicode characters from different fonts. Unfortunately, a bug
@ -93,6 +93,7 @@
'}' + '}' +
'</style>'); '</style>');
} }
})();
--></script> --></script>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon"> <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<script type="text/javascript" src="ShellInABox.js"></script> <script type="text/javascript" src="ShellInABox.js"></script>

View file

@ -50,10 +50,10 @@
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include "shellinabox/service.h" #include "logging/logging.h"
#include "shellinabox/launcher.h" #include "shellinabox/launcher.h"
#include "shellinabox/privileges.h" #include "shellinabox/privileges.h"
#include "logging/logging.h" #include "shellinabox/service.h"
struct Service **services; struct Service **services;

View file

@ -355,7 +355,7 @@ ShellInABox.prototype.extendContextMenu = function(entries, actions) {
}; };
ShellInABox.prototype.about = function() { 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" + "\nCopyright 2008-2009 by Markus Gutschke\n" +
"For more information check http://shellinabox.com" + "For more information check http://shellinabox.com" +
(typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ? (typeof serverSupportsSSL != 'undefined' && serverSupportsSSL ?

View file

@ -72,6 +72,7 @@
#include "shellinabox/privileges.h" #include "shellinabox/privileges.h"
#include "shellinabox/service.h" #include "shellinabox/service.h"
#include "shellinabox/session.h" #include "shellinabox/session.h"
#include "shellinabox/usercss.h"
#define PORTNUM 4200 #define PORTNUM 4200
#define MAX_RESPONSE 2048 #define MAX_RESPONSE 2048
@ -92,6 +93,7 @@ static Server *cgiServer;
static char *cgiSessionKey; static char *cgiSessionKey;
static int cgiSessions; static int cgiSessions;
static char *cssStyleSheet; static char *cssStyleSheet;
static struct UserCSS *userCSSList;
static char *jsonEscape(const char *buf, int len) { static char *jsonEscape(const char *buf, int len) {
static const char *hexDigit = "0123456789ABCDEF"; static const char *hexDigit = "0123456789ABCDEF";
@ -517,15 +519,18 @@ static int shellInABoxHttpHandler(HttpConnection *http, void *arg,
extern char vt100End[]; extern char vt100End[];
extern char shellInABoxStart[]; extern char shellInABoxStart[];
extern char shellInABoxEnd[]; extern char shellInABoxEnd[];
char *userCSSString = getUserCSSString(userCSSList);
char *stateVars = stringPrintf(NULL, char *stateVars = stringPrintf(NULL,
"serverSupportsSSL = %s;\n" "serverSupportsSSL = %s;\n"
"disableSSLMenu = %s;\n" "disableSSLMenu = %s;\n"
"suppressAllAudio = %s;\n" "suppressAllAudio = %s;\n"
"linkifyURLs = %d;\n\n", "linkifyURLs = %d;\n"
"userCSSList = %s;\n\n",
enableSSL ? "true" : "false", enableSSL ? "true" : "false",
!enableSSLMenu ? "true" : "false", !enableSSLMenu ? "true" : "false",
noBeep ? "true" : "false", noBeep ? "true" : "false",
linkifyURLs); linkifyURLs, userCSSString);
free(userCSSString);
int stateVarsLength = strlen(stateVars); int stateVarsLength = strlen(stateVars);
int contentLength = stateVarsLength + int contentLength = stateVarsLength +
(addr(vt100End) - addr(vt100Start)) + (addr(vt100End) - addr(vt100Start)) +
@ -552,6 +557,18 @@ static int shellInABoxHttpHandler(HttpConnection *http, void *arg,
// Serve the style sheet. // Serve the style sheet.
serveStaticFile(http, "text/css; charset=utf-8", serveStaticFile(http, "text/css; charset=utf-8",
cssStyleSheet, strrchr(cssStyleSheet, '\000')); 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 { } else {
httpSendReply(http, 404, "File not found", NO_MSG); httpSendReply(http, 404, "File not found", NO_MSG);
} }
@ -605,6 +622,7 @@ static void usage(void) {
"%s" "%s"
" -q, --quiet turn off all messages\n" " -q, --quiet turn off all messages\n"
" -u, --user=UID switch to this user (default: %s)\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" " -v, --verbose enable logging messages\n"
" --version prints version information\n" " --version prints version information\n"
"\n" "\n"
@ -627,7 +645,18 @@ static void usage(void) {
" ${lines} - number of rows\n" " ${lines} - number of rows\n"
" ${peer} - name of remote peer\n" " ${peer} - name of remote peer\n"
" ${uid} - user id\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() ? "" : !serverSupportsSSL() ? "" :
" -c, --cert=CERTDIR set certificate dir " " -c, --cert=CERTDIR set certificate dir "
"(default: $PWD)\n" "(default: $PWD)\n"
@ -685,6 +714,7 @@ static void parseArgs(int argc, char * const argv[]) {
{ "disable-ssl-menu", 0, 0, 0 }, { "disable-ssl-menu", 0, 0, 0 },
{ "quiet", 0, 0, 'q' }, { "quiet", 0, 0, 'q' },
{ "user", 1, 0, 'u' }, { "user", 1, 0, 'u' },
{ "user-css", 1, 0, 0 },
{ "verbose", 0, 0, 'v' }, { "verbose", 0, 0, 'v' },
{ "version", 0, 0, 0 }, { "version", 0, 0, 0 },
{ 0, 0, 0, 0 } }; { 0, 0, 0, 0 } };
@ -703,7 +733,7 @@ static void parseArgs(int argc, char * const argv[]) {
if (idx-- <= 0) { if (idx-- <= 0) {
// Help (or invalid argument) // Help (or invalid argument)
usage(); usage();
if (idx == -1) { if (idx < -1) {
fatal("Failed to parse command line"); fatal("Failed to parse command line");
} }
exit(0); exit(0);
@ -894,6 +924,12 @@ static void parseArgs(int argc, char * const argv[]) {
fatal("\"--user\" expects a user name."); fatal("\"--user\" expects a user name.");
} }
runAsUser = parseUser(optarg, NULL); 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--) { } else if (!idx--) {
// Verbose // Verbose
if (!logIsDefault() && (!logIsInfo() || logIsDebug())) { if (!logIsDefault() && (!logIsInfo() || logIsDebug())) {

210
shellinabox/usercss.c Normal file
View file

@ -0,0 +1,210 @@
// 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, "&lt;", 4);
dst += 4;
} else if (ch == '&') {
memcpy((char *)dst, "&amp;", 5);
dst += 5;
} else if (ch == '\'') {
memcpy((char *)dst, "&apos;", 6);
dst += 6;
} else if (ch == '"') {
memcpy((char *)dst, "&quot;", 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, " ]");
}

65
shellinabox/usercss.h Normal file
View file

@ -0,0 +1,65 @@
// 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

View file

@ -259,6 +259,108 @@ VT100.prototype.addListener = function(elem, event, listener) {
} }
}; };
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= '&#10004; ' + 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 ? '&#10004; ' : '') + label + '</li>';
}
this.usercss.innerHTML = menu;
}
};
VT100.prototype.initializeElements = function(container) { VT100.prototype.initializeElements = function(container) {
// If the necessary objects have not already been defined in the HTML // If the necessary objects have not already been defined in the HTML
// page, create them now. // page, create them now.
@ -279,6 +381,7 @@ VT100.prototype.initializeElements = function(container) {
!this.getChildById(this.container, 'padding') || !this.getChildById(this.container, 'padding') ||
!this.getChildById(this.container, 'cursor') || !this.getChildById(this.container, 'cursor') ||
!this.getChildById(this.container, 'lineheight') || !this.getChildById(this.container, 'lineheight') ||
!this.getChildById(this.container, 'usercss') ||
!this.getChildById(this.container, 'space') || !this.getChildById(this.container, 'space') ||
!this.getChildById(this.container, 'input') || !this.getChildById(this.container, 'input') ||
!this.getChildById(this.container, 'cliphelper') || !this.getChildById(this.container, 'cliphelper') ||
@ -325,6 +428,7 @@ VT100.prototype.initializeElements = function(container) {
'<pre id="cursor">&nbsp;</pre>' + '<pre id="cursor">&nbsp;</pre>' +
'</div>' + '</div>' +
'<div class="hidden">' + '<div class="hidden">' +
'<div id="usercss"></div>' +
'<pre><div><span id="space"></span></div></pre>' + '<pre><div><span id="space"></span></div></pre>' +
'<input type="textfield" id="input" />' + '<input type="textfield" id="input" />' +
'<input type="textfield" id="cliphelper" />' + '<input type="textfield" id="cliphelper" />' +
@ -365,12 +469,16 @@ VT100.prototype.initializeElements = function(container) {
var ieProbe = this.getChildById(this.container, 'ieprobe'); var ieProbe = this.getChildById(this.container, 'ieprobe');
this.padding = this.getChildById(this.container, 'padding'); this.padding = this.getChildById(this.container, 'padding');
this.cursor = this.getChildById(this.container, 'cursor'); this.cursor = this.getChildById(this.container, 'cursor');
this.usercss = this.getChildById(this.container, 'usercss');
this.space = this.getChildById(this.container, 'space'); this.space = this.getChildById(this.container, 'space');
this.input = this.getChildById(this.container, 'input'); this.input = this.getChildById(this.container, 'input');
this.cliphelper = this.getChildById(this.container, this.cliphelper = this.getChildById(this.container,
'cliphelper'); 'cliphelper');
this.attributeHelper = this.getChildById(this.container, 'attrib'); 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 // Remember the dimensions of a standard character glyph. We would
// expect that we could just check cursor.clientWidth/Height at any time, // expect that we could just check cursor.clientWidth/Height at any time,
// but it turns out that browsers sometimes invalidate these values // but it turns out that browsers sometimes invalidate these values
@ -1693,7 +1801,7 @@ VT100.prototype.toggleBell = function() {
}; };
VT100.prototype.about = function() { 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" + "\nCopyright 2008-2009 by Markus Gutschke\n" +
"For more information check http://shellinabox.com"); "For more information check http://shellinabox.com");
}; };
@ -1724,7 +1832,11 @@ VT100.prototype.showContextMenu = function(x, y) {
(this.utfEnabled ? '&#10004; ' : '') + 'Unicode</li>' + (this.utfEnabled ? '&#10004; ' : '') + 'Unicode</li>' +
'<li id="endconfig">' + '<li id="endconfig">' +
(this.visualBell ? '&#10004; ' : '') + 'Visual Bell</li>'+ (this.visualBell ? '&#10004; ' : '') + 'Visual Bell</li>'+
'<hr />' + (this.usercss.firstChild ?
'<hr id="beginusercss" />' +
this.usercss.innerHTML +
'<hr id="endusercss" />' :
'<hr />') +
'<li id="about">About...</li>' + '<li id="about">About...</li>' +
'</ul>' + '</ul>' +
'</td></tr>' + '</td></tr>' +
@ -1744,9 +1856,16 @@ VT100.prototype.showContextMenu = function(x, y) {
menuentries.childNodes[1].className menuentries.childNodes[1].className
= 'disabled'; = 'disabled';
} }
// Actions for default items
var actions = [ this.copyLast, p, this.reset, var actions = [ this.copyLast, p, this.reset,
this.toggleUTF, this.toggleBell, this.toggleUTF, this.toggleBell ];
this.about ];
// 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 // Allow subclasses to dynamically add entries to the context menu
this.extendContextMenu(menuentries, actions); this.extendContextMenu(menuentries, actions);

View file

@ -259,6 +259,108 @@ VT100.prototype.addListener = function(elem, event, listener) {
} }
}; };
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= '&#10004; ' + 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 ? '&#10004; ' : '') + label + '</li>';
}
this.usercss.innerHTML = menu;
}
};
VT100.prototype.initializeElements = function(container) { VT100.prototype.initializeElements = function(container) {
// If the necessary objects have not already been defined in the HTML // If the necessary objects have not already been defined in the HTML
// page, create them now. // page, create them now.
@ -279,6 +381,7 @@ VT100.prototype.initializeElements = function(container) {
!this.getChildById(this.container, 'padding') || !this.getChildById(this.container, 'padding') ||
!this.getChildById(this.container, 'cursor') || !this.getChildById(this.container, 'cursor') ||
!this.getChildById(this.container, 'lineheight') || !this.getChildById(this.container, 'lineheight') ||
!this.getChildById(this.container, 'usercss') ||
!this.getChildById(this.container, 'space') || !this.getChildById(this.container, 'space') ||
!this.getChildById(this.container, 'input') || !this.getChildById(this.container, 'input') ||
!this.getChildById(this.container, 'cliphelper') || !this.getChildById(this.container, 'cliphelper') ||
@ -325,6 +428,7 @@ VT100.prototype.initializeElements = function(container) {
'<pre id="cursor">&nbsp;</pre>' + '<pre id="cursor">&nbsp;</pre>' +
'</div>' + '</div>' +
'<div class="hidden">' + '<div class="hidden">' +
'<div id="usercss"></div>' +
'<pre><div><span id="space"></span></div></pre>' + '<pre><div><span id="space"></span></div></pre>' +
'<input type="textfield" id="input" />' + '<input type="textfield" id="input" />' +
'<input type="textfield" id="cliphelper" />' + '<input type="textfield" id="cliphelper" />' +
@ -365,12 +469,16 @@ VT100.prototype.initializeElements = function(container) {
var ieProbe = this.getChildById(this.container, 'ieprobe'); var ieProbe = this.getChildById(this.container, 'ieprobe');
this.padding = this.getChildById(this.container, 'padding'); this.padding = this.getChildById(this.container, 'padding');
this.cursor = this.getChildById(this.container, 'cursor'); this.cursor = this.getChildById(this.container, 'cursor');
this.usercss = this.getChildById(this.container, 'usercss');
this.space = this.getChildById(this.container, 'space'); this.space = this.getChildById(this.container, 'space');
this.input = this.getChildById(this.container, 'input'); this.input = this.getChildById(this.container, 'input');
this.cliphelper = this.getChildById(this.container, this.cliphelper = this.getChildById(this.container,
'cliphelper'); 'cliphelper');
this.attributeHelper = this.getChildById(this.container, 'attrib'); 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 // Remember the dimensions of a standard character glyph. We would
// expect that we could just check cursor.clientWidth/Height at any time, // expect that we could just check cursor.clientWidth/Height at any time,
// but it turns out that browsers sometimes invalidate these values // but it turns out that browsers sometimes invalidate these values
@ -1724,7 +1832,11 @@ VT100.prototype.showContextMenu = function(x, y) {
(this.utfEnabled ? '&#10004; ' : '') + 'Unicode</li>' + (this.utfEnabled ? '&#10004; ' : '') + 'Unicode</li>' +
'<li id="endconfig">' + '<li id="endconfig">' +
(this.visualBell ? '&#10004; ' : '') + 'Visual Bell</li>'+ (this.visualBell ? '&#10004; ' : '') + 'Visual Bell</li>'+
'<hr />' + (this.usercss.firstChild ?
'<hr id="beginusercss" />' +
this.usercss.innerHTML +
'<hr id="endusercss" />' :
'<hr />') +
'<li id="about">About...</li>' + '<li id="about">About...</li>' +
'</ul>' + '</ul>' +
'</td></tr>' + '</td></tr>' +
@ -1744,9 +1856,16 @@ VT100.prototype.showContextMenu = function(x, y) {
menuentries.childNodes[1].className menuentries.childNodes[1].className
= 'disabled'; = 'disabled';
} }
// Actions for default items
var actions = [ this.copyLast, p, this.reset, var actions = [ this.copyLast, p, this.reset,
this.toggleUTF, this.toggleBell, this.toggleUTF, this.toggleBell ];
this.about ];
// 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 // Allow subclasses to dynamically add entries to the context menu
this.extendContextMenu(menuentries, actions); this.extendContextMenu(menuentries, actions);