
In input.js, a new keyboard handler was added to deal exclusively with the QEMU key event extension. '_onKeyPress()' signature was changed to allow the same method to treat both cases. The extension will only be enabled if the browser has support for the KeyboardEvent.code property. Changes in rfb.js: - added a new extension code, QEMUExtendedKeyEvent, value -258. - handleKeyPress now receives 'keyevent' instead of 'keysym' and 'down'. Both values are retrieved from keyevent as they were in the previous signature. This method now can send QEMU RFB extended key messages if the flag was set to 'true'. - tests/test.rfb.js were changed folowing the onKeyPress() signature change. - added a new function to send the QEMU extended key message. Signed-off-by: Daniel Henrique Barboza <danielhb@linux.vnet.ibm.com>
398 lines
14 KiB
JavaScript
398 lines
14 KiB
JavaScript
/*
|
|
* noVNC: HTML5 VNC client
|
|
* Copyright (C) 2012 Joel Martin
|
|
* Copyright (C) 2013 Samuel Mannehed for Cendio AB
|
|
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
|
|
*/
|
|
|
|
/*jslint browser: true, white: false */
|
|
/*global window, Util */
|
|
|
|
var Keyboard, Mouse;
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
//
|
|
// Keyboard event handler
|
|
//
|
|
|
|
Keyboard = function (defaults) {
|
|
this._keyDownList = []; // List of depressed keys
|
|
// (even if they are happy)
|
|
|
|
Util.set_defaults(this, defaults, {
|
|
'target': document,
|
|
'focused': true
|
|
});
|
|
|
|
// create the keyboard handler
|
|
this._handler = new KeyEventDecoder(kbdUtil.ModifierSync(),
|
|
VerifyCharModifier( /* jshint newcap: false */
|
|
TrackKeyState(
|
|
EscapeModifiers(this._handleRfbEvent.bind(this))
|
|
)
|
|
)
|
|
); /* jshint newcap: true */
|
|
|
|
// 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)
|
|
};
|
|
};
|
|
|
|
Keyboard.prototype = {
|
|
// private methods
|
|
|
|
_handleRfbEvent: function (e) {
|
|
if (this._onKeyPress) {
|
|
Util.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") +
|
|
", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")");
|
|
this._onKeyPress(e);
|
|
}
|
|
},
|
|
|
|
setQEMUVNCKeyboardHandler: function () {
|
|
this._handler = new QEMUKeyEventDecoder(kbdUtil.ModifierSync(),
|
|
TrackQEMUKeyState(
|
|
this._handleRfbEvent.bind(this)
|
|
)
|
|
);
|
|
},
|
|
|
|
_handleKeyDown: function (e) {
|
|
if (!this._focused) { return true; }
|
|
|
|
if (this._handler.keydown(e)) {
|
|
// Suppress bubbling/default actions
|
|
Util.stopEvent(e);
|
|
return false;
|
|
} else {
|
|
// Allow the event to bubble and become a keyPress event which
|
|
// will have the character code translated
|
|
return true;
|
|
}
|
|
},
|
|
|
|
_handleKeyPress: function (e) {
|
|
if (!this._focused) { return true; }
|
|
|
|
if (this._handler.keypress(e)) {
|
|
// Suppress bubbling/default actions
|
|
Util.stopEvent(e);
|
|
return false;
|
|
} else {
|
|
// Allow the event to bubble and become a keyPress event which
|
|
// will have the character code translated
|
|
return true;
|
|
}
|
|
},
|
|
|
|
_handleKeyUp: function (e) {
|
|
if (!this._focused) { return true; }
|
|
|
|
if (this._handler.keyup(e)) {
|
|
// Suppress bubbling/default actions
|
|
Util.stopEvent(e);
|
|
return false;
|
|
} else {
|
|
// Allow the event to bubble and become a keyPress event which
|
|
// will have the character code translated
|
|
return true;
|
|
}
|
|
},
|
|
|
|
_allKeysUp: function () {
|
|
Util.Debug(">> Keyboard.allKeysUp");
|
|
this._handler.releaseAll();
|
|
Util.Debug("<< Keyboard.allKeysUp");
|
|
},
|
|
|
|
// Public methods
|
|
|
|
grab: function () {
|
|
//Util.Debug(">> Keyboard.grab");
|
|
var c = this._target;
|
|
|
|
Util.addEvent(c, 'keydown', this._eventHandlers.keydown);
|
|
Util.addEvent(c, 'keyup', this._eventHandlers.keyup);
|
|
Util.addEvent(c, 'keypress', this._eventHandlers.keypress);
|
|
|
|
// Release (key up) if window loses focus
|
|
Util.addEvent(window, 'blur', this._eventHandlers.blur);
|
|
|
|
//Util.Debug("<< Keyboard.grab");
|
|
},
|
|
|
|
ungrab: function () {
|
|
//Util.Debug(">> Keyboard.ungrab");
|
|
var c = this._target;
|
|
|
|
Util.removeEvent(c, 'keydown', this._eventHandlers.keydown);
|
|
Util.removeEvent(c, 'keyup', this._eventHandlers.keyup);
|
|
Util.removeEvent(c, 'keypress', this._eventHandlers.keypress);
|
|
Util.removeEvent(window, 'blur', this._eventHandlers.blur);
|
|
|
|
// Release (key up) all keys that are in a down state
|
|
this._allKeysUp();
|
|
|
|
//Util.Debug(">> Keyboard.ungrab");
|
|
},
|
|
|
|
sync: function (e) {
|
|
this._handler.syncModifiers(e);
|
|
}
|
|
};
|
|
|
|
Util.make_properties(Keyboard, [
|
|
['target', 'wo', 'dom'], // DOM element that captures keyboard input
|
|
['focused', 'rw', 'bool'], // Capture and send key events
|
|
|
|
['onKeyPress', 'rw', 'func'] // Handler for key press/release
|
|
]);
|
|
|
|
//
|
|
// Mouse event handler
|
|
//
|
|
|
|
Mouse = function (defaults) {
|
|
this._mouseCaptured = false;
|
|
|
|
this._doubleClickTimer = null;
|
|
this._lastTouchPos = null;
|
|
|
|
// Configuration attributes
|
|
Util.set_defaults(this, defaults, {
|
|
'target': document,
|
|
'focused': true,
|
|
'scale': 1.0,
|
|
'touchButton': 1
|
|
});
|
|
|
|
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)
|
|
};
|
|
};
|
|
|
|
Mouse.prototype = {
|
|
// private methods
|
|
_captureMouse: function () {
|
|
// capturing the mouse ensures we get the mouseup event
|
|
if (this._target.setCapture) {
|
|
this._target.setCapture();
|
|
}
|
|
|
|
// some browsers give us mouseup events regardless,
|
|
// so if we never captured the mouse, we can disregard the event
|
|
this._mouseCaptured = true;
|
|
},
|
|
|
|
_releaseMouse: function () {
|
|
if (this._target.releaseCapture) {
|
|
this._target.releaseCapture();
|
|
}
|
|
this._mouseCaptured = false;
|
|
},
|
|
|
|
_resetDoubleClickTimer: function () {
|
|
this._doubleClickTimer = null;
|
|
},
|
|
|
|
_handleMouseButton: function (e, down) {
|
|
if (!this._focused) { return true; }
|
|
|
|
if (this._notify) {
|
|
this._notify(e);
|
|
}
|
|
|
|
var evt = (e ? e : window.event);
|
|
var pos = Util.getEventPosition(e, this._target, this._scale);
|
|
|
|
var 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.
|
|
|
|
var xs = this._lastTouchPos.x - pos.x;
|
|
var ys = this._lastTouchPos.y - pos.y;
|
|
var 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.
|
|
var 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 (evt.which) {
|
|
/* everything except IE */
|
|
bmask = 1 << evt.button;
|
|
} else {
|
|
/* IE including 9 */
|
|
bmask = (evt.button & 0x1) + // Left
|
|
(evt.button & 0x2) * 2 + // Right
|
|
(evt.button & 0x4) / 2; // Middle
|
|
}
|
|
|
|
if (this._onMouseButton) {
|
|
Util.Debug("onMouseButton " + (down ? "down" : "up") +
|
|
", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
|
|
this._onMouseButton(pos.x, pos.y, down, bmask);
|
|
}
|
|
Util.stopEvent(e);
|
|
return false;
|
|
},
|
|
|
|
_handleMouseDown: function (e) {
|
|
this._captureMouse();
|
|
this._handleMouseButton(e, 1);
|
|
},
|
|
|
|
_handleMouseUp: function (e) {
|
|
if (!this._mouseCaptured) { return; }
|
|
|
|
this._handleMouseButton(e, 0);
|
|
this._releaseMouse();
|
|
},
|
|
|
|
_handleMouseWheel: function (e) {
|
|
if (!this._focused) { return true; }
|
|
|
|
if (this._notify) {
|
|
this._notify(e);
|
|
}
|
|
|
|
var evt = (e ? e : window.event);
|
|
var pos = Util.getEventPosition(e, this._target, this._scale);
|
|
var wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
|
|
var bmask;
|
|
if (wheelData > 0) {
|
|
bmask = 1 << 3;
|
|
} else {
|
|
bmask = 1 << 4;
|
|
}
|
|
|
|
if (this._onMouseButton) {
|
|
this._onMouseButton(pos.x, pos.y, 1, bmask);
|
|
this._onMouseButton(pos.x, pos.y, 0, bmask);
|
|
}
|
|
Util.stopEvent(e);
|
|
return false;
|
|
},
|
|
|
|
_handleMouseMove: function (e) {
|
|
if (! this._focused) { return true; }
|
|
|
|
if (this._notify) {
|
|
this._notify(e);
|
|
}
|
|
|
|
var evt = (e ? e : window.event);
|
|
var pos = Util.getEventPosition(e, this._target, this._scale);
|
|
if (this._onMouseMove) {
|
|
this._onMouseMove(pos.x, pos.y);
|
|
}
|
|
Util.stopEvent(e);
|
|
return false;
|
|
},
|
|
|
|
_handleMouseDisable: function (e) {
|
|
if (!this._focused) { return true; }
|
|
|
|
var evt = (e ? e : window.event);
|
|
var pos = Util.getEventPosition(e, this._target, this._scale);
|
|
|
|
/* Stop propagation if inside canvas area */
|
|
if ((pos.realx >= 0) && (pos.realy >= 0) &&
|
|
(pos.realx < this._target.offsetWidth) &&
|
|
(pos.realy < this._target.offsetHeight)) {
|
|
//Util.Debug("mouse event disabled");
|
|
Util.stopEvent(e);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
|
|
// Public methods
|
|
grab: function () {
|
|
var c = this._target;
|
|
|
|
if ('ontouchstart' in document.documentElement) {
|
|
Util.addEvent(c, 'touchstart', this._eventHandlers.mousedown);
|
|
Util.addEvent(window, 'touchend', this._eventHandlers.mouseup);
|
|
Util.addEvent(c, 'touchend', this._eventHandlers.mouseup);
|
|
Util.addEvent(c, 'touchmove', this._eventHandlers.mousemove);
|
|
} else {
|
|
Util.addEvent(c, 'mousedown', this._eventHandlers.mousedown);
|
|
Util.addEvent(window, 'mouseup', this._eventHandlers.mouseup);
|
|
Util.addEvent(c, 'mouseup', this._eventHandlers.mouseup);
|
|
Util.addEvent(c, 'mousemove', this._eventHandlers.mousemove);
|
|
Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
|
|
this._eventHandlers.mousewheel);
|
|
}
|
|
|
|
/* Work around right and middle click browser behaviors */
|
|
Util.addEvent(document, 'click', this._eventHandlers.mousedisable);
|
|
Util.addEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable);
|
|
},
|
|
|
|
ungrab: function () {
|
|
var c = this._target;
|
|
|
|
if ('ontouchstart' in document.documentElement) {
|
|
Util.removeEvent(c, 'touchstart', this._eventHandlers.mousedown);
|
|
Util.removeEvent(window, 'touchend', this._eventHandlers.mouseup);
|
|
Util.removeEvent(c, 'touchend', this._eventHandlers.mouseup);
|
|
Util.removeEvent(c, 'touchmove', this._eventHandlers.mousemove);
|
|
} else {
|
|
Util.removeEvent(c, 'mousedown', this._eventHandlers.mousedown);
|
|
Util.removeEvent(window, 'mouseup', this._eventHandlers.mouseup);
|
|
Util.removeEvent(c, 'mouseup', this._eventHandlers.mouseup);
|
|
Util.removeEvent(c, 'mousemove', this._eventHandlers.mousemove);
|
|
Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
|
|
this._eventHandlers.mousewheel);
|
|
}
|
|
|
|
/* Work around right and middle click browser behaviors */
|
|
Util.removeEvent(document, 'click', this._eventHandlers.mousedisable);
|
|
Util.removeEvent(document.body, 'contextmenu', this._eventHandlers.mousedisable);
|
|
|
|
}
|
|
};
|
|
|
|
Util.make_properties(Mouse, [
|
|
['target', 'ro', 'dom'], // DOM element that captures mouse input
|
|
['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received
|
|
['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement
|
|
['scale', 'rw', 'float'], // Viewport scale factor 0.0 - 1.0
|
|
|
|
['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release
|
|
['onMouseMove', 'rw', 'func'], // Handler for mouse movement
|
|
['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
|
|
]);
|
|
})();
|