@@ -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 */
17601805Guacamole . 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
0 commit comments