$ /* VFS Controller _readBlock, _readINode and similar calls utilize IDBDriver for persistent file operations
*/ async open(path: string) { const { parent, name } = await this._resolvePath(path); const parentDir = this._decodeDirBlock(await this._readBlock(parent.id)); let node: INode; if(parentDir[name] === undefined) { node = await this._createFile(parent, name, FileType.FILE); } else { node = await this._readINode(parentDir[name]); if(node.type !== FileType.FILE) { throw new Error(`${name} is not a file`); } } return { id: this._fdCounter++, inode: node.id, position: 0 } as FileDescriptor;
} async read(fd: FileDescriptor, length: number): Promise<Uint8Array> { const inode = await this._readINode(fd.inode); if(inode.type !== FileType.FILE) { throw new Error("Cannot read from a directory"); } const block = await this._readBlock(inode.id); const data = block.slice(fd.position, fd.position + length); fd.position += data.length; return data;
} /* IndexedDB driver */ async read(store: string, key: IDBValidKey) { return new Promise<any>((resolve, reject) => { const transaction = this._db.transaction([store], "readonly"); const objectStore = transaction.objectStore(store); const request = objectStore.get(key); request.onerror = (event) => { reject(new Error(`IndexedDB error: ${request.error}`)); }; request.onsuccess = (event) => { resolve(request.result); }; });
}
/* VFS Controller _readBlock, _readINode and similar calls utilize IDBDriver for persistent file operations
*/ async open(path: string) { const { parent, name } = await this._resolvePath(path); const parentDir = this._decodeDirBlock(await this._readBlock(parent.id)); let node: INode; if(parentDir[name] === undefined) { node = await this._createFile(parent, name, FileType.FILE); } else { node = await this._readINode(parentDir[name]); if(node.type !== FileType.FILE) { throw new Error(`${name} is not a file`); } } return { id: this._fdCounter++, inode: node.id, position: 0 } as FileDescriptor;
} async read(fd: FileDescriptor, length: number): Promise<Uint8Array> { const inode = await this._readINode(fd.inode); if(inode.type !== FileType.FILE) { throw new Error("Cannot read from a directory"); } const block = await this._readBlock(inode.id); const data = block.slice(fd.position, fd.position + length); fd.position += data.length; return data;
} /* IndexedDB driver */ async read(store: string, key: IDBValidKey) { return new Promise<any>((resolve, reject) => { const transaction = this._db.transaction([store], "readonly"); const objectStore = transaction.objectStore(store); const request = objectStore.get(key); request.onerror = (event) => { reject(new Error(`IndexedDB error: ${request.error}`)); }; request.onsuccess = (event) => { resolve(request.result); }; });
}
/* VFS Controller _readBlock, _readINode and similar calls utilize IDBDriver for persistent file operations
*/ async open(path: string) { const { parent, name } = await this._resolvePath(path); const parentDir = this._decodeDirBlock(await this._readBlock(parent.id)); let node: INode; if(parentDir[name] === undefined) { node = await this._createFile(parent, name, FileType.FILE); } else { node = await this._readINode(parentDir[name]); if(node.type !== FileType.FILE) { throw new Error(`${name} is not a file`); } } return { id: this._fdCounter++, inode: node.id, position: 0 } as FileDescriptor;
} async read(fd: FileDescriptor, length: number): Promise<Uint8Array> { const inode = await this._readINode(fd.inode); if(inode.type !== FileType.FILE) { throw new Error("Cannot read from a directory"); } const block = await this._readBlock(inode.id); const data = block.slice(fd.position, fd.position + length); fd.position += data.length; return data;
} /* IndexedDB driver */ async read(store: string, key: IDBValidKey) { return new Promise<any>((resolve, reject) => { const transaction = this._db.transaction([store], "readonly"); const objectStore = transaction.objectStore(store); const request = objectStore.get(key); request.onerror = (event) => { reject(new Error(`IndexedDB error: ${request.error}`)); }; request.onsuccess = (event) => { resolve(request.result); }; });
}
new Function(...)
/* Example application pre-compiled code */ const utils = include("utils"); export default async function(args) { const pid = kernel.getpid(); console.log(`Hello World! PID is ${pid}.`); const ext = utils.pathUtils.extname("/libs/utils.lib"); console.log(`Utils library file extension is ${ext}`); let counter = 0; let should_close = false; while(!should_close) { counter++; if(counter >= 1000) { should_close = true; } } console.log("Exiting...");
} /* Kernel loads compiled file from VFS and runs it using new Function() */ async exec(code: string, args: ...args[]) { /* ... */ const fn = new Function( "kernel", "include", `${code} return main;` )(proxyKernel, include); /* ... */
}
/* Example application pre-compiled code */ const utils = include("utils"); export default async function(args) { const pid = kernel.getpid(); console.log(`Hello World! PID is ${pid}.`); const ext = utils.pathUtils.extname("/libs/utils.lib"); console.log(`Utils library file extension is ${ext}`); let counter = 0; let should_close = false; while(!should_close) { counter++; if(counter >= 1000) { should_close = true; } } console.log("Exiting...");
} /* Kernel loads compiled file from VFS and runs it using new Function() */ async exec(code: string, args: ...args[]) { /* ... */ const fn = new Function( "kernel", "include", `${code} return main;` )(proxyKernel, include); /* ... */
}
/* Example application pre-compiled code */ const utils = include("utils"); export default async function(args) { const pid = kernel.getpid(); console.log(`Hello World! PID is ${pid}.`); const ext = utils.pathUtils.extname("/libs/utils.lib"); console.log(`Utils library file extension is ${ext}`); let counter = 0; let should_close = false; while(!should_close) { counter++; if(counter >= 1000) { should_close = true; } } console.log("Exiting...");
} /* Kernel loads compiled file from VFS and runs it using new Function() */ async exec(code: string, args: ...args[]) { /* ... */ const fn = new Function( "kernel", "include", `${code} return main;` )(proxyKernel, include); /* ... */
}
new Function(...)
currentProcess
/* Kernel exec call */ async exec(code: string, ...args: any[]) { const pid = this._nextPid++; const process: Process = { pid, executable: null!, }; // escape proxy if called inside the userspace application const realKernel = (this as any).__target ?? this; const proxyKernel = new Proxy(realKernel, { get: (target, prop) => { if(prop === "__target") return this; const orig = (target as any)[prop]; if (typeof orig !== "function") return orig; return (...args: any[]) => { this._currentProcess = process; try { return orig.apply(proxyKernel, args); } finally { this._currentProcess = null; } }; } }); /* ... */
}
/* Kernel exec call */ async exec(code: string, ...args: any[]) { const pid = this._nextPid++; const process: Process = { pid, executable: null!, }; // escape proxy if called inside the userspace application const realKernel = (this as any).__target ?? this; const proxyKernel = new Proxy(realKernel, { get: (target, prop) => { if(prop === "__target") return this; const orig = (target as any)[prop]; if (typeof orig !== "function") return orig; return (...args: any[]) => { this._currentProcess = process; try { return orig.apply(proxyKernel, args); } finally { this._currentProcess = null; } }; } }); /* ... */
}
/* Kernel exec call */ async exec(code: string, ...args: any[]) { const pid = this._nextPid++; const process: Process = { pid, executable: null!, }; // escape proxy if called inside the userspace application const realKernel = (this as any).__target ?? this; const proxyKernel = new Proxy(realKernel, { get: (target, prop) => { if(prop === "__target") return this; const orig = (target as any)[prop]; if (typeof orig !== "function") return orig; return (...args: any[]) => { this._currentProcess = process; try { return orig.apply(proxyKernel, args); } finally { this._currentProcess = null; } }; } }); /* ... */
}
new Function(...)
/* Precompiled Y11 client library */ // libraries can declare and use their own global objects, since they
// are encapsulated in new Function(...), they are not visible outside
const _displays = new Map<number, _Display>(); export async function openDisplay() { /* ... */ }
export async function createWindow() { /* ... */ } /* Example usage in application */ const y11 = include("y11"); // loads library from /libs/y11.lib file export default async function() { const display = await y11.openDisplay(); const win = await y11.createWindow(); /* ... */
} /* Kernel exec call */ async exec(code: string, ...args: any[]) { /* ... */ const libMap = new Map<string, any>(); // search code for needed imports that have format include("<libname>") const importRegex = /include\("([^"]+)"\)/g; let match: RegExpExecArray | null; while((match = importRegex.exec(code)) !== null) { const [fullMatch, libName] = match; if(libMap.has(libName)) { continue; } console.log(`Kernel: Found import "${libName}" in process ${pid}. Attempting to load...`); // _loadLib call simply loads the library code from the VFS file and // returns new Function("kernel", `${code} return main`); const lib = (await this._loadLib(libName))(proxyKernel); libMap.set(libName, lib); }
}
/* Precompiled Y11 client library */ // libraries can declare and use their own global objects, since they
// are encapsulated in new Function(...), they are not visible outside
const _displays = new Map<number, _Display>(); export async function openDisplay() { /* ... */ }
export async function createWindow() { /* ... */ } /* Example usage in application */ const y11 = include("y11"); // loads library from /libs/y11.lib file export default async function() { const display = await y11.openDisplay(); const win = await y11.createWindow(); /* ... */
} /* Kernel exec call */ async exec(code: string, ...args: any[]) { /* ... */ const libMap = new Map<string, any>(); // search code for needed imports that have format include("<libname>") const importRegex = /include\("([^"]+)"\)/g; let match: RegExpExecArray | null; while((match = importRegex.exec(code)) !== null) { const [fullMatch, libName] = match; if(libMap.has(libName)) { continue; } console.log(`Kernel: Found import "${libName}" in process ${pid}. Attempting to load...`); // _loadLib call simply loads the library code from the VFS file and // returns new Function("kernel", `${code} return main`); const lib = (await this._loadLib(libName))(proxyKernel); libMap.set(libName, lib); }
}
/* Precompiled Y11 client library */ // libraries can declare and use their own global objects, since they
// are encapsulated in new Function(...), they are not visible outside
const _displays = new Map<number, _Display>(); export async function openDisplay() { /* ... */ }
export async function createWindow() { /* ... */ } /* Example usage in application */ const y11 = include("y11"); // loads library from /libs/y11.lib file export default async function() { const display = await y11.openDisplay(); const win = await y11.createWindow(); /* ... */
} /* Kernel exec call */ async exec(code: string, ...args: any[]) { /* ... */ const libMap = new Map<string, any>(); // search code for needed imports that have format include("<libname>") const importRegex = /include\("([^"]+)"\)/g; let match: RegExpExecArray | null; while((match = importRegex.exec(code)) !== null) { const [fullMatch, libName] = match; if(libMap.has(libName)) { continue; } console.log(`Kernel: Found import "${libName}" in process ${pid}. Attempting to load...`); // _loadLib call simply loads the library code from the VFS file and // returns new Function("kernel", `${code} return main`); const lib = (await this._loadLib(libName))(proxyKernel); libMap.set(libName, lib); }
}
/* Example server and client applications communicating via domain socket. In real implementation, they would be separate applications, but for simplicity they are together here.
*/ export default async function(...args: any[]) { const server = socket_server(); const client = socket_client(); await Promise.all([server, client]);
} async function socket_server() { const sock = await socket(); await bind(sock, "test_socket"); await listen(sock); const clientSock = await accept(sock); console.log("Accepted connection on test_socket!"); const data = await readv(clientSock); console.log("Received from client:", new TextDecoder().decode(data)); const should_be_null = await readv(clientSock); console.log("Read from client after closing connection (should be null):", should_be_null); await close(clientSock); await close(sock);
} async function socket_client() { await new Promise(resolve => setTimeout(resolve, 100)); const sock = await socket(); await connect(sock, "test_socket"); console.log("Connected to test_socket!"); const message = "Hello from client!"; await send(sock, new TextEncoder().encode(message)); console.log("Sent to server:", message); await close(sock);
}
/* Example server and client applications communicating via domain socket. In real implementation, they would be separate applications, but for simplicity they are together here.
*/ export default async function(...args: any[]) { const server = socket_server(); const client = socket_client(); await Promise.all([server, client]);
} async function socket_server() { const sock = await socket(); await bind(sock, "test_socket"); await listen(sock); const clientSock = await accept(sock); console.log("Accepted connection on test_socket!"); const data = await readv(clientSock); console.log("Received from client:", new TextDecoder().decode(data)); const should_be_null = await readv(clientSock); console.log("Read from client after closing connection (should be null):", should_be_null); await close(clientSock); await close(sock);
} async function socket_client() { await new Promise(resolve => setTimeout(resolve, 100)); const sock = await socket(); await connect(sock, "test_socket"); console.log("Connected to test_socket!"); const message = "Hello from client!"; await send(sock, new TextEncoder().encode(message)); console.log("Sent to server:", message); await close(sock);
}
/* Example server and client applications communicating via domain socket. In real implementation, they would be separate applications, but for simplicity they are together here.
*/ export default async function(...args: any[]) { const server = socket_server(); const client = socket_client(); await Promise.all([server, client]);
} async function socket_server() { const sock = await socket(); await bind(sock, "test_socket"); await listen(sock); const clientSock = await accept(sock); console.log("Accepted connection on test_socket!"); const data = await readv(clientSock); console.log("Received from client:", new TextDecoder().decode(data)); const should_be_null = await readv(clientSock); console.log("Read from client after closing connection (should be null):", should_be_null); await close(clientSock); await close(sock);
} async function socket_client() { await new Promise(resolve => setTimeout(resolve, 100)); const sock = await socket(); await connect(sock, "test_socket"); console.log("Connected to test_socket!"); const message = "Hello from client!"; await send(sock, new TextEncoder().encode(message)); console.log("Sent to server:", message); await close(sock);
}
openDisplay
createWindow
selectInput
reparentWindow
raiseWindow
configureWindow
/* Y11 server */ export default async function() { /* ... */ let socket = kernel.socket(); await kernel.bind(socket, `y11.sock`); await kernel.listen(socket); kernel.emit({ type: "y11:ready" }); let should_close = false; while(!should_close) { const connection = await kernel.accept(socket); handleNewConnection(connection); }
} async function handleNewConnection(socket: Socket) { const display: YDisplay = { id: nonce++, pid: socket.pid, socket: socket, event_queue: [], windows: new Set(), baseResourceId: nextResourceId }; /* initialization... */ let should_close = false; while(!should_close) { const res = await kernel.readv(socket); if(res === null) { console.log(`Y11: Socket ${socket.id} closed by client, closing display ${display.id}`); should_close = true; break; } const data = JSON.parse(new TextDecoder().decode(res)); handleClientMessage(display, data); } /* cleanup... */
}
/* Y11 server */ export default async function() { /* ... */ let socket = kernel.socket(); await kernel.bind(socket, `y11.sock`); await kernel.listen(socket); kernel.emit({ type: "y11:ready" }); let should_close = false; while(!should_close) { const connection = await kernel.accept(socket); handleNewConnection(connection); }
} async function handleNewConnection(socket: Socket) { const display: YDisplay = { id: nonce++, pid: socket.pid, socket: socket, event_queue: [], windows: new Set(), baseResourceId: nextResourceId }; /* initialization... */ let should_close = false; while(!should_close) { const res = await kernel.readv(socket); if(res === null) { console.log(`Y11: Socket ${socket.id} closed by client, closing display ${display.id}`); should_close = true; break; } const data = JSON.parse(new TextDecoder().decode(res)); handleClientMessage(display, data); } /* cleanup... */
}
/* Y11 server */ export default async function() { /* ... */ let socket = kernel.socket(); await kernel.bind(socket, `y11.sock`); await kernel.listen(socket); kernel.emit({ type: "y11:ready" }); let should_close = false; while(!should_close) { const connection = await kernel.accept(socket); handleNewConnection(connection); }
} async function handleNewConnection(socket: Socket) { const display: YDisplay = { id: nonce++, pid: socket.pid, socket: socket, event_queue: [], windows: new Set(), baseResourceId: nextResourceId }; /* initialization... */ let should_close = false; while(!should_close) { const res = await kernel.readv(socket); if(res === null) { console.log(`Y11: Socket ${socket.id} closed by client, closing display ${display.id}`); should_close = true; break; } const data = JSON.parse(new TextDecoder().decode(res)); handleClientMessage(display, data); } /* cleanup... */
}
attachContext
createContext
/* Y11 client library */ export async function openDisplay() { const pid = kernel.getpid(); if(pid === null) { throw new Error("Y11: Cannot open display outside of a process context"); } const socket = kernel.socket(); await kernel.connect(socket, "y11.sock"); const res = await kernel.readv(socket); // @ts-ignore const data = JSON.parse(new TextDecoder().decode(res)); /* process initial data from server... */ return display;
} export async function createWindow(display: _Display, parent: _Window) { const windowId = display.nextResourceId++; const seq = display.nextSeq++; console.log(`Y11: Creating window with id ${windowId} for display socket id ${display.socket.id} with parent window id ${parent.id}`); kernel.send(display.socket, new TextEncoder().encode(JSON.stringify({ type: "create_window", seq: seq, windowId: windowId, parentId: parent.id }))); await _waitForResponse(display, seq); return { id: windowId } satisfies _Window;
} export function attachContext( window: _Window, context: (root: HTMLElement) => () => void
) { const id = `window-${window.id}`; const root = document.getElementById(id); if(!root) { throw new Error(`Y11: ${id} element not found in DOM, cannot attach context`); } const unmount = context(root); _contextUnmounts.set(window.id, unmount);
}
/* Y11 client library */ export async function openDisplay() { const pid = kernel.getpid(); if(pid === null) { throw new Error("Y11: Cannot open display outside of a process context"); } const socket = kernel.socket(); await kernel.connect(socket, "y11.sock"); const res = await kernel.readv(socket); // @ts-ignore const data = JSON.parse(new TextDecoder().decode(res)); /* process initial data from server... */ return display;
} export async function createWindow(display: _Display, parent: _Window) { const windowId = display.nextResourceId++; const seq = display.nextSeq++; console.log(`Y11: Creating window with id ${windowId} for display socket id ${display.socket.id} with parent window id ${parent.id}`); kernel.send(display.socket, new TextEncoder().encode(JSON.stringify({ type: "create_window", seq: seq, windowId: windowId, parentId: parent.id }))); await _waitForResponse(display, seq); return { id: windowId } satisfies _Window;
} export function attachContext( window: _Window, context: (root: HTMLElement) => () => void
) { const id = `window-${window.id}`; const root = document.getElementById(id); if(!root) { throw new Error(`Y11: ${id} element not found in DOM, cannot attach context`); } const unmount = context(root); _contextUnmounts.set(window.id, unmount);
}
/* Y11 client library */ export async function openDisplay() { const pid = kernel.getpid(); if(pid === null) { throw new Error("Y11: Cannot open display outside of a process context"); } const socket = kernel.socket(); await kernel.connect(socket, "y11.sock"); const res = await kernel.readv(socket); // @ts-ignore const data = JSON.parse(new TextDecoder().decode(res)); /* process initial data from server... */ return display;
} export async function createWindow(display: _Display, parent: _Window) { const windowId = display.nextResourceId++; const seq = display.nextSeq++; console.log(`Y11: Creating window with id ${windowId} for display socket id ${display.socket.id} with parent window id ${parent.id}`); kernel.send(display.socket, new TextEncoder().encode(JSON.stringify({ type: "create_window", seq: seq, windowId: windowId, parentId: parent.id }))); await _waitForResponse(display, seq); return { id: windowId } satisfies _Window;
} export function attachContext( window: _Window, context: (root: HTMLElement) => () => void
) { const id = `window-${window.id}`; const root = document.getElementById(id); if(!root) { throw new Error(`Y11: ${id} element not found in DOM, cannot attach context`); } const unmount = context(root); _contextUnmounts.set(window.id, unmount);
}
_NET_WM_NAME
_NET_WM_ICON
_NET_ACTIVE_WINDOW
apps/ywm/index.js
virtual:apps
virtual:libs
/* Entry script */ import { apps, appUrls } from "virtual:apps";
import { libs, libUrls } from "virtual:libs"; async function main() { const kernel = await Kernel.create(); /* ... */ await launch_installer(kernel); /* ... */
} async function launch_installer(kernel: Kernel) { let installerCode: string; if(appUrls && appUrls["installer"]) { console.log(`Fetching installer app from ${appUrls["installer"]}...`); const response = await fetch(appUrls["installer"]); installerCode = await response.text(); } else if(apps && apps["installer"]) { installerCode = apps["installer"]; } else { throw new Error("Installer app not found!"); } const process = await kernel.exec(installerCode, apps ?? appUrls, libs ?? libUrls, !!appUrls); while(!process.is_dead) { await new Promise(resolve => setTimeout(resolve, 0)); }
}
/* Entry script */ import { apps, appUrls } from "virtual:apps";
import { libs, libUrls } from "virtual:libs"; async function main() { const kernel = await Kernel.create(); /* ... */ await launch_installer(kernel); /* ... */
} async function launch_installer(kernel: Kernel) { let installerCode: string; if(appUrls && appUrls["installer"]) { console.log(`Fetching installer app from ${appUrls["installer"]}...`); const response = await fetch(appUrls["installer"]); installerCode = await response.text(); } else if(apps && apps["installer"]) { installerCode = apps["installer"]; } else { throw new Error("Installer app not found!"); } const process = await kernel.exec(installerCode, apps ?? appUrls, libs ?? libUrls, !!appUrls); while(!process.is_dead) { await new Promise(resolve => setTimeout(resolve, 0)); }
}
/* Entry script */ import { apps, appUrls } from "virtual:apps";
import { libs, libUrls } from "virtual:libs"; async function main() { const kernel = await Kernel.create(); /* ... */ await launch_installer(kernel); /* ... */
} async function launch_installer(kernel: Kernel) { let installerCode: string; if(appUrls && appUrls["installer"]) { console.log(`Fetching installer app from ${appUrls["installer"]}...`); const response = await fetch(appUrls["installer"]); installerCode = await response.text(); } else if(apps && apps["installer"]) { installerCode = apps["installer"]; } else { throw new Error("Installer app not found!"); } const process = await kernel.exec(installerCode, apps ?? appUrls, libs ?? libUrls, !!appUrls); while(!process.is_dead) { await new Promise(resolve => setTimeout(resolve, 0)); }
} - More OS-like process management using WebWorkers - I already started working on it, but WebWorker processes require a completely different approach to UI creation (since WebWorkers can't manipulate the DOM directly), which lead me to creating a custom JSX framework, but more on that in a future post.
- Compiler - ideally I would love to be able to write applications in TypeScript and compile them directly in the OS, but for now they have to be precompiled in the host environment and only then loaded to the VFS.
- Missing apps - File Manager, context menus, and more utilities. - Live demo - https://y3v4d.com
- GitHub - https://github.com/y3v4d/yos