Use ECMAScript modules
This commit is contained in:
parent
e5369af219
commit
b239ca7160
12 changed files with 879 additions and 664 deletions
76
webdata/app/compat.mjs
Normal file
76
webdata/app/compat.mjs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Remote-Touchpad is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const fullscreenEnabled = () => {
|
||||
return (document.fullscreenEnabled ||
|
||||
document.webkitFullscreenEnabled ||
|
||||
false);
|
||||
};
|
||||
|
||||
export const requestFullscreen = (element, options) => {
|
||||
if (element.requestFullscreen) {
|
||||
element.requestFullscreen(options);
|
||||
} else if (element.webkitRequestFullscreen) {
|
||||
element.webkitRequestFullscreen(options);
|
||||
}
|
||||
};
|
||||
|
||||
export const exitFullscreen = () => {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
}
|
||||
};
|
||||
|
||||
export const fullscreenElement = () => {
|
||||
return (document.fullscreenElement ||
|
||||
document.webkitFullscreenElement ||
|
||||
null);
|
||||
};
|
||||
|
||||
export const addFullscreenchangeEventListener = (listener) => {
|
||||
if ("onfullscreenchange" in document) {
|
||||
document.addEventListener("fullscreenchange", listener);
|
||||
} else if ("onwebkitfullscreenchange" in document) {
|
||||
document.addEventListener("webkitfullscreenchange", listener);
|
||||
}
|
||||
};
|
||||
|
||||
export const requestPointerLock = (element) => {
|
||||
if (element.requestPointerLock) {
|
||||
element.requestPointerLock();
|
||||
}
|
||||
};
|
||||
|
||||
export const exitPointerLock = () => {
|
||||
if (document.exitPointerLock) {
|
||||
document.exitPointerLock();
|
||||
}
|
||||
};
|
||||
|
||||
export const pointerLockElement = () => {
|
||||
return document.pointerLockElement || null;
|
||||
};
|
||||
|
||||
export const addPointerlockchangeEventListener = (listener) => {
|
||||
if ("onpointerlockchange" in document) {
|
||||
document.addEventListener("pointerlockchange", listener);
|
||||
}
|
||||
};
|
||||
121
webdata/app/inputcontroller.mjs
Normal file
121
webdata/app/inputcontroller.mjs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Remote-Touchpad is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const POINTER_BUTTON_LEFT = 0;
|
||||
export const POINTER_BUTTON_RIGHT = 1;
|
||||
export const POINTER_BUTTON_MIDDLE = 2;
|
||||
|
||||
export const KEY_VOLUME_MUTE = 0;
|
||||
export const KEY_VOLUME_DOWN = 1;
|
||||
export const KEY_VOLUME_UP = 2;
|
||||
export const KEY_MEDIA_PLAY_PAUSE = 3;
|
||||
export const KEY_MEDIA_PREV_TRACK = 4;
|
||||
export const KEY_MEDIA_NEXT_TRACK = 5;
|
||||
export const KEY_BROWSER_BACK = 6;
|
||||
export const KEY_BROWSER_FORWARD = 7;
|
||||
export const KEY_SUPER = 8;
|
||||
export const KEY_LEFT = 9;
|
||||
export const KEY_RIGHT = 10;
|
||||
export const KEY_UP = 11;
|
||||
export const KEY_DOWN = 12;
|
||||
export const KEY_HOME = 13;
|
||||
export const KEY_END = 14;
|
||||
export const KEY_BACK_SPACE = 15;
|
||||
export const KEY_DELETE = 16;
|
||||
export const KEY_RETURN = 17;
|
||||
|
||||
export default class InputController {
|
||||
#updateRate = 0;
|
||||
|
||||
#moveXSum = 0;
|
||||
#moveYSum = 0;
|
||||
#scrollHSum = 0;
|
||||
#scrollVSum = 0;
|
||||
#scrolling = false;
|
||||
#scrollFinish = false;
|
||||
#updateTimeoutActive = false;
|
||||
#socket;
|
||||
|
||||
constructor(socket) {
|
||||
this.#socket = socket;
|
||||
}
|
||||
|
||||
configure(config) {
|
||||
this.#updateRate = config.updateRate;
|
||||
}
|
||||
|
||||
#startUpdate(fromTimeout) {
|
||||
if (this.#updateTimeoutActive && !fromTimeout) {
|
||||
return;
|
||||
}
|
||||
this.#updateTimeoutActive = false;
|
||||
let finished = true;
|
||||
const xInt = Math.trunc(this.#moveXSum);
|
||||
const yInt = Math.trunc(this.#moveYSum);
|
||||
if (xInt != 0 || yInt != 0) {
|
||||
this.#socket.send("m" + xInt + ";" + yInt);
|
||||
this.#moveXSum -= xInt;
|
||||
this.#moveYSum -= yInt;
|
||||
finished = false;
|
||||
}
|
||||
const hInt = Math.trunc(this.#scrollHSum);
|
||||
const vInt = Math.trunc(this.#scrollVSum);
|
||||
if (hInt != 0 || vInt != 0) {
|
||||
this.#socket.send((this.#scrollFinish ? "S" : "s") + hInt + ";" + vInt);
|
||||
this.#scrollHSum -= hInt;
|
||||
this.#scrollVSum -= vInt;
|
||||
this.#scrolling = !this.#scrollFinish;
|
||||
this.#scrollFinish = false;
|
||||
finished = false;
|
||||
} else if (this.#scrollFinish && this.#scrolling) {
|
||||
this.#socket.send("S");
|
||||
this.#scrolling = false;
|
||||
this.#scrollFinish = false;
|
||||
}
|
||||
this.#updateTimeoutActive = !finished && this.#updateRate > 0;
|
||||
if (this.#updateTimeoutActive) {
|
||||
setTimeout(this.#startUpdate.bind(this), 1000 / this.#updateRate, true);
|
||||
}
|
||||
}
|
||||
|
||||
pointerMove(deltaX, deltaY) {
|
||||
this.#moveXSum += deltaX;
|
||||
this.#moveYSum += deltaY;
|
||||
this.#startUpdate();
|
||||
}
|
||||
|
||||
pointerScroll(deltaHorizontal, deltaVertical, finish) {
|
||||
this.#scrollHSum += deltaHorizontal;
|
||||
this.#scrollVSum += deltaVertical;
|
||||
this.#scrollFinish |= finish;
|
||||
this.#startUpdate();
|
||||
};
|
||||
|
||||
pointerButton(button, press) {
|
||||
this.#socket.send("b" + button + ";" + (press ? 1 : 0));
|
||||
}
|
||||
|
||||
keyboardKey(key) {
|
||||
this.#socket.send("k" + key);
|
||||
}
|
||||
|
||||
keyboardText(text) {
|
||||
this.#socket.send("t" + text);
|
||||
}
|
||||
}
|
||||
74
webdata/app/keyboard.mjs
Normal file
74
webdata/app/keyboard.mjs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Remote-Touchpad is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
KEY_SUPER, KEY_BACK_SPACE, KEY_RETURN, KEY_DELETE, KEY_HOME, KEY_END,
|
||||
KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN,
|
||||
} from "./inputcontroller.mjs";
|
||||
|
||||
export default class Keyboard {
|
||||
#inputController;
|
||||
#checkAllowedCallback;
|
||||
|
||||
constructor(inputController, checkAllowedCallback) {
|
||||
this.#inputController = inputController;
|
||||
this.#checkAllowedCallback = checkAllowedCallback;
|
||||
document.addEventListener("keydown", this.#handleKeydown.bind(this));
|
||||
}
|
||||
|
||||
configure() {}
|
||||
|
||||
#handleKeydown(event) {
|
||||
if (!this.#checkAllowedCallback() ||
|
||||
event.ctrlKey || event.altKey || event.isComposing) {
|
||||
return;
|
||||
}
|
||||
let key = null;
|
||||
if (event.key == "OS" || event.key == "Super" || event.key == "Meta") {
|
||||
key = KEY_SUPER;
|
||||
} else if (event.key == "Backspace") {
|
||||
key = KEY_BACK_SPACE;
|
||||
} else if (event.key == "Enter") {
|
||||
key = KEY_RETURN;
|
||||
} else if (event.key == "Delete") {
|
||||
key = KEY_DELETE;
|
||||
} else if (event.key == "Home") {
|
||||
key = KEY_HOME;
|
||||
} else if (event.key == "End") {
|
||||
key = KEY_END;
|
||||
} else if (event.key == "Left" || event.key == "ArrowLeft") {
|
||||
key = KEY_LEFT;
|
||||
} else if (event.key == "Right" || event.key == "ArrowRight") {
|
||||
key = KEY_RIGHT;
|
||||
} else if (event.key == "Up" || event.key == "ArrowUp") {
|
||||
key = KEY_UP;
|
||||
} else if (event.key == "Down" || event.key == "ArrowDown") {
|
||||
key = KEY_DOWN;
|
||||
}
|
||||
if (key != null) {
|
||||
if (!event.shiftKey) {
|
||||
event.preventDefault();
|
||||
this.#inputController.keyboardKey(key);
|
||||
}
|
||||
} else if (event.key.length == 1) {
|
||||
event.preventDefault();
|
||||
this.#inputController.keyboardText(event.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
webdata/app/main.mjs
Normal file
53
webdata/app/main.mjs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Remote-Touchpad is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import InputController, * as inputcontrollerModule from "./inputcontroller.mjs";
|
||||
import Socket from "./socket.mjs";
|
||||
import UI from "./ui.mjs";
|
||||
|
||||
const url = new URL("ws", location.href);
|
||||
url.protocol = url.protocol == "http:" ? "ws:" : "wss:";
|
||||
|
||||
const socket = new Socket(url, window.location.hash.substr(1));
|
||||
const inputController = new InputController(socket);
|
||||
const ui = new UI(inputController);
|
||||
|
||||
socket.addEventListener("config", (event) => {
|
||||
const config = event.detail;
|
||||
inputController.configure(config);
|
||||
ui.configure(config);
|
||||
});
|
||||
|
||||
socket.addEventListener("close", () => {
|
||||
ui.close();
|
||||
});
|
||||
|
||||
window.app = {
|
||||
key: inputController.keyboardKey.bind(inputController),
|
||||
text: inputController.keyboardText.bind(inputController),
|
||||
toggleFullscreen: ui.toggleFullscreen.bind(ui),
|
||||
showTextInput: ui.showTextInput.bind(ui),
|
||||
showKeys: ui.showKeys.bind(ui),
|
||||
setKeysPage: ui.setKeysPage.bind(ui),
|
||||
};
|
||||
for (const name in inputcontrollerModule) {
|
||||
if (name.startsWith("KEY_")) {
|
||||
window.app[name] = inputcontrollerModule[name];
|
||||
}
|
||||
}
|
||||
74
webdata/app/mouse.mjs
Normal file
74
webdata/app/mouse.mjs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Remote-Touchpad is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export default class Mouse {
|
||||
#moveSpeed = 1;
|
||||
#scrollSpeed = 1;
|
||||
|
||||
#buttons = 0;
|
||||
#inputController;
|
||||
|
||||
constructor (inputController, element) {
|
||||
this.#inputController = inputController;
|
||||
for (const type of ["touchstart", "touchend", "touchcancel", "touchmove"]) {
|
||||
element.addEventListener(type, (event) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
element.addEventListener("mousedown", this.#handleMouseDownAndUp.bind(this));
|
||||
element.addEventListener("mouseup", this.#handleMouseDownAndUp.bind(this));
|
||||
element.addEventListener("mousemove", this.#handleMousemove.bind(this));
|
||||
element.addEventListener("wheel", this.#handleWheel.bind(this));
|
||||
}
|
||||
|
||||
configure(config) {
|
||||
this.#moveSpeed = config.mouseMoveSpeed;
|
||||
this.#scrollSpeed = config.mouseScrollSpeed;
|
||||
}
|
||||
|
||||
#updateButtons(newButtons) {
|
||||
for (let button = 0; button < 3; button += 1) {
|
||||
const flag = 1 << button;
|
||||
if ((newButtons&flag) != (this.#buttons & flag)) {
|
||||
this.#inputController.pointerButton(button, newButtons & flag);
|
||||
}
|
||||
}
|
||||
this.#buttons = newButtons;
|
||||
}
|
||||
|
||||
#handleMouseDownAndUp(event) {
|
||||
this.#updateButtons(event.buttons);
|
||||
}
|
||||
|
||||
#handleMousemove(event) {
|
||||
this.#inputController.pointerMove(
|
||||
event.movementX * this.#moveSpeed, event.movementY * this.#moveSpeed);
|
||||
}
|
||||
|
||||
#handleWheel(event) {
|
||||
if (event.deltaMode == WheelEvent.DOM_DELTA_PIXEL) {
|
||||
this.#inputController.pointerScroll(
|
||||
event.deltaX * this.#scrollSpeed, event.deltaY * this.#scrollSpeed, true);
|
||||
} else if (event.deltaMode == WheelEvent.DOM_DELTA_LINE) {
|
||||
this.#inputController.pointerScroll(
|
||||
event.deltaX * 20 * this.#scrollSpeed, event.deltaY * 20 * this.#scrollSpeed,
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
webdata/app/sha256.mjs
Normal file
9
webdata/app/sha256.mjs
Normal file
File diff suppressed because one or more lines are too long
66
webdata/app/socket.mjs
Normal file
66
webdata/app/socket.mjs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Remote-Touchpad is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import jsSHA from "./sha256.mjs"
|
||||
|
||||
const challengeResponse = (message, secret) => {
|
||||
const shaObj = new jsSHA("SHA-256", "TEXT");
|
||||
shaObj.setHMACKey(message, "TEXT");
|
||||
shaObj.update(secret);
|
||||
return btoa(shaObj.getHMAC("BYTES"));
|
||||
};
|
||||
|
||||
export default class Socket extends EventTarget {
|
||||
#secret;
|
||||
#authenticated;
|
||||
#ws;
|
||||
|
||||
constructor(url, secret) {
|
||||
super();
|
||||
this.#secret = secret;
|
||||
this.#authenticated = false;
|
||||
this.#ws = new WebSocket(url);
|
||||
this.#ws.addEventListener("message", this.#handle_ws_message.bind(this));
|
||||
this.#ws.addEventListener("close", this.#handle_ws_close.bind(this));
|
||||
}
|
||||
|
||||
#handle_ws_message(event) {
|
||||
if (!this.#authenticated) {
|
||||
this.#ws.send(challengeResponse(event.data, this.#secret));
|
||||
this.#authenticated = true;
|
||||
return;
|
||||
}
|
||||
let config;
|
||||
try {
|
||||
config = JSON.parse(event.data);
|
||||
} catch (e) {
|
||||
this.#ws.close();
|
||||
throw (e);
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent("config", {detail: config}));
|
||||
}
|
||||
|
||||
#handle_ws_close() {
|
||||
this.dispatchEvent(new CustomEvent("close"));
|
||||
}
|
||||
|
||||
send(message) {
|
||||
this.#ws.send(message);
|
||||
}
|
||||
}
|
||||
232
webdata/app/touchpad.mjs
Normal file
232
webdata/app/touchpad.mjs
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Remote-Touchpad is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {POINTER_BUTTON_LEFT, POINTER_BUTTON_MIDDLE, POINTER_BUTTON_RIGHT} from "./inputcontroller.mjs";
|
||||
|
||||
// [1 Touch, 2 Touches, 3 Touches] (as pixel)
|
||||
const TOUCH_MOVE_THRESHOLD = [10, 15, 15];
|
||||
// Max time between consecutive touches for clicking or dragging (as milliseconds)
|
||||
const TOUCH_TIMEOUT = 250;
|
||||
// [[pixel/second, multiplicator], ...]
|
||||
const POINTER_ACCELERATION = [
|
||||
[0, 0],
|
||||
[87, 1],
|
||||
[173, 1],
|
||||
[553, 2],
|
||||
];
|
||||
|
||||
const copyTouch = (touch, timeStamp) => ({
|
||||
identifier: touch.identifier,
|
||||
pageX: touch.pageX,
|
||||
pageXStart: touch.pageX,
|
||||
pageY: touch.pageY,
|
||||
pageYStart: touch.pageY,
|
||||
timeStamp: timeStamp,
|
||||
});
|
||||
|
||||
const calculateAccelerationMult = (speed) => {
|
||||
for (let i = 0; i < POINTER_ACCELERATION.length; i += 1) {
|
||||
const s2 = POINTER_ACCELERATION[i][0];
|
||||
const a2 = POINTER_ACCELERATION[i][1];
|
||||
if (s2 <= speed) {
|
||||
continue;
|
||||
}
|
||||
if (i == 0) {
|
||||
return a2;
|
||||
}
|
||||
const s1 = POINTER_ACCELERATION[i - 1][0];
|
||||
const a1 = POINTER_ACCELERATION[i - 1][1];
|
||||
return ((speed - s1) / (s2 - s1)) * (a2 - a1) + a1;
|
||||
}
|
||||
if (POINTER_ACCELERATION.length > 0) {
|
||||
return POINTER_ACCELERATION[POINTER_ACCELERATION.length - 1][1];
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
|
||||
export default class Touchpad {
|
||||
#moveSpeed = 1;
|
||||
#scrollSpeed = 1;
|
||||
|
||||
#moved = false;
|
||||
#startTimeStamp = 0;
|
||||
#lastEndTimeStamp = 0;
|
||||
#releasedCount = 0;
|
||||
#ongoingTouches = [];
|
||||
#dragging = false;
|
||||
#draggingTimeout = null;
|
||||
#inputController;
|
||||
#checkAllowedCallback;
|
||||
|
||||
constructor(inputController, checkAllowedCallback) {
|
||||
this.#inputController = inputController;
|
||||
this.#checkAllowedCallback = checkAllowedCallback;
|
||||
document.addEventListener("touchstart", this.#handleTouchstart.bind(this));
|
||||
document.addEventListener("touchend", this.#handleTouchend.bind(this));
|
||||
document.addEventListener("touchcancel", this.#handleTouchend.bind(this));
|
||||
document.addEventListener("touchmove", this.#handleTouchmove.bind(this));
|
||||
}
|
||||
|
||||
configure(config) {
|
||||
this.#moveSpeed = config.moveSpeed;
|
||||
this.#scrollSpeed = config.scrollSpeed;
|
||||
}
|
||||
|
||||
#ongoingTouchIndexById(idToFind) {
|
||||
for (let i = 0; i < this.#ongoingTouches.length; i += 1) {
|
||||
if (this.#ongoingTouches[i].identifier == idToFind) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#handleDraggingTimeout() {
|
||||
this.#draggingTimeout = null;
|
||||
this.#inputController.pointerButton(POINTER_BUTTON_LEFT, false);
|
||||
}
|
||||
|
||||
#handleTouchstart(event) {
|
||||
// Might get called multiple times for the same touches
|
||||
if (this.#ongoingTouches.length == 0) {
|
||||
this.#startTimeStamp = event.timeStamp;
|
||||
this.#moved = false;
|
||||
}
|
||||
const touches = event.changedTouches;
|
||||
let foundTouch = false;
|
||||
for (let i = 0; i < touches.length; i += 1) {
|
||||
if (this.#ongoingTouches.length == 0 &&
|
||||
!this.#checkAllowedCallback(touches[i].target)) {
|
||||
continue;
|
||||
}
|
||||
foundTouch = true;
|
||||
const touch = copyTouch(touches[i], event.timeStamp);
|
||||
const idx = this.#ongoingTouchIndexById(touch.identifier);
|
||||
if (idx < 0) {
|
||||
this.#ongoingTouches.push(touch);
|
||||
} else {
|
||||
this.#ongoingTouches[idx] = touch;
|
||||
}
|
||||
}
|
||||
if (!foundTouch) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
this.#lastEndTimeStamp = 0;
|
||||
if (this.#draggingTimeout != null) {
|
||||
clearTimeout(this.#draggingTimeout);
|
||||
this.#draggingTimeout = null;
|
||||
this.#dragging = true;
|
||||
}
|
||||
this.#inputController.pointerScroll(0, 0, true);
|
||||
}
|
||||
|
||||
#handleTouchend(event) {
|
||||
const touches = event.changedTouches;
|
||||
let foundTouch = false;
|
||||
for (let i = 0; i < touches.length; i += 1) {
|
||||
const idx = this.#ongoingTouchIndexById(touches[i].identifier);
|
||||
if (idx < 0) {
|
||||
continue;
|
||||
}
|
||||
foundTouch = true;
|
||||
this.#ongoingTouches.splice(idx, 1);
|
||||
this.#releasedCount += 1;
|
||||
}
|
||||
if (!foundTouch) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
this.#lastEndTimeStamp = event.timeStamp;
|
||||
this.#inputController.pointerScroll(0, 0, true);
|
||||
if (this.#releasedCount > TOUCH_MOVE_THRESHOLD.length) {
|
||||
this.#moved = true;
|
||||
}
|
||||
if (this.#ongoingTouches.length == 0 && this.#releasedCount >= 1) {
|
||||
if (this.#dragging) {
|
||||
this.#dragging = false;
|
||||
this.#inputController.pointerButton(POINTER_BUTTON_LEFT, false);
|
||||
}
|
||||
if (!this.#moved && event.timeStamp - this.#startTimeStamp < TOUCH_TIMEOUT) {
|
||||
let button = 0;
|
||||
if (this.#releasedCount == 1) {
|
||||
button = POINTER_BUTTON_LEFT;
|
||||
} else if (this.#releasedCount == 2) {
|
||||
button = POINTER_BUTTON_RIGHT;
|
||||
} else if (this.#releasedCount == 3) {
|
||||
button = POINTER_BUTTON_MIDDLE;
|
||||
}
|
||||
this.#inputController.pointerButton(button, true);
|
||||
if (button == POINTER_BUTTON_LEFT) {
|
||||
this.#draggingTimeout = setTimeout(
|
||||
this.#handleDraggingTimeout.bind(this), TOUCH_TIMEOUT);
|
||||
} else {
|
||||
this.#inputController.pointerButton(button, false);
|
||||
}
|
||||
}
|
||||
this.#releasedCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#handleTouchmove(event) {
|
||||
let sumX = 0;
|
||||
let sumY = 0;
|
||||
const touches = event.changedTouches;
|
||||
let foundTouch = false;
|
||||
for (let i = 0; i < touches.length; i += 1) {
|
||||
const idx = this.#ongoingTouchIndexById(touches[i].identifier);
|
||||
if (idx < 0) {
|
||||
continue;
|
||||
}
|
||||
foundTouch = true;
|
||||
if (!this.#moved) {
|
||||
const dist = Math.sqrt(
|
||||
Math.pow(touches[i].pageX - this.#ongoingTouches[idx].pageXStart, 2) +
|
||||
Math.pow(touches[i].pageY - this.#ongoingTouches[idx].pageYStart, 2)
|
||||
);
|
||||
if (this.#ongoingTouches.length > TOUCH_MOVE_THRESHOLD.length ||
|
||||
dist > TOUCH_MOVE_THRESHOLD[this.#ongoingTouches.length - 1] ||
|
||||
event.timeStamp - this.#startTimeStamp >= TOUCH_TIMEOUT) {
|
||||
this.#moved = true;
|
||||
}
|
||||
}
|
||||
const dx = touches[i].pageX - this.#ongoingTouches[idx].pageX;
|
||||
const dy = touches[i].pageY - this.#ongoingTouches[idx].pageY;
|
||||
const timeDelta = event.timeStamp - this.#ongoingTouches[idx].timeStamp;
|
||||
sumX += dx * calculateAccelerationMult(Math.abs(dx) / timeDelta * 1000);
|
||||
sumY += dy * calculateAccelerationMult(Math.abs(dy) / timeDelta * 1000);
|
||||
this.#ongoingTouches[idx].pageX = touches[i].pageX;
|
||||
this.#ongoingTouches[idx].pageY = touches[i].pageY;
|
||||
this.#ongoingTouches[idx].timeStamp = event.timeStamp;
|
||||
}
|
||||
if (!foundTouch) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
if (this.#moved && event.timeStamp - this.#lastEndTimeStamp >= TOUCH_TIMEOUT) {
|
||||
if (this.#ongoingTouches.length == 1 || this.#dragging) {
|
||||
this.#inputController.pointerMove(
|
||||
sumX * this.#moveSpeed, sumY * this.#moveSpeed);
|
||||
} else if (this.#ongoingTouches.length == 2) {
|
||||
this.#inputController.pointerScroll(
|
||||
-sumX * this.#scrollSpeed, -sumY * this.#scrollSpeed, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
173
webdata/app/ui.mjs
Normal file
173
webdata/app/ui.mjs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019, 2023 Unrud <unrud@outlook.com>
|
||||
*
|
||||
* This file is part of Remote-Touchpad.
|
||||
*
|
||||
* Remote-Touchpad is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Remote-Touchpad is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Keyboard from "./keyboard.mjs";
|
||||
import Mouse from "./mouse.mjs";
|
||||
import Touchpad from "./touchpad.mjs";
|
||||
import * as compat from "./compat.mjs";
|
||||
|
||||
const scenes = document.querySelectorAll("body > .scene");
|
||||
const openingScene = document.getElementById("opening");
|
||||
const closedScene = document.getElementById("closed");
|
||||
const padScene = document.getElementById("pad");
|
||||
const keysScene = document.getElementById("keys");
|
||||
const keysPages = keysScene.querySelectorAll(":scope > .page");
|
||||
const textInputScene = document.getElementById("text-input");
|
||||
const textInput = textInputScene.querySelector("textarea");
|
||||
const mouseScene = document.getElementById("mouse");
|
||||
const sendText = document.getElementById("send-text");
|
||||
|
||||
export default class UI {
|
||||
#activeScene = null;
|
||||
#keysActiveName = "";
|
||||
#ready = false;
|
||||
#closed = false;
|
||||
#inputController;
|
||||
#mouse;
|
||||
#keyboard;
|
||||
#touchpad;
|
||||
|
||||
constructor(inputController) {
|
||||
this.#inputController = inputController;
|
||||
this.#mouse = new Mouse(inputController, mouseScene);
|
||||
this.#keyboard = new Keyboard(inputController,
|
||||
() => this.#activeScene?.classList.contains("keyboard-input"));
|
||||
this.#touchpad = new Touchpad(inputController,
|
||||
(target) => target.classList.contains("touch-input"));
|
||||
document.addEventListener("mousedown", this.#handleMousedown.bind(this));
|
||||
textInput.addEventListener("input", this.#handleTextInput.bind(this));
|
||||
sendText.addEventListener("click", this.#handleSendText.bind(this));
|
||||
window.addEventListener("popstate", () => { this.#update(); });
|
||||
compat.addFullscreenchangeEventListener(() => { this.#update(); });
|
||||
compat.addPointerlockchangeEventListener(() => { this.#update(); });
|
||||
this.#update();
|
||||
}
|
||||
|
||||
configure(config) {
|
||||
this.#mouse.configure(config);
|
||||
this.#keyboard.configure(config);
|
||||
this.#touchpad.configure(config);
|
||||
if (!this.#closed) {
|
||||
this.#ready = true;
|
||||
}
|
||||
this.#update();
|
||||
}
|
||||
|
||||
close() {
|
||||
this.#ready = false;
|
||||
this.#closed = true;
|
||||
this.#update();
|
||||
}
|
||||
|
||||
#handleMousedown(event) {
|
||||
if (this.#activeScene != mouseScene && event.buttons == 1 &&
|
||||
event.target.classList.contains("mouse-input")) {
|
||||
compat.requestPointerLock(mouseScene);
|
||||
}
|
||||
}
|
||||
|
||||
#handleTextInput() {
|
||||
sessionStorage.setItem("text-input", textInput.value);
|
||||
}
|
||||
|
||||
#handleSendText() {
|
||||
if (textInput.value) {
|
||||
// normalize line endings
|
||||
const text = textInput.value.replace(/\r\n?/g, "\n");
|
||||
this.#inputController.keyboardText(text);
|
||||
textInput.value = "";
|
||||
this.#handleTextInput();
|
||||
}
|
||||
history.back();
|
||||
}
|
||||
|
||||
#showScene(scene) {
|
||||
this.#activeScene = scene;
|
||||
if (compat.fullscreenElement() && !scene.classList.contains("allow-fullscreen")) {
|
||||
compat.exitFullscreen();
|
||||
}
|
||||
if (compat.pointerLockElement() && this.#activeScene != mouseScene) {
|
||||
compat.exitPointerLock();
|
||||
}
|
||||
textInput.value = "";
|
||||
for (const otherScene of scenes) {
|
||||
otherScene.classList.toggle("hidden", otherScene != scene);
|
||||
}
|
||||
}
|
||||
|
||||
setKeysPage(index, relative = false) {
|
||||
if (relative) {
|
||||
for (let i = 0; i < keysPages.length && keysPages[i].classList.contains("hidden");
|
||||
i += 1, index += 1);
|
||||
}
|
||||
index = ((index % keysPages.length) + keysPages.length) % keysPages.length;
|
||||
sessionStorage.setItem(this.#keysActiveName, index);
|
||||
for (let i = 0; i < keysPages.length; i += 1) {
|
||||
keysPages[i].classList.toggle("hidden", i != index);
|
||||
}
|
||||
}
|
||||
|
||||
showKeys(name = "", defaultPageIndex = 0) {
|
||||
this.#showScene(keysScene);
|
||||
this.#keysActiveName = "keys" + (name ? ":" + name : "");
|
||||
let pageIndex = parseInt(sessionStorage.getItem(this.#keysActiveName));
|
||||
if (isNaN(pageIndex)) {
|
||||
pageIndex = defaultPageIndex;
|
||||
}
|
||||
this.setKeysPage(pageIndex);
|
||||
if (history.state != this.#keysActiveName) {
|
||||
history.pushState(this.#keysActiveName, "");
|
||||
}
|
||||
}
|
||||
|
||||
showTextInput() {
|
||||
this.#showScene(textInputScene);
|
||||
textInput.value = sessionStorage.getItem("text-input") || "";
|
||||
textInput.focus();
|
||||
if (history.state != "text-input") {
|
||||
history.pushState("text-input", "");
|
||||
}
|
||||
}
|
||||
|
||||
toggleFullscreen() {
|
||||
if (compat.fullscreenElement()) {
|
||||
compat.exitFullscreen();
|
||||
} else {
|
||||
compat.requestFullscreen(document.documentElement, {navigationUI: "hide"});
|
||||
}
|
||||
}
|
||||
|
||||
#update() {
|
||||
const fullscreenEnabled = compat.fullscreenEnabled();
|
||||
for (const element of document.querySelectorAll(".visble-if-fullscreen-enabled")) {
|
||||
element.classList.toggle("hidden", !fullscreenEnabled);
|
||||
}
|
||||
if (!this.#ready) {
|
||||
this.#showScene(this.#closed ? closedScene : openingScene);
|
||||
} else if (compat.pointerLockElement()) {
|
||||
this.#showScene(mouseScene);
|
||||
} else if ((history.state || "").split(":")[0] == "keys") {
|
||||
this.showKeys(history.state.substr("keys:".length));
|
||||
} else if (history.state == "text-input") {
|
||||
this.showTextInput();
|
||||
} else {
|
||||
this.#showScene(padScene);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue