├── .gitignore
├── Cargo.toml
├── Makefile
├── README.md
├── examples
├── echo
│ ├── Cargo.toml
│ ├── Makefile
│ ├── echo.wasm
│ ├── index.html
│ └── src
│ │ └── lib.rs
└── helloworld
│ ├── Cargo.toml
│ ├── Makefile
│ ├── helloworld.wasm
│ ├── index.html
│ └── src
│ └── lib.rs
├── src
└── lib.rs
├── wash.js
└── wash.wasm
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | **/*.rs.bk
3 | Cargo.lock
4 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "wash"
3 | version = "0.0.1"
4 | authors = ["Richard Anaya"]
5 | edition = "2018"
6 | description = "an interactive shell for web assembly"
7 | license = "MIT"
8 |
9 | [lib]
10 | crate-type =["cdylib"]
11 |
12 | [dependencies]
13 | web-dom = "0.1.3"
14 | joss = "0"
15 | ref_thread_local = "0.0"
16 | serde_derive="1"
17 | serde="1"
18 | serde_json="1"
19 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | @cargo build --target wasm32-unknown-unknown --release
3 | @cp target/wasm32-unknown-unknown/release/wash.wasm .
4 | serve:
5 | http-server -p 8080
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wash 🖥️
2 |
3 | `wash` is a web assembly shell for a unix-like operating system. Applications use [JOSS (JSON Operating System Schema)](https://github.com/web-dom/joss/) to communicate with the operating system. For example a hello world:
4 |
5 | ```rust
6 | use joss;
7 |
8 | #[no_mangle]
9 | pub fn main() -> () {
10 | // write to stdout
11 | joss::syscall(r#"{
12 | "operation": "write_file",
13 | "file_descriptor": 1,
14 | "text":"Hello World!"
15 | }"#.to_owned());
16 | }
17 | ```
18 |
19 | See a demo [here](https://web-dom.github.io/wash/examples/helloworld/)
20 |
21 | `wash` is meant to be very easily embeddable with a simple html element that defines what commands are available:
22 |
23 | ```html
24 |
25 |
26 |
27 | ```
28 |
--------------------------------------------------------------------------------
/examples/echo/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "echo"
3 | version = "0.1.0"
4 | authors = ["Richard "]
5 | edition = "2018"
6 |
7 | [lib]
8 | crate-type =["cdylib"]
9 |
10 | [dependencies]
11 | joss="0"
12 | serde_derive="1"
13 | serde="1"
14 | serde_json="1"
15 | malloc="0"
16 | web-dom = "0.1.3"
17 |
--------------------------------------------------------------------------------
/examples/echo/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | @cd .. && cd .. && make
3 | @cargo build --target wasm32-unknown-unknown --release
4 | @cp target/wasm32-unknown-unknown/release/echo.wasm .
5 |
--------------------------------------------------------------------------------
/examples/echo/echo.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/web-dom/wash/4d7a88a77eff06c82b124e05ab6b7bd96dd33c16/examples/echo/echo.wasm
--------------------------------------------------------------------------------
/examples/echo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | wash
5 |
6 |
7 |
8 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/examples/echo/src/lib.rs:
--------------------------------------------------------------------------------
1 | #[macro_use]
2 | extern crate serde_derive;
3 | extern crate malloc;
4 | use joss;
5 | use serde_json::{from_str, json};
6 |
7 | #[derive(Serialize, Deserialize)]
8 | struct CommandLineArguments {
9 | arguments: Vec,
10 | }
11 |
12 | #[no_mangle]
13 | fn main() {
14 | // get command line args
15 | let request_json = json!({
16 | "operation": "get_command_line_arguments"
17 | });
18 | let response = joss::syscall(request_json.to_string());
19 | let response_json: CommandLineArguments = from_str(&response).unwrap();
20 | let output = response_json.arguments.clone().join(" ");
21 |
22 | // write to stdout
23 | let output_json = json!({
24 | "operation": "write_file",
25 | "file_descriptor": 1,
26 | "text":output
27 | });
28 | joss::syscall(output_json.to_string());
29 | }
30 |
--------------------------------------------------------------------------------
/examples/helloworld/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "helloworld"
3 | version = "0.0.0"
4 | edition = "2018"
5 |
6 | [lib]
7 | crate-type =["cdylib"]
8 |
9 | [dependencies]
10 | joss="0"
11 |
--------------------------------------------------------------------------------
/examples/helloworld/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | @cd .. && cd .. && make
3 | @cargo build --target wasm32-unknown-unknown --release
4 | @cp target/wasm32-unknown-unknown/release/helloworld.wasm .
5 |
--------------------------------------------------------------------------------
/examples/helloworld/helloworld.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/web-dom/wash/4d7a88a77eff06c82b124e05ab6b7bd96dd33c16/examples/helloworld/helloworld.wasm
--------------------------------------------------------------------------------
/examples/helloworld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | wash
5 |
6 |
7 |
8 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/examples/helloworld/src/lib.rs:
--------------------------------------------------------------------------------
1 | use joss;
2 |
3 | #[no_mangle]
4 | pub fn main() -> () {
5 | // write to stdout
6 | joss::syscall(r#"{
7 | "operation": "write_file",
8 | "file_descriptor": 1,
9 | "text":"Hello World!"
10 | }"#.to_owned());
11 | }
12 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | use core::str::from_utf8;
2 | use joss;
3 | use ref_thread_local::RefThreadLocal;
4 | use std::collections::HashMap;
5 | use web_dom::*;
6 | #[macro_use]
7 | extern crate ref_thread_local;
8 |
9 | #[derive(Default)]
10 | struct Shell {
11 | window: DOMReference,
12 | document: DOMReference,
13 | component: DOMReference,
14 | canvas: DOMReference,
15 | ctx: DOMReference,
16 | shadow: DOMReference,
17 | characters: Vec,
18 | width: usize,
19 | height: usize,
20 | pos: usize,
21 | key_down_listener: EventListener,
22 | command: Vec,
23 | known_commands: HashMap,
24 | current_directory: String,
25 | }
26 |
27 | ref_thread_local! {
28 | static managed SHELL: Shell = Shell::default();
29 | }
30 |
31 | impl Shell {
32 | fn handle_syscall(&mut self, response: CString) -> CString {
33 | let str = to_string(response);
34 | let command: serde_json::Value = serde_json::from_str(&str).unwrap();
35 | let operation = match command["operation"].as_str() {
36 | Some(o) => o,
37 | _ => {
38 | console::error("unknown response");
39 | return -1;
40 | }
41 | };
42 | if operation == "wash_os_registered" {
43 | self.current_directory = "/".to_owned();
44 | self.width = 60;
45 | self.height = 40;
46 | self.characters = vec![32; self.width * self.height];
47 | self.component = command["root_component"].as_u64().unwrap() as i32;
48 | self.window = window();
49 | self.document = window::get_document(self.window);
50 | self.canvas = document::create_element(self.document, "canvas");
51 | element::set_attribute(
52 | self.canvas,
53 | "style",
54 | r#"image-rendering: -moz-crisp-edges;
55 | image-rendering: -webkit-crisp-edges;
56 | image-rendering: pixelated;
57 | image-rendering: crisp-edges;"#,
58 | );
59 | element::set_attribute(self.canvas, "width", "800");
60 | element::set_attribute(self.canvas, "height", "600");
61 | self.shadow = customelement::attach_shadow(self.component);
62 | node::append_child(self.shadow, self.canvas);
63 | self.ctx = htmlcanvas::get_context(self.canvas, "2d");
64 | canvas::set_fill_style(self.ctx, "black");
65 | canvas::fill_rect(self.ctx, 0.0, 0.0, 800.0, 600.0);
66 | self.key_down_listener = create_event_listener();
67 | eventtarget::add_event_listener(self.document, "keydown", self.key_down_listener);
68 | self.print("welcome to WASH, type \"help\" to see a list of commands\n");
69 | self.characters[self.pos] = 124;
70 | self.render();
71 | let child_count = element::get_child_element_count(self.component) as i32;
72 | if child_count > 0 {
73 | let mut el = element::get_first_element_child(self.component);
74 | for i in 0..child_count {
75 | if i != 0 {
76 | el = element::get_next_element_sibling(el);
77 | }
78 | self.known_commands.insert(
79 | to_string(element::get_attribute(el, "name")),
80 | to_string(element::get_attribute(el, "module")),
81 | );
82 | }
83 | }
84 | } else if operation == "wash_process_command" {
85 | let op = command["command"]["operation"].as_str().unwrap();
86 | if op == "get_command_line_arguments" {
87 | return to_cstring(r#"{"arguments":["vim","hello.txt"]}"#);
88 | } if op == "write_file" {
89 | let text = command["command"]["text"].as_str().unwrap();
90 | self.print(text);
91 | }
92 | } else {
93 | console::error("unknown command");
94 | return -1;
95 | }
96 | 0
97 | }
98 |
99 | fn print(&mut self, s: &str) {
100 | let cs: Vec = s.chars().collect();
101 | for i in 0..cs.len() {
102 | let c = cs[i] as u8;
103 | self.characters[self.pos] = c;
104 | if c == 10 {
105 | self.pos = (self.pos / self.width + 1) * self.width;
106 | } else {
107 | self.pos += 1;
108 | }
109 | }
110 | self.render();
111 | }
112 |
113 | fn handle_event(&mut self, listener: EventListener, event: Event) {
114 | if listener == self.key_down_listener {
115 | let key = keyboardevent::get_key(event);
116 | let key_chars: Vec = key.chars().collect();
117 | if key.len() == 1 {
118 | let key_code = key_chars[0] as u8;
119 | self.process_char(key_code);
120 | self.command.push(key_code);
121 | } else if key == "Backspace" {
122 | self.process_char(8);
123 | } else if key == "Enter" {
124 | self.process_char(13);
125 | } else {
126 | return;
127 | }
128 | self.render();
129 | }
130 | }
131 |
132 | fn execute(&mut self) {
133 | let s = from_utf8(&self.command).unwrap();
134 | if s == "help" {
135 | let keys: Vec = self.known_commands.keys().map(|x| x.clone()).collect();
136 | if keys.len() == 0 {
137 | self.print("embarassing...there doesn't seem to be any commands\n");
138 | self.characters[self.pos] = 124;
139 | self.render();
140 | } else {
141 | self.print(&keys.join(" "));
142 | self.print("\n");
143 | self.characters[self.pos] = 124;
144 | self.render();
145 | }
146 | } else {
147 | let cmd = self.known_commands.get(s);
148 | if cmd.is_some() {
149 | joss::syscall(
150 | format!(
151 | r#"{{"operation":"wash_spawn_process","path":"{}"}}"#,
152 | cmd.unwrap()
153 | )
154 | .to_owned(),
155 | );
156 | } else {
157 | self.print("command not found\n");
158 | self.characters[self.pos] = 124;
159 | self.render();
160 | }
161 | }
162 | self.command = vec![];
163 | }
164 |
165 | fn process_char(&mut self, k: u8) {
166 | if k == 13 {
167 | self.characters[self.pos] = 32;
168 | self.pos = (self.pos / self.width + 1) * self.width;
169 | self.characters[self.pos] = 124;
170 | self.execute();
171 | return;
172 | }
173 | if k == 10 {
174 | self.characters[self.pos] = 32;
175 | self.pos = (self.pos / self.width + 1) * self.width;
176 | self.characters[self.pos] = 124;
177 | return;
178 | }
179 | if k == 8 {
180 | if self.command.len() == 0 {
181 | return;
182 | }
183 | self.command.pop();
184 | self.characters[self.pos] = 32;
185 | self.pos -= 1;
186 | self.characters[self.pos] = 124;
187 | return;
188 | }
189 | self.characters[self.pos] = k;
190 | self.pos += 1;
191 | self.characters[self.pos] = 124;
192 | }
193 |
194 | fn render(&self) {
195 | canvas::set_fill_style(self.ctx, "black");
196 | canvas::fill_rect(self.ctx, 0.0, 0.0, 800.0, 600.0);
197 | canvas::set_fill_style(self.ctx, "white");
198 | canvas::set_font(self.ctx, "18px monospace");
199 | for x in 0..self.width {
200 | for y in 0..self.height {
201 | canvas::fill_text(
202 | self.ctx,
203 | &(self.characters[y * self.width + x] as char).to_string(),
204 | 0.0 + (800.0 / self.width as f32) * x as f32,
205 | 15.0 + (600.0 / self.height as f32) * y as f32,
206 | );
207 | }
208 | }
209 | }
210 | }
211 |
212 | #[no_mangle]
213 | pub fn callback(listener: EventListener, event: Event) -> () {
214 | SHELL.borrow_mut().handle_event(listener, event);
215 | }
216 |
217 | #[no_mangle]
218 | pub fn joss_syscall_handler(response: CString) -> CString {
219 | SHELL.borrow_mut().handle_syscall(response)
220 | }
221 |
222 | #[no_mangle]
223 | pub fn main() -> () {
224 | joss::syscall(r#"{"operation":"wash_register_os"}"#.to_owned());
225 | }
226 |
--------------------------------------------------------------------------------
/wash.js:
--------------------------------------------------------------------------------
1 | const OP_SYSTEM = 0;
2 | const SUBOP_INITIALIZATION = 0;
3 | const SUBOP_SPAWN = 1;
4 |
5 | class WasmShell extends HTMLElement {
6 | connectedCallback(){
7 | this.nextProcessID = 0;
8 | let mod = this.getAttribute("module");
9 | this.autorun = this.getAttribute("autorun");
10 | this.spawn(mod==null?"wash.wasm":mod,this.nextProcessID);
11 | }
12 |
13 | spawn(wasmSrc,processId,os){
14 | let isOperatingSystem = processId == 0;
15 | let component = this;
16 | component.nextProcessID+=1;
17 | WebDOM.run(wasmSrc,{
18 | joss_syscall:function(request){
19 | let _request = this.readStringFromMemory(request);
20 | let command = JSON.parse(_request);
21 | if(command.operation == "wash_register_os"){
22 | if(isOperatingSystem){
23 | let el = this.env.allocator().a(component);
24 | let response = {
25 | operation:"wash_os_registered",
26 | root_component: el
27 | };
28 | this.exports.joss_syscall_handler(this.makeString(JSON.stringify(response)));
29 | }
30 | }
31 | else if(command.operation == "wash_spawn_process"){
32 | if(isOperatingSystem){
33 | window.setTimeout(()=>{
34 | component.spawn(command.path,component.nextProcessID,this);
35 | },1)
36 | }
37 | } else {
38 | let response = JSON.stringify({operation:"wash_process_command",process:processId,command:command});
39 | let r = os.exports.joss_syscall_handler(os.makeString(response));
40 | let s = os.readStringFromMemory(r);
41 | return this.makeString(s);
42 | }
43 | }
44 | },function(){})
45 | }
46 | }
47 |
48 | customElements.define('wasm-shell', WasmShell);
49 |
--------------------------------------------------------------------------------
/wash.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/web-dom/wash/4d7a88a77eff06c82b124e05ab6b7bd96dd33c16/wash.wasm
--------------------------------------------------------------------------------