Skip to content

Commit 2bd1f7e

Browse files
committed
GUACAMOLE-2237: Multiline paste and text input client fixes.
1 parent a301739 commit 2bd1f7e

2 files changed

Lines changed: 140 additions & 0 deletions

File tree

guacamole-common-js/src/main/webapp/modules/Keyboard.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,10 +1737,55 @@ Guacamole.Keyboard = function Keyboard(element) {
17371737

17381738
};
17391739

1740+
/**
1741+
* Handles the given "paste" event, normalizing clipboard text before
1742+
* typing it into the remote session.
1743+
*
1744+
* @private
1745+
* @param {!ClipboardEvent} e
1746+
* The "paste" event to handle.
1747+
*/
1748+
var handlePaste = function handlePaste(e) {
1749+
1750+
// Only intercept if handler set
1751+
if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;
1752+
1753+
// Ignore events which have already been handled
1754+
if (!markEvent(e)) return;
1755+
1756+
if (!e.clipboardData)
1757+
return;
1758+
1759+
var content = e.clipboardData.getData('text/plain');
1760+
if (!content)
1761+
return;
1762+
1763+
// This paste path replays clipboard content as synthetic keyboard
1764+
// input. Normalize to CR here because the typing path treats CR as
1765+
// Enter.
1766+
content = Guacamole.Keyboard.normalizeToCR(content);
1767+
1768+
// Release held modifiers so pasted text isn't treated as modified
1769+
// input or shortcuts on the remote side.
1770+
Guacamole.Keyboard.pasteModifierKeysyms.forEach(function(keysym) {
1771+
guac_keyboard.release(keysym);
1772+
});
1773+
1774+
guac_keyboard.type(content);
1775+
1776+
e.preventDefault();
1777+
1778+
// Stop propagation defensively in case paste handling is added
1779+
// on parent elements.
1780+
e.stopPropagation();
1781+
1782+
};
1783+
17401784
// Automatically type text entered into the wrapped field
17411785
element.addEventListener("input", handleInput, false);
17421786
element.addEventListener("compositionend", handleCompositionEnd, false);
17431787
element.addEventListener("compositionstart", handleCompositionStart, false);
1788+
element.addEventListener("paste", handlePaste, false);
17441789

17451790
};
17461791

@@ -1759,6 +1804,59 @@ Guacamole.Keyboard = function Keyboard(element) {
17591804
*/
17601805
Guacamole.Keyboard._nextID = 0;
17611806

1807+
/**
1808+
* Normalizes clipboard text line endings to "\n".
1809+
*
1810+
* Used when working with text content (e.g. clipboard buffering or text input),
1811+
* where LF is the preferred canonical newline.
1812+
*
1813+
* @param {!string} content
1814+
* The clipboard text to normalize.
1815+
*
1816+
* @returns {!string}
1817+
* The clipboard text with all CRLF and CR sequences converted to LF.
1818+
*/
1819+
Guacamole.Keyboard.normalizeToLF = function normalizeToLF(content) {
1820+
return content.replace(/\r\n|\r/g, '\n');
1821+
};
1822+
1823+
/**
1824+
* Normalizes clipboard text line endings to "\r".
1825+
*
1826+
* Used when replaying text as keyboard input. Terminals interpret CR as the
1827+
* Enter key, while LF is just a newline and is not reliably treated as Enter.
1828+
*
1829+
* @param {!string} content
1830+
* The clipboard text to normalize.
1831+
*
1832+
* @returns {!string}
1833+
* The clipboard text with all CRLF, CR, and LF sequences converted to CR.
1834+
*/
1835+
Guacamole.Keyboard.normalizeToCR = function normalizeToCR(content) {
1836+
return content.replace(/\r\n|\n/g, '\r');
1837+
};
1838+
1839+
/**
1840+
* Modifiers to release before pasting so shortcut keys do not affect input
1841+
* on the remote side. Lock keys are excluded because they toggle state rather
1842+
* than being held.
1843+
*
1844+
* @type {!number[]}
1845+
*/
1846+
Guacamole.Keyboard.pasteModifierKeysyms = [
1847+
0xFE03, // AltGr
1848+
0xFFE1, // Left shift
1849+
0xFFE2, // Right shift
1850+
0xFFE3, // Left ctrl
1851+
0xFFE4, // Right ctrl
1852+
0xFFE7, // Left meta
1853+
0xFFE8, // Right meta
1854+
0xFFE9, // Left alt
1855+
0xFFEA, // Right alt
1856+
0xFFEB, // Left super/hyper
1857+
0xFFEC // Right super/hyper
1858+
];
1859+
17621860
/**
17631861
* The state of all supported keyboard modifiers.
17641862
* @constructor

guacamole/src/main/frontend/src/app/textInput/directives/guacTextInput.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,48 @@ angular.module('textInput').directive('guacTextInput', [function guacTextInput()
286286

287287
};
288288

289+
/**
290+
* Handles the given "paste" event, normalizing clipboard text
291+
* before sending it to the remote session.
292+
*
293+
* @private
294+
* @param {!ClipboardEvent} e
295+
* The "paste" event to handle.
296+
*/
297+
target.addEventListener("paste", function(e) {
298+
299+
if (!e.clipboardData)
300+
return;
301+
302+
var content = e.clipboardData.getData('text/plain');
303+
if (!content)
304+
return;
305+
306+
// This paste path works with canonical text buffered by the
307+
// text input control. Normalize to LF here and let
308+
// sendString()/sendCodepoint() map LF to Enter where needed.
309+
content = Guacamole.Keyboard.normalizeToLF(content);
310+
311+
// Release held modifiers so pasted text isn't treated as
312+
// modified input or shortcuts on the remote side.
313+
Guacamole.Keyboard.pasteModifierKeysyms.forEach(function(keysym) {
314+
$rootScope.$broadcast('guacSyntheticKeyup', keysym);
315+
});
316+
317+
sendString(content);
318+
319+
releaseStickyKeys();
320+
321+
// Reset content
322+
resetTextInputTarget(TEXT_INPUT_PADDING);
323+
e.preventDefault();
324+
325+
// Stop propagation defensively in case paste handling is added
326+
// on parent elements.
327+
e.stopPropagation();
328+
329+
}, false);
330+
289331
target.addEventListener("input", function(e) {
290332

291333
// Ignore input events during text composition

0 commit comments

Comments
 (0)