#include "kernel.h" #include "egl_context.h" #include "render_target.h" #include #include "assets_manager.h" #include "apps/app_manager.h" #include "apps/update_service.h" #include "apps/app_api.h" #include "sandbox/sandbox_manager.h" #include "aidl/com/omixlab/mosis/IMosisListener.h" #include #include #include #include #include #include "RmlUi_Renderer_GL3.h" #include #include #include #include #include #include // Sandbox API includes for RmlUi integration #include #include #include #include // Global state for Lua access static Rml::Context* g_context = nullptr; static Rml::ElementDocument* g_document = nullptr; static mosis::AppManager* g_app_manager = nullptr; static mosis::UpdateService* g_update_service = nullptr; static mosis::LuaSandboxManager* g_sandbox_manager = nullptr; // RmlUi sandbox state - APIs registered into RmlUi's Lua state static std::unique_ptr g_rmlui_timer_manager; static std::unique_ptr g_rmlui_vfs; static std::string g_current_app_id = "com.mosis.home"; static std::string g_data_root; // Forward declarations static void SwitchAppSandbox(const std::string& app_id, const std::string& app_data_path); using namespace aidl::com::omixlab::mosis; using namespace aidl::android::hardware; // File handle wrapper to support both assets and filesystem files struct FileHandleWrapper { enum class Type { Asset, File }; Type type; union { AAsset* asset; std::ifstream* file; }; size_t file_size = 0; // For filesystem files }; class AssetFilesInterface : public Rml::FileInterface { public: static AssetFilesInterface& Instance() { static AssetFilesInterface instance; return instance; } // Check if path is a filesystem path (starts with / or file://) static bool IsFilesystemPath(const Rml::String& path) { return !path.empty() && (path[0] == '/' || path.rfind("file://", 0) == 0); } // Get the actual filesystem path, stripping file:// prefix if present static Rml::String GetFilesystemPath(const Rml::String& path) { if (path.rfind("file://", 0) == 0) { return path.substr(7); // Strip "file://" } return path; } Rml::FileHandle Open(const Rml::String &path) override { auto* wrapper = new FileHandleWrapper(); if (IsFilesystemPath(path)) { // Filesystem path Rml::String fs_path = GetFilesystemPath(path); auto* file = new std::ifstream(fs_path, std::ios::binary); if (!file->is_open()) { delete file; delete wrapper; return 0; } file->seekg(0, std::ios::end); wrapper->file_size = file->tellg(); file->seekg(0, std::ios::beg); wrapper->type = FileHandleWrapper::Type::File; wrapper->file = file; } else { // Asset path AAssetManager* am = AssetsManager::Native(); AAsset* asset = AAssetManager_open(am, path.c_str(), AASSET_MODE_BUFFER); if (!asset) { delete wrapper; return 0; } wrapper->type = FileHandleWrapper::Type::Asset; wrapper->asset = asset; } return reinterpret_cast(wrapper); } void Close(Rml::FileHandle file) override { auto* wrapper = reinterpret_cast(file); if (!wrapper) return; if (wrapper->type == FileHandleWrapper::Type::Asset) { AAsset_close(wrapper->asset); } else { wrapper->file->close(); delete wrapper->file; } delete wrapper; } size_t Read(void *buffer, size_t size, Rml::FileHandle file) override { auto* wrapper = reinterpret_cast(file); if (!wrapper) return 0; if (wrapper->type == FileHandleWrapper::Type::Asset) { return AAsset_read(wrapper->asset, buffer, size); } else { wrapper->file->read(static_cast(buffer), size); return wrapper->file->gcount(); } } bool Seek(Rml::FileHandle file, long offset, int origin) override { auto* wrapper = reinterpret_cast(file); if (!wrapper) return false; if (wrapper->type == FileHandleWrapper::Type::Asset) { off_t new_cursor = AAsset_seek(wrapper->asset, offset, origin); return new_cursor != -1; } else { std::ios_base::seekdir dir = std::ios::beg; if (origin == SEEK_CUR) dir = std::ios::cur; else if (origin == SEEK_END) dir = std::ios::end; wrapper->file->seekg(offset, dir); return !wrapper->file->fail(); } } size_t Tell(Rml::FileHandle file) override { auto* wrapper = reinterpret_cast(file); if (!wrapper) return 0; if (wrapper->type == FileHandleWrapper::Type::Asset) { return AAsset_seek(wrapper->asset, 0, SEEK_CUR); } else { return wrapper->file->tellg(); } } size_t Length(Rml::FileHandle file) override { auto* wrapper = reinterpret_cast(file); if (!wrapper) return 0; if (wrapper->type == FileHandleWrapper::Type::Asset) { return AAsset_getLength(wrapper->asset); } else { return wrapper->file_size; } } bool LoadFile(const Rml::String &path, Rml::String &out_data) override { if (IsFilesystemPath(path)) { // Load from filesystem Rml::String fs_path = GetFilesystemPath(path); std::ifstream file(fs_path, std::ios::binary | std::ios::ate); if (!file.is_open()) return false; size_t size = file.tellg(); file.seekg(0, std::ios::beg); out_data.resize(size); file.read(out_data.data(), size); return true; } else { // Load from assets AAssetManager* am = AssetsManager::Native(); AAsset* asset = AAssetManager_open(am, path.c_str(), AASSET_MODE_BUFFER); if (!asset) return false; out_data.resize(AAsset_getLength(asset)); auto data_ptr = static_cast(AAsset_getBuffer(asset)); std::span data = std::span(data_ptr, out_data.size()); std::ranges::copy(data, out_data.begin()); AAsset_close(asset); return true; } } }; class SystemInterface : public Rml::SystemInterface { public: static SystemInterface& Instance() { static SystemInterface instance; return instance; } bool LogMessage(Rml::Log::Type type, const Rml::String &message) override { Logger::Log(std::format("RMLUI: {}", message)); return true; } }; // Lua function to load a screen document static int LuaLoadScreen(lua_State* L) { if (!g_context) { lua_pushboolean(L, false); return 1; } const char* path = luaL_checkstring(L, 1); Logger::Log(std::format("Loading screen: {}", path)); // Check if file exists - support both asset and filesystem paths bool exists = false; if (AssetFilesInterface::IsFilesystemPath(path)) { // Filesystem path exists = std::filesystem::exists(path); } else { // Asset path AAssetManager* am = AssetsManager::Native(); AAsset* asset = AAssetManager_open(am, path, AASSET_MODE_BUFFER); if (asset) { exists = true; AAsset_close(asset); } } if (!exists) { Logger::Log(std::format("Screen not found: {}", path)); lua_pushboolean(L, false); return 1; } // Unload current document if (g_document) { g_context->UnloadDocument(g_document); g_document = nullptr; } // Load new document g_document = g_context->LoadDocument(path); if (g_document) { g_document->Show(); Logger::Log(std::format("Loaded screen: {}", path)); lua_pushboolean(L, true); } else { Logger::Log(std::format("Failed to load screen: {}", path)); lua_pushboolean(L, false); } return 1; } // Lua function to go back to home screen static int LuaGoHome(lua_State* L) { if (!g_context) { lua_pushboolean(L, false); return 1; } Logger::Log("goHome called - returning to home screen"); // Stop any running third-party app in the legacy sandbox manager if (g_sandbox_manager) { auto running_apps = g_sandbox_manager->GetRunningApps(); for (const auto& app_id : running_apps) { // Don't stop system apps if (app_id.find("com.mosis.") == 0 && app_id != "com.mosis.home") { Logger::Log(std::format("Stopping app: {}", app_id)); g_sandbox_manager->StopApp(app_id); } } } // Switch sandbox context back to home screen SwitchAppSandbox("com.mosis.home", g_data_root); // Load home screen const char* home_path = "apps/home/home.rml"; // Unload current document if (g_document) { g_context->UnloadDocument(g_document); g_document = nullptr; } // Load home document g_document = g_context->LoadDocument(home_path); if (g_document) { g_document->Show(); Logger::Log("Returned to home screen"); lua_pushboolean(L, true); } else { Logger::Log("Failed to load home screen"); lua_pushboolean(L, false); } return 1; } // Register sandbox APIs (timer, json, crypto, fs) into RmlUi's Lua state static void RegisterSandboxAPIs(lua_State* L) { Logger::Log("Registering sandbox APIs..."); // Register timer API if (g_rmlui_timer_manager) { mosis::RegisterTimerAPI(L, g_rmlui_timer_manager.get(), g_current_app_id); Logger::Log(" Timer API registered"); } // Register JSON API mosis::RegisterJsonAPI(L, mosis::JsonLimits{}); Logger::Log(" JSON API registered"); // Register crypto API mosis::RegisterCryptoAPI(L); Logger::Log(" Crypto API registered"); // Register virtual filesystem API if (g_rmlui_vfs) { mosis::RegisterVirtualFS(L, g_rmlui_vfs.get()); Logger::Log(" VirtualFS API registered"); } Logger::Log("All sandbox APIs registered"); } // Unregister sandbox APIs (for clean switch between apps) static void UnregisterSandboxAPIs(lua_State* L) { // Clear timer globals 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"); // Clear json global lua_pushnil(L); lua_setglobal(L, "json"); // Clear crypto global lua_pushnil(L); lua_setglobal(L, "crypto"); // Clear fs global lua_pushnil(L); lua_setglobal(L, "fs"); Logger::Log("Sandbox APIs unregistered"); } // Switch sandbox context to a different app static void SwitchAppSandbox(const std::string& app_id, const std::string& app_data_path) { lua_State* L = Rml::Lua::Interpreter::GetLuaState(); // Clear timers for current app if (g_rmlui_timer_manager) { g_rmlui_timer_manager->ClearAppTimers(g_current_app_id); Logger::Log(std::format("Timers cleared for {}", g_current_app_id)); } // Unregister current APIs UnregisterSandboxAPIs(L); // Update current app ID g_current_app_id = app_id; // Create new VirtualFS for the app std::string vfs_path = app_data_path.empty() ? (g_data_root + "/sandbox_data") : (app_data_path + "/sandbox_data"); std::filesystem::create_directories(vfs_path); g_rmlui_vfs = std::make_unique( app_id, vfs_path, mosis::VirtualFSLimits{} ); Logger::Log(std::format("VirtualFS created for {} at {}", app_id, vfs_path)); // Register APIs for new app RegisterSandboxAPIs(L); } // Lua function to switch sandbox context for a third-party app // Called before loading an app's UI: switchAppSandbox(app_id, install_path) static int LuaSwitchAppSandbox(lua_State* L) { const char* app_id = luaL_checkstring(L, 1); const char* install_path = luaL_checkstring(L, 2); Logger::Log(std::format("Switching sandbox to app: {} at {}", app_id, install_path)); SwitchAppSandbox(app_id, install_path); lua_pushboolean(L, true); return 1; } // Register Lua functions for navigation static void RegisterLuaFunctions(const std::string& current_app_id, bool is_system_app) { lua_State* L = Rml::Lua::Interpreter::GetLuaState(); lua_pushcfunction(L, LuaLoadScreen); lua_setglobal(L, "loadScreen"); lua_pushcfunction(L, LuaGoHome); lua_setglobal(L, "goHome"); lua_pushcfunction(L, LuaSwitchAppSandbox); lua_setglobal(L, "switchAppSandbox"); Logger::Log("Registered Lua loadScreen, goHome, and switchAppSandbox functions"); // Register app management APIs if (g_app_manager && g_update_service) { mosis::RegisterAppAPIs(L, g_app_manager, g_update_service, current_app_id, is_system_app); Logger::Log("Registered Lua app management APIs"); } // Initialize and register sandbox APIs RegisterSandboxAPIs(L); } void Kernel::main_loop() { m_egl_context = std::make_unique(); if (!m_egl_context->create()) { Logger::Log("failed to create EGL context"); return; } m_render_target = std::make_unique(); if (!m_render_target->create_exported(540, 960)) { Logger::Log("failed to create render target"); return; } m_render_target->bind(); for (const auto& [pid, l] : m_listeners) l->onServiceInitialized(true); m_aidl_buffer = std::make_unique(); m_aidl_buffer->reset(m_render_target->hardware_buffer()); for (const auto& [pid, l] : m_listeners) l->onBufferAvailable(*m_aidl_buffer); RenderInterface_GL3 rmlui_render_interface; if (!rmlui_render_interface) { Logger::Log("failed to create render interface"); return; } Rml::SetRenderInterface(&rmlui_render_interface); Rml::SetFileInterface(&AssetFilesInterface::Instance()); Rml::SetSystemInterface(&SystemInterface::Instance()); Rml::Initialise(); Rml::Lua::Initialise(); Logger::Log("RmlUi Lua bindings initialized"); // Initialize app management system // TODO: Get data root from Android context (for now use a placeholder) std::string data_root = "/data/data/com.omixlab.mosis/files"; g_data_root = data_root; // Store for sandbox switching m_app_manager = std::make_unique(data_root); m_update_service = std::make_unique( m_app_manager.get(), "https://portal.mosis.dev/api/v1"); m_sandbox_manager = std::make_unique(data_root); m_app_manager->SetSandboxManager(m_sandbox_manager.get()); g_app_manager = m_app_manager.get(); g_update_service = m_update_service.get(); g_sandbox_manager = m_sandbox_manager.get(); Logger::Log("App management system initialized"); // Initialize RmlUi sandbox state (timer manager and VFS for home screen) g_rmlui_timer_manager = std::make_unique(); std::string home_sandbox_path = data_root + "/sandbox_data"; std::filesystem::create_directories(home_sandbox_path); g_rmlui_vfs = std::make_unique( "com.mosis.home", home_sandbox_path, mosis::VirtualFSLimits{}); Logger::Log("RmlUi sandbox initialized"); // Start background update checks (every 24 hours) m_update_service->Start(std::chrono::hours(24)); // Register navigation functions with Lua // Home screen is a system app with full access RegisterLuaFunctions("com.mosis.home", true); g_context = Rml::CreateContext("default", Rml::Vector2i(540, 960)); if (!g_context) { Logger::Log("RMLUI failed to create a context"); Rml::Shutdown(); return; } // Load fonts from assets/fonts/ Rml::LoadFontFace("fonts/LatoLatin-Bold.ttf"); Rml::LoadFontFace("fonts/LatoLatin-BoldItalic.ttf"); Rml::LoadFontFace("fonts/LatoLatin-Italic.ttf"); Rml::LoadFontFace("fonts/LatoLatin-Regular.ttf"); Rml::LoadFontFace("fonts/NotoEmoji-Regular.ttf", true); Rml::LoadFontFace("fonts/Roboto/Roboto-VariableFont_wdth,wght.ttf"); Rml::LoadFontFace("fonts/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf"); // Load home screen document g_document = g_context->LoadDocument("apps/home/home.rml"); if (!g_document) { Logger::Log("Failed to load home.rml document"); Rml::Shutdown(); return; } g_document->Show(); while (true) { if (!m_tasks.empty()) { std::lock_guard _lock(m_mutex); for (const auto &task: m_tasks) task(g_context); m_tasks.clear(); } // Update sandbox timers for running apps (legacy sandbox manager) if (m_sandbox_manager) { m_sandbox_manager->UpdateTimers(); } // Update RmlUi sandbox timers (for UI-based apps) if (g_rmlui_timer_manager) { g_rmlui_timer_manager->ProcessTimers(); } m_render_target->bind(); glClearColor(0.f, 0.f, 0.f, 1.f); glClear(GL_COLOR_BUFFER_BIT); glViewport(0, 0, 540, 960); g_context->Update(); rmlui_render_interface.SetViewport(540, 960); rmlui_render_interface.BeginFrame(); g_context->Render(); rmlui_render_interface.EndFrame(m_render_target->framebuffer()); glFinish(); { std::lock_guard _lock(m_mutex); for (const auto& [pid, l] : m_listeners) l->onFrameAvailable(); } std::this_thread::sleep_for(std::chrono::milliseconds(16)); // ~60 FPS } Rml::Shutdown(); } Kernel::Kernel(const std::shared_ptr &listener) { m_listeners.emplace(AIBinder_getCallingPid(), listener); m_main_loop_thread = std::thread(&Kernel::main_loop, this); } void Kernel::add_listener(const std::shared_ptr &listener) { std::lock_guard _lock(m_mutex); m_listeners.emplace(AIBinder_getCallingPid(), listener); listener->onServiceInitialized(true); listener->onBufferAvailable(*m_aidl_buffer); } void Kernel::on_touch_down(float x, float y) { Logger::Log(std::format("on_touch_down {} - {}", x, y)); std::lock_guard _lock(m_mutex); m_tasks.emplace_back([x, y](Rml::Context* context){ int px = static_cast(x * 540); int py = static_cast(y * 960); bool move_handled = context->ProcessMouseMove(px, py, 0); bool down_handled = context->ProcessMouseButtonDown(0, 0); Logger::Log(std::format("Touch at ({}, {}): move={}, down={}", px, py, move_handled, down_handled)); // Debug: Check if there's a hover element auto* hover_elem = context->GetHoverElement(); if (hover_elem) { Logger::Log(std::format(" Hover element: {} (id={})", hover_elem->GetTagName().c_str(), hover_elem->GetId().c_str())); } else { Logger::Log(" No hover element at this position"); } }); } void Kernel::on_touch_move(float x, float y) { Logger::Log(std::format("on_touch_move {} - {}", x, y)); std::lock_guard _lock(m_mutex); m_tasks.emplace_back([x, y](Rml::Context* context){ context->ProcessMouseMove(static_cast(x * 540), static_cast(y * 960), 0); }); } void Kernel::on_touch_up(float x, float y) { Logger::Log(std::format("on_touch_up {} - {}", x, y)); std::lock_guard _lock(m_mutex); m_tasks.emplace_back([x, y](Rml::Context* context){ context->ProcessMouseMove(static_cast(x * 540), static_cast(y * 960), 0); context->ProcessMouseButtonUp(0, 0); }); } Kernel::~Kernel() = default;