diff --git a/app/static/js/app.js b/app/static/js/app.js index 08cf9553b..dfd298a85 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -142,6 +142,12 @@ function browserLanguage() { // Send a keystroke message to the backend, and add a key card to the web UI. function sendKeystroke(keystroke) { + // On Android, when the user is typing with autocomplete enabled, the browser + // sends dummy keydown events with a keycode of 229. Ignore these events, as + // there's no way to map it to a real key. + if (keystroke.keyCode === 229) { + return; + } let keyCard = undefined; if (!keystroke.metaKey) { keyCard = addKeyCard(keystroke.key); diff --git a/app/templates/custom-elements/remote-screen.html b/app/templates/custom-elements/remote-screen.html index 7ebea4037..07943162c 100644 --- a/app/templates/custom-elements/remote-screen.html +++ b/app/templates/custom-elements/remote-screen.html @@ -30,8 +30,14 @@ :host([fullscreen="true"]) #remote-screen-img.full-height { height: 100%; } + + #mobile-keyboard-input { + position: fixed; + bottom: -1000px; + } </style> <div class="screen-wrapper"> + <input id="mobile-keyboard-input" autocapitalize="off" type="text" /> <img id="remote-screen-img" src="/stream?advance_headers=1" /> </div> <script @@ -101,6 +107,40 @@ }); window.addEventListener("resize", this.onWindowResize); + + // Detect whether this is a touchscreen device. + let isTouchScreen = false; + this.shadowRoot.addEventListener("touchend", () => { + isTouchScreen = true; + }); + this.shadowRoot.addEventListener("click", () => { + if (isTouchScreen) { + this.shadowRoot.getElementById("mobile-keyboard-input").focus(); + } + }); + + // On mobile, the keydown events function differently due to the OS + // attempting to autocomplete text. Instead of listening for keydown + // events, we listen for input events. + const mobileKeyboard = this.shadowRoot.getElementById( + "mobile-keyboard-input" + ); + mobileKeyboard.addEventListener("input", (evt) => { + // Handle insertCompositionText, which mean typing in autocomplete + // mode. The global keydown event handler processes all other key + // input events. + if ( + evt.inputType === "insertText" || + evt.inputType === "insertCompositionText" + ) { + sendTextInput(evt.data); + } + + // Force the autocomplete sequence to restart. + mobileKeyboard.blur(); + mobileKeyboard.value = ""; + mobileKeyboard.focus(); + }); } disconnectedCallback() { diff --git a/app/templates/index.html b/app/templates/index.html index c4e656677..00f74eb47 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -12,6 +12,10 @@ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> + <meta + name="viewport" + content="width=device-width, initial-scale=1, maximum-scale=1; scalable=no;" + /> <meta name="csrf-token" content="{{ csrf_token() }}" /> </head> <body>