Merge 8eedf9e7c6 into 885363a373
This commit is contained in:
commit
6f7e831a0b
514
.eslintrc
514
.eslintrc
|
|
@ -18,5 +18,519 @@
|
|||
"arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }],
|
||||
"arrow-spacing": ["error"],
|
||||
"no-confusing-arrow": ["error", { "allowParens": true }],
|
||||
|
||||
// enforce line breaks after opening and before closing array brackets
|
||||
// https://eslint.org/docs/rules/array-bracket-newline
|
||||
// TODO: enable? semver-major
|
||||
"array-bracket-newline": ["off", "consistent"], // object option alternative: { multiline: true, minItems: 3 }
|
||||
|
||||
// enforce line breaks between array elements
|
||||
// https://eslint.org/docs/rules/array-element-newline
|
||||
// TODO: enable? semver-major
|
||||
"array-element-newline": ["off", { multiline: true, minItems: 3 }],
|
||||
|
||||
// enforce spacing inside array brackets
|
||||
"array-bracket-spacing": ["error", "never"],
|
||||
|
||||
// enforce spacing inside single-line blocks
|
||||
// https://eslint.org/docs/rules/block-spacing
|
||||
"block-spacing": ["error", "always"],
|
||||
|
||||
// enforce one true brace style
|
||||
"brace-style": ["error", "1tbs", { allowSingleLine: true }],
|
||||
|
||||
// require camel case names
|
||||
// TODO: semver-major (eslint 5): add ignoreDestructuring: false option
|
||||
// camelcase: ["error", { properties: "never" }],
|
||||
|
||||
// enforce or disallow capitalization of the first letter of a comment
|
||||
// https://eslint.org/docs/rules/capitalized-comments
|
||||
"capitalized-comments": ["off", "never", {
|
||||
line: {
|
||||
ignorePattern: ".*",
|
||||
ignoreInlineComments: true,
|
||||
ignoreConsecutiveComments: true,
|
||||
},
|
||||
block: {
|
||||
ignorePattern: ".*",
|
||||
ignoreInlineComments: true,
|
||||
ignoreConsecutiveComments: true,
|
||||
},
|
||||
}],
|
||||
|
||||
// require trailing commas in multiline object literals
|
||||
// "comma-dangle": ["error", {
|
||||
// arrays: "always-multiline",
|
||||
// objects: "always-multiline",
|
||||
// imports: "always-multiline",
|
||||
// exports: "always-multiline",
|
||||
// functions: "always-multiline",
|
||||
// }],
|
||||
|
||||
// enforce spacing before and after comma
|
||||
"comma-spacing": ["error", { before: false, after: true }],
|
||||
|
||||
// enforce one true comma style
|
||||
"comma-style": ["error", "last", {
|
||||
exceptions: {
|
||||
ArrayExpression: false,
|
||||
ArrayPattern: false,
|
||||
ArrowFunctionExpression: false,
|
||||
CallExpression: false,
|
||||
FunctionDeclaration: false,
|
||||
FunctionExpression: false,
|
||||
ImportDeclaration: false,
|
||||
ObjectExpression: false,
|
||||
ObjectPattern: false,
|
||||
VariableDeclaration: false,
|
||||
NewExpression: false,
|
||||
}
|
||||
}],
|
||||
|
||||
// disallow padding inside computed properties
|
||||
"computed-property-spacing": ["error", "never"],
|
||||
|
||||
// enforces consistent naming when capturing the current execution context
|
||||
"consistent-this": "off",
|
||||
|
||||
// enforce newline at the end of file, with no multiple empty lines
|
||||
"eol-last": ["error", "always"],
|
||||
|
||||
// enforce spacing between functions and their invocations
|
||||
// https://eslint.org/docs/rules/func-call-spacing
|
||||
"func-call-spacing": ["error", "never"],
|
||||
|
||||
// requires function names to match the name of the variable or property to which they are
|
||||
// assigned
|
||||
// https://eslint.org/docs/rules/func-name-matching
|
||||
// TODO: semver-major (eslint 5): add considerPropertyDescriptor: true
|
||||
"func-name-matching": ["off", "always", {
|
||||
includeCommonJSModuleExports: false
|
||||
}],
|
||||
|
||||
// require function expressions to have a name
|
||||
// https://eslint.org/docs/rules/func-names
|
||||
// "func-names": "warn",
|
||||
|
||||
// enforces use of function declarations or expressions
|
||||
// https://eslint.org/docs/rules/func-style
|
||||
// TODO: enable
|
||||
"func-style": ["off", "expression"],
|
||||
|
||||
// enforce consistent line breaks inside function parentheses
|
||||
// https://eslint.org/docs/rules/function-paren-newline
|
||||
"function-paren-newline": ["error", "consistent"],
|
||||
|
||||
// Blacklist certain identifiers to prevent them being used
|
||||
// https://eslint.org/docs/rules/id-blacklist
|
||||
"id-blacklist": "off",
|
||||
|
||||
// this option enforces minimum and maximum identifier lengths
|
||||
// (variable names, property names etc.)
|
||||
"id-length": "off",
|
||||
|
||||
// require identifiers to match the provided regular expression
|
||||
"id-match": "off",
|
||||
|
||||
// Enforce the location of arrow function bodies with implicit returns
|
||||
// https://eslint.org/docs/rules/implicit-arrow-linebreak
|
||||
"implicit-arrow-linebreak": ["error", "beside"],
|
||||
|
||||
// this option sets a specific tab width for your code
|
||||
// https://eslint.org/docs/rules/indent
|
||||
indent: ["error", 2, {
|
||||
SwitchCase: 1,
|
||||
VariableDeclarator: 1,
|
||||
outerIIFEBody: 1,
|
||||
// MemberExpression: null,
|
||||
FunctionDeclaration: {
|
||||
parameters: 1,
|
||||
body: 1
|
||||
},
|
||||
FunctionExpression: {
|
||||
parameters: 1,
|
||||
body: 1
|
||||
},
|
||||
CallExpression: {
|
||||
arguments: 1
|
||||
},
|
||||
ArrayExpression: 1,
|
||||
ObjectExpression: 1,
|
||||
ImportDeclaration: 1,
|
||||
flatTernaryExpressions: false,
|
||||
// list derived from https://github.com/benjamn/ast-types/blob/HEAD/def/jsx.js
|
||||
ignoredNodes: ["JSXElement", "JSXElement > *", "JSXAttribute", "JSXIdentifier", "JSXNamespacedName", "JSXMemberExpression", "JSXSpreadAttribute", "JSXExpressionContainer", "JSXOpeningElement", "JSXClosingElement", "JSXText", "JSXEmptyExpression", "JSXSpreadChild"],
|
||||
ignoreComments: false
|
||||
}],
|
||||
|
||||
// specify whether double or single quotes should be used in JSX attributes
|
||||
// https://eslint.org/docs/rules/jsx-quotes
|
||||
"jsx-quotes": ["off", "prefer-double"],
|
||||
|
||||
// enforces spacing between keys and values in object literal properties
|
||||
"key-spacing": ["error", { beforeColon: false, afterColon: true }],
|
||||
|
||||
// require a space before & after certain keywords
|
||||
"keyword-spacing": ["error", {
|
||||
before: true,
|
||||
after: true,
|
||||
overrides: {
|
||||
return: { after: true },
|
||||
throw: { after: true },
|
||||
case: { after: true }
|
||||
}
|
||||
}],
|
||||
|
||||
// enforce position of line comments
|
||||
// https://eslint.org/docs/rules/line-comment-position
|
||||
// TODO: enable?
|
||||
"line-comment-position": ["off", {
|
||||
position: "above",
|
||||
ignorePattern: "",
|
||||
applyDefaultPatterns: true,
|
||||
}],
|
||||
|
||||
// disallow mixed "LF" and "CRLF" as linebreaks
|
||||
// https://eslint.org/docs/rules/linebreak-style
|
||||
"linebreak-style": ["error", "unix"],
|
||||
|
||||
// require or disallow an empty line between class members
|
||||
// https://eslint.org/docs/rules/lines-between-class-members
|
||||
"lines-between-class-members": ["error", "always", { exceptAfterSingleLine: false }],
|
||||
|
||||
// enforces empty lines around comments
|
||||
"lines-around-comment": "off",
|
||||
|
||||
// require or disallow newlines around directives
|
||||
// https://eslint.org/docs/rules/lines-around-directive
|
||||
// "lines-around-directive": ["error", {
|
||||
// before: "always",
|
||||
// after: "always",
|
||||
// }],
|
||||
|
||||
// specify the maximum depth that blocks can be nested
|
||||
"max-depth": ["off", 4],
|
||||
|
||||
// specify the maximum length of a line in your program
|
||||
// https://eslint.org/docs/rules/max-len
|
||||
"max-len": ["error", 100, 2, {
|
||||
ignoreUrls: true,
|
||||
ignoreComments: false,
|
||||
ignoreRegExpLiterals: true,
|
||||
ignoreStrings: true,
|
||||
ignoreTemplateLiterals: true,
|
||||
}],
|
||||
|
||||
// specify the max number of lines in a file
|
||||
// https://eslint.org/docs/rules/max-lines
|
||||
"max-lines": ["off", {
|
||||
max: 300,
|
||||
skipBlankLines: true,
|
||||
skipComments: true
|
||||
}],
|
||||
|
||||
// enforce a maximum function length
|
||||
// https://eslint.org/docs/rules/max-lines-per-function
|
||||
"max-lines-per-function": ["off", {
|
||||
max: 50,
|
||||
skipBlankLines: true,
|
||||
skipComments: true,
|
||||
IIFEs: true,
|
||||
}],
|
||||
|
||||
// specify the maximum depth callbacks can be nested
|
||||
"max-nested-callbacks": "off",
|
||||
|
||||
// limits the number of parameters that can be used in the function declaration.
|
||||
"max-params": ["off", 3],
|
||||
|
||||
// specify the maximum number of statement allowed in a function
|
||||
"max-statements": ["off", 10],
|
||||
|
||||
// restrict the number of statements per line
|
||||
// https://eslint.org/docs/rules/max-statements-per-line
|
||||
"max-statements-per-line": ["off", { max: 1 }],
|
||||
|
||||
// enforce a particular style for multiline comments
|
||||
// https://eslint.org/docs/rules/multiline-comment-style
|
||||
"multiline-comment-style": ["off", "starred-block"],
|
||||
|
||||
// require multiline ternary
|
||||
// https://eslint.org/docs/rules/multiline-ternary
|
||||
// TODO: enable?
|
||||
"multiline-ternary": ["off", "never"],
|
||||
|
||||
// require a capital letter for constructors
|
||||
"new-cap": ["error", {
|
||||
newIsCap: true,
|
||||
newIsCapExceptions: [],
|
||||
capIsNew: false,
|
||||
capIsNewExceptions: ["Immutable.Map", "Immutable.Set", "Immutable.List"],
|
||||
}],
|
||||
|
||||
// disallow the omission of parentheses when invoking a constructor with no arguments
|
||||
// https://eslint.org/docs/rules/new-parens
|
||||
"new-parens": "error",
|
||||
|
||||
// allow/disallow an empty newline after var statement
|
||||
"newline-after-var": "off",
|
||||
|
||||
// https://eslint.org/docs/rules/newline-before-return
|
||||
"newline-before-return": "off",
|
||||
|
||||
// enforces new line after each method call in the chain to make it
|
||||
// more readable and easy to maintain
|
||||
// https://eslint.org/docs/rules/newline-per-chained-call
|
||||
"newline-per-chained-call": ["error", { ignoreChainWithDepth: 4 }],
|
||||
|
||||
// disallow use of the Array constructor
|
||||
"no-array-constructor": "error",
|
||||
|
||||
// disallow use of bitwise operators
|
||||
// https://eslint.org/docs/rules/no-bitwise
|
||||
// "no-bitwise": "error",
|
||||
|
||||
// disallow use of the continue statement
|
||||
// https://eslint.org/docs/rules/no-continue
|
||||
// "no-continue": "error",
|
||||
|
||||
// disallow comments inline after code
|
||||
"no-inline-comments": "off",
|
||||
|
||||
// disallow if as the only statement in an else block
|
||||
// https://eslint.org/docs/rules/no-lonely-if
|
||||
"no-lonely-if": "error",
|
||||
|
||||
// disallow un-paren"d mixes of different operators
|
||||
// https://eslint.org/docs/rules/no-mixed-operators
|
||||
// "no-mixed-operators": ["error", {
|
||||
// // the list of arthmetic groups disallows mixing `%` and `**`
|
||||
// // with other arithmetic operators.
|
||||
// groups: [
|
||||
// ["%", "**"],
|
||||
// ["%", "+"],
|
||||
// ["%", "-"],
|
||||
// ["%", "*"],
|
||||
// ["%", "/"],
|
||||
// ["**", "+"],
|
||||
// ["**", "-"],
|
||||
// ["**", "*"],
|
||||
// ["**", "/"],
|
||||
// ["&", "|", "^", "~", "<<", ">>", ">>>"],
|
||||
// ["==", "!=", "===", "!==", ">", ">=", "<", "<="],
|
||||
// ["&&", "||"],
|
||||
// ["in", "instanceof"]
|
||||
// ],
|
||||
// allowSamePrecedence: false
|
||||
// }],
|
||||
|
||||
// disallow mixed spaces and tabs for indentation
|
||||
"no-mixed-spaces-and-tabs": "error",
|
||||
|
||||
// disallow use of chained assignment expressions
|
||||
// https://eslint.org/docs/rules/no-multi-assign
|
||||
"no-multi-assign": ["error"],
|
||||
|
||||
// disallow multiple empty lines and only one newline at the end
|
||||
"no-multiple-empty-lines": ["error", { max: 2, maxEOF: 0 }],
|
||||
|
||||
// disallow negated conditions
|
||||
// https://eslint.org/docs/rules/no-negated-condition
|
||||
"no-negated-condition": "off",
|
||||
|
||||
// disallow nested ternary expressions
|
||||
"no-nested-ternary": "error",
|
||||
|
||||
// disallow use of the Object constructor
|
||||
"no-new-object": "error",
|
||||
|
||||
// disallow use of unary operators, ++ and --
|
||||
// https://eslint.org/docs/rules/no-plusplus
|
||||
// "no-plusplus": "error",
|
||||
|
||||
// disallow certain syntax forms
|
||||
// https://eslint.org/docs/rules/no-restricted-syntax
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
// {
|
||||
// selector: "ForInStatement",
|
||||
// message: "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.",
|
||||
// },
|
||||
{
|
||||
selector: "ForOfStatement",
|
||||
message: "iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.",
|
||||
},
|
||||
{
|
||||
selector: "LabeledStatement",
|
||||
message: "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.",
|
||||
},
|
||||
{
|
||||
selector: "WithStatement",
|
||||
message: "`with` is disallowed in strict mode because it makes code impossible to predict and optimize.",
|
||||
},
|
||||
],
|
||||
|
||||
// disallow space between function identifier and application
|
||||
"no-spaced-func": "error",
|
||||
|
||||
// disallow tab characters entirely
|
||||
"no-tabs": "error",
|
||||
|
||||
// disallow the use of ternary operators
|
||||
"no-ternary": "off",
|
||||
|
||||
// disallow trailing whitespace at the end of lines
|
||||
"no-trailing-spaces": ["error", {
|
||||
skipBlankLines: false,
|
||||
ignoreComments: false,
|
||||
}],
|
||||
|
||||
// disallow dangling underscores in identifiers
|
||||
// "no-underscore-dangle": ["error", {
|
||||
// allow: [],
|
||||
// allowAfterThis: true,
|
||||
// allowAfterSuper: false,
|
||||
// enforceInMethodNames: false,
|
||||
// }],
|
||||
|
||||
// disallow the use of Boolean literals in conditional expressions
|
||||
// also, prefer `a || b` over `a ? a : b`
|
||||
// https://eslint.org/docs/rules/no-unneeded-ternary
|
||||
"no-unneeded-ternary": ["error", { defaultAssignment: false }],
|
||||
|
||||
// disallow whitespace before properties
|
||||
// https://eslint.org/docs/rules/no-whitespace-before-property
|
||||
"no-whitespace-before-property": "error",
|
||||
|
||||
// enforce the location of single-line statements
|
||||
// https://eslint.org/docs/rules/nonblock-statement-body-position
|
||||
"nonblock-statement-body-position": ["error", "beside", { overrides: {} }],
|
||||
|
||||
// require padding inside curly braces
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
|
||||
// enforce line breaks between braces
|
||||
// https://eslint.org/docs/rules/object-curly-newline
|
||||
"object-curly-newline": ["error", {
|
||||
ObjectExpression: { minProperties: 4, multiline: true, consistent: true },
|
||||
ObjectPattern: { minProperties: 4, multiline: true, consistent: true },
|
||||
ImportDeclaration: { minProperties: 4, multiline: true, consistent: true },
|
||||
ExportDeclaration: { minProperties: 4, multiline: true, consistent: true },
|
||||
}],
|
||||
|
||||
// enforce "same line" or "multiple line" on object properties.
|
||||
// https://eslint.org/docs/rules/object-property-newline
|
||||
"object-property-newline": ["error", {
|
||||
allowAllPropertiesOnSameLine: true,
|
||||
}],
|
||||
|
||||
// allow just one var statement per function
|
||||
"one-var": ["error", "never"],
|
||||
|
||||
// require a newline around variable declaration
|
||||
// https://eslint.org/docs/rules/one-var-declaration-per-line
|
||||
"one-var-declaration-per-line": ["error", "always"],
|
||||
|
||||
// require assignment operator shorthand where possible or prohibit it entirely
|
||||
// https://eslint.org/docs/rules/operator-assignment
|
||||
"operator-assignment": ["error", "always"],
|
||||
|
||||
// Requires operator at the beginning of the line in multiline statements
|
||||
// https://eslint.org/docs/rules/operator-linebreak
|
||||
"operator-linebreak": ["error", "before", { overrides: { "=": "none" } }],
|
||||
|
||||
// disallow padding within blocks
|
||||
"padded-blocks": ["error", { blocks: "never", classes: "never", switches: "never" }],
|
||||
|
||||
// Require or disallow padding lines between statements
|
||||
// https://eslint.org/docs/rules/padding-line-between-statements
|
||||
"padding-line-between-statements": "off",
|
||||
|
||||
// Prefer use of an object spread over Object.assign
|
||||
// https://eslint.org/docs/rules/prefer-object-spread
|
||||
// TODO: semver-major (eslint 5): enable
|
||||
"prefer-object-spread": "off",
|
||||
|
||||
// require quotes around object literal property names
|
||||
// https://eslint.org/docs/rules/quote-props.html
|
||||
"quote-props": ["error", "as-needed", { keywords: false, unnecessary: true, numbers: false }],
|
||||
|
||||
// specify whether double or single quotes should be used
|
||||
quotes: ["error", "single", { avoidEscape: true }],
|
||||
|
||||
// do not require jsdoc
|
||||
// https://eslint.org/docs/rules/require-jsdoc
|
||||
"require-jsdoc": "off",
|
||||
|
||||
// require or disallow use of semicolons instead of ASI
|
||||
semi: ["error", "always"],
|
||||
|
||||
// enforce spacing before and after semicolons
|
||||
"semi-spacing": ["error", { before: false, after: true }],
|
||||
|
||||
// Enforce location of semicolons
|
||||
// https://eslint.org/docs/rules/semi-style
|
||||
"semi-style": ["error", "last"],
|
||||
|
||||
// requires object keys to be sorted
|
||||
"sort-keys": ["off", "asc", { caseSensitive: false, natural: true }],
|
||||
|
||||
// sort variables within the same declaration block
|
||||
"sort-vars": "off",
|
||||
|
||||
// require or disallow space before blocks
|
||||
"space-before-blocks": "error",
|
||||
|
||||
// require or disallow space before function opening parenthesis
|
||||
// https://eslint.org/docs/rules/space-before-function-paren
|
||||
"space-before-function-paren": ["error", {
|
||||
anonymous: "always",
|
||||
named: "never",
|
||||
asyncArrow: "always"
|
||||
}],
|
||||
|
||||
// require or disallow spaces inside parentheses
|
||||
"space-in-parens": ["error", "never"],
|
||||
|
||||
// require spaces around operators
|
||||
"space-infix-ops": "error",
|
||||
|
||||
// Require or disallow spaces before/after unary operators
|
||||
// https://eslint.org/docs/rules/space-unary-ops
|
||||
"space-unary-ops": ["error", {
|
||||
words: true,
|
||||
nonwords: false,
|
||||
overrides: {
|
||||
},
|
||||
}],
|
||||
|
||||
// require or disallow a space immediately following the // or /* in a comment
|
||||
// https://eslint.org/docs/rules/spaced-comment
|
||||
"spaced-comment": ["error", "always", {
|
||||
line: {
|
||||
exceptions: ["-", "+"],
|
||||
markers: ["=", "!"], // space here to support sprockets directives
|
||||
},
|
||||
block: {
|
||||
exceptions: ["-", "+"],
|
||||
markers: ["=", "!"], // space here to support sprockets directives
|
||||
balanced: true,
|
||||
}
|
||||
}],
|
||||
|
||||
// Enforce spacing around colons of switch statements
|
||||
// https://eslint.org/docs/rules/switch-colon-spacing
|
||||
"switch-colon-spacing": ["error", { after: true, before: false }],
|
||||
|
||||
// Require or disallow spacing between template tags and their literals
|
||||
// https://eslint.org/docs/rules/template-tag-spacing
|
||||
"template-tag-spacing": ["error", "never"],
|
||||
|
||||
// require or disallow the Unicode Byte Order Mark
|
||||
// https://eslint.org/docs/rules/unicode-bom
|
||||
"unicode-bom": ["error", "never"],
|
||||
|
||||
// require regex literals to be wrapped in parentheses
|
||||
"wrap-regex": "off"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,54 +5,54 @@
|
|||
// No ES6 can be used in this file since it's used for the translation
|
||||
/* eslint-disable prefer-arrow-callback */
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Fallback for all uncought errors
|
||||
function handleError (event, err) {
|
||||
try {
|
||||
const msg = document.getElementById('noVNC_fallback_errormsg');
|
||||
// Fallback for all uncought errors
|
||||
function handleError(event, err) {
|
||||
try {
|
||||
const msg = document.getElementById('noVNC_fallback_errormsg');
|
||||
|
||||
// Only show the initial error
|
||||
if (msg.hasChildNodes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let div = document.createElement("div");
|
||||
div.classList.add('noVNC_message');
|
||||
div.appendChild(document.createTextNode(event.message));
|
||||
msg.appendChild(div);
|
||||
|
||||
if (event.filename) {
|
||||
div = document.createElement("div");
|
||||
div.className = 'noVNC_location';
|
||||
let text = event.filename;
|
||||
if (event.lineno !== undefined) {
|
||||
text += ":" + event.lineno;
|
||||
if (event.colno !== undefined) {
|
||||
text += ":" + event.colno;
|
||||
}
|
||||
}
|
||||
div.appendChild(document.createTextNode(text));
|
||||
msg.appendChild(div);
|
||||
}
|
||||
|
||||
if (err && err.stack) {
|
||||
div = document.createElement("div");
|
||||
div.className = 'noVNC_stack';
|
||||
div.appendChild(document.createTextNode(err.stack));
|
||||
msg.appendChild(div);
|
||||
}
|
||||
|
||||
document.getElementById('noVNC_fallback_error')
|
||||
.classList.add("noVNC_open");
|
||||
} catch (exc) {
|
||||
document.write("noVNC encountered an error.");
|
||||
}
|
||||
// Don't return true since this would prevent the error
|
||||
// from being printed to the browser console.
|
||||
// Only show the initial error
|
||||
if (msg.hasChildNodes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let div = document.createElement('div');
|
||||
div.classList.add('noVNC_message');
|
||||
div.appendChild(document.createTextNode(event.message));
|
||||
msg.appendChild(div);
|
||||
|
||||
if (event.filename) {
|
||||
div = document.createElement('div');
|
||||
div.className = 'noVNC_location';
|
||||
let text = event.filename;
|
||||
if (event.lineno !== undefined) {
|
||||
text += ':' + event.lineno;
|
||||
if (event.colno !== undefined) {
|
||||
text += ':' + event.colno;
|
||||
}
|
||||
}
|
||||
div.appendChild(document.createTextNode(text));
|
||||
msg.appendChild(div);
|
||||
}
|
||||
|
||||
if (err && err.stack) {
|
||||
div = document.createElement('div');
|
||||
div.className = 'noVNC_stack';
|
||||
div.appendChild(document.createTextNode(err.stack));
|
||||
msg.appendChild(div);
|
||||
}
|
||||
|
||||
document.getElementById('noVNC_fallback_error')
|
||||
.classList.add('noVNC_open');
|
||||
} catch (exc) {
|
||||
document.write('noVNC encountered an error.');
|
||||
}
|
||||
window.addEventListener('error', function (evt) { handleError(evt, evt.error); });
|
||||
window.addEventListener('unhandledrejection', function (evt) { handleError(evt.reason, evt.reason); });
|
||||
// Don't return true since this would prevent the error
|
||||
// from being printed to the browser console.
|
||||
return false;
|
||||
}
|
||||
window.addEventListener('error', function (evt) { handleError(evt, evt.error); });
|
||||
window.addEventListener('unhandledrejection', function (evt) { handleError(evt.reason, evt.reason); });
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -11,157 +11,153 @@
|
|||
*/
|
||||
|
||||
export class Localizer {
|
||||
constructor() {
|
||||
// Currently configured language
|
||||
this.language = 'en';
|
||||
constructor() {
|
||||
// Currently configured language
|
||||
this.language = 'en';
|
||||
|
||||
// Current dictionary of translations
|
||||
this.dictionary = undefined;
|
||||
}
|
||||
// Current dictionary of translations
|
||||
this.dictionary = undefined;
|
||||
}
|
||||
|
||||
// Configure suitable language based on user preferences
|
||||
setup(supportedLanguages) {
|
||||
this.language = 'en'; // Default: US English
|
||||
// Configure suitable language based on user preferences
|
||||
setup(supportedLanguages) {
|
||||
this.language = 'en'; // Default: US English
|
||||
|
||||
/*
|
||||
/*
|
||||
* Navigator.languages only available in Chrome (32+) and FireFox (32+)
|
||||
* Fall back to navigator.language for other browsers
|
||||
*/
|
||||
let userLanguages;
|
||||
if (typeof window.navigator.languages == 'object') {
|
||||
userLanguages = window.navigator.languages;
|
||||
} else {
|
||||
userLanguages = [navigator.language || navigator.userLanguage];
|
||||
}
|
||||
|
||||
for (let i = 0;i < userLanguages.length;i++) {
|
||||
const userLang = userLanguages[i]
|
||||
.toLowerCase()
|
||||
.replace("_", "-")
|
||||
.split("-");
|
||||
|
||||
// Built-in default?
|
||||
if ((userLang[0] === 'en') &&
|
||||
((userLang[1] === undefined) || (userLang[1] === 'us'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First pass: perfect match
|
||||
for (let j = 0; j < supportedLanguages.length; j++) {
|
||||
const supLang = supportedLanguages[j]
|
||||
.toLowerCase()
|
||||
.replace("_", "-")
|
||||
.split("-");
|
||||
|
||||
if (userLang[0] !== supLang[0])
|
||||
continue;
|
||||
if (userLang[1] !== supLang[1])
|
||||
continue;
|
||||
|
||||
this.language = supportedLanguages[j];
|
||||
return;
|
||||
}
|
||||
|
||||
// Second pass: fallback
|
||||
for (let j = 0;j < supportedLanguages.length;j++) {
|
||||
const supLang = supportedLanguages[j]
|
||||
.toLowerCase()
|
||||
.replace("_", "-")
|
||||
.split("-");
|
||||
|
||||
if (userLang[0] !== supLang[0])
|
||||
continue;
|
||||
if (supLang[1] !== undefined)
|
||||
continue;
|
||||
|
||||
this.language = supportedLanguages[j];
|
||||
return;
|
||||
}
|
||||
}
|
||||
let userLanguages;
|
||||
if (typeof window.navigator.languages == 'object') {
|
||||
userLanguages = window.navigator.languages;
|
||||
} else {
|
||||
userLanguages = [navigator.language || navigator.userLanguage];
|
||||
}
|
||||
|
||||
// Retrieve localised text
|
||||
get(id) {
|
||||
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
|
||||
return this.dictionary[id];
|
||||
} else {
|
||||
return id;
|
||||
for (let i = 0; i < userLanguages.length; i++) {
|
||||
const userLang = userLanguages[i]
|
||||
.toLowerCase()
|
||||
.replace('_', '-')
|
||||
.split('-');
|
||||
|
||||
// Built-in default?
|
||||
if ((userLang[0] === 'en')
|
||||
&& ((userLang[1] === undefined) || (userLang[1] === 'us'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First pass: perfect match
|
||||
for (let j = 0; j < supportedLanguages.length; j++) {
|
||||
const supLang = supportedLanguages[j]
|
||||
.toLowerCase()
|
||||
.replace('_', '-')
|
||||
.split('-');
|
||||
|
||||
if (userLang[0] !== supLang[0]) continue;
|
||||
if (userLang[1] !== supLang[1]) continue;
|
||||
|
||||
this.language = supportedLanguages[j];
|
||||
return;
|
||||
}
|
||||
|
||||
// Second pass: fallback
|
||||
for (let j = 0; j < supportedLanguages.length; j++) {
|
||||
const supLang = supportedLanguages[j]
|
||||
.toLowerCase()
|
||||
.replace('_', '-')
|
||||
.split('-');
|
||||
|
||||
if (userLang[0] !== supLang[0]) continue;
|
||||
if (supLang[1] !== undefined) continue;
|
||||
|
||||
this.language = supportedLanguages[j];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve localised text
|
||||
get(id) {
|
||||
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
|
||||
return this.dictionary[id];
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
// Traverses the DOM and translates relevant fields
|
||||
// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
|
||||
translateDOM() {
|
||||
const self = this;
|
||||
|
||||
function process(elem, enabled) {
|
||||
function isAnyOf(searchElement, items) {
|
||||
return items.indexOf(searchElement) !== -1;
|
||||
}
|
||||
|
||||
function translateAttribute(elem, attr) {
|
||||
const str = self.get(elem.getAttribute(attr));
|
||||
elem.setAttribute(attr, str);
|
||||
}
|
||||
|
||||
function translateTextNode(node) {
|
||||
const str = self.get(node.data.trim());
|
||||
node.data = str;
|
||||
}
|
||||
|
||||
if (elem.hasAttribute('translate')) {
|
||||
if (isAnyOf(elem.getAttribute('translate'), ['', 'yes'])) {
|
||||
enabled = true;
|
||||
} else if (isAnyOf(elem.getAttribute('translate'), ['no'])) {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
if (elem.hasAttribute('abbr')
|
||||
&& elem.tagName === 'TH') {
|
||||
translateAttribute(elem, 'abbr');
|
||||
}
|
||||
if (elem.hasAttribute('alt')
|
||||
&& isAnyOf(elem.tagName, ['AREA', 'IMG', 'INPUT'])) {
|
||||
translateAttribute(elem, 'alt');
|
||||
}
|
||||
if (elem.hasAttribute('download')
|
||||
&& isAnyOf(elem.tagName, ['A', 'AREA'])) {
|
||||
translateAttribute(elem, 'download');
|
||||
}
|
||||
if (elem.hasAttribute('label')
|
||||
&& isAnyOf(elem.tagName, ['MENUITEM', 'MENU', 'OPTGROUP',
|
||||
'OPTION', 'TRACK'])) {
|
||||
translateAttribute(elem, 'label');
|
||||
}
|
||||
// FIXME: Should update "lang"
|
||||
if (elem.hasAttribute('placeholder')
|
||||
&& isAnyOf(elem.tagName, ['INPUT', 'TEXTAREA'])) {
|
||||
translateAttribute(elem, 'placeholder');
|
||||
}
|
||||
if (elem.hasAttribute('title')) {
|
||||
translateAttribute(elem, 'title');
|
||||
}
|
||||
if (elem.hasAttribute('value')
|
||||
&& elem.tagName === 'INPUT'
|
||||
&& isAnyOf(elem.getAttribute('type'), ['reset', 'button', 'submit'])) {
|
||||
translateAttribute(elem, 'value');
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < elem.childNodes.length; i++) {
|
||||
const node = elem.childNodes[i];
|
||||
if (node.nodeType === node.ELEMENT_NODE) {
|
||||
process(node, enabled);
|
||||
} else if (node.nodeType === node.TEXT_NODE && enabled) {
|
||||
translateTextNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Traverses the DOM and translates relevant fields
|
||||
// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
|
||||
translateDOM() {
|
||||
const self = this;
|
||||
|
||||
function process(elem, enabled) {
|
||||
function isAnyOf(searchElement, items) {
|
||||
return items.indexOf(searchElement) !== -1;
|
||||
}
|
||||
|
||||
function translateAttribute(elem, attr) {
|
||||
const str = self.get(elem.getAttribute(attr));
|
||||
elem.setAttribute(attr, str);
|
||||
}
|
||||
|
||||
function translateTextNode(node) {
|
||||
const str = self.get(node.data.trim());
|
||||
node.data = str;
|
||||
}
|
||||
|
||||
if (elem.hasAttribute("translate")) {
|
||||
if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
|
||||
enabled = true;
|
||||
} else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
if (elem.hasAttribute("abbr") &&
|
||||
elem.tagName === "TH") {
|
||||
translateAttribute(elem, "abbr");
|
||||
}
|
||||
if (elem.hasAttribute("alt") &&
|
||||
isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
|
||||
translateAttribute(elem, "alt");
|
||||
}
|
||||
if (elem.hasAttribute("download") &&
|
||||
isAnyOf(elem.tagName, ["A", "AREA"])) {
|
||||
translateAttribute(elem, "download");
|
||||
}
|
||||
if (elem.hasAttribute("label") &&
|
||||
isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
|
||||
"OPTION", "TRACK"])) {
|
||||
translateAttribute(elem, "label");
|
||||
}
|
||||
// FIXME: Should update "lang"
|
||||
if (elem.hasAttribute("placeholder") &&
|
||||
isAnyOf(elem.tagName, ["INPUT", "TEXTAREA"])) {
|
||||
translateAttribute(elem, "placeholder");
|
||||
}
|
||||
if (elem.hasAttribute("title")) {
|
||||
translateAttribute(elem, "title");
|
||||
}
|
||||
if (elem.hasAttribute("value") &&
|
||||
elem.tagName === "INPUT" &&
|
||||
isAnyOf(elem.getAttribute("type"), ["reset", "button", "submit"])) {
|
||||
translateAttribute(elem, "value");
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < elem.childNodes.length; i++) {
|
||||
const node = elem.childNodes[i];
|
||||
if (node.nodeType === node.ELEMENT_NODE) {
|
||||
process(node, enabled);
|
||||
} else if (node.nodeType === node.TEXT_NODE && enabled) {
|
||||
translateTextNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process(document.body, true);
|
||||
}
|
||||
process(document.body, true);
|
||||
}
|
||||
}
|
||||
|
||||
export const l10n = new Localizer();
|
||||
|
|
|
|||
348
app/webutil.js
348
app/webutil.js
|
|
@ -10,55 +10,63 @@
|
|||
import { init_logging as main_init_logging } from '../core/util/logging.js';
|
||||
|
||||
// init log level reading the logging HTTP param
|
||||
export function init_logging (level) {
|
||||
"use strict";
|
||||
if (typeof level !== "undefined") {
|
||||
main_init_logging(level);
|
||||
} else {
|
||||
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
|
||||
main_init_logging(param || undefined);
|
||||
}
|
||||
export function init_logging(level) {
|
||||
'use strict';
|
||||
|
||||
if (typeof level !== 'undefined') {
|
||||
main_init_logging(level);
|
||||
} else {
|
||||
const param = document.location.href.match(/logging=([A-Za-z0-9._-]*)/);
|
||||
main_init_logging(param || undefined);
|
||||
}
|
||||
}
|
||||
|
||||
// Read a query string variable
|
||||
export function getQueryVar (name, defVal) {
|
||||
"use strict";
|
||||
const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
|
||||
match = document.location.href.match(re);
|
||||
if (typeof defVal === 'undefined') { defVal = null; }
|
||||
|
||||
if (match) {
|
||||
return decodeURIComponent(match[1]);
|
||||
}
|
||||
export function getQueryVar(name, defVal) {
|
||||
'use strict';
|
||||
|
||||
return defVal;
|
||||
const re = new RegExp('.*[?&]' + name + '=([^&#]*)');
|
||||
|
||||
|
||||
const match = document.location.href.match(re);
|
||||
if (typeof defVal === 'undefined') { defVal = null; }
|
||||
|
||||
if (match) {
|
||||
return decodeURIComponent(match[1]);
|
||||
}
|
||||
|
||||
return defVal;
|
||||
}
|
||||
|
||||
// Read a hash fragment variable
|
||||
export function getHashVar (name, defVal) {
|
||||
"use strict";
|
||||
const re = new RegExp('.*[&#]' + name + '=([^&]*)'),
|
||||
match = document.location.hash.match(re);
|
||||
if (typeof defVal === 'undefined') { defVal = null; }
|
||||
export function getHashVar(name, defVal) {
|
||||
'use strict';
|
||||
|
||||
if (match) {
|
||||
return decodeURIComponent(match[1]);
|
||||
}
|
||||
const re = new RegExp('.*[&#]' + name + '=([^&]*)');
|
||||
|
||||
return defVal;
|
||||
|
||||
const match = document.location.hash.match(re);
|
||||
if (typeof defVal === 'undefined') { defVal = null; }
|
||||
|
||||
if (match) {
|
||||
return decodeURIComponent(match[1]);
|
||||
}
|
||||
|
||||
return defVal;
|
||||
}
|
||||
|
||||
// Read a variable from the fragment or the query string
|
||||
// Fragment takes precedence
|
||||
export function getConfigVar (name, defVal) {
|
||||
"use strict";
|
||||
const val = getHashVar(name);
|
||||
export function getConfigVar(name, defVal) {
|
||||
'use strict';
|
||||
|
||||
if (val === null) {
|
||||
return getQueryVar(name, defVal);
|
||||
}
|
||||
const val = getHashVar(name);
|
||||
|
||||
return val;
|
||||
if (val === null) {
|
||||
return getQueryVar(name, defVal);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -66,47 +74,51 @@ export function getConfigVar (name, defVal) {
|
|||
*/
|
||||
|
||||
// No days means only for this browser session
|
||||
export function createCookie (name, value, days) {
|
||||
"use strict";
|
||||
let date, expires;
|
||||
if (days) {
|
||||
date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
expires = "; expires=" + date.toGMTString();
|
||||
} else {
|
||||
expires = "";
|
||||
}
|
||||
export function createCookie(name, value, days) {
|
||||
'use strict';
|
||||
|
||||
let secure;
|
||||
if (document.location.protocol === "https:") {
|
||||
secure = "; secure";
|
||||
} else {
|
||||
secure = "";
|
||||
}
|
||||
document.cookie = name + "=" + value + expires + "; path=/" + secure;
|
||||
let date; let
|
||||
expires;
|
||||
if (days) {
|
||||
date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
expires = '; expires=' + date.toGMTString();
|
||||
} else {
|
||||
expires = '';
|
||||
}
|
||||
|
||||
let secure;
|
||||
if (document.location.protocol === 'https:') {
|
||||
secure = '; secure';
|
||||
} else {
|
||||
secure = '';
|
||||
}
|
||||
document.cookie = name + '=' + value + expires + '; path=/' + secure;
|
||||
}
|
||||
|
||||
export function readCookie (name, defaultValue) {
|
||||
"use strict";
|
||||
const nameEQ = name + "=";
|
||||
const ca = document.cookie.split(';');
|
||||
export function readCookie(name, defaultValue) {
|
||||
'use strict';
|
||||
|
||||
for (let i = 0; i < ca.length; i += 1) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') {
|
||||
c = c.substring(1, c.length);
|
||||
}
|
||||
if (c.indexOf(nameEQ) === 0) {
|
||||
return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
const nameEQ = name + '=';
|
||||
const ca = document.cookie.split(';');
|
||||
|
||||
for (let i = 0; i < ca.length; i += 1) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) === ' ') {
|
||||
c = c.substring(1, c.length);
|
||||
}
|
||||
if (c.indexOf(nameEQ) === 0) {
|
||||
return c.substring(nameEQ.length, c.length);
|
||||
}
|
||||
}
|
||||
|
||||
return (typeof defaultValue !== 'undefined') ? defaultValue : null;
|
||||
return (typeof defaultValue !== 'undefined') ? defaultValue : null;
|
||||
}
|
||||
|
||||
export function eraseCookie (name) {
|
||||
"use strict";
|
||||
createCookie(name, "", -1);
|
||||
export function eraseCookie(name) {
|
||||
'use strict';
|
||||
|
||||
createCookie(name, '', -1);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -115,104 +127,108 @@ export function eraseCookie (name) {
|
|||
|
||||
let settings = {};
|
||||
|
||||
export function initSettings (callback /*, ...callbackArgs */) {
|
||||
"use strict";
|
||||
const callbackArgs = Array.prototype.slice.call(arguments, 1);
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.get((cfg) => {
|
||||
settings = cfg;
|
||||
if (callback) {
|
||||
callback.apply(this, callbackArgs);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
settings = {};
|
||||
if (callback) {
|
||||
callback.apply(this, callbackArgs);
|
||||
}
|
||||
export function initSettings(callback /* , ...callbackArgs */) {
|
||||
'use strict';
|
||||
|
||||
const callbackArgs = Array.prototype.slice.call(arguments, 1);
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.get((cfg) => {
|
||||
settings = cfg;
|
||||
if (callback) {
|
||||
callback.apply(this, callbackArgs);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
settings = {};
|
||||
if (callback) {
|
||||
callback.apply(this, callbackArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the settings cache, but do not write to permanent storage
|
||||
export function setSetting (name, value) {
|
||||
settings[name] = value;
|
||||
export function setSetting(name, value) {
|
||||
settings[name] = value;
|
||||
}
|
||||
|
||||
// No days means only for this browser session
|
||||
export function writeSetting (name, value) {
|
||||
"use strict";
|
||||
if (settings[name] === value) return;
|
||||
export function writeSetting(name, value) {
|
||||
'use strict';
|
||||
|
||||
if (settings[name] === value) return;
|
||||
settings[name] = value;
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.set(settings);
|
||||
} else {
|
||||
localStorage.setItem(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
export function readSetting(name, defaultValue) {
|
||||
'use strict';
|
||||
|
||||
let value;
|
||||
if ((name in settings) || (window.chrome && window.chrome.storage)) {
|
||||
value = settings[name];
|
||||
} else {
|
||||
value = localStorage.getItem(name);
|
||||
settings[name] = value;
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.set(settings);
|
||||
} else {
|
||||
localStorage.setItem(name, value);
|
||||
}
|
||||
}
|
||||
if (typeof value === 'undefined') {
|
||||
value = null;
|
||||
}
|
||||
|
||||
if (value === null && typeof defaultValue !== 'undefined') {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function readSetting (name, defaultValue) {
|
||||
"use strict";
|
||||
let value;
|
||||
if ((name in settings) || (window.chrome && window.chrome.storage)) {
|
||||
value = settings[name];
|
||||
} else {
|
||||
value = localStorage.getItem(name);
|
||||
settings[name] = value;
|
||||
}
|
||||
if (typeof value === "undefined") {
|
||||
value = null;
|
||||
}
|
||||
export function eraseSetting(name) {
|
||||
'use strict';
|
||||
|
||||
if (value === null && typeof defaultValue !== "undefined") {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
// Deleting here means that next time the setting is read when using local
|
||||
// storage, it will be pulled from local storage again.
|
||||
// If the setting in local storage is changed (e.g. in another tab)
|
||||
// between this delete and the next read, it could lead to an unexpected
|
||||
// value change.
|
||||
delete settings[name];
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.remove(name);
|
||||
} else {
|
||||
localStorage.removeItem(name);
|
||||
}
|
||||
}
|
||||
|
||||
export function eraseSetting (name) {
|
||||
"use strict";
|
||||
// Deleting here means that next time the setting is read when using local
|
||||
// storage, it will be pulled from local storage again.
|
||||
// If the setting in local storage is changed (e.g. in another tab)
|
||||
// between this delete and the next read, it could lead to an unexpected
|
||||
// value change.
|
||||
delete settings[name];
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.remove(name);
|
||||
} else {
|
||||
localStorage.removeItem(name);
|
||||
}
|
||||
}
|
||||
export function injectParamIfMissing(path, param, value) {
|
||||
// force pretend that we're dealing with a relative path
|
||||
// (assume that we wanted an extra if we pass one in)
|
||||
path = '/' + path;
|
||||
|
||||
export function injectParamIfMissing (path, param, value) {
|
||||
// force pretend that we're dealing with a relative path
|
||||
// (assume that we wanted an extra if we pass one in)
|
||||
path = "/" + path;
|
||||
const elem = document.createElement('a');
|
||||
elem.href = path;
|
||||
|
||||
const elem = document.createElement('a');
|
||||
elem.href = path;
|
||||
const param_eq = encodeURIComponent(param) + '=';
|
||||
let query;
|
||||
if (elem.search) {
|
||||
query = elem.search.slice(1).split('&');
|
||||
} else {
|
||||
query = [];
|
||||
}
|
||||
|
||||
const param_eq = encodeURIComponent(param) + "=";
|
||||
let query;
|
||||
if (elem.search) {
|
||||
query = elem.search.slice(1).split('&');
|
||||
} else {
|
||||
query = [];
|
||||
}
|
||||
if (!query.some(v => v.startsWith(param_eq))) {
|
||||
query.push(param_eq + encodeURIComponent(value));
|
||||
elem.search = '?' + query.join('&');
|
||||
}
|
||||
|
||||
if (!query.some(v => v.startsWith(param_eq))) {
|
||||
query.push(param_eq + encodeURIComponent(value));
|
||||
elem.search = "?" + query.join("&");
|
||||
}
|
||||
// some browsers (e.g. IE11) may occasionally omit the leading slash
|
||||
// in the elem.pathname string. Handle that case gracefully.
|
||||
if (elem.pathname.charAt(0) == '/') {
|
||||
return elem.pathname.slice(1) + elem.search + elem.hash;
|
||||
}
|
||||
|
||||
// some browsers (e.g. IE11) may occasionally omit the leading slash
|
||||
// in the elem.pathname string. Handle that case gracefully.
|
||||
if (elem.pathname.charAt(0) == "/") {
|
||||
return elem.pathname.slice(1) + elem.search + elem.hash;
|
||||
}
|
||||
|
||||
return elem.pathname + elem.search + elem.hash;
|
||||
return elem.pathname + elem.search + elem.hash;
|
||||
}
|
||||
|
||||
// sadly, we can't use the Fetch API until we decide to drop
|
||||
|
|
@ -220,27 +236,27 @@ export function injectParamIfMissing (path, param, value) {
|
|||
// resolve will receive an object on success, while reject
|
||||
// will receive either an event or an error on failure.
|
||||
export function fetchJSON(path, resolve, reject) {
|
||||
// NB: IE11 doesn't support JSON as a responseType
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('GET', path);
|
||||
// NB: IE11 doesn't support JSON as a responseType
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('GET', path);
|
||||
|
||||
req.onload = () => {
|
||||
if (req.status === 200) {
|
||||
let resObj;
|
||||
try {
|
||||
resObj = JSON.parse(req.responseText);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(resObj);
|
||||
} else {
|
||||
reject(new Error("XHR got non-200 status while trying to load '" + path + "': " + req.status));
|
||||
}
|
||||
};
|
||||
req.onload = () => {
|
||||
if (req.status === 200) {
|
||||
let resObj;
|
||||
try {
|
||||
resObj = JSON.parse(req.responseText);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(resObj);
|
||||
} else {
|
||||
reject(new Error("XHR got non-200 status while trying to load '" + path + "': " + req.status));
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = evt => reject(new Error("XHR encountered an error while trying to load '" + path + "': " + evt.message));
|
||||
req.onerror = evt => reject(new Error("XHR encountered an error while trying to load '" + path + "': " + evt.message));
|
||||
|
||||
req.ontimeout = evt => reject(new Error("XHR timed out while trying to load '" + path + "'"));
|
||||
req.ontimeout = evt => reject(new Error("XHR timed out while trying to load '" + path + "'"));
|
||||
|
||||
req.send();
|
||||
req.send();
|
||||
}
|
||||
|
|
|
|||
181
core/base64.js
181
core/base64.js
|
|
@ -7,98 +7,99 @@
|
|||
import * as Log from './util/logging.js';
|
||||
|
||||
export default {
|
||||
/* Convert data (an array of integers) to a Base64 string. */
|
||||
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
|
||||
base64Pad : '=',
|
||||
/* Convert data (an array of integers) to a Base64 string. */
|
||||
toBase64Table: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
|
||||
base64Pad: '=',
|
||||
|
||||
encode(data) {
|
||||
"use strict";
|
||||
let result = '';
|
||||
const length = data.length;
|
||||
const lengthpad = (length % 3);
|
||||
// Convert every three bytes to 4 ascii characters.
|
||||
encode(data) {
|
||||
'use strict';
|
||||
|
||||
for (let i = 0; i < (length - 2); i += 3) {
|
||||
result += this.toBase64Table[data[i] >> 2];
|
||||
result += this.toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];
|
||||
result += this.toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)];
|
||||
result += this.toBase64Table[data[i + 2] & 0x3f];
|
||||
}
|
||||
let result = '';
|
||||
const length = data.length;
|
||||
const lengthpad = (length % 3);
|
||||
// Convert every three bytes to 4 ascii characters.
|
||||
|
||||
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
|
||||
const j = length - lengthpad;
|
||||
if (lengthpad === 2) {
|
||||
result += this.toBase64Table[data[j] >> 2];
|
||||
result += this.toBase64Table[((data[j] & 0x03) << 4) + (data[j + 1] >> 4)];
|
||||
result += this.toBase64Table[(data[j + 1] & 0x0f) << 2];
|
||||
result += this.toBase64Table[64];
|
||||
} else if (lengthpad === 1) {
|
||||
result += this.toBase64Table[data[j] >> 2];
|
||||
result += this.toBase64Table[(data[j] & 0x03) << 4];
|
||||
result += this.toBase64Table[64];
|
||||
result += this.toBase64Table[64];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/* Convert Base64 data to a string */
|
||||
toBinaryTable : [
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
|
||||
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
|
||||
52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
|
||||
15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
|
||||
-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
|
||||
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
|
||||
],
|
||||
|
||||
decode(data, offset) {
|
||||
offset = typeof(offset) !== 'undefined' ? offset : 0;
|
||||
|
||||
let data_length = data.indexOf('=') - offset;
|
||||
if (data_length < 0) { data_length = data.length - offset; }
|
||||
|
||||
/* Every four characters is 3 resulting numbers */
|
||||
const result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5);
|
||||
const result = new Array(result_length);
|
||||
|
||||
// Convert one by one.
|
||||
|
||||
let leftbits = 0; // number of bits decoded, but yet to be appended
|
||||
let leftdata = 0; // bits decoded, but yet to be appended
|
||||
for (let idx = 0, i = offset; i < data.length; i++) {
|
||||
const c = this.toBinaryTable[data.charCodeAt(i) & 0x7f];
|
||||
const padding = (data.charAt(i) === this.base64Pad);
|
||||
// Skip illegal characters and whitespace
|
||||
if (c === -1) {
|
||||
Log.Error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect data into leftdata, update bitcount
|
||||
leftdata = (leftdata << 6) | c;
|
||||
leftbits += 6;
|
||||
|
||||
// If we have 8 or more bits, append 8 bits to the result
|
||||
if (leftbits >= 8) {
|
||||
leftbits -= 8;
|
||||
// Append if not padding.
|
||||
if (!padding) {
|
||||
result[idx++] = (leftdata >> leftbits) & 0xff;
|
||||
}
|
||||
leftdata &= (1 << leftbits) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are any bits left, the base64 string was corrupted
|
||||
if (leftbits) {
|
||||
const err = new Error('Corrupted base64 string');
|
||||
err.name = 'Base64-Error';
|
||||
throw err;
|
||||
}
|
||||
|
||||
return result;
|
||||
for (let i = 0; i < (length - 2); i += 3) {
|
||||
result += this.toBase64Table[data[i] >> 2];
|
||||
result += this.toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];
|
||||
result += this.toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)];
|
||||
result += this.toBase64Table[data[i + 2] & 0x3f];
|
||||
}
|
||||
|
||||
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
|
||||
const j = length - lengthpad;
|
||||
if (lengthpad === 2) {
|
||||
result += this.toBase64Table[data[j] >> 2];
|
||||
result += this.toBase64Table[((data[j] & 0x03) << 4) + (data[j + 1] >> 4)];
|
||||
result += this.toBase64Table[(data[j + 1] & 0x0f) << 2];
|
||||
result += this.toBase64Table[64];
|
||||
} else if (lengthpad === 1) {
|
||||
result += this.toBase64Table[data[j] >> 2];
|
||||
result += this.toBase64Table[(data[j] & 0x03) << 4];
|
||||
result += this.toBase64Table[64];
|
||||
result += this.toBase64Table[64];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
/* Convert Base64 data to a string */
|
||||
toBinaryTable: [
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
|
||||
],
|
||||
|
||||
decode(data, offset) {
|
||||
offset = typeof (offset) !== 'undefined' ? offset : 0;
|
||||
|
||||
let data_length = data.indexOf('=') - offset;
|
||||
if (data_length < 0) { data_length = data.length - offset; }
|
||||
|
||||
/* Every four characters is 3 resulting numbers */
|
||||
const result_length = (data_length >> 2) * 3 + Math.floor((data_length % 4) / 1.5);
|
||||
const result = new Array(result_length);
|
||||
|
||||
// Convert one by one.
|
||||
|
||||
let leftbits = 0; // number of bits decoded, but yet to be appended
|
||||
let leftdata = 0; // bits decoded, but yet to be appended
|
||||
for (let idx = 0, i = offset; i < data.length; i++) {
|
||||
const c = this.toBinaryTable[data.charCodeAt(i) & 0x7f];
|
||||
const padding = (data.charAt(i) === this.base64Pad);
|
||||
// Skip illegal characters and whitespace
|
||||
if (c === -1) {
|
||||
Log.Error('Illegal character code ' + data.charCodeAt(i) + ' at position ' + i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect data into leftdata, update bitcount
|
||||
leftdata = (leftdata << 6) | c;
|
||||
leftbits += 6;
|
||||
|
||||
// If we have 8 or more bits, append 8 bits to the result
|
||||
if (leftbits >= 8) {
|
||||
leftbits -= 8;
|
||||
// Append if not padding.
|
||||
if (!padding) {
|
||||
result[idx++] = (leftdata >> leftbits) & 0xff;
|
||||
}
|
||||
leftdata &= (1 << leftbits) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are any bits left, the base64 string was corrupted
|
||||
if (leftbits) {
|
||||
const err = new Error('Corrupted base64 string');
|
||||
err.name = 'Base64-Error';
|
||||
throw err;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}; /* End of Base64 namespace */
|
||||
|
|
|
|||
367
core/des.js
367
core/des.js
|
|
@ -76,192 +76,213 @@
|
|||
*/
|
||||
|
||||
export default function DES(passwd) {
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
// Tables, permutations, S-boxes, etc.
|
||||
const PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
|
||||
25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
|
||||
50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
|
||||
totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28],
|
||||
z = 0x0,
|
||||
keys = [];
|
||||
let a,b,c,d,e,f;
|
||||
/* eslint-disable indent, comma-spacing, space-infix-ops */
|
||||
// Tables, permutations, S-boxes, etc.
|
||||
const PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
|
||||
25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
|
||||
50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31];
|
||||
const totrot = [1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28];
|
||||
|
||||
a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
|
||||
const SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
|
||||
z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
|
||||
a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
|
||||
c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
|
||||
a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
|
||||
const SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
|
||||
a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
|
||||
z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
|
||||
z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
|
||||
a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
|
||||
const SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
|
||||
b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
|
||||
c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
|
||||
b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
|
||||
a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
|
||||
const SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
|
||||
z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
|
||||
b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
|
||||
c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
|
||||
a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
|
||||
const SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
|
||||
a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
|
||||
z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
|
||||
c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
|
||||
a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
|
||||
const SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
|
||||
z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
|
||||
b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
|
||||
a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
|
||||
a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
|
||||
const SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
|
||||
b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
|
||||
b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
|
||||
z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
|
||||
a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
|
||||
const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
|
||||
c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
|
||||
a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
|
||||
z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
|
||||
const z = 0x0;
|
||||
|
||||
// Set the key.
|
||||
function setKeys(keyBlock) {
|
||||
const pc1m = [], pcr = [], kn = [];
|
||||
const keys = [];
|
||||
let a;
|
||||
let b;
|
||||
let c;
|
||||
let d;
|
||||
let e;
|
||||
let f;
|
||||
|
||||
for (let j = 0, l = 56; j < 56; ++j, l -= 8) {
|
||||
l += l < -5 ? 65 : l < -3 ? 31 : l < -1 ? 63 : l === 27 ? 35 : 0; // PC1
|
||||
const m = l & 0x7;
|
||||
pc1m[j] = ((keyBlock[l >>> 3] & (1<<m)) !== 0) ? 1: 0;
|
||||
}
|
||||
a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
|
||||
const SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
|
||||
z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
|
||||
a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
|
||||
c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
|
||||
a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
|
||||
const SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
|
||||
a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
|
||||
z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
|
||||
z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
|
||||
a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
|
||||
const SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
|
||||
b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
|
||||
c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
|
||||
b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
|
||||
a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
|
||||
const SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
|
||||
z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
|
||||
b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
|
||||
c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
|
||||
a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
|
||||
const SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
|
||||
a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
|
||||
z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
|
||||
c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
|
||||
a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
|
||||
const SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
|
||||
z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
|
||||
b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
|
||||
a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
|
||||
a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
|
||||
const SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
|
||||
b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
|
||||
b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
|
||||
z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
|
||||
a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
|
||||
const SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
|
||||
c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
|
||||
a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
|
||||
z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
|
||||
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
const m = i << 1;
|
||||
const n = m + 1;
|
||||
kn[m] = kn[n] = 0;
|
||||
for (let o = 28; o < 59; o += 28) {
|
||||
for (let j = o - 28; j < o; ++j) {
|
||||
const l = j + totrot[i];
|
||||
pcr[j] = l < o ? pc1m[l] : pc1m[l - 28];
|
||||
}
|
||||
}
|
||||
for (let j = 0; j < 24; ++j) {
|
||||
if (pcr[PC2[j]] !== 0) {
|
||||
kn[m] |= 1 << (23 - j);
|
||||
}
|
||||
if (pcr[PC2[j + 24]] !== 0) {
|
||||
kn[n] |= 1 << (23 - j);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* eslint-enable indent, comma-spacing, space-infix-ops */
|
||||
|
||||
// cookey
|
||||
for (let i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
|
||||
const raw0 = kn[rawi++];
|
||||
const raw1 = kn[rawi++];
|
||||
keys[KnLi] = (raw0 & 0x00fc0000) << 6;
|
||||
keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
|
||||
keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
|
||||
keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
|
||||
++KnLi;
|
||||
keys[KnLi] = (raw0 & 0x0003f000) << 12;
|
||||
keys[KnLi] |= (raw0 & 0x0000003f) << 16;
|
||||
keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
|
||||
keys[KnLi] |= (raw1 & 0x0000003f);
|
||||
++KnLi;
|
||||
}
|
||||
// Set the key.
|
||||
function setKeys(keyBlock) {
|
||||
const pc1m = []; const pcr = []; const
|
||||
kn = [];
|
||||
|
||||
for (let j = 0, l = 56; j < 56; ++j, l -= 8) {
|
||||
// PC1
|
||||
if (l < -5) {
|
||||
l += 65;
|
||||
} else if (l < -3) {
|
||||
l += 31;
|
||||
} else if (l < -1) {
|
||||
l += 63;
|
||||
} else if (l === 27) {
|
||||
l += 35;
|
||||
}
|
||||
const m = l & 0x7;
|
||||
pc1m[j] = ((keyBlock[l >>> 3] & (1 << m)) !== 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
// Encrypt 8 bytes of text
|
||||
function enc8(text) {
|
||||
const b = text.slice();
|
||||
let i = 0, l, r, x; // left, right, accumulator
|
||||
|
||||
// Squash 8 bytes to 2 ints
|
||||
l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
|
||||
r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
|
||||
|
||||
x = ((l >>> 4) ^ r) & 0x0f0f0f0f;
|
||||
r ^= x;
|
||||
l ^= (x << 4);
|
||||
x = ((l >>> 16) ^ r) & 0x0000ffff;
|
||||
r ^= x;
|
||||
l ^= (x << 16);
|
||||
x = ((r >>> 2) ^ l) & 0x33333333;
|
||||
l ^= x;
|
||||
r ^= (x << 2);
|
||||
x = ((r >>> 8) ^ l) & 0x00ff00ff;
|
||||
l ^= x;
|
||||
r ^= (x << 8);
|
||||
r = (r << 1) | ((r >>> 31) & 1);
|
||||
x = (l ^ r) & 0xaaaaaaaa;
|
||||
l ^= x;
|
||||
r ^= x;
|
||||
l = (l << 1) | ((l >>> 31) & 1);
|
||||
|
||||
for (let i = 0, keysi = 0; i < 8; ++i) {
|
||||
x = (r << 28) | (r >>> 4);
|
||||
x ^= keys[keysi++];
|
||||
let fval = SP7[x & 0x3f];
|
||||
fval |= SP5[(x >>> 8) & 0x3f];
|
||||
fval |= SP3[(x >>> 16) & 0x3f];
|
||||
fval |= SP1[(x >>> 24) & 0x3f];
|
||||
x = r ^ keys[keysi++];
|
||||
fval |= SP8[x & 0x3f];
|
||||
fval |= SP6[(x >>> 8) & 0x3f];
|
||||
fval |= SP4[(x >>> 16) & 0x3f];
|
||||
fval |= SP2[(x >>> 24) & 0x3f];
|
||||
l ^= fval;
|
||||
x = (l << 28) | (l >>> 4);
|
||||
x ^= keys[keysi++];
|
||||
fval = SP7[x & 0x3f];
|
||||
fval |= SP5[(x >>> 8) & 0x3f];
|
||||
fval |= SP3[(x >>> 16) & 0x3f];
|
||||
fval |= SP1[(x >>> 24) & 0x3f];
|
||||
x = l ^ keys[keysi++];
|
||||
fval |= SP8[x & 0x0000003f];
|
||||
fval |= SP6[(x >>> 8) & 0x3f];
|
||||
fval |= SP4[(x >>> 16) & 0x3f];
|
||||
fval |= SP2[(x >>> 24) & 0x3f];
|
||||
r ^= fval;
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
const m = i << 1;
|
||||
const n = m + 1;
|
||||
kn[m] = 0;
|
||||
kn[n] = 0;
|
||||
for (let o = 28; o < 59; o += 28) {
|
||||
for (let j = o - 28; j < o; ++j) {
|
||||
const l = j + totrot[i];
|
||||
pcr[j] = l < o ? pc1m[l] : pc1m[l - 28];
|
||||
}
|
||||
|
||||
r = (r << 31) | (r >>> 1);
|
||||
x = (l ^ r) & 0xaaaaaaaa;
|
||||
l ^= x;
|
||||
r ^= x;
|
||||
l = (l << 31) | (l >>> 1);
|
||||
x = ((l >>> 8) ^ r) & 0x00ff00ff;
|
||||
r ^= x;
|
||||
l ^= (x << 8);
|
||||
x = ((l >>> 2) ^ r) & 0x33333333;
|
||||
r ^= x;
|
||||
l ^= (x << 2);
|
||||
x = ((r >>> 16) ^ l) & 0x0000ffff;
|
||||
l ^= x;
|
||||
r ^= (x << 16);
|
||||
x = ((r >>> 4) ^ l) & 0x0f0f0f0f;
|
||||
l ^= x;
|
||||
r ^= (x << 4);
|
||||
|
||||
// Spread ints to bytes
|
||||
x = [r, l];
|
||||
for (i = 0; i < 8; i++) {
|
||||
b[i] = (x[i>>>2] >>> (8 * (3 - (i % 4)))) % 256;
|
||||
if (b[i] < 0) { b[i] += 256; } // unsigned
|
||||
}
|
||||
for (let j = 0; j < 24; ++j) {
|
||||
if (pcr[PC2[j]] !== 0) {
|
||||
kn[m] |= 1 << (23 - j);
|
||||
}
|
||||
return b;
|
||||
if (pcr[PC2[j + 24]] !== 0) {
|
||||
kn[n] |= 1 << (23 - j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt 16 bytes of text using passwd as key
|
||||
function encrypt(t) {
|
||||
return enc8(t.slice(0, 8)).concat(enc8(t.slice(8, 16)));
|
||||
// cookey
|
||||
for (let i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
|
||||
const raw0 = kn[rawi++];
|
||||
const raw1 = kn[rawi++];
|
||||
keys[KnLi] = (raw0 & 0x00fc0000) << 6;
|
||||
keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
|
||||
keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
|
||||
keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
|
||||
++KnLi;
|
||||
keys[KnLi] = (raw0 & 0x0003f000) << 12;
|
||||
keys[KnLi] |= (raw0 & 0x0000003f) << 16;
|
||||
keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
|
||||
keys[KnLi] |= (raw1 & 0x0000003f);
|
||||
++KnLi;
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt 8 bytes of text
|
||||
function enc8(text) {
|
||||
const b = text.slice();
|
||||
let i = 0; let l; let r; let
|
||||
x; // left, right, accumulator
|
||||
|
||||
// Squash 8 bytes to 2 ints
|
||||
l = b[i++] << 24 | b[i++] << 16 | b[i++] << 8 | b[i++];
|
||||
r = b[i++] << 24 | b[i++] << 16 | b[i++] << 8 | b[i++];
|
||||
|
||||
x = ((l >>> 4) ^ r) & 0x0f0f0f0f;
|
||||
r ^= x;
|
||||
l ^= (x << 4);
|
||||
x = ((l >>> 16) ^ r) & 0x0000ffff;
|
||||
r ^= x;
|
||||
l ^= (x << 16);
|
||||
x = ((r >>> 2) ^ l) & 0x33333333;
|
||||
l ^= x;
|
||||
r ^= (x << 2);
|
||||
x = ((r >>> 8) ^ l) & 0x00ff00ff;
|
||||
l ^= x;
|
||||
r ^= (x << 8);
|
||||
r = (r << 1) | ((r >>> 31) & 1);
|
||||
x = (l ^ r) & 0xaaaaaaaa;
|
||||
l ^= x;
|
||||
r ^= x;
|
||||
l = (l << 1) | ((l >>> 31) & 1);
|
||||
|
||||
for (let i = 0, keysi = 0; i < 8; ++i) {
|
||||
x = (r << 28) | (r >>> 4);
|
||||
x ^= keys[keysi++];
|
||||
let fval = SP7[x & 0x3f];
|
||||
fval |= SP5[(x >>> 8) & 0x3f];
|
||||
fval |= SP3[(x >>> 16) & 0x3f];
|
||||
fval |= SP1[(x >>> 24) & 0x3f];
|
||||
x = r ^ keys[keysi++];
|
||||
fval |= SP8[x & 0x3f];
|
||||
fval |= SP6[(x >>> 8) & 0x3f];
|
||||
fval |= SP4[(x >>> 16) & 0x3f];
|
||||
fval |= SP2[(x >>> 24) & 0x3f];
|
||||
l ^= fval;
|
||||
x = (l << 28) | (l >>> 4);
|
||||
x ^= keys[keysi++];
|
||||
fval = SP7[x & 0x3f];
|
||||
fval |= SP5[(x >>> 8) & 0x3f];
|
||||
fval |= SP3[(x >>> 16) & 0x3f];
|
||||
fval |= SP1[(x >>> 24) & 0x3f];
|
||||
x = l ^ keys[keysi++];
|
||||
fval |= SP8[x & 0x0000003f];
|
||||
fval |= SP6[(x >>> 8) & 0x3f];
|
||||
fval |= SP4[(x >>> 16) & 0x3f];
|
||||
fval |= SP2[(x >>> 24) & 0x3f];
|
||||
r ^= fval;
|
||||
}
|
||||
|
||||
setKeys(passwd); // Setup keys
|
||||
return {'encrypt': encrypt}; // Public interface
|
||||
r = (r << 31) | (r >>> 1);
|
||||
x = (l ^ r) & 0xaaaaaaaa;
|
||||
l ^= x;
|
||||
r ^= x;
|
||||
l = (l << 31) | (l >>> 1);
|
||||
x = ((l >>> 8) ^ r) & 0x00ff00ff;
|
||||
r ^= x;
|
||||
l ^= (x << 8);
|
||||
x = ((l >>> 2) ^ r) & 0x33333333;
|
||||
r ^= x;
|
||||
l ^= (x << 2);
|
||||
x = ((r >>> 16) ^ l) & 0x0000ffff;
|
||||
l ^= x;
|
||||
r ^= (x << 16);
|
||||
x = ((r >>> 4) ^ l) & 0x0f0f0f0f;
|
||||
l ^= x;
|
||||
r ^= (x << 4);
|
||||
|
||||
// Spread ints to bytes
|
||||
x = [r, l];
|
||||
for (i = 0; i < 8; i++) {
|
||||
b[i] = (x[i >>> 2] >>> (8 * (3 - (i % 4)))) % 256;
|
||||
if (b[i] < 0) { b[i] += 256; } // unsigned
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
// Encrypt 16 bytes of text using passwd as key
|
||||
function encrypt(t) {
|
||||
return enc8(t.slice(0, 8)).concat(enc8(t.slice(8, 16)));
|
||||
}
|
||||
|
||||
setKeys(passwd); // Setup keys
|
||||
return { encrypt: encrypt }; // Public interface
|
||||
}
|
||||
|
|
|
|||
1180
core/display.js
1180
core/display.js
File diff suppressed because it is too large
Load Diff
|
|
@ -7,35 +7,35 @@
|
|||
*/
|
||||
|
||||
export const encodings = {
|
||||
encodingRaw: 0,
|
||||
encodingCopyRect: 1,
|
||||
encodingRRE: 2,
|
||||
encodingHextile: 5,
|
||||
encodingTight: 7,
|
||||
encodingTightPNG: -260,
|
||||
encodingRaw: 0,
|
||||
encodingCopyRect: 1,
|
||||
encodingRRE: 2,
|
||||
encodingHextile: 5,
|
||||
encodingTight: 7,
|
||||
encodingTightPNG: -260,
|
||||
|
||||
pseudoEncodingQualityLevel9: -23,
|
||||
pseudoEncodingQualityLevel0: -32,
|
||||
pseudoEncodingDesktopSize: -223,
|
||||
pseudoEncodingLastRect: -224,
|
||||
pseudoEncodingCursor: -239,
|
||||
pseudoEncodingQEMUExtendedKeyEvent: -258,
|
||||
pseudoEncodingExtendedDesktopSize: -308,
|
||||
pseudoEncodingXvp: -309,
|
||||
pseudoEncodingFence: -312,
|
||||
pseudoEncodingContinuousUpdates: -313,
|
||||
pseudoEncodingCompressLevel9: -247,
|
||||
pseudoEncodingCompressLevel0: -256,
|
||||
pseudoEncodingQualityLevel9: -23,
|
||||
pseudoEncodingQualityLevel0: -32,
|
||||
pseudoEncodingDesktopSize: -223,
|
||||
pseudoEncodingLastRect: -224,
|
||||
pseudoEncodingCursor: -239,
|
||||
pseudoEncodingQEMUExtendedKeyEvent: -258,
|
||||
pseudoEncodingExtendedDesktopSize: -308,
|
||||
pseudoEncodingXvp: -309,
|
||||
pseudoEncodingFence: -312,
|
||||
pseudoEncodingContinuousUpdates: -313,
|
||||
pseudoEncodingCompressLevel9: -247,
|
||||
pseudoEncodingCompressLevel0: -256,
|
||||
};
|
||||
|
||||
export function encodingName(num) {
|
||||
switch (num) {
|
||||
case encodings.encodingRaw: return "Raw";
|
||||
case encodings.encodingCopyRect: return "CopyRect";
|
||||
case encodings.encodingRRE: return "RRE";
|
||||
case encodings.encodingHextile: return "Hextile";
|
||||
case encodings.encodingTight: return "Tight";
|
||||
case encodings.encodingTightPNG: return "TightPNG";
|
||||
default: return "[unknown encoding " + num + "]";
|
||||
}
|
||||
switch (num) {
|
||||
case encodings.encodingRaw: return 'Raw';
|
||||
case encodings.encodingCopyRect: return 'CopyRect';
|
||||
case encodings.encodingRRE: return 'RRE';
|
||||
case encodings.encodingHextile: return 'Hextile';
|
||||
case encodings.encodingTight: return 'Tight';
|
||||
case encodings.encodingTightPNG: return 'TightPNG';
|
||||
default: return '[unknown encoding ' + num + ']';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,38 @@
|
|||
import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js";
|
||||
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
|
||||
import { inflateInit, inflate, inflateReset } from '../vendor/pako/lib/zlib/inflate.js';
|
||||
import ZStream from '../vendor/pako/lib/zlib/zstream.js';
|
||||
|
||||
export default class Inflate {
|
||||
constructor() {
|
||||
this.strm = new ZStream();
|
||||
this.chunkSize = 1024 * 10 * 10;
|
||||
this.strm.output = new Uint8Array(this.chunkSize);
|
||||
this.windowBits = 5;
|
||||
constructor() {
|
||||
this.strm = new ZStream();
|
||||
this.chunkSize = 1024 * 10 * 10;
|
||||
this.strm.output = new Uint8Array(this.chunkSize);
|
||||
this.windowBits = 5;
|
||||
|
||||
inflateInit(this.strm, this.windowBits);
|
||||
inflateInit(this.strm, this.windowBits);
|
||||
}
|
||||
|
||||
inflate(data, flush, expected) {
|
||||
this.strm.input = data;
|
||||
this.strm.avail_in = this.strm.input.length;
|
||||
this.strm.next_in = 0;
|
||||
this.strm.next_out = 0;
|
||||
|
||||
// resize our output buffer if it's too small
|
||||
// (we could just use multiple chunks, but that would cause an extra
|
||||
// allocation each time to flatten the chunks)
|
||||
if (expected > this.chunkSize) {
|
||||
this.chunkSize = expected;
|
||||
this.strm.output = new Uint8Array(this.chunkSize);
|
||||
}
|
||||
|
||||
inflate(data, flush, expected) {
|
||||
this.strm.input = data;
|
||||
this.strm.avail_in = this.strm.input.length;
|
||||
this.strm.next_in = 0;
|
||||
this.strm.next_out = 0;
|
||||
this.strm.avail_out = this.chunkSize;
|
||||
|
||||
// resize our output buffer if it's too small
|
||||
// (we could just use multiple chunks, but that would cause an extra
|
||||
// allocation each time to flatten the chunks)
|
||||
if (expected > this.chunkSize) {
|
||||
this.chunkSize = expected;
|
||||
this.strm.output = new Uint8Array(this.chunkSize);
|
||||
}
|
||||
inflate(this.strm, flush);
|
||||
|
||||
this.strm.avail_out = this.chunkSize;
|
||||
return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
||||
}
|
||||
|
||||
inflate(this.strm, flush);
|
||||
|
||||
return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
|
||||
}
|
||||
|
||||
reset() {
|
||||
inflateReset(this.strm);
|
||||
}
|
||||
reset() {
|
||||
inflateReset(this.strm);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
import KeyTable from "./keysym.js";
|
||||
import KeyTable from './keysym.js';
|
||||
|
||||
/*
|
||||
* Mapping between HTML key values and VNC/X11 keysyms for "special"
|
||||
|
|
@ -15,202 +15,199 @@ import KeyTable from "./keysym.js";
|
|||
|
||||
const DOMKeyTable = {};
|
||||
|
||||
function addStandard(key, standard)
|
||||
{
|
||||
if (standard === undefined) throw "Undefined keysym for key \"" + key + "\"";
|
||||
if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\"";
|
||||
DOMKeyTable[key] = [standard, standard, standard, standard];
|
||||
function addStandard(key, standard) {
|
||||
if (standard === undefined) throw 'Undefined keysym for key "' + key + '"';
|
||||
if (key in DOMKeyTable) throw 'Duplicate entry for key "' + key + '"';
|
||||
DOMKeyTable[key] = [standard, standard, standard, standard];
|
||||
}
|
||||
|
||||
function addLeftRight(key, left, right)
|
||||
{
|
||||
if (left === undefined) throw "Undefined keysym for key \"" + key + "\"";
|
||||
if (right === undefined) throw "Undefined keysym for key \"" + key + "\"";
|
||||
if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\"";
|
||||
DOMKeyTable[key] = [left, left, right, left];
|
||||
function addLeftRight(key, left, right) {
|
||||
if (left === undefined) throw 'Undefined keysym for key "' + key + '"';
|
||||
if (right === undefined) throw 'Undefined keysym for key "' + key + '"';
|
||||
if (key in DOMKeyTable) throw 'Duplicate entry for key "' + key + '"';
|
||||
DOMKeyTable[key] = [left, left, right, left];
|
||||
}
|
||||
|
||||
function addNumpad(key, standard, numpad)
|
||||
{
|
||||
if (standard === undefined) throw "Undefined keysym for key \"" + key + "\"";
|
||||
if (numpad === undefined) throw "Undefined keysym for key \"" + key + "\"";
|
||||
if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\"";
|
||||
DOMKeyTable[key] = [standard, standard, standard, numpad];
|
||||
function addNumpad(key, standard, numpad) {
|
||||
if (standard === undefined) throw 'Undefined keysym for key "' + key + '"';
|
||||
if (numpad === undefined) throw 'Undefined keysym for key "' + key + '"';
|
||||
if (key in DOMKeyTable) throw 'Duplicate entry for key "' + key + '"';
|
||||
DOMKeyTable[key] = [standard, standard, standard, numpad];
|
||||
}
|
||||
|
||||
// 2.2. Modifier Keys
|
||||
|
||||
addLeftRight("Alt", KeyTable.XK_Alt_L, KeyTable.XK_Alt_R);
|
||||
addStandard("AltGraph", KeyTable.XK_ISO_Level3_Shift);
|
||||
addStandard("CapsLock", KeyTable.XK_Caps_Lock);
|
||||
addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R);
|
||||
addLeftRight('Alt', KeyTable.XK_Alt_L, KeyTable.XK_Alt_R);
|
||||
addStandard('AltGraph', KeyTable.XK_ISO_Level3_Shift);
|
||||
addStandard('CapsLock', KeyTable.XK_Caps_Lock);
|
||||
addLeftRight('Control', KeyTable.XK_Control_L, KeyTable.XK_Control_R);
|
||||
// - Fn
|
||||
// - FnLock
|
||||
addLeftRight("Hyper", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||
addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||
addStandard("NumLock", KeyTable.XK_Num_Lock);
|
||||
addStandard("ScrollLock", KeyTable.XK_Scroll_Lock);
|
||||
addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
|
||||
addLeftRight("Super", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||
addLeftRight('Hyper', KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||
addLeftRight('Meta', KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||
addStandard('NumLock', KeyTable.XK_Num_Lock);
|
||||
addStandard('ScrollLock', KeyTable.XK_Scroll_Lock);
|
||||
addLeftRight('Shift', KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
|
||||
addLeftRight('Super', KeyTable.XK_Super_L, KeyTable.XK_Super_R);
|
||||
// - Symbol
|
||||
// - SymbolLock
|
||||
|
||||
// 2.3. Whitespace Keys
|
||||
|
||||
addNumpad("Enter", KeyTable.XK_Return, KeyTable.XK_KP_Enter);
|
||||
addStandard("Tab", KeyTable.XK_Tab);
|
||||
addNumpad(" ", KeyTable.XK_space, KeyTable.XK_KP_Space);
|
||||
addNumpad('Enter', KeyTable.XK_Return, KeyTable.XK_KP_Enter);
|
||||
addStandard('Tab', KeyTable.XK_Tab);
|
||||
addNumpad(' ', KeyTable.XK_space, KeyTable.XK_KP_Space);
|
||||
|
||||
// 2.4. Navigation Keys
|
||||
|
||||
addNumpad("ArrowDown", KeyTable.XK_Down, KeyTable.XK_KP_Down);
|
||||
addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up);
|
||||
addNumpad("ArrowLeft", KeyTable.XK_Left, KeyTable.XK_KP_Left);
|
||||
addNumpad("ArrowRight", KeyTable.XK_Right, KeyTable.XK_KP_Right);
|
||||
addNumpad("End", KeyTable.XK_End, KeyTable.XK_KP_End);
|
||||
addNumpad("Home", KeyTable.XK_Home, KeyTable.XK_KP_Home);
|
||||
addNumpad("PageDown", KeyTable.XK_Next, KeyTable.XK_KP_Next);
|
||||
addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
|
||||
addNumpad('ArrowDown', KeyTable.XK_Down, KeyTable.XK_KP_Down);
|
||||
addNumpad('ArrowUp', KeyTable.XK_Up, KeyTable.XK_KP_Up);
|
||||
addNumpad('ArrowLeft', KeyTable.XK_Left, KeyTable.XK_KP_Left);
|
||||
addNumpad('ArrowRight', KeyTable.XK_Right, KeyTable.XK_KP_Right);
|
||||
addNumpad('End', KeyTable.XK_End, KeyTable.XK_KP_End);
|
||||
addNumpad('Home', KeyTable.XK_Home, KeyTable.XK_KP_Home);
|
||||
addNumpad('PageDown', KeyTable.XK_Next, KeyTable.XK_KP_Next);
|
||||
addNumpad('PageUp', KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
|
||||
|
||||
// 2.5. Editing Keys
|
||||
|
||||
addStandard("Backspace", KeyTable.XK_BackSpace);
|
||||
addNumpad("Clear", KeyTable.XK_Clear, KeyTable.XK_KP_Begin);
|
||||
addStandard("Copy", KeyTable.XF86XK_Copy);
|
||||
addStandard('Backspace', KeyTable.XK_BackSpace);
|
||||
addNumpad('Clear', KeyTable.XK_Clear, KeyTable.XK_KP_Begin);
|
||||
addStandard('Copy', KeyTable.XF86XK_Copy);
|
||||
// - CrSel
|
||||
addStandard("Cut", KeyTable.XF86XK_Cut);
|
||||
addNumpad("Delete", KeyTable.XK_Delete, KeyTable.XK_KP_Delete);
|
||||
addStandard('Cut', KeyTable.XF86XK_Cut);
|
||||
addNumpad('Delete', KeyTable.XK_Delete, KeyTable.XK_KP_Delete);
|
||||
// - EraseEof
|
||||
// - ExSel
|
||||
addNumpad("Insert", KeyTable.XK_Insert, KeyTable.XK_KP_Insert);
|
||||
addStandard("Paste", KeyTable.XF86XK_Paste);
|
||||
addStandard("Redo", KeyTable.XK_Redo);
|
||||
addStandard("Undo", KeyTable.XK_Undo);
|
||||
addNumpad('Insert', KeyTable.XK_Insert, KeyTable.XK_KP_Insert);
|
||||
addStandard('Paste', KeyTable.XF86XK_Paste);
|
||||
addStandard('Redo', KeyTable.XK_Redo);
|
||||
addStandard('Undo', KeyTable.XK_Undo);
|
||||
|
||||
// 2.6. UI Keys
|
||||
|
||||
// - Accept
|
||||
// - Again (could just be XK_Redo)
|
||||
// - Attn
|
||||
addStandard("Cancel", KeyTable.XK_Cancel);
|
||||
addStandard("ContextMenu", KeyTable.XK_Menu);
|
||||
addStandard("Escape", KeyTable.XK_Escape);
|
||||
addStandard("Execute", KeyTable.XK_Execute);
|
||||
addStandard("Find", KeyTable.XK_Find);
|
||||
addStandard("Help", KeyTable.XK_Help);
|
||||
addStandard("Pause", KeyTable.XK_Pause);
|
||||
addStandard('Cancel', KeyTable.XK_Cancel);
|
||||
addStandard('ContextMenu', KeyTable.XK_Menu);
|
||||
addStandard('Escape', KeyTable.XK_Escape);
|
||||
addStandard('Execute', KeyTable.XK_Execute);
|
||||
addStandard('Find', KeyTable.XK_Find);
|
||||
addStandard('Help', KeyTable.XK_Help);
|
||||
addStandard('Pause', KeyTable.XK_Pause);
|
||||
// - Play
|
||||
// - Props
|
||||
addStandard("Select", KeyTable.XK_Select);
|
||||
addStandard("ZoomIn", KeyTable.XF86XK_ZoomIn);
|
||||
addStandard("ZoomOut", KeyTable.XF86XK_ZoomOut);
|
||||
addStandard('Select', KeyTable.XK_Select);
|
||||
addStandard('ZoomIn', KeyTable.XF86XK_ZoomIn);
|
||||
addStandard('ZoomOut', KeyTable.XF86XK_ZoomOut);
|
||||
|
||||
// 2.7. Device Keys
|
||||
|
||||
addStandard("BrightnessDown", KeyTable.XF86XK_MonBrightnessDown);
|
||||
addStandard("BrightnessUp", KeyTable.XF86XK_MonBrightnessUp);
|
||||
addStandard("Eject", KeyTable.XF86XK_Eject);
|
||||
addStandard("LogOff", KeyTable.XF86XK_LogOff);
|
||||
addStandard("Power", KeyTable.XF86XK_PowerOff);
|
||||
addStandard("PowerOff", KeyTable.XF86XK_PowerDown);
|
||||
addStandard("PrintScreen", KeyTable.XK_Print);
|
||||
addStandard("Hibernate", KeyTable.XF86XK_Hibernate);
|
||||
addStandard("Standby", KeyTable.XF86XK_Standby);
|
||||
addStandard("WakeUp", KeyTable.XF86XK_WakeUp);
|
||||
addStandard('BrightnessDown', KeyTable.XF86XK_MonBrightnessDown);
|
||||
addStandard('BrightnessUp', KeyTable.XF86XK_MonBrightnessUp);
|
||||
addStandard('Eject', KeyTable.XF86XK_Eject);
|
||||
addStandard('LogOff', KeyTable.XF86XK_LogOff);
|
||||
addStandard('Power', KeyTable.XF86XK_PowerOff);
|
||||
addStandard('PowerOff', KeyTable.XF86XK_PowerDown);
|
||||
addStandard('PrintScreen', KeyTable.XK_Print);
|
||||
addStandard('Hibernate', KeyTable.XF86XK_Hibernate);
|
||||
addStandard('Standby', KeyTable.XF86XK_Standby);
|
||||
addStandard('WakeUp', KeyTable.XF86XK_WakeUp);
|
||||
|
||||
// 2.8. IME and Composition Keys
|
||||
|
||||
addStandard("AllCandidates", KeyTable.XK_MultipleCandidate);
|
||||
addStandard("Alphanumeric", KeyTable.XK_Eisu_Shift); // could also be _Eisu_Toggle
|
||||
addStandard("CodeInput", KeyTable.XK_Codeinput);
|
||||
addStandard("Compose", KeyTable.XK_Multi_key);
|
||||
addStandard("Convert", KeyTable.XK_Henkan);
|
||||
addStandard('AllCandidates', KeyTable.XK_MultipleCandidate);
|
||||
addStandard('Alphanumeric', KeyTable.XK_Eisu_Shift); // could also be _Eisu_Toggle
|
||||
addStandard('CodeInput', KeyTable.XK_Codeinput);
|
||||
addStandard('Compose', KeyTable.XK_Multi_key);
|
||||
addStandard('Convert', KeyTable.XK_Henkan);
|
||||
// - Dead
|
||||
// - FinalMode
|
||||
addStandard("GroupFirst", KeyTable.XK_ISO_First_Group);
|
||||
addStandard("GroupLast", KeyTable.XK_ISO_Last_Group);
|
||||
addStandard("GroupNext", KeyTable.XK_ISO_Next_Group);
|
||||
addStandard("GroupPrevious", KeyTable.XK_ISO_Prev_Group);
|
||||
addStandard('GroupFirst', KeyTable.XK_ISO_First_Group);
|
||||
addStandard('GroupLast', KeyTable.XK_ISO_Last_Group);
|
||||
addStandard('GroupNext', KeyTable.XK_ISO_Next_Group);
|
||||
addStandard('GroupPrevious', KeyTable.XK_ISO_Prev_Group);
|
||||
// - ModeChange (XK_Mode_switch is often used for AltGr)
|
||||
// - NextCandidate
|
||||
addStandard("NonConvert", KeyTable.XK_Muhenkan);
|
||||
addStandard("PreviousCandidate", KeyTable.XK_PreviousCandidate);
|
||||
addStandard('NonConvert', KeyTable.XK_Muhenkan);
|
||||
addStandard('PreviousCandidate', KeyTable.XK_PreviousCandidate);
|
||||
// - Process
|
||||
addStandard("SingleCandidate", KeyTable.XK_SingleCandidate);
|
||||
addStandard("HangulMode", KeyTable.XK_Hangul);
|
||||
addStandard("HanjaMode", KeyTable.XK_Hangul_Hanja);
|
||||
addStandard("JunjuaMode", KeyTable.XK_Hangul_Jeonja);
|
||||
addStandard("Eisu", KeyTable.XK_Eisu_toggle);
|
||||
addStandard("Hankaku", KeyTable.XK_Hankaku);
|
||||
addStandard("Hiragana", KeyTable.XK_Hiragana);
|
||||
addStandard("HiraganaKatakana", KeyTable.XK_Hiragana_Katakana);
|
||||
addStandard("KanaMode", KeyTable.XK_Kana_Shift); // could also be _Kana_Lock
|
||||
addStandard("KanjiMode", KeyTable.XK_Kanji);
|
||||
addStandard("Katakana", KeyTable.XK_Katakana);
|
||||
addStandard("Romaji", KeyTable.XK_Romaji);
|
||||
addStandard("Zenkaku", KeyTable.XK_Zenkaku);
|
||||
addStandard("ZenkakuHanaku", KeyTable.XK_Zenkaku_Hankaku);
|
||||
addStandard('SingleCandidate', KeyTable.XK_SingleCandidate);
|
||||
addStandard('HangulMode', KeyTable.XK_Hangul);
|
||||
addStandard('HanjaMode', KeyTable.XK_Hangul_Hanja);
|
||||
addStandard('JunjuaMode', KeyTable.XK_Hangul_Jeonja);
|
||||
addStandard('Eisu', KeyTable.XK_Eisu_toggle);
|
||||
addStandard('Hankaku', KeyTable.XK_Hankaku);
|
||||
addStandard('Hiragana', KeyTable.XK_Hiragana);
|
||||
addStandard('HiraganaKatakana', KeyTable.XK_Hiragana_Katakana);
|
||||
addStandard('KanaMode', KeyTable.XK_Kana_Shift); // could also be _Kana_Lock
|
||||
addStandard('KanjiMode', KeyTable.XK_Kanji);
|
||||
addStandard('Katakana', KeyTable.XK_Katakana);
|
||||
addStandard('Romaji', KeyTable.XK_Romaji);
|
||||
addStandard('Zenkaku', KeyTable.XK_Zenkaku);
|
||||
addStandard('ZenkakuHanaku', KeyTable.XK_Zenkaku_Hankaku);
|
||||
|
||||
// 2.9. General-Purpose Function Keys
|
||||
|
||||
addStandard("F1", KeyTable.XK_F1);
|
||||
addStandard("F2", KeyTable.XK_F2);
|
||||
addStandard("F3", KeyTable.XK_F3);
|
||||
addStandard("F4", KeyTable.XK_F4);
|
||||
addStandard("F5", KeyTable.XK_F5);
|
||||
addStandard("F6", KeyTable.XK_F6);
|
||||
addStandard("F7", KeyTable.XK_F7);
|
||||
addStandard("F8", KeyTable.XK_F8);
|
||||
addStandard("F9", KeyTable.XK_F9);
|
||||
addStandard("F10", KeyTable.XK_F10);
|
||||
addStandard("F11", KeyTable.XK_F11);
|
||||
addStandard("F12", KeyTable.XK_F12);
|
||||
addStandard("F13", KeyTable.XK_F13);
|
||||
addStandard("F14", KeyTable.XK_F14);
|
||||
addStandard("F15", KeyTable.XK_F15);
|
||||
addStandard("F16", KeyTable.XK_F16);
|
||||
addStandard("F17", KeyTable.XK_F17);
|
||||
addStandard("F18", KeyTable.XK_F18);
|
||||
addStandard("F19", KeyTable.XK_F19);
|
||||
addStandard("F20", KeyTable.XK_F20);
|
||||
addStandard("F21", KeyTable.XK_F21);
|
||||
addStandard("F22", KeyTable.XK_F22);
|
||||
addStandard("F23", KeyTable.XK_F23);
|
||||
addStandard("F24", KeyTable.XK_F24);
|
||||
addStandard("F25", KeyTable.XK_F25);
|
||||
addStandard("F26", KeyTable.XK_F26);
|
||||
addStandard("F27", KeyTable.XK_F27);
|
||||
addStandard("F28", KeyTable.XK_F28);
|
||||
addStandard("F29", KeyTable.XK_F29);
|
||||
addStandard("F30", KeyTable.XK_F30);
|
||||
addStandard("F31", KeyTable.XK_F31);
|
||||
addStandard("F32", KeyTable.XK_F32);
|
||||
addStandard("F33", KeyTable.XK_F33);
|
||||
addStandard("F34", KeyTable.XK_F34);
|
||||
addStandard("F35", KeyTable.XK_F35);
|
||||
addStandard('F1', KeyTable.XK_F1);
|
||||
addStandard('F2', KeyTable.XK_F2);
|
||||
addStandard('F3', KeyTable.XK_F3);
|
||||
addStandard('F4', KeyTable.XK_F4);
|
||||
addStandard('F5', KeyTable.XK_F5);
|
||||
addStandard('F6', KeyTable.XK_F6);
|
||||
addStandard('F7', KeyTable.XK_F7);
|
||||
addStandard('F8', KeyTable.XK_F8);
|
||||
addStandard('F9', KeyTable.XK_F9);
|
||||
addStandard('F10', KeyTable.XK_F10);
|
||||
addStandard('F11', KeyTable.XK_F11);
|
||||
addStandard('F12', KeyTable.XK_F12);
|
||||
addStandard('F13', KeyTable.XK_F13);
|
||||
addStandard('F14', KeyTable.XK_F14);
|
||||
addStandard('F15', KeyTable.XK_F15);
|
||||
addStandard('F16', KeyTable.XK_F16);
|
||||
addStandard('F17', KeyTable.XK_F17);
|
||||
addStandard('F18', KeyTable.XK_F18);
|
||||
addStandard('F19', KeyTable.XK_F19);
|
||||
addStandard('F20', KeyTable.XK_F20);
|
||||
addStandard('F21', KeyTable.XK_F21);
|
||||
addStandard('F22', KeyTable.XK_F22);
|
||||
addStandard('F23', KeyTable.XK_F23);
|
||||
addStandard('F24', KeyTable.XK_F24);
|
||||
addStandard('F25', KeyTable.XK_F25);
|
||||
addStandard('F26', KeyTable.XK_F26);
|
||||
addStandard('F27', KeyTable.XK_F27);
|
||||
addStandard('F28', KeyTable.XK_F28);
|
||||
addStandard('F29', KeyTable.XK_F29);
|
||||
addStandard('F30', KeyTable.XK_F30);
|
||||
addStandard('F31', KeyTable.XK_F31);
|
||||
addStandard('F32', KeyTable.XK_F32);
|
||||
addStandard('F33', KeyTable.XK_F33);
|
||||
addStandard('F34', KeyTable.XK_F34);
|
||||
addStandard('F35', KeyTable.XK_F35);
|
||||
// - Soft1...
|
||||
|
||||
// 2.10. Multimedia Keys
|
||||
|
||||
// - ChannelDown
|
||||
// - ChannelUp
|
||||
addStandard("Close", KeyTable.XF86XK_Close);
|
||||
addStandard("MailForward", KeyTable.XF86XK_MailForward);
|
||||
addStandard("MailReply", KeyTable.XF86XK_Reply);
|
||||
addStandard("MainSend", KeyTable.XF86XK_Send);
|
||||
addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
|
||||
addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
|
||||
addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
|
||||
addStandard("MediaRecord", KeyTable.XF86XK_AudioRecord);
|
||||
addStandard("MediaRewind", KeyTable.XF86XK_AudioRewind);
|
||||
addStandard("MediaStop", KeyTable.XF86XK_AudioStop);
|
||||
addStandard("MediaTrackNext", KeyTable.XF86XK_AudioNext);
|
||||
addStandard("MediaTrackPrevious", KeyTable.XF86XK_AudioPrev);
|
||||
addStandard("New", KeyTable.XF86XK_New);
|
||||
addStandard("Open", KeyTable.XF86XK_Open);
|
||||
addStandard("Print", KeyTable.XK_Print);
|
||||
addStandard("Save", KeyTable.XF86XK_Save);
|
||||
addStandard("SpellCheck", KeyTable.XF86XK_Spell);
|
||||
addStandard('Close', KeyTable.XF86XK_Close);
|
||||
addStandard('MailForward', KeyTable.XF86XK_MailForward);
|
||||
addStandard('MailReply', KeyTable.XF86XK_Reply);
|
||||
addStandard('MainSend', KeyTable.XF86XK_Send);
|
||||
addStandard('MediaFastForward', KeyTable.XF86XK_AudioForward);
|
||||
addStandard('MediaPause', KeyTable.XF86XK_AudioPause);
|
||||
addStandard('MediaPlay', KeyTable.XF86XK_AudioPlay);
|
||||
addStandard('MediaRecord', KeyTable.XF86XK_AudioRecord);
|
||||
addStandard('MediaRewind', KeyTable.XF86XK_AudioRewind);
|
||||
addStandard('MediaStop', KeyTable.XF86XK_AudioStop);
|
||||
addStandard('MediaTrackNext', KeyTable.XF86XK_AudioNext);
|
||||
addStandard('MediaTrackPrevious', KeyTable.XF86XK_AudioPrev);
|
||||
addStandard('New', KeyTable.XF86XK_New);
|
||||
addStandard('Open', KeyTable.XF86XK_Open);
|
||||
addStandard('Print', KeyTable.XK_Print);
|
||||
addStandard('Save', KeyTable.XF86XK_Save);
|
||||
addStandard('SpellCheck', KeyTable.XF86XK_Spell);
|
||||
|
||||
// 2.11. Multimedia Numpad Keys
|
||||
|
||||
|
|
@ -231,13 +228,13 @@ addStandard("SpellCheck", KeyTable.XF86XK_Spell);
|
|||
// - AudioSurroundModeNext
|
||||
// - AudioTrebleDown
|
||||
// - AudioTrebleUp
|
||||
addStandard("AudioVolumeDown", KeyTable.XF86XK_AudioLowerVolume);
|
||||
addStandard("AudioVolumeUp", KeyTable.XF86XK_AudioRaiseVolume);
|
||||
addStandard("AudioVolumeMute", KeyTable.XF86XK_AudioMute);
|
||||
addStandard('AudioVolumeDown', KeyTable.XF86XK_AudioLowerVolume);
|
||||
addStandard('AudioVolumeUp', KeyTable.XF86XK_AudioRaiseVolume);
|
||||
addStandard('AudioVolumeMute', KeyTable.XF86XK_AudioMute);
|
||||
// - MicrophoneToggle
|
||||
// - MicrophoneVolumeDown
|
||||
// - MicrophoneVolumeUp
|
||||
addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
|
||||
addStandard('MicrophoneVolumeMute', KeyTable.XF86XK_AudioMicMute);
|
||||
|
||||
// 2.13. Speech Keys
|
||||
|
||||
|
|
@ -246,28 +243,28 @@ addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
|
|||
|
||||
// 2.14. Application Keys
|
||||
|
||||
addStandard("LaunchCalculator", KeyTable.XF86XK_Calculator);
|
||||
addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar);
|
||||
addStandard("LaunchMail", KeyTable.XF86XK_Mail);
|
||||
addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia);
|
||||
addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music);
|
||||
addStandard("LaunchMyComputer", KeyTable.XF86XK_MyComputer);
|
||||
addStandard("LaunchPhone", KeyTable.XF86XK_Phone);
|
||||
addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver);
|
||||
addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel);
|
||||
addStandard("LaunchWebBrowser", KeyTable.XF86XK_WWW);
|
||||
addStandard("LaunchWebCam", KeyTable.XF86XK_WebCam);
|
||||
addStandard("LaunchWordProcessor", KeyTable.XF86XK_Word);
|
||||
addStandard('LaunchCalculator', KeyTable.XF86XK_Calculator);
|
||||
addStandard('LaunchCalendar', KeyTable.XF86XK_Calendar);
|
||||
addStandard('LaunchMail', KeyTable.XF86XK_Mail);
|
||||
addStandard('LaunchMediaPlayer', KeyTable.XF86XK_AudioMedia);
|
||||
addStandard('LaunchMusicPlayer', KeyTable.XF86XK_Music);
|
||||
addStandard('LaunchMyComputer', KeyTable.XF86XK_MyComputer);
|
||||
addStandard('LaunchPhone', KeyTable.XF86XK_Phone);
|
||||
addStandard('LaunchScreenSaver', KeyTable.XF86XK_ScreenSaver);
|
||||
addStandard('LaunchSpreadsheet', KeyTable.XF86XK_Excel);
|
||||
addStandard('LaunchWebBrowser', KeyTable.XF86XK_WWW);
|
||||
addStandard('LaunchWebCam', KeyTable.XF86XK_WebCam);
|
||||
addStandard('LaunchWordProcessor', KeyTable.XF86XK_Word);
|
||||
|
||||
// 2.15. Browser Keys
|
||||
|
||||
addStandard("BrowserBack", KeyTable.XF86XK_Back);
|
||||
addStandard("BrowserFavorites", KeyTable.XF86XK_Favorites);
|
||||
addStandard("BrowserForward", KeyTable.XF86XK_Forward);
|
||||
addStandard("BrowserHome", KeyTable.XF86XK_HomePage);
|
||||
addStandard("BrowserRefresh", KeyTable.XF86XK_Refresh);
|
||||
addStandard("BrowserSearch", KeyTable.XF86XK_Search);
|
||||
addStandard("BrowserStop", KeyTable.XF86XK_Stop);
|
||||
addStandard('BrowserBack', KeyTable.XF86XK_Back);
|
||||
addStandard('BrowserFavorites', KeyTable.XF86XK_Favorites);
|
||||
addStandard('BrowserForward', KeyTable.XF86XK_Forward);
|
||||
addStandard('BrowserHome', KeyTable.XF86XK_HomePage);
|
||||
addStandard('BrowserRefresh', KeyTable.XF86XK_Refresh);
|
||||
addStandard('BrowserSearch', KeyTable.XF86XK_Search);
|
||||
addStandard('BrowserStop', KeyTable.XF86XK_Stop);
|
||||
|
||||
// 2.16. Mobile Phone Keys
|
||||
|
||||
|
|
@ -280,31 +277,31 @@ addStandard("BrowserStop", KeyTable.XF86XK_Stop);
|
|||
// 2.18. Media Controller Keys
|
||||
|
||||
// - A whole bunch...
|
||||
addStandard("Dimmer", KeyTable.XF86XK_BrightnessAdjust);
|
||||
addStandard("MediaAudioTrack", KeyTable.XF86XK_AudioCycleTrack);
|
||||
addStandard("RandomToggle", KeyTable.XF86XK_AudioRandomPlay);
|
||||
addStandard("SplitScreenToggle", KeyTable.XF86XK_SplitScreen);
|
||||
addStandard("Subtitle", KeyTable.XF86XK_Subtitle);
|
||||
addStandard("VideoModeNext", KeyTable.XF86XK_Next_VMode);
|
||||
addStandard('Dimmer', KeyTable.XF86XK_BrightnessAdjust);
|
||||
addStandard('MediaAudioTrack', KeyTable.XF86XK_AudioCycleTrack);
|
||||
addStandard('RandomToggle', KeyTable.XF86XK_AudioRandomPlay);
|
||||
addStandard('SplitScreenToggle', KeyTable.XF86XK_SplitScreen);
|
||||
addStandard('Subtitle', KeyTable.XF86XK_Subtitle);
|
||||
addStandard('VideoModeNext', KeyTable.XF86XK_Next_VMode);
|
||||
|
||||
// Extra: Numpad
|
||||
|
||||
addNumpad("=", KeyTable.XK_equal, KeyTable.XK_KP_Equal);
|
||||
addNumpad("+", KeyTable.XK_plus, KeyTable.XK_KP_Add);
|
||||
addNumpad("-", KeyTable.XK_minus, KeyTable.XK_KP_Subtract);
|
||||
addNumpad("*", KeyTable.XK_asterisk, KeyTable.XK_KP_Multiply);
|
||||
addNumpad("/", KeyTable.XK_slash, KeyTable.XK_KP_Divide);
|
||||
addNumpad(".", KeyTable.XK_period, KeyTable.XK_KP_Decimal);
|
||||
addNumpad(",", KeyTable.XK_comma, KeyTable.XK_KP_Separator);
|
||||
addNumpad("0", KeyTable.XK_0, KeyTable.XK_KP_0);
|
||||
addNumpad("1", KeyTable.XK_1, KeyTable.XK_KP_1);
|
||||
addNumpad("2", KeyTable.XK_2, KeyTable.XK_KP_2);
|
||||
addNumpad("3", KeyTable.XK_3, KeyTable.XK_KP_3);
|
||||
addNumpad("4", KeyTable.XK_4, KeyTable.XK_KP_4);
|
||||
addNumpad("5", KeyTable.XK_5, KeyTable.XK_KP_5);
|
||||
addNumpad("6", KeyTable.XK_6, KeyTable.XK_KP_6);
|
||||
addNumpad("7", KeyTable.XK_7, KeyTable.XK_KP_7);
|
||||
addNumpad("8", KeyTable.XK_8, KeyTable.XK_KP_8);
|
||||
addNumpad("9", KeyTable.XK_9, KeyTable.XK_KP_9);
|
||||
addNumpad('=', KeyTable.XK_equal, KeyTable.XK_KP_Equal);
|
||||
addNumpad('+', KeyTable.XK_plus, KeyTable.XK_KP_Add);
|
||||
addNumpad('-', KeyTable.XK_minus, KeyTable.XK_KP_Subtract);
|
||||
addNumpad('*', KeyTable.XK_asterisk, KeyTable.XK_KP_Multiply);
|
||||
addNumpad('/', KeyTable.XK_slash, KeyTable.XK_KP_Divide);
|
||||
addNumpad('.', KeyTable.XK_period, KeyTable.XK_KP_Decimal);
|
||||
addNumpad(',', KeyTable.XK_comma, KeyTable.XK_KP_Separator);
|
||||
addNumpad('0', KeyTable.XK_0, KeyTable.XK_KP_0);
|
||||
addNumpad('1', KeyTable.XK_1, KeyTable.XK_KP_1);
|
||||
addNumpad('2', KeyTable.XK_2, KeyTable.XK_KP_2);
|
||||
addNumpad('3', KeyTable.XK_3, KeyTable.XK_KP_3);
|
||||
addNumpad('4', KeyTable.XK_4, KeyTable.XK_KP_4);
|
||||
addNumpad('5', KeyTable.XK_5, KeyTable.XK_KP_5);
|
||||
addNumpad('6', KeyTable.XK_6, KeyTable.XK_KP_6);
|
||||
addNumpad('7', KeyTable.XK_7, KeyTable.XK_KP_7);
|
||||
addNumpad('8', KeyTable.XK_8, KeyTable.XK_KP_8);
|
||||
addNumpad('9', KeyTable.XK_9, KeyTable.XK_KP_9);
|
||||
|
||||
export default DOMKeyTable;
|
||||
|
|
|
|||
|
|
@ -18,110 +18,110 @@ export default {
|
|||
|
||||
// 3.1.1.1. Writing System Keys
|
||||
|
||||
'Backspace': 'Backspace',
|
||||
Backspace: 'Backspace',
|
||||
|
||||
// 3.1.1.2. Functional Keys
|
||||
// 3.1.1.2. Functional Keys
|
||||
|
||||
'AltLeft': 'Alt',
|
||||
'AltRight': 'Alt', // This could also be 'AltGraph'
|
||||
'CapsLock': 'CapsLock',
|
||||
'ContextMenu': 'ContextMenu',
|
||||
'ControlLeft': 'Control',
|
||||
'ControlRight': 'Control',
|
||||
'Enter': 'Enter',
|
||||
'MetaLeft': 'Meta',
|
||||
'MetaRight': 'Meta',
|
||||
'ShiftLeft': 'Shift',
|
||||
'ShiftRight': 'Shift',
|
||||
'Tab': 'Tab',
|
||||
// FIXME: Japanese/Korean keys
|
||||
AltLeft: 'Alt',
|
||||
AltRight: 'Alt', // This could also be 'AltGraph'
|
||||
CapsLock: 'CapsLock',
|
||||
ContextMenu: 'ContextMenu',
|
||||
ControlLeft: 'Control',
|
||||
ControlRight: 'Control',
|
||||
Enter: 'Enter',
|
||||
MetaLeft: 'Meta',
|
||||
MetaRight: 'Meta',
|
||||
ShiftLeft: 'Shift',
|
||||
ShiftRight: 'Shift',
|
||||
Tab: 'Tab',
|
||||
// FIXME: Japanese/Korean keys
|
||||
|
||||
// 3.1.2. Control Pad Section
|
||||
// 3.1.2. Control Pad Section
|
||||
|
||||
'Delete': 'Delete',
|
||||
'End': 'End',
|
||||
'Help': 'Help',
|
||||
'Home': 'Home',
|
||||
'Insert': 'Insert',
|
||||
'PageDown': 'PageDown',
|
||||
'PageUp': 'PageUp',
|
||||
Delete: 'Delete',
|
||||
End: 'End',
|
||||
Help: 'Help',
|
||||
Home: 'Home',
|
||||
Insert: 'Insert',
|
||||
PageDown: 'PageDown',
|
||||
PageUp: 'PageUp',
|
||||
|
||||
// 3.1.3. Arrow Pad Section
|
||||
// 3.1.3. Arrow Pad Section
|
||||
|
||||
'ArrowDown': 'ArrowDown',
|
||||
'ArrowLeft': 'ArrowLeft',
|
||||
'ArrowRight': 'ArrowRight',
|
||||
'ArrowUp': 'ArrowUp',
|
||||
ArrowDown: 'ArrowDown',
|
||||
ArrowLeft: 'ArrowLeft',
|
||||
ArrowRight: 'ArrowRight',
|
||||
ArrowUp: 'ArrowUp',
|
||||
|
||||
// 3.1.4. Numpad Section
|
||||
// 3.1.4. Numpad Section
|
||||
|
||||
'NumLock': 'NumLock',
|
||||
'NumpadBackspace': 'Backspace',
|
||||
'NumpadClear': 'Clear',
|
||||
NumLock: 'NumLock',
|
||||
NumpadBackspace: 'Backspace',
|
||||
NumpadClear: 'Clear',
|
||||
|
||||
// 3.1.5. Function Section
|
||||
// 3.1.5. Function Section
|
||||
|
||||
'Escape': 'Escape',
|
||||
'F1': 'F1',
|
||||
'F2': 'F2',
|
||||
'F3': 'F3',
|
||||
'F4': 'F4',
|
||||
'F5': 'F5',
|
||||
'F6': 'F6',
|
||||
'F7': 'F7',
|
||||
'F8': 'F8',
|
||||
'F9': 'F9',
|
||||
'F10': 'F10',
|
||||
'F11': 'F11',
|
||||
'F12': 'F12',
|
||||
'F13': 'F13',
|
||||
'F14': 'F14',
|
||||
'F15': 'F15',
|
||||
'F16': 'F16',
|
||||
'F17': 'F17',
|
||||
'F18': 'F18',
|
||||
'F19': 'F19',
|
||||
'F20': 'F20',
|
||||
'F21': 'F21',
|
||||
'F22': 'F22',
|
||||
'F23': 'F23',
|
||||
'F24': 'F24',
|
||||
'F25': 'F25',
|
||||
'F26': 'F26',
|
||||
'F27': 'F27',
|
||||
'F28': 'F28',
|
||||
'F29': 'F29',
|
||||
'F30': 'F30',
|
||||
'F31': 'F31',
|
||||
'F32': 'F32',
|
||||
'F33': 'F33',
|
||||
'F34': 'F34',
|
||||
'F35': 'F35',
|
||||
'PrintScreen': 'PrintScreen',
|
||||
'ScrollLock': 'ScrollLock',
|
||||
'Pause': 'Pause',
|
||||
Escape: 'Escape',
|
||||
F1: 'F1',
|
||||
F2: 'F2',
|
||||
F3: 'F3',
|
||||
F4: 'F4',
|
||||
F5: 'F5',
|
||||
F6: 'F6',
|
||||
F7: 'F7',
|
||||
F8: 'F8',
|
||||
F9: 'F9',
|
||||
F10: 'F10',
|
||||
F11: 'F11',
|
||||
F12: 'F12',
|
||||
F13: 'F13',
|
||||
F14: 'F14',
|
||||
F15: 'F15',
|
||||
F16: 'F16',
|
||||
F17: 'F17',
|
||||
F18: 'F18',
|
||||
F19: 'F19',
|
||||
F20: 'F20',
|
||||
F21: 'F21',
|
||||
F22: 'F22',
|
||||
F23: 'F23',
|
||||
F24: 'F24',
|
||||
F25: 'F25',
|
||||
F26: 'F26',
|
||||
F27: 'F27',
|
||||
F28: 'F28',
|
||||
F29: 'F29',
|
||||
F30: 'F30',
|
||||
F31: 'F31',
|
||||
F32: 'F32',
|
||||
F33: 'F33',
|
||||
F34: 'F34',
|
||||
F35: 'F35',
|
||||
PrintScreen: 'PrintScreen',
|
||||
ScrollLock: 'ScrollLock',
|
||||
Pause: 'Pause',
|
||||
|
||||
// 3.1.6. Media Keys
|
||||
// 3.1.6. Media Keys
|
||||
|
||||
'BrowserBack': 'BrowserBack',
|
||||
'BrowserFavorites': 'BrowserFavorites',
|
||||
'BrowserForward': 'BrowserForward',
|
||||
'BrowserHome': 'BrowserHome',
|
||||
'BrowserRefresh': 'BrowserRefresh',
|
||||
'BrowserSearch': 'BrowserSearch',
|
||||
'BrowserStop': 'BrowserStop',
|
||||
'Eject': 'Eject',
|
||||
'LaunchApp1': 'LaunchMyComputer',
|
||||
'LaunchApp2': 'LaunchCalendar',
|
||||
'LaunchMail': 'LaunchMail',
|
||||
'MediaPlayPause': 'MediaPlay',
|
||||
'MediaStop': 'MediaStop',
|
||||
'MediaTrackNext': 'MediaTrackNext',
|
||||
'MediaTrackPrevious': 'MediaTrackPrevious',
|
||||
'Power': 'Power',
|
||||
'Sleep': 'Sleep',
|
||||
'AudioVolumeDown': 'AudioVolumeDown',
|
||||
'AudioVolumeMute': 'AudioVolumeMute',
|
||||
'AudioVolumeUp': 'AudioVolumeUp',
|
||||
'WakeUp': 'WakeUp',
|
||||
BrowserBack: 'BrowserBack',
|
||||
BrowserFavorites: 'BrowserFavorites',
|
||||
BrowserForward: 'BrowserForward',
|
||||
BrowserHome: 'BrowserHome',
|
||||
BrowserRefresh: 'BrowserRefresh',
|
||||
BrowserSearch: 'BrowserSearch',
|
||||
BrowserStop: 'BrowserStop',
|
||||
Eject: 'Eject',
|
||||
LaunchApp1: 'LaunchMyComputer',
|
||||
LaunchApp2: 'LaunchCalendar',
|
||||
LaunchMail: 'LaunchMail',
|
||||
MediaPlayPause: 'MediaPlay',
|
||||
MediaStop: 'MediaStop',
|
||||
MediaTrackNext: 'MediaTrackNext',
|
||||
MediaTrackPrevious: 'MediaTrackPrevious',
|
||||
Power: 'Power',
|
||||
Sleep: 'Sleep',
|
||||
AudioVolumeDown: 'AudioVolumeDown',
|
||||
AudioVolumeMute: 'AudioVolumeMute',
|
||||
AudioVolumeUp: 'AudioVolumeUp',
|
||||
WakeUp: 'WakeUp',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,364 +7,365 @@
|
|||
|
||||
import * as Log from '../util/logging.js';
|
||||
import { stopEvent } from '../util/events.js';
|
||||
import * as KeyboardUtil from "./util.js";
|
||||
import KeyTable from "./keysym.js";
|
||||
import * as browser from "../util/browser.js";
|
||||
import * as KeyboardUtil from './util.js';
|
||||
import KeyTable from './keysym.js';
|
||||
import * as browser from '../util/browser.js';
|
||||
|
||||
//
|
||||
// Keyboard event handler
|
||||
//
|
||||
|
||||
export default class Keyboard {
|
||||
constructor(target) {
|
||||
this._target = target || null;
|
||||
constructor(target) {
|
||||
this._target = target || null;
|
||||
|
||||
this._keyDownList = {}; // List of depressed keys
|
||||
// (even if they are happy)
|
||||
this._pendingKey = null; // Key waiting for keypress
|
||||
this._altGrArmed = false; // Windows AltGr detection
|
||||
this._keyDownList = {}; // List of depressed keys
|
||||
// (even if they are happy)
|
||||
this._pendingKey = null; // Key waiting for keypress
|
||||
this._altGrArmed = false; // Windows AltGr detection
|
||||
|
||||
// keep these here so we can refer to them later
|
||||
this._eventHandlers = {
|
||||
'keyup': this._handleKeyUp.bind(this),
|
||||
'keydown': this._handleKeyDown.bind(this),
|
||||
'keypress': this._handleKeyPress.bind(this),
|
||||
'blur': this._allKeysUp.bind(this),
|
||||
'checkalt': this._checkAlt.bind(this),
|
||||
};
|
||||
// keep these here so we can refer to them later
|
||||
this._eventHandlers = {
|
||||
keyup: this._handleKeyUp.bind(this),
|
||||
keydown: this._handleKeyDown.bind(this),
|
||||
keypress: this._handleKeyPress.bind(this),
|
||||
blur: this._allKeysUp.bind(this),
|
||||
checkalt: this._checkAlt.bind(this),
|
||||
};
|
||||
|
||||
// ===== EVENT HANDLERS =====
|
||||
// ===== EVENT HANDLERS =====
|
||||
|
||||
this.onkeyevent = () => {}; // Handler for key press/release
|
||||
this.onkeyevent = () => {}; // Handler for key press/release
|
||||
}
|
||||
|
||||
// ===== PRIVATE METHODS =====
|
||||
|
||||
_sendKeyEvent(keysym, code, down) {
|
||||
if (down) {
|
||||
this._keyDownList[code] = keysym;
|
||||
} else {
|
||||
// Do we really think this key is down?
|
||||
if (!(code in this._keyDownList)) {
|
||||
return;
|
||||
}
|
||||
delete this._keyDownList[code];
|
||||
}
|
||||
|
||||
// ===== PRIVATE METHODS =====
|
||||
Log.Debug('onkeyevent ' + (down ? 'down' : 'up')
|
||||
+ ', keysym: ' + keysym, ', code: ' + code);
|
||||
this.onkeyevent(keysym, code, down);
|
||||
}
|
||||
|
||||
_sendKeyEvent(keysym, code, down) {
|
||||
if (down) {
|
||||
this._keyDownList[code] = keysym;
|
||||
} else {
|
||||
// Do we really think this key is down?
|
||||
if (!(code in this._keyDownList)) {
|
||||
return;
|
||||
}
|
||||
delete this._keyDownList[code];
|
||||
}
|
||||
|
||||
Log.Debug("onkeyevent " + (down ? "down" : "up") +
|
||||
", keysym: " + keysym, ", code: " + code);
|
||||
this.onkeyevent(keysym, code, down);
|
||||
_getKeyCode(e) {
|
||||
const code = KeyboardUtil.getKeycode(e);
|
||||
if (code !== 'Unidentified') {
|
||||
return code;
|
||||
}
|
||||
|
||||
_getKeyCode(e) {
|
||||
const code = KeyboardUtil.getKeycode(e);
|
||||
if (code !== 'Unidentified') {
|
||||
return code;
|
||||
}
|
||||
|
||||
// Unstable, but we don't have anything else to go on
|
||||
// (don't use it for 'keypress' events thought since
|
||||
// WebKit sets it to the same as charCode)
|
||||
if (e.keyCode && (e.type !== 'keypress')) {
|
||||
// 229 is used for composition events
|
||||
if (e.keyCode !== 229) {
|
||||
return 'Platform' + e.keyCode;
|
||||
}
|
||||
}
|
||||
|
||||
// A precursor to the final DOM3 standard. Unfortunately it
|
||||
// is not layout independent, so it is as bad as using keyCode
|
||||
if (e.keyIdentifier) {
|
||||
// Non-character key?
|
||||
if (e.keyIdentifier.substr(0, 2) !== 'U+') {
|
||||
return e.keyIdentifier;
|
||||
}
|
||||
|
||||
const codepoint = parseInt(e.keyIdentifier.substr(2), 16);
|
||||
const char = String.fromCharCode(codepoint).toUpperCase();
|
||||
|
||||
return 'Platform' + char.charCodeAt();
|
||||
}
|
||||
|
||||
return 'Unidentified';
|
||||
// Unstable, but we don't have anything else to go on
|
||||
// (don't use it for 'keypress' events thought since
|
||||
// WebKit sets it to the same as charCode)
|
||||
if (e.keyCode && (e.type !== 'keypress')) {
|
||||
// 229 is used for composition events
|
||||
if (e.keyCode !== 229) {
|
||||
return 'Platform' + e.keyCode;
|
||||
}
|
||||
}
|
||||
|
||||
_handleKeyDown(e) {
|
||||
const code = this._getKeyCode(e);
|
||||
let keysym = KeyboardUtil.getKeysym(e);
|
||||
// A precursor to the final DOM3 standard. Unfortunately it
|
||||
// is not layout independent, so it is as bad as using keyCode
|
||||
if (e.keyIdentifier) {
|
||||
// Non-character key?
|
||||
if (e.keyIdentifier.substr(0, 2) !== 'U+') {
|
||||
return e.keyIdentifier;
|
||||
}
|
||||
|
||||
// Windows doesn't have a proper AltGr, but handles it using
|
||||
// fake Ctrl+Alt. However the remote end might not be Windows,
|
||||
// so we need to merge those in to a single AltGr event. We
|
||||
// detect this case by seeing the two key events directly after
|
||||
// each other with a very short time between them (<50ms).
|
||||
if (this._altGrArmed) {
|
||||
this._altGrArmed = false;
|
||||
clearTimeout(this._altGrTimeout);
|
||||
const codepoint = parseInt(e.keyIdentifier.substr(2), 16);
|
||||
const char = String.fromCharCode(codepoint).toUpperCase();
|
||||
|
||||
if ((code === "AltRight") &&
|
||||
((e.timeStamp - this._altGrCtrlTime) < 50)) {
|
||||
// FIXME: We fail to detect this if either Ctrl key is
|
||||
// first manually pressed as Windows then no
|
||||
// longer sends the fake Ctrl down event. It
|
||||
// does however happily send real Ctrl events
|
||||
// even when AltGr is already down. Some
|
||||
// browsers detect this for us though and set the
|
||||
// key to "AltGraph".
|
||||
keysym = KeyTable.XK_ISO_Level3_Shift;
|
||||
} else {
|
||||
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||
}
|
||||
}
|
||||
return 'Platform' + char.charCodeAt();
|
||||
}
|
||||
|
||||
// We cannot handle keys we cannot track, but we also need
|
||||
// to deal with virtual keyboards which omit key info
|
||||
// (iOS omits tracking info on keyup events, which forces us to
|
||||
// special treat that platform here)
|
||||
if ((code === 'Unidentified') || browser.isIOS()) {
|
||||
if (keysym) {
|
||||
// If it's a virtual keyboard then it should be
|
||||
// sufficient to just send press and release right
|
||||
// after each other
|
||||
this._sendKeyEvent(keysym, code, true);
|
||||
this._sendKeyEvent(keysym, code, false);
|
||||
}
|
||||
return 'Unidentified';
|
||||
}
|
||||
|
||||
stopEvent(e);
|
||||
return;
|
||||
}
|
||||
_handleKeyDown(e) {
|
||||
const code = this._getKeyCode(e);
|
||||
let keysym = KeyboardUtil.getKeysym(e);
|
||||
|
||||
// Alt behaves more like AltGraph on macOS, so shuffle the
|
||||
// keys around a bit to make things more sane for the remote
|
||||
// server. This method is used by RealVNC and TigerVNC (and
|
||||
// possibly others).
|
||||
if (browser.isMac()) {
|
||||
switch (keysym) {
|
||||
case KeyTable.XK_Super_L:
|
||||
keysym = KeyTable.XK_Alt_L;
|
||||
break;
|
||||
case KeyTable.XK_Super_R:
|
||||
keysym = KeyTable.XK_Super_L;
|
||||
break;
|
||||
case KeyTable.XK_Alt_L:
|
||||
keysym = KeyTable.XK_Mode_switch;
|
||||
break;
|
||||
case KeyTable.XK_Alt_R:
|
||||
keysym = KeyTable.XK_ISO_Level3_Shift;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Windows doesn't have a proper AltGr, but handles it using
|
||||
// fake Ctrl+Alt. However the remote end might not be Windows,
|
||||
// so we need to merge those in to a single AltGr event. We
|
||||
// detect this case by seeing the two key events directly after
|
||||
// each other with a very short time between them (<50ms).
|
||||
if (this._altGrArmed) {
|
||||
this._altGrArmed = false;
|
||||
clearTimeout(this._altGrTimeout);
|
||||
|
||||
// Is this key already pressed? If so, then we must use the
|
||||
// same keysym or we'll confuse the server
|
||||
if (code in this._keyDownList) {
|
||||
keysym = this._keyDownList[code];
|
||||
}
|
||||
|
||||
// macOS doesn't send proper key events for modifiers, only
|
||||
// state change events. That gets extra confusing for CapsLock
|
||||
// which toggles on each press, but not on release. So pretend
|
||||
// it was a quick press and release of the button.
|
||||
if (browser.isMac() && (code === 'CapsLock')) {
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
||||
stopEvent(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is a legacy browser then we'll need to wait for
|
||||
// a keypress event as well
|
||||
// (IE and Edge has a broken KeyboardEvent.key, so we can't
|
||||
// just check for the presence of that field)
|
||||
if (!keysym && (!e.key || browser.isIE() || browser.isEdge())) {
|
||||
this._pendingKey = code;
|
||||
// However we might not get a keypress event if the key
|
||||
// is non-printable, which needs some special fallback
|
||||
// handling
|
||||
setTimeout(this._handleKeyPressTimeout.bind(this), 10, e);
|
||||
return;
|
||||
}
|
||||
|
||||
this._pendingKey = null;
|
||||
stopEvent(e);
|
||||
|
||||
// Possible start of AltGr sequence? (see above)
|
||||
if ((code === "ControlLeft") && browser.isWindows() &&
|
||||
!("ControlLeft" in this._keyDownList)) {
|
||||
this._altGrArmed = true;
|
||||
this._altGrTimeout = setTimeout(this._handleAltGrTimeout.bind(this), 100);
|
||||
this._altGrCtrlTime = e.timeStamp;
|
||||
return;
|
||||
}
|
||||
if ((code === 'AltRight')
|
||||
&& ((e.timeStamp - this._altGrCtrlTime) < 50)) {
|
||||
// FIXME: We fail to detect this if either Ctrl key is
|
||||
// first manually pressed as Windows then no
|
||||
// longer sends the fake Ctrl down event. It
|
||||
// does however happily send real Ctrl events
|
||||
// even when AltGr is already down. Some
|
||||
// browsers detect this for us though and set the
|
||||
// key to "AltGraph".
|
||||
keysym = KeyTable.XK_ISO_Level3_Shift;
|
||||
} else {
|
||||
this._sendKeyEvent(KeyTable.XK_Control_L, 'ControlLeft', true);
|
||||
}
|
||||
}
|
||||
|
||||
// We cannot handle keys we cannot track, but we also need
|
||||
// to deal with virtual keyboards which omit key info
|
||||
// (iOS omits tracking info on keyup events, which forces us to
|
||||
// special treat that platform here)
|
||||
if ((code === 'Unidentified') || browser.isIOS()) {
|
||||
if (keysym) {
|
||||
// If it's a virtual keyboard then it should be
|
||||
// sufficient to just send press and release right
|
||||
// after each other
|
||||
this._sendKeyEvent(keysym, code, true);
|
||||
this._sendKeyEvent(keysym, code, false);
|
||||
}
|
||||
|
||||
stopEvent(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy event for browsers without code/key
|
||||
_handleKeyPress(e) {
|
||||
stopEvent(e);
|
||||
|
||||
// Are we expecting a keypress?
|
||||
if (this._pendingKey === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = this._getKeyCode(e);
|
||||
const keysym = KeyboardUtil.getKeysym(e);
|
||||
|
||||
// The key we were waiting for?
|
||||
if ((code !== 'Unidentified') && (code != this._pendingKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
code = this._pendingKey;
|
||||
this._pendingKey = null;
|
||||
|
||||
if (!keysym) {
|
||||
Log.Info('keypress with no keysym:', e);
|
||||
return;
|
||||
}
|
||||
|
||||
this._sendKeyEvent(keysym, code, true);
|
||||
// Alt behaves more like AltGraph on macOS, so shuffle the
|
||||
// keys around a bit to make things more sane for the remote
|
||||
// server. This method is used by RealVNC and TigerVNC (and
|
||||
// possibly others).
|
||||
if (browser.isMac()) {
|
||||
switch (keysym) {
|
||||
case KeyTable.XK_Super_L:
|
||||
keysym = KeyTable.XK_Alt_L;
|
||||
break;
|
||||
case KeyTable.XK_Super_R:
|
||||
keysym = KeyTable.XK_Super_L;
|
||||
break;
|
||||
case KeyTable.XK_Alt_L:
|
||||
keysym = KeyTable.XK_Mode_switch;
|
||||
break;
|
||||
case KeyTable.XK_Alt_R:
|
||||
keysym = KeyTable.XK_ISO_Level3_Shift;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_handleKeyPressTimeout(e) {
|
||||
// Did someone manage to sort out the key already?
|
||||
if (this._pendingKey === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let keysym;
|
||||
|
||||
const code = this._pendingKey;
|
||||
this._pendingKey = null;
|
||||
|
||||
// We have no way of knowing the proper keysym with the
|
||||
// information given, but the following are true for most
|
||||
// layouts
|
||||
if ((e.keyCode >= 0x30) && (e.keyCode <= 0x39)) {
|
||||
// Digit
|
||||
keysym = e.keyCode;
|
||||
} else if ((e.keyCode >= 0x41) && (e.keyCode <= 0x5a)) {
|
||||
// Character (A-Z)
|
||||
let char = String.fromCharCode(e.keyCode);
|
||||
// A feeble attempt at the correct case
|
||||
if (e.shiftKey)
|
||||
char = char.toUpperCase();
|
||||
else
|
||||
char = char.toLowerCase();
|
||||
keysym = char.charCodeAt();
|
||||
} else {
|
||||
// Unknown, give up
|
||||
keysym = 0;
|
||||
}
|
||||
|
||||
this._sendKeyEvent(keysym, code, true);
|
||||
// Is this key already pressed? If so, then we must use the
|
||||
// same keysym or we'll confuse the server
|
||||
if (code in this._keyDownList) {
|
||||
keysym = this._keyDownList[code];
|
||||
}
|
||||
|
||||
_handleKeyUp(e) {
|
||||
stopEvent(e);
|
||||
|
||||
const code = this._getKeyCode(e);
|
||||
|
||||
// We can't get a release in the middle of an AltGr sequence, so
|
||||
// abort that detection
|
||||
if (this._altGrArmed) {
|
||||
this._altGrArmed = false;
|
||||
clearTimeout(this._altGrTimeout);
|
||||
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||
}
|
||||
|
||||
// See comment in _handleKeyDown()
|
||||
if (browser.isMac() && (code === 'CapsLock')) {
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
||||
return;
|
||||
}
|
||||
|
||||
this._sendKeyEvent(this._keyDownList[code], code, false);
|
||||
// macOS doesn't send proper key events for modifiers, only
|
||||
// state change events. That gets extra confusing for CapsLock
|
||||
// which toggles on each press, but not on release. So pretend
|
||||
// it was a quick press and release of the button.
|
||||
if (browser.isMac() && (code === 'CapsLock')) {
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
||||
stopEvent(e);
|
||||
return;
|
||||
}
|
||||
|
||||
_handleAltGrTimeout() {
|
||||
this._altGrArmed = false;
|
||||
clearTimeout(this._altGrTimeout);
|
||||
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||
// If this is a legacy browser then we'll need to wait for
|
||||
// a keypress event as well
|
||||
// (IE and Edge has a broken KeyboardEvent.key, so we can't
|
||||
// just check for the presence of that field)
|
||||
if (!keysym && (!e.key || browser.isIE() || browser.isEdge())) {
|
||||
this._pendingKey = code;
|
||||
// However we might not get a keypress event if the key
|
||||
// is non-printable, which needs some special fallback
|
||||
// handling
|
||||
setTimeout(this._handleKeyPressTimeout.bind(this), 10, e);
|
||||
return;
|
||||
}
|
||||
|
||||
_allKeysUp() {
|
||||
Log.Debug(">> Keyboard.allKeysUp");
|
||||
for (let code in this._keyDownList) {
|
||||
this._sendKeyEvent(this._keyDownList[code], code, false);
|
||||
}
|
||||
Log.Debug("<< Keyboard.allKeysUp");
|
||||
this._pendingKey = null;
|
||||
stopEvent(e);
|
||||
|
||||
// Possible start of AltGr sequence? (see above)
|
||||
if ((code === 'ControlLeft') && browser.isWindows()
|
||||
&& !('ControlLeft' in this._keyDownList)) {
|
||||
this._altGrArmed = true;
|
||||
this._altGrTimeout = setTimeout(this._handleAltGrTimeout.bind(this), 100);
|
||||
this._altGrCtrlTime = e.timeStamp;
|
||||
return;
|
||||
}
|
||||
|
||||
// Firefox Alt workaround, see below
|
||||
_checkAlt(e) {
|
||||
if (e.altKey) {
|
||||
return;
|
||||
}
|
||||
this._sendKeyEvent(keysym, code, true);
|
||||
}
|
||||
|
||||
const target = this._target;
|
||||
const downList = this._keyDownList;
|
||||
['AltLeft', 'AltRight'].forEach((code) => {
|
||||
if (!(code in downList)) {
|
||||
return;
|
||||
}
|
||||
// Legacy event for browsers without code/key
|
||||
_handleKeyPress(e) {
|
||||
stopEvent(e);
|
||||
|
||||
const event = new KeyboardEvent('keyup',
|
||||
{ key: downList[code],
|
||||
code: code });
|
||||
target.dispatchEvent(event);
|
||||
// Are we expecting a keypress?
|
||||
if (this._pendingKey === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let code = this._getKeyCode(e);
|
||||
const keysym = KeyboardUtil.getKeysym(e);
|
||||
|
||||
// The key we were waiting for?
|
||||
if ((code !== 'Unidentified') && (code != this._pendingKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
code = this._pendingKey;
|
||||
this._pendingKey = null;
|
||||
|
||||
if (!keysym) {
|
||||
Log.Info('keypress with no keysym:', e);
|
||||
return;
|
||||
}
|
||||
|
||||
this._sendKeyEvent(keysym, code, true);
|
||||
}
|
||||
|
||||
_handleKeyPressTimeout(e) {
|
||||
// Did someone manage to sort out the key already?
|
||||
if (this._pendingKey === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let keysym;
|
||||
|
||||
const code = this._pendingKey;
|
||||
this._pendingKey = null;
|
||||
|
||||
// We have no way of knowing the proper keysym with the
|
||||
// information given, but the following are true for most
|
||||
// layouts
|
||||
if ((e.keyCode >= 0x30) && (e.keyCode <= 0x39)) {
|
||||
// Digit
|
||||
keysym = e.keyCode;
|
||||
} else if ((e.keyCode >= 0x41) && (e.keyCode <= 0x5a)) {
|
||||
// Character (A-Z)
|
||||
let char = String.fromCharCode(e.keyCode);
|
||||
// A feeble attempt at the correct case
|
||||
if (e.shiftKey) char = char.toUpperCase();
|
||||
else char = char.toLowerCase();
|
||||
keysym = char.charCodeAt();
|
||||
} else {
|
||||
// Unknown, give up
|
||||
keysym = 0;
|
||||
}
|
||||
|
||||
this._sendKeyEvent(keysym, code, true);
|
||||
}
|
||||
|
||||
_handleKeyUp(e) {
|
||||
stopEvent(e);
|
||||
|
||||
const code = this._getKeyCode(e);
|
||||
|
||||
// We can't get a release in the middle of an AltGr sequence, so
|
||||
// abort that detection
|
||||
if (this._altGrArmed) {
|
||||
this._altGrArmed = false;
|
||||
clearTimeout(this._altGrTimeout);
|
||||
this._sendKeyEvent(KeyTable.XK_Control_L, 'ControlLeft', true);
|
||||
}
|
||||
|
||||
// See comment in _handleKeyDown()
|
||||
if (browser.isMac() && (code === 'CapsLock')) {
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
|
||||
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
|
||||
return;
|
||||
}
|
||||
|
||||
this._sendKeyEvent(this._keyDownList[code], code, false);
|
||||
}
|
||||
|
||||
_handleAltGrTimeout() {
|
||||
this._altGrArmed = false;
|
||||
clearTimeout(this._altGrTimeout);
|
||||
this._sendKeyEvent(KeyTable.XK_Control_L, 'ControlLeft', true);
|
||||
}
|
||||
|
||||
_allKeysUp() {
|
||||
Log.Debug('>> Keyboard.allKeysUp');
|
||||
for (let code in this._keyDownList) {
|
||||
this._sendKeyEvent(this._keyDownList[code], code, false);
|
||||
}
|
||||
Log.Debug('<< Keyboard.allKeysUp');
|
||||
}
|
||||
|
||||
// Firefox Alt workaround, see below
|
||||
_checkAlt(e) {
|
||||
if (e.altKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = this._target;
|
||||
const downList = this._keyDownList;
|
||||
['AltLeft', 'AltRight'].forEach((code) => {
|
||||
if (!(code in downList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const event = new KeyboardEvent('keyup',
|
||||
{
|
||||
key: downList[code],
|
||||
code: code
|
||||
});
|
||||
target.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
|
||||
// ===== PUBLIC METHODS =====
|
||||
|
||||
grab() {
|
||||
// Log.Debug(">> Keyboard.grab");
|
||||
|
||||
this._target.addEventListener('keydown', this._eventHandlers.keydown);
|
||||
this._target.addEventListener('keyup', this._eventHandlers.keyup);
|
||||
this._target.addEventListener('keypress', this._eventHandlers.keypress);
|
||||
|
||||
// Release (key up) if window loses focus
|
||||
window.addEventListener('blur', this._eventHandlers.blur);
|
||||
|
||||
// Firefox has broken handling of Alt, so we need to poll as
|
||||
// best we can for releases (still doesn't prevent the menu
|
||||
// from popping up though as we can't call preventDefault())
|
||||
if (browser.isWindows() && browser.isFirefox()) {
|
||||
const handler = this._eventHandlers.checkalt;
|
||||
['mousedown', 'mouseup', 'mousemove', 'wheel',
|
||||
'touchstart', 'touchend', 'touchmove',
|
||||
'keydown', 'keyup'].forEach(type => document.addEventListener(type, handler,
|
||||
{
|
||||
capture: true,
|
||||
passive: true
|
||||
}));
|
||||
}
|
||||
|
||||
// ===== PUBLIC METHODS =====
|
||||
// Log.Debug("<< Keyboard.grab");
|
||||
}
|
||||
|
||||
grab() {
|
||||
//Log.Debug(">> Keyboard.grab");
|
||||
ungrab() {
|
||||
// Log.Debug(">> Keyboard.ungrab");
|
||||
|
||||
this._target.addEventListener('keydown', this._eventHandlers.keydown);
|
||||
this._target.addEventListener('keyup', this._eventHandlers.keyup);
|
||||
this._target.addEventListener('keypress', this._eventHandlers.keypress);
|
||||
|
||||
// Release (key up) if window loses focus
|
||||
window.addEventListener('blur', this._eventHandlers.blur);
|
||||
|
||||
// Firefox has broken handling of Alt, so we need to poll as
|
||||
// best we can for releases (still doesn't prevent the menu
|
||||
// from popping up though as we can't call preventDefault())
|
||||
if (browser.isWindows() && browser.isFirefox()) {
|
||||
const handler = this._eventHandlers.checkalt;
|
||||
['mousedown', 'mouseup', 'mousemove', 'wheel',
|
||||
'touchstart', 'touchend', 'touchmove',
|
||||
'keydown', 'keyup'].forEach(type =>
|
||||
document.addEventListener(type, handler,
|
||||
{ capture: true,
|
||||
passive: true }));
|
||||
}
|
||||
|
||||
//Log.Debug("<< Keyboard.grab");
|
||||
if (browser.isWindows() && browser.isFirefox()) {
|
||||
const handler = this._eventHandlers.checkalt;
|
||||
['mousedown', 'mouseup', 'mousemove', 'wheel',
|
||||
'touchstart', 'touchend', 'touchmove',
|
||||
'keydown', 'keyup'].forEach(type => document.removeEventListener(type, handler));
|
||||
}
|
||||
|
||||
ungrab() {
|
||||
//Log.Debug(">> Keyboard.ungrab");
|
||||
this._target.removeEventListener('keydown', this._eventHandlers.keydown);
|
||||
this._target.removeEventListener('keyup', this._eventHandlers.keyup);
|
||||
this._target.removeEventListener('keypress', this._eventHandlers.keypress);
|
||||
window.removeEventListener('blur', this._eventHandlers.blur);
|
||||
|
||||
if (browser.isWindows() && browser.isFirefox()) {
|
||||
const handler = this._eventHandlers.checkalt;
|
||||
['mousedown', 'mouseup', 'mousemove', 'wheel',
|
||||
'touchstart', 'touchend', 'touchmove',
|
||||
'keydown', 'keyup'].forEach(type => document.removeEventListener(type, handler));
|
||||
}
|
||||
// Release (key up) all keys that are in a down state
|
||||
this._allKeysUp();
|
||||
|
||||
this._target.removeEventListener('keydown', this._eventHandlers.keydown);
|
||||
this._target.removeEventListener('keyup', this._eventHandlers.keyup);
|
||||
this._target.removeEventListener('keypress', this._eventHandlers.keypress);
|
||||
window.removeEventListener('blur', this._eventHandlers.blur);
|
||||
|
||||
// Release (key up) all keys that are in a down state
|
||||
this._allKeysUp();
|
||||
|
||||
//Log.Debug(">> Keyboard.ungrab");
|
||||
}
|
||||
// Log.Debug(">> Keyboard.ungrab");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1130
core/input/keysym.js
1130
core/input/keysym.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -14,268 +14,264 @@ const WHEEL_STEP_TIMEOUT = 50; // ms
|
|||
const WHEEL_LINE_HEIGHT = 19;
|
||||
|
||||
export default class Mouse {
|
||||
constructor(target) {
|
||||
this._target = target || document;
|
||||
constructor(target) {
|
||||
this._target = target || document;
|
||||
|
||||
this._doubleClickTimer = null;
|
||||
this._lastTouchPos = null;
|
||||
this._doubleClickTimer = null;
|
||||
this._lastTouchPos = null;
|
||||
|
||||
this._pos = null;
|
||||
this._wheelStepXTimer = null;
|
||||
this._wheelStepYTimer = null;
|
||||
this._accumulatedWheelDeltaX = 0;
|
||||
this._accumulatedWheelDeltaY = 0;
|
||||
this._pos = null;
|
||||
this._wheelStepXTimer = null;
|
||||
this._wheelStepYTimer = null;
|
||||
this._accumulatedWheelDeltaX = 0;
|
||||
this._accumulatedWheelDeltaY = 0;
|
||||
|
||||
this._eventHandlers = {
|
||||
'mousedown': this._handleMouseDown.bind(this),
|
||||
'mouseup': this._handleMouseUp.bind(this),
|
||||
'mousemove': this._handleMouseMove.bind(this),
|
||||
'mousewheel': this._handleMouseWheel.bind(this),
|
||||
'mousedisable': this._handleMouseDisable.bind(this)
|
||||
};
|
||||
this._eventHandlers = {
|
||||
mousedown: this._handleMouseDown.bind(this),
|
||||
mouseup: this._handleMouseUp.bind(this),
|
||||
mousemove: this._handleMouseMove.bind(this),
|
||||
mousewheel: this._handleMouseWheel.bind(this),
|
||||
mousedisable: this._handleMouseDisable.bind(this)
|
||||
};
|
||||
|
||||
// ===== PROPERTIES =====
|
||||
// ===== PROPERTIES =====
|
||||
|
||||
this.touchButton = 1; // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
|
||||
this.touchButton = 1; // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
|
||||
|
||||
// ===== EVENT HANDLERS =====
|
||||
// ===== EVENT HANDLERS =====
|
||||
|
||||
this.onmousebutton = () => {}; // Handler for mouse button click/release
|
||||
this.onmousemove = () => {}; // Handler for mouse movement
|
||||
}
|
||||
this.onmousebutton = () => {}; // Handler for mouse button click/release
|
||||
this.onmousemove = () => {}; // Handler for mouse movement
|
||||
}
|
||||
|
||||
// ===== PRIVATE METHODS =====
|
||||
// ===== PRIVATE METHODS =====
|
||||
|
||||
_resetDoubleClickTimer() {
|
||||
this._doubleClickTimer = null;
|
||||
}
|
||||
_resetDoubleClickTimer() {
|
||||
this._doubleClickTimer = null;
|
||||
}
|
||||
|
||||
_handleMouseButton(e, down) {
|
||||
this._updateMousePosition(e);
|
||||
let pos = this._pos;
|
||||
_handleMouseButton(e, down) {
|
||||
this._updateMousePosition(e);
|
||||
let pos = this._pos;
|
||||
|
||||
let bmask;
|
||||
if (e.touches || e.changedTouches) {
|
||||
// Touch device
|
||||
let bmask;
|
||||
if (e.touches || e.changedTouches) {
|
||||
// Touch device
|
||||
|
||||
// When two touches occur within 500 ms of each other and are
|
||||
// close enough together a double click is triggered.
|
||||
if (down == 1) {
|
||||
if (this._doubleClickTimer === null) {
|
||||
this._lastTouchPos = pos;
|
||||
} else {
|
||||
clearTimeout(this._doubleClickTimer);
|
||||
|
||||
// When the distance between the two touches is small enough
|
||||
// force the position of the latter touch to the position of
|
||||
// the first.
|
||||
|
||||
const xs = this._lastTouchPos.x - pos.x;
|
||||
const ys = this._lastTouchPos.y - pos.y;
|
||||
const d = Math.sqrt((xs * xs) + (ys * ys));
|
||||
|
||||
// The goal is to trigger on a certain physical width, the
|
||||
// devicePixelRatio brings us a bit closer but is not optimal.
|
||||
const threshold = 20 * (window.devicePixelRatio || 1);
|
||||
if (d < threshold) {
|
||||
pos = this._lastTouchPos;
|
||||
}
|
||||
}
|
||||
this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
|
||||
}
|
||||
bmask = this.touchButton;
|
||||
// If bmask is set
|
||||
} else if (e.which) {
|
||||
/* everything except IE */
|
||||
bmask = 1 << e.button;
|
||||
// When two touches occur within 500 ms of each other and are
|
||||
// close enough together a double click is triggered.
|
||||
if (down == 1) {
|
||||
if (this._doubleClickTimer === null) {
|
||||
this._lastTouchPos = pos;
|
||||
} else {
|
||||
/* IE including 9 */
|
||||
bmask = (e.button & 0x1) + // Left
|
||||
(e.button & 0x2) * 2 + // Right
|
||||
(e.button & 0x4) / 2; // Middle
|
||||
clearTimeout(this._doubleClickTimer);
|
||||
|
||||
// When the distance between the two touches is small enough
|
||||
// force the position of the latter touch to the position of
|
||||
// the first.
|
||||
|
||||
const xs = this._lastTouchPos.x - pos.x;
|
||||
const ys = this._lastTouchPos.y - pos.y;
|
||||
const d = Math.sqrt((xs * xs) + (ys * ys));
|
||||
|
||||
// The goal is to trigger on a certain physical width, the
|
||||
// devicePixelRatio brings us a bit closer but is not optimal.
|
||||
const threshold = 20 * (window.devicePixelRatio || 1);
|
||||
if (d < threshold) {
|
||||
pos = this._lastTouchPos;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Debug("onmousebutton " + (down ? "down" : "up") +
|
||||
", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
|
||||
this.onmousebutton(pos.x, pos.y, down, bmask);
|
||||
|
||||
stopEvent(e);
|
||||
this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
|
||||
}
|
||||
bmask = this.touchButton;
|
||||
// If bmask is set
|
||||
} else if (e.which) {
|
||||
/* everything except IE */
|
||||
bmask = 1 << e.button;
|
||||
} else {
|
||||
/* IE including 9 */
|
||||
bmask = (e.button & 0x1) // Left
|
||||
+ (e.button & 0x2) * 2 // Right
|
||||
+ (e.button & 0x4) / 2; // Middle
|
||||
}
|
||||
|
||||
_handleMouseDown(e) {
|
||||
// Touch events have implicit capture
|
||||
if (e.type === "mousedown") {
|
||||
setCapture(this._target);
|
||||
}
|
||||
Log.Debug('onmousebutton ' + (down ? 'down' : 'up')
|
||||
+ ', x: ' + pos.x + ', y: ' + pos.y + ', bmask: ' + bmask);
|
||||
this.onmousebutton(pos.x, pos.y, down, bmask);
|
||||
|
||||
this._handleMouseButton(e, 1);
|
||||
stopEvent(e);
|
||||
}
|
||||
|
||||
_handleMouseDown(e) {
|
||||
// Touch events have implicit capture
|
||||
if (e.type === 'mousedown') {
|
||||
setCapture(this._target);
|
||||
}
|
||||
|
||||
_handleMouseUp(e) {
|
||||
this._handleMouseButton(e, 0);
|
||||
this._handleMouseButton(e, 1);
|
||||
}
|
||||
|
||||
_handleMouseUp(e) {
|
||||
this._handleMouseButton(e, 0);
|
||||
}
|
||||
|
||||
// Mouse wheel events are sent in steps over VNC. This means that the VNC
|
||||
// protocol can't handle a wheel event with specific distance or speed.
|
||||
// Therefor, if we get a lot of small mouse wheel events we combine them.
|
||||
_generateWheelStepX() {
|
||||
if (this._accumulatedWheelDeltaX < 0) {
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5);
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 5);
|
||||
} else if (this._accumulatedWheelDeltaX > 0) {
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 6);
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 6);
|
||||
}
|
||||
|
||||
// Mouse wheel events are sent in steps over VNC. This means that the VNC
|
||||
// protocol can't handle a wheel event with specific distance or speed.
|
||||
// Therefor, if we get a lot of small mouse wheel events we combine them.
|
||||
_generateWheelStepX() {
|
||||
this._accumulatedWheelDeltaX = 0;
|
||||
}
|
||||
|
||||
if (this._accumulatedWheelDeltaX < 0) {
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5);
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 5);
|
||||
} else if (this._accumulatedWheelDeltaX > 0) {
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 6);
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 6);
|
||||
}
|
||||
|
||||
this._accumulatedWheelDeltaX = 0;
|
||||
_generateWheelStepY() {
|
||||
if (this._accumulatedWheelDeltaY < 0) {
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3);
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 3);
|
||||
} else if (this._accumulatedWheelDeltaY > 0) {
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 4);
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 4);
|
||||
}
|
||||
|
||||
_generateWheelStepY() {
|
||||
this._accumulatedWheelDeltaY = 0;
|
||||
}
|
||||
|
||||
if (this._accumulatedWheelDeltaY < 0) {
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3);
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 3);
|
||||
} else if (this._accumulatedWheelDeltaY > 0) {
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 4);
|
||||
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 4);
|
||||
}
|
||||
_resetWheelStepTimers() {
|
||||
window.clearTimeout(this._wheelStepXTimer);
|
||||
window.clearTimeout(this._wheelStepYTimer);
|
||||
this._wheelStepXTimer = null;
|
||||
this._wheelStepYTimer = null;
|
||||
}
|
||||
|
||||
this._accumulatedWheelDeltaY = 0;
|
||||
_handleMouseWheel(e) {
|
||||
this._resetWheelStepTimers();
|
||||
|
||||
this._updateMousePosition(e);
|
||||
|
||||
let dX = e.deltaX;
|
||||
let dY = e.deltaY;
|
||||
|
||||
// Pixel units unless it's non-zero.
|
||||
// Note that if deltamode is line or page won't matter since we aren't
|
||||
// sending the mouse wheel delta to the server anyway.
|
||||
// The difference between pixel and line can be important however since
|
||||
// we have a threshold that can be smaller than the line height.
|
||||
if (e.deltaMode !== 0) {
|
||||
dX *= WHEEL_LINE_HEIGHT;
|
||||
dY *= WHEEL_LINE_HEIGHT;
|
||||
}
|
||||
|
||||
_resetWheelStepTimers() {
|
||||
window.clearTimeout(this._wheelStepXTimer);
|
||||
window.clearTimeout(this._wheelStepYTimer);
|
||||
this._wheelStepXTimer = null;
|
||||
this._wheelStepYTimer = null;
|
||||
this._accumulatedWheelDeltaX += dX;
|
||||
this._accumulatedWheelDeltaY += dY;
|
||||
|
||||
// Generate a mouse wheel step event when the accumulated delta
|
||||
// for one of the axes is large enough.
|
||||
// Small delta events that do not pass the threshold get sent
|
||||
// after a timeout.
|
||||
if (Math.abs(this._accumulatedWheelDeltaX) > WHEEL_STEP) {
|
||||
this._generateWheelStepX();
|
||||
} else {
|
||||
this._wheelStepXTimer = window.setTimeout(this._generateWheelStepX.bind(this),
|
||||
WHEEL_STEP_TIMEOUT);
|
||||
}
|
||||
if (Math.abs(this._accumulatedWheelDeltaY) > WHEEL_STEP) {
|
||||
this._generateWheelStepY();
|
||||
} else {
|
||||
this._wheelStepYTimer = window.setTimeout(this._generateWheelStepY.bind(this),
|
||||
WHEEL_STEP_TIMEOUT);
|
||||
}
|
||||
|
||||
_handleMouseWheel(e) {
|
||||
this._resetWheelStepTimers();
|
||||
stopEvent(e);
|
||||
}
|
||||
|
||||
this._updateMousePosition(e);
|
||||
_handleMouseMove(e) {
|
||||
this._updateMousePosition(e);
|
||||
this.onmousemove(this._pos.x, this._pos.y);
|
||||
stopEvent(e);
|
||||
}
|
||||
|
||||
let dX = e.deltaX;
|
||||
let dY = e.deltaY;
|
||||
|
||||
// Pixel units unless it's non-zero.
|
||||
// Note that if deltamode is line or page won't matter since we aren't
|
||||
// sending the mouse wheel delta to the server anyway.
|
||||
// The difference between pixel and line can be important however since
|
||||
// we have a threshold that can be smaller than the line height.
|
||||
if (e.deltaMode !== 0) {
|
||||
dX *= WHEEL_LINE_HEIGHT;
|
||||
dY *= WHEEL_LINE_HEIGHT;
|
||||
}
|
||||
|
||||
this._accumulatedWheelDeltaX += dX;
|
||||
this._accumulatedWheelDeltaY += dY;
|
||||
|
||||
// Generate a mouse wheel step event when the accumulated delta
|
||||
// for one of the axes is large enough.
|
||||
// Small delta events that do not pass the threshold get sent
|
||||
// after a timeout.
|
||||
if (Math.abs(this._accumulatedWheelDeltaX) > WHEEL_STEP) {
|
||||
this._generateWheelStepX();
|
||||
} else {
|
||||
this._wheelStepXTimer =
|
||||
window.setTimeout(this._generateWheelStepX.bind(this),
|
||||
WHEEL_STEP_TIMEOUT);
|
||||
}
|
||||
if (Math.abs(this._accumulatedWheelDeltaY) > WHEEL_STEP) {
|
||||
this._generateWheelStepY();
|
||||
} else {
|
||||
this._wheelStepYTimer =
|
||||
window.setTimeout(this._generateWheelStepY.bind(this),
|
||||
WHEEL_STEP_TIMEOUT);
|
||||
}
|
||||
|
||||
stopEvent(e);
|
||||
}
|
||||
|
||||
_handleMouseMove(e) {
|
||||
this._updateMousePosition(e);
|
||||
this.onmousemove(this._pos.x, this._pos.y);
|
||||
stopEvent(e);
|
||||
}
|
||||
|
||||
_handleMouseDisable(e) {
|
||||
/*
|
||||
_handleMouseDisable(e) {
|
||||
/*
|
||||
* Stop propagation if inside canvas area
|
||||
* Note: This is only needed for the 'click' event as it fails
|
||||
* to fire properly for the target element so we have
|
||||
* to listen on the document element instead.
|
||||
*/
|
||||
if (e.target == this._target) {
|
||||
stopEvent(e);
|
||||
}
|
||||
if (e.target == this._target) {
|
||||
stopEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Update coordinates relative to target
|
||||
_updateMousePosition(e) {
|
||||
e = getPointerEvent(e);
|
||||
const bounds = this._target.getBoundingClientRect();
|
||||
let x;
|
||||
let y;
|
||||
// Clip to target bounds
|
||||
if (e.clientX < bounds.left) {
|
||||
x = 0;
|
||||
} else if (e.clientX >= bounds.right) {
|
||||
x = bounds.width - 1;
|
||||
} else {
|
||||
x = e.clientX - bounds.left;
|
||||
}
|
||||
if (e.clientY < bounds.top) {
|
||||
y = 0;
|
||||
} else if (e.clientY >= bounds.bottom) {
|
||||
y = bounds.height - 1;
|
||||
} else {
|
||||
y = e.clientY - bounds.top;
|
||||
}
|
||||
this._pos = {x:x, y:y};
|
||||
// Update coordinates relative to target
|
||||
_updateMousePosition(e) {
|
||||
e = getPointerEvent(e);
|
||||
const bounds = this._target.getBoundingClientRect();
|
||||
let x;
|
||||
let y;
|
||||
// Clip to target bounds
|
||||
if (e.clientX < bounds.left) {
|
||||
x = 0;
|
||||
} else if (e.clientX >= bounds.right) {
|
||||
x = bounds.width - 1;
|
||||
} else {
|
||||
x = e.clientX - bounds.left;
|
||||
}
|
||||
if (e.clientY < bounds.top) {
|
||||
y = 0;
|
||||
} else if (e.clientY >= bounds.bottom) {
|
||||
y = bounds.height - 1;
|
||||
} else {
|
||||
y = e.clientY - bounds.top;
|
||||
}
|
||||
this._pos = { x: x, y: y };
|
||||
}
|
||||
|
||||
// ===== PUBLIC METHODS =====
|
||||
// ===== PUBLIC METHODS =====
|
||||
|
||||
grab() {
|
||||
const c = this._target;
|
||||
grab() {
|
||||
const c = this._target;
|
||||
|
||||
if (isTouchDevice) {
|
||||
c.addEventListener('touchstart', this._eventHandlers.mousedown);
|
||||
c.addEventListener('touchend', this._eventHandlers.mouseup);
|
||||
c.addEventListener('touchmove', this._eventHandlers.mousemove);
|
||||
}
|
||||
c.addEventListener('mousedown', this._eventHandlers.mousedown);
|
||||
c.addEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
c.addEventListener('mousemove', this._eventHandlers.mousemove);
|
||||
c.addEventListener('wheel', this._eventHandlers.mousewheel);
|
||||
if (isTouchDevice) {
|
||||
c.addEventListener('touchstart', this._eventHandlers.mousedown);
|
||||
c.addEventListener('touchend', this._eventHandlers.mouseup);
|
||||
c.addEventListener('touchmove', this._eventHandlers.mousemove);
|
||||
}
|
||||
c.addEventListener('mousedown', this._eventHandlers.mousedown);
|
||||
c.addEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
c.addEventListener('mousemove', this._eventHandlers.mousemove);
|
||||
c.addEventListener('wheel', this._eventHandlers.mousewheel);
|
||||
|
||||
/* Prevent middle-click pasting (see above for why we bind to document) */
|
||||
document.addEventListener('click', this._eventHandlers.mousedisable);
|
||||
/* Prevent middle-click pasting (see above for why we bind to document) */
|
||||
document.addEventListener('click', this._eventHandlers.mousedisable);
|
||||
|
||||
/* preventDefault() on mousedown doesn't stop this event for some
|
||||
/* preventDefault() on mousedown doesn't stop this event for some
|
||||
reason so we have to explicitly block it */
|
||||
c.addEventListener('contextmenu', this._eventHandlers.mousedisable);
|
||||
c.addEventListener('contextmenu', this._eventHandlers.mousedisable);
|
||||
}
|
||||
|
||||
ungrab() {
|
||||
const c = this._target;
|
||||
|
||||
this._resetWheelStepTimers();
|
||||
|
||||
if (isTouchDevice) {
|
||||
c.removeEventListener('touchstart', this._eventHandlers.mousedown);
|
||||
c.removeEventListener('touchend', this._eventHandlers.mouseup);
|
||||
c.removeEventListener('touchmove', this._eventHandlers.mousemove);
|
||||
}
|
||||
c.removeEventListener('mousedown', this._eventHandlers.mousedown);
|
||||
c.removeEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
c.removeEventListener('mousemove', this._eventHandlers.mousemove);
|
||||
c.removeEventListener('wheel', this._eventHandlers.mousewheel);
|
||||
|
||||
ungrab() {
|
||||
const c = this._target;
|
||||
document.removeEventListener('click', this._eventHandlers.mousedisable);
|
||||
|
||||
this._resetWheelStepTimers();
|
||||
|
||||
if (isTouchDevice) {
|
||||
c.removeEventListener('touchstart', this._eventHandlers.mousedown);
|
||||
c.removeEventListener('touchend', this._eventHandlers.mouseup);
|
||||
c.removeEventListener('touchmove', this._eventHandlers.mousemove);
|
||||
}
|
||||
c.removeEventListener('mousedown', this._eventHandlers.mousedown);
|
||||
c.removeEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
c.removeEventListener('mousemove', this._eventHandlers.mousemove);
|
||||
c.removeEventListener('wheel', this._eventHandlers.mousewheel);
|
||||
|
||||
document.removeEventListener('click', this._eventHandlers.mousedisable);
|
||||
|
||||
c.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
|
||||
}
|
||||
c.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,164 +1,164 @@
|
|||
import keysyms from "./keysymdef.js";
|
||||
import vkeys from "./vkeys.js";
|
||||
import fixedkeys from "./fixedkeys.js";
|
||||
import DOMKeyTable from "./domkeytable.js";
|
||||
import * as browser from "../util/browser.js";
|
||||
import keysyms from './keysymdef.js';
|
||||
import vkeys from './vkeys.js';
|
||||
import fixedkeys from './fixedkeys.js';
|
||||
import DOMKeyTable from './domkeytable.js';
|
||||
import * as browser from '../util/browser.js';
|
||||
|
||||
// Get 'KeyboardEvent.code', handling legacy browsers
|
||||
export function getKeycode(evt){
|
||||
// Are we getting proper key identifiers?
|
||||
// (unfortunately Firefox and Chrome are crappy here and gives
|
||||
// us an empty string on some platforms, rather than leaving it
|
||||
// undefined)
|
||||
if (evt.code) {
|
||||
// Mozilla isn't fully in sync with the spec yet
|
||||
switch (evt.code) {
|
||||
case 'OSLeft': return 'MetaLeft';
|
||||
case 'OSRight': return 'MetaRight';
|
||||
}
|
||||
|
||||
return evt.code;
|
||||
export function getKeycode(evt) {
|
||||
// Are we getting proper key identifiers?
|
||||
// (unfortunately Firefox and Chrome are crappy here and gives
|
||||
// us an empty string on some platforms, rather than leaving it
|
||||
// undefined)
|
||||
if (evt.code) {
|
||||
// Mozilla isn't fully in sync with the spec yet
|
||||
switch (evt.code) {
|
||||
case 'OSLeft': return 'MetaLeft';
|
||||
case 'OSRight': return 'MetaRight';
|
||||
}
|
||||
|
||||
// The de-facto standard is to use Windows Virtual-Key codes
|
||||
// in the 'keyCode' field for non-printable characters. However
|
||||
// Webkit sets it to the same as charCode in 'keypress' events.
|
||||
if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
|
||||
let code = vkeys[evt.keyCode];
|
||||
return evt.code;
|
||||
}
|
||||
|
||||
// macOS has messed up this code for some reason
|
||||
if (browser.isMac() && (code === 'ContextMenu')) {
|
||||
code = 'MetaRight';
|
||||
}
|
||||
// The de-facto standard is to use Windows Virtual-Key codes
|
||||
// in the 'keyCode' field for non-printable characters. However
|
||||
// Webkit sets it to the same as charCode in 'keypress' events.
|
||||
if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
|
||||
let code = vkeys[evt.keyCode];
|
||||
|
||||
// The keyCode doesn't distinguish between left and right
|
||||
// for the standard modifiers
|
||||
if (evt.location === 2) {
|
||||
switch (code) {
|
||||
case 'ShiftLeft': return 'ShiftRight';
|
||||
case 'ControlLeft': return 'ControlRight';
|
||||
case 'AltLeft': return 'AltRight';
|
||||
}
|
||||
}
|
||||
|
||||
// Nor a bunch of the numpad keys
|
||||
if (evt.location === 3) {
|
||||
switch (code) {
|
||||
case 'Delete': return 'NumpadDecimal';
|
||||
case 'Insert': return 'Numpad0';
|
||||
case 'End': return 'Numpad1';
|
||||
case 'ArrowDown': return 'Numpad2';
|
||||
case 'PageDown': return 'Numpad3';
|
||||
case 'ArrowLeft': return 'Numpad4';
|
||||
case 'ArrowRight': return 'Numpad6';
|
||||
case 'Home': return 'Numpad7';
|
||||
case 'ArrowUp': return 'Numpad8';
|
||||
case 'PageUp': return 'Numpad9';
|
||||
case 'Enter': return 'NumpadEnter';
|
||||
}
|
||||
}
|
||||
|
||||
return code;
|
||||
// macOS has messed up this code for some reason
|
||||
if (browser.isMac() && (code === 'ContextMenu')) {
|
||||
code = 'MetaRight';
|
||||
}
|
||||
|
||||
return 'Unidentified';
|
||||
// The keyCode doesn't distinguish between left and right
|
||||
// for the standard modifiers
|
||||
if (evt.location === 2) {
|
||||
switch (code) {
|
||||
case 'ShiftLeft': return 'ShiftRight';
|
||||
case 'ControlLeft': return 'ControlRight';
|
||||
case 'AltLeft': return 'AltRight';
|
||||
}
|
||||
}
|
||||
|
||||
// Nor a bunch of the numpad keys
|
||||
if (evt.location === 3) {
|
||||
switch (code) {
|
||||
case 'Delete': return 'NumpadDecimal';
|
||||
case 'Insert': return 'Numpad0';
|
||||
case 'End': return 'Numpad1';
|
||||
case 'ArrowDown': return 'Numpad2';
|
||||
case 'PageDown': return 'Numpad3';
|
||||
case 'ArrowLeft': return 'Numpad4';
|
||||
case 'ArrowRight': return 'Numpad6';
|
||||
case 'Home': return 'Numpad7';
|
||||
case 'ArrowUp': return 'Numpad8';
|
||||
case 'PageUp': return 'Numpad9';
|
||||
case 'Enter': return 'NumpadEnter';
|
||||
}
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
return 'Unidentified';
|
||||
}
|
||||
|
||||
// Get 'KeyboardEvent.key', handling legacy browsers
|
||||
export function getKey(evt) {
|
||||
// Are we getting a proper key value?
|
||||
if (evt.key !== undefined) {
|
||||
// IE and Edge use some ancient version of the spec
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
|
||||
switch (evt.key) {
|
||||
case 'Spacebar': return ' ';
|
||||
case 'Esc': return 'Escape';
|
||||
case 'Scroll': return 'ScrollLock';
|
||||
case 'Win': return 'Meta';
|
||||
case 'Apps': return 'ContextMenu';
|
||||
case 'Up': return 'ArrowUp';
|
||||
case 'Left': return 'ArrowLeft';
|
||||
case 'Right': return 'ArrowRight';
|
||||
case 'Down': return 'ArrowDown';
|
||||
case 'Del': return 'Delete';
|
||||
case 'Divide': return '/';
|
||||
case 'Multiply': return '*';
|
||||
case 'Subtract': return '-';
|
||||
case 'Add': return '+';
|
||||
case 'Decimal': return evt.char;
|
||||
}
|
||||
|
||||
// Mozilla isn't fully in sync with the spec yet
|
||||
switch (evt.key) {
|
||||
case 'OS': return 'Meta';
|
||||
}
|
||||
|
||||
// iOS leaks some OS names
|
||||
switch (evt.key) {
|
||||
case 'UIKeyInputUpArrow': return 'ArrowUp';
|
||||
case 'UIKeyInputDownArrow': return 'ArrowDown';
|
||||
case 'UIKeyInputLeftArrow': return 'ArrowLeft';
|
||||
case 'UIKeyInputRightArrow': return 'ArrowRight';
|
||||
case 'UIKeyInputEscape': return 'Escape';
|
||||
}
|
||||
|
||||
// IE and Edge have broken handling of AltGraph so we cannot
|
||||
// trust them for printable characters
|
||||
if ((evt.key.length !== 1) || (!browser.isIE() && !browser.isEdge())) {
|
||||
return evt.key;
|
||||
}
|
||||
// Are we getting a proper key value?
|
||||
if (evt.key !== undefined) {
|
||||
// IE and Edge use some ancient version of the spec
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
|
||||
switch (evt.key) {
|
||||
case 'Spacebar': return ' ';
|
||||
case 'Esc': return 'Escape';
|
||||
case 'Scroll': return 'ScrollLock';
|
||||
case 'Win': return 'Meta';
|
||||
case 'Apps': return 'ContextMenu';
|
||||
case 'Up': return 'ArrowUp';
|
||||
case 'Left': return 'ArrowLeft';
|
||||
case 'Right': return 'ArrowRight';
|
||||
case 'Down': return 'ArrowDown';
|
||||
case 'Del': return 'Delete';
|
||||
case 'Divide': return '/';
|
||||
case 'Multiply': return '*';
|
||||
case 'Subtract': return '-';
|
||||
case 'Add': return '+';
|
||||
case 'Decimal': return evt.char;
|
||||
}
|
||||
|
||||
// Try to deduce it based on the physical key
|
||||
const code = getKeycode(evt);
|
||||
if (code in fixedkeys) {
|
||||
return fixedkeys[code];
|
||||
// Mozilla isn't fully in sync with the spec yet
|
||||
switch (evt.key) {
|
||||
case 'OS': return 'Meta';
|
||||
}
|
||||
|
||||
// If that failed, then see if we have a printable character
|
||||
if (evt.charCode) {
|
||||
return String.fromCharCode(evt.charCode);
|
||||
// iOS leaks some OS names
|
||||
switch (evt.key) {
|
||||
case 'UIKeyInputUpArrow': return 'ArrowUp';
|
||||
case 'UIKeyInputDownArrow': return 'ArrowDown';
|
||||
case 'UIKeyInputLeftArrow': return 'ArrowLeft';
|
||||
case 'UIKeyInputRightArrow': return 'ArrowRight';
|
||||
case 'UIKeyInputEscape': return 'Escape';
|
||||
}
|
||||
|
||||
// At this point we have nothing left to go on
|
||||
return 'Unidentified';
|
||||
// IE and Edge have broken handling of AltGraph so we cannot
|
||||
// trust them for printable characters
|
||||
if ((evt.key.length !== 1) || (!browser.isIE() && !browser.isEdge())) {
|
||||
return evt.key;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to deduce it based on the physical key
|
||||
const code = getKeycode(evt);
|
||||
if (code in fixedkeys) {
|
||||
return fixedkeys[code];
|
||||
}
|
||||
|
||||
// If that failed, then see if we have a printable character
|
||||
if (evt.charCode) {
|
||||
return String.fromCharCode(evt.charCode);
|
||||
}
|
||||
|
||||
// At this point we have nothing left to go on
|
||||
return 'Unidentified';
|
||||
}
|
||||
|
||||
// Get the most reliable keysym value we can get from a key event
|
||||
export function getKeysym(evt){
|
||||
const key = getKey(evt);
|
||||
|
||||
if (key === 'Unidentified') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// First look up special keys
|
||||
if (key in DOMKeyTable) {
|
||||
let location = evt.location;
|
||||
|
||||
// Safari screws up location for the right cmd key
|
||||
if ((key === 'Meta') && (location === 0)) {
|
||||
location = 2;
|
||||
}
|
||||
|
||||
if ((location === undefined) || (location > 3)) {
|
||||
location = 0;
|
||||
}
|
||||
|
||||
return DOMKeyTable[key][location];
|
||||
}
|
||||
|
||||
// Now we need to look at the Unicode symbol instead
|
||||
|
||||
// Special key? (FIXME: Should have been caught earlier)
|
||||
if (key.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const codepoint = key.charCodeAt();
|
||||
if (codepoint) {
|
||||
return keysyms.lookup(codepoint);
|
||||
}
|
||||
export function getKeysym(evt) {
|
||||
const key = getKey(evt);
|
||||
|
||||
if (key === 'Unidentified') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// First look up special keys
|
||||
if (key in DOMKeyTable) {
|
||||
let location = evt.location;
|
||||
|
||||
// Safari screws up location for the right cmd key
|
||||
if ((key === 'Meta') && (location === 0)) {
|
||||
location = 2;
|
||||
}
|
||||
|
||||
if ((location === undefined) || (location > 3)) {
|
||||
location = 0;
|
||||
}
|
||||
|
||||
return DOMKeyTable[key][location];
|
||||
}
|
||||
|
||||
// Now we need to look at the Unicode symbol instead
|
||||
|
||||
// Special key? (FIXME: Should have been caught earlier)
|
||||
if (key.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const codepoint = key.charCodeAt();
|
||||
if (codepoint) {
|
||||
return keysyms.lookup(codepoint);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,108 +10,108 @@
|
|||
*/
|
||||
|
||||
export default {
|
||||
0x08: 'Backspace',
|
||||
0x09: 'Tab',
|
||||
0x0a: 'NumpadClear',
|
||||
0x0c: 'Numpad5', // IE11 sends evt.keyCode: 12 when numlock is off
|
||||
0x0d: 'Enter',
|
||||
0x10: 'ShiftLeft',
|
||||
0x11: 'ControlLeft',
|
||||
0x12: 'AltLeft',
|
||||
0x13: 'Pause',
|
||||
0x14: 'CapsLock',
|
||||
0x15: 'Lang1',
|
||||
0x19: 'Lang2',
|
||||
0x1b: 'Escape',
|
||||
0x1c: 'Convert',
|
||||
0x1d: 'NonConvert',
|
||||
0x20: 'Space',
|
||||
0x21: 'PageUp',
|
||||
0x22: 'PageDown',
|
||||
0x23: 'End',
|
||||
0x24: 'Home',
|
||||
0x25: 'ArrowLeft',
|
||||
0x26: 'ArrowUp',
|
||||
0x27: 'ArrowRight',
|
||||
0x28: 'ArrowDown',
|
||||
0x29: 'Select',
|
||||
0x2c: 'PrintScreen',
|
||||
0x2d: 'Insert',
|
||||
0x2e: 'Delete',
|
||||
0x2f: 'Help',
|
||||
0x30: 'Digit0',
|
||||
0x31: 'Digit1',
|
||||
0x32: 'Digit2',
|
||||
0x33: 'Digit3',
|
||||
0x34: 'Digit4',
|
||||
0x35: 'Digit5',
|
||||
0x36: 'Digit6',
|
||||
0x37: 'Digit7',
|
||||
0x38: 'Digit8',
|
||||
0x39: 'Digit9',
|
||||
0x5b: 'MetaLeft',
|
||||
0x5c: 'MetaRight',
|
||||
0x5d: 'ContextMenu',
|
||||
0x5f: 'Sleep',
|
||||
0x60: 'Numpad0',
|
||||
0x61: 'Numpad1',
|
||||
0x62: 'Numpad2',
|
||||
0x63: 'Numpad3',
|
||||
0x64: 'Numpad4',
|
||||
0x65: 'Numpad5',
|
||||
0x66: 'Numpad6',
|
||||
0x67: 'Numpad7',
|
||||
0x68: 'Numpad8',
|
||||
0x69: 'Numpad9',
|
||||
0x6a: 'NumpadMultiply',
|
||||
0x6b: 'NumpadAdd',
|
||||
0x6c: 'NumpadDecimal',
|
||||
0x6d: 'NumpadSubtract',
|
||||
0x6e: 'NumpadDecimal', // Duplicate, because buggy on Windows
|
||||
0x6f: 'NumpadDivide',
|
||||
0x70: 'F1',
|
||||
0x71: 'F2',
|
||||
0x72: 'F3',
|
||||
0x73: 'F4',
|
||||
0x74: 'F5',
|
||||
0x75: 'F6',
|
||||
0x76: 'F7',
|
||||
0x77: 'F8',
|
||||
0x78: 'F9',
|
||||
0x79: 'F10',
|
||||
0x7a: 'F11',
|
||||
0x7b: 'F12',
|
||||
0x7c: 'F13',
|
||||
0x7d: 'F14',
|
||||
0x7e: 'F15',
|
||||
0x7f: 'F16',
|
||||
0x80: 'F17',
|
||||
0x81: 'F18',
|
||||
0x82: 'F19',
|
||||
0x83: 'F20',
|
||||
0x84: 'F21',
|
||||
0x85: 'F22',
|
||||
0x86: 'F23',
|
||||
0x87: 'F24',
|
||||
0x90: 'NumLock',
|
||||
0x91: 'ScrollLock',
|
||||
0xa6: 'BrowserBack',
|
||||
0xa7: 'BrowserForward',
|
||||
0xa8: 'BrowserRefresh',
|
||||
0xa9: 'BrowserStop',
|
||||
0xaa: 'BrowserSearch',
|
||||
0xab: 'BrowserFavorites',
|
||||
0xac: 'BrowserHome',
|
||||
0xad: 'AudioVolumeMute',
|
||||
0xae: 'AudioVolumeDown',
|
||||
0xaf: 'AudioVolumeUp',
|
||||
0xb0: 'MediaTrackNext',
|
||||
0xb1: 'MediaTrackPrevious',
|
||||
0xb2: 'MediaStop',
|
||||
0xb3: 'MediaPlayPause',
|
||||
0xb4: 'LaunchMail',
|
||||
0xb5: 'MediaSelect',
|
||||
0xb6: 'LaunchApp1',
|
||||
0xb7: 'LaunchApp2',
|
||||
0xe1: 'AltRight', // Only when it is AltGraph
|
||||
0x08: 'Backspace',
|
||||
0x09: 'Tab',
|
||||
0x0a: 'NumpadClear',
|
||||
0x0c: 'Numpad5', // IE11 sends evt.keyCode: 12 when numlock is off
|
||||
0x0d: 'Enter',
|
||||
0x10: 'ShiftLeft',
|
||||
0x11: 'ControlLeft',
|
||||
0x12: 'AltLeft',
|
||||
0x13: 'Pause',
|
||||
0x14: 'CapsLock',
|
||||
0x15: 'Lang1',
|
||||
0x19: 'Lang2',
|
||||
0x1b: 'Escape',
|
||||
0x1c: 'Convert',
|
||||
0x1d: 'NonConvert',
|
||||
0x20: 'Space',
|
||||
0x21: 'PageUp',
|
||||
0x22: 'PageDown',
|
||||
0x23: 'End',
|
||||
0x24: 'Home',
|
||||
0x25: 'ArrowLeft',
|
||||
0x26: 'ArrowUp',
|
||||
0x27: 'ArrowRight',
|
||||
0x28: 'ArrowDown',
|
||||
0x29: 'Select',
|
||||
0x2c: 'PrintScreen',
|
||||
0x2d: 'Insert',
|
||||
0x2e: 'Delete',
|
||||
0x2f: 'Help',
|
||||
0x30: 'Digit0',
|
||||
0x31: 'Digit1',
|
||||
0x32: 'Digit2',
|
||||
0x33: 'Digit3',
|
||||
0x34: 'Digit4',
|
||||
0x35: 'Digit5',
|
||||
0x36: 'Digit6',
|
||||
0x37: 'Digit7',
|
||||
0x38: 'Digit8',
|
||||
0x39: 'Digit9',
|
||||
0x5b: 'MetaLeft',
|
||||
0x5c: 'MetaRight',
|
||||
0x5d: 'ContextMenu',
|
||||
0x5f: 'Sleep',
|
||||
0x60: 'Numpad0',
|
||||
0x61: 'Numpad1',
|
||||
0x62: 'Numpad2',
|
||||
0x63: 'Numpad3',
|
||||
0x64: 'Numpad4',
|
||||
0x65: 'Numpad5',
|
||||
0x66: 'Numpad6',
|
||||
0x67: 'Numpad7',
|
||||
0x68: 'Numpad8',
|
||||
0x69: 'Numpad9',
|
||||
0x6a: 'NumpadMultiply',
|
||||
0x6b: 'NumpadAdd',
|
||||
0x6c: 'NumpadDecimal',
|
||||
0x6d: 'NumpadSubtract',
|
||||
0x6e: 'NumpadDecimal', // Duplicate, because buggy on Windows
|
||||
0x6f: 'NumpadDivide',
|
||||
0x70: 'F1',
|
||||
0x71: 'F2',
|
||||
0x72: 'F3',
|
||||
0x73: 'F4',
|
||||
0x74: 'F5',
|
||||
0x75: 'F6',
|
||||
0x76: 'F7',
|
||||
0x77: 'F8',
|
||||
0x78: 'F9',
|
||||
0x79: 'F10',
|
||||
0x7a: 'F11',
|
||||
0x7b: 'F12',
|
||||
0x7c: 'F13',
|
||||
0x7d: 'F14',
|
||||
0x7e: 'F15',
|
||||
0x7f: 'F16',
|
||||
0x80: 'F17',
|
||||
0x81: 'F18',
|
||||
0x82: 'F19',
|
||||
0x83: 'F20',
|
||||
0x84: 'F21',
|
||||
0x85: 'F22',
|
||||
0x86: 'F23',
|
||||
0x87: 'F24',
|
||||
0x90: 'NumLock',
|
||||
0x91: 'ScrollLock',
|
||||
0xa6: 'BrowserBack',
|
||||
0xa7: 'BrowserForward',
|
||||
0xa8: 'BrowserRefresh',
|
||||
0xa9: 'BrowserStop',
|
||||
0xaa: 'BrowserSearch',
|
||||
0xab: 'BrowserFavorites',
|
||||
0xac: 'BrowserHome',
|
||||
0xad: 'AudioVolumeMute',
|
||||
0xae: 'AudioVolumeDown',
|
||||
0xaf: 'AudioVolumeUp',
|
||||
0xb0: 'MediaTrackNext',
|
||||
0xb1: 'MediaTrackPrevious',
|
||||
0xb2: 'MediaStop',
|
||||
0xb3: 'MediaPlayPause',
|
||||
0xb4: 'LaunchMail',
|
||||
0xb5: 'MediaSelect',
|
||||
0xb6: 'LaunchApp1',
|
||||
0xb7: 'LaunchApp2',
|
||||
0xe1: 'AltRight', // Only when it is AltGraph
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,168 +4,169 @@
|
|||
* To re-generate, run:
|
||||
* keymap-gen --lang=js code-map keymaps.csv html atset1
|
||||
*/
|
||||
/* eslint-disable max-len */
|
||||
export default {
|
||||
"Again": 0xe005, /* html:Again (Again) -> linux:129 (KEY_AGAIN) -> atset1:57349 */
|
||||
"AltLeft": 0x38, /* html:AltLeft (AltLeft) -> linux:56 (KEY_LEFTALT) -> atset1:56 */
|
||||
"AltRight": 0xe038, /* html:AltRight (AltRight) -> linux:100 (KEY_RIGHTALT) -> atset1:57400 */
|
||||
"ArrowDown": 0xe050, /* html:ArrowDown (ArrowDown) -> linux:108 (KEY_DOWN) -> atset1:57424 */
|
||||
"ArrowLeft": 0xe04b, /* html:ArrowLeft (ArrowLeft) -> linux:105 (KEY_LEFT) -> atset1:57419 */
|
||||
"ArrowRight": 0xe04d, /* html:ArrowRight (ArrowRight) -> linux:106 (KEY_RIGHT) -> atset1:57421 */
|
||||
"ArrowUp": 0xe048, /* html:ArrowUp (ArrowUp) -> linux:103 (KEY_UP) -> atset1:57416 */
|
||||
"AudioVolumeDown": 0xe02e, /* html:AudioVolumeDown (AudioVolumeDown) -> linux:114 (KEY_VOLUMEDOWN) -> atset1:57390 */
|
||||
"AudioVolumeMute": 0xe020, /* html:AudioVolumeMute (AudioVolumeMute) -> linux:113 (KEY_MUTE) -> atset1:57376 */
|
||||
"AudioVolumeUp": 0xe030, /* html:AudioVolumeUp (AudioVolumeUp) -> linux:115 (KEY_VOLUMEUP) -> atset1:57392 */
|
||||
"Backquote": 0x29, /* html:Backquote (Backquote) -> linux:41 (KEY_GRAVE) -> atset1:41 */
|
||||
"Backslash": 0x2b, /* html:Backslash (Backslash) -> linux:43 (KEY_BACKSLASH) -> atset1:43 */
|
||||
"Backspace": 0xe, /* html:Backspace (Backspace) -> linux:14 (KEY_BACKSPACE) -> atset1:14 */
|
||||
"BracketLeft": 0x1a, /* html:BracketLeft (BracketLeft) -> linux:26 (KEY_LEFTBRACE) -> atset1:26 */
|
||||
"BracketRight": 0x1b, /* html:BracketRight (BracketRight) -> linux:27 (KEY_RIGHTBRACE) -> atset1:27 */
|
||||
"BrowserBack": 0xe06a, /* html:BrowserBack (BrowserBack) -> linux:158 (KEY_BACK) -> atset1:57450 */
|
||||
"BrowserFavorites": 0xe066, /* html:BrowserFavorites (BrowserFavorites) -> linux:156 (KEY_BOOKMARKS) -> atset1:57446 */
|
||||
"BrowserForward": 0xe069, /* html:BrowserForward (BrowserForward) -> linux:159 (KEY_FORWARD) -> atset1:57449 */
|
||||
"BrowserHome": 0xe032, /* html:BrowserHome (BrowserHome) -> linux:172 (KEY_HOMEPAGE) -> atset1:57394 */
|
||||
"BrowserRefresh": 0xe067, /* html:BrowserRefresh (BrowserRefresh) -> linux:173 (KEY_REFRESH) -> atset1:57447 */
|
||||
"BrowserSearch": 0xe065, /* html:BrowserSearch (BrowserSearch) -> linux:217 (KEY_SEARCH) -> atset1:57445 */
|
||||
"BrowserStop": 0xe068, /* html:BrowserStop (BrowserStop) -> linux:128 (KEY_STOP) -> atset1:57448 */
|
||||
"CapsLock": 0x3a, /* html:CapsLock (CapsLock) -> linux:58 (KEY_CAPSLOCK) -> atset1:58 */
|
||||
"Comma": 0x33, /* html:Comma (Comma) -> linux:51 (KEY_COMMA) -> atset1:51 */
|
||||
"ContextMenu": 0xe05d, /* html:ContextMenu (ContextMenu) -> linux:127 (KEY_COMPOSE) -> atset1:57437 */
|
||||
"ControlLeft": 0x1d, /* html:ControlLeft (ControlLeft) -> linux:29 (KEY_LEFTCTRL) -> atset1:29 */
|
||||
"ControlRight": 0xe01d, /* html:ControlRight (ControlRight) -> linux:97 (KEY_RIGHTCTRL) -> atset1:57373 */
|
||||
"Convert": 0x79, /* html:Convert (Convert) -> linux:92 (KEY_HENKAN) -> atset1:121 */
|
||||
"Copy": 0xe078, /* html:Copy (Copy) -> linux:133 (KEY_COPY) -> atset1:57464 */
|
||||
"Cut": 0xe03c, /* html:Cut (Cut) -> linux:137 (KEY_CUT) -> atset1:57404 */
|
||||
"Delete": 0xe053, /* html:Delete (Delete) -> linux:111 (KEY_DELETE) -> atset1:57427 */
|
||||
"Digit0": 0xb, /* html:Digit0 (Digit0) -> linux:11 (KEY_0) -> atset1:11 */
|
||||
"Digit1": 0x2, /* html:Digit1 (Digit1) -> linux:2 (KEY_1) -> atset1:2 */
|
||||
"Digit2": 0x3, /* html:Digit2 (Digit2) -> linux:3 (KEY_2) -> atset1:3 */
|
||||
"Digit3": 0x4, /* html:Digit3 (Digit3) -> linux:4 (KEY_3) -> atset1:4 */
|
||||
"Digit4": 0x5, /* html:Digit4 (Digit4) -> linux:5 (KEY_4) -> atset1:5 */
|
||||
"Digit5": 0x6, /* html:Digit5 (Digit5) -> linux:6 (KEY_5) -> atset1:6 */
|
||||
"Digit6": 0x7, /* html:Digit6 (Digit6) -> linux:7 (KEY_6) -> atset1:7 */
|
||||
"Digit7": 0x8, /* html:Digit7 (Digit7) -> linux:8 (KEY_7) -> atset1:8 */
|
||||
"Digit8": 0x9, /* html:Digit8 (Digit8) -> linux:9 (KEY_8) -> atset1:9 */
|
||||
"Digit9": 0xa, /* html:Digit9 (Digit9) -> linux:10 (KEY_9) -> atset1:10 */
|
||||
"Eject": 0xe07d, /* html:Eject (Eject) -> linux:162 (KEY_EJECTCLOSECD) -> atset1:57469 */
|
||||
"End": 0xe04f, /* html:End (End) -> linux:107 (KEY_END) -> atset1:57423 */
|
||||
"Enter": 0x1c, /* html:Enter (Enter) -> linux:28 (KEY_ENTER) -> atset1:28 */
|
||||
"Equal": 0xd, /* html:Equal (Equal) -> linux:13 (KEY_EQUAL) -> atset1:13 */
|
||||
"Escape": 0x1, /* html:Escape (Escape) -> linux:1 (KEY_ESC) -> atset1:1 */
|
||||
"F1": 0x3b, /* html:F1 (F1) -> linux:59 (KEY_F1) -> atset1:59 */
|
||||
"F10": 0x44, /* html:F10 (F10) -> linux:68 (KEY_F10) -> atset1:68 */
|
||||
"F11": 0x57, /* html:F11 (F11) -> linux:87 (KEY_F11) -> atset1:87 */
|
||||
"F12": 0x58, /* html:F12 (F12) -> linux:88 (KEY_F12) -> atset1:88 */
|
||||
"F13": 0x5d, /* html:F13 (F13) -> linux:183 (KEY_F13) -> atset1:93 */
|
||||
"F14": 0x5e, /* html:F14 (F14) -> linux:184 (KEY_F14) -> atset1:94 */
|
||||
"F15": 0x5f, /* html:F15 (F15) -> linux:185 (KEY_F15) -> atset1:95 */
|
||||
"F16": 0x55, /* html:F16 (F16) -> linux:186 (KEY_F16) -> atset1:85 */
|
||||
"F17": 0xe003, /* html:F17 (F17) -> linux:187 (KEY_F17) -> atset1:57347 */
|
||||
"F18": 0xe077, /* html:F18 (F18) -> linux:188 (KEY_F18) -> atset1:57463 */
|
||||
"F19": 0xe004, /* html:F19 (F19) -> linux:189 (KEY_F19) -> atset1:57348 */
|
||||
"F2": 0x3c, /* html:F2 (F2) -> linux:60 (KEY_F2) -> atset1:60 */
|
||||
"F20": 0x5a, /* html:F20 (F20) -> linux:190 (KEY_F20) -> atset1:90 */
|
||||
"F21": 0x74, /* html:F21 (F21) -> linux:191 (KEY_F21) -> atset1:116 */
|
||||
"F22": 0xe079, /* html:F22 (F22) -> linux:192 (KEY_F22) -> atset1:57465 */
|
||||
"F23": 0x6d, /* html:F23 (F23) -> linux:193 (KEY_F23) -> atset1:109 */
|
||||
"F24": 0x6f, /* html:F24 (F24) -> linux:194 (KEY_F24) -> atset1:111 */
|
||||
"F3": 0x3d, /* html:F3 (F3) -> linux:61 (KEY_F3) -> atset1:61 */
|
||||
"F4": 0x3e, /* html:F4 (F4) -> linux:62 (KEY_F4) -> atset1:62 */
|
||||
"F5": 0x3f, /* html:F5 (F5) -> linux:63 (KEY_F5) -> atset1:63 */
|
||||
"F6": 0x40, /* html:F6 (F6) -> linux:64 (KEY_F6) -> atset1:64 */
|
||||
"F7": 0x41, /* html:F7 (F7) -> linux:65 (KEY_F7) -> atset1:65 */
|
||||
"F8": 0x42, /* html:F8 (F8) -> linux:66 (KEY_F8) -> atset1:66 */
|
||||
"F9": 0x43, /* html:F9 (F9) -> linux:67 (KEY_F9) -> atset1:67 */
|
||||
"Find": 0xe041, /* html:Find (Find) -> linux:136 (KEY_FIND) -> atset1:57409 */
|
||||
"Help": 0xe075, /* html:Help (Help) -> linux:138 (KEY_HELP) -> atset1:57461 */
|
||||
"Hiragana": 0x77, /* html:Hiragana (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */
|
||||
"Home": 0xe047, /* html:Home (Home) -> linux:102 (KEY_HOME) -> atset1:57415 */
|
||||
"Insert": 0xe052, /* html:Insert (Insert) -> linux:110 (KEY_INSERT) -> atset1:57426 */
|
||||
"IntlBackslash": 0x56, /* html:IntlBackslash (IntlBackslash) -> linux:86 (KEY_102ND) -> atset1:86 */
|
||||
"IntlRo": 0x73, /* html:IntlRo (IntlRo) -> linux:89 (KEY_RO) -> atset1:115 */
|
||||
"IntlYen": 0x7d, /* html:IntlYen (IntlYen) -> linux:124 (KEY_YEN) -> atset1:125 */
|
||||
"KanaMode": 0x70, /* html:KanaMode (KanaMode) -> linux:93 (KEY_KATAKANAHIRAGANA) -> atset1:112 */
|
||||
"Katakana": 0x78, /* html:Katakana (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */
|
||||
"KeyA": 0x1e, /* html:KeyA (KeyA) -> linux:30 (KEY_A) -> atset1:30 */
|
||||
"KeyB": 0x30, /* html:KeyB (KeyB) -> linux:48 (KEY_B) -> atset1:48 */
|
||||
"KeyC": 0x2e, /* html:KeyC (KeyC) -> linux:46 (KEY_C) -> atset1:46 */
|
||||
"KeyD": 0x20, /* html:KeyD (KeyD) -> linux:32 (KEY_D) -> atset1:32 */
|
||||
"KeyE": 0x12, /* html:KeyE (KeyE) -> linux:18 (KEY_E) -> atset1:18 */
|
||||
"KeyF": 0x21, /* html:KeyF (KeyF) -> linux:33 (KEY_F) -> atset1:33 */
|
||||
"KeyG": 0x22, /* html:KeyG (KeyG) -> linux:34 (KEY_G) -> atset1:34 */
|
||||
"KeyH": 0x23, /* html:KeyH (KeyH) -> linux:35 (KEY_H) -> atset1:35 */
|
||||
"KeyI": 0x17, /* html:KeyI (KeyI) -> linux:23 (KEY_I) -> atset1:23 */
|
||||
"KeyJ": 0x24, /* html:KeyJ (KeyJ) -> linux:36 (KEY_J) -> atset1:36 */
|
||||
"KeyK": 0x25, /* html:KeyK (KeyK) -> linux:37 (KEY_K) -> atset1:37 */
|
||||
"KeyL": 0x26, /* html:KeyL (KeyL) -> linux:38 (KEY_L) -> atset1:38 */
|
||||
"KeyM": 0x32, /* html:KeyM (KeyM) -> linux:50 (KEY_M) -> atset1:50 */
|
||||
"KeyN": 0x31, /* html:KeyN (KeyN) -> linux:49 (KEY_N) -> atset1:49 */
|
||||
"KeyO": 0x18, /* html:KeyO (KeyO) -> linux:24 (KEY_O) -> atset1:24 */
|
||||
"KeyP": 0x19, /* html:KeyP (KeyP) -> linux:25 (KEY_P) -> atset1:25 */
|
||||
"KeyQ": 0x10, /* html:KeyQ (KeyQ) -> linux:16 (KEY_Q) -> atset1:16 */
|
||||
"KeyR": 0x13, /* html:KeyR (KeyR) -> linux:19 (KEY_R) -> atset1:19 */
|
||||
"KeyS": 0x1f, /* html:KeyS (KeyS) -> linux:31 (KEY_S) -> atset1:31 */
|
||||
"KeyT": 0x14, /* html:KeyT (KeyT) -> linux:20 (KEY_T) -> atset1:20 */
|
||||
"KeyU": 0x16, /* html:KeyU (KeyU) -> linux:22 (KEY_U) -> atset1:22 */
|
||||
"KeyV": 0x2f, /* html:KeyV (KeyV) -> linux:47 (KEY_V) -> atset1:47 */
|
||||
"KeyW": 0x11, /* html:KeyW (KeyW) -> linux:17 (KEY_W) -> atset1:17 */
|
||||
"KeyX": 0x2d, /* html:KeyX (KeyX) -> linux:45 (KEY_X) -> atset1:45 */
|
||||
"KeyY": 0x15, /* html:KeyY (KeyY) -> linux:21 (KEY_Y) -> atset1:21 */
|
||||
"KeyZ": 0x2c, /* html:KeyZ (KeyZ) -> linux:44 (KEY_Z) -> atset1:44 */
|
||||
"Lang3": 0x78, /* html:Lang3 (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */
|
||||
"Lang4": 0x77, /* html:Lang4 (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */
|
||||
"Lang5": 0x76, /* html:Lang5 (Lang5) -> linux:85 (KEY_ZENKAKUHANKAKU) -> atset1:118 */
|
||||
"LaunchApp1": 0xe06b, /* html:LaunchApp1 (LaunchApp1) -> linux:157 (KEY_COMPUTER) -> atset1:57451 */
|
||||
"LaunchApp2": 0xe021, /* html:LaunchApp2 (LaunchApp2) -> linux:140 (KEY_CALC) -> atset1:57377 */
|
||||
"LaunchMail": 0xe06c, /* html:LaunchMail (LaunchMail) -> linux:155 (KEY_MAIL) -> atset1:57452 */
|
||||
"MediaPlayPause": 0xe022, /* html:MediaPlayPause (MediaPlayPause) -> linux:164 (KEY_PLAYPAUSE) -> atset1:57378 */
|
||||
"MediaSelect": 0xe06d, /* html:MediaSelect (MediaSelect) -> linux:226 (KEY_MEDIA) -> atset1:57453 */
|
||||
"MediaStop": 0xe024, /* html:MediaStop (MediaStop) -> linux:166 (KEY_STOPCD) -> atset1:57380 */
|
||||
"MediaTrackNext": 0xe019, /* html:MediaTrackNext (MediaTrackNext) -> linux:163 (KEY_NEXTSONG) -> atset1:57369 */
|
||||
"MediaTrackPrevious": 0xe010, /* html:MediaTrackPrevious (MediaTrackPrevious) -> linux:165 (KEY_PREVIOUSSONG) -> atset1:57360 */
|
||||
"MetaLeft": 0xe05b, /* html:MetaLeft (MetaLeft) -> linux:125 (KEY_LEFTMETA) -> atset1:57435 */
|
||||
"MetaRight": 0xe05c, /* html:MetaRight (MetaRight) -> linux:126 (KEY_RIGHTMETA) -> atset1:57436 */
|
||||
"Minus": 0xc, /* html:Minus (Minus) -> linux:12 (KEY_MINUS) -> atset1:12 */
|
||||
"NonConvert": 0x7b, /* html:NonConvert (NonConvert) -> linux:94 (KEY_MUHENKAN) -> atset1:123 */
|
||||
"NumLock": 0x45, /* html:NumLock (NumLock) -> linux:69 (KEY_NUMLOCK) -> atset1:69 */
|
||||
"Numpad0": 0x52, /* html:Numpad0 (Numpad0) -> linux:82 (KEY_KP0) -> atset1:82 */
|
||||
"Numpad1": 0x4f, /* html:Numpad1 (Numpad1) -> linux:79 (KEY_KP1) -> atset1:79 */
|
||||
"Numpad2": 0x50, /* html:Numpad2 (Numpad2) -> linux:80 (KEY_KP2) -> atset1:80 */
|
||||
"Numpad3": 0x51, /* html:Numpad3 (Numpad3) -> linux:81 (KEY_KP3) -> atset1:81 */
|
||||
"Numpad4": 0x4b, /* html:Numpad4 (Numpad4) -> linux:75 (KEY_KP4) -> atset1:75 */
|
||||
"Numpad5": 0x4c, /* html:Numpad5 (Numpad5) -> linux:76 (KEY_KP5) -> atset1:76 */
|
||||
"Numpad6": 0x4d, /* html:Numpad6 (Numpad6) -> linux:77 (KEY_KP6) -> atset1:77 */
|
||||
"Numpad7": 0x47, /* html:Numpad7 (Numpad7) -> linux:71 (KEY_KP7) -> atset1:71 */
|
||||
"Numpad8": 0x48, /* html:Numpad8 (Numpad8) -> linux:72 (KEY_KP8) -> atset1:72 */
|
||||
"Numpad9": 0x49, /* html:Numpad9 (Numpad9) -> linux:73 (KEY_KP9) -> atset1:73 */
|
||||
"NumpadAdd": 0x4e, /* html:NumpadAdd (NumpadAdd) -> linux:78 (KEY_KPPLUS) -> atset1:78 */
|
||||
"NumpadComma": 0x7e, /* html:NumpadComma (NumpadComma) -> linux:121 (KEY_KPCOMMA) -> atset1:126 */
|
||||
"NumpadDecimal": 0x53, /* html:NumpadDecimal (NumpadDecimal) -> linux:83 (KEY_KPDOT) -> atset1:83 */
|
||||
"NumpadDivide": 0xe035, /* html:NumpadDivide (NumpadDivide) -> linux:98 (KEY_KPSLASH) -> atset1:57397 */
|
||||
"NumpadEnter": 0xe01c, /* html:NumpadEnter (NumpadEnter) -> linux:96 (KEY_KPENTER) -> atset1:57372 */
|
||||
"NumpadEqual": 0x59, /* html:NumpadEqual (NumpadEqual) -> linux:117 (KEY_KPEQUAL) -> atset1:89 */
|
||||
"NumpadMultiply": 0x37, /* html:NumpadMultiply (NumpadMultiply) -> linux:55 (KEY_KPASTERISK) -> atset1:55 */
|
||||
"NumpadParenLeft": 0xe076, /* html:NumpadParenLeft (NumpadParenLeft) -> linux:179 (KEY_KPLEFTPAREN) -> atset1:57462 */
|
||||
"NumpadParenRight": 0xe07b, /* html:NumpadParenRight (NumpadParenRight) -> linux:180 (KEY_KPRIGHTPAREN) -> atset1:57467 */
|
||||
"NumpadSubtract": 0x4a, /* html:NumpadSubtract (NumpadSubtract) -> linux:74 (KEY_KPMINUS) -> atset1:74 */
|
||||
"Open": 0x64, /* html:Open (Open) -> linux:134 (KEY_OPEN) -> atset1:100 */
|
||||
"PageDown": 0xe051, /* html:PageDown (PageDown) -> linux:109 (KEY_PAGEDOWN) -> atset1:57425 */
|
||||
"PageUp": 0xe049, /* html:PageUp (PageUp) -> linux:104 (KEY_PAGEUP) -> atset1:57417 */
|
||||
"Paste": 0x65, /* html:Paste (Paste) -> linux:135 (KEY_PASTE) -> atset1:101 */
|
||||
"Pause": 0xe046, /* html:Pause (Pause) -> linux:119 (KEY_PAUSE) -> atset1:57414 */
|
||||
"Period": 0x34, /* html:Period (Period) -> linux:52 (KEY_DOT) -> atset1:52 */
|
||||
"Power": 0xe05e, /* html:Power (Power) -> linux:116 (KEY_POWER) -> atset1:57438 */
|
||||
"PrintScreen": 0x54, /* html:PrintScreen (PrintScreen) -> linux:99 (KEY_SYSRQ) -> atset1:84 */
|
||||
"Props": 0xe006, /* html:Props (Props) -> linux:130 (KEY_PROPS) -> atset1:57350 */
|
||||
"Quote": 0x28, /* html:Quote (Quote) -> linux:40 (KEY_APOSTROPHE) -> atset1:40 */
|
||||
"ScrollLock": 0x46, /* html:ScrollLock (ScrollLock) -> linux:70 (KEY_SCROLLLOCK) -> atset1:70 */
|
||||
"Semicolon": 0x27, /* html:Semicolon (Semicolon) -> linux:39 (KEY_SEMICOLON) -> atset1:39 */
|
||||
"ShiftLeft": 0x2a, /* html:ShiftLeft (ShiftLeft) -> linux:42 (KEY_LEFTSHIFT) -> atset1:42 */
|
||||
"ShiftRight": 0x36, /* html:ShiftRight (ShiftRight) -> linux:54 (KEY_RIGHTSHIFT) -> atset1:54 */
|
||||
"Slash": 0x35, /* html:Slash (Slash) -> linux:53 (KEY_SLASH) -> atset1:53 */
|
||||
"Sleep": 0xe05f, /* html:Sleep (Sleep) -> linux:142 (KEY_SLEEP) -> atset1:57439 */
|
||||
"Space": 0x39, /* html:Space (Space) -> linux:57 (KEY_SPACE) -> atset1:57 */
|
||||
"Suspend": 0xe025, /* html:Suspend (Suspend) -> linux:205 (KEY_SUSPEND) -> atset1:57381 */
|
||||
"Tab": 0xf, /* html:Tab (Tab) -> linux:15 (KEY_TAB) -> atset1:15 */
|
||||
"Undo": 0xe007, /* html:Undo (Undo) -> linux:131 (KEY_UNDO) -> atset1:57351 */
|
||||
"WakeUp": 0xe063, /* html:WakeUp (WakeUp) -> linux:143 (KEY_WAKEUP) -> atset1:57443 */
|
||||
Again: 0xe005, /* html:Again (Again) -> linux:129 (KEY_AGAIN) -> atset1:57349 */
|
||||
AltLeft: 0x38, /* html:AltLeft (AltLeft) -> linux:56 (KEY_LEFTALT) -> atset1:56 */
|
||||
AltRight: 0xe038, /* html:AltRight (AltRight) -> linux:100 (KEY_RIGHTALT) -> atset1:57400 */
|
||||
ArrowDown: 0xe050, /* html:ArrowDown (ArrowDown) -> linux:108 (KEY_DOWN) -> atset1:57424 */
|
||||
ArrowLeft: 0xe04b, /* html:ArrowLeft (ArrowLeft) -> linux:105 (KEY_LEFT) -> atset1:57419 */
|
||||
ArrowRight: 0xe04d, /* html:ArrowRight (ArrowRight) -> linux:106 (KEY_RIGHT) -> atset1:57421 */
|
||||
ArrowUp: 0xe048, /* html:ArrowUp (ArrowUp) -> linux:103 (KEY_UP) -> atset1:57416 */
|
||||
AudioVolumeDown: 0xe02e, /* html:AudioVolumeDown (AudioVolumeDown) -> linux:114 (KEY_VOLUMEDOWN) -> atset1:57390 */
|
||||
AudioVolumeMute: 0xe020, /* html:AudioVolumeMute (AudioVolumeMute) -> linux:113 (KEY_MUTE) -> atset1:57376 */
|
||||
AudioVolumeUp: 0xe030, /* html:AudioVolumeUp (AudioVolumeUp) -> linux:115 (KEY_VOLUMEUP) -> atset1:57392 */
|
||||
Backquote: 0x29, /* html:Backquote (Backquote) -> linux:41 (KEY_GRAVE) -> atset1:41 */
|
||||
Backslash: 0x2b, /* html:Backslash (Backslash) -> linux:43 (KEY_BACKSLASH) -> atset1:43 */
|
||||
Backspace: 0xe, /* html:Backspace (Backspace) -> linux:14 (KEY_BACKSPACE) -> atset1:14 */
|
||||
BracketLeft: 0x1a, /* html:BracketLeft (BracketLeft) -> linux:26 (KEY_LEFTBRACE) -> atset1:26 */
|
||||
BracketRight: 0x1b, /* html:BracketRight (BracketRight) -> linux:27 (KEY_RIGHTBRACE) -> atset1:27 */
|
||||
BrowserBack: 0xe06a, /* html:BrowserBack (BrowserBack) -> linux:158 (KEY_BACK) -> atset1:57450 */
|
||||
BrowserFavorites: 0xe066, /* html:BrowserFavorites (BrowserFavorites) -> linux:156 (KEY_BOOKMARKS) -> atset1:57446 */
|
||||
BrowserForward: 0xe069, /* html:BrowserForward (BrowserForward) -> linux:159 (KEY_FORWARD) -> atset1:57449 */
|
||||
BrowserHome: 0xe032, /* html:BrowserHome (BrowserHome) -> linux:172 (KEY_HOMEPAGE) -> atset1:57394 */
|
||||
BrowserRefresh: 0xe067, /* html:BrowserRefresh (BrowserRefresh) -> linux:173 (KEY_REFRESH) -> atset1:57447 */
|
||||
BrowserSearch: 0xe065, /* html:BrowserSearch (BrowserSearch) -> linux:217 (KEY_SEARCH) -> atset1:57445 */
|
||||
BrowserStop: 0xe068, /* html:BrowserStop (BrowserStop) -> linux:128 (KEY_STOP) -> atset1:57448 */
|
||||
CapsLock: 0x3a, /* html:CapsLock (CapsLock) -> linux:58 (KEY_CAPSLOCK) -> atset1:58 */
|
||||
Comma: 0x33, /* html:Comma (Comma) -> linux:51 (KEY_COMMA) -> atset1:51 */
|
||||
ContextMenu: 0xe05d, /* html:ContextMenu (ContextMenu) -> linux:127 (KEY_COMPOSE) -> atset1:57437 */
|
||||
ControlLeft: 0x1d, /* html:ControlLeft (ControlLeft) -> linux:29 (KEY_LEFTCTRL) -> atset1:29 */
|
||||
ControlRight: 0xe01d, /* html:ControlRight (ControlRight) -> linux:97 (KEY_RIGHTCTRL) -> atset1:57373 */
|
||||
Convert: 0x79, /* html:Convert (Convert) -> linux:92 (KEY_HENKAN) -> atset1:121 */
|
||||
Copy: 0xe078, /* html:Copy (Copy) -> linux:133 (KEY_COPY) -> atset1:57464 */
|
||||
Cut: 0xe03c, /* html:Cut (Cut) -> linux:137 (KEY_CUT) -> atset1:57404 */
|
||||
Delete: 0xe053, /* html:Delete (Delete) -> linux:111 (KEY_DELETE) -> atset1:57427 */
|
||||
Digit0: 0xb, /* html:Digit0 (Digit0) -> linux:11 (KEY_0) -> atset1:11 */
|
||||
Digit1: 0x2, /* html:Digit1 (Digit1) -> linux:2 (KEY_1) -> atset1:2 */
|
||||
Digit2: 0x3, /* html:Digit2 (Digit2) -> linux:3 (KEY_2) -> atset1:3 */
|
||||
Digit3: 0x4, /* html:Digit3 (Digit3) -> linux:4 (KEY_3) -> atset1:4 */
|
||||
Digit4: 0x5, /* html:Digit4 (Digit4) -> linux:5 (KEY_4) -> atset1:5 */
|
||||
Digit5: 0x6, /* html:Digit5 (Digit5) -> linux:6 (KEY_5) -> atset1:6 */
|
||||
Digit6: 0x7, /* html:Digit6 (Digit6) -> linux:7 (KEY_6) -> atset1:7 */
|
||||
Digit7: 0x8, /* html:Digit7 (Digit7) -> linux:8 (KEY_7) -> atset1:8 */
|
||||
Digit8: 0x9, /* html:Digit8 (Digit8) -> linux:9 (KEY_8) -> atset1:9 */
|
||||
Digit9: 0xa, /* html:Digit9 (Digit9) -> linux:10 (KEY_9) -> atset1:10 */
|
||||
Eject: 0xe07d, /* html:Eject (Eject) -> linux:162 (KEY_EJECTCLOSECD) -> atset1:57469 */
|
||||
End: 0xe04f, /* html:End (End) -> linux:107 (KEY_END) -> atset1:57423 */
|
||||
Enter: 0x1c, /* html:Enter (Enter) -> linux:28 (KEY_ENTER) -> atset1:28 */
|
||||
Equal: 0xd, /* html:Equal (Equal) -> linux:13 (KEY_EQUAL) -> atset1:13 */
|
||||
Escape: 0x1, /* html:Escape (Escape) -> linux:1 (KEY_ESC) -> atset1:1 */
|
||||
F1: 0x3b, /* html:F1 (F1) -> linux:59 (KEY_F1) -> atset1:59 */
|
||||
F10: 0x44, /* html:F10 (F10) -> linux:68 (KEY_F10) -> atset1:68 */
|
||||
F11: 0x57, /* html:F11 (F11) -> linux:87 (KEY_F11) -> atset1:87 */
|
||||
F12: 0x58, /* html:F12 (F12) -> linux:88 (KEY_F12) -> atset1:88 */
|
||||
F13: 0x5d, /* html:F13 (F13) -> linux:183 (KEY_F13) -> atset1:93 */
|
||||
F14: 0x5e, /* html:F14 (F14) -> linux:184 (KEY_F14) -> atset1:94 */
|
||||
F15: 0x5f, /* html:F15 (F15) -> linux:185 (KEY_F15) -> atset1:95 */
|
||||
F16: 0x55, /* html:F16 (F16) -> linux:186 (KEY_F16) -> atset1:85 */
|
||||
F17: 0xe003, /* html:F17 (F17) -> linux:187 (KEY_F17) -> atset1:57347 */
|
||||
F18: 0xe077, /* html:F18 (F18) -> linux:188 (KEY_F18) -> atset1:57463 */
|
||||
F19: 0xe004, /* html:F19 (F19) -> linux:189 (KEY_F19) -> atset1:57348 */
|
||||
F2: 0x3c, /* html:F2 (F2) -> linux:60 (KEY_F2) -> atset1:60 */
|
||||
F20: 0x5a, /* html:F20 (F20) -> linux:190 (KEY_F20) -> atset1:90 */
|
||||
F21: 0x74, /* html:F21 (F21) -> linux:191 (KEY_F21) -> atset1:116 */
|
||||
F22: 0xe079, /* html:F22 (F22) -> linux:192 (KEY_F22) -> atset1:57465 */
|
||||
F23: 0x6d, /* html:F23 (F23) -> linux:193 (KEY_F23) -> atset1:109 */
|
||||
F24: 0x6f, /* html:F24 (F24) -> linux:194 (KEY_F24) -> atset1:111 */
|
||||
F3: 0x3d, /* html:F3 (F3) -> linux:61 (KEY_F3) -> atset1:61 */
|
||||
F4: 0x3e, /* html:F4 (F4) -> linux:62 (KEY_F4) -> atset1:62 */
|
||||
F5: 0x3f, /* html:F5 (F5) -> linux:63 (KEY_F5) -> atset1:63 */
|
||||
F6: 0x40, /* html:F6 (F6) -> linux:64 (KEY_F6) -> atset1:64 */
|
||||
F7: 0x41, /* html:F7 (F7) -> linux:65 (KEY_F7) -> atset1:65 */
|
||||
F8: 0x42, /* html:F8 (F8) -> linux:66 (KEY_F8) -> atset1:66 */
|
||||
F9: 0x43, /* html:F9 (F9) -> linux:67 (KEY_F9) -> atset1:67 */
|
||||
Find: 0xe041, /* html:Find (Find) -> linux:136 (KEY_FIND) -> atset1:57409 */
|
||||
Help: 0xe075, /* html:Help (Help) -> linux:138 (KEY_HELP) -> atset1:57461 */
|
||||
Hiragana: 0x77, /* html:Hiragana (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */
|
||||
Home: 0xe047, /* html:Home (Home) -> linux:102 (KEY_HOME) -> atset1:57415 */
|
||||
Insert: 0xe052, /* html:Insert (Insert) -> linux:110 (KEY_INSERT) -> atset1:57426 */
|
||||
IntlBackslash: 0x56, /* html:IntlBackslash (IntlBackslash) -> linux:86 (KEY_102ND) -> atset1:86 */
|
||||
IntlRo: 0x73, /* html:IntlRo (IntlRo) -> linux:89 (KEY_RO) -> atset1:115 */
|
||||
IntlYen: 0x7d, /* html:IntlYen (IntlYen) -> linux:124 (KEY_YEN) -> atset1:125 */
|
||||
KanaMode: 0x70, /* html:KanaMode (KanaMode) -> linux:93 (KEY_KATAKANAHIRAGANA) -> atset1:112 */
|
||||
Katakana: 0x78, /* html:Katakana (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */
|
||||
KeyA: 0x1e, /* html:KeyA (KeyA) -> linux:30 (KEY_A) -> atset1:30 */
|
||||
KeyB: 0x30, /* html:KeyB (KeyB) -> linux:48 (KEY_B) -> atset1:48 */
|
||||
KeyC: 0x2e, /* html:KeyC (KeyC) -> linux:46 (KEY_C) -> atset1:46 */
|
||||
KeyD: 0x20, /* html:KeyD (KeyD) -> linux:32 (KEY_D) -> atset1:32 */
|
||||
KeyE: 0x12, /* html:KeyE (KeyE) -> linux:18 (KEY_E) -> atset1:18 */
|
||||
KeyF: 0x21, /* html:KeyF (KeyF) -> linux:33 (KEY_F) -> atset1:33 */
|
||||
KeyG: 0x22, /* html:KeyG (KeyG) -> linux:34 (KEY_G) -> atset1:34 */
|
||||
KeyH: 0x23, /* html:KeyH (KeyH) -> linux:35 (KEY_H) -> atset1:35 */
|
||||
KeyI: 0x17, /* html:KeyI (KeyI) -> linux:23 (KEY_I) -> atset1:23 */
|
||||
KeyJ: 0x24, /* html:KeyJ (KeyJ) -> linux:36 (KEY_J) -> atset1:36 */
|
||||
KeyK: 0x25, /* html:KeyK (KeyK) -> linux:37 (KEY_K) -> atset1:37 */
|
||||
KeyL: 0x26, /* html:KeyL (KeyL) -> linux:38 (KEY_L) -> atset1:38 */
|
||||
KeyM: 0x32, /* html:KeyM (KeyM) -> linux:50 (KEY_M) -> atset1:50 */
|
||||
KeyN: 0x31, /* html:KeyN (KeyN) -> linux:49 (KEY_N) -> atset1:49 */
|
||||
KeyO: 0x18, /* html:KeyO (KeyO) -> linux:24 (KEY_O) -> atset1:24 */
|
||||
KeyP: 0x19, /* html:KeyP (KeyP) -> linux:25 (KEY_P) -> atset1:25 */
|
||||
KeyQ: 0x10, /* html:KeyQ (KeyQ) -> linux:16 (KEY_Q) -> atset1:16 */
|
||||
KeyR: 0x13, /* html:KeyR (KeyR) -> linux:19 (KEY_R) -> atset1:19 */
|
||||
KeyS: 0x1f, /* html:KeyS (KeyS) -> linux:31 (KEY_S) -> atset1:31 */
|
||||
KeyT: 0x14, /* html:KeyT (KeyT) -> linux:20 (KEY_T) -> atset1:20 */
|
||||
KeyU: 0x16, /* html:KeyU (KeyU) -> linux:22 (KEY_U) -> atset1:22 */
|
||||
KeyV: 0x2f, /* html:KeyV (KeyV) -> linux:47 (KEY_V) -> atset1:47 */
|
||||
KeyW: 0x11, /* html:KeyW (KeyW) -> linux:17 (KEY_W) -> atset1:17 */
|
||||
KeyX: 0x2d, /* html:KeyX (KeyX) -> linux:45 (KEY_X) -> atset1:45 */
|
||||
KeyY: 0x15, /* html:KeyY (KeyY) -> linux:21 (KEY_Y) -> atset1:21 */
|
||||
KeyZ: 0x2c, /* html:KeyZ (KeyZ) -> linux:44 (KEY_Z) -> atset1:44 */
|
||||
Lang3: 0x78, /* html:Lang3 (Lang3) -> linux:90 (KEY_KATAKANA) -> atset1:120 */
|
||||
Lang4: 0x77, /* html:Lang4 (Lang4) -> linux:91 (KEY_HIRAGANA) -> atset1:119 */
|
||||
Lang5: 0x76, /* html:Lang5 (Lang5) -> linux:85 (KEY_ZENKAKUHANKAKU) -> atset1:118 */
|
||||
LaunchApp1: 0xe06b, /* html:LaunchApp1 (LaunchApp1) -> linux:157 (KEY_COMPUTER) -> atset1:57451 */
|
||||
LaunchApp2: 0xe021, /* html:LaunchApp2 (LaunchApp2) -> linux:140 (KEY_CALC) -> atset1:57377 */
|
||||
LaunchMail: 0xe06c, /* html:LaunchMail (LaunchMail) -> linux:155 (KEY_MAIL) -> atset1:57452 */
|
||||
MediaPlayPause: 0xe022, /* html:MediaPlayPause (MediaPlayPause) -> linux:164 (KEY_PLAYPAUSE) -> atset1:57378 */
|
||||
MediaSelect: 0xe06d, /* html:MediaSelect (MediaSelect) -> linux:226 (KEY_MEDIA) -> atset1:57453 */
|
||||
MediaStop: 0xe024, /* html:MediaStop (MediaStop) -> linux:166 (KEY_STOPCD) -> atset1:57380 */
|
||||
MediaTrackNext: 0xe019, /* html:MediaTrackNext (MediaTrackNext) -> linux:163 (KEY_NEXTSONG) -> atset1:57369 */
|
||||
MediaTrackPrevious: 0xe010, /* html:MediaTrackPrevious (MediaTrackPrevious) -> linux:165 (KEY_PREVIOUSSONG) -> atset1:57360 */
|
||||
MetaLeft: 0xe05b, /* html:MetaLeft (MetaLeft) -> linux:125 (KEY_LEFTMETA) -> atset1:57435 */
|
||||
MetaRight: 0xe05c, /* html:MetaRight (MetaRight) -> linux:126 (KEY_RIGHTMETA) -> atset1:57436 */
|
||||
Minus: 0xc, /* html:Minus (Minus) -> linux:12 (KEY_MINUS) -> atset1:12 */
|
||||
NonConvert: 0x7b, /* html:NonConvert (NonConvert) -> linux:94 (KEY_MUHENKAN) -> atset1:123 */
|
||||
NumLock: 0x45, /* html:NumLock (NumLock) -> linux:69 (KEY_NUMLOCK) -> atset1:69 */
|
||||
Numpad0: 0x52, /* html:Numpad0 (Numpad0) -> linux:82 (KEY_KP0) -> atset1:82 */
|
||||
Numpad1: 0x4f, /* html:Numpad1 (Numpad1) -> linux:79 (KEY_KP1) -> atset1:79 */
|
||||
Numpad2: 0x50, /* html:Numpad2 (Numpad2) -> linux:80 (KEY_KP2) -> atset1:80 */
|
||||
Numpad3: 0x51, /* html:Numpad3 (Numpad3) -> linux:81 (KEY_KP3) -> atset1:81 */
|
||||
Numpad4: 0x4b, /* html:Numpad4 (Numpad4) -> linux:75 (KEY_KP4) -> atset1:75 */
|
||||
Numpad5: 0x4c, /* html:Numpad5 (Numpad5) -> linux:76 (KEY_KP5) -> atset1:76 */
|
||||
Numpad6: 0x4d, /* html:Numpad6 (Numpad6) -> linux:77 (KEY_KP6) -> atset1:77 */
|
||||
Numpad7: 0x47, /* html:Numpad7 (Numpad7) -> linux:71 (KEY_KP7) -> atset1:71 */
|
||||
Numpad8: 0x48, /* html:Numpad8 (Numpad8) -> linux:72 (KEY_KP8) -> atset1:72 */
|
||||
Numpad9: 0x49, /* html:Numpad9 (Numpad9) -> linux:73 (KEY_KP9) -> atset1:73 */
|
||||
NumpadAdd: 0x4e, /* html:NumpadAdd (NumpadAdd) -> linux:78 (KEY_KPPLUS) -> atset1:78 */
|
||||
NumpadComma: 0x7e, /* html:NumpadComma (NumpadComma) -> linux:121 (KEY_KPCOMMA) -> atset1:126 */
|
||||
NumpadDecimal: 0x53, /* html:NumpadDecimal (NumpadDecimal) -> linux:83 (KEY_KPDOT) -> atset1:83 */
|
||||
NumpadDivide: 0xe035, /* html:NumpadDivide (NumpadDivide) -> linux:98 (KEY_KPSLASH) -> atset1:57397 */
|
||||
NumpadEnter: 0xe01c, /* html:NumpadEnter (NumpadEnter) -> linux:96 (KEY_KPENTER) -> atset1:57372 */
|
||||
NumpadEqual: 0x59, /* html:NumpadEqual (NumpadEqual) -> linux:117 (KEY_KPEQUAL) -> atset1:89 */
|
||||
NumpadMultiply: 0x37, /* html:NumpadMultiply (NumpadMultiply) -> linux:55 (KEY_KPASTERISK) -> atset1:55 */
|
||||
NumpadParenLeft: 0xe076, /* html:NumpadParenLeft (NumpadParenLeft) -> linux:179 (KEY_KPLEFTPAREN) -> atset1:57462 */
|
||||
NumpadParenRight: 0xe07b, /* html:NumpadParenRight (NumpadParenRight) -> linux:180 (KEY_KPRIGHTPAREN) -> atset1:57467 */
|
||||
NumpadSubtract: 0x4a, /* html:NumpadSubtract (NumpadSubtract) -> linux:74 (KEY_KPMINUS) -> atset1:74 */
|
||||
Open: 0x64, /* html:Open (Open) -> linux:134 (KEY_OPEN) -> atset1:100 */
|
||||
PageDown: 0xe051, /* html:PageDown (PageDown) -> linux:109 (KEY_PAGEDOWN) -> atset1:57425 */
|
||||
PageUp: 0xe049, /* html:PageUp (PageUp) -> linux:104 (KEY_PAGEUP) -> atset1:57417 */
|
||||
Paste: 0x65, /* html:Paste (Paste) -> linux:135 (KEY_PASTE) -> atset1:101 */
|
||||
Pause: 0xe046, /* html:Pause (Pause) -> linux:119 (KEY_PAUSE) -> atset1:57414 */
|
||||
Period: 0x34, /* html:Period (Period) -> linux:52 (KEY_DOT) -> atset1:52 */
|
||||
Power: 0xe05e, /* html:Power (Power) -> linux:116 (KEY_POWER) -> atset1:57438 */
|
||||
PrintScreen: 0x54, /* html:PrintScreen (PrintScreen) -> linux:99 (KEY_SYSRQ) -> atset1:84 */
|
||||
Props: 0xe006, /* html:Props (Props) -> linux:130 (KEY_PROPS) -> atset1:57350 */
|
||||
Quote: 0x28, /* html:Quote (Quote) -> linux:40 (KEY_APOSTROPHE) -> atset1:40 */
|
||||
ScrollLock: 0x46, /* html:ScrollLock (ScrollLock) -> linux:70 (KEY_SCROLLLOCK) -> atset1:70 */
|
||||
Semicolon: 0x27, /* html:Semicolon (Semicolon) -> linux:39 (KEY_SEMICOLON) -> atset1:39 */
|
||||
ShiftLeft: 0x2a, /* html:ShiftLeft (ShiftLeft) -> linux:42 (KEY_LEFTSHIFT) -> atset1:42 */
|
||||
ShiftRight: 0x36, /* html:ShiftRight (ShiftRight) -> linux:54 (KEY_RIGHTSHIFT) -> atset1:54 */
|
||||
Slash: 0x35, /* html:Slash (Slash) -> linux:53 (KEY_SLASH) -> atset1:53 */
|
||||
Sleep: 0xe05f, /* html:Sleep (Sleep) -> linux:142 (KEY_SLEEP) -> atset1:57439 */
|
||||
Space: 0x39, /* html:Space (Space) -> linux:57 (KEY_SPACE) -> atset1:57 */
|
||||
Suspend: 0xe025, /* html:Suspend (Suspend) -> linux:205 (KEY_SUSPEND) -> atset1:57381 */
|
||||
Tab: 0xf, /* html:Tab (Tab) -> linux:15 (KEY_TAB) -> atset1:15 */
|
||||
Undo: 0xe007, /* html:Undo (Undo) -> linux:131 (KEY_UNDO) -> atset1:57351 */
|
||||
WakeUp: 0xe063, /* html:WakeUp (WakeUp) -> linux:143 (KEY_WAKEUP) -> atset1:57443 */
|
||||
};
|
||||
|
|
|
|||
4585
core/rfb.js
4585
core/rfb.js
File diff suppressed because it is too large
Load Diff
|
|
@ -9,65 +9,64 @@
|
|||
import * as Log from './logging.js';
|
||||
|
||||
// Touch detection
|
||||
export let isTouchDevice = ('ontouchstart' in document.documentElement) ||
|
||||
export let isTouchDevice = ('ontouchstart' in document.documentElement)
|
||||
// requried for Chrome debugger
|
||||
(document.ontouchstart !== undefined) ||
|
||||
|| (document.ontouchstart !== undefined)
|
||||
// required for MS Surface
|
||||
(navigator.maxTouchPoints > 0) ||
|
||||
(navigator.msMaxTouchPoints > 0);
|
||||
|| (navigator.maxTouchPoints > 0)
|
||||
|| (navigator.msMaxTouchPoints > 0);
|
||||
window.addEventListener('touchstart', function onFirstTouch() {
|
||||
isTouchDevice = true;
|
||||
window.removeEventListener('touchstart', onFirstTouch, false);
|
||||
isTouchDevice = true;
|
||||
window.removeEventListener('touchstart', onFirstTouch, false);
|
||||
}, false);
|
||||
|
||||
let _cursor_uris_supported = null;
|
||||
|
||||
export function supportsCursorURIs () {
|
||||
if (_cursor_uris_supported === null) {
|
||||
try {
|
||||
const target = document.createElement('canvas');
|
||||
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
|
||||
export function supportsCursorURIs() {
|
||||
if (_cursor_uris_supported === null) {
|
||||
try {
|
||||
const target = document.createElement('canvas');
|
||||
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
|
||||
|
||||
if (target.style.cursor) {
|
||||
Log.Info("Data URI scheme cursor supported");
|
||||
_cursor_uris_supported = true;
|
||||
} else {
|
||||
Log.Warn("Data URI scheme cursor not supported");
|
||||
_cursor_uris_supported = false;
|
||||
}
|
||||
} catch (exc) {
|
||||
Log.Error("Data URI scheme cursor test exception: " + exc);
|
||||
_cursor_uris_supported = false;
|
||||
}
|
||||
if (target.style.cursor) {
|
||||
Log.Info('Data URI scheme cursor supported');
|
||||
_cursor_uris_supported = true;
|
||||
} else {
|
||||
Log.Warn('Data URI scheme cursor not supported');
|
||||
_cursor_uris_supported = false;
|
||||
}
|
||||
} catch (exc) {
|
||||
Log.Error('Data URI scheme cursor test exception: ' + exc);
|
||||
_cursor_uris_supported = false;
|
||||
}
|
||||
}
|
||||
|
||||
return _cursor_uris_supported;
|
||||
return _cursor_uris_supported;
|
||||
}
|
||||
|
||||
export function isMac() {
|
||||
return navigator && !!(/mac/i).exec(navigator.platform);
|
||||
return navigator && !!(/mac/i).exec(navigator.platform);
|
||||
}
|
||||
|
||||
export function isIE() {
|
||||
return navigator && !!(/trident/i).exec(navigator.userAgent);
|
||||
return navigator && !!(/trident/i).exec(navigator.userAgent);
|
||||
}
|
||||
|
||||
export function isEdge() {
|
||||
return navigator && !!(/edge/i).exec(navigator.userAgent);
|
||||
return navigator && !!(/edge/i).exec(navigator.userAgent);
|
||||
}
|
||||
|
||||
export function isFirefox() {
|
||||
return navigator && !!(/firefox/i).exec(navigator.userAgent);
|
||||
return navigator && !!(/firefox/i).exec(navigator.userAgent);
|
||||
}
|
||||
|
||||
export function isWindows() {
|
||||
return navigator && !!(/win/i).exec(navigator.platform);
|
||||
return navigator && !!(/win/i).exec(navigator.platform);
|
||||
}
|
||||
|
||||
export function isIOS() {
|
||||
return navigator &&
|
||||
(!!(/ipad/i).exec(navigator.platform) ||
|
||||
!!(/iphone/i).exec(navigator.platform) ||
|
||||
!!(/ipod/i).exec(navigator.platform));
|
||||
return navigator
|
||||
&& (!!(/ipad/i).exec(navigator.platform)
|
||||
|| !!(/iphone/i).exec(navigator.platform)
|
||||
|| !!(/ipod/i).exec(navigator.platform));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,220 +9,213 @@ import { supportsCursorURIs, isTouchDevice } from './browser.js';
|
|||
const useFallback = !supportsCursorURIs() || isTouchDevice;
|
||||
|
||||
export default class Cursor {
|
||||
constructor(container) {
|
||||
this._target = null;
|
||||
constructor(container) {
|
||||
this._target = null;
|
||||
|
||||
this._canvas = document.createElement('canvas');
|
||||
this._canvas = document.createElement('canvas');
|
||||
|
||||
if (useFallback) {
|
||||
this._canvas.style.position = 'fixed';
|
||||
this._canvas.style.zIndex = '65535';
|
||||
this._canvas.style.pointerEvents = 'none';
|
||||
// Can't use "display" because of Firefox bug #1445997
|
||||
this._canvas.style.visibility = 'hidden';
|
||||
document.body.appendChild(this._canvas);
|
||||
}
|
||||
|
||||
this._position = { x: 0, y: 0 };
|
||||
this._hotSpot = { x: 0, y: 0 };
|
||||
|
||||
this._eventHandlers = {
|
||||
'mouseover': this._handleMouseOver.bind(this),
|
||||
'mouseleave': this._handleMouseLeave.bind(this),
|
||||
'mousemove': this._handleMouseMove.bind(this),
|
||||
'mouseup': this._handleMouseUp.bind(this),
|
||||
'touchstart': this._handleTouchStart.bind(this),
|
||||
'touchmove': this._handleTouchMove.bind(this),
|
||||
'touchend': this._handleTouchEnd.bind(this),
|
||||
};
|
||||
if (useFallback) {
|
||||
this._canvas.style.position = 'fixed';
|
||||
this._canvas.style.zIndex = '65535';
|
||||
this._canvas.style.pointerEvents = 'none';
|
||||
// Can't use "display" because of Firefox bug #1445997
|
||||
this._canvas.style.visibility = 'hidden';
|
||||
document.body.appendChild(this._canvas);
|
||||
}
|
||||
|
||||
attach(target) {
|
||||
if (this._target) {
|
||||
this.detach();
|
||||
}
|
||||
this._position = { x: 0, y: 0 };
|
||||
this._hotSpot = { x: 0, y: 0 };
|
||||
|
||||
this._target = target;
|
||||
this._eventHandlers = {
|
||||
mouseover: this._handleMouseOver.bind(this),
|
||||
mouseleave: this._handleMouseLeave.bind(this),
|
||||
mousemove: this._handleMouseMove.bind(this),
|
||||
mouseup: this._handleMouseUp.bind(this),
|
||||
touchstart: this._handleTouchStart.bind(this),
|
||||
touchmove: this._handleTouchMove.bind(this),
|
||||
touchend: this._handleTouchEnd.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
if (useFallback) {
|
||||
// FIXME: These don't fire properly except for mouse
|
||||
/// movement in IE. We want to also capture element
|
||||
// movement, size changes, visibility, etc.
|
||||
const options = { capture: true, passive: true };
|
||||
this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
||||
this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);
|
||||
this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);
|
||||
|
||||
// There is no "touchleave" so we monitor touchstart globally
|
||||
window.addEventListener('touchstart', this._eventHandlers.touchstart, options);
|
||||
this._target.addEventListener('touchmove', this._eventHandlers.touchmove, options);
|
||||
this._target.addEventListener('touchend', this._eventHandlers.touchend, options);
|
||||
}
|
||||
|
||||
this.clear();
|
||||
attach(target) {
|
||||
if (this._target) {
|
||||
this.detach();
|
||||
}
|
||||
|
||||
detach() {
|
||||
if (useFallback) {
|
||||
const options = { capture: true, passive: true };
|
||||
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||
this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
||||
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
|
||||
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
|
||||
this._target = target;
|
||||
|
||||
window.removeEventListener('touchstart', this._eventHandlers.touchstart, options);
|
||||
this._target.removeEventListener('touchmove', this._eventHandlers.touchmove, options);
|
||||
this._target.removeEventListener('touchend', this._eventHandlers.touchend, options);
|
||||
}
|
||||
if (useFallback) {
|
||||
// FIXME: These don't fire properly except for mouse
|
||||
// / movement in IE. We want to also capture element
|
||||
// movement, size changes, visibility, etc.
|
||||
const options = { capture: true, passive: true };
|
||||
this._target.addEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||
this._target.addEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
||||
this._target.addEventListener('mousemove', this._eventHandlers.mousemove, options);
|
||||
this._target.addEventListener('mouseup', this._eventHandlers.mouseup, options);
|
||||
|
||||
this._target = null;
|
||||
// There is no "touchleave" so we monitor touchstart globally
|
||||
window.addEventListener('touchstart', this._eventHandlers.touchstart, options);
|
||||
this._target.addEventListener('touchmove', this._eventHandlers.touchmove, options);
|
||||
this._target.addEventListener('touchend', this._eventHandlers.touchend, options);
|
||||
}
|
||||
|
||||
change(pixels, mask, hotx, hoty, w, h) {
|
||||
if ((w === 0) || (h === 0)) {
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
this.clear();
|
||||
}
|
||||
|
||||
let cur = []
|
||||
for (let y = 0; y < h; y++) {
|
||||
for (let x = 0; x < w; x++) {
|
||||
let idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
|
||||
let alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
|
||||
idx = ((w * y) + x) * 4;
|
||||
cur.push(pixels[idx + 2]); // red
|
||||
cur.push(pixels[idx + 1]); // green
|
||||
cur.push(pixels[idx]); // blue
|
||||
cur.push(alpha); // alpha
|
||||
}
|
||||
}
|
||||
detach() {
|
||||
if (useFallback) {
|
||||
const options = { capture: true, passive: true };
|
||||
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
|
||||
this._target.removeEventListener('mouseleave', this._eventHandlers.mouseleave, options);
|
||||
this._target.removeEventListener('mousemove', this._eventHandlers.mousemove, options);
|
||||
this._target.removeEventListener('mouseup', this._eventHandlers.mouseup, options);
|
||||
|
||||
this._position.x = this._position.x + this._hotSpot.x - hotx;
|
||||
this._position.y = this._position.y + this._hotSpot.y - hoty;
|
||||
this._hotSpot.x = hotx;
|
||||
this._hotSpot.y = hoty;
|
||||
|
||||
let ctx = this._canvas.getContext('2d');
|
||||
|
||||
this._canvas.width = w;
|
||||
this._canvas.height = h;
|
||||
|
||||
let img;
|
||||
try {
|
||||
// IE doesn't support this
|
||||
img = new ImageData(new Uint8ClampedArray(cur), w, h);
|
||||
} catch (ex) {
|
||||
img = ctx.createImageData(w, h);
|
||||
img.data.set(new Uint8ClampedArray(cur));
|
||||
}
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.putImageData(img, 0, 0);
|
||||
|
||||
if (useFallback) {
|
||||
this._updatePosition();
|
||||
} else {
|
||||
let url = this._canvas.toDataURL();
|
||||
this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
|
||||
}
|
||||
window.removeEventListener('touchstart', this._eventHandlers.touchstart, options);
|
||||
this._target.removeEventListener('touchmove', this._eventHandlers.touchmove, options);
|
||||
this._target.removeEventListener('touchend', this._eventHandlers.touchend, options);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._target.style.cursor = 'none';
|
||||
this._canvas.width = 0;
|
||||
this._canvas.height = 0;
|
||||
this._position.x = this._position.x + this._hotSpot.x;
|
||||
this._position.y = this._position.y + this._hotSpot.y;
|
||||
this._hotSpot.x = 0;
|
||||
this._hotSpot.y = 0;
|
||||
this._target = null;
|
||||
}
|
||||
|
||||
change(pixels, mask, hotx, hoty, w, h) {
|
||||
if ((w === 0) || (h === 0)) {
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
_handleMouseOver(event) {
|
||||
// This event could be because we're entering the target, or
|
||||
// moving around amongst its sub elements. Let the move handler
|
||||
// sort things out.
|
||||
this._handleMouseMove(event);
|
||||
let cur = [];
|
||||
for (let y = 0; y < h; y++) {
|
||||
for (let x = 0; x < w; x++) {
|
||||
let idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
|
||||
let alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
|
||||
idx = ((w * y) + x) * 4;
|
||||
cur.push(pixels[idx + 2]); // red
|
||||
cur.push(pixels[idx + 1]); // green
|
||||
cur.push(pixels[idx]); // blue
|
||||
cur.push(alpha); // alpha
|
||||
}
|
||||
}
|
||||
|
||||
_handleMouseLeave(event) {
|
||||
this._hideCursor();
|
||||
this._position.x = this._position.x + this._hotSpot.x - hotx;
|
||||
this._position.y = this._position.y + this._hotSpot.y - hoty;
|
||||
this._hotSpot.x = hotx;
|
||||
this._hotSpot.y = hoty;
|
||||
|
||||
let ctx = this._canvas.getContext('2d');
|
||||
|
||||
this._canvas.width = w;
|
||||
this._canvas.height = h;
|
||||
|
||||
let img;
|
||||
try {
|
||||
// IE doesn't support this
|
||||
img = new ImageData(new Uint8ClampedArray(cur), w, h);
|
||||
} catch (ex) {
|
||||
img = ctx.createImageData(w, h);
|
||||
img.data.set(new Uint8ClampedArray(cur));
|
||||
}
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.putImageData(img, 0, 0);
|
||||
|
||||
_handleMouseMove(event) {
|
||||
this._updateVisibility(event.target);
|
||||
|
||||
this._position.x = event.clientX - this._hotSpot.x;
|
||||
this._position.y = event.clientY - this._hotSpot.y;
|
||||
|
||||
this._updatePosition();
|
||||
if (useFallback) {
|
||||
this._updatePosition();
|
||||
} else {
|
||||
let url = this._canvas.toDataURL();
|
||||
this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
|
||||
}
|
||||
}
|
||||
|
||||
_handleMouseUp(event) {
|
||||
// We might get this event because of a drag operation that
|
||||
// moved outside of the target. Check what's under the cursor
|
||||
// now and adjust visibility based on that.
|
||||
let target = document.elementFromPoint(event.clientX, event.clientY);
|
||||
this._updateVisibility(target);
|
||||
}
|
||||
clear() {
|
||||
this._target.style.cursor = 'none';
|
||||
this._canvas.width = 0;
|
||||
this._canvas.height = 0;
|
||||
this._position.x = this._position.x + this._hotSpot.x;
|
||||
this._position.y = this._position.y + this._hotSpot.y;
|
||||
this._hotSpot.x = 0;
|
||||
this._hotSpot.y = 0;
|
||||
}
|
||||
|
||||
_handleTouchStart(event) {
|
||||
// Just as for mouseover, we let the move handler deal with it
|
||||
this._handleTouchMove(event);
|
||||
}
|
||||
_handleMouseOver(event) {
|
||||
// This event could be because we're entering the target, or
|
||||
// moving around amongst its sub elements. Let the move handler
|
||||
// sort things out.
|
||||
this._handleMouseMove(event);
|
||||
}
|
||||
|
||||
_handleTouchMove(event) {
|
||||
this._updateVisibility(event.target);
|
||||
_handleMouseLeave(event) {
|
||||
this._hideCursor();
|
||||
}
|
||||
|
||||
this._position.x = event.changedTouches[0].clientX - this._hotSpot.x;
|
||||
this._position.y = event.changedTouches[0].clientY - this._hotSpot.y;
|
||||
_handleMouseMove(event) {
|
||||
this._updateVisibility(event.target);
|
||||
|
||||
this._updatePosition();
|
||||
}
|
||||
this._position.x = event.clientX - this._hotSpot.x;
|
||||
this._position.y = event.clientY - this._hotSpot.y;
|
||||
|
||||
_handleTouchEnd(event) {
|
||||
// Same principle as for mouseup
|
||||
let target = document.elementFromPoint(event.changedTouches[0].clientX,
|
||||
event.changedTouches[0].clientY);
|
||||
this._updateVisibility(target);
|
||||
}
|
||||
this._updatePosition();
|
||||
}
|
||||
|
||||
_showCursor() {
|
||||
if (this._canvas.style.visibility === 'hidden')
|
||||
this._canvas.style.visibility = '';
|
||||
}
|
||||
_handleMouseUp(event) {
|
||||
// We might get this event because of a drag operation that
|
||||
// moved outside of the target. Check what's under the cursor
|
||||
// now and adjust visibility based on that.
|
||||
let target = document.elementFromPoint(event.clientX, event.clientY);
|
||||
this._updateVisibility(target);
|
||||
}
|
||||
|
||||
_hideCursor() {
|
||||
if (this._canvas.style.visibility !== 'hidden')
|
||||
this._canvas.style.visibility = 'hidden';
|
||||
}
|
||||
_handleTouchStart(event) {
|
||||
// Just as for mouseover, we let the move handler deal with it
|
||||
this._handleTouchMove(event);
|
||||
}
|
||||
|
||||
// Should we currently display the cursor?
|
||||
// (i.e. are we over the target, or a child of the target without a
|
||||
// different cursor set)
|
||||
_shouldShowCursor(target) {
|
||||
// Easy case
|
||||
if (target === this._target)
|
||||
return true;
|
||||
// Other part of the DOM?
|
||||
if (!this._target.contains(target))
|
||||
return false;
|
||||
// Has the child its own cursor?
|
||||
// FIXME: How can we tell that a sub element has an
|
||||
// explicit "cursor: none;"?
|
||||
if (window.getComputedStyle(target).cursor !== 'none')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
_handleTouchMove(event) {
|
||||
this._updateVisibility(event.target);
|
||||
|
||||
_updateVisibility(target) {
|
||||
if (this._shouldShowCursor(target))
|
||||
this._showCursor();
|
||||
else
|
||||
this._hideCursor();
|
||||
}
|
||||
this._position.x = event.changedTouches[0].clientX - this._hotSpot.x;
|
||||
this._position.y = event.changedTouches[0].clientY - this._hotSpot.y;
|
||||
|
||||
_updatePosition() {
|
||||
this._canvas.style.left = this._position.x + "px";
|
||||
this._canvas.style.top = this._position.y + "px";
|
||||
}
|
||||
this._updatePosition();
|
||||
}
|
||||
|
||||
_handleTouchEnd(event) {
|
||||
// Same principle as for mouseup
|
||||
let target = document.elementFromPoint(event.changedTouches[0].clientX,
|
||||
event.changedTouches[0].clientY);
|
||||
this._updateVisibility(target);
|
||||
}
|
||||
|
||||
_showCursor() {
|
||||
if (this._canvas.style.visibility === 'hidden') this._canvas.style.visibility = '';
|
||||
}
|
||||
|
||||
_hideCursor() {
|
||||
if (this._canvas.style.visibility !== 'hidden') this._canvas.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
// Should we currently display the cursor?
|
||||
// (i.e. are we over the target, or a child of the target without a
|
||||
// different cursor set)
|
||||
_shouldShowCursor(target) {
|
||||
// Easy case
|
||||
if (target === this._target) return true;
|
||||
// Other part of the DOM?
|
||||
if (!this._target.contains(target)) return false;
|
||||
// Has the child its own cursor?
|
||||
// FIXME: How can we tell that a sub element has an
|
||||
// explicit "cursor: none;"?
|
||||
if (window.getComputedStyle(target).cursor !== 'none') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
_updateVisibility(target) {
|
||||
if (this._shouldShowCursor(target)) this._showCursor();
|
||||
else this._hideCursor();
|
||||
}
|
||||
|
||||
_updatePosition() {
|
||||
this._canvas.style.left = this._position.x + 'px';
|
||||
this._canvas.style.top = this._position.y + 'px';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,130 +10,128 @@
|
|||
* Cross-browser event and position routines
|
||||
*/
|
||||
|
||||
export function getPointerEvent (e) {
|
||||
return e.changedTouches ? e.changedTouches[0] : e.touches ? e.touches[0] : e;
|
||||
export function getPointerEvent(e) {
|
||||
if (e.changedTouches) return e.changedTouches[0];
|
||||
if (e.touches) return e.touches[0];
|
||||
return e;
|
||||
}
|
||||
|
||||
export function stopEvent (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
export function stopEvent(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// Emulate Element.setCapture() when not supported
|
||||
let _captureRecursion = false;
|
||||
let _captureElem = null;
|
||||
function _captureProxy(e) {
|
||||
// Recursion protection as we'll see our own event
|
||||
if (_captureRecursion) return;
|
||||
// Recursion protection as we'll see our own event
|
||||
if (_captureRecursion) return;
|
||||
|
||||
// Clone the event as we cannot dispatch an already dispatched event
|
||||
const newEv = new e.constructor(e.type, e);
|
||||
// Clone the event as we cannot dispatch an already dispatched event
|
||||
const newEv = new e.constructor(e.type, e);
|
||||
|
||||
_captureRecursion = true;
|
||||
_captureElem.dispatchEvent(newEv);
|
||||
_captureRecursion = false;
|
||||
_captureRecursion = true;
|
||||
_captureElem.dispatchEvent(newEv);
|
||||
_captureRecursion = false;
|
||||
|
||||
// Avoid double events
|
||||
e.stopPropagation();
|
||||
// Avoid double events
|
||||
e.stopPropagation();
|
||||
|
||||
// Respect the wishes of the redirected event handlers
|
||||
if (newEv.defaultPrevented) {
|
||||
e.preventDefault();
|
||||
}
|
||||
// Respect the wishes of the redirected event handlers
|
||||
if (newEv.defaultPrevented) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// Implicitly release the capture on button release
|
||||
if (e.type === "mouseup") {
|
||||
releaseCapture();
|
||||
}
|
||||
// Implicitly release the capture on button release
|
||||
if (e.type === 'mouseup') {
|
||||
releaseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
// Follow cursor style of target element
|
||||
function _captureElemChanged() {
|
||||
const captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor;
|
||||
const captureElem = document.getElementById('noVNC_mouse_capture_elem');
|
||||
captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor;
|
||||
}
|
||||
|
||||
const _captureObserver = new MutationObserver(_captureElemChanged);
|
||||
|
||||
let _captureIndex = 0;
|
||||
|
||||
export function setCapture (elem) {
|
||||
if (elem.setCapture) {
|
||||
export function setCapture(elem) {
|
||||
if (elem.setCapture) {
|
||||
elem.setCapture();
|
||||
|
||||
elem.setCapture();
|
||||
// IE releases capture on 'click' events which might not trigger
|
||||
elem.addEventListener('mouseup', releaseCapture);
|
||||
} else {
|
||||
// Release any existing capture in case this method is
|
||||
// called multiple times without coordination
|
||||
releaseCapture();
|
||||
|
||||
// IE releases capture on 'click' events which might not trigger
|
||||
elem.addEventListener('mouseup', releaseCapture);
|
||||
let captureElem = document.getElementById('noVNC_mouse_capture_elem');
|
||||
|
||||
} else {
|
||||
// Release any existing capture in case this method is
|
||||
// called multiple times without coordination
|
||||
releaseCapture();
|
||||
if (captureElem === null) {
|
||||
captureElem = document.createElement('div');
|
||||
captureElem.id = 'noVNC_mouse_capture_elem';
|
||||
captureElem.style.position = 'fixed';
|
||||
captureElem.style.top = '0px';
|
||||
captureElem.style.left = '0px';
|
||||
captureElem.style.width = '100%';
|
||||
captureElem.style.height = '100%';
|
||||
captureElem.style.zIndex = 10000;
|
||||
captureElem.style.display = 'none';
|
||||
document.body.appendChild(captureElem);
|
||||
|
||||
let captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
// This is to make sure callers don't get confused by having
|
||||
// our blocking element as the target
|
||||
captureElem.addEventListener('contextmenu', _captureProxy);
|
||||
|
||||
if (captureElem === null) {
|
||||
captureElem = document.createElement("div");
|
||||
captureElem.id = "noVNC_mouse_capture_elem";
|
||||
captureElem.style.position = "fixed";
|
||||
captureElem.style.top = "0px";
|
||||
captureElem.style.left = "0px";
|
||||
captureElem.style.width = "100%";
|
||||
captureElem.style.height = "100%";
|
||||
captureElem.style.zIndex = 10000;
|
||||
captureElem.style.display = "none";
|
||||
document.body.appendChild(captureElem);
|
||||
|
||||
// This is to make sure callers don't get confused by having
|
||||
// our blocking element as the target
|
||||
captureElem.addEventListener('contextmenu', _captureProxy);
|
||||
|
||||
captureElem.addEventListener('mousemove', _captureProxy);
|
||||
captureElem.addEventListener('mouseup', _captureProxy);
|
||||
}
|
||||
|
||||
_captureElem = elem;
|
||||
_captureIndex++;
|
||||
|
||||
// Track cursor and get initial cursor
|
||||
_captureObserver.observe(elem, {attributes:true});
|
||||
_captureElemChanged();
|
||||
|
||||
captureElem.style.display = "";
|
||||
|
||||
// We listen to events on window in order to keep tracking if it
|
||||
// happens to leave the viewport
|
||||
window.addEventListener('mousemove', _captureProxy);
|
||||
window.addEventListener('mouseup', _captureProxy);
|
||||
captureElem.addEventListener('mousemove', _captureProxy);
|
||||
captureElem.addEventListener('mouseup', _captureProxy);
|
||||
}
|
||||
|
||||
_captureElem = elem;
|
||||
_captureIndex++;
|
||||
|
||||
// Track cursor and get initial cursor
|
||||
_captureObserver.observe(elem, { attributes: true });
|
||||
_captureElemChanged();
|
||||
|
||||
captureElem.style.display = '';
|
||||
|
||||
// We listen to events on window in order to keep tracking if it
|
||||
// happens to leave the viewport
|
||||
window.addEventListener('mousemove', _captureProxy);
|
||||
window.addEventListener('mouseup', _captureProxy);
|
||||
}
|
||||
}
|
||||
|
||||
export function releaseCapture () {
|
||||
if (document.releaseCapture) {
|
||||
|
||||
document.releaseCapture();
|
||||
|
||||
} else {
|
||||
if (!_captureElem) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There might be events already queued, so we need to wait for
|
||||
// them to flush. E.g. contextmenu in Microsoft Edge
|
||||
window.setTimeout((expected) => {
|
||||
// Only clear it if it's the expected grab (i.e. no one
|
||||
// else has initiated a new grab)
|
||||
if (_captureIndex === expected) {
|
||||
_captureElem = null;
|
||||
}
|
||||
}, 0, _captureIndex);
|
||||
|
||||
_captureObserver.disconnect();
|
||||
|
||||
const captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
captureElem.style.display = "none";
|
||||
|
||||
window.removeEventListener('mousemove', _captureProxy);
|
||||
window.removeEventListener('mouseup', _captureProxy);
|
||||
export function releaseCapture() {
|
||||
if (document.releaseCapture) {
|
||||
document.releaseCapture();
|
||||
} else {
|
||||
if (!_captureElem) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There might be events already queued, so we need to wait for
|
||||
// them to flush. E.g. contextmenu in Microsoft Edge
|
||||
window.setTimeout((expected) => {
|
||||
// Only clear it if it's the expected grab (i.e. no one
|
||||
// else has initiated a new grab)
|
||||
if (_captureIndex === expected) {
|
||||
_captureElem = null;
|
||||
}
|
||||
}, 0, _captureIndex);
|
||||
|
||||
_captureObserver.disconnect();
|
||||
|
||||
const captureElem = document.getElementById('noVNC_mouse_capture_elem');
|
||||
captureElem.style.display = 'none';
|
||||
|
||||
window.removeEventListener('mousemove', _captureProxy);
|
||||
window.removeEventListener('mouseup', _captureProxy);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,34 +7,34 @@
|
|||
*/
|
||||
|
||||
export default class EventTargetMixin {
|
||||
constructor() {
|
||||
this._listeners = null;
|
||||
constructor() {
|
||||
this._listeners = null;
|
||||
}
|
||||
|
||||
addEventListener(type, callback) {
|
||||
if (!this._listeners) {
|
||||
this._listeners = new Map();
|
||||
}
|
||||
if (!this._listeners.has(type)) {
|
||||
this._listeners.set(type, new Set());
|
||||
}
|
||||
this._listeners.get(type).add(callback);
|
||||
}
|
||||
|
||||
addEventListener(type, callback) {
|
||||
if (!this._listeners) {
|
||||
this._listeners = new Map();
|
||||
}
|
||||
if (!this._listeners.has(type)) {
|
||||
this._listeners.set(type, new Set());
|
||||
}
|
||||
this._listeners.get(type).add(callback);
|
||||
}
|
||||
removeEventListener(type, callback) {
|
||||
if (!this._listeners || !this._listeners.has(type)) {
|
||||
return;
|
||||
}
|
||||
this._listeners.get(type).delete(callback);
|
||||
}
|
||||
|
||||
removeEventListener(type, callback) {
|
||||
if (!this._listeners || !this._listeners.has(type)) {
|
||||
return;
|
||||
}
|
||||
this._listeners.get(type).delete(callback);
|
||||
}
|
||||
|
||||
dispatchEvent(event) {
|
||||
if (!this._listeners || !this._listeners.has(event.type)) {
|
||||
return true;
|
||||
}
|
||||
this._listeners.get(event.type).forEach((callback) => {
|
||||
callback.call(this, event);
|
||||
}, this);
|
||||
return !event.defaultPrevented;
|
||||
}
|
||||
dispatchEvent(event) {
|
||||
if (!this._listeners || !this._listeners.has(event.type)) {
|
||||
return true;
|
||||
}
|
||||
this._listeners.get(event.type).forEach((callback) => {
|
||||
callback.call(this, event);
|
||||
}, this);
|
||||
return !event.defaultPrevented;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,40 +17,45 @@ let Info = () => {};
|
|||
let Warn = () => {};
|
||||
let Error = () => {};
|
||||
|
||||
export function init_logging (level) {
|
||||
if (typeof level === 'undefined') {
|
||||
level = _log_level;
|
||||
} else {
|
||||
_log_level = level;
|
||||
}
|
||||
export function init_logging(level) {
|
||||
if (typeof level === 'undefined') {
|
||||
level = _log_level;
|
||||
} else {
|
||||
_log_level = level;
|
||||
}
|
||||
|
||||
Debug = Info = Warn = Error = () => {};
|
||||
Debug = () => {};
|
||||
Info = () => {};
|
||||
Warn = () => {};
|
||||
Error = () => {};
|
||||
|
||||
if (typeof window.console !== "undefined") {
|
||||
/* eslint-disable no-console, no-fallthrough */
|
||||
switch (level) {
|
||||
case 'debug':
|
||||
Debug = console.debug.bind(window.console);
|
||||
case 'info':
|
||||
Info = console.info.bind(window.console);
|
||||
case 'warn':
|
||||
Warn = console.warn.bind(window.console);
|
||||
case 'error':
|
||||
Error = console.error.bind(window.console);
|
||||
case 'none':
|
||||
break;
|
||||
default:
|
||||
throw new Error("invalid logging type '" + level + "'");
|
||||
}
|
||||
/* eslint-enable no-console, no-fallthrough */
|
||||
if (typeof window.console !== 'undefined') {
|
||||
/* eslint-disable no-console, no-fallthrough */
|
||||
switch (level) {
|
||||
case 'debug':
|
||||
Debug = console.debug.bind(window.console);
|
||||
case 'info':
|
||||
Info = console.info.bind(window.console);
|
||||
case 'warn':
|
||||
Warn = console.warn.bind(window.console);
|
||||
case 'error':
|
||||
Error = console.error.bind(window.console);
|
||||
case 'none':
|
||||
break;
|
||||
default:
|
||||
throw new Error("invalid logging type '" + level + "'");
|
||||
}
|
||||
/* eslint-enable no-console, no-fallthrough */
|
||||
}
|
||||
}
|
||||
|
||||
export function get_logging () {
|
||||
return _log_level;
|
||||
export function get_logging() {
|
||||
return _log_level;
|
||||
}
|
||||
|
||||
export { Debug, Info, Warn, Error };
|
||||
export {
|
||||
Debug, Info, Warn, Error
|
||||
};
|
||||
|
||||
// Initialize logging level
|
||||
init_logging();
|
||||
|
|
|
|||
|
|
@ -8,47 +8,48 @@
|
|||
|
||||
/* Object.assign() (taken from MDN) */
|
||||
if (typeof Object.assign != 'function') {
|
||||
// Must be writable: true, enumerable: false, configurable: true
|
||||
Object.defineProperty(Object, "assign", {
|
||||
value: function assign(target, varArgs) { // .length of function is 2
|
||||
'use strict';
|
||||
if (target == null) { // TypeError if undefined or null
|
||||
throw new TypeError('Cannot convert undefined or null to object');
|
||||
// Must be writable: true, enumerable: false, configurable: true
|
||||
Object.defineProperty(Object, 'assign', {
|
||||
value: function assign(target, varArgs) { // .length of function is 2
|
||||
'use strict';
|
||||
|
||||
if (target == null) { // TypeError if undefined or null
|
||||
throw new TypeError('Cannot convert undefined or null to object');
|
||||
}
|
||||
|
||||
const to = Object(target);
|
||||
|
||||
for (let index = 1; index < arguments.length; index++) {
|
||||
const nextSource = arguments[index];
|
||||
|
||||
if (nextSource != null) { // Skip over if undefined or null
|
||||
for (let nextKey in nextSource) {
|
||||
// Avoid bugs when hasOwnProperty is shadowed
|
||||
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
|
||||
to[nextKey] = nextSource[nextKey];
|
||||
}
|
||||
|
||||
const to = Object(target);
|
||||
|
||||
for (let index = 1; index < arguments.length; index++) {
|
||||
const nextSource = arguments[index];
|
||||
|
||||
if (nextSource != null) { // Skip over if undefined or null
|
||||
for (let nextKey in nextSource) {
|
||||
// Avoid bugs when hasOwnProperty is shadowed
|
||||
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
|
||||
to[nextKey] = nextSource[nextKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return to;
|
||||
},
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return to;
|
||||
},
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
/* CustomEvent constructor (taken from MDN) */
|
||||
(() => {
|
||||
function CustomEvent (event, params) {
|
||||
params = params || { bubbles: false, cancelable: false, detail: undefined };
|
||||
const evt = document.createEvent( 'CustomEvent' );
|
||||
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
|
||||
return evt;
|
||||
}
|
||||
function CustomEvent(event, params) {
|
||||
params = params || { bubbles: false, cancelable: false, detail: undefined };
|
||||
const evt = document.createEvent('CustomEvent');
|
||||
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
|
||||
return evt;
|
||||
}
|
||||
|
||||
CustomEvent.prototype = window.Event.prototype;
|
||||
CustomEvent.prototype = window.Event.prototype;
|
||||
|
||||
if (typeof window.CustomEvent !== "function") {
|
||||
window.CustomEvent = CustomEvent;
|
||||
}
|
||||
if (typeof window.CustomEvent !== 'function') {
|
||||
window.CustomEvent = CustomEvent;
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@
|
|||
/*
|
||||
* Decode from UTF-8
|
||||
*/
|
||||
export function decodeUTF8 (utf8string) {
|
||||
return decodeURIComponent(escape(utf8string));
|
||||
export function decodeUTF8(utf8string) {
|
||||
return decodeURIComponent(escape(utf8string));
|
||||
}
|
||||
|
|
|
|||
512
core/websock.js
512
core/websock.js
|
|
@ -21,279 +21,277 @@ const ENABLE_COPYWITHIN = false;
|
|||
const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
|
||||
|
||||
export default class Websock {
|
||||
constructor() {
|
||||
this._websocket = null; // WebSocket object
|
||||
constructor() {
|
||||
this._websocket = null; // WebSocket object
|
||||
|
||||
this._rQi = 0; // Receive queue index
|
||||
this._rQlen = 0; // Next write position in the receive queue
|
||||
this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
|
||||
this._rQmax = this._rQbufferSize / 8;
|
||||
// called in init: this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._rQ = null; // Receive queue
|
||||
this._rQi = 0; // Receive queue index
|
||||
this._rQlen = 0; // Next write position in the receive queue
|
||||
this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
|
||||
this._rQmax = this._rQbufferSize / 8;
|
||||
// called in init: this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._rQ = null; // Receive queue
|
||||
|
||||
this._sQbufferSize = 1024 * 10; // 10 KiB
|
||||
// called in init: this._sQ = new Uint8Array(this._sQbufferSize);
|
||||
this._sQlen = 0;
|
||||
this._sQ = null; // Send queue
|
||||
this._sQbufferSize = 1024 * 10; // 10 KiB
|
||||
// called in init: this._sQ = new Uint8Array(this._sQbufferSize);
|
||||
this._sQlen = 0;
|
||||
this._sQ = null; // Send queue
|
||||
|
||||
this._eventHandlers = {
|
||||
message: () => {},
|
||||
open: () => {},
|
||||
close: () => {},
|
||||
error: () => {}
|
||||
};
|
||||
this._eventHandlers = {
|
||||
message: () => {},
|
||||
open: () => {},
|
||||
close: () => {},
|
||||
error: () => {}
|
||||
};
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
get_sQ() {
|
||||
return this._sQ;
|
||||
}
|
||||
|
||||
get_rQ() {
|
||||
return this._rQ;
|
||||
}
|
||||
|
||||
get_rQi() {
|
||||
return this._rQi;
|
||||
}
|
||||
|
||||
set_rQi(val) {
|
||||
this._rQi = val;
|
||||
}
|
||||
|
||||
// Receive Queue
|
||||
rQlen() {
|
||||
return this._rQlen - this._rQi;
|
||||
}
|
||||
|
||||
rQpeek8() {
|
||||
return this._rQ[this._rQi];
|
||||
}
|
||||
|
||||
rQshift8() {
|
||||
return this._rQ[this._rQi++];
|
||||
}
|
||||
|
||||
rQskip8() {
|
||||
this._rQi++;
|
||||
}
|
||||
|
||||
rQskipBytes(num) {
|
||||
this._rQi += num;
|
||||
}
|
||||
|
||||
// TODO(directxman12): test performance with these vs a DataView
|
||||
rQshift16() {
|
||||
return (this._rQ[this._rQi++] << 8)
|
||||
+ this._rQ[this._rQi++];
|
||||
}
|
||||
|
||||
rQshift32() {
|
||||
return (this._rQ[this._rQi++] << 24)
|
||||
+ (this._rQ[this._rQi++] << 16)
|
||||
+ (this._rQ[this._rQi++] << 8)
|
||||
+ this._rQ[this._rQi++];
|
||||
}
|
||||
|
||||
rQshiftStr(len) {
|
||||
if (typeof (len) === 'undefined') { len = this.rQlen(); }
|
||||
let str = '';
|
||||
// Handle large arrays in steps to avoid long strings on the stack
|
||||
for (let i = 0; i < len; i += 4096) {
|
||||
let part = this.rQshiftBytes(Math.min(4096, len - i));
|
||||
str += String.fromCharCode.apply(null, part);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
get_sQ() {
|
||||
return this._sQ;
|
||||
rQshiftBytes(len) {
|
||||
if (typeof (len) === 'undefined') { len = this.rQlen(); }
|
||||
this._rQi += len;
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
|
||||
}
|
||||
|
||||
rQshiftTo(target, len) {
|
||||
if (len === undefined) { len = this.rQlen(); }
|
||||
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
|
||||
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
|
||||
this._rQi += len;
|
||||
}
|
||||
|
||||
rQwhole() {
|
||||
return new Uint8Array(this._rQ.buffer, 0, this._rQlen);
|
||||
}
|
||||
|
||||
rQslice(start, end) {
|
||||
if (end) {
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
|
||||
} else {
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start);
|
||||
}
|
||||
}
|
||||
|
||||
get_rQ() {
|
||||
return this._rQ;
|
||||
}
|
||||
|
||||
get_rQi() {
|
||||
return this._rQi;
|
||||
}
|
||||
|
||||
set_rQi(val) {
|
||||
this._rQi = val;
|
||||
}
|
||||
|
||||
// Receive Queue
|
||||
rQlen() {
|
||||
return this._rQlen - this._rQi;
|
||||
}
|
||||
|
||||
rQpeek8() {
|
||||
return this._rQ[this._rQi];
|
||||
}
|
||||
|
||||
rQshift8() {
|
||||
return this._rQ[this._rQi++];
|
||||
}
|
||||
|
||||
rQskip8() {
|
||||
this._rQi++;
|
||||
}
|
||||
|
||||
rQskipBytes(num) {
|
||||
this._rQi += num;
|
||||
}
|
||||
|
||||
// TODO(directxman12): test performance with these vs a DataView
|
||||
rQshift16() {
|
||||
return (this._rQ[this._rQi++] << 8) +
|
||||
this._rQ[this._rQi++];
|
||||
}
|
||||
|
||||
rQshift32() {
|
||||
return (this._rQ[this._rQi++] << 24) +
|
||||
(this._rQ[this._rQi++] << 16) +
|
||||
(this._rQ[this._rQi++] << 8) +
|
||||
this._rQ[this._rQi++];
|
||||
}
|
||||
|
||||
rQshiftStr(len) {
|
||||
if (typeof(len) === 'undefined') { len = this.rQlen(); }
|
||||
let str = "";
|
||||
// Handle large arrays in steps to avoid long strings on the stack
|
||||
for (let i = 0; i < len; i += 4096) {
|
||||
let part = this.rQshiftBytes(Math.min(4096, len - i));
|
||||
str += String.fromCharCode.apply(null, part);
|
||||
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
|
||||
// to be available in the receive queue. Return true if we need to
|
||||
// wait (and possibly print a debug message), otherwise false.
|
||||
rQwait(msg, num, goback) {
|
||||
const rQlen = this._rQlen - this._rQi; // Skip rQlen() function call
|
||||
if (rQlen < num) {
|
||||
if (goback) {
|
||||
if (this._rQi < goback) {
|
||||
throw new Error('rQwait cannot backup ' + goback + ' bytes');
|
||||
}
|
||||
return str;
|
||||
this._rQi -= goback;
|
||||
}
|
||||
return true; // true means need more data
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send Queue
|
||||
|
||||
flush() {
|
||||
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
|
||||
this._websocket.send(this._encode_message());
|
||||
this._sQlen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
send(arr) {
|
||||
this._sQ.set(arr, this._sQlen);
|
||||
this._sQlen += arr.length;
|
||||
this.flush();
|
||||
}
|
||||
|
||||
send_string(str) {
|
||||
this.send(str.split('').map(chr => chr.charCodeAt(0)));
|
||||
}
|
||||
|
||||
// Event Handlers
|
||||
off(evt) {
|
||||
this._eventHandlers[evt] = () => {};
|
||||
}
|
||||
|
||||
on(evt, handler) {
|
||||
this._eventHandlers[evt] = handler;
|
||||
}
|
||||
|
||||
_allocate_buffers() {
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._sQ = new Uint8Array(this._sQbufferSize);
|
||||
}
|
||||
|
||||
init() {
|
||||
this._allocate_buffers();
|
||||
this._rQi = 0;
|
||||
this._websocket = null;
|
||||
}
|
||||
|
||||
open(uri, protocols) {
|
||||
this.init();
|
||||
|
||||
this._websocket = new WebSocket(uri, protocols);
|
||||
this._websocket.binaryType = 'arraybuffer';
|
||||
|
||||
this._websocket.onmessage = this._recv_message.bind(this);
|
||||
this._websocket.onopen = () => {
|
||||
Log.Debug('>> WebSock.onopen');
|
||||
if (this._websocket.protocol) {
|
||||
Log.Info('Server choose sub-protocol: ' + this._websocket.protocol);
|
||||
}
|
||||
|
||||
this._eventHandlers.open();
|
||||
Log.Debug('<< WebSock.onopen');
|
||||
};
|
||||
this._websocket.onclose = (e) => {
|
||||
Log.Debug('>> WebSock.onclose');
|
||||
this._eventHandlers.close(e);
|
||||
Log.Debug('<< WebSock.onclose');
|
||||
};
|
||||
this._websocket.onerror = (e) => {
|
||||
Log.Debug('>> WebSock.onerror: ' + e);
|
||||
this._eventHandlers.error(e);
|
||||
Log.Debug('<< WebSock.onerror: ' + e);
|
||||
};
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this._websocket) {
|
||||
if ((this._websocket.readyState === WebSocket.OPEN)
|
||||
|| (this._websocket.readyState === WebSocket.CONNECTING)) {
|
||||
Log.Info('Closing WebSocket connection');
|
||||
this._websocket.close();
|
||||
}
|
||||
|
||||
this._websocket.onmessage = () => {};
|
||||
}
|
||||
}
|
||||
|
||||
// private methods
|
||||
_encode_message() {
|
||||
// Put in a binary arraybuffer
|
||||
// according to the spec, you can send ArrayBufferViews with the send method
|
||||
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
|
||||
}
|
||||
|
||||
_expand_compact_rQ(min_fit) {
|
||||
const resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2;
|
||||
if (resizeNeeded) {
|
||||
if (!min_fit) {
|
||||
// just double the size if we need to do compaction
|
||||
this._rQbufferSize *= 2;
|
||||
} else {
|
||||
// otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
|
||||
this._rQbufferSize = (this._rQlen - this._rQi + min_fit) * 8;
|
||||
}
|
||||
}
|
||||
|
||||
rQshiftBytes(len) {
|
||||
if (typeof(len) === 'undefined') { len = this.rQlen(); }
|
||||
this._rQi += len;
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
|
||||
// we don't want to grow unboundedly
|
||||
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
|
||||
this._rQbufferSize = MAX_RQ_GROW_SIZE;
|
||||
if (this._rQbufferSize - this._rQlen - this._rQi < min_fit) {
|
||||
throw new Error('Receive Queue buffer exceeded ' + MAX_RQ_GROW_SIZE + ' bytes, and the new message could not fit');
|
||||
}
|
||||
}
|
||||
|
||||
rQshiftTo(target, len) {
|
||||
if (len === undefined) { len = this.rQlen(); }
|
||||
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
|
||||
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
|
||||
this._rQi += len;
|
||||
if (resizeNeeded) {
|
||||
const old_rQbuffer = this._rQ.buffer;
|
||||
this._rQmax = this._rQbufferSize / 8;
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
|
||||
} else if (ENABLE_COPYWITHIN) {
|
||||
this._rQ.copyWithin(0, this._rQi);
|
||||
} else {
|
||||
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
|
||||
}
|
||||
|
||||
rQwhole() {
|
||||
return new Uint8Array(this._rQ.buffer, 0, this._rQlen);
|
||||
this._rQlen = this._rQlen - this._rQi;
|
||||
this._rQi = 0;
|
||||
}
|
||||
|
||||
_decode_message(data) {
|
||||
// push arraybuffer values onto the end
|
||||
const u8 = new Uint8Array(data);
|
||||
if (u8.length > this._rQbufferSize - this._rQlen) {
|
||||
this._expand_compact_rQ(u8.length);
|
||||
}
|
||||
this._rQ.set(u8, this._rQlen);
|
||||
this._rQlen += u8.length;
|
||||
}
|
||||
|
||||
rQslice(start, end) {
|
||||
if (end) {
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
|
||||
} else {
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start);
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
|
||||
// to be available in the receive queue. Return true if we need to
|
||||
// wait (and possibly print a debug message), otherwise false.
|
||||
rQwait(msg, num, goback) {
|
||||
const rQlen = this._rQlen - this._rQi; // Skip rQlen() function call
|
||||
if (rQlen < num) {
|
||||
if (goback) {
|
||||
if (this._rQi < goback) {
|
||||
throw new Error("rQwait cannot backup " + goback + " bytes");
|
||||
}
|
||||
this._rQi -= goback;
|
||||
}
|
||||
return true; // true means need more data
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send Queue
|
||||
|
||||
flush() {
|
||||
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
|
||||
this._websocket.send(this._encode_message());
|
||||
this._sQlen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
send(arr) {
|
||||
this._sQ.set(arr, this._sQlen);
|
||||
this._sQlen += arr.length;
|
||||
this.flush();
|
||||
}
|
||||
|
||||
send_string(str) {
|
||||
this.send(str.split('').map(chr => chr.charCodeAt(0)));
|
||||
}
|
||||
|
||||
// Event Handlers
|
||||
off(evt) {
|
||||
this._eventHandlers[evt] = () => {};
|
||||
}
|
||||
|
||||
on(evt, handler) {
|
||||
this._eventHandlers[evt] = handler;
|
||||
}
|
||||
|
||||
_allocate_buffers() {
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._sQ = new Uint8Array(this._sQbufferSize);
|
||||
}
|
||||
|
||||
init() {
|
||||
this._allocate_buffers();
|
||||
_recv_message(e) {
|
||||
this._decode_message(e.data);
|
||||
if (this.rQlen() > 0) {
|
||||
this._eventHandlers.message();
|
||||
// Compact the receive queue
|
||||
if (this._rQlen == this._rQi) {
|
||||
this._rQlen = 0;
|
||||
this._rQi = 0;
|
||||
this._websocket = null;
|
||||
}
|
||||
|
||||
open(uri, protocols) {
|
||||
this.init();
|
||||
|
||||
this._websocket = new WebSocket(uri, protocols);
|
||||
this._websocket.binaryType = 'arraybuffer';
|
||||
|
||||
this._websocket.onmessage = this._recv_message.bind(this);
|
||||
this._websocket.onopen = () => {
|
||||
Log.Debug('>> WebSock.onopen');
|
||||
if (this._websocket.protocol) {
|
||||
Log.Info("Server choose sub-protocol: " + this._websocket.protocol);
|
||||
}
|
||||
|
||||
this._eventHandlers.open();
|
||||
Log.Debug("<< WebSock.onopen");
|
||||
};
|
||||
this._websocket.onclose = (e) => {
|
||||
Log.Debug(">> WebSock.onclose");
|
||||
this._eventHandlers.close(e);
|
||||
Log.Debug("<< WebSock.onclose");
|
||||
};
|
||||
this._websocket.onerror = (e) => {
|
||||
Log.Debug(">> WebSock.onerror: " + e);
|
||||
this._eventHandlers.error(e);
|
||||
Log.Debug("<< WebSock.onerror: " + e);
|
||||
};
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this._websocket) {
|
||||
if ((this._websocket.readyState === WebSocket.OPEN) ||
|
||||
(this._websocket.readyState === WebSocket.CONNECTING)) {
|
||||
Log.Info("Closing WebSocket connection");
|
||||
this._websocket.close();
|
||||
}
|
||||
|
||||
this._websocket.onmessage = () => {};
|
||||
}
|
||||
}
|
||||
|
||||
// private methods
|
||||
_encode_message() {
|
||||
// Put in a binary arraybuffer
|
||||
// according to the spec, you can send ArrayBufferViews with the send method
|
||||
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
|
||||
}
|
||||
|
||||
_expand_compact_rQ(min_fit) {
|
||||
const resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2;
|
||||
if (resizeNeeded) {
|
||||
if (!min_fit) {
|
||||
// just double the size if we need to do compaction
|
||||
this._rQbufferSize *= 2;
|
||||
} else {
|
||||
// otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
|
||||
this._rQbufferSize = (this._rQlen - this._rQi + min_fit) * 8;
|
||||
}
|
||||
}
|
||||
|
||||
// we don't want to grow unboundedly
|
||||
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
|
||||
this._rQbufferSize = MAX_RQ_GROW_SIZE;
|
||||
if (this._rQbufferSize - this._rQlen - this._rQi < min_fit) {
|
||||
throw new Error("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
|
||||
}
|
||||
}
|
||||
|
||||
if (resizeNeeded) {
|
||||
const old_rQbuffer = this._rQ.buffer;
|
||||
this._rQmax = this._rQbufferSize / 8;
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
|
||||
} else {
|
||||
if (ENABLE_COPYWITHIN) {
|
||||
this._rQ.copyWithin(0, this._rQi);
|
||||
} else {
|
||||
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
|
||||
}
|
||||
}
|
||||
|
||||
this._rQlen = this._rQlen - this._rQi;
|
||||
this._rQi = 0;
|
||||
}
|
||||
|
||||
_decode_message(data) {
|
||||
// push arraybuffer values onto the end
|
||||
const u8 = new Uint8Array(data);
|
||||
if (u8.length > this._rQbufferSize - this._rQlen) {
|
||||
this._expand_compact_rQ(u8.length);
|
||||
}
|
||||
this._rQ.set(u8, this._rQlen);
|
||||
this._rQlen += u8.length;
|
||||
}
|
||||
|
||||
_recv_message(e) {
|
||||
this._decode_message(e.data);
|
||||
if (this.rQlen() > 0) {
|
||||
this._eventHandlers.message();
|
||||
// Compact the receive queue
|
||||
if (this._rQlen == this._rQi) {
|
||||
this._rQlen = 0;
|
||||
this._rQi = 0;
|
||||
} else if (this._rQlen > this._rQmax) {
|
||||
this._expand_compact_rQ();
|
||||
}
|
||||
} else {
|
||||
Log.Debug("Ignoring empty message");
|
||||
}
|
||||
} else if (this._rQlen > this._rQmax) {
|
||||
this._expand_compact_rQ();
|
||||
}
|
||||
} else {
|
||||
Log.Debug('Ignoring empty message');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"test": "tests"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint app core po tests utils",
|
||||
"lint": "eslint app core po/po2js tests utils",
|
||||
"test": "karma start karma.conf.js",
|
||||
"prepare": "node ./utils/use_require.js --as commonjs --clean"
|
||||
},
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
"chai": "^3.5.0",
|
||||
"commander": "^2.9.0",
|
||||
"es-module-loader": "^2.1.0",
|
||||
"eslint": "^4.16.0",
|
||||
"eslint": "^5.1.0",
|
||||
"fs-extra": "^1.0.0",
|
||||
"jsdom": "*",
|
||||
"karma": "^1.3.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": 0
|
||||
}
|
||||
}
|
||||
18
po/po2js
18
po/po2js
|
|
@ -19,25 +19,25 @@
|
|||
|
||||
const getopt = require('node-getopt');
|
||||
const fs = require('fs');
|
||||
const po2json = require("po2json");
|
||||
const po2json = require('po2json');
|
||||
|
||||
const opt = getopt.create([
|
||||
['h' , 'help' , 'display this help'],
|
||||
['h', 'help', 'display this help'],
|
||||
]).bindHelp().parseSystem();
|
||||
|
||||
if (opt.argv.length != 2) {
|
||||
console.error("Incorrect number of arguments given");
|
||||
console.error('Incorrect number of arguments given');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const data = po2json.parseFileSync(opt.argv[0]);
|
||||
|
||||
const bodyPart = Object.keys(data).filter((msgid) => msgid !== "").map((msgid) => {
|
||||
if (msgid === "") return;
|
||||
const msgstr = data[msgid][1];
|
||||
return " " + JSON.stringify(msgid) + ": " + JSON.stringify(msgstr);
|
||||
}).join(",\n");
|
||||
const bodyPart = Object.keys(data).filter(msgid => msgid !== '').map((msgid) => {
|
||||
if (msgid === '') return;
|
||||
const msgstr = data[msgid][1];
|
||||
return ' ' + JSON.stringify(msgid) + ': ' + JSON.stringify(msgstr);
|
||||
}).join(',\n');
|
||||
|
||||
const output = "{\n" + bodyPart + "\n}";
|
||||
const output = '{\n' + bodyPart + '\n}';
|
||||
|
||||
fs.writeFileSync(opt.argv[1], output);
|
||||
|
|
|
|||
|
|
@ -4,102 +4,106 @@ chai.use(sinonChai);
|
|||
|
||||
// noVNC specific assertions
|
||||
chai.use(function (_chai, utils) {
|
||||
_chai.Assertion.addMethod('displayed', function (target_data) {
|
||||
const obj = this._obj;
|
||||
const ctx = obj._target.getContext('2d');
|
||||
const data_cl = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data;
|
||||
// NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
|
||||
const data = new Uint8Array(data_cl);
|
||||
const len = data_cl.length;
|
||||
new chai.Assertion(len).to.be.equal(target_data.length, "unexpected display size");
|
||||
let same = true;
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (data[i] != target_data[i]) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!same) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("expected data: %o, actual data: %o", target_data, data);
|
||||
}
|
||||
this.assert(same,
|
||||
"expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}",
|
||||
"expected #{this} not to have displayed the image #{act}",
|
||||
target_data,
|
||||
data);
|
||||
});
|
||||
_chai.Assertion.addMethod('displayed', function (target_data) {
|
||||
const obj = this._obj;
|
||||
const ctx = obj._target.getContext('2d');
|
||||
const data_cl = ctx.getImageData(0, 0, obj._target.width, obj._target.height).data;
|
||||
// NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray, so work around that
|
||||
const data = new Uint8Array(data_cl);
|
||||
const len = data_cl.length;
|
||||
new chai.Assertion(len).to.be.equal(target_data.length, 'unexpected display size');
|
||||
let same = true;
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (data[i] != target_data[i]) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!same) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('expected data: %o, actual data: %o', target_data, data);
|
||||
}
|
||||
this.assert(same,
|
||||
'expected #{this} to have displayed the image #{exp}, but instead it displayed #{act}',
|
||||
'expected #{this} not to have displayed the image #{act}',
|
||||
target_data,
|
||||
data);
|
||||
});
|
||||
|
||||
_chai.Assertion.addMethod('sent', function (target_data) {
|
||||
_chai.Assertion.addMethod('sent', function (target_data) {
|
||||
const obj = this._obj;
|
||||
obj.inspect = () => {
|
||||
const res = {
|
||||
_websocket: obj._websocket,
|
||||
rQi: obj._rQi,
|
||||
_rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen),
|
||||
_sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen)
|
||||
};
|
||||
res.prototype = obj;
|
||||
return res;
|
||||
};
|
||||
const data = obj._websocket._get_sent_data();
|
||||
let same = true;
|
||||
if (data.length != target_data.length) {
|
||||
same = false;
|
||||
} else {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i] != target_data[i]) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!same) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('expected data: %o, actual data: %o', target_data, data);
|
||||
}
|
||||
this.assert(same,
|
||||
'expected #{this} to have sent the data #{exp}, but it actually sent #{act}',
|
||||
'expected #{this} not to have sent the data #{act}',
|
||||
Array.prototype.slice.call(target_data),
|
||||
Array.prototype.slice.call(data));
|
||||
});
|
||||
|
||||
_chai.Assertion.addProperty('array', function () {
|
||||
utils.flag(this, 'array', true);
|
||||
});
|
||||
|
||||
_chai.Assertion.overwriteMethod('equal', function (_super) {
|
||||
return function assertArrayEqual(target) {
|
||||
if (utils.flag(this, 'array')) {
|
||||
const obj = this._obj;
|
||||
obj.inspect = () => {
|
||||
const res = { _websocket: obj._websocket, rQi: obj._rQi, _rQ: new Uint8Array(obj._rQ.buffer, 0, obj._rQlen),
|
||||
_sQ: new Uint8Array(obj._sQ.buffer, 0, obj._sQlen) };
|
||||
res.prototype = obj;
|
||||
return res;
|
||||
};
|
||||
const data = obj._websocket._get_sent_data();
|
||||
|
||||
let same = true;
|
||||
if (data.length != target_data.length) {
|
||||
same = false;
|
||||
|
||||
if (utils.flag(this, 'deep')) {
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
if (!utils.eql(obj[i], target[i])) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.assert(same,
|
||||
'expected #{this} to have elements deeply equal to #{exp}',
|
||||
'expected #{this} not to have elements deeply equal to #{exp}',
|
||||
Array.prototype.slice.call(target));
|
||||
} else {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i] != target_data[i]) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
if (obj[i] != target[i]) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.assert(same,
|
||||
'expected #{this} to have elements equal to #{exp}',
|
||||
'expected #{this} not to have elements equal to #{exp}',
|
||||
Array.prototype.slice.call(target));
|
||||
}
|
||||
if (!same) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("expected data: %o, actual data: %o", target_data, data);
|
||||
}
|
||||
this.assert(same,
|
||||
"expected #{this} to have sent the data #{exp}, but it actually sent #{act}",
|
||||
"expected #{this} not to have sent the data #{act}",
|
||||
Array.prototype.slice.call(target_data),
|
||||
Array.prototype.slice.call(data));
|
||||
});
|
||||
|
||||
_chai.Assertion.addProperty('array', function () {
|
||||
utils.flag(this, 'array', true);
|
||||
});
|
||||
|
||||
_chai.Assertion.overwriteMethod('equal', function (_super) {
|
||||
return function assertArrayEqual(target) {
|
||||
if (utils.flag(this, 'array')) {
|
||||
const obj = this._obj;
|
||||
|
||||
let same = true;
|
||||
|
||||
if (utils.flag(this, 'deep')) {
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
if (!utils.eql(obj[i], target[i])) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.assert(same,
|
||||
"expected #{this} to have elements deeply equal to #{exp}",
|
||||
"expected #{this} not to have elements deeply equal to #{exp}",
|
||||
Array.prototype.slice.call(target));
|
||||
} else {
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
if (obj[i] != target[i]) {
|
||||
same = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.assert(same,
|
||||
"expected #{this} to have elements equal to #{exp}",
|
||||
"expected #{this} not to have elements equal to #{exp}",
|
||||
Array.prototype.slice.call(target));
|
||||
}
|
||||
} else {
|
||||
_super.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
});
|
||||
} else {
|
||||
_super.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,69 +2,69 @@ import Base64 from '../core/base64.js';
|
|||
|
||||
// PhantomJS can't create Event objects directly, so we need to use this
|
||||
function make_event(name, props) {
|
||||
const evt = document.createEvent('Event');
|
||||
evt.initEvent(name, true, true);
|
||||
if (props) {
|
||||
for (let prop in props) {
|
||||
evt[prop] = props[prop];
|
||||
}
|
||||
const evt = document.createEvent('Event');
|
||||
evt.initEvent(name, true, true);
|
||||
if (props) {
|
||||
for (let prop in props) {
|
||||
evt[prop] = props[prop];
|
||||
}
|
||||
return evt;
|
||||
}
|
||||
return evt;
|
||||
}
|
||||
|
||||
export default class FakeWebSocket {
|
||||
constructor(uri, protocols) {
|
||||
this.url = uri;
|
||||
this.binaryType = "arraybuffer";
|
||||
this.extensions = "";
|
||||
constructor(uri, protocols) {
|
||||
this.url = uri;
|
||||
this.binaryType = 'arraybuffer';
|
||||
this.extensions = '';
|
||||
|
||||
if (!protocols || typeof protocols === 'string') {
|
||||
this.protocol = protocols;
|
||||
} else {
|
||||
this.protocol = protocols[0];
|
||||
}
|
||||
|
||||
this._send_queue = new Uint8Array(20000);
|
||||
|
||||
this.readyState = FakeWebSocket.CONNECTING;
|
||||
this.bufferedAmount = 0;
|
||||
|
||||
this.__is_fake = true;
|
||||
if (!protocols || typeof protocols === 'string') {
|
||||
this.protocol = protocols;
|
||||
} else {
|
||||
this.protocol = protocols[0];
|
||||
}
|
||||
|
||||
close(code, reason) {
|
||||
this.readyState = FakeWebSocket.CLOSED;
|
||||
if (this.onclose) {
|
||||
this.onclose(make_event("close", { 'code': code, 'reason': reason, 'wasClean': true }));
|
||||
}
|
||||
}
|
||||
this._send_queue = new Uint8Array(20000);
|
||||
|
||||
send(data) {
|
||||
if (this.protocol == 'base64') {
|
||||
data = Base64.decode(data);
|
||||
} else {
|
||||
data = new Uint8Array(data);
|
||||
}
|
||||
this._send_queue.set(data, this.bufferedAmount);
|
||||
this.bufferedAmount += data.length;
|
||||
}
|
||||
this.readyState = FakeWebSocket.CONNECTING;
|
||||
this.bufferedAmount = 0;
|
||||
|
||||
_get_sent_data() {
|
||||
const res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount);
|
||||
this.bufferedAmount = 0;
|
||||
return res;
|
||||
}
|
||||
this.__is_fake = true;
|
||||
}
|
||||
|
||||
_open() {
|
||||
this.readyState = FakeWebSocket.OPEN;
|
||||
if (this.onopen) {
|
||||
this.onopen(make_event('open'));
|
||||
}
|
||||
close(code, reason) {
|
||||
this.readyState = FakeWebSocket.CLOSED;
|
||||
if (this.onclose) {
|
||||
this.onclose(make_event('close', { code: code, reason: reason, wasClean: true }));
|
||||
}
|
||||
}
|
||||
|
||||
_receive_data(data) {
|
||||
this.onmessage(make_event("message", { 'data': data }));
|
||||
send(data) {
|
||||
if (this.protocol == 'base64') {
|
||||
data = Base64.decode(data);
|
||||
} else {
|
||||
data = new Uint8Array(data);
|
||||
}
|
||||
this._send_queue.set(data, this.bufferedAmount);
|
||||
this.bufferedAmount += data.length;
|
||||
}
|
||||
|
||||
_get_sent_data() {
|
||||
const res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount);
|
||||
this.bufferedAmount = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
_open() {
|
||||
this.readyState = FakeWebSocket.OPEN;
|
||||
if (this.onopen) {
|
||||
this.onopen(make_event('open'));
|
||||
}
|
||||
}
|
||||
|
||||
_receive_data(data) {
|
||||
this.onmessage(make_event('message', { data: data }));
|
||||
}
|
||||
}
|
||||
|
||||
FakeWebSocket.OPEN = WebSocket.OPEN;
|
||||
|
|
@ -75,17 +75,17 @@ FakeWebSocket.CLOSED = WebSocket.CLOSED;
|
|||
FakeWebSocket.__is_fake = true;
|
||||
|
||||
FakeWebSocket.replace = () => {
|
||||
if (!WebSocket.__is_fake) {
|
||||
const real_version = WebSocket;
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = FakeWebSocket;
|
||||
FakeWebSocket.__real_version = real_version;
|
||||
}
|
||||
if (!WebSocket.__is_fake) {
|
||||
const real_version = WebSocket;
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = FakeWebSocket;
|
||||
FakeWebSocket.__real_version = real_version;
|
||||
}
|
||||
};
|
||||
|
||||
FakeWebSocket.restore = () => {
|
||||
if (WebSocket.__is_fake) {
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = WebSocket.__real_version;
|
||||
}
|
||||
if (WebSocket.__is_fake) {
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = WebSocket.__real_version;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ const allTestFiles = [];
|
|||
const extraFiles = ['/base/tests/assertions.js'];
|
||||
|
||||
Object.keys(window.__karma__.files).forEach(function (file) {
|
||||
if (TEST_REGEXP.test(file)) {
|
||||
// TODO: normalize?
|
||||
allTestFiles.push(file);
|
||||
}
|
||||
if (TEST_REGEXP.test(file)) {
|
||||
// TODO: normalize?
|
||||
allTestFiles.push(file);
|
||||
}
|
||||
});
|
||||
|
||||
require.config({
|
||||
baseUrl: '/base',
|
||||
deps: allTestFiles.concat(extraFiles),
|
||||
callback: window.__karma__.start,
|
||||
baseUrl: '/base',
|
||||
deps: allTestFiles.concat(extraFiles),
|
||||
callback: window.__karma__.start,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,162 +7,161 @@ let frames = null;
|
|||
let encoding = null;
|
||||
|
||||
function message(str) {
|
||||
const cell = document.getElementById('messages');
|
||||
cell.textContent += str + "\n";
|
||||
cell.scrollTop = cell.scrollHeight;
|
||||
const cell = document.getElementById('messages');
|
||||
cell.textContent += str + '\n';
|
||||
cell.scrollTop = cell.scrollHeight;
|
||||
}
|
||||
|
||||
function loadFile() {
|
||||
const fname = WebUtil.getQueryVar('data', null);
|
||||
const fname = WebUtil.getQueryVar('data', null);
|
||||
|
||||
if (!fname) {
|
||||
return Promise.reject("Must specify data=FOO in query string.");
|
||||
}
|
||||
if (!fname) {
|
||||
return Promise.reject('Must specify data=FOO in query string.');
|
||||
}
|
||||
|
||||
message("Loading " + fname);
|
||||
message('Loading ' + fname);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script");
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.body.appendChild(script);
|
||||
script.src = "../recordings/" + fname;
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.body.appendChild(script);
|
||||
script.src = '../recordings/' + fname;
|
||||
});
|
||||
}
|
||||
|
||||
function enableUI() {
|
||||
const iterations = WebUtil.getQueryVar('iterations', 3);
|
||||
document.getElementById('iterations').value = iterations;
|
||||
const iterations = WebUtil.getQueryVar('iterations', 3);
|
||||
document.getElementById('iterations').value = iterations;
|
||||
|
||||
const mode = WebUtil.getQueryVar('mode', 3);
|
||||
if (mode === 'realtime') {
|
||||
document.getElementById('mode2').checked = true;
|
||||
} else {
|
||||
document.getElementById('mode1').checked = true;
|
||||
}
|
||||
const mode = WebUtil.getQueryVar('mode', 3);
|
||||
if (mode === 'realtime') {
|
||||
document.getElementById('mode2').checked = true;
|
||||
} else {
|
||||
document.getElementById('mode1').checked = true;
|
||||
}
|
||||
|
||||
message("VNC_frame_data.length: " + VNC_frame_data.length);
|
||||
message('VNC_frame_data.length: ' + VNC_frame_data.length);
|
||||
|
||||
const startButton = document.getElementById('startButton');
|
||||
startButton.disabled = false;
|
||||
startButton.addEventListener('click', start);
|
||||
const startButton = document.getElementById('startButton');
|
||||
startButton.disabled = false;
|
||||
startButton.addEventListener('click', start);
|
||||
|
||||
frames = VNC_frame_data;
|
||||
// Only present in older recordings
|
||||
if (window.VNC_frame_encoding)
|
||||
encoding = VNC_frame_encoding;
|
||||
frames = VNC_frame_data;
|
||||
// Only present in older recordings
|
||||
if (window.VNC_frame_encoding) encoding = VNC_frame_encoding;
|
||||
}
|
||||
|
||||
class IterationPlayer {
|
||||
constructor(iterations, frames, encoding) {
|
||||
this._iterations = iterations;
|
||||
constructor(iterations, frames, encoding) {
|
||||
this._iterations = iterations;
|
||||
|
||||
this._iteration = undefined;
|
||||
this._player = undefined;
|
||||
this._iteration = undefined;
|
||||
this._player = undefined;
|
||||
|
||||
this._start_time = undefined;
|
||||
this._start_time = undefined;
|
||||
|
||||
this._frames = frames;
|
||||
this._encoding = encoding;
|
||||
this._frames = frames;
|
||||
this._encoding = encoding;
|
||||
|
||||
this._state = 'running';
|
||||
this._state = 'running';
|
||||
|
||||
this.onfinish = () => {};
|
||||
this.oniterationfinish = () => {};
|
||||
this.rfbdisconnected = () => {};
|
||||
this.onfinish = () => {};
|
||||
this.oniterationfinish = () => {};
|
||||
this.rfbdisconnected = () => {};
|
||||
}
|
||||
|
||||
start(mode) {
|
||||
this._iteration = 0;
|
||||
this._start_time = (new Date()).getTime();
|
||||
|
||||
this._realtime = mode.startsWith('realtime');
|
||||
this._trafficMgmt = !mode.endsWith('-no-mgmt');
|
||||
|
||||
this._nextIteration();
|
||||
}
|
||||
|
||||
_nextIteration() {
|
||||
const player = new RecordingPlayer(this._frames, this._encoding, this._disconnected.bind(this));
|
||||
player.onfinish = this._iterationFinish.bind(this);
|
||||
|
||||
if (this._state !== 'running') { return; }
|
||||
|
||||
this._iteration++;
|
||||
if (this._iteration > this._iterations) {
|
||||
this._finish();
|
||||
return;
|
||||
}
|
||||
|
||||
start(mode) {
|
||||
this._iteration = 0;
|
||||
this._start_time = (new Date()).getTime();
|
||||
player.run(this._realtime, this._trafficMgmt);
|
||||
}
|
||||
|
||||
this._realtime = mode.startsWith('realtime');
|
||||
this._trafficMgmt = !mode.endsWith('-no-mgmt');
|
||||
_finish() {
|
||||
const endTime = (new Date()).getTime();
|
||||
const totalDuration = endTime - this._start_time;
|
||||
|
||||
this._nextIteration();
|
||||
const evt = new Event('finish');
|
||||
evt.duration = totalDuration;
|
||||
evt.iterations = this._iterations;
|
||||
this.onfinish(evt);
|
||||
}
|
||||
|
||||
_iterationFinish(duration) {
|
||||
const evt = new Event('iterationfinish');
|
||||
evt.duration = duration;
|
||||
evt.number = this._iteration;
|
||||
this.oniterationfinish(evt);
|
||||
|
||||
this._nextIteration();
|
||||
}
|
||||
|
||||
_disconnected(clean, frame) {
|
||||
if (!clean) {
|
||||
this._state = 'failed';
|
||||
}
|
||||
|
||||
_nextIteration() {
|
||||
const player = new RecordingPlayer(this._frames, this._encoding, this._disconnected.bind(this));
|
||||
player.onfinish = this._iterationFinish.bind(this);
|
||||
const evt = new Event('rfbdisconnected');
|
||||
evt.clean = clean;
|
||||
evt.frame = frame;
|
||||
evt.iteration = this._iteration;
|
||||
|
||||
if (this._state !== 'running') { return; }
|
||||
|
||||
this._iteration++;
|
||||
if (this._iteration > this._iterations) {
|
||||
this._finish();
|
||||
return;
|
||||
}
|
||||
|
||||
player.run(this._realtime, this._trafficMgmt);
|
||||
}
|
||||
|
||||
_finish() {
|
||||
const endTime = (new Date()).getTime();
|
||||
const totalDuration = endTime - this._start_time;
|
||||
|
||||
const evt = new Event('finish');
|
||||
evt.duration = totalDuration;
|
||||
evt.iterations = this._iterations;
|
||||
this.onfinish(evt);
|
||||
}
|
||||
|
||||
_iterationFinish(duration) {
|
||||
const evt = new Event('iterationfinish');
|
||||
evt.duration = duration;
|
||||
evt.number = this._iteration;
|
||||
this.oniterationfinish(evt);
|
||||
|
||||
this._nextIteration();
|
||||
}
|
||||
|
||||
_disconnected(clean, frame) {
|
||||
if (!clean) {
|
||||
this._state = 'failed';
|
||||
}
|
||||
|
||||
const evt = new Event('rfbdisconnected');
|
||||
evt.clean = clean;
|
||||
evt.frame = frame;
|
||||
evt.iteration = this._iteration;
|
||||
|
||||
this.onrfbdisconnected(evt);
|
||||
}
|
||||
this.onrfbdisconnected(evt);
|
||||
}
|
||||
}
|
||||
|
||||
function start() {
|
||||
document.getElementById('startButton').value = "Running";
|
||||
document.getElementById('startButton').disabled = true;
|
||||
document.getElementById('startButton').value = 'Running';
|
||||
document.getElementById('startButton').disabled = true;
|
||||
|
||||
const iterations = document.getElementById('iterations').value;
|
||||
const iterations = document.getElementById('iterations').value;
|
||||
|
||||
let mode;
|
||||
let mode;
|
||||
|
||||
if (document.getElementById('mode1').checked) {
|
||||
message(`Starting performance playback (fullspeed) [${iterations} iteration(s)]`);
|
||||
mode = 'perftest';
|
||||
} else {
|
||||
message(`Starting realtime playback [${iterations} iteration(s)]`);
|
||||
mode = 'realtime';
|
||||
if (document.getElementById('mode1').checked) {
|
||||
message(`Starting performance playback (fullspeed) [${iterations} iteration(s)]`);
|
||||
mode = 'perftest';
|
||||
} else {
|
||||
message(`Starting realtime playback [${iterations} iteration(s)]`);
|
||||
mode = 'realtime';
|
||||
}
|
||||
|
||||
const player = new IterationPlayer(iterations, frames, encoding);
|
||||
player.oniterationfinish = (evt) => {
|
||||
message(`Iteration ${evt.number} took ${evt.duration}ms`);
|
||||
};
|
||||
player.onrfbdisconnected = (evt) => {
|
||||
if (!evt.clean) {
|
||||
message(`noVNC sent disconnected during iteration ${evt.iteration} frame ${evt.frame}`);
|
||||
}
|
||||
};
|
||||
player.onfinish = (evt) => {
|
||||
const iterTime = parseInt(evt.duration / evt.iterations, 10);
|
||||
message(`${evt.iterations} iterations took ${evt.duration}ms (average ${iterTime}ms / iteration)`);
|
||||
|
||||
const player = new IterationPlayer(iterations, frames, encoding);
|
||||
player.oniterationfinish = (evt) => {
|
||||
message(`Iteration ${evt.number} took ${evt.duration}ms`);
|
||||
};
|
||||
player.onrfbdisconnected = (evt) => {
|
||||
if (!evt.clean) {
|
||||
message(`noVNC sent disconnected during iteration ${evt.iteration} frame ${evt.frame}`);
|
||||
}
|
||||
};
|
||||
player.onfinish = (evt) => {
|
||||
const iterTime = parseInt(evt.duration / evt.iterations, 10);
|
||||
message(`${evt.iterations} iterations took ${evt.duration}ms (average ${iterTime}ms / iteration)`);
|
||||
|
||||
document.getElementById('startButton').disabled = false;
|
||||
document.getElementById('startButton').value = "Start";
|
||||
};
|
||||
player.start(mode);
|
||||
document.getElementById('startButton').disabled = false;
|
||||
document.getElementById('startButton').value = 'Start';
|
||||
};
|
||||
player.start(mode);
|
||||
}
|
||||
|
||||
loadFile().then(enableUI).catch(e => message("Error loading recording: " + e));
|
||||
loadFile().then(enableUI).catch(e => message('Error loading recording: ' + e));
|
||||
|
|
|
|||
|
|
@ -10,185 +10,185 @@ import Base64 from '../core/base64.js';
|
|||
|
||||
// Immediate polyfill
|
||||
if (window.setImmediate === undefined) {
|
||||
let _immediateIdCounter = 1;
|
||||
const _immediateFuncs = {};
|
||||
let _immediateIdCounter = 1;
|
||||
const _immediateFuncs = {};
|
||||
|
||||
window.setImmediate = (func) => {
|
||||
const index = _immediateIdCounter++;
|
||||
_immediateFuncs[index] = func;
|
||||
window.postMessage("noVNC immediate trigger:" + index, "*");
|
||||
return index;
|
||||
};
|
||||
window.setImmediate = (func) => {
|
||||
const index = _immediateIdCounter++;
|
||||
_immediateFuncs[index] = func;
|
||||
window.postMessage('noVNC immediate trigger:' + index, '*');
|
||||
return index;
|
||||
};
|
||||
|
||||
window.clearImmediate = (id) => {
|
||||
_immediateFuncs[id];
|
||||
};
|
||||
window.clearImmediate = (id) => {
|
||||
_immediateFuncs[id];
|
||||
};
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
if ((typeof event.data !== "string") ||
|
||||
(event.data.indexOf("noVNC immediate trigger:") !== 0)) {
|
||||
return;
|
||||
}
|
||||
window.addEventListener('message', (event) => {
|
||||
if ((typeof event.data !== 'string')
|
||||
|| (event.data.indexOf('noVNC immediate trigger:') !== 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = event.data.slice("noVNC immediate trigger:".length);
|
||||
const index = event.data.slice('noVNC immediate trigger:'.length);
|
||||
|
||||
const callback = _immediateFuncs[index];
|
||||
if (callback === undefined) {
|
||||
return;
|
||||
}
|
||||
const callback = _immediateFuncs[index];
|
||||
if (callback === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete _immediateFuncs[index];
|
||||
delete _immediateFuncs[index];
|
||||
|
||||
callback();
|
||||
});
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
export default class RecordingPlayer {
|
||||
constructor(frames, encoding, disconnected) {
|
||||
this._frames = frames;
|
||||
this._encoding = encoding;
|
||||
constructor(frames, encoding, disconnected) {
|
||||
this._frames = frames;
|
||||
this._encoding = encoding;
|
||||
|
||||
this._disconnected = disconnected;
|
||||
this._disconnected = disconnected;
|
||||
|
||||
if (this._encoding === undefined) {
|
||||
const frame = this._frames[0];
|
||||
const start = frame.indexOf('{', 1) + 1;
|
||||
if (frame.slice(start).startsWith('UkZC')) {
|
||||
this._encoding = 'base64';
|
||||
} else {
|
||||
this._encoding = 'binary';
|
||||
}
|
||||
}
|
||||
|
||||
this._rfb = undefined;
|
||||
this._frame_length = this._frames.length;
|
||||
|
||||
this._frame_index = 0;
|
||||
this._start_time = undefined;
|
||||
this._realtime = true;
|
||||
this._trafficManagement = true;
|
||||
|
||||
this._running = false;
|
||||
|
||||
this.onfinish = () => {};
|
||||
if (this._encoding === undefined) {
|
||||
const frame = this._frames[0];
|
||||
const start = frame.indexOf('{', 1) + 1;
|
||||
if (frame.slice(start).startsWith('UkZC')) {
|
||||
this._encoding = 'base64';
|
||||
} else {
|
||||
this._encoding = 'binary';
|
||||
}
|
||||
}
|
||||
|
||||
run(realtime, trafficManagement) {
|
||||
// initialize a new RFB
|
||||
this._rfb = new RFB(document.getElementById('VNC_screen'), 'wss://test');
|
||||
this._rfb.viewOnly = true;
|
||||
this._rfb.addEventListener("disconnect",
|
||||
this._handleDisconnect.bind(this));
|
||||
this._enablePlaybackMode();
|
||||
this._rfb = undefined;
|
||||
this._frame_length = this._frames.length;
|
||||
|
||||
// reset the frame index and timer
|
||||
this._frame_index = 0;
|
||||
this._start_time = (new Date()).getTime();
|
||||
this._frame_index = 0;
|
||||
this._start_time = undefined;
|
||||
this._realtime = true;
|
||||
this._trafficManagement = true;
|
||||
|
||||
this._realtime = realtime;
|
||||
this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
|
||||
this._running = false;
|
||||
|
||||
this._running = true;
|
||||
this.onfinish = () => {};
|
||||
}
|
||||
|
||||
this._queueNextPacket();
|
||||
run(realtime, trafficManagement) {
|
||||
// initialize a new RFB
|
||||
this._rfb = new RFB(document.getElementById('VNC_screen'), 'wss://test');
|
||||
this._rfb.viewOnly = true;
|
||||
this._rfb.addEventListener('disconnect',
|
||||
this._handleDisconnect.bind(this));
|
||||
this._enablePlaybackMode();
|
||||
|
||||
// reset the frame index and timer
|
||||
this._frame_index = 0;
|
||||
this._start_time = (new Date()).getTime();
|
||||
|
||||
this._realtime = realtime;
|
||||
this._trafficManagement = (trafficManagement === undefined) ? !realtime : trafficManagement;
|
||||
|
||||
this._running = true;
|
||||
|
||||
this._queueNextPacket();
|
||||
}
|
||||
|
||||
// _enablePlaybackMode mocks out things not required for running playback
|
||||
_enablePlaybackMode() {
|
||||
this._rfb._sock.send = () => {};
|
||||
this._rfb._sock.close = () => {};
|
||||
this._rfb._sock.flush = () => {};
|
||||
this._rfb._sock.open = function () {
|
||||
this.init();
|
||||
this._eventHandlers.open();
|
||||
};
|
||||
}
|
||||
|
||||
_queueNextPacket() {
|
||||
if (!this._running) { return; }
|
||||
|
||||
let frame = this._frames[this._frame_index];
|
||||
|
||||
// skip send frames
|
||||
while (this._frame_index < this._frame_length && frame.charAt(0) === '}') {
|
||||
this._frame_index++;
|
||||
frame = this._frames[this._frame_index];
|
||||
}
|
||||
|
||||
// _enablePlaybackMode mocks out things not required for running playback
|
||||
_enablePlaybackMode() {
|
||||
this._rfb._sock.send = () => {};
|
||||
this._rfb._sock.close = () => {};
|
||||
this._rfb._sock.flush = () => {};
|
||||
this._rfb._sock.open = function () {
|
||||
this.init();
|
||||
this._eventHandlers.open();
|
||||
};
|
||||
if (frame === 'EOF') {
|
||||
Log.Debug('Finished, found EOF');
|
||||
this._finish();
|
||||
return;
|
||||
}
|
||||
|
||||
_queueNextPacket() {
|
||||
if (!this._running) { return; }
|
||||
|
||||
let frame = this._frames[this._frame_index];
|
||||
|
||||
// skip send frames
|
||||
while (this._frame_index < this._frame_length && frame.charAt(0) === "}") {
|
||||
this._frame_index++;
|
||||
frame = this._frames[this._frame_index];
|
||||
}
|
||||
|
||||
if (frame === 'EOF') {
|
||||
Log.Debug('Finished, found EOF');
|
||||
this._finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._frame_index >= this._frame_length) {
|
||||
Log.Debug('Finished, no more frames');
|
||||
this._finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._realtime) {
|
||||
const foffset = frame.slice(1, frame.indexOf('{', 1));
|
||||
const toffset = (new Date()).getTime() - this._start_time;
|
||||
let delay = foffset - toffset;
|
||||
if (delay < 1) delay = 1;
|
||||
|
||||
setTimeout(this._doPacket.bind(this), delay);
|
||||
} else {
|
||||
setImmediate(this._doPacket.bind(this));
|
||||
}
|
||||
if (this._frame_index >= this._frame_length) {
|
||||
Log.Debug('Finished, no more frames');
|
||||
this._finish();
|
||||
return;
|
||||
}
|
||||
|
||||
_doPacket() {
|
||||
// Avoid having excessive queue buildup in non-realtime mode
|
||||
if (this._trafficManagement && this._rfb._flushing) {
|
||||
const orig = this._rfb._display.onflush;
|
||||
this._rfb._display.onflush = () => {
|
||||
this._rfb._display.onflush = orig;
|
||||
this._rfb._onFlush();
|
||||
this._doPacket();
|
||||
};
|
||||
return;
|
||||
}
|
||||
if (this._realtime) {
|
||||
const foffset = frame.slice(1, frame.indexOf('{', 1));
|
||||
const toffset = (new Date()).getTime() - this._start_time;
|
||||
let delay = foffset - toffset;
|
||||
if (delay < 1) delay = 1;
|
||||
|
||||
const frame = this._frames[this._frame_index];
|
||||
let start = frame.indexOf('{', 1) + 1;
|
||||
let u8;
|
||||
if (this._encoding === 'base64') {
|
||||
u8 = Base64.decode(frame.slice(start));
|
||||
start = 0;
|
||||
} else {
|
||||
u8 = new Uint8Array(frame.length - start);
|
||||
for (let i = 0; i < frame.length - start; i++) {
|
||||
u8[i] = frame.charCodeAt(start + i);
|
||||
}
|
||||
}
|
||||
setTimeout(this._doPacket.bind(this), delay);
|
||||
} else {
|
||||
setImmediate(this._doPacket.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
this._rfb._sock._recv_message({'data': u8});
|
||||
this._frame_index++;
|
||||
|
||||
this._queueNextPacket();
|
||||
_doPacket() {
|
||||
// Avoid having excessive queue buildup in non-realtime mode
|
||||
if (this._trafficManagement && this._rfb._flushing) {
|
||||
const orig = this._rfb._display.onflush;
|
||||
this._rfb._display.onflush = () => {
|
||||
this._rfb._display.onflush = orig;
|
||||
this._rfb._onFlush();
|
||||
this._doPacket();
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
_finish() {
|
||||
if (this._rfb._display.pending()) {
|
||||
this._rfb._display.onflush = () => {
|
||||
if (this._rfb._flushing) {
|
||||
this._rfb._onFlush();
|
||||
}
|
||||
this._finish();
|
||||
};
|
||||
this._rfb._display.flush();
|
||||
} else {
|
||||
this._running = false;
|
||||
this._rfb._sock._eventHandlers.close({code: 1000, reason: ""});
|
||||
delete this._rfb;
|
||||
this.onfinish((new Date()).getTime() - this._start_time);
|
||||
}
|
||||
const frame = this._frames[this._frame_index];
|
||||
let start = frame.indexOf('{', 1) + 1;
|
||||
let u8;
|
||||
if (this._encoding === 'base64') {
|
||||
u8 = Base64.decode(frame.slice(start));
|
||||
start = 0;
|
||||
} else {
|
||||
u8 = new Uint8Array(frame.length - start);
|
||||
for (let i = 0; i < frame.length - start; i++) {
|
||||
u8[i] = frame.charCodeAt(start + i);
|
||||
}
|
||||
}
|
||||
|
||||
_handleDisconnect(evt) {
|
||||
this._running = false;
|
||||
this._disconnected(evt.detail.clean, this._frame_index);
|
||||
this._rfb._sock._recv_message({ data: u8 });
|
||||
this._frame_index++;
|
||||
|
||||
this._queueNextPacket();
|
||||
}
|
||||
|
||||
_finish() {
|
||||
if (this._rfb._display.pending()) {
|
||||
this._rfb._display.onflush = () => {
|
||||
if (this._rfb._flushing) {
|
||||
this._rfb._onFlush();
|
||||
}
|
||||
this._finish();
|
||||
};
|
||||
this._rfb._display.flush();
|
||||
} else {
|
||||
this._running = false;
|
||||
this._rfb._sock._eventHandlers.close({ code: 1000, reason: '' });
|
||||
delete this._rfb;
|
||||
this.onfinish((new Date()).getTime() - this._start_time);
|
||||
}
|
||||
}
|
||||
|
||||
_handleDisconnect(evt) {
|
||||
this._running = false;
|
||||
this._disconnected(evt.detail.clean, this._frame_index);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,32 +2,32 @@ const expect = chai.expect;
|
|||
|
||||
import Base64 from '../core/base64.js';
|
||||
|
||||
describe('Base64 Tools', function() {
|
||||
"use strict";
|
||||
describe('Base64 Tools', function () {
|
||||
'use strict';
|
||||
|
||||
const BIN_ARR = new Array(256);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
BIN_ARR[i] = i;
|
||||
}
|
||||
const BIN_ARR = new Array(256);
|
||||
for (let i = 0; i < 256; i++) {
|
||||
BIN_ARR[i] = i;
|
||||
}
|
||||
|
||||
const B64_STR = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==";
|
||||
const B64_STR = 'AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==';
|
||||
|
||||
|
||||
describe('encode', function() {
|
||||
it('should encode a binary string into Base64', function() {
|
||||
const encoded = Base64.encode(BIN_ARR);
|
||||
expect(encoded).to.equal(B64_STR);
|
||||
});
|
||||
describe('encode', function () {
|
||||
it('should encode a binary string into Base64', function () {
|
||||
const encoded = Base64.encode(BIN_ARR);
|
||||
expect(encoded).to.equal(B64_STR);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decode', function () {
|
||||
it('should decode a Base64 string into a normal string', function () {
|
||||
const decoded = Base64.decode(B64_STR);
|
||||
expect(decoded).to.deep.equal(BIN_ARR);
|
||||
});
|
||||
|
||||
describe('decode', function() {
|
||||
it('should decode a Base64 string into a normal string', function() {
|
||||
const decoded = Base64.decode(B64_STR);
|
||||
expect(decoded).to.deep.equal(BIN_ARR);
|
||||
});
|
||||
|
||||
it('should throw an error if we have extra characters at the end of the string', function() {
|
||||
expect(() => Base64.decode(B64_STR+'abcdef')).to.throw(Error);
|
||||
});
|
||||
it('should throw an error if we have extra characters at the end of the string', function () {
|
||||
expect(() => Base64.decode(B64_STR + 'abcdef')).to.throw(Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,483 +6,501 @@ import Display from '../core/display.js';
|
|||
import sinon from '../vendor/sinon.js';
|
||||
|
||||
describe('Display/Canvas Helper', function () {
|
||||
const checked_data = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
const checked_data = new Uint8Array([
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255,
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255,
|
||||
0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255
|
||||
]);
|
||||
|
||||
const basic_data = new Uint8Array([0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]);
|
||||
const basic_data = new Uint8Array([
|
||||
0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255
|
||||
]);
|
||||
|
||||
function make_image_canvas (input_data) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 4;
|
||||
canvas.height = 4;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const data = ctx.createImageData(4, 4);
|
||||
for (let i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
|
||||
ctx.putImageData(data, 0, 0);
|
||||
return canvas;
|
||||
}
|
||||
function make_image_canvas(input_data) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 4;
|
||||
canvas.height = 4;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const data = ctx.createImageData(4, 4);
|
||||
for (let i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; }
|
||||
ctx.putImageData(data, 0, 0);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function make_image_png (input_data) {
|
||||
const canvas = make_image_canvas(input_data);
|
||||
const url = canvas.toDataURL();
|
||||
const data = url.split(",")[1];
|
||||
return Base64.decode(data);
|
||||
}
|
||||
function make_image_png(input_data) {
|
||||
const canvas = make_image_canvas(input_data);
|
||||
const url = canvas.toDataURL();
|
||||
const data = url.split(',')[1];
|
||||
return Base64.decode(data);
|
||||
}
|
||||
|
||||
describe('viewport handling', function () {
|
||||
let display;
|
||||
beforeEach(function () {
|
||||
display = new Display(document.createElement('canvas'));
|
||||
display.clipViewport = true;
|
||||
display.resize(5, 5);
|
||||
display.viewportChangeSize(3, 3);
|
||||
display.viewportChangePos(1, 1);
|
||||
});
|
||||
|
||||
it('should take viewport location into consideration when drawing images', function () {
|
||||
display.resize(4, 4);
|
||||
display.viewportChangeSize(2, 2);
|
||||
display.drawImage(make_image_canvas(basic_data), 1, 1);
|
||||
display.flip();
|
||||
|
||||
const expected = new Uint8Array(16);
|
||||
for (let i = 0; i < 8; i++) { expected[i] = basic_data[i]; }
|
||||
for (let i = 8; i < 16; i++) { expected[i] = 0; }
|
||||
expect(display).to.have.displayed(expected);
|
||||
});
|
||||
|
||||
it('should resize the target canvas when resizing the viewport', function() {
|
||||
display.viewportChangeSize(2, 2);
|
||||
expect(display._target.width).to.equal(2);
|
||||
expect(display._target.height).to.equal(2);
|
||||
});
|
||||
|
||||
it('should move the viewport if necessary', function() {
|
||||
display.viewportChangeSize(5, 5);
|
||||
expect(display.absX(0)).to.equal(0);
|
||||
expect(display.absY(0)).to.equal(0);
|
||||
expect(display._target.width).to.equal(5);
|
||||
expect(display._target.height).to.equal(5);
|
||||
});
|
||||
|
||||
it('should limit the viewport to the framebuffer size', function() {
|
||||
display.viewportChangeSize(6, 6);
|
||||
expect(display._target.width).to.equal(5);
|
||||
expect(display._target.height).to.equal(5);
|
||||
});
|
||||
|
||||
it('should redraw when moving the viewport', function () {
|
||||
display.flip = sinon.spy();
|
||||
display.viewportChangePos(-1, 1);
|
||||
expect(display.flip).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should redraw when resizing the viewport', function () {
|
||||
display.flip = sinon.spy();
|
||||
display.viewportChangeSize(2, 2);
|
||||
expect(display.flip).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should show the entire framebuffer when disabling the viewport', function() {
|
||||
display.clipViewport = false;
|
||||
expect(display.absX(0)).to.equal(0);
|
||||
expect(display.absY(0)).to.equal(0);
|
||||
expect(display._target.width).to.equal(5);
|
||||
expect(display._target.height).to.equal(5);
|
||||
});
|
||||
|
||||
it('should ignore viewport changes when the viewport is disabled', function() {
|
||||
display.clipViewport = false;
|
||||
display.viewportChangeSize(2, 2);
|
||||
display.viewportChangePos(1, 1);
|
||||
expect(display.absX(0)).to.equal(0);
|
||||
expect(display.absY(0)).to.equal(0);
|
||||
expect(display._target.width).to.equal(5);
|
||||
expect(display._target.height).to.equal(5);
|
||||
});
|
||||
|
||||
it('should show the entire framebuffer just after enabling the viewport', function() {
|
||||
display.clipViewport = false;
|
||||
display.clipViewport = true;
|
||||
expect(display.absX(0)).to.equal(0);
|
||||
expect(display.absY(0)).to.equal(0);
|
||||
expect(display._target.width).to.equal(5);
|
||||
expect(display._target.height).to.equal(5);
|
||||
});
|
||||
describe('viewport handling', function () {
|
||||
let display;
|
||||
beforeEach(function () {
|
||||
display = new Display(document.createElement('canvas'));
|
||||
display.clipViewport = true;
|
||||
display.resize(5, 5);
|
||||
display.viewportChangeSize(3, 3);
|
||||
display.viewportChangePos(1, 1);
|
||||
});
|
||||
|
||||
describe('resizing', function () {
|
||||
let display;
|
||||
beforeEach(function () {
|
||||
display = new Display(document.createElement('canvas'));
|
||||
display.clipViewport = false;
|
||||
display.resize(4, 4);
|
||||
});
|
||||
it('should take viewport location into consideration when drawing images', function () {
|
||||
display.resize(4, 4);
|
||||
display.viewportChangeSize(2, 2);
|
||||
display.drawImage(make_image_canvas(basic_data), 1, 1);
|
||||
display.flip();
|
||||
|
||||
it('should change the size of the logical canvas', function () {
|
||||
display.resize(5, 7);
|
||||
expect(display._fb_width).to.equal(5);
|
||||
expect(display._fb_height).to.equal(7);
|
||||
});
|
||||
|
||||
it('should keep the framebuffer data', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
|
||||
display.resize(2, 2);
|
||||
display.flip();
|
||||
const expected = [];
|
||||
for (let i = 0; i < 4 * 2*2; i += 4) {
|
||||
expected[i] = 0xff;
|
||||
expected[i+1] = expected[i+2] = 0;
|
||||
expected[i+3] = 0xff;
|
||||
}
|
||||
expect(display).to.have.displayed(new Uint8Array(expected));
|
||||
});
|
||||
|
||||
describe('viewport', function () {
|
||||
beforeEach(function () {
|
||||
display.clipViewport = true;
|
||||
display.viewportChangeSize(3, 3);
|
||||
display.viewportChangePos(1, 1);
|
||||
});
|
||||
|
||||
it('should keep the viewport position and size if possible', function () {
|
||||
display.resize(6, 6);
|
||||
expect(display.absX(0)).to.equal(1);
|
||||
expect(display.absY(0)).to.equal(1);
|
||||
expect(display._target.width).to.equal(3);
|
||||
expect(display._target.height).to.equal(3);
|
||||
});
|
||||
|
||||
it('should move the viewport if necessary', function () {
|
||||
display.resize(3, 3);
|
||||
expect(display.absX(0)).to.equal(0);
|
||||
expect(display.absY(0)).to.equal(0);
|
||||
expect(display._target.width).to.equal(3);
|
||||
expect(display._target.height).to.equal(3);
|
||||
});
|
||||
|
||||
it('should shrink the viewport if necessary', function () {
|
||||
display.resize(2, 2);
|
||||
expect(display.absX(0)).to.equal(0);
|
||||
expect(display.absY(0)).to.equal(0);
|
||||
expect(display._target.width).to.equal(2);
|
||||
expect(display._target.height).to.equal(2);
|
||||
});
|
||||
});
|
||||
const expected = new Uint8Array(16);
|
||||
for (let i = 0; i < 8; i++) { expected[i] = basic_data[i]; }
|
||||
for (let i = 8; i < 16; i++) { expected[i] = 0; }
|
||||
expect(display).to.have.displayed(expected);
|
||||
});
|
||||
|
||||
describe('rescaling', function () {
|
||||
let display;
|
||||
let canvas;
|
||||
|
||||
beforeEach(function () {
|
||||
canvas = document.createElement('canvas');
|
||||
display = new Display(canvas);
|
||||
display.clipViewport = true;
|
||||
display.resize(4, 4);
|
||||
display.viewportChangeSize(3, 3);
|
||||
display.viewportChangePos(1, 1);
|
||||
document.body.appendChild(canvas);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
document.body.removeChild(canvas);
|
||||
});
|
||||
|
||||
it('should not change the bitmap size of the canvas', function () {
|
||||
display.scale = 2.0;
|
||||
expect(canvas.width).to.equal(3);
|
||||
expect(canvas.height).to.equal(3);
|
||||
});
|
||||
|
||||
it('should change the effective rendered size of the canvas', function () {
|
||||
display.scale = 2.0;
|
||||
expect(canvas.clientWidth).to.equal(6);
|
||||
expect(canvas.clientHeight).to.equal(6);
|
||||
});
|
||||
|
||||
it('should not change when resizing', function () {
|
||||
display.scale = 2.0;
|
||||
display.resize(5, 5);
|
||||
expect(display.scale).to.equal(2.0);
|
||||
expect(canvas.width).to.equal(3);
|
||||
expect(canvas.height).to.equal(3);
|
||||
expect(canvas.clientWidth).to.equal(6);
|
||||
expect(canvas.clientHeight).to.equal(6);
|
||||
});
|
||||
it('should resize the target canvas when resizing the viewport', function () {
|
||||
display.viewportChangeSize(2, 2);
|
||||
expect(display._target.width).to.equal(2);
|
||||
expect(display._target.height).to.equal(2);
|
||||
});
|
||||
|
||||
describe('autoscaling', function () {
|
||||
let display;
|
||||
let canvas;
|
||||
|
||||
beforeEach(function () {
|
||||
canvas = document.createElement('canvas');
|
||||
display = new Display(canvas);
|
||||
display.clipViewport = true;
|
||||
display.resize(4, 3);
|
||||
document.body.appendChild(canvas);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
document.body.removeChild(canvas);
|
||||
});
|
||||
|
||||
it('should preserve aspect ratio while autoscaling', function () {
|
||||
display.autoscale(16, 9);
|
||||
expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3);
|
||||
});
|
||||
|
||||
it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
|
||||
display.autoscale(9, 16);
|
||||
expect(display.absX(9)).to.equal(4);
|
||||
expect(display.absY(18)).to.equal(8);
|
||||
expect(canvas.clientWidth).to.equal(9);
|
||||
expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3)
|
||||
});
|
||||
|
||||
it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
|
||||
display.autoscale(16, 9);
|
||||
expect(display.absX(9)).to.equal(3);
|
||||
expect(display.absY(18)).to.equal(6);
|
||||
expect(canvas.clientWidth).to.equal(12); // 16 * (4 / 3)
|
||||
expect(canvas.clientHeight).to.equal(9);
|
||||
|
||||
});
|
||||
|
||||
it('should not change the bitmap size of the canvas', function () {
|
||||
display.autoscale(16, 9);
|
||||
expect(canvas.width).to.equal(4);
|
||||
expect(canvas.height).to.equal(3);
|
||||
});
|
||||
it('should move the viewport if necessary', function () {
|
||||
display.viewportChangeSize(5, 5);
|
||||
expect(display.absX(0)).to.equal(0);
|
||||
expect(display.absY(0)).to.equal(0);
|
||||
expect(display._target.width).to.equal(5);
|
||||
expect(display._target.height).to.equal(5);
|
||||
});
|
||||
|
||||
describe('drawing', function () {
|
||||
|
||||
// TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
|
||||
// basic cases
|
||||
let display;
|
||||
beforeEach(function () {
|
||||
display = new Display(document.createElement('canvas'));
|
||||
display.resize(4, 4);
|
||||
});
|
||||
|
||||
it('should clear the screen on #clear without a logo set', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
|
||||
display._logo = null;
|
||||
display.clear();
|
||||
display.resize(4, 4);
|
||||
const empty = [];
|
||||
for (let i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
|
||||
expect(display).to.have.displayed(new Uint8Array(empty));
|
||||
});
|
||||
|
||||
it('should draw the logo on #clear with a logo set', function (done) {
|
||||
display._logo = { width: 4, height: 4, type: "image/png", data: make_image_png(checked_data) };
|
||||
display.clear();
|
||||
display.onflush = () => {
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
expect(display._fb_width).to.equal(4);
|
||||
expect(display._fb_height).to.equal(4);
|
||||
done();
|
||||
};
|
||||
display.flush();
|
||||
});
|
||||
|
||||
it('should not draw directly on the target canvas', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
|
||||
display.flip();
|
||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
const expected = [];
|
||||
for (let i = 0; i < 4 * display._fb_width * display._fb_height; i += 4) {
|
||||
expected[i] = 0xff;
|
||||
expected[i+1] = expected[i+2] = 0;
|
||||
expected[i+3] = 0xff;
|
||||
}
|
||||
expect(display).to.have.displayed(new Uint8Array(expected));
|
||||
});
|
||||
|
||||
it('should support filling a rectangle with particular color via #fillRect', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
|
||||
display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support copying an portion of the canvas via #copyImage', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
|
||||
display.copyImage(0, 0, 2, 2, 2, 2);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing images via #imageRect', function (done) {
|
||||
display.imageRect(0, 0, "image/png", make_image_png(checked_data));
|
||||
display.flip();
|
||||
display.onflush = () => {
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
done();
|
||||
};
|
||||
display.flush();
|
||||
});
|
||||
|
||||
it('should support drawing tile data with a background color and sub tiles', function () {
|
||||
display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
|
||||
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.finishTile();
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
// We have a special cache for 16x16 tiles that we need to test
|
||||
it('should support drawing a 16x16 tile', function () {
|
||||
const large_checked_data = new Uint8Array(16*16*4);
|
||||
display.resize(16, 16);
|
||||
|
||||
for (let y = 0;y < 16;y++) {
|
||||
for (let x = 0;x < 16;x++) {
|
||||
let pixel;
|
||||
if ((x < 4) && (y < 4)) {
|
||||
// NB: of course IE11 doesn't support #slice on ArrayBufferViews...
|
||||
pixel = Array.prototype.slice.call(checked_data, (y*4+x)*4, (y*4+x+1)*4);
|
||||
} else {
|
||||
pixel = [0, 0xff, 0, 255];
|
||||
}
|
||||
large_checked_data.set(pixel, (y*16+x)*4);
|
||||
}
|
||||
}
|
||||
|
||||
display.startTile(0, 0, 16, 16, [0, 0xff, 0]);
|
||||
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
|
||||
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.finishTile();
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(large_checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing BGRX blit images with true color via #blitImage', function () {
|
||||
const data = [];
|
||||
for (let i = 0; i < 16; i++) {
|
||||
data[i * 4] = checked_data[i * 4 + 2];
|
||||
data[i * 4 + 1] = checked_data[i * 4 + 1];
|
||||
data[i * 4 + 2] = checked_data[i * 4];
|
||||
data[i * 4 + 3] = checked_data[i * 4 + 3];
|
||||
}
|
||||
display.blitImage(0, 0, 4, 4, data, 0);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
|
||||
const data = [];
|
||||
for (let i = 0; i < 16; i++) {
|
||||
data[i * 3] = checked_data[i * 4];
|
||||
data[i * 3 + 1] = checked_data[i * 4 + 1];
|
||||
data[i * 3 + 2] = checked_data[i * 4 + 2];
|
||||
}
|
||||
display.blitRgbImage(0, 0, 4, 4, data, 0);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing an image object via #drawImage', function () {
|
||||
const img = make_image_canvas(checked_data);
|
||||
display.drawImage(img, 0, 0);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
it('should limit the viewport to the framebuffer size', function () {
|
||||
display.viewportChangeSize(6, 6);
|
||||
expect(display._target.width).to.equal(5);
|
||||
expect(display._target.height).to.equal(5);
|
||||
});
|
||||
|
||||
describe('the render queue processor', function () {
|
||||
let display;
|
||||
beforeEach(function () {
|
||||
display = new Display(document.createElement('canvas'));
|
||||
display.resize(4, 4);
|
||||
sinon.spy(display, '_scan_renderQ');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
window.requestAnimationFrame = this.old_requestAnimationFrame;
|
||||
});
|
||||
|
||||
it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
|
||||
display._renderQ_push({ type: 'noop' }); // does nothing
|
||||
expect(display._scan_renderQ).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
|
||||
display._renderQ.length = 2;
|
||||
display._renderQ_push({ type: 'noop' });
|
||||
expect(display._scan_renderQ).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
|
||||
const img = { complete: false, addEventListener: sinon.spy() }
|
||||
display._renderQ = [{ type: 'img', x: 3, y: 4, img: img },
|
||||
{ type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
|
||||
display.drawImage = sinon.spy();
|
||||
display.fillRect = sinon.spy();
|
||||
|
||||
display._scan_renderQ();
|
||||
expect(display.drawImage).to.not.have.been.called;
|
||||
expect(display.fillRect).to.not.have.been.called;
|
||||
expect(img.addEventListener).to.have.been.calledOnce;
|
||||
|
||||
display._renderQ[0].img.complete = true;
|
||||
display._scan_renderQ();
|
||||
expect(display.drawImage).to.have.been.calledOnce;
|
||||
expect(display.fillRect).to.have.been.calledOnce;
|
||||
expect(img.addEventListener).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should call callback when queue is flushed', function () {
|
||||
display.onflush = sinon.spy();
|
||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
expect(display.onflush).to.not.have.been.called;
|
||||
display.flush();
|
||||
expect(display.onflush).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should draw a blit image on type "blit"', function () {
|
||||
display.blitImage = sinon.spy();
|
||||
display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
|
||||
expect(display.blitImage).to.have.been.calledOnce;
|
||||
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
|
||||
});
|
||||
|
||||
it('should draw a blit RGB image on type "blitRgb"', function () {
|
||||
display.blitRgbImage = sinon.spy();
|
||||
display._renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
|
||||
expect(display.blitRgbImage).to.have.been.calledOnce;
|
||||
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
|
||||
});
|
||||
|
||||
it('should copy a region on type "copy"', function () {
|
||||
display.copyImage = sinon.spy();
|
||||
display._renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
|
||||
expect(display.copyImage).to.have.been.calledOnce;
|
||||
expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
|
||||
});
|
||||
|
||||
it('should fill a rect with a given color on type "fill"', function () {
|
||||
display.fillRect = sinon.spy();
|
||||
display._renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
|
||||
expect(display.fillRect).to.have.been.calledOnce;
|
||||
expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
|
||||
});
|
||||
|
||||
it('should draw an image from an image object on type "img" (if complete)', function () {
|
||||
display.drawImage = sinon.spy();
|
||||
display._renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
|
||||
expect(display.drawImage).to.have.been.calledOnce;
|
||||
expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
|
||||
});
|
||||
it('should redraw when moving the viewport', function () {
|
||||
display.flip = sinon.spy();
|
||||
display.viewportChangePos(-1, 1);
|
||||
expect(display.flip).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should redraw when resizing the viewport', function () {
|
||||
display.flip = sinon.spy();
|
||||
display.viewportChangeSize(2, 2);
|
||||
expect(display.flip).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should show the entire framebuffer when disabling the viewport', function () {
|
||||
display.clipViewport = false;
|
||||
expect(display.absX(0)).to.equal(0);
|
||||
expect(display.absY(0)).to.equal(0);
|
||||
expect(display._target.width).to.equal(5);
|
||||
expect(display._target.height).to.equal(5);
|
||||
});
|
||||
|
||||
it('should ignore viewport changes when the viewport is disabled', function () {
|
||||
display.clipViewport = false;
|
||||
display.viewportChangeSize(2, 2);
|
||||
display.viewportChangePos(1, 1);
|
||||
expect(display.absX(0)).to.equal(0);
|
||||
expect(display.absY(0)).to.equal(0);
|
||||
expect(display._target.width).to.equal(5);
|
||||
expect(display._target.height).to.equal(5);
|
||||
});
|
||||
|
||||
it('should show the entire framebuffer just after enabling the viewport', function () {
|
||||
display.clipViewport = false;
|
||||
display.clipViewport = true;
|
||||
expect(display.absX(0)).to.equal(0);
|
||||
expect(display.absY(0)).to.equal(0);
|
||||
expect(display._target.width).to.equal(5);
|
||||
expect(display._target.height).to.equal(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resizing', function () {
|
||||
let display;
|
||||
beforeEach(function () {
|
||||
display = new Display(document.createElement('canvas'));
|
||||
display.clipViewport = false;
|
||||
display.resize(4, 4);
|
||||
});
|
||||
|
||||
it('should change the size of the logical canvas', function () {
|
||||
display.resize(5, 7);
|
||||
expect(display._fb_width).to.equal(5);
|
||||
expect(display._fb_height).to.equal(7);
|
||||
});
|
||||
|
||||
it('should keep the framebuffer data', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
|
||||
display.resize(2, 2);
|
||||
display.flip();
|
||||
const expected = [];
|
||||
for (let i = 0; i < 4 * 2 * 2; i += 4) {
|
||||
expected[i] = 0xff;
|
||||
expected[i + 1] = 0;
|
||||
expected[i + 2] = 0;
|
||||
expected[i + 3] = 0xff;
|
||||
}
|
||||
expect(display).to.have.displayed(new Uint8Array(expected));
|
||||
});
|
||||
|
||||
describe('viewport', function () {
|
||||
beforeEach(function () {
|
||||
display.clipViewport = true;
|
||||
display.viewportChangeSize(3, 3);
|
||||
display.viewportChangePos(1, 1);
|
||||
});
|
||||
|
||||
it('should keep the viewport position and size if possible', function () {
|
||||
display.resize(6, 6);
|
||||
expect(display.absX(0)).to.equal(1);
|
||||
expect(display.absY(0)).to.equal(1);
|
||||
expect(display._target.width).to.equal(3);
|
||||
expect(display._target.height).to.equal(3);
|
||||
});
|
||||
|
||||
it('should move the viewport if necessary', function () {
|
||||
display.resize(3, 3);
|
||||
expect(display.absX(0)).to.equal(0);
|
||||
expect(display.absY(0)).to.equal(0);
|
||||
expect(display._target.width).to.equal(3);
|
||||
expect(display._target.height).to.equal(3);
|
||||
});
|
||||
|
||||
it('should shrink the viewport if necessary', function () {
|
||||
display.resize(2, 2);
|
||||
expect(display.absX(0)).to.equal(0);
|
||||
expect(display.absY(0)).to.equal(0);
|
||||
expect(display._target.width).to.equal(2);
|
||||
expect(display._target.height).to.equal(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('rescaling', function () {
|
||||
let display;
|
||||
let canvas;
|
||||
|
||||
beforeEach(function () {
|
||||
canvas = document.createElement('canvas');
|
||||
display = new Display(canvas);
|
||||
display.clipViewport = true;
|
||||
display.resize(4, 4);
|
||||
display.viewportChangeSize(3, 3);
|
||||
display.viewportChangePos(1, 1);
|
||||
document.body.appendChild(canvas);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
document.body.removeChild(canvas);
|
||||
});
|
||||
|
||||
it('should not change the bitmap size of the canvas', function () {
|
||||
display.scale = 2.0;
|
||||
expect(canvas.width).to.equal(3);
|
||||
expect(canvas.height).to.equal(3);
|
||||
});
|
||||
|
||||
it('should change the effective rendered size of the canvas', function () {
|
||||
display.scale = 2.0;
|
||||
expect(canvas.clientWidth).to.equal(6);
|
||||
expect(canvas.clientHeight).to.equal(6);
|
||||
});
|
||||
|
||||
it('should not change when resizing', function () {
|
||||
display.scale = 2.0;
|
||||
display.resize(5, 5);
|
||||
expect(display.scale).to.equal(2.0);
|
||||
expect(canvas.width).to.equal(3);
|
||||
expect(canvas.height).to.equal(3);
|
||||
expect(canvas.clientWidth).to.equal(6);
|
||||
expect(canvas.clientHeight).to.equal(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('autoscaling', function () {
|
||||
let display;
|
||||
let canvas;
|
||||
|
||||
beforeEach(function () {
|
||||
canvas = document.createElement('canvas');
|
||||
display = new Display(canvas);
|
||||
display.clipViewport = true;
|
||||
display.resize(4, 3);
|
||||
document.body.appendChild(canvas);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
document.body.removeChild(canvas);
|
||||
});
|
||||
|
||||
it('should preserve aspect ratio while autoscaling', function () {
|
||||
display.autoscale(16, 9);
|
||||
expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3);
|
||||
});
|
||||
|
||||
it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
|
||||
display.autoscale(9, 16);
|
||||
expect(display.absX(9)).to.equal(4);
|
||||
expect(display.absY(18)).to.equal(8);
|
||||
expect(canvas.clientWidth).to.equal(9);
|
||||
expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3)
|
||||
});
|
||||
|
||||
it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
|
||||
display.autoscale(16, 9);
|
||||
expect(display.absX(9)).to.equal(3);
|
||||
expect(display.absY(18)).to.equal(6);
|
||||
expect(canvas.clientWidth).to.equal(12); // 16 * (4 / 3)
|
||||
expect(canvas.clientHeight).to.equal(9);
|
||||
});
|
||||
|
||||
it('should not change the bitmap size of the canvas', function () {
|
||||
display.autoscale(16, 9);
|
||||
expect(canvas.width).to.equal(4);
|
||||
expect(canvas.height).to.equal(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('drawing', function () {
|
||||
// TODO(directxman12): improve the tests for each of the drawing functions to cover
|
||||
// more than just the basic cases
|
||||
let display;
|
||||
beforeEach(function () {
|
||||
display = new Display(document.createElement('canvas'));
|
||||
display.resize(4, 4);
|
||||
});
|
||||
|
||||
it('should clear the screen on #clear without a logo set', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]);
|
||||
display._logo = null;
|
||||
display.clear();
|
||||
display.resize(4, 4);
|
||||
const empty = [];
|
||||
for (let i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; }
|
||||
expect(display).to.have.displayed(new Uint8Array(empty));
|
||||
});
|
||||
|
||||
it('should draw the logo on #clear with a logo set', function (done) {
|
||||
display._logo = {
|
||||
width: 4, height: 4, type: 'image/png', data: make_image_png(checked_data)
|
||||
};
|
||||
display.clear();
|
||||
display.onflush = () => {
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
expect(display._fb_width).to.equal(4);
|
||||
expect(display._fb_height).to.equal(4);
|
||||
done();
|
||||
};
|
||||
display.flush();
|
||||
});
|
||||
|
||||
it('should not draw directly on the target canvas', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0, 0, 0xff]);
|
||||
display.flip();
|
||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
const expected = [];
|
||||
for (let i = 0; i < 4 * display._fb_width * display._fb_height; i += 4) {
|
||||
expected[i] = 0xff;
|
||||
expected[i + 1] = 0;
|
||||
expected[i + 2] = 0;
|
||||
expected[i + 3] = 0xff;
|
||||
}
|
||||
expect(display).to.have.displayed(new Uint8Array(expected));
|
||||
});
|
||||
|
||||
it('should support filling a rectangle with particular color via #fillRect', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
display.fillRect(0, 0, 2, 2, [0xff, 0, 0]);
|
||||
display.fillRect(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support copying an portion of the canvas via #copyImage', function () {
|
||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]);
|
||||
display.copyImage(0, 0, 2, 2, 2, 2);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing images via #imageRect', function (done) {
|
||||
display.imageRect(0, 0, 'image/png', make_image_png(checked_data));
|
||||
display.flip();
|
||||
display.onflush = () => {
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
done();
|
||||
};
|
||||
display.flush();
|
||||
});
|
||||
|
||||
it('should support drawing tile data with a background color and sub tiles', function () {
|
||||
display.startTile(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
|
||||
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.finishTile();
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
// We have a special cache for 16x16 tiles that we need to test
|
||||
it('should support drawing a 16x16 tile', function () {
|
||||
const large_checked_data = new Uint8Array(16 * 16 * 4);
|
||||
display.resize(16, 16);
|
||||
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
let pixel;
|
||||
if ((x < 4) && (y < 4)) {
|
||||
// NB: of course IE11 doesn't support #slice on ArrayBufferViews...
|
||||
pixel = Array.prototype.slice.call(checked_data, (y * 4 + x) * 4, (y * 4 + x + 1) * 4);
|
||||
} else {
|
||||
pixel = [0, 0xff, 0, 255];
|
||||
}
|
||||
large_checked_data.set(pixel, (y * 16 + x) * 4);
|
||||
}
|
||||
}
|
||||
|
||||
display.startTile(0, 0, 16, 16, [0, 0xff, 0]);
|
||||
display.subTile(0, 0, 2, 2, [0xff, 0, 0]);
|
||||
display.subTile(2, 2, 2, 2, [0xff, 0, 0]);
|
||||
display.finishTile();
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(large_checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing BGRX blit images with true color via #blitImage', function () {
|
||||
const data = [];
|
||||
for (let i = 0; i < 16; i++) {
|
||||
data[i * 4] = checked_data[i * 4 + 2];
|
||||
data[i * 4 + 1] = checked_data[i * 4 + 1];
|
||||
data[i * 4 + 2] = checked_data[i * 4];
|
||||
data[i * 4 + 3] = checked_data[i * 4 + 3];
|
||||
}
|
||||
display.blitImage(0, 0, 4, 4, data, 0);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing RGB blit images with true color via #blitRgbImage', function () {
|
||||
const data = [];
|
||||
for (let i = 0; i < 16; i++) {
|
||||
data[i * 3] = checked_data[i * 4];
|
||||
data[i * 3 + 1] = checked_data[i * 4 + 1];
|
||||
data[i * 3 + 2] = checked_data[i * 4 + 2];
|
||||
}
|
||||
display.blitRgbImage(0, 0, 4, 4, data, 0);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
|
||||
it('should support drawing an image object via #drawImage', function () {
|
||||
const img = make_image_canvas(checked_data);
|
||||
display.drawImage(img, 0, 0);
|
||||
display.flip();
|
||||
expect(display).to.have.displayed(checked_data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('the render queue processor', function () {
|
||||
let display;
|
||||
beforeEach(function () {
|
||||
display = new Display(document.createElement('canvas'));
|
||||
display.resize(4, 4);
|
||||
sinon.spy(display, '_scan_renderQ');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
window.requestAnimationFrame = this.old_requestAnimationFrame;
|
||||
});
|
||||
|
||||
it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
|
||||
display._renderQ_push({ type: 'noop' }); // does nothing
|
||||
expect(display._scan_renderQ).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
|
||||
display._renderQ.length = 2;
|
||||
display._renderQ_push({ type: 'noop' });
|
||||
expect(display._scan_renderQ).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
|
||||
const img = { complete: false, addEventListener: sinon.spy() };
|
||||
display._renderQ = [{
|
||||
type: 'img', x: 3, y: 4, img: img
|
||||
},
|
||||
{
|
||||
type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5
|
||||
}];
|
||||
display.drawImage = sinon.spy();
|
||||
display.fillRect = sinon.spy();
|
||||
|
||||
display._scan_renderQ();
|
||||
expect(display.drawImage).to.not.have.been.called;
|
||||
expect(display.fillRect).to.not.have.been.called;
|
||||
expect(img.addEventListener).to.have.been.calledOnce;
|
||||
|
||||
display._renderQ[0].img.complete = true;
|
||||
display._scan_renderQ();
|
||||
expect(display.drawImage).to.have.been.calledOnce;
|
||||
expect(display.fillRect).to.have.been.calledOnce;
|
||||
expect(img.addEventListener).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should call callback when queue is flushed', function () {
|
||||
display.onflush = sinon.spy();
|
||||
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
|
||||
expect(display.onflush).to.not.have.been.called;
|
||||
display.flush();
|
||||
expect(display.onflush).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should draw a blit image on type "blit"', function () {
|
||||
display.blitImage = sinon.spy();
|
||||
display._renderQ_push({
|
||||
type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9]
|
||||
});
|
||||
expect(display.blitImage).to.have.been.calledOnce;
|
||||
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
|
||||
});
|
||||
|
||||
it('should draw a blit RGB image on type "blitRgb"', function () {
|
||||
display.blitRgbImage = sinon.spy();
|
||||
display._renderQ_push({
|
||||
type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9]
|
||||
});
|
||||
expect(display.blitRgbImage).to.have.been.calledOnce;
|
||||
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
|
||||
});
|
||||
|
||||
it('should copy a region on type "copy"', function () {
|
||||
display.copyImage = sinon.spy();
|
||||
display._renderQ_push({
|
||||
type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8
|
||||
});
|
||||
expect(display.copyImage).to.have.been.calledOnce;
|
||||
expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
|
||||
});
|
||||
|
||||
it('should fill a rect with a given color on type "fill"', function () {
|
||||
display.fillRect = sinon.spy();
|
||||
display._renderQ_push({
|
||||
type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]
|
||||
});
|
||||
expect(display.fillRect).to.have.been.calledOnce;
|
||||
expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
|
||||
});
|
||||
|
||||
it('should draw an image from an image object on type "img" (if complete)', function () {
|
||||
display.drawImage = sinon.spy();
|
||||
display._renderQ_push({
|
||||
type: 'img', x: 3, y: 4, img: { complete: true }
|
||||
});
|
||||
expect(display.drawImage).to.have.been.calledOnce;
|
||||
expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,223 +1,225 @@
|
|||
const expect = chai.expect;
|
||||
const expect = chai.expect;
|
||||
|
||||
import keysyms from '../core/input/keysymdef.js';
|
||||
import * as KeyboardUtil from "../core/input/util.js";
|
||||
import * as KeyboardUtil from '../core/input/util.js';
|
||||
import * as browser from '../core/util/browser.js';
|
||||
|
||||
describe('Helpers', function() {
|
||||
"use strict";
|
||||
describe('Helpers', function () {
|
||||
'use strict';
|
||||
|
||||
describe('keysyms.lookup', function() {
|
||||
it('should map ASCII characters to keysyms', function() {
|
||||
expect(keysyms.lookup('a'.charCodeAt())).to.be.equal(0x61);
|
||||
expect(keysyms.lookup('A'.charCodeAt())).to.be.equal(0x41);
|
||||
});
|
||||
it('should map Latin-1 characters to keysyms', function() {
|
||||
expect(keysyms.lookup('ø'.charCodeAt())).to.be.equal(0xf8);
|
||||
describe('keysyms.lookup', function () {
|
||||
it('should map ASCII characters to keysyms', function () {
|
||||
expect(keysyms.lookup('a'.charCodeAt())).to.be.equal(0x61);
|
||||
expect(keysyms.lookup('A'.charCodeAt())).to.be.equal(0x41);
|
||||
});
|
||||
it('should map Latin-1 characters to keysyms', function () {
|
||||
expect(keysyms.lookup('ø'.charCodeAt())).to.be.equal(0xf8);
|
||||
|
||||
expect(keysyms.lookup('é'.charCodeAt())).to.be.equal(0xe9);
|
||||
});
|
||||
it('should map characters that are in Windows-1252 but not in Latin-1 to keysyms', function() {
|
||||
expect(keysyms.lookup('Š'.charCodeAt())).to.be.equal(0x01a9);
|
||||
});
|
||||
it('should map characters which aren\'t in Latin1 *or* Windows-1252 to keysyms', function() {
|
||||
expect(keysyms.lookup('ũ'.charCodeAt())).to.be.equal(0x03fd);
|
||||
});
|
||||
it('should map unknown codepoints to the Unicode range', function() {
|
||||
expect(keysyms.lookup('\n'.charCodeAt())).to.be.equal(0x100000a);
|
||||
expect(keysyms.lookup('\u262D'.charCodeAt())).to.be.equal(0x100262d);
|
||||
});
|
||||
// This requires very recent versions of most browsers... skipping for now
|
||||
it.skip('should map UCS-4 codepoints to the Unicode range', function() {
|
||||
//expect(keysyms.lookup('\u{1F686}'.codePointAt())).to.be.equal(0x101f686);
|
||||
});
|
||||
expect(keysyms.lookup('é'.charCodeAt())).to.be.equal(0xe9);
|
||||
});
|
||||
it('should map characters that are in Windows-1252 but not in Latin-1 to keysyms', function () {
|
||||
expect(keysyms.lookup('Š'.charCodeAt())).to.be.equal(0x01a9);
|
||||
});
|
||||
it('should map characters which aren\'t in Latin1 *or* Windows-1252 to keysyms', function () {
|
||||
expect(keysyms.lookup('ũ'.charCodeAt())).to.be.equal(0x03fd);
|
||||
});
|
||||
it('should map unknown codepoints to the Unicode range', function () {
|
||||
expect(keysyms.lookup('\n'.charCodeAt())).to.be.equal(0x100000a);
|
||||
expect(keysyms.lookup('\u262D'.charCodeAt())).to.be.equal(0x100262d);
|
||||
});
|
||||
// This requires very recent versions of most browsers... skipping for now
|
||||
it.skip('should map UCS-4 codepoints to the Unicode range', function () {
|
||||
// expect(keysyms.lookup('\u{1F686}'.codePointAt())).to.be.equal(0x101f686);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getKeycode', function () {
|
||||
it('should pass through proper code', function () {
|
||||
expect(KeyboardUtil.getKeycode({ code: 'Semicolon' })).to.be.equal('Semicolon');
|
||||
});
|
||||
it('should map legacy values', function () {
|
||||
expect(KeyboardUtil.getKeycode({ code: '' })).to.be.equal('Unidentified');
|
||||
expect(KeyboardUtil.getKeycode({ code: 'OSLeft' })).to.be.equal('MetaLeft');
|
||||
});
|
||||
it('should map keyCode to code when possible', function () {
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x14 })).to.be.equal('CapsLock');
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x5b })).to.be.equal('MetaLeft');
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x35 })).to.be.equal('Digit5');
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x65 })).to.be.equal('Numpad5');
|
||||
});
|
||||
it('should map keyCode left/right side', function () {
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x10, location: 1 })).to.be.equal('ShiftLeft');
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x10, location: 2 })).to.be.equal('ShiftRight');
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x11, location: 1 })).to.be.equal('ControlLeft');
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x11, location: 2 })).to.be.equal('ControlRight');
|
||||
});
|
||||
it('should map keyCode on numpad', function () {
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x0d, location: 0 })).to.be.equal('Enter');
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x0d, location: 3 })).to.be.equal('NumpadEnter');
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x23, location: 0 })).to.be.equal('End');
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x23, location: 3 })).to.be.equal('Numpad1');
|
||||
});
|
||||
it('should return Unidentified when it cannot map the keyCode', function () {
|
||||
expect(KeyboardUtil.getKeycode({ keycode: 0x42 })).to.be.equal('Unidentified');
|
||||
});
|
||||
|
||||
describe('getKeycode', function() {
|
||||
it('should pass through proper code', function() {
|
||||
expect(KeyboardUtil.getKeycode({code: 'Semicolon'})).to.be.equal('Semicolon');
|
||||
});
|
||||
it('should map legacy values', function() {
|
||||
expect(KeyboardUtil.getKeycode({code: ''})).to.be.equal('Unidentified');
|
||||
expect(KeyboardUtil.getKeycode({code: 'OSLeft'})).to.be.equal('MetaLeft');
|
||||
});
|
||||
it('should map keyCode to code when possible', function() {
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x14})).to.be.equal('CapsLock');
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x5b})).to.be.equal('MetaLeft');
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x35})).to.be.equal('Digit5');
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x65})).to.be.equal('Numpad5');
|
||||
});
|
||||
it('should map keyCode left/right side', function() {
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 1})).to.be.equal('ShiftLeft');
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 2})).to.be.equal('ShiftRight');
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 1})).to.be.equal('ControlLeft');
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 2})).to.be.equal('ControlRight');
|
||||
});
|
||||
it('should map keyCode on numpad', function() {
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 0})).to.be.equal('Enter');
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 3})).to.be.equal('NumpadEnter');
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 0})).to.be.equal('End');
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 3})).to.be.equal('Numpad1');
|
||||
});
|
||||
it('should return Unidentified when it cannot map the keyCode', function() {
|
||||
expect(KeyboardUtil.getKeycode({keycode: 0x42})).to.be.equal('Unidentified');
|
||||
});
|
||||
describe('Fix Meta on macOS', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, 'navigator');
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
|
||||
describe('Fix Meta on macOS', function() {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
Object.defineProperty(window, 'navigator', { value: {} });
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
window.navigator.platform = 'Mac x86_64';
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, 'navigator', origNavigator);
|
||||
});
|
||||
|
||||
window.navigator.platform = "Mac x86_64";
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
it('should respect ContextMenu on modern browser', function () {
|
||||
expect(KeyboardUtil.getKeycode({ code: 'ContextMenu', keyCode: 0x5d })).to.be.equal('ContextMenu');
|
||||
});
|
||||
it('should translate legacy ContextMenu to MetaRight', function () {
|
||||
expect(KeyboardUtil.getKeycode({ keyCode: 0x5d })).to.be.equal('MetaRight');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should respect ContextMenu on modern browser', function() {
|
||||
expect(KeyboardUtil.getKeycode({code: 'ContextMenu', keyCode: 0x5d})).to.be.equal('ContextMenu');
|
||||
});
|
||||
it('should translate legacy ContextMenu to MetaRight', function() {
|
||||
expect(KeyboardUtil.getKeycode({keyCode: 0x5d})).to.be.equal('MetaRight');
|
||||
});
|
||||
});
|
||||
describe('getKey', function () {
|
||||
it('should prefer key', function () {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
expect(KeyboardUtil.getKey({
|
||||
key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43
|
||||
})).to.be.equal('a');
|
||||
});
|
||||
it('should map legacy values', function () {
|
||||
expect(KeyboardUtil.getKey({ key: 'Spacebar' })).to.be.equal(' ');
|
||||
expect(KeyboardUtil.getKey({ key: 'Left' })).to.be.equal('ArrowLeft');
|
||||
expect(KeyboardUtil.getKey({ key: 'OS' })).to.be.equal('Meta');
|
||||
expect(KeyboardUtil.getKey({ key: 'Win' })).to.be.equal('Meta');
|
||||
expect(KeyboardUtil.getKey({ key: 'UIKeyInputLeftArrow' })).to.be.equal('ArrowLeft');
|
||||
});
|
||||
it('should use code if no key', function () {
|
||||
expect(KeyboardUtil.getKey({ code: 'NumpadBackspace' })).to.be.equal('Backspace');
|
||||
});
|
||||
it('should not use code fallback for character keys', function () {
|
||||
expect(KeyboardUtil.getKey({ code: 'KeyA' })).to.be.equal('Unidentified');
|
||||
expect(KeyboardUtil.getKey({ code: 'Digit1' })).to.be.equal('Unidentified');
|
||||
expect(KeyboardUtil.getKey({ code: 'Period' })).to.be.equal('Unidentified');
|
||||
expect(KeyboardUtil.getKey({ code: 'Numpad1' })).to.be.equal('Unidentified');
|
||||
});
|
||||
it('should use charCode if no key', function () {
|
||||
expect(KeyboardUtil.getKey({ charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43 })).to.be.equal('Š');
|
||||
});
|
||||
it('should return Unidentified when it cannot map the key', function () {
|
||||
expect(KeyboardUtil.getKey({ keycode: 0x42 })).to.be.equal('Unidentified');
|
||||
});
|
||||
|
||||
describe('getKey', function() {
|
||||
it('should prefer key', function() {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
expect(KeyboardUtil.getKey({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('a');
|
||||
});
|
||||
it('should map legacy values', function() {
|
||||
expect(KeyboardUtil.getKey({key: 'Spacebar'})).to.be.equal(' ');
|
||||
expect(KeyboardUtil.getKey({key: 'Left'})).to.be.equal('ArrowLeft');
|
||||
expect(KeyboardUtil.getKey({key: 'OS'})).to.be.equal('Meta');
|
||||
expect(KeyboardUtil.getKey({key: 'Win'})).to.be.equal('Meta');
|
||||
expect(KeyboardUtil.getKey({key: 'UIKeyInputLeftArrow'})).to.be.equal('ArrowLeft');
|
||||
});
|
||||
it('should use code if no key', function() {
|
||||
expect(KeyboardUtil.getKey({code: 'NumpadBackspace'})).to.be.equal('Backspace');
|
||||
});
|
||||
it('should not use code fallback for character keys', function() {
|
||||
expect(KeyboardUtil.getKey({code: 'KeyA'})).to.be.equal('Unidentified');
|
||||
expect(KeyboardUtil.getKey({code: 'Digit1'})).to.be.equal('Unidentified');
|
||||
expect(KeyboardUtil.getKey({code: 'Period'})).to.be.equal('Unidentified');
|
||||
expect(KeyboardUtil.getKey({code: 'Numpad1'})).to.be.equal('Unidentified');
|
||||
});
|
||||
it('should use charCode if no key', function() {
|
||||
expect(KeyboardUtil.getKey({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('Š');
|
||||
});
|
||||
it('should return Unidentified when it cannot map the key', function() {
|
||||
expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified');
|
||||
});
|
||||
describe('Broken key AltGraph on IE/Edge', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, 'navigator');
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
|
||||
describe('Broken key AltGraph on IE/Edge', function() {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
Object.defineProperty(window, 'navigator', { value: {} });
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, 'navigator', origNavigator);
|
||||
});
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.platform !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
it('should ignore printable character key on IE', function () {
|
||||
window.navigator.userAgent = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
|
||||
expect(KeyboardUtil.getKey({ key: 'a' })).to.be.equal('Unidentified');
|
||||
});
|
||||
it('should ignore printable character key on Edge', function () {
|
||||
window.navigator.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393';
|
||||
expect(KeyboardUtil.getKey({ key: 'a' })).to.be.equal('Unidentified');
|
||||
});
|
||||
it('should allow non-printable character key on IE', function () {
|
||||
window.navigator.userAgent = 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
|
||||
expect(KeyboardUtil.getKey({ key: 'Shift' })).to.be.equal('Shift');
|
||||
});
|
||||
it('should allow non-printable character key on Edge', function () {
|
||||
window.navigator.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393';
|
||||
expect(KeyboardUtil.getKey({ key: 'Shift' })).to.be.equal('Shift');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore printable character key on IE', function() {
|
||||
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
|
||||
expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified');
|
||||
});
|
||||
it('should ignore printable character key on Edge', function() {
|
||||
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
|
||||
expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified');
|
||||
});
|
||||
it('should allow non-printable character key on IE', function() {
|
||||
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
|
||||
expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
|
||||
});
|
||||
it('should allow non-printable character key on Edge', function() {
|
||||
window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
|
||||
expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
|
||||
});
|
||||
});
|
||||
describe('getKeysym', function () {
|
||||
describe('Non-character keys', function () {
|
||||
it('should recognize the right keys', function () {
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Enter' })).to.be.equal(0xFF0D);
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Backspace' })).to.be.equal(0xFF08);
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Tab' })).to.be.equal(0xFF09);
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Shift' })).to.be.equal(0xFFE1);
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Control' })).to.be.equal(0xFFE3);
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Alt' })).to.be.equal(0xFFE9);
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Meta' })).to.be.equal(0xFFEB);
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Escape' })).to.be.equal(0xFF1B);
|
||||
expect(KeyboardUtil.getKeysym({ key: 'ArrowUp' })).to.be.equal(0xFF52);
|
||||
});
|
||||
it('should map left/right side', function () {
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Shift', location: 1 })).to.be.equal(0xFFE1);
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Shift', location: 2 })).to.be.equal(0xFFE2);
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Control', location: 1 })).to.be.equal(0xFFE3);
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Control', location: 2 })).to.be.equal(0xFFE4);
|
||||
});
|
||||
it('should handle AltGraph', function () {
|
||||
expect(KeyboardUtil.getKeysym({ code: 'AltRight', key: 'Alt', location: 2 })).to.be.equal(0xFFEA);
|
||||
expect(KeyboardUtil.getKeysym({ code: 'AltRight', key: 'AltGraph', location: 2 })).to.be.equal(0xFE03);
|
||||
});
|
||||
it('should return null for unknown keys', function () {
|
||||
expect(KeyboardUtil.getKeysym({ key: 'Semicolon' })).to.be.null;
|
||||
expect(KeyboardUtil.getKeysym({ key: 'BracketRight' })).to.be.null;
|
||||
});
|
||||
it('should handle remappings', function () {
|
||||
expect(KeyboardUtil.getKeysym({ code: 'ControlLeft', key: 'Tab' })).to.be.equal(0xFF09);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getKeysym', function() {
|
||||
describe('Non-character keys', function() {
|
||||
it('should recognize the right keys', function() {
|
||||
expect(KeyboardUtil.getKeysym({key: 'Enter'})).to.be.equal(0xFF0D);
|
||||
expect(KeyboardUtil.getKeysym({key: 'Backspace'})).to.be.equal(0xFF08);
|
||||
expect(KeyboardUtil.getKeysym({key: 'Tab'})).to.be.equal(0xFF09);
|
||||
expect(KeyboardUtil.getKeysym({key: 'Shift'})).to.be.equal(0xFFE1);
|
||||
expect(KeyboardUtil.getKeysym({key: 'Control'})).to.be.equal(0xFFE3);
|
||||
expect(KeyboardUtil.getKeysym({key: 'Alt'})).to.be.equal(0xFFE9);
|
||||
expect(KeyboardUtil.getKeysym({key: 'Meta'})).to.be.equal(0xFFEB);
|
||||
expect(KeyboardUtil.getKeysym({key: 'Escape'})).to.be.equal(0xFF1B);
|
||||
expect(KeyboardUtil.getKeysym({key: 'ArrowUp'})).to.be.equal(0xFF52);
|
||||
});
|
||||
it('should map left/right side', function() {
|
||||
expect(KeyboardUtil.getKeysym({key: 'Shift', location: 1})).to.be.equal(0xFFE1);
|
||||
expect(KeyboardUtil.getKeysym({key: 'Shift', location: 2})).to.be.equal(0xFFE2);
|
||||
expect(KeyboardUtil.getKeysym({key: 'Control', location: 1})).to.be.equal(0xFFE3);
|
||||
expect(KeyboardUtil.getKeysym({key: 'Control', location: 2})).to.be.equal(0xFFE4);
|
||||
});
|
||||
it('should handle AltGraph', function() {
|
||||
expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'Alt', location: 2})).to.be.equal(0xFFEA);
|
||||
expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltGraph', location: 2})).to.be.equal(0xFE03);
|
||||
});
|
||||
it('should return null for unknown keys', function() {
|
||||
expect(KeyboardUtil.getKeysym({key: 'Semicolon'})).to.be.null;
|
||||
expect(KeyboardUtil.getKeysym({key: 'BracketRight'})).to.be.null;
|
||||
});
|
||||
it('should handle remappings', function() {
|
||||
expect(KeyboardUtil.getKeysym({code: 'ControlLeft', key: 'Tab'})).to.be.equal(0xFF09);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Numpad', function() {
|
||||
it('should handle Numpad numbers', function() {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
expect(KeyboardUtil.getKeysym({code: 'Digit5', key: '5', location: 0})).to.be.equal(0x0035);
|
||||
expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5);
|
||||
});
|
||||
it('should handle Numpad non-character keys', function() {
|
||||
expect(KeyboardUtil.getKeysym({code: 'Home', key: 'Home', location: 0})).to.be.equal(0xFF50);
|
||||
expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: 'Home', location: 3})).to.be.equal(0xFF95);
|
||||
expect(KeyboardUtil.getKeysym({code: 'Delete', key: 'Delete', location: 0})).to.be.equal(0xFFFF);
|
||||
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F);
|
||||
});
|
||||
it('should handle Numpad Decimal key', function() {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE);
|
||||
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC);
|
||||
});
|
||||
});
|
||||
describe('Numpad', function () {
|
||||
it('should handle Numpad numbers', function () {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
expect(KeyboardUtil.getKeysym({ code: 'Digit5', key: '5', location: 0 })).to.be.equal(0x0035);
|
||||
expect(KeyboardUtil.getKeysym({ code: 'Numpad5', key: '5', location: 3 })).to.be.equal(0xFFB5);
|
||||
});
|
||||
it('should handle Numpad non-character keys', function () {
|
||||
expect(KeyboardUtil.getKeysym({ code: 'Home', key: 'Home', location: 0 })).to.be.equal(0xFF50);
|
||||
expect(KeyboardUtil.getKeysym({ code: 'Numpad5', key: 'Home', location: 3 })).to.be.equal(0xFF95);
|
||||
expect(KeyboardUtil.getKeysym({ code: 'Delete', key: 'Delete', location: 0 })).to.be.equal(0xFFFF);
|
||||
expect(KeyboardUtil.getKeysym({ code: 'NumpadDecimal', key: 'Delete', location: 3 })).to.be.equal(0xFF9F);
|
||||
});
|
||||
it('should handle Numpad Decimal key', function () {
|
||||
if (browser.isIE() || browser.isEdge()) this.skip();
|
||||
expect(KeyboardUtil.getKeysym({ code: 'NumpadDecimal', key: '.', location: 3 })).to.be.equal(0xFFAE);
|
||||
expect(KeyboardUtil.getKeysym({ code: 'NumpadDecimal', key: ',', location: 3 })).to.be.equal(0xFFAC);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,72 +1,72 @@
|
|||
const expect = chai.expect;
|
||||
import { l10n } from '../app/localization.js';
|
||||
|
||||
describe('Localization', function() {
|
||||
"use strict";
|
||||
describe('Localization', function () {
|
||||
'use strict';
|
||||
|
||||
describe('language selection', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
describe('language selection', function () {
|
||||
let origNavigator;
|
||||
beforeEach(function () {
|
||||
// window.navigator is a protected read-only property in many
|
||||
// environments, so we need to redefine it whilst running these
|
||||
// tests.
|
||||
origNavigator = Object.getOwnPropertyDescriptor(window, 'navigator');
|
||||
if (origNavigator === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
|
||||
Object.defineProperty(window, "navigator", {value: {}});
|
||||
if (window.navigator.languages !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
Object.defineProperty(window, 'navigator', { value: {} });
|
||||
if (window.navigator.languages !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
window.navigator.languages = [];
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, "navigator", origNavigator);
|
||||
});
|
||||
|
||||
it('should use English by default', function() {
|
||||
expect(l10n.language).to.equal('en');
|
||||
});
|
||||
it('should use English if no user language matches', function() {
|
||||
window.navigator.languages = ["nl", "de"];
|
||||
l10n.setup(["es", "fr"]);
|
||||
expect(l10n.language).to.equal('en');
|
||||
});
|
||||
it('should use the most preferred user language', function() {
|
||||
window.navigator.languages = ["nl", "de", "fr"];
|
||||
l10n.setup(["es", "fr", "de"]);
|
||||
expect(l10n.language).to.equal('de');
|
||||
});
|
||||
it('should prefer sub-languages languages', function() {
|
||||
window.navigator.languages = ["pt-BR"];
|
||||
l10n.setup(["pt", "pt-BR"]);
|
||||
expect(l10n.language).to.equal('pt-BR');
|
||||
});
|
||||
it('should fall back to language "parents"', function() {
|
||||
window.navigator.languages = ["pt-BR"];
|
||||
l10n.setup(["fr", "pt", "de"]);
|
||||
expect(l10n.language).to.equal('pt');
|
||||
});
|
||||
it('should not use specific language when user asks for a generic language', function() {
|
||||
window.navigator.languages = ["pt", "de"];
|
||||
l10n.setup(["fr", "pt-BR", "de"]);
|
||||
expect(l10n.language).to.equal('de');
|
||||
});
|
||||
it('should handle underscore as a separator', function() {
|
||||
window.navigator.languages = ["pt-BR"];
|
||||
l10n.setup(["pt_BR"]);
|
||||
expect(l10n.language).to.equal('pt_BR');
|
||||
});
|
||||
it('should handle difference in case', function() {
|
||||
window.navigator.languages = ["pt-br"];
|
||||
l10n.setup(["pt-BR"]);
|
||||
expect(l10n.language).to.equal('pt-BR');
|
||||
});
|
||||
window.navigator.languages = [];
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, 'navigator', origNavigator);
|
||||
});
|
||||
|
||||
it('should use English by default', function () {
|
||||
expect(l10n.language).to.equal('en');
|
||||
});
|
||||
it('should use English if no user language matches', function () {
|
||||
window.navigator.languages = ['nl', 'de'];
|
||||
l10n.setup(['es', 'fr']);
|
||||
expect(l10n.language).to.equal('en');
|
||||
});
|
||||
it('should use the most preferred user language', function () {
|
||||
window.navigator.languages = ['nl', 'de', 'fr'];
|
||||
l10n.setup(['es', 'fr', 'de']);
|
||||
expect(l10n.language).to.equal('de');
|
||||
});
|
||||
it('should prefer sub-languages languages', function () {
|
||||
window.navigator.languages = ['pt-BR'];
|
||||
l10n.setup(['pt', 'pt-BR']);
|
||||
expect(l10n.language).to.equal('pt-BR');
|
||||
});
|
||||
it('should fall back to language "parents"', function () {
|
||||
window.navigator.languages = ['pt-BR'];
|
||||
l10n.setup(['fr', 'pt', 'de']);
|
||||
expect(l10n.language).to.equal('pt');
|
||||
});
|
||||
it('should not use specific language when user asks for a generic language', function () {
|
||||
window.navigator.languages = ['pt', 'de'];
|
||||
l10n.setup(['fr', 'pt-BR', 'de']);
|
||||
expect(l10n.language).to.equal('de');
|
||||
});
|
||||
it('should handle underscore as a separator', function () {
|
||||
window.navigator.languages = ['pt-BR'];
|
||||
l10n.setup(['pt_BR']);
|
||||
expect(l10n.language).to.equal('pt_BR');
|
||||
});
|
||||
it('should handle difference in case', function () {
|
||||
window.navigator.languages = ['pt-br'];
|
||||
l10n.setup(['pt-BR']);
|
||||
expect(l10n.language).to.equal('pt-BR');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,293 +5,371 @@ import sinon from '../vendor/sinon.js';
|
|||
import Mouse from '../core/input/mouse.js';
|
||||
import * as eventUtils from '../core/util/events.js';
|
||||
|
||||
describe('Mouse Event Handling', function() {
|
||||
"use strict";
|
||||
describe('Mouse Event Handling', function () {
|
||||
'use strict';
|
||||
|
||||
sinon.stub(eventUtils, 'setCapture');
|
||||
// This function is only used on target (the canvas)
|
||||
// and for these tests we can assume that the canvas is 100x100
|
||||
// located at coordinates 10x10
|
||||
sinon.stub(Element.prototype, 'getBoundingClientRect').returns(
|
||||
{left: 10, right: 110, top: 10, bottom: 110, width: 100, height: 100});
|
||||
const target = document.createElement('canvas');
|
||||
sinon.stub(eventUtils, 'setCapture');
|
||||
// This function is only used on target (the canvas)
|
||||
// and for these tests we can assume that the canvas is 100x100
|
||||
// located at coordinates 10x10
|
||||
sinon.stub(Element.prototype, 'getBoundingClientRect').returns(
|
||||
{
|
||||
left: 10, right: 110, top: 10, bottom: 110, width: 100, height: 100
|
||||
}
|
||||
);
|
||||
const target = document.createElement('canvas');
|
||||
|
||||
// The real constructors might not work everywhere we
|
||||
// want to run these tests
|
||||
const mouseevent = (typeArg, MouseEventInit) => {
|
||||
const e = { type: typeArg };
|
||||
for (let key in MouseEventInit) {
|
||||
e[key] = MouseEventInit[key];
|
||||
// The real constructors might not work everywhere we
|
||||
// want to run these tests
|
||||
const mouseevent = (typeArg, MouseEventInit) => {
|
||||
const e = { type: typeArg };
|
||||
for (let key in MouseEventInit) {
|
||||
e[key] = MouseEventInit[key];
|
||||
}
|
||||
e.stopPropagation = sinon.spy();
|
||||
e.preventDefault = sinon.spy();
|
||||
return e;
|
||||
};
|
||||
const touchevent = mouseevent;
|
||||
|
||||
describe('Decode Mouse Events', function () {
|
||||
it('should decode mousedown events', function (done) {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
expect(bmask).to.be.equal(0x01);
|
||||
expect(down).to.be.equal(1);
|
||||
done();
|
||||
};
|
||||
mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' }));
|
||||
});
|
||||
it('should decode mouseup events', function (done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
expect(bmask).to.be.equal(0x01);
|
||||
if (calls++ === 1) {
|
||||
expect(down).to.not.be.equal(1);
|
||||
done();
|
||||
}
|
||||
e.stopPropagation = sinon.spy();
|
||||
e.preventDefault = sinon.spy();
|
||||
return e;
|
||||
};
|
||||
const touchevent = mouseevent;
|
||||
};
|
||||
mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' }));
|
||||
mouse._handleMouseUp(mouseevent('mouseup', { button: '0x01' }));
|
||||
});
|
||||
it('should decode mousemove events', function (done) {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousemove = (x, y) => {
|
||||
// Note that target relative coordinates are sent
|
||||
expect(x).to.be.equal(40);
|
||||
expect(y).to.be.equal(10);
|
||||
done();
|
||||
};
|
||||
mouse._handleMouseMove(mouseevent('mousemove',
|
||||
{ clientX: 50, clientY: 20 }));
|
||||
});
|
||||
it('should decode mousewheel events', function (done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
expect(bmask).to.be.equal(1 << 6);
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
} else if (calls === 2) {
|
||||
expect(down).to.not.be.equal(1);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseWheel(mouseevent('mousewheel',
|
||||
{
|
||||
deltaX: 50,
|
||||
deltaY: 0,
|
||||
deltaMode: 0
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Decode Mouse Events', function() {
|
||||
it('should decode mousedown events', function(done) {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
expect(bmask).to.be.equal(0x01);
|
||||
expect(down).to.be.equal(1);
|
||||
done();
|
||||
};
|
||||
mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' }));
|
||||
});
|
||||
it('should decode mouseup events', function(done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
expect(bmask).to.be.equal(0x01);
|
||||
if (calls++ === 1) {
|
||||
expect(down).to.not.be.equal(1);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' }));
|
||||
mouse._handleMouseUp(mouseevent('mouseup', { button: '0x01' }));
|
||||
});
|
||||
it('should decode mousemove events', function(done) {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousemove = (x, y) => {
|
||||
// Note that target relative coordinates are sent
|
||||
expect(x).to.be.equal(40);
|
||||
expect(y).to.be.equal(10);
|
||||
done();
|
||||
};
|
||||
mouse._handleMouseMove(mouseevent('mousemove',
|
||||
{ clientX: 50, clientY: 20 }));
|
||||
});
|
||||
it('should decode mousewheel events', function(done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
expect(bmask).to.be.equal(1<<6);
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
} else if (calls === 2) {
|
||||
expect(down).to.not.be.equal(1);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseWheel(mouseevent('mousewheel',
|
||||
{ deltaX: 50, deltaY: 0,
|
||||
deltaMode: 0}));
|
||||
});
|
||||
describe('Double-click for Touch', function () {
|
||||
beforeEach(function () { this.clock = sinon.useFakeTimers(); });
|
||||
afterEach(function () { this.clock.restore(); });
|
||||
|
||||
it('should use same pos for 2nd tap if close enough', function (done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
} else if (calls === 3) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
done();
|
||||
}
|
||||
};
|
||||
// touch events are sent in an array of events
|
||||
// with one item for each touch point
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 78, clientY: 46 }] }
|
||||
));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 79, clientY: 45 }] }
|
||||
));
|
||||
this.clock.tick(200);
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 67, clientY: 35 }] }
|
||||
));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 66, clientY: 36 }] }
|
||||
));
|
||||
});
|
||||
|
||||
describe('Double-click for Touch', function() {
|
||||
|
||||
beforeEach(function () { this.clock = sinon.useFakeTimers(); });
|
||||
afterEach(function () { this.clock.restore(); });
|
||||
|
||||
it('should use same pos for 2nd tap if close enough', function(done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
} else if (calls === 3) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
done();
|
||||
}
|
||||
};
|
||||
// touch events are sent in an array of events
|
||||
// with one item for each touch point
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
|
||||
this.clock.tick(200);
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 67, clientY: 35 }]}));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 66, clientY: 36 }]}));
|
||||
});
|
||||
|
||||
it('should not modify 2nd tap pos if far apart', function(done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
} else if (calls === 3) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.not.be.equal(68);
|
||||
expect(y).to.not.be.equal(36);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
|
||||
this.clock.tick(200);
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 57, clientY: 35 }]}));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 56, clientY: 36 }]}));
|
||||
});
|
||||
|
||||
it('should not modify 2nd tap pos if not soon enough', function(done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
} else if (calls === 3) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.not.be.equal(68);
|
||||
expect(y).to.not.be.equal(36);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 78, clientY: 46 }]}));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 79, clientY: 45 }]}));
|
||||
this.clock.tick(500);
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 67, clientY: 35 }]}));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 66, clientY: 36 }]}));
|
||||
});
|
||||
|
||||
it('should not modify 2nd tap pos if not touch', function(done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
} else if (calls === 3) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.not.be.equal(68);
|
||||
expect(y).to.not.be.equal(36);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseDown(mouseevent(
|
||||
'mousedown', { button: '0x01', clientX: 78, clientY: 46 }));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(mouseevent(
|
||||
'mouseup', { button: '0x01', clientX: 79, clientY: 45 }));
|
||||
this.clock.tick(200);
|
||||
mouse._handleMouseDown(mouseevent(
|
||||
'mousedown', { button: '0x01', clientX: 67, clientY: 35 }));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(mouseevent(
|
||||
'mouseup', { button: '0x01', clientX: 66, clientY: 36 }));
|
||||
});
|
||||
|
||||
it('should not modify 2nd tap pos if far apart', function (done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
} else if (calls === 3) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.not.be.equal(68);
|
||||
expect(y).to.not.be.equal(36);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 78, clientY: 46 }] }
|
||||
));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 79, clientY: 45 }] }
|
||||
));
|
||||
this.clock.tick(200);
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 57, clientY: 35 }] }
|
||||
));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 56, clientY: 36 }] }
|
||||
));
|
||||
});
|
||||
|
||||
describe('Accumulate mouse wheel events with small delta', function() {
|
||||
|
||||
beforeEach(function () { this.clock = sinon.useFakeTimers(); });
|
||||
afterEach(function () { this.clock.restore(); });
|
||||
|
||||
it('should accumulate wheel events if small enough', function () {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = sinon.spy();
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 4, deltaY: 0, deltaMode: 0 }));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 4, deltaY: 0, deltaMode: 0 }));
|
||||
|
||||
// threshold is 10
|
||||
expect(mouse._accumulatedWheelDeltaX).to.be.equal(8);
|
||||
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 4, deltaY: 0, deltaMode: 0 }));
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up
|
||||
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 4, deltaY: 9, deltaMode: 0 }));
|
||||
|
||||
expect(mouse._accumulatedWheelDeltaX).to.be.equal(4);
|
||||
expect(mouse._accumulatedWheelDeltaY).to.be.equal(9);
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(2); // still
|
||||
});
|
||||
|
||||
it('should not accumulate large wheel events', function () {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = sinon.spy();
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 11, deltaY: 0, deltaMode: 0 }));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 0, deltaY: 70, deltaMode: 0 }));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 400, deltaY: 400, deltaMode: 0 }));
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(8); // mouse down and up
|
||||
});
|
||||
|
||||
it('should send even small wheel events after a timeout', function () {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = sinon.spy();
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 1, deltaY: 0, deltaMode: 0 }));
|
||||
this.clock.tick(51); // timeout on 50 ms
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up
|
||||
});
|
||||
|
||||
it('should account for non-zero deltaMode', function () {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = sinon.spy();
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 0, deltaY: 2, deltaMode: 1 }));
|
||||
|
||||
this.clock.tick(10);
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', { clientX: 18, clientY: 40,
|
||||
deltaX: 1, deltaY: 0, deltaMode: 2 }));
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(4); // mouse down and up
|
||||
});
|
||||
it('should not modify 2nd tap pos if not soon enough', function (done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
} else if (calls === 3) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.not.be.equal(68);
|
||||
expect(y).to.not.be.equal(36);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 78, clientY: 46 }] }
|
||||
));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 79, clientY: 45 }] }
|
||||
));
|
||||
this.clock.tick(500);
|
||||
mouse._handleMouseDown(touchevent(
|
||||
'touchstart', { touches: [{ clientX: 67, clientY: 35 }] }
|
||||
));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(touchevent(
|
||||
'touchend', { touches: [{ clientX: 66, clientY: 36 }] }
|
||||
));
|
||||
});
|
||||
|
||||
it('should not modify 2nd tap pos if not touch', function (done) {
|
||||
let calls = 0;
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = (x, y, down, bmask) => {
|
||||
calls++;
|
||||
if (calls === 1) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.be.equal(68);
|
||||
expect(y).to.be.equal(36);
|
||||
} else if (calls === 3) {
|
||||
expect(down).to.be.equal(1);
|
||||
expect(x).to.not.be.equal(68);
|
||||
expect(y).to.not.be.equal(36);
|
||||
done();
|
||||
}
|
||||
};
|
||||
mouse._handleMouseDown(mouseevent(
|
||||
'mousedown', { button: '0x01', clientX: 78, clientY: 46 }
|
||||
));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(mouseevent(
|
||||
'mouseup', { button: '0x01', clientX: 79, clientY: 45 }
|
||||
));
|
||||
this.clock.tick(200);
|
||||
mouse._handleMouseDown(mouseevent(
|
||||
'mousedown', { button: '0x01', clientX: 67, clientY: 35 }
|
||||
));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseUp(mouseevent(
|
||||
'mouseup', { button: '0x01', clientX: 66, clientY: 36 }
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accumulate mouse wheel events with small delta', function () {
|
||||
beforeEach(function () { this.clock = sinon.useFakeTimers(); });
|
||||
afterEach(function () { this.clock.restore(); });
|
||||
|
||||
it('should accumulate wheel events if small enough', function () {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = sinon.spy();
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', {
|
||||
clientX: 18,
|
||||
clientY: 40,
|
||||
deltaX: 4,
|
||||
deltaY: 0,
|
||||
deltaMode: 0
|
||||
}
|
||||
));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', {
|
||||
clientX: 18,
|
||||
clientY: 40,
|
||||
deltaX: 4,
|
||||
deltaY: 0,
|
||||
deltaMode: 0
|
||||
}
|
||||
));
|
||||
|
||||
// threshold is 10
|
||||
expect(mouse._accumulatedWheelDeltaX).to.be.equal(8);
|
||||
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', {
|
||||
clientX: 18,
|
||||
clientY: 40,
|
||||
deltaX: 4,
|
||||
deltaY: 0,
|
||||
deltaMode: 0
|
||||
}
|
||||
));
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up
|
||||
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', {
|
||||
clientX: 18,
|
||||
clientY: 40,
|
||||
deltaX: 4,
|
||||
deltaY: 9,
|
||||
deltaMode: 0
|
||||
}
|
||||
));
|
||||
|
||||
expect(mouse._accumulatedWheelDeltaX).to.be.equal(4);
|
||||
expect(mouse._accumulatedWheelDeltaY).to.be.equal(9);
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(2); // still
|
||||
});
|
||||
|
||||
it('should not accumulate large wheel events', function () {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = sinon.spy();
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', {
|
||||
clientX: 18,
|
||||
clientY: 40,
|
||||
deltaX: 11,
|
||||
deltaY: 0,
|
||||
deltaMode: 0
|
||||
}
|
||||
));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', {
|
||||
clientX: 18,
|
||||
clientY: 40,
|
||||
deltaX: 0,
|
||||
deltaY: 70,
|
||||
deltaMode: 0
|
||||
}
|
||||
));
|
||||
this.clock.tick(10);
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', {
|
||||
clientX: 18,
|
||||
clientY: 40,
|
||||
deltaX: 400,
|
||||
deltaY: 400,
|
||||
deltaMode: 0
|
||||
}
|
||||
));
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(8); // mouse down and up
|
||||
});
|
||||
|
||||
it('should send even small wheel events after a timeout', function () {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = sinon.spy();
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', {
|
||||
clientX: 18,
|
||||
clientY: 40,
|
||||
deltaX: 1,
|
||||
deltaY: 0,
|
||||
deltaMode: 0
|
||||
}
|
||||
));
|
||||
this.clock.tick(51); // timeout on 50 ms
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up
|
||||
});
|
||||
|
||||
it('should account for non-zero deltaMode', function () {
|
||||
const mouse = new Mouse(target);
|
||||
mouse.onmousebutton = sinon.spy();
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', {
|
||||
clientX: 18,
|
||||
clientY: 40,
|
||||
deltaX: 0,
|
||||
deltaY: 2,
|
||||
deltaMode: 1
|
||||
}
|
||||
));
|
||||
|
||||
this.clock.tick(10);
|
||||
|
||||
mouse._handleMouseWheel(mouseevent(
|
||||
'mousewheel', {
|
||||
clientX: 18,
|
||||
clientY: 40,
|
||||
deltaX: 1,
|
||||
deltaY: 0,
|
||||
deltaMode: 2
|
||||
}
|
||||
));
|
||||
|
||||
expect(mouse.onmousebutton).to.have.callCount(4); // mouse down and up
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
4371
tests/test.rfb.js
4371
tests/test.rfb.js
File diff suppressed because it is too large
Load Diff
|
|
@ -5,67 +5,67 @@ import * as Log from '../core/util/logging.js';
|
|||
|
||||
import sinon from '../vendor/sinon.js';
|
||||
|
||||
describe('Utils', function() {
|
||||
"use strict";
|
||||
describe('Utils', function () {
|
||||
'use strict';
|
||||
|
||||
describe('logging functions', function () {
|
||||
beforeEach(function () {
|
||||
sinon.spy(console, 'log');
|
||||
sinon.spy(console, 'debug');
|
||||
sinon.spy(console, 'warn');
|
||||
sinon.spy(console, 'error');
|
||||
sinon.spy(console, 'info');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
console.log.restore();
|
||||
console.debug.restore();
|
||||
console.warn.restore();
|
||||
console.error.restore();
|
||||
console.info.restore();
|
||||
Log.init_logging();
|
||||
});
|
||||
|
||||
it('should use noop for levels lower than the min level', function () {
|
||||
Log.init_logging('warn');
|
||||
Log.Debug('hi');
|
||||
Log.Info('hello');
|
||||
expect(console.log).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should use console.debug for Debug', function () {
|
||||
Log.init_logging('debug');
|
||||
Log.Debug('dbg');
|
||||
expect(console.debug).to.have.been.calledWith('dbg');
|
||||
});
|
||||
|
||||
it('should use console.info for Info', function () {
|
||||
Log.init_logging('debug');
|
||||
Log.Info('inf');
|
||||
expect(console.info).to.have.been.calledWith('inf');
|
||||
});
|
||||
|
||||
it('should use console.warn for Warn', function () {
|
||||
Log.init_logging('warn');
|
||||
Log.Warn('wrn');
|
||||
expect(console.warn).to.have.been.called;
|
||||
expect(console.warn).to.have.been.calledWith('wrn');
|
||||
});
|
||||
|
||||
it('should use console.error for Error', function () {
|
||||
Log.init_logging('error');
|
||||
Log.Error('err');
|
||||
expect(console.error).to.have.been.called;
|
||||
expect(console.error).to.have.been.calledWith('err');
|
||||
});
|
||||
describe('logging functions', function () {
|
||||
beforeEach(function () {
|
||||
sinon.spy(console, 'log');
|
||||
sinon.spy(console, 'debug');
|
||||
sinon.spy(console, 'warn');
|
||||
sinon.spy(console, 'error');
|
||||
sinon.spy(console, 'info');
|
||||
});
|
||||
|
||||
// TODO(directxman12): test the conf_default and conf_defaults methods
|
||||
// TODO(directxman12): test decodeUTF8
|
||||
// TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent)
|
||||
// TODO(directxman12): figure out a good way to test getPosition and getEventPosition
|
||||
// TODO(directxman12): figure out how to test the browser detection functions properly
|
||||
// (we can't really test them against the browsers, except for Gecko
|
||||
// via PhantomJS, the default test driver)
|
||||
afterEach(function () {
|
||||
console.log.restore();
|
||||
console.debug.restore();
|
||||
console.warn.restore();
|
||||
console.error.restore();
|
||||
console.info.restore();
|
||||
Log.init_logging();
|
||||
});
|
||||
|
||||
it('should use noop for levels lower than the min level', function () {
|
||||
Log.init_logging('warn');
|
||||
Log.Debug('hi');
|
||||
Log.Info('hello');
|
||||
expect(console.log).to.not.have.been.called;
|
||||
});
|
||||
|
||||
it('should use console.debug for Debug', function () {
|
||||
Log.init_logging('debug');
|
||||
Log.Debug('dbg');
|
||||
expect(console.debug).to.have.been.calledWith('dbg');
|
||||
});
|
||||
|
||||
it('should use console.info for Info', function () {
|
||||
Log.init_logging('debug');
|
||||
Log.Info('inf');
|
||||
expect(console.info).to.have.been.calledWith('inf');
|
||||
});
|
||||
|
||||
it('should use console.warn for Warn', function () {
|
||||
Log.init_logging('warn');
|
||||
Log.Warn('wrn');
|
||||
expect(console.warn).to.have.been.called;
|
||||
expect(console.warn).to.have.been.calledWith('wrn');
|
||||
});
|
||||
|
||||
it('should use console.error for Error', function () {
|
||||
Log.init_logging('error');
|
||||
Log.Error('err');
|
||||
expect(console.error).to.have.been.called;
|
||||
expect(console.error).to.have.been.calledWith('err');
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(directxman12): test the conf_default and conf_defaults methods
|
||||
// TODO(directxman12): test decodeUTF8
|
||||
// TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent)
|
||||
// TODO(directxman12): figure out a good way to test getPosition and getEventPosition
|
||||
// TODO(directxman12): figure out how to test the browser detection functions properly
|
||||
// (we can't really test them against the browsers, except for Gecko
|
||||
// via PhantomJS, the default test driver)
|
||||
});
|
||||
/* eslint-enable no-console */
|
||||
|
|
|
|||
|
|
@ -5,439 +5,441 @@ import FakeWebSocket from './fake.websocket.js';
|
|||
|
||||
import sinon from '../vendor/sinon.js';
|
||||
|
||||
describe('Websock', function() {
|
||||
"use strict";
|
||||
describe('Websock', function () {
|
||||
'use strict';
|
||||
|
||||
describe('Queue methods', function () {
|
||||
let sock;
|
||||
const RQ_TEMPLATE = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
describe('Queue methods', function () {
|
||||
let sock;
|
||||
const RQ_TEMPLATE = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
|
||||
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
// skip init
|
||||
sock._allocate_buffers();
|
||||
sock._rQ.set(RQ_TEMPLATE);
|
||||
sock._rQlen = RQ_TEMPLATE.length;
|
||||
});
|
||||
describe('rQlen', function () {
|
||||
it('should return the length of the receive queue', function () {
|
||||
sock.set_rQi(0);
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
// skip init
|
||||
sock._allocate_buffers();
|
||||
sock._rQ.set(RQ_TEMPLATE);
|
||||
sock._rQlen = RQ_TEMPLATE.length;
|
||||
});
|
||||
describe('rQlen', function () {
|
||||
it('should return the length of the receive queue', function () {
|
||||
sock.set_rQi(0);
|
||||
|
||||
expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length);
|
||||
});
|
||||
expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length);
|
||||
});
|
||||
|
||||
it("should return the proper length if we read some from the receive queue", function () {
|
||||
sock.set_rQi(1);
|
||||
it('should return the proper length if we read some from the receive queue', function () {
|
||||
sock.set_rQi(1);
|
||||
|
||||
expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length - 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQpeek8', function () {
|
||||
it('should peek at the next byte without poping it off the queue', function () {
|
||||
const bef_len = sock.rQlen();
|
||||
const peek = sock.rQpeek8();
|
||||
expect(sock.rQpeek8()).to.equal(peek);
|
||||
expect(sock.rQlen()).to.equal(bef_len);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshift8', function () {
|
||||
it('should pop a single byte from the receive queue', function () {
|
||||
const peek = sock.rQpeek8();
|
||||
const bef_len = sock.rQlen();
|
||||
expect(sock.rQshift8()).to.equal(peek);
|
||||
expect(sock.rQlen()).to.equal(bef_len - 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshift16', function () {
|
||||
it('should pop two bytes from the receive queue and return a single number', function () {
|
||||
const bef_len = sock.rQlen();
|
||||
const expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1];
|
||||
expect(sock.rQshift16()).to.equal(expected);
|
||||
expect(sock.rQlen()).to.equal(bef_len - 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshift32', function () {
|
||||
it('should pop four bytes from the receive queue and return a single number', function () {
|
||||
const bef_len = sock.rQlen();
|
||||
const expected = (RQ_TEMPLATE[0] << 24) +
|
||||
(RQ_TEMPLATE[1] << 16) +
|
||||
(RQ_TEMPLATE[2] << 8) +
|
||||
RQ_TEMPLATE[3];
|
||||
expect(sock.rQshift32()).to.equal(expected);
|
||||
expect(sock.rQlen()).to.equal(bef_len - 4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshiftStr', function () {
|
||||
it('should shift the given number of bytes off of the receive queue and return a string', function () {
|
||||
const bef_len = sock.rQlen();
|
||||
const bef_rQi = sock.get_rQi();
|
||||
const shifted = sock.rQshiftStr(3);
|
||||
expect(shifted).to.be.a('string');
|
||||
expect(shifted).to.equal(String.fromCharCode.apply(null, Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3))));
|
||||
expect(sock.rQlen()).to.equal(bef_len - 3);
|
||||
});
|
||||
|
||||
it('should shift the entire rest of the queue off if no length is given', function () {
|
||||
sock.rQshiftStr();
|
||||
expect(sock.rQlen()).to.equal(0);
|
||||
});
|
||||
|
||||
it('should be able to handle very large strings', function () {
|
||||
const BIG_LEN = 500000;
|
||||
const RQ_BIG = new Uint8Array(BIG_LEN);
|
||||
let expected = "";
|
||||
let letterCode = 'a'.charCodeAt(0);
|
||||
for (let i = 0; i < BIG_LEN; i++) {
|
||||
RQ_BIG[i] = letterCode;
|
||||
expected += String.fromCharCode(letterCode);
|
||||
|
||||
if (letterCode < 'z'.charCodeAt(0)) {
|
||||
letterCode++;
|
||||
} else {
|
||||
letterCode = 'a'.charCodeAt(0);
|
||||
}
|
||||
}
|
||||
sock._rQ.set(RQ_BIG);
|
||||
sock._rQlen = RQ_BIG.length;
|
||||
|
||||
const shifted = sock.rQshiftStr();
|
||||
|
||||
expect(shifted).to.be.equal(expected);
|
||||
expect(sock.rQlen()).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshiftBytes', function () {
|
||||
it('should shift the given number of bytes of the receive queue and return an array', function () {
|
||||
const bef_len = sock.rQlen();
|
||||
const bef_rQi = sock.get_rQi();
|
||||
const shifted = sock.rQshiftBytes(3);
|
||||
expect(shifted).to.be.an.instanceof(Uint8Array);
|
||||
expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3));
|
||||
expect(sock.rQlen()).to.equal(bef_len - 3);
|
||||
});
|
||||
|
||||
it('should shift the entire rest of the queue off if no length is given', function () {
|
||||
sock.rQshiftBytes();
|
||||
expect(sock.rQlen()).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQslice', function () {
|
||||
beforeEach(function () {
|
||||
sock.set_rQi(0);
|
||||
});
|
||||
|
||||
it('should not modify the receive queue', function () {
|
||||
const bef_len = sock.rQlen();
|
||||
sock.rQslice(0, 2);
|
||||
expect(sock.rQlen()).to.equal(bef_len);
|
||||
});
|
||||
|
||||
it('should return an array containing the given slice of the receive queue', function () {
|
||||
const sl = sock.rQslice(0, 2);
|
||||
expect(sl).to.be.an.instanceof(Uint8Array);
|
||||
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 0, 2));
|
||||
});
|
||||
|
||||
it('should use the rest of the receive queue if no end is given', function () {
|
||||
const sl = sock.rQslice(1);
|
||||
expect(sl).to.have.length(RQ_TEMPLATE.length - 1);
|
||||
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1));
|
||||
});
|
||||
|
||||
it('should take the current rQi in to account', function () {
|
||||
sock.set_rQi(1);
|
||||
expect(sock.rQslice(0, 2)).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1, 2));
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQwait', function () {
|
||||
beforeEach(function () {
|
||||
sock.set_rQi(0);
|
||||
});
|
||||
|
||||
it('should return true if there are not enough bytes in the receive queue', function () {
|
||||
expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true;
|
||||
});
|
||||
|
||||
it('should return false if there are enough bytes in the receive queue', function () {
|
||||
expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false;
|
||||
});
|
||||
|
||||
it('should return true and reduce rQi by "goback" if there are not enough bytes', function () {
|
||||
sock.set_rQi(5);
|
||||
expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true;
|
||||
expect(sock.get_rQi()).to.equal(1);
|
||||
});
|
||||
|
||||
it('should raise an error if we try to go back more than possible', function () {
|
||||
sock.set_rQi(5);
|
||||
expect(() => sock.rQwait('hi', RQ_TEMPLATE.length, 6)).to.throw(Error);
|
||||
});
|
||||
|
||||
it('should not reduce rQi if there are enough bytes', function () {
|
||||
sock.set_rQi(5);
|
||||
sock.rQwait('hi', 1, 6);
|
||||
expect(sock.get_rQi()).to.equal(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('flush', function () {
|
||||
beforeEach(function () {
|
||||
sock._websocket = {
|
||||
send: sinon.spy()
|
||||
};
|
||||
});
|
||||
|
||||
it('should actually send on the websocket', function () {
|
||||
sock._websocket.bufferedAmount = 8;
|
||||
sock._websocket.readyState = WebSocket.OPEN
|
||||
sock._sQ = new Uint8Array([1, 2, 3]);
|
||||
sock._sQlen = 3;
|
||||
const encoded = sock._encode_message();
|
||||
|
||||
sock.flush();
|
||||
expect(sock._websocket.send).to.have.been.calledOnce;
|
||||
expect(sock._websocket.send).to.have.been.calledWith(encoded);
|
||||
});
|
||||
|
||||
it('should not call send if we do not have anything queued up', function () {
|
||||
sock._sQlen = 0;
|
||||
sock._websocket.bufferedAmount = 8;
|
||||
|
||||
sock.flush();
|
||||
|
||||
expect(sock._websocket.send).not.to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('send', function () {
|
||||
beforeEach(function () {
|
||||
sock.flush = sinon.spy();
|
||||
});
|
||||
|
||||
it('should add to the send queue', function () {
|
||||
sock.send([1, 2, 3]);
|
||||
const sq = sock.get_sQ();
|
||||
expect(new Uint8Array(sq.buffer, sock._sQlen - 3, 3)).to.array.equal(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
|
||||
it('should call flush', function () {
|
||||
sock.send([1, 2, 3]);
|
||||
expect(sock.flush).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
describe('send_string', function () {
|
||||
beforeEach(function () {
|
||||
sock.send = sinon.spy();
|
||||
});
|
||||
|
||||
it('should call send after converting the string to an array', function () {
|
||||
sock.send_string("\x01\x02\x03");
|
||||
expect(sock.send).to.have.been.calledWith([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length - 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('lifecycle methods', function () {
|
||||
let old_WS;
|
||||
before(function () {
|
||||
old_WS = WebSocket;
|
||||
});
|
||||
|
||||
let sock;
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = sinon.spy();
|
||||
WebSocket.OPEN = old_WS.OPEN;
|
||||
WebSocket.CONNECTING = old_WS.CONNECTING;
|
||||
WebSocket.CLOSING = old_WS.CLOSING;
|
||||
WebSocket.CLOSED = old_WS.CLOSED;
|
||||
|
||||
WebSocket.prototype.binaryType = 'arraybuffer';
|
||||
});
|
||||
|
||||
describe('opening', function () {
|
||||
it('should pick the correct protocols if none are given' , function () {
|
||||
|
||||
});
|
||||
|
||||
it('should open the actual websocket', function () {
|
||||
sock.open('ws://localhost:8675', 'binary');
|
||||
expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'binary');
|
||||
});
|
||||
|
||||
// it('should initialize the event handlers')?
|
||||
});
|
||||
|
||||
describe('closing', function () {
|
||||
beforeEach(function () {
|
||||
sock.open('ws://');
|
||||
sock._websocket.close = sinon.spy();
|
||||
});
|
||||
|
||||
it('should close the actual websocket if it is open', function () {
|
||||
sock._websocket.readyState = WebSocket.OPEN;
|
||||
sock.close();
|
||||
expect(sock._websocket.close).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should close the actual websocket if it is connecting', function () {
|
||||
sock._websocket.readyState = WebSocket.CONNECTING;
|
||||
sock.close();
|
||||
expect(sock._websocket.close).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should not try to close the actual websocket if closing', function () {
|
||||
sock._websocket.readyState = WebSocket.CLOSING;
|
||||
sock.close();
|
||||
expect(sock._websocket.close).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('should not try to close the actual websocket if closed', function () {
|
||||
sock._websocket.readyState = WebSocket.CLOSED;
|
||||
sock.close();
|
||||
expect(sock._websocket.close).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('should reset onmessage to not call _recv_message', function () {
|
||||
sinon.spy(sock, '_recv_message');
|
||||
sock.close();
|
||||
sock._websocket.onmessage(null);
|
||||
try {
|
||||
expect(sock._recv_message).not.to.have.been.called;
|
||||
} finally {
|
||||
sock._recv_message.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('event handlers', function () {
|
||||
beforeEach(function () {
|
||||
sock._recv_message = sinon.spy();
|
||||
sock.on('open', sinon.spy());
|
||||
sock.on('close', sinon.spy());
|
||||
sock.on('error', sinon.spy());
|
||||
sock.open('ws://');
|
||||
});
|
||||
|
||||
it('should call _recv_message on a message', function () {
|
||||
sock._websocket.onmessage(null);
|
||||
expect(sock._recv_message).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should call the open event handler on opening', function () {
|
||||
sock._websocket.onopen();
|
||||
expect(sock._eventHandlers.open).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should call the close event handler on closing', function () {
|
||||
sock._websocket.onclose();
|
||||
expect(sock._eventHandlers.close).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should call the error event handler on error', function () {
|
||||
sock._websocket.onerror();
|
||||
expect(sock._eventHandlers.error).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
after(function () {
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = old_WS;
|
||||
});
|
||||
describe('rQpeek8', function () {
|
||||
it('should peek at the next byte without poping it off the queue', function () {
|
||||
const bef_len = sock.rQlen();
|
||||
const peek = sock.rQpeek8();
|
||||
expect(sock.rQpeek8()).to.equal(peek);
|
||||
expect(sock.rQlen()).to.equal(bef_len);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WebSocket Receiving', function () {
|
||||
let sock;
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
sock._allocate_buffers();
|
||||
});
|
||||
|
||||
it('should support adding binary Uint8Array data to the receive queue', function () {
|
||||
const msg = { data: new Uint8Array([1, 2, 3]) };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
|
||||
});
|
||||
|
||||
it('should call the message event handler if present', function () {
|
||||
sock._eventHandlers.message = sinon.spy();
|
||||
const msg = { data: new Uint8Array([1, 2, 3]).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
expect(sock._eventHandlers.message).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should not call the message event handler if there is nothing in the receive queue', function () {
|
||||
sock._eventHandlers.message = sinon.spy();
|
||||
const msg = { data: new Uint8Array([]).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
expect(sock._eventHandlers.message).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('should compact the receive queue', function () {
|
||||
// NB(sross): while this is an internal implementation detail, it's important to
|
||||
// test, otherwise the receive queue could become very large very quickly
|
||||
sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]);
|
||||
sock._rQlen = 6;
|
||||
sock.set_rQi(6);
|
||||
sock._rQmax = 3;
|
||||
const msg = { data: new Uint8Array([1, 2, 3]).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
expect(sock._rQlen).to.equal(3);
|
||||
expect(sock.get_rQi()).to.equal(0);
|
||||
});
|
||||
|
||||
it('should automatically resize the receive queue if the incoming message is too large', function () {
|
||||
sock._rQ = new Uint8Array(20);
|
||||
sock._rQlen = 0;
|
||||
sock.set_rQi(0);
|
||||
sock._rQbufferSize = 20;
|
||||
sock._rQmax = 2;
|
||||
const msg = { data: new Uint8Array(30).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
expect(sock._rQlen).to.equal(30);
|
||||
expect(sock.get_rQi()).to.equal(0);
|
||||
expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen
|
||||
});
|
||||
describe('rQshift8', function () {
|
||||
it('should pop a single byte from the receive queue', function () {
|
||||
const peek = sock.rQpeek8();
|
||||
const bef_len = sock.rQlen();
|
||||
expect(sock.rQshift8()).to.equal(peek);
|
||||
expect(sock.rQlen()).to.equal(bef_len - 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Data encoding', function () {
|
||||
before(function () { FakeWebSocket.replace(); });
|
||||
after(function () { FakeWebSocket.restore(); });
|
||||
|
||||
describe('as binary data', function () {
|
||||
let sock;
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
sock.open('ws://', 'binary');
|
||||
sock._websocket._open();
|
||||
});
|
||||
|
||||
it('should only send the send queue up to the send queue length', function () {
|
||||
sock._sQ = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
sock._sQlen = 3;
|
||||
const res = sock._encode_message();
|
||||
expect(res).to.array.equal(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
|
||||
it('should properly pass the encoded data off to the actual WebSocket', function () {
|
||||
sock.send([1, 2, 3]);
|
||||
expect(sock._websocket._get_sent_data()).to.array.equal(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
});
|
||||
describe('rQshift16', function () {
|
||||
it('should pop two bytes from the receive queue and return a single number', function () {
|
||||
const bef_len = sock.rQlen();
|
||||
const expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1];
|
||||
expect(sock.rQshift16()).to.equal(expected);
|
||||
expect(sock.rQlen()).to.equal(bef_len - 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshift32', function () {
|
||||
it('should pop four bytes from the receive queue and return a single number', function () {
|
||||
const bef_len = sock.rQlen();
|
||||
const expected = (RQ_TEMPLATE[0] << 24)
|
||||
+ (RQ_TEMPLATE[1] << 16)
|
||||
+ (RQ_TEMPLATE[2] << 8)
|
||||
+ RQ_TEMPLATE[3];
|
||||
expect(sock.rQshift32()).to.equal(expected);
|
||||
expect(sock.rQlen()).to.equal(bef_len - 4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshiftStr', function () {
|
||||
it('should shift the given number of bytes off of the receive queue and return a string', function () {
|
||||
const bef_len = sock.rQlen();
|
||||
const bef_rQi = sock.get_rQi();
|
||||
const shifted = sock.rQshiftStr(3);
|
||||
expect(shifted).to.be.a('string');
|
||||
expect(shifted).to.equal(String.fromCharCode.apply(null,
|
||||
Array.prototype.slice.call(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3))));
|
||||
expect(sock.rQlen()).to.equal(bef_len - 3);
|
||||
});
|
||||
|
||||
it('should shift the entire rest of the queue off if no length is given', function () {
|
||||
sock.rQshiftStr();
|
||||
expect(sock.rQlen()).to.equal(0);
|
||||
});
|
||||
|
||||
it('should be able to handle very large strings', function () {
|
||||
const BIG_LEN = 500000;
|
||||
const RQ_BIG = new Uint8Array(BIG_LEN);
|
||||
let expected = '';
|
||||
let letterCode = 'a'.charCodeAt(0);
|
||||
for (let i = 0; i < BIG_LEN; i++) {
|
||||
RQ_BIG[i] = letterCode;
|
||||
expected += String.fromCharCode(letterCode);
|
||||
|
||||
if (letterCode < 'z'.charCodeAt(0)) {
|
||||
letterCode++;
|
||||
} else {
|
||||
letterCode = 'a'.charCodeAt(0);
|
||||
}
|
||||
}
|
||||
sock._rQ.set(RQ_BIG);
|
||||
sock._rQlen = RQ_BIG.length;
|
||||
|
||||
const shifted = sock.rQshiftStr();
|
||||
|
||||
expect(shifted).to.be.equal(expected);
|
||||
expect(sock.rQlen()).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQshiftBytes', function () {
|
||||
it('should shift the given number of bytes of the receive queue and return an array', function () {
|
||||
const bef_len = sock.rQlen();
|
||||
const bef_rQi = sock.get_rQi();
|
||||
const shifted = sock.rQshiftBytes(3);
|
||||
expect(shifted).to.be.an.instanceof(Uint8Array);
|
||||
expect(shifted).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, bef_rQi, 3));
|
||||
expect(sock.rQlen()).to.equal(bef_len - 3);
|
||||
});
|
||||
|
||||
it('should shift the entire rest of the queue off if no length is given', function () {
|
||||
sock.rQshiftBytes();
|
||||
expect(sock.rQlen()).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQslice', function () {
|
||||
beforeEach(function () {
|
||||
sock.set_rQi(0);
|
||||
});
|
||||
|
||||
it('should not modify the receive queue', function () {
|
||||
const bef_len = sock.rQlen();
|
||||
sock.rQslice(0, 2);
|
||||
expect(sock.rQlen()).to.equal(bef_len);
|
||||
});
|
||||
|
||||
it('should return an array containing the given slice of the receive queue', function () {
|
||||
const sl = sock.rQslice(0, 2);
|
||||
expect(sl).to.be.an.instanceof(Uint8Array);
|
||||
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 0, 2));
|
||||
});
|
||||
|
||||
it('should use the rest of the receive queue if no end is given', function () {
|
||||
const sl = sock.rQslice(1);
|
||||
expect(sl).to.have.length(RQ_TEMPLATE.length - 1);
|
||||
expect(sl).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1));
|
||||
});
|
||||
|
||||
it('should take the current rQi in to account', function () {
|
||||
sock.set_rQi(1);
|
||||
expect(sock.rQslice(0, 2)).to.array.equal(new Uint8Array(RQ_TEMPLATE.buffer, 1, 2));
|
||||
});
|
||||
});
|
||||
|
||||
describe('rQwait', function () {
|
||||
beforeEach(function () {
|
||||
sock.set_rQi(0);
|
||||
});
|
||||
|
||||
it('should return true if there are not enough bytes in the receive queue', function () {
|
||||
expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true;
|
||||
});
|
||||
|
||||
it('should return false if there are enough bytes in the receive queue', function () {
|
||||
expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false;
|
||||
});
|
||||
|
||||
it('should return true and reduce rQi by "goback" if there are not enough bytes', function () {
|
||||
sock.set_rQi(5);
|
||||
expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true;
|
||||
expect(sock.get_rQi()).to.equal(1);
|
||||
});
|
||||
|
||||
it('should raise an error if we try to go back more than possible', function () {
|
||||
sock.set_rQi(5);
|
||||
expect(() => sock.rQwait('hi', RQ_TEMPLATE.length, 6)).to.throw(Error);
|
||||
});
|
||||
|
||||
it('should not reduce rQi if there are enough bytes', function () {
|
||||
sock.set_rQi(5);
|
||||
sock.rQwait('hi', 1, 6);
|
||||
expect(sock.get_rQi()).to.equal(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('flush', function () {
|
||||
beforeEach(function () {
|
||||
sock._websocket = {
|
||||
send: sinon.spy()
|
||||
};
|
||||
});
|
||||
|
||||
it('should actually send on the websocket', function () {
|
||||
sock._websocket.bufferedAmount = 8;
|
||||
sock._websocket.readyState = WebSocket.OPEN;
|
||||
sock._sQ = new Uint8Array([1, 2, 3]);
|
||||
sock._sQlen = 3;
|
||||
const encoded = sock._encode_message();
|
||||
|
||||
sock.flush();
|
||||
expect(sock._websocket.send).to.have.been.calledOnce;
|
||||
expect(sock._websocket.send).to.have.been.calledWith(encoded);
|
||||
});
|
||||
|
||||
it('should not call send if we do not have anything queued up', function () {
|
||||
sock._sQlen = 0;
|
||||
sock._websocket.bufferedAmount = 8;
|
||||
|
||||
sock.flush();
|
||||
|
||||
expect(sock._websocket.send).not.to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('send', function () {
|
||||
beforeEach(function () {
|
||||
sock.flush = sinon.spy();
|
||||
});
|
||||
|
||||
it('should add to the send queue', function () {
|
||||
sock.send([1, 2, 3]);
|
||||
const sq = sock.get_sQ();
|
||||
const sendQueue = new Uint8Array(sq.buffer, sock._sQlen - 3, 3);
|
||||
expect(sendQueue).to.array.equal(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
|
||||
it('should call flush', function () {
|
||||
sock.send([1, 2, 3]);
|
||||
expect(sock.flush).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
describe('send_string', function () {
|
||||
beforeEach(function () {
|
||||
sock.send = sinon.spy();
|
||||
});
|
||||
|
||||
it('should call send after converting the string to an array', function () {
|
||||
sock.send_string('\x01\x02\x03');
|
||||
expect(sock.send).to.have.been.calledWith([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('lifecycle methods', function () {
|
||||
let old_WS;
|
||||
before(function () {
|
||||
old_WS = WebSocket;
|
||||
});
|
||||
|
||||
let sock;
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = sinon.spy();
|
||||
WebSocket.OPEN = old_WS.OPEN;
|
||||
WebSocket.CONNECTING = old_WS.CONNECTING;
|
||||
WebSocket.CLOSING = old_WS.CLOSING;
|
||||
WebSocket.CLOSED = old_WS.CLOSED;
|
||||
|
||||
WebSocket.prototype.binaryType = 'arraybuffer';
|
||||
});
|
||||
|
||||
describe('opening', function () {
|
||||
it('should pick the correct protocols if none are given', function () {
|
||||
|
||||
});
|
||||
|
||||
it('should open the actual websocket', function () {
|
||||
sock.open('ws://localhost:8675', 'binary');
|
||||
expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'binary');
|
||||
});
|
||||
|
||||
// it('should initialize the event handlers')?
|
||||
});
|
||||
|
||||
describe('closing', function () {
|
||||
beforeEach(function () {
|
||||
sock.open('ws://');
|
||||
sock._websocket.close = sinon.spy();
|
||||
});
|
||||
|
||||
it('should close the actual websocket if it is open', function () {
|
||||
sock._websocket.readyState = WebSocket.OPEN;
|
||||
sock.close();
|
||||
expect(sock._websocket.close).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should close the actual websocket if it is connecting', function () {
|
||||
sock._websocket.readyState = WebSocket.CONNECTING;
|
||||
sock.close();
|
||||
expect(sock._websocket.close).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should not try to close the actual websocket if closing', function () {
|
||||
sock._websocket.readyState = WebSocket.CLOSING;
|
||||
sock.close();
|
||||
expect(sock._websocket.close).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('should not try to close the actual websocket if closed', function () {
|
||||
sock._websocket.readyState = WebSocket.CLOSED;
|
||||
sock.close();
|
||||
expect(sock._websocket.close).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('should reset onmessage to not call _recv_message', function () {
|
||||
sinon.spy(sock, '_recv_message');
|
||||
sock.close();
|
||||
sock._websocket.onmessage(null);
|
||||
try {
|
||||
expect(sock._recv_message).not.to.have.been.called;
|
||||
} finally {
|
||||
sock._recv_message.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('event handlers', function () {
|
||||
beforeEach(function () {
|
||||
sock._recv_message = sinon.spy();
|
||||
sock.on('open', sinon.spy());
|
||||
sock.on('close', sinon.spy());
|
||||
sock.on('error', sinon.spy());
|
||||
sock.open('ws://');
|
||||
});
|
||||
|
||||
it('should call _recv_message on a message', function () {
|
||||
sock._websocket.onmessage(null);
|
||||
expect(sock._recv_message).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should call the open event handler on opening', function () {
|
||||
sock._websocket.onopen();
|
||||
expect(sock._eventHandlers.open).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should call the close event handler on closing', function () {
|
||||
sock._websocket.onclose();
|
||||
expect(sock._eventHandlers.close).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should call the error event handler on error', function () {
|
||||
sock._websocket.onerror();
|
||||
expect(sock._eventHandlers.error).to.have.been.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
after(function () {
|
||||
// eslint-disable-next-line no-global-assign
|
||||
WebSocket = old_WS;
|
||||
});
|
||||
});
|
||||
|
||||
describe('WebSocket Receiving', function () {
|
||||
let sock;
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
sock._allocate_buffers();
|
||||
});
|
||||
|
||||
it('should support adding binary Uint8Array data to the receive queue', function () {
|
||||
const msg = { data: new Uint8Array([1, 2, 3]) };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03');
|
||||
});
|
||||
|
||||
it('should call the message event handler if present', function () {
|
||||
sock._eventHandlers.message = sinon.spy();
|
||||
const msg = { data: new Uint8Array([1, 2, 3]).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
expect(sock._eventHandlers.message).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should not call the message event handler if there is nothing in the receive queue', function () {
|
||||
sock._eventHandlers.message = sinon.spy();
|
||||
const msg = { data: new Uint8Array([]).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
expect(sock._eventHandlers.message).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('should compact the receive queue', function () {
|
||||
// NB(sross): while this is an internal implementation detail, it's important to
|
||||
// test, otherwise the receive queue could become very large very quickly
|
||||
sock._rQ = new Uint8Array([0, 1, 2, 3, 4, 5, 0, 0, 0, 0]);
|
||||
sock._rQlen = 6;
|
||||
sock.set_rQi(6);
|
||||
sock._rQmax = 3;
|
||||
const msg = { data: new Uint8Array([1, 2, 3]).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
expect(sock._rQlen).to.equal(3);
|
||||
expect(sock.get_rQi()).to.equal(0);
|
||||
});
|
||||
|
||||
it('should automatically resize the receive queue if the incoming message is too large', function () {
|
||||
sock._rQ = new Uint8Array(20);
|
||||
sock._rQlen = 0;
|
||||
sock.set_rQi(0);
|
||||
sock._rQbufferSize = 20;
|
||||
sock._rQmax = 2;
|
||||
const msg = { data: new Uint8Array(30).buffer };
|
||||
sock._mode = 'binary';
|
||||
sock._recv_message(msg);
|
||||
expect(sock._rQlen).to.equal(30);
|
||||
expect(sock.get_rQi()).to.equal(0);
|
||||
expect(sock._rQ.length).to.equal(240); // keep the invariant that rQbufferSize / 8 >= rQlen
|
||||
});
|
||||
});
|
||||
|
||||
describe('Data encoding', function () {
|
||||
before(function () { FakeWebSocket.replace(); });
|
||||
after(function () { FakeWebSocket.restore(); });
|
||||
|
||||
describe('as binary data', function () {
|
||||
let sock;
|
||||
beforeEach(function () {
|
||||
sock = new Websock();
|
||||
sock.open('ws://', 'binary');
|
||||
sock._websocket._open();
|
||||
});
|
||||
|
||||
it('should only send the send queue up to the send queue length', function () {
|
||||
sock._sQ = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
sock._sQlen = 3;
|
||||
const res = sock._encode_message();
|
||||
expect(res).to.array.equal(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
|
||||
it('should properly pass the encoded data off to the actual WebSocket', function () {
|
||||
sock.send([1, 2, 3]);
|
||||
expect(sock._websocket._get_sent_data()).to.array.equal(new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,181 +6,180 @@ import * as WebUtil from '../app/webutil.js';
|
|||
|
||||
import sinon from '../vendor/sinon.js';
|
||||
|
||||
describe('WebUtil', function() {
|
||||
"use strict";
|
||||
describe('WebUtil', function () {
|
||||
'use strict';
|
||||
|
||||
describe('settings', function () {
|
||||
describe('settings', function () {
|
||||
describe('localStorage', function () {
|
||||
let chrome = window.chrome;
|
||||
before(function () {
|
||||
chrome = window.chrome;
|
||||
window.chrome = null;
|
||||
});
|
||||
after(function () {
|
||||
window.chrome = chrome;
|
||||
});
|
||||
|
||||
describe('localStorage', function() {
|
||||
let chrome = window.chrome;
|
||||
before(function() {
|
||||
chrome = window.chrome;
|
||||
window.chrome = null;
|
||||
});
|
||||
after(function() {
|
||||
window.chrome = chrome;
|
||||
});
|
||||
let origLocalStorage;
|
||||
beforeEach(function () {
|
||||
origLocalStorage = Object.getOwnPropertyDescriptor(window, 'localStorage');
|
||||
if (origLocalStorage === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
|
||||
let origLocalStorage;
|
||||
beforeEach(function() {
|
||||
origLocalStorage = Object.getOwnPropertyDescriptor(window, "localStorage");
|
||||
if (origLocalStorage === undefined) {
|
||||
// Object.getOwnPropertyDescriptor() doesn't work
|
||||
// properly in any version of IE
|
||||
this.skip();
|
||||
}
|
||||
Object.defineProperty(window, 'localStorage', { value: {} });
|
||||
if (window.localStorage.setItem !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
|
||||
Object.defineProperty(window, "localStorage", {value: {}});
|
||||
if (window.localStorage.setItem !== undefined) {
|
||||
// Object.defineProperty() doesn't work properly in old
|
||||
// versions of Chrome
|
||||
this.skip();
|
||||
}
|
||||
window.localStorage.setItem = sinon.stub();
|
||||
window.localStorage.getItem = sinon.stub();
|
||||
window.localStorage.removeItem = sinon.stub();
|
||||
|
||||
window.localStorage.setItem = sinon.stub();
|
||||
window.localStorage.getItem = sinon.stub();
|
||||
window.localStorage.removeItem = sinon.stub();
|
||||
WebUtil.initSettings();
|
||||
});
|
||||
afterEach(function () {
|
||||
Object.defineProperty(window, 'localStorage', origLocalStorage);
|
||||
});
|
||||
|
||||
WebUtil.initSettings();
|
||||
});
|
||||
afterEach(function() {
|
||||
Object.defineProperty(window, "localStorage", origLocalStorage);
|
||||
});
|
||||
describe('writeSetting', function () {
|
||||
it('should save the setting value to local storage', function () {
|
||||
WebUtil.writeSetting('test', 'value');
|
||||
expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value');
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeSetting', function() {
|
||||
it('should save the setting value to local storage', function() {
|
||||
WebUtil.writeSetting('test', 'value');
|
||||
expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value');
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
});
|
||||
describe('setSetting', function () {
|
||||
it('should update the setting but not save to local storage', function () {
|
||||
WebUtil.setSetting('test', 'value');
|
||||
expect(window.localStorage.setItem).to.not.have.been.called;
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSetting', function() {
|
||||
it('should update the setting but not save to local storage', function() {
|
||||
WebUtil.setSetting('test', 'value');
|
||||
expect(window.localStorage.setItem).to.not.have.been.called;
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
});
|
||||
|
||||
describe('readSetting', function() {
|
||||
it('should read the setting value from local storage', function() {
|
||||
localStorage.getItem.returns('value');
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
|
||||
it('should return the default value when not in local storage', function() {
|
||||
expect(WebUtil.readSetting('test', 'default')).to.equal('default');
|
||||
});
|
||||
|
||||
it('should return the cached value even if local storage changed', function() {
|
||||
localStorage.getItem.returns('value');
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
localStorage.getItem.returns('something else');
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
|
||||
it('should cache the value even if it is not initially in local storage', function() {
|
||||
expect(WebUtil.readSetting('test')).to.be.null;
|
||||
localStorage.getItem.returns('value');
|
||||
expect(WebUtil.readSetting('test')).to.be.null;
|
||||
});
|
||||
|
||||
it('should return the default value always if the first read was not in local storage', function() {
|
||||
expect(WebUtil.readSetting('test', 'default')).to.equal('default');
|
||||
localStorage.getItem.returns('value');
|
||||
expect(WebUtil.readSetting('test', 'another default')).to.equal('another default');
|
||||
});
|
||||
|
||||
it('should return the last local written value', function() {
|
||||
localStorage.getItem.returns('value');
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
WebUtil.writeSetting('test', 'something else');
|
||||
expect(WebUtil.readSetting('test')).to.equal('something else');
|
||||
});
|
||||
});
|
||||
|
||||
// this doesn't appear to be used anywhere
|
||||
describe('eraseSetting', function() {
|
||||
it('should remove the setting from local storage', function() {
|
||||
WebUtil.eraseSetting('test');
|
||||
expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test');
|
||||
});
|
||||
});
|
||||
describe('readSetting', function () {
|
||||
it('should read the setting value from local storage', function () {
|
||||
localStorage.getItem.returns('value');
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
|
||||
describe('chrome.storage', function() {
|
||||
let chrome = window.chrome;
|
||||
let settings = {};
|
||||
before(function() {
|
||||
chrome = window.chrome;
|
||||
window.chrome = {
|
||||
storage: {
|
||||
sync: {
|
||||
get(cb){ cb(settings); },
|
||||
set(){},
|
||||
remove() {}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
after(function() {
|
||||
window.chrome = chrome;
|
||||
});
|
||||
|
||||
const csSandbox = sinon.createSandbox();
|
||||
|
||||
beforeEach(function() {
|
||||
settings = {};
|
||||
csSandbox.spy(window.chrome.storage.sync, 'set');
|
||||
csSandbox.spy(window.chrome.storage.sync, 'remove');
|
||||
WebUtil.initSettings();
|
||||
});
|
||||
afterEach(function() {
|
||||
csSandbox.restore();
|
||||
});
|
||||
|
||||
describe('writeSetting', function() {
|
||||
it('should save the setting value to chrome storage', function() {
|
||||
WebUtil.writeSetting('test', 'value');
|
||||
expect(window.chrome.storage.sync.set).to.have.been.calledWithExactly(sinon.match({ test: 'value' }));
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSetting', function() {
|
||||
it('should update the setting but not save to chrome storage', function() {
|
||||
WebUtil.setSetting('test', 'value');
|
||||
expect(window.chrome.storage.sync.set).to.not.have.been.called;
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
});
|
||||
|
||||
describe('readSetting', function() {
|
||||
it('should read the setting value from chrome storage', function() {
|
||||
settings.test = 'value';
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
|
||||
it('should return the default value when not in chrome storage', function() {
|
||||
expect(WebUtil.readSetting('test', 'default')).to.equal('default');
|
||||
});
|
||||
|
||||
it('should return the last local written value', function() {
|
||||
settings.test = 'value';
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
WebUtil.writeSetting('test', 'something else');
|
||||
expect(WebUtil.readSetting('test')).to.equal('something else');
|
||||
});
|
||||
});
|
||||
|
||||
// this doesn't appear to be used anywhere
|
||||
describe('eraseSetting', function() {
|
||||
it('should remove the setting from chrome storage', function() {
|
||||
WebUtil.eraseSetting('test');
|
||||
expect(window.chrome.storage.sync.remove).to.have.been.calledWithExactly('test');
|
||||
});
|
||||
});
|
||||
it('should return the default value when not in local storage', function () {
|
||||
expect(WebUtil.readSetting('test', 'default')).to.equal('default');
|
||||
});
|
||||
|
||||
it('should return the cached value even if local storage changed', function () {
|
||||
localStorage.getItem.returns('value');
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
localStorage.getItem.returns('something else');
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
|
||||
it('should cache the value even if it is not initially in local storage', function () {
|
||||
expect(WebUtil.readSetting('test')).to.be.null;
|
||||
localStorage.getItem.returns('value');
|
||||
expect(WebUtil.readSetting('test')).to.be.null;
|
||||
});
|
||||
|
||||
it('should return the default value always if the first read was not in local storage', function () {
|
||||
expect(WebUtil.readSetting('test', 'default')).to.equal('default');
|
||||
localStorage.getItem.returns('value');
|
||||
expect(WebUtil.readSetting('test', 'another default')).to.equal('another default');
|
||||
});
|
||||
|
||||
it('should return the last local written value', function () {
|
||||
localStorage.getItem.returns('value');
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
WebUtil.writeSetting('test', 'something else');
|
||||
expect(WebUtil.readSetting('test')).to.equal('something else');
|
||||
});
|
||||
});
|
||||
|
||||
// this doesn't appear to be used anywhere
|
||||
describe('eraseSetting', function () {
|
||||
it('should remove the setting from local storage', function () {
|
||||
WebUtil.eraseSetting('test');
|
||||
expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('chrome.storage', function () {
|
||||
let chrome = window.chrome;
|
||||
let settings = {};
|
||||
before(function () {
|
||||
chrome = window.chrome;
|
||||
window.chrome = {
|
||||
storage: {
|
||||
sync: {
|
||||
get(cb) { cb(settings); },
|
||||
set() {},
|
||||
remove() {}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
after(function () {
|
||||
window.chrome = chrome;
|
||||
});
|
||||
|
||||
const csSandbox = sinon.createSandbox();
|
||||
|
||||
beforeEach(function () {
|
||||
settings = {};
|
||||
csSandbox.spy(window.chrome.storage.sync, 'set');
|
||||
csSandbox.spy(window.chrome.storage.sync, 'remove');
|
||||
WebUtil.initSettings();
|
||||
});
|
||||
afterEach(function () {
|
||||
csSandbox.restore();
|
||||
});
|
||||
|
||||
describe('writeSetting', function () {
|
||||
it('should save the setting value to chrome storage', function () {
|
||||
WebUtil.writeSetting('test', 'value');
|
||||
expect(window.chrome.storage.sync.set).to.have.been.calledWithExactly(sinon.match({ test: 'value' }));
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSetting', function () {
|
||||
it('should update the setting but not save to chrome storage', function () {
|
||||
WebUtil.setSetting('test', 'value');
|
||||
expect(window.chrome.storage.sync.set).to.not.have.been.called;
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
});
|
||||
|
||||
describe('readSetting', function () {
|
||||
it('should read the setting value from chrome storage', function () {
|
||||
settings.test = 'value';
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
});
|
||||
|
||||
it('should return the default value when not in chrome storage', function () {
|
||||
expect(WebUtil.readSetting('test', 'default')).to.equal('default');
|
||||
});
|
||||
|
||||
it('should return the last local written value', function () {
|
||||
settings.test = 'value';
|
||||
expect(WebUtil.readSetting('test')).to.equal('value');
|
||||
WebUtil.writeSetting('test', 'something else');
|
||||
expect(WebUtil.readSetting('test')).to.equal('something else');
|
||||
});
|
||||
});
|
||||
|
||||
// this doesn't appear to be used anywhere
|
||||
describe('eraseSetting', function () {
|
||||
it('should remove the setting from chrome storage', function () {
|
||||
WebUtil.eraseSetting('test');
|
||||
expect(window.chrome.storage.sync.remove).to.have.been.calledWithExactly('test');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
|
|
@ -15,12 +15,12 @@ let filename;
|
|||
|
||||
for (let i = 2; i < process.argv.length; ++i) {
|
||||
switch (process.argv[i]) {
|
||||
case "--help":
|
||||
case "-h":
|
||||
case '--help':
|
||||
case '-h':
|
||||
show_help = true;
|
||||
break;
|
||||
case "--file":
|
||||
case "-f":
|
||||
case '--file':
|
||||
case '-f':
|
||||
default:
|
||||
filename = process.argv[i];
|
||||
}
|
||||
|
|
@ -28,14 +28,14 @@ for (let i = 2; i < process.argv.length; ++i) {
|
|||
|
||||
if (!filename) {
|
||||
show_help = true;
|
||||
console.log("Error: No filename specified\n");
|
||||
console.log('Error: No filename specified\n');
|
||||
}
|
||||
|
||||
if (show_help) {
|
||||
console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings");
|
||||
console.log("Usage: node parse.js [options] filename:");
|
||||
console.log(" -h [ --help ] Produce this help message");
|
||||
console.log(" filename The keysymdef.h file to parse");
|
||||
console.log('Parses a *nix keysymdef.h to generate Unicode code point mappings');
|
||||
console.log('Usage: node parse.js [options] filename:');
|
||||
console.log(' -h [ --help ] Produce this help message');
|
||||
console.log(' filename The keysymdef.h file to parse');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
|
@ -49,80 +49,79 @@ const arr = str.split('\n');
|
|||
const codepoints = {};
|
||||
|
||||
for (let i = 0; i < arr.length; ++i) {
|
||||
const result = re.exec(arr[i]);
|
||||
if (result){
|
||||
const keyname = result[1];
|
||||
const keysym = parseInt(result[2], 16);
|
||||
const remainder = result[3];
|
||||
const result = re.exec(arr[i]);
|
||||
if (result) {
|
||||
const keyname = result[1];
|
||||
const keysym = parseInt(result[2], 16);
|
||||
const remainder = result[3];
|
||||
|
||||
const unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder);
|
||||
if (unicodeRes) {
|
||||
const unicode = parseInt(unicodeRes[1], 16);
|
||||
// The first entry is the preferred one
|
||||
if (!codepoints[unicode]){
|
||||
codepoints[unicode] = { keysym: keysym, name: keyname };
|
||||
}
|
||||
}
|
||||
const unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder);
|
||||
if (unicodeRes) {
|
||||
const unicode = parseInt(unicodeRes[1], 16);
|
||||
// The first entry is the preferred one
|
||||
if (!codepoints[unicode]) {
|
||||
codepoints[unicode] = { keysym: keysym, name: keyname };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let out =
|
||||
"/*\n" +
|
||||
" * Mapping from Unicode codepoints to X11/RFB keysyms\n" +
|
||||
" *\n" +
|
||||
" * This file was automatically generated from keysymdef.h\n" +
|
||||
" * DO NOT EDIT!\n" +
|
||||
" */\n" +
|
||||
"\n" +
|
||||
"/* Functions at the bottom */\n" +
|
||||
"\n" +
|
||||
"const codepoints = {\n";
|
||||
let out = '/*\n'
|
||||
+ ' * Mapping from Unicode codepoints to X11/RFB keysyms\n'
|
||||
+ ' *\n'
|
||||
+ ' * This file was automatically generated from keysymdef.h\n'
|
||||
+ ' * DO NOT EDIT!\n'
|
||||
+ ' */\n'
|
||||
+ '\n'
|
||||
+ '/* Functions at the bottom */\n'
|
||||
+ '\n'
|
||||
+ 'const codepoints = {\n';
|
||||
|
||||
function toHex(num) {
|
||||
let s = num.toString(16);
|
||||
if (s.length < 4) {
|
||||
s = ("0000" + s).slice(-4);
|
||||
}
|
||||
return "0x" + s;
|
||||
let s = num.toString(16);
|
||||
if (s.length < 4) {
|
||||
s = ('0000' + s).slice(-4);
|
||||
}
|
||||
return '0x' + s;
|
||||
}
|
||||
|
||||
for (let codepoint in codepoints) {
|
||||
codepoint = parseInt(codepoint);
|
||||
codepoint = parseInt(codepoint);
|
||||
|
||||
// Latin-1?
|
||||
if ((codepoint >= 0x20) && (codepoint <= 0xff)) {
|
||||
continue;
|
||||
}
|
||||
// Latin-1?
|
||||
if ((codepoint >= 0x20) && (codepoint <= 0xff)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handled by the general Unicode mapping?
|
||||
if ((codepoint | 0x01000000) === codepoints[codepoint].keysym) {
|
||||
continue;
|
||||
}
|
||||
// Handled by the general Unicode mapping?
|
||||
if ((codepoint | 0x01000000) === codepoints[codepoint].keysym) {
|
||||
continue;
|
||||
}
|
||||
|
||||
out += " " + toHex(codepoint) + ": " +
|
||||
toHex(codepoints[codepoint].keysym) +
|
||||
", // XK_" + codepoints[codepoint].name + "\n";
|
||||
out += ' ' + toHex(codepoint) + ': '
|
||||
+ toHex(codepoints[codepoint].keysym)
|
||||
+ ', // XK_' + codepoints[codepoint].name + '\n';
|
||||
}
|
||||
|
||||
out +=
|
||||
"};\n" +
|
||||
"\n" +
|
||||
"export default {\n" +
|
||||
" lookup(u) {\n" +
|
||||
" // Latin-1 is one-to-one mapping\n" +
|
||||
" if ((u >= 0x20) && (u <= 0xff)) {\n" +
|
||||
" return u;\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" // Lookup table (fairly random)\n" +
|
||||
" const keysym = codepoints[u];\n" +
|
||||
" if (keysym !== undefined) {\n" +
|
||||
" return keysym;\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" // General mapping as final fallback\n" +
|
||||
" return 0x01000000 | u;\n" +
|
||||
" },\n" +
|
||||
"};";
|
||||
out
|
||||
+= '};\n'
|
||||
+ '\n'
|
||||
+ 'export default {\n'
|
||||
+ ' lookup(u) {\n'
|
||||
+ ' // Latin-1 is one-to-one mapping\n'
|
||||
+ ' if ((u >= 0x20) && (u <= 0xff)) {\n'
|
||||
+ ' return u;\n'
|
||||
+ ' }\n'
|
||||
+ '\n'
|
||||
+ ' // Lookup table (fairly random)\n'
|
||||
+ ' const keysym = codepoints[u];\n'
|
||||
+ ' if (keysym !== undefined) {\n'
|
||||
+ ' return keysym;\n'
|
||||
+ ' }\n'
|
||||
+ '\n'
|
||||
+ ' // General mapping as final fallback\n'
|
||||
+ ' return 0x01000000 | u;\n'
|
||||
+ ' },\n'
|
||||
+ '};';
|
||||
|
||||
console.log(out);
|
||||
|
|
|
|||
|
|
@ -9,49 +9,49 @@ const babel = require('babel-core');
|
|||
const SUPPORTED_FORMATS = new Set(['amd', 'commonjs', 'systemjs', 'umd']);
|
||||
|
||||
program
|
||||
.option('--as [format]', `output files using various import formats instead of ES6 import and export. Supports ${Array.from(SUPPORTED_FORMATS)}.`)
|
||||
.option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ')
|
||||
.option('--with-app', 'process app files as well as core files')
|
||||
.option('--only-legacy', 'only output legacy files (no ES6 modules) for the app')
|
||||
.option('--clean', 'clear the lib folder before building')
|
||||
.parse(process.argv);
|
||||
.option('--as [format]', `output files using various import formats instead of ES6 import and export. Supports ${Array.from(SUPPORTED_FORMATS)}.`)
|
||||
.option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ')
|
||||
.option('--with-app', 'process app files as well as core files')
|
||||
.option('--only-legacy', 'only output legacy files (no ES6 modules) for the app')
|
||||
.option('--clean', 'clear the lib folder before building')
|
||||
.parse(process.argv);
|
||||
|
||||
// the various important paths
|
||||
const paths = {
|
||||
main: path.resolve(__dirname, '..'),
|
||||
core: path.resolve(__dirname, '..', 'core'),
|
||||
app: path.resolve(__dirname, '..', 'app'),
|
||||
vendor: path.resolve(__dirname, '..', 'vendor'),
|
||||
out_dir_base: path.resolve(__dirname, '..', 'build'),
|
||||
lib_dir_base: path.resolve(__dirname, '..', 'lib'),
|
||||
main: path.resolve(__dirname, '..'),
|
||||
core: path.resolve(__dirname, '..', 'core'),
|
||||
app: path.resolve(__dirname, '..', 'app'),
|
||||
vendor: path.resolve(__dirname, '..', 'vendor'),
|
||||
out_dir_base: path.resolve(__dirname, '..', 'build'),
|
||||
lib_dir_base: path.resolve(__dirname, '..', 'lib'),
|
||||
};
|
||||
|
||||
const no_copy_files = new Set([
|
||||
// skip these -- they don't belong in the processed application
|
||||
path.join(paths.vendor, 'sinon.js'),
|
||||
path.join(paths.vendor, 'browser-es-module-loader'),
|
||||
path.join(paths.vendor, 'promise.js'),
|
||||
path.join(paths.app, 'images', 'icons', 'Makefile'),
|
||||
// skip these -- they don't belong in the processed application
|
||||
path.join(paths.vendor, 'sinon.js'),
|
||||
path.join(paths.vendor, 'browser-es-module-loader'),
|
||||
path.join(paths.vendor, 'promise.js'),
|
||||
path.join(paths.app, 'images', 'icons', 'Makefile'),
|
||||
]);
|
||||
|
||||
const no_transform_files = new Set([
|
||||
// don't transform this -- we want it imported as-is to properly catch loading errors
|
||||
path.join(paths.app, 'error-handler.js'),
|
||||
// don't transform this -- we want it imported as-is to properly catch loading errors
|
||||
path.join(paths.app, 'error-handler.js'),
|
||||
]);
|
||||
|
||||
no_copy_files.forEach(file => no_transform_files.add(file));
|
||||
|
||||
// util.promisify requires Node.js 8.x, so we have our own
|
||||
function promisify(original) {
|
||||
return function () {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
return new Promise((resolve, reject) => {
|
||||
original.apply(this, args.concat((err, value) => {
|
||||
if (err) return reject(err);
|
||||
resolve(value);
|
||||
}));
|
||||
});
|
||||
}
|
||||
return function () {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
return new Promise((resolve, reject) => {
|
||||
original.apply(this, args.concat((err, value) => {
|
||||
if (err) return reject(err);
|
||||
resolve(value);
|
||||
}));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const readFile = promisify(fs.readFile);
|
||||
|
|
@ -70,44 +70,44 @@ const babelTransformFile = promisify(babel.transformFile);
|
|||
// walkDir *recursively* walks directories trees,
|
||||
// calling the callback for all normal files found.
|
||||
function walkDir(base_path, cb, filter) {
|
||||
return readdir(base_path)
|
||||
return readdir(base_path)
|
||||
.then((files) => {
|
||||
const paths = files.map(filename => path.join(base_path, filename));
|
||||
return Promise.all(paths.map(filepath => lstat(filepath)
|
||||
const paths = files.map(filename => path.join(base_path, filename));
|
||||
return Promise.all(paths.map(filepath => lstat(filepath)
|
||||
.then((stats) => {
|
||||
if (filter !== undefined && !filter(filepath, stats)) return;
|
||||
if (filter !== undefined && !filter(filepath, stats)) return;
|
||||
|
||||
if (stats.isSymbolicLink()) return;
|
||||
if (stats.isFile()) return cb(filepath);
|
||||
if (stats.isDirectory()) return walkDir(filepath, cb, filter);
|
||||
if (stats.isSymbolicLink()) return;
|
||||
if (stats.isFile()) return cb(filepath);
|
||||
if (stats.isDirectory()) return walkDir(filepath, cb, filter);
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
function transform_html (legacy_scripts, only_legacy) {
|
||||
// write out the modified vnc.html file that works with the bundle
|
||||
const src_html_path = path.resolve(__dirname, '..', 'vnc.html');
|
||||
const out_html_path = path.resolve(paths.out_dir_base, 'vnc.html');
|
||||
return readFile(src_html_path)
|
||||
function transform_html(legacy_scripts, only_legacy) {
|
||||
// write out the modified vnc.html file that works with the bundle
|
||||
const src_html_path = path.resolve(__dirname, '..', 'vnc.html');
|
||||
const out_html_path = path.resolve(paths.out_dir_base, 'vnc.html');
|
||||
return readFile(src_html_path)
|
||||
.then((contents_raw) => {
|
||||
let contents = contents_raw.toString();
|
||||
let contents = contents_raw.toString();
|
||||
|
||||
const start_marker = '<!-- begin scripts -->\n';
|
||||
const end_marker = '<!-- end scripts -->';
|
||||
const start_ind = contents.indexOf(start_marker) + start_marker.length;
|
||||
const end_ind = contents.indexOf(end_marker, start_ind);
|
||||
const start_marker = '<!-- begin scripts -->\n';
|
||||
const end_marker = '<!-- end scripts -->';
|
||||
const start_ind = contents.indexOf(start_marker) + start_marker.length;
|
||||
const end_ind = contents.indexOf(end_marker, start_ind);
|
||||
|
||||
let new_script = '';
|
||||
let new_script = '';
|
||||
|
||||
if (only_legacy) {
|
||||
// Only legacy version, so include things directly
|
||||
for (let i = 0;i < legacy_scripts.length;i++) {
|
||||
new_script += ` <script src="${legacy_scripts[i]}"></script>\n`;
|
||||
}
|
||||
} else {
|
||||
// Otherwise detect if it's a modern browser and select
|
||||
// variant accordingly
|
||||
new_script += `\
|
||||
if (only_legacy) {
|
||||
// Only legacy version, so include things directly
|
||||
for (let i = 0; i < legacy_scripts.length; i++) {
|
||||
new_script += ` <script src="${legacy_scripts[i]}"></script>\n`;
|
||||
}
|
||||
} else {
|
||||
// Otherwise detect if it's a modern browser and select
|
||||
// variant accordingly
|
||||
new_script += `\
|
||||
<script type="module">\n\
|
||||
window._noVNC_has_module_support = true;\n\
|
||||
</script>\n\
|
||||
|
|
@ -124,190 +124,191 @@ function transform_html (legacy_scripts, only_legacy) {
|
|||
});\n\
|
||||
</script>\n`;
|
||||
|
||||
// Original, ES6 modules
|
||||
new_script += ' <script type="module" crossorigin="anonymous" src="app/ui.js"></script>\n';
|
||||
}
|
||||
// Original, ES6 modules
|
||||
new_script += ' <script type="module" crossorigin="anonymous" src="app/ui.js"></script>\n';
|
||||
}
|
||||
|
||||
contents = contents.slice(0, start_ind) + `${new_script}\n` + contents.slice(end_ind);
|
||||
contents = contents.slice(0, start_ind) + `${new_script}\n` + contents.slice(end_ind);
|
||||
|
||||
return contents;
|
||||
return contents;
|
||||
})
|
||||
.then((contents) => {
|
||||
console.log(`Writing ${out_html_path}`);
|
||||
return writeFile(out_html_path, contents);
|
||||
console.log(`Writing ${out_html_path}`);
|
||||
return writeFile(out_html_path, contents);
|
||||
});
|
||||
}
|
||||
|
||||
function make_lib_files(import_format, source_maps, with_app_dir, only_legacy) {
|
||||
if (!import_format) {
|
||||
throw new Error("you must specify an import format to generate compiled noVNC libraries");
|
||||
} else if (!SUPPORTED_FORMATS.has(import_format)) {
|
||||
throw new Error(`unsupported output format "${import_format}" for import/export -- only ${Array.from(SUPPORTED_FORMATS)} are supported`);
|
||||
}
|
||||
if (!import_format) {
|
||||
throw new Error('you must specify an import format to generate compiled noVNC libraries');
|
||||
} else if (!SUPPORTED_FORMATS.has(import_format)) {
|
||||
throw new Error(`unsupported output format "${import_format}" for import/export -- only ${Array.from(SUPPORTED_FORMATS)} are supported`);
|
||||
}
|
||||
|
||||
// NB: we need to make a copy of babel_opts, since babel sets some defaults on it
|
||||
const babel_opts = () => ({
|
||||
plugins: [`transform-es2015-modules-${import_format}`],
|
||||
presets: ['es2015'],
|
||||
ast: false,
|
||||
sourceMaps: source_maps,
|
||||
});
|
||||
// NB: we need to make a copy of babel_opts, since babel sets some defaults on it
|
||||
const babel_opts = () => ({
|
||||
plugins: [`transform-es2015-modules-${import_format}`],
|
||||
presets: ['es2015'],
|
||||
ast: false,
|
||||
sourceMaps: source_maps,
|
||||
});
|
||||
|
||||
// No point in duplicate files without the app, so force only converted files
|
||||
if (!with_app_dir) {
|
||||
only_legacy = true;
|
||||
}
|
||||
if (!with_app_dir) {
|
||||
only_legacy = true;
|
||||
}
|
||||
|
||||
let in_path;
|
||||
let out_path_base;
|
||||
if (with_app_dir) {
|
||||
out_path_base = paths.out_dir_base;
|
||||
in_path = paths.main;
|
||||
} else {
|
||||
out_path_base = paths.lib_dir_base;
|
||||
}
|
||||
const legacy_path_base = only_legacy ? out_path_base : path.join(out_path_base, 'legacy');
|
||||
let in_path;
|
||||
let out_path_base;
|
||||
if (with_app_dir) {
|
||||
out_path_base = paths.out_dir_base;
|
||||
in_path = paths.main;
|
||||
} else {
|
||||
out_path_base = paths.lib_dir_base;
|
||||
}
|
||||
const legacy_path_base = only_legacy ? out_path_base : path.join(out_path_base, 'legacy');
|
||||
|
||||
fse.ensureDirSync(out_path_base);
|
||||
fse.ensureDirSync(out_path_base);
|
||||
|
||||
const helpers = require('./use_require_helpers');
|
||||
const helper = helpers[import_format];
|
||||
const helpers = require('./use_require_helpers');
|
||||
const helper = helpers[import_format];
|
||||
|
||||
const outFiles = [];
|
||||
const outFiles = [];
|
||||
|
||||
const handleDir = (js_only, vendor_rewrite, in_path_base, filename) => Promise.resolve()
|
||||
const handleDir = (js_only, vendor_rewrite, in_path_base, filename) => Promise.resolve()
|
||||
.then(() => {
|
||||
if (no_copy_files.has(filename)) return;
|
||||
if (no_copy_files.has(filename)) return;
|
||||
|
||||
const out_path = path.join(out_path_base, path.relative(in_path_base, filename));
|
||||
const legacy_path = path.join(legacy_path_base, path.relative(in_path_base, filename));
|
||||
const out_path = path.join(out_path_base, path.relative(in_path_base, filename));
|
||||
const legacy_path = path.join(legacy_path_base, path.relative(in_path_base, filename));
|
||||
|
||||
if(path.extname(filename) !== '.js') {
|
||||
if (!js_only) {
|
||||
console.log(`Writing ${out_path}`);
|
||||
return copy(filename, out_path);
|
||||
}
|
||||
return; // skip non-javascript files
|
||||
if (path.extname(filename) !== '.js') {
|
||||
if (!js_only) {
|
||||
console.log(`Writing ${out_path}`);
|
||||
return copy(filename, out_path);
|
||||
}
|
||||
return; // skip non-javascript files
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
return Promise.resolve()
|
||||
.then(() => {
|
||||
if (only_legacy && !no_transform_files.has(filename)) {
|
||||
return;
|
||||
}
|
||||
return ensureDir(path.dirname(out_path))
|
||||
if (only_legacy && !no_transform_files.has(filename)) {
|
||||
return;
|
||||
}
|
||||
return ensureDir(path.dirname(out_path))
|
||||
.then(() => {
|
||||
console.log(`Writing ${out_path}`);
|
||||
return copy(filename, out_path);
|
||||
})
|
||||
console.log(`Writing ${out_path}`);
|
||||
return copy(filename, out_path);
|
||||
});
|
||||
})
|
||||
.then(() => ensureDir(path.dirname(legacy_path)))
|
||||
.then(() => {
|
||||
if (no_transform_files.has(filename)) {
|
||||
return;
|
||||
}
|
||||
if (no_transform_files.has(filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const opts = babel_opts();
|
||||
if (helper && helpers.optionsOverride) {
|
||||
helper.optionsOverride(opts);
|
||||
}
|
||||
// Adjust for the fact that we move the core files relative
|
||||
// to the vendor directory
|
||||
if (vendor_rewrite) {
|
||||
opts.plugins.push(["import-redirect",
|
||||
{"root": legacy_path_base,
|
||||
"redirect": { "vendor/(.+)": "./vendor/$1"}}]);
|
||||
}
|
||||
const opts = babel_opts();
|
||||
if (helper && helpers.optionsOverride) {
|
||||
helper.optionsOverride(opts);
|
||||
}
|
||||
// Adjust for the fact that we move the core files relative
|
||||
// to the vendor directory
|
||||
if (vendor_rewrite) {
|
||||
opts.plugins.push(['import-redirect',
|
||||
{
|
||||
root: legacy_path_base,
|
||||
redirect: { 'vendor/(.+)': './vendor/$1' }
|
||||
}]);
|
||||
}
|
||||
|
||||
return babelTransformFile(filename, opts)
|
||||
return babelTransformFile(filename, opts)
|
||||
.then((res) => {
|
||||
console.log(`Writing ${legacy_path}`);
|
||||
const {map} = res;
|
||||
let {code} = res;
|
||||
if (source_maps === true) {
|
||||
// append URL for external source map
|
||||
code += `\n//# sourceMappingURL=${path.basename(legacy_path)}.map\n`;
|
||||
}
|
||||
outFiles.push(`${legacy_path}`);
|
||||
return writeFile(legacy_path, code)
|
||||
console.log(`Writing ${legacy_path}`);
|
||||
const { map } = res;
|
||||
let { code } = res;
|
||||
if (source_maps === true) {
|
||||
// append URL for external source map
|
||||
code += `\n//# sourceMappingURL=${path.basename(legacy_path)}.map\n`;
|
||||
}
|
||||
outFiles.push(`${legacy_path}`);
|
||||
return writeFile(legacy_path, code)
|
||||
.then(() => {
|
||||
if (source_maps === true || source_maps === 'both') {
|
||||
console.log(` and ${legacy_path}.map`);
|
||||
outFiles.push(`${legacy_path}.map`);
|
||||
return writeFile(`${legacy_path}.map`, JSON.stringify(map));
|
||||
}
|
||||
if (source_maps === true || source_maps === 'both') {
|
||||
console.log(` and ${legacy_path}.map`);
|
||||
outFiles.push(`${legacy_path}.map`);
|
||||
return writeFile(`${legacy_path}.map`, JSON.stringify(map));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (with_app_dir && helper && helper.noCopyOverride) {
|
||||
helper.noCopyOverride(paths, no_copy_files);
|
||||
}
|
||||
if (with_app_dir && helper && helper.noCopyOverride) {
|
||||
helper.noCopyOverride(paths, no_copy_files);
|
||||
}
|
||||
|
||||
Promise.resolve()
|
||||
Promise.resolve()
|
||||
.then(() => {
|
||||
const handler = handleDir.bind(null, true, false, in_path || paths.main);
|
||||
const filter = (filename, stats) => !no_copy_files.has(filename);
|
||||
return walkDir(paths.vendor, handler, filter);
|
||||
const handler = handleDir.bind(null, true, false, in_path || paths.main);
|
||||
const filter = (filename, stats) => !no_copy_files.has(filename);
|
||||
return walkDir(paths.vendor, handler, filter);
|
||||
})
|
||||
.then(() => {
|
||||
const handler = handleDir.bind(null, true, !in_path, in_path || paths.core);
|
||||
const filter = (filename, stats) => !no_copy_files.has(filename);
|
||||
return walkDir(paths.core, handler, filter);
|
||||
const handler = handleDir.bind(null, true, !in_path, in_path || paths.core);
|
||||
const filter = (filename, stats) => !no_copy_files.has(filename);
|
||||
return walkDir(paths.core, handler, filter);
|
||||
})
|
||||
.then(() => {
|
||||
if (!with_app_dir) return;
|
||||
const handler = handleDir.bind(null, false, false, in_path);
|
||||
const filter = (filename, stats) => !no_copy_files.has(filename);
|
||||
return walkDir(paths.app, handler, filter);
|
||||
if (!with_app_dir) return;
|
||||
const handler = handleDir.bind(null, false, false, in_path);
|
||||
const filter = (filename, stats) => !no_copy_files.has(filename);
|
||||
return walkDir(paths.app, handler, filter);
|
||||
})
|
||||
.then(() => {
|
||||
if (!with_app_dir) return;
|
||||
if (!with_app_dir) return;
|
||||
|
||||
if (!helper || !helper.appWriter) {
|
||||
throw new Error(`Unable to generate app for the ${import_format} format!`);
|
||||
}
|
||||
if (!helper || !helper.appWriter) {
|
||||
throw new Error(`Unable to generate app for the ${import_format} format!`);
|
||||
}
|
||||
|
||||
const out_app_path = path.join(legacy_path_base, 'app.js');
|
||||
console.log(`Writing ${out_app_path}`);
|
||||
return helper.appWriter(out_path_base, legacy_path_base, out_app_path)
|
||||
const out_app_path = path.join(legacy_path_base, 'app.js');
|
||||
console.log(`Writing ${out_app_path}`);
|
||||
return helper.appWriter(out_path_base, legacy_path_base, out_app_path)
|
||||
.then((extra_scripts) => {
|
||||
const rel_app_path = path.relative(out_path_base, out_app_path);
|
||||
const legacy_scripts = extra_scripts.concat([rel_app_path]);
|
||||
transform_html(legacy_scripts, only_legacy);
|
||||
const rel_app_path = path.relative(out_path_base, out_app_path);
|
||||
const legacy_scripts = extra_scripts.concat([rel_app_path]);
|
||||
transform_html(legacy_scripts, only_legacy);
|
||||
})
|
||||
.then(() => {
|
||||
if (!helper.removeModules) return;
|
||||
console.log(`Cleaning up temporary files...`);
|
||||
return Promise.all(outFiles.map((filepath) => {
|
||||
unlink(filepath)
|
||||
.then(() => {
|
||||
// Try to clean up any empty directories if this
|
||||
// was the last file in there
|
||||
const rmdir_r = dir =>
|
||||
rmdir(dir)
|
||||
.then(() => rmdir_r(path.dirname(dir)))
|
||||
.catch(() => {
|
||||
// Assume the error was ENOTEMPTY and ignore it
|
||||
});
|
||||
return rmdir_r(path.dirname(filepath));
|
||||
});
|
||||
}));
|
||||
if (!helper.removeModules) return;
|
||||
console.log('Cleaning up temporary files...');
|
||||
return Promise.all(outFiles.map((filepath) => {
|
||||
unlink(filepath)
|
||||
.then(() => {
|
||||
// Try to clean up any empty directories if this
|
||||
// was the last file in there
|
||||
const rmdir_r = dir => rmdir(dir)
|
||||
.then(() => rmdir_r(path.dirname(dir)))
|
||||
.catch(() => {
|
||||
// Assume the error was ENOTEMPTY and ignore it
|
||||
});
|
||||
return rmdir_r(path.dirname(filepath));
|
||||
});
|
||||
}));
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`Failure converting modules: ${err}`);
|
||||
process.exit(1);
|
||||
console.error(`Failure converting modules: ${err}`);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
if (program.clean) {
|
||||
console.log(`Removing ${paths.lib_dir_base}`);
|
||||
fse.removeSync(paths.lib_dir_base);
|
||||
console.log(`Removing ${paths.lib_dir_base}`);
|
||||
fse.removeSync(paths.lib_dir_base);
|
||||
|
||||
console.log(`Removing ${paths.out_dir_base}`);
|
||||
fse.removeSync(paths.out_dir_base);
|
||||
console.log(`Removing ${paths.out_dir_base}`);
|
||||
fse.removeSync(paths.out_dir_base);
|
||||
}
|
||||
|
||||
make_lib_files(program.as, program.withSourceMaps, program.withApp, program.onlyLegacy);
|
||||
|
|
|
|||
|
|
@ -4,73 +4,73 @@ const path = require('path');
|
|||
|
||||
// util.promisify requires Node.js 8.x, so we have our own
|
||||
function promisify(original) {
|
||||
return function () {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
return new Promise((resolve, reject) => {
|
||||
original.apply(this, args.concat((err, value) => {
|
||||
if (err) return reject(err);
|
||||
resolve(value);
|
||||
}));
|
||||
});
|
||||
}
|
||||
return function () {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
return new Promise((resolve, reject) => {
|
||||
original.apply(this, args.concat((err, value) => {
|
||||
if (err) return reject(err);
|
||||
resolve(value);
|
||||
}));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
|
||||
module.exports = {
|
||||
'amd': {
|
||||
appWriter: (base_out_path, script_base_path, out_path) => {
|
||||
// setup for requirejs
|
||||
const ui_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'app', 'ui'));
|
||||
return writeFile(out_path, `requirejs(["${ui_path}"], (ui) => {});`)
|
||||
.then(() => {
|
||||
console.log(`Please place RequireJS in ${path.join(script_base_path, 'require.js')}`);
|
||||
const require_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'require.js'))
|
||||
return [ require_path ];
|
||||
});
|
||||
},
|
||||
noCopyOverride: () => {},
|
||||
amd: {
|
||||
appWriter: (base_out_path, script_base_path, out_path) => {
|
||||
// setup for requirejs
|
||||
const ui_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'app', 'ui'));
|
||||
return writeFile(out_path, `requirejs(["${ui_path}"], (ui) => {});`)
|
||||
.then(() => {
|
||||
console.log(`Please place RequireJS in ${path.join(script_base_path, 'require.js')}`);
|
||||
const require_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'require.js'));
|
||||
return [require_path];
|
||||
});
|
||||
},
|
||||
'commonjs': {
|
||||
optionsOverride: (opts) => {
|
||||
// CommonJS supports properly shifting the default export to work as normal
|
||||
opts.plugins.unshift("add-module-exports");
|
||||
},
|
||||
appWriter: (base_out_path, script_base_path, out_path) => {
|
||||
const browserify = require('browserify');
|
||||
const b = browserify(path.join(script_base_path, 'app/ui.js'), {});
|
||||
return promisify(b.bundle).call(b)
|
||||
.then(buf => writeFile(out_path, buf))
|
||||
.then(() => []);
|
||||
},
|
||||
noCopyOverride: () => {},
|
||||
removeModules: true,
|
||||
noCopyOverride: () => {},
|
||||
},
|
||||
commonjs: {
|
||||
optionsOverride: (opts) => {
|
||||
// CommonJS supports properly shifting the default export to work as normal
|
||||
opts.plugins.unshift('add-module-exports');
|
||||
},
|
||||
'systemjs': {
|
||||
appWriter: (base_out_path, script_base_path, out_path) => {
|
||||
const ui_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'app', 'ui.js'));
|
||||
return writeFile(out_path, `SystemJS.import("${ui_path}");`)
|
||||
.then(() => {
|
||||
console.log(`Please place SystemJS in ${path.join(script_base_path, 'system-production.js')}`);
|
||||
// FIXME: Should probably be in the legacy directory
|
||||
const promise_path = path.relative(base_out_path,
|
||||
path.join(base_out_path, 'vendor', 'promise.js'))
|
||||
const systemjs_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'system-production.js'))
|
||||
return [ promise_path, systemjs_path ];
|
||||
});
|
||||
},
|
||||
noCopyOverride: (paths, no_copy_files) => {
|
||||
no_copy_files.delete(path.join(paths.vendor, 'promise.js'));
|
||||
},
|
||||
appWriter: (base_out_path, script_base_path, out_path) => {
|
||||
const browserify = require('browserify');
|
||||
const b = browserify(path.join(script_base_path, 'app/ui.js'), {});
|
||||
return promisify(b.bundle).call(b)
|
||||
.then(buf => writeFile(out_path, buf))
|
||||
.then(() => []);
|
||||
},
|
||||
'umd': {
|
||||
optionsOverride: (opts) => {
|
||||
// umd supports properly shifting the default export to work as normal
|
||||
opts.plugins.unshift("add-module-exports");
|
||||
},
|
||||
noCopyOverride: () => {},
|
||||
removeModules: true,
|
||||
},
|
||||
systemjs: {
|
||||
appWriter: (base_out_path, script_base_path, out_path) => {
|
||||
const ui_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'app', 'ui.js'));
|
||||
return writeFile(out_path, `SystemJS.import("${ui_path}");`)
|
||||
.then(() => {
|
||||
console.log(`Please place SystemJS in ${path.join(script_base_path, 'system-production.js')}`);
|
||||
// FIXME: Should probably be in the legacy directory
|
||||
const promise_path = path.relative(base_out_path,
|
||||
path.join(base_out_path, 'vendor', 'promise.js'));
|
||||
const systemjs_path = path.relative(base_out_path,
|
||||
path.join(script_base_path, 'system-production.js'));
|
||||
return [promise_path, systemjs_path];
|
||||
});
|
||||
},
|
||||
}
|
||||
noCopyOverride: (paths, no_copy_files) => {
|
||||
no_copy_files.delete(path.join(paths.vendor, 'promise.js'));
|
||||
},
|
||||
},
|
||||
umd: {
|
||||
optionsOverride: (opts) => {
|
||||
// umd supports properly shifting the default export to work as normal
|
||||
opts.plugins.unshift('add-module-exports');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue