WebVM.svelte 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. <script>
  2. import { onMount } from 'svelte';
  3. import Nav from 'labs/packages/global-navbar/src/Nav.svelte';
  4. import SideBar from '$lib/SideBar.svelte';
  5. import '$lib/global.css';
  6. import '@xterm/xterm/css/xterm.css'
  7. import '@fortawesome/fontawesome-free/css/all.min.css'
  8. import { networkInterface, startLogin } from '$lib/network.js'
  9. import { cpuActivity, diskActivity } from '$lib/activities.js'
  10. import { introMessage, errorMessage } from '$lib/messages.js'
  11. export let configObj = null;
  12. export let processCallback = null;
  13. export let cacheId = null;
  14. var term = null;
  15. var cx = null;
  16. var cxReadFunc = null;
  17. var processCount = 0;
  18. var curVT = 0;
  19. function writeData(buf, vt)
  20. {
  21. if(vt != 1)
  22. return;
  23. term.write(new Uint8Array(buf));
  24. }
  25. function readData(str)
  26. {
  27. if(cxReadFunc == null)
  28. return;
  29. for(var i=0;i<str.length;i++)
  30. cxReadFunc(str.charCodeAt(i));
  31. }
  32. function printMessage(msg)
  33. {
  34. for(var i=0;i<msg.length;i++)
  35. term.write(msg[i] + "\n");
  36. }
  37. function hddCallback(state)
  38. {
  39. diskActivity.set(state != "ready");
  40. }
  41. function cpuCallback(state)
  42. {
  43. cpuActivity.set(state != "ready");
  44. }
  45. async function initTerminal()
  46. {
  47. const { Terminal } = await import('@xterm/xterm');
  48. const { FitAddon } = await import('@xterm/addon-fit');
  49. const { WebLinksAddon } = await import('@xterm/addon-web-links');
  50. term = new Terminal({cursorBlink:true, convertEol:true, fontFamily:"monospace", fontWeight: 400, fontWeightBold: 700});
  51. var fitAddon = new FitAddon();
  52. term.loadAddon(fitAddon);
  53. var linkAddon = new WebLinksAddon();
  54. term.loadAddon(linkAddon);
  55. const consoleDiv = document.getElementById("console");
  56. term.open(consoleDiv);
  57. term.scrollToTop();
  58. fitAddon.fit();
  59. window.addEventListener("resize", function(ev){ fitAddon.fit(); });
  60. term.focus();
  61. term.onData(readData);
  62. // Avoid undesired default DnD handling
  63. function preventDefaults (e) {
  64. e.preventDefault()
  65. e.stopPropagation()
  66. }
  67. consoleDiv.addEventListener("dragover", preventDefaults, false);
  68. consoleDiv.addEventListener("dragenter", preventDefaults, false);
  69. consoleDiv.addEventListener("dragleave", preventDefaults, false);
  70. consoleDiv.addEventListener("drop", preventDefaults, false);
  71. if(configObj.printIntro)
  72. printMessage(introMessage);
  73. initCheerpX();
  74. }
  75. function handleActivateConsole(vt)
  76. {
  77. if(curVT == vt)
  78. return;
  79. curVT = vt;
  80. if(vt != 7)
  81. return;
  82. // Raise the display to the foreground
  83. const display = document.getElementById("display");
  84. display.style.zIndex = 10;
  85. plausible("Display activated");
  86. }
  87. function handleProcessCreated()
  88. {
  89. processCount++;
  90. if(processCallback)
  91. processCallback(processCount);
  92. }
  93. async function initCheerpX()
  94. {
  95. const CheerpX = await import('@leaningtech/cheerpx');
  96. var blockDevice = null;
  97. switch(configObj.diskImageType)
  98. {
  99. case "cloud":
  100. try
  101. {
  102. blockDevice = await CheerpX.CloudDevice.create(configObj.diskImageUrl);
  103. }
  104. catch(e)
  105. {
  106. // Report the failure and try again with plain HTTP
  107. var wssProtocol = "wss:";
  108. if(configObj.diskImageUrl.startsWith(wssProtocol))
  109. {
  110. // WebSocket protocol failed, try agin using plain HTTP
  111. plausible("WS Disk failure");
  112. blockDevice = await CheerpX.CloudDevice.create("https:" + configObj.diskImageUrl.substr(wssProtocol.length));
  113. }
  114. else
  115. {
  116. // No other recovery option
  117. throw e;
  118. }
  119. }
  120. break;
  121. case "bytes":
  122. blockDevice = await CheerpX.HttpBytesDevice.create(configObj.diskImageUrl);
  123. break;
  124. case "github":
  125. blockDevice = await CheerpX.GitHubDevice.create(configObj.diskImageUrl);
  126. break;
  127. default:
  128. throw new Error("Unrecognized device type");
  129. }
  130. var overlayDevice = await CheerpX.OverlayDevice.create(blockDevice, await CheerpX.IDBDevice.create(cacheId));
  131. var webDevice = await CheerpX.WebDevice.create("");
  132. var dataDevice = await CheerpX.DataDevice.create();
  133. var mountPoints = [
  134. // The root filesystem, as an Ext2 image
  135. {type:"ext2", dev:overlayDevice, path:"/"},
  136. // Access to files on the Web server, relative to the current page
  137. {type:"dir", dev:webDevice, path:"/web"},
  138. // Access to read-only data coming from JavaScript
  139. {type:"dir", dev:dataDevice, path:"/data"},
  140. // Automatically created device files
  141. {type:"devs", path:"/dev"},
  142. // The Linux 'proc' filesystem which provides information about running processes
  143. {type:"proc", path:"/proc"}
  144. ];
  145. try
  146. {
  147. cx = await CheerpX.Linux.create({mounts: mountPoints, networkInterface: networkInterface});
  148. }
  149. catch(e)
  150. {
  151. printMessage(errorMessage);
  152. printMessage([e.toString()]);
  153. return;
  154. }
  155. cx.registerCallback("cpuActivity", cpuCallback);
  156. cx.registerCallback("diskActivity", hddCallback);
  157. cx.registerCallback("processCreated", handleProcessCreated);
  158. term.scrollToBottom();
  159. cxReadFunc = cx.setCustomConsole(writeData, term.cols, term.rows);
  160. const display = document.getElementById("display");
  161. if(display)
  162. {
  163. cx.setKmsCanvas(display, 1024, 768);
  164. cx.setActivateConsole(handleActivateConsole);
  165. }
  166. // Run the command in a loop, in case the user exits
  167. while (true)
  168. {
  169. await cx.run(configObj.cmd, configObj.args, configObj.opts);
  170. }
  171. }
  172. onMount(initTerminal);
  173. async function handleConnect()
  174. {
  175. const w = window.open("login.html", "_blank");
  176. await cx.networkLogin();
  177. w.location.href = await startLogin();
  178. }
  179. </script>
  180. <main class="relative w-full h-full">
  181. <Nav />
  182. <div class="absolute top-10 bottom-0 left-0 right-0">
  183. <SideBar on:connect={handleConnect}/>
  184. {#if configObj.needsDisplay}
  185. <canvas class="absolute top-0 bottom-0 left-14 right-0" width="1024" height="768" id="display"></canvas>
  186. {/if}
  187. <div class="absolute top-0 bottom-0 left-14 right-0 p-1 scrollbar" id="console">
  188. </div>
  189. </div>
  190. </main>