web-console-rails3 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+