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>
This commit is contained in:
167
designer/src/desktop_sandbox.cpp
Normal file
167
designer/src/desktop_sandbox.cpp
Normal 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
|
||||
33
designer/src/desktop_sandbox.h
Normal file
33
designer/src/desktop_sandbox.h
Normal 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
|
||||
@@ -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));
|
||||
|
||||
@@ -29,6 +29,7 @@ private:
|
||||
|
||||
#ifdef _WIN32
|
||||
void* m_notification_handle = nullptr; // HANDLE
|
||||
void* m_stop_event = nullptr; // HANDLE for signaling shutdown
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user