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: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to