web-console-rails3 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.markdown +2 -2
  3. data/app/assets/javascripts/web_console/application.js +1 -3
  4. data/app/assets/javascripts/web_console/console_sessions.js +3 -24
  5. data/app/assets/stylesheets/web_console/application.css +1 -0
  6. data/app/assets/stylesheets/web_console/console_sessions.css +0 -8
  7. data/app/controllers/web_console/application_controller.rb +1 -0
  8. data/app/controllers/web_console/console_sessions_controller.rb +24 -4
  9. data/app/models/web_console/console_session.rb +32 -62
  10. data/app/views/web_console/console_sessions/index.html.erb +9 -4
  11. data/config/routes.rb +7 -1
  12. data/lib/assets/javascripts/web-console.js +1 -0
  13. data/lib/assets/javascripts/web_console.js +202 -0
  14. data/lib/web_console.rb +1 -1
  15. data/lib/web_console/engine.rb +17 -7
  16. data/lib/web_console/slave.rb +112 -0
  17. data/lib/web_console/version.rb +1 -1
  18. data/test/controllers/web_console/console_sessions_controller_test.rb +29 -11
  19. data/test/dummy/config/application.rb +17 -1
  20. data/test/dummy/config/application.rb.orig +58 -0
  21. data/test/dummy/log/development.log +28962 -0
  22. data/test/dummy/log/test.log +329 -0
  23. data/test/dummy/tmp/cache/assets/CBD/6E0/sprockets%2F3293dfc0984076f0e8371a9e7640f4a8 +0 -0
  24. data/test/dummy/tmp/cache/assets/CD0/FC0/sprockets%2Fbb2777627d42a216f3d9ced4322040b3 +0 -0
  25. data/test/dummy/tmp/cache/assets/CFA/E30/sprockets%2F35be1d26565dc0310c29f1a5e2f62f10 +0 -0
  26. data/test/dummy/tmp/cache/assets/D1D/FD0/sprockets%2Fa4a5ffe670666ce3d8d59179905201ef +0 -0
  27. data/test/dummy/tmp/cache/assets/D21/6B0/sprockets%2F9e242803fe56d6305274ff7e6487deda +0 -0
  28. data/test/dummy/tmp/cache/assets/D22/980/sprockets%2Fce6aa94ad2bc107104c0540f62c5128c +0 -0
  29. data/test/dummy/tmp/cache/assets/D3E/380/sprockets%2F434d98c8380bb9daf43810155aaf68ba +0 -0
  30. data/test/dummy/tmp/cache/assets/D66/940/sprockets%2F6151175b6ad4f9bab0c7e2b508e7b70f +0 -0
  31. data/test/dummy/tmp/cache/assets/D69/710/sprockets%2Ff67078d4b979a58c97feede196f6b385 +0 -0
  32. data/test/dummy/tmp/cache/assets/D95/C40/sprockets%2F09cb0a274209abf0391cbfce6ee67b82 +0 -0
  33. data/test/dummy/tmp/cache/assets/D9B/A30/sprockets%2Fc3436b3fe5da7c2456f26e2ae36da6c5 +0 -0
  34. data/test/dummy/tmp/cache/assets/D9F/400/sprockets%2F7f60332f86073dc8ed80b4c2a9dfcbe1 +0 -0
  35. data/test/dummy/tmp/cache/assets/DAE/8C0/sprockets%2Fa9b8f7bc5ca2efe658a13d7f4609c729 +0 -0
  36. data/test/dummy/tmp/cache/assets/DB3/0C0/sprockets%2F689fb998e2a7add3e00db88df254c87a +0 -0
  37. data/test/dummy/tmp/cache/assets/DD4/440/sprockets%2Fa33d646ac00d8bc87a4a496af6eed96f +0 -0
  38. data/test/dummy/tmp/cache/assets/DDB/890/sprockets%2F5ed566ca9fafd1b82373ffea2a8d8681 +0 -0
  39. data/test/models/console_session_test.rb +16 -75
  40. data/test/web_console/slave_test.rb +53 -0
  41. data/test/web_console_test.rb +51 -11
  42. data/vendor/assets/javascripts/vt100.js +4408 -0
  43. data/vendor/assets/stylesheets/vt100.css +272 -0
  44. metadata +23 -42
  45. data/lib/web_console/fiber.rb +0 -48
  46. data/lib/web_console/repl.rb +0 -59
  47. data/lib/web_console/repl/dummy.rb +0 -38
  48. data/lib/web_console/repl/irb.rb +0 -62
  49. data/lib/web_console/stream.rb +0 -30
  50. data/test/web_console/repl/dummy_test.rb +0 -54
  51. data/test/web_console/repl/irb_test.rb +0 -156
  52. data/test/web_console/repl_test.rb +0 -15
  53. data/vendor/assets/javascripts/jquery.console.js +0 -727
@@ -0,0 +1,53 @@
1
+ require 'stringio'
2
+ require 'test_helper'
3
+
4
+ class SlaveTest < ActiveSupport::TestCase
5
+ setup do
6
+ PTY.stubs(:spawn).returns([StringIO.new, StringIO.new, Random.rand(20000)])
7
+ @slave = WebConsole::Slave.new
8
+ end
9
+
10
+ test '#send_input raises ArgumentError on bad input' do
11
+ assert_raises(ArgumentError) { @slave.send_input(nil) }
12
+ assert_raises(ArgumentError) { @slave.send_input('') }
13
+ end
14
+
15
+ test '#pending_output returns nil on no pending output' do
16
+ @slave.stubs(:pending_output?).returns(false)
17
+ assert_nil @slave.pending_output
18
+ end
19
+
20
+ test '#pending_output returns a string with the current output' do
21
+ @slave.stubs(:pending_output?).returns(true)
22
+ @slave.instance_variable_get(:@output).stubs(:read_nonblock).returns('foo', nil)
23
+ assert_equal 'foo', @slave.pending_output
24
+ end
25
+
26
+ test '#pending_output always encodes output in UTF-8' do
27
+ @slave.stubs(:pending_output?).returns(true)
28
+ @slave.instance_variable_get(:@output).stubs(:read_nonblock).returns('foo', nil)
29
+ assert_equal Encoding::UTF_8, @slave.pending_output.encoding
30
+ end
31
+
32
+ test '#configure changes @input dimentions' do
33
+ @slave.instance_variable_get(:@input).expects(:winsize=).with([32, 64])
34
+ @slave.configure(height: 32, width: 64)
35
+ end
36
+
37
+ test '#configure only changes the @input dimentions if width is zero' do
38
+ @slave.instance_variable_get(:@input).expects(:winsize=).never
39
+ @slave.configure(height: 32, width: 0)
40
+ end
41
+
42
+ test '#configure only changes the @input dimentions if height is zero' do
43
+ @slave.instance_variable_get(:@input).expects(:winsize=).never
44
+ @slave.configure(height: 0, width: 64)
45
+ end
46
+
47
+ { dispose: :SIGTERM, dispose!: :SIGKILL }.each do |method, signal|
48
+ test "##{method} sends #{signal} to the process and detaches it" do
49
+ Process.expects(:kill).with(signal, @slave.pid)
50
+ @slave.send(method)
51
+ end
52
+ end
53
+ end
@@ -1,7 +1,7 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class WebConsoleTest < ActiveSupport::TestCase
4
- test 'different default_mount_path' do
4
+ test 'custom default_mount_path' do
5
5
  new_uninitialized_app do |app|
6
6
  app.config.web_console.default_mount_path = '/shell'
7
7
  app.initialize!
@@ -10,6 +10,33 @@ class WebConsoleTest < ActiveSupport::TestCase
10
10
  end
11
11
  end
12
12
 
13
+ test 'disabling automounting' do
14
+ new_uninitialized_app do |app|
15
+ app.config.web_console.automount = false
16
+ app.initialize!
17
+
18
+ refute app.routes.named_routes['web_console']
19
+ end
20
+ end
21
+
22
+ test 'blank commands are expanded to the rails console' do
23
+ new_uninitialized_app do |app|
24
+ app.config.web_console.command = ' '
25
+ app.initialize!
26
+
27
+ assert_equal 'rails_console', app.config.web_console.command
28
+ end
29
+ end
30
+
31
+ test 'present commands are not processed' do
32
+ new_uninitialized_app do |app|
33
+ app.config.web_console.command = '/bin/login'
34
+ app.initialize!
35
+
36
+ assert_equal '/bin/login', app.config.web_console.command
37
+ end
38
+ end
39
+
13
40
  test 'whitelisted ips are courced to IPAddr' do
14
41
  new_uninitialized_app do |app|
15
42
  app.config.web_console.whitelisted_ips = '127.0.0.1'
@@ -74,20 +101,33 @@ class WebConsoleTest < ActiveSupport::TestCase
74
101
  def new_uninitialized_app(root = File.expand_path('../dummy', __FILE__))
75
102
  skip if Rails::VERSION::MAJOR == 3
76
103
 
77
- FileUtils.mkdir_p root
78
- Dir.chdir root
79
-
80
104
  old_app = Rails.application
81
- Rails.application = nil
82
105
 
83
- app = Class.new(Rails::Application)
84
- app.config.eager_load = false
85
- app.config.time_zone = 'UTC'
86
- app.config.middleware ||= Rails::Configuration::MiddlewareStackProxy.new
87
- app.config.active_support.deprecation = :notify
106
+ FileUtils.mkdir_p(root)
107
+ Dir.chdir(root) do
108
+ Rails.application = nil
109
+
110
+ app = Class.new(Rails::Application)
111
+ app.config.eager_load = false
112
+ app.config.time_zone = 'UTC'
113
+ app.config.middleware ||= Rails::Configuration::MiddlewareStackProxy.new
114
+ app.config.active_support.deprecation = :notify
88
115
 
89
- yield app
116
+ yield app
117
+ end
90
118
  ensure
91
119
  Rails.application = old_app
92
120
  end
121
+
122
+ def teardown_fixtures(*)
123
+ super
124
+ rescue
125
+ # This is nasty hack to prevent a connection to the database in JRuby's
126
+ # activerecord-jdbcsqlite3-adapter. We don't really require a database
127
+ # connection, for the tests to run.
128
+ #
129
+ # The sad thing is that I couldn't figure out why does it only happens on
130
+ # activerecord-jdbcsqlite3-adapter and how to actually prevent it, rather
131
+ # than work-around it.
132
+ end
93
133
  end
@@ -0,0 +1,4408 @@
1
+ // VT100.js -- JavaScript based terminal emulator
2
+ // Copyright (C) 2008-2010 Markus Gutschke <markus@shellinabox.com>
3
+ //
4
+ // This program is free software; you can redistribute it and/or modify
5
+ // it under the terms of the GNU General Public License version 2 as
6
+ // published by the Free Software Foundation.
7
+ //
8
+ // This program is distributed in the hope that it will be useful,
9
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ // GNU General Public License for more details.
12
+ //
13
+ // You should have received a copy of the GNU General Public License along
14
+ // with this program; if not, write to the Free Software Foundation, Inc.,
15
+ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16
+ //
17
+ // In addition to these license terms, the author grants the following
18
+ // additional rights:
19
+ //
20
+ // If you modify this program, or any covered work, by linking or
21
+ // combining it with the OpenSSL project's OpenSSL library (or a
22
+ // modified version of that library), containing parts covered by the
23
+ // terms of the OpenSSL or SSLeay licenses, the author
24
+ // grants you additional permission to convey the resulting work.
25
+ // Corresponding Source for a non-source form of such a combination
26
+ // shall include the source code for the parts of OpenSSL used as well
27
+ // as that of the covered work.
28
+ //
29
+ // You may at your option choose to remove this additional permission from
30
+ // the work, or from any part of it.
31
+ //
32
+ // It is possible to build this program in a way that it loads OpenSSL
33
+ // libraries at run-time. If doing so, the following notices are required
34
+ // by the OpenSSL and SSLeay licenses:
35
+ //
36
+ // This product includes software developed by the OpenSSL Project
37
+ // for use in the OpenSSL Toolkit. (http://www.openssl.org/)
38
+ //
39
+ // This product includes cryptographic software written by Eric Young
40
+ // (eay@cryptsoft.com)
41
+ //
42
+ //
43
+ // The most up-to-date version of this program is always available from
44
+ // http://shellinabox.com
45
+ //
46
+ //
47
+ // Notes:
48
+ //
49
+ // The author believes that for the purposes of this license, you meet the
50
+ // requirements for publishing the source code, if your web server publishes
51
+ // the source in unmodified form (i.e. with licensing information, comments,
52
+ // formatting, and identifier names intact). If there are technical reasons
53
+ // that require you to make changes to the source code when serving the
54
+ // JavaScript (e.g to remove pre-processor directives from the source), these
55
+ // changes should be done in a reversible fashion.
56
+ //
57
+ // The author does not consider websites that reference this script in
58
+ // unmodified form, and web servers that serve this script in unmodified form
59
+ // to be derived works. As such, they are believed to be outside of the
60
+ // scope of this license and not subject to the rights or restrictions of the
61
+ // GNU General Public License.
62
+ //
63
+ // If in doubt, consult a legal professional familiar with the laws that
64
+ // apply in your country.
65
+
66
+ // #define ESnormal 0
67
+ // #define ESesc 1
68
+ // #define ESsquare 2
69
+ // #define ESgetpars 3
70
+ // #define ESgotpars 4
71
+ // #define ESdeviceattr 5
72
+ // #define ESfunckey 6
73
+ // #define EShash 7
74
+ // #define ESsetG0 8
75
+ // #define ESsetG1 9
76
+ // #define ESsetG2 10
77
+ // #define ESsetG3 11
78
+ // #define ESbang 12
79
+ // #define ESpercent 13
80
+ // #define ESignore 14
81
+ // #define ESnonstd 15
82
+ // #define ESpalette 16
83
+ // #define EStitle 17
84
+ // #define ESss2 18
85
+ // #define ESss3 19
86
+
87
+ // #define ATTR_DEFAULT 0x00F0
88
+ // #define ATTR_REVERSE 0x0100
89
+ // #define ATTR_UNDERLINE 0x0200
90
+ // #define ATTR_DIM 0x0400
91
+ // #define ATTR_BRIGHT 0x0800
92
+ // #define ATTR_BLINK 0x1000
93
+
94
+ // #define MOUSE_DOWN 0
95
+ // #define MOUSE_UP 1
96
+ // #define MOUSE_CLICK 2
97
+
98
+ function VT100(container) {
99
+ if (typeof linkifyURLs == 'undefined' || linkifyURLs <= 0) {
100
+ this.urlRE = null;
101
+ } else {
102
+ this.urlRE = new RegExp(
103
+ // Known URL protocol are "http", "https", and "ftp".
104
+ '(?:http|https|ftp)://' +
105
+
106
+ // Optionally allow username and passwords.
107
+ '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
108
+
109
+ // Hostname.
110
+ '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
111
+ '[0-9a-fA-F]{0,4}(?::{1,2}[0-9a-fA-F]{1,4})+|' +
112
+ '(?!-)[^[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+)' +
113
+
114
+ // Port
115
+ '(?::[1-9][0-9]*)?' +
116
+
117
+ // Path.
118
+ '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|' +
119
+
120
+ (linkifyURLs <= 1 ? '' :
121
+ // Also support URLs without a protocol (assume "http").
122
+ // Optional username and password.
123
+ '(?:[^:@/ \u00A0]*(?::[^@/ \u00A0]*)?@)?' +
124
+
125
+ // Hostnames must end with a well-known top-level domain or must be
126
+ // numeric.
127
+ '(?:[1-9][0-9]{0,2}(?:[.][1-9][0-9]{0,2}){3}|' +
128
+ 'localhost|' +
129
+ '(?:(?!-)' +
130
+ '[^.[!"#$%&\'()*+,/:;<=>?@\\^_`{|}~\u0000- \u007F-\u00A0]+[.]){2,}' +
131
+ '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
132
+ 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
133
+ 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
134
+ 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
135
+ 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
136
+ 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
137
+ 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
138
+ 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
139
+ 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
140
+ 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
141
+ 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
142
+ 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
143
+ 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+))' +
144
+
145
+ // Port
146
+ '(?::[1-9][0-9]{0,4})?' +
147
+
148
+ // Path.
149
+ '(?:/(?:(?![/ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)*|') +
150
+
151
+ // In addition, support e-mail address. Optionally, recognize "mailto:"
152
+ '(?:mailto:)' + (linkifyURLs <= 1 ? '' : '?') +
153
+
154
+ // Username:
155
+ '[-_.+a-zA-Z0-9]+@' +
156
+
157
+ // Hostname.
158
+ '(?!-)[-a-zA-Z0-9]+(?:[.](?!-)[-a-zA-Z0-9]+)?[.]' +
159
+ '(?:(?:com|net|org|edu|gov|aero|asia|biz|cat|coop|info|int|jobs|mil|mobi|'+
160
+ 'museum|name|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|' +
161
+ 'au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|' +
162
+ 'ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|' +
163
+ 'dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|' +
164
+ 'gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|' +
165
+ 'ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|' +
166
+ 'lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|' +
167
+ 'mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|' +
168
+ 'pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|' +
169
+ 'sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|' +
170
+ 'tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|' +
171
+ 'yu|za|zm|zw|arpa)(?![a-zA-Z0-9])|[Xx][Nn]--[-a-zA-Z0-9]+)' +
172
+
173
+ // Optional arguments
174
+ '(?:[?](?:(?![ \u00A0]|[,.)}"\u0027!]+[ \u00A0]|[,.)}"\u0027!]+$).)*)?');
175
+ }
176
+ this.getUserSettings();
177
+ this.initializeElements(container);
178
+ this.maxScrollbackLines = 500;
179
+ this.npar = 0;
180
+ this.par = [ ];
181
+ this.isQuestionMark = false;
182
+ this.savedX = [ ];
183
+ this.savedY = [ ];
184
+ this.savedAttr = [ ];
185
+ this.savedUseGMap = 0;
186
+ this.savedGMap = [ this.Latin1Map, this.VT100GraphicsMap,
187
+ this.CodePage437Map, this.DirectToFontMap ];
188
+ this.savedValid = [ ];
189
+ this.respondString = '';
190
+ this.titleString = '';
191
+ this.internalClipboard = undefined;
192
+ this.reset(true);
193
+ }
194
+
195
+ VT100.prototype.reset = function(clearHistory) {
196
+ this.isEsc = 0 /* ESnormal */;
197
+ this.needWrap = false;
198
+ this.autoWrapMode = true;
199
+ this.dispCtrl = false;
200
+ this.toggleMeta = false;
201
+ this.insertMode = false;
202
+ this.applKeyMode = false;
203
+ this.cursorKeyMode = false;
204
+ this.crLfMode = false;
205
+ this.offsetMode = false;
206
+ this.mouseReporting = false;
207
+ this.printing = false;
208
+ if (typeof this.printWin != 'undefined' &&
209
+ this.printWin && !this.printWin.closed) {
210
+ this.printWin.close();
211
+ }
212
+ this.printWin = null;
213
+ this.utfEnabled = this.utfPreferred;
214
+ this.utfCount = 0;
215
+ this.utfChar = 0;
216
+ this.color = 'ansi0 bgAnsi15';
217
+ this.style = '';
218
+ this.attr = 0x00F0 /* ATTR_DEFAULT */;
219
+ this.useGMap = 0;
220
+ this.GMap = [ this.Latin1Map,
221
+ this.VT100GraphicsMap,
222
+ this.CodePage437Map,
223
+ this.DirectToFontMap];
224
+ this.translate = this.GMap[this.useGMap];
225
+ this.top = 0;
226
+ this.bottom = this.terminalHeight;
227
+ this.lastCharacter = ' ';
228
+ this.userTabStop = [ ];
229
+
230
+ if (clearHistory) {
231
+ for (var i = 0; i < 2; i++) {
232
+ while (this.console[i].firstChild) {
233
+ this.console[i].removeChild(this.console[i].firstChild);
234
+ }
235
+ }
236
+ }
237
+
238
+ this.enableAlternateScreen(false);
239
+
240
+ var wasCompressed = false;
241
+ var transform = this.getTransformName();
242
+ if (transform) {
243
+ for (var i = 0; i < 2; ++i) {
244
+ wasCompressed |= this.console[i].style[transform] != '';
245
+ this.console[i].style[transform] = '';
246
+ }
247
+ this.cursor.style[transform] = '';
248
+ this.space.style[transform] = '';
249
+ if (transform == 'filter') {
250
+ this.console[this.currentScreen].style.width = '';
251
+ }
252
+ }
253
+ this.scale = 1.0;
254
+ if (wasCompressed) {
255
+ this.resizer();
256
+ }
257
+
258
+ this.gotoXY(0, 0);
259
+ this.showCursor();
260
+ this.isInverted = false;
261
+ this.refreshInvertedState();
262
+ this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
263
+ this.color, this.style);
264
+ };
265
+
266
+ VT100.prototype.addListener = function(elem, event, listener) {
267
+ try {
268
+ if (elem.addEventListener) {
269
+ elem.addEventListener(event, listener, false);
270
+ } else {
271
+ elem.attachEvent('on' + event, listener);
272
+ }
273
+ } catch (e) {
274
+ }
275
+ };
276
+
277
+ VT100.prototype.getUserSettings = function() {
278
+ // Compute hash signature to identify the entries in the userCSS menu.
279
+ // If the menu is unchanged from last time, default values can be
280
+ // looked up in a cookie associated with this page.
281
+ this.signature = 3;
282
+ this.utfPreferred = true;
283
+ this.visualBell = typeof disableAudio != 'undefined' &&
284
+ disableAudio;
285
+ this.autoprint = true;
286
+ this.softKeyboard = false;
287
+ this.blinkingCursor = true;
288
+ if (this.visualBell) {
289
+ this.signature = Math.floor(16807*this.signature + 1) %
290
+ ((1 << 31) - 1);
291
+ }
292
+ if (typeof userCSSList != 'undefined') {
293
+ for (var i = 0; i < userCSSList.length; ++i) {
294
+ var label = userCSSList[i][0];
295
+ for (var j = 0; j < label.length; ++j) {
296
+ this.signature = Math.floor(16807*this.signature+
297
+ label.charCodeAt(j)) %
298
+ ((1 << 31) - 1);
299
+ }
300
+ if (userCSSList[i][1]) {
301
+ this.signature = Math.floor(16807*this.signature + 1) %
302
+ ((1 << 31) - 1);
303
+ }
304
+ }
305
+ }
306
+
307
+ var key = 'shellInABox=' + this.signature + ':';
308
+ var settings = document.cookie.indexOf(key);
309
+ if (settings >= 0) {
310
+ settings = document.cookie.substr(settings + key.length).
311
+ replace(/([0-1]*).*/, "$1");
312
+ if (settings.length == 5 + (typeof userCSSList == 'undefined' ?
313
+ 0 : userCSSList.length)) {
314
+ this.utfPreferred = settings.charAt(0) != '0';
315
+ this.visualBell = settings.charAt(1) != '0';
316
+ this.autoprint = settings.charAt(2) != '0';
317
+ this.softKeyboard = settings.charAt(3) != '0';
318
+ this.blinkingCursor = settings.charAt(4) != '0';
319
+ if (typeof userCSSList != 'undefined') {
320
+ for (var i = 0; i < userCSSList.length; ++i) {
321
+ userCSSList[i][2] = settings.charAt(i + 5) != '0';
322
+ }
323
+ }
324
+ }
325
+ }
326
+ this.utfEnabled = this.utfPreferred;
327
+ };
328
+
329
+ VT100.prototype.storeUserSettings = function() {
330
+ var settings = 'shellInABox=' + this.signature + ':' +
331
+ (this.utfEnabled ? '1' : '0') +
332
+ (this.visualBell ? '1' : '0') +
333
+ (this.autoprint ? '1' : '0') +
334
+ (this.softKeyboard ? '1' : '0') +
335
+ (this.blinkingCursor ? '1' : '0');
336
+ if (typeof userCSSList != 'undefined') {
337
+ for (var i = 0; i < userCSSList.length; ++i) {
338
+ settings += userCSSList[i][2] ? '1' : '0';
339
+ }
340
+ }
341
+ var d = new Date();
342
+ d.setDate(d.getDate() + 3653);
343
+ document.cookie = settings + ';expires=' + d.toGMTString();
344
+ };
345
+
346
+ VT100.prototype.initializeUserCSSStyles = function() {
347
+ this.usercssActions = [];
348
+ if (typeof userCSSList != 'undefined') {
349
+ var menu = '';
350
+ var group = '';
351
+ var wasSingleSel = 1;
352
+ var beginOfGroup = 0;
353
+ for (var i = 0; i <= userCSSList.length; ++i) {
354
+ if (i < userCSSList.length) {
355
+ var label = userCSSList[i][0];
356
+ var newGroup = userCSSList[i][1];
357
+ var enabled = userCSSList[i][2];
358
+
359
+ // Add user style sheet to document
360
+ var style = document.createElement('link');
361
+ var id = document.createAttribute('id');
362
+ id.nodeValue = 'usercss-' + i;
363
+ style.setAttributeNode(id);
364
+ var rel = document.createAttribute('rel');
365
+ rel.nodeValue = 'stylesheet';
366
+ style.setAttributeNode(rel);
367
+ var href = document.createAttribute('href');
368
+ href.nodeValue = 'usercss-' + i + '.css';
369
+ style.setAttributeNode(href);
370
+ var type = document.createAttribute('type');
371
+ type.nodeValue = 'text/css';
372
+ style.setAttributeNode(type);
373
+ document.getElementsByTagName('head')[0].appendChild(style);
374
+ style.disabled = !enabled;
375
+ }
376
+
377
+ // Add entry to menu
378
+ if (newGroup || i == userCSSList.length) {
379
+ if (beginOfGroup != 0 && (i - beginOfGroup > 1 || !wasSingleSel)) {
380
+ // The last group had multiple entries that are mutually exclusive;
381
+ // or the previous to last group did. In either case, we need to
382
+ // append a "<hr />" before we can add the last group to the menu.
383
+ menu += '<hr />';
384
+ }
385
+ wasSingleSel = i - beginOfGroup < 1;
386
+ menu += group;
387
+ group = '';
388
+
389
+ for (var j = beginOfGroup; j < i; ++j) {
390
+ this.usercssActions[this.usercssActions.length] =
391
+ function(vt100, current, begin, count) {
392
+
393
+ // Deselect all other entries in the group, then either select
394
+ // (for multiple entries in group) or toggle (for on/off entry)
395
+ // the current entry.
396
+ return function() {
397
+ var entry = vt100.getChildById(vt100.menu,
398
+ 'beginusercss');
399
+ var i = -1;
400
+ var j = -1;
401
+ for (var c = count; c > 0; ++j) {
402
+ if (entry.tagName == 'LI') {
403
+ if (++i >= begin) {
404
+ --c;
405
+ var label = vt100.usercss.childNodes[j];
406
+
407
+ // Restore label to just the text content
408
+ if (typeof label.textContent == 'undefined') {
409
+ var s = label.innerText;
410
+ label.innerHTML = '';
411
+ label.appendChild(document.createTextNode(s));
412
+ } else {
413
+ label.textContent= label.textContent;
414
+ }
415
+
416
+ // User style sheets are numbered sequentially
417
+ var sheet = document.getElementById(
418
+ 'usercss-' + i);
419
+ if (i == current) {
420
+ if (count == 1) {
421
+ sheet.disabled = !sheet.disabled;
422
+ } else {
423
+ sheet.disabled = false;
424
+ }
425
+ if (!sheet.disabled) {
426
+ label.innerHTML= '<img src="enabled.gif" />' +
427
+ label.innerHTML;
428
+ }
429
+ } else {
430
+ sheet.disabled = true;
431
+ }
432
+ userCSSList[i][2] = !sheet.disabled;
433
+ }
434
+ }
435
+ entry = entry.nextSibling;
436
+ }
437
+
438
+ // If the font size changed, adjust cursor and line dimensions
439
+ this.cursor.style.cssText= '';
440
+ this.cursorWidth = this.cursor.clientWidth;
441
+ this.cursorHeight = this.lineheight.clientHeight;
442
+ for (i = 0; i < this.console.length; ++i) {
443
+ for (var line = this.console[i].firstChild; line;
444
+ line = line.nextSibling) {
445
+ line.style.height = this.cursorHeight + 'px';
446
+ }
447
+ }
448
+ vt100.resizer();
449
+ };
450
+ }(this, j, beginOfGroup, i - beginOfGroup);
451
+ }
452
+
453
+ if (i == userCSSList.length) {
454
+ break;
455
+ }
456
+
457
+ beginOfGroup = i;
458
+ }
459
+ // Collect all entries in a group, before attaching them to the menu.
460
+ // This is necessary as we don't know whether this is a group of
461
+ // mutually exclusive options (which should be separated by "<hr />" on
462
+ // both ends), or whether this is a on/off toggle, which can be grouped
463
+ // together with other on/off options.
464
+ group +=
465
+ '<li>' + (enabled ? '<img src="enabled.gif" />' : '') +
466
+ label +
467
+ '</li>';
468
+ }
469
+ this.usercss.innerHTML = menu;
470
+ }
471
+ };
472
+
473
+ VT100.prototype.resetLastSelectedKey = function(e) {
474
+ var key = this.lastSelectedKey;
475
+ if (!key) {
476
+ return false;
477
+ }
478
+
479
+ var position = this.mousePosition(e);
480
+
481
+ // We don't get all the necessary events to reliably reselect a key
482
+ // if we moved away from it and then back onto it. We approximate the
483
+ // behavior by remembering the key until either we release the mouse
484
+ // button (we might never get this event if the mouse has since left
485
+ // the window), or until we move away too far.
486
+ var box = this.keyboard.firstChild;
487
+ if (position[0] < box.offsetLeft + key.offsetWidth ||
488
+ position[1] < box.offsetTop + key.offsetHeight ||
489
+ position[0] >= box.offsetLeft + box.offsetWidth - key.offsetWidth ||
490
+ position[1] >= box.offsetTop + box.offsetHeight - key.offsetHeight ||
491
+ position[0] < box.offsetLeft + key.offsetLeft - key.offsetWidth ||
492
+ position[1] < box.offsetTop + key.offsetTop - key.offsetHeight ||
493
+ position[0] >= box.offsetLeft + key.offsetLeft + 2*key.offsetWidth ||
494
+ position[1] >= box.offsetTop + key.offsetTop + 2*key.offsetHeight) {
495
+ if (this.lastSelectedKey.className) log.console('reset: deselecting');
496
+ this.lastSelectedKey.className = '';
497
+ this.lastSelectedKey = undefined;
498
+ }
499
+ return false;
500
+ };
501
+
502
+ VT100.prototype.showShiftState = function(state) {
503
+ var style = document.getElementById('shift_state');
504
+ if (state) {
505
+ this.setTextContentRaw(style,
506
+ '#vt100 #keyboard .shifted {' +
507
+ 'display: inline }' +
508
+ '#vt100 #keyboard .unshifted {' +
509
+ 'display: none }');
510
+ } else {
511
+ this.setTextContentRaw(style, '');
512
+ }
513
+ var elems = this.keyboard.getElementsByTagName('I');
514
+ for (var i = 0; i < elems.length; ++i) {
515
+ if (elems[i].id == '16') {
516
+ elems[i].className = state ? 'selected' : '';
517
+ }
518
+ }
519
+ };
520
+
521
+ VT100.prototype.showCtrlState = function(state) {
522
+ var ctrl = this.getChildById(this.keyboard, '17' /* Ctrl */);
523
+ if (ctrl) {
524
+ ctrl.className = state ? 'selected' : '';
525
+ }
526
+ };
527
+
528
+ VT100.prototype.showAltState = function(state) {
529
+ var alt = this.getChildById(this.keyboard, '18' /* Alt */);
530
+ if (alt) {
531
+ alt.className = state ? 'selected' : '';
532
+ }
533
+ };
534
+
535
+ VT100.prototype.clickedKeyboard = function(e, elem, ch, key, shift, ctrl, alt){
536
+ var fake = [ ];
537
+ fake.charCode = ch;
538
+ fake.keyCode = key;
539
+ fake.ctrlKey = ctrl;
540
+ fake.shiftKey = shift;
541
+ fake.altKey = alt;
542
+ fake.metaKey = alt;
543
+ return this.handleKey(fake);
544
+ };
545
+
546
+ VT100.prototype.addKeyBinding = function(elem, ch, key, CH, KEY) {
547
+ if (elem == undefined) {
548
+ return;
549
+ }
550
+ if (ch == '\u00A0') {
551
+ // &nbsp; should be treated as a regular space character.
552
+ ch = ' ';
553
+ }
554
+ if (ch != undefined && CH == undefined) {
555
+ // For letter keys, we automatically compute the uppercase character code
556
+ // from the lowercase one.
557
+ CH = ch.toUpperCase();
558
+ }
559
+ if (KEY == undefined && key != undefined) {
560
+ // Most keys have identically key codes for both lowercase and uppercase
561
+ // keypresses. Normally, only function keys would have distinct key codes,
562
+ // whereas regular keys have character codes.
563
+ KEY = key;
564
+ } else if (KEY == undefined && CH != undefined) {
565
+ // For regular keys, copy the character code to the key code.
566
+ KEY = CH.charCodeAt(0);
567
+ }
568
+ if (key == undefined && ch != undefined) {
569
+ // For regular keys, copy the character code to the key code.
570
+ key = ch.charCodeAt(0);
571
+ }
572
+ // Convert characters to numeric character codes. If the character code
573
+ // is undefined (i.e. this is a function key), set it to zero.
574
+ ch = ch ? ch.charCodeAt(0) : 0;
575
+ CH = CH ? CH.charCodeAt(0) : 0;
576
+
577
+ // Mouse down events high light the key. We also set lastSelectedKey. This
578
+ // is needed to that mouseout/mouseover can keep track of the key that
579
+ // is currently being clicked.
580
+ this.addListener(elem, 'mousedown',
581
+ function(vt100, elem, key) { return function(e) {
582
+ if ((e.which || e.button) == 1) {
583
+ if (vt100.lastSelectedKey) {
584
+ vt100.lastSelectedKey.className= '';
585
+ }
586
+ // Highlight the key while the mouse button is held down.
587
+ if (key == 16 /* Shift */) {
588
+ if (!elem.className != vt100.isShift) {
589
+ vt100.showShiftState(!vt100.isShift);
590
+ }
591
+ } else if (key == 17 /* Ctrl */) {
592
+ if (!elem.className != vt100.isCtrl) {
593
+ vt100.showCtrlState(!vt100.isCtrl);
594
+ }
595
+ } else if (key == 18 /* Alt */) {
596
+ if (!elem.className != vt100.isAlt) {
597
+ vt100.showAltState(!vt100.isAlt);
598
+ }
599
+ } else {
600
+ elem.className = 'selected';
601
+ }
602
+ vt100.lastSelectedKey = elem;
603
+ }
604
+ return false; }; }(this, elem, key));
605
+ var clicked =
606
+ // Modifier keys update the state of the keyboard, but do not generate
607
+ // any key clicks that get forwarded to the application.
608
+ key >= 16 /* Shift */ && key <= 18 /* Alt */ ?
609
+ function(vt100, elem) { return function(e) {
610
+ if (elem == vt100.lastSelectedKey) {
611
+ if (key == 16 /* Shift */) {
612
+ // The user clicked the Shift key
613
+ vt100.isShift = !vt100.isShift;
614
+ vt100.showShiftState(vt100.isShift);
615
+ } else if (key == 17 /* Ctrl */) {
616
+ vt100.isCtrl = !vt100.isCtrl;
617
+ vt100.showCtrlState(vt100.isCtrl);
618
+ } else if (key == 18 /* Alt */) {
619
+ vt100.isAlt = !vt100.isAlt;
620
+ vt100.showAltState(vt100.isAlt);
621
+ }
622
+ vt100.lastSelectedKey = undefined;
623
+ }
624
+ if (vt100.lastSelectedKey) {
625
+ vt100.lastSelectedKey.className = '';
626
+ vt100.lastSelectedKey = undefined;
627
+ }
628
+ return false; }; }(this, elem) :
629
+ // Regular keys generate key clicks, when the mouse button is released or
630
+ // when a mouse click event is received.
631
+ function(vt100, elem, ch, key, CH, KEY) { return function(e) {
632
+ if (vt100.lastSelectedKey) {
633
+ if (elem == vt100.lastSelectedKey) {
634
+ // The user clicked a key.
635
+ if (vt100.isShift) {
636
+ vt100.clickedKeyboard(e, elem, CH, KEY,
637
+ true, vt100.isCtrl, vt100.isAlt);
638
+ } else {
639
+ vt100.clickedKeyboard(e, elem, ch, key,
640
+ false, vt100.isCtrl, vt100.isAlt);
641
+ }
642
+ vt100.isShift = false;
643
+ vt100.showShiftState(false);
644
+ vt100.isCtrl = false;
645
+ vt100.showCtrlState(false);
646
+ vt100.isAlt = false;
647
+ vt100.showAltState(false);
648
+ }
649
+ vt100.lastSelectedKey.className = '';
650
+ vt100.lastSelectedKey = undefined;
651
+ }
652
+ elem.className = '';
653
+ return false; }; }(this, elem, ch, key, CH, KEY);
654
+ this.addListener(elem, 'mouseup', clicked);
655
+ this.addListener(elem, 'click', clicked);
656
+
657
+ // When moving the mouse away from a key, check if any keys need to be
658
+ // deselected.
659
+ this.addListener(elem, 'mouseout',
660
+ function(vt100, elem, key) { return function(e) {
661
+ if (key == 16 /* Shift */) {
662
+ if (!elem.className == vt100.isShift) {
663
+ vt100.showShiftState(vt100.isShift);
664
+ }
665
+ } else if (key == 17 /* Ctrl */) {
666
+ if (!elem.className == vt100.isCtrl) {
667
+ vt100.showCtrlState(vt100.isCtrl);
668
+ }
669
+ } else if (key == 18 /* Alt */) {
670
+ if (!elem.className == vt100.isAlt) {
671
+ vt100.showAltState(vt100.isAlt);
672
+ }
673
+ } else if (elem.className) {
674
+ elem.className = '';
675
+ vt100.lastSelectedKey = elem;
676
+ } else if (vt100.lastSelectedKey) {
677
+ vt100.resetLastSelectedKey(e);
678
+ }
679
+ return false; }; }(this, elem, key));
680
+
681
+ // When moving the mouse over a key, select it if the user is still holding
682
+ // the mouse button down (i.e. elem == lastSelectedKey)
683
+ this.addListener(elem, 'mouseover',
684
+ function(vt100, elem, key) { return function(e) {
685
+ if (elem == vt100.lastSelectedKey) {
686
+ if (key == 16 /* Shift */) {
687
+ if (!elem.className != vt100.isShift) {
688
+ vt100.showShiftState(!vt100.isShift);
689
+ }
690
+ } else if (key == 17 /* Ctrl */) {
691
+ if (!elem.className != vt100.isCtrl) {
692
+ vt100.showCtrlState(!vt100.isCtrl);
693
+ }
694
+ } else if (key == 18 /* Alt */) {
695
+ if (!elem.className != vt100.isAlt) {
696
+ vt100.showAltState(!vt100.isAlt);
697
+ }
698
+ } else if (!elem.className) {
699
+ elem.className = 'selected';
700
+ }
701
+ } else {
702
+ vt100.resetLastSelectedKey(e);
703
+ }
704
+ return false; }; }(this, elem, key));
705
+ };
706
+
707
+ VT100.prototype.initializeKeyBindings = function(elem) {
708
+ if (elem) {
709
+ if (elem.nodeName == "I" || elem.nodeName == "B") {
710
+ if (elem.id) {
711
+ // Function keys. The Javascript keycode is part of the "id"
712
+ var i = parseInt(elem.id);
713
+ if (i) {
714
+ // If the id does not parse as a number, it is not a keycode.
715
+ this.addKeyBinding(elem, undefined, i);
716
+ }
717
+ } else {
718
+ var child = elem.firstChild;
719
+ if (child) {
720
+ if (child.nodeName == "#text") {
721
+ // If the key only has a text node as a child, then it is a letter.
722
+ // Automatically compute the lower and upper case version of the
723
+ // key.
724
+ var text = this.getTextContent(child) ||
725
+ this.getTextContent(elem);
726
+ this.addKeyBinding(elem, text.toLowerCase());
727
+ } else if (child.nextSibling) {
728
+ // If the key has two children, they are the lower and upper case
729
+ // character code, respectively.
730
+ this.addKeyBinding(elem, this.getTextContent(child), undefined,
731
+ this.getTextContent(child.nextSibling));
732
+ }
733
+ }
734
+ }
735
+ }
736
+ }
737
+ // Recursively parse all other child nodes.
738
+ for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
739
+ this.initializeKeyBindings(elem);
740
+ }
741
+ };
742
+
743
+ VT100.prototype.initializeKeyboardButton = function() {
744
+ // Configure mouse event handlers for button that displays/hides keyboard
745
+ this.addListener(this.keyboardImage, 'click',
746
+ function(vt100) { return function(e) {
747
+ if (vt100.keyboard.style.display != '') {
748
+ if (vt100.reconnectBtn.style.visibility != '') {
749
+ vt100.initializeKeyboard();
750
+ vt100.showSoftKeyboard();
751
+ }
752
+ } else {
753
+ vt100.hideSoftKeyboard();
754
+ vt100.input.focus();
755
+ }
756
+ return false; }; }(this));
757
+
758
+ // Enable button that displays keyboard
759
+ if (this.softKeyboard) {
760
+ this.keyboardImage.style.visibility = 'visible';
761
+ }
762
+ };
763
+
764
+ VT100.prototype.initializeKeyboard = function() {
765
+ // Only need to initialize the keyboard the very first time. When doing so,
766
+ // copy the keyboard layout from the iframe.
767
+ if (this.keyboard.firstChild) {
768
+ return;
769
+ }
770
+ this.keyboard.innerHTML =
771
+ this.layout.contentDocument.body.innerHTML;
772
+ var box = this.keyboard.firstChild;
773
+ this.hideSoftKeyboard();
774
+
775
+ // Configure mouse event handlers for on-screen keyboard
776
+ this.addListener(this.keyboard, 'click',
777
+ function(vt100) { return function(e) {
778
+ vt100.hideSoftKeyboard();
779
+ vt100.input.focus();
780
+ return false; }; }(this));
781
+ this.addListener(this.keyboard, 'selectstart', this.cancelEvent);
782
+ this.addListener(box, 'click', this.cancelEvent);
783
+ this.addListener(box, 'mouseup',
784
+ function(vt100) { return function(e) {
785
+ if (vt100.lastSelectedKey) {
786
+ vt100.lastSelectedKey.className = '';
787
+ vt100.lastSelectedKey = undefined;
788
+ }
789
+ return false; }; }(this));
790
+ this.addListener(box, 'mouseout',
791
+ function(vt100) { return function(e) {
792
+ return vt100.resetLastSelectedKey(e); }; }(this));
793
+ this.addListener(box, 'mouseover',
794
+ function(vt100) { return function(e) {
795
+ return vt100.resetLastSelectedKey(e); }; }(this));
796
+
797
+ // Configure SHIFT key behavior
798
+ var style = document.createElement('style');
799
+ var id = document.createAttribute('id');
800
+ id.nodeValue = 'shift_state';
801
+ style.setAttributeNode(id);
802
+ var type = document.createAttribute('type');
803
+ type.nodeValue = 'text/css';
804
+ style.setAttributeNode(type);
805
+ document.getElementsByTagName('head')[0].appendChild(style);
806
+
807
+ // Set up key bindings
808
+ this.initializeKeyBindings(box);
809
+ };
810
+
811
+ VT100.prototype.initializeElements = function(container) {
812
+ // If the necessary objects have not already been defined in the HTML
813
+ // page, create them now.
814
+ if (container) {
815
+ this.container = container;
816
+ } else if (!(this.container = document.getElementById('vt100'))) {
817
+ this.container = document.createElement('div');
818
+ this.container.id = 'vt100';
819
+ document.body.appendChild(this.container);
820
+ }
821
+
822
+ if (!this.getChildById(this.container, 'reconnect') ||
823
+ !this.getChildById(this.container, 'menu') ||
824
+ !this.getChildById(this.container, 'keyboard') ||
825
+ !this.getChildById(this.container, 'kbd_button') ||
826
+ !this.getChildById(this.container, 'kbd_img') ||
827
+ !this.getChildById(this.container, 'layout') ||
828
+ !this.getChildById(this.container, 'scrollable') ||
829
+ !this.getChildById(this.container, 'console') ||
830
+ !this.getChildById(this.container, 'alt_console') ||
831
+ !this.getChildById(this.container, 'ieprobe') ||
832
+ !this.getChildById(this.container, 'padding') ||
833
+ !this.getChildById(this.container, 'cursor') ||
834
+ !this.getChildById(this.container, 'lineheight') ||
835
+ !this.getChildById(this.container, 'usercss') ||
836
+ !this.getChildById(this.container, 'space') ||
837
+ !this.getChildById(this.container, 'input') ||
838
+ !this.getChildById(this.container, 'cliphelper')) {
839
+ // Only enable the "embed" object, if we have a suitable plugin. Otherwise,
840
+ // we might get a pointless warning that a suitable plugin is not yet
841
+ // installed. If in doubt, we'd rather just stay silent.
842
+ var embed = '';
843
+ try {
844
+ if (typeof navigator.mimeTypes["audio/x-wav"].enabledPlugin.name !=
845
+ 'undefined') {
846
+ embed = this.disableAudio ? "" :
847
+ '<embed classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" ' +
848
+ 'id="beep_embed" ' +
849
+ 'src="beep.wav" ' +
850
+ 'autostart="false" ' +
851
+ 'volume="100" ' +
852
+ 'enablejavascript="true" ' +
853
+ 'type="audio/x-wav" ' +
854
+ 'height="16" ' +
855
+ 'width="200" ' +
856
+ 'style="position:absolute;left:-1000px;top:-1000px" />';
857
+ }
858
+ } catch (e) {
859
+ }
860
+
861
+ this.container.innerHTML =
862
+ '<div id="reconnect" style="visibility: hidden">' +
863
+ '<input type="button" value="Connect" ' +
864
+ 'onsubmit="return false" />' +
865
+ '</div>' +
866
+ '<div id="cursize" style="visibility: hidden">' +
867
+ '</div>' +
868
+ '<div id="menu"></div>' +
869
+ (!this.softKeyboard ? "" :
870
+ '<div id="keyboard" unselectable="on">' +
871
+ '</div>') +
872
+ '<div id="scrollable">' +
873
+ (!this.softKeyboard ? "" :
874
+ '<table id="kbd_button">' +
875
+ '<tr><td width="100%">&nbsp;</td>' +
876
+ '<td><img id="kbd_img" src="keyboard.png" /></td>' +
877
+ '<td>&nbsp;&nbsp;&nbsp;&nbsp;</td></tr>') +
878
+ '</table>' +
879
+ '<pre id="lineheight">&nbsp;</pre>' +
880
+ '<pre id="console">' +
881
+ '<pre></pre>' +
882
+ '<div id="ieprobe"><span>&nbsp;</span></div>' +
883
+ '</pre>' +
884
+ '<pre id="alt_console" style="display: none"></pre>' +
885
+ '<div id="padding"></div>' +
886
+ '<pre id="cursor">&nbsp;</pre>' +
887
+ '</div>' +
888
+ '<div class="hidden">' +
889
+ '<div id="usercss"></div>' +
890
+ '<pre><div><span id="space"></span></div></pre>' +
891
+ '<input type="textfield" id="input" />' +
892
+ '<input type="textfield" id="cliphelper" />' +
893
+ (this.disableAudio ? "" : embed +
894
+ '<bgsound id="beep_bgsound" loop=1 />') +
895
+ (!this.softKeyboard ? "" :
896
+ '<iframe id="layout" src="keyboard.html" />') +
897
+ '</div>';
898
+ }
899
+
900
+ // Find the object used for playing the "beep" sound, if any.
901
+ if (this.disableAudio) {
902
+ this.beeper = undefined;
903
+ } else {
904
+ this.beeper = this.getChildById(this.container,
905
+ 'beep_embed');
906
+ if (!this.beeper || !this.beeper.Play) {
907
+ this.beeper = this.getChildById(this.container,
908
+ 'beep_bgsound');
909
+ if (!this.beeper || typeof this.beeper.src == 'undefined') {
910
+ this.beeper = undefined;
911
+ }
912
+ }
913
+ }
914
+
915
+ // Initialize the variables for finding the text console and the
916
+ // cursor.
917
+ this.reconnectBtn = this.getChildById(this.container,'reconnect');
918
+ this.curSizeBox = this.getChildById(this.container, 'cursize');
919
+ this.menu = this.getChildById(this.container, 'menu');
920
+ this.keyboard = this.getChildById(this.container, 'keyboard');
921
+ this.keyboardImage = this.getChildById(this.container, 'kbd_img');
922
+ this.layout = this.getChildById(this.container, 'layout');
923
+ this.scrollable = this.getChildById(this.container,
924
+ 'scrollable');
925
+ this.lineheight = this.getChildById(this.container,
926
+ 'lineheight');
927
+ this.console =
928
+ [ this.getChildById(this.container, 'console'),
929
+ this.getChildById(this.container, 'alt_console') ];
930
+ var ieProbe = this.getChildById(this.container, 'ieprobe');
931
+ this.padding = this.getChildById(this.container, 'padding');
932
+ this.cursor = this.getChildById(this.container, 'cursor');
933
+ this.usercss = this.getChildById(this.container, 'usercss');
934
+ this.space = this.getChildById(this.container, 'space');
935
+ this.input = this.getChildById(this.container, 'input');
936
+ this.cliphelper = this.getChildById(this.container,
937
+ 'cliphelper');
938
+
939
+ // Add any user selectable style sheets to the menu
940
+ this.initializeUserCSSStyles();
941
+
942
+ // Remember the dimensions of a standard character glyph. We would
943
+ // expect that we could just check cursor.clientWidth/Height at any time,
944
+ // but it turns out that browsers sometimes invalidate these values
945
+ // (e.g. while displaying a print preview screen).
946
+ this.cursorWidth = this.cursor.clientWidth;
947
+ this.cursorHeight = this.lineheight.clientHeight;
948
+
949
+ // IE has a slightly different boxing model, that we need to compensate for
950
+ this.isIE = ieProbe.offsetTop > 1;
951
+ ieProbe = undefined;
952
+ this.console.innerHTML = '';
953
+
954
+ // Determine if the terminal window is positioned at the beginning of the
955
+ // page, or if it is embedded somewhere else in the page. For full-screen
956
+ // terminals, automatically resize whenever the browser window changes.
957
+ var marginTop = parseInt(this.getCurrentComputedStyle(
958
+ document.body, 'marginTop'));
959
+ var marginLeft = parseInt(this.getCurrentComputedStyle(
960
+ document.body, 'marginLeft'));
961
+ var marginRight = parseInt(this.getCurrentComputedStyle(
962
+ document.body, 'marginRight'));
963
+ var x = this.container.offsetLeft;
964
+ var y = this.container.offsetTop;
965
+ for (var parent = this.container; parent = parent.offsetParent; ) {
966
+ x += parent.offsetLeft;
967
+ y += parent.offsetTop;
968
+ }
969
+ this.isEmbedded = marginTop != y ||
970
+ marginLeft != x ||
971
+ (window.innerWidth ||
972
+ document.documentElement.clientWidth ||
973
+ document.body.clientWidth) -
974
+ marginRight != x + this.container.offsetWidth;
975
+ if (!this.isEmbedded) {
976
+ // Some browsers generate resize events when the terminal is first
977
+ // shown. Disable showing the size indicator until a little bit after
978
+ // the terminal has been rendered the first time.
979
+ this.indicateSize = false;
980
+ setTimeout(function(vt100) {
981
+ return function() {
982
+ vt100.indicateSize = true;
983
+ };
984
+ }(this), 100);
985
+ this.addListener(window, 'resize',
986
+ function(vt100) {
987
+ return function() {
988
+ vt100.hideContextMenu();
989
+ vt100.resizer();
990
+ vt100.showCurrentSize();
991
+ }
992
+ }(this));
993
+
994
+ // Hide extra scrollbars attached to window
995
+ document.body.style.margin = '0px';
996
+ try { document.body.style.overflow ='hidden'; } catch (e) { }
997
+ try { document.body.oncontextmenu = function() {return false;};} catch(e){}
998
+ }
999
+
1000
+ if (this.softKeyboard) {
1001
+ // Set up onscreen soft keyboard if it is enabled.
1002
+ this.initializeKeyboardButton();
1003
+ }
1004
+
1005
+ // Hide context menu
1006
+ this.hideContextMenu();
1007
+
1008
+ // Add listener to reconnect button
1009
+ this.addListener(this.reconnectBtn.firstChild, 'click',
1010
+ function(vt100) {
1011
+ return function() {
1012
+ var rc = vt100.reconnect();
1013
+ vt100.input.focus();
1014
+ return rc;
1015
+ }
1016
+ }(this));
1017
+
1018
+ // Add input listeners
1019
+ this.addListener(this.input, 'blur',
1020
+ function(vt100) {
1021
+ return function() { vt100.blurCursor(); } }(this));
1022
+ this.addListener(this.input, 'focus',
1023
+ function(vt100) {
1024
+ return function() { vt100.focusCursor(); } }(this));
1025
+ this.addListener(this.input, 'keydown',
1026
+ function(vt100) {
1027
+ return function(e) {
1028
+ if (!e) e = window.event;
1029
+ return vt100.keyDown(e); } }(this));
1030
+ this.addListener(this.input, 'keypress',
1031
+ function(vt100) {
1032
+ return function(e) {
1033
+ if (!e) e = window.event;
1034
+ return vt100.keyPressed(e); } }(this));
1035
+ this.addListener(this.input, 'keyup',
1036
+ function(vt100) {
1037
+ return function(e) {
1038
+ if (!e) e = window.event;
1039
+ return vt100.keyUp(e); } }(this));
1040
+
1041
+ // Attach listeners that move the focus to the <input> field. This way we
1042
+ // can make sure that we can receive keyboard input.
1043
+ var mouseEvent = function(vt100, type) {
1044
+ return function(e) {
1045
+ if (!e) e = window.event;
1046
+ return vt100.mouseEvent(e, type);
1047
+ };
1048
+ };
1049
+ this.addListener(this.scrollable,'mousedown',mouseEvent(this, 0 /* MOUSE_DOWN */));
1050
+ this.addListener(this.scrollable,'mouseup', mouseEvent(this, 1 /* MOUSE_UP */));
1051
+ this.addListener(this.scrollable,'click', mouseEvent(this, 2 /* MOUSE_CLICK */));
1052
+
1053
+ // Initialize the blank terminal window.
1054
+ this.currentScreen = 0;
1055
+ this.cursorX = 0;
1056
+ this.cursorY = 0;
1057
+ this.numScrollbackLines = 0;
1058
+ this.top = 0;
1059
+ this.bottom = 0x7FFFFFFF;
1060
+ this.scale = 1.0;
1061
+ this.resizer();
1062
+ this.focusCursor();
1063
+ this.input.focus();
1064
+ };
1065
+
1066
+ VT100.prototype.getChildById = function(parent, id) {
1067
+ var nodeList = parent.all || parent.getElementsByTagName('*');
1068
+ if (typeof nodeList.namedItem == 'undefined') {
1069
+ for (var i = 0; i < nodeList.length; i++) {
1070
+ if (nodeList[i].id == id) {
1071
+ return nodeList[i];
1072
+ }
1073
+ }
1074
+ return null;
1075
+ } else {
1076
+ var elem = (parent.all || parent.getElementsByTagName('*')).namedItem(id);
1077
+ return elem ? elem[0] || elem : null;
1078
+ }
1079
+ };
1080
+
1081
+ VT100.prototype.getCurrentComputedStyle = function(elem, style) {
1082
+ if (typeof elem.currentStyle != 'undefined') {
1083
+ return elem.currentStyle[style];
1084
+ } else {
1085
+ return document.defaultView.getComputedStyle(elem, null)[style];
1086
+ }
1087
+ };
1088
+
1089
+ VT100.prototype.reconnect = function() {
1090
+ return false;
1091
+ };
1092
+
1093
+ VT100.prototype.showReconnect = function(state) {
1094
+ if (state) {
1095
+ this.hideSoftKeyboard();
1096
+ this.reconnectBtn.style.visibility = '';
1097
+ } else {
1098
+ this.reconnectBtn.style.visibility = 'hidden';
1099
+ }
1100
+ };
1101
+
1102
+ VT100.prototype.repairElements = function(console) {
1103
+ for (var line = console.firstChild; line; line = line.nextSibling) {
1104
+ if (!line.clientHeight) {
1105
+ var newLine = document.createElement(line.tagName);
1106
+ newLine.style.cssText = line.style.cssText;
1107
+ newLine.className = line.className;
1108
+ if (line.tagName == 'DIV') {
1109
+ for (var span = line.firstChild; span; span = span.nextSibling) {
1110
+ var newSpan = document.createElement(span.tagName);
1111
+ newSpan.style.cssText = span.style.cssText;
1112
+ newSpan.style.className = span.style.className;
1113
+ this.setTextContent(newSpan, this.getTextContent(span));
1114
+ newLine.appendChild(newSpan);
1115
+ }
1116
+ } else {
1117
+ this.setTextContent(newLine, this.getTextContent(line));
1118
+ }
1119
+ line.parentNode.replaceChild(newLine, line);
1120
+ line = newLine;
1121
+ }
1122
+ }
1123
+ };
1124
+
1125
+ VT100.prototype.resized = function(w, h) {
1126
+ };
1127
+
1128
+ VT100.prototype.resizer = function() {
1129
+ // Hide onscreen soft keyboard
1130
+ this.hideSoftKeyboard();
1131
+
1132
+ // The cursor can get corrupted if the print-preview is displayed in Firefox.
1133
+ // Recreating it, will repair it.
1134
+ var newCursor = document.createElement('pre');
1135
+ this.setTextContent(newCursor, ' ');
1136
+ newCursor.id = 'cursor';
1137
+ newCursor.style.cssText = this.cursor.style.cssText;
1138
+ this.cursor.parentNode.insertBefore(newCursor, this.cursor);
1139
+ if (!newCursor.clientHeight) {
1140
+ // Things are broken right now. This is probably because we are
1141
+ // displaying the print-preview. Just don't change any of our settings
1142
+ // until the print dialog is closed again.
1143
+ newCursor.parentNode.removeChild(newCursor);
1144
+ return;
1145
+ } else {
1146
+ // Swap the old broken cursor for the newly created one.
1147
+ this.cursor.parentNode.removeChild(this.cursor);
1148
+ this.cursor = newCursor;
1149
+ }
1150
+
1151
+ // Really horrible things happen if the contents of the terminal changes
1152
+ // while the print-preview is showing. We get HTML elements that show up
1153
+ // in the DOM, but that do not take up any space. Find these elements and
1154
+ // try to fix them.
1155
+ this.repairElements(this.console[0]);
1156
+ this.repairElements(this.console[1]);
1157
+
1158
+ // Lock the cursor size to the size of a normal character. This helps with
1159
+ // characters that are taller/shorter than normal. Unfortunately, we will
1160
+ // still get confused if somebody enters a character that is wider/narrower
1161
+ // than normal. This can happen if the browser tries to substitute a
1162
+ // characters from a different font.
1163
+ this.cursor.style.width = this.cursorWidth + 'px';
1164
+ this.cursor.style.height = this.cursorHeight + 'px';
1165
+
1166
+ // Adjust height for one pixel padding of the #vt100 element.
1167
+ // The latter is necessary to properly display the inactive cursor.
1168
+ var console = this.console[this.currentScreen];
1169
+ var height = (this.isEmbedded ? this.container.clientHeight
1170
+ : (window.innerHeight ||
1171
+ document.documentElement.clientHeight ||
1172
+ document.body.clientHeight))-1;
1173
+ var partial = height % this.cursorHeight;
1174
+ this.scrollable.style.height = (height > 0 ? height : 0) + 'px';
1175
+ this.padding.style.height = (partial > 0 ? partial : 0) + 'px';
1176
+ var oldTerminalHeight = this.terminalHeight;
1177
+ this.updateWidth();
1178
+ this.updateHeight();
1179
+
1180
+ // Clip the cursor to the visible screen.
1181
+ var cx = this.cursorX;
1182
+ var cy = this.cursorY + this.numScrollbackLines;
1183
+
1184
+ // The alternate screen never keeps a scroll back buffer.
1185
+ this.updateNumScrollbackLines();
1186
+ while (this.currentScreen && this.numScrollbackLines > 0) {
1187
+ console.removeChild(console.firstChild);
1188
+ this.numScrollbackLines--;
1189
+ }
1190
+ cy -= this.numScrollbackLines;
1191
+ if (cx < 0) {
1192
+ cx = 0;
1193
+ } else if (cx > this.terminalWidth) {
1194
+ cx = this.terminalWidth - 1;
1195
+ if (cx < 0) {
1196
+ cx = 0;
1197
+ }
1198
+ }
1199
+ if (cy < 0) {
1200
+ cy = 0;
1201
+ } else if (cy > this.terminalHeight) {
1202
+ cy = this.terminalHeight - 1;
1203
+ if (cy < 0) {
1204
+ cy = 0;
1205
+ }
1206
+ }
1207
+
1208
+ // Clip the scroll region to the visible screen.
1209
+ if (this.bottom > this.terminalHeight ||
1210
+ this.bottom == oldTerminalHeight) {
1211
+ this.bottom = this.terminalHeight;
1212
+ }
1213
+ if (this.top >= this.bottom) {
1214
+ this.top = this.bottom-1;
1215
+ if (this.top < 0) {
1216
+ this.top = 0;
1217
+ }
1218
+ }
1219
+
1220
+ // Truncate lines, if necessary. Explicitly reposition cursor (this is
1221
+ // particularly important after changing the screen number), and reset
1222
+ // the scroll region to the default.
1223
+ this.truncateLines(this.terminalWidth);
1224
+ this.putString(cx, cy, '', undefined);
1225
+ this.scrollable.scrollTop = this.numScrollbackLines *
1226
+ this.cursorHeight + 1;
1227
+
1228
+ // Update classNames for lines in the scrollback buffer
1229
+ var line = console.firstChild;
1230
+ for (var i = 0; i < this.numScrollbackLines; i++) {
1231
+ line.className = 'scrollback';
1232
+ line = line.nextSibling;
1233
+ }
1234
+ while (line) {
1235
+ line.className = '';
1236
+ line = line.nextSibling;
1237
+ }
1238
+
1239
+ // Reposition the reconnect button
1240
+ this.reconnectBtn.style.left = (this.terminalWidth*this.cursorWidth/
1241
+ this.scale -
1242
+ this.reconnectBtn.clientWidth)/2 + 'px';
1243
+ this.reconnectBtn.style.top = (this.terminalHeight*this.cursorHeight-
1244
+ this.reconnectBtn.clientHeight)/2 + 'px';
1245
+
1246
+ // Send notification that the window size has been changed
1247
+ this.resized(this.terminalWidth, this.terminalHeight);
1248
+ };
1249
+
1250
+ VT100.prototype.showCurrentSize = function() {
1251
+ if (!this.indicateSize) {
1252
+ return;
1253
+ }
1254
+ this.curSizeBox.innerHTML = '' + this.terminalWidth + 'x' +
1255
+ this.terminalHeight;
1256
+ this.curSizeBox.style.left =
1257
+ (this.terminalWidth*this.cursorWidth/
1258
+ this.scale -
1259
+ this.curSizeBox.clientWidth)/2 + 'px';
1260
+ this.curSizeBox.style.top =
1261
+ (this.terminalHeight*this.cursorHeight -
1262
+ this.curSizeBox.clientHeight)/2 + 'px';
1263
+ this.curSizeBox.style.visibility = '';
1264
+ if (this.curSizeTimeout) {
1265
+ clearTimeout(this.curSizeTimeout);
1266
+ }
1267
+
1268
+ // Only show the terminal size for a short amount of time after resizing.
1269
+ // Then hide this information, again. Some browsers generate resize events
1270
+ // throughout the entire resize operation. This is nice, and we will show
1271
+ // the terminal size while the user is dragging the window borders.
1272
+ // Other browsers only generate a single event when the user releases the
1273
+ // mouse. In those cases, we can only show the terminal size once at the
1274
+ // end of the resize operation.
1275
+ this.curSizeTimeout = setTimeout(function(vt100) {
1276
+ return function() {
1277
+ vt100.curSizeTimeout = null;
1278
+ vt100.curSizeBox.style.visibility = 'hidden';
1279
+ };
1280
+ }(this), 1000);
1281
+ };
1282
+
1283
+ VT100.prototype.selection = function() {
1284
+ try {
1285
+ return '' + (window.getSelection && window.getSelection() ||
1286
+ document.selection && document.selection.type == 'Text' &&
1287
+ document.selection.createRange().text || '');
1288
+ } catch (e) {
1289
+ }
1290
+ return '';
1291
+ };
1292
+
1293
+ VT100.prototype.cancelEvent = function(event) {
1294
+ try {
1295
+ // For non-IE browsers
1296
+ event.stopPropagation();
1297
+ event.preventDefault();
1298
+ } catch (e) {
1299
+ }
1300
+ try {
1301
+ // For IE
1302
+ event.cancelBubble = true;
1303
+ event.returnValue = false;
1304
+ event.button = 0;
1305
+ event.keyCode = 0;
1306
+ } catch (e) {
1307
+ }
1308
+ return false;
1309
+ };
1310
+
1311
+ VT100.prototype.mousePosition = function(event) {
1312
+ var offsetX = this.container.offsetLeft;
1313
+ var offsetY = this.container.offsetTop;
1314
+ for (var e = this.container; e = e.offsetParent; ) {
1315
+ offsetX += e.offsetLeft;
1316
+ offsetY += e.offsetTop;
1317
+ }
1318
+ return [ event.clientX - offsetX,
1319
+ event.clientY - offsetY ];
1320
+ };
1321
+
1322
+ VT100.prototype.mouseEvent = function(event, type) {
1323
+ // If any text is currently selected, do not move the focus as that would
1324
+ // invalidate the selection.
1325
+ var selection = this.selection();
1326
+ if ((type == 1 /* MOUSE_UP */ || type == 2 /* MOUSE_CLICK */) && !selection.length) {
1327
+ this.input.focus();
1328
+ }
1329
+
1330
+ // Compute mouse position in characters.
1331
+ var position = this.mousePosition(event);
1332
+ var x = Math.floor(position[0] / this.cursorWidth);
1333
+ var y = Math.floor((position[1] + this.scrollable.scrollTop) /
1334
+ this.cursorHeight) - this.numScrollbackLines;
1335
+ var inside = true;
1336
+ if (x >= this.terminalWidth) {
1337
+ x = this.terminalWidth - 1;
1338
+ inside = false;
1339
+ }
1340
+ if (x < 0) {
1341
+ x = 0;
1342
+ inside = false;
1343
+ }
1344
+ if (y >= this.terminalHeight) {
1345
+ y = this.terminalHeight - 1;
1346
+ inside = false;
1347
+ }
1348
+ if (y < 0) {
1349
+ y = 0;
1350
+ inside = false;
1351
+ }
1352
+
1353
+ // Compute button number and modifier keys.
1354
+ var button = type != 0 /* MOUSE_DOWN */ ? 3 :
1355
+ typeof event.pageX != 'undefined' ? event.button :
1356
+ [ undefined, 0, 2, 0, 1, 0, 1, 0 ][event.button];
1357
+ if (button != undefined) {
1358
+ if (event.shiftKey) {
1359
+ button |= 0x04;
1360
+ }
1361
+ if (event.altKey || event.metaKey) {
1362
+ button |= 0x08;
1363
+ }
1364
+ if (event.ctrlKey) {
1365
+ button |= 0x10;
1366
+ }
1367
+ }
1368
+
1369
+ // Report mouse events if they happen inside of the current screen and
1370
+ // with the SHIFT key unpressed. Both of these restrictions do not apply
1371
+ // for button releases, as we always want to report those.
1372
+ if (this.mouseReporting && !selection.length &&
1373
+ (type != 0 /* MOUSE_DOWN */ || !event.shiftKey)) {
1374
+ if (inside || type != 0 /* MOUSE_DOWN */) {
1375
+ if (button != undefined) {
1376
+ var report = '\u001B[M' + String.fromCharCode(button + 32) +
1377
+ String.fromCharCode(x + 33) +
1378
+ String.fromCharCode(y + 33);
1379
+ if (type != 2 /* MOUSE_CLICK */) {
1380
+ this.keysPressed(report);
1381
+ }
1382
+
1383
+ // If we reported the event, stop propagating it (not sure, if this
1384
+ // actually works on most browsers; blocking the global "oncontextmenu"
1385
+ // even is still necessary).
1386
+ return this.cancelEvent(event);
1387
+ }
1388
+ }
1389
+ }
1390
+
1391
+ // Bring up context menu.
1392
+ if (button == 2 && !event.shiftKey) {
1393
+ if (type == 0 /* MOUSE_DOWN */) {
1394
+ this.showContextMenu(position[0], position[1]);
1395
+ }
1396
+ return this.cancelEvent(event);
1397
+ }
1398
+
1399
+ if (this.mouseReporting) {
1400
+ try {
1401
+ event.shiftKey = false;
1402
+ } catch (e) {
1403
+ }
1404
+ }
1405
+
1406
+ return true;
1407
+ };
1408
+
1409
+ VT100.prototype.replaceChar = function(s, ch, repl) {
1410
+ for (var i = -1;;) {
1411
+ i = s.indexOf(ch, i + 1);
1412
+ if (i < 0) {
1413
+ break;
1414
+ }
1415
+ s = s.substr(0, i) + repl + s.substr(i + 1);
1416
+ }
1417
+ return s;
1418
+ };
1419
+
1420
+ VT100.prototype.htmlEscape = function(s) {
1421
+ return this.replaceChar(this.replaceChar(this.replaceChar(this.replaceChar(
1422
+ s, '&', '&amp;'), '<', '&lt;'), '"', '&quot;'), ' ', '\u00A0');
1423
+ };
1424
+
1425
+ VT100.prototype.getTextContent = function(elem) {
1426
+ return elem.textContent ||
1427
+ (typeof elem.textContent == 'undefined' ? elem.innerText : '');
1428
+ };
1429
+
1430
+ VT100.prototype.setTextContentRaw = function(elem, s) {
1431
+ // Updating the content of an element is an expensive operation. It actually
1432
+ // pays off to first check whether the element is still unchanged.
1433
+ if (typeof elem.textContent == 'undefined') {
1434
+ if (elem.innerText != s) {
1435
+ try {
1436
+ elem.innerText = s;
1437
+ } catch (e) {
1438
+ // Very old versions of IE do not allow setting innerText. Instead,
1439
+ // remove all children, by setting innerHTML and then set the text
1440
+ // using DOM methods.
1441
+ elem.innerHTML = '';
1442
+ elem.appendChild(document.createTextNode(
1443
+ this.replaceChar(s, ' ', '\u00A0')));
1444
+ }
1445
+ }
1446
+ } else {
1447
+ if (elem.textContent != s) {
1448
+ elem.textContent = s;
1449
+ }
1450
+ }
1451
+ };
1452
+
1453
+ VT100.prototype.setTextContent = function(elem, s) {
1454
+ // Check if we find any URLs in the text. If so, automatically convert them
1455
+ // to links.
1456
+ if (this.urlRE && this.urlRE.test(s)) {
1457
+ var inner = '';
1458
+ for (;;) {
1459
+ var consumed = 0;
1460
+ if (RegExp.leftContext != null) {
1461
+ inner += this.htmlEscape(RegExp.leftContext);
1462
+ consumed += RegExp.leftContext.length;
1463
+ }
1464
+ var url = this.htmlEscape(RegExp.lastMatch);
1465
+ var fullUrl = url;
1466
+
1467
+ // If no protocol was specified, try to guess a reasonable one.
1468
+ if (url.indexOf('http://') < 0 && url.indexOf('https://') < 0 &&
1469
+ url.indexOf('ftp://') < 0 && url.indexOf('mailto:') < 0) {
1470
+ var slash = url.indexOf('/');
1471
+ var at = url.indexOf('@');
1472
+ var question = url.indexOf('?');
1473
+ if (at > 0 &&
1474
+ (at < question || question < 0) &&
1475
+ (slash < 0 || (question > 0 && slash > question))) {
1476
+ fullUrl = 'mailto:' + url;
1477
+ } else {
1478
+ fullUrl = (url.indexOf('ftp.') == 0 ? 'ftp://' : 'http://') +
1479
+ url;
1480
+ }
1481
+ }
1482
+
1483
+ inner += '<a target="vt100Link" href="' + fullUrl +
1484
+ '">' + url + '</a>';
1485
+ consumed += RegExp.lastMatch.length;
1486
+ s = s.substr(consumed);
1487
+ if (!this.urlRE.test(s)) {
1488
+ if (RegExp.rightContext != null) {
1489
+ inner += this.htmlEscape(RegExp.rightContext);
1490
+ }
1491
+ break;
1492
+ }
1493
+ }
1494
+ elem.innerHTML = inner;
1495
+ return;
1496
+ }
1497
+
1498
+ this.setTextContentRaw(elem, s);
1499
+ };
1500
+
1501
+ VT100.prototype.insertBlankLine = function(y, color, style) {
1502
+ // Insert a blank line a position y. This method ignores the scrollback
1503
+ // buffer. The caller has to add the length of the scrollback buffer to
1504
+ // the position, if necessary.
1505
+ // If the position is larger than the number of current lines, this
1506
+ // method just adds a new line right after the last existing one. It does
1507
+ // not add any missing lines in between. It is the caller's responsibility
1508
+ // to do so.
1509
+ if (!color) {
1510
+ color = 'ansi0 bgAnsi15';
1511
+ }
1512
+ if (!style) {
1513
+ style = '';
1514
+ }
1515
+ var line;
1516
+ if (color != 'ansi0 bgAnsi15' && !style) {
1517
+ line = document.createElement('pre');
1518
+ this.setTextContent(line, '\n');
1519
+ } else {
1520
+ line = document.createElement('div');
1521
+ var span = document.createElement('span');
1522
+ span.style.cssText = style;
1523
+ span.style.className = color;
1524
+ this.setTextContent(span, this.spaces(this.terminalWidth));
1525
+ line.appendChild(span);
1526
+ }
1527
+ line.style.height = this.cursorHeight + 'px';
1528
+ var console = this.console[this.currentScreen];
1529
+ if (console.childNodes.length > y) {
1530
+ console.insertBefore(line, console.childNodes[y]);
1531
+ } else {
1532
+ console.appendChild(line);
1533
+ }
1534
+ };
1535
+
1536
+ VT100.prototype.updateWidth = function() {
1537
+ this.terminalWidth = Math.floor(this.console[this.currentScreen].offsetWidth/
1538
+ this.cursorWidth*this.scale);
1539
+ return this.terminalWidth;
1540
+ };
1541
+
1542
+ VT100.prototype.updateHeight = function() {
1543
+ // We want to be able to display either a terminal window that fills the
1544
+ // entire browser window, or a terminal window that is contained in a
1545
+ // <div> which is embededded somewhere in the web page.
1546
+ if (this.isEmbedded) {
1547
+ // Embedded terminal. Use size of the containing <div> (id="vt100").
1548
+ this.terminalHeight = Math.floor((this.container.clientHeight-1) /
1549
+ this.cursorHeight);
1550
+ } else {
1551
+ // Use the full browser window.
1552
+ this.terminalHeight = Math.floor(((window.innerHeight ||
1553
+ document.documentElement.clientHeight ||
1554
+ document.body.clientHeight)-1)/
1555
+ this.cursorHeight);
1556
+ }
1557
+ return this.terminalHeight;
1558
+ };
1559
+
1560
+ VT100.prototype.updateNumScrollbackLines = function() {
1561
+ var scrollback = Math.floor(
1562
+ this.console[this.currentScreen].offsetHeight /
1563
+ this.cursorHeight) -
1564
+ this.terminalHeight;
1565
+ this.numScrollbackLines = scrollback < 0 ? 0 : scrollback;
1566
+ return this.numScrollbackLines;
1567
+ };
1568
+
1569
+ VT100.prototype.truncateLines = function(width) {
1570
+ if (width < 0) {
1571
+ width = 0;
1572
+ }
1573
+ for (var line = this.console[this.currentScreen].firstChild; line;
1574
+ line = line.nextSibling) {
1575
+ if (line.tagName == 'DIV') {
1576
+ var x = 0;
1577
+
1578
+ // Traverse current line and truncate it once we saw "width" characters
1579
+ for (var span = line.firstChild; span;
1580
+ span = span.nextSibling) {
1581
+ var s = this.getTextContent(span);
1582
+ var l = s.length;
1583
+ if (x + l > width) {
1584
+ this.setTextContent(span, s.substr(0, width - x));
1585
+ while (span.nextSibling) {
1586
+ line.removeChild(line.lastChild);
1587
+ }
1588
+ break;
1589
+ }
1590
+ x += l;
1591
+ }
1592
+ // Prune white space from the end of the current line
1593
+ var span = line.lastChild;
1594
+ while (span &&
1595
+ span.className == 'ansi0 bgAnsi15' &&
1596
+ !span.style.cssText.length) {
1597
+ // Scan backwards looking for first non-space character
1598
+ var s = this.getTextContent(span);
1599
+ for (var i = s.length; i--; ) {
1600
+ if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1601
+ if (i+1 != s.length) {
1602
+ this.setTextContent(s.substr(0, i+1));
1603
+ }
1604
+ span = null;
1605
+ break;
1606
+ }
1607
+ }
1608
+ if (span) {
1609
+ var sibling = span;
1610
+ span = span.previousSibling;
1611
+ if (span) {
1612
+ // Remove blank <span>'s from end of line
1613
+ line.removeChild(sibling);
1614
+ } else {
1615
+ // Remove entire line (i.e. <div>), if empty
1616
+ var blank = document.createElement('pre');
1617
+ blank.style.height = this.cursorHeight + 'px';
1618
+ this.setTextContent(blank, '\n');
1619
+ line.parentNode.replaceChild(blank, line);
1620
+ }
1621
+ }
1622
+ }
1623
+ }
1624
+ }
1625
+ };
1626
+
1627
+ VT100.prototype.putString = function(x, y, text, color, style) {
1628
+ if (!color) {
1629
+ color = 'ansi0 bgAnsi15';
1630
+ }
1631
+ if (!style) {
1632
+ style = '';
1633
+ }
1634
+ var yIdx = y + this.numScrollbackLines;
1635
+ var line;
1636
+ var sibling;
1637
+ var s;
1638
+ var span;
1639
+ var xPos = 0;
1640
+ var console = this.console[this.currentScreen];
1641
+ if (!text.length && (yIdx >= console.childNodes.length ||
1642
+ console.childNodes[yIdx].tagName != 'DIV')) {
1643
+ // Positioning cursor to a blank location
1644
+ span = null;
1645
+ } else {
1646
+ // Create missing blank lines at end of page
1647
+ while (console.childNodes.length <= yIdx) {
1648
+ // In order to simplify lookups, we want to make sure that each line
1649
+ // is represented by exactly one element (and possibly a whole bunch of
1650
+ // children).
1651
+ // For non-blank lines, we can create a <div> containing one or more
1652
+ // <span>s. For blank lines, this fails as browsers tend to optimize them
1653
+ // away. But fortunately, a <pre> tag containing a newline character
1654
+ // appears to work for all browsers (a &nbsp; would also work, but then
1655
+ // copying from the browser window would insert superfluous spaces into
1656
+ // the clipboard).
1657
+ this.insertBlankLine(yIdx);
1658
+ }
1659
+ line = console.childNodes[yIdx];
1660
+
1661
+ // If necessary, promote blank '\n' line to a <div> tag
1662
+ if (line.tagName != 'DIV') {
1663
+ var div = document.createElement('div');
1664
+ div.style.height = this.cursorHeight + 'px';
1665
+ div.innerHTML = '<span></span>';
1666
+ console.replaceChild(div, line);
1667
+ line = div;
1668
+ }
1669
+
1670
+ // Scan through list of <span>'s until we find the one where our text
1671
+ // starts
1672
+ span = line.firstChild;
1673
+ var len;
1674
+ while (span.nextSibling && xPos < x) {
1675
+ len = this.getTextContent(span).length;
1676
+ if (xPos + len > x) {
1677
+ break;
1678
+ }
1679
+ xPos += len;
1680
+ span = span.nextSibling;
1681
+ }
1682
+
1683
+ if (text.length) {
1684
+ // If current <span> is not long enough, pad with spaces or add new
1685
+ // span
1686
+ s = this.getTextContent(span);
1687
+ var oldColor = span.className;
1688
+ var oldStyle = span.style.cssText;
1689
+ if (xPos + s.length < x) {
1690
+ if (oldColor != 'ansi0 bgAnsi15' || oldStyle != '') {
1691
+ span = document.createElement('span');
1692
+ line.appendChild(span);
1693
+ span.className = 'ansi0 bgAnsi15';
1694
+ span.style.cssText = '';
1695
+ oldColor = 'ansi0 bgAnsi15';
1696
+ oldStyle = '';
1697
+ xPos += s.length;
1698
+ s = '';
1699
+ }
1700
+ do {
1701
+ s += ' ';
1702
+ } while (xPos + s.length < x);
1703
+ }
1704
+
1705
+ // If styles do not match, create a new <span>
1706
+ var del = text.length - s.length + x - xPos;
1707
+ if (oldColor != color ||
1708
+ (oldStyle != style && (oldStyle || style))) {
1709
+ if (xPos == x) {
1710
+ // Replacing text at beginning of existing <span>
1711
+ if (text.length >= s.length) {
1712
+ // New text is equal or longer than existing text
1713
+ s = text;
1714
+ } else {
1715
+ // Insert new <span> before the current one, then remove leading
1716
+ // part of existing <span>, adjust style of new <span>, and finally
1717
+ // set its contents
1718
+ sibling = document.createElement('span');
1719
+ line.insertBefore(sibling, span);
1720
+ this.setTextContent(span, s.substr(text.length));
1721
+ span = sibling;
1722
+ s = text;
1723
+ }
1724
+ } else {
1725
+ // Replacing text some way into the existing <span>
1726
+ var remainder = s.substr(x + text.length - xPos);
1727
+ this.setTextContent(span, s.substr(0, x - xPos));
1728
+ xPos = x;
1729
+ sibling = document.createElement('span');
1730
+ if (span.nextSibling) {
1731
+ line.insertBefore(sibling, span.nextSibling);
1732
+ span = sibling;
1733
+ if (remainder.length) {
1734
+ sibling = document.createElement('span');
1735
+ sibling.className = oldColor;
1736
+ sibling.style.cssText = oldStyle;
1737
+ this.setTextContent(sibling, remainder);
1738
+ line.insertBefore(sibling, span.nextSibling);
1739
+ }
1740
+ } else {
1741
+ line.appendChild(sibling);
1742
+ span = sibling;
1743
+ if (remainder.length) {
1744
+ sibling = document.createElement('span');
1745
+ sibling.className = oldColor;
1746
+ sibling.style.cssText = oldStyle;
1747
+ this.setTextContent(sibling, remainder);
1748
+ line.appendChild(sibling);
1749
+ }
1750
+ }
1751
+ s = text;
1752
+ }
1753
+ span.className = color;
1754
+ span.style.cssText = style;
1755
+ } else {
1756
+ // Overwrite (partial) <span> with new text
1757
+ s = s.substr(0, x - xPos) +
1758
+ text +
1759
+ s.substr(x + text.length - xPos);
1760
+ }
1761
+ this.setTextContent(span, s);
1762
+
1763
+
1764
+ // Delete all subsequent <span>'s that have just been overwritten
1765
+ sibling = span.nextSibling;
1766
+ while (del > 0 && sibling) {
1767
+ s = this.getTextContent(sibling);
1768
+ len = s.length;
1769
+ if (len <= del) {
1770
+ line.removeChild(sibling);
1771
+ del -= len;
1772
+ sibling = span.nextSibling;
1773
+ } else {
1774
+ this.setTextContent(sibling, s.substr(del));
1775
+ break;
1776
+ }
1777
+ }
1778
+
1779
+ // Merge <span> with next sibling, if styles are identical
1780
+ if (sibling && span.className == sibling.className &&
1781
+ span.style.cssText == sibling.style.cssText) {
1782
+ this.setTextContent(span,
1783
+ this.getTextContent(span) +
1784
+ this.getTextContent(sibling));
1785
+ line.removeChild(sibling);
1786
+ }
1787
+ }
1788
+ }
1789
+
1790
+ // Position cursor
1791
+ this.cursorX = x + text.length;
1792
+ if (this.cursorX >= this.terminalWidth) {
1793
+ this.cursorX = this.terminalWidth - 1;
1794
+ if (this.cursorX < 0) {
1795
+ this.cursorX = 0;
1796
+ }
1797
+ }
1798
+ var pixelX = -1;
1799
+ var pixelY = -1;
1800
+ if (!this.cursor.style.visibility) {
1801
+ var idx = this.cursorX - xPos;
1802
+ if (span) {
1803
+ // If we are in a non-empty line, take the cursor Y position from the
1804
+ // other elements in this line. If dealing with broken, non-proportional
1805
+ // fonts, this is likely to yield better results.
1806
+ pixelY = span.offsetTop +
1807
+ span.offsetParent.offsetTop;
1808
+ s = this.getTextContent(span);
1809
+ var nxtIdx = idx - s.length;
1810
+ if (nxtIdx < 0) {
1811
+ this.setTextContent(this.cursor, s.charAt(idx));
1812
+ pixelX = span.offsetLeft +
1813
+ idx*span.offsetWidth / s.length;
1814
+ } else {
1815
+ if (nxtIdx == 0) {
1816
+ pixelX = span.offsetLeft + span.offsetWidth;
1817
+ }
1818
+ if (span.nextSibling) {
1819
+ s = this.getTextContent(span.nextSibling);
1820
+ this.setTextContent(this.cursor, s.charAt(nxtIdx));
1821
+ if (pixelX < 0) {
1822
+ pixelX = span.nextSibling.offsetLeft +
1823
+ nxtIdx*span.offsetWidth / s.length;
1824
+ }
1825
+ } else {
1826
+ this.setTextContent(this.cursor, ' ');
1827
+ }
1828
+ }
1829
+ } else {
1830
+ this.setTextContent(this.cursor, ' ');
1831
+ }
1832
+ }
1833
+ if (pixelX >= 0) {
1834
+ this.cursor.style.left = (pixelX + (this.isIE ? 1 : 0))/
1835
+ this.scale + 'px';
1836
+ } else {
1837
+ this.setTextContent(this.space, this.spaces(this.cursorX));
1838
+ this.cursor.style.left = (this.space.offsetWidth +
1839
+ console.offsetLeft)/this.scale + 'px';
1840
+ }
1841
+ this.cursorY = yIdx - this.numScrollbackLines;
1842
+ if (pixelY >= 0) {
1843
+ this.cursor.style.top = pixelY + 'px';
1844
+ } else {
1845
+ this.cursor.style.top = yIdx*this.cursorHeight +
1846
+ console.offsetTop + 'px';
1847
+ }
1848
+
1849
+ if (text.length) {
1850
+ // Merge <span> with previous sibling, if styles are identical
1851
+ if ((sibling = span.previousSibling) &&
1852
+ span.className == sibling.className &&
1853
+ span.style.cssText == sibling.style.cssText) {
1854
+ this.setTextContent(span,
1855
+ this.getTextContent(sibling) +
1856
+ this.getTextContent(span));
1857
+ line.removeChild(sibling);
1858
+ }
1859
+
1860
+ // Prune white space from the end of the current line
1861
+ span = line.lastChild;
1862
+ while (span &&
1863
+ span.className == 'ansi0 bgAnsi15' &&
1864
+ !span.style.cssText.length) {
1865
+ // Scan backwards looking for first non-space character
1866
+ s = this.getTextContent(span);
1867
+ for (var i = s.length; i--; ) {
1868
+ if (s.charAt(i) != ' ' && s.charAt(i) != '\u00A0') {
1869
+ if (i+1 != s.length) {
1870
+ this.setTextContent(s.substr(0, i+1));
1871
+ }
1872
+ span = null;
1873
+ break;
1874
+ }
1875
+ }
1876
+ if (span) {
1877
+ sibling = span;
1878
+ span = span.previousSibling;
1879
+ if (span) {
1880
+ // Remove blank <span>'s from end of line
1881
+ line.removeChild(sibling);
1882
+ } else {
1883
+ // Remove entire line (i.e. <div>), if empty
1884
+ var blank = document.createElement('pre');
1885
+ blank.style.height = this.cursorHeight + 'px';
1886
+ this.setTextContent(blank, '\n');
1887
+ line.parentNode.replaceChild(blank, line);
1888
+ }
1889
+ }
1890
+ }
1891
+ }
1892
+ };
1893
+
1894
+ VT100.prototype.gotoXY = function(x, y) {
1895
+ if (x >= this.terminalWidth) {
1896
+ x = this.terminalWidth - 1;
1897
+ }
1898
+ if (x < 0) {
1899
+ x = 0;
1900
+ }
1901
+ var minY, maxY;
1902
+ if (this.offsetMode) {
1903
+ minY = this.top;
1904
+ maxY = this.bottom;
1905
+ } else {
1906
+ minY = 0;
1907
+ maxY = this.terminalHeight;
1908
+ }
1909
+ if (y >= maxY) {
1910
+ y = maxY - 1;
1911
+ }
1912
+ if (y < minY) {
1913
+ y = minY;
1914
+ }
1915
+ this.putString(x, y, '', undefined);
1916
+ this.needWrap = false;
1917
+ };
1918
+
1919
+ VT100.prototype.gotoXaY = function(x, y) {
1920
+ this.gotoXY(x, this.offsetMode ? (this.top + y) : y);
1921
+ };
1922
+
1923
+ VT100.prototype.refreshInvertedState = function() {
1924
+ if (this.isInverted) {
1925
+ this.scrollable.className += ' inverted';
1926
+ } else {
1927
+ this.scrollable.className = this.scrollable.className.
1928
+ replace(/ *inverted/, '');
1929
+ }
1930
+ };
1931
+
1932
+ VT100.prototype.enableAlternateScreen = function(state) {
1933
+ // Don't do anything, if we are already on the desired screen
1934
+ if ((state ? 1 : 0) == this.currentScreen) {
1935
+ // Calling the resizer is not actually necessary. But it is a good way
1936
+ // of resetting state that might have gotten corrupted.
1937
+ this.resizer();
1938
+ return;
1939
+ }
1940
+
1941
+ // We save the full state of the normal screen, when we switch away from it.
1942
+ // But for the alternate screen, no saving is necessary. We always reset
1943
+ // it when we switch to it.
1944
+ if (state) {
1945
+ this.saveCursor();
1946
+ }
1947
+
1948
+ // Display new screen, and initialize state (the resizer does that for us).
1949
+ this.currentScreen = state ? 1 : 0;
1950
+ this.console[1-this.currentScreen].style.display = 'none';
1951
+ this.console[this.currentScreen].style.display = '';
1952
+
1953
+ // Select appropriate character pitch.
1954
+ var transform = this.getTransformName();
1955
+ if (transform) {
1956
+ if (state) {
1957
+ // Upon enabling the alternate screen, we switch to 80 column mode. But
1958
+ // upon returning to the regular screen, we restore the mode that was
1959
+ // in effect previously.
1960
+ this.console[1].style[transform] = '';
1961
+ }
1962
+ var style =
1963
+ this.console[this.currentScreen].style[transform];
1964
+ this.cursor.style[transform] = style;
1965
+ this.space.style[transform] = style;
1966
+ this.scale = style == '' ? 1.0:1.65;
1967
+ if (transform == 'filter') {
1968
+ this.console[this.currentScreen].style.width = style == '' ? '165%':'';
1969
+ }
1970
+ }
1971
+ this.resizer();
1972
+
1973
+ // If we switched to the alternate screen, reset it completely. Otherwise,
1974
+ // restore the saved state.
1975
+ if (state) {
1976
+ this.gotoXY(0, 0);
1977
+ this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight);
1978
+ } else {
1979
+ this.restoreCursor();
1980
+ }
1981
+ };
1982
+
1983
+ VT100.prototype.hideCursor = function() {
1984
+ var hidden = this.cursor.style.visibility == 'hidden';
1985
+ if (!hidden) {
1986
+ this.cursor.style.visibility = 'hidden';
1987
+ return true;
1988
+ }
1989
+ return false;
1990
+ };
1991
+
1992
+ VT100.prototype.showCursor = function(x, y) {
1993
+ if (this.cursor.style.visibility) {
1994
+ this.cursor.style.visibility = '';
1995
+ this.putString(x == undefined ? this.cursorX : x,
1996
+ y == undefined ? this.cursorY : y,
1997
+ '', undefined);
1998
+ return true;
1999
+ }
2000
+ return false;
2001
+ };
2002
+
2003
+ VT100.prototype.scrollBack = function() {
2004
+ var i = this.scrollable.scrollTop -
2005
+ this.scrollable.clientHeight;
2006
+ this.scrollable.scrollTop = i < 0 ? 0 : i;
2007
+ };
2008
+
2009
+ VT100.prototype.scrollFore = function() {
2010
+ var i = this.scrollable.scrollTop +
2011
+ this.scrollable.clientHeight;
2012
+ this.scrollable.scrollTop = i > this.numScrollbackLines *
2013
+ this.cursorHeight + 1
2014
+ ? this.numScrollbackLines *
2015
+ this.cursorHeight + 1
2016
+ : i;
2017
+ };
2018
+
2019
+ VT100.prototype.spaces = function(i) {
2020
+ var s = '';
2021
+ while (i-- > 0) {
2022
+ s += ' ';
2023
+ }
2024
+ return s;
2025
+ };
2026
+
2027
+ VT100.prototype.clearRegion = function(x, y, w, h, color, style) {
2028
+ w += x;
2029
+ if (x < 0) {
2030
+ x = 0;
2031
+ }
2032
+ if (w > this.terminalWidth) {
2033
+ w = this.terminalWidth;
2034
+ }
2035
+ if ((w -= x) <= 0) {
2036
+ return;
2037
+ }
2038
+ h += y;
2039
+ if (y < 0) {
2040
+ y = 0;
2041
+ }
2042
+ if (h > this.terminalHeight) {
2043
+ h = this.terminalHeight;
2044
+ }
2045
+ if ((h -= y) <= 0) {
2046
+ return;
2047
+ }
2048
+
2049
+ // Special case the situation where we clear the entire screen, and we do
2050
+ // not have a scrollback buffer. In that case, we should just remove all
2051
+ // child nodes.
2052
+ if (!this.numScrollbackLines &&
2053
+ w == this.terminalWidth && h == this.terminalHeight &&
2054
+ (color == undefined || color == 'ansi0 bgAnsi15') && !style) {
2055
+ var console = this.console[this.currentScreen];
2056
+ while (console.lastChild) {
2057
+ console.removeChild(console.lastChild);
2058
+ }
2059
+ this.putString(this.cursorX, this.cursorY, '', undefined);
2060
+ } else {
2061
+ var hidden = this.hideCursor();
2062
+ var cx = this.cursorX;
2063
+ var cy = this.cursorY;
2064
+ var s = this.spaces(w);
2065
+ for (var i = y+h; i-- > y; ) {
2066
+ this.putString(x, i, s, color, style);
2067
+ }
2068
+ hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2069
+ }
2070
+ };
2071
+
2072
+ VT100.prototype.copyLineSegment = function(dX, dY, sX, sY, w) {
2073
+ var text = [ ];
2074
+ var className = [ ];
2075
+ var style = [ ];
2076
+ var console = this.console[this.currentScreen];
2077
+ if (sY >= console.childNodes.length) {
2078
+ text[0] = this.spaces(w);
2079
+ className[0] = undefined;
2080
+ style[0] = undefined;
2081
+ } else {
2082
+ var line = console.childNodes[sY];
2083
+ if (line.tagName != 'DIV' || !line.childNodes.length) {
2084
+ text[0] = this.spaces(w);
2085
+ className[0] = undefined;
2086
+ style[0] = undefined;
2087
+ } else {
2088
+ var x = 0;
2089
+ for (var span = line.firstChild; span && w > 0; span = span.nextSibling){
2090
+ var s = this.getTextContent(span);
2091
+ var len = s.length;
2092
+ if (x + len > sX) {
2093
+ var o = sX > x ? sX - x : 0;
2094
+ text[text.length] = s.substr(o, w);
2095
+ className[className.length] = span.className;
2096
+ style[style.length] = span.style.cssText;
2097
+ w -= len - o;
2098
+ }
2099
+ x += len;
2100
+ }
2101
+ if (w > 0) {
2102
+ text[text.length] = this.spaces(w);
2103
+ className[className.length] = undefined;
2104
+ style[style.length] = undefined;
2105
+ }
2106
+ }
2107
+ }
2108
+ var hidden = this.hideCursor();
2109
+ var cx = this.cursorX;
2110
+ var cy = this.cursorY;
2111
+ for (var i = 0; i < text.length; i++) {
2112
+ var color;
2113
+ if (className[i]) {
2114
+ color = className[i];
2115
+ } else {
2116
+ color = 'ansi0 bgAnsi15';
2117
+ }
2118
+ this.putString(dX, dY - this.numScrollbackLines, text[i], color, style[i]);
2119
+ dX += text[i].length;
2120
+ }
2121
+ hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2122
+ };
2123
+
2124
+ VT100.prototype.scrollRegion = function(x, y, w, h, incX, incY,
2125
+ color, style) {
2126
+ var left = incX < 0 ? -incX : 0;
2127
+ var right = incX > 0 ? incX : 0;
2128
+ var up = incY < 0 ? -incY : 0;
2129
+ var down = incY > 0 ? incY : 0;
2130
+
2131
+ // Clip region against terminal size
2132
+ var dontScroll = null;
2133
+ w += x;
2134
+ if (x < left) {
2135
+ x = left;
2136
+ }
2137
+ if (w > this.terminalWidth - right) {
2138
+ w = this.terminalWidth - right;
2139
+ }
2140
+ if ((w -= x) <= 0) {
2141
+ dontScroll = 1;
2142
+ }
2143
+ h += y;
2144
+ if (y < up) {
2145
+ y = up;
2146
+ }
2147
+ if (h > this.terminalHeight - down) {
2148
+ h = this.terminalHeight - down;
2149
+ }
2150
+ if ((h -= y) < 0) {
2151
+ dontScroll = 1;
2152
+ }
2153
+ if (!dontScroll) {
2154
+ if (style && style.indexOf('underline')) {
2155
+ // Different terminal emulators disagree on the attributes that
2156
+ // are used for scrolling. The consensus seems to be, never to
2157
+ // fill with underlined spaces. N.B. this is different from the
2158
+ // cases when the user blanks a region. User-initiated blanking
2159
+ // always fills with all of the current attributes.
2160
+ style = style.replace(/text-decoration:underline;/, '');
2161
+ }
2162
+
2163
+ // Compute current scroll position
2164
+ var scrollPos = this.numScrollbackLines -
2165
+ (this.scrollable.scrollTop-1) / this.cursorHeight;
2166
+
2167
+ // Determine original cursor position. Hide cursor temporarily to avoid
2168
+ // visual artifacts.
2169
+ var hidden = this.hideCursor();
2170
+ var cx = this.cursorX;
2171
+ var cy = this.cursorY;
2172
+ var console = this.console[this.currentScreen];
2173
+
2174
+ if (!incX && !x && w == this.terminalWidth) {
2175
+ // Scrolling entire lines
2176
+ if (incY < 0) {
2177
+ // Scrolling up
2178
+ if (!this.currentScreen && y == -incY &&
2179
+ h == this.terminalHeight + incY) {
2180
+ // Scrolling up with adding to the scrollback buffer. This is only
2181
+ // possible if there are at least as many lines in the console,
2182
+ // as the terminal is high
2183
+ while (console.childNodes.length < this.terminalHeight) {
2184
+ this.insertBlankLine(this.terminalHeight);
2185
+ }
2186
+
2187
+ // Add new lines at bottom in order to force scrolling
2188
+ for (var i = 0; i < y; i++) {
2189
+ this.insertBlankLine(console.childNodes.length, color, style);
2190
+ }
2191
+
2192
+ // Adjust the number of lines in the scrollback buffer by
2193
+ // removing excess entries.
2194
+ this.updateNumScrollbackLines();
2195
+ while (this.numScrollbackLines >
2196
+ (this.currentScreen ? 0 : this.maxScrollbackLines)) {
2197
+ console.removeChild(console.firstChild);
2198
+ this.numScrollbackLines--;
2199
+ }
2200
+
2201
+ // Mark lines in the scrollback buffer, so that they do not get
2202
+ // printed.
2203
+ for (var i = this.numScrollbackLines, j = -incY;
2204
+ i-- > 0 && j-- > 0; ) {
2205
+ console.childNodes[i].className = 'scrollback';
2206
+ }
2207
+ } else {
2208
+ // Scrolling up without adding to the scrollback buffer.
2209
+ for (var i = -incY;
2210
+ i-- > 0 &&
2211
+ console.childNodes.length >
2212
+ this.numScrollbackLines + y + incY; ) {
2213
+ console.removeChild(console.childNodes[
2214
+ this.numScrollbackLines + y + incY]);
2215
+ }
2216
+
2217
+ // If we used to have a scrollback buffer, then we must make sure
2218
+ // that we add back blank lines at the bottom of the terminal.
2219
+ // Similarly, if we are scrolling in the middle of the screen,
2220
+ // we must add blank lines to ensure that the bottom of the screen
2221
+ // does not move up.
2222
+ if (this.numScrollbackLines > 0 ||
2223
+ console.childNodes.length > this.numScrollbackLines+y+h+incY) {
2224
+ for (var i = -incY; i-- > 0; ) {
2225
+ this.insertBlankLine(this.numScrollbackLines + y + h + incY,
2226
+ color, style);
2227
+ }
2228
+ }
2229
+ }
2230
+ } else {
2231
+ // Scrolling down
2232
+ for (var i = incY;
2233
+ i-- > 0 &&
2234
+ console.childNodes.length > this.numScrollbackLines + y + h; ) {
2235
+ console.removeChild(console.childNodes[this.numScrollbackLines+y+h]);
2236
+ }
2237
+ for (var i = incY; i--; ) {
2238
+ this.insertBlankLine(this.numScrollbackLines + y, color, style);
2239
+ }
2240
+ }
2241
+ } else {
2242
+ // Scrolling partial lines
2243
+ if (incY <= 0) {
2244
+ // Scrolling up or horizontally within a line
2245
+ for (var i = y + this.numScrollbackLines;
2246
+ i < y + this.numScrollbackLines + h;
2247
+ i++) {
2248
+ this.copyLineSegment(x + incX, i + incY, x, i, w);
2249
+ }
2250
+ } else {
2251
+ // Scrolling down
2252
+ for (var i = y + this.numScrollbackLines + h;
2253
+ i-- > y + this.numScrollbackLines; ) {
2254
+ this.copyLineSegment(x + incX, i + incY, x, i, w);
2255
+ }
2256
+ }
2257
+
2258
+ // Clear blank regions
2259
+ if (incX > 0) {
2260
+ this.clearRegion(x, y, incX, h, color, style);
2261
+ } else if (incX < 0) {
2262
+ this.clearRegion(x + w + incX, y, -incX, h, color, style);
2263
+ }
2264
+ if (incY > 0) {
2265
+ this.clearRegion(x, y, w, incY, color, style);
2266
+ } else if (incY < 0) {
2267
+ this.clearRegion(x, y + h + incY, w, -incY, color, style);
2268
+ }
2269
+ }
2270
+
2271
+ // Reset scroll position
2272
+ this.scrollable.scrollTop = (this.numScrollbackLines-scrollPos) *
2273
+ this.cursorHeight + 1;
2274
+
2275
+ // Move cursor back to its original position
2276
+ hidden ? this.showCursor(cx, cy) : this.putString(cx, cy, '', undefined);
2277
+ }
2278
+ };
2279
+
2280
+ VT100.prototype.copy = function(selection) {
2281
+ if (selection == undefined) {
2282
+ selection = this.selection();
2283
+ }
2284
+ this.internalClipboard = undefined;
2285
+ if (selection.length) {
2286
+ try {
2287
+ // IE
2288
+ this.cliphelper.value = selection;
2289
+ this.cliphelper.select();
2290
+ this.cliphelper.createTextRange().execCommand('copy');
2291
+ } catch (e) {
2292
+ this.internalClipboard = selection;
2293
+ }
2294
+ this.cliphelper.value = '';
2295
+ }
2296
+ };
2297
+
2298
+ VT100.prototype.copyLast = function() {
2299
+ // Opening the context menu can remove the selection. We try to prevent this
2300
+ // from happening, but that is not possible for all browsers. So, instead,
2301
+ // we compute the selection before showing the menu.
2302
+ this.copy(this.lastSelection);
2303
+ };
2304
+
2305
+ VT100.prototype.pasteFnc = function() {
2306
+ var clipboard = undefined;
2307
+ if (this.internalClipboard != undefined) {
2308
+ clipboard = this.internalClipboard;
2309
+ } else {
2310
+ try {
2311
+ this.cliphelper.value = '';
2312
+ this.cliphelper.createTextRange().execCommand('paste');
2313
+ clipboard = this.cliphelper.value;
2314
+ } catch (e) {
2315
+ }
2316
+ }
2317
+ this.cliphelper.value = '';
2318
+ if (clipboard && this.menu.style.visibility == 'hidden') {
2319
+ return function() {
2320
+ this.keysPressed('' + clipboard);
2321
+ };
2322
+ } else {
2323
+ return undefined;
2324
+ }
2325
+ };
2326
+
2327
+ VT100.prototype.toggleUTF = function() {
2328
+ this.utfEnabled = !this.utfEnabled;
2329
+
2330
+ // We always persist the last value that the user selected. Not necessarily
2331
+ // the last value that a random program requested.
2332
+ this.utfPreferred = this.utfEnabled;
2333
+ };
2334
+
2335
+ VT100.prototype.toggleBell = function() {
2336
+ this.visualBell = !this.visualBell;
2337
+ };
2338
+
2339
+ VT100.prototype.toggleSoftKeyboard = function() {
2340
+ this.softKeyboard = !this.softKeyboard;
2341
+ this.keyboardImage.style.visibility = this.softKeyboard ? 'visible' : '';
2342
+ };
2343
+
2344
+ VT100.prototype.deselectKeys = function(elem) {
2345
+ if (elem && elem.className == 'selected') {
2346
+ elem.className = '';
2347
+ }
2348
+ for (elem = elem.firstChild; elem; elem = elem.nextSibling) {
2349
+ this.deselectKeys(elem);
2350
+ }
2351
+ };
2352
+
2353
+ VT100.prototype.showSoftKeyboard = function() {
2354
+ // Make sure no key is currently selected
2355
+ this.lastSelectedKey = undefined;
2356
+ this.deselectKeys(this.keyboard);
2357
+ this.isShift = false;
2358
+ this.showShiftState(false);
2359
+ this.isCtrl = false;
2360
+ this.showCtrlState(false);
2361
+ this.isAlt = false;
2362
+ this.showAltState(false);
2363
+
2364
+ this.keyboard.style.left = '0px';
2365
+ this.keyboard.style.top = '0px';
2366
+ this.keyboard.style.width = this.container.offsetWidth + 'px';
2367
+ this.keyboard.style.height = this.container.offsetHeight + 'px';
2368
+ this.keyboard.style.visibility = 'hidden';
2369
+ this.keyboard.style.display = '';
2370
+
2371
+ var kbd = this.keyboard.firstChild;
2372
+ var scale = 1.0;
2373
+ var transform = this.getTransformName();
2374
+ if (transform) {
2375
+ kbd.style[transform] = '';
2376
+ if (kbd.offsetWidth > 0.9 * this.container.offsetWidth) {
2377
+ scale = (kbd.offsetWidth/
2378
+ this.container.offsetWidth)/0.9;
2379
+ }
2380
+ if (kbd.offsetHeight > 0.9 * this.container.offsetHeight) {
2381
+ scale = Math.max((kbd.offsetHeight/
2382
+ this.container.offsetHeight)/0.9);
2383
+ }
2384
+ var style = this.getTransformStyle(transform,
2385
+ scale > 1.0 ? scale : undefined);
2386
+ kbd.style[transform] = style;
2387
+ }
2388
+ if (transform == 'filter') {
2389
+ scale = 1.0;
2390
+ }
2391
+ kbd.style.left = ((this.container.offsetWidth -
2392
+ kbd.offsetWidth/scale)/2) + 'px';
2393
+ kbd.style.top = ((this.container.offsetHeight -
2394
+ kbd.offsetHeight/scale)/2) + 'px';
2395
+
2396
+ this.keyboard.style.visibility = 'visible';
2397
+ };
2398
+
2399
+ VT100.prototype.hideSoftKeyboard = function() {
2400
+ if (this.softKeyboard) {
2401
+ this.keyboard.style.display = 'none';
2402
+ }
2403
+ };
2404
+
2405
+ VT100.prototype.toggleCursorBlinking = function() {
2406
+ this.blinkingCursor = !this.blinkingCursor;
2407
+ };
2408
+
2409
+ VT100.prototype.about = function() {
2410
+ alert("VT100 Terminal Emulator " + "2.10 (revision 239)" +
2411
+ "\nCopyright 2008-2010 by Markus Gutschke\n" +
2412
+ "For more information check http://shellinabox.com");
2413
+ };
2414
+
2415
+ VT100.prototype.hideContextMenu = function() {
2416
+ this.menu.style.visibility = 'hidden';
2417
+ this.menu.style.top = '-100px';
2418
+ this.menu.style.left = '-100px';
2419
+ this.menu.style.width = '0px';
2420
+ this.menu.style.height = '0px';
2421
+ };
2422
+
2423
+ VT100.prototype.extendContextMenu = function(entries, actions) {
2424
+ };
2425
+
2426
+ VT100.prototype.showContextMenu = function(x, y) {
2427
+ this.menu.innerHTML =
2428
+ '<table class="popup" ' +
2429
+ 'cellpadding="0" cellspacing="0">' +
2430
+ '<tr><td>' +
2431
+ '<ul id="menuentries">' +
2432
+ '<li id="beginclipboard">Copy</li>' +
2433
+ '<li id="endclipboard">Paste</li>' +
2434
+ '<hr />' +
2435
+ '<li id="reset">Reset</li>' +
2436
+ '<hr />' +
2437
+ '<li id="beginconfig">' +
2438
+ (this.utfEnabled ? '<img src="enabled.gif" />' : '') +
2439
+ 'Unicode</li>' +
2440
+ '<li>' +
2441
+ (this.visualBell ? '<img src="enabled.gif" />' : '') +
2442
+ 'Visual Bell</li>'+
2443
+ '<li>' +
2444
+ (this.softKeyboard ? '<img src="enabled.gif" />' : '') +
2445
+ 'Onscreen Keyboard</li>' +
2446
+ '<li id="endconfig">' +
2447
+ (this.blinkingCursor ? '<img src="enabled.gif" />' : '') +
2448
+ 'Blinking Cursor</li>'+
2449
+ (this.usercss.firstChild ?
2450
+ '<hr id="beginusercss" />' +
2451
+ this.usercss.innerHTML +
2452
+ '<hr id="endusercss" />' :
2453
+ '<hr />') +
2454
+ '<li id="about">About...</li>' +
2455
+ '</ul>' +
2456
+ '</td></tr>' +
2457
+ '</table>';
2458
+
2459
+ var popup = this.menu.firstChild;
2460
+ var menuentries = this.getChildById(popup, 'menuentries');
2461
+
2462
+ // Determine menu entries that should be disabled
2463
+ this.lastSelection = this.selection();
2464
+ if (!this.lastSelection.length) {
2465
+ menuentries.firstChild.className
2466
+ = 'disabled';
2467
+ }
2468
+ var p = this.pasteFnc();
2469
+ if (!p) {
2470
+ menuentries.childNodes[1].className
2471
+ = 'disabled';
2472
+ }
2473
+
2474
+ // Actions for default items
2475
+ var actions = [ this.copyLast, p, this.reset,
2476
+ this.toggleUTF, this.toggleBell,
2477
+ this.toggleSoftKeyboard,
2478
+ this.toggleCursorBlinking ];
2479
+
2480
+ // Actions for user CSS styles (if any)
2481
+ for (var i = 0; i < this.usercssActions.length; ++i) {
2482
+ actions[actions.length] = this.usercssActions[i];
2483
+ }
2484
+ actions[actions.length] = this.about;
2485
+
2486
+ // Allow subclasses to dynamically add entries to the context menu
2487
+ this.extendContextMenu(menuentries, actions);
2488
+
2489
+ // Hook up event listeners
2490
+ for (var node = menuentries.firstChild, i = 0; node;
2491
+ node = node.nextSibling) {
2492
+ if (node.tagName == 'LI') {
2493
+ if (node.className != 'disabled') {
2494
+ this.addListener(node, 'mouseover',
2495
+ function(vt100, node) {
2496
+ return function() {
2497
+ node.className = 'hover';
2498
+ }
2499
+ }(this, node));
2500
+ this.addListener(node, 'mouseout',
2501
+ function(vt100, node) {
2502
+ return function() {
2503
+ node.className = '';
2504
+ }
2505
+ }(this, node));
2506
+ this.addListener(node, 'mousedown',
2507
+ function(vt100, action) {
2508
+ return function(event) {
2509
+ vt100.hideContextMenu();
2510
+ action.call(vt100);
2511
+ vt100.storeUserSettings();
2512
+ return vt100.cancelEvent(event || window.event);
2513
+ }
2514
+ }(this, actions[i]));
2515
+ this.addListener(node, 'mouseup',
2516
+ function(vt100) {
2517
+ return function(event) {
2518
+ return vt100.cancelEvent(event || window.event);
2519
+ }
2520
+ }(this));
2521
+ this.addListener(node, 'mouseclick',
2522
+ function(vt100) {
2523
+ return function(event) {
2524
+ return vt100.cancelEvent(event || window.event);
2525
+ }
2526
+ }());
2527
+ }
2528
+ i++;
2529
+ }
2530
+ }
2531
+
2532
+ // Position menu next to the mouse pointer
2533
+ this.menu.style.left = '0px';
2534
+ this.menu.style.top = '0px';
2535
+ this.menu.style.width = this.container.offsetWidth + 'px';
2536
+ this.menu.style.height = this.container.offsetHeight + 'px';
2537
+ popup.style.left = '0px';
2538
+ popup.style.top = '0px';
2539
+
2540
+ var margin = 2;
2541
+ if (x + popup.clientWidth >= this.container.offsetWidth - margin) {
2542
+ x = this.container.offsetWidth-popup.clientWidth - margin - 1;
2543
+ }
2544
+ if (x < margin) {
2545
+ x = margin;
2546
+ }
2547
+ if (y + popup.clientHeight >= this.container.offsetHeight - margin) {
2548
+ y = this.container.offsetHeight-popup.clientHeight - margin - 1;
2549
+ }
2550
+ if (y < margin) {
2551
+ y = margin;
2552
+ }
2553
+ popup.style.left = x + 'px';
2554
+ popup.style.top = y + 'px';
2555
+
2556
+ // Block all other interactions with the terminal emulator
2557
+ this.addListener(this.menu, 'click', function(vt100) {
2558
+ return function() {
2559
+ vt100.hideContextMenu();
2560
+ }
2561
+ }(this));
2562
+
2563
+ // Show the menu
2564
+ this.menu.style.visibility = '';
2565
+ };
2566
+
2567
+ VT100.prototype.keysPressed = function(ch) {
2568
+ for (var i = 0; i < ch.length; i++) {
2569
+ var c = ch.charCodeAt(i);
2570
+ this.vt100(c >= 7 && c <= 15 ||
2571
+ c == 24 || c == 26 || c == 27 || c >= 32
2572
+ ? String.fromCharCode(c) : '<' + c + '>');
2573
+ }
2574
+ };
2575
+
2576
+ VT100.prototype.applyModifiers = function(ch, event) {
2577
+ if (ch) {
2578
+ if (event.ctrlKey) {
2579
+ if (ch >= 32 && ch <= 127) {
2580
+ // For historic reasons, some control characters are treated specially
2581
+ switch (ch) {
2582
+ case /* 3 */ 51: ch = 27; break;
2583
+ case /* 4 */ 52: ch = 28; break;
2584
+ case /* 5 */ 53: ch = 29; break;
2585
+ case /* 6 */ 54: ch = 30; break;
2586
+ case /* 7 */ 55: ch = 31; break;
2587
+ case /* 8 */ 56: ch = 127; break;
2588
+ case /* ? */ 63: ch = 127; break;
2589
+ default: ch &= 31; break;
2590
+ }
2591
+ }
2592
+ }
2593
+ return String.fromCharCode(ch);
2594
+ } else {
2595
+ return undefined;
2596
+ }
2597
+ };
2598
+
2599
+ VT100.prototype.handleKey = function(event) {
2600
+ // this.vt100('H: c=' + event.charCode + ', k=' + event.keyCode +
2601
+ // (event.shiftKey || event.ctrlKey || event.altKey ||
2602
+ // event.metaKey ? ', ' +
2603
+ // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2604
+ // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2605
+ // '\r\n');
2606
+ var ch, key;
2607
+ if (typeof event.charCode != 'undefined') {
2608
+ // non-IE keypress events have a translated charCode value. Also, our
2609
+ // fake events generated when receiving keydown events include this data
2610
+ // on all browsers.
2611
+ ch = event.charCode;
2612
+ key = event.keyCode;
2613
+ } else {
2614
+ // When sending a keypress event, IE includes the translated character
2615
+ // code in the keyCode field.
2616
+ ch = event.keyCode;
2617
+ key = undefined;
2618
+ }
2619
+
2620
+ // Apply modifier keys (ctrl and shift)
2621
+ if (ch) {
2622
+ key = undefined;
2623
+ }
2624
+ ch = this.applyModifiers(ch, event);
2625
+
2626
+ // By this point, "ch" is either defined and contains the character code, or
2627
+ // it is undefined and "key" defines the code of a function key
2628
+ if (ch != undefined) {
2629
+ this.scrollable.scrollTop = this.numScrollbackLines *
2630
+ this.cursorHeight + 1;
2631
+ } else {
2632
+ if ((event.altKey || event.metaKey) && !event.shiftKey && !event.ctrlKey) {
2633
+ // Many programs have difficulties dealing with parametrized escape
2634
+ // sequences for function keys. Thus, if ALT is the only modifier
2635
+ // key, return Emacs-style keycodes for commonly used keys.
2636
+ switch (key) {
2637
+ case 33: /* Page Up */ ch = '\u001B<'; break;
2638
+ case 34: /* Page Down */ ch = '\u001B>'; break;
2639
+ case 37: /* Left */ ch = '\u001Bb'; break;
2640
+ case 38: /* Up */ ch = '\u001Bp'; break;
2641
+ case 39: /* Right */ ch = '\u001Bf'; break;
2642
+ case 40: /* Down */ ch = '\u001Bn'; break;
2643
+ case 46: /* Delete */ ch = '\u001Bd'; break;
2644
+ default: break;
2645
+ }
2646
+ } else if (event.shiftKey && !event.ctrlKey &&
2647
+ !event.altKey && !event.metaKey) {
2648
+ switch (key) {
2649
+ case 33: /* Page Up */ this.scrollBack(); return;
2650
+ case 34: /* Page Down */ this.scrollFore(); return;
2651
+ default: break;
2652
+ }
2653
+ }
2654
+ if (ch == undefined) {
2655
+ switch (key) {
2656
+ // Firefox > 15 changes some of the codes, see
2657
+ // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
2658
+ case 163: /* # */ ch = this.applyModifiers(35, event); break;
2659
+ case 173: /* - */ ch = this.applyModifiers(45, event); break;
2660
+
2661
+ case 8: /* Backspace */ ch = '\u007f'; break;
2662
+ case 9: /* Tab */ ch = '\u0009'; break;
2663
+ case 10: /* Return */ ch = '\u000A'; break;
2664
+ case 13: /* Enter */ ch = this.crLfMode ?
2665
+ '\r\n' : '\r'; break;
2666
+ case 16: /* Shift */ return;
2667
+ case 17: /* Ctrl */ return;
2668
+ case 18: /* Alt */ return;
2669
+ case 19: /* Break */ return;
2670
+ case 20: /* Caps Lock */ return;
2671
+ case 27: /* Escape */ ch = '\u001B'; break;
2672
+ case 33: /* Page Up */ ch = '\u001B[5~'; break;
2673
+ case 34: /* Page Down */ ch = '\u001B[6~'; break;
2674
+ case 35: /* End */ ch = '\u001BOF'; break;
2675
+ case 36: /* Home */ ch = '\u001BOH'; break;
2676
+ case 37: /* Left */ ch = this.cursorKeyMode ?
2677
+ '\u001BOD' : '\u001B[D'; break;
2678
+ case 38: /* Up */ ch = this.cursorKeyMode ?
2679
+ '\u001BOA' : '\u001B[A'; break;
2680
+ case 39: /* Right */ ch = this.cursorKeyMode ?
2681
+ '\u001BOC' : '\u001B[C'; break;
2682
+ case 40: /* Down */ ch = this.cursorKeyMode ?
2683
+ '\u001BOB' : '\u001B[B'; break;
2684
+ case 45: /* Insert */ ch = '\u001B[2~'; break;
2685
+ case 46: /* Delete */ ch = '\u001B[3~'; break;
2686
+ case 91: /* Left Window */ return;
2687
+ case 92: /* Right Window */ return;
2688
+ case 93: /* Select */ return;
2689
+ case 96: /* 0 */ ch = this.applyModifiers(48, event); break;
2690
+ case 97: /* 1 */ ch = this.applyModifiers(49, event); break;
2691
+ case 98: /* 2 */ ch = this.applyModifiers(50, event); break;
2692
+ case 99: /* 3 */ ch = this.applyModifiers(51, event); break;
2693
+ case 100: /* 4 */ ch = this.applyModifiers(52, event); break;
2694
+ case 101: /* 5 */ ch = this.applyModifiers(53, event); break;
2695
+ case 102: /* 6 */ ch = this.applyModifiers(54, event); break;
2696
+ case 103: /* 7 */ ch = this.applyModifiers(55, event); break;
2697
+ case 104: /* 8 */ ch = this.applyModifiers(56, event); break;
2698
+ case 105: /* 9 */ ch = this.applyModifiers(58, event); break;
2699
+ case 106: /* * */ ch = this.applyModifiers(42, event); break;
2700
+ case 107: /* + */ ch = this.applyModifiers(43, event); break;
2701
+ case 109: /* - */ ch = this.applyModifiers(45, event); break;
2702
+ case 110: /* . */ ch = this.applyModifiers(46, event); break;
2703
+ case 111: /* / */ ch = this.applyModifiers(47, event); break;
2704
+ case 112: /* F1 */ ch = '\u001BOP'; break;
2705
+ case 113: /* F2 */ ch = '\u001BOQ'; break;
2706
+ case 114: /* F3 */ ch = '\u001BOR'; break;
2707
+ case 115: /* F4 */ ch = '\u001BOS'; break;
2708
+ case 116: /* F5 */ ch = '\u001B[15~'; break;
2709
+ case 117: /* F6 */ ch = '\u001B[17~'; break;
2710
+ case 118: /* F7 */ ch = '\u001B[18~'; break;
2711
+ case 119: /* F8 */ ch = '\u001B[19~'; break;
2712
+ case 120: /* F9 */ ch = '\u001B[20~'; break;
2713
+ case 121: /* F10 */ ch = '\u001B[21~'; break;
2714
+ case 122: /* F11 */ ch = '\u001B[23~'; break;
2715
+ case 123: /* F12 */ ch = '\u001B[24~'; break;
2716
+ case 144: /* Num Lock */ return;
2717
+ case 145: /* Scroll Lock */ return;
2718
+ case 186: /* ; */ ch = this.applyModifiers(59, event); break;
2719
+ case 187: /* = */ ch = this.applyModifiers(61, event); break;
2720
+ case 188: /* , */ ch = this.applyModifiers(44, event); break;
2721
+ case 189: /* - */ ch = this.applyModifiers(45, event); break;
2722
+ case 190: /* . */ ch = this.applyModifiers(46, event); break;
2723
+ case 191: /* / */ ch = this.applyModifiers(47, event); break;
2724
+ case 192: /* ` */ ch = this.applyModifiers(96, event); break;
2725
+ case 219: /* [ */ ch = this.applyModifiers(91, event); break;
2726
+ case 220: /* \ */ ch = this.applyModifiers(92, event); break;
2727
+ case 221: /* ] */ ch = this.applyModifiers(93, event); break;
2728
+ case 222: /* ' */ ch = this.applyModifiers(39, event); break;
2729
+ default: return;
2730
+ }
2731
+ this.scrollable.scrollTop = this.numScrollbackLines *
2732
+ this.cursorHeight + 1;
2733
+ }
2734
+ }
2735
+
2736
+ // "ch" now contains the sequence of keycodes to send. But we might still
2737
+ // have to apply the effects of modifier keys.
2738
+ if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
2739
+ var start, digit, part1, part2;
2740
+ if ((start = ch.substr(0, 2)) == '\u001B[') {
2741
+ for (part1 = start;
2742
+ part1.length < ch.length &&
2743
+ (digit = ch.charCodeAt(part1.length)) >= 48 && digit <= 57; ) {
2744
+ part1 = ch.substr(0, part1.length + 1);
2745
+ }
2746
+ part2 = ch.substr(part1.length);
2747
+ if (part1.length > 2) {
2748
+ part1 += ';';
2749
+ }
2750
+ } else if (start == '\u001BO') {
2751
+ part1 = start;
2752
+ part2 = ch.substr(2);
2753
+ }
2754
+ if (part1 != undefined) {
2755
+ ch = part1 +
2756
+ ((event.shiftKey ? 1 : 0) +
2757
+ (event.altKey|event.metaKey ? 2 : 0) +
2758
+ (event.ctrlKey ? 4 : 0)) +
2759
+ part2;
2760
+ } else if (ch.length == 1 && (event.altKey || event.metaKey)) {
2761
+ ch = '\u001B' + ch;
2762
+ }
2763
+ }
2764
+
2765
+ if (this.menu.style.visibility == 'hidden') {
2766
+ // this.vt100('R: c=');
2767
+ // for (var i = 0; i < ch.length; i++)
2768
+ // this.vt100((i != 0 ? ', ' : '') + ch.charCodeAt(i));
2769
+ // this.vt100('\r\n');
2770
+ this.keysPressed(ch);
2771
+ }
2772
+ };
2773
+
2774
+ VT100.prototype.inspect = function(o, d) {
2775
+ if (d == undefined) {
2776
+ d = 0;
2777
+ }
2778
+ var rc = '';
2779
+ if (typeof o == 'object' && ++d < 2) {
2780
+ rc = '[\r\n';
2781
+ for (i in o) {
2782
+ rc += this.spaces(d * 2) + i + ' -> ';
2783
+ try {
2784
+ rc += this.inspect(o[i], d);
2785
+ } catch (e) {
2786
+ rc += '?' + '?' + '?\r\n';
2787
+ }
2788
+ }
2789
+ rc += ']\r\n';
2790
+ } else {
2791
+ rc += ('' + o).replace(/\n/g, ' ').replace(/ +/g,' ') + '\r\n';
2792
+ }
2793
+ return rc;
2794
+ };
2795
+
2796
+ VT100.prototype.checkComposedKeys = function(event) {
2797
+ // Composed keys (at least on Linux) do not generate normal events.
2798
+ // Instead, they get entered into the text field. We normally catch
2799
+ // this on the next keyup event.
2800
+ var s = this.input.value;
2801
+ if (s.length) {
2802
+ this.input.value = '';
2803
+ if (this.menu.style.visibility == 'hidden') {
2804
+ this.keysPressed(s);
2805
+ }
2806
+ }
2807
+ };
2808
+
2809
+ VT100.prototype.fixEvent = function(event) {
2810
+ // Some browsers report AltGR as a combination of ALT and CTRL. As AltGr
2811
+ // is used as a second-level selector, clear the modifier bits before
2812
+ // handling the event.
2813
+ if (event.ctrlKey && event.altKey) {
2814
+ var fake = [ ];
2815
+ fake.charCode = event.charCode;
2816
+ fake.keyCode = event.keyCode;
2817
+ fake.ctrlKey = false;
2818
+ fake.shiftKey = event.shiftKey;
2819
+ fake.altKey = false;
2820
+ fake.metaKey = event.metaKey;
2821
+ return fake;
2822
+ }
2823
+
2824
+ // Some browsers fail to translate keys, if both shift and alt/meta is
2825
+ // pressed at the same time. We try to translate those cases, but that
2826
+ // only works for US keyboard layouts.
2827
+ if (event.shiftKey) {
2828
+ var u = undefined;
2829
+ var s = undefined;
2830
+ switch (this.lastNormalKeyDownEvent.keyCode) {
2831
+ // Firefox > 15 changes some of the codes, see
2832
+ // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
2833
+ case 163: /* # -> ~ */ u = 96; s = 126; break;
2834
+ case 173: /* - -> _ */ u = 45; s = 95; break;
2835
+
2836
+ case 39: /* ' -> " */ u = 39; s = 34; break;
2837
+ case 44: /* , -> < */ u = 44; s = 60; break;
2838
+ case 45: /* - -> _ */ u = 45; s = 95; break;
2839
+ case 46: /* . -> > */ u = 46; s = 62; break;
2840
+ case 47: /* / -> ? */ u = 47; s = 63; break;
2841
+
2842
+ case 48: /* 0 -> ) */ u = 48; s = 41; break;
2843
+ case 49: /* 1 -> ! */ u = 49; s = 33; break;
2844
+ case 50: /* 2 -> @ */ u = 50; s = 64; break;
2845
+ case 51: /* 3 -> # */ u = 51; s = 35; break;
2846
+ case 52: /* 4 -> $ */ u = 52; s = 36; break;
2847
+ case 53: /* 5 -> % */ u = 53; s = 37; break;
2848
+ case 54: /* 6 -> ^ */ u = 54; s = 94; break;
2849
+ case 55: /* 7 -> & */ u = 55; s = 38; break;
2850
+ case 56: /* 8 -> * */ u = 56; s = 42; break;
2851
+ case 57: /* 9 -> ( */ u = 57; s = 40; break;
2852
+
2853
+ case 59: /* ; -> : */ u = 59; s = 58; break;
2854
+ case 61: /* = -> + */ u = 61; s = 43; break;
2855
+ case 91: /* [ -> { */ u = 91; s = 123; break;
2856
+ case 92: /* \ -> | */ u = 92; s = 124; break;
2857
+ case 93: /* ] -> } */ u = 93; s = 125; break;
2858
+ case 96: /* ` -> ~ */ u = 96; s = 126; break;
2859
+
2860
+ case 109: /* - -> _ */ u = 45; s = 95; break;
2861
+ case 111: /* / -> ? */ u = 47; s = 63; break;
2862
+
2863
+ case 186: /* ; -> : */ u = 59; s = 58; break;
2864
+ case 187: /* = -> + */ u = 61; s = 43; break;
2865
+ case 188: /* , -> < */ u = 44; s = 60; break;
2866
+ case 189: /* - -> _ */ u = 45; s = 95; break;
2867
+ case 190: /* . -> > */ u = 46; s = 62; break;
2868
+ case 191: /* / -> ? */ u = 47; s = 63; break;
2869
+ case 192: /* ` -> ~ */ u = 96; s = 126; break;
2870
+ case 219: /* [ -> { */ u = 91; s = 123; break;
2871
+ case 220: /* \ -> | */ u = 92; s = 124; break;
2872
+ case 221: /* ] -> } */ u = 93; s = 125; break;
2873
+ case 222: /* ' -> " */ u = 39; s = 34; break;
2874
+ default: break;
2875
+ }
2876
+ if (s && (event.charCode == u || event.charCode == 0)) {
2877
+ var fake = [ ];
2878
+ fake.charCode = s;
2879
+ fake.keyCode = event.keyCode;
2880
+ fake.ctrlKey = event.ctrlKey;
2881
+ fake.shiftKey = event.shiftKey;
2882
+ fake.altKey = event.altKey;
2883
+ fake.metaKey = event.metaKey;
2884
+ return fake;
2885
+ }
2886
+ }
2887
+ return event;
2888
+ };
2889
+
2890
+ VT100.prototype.keyDown = function(event) {
2891
+ // this.vt100('D: c=' + event.charCode + ', k=' + event.keyCode +
2892
+ // (event.shiftKey || event.ctrlKey || event.altKey ||
2893
+ // event.metaKey ? ', ' +
2894
+ // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2895
+ // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2896
+ // '\r\n');
2897
+ this.checkComposedKeys(event);
2898
+ this.lastKeyPressedEvent = undefined;
2899
+ this.lastKeyDownEvent = undefined;
2900
+ this.lastNormalKeyDownEvent = event;
2901
+
2902
+ var asciiKey =
2903
+ event.keyCode == 32 ||
2904
+ event.keyCode >= 48 && event.keyCode <= 57 ||
2905
+ event.keyCode >= 65 && event.keyCode <= 90;
2906
+ var alphNumKey =
2907
+ asciiKey ||
2908
+ event.keyCode >= 96 && event.keyCode <= 105 ||
2909
+ event.keyCode == 226;
2910
+ var normalKey =
2911
+ alphNumKey ||
2912
+ event.keyCode == 59 || event.keyCode == 61 ||
2913
+ event.keyCode == 106 || event.keyCode == 107 ||
2914
+ event.keyCode >= 109 && event.keyCode <= 111 ||
2915
+ event.keyCode >= 186 && event.keyCode <= 192 ||
2916
+ event.keyCode >= 219 && event.keyCode <= 223 ||
2917
+ event.keyCode == 252;
2918
+ try {
2919
+ if (navigator.appName == 'Konqueror') {
2920
+ normalKey |= event.keyCode < 128;
2921
+ }
2922
+ } catch (e) {
2923
+ }
2924
+
2925
+ // We normally prefer to look at keypress events, as they perform the
2926
+ // translation from keyCode to charCode. This is important, as the
2927
+ // translation is locale-dependent.
2928
+ // But for some keys, we must intercept them during the keydown event,
2929
+ // as they would otherwise get interpreted by the browser.
2930
+ // Even, when doing all of this, there are some keys that we can never
2931
+ // intercept. This applies to some of the menu navigation keys in IE.
2932
+ // In fact, we see them, but we cannot stop IE from seeing them, too.
2933
+ if ((event.charCode || event.keyCode) &&
2934
+ ((alphNumKey && (event.ctrlKey || event.altKey || event.metaKey) &&
2935
+ !event.shiftKey &&
2936
+ // Some browsers signal AltGR as both CTRL and ALT. Do not try to
2937
+ // interpret this sequence ourselves, as some keyboard layouts use
2938
+ // it for second-level layouts.
2939
+ !(event.ctrlKey && event.altKey)) ||
2940
+ this.catchModifiersEarly && normalKey && !alphNumKey &&
2941
+ (event.ctrlKey || event.altKey || event.metaKey) ||
2942
+ !normalKey)) {
2943
+ this.lastKeyDownEvent = event;
2944
+ var fake = [ ];
2945
+ fake.ctrlKey = event.ctrlKey;
2946
+ fake.shiftKey = event.shiftKey;
2947
+ fake.altKey = event.altKey;
2948
+ fake.metaKey = event.metaKey;
2949
+ if (asciiKey) {
2950
+ fake.charCode = event.keyCode;
2951
+ fake.keyCode = 0;
2952
+ } else {
2953
+ fake.charCode = 0;
2954
+ fake.keyCode = event.keyCode;
2955
+ if (!alphNumKey && event.shiftKey) {
2956
+ fake = this.fixEvent(fake);
2957
+ }
2958
+ }
2959
+
2960
+ this.handleKey(fake);
2961
+ this.lastNormalKeyDownEvent = undefined;
2962
+
2963
+ try {
2964
+ // For non-IE browsers
2965
+ event.stopPropagation();
2966
+ event.preventDefault();
2967
+ } catch (e) {
2968
+ }
2969
+ try {
2970
+ // For IE
2971
+ event.cancelBubble = true;
2972
+ event.returnValue = false;
2973
+ event.keyCode = 0;
2974
+ } catch (e) {
2975
+ }
2976
+
2977
+ return false;
2978
+ }
2979
+ return true;
2980
+ };
2981
+
2982
+ VT100.prototype.keyPressed = function(event) {
2983
+ // this.vt100('P: c=' + event.charCode + ', k=' + event.keyCode +
2984
+ // (event.shiftKey || event.ctrlKey || event.altKey ||
2985
+ // event.metaKey ? ', ' +
2986
+ // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
2987
+ // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
2988
+ // '\r\n');
2989
+ if (this.lastKeyDownEvent) {
2990
+ // If we already processed the key on keydown, do not process it
2991
+ // again here. Ideally, the browser should not even have generated a
2992
+ // keypress event in this case. But that does not appear to always work.
2993
+ this.lastKeyDownEvent = undefined;
2994
+ } else {
2995
+ this.handleKey(event.altKey || event.metaKey
2996
+ ? this.fixEvent(event) : event);
2997
+ }
2998
+
2999
+ try {
3000
+ // For non-IE browsers
3001
+ event.preventDefault();
3002
+ } catch (e) {
3003
+ }
3004
+
3005
+ try {
3006
+ // For IE
3007
+ event.cancelBubble = true;
3008
+ event.returnValue = false;
3009
+ event.keyCode = 0;
3010
+ } catch (e) {
3011
+ }
3012
+
3013
+ this.lastNormalKeyDownEvent = undefined;
3014
+ this.lastKeyPressedEvent = event;
3015
+ return false;
3016
+ };
3017
+
3018
+ VT100.prototype.keyUp = function(event) {
3019
+ // this.vt100('U: c=' + event.charCode + ', k=' + event.keyCode +
3020
+ // (event.shiftKey || event.ctrlKey || event.altKey ||
3021
+ // event.metaKey ? ', ' +
3022
+ // (event.shiftKey ? 'S' : '') + (event.ctrlKey ? 'C' : '') +
3023
+ // (event.altKey ? 'A' : '') + (event.metaKey ? 'M' : '') : '') +
3024
+ // '\r\n');
3025
+ if (this.lastKeyPressedEvent) {
3026
+ // The compose key on Linux occasionally confuses the browser and keeps
3027
+ // inserting bogus characters into the input field, even if just a regular
3028
+ // key has been pressed. Detect this case and drop the bogus characters.
3029
+ (event.target ||
3030
+ event.srcElement).value = '';
3031
+ } else {
3032
+ // This is usually were we notice that a key has been composed and
3033
+ // thus failed to generate normal events.
3034
+ this.checkComposedKeys(event);
3035
+
3036
+ // Some browsers don't report keypress events if ctrl or alt is pressed
3037
+ // for non-alphanumerical keys. Patch things up for now, but in the
3038
+ // future we will catch these keys earlier (in the keydown handler).
3039
+ if (this.lastNormalKeyDownEvent) {
3040
+ // this.vt100('ENABLING EARLY CATCHING OF MODIFIER KEYS\r\n');
3041
+ this.catchModifiersEarly = true;
3042
+ var asciiKey =
3043
+ event.keyCode == 32 ||
3044
+ event.keyCode >= 48 && event.keyCode <= 57 ||
3045
+ event.keyCode >= 65 && event.keyCode <= 90;
3046
+ var alphNumKey =
3047
+ asciiKey ||
3048
+ event.keyCode >= 96 && event.keyCode <= 105;
3049
+ var normalKey =
3050
+ alphNumKey ||
3051
+ event.keyCode == 59 || event.keyCode == 61 ||
3052
+ event.keyCode == 106 || event.keyCode == 107 ||
3053
+ event.keyCode >= 109 && event.keyCode <= 111 ||
3054
+ event.keyCode >= 186 && event.keyCode <= 192 ||
3055
+ event.keyCode >= 219 && event.keyCode <= 223 ||
3056
+ event.keyCode == 252;
3057
+ var fake = [ ];
3058
+ fake.ctrlKey = event.ctrlKey;
3059
+ fake.shiftKey = event.shiftKey;
3060
+ fake.altKey = event.altKey;
3061
+ fake.metaKey = event.metaKey;
3062
+ if (asciiKey) {
3063
+ fake.charCode = event.keyCode;
3064
+ fake.keyCode = 0;
3065
+ } else {
3066
+ fake.charCode = 0;
3067
+ fake.keyCode = event.keyCode;
3068
+ if (!alphNumKey && (event.ctrlKey || event.altKey || event.metaKey)) {
3069
+ fake = this.fixEvent(fake);
3070
+ }
3071
+ }
3072
+ this.lastNormalKeyDownEvent = undefined;
3073
+ this.handleKey(fake);
3074
+ }
3075
+ }
3076
+
3077
+ try {
3078
+ // For IE
3079
+ event.cancelBubble = true;
3080
+ event.returnValue = false;
3081
+ event.keyCode = 0;
3082
+ } catch (e) {
3083
+ }
3084
+
3085
+ this.lastKeyDownEvent = undefined;
3086
+ this.lastKeyPressedEvent = undefined;
3087
+ return false;
3088
+ };
3089
+
3090
+ VT100.prototype.animateCursor = function(inactive) {
3091
+ if (!this.cursorInterval) {
3092
+ this.cursorInterval = setInterval(
3093
+ function(vt100) {
3094
+ return function() {
3095
+ vt100.animateCursor();
3096
+
3097
+ // Use this opportunity to check whether the user entered a composed
3098
+ // key, or whether somebody pasted text into the textfield.
3099
+ vt100.checkComposedKeys();
3100
+ }
3101
+ }(this), 500);
3102
+ }
3103
+ if (inactive != undefined || this.cursor.className != 'inactive') {
3104
+ if (inactive) {
3105
+ this.cursor.className = 'inactive';
3106
+ } else {
3107
+ if (this.blinkingCursor) {
3108
+ this.cursor.className = this.cursor.className == 'bright'
3109
+ ? 'dim' : 'bright';
3110
+ } else {
3111
+ this.cursor.className = 'bright';
3112
+ }
3113
+ }
3114
+ }
3115
+ };
3116
+
3117
+ VT100.prototype.blurCursor = function() {
3118
+ this.animateCursor(true);
3119
+ };
3120
+
3121
+ VT100.prototype.focusCursor = function() {
3122
+ this.animateCursor(false);
3123
+ };
3124
+
3125
+ VT100.prototype.flashScreen = function() {
3126
+ this.isInverted = !this.isInverted;
3127
+ this.refreshInvertedState();
3128
+ this.isInverted = !this.isInverted;
3129
+ setTimeout(function(vt100) {
3130
+ return function() {
3131
+ vt100.refreshInvertedState();
3132
+ };
3133
+ }(this), 100);
3134
+ };
3135
+
3136
+ VT100.prototype.beep = function() {
3137
+ if (this.visualBell) {
3138
+ this.flashScreen();
3139
+ } else {
3140
+ try {
3141
+ this.beeper.Play();
3142
+ } catch (e) {
3143
+ try {
3144
+ this.beeper.src = 'beep.wav';
3145
+ } catch (e) {
3146
+ }
3147
+ }
3148
+ }
3149
+ };
3150
+
3151
+ VT100.prototype.bs = function() {
3152
+ if (this.cursorX > 0) {
3153
+ this.gotoXY(this.cursorX - 1, this.cursorY);
3154
+ this.needWrap = false;
3155
+ }
3156
+ };
3157
+
3158
+ VT100.prototype.ht = function(count) {
3159
+ if (count == undefined) {
3160
+ count = 1;
3161
+ }
3162
+ var cx = this.cursorX;
3163
+ while (count-- > 0) {
3164
+ while (cx++ < this.terminalWidth) {
3165
+ var tabState = this.userTabStop[cx];
3166
+ if (tabState == false) {
3167
+ // Explicitly cleared tab stop
3168
+ continue;
3169
+ } else if (tabState) {
3170
+ // Explicitly set tab stop
3171
+ break;
3172
+ } else {
3173
+ // Default tab stop at each eighth column
3174
+ if (cx % 8 == 0) {
3175
+ break;
3176
+ }
3177
+ }
3178
+ }
3179
+ }
3180
+ if (cx > this.terminalWidth - 1) {
3181
+ cx = this.terminalWidth - 1;
3182
+ }
3183
+ if (cx != this.cursorX) {
3184
+ this.gotoXY(cx, this.cursorY);
3185
+ }
3186
+ };
3187
+
3188
+ VT100.prototype.rt = function(count) {
3189
+ if (count == undefined) {
3190
+ count = 1 ;
3191
+ }
3192
+ var cx = this.cursorX;
3193
+ while (count-- > 0) {
3194
+ while (cx-- > 0) {
3195
+ var tabState = this.userTabStop[cx];
3196
+ if (tabState == false) {
3197
+ // Explicitly cleared tab stop
3198
+ continue;
3199
+ } else if (tabState) {
3200
+ // Explicitly set tab stop
3201
+ break;
3202
+ } else {
3203
+ // Default tab stop at each eighth column
3204
+ if (cx % 8 == 0) {
3205
+ break;
3206
+ }
3207
+ }
3208
+ }
3209
+ }
3210
+ if (cx < 0) {
3211
+ cx = 0;
3212
+ }
3213
+ if (cx != this.cursorX) {
3214
+ this.gotoXY(cx, this.cursorY);
3215
+ }
3216
+ };
3217
+
3218
+ VT100.prototype.cr = function() {
3219
+ this.gotoXY(0, this.cursorY);
3220
+ this.needWrap = false;
3221
+ };
3222
+
3223
+ VT100.prototype.lf = function(count) {
3224
+ if (count == undefined) {
3225
+ count = 1;
3226
+ } else {
3227
+ if (count > this.terminalHeight) {
3228
+ count = this.terminalHeight;
3229
+ }
3230
+ if (count < 1) {
3231
+ count = 1;
3232
+ }
3233
+ }
3234
+ while (count-- > 0) {
3235
+ if (this.cursorY == this.bottom - 1) {
3236
+ this.scrollRegion(0, this.top + 1,
3237
+ this.terminalWidth, this.bottom - this.top - 1,
3238
+ 0, -1, this.color, this.style);
3239
+ offset = undefined;
3240
+ } else if (this.cursorY < this.terminalHeight - 1) {
3241
+ this.gotoXY(this.cursorX, this.cursorY + 1);
3242
+ }
3243
+ }
3244
+ };
3245
+
3246
+ VT100.prototype.ri = function(count) {
3247
+ if (count == undefined) {
3248
+ count = 1;
3249
+ } else {
3250
+ if (count > this.terminalHeight) {
3251
+ count = this.terminalHeight;
3252
+ }
3253
+ if (count < 1) {
3254
+ count = 1;
3255
+ }
3256
+ }
3257
+ while (count-- > 0) {
3258
+ if (this.cursorY == this.top) {
3259
+ this.scrollRegion(0, this.top,
3260
+ this.terminalWidth, this.bottom - this.top - 1,
3261
+ 0, 1, this.color, this.style);
3262
+ } else if (this.cursorY > 0) {
3263
+ this.gotoXY(this.cursorX, this.cursorY - 1);
3264
+ }
3265
+ }
3266
+ this.needWrap = false;
3267
+ };
3268
+
3269
+ VT100.prototype.respondID = function() {
3270
+ this.respondString += '\u001B[?6c';
3271
+ };
3272
+
3273
+ VT100.prototype.respondSecondaryDA = function() {
3274
+ this.respondString += '\u001B[>0;0;0c';
3275
+ };
3276
+
3277
+
3278
+ VT100.prototype.updateStyle = function() {
3279
+ this.style = '';
3280
+ if (this.attr & 0x0200 /* ATTR_UNDERLINE */) {
3281
+ this.style = 'text-decoration: underline;';
3282
+ }
3283
+ var bg = (this.attr >> 4) & 0xF;
3284
+ var fg = this.attr & 0xF;
3285
+ if (this.attr & 0x0100 /* ATTR_REVERSE */) {
3286
+ var tmp = bg;
3287
+ bg = fg;
3288
+ fg = tmp;
3289
+ }
3290
+ if ((this.attr & (0x0100 /* ATTR_REVERSE */ | 0x0400 /* ATTR_DIM */)) == 0x0400 /* ATTR_DIM */) {
3291
+ fg = 8; // Dark grey
3292
+ } else if (this.attr & 0x0800 /* ATTR_BRIGHT */) {
3293
+ fg |= 8;
3294
+ this.style = 'font-weight: bold;';
3295
+ }
3296
+ if (this.attr & 0x1000 /* ATTR_BLINK */) {
3297
+ this.style = 'text-decoration: blink;';
3298
+ }
3299
+ this.color = 'ansi' + fg + ' bgAnsi' + bg;
3300
+ };
3301
+
3302
+ VT100.prototype.setAttrColors = function(attr) {
3303
+ if (attr != this.attr) {
3304
+ this.attr = attr;
3305
+ this.updateStyle();
3306
+ }
3307
+ };
3308
+
3309
+ VT100.prototype.saveCursor = function() {
3310
+ this.savedX[this.currentScreen] = this.cursorX;
3311
+ this.savedY[this.currentScreen] = this.cursorY;
3312
+ this.savedAttr[this.currentScreen] = this.attr;
3313
+ this.savedUseGMap = this.useGMap;
3314
+ for (var i = 0; i < 4; i++) {
3315
+ this.savedGMap[i] = this.GMap[i];
3316
+ }
3317
+ this.savedValid[this.currentScreen] = true;
3318
+ };
3319
+
3320
+ VT100.prototype.restoreCursor = function() {
3321
+ if (!this.savedValid[this.currentScreen]) {
3322
+ return;
3323
+ }
3324
+ this.attr = this.savedAttr[this.currentScreen];
3325
+ this.updateStyle();
3326
+ this.useGMap = this.savedUseGMap;
3327
+ for (var i = 0; i < 4; i++) {
3328
+ this.GMap[i] = this.savedGMap[i];
3329
+ }
3330
+ this.translate = this.GMap[this.useGMap];
3331
+ this.needWrap = false;
3332
+ this.gotoXY(this.savedX[this.currentScreen],
3333
+ this.savedY[this.currentScreen]);
3334
+ };
3335
+
3336
+ VT100.prototype.getTransformName = function() {
3337
+ var styles = [ 'transform', 'WebkitTransform', 'MozTransform', 'filter' ];
3338
+ for (var i = 0; i < styles.length; ++i) {
3339
+ if (typeof this.console[0].style[styles[i]] != 'undefined') {
3340
+ return styles[i];
3341
+ }
3342
+ }
3343
+ return undefined;
3344
+ };
3345
+
3346
+ VT100.prototype.getTransformStyle = function(transform, scale) {
3347
+ return scale && scale != 1.0
3348
+ ? transform == 'filter'
3349
+ ? 'progid:DXImageTransform.Microsoft.Matrix(' +
3350
+ 'M11=' + (1.0/scale) + ',M12=0,M21=0,M22=1,' +
3351
+ "sizingMethod='auto expand')"
3352
+ : 'translateX(-50%) ' +
3353
+ 'scaleX(' + (1.0/scale) + ') ' +
3354
+ 'translateX(50%)'
3355
+ : '';
3356
+ };
3357
+
3358
+ VT100.prototype.set80_132Mode = function(state) {
3359
+ var transform = this.getTransformName();
3360
+ if (transform) {
3361
+ if ((this.console[this.currentScreen].style[transform] != '') == state) {
3362
+ return;
3363
+ }
3364
+ var style = state ?
3365
+ this.getTransformStyle(transform, 1.65):'';
3366
+ this.console[this.currentScreen].style[transform] = style;
3367
+ this.cursor.style[transform] = style;
3368
+ this.space.style[transform] = style;
3369
+ this.scale = state ? 1.65 : 1.0;
3370
+ if (transform == 'filter') {
3371
+ this.console[this.currentScreen].style.width = state ? '165%' : '';
3372
+ }
3373
+ this.resizer();
3374
+ }
3375
+ };
3376
+
3377
+ VT100.prototype.setMode = function(state) {
3378
+ for (var i = 0; i <= this.npar; i++) {
3379
+ if (this.isQuestionMark) {
3380
+ switch (this.par[i]) {
3381
+ case 1: this.cursorKeyMode = state; break;
3382
+ case 3: this.set80_132Mode(state); break;
3383
+ case 5: this.isInverted = state; this.refreshInvertedState(); break;
3384
+ case 6: this.offsetMode = state; break;
3385
+ case 7: this.autoWrapMode = state; break;
3386
+ case 1000:
3387
+ case 9: this.mouseReporting = state; break;
3388
+ case 25: this.cursorNeedsShowing = state;
3389
+ if (state) { this.showCursor(); }
3390
+ else { this.hideCursor(); } break;
3391
+ case 1047:
3392
+ case 1049:
3393
+ case 47: this.enableAlternateScreen(state); break;
3394
+ default: break;
3395
+ }
3396
+ } else {
3397
+ switch (this.par[i]) {
3398
+ case 3: this.dispCtrl = state; break;
3399
+ case 4: this.insertMode = state; break;
3400
+ case 20:this.crLfMode = state; break;
3401
+ default: break;
3402
+ }
3403
+ }
3404
+ }
3405
+ };
3406
+
3407
+ VT100.prototype.statusReport = function() {
3408
+ // Ready and operational.
3409
+ this.respondString += '\u001B[0n';
3410
+ };
3411
+
3412
+ VT100.prototype.cursorReport = function() {
3413
+ this.respondString += '\u001B[' +
3414
+ (this.cursorY + (this.offsetMode ? this.top + 1 : 1)) +
3415
+ ';' +
3416
+ (this.cursorX + 1) +
3417
+ 'R';
3418
+ };
3419
+
3420
+ VT100.prototype.setCursorAttr = function(setAttr, xorAttr) {
3421
+ // Changing of cursor color is not implemented.
3422
+ };
3423
+
3424
+ VT100.prototype.openPrinterWindow = function() {
3425
+ var rc = true;
3426
+ try {
3427
+ if (!this.printWin || this.printWin.closed) {
3428
+ this.printWin = window.open('', 'print-output',
3429
+ 'width=800,height=600,directories=no,location=no,menubar=yes,' +
3430
+ 'status=no,toolbar=no,titlebar=yes,scrollbars=yes,resizable=yes');
3431
+ this.printWin.document.body.innerHTML =
3432
+ '<link rel="stylesheet" href="' +
3433
+ document.location.protocol + '//' + document.location.host +
3434
+ document.location.pathname.replace(/[^/]*$/, '') +
3435
+ 'print-styles.css" type="text/css">\n' +
3436
+ '<div id="options"><input id="autoprint" type="checkbox"' +
3437
+ (this.autoprint ? ' checked' : '') + '>' +
3438
+ 'Automatically, print page(s) when job is ready' +
3439
+ '</input></div>\n' +
3440
+ '<div id="spacer"><input type="checkbox">&nbsp;</input></div>' +
3441
+ '<pre id="print"></pre>\n';
3442
+ var autoprint = this.printWin.document.getElementById('autoprint');
3443
+ this.addListener(autoprint, 'click',
3444
+ (function(vt100, autoprint) {
3445
+ return function() {
3446
+ vt100.autoprint = autoprint.checked;
3447
+ vt100.storeUserSettings();
3448
+ return false;
3449
+ };
3450
+ })(this, autoprint));
3451
+ this.printWin.document.title = 'ShellInABox Printer Output';
3452
+ }
3453
+ } catch (e) {
3454
+ // Maybe, a popup blocker prevented us from working. Better catch the
3455
+ // exception, so that we won't break the entire terminal session. The
3456
+ // user probably needs to disable the blocker first before retrying the
3457
+ // operation.
3458
+ rc = false;
3459
+ }
3460
+ rc &= this.printWin && !this.printWin.closed &&
3461
+ (this.printWin.innerWidth ||
3462
+ this.printWin.document.documentElement.clientWidth ||
3463
+ this.printWin.document.body.clientWidth) > 1;
3464
+
3465
+ if (!rc && this.printing == 100) {
3466
+ // Different popup blockers work differently. We try to detect a couple
3467
+ // of common methods. And then we retry again a brief amount later, as
3468
+ // false positives are otherwise possible. If we are sure that there is
3469
+ // a popup blocker in effect, we alert the user to it. This is helpful
3470
+ // as some popup blockers have minimal or no UI, and the user might not
3471
+ // notice that they are missing the popup. In any case, we only show at
3472
+ // most one message per print job.
3473
+ this.printing = true;
3474
+ setTimeout((function(win) {
3475
+ return function() {
3476
+ if (!win || win.closed ||
3477
+ (win.innerWidth ||
3478
+ win.document.documentElement.clientWidth ||
3479
+ win.document.body.clientWidth) <= 1) {
3480
+ alert('Attempted to print, but a popup blocker ' +
3481
+ 'prevented the printer window from opening');
3482
+ }
3483
+ };
3484
+ })(this.printWin), 2000);
3485
+ }
3486
+ return rc;
3487
+ };
3488
+
3489
+ VT100.prototype.sendToPrinter = function(s) {
3490
+ this.openPrinterWindow();
3491
+ try {
3492
+ var doc = this.printWin.document;
3493
+ var print = doc.getElementById('print');
3494
+ if (print.lastChild && print.lastChild.nodeName == '#text') {
3495
+ print.lastChild.textContent += this.replaceChar(s, ' ', '\u00A0');
3496
+ } else {
3497
+ print.appendChild(doc.createTextNode(this.replaceChar(s, ' ','\u00A0')));
3498
+ }
3499
+ } catch (e) {
3500
+ // There probably was a more aggressive popup blocker that prevented us
3501
+ // from accessing the printer windows.
3502
+ }
3503
+ };
3504
+
3505
+ VT100.prototype.sendControlToPrinter = function(ch) {
3506
+ // We get called whenever doControl() is active. But for the printer, we
3507
+ // only implement a basic line printer that doesn't understand most of
3508
+ // the escape sequences of the VT100 terminal. In fact, the only escape
3509
+ // sequence that we really need to recognize is '^[[5i' for turning the
3510
+ // printer off.
3511
+ try {
3512
+ switch (ch) {
3513
+ case 9:
3514
+ // HT
3515
+ this.openPrinterWindow();
3516
+ var doc = this.printWin.document;
3517
+ var print = doc.getElementById('print');
3518
+ var chars = print.lastChild &&
3519
+ print.lastChild.nodeName == '#text' ?
3520
+ print.lastChild.textContent.length : 0;
3521
+ this.sendToPrinter(this.spaces(8 - (chars % 8)));
3522
+ break;
3523
+ case 10:
3524
+ // CR
3525
+ break;
3526
+ case 12:
3527
+ // FF
3528
+ this.openPrinterWindow();
3529
+ var pageBreak = this.printWin.document.createElement('div');
3530
+ pageBreak.className = 'pagebreak';
3531
+ pageBreak.innerHTML = '<hr />';
3532
+ this.printWin.document.getElementById('print').appendChild(pageBreak);
3533
+ break;
3534
+ case 13:
3535
+ // LF
3536
+ this.openPrinterWindow();
3537
+ var lineBreak = this.printWin.document.createElement('br');
3538
+ this.printWin.document.getElementById('print').appendChild(lineBreak);
3539
+ break;
3540
+ case 27:
3541
+ // ESC
3542
+ this.isEsc = 1 /* ESesc */;
3543
+ break;
3544
+ default:
3545
+ switch (this.isEsc) {
3546
+ case 1 /* ESesc */:
3547
+ this.isEsc = 0 /* ESnormal */;
3548
+ switch (ch) {
3549
+ case 0x5B /*[*/:
3550
+ this.isEsc = 2 /* ESsquare */;
3551
+ break;
3552
+ default:
3553
+ break;
3554
+ }
3555
+ break;
3556
+ case 2 /* ESsquare */:
3557
+ this.npar = 0;
3558
+ this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3559
+ 0, 0, 0, 0, 0, 0, 0, 0 ];
3560
+ this.isEsc = 3 /* ESgetpars */;
3561
+ this.isQuestionMark = ch == 0x3F /*?*/;
3562
+ if (this.isQuestionMark) {
3563
+ break;
3564
+ }
3565
+ // Fall through
3566
+ case 3 /* ESgetpars */:
3567
+ if (ch == 0x3B /*;*/) {
3568
+ this.npar++;
3569
+ break;
3570
+ } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3571
+ var par = this.par[this.npar];
3572
+ if (par == undefined) {
3573
+ par = 0;
3574
+ }
3575
+ this.par[this.npar] = 10*par + (ch & 0xF);
3576
+ break;
3577
+ } else {
3578
+ this.isEsc = 4 /* ESgotpars */;
3579
+ }
3580
+ // Fall through
3581
+ case 4 /* ESgotpars */:
3582
+ this.isEsc = 0 /* ESnormal */;
3583
+ if (this.isQuestionMark) {
3584
+ break;
3585
+ }
3586
+ switch (ch) {
3587
+ case 0x69 /*i*/:
3588
+ this.csii(this.par[0]);
3589
+ break;
3590
+ default:
3591
+ break;
3592
+ }
3593
+ break;
3594
+ default:
3595
+ this.isEsc = 0 /* ESnormal */;
3596
+ break;
3597
+ }
3598
+ break;
3599
+ }
3600
+ } catch (e) {
3601
+ // There probably was a more aggressive popup blocker that prevented us
3602
+ // from accessing the printer windows.
3603
+ }
3604
+ };
3605
+
3606
+ VT100.prototype.csiAt = function(number) {
3607
+ // Insert spaces
3608
+ if (number == 0) {
3609
+ number = 1;
3610
+ }
3611
+ if (number > this.terminalWidth - this.cursorX) {
3612
+ number = this.terminalWidth - this.cursorX;
3613
+ }
3614
+ this.scrollRegion(this.cursorX, this.cursorY,
3615
+ this.terminalWidth - this.cursorX - number, 1,
3616
+ number, 0, this.color, this.style);
3617
+ this.needWrap = false;
3618
+ };
3619
+
3620
+ VT100.prototype.csii = function(number) {
3621
+ // Printer control
3622
+ switch (number) {
3623
+ case 0: // Print Screen
3624
+ window.print();
3625
+ break;
3626
+ case 4: // Stop printing
3627
+ try {
3628
+ if (this.printing && this.printWin && !this.printWin.closed) {
3629
+ var print = this.printWin.document.getElementById('print');
3630
+ while (print.lastChild &&
3631
+ print.lastChild.tagName == 'DIV' &&
3632
+ print.lastChild.className == 'pagebreak') {
3633
+ // Remove trailing blank pages
3634
+ print.removeChild(print.lastChild);
3635
+ }
3636
+ if (this.autoprint) {
3637
+ this.printWin.print();
3638
+ }
3639
+ }
3640
+ } catch (e) {
3641
+ }
3642
+ this.printing = false;
3643
+ break;
3644
+ case 5: // Start printing
3645
+ if (!this.printing && this.printWin && !this.printWin.closed) {
3646
+ this.printWin.document.getElementById('print').innerHTML = '';
3647
+ }
3648
+ this.printing = 100;
3649
+ break;
3650
+ default:
3651
+ break;
3652
+ }
3653
+ };
3654
+
3655
+ VT100.prototype.csiJ = function(number) {
3656
+ switch (number) {
3657
+ case 0: // Erase from cursor to end of display
3658
+ this.clearRegion(this.cursorX, this.cursorY,
3659
+ this.terminalWidth - this.cursorX, 1,
3660
+ this.color, this.style);
3661
+ if (this.cursorY < this.terminalHeight-2) {
3662
+ this.clearRegion(0, this.cursorY+1,
3663
+ this.terminalWidth, this.terminalHeight-this.cursorY-1,
3664
+ this.color, this.style);
3665
+ }
3666
+ break;
3667
+ case 1: // Erase from start to cursor
3668
+ if (this.cursorY > 0) {
3669
+ this.clearRegion(0, 0,
3670
+ this.terminalWidth, this.cursorY,
3671
+ this.color, this.style);
3672
+ }
3673
+ this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3674
+ this.color, this.style);
3675
+ break;
3676
+ case 2: // Erase whole display
3677
+ this.clearRegion(0, 0, this.terminalWidth, this.terminalHeight,
3678
+ this.color, this.style);
3679
+ break;
3680
+ default:
3681
+ return;
3682
+ }
3683
+ needWrap = false;
3684
+ };
3685
+
3686
+ VT100.prototype.csiK = function(number) {
3687
+ switch (number) {
3688
+ case 0: // Erase from cursor to end of line
3689
+ this.clearRegion(this.cursorX, this.cursorY,
3690
+ this.terminalWidth - this.cursorX, 1,
3691
+ this.color, this.style);
3692
+ break;
3693
+ case 1: // Erase from start of line to cursor
3694
+ this.clearRegion(0, this.cursorY, this.cursorX + 1, 1,
3695
+ this.color, this.style);
3696
+ break;
3697
+ case 2: // Erase whole line
3698
+ this.clearRegion(0, this.cursorY, this.terminalWidth, 1,
3699
+ this.color, this.style);
3700
+ break;
3701
+ default:
3702
+ return;
3703
+ }
3704
+ needWrap = false;
3705
+ };
3706
+
3707
+ VT100.prototype.csiL = function(number) {
3708
+ // Open line by inserting blank line(s)
3709
+ if (this.cursorY >= this.bottom) {
3710
+ return;
3711
+ }
3712
+ if (number == 0) {
3713
+ number = 1;
3714
+ }
3715
+ if (number > this.bottom - this.cursorY) {
3716
+ number = this.bottom - this.cursorY;
3717
+ }
3718
+ this.scrollRegion(0, this.cursorY,
3719
+ this.terminalWidth, this.bottom - this.cursorY - number,
3720
+ 0, number, this.color, this.style);
3721
+ needWrap = false;
3722
+ };
3723
+
3724
+ VT100.prototype.csiM = function(number) {
3725
+ // Delete line(s), scrolling up the bottom of the screen.
3726
+ if (this.cursorY >= this.bottom) {
3727
+ return;
3728
+ }
3729
+ if (number == 0) {
3730
+ number = 1;
3731
+ }
3732
+ if (number > this.bottom - this.cursorY) {
3733
+ number = bottom - cursorY;
3734
+ }
3735
+ this.scrollRegion(0, this.cursorY + number,
3736
+ this.terminalWidth, this.bottom - this.cursorY - number,
3737
+ 0, -number, this.color, this.style);
3738
+ needWrap = false;
3739
+ };
3740
+
3741
+ VT100.prototype.csim = function() {
3742
+ for (var i = 0; i <= this.npar; i++) {
3743
+ switch (this.par[i]) {
3744
+ case 0: this.attr = 0x00F0 /* ATTR_DEFAULT */; break;
3745
+ case 1: this.attr = (this.attr & ~0x0400 /* ATTR_DIM */)|0x0800 /* ATTR_BRIGHT */; break;
3746
+ case 2: this.attr = (this.attr & ~0x0800 /* ATTR_BRIGHT */)|0x0400 /* ATTR_DIM */; break;
3747
+ case 4: this.attr |= 0x0200 /* ATTR_UNDERLINE */; break;
3748
+ case 5: this.attr |= 0x1000 /* ATTR_BLINK */; break;
3749
+ case 7: this.attr |= 0x0100 /* ATTR_REVERSE */; break;
3750
+ case 10:
3751
+ this.translate = this.GMap[this.useGMap];
3752
+ this.dispCtrl = false;
3753
+ this.toggleMeta = false;
3754
+ break;
3755
+ case 11:
3756
+ this.translate = this.CodePage437Map;
3757
+ this.dispCtrl = true;
3758
+ this.toggleMeta = false;
3759
+ break;
3760
+ case 12:
3761
+ this.translate = this.CodePage437Map;
3762
+ this.dispCtrl = true;
3763
+ this.toggleMeta = true;
3764
+ break;
3765
+ case 21:
3766
+ case 22: this.attr &= ~(0x0800 /* ATTR_BRIGHT */|0x0400 /* ATTR_DIM */); break;
3767
+ case 24: this.attr &= ~ 0x0200 /* ATTR_UNDERLINE */; break;
3768
+ case 25: this.attr &= ~ 0x1000 /* ATTR_BLINK */; break;
3769
+ case 27: this.attr &= ~ 0x0100 /* ATTR_REVERSE */; break;
3770
+ case 38: this.attr = (this.attr & ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0F))|
3771
+ 0x0200 /* ATTR_UNDERLINE */; break;
3772
+ case 39: this.attr &= ~(0x0400 /* ATTR_DIM */|0x0800 /* ATTR_BRIGHT */|0x0200 /* ATTR_UNDERLINE */|0x0F); break;
3773
+ case 49: this.attr |= 0xF0; break;
3774
+ default:
3775
+ if (this.par[i] >= 30 && this.par[i] <= 37) {
3776
+ var fg = this.par[i] - 30;
3777
+ this.attr = (this.attr & ~0x0F) | fg;
3778
+ } else if (this.par[i] >= 40 && this.par[i] <= 47) {
3779
+ var bg = this.par[i] - 40;
3780
+ this.attr = (this.attr & ~0xF0) | (bg << 4);
3781
+ }
3782
+ break;
3783
+ }
3784
+ }
3785
+ this.updateStyle();
3786
+ };
3787
+
3788
+ VT100.prototype.csiP = function(number) {
3789
+ // Delete character(s) following cursor
3790
+ if (number == 0) {
3791
+ number = 1;
3792
+ }
3793
+ if (number > this.terminalWidth - this.cursorX) {
3794
+ number = this.terminalWidth - this.cursorX;
3795
+ }
3796
+ this.scrollRegion(this.cursorX + number, this.cursorY,
3797
+ this.terminalWidth - this.cursorX - number, 1,
3798
+ -number, 0, this.color, this.style);
3799
+ needWrap = false;
3800
+ };
3801
+
3802
+ VT100.prototype.csiX = function(number) {
3803
+ // Clear characters following cursor
3804
+ if (number == 0) {
3805
+ number++;
3806
+ }
3807
+ if (number > this.terminalWidth - this.cursorX) {
3808
+ number = this.terminalWidth - this.cursorX;
3809
+ }
3810
+ this.clearRegion(this.cursorX, this.cursorY, number, 1,
3811
+ this.color, this.style);
3812
+ needWrap = false;
3813
+ };
3814
+
3815
+ VT100.prototype.settermCommand = function() {
3816
+ // Setterm commands are not implemented
3817
+ };
3818
+
3819
+ VT100.prototype.doControl = function(ch) {
3820
+ if (this.printing) {
3821
+ this.sendControlToPrinter(ch);
3822
+ return '';
3823
+ }
3824
+ var lineBuf = '';
3825
+ switch (ch) {
3826
+ case 0x00: /* ignored */ break;
3827
+ case 0x08: this.bs(); break;
3828
+ case 0x09: this.ht(); break;
3829
+ case 0x0A:
3830
+ case 0x0B:
3831
+ case 0x0C:
3832
+ case 0x84: this.lf(); if (!this.crLfMode) break;
3833
+ case 0x0D: this.cr(); break;
3834
+ case 0x85: this.cr(); this.lf(); break;
3835
+ case 0x0E: this.useGMap = 1;
3836
+ this.translate = this.GMap[1];
3837
+ this.dispCtrl = true; break;
3838
+ case 0x0F: this.useGMap = 0;
3839
+ this.translate = this.GMap[0];
3840
+ this.dispCtrl = false; break;
3841
+ case 0x18:
3842
+ case 0x1A: this.isEsc = 0 /* ESnormal */; break;
3843
+ case 0x1B: this.isEsc = 1 /* ESesc */; break;
3844
+ case 0x7F: /* ignored */ break;
3845
+ case 0x88: this.userTabStop[this.cursorX] = true; break;
3846
+ case 0x8D: this.ri(); break;
3847
+ case 0x8E: this.isEsc = 18 /* ESss2 */; break;
3848
+ case 0x8F: this.isEsc = 19 /* ESss3 */; break;
3849
+ case 0x9A: this.respondID(); break;
3850
+ case 0x9B: this.isEsc = 2 /* ESsquare */; break;
3851
+ case 0x07: if (this.isEsc != 17 /* EStitle */) {
3852
+ this.beep(); break;
3853
+ }
3854
+ /* fall thru */
3855
+ default: switch (this.isEsc) {
3856
+ case 1 /* ESesc */:
3857
+ this.isEsc = 0 /* ESnormal */;
3858
+ switch (ch) {
3859
+ /*%*/ case 0x25: this.isEsc = 13 /* ESpercent */; break;
3860
+ /*(*/ case 0x28: this.isEsc = 8 /* ESsetG0 */; break;
3861
+ /*-*/ case 0x2D:
3862
+ /*)*/ case 0x29: this.isEsc = 9 /* ESsetG1 */; break;
3863
+ /*.*/ case 0x2E:
3864
+ /***/ case 0x2A: this.isEsc = 10 /* ESsetG2 */; break;
3865
+ /*/*/ case 0x2F:
3866
+ /*+*/ case 0x2B: this.isEsc = 11 /* ESsetG3 */; break;
3867
+ /*#*/ case 0x23: this.isEsc = 7 /* EShash */; break;
3868
+ /*7*/ case 0x37: this.saveCursor(); break;
3869
+ /*8*/ case 0x38: this.restoreCursor(); break;
3870
+ /*>*/ case 0x3E: this.applKeyMode = false; break;
3871
+ /*=*/ case 0x3D: this.applKeyMode = true; break;
3872
+ /*D*/ case 0x44: this.lf(); break;
3873
+ /*E*/ case 0x45: this.cr(); this.lf(); break;
3874
+ /*M*/ case 0x4D: this.ri(); break;
3875
+ /*N*/ case 0x4E: this.isEsc = 18 /* ESss2 */; break;
3876
+ /*O*/ case 0x4F: this.isEsc = 19 /* ESss3 */; break;
3877
+ /*H*/ case 0x48: this.userTabStop[this.cursorX] = true; break;
3878
+ /*Z*/ case 0x5A: this.respondID(); break;
3879
+ /*[*/ case 0x5B: this.isEsc = 2 /* ESsquare */; break;
3880
+ /*]*/ case 0x5D: this.isEsc = 15 /* ESnonstd */; break;
3881
+ /*c*/ case 0x63: this.reset(); break;
3882
+ /*g*/ case 0x67: this.flashScreen(); break;
3883
+ default: break;
3884
+ }
3885
+ break;
3886
+ case 15 /* ESnonstd */:
3887
+ switch (ch) {
3888
+ /*0*/ case 0x30:
3889
+ /*1*/ case 0x31:
3890
+ /*2*/ case 0x32: this.isEsc = 17 /* EStitle */; this.titleString = ''; break;
3891
+ /*P*/ case 0x50: this.npar = 0; this.par = [ 0, 0, 0, 0, 0, 0, 0 ];
3892
+ this.isEsc = 16 /* ESpalette */; break;
3893
+ /*R*/ case 0x52: // Palette support is not implemented
3894
+ this.isEsc = 0 /* ESnormal */; break;
3895
+ default: this.isEsc = 0 /* ESnormal */; break;
3896
+ }
3897
+ break;
3898
+ case 16 /* ESpalette */:
3899
+ if ((ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) ||
3900
+ (ch >= 0x41 /*A*/ && ch <= 0x46 /*F*/) ||
3901
+ (ch >= 0x61 /*a*/ && ch <= 0x66 /*f*/)) {
3902
+ this.par[this.npar++] = ch > 0x39 /*9*/ ? (ch & 0xDF) - 55
3903
+ : (ch & 0xF);
3904
+ if (this.npar == 7) {
3905
+ // Palette support is not implemented
3906
+ this.isEsc = 0 /* ESnormal */;
3907
+ }
3908
+ } else {
3909
+ this.isEsc = 0 /* ESnormal */;
3910
+ }
3911
+ break;
3912
+ case 2 /* ESsquare */:
3913
+ this.npar = 0;
3914
+ this.par = [ 0, 0, 0, 0, 0, 0, 0, 0,
3915
+ 0, 0, 0, 0, 0, 0, 0, 0 ];
3916
+ this.isEsc = 3 /* ESgetpars */;
3917
+ /*[*/ if (ch == 0x5B) { // Function key
3918
+ this.isEsc = 6 /* ESfunckey */;
3919
+ break;
3920
+ } else {
3921
+ /*?*/ this.isQuestionMark = ch == 0x3F;
3922
+ if (this.isQuestionMark) {
3923
+ break;
3924
+ }
3925
+ }
3926
+ // Fall through
3927
+ case 5 /* ESdeviceattr */:
3928
+ case 3 /* ESgetpars */:
3929
+ /*;*/ if (ch == 0x3B) {
3930
+ this.npar++;
3931
+ break;
3932
+ } else if (ch >= 0x30 /*0*/ && ch <= 0x39 /*9*/) {
3933
+ var par = this.par[this.npar];
3934
+ if (par == undefined) {
3935
+ par = 0;
3936
+ }
3937
+ this.par[this.npar] = 10*par + (ch & 0xF);
3938
+ break;
3939
+ } else if (this.isEsc == 5 /* ESdeviceattr */) {
3940
+ switch (ch) {
3941
+ /*c*/ case 0x63: if (this.par[0] == 0) this.respondSecondaryDA(); break;
3942
+ /*m*/ case 0x6D: /* (re)set key modifier resource values */ break;
3943
+ /*n*/ case 0x6E: /* disable key modifier resource values */ break;
3944
+ /*p*/ case 0x70: /* set pointer mode resource value */ break;
3945
+ default: break;
3946
+ }
3947
+ this.isEsc = 0 /* ESnormal */;
3948
+ break;
3949
+ } else {
3950
+ this.isEsc = 4 /* ESgotpars */;
3951
+ }
3952
+ // Fall through
3953
+ case 4 /* ESgotpars */:
3954
+ this.isEsc = 0 /* ESnormal */;
3955
+ if (this.isQuestionMark) {
3956
+ switch (ch) {
3957
+ /*h*/ case 0x68: this.setMode(true); break;
3958
+ /*l*/ case 0x6C: this.setMode(false); break;
3959
+ /*c*/ case 0x63: this.setCursorAttr(this.par[2], this.par[1]); break;
3960
+ default: break;
3961
+ }
3962
+ this.isQuestionMark = false;
3963
+ break;
3964
+ }
3965
+ switch (ch) {
3966
+ /*!*/ case 0x21: this.isEsc = 12 /* ESbang */; break;
3967
+ /*>*/ case 0x3E: if (!this.npar) this.isEsc = 5 /* ESdeviceattr */; break;
3968
+ /*G*/ case 0x47:
3969
+ /*`*/ case 0x60: this.gotoXY(this.par[0] - 1, this.cursorY); break;
3970
+ /*A*/ case 0x41: this.gotoXY(this.cursorX,
3971
+ this.cursorY - (this.par[0] ? this.par[0] : 1));
3972
+ break;
3973
+ /*B*/ case 0x42:
3974
+ /*e*/ case 0x65: this.gotoXY(this.cursorX,
3975
+ this.cursorY + (this.par[0] ? this.par[0] : 1));
3976
+ break;
3977
+ /*C*/ case 0x43:
3978
+ /*a*/ case 0x61: this.gotoXY(this.cursorX + (this.par[0] ? this.par[0] : 1),
3979
+ this.cursorY); break;
3980
+ /*D*/ case 0x44: this.gotoXY(this.cursorX - (this.par[0] ? this.par[0] : 1),
3981
+ this.cursorY); break;
3982
+ /*E*/ case 0x45: this.gotoXY(0, this.cursorY + (this.par[0] ? this.par[0] :1));
3983
+ break;
3984
+ /*F*/ case 0x46: this.gotoXY(0, this.cursorY - (this.par[0] ? this.par[0] :1));
3985
+ break;
3986
+ /*d*/ case 0x64: this.gotoXaY(this.cursorX, this.par[0] - 1); break;
3987
+ /*H*/ case 0x48:
3988
+ /*f*/ case 0x66: this.gotoXaY(this.par[1] - 1, this.par[0] - 1); break;
3989
+ /*I*/ case 0x49: this.ht(this.par[0] ? this.par[0] : 1); break;
3990
+ /*@*/ case 0x40: this.csiAt(this.par[0]); break;
3991
+ /*i*/ case 0x69: this.csii(this.par[0]); break;
3992
+ /*J*/ case 0x4A: this.csiJ(this.par[0]); break;
3993
+ /*K*/ case 0x4B: this.csiK(this.par[0]); break;
3994
+ /*L*/ case 0x4C: this.csiL(this.par[0]); break;
3995
+ /*M*/ case 0x4D: this.csiM(this.par[0]); break;
3996
+ /*m*/ case 0x6D: this.csim(); break;
3997
+ /*P*/ case 0x50: this.csiP(this.par[0]); break;
3998
+ /*X*/ case 0x58: this.csiX(this.par[0]); break;
3999
+ /*S*/ case 0x53: this.lf(this.par[0] ? this.par[0] : 1); break;
4000
+ /*T*/ case 0x54: this.ri(this.par[0] ? this.par[0] : 1); break;
4001
+ /*c*/ case 0x63: if (!this.par[0]) this.respondID(); break;
4002
+ /*g*/ case 0x67: if (this.par[0] == 0) {
4003
+ this.userTabStop[this.cursorX] = false;
4004
+ } else if (this.par[0] == 2 || this.par[0] == 3) {
4005
+ this.userTabStop = [ ];
4006
+ for (var i = 0; i < this.terminalWidth; i++) {
4007
+ this.userTabStop[i] = false;
4008
+ }
4009
+ }
4010
+ break;
4011
+ /*h*/ case 0x68: this.setMode(true); break;
4012
+ /*l*/ case 0x6C: this.setMode(false); break;
4013
+ /*n*/ case 0x6E: switch (this.par[0]) {
4014
+ case 5: this.statusReport(); break;
4015
+ case 6: this.cursorReport(); break;
4016
+ default: break;
4017
+ }
4018
+ break;
4019
+ /*q*/ case 0x71: // LED control not implemented
4020
+ break;
4021
+ /*r*/ case 0x72: var t = this.par[0] ? this.par[0] : 1;
4022
+ var b = this.par[1] ? this.par[1]
4023
+ : this.terminalHeight;
4024
+ if (t < b && b <= this.terminalHeight) {
4025
+ this.top = t - 1;
4026
+ this.bottom= b;
4027
+ this.gotoXaY(0, 0);
4028
+ }
4029
+ break;
4030
+ /*b*/ case 0x62: var c = this.par[0] ? this.par[0] : 1;
4031
+ if (c > this.terminalWidth * this.terminalHeight) {
4032
+ c = this.terminalWidth * this.terminalHeight;
4033
+ }
4034
+ while (c-- > 0) {
4035
+ lineBuf += this.lastCharacter;
4036
+ }
4037
+ break;
4038
+ /*s*/ case 0x73: this.saveCursor(); break;
4039
+ /*u*/ case 0x75: this.restoreCursor(); break;
4040
+ /*Z*/ case 0x5A: this.rt(this.par[0] ? this.par[0] : 1); break;
4041
+ /*]*/ case 0x5D: this.settermCommand(); break;
4042
+ default: break;
4043
+ }
4044
+ break;
4045
+ case 12 /* ESbang */:
4046
+ if (ch == 'p') {
4047
+ this.reset();
4048
+ }
4049
+ this.isEsc = 0 /* ESnormal */;
4050
+ break;
4051
+ case 13 /* ESpercent */:
4052
+ this.isEsc = 0 /* ESnormal */;
4053
+ switch (ch) {
4054
+ /*@*/ case 0x40: this.utfEnabled = false; break;
4055
+ /*G*/ case 0x47:
4056
+ /*8*/ case 0x38: this.utfEnabled = true; break;
4057
+ default: break;
4058
+ }
4059
+ break;
4060
+ case 6 /* ESfunckey */:
4061
+ this.isEsc = 0 /* ESnormal */; break;
4062
+ case 7 /* EShash */:
4063
+ this.isEsc = 0 /* ESnormal */;
4064
+ /*8*/ if (ch == 0x38) {
4065
+ // Screen alignment test not implemented
4066
+ }
4067
+ break;
4068
+ case 8 /* ESsetG0 */:
4069
+ case 9 /* ESsetG1 */:
4070
+ case 10 /* ESsetG2 */:
4071
+ case 11 /* ESsetG3 */:
4072
+ var g = this.isEsc - 8 /* ESsetG0 */;
4073
+ this.isEsc = 0 /* ESnormal */;
4074
+ switch (ch) {
4075
+ /*0*/ case 0x30: this.GMap[g] = this.VT100GraphicsMap; break;
4076
+ /*A*/ case 0x42:
4077
+ /*B*/ case 0x42: this.GMap[g] = this.Latin1Map; break;
4078
+ /*U*/ case 0x55: this.GMap[g] = this.CodePage437Map; break;
4079
+ /*K*/ case 0x4B: this.GMap[g] = this.DirectToFontMap; break;
4080
+ default: break;
4081
+ }
4082
+ if (this.useGMap == g) {
4083
+ this.translate = this.GMap[g];
4084
+ }
4085
+ break;
4086
+ case 17 /* EStitle */:
4087
+ if (ch == 0x07) {
4088
+ if (this.titleString && this.titleString.charAt(0) == ';') {
4089
+ this.titleString = this.titleString.substr(1);
4090
+ if (this.titleString != '') {
4091
+ this.titleString += ' - ';
4092
+ }
4093
+ this.titleString += 'Shell In A Box'
4094
+ }
4095
+ try {
4096
+ window.document.title = this.titleString;
4097
+ } catch (e) {
4098
+ }
4099
+ this.isEsc = 0 /* ESnormal */;
4100
+ } else {
4101
+ this.titleString += String.fromCharCode(ch);
4102
+ }
4103
+ break;
4104
+ case 18 /* ESss2 */:
4105
+ case 19 /* ESss3 */:
4106
+ if (ch < 256) {
4107
+ ch = this.GMap[this.isEsc - 18 /* ESss2 */ + 2]
4108
+ [this.toggleMeta ? (ch | 0x80) : ch];
4109
+ if ((ch & 0xFF00) == 0xF000) {
4110
+ ch = ch & 0xFF;
4111
+ } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4112
+ this.isEsc = 0 /* ESnormal */; break;
4113
+ }
4114
+ }
4115
+ this.lastCharacter = String.fromCharCode(ch);
4116
+ lineBuf += this.lastCharacter;
4117
+ this.isEsc = 0 /* ESnormal */; break;
4118
+ default:
4119
+ this.isEsc = 0 /* ESnormal */; break;
4120
+ }
4121
+ break;
4122
+ }
4123
+ return lineBuf;
4124
+ };
4125
+
4126
+ VT100.prototype.renderString = function(s, showCursor) {
4127
+ if (this.printing) {
4128
+ this.sendToPrinter(s);
4129
+ if (showCursor) {
4130
+ this.showCursor();
4131
+ }
4132
+ return;
4133
+ }
4134
+
4135
+ // We try to minimize the number of DOM operations by coalescing individual
4136
+ // characters into strings. This is a significant performance improvement.
4137
+ var incX = s.length;
4138
+ if (incX > this.terminalWidth - this.cursorX) {
4139
+ incX = this.terminalWidth - this.cursorX;
4140
+ if (incX <= 0) {
4141
+ return;
4142
+ }
4143
+ s = s.substr(0, incX - 1) + s.charAt(s.length - 1);
4144
+ }
4145
+ if (showCursor) {
4146
+ // Minimize the number of calls to putString(), by avoiding a direct
4147
+ // call to this.showCursor()
4148
+ this.cursor.style.visibility = '';
4149
+ }
4150
+ this.putString(this.cursorX, this.cursorY, s, this.color, this.style);
4151
+ };
4152
+
4153
+ VT100.prototype.vt100 = function(s) {
4154
+ this.cursorNeedsShowing = this.hideCursor();
4155
+ this.respondString = '';
4156
+ var lineBuf = '';
4157
+ for (var i = 0; i < s.length; i++) {
4158
+ var ch = s.charCodeAt(i);
4159
+ if (this.utfEnabled) {
4160
+ // Decode UTF8 encoded character
4161
+ if (ch > 0x7F) {
4162
+ if (this.utfCount > 0 && (ch & 0xC0) == 0x80) {
4163
+ this.utfChar = (this.utfChar << 6) | (ch & 0x3F);
4164
+ if (--this.utfCount <= 0) {
4165
+ if (this.utfChar > 0xFFFF || this.utfChar < 0) {
4166
+ ch = 0xFFFD;
4167
+ } else {
4168
+ ch = this.utfChar;
4169
+ }
4170
+ } else {
4171
+ continue;
4172
+ }
4173
+ } else {
4174
+ if ((ch & 0xE0) == 0xC0) {
4175
+ this.utfCount = 1;
4176
+ this.utfChar = ch & 0x1F;
4177
+ } else if ((ch & 0xF0) == 0xE0) {
4178
+ this.utfCount = 2;
4179
+ this.utfChar = ch & 0x0F;
4180
+ } else if ((ch & 0xF8) == 0xF0) {
4181
+ this.utfCount = 3;
4182
+ this.utfChar = ch & 0x07;
4183
+ } else if ((ch & 0xFC) == 0xF8) {
4184
+ this.utfCount = 4;
4185
+ this.utfChar = ch & 0x03;
4186
+ } else if ((ch & 0xFE) == 0xFC) {
4187
+ this.utfCount = 5;
4188
+ this.utfChar = ch & 0x01;
4189
+ } else {
4190
+ this.utfCount = 0;
4191
+ }
4192
+ continue;
4193
+ }
4194
+ } else {
4195
+ this.utfCount = 0;
4196
+ }
4197
+ }
4198
+ var isNormalCharacter =
4199
+ (ch >= 32 && ch <= 127 || ch >= 160 ||
4200
+ this.utfEnabled && ch >= 128 ||
4201
+ !(this.dispCtrl ? this.ctrlAlways : this.ctrlAction)[ch & 0x1F]) &&
4202
+ (ch != 0x7F || this.dispCtrl);
4203
+
4204
+ if (isNormalCharacter && this.isEsc == 0 /* ESnormal */) {
4205
+ if (ch < 256) {
4206
+ ch = this.translate[this.toggleMeta ? (ch | 0x80) : ch];
4207
+ }
4208
+ if ((ch & 0xFF00) == 0xF000) {
4209
+ ch = ch & 0xFF;
4210
+ } else if (ch == 0xFEFF || (ch >= 0x200A && ch <= 0x200F)) {
4211
+ continue;
4212
+ }
4213
+ if (!this.printing) {
4214
+ if (this.needWrap || this.insertMode) {
4215
+ if (lineBuf) {
4216
+ this.renderString(lineBuf);
4217
+ lineBuf = '';
4218
+ }
4219
+ }
4220
+ if (this.needWrap) {
4221
+ this.cr(); this.lf();
4222
+ }
4223
+ if (this.insertMode) {
4224
+ this.scrollRegion(this.cursorX, this.cursorY,
4225
+ this.terminalWidth - this.cursorX - 1, 1,
4226
+ 1, 0, this.color, this.style);
4227
+ }
4228
+ }
4229
+ this.lastCharacter = String.fromCharCode(ch);
4230
+ lineBuf += this.lastCharacter;
4231
+ if (!this.printing &&
4232
+ this.cursorX + lineBuf.length >= this.terminalWidth) {
4233
+ this.needWrap = this.autoWrapMode;
4234
+ }
4235
+ } else {
4236
+ if (lineBuf) {
4237
+ this.renderString(lineBuf);
4238
+ lineBuf = '';
4239
+ }
4240
+ var expand = this.doControl(ch);
4241
+ if (expand.length) {
4242
+ var r = this.respondString;
4243
+ this.respondString= r + this.vt100(expand);
4244
+ }
4245
+ }
4246
+ }
4247
+ if (lineBuf) {
4248
+ this.renderString(lineBuf, this.cursorNeedsShowing);
4249
+ } else if (this.cursorNeedsShowing) {
4250
+ this.showCursor();
4251
+ }
4252
+ return this.respondString;
4253
+ };
4254
+
4255
+ VT100.prototype.Latin1Map = [
4256
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4257
+ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4258
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4259
+ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4260
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4261
+ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4262
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4263
+ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4264
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4265
+ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4266
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4267
+ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4268
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4269
+ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4270
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4271
+ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
4272
+ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4273
+ 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4274
+ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4275
+ 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4276
+ 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4277
+ 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4278
+ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4279
+ 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4280
+ 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4281
+ 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4282
+ 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4283
+ 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4284
+ 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4285
+ 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4286
+ 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4287
+ 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4288
+ ];
4289
+
4290
+ VT100.prototype.VT100GraphicsMap = [
4291
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
4292
+ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
4293
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
4294
+ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
4295
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4296
+ 0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
4297
+ 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4298
+ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4299
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4300
+ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4301
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4302
+ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x00A0,
4303
+ 0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
4304
+ 0x2591, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0xF800,
4305
+ 0xF801, 0x2500, 0xF803, 0xF804, 0x251C, 0x2524, 0x2534, 0x252C,
4306
+ 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
4307
+ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
4308
+ 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
4309
+ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
4310
+ 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
4311
+ 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
4312
+ 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
4313
+ 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
4314
+ 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
4315
+ 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
4316
+ 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
4317
+ 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
4318
+ 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
4319
+ 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
4320
+ 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
4321
+ 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
4322
+ 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF
4323
+ ];
4324
+
4325
+ VT100.prototype.CodePage437Map = [
4326
+ 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
4327
+ 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
4328
+ 0x25B6, 0x25C0, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
4329
+ 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
4330
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
4331
+ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
4332
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
4333
+ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
4334
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
4335
+ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
4336
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
4337
+ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
4338
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
4339
+ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
4340
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
4341
+ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
4342
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
4343
+ 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
4344
+ 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
4345
+ 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
4346
+ 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
4347
+ 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
4348
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
4349
+ 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
4350
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
4351
+ 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
4352
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
4353
+ 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
4354
+ 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
4355
+ 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
4356
+ 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
4357
+ 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
4358
+ ];
4359
+
4360
+ VT100.prototype.DirectToFontMap = [
4361
+ 0xF000, 0xF001, 0xF002, 0xF003, 0xF004, 0xF005, 0xF006, 0xF007,
4362
+ 0xF008, 0xF009, 0xF00A, 0xF00B, 0xF00C, 0xF00D, 0xF00E, 0xF00F,
4363
+ 0xF010, 0xF011, 0xF012, 0xF013, 0xF014, 0xF015, 0xF016, 0xF017,
4364
+ 0xF018, 0xF019, 0xF01A, 0xF01B, 0xF01C, 0xF01D, 0xF01E, 0xF01F,
4365
+ 0xF020, 0xF021, 0xF022, 0xF023, 0xF024, 0xF025, 0xF026, 0xF027,
4366
+ 0xF028, 0xF029, 0xF02A, 0xF02B, 0xF02C, 0xF02D, 0xF02E, 0xF02F,
4367
+ 0xF030, 0xF031, 0xF032, 0xF033, 0xF034, 0xF035, 0xF036, 0xF037,
4368
+ 0xF038, 0xF039, 0xF03A, 0xF03B, 0xF03C, 0xF03D, 0xF03E, 0xF03F,
4369
+ 0xF040, 0xF041, 0xF042, 0xF043, 0xF044, 0xF045, 0xF046, 0xF047,
4370
+ 0xF048, 0xF049, 0xF04A, 0xF04B, 0xF04C, 0xF04D, 0xF04E, 0xF04F,
4371
+ 0xF050, 0xF051, 0xF052, 0xF053, 0xF054, 0xF055, 0xF056, 0xF057,
4372
+ 0xF058, 0xF059, 0xF05A, 0xF05B, 0xF05C, 0xF05D, 0xF05E, 0xF05F,
4373
+ 0xF060, 0xF061, 0xF062, 0xF063, 0xF064, 0xF065, 0xF066, 0xF067,
4374
+ 0xF068, 0xF069, 0xF06A, 0xF06B, 0xF06C, 0xF06D, 0xF06E, 0xF06F,
4375
+ 0xF070, 0xF071, 0xF072, 0xF073, 0xF074, 0xF075, 0xF076, 0xF077,
4376
+ 0xF078, 0xF079, 0xF07A, 0xF07B, 0xF07C, 0xF07D, 0xF07E, 0xF07F,
4377
+ 0xF080, 0xF081, 0xF082, 0xF083, 0xF084, 0xF085, 0xF086, 0xF087,
4378
+ 0xF088, 0xF089, 0xF08A, 0xF08B, 0xF08C, 0xF08D, 0xF08E, 0xF08F,
4379
+ 0xF090, 0xF091, 0xF092, 0xF093, 0xF094, 0xF095, 0xF096, 0xF097,
4380
+ 0xF098, 0xF099, 0xF09A, 0xF09B, 0xF09C, 0xF09D, 0xF09E, 0xF09F,
4381
+ 0xF0A0, 0xF0A1, 0xF0A2, 0xF0A3, 0xF0A4, 0xF0A5, 0xF0A6, 0xF0A7,
4382
+ 0xF0A8, 0xF0A9, 0xF0AA, 0xF0AB, 0xF0AC, 0xF0AD, 0xF0AE, 0xF0AF,
4383
+ 0xF0B0, 0xF0B1, 0xF0B2, 0xF0B3, 0xF0B4, 0xF0B5, 0xF0B6, 0xF0B7,
4384
+ 0xF0B8, 0xF0B9, 0xF0BA, 0xF0BB, 0xF0BC, 0xF0BD, 0xF0BE, 0xF0BF,
4385
+ 0xF0C0, 0xF0C1, 0xF0C2, 0xF0C3, 0xF0C4, 0xF0C5, 0xF0C6, 0xF0C7,
4386
+ 0xF0C8, 0xF0C9, 0xF0CA, 0xF0CB, 0xF0CC, 0xF0CD, 0xF0CE, 0xF0CF,
4387
+ 0xF0D0, 0xF0D1, 0xF0D2, 0xF0D3, 0xF0D4, 0xF0D5, 0xF0D6, 0xF0D7,
4388
+ 0xF0D8, 0xF0D9, 0xF0DA, 0xF0DB, 0xF0DC, 0xF0DD, 0xF0DE, 0xF0DF,
4389
+ 0xF0E0, 0xF0E1, 0xF0E2, 0xF0E3, 0xF0E4, 0xF0E5, 0xF0E6, 0xF0E7,
4390
+ 0xF0E8, 0xF0E9, 0xF0EA, 0xF0EB, 0xF0EC, 0xF0ED, 0xF0EE, 0xF0EF,
4391
+ 0xF0F0, 0xF0F1, 0xF0F2, 0xF0F3, 0xF0F4, 0xF0F5, 0xF0F6, 0xF0F7,
4392
+ 0xF0F8, 0xF0F9, 0xF0FA, 0xF0FB, 0xF0FC, 0xF0FD, 0xF0FE, 0xF0FF
4393
+ ];
4394
+
4395
+ VT100.prototype.ctrlAction = [
4396
+ true, false, false, false, false, false, false, true,
4397
+ true, true, true, true, true, true, true, true,
4398
+ false, false, false, false, false, false, false, false,
4399
+ true, false, true, true, false, false, false, false
4400
+ ];
4401
+
4402
+ VT100.prototype.ctrlAlways = [
4403
+ true, false, false, false, false, false, false, false,
4404
+ true, false, true, false, true, true, true, true,
4405
+ false, false, false, false, false, false, false, false,
4406
+ false, false, false, true, false, false, false, false
4407
+ ];
4408
+