]> git.proxmox.com Git - pve-xtermjs.git/blame - xterm.js/src/main.js
xtermjs: try to detect hardware support for webgl2
[pve-xtermjs.git] / xterm.js / src / main.js
CommitLineData
dcf3d43b
DC
1console.log('xtermjs: starting');
2
3var states = {
4 start: 1,
5 connecting: 2,
6 connected: 3,
7 disconnecting: 4,
8 disconnected: 5,
291e1a24 9 reconnecting: 6,
dcf3d43b
DC
10};
11
12var term,
13 protocol,
14 socketURL,
15 socket,
16 ticket,
dcf3d43b
DC
17 resize,
18 ping,
291e1a24
DC
19 state = states.start,
20 starttime = new Date();
21
22var type = getQueryParameter('console');
23var vmid = getQueryParameter('vmid');
24var vmname = getQueryParameter('vmname');
25var nodename = getQueryParameter('node');
6c7c792d 26var cmd = getQueryParameter('cmd');
1d3d2919 27var cmdOpts = getQueryParameter('cmd-opts');
dcf3d43b 28
93c96ac5 29function updateState(newState, msg, code) {
dcf3d43b
DC
30 var timeout, severity, message;
31 switch (newState) {
32 case states.connecting:
33 message = "Connecting...";
34 timeout = 0;
35 severity = severities.warning;
36 break;
37 case states.connected:
e435736a 38 window.onbeforeunload = windowUnload;
dcf3d43b
DC
39 message = "Connected";
40 break;
41 case states.disconnecting:
f7fd260c 42 window.onbeforeunload = undefined;
dcf3d43b
DC
43 message = "Disconnecting...";
44 timeout = 0;
45 severity = severities.warning;
46 break;
291e1a24 47 case states.reconnecting:
f7fd260c 48 window.onbeforeunload = undefined;
291e1a24
DC
49 message = "Reconnecting...";
50 timeout = 0;
51 severity = severities.warning;
52 break;
dcf3d43b 53 case states.disconnected:
e435736a 54 window.onbeforeunload = undefined;
dcf3d43b
DC
55 switch (state) {
56 case states.start:
57 case states.connecting:
291e1a24 58 case states.reconnecting:
dcf3d43b
DC
59 message = "Connection failed";
60 timeout = 0;
61 severity = severities.error;
62 break;
63 case states.connected:
64 case states.disconnecting:
291e1a24
DC
65 var time_since_started = new Date() - starttime;
66 timeout = 5000;
3645b3f4 67 if (time_since_started > 5*1000 || type === 'shell') {
291e1a24
DC
68 message = "Connection closed";
69 } else {
70 message = "Connection failed";
71 severity = severities.error;
72 }
dcf3d43b
DC
73 break;
74 case states.disconnected:
75 // no state change
76 break;
77 default:
78 throw "unknown state";
79 }
80 break;
81 default:
82 throw "unknown state";
83 }
93c96ac5 84 let msgArr = [];
dcf3d43b 85 if (msg) {
93c96ac5
DC
86 msgArr.push(msg);
87 }
88 if (code !== undefined) {
89 msgArr.push(`Code: ${code}`);
90 }
91 if (msgArr.length > 0) {
92 message += ` (${msgArr.join(', ')})`;
dcf3d43b
DC
93 }
94 state = newState;
95 showMsg(message, timeout, severity);
96}
97
98var terminalContainer = document.getElementById('terminal-container');
99document.getElementById('status_bar').addEventListener('click', hideMsg);
9871a3b8 100document.getElementById('connect_btn').addEventListener('click', startGuest);
b9bbd688 101const fitAddon = new FitAddon.FitAddon();
154b65d5 102const webglAddon = new WebglAddon.WebglAddon();
dcf3d43b
DC
103
104createTerminal();
105
9871a3b8
DC
106function startConnection(url, params, term) {
107 API2Request({
108 method: 'POST',
109 params: params,
110 url: url + '/termproxy',
111 success: function(result) {
112 var port = encodeURIComponent(result.data.port);
113 ticket = result.data.ticket;
114 socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/api2/json' + url + '/vncwebsocket?port=' + port + '&vncticket=' + encodeURIComponent(ticket);
115
9871a3b8
DC
116 socket = new WebSocket(socketURL, 'binary');
117 socket.binaryType = 'arraybuffer';
118 socket.onopen = runTerminal;
119 socket.onclose = tryReconnect;
120 socket.onerror = tryReconnect;
121 updateState(states.connecting);
122 },
123 failure: function(msg) {
124 updateState(states.disconnected,msg);
125 }
126 });
127}
128
129function startGuest() {
130 let api_type = type === 'kvm' ? 'qemu' : 'lxc';
131 API2Request({
132 method: 'POST',
133 url: `/nodes/${nodename}/${api_type}/${vmid}/status/start`,
134 success: function(result) {
135 showMsg('Guest started successfully', 0);
136 setTimeout(function() {
137 location.reload();
138 }, 1000);
139 },
140 failure: function(msg) {
141 if (msg.match(/already running/)) {
142 showMsg('Guest started successfully', 0);
143 setTimeout(function() {
144 location.reload();
145 }, 1000);
146 } else {
147 updateState(states.disconnected,msg);
148 }
149 }
150 });
151}
152
dcf3d43b 153function createTerminal() {
37445374 154 term = new Terminal(getTerminalSettings());
154b65d5 155 term.open(terminalContainer);
b9bbd688 156 term.loadAddon(fitAddon);
aa9a479c 157 let loadedWebgl = false;
154b65d5 158 try {
aa9a479c
DC
159 if (detectWebgl()) {
160 term.loadAddon(webglAddon);
161 loadedWebgl = true;
162 }
163 } catch (_e) { }
164
165 if (!loadedWebgl) {
154b65d5
DC
166 console.warn("webgl-addon loading failed, falling back to regular dom renderer");
167 }
dcf3d43b 168
b9bbd688 169 term.onResize(function (size) {
dcf3d43b
DC
170 if (state === states.connected) {
171 socket.send("1:" + size.cols + ":" + size.rows + ":");
172 }
173 });
174
175 protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
176
177 var params = {};
dcf3d43b
DC
178 var url = '/nodes/' + nodename;
179 switch (type) {
180 case 'kvm':
181 url += '/qemu/' + vmid;
dcf3d43b
DC
182 break;
183 case 'lxc':
184 url += '/lxc/' + vmid;
dcf3d43b
DC
185 break;
186 case 'upgrade':
af39a6f0 187 params.cmd = 'upgrade';
dcf3d43b 188 break;
6c7c792d
TM
189 case 'cmd':
190 params.cmd = decodeURI(cmd);
bc2ce72a 191 if (cmdOpts !== undefined && cmdOpts !== null && cmdOpts !== "") {
1d3d2919
TL
192 params['cmd-opts'] = decodeURI(cmdOpts);
193 }
6c7c792d 194 break;
dcf3d43b 195 }
9871a3b8
DC
196 if (type === 'kvm' || type === 'lxc') {
197 API2Request({
198 method: 'GET',
199 url: `${url}/status/current`,
200 success: function(result) {
201 if (result.data.status === 'running') {
202 startConnection(url, params, term);
203 } else {
204 document.getElementById('connect_dlg').classList.add('pve_open');
205 }
206 },
207 failure: function(msg) {
208 updateState(states.disconnected, msg);
209 },
210 });
211 } else {
212 startConnection(url, params, term);
213 }
dcf3d43b
DC
214}
215
216function runTerminal() {
217 socket.onmessage = function(event) {
920ae86d 218 var answer = new Uint8Array(event.data);
dcf3d43b
DC
219 if (state === states.connected) {
220 term.write(answer);
221 } else if(state === states.connecting) {
920ae86d 222 if (answer[0] === 79 && answer[1] === 75) { // "OK"
dcf3d43b
DC
223 updateState(states.connected);
224 term.write(answer.slice(2));
225 } else {
226 socket.close();
227 }
228 }
229 };
230
b9bbd688 231 term.onData(function(data) {
dcf3d43b
DC
232 if (state === states.connected) {
233 socket.send("0:" + unescape(encodeURIComponent(data)).length.toString() + ":" + data);
234 }
235 });
236
237 ping = setInterval(function() {
238 socket.send("2");
239 }, 30*1000);
240
241 window.addEventListener('resize', function() {
242 clearTimeout(resize);
243 resize = setTimeout(function() {
244 // done resizing
b9bbd688 245 fitAddon.fit();
dcf3d43b
DC
246 }, 250);
247 });
248
5e91985c 249 socket.send(PVE.UserName + ':' + ticket + "\n");
dcf3d43b 250
1553e6ef
DC
251 // initial focus and resize
252 setTimeout(function() {
253 term.focus();
b9bbd688 254 fitAddon.fit();
1553e6ef 255 }, 250);
dcf3d43b
DC
256}
257
291e1a24
DC
258function getLxcStatus(callback) {
259 API2Request({
260 method: 'GET',
261 url: '/nodes/' + nodename + '/lxc/' + vmid + '/status/current',
262 success: function(result) {
263 if (typeof callback === 'function') {
264 callback(true, result);
265 }
266 },
267 failure: function(msg) {
268 if (typeof callback === 'function') {
269 callback(false, msg);
270 }
271 }
272 });
273}
274
275function checkMigration() {
276 var apitype = type;
277 if (apitype === 'kvm') {
278 apitype = 'qemu';
279 }
280 API2Request({
281 method: 'GET',
282 params: {
283 type: 'vm'
284 },
285 url: '/cluster/resources',
286 success: function(result) {
287 // if not yet migrated , wait and try again
288 // if not migrating and stopped, cancel
289 // if started, connect
290 result.data.forEach(function(entity) {
291 if (entity.id === (apitype + '/' + vmid)) {
292 var started = entity.status === 'running';
293 var migrated = entity.node !== nodename;
294 if (migrated) {
295 if (started) {
296 // goto different node
297 location.href = '?console=' + type +
298 '&xtermjs=1&vmid=' + vmid + '&vmname=' +
299 vmname + '&node=' + entity.node;
300 } else {
301 // wait again
302 updateState(states.reconnecting, 'waiting for migration to finish...');
303 setTimeout(checkMigration, 5000);
304 }
305 } else {
306 if (type === 'lxc') {
307 // we have to check the status of the
308 // container to know if it has the
309 // migration lock
310 getLxcStatus(function(success, result) {
311 if (success) {
312 if (result.data.lock === 'migrate') {
313 // still waiting
314 updateState(states.reconnecting, 'waiting for migration to finish...');
315 setTimeout(checkMigration, 5000);
8f2ac472
DC
316 } else if (started) {
317 // container was rebooted
318 location.reload();
291e1a24
DC
319 } else {
320 stopTerminal();
321 }
322 } else {
323 // probably the status call failed because
324 // the ct is already somewhere else, so retry
325 setTimeout(checkMigration, 1000);
326 }
327 });
328 } else if (started) {
329 // this happens if we have old data in
330 // /cluster/resources, or the connection
331 // disconnected, so simply try to reload here
332 location.reload();
333 } else if (type === 'kvm') {
334 // it seems the guest simply stopped
335 stopTerminal();
336 }
337 }
338
339 return;
340 }
341 });
342 },
343 failure: function(msg) {
344 errorTerminal({msg: msg});
345 }
346 });
347}
348
93c96ac5 349function tryReconnect(event) {
291e1a24
DC
350 var time_since_started = new Date() - starttime;
351 var type = getQueryParameter('console');
6c7c792d 352 if (time_since_started < 5*1000 || type === 'shell' || type === 'cmd') { // 5 seconds
93c96ac5 353 stopTerminal(event);
291e1a24 354 return;
291e1a24
DC
355 }
356
357 updateState(states.disconnecting, 'Detecting migration...');
358 setTimeout(checkMigration, 5000);
359}
360
b9bbd688
DC
361function clearEvents() {
362 term.onResize(() => {});
363 term.onData(() => {});
364}
365
e435736a
DC
366function windowUnload(e) {
367 let message = "Are you sure you want to leave this page?";
368
369 e = e || window.event;
370 if (e) {
371 e.returnValue = message;
372 }
373
374 return message;
375}
376
dcf3d43b 377function stopTerminal(event) {
8c502b81 378 event = event || {};
b9bbd688 379 clearEvents();
dcf3d43b
DC
380 clearInterval(ping);
381 socket.close();
93c96ac5 382 updateState(states.disconnected, event.reason, event.code);
dcf3d43b
DC
383}
384
385function errorTerminal(event) {
34d796a1 386 event = event || {};
b9bbd688 387 clearEvents();
dcf3d43b
DC
388 clearInterval(ping);
389 socket.close();
f21a83b7 390 term.dispose();
93c96ac5 391 updateState(states.disconnected, event.msg, event.code);
dcf3d43b 392}
aa9a479c
DC
393
394// try to detect hardware acceleration, can throw an exception if there is no webgl support
395//
396// for chrome we have to give the parameter, otherwise it'll use its software renderer
397// which is buggy: https://github.com/xtermjs/xterm.js/issues/4574
398//
399// firefox will fail on the getContext anyway if there is not hardware support
400function detectWebgl() {
401 const canvas = document.createElement("canvas");
402 const gl = canvas.getContext("webgl2", { failIfMajorPerformanceCaveat: true });
403 return !!gl;
404}