You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@guacamole.apache.org by "jmuehlner (via GitHub)" <gi...@apache.org> on 2023/06/24 00:22:02 UTC
[GitHub] [guacamole-client] jmuehlner opened a new pull request, #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
jmuehlner opened a new pull request, #894:
URL: https://github.com/apache/guacamole-client/pull/894
<img width="1130" alt="image" src="https://github.com/apache/guacamole-client/assets/4633119/e06a6bbc-b760-4f06-8d22-64d7f1df7e7f">
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] mike-jumper commented on pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "mike-jumper (via GitHub)" <gi...@apache.org>.
mike-jumper commented on PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#issuecomment-1609869410
> > Currently, every single key event has its own DOM element, so you can watch the characters flipping from gray to black (which is pretty fun TBH).
>
> Like a Mickey Mouse sing-a-long! 😆
Excellent ... now all we need to do is write a key-raoke extension ...
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1254796818
##########
guacamole/src/main/frontend/src/app/player/styles/textView.css:
##########
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * NOTE: This session recording player implementation is based on the Session
+ * Recording Player for Glyptodon Enterprise which is available at
+ * https://github.com/glyptodon/glyptodon-enterprise-player under the
+ * following license:
Review Comment:
I guess not, no. Removed.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#issuecomment-1608570066
> * If possible, visibly distinguish between typed characters and non-printable keys.
> * If possible, visibly distinguish between characters that have been typed, are being typed, and will be typed.
Ok, I've visually distinguished these by bolding the non-printable keys and control characters, and changing the color of future keypresses to a slightly lighter gray.
<img width="835" alt="image" src="https://github.com/apache/guacamole-client/assets/4633119/dcb51827-4a57-409e-979a-78297f5edd73">
Currently, every single key event has its own DOM element, so you can watch the characters flipping from gray to black (which is pretty fun TBH).
With my test recordings, the performance seemed just the same as before. I also tried doing an SSH recording where I held down keys on the keyboard for 20 minutes straight, resulting in 23499 DOM elements being rendered for the individual characters.
I tested this recording on Firefox and Chrome, and it performed about the same in both. Seeking around was pretty slow, but that seemed to be mostly rendering. The initial video processing was pretty smooth, taking ~10 seconds or so. When playing back the recording, the browser did use quite a bit of CPU, but never crashed or had major usability issues.
I'd be curious to know people's thoughts on this approach. If this is too performance-heavy, I could also somewhat streamline things by combining sequential similarly-styled key events into a single DOM element, though we'd lose granularity as far as which events occurred before or after the current recording playback position.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#issuecomment-1634691758
> Alrighty - just a few missing semicolons, but otherwise LGTM.
Semicolons fixed.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1253717238
##########
guacamole-common-js/src/main/webapp/modules/SessionRecording.js:
##########
@@ -872,6 +911,19 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
*/
this.onpause = null;
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available.
+ *
+ * @event
+ * @param {!String} text
+ * The typed text associated with the batch of text.
+ *
+ * @param {!number} timestamp
+ * The relative timestamp associated with the batch of text.
Review Comment:
This is addressed now.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1254747185
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
+ * UTF-8 keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ /* Determine size and initial byte mask */
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym.toString(), name: name, value: name, modifier: false});
Review Comment:
I don't see where that's happening. Can you be a bit more specific?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1242512781
##########
guacamole/src/main/frontend/src/app/player/directives/textView.js:
##########
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * NOTE: This session recording player implementation is based on the Session
+ * Recording Player for Glyptodon Enterprise which is available at
+ * https://github.com/glyptodon/glyptodon-enterprise-player under the
+ * following license:
+ *
+ * Copyright (C) 2019 Glyptodon, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
Review Comment:
Oops, no.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1254796972
##########
guacamole-common-js/src/main/webapp/modules/SessionRecording.js:
##########
@@ -104,6 +104,16 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
*/
var KEYFRAME_TIME_INTERVAL = 5000;
+ /**
+ * The minimum number of milliseconds which must elapse between key events
+ * before text can be split across multiple frames.
+ *
+ * @private
+ * @constant
+ * @type {Number}
+ */
+ var TYPED_TEXT_INTERVAL = 5000;
+
Review Comment:
Yeah, it was vestigal. Now gone.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1254736643
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
Review Comment:

--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] mike-jumper commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "mike-jumper (via GitHub)" <gi...@apache.org>.
mike-jumper commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1254611259
##########
guacamole/src/main/frontend/src/app/player/styles/textView.css:
##########
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * NOTE: This session recording player implementation is based on the Session
+ * Recording Player for Glyptodon Enterprise which is available at
+ * https://github.com/glyptodon/glyptodon-enterprise-player under the
+ * following license:
Review Comment:
Does this statement apply to this file?
##########
guacamole-common-js/src/main/webapp/modules/SessionRecording.js:
##########
@@ -104,6 +104,16 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
*/
var KEYFRAME_TIME_INTERVAL = 5000;
+ /**
+ * The minimum number of milliseconds which must elapse between key events
+ * before text can be split across multiple frames.
+ *
+ * @private
+ * @constant
+ * @type {Number}
+ */
+ var TYPED_TEXT_INTERVAL = 5000;
+
Review Comment:
I don't see this used anywhere. Is this from something that's no longer present?
##########
guacamole/src/main/frontend/src/app/player/services/playerTimeService.js:
##########
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * 'License'); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * A service for formatting time, specifically for the recording player.
+ */
+angular.module('player').factory('playerTimeService',
+ ['$injector', function playerTimeService($injector) {
+
+ const service = {};
+
+ /**
+ * Formats the given number as a decimal string, adding leading zeroes
+ * such that the string contains at least two digits. The given number
+ * MUST NOT be negative.
+ *
+ * @param {!number} value
+ * The number to format.
+ *
+ * @returns {!string}
+ * The decimal string representation of the given value, padded
+ * with leading zeroes up to a minimum length of two digits.
+ */
+ const zeroPad = function zeroPad(value) {
+ return value > 9 ? value : '0' + value;
+ };
+
+ /**
+ * Formats the given quantity of milliseconds as days, hours, minutes,
+ * and whole seconds, separated by colons (DD:HH:MM:SS). Hours are
+ * included only if the quantity is at least one hour, and days are
+ * included only if the quantity is at least one day. All included
+ * groups are zero-padded to two digits with the exception of the
+ * left-most group.
+ *
+ * @param {!number} value
+ * The time to format, in milliseconds.
+ *
+ * @returns {!string}
+ * The given quantity of milliseconds formatted as "DD:HH:MM:SS".
+ */
+ service.formatTime = function formatTime(value) {
+
+ // Round provided value down to whole seconds
+ value = Math.floor((value || 0) / 1000);
+
+ // Separate seconds into logical groups of seconds, minutes,
+ // hours, etc.
+ var groups = [ 1, 24, 60, 60 ];
+ for (var i = groups.length - 1; i >= 0; i--) {
+ var placeValue = groups[i];
+ groups[i] = zeroPad(value % placeValue);
+ value = Math.floor(value / placeValue);
+ }
+
+ // Format groups separated by colons, stripping leading zeroes and
+ // groups which are entirely zeroes, leaving at least minutes and
+ // seconds
+ var formatted = groups.join(':');
+ return /^[0:]*([0-9]{1,2}(?::[0-9]{2})+)$/.exec(formatted)[1];
+
+ };
Review Comment:
As a modified version of the function originally brought in from:
https://github.com/glyptodon/glyptodon-enterprise-player/blob/8e2d76c7bee77043afda3b6f351fff9f74afe5e2/src/main/webapp/modules/player/directives/player.js#L158-L192
this file would need that additional note regarding copyright and original licensing:
https://github.com/apache/guacamole-client/blob/e46d06e6b832c02a666e0f99f4d2a3495b030a23/guacamole/src/main/frontend/src/app/player/directives/player.js#L20-L45
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
+ * UTF-8 keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ /* Determine size and initial byte mask */
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym.toString(), name: name, value: name, modifier: false});
Review Comment:
Why do we need to convert the codepoint to UTF-8 and back again?
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
+ * UTF-8 keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ /* Determine size and initial byte mask */
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym.toString(), name: name, value: name, modifier: false});
+
+ }
+
+ /**
+ * Return a KeyDefinition corresponding to the provided keysym.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to return a KeyDefinition for.
+ *
+ * @returns
Review Comment:
Please add JSDoc type annotation.
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
+ * UTF-8 keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ /* Determine size and initial byte mask */
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym.toString(), name: name, value: name, modifier: false});
+
+ }
+
+ /**
+ * Return a KeyDefinition corresponding to the provided keysym.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to return a KeyDefinition for.
+ *
+ * @returns
+ * A KeyDefinition corresponding to the provided keysym.
+ */
+ function getKeyDefinitionByKeysym(keysym) {
+
+ // If it's a known type, return the existing definition
+ if (keysym in KNOWN_KEYS)
+ return KNOWN_KEYS[keysym];
+
+ // Return a UTF-8 KeyDefinition, if valid
+ var definition = getUnicodeKeyDefinition(keysym);
+ if (definition != null)
+ return definition;
+
+ // If it's not UTF-8, return an unknown definition, with the name
+ // just set to the hex value of the keysym
+ return new KeyDefinition({
+ keysym: keysym,
+ name: '0x' + String(keysym.toString(16))
+ })
+
+ }
+
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available. A new batch will be provided every time a new key event
+ * is processed after more than batchSeperation milliseconds after the
+ * previous key event.
+ *
+ * @event
+ * @param {!Guacamole.KeyEventInterpreter.KeyEventBatch}
+ */
+ interpreter.onBatch = null;
+
+ /**
+ * Handles a raw key event, potentially appending typed text to the
+ * current batch, and calling onBatch with the current batch, if the
+ * callback is set and a new batch is about to be started.
+ *
+ * @param {!string[]} args
+ * The arguments of the key event.
+ */
+ interpreter.handleKeyEvent = function handleKeyEvent(args) {
+
+ // The X11 keysym
+ var keysym = parseInt(args[0]);
+
+ // Either 1 or 0 for pressed or released, respectively
+ var pressed = parseInt(args[1]);
+
+ // The timestamp when this key event occured
+ var timestamp = parseInt(args[2]);
+
+ // If no current batch exists, start a new one now
+ if (!currentBatch)
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ // Only switch to a new batch of text if sufficient time has passed
+ // since the last key event
+ var newBatch = (batchSeperation >= 0
+ && (timestamp - lastKeyEvent) >= batchSeperation);
+ lastKeyEvent = timestamp;
+
+ if (newBatch) {
+
+ // Call the handler with the current batch of text and the timestamp
+ // at which the current batch started
+ if (currentBatch.events.length && interpreter.onBatch)
+ interpreter.onBatch(currentBatch);
+
+ // Move on to the next batch of text
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ }
+
+ var keyDefinition = getKeyDefinitionByKeysym(keysym);
+
+ // Mark down whether the key was pressed or released
+ if (keyDefinition.modifier) {
+ if (pressed)
+ pressedKeys[keysym] = keyDefinition;
+ else
+ delete pressedKeys[keysym];
+ }
+
+ // Append to the current typed value when a printable
+ // (non-modifier) key is pressed
+ else if (pressed) {
+
+ var relativeTimestap = timestamp - startTimestamp;
+
+ if (isShortcut()) {
+
+ var shortcutText = '<'
+
+ var firstKey = true;
+
+ // Compose entry by inspecting the state of each tracked key.
+ // At least one key must be pressed when in a shortcut.
+ for (var keysym in pressedKeys) {
+
+ var pressedKeyDefinition = pressedKeys[keysym];
+
+ // Print name of key
+ if (firstKey) {
+ shortcutText += pressedKeyDefinition.name;
+ firstKey = false;
+ }
+
+ else
+ shortcutText += ('+' + pressedKeyDefinition.name);
Review Comment:
Indentation looks to have been accidentally misaligned here.
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,579 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
+ * UTF-8 keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ /* Determine size and initial byte mask */
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym.toString(), name: name, value: name, modifier: false});
+
+ }
+
+ /**
+ * Return a KeyDefinition corresponding to the provided keysym.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to return a KeyDefinition for.
+ *
+ * @returns
+ * A KeyDefinition corresponding to the provided keysym.
+ */
+ function getKeyDefinitionByKeysym(keysym) {
+
+ // If it's a known type, return the existing definition
+ if (keysym in KNOWN_KEYS)
+ return KNOWN_KEYS[keysym];
+
+ // Return a UTF-8 KeyDefinition, if valid
+ var definition = getUnicodeKeyDefinition(keysym);
+ if (definition != null)
+ return definition;
+
+ // If it's not UTF-8, return an unknown definition, with the name
+ // just set to the hex value of the keysym
+ return new KeyDefinition({
+ keysym: keysym,
+ name: '0x' + String(keysym.toString(16))
+ })
+
+ }
+
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available. A new batch will be provided every time a new key event
+ * is processed after more than batchSeperation milliseconds after the
+ * previous key event.
+ *
+ * @event
+ * @param {!Guacamole.KeyEventInterpreter.KeyEventBatch}
+ */
+ interpreter.onBatch = null;
+
+ /**
+ * Handles a raw key event, potentially appending typed text to the
+ * current batch, and calling onBatch with the current batch, if the
+ * callback is set and a new batch is about to be started.
+ *
+ * @param {!string[]} args
+ * The arguments of the key event.
+ */
+ interpreter.handleKeyEvent = function handleKeyEvent(args) {
Review Comment:
Does JSDoc correctly generate the docs for this, etc. when the function is added to the object via `interpreter.nameOfTheThing` instead of `this.nameOfTheThing`?
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
+ * UTF-8 keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ /* Determine size and initial byte mask */
Review Comment:
JavaScript comments should be:
```
// C++ style
```
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
Review Comment:
"it it's"
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
+ * UTF-8 keysym, or null otherwise.
Review Comment:
Please:
* Add the return type JSDoc annotation.
* Omit "Return" from the description following `@returns` (redundant).
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
+ * UTF-8 keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ /* Determine size and initial byte mask */
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym.toString(), name: name, value: name, modifier: false});
+
+ }
+
+ /**
+ * Return a KeyDefinition corresponding to the provided keysym.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to return a KeyDefinition for.
+ *
+ * @returns
+ * A KeyDefinition corresponding to the provided keysym.
+ */
+ function getKeyDefinitionByKeysym(keysym) {
+
+ // If it's a known type, return the existing definition
+ if (keysym in KNOWN_KEYS)
+ return KNOWN_KEYS[keysym];
+
+ // Return a UTF-8 KeyDefinition, if valid
+ var definition = getUnicodeKeyDefinition(keysym);
+ if (definition != null)
+ return definition;
+
+ // If it's not UTF-8, return an unknown definition, with the name
+ // just set to the hex value of the keysym
+ return new KeyDefinition({
+ keysym: keysym,
+ name: '0x' + String(keysym.toString(16))
+ })
+
+ }
+
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available. A new batch will be provided every time a new key event
+ * is processed after more than batchSeperation milliseconds after the
+ * previous key event.
+ *
+ * @event
+ * @param {!Guacamole.KeyEventInterpreter.KeyEventBatch}
+ */
+ interpreter.onBatch = null;
Review Comment:
To align with established naming conventions, this should be `onbatch`.
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,579 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
+ * UTF-8 keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ /* Determine size and initial byte mask */
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym.toString(), name: name, value: name, modifier: false});
+
+ }
+
+ /**
+ * Return a KeyDefinition corresponding to the provided keysym.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to return a KeyDefinition for.
+ *
+ * @returns
+ * A KeyDefinition corresponding to the provided keysym.
+ */
+ function getKeyDefinitionByKeysym(keysym) {
+
+ // If it's a known type, return the existing definition
+ if (keysym in KNOWN_KEYS)
+ return KNOWN_KEYS[keysym];
+
+ // Return a UTF-8 KeyDefinition, if valid
+ var definition = getUnicodeKeyDefinition(keysym);
+ if (definition != null)
+ return definition;
+
+ // If it's not UTF-8, return an unknown definition, with the name
+ // just set to the hex value of the keysym
+ return new KeyDefinition({
+ keysym: keysym,
+ name: '0x' + String(keysym.toString(16))
+ })
+
+ }
+
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available. A new batch will be provided every time a new key event
+ * is processed after more than batchSeperation milliseconds after the
+ * previous key event.
+ *
+ * @event
+ * @param {!Guacamole.KeyEventInterpreter.KeyEventBatch}
+ */
+ interpreter.onBatch = null;
+
+ /**
+ * Handles a raw key event, potentially appending typed text to the
+ * current batch, and calling onBatch with the current batch, if the
+ * callback is set and a new batch is about to be started.
+ *
+ * @param {!string[]} args
+ * The arguments of the key event.
+ */
+ interpreter.handleKeyEvent = function handleKeyEvent(args) {
+
+ // The X11 keysym
+ var keysym = parseInt(args[0]);
+
+ // Either 1 or 0 for pressed or released, respectively
+ var pressed = parseInt(args[1]);
+
+ // The timestamp when this key event occured
+ var timestamp = parseInt(args[2]);
+
+ // If no current batch exists, start a new one now
+ if (!currentBatch)
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ // Only switch to a new batch of text if sufficient time has passed
+ // since the last key event
+ var newBatch = (batchSeperation >= 0
+ && (timestamp - lastKeyEvent) >= batchSeperation);
+ lastKeyEvent = timestamp;
+
+ if (newBatch) {
+
+ // Call the handler with the current batch of text and the timestamp
+ // at which the current batch started
+ if (currentBatch.events.length && interpreter.onBatch)
+ interpreter.onBatch(currentBatch);
+
+ // Move on to the next batch of text
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ }
+
+ var keyDefinition = getKeyDefinitionByKeysym(keysym);
+
+ // Mark down whether the key was pressed or released
+ if (keyDefinition.modifier) {
+ if (pressed)
+ pressedKeys[keysym] = keyDefinition;
+ else
+ delete pressedKeys[keysym];
+ }
+
+ // Append to the current typed value when a printable
+ // (non-modifier) key is pressed
+ else if (pressed) {
+
+ var relativeTimestap = timestamp - startTimestamp;
+
+ if (isShortcut()) {
+
+ var shortcutText = '<'
+
+ var firstKey = true;
+
+ // Compose entry by inspecting the state of each tracked key.
+ // At least one key must be pressed when in a shortcut.
+ for (var keysym in pressedKeys) {
+
+ var pressedKeyDefinition = pressedKeys[keysym];
+
+ // Print name of key
+ if (firstKey) {
+ shortcutText += pressedKeyDefinition.name;
+ firstKey = false;
+ }
+
+ else
+ shortcutText += ('+' + pressedKeyDefinition.name);
+
+ }
+
+ // Finally, append the printable key to close the shortcut
+ shortcutText += ('+' + keyDefinition.name + '>')
+
+ // Add the shortcut to the current batch
+ currentBatch.simpleValue += shortcutText;
+ currentBatch.events.push(new Guacamole.KeyEventInterpreter.KeyEvent(
+ shortcutText, false, relativeTimestap));
+
+ }
+
+ // Print the key itself
+ else {
+
+ var keyText;
+ var typed;
+
+ // Print the value if explicitly defined
+ if (keyDefinition.value != null) {
+
+ keyText = keyDefinition.value;
+ typed = true;
+
+ }
+
+ // Otherwise print the name
+ else {
+
+ keyText = ('<' + keyDefinition.name + '>');
+
+ // While this is a representation for a single character,
+ // the key text is the name of the key, not the actual
+ // character itself
+ typed = false;
+
+ }
+
+ // Add the key to the current batch
+ currentBatch.simpleValue += keyText;
+ currentBatch.events.push(new Guacamole.KeyEventInterpreter.KeyEvent(
+ keyText, typed, relativeTimestap));
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Return the current batch of typed text. Note that the batch may be
+ * incomplete, as more key events might be processed before the next
+ * batch starts.
+ *
+ * @returns
Review Comment:
Please add JSDoc type notation.
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
+ * UTF-8 keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ /* Determine size and initial byte mask */
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym.toString(), name: name, value: name, modifier: false});
+
+ }
+
+ /**
+ * Return a KeyDefinition corresponding to the provided keysym.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to return a KeyDefinition for.
+ *
+ * @returns
+ * A KeyDefinition corresponding to the provided keysym.
+ */
+ function getKeyDefinitionByKeysym(keysym) {
+
+ // If it's a known type, return the existing definition
+ if (keysym in KNOWN_KEYS)
+ return KNOWN_KEYS[keysym];
+
+ // Return a UTF-8 KeyDefinition, if valid
+ var definition = getUnicodeKeyDefinition(keysym);
+ if (definition != null)
+ return definition;
+
+ // If it's not UTF-8, return an unknown definition, with the name
+ // just set to the hex value of the keysym
+ return new KeyDefinition({
+ keysym: keysym,
+ name: '0x' + String(keysym.toString(16))
+ })
+
+ }
+
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available. A new batch will be provided every time a new key event
+ * is processed after more than batchSeperation milliseconds after the
+ * previous key event.
+ *
+ * @event
+ * @param {!Guacamole.KeyEventInterpreter.KeyEventBatch}
+ */
+ interpreter.onBatch = null;
+
+ /**
+ * Handles a raw key event, potentially appending typed text to the
+ * current batch, and calling onBatch with the current batch, if the
+ * callback is set and a new batch is about to be started.
+ *
+ * @param {!string[]} args
+ * The arguments of the key event.
+ */
+ interpreter.handleKeyEvent = function handleKeyEvent(args) {
+
+ // The X11 keysym
+ var keysym = parseInt(args[0]);
+
+ // Either 1 or 0 for pressed or released, respectively
+ var pressed = parseInt(args[1]);
+
+ // The timestamp when this key event occured
+ var timestamp = parseInt(args[2]);
+
+ // If no current batch exists, start a new one now
+ if (!currentBatch)
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ // Only switch to a new batch of text if sufficient time has passed
+ // since the last key event
+ var newBatch = (batchSeperation >= 0
+ && (timestamp - lastKeyEvent) >= batchSeperation);
+ lastKeyEvent = timestamp;
+
+ if (newBatch) {
+
+ // Call the handler with the current batch of text and the timestamp
+ // at which the current batch started
+ if (currentBatch.events.length && interpreter.onBatch)
+ interpreter.onBatch(currentBatch);
+
+ // Move on to the next batch of text
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ }
+
+ var keyDefinition = getKeyDefinitionByKeysym(keysym);
+
+ // Mark down whether the key was pressed or released
+ if (keyDefinition.modifier) {
+ if (pressed)
+ pressedKeys[keysym] = keyDefinition;
+ else
+ delete pressedKeys[keysym];
+ }
+
+ // Append to the current typed value when a printable
+ // (non-modifier) key is pressed
+ else if (pressed) {
+
+ var relativeTimestap = timestamp - startTimestamp;
+
+ if (isShortcut()) {
+
+ var shortcutText = '<'
Review Comment:
Missing semicolon.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1260093311
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,579 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onbatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onbatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns {KeyDefinition}
+ * A KeyDefinition for the provided keysym, if it's a valid UTF-8
+ * keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ // Determine size and initial byte mask
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
Review Comment:
Uh yeah, we definitely know that it will be no greater than `0xFFFF` considering `var codepoint = keysym & 0xFFFF;` on line 281. I'll use `fromCharCode`. All this stuff was carried over from the `guaclog` codebase and is indeed not needed.
Good catch.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1253717354
##########
guacamole-common-js/src/main/webapp/modules/SessionRecording.js:
##########
@@ -872,6 +911,19 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
*/
this.onpause = null;
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available.
+ *
+ * @event
+ * @param {!String} text
+ * The typed text associated with the batch of text.
+ *
+ * @param {!number} timestamp
+ * The relative timestamp associated with the batch of text.
Review Comment:
This is addressed now.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1254753712
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,578 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
+ * UTF-8 keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ /* Determine size and initial byte mask */
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym.toString(), name: name, value: name, modifier: false});
Review Comment:
Ah, I do see that calling `toString()` here is pointless. I'll get rid of that.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] mike-jumper commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "mike-jumper (via GitHub)" <gi...@apache.org>.
mike-jumper commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1260033091
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,579 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onbatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onbatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns {KeyDefinition}
+ * A KeyDefinition for the provided keysym, if it's a valid UTF-8
+ * keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ // Determine size and initial byte mask
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
Review Comment:
Or even:
```js
var name = String.fromCharCode(codepoint);
```
so long as we know `codepoint` will always be no greater than `0xFFFF`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] mike-jumper merged pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "mike-jumper (via GitHub)" <gi...@apache.org>.
mike-jumper merged PR #894:
URL: https://github.com/apache/guacamole-client/pull/894
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] mike-jumper commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "mike-jumper (via GitHub)" <gi...@apache.org>.
mike-jumper commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1262857349
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,550 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onbatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KeyDefinition|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KeyDefinition.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onbatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns {KeyDefinition}
+ * A KeyDefinition for the provided keysym, if it's a valid UTF-8
+ * keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ // Convert to UTF8 string
+ var codepoint = keysym & 0xFFFF;
+ var name = String.fromCharCode(codepoint);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym, name: name, value: name, modifier: false});
+
+ }
+
+ /**
+ * Return a KeyDefinition corresponding to the provided keysym.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to return a KeyDefinition for.
+ *
+ * @returns {KeyDefinition}
+ * A KeyDefinition corresponding to the provided keysym.
+ */
+ function getKeyDefinitionByKeysym(keysym) {
+
+ // If it's a known type, return the existing definition
+ if (keysym in KNOWN_KEYS)
+ return KNOWN_KEYS[keysym];
+
+ // Return a UTF-8 KeyDefinition, if valid
+ var definition = getUnicodeKeyDefinition(keysym);
+ if (definition != null)
+ return definition;
+
+ // If it's not UTF-8, return an unknown definition, with the name
+ // just set to the hex value of the keysym
+ return new KeyDefinition({
+ keysym: keysym,
+ name: '0x' + String(keysym.toString(16))
+ })
+
+ }
+
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available. A new batch will be provided every time a new key event
+ * is processed after more than batchSeperation milliseconds after the
+ * previous key event.
+ *
+ * @event
+ * @param {!Guacamole.KeyEventInterpreter.KeyEventBatch}
+ */
+ this.onbatch = null;
+
+ /**
+ * Handles a raw key event, potentially appending typed text to the
+ * current batch, and calling onbatch with the current batch, if the
+ * callback is set and a new batch is about to be started.
+ *
+ * @param {!string[]} args
+ * The arguments of the key event.
+ */
+ this.handleKeyEvent = function handleKeyEvent(args) {
+
+ // The X11 keysym
+ var keysym = parseInt(args[0]);
+
+ // Either 1 or 0 for pressed or released, respectively
+ var pressed = parseInt(args[1]);
+
+ // The timestamp when this key event occured
+ var timestamp = parseInt(args[2]);
+
+ // If no current batch exists, start a new one now
+ if (!currentBatch)
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ // Only switch to a new batch of text if sufficient time has passed
+ // since the last key event
+ var newBatch = (batchSeperation >= 0
+ && (timestamp - lastKeyEvent) >= batchSeperation);
+ lastKeyEvent = timestamp;
+
+ if (newBatch) {
+
+ // Call the handler with the current batch of text and the timestamp
+ // at which the current batch started
+ if (currentBatch.events.length && interpreter.onbatch)
+ interpreter.onbatch(currentBatch);
+
+ // Move on to the next batch of text
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ }
+
+ var keyDefinition = getKeyDefinitionByKeysym(keysym);
+
+ // Mark down whether the key was pressed or released
+ if (keyDefinition.modifier) {
+ if (pressed)
+ pressedKeys[keysym] = keyDefinition;
+ else
+ delete pressedKeys[keysym];
+ }
+
+ // Append to the current typed value when a printable
+ // (non-modifier) key is pressed
+ else if (pressed) {
+
+ var relativeTimestap = timestamp - startTimestamp;
+
+ if (isShortcut()) {
+
+ var shortcutText = '<';
+
+ var firstKey = true;
+
+ // Compose entry by inspecting the state of each tracked key.
+ // At least one key must be pressed when in a shortcut.
+ for (var keysym in pressedKeys) {
+
+ var pressedKeyDefinition = pressedKeys[keysym];
+
+ // Print name of key
+ if (firstKey) {
+ shortcutText += pressedKeyDefinition.name;
+ firstKey = false;
+ }
+
+ else
+ shortcutText += ('+' + pressedKeyDefinition.name);
+
+ }
+
+ // Finally, append the printable key to close the shortcut
+ shortcutText += ('+' + keyDefinition.name + '>')
+
+ // Add the shortcut to the current batch
+ currentBatch.simpleValue += shortcutText;
+ currentBatch.events.push(new Guacamole.KeyEventInterpreter.KeyEvent(
+ shortcutText, false, relativeTimestap));
+
+ }
+
+ // Print the key itself
+ else {
+
+ var keyText;
+ var typed;
+
+ // Print the value if explicitly defined
+ if (keyDefinition.value != null) {
+
+ keyText = keyDefinition.value;
+ typed = true;
+
+ }
+
+ // Otherwise print the name
+ else {
+
+ keyText = ('<' + keyDefinition.name + '>');
+
+ // While this is a representation for a single character,
+ // the key text is the name of the key, not the actual
+ // character itself
+ typed = false;
+
+ }
+
+ // Add the key to the current batch
+ currentBatch.simpleValue += keyText;
+ currentBatch.events.push(new Guacamole.KeyEventInterpreter.KeyEvent(
+ keyText, typed, relativeTimestap));
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Return the current batch of typed text. Note that the batch may be
+ * incomplete, as more key events might be processed before the next
+ * batch starts.
+ *
+ * @returns {Guacamole.KeyEventInterpreter.KeyEventBatch}
+ * The current batch of text.
+ */
+ this.getCurrentBatch = function getCurrentBatch() {
+ return currentBatch;
+ }
+}
+
+/**
+ * A granular description of an extracted key event, including a human-readable
+ * text representation of the event, whether the event is directly typed or not,
+ * and the timestamp when the event occured.
+ *
+ * @constructor
+ * @param {!String} text
+ * A human-readable representation of the event.
+ *
+ * @param {!boolean} typed
+ * True if this event represents a directly-typed character, or false
+ * otherwise.
+ *
+ * @param {!Number} timestamp
+ * The timestamp from the recording when this event occured.
+ */
+Guacamole.KeyEventInterpreter.KeyEvent = function KeyEvent(text, typed, timestamp) {
+
+ /**
+ * A human-readable representation of the event. If a printable character
+ * was directly typed, this will just be that character. Otherwise it will
+ * be a string describing the event.
+ *
+ * @type {!String}
+ */
+ this.text = text;
+
+ /**
+ * True if this text of this event is exactly a typed character, or false
+ * otherwise.
+ *
+ * @type {!boolean}
+ */
+ this.typed = typed;
+
+ /**
+ * The timestamp from the recording when this event occured. If a
+ * `startTimestamp` value was provided to the interpreter constructor, this
+ * will be relative to start of the recording. If not, it will be the raw
+ * timestamp from the key event.
+ *
+ * @type {!Number}
+ */
+ this.timestamp = timestamp;
+
+};
+
+/**
+ * A series of intepreted key events, seperated by at least the configured
+ * batchSeperation value from any other key events in the recording corresponding
+ * to the interpreted key events. A batch will always consist of at least one key
+ * event, and an associated simplified representation of the event(s).
+ *
+ * @constructor
+ * @param {!Guacamole.KeyEventInterpreter.KeyEvent[]} events
+ * The interpreted key events for this batch.
+ *
+ * @param {!String} simpleValue
+ * The simplified, human-readable value representing the key events for
+ * this batch.
+ */
+Guacamole.KeyEventInterpreter.KeyEventBatch = function KeyEventBatch(events, simpleValue) {
+
+ /**
+ * All key events for this batch.
+ *
+ * @type {!Guacamole.KeyEventInterpreter.KeyEvent[]}
+ */
+ this.events = events || [];
+
+ /**
+ * The simplified, human-readable value representing the key events for
+ * this batch, equivalent to concatenating the `text` field of all key
+ * events in the batch.
+ *
+ * @type {!String}
+ */
+ this.simpleValue = simpleValue || '';
+
+}
Review Comment:
Missing semicolon.
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,550 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onbatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KeyDefinition|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KeyDefinition.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onbatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns {KeyDefinition}
+ * A KeyDefinition for the provided keysym, if it's a valid UTF-8
+ * keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ // Convert to UTF8 string
+ var codepoint = keysym & 0xFFFF;
+ var name = String.fromCharCode(codepoint);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym, name: name, value: name, modifier: false});
+
+ }
+
+ /**
+ * Return a KeyDefinition corresponding to the provided keysym.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to return a KeyDefinition for.
+ *
+ * @returns {KeyDefinition}
+ * A KeyDefinition corresponding to the provided keysym.
+ */
+ function getKeyDefinitionByKeysym(keysym) {
+
+ // If it's a known type, return the existing definition
+ if (keysym in KNOWN_KEYS)
+ return KNOWN_KEYS[keysym];
+
+ // Return a UTF-8 KeyDefinition, if valid
+ var definition = getUnicodeKeyDefinition(keysym);
+ if (definition != null)
+ return definition;
+
+ // If it's not UTF-8, return an unknown definition, with the name
+ // just set to the hex value of the keysym
+ return new KeyDefinition({
+ keysym: keysym,
+ name: '0x' + String(keysym.toString(16))
+ })
+
+ }
+
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available. A new batch will be provided every time a new key event
+ * is processed after more than batchSeperation milliseconds after the
+ * previous key event.
+ *
+ * @event
+ * @param {!Guacamole.KeyEventInterpreter.KeyEventBatch}
+ */
+ this.onbatch = null;
+
+ /**
+ * Handles a raw key event, potentially appending typed text to the
+ * current batch, and calling onbatch with the current batch, if the
+ * callback is set and a new batch is about to be started.
+ *
+ * @param {!string[]} args
+ * The arguments of the key event.
+ */
+ this.handleKeyEvent = function handleKeyEvent(args) {
+
+ // The X11 keysym
+ var keysym = parseInt(args[0]);
+
+ // Either 1 or 0 for pressed or released, respectively
+ var pressed = parseInt(args[1]);
+
+ // The timestamp when this key event occured
+ var timestamp = parseInt(args[2]);
+
+ // If no current batch exists, start a new one now
+ if (!currentBatch)
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ // Only switch to a new batch of text if sufficient time has passed
+ // since the last key event
+ var newBatch = (batchSeperation >= 0
+ && (timestamp - lastKeyEvent) >= batchSeperation);
+ lastKeyEvent = timestamp;
+
+ if (newBatch) {
+
+ // Call the handler with the current batch of text and the timestamp
+ // at which the current batch started
+ if (currentBatch.events.length && interpreter.onbatch)
+ interpreter.onbatch(currentBatch);
+
+ // Move on to the next batch of text
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ }
+
+ var keyDefinition = getKeyDefinitionByKeysym(keysym);
+
+ // Mark down whether the key was pressed or released
+ if (keyDefinition.modifier) {
+ if (pressed)
+ pressedKeys[keysym] = keyDefinition;
+ else
+ delete pressedKeys[keysym];
+ }
+
+ // Append to the current typed value when a printable
+ // (non-modifier) key is pressed
+ else if (pressed) {
+
+ var relativeTimestap = timestamp - startTimestamp;
+
+ if (isShortcut()) {
+
+ var shortcutText = '<';
+
+ var firstKey = true;
+
+ // Compose entry by inspecting the state of each tracked key.
+ // At least one key must be pressed when in a shortcut.
+ for (var keysym in pressedKeys) {
+
+ var pressedKeyDefinition = pressedKeys[keysym];
+
+ // Print name of key
+ if (firstKey) {
+ shortcutText += pressedKeyDefinition.name;
+ firstKey = false;
+ }
+
+ else
+ shortcutText += ('+' + pressedKeyDefinition.name);
+
+ }
+
+ // Finally, append the printable key to close the shortcut
+ shortcutText += ('+' + keyDefinition.name + '>')
+
+ // Add the shortcut to the current batch
+ currentBatch.simpleValue += shortcutText;
+ currentBatch.events.push(new Guacamole.KeyEventInterpreter.KeyEvent(
+ shortcutText, false, relativeTimestap));
+
+ }
+
+ // Print the key itself
+ else {
+
+ var keyText;
+ var typed;
+
+ // Print the value if explicitly defined
+ if (keyDefinition.value != null) {
+
+ keyText = keyDefinition.value;
+ typed = true;
+
+ }
+
+ // Otherwise print the name
+ else {
+
+ keyText = ('<' + keyDefinition.name + '>');
+
+ // While this is a representation for a single character,
+ // the key text is the name of the key, not the actual
+ // character itself
+ typed = false;
+
+ }
+
+ // Add the key to the current batch
+ currentBatch.simpleValue += keyText;
+ currentBatch.events.push(new Guacamole.KeyEventInterpreter.KeyEvent(
+ keyText, typed, relativeTimestap));
+
+ }
+
+ }
+
+ }
Review Comment:
Missing semicolon.
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,550 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onbatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KeyDefinition|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KeyDefinition.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onbatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns {KeyDefinition}
+ * A KeyDefinition for the provided keysym, if it's a valid UTF-8
+ * keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ // Convert to UTF8 string
+ var codepoint = keysym & 0xFFFF;
+ var name = String.fromCharCode(codepoint);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym, name: name, value: name, modifier: false});
+
+ }
+
+ /**
+ * Return a KeyDefinition corresponding to the provided keysym.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to return a KeyDefinition for.
+ *
+ * @returns {KeyDefinition}
+ * A KeyDefinition corresponding to the provided keysym.
+ */
+ function getKeyDefinitionByKeysym(keysym) {
+
+ // If it's a known type, return the existing definition
+ if (keysym in KNOWN_KEYS)
+ return KNOWN_KEYS[keysym];
+
+ // Return a UTF-8 KeyDefinition, if valid
+ var definition = getUnicodeKeyDefinition(keysym);
+ if (definition != null)
+ return definition;
+
+ // If it's not UTF-8, return an unknown definition, with the name
+ // just set to the hex value of the keysym
+ return new KeyDefinition({
+ keysym: keysym,
+ name: '0x' + String(keysym.toString(16))
+ })
+
+ }
+
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available. A new batch will be provided every time a new key event
+ * is processed after more than batchSeperation milliseconds after the
+ * previous key event.
+ *
+ * @event
+ * @param {!Guacamole.KeyEventInterpreter.KeyEventBatch}
+ */
+ this.onbatch = null;
+
+ /**
+ * Handles a raw key event, potentially appending typed text to the
+ * current batch, and calling onbatch with the current batch, if the
+ * callback is set and a new batch is about to be started.
+ *
+ * @param {!string[]} args
+ * The arguments of the key event.
+ */
+ this.handleKeyEvent = function handleKeyEvent(args) {
+
+ // The X11 keysym
+ var keysym = parseInt(args[0]);
+
+ // Either 1 or 0 for pressed or released, respectively
+ var pressed = parseInt(args[1]);
+
+ // The timestamp when this key event occured
+ var timestamp = parseInt(args[2]);
+
+ // If no current batch exists, start a new one now
+ if (!currentBatch)
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ // Only switch to a new batch of text if sufficient time has passed
+ // since the last key event
+ var newBatch = (batchSeperation >= 0
+ && (timestamp - lastKeyEvent) >= batchSeperation);
+ lastKeyEvent = timestamp;
+
+ if (newBatch) {
+
+ // Call the handler with the current batch of text and the timestamp
+ // at which the current batch started
+ if (currentBatch.events.length && interpreter.onbatch)
+ interpreter.onbatch(currentBatch);
+
+ // Move on to the next batch of text
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ }
+
+ var keyDefinition = getKeyDefinitionByKeysym(keysym);
+
+ // Mark down whether the key was pressed or released
+ if (keyDefinition.modifier) {
+ if (pressed)
+ pressedKeys[keysym] = keyDefinition;
+ else
+ delete pressedKeys[keysym];
+ }
+
+ // Append to the current typed value when a printable
+ // (non-modifier) key is pressed
+ else if (pressed) {
+
+ var relativeTimestap = timestamp - startTimestamp;
+
+ if (isShortcut()) {
+
+ var shortcutText = '<';
+
+ var firstKey = true;
+
+ // Compose entry by inspecting the state of each tracked key.
+ // At least one key must be pressed when in a shortcut.
+ for (var keysym in pressedKeys) {
+
+ var pressedKeyDefinition = pressedKeys[keysym];
+
+ // Print name of key
+ if (firstKey) {
+ shortcutText += pressedKeyDefinition.name;
+ firstKey = false;
+ }
+
+ else
+ shortcutText += ('+' + pressedKeyDefinition.name);
+
+ }
+
+ // Finally, append the printable key to close the shortcut
+ shortcutText += ('+' + keyDefinition.name + '>')
+
+ // Add the shortcut to the current batch
+ currentBatch.simpleValue += shortcutText;
+ currentBatch.events.push(new Guacamole.KeyEventInterpreter.KeyEvent(
+ shortcutText, false, relativeTimestap));
+
+ }
+
+ // Print the key itself
+ else {
+
+ var keyText;
+ var typed;
+
+ // Print the value if explicitly defined
+ if (keyDefinition.value != null) {
+
+ keyText = keyDefinition.value;
+ typed = true;
+
+ }
+
+ // Otherwise print the name
+ else {
+
+ keyText = ('<' + keyDefinition.name + '>');
+
+ // While this is a representation for a single character,
+ // the key text is the name of the key, not the actual
+ // character itself
+ typed = false;
+
+ }
+
+ // Add the key to the current batch
+ currentBatch.simpleValue += keyText;
+ currentBatch.events.push(new Guacamole.KeyEventInterpreter.KeyEvent(
+ keyText, typed, relativeTimestap));
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Return the current batch of typed text. Note that the batch may be
+ * incomplete, as more key events might be processed before the next
+ * batch starts.
+ *
+ * @returns {Guacamole.KeyEventInterpreter.KeyEventBatch}
+ * The current batch of text.
+ */
+ this.getCurrentBatch = function getCurrentBatch() {
+ return currentBatch;
+ }
Review Comment:
Missing semicolon.
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,550 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onbatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KeyDefinition|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KeyDefinition.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onbatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns {KeyDefinition}
+ * A KeyDefinition for the provided keysym, if it's a valid UTF-8
+ * keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ // Convert to UTF8 string
+ var codepoint = keysym & 0xFFFF;
+ var name = String.fromCharCode(codepoint);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym, name: name, value: name, modifier: false});
+
+ }
+
+ /**
+ * Return a KeyDefinition corresponding to the provided keysym.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to return a KeyDefinition for.
+ *
+ * @returns {KeyDefinition}
+ * A KeyDefinition corresponding to the provided keysym.
+ */
+ function getKeyDefinitionByKeysym(keysym) {
+
+ // If it's a known type, return the existing definition
+ if (keysym in KNOWN_KEYS)
+ return KNOWN_KEYS[keysym];
+
+ // Return a UTF-8 KeyDefinition, if valid
+ var definition = getUnicodeKeyDefinition(keysym);
+ if (definition != null)
+ return definition;
+
+ // If it's not UTF-8, return an unknown definition, with the name
+ // just set to the hex value of the keysym
+ return new KeyDefinition({
+ keysym: keysym,
+ name: '0x' + String(keysym.toString(16))
+ })
+
+ }
+
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available. A new batch will be provided every time a new key event
+ * is processed after more than batchSeperation milliseconds after the
+ * previous key event.
+ *
+ * @event
+ * @param {!Guacamole.KeyEventInterpreter.KeyEventBatch}
+ */
+ this.onbatch = null;
+
+ /**
+ * Handles a raw key event, potentially appending typed text to the
+ * current batch, and calling onbatch with the current batch, if the
+ * callback is set and a new batch is about to be started.
+ *
+ * @param {!string[]} args
+ * The arguments of the key event.
+ */
+ this.handleKeyEvent = function handleKeyEvent(args) {
+
+ // The X11 keysym
+ var keysym = parseInt(args[0]);
+
+ // Either 1 or 0 for pressed or released, respectively
+ var pressed = parseInt(args[1]);
+
+ // The timestamp when this key event occured
+ var timestamp = parseInt(args[2]);
+
+ // If no current batch exists, start a new one now
+ if (!currentBatch)
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ // Only switch to a new batch of text if sufficient time has passed
+ // since the last key event
+ var newBatch = (batchSeperation >= 0
+ && (timestamp - lastKeyEvent) >= batchSeperation);
+ lastKeyEvent = timestamp;
+
+ if (newBatch) {
+
+ // Call the handler with the current batch of text and the timestamp
+ // at which the current batch started
+ if (currentBatch.events.length && interpreter.onbatch)
+ interpreter.onbatch(currentBatch);
+
+ // Move on to the next batch of text
+ currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
+
+ }
+
+ var keyDefinition = getKeyDefinitionByKeysym(keysym);
+
+ // Mark down whether the key was pressed or released
+ if (keyDefinition.modifier) {
+ if (pressed)
+ pressedKeys[keysym] = keyDefinition;
+ else
+ delete pressedKeys[keysym];
+ }
+
+ // Append to the current typed value when a printable
+ // (non-modifier) key is pressed
+ else if (pressed) {
+
+ var relativeTimestap = timestamp - startTimestamp;
+
+ if (isShortcut()) {
+
+ var shortcutText = '<';
+
+ var firstKey = true;
+
+ // Compose entry by inspecting the state of each tracked key.
+ // At least one key must be pressed when in a shortcut.
+ for (var keysym in pressedKeys) {
+
+ var pressedKeyDefinition = pressedKeys[keysym];
+
+ // Print name of key
+ if (firstKey) {
+ shortcutText += pressedKeyDefinition.name;
+ firstKey = false;
+ }
+
+ else
+ shortcutText += ('+' + pressedKeyDefinition.name);
+
+ }
+
+ // Finally, append the printable key to close the shortcut
+ shortcutText += ('+' + keyDefinition.name + '>')
+
+ // Add the shortcut to the current batch
+ currentBatch.simpleValue += shortcutText;
+ currentBatch.events.push(new Guacamole.KeyEventInterpreter.KeyEvent(
+ shortcutText, false, relativeTimestap));
+
+ }
+
+ // Print the key itself
+ else {
+
+ var keyText;
+ var typed;
+
+ // Print the value if explicitly defined
+ if (keyDefinition.value != null) {
+
+ keyText = keyDefinition.value;
+ typed = true;
+
+ }
+
+ // Otherwise print the name
+ else {
+
+ keyText = ('<' + keyDefinition.name + '>');
+
+ // While this is a representation for a single character,
+ // the key text is the name of the key, not the actual
+ // character itself
+ typed = false;
+
+ }
+
+ // Add the key to the current batch
+ currentBatch.simpleValue += keyText;
+ currentBatch.events.push(new Guacamole.KeyEventInterpreter.KeyEvent(
+ keyText, typed, relativeTimestap));
+
+ }
+
+ }
+
+ }
+
+ /**
+ * Return the current batch of typed text. Note that the batch may be
+ * incomplete, as more key events might be processed before the next
+ * batch starts.
+ *
+ * @returns {Guacamole.KeyEventInterpreter.KeyEventBatch}
+ * The current batch of text.
+ */
+ this.getCurrentBatch = function getCurrentBatch() {
+ return currentBatch;
+ }
+}
Review Comment:
Missing semicolon.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] mike-jumper commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "mike-jumper (via GitHub)" <gi...@apache.org>.
mike-jumper commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1240874282
##########
guacamole/src/main/frontend/src/app/player/directives/textView.js:
##########
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * NOTE: This session recording player implementation is based on the Session
+ * Recording Player for Glyptodon Enterprise which is available at
+ * https://github.com/glyptodon/glyptodon-enterprise-player under the
+ * following license:
+ *
+ * Copyright (C) 2019 Glyptodon, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
Review Comment:
Does this file come from https://github.com/glyptodon/glyptodon-enterprise-player? The original player implementation did, but I don't recall this being part of that.
##########
guacamole-common-js/src/main/webapp/modules/KeyEventBatcher.js:
##########
@@ -0,0 +1,481 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ */
+Guacamole.KeyEventBatcher = function KeyEventBatcher(batchSeperation) {
Review Comment:
I'd recommend `KeyEventInterpreter` over `KeyEventBatcher`.
##########
guacamole-common-js/src/main/webapp/modules/KeyEventBatcher.js:
##########
@@ -0,0 +1,481 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ */
+Guacamole.KeyEventBatcher = function KeyEventBatcher(batchSeperation) {
+
+ /**
+ * Reference to this Guacamole.KeyEventBatcher.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var batcher = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
Review Comment:
This should also be annotated `@private`.
##########
doc/licenses/fuzzysort-2.0.4/README:
##########
@@ -0,0 +1,8 @@
+fuzzysort (https://github.com/farzher/fuzzysort/tree/master)
+---------------------------------------------
+
+ Version: 2.0.4
+ From: 'farzher' (https://github.com/farzher)
Review Comment:
This should be:
```
From: 'Stephen Kamenar' (https://github.com/farzher)
```
##########
guacamole-common-js/src/main/webapp/modules/SessionRecording.js:
##########
@@ -872,6 +911,19 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
*/
this.onpause = null;
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available.
+ *
+ * @event
+ * @param {!String} text
+ * The typed text associated with the batch of text.
+ *
+ * @param {!number} timestamp
+ * The relative timestamp associated with the batch of text.
+ */
+ this.onText = null;
Review Comment:
In keeping with the style of other events, this should be `ontext`.
##########
guacamole/src/main/frontend/src/app/player/directives/player.js:
##########
@@ -161,56 +182,36 @@ angular.module('player').directive('guacPlayer', ['$injector', function guacPlay
var resumeAfterSeekRequest = false;
/**
- * Formats the given number as a decimal string, adding leading zeroes
- * such that the string contains at least two digits. The given number
- * MUST NOT be negative.
- *
- * @param {!number} value
- * The number to format.
+ * Return true if any batches of key event logs are available for this
+ * recording, or false otherwise.
*
- * @returns {!string}
- * The decimal string representation of the given value, padded
- * with leading zeroes up to a minimum length of two digits.
+ * @return
+ * True if any batches of key event logs are avaiable for this
+ * recording, or false otherwise.
+ */
+ $scope.hasTextBatches = function hasTextBatches () {
+ return $scope.textBatches.length >= 0;
+ };
+
+ /**
+ * Toggle the visibility of the text key log viewer.
*/
- const zeroPad = function zeroPad(value) {
- return value > 9 ? value : '0' + value;
+ $scope.toggleKeyLogView = function toggleKeyLogView() {
+ $scope.showKeyLog = !$scope.showKeyLog;
};
/**
- * Formats the given quantity of milliseconds as days, hours, minutes,
- * and whole seconds, separated by colons (DD:HH:MM:SS). Hours are
- * included only if the quantity is at least one hour, and days are
- * included only if the quantity is at least one day. All included
- * groups are zero-padded to two digits with the exception of the
- * left-most group.
+ * Convert the given quantity of milliseconds into a human readable
+ * format, deferring to the time service for the implementation.
*
* @param {!number} value
* The time to format, in milliseconds.
*
* @returns {!string}
- * The given quantity of milliseconds formatted as "DD:HH:MM:SS".
+ * A human readable string representing the provided number of
+ * milliseconds.
*/
Review Comment:
Can just annotate:
```js
/**
* @borrows playerTimeService.formatTime
*/
```
rather than copy the docs verbatim.
##########
guacamole-common-js/src/main/webapp/modules/SessionRecording.js:
##########
@@ -872,6 +911,19 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
*/
this.onpause = null;
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available.
+ *
+ * @event
+ * @param {!String} text
+ * The typed text associated with the batch of text.
+ *
+ * @param {!number} timestamp
+ * The relative timestamp associated with the batch of text.
Review Comment:
Could this event should be richer? There definitely needs to be the rough text equivalent available for easy consumption, so this much is pretty good, but:
* This doesn't appear to provide a way for consuming code to distinguish between non-printable keypresses (like "<Backspace">) and the text equivalent to how we represent them (typing "<", "B", "a", ... etc.), which would prevent implementations from styling such keys differently or taking reliable automated action based on the presumed text typed.
* Timing information for keypresses vs. frames is lost (ie: there's no way for consuming code to choose to represent the keys pressed/released one key at a time)
Perhaps there's a way we can represent that in an object received by this event?
##########
guacamole-common-js/src/main/webapp/modules/KeyEventBatcher.js:
##########
@@ -0,0 +1,481 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ */
+Guacamole.KeyEventBatcher = function KeyEventBatcher(batchSeperation) {
+
+ /**
+ * Reference to this Guacamole.KeyEventBatcher.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var batcher = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * A human-readable representation of all keys pressed since the last keyframe.
+ *
+ * @private
+ * @type {String}
+ */
+ var currentTypedValue = '';
+
+ /**
+ * The timestamp of the key event that started the most recent batch of
+ * text content. If 0, no key events have been processed yet.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastTextTimestamp = 0;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
Review Comment:
This should also be annotated `@private`.
##########
guacamole-common-js/src/main/webapp/modules/KeyEventBatcher.js:
##########
@@ -0,0 +1,481 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ */
+Guacamole.KeyEventBatcher = function KeyEventBatcher(batchSeperation) {
+
+ /**
+ * Reference to this Guacamole.KeyEventBatcher.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var batcher = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
Review Comment:
This should also be annotated `@private`.
##########
guacamole/src/main/frontend/src/app/player/directives/textView.js:
##########
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * NOTE: This session recording player implementation is based on the Session
+ * Recording Player for Glyptodon Enterprise which is available at
+ * https://github.com/glyptodon/glyptodon-enterprise-player under the
+ * following license:
+ *
+ * Copyright (C) 2019 Glyptodon, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+const fuzzysort = require('fuzzysort')
+
+/**
+ * Directive which plays back session recordings.
+ */
+angular.module('player').directive('guacPlayerTextView',
+ ['$injector', function guacPlayer($injector) {
+
+ // Required types
+ const TextBatch = $injector.get('TextBatch');
+
+ // Required services
+ const playerTimeService = $injector.get('playerTimeService');
+
+ const config = {
+ restrict : 'E',
+ templateUrl : 'app/player/templates/textView.html'
+ };
+
+ config.scope = {
+
+ /**
+ * All the batches of text extracted from this recording.
+ *
+ * @type {!TextBatch[]}
+ */
+ textBatches : '=',
+
+ /**
+ * A callback that accepts a timestamp, and seeks the recording to
+ * that provided timestamp.
+ *
+ * @type {!Function}
+ */
+ seek: '&',
+
+ /**
+ * The current position within the recording.
+ *
+ * @type {!Number}
+ */
+ currentPosition: '='
+
+ };
+
+ config.controller = ['$scope', '$element', '$injector',
+ function guacPlayerController($scope, $element) {
+
+ /**
+ * The phrase to search within the text batches in order to produce the
+ * filtered list for display.
+ *
+ * @type {String}
+ */
+ $scope.searchPhrase = '';
+
+ /**
+ * The text batches that match the current search phrase, or all
+ * batches if no search phrase is set.
+ *
+ * @type {!TextBatch[]}
+ */
+ $scope.filteredBatches = $scope.textBatches;
+
+ /**
+ * Whether or not the key log viewer should be full-screen. False by
+ * default unless explicitly enabled by user interaction.
+ *
+ * @type {boolean}
+ */
+ $scope.fullscreenKeyLog = false;
+
+ /**
+ * Toggle whether the key log viewer should take up the whole screen.
+ */
+ $scope.toggleKeyLogFullscreen = function toggleKeyLogFullscreen() {
+ $element.toggleClass("fullscreen");
+ };
+
+ /**
+ * Filter the provided text batches using the provided search phrase to
+ * generate the list of filtered batches, or set to all provided
+ * batches if no search phrase is provided.
+ *
+ * @param {String} searchPhrase
+ * The phrase to search the text batches for. If no phrase is
+ * provided, the list of batches will not be filtered.
+ */
+ const applyFilter = searchPhrase => {
+
+ // If there's search phrase entered, search the text within the
+ // batches for it
+ if (searchPhrase)
+ $scope.filteredBatches = fuzzysort.go(
+ searchPhrase, $scope.textBatches, {key: 'text'})
+ .map(result => result.obj);
+
+ // Otherwise, do not filter the batches
+ else
+ $scope.filteredBatches = $scope.textBatches;
+
+ };
+
+ // Reapply the filter to the updated text batches
+ $scope.$watch('textBatches', applyFilter);
+
+ // Reapply the filter whenever the search phrase is updated
+ $scope.$watch('searchPhrase', applyFilter);
+
+ /**
+ * Convert the given quantity of milliseconds into a human readable
+ * format, deferring to the time service for the implementation.
+ *
+ * @param {!number} value
+ * The time to format, in milliseconds.
+ *
+ * @returns {!string}
+ * A human readable string representing the provided number of
+ * milliseconds.
+ */
Review Comment:
Same here - you can just annotate:
```js
/**
* @borrows playerTimeService.formatTime
*/
```
and avoid multiple copies of the same docs.
##########
guacamole-common-js/src/main/webapp/modules/KeyEventBatcher.js:
##########
@@ -0,0 +1,481 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ */
+Guacamole.KeyEventBatcher = function KeyEventBatcher(batchSeperation) {
+
+ /**
+ * Reference to this Guacamole.KeyEventBatcher.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var batcher = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
Review Comment:
This should also be annotated `@private`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] necouchman commented on pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "necouchman (via GitHub)" <gi...@apache.org>.
necouchman commented on PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#issuecomment-1608594232
> Currently, every single key event has its own DOM element, so you can watch the characters flipping from gray to black (which is pretty fun TBH).
Like a Mickey Mouse sing-a-long! :laughing:
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#issuecomment-1622612208
> If this is too performance-heavy, I could also somewhat streamline things by combining sequential similarly-styled key events into a single DOM element, though we'd lose granularity as far as which events occurred before or after the current recording playback position.
Well, I went ahead and implemented this in a seperate commit that we can always pull off if people don't like it. Let me know what you think.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1254794495
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,579 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onBatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onBatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns
+ * Return a KeyDefinition for the provided keysym, if it it's a valid
+ * UTF-8 keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ /* Determine size and initial byte mask */
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
+
+ // Create and return the definition
+ return new KeyDefinition({keysym: keysym.toString(), name: name, value: name, modifier: false});
+
+ }
+
+ /**
+ * Return a KeyDefinition corresponding to the provided keysym.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to return a KeyDefinition for.
+ *
+ * @returns
+ * A KeyDefinition corresponding to the provided keysym.
+ */
+ function getKeyDefinitionByKeysym(keysym) {
+
+ // If it's a known type, return the existing definition
+ if (keysym in KNOWN_KEYS)
+ return KNOWN_KEYS[keysym];
+
+ // Return a UTF-8 KeyDefinition, if valid
+ var definition = getUnicodeKeyDefinition(keysym);
+ if (definition != null)
+ return definition;
+
+ // If it's not UTF-8, return an unknown definition, with the name
+ // just set to the hex value of the keysym
+ return new KeyDefinition({
+ keysym: keysym,
+ name: '0x' + String(keysym.toString(16))
+ })
+
+ }
+
+ /**
+ * Fired whenever a new batch of typed text extracted from key events
+ * is available. A new batch will be provided every time a new key event
+ * is processed after more than batchSeperation milliseconds after the
+ * previous key event.
+ *
+ * @event
+ * @param {!Guacamole.KeyEventInterpreter.KeyEventBatch}
+ */
+ interpreter.onBatch = null;
+
+ /**
+ * Handles a raw key event, potentially appending typed text to the
+ * current batch, and calling onBatch with the current batch, if the
+ * callback is set and a new batch is about to be started.
+ *
+ * @param {!string[]} args
+ * The arguments of the key event.
+ */
+ interpreter.handleKeyEvent = function handleKeyEvent(args) {
Review Comment:
Uh, not sure, but it's easiest to just match convention and do `this.nameOfTheThing`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] jmuehlner commented on pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "jmuehlner (via GitHub)" <gi...@apache.org>.
jmuehlner commented on PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#issuecomment-1622633917
It's unclear why the build failure is happening only for my branch and not `master`, since I didn't modify any of the affected projects, but the problem seems to be that `guacamole-auth-ban` and `guacamole-auth-sso` both have the following in their `pom.xml`:
```
<parent>
<groupId>org.apache.guacamole</groupId>
<artifactId>extensions</artifactId>
<version>1.5.2</version>
<relativePath>../</relativePath>
</parent>
```
This is an issue because there's no `extensions` project at version 1.5.2 located at `../`. It's now 1.5.3 instead.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
[GitHub] [guacamole-client] mike-jumper commented on a diff in pull request #894: GUACAMOLE-1820: Create UI for viewing, searching, and navigating to key events in session recording player.
Posted by "mike-jumper (via GitHub)" <gi...@apache.org>.
mike-jumper commented on code in PR #894:
URL: https://github.com/apache/guacamole-client/pull/894#discussion_r1260017747
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,579 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onbatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
Review Comment:
`KEY_DEFINITION` is not a type. Should this be `KeyDefinition`?
##########
guacamole-common-js/src/main/webapp/modules/KeyEventInterpreter.js:
##########
@@ -0,0 +1,579 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var Guacamole = Guacamole || {};
+
+/**
+ * An object that will accept raw key events and produce human readable text
+ * batches, seperated by at least `batchSeperation` milliseconds, which can be
+ * retrieved through the onbatch callback or by calling getCurrentBatch().
+ *
+ * NOTE: The event processing logic and output format is based on the `guaclog`
+ * tool, with the addition of batching support.
+ *
+ * @constructor
+ *
+ * @param {number} [batchSeperation=5000]
+ * The minimum number of milliseconds that must elapse between subsequent
+ * batches of key-event-generated text. If 0 or negative, no splitting will
+ * occur, resulting in a single batch for all provided key events.
+ *
+ * @param {number} [startTimestamp=0]
+ * The starting timestamp for the recording being intepreted. If provided,
+ * the timestamp of each intepreted event will be relative to this timestamp.
+ * If not provided, the raw recording timestamp will be used.
+ */
+Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
+
+ /**
+ * Reference to this Guacamole.KeyEventInterpreter.
+ *
+ * @private
+ * @type {!Guacamole.SessionRecording}
+ */
+ var interpreter = this;
+
+ // Default to 5 seconds if the batch seperation was not provided
+ if (batchSeperation === undefined || batchSeperation === null)
+ batchSeperation = 5000;
+
+ // Default to 0 seconds to keep the raw timestamps
+ if (startTimestamp === undefined || startTimestamp === null)
+ startTimestamp = 0;
+
+ /**
+ * A definition for a known key.
+ *
+ * @constructor
+ * @private
+ * @param {KEY_DEFINITION|object} [template={}]
+ * The object whose properties should be copied within the new
+ * KEY_DEFINITION.
+ */
+ var KeyDefinition = function KeyDefinition(template) {
+
+ /**
+ * The X11 keysym of the key.
+ * @type {!number}
+ */
+ this.keysym = parseInt(template.keysym);
+
+ /**
+ * A human-readable name for the key.
+ * @type {!String}
+ */
+ this.name = template.name;
+
+ /**
+ * The value which would be typed in a typical text editor, if any. If the
+ * key is not associated with any typable value, or if the typable value is
+ * not generally useful in an auditing context, this will be undefined.
+ * @type {String}
+ */
+ this.value = template.value;
+
+ /**
+ * Whether this key is a modifier which may affect the interpretation of
+ * other keys, and thus should be tracked as it is held down.
+ * @type {!boolean}
+ * @default false
+ */
+ this.modifier = template.modifier || false;
+
+ };
+
+ /**
+ * A precursor array to the KNOWN_KEYS map. The objects contained within
+ * will be constructed into full KeyDefinition objects.
+ *
+ * @constant
+ * @private
+ * @type {Object[]}
+ */
+ var _KNOWN_KEYS = [
+ {keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
+ {keysym: 0xFF08, name: 'Backspace' },
+ {keysym: 0xFF09, name: 'Tab' },
+ {keysym: 0xFF0B, name: 'Clear' },
+ {keysym: 0xFF0D, name: 'Return', value: "\n" },
+ {keysym: 0xFF13, name: 'Pause' },
+ {keysym: 0xFF14, name: 'Scroll' },
+ {keysym: 0xFF15, name: 'SysReq' },
+ {keysym: 0xFF1B, name: 'Escape' },
+ {keysym: 0xFF50, name: 'Home' },
+ {keysym: 0xFF51, name: 'Left' },
+ {keysym: 0xFF52, name: 'Up' },
+ {keysym: 0xFF53, name: 'Right' },
+ {keysym: 0xFF54, name: 'Down' },
+ {keysym: 0xFF55, name: 'Page Up' },
+ {keysym: 0xFF56, name: 'Page Down' },
+ {keysym: 0xFF57, name: 'End' },
+ {keysym: 0xFF63, name: 'Insert' },
+ {keysym: 0xFF65, name: 'Undo' },
+ {keysym: 0xFF6A, name: 'Help' },
+ {keysym: 0xFF7F, name: 'Num' },
+ {keysym: 0xFF80, name: 'Space', value: " " },
+ {keysym: 0xFF8D, name: 'Enter', value: "\n" },
+ {keysym: 0xFF95, name: 'Home' },
+ {keysym: 0xFF96, name: 'Left' },
+ {keysym: 0xFF97, name: 'Up' },
+ {keysym: 0xFF98, name: 'Right' },
+ {keysym: 0xFF99, name: 'Down' },
+ {keysym: 0xFF9A, name: 'Page Up' },
+ {keysym: 0xFF9B, name: 'Page Down' },
+ {keysym: 0xFF9C, name: 'End' },
+ {keysym: 0xFF9E, name: 'Insert' },
+ {keysym: 0xFFAA, name: '*', value: "*" },
+ {keysym: 0xFFAB, name: '+', value: "+" },
+ {keysym: 0xFFAD, name: '-', value: "-" },
+ {keysym: 0xFFAE, name: '.', value: "." },
+ {keysym: 0xFFAF, name: '/', value: "/" },
+ {keysym: 0xFFB0, name: '0', value: "0" },
+ {keysym: 0xFFB1, name: '1', value: "1" },
+ {keysym: 0xFFB2, name: '2', value: "2" },
+ {keysym: 0xFFB3, name: '3', value: "3" },
+ {keysym: 0xFFB4, name: '4', value: "4" },
+ {keysym: 0xFFB5, name: '5', value: "5" },
+ {keysym: 0xFFB6, name: '6', value: "6" },
+ {keysym: 0xFFB7, name: '7', value: "7" },
+ {keysym: 0xFFB8, name: '8', value: "8" },
+ {keysym: 0xFFB9, name: '9', value: "9" },
+ {keysym: 0xFFBE, name: 'F1' },
+ {keysym: 0xFFBF, name: 'F2' },
+ {keysym: 0xFFC0, name: 'F3' },
+ {keysym: 0xFFC1, name: 'F4' },
+ {keysym: 0xFFC2, name: 'F5' },
+ {keysym: 0xFFC3, name: 'F6' },
+ {keysym: 0xFFC4, name: 'F7' },
+ {keysym: 0xFFC5, name: 'F8' },
+ {keysym: 0xFFC6, name: 'F9' },
+ {keysym: 0xFFC7, name: 'F10' },
+ {keysym: 0xFFC8, name: 'F11' },
+ {keysym: 0xFFC9, name: 'F12' },
+ {keysym: 0xFFCA, name: 'F13' },
+ {keysym: 0xFFCB, name: 'F14' },
+ {keysym: 0xFFCC, name: 'F15' },
+ {keysym: 0xFFCD, name: 'F16' },
+ {keysym: 0xFFCE, name: 'F17' },
+ {keysym: 0xFFCF, name: 'F18' },
+ {keysym: 0xFFD0, name: 'F19' },
+ {keysym: 0xFFD1, name: 'F20' },
+ {keysym: 0xFFD2, name: 'F21' },
+ {keysym: 0xFFD3, name: 'F22' },
+ {keysym: 0xFFD4, name: 'F23' },
+ {keysym: 0xFFD5, name: 'F24' },
+ {keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
+ {keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
+ {keysym: 0xFFE5, name: 'Caps' },
+ {keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
+ {keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
+ {keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
+ {keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
+ {keysym: 0xFFFF, name: 'Delete' }
+ ];
+
+ /**
+ * All known keys, as a map of X11 keysym to KeyDefinition.
+ *
+ * @constant
+ * @private
+ * @type {Object.<String, KeyDefinition>}
+ */
+ var KNOWN_KEYS = {};
+ _KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
+
+ // Construct a map of keysym to KeyDefinition object
+ KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
+
+ });
+
+ /**
+ * A map of X11 keysyms to a KeyDefinition object, if the corresponding
+ * key is currently pressed. If a keysym has no entry in this map at all,
+ * it means that the key is not being pressed. Note that not all keysyms
+ * are necessarily tracked within this map - only those that are explicitly
+ * tracked.
+ *
+ * @private
+ * @type {Object.<String,KeyDefinition> }
+ */
+ var pressedKeys = {};
+
+ /**
+ * The current key event batch, containing a representation of all key
+ * events processed since the end of the last batch passed to onbatch.
+ * Null if no key events have been processed yet.
+ *
+ * @private
+ * @type {!KeyEventBatch}
+ */
+ var currentBatch = null;
+
+ /**
+ * The timestamp of the most recent key event processed.
+ *
+ * @private
+ * @type {Number}
+ */
+ var lastKeyEvent = 0;
+
+ /**
+ * Returns true if the currently-pressed keys are part of a shortcut, or
+ * false otherwise.
+ *
+ * @private
+ * @returns {!boolean}
+ * True if the currently-pressed keys are part of a shortcut, or false
+ * otherwise.
+ */
+ function isShortcut() {
+
+ // If one of the currently-pressed keys is non-printable, a shortcut
+ // is being typed
+ for (var keysym in pressedKeys) {
+ if (pressedKeys[keysym].value === null)
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the provided keysym corresponds to a valid UTF-8 character, return
+ * a KeyDefinition for that keysym. Otherwise, return null.
+ *
+ * @private
+ * @param {Number} keysym
+ * The keysym to produce a UTF-8 KeyDefinition for, if valid.
+ *
+ * @returns {KeyDefinition}
+ * A KeyDefinition for the provided keysym, if it's a valid UTF-8
+ * keysym, or null otherwise.
+ */
+ function getUnicodeKeyDefinition(keysym) {
+
+ // Translate only if keysym maps to Unicode
+ if (keysym < 0x00 || (keysym > 0xFF && (keysym | 0xFFFF) != 0x0100FFFF))
+ return null;
+
+ var codepoint = keysym & 0xFFFF;
+ var mask;
+ var bytes;
+
+ // Determine size and initial byte mask
+ if (codepoint <= 0x007F) {
+ mask = 0x00;
+ bytes = 1;
+ }
+ else if (codepoint <= 0x7FF) {
+ mask = 0xC0;
+ bytes = 2;
+ }
+ else {
+ mask = 0xE0;
+ bytes = 3;
+ }
+
+ var byteArray = new ArrayBuffer(bytes);
+ var byteView = new Int8Array(byteArray);
+
+ // Add trailing bytes, if any
+ for (var i = 1; i < bytes; i++) {
+ byteView[bytes - i] = 0x80 | (codepoint & 0x3F);
+ codepoint >>= 6;
+ }
+
+ // Set initial byte
+ byteView[0] = mask | codepoint;
+
+ // Convert to UTF8 string
+ var name = new TextDecoder("utf-8").decode(byteArray);
Review Comment:
Can you clarify why this is necessary? I read:
1. X11 keysym is converted to a Unicode codepoint.
2. Codepoint is manually encoded as UTF-8 bytes.
3. UTF-8 bytes are decoded to text.
Seems like a verrrry roundabout way to do:
```js
var name = String.fromCodePoint(codepoint);
```
##########
guacamole/src/main/frontend/src/app/player/directives/player.js:
##########
@@ -342,6 +357,14 @@ angular.module('player').directive('guacPlayer', ['$injector', function guacPlay
$scope.$evalAsync();
};
+ // Append any extracted batches of typed text
+ $scope.recording.ontext = function appendTextBatch(batch) {
+
+ // Convert to the display-optimized TextBatch type
+ $scope.textBatches.push(new TextBatch(batch));
+
+ }
Review Comment:
Missing semicolon.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: dev-unsubscribe@guacamole.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org