You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

remote.html 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Mediator - Remote</title>
  6. <link rel="stylesheet" href="style.css">
  7. <link rel="icon" type="image/png" href="favicon.png">
  8. <style>
  9. body {
  10. margin: 0px;
  11. }
  12. #cursor {
  13. position: absolute;
  14. left: 0px;
  15. top: 0px;
  16. pointer-events: none;
  17. }
  18. #screencast-container {
  19. max-height: calc(100% - 200px);
  20. height: 100%;
  21. text-align: center;
  22. }
  23. #screencast-container {
  24. background: black;
  25. }
  26. #screencast {
  27. max-height: calc(100vh - 200px);
  28. max-width: 100%;
  29. }
  30. </style>
  31. </head>
  32. <body>
  33. <img id="cursor" src="cursor.png" width=25>
  34. <div id="screencast-container">
  35. <img id="screencast" src="/api/remote/screencast">
  36. </div>
  37. <form id="text-form">
  38. <input id="text" name="text" type="text">
  39. </form>
  40. <script src="util.js"></script>
  41. <script>
  42. let cursorEl = document.getElementById("cursor");
  43. let screencastEl = document.getElementById("screencast");
  44. let screencastContainerEl = document.getElementById("screencast-container");
  45. let textFormEl = document.getElementById("text-form");
  46. let textEl = document.getElementById("text");
  47. let screencastSrc = screencastEl.src;
  48. window.addEventListener("blur", () => {
  49. let canvas = document.createElement("canvas");
  50. canvas.width = screencastEl.width;
  51. canvas.height = screencastEl.height;
  52. let ctx = canvas.getContext("2d");
  53. ctx.drawImage(screencastEl, 0, 0, canvas.width, canvas.height);
  54. screencastEl.src = canvas.toDataURL();
  55. });
  56. window.addEventListener("focus", () => {
  57. screencastEl.src = screencastSrc;
  58. });
  59. window.addEventListener("keydown", evt => {
  60. let handled = true;
  61. console.log("key:", evt.key);
  62. if (evt.key == " ") {
  63. api("POST", "remote/keyboard-key", {key: "space"});
  64. } else if (evt.key == "Enter" && document.activeElement.tagName != "INPUT") {
  65. api("POST", "remote/keyboard-key", {key: "enter"});
  66. } else if (evt.key == "ArrowUp") {
  67. api("POST", "remote/keyboard-key", {key: "up"});
  68. } else if (evt.key == "ArrowDown") {
  69. api("POST", "remote/keyboard-key", {key: "down"});
  70. } else if (evt.key == "ArrowLeft") {
  71. api("POST", "remote/keyboard-key", {key: "left"});
  72. } else if (evt.key == "ArrowRight") {
  73. api("POST", "remote/keyboard-key", {key: "right"});
  74. } else if (evt.key == "Escape") {
  75. api("POST", "remote/keyboard-key", {key: "escape"});
  76. } else {
  77. handled = false;
  78. if (evt.key.length == 1 && document.activeElement.tagName != "INPUT") {
  79. textEl.focus();
  80. }
  81. }
  82. if (handled) {
  83. evt.preventDefault();
  84. }
  85. });
  86. function updateCursor(mousePos, screenSize) {
  87. let fracX = mousePos.x / screenSize.width;
  88. let fracY = mousePos.y / screenSize.height;
  89. let left = fracX * screencastEl.offsetWidth + screencastEl.offsetLeft;
  90. let top = fracY * screencastEl.offsetHeight + screencastEl.offsetTop;
  91. cursorEl.style.left = left + "px";
  92. cursorEl.style.top = top + "px";
  93. }
  94. let scrollDist = {x: 0, y: 0};
  95. function onScroll(delta) {
  96. // Scroll is inverted
  97. delta.x = -delta.x;
  98. delta.y = -delta.y;
  99. if (Math.abs(delta.x) > Math.abs(delta.y)) {
  100. scrollDist.x += delta.x;
  101. let distX = roundToZero(scrollDist.x);
  102. if (distX != 0) {
  103. api("POST", "remote/scroll", {x: distX, y: 0});
  104. scrollDist.x -= distX;
  105. }
  106. } else {
  107. scrollDist.y += delta.y;
  108. let distY = roundToZero(scrollDist.y);
  109. if (distY != 0) {
  110. api("POST", "remote/scroll", {x: 0, y: distY});
  111. scrollDist.y -= distY;
  112. }
  113. }
  114. }
  115. function moveDelta(mousePos, screenSize, delta) {
  116. mousePos.x += delta.x;
  117. if (mousePos.x >= screenSize.width) {
  118. mousePos.x = screenSize.width - 1;
  119. } else if (mousePos.x < 0) {
  120. mousePos.x = 0;
  121. }
  122. mousePos.y += delta.y;
  123. if (mousePos.y >= screenSize.height) {
  124. mousePos.y = screenSize.height - 1;
  125. } else if (mousePos.y < 0) {
  126. mousePos.y = 0;
  127. }
  128. updateCursor(mousePos, screenSize);
  129. api("PUT", "remote/mouse-pos", mousePos);
  130. }
  131. function signPow(num, pow) {
  132. if (num >= 0) {
  133. return Math.pow(num, pow);
  134. } else {
  135. return -Math.pow(-num, pow);
  136. }
  137. }
  138. function roundToZero(num) {
  139. if (num >= 0) {
  140. return Math.floor(num);
  141. } else {
  142. return Math.ceil(num);
  143. }
  144. }
  145. async function main() {
  146. let screenSize = await api("GET", "remote/screen-size");
  147. let mousePos = await api("GET", "remote/mouse-pos");
  148. updateCursor(mousePos, screenSize);
  149. setInterval(async () => {
  150. mousePos = await api("GET", "remote/mouse-pos");
  151. updateCursor(mousePos, screenSize);
  152. }, 1000);
  153. textFormEl.addEventListener("submit", async evt => {
  154. evt.preventDefault();
  155. let text = evt.target.elements.text.value;
  156. evt.target.elements.text.value = "";
  157. await api("POST", "remote/keyboard-type", {text});
  158. api("POST", "remote/keyboard-key", {key: "enter"});
  159. });
  160. screencastEl.addEventListener("click", evt => {
  161. evt.preventDefault();
  162. api("POST", "remote/mouse-click", {button: "left", doubleClick: false});
  163. });
  164. screencastEl.addEventListener("dblclick", evt => {
  165. evt.preventDefault();
  166. api("POST", "remote/mouse-click", {button: "left", doubleClick: true});
  167. });
  168. screencastEl.addEventListener("contextmenu", evt => {
  169. evt.preventDefault();
  170. api("POST", "remote/mouse-click", {button: "right", doubleClick: false});
  171. });
  172. screencastEl.addEventListener("wheel", evt => {
  173. evt.preventDefault();
  174. onScroll({x: evt.deltaX / 5, y: evt.deltaY / 5});
  175. });
  176. screencastEl.addEventListener("mousemove", evt => {
  177. if (evt.buttons != 0) {
  178. return;
  179. }
  180. evt.preventDefault();
  181. let fracX = evt.offsetX / evt.target.offsetWidth;
  182. let fracY = evt.offsetY / evt.target.offsetHeight;
  183. mousePos.x = Math.round(fracX * screenSize.width);
  184. mousePos.y = Math.round(fracY * screenSize.height);
  185. updateCursor(mousePos, screenSize);
  186. api("PUT", "remote/mouse-pos", mousePos);
  187. });
  188. let numTouches = 0;
  189. let touches = {};
  190. screencastContainerEl.addEventListener("touchstart", evt => {
  191. evt.preventDefault();
  192. numTouches += evt.changedTouches.length;
  193. for (let touch of evt.changedTouches) {
  194. touches[touch.identifier] = {x: touch.clientX, y: touch.clientY, moveDist: 0};
  195. }
  196. });
  197. screencastContainerEl.addEventListener("touchmove", evt => {
  198. evt.preventDefault();
  199. let delta = {x: 0, y: 0}
  200. for (let touch of evt.changedTouches) {
  201. let oldTouch = touches[touch.identifier];
  202. let d = {x: touch.clientX - oldTouch.x, y: touch.clientY - oldTouch.y};
  203. oldTouch.moveDist += Math.sqrt(d.x * d.x + d.y * d.y);
  204. oldTouch.x = touch.clientX;
  205. oldTouch.y = touch.clientY;
  206. delta.x += d.x;
  207. delta.y += d.y;
  208. }
  209. if (numTouches == 1) {
  210. delta.x = Math.round(signPow(delta.x / 2, 1.5));
  211. delta.y = Math.round(signPow(delta.y / 2, 1.5));
  212. moveDelta(mousePos, screenSize, delta);
  213. } else if (numTouches == 2) {
  214. delta.x = -delta.x / 5 / numTouches;
  215. delta.y = -delta.y / 5 / numTouches;
  216. onScroll(delta);
  217. }
  218. });
  219. screencastContainerEl.addEventListener("touchend", evt => {
  220. evt.preventDefault();
  221. numTouches -= evt.changedTouches.length;
  222. for (let touch of evt.changedTouches) {
  223. let oldTouch = touches[touch.identifier];
  224. touches[touch.identifier] = null;
  225. if (oldTouch.moveDist < 10) {
  226. api("POST", "remote/mouse-click", {button: "left", doubleClick: false});
  227. break;
  228. }
  229. }
  230. });
  231. }
  232. main();
  233. </script>
  234. </body>
  235. </html>