| <!-- |
| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| --> |
| <import src="/sky/framework/elements/sky-element.sky" /> |
| <sky-element> |
| <template> |
| <style> |
| #container { |
| height: -webkit-fill-available; |
| } |
| #input-box { |
| position: absolute; |
| left: 0; |
| top: 0; |
| z-index: 10; |
| width: 100%; |
| height: 100%; |
| opacity: 0; |
| } |
| #output-box { |
| position: absolute; |
| left: 0; |
| top: 0; |
| z-index: 0; |
| width: 100%; |
| height: 100%; |
| background-color: black; |
| color: rgb(255, 191, 0); |
| font-family: 'Courier', 'monospace'; |
| font-size: small; |
| } |
| .line { |
| height: 1em; |
| white-space: nowrap; |
| } |
| </style> |
| <div id="container"> |
| <div id="input-box" contenteditable /> |
| <div id="output-box" /> |
| </div> |
| </div> |
| </template> |
| <script> |
| import 'dart:async'; |
| import 'dart:core'; |
| import 'dart:sky'; |
| import 'package:mojo/services/terminal/public/interfaces/terminal_client.mojom.dart' as terminal; |
| import 'package:sky/framework/embedder.dart'; |
| import 'terminal_display.dart'; |
| import 'terminal_file_impl.dart'; |
| |
| // Implements the <terminal> element, which implements a "terminal display". Has |
| // an |url| attribute, whose value should be a Mojo app that provides the |
| // |terminal.TerminalClient| service. |
| @Tagname('terminal') |
| class TerminalDisplayImpl extends SkyElement implements TerminalDisplay { |
| Element _inputBox; |
| Element _outputBox; |
| int _maxLines; |
| |
| // Queue of unconsumed input (keystrokes), with the head at index 0. |
| // Keystrokes end up here if there's no reader (i.e., |getChar()|) pending, |
| // i.e., if |_readerQueue| is empty. Invariant: At most one of |_inputQueue| |
| // and |_readerQueue| may be nonempty at any given time. |
| List<int> _inputQueue; |
| |
| // Queue of things waiting for input, with the head at index 0. If a keystroke |
| // is received and this is nonempty, the head is given that keystroke (and |
| // dequeued). |
| List<Completer<int>> _readerQueue; |
| |
| TerminalDisplayImpl() |
| : _inputQueue = new List<int>(), |
| _readerQueue = new List<Completer<int>>() { |
| } |
| |
| void shadowRootReady() { |
| _inputBox = shadowRoot.getElementById('input-box'); |
| _inputBox.addEventListener('keydown', _handleKeyDown); |
| _inputBox.addEventListener('keypress', _handleKeyPress); |
| _inputBox.addEventListener('wheel', _handleWheel); |
| _outputBox = shadowRoot.getElementById('output-box'); |
| |
| // Hack to allow |_newLine()| to work. |
| _maxLines = 100; |
| |
| // Initialize with the first line. |
| _newLine(); |
| |
| // Actually compute the maximum number of lines. |
| // TODO(vtl): Recompute on resize. |
| _maxLines = _outputBox.clientHeight ~/ _outputBox.firstChild.offsetHeight; |
| |
| var url = getAttribute('url'); |
| if (url != null) { |
| connect(url); |
| } |
| } |
| |
| void _handleKeyDown(KeyboardEvent event) { |
| // TODO(vtl): In general, our key handling is a total hack (due in part to |
| // sky's keyboard support being incomplete) -- e.g., we shouldn't have to |
| // make our div contenteditable. We have to intercept backspace (^H) here, |
| // since we won't actually get a keypress event for it. (Possibly, we should |
| // only handle keydown instead of keypress, but then we'd have to handle |
| // shift, etc. ourselves.) |
| if (event.key == 8) { |
| _enqueueChar(8); |
| event.preventDefault(); |
| } |
| } |
| |
| void _handleKeyPress(KeyboardEvent event) { |
| if (event.charCode != 0) { |
| _enqueueChar(event.charCode); |
| } |
| event.preventDefault(); |
| } |
| |
| void _handleWheel(WheelEvent event) { |
| _outputBox.dispatchEvent(event); |
| } |
| |
| void _enqueueChar(int charCode) { |
| // TODO(vtl): Add "echo" mode; do |putChar(event.charCode);| if echo is on. |
| |
| if (_readerQueue.isEmpty) { |
| _inputQueue.add(charCode); |
| } else { |
| _readerQueue.removeAt(0).complete(charCode); |
| } |
| } |
| |
| void _backspace() { |
| var oldText = _outputBox.lastChild.textContent; |
| if (oldText.length > 0) { |
| _outputBox.lastChild.textContent = oldText.substring(0, |
| oldText.length - 1); |
| } |
| } |
| |
| void _newLine() { |
| var line = document.createElement('div'); |
| line.setAttribute('class', 'line'); |
| _outputBox.appendChild(line); |
| |
| // Scroll if necessary. |
| var children = _outputBox.getChildNodes(); |
| if (children.length > _maxLines) { |
| children = new List.from(children.skip(children.length - _maxLines)); |
| _outputBox.setChildren(children); |
| } |
| } |
| |
| void _clear() { |
| _outputBox.setChildren([]); |
| _newLine(); |
| } |
| |
| void connect(String url) { |
| var terminalClient = new terminal.TerminalClientProxy.unbound(); |
| embedder.connectToService(url, terminalClient); |
| terminalClient.ptr.connectToTerminal(new TerminalFileImpl(this).stub); |
| terminalClient.close(); |
| } |
| |
| void putString(String s) { |
| for (var i = 0; i < s.length; i++) { |
| putChar(s.codeUnitAt(i)); |
| } |
| } |
| |
| // |TerminalDisplay| implementation: |
| |
| @override |
| void putChar(int byte) { |
| // Fast-path for printable chars. |
| if (byte >= 32) { |
| _outputBox.lastChild.textContent += new String.fromCharCode(byte); |
| return; |
| } |
| |
| switch (byte) { |
| case 8: // BS (^H). |
| _backspace(); |
| break; |
| case 10: // LF ('\n'). |
| _newLine(); // TODO(vtl): LF and CR should be separated. |
| break; |
| case 12: // FF (^L). |
| _clear(); |
| break; |
| case 13: // CR ('\r'). |
| _newLine(); // TODO(vtl): LF and CR should be separated. |
| break; |
| default: |
| // Should beep or something. |
| break; |
| } |
| } |
| |
| @override |
| Future<int> getChar() async { |
| if (_inputQueue.isNotEmpty) { |
| return new Future.value(_inputQueue.removeAt(0)); |
| } |
| |
| var completer = new Completer<int>(); |
| _readerQueue.add(completer); |
| return completer.future; |
| } |
| } |
| |
| _init(script) => register(script, TerminalDisplayImpl); |
| </script> |
| </sky-element> |