Compare commits

...

5 Commits

Author SHA1 Message Date
8432bbb986 add sandbox support to desktop designer, fix mouse coordinates and UI issues
- Add DesktopSandbox class that integrates timer, JSON, crypto, and VirtualFS APIs
- Fix mouse coordinate handling: GLFW reports window coordinates, not physical pixels
- Fix font path resolution to search multiple locations for test apps
- Fix screenshot capture timing (capture before buffer swap)
- Fix test app CSS: use border-width instead of border:none, add display:block
- Fix test app Lua: add document nil checks, use HTML entities for symbols
- Update hot_reload to reset sandbox state on reload

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:22:32 +01:00
d40ea1e537 add generated icon for sandbox test app 2026-01-19 09:17:56 +01:00
d88bddbf75 fix sandbox test app: replace os.date, add goBack, remove missing icon ref 2026-01-19 09:15:30 +01:00
bbf1638f20 update roadmap with completed milestones, trim SANDBOX.md to reference milestone files 2026-01-19 09:07:31 +01:00
010e11cf6b move docs to docs/ folder, merge architecture files, update references 2026-01-19 09:02:11 +01:00
76 changed files with 2338 additions and 5076 deletions

View File

@@ -0,0 +1,75 @@
{
"permissions": {
"allow": [
"Bash(./gradlew assembleDebug:*)",
"Bash(./gradlew installDebug:*)",
"Bash(adb logcat:*)",
"Bash(adb shell am start:*)",
"Bash(adb devices:*)",
"Bash(findstr:*)",
"Bash(tree:*)",
"Bash(cmake:*)",
"Bash(\".\\\\build\\\\Debug\\\\mosis-designer.exe\" \"..\\\\src\\\\main\\\\assets\\\\apps\\\\home\\\\home.rml\")",
"Bash(dir:*)",
"Bash(start \"\" \".\\\\build\\\\Debug\\\\mosis-designer.exe\" \"..\\\\src\\\\main\\\\assets\\\\apps\\\\home\\\\home.rml\")",
"Bash(\"C:\\\\Program Files\\\\AutoHotkey\\\\v2\\\\AutoHotkey64.exe\" \"D:\\\\Dev\\\\Mosis\\\\MosisService\\\\designer\\\\test\\\\diagnose.ahk\")",
"Bash(tasklist:*)",
"Bash(\"C:\\\\Program Files\\\\AutoHotkey\\\\v2\\\\AutoHotkey64.exe\" /ErrorStdOut diagnose.ahk)",
"Bash(taskkill:*)",
"Bash(\"C:\\\\Program Files\\\\AutoHotkey\\\\v2\\\\AutoHotkey64.exe\" full_click_test.ahk)",
"Bash(\"C:\\\\Program Files\\\\AutoHotkey\\\\v2\\\\AutoHotkey64.exe\" visual_click_test.ahk)",
"Bash(\".\\\\build\\\\Debug\\\\designer-test.exe\" --designer \"..\\\\designer\\\\build\\\\Debug\\\\mosis-designer.exe\" --document \"..\\\\src\\\\main\\\\assets\\\\apps\\\\home\\\\home.rml\")",
"Bash(./designer-test.exe)",
"Bash(\"D:\\\\Dev\\\\Mosis\\\\MosisService\\\\designer-test\\\\build\\\\Debug\\\\designer-test.exe\")",
"Bash(git add:*)",
"Bash(adb install:*)",
"Bash(adb shell am broadcast:*)",
"Bash(timeout 5 ./mosis-designer.exe:*)",
"Bash(.buildDebugmosis-designer.exe --dump D:DevMosisMosisServicesrcmainassetsappshomehome.rml)",
"Bash(cmd /c \"cd /d D:\\\\Dev\\\\Mosis\\\\MosisService\\\\designer && .\\\\build\\\\Debug\\\\mosis-designer.exe --dump ..\\\\src\\\\main\\\\assets\\\\apps\\\\home\\\\home.rml 2>&1\")",
"Bash(start /min cmd /c \".\\\\build\\\\Debug\\\\mosis-designer.exe ..\\\\src\\\\main\\\\assets\\\\apps\\\\home\\\\home.rml --hierarchy hierarchy.json\")",
"Bash(powershell -Command:*)",
"Bash(adb shell \"ps -A | grep mosis\")",
"Bash(adb shell:*)",
"Bash(ls:*)",
"Bash(magick:*)",
"Bash(fc:*)",
"Bash(cmp:*)",
"Bash(sort:*)",
"Bash(vcpkg search:*)",
"Bash(D:/vcpkg/vcpkg search glad)",
"Bash(timeout 8 ./mosis-designer.exe:*)",
"Bash(start mosis-designer.exe apps/home/home.rml)",
"Bash(./mosis-designer.exe:*)",
"Bash(cmd /c \"cd /d D:\\\\Dev\\\\Mosis\\\\MosisService\\\\designer && cmake --build build --config Debug\")",
"Bash(cmd /c:*)",
"Bash(\"D:/Dev/Mosis/MosisService/designer-test/build/Debug/designer-test.exe\")",
"Bash(python:*)",
"Bash(./gradlew connectedAndroidTest:*)",
"Bash(\"D:\\\\Epic\\\\UE_5.5\\\\Engine\\\\Build\\\\BatchFiles\\\\Build.bat\" MosisUnreal Android Development -Project=\"D:\\\\Dev\\\\Mosis\\\\MosisUnreal\\\\MosisUnreal.uproject\")",
"Bash(\"D:\\\\Epic\\\\UE_5.5\\\\Engine\\\\Build\\\\BatchFiles\\\\Build.bat\" MosisUnreal Win64 Development -Project=\"D:\\\\Dev\\\\Mosis\\\\MosisUnreal\\\\MosisUnreal.uproject\")",
"Bash(D:)",
"Bash(\"D:\\\\Epic\\\\UE_5.5\\\\Engine\\\\Build\\\\BatchFiles\\\\Build.bat\" MosisUnrealEditor Win64 Development -Project=\"D:\\\\Dev\\\\Mosis\\\\MosisUnreal\\\\MosisUnreal.uproject\")",
"Bash(\"D:\\\\Epic\\\\UE_5.5\\\\Engine\\\\Build\\\\BatchFiles\\\\RunUAT.bat\" BuildCookRun -project=\"D:\\\\Dev\\\\Mosis\\\\MosisUnreal\\\\MosisUnreal.uproject\" -platform=Android -clientconfig=Development -build -noP4 -utf8output)",
"Bash(\"D:\\\\Epic\\\\UE_5.5\\\\Engine\\\\Build\\\\BatchFiles\\\\RunUAT.bat\" BuildCookRun -project=\"D:\\\\Dev\\\\Mosis\\\\MosisUnreal\\\\MosisUnreal.uproject\" -platform=Android -clientconfig=Development -build -cook -stage -pak -package -noP4 -utf8output)",
"Bash(./gradlew.bat:*)",
"Bash(gradle assembleRelease:*)",
"Bash(\"C:\\\\Program Files\\\\Unity\\\\Hub\\\\Editor\\\\6000.3.2f1\\\\Editor\\\\Unity.exe\" -batchmode -quit -nographics -projectPath \"D:\\\\Dev\\\\Mosis\\\\MosisVR\" -executeMethod BuildScript.BuildAndroidCI -outputPath \"D:\\\\Dev\\\\Mosis\\\\Builds\\\\Unity\\\\Android\\\\MosisVR.apk\" -logFile \"D:\\\\Dev\\\\Mosis\\\\Builds\\\\Unity\\\\build3.log\")",
"Bash(gradlew.bat assembleRelease)",
"Bash(\"C:\\\\Program Files\\\\Unity\\\\Hub\\\\Editor\\\\6000.3.2f1\\\\Editor\\\\Unity.exe\" -batchmode -quit -nographics -projectPath \"D:\\\\Dev\\\\Mosis\\\\MosisVR\" -executeMethod BuildScript.BuildAndroidDirectCI -outputPath \"D:\\\\Dev\\\\Mosis\\\\Builds\\\\Unity\\\\Android\\\\MosisVR-direct.apk\" -logFile \"D:\\\\Dev\\\\Mosis\\\\Builds\\\\Unity\\\\build-direct.log\")",
"Bash(gradlew assembleDebug:*)",
"Bash(\"D:\\\\Epic\\\\UE_5.5\\\\Engine\\\\Build\\\\BatchFiles\\\\RunUAT.bat\" BuildCookRun -project=\"D:\\\\Dev\\\\Mosis\\\\MosisUnreal\\\\MosisUnreal.uproject\" -platform=Android -clientconfig=Development -build -cook -stage -pak -package -archive -archivedirectory=\"D:\\\\Dev\\\\Mosis\\\\Builds\\\\Unreal\" -noP4)",
"Bash(adb:*)",
"Bash(\"D:/Epic/UE_5.5/Engine/Build/BatchFiles/RunUAT.bat\" BuildCookRun -project=\"D:/Dev/Mosis/MosisUnreal/MosisUnreal.uproject\" -platform=Android -clientconfig=Development -build -cook -stage -pak -package -noP4)",
"Bash(MSYS_NO_PATHCONV=1 adb:*)",
"Bash(\"D:/Epic/UE_5.5/Engine/Build/BatchFiles/Build.bat\" MosisUnrealEditor Win64 Development -Project=\"D:/Dev/Mosis/MosisUnreal/MosisUnreal.uproject\")",
"Bash(\"D:/Epic/UE_5.5/Engine/Build/BatchFiles/Build.bat\" MosisUnreal Android Development -Project=\"D:/Dev/Mosis/MosisUnreal/MosisUnreal.uproject\")",
"Bash(\"D:/Epic/UE_5.5/Engine/Build/BatchFiles/RunUAT.bat\" BuildCookRun -project=\"D:/Dev/Mosis/MosisUnreal/MosisUnreal.uproject\" -platform=Android -clientconfig=Development -build -cook -stage -pak -package -noP4 -utf8output)",
"Bash(git -C \"D:/Dev/Mosis/MosisUnreal\" add Plugins/MosisSDK/MosisSDK.uplugin Plugins/MosisSDK/Source/MosisSDK/MosisSDK.Build.cs Plugins/MosisSDK/Source/MosisSDK/Private/MosisPhoneActor.cpp Plugins/MosisSDK/Source/MosisSDK/Private/MosisPointerComponent.cpp Plugins/MosisSDK/Source/MosisSDK/Public/MosisPointerComponent.h)"
],
"additionalDirectories": [
"D:\\Dev\\Mosis\\MosisUnreal",
"D:\\Dev\\Mosis\\MosisVR"
]
}
}

View File

@@ -1,96 +0,0 @@
# MosisService Architecture
## Overview
MosisService is an Android application that combines Kotlin UI components with native C++ libraries for UI rendering and system interaction. The architecture is built around a service-oriented design using Android's Binder system and integrates with RmlUi for rich UI rendering.
## Core Components
### 1. Service Layer (mosis-service)
- **Purpose**: Main Android Binder service implementation
- **Interface**: Implements `IMosisService.aidl`
- **Functionality**:
- Touch event processing (onTouchDown, onTouchMove, onTouchUp)
- Service initialization and listener management
- Integration with kernel-based rendering system
- Asset loading through Android AssetManager
### 2. Rendering Layer (mosis-test)
- **Purpose**: UI rendering and testing infrastructure
- **Interface**: Implements `IMosisListener.aidl`
- **Functionality**:
- OpenGL ES 2.0 rendering pipeline using GLAD
- Multi-threaded rendering with EGL context management
- Surface and buffer handling for Android native windows
- Task queue management for asynchronous rendering operations
- Hardware buffer management and processing
### 3. Core Engine (Kernel)
- **Purpose**: Central rendering and event processing engine
- **Components**:
- RmlUi-based UI rendering engine
- EGL context creation and management
- Render target handling
- Touch event processing and UI updates
- Multi-threaded execution using std::thread
### 4. Supporting Libraries
- **AssetsManager**: Asset loading from Android AssetManager
- **Logger**: Cross-platform logging system
- **EGL Context**: OpenGL ES context management
- **Render Target**: Framebuffer and buffer management
- **Shader**: OpenGL shader program handling
- **External Texture**: Hardware buffer texture creation
## System Architecture
### Android Binder Integration
- Uses AIDL (Android Interface Definition Language) for cross-process communication
- Service exposes `IMosisService` interface for touch events and initialization
- Listener pattern with `IMosisListener` for rendering callbacks
- Binds to Java/Kotlin components through JNI
### Multi-threading Model
- Service layer runs in main thread for event handling
- Rendering loop runs in dedicated thread managed by Kernel
- Async task processing for UI updates
- Thread-safe communication between components
### Rendering Pipeline
1. **Initialization**: Service connects to test layer, creates EGL context
2. **Buffer Management**: Hardware buffers allocated and shared between layers
3. **Event Processing**: Touch events processed and forwarded to Kernel
4. **Rendering Loop**: Continuous rendering with frame synchronization
5. **UI Updates**: RmlUi engine updates UI based on events and data
### Data Flow
```
User Touch Events → IMosisService.onTouch* → Kernel.process → RmlUi UI Updates
Service Initialized → IMosisListener.onServiceInitialized → Rendering Setup
Buffer Available → IMosisListener.onBufferAvailable → Texture Creation
Frame Available → IMosisListener.onFrameAvailable → Frame Rendering
```
## Key Technologies
- **Android NDK**: Native development with C++23
- **CMake**: Build system for native libraries
- **RmlUi**: UI rendering engine with HTML/CSS-like markup
- **OpenGL ES 2.0**: Graphics rendering
- **GLAD**: OpenGL loader library
- **AIDL**: Inter-process communication
- **Binder**: Android's IPC mechanism
## File Structure
- `mosis-service.cpp`: Main service implementation
- `mosis-test.cpp`: Test/rendering implementation
- `kernel.cpp`: Core rendering engine and event processing
- `assets_manager.cpp`: Asset loading utilities
- `egl_context.cpp`: EGL context management
- `render_target.cpp`: Framebuffer handling
- `logger.cpp`: Cross-platform logging
## Code Standards
- Modern C++23 with smart pointers and RAII
- Thread-safe operations using std::mutex
- Resource management with proper cleanup
- Use of std::span, std::format for modern features
- Cross-platform compatibility through Android NDK

1254
CLAUDE.md

File diff suppressed because it is too large Load Diff

3569
SANDBOX.md

File diff suppressed because it is too large Load Diff

View File

@@ -55,6 +55,14 @@ target_compile_definitions(mosis-kernel PUBLIC
RMLUI_GL3_CUSTOM_LOADER="glad_loader.h"
)
# Sandbox sources (reuse from MosisService)
set(SANDBOX_SOURCES
../src/main/cpp/sandbox/timer_manager.cpp
../src/main/cpp/sandbox/json_api.cpp
../src/main/cpp/sandbox/crypto_api.cpp
../src/main/cpp/sandbox/virtual_fs.cpp
)
# Designer executable
add_executable(mosis-designer
main.cpp
@@ -62,6 +70,7 @@ add_executable(mosis-designer
src/desktop_file_interface.cpp
src/hot_reload.cpp
src/platform_singleton.cpp
src/desktop_sandbox.cpp
src/testing/action_recorder.cpp
src/testing/action_player.cpp
src/testing/ui_inspector.cpp
@@ -69,6 +78,8 @@ add_executable(mosis-designer
# Local backend with input recording hooks
src/backend/RmlUi_Backend_GLFW_GL3.cpp
src/backend/RmlUi_Platform_GLFW.cpp
# Sandbox APIs
${SANDBOX_SOURCES}
)
target_include_directories(mosis-designer PRIVATE
@@ -77,6 +88,7 @@ target_include_directories(mosis-designer PRIVATE
src/backend
../src/main/kernel/include
../src/main/cpp
../src/main/cpp/sandbox
)
target_link_libraries(mosis-designer PRIVATE

View File

@@ -22,6 +22,7 @@
#include <memory>
#include <chrono>
#include <ctime>
#include "src/desktop_sandbox.h"
namespace fs = std::filesystem;
@@ -34,6 +35,7 @@ static mosis::HotReload* g_hot_reload = nullptr;
static std::string g_current_document_path;
static std::string g_current_screen_url; // For hierarchy dump - tracks current screen
static bool g_needs_reload = false;
static std::unique_ptr<mosis::DesktopSandbox> g_sandbox;
// Resolution presets
static int g_width = 540;
@@ -126,36 +128,28 @@ static void MouseButtonCallback(GLFWwindow* window, int button, int action, int
double xpos, ypos;
glfwGetCursorPos(window, &xpos, &ypos);
// Convert from physical (GLFW) to logical (RmlUi) coordinates
// GLFW reports physical pixels, but RmlUi context uses logical pixels
float scaleX, scaleY;
glfwGetWindowContentScale(window, &scaleX, &scaleY);
int logicalX = static_cast<int>(xpos / scaleX);
int logicalY = static_cast<int>(ypos / scaleY);
// Debug log for click detection
Rml::Log::Message(Rml::Log::LT_INFO, "Mouse %s at physical(%.0f, %.0f) logical(%d, %d) button=%d",
action == GLFW_PRESS ? "down" : "up", xpos, ypos, logicalX, logicalY, button);
// GLFW cursor callbacks report coordinates in screen/window coordinates (logical pixels)
// which match the RmlUi context dimensions, so no scaling needed
int mouseX = static_cast<int>(xpos);
int mouseY = static_cast<int>(ypos);
int key_modifier = 0;
if (mods & GLFW_MOD_CONTROL) key_modifier |= Rml::Input::KM_CTRL;
if (mods & GLFW_MOD_SHIFT) key_modifier |= Rml::Input::KM_SHIFT;
if (mods & GLFW_MOD_ALT) key_modifier |= Rml::Input::KM_ALT;
// Update mouse position before processing button event (using logical coords)
g_context->ProcessMouseMove(logicalX, logicalY, key_modifier);
// Update mouse position before processing button event
g_context->ProcessMouseMove(mouseX, mouseY, key_modifier);
if (button == GLFW_MOUSE_BUTTON_LEFT) {
if (action == GLFW_PRESS) {
// Record mouse down in record mode (use logical coords)
if (g_action_recorder && g_action_recorder->IsRecording()) {
g_action_recorder->RecordMouseDown(logicalX, logicalY);
g_action_recorder->RecordMouseDown(mouseX, mouseY);
}
g_context->ProcessMouseButtonDown(0, key_modifier);
} else if (action == GLFW_RELEASE) {
// Record mouse up in record mode
if (g_action_recorder && g_action_recorder->IsRecording()) {
g_action_recorder->RecordMouseUp(logicalX, logicalY);
g_action_recorder->RecordMouseUp(mouseX, mouseY);
}
g_context->ProcessMouseButtonUp(0, key_modifier);
}
@@ -165,13 +159,12 @@ static void MouseButtonCallback(GLFWwindow* window, int button, int action, int
static void CursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
if (!g_context) return;
// Convert from physical to logical coordinates
float scaleX, scaleY;
glfwGetWindowContentScale(window, &scaleX, &scaleY);
int logicalX = static_cast<int>(xpos / scaleX);
int logicalY = static_cast<int>(ypos / scaleY);
// GLFW cursor callbacks report coordinates in screen/window coordinates (logical pixels)
// which match the RmlUi context dimensions, so no scaling needed
int mouseX = static_cast<int>(xpos);
int mouseY = static_cast<int>(ypos);
g_context->ProcessMouseMove(logicalX, logicalY, 0);
g_context->ProcessMouseMove(mouseX, mouseY, 0);
}
static void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset) {
@@ -291,10 +284,11 @@ int main(int argc, char* argv[]) {
if (!assets_path_specified) {
fs::path doc_path = fs::absolute(document_path);
fs::path current = doc_path.parent_path();
fs::path prev_path;
// Walk up the directory tree looking for a folder that ends with "assets"
// or contains typical asset folders like "apps", "ui", "fonts"
while (!current.empty() && current.has_parent_path()) {
while (!current.empty() && current != prev_path) {
std::string folder_name = current.filename().string();
if (folder_name == "assets") {
assets_path = current.string();
@@ -305,12 +299,38 @@ int main(int argc, char* argv[]) {
assets_path = current.string();
break;
}
prev_path = current;
current = current.parent_path();
}
// Fall back to "assets" relative to executable if not found
// Fall back options if no standard assets folder found
if (assets_path.empty()) {
assets_path = "assets";
// If the document exists, use its parent directory as assets path
fs::path doc_path = fs::absolute(document_path);
if (fs::exists(doc_path)) {
assets_path = doc_path.parent_path().string();
} else {
// Try executable's assets folder
fs::path exe_path = fs::path(argv[0]).parent_path();
if (exe_path.empty()) {
exe_path = ".";
}
fs::path exe_assets = fs::absolute(exe_path) / "assets";
if (fs::exists(exe_assets)) {
assets_path = exe_assets.string();
} else {
// Last resort: current directory
assets_path = fs::absolute(".").string();
}
}
}
}
// Make document_path absolute if it's relative and exists
if (!fs::path(document_path).is_absolute()) {
fs::path abs_doc = fs::absolute(document_path);
if (fs::exists(abs_doc)) {
document_path = abs_doc.string();
}
}
@@ -452,8 +472,7 @@ int main(int argc, char* argv[]) {
g_render_interface->EndFrame(0);
}
glfwSwapBuffers(g_window);
// Capture screenshot BEFORE swap (glReadPixels reads from back buffer)
if (g_test_mode == TestMode::Screenshot) {
mosis::testing::VisualCapture capture(fb_width, fb_height);
if (capture.CaptureScreenshot(g_test_output_path)) {
@@ -470,6 +489,8 @@ int main(int argc, char* argv[]) {
}
}
glfwSwapBuffers(g_window);
// Cleanup and exit
ShutdownRmlUi();
glfwDestroyWindow(g_window);
@@ -503,6 +524,11 @@ int main(int argc, char* argv[]) {
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Update sandbox (process timers)
if (g_sandbox) {
g_sandbox->Update();
}
// Update and render
if (g_context) {
g_context->Update();
@@ -541,6 +567,7 @@ int main(int argc, char* argv[]) {
if (g_log_file.is_open()) {
g_log_file.close();
}
g_sandbox.reset();
ShutdownRmlUi();
glfwDestroyWindow(g_window);
glfwTerminate();
@@ -572,8 +599,16 @@ bool InitializeRmlUi(const std::string& assets_path, int fb_width, int fb_height
// Initialize Lua bindings
Rml::Lua::Initialise();
// Register loadScreen function for navigation
// Get Lua state
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
// Initialize sandbox with APIs (timers, JSON, crypto, fs)
mosis::DesktopSandboxConfig sandbox_config;
sandbox_config.data_root = assets_path + "/sandbox_data";
g_sandbox = std::make_unique<mosis::DesktopSandbox>(sandbox_config);
g_sandbox->RegisterAPIs(L);
// Register loadScreen function for navigation
lua_pushcfunction(L, [](lua_State* L) -> int {
const char* path = luaL_checkstring(L, 1);
@@ -610,15 +645,33 @@ bool InitializeRmlUi(const std::string& assets_path, int fb_width, int fb_height
lua_setglobal(L, "loadScreen");
std::cout << "Registered Lua loadScreen function" << std::endl;
// Load fonts
std::vector<std::string> fonts = {
"fonts/LatoLatin-Regular.ttf",
"fonts/LatoLatin-Bold.ttf",
"fonts/LatoLatin-Light.ttf",
// Load fonts - search for fonts directory in multiple locations
std::string fonts_root;
std::vector<std::string> font_search_paths = {
assets_path + "/fonts",
assets_path + "/../src/main/assets/fonts", // If assets_path is test-app dir
assets_path + "/../../src/main/assets/fonts",
std::filesystem::absolute("src/main/assets/fonts").string(),
};
for (const auto& font : fonts) {
if (!Rml::LoadFontFace(font)) {
std::cerr << "Warning: Failed to load font: " << font << std::endl;
for (const auto& search_path : font_search_paths) {
if (std::filesystem::exists(search_path + "/LatoLatin-Regular.ttf")) {
fonts_root = search_path;
break;
}
}
if (fonts_root.empty()) {
std::cerr << "Warning: Could not find fonts directory" << std::endl;
} else {
std::cout << "Fonts path: " << fonts_root << std::endl;
std::vector<std::string> fonts = {
fonts_root + "/LatoLatin-Regular.ttf",
fonts_root + "/LatoLatin-Bold.ttf",
fonts_root + "/LatoLatin-Italic.ttf",
};
for (const auto& font : fonts) {
if (!Rml::LoadFontFace(font)) {
std::cerr << "Warning: Failed to load font: " << font << std::endl;
}
}
}
@@ -676,7 +729,15 @@ bool LoadDocument(const std::string& path) {
void ReloadDocument() {
std::cout << "Reloading..." << std::endl;
// Reset sandbox (clear timers, re-register APIs)
if (g_sandbox) {
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
g_sandbox->UnregisterAPIs(L);
g_sandbox->Reset();
g_sandbox->RegisterAPIs(L);
}
// Reload stylesheets
for (int i = 0; i < g_context->GetNumDocuments(); ++i) {
auto* doc = g_context->GetDocument(i);

View File

@@ -0,0 +1,167 @@
#include "desktop_sandbox.h"
#include "timer_manager.h"
#include "json_api.h"
#include "crypto_api.h"
#include "virtual_fs.h"
#include <lua.hpp>
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
namespace mosis {
DesktopSandbox::DesktopSandbox(const DesktopSandboxConfig& config)
: m_config(config) {
// Create timer manager
m_timer_manager = std::make_unique<TimerManager>();
// Create virtual filesystem
// Ensure data root exists
std::error_code ec;
fs::create_directories(m_config.data_root, ec);
m_vfs = std::make_unique<VirtualFS>(
m_config.app_id,
m_config.data_root,
VirtualFSLimits{}
);
std::cout << "DesktopSandbox initialized for app: " << m_config.app_id << std::endl;
std::cout << " Data root: " << m_config.data_root << std::endl;
}
DesktopSandbox::~DesktopSandbox() {
std::cout << "DesktopSandbox destroyed" << std::endl;
}
// crypto.sha256(data) -> hash
// Convenience wrapper for crypto.hash("sha256", data)
static int lua_crypto_sha256(lua_State* L) {
// Get the crypto table
lua_getglobal(L, "crypto");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return luaL_error(L, "crypto table not found");
}
// Get crypto.hash function
lua_getfield(L, -1, "hash");
if (!lua_isfunction(L, -1)) {
lua_pop(L, 2);
return luaL_error(L, "crypto.hash function not found");
}
// Call hash("sha256", data)
lua_pushstring(L, "sha256");
lua_pushvalue(L, 1); // Push the original data argument
lua_call(L, 2, 1);
// Remove the crypto table from stack, keep result
lua_remove(L, -2);
return 1;
}
// crypto.sha512(data) -> hash
static int lua_crypto_sha512(lua_State* L) {
lua_getglobal(L, "crypto");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return luaL_error(L, "crypto table not found");
}
lua_getfield(L, -1, "hash");
if (!lua_isfunction(L, -1)) {
lua_pop(L, 2);
return luaL_error(L, "crypto.hash function not found");
}
lua_pushstring(L, "sha512");
lua_pushvalue(L, 1);
lua_call(L, 2, 1);
lua_remove(L, -2);
return 1;
}
void DesktopSandbox::RegisterAPIs(lua_State* L) {
std::cout << "Registering sandbox APIs..." << std::endl;
// Register timer API (setTimeout, setInterval, clearTimeout, clearInterval)
RegisterTimerAPI(L, m_timer_manager.get(), m_config.app_id);
std::cout << " Timer API registered" << std::endl;
// Register JSON API (json.encode, json.decode)
RegisterJsonAPI(L, JsonLimits{});
std::cout << " JSON API registered" << std::endl;
// Register crypto API (crypto.randomBytes, crypto.hash, crypto.hmac)
RegisterCryptoAPI(L);
// Add convenience wrappers for common hash algorithms
lua_getglobal(L, "crypto");
if (lua_istable(L, -1)) {
lua_pushcfunction(L, lua_crypto_sha256);
lua_setfield(L, -2, "sha256");
lua_pushcfunction(L, lua_crypto_sha512);
lua_setfield(L, -2, "sha512");
}
lua_pop(L, 1);
std::cout << " Crypto API registered" << std::endl;
// Register virtual filesystem API (fs.read, fs.write, fs.delete, etc.)
RegisterVirtualFS(L, m_vfs.get());
std::cout << " VirtualFS API registered" << std::endl;
std::cout << "All sandbox APIs registered" << std::endl;
}
void DesktopSandbox::UnregisterAPIs(lua_State* L) {
// Clear the global tables by setting them to nil
// This ensures clean state on hot-reload
lua_pushnil(L);
lua_setglobal(L, "setTimeout");
lua_pushnil(L);
lua_setglobal(L, "clearTimeout");
lua_pushnil(L);
lua_setglobal(L, "setInterval");
lua_pushnil(L);
lua_setglobal(L, "clearInterval");
lua_pushnil(L);
lua_setglobal(L, "json");
lua_pushnil(L);
lua_setglobal(L, "crypto");
lua_pushnil(L);
lua_setglobal(L, "fs");
std::cout << "Sandbox APIs unregistered" << std::endl;
}
void DesktopSandbox::Update() {
// Process any pending timers
if (m_timer_manager) {
m_timer_manager->ProcessTimers();
}
}
void DesktopSandbox::Reset() {
// Clear all timers for the app
if (m_timer_manager) {
m_timer_manager->ClearAppTimers(m_config.app_id);
std::cout << "Timers cleared for hot-reload" << std::endl;
}
// Optionally clear temp files
if (m_vfs) {
m_vfs->ClearTemp();
}
}
} // namespace mosis

View File

@@ -0,0 +1,33 @@
#pragma once
#include <string>
#include <memory>
struct lua_State;
namespace mosis {
class TimerManager;
class VirtualFS;
struct DesktopSandboxConfig {
std::string app_id = "com.mosis.designer";
std::string data_root = "./sandbox_data";
};
class DesktopSandbox {
public:
explicit DesktopSandbox(const DesktopSandboxConfig& config);
~DesktopSandbox();
void RegisterAPIs(lua_State* L);
void UnregisterAPIs(lua_State* L);
void Update(); // Process timers - call each frame
void Reset(); // For hot-reload
private:
DesktopSandboxConfig m_config;
std::unique_ptr<TimerManager> m_timer_manager;
std::unique_ptr<VirtualFS> m_vfs;
};
} // namespace mosis

View File

@@ -22,14 +22,24 @@ void HotReload::Start() {
m_running = true;
#ifdef _WIN32
// Create a manual-reset event for signaling shutdown
m_stop_event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
if (!m_stop_event) {
std::cerr << "Failed to create stop event" << std::endl;
m_running = false;
return;
}
m_notification_handle = FindFirstChangeNotificationW(
m_watch_path.c_str(),
TRUE, // Watch subtree
FILE_NOTIFY_CHANGE_LAST_WRITE
);
if (m_notification_handle == INVALID_HANDLE_VALUE) {
std::cerr << "Failed to set up file watching" << std::endl;
CloseHandle(m_stop_event);
m_stop_event = nullptr;
m_running = false;
return;
}
@@ -39,6 +49,12 @@ void HotReload::Start() {
void HotReload::Stop() {
m_running = false;
#ifdef _WIN32
// Signal the stop event to wake up the watch thread
if (m_stop_event) {
SetEvent(m_stop_event);
}
#endif
if (m_watch_thread.joinable()) {
m_watch_thread.join();
}
@@ -47,21 +63,33 @@ void HotReload::Stop() {
FindCloseChangeNotification(m_notification_handle);
m_notification_handle = nullptr;
}
if (m_stop_event) {
CloseHandle(m_stop_event);
m_stop_event = nullptr;
}
#endif
}
void HotReload::WatchThread() {
while (m_running) {
#ifdef _WIN32
DWORD result = WaitForSingleObject(m_notification_handle, 100); // 100ms timeout
// Wait on both the file change notification and the stop event
HANDLE handles[2] = { m_notification_handle, m_stop_event };
DWORD result = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
if (result == WAIT_OBJECT_0) {
// File change notification
// Debounce - wait a bit for file writes to complete
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (m_callback) {
if (m_callback && m_running) {
m_callback();
}
FindNextChangeNotification(m_notification_handle);
} else if (result == WAIT_OBJECT_0 + 1) {
// Stop event signaled - exit the loop
break;
}
// WAIT_FAILED or other errors will just loop and check m_running
#else
// TODO: Linux inotify / macOS FSEvents implementation
std::this_thread::sleep_for(std::chrono::milliseconds(100));

View File

@@ -29,6 +29,7 @@ private:
#ifdef _WIN32
void* m_notification_handle = nullptr; // HANDLE
void* m_stop_event = nullptr; // HANDLE for signaling shutdown
#endif
};

129
docs/ANDROID-TESTING.md Normal file
View File

@@ -0,0 +1,129 @@
# Android Device Testing
## Prerequisites
```bash
# Check connected device
adb devices -l
# Verify Mosis app is installed
adb shell pm list packages | grep mosis
```
## Build and Install
```bash
# Build debug APK
./gradlew assembleDebug
# Install on device
adb install -r build/outputs/apk/debug/MosisService-debug.apk
# Launch the app
adb shell am start -n com.omixlab.mosis/.MainActivity
```
## Run Gradle Connected Tests
```bash
# Run all connected Android tests
./gradlew connectedAndroidTest
```
## Event Injection via ADB
Inject touch events for automated testing:
```bash
# Click at normalized coordinates (0.0-1.0)
adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \
--es touch_type "click" --ef x 0.5 --ef y 0.5
# Touch down
adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \
--es touch_type "down" --ef x 0.2 --ef y 0.9
# Touch up
adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \
--es touch_type "up" --ef x 0.2 --ef y 0.9
```
## Dock Element Coordinates (Normalized)
| Element | X | Y |
|---------|---|---|
| dock-phone | 0.16 | 0.97 |
| dock-messages | 0.39 | 0.97 |
| dock-contacts | 0.61 | 0.97 |
| dock-browser | 0.84 | 0.97 |
| back-button | 0.10 | 0.05 |
## Full Navigation Test Sequence
```bash
# Clear logs and run navigation test sequence
adb logcat -c
# Click Phone dock icon
adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \
--es touch_type "click" --ef x 0.16 --ef y 0.97
sleep 2
# Click back to return home
adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \
--es touch_type "click" --ef x 0.1 --ef y 0.05
sleep 2
# Click Messages dock icon
adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \
--es touch_type "click" --ef x 0.39 --ef y 0.97
sleep 2
# Click back to return home
adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \
--es touch_type "click" --ef x 0.1 --ef y 0.05
sleep 2
# Click Contacts dock icon
adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \
--es touch_type "click" --ef x 0.61 --ef y 0.97
sleep 2
# Click back to return home
adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \
--es touch_type "click" --ef x 0.1 --ef y 0.05
sleep 2
# Click Browser dock icon
adb shell am broadcast -a com.omixlab.mosis.INJECT_TOUCH \
--es touch_type "click" --ef x 0.84 --ef y 0.97
```
## Reading Logs
```bash
# Filter for Mosis logs
adb logcat -s MosisTest ServiceTester RMLUI
# Filter for navigation events
adb logcat -d | grep -iE "navigat|loaded|goBack|rml"
# Save to file
adb logcat -s MosisTest > mosis-log.txt
# Clear logs
adb logcat -c
```
## Expected Log Output
Successful navigation shows these log patterns:
```
RMLUI: navigateTo called with: dialer
Loading screen: apps/dialer/dialer.rml
RMLUI: Navigated to: dialer (history depth: 1)
RMLUI: goBack called (history depth: 1)
Loading screen: apps/home/home.rml
RMLUI: Back to: home
```

99
docs/APP-MANAGEMENT.md Normal file
View File

@@ -0,0 +1,99 @@
# App Management System
The device-side app management system handles installation, updates, and launching of third-party apps.
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ AppManager │
│ - Install/Uninstall apps from .mosis packages │
│ - Track installed apps in JSON registry │
│ - Manage app data/cache directories │
└─────────────────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ LuaSandboxManager │
│ - StartApp/StopApp lifecycle │
│ - Per-app isolated Lua environments │
│ - Resource limits (memory, CPU, timers) │
└─────────────────────────────────────────────────────────────┘
```
## App Package Format (.mosis)
Apps are distributed as ZIP files with `.mosis` extension:
```
myapp.mosis
├── manifest.json # Required: app metadata
├── main.rml # Entry point (RmlUi document)
├── styles.rcss # Stylesheets
├── scripts/ # Lua scripts
│ └── app.lua
└── assets/ # Icons, images, etc.
```
## Manifest Format
```json
{
"id": "com.example.myapp",
"name": "My App",
"version": "1.0.0",
"version_code": 1,
"entry": "main.rml",
"icon": "icon.tga",
"description": "App description",
"developer": {
"name": "Developer Name",
"email": "dev@example.com"
},
"permissions": [
"network",
"storage",
"camera"
],
"min_api_version": 1
}
```
## App Lifecycle
```cpp
// Install from local file
app_manager->InstallFromFile("/path/to/app.mosis", [](auto progress) {
LOG_INFO("Install progress: %s %.0f%%",
InstallProgress::StageName(progress.stage),
progress.progress * 100);
});
// Launch app (starts sandbox)
app_manager->LaunchApp("com.example.myapp");
// Check if running
bool running = app_manager->IsAppRunning("com.example.myapp");
// Stop app (cleanup sandbox)
app_manager->StopApp("com.example.myapp");
// Uninstall (stops if running, removes files)
app_manager->Uninstall("com.example.myapp", false); // keep_data=false
```
## Directory Structure
```
/data/data/com.omixlab.mosis/files/
├── apps/
│ └── com.example.myapp/
│ ├── package/ # Extracted app files
│ ├── data/ # App persistent data (VirtualFS)
│ ├── cache/ # App cache (clearable)
│ └── db/ # SQLite databases
├── downloads/ # Temporary download location
├── backups/ # App data backups
└── config/
└── apps.json # Installed apps registry
```

105
docs/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,105 @@
# Architecture Overview
MosisService is an Android application combining Kotlin UI with native C++ libraries for UI rendering via Android's Binder IPC system.
## Two Native Libraries
**mosis-service** (`libmosis-service.so`):
- Main Android Binder service implementation
- Implements `IMosisService.aidl` interface for touch events and initialization
- Contains the Kernel rendering engine with RmlUi integration
- Links against RmlUi for HTML/CSS-like UI rendering
**mosis-test** (`libmosis-test.so`):
- Test/rendering client implementation
- Implements `IMosisListener.aidl` for receiving callbacks
- OpenGL ES 2.0 rendering pipeline using GLAD
## IPC Flow
```
Kotlin NativeService → JNI → mosis-service (IMosisService)
IMosisListener callbacks
mosis-test (rendering client)
```
## Key Interfaces (AIDL)
`IMosisService`: `initOS()`, `onTouchDown()`, `onTouchMove()`, `onTouchUp()`
`IMosisListener` (oneway/async): `onServiceInitialized()`, `onBufferAvailable()`, `onFrameAvailable()`
## Multi-threading Model
- Service layer runs in main thread for event handling
- Rendering loop runs in dedicated thread managed by Kernel
- Async task processing for UI updates
- Thread-safe communication between components using std::mutex
## Rendering Pipeline
1. **Initialization**: Service connects to test layer, creates EGL context
2. **Buffer Management**: Hardware buffers allocated and shared between layers
3. **Event Processing**: Touch events processed and forwarded to Kernel
4. **Rendering Loop**: Continuous rendering with frame synchronization
5. **UI Updates**: RmlUi engine updates UI based on events and data
### Data Flow
```
User Touch Events → IMosisService.onTouch* → Kernel.process → RmlUi UI Updates
Service Initialized → IMosisListener.onServiceInitialized → Rendering Setup
Buffer Available → IMosisListener.onBufferAvailable → Texture Creation
Frame Available → IMosisListener.onFrameAvailable → Frame Rendering
```
## Native Code Structure (src/main/cpp/)
**Core:**
- `kernel.cpp` - Core rendering engine, RmlUi integration, event processing
- `mosis-service.cpp` - Binder service implementation, JNI entry points
- `mosis-test.cpp` - Test client implementation
- `egl_context.cpp` - OpenGL ES context management
- `render_target.cpp` - Framebuffer and buffer management
- `RmlUi_Renderer_GL3.cpp` - RmlUi OpenGL renderer backend
- `assets_manager.cpp` - Android AssetManager integration
**App Management (`apps/`):**
- `app_manager.cpp` - App install/uninstall/launch lifecycle
- `app_api.cpp` - Lua API bindings for app management
- `update_service.cpp` - Background update checking
**Lua Sandbox (`sandbox/`):**
- `sandbox_manager.cpp` - Multi-app sandbox orchestrator
- `lua_sandbox.cpp` - Core Lua sandbox with resource limits
- `permission_gate.cpp` - Permission system (normal/dangerous/signature)
- `virtual_fs.cpp` - Per-app virtual filesystem with quotas
- `database_manager.cpp` - SQLite database per app
- `network_manager.cpp` - HTTP request validation
- `websocket_manager.cpp` - WebSocket connections
- `timer_manager.cpp` - setTimeout/setInterval implementation
- `json_api.cpp` - Safe JSON encode/decode
- `crypto_api.cpp` - Cryptographic functions (SHA256, HMAC)
- `camera_interface.cpp` - Camera access with indicators
- `microphone_interface.cpp` - Microphone access with indicators
- `audio_output.cpp` - Audio playback
- `location_interface.cpp` - GPS with precision reduction
- `sensor_interface.cpp` - Accelerometer, gyroscope, etc.
- `bluetooth_interface.cpp` - Bluetooth device access
- `contacts_interface.cpp` - Contacts read/write
- `message_bus.cpp` - Inter-app communication
## Code Style
- C++23 standard with modern features (std::span, std::format)
- PascalCase for classes/functions, camelCase for variables
- RAII principles with smart pointers
- Kotlin code follows Android conventions
## Dependencies
- vcpkg manages native dependencies (RmlUi, GLFW, Freetype, Lua, libpng, nlohmann-json, minizip, sqlite3)
- CMake build system with vcpkg toolchain integration
- Android target architecture: arm64-v8a only
- Desktop target: Windows x64 (MSVC)

86
docs/BUILD-COMMANDS.md Normal file
View File

@@ -0,0 +1,86 @@
# Build Commands
## Android (Gradle)
```bash
# Build entire project
./gradlew build
# Build debug APK
./gradlew assembleDebug
# Build release APK
./gradlew assembleRelease
# Clean build outputs
./gradlew clean
# Build with verbose output
./gradlew build --info --stacktrace
# Run lint checks
./gradlew lint
# Run unit tests
./gradlew test
# Run connected device tests
./gradlew connectedAndroidTest
```
## Desktop Designer (CMake)
```bash
# Configure (from designer/ folder)
cmake -B build -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake
# Build
cmake --build build --config Debug
# Run with hot-reload
./build/Debug/mosis-designer.exe ../src/main/assets/apps/home/home.rml
# Run with logging and hierarchy dump
./build/Debug/mosis-designer.exe ../src/main/assets/apps/home/home.rml --log output.log --hierarchy hierarchy.json
```
## Designer Tests (CMake)
```bash
# Configure (from designer-test/ folder)
cmake -B build -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake
# Build
cmake --build build --config Debug
# Run tests
./build/Debug/designer-test.exe
```
## Sandbox Security Tests (CMake)
```bash
# Configure (from sandbox-test/ folder)
cmake -B build -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake
# Build
cmake --build build --config Debug
# Run all tests (uber command)
./run_tests.bat
# Or run directly
./build/Debug/sandbox-test.exe
# Run specific test
./build/Debug/sandbox-test.exe --test DangerousGlobals
./build/Debug/sandbox-test.exe --test Memory
./build/Debug/sandbox-test.exe --test CPU
```
## Environment Requirements
Required environment variables:
- `ANDROID_HOME` - Android SDK path
- `ANDROID_NDK_HOME` - Android NDK path (version 29.0.14206865)
- `VCPKG_ROOT` - vcpkg package manager root

106
docs/CLAUDE.md Normal file
View File

@@ -0,0 +1,106 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Company
**OmixLab LTD** - Package namespace: `com.omixlab`
## Git Commit Guidelines
**IMPORTANT**: When creating git commits:
- **DO NOT** add yourself as a co-author (no `Co-Authored-By` lines)
- **Commit messages must be a single line** - keep it concise and descriptive
## Project Overview
Mosis is a **virtual smartphone OS** for VR games and applications. It provides a phone-like device that users can interact with inside VR environments, with real smartphone functionality.
### Current Status
| Component | Status | Notes |
|-----------|--------|-------|
| MosisService | ✅ Working | RmlUi rendering, touch input, navigation |
| App Management | ✅ Working | Install/uninstall apps, sandbox integration |
| Lua Sandbox | ✅ Working | 149 security tests passing |
| Desktop Designer | ✅ Working | Hot-reload, hierarchy dump, recording |
| Designer Tests | ✅ 5/5 Passing | Navigation tests automated |
| MosisVR (Unity) | ✅ Building | OpenGL backend working, Vulkan in progress |
| MosisUnreal | ✅ Working | Vulkan texture import via UE5 RHI, phone actor with mesh |
### Project Components
| Component | Location | Purpose |
|-----------|----------|---------|
| Android Service | `src/main/` | Native service running RmlUi renderer |
| App Management | `src/main/cpp/apps/` | App install/uninstall/launch with sandbox |
| Lua Sandbox | `src/main/cpp/sandbox/` | Per-app Lua isolation (22 modules) |
| Desktop Designer | `designer/` | UI development with hot-reload |
| Designer Tests | `designer-test/` | Automated UI testing framework |
| Sandbox Tests | `sandbox-test/` | Lua sandbox security tests (149 tests) |
| UI Assets | `src/main/assets/` | Shared RML/RCSS/Lua assets |
## Detailed Documentation
All detailed documentation is in `docs/`:
| Document | Description |
|----------|-------------|
| [BUILD-COMMANDS.md](BUILD-COMMANDS.md) | Android, Desktop Designer, and test build commands |
| [ARCHITECTURE.md](ARCHITECTURE.md) | Native libraries, IPC flow, code structure |
| [DESKTOP-DESIGNER.md](DESKTOP-DESIGNER.md) | Hot-reload, recording, key files |
| [TESTING-FRAMEWORK.md](TESTING-FRAMEWORK.md) | Automated UI testing, writing tests |
| [UI-ASSETS.md](UI-ASSETS.md) | Asset structure, navigation system, element IDs |
| [MATERIAL-DESIGN.md](MATERIAL-DESIGN.md) | Icons, MDL components, usage guide |
| [ANDROID-TESTING.md](ANDROID-TESTING.md) | ADB commands, event injection, logs |
| [GAME-ENGINES.md](GAME-ENGINES.md) | Unreal & Unity integration, Vulkan import |
| [DEVELOPER-PORTAL.md](DEVELOPER-PORTAL.md) | Portal architecture, milestones |
| [APP-MANAGEMENT.md](APP-MANAGEMENT.md) | Package format, manifest, lifecycle |
| [LUA-SANDBOX.md](LUA-SANDBOX.md) | Security features, permissions, APIs |
## Quick Reference
### Environment Requirements
- `ANDROID_HOME` - Android SDK path
- `ANDROID_NDK_HOME` - Android NDK path (version 29.0.14206865)
- `VCPKG_ROOT` - vcpkg package manager root
### Common Build Commands
```bash
# Android APK
./gradlew assembleDebug
# Desktop Designer (from designer/)
cmake -B build -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake
cmake --build build --config Debug
# Sandbox Tests (from sandbox-test/)
./run_tests.bat
```
### Code Style
- C++23 standard with modern features (std::span, std::format)
- PascalCase for classes/functions, camelCase for variables
- RAII principles with smart pointers
- Kotlin code follows Android conventions
### Dependencies
- vcpkg: RmlUi, GLFW, Freetype, Lua, libpng, nlohmann-json, minizip, sqlite3
- Android: arm64-v8a only
- Desktop: Windows x64 (MSVC)
## Documentation Guidelines
**IMPORTANT**: Always document progress and new commands to avoid rediscovery.
| Content Type | Location |
|--------------|----------|
| General concepts, architecture | `MosisService/docs/CLAUDE.md` or `docs/` |
| Unreal plugin docs | `MosisUnreal/Plugins/MosisSDK/README.md` |
| Unity package docs | `MosisVR/Packages/com.omixlab.mosis_sdk/README.md` |
**DO NOT** put documentation in the root `D:\Dev\Mosis\` directory - it is not versioned.

40
docs/DESKTOP-DESIGNER.md Normal file
View File

@@ -0,0 +1,40 @@
# Desktop Designer
The desktop designer (`designer/`) provides rapid UI development with:
- **Hot-reload**: Automatically reloads when RML/RCSS/Lua files change
- **UI Hierarchy Dumping**: Exports element tree to JSON for inspection
- **Screenshot Capture**: PNG export via F12 key
- **Logging**: Detailed output for debugging navigation and events
- **Action Recording**: Record mouse/keyboard interactions to JSON
- **Action Playback**: Replay recorded interactions with timing
## Key Files
| File | Purpose |
|------|---------|
| `designer/src/main.cpp` | Main entry point, GLFW window, event loop |
| `designer/src/desktop_kernel.cpp` | RmlUi context management, rendering |
| `designer/src/testing/ui_inspector.cpp` | UI hierarchy JSON export |
| `designer/src/testing/visual_capture.cpp` | PNG screenshot capture and comparison |
| `designer/src/testing/action_recorder.cpp` | Record user interactions to JSON |
| `designer/src/testing/action_player.cpp` | Playback recorded actions |
| `designer/src/backend/RmlUi_Backend_GLFW_GL3.cpp` | GLFW backend with input hooks |
## Command Line Options
```
--log <path> Write logs to file
--hierarchy <path> Dump UI hierarchy JSON each frame
--dump Single-shot dump mode (screenshot + hierarchy)
--record <path> Enable recording mode (F5 to start/stop)
--playback <path> Play back recorded actions from JSON
```
## Keyboard Controls
| Key | Function |
|-----|----------|
| F5 | Start/stop recording (when --record is enabled) |
| F6 | Pause/resume playback (when --playback is enabled) |
| F12 | Take screenshot |

56
docs/DEVELOPER-PORTAL.md Normal file
View File

@@ -0,0 +1,56 @@
# Developer Portal
The Developer Portal is a web application for app developers to publish and manage their Mosis apps. Planning documents are in `DEV_PORTAL_M01-M12.md` files.
## Architecture Decisions
| Component | Technology | Rationale |
|-----------|------------|-----------|
| Backend | Go 1.22+ | Simple, fast, single binary deployment |
| Router | Chi | Lightweight, idiomatic Go HTTP routing |
| Database | SQLite + Litestream | Zero-ops, continuous backup to NAS storage |
| Frontend | htmx + Go templates | Server-rendered, minimal JS, fast |
| Storage | Synology NAS filesystem | Self-hosted, local volume mounts |
| Auth | OAuth2 (GitHub/Google) + JWT | golang-jwt for tokens, Ed25519 for signing |
| CLI | Go + Cobra | Cross-platform single binary |
| Docs | Hugo + Docsy | Static site served from /docs/ |
## Deployment Target
Self-hosted on Synology NAS via Docker:
```
/volume1/mosis/
├── data/
│ ├── portal.db # Main SQLite database
│ └── telemetry.db # Separate telemetry database
├── packages/ # Uploaded app packages
│ └── {dev_id}/{app_id}/{version}/
├── backups/ # Litestream replicas
└── docs/ # Hugo static site output
```
## Milestone Documents
| File | Topic | Status |
|------|-------|--------|
| DEV_PORTAL_M01_OVERVIEW.md | Project overview | Decided |
| DEV_PORTAL_M02_WEB_STACK.md | Go + Chi + htmx | Decided |
| DEV_PORTAL_M03_DATABASE.md | SQLite + Litestream | Decided |
| DEV_PORTAL_M04_AUTH.md | OAuth2 + JWT + Ed25519 | Decided |
| DEV_PORTAL_M05_FRONTEND.md | htmx + Go templates | Decided |
| DEV_PORTAL_M06_API.md | REST API design | Decided |
| DEV_PORTAL_M07_STORAGE.md | NAS filesystem | Decided |
| DEV_PORTAL_M08_TELEMETRY.md | SQLite + background workers | Decided |
| DEV_PORTAL_M09_REVIEW.md | Go validation workers | Decided |
| DEV_PORTAL_M10_DEVICE.md | C++ AppManager | Decided |
| DEV_PORTAL_M11_CLI.md | Go + Cobra CLI | Decided |
| DEV_PORTAL_M12_DOCS.md | Hugo + Docsy | Decided |
## Key Design Principles
1. **Single container** - Portal runs as one Docker container on NAS
2. **No external services** - SQLite, local filesystem, Pagefind search
3. **Pure Go** - `modernc.org/sqlite` (no CGO required)
4. **Server-rendered** - htmx for interactivity, no SPA framework
5. **Background workers** - Go goroutines for aggregation/cleanup jobs

348
docs/GAME-ENGINES.md Normal file
View File

@@ -0,0 +1,348 @@
# Game Engine Integrations
MosisService provides a virtual phone that game engines can display and interact with.
## Integration Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ MosisService │
│ (OpenGL ES rendering → AHardwareBuffer) │
└───────────────────────────┬─────────────────────────────────────┘
│ Binder IPC + Shared Memory
┌─────────────────┴─────────────────┐
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ MosisUnreal │ │ MosisVR │
│ (UE5.5 Plugin) │ │ (Unity Package) │
│ Vulkan Import │ │ Vulkan/OpenGL │
└─────────────────────┘ └─────────────────────┘
```
## MosisUnreal (Unreal Engine 5.5)
**Location**: `D:\Dev\Mosis\MosisUnreal\Plugins\MosisSDK\`
### Build Commands
```batch
:: Windows Editor Build
"D:\Epic\UE_5.5\Engine\Build\BatchFiles\Build.bat" ^
MosisUnrealEditor Win64 Development ^
-Project="D:\Dev\Mosis\MosisUnreal\MosisUnreal.uproject"
:: Android APK Build
"D:\Epic\UE_5.5\Engine\Build\BatchFiles\RunUAT.bat" ^
BuildCookRun ^
-project="D:\Dev\Mosis\MosisUnreal\MosisUnreal.uproject" ^
-platform=Android -clientconfig=Development ^
-build -cook -stage -pak -package -noP4
:: Clean Build (delete these folders first)
rmdir /s /q "Intermediate\Build"
rmdir /s /q "Binaries"
```
### Output Files
| Build | Output |
|-------|--------|
| Windows Editor | `Plugins/MosisSDK/Binaries/Win64/UnrealEditor-MosisSDK.dll` |
| Android APK | `Binaries/Android/MosisUnreal-arm64.apk` |
| Android OBB | `Binaries/Android/main.1.com.omixlab.MosisUnreal.obb` |
### Requirements
- Android SDK Platform 36 (for AIDL binder headers)
- Android Build Tools 36.1.0 (for AIDL compiler)
- `ANDROID_HOME` environment variable set
### Quest Deployment
**IMPORTANT**: Git Bash on Windows converts Unix paths to Windows paths. Use `MSYS_NO_PATHCONV=1` prefix for all adb push commands.
```bash
# Set Quest device ID (check with: adb devices -l)
QUEST=2G0YC5ZF7W01T0
# Install APK
adb -s $QUEST install -r D:/Dev/Mosis/MosisUnreal/Binaries/Android/MosisUnreal-arm64.apk
# Create OBB temp directory
adb -s $QUEST shell "mkdir -p /data/local/tmp/obb/com.omixlab.MosisUnreal"
# Push OBB to temp location (MUST use MSYS_NO_PATHCONV=1)
MSYS_NO_PATHCONV=1 adb -s $QUEST push D:/Dev/Mosis/MosisUnreal/Binaries/Android/main.1.com.omixlab.MosisUnreal.obb /data/local/tmp/obb/com.omixlab.MosisUnreal/
# Move OBB to final location
adb -s $QUEST shell "rm -rf /sdcard/Android/obb/com.omixlab.MosisUnreal"
adb -s $QUEST shell "mv /data/local/tmp/obb/com.omixlab.MosisUnreal /sdcard/Android/obb/"
# Start MosisService first, then MosisUnreal
adb -s $QUEST shell am start -n com.omixlab.mosis/.MainActivity
sleep 3
adb -s $QUEST shell am start -n com.omixlab.MosisUnreal/com.epicgames.unreal.GameActivity
# Monitor logs
adb -s $QUEST logcat -s MosisSDK MosisOS MosisTest
```
**Full rebuild and deploy sequence**:
```bash
# 1. Build Android APK+OBB
"D:\Epic\UE_5.5\Engine\Build\BatchFiles\RunUAT.bat" BuildCookRun \
-project="D:\Dev\Mosis\MosisUnreal\MosisUnreal.uproject" \
-platform=Android -clientconfig=Development \
-build -cook -stage -pak -package -noP4
# 2. Stop app, push APK and OBB, restart
QUEST=2G0YC5ZF7W01T0
adb -s $QUEST shell am force-stop com.omixlab.MosisUnreal
adb -s $QUEST install -r D:/Dev/Mosis/MosisUnreal/Binaries/Android/MosisUnreal-arm64.apk
adb -s $QUEST shell "mkdir -p /data/local/tmp/obb/com.omixlab.MosisUnreal"
MSYS_NO_PATHCONV=1 adb -s $QUEST push D:/Dev/Mosis/MosisUnreal/Binaries/Android/main.1.com.omixlab.MosisUnreal.obb /data/local/tmp/obb/com.omixlab.MosisUnreal/
adb -s $QUEST shell "rm -rf /sdcard/Android/obb/com.omixlab.MosisUnreal && mv /data/local/tmp/obb/com.omixlab.MosisUnreal /sdcard/Android/obb/"
adb -s $QUEST shell am start -n com.omixlab.MosisUnreal/com.epicgames.unreal.GameActivity
```
**Common issues**:
- App stuck on loading screen: OBB not pushed or wrong version
- `adb push` path errors: Missing `MSYS_NO_PATHCONV=1` prefix
- Service not connecting: Start `com.omixlab.mosis` before the game
### VR Pointer Interaction
The MosisSDK includes `UMosisPointerComponent` for VR ray-based touch interaction.
**Key Files**:
- `Public/MosisPointerComponent.h` - Component header
- `Private/MosisPointerComponent.cpp` - Implementation
**Features**:
- Raycast from component transform (attach as child of motion controller)
- Automatic detection of `AMosisPhoneActor` hits
- Touch state machine: Down/Move/Up events
- Optional Enhanced Input binding via `TriggerAction` property
- Manual control via `SetTriggerPressed(bool)`
- Debug ray visualization
**Blueprint Setup**:
1. Add `MosisPointerComponent` as child of `MotionControllerComponent`
2. Set `TriggerAction` to your trigger input action (optional)
3. Touch events are sent automatically when pointing at phone and triggering
**C++ Setup**:
```cpp
// In VR Pawn header
UPROPERTY(VisibleAnywhere)
UMosisPointerComponent* RightPointer;
// In constructor
RightPointer = CreateDefaultSubobject<UMosisPointerComponent>(TEXT("RightPointer"));
RightPointer->SetupAttachment(RightMotionController);
// In BeginPlay (if using Enhanced Input)
RightPointer->TriggerAction = TriggerInputAction;
```
**Manual Control (without Enhanced Input)**:
```cpp
// In your input handling code
void AMyVRPawn::OnTriggerPressed()
{
RightPointer->SetTriggerPressed(true);
}
void AMyVRPawn::OnTriggerReleased()
{
RightPointer->SetTriggerPressed(false);
}
```
**Component Properties**:
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `RayLength` | float | 500.0 | Maximum ray length in cm |
| `bShowDebugRay` | bool | false | Draw debug visualization |
| `DebugRayColor` | FLinearColor | Red | Ray color when not hitting |
| `DebugRayHitColor` | FLinearColor | Green | Ray color when hitting phone |
| `TraceChannel` | ECollisionChannel | Visibility | Collision channel for raycast |
| `TriggerAction` | UInputAction* | nullptr | Enhanced Input action for trigger |
**Blueprint Functions**:
| Function | Returns | Description |
|----------|---------|-------------|
| `IsPointingAtPhone()` | bool | True if ray hits a phone actor |
| `GetTargetPhone()` | AMosisPhoneActor* | Currently targeted phone (or nullptr) |
| `GetHitLocation()` | FVector | World location of ray hit |
| `GetRayOrigin()` | FVector | Ray start position |
| `GetRayDirection()` | FVector | Ray direction vector |
| `IsTriggerPressed()` | bool | Current trigger state |
| `SetTriggerPressed(bool)` | void | Manual trigger control |
**Touch State Machine**:
```
Idle ──[raycast hit]──► Hover ──[trigger]──► Pressed
▲ │ │
│ │ raycast miss │ trigger release
│ ▼ ▼
└──────────────────── Idle ◄────────────────────
Events:
- Idle → Pressed: SendTouch(Down)
- Pressed + moving: SendTouch(Move)
- Pressed → Idle: SendTouch(Up)
```
## MosisVR (Unity 6000.3.2f1)
**Location**: `D:\Dev\Mosis\MosisVR\Packages\com.omixlab.mosis_sdk\`
### Direct APK Build (Recommended)
```batch
"C:\Program Files\Unity\Hub\Editor\6000.3.2f1\Editor\Unity.exe" ^
-batchmode -quit -nographics ^
-projectPath "D:\Dev\Mosis\MosisVR" ^
-executeMethod BuildScript.BuildAndroidDirectCI ^
-outputPath "D:\Dev\Mosis\Builds\Unity\Android\MosisVR.apk"
```
### Export + Gradle Build
For more control, export a Gradle project then build separately:
```batch
:: Step 1: Export from Unity
"C:\Program Files\Unity\Hub\Editor\6000.3.2f1\Editor\Unity.exe" ^
-batchmode -quit -nographics ^
-projectPath "D:\Dev\Mosis\MosisVR" ^
-executeMethod BuildScript.BuildAndroidCI ^
-export true ^
-outputPath "D:\Dev\Mosis\Builds\Unity\Android\MosisVR"
:: Step 2: Build with Gradle
cd D:\Dev\Mosis\Builds\Unity\Android\MosisVR
gradle assembleRelease
:: APK at: launcher\build\outputs\apk\release\launcher-release.apk
```
### Unity Editor Manual Build
1. File > Build Settings > Android
2. Player Settings: IL2CPP, ARM64, Vulkan + OpenGLES3
3. For direct APK: Uncheck "Export Project", click Build
4. For export: Check "Export Project", click Export
### Native Plugin Build (Manual)
The native plugin builds automatically via CMake during Unity's build. To rebuild manually:
```batch
cd Packages/com.omixlab.mosis_sdk/Plugins/Android/cpp
cmake -B build -DCMAKE_TOOLCHAIN_FILE=%ANDROID_NDK_HOME%/build/cmake/android.toolchain.cmake ^
-DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-29
cmake --build build
```
## Device Testing (Both Engines)
```bash
# Install MosisService first
adb install -r MosisService-debug.apk
# Install game client
adb install -r MosisUnreal-arm64.apk # or MosisVR.apk
# Launch service
adb shell am start -n com.omixlab.mosis/.MainActivity
# Launch client
adb shell am start -n com.omixlab.MosisUnreal/com.epicgames.unreal.GameActivity
# or for Unity:
adb shell am start -n com.omixlab.mosisvr/com.unity3d.player.UnityPlayerActivity
# Monitor all Mosis logs
adb logcat -s MosisSDK MosisTest RMLUI Vulkan
```
## Vulkan HardwareBuffer Import
Both game engines use Vulkan to import AHardwareBuffer from MosisService.
### Required Vulkan Extensions
```
VK_ANDROID_external_memory_android_hardware_buffer
VK_KHR_external_memory
VK_KHR_dedicated_allocation
```
### Import Pattern
```cpp
// 1. Query buffer properties
VkAndroidHardwareBufferPropertiesANDROID props = {
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID
};
VkAndroidHardwareBufferFormatPropertiesANDROID formatProps = {
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID
};
props.pNext = &formatProps;
vkGetAndroidHardwareBufferPropertiesANDROID(device, buffer, &props);
// 2. Create image with external memory
VkExternalMemoryImageCreateInfo extInfo = {
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID
};
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(buffer, &desc);
VkImageCreateInfo imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = &extInfo,
.imageType = VK_IMAGE_TYPE_2D,
.format = formatProps.format,
.extent = {desc.width, desc.height, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
};
vkCreateImage(device, &imageInfo, nullptr, &image);
// 3. Import memory from HardwareBuffer
VkImportAndroidHardwareBufferInfoANDROID importInfo = {
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
.buffer = buffer
};
VkMemoryDedicatedAllocateInfo dedicatedInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
.pNext = &importInfo,
.image = image
};
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &dedicatedInfo,
.allocationSize = props.allocationSize,
.memoryTypeIndex = FindMemoryType(props.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
};
vkAllocateMemory(device, &allocInfo, nullptr, &memory);
vkBindImageMemory(device, image, memory, 0);
```
### Synchronization
Currently using CPU synchronization (`vkQueueWaitIdle` / `glFinish`). Future improvement: use Vulkan semaphores for GPU-GPU sync.
### Double Buffering
The imported image is copied to a local texture each frame to prevent data races with MosisService rendering.

126
docs/LUA-SANDBOX.md Normal file
View File

@@ -0,0 +1,126 @@
# Lua Sandbox System
The sandbox provides secure, isolated Lua environments for third-party apps.
## Security Features
| Feature | Implementation |
|---------|----------------|
| Dangerous globals removed | `os`, `io`, `loadfile`, `dofile`, `debug` |
| Memory limits | Configurable per-app (default 10MB) |
| CPU limits | Instruction counting with timeout |
| Bytecode rejected | Only source code allowed |
| Metatables protected | Cannot modify string/table metatables |
| Path traversal blocked | `../` and absolute paths rejected |
## Permission Categories
| Category | Auto-Grant | Examples |
|----------|------------|----------|
| Normal | Yes | `storage`, `network` |
| Dangerous | User consent | `camera`, `microphone`, `location`, `contacts` |
| Signature | System apps only | `system_settings`, `install_packages` |
## Available APIs
**Core APIs** (always available):
```lua
-- Timers
local id = setTimeout(function() end, 1000)
clearTimeout(id)
local id = setInterval(function() end, 500)
clearInterval(id)
-- JSON
local obj = json.decode('{"key": "value"}')
local str = json.encode({key = "value"})
-- Crypto
local bytes = crypto.randomBytes(16)
local hash = crypto.sha256("data")
local hmac = crypto.hmac("sha256", "key", "data")
```
**Storage APIs** (requires `storage` permission):
```lua
-- Virtual filesystem (sandboxed to app directory)
fs.write("data.txt", "content")
local content = fs.read("data.txt")
local files = fs.list("/")
local stat = fs.stat("data.txt")
fs.delete("data.txt")
-- SQLite database
local db = database.open("mydb")
db:execute("CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, name TEXT)")
db:execute("INSERT INTO items (name) VALUES (?)", {"item1"})
local rows = db:query("SELECT * FROM items WHERE id = ?", {1})
```
**Network APIs** (requires `network` permission):
```lua
-- HTTP (HTTPS only, private IPs blocked)
local response = http.get("https://api.example.com/data")
local response = http.post("https://api.example.com/data", {
headers = {["Content-Type"] = "application/json"},
body = json.encode({key = "value"})
})
-- WebSocket
local ws = websocket.connect("wss://example.com/ws")
ws:send("message")
ws:onMessage(function(data) end)
ws:close()
```
**Hardware APIs** (requires dangerous permissions + user gesture):
```lua
-- Camera (requires camera permission)
camera.start(function(frame) end)
camera.stop()
-- Microphone (requires microphone permission)
microphone.start(function(samples) end)
microphone.stop()
-- Location (requires location permission)
location.getCurrentPosition(function(pos)
print(pos.latitude, pos.longitude)
end)
-- Sensors (requires sensors permission)
sensors.subscribe("accelerometer", function(data)
print(data.x, data.y, data.z)
end)
```
## Running Sandbox Tests
```bash
cd sandbox-test
cmake -B build -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%/scripts/buildsystems/vcpkg.cmake
cmake --build build --config Debug
./build/Debug/sandbox-test.exe
# Output: 149 tests, all passing
```
## Test Categories
| Category | Tests | Description |
|----------|-------|-------------|
| Security | 11 | Globals removal, bytecode, metatables |
| Resources | 8 | Memory, CPU limits, instruction counting |
| Permissions | 7 | Normal/dangerous/signature grants |
| Rate Limiting | 6 | API call throttling |
| Timers | 7 | setTimeout/setInterval behavior |
| JSON | 5 | Encode/decode, depth limits |
| Crypto | 4 | Random, SHA256, HMAC |
| VirtualFS | 8 | Read/write, quotas, traversal |
| Database | 8 | SQLite operations, injection prevention |
| Network | 8 | URL validation, private IP blocking |
| WebSocket | 7 | Connection limits, message size |
| Hardware | 42 | Camera, mic, location, sensors, bluetooth |
| IPC | 7 | Message bus between apps |
| Integration | 9 | Full app lifecycle |
| Fuzzing | 3 | Random input crash testing |

79
docs/MATERIAL-DESIGN.md Normal file
View File

@@ -0,0 +1,79 @@
# Material Design Resources
Material Design icons and components are available in the MosisDesigner repository.
## Material Design Icons
**Location**: `D:\Dev\Mosis\MosisDesigner\material-design-icons`
A comprehensive icon library from Google with 2000+ icons across 20 categories:
| Category | Examples |
|----------|----------|
| action | home, search, settings, delete, info |
| alert | error, warning, notification |
| av | play, pause, volume, mic |
| communication | phone, message, email, contacts |
| content | add, remove, copy, paste |
| device | battery, wifi, bluetooth, gps |
| editor | format, text, color, brush |
| file | folder, attachment, download, upload |
| hardware | keyboard, mouse, phone, tablet |
| home | lightbulb, thermostat, security |
| image | camera, photo, filter, tune |
| maps | location, directions, navigation |
| navigation | arrow, chevron, menu, close |
| notification | sync, update, event |
| places | hotel, restaurant, airport |
| search | search variants |
| social | share, person, group, notifications |
| toggle | star, checkbox, radio |
**Available Formats**:
- `src/` - SVG source files organized by category
- `png/` - PNG files at multiple DPIs (24dp, 36dp, 48dp)
- `font/` - Icon fonts (WOFF, TTF)
- `symbols/` - Material Symbols variable font (newer)
- `variablefont/` - Variable font files
**Icon Styles**:
- Outlined (default)
- Filled
- Rounded
- Sharp
- Two-tone (Material Icons only)
## Material Design Lite
**Location**: `D:\Dev\Mosis\MosisDesigner\material-design-lite`
CSS/JS component library implementing Material Design (reference implementation):
| Directory | Contents |
|-----------|----------|
| `src/` | SASS source for components |
| `docs/` | Component documentation |
| `templates/` | Page templates |
**Key Components** (for design reference):
- Buttons (raised, flat, FAB)
- Cards
- Dialogs
- Lists
- Menus
- Navigation drawers
- Progress indicators
- Sliders
- Snackbars
- Tables
- Tabs
- Text fields
- Tooltips
## Using Icons in Mosis
1. **Find icon** at https://fonts.google.com/icons
2. **Export SVG** from `material-design-icons/src/<category>/<name>/`
3. **Convert to TGA** using image tool (24x24 or 32x32, RGBA)
4. **Place in** `src/main/assets/icons/`
5. **Reference in RML**: `<img src="../../icons/<name>.tga"/>`

View File

@@ -29,11 +29,11 @@ Mosis is a **virtual smartphone OS** for VR games and applications. It provides
|---|-----------|--------|-------------|
| 1 | Cross-Platform Kernel | ✅ Complete | Desktop designer with shared kernel code |
| 2 | Testing Framework | ✅ Complete | Automated UI testing and inspection |
| 3 | Virtual Hardware | ❌ Not started | Camera, mic, speaker, filesystem APIs |
| 4 | App Sandboxing | ❌ Not started | Lua/WASM runtime, package format |
| 3 | Virtual Hardware | ✅ Complete | Camera, mic, speaker, filesystem APIs (via sandbox) |
| 4 | App Sandboxing | ✅ Complete | Lua runtime, package format, 149 security tests |
| 5 | WebRTC Bridge | ❌ Not started | Phone-to-phone communication |
| 6 | System Apps | 🔶 75% | Core phone apps |
| 7 | Game Integration | ❌ Not started | Unity/Unreal plugin polish |
| 7 | Game Integration | 🔶 In Progress | Unity/Unreal plugins (Vulkan working) |
---
@@ -101,118 +101,48 @@ Mosis is a **virtual smartphone OS** for VR games and applications. It provides
---
## Milestone 3: Virtual Hardware ❌ NOT STARTED
## Milestone 3: Virtual Hardware ✅ COMPLETE
**Goal**: Hardware-like APIs backed by game engine or real devices.
### 3.1 Camera Interface
Implemented as part of the Lua Sandbox system. See [SANDBOX_MILESTONES.md](SANDBOX_MILESTONES.md) for details.
```cpp
class ICamera {
virtual void RequestFrame(FrameCallback callback) = 0;
virtual void SetResolution(int width, int height) = 0;
virtual bool IsAvailable() const = 0;
};
```
### Completed Components
**Implementations**:
- **Game Mode**: Receives texture from Unity/Unreal
- **Desktop Mode**: System webcam (optional)
- **Android Test Mode**: SharedTexture from MainActivity
- **Mock Mode**: Test patterns
### 3.2 Microphone Interface
```cpp
class IMicrophone {
virtual void StartCapture(AudioCallback callback) = 0;
virtual void StopCapture() = 0;
virtual bool IsAvailable() const = 0;
};
```
### 3.3 Speaker Interface
```cpp
class ISpeaker {
virtual void PlayAudio(AudioBuffer buffer) = 0;
virtual void SetVolume(float volume) = 0;
};
```
### 3.4 Filesystem Interface
```cpp
class IFileSystem {
virtual FileHandle Open(const std::string& path, Mode mode) = 0;
virtual std::vector<std::string> List(const std::string& dir) = 0;
virtual bool CreateDirectory(const std::string& path) = 0;
virtual bool Delete(const std::string& path) = 0;
};
```
### 3.5 Network Interface
```cpp
class INetwork {
virtual HttpResponse Fetch(const HttpRequest& request) = 0;
virtual WebSocket Connect(const std::string& url) = 0;
virtual bool IsOnline() const = 0;
};
```
| Component | Sandbox Milestone | Location |
|-----------|------------------|----------|
| Camera | Milestone 11 | `sandbox/camera_interface.cpp` |
| Microphone | Milestone 12 | `sandbox/microphone_interface.cpp` |
| Audio Output | Milestone 13 | `sandbox/audio_output.cpp` |
| Location/GPS | Milestone 14 | `sandbox/location_interface.cpp` |
| Sensors | Milestone 15 | `sandbox/sensor_interface.cpp` |
| Bluetooth | Milestone 16 | `sandbox/bluetooth_interface.cpp` |
| Filesystem | Milestone 7 | `sandbox/virtual_fs.cpp` |
| Network/HTTP | Milestone 9 | `sandbox/network_manager.cpp` |
| WebSocket | Milestone 10 | `sandbox/websocket_manager.cpp` |
---
## Milestone 4: App Sandboxing ❌ NOT STARTED
## Milestone 4: App Sandboxing ✅ COMPLETE
**Goal**: Secure app runtime with defined package format.
### 4.1 Runtime Decision
Implemented with 20 sub-milestones covering security, APIs, and hardware interfaces. 149 security tests passing.
| Aspect | Lua | WASM |
|--------|-----|------|
| Isolation | Weak | Strong |
| Performance | Good for UI | Near-native |
| RmlUi Integration | Native | Needs bridge |
| Ecosystem | Small | Large |
See:
- [SANDBOX_MILESTONES.md](SANDBOX_MILESTONES.md) - Implementation milestones
- [LUA-SANDBOX.md](LUA-SANDBOX.md) - Security features, permissions, APIs
- [APP-MANAGEMENT.md](APP-MANAGEMENT.md) - Package format, manifest, lifecycle
**Recommendation**: Hybrid approach
- Lua for UI scripting (RmlUi integration)
- WASM for app logic needing isolation (future)
### Key Features
### 4.2 Package Format (.mpkg)
```
myapp.mpkg/
├── manifest.json # Metadata, permissions, entry
├── ui/
│ ├── main.rml
│ ├── styles.rcss
│ └── scripts/
├── assets/
│ ├── icons/
│ └── images/
└── data/
```
### 4.3 Manifest Schema
```json
{
"id": "com.example.myapp",
"name": "My App",
"version": "1.0.0",
"permissions": ["camera", "network", "storage"],
"entry": "ui/main.rml",
"icon": "assets/icons/app.png"
}
```
### 4.4 App Lifecycle
```
INSTALLED → LAUNCHING → RUNNING → PAUSED → STOPPED → UNINSTALLED
```
- **Lua Runtime**: Isolated `lua_State` per app with dangerous globals removed
- **Resource Limits**: Memory (16MB), CPU (instruction hooks), timers (100 max)
- **Permission System**: Normal/Dangerous/Signature categories with user gesture requirements
- **Virtual Filesystem**: Per-app storage with 50MB quota
- **SQLite Database**: Per-app with authorizer blocking dangerous operations
- **Network Security**: HTTPS required, private IP blocking, domain whitelists
- **Hardware Interfaces**: Camera, mic, location, sensors with mandatory indicators
---
@@ -420,24 +350,22 @@ cmake --build build --config Debug
---
## Current Sprint: Complete Partial Tasks
## Current Sprint: Next Steps
### Priority 1: Testing Framework Completion ✅ DONE
- [x] Action recording (capture interactions to JSON)
- [x] Action playback (replay with timing)
- [x] Screenshot diff (visual regression)
- [x] GLFW input hooks for automatic recording
### Priority 2: Remaining System Apps
### Priority 1: System Apps Completion
- [ ] Store app (UI only - browse, install)
- [ ] Camera app (UI + shared texture from MainActivity)
- [ ] Camera app (UI + game engine texture)
- [ ] Music app (UI outline only)
### Priority 3: App Data Persistence
- [ ] JSON/SQLite storage layer
- [ ] Contact CRUD operations
- [ ] Message history
- [ ] Settings persistence
### Priority 2: Game Engine Integration
- [x] MosisUnreal - Vulkan texture import working
- [ ] MosisUnreal - Blueprint API polish
- [ ] MosisVR (Unity) - Vulkan backend
### Priority 3: WebRTC Bridge (Milestone 5)
- [ ] libdatachannel integration
- [ ] Phone-to-phone communication
- [ ] Real smartphone bridge app
---
@@ -454,10 +382,11 @@ cmake --build build --config Debug
### Documentation
All documentation is in `docs/`:
- `CLAUDE.md` - Development guide
- `TESTING.md` - Testing framework documentation
- `SANDBOX_MILESTONES.md` - Sandbox implementation details
- `ROADMAP.md` - This file
---
*Last updated: 2026-01-16*
*Last updated: 2026-01-19*

178
docs/SANDBOX.md Normal file
View File

@@ -0,0 +1,178 @@
# Mosis Lua Sandbox Security
**Status**: ✅ Complete (149 security tests passing)
**Goal**: Secure app isolation with defense-in-depth approach.
---
## Overview
Third-party apps run in isolated Lua environments with restricted access to system resources. Each app gets its own `lua_State` with carefully controlled APIs.
```
┌─────────────────────────────────────────────────────────────────────┐
│ Mosis Kernel │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ LuaSandboxManager │ │
│ │ - Creates per-app lua_State with custom allocator │ │
│ │ - Enforces memory/CPU limits │ │
│ │ - Routes permission-gated API calls │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ App A State │ │ App B State │ │ App C State │ │
│ │ (isolated) │ │ (isolated) │ │ (isolated) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ └───────────────────┴───────────────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Permission Gate │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────────────────────────▼──────────────────────────────────┐ │
│ │ System Services │ │
│ │ Camera │ Network │ Storage │ Contacts │ Messages │ ... │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## Threat Model
### Threat Categories
| Category | Threat | Severity | Mitigation |
|----------|--------|----------|------------|
| **Code Execution** | `os.execute()`, `io.popen()` | Critical | Remove globals |
| **File Access** | `io.open()`, `loadfile()` | Critical | Remove globals |
| **Bytecode Injection** | `load()` with binary chunks | Critical | Text-only loading |
| **Memory Exhaustion** | Infinite tables/strings | High | Custom allocator with limits |
| **CPU Exhaustion** | Infinite loops | High | Instruction count hook |
| **Sandbox Escape** | `debug.getregistry()` | Critical | Remove debug library |
| **Global Pollution** | Modifying `_G`, `string` | Medium | Frozen globals |
| **Path Traversal** | `require("../../etc/passwd")` | Critical | Path validation |
| **Data Exfiltration** | Unauthorized network access | High | Permission-gated network |
| **Privilege Escalation** | Access other app's data | High | Per-app storage isolation |
| **Timing Attacks** | High-resolution timers | Low | Limit timer precision |
| **Side Channels** | Memory/CPU usage patterns | Low | Rate limiting |
### Attacker Capabilities
We assume a malicious app developer can:
- Write arbitrary Lua code within the sandbox
- Attempt to exploit any exposed API
- Try to escape the sandbox via Lua language features
- Attempt DoS via resource exhaustion
- Try to access other apps' data
- Attempt to exfiltrate user data
---
## Security Layers
### Layer 1: Dangerous Globals Removal
Remove all dangerous functions before any app code runs: `os`, `io`, `debug`, `package`, `require`, `ffi`, `jit`, `dofile`, `loadfile`, `load`, `loadstring`, `rawget`, `rawset`, `collectgarbage`, `string.dump`.
### Layer 2: Bytecode Prevention
Only allow text chunks (`"t"` mode), reject binary Lua bytecode.
### Layer 3: Memory Limits
Custom allocator tracks and limits memory per app (16 MB default).
### Layer 4: CPU Limits
Instruction count hook interrupts runaway code (10M instructions default).
### Layer 5: Metatable Protection
Freeze `_G` and string metatable to prevent modification.
### Layer 6: Permission System
Three categories: Normal (auto-granted), Dangerous (user prompt), Signature (system only).
### Layer 7: Rate Limiting
Token bucket rate limiting on sensitive operations.
### Layer 8: Audit Logging
All security events logged for forensics.
---
## Implementation
The sandbox is implemented across 20 milestones with 22 modules. See:
- **[SANDBOX_MILESTONES.md](SANDBOX_MILESTONES.md)** - Complete implementation details for all 20 milestones
- **[LUA-SANDBOX.md](LUA-SANDBOX.md)** - API documentation for app developers
### Milestone Overview
| Phase | Milestones | Components |
|-------|------------|------------|
| **Foundation** | 1-4 | Core sandbox, permissions, audit logging, path security |
| **Core APIs** | 5-8 | Timers, JSON, crypto, virtual filesystem, SQLite |
| **Network** | 9-10 | HTTP requests, WebSocket connections |
| **Hardware** | 11-17 | Camera, mic, audio, location, sensors, Bluetooth, contacts |
| **System** | 18-20 | Inter-app messaging, security tests, kernel integration |
### Source Files
All sandbox code is in `src/main/cpp/sandbox/`:
| File | Description |
|------|-------------|
| `sandbox_manager.cpp` | Multi-app orchestrator |
| `lua_sandbox.cpp` | Core Lua sandbox with resource limits |
| `permission_gate.cpp` | Permission system |
| `virtual_fs.cpp` | Per-app virtual filesystem |
| `database_manager.cpp` | SQLite per app |
| `network_manager.cpp` | HTTP request validation |
| `websocket_manager.cpp` | WebSocket connections |
| `timer_manager.cpp` | setTimeout/setInterval |
| `json_api.cpp` | Safe JSON encode/decode |
| `crypto_api.cpp` | SHA256, HMAC, secure random |
| `camera_interface.cpp` | Camera with indicators |
| `microphone_interface.cpp` | Microphone with indicators |
| `audio_output.cpp` | Audio playback |
| `location_interface.cpp` | GPS with precision control |
| `sensor_interface.cpp` | Accelerometer, gyroscope, etc. |
| `bluetooth_interface.cpp` | Bluetooth device access |
| `contacts_interface.cpp` | Contacts read/write |
| `message_bus.cpp` | Inter-app communication |
---
## Testing
149 security tests verify sandbox integrity. Run with:
```bash
cd sandbox-test
./run_tests.bat
```
### Test Categories
- Dangerous globals removal
- Bytecode rejection
- Memory limit enforcement
- CPU limit enforcement
- Metatable protection
- Path traversal prevention
- Permission checks
- Rate limiting
- Network security (private IP blocking, HTTPS enforcement)
- Hardware indicator requirements
---
## References
- [Lua 5.4 Manual - Sandboxing](https://www.lua.org/manual/5.4/)
- [OWASP Secure Coding Practices](https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/)
- Android permission model
---
*Last updated: 2026-01-19*

60
docs/TESTING-FRAMEWORK.md Normal file
View File

@@ -0,0 +1,60 @@
# Automated Testing Framework
The designer-test (`designer-test/`) provides automated UI testing.
## Test Architecture
1. **WindowController**: Finds designer window, sends mouse/keyboard events via Windows SendInput API
2. **HierarchyReader**: Parses UI hierarchy JSON to find elements by ID/class (with retry logic and exponential backoff)
3. **LogParser**: Monitors log file for navigation events
4. **TestRunner**: Orchestrates test execution, reports results
5. **UIInspector**: Dumps UI hierarchy with atomic writes (temp file + rename pattern)
## Key Implementation Details
- **Path Normalization**: RmlUi uses `|` instead of `:` in Windows paths (e.g., `D|\Dev\...`). The UIInspector normalizes paths for correct document matching.
- **Atomic File Writes**: Hierarchy files are written to `.tmp` then renamed to prevent partial reads.
- **Retry with Backoff**: HierarchyReader retries up to 10 times with exponential backoff (30ms base) and validates JSON completeness.
- **Dynamic Back Button**: `GoHome()` finds back buttons from hierarchy by class (`app-bar-nav` or `browser-nav-btn`) instead of fixed coordinates.
## Writing Tests
```cpp
// Find element by ID and click it
bool ClickById(TestContext& ctx, const std::string& id) {
ctx.hierarchy.Reload();
auto element = ctx.hierarchy.FindById(id);
if (!element) return false;
int x = element->bounds.centerX();
int y = element->bounds.centerY();
ScaleToPhysical(ctx, x, y); // Convert logical to physical coords
ctx.window.SendClick(x, y);
return true;
}
// Test example
bool TestNavigateToDialer(TestContext& ctx) {
GoHome(ctx);
ctx.log.Clear();
if (!ClickById(ctx, "dock-phone")) return false;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
ctx.log.Reload();
return ctx.log.Contains("Loaded screen: apps/dialer/dialer.rml");
}
```
## Test Output
Tests produce JSON results at `test_results.json`:
```json
{
"name": "Mosis Designer UI Tests",
"summary": {"passed": 5, "failed": 0, "total": 5},
"tests": [
{"name": "Navigate to Dialer", "status": "passed", "duration_ms": 3500}
]
}
```

67
docs/UI-ASSETS.md Normal file
View File

@@ -0,0 +1,67 @@
# UI Assets Structure
All UI assets are in `src/main/assets/`:
```
assets/
├── apps/ # System apps (RML documents)
│ ├── home/home.rml # Home screen launcher
│ ├── dialer/dialer.rml # Phone dialer
│ ├── messages/ # Messages app
│ ├── contacts/ # Contacts app
│ ├── settings/ # Settings app
│ └── browser/ # Web browser
├── ui/ # Shared stylesheets
│ ├── html.rcss # Base HTML element styles
│ ├── theme.rcss # Design tokens (colors, typography)
│ └── components.rcss # Reusable UI components
├── scripts/ # Lua scripts
│ └── navigation.lua # Navigation system
├── icons/ # TGA icon files (24x24, 32x32)
└── fonts/ # TTF fonts (LatoLatin, Roboto)
```
## Navigation System
Navigation is handled by `scripts/navigation.lua`:
```lua
-- Navigate to a screen
navigateTo('dialer') -- Push to history, animate forward
-- Go back
goBack() -- Pop from history, animate back
-- Go home
goHome() -- Clear history, return to home
```
## Element IDs for Testing
Key elements have IDs for automated testing:
| ID | Location | Purpose |
|----|----------|---------|
| `dock-phone` | home.rml | Phone dock icon |
| `dock-messages` | home.rml | Messages dock icon |
| `dock-contacts` | home.rml | Contacts dock icon |
| `dock-browser` | home.rml | Browser dock icon |
| `app-settings` | home.rml | Settings app icon |
## CSS Classes for Navigation
Back buttons use `app-bar-nav` class for automated GoHome:
```html
<div class="app-bar-nav btn-icon" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
```
Browser uses `browser-nav-btn` class for its toolbar back button:
```html
<div class="app-bar-nav browser-nav-btn" onclick="goBack()">
<img src="../../icons/back.tga"/>
</div>
```
The test framework's `FindBackButton()` searches for both classes to handle all screen layouts.

Binary file not shown.

View File

@@ -0,0 +1,211 @@
-- Sandbox Test App
-- Tests: timers, JSON, crypto, storage
local results = {}
local logCounter = 0
local function log(msg)
logCounter = logCounter + 1
table.insert(results, string.format("[%03d] %s", logCounter, msg))
-- document may not be available during initial script load
if document then
local el = document:GetElementById("results")
if el then
el.inner_rml = table.concat(results, "\n")
end
end
end
-- Navigation helper
function goBack()
if navigation and navigation.back then
navigation.back()
else
log("Navigation not available")
end
end
local function setStatus(id, status, success)
-- document may not be available during initial script load
if not document then return end
local el = document:GetElementById(id)
if el then
if success then
el.inner_rml = "&#x2713; " .. status
else
el.inner_rml = "&#x2717; " .. status
end
end
end
-- Timer test
function testTimer()
setStatus("timer-status", "Running...", true)
log("Starting timer test...")
local count = 0
local timerId = nil
timerId = setInterval(function()
count = count + 1
log("Timer tick: " .. count)
if count >= 3 then
clearInterval(timerId)
setStatus("timer-status", "Passed (3 ticks)", true)
log("Timer test complete!")
end
end, 1000)
log("Timer started with ID: " .. tostring(timerId))
end
-- JSON test
function testJSON()
log("Starting JSON test...")
local success = true
local msg = ""
-- Test encode
local data = {
name = "test",
value = 42,
nested = { a = 1, b = 2 }
}
local encoded = json.encode(data)
if encoded then
log("Encoded: " .. encoded)
else
success = false
msg = "encode failed"
end
-- Test decode
if success then
local decoded = json.decode(encoded)
if decoded and decoded.name == "test" and decoded.value == 42 then
log("Decoded successfully, name=" .. decoded.name)
else
success = false
msg = "decode failed"
end
end
if success then
setStatus("json-status", "Passed", true)
log("JSON test complete!")
else
setStatus("json-status", "Failed: " .. msg, false)
end
end
-- Crypto test
function testCrypto()
log("Starting crypto test...")
local success = true
local msg = ""
-- Test random bytes
local bytes = crypto.randomBytes(16)
if bytes and #bytes == 16 then
log("Random bytes (hex): " .. bytes:gsub(".", function(c)
return string.format("%02x", c:byte())
end))
else
success = false
msg = "randomBytes failed"
end
-- Test SHA256
if success then
local hash = crypto.sha256("hello world")
if hash then
log("SHA256: " .. hash:sub(1, 32) .. "...")
else
success = false
msg = "sha256 failed"
end
end
-- Test HMAC
if success then
local hmac = crypto.hmac("sha256", "secret", "message")
if hmac then
log("HMAC: " .. hmac:sub(1, 32) .. "...")
else
success = false
msg = "hmac failed"
end
end
if success then
setStatus("crypto-status", "Passed", true)
log("Crypto test complete!")
else
setStatus("crypto-status", "Failed: " .. msg, false)
end
end
-- Storage test
function testStorage()
log("Starting storage test...")
local success = true
local msg = ""
-- Test write (VirtualFS requires /data/, /cache/, /temp/, or /shared/ prefix)
local writeOk = fs.write("/data/test.txt", "Hello from sandbox!")
if writeOk then
log("Write successful")
else
success = false
msg = "write failed"
end
-- Test read
if success then
local content = fs.read("/data/test.txt")
if content == "Hello from sandbox!" then
log("Read successful: " .. content)
else
success = false
msg = "read mismatch"
end
end
-- Test list
if success then
local files = fs.list("/data")
if files then
log("Files in /data: " .. #files)
for _, f in ipairs(files) do
log(" - " .. f)
end
end
end
-- Test delete
if success then
local deleteOk = fs.delete("/data/test.txt")
if deleteOk then
log("Delete successful")
else
success = false
msg = "delete failed"
end
end
if success then
setStatus("storage-status", "Passed", true)
log("Storage test complete!")
else
setStatus("storage-status", "Failed: " .. msg, false)
end
end
-- Initialize
log("Sandbox Test App loaded")
log("Lua version: " .. (_VERSION or "unknown"))

Binary file not shown.

View File

@@ -0,0 +1,46 @@
<rml>
<head>
<title>Sandbox Test</title>
<link type="text/rcss" href="styles.rcss"/>
<script src="app.lua"></script>
</head>
<body>
<div class="app-bar">
<div class="app-bar-nav btn-icon" onclick="goBack()">
<span class="icon">←</span>
</div>
<div class="app-bar-title">Sandbox Test</div>
</div>
<div class="content">
<div class="card">
<div class="card-title">Timer Test</div>
<div id="timer-status">Not started</div>
<button onclick="testTimer()">Start Timer</button>
</div>
<div class="card">
<div class="card-title">JSON Test</div>
<div id="json-status">Not tested</div>
<button onclick="testJSON()">Test JSON</button>
</div>
<div class="card">
<div class="card-title">Crypto Test</div>
<div id="crypto-status">Not tested</div>
<button onclick="testCrypto()">Test Crypto</button>
</div>
<div class="card">
<div class="card-title">Storage Test</div>
<div id="storage-status">Not tested</div>
<button onclick="testStorage()">Test Storage</button>
</div>
<div class="card">
<div class="card-title">Results</div>
<div id="results">Click buttons above to run tests</div>
</div>
</div>
</body>
</rml>

View File

@@ -0,0 +1,18 @@
{
"id": "com.mosis.sandbox-test",
"name": "Sandbox Test",
"version": "1.0.0",
"version_code": 1,
"entry": "main.rml",
"icon": "icon.tga",
"description": "Tests sandbox APIs: timers, storage, JSON, crypto",
"developer": {
"name": "Mosis Team",
"email": "dev@mosis.dev"
},
"permissions": [
"storage",
"network"
],
"min_api_version": 1
}

View File

@@ -0,0 +1,97 @@
body {
font-family: LatoLatin;
font-size: 16dp;
background-color: #121212;
color: #ffffff;
width: 100%;
height: 100%;
}
.app-bar {
display: flex;
flex-direction: row;
align-items: center;
height: 56dp;
background-color: #1e1e1e;
padding: 0 8dp;
}
.app-bar-nav {
width: 40dp;
height: 40dp;
display: flex;
align-items: center;
justify-content: center;
border-radius: 20dp;
}
.app-bar-nav:hover {
background-color: #333333;
}
.icon {
font-size: 24dp;
}
.app-bar-title {
font-size: 20dp;
font-weight: bold;
margin-left: 16dp;
}
.content {
display: block;
padding: 16dp;
width: auto;
box-sizing: border-box;
}
.card {
display: block;
background-color: #1e1e1e;
border-radius: 12dp;
padding: 16dp;
margin-bottom: 12dp;
}
.card-title {
display: block;
font-size: 18dp;
font-weight: bold;
margin-bottom: 8dp;
color: #bb86fc;
}
.card div {
display: block;
}
button {
display: block;
background-color: #bb86fc;
color: #000000;
border-width: 0;
border-radius: 8dp;
padding: 12dp 24dp;
font-size: 14dp;
font-weight: bold;
margin-top: 8dp;
}
button:hover {
background-color: #cf9fff;
}
button:active {
background-color: #9a67ea;
}
#results {
font-family: LatoLatin;
font-size: 12dp;
background-color: #0d0d0d;
padding: 12dp;
border-radius: 8dp;
white-space: pre-wrap;
color: #00ff00;
}

21
test-apps/package.bat Normal file
View File

@@ -0,0 +1,21 @@
@echo off
REM Package test apps as .mosis files
setlocal enabledelayedexpansion
for /d %%d in (*) do (
if exist "%%d\manifest.json" (
echo Packaging %%d...
cd %%d
if exist "..\%%d.mosis" del "..\%%d.mosis"
tar -a -cf "..\%%d.mosis" *
cd ..
echo Created %%d.mosis
)
)
echo.
echo Done! Package files:
dir /b *.mosis 2>nul
endlocal