From 7543d7381c717cf1d1abdfd8a26c94fbbe8a8b8b Mon Sep 17 00:00:00 2001 From: Bradley Bennett Date: Thu, 9 Apr 2026 11:21:17 -0400 Subject: [PATCH] GUACAMOLE-2237: Multiline paste and text input client fixes. --- .../src/main/webapp/modules/Keyboard.js | 27 ++++++++++++++++- .../app/textInput/directives/guacTextInput.js | 29 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/guacamole-common-js/src/main/webapp/modules/Keyboard.js b/guacamole-common-js/src/main/webapp/modules/Keyboard.js index 9d0814ac2c..c477f03a25 100644 --- a/guacamole-common-js/src/main/webapp/modules/Keyboard.js +++ b/guacamole-common-js/src/main/webapp/modules/Keyboard.js @@ -407,6 +407,7 @@ Guacamole.Keyboard = function Keyboard(element) { var keycodeKeysyms = { 8: [0xFF08], // backspace 9: [0xFF09], // tab + // 10 (LF) intentionally absent: no physical key generates a linefeed. 12: [0xFF0B, 0xFF0B, 0xFF0B, 0xFFB5], // clear / KP 5 13: [0xFF0D], // enter 16: [0xFFE1, 0xFFE1, 0xFFE2], // shift @@ -1060,7 +1061,31 @@ Guacamole.Keyboard = function Keyboard(element) { if (str.charCodeAt(i) !== codepoint) { i++; } - var keysym = keysym_from_charcode(codepoint); + + // CR and LF are handled explicitly; all other codepoints go through + // keysym_from_charcode(). + var keysym; + + if (codepoint === 0x0D) { + keysym = 0xFF0D; + + // CRLF pairs are collapsed to produce a single Return. + var nextCodepoint = (i + 1 < str.length) + ? (str.codePointAt ? str.codePointAt(i + 1) : str.charCodeAt(i + 1)) + : 0; + + if (nextCodepoint === 0x0A) + i++; + } + else if (codepoint === 0x0A) { + // keysym_from_charcode(0x0A) would return 0xFF0A which is not + // a keyboard character and is ignored by protocol input + // handlers. + keysym = 0xFF0D; + } + else { + keysym = keysym_from_charcode(codepoint); + } // Press and release key for current character guac_keyboard.press(keysym); diff --git a/guacamole/src/main/frontend/src/app/textInput/directives/guacTextInput.js b/guacamole/src/main/frontend/src/app/textInput/directives/guacTextInput.js index eeac777e3e..82b4f25920 100644 --- a/guacamole/src/main/frontend/src/app/textInput/directives/guacTextInput.js +++ b/guacamole/src/main/frontend/src/app/textInput/directives/guacTextInput.js @@ -286,6 +286,35 @@ angular.module('textInput').directive('guacTextInput', [function guacTextInput() }; + // Handle paste separately from normal input. + target.addEventListener("paste", function targetPaste(e) { + + if (!e.clipboardData) + return; + + // Take over paste handling before the browser inserts anything. + e.preventDefault(); + e.stopPropagation(); + + var content = e.clipboardData.getData('text/plain'); + + // Normalize line endings to \n. + content = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + + // Release modifiers so pasted text isn't treated as Ctrl/Alt combos. + $rootScope.$broadcast('guacSyntheticKeyup', 0xFFE3); // Left Ctrl + $rootScope.$broadcast('guacSyntheticKeyup', 0xFFE4); // Right Ctrl + $rootScope.$broadcast('guacSyntheticKeyup', 0xFFE9); // Left Alt + $rootScope.$broadcast('guacSyntheticKeyup', 0xFFEA); // Right Alt + + sendString(content); + + releaseStickyKeys(); + + // Reset the text input box to its normal padded cursor position. + resetTextInputTarget(TEXT_INPUT_PADDING); + + }, false); target.addEventListener("input", function(e) { // Ignore input events during text composition