luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit fe2850128e724ff02aab2f33fa12fd8036a58f7c
parent dc0a6d19c05bedb1d733de4bcfd4dc753befdacb
Author: luajitos <bbhbb2094@gmail.com>
Date:   Sun, 30 Nov 2025 08:50:40 +0000

added system-apps permission, fixed manifest parsing, fixed fs & app  proxy, fixed inline html rendering

Diffstat:
ACLAUDE.md | 2++
Miso_includes/apps/com.luajitos.htmltest/src/index.html | 5++++-
Miso_includes/apps/com.luajitos.moonbrowser/src/render.lua | 47+++++++++++++++++++++++++++++++++++++----------
Miso_includes/apps/com.luajitos.shell/manifest.lua | 2+-
Miso_includes/apps/com.luajitos.shell/src/shell.lua | 13++++++++++++-
Miso_includes/apps/com.luajitos.taskbar/manifest.lua | 2++
Miso_includes/apps/com.luajitos.taskbar/src/init.lua | 4++--
Miso_includes/os/init.lua | 8++++++--
Miso_includes/os/libs/Run.lua | 470++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Miso_includes/scripts/test.lua | 27+++++++++++++++++++--------
Arepack_lua.sh | 148+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 579 insertions(+), 149 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md @@ -0,0 +1 @@ +- if you only change lua then just run ./repack_lua.sh and tell me that you have repacked it, if you have changed C code then rebuild it with ./build.sh +\ No newline at end of file diff --git a/iso_includes/apps/com.luajitos.htmltest/src/index.html b/iso_includes/apps/com.luajitos.htmltest/src/index.html @@ -1,7 +1,10 @@ <!DOCTYPE html> <html> <head> - <title>HTML Test App</title> + <title>HTML Test Application</title> + <meta name="window-width" content="500"> + <meta name="window-height" content="600"> + <!-- meta name="window-height" value="600"> also works --> </head> <body> <h1>HTML App Test</h1> diff --git a/iso_includes/apps/com.luajitos.moonbrowser/src/render.lua b/iso_includes/apps/com.luajitos.moonbrowser/src/render.lua @@ -88,6 +88,9 @@ function Renderer.new(app, width, height) self.interactiveElements = {} -- Store buttons/inputs for click handling self.inputValues = {} -- Store input field values self.focusedInput = nil -- Currently focused input + self.inlineX = 0 -- Track X position for inline elements + self.inlineY = 0 -- Track Y position for current inline row + self.inlineHeight = 0 -- Track max height of current inline row return self end @@ -151,6 +154,13 @@ function Renderer:renderElement(elem, gfx, x, y, maxWidth, inheritedFontSize) -- Get display type local display = getStyle(elem, "display") or "block" + -- Reset inline flow when hitting a block element + if display == "block" and self.inlineX > 0 then + y = y + self.inlineHeight + 4 + self.inlineX = 0 + self.inlineHeight = 0 + end + -- Apply top margin for block elements local marginTop = getStyle(elem, "marginTop") or 0 if display == "block" and marginTop > 0 then @@ -280,24 +290,36 @@ function Renderer:renderElement(elem, gfx, x, y, maxWidth, inheritedFontSize) local btnWidth = #buttonText * 8 + 16 local btnHeight = 20 + local btnMargin = 4 + + -- Use inline flow - check if we need to wrap to new line + if self.inlineX > 0 and self.inlineX + btnWidth + btnMargin > maxWidth then + -- Wrap to new line + y = y + self.inlineHeight + 4 + self.inlineX = 0 + self.inlineHeight = 0 + end + + local drawX = x + self.inlineX + local drawY = y - if y >= -btnHeight and y < self.height then + if drawY >= -btnHeight and drawY < self.height then -- Button background (gradient effect) - gfx:fillRect(currentX, y, btnWidth, btnHeight, 0xE0E0E0) + gfx:fillRect(drawX, drawY, btnWidth, btnHeight, 0xE0E0E0) -- Top/left highlight - gfx:fillRect(currentX, y, btnWidth, 1, 0xFFFFFF) - gfx:fillRect(currentX, y, 1, btnHeight, 0xFFFFFF) + gfx:fillRect(drawX, drawY, btnWidth, 1, 0xFFFFFF) + gfx:fillRect(drawX, drawY, 1, btnHeight, 0xFFFFFF) -- Bottom/right shadow - gfx:fillRect(currentX, y + btnHeight - 1, btnWidth, 1, 0x808080) - gfx:fillRect(currentX + btnWidth - 1, y, 1, btnHeight, 0x808080) + gfx:fillRect(drawX, drawY + btnHeight - 1, btnWidth, 1, 0x808080) + gfx:fillRect(drawX + btnWidth - 1, drawY, 1, btnHeight, 0x808080) -- Button text (centered) - gfx:drawText(currentX + 8, y + 6, buttonText, 0x000000) + gfx:drawText(drawX + 8, drawY + 6, buttonText, 0x000000) -- Store for click handling table.insert(self.interactiveElements, { type = "button", - x = currentX, - y = y + self.scrollY, -- Store absolute position + x = drawX, + y = drawY + self.scrollY, -- Store absolute position width = btnWidth, height = btnHeight, text = buttonText, @@ -306,7 +328,12 @@ function Renderer:renderElement(elem, gfx, x, y, maxWidth, inheritedFontSize) }) end - y = y + btnHeight + 4 + -- Advance inline position + self.inlineX = self.inlineX + btnWidth + btnMargin + if btnHeight > self.inlineHeight then + self.inlineHeight = btnHeight + end + return y -- Don't render children for button (already got text) elseif tag == "input" then diff --git a/iso_includes/apps/com.luajitos.shell/manifest.lua b/iso_includes/apps/com.luajitos.shell/manifest.lua @@ -6,7 +6,7 @@ return { category = "System", description = "LuajitOS Shell Terminal", entry = "shell.lua", - type = "cli", + type = "gui", permissions = { "filesystem", "scheduling", diff --git a/iso_includes/apps/com.luajitos.shell/src/shell.lua b/iso_includes/apps/com.luajitos.shell/src/shell.lua @@ -9,6 +9,9 @@ local text = "LuajitOS Shell\n> " local current_line = "" local i = 0 local scroll_offset = 0 -- Number of lines to scroll up (0 = show bottom) + +-- Track error line ranges (pairs of {start_line, end_line} in the wrapped output) +local error_lines = {} -- List of lines that are errors (by content prefix) local cwd = "/home" -- Current working directory (local to shell), ~ = /home -- Get app path for $ expansion (shell's own app path) @@ -520,7 +523,15 @@ window.onDraw = function(gfx) local endLine = math.min(totalLines, bottomLine) for i = startLine, endLine do - gfx:drawText(10, y, wrappedLines[i], 0x00FF00) + local line = wrappedLines[i] + local color = 0x00FF00 -- Default green + + -- Check if this line is an error (starts with "Error:" or is continuation of error) + if line:match("^Error:") or line:match("^%s+at ") or line:match("^%s+in ") then + color = 0xFF4444 -- Red for errors + end + + gfx:drawText(10, y, line, color) y = y + lineHeight end end diff --git a/iso_includes/apps/com.luajitos.taskbar/manifest.lua b/iso_includes/apps/com.luajitos.taskbar/manifest.lua @@ -13,8 +13,10 @@ return { "import", "draw", "system-all", + "system-apps", "run", "ramdisk", + "system", "system-hook" }, allowedPaths = { diff --git a/iso_includes/apps/com.luajitos.taskbar/src/init.lua b/iso_includes/apps/com.luajitos.taskbar/src/init.lua @@ -58,8 +58,8 @@ local function getRunningApplications() local appList = {} -- Access global applications from sys - if sys and sys.applications then - for pid, application in pairs(sys.applications) do + if sys and sys.apps then + for pid, application in pairs(sys.apps) do -- Check if app has any visible or minimized non-taskbar windows local hasWindows = false if application.windows then diff --git a/iso_includes/os/init.lua b/iso_includes/os/init.lua @@ -632,8 +632,12 @@ local function rebuildWindowStack() if _G.sys and _G.sys.applications then for pid, app in pairs(_G.sys.applications) do if app.windows then - for _, window in ipairs(app.windows) do - table.insert(_G.window_stack, window) + for idx, window in ipairs(app.windows) do + if type(window) ~= "table" then + osprint("ERROR: app.windows[" .. idx .. "] is a " .. type(window) .. " not a table! pid=" .. tostring(pid) .. " appName=" .. tostring(app.appName) .. "\n") + else + table.insert(_G.window_stack, window) + end end end end diff --git a/iso_includes/os/libs/Run.lua b/iso_includes/os/libs/Run.lua @@ -115,29 +115,156 @@ local function deep_copy(obj) end -- Utility: Parse manifest (simple Lua code execution) +-- Parse manifest content as text (no Lua execution for security) local function parse_manifest(manifest_code) if not manifest_code then return {} end - -- Try to load and execute manifest code directly - local manifest_func, err = load(manifest_code, "manifest", "t") - if not manifest_func then - if osprint then - osprint("ERROR: Failed to parse manifest: " .. tostring(err) .. "\n") + -- Remove "return" if present at start + manifest_code = string.gsub(manifest_code, "^%s*return%s*", "") + + -- Remove outer braces only if both opening and closing exist as a pair + local trimmed = string.gsub(manifest_code, "^%s+", "") + trimmed = string.gsub(trimmed, "%s+$", "") + if string.sub(trimmed, 1, 1) == "{" and string.sub(trimmed, -1) == "}" then + manifest_code = string.sub(trimmed, 2, -2) + end + + -- Extract [[long strings]] before removing comments (to preserve their content) + local long_strings = {} + local placeholder_idx = 0 + manifest_code = string.gsub(manifest_code, "%[%[(.-)%]%]", function(content) + placeholder_idx = placeholder_idx + 1 + local placeholder = "\001LONGSTR" .. placeholder_idx .. "\001" + long_strings[placeholder] = content + return placeholder + end) + + -- Remove single-line comments (-- to end of line) + manifest_code = string.gsub(manifest_code, "%-%-[^\n]*", "") + + -- Normalize whitespace: replace newlines/tabs with spaces, collapse multiple spaces + manifest_code = string.gsub(manifest_code, "[\r\n\t]+", " ") + manifest_code = string.gsub(manifest_code, " +", " ") + + local manifest = {} + + -- Helper to parse array content (handles [[strings]] in arrays) + local function parse_array_item(item) + item = string.gsub(item, "^%s+", "") + item = string.gsub(item, "%s+$", "") + -- Check for long string placeholder + if long_strings[item] then + return long_strings[item] + elseif string.match(item, '^".-"$') then + return string.sub(item, 2, -2) + elseif string.match(item, "^'.-'$") then + return string.sub(item, 2, -2) + elseif item ~= "" then + return item end - return {} + return nil end - local success, manifest = pcall(manifest_func) - if not success then - if osprint then - osprint("ERROR: Failed to execute manifest: " .. tostring(manifest) .. "\n") + -- Match key = value pairs (handles arrays with spaces like { "a", "b" }) + -- Pattern: word = (value until next word= or end) + local pos = 1 + while pos <= #manifest_code do + -- Skip whitespace, commas, and semicolons + local ws_end = string.match(manifest_code, "^[%s,;]*()", pos) + if ws_end then pos = ws_end end + + -- Match key = value + local key, value_start = string.match(manifest_code, "^([%w_]+)%s*=%s*()", pos) + if not key then break end + + pos = value_start + + -- Determine value type and extract it + local value + local char = string.sub(manifest_code, pos, pos) + + if char == "{" then + -- Array: find matching } by counting brace depth + local depth = 1 + local brace_end = pos + 1 + while brace_end <= #manifest_code and depth > 0 do + local c = string.sub(manifest_code, brace_end, brace_end) + if c == "{" then + depth = depth + 1 + elseif c == "}" then + depth = depth - 1 + end + brace_end = brace_end + 1 + end + brace_end = brace_end - 1 -- Point to the closing } + if depth == 0 then + local arr_content = string.sub(manifest_code, pos + 1, brace_end - 1) + local arr = {} + for item in string.gmatch(arr_content, '[^,;]+') do + local parsed = parse_array_item(item) + if parsed then + table.insert(arr, parsed) + end + end + value = arr + pos = brace_end + 1 + else + break + end + elseif char == "\001" then + -- Long string placeholder + local placeholder = string.match(manifest_code, "^(\001LONGSTR%d+\001)", pos) + if placeholder and long_strings[placeholder] then + value = long_strings[placeholder] + pos = pos + #placeholder + else + break + end + elseif char == '"' then + -- Double-quoted string + local str_end = string.find(manifest_code, '"', pos + 1, true) + if str_end then + value = string.sub(manifest_code, pos + 1, str_end - 1) + pos = str_end + 1 + else + break + end + elseif char == "'" then + -- Single-quoted string + local str_end = string.find(manifest_code, "'", pos + 1, true) + if str_end then + value = string.sub(manifest_code, pos + 1, str_end - 1) + pos = str_end + 1 + else + break + end + else + -- Unquoted value (boolean, number, or identifier) + local val_str = string.match(manifest_code, "^([^%s,;}]+)", pos) + if val_str then + if val_str == "true" then + value = true + elseif val_str == "false" then + value = false + elseif tonumber(val_str) then + value = tonumber(val_str) + else + value = val_str + end + pos = pos + #val_str + else + break + end + end + + if key and value ~= nil then + manifest[key] = value end - return {} end - return manifest or {} + return manifest end -- Utility: Check if directory exists @@ -272,57 +399,24 @@ local function extract_embedded_manifest(script_content) return nil end - -- Find MANIFEST START and END markers - local start_marker = "%-%-%-%-%-+ MANIFEST START" - local end_marker = "%-%-%-%-%-+ MANIFEST END" + -- Look for --[[ MANIFEST ... --]] block comment format + local manifest_block = string.match(script_content, "%-%-%[%[%s*MANIFEST%s*(.-)%-%-%]%]") - local start_pos = string.find(script_content, start_marker) - if not start_pos then + if not manifest_block then return nil end - local end_pos = string.find(script_content, end_marker, start_pos) - if not end_pos then - return nil - end - - -- Extract the manifest block - local manifest_block = string.sub(script_content, start_pos, end_pos) - - -- Convert comment lines to Lua code - -- Remove leading "-- " from each line and build a return statement - local lua_code = "return {\n" - for line in string.gmatch(manifest_block, "[^\r\n]+") do - -- Skip the marker lines - if not string.match(line, "MANIFEST START") and - not string.match(line, "MANIFEST END") then - -- Remove leading "-- " or "--" - local cleaned = string.gsub(line, "^%s*%-%-%s*", "") - if cleaned ~= "" then - lua_code = lua_code .. cleaned .. "\n" - end - end - end - lua_code = lua_code .. "}" - - -- Parse the manifest - local manifest_func, err = load(lua_code, "embedded_manifest", "t") - if not manifest_func then - if osprint then - osprint("Warning: Failed to parse embedded manifest: " .. tostring(err) .. "\n") - end - return nil - end + -- Reuse parse_manifest which handles multi-line arrays and text-based parsing + local manifest = parse_manifest(manifest_block) - local success, manifest = pcall(manifest_func) - if not success then - if osprint then - osprint("Warning: Failed to execute embedded manifest: " .. tostring(manifest) .. "\n") - end - return nil + -- Return nil if manifest is empty + local has_keys = false + for _ in pairs(manifest) do + has_keys = true + break end - return manifest + return has_keys and manifest or nil end -- Run an installed app with sandboxing @@ -363,9 +457,10 @@ function run.execute(app_name, fsRoot) end -- Extract embedded manifest - manifest = extract_embedded_manifest(init_content) + local extracted_manifest = extract_embedded_manifest(init_content) - if manifest then + if extracted_manifest then + manifest = extracted_manifest run_print("Found embedded manifest\n") -- Parse permissions from embedded manifest @@ -388,6 +483,7 @@ function run.execute(app_name, fsRoot) end else run_print("No embedded manifest found, running with no permissions\n") + manifest = {} -- Ensure manifest is an empty table, not nil end -- For standalone scripts, use a simple app name based on the file path @@ -521,13 +617,25 @@ function run.execute(app_name, fsRoot) } -- Add optional globals only if they exist (avoid nil values in sandbox) - if _G.crypto then - sandbox_env.crypto = _G.crypto - end if _G.Dialog then sandbox_env.Dialog = _G.Dialog end + -- Check for crypto permission and add crypto object + local has_crypto = false + for _, perm in ipairs(permissions) do + if perm == "crypto" then + has_crypto = true + break + end + end + if has_crypto and _G.crypto then + sandbox_env.crypto = _G.crypto + if osprint then + osprint("Crypto permission granted - crypto object available\n") + end + end + -- Check for system-all permission and add sys object local has_system_all = false for _, perm in ipairs(permissions) do @@ -555,46 +663,69 @@ function run.execute(app_name, fsRoot) end end + -- Check for system-apps permission + local has_system_apps = false + for _, perm in ipairs(permissions) do + if perm == "system-apps" then + has_system_apps = true + break + end + end + if has_system_all then - if has_system_hook then - -- Apps with system-hook permission get full sys access including hook - sandbox_env.sys = _G.sys - if osprint then - osprint("System-all permission granted - sys object available (with hook)\n") - end - else - -- Apps without system-hook get sys without hook - sandbox_env.sys = setmetatable({}, { - __index = function(t, k) - if k == "hook" then - error("sys.hook requires 'system-hook' permission") + -- Create a proxy for sys that: + -- 1. Blocks hook unless system-hook permission + -- 2. Exposes applications as 'apps' only if system-apps permission + -- 3. Blocks direct access to 'applications' + sandbox_env.sys = setmetatable({}, { + __index = function(t, k) + if k == "hook" and not has_system_hook then + error("sys.hook requires 'system-hook' permission") + end + if k == "applications" then + error("sys.applications is not accessible, use sys.apps with 'system-apps' permission") + end + if k == "apps" then + if has_system_apps then + return _G.sys.applications + else + error("sys.apps requires 'system-apps' permission") end - return _G.sys[k] - end, - __newindex = function(t, k, v) - if k == "hook" then - error("sys.hook requires 'system-hook' permission") + end + return _G.sys[k] + end, + __newindex = function(t, k, v) + if k == "hook" and not has_system_hook then + error("sys.hook requires 'system-hook' permission") + end + if k == "applications" or k == "apps" then + error("Cannot modify sys.apps") + end + _G.sys[k] = v + end, + __pairs = function(t) + return function(tbl, k) + local nextKey, nextValue = next(_G.sys, k) + -- Skip hook if no permission + if nextKey == "hook" and not has_system_hook then + nextKey, nextValue = next(_G.sys, nextKey) end - _G.sys[k] = v - end, - __pairs = function(t) - return function(tbl, k) - local nextKey, nextValue = next(_G.sys, k) - -- Skip hook key - if nextKey == "hook" then - nextKey, nextValue = next(_G.sys, nextKey) - end - return nextKey, nextValue - end, t, nil - end, - __ipairs = function(t) - return ipairs(_G.sys) - end, - __metatable = false -- Prevent metatable access/modification - }) - if osprint then - osprint("System-all permission granted - sys object available (without hook)\n") - end + -- Skip applications (exposed as apps) + if nextKey == "applications" then + nextKey, nextValue = next(_G.sys, nextKey) + end + return nextKey, nextValue + end, t, nil + end, + __ipairs = function(t) + return ipairs(_G.sys) + end, + __metatable = false -- Prevent metatable access/modification + }) + if osprint then + osprint("System-all permission granted - sys object available\n") + if has_system_hook then osprint(" - sys.hook available\n") end + if has_system_apps then osprint(" - sys.apps available\n") end end end @@ -619,7 +750,7 @@ function run.execute(app_name, fsRoot) end end -- Enable SafeFS if manifest has allowedPaths defined - if not has_filesystem and manifest.allowedPaths and #manifest.allowedPaths > 0 then + if not has_filesystem and manifest and manifest.allowedPaths and #manifest.allowedPaths > 0 then has_filesystem = true end @@ -826,8 +957,44 @@ function run.execute(app_name, fsRoot) end end + -- Create a proxy for app that only exposes safe methods + -- This prevents apps from directly modifying exports, windows, etc. + local safe_app_methods = { + "export", "call", "getExport", "listExports", "getInfo", + "getExportCount", "getHelp", "printExports", + "writeStdout", "getStdout", "clearStdout", + "newWindow", "enterFullscreen", "exitFullscreen" + } + -- Also expose these read-only properties + local safe_app_properties = { + "appName", "pid", "startTime", "status" + } + local app_proxy = {} + for _, method in ipairs(safe_app_methods) do + if app_instance[method] then + app_proxy[method] = function(self, ...) + return app_instance[method](app_instance, ...) + end + end + end + setmetatable(app_proxy, { + __index = function(t, k) + -- Allow read-only access to safe properties + for _, prop in ipairs(safe_app_properties) do + if k == prop then + return app_instance[k] + end + end + error("app." .. tostring(k) .. " is not accessible", 2) + end, + __newindex = function(t, k, v) + error("Cannot modify app object", 2) + end, + __metatable = false + }) + -- Add to sandbox environment - sandbox_env.app = app_instance + sandbox_env.app = app_proxy -- Add Timer API scoped to this app if _G.Timer then @@ -1048,7 +1215,7 @@ function run.execute(app_name, fsRoot) local allowed_paths = {data_dir .. "/*"} -- Always allow data directory -- Add additional paths from manifest.allowedPaths - if manifest.allowedPaths and type(manifest.allowedPaths) == "table" then + if manifest and manifest.allowedPaths and type(manifest.allowedPaths) == "table" then for _, path in ipairs(manifest.allowedPaths) do if type(path) == "string" then -- Replace $ with app directory @@ -1132,10 +1299,38 @@ function run.execute(app_name, fsRoot) end end + -- Create a proxy table that only exposes safe methods + -- This prevents apps from accessing internal properties like allowedDirs + -- Note: createDiskFSMount and mountAllDiskDrives are NOT exposed - they're internal only + local safe_fs_methods = { + "resolvePath", "getCWD", "setCWD", "read", "write", "open", + "delete", "dirs", "files", "exists", "getType", "mkdir", + "fileName", "parentDir", "join", "relativeTo", "copy", "move", + "createPseudoFile", "createPseudoDir", "addFileHandler", + "removeFileHandler" + } + local fs_proxy = {} + for _, method in ipairs(safe_fs_methods) do + if fs_instance[method] then + fs_proxy[method] = function(self, ...) + return fs_instance[method](fs_instance, ...) + end + end + end + setmetatable(fs_proxy, { + __index = function(t, k) + error("fs." .. tostring(k) .. " is not accessible", 2) + end, + __newindex = function(t, k, v) + error("Cannot modify fs object", 2) + end, + __metatable = false + }) + -- Add to sandbox environment - sandbox_env.fs = fs_instance + sandbox_env.fs = fs_proxy if osprint then - osprint("[RUN] Set sandbox_env.fs for " .. app_name .. ", fs=" .. tostring(fs_instance) .. "\n") + osprint("[RUN] Set sandbox_env.fs (proxied) for " .. app_name .. "\n") end else if osprint then @@ -1182,7 +1377,7 @@ function run.execute(app_name, fsRoot) if success and SafeHTTP then -- Get allowed domains from manifest - local allowed_domains = manifest.allowedDomains or {} + local allowed_domains = (manifest and manifest.allowedDomains) or {} -- If no domains specified but network permission granted, allow all (backwards compat) if #allowed_domains == 0 then @@ -1197,7 +1392,7 @@ function run.execute(app_name, fsRoot) local http_instance = SafeHTTP.new(_G.NetworkStack, allowed_domains, { timeout = 30, max_size = 10485760, -- 10MB default - user_agent = manifest.name .. "/" .. (manifest.version or "1.0") + user_agent = (manifest and manifest.name or "Script") .. "/" .. (manifest and manifest.version or "1.0") }) -- Add to sandbox environment @@ -1877,19 +2072,34 @@ function run.execute(app_name, fsRoot) end else -- Standard sandbox: prevent access to undefined globals + -- Track which keys are allowed (even if nil) vs truly undefined + local allowed_keys = {} + for k, _ in pairs(sandbox_env) do + allowed_keys[k] = true + end + -- Also allow keys that were explicitly set to nil + allowed_keys.fs = true + allowed_keys.crypto = true + allowed_keys.sys = true + allowed_keys.window = true + setmetatable(sandbox_env, { __index = function(t, k) + if allowed_keys[k] then + return nil -- Key is allowed but not set, return nil + end error("Attempt to access undefined global: " .. tostring(k), 2) end, __newindex = function(t, k, v) - rawset(t, k, v) -- Allow setting new globals within sandbox + allowed_keys[k] = true -- Mark as allowed when set + rawset(t, k, v) end, __metatable = false -- Prevent metatable access/modification }) end -- Register sandbox environment with sys (after sandbox is fully set up) - local app_instance = rawget(sandbox_env, "app") + -- Note: app_instance is already defined above, don't redefine it from the proxy if app_instance and app_instance.pid and _G.sys and _G.sys.registerEnvironment then if osprint then osprint("[run] Registering sandbox environment for PID " .. app_instance.pid .. "\n") @@ -1926,6 +2136,18 @@ function run.execute(app_name, fsRoot) osprint("[DEBUG] HTML type app detected, creating HTML window\n") end + -- Extract window dimensions from HTML meta tags + -- Supports: <meta name="window-width" content="500"> or value="500" + local html_width = manifest.width or 800 + local html_height = manifest.height or 600 + -- Try content= first, then value= + local meta_width = string.match(init_content, '<meta[^>]+name=["\']window%-width["\'][^>]+content=["\'](%d+)["\']') + or string.match(init_content, '<meta[^>]+name=["\']window%-width["\'][^>]+value=["\'](%d+)["\']') + local meta_height = string.match(init_content, '<meta[^>]+name=["\']window%-height["\'][^>]+content=["\'](%d+)["\']') + or string.match(init_content, '<meta[^>]+name=["\']window%-height["\'][^>]+value=["\'](%d+)["\']') + if meta_width then html_width = tonumber(meta_width) or html_width end + if meta_height then html_height = tonumber(meta_height) or html_height end + -- For HTML apps, init_content is HTML, not Lua local htmlWindow, htmlErr @@ -1935,8 +2157,8 @@ function run.execute(app_name, fsRoot) html = init_content, file = init_file, fs = rawget(sandbox_env, "fs"), -- Use rawget to avoid metatable errors - width = manifest.width or 800, - height = manifest.height or 600, + width = html_width, + height = html_height, title = manifest.name or "HTML App", Dialog = rawget(sandbox_env, "Dialog"), loadstring = rawget(sandbox_env, "loadstring"), @@ -1972,14 +2194,14 @@ function run.execute(app_name, fsRoot) end -- Attach CLI buffer to app instance for proper output handling - if sandbox_env.app and sandbox_env.app.attachCLI then - sandbox_env.app:attachCLI(cli) + if app_instance and app_instance.attachCLI then + app_instance:attachCLI(cli) if osprint then osprint("[run] Attached CLI to HTML app instance\n") end end - return true, sandbox_env.app + return true, app_instance end -- Load and execute the app in the sandbox (Lua apps) @@ -2094,11 +2316,11 @@ function run.execute(app_name, fsRoot) print("Error: App execution failed: " .. result) -- Mark app as stopped if it exists - if Application and sandbox_env.app then - sandbox_env.app:setStatus("stopped", "execution_error: " .. tostring(result)) + if Application and app_instance then + app_instance:setStatus("stopped", "execution_error: " .. tostring(result)) -- Clean up /proc/$PID directory - local pid = sandbox_env.app.pid + local pid = app_instance.pid if pid and CRamdiskRemove then local proc_dir = "/proc/" .. pid CRamdiskRemove(proc_dir .. "/process") @@ -2129,21 +2351,21 @@ function run.execute(app_name, fsRoot) -- Check if app should keep running (has windows or callbacks) local keepRunning = false - if Application and sandbox_env.app then - if sandbox_env.app.windows and #sandbox_env.app.windows > 0 then + if Application and app_instance then + if app_instance.windows and #app_instance.windows > 0 then keepRunning = true if osprint then - osprint("App has " .. #sandbox_env.app.windows .. " windows, keeping it running\n") + osprint("App has " .. #app_instance.windows .. " windows, keeping it running\n") end end end -- Only mark app as stopped and clean up if it has no windows - if not keepRunning and Application and sandbox_env.app then - sandbox_env.app:setStatus("stopped", "no_windows") + if not keepRunning and Application and app_instance then + app_instance:setStatus("stopped", "no_windows") -- Clean up /proc/$PID directory - local pid = sandbox_env.app.pid + local pid = app_instance.pid if pid and CRamdiskRemove then local proc_dir = "/proc/" .. pid CRamdiskRemove(proc_dir .. "/process") @@ -2168,8 +2390,8 @@ function run.execute(app_name, fsRoot) end -- Attach CLI buffer to app instance for easy access - if sandbox_env.app and sandbox_env.cli then - sandbox_env.app.cli = sandbox_env.cli + if app_instance and sandbox_env.cli then + app_instance.cli = sandbox_env.cli if osprint then osprint("[run] Attached CLI to app instance\n") osprint("[run] CLI has getText: " .. tostring(sandbox_env.cli.getText ~= nil) .. "\n") @@ -2182,7 +2404,7 @@ function run.execute(app_name, fsRoot) end -- Return success and the app instance (so caller can get PID, stdout, etc) - return true, sandbox_env.app + return true, app_instance end -- Export to global namespace for easy access diff --git a/iso_includes/scripts/test.lua b/iso_includes/scripts/test.lua @@ -1,12 +1,23 @@ ---[[MANIFEST -{ - permissions = {"filesystem", "draw"} -} -]] +--[[ MANIFEST + name = "Test Script" + permissions = {"filesystem", "crypto"} +--]] + osprint("Test OSPrint") print("Hi from TEST PROGRAM!") - -for k,v in pairs(crypto) do - print("crypto", k, v) +function testTable(tab, tabname) + if tab then + for k,v in pairs(tab) do + print(tabname, k, v) + end + else + print("no "..tabname) + end end + +testTable(crypto, "crypto") +testTable(fs, "fs") +testTable(sys, "sys") +testTable(app, "app") +testTable(window, "window") diff --git a/repack_lua.sh b/repack_lua.sh @@ -0,0 +1,148 @@ +#!/bin/bash + +# Fast Lua-only rebuild script +# Recompiles Lua files (excluding manifest.lua and /scripts/*), repacks ramdisk, and creates ISO +# Much faster than full build.sh since it skips C compilation + +set -e # Exit on error + +echo "=== LuajitOS Fast Lua Repack ===" + +# Check if we have the required compiled objects +if [ ! -f "build/kernel.o" ]; then + echo "ERROR: build/kernel.o not found!" + echo "Please run ./build.sh first to compile C code." + exit 1 +fi + +# Configuration for linking +CROSS_COMPILE=i686-elf- +LD="${CROSS_COMPILE}ld" +OBJCOPY="${CROSS_COMPILE}objcopy" + +# Check if cross-compiler exists, fallback to native +if ! command -v ${LD} &> /dev/null; then + LD="ld" + OBJCOPY="objcopy" +fi + +LDFLAGS="-m elf_i386 -nostdlib -static" + +# Step 1: Recompile Lua files (excluding manifest.lua and scripts/*) +echo "Step 1: Recompiling Lua files..." +if command -v luajit &> /dev/null; then + find iso_includes -name "*.lua" | while read lua_file; do + # Skip manifest.lua files + if [[ "$lua_file" == */manifest.lua ]]; then + continue + fi + # Skip scripts directory + if [[ "$lua_file" == iso_includes/scripts/* ]]; then + continue + fi + + luac_file="${lua_file}c" + # Only recompile if .lua is newer than .luac or .luac doesn't exist + if [ ! -f "$luac_file" ] || [ "$lua_file" -nt "$luac_file" ]; then + echo " Compiling: $lua_file" + luajit -bW "$lua_file" "$luac_file" 2>/dev/null || true + fi + done + echo " Bytecode compilation complete" +else + echo "ERROR: luajit not found!" + exit 1 +fi + +# Step 2: Generate ramdisk +echo "Step 2: Generating ramdisk..." +if [ -d iso_includes ]; then + # Remove any previously embedded kernel to prevent recursive embedding + rm -rf iso_includes/os/boot + + # If we have a kernel for install, include it + if [ -f build/kernel_install.bin ]; then + mkdir -p iso_includes/os/boot + cp build/kernel_install.bin iso_includes/os/boot/kernel.bin + echo " Including install kernel ($(stat -c%s build/kernel_install.bin 2>/dev/null || stat -f%z build/kernel_install.bin 2>/dev/null || echo '?') bytes)" + fi + + luajit pack_ramdisk.lua iso_includes packed.bin + if [ $? -ne 0 ]; then + echo "Error: Failed to generate ramdisk" + exit 1 + fi +else + echo "ERROR: iso_includes directory not found!" + exit 1 +fi + +# Step 3: Convert packed.bin to object file +echo "Step 3: Converting packed.bin to object file..." +${OBJCOPY} -I binary -O elf32-i386 -B i386 \ + --rename-section .data=.rodata,alloc,load,readonly,data,contents \ + packed.bin build/packed.o +echo " Embedded packed.bin ($(stat -c%s packed.bin 2>/dev/null || stat -f%z packed.bin 2>/dev/null || echo '?') bytes)" + +# Step 4: Re-link kernel +echo "Step 4: Linking kernel..." + +# Find LuaJIT static library +LUAJIT_LIB="" +if [ -f "lib/libluajit-5.1-32bit.a" ]; then + LUAJIT_LIB="lib/libluajit-5.1-32bit.a" +elif [ -f "/usr/lib/i386-linux-gnu/libluajit-5.1.a" ]; then + LUAJIT_LIB="/usr/lib/i386-linux-gnu/libluajit-5.1.a" +else + echo "ERROR: Could not find 32-bit LuaJIT static library!" + exit 1 +fi + +# Find libgcc +CC="gcc" +if command -v i686-elf-gcc &> /dev/null; then + CC="i686-elf-gcc" +fi +LIBGCC_PATH=$(${CC} -m32 -print-libgcc-file-name 2>/dev/null) +LIBGCC="" +if [ -f "$LIBGCC_PATH" ]; then + LIBGCC="$LIBGCC_PATH" +fi + +# Link all objects together +${LD} ${LDFLAGS} -T linker.ld -o build/kernel.bin \ + build/boot.o build/syscall.o build/exceptions.o build/libc.o build/paging.o build/graphics.o build/vesa.o build/mouse.o build/usb_uhci.o build/keyboard.o build/ata.o build/fde.o build/diskfs.o build/partition.o build/fat16.o \ + build/decoder.o build/decoder_BMP.o build/decoder_PNG.o build/splash.o \ + build/compression.o build/deflate_impl.o build/Deflate.o build/LZMA.o build/GZip.o build/zlib.o \ + build/ramdisk.o build/luajit_init.o build/kernel.o \ + build/crypto_baremetal.o build/CSPRNG.o build/Ed25519.o build/X25519.o build/Curve25519.o \ + build/AES-256-GCM.o build/AES-128-GCM.o build/ChaCha20-Poly1305.o build/XChaCha20-Poly1305.o \ + build/Serpent-256-GCM.o build/Twofish-256-GCM.o build/Salsa20-Poly1305.o \ + build/PBKDF2.o build/Argon2.o build/stubs.o build/RSA.o build/P256.o \ + build/Kyber.o build/Dilithium.o \ + build/hash.o build/MD5.o build/SHA1.o build/SHA256.o build/SHA3.o build/BLAKE2.o \ + build/CSPRNG_Lua.o build/Ed25519_Lua.o build/X25519_Lua.o build/Hash_Lua.o \ + build/PBKDF2_Lua.o build/RSA_Lua.o build/P256_Lua.o \ + build/Kyber_Lua.o build/Dilithium_Lua.o \ + build/crypto.o \ + build/packed.o ${LUAJIT_LIB} ${LIBGCC} + +# Step 5: Create ISO +echo "Step 5: Creating ISO..." +cp build/kernel.bin isodir/boot/kernel.bin + +if command -v grub-mkrescue &> /dev/null; then + grub-mkrescue -o luajitos.iso isodir 2>&1 +elif command -v grub2-mkrescue &> /dev/null; then + grub2-mkrescue -o luajitos.iso isodir 2>&1 +else + echo "ERROR: grub-mkrescue not found!" + exit 1 +fi + +echo "" +echo "=== Repack complete! ===" +echo "ISO updated: luajitos.iso" +echo "" +echo "To test with QEMU, run:" +echo " qemu-system-x86_64 -cdrom luajitos.iso -m 512 -serial stdio -vga std -display sdl"