Skip to content
Snippets Groups Projects
vplterminal.js 9.37 KiB
// This file is part of VPL for Moodle - http://vpl.dis.ulpgc.es/
//
// VPL for Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// VPL for Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with VPL for Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Terminal control
 *
 * @package mod_vpl
 * @copyright 2014 Juan Carlos Rodríguez-del-Pino
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @author Juan Carlos Rodríguez-del-Pino <jcrodriguez@dis.ulpgc.es>
 */

/* globals Terminal */

define([ 'jquery', 'jqueryui', 'mod_vpl/vplutil', 'mod_vpl/vplclipboard' ],
        function($, jqui, VPLUtil, VPLClipboard) {
    if ( typeof VPLTerminal !== 'undefined' ) {
        return VPLTerminal;
    }
    var VPLTerminal = function(dialogId, terminalId, str) {
        var self = this;
        var ws = null;
        var onCloseAction = function() {
        };
        var title = '';
        var message = '';
        var tdialog = $('#' + dialogId);
        var titleText = '';
        var clipboard = null;
        var cliboardMaxsize = 64000;
        var clipboardData = '';

        var terminal;
        var terminalTag = $('#' + terminalId);
        this.updateTitle = function() {
            var text = title;
            if (message !== '') {
                text += ' (' + message + ')';
            }
            titleText.text(str('console') + ": " + text);
        };
        this.setTitle = function(t) {
            title = t;
            this.updateTitle();
        };
        this.setMessage = function(t) {
            message = t;
            this.updateTitle();
        };
        function receiveClipboard(data) {
            clipboardData += data;
            if (clipboardData.length > cliboardMaxsize) {
                var from = clipboardData.length - cliboardMaxsize / 2;
                clipboardData = clipboardData.substr(from);
            }
        }
        function pasteClipboard() {
            if (ws && ws.readyState == ws.OPEN) {
                ws.send(clipboard.getEntry2());
            }
        }
        function updateClipboard() {
            clipboard.setEntry1(clipboardData);
        }
        function openClipboard() {
            updateClipboard();
            clipboard.show();
        }
        this.write = function(text) {
            terminal.write(text);
            return text;
        };

        this.connect = function(server, onClose) {
            onCloseAction = onClose;
            if ("WebSocket" in window) {
                terminal.reset();
                terminal.startBlink();
                self.show();
                if (ws) {
                    ws.close();
                }
                clipboardData = '';
                self.setMessage('');
                self.setTitle(str('connecting'));
                ws = new WebSocket(server);
                ws.writeBuffer = '';
                ws.writeIt = function() {
                    terminal.write(ws.writeBuffer);
                    receiveClipboard(ws.writeBuffer);
                    ws.writeBuffer = '';
                };
                ws.onmessage = function(event) {
                    if (ws.writeBuffer.length > 0) {
                        ws.writeBuffer += event.data;
                    } else {
                        ws.writeBuffer = event.data;
                        setTimeout(ws.writeIt, 0);
                    }
                };
                ws.onopen = function() {
                    self.setMessage('');
                    self.setTitle(str('connected'));
                };
                ws.onclose = function() {
                    self.setTitle(str('connection_closed'));
                    terminal.blur();
                    terminal.stopBlink();
                    onClose();
                    ws.stopOutput = true;
                };
            } else {
                terminal.write('WebSocket not available: Upgrade your browser');
            }
        };
        this.writeLocal = function(text) {
            ws.onmessage({
                data : text
            });
            return text;
        };
        this.setDataCallback = function(call) {
            ws.onData = call;
        };
        this.closeLocal = function() {
            if (ws) {
                ws.writeIt();
                ws.close();
                terminal.stopBlink();
                self.setTitle(str('connection_closed'));
            }
        };
        this.connectLocal = function(onClose, onData) {
            onCloseAction = onClose;
            terminal.reset();
            terminal.startBlink();
            self.show();
            if (ws) {
                ws.close();
            }
            clipboardData = '';
            self.setMessage('');
            self.setTitle(str('running'));
            ws = {};
            ws.onData = onData;
            ws.writeBuffer = '';
            ws.readBuffer = '';
            ws.readyState = 1;
            ws.OPEN = 1;
            ws.close = function() {
                ws = false;
            };
            ws.onmessage = function(event) {
                ws.writeBuffer = event.data;
                ws.writeIt();
            };
            ws.writeIt = function() {
                if (ws) {
                    terminal.write(ws.writeBuffer);
                    receiveClipboard(ws.writeBuffer);
                    ws.writeBuffer = '';
                }
            };
            ws.send = function(text) {
                // Process backspace.
                if (text == '\u007f') {
                    if (ws.readBuffer.length > 0) {
                        self.writeLocal('\b \b');
                        ws.readBuffer = ws.readBuffer.substr(0, ws.readBuffer.length - 1);
                    }
                } else {
                    self.writeLocal(text);
                    ws.readBuffer += text;
                }
                var pos = ws.readBuffer.indexOf("\r");
                if (pos != -1) {
                    var data = ws.readBuffer.substr(0, pos);
                    ws.readBuffer = ws.readBuffer.substr(pos + 1);
                    ws.onData(data);
                }
            };
        };
        this.isOpen = function() {
            return tdialog.dialog("isOpen") === true;
        };
        this.isConnected = function() {
            return ws && ws.readyState != ws.CLOSED;
        };
        this.disconnect = function() {
            if (ws && ws.readyState == ws.OPEN) {
                onCloseAction();
                if (ws) {
                    ws.close();
                }
                terminal.stopBlink();
            }
        };
        var HTMLUpdateClipboard = VPLUtil.genIcon('copy', 'sw') + ' ' + str('copy');
        var HTMLPaste = VPLUtil.genIcon('paste', 'sw') + ' ' + str('paste');
        clipboard = new VPLClipboard('vpl_dialog_terminal_clipboard', HTMLUpdateClipboard, function() {
            updateClipboard();
            document.execCommand('copy');
        }, HTMLPaste, pasteClipboard);
        this.closeDialog = function() {
            clipboard.hide();
            self.disconnect();
        };
        tdialog.dialog({
            closeOnEscape : false,
            autoOpen : false,
            width : 'auto',
            height : 'auto',
            resizable : true,
            focus : function() {
                terminal.focus();
            },
            dialogClass : 'vpl_ide vpl_vnc ' + $('#vplide').attr('class').match(/(^| )(vpl_theme_[^ ]*)/)[2],
            create : function() {
                titleText = VPLUtil.setTitleBar(tdialog, 'console', 'console', [ 'clipboard', 'keyboard' ], [ openClipboard,
                        function() {
                            terminal.focus();
                        } ]);
            },
            close : function() {
                self.closeDialog();
            },
            resizeStart : function() {
                var cur = tdialog.find('pre').width();
                var curin = tdialog.find('pre div').width();
                if (cur <= curin) {
                    tdialog.find('pre').width(curin);
                }
            }
        });
        tdialog.css("padding", "1px");
        this.show = function() {
            tdialog.dialog('open');
            terminal.focus();
        };
        this.init = function() {
            if ( typeof Terminal === 'undefined' ) {
                VPLUtil.loadScript(['../../vpl/editor/xterm/term.js'], function() {self.init();});
                return;
            }
            terminal = new Terminal({
                cols : 80,
                rows : 24,
                useStyle : true,
                screenKeys : true
            });
            terminal.on('data', function(data) {
                if (ws && ws.readyState == ws.OPEN) {
                    ws.send(data);
                }
            });
            terminal.open(terminalTag[0]);
            terminal.reset();
            terminal.stopBlink();
        };
        this.init();
    };
    window.VPLTerminal = VPLTerminal;
    return VPLTerminal;
});