window.initSoftKeyboard = function (screen, input) { const keyInput = qs('#softkb-input') if (!keyInput) return // abort, we're not on the terminal page let keyboardOpen = false // moves the input to where the cursor is on the canvas. // this is because most browsers will always scroll to wherever the focused // input is let updateInputPosition = function () { if (!keyboardOpen) return let [x, y] = screen.gridToScreen(screen.cursor.x, screen.cursor.y, true) keyInput.style.transform = `translate(${x}px, ${y}px)` } keyInput.addEventListener('focus', () => { keyboardOpen = true updateInputPosition() }) keyInput.addEventListener('blur', () => (keyboardOpen = false)) screen.on('cursor-moved', updateInputPosition) qs('#term-kb-open').addEventListener('click', e => { e.preventDefault() keyInput.focus() }) // Chrome for Android doesn't send proper keydown/keypress events with // real key values instead of 229 “Unidentified,” so here's a workaround // that deals with the input composition events. let lastCompositionString = '' let compositing = false // sends the difference between the last and the new composition string let sendInputDelta = function (newValue) { let resend = false if (newValue.length > lastCompositionString.length) { if (newValue.startsWith(lastCompositionString)) { // characters have been added at the end input.sendString(newValue.substr(lastCompositionString.length)) } else resend = true } else if (newValue.length < lastCompositionString.length) { if (lastCompositionString.startsWith(newValue)) { // characters have been removed at the end input.sendString('\b'.repeat(lastCompositionString.length - newValue.length)) } else resend = true } else if (newValue !== lastCompositionString) resend = true if (resend) { // the entire string changed; resend everything input.sendString('\b'.repeat(lastCompositionString.length) + newValue) } lastCompositionString = newValue } keyInput.addEventListener('keydown', e => { if (e.key === 'Unidentified') return keyInput.value = '' if (e.key === 'Backspace') { e.preventDefault() input.sendString('\b') } else if (e.key === 'Enter') { e.preventDefault() input.sendString('\x0d') } }) keyInput.addEventListener('keypress', e => { // prevent key duplication on iOS (because Safari *does* send proper events) e.stopPropagation() }) keyInput.addEventListener('input', e => { e.stopPropagation() if (e.isComposing) { sendInputDelta(e.data) } else { if (e.inputType === 'insertCompositionText') input.sendString(e.data) else if (e.inputType === 'deleteContentBackward') { lastCompositionString = '' sendInputDelta('') } else if (e.inputType === 'insertText') { input.sendString(e.data) } } }) keyInput.addEventListener('compositionstart', e => { lastCompositionString = '' compositing = true }) keyInput.addEventListener('compositionend', e => { lastCompositionString = '' compositing = false keyInput.value = '' }) screen.on('open-soft-keyboard', () => keyInput.focus()) }