The system is called "Linux on the Web" (LOTW). It is a thick JS client, which runs in modern web browsers, and has full access to modern Web APIs.
The system's volatile state is kept on the global browser object: `window.LOTW`.
It should be assumed that persistent storage works primarily via a shell accessible, POSIX-like file abstraction layer that uses the origin-private file system API (OPFS) as the physical storage backend. The file system's directory structure is stored via indexedDb. This database-centric indirection enables the implementation of symbolic links as well as hard links in the LOTW file system, making it largely compliant with the published POSIX specifications.
There are certain persistent storage use-cases that use the localStorage API, but these are generally of no interest to system users, or even most developers for that matter. Saving the graphical locations of icons as they exist on the LOTW desktop is one example of using the (extremely simple) localStorage API.
All GUI elements in LOTW are constructed by hand-crafted (vanilla) JS code, such as document.createElement and parentNode.appendChild. Only in the case of the development of windowed applications must developers worry about the construction of their own GUI elements. It is up to the developer to ensure that GUI-heavy frameworks (like React, Angular, etc.) are compatible with the underlying LOTW system.
Constants:
PR: System root directory, e.g. "/home/bob/my_system/"
APPS: Path for the application logic of windowed apps: $PR/apps/
COMS: Path for shell command libraries: $PR/coms/
MODS: Path for generic code: $PR/mods/
SYS_CFG: System config file: $PR/sys/config.js
SHELL: POSIX shell implementation: $MODS/lang/shell.js
DESK: Desktop implementation: $PR/sys/desk.js
The above paths represent the backend files that implement the system itself
In its normal operation, the system (applications or commands) may write to file paths that *look like* those above (e.g. ~/Desktop/MyApp.app or ~/.env), but they must be considered frontend files (stored via Web APIs) with no relation to the backend system.
Coding style:
const my_func = (arg1) => { // Pure stateless functions
//...
};
class myClass { // Pure stateful objects
//...
}
// Do not generally use this syntax: it is a strange mixture of the
// 2 pure forms above
function my_func(){ // Confusing!
//...
}
There are two ways to hook user-facing code into the system: Windowed applications and Shell commands.
The code for these are kept in $APPS
Apps are custom classes, which are kept in files with the following form:
*FILE_BEGIN*
(()=>{"use strict";
const APPNAME = "path.to.AppName"; // File location: $APPS/path/to/AppName.js
// Helper code here...
class App {
constructor(Win){
this.Win = Win; // Handle to a system `Window` object
this.Main = Win.Main; // The Window's client area (an HTML div element)
/* See onloadfile below
Other choices in Win.fmts are:
- TYPED_U8: Uint8Array (default)
- BLOB: Blob
- ARR_BUFF: ArrayBuffer
- PARSED_OR_ERR: try to run the file's contents through JSON.parse, or return the caught Error
*/
this.loadFmt = Win.fmts.TEXT; // String
this.makeDOM();
}
makeDOM(){
// Dynamically generate and style the Window's visible area
// All graphical event handlers (mouse and touch) must be implemented here
}
meth1(){
}
meth2(){
}
meth3(){
}
getContext(){
// Dynamically generate the choices for the app's context menu and return
// them to the system to be rendered
/*
In practice, the spaces in the strings below should be *non-breaking spaces*
*/
return [ // Main menu
"Save...", ()=>{this.saveFile()},
"Choice 1", ()=>{this.meth1()},
"Choice 2", [ // Submenu
"Sub choice 1", ()=>{this.meth2()},
"Sub choice 2", ()=>{this.meth3()},
]
];
}
async getValue(){
let val;
// Serialize the application state here...
return val;
}
async saveFile(){
let ext = "myext"; // Placeholder for how the app's file extension is determined
let rv = await this.Win.saveFile(ext);
if (Number.isFinite(rv)) {
// Success
}
}
onescape(){
// When the app is in an escapable mode, return true.
// Otherwise, let the system window manager take control by returning false
}
onfocus(){
// The Window is now focused
}
onblur(){
// The Window is now blurred
}
onkill(){
// The Window object is being deleted. Application cleanup logic goes here
}
onappinit(appargs={}){
/* Called by the system when an app is:
1) in its "New File" (unsaved) state
2) not saveable, i.e. a media player
*/
}
onloadfile(val){
/* Called by the system when a file's icon is activated
In this case, `val` will be a JS string, given our format preference given in
the constructor
*/
}
onkeydown(e, sym){
/*
1) e: the keydown event object. e.preventDefault may be called to prevent
unwanted behavior
2) sym: a string-like version of the event, such as:
a_: unmodified 'a' key
a_S: Shift + 'a'
a_C: Ctrl + 'a'
a_A: Alt + 'a'
Modifier "precedence" is: C < A < S, giving these possible combinations:
a_CA, a_CS, a_AS, a_CAS
When a key represents a function or non-printable character, it is capitalized
as such:
ENTER,
BACK,
DEL,
PGUP,
PGDOWN,
HOME,
END
*/
}
}
LOTW.apps[APPNAME] = App; // Export the app to the system
})();
*FILE_END*
Application authors may want to register a file extension with the system, with
these 2 steps:
1) Locate the `EXT_TO_APP_MAP` object in $SYS_CFG. The keys are the (lowercase)
extensions and the values are the "fully-qualified app names" (using the same
dotted notation format in $APPNAME above). For example:
const EXT_TO_APP_MAP = {
txt: "TextEdit", // .txt -> $APPS/TextEdit.js
html: "util.HTML", // .html -> $APPS/util/HTML.js
myext: "dev.MyApp" // .myext -> $APPS/dev/MyApp.js
}
2) Locate the `APP_NAME_TO_ICON` object in $SYS_CFG. The keys are the "simple"
app names (not fully-qualified). The values are the number-only portions of the
Unicode values, encoded in hex. For example:
const APP_NAME_TO_ICON = {
TextEdit: "1f4dd", // i.e. "\u{1f4dd}" -> 📝 (Paper and pencil icon)
HTML: "1f310", // 🌐
MyApp: "1f680" // 🚀
}
There are several methods for launching applications in "New File" states (i.e.
without a file icon to double-click in the interface)
1) Menu-driven:
In $SYS_CFG, locate `APPLICATIONS_MENU`, which looks like:
const APPLICATIONS_MENU = [ // JS Array
"Text Editor", "TextEdit",
"Your App", "path.to.YourApp"
];
An option for "Your App" will then appear in the `Applications` submenu of the
main system menu.
2) Keyboard-driven:
In $DESK, search for the string "SHORTCUTS1" or "SHORTCUTS2", depending on
whether you want to give any focused windows the chance to consume the given
keydown event. To associate a "sym" with an application, add a case clause to
the relevant switch statement, for example:
switch(sym) {
//...
case "m_CAS": return open_app("path.to.MyApp");
//...
}
3) Icon-driven:
Run the following command in the terminal to generate an icon on the desktop
(or any other writable path):
`appicon path.to.MyApp > ~/Desktop/MyApp.app`
... then when the icon is activated, the system will attempt to load an application
at $APPS/path/to/MyApp.js. Note: while the name of the icon may be arbitrary
(MyApp *or* Whatever, etc), the `.app` extension is necessary.