luajitos

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

commit ce18d357fac664f477697c71a1db6b2e8ceab71e
parent cbbaa8fda857e5bfc3642f732bb56a365bf45bbb
Author: luajitos <bbhbb2094@gmail.com>
Date:   Sun,  7 Dec 2025 08:52:39 +0000

Added Spreadsheet App

Diffstat:
Mcrypto/Hash_Lua.c | 145++++++++++++-------------------------------------------------------------------
Mcrypto/crypto.c | 3+++
Mcrypto/hashing/SHA3.c | 235++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mdecoder.c | 770+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Miso_includes/apps/com.luajitos.background/manifest.lua | 2+-
Miso_includes/apps/com.luajitos.crypto/manifest.lua | 4+++-
Miso_includes/apps/com.luajitos.crypto/src/init.lua | 22+++++++++++++++++++++-
Miso_includes/apps/com.luajitos.lunareditor/src/editor.lua | 21+++++++++++++++++----
Miso_includes/apps/com.luajitos.moonbrowser/src/render.lua | 18+++++++++---------
Miso_includes/apps/com.luajitos.paint/icon.png | 0
Miso_includes/apps/com.luajitos.paint/src/init.lua | 186++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Aiso_includes/apps/com.luajitos.spreadsheet/icon.png | 0
Aiso_includes/apps/com.luajitos.spreadsheet/manifest.lua | 14++++++++++++++
Aiso_includes/apps/com.luajitos.spreadsheet/src/init.lua | 835+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Miso_includes/apps/com.luajitos.taskbar/manifest.lua | 2+-
Miso_includes/apps/com.luajitos.taskbar/src/init.lua | 19++++++++++---------
Miso_includes/os/init.lua | 71++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Miso_includes/os/libs/Application.lua | 22++++++++++++++++++++++
Miso_includes/os/libs/Dialog.lua | 404++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Miso_includes/os/libs/HTMLWindow.lua | 6+++---
Miso_includes/os/libs/Image.lua | 217++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Miso_includes/os/libs/Run.lua | 34+++++++++++++++++++++++++++++++---
Miso_includes/os/libs/Sys.lua | 2+-
Miso_includes/os/postinit.lua | 51++++++++++++++++++++++++++++++++++++++++++++++-----
Mkernel.c | 38++++++++++++++++++++++++++++++++++++++
Mvesa.c | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
26 files changed, 2859 insertions(+), 330 deletions(-)

diff --git a/crypto/Hash_Lua.c b/crypto/Hash_Lua.c @@ -69,7 +69,7 @@ static int is_base64(const char *str, size_t len) { * Hash Functions * ========================================================================= */ -/* hash(string) - SHA-256 hash returning base64 string */ +/* hash(string) - SHA-256 hash returning raw binary */ int l_hash(lua_State *L) { size_t input_len; const char *input = luaL_checklstring(L, 1, &input_len); @@ -77,18 +77,11 @@ int l_hash(lua_State *L) { uint8_t digest[32]; sha256((const uint8_t*)input, input_len, digest); - size_t b64_len; - char *b64 = base64_encode(digest, 32, &b64_len); - if (!b64) { - return luaL_error(L, "Base64 encoding failed"); - } - - lua_pushlstring(L, b64, b64_len); - free(b64); + lua_pushlstring(L, (const char*)digest, 32); return 1; } -/* hash.SHA512(string) - SHA-512 hash returning base64 string */ +/* hash.SHA512(string) - SHA-512 hash returning raw binary */ int l_hash_sha512(lua_State *L) { size_t input_len; const char *input = luaL_checklstring(L, 1, &input_len); @@ -96,18 +89,11 @@ int l_hash_sha512(lua_State *L) { uint8_t digest[64]; sha512((const uint8_t*)input, input_len, digest); - size_t b64_len; - char *b64 = base64_encode(digest, 64, &b64_len); - if (!b64) { - return luaL_error(L, "Base64 encoding failed"); - } - - lua_pushlstring(L, b64, b64_len); - free(b64); + lua_pushlstring(L, (const char*)digest, 64); return 1; } -/* hash.SHA1(string) - SHA-1 hash (legacy) returning base64 string */ +/* hash.SHA1(string) - SHA-1 hash (legacy) returning raw binary */ int l_hash_sha1(lua_State *L) { size_t input_len; const char *input = luaL_checklstring(L, 1, &input_len); @@ -115,18 +101,11 @@ int l_hash_sha1(lua_State *L) { uint8_t digest[20]; sha1((const uint8_t*)input, input_len, digest); - size_t b64_len; - char *b64 = base64_encode(digest, 20, &b64_len); - if (!b64) { - return luaL_error(L, "Base64 encoding failed"); - } - - lua_pushlstring(L, b64, b64_len); - free(b64); + lua_pushlstring(L, (const char*)digest, 20); return 1; } -/* hash.CRC32(string) - CRC32 checksum returning base64 string */ +/* hash.CRC32(string) - CRC32 checksum returning raw binary (4 bytes, big-endian) */ int l_hash_crc32(lua_State *L) { size_t input_len; const char *input = luaL_checklstring(L, 1, &input_len); @@ -140,71 +119,24 @@ int l_hash_crc32(lua_State *L) { digest[2] = (checksum >> 8) & 0xFF; digest[3] = checksum & 0xFF; - size_t b64_len; - char *b64 = base64_encode(digest, 4, &b64_len); - if (!b64) { - return luaL_error(L, "Base64 encoding failed"); - } - - lua_pushlstring(L, b64, b64_len); - free(b64); + lua_pushlstring(L, (const char*)digest, 4); return 1; } -/* hash.MD5(base64string) or hash.MD5(binstring, binstrlength) - MD5 hash */ +/* hash.MD5(string) - MD5 hash returning raw binary */ int l_hash_md5(lua_State *L) { - const uint8_t *input; size_t input_len; - uint8_t *decoded = NULL; - - /* Check if we have two arguments (binary string + length) */ - if (lua_gettop(L) == 2) { - /* Binary string with explicit length */ - input = (const uint8_t*)lua_tolstring(L, 1, &input_len); - size_t explicit_len = luaL_checkinteger(L, 2); - - if (explicit_len > input_len) { - return luaL_error(L, "Specified length exceeds string length"); - } - input_len = explicit_len; - } else { - /* Single argument - check if base64 or binary */ - const char *str = luaL_checklstring(L, 1, &input_len); - - if (is_base64(str, input_len)) { - /* Base64 input - decode it */ - decoded = base64_decode_simple(str, input_len, &input_len); - if (!decoded) { - return luaL_error(L, "Base64 decoding failed"); - } - input = decoded; - } else { - /* Binary input */ - input = (const uint8_t*)str; - } - } + const char *input = luaL_checklstring(L, 1, &input_len); /* Compute MD5 hash */ uint8_t digest[16]; - md5(input, input_len, digest); + md5((const uint8_t*)input, input_len, digest); - if (decoded) { - free(decoded); - } - - /* Return as base64 */ - size_t b64_len; - char *b64 = base64_encode(digest, 16, &b64_len); - if (!b64) { - return luaL_error(L, "Base64 encoding failed"); - } - - lua_pushlstring(L, b64, b64_len); - free(b64); + lua_pushlstring(L, (const char*)digest, 16); return 1; } -/* hash.SHA3(string) or hash.SHA3_256(string) - SHA3-256 hash */ +/* hash.SHA3(string) or hash.SHA3_256(string) - SHA3-256 hash returning raw binary */ int l_hash_sha3(lua_State *L) { size_t input_len; const char *input = luaL_checklstring(L, 1, &input_len); @@ -212,18 +144,11 @@ int l_hash_sha3(lua_State *L) { uint8_t digest[32]; sha3_256((const uint8_t*)input, input_len, digest); - size_t b64_len; - char *b64 = base64_encode(digest, 32, &b64_len); - if (!b64) { - return luaL_error(L, "Base64 encoding failed"); - } - - lua_pushlstring(L, b64, b64_len); - free(b64); + lua_pushlstring(L, (const char*)digest, 32); return 1; } -/* hash.SHA3_512(string) - SHA3-512 hash */ +/* hash.SHA3_512(string) - SHA3-512 hash returning raw binary */ int l_hash_sha3_512(lua_State *L) { size_t input_len; const char *input = luaL_checklstring(L, 1, &input_len); @@ -231,14 +156,7 @@ int l_hash_sha3_512(lua_State *L) { uint8_t digest[64]; sha3_512((const uint8_t*)input, input_len, digest); - size_t b64_len; - char *b64 = base64_encode(digest, 64, &b64_len); - if (!b64) { - return luaL_error(L, "Base64 encoding failed"); - } - - lua_pushlstring(L, b64, b64_len); - free(b64); + lua_pushlstring(L, (const char*)digest, 64); return 1; } @@ -252,18 +170,11 @@ int l_hash_call(lua_State *L) { uint8_t digest[32]; sha3_256((const uint8_t*)input, input_len, digest); - size_t b64_len; - char *b64 = base64_encode(digest, 32, &b64_len); - if (!b64) { - return luaL_error(L, "Base64 encoding failed"); - } - - lua_pushlstring(L, b64, b64_len); - free(b64); + lua_pushlstring(L, (const char*)digest, 32); return 1; } -/* hash.BLAKE2b(string) or hash.BLAKE2b_256(string) - BLAKE2b-256 hash */ +/* hash.BLAKE2b(string) or hash.BLAKE2b_256(string) - BLAKE2b-256 hash returning raw binary */ int l_hash_blake2b(lua_State *L) { size_t input_len; const char *input = luaL_checklstring(L, 1, &input_len); @@ -271,18 +182,11 @@ int l_hash_blake2b(lua_State *L) { uint8_t digest[32]; blake2b_256((const uint8_t*)input, input_len, digest); - size_t b64_len; - char *b64 = base64_encode(digest, 32, &b64_len); - if (!b64) { - return luaL_error(L, "Base64 encoding failed"); - } - - lua_pushlstring(L, b64, b64_len); - free(b64); + lua_pushlstring(L, (const char*)digest, 32); return 1; } -/* hash.BLAKE2b_512(string) - BLAKE2b-512 hash */ +/* hash.BLAKE2b_512(string) - BLAKE2b-512 hash returning raw binary */ int l_hash_blake2b_512(lua_State *L) { size_t input_len; const char *input = luaL_checklstring(L, 1, &input_len); @@ -290,13 +194,6 @@ int l_hash_blake2b_512(lua_State *L) { uint8_t digest[64]; blake2b_512((const uint8_t*)input, input_len, digest); - size_t b64_len; - char *b64 = base64_encode(digest, 64, &b64_len); - if (!b64) { - return luaL_error(L, "Base64 encoding failed"); - } - - lua_pushlstring(L, b64, b64_len); - free(b64); + lua_pushlstring(L, (const char*)digest, 64); return 1; } diff --git a/crypto/crypto.c b/crypto/crypto.c @@ -1209,6 +1209,9 @@ int luaopen_crypto(lua_State *L) { lua_setfield(L, -2, "Salsa20"); /* Register hash functions directly on crypto table */ + lua_pushcfunction(L, l_hash); + lua_setfield(L, -2, "SHA256"); + lua_pushcfunction(L, l_hash_sha512); lua_setfield(L, -2, "SHA512"); diff --git a/crypto/hashing/SHA3.c b/crypto/hashing/SHA3.c @@ -11,7 +11,7 @@ #include <stdlib.h> /* Keccak round constants */ -static const uint64_t keccak_round_constants[24] = { +static const uint64_t KeccakF_RoundConstants[24] = { 0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808aULL, 0x8000000080008000ULL, 0x000000000000808bULL, 0x0000000080000001ULL, 0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008aULL, @@ -22,14 +22,173 @@ static const uint64_t keccak_round_constants[24] = { 0x8000000000008080ULL, 0x0000000080000001ULL, 0x8000000080008008ULL }; -/* Rotation offsets */ -static const int keccak_rotation_constants[24] = { - 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, - 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 -}; +#define ROL64(a, n) (((a) << (n)) | ((a) >> (64 - (n)))) + +/* Keccak-f[1600] permutation - unrolled reference implementation */ +static void KeccakF1600(uint64_t *state) { + uint64_t Aba, Abe, Abi, Abo, Abu; + uint64_t Aga, Age, Agi, Ago, Agu; + uint64_t Aka, Ake, Aki, Ako, Aku; + uint64_t Ama, Ame, Ami, Amo, Amu; + uint64_t Asa, Ase, Asi, Aso, Asu; + uint64_t BCa, BCe, BCi, BCo, BCu; + uint64_t Da, De, Di, Do, Du; + uint64_t Eba, Ebe, Ebi, Ebo, Ebu; + uint64_t Ega, Ege, Egi, Ego, Egu; + uint64_t Eka, Eke, Eki, Eko, Eku; + uint64_t Ema, Eme, Emi, Emo, Emu; + uint64_t Esa, Ese, Esi, Eso, Esu; + + Aba = state[ 0]; Abe = state[ 1]; Abi = state[ 2]; Abo = state[ 3]; Abu = state[ 4]; + Aga = state[ 5]; Age = state[ 6]; Agi = state[ 7]; Ago = state[ 8]; Agu = state[ 9]; + Aka = state[10]; Ake = state[11]; Aki = state[12]; Ako = state[13]; Aku = state[14]; + Ama = state[15]; Ame = state[16]; Ami = state[17]; Amo = state[18]; Amu = state[19]; + Asa = state[20]; Ase = state[21]; Asi = state[22]; Aso = state[23]; Asu = state[24]; + + for (int round = 0; round < 24; round += 2) { + /* Round 1 */ + BCa = Aba^Aga^Aka^Ama^Asa; + BCe = Abe^Age^Ake^Ame^Ase; + BCi = Abi^Agi^Aki^Ami^Asi; + BCo = Abo^Ago^Ako^Amo^Aso; + BCu = Abu^Agu^Aku^Amu^Asu; + + Da = BCu^ROL64(BCe, 1); + De = BCa^ROL64(BCi, 1); + Di = BCe^ROL64(BCo, 1); + Do = BCi^ROL64(BCu, 1); + Du = BCo^ROL64(BCa, 1); + + Aba ^= Da; BCa = Aba; + Age ^= De; BCe = ROL64(Age, 44); + Aki ^= Di; BCi = ROL64(Aki, 43); + Amo ^= Do; BCo = ROL64(Amo, 21); + Asu ^= Du; BCu = ROL64(Asu, 14); + Eba = BCa^((~BCe)&BCi); Eba ^= KeccakF_RoundConstants[round]; + Ebe = BCe^((~BCi)&BCo); + Ebi = BCi^((~BCo)&BCu); + Ebo = BCo^((~BCu)&BCa); + Ebu = BCu^((~BCa)&BCe); + + Abo ^= Do; BCa = ROL64(Abo, 28); + Agu ^= Du; BCe = ROL64(Agu, 20); + Aka ^= Da; BCi = ROL64(Aka, 3); + Ame ^= De; BCo = ROL64(Ame, 45); + Asi ^= Di; BCu = ROL64(Asi, 61); + Ega = BCa^((~BCe)&BCi); + Ege = BCe^((~BCi)&BCo); + Egi = BCi^((~BCo)&BCu); + Ego = BCo^((~BCu)&BCa); + Egu = BCu^((~BCa)&BCe); + + Abe ^= De; BCa = ROL64(Abe, 1); + Agi ^= Di; BCe = ROL64(Agi, 6); + Ako ^= Do; BCi = ROL64(Ako, 25); + Amu ^= Du; BCo = ROL64(Amu, 8); + Asa ^= Da; BCu = ROL64(Asa, 18); + Eka = BCa^((~BCe)&BCi); + Eke = BCe^((~BCi)&BCo); + Eki = BCi^((~BCo)&BCu); + Eko = BCo^((~BCu)&BCa); + Eku = BCu^((~BCa)&BCe); + + Abu ^= Du; BCa = ROL64(Abu, 27); + Aga ^= Da; BCe = ROL64(Aga, 36); + Ake ^= De; BCi = ROL64(Ake, 10); + Ami ^= Di; BCo = ROL64(Ami, 15); + Aso ^= Do; BCu = ROL64(Aso, 56); + Ema = BCa^((~BCe)&BCi); + Eme = BCe^((~BCi)&BCo); + Emi = BCi^((~BCo)&BCu); + Emo = BCo^((~BCu)&BCa); + Emu = BCu^((~BCa)&BCe); + + Abi ^= Di; BCa = ROL64(Abi, 62); + Ago ^= Do; BCe = ROL64(Ago, 55); + Aku ^= Du; BCi = ROL64(Aku, 39); + Ama ^= Da; BCo = ROL64(Ama, 41); + Ase ^= De; BCu = ROL64(Ase, 2); + Esa = BCa^((~BCe)&BCi); + Ese = BCe^((~BCi)&BCo); + Esi = BCi^((~BCo)&BCu); + Eso = BCo^((~BCu)&BCa); + Esu = BCu^((~BCa)&BCe); + + /* Round 2 */ + BCa = Eba^Ega^Eka^Ema^Esa; + BCe = Ebe^Ege^Eke^Eme^Ese; + BCi = Ebi^Egi^Eki^Emi^Esi; + BCo = Ebo^Ego^Eko^Emo^Eso; + BCu = Ebu^Egu^Eku^Emu^Esu; + + Da = BCu^ROL64(BCe, 1); + De = BCa^ROL64(BCi, 1); + Di = BCe^ROL64(BCo, 1); + Do = BCi^ROL64(BCu, 1); + Du = BCo^ROL64(BCa, 1); -/* ROL64 - Rotate left */ -#define ROL64(x, n) (((x) << (n)) | ((x) >> (64 - (n)))) + Eba ^= Da; BCa = Eba; + Ege ^= De; BCe = ROL64(Ege, 44); + Eki ^= Di; BCi = ROL64(Eki, 43); + Emo ^= Do; BCo = ROL64(Emo, 21); + Esu ^= Du; BCu = ROL64(Esu, 14); + Aba = BCa^((~BCe)&BCi); Aba ^= KeccakF_RoundConstants[round+1]; + Abe = BCe^((~BCi)&BCo); + Abi = BCi^((~BCo)&BCu); + Abo = BCo^((~BCu)&BCa); + Abu = BCu^((~BCa)&BCe); + + Ebo ^= Do; BCa = ROL64(Ebo, 28); + Egu ^= Du; BCe = ROL64(Egu, 20); + Eka ^= Da; BCi = ROL64(Eka, 3); + Eme ^= De; BCo = ROL64(Eme, 45); + Esi ^= Di; BCu = ROL64(Esi, 61); + Aga = BCa^((~BCe)&BCi); + Age = BCe^((~BCi)&BCo); + Agi = BCi^((~BCo)&BCu); + Ago = BCo^((~BCu)&BCa); + Agu = BCu^((~BCa)&BCe); + + Ebe ^= De; BCa = ROL64(Ebe, 1); + Egi ^= Di; BCe = ROL64(Egi, 6); + Eko ^= Do; BCi = ROL64(Eko, 25); + Emu ^= Du; BCo = ROL64(Emu, 8); + Esa ^= Da; BCu = ROL64(Esa, 18); + Aka = BCa^((~BCe)&BCi); + Ake = BCe^((~BCi)&BCo); + Aki = BCi^((~BCo)&BCu); + Ako = BCo^((~BCu)&BCa); + Aku = BCu^((~BCa)&BCe); + + Ebu ^= Du; BCa = ROL64(Ebu, 27); + Ega ^= Da; BCe = ROL64(Ega, 36); + Eke ^= De; BCi = ROL64(Eke, 10); + Emi ^= Di; BCo = ROL64(Emi, 15); + Eso ^= Do; BCu = ROL64(Eso, 56); + Ama = BCa^((~BCe)&BCi); + Ame = BCe^((~BCi)&BCo); + Ami = BCi^((~BCo)&BCu); + Amo = BCo^((~BCu)&BCa); + Amu = BCu^((~BCa)&BCe); + + Ebi ^= Di; BCa = ROL64(Ebi, 62); + Ego ^= Do; BCe = ROL64(Ego, 55); + Eku ^= Du; BCi = ROL64(Eku, 39); + Ema ^= Da; BCo = ROL64(Ema, 41); + Ese ^= De; BCu = ROL64(Ese, 2); + Asa = BCa^((~BCe)&BCi); + Ase = BCe^((~BCi)&BCo); + Asi = BCi^((~BCo)&BCu); + Aso = BCo^((~BCu)&BCa); + Asu = BCu^((~BCa)&BCe); + } + + state[ 0] = Aba; state[ 1] = Abe; state[ 2] = Abi; state[ 3] = Abo; state[ 4] = Abu; + state[ 5] = Aga; state[ 6] = Age; state[ 7] = Agi; state[ 8] = Ago; state[ 9] = Agu; + state[10] = Aka; state[11] = Ake; state[12] = Aki; state[13] = Ako; state[14] = Aku; + state[15] = Ama; state[16] = Ame; state[17] = Ami; state[18] = Amo; state[19] = Amu; + state[20] = Asa; state[21] = Ase; state[22] = Asi; state[23] = Aso; state[24] = Asu; +} /* Load 64-bit little-endian */ static inline uint64_t load64_le(const uint8_t *x) { @@ -55,44 +214,6 @@ static inline void store64_le(uint8_t *x, uint64_t u) { x[7] = (uint8_t)(u >> 56); } -/* Keccak-f[1600] permutation */ -static void keccak_f1600(uint64_t state[25]) { - uint64_t C[5], D[5], B[25]; - - for (int round = 0; round < 24; round++) { - /* Theta */ - for (int i = 0; i < 5; i++) { - C[i] = state[i] ^ state[i + 5] ^ state[i + 10] ^ state[i + 15] ^ state[i + 20]; - } - for (int i = 0; i < 5; i++) { - D[i] = C[(i + 4) % 5] ^ ROL64(C[(i + 1) % 5], 1); - } - for (int i = 0; i < 25; i++) { - state[i] ^= D[i % 5]; - } - - /* Rho and Pi */ - B[0] = state[0]; - int x = 1, y = 0; - for (int i = 0; i < 24; i++) { - B[y + 5 * ((2 * x + 3 * y) % 5)] = ROL64(state[y + 5 * x], keccak_rotation_constants[i]); - int temp = y; - y = (2 * x + 3 * y) % 5; - x = temp; - } - - /* Chi */ - for (int j = 0; j < 25; j += 5) { - for (int i = 0; i < 5; i++) { - state[j + i] = B[j + i] ^ ((~B[j + ((i + 1) % 5)]) & B[j + ((i + 2) % 5)]); - } - } - - /* Iota */ - state[0] ^= keccak_round_constants[round]; - } -} - /* Keccak context */ typedef struct { uint64_t state[25]; @@ -128,7 +249,7 @@ static void keccak_update(keccak_context *ctx, const uint8_t *data, size_t len) for (size_t i = 0; i < ctx->rate / 8; i++) { ctx->state[i] ^= load64_le(ctx->buffer + i * 8); } - keccak_f1600(ctx->state); + KeccakF1600(ctx->state); ctx->buffer_len = 0; } } @@ -145,7 +266,7 @@ static void keccak_final(keccak_context *ctx, uint8_t *digest, size_t digest_len for (size_t i = 0; i < ctx->rate / 8; i++) { ctx->state[i] ^= load64_le(ctx->buffer + i * 8); } - keccak_f1600(ctx->state); + KeccakF1600(ctx->state); /* Squeeze */ size_t extracted = 0; @@ -168,26 +289,26 @@ static void keccak_final(keccak_context *ctx, uint8_t *digest, size_t digest_len extracted += take; if (extracted < digest_len) { - keccak_f1600(ctx->state); + KeccakF1600(ctx->state); } } } -/* SHA3-256 */ -void sha3_256(const uint8_t *data, size_t len, uint8_t digest[32]) { +/* SHA3-224 */ +void sha3_224(const uint8_t *data, size_t len, uint8_t digest[28]) { keccak_context ctx; - keccak_init(&ctx, 136, 0x06); /* rate=1088 bits=136 bytes, SHA3 delim */ + keccak_init(&ctx, 144, 0x06); /* rate=1152 bits=144 bytes */ keccak_update(&ctx, data, len); - keccak_final(&ctx, digest, 32); + keccak_final(&ctx, digest, 28); memset(&ctx, 0, sizeof(ctx)); } -/* SHA3-224 */ -void sha3_224(const uint8_t *data, size_t len, uint8_t digest[28]) { +/* SHA3-256 */ +void sha3_256(const uint8_t *data, size_t len, uint8_t digest[32]) { keccak_context ctx; - keccak_init(&ctx, 144, 0x06); /* rate=1152 bits=144 bytes */ + keccak_init(&ctx, 136, 0x06); /* rate=1088 bits=136 bytes, SHA3 delim */ keccak_update(&ctx, data, len); - keccak_final(&ctx, digest, 28); + keccak_final(&ctx, digest, 32); memset(&ctx, 0, sizeof(ctx)); } diff --git a/decoder.c b/decoder.c @@ -637,3 +637,773 @@ int lua_image_get_buffer_bgra(lua_State* L) { return 1; } + +/* ============================================================================ + * Lua ImageBuffer - Mutable BGRA pixel buffer as userdata + * This allows true in-place modification without string copies + * ============================================================================ */ + +typedef struct { + int width; + int height; + uint8_t* data; /* BGRA format, 4 bytes per pixel */ +} ImageBuffer; + +#define IMAGEBUFFER_MT "ImageBuffer" + +/* Helper: Set a single pixel in a BGRA buffer (modifies in place) */ +static inline void buffer_set_pixel(uint8_t* buf, int width, int height, int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + if (x < 0 || x >= width || y < 0 || y >= height) return; + size_t offset = ((size_t)y * width + x) * 4; + buf[offset] = b; + buf[offset + 1] = g; + buf[offset + 2] = r; + buf[offset + 3] = a; +} + +/* Create a new ImageBuffer userdata + * ImageBufferNew(width, height) -> ImageBuffer userdata */ +int lua_imagebuffer_new(lua_State* L) { + int width = luaL_checkinteger(L, 1); + int height = luaL_checkinteger(L, 2); + + if (width <= 0 || height <= 0 || width > 8192 || height > 8192) { + return luaL_error(L, "Invalid buffer dimensions"); + } + + /* Create userdata */ + ImageBuffer* ib = (ImageBuffer*)lua_newuserdata(L, sizeof(ImageBuffer)); + ib->width = width; + ib->height = height; + ib->data = (uint8_t*)malloc((size_t)width * height * 4); + + if (!ib->data) { + return luaL_error(L, "Out of memory"); + } + + /* Initialize to white (opaque) */ + size_t size = (size_t)width * height * 4; + for (size_t i = 0; i < size; i += 4) { + ib->data[i] = 255; /* B */ + ib->data[i + 1] = 255; /* G */ + ib->data[i + 2] = 255; /* R */ + ib->data[i + 3] = 255; /* A */ + } + + /* Set metatable */ + luaL_getmetatable(L, IMAGEBUFFER_MT); + lua_setmetatable(L, -2); + + return 1; +} + +/* Get ImageBuffer from stack (with type check) */ +static ImageBuffer* check_imagebuffer(lua_State* L, int idx) { + void* ud = lua_touserdata(L, idx); + if (ud == NULL) { + luaL_error(L, "ImageBuffer expected, got nil"); + return NULL; + } + return (ImageBuffer*)ud; +} + +/* ImageBuffer:fillCircle(cx, cy, radius, color) - true in-place modification */ +int lua_imagebuffer_fill_circle(lua_State* L) { + ImageBuffer* ib = check_imagebuffer(L, 1); + if (!ib || !ib->data) return 0; + + int cx = luaL_checkinteger(L, 2); + int cy = luaL_checkinteger(L, 3); + int radius = luaL_checkinteger(L, 4); + uint32_t color = luaL_checkinteger(L, 5); + + /* Extract RGB from color (0xRRGGBB format) */ + uint8_t r = (color >> 16) & 0xFF; + uint8_t g = (color >> 8) & 0xFF; + uint8_t b = color & 0xFF; + uint8_t a = 255; + + int r2 = radius * radius; + int width = ib->width; + int height = ib->height; + uint8_t* data = ib->data; + + /* Optimized: only iterate within bounding box */ + int minY = cy - radius; + int maxY = cy + radius; + int minX = cx - radius; + int maxX = cx + radius; + + /* Clip to buffer bounds */ + if (minY < 0) minY = 0; + if (maxY >= height) maxY = height - 1; + if (minX < 0) minX = 0; + if (maxX >= width) maxX = width - 1; + + for (int y = minY; y <= maxY; y++) { + int dy = y - cy; + int dy2 = dy * dy; + for (int x = minX; x <= maxX; x++) { + int dx = x - cx; + if (dx*dx + dy2 <= r2) { + size_t offset = ((size_t)y * width + x) * 4; + data[offset] = b; + data[offset + 1] = g; + data[offset + 2] = r; + data[offset + 3] = a; + } + } + } + + return 0; +} + +/* ImageBuffer:drawLine(x1, y1, x2, y2, thickness, color) - true in-place modification */ +int lua_imagebuffer_draw_line(lua_State* L) { + ImageBuffer* ib = check_imagebuffer(L, 1); + if (!ib || !ib->data) return 0; + + int x1 = luaL_checkinteger(L, 2); + int y1 = luaL_checkinteger(L, 3); + int x2 = luaL_checkinteger(L, 4); + int y2 = luaL_checkinteger(L, 5); + int thickness = luaL_checkinteger(L, 6); + uint32_t color = luaL_checkinteger(L, 7); + + uint8_t r = (color >> 16) & 0xFF; + uint8_t g = (color >> 8) & 0xFF; + uint8_t b = color & 0xFF; + uint8_t a = 255; + + int width = ib->width; + int height = ib->height; + uint8_t* data = ib->data; + + int radius = thickness / 2; + int r2 = radius * radius; + + /* Bresenham's line algorithm */ + int dx = abs(x2 - x1); + int dy = abs(y2 - y1); + int sx = x1 < x2 ? 1 : -1; + int sy = y1 < y2 ? 1 : -1; + int err = dx - dy; + + while (1) { + /* Draw circle at current point */ + if (radius <= 0) { + /* Single pixel */ + if (x1 >= 0 && x1 < width && y1 >= 0 && y1 < height) { + size_t offset = ((size_t)y1 * width + x1) * 4; + data[offset] = b; + data[offset + 1] = g; + data[offset + 2] = r; + data[offset + 3] = a; + } + } else { + /* Draw filled circle for thickness */ + int minY = y1 - radius; + int maxY = y1 + radius; + int minX = x1 - radius; + int maxX = x1 + radius; + + if (minY < 0) minY = 0; + if (maxY >= height) maxY = height - 1; + if (minX < 0) minX = 0; + if (maxX >= width) maxX = width - 1; + + for (int py = minY; py <= maxY; py++) { + int tdy = py - y1; + int tdy2 = tdy * tdy; + for (int px = minX; px <= maxX; px++) { + int tdx = px - x1; + if (tdx*tdx + tdy2 <= r2) { + size_t offset = ((size_t)py * width + px) * 4; + data[offset] = b; + data[offset + 1] = g; + data[offset + 2] = r; + data[offset + 3] = a; + } + } + } + } + + if (x1 == x2 && y1 == y2) break; + + int e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x1 += sx; + } + if (e2 < dx) { + err += dx; + y1 += sy; + } + } + + return 0; +} + +/* ImageBuffer:fill(color) - fill entire buffer */ +int lua_imagebuffer_fill(lua_State* L) { + ImageBuffer* ib = check_imagebuffer(L, 1); + if (!ib || !ib->data) return 0; + + uint32_t color = luaL_checkinteger(L, 2); + + uint8_t r = (color >> 16) & 0xFF; + uint8_t g = (color >> 8) & 0xFF; + uint8_t b = color & 0xFF; + uint8_t a = 255; + + size_t size = (size_t)ib->width * ib->height * 4; + uint8_t* data = ib->data; + + for (size_t i = 0; i < size; i += 4) { + data[i] = b; + data[i + 1] = g; + data[i + 2] = r; + data[i + 3] = a; + } + + return 0; +} + +/* ImageBuffer:getSize() -> width, height */ +int lua_imagebuffer_get_size(lua_State* L) { + ImageBuffer* ib = check_imagebuffer(L, 1); + if (!ib) { + lua_pushinteger(L, 0); + lua_pushinteger(L, 0); + return 2; + } + lua_pushinteger(L, ib->width); + lua_pushinteger(L, ib->height); + return 2; +} + +/* ImageBuffer:getData() -> string (for blitting to screen) */ +int lua_imagebuffer_get_data(lua_State* L) { + ImageBuffer* ib = check_imagebuffer(L, 1); + if (!ib || !ib->data) { + lua_pushstring(L, ""); + return 1; + } + lua_pushlstring(L, (const char*)ib->data, (size_t)ib->width * ib->height * 4); + return 1; +} + +/* ImageBuffer:getPixel(x, y) -> r, g, b, a */ +int lua_imagebuffer_get_pixel(lua_State* L) { + ImageBuffer* ib = check_imagebuffer(L, 1); + if (!ib || !ib->data) return 0; + + int x = luaL_checkinteger(L, 2); + int y = luaL_checkinteger(L, 3); + + if (x < 0 || x >= ib->width || y < 0 || y >= ib->height) { + return 0; + } + + size_t offset = ((size_t)y * ib->width + x) * 4; + lua_pushinteger(L, ib->data[offset + 2]); /* R */ + lua_pushinteger(L, ib->data[offset + 1]); /* G */ + lua_pushinteger(L, ib->data[offset]); /* B */ + lua_pushinteger(L, ib->data[offset + 3]); /* A */ + return 4; +} + +/* ImageBuffer:setPixel(x, y, r, g, b, a) */ +int lua_imagebuffer_set_pixel(lua_State* L) { + ImageBuffer* ib = check_imagebuffer(L, 1); + if (!ib || !ib->data) return 0; + + int x = luaL_checkinteger(L, 2); + int y = luaL_checkinteger(L, 3); + int r = luaL_checkinteger(L, 4); + int g = luaL_checkinteger(L, 5); + int b = luaL_checkinteger(L, 6); + int a = luaL_optinteger(L, 7, 255); + + if (x < 0 || x >= ib->width || y < 0 || y >= ib->height) { + return 0; + } + + size_t offset = ((size_t)y * ib->width + x) * 4; + ib->data[offset] = b; + ib->data[offset + 1] = g; + ib->data[offset + 2] = r; + ib->data[offset + 3] = a; + return 0; +} + +/* ImageBuffer garbage collection */ +int lua_imagebuffer_gc(lua_State* L) { + ImageBuffer* ib = check_imagebuffer(L, 1); + if (ib->data) { + free(ib->data); + ib->data = NULL; + } + return 0; +} + +/* Register ImageBuffer metatable */ +void lua_imagebuffer_register(lua_State* L) { + luaL_newmetatable(L, IMAGEBUFFER_MT); + + /* __index = methods table */ + lua_newtable(L); + + lua_pushcfunction(L, lua_imagebuffer_fill_circle); + lua_setfield(L, -2, "fillCircle"); + + lua_pushcfunction(L, lua_imagebuffer_draw_line); + lua_setfield(L, -2, "drawLine"); + + lua_pushcfunction(L, lua_imagebuffer_fill); + lua_setfield(L, -2, "fill"); + + lua_pushcfunction(L, lua_imagebuffer_get_size); + lua_setfield(L, -2, "getSize"); + + lua_pushcfunction(L, lua_imagebuffer_get_data); + lua_setfield(L, -2, "getData"); + + lua_pushcfunction(L, lua_imagebuffer_get_pixel); + lua_setfield(L, -2, "getPixel"); + + lua_pushcfunction(L, lua_imagebuffer_set_pixel); + lua_setfield(L, -2, "setPixel"); + + lua_setfield(L, -2, "__index"); + + /* __gc for cleanup */ + lua_pushcfunction(L, lua_imagebuffer_gc); + lua_setfield(L, -2, "__gc"); + + lua_pop(L, 1); /* Pop metatable */ +} + +/* ============================================================================ + * Legacy Lua Image Buffer Drawing Functions (string-based, copies on write) + * ============================================================================ */ + +/* lua_buffer_set_pixel(buffer, width, height, x, y, r, g, b, a) -> new_buffer + * Sets a single pixel and returns the modified buffer */ +int lua_buffer_set_pixel(lua_State* L) { + size_t buf_len; + const char* buf = luaL_checklstring(L, 1, &buf_len); + int width = luaL_checkinteger(L, 2); + int height = luaL_checkinteger(L, 3); + int x = luaL_checkinteger(L, 4); + int y = luaL_checkinteger(L, 5); + int r = luaL_checkinteger(L, 6); + int g = luaL_checkinteger(L, 7); + int b = luaL_checkinteger(L, 8); + int a = luaL_optinteger(L, 9, 255); + + size_t expected = (size_t)width * height * 4; + if (buf_len < expected) { + return luaL_error(L, "Buffer too small"); + } + + /* Create a copy of the buffer to modify */ + char* new_buf = (char*)malloc(buf_len); + if (!new_buf) { + return luaL_error(L, "Out of memory"); + } + memcpy(new_buf, buf, buf_len); + + buffer_set_pixel((uint8_t*)new_buf, width, height, x, y, r, g, b, a); + + lua_pushlstring(L, new_buf, buf_len); + free(new_buf); + return 1; +} + +/* lua_buffer_fill_circle(buffer, width, height, cx, cy, radius, r, g, b, a) -> new_buffer + * Fills a circle and returns the modified buffer */ +int lua_buffer_fill_circle(lua_State* L) { + size_t buf_len; + const char* buf = luaL_checklstring(L, 1, &buf_len); + int width = luaL_checkinteger(L, 2); + int height = luaL_checkinteger(L, 3); + int cx = luaL_checkinteger(L, 4); + int cy = luaL_checkinteger(L, 5); + int radius = luaL_checkinteger(L, 6); + int r = luaL_checkinteger(L, 7); + int g = luaL_checkinteger(L, 8); + int b = luaL_checkinteger(L, 9); + int a = luaL_optinteger(L, 10, 255); + + size_t expected = (size_t)width * height * 4; + if (buf_len < expected) { + return luaL_error(L, "Buffer too small"); + } + + /* Create a copy of the buffer to modify */ + char* new_buf = (char*)malloc(buf_len); + if (!new_buf) { + return luaL_error(L, "Out of memory"); + } + memcpy(new_buf, buf, buf_len); + + /* Fill circle using squared distance */ + int r2 = radius * radius; + for (int dy = -radius; dy <= radius; dy++) { + for (int dx = -radius; dx <= radius; dx++) { + if (dx*dx + dy*dy <= r2) { + buffer_set_pixel((uint8_t*)new_buf, width, height, cx + dx, cy + dy, r, g, b, a); + } + } + } + + lua_pushlstring(L, new_buf, buf_len); + free(new_buf); + return 1; +} + +/* lua_buffer_fill_circle_inplace(buffer, width, height, cx, cy, radius, r, g, b, a) + * Fills a circle by modifying buffer in-place (no copy, no return) */ +int lua_buffer_fill_circle_inplace(lua_State* L) { + size_t buf_len; + char* buf = (char*)luaL_checklstring(L, 1, &buf_len); + int width = luaL_checkinteger(L, 2); + int height = luaL_checkinteger(L, 3); + int cx = luaL_checkinteger(L, 4); + int cy = luaL_checkinteger(L, 5); + int radius = luaL_checkinteger(L, 6); + int r = luaL_checkinteger(L, 7); + int g = luaL_checkinteger(L, 8); + int b = luaL_checkinteger(L, 9); + int a = luaL_optinteger(L, 10, 255); + + size_t expected = (size_t)width * height * 4; + if (buf_len < expected) { + return 0; + } + + /* Modify buffer in-place */ + int r2 = radius * radius; + for (int dy = -radius; dy <= radius; dy++) { + for (int dx = -radius; dx <= radius; dx++) { + if (dx*dx + dy*dy <= r2) { + buffer_set_pixel((uint8_t*)buf, width, height, cx + dx, cy + dy, r, g, b, a); + } + } + } + + return 0; +} + +/* lua_buffer_draw_line_inplace(buffer, width, height, x1, y1, x2, y2, thickness, r, g, b, a) + * Draws a line with variable thickness by modifying buffer in-place (no copy, no return) */ +int lua_buffer_draw_line_inplace(lua_State* L) { + size_t buf_len; + char* buf = (char*)luaL_checklstring(L, 1, &buf_len); + int width = luaL_checkinteger(L, 2); + int height = luaL_checkinteger(L, 3); + int x1 = luaL_checkinteger(L, 4); + int y1 = luaL_checkinteger(L, 5); + int x2 = luaL_checkinteger(L, 6); + int y2 = luaL_checkinteger(L, 7); + int thickness = luaL_checkinteger(L, 8); + int r = luaL_checkinteger(L, 9); + int g = luaL_checkinteger(L, 10); + int b = luaL_checkinteger(L, 11); + int a = luaL_optinteger(L, 12, 255); + + size_t expected = (size_t)width * height * 4; + if (buf_len < expected) { + return 0; + } + + /* Bresenham's line algorithm with thickness */ + int dx = abs(x2 - x1); + int dy = abs(y2 - y1); + int sx = x1 < x2 ? 1 : -1; + int sy = y1 < y2 ? 1 : -1; + int err = dx - dy; + + int radius = thickness / 2; + int r2 = radius * radius; + + while (1) { + /* Draw a filled circle at each point for thickness */ + if (radius <= 0) { + buffer_set_pixel((uint8_t*)buf, width, height, x1, y1, r, g, b, a); + } else { + for (int tdy = -radius; tdy <= radius; tdy++) { + for (int tdx = -radius; tdx <= radius; tdx++) { + if (tdx*tdx + tdy*tdy <= r2) { + buffer_set_pixel((uint8_t*)buf, width, height, x1 + tdx, y1 + tdy, r, g, b, a); + } + } + } + } + + if (x1 == x2 && y1 == y2) break; + + int e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x1 += sx; + } + if (e2 < dx) { + err += dx; + y1 += sy; + } + } + + return 0; +} + +/* lua_buffer_draw_line(buffer, width, height, x1, y1, x2, y2, thickness, r, g, b, a) -> new_buffer + * Draws a line with variable thickness using Bresenham's algorithm */ +int lua_buffer_draw_line(lua_State* L) { + size_t buf_len; + const char* buf = luaL_checklstring(L, 1, &buf_len); + int width = luaL_checkinteger(L, 2); + int height = luaL_checkinteger(L, 3); + int x1 = luaL_checkinteger(L, 4); + int y1 = luaL_checkinteger(L, 5); + int x2 = luaL_checkinteger(L, 6); + int y2 = luaL_checkinteger(L, 7); + int thickness = luaL_checkinteger(L, 8); + int r = luaL_checkinteger(L, 9); + int g = luaL_checkinteger(L, 10); + int b = luaL_checkinteger(L, 11); + int a = luaL_optinteger(L, 12, 255); + + size_t expected = (size_t)width * height * 4; + if (buf_len < expected) { + return luaL_error(L, "Buffer too small"); + } + + /* Create a copy of the buffer to modify */ + char* new_buf = (char*)malloc(buf_len); + if (!new_buf) { + return luaL_error(L, "Out of memory"); + } + memcpy(new_buf, buf, buf_len); + + /* Bresenham's line algorithm with thickness */ + int dx = abs(x2 - x1); + int dy = abs(y2 - y1); + int sx = x1 < x2 ? 1 : -1; + int sy = y1 < y2 ? 1 : -1; + int err = dx - dy; + + int radius = thickness / 2; + int r2 = radius * radius; + + while (1) { + /* Draw a filled circle at each point for thickness */ + if (radius <= 0) { + buffer_set_pixel((uint8_t*)new_buf, width, height, x1, y1, r, g, b, a); + } else { + for (int tdy = -radius; tdy <= radius; tdy++) { + for (int tdx = -radius; tdx <= radius; tdx++) { + if (tdx*tdx + tdy*tdy <= r2) { + buffer_set_pixel((uint8_t*)new_buf, width, height, x1 + tdx, y1 + tdy, r, g, b, a); + } + } + } + } + + if (x1 == x2 && y1 == y2) break; + + int e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x1 += sx; + } + if (e2 < dx) { + err += dx; + y1 += sy; + } + } + + lua_pushlstring(L, new_buf, buf_len); + free(new_buf); + return 1; +} + +/* lua_buffer_fill_rect(buffer, width, height, x, y, w, h, r, g, b, a) -> new_buffer + * Fills a rectangle and returns the modified buffer */ +int lua_buffer_fill_rect(lua_State* L) { + size_t buf_len; + const char* buf = luaL_checklstring(L, 1, &buf_len); + int buf_width = luaL_checkinteger(L, 2); + int buf_height = luaL_checkinteger(L, 3); + int x = luaL_checkinteger(L, 4); + int y = luaL_checkinteger(L, 5); + int w = luaL_checkinteger(L, 6); + int h = luaL_checkinteger(L, 7); + int r = luaL_checkinteger(L, 8); + int g = luaL_checkinteger(L, 9); + int b = luaL_checkinteger(L, 10); + int a = luaL_optinteger(L, 11, 255); + + size_t expected = (size_t)buf_width * buf_height * 4; + if (buf_len < expected) { + return luaL_error(L, "Buffer too small"); + } + + /* Create a copy of the buffer to modify */ + char* new_buf = (char*)malloc(buf_len); + if (!new_buf) { + return luaL_error(L, "Out of memory"); + } + memcpy(new_buf, buf, buf_len); + + /* Clip rectangle to buffer bounds */ + int x1 = x < 0 ? 0 : x; + int y1 = y < 0 ? 0 : y; + int x2 = (x + w) > buf_width ? buf_width : (x + w); + int y2 = (y + h) > buf_height ? buf_height : (y + h); + + /* Fill rectangle row by row (cache-friendly) */ + for (int py = y1; py < y2; py++) { + for (int px = x1; px < x2; px++) { + size_t offset = ((size_t)py * buf_width + px) * 4; + new_buf[offset] = b; + new_buf[offset + 1] = g; + new_buf[offset + 2] = r; + new_buf[offset + 3] = a; + } + } + + lua_pushlstring(L, new_buf, buf_len); + free(new_buf); + return 1; +} + +/* lua_buffer_fill(buffer, width, height, r, g, b, a) -> new_buffer + * Fills the entire buffer with a color */ +int lua_buffer_fill(lua_State* L) { + size_t buf_len; + const char* buf = luaL_checklstring(L, 1, &buf_len); + int width = luaL_checkinteger(L, 2); + int height = luaL_checkinteger(L, 3); + int r = luaL_checkinteger(L, 4); + int g = luaL_checkinteger(L, 5); + int b = luaL_checkinteger(L, 6); + int a = luaL_optinteger(L, 7, 255); + + size_t expected = (size_t)width * height * 4; + if (buf_len < expected) { + return luaL_error(L, "Buffer too small"); + } + + /* Create new buffer and fill it */ + char* new_buf = (char*)malloc(buf_len); + if (!new_buf) { + return luaL_error(L, "Out of memory"); + } + + /* Fill all pixels */ + size_t num_pixels = (size_t)width * height; + for (size_t i = 0; i < num_pixels; i++) { + size_t offset = i * 4; + new_buf[offset] = b; + new_buf[offset + 1] = g; + new_buf[offset + 2] = r; + new_buf[offset + 3] = a; + } + + lua_pushlstring(L, new_buf, buf_len); + free(new_buf); + return 1; +} + +/* lua_buffer_blit_row(buffer, width, height, y, row_data, start_x) -> new_buffer + * Blits a row of BGRA pixel data at the specified y position */ +int lua_buffer_blit_row(lua_State* L) { + size_t buf_len; + const char* buf = luaL_checklstring(L, 1, &buf_len); + int width = luaL_checkinteger(L, 2); + int height = luaL_checkinteger(L, 3); + int y = luaL_checkinteger(L, 4); + size_t row_len; + const char* row_data = luaL_checklstring(L, 5, &row_len); + int start_x = luaL_optinteger(L, 6, 0); + + size_t expected = (size_t)width * height * 4; + if (buf_len < expected) { + return luaL_error(L, "Buffer too small"); + } + + if (y < 0 || y >= height) { + /* Row out of bounds, return original buffer */ + lua_pushvalue(L, 1); + return 1; + } + + /* Create a copy of the buffer to modify */ + char* new_buf = (char*)malloc(buf_len); + if (!new_buf) { + return luaL_error(L, "Out of memory"); + } + memcpy(new_buf, buf, buf_len); + + /* Calculate how many pixels to copy */ + int pixels_in_row = row_len / 4; + int dest_offset = (y * width + start_x) * 4; + int pixels_to_copy = pixels_in_row; + + /* Clip to buffer bounds */ + if (start_x < 0) { + int skip = -start_x; + row_data += skip * 4; + pixels_to_copy -= skip; + start_x = 0; + dest_offset = y * width * 4; + } + if (start_x + pixels_to_copy > width) { + pixels_to_copy = width - start_x; + } + + if (pixels_to_copy > 0) { + memcpy(new_buf + dest_offset, row_data, pixels_to_copy * 4); + } + + lua_pushlstring(L, new_buf, buf_len); + free(new_buf); + return 1; +} + +/* lua_buffer_create(width, height, r, g, b, a) -> buffer + * Creates a new BGRA buffer filled with the specified color */ +int lua_buffer_create(lua_State* L) { + int width = luaL_checkinteger(L, 1); + int height = luaL_checkinteger(L, 2); + int r = luaL_optinteger(L, 3, 255); + int g = luaL_optinteger(L, 4, 255); + int b = luaL_optinteger(L, 5, 255); + int a = luaL_optinteger(L, 6, 255); + + if (width <= 0 || height <= 0 || width > 8192 || height > 8192) { + return luaL_error(L, "Invalid buffer dimensions"); + } + + size_t buf_len = (size_t)width * height * 4; + char* buf = (char*)malloc(buf_len); + if (!buf) { + return luaL_error(L, "Out of memory"); + } + + /* Fill all pixels */ + size_t num_pixels = (size_t)width * height; + for (size_t i = 0; i < num_pixels; i++) { + size_t offset = i * 4; + buf[offset] = b; + buf[offset + 1] = g; + buf[offset + 2] = r; + buf[offset + 3] = a; + } + + lua_pushlstring(L, buf, buf_len); + free(buf); + return 1; +} diff --git a/iso_includes/apps/com.luajitos.background/manifest.lua b/iso_includes/apps/com.luajitos.background/manifest.lua @@ -5,7 +5,7 @@ return { category = "all", description = "LuajitOS Desktop Background"; entry = "background.lua"; - type = "gui"; + type = "background"; hidden = true; -- Hide from start menu autostart = true; -- Launch on system startup autostartPriority = 1; -- Launch first (background should be behind everything) diff --git a/iso_includes/apps/com.luajitos.crypto/manifest.lua b/iso_includes/apps/com.luajitos.crypto/manifest.lua @@ -6,8 +6,10 @@ return { description = "Command-line cryptographic utility for hashing, encryption, signing, and key derivation", entry = "init.lua", type = "cli", + autorun = true, permissions = { "stdio", -- Command-line output - "export" -- Export crypto library for other apps + "export", -- Export crypto library for other apps + "crypto" -- Access to crypto library } } diff --git a/iso_includes/apps/com.luajitos.crypto/src/init.lua b/iso_includes/apps/com.luajitos.crypto/src/init.lua @@ -295,7 +295,27 @@ if #positional == 0 then return end -local command = positional[1] +-- Normalize command - uppercase for hash/kdf commands, preserve case for others +local rawCommand = positional[1] +local command = rawCommand:upper() + +-- Map uppercase to proper mixed-case for specific commands +local commandMap = { + ["ED25519.KEYPAIR"] = "Ed25519.keypair", + ["ED25519.SIGN"] = "Ed25519.sign", + ["ED25519.VERIFY"] = "Ed25519.verify", + ["X25519.KEYPAIR"] = "X25519.keypair", + ["X25519.PUBLICKEY"] = "X25519.publicKey", + ["X25519.SHARED"] = "X25519.shared", + ["GENERATESALT"] = "generateSalt", + ["NEWKEY"] = "newKey", + ["ENCRYPT"] = "encrypt", + ["DECRYPT"] = "decrypt", + ["RANDOM"] = "random", +} +if commandMap[command] then + command = commandMap[command] +end -- Hash commands if command == "MD5" then diff --git a/iso_includes/apps/com.luajitos.lunareditor/src/editor.lua b/iso_includes/apps/com.luajitos.lunareditor/src/editor.lua @@ -438,6 +438,11 @@ end -- Selection edit callback for multi-line selection editing -- point1 and point2 are {x, y} coordinates in the content area window.onSelectionEditted = function(point1, point2, newContent) + -- Safety check for valid points + if not point1 or not point2 or not point1.x or not point1.y or not point2.x or not point2.y then + return + end + -- Convert y coordinates to line numbers -- Text starts at y = toolbarHeight + 2, each line is lineHeight pixels local textStartY = toolbarHeight + 2 @@ -456,16 +461,24 @@ window.onSelectionEditted = function(point1, point2, newContent) startCol, endCol = endCol, startCol end - -- Validate line numbers + -- Validate line numbers - if selection is completely outside valid range, abort + if startLine > #lines and endLine > #lines then return end if startLine < 1 then startLine = 1 end if endLine > #lines then endLine = #lines end - if startLine > #lines then return end + if startLine > #lines then startLine = #lines end + if #lines == 0 then + lines = {""} + startLine = 1 + endLine = 1 + end -- Clamp column positions if startCol < 1 then startCol = 1 end if endCol < 1 then endCol = 1 end - if startCol > #lines[startLine] + 1 then startCol = #lines[startLine] + 1 end - if endCol > #lines[endLine] then endCol = #lines[endLine] end + local startLineLen = lines[startLine] and #lines[startLine] or 0 + local endLineLen = lines[endLine] and #lines[endLine] or 0 + if startCol > startLineLen + 1 then startCol = startLineLen + 1 end + if endCol > endLineLen then endCol = endLineLen end -- Get the text before and after the selection local beforeText = lines[startLine]:sub(1, startCol - 1) diff --git a/iso_includes/apps/com.luajitos.moonbrowser/src/render.lua b/iso_includes/apps/com.luajitos.moonbrowser/src/render.lua @@ -260,11 +260,11 @@ function Renderer:renderElement(elem, gfx, x, y, maxWidth, inheritedFontSize) -- Draw underline gfx:fillRect(currentX, y + linkHeight - 2, linkWidth, 1, 0x0000FF) - -- Store for click handling + -- Store for click handling (subtract offsetY since y includes it but clicks are relative to content) table.insert(self.interactiveElements, { type = "link", x = currentX, - y = y + self.scrollY, + y = y + self.scrollY - self.offsetY, width = linkWidth, height = linkHeight, text = linkText, @@ -316,11 +316,11 @@ function Renderer:renderElement(elem, gfx, x, y, maxWidth, inheritedFontSize) -- Button text (centered) gfx:drawText(drawX + 8, drawY + 6, buttonText, 0x000000) - -- Store for click handling + -- Store for click handling (subtract offsetY since drawY includes it) table.insert(self.interactiveElements, { type = "button", x = drawX, - y = drawY + self.scrollY, -- Store absolute position + y = drawY + self.scrollY - self.offsetY, width = btnWidth, height = btnHeight, text = buttonText, @@ -389,12 +389,12 @@ function Renderer:renderElement(elem, gfx, x, y, maxWidth, inheritedFontSize) end end - -- Store for click handling + -- Store for click handling (subtract offsetY) table.insert(self.interactiveElements, { type = "input", inputType = inputType, x = currentX, - y = y + self.scrollY, + y = y + self.scrollY - self.offsetY, width = inputWidth, height = inputHeight, name = inputName, @@ -425,7 +425,7 @@ function Renderer:renderElement(elem, gfx, x, y, maxWidth, inheritedFontSize) table.insert(self.interactiveElements, { type = "submit", x = currentX, - y = y + self.scrollY, + y = y + self.scrollY - self.offsetY, width = btnWidth, height = btnHeight, text = buttonText, @@ -467,7 +467,7 @@ function Renderer:renderElement(elem, gfx, x, y, maxWidth, inheritedFontSize) table.insert(self.interactiveElements, { type = "checkbox", x = currentX, - y = y + 2 + self.scrollY, + y = y + 2 + self.scrollY - self.offsetY, width = checkSize, height = checkSize, name = inputName, @@ -519,7 +519,7 @@ function Renderer:renderElement(elem, gfx, x, y, maxWidth, inheritedFontSize) table.insert(self.interactiveElements, { type = "textarea", x = currentX, - y = y + self.scrollY, + y = y + self.scrollY - self.offsetY, width = textareaWidth, height = textareaHeight, name = inputName, diff --git a/iso_includes/apps/com.luajitos.paint/icon.png b/iso_includes/apps/com.luajitos.paint/icon.png Binary files differ. diff --git a/iso_includes/apps/com.luajitos.paint/src/init.lua b/iso_includes/apps/com.luajitos.paint/src/init.lua @@ -1,5 +1,5 @@ -- LuaJIT OS Paint App --- Simple version - draws directly to screen via gfx +-- Uses ImageBuffer userdata for true in-place drawing (no string copies) -- Dialog is pre-loaded in the sandbox, no require() needed @@ -10,8 +10,10 @@ local initialHeight = 250 + toolbarHeight local window = app:newWindow("Paint", initialWidth, initialHeight) window.resizable = true -- Allow resizing --- Store drawn strokes (lines between points) -local strokes = {} +-- Canvas buffer (ImageBuffer userdata - true mutable buffer) +local canvasBuffer = nil +local canvasBufferWidth = nil +local canvasBufferHeight = nil -- Background image (native C image for display) local bgImage = nil @@ -47,8 +49,49 @@ local function isInside(x, y, rx, ry, rw, rh) return x >= rx and x < rx + rw and y >= ry and y < ry + rh end --- Draw line between two points using Bresenham's algorithm +-- Ensure canvas buffer exists and matches window size +local function ensureCanvasBuffer() + local canvasW = window.width + local canvasH = window.height - toolbarHeight + + if not canvasBuffer or canvasBufferWidth ~= canvasW or canvasBufferHeight ~= canvasH then + -- Create new ImageBuffer userdata (true mutable buffer) + local newBuffer = ImageBufferNew(canvasW, canvasH) + newBuffer:fill(0xFFFFFF) + + -- If we had an old buffer, copy it over (for resize) + if canvasBuffer then + local oldW, oldH = canvasBuffer:getSize() + for y = 0, math.min(oldH, canvasH) - 1 do + for x = 0, math.min(oldW, canvasW) - 1 do + local r, g, b = canvasBuffer:getPixel(x, y) + if r then + newBuffer:setPixel(x, y, r, g, b) + end + end + end + end + + canvasBuffer = newBuffer + canvasBufferWidth = canvasW + canvasBufferHeight = canvasH + end +end + +-- Draw a filled circle to the canvas buffer (in-place, no copies) +local function drawCircleToBuffer(cx, cy, radius, color) + if not canvasBuffer then return end + canvasBuffer:fillCircle(cx, cy, radius, color) +end + +-- Draw line between two points with thickness (in-place, no copies) local function addLine(x1, y1, x2, y2) + if not canvasBuffer then return end + canvasBuffer:drawLine(x1, y1, x2, y2, brushRadius * 2, brushColor) +end + +-- Legacy addLine implementation for fallback (not used when C functions available) +local function addLineLegacy(x1, y1, x2, y2) local dx = math.abs(x2 - x1) local dy = math.abs(y2 - y1) local sx = x1 < x2 and 1 or -1 @@ -56,8 +99,8 @@ local function addLine(x1, y1, x2, y2) local err = dx - dy while true do - -- Add point - strokes[#strokes + 1] = {x = x1, y = y1, color = brushColor, r = brushRadius} + -- Draw point directly to buffer + drawCircleToBuffer(x1, y1, brushRadius, brushColor) if x1 == x2 and y1 == y2 then break end local e2 = 2 * err @@ -107,7 +150,29 @@ local function loadImage(path) bgImageHeight = ImageGetHeight(nativeImg) end currentFile = path - strokes = {} + + -- Load image into canvas buffer using Image.open + Image.fsOverride = fs + local luaImg, err = Image.open(path) + Image.fsOverride = nil + + if luaImg then + local imgW, imgH = luaImg:getSize() + canvasBuffer = Image.new(imgW, imgH, false) + canvasBuffer:fill(0xFFFFFF) + -- Copy pixels + for y = 0, imgH - 1 do + for x = 0, imgW - 1 do + local r, g, b = luaImg:getPixel(x, y) + if r then + canvasBuffer:setPixel(x, y, r, g, b) + end + end + end + canvasBufferWidth = imgW + canvasBufferHeight = imgH + end + return true end @@ -117,7 +182,10 @@ end -- Button definitions local buttons = { {x = 5, y = 5, w = 40, h = 20, label = "New", action = function() - strokes = {} + -- Clear canvas buffer + if canvasBuffer then + canvasBuffer:fill(0xFFFFFF) + end if bgImage and ImageDestroy then ImageDestroy(bgImage) bgImage = nil @@ -153,63 +221,15 @@ local buttons = { }) dlg:openDialog(function(path) if path then - -- Get current canvas dimensions - local canvasW = window.width - local canvasH = window.height - toolbarHeight - - local img - local imgW, imgH - - -- If we have a background image, load it with Image.open for saving - if currentFile and bgImage then - -- Use Image.open to get the Lua Image object - Image.fsOverride = fs - local loadedImg, err = Image.open(currentFile) - Image.fsOverride = nil - - if loadedImg then - img = loadedImg - imgW, imgH = img:getSize() - else - print("Paint: Failed to reload image for save: " .. (err or "unknown")) - end - end + -- Ensure we have a canvas buffer + ensureCanvasBuffer() - -- If no background image loaded, create new white canvas - if not img then - imgW = canvasW - imgH = canvasH - img = Image.new(imgW, imgH, false) - img:fill(0xFFFFFF) - end - - -- Use batch mode for efficient stroke drawing - img:beginBatch() - - -- Draw all strokes - for _, s in ipairs(strokes) do - local rad = s.r - -- Extract RGB from color - local sr = bit.band(bit.rshift(s.color, 16), 0xFF) - local sg = bit.band(bit.rshift(s.color, 8), 0xFF) - local sb = bit.band(s.color, 0xFF) - - for dy = -rad, rad do - for dx = -rad, rad do - if dx*dx + dy*dy <= rad*rad then - local px, py = s.x + dx, s.y + dy - if px >= 0 and px < imgW and py >= 0 and py < imgH then - img:_batchWritePixel(px, py, sr, sg, sb) - end - end - end - end + if not canvasBuffer then + print("Paint: No canvas to save") + return end - -- End batch mode to finalize buffer - img:endBatch() - - -- Detect format from extension, or use magic numbers from original file + -- Detect format from extension local ext = path:lower():match("%.([^%.]+)$") -- If no extension, try to detect from original file's magic bytes @@ -232,15 +252,15 @@ local buttons = { local success, err if ext == "bmp" then - success, err = img:saveAsBMP(path, {fs = fs}) + success, err = canvasBuffer:saveAsBMP(path, {fs = fs}) elseif ext == "jpg" or ext == "jpeg" then - success, err = img:saveAsJPEG(path, {fs = fs}) + success, err = canvasBuffer:saveAsJPEG(path, {fs = fs}) elseif ext == "png" then - success, err = img:saveAsPNG(path, {fs = fs}) + success, err = canvasBuffer:saveAsPNG(path, {fs = fs}) else -- Unknown extension, add .png and save path = path .. ".png" - success, err = img:saveAsPNG(path, {fs = fs}) + success, err = canvasBuffer:saveAsPNG(path, {fs = fs}) end if success then @@ -281,22 +301,25 @@ window.onMouseDown = function(mx, my) -- Clear button local clearX = margin + #colors*(sz+margin) + 10 if isInside(mx, my, clearX, colorY, 40, sz) then - strokes = {} + if canvasBuffer then + canvasBuffer:fill(0xFFFFFF) + end window:markDirty() return end else -- Start drawing on canvas + ensureCanvasBuffer() isDrawing = true local canvasY = my - toolbarHeight lastX, lastY = mx, canvasY - -- Add initial point - strokes[#strokes + 1] = {x = mx, y = canvasY, color = brushColor, r = brushRadius} + -- Add initial point directly to buffer + drawCircleToBuffer(mx, canvasY, brushRadius, brushColor) window:markDirty() end end --- Mouse move - continue drawing line +-- Mouse move - continue drawing line (no throttling needed with ImageBuffer) window.onMouseMove = function(mx, my) if isDrawing and my >= toolbarHeight then local canvasY = my - toolbarHeight @@ -351,28 +374,15 @@ window.onDraw = function(gfx) gfx:drawRect(clearX, colorY, 40, sz, 0xAAAAAA) gfx:drawText(clearX + 4, colorY + 4, "Clr", 0xFFFFFF) - -- Canvas background (white or loaded image) - if bgImage and ImageDraw then - ImageDraw(bgImage, 0, toolbarHeight) + -- Draw canvas buffer (contains both background and strokes) + ensureCanvasBuffer() + if canvasBuffer then + -- Draw the entire canvas buffer in one operation + gfx:drawBuffer(canvasBuffer, 0, toolbarHeight) else gfx:fillRect(0, toolbarHeight, canvasW, canvasH, 0xFFFFFF) end - -- Draw all strokes as filled circles - for _, s in ipairs(strokes) do - local r = s.r - for dy = -r, r do - for dx = -r, r do - if dx*dx + dy*dy <= r*r then - local px, py = s.x + dx, s.y + dy + toolbarHeight - if px >= 0 and px < canvasW and py >= toolbarHeight and py < winH then - gfx:fillRect(px, py, 1, 1, s.color) - end - end - end - end - end - gfx:drawRect(0, toolbarHeight, canvasW, canvasH, 0x000000) end diff --git a/iso_includes/apps/com.luajitos.spreadsheet/icon.png b/iso_includes/apps/com.luajitos.spreadsheet/icon.png Binary files differ. diff --git a/iso_includes/apps/com.luajitos.spreadsheet/manifest.lua b/iso_includes/apps/com.luajitos.spreadsheet/manifest.lua @@ -0,0 +1,14 @@ +return { + name = "Spreadsheet", + identifier = "com.luajitos.spreadsheet", + version = "1.0.0", + author = "LuajitOS", + description = "Spreadsheet application with formula support", + entry = "init.lua", + permissions = { + "filesystem", + "window", + "load", + "setfenv" + } +} diff --git a/iso_includes/apps/com.luajitos.spreadsheet/src/init.lua b/iso_includes/apps/com.luajitos.spreadsheet/src/init.lua @@ -0,0 +1,835 @@ +-- LuaJIT OS Spreadsheet Application +-- Infinite spreadsheet with CSV support and formula evaluation + +local toolbarHeight = 30 +local headerHeight = 20 +local rowHeight = 22 +local defaultColWidth = 80 +local rowHeaderWidth = 40 + +local initialWidth = 600 +local initialHeight = 400 + +local window = app:newWindow("Spreadsheet", initialWidth, initialHeight) +window.resizable = true + +-- Spreadsheet data: rows[y] = {cells...}, grows as needed +local rows = {} +local colWidths = {} -- Custom column widths + +-- Selection +local selectedX = 1 +local selectedY = 1 +local editing = false +local editBuffer = "" + +-- Formula bar +local formulaBarActive = false +local formulaBuffer = "" + +-- Scroll position +local scrollX = 0 +local scrollY = 0 + +-- Current file +local currentFile = nil + +-- Forward declaration +local ensureSelectionVisible + +-- Get or create a row +local function getRow(y) + if not rows[y] then + rows[y] = {} + end + return rows[y] +end + +-- Get cell value +local function getCell(x, y) + local row = rows[y] + if row then + return row[x] or "" + end + return "" +end + +-- Set cell value +local function setCell(x, y, value) + local row = getRow(y) + row[x] = value +end + +-- Get column width +local function getColWidth(x) + return colWidths[x] or defaultColWidth +end + +-- Ensure selected cell is visible (scroll if needed) +ensureSelectionVisible = function(winW, winH) + -- Calculate cell position + local cellX = rowHeaderWidth + for col = 1, selectedX - 1 do + cellX = cellX + getColWidth(col) + end + local cellW = getColWidth(selectedX) + + -- Horizontal scrolling + local visibleLeft = scrollX + local visibleRight = scrollX + (winW - rowHeaderWidth) + + if cellX - rowHeaderWidth < visibleLeft then + -- Cell is too far left, scroll left + scrollX = cellX - rowHeaderWidth + elseif cellX - rowHeaderWidth + cellW > visibleRight then + -- Cell is too far right, scroll right + scrollX = cellX - rowHeaderWidth + cellW - (winW - rowHeaderWidth) + end + + -- Vertical scrolling + local visibleTop = scrollY + local visibleBottom = scrollY + (winH - toolbarHeight - headerHeight) + local cellTop = (selectedY - 1) * rowHeight + local cellBottom = cellTop + rowHeight + + if cellTop < visibleTop then + -- Cell is above view, scroll up + scrollY = cellTop + elseif cellBottom > visibleBottom then + -- Cell is below view, scroll down + scrollY = cellBottom - (winH - toolbarHeight - headerHeight) + end + + -- Clamp scroll values + if scrollX < 0 then scrollX = 0 end + if scrollY < 0 then scrollY = 0 end +end + +-- Convert column number to letter (1=A, 2=B, ..., 27=AA) +local function colToLetter(n) + local result = "" + while n > 0 do + n = n - 1 + result = string.char(65 + (n % 26)) .. result + n = math.floor(n / 26) + end + return result +end + +-- Convert column letter to number (A=1, B=2, ..., AA=27) +local function letterToCol(letters) + local result = 0 + for i = 1, #letters do + result = result * 26 + (letters:byte(i) - 64) + end + return result +end + +-- Expand range notation [A1-C4] to list of IN() calls +local function expandRange(formula) + return formula:gsub("%[([A-Z]+)(%d+)%-([A-Z]+)(%d+)%]", function(col1Str, row1Str, col2Str, row2Str) + local col1 = letterToCol(col1Str) + local row1 = tonumber(row1Str) + local col2 = letterToCol(col2Str) + local row2 = tonumber(row2Str) + local cells = {} + -- Iterate rows first, then columns + for row = row1, row2 do + for col = col1, col2 do + table.insert(cells, "IN(" .. col .. "," .. row .. ")") + end + end + return table.concat(cells, ", ") + end) +end + +-- Convert cell references like A4, BC12 to IN(col, row) +local function preprocessFormula(formula) + -- First expand range notation [A1-C4] + formula = expandRange(formula) + + -- Then replace cell references A1, BC23, etc. with IN(col, row) + -- Use frontier pattern %f to ensure we don't match inside words like SUM, AVG, etc. + -- Match cell refs that are preceded by non-letter or start of string + local result = "" + local i = 1 + while i <= #formula do + -- Check if we're at a potential cell reference + local col, row, endPos = formula:match("^([A-Z]+)(%d+)()", i) + if col then + -- Check if preceded by a letter (would be part of function name) + local prevChar = i > 1 and formula:sub(i-1, i-1) or "" + if prevChar:match("[A-Za-z]") then + -- Part of a function name, don't convert + result = result .. col + i = i + #col + else + -- Standalone cell reference, convert it + local colNum = letterToCol(col) + result = result .. "IN(" .. colNum .. "," .. row .. ")" + i = endPos + end + else + result = result .. formula:sub(i, i) + i = i + 1 + end + end + return result +end + +-- Formula sandbox environment +local function createFormulaSandbox() + local sandbox = {} + + -- IN(x, y) - get cell value at position + sandbox.IN = function(x, y) + local val = getCell(x, y) + -- Try to convert to number + local num = tonumber(val) + if num then return num end + -- Check if it's a formula result + if type(val) == "string" and val:sub(1, 2) == "%=" then + -- Evaluate nested formula + local result = evaluateFormula(val) + return result + end + return val + end + + -- SELECT(x, y, direction, length) - get multiple cells + sandbox.SELECT = function(x, y, direction, length) + local results = {} + local dx, dy = 0, 0 + if direction == "up" then dy = -1 + elseif direction == "down" then dy = 1 + elseif direction == "left" then dx = -1 + elseif direction == "right" then dx = 1 + end + + for i = 0, length - 1 do + local cx = x + dx * i + local cy = y + dy * i + local val = sandbox.IN(cx, cy) + table.insert(results, val) + end + return unpack(results) + end + + -- SUM(...) - sum all numeric arguments + sandbox.SUM = function(...) + local args = {...} + local total = 0 + for _, v in ipairs(args) do + local num = tonumber(v) + if num then + total = total + num + end + end + return total + end + + -- CONCAT(...) - concatenate strings + sandbox.CONCAT = function(...) + local args = {...} + local result = "" + for _, v in ipairs(args) do + result = result .. tostring(v) + end + return result + end + + -- REPEAT(str, count, separator) + sandbox.REPEAT = function(str, count, separator) + separator = separator or "" + local parts = {} + for i = 1, count do + table.insert(parts, str) + end + return table.concat(parts, separator) + end + + -- AVG(...) - average of numeric arguments + sandbox.AVG = function(...) + local args = {...} + local total = 0 + local count = 0 + for _, v in ipairs(args) do + local num = tonumber(v) + if num then + total = total + num + count = count + 1 + end + end + if count == 0 then return 0 end + return total / count + end + + -- MIN/MAX + sandbox.MIN = function(...) + local args = {...} + local result = nil + for _, v in ipairs(args) do + local num = tonumber(v) + if num then + if result == nil or num < result then + result = num + end + end + end + return result or 0 + end + + sandbox.MAX = function(...) + local args = {...} + local result = nil + for _, v in ipairs(args) do + local num = tonumber(v) + if num then + if result == nil or num > result then + result = num + end + end + end + return result or 0 + end + + -- Basic math + sandbox.math = math + sandbox.tostring = tostring + sandbox.tonumber = tonumber + sandbox.type = type + + return sandbox +end + +-- Evaluate a formula +local function evaluateFormula(formula) + if type(formula) ~= "string" or formula:sub(1, 2) ~= "%=" then + return formula + end + + local code = formula:sub(3) -- Remove %= + + -- Preprocess: convert cell references (A1, BC23) to IN(col, row) + code = preprocessFormula(code) + + local sandbox = createFormulaSandbox() + + -- Try to compile the formula + local fn, err = loadstring("return " .. code) + if not fn then + return "#ERR: " .. tostring(err) + end + + -- Set environment to sandbox + setfenv(fn, sandbox) + + local ok, result = pcall(fn) + if not ok then + return "#ERR: " .. tostring(result) + end + + return result +end + +-- Get display value for a cell (evaluates formulas) +local function getDisplayValue(x, y) + local val = getCell(x, y) + if type(val) == "string" and val:sub(1, 2) == "%=" then + return tostring(evaluateFormula(val)) + end + return tostring(val) +end + +-- Parse CSV line +local function parseCSVLine(line) + local cells = {} + local current = "" + local inQuotes = false + + for i = 1, #line do + local c = line:sub(i, i) + if c == '"' then + inQuotes = not inQuotes + elseif c == ',' and not inQuotes then + table.insert(cells, current) + current = "" + else + current = current .. c + end + end + table.insert(cells, current) + + return cells +end + +-- Load CSV file +local function loadCSV(path) + local data = fs:read(path) + if not data then return false end + + rows = {} + colWidths = {} + + local y = 1 + for line in data:gmatch("[^\r\n]+") do + local cells = parseCSVLine(line) + rows[y] = {} + for x, val in ipairs(cells) do + rows[y][x] = val + end + y = y + 1 + end + + currentFile = path + selectedX = 1 + selectedY = 1 + scrollX = 0 + scrollY = 0 + window:markDirty() + return true +end + +-- Generate CSV content +local function generateCSV() + local maxX = 0 + local maxY = 0 + + -- Find bounds + for y, row in pairs(rows) do + if y > maxY then maxY = y end + for x, _ in pairs(row) do + if x > maxX then maxX = x end + end + end + + local lines = {} + for y = 1, maxY do + local cells = {} + for x = 1, maxX do + local val = tostring(getCell(x, y) or "") + -- Escape commas and quotes + if val:find('[,"\n]') then + val = '"' .. val:gsub('"', '""') .. '"' + end + table.insert(cells, val) + end + table.insert(lines, table.concat(cells, ",")) + end + + return table.concat(lines, "\n") +end + +-- Save CSV file +local function saveCSV(path) + local content = generateCSV() + local ok = fs:write(path, content) + if ok then + currentFile = path + end + return ok +end + +-- Clear spreadsheet +local function newSpreadsheet() + rows = {} + colWidths = {} + currentFile = nil + selectedX = 1 + selectedY = 1 + scrollX = 0 + scrollY = 0 + editing = false + editBuffer = "" + window:markDirty() +end + +-- Formula bar dimensions +local formulaBarX = 145 +local formulaBarY = 5 +local formulaBarH = 20 + +-- Button definitions +local buttons = { + {x = 5, y = 5, w = 40, h = 20, label = "New", action = newSpreadsheet}, + {x = 50, y = 5, w = 45, h = 20, label = "Open", action = function() + local dlg = Dialog.fileOpen("/", { + app = app, + fs = fs, + title = "Open CSV", + filter = {"csv"} + }) + dlg:openDialog(function(path) + if path then + loadCSV(path) + end + end) + end}, + {x = 100, y = 5, w = 40, h = 20, label = "Save", action = function() + local defaultName = "spreadsheet.csv" + if currentFile then + defaultName = currentFile:match("([^/]+)$") or defaultName + end + local dlg = Dialog.fileSave("/home", defaultName, { + app = app, + fs = fs, + title = "Save CSV" + }) + dlg:openDialog(function(path) + if path then + if not path:match("%.csv$") then + path = path .. ".csv" + end + saveCSV(path) + end + end) + end} +} + +-- Helper: check if point is inside rect +local function isInside(px, py, x, y, w, h) + return px >= x and px < x + w and py >= y and py < y + h +end + +-- Get column at screen X position +local function getColumnAtX(screenX) + local x = rowHeaderWidth - scrollX + local col = 1 + while x < screenX do + x = x + getColWidth(col) + if x >= screenX then + return col + end + col = col + 1 + if col > 1000 then break end -- Safety limit + end + return col +end + +-- Get row at screen Y position +local function getRowAtY(screenY) + local contentY = screenY - toolbarHeight - headerHeight + if contentY < 0 then return nil end + local row = math.floor((contentY + scrollY) / rowHeight) + 1 + return row +end + +-- Get X position for column +local function getColumnX(col) + local x = rowHeaderWidth + for c = 1, col - 1 do + x = x + getColWidth(c) + end + return x - scrollX +end + +-- Key handler (onInput for keyboard input) +window.onInput = function(key, scancode) + -- Scancodes: up=72, down=80, left=75, right=77, escape=1 + + if formulaBarActive then + -- Formula bar input + if key == "\n" then + -- Confirm formula - prepend %= and set cell + if formulaBuffer ~= "" then + setCell(selectedX, selectedY, "%=" .. formulaBuffer) + end + formulaBarActive = false + formulaBuffer = "" + elseif key == "\b" then + if #formulaBuffer > 0 then + formulaBuffer = formulaBuffer:sub(1, -2) + end + elseif scancode == 1 then -- Escape + formulaBarActive = false + formulaBuffer = "" + elseif key and #key == 1 and key:byte() >= 32 then + formulaBuffer = formulaBuffer .. key + end + elseif editing then + if key == "\n" then + -- Confirm edit + setCell(selectedX, selectedY, editBuffer) + editing = false + editBuffer = "" + selectedY = selectedY + 1 + elseif key == "\b" then + if #editBuffer > 0 then + editBuffer = editBuffer:sub(1, -2) + end + elseif scancode == 1 then -- Escape + editing = false + editBuffer = "" + elseif key and #key == 1 and key:byte() >= 32 then + editBuffer = editBuffer .. key + end + else + -- Navigation + local moved = false + if key == "\n" then + -- Start editing + editing = true + editBuffer = getCell(selectedX, selectedY) + elseif key == "\t" then + selectedX = selectedX + 1 + moved = true + elseif scancode == 72 then -- Up arrow + if selectedY > 1 then selectedY = selectedY - 1 end + moved = true + elseif scancode == 80 then -- Down arrow + selectedY = selectedY + 1 + moved = true + elseif scancode == 75 then -- Left arrow + if selectedX > 1 then selectedX = selectedX - 1 end + moved = true + elseif scancode == 77 then -- Right arrow + selectedX = selectedX + 1 + moved = true + elseif key == "\b" then + -- Delete cell content + setCell(selectedX, selectedY, "") + elseif key and #key == 1 and key:byte() >= 32 then + -- Start typing immediately + editing = true + editBuffer = key + end + + -- Scroll to keep selection visible + if moved then + ensureSelectionVisible(window.width, window.height) + end + end + window:markDirty() +end + +-- Click handler +window.onClick = function(mx, my) + -- Save current edit before doing anything else + if editing then + setCell(selectedX, selectedY, editBuffer) + editing = false + editBuffer = "" + end + + -- Save formula bar edit + if formulaBarActive then + if formulaBuffer ~= "" then + setCell(selectedX, selectedY, "%=" .. formulaBuffer) + end + formulaBarActive = false + formulaBuffer = "" + end + + -- Check toolbar buttons + if my < toolbarHeight then + for _, btn in ipairs(buttons) do + if isInside(mx, my, btn.x, btn.y, btn.w, btn.h) then + btn.action() + return + end + end + + -- Check formula bar click (from formulaBarX to end of window) + local formulaBarW = window.width - formulaBarX - 5 + if isInside(mx, my, formulaBarX, formulaBarY, formulaBarW, formulaBarH) then + formulaBarActive = true + -- Load existing formula if cell has one + local cellVal = getCell(selectedX, selectedY) + if type(cellVal) == "string" and cellVal:sub(1, 2) == "%=" then + formulaBuffer = cellVal:sub(3) + else + formulaBuffer = "" + end + window:markDirty() + return + end + + window:markDirty() + return + end + + -- Check column headers (for future: resize) + if my < toolbarHeight + headerHeight then + window:markDirty() + return + end + + -- Check row headers + if mx < rowHeaderWidth then + local row = getRowAtY(my) + if row then + selectedY = row + selectedX = 1 + window:markDirty() + end + return + end + + -- Click on cell + local col = getColumnAtX(mx) + local row = getRowAtY(my) + + if col and row then + if selectedX == col and selectedY == row then + -- Double-click to edit (single click already selected) + editing = true + editBuffer = getCell(selectedX, selectedY) + else + selectedX = col + selectedY = row + end + window:markDirty() + end +end + +-- Scroll handler +window.onScroll = function(delta) + scrollY = math.max(0, scrollY - delta * rowHeight * 2) + window:markDirty() +end + +-- Draw handler +window.onDraw = function(gfx) + local winW = window.width + local winH = window.height + + -- Background + gfx:fillRect(0, 0, winW, winH, 0xFFFFFF) + + -- Toolbar + gfx:fillRect(0, 0, winW, toolbarHeight, 0xE0E0E0) + for _, btn in ipairs(buttons) do + gfx:fillRect(btn.x, btn.y, btn.w, btn.h, 0xD0D0D0) + gfx:drawRect(btn.x, btn.y, btn.w, btn.h, 0x888888) + gfx:drawText(btn.x + 4, btn.y + 4, btn.label, 0x000000) + end + + -- Formula bar + local formulaBarW = winW - formulaBarX - 5 + local fBarBgColor = formulaBarActive and 0xFFFFFF or 0xFAFAFA + local fBarBorderColor = formulaBarActive and 0x0066CC or 0x888888 + gfx:fillRect(formulaBarX, formulaBarY, formulaBarW, formulaBarH, fBarBgColor) + gfx:drawRect(formulaBarX, formulaBarY, formulaBarW, formulaBarH, fBarBorderColor) + + -- Formula bar content + local fBarText = "%=" + if formulaBarActive then + fBarText = fBarText .. formulaBuffer .. "|" + else + -- Show existing formula if cell has one + local cellVal = getCell(selectedX, selectedY) + if type(cellVal) == "string" and cellVal:sub(1, 2) == "%=" then + fBarText = cellVal + else + fBarText = "%=" + end + end + gfx:drawText(formulaBarX + 3, formulaBarY + 4, fBarText, 0x000000) + + -- Column headers background + gfx:fillRect(0, toolbarHeight, winW, headerHeight, 0xD0D0D0) + + -- Row headers background + gfx:fillRect(0, toolbarHeight + headerHeight, rowHeaderWidth, winH, 0xD0D0D0) + + -- Corner + gfx:fillRect(0, toolbarHeight, rowHeaderWidth, headerHeight, 0xC0C0C0) + gfx:drawRect(0, toolbarHeight, rowHeaderWidth, headerHeight, 0x888888) + + -- Calculate visible range + local startRow = math.floor(scrollY / rowHeight) + 1 + local visibleRows = math.ceil((winH - toolbarHeight - headerHeight) / rowHeight) + 1 + local endRow = startRow + visibleRows + + -- Draw column headers + local x = rowHeaderWidth - scrollX + local col = 1 + while x < winW do + local colW = getColWidth(col) + if x + colW > rowHeaderWidth then + local drawX = math.max(rowHeaderWidth, x) + local drawW = math.min(colW, x + colW - drawX) + if x >= rowHeaderWidth then + gfx:drawRect(x, toolbarHeight, colW, headerHeight, 0x888888) + gfx:drawText(x + 4, toolbarHeight + 4, colToLetter(col), 0x000000) + end + end + x = x + colW + col = col + 1 + if col > 100 then break end -- Limit columns drawn + end + + -- Draw rows + for row = startRow, endRow do + local y = toolbarHeight + headerHeight + (row - 1) * rowHeight - scrollY + + if y + rowHeight > toolbarHeight + headerHeight and y < winH then + -- Row header + gfx:drawRect(0, y, rowHeaderWidth, rowHeight, 0x888888) + gfx:drawText(4, y + 4, tostring(row), 0x000000) + + -- Cells + x = rowHeaderWidth - scrollX + col = 1 + while x < winW do + local colW = getColWidth(col) + + if x + colW > rowHeaderWidth then + -- Cell background + local isSelected = (col == selectedX and row == selectedY) + local bgColor = isSelected and 0xCCE5FF or 0xFFFFFF + + local cellX = math.max(rowHeaderWidth, x) + local cellW = colW - (cellX - x) + if cellX + cellW > winW then cellW = winW - cellX end + + if cellW > 0 then + gfx:fillRect(cellX, y, cellW, rowHeight, bgColor) + gfx:drawRect(cellX, y, cellW, rowHeight, 0xCCCCCC) + + -- Check if cell has a formula + local rawVal = getCell(col, row) + local isFormula = type(rawVal) == "string" and rawVal:sub(1, 2) == "%=" + + -- Cell content + if isSelected and editing then + -- Show edit buffer with cursor + local displayText = editBuffer .. "|" + gfx:drawText(cellX + 2, y + 4, displayText, 0x000000) + else + local displayVal = getDisplayValue(col, row) + if displayVal ~= "" then + -- Truncate if too long + local maxChars = math.floor((colW - 4) / 7) + if #displayVal > maxChars then + displayVal = displayVal:sub(1, maxChars - 1) .. "..." + end + gfx:drawText(cellX + 2, y + 4, displayVal, 0x000000) + end + end + + -- Formula cell border (light blue) + if isFormula and not isSelected then + gfx:drawRect(cellX, y, cellW, rowHeight, 0x66AAFF) + end + + -- Selection border + if isSelected then + gfx:drawRect(cellX, y, cellW, rowHeight, 0x0066CC) + gfx:drawRect(cellX + 1, y + 1, cellW - 2, rowHeight - 2, 0x0066CC) + end + end + end + + x = x + colW + col = col + 1 + if col > 100 then break end + end + end + end + + -- Grid border + gfx:drawRect(0, toolbarHeight, winW, winH - toolbarHeight, 0x888888) +end + +print("Spreadsheet loaded") diff --git a/iso_includes/apps/com.luajitos.taskbar/manifest.lua b/iso_includes/apps/com.luajitos.taskbar/manifest.lua @@ -5,7 +5,7 @@ return { category = "all", description = "System taskbar with start menu and running applications", entry = "init.lua", - type = "gui", + type = "taskbar", hidden = true, -- Hide from start menu autostart = true, -- Launch on system startup autostartPriority = 2, -- Launch after background diff --git a/iso_includes/apps/com.luajitos.taskbar/src/init.lua b/iso_includes/apps/com.luajitos.taskbar/src/init.lua @@ -24,17 +24,18 @@ local startMenuJustClosed = false -- Track if menu was just closed by focus los local windowPopup = nil local windowPopupVisible = false --- Start button dimensions -local startButtonWidth = 80 -local startButtonHeight = 40 -local startButtonX = 5 -local startButtonY = 5 +-- Start button dimensions (2px margin on each side) +local startButtonMargin = 2 +local startButtonWidth = 76 -- 80 - 4 (2px on each side) +local startButtonHeight = taskbarHeight - 4 -- 2px top + 2px bottom +local startButtonX = startButtonMargin +local startButtonY = startButtonMargin -- App button dimensions local appButtonWidth = 120 local appButtonHeight = 40 local appButtonY = 5 -local appButtonStartX = startButtonX + startButtonWidth + 10 +local appButtonStartX = startButtonX + startButtonWidth + 5 -- Helper: Check if click is inside a rectangle local function isInside(x, y, rx, ry, rw, rh) @@ -616,10 +617,10 @@ taskbar.onDraw = function(gfx) -- Draw start button gfx:fillRect(startButtonX, startButtonY, startButtonWidth, startButtonHeight, 0x0066CC) - gfx:drawRect(startButtonX, startButtonY, startButtonWidth, startButtonHeight, 0x0088FF) - -- Start button text - gfx:drawUText(startButtonX + 20, startButtonY + 13, "Start", 0xFFFFFF) + -- Start button text (centered vertically) + local textY = startButtonY + math.floor((startButtonHeight - 8) / 2) + gfx:drawUText(startButtonX + 20, textY, "Start", 0xFFFFFF) -- Draw application buttons local apps = getRunningApplications() diff --git a/iso_includes/os/init.lua b/iso_includes/os/init.lua @@ -13,6 +13,7 @@ local OP_POLYGON_FILL = 8 local OP_LINE = 9 local OP_TEXT = 10 local OP_IMAGE = 11 +local OP_BUFFER = 12 -- Global cursor state _G.cursor_state = { @@ -823,6 +824,10 @@ local function drawAllWindows() local promptWindow = inPromptMode and promptMode.window or nil -- Phase 1: Render dirty windows to their buffers + if type(_G.window_stack) ~= "table" then + osprint("[ERROR] window_stack is not a table, it is: " .. type(_G.window_stack) .. "\n") + _G.window_stack = {} + end for i, window in ipairs(_G.window_stack) do -- In prompt mode, only draw the prompt window if inPromptMode and window ~= promptWindow then @@ -1092,6 +1097,38 @@ local function drawAllWindows() -- This ensures images are drawn in the correct order relative to other elements draw_ops[#draw_ops + 1] = {OP_IMAGE, image, content_x + x, content_y + y, w, h, 1} end, + drawBuffer = function(self, luaImage, x, y, srcX, srcY, srcW, srcH) + -- Handle both method and function call syntax + if type(self) == "table" and self.buffer then + -- Called as drawBuffer(luaImage, x, y, ...) + srcH = srcW + srcW = srcY + srcY = srcX + srcX = y + y = x + x = luaImage + luaImage = self + end + if not luaImage then return end + + -- Get buffer data - support both Image (.buffer) and ImageBuffer (:getData()) + local bufferData + local bufW, bufH + if type(luaImage) == "userdata" then + -- ImageBuffer userdata - use getData() method + bufferData = luaImage:getData() + bufW, bufH = luaImage:getSize() + elseif luaImage.buffer then + -- Image object with .buffer property + bufferData = luaImage.buffer + bufW, bufH = luaImage:getSize() + else + return + end + + -- {12, buffer, x, y, bufWidth, bufHeight, srcX, srcY, srcW, srcH} + draw_ops[#draw_ops + 1] = {OP_BUFFER, bufferData, content_x + x, content_y + y, bufW, bufH, srcX or 0, srcY or 0, srcW or 0, srcH or 0} + end, getWidth = function(self) return window.width end, @@ -1205,13 +1242,24 @@ local function drawAllWindows() adjusted_op[5] = op[5] adjusted_op[6] = op[6] elseif op[1] == OP_IMAGE then - -- {4, image, x, y, w, h, scale} + -- {11, image, x, y, w, h, scale} adjusted_op[2] = op[2] adjusted_op[3] = content_x + op[3] adjusted_op[4] = content_y + op[4] adjusted_op[5] = op[5] adjusted_op[6] = op[6] adjusted_op[7] = op[7] + elseif op[1] == OP_BUFFER then + -- {12, buffer, x, y, bufWidth, bufHeight, srcX, srcY, srcW, srcH} + adjusted_op[2] = op[2] -- buffer string + adjusted_op[3] = content_x + op[3] -- x + adjusted_op[4] = content_y + op[4] -- y + adjusted_op[5] = op[5] -- bufWidth + adjusted_op[6] = op[6] -- bufHeight + adjusted_op[7] = op[7] -- srcX + adjusted_op[8] = op[8] -- srcY + adjusted_op[9] = op[9] -- srcW + adjusted_op[10] = op[10] -- srcH end if not skipNormalAdd then @@ -1323,7 +1371,8 @@ _G.resize_start_win_w = _G.resize_start_win_w or 0 _G.resize_start_win_h = _G.resize_start_win_h or 0 _G.force_redraw = true -- Force initial redraw -function MainDraw() +-- Internal MainDraw implementation +local function MainDrawImpl() local redraw_interval = 1 -- Redraw every frame for smooth mouse cursor -- Update Timer system @@ -1956,6 +2005,22 @@ function MainDraw() _G.last_cursor_x = nil _G.last_cursor_y = nil end +end - +-- Main entry point with error handling +function MainDraw() + local success, err = pcall(MainDrawImpl) + if not success then + -- Print error to serial console + if osprint then + osprint("\n[FATAL ERROR in MainDraw]\n") + osprint(tostring(err) .. "\n") + osprint("Stack trace:\n") + osprint(debug.traceback() .. "\n") + end + -- Try to recover by resetting window_stack if corrupted + if type(_G.window_stack) ~= "table" then + _G.window_stack = {} + end + end end diff --git a/iso_includes/os/libs/Application.lua b/iso_includes/os/libs/Application.lua @@ -856,6 +856,28 @@ function Application:newWindow(arg1, arg2, arg3, arg4, arg5, arg6) table.insert(gfxSelf._window._draw_ops, {0, x, y, r, g, b}) end, + -- Draw a raw BGRA buffer (from Image object) directly + -- luaImage: Image object with .buffer and :getSize() + -- x, y: destination position + -- srcX, srcY, srcW, srcH: optional source region (defaults to full image) + drawBuffer = function(gfxSelf, luaImage, x, y, srcX, srcY, srcW, srcH) + if type(gfxSelf) ~= "table" or not gfxSelf._window then + -- Called without self, shift args + srcH = srcW + srcW = srcY + srcY = srcX + srcX = y + y = x + x = luaImage + luaImage = gfxSelf + gfxSelf = window.gfx + end + if not luaImage or not luaImage.buffer then return end + local bufW, bufH = luaImage:getSize() + -- {12, buffer, x, y, bufWidth, bufHeight, srcX, srcY, srcW, srcH} + table.insert(gfxSelf._window._draw_ops, {12, luaImage.buffer, x, y, bufW, bufH, srcX or 0, srcY or 0, srcW or 0, srcH or 0}) + end, + getWidth = function(gfxSelf) if type(gfxSelf) == "table" and gfxSelf._window then return gfxSelf._window.width diff --git a/iso_includes/os/libs/Dialog.lua b/iso_includes/os/libs/Dialog.lua @@ -1953,7 +1953,7 @@ function Dialog.html(html, options) title = title, close = function() dialog:close() end } - }, { __index = _G, __metatable = env }) -- __metatable returns caller's env + }, { __index = _G, __metatable = false }) -- Prevent metatable access -- Draw callback dialog.window.onDraw = function(gfx) @@ -2021,4 +2021,406 @@ function Dialog.html(html, options) return dialog end +-- Custom Dialog +-- Creates a dialog with dynamic content based on arguments +-- Usage: Dialog.customDialog("Label", "STRING", "\n", "Age:", "NUMBER", "\n", "Agree?", "BOOLEAN", "\n", "BUTTON=Cancel", "BUTTON=Ok", callback) +-- @param ... Variable arguments: strings are labels, "STRING"/"NUMBER"/"BOOLEAN" are input types, +-- "\n" is a line break, "BUTTON=Label" creates a button +-- @param callback The last argument must be a callback function that receives all input values followed by button label +-- @param options Optional table with app, title, width (can be passed as second-to-last arg before callback) +function Dialog.customDialog(...) + local args = {...} + local callback = nil + local options = {} + + -- Last arg must be callback + if type(args[#args]) == "function" then + callback = table.remove(args) + else + error("Dialog.customDialog: last argument must be a callback function") + end + + -- Check if second-to-last is options table + if type(args[#args]) == "table" and args[#args].app then + options = table.remove(args) + end + + local app_instance = options.app or app + if not app_instance then + error("Dialog.customDialog requires app instance (provide options.app or use from sandboxed context)") + end + + local title = options.title or "Dialog" + + -- Parse arguments to build UI elements + local elements = {} -- {type, label, value, x, y, w, h} + local inputs = {} -- Track input elements for collecting values + local buttons = {} -- Track button elements + + local cursorX = 10 + local cursorY = 20 + local lineHeight = 30 + local inputHeight = 22 + local checkboxSize = 18 + local buttonHeight = 25 + local buttonWidth = 80 + local padding = 10 + + local maxWidth = 300 + local maxY = cursorY + + for i, arg in ipairs(args) do + if arg == "\n" then + -- Line break + cursorX = 10 + cursorY = cursorY + lineHeight + elseif arg == "STRING" then + -- String input field + local inputWidth = 150 + local input = { + type = "string", + x = cursorX, + y = cursorY, + w = inputWidth, + h = inputHeight, + value = "", + active = false + } + table.insert(elements, input) + table.insert(inputs, input) + cursorX = cursorX + inputWidth + padding + if cursorX > maxWidth then maxWidth = cursorX end + elseif arg == "NUMBER" then + -- Number input field + local inputWidth = 80 + local input = { + type = "number", + x = cursorX, + y = cursorY, + w = inputWidth, + h = inputHeight, + value = "", + active = false + } + table.insert(elements, input) + table.insert(inputs, input) + cursorX = cursorX + inputWidth + padding + if cursorX > maxWidth then maxWidth = cursorX end + elseif arg == "PASSWORD" then + -- Password input field (masked with asterisks) + local inputWidth = 150 + local input = { + type = "password", + x = cursorX, + y = cursorY, + w = inputWidth, + h = inputHeight, + value = "", + active = false + } + table.insert(elements, input) + table.insert(inputs, input) + cursorX = cursorX + inputWidth + padding + if cursorX > maxWidth then maxWidth = cursorX end + elseif arg == "BOOLEAN" then + -- Checkbox + local input = { + type = "boolean", + x = cursorX, + y = cursorY + 2, + w = checkboxSize, + h = checkboxSize, + value = false + } + table.insert(elements, input) + table.insert(inputs, input) + cursorX = cursorX + checkboxSize + padding + if cursorX > maxWidth then maxWidth = cursorX end + elseif type(arg) == "string" and arg:sub(1, 7) == "BUTTON=" then + -- Button + local label = arg:sub(8) + local btn = { + type = "button", + label = label, + x = 0, -- Will be positioned later + y = 0, + w = buttonWidth, + h = buttonHeight + } + table.insert(buttons, btn) + elseif type(arg) == "string" then + -- Text label + local textWidth = #arg * 7 + 5 -- Approximate width + local label = { + type = "label", + text = arg, + x = cursorX, + y = cursorY + 3, + w = textWidth, + h = lineHeight + } + table.insert(elements, label) + cursorX = cursorX + textWidth + 5 + if cursorX > maxWidth then maxWidth = cursorX end + end + + if cursorY + lineHeight > maxY then + maxY = cursorY + lineHeight + end + end + + -- Position buttons at the bottom + cursorY = maxY + 10 + local totalButtonWidth = #buttons * buttonWidth + (#buttons - 1) * padding + local buttonStartX = math.floor((maxWidth - totalButtonWidth) / 2) + if buttonStartX < 10 then buttonStartX = 10 end + + for i, btn in ipairs(buttons) do + btn.x = buttonStartX + (i - 1) * (buttonWidth + padding) + btn.y = cursorY + table.insert(elements, btn) + end + + -- Calculate dialog size + local width = options.width or math.max(maxWidth + 20, 200) + local height = cursorY + buttonHeight + 20 + + -- Create dialog object + local dialog = { + app = app_instance, + window = nil, + elements = elements, + inputs = inputs, + buttons = buttons, + activeInput = nil, + callback = callback + } + + -- Collect input values and call callback + -- If callback returns true, keep dialog open + -- If callback returns a number, clear that input index and focus it + function dialog:submit(buttonLabel) + local values = {} + for _, input in ipairs(self.inputs) do + if input.type == "string" or input.type == "password" then + table.insert(values, input.value) + elseif input.type == "number" then + table.insert(values, tonumber(input.value) or 0) + elseif input.type == "boolean" then + table.insert(values, input.value) + end + end + table.insert(values, buttonLabel) + + local result = nil + if self.callback then + result = self.callback(unpack(values)) + end + + if result == true then + -- Keep dialog open, do nothing + return + elseif type(result) == "number" then + -- Clear the input at that index and focus it + local inputIndex = result + if inputIndex >= 1 and inputIndex <= #self.inputs then + local input = self.inputs[inputIndex] + -- Clear value + if input.type == "boolean" then + input.value = false + else + input.value = "" + end + -- Deactivate current input + if self.activeInput then + self.activeInput.active = false + end + -- Focus the specified input + input.active = true + self.activeInput = input + if self.window then + self.window:markDirty() + end + end + return + end + + -- Default: close the dialog + if self.window then + self.window:close() + end + end + + -- Close method + function dialog:close() + if self.window then + self.window:close() + self.window = nil + end + end + + -- Show method + function dialog:show() + if self.window then return self end + + -- Create window + self.window = self.app:newWindow(title, width, height) + + -- Draw callback + self.window.onDraw = function(gfx) + -- Background + gfx:fillRect(0, 0, width, height, 0xF0F0F0) + + -- Draw all elements + for _, elem in ipairs(self.elements) do + if elem.type == "label" then + gfx:drawText(elem.x, elem.y, elem.text, 0x000000) + + elseif elem.type == "string" or elem.type == "number" or elem.type == "password" then + -- Input field background + local bgColor = elem.active and 0xFFFFFF or 0xFAFAFA + local borderColor = elem.active and 0x0066CC or 0x888888 + gfx:fillRect(elem.x, elem.y, elem.w, elem.h, bgColor) + gfx:drawRect(elem.x, elem.y, elem.w, elem.h, borderColor) + -- Text (show asterisks for password) + local displayText + if elem.type == "password" then + displayText = string.rep("*", #elem.value) + else + displayText = elem.value + end + if elem.active then + displayText = displayText .. "|" + end + gfx:drawText(elem.x + 3, elem.y + 4, displayText, 0x000000) + + elseif elem.type == "boolean" then + -- Checkbox + gfx:fillRect(elem.x, elem.y, elem.w, elem.h, 0xFFFFFF) + gfx:drawRect(elem.x, elem.y, elem.w, elem.h, 0x666666) + if elem.value then + -- Draw checkmark + local cx, cy = elem.x + elem.w/2, elem.y + elem.h/2 + gfx:fillRect(elem.x + 4, elem.y + 4, elem.w - 8, elem.h - 8, 0x0066CC) + end + + elseif elem.type == "button" then + -- Button + gfx:fillRect(elem.x, elem.y, elem.w, elem.h, 0xDDDDDD) + gfx:drawRect(elem.x, elem.y, elem.w, elem.h, 0x888888) + local textX = elem.x + math.floor((elem.w - #elem.label * 7) / 2) + local textY = elem.y + 6 + gfx:drawText(textX, textY, elem.label, 0x000000) + end + end + end + + -- Key handler + self.window.onKey = function(key) + if self.activeInput then + local input = self.activeInput + if key == "\b" then + -- Backspace + if #input.value > 0 then + input.value = input.value:sub(1, -2) + self.window:markDirty() + end + elseif key == "\n" then + -- Enter - deactivate input + input.active = false + self.activeInput = nil + self.window:markDirty() + elseif key == "\t" then + -- Tab - move to next input + input.active = false + local found = false + for i, inp in ipairs(self.inputs) do + if found and (inp.type == "string" or inp.type == "number" or inp.type == "password") then + inp.active = true + self.activeInput = inp + self.window:markDirty() + break + end + if inp == input then + found = true + end + end + if self.activeInput == input then + -- Wrap around + for i, inp in ipairs(self.inputs) do + if inp.type == "string" or inp.type == "number" or inp.type == "password" then + inp.active = true + self.activeInput = inp + self.window:markDirty() + break + end + end + end + elseif #key == 1 then + -- Regular character + if input.type == "number" then + -- Only allow digits, minus, dot + if key:match("[%d%.%-]") then + input.value = input.value .. key + self.window:markDirty() + end + else + input.value = input.value .. key + self.window:markDirty() + end + end + end + end + + -- Click handler + self.window.onClick = function(mx, my) + -- Deactivate current input + if self.activeInput then + self.activeInput.active = false + self.activeInput = nil + end + + -- Check all elements + for _, elem in ipairs(self.elements) do + if mx >= elem.x and mx < elem.x + elem.w and + my >= elem.y and my < elem.y + elem.h then + + if elem.type == "string" or elem.type == "number" or elem.type == "password" then + -- Activate input + elem.active = true + self.activeInput = elem + self.window:markDirty() + return + + elseif elem.type == "boolean" then + -- Toggle checkbox + elem.value = not elem.value + self.window:markDirty() + return + + elseif elem.type == "button" then + -- Button clicked - submit with button label + self:submit(elem.label) + return + end + end + end + + self.window:markDirty() + end + + return self + end + + -- openDialog method (convenience) + function dialog:openDialog(cb) + if cb then + self.callback = cb + end + return self:show() + end + + return dialog +end + return Dialog diff --git a/iso_includes/os/libs/HTMLWindow.lua b/iso_includes/os/libs/HTMLWindow.lua @@ -96,7 +96,7 @@ local function loadModules() end -- Compile and execute DOM module - local domEnv = setmetatable({}, { __index = _G }) + local domEnv = setmetatable({}, { __index = _G, __metatable = false }) local domFunc, domErr = loadstring(domCode, "dom") if not domFunc then return false, "Failed to compile DOM: " .. tostring(domErr) @@ -115,7 +115,7 @@ local function loadModules() end return nil end - }, { __index = _G }) + }, { __index = _G, __metatable = false }) local parserFunc, parserErr = loadstring(parserCode, "parser") if not parserFunc then @@ -129,7 +129,7 @@ local function loadModules() parserModule = parserResult -- Compile and execute renderer - local renderEnv = setmetatable({}, { __index = _G }) + local renderEnv = setmetatable({}, { __index = _G, __metatable = false }) local renderFunc, renderErr = loadstring(renderCode, "render") if not renderFunc then return false, "Failed to compile renderer: " .. tostring(renderErr) diff --git a/iso_includes/os/libs/Image.lua b/iso_includes/os/libs/Image.lua @@ -291,35 +291,210 @@ function ImageObj:getPixel(x, y) } end --- Set pixel from RGBA table +-- Set pixel color using C function for efficiency -- @param x X coordinate (0-based) -- @param y Y coordinate (0-based) --- @param rgba Table with r, g, b, a fields -function ImageObj:setPixel(x, y, rgba) +-- @param r_or_rgba Red value (0-255) or table {r, g, b, a} +-- @param g Green value (0-255), optional if first arg is table +-- @param b Blue value (0-255), optional if first arg is table +-- @param a Alpha value (0-255), optional, defaults to 255 +function ImageObj:setPixel(x, y, r_or_rgba, g, b, a) checkPermission() - if not x or not y or not rgba then - error("setPixel requires x, y, and rgba table") + if x == nil or y == nil or r_or_rgba == nil then + error("setPixel requires x, y, and color") end - if x < 0 or x >= self.width or y < 0 or y >= self.height then - return -- Silently ignore out of bounds + local r, g_val, b_val, a_val + if type(r_or_rgba) == "table" then + r = r_or_rgba.r or r_or_rgba[1] or 0 + g_val = r_or_rgba.g or r_or_rgba[2] or 0 + b_val = r_or_rgba.b or r_or_rgba[3] or 0 + a_val = r_or_rgba.a or r_or_rgba[4] or 255 + else + r = r_or_rgba or 0 + g_val = g or 0 + b_val = b or 0 + a_val = a or 255 end - local r = rgba.r or rgba[1] or 0 - local g = rgba.g or rgba[2] or 0 - local b = rgba.b or rgba[3] or 0 - local a = rgba.a or rgba[4] or 255 + -- Use C function for efficiency + if BufferSetPixel then + self.buffer = BufferSetPixel(self.buffer, self.width, self.height, x, y, r, g_val, b_val, a_val) + return + end + + -- Fallback to Lua implementation + if x < 0 or x >= self.width or y < 0 or y >= self.height then + return + end local pixelIndex = y * self.width + x local byteOffset = pixelIndex * 4 + 1 + local newBytes = string.char(b_val, g_val, r, a_val) + self.buffer = self.buffer:sub(1, byteOffset - 1) .. newBytes .. self.buffer:sub(byteOffset + 4) +end - -- Write BGRA bytes - local newBytes = string.char(b, g, r, a) +-- Fill a circle with a color (uses C for efficiency) +-- @param cx Center X coordinate +-- @param cy Center Y coordinate +-- @param radius Circle radius +-- @param color Color as number (0xRRGGBB or 0xAARRGGBB) or table {r, g, b, a} +function ImageObj:fillCircle(cx, cy, radius, color) + checkPermission() - self.buffer = self.buffer:sub(1, byteOffset - 1) .. - newBytes .. - self.buffer:sub(byteOffset + 4) + local r, g, b, a = 0, 0, 0, 255 + if type(color) == "table" then + r = color.r or color[1] or 0 + g = color.g or color[2] or 0 + b = color.b or color[3] or 0 + a = color.a or color[4] or 255 + elseif type(color) == "number" then + if color <= 0xFFFFFF then + r = bit.rshift(bit.band(color, 0xFF0000), 16) + g = bit.rshift(bit.band(color, 0x00FF00), 8) + b = bit.band(color, 0x0000FF) + else + a = bit.rshift(bit.band(color, 0xFF000000), 24) + r = bit.rshift(bit.band(color, 0x00FF0000), 16) + g = bit.rshift(bit.band(color, 0x0000FF00), 8) + b = bit.band(color, 0x000000FF) + end + end + + -- Use in-place version (no buffer copy, modifies directly) + if BufferFillCircleInplace then + BufferFillCircleInplace(self.buffer, self.width, self.height, cx, cy, radius, r, g, b, a) + elseif BufferFillCircle then + self.buffer = BufferFillCircle(self.buffer, self.width, self.height, cx, cy, radius, r, g, b, a) + else + -- Fallback to Lua + local r2 = radius * radius + for dy = -radius, radius do + for dx = -radius, radius do + if dx*dx + dy*dy <= r2 then + self:setPixel(cx + dx, cy + dy, r, g, b, a) + end + end + end + end +end + +-- Draw a line with variable thickness (uses C for efficiency) +-- @param x1 Start X +-- @param y1 Start Y +-- @param x2 End X +-- @param y2 End Y +-- @param thickness Line thickness in pixels +-- @param color Color as number or table +function ImageObj:drawLine(x1, y1, x2, y2, thickness, color) + checkPermission() + + local r, g, b, a = 0, 0, 0, 255 + if type(color) == "table" then + r = color.r or color[1] or 0 + g = color.g or color[2] or 0 + b = color.b or color[3] or 0 + a = color.a or color[4] or 255 + elseif type(color) == "number" then + if color <= 0xFFFFFF then + r = bit.rshift(bit.band(color, 0xFF0000), 16) + g = bit.rshift(bit.band(color, 0x00FF00), 8) + b = bit.band(color, 0x0000FF) + else + a = bit.rshift(bit.band(color, 0xFF000000), 24) + r = bit.rshift(bit.band(color, 0x00FF0000), 16) + g = bit.rshift(bit.band(color, 0x0000FF00), 8) + b = bit.band(color, 0x000000FF) + end + end + + -- Use in-place version (no buffer copy, modifies directly) + if BufferDrawLineInplace then + BufferDrawLineInplace(self.buffer, self.width, self.height, x1, y1, x2, y2, thickness, r, g, b, a) + elseif BufferDrawLine then + self.buffer = BufferDrawLine(self.buffer, self.width, self.height, x1, y1, x2, y2, thickness, r, g, b, a) + else + -- Fallback: just draw circles along the line + local dx = math.abs(x2 - x1) + local dy = math.abs(y2 - y1) + local sx = x1 < x2 and 1 or -1 + local sy = y1 < y2 and 1 or -1 + local err = dx - dy + local radius = math.floor(thickness / 2) + + while true do + self:fillCircle(x1, y1, radius, {r, g, b, a}) + if x1 == x2 and y1 == y2 then break end + local e2 = 2 * err + if e2 > -dy then err = err - dy; x1 = x1 + sx end + if e2 < dx then err = err + dx; y1 = y1 + sy end + end + end +end + +-- Fill a rectangle with a color (uses C for efficiency) +-- @param x Top-left X +-- @param y Top-left Y +-- @param w Width +-- @param h Height +-- @param color Color as number or table +function ImageObj:fillRect(x, y, w, h, color) + checkPermission() + + local r, g, b, a = 0, 0, 0, 255 + if type(color) == "table" then + r = color.r or color[1] or 0 + g = color.g or color[2] or 0 + b = color.b or color[3] or 0 + a = color.a or color[4] or 255 + elseif type(color) == "number" then + if color <= 0xFFFFFF then + r = bit.rshift(bit.band(color, 0xFF0000), 16) + g = bit.rshift(bit.band(color, 0x00FF00), 8) + b = bit.band(color, 0x0000FF) + else + a = bit.rshift(bit.band(color, 0xFF000000), 24) + r = bit.rshift(bit.band(color, 0x00FF0000), 16) + g = bit.rshift(bit.band(color, 0x0000FF00), 8) + b = bit.band(color, 0x000000FF) + end + end + + if BufferFillRect then + self.buffer = BufferFillRect(self.buffer, self.width, self.height, x, y, w, h, r, g, b, a) + else + -- Fallback to Lua + for py = y, y + h - 1 do + for px = x, x + w - 1 do + self:setPixel(px, py, r, g, b, a) + end + end + end +end + +-- Blit a row of BGRA data at the specified y position +-- @param y Y coordinate +-- @param rowData Binary string of BGRA pixels +-- @param startX Starting X position (optional, defaults to 0) +function ImageObj:blitRow(y, rowData, startX) + checkPermission() + startX = startX or 0 + + if BufferBlitRow then + self.buffer = BufferBlitRow(self.buffer, self.width, self.height, y, rowData, startX) + else + -- Fallback: copy pixel by pixel + local pixels = #rowData / 4 + for i = 0, pixels - 1 do + local offset = i * 4 + 1 + local b = rowData:byte(offset) + local g = rowData:byte(offset + 1) + local r = rowData:byte(offset + 2) + local a = rowData:byte(offset + 3) + self:setPixel(startX + i, y, r, g, b, a) + end + end end -- Fill entire image with a color @@ -362,10 +537,14 @@ function ImageObj:fill(color) end end - -- Create single pixel in BGRA format - local pixel = string.char(b, g, r, a) + -- Use C function if available + if BufferFill then + self.buffer = BufferFill(self.buffer, self.width, self.height, r, g, b, a) + return + end - -- Fill buffer efficiently using string.rep + -- Fallback: Create single pixel in BGRA format and repeat + local pixel = string.char(b, g, r, a) local numPixels = self.width * self.height self.buffer = string.rep(pixel, numPixels) end diff --git a/iso_includes/os/libs/Run.lua b/iso_includes/os/libs/Run.lua @@ -794,7 +794,7 @@ function run.execute(app_name, fsRoot) local app_env = { sys = _G.sys -- Explicitly provide sys to Application.lua } - setmetatable(app_env, {__index = _G}) + setmetatable(app_env, {__index = _G, __metatable = false}) local app_func, err = load(app_module_code, app_module_path, "t", app_env) if app_func then @@ -1218,7 +1218,7 @@ function run.execute(app_name, fsRoot) if safefs_code then -- Load SafeFS in a temporary environment local safefs_env = {} - setmetatable(safefs_env, {__index = _G}) + setmetatable(safefs_env, {__index = _G, __metatable = false}) local safefs_func, err = load(safefs_code, safefs_path, "t", safefs_env) if safefs_func then @@ -1492,7 +1492,7 @@ function run.execute(app_name, fsRoot) if scheduler_code then -- Load scheduler in a temporary environment local scheduler_env = {} - setmetatable(scheduler_env, {__index = _G}) + setmetatable(scheduler_env, {__index = _G, __metatable = false}) local scheduler_func, err = load(scheduler_code, scheduler_path, "t", scheduler_env) if scheduler_func then @@ -1778,6 +1778,26 @@ function run.execute(app_name, fsRoot) end end + -- Check for setfenv permission + local has_setfenv = false + for _, perm in ipairs(permissions) do + if perm == "setfenv" then + has_setfenv = true + break + end + end + + if has_setfenv then + -- Grant access to setfenv for environment manipulation + -- Use direct reference (available in Run.lua context) rather than _G + sandbox_env.setfenv = setfenv + sandbox_env.getfenv = getfenv + + if osprint then + osprint("Setfenv permission granted - setfenv/getfenv functions available\n") + end + end + -- Check for system permission and setup system information API local has_system = false for _, perm in ipairs(permissions) do @@ -1890,6 +1910,11 @@ function run.execute(app_name, fsRoot) is_permitted = true end + -- Check if it's setfenv/getfenv (setfenv permission) + if (key == "setfenv" or key == "getfenv") and has_setfenv then + is_permitted = true + end + -- Remove if not permitted if not is_permitted then sandbox_env[key] = nil @@ -2111,6 +2136,9 @@ function run.execute(app_name, fsRoot) allowed_keys.ImageDrawScaled = true allowed_keys.ImageDestroy = true allowed_keys.ImageGetInfo = true + allowed_keys.setfenv = true + allowed_keys.getfenv = true + allowed_keys.loadstring = true setmetatable(sandbox_env, { __index = function(t, k) diff --git a/iso_includes/os/libs/Sys.lua b/iso_includes/os/libs/Sys.lua @@ -1151,7 +1151,7 @@ sys.browser = { return nil, "HTMLWindow module not found" end - local env = setmetatable({}, { __index = _G }) + local env = setmetatable({}, { __index = _G, __metatable = false }) local func, err = loadstring(htmlWindowCode, "HTMLWindow") if not func then return nil, "Failed to compile HTMLWindow: " .. tostring(err) diff --git a/iso_includes/os/postinit.lua b/iso_includes/os/postinit.lua @@ -422,18 +422,30 @@ local function parse_manifest_simple(manifest_code) manifest.autostart = true end + -- Check for autorun = true (runs at end of startup) + if manifest_code:match("autorun%s*=%s*true") then + manifest.autorun = true + end + -- Check for autostartPriority = <number> local priority = manifest_code:match("autostartPriority%s*=%s*(%d+)") if priority then manifest.autostartPriority = tonumber(priority) end + -- Check for type (gui, cli, background, taskbar, service) + local app_type = manifest_code:match('type%s*=%s*"([^"]+)"') + if app_type then + manifest.type = app_type + end + return manifest end --- Scan all apps for autostart field and launch them -osprint("Scanning apps for autostart...\n") +-- Scan all apps for autostart and autorun fields +osprint("Scanning apps for autostart/autorun...\n") local autostart_apps = {} +local autorun_apps = {} if CRamdiskList and CRamdiskOpen and CRamdiskRead and CRamdiskClose then local apps = CRamdiskList("/apps") @@ -450,12 +462,25 @@ if CRamdiskList and CRamdiskOpen and CRamdiskRead and CRamdiskClose then if manifest_code then local manifest = parse_manifest_simple(manifest_code) + + -- Check for autostart (background, taskbar, early gui apps) if manifest.autostart then table.insert(autostart_apps, { id = app_id, - priority = manifest.autostartPriority or 100 + priority = manifest.autostartPriority or 100, + type = manifest.type or "gui" }) - osprint(" Found autostart app: " .. app_id .. " (priority: " .. (manifest.autostartPriority or 100) .. ")\n") + osprint(" Found autostart app: " .. app_id .. " (priority: " .. (manifest.autostartPriority or 100) .. ", type: " .. (manifest.type or "gui") .. ")\n") + end + + -- Check for autorun (services that run at end of startup) + if manifest.autorun then + table.insert(autorun_apps, { + id = app_id, + priority = manifest.autostartPriority or 100, + type = manifest.type or "service" + }) + osprint(" Found autorun app: " .. app_id .. " (type: " .. (manifest.type or "service") .. ")\n") end end end @@ -469,7 +494,11 @@ table.sort(autostart_apps, function(a, b) return a.priority < b.priority end) --- Launch autostart apps in priority order +table.sort(autorun_apps, function(a, b) + return a.priority < b.priority +end) + +-- Launch autostart apps in priority order (background, taskbar first) osprint("Launching " .. #autostart_apps .. " autostart app(s)...\n") for _, app_info in ipairs(autostart_apps) do osprint("Launching autostart app: " .. app_info.id .. "...\n") @@ -481,4 +510,16 @@ for _, app_info in ipairs(autostart_apps) do end end +-- Launch autorun apps at end of startup (services, libraries) +osprint("Launching " .. #autorun_apps .. " autorun app(s)...\n") +for _, app_info in ipairs(autorun_apps) do + osprint("Launching autorun app: " .. app_info.id .. "...\n") + local success, app = _G.run.execute(app_info.id, _G.fsRoot) + if success and app then + osprint(" " .. app_info.id .. " launched with PID: " .. tostring(app.pid) .. "\n") + else + osprint(" ERROR: Failed to launch " .. app_info.id .. "\n") + end +end + osprint("Post-init complete\n") diff --git a/kernel.c b/kernel.c @@ -785,6 +785,7 @@ void usermode_function(void) { /* Initialize crypto library */ terminal_writestring("Initializing crypto library...\n"); luaopen_crypto(L); + lua_setglobal(L, "crypto"); terminal_writestring("Crypto library loaded!\n"); /* Initialize ATA driver and Lua bindings */ @@ -1068,6 +1069,43 @@ void usermode_function(void) { lua_pushcfunction(L, lua_image_get_buffer_bgra); lua_setglobal(L, "ImageGetBufferBGRA"); + /* Buffer drawing functions for Image library */ + extern int lua_buffer_create(lua_State* L); + lua_pushcfunction(L, lua_buffer_create); + lua_setglobal(L, "BufferCreate"); + + extern int lua_buffer_set_pixel(lua_State* L); + lua_pushcfunction(L, lua_buffer_set_pixel); + lua_setglobal(L, "BufferSetPixel"); + + extern int lua_buffer_fill_circle(lua_State* L); + lua_pushcfunction(L, lua_buffer_fill_circle); + lua_setglobal(L, "BufferFillCircle"); + + extern int lua_buffer_draw_line(lua_State* L); + lua_pushcfunction(L, lua_buffer_draw_line); + lua_setglobal(L, "BufferDrawLine"); + + extern int lua_buffer_fill_rect(lua_State* L); + lua_pushcfunction(L, lua_buffer_fill_rect); + lua_setglobal(L, "BufferFillRect"); + + extern int lua_buffer_fill(lua_State* L); + lua_pushcfunction(L, lua_buffer_fill); + lua_setglobal(L, "BufferFill"); + + extern int lua_buffer_blit_row(lua_State* L); + lua_pushcfunction(L, lua_buffer_blit_row); + lua_setglobal(L, "BufferBlitRow"); + + /* ImageBuffer - mutable userdata buffer for efficient drawing */ + extern void lua_imagebuffer_register(lua_State* L); + lua_imagebuffer_register(L); + + extern int lua_imagebuffer_new(lua_State* L); + lua_pushcfunction(L, lua_imagebuffer_new); + lua_setglobal(L, "ImageBufferNew"); + /* Test simple Lua execution first */ terminal_writestring("Testing simple Lua expression...\n"); const char* simple_test = "osprint('Simple test works!\\n')"; diff --git a/vesa.c b/vesa.c @@ -689,6 +689,7 @@ int lua_vesa_process_buffered_draw_ops(lua_State* L) { #define OP_LINE 9 #define OP_TEXT 10 #define OP_IMAGE 11 + #define OP_BUFFER 12 if (!vesa_state.active) return 0; @@ -885,6 +886,73 @@ int lua_vesa_process_buffered_draw_ops(lua_State* L) { } break; } + + case OP_BUFFER: { + /* {BUFFER, buffer_string, x, y, width, height, src_x, src_y, src_w, src_h} */ + /* Draws a raw BGRA buffer (from Lua Image) directly to the render target */ + /* If src_x/y/w/h are provided, draws only that region of the buffer */ + lua_rawgeti(L, -1, 2); + size_t buf_len; + const char* buf = lua_tolstring(L, -1, &buf_len); + lua_pop(L, 1); + + lua_rawgeti(L, -1, 3); int dst_x = lua_tointeger(L, -1); lua_pop(L, 1); + lua_rawgeti(L, -1, 4); int dst_y = lua_tointeger(L, -1); lua_pop(L, 1); + lua_rawgeti(L, -1, 5); int buf_width = lua_tointeger(L, -1); lua_pop(L, 1); + lua_rawgeti(L, -1, 6); int buf_height = lua_tointeger(L, -1); lua_pop(L, 1); + lua_rawgeti(L, -1, 7); int src_x = lua_tointeger(L, -1); lua_pop(L, 1); + lua_rawgeti(L, -1, 8); int src_y = lua_tointeger(L, -1); lua_pop(L, 1); + lua_rawgeti(L, -1, 9); int src_w = lua_tointeger(L, -1); lua_pop(L, 1); + lua_rawgeti(L, -1, 10); int src_h = lua_tointeger(L, -1); lua_pop(L, 1); + + if (!buf || buf_width <= 0 || buf_height <= 0) break; + + /* Default to full buffer if src region not specified */ + if (src_w <= 0) src_w = buf_width; + if (src_h <= 0) src_h = buf_height; + + /* Calculate expected buffer size (4 bytes per pixel - BGRA) */ + size_t expected_size = (size_t)buf_width * (size_t)buf_height * 4; + if (buf_len < expected_size) break; + + /* Must be rendering to a window buffer (not screen) */ + if (!current_render_target) break; + + int target_width = current_render_target->width; + int target_height = current_render_target->height; + uint32_t* target_pixels = current_render_target->pixels; + + /* Copy pixels from buffer to render target */ + const uint8_t* src = (const uint8_t*)buf; + + for (int row = 0; row < src_h; row++) { + int screen_y = dst_y + row; + int buf_row = src_y + row; + + if (screen_y < 0 || screen_y >= target_height) continue; + if (buf_row < 0 || buf_row >= buf_height) continue; + + for (int col = 0; col < src_w; col++) { + int screen_x = dst_x + col; + int buf_col = src_x + col; + + if (screen_x < 0 || screen_x >= target_width) continue; + if (buf_col < 0 || buf_col >= buf_width) continue; + + /* Get pixel from buffer (BGRA format) */ + size_t buf_offset = ((size_t)buf_row * buf_width + buf_col) * 4; + uint8_t b = src[buf_offset]; + uint8_t g = src[buf_offset + 1]; + uint8_t r = src[buf_offset + 2]; + /* uint8_t a = src[buf_offset + 3]; - alpha ignored for now */ + + /* Write to render target (ARGB format) */ + uint32_t color = (0xFF << 24) | (r << 16) | (g << 8) | b; + target_pixels[screen_y * target_width + screen_x] = color; + } + } + break; + } } lua_pop(L, 1); /* Pop operation table */