mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-1820: Merge inclusion of "Enter" / newlines within key event viewer.
This commit is contained in:
@@ -20,84 +20,22 @@
|
|||||||
var Guacamole = Guacamole || {};
|
var Guacamole = Guacamole || {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object that will accept raw key events and produce human readable text
|
* An object that will accept raw key events and produce a chronologically
|
||||||
* batches, seperated by at least `batchSeperation` milliseconds, which can be
|
* ordered array of key event objects. These events can be obtained by
|
||||||
* retrieved through the onbatch callback or by calling getCurrentBatch().
|
* calling getEvents().
|
||||||
*
|
|
||||||
* NOTE: The event processing logic and output format is based on the `guaclog`
|
|
||||||
* tool, with the addition of batching support.
|
|
||||||
*
|
*
|
||||||
* @constructor
|
* @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]
|
* @param {number} [startTimestamp=0]
|
||||||
* The starting timestamp for the recording being intepreted. If provided,
|
* The starting timestamp for the recording being intepreted. If provided,
|
||||||
* the timestamp of each intepreted event will be relative to this timestamp.
|
* the timestamp of each intepreted event will be relative to this timestamp.
|
||||||
* If not provided, the raw recording timestamp will be used.
|
* If not provided, the raw recording timestamp will be used.
|
||||||
*/
|
*/
|
||||||
Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, startTimestamp) {
|
Guacamole.KeyEventInterpreter = function KeyEventInterpreter(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
|
// Default to 0 seconds to keep the raw timestamps
|
||||||
if (startTimestamp === undefined || startTimestamp === null)
|
if (startTimestamp === undefined || startTimestamp === null)
|
||||||
startTimestamp = 0;
|
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
|
* A precursor array to the KNOWN_KEYS map. The objects contained within
|
||||||
* will be constructed into full KeyDefinition objects.
|
* will be constructed into full KeyDefinition objects.
|
||||||
@@ -107,7 +45,7 @@ Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, st
|
|||||||
* @type {Object[]}
|
* @type {Object[]}
|
||||||
*/
|
*/
|
||||||
var _KNOWN_KEYS = [
|
var _KNOWN_KEYS = [
|
||||||
{keysym: 0xFE03, name: 'AltGr', value: "", modifier: true },
|
{keysym: 0xFE03, name: 'AltGr' },
|
||||||
{keysym: 0xFF08, name: 'Backspace' },
|
{keysym: 0xFF08, name: 'Backspace' },
|
||||||
{keysym: 0xFF09, name: 'Tab' },
|
{keysym: 0xFF09, name: 'Tab' },
|
||||||
{keysym: 0xFF0B, name: 'Clear' },
|
{keysym: 0xFF0B, name: 'Clear' },
|
||||||
@@ -178,19 +116,19 @@ Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, st
|
|||||||
{keysym: 0xFFD3, name: 'F22' },
|
{keysym: 0xFFD3, name: 'F22' },
|
||||||
{keysym: 0xFFD4, name: 'F23' },
|
{keysym: 0xFFD4, name: 'F23' },
|
||||||
{keysym: 0xFFD5, name: 'F24' },
|
{keysym: 0xFFD5, name: 'F24' },
|
||||||
{keysym: 0xFFE1, name: 'Shift', value: "", modifier: true },
|
{keysym: 0xFFE1, name: 'Shift' },
|
||||||
{keysym: 0xFFE2, name: 'Shift', value: "", modifier: true },
|
{keysym: 0xFFE2, name: 'Shift' },
|
||||||
{keysym: 0xFFE3, name: 'Ctrl', value: null, modifier: true },
|
{keysym: 0xFFE3, name: 'Ctrl' },
|
||||||
{keysym: 0xFFE4, name: 'Ctrl', value: null, modifier: true },
|
{keysym: 0xFFE4, name: 'Ctrl' },
|
||||||
{keysym: 0xFFE5, name: 'Caps' },
|
{keysym: 0xFFE5, name: 'Caps' },
|
||||||
{keysym: 0xFFE7, name: 'Meta', value: null, modifier: true },
|
{keysym: 0xFFE7, name: 'Meta' },
|
||||||
{keysym: 0xFFE8, name: 'Meta', value: null, modifier: true },
|
{keysym: 0xFFE8, name: 'Meta' },
|
||||||
{keysym: 0xFFE9, name: 'Alt', value: null, modifier: true },
|
{keysym: 0xFFE9, name: 'Alt' },
|
||||||
{keysym: 0xFFEA, name: 'Alt', value: null, modifier: true },
|
{keysym: 0xFFEA, name: 'Alt' },
|
||||||
{keysym: 0xFFEB, name: 'Super', value: null, modifier: true },
|
{keysym: 0xFFEB, name: 'Super' },
|
||||||
{keysym: 0xFFEC, name: 'Super', value: null, modifier: true },
|
{keysym: 0xFFEC, name: 'Super' },
|
||||||
{keysym: 0xFFED, name: 'Hyper', value: null, modifier: true },
|
{keysym: 0xFFED, name: 'Hyper' },
|
||||||
{keysym: 0xFFEE, name: 'Hyper', value: null, modifier: true },
|
{keysym: 0xFFEE, name: 'Hyper' },
|
||||||
{keysym: 0xFFFF, name: 'Delete' }
|
{keysym: 0xFFFF, name: 'Delete' }
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -205,60 +143,18 @@ Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, st
|
|||||||
_KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
|
_KNOWN_KEYS.forEach(function createKeyDefinitionMap(keyDefinition) {
|
||||||
|
|
||||||
// Construct a map of keysym to KeyDefinition object
|
// Construct a map of keysym to KeyDefinition object
|
||||||
KNOWN_KEYS[keyDefinition.keysym] = new KeyDefinition(keyDefinition)
|
KNOWN_KEYS[keyDefinition.keysym] = (
|
||||||
|
new Guacamole.KeyEventInterpreter.KeyDefinition(keyDefinition));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of X11 keysyms to a KeyDefinition object, if the corresponding
|
* All key events parsed as of the most recent handleKeyEvent() invocation.
|
||||||
* 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
|
* @private
|
||||||
* @type {Object.<String,KeyDefinition> }
|
* @type {!Guacamole.KeyEventInterpreter.KeyEvent[]}
|
||||||
*/
|
*/
|
||||||
var pressedKeys = {};
|
var parsedEvents = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* If the provided keysym corresponds to a valid UTF-8 character, return
|
||||||
@@ -268,7 +164,7 @@ Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, st
|
|||||||
* @param {Number} keysym
|
* @param {Number} keysym
|
||||||
* The keysym to produce a UTF-8 KeyDefinition for, if valid.
|
* The keysym to produce a UTF-8 KeyDefinition for, if valid.
|
||||||
*
|
*
|
||||||
* @returns {KeyDefinition}
|
* @returns {Guacamole.KeyEventInterpreter.KeyDefinition}
|
||||||
* A KeyDefinition for the provided keysym, if it's a valid UTF-8
|
* A KeyDefinition for the provided keysym, if it's a valid UTF-8
|
||||||
* keysym, or null otherwise.
|
* keysym, or null otherwise.
|
||||||
*/
|
*/
|
||||||
@@ -283,7 +179,8 @@ Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, st
|
|||||||
var name = String.fromCharCode(codepoint);
|
var name = String.fromCharCode(codepoint);
|
||||||
|
|
||||||
// Create and return the definition
|
// Create and return the definition
|
||||||
return new KeyDefinition({keysym: keysym, name: name, value: name, modifier: false});
|
return new Guacamole.KeyEventInterpreter.KeyDefinition({
|
||||||
|
keysym: keysym, name: name, value: name});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +207,7 @@ Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, st
|
|||||||
|
|
||||||
// If it's not UTF-8, return an unknown definition, with the name
|
// If it's not UTF-8, return an unknown definition, with the name
|
||||||
// just set to the hex value of the keysym
|
// just set to the hex value of the keysym
|
||||||
return new KeyDefinition({
|
return new Guacamole.KeyEventInterpreter.KeyDefinition({
|
||||||
keysym: keysym,
|
keysym: keysym,
|
||||||
name: '0x' + String(keysym.toString(16))
|
name: '0x' + String(keysym.toString(16))
|
||||||
})
|
})
|
||||||
@@ -318,20 +215,8 @@ Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, st
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired whenever a new batch of typed text extracted from key events
|
* Handles a raw key event, appending a new key event object for every
|
||||||
* is available. A new batch will be provided every time a new key event
|
* handled raw 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
|
* @param {!string[]} args
|
||||||
* The arguments of the key event.
|
* The arguments of the key event.
|
||||||
@@ -347,111 +232,18 @@ Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, st
|
|||||||
// The timestamp when this key event occured
|
// The timestamp when this key event occured
|
||||||
var timestamp = parseInt(args[2]);
|
var timestamp = parseInt(args[2]);
|
||||||
|
|
||||||
// If no current batch exists, start a new one now
|
// The timestamp relative to the provided initial timestamp
|
||||||
if (!currentBatch)
|
var relativeTimestap = timestamp - startTimestamp;
|
||||||
currentBatch = new Guacamole.KeyEventInterpreter.KeyEventBatch();
|
|
||||||
|
|
||||||
// Only switch to a new batch of text if sufficient time has passed
|
// Known information about the parsed key
|
||||||
// since the last key event
|
var definition = getKeyDefinitionByKeysym(keysym);
|
||||||
var newBatch = (batchSeperation >= 0
|
|
||||||
&& (timestamp - lastKeyEvent) >= batchSeperation);
|
|
||||||
lastKeyEvent = timestamp;
|
|
||||||
|
|
||||||
if (newBatch) {
|
// Push the latest parsed event into the list
|
||||||
|
parsedEvents.push(new Guacamole.KeyEventInterpreter.KeyEvent({
|
||||||
// Call the handler with the current batch of text and the timestamp
|
definition: definition,
|
||||||
// at which the current batch started
|
pressed: pressed,
|
||||||
if (currentBatch.events.length && interpreter.onbatch)
|
timestamp: relativeTimestap
|
||||||
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));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -460,12 +252,47 @@ Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, st
|
|||||||
* incomplete, as more key events might be processed before the next
|
* incomplete, as more key events might be processed before the next
|
||||||
* batch starts.
|
* batch starts.
|
||||||
*
|
*
|
||||||
* @returns {Guacamole.KeyEventInterpreter.KeyEventBatch}
|
* @returns {Guacamole.KeyEventInterpreter.KeyEvent[]}
|
||||||
* The current batch of text.
|
* The current batch of text.
|
||||||
*/
|
*/
|
||||||
this.getCurrentBatch = function getCurrentBatch() {
|
this.getEvents = function getEvents() {
|
||||||
return currentBatch;
|
return parsedEvents;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A definition for a known key.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {Guacamole.KeyEventInterpreter.KeyDefinition|object} [template={}]
|
||||||
|
* The object whose properties should be copied within the new
|
||||||
|
* KeyDefinition.
|
||||||
|
*/
|
||||||
|
Guacamole.KeyEventInterpreter.KeyDefinition = function KeyDefinition(template) {
|
||||||
|
|
||||||
|
// Use empty object by default
|
||||||
|
template = 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 typeable value, this will be undefined.
|
||||||
|
* @type {String}
|
||||||
|
*/
|
||||||
|
this.value = template.value;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -474,77 +301,35 @@ Guacamole.KeyEventInterpreter = function KeyEventInterpreter(batchSeperation, st
|
|||||||
* and the timestamp when the event occured.
|
* and the timestamp when the event occured.
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {!String} text
|
* @param {Guacamole.KeyEventInterpreter.KeyEvent|object} [template={}]
|
||||||
* A human-readable representation of the event.
|
* The object whose properties should be copied within the new
|
||||||
*
|
* KeyEvent.
|
||||||
* @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) {
|
Guacamole.KeyEventInterpreter.KeyEvent = function KeyEvent(template) {
|
||||||
|
|
||||||
|
// Use empty object by default
|
||||||
|
template = template || {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A human-readable representation of the event. If a printable character
|
* The key definition for the pressed key.
|
||||||
* was directly typed, this will just be that character. Otherwise it will
|
|
||||||
* be a string describing the event.
|
|
||||||
*
|
*
|
||||||
* @type {!String}
|
* @type {!Guacamole.KeyEventInterpreter.KeyDefinition}
|
||||||
*/
|
*/
|
||||||
this.text = text;
|
this.definition = template.definition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if this text of this event is exactly a typed character, or false
|
* True if the key was pressed to create this event, or false if it was
|
||||||
* otherwise.
|
* released.
|
||||||
*
|
*
|
||||||
* @type {!boolean}
|
* @type {!boolean}
|
||||||
*/
|
*/
|
||||||
this.typed = typed;
|
this.pressed = !!template.pressed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The timestamp from the recording when this event occured. If a
|
* The timestamp from the recording when this event occured.
|
||||||
* `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}
|
* @type {!Number}
|
||||||
*/
|
*/
|
||||||
this.timestamp = timestamp;
|
this.timestamp = template.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 || '';
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@@ -394,17 +394,7 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
|
|||||||
* the recording.
|
* the recording.
|
||||||
*/
|
*/
|
||||||
function initializeKeyInterpreter(startTimestamp) {
|
function initializeKeyInterpreter(startTimestamp) {
|
||||||
|
keyEventInterpreter = new Guacamole.KeyEventInterpreter(startTimestamp);
|
||||||
keyEventInterpreter = new Guacamole.KeyEventInterpreter(null, startTimestamp);
|
|
||||||
|
|
||||||
// Pass through any received batches to the recording ontext handler
|
|
||||||
keyEventInterpreter.onbatch = function onbatch(batch) {
|
|
||||||
|
|
||||||
// Pass the batch through if a handler is set
|
|
||||||
if (recording.ontext)
|
|
||||||
recording.ontext(batch);
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -527,11 +517,10 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
|
|||||||
instructionBuffer = '';
|
instructionBuffer = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's any typed text that's yet to be sent to the ontext
|
// Now that the recording is fully processed, and all key events
|
||||||
// handler, send it now
|
// have been extracted, call the onkeyevents handler if defined
|
||||||
var batch = keyEventInterpreter.getCurrentBatch();
|
if (recording.onkeyevents)
|
||||||
if (batch && recording.ontext)
|
recording.onkeyevents(keyEventInterpreter.getEvents());
|
||||||
recording.ontext(batch);
|
|
||||||
|
|
||||||
// Consider recording loaded if tunnel has closed without errors
|
// Consider recording loaded if tunnel has closed without errors
|
||||||
if (!errorEncountered)
|
if (!errorEncountered)
|
||||||
@@ -919,14 +908,15 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval)
|
|||||||
this.onpause = null;
|
this.onpause = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired whenever a new batch of typed text extracted from key events
|
* Fired with all extracted key events when the recording is fully
|
||||||
* is available.
|
* processed. The callback will be invoked with an empty list
|
||||||
|
* if no key events were extracted.
|
||||||
*
|
*
|
||||||
* @event
|
* @event
|
||||||
* @param {!Guacamole.KeyEventInterpreter.KeyEventBatch} batch
|
* @param {!Guacamole.KeyEventInterpreter.KeyEvent[]} batch
|
||||||
* The batch of extracted text.
|
* The extracted key events.
|
||||||
*/
|
*/
|
||||||
this.ontext = null;
|
this.onkeyevents = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fired whenever the playback position within the recording changes.
|
* Fired whenever the playback position within the recording changes.
|
||||||
|
@@ -78,11 +78,9 @@
|
|||||||
angular.module('player').directive('guacPlayer', ['$injector', function guacPlayer($injector) {
|
angular.module('player').directive('guacPlayer', ['$injector', function guacPlayer($injector) {
|
||||||
|
|
||||||
// Required services
|
// Required services
|
||||||
|
const keyEventDisplayService = $injector.get('keyEventDisplayService');
|
||||||
const playerTimeService = $injector.get('playerTimeService');
|
const playerTimeService = $injector.get('playerTimeService');
|
||||||
|
|
||||||
// Required types
|
|
||||||
const TextBatch = $injector.get('TextBatch');
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
restrict : 'E',
|
restrict : 'E',
|
||||||
templateUrl : 'app/player/templates/player.html'
|
templateUrl : 'app/player/templates/player.html'
|
||||||
@@ -151,7 +149,7 @@ angular.module('player').directive('guacPlayer', ['$injector', function guacPlay
|
|||||||
/**
|
/**
|
||||||
* Any batches of text typed during the recording.
|
* Any batches of text typed during the recording.
|
||||||
*
|
*
|
||||||
* @type {TextBatch[]}
|
* @type {keyEventDisplayService.TextBatch[]}
|
||||||
*/
|
*/
|
||||||
$scope.textBatches = [];
|
$scope.textBatches = [];
|
||||||
|
|
||||||
@@ -357,11 +355,12 @@ angular.module('player').directive('guacPlayer', ['$injector', function guacPlay
|
|||||||
$scope.$evalAsync();
|
$scope.$evalAsync();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Append any extracted batches of typed text
|
// Extract key events from the recording
|
||||||
$scope.recording.ontext = function appendTextBatch(batch) {
|
$scope.recording.onkeyevents = function keyEventsReceived(events) {
|
||||||
|
|
||||||
// Convert to the display-optimized TextBatch type
|
// Convert to a display-optimized format
|
||||||
$scope.textBatches.push(new TextBatch(batch));
|
$scope.textBatches = (
|
||||||
|
keyEventDisplayService.parseEvents(events));
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -38,7 +38,7 @@ angular.module('player').directive('guacPlayerTextView',
|
|||||||
/**
|
/**
|
||||||
* All the batches of text extracted from this recording.
|
* All the batches of text extracted from this recording.
|
||||||
*
|
*
|
||||||
* @type {!TextBatch[]}
|
* @type {!keyEventDisplayService.TextBatch[]}
|
||||||
*/
|
*/
|
||||||
textBatches : '=',
|
textBatches : '=',
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ angular.module('player').directive('guacPlayerTextView',
|
|||||||
* The text batches that match the current search phrase, or all
|
* The text batches that match the current search phrase, or all
|
||||||
* batches if no search phrase is set.
|
* batches if no search phrase is set.
|
||||||
*
|
*
|
||||||
* @type {!TextBatch[]}
|
* @type {!keyEventDisplayService.TextBatch[]}
|
||||||
*/
|
*/
|
||||||
$scope.filteredBatches = $scope.textBatches;
|
$scope.filteredBatches = $scope.textBatches;
|
||||||
|
|
||||||
@@ -117,8 +117,8 @@ angular.module('player').directive('guacPlayerTextView',
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reapply the filter to the updated text batches
|
// Reapply the current filter to the updated text batches
|
||||||
$scope.$watch('textBatches', applyFilter);
|
$scope.$watch('textBatches', () => applyFilter($scope.searchPhrase));
|
||||||
|
|
||||||
// Reapply the filter whenever the search phrase is updated
|
// Reapply the filter whenever the search phrase is updated
|
||||||
$scope.$watch('searchPhrase', applyFilter);
|
$scope.$watch('searchPhrase', applyFilter);
|
||||||
|
@@ -0,0 +1,371 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global _ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service for translating parsed key events in the format produced by
|
||||||
|
* KeyEventInterpreter into display-optimized text batches.
|
||||||
|
*/
|
||||||
|
angular.module('player').factory('keyEventDisplayService',
|
||||||
|
['$injector', function keyEventDisplayService($injector) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of all keysyms corresponding to modifier keys.
|
||||||
|
* @type{Object.<Number, Boolean>}
|
||||||
|
*/
|
||||||
|
const MODIFIER_KEYS = {
|
||||||
|
0xFE03: true, // AltGr
|
||||||
|
0xFFE1: true, // Left Shift
|
||||||
|
0xFFE2: true, // Right Shift
|
||||||
|
0xFFE3: true, // Left Control
|
||||||
|
0xFFE4: true, // Right Control,
|
||||||
|
0xFFE7: true, // Left Meta
|
||||||
|
0xFFE8: true, // Right Meta
|
||||||
|
0xFFE9: true, // Left Alt
|
||||||
|
0xFFEA: true, // Right Alt
|
||||||
|
0xFFEB: true, // Left Super
|
||||||
|
0xFFEC: true, // Right Super
|
||||||
|
0xFFED: true, // Left Hyper
|
||||||
|
0xFFEE: true // Right Super
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of all keysyms for which the name should be printed alongside the
|
||||||
|
* value of the key itself.
|
||||||
|
* @type{Object.<Number, Boolean>}
|
||||||
|
*/
|
||||||
|
const PRINT_NAME_TOO_KEYS = {
|
||||||
|
0xFF09: true, // Tab
|
||||||
|
0xFF0D: true, // Return
|
||||||
|
0xFF8D: true, // Enter
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of all keysyms corresponding to keys commonly used in shortcuts.
|
||||||
|
* @type{Object.<Number, Boolean>}
|
||||||
|
*/
|
||||||
|
const SHORTCUT_KEYS = {
|
||||||
|
0xFFE3: true, // Left Control
|
||||||
|
0xFFE4: true, // Right Control,
|
||||||
|
0xFFE7: true, // Left Meta
|
||||||
|
0xFFE8: true, // Right Meta
|
||||||
|
0xFFE9: true, // Left Alt
|
||||||
|
0xFFEA: true, // Right Alt
|
||||||
|
0xFFEB: true, // Left Super
|
||||||
|
0xFFEC: true, // Right Super
|
||||||
|
0xFFED: true, // Left Hyper
|
||||||
|
0xFFEE: true // Right Super
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format and return a key name for display.
|
||||||
|
*
|
||||||
|
* @param {*} name
|
||||||
|
* The name of the key
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
* The formatted key name.
|
||||||
|
*/
|
||||||
|
const formatKeyName = name => ('<' + name + '>');
|
||||||
|
|
||||||
|
const service = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A batch of text associated with a recording. The batch consists of a
|
||||||
|
* string representation of the text that would be typed based on the key
|
||||||
|
* events in the recording, as well as a timestamp when the batch started.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {TextBatch|Object} [template={}]
|
||||||
|
* The object whose properties should be copied within the new TextBatch.
|
||||||
|
*/
|
||||||
|
service.TextBatch = function TextBatch(template) {
|
||||||
|
|
||||||
|
// Use empty object by default
|
||||||
|
template = template || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All key events for this batch, some of which may be conslidated,
|
||||||
|
* representing multiple raw events.
|
||||||
|
*
|
||||||
|
* @type {ConsolidatedKeyEvent[]}
|
||||||
|
*/
|
||||||
|
this.events = template.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 = template.simpleValue || '';
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A granular description of an extracted key event or sequence of events.
|
||||||
|
* It may contain multiple contiguous events of the same type, meaning that all
|
||||||
|
* event(s) that were combined into this event must have had the same `typed`
|
||||||
|
* field value. A single timestamp for the first combined event will be used
|
||||||
|
* for the whole batch if consolidated.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {ConsolidatedKeyEvent|Object} [template={}]
|
||||||
|
* The object whose properties should be copied within the new KeyEventBatch.
|
||||||
|
*/
|
||||||
|
service.ConsolidatedKeyEvent = function ConsolidatedKeyEvent(template) {
|
||||||
|
|
||||||
|
// Use empty object by default
|
||||||
|
template = template || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A human-readable representation of the event(s). If a series of printable
|
||||||
|
* characters was directly typed, this will just be those character(s).
|
||||||
|
* Otherwise it will be a string describing the event(s).
|
||||||
|
*
|
||||||
|
* @type {!String}
|
||||||
|
*/
|
||||||
|
this.text = template.text;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if this text of this event is exactly a typed character, or false
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* @type {!boolean}
|
||||||
|
*/
|
||||||
|
this.typed = template.typed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp from the recording when this event occured.
|
||||||
|
*
|
||||||
|
* @type {!Number}
|
||||||
|
*/
|
||||||
|
this.timestamp = template.timestamp;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts key events in the format produced by KeyEventInterpreter and returns
|
||||||
|
* human readable text batches, seperated by at least `batchSeperation` milliseconds
|
||||||
|
* if provided.
|
||||||
|
*
|
||||||
|
* NOTE: The event processing logic and output format is based on the `guaclog`
|
||||||
|
* tool, with the addition of batching support.
|
||||||
|
*
|
||||||
|
* @param {Guacamole.KeyEventInterpreter.KeyEvent[]} [rawEvents]
|
||||||
|
* The raw key events to prepare for display.
|
||||||
|
*
|
||||||
|
* @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 {boolean} [consolidateEvents=false]
|
||||||
|
* Whether consecutive sequences of events with similar properties
|
||||||
|
* should be consolidated into a single ConsolidatedKeyEvent object for
|
||||||
|
* display performance reasons.
|
||||||
|
*/
|
||||||
|
service.parseEvents = function parseEvents(
|
||||||
|
rawEvents, batchSeperation, consolidateEvents) {
|
||||||
|
|
||||||
|
// Default to 5 seconds if the batch seperation was not provided
|
||||||
|
if (batchSeperation === undefined || batchSeperation === null)
|
||||||
|
batchSeperation = 5000;
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
const pressedKeys = {};
|
||||||
|
|
||||||
|
// The timestamp of the most recent key event processed
|
||||||
|
let lastKeyEvent = 0;
|
||||||
|
|
||||||
|
// All text batches produced from the provided raw key events
|
||||||
|
const batches = [new service.TextBatch()];
|
||||||
|
|
||||||
|
// Process every provided raw
|
||||||
|
_.forEach(rawEvents, event => {
|
||||||
|
|
||||||
|
// Extract all fields from the raw event
|
||||||
|
const { definition, pressed, timestamp } = event;
|
||||||
|
const { keysym, name, value } = definition;
|
||||||
|
|
||||||
|
// Only switch to a new batch of text if sufficient time has passed
|
||||||
|
// since the last key event
|
||||||
|
const newBatch = (batchSeperation >= 0
|
||||||
|
&& (timestamp - lastKeyEvent) >= batchSeperation);
|
||||||
|
lastKeyEvent = timestamp;
|
||||||
|
|
||||||
|
if (newBatch)
|
||||||
|
batches.push(new service.TextBatch());
|
||||||
|
|
||||||
|
const currentBatch = _.last(batches);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Either push the a new event constructed using the provided fields
|
||||||
|
* into the latest batch, or consolidate into the latest event as
|
||||||
|
* appropriate given the consolidation configuration and event type.
|
||||||
|
*
|
||||||
|
* @param {!String} text
|
||||||
|
* The text representation of the event.
|
||||||
|
*
|
||||||
|
* @param {!Boolean} typed
|
||||||
|
* Whether the text value would be literally produced by typing
|
||||||
|
* the key that produced the event.
|
||||||
|
*/
|
||||||
|
const pushEvent = (text, typed) => {
|
||||||
|
const latestEvent = _.last(currentBatch.events);
|
||||||
|
|
||||||
|
// Only consolidate the event if configured to do so and it
|
||||||
|
// matches the type of the previous event
|
||||||
|
if (consolidateEvents && latestEvent && latestEvent.typed === typed) {
|
||||||
|
latestEvent.text += text;
|
||||||
|
currentBatch.simpleValue += text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, push a new event
|
||||||
|
else {
|
||||||
|
currentBatch.events.push(new service.ConsolidatedKeyEvent({
|
||||||
|
text, typed, timestamp}));
|
||||||
|
currentBatch.simpleValue += text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track modifier state
|
||||||
|
if (MODIFIER_KEYS[keysym]) {
|
||||||
|
if (pressed)
|
||||||
|
pressedKeys[keysym] = definition;
|
||||||
|
else
|
||||||
|
delete pressedKeys[keysym];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append to the current typed value when a printable
|
||||||
|
// (non-modifier) key is pressed
|
||||||
|
else if (pressed) {
|
||||||
|
|
||||||
|
// If any shorcut keys are currently pressed
|
||||||
|
if (_.some(pressedKeys, (def, key) => SHORTCUT_KEYS[key])) {
|
||||||
|
|
||||||
|
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 (let pressedKeysym in pressedKeys) {
|
||||||
|
|
||||||
|
var pressedKeyDefinition = pressedKeys[pressedKeysym];
|
||||||
|
|
||||||
|
// 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 += ('+' + name + '>')
|
||||||
|
|
||||||
|
// Add the shortcut to the current batch
|
||||||
|
pushEvent(shortcutText, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the key itself
|
||||||
|
else {
|
||||||
|
|
||||||
|
var keyText;
|
||||||
|
var typed;
|
||||||
|
|
||||||
|
// Print the value if explicitly defined
|
||||||
|
if (value !== undefined) {
|
||||||
|
|
||||||
|
keyText = value;
|
||||||
|
typed = true;
|
||||||
|
|
||||||
|
// If the name should be printed in addition, add it as a
|
||||||
|
// seperate event before the actual character value
|
||||||
|
if (PRINT_NAME_TOO_KEYS[keysym])
|
||||||
|
pushEvent(formatKeyName(name), false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise print the name
|
||||||
|
else {
|
||||||
|
|
||||||
|
keyText = formatKeyName(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
|
||||||
|
pushEvent(keyText, typed);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// All processed batches
|
||||||
|
return batches;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
|
||||||
|
}]);
|
@@ -97,6 +97,12 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-batches .text .key-event {
|
||||||
|
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.text-batches .text .not-typed {
|
.text-batches .text .not-typed {
|
||||||
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@@ -1,117 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* global _ */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service which defines the TextBatch class.
|
|
||||||
*/
|
|
||||||
angular.module('player').factory('TextBatch', [function defineTextBatch() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A batch of text associated with a recording. The batch consists of a
|
|
||||||
* string representation of the text that would be typed based on the key
|
|
||||||
* events in the recording, as well as a timestamp when the batch started.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Guacamole.KeyEventInterpreter.KeyEvent|TextBatch|Object} [template={}]
|
|
||||||
* The object whose properties should be copied within the new TextBatch.
|
|
||||||
*/
|
|
||||||
const TextBatch = function TextBatch(template) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All key events for this batch, with sequences of key events having
|
|
||||||
* the same `typed` field value combined.
|
|
||||||
*
|
|
||||||
* @type {!KeyEventBatch[]}
|
|
||||||
*/
|
|
||||||
this.events = _.reduce(template.events, (consolidatedEvents, rawEvent) => {
|
|
||||||
|
|
||||||
const currentEvent = _.last(consolidatedEvents);
|
|
||||||
|
|
||||||
// If a current event exists with the same `typed` value, conslidate
|
|
||||||
// the raw text event into it
|
|
||||||
if (currentEvent && currentEvent.typed === rawEvent.typed)
|
|
||||||
currentEvent.text += rawEvent.text;
|
|
||||||
|
|
||||||
// Otherwise, create a new conslidated event starting now
|
|
||||||
else
|
|
||||||
consolidatedEvents.push(new TextBatch.ConsolidatedKeyEvent(rawEvent));
|
|
||||||
|
|
||||||
return consolidatedEvents;
|
|
||||||
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 = template.simpleValue || '';
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A granular description of an extracted key event or sequence of events.
|
|
||||||
* Similar to the Guacamole.KeyEventInterpreter.KeyEvent type, except that
|
|
||||||
* this KeyEventBatch may contain multiple contiguous events of the same type,
|
|
||||||
* meaning that all event(s) that were combined into this event must have
|
|
||||||
* had the same `typed` field value. A single timestamp for the first combined
|
|
||||||
* event will be used for the whole batch.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Guacamole.KeyEventInterpreter.KeyEventBatch|ConsolidatedKeyEvent|Object} [template={}]
|
|
||||||
* The object whose properties should be copied within the new KeyEventBatch.
|
|
||||||
*/
|
|
||||||
TextBatch.ConsolidatedKeyEvent = function ConsolidatedKeyEvent(template) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A human-readable representation of the event(s). If a series of printable
|
|
||||||
* characters was directly typed, this will just be those character(s).
|
|
||||||
* Otherwise it will be a string describing the event(s).
|
|
||||||
*
|
|
||||||
* @type {!String}
|
|
||||||
*/
|
|
||||||
this.text = template.text;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* True if this text of this event is exactly a typed character, or false
|
|
||||||
* otherwise.
|
|
||||||
*
|
|
||||||
* @type {!boolean}
|
|
||||||
*/
|
|
||||||
this.typed = template.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 = template.timestamp;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
return TextBatch;
|
|
||||||
|
|
||||||
}]);
|
|
Reference in New Issue
Block a user