mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-11-23 10:58:06 +11:00
Add early version of app-metamachine, update kn-refer for r3
This commit is contained in:
parent
d5405685dd
commit
7fa7441794
538
repository/apps/app-metamachine.lua
Normal file
538
repository/apps/app-metamachine.lua
Normal file
@ -0,0 +1,538 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- app-metamachine.lua : Virtual Machine
|
||||
-- Authors: 20kdc
|
||||
|
||||
local loaderPkg, loaderPid, vmName = ...
|
||||
|
||||
local icecap = neo.requireAccess("x.neo.pub.base", "fs")
|
||||
|
||||
local libVGPU = require("metamachine-vgpu")
|
||||
|
||||
local vmBaseCoroutineWrap
|
||||
local vmComponent, vmComputer, vmOs
|
||||
local vmEnvironment
|
||||
local vmSelfdestruct = false
|
||||
local vmSuperVM = true
|
||||
local signalStack = {}
|
||||
local postVMRList = {}
|
||||
|
||||
-- File structure:
|
||||
-- vm-* : Virtual machine configuration
|
||||
-- vm-
|
||||
|
||||
local vmConfiguration = {
|
||||
-- true : Physical
|
||||
-- {type, ...} : Virtual
|
||||
-- NOTE : The following rules are set.
|
||||
-- k-computer always exists
|
||||
-- k-gpu always exists
|
||||
-- k-log always exists
|
||||
-- k-tmpfs always exists in non-Super VMs
|
||||
["world"] = {"filesystem", "/", false},
|
||||
["eeprom"] = {"eeprom", "/confboot.lua", "/confdata.bin", "Configurator", true},
|
||||
["screen"] = {"screen", "configurator", 50, 15, 8}
|
||||
}
|
||||
|
||||
if vmName then
|
||||
neo.ensurePathComponent("vm-" .. vmName)
|
||||
vmSuperVM = false
|
||||
local f = icecap.open("/vm-" .. vmName, false)
|
||||
vmConfiguration = require("serial").deserialize(f.read("*a"))
|
||||
f.close()
|
||||
if not vmConfiguration then error("The VM configuration was unloadable.") end
|
||||
vmConfiguration["k-tmpfs"] = {"filesystem", "/vt-" .. vmName .. "/", false}
|
||||
end
|
||||
|
||||
local function clone(t)
|
||||
if type(t) == "table" then
|
||||
local b = {}
|
||||
for k, v in pairs(t) do
|
||||
b[k] = v
|
||||
end
|
||||
return b
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
-- by window ID = {address, internal}
|
||||
local screensInt = {
|
||||
}
|
||||
-- by component address = callback
|
||||
local screensAll = {
|
||||
}
|
||||
|
||||
local tmpAddress = "k-tmpfs"
|
||||
local passthroughs = {}
|
||||
local components = {
|
||||
["k-computer"] = {
|
||||
type = "computer",
|
||||
beep = function ()
|
||||
end,
|
||||
start = function ()
|
||||
return false
|
||||
end,
|
||||
stop = function ()
|
||||
vmSelfdestruct = true
|
||||
coroutine.yield(0.05)
|
||||
end,
|
||||
isRunning = function ()
|
||||
return true
|
||||
end,
|
||||
getProgramLocations = function ()
|
||||
-- Entries of {"file", "lootdisk"}
|
||||
return {}
|
||||
end
|
||||
},
|
||||
["k-gpu"] = libVGPU.newGPU(screensAll),
|
||||
["k-log"] = {
|
||||
type = "ocemu",
|
||||
log = neo.emergency
|
||||
}
|
||||
}
|
||||
-- Clones of components made on-demand.
|
||||
local proxies = {}
|
||||
setmetatable(proxies, {__mode = "v"})
|
||||
|
||||
vmComponent = {
|
||||
list = function (filter, exact)
|
||||
-- This is an iterator :(
|
||||
local t = {}
|
||||
for k, v in pairs(components) do
|
||||
local ok = false
|
||||
if filter then
|
||||
if v.type == filter or ((not exact) and v.type:match(filter, 1, true)) then
|
||||
ok = true
|
||||
end
|
||||
else
|
||||
ok = true
|
||||
end
|
||||
if ok then
|
||||
table.insert(t, {k, v.type})
|
||||
end
|
||||
end
|
||||
return function ()
|
||||
local tr1 = table.remove(t, 1)
|
||||
if not tr1 then return end
|
||||
return table.unpack(tr1)
|
||||
end, 9, nil
|
||||
end,
|
||||
invoke = function (com, me, ...)
|
||||
if not components[com] then error("no component " .. com) end
|
||||
if not components[com][me] then error("no method " .. com .. "." .. me) end
|
||||
return components[com][me](...)
|
||||
end,
|
||||
proxy = function (com)
|
||||
if not components[com] then
|
||||
return nil, "no such component"
|
||||
end
|
||||
local p = proxies[com]
|
||||
if p then return p end
|
||||
p = clone(components[com])
|
||||
p.address = com
|
||||
p.fields = {}
|
||||
p.slot = 0
|
||||
proxies[com] = p
|
||||
return p
|
||||
end,
|
||||
type = function (com)
|
||||
if not components[com] then
|
||||
return nil, "no such component"
|
||||
end
|
||||
return components[com].type
|
||||
end,
|
||||
methods = function (com)
|
||||
local mt = {}
|
||||
for k, v in pairs(components[address]) do
|
||||
if type(v) == "function" then
|
||||
mt[k] = true
|
||||
end
|
||||
end
|
||||
return mt
|
||||
end,
|
||||
fields = function (com)
|
||||
-- This isn't actually supported,
|
||||
-- because fields are bad-sec nonsense.
|
||||
-- Luckily, everybody knows this, so nobody uses them.
|
||||
return {}
|
||||
end,
|
||||
doc = function (address, method)
|
||||
if not components[address] then
|
||||
error("No such component " .. address)
|
||||
end
|
||||
if not components[address][method] then
|
||||
error("No such method " .. method)
|
||||
end
|
||||
return tostring(components[address][method])
|
||||
end
|
||||
}
|
||||
|
||||
-- Prepare configured components
|
||||
local insertionCallbacks = {
|
||||
["screen"] = function (address, title, w, h, d)
|
||||
local activeLines = {}
|
||||
local scrW = neo.requireAccess("x.neo.pub.window", "primary window")(w, h, title)
|
||||
local gpuC, scrI, scrC
|
||||
gpuC, scrI, scrC = libVGPU.newBuffer(scrW, {address .. "-kb"}, w, h, function (nw, nh)
|
||||
table.insert(signalStack, {"screen_resized", address, nw, nh})
|
||||
end, function (l)
|
||||
if activeLines[l] then
|
||||
return
|
||||
end
|
||||
activeLines[l] = true
|
||||
table.insert(postVMRList, function ()
|
||||
scrI.line(l)
|
||||
activeLines = {}
|
||||
end)
|
||||
end)
|
||||
components[address] = scrC
|
||||
components[address .. "-kb"] = {type = "keyboard"}
|
||||
screensInt[scrW.id] = {address, scrI}
|
||||
screensAll[address] = gpuC
|
||||
end,
|
||||
["eeprom"] = function (address, boot, data, name, ro)
|
||||
local codeSize = 4096
|
||||
local dataSize = 256
|
||||
local function getCore(fd)
|
||||
local f = icecap.open(fd, false)
|
||||
if not f then return "" end
|
||||
local contents = f.read("*a")
|
||||
f.close()
|
||||
return contents
|
||||
end
|
||||
local function setCore(fd, size, contents)
|
||||
checkArg(1, contents, "string")
|
||||
if #contents > size then return nil, "too large" end
|
||||
if ro then
|
||||
return nil, "storage is readonly"
|
||||
end
|
||||
local f = icecap.open(fd, true)
|
||||
if not f then return nil, "storage is readonly" end
|
||||
f.write(contents)
|
||||
f.close()
|
||||
return true
|
||||
end
|
||||
components[address] = {
|
||||
type = "eeprom",
|
||||
get = function ()
|
||||
return getCore(boot)
|
||||
end,
|
||||
set = function (contents)
|
||||
return setCore(boot, codeSize, contents)
|
||||
end,
|
||||
makeReadonly = function ()
|
||||
ro = true
|
||||
return true
|
||||
end,
|
||||
getChecksum = function ()
|
||||
return "00000000"
|
||||
end,
|
||||
getSize = function ()
|
||||
return codeSize
|
||||
end,
|
||||
getDataSize = function ()
|
||||
return dataSize
|
||||
end,
|
||||
getData = function ()
|
||||
return getCore(data)
|
||||
end,
|
||||
setData = function ()
|
||||
return setCore(data, dataSize, contents)
|
||||
end
|
||||
}
|
||||
end,
|
||||
["filesystem"] = function (address, path, ro)
|
||||
components[address] = require("metamachine-vfs")(icecap, address, path, ro)
|
||||
end
|
||||
}
|
||||
|
||||
for k, v in pairs(vmConfiguration) do
|
||||
if type(v) == "string" then
|
||||
local root = neo.requireAccess("k.root", "component passthrough")
|
||||
local ty = root.component.type(k)
|
||||
if ty then
|
||||
passthroughs[k] = true
|
||||
components[k] = root.component.proxy(k)
|
||||
if ty == "screen" then
|
||||
-- Need to ensure the screen in question is for the taking
|
||||
local div = neo.requireAccess("x.neo.sys.session", "ability to divorce screens")
|
||||
div.disclaimMonitor(k)
|
||||
local div2 = neo.requireAccess("x.neo.sys.screens", "ability to claim screens")
|
||||
screensAll[k] = div2.claim(k)
|
||||
assert(screensAll[k], "Hardware screen " .. k .. " unavailable.")
|
||||
end
|
||||
end
|
||||
else
|
||||
assert(insertionCallbacks[v[1]], "Cannot insert virtual " .. v[1])
|
||||
insertionCallbacks[v[1]](k, table.unpack(v, 2))
|
||||
end
|
||||
end
|
||||
|
||||
vmOs = clone(os)
|
||||
|
||||
vmComputer = {}
|
||||
vmComputer.shutdown = function (...)
|
||||
vmSelfdestruct = true
|
||||
coroutine.yield(0.05)
|
||||
end
|
||||
vmComputer.pushSignal = function (...)
|
||||
table.insert(signalStack, {...})
|
||||
end
|
||||
vmComputer.pullSignal = function (time)
|
||||
if not signalStack[1] then
|
||||
if type(time) == "number" then
|
||||
time = time + os.uptime()
|
||||
coroutine.yield(time)
|
||||
if not signalStack[1] then
|
||||
return
|
||||
end
|
||||
else
|
||||
while not signalStack[1] do
|
||||
coroutine.yield(math.huge)
|
||||
end
|
||||
end
|
||||
end
|
||||
return table.unpack(table.remove(signalStack, 1))
|
||||
end
|
||||
|
||||
vmComputer.totalMemory = os.totalMemory
|
||||
vmOs.totalMemory = nil
|
||||
vmComputer.freeMemory = os.freeMemory
|
||||
vmOs.freeMemory = nil
|
||||
vmComputer.energy = os.energy
|
||||
vmOs.energy = nil
|
||||
vmComputer.maxEnergy = os.maxEnergy
|
||||
vmOs.maxEnergy = nil
|
||||
vmComputer.uptime = os.uptime
|
||||
vmOs.uptime = nil
|
||||
vmComputer.address = os.address
|
||||
vmOs.address = nil
|
||||
|
||||
vmComputer.isRobot = function ()
|
||||
return false
|
||||
end
|
||||
vmComputer.address = function ()
|
||||
return "k-computer"
|
||||
end
|
||||
vmComputer.tmpAddress = function ()
|
||||
return tmpAddress
|
||||
end
|
||||
|
||||
vmComputer.getBootAddress = function ()
|
||||
return "k-eeprom"
|
||||
end
|
||||
vmComputer.setBootAddress = function ()
|
||||
end
|
||||
vmComputer.users = function ()
|
||||
return {}
|
||||
end
|
||||
vmComputer.addUser = function ()
|
||||
return false, "user support not available"
|
||||
end
|
||||
vmComputer.removeUser = function ()
|
||||
return false, "user support not available"
|
||||
end
|
||||
vmComputer.beep = function (...)
|
||||
return vmComponent.invoke("k-computer", "beep", ...)
|
||||
end
|
||||
vmComputer.getDeviceInfo = function (...)
|
||||
return vmComponent.invoke("k-computer", "getDeviceInfo", ...)
|
||||
end
|
||||
vmComputer.getProgramLocations = function (...)
|
||||
return vmComponent.invoke("k-computer", "getProgramLocations", ...)
|
||||
end
|
||||
vmComputer.getArchitectures = function (...)
|
||||
return vmComponent.invoke("k-computer", "getArchitectures", ...)
|
||||
end
|
||||
vmComputer.getArchitecture = function (...)
|
||||
return vmComponent.invoke("k-computer", "getArchitecture", ...)
|
||||
end
|
||||
vmComputer.setArchitecture = function (...)
|
||||
return vmComponent.invoke("k-computer", "setArchitecture", ...)
|
||||
end
|
||||
|
||||
vmUnicode = clone(unicode)
|
||||
vmUnicode.safeTextSupport = nil
|
||||
vmUnicode.undoSafeTextSupport = nil
|
||||
|
||||
vmEnvironment = {
|
||||
_VERSION = _VERSION,
|
||||
component = vmComponent,
|
||||
computer = vmComputer,
|
||||
table = clone(table),
|
||||
math = clone(math),
|
||||
string = clone(string),
|
||||
unicode = vmUnicode,
|
||||
-- Scheme here:
|
||||
-- A yield's first argument is nil for an actual yield,
|
||||
-- or the time to add a timer at (math.huge if no timeout) for a pullSignal.
|
||||
-- This is not exactly the same, but is very similar, to that of machine.lua,
|
||||
-- differing mainly in how pullSignal timeout scheduling occurs.
|
||||
coroutine = {
|
||||
yield = function (...)
|
||||
return coroutine.yield(nil, ...)
|
||||
end,
|
||||
-- The way this is defined by machine.lua makes it true even when it arguably shouldn't be. Oh well.
|
||||
isyieldable = coroutine.isyieldable,
|
||||
status = coroutine.status,
|
||||
create = function (f)
|
||||
return coroutine.create(function (...)
|
||||
return nil, f(...)
|
||||
end)
|
||||
end,
|
||||
running = coroutine.running,
|
||||
wrap = function (f)
|
||||
local pf = coroutine.wrap(function (...)
|
||||
return nil, f(...)
|
||||
end)
|
||||
return function (...)
|
||||
local last = {...}
|
||||
while true do
|
||||
local tabpack = {pf(table.unpack(last))}
|
||||
if not tabpack[1] then
|
||||
return table.unpack(tabpack, 2)
|
||||
end
|
||||
last = {coroutine.yield(tabpack[1])}
|
||||
end
|
||||
end
|
||||
end,
|
||||
resume = function (co, ...)
|
||||
local last = {...}
|
||||
while true do
|
||||
local tabpack = {coroutine.resume(co, table.unpack(last))}
|
||||
if not tabpack[1] then
|
||||
neo.emergency(co, table.unpack(tabpack))
|
||||
return table.unpack(tabpack)
|
||||
elseif not tabpack[2] then
|
||||
return tabpack[1], table.unpack(tabpack, 3)
|
||||
end
|
||||
last = {coroutine.yield(tabpack[2])}
|
||||
end
|
||||
end
|
||||
},
|
||||
os = vmOs,
|
||||
debug = clone(debug),
|
||||
bit32 = clone(bit32),
|
||||
utf8 = clone(utf8),
|
||||
assert = assert,
|
||||
ipairs = ipairs,
|
||||
next = next,
|
||||
load = function (a, b, c, d)
|
||||
if rawequal(d, nil) then
|
||||
d = vmEnvironment
|
||||
end
|
||||
return load(a, b, c, d)
|
||||
end,
|
||||
|
||||
pairs = pairs, pcall = function (...)
|
||||
local r = {pcall(...)}
|
||||
if not r[1] then
|
||||
neo.emergency("pcall error:", table.unpack(r, 2))
|
||||
end
|
||||
return table.unpack(r)
|
||||
end,
|
||||
xpcall = xpcall, select = select,
|
||||
type = type, error = error,
|
||||
tonumber = tonumber, tostring = tostring,
|
||||
|
||||
setmetatable = setmetatable, getmetatable = getmetatable,
|
||||
rawset = rawset, rawget = rawget,
|
||||
rawlen = rawlen, rawequal = rawequal,
|
||||
checkArg = checkArg
|
||||
}
|
||||
vmEnvironment._G = vmEnvironment
|
||||
|
||||
if vmSuperVM then
|
||||
vmEnvironment._MMstartVM = function (vmName)
|
||||
neo.executeAsync("app-metamachine", vmName)
|
||||
end
|
||||
vmEnvironment._MMserial = function (...)
|
||||
return require("serial").serialize(...)
|
||||
end
|
||||
vmEnvironment._MMdeserial = function (...)
|
||||
return require("serial").deserialize(...)
|
||||
end
|
||||
vmEnvironment.print = neo.emergency
|
||||
local root = neo.requestAccess("k.root")
|
||||
if root then
|
||||
vmEnvironment._MMcomList = root.component.list
|
||||
else
|
||||
vmEnvironment._MMcomList = function ()
|
||||
return function ()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- bootstrap
|
||||
|
||||
vmBaseCoroutineWrap = coroutine.wrap(function ()
|
||||
vmBaseCoroutine = coroutine.running()
|
||||
local eepromAddress = vmComponent.list("eeprom")()
|
||||
if not eepromAddress then
|
||||
error("No EEPROM")
|
||||
end
|
||||
local code = vmComponent.invoke(eepromAddress, "get")
|
||||
local res, f = load(code, "=eeprom", "t", vmEnvironment)
|
||||
if not res then
|
||||
error(f)
|
||||
else
|
||||
res()
|
||||
end
|
||||
end)
|
||||
|
||||
while ((not vmBaseCoroutine) or (coroutine.status(vmBaseCoroutine) ~= "dead")) and not vmSelfdestruct do
|
||||
local details = {vmBaseCoroutineWrap()}
|
||||
while postVMRList[1] do
|
||||
table.remove(postVMRList, 1)()
|
||||
end
|
||||
if details[1] then
|
||||
local checkTimer = nil
|
||||
if details[1] ~= math.huge then
|
||||
checkTimer = neo.scheduleTimer(details[1])
|
||||
--neo.emergency("metamachine timer " .. details[1])
|
||||
else
|
||||
--neo.emergency("metamachine HANG")
|
||||
end
|
||||
while true do
|
||||
local ev = {coroutine.yield()}
|
||||
if ev[1] == "k.timer" then
|
||||
if ev[2] == checkTimer then
|
||||
break
|
||||
end
|
||||
elseif ev[1]:sub(1, 2) == "h." then
|
||||
if passthroughs[ev[2]] then
|
||||
ev[1] = ev[1]:sub(3)
|
||||
table.insert(signalStack, ev)
|
||||
break
|
||||
end
|
||||
elseif ev[1] == "x.neo.pub.window" then
|
||||
local id = ev[2]
|
||||
if ev[3] == "key" then
|
||||
if ev[6] then
|
||||
table.insert(signalStack, {"key_down", screensInt[id][1] .. "-kb", ev[4], ev[5], "neo"})
|
||||
else
|
||||
table.insert(signalStack, {"key_up", screensInt[id][1] .. "-kb", ev[4], ev[5], "neo"})
|
||||
end
|
||||
break
|
||||
elseif ev[3] == "line" then
|
||||
screensInt[id][2].line(ev[4])
|
||||
elseif ev[3] == "touch" or ev[3] == "drag" or ev[3] == "drop" or ev[3] == "scroll" then
|
||||
local x = ev[4]
|
||||
local y = ev[5]
|
||||
if screensInt[id][2].precise then
|
||||
x = (x - 1) + ev[6]
|
||||
y = (y - 1) + ev[7]
|
||||
end
|
||||
table.insert(signalStack, {ev[3], screensInt[id][1], x, y, ev[8], "neo"})
|
||||
break
|
||||
elseif ev[3] == "close" then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
error("Yield in root coroutine")
|
||||
end
|
||||
end
|
@ -24,7 +24,7 @@ return {
|
||||
},
|
||||
["neo-docs"] = {
|
||||
desc = "KittenOS NEO system documentation",
|
||||
v = 2,
|
||||
v = 3,
|
||||
deps = {
|
||||
"zzz-license-pd"
|
||||
},
|
||||
@ -106,6 +106,30 @@ return {
|
||||
"docs/repoauthors/svc-ghostie"
|
||||
},
|
||||
},
|
||||
["app-metamachine"] = {
|
||||
desc = "Virtual machine",
|
||||
v = 0,
|
||||
deps = {
|
||||
"neo",
|
||||
"zzz-license-pd"
|
||||
},
|
||||
dirs = {
|
||||
"apps",
|
||||
"libs",
|
||||
"docs",
|
||||
"docs/repoauthors",
|
||||
"data",
|
||||
"data/app-metamachine"
|
||||
},
|
||||
files = {
|
||||
"apps/app-metamachine.lua",
|
||||
"libs/metamachine-vgpu.lua",
|
||||
"libs/metamachine-vfs.lua",
|
||||
"docs/repoauthors/app-metamachine",
|
||||
"data/app-metamachine/confboot.lua",
|
||||
"data/app-metamachine/lucaboot.lua"
|
||||
},
|
||||
},
|
||||
["app-launchbar"] = {
|
||||
desc = "Application launcher bar",
|
||||
v = 0,
|
||||
@ -115,6 +139,7 @@ return {
|
||||
},
|
||||
dirs = {
|
||||
"apps",
|
||||
"docs",
|
||||
"docs/repoauthors"
|
||||
},
|
||||
files = {
|
||||
@ -131,6 +156,7 @@ return {
|
||||
},
|
||||
dirs = {
|
||||
"apps",
|
||||
"docs",
|
||||
"docs/repoauthors"
|
||||
},
|
||||
files = {
|
||||
|
237
repository/data/app-metamachine/confboot.lua
Normal file
237
repository/data/app-metamachine/confboot.lua
Normal file
@ -0,0 +1,237 @@
|
||||
-- _MMstartVM(name)
|
||||
-- _MMcomList(...)
|
||||
-- _MMserial(str)
|
||||
-- _MMdeserial(str)
|
||||
local screen = component.proxy(component.list("screen", true)())
|
||||
local gpu = component.proxy(component.list("gpu", true)())
|
||||
local fs = component.proxy("world")
|
||||
|
||||
screen.turnOn()
|
||||
gpu.bind(screen.address)
|
||||
gpu.setResolution(50, 15)
|
||||
gpu.setForeground(0)
|
||||
gpu.setBackground(0xFFFFFF)
|
||||
|
||||
local menu
|
||||
local currentY
|
||||
|
||||
local currentVMId
|
||||
local currentVM
|
||||
|
||||
local genMainMenu, genFSSelector, genEditor
|
||||
|
||||
function genFSSelector(cb)
|
||||
local fsName = ""
|
||||
menu = {
|
||||
{" - Select VFS -", function () end},
|
||||
{"Cancel", cb},
|
||||
{"New FS: ", function ()
|
||||
fs.makeDirectory("fs-" .. fsName)
|
||||
genFSSelector(cb)
|
||||
end, function (text)
|
||||
if text then fsName = text end
|
||||
return fsName
|
||||
end}
|
||||
}
|
||||
currentY = 2
|
||||
local fsl = fs.list("")
|
||||
table.sort(fsl)
|
||||
for k, v in ipairs(fsl) do
|
||||
if v:sub(#v) == "/" and v:sub(1, 3) == "fs-" then
|
||||
local id = v:sub(4, #v - 1)
|
||||
table.insert(menu, {id, function ()
|
||||
cb(id)
|
||||
end})
|
||||
table.insert(menu, {" Delete", function ()
|
||||
fs.remove("fs-" .. id)
|
||||
genFSSelector(cb)
|
||||
end})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function doVMSave()
|
||||
local f = fs.open("vm-" .. currentVMId, "wb")
|
||||
if not f then error("VM Save failed...") end
|
||||
fs.write(f, _MMserial(currentVM))
|
||||
fs.close(f)
|
||||
end
|
||||
|
||||
function genEditor()
|
||||
menu = {
|
||||
--01234567890123456789012345678901234567890123456789
|
||||
{" - configuring VM: " .. currentVMId, function () end},
|
||||
{"Save & Return", function ()
|
||||
doVMSave()
|
||||
currentVM, currentVMId = nil
|
||||
genMainMenu()
|
||||
end},
|
||||
{"Save & Launch", function ()
|
||||
doVMSave()
|
||||
_MMstartVM(currentVMId)
|
||||
computer.shutdown()
|
||||
end},
|
||||
{"Delete", function ()
|
||||
fs.remove("vm-" .. currentVMId)
|
||||
currentVM, currentVMId = nil
|
||||
genMainMenu()
|
||||
end},
|
||||
}
|
||||
currentY = 3
|
||||
for k, v in pairs(currentVM) do
|
||||
local v1 = tostring(v)
|
||||
if type(v) ~= "string" then
|
||||
v1 = "virt. ".. v[1]
|
||||
end
|
||||
table.insert(menu, {"Del. " .. v1 .. " " .. k, function ()
|
||||
currentVM[k] = nil
|
||||
genEditor()
|
||||
end})
|
||||
end
|
||||
table.insert(menu, {"+ Virtual FS (R/W)...", function ()
|
||||
genFSSelector(function (fsa)
|
||||
if fsa then
|
||||
currentVM["fs-" .. fsa] = {"filesystem", "/fs-" .. fsa .. "/", false}
|
||||
end
|
||||
genEditor()
|
||||
end)
|
||||
end})
|
||||
table.insert(menu, {"+ Virtual FS (R/O)...", function ()
|
||||
genFSSelector(function (fsa)
|
||||
if fsa then
|
||||
currentVM[fsa .. "-fs"] = {"filesystem", fsa, true}
|
||||
end
|
||||
genEditor()
|
||||
end)
|
||||
end})
|
||||
local tx = {
|
||||
"+ Screen 50x15:",
|
||||
"+ Screen 80x24:",
|
||||
"+ Screen 160x49:"
|
||||
}
|
||||
local txw = {
|
||||
50,
|
||||
80,
|
||||
160
|
||||
}
|
||||
local txh = {
|
||||
15,
|
||||
24,
|
||||
49
|
||||
}
|
||||
for i = 1, 3 do
|
||||
local cName = currentVMId .. "-screen"
|
||||
local nt = 0
|
||||
while currentVM[cName] do
|
||||
nt = nt + 1
|
||||
cName = currentVMId .. "-" .. nt
|
||||
end
|
||||
table.insert(menu, {tx[i], function ()
|
||||
currentVM[cName] = {"screen", cName, txw[i], txh[i], 8}
|
||||
genEditor()
|
||||
end, function (text)
|
||||
if text then cName = text end
|
||||
return cName
|
||||
end})
|
||||
end
|
||||
for address, ty in _MMcomList("") do
|
||||
if (not currentVM[address]) and ty ~= "gpu" then
|
||||
table.insert(menu, {"+ Host " .. ty .. " " .. address, function ()
|
||||
currentVM[address] = ty
|
||||
genEditor()
|
||||
end})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function genMainMenu()
|
||||
local vmName = ""
|
||||
menu = {
|
||||
--01234567890123456789012345678901234567890123456789
|
||||
{" - metamachine configurator -- use keyboard - ", function () end},
|
||||
{"Shutdown", computer.shutdown},
|
||||
{"New VM: ", function ()
|
||||
local f = fs.open("vm-" .. vmName, "wb")
|
||||
if not f then return end
|
||||
fs.write(f, _MMserial({
|
||||
[vmName .. "-eeprom"] = {"eeprom", "/lucaboot.lua", "/vd-" .. vmName .. ".bin", "LUCcABOOT VM BIOS", true},
|
||||
[vmName .. "-screen"] = {"screen", vmName, 50, 15, 8}
|
||||
}))
|
||||
fs.close(f)
|
||||
genMainMenu()
|
||||
end, function (text)
|
||||
if text then vmName = text end
|
||||
return vmName
|
||||
end}
|
||||
}
|
||||
currentY = 3
|
||||
local fsl = fs.list("")
|
||||
table.sort(fsl)
|
||||
for k, v in ipairs(fsl) do
|
||||
if v:sub(#v) == "/" then
|
||||
elseif v:sub(1, 3) == "vm-" then
|
||||
local id = v:sub(4)
|
||||
table.insert(menu, #menu, {id, function ()
|
||||
local f = fs.open("vm-" .. id, "rb")
|
||||
if not f then return end
|
||||
local str = ""
|
||||
while true do
|
||||
local sb = fs.read(f, 2048)
|
||||
if not sb then break end
|
||||
str = str .. sb
|
||||
end
|
||||
currentVM = _MMdeserial(str) or {}
|
||||
fs.close(f)
|
||||
currentVMId = id
|
||||
genEditor()
|
||||
end})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
----
|
||||
|
||||
genMainMenu()
|
||||
|
||||
local function draw()
|
||||
gpu.fill(1, 1, 50, 15, " ")
|
||||
local camera = math.max(0, math.min(math.floor(currentY - 7), #menu - 15))
|
||||
for i = 1, #menu do
|
||||
local pfx = " "
|
||||
if currentY == i then
|
||||
pfx = "> "
|
||||
end
|
||||
local pox = ""
|
||||
if menu[i][3] then
|
||||
pox = menu[i][3]()
|
||||
end
|
||||
gpu.set(1, i - camera, pfx .. menu[i][1] .. pox)
|
||||
end
|
||||
end
|
||||
|
||||
-- Final main loop.
|
||||
draw()
|
||||
while true do
|
||||
local t = {computer.pullSignal()}
|
||||
if t[1] == "key_down" then
|
||||
if t[4] == 200 then
|
||||
currentY = math.max(1, currentY - 1)
|
||||
draw()
|
||||
elseif t[4] == 208 then
|
||||
currentY = math.min(currentY + 1, #menu)
|
||||
draw()
|
||||
elseif t[3] == 13 then
|
||||
menu[currentY][2]()
|
||||
draw()
|
||||
elseif t[3] == 8 then
|
||||
local tx = menu[currentY][3]()
|
||||
menu[currentY][3](unicode.sub(tx, 1, unicode.len(tx) - 1))
|
||||
draw()
|
||||
elseif t[3] >= 32 then
|
||||
if menu[currentY][3] then
|
||||
menu[currentY][3](menu[currentY][3]() .. unicode.char(t[3]))
|
||||
draw()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
26
repository/data/app-metamachine/lucaboot.lua
Normal file
26
repository/data/app-metamachine/lucaboot.lua
Normal file
@ -0,0 +1,26 @@
|
||||
-- LUCcABOOT v0
|
||||
local lr = "(no inits)"
|
||||
for a in component.list("filesystem", true) do
|
||||
local dat = component.proxy(a)
|
||||
local fh = dat.open("/init.lua", "rb")
|
||||
if fh then
|
||||
local ttl = ""
|
||||
while true do
|
||||
local chk = dat.read(fh, 2048)
|
||||
if not chk then break end
|
||||
ttl = ttl .. chk
|
||||
end
|
||||
computer.getBootAddress = function () return a end
|
||||
computer.setBootAddress = function () end
|
||||
local fn, r = load(ttl, "=init.lua", "t")
|
||||
if not fn then
|
||||
lr = r
|
||||
dat.close(fh)
|
||||
else
|
||||
dat.close(fh)
|
||||
return fn()
|
||||
end
|
||||
end
|
||||
end
|
||||
error("No available operating systems. " .. lr)
|
||||
|
@ -123,28 +123,19 @@ os is extended with:
|
||||
address = computer.address
|
||||
|
||||
The following are just host functions
|
||||
(*: wrapped for security - these
|
||||
functions detect metatable abuse):
|
||||
(*: wrapped for security):
|
||||
|
||||
assert, ipairs, load, next*,
|
||||
assert, ipairs, load*, next*,
|
||||
pairs, pcall, xpcall, select,
|
||||
type, error, tonumber, tostring,
|
||||
setmetatable, getmetatable*,
|
||||
rawset*, rawget, rawlen, rawequal
|
||||
|
||||
(NOTE: Before you consider that load
|
||||
has no checks: The policy regarding
|
||||
load is taken from the host system.
|
||||
Which means bytecode loading is
|
||||
almost certainly off. If it's not
|
||||
off, then this is the user's fault,
|
||||
as doing so is marked as a security
|
||||
risk for very obvious reasons.
|
||||
As for trying to use the environment
|
||||
to bypass a metatable, I tested.
|
||||
Metatables do apply to environments,
|
||||
including global creation.
|
||||
There is no way to win.)
|
||||
(Apparently load, if not given an
|
||||
argument, uses the global metatable.
|
||||
This is of course a security hole.
|
||||
A very big one. So it ended up
|
||||
getting wrapped as of R3.)
|
||||
|
||||
"require" and "neo" are the parts of
|
||||
the environment where a NEO-specific
|
||||
|
6
repository/docs/repoauthors/app-metamachine
Normal file
6
repository/docs/repoauthors/app-metamachine
Normal file
@ -0,0 +1,6 @@
|
||||
repository/apps/app-metamachine.lua: 20kdc, Public Domain
|
||||
repository/libs/metamachine-vgpu.lua: 20kdc, Public Domain
|
||||
repository/libs/metamachine-vfs.lua: 20kdc, Public Domain
|
||||
repository/data/app-metamachine/confboot.lua: 20kdc, Public Domain
|
||||
repository/data/app-metamachine/lucaboot.lua: 20kdc, Public Domain
|
||||
|
161
repository/libs/metamachine-vfs.lua
Normal file
161
repository/libs/metamachine-vfs.lua
Normal file
@ -0,0 +1,161 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- metamachine-vgpu.lua : Virtual GPU library
|
||||
-- Authors: 20kdc
|
||||
|
||||
return function (icecap, address, path, ro)
|
||||
if path ~= "/" then
|
||||
icecap.makeDirectory(path:sub(1, #path - 1))
|
||||
end
|
||||
local function resolvePath(p, post)
|
||||
local pth = {}
|
||||
local issue = false
|
||||
string.gsub(p, "[^\\/]+", function (str)
|
||||
if str == ".." then
|
||||
if not pth[1] then
|
||||
issue = true
|
||||
else
|
||||
table.remove(pth, #pth)
|
||||
end
|
||||
elseif str ~= "." then
|
||||
table.insert(pth, str)
|
||||
end
|
||||
end)
|
||||
if issue then
|
||||
return
|
||||
end
|
||||
local str = path
|
||||
if post then
|
||||
str = str:sub(1, #str - 1)
|
||||
end
|
||||
for k, v in ipairs(pth) do
|
||||
if k > 1 or post then
|
||||
str = str .. "/"
|
||||
end
|
||||
str = str .. v
|
||||
end
|
||||
return str
|
||||
end
|
||||
local function wrapThing(fn, post, roStop)
|
||||
-- If we're adding a "/", we get rid of the original "/".
|
||||
--
|
||||
local pofx = ""
|
||||
if post then
|
||||
pofx = "/"
|
||||
end
|
||||
return function (p)
|
||||
if ro and roStop then
|
||||
return false, "read-only filesystem"
|
||||
end
|
||||
p = resolvePath(p, post)
|
||||
if p then
|
||||
local nt = {pcall(fn, p .. pofx)}
|
||||
if nt[1] then
|
||||
return table.unpack(nt, 2)
|
||||
end
|
||||
end
|
||||
return nil, "no such file or directory"
|
||||
end
|
||||
end
|
||||
local function wrapStat(s)
|
||||
return wrapThing(function (px)
|
||||
local stat = icecap.stat(px)
|
||||
if stat then
|
||||
return stat[s]
|
||||
end
|
||||
end, false, false)
|
||||
end
|
||||
local handles = {}
|
||||
local lHandle = 0
|
||||
local modeMapping = {
|
||||
r = false,
|
||||
rb = false,
|
||||
w = true,
|
||||
wb = true,
|
||||
a = "append",
|
||||
ab = "append"
|
||||
}
|
||||
return {
|
||||
type = "filesystem",
|
||||
getLabel = function ()
|
||||
return "VFS"
|
||||
end,
|
||||
setLabel = function (label)
|
||||
end,
|
||||
isReadOnly = function ()
|
||||
return ro or icecap.isReadOnly()
|
||||
end,
|
||||
spaceUsed = function ()
|
||||
return icecap.spaceUsed()
|
||||
end,
|
||||
spaceTotal = function ()
|
||||
return icecap.spaceTotal()
|
||||
end,
|
||||
list = wrapThing(icecap.list, true, false),
|
||||
exists = wrapThing(function (px)
|
||||
if icecap.stat(px) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end, false, false),
|
||||
isDirectory = wrapStat(1),
|
||||
size = wrapStat(2),
|
||||
lastModified = wrapStat(3),
|
||||
makeDirectory = wrapThing(icecap.makeDirectory, false, true),
|
||||
rename = function (a, b)
|
||||
if ro then return false, "read-only filesystem" end
|
||||
a = resolvePath(a)
|
||||
b = resolvePath(b)
|
||||
if not (a and b) then
|
||||
return nil, a
|
||||
end
|
||||
return icecap.rename(a, b)
|
||||
end,
|
||||
remove = wrapThing(icecap.remove, false, true),
|
||||
--
|
||||
open = function (p, mode)
|
||||
checkArg(1, p, "string")
|
||||
p = resolvePath(p)
|
||||
if not p then return nil, "failed to open" end
|
||||
if rawequal(mode, nil) then mode = "r" end
|
||||
if modeMapping[mode] == nil then
|
||||
error("unsupported mode " .. tostring(mode))
|
||||
end
|
||||
mode = modeMapping[mode]
|
||||
if (mode ~= false) and ro then return nil, "read-only filesystem" end
|
||||
lHandle = lHandle + 1
|
||||
handles[lHandle] = icecap.open(p, mode)
|
||||
if not handles[lHandle] then
|
||||
return nil, "failed to open"
|
||||
end
|
||||
return lHandle
|
||||
end,
|
||||
read = function (fh, len)
|
||||
checkArg(1, fh, "number")
|
||||
checkArg(2, len, "number")
|
||||
if not handles[fh] then return nil, "bad file descriptor" end
|
||||
if not handles[fh].read then return nil, "bad file descriptor" end
|
||||
return handles[fh].read(len)
|
||||
end,
|
||||
write = function (fh, data)
|
||||
checkArg(1, fh, "number")
|
||||
if not handles[fh] then return nil, "bad file descriptor" end
|
||||
if not handles[fh].write then return nil, "bad file descriptor" end
|
||||
return handles[fh].write(data)
|
||||
end,
|
||||
seek = function (fh, whence, point)
|
||||
checkArg(1, fh, "number")
|
||||
if not handles[fh] then return nil, "bad file descriptor" end
|
||||
if not handles[fh].seek then return nil, "bad file descriptor" end
|
||||
return handles[fh].seek(whence, point)
|
||||
end,
|
||||
close = function (fh)
|
||||
checkArg(1, fh, "number")
|
||||
if not handles[fh] then return nil, "bad file descriptor" end
|
||||
handles[fh].close()
|
||||
handles[fh] = nil
|
||||
return true
|
||||
end,
|
||||
}
|
||||
end
|
408
repository/libs/metamachine-vgpu.lua
Normal file
408
repository/libs/metamachine-vgpu.lua
Normal file
@ -0,0 +1,408 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- metamachine-vgpu.lua : Virtual GPU library
|
||||
-- Authors: 20kdc
|
||||
|
||||
return {
|
||||
-- Creates a new virtual GPU.
|
||||
-- 'screens' is the component mapping, and thus:
|
||||
-- [id] = <glacier GPU function: gpu, rebound = f()>
|
||||
newGPU = function (screens)
|
||||
local boundScreen
|
||||
local backgroundRGB, foregroundRGB = 0, 0xFFFFFF
|
||||
local function bound()
|
||||
if not screens[boundScreen] then return end
|
||||
local gpu, rebound = screens[boundScreen]()
|
||||
if not gpu then screens[boundScreen] = nil return end
|
||||
if rebound then
|
||||
gpu.setBackground(backgroundRGB)
|
||||
gpu.setForeground(foregroundRGB)
|
||||
end
|
||||
return gpu
|
||||
end
|
||||
return {
|
||||
-- Virtual GPU proxy
|
||||
type = "gpu",
|
||||
-- == getAspectRatio more or less
|
||||
getSize = function ()
|
||||
local gpu = bound()
|
||||
if not gpu then
|
||||
return 1, 1
|
||||
else
|
||||
return gpu.getSize()
|
||||
end
|
||||
end,
|
||||
bind = function (adr, rst)
|
||||
boundScreen = adr
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
if rst then
|
||||
gpu.setResolution(gpu.maxResolution())
|
||||
end
|
||||
return true
|
||||
end
|
||||
boundScreen = nil
|
||||
-- :(
|
||||
return false, "No such virtual screen"
|
||||
end,
|
||||
getScreen = function ()
|
||||
return boundScreen
|
||||
end,
|
||||
maxResolution = function ()
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
return gpu.maxResolution()
|
||||
end
|
||||
error("unbound")
|
||||
end,
|
||||
getResolution = function ()
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
return gpu.getResolution()
|
||||
end
|
||||
error("unbound")
|
||||
end,
|
||||
getViewport = function ()
|
||||
-- annoyingly undocumented so we'll pretend it's this, as OCEmu does
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
return gpu.getResolution()
|
||||
end
|
||||
error("unbound")
|
||||
end,
|
||||
setResolution = function (...)
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
return gpu.setResolution(...)
|
||||
end
|
||||
error("unbound")
|
||||
end,
|
||||
setViewport = function (...)
|
||||
-- annoyingly undocumented so we'll pretend it's this, as OCEmu does
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
return gpu.setResolution(...)
|
||||
end
|
||||
error("unbound")
|
||||
end,
|
||||
maxDepth = function ()
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
return gpu.maxDepth()
|
||||
end
|
||||
error("unbound")
|
||||
end,
|
||||
getDepth = function ()
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
return gpu.getDepth()
|
||||
end
|
||||
error("unbound")
|
||||
end,
|
||||
setDepth = function (...)
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
return gpu.setDepth(...)
|
||||
end
|
||||
error("unbound")
|
||||
end,
|
||||
get = function (...)
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
return gpu.get(...)
|
||||
end
|
||||
error("unbound")
|
||||
end,
|
||||
set = function (...)
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
return gpu.set(...)
|
||||
end
|
||||
error("unbound")
|
||||
end,
|
||||
copy = function (...)
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
return gpu.copy(...)
|
||||
end
|
||||
error("unbound")
|
||||
end,
|
||||
fill = function (...)
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
return gpu.fill(...)
|
||||
end
|
||||
error("unbound")
|
||||
end,
|
||||
getPaletteColor = function ()
|
||||
return 0
|
||||
end,
|
||||
setPaletteColor = function ()
|
||||
-- Fail
|
||||
end,
|
||||
setForeground = function (rgb)
|
||||
checkArg(1, rgb, "number")
|
||||
local old = foregroundRGB
|
||||
foregroundRGB = rgb
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
gpu.setForeground(foregroundRGB)
|
||||
end
|
||||
return old
|
||||
end,
|
||||
setBackground = function (rgb)
|
||||
checkArg(1, rgb, "number")
|
||||
local old = backgroundRGB
|
||||
backgroundRGB = rgb
|
||||
local gpu = bound()
|
||||
if gpu then
|
||||
gpu.setBackground(backgroundRGB)
|
||||
end
|
||||
return old
|
||||
end,
|
||||
getForeground = function ()
|
||||
return foregroundRGB
|
||||
end,
|
||||
getBackground = function ()
|
||||
return backgroundRGB
|
||||
end
|
||||
}
|
||||
end,
|
||||
-- 'window' is used for span and setSize.
|
||||
-- emitResize(w, h) is used for screen_resized events.
|
||||
-- queueLine(y) is used for line queuing.
|
||||
newBuffer = function (window, keyboards, maxW, maxH, emitResize, queueLine)
|
||||
local screenW, screenH
|
||||
local screenText = ""
|
||||
local screenFR = ""
|
||||
local screenFG = ""
|
||||
local screenFB = ""
|
||||
local screenBR = ""
|
||||
local screenBG = ""
|
||||
local screenBB = ""
|
||||
-- Gets characters for R, G and B
|
||||
local function decodeRGB(rgb)
|
||||
return
|
||||
string.char(math.floor(rgb / 65536) % 256),
|
||||
string.char(math.floor(rgb / 256) % 256),
|
||||
string.char(rgb % 256)
|
||||
end
|
||||
-- Returns the width, or nothing if totally out of bounds.
|
||||
local function put(x, y, ch, fg, bg)
|
||||
if x < 1 or x > screenW then return end
|
||||
if y < 1 or y > screenH then return end
|
||||
local fr, fg, fb = decodeRGB(fg)
|
||||
local br, bg, bb = decodeRGB(bg)
|
||||
ch = unicode.safeTextFormat(ch)
|
||||
local chw = unicode.len(ch)
|
||||
-- Crop
|
||||
ch = unicode.sub(ch, 1, (screenW - x) + 1)
|
||||
chw = unicode.len(ch)
|
||||
|
||||
local index = x + ((y - 1) * screenW)
|
||||
screenText = unicode.sub(screenText, 1, index - 1) .. ch .. unicode.sub(screenText, index + chw)
|
||||
screenFR = screenFR:sub(1, index - 1) .. fr:rep(chw) .. screenFR:sub(index + chw)
|
||||
screenFG = screenFG:sub(1, index - 1) .. fg:rep(chw) .. screenFG:sub(index + chw)
|
||||
screenFB = screenFB:sub(1, index - 1) .. fb:rep(chw) .. screenFB:sub(index + chw)
|
||||
screenBR = screenBR:sub(1, index - 1) .. br:rep(chw) .. screenBR:sub(index + chw)
|
||||
screenBG = screenBG:sub(1, index - 1) .. bg:rep(chw) .. screenBG:sub(index + chw)
|
||||
screenBB = screenBB:sub(1, index - 1) .. bb:rep(chw) .. screenBB:sub(index + chw)
|
||||
return chw
|
||||
end
|
||||
local function getCh(x, y)
|
||||
x, y = math.floor(x), math.floor(y)
|
||||
if x < 1 or x > screenW then return " ", 0, 0 end
|
||||
if y < 1 or y > screenH then return " ", 0, 0 end
|
||||
local index = x + ((y - 1) * screenW)
|
||||
local fg = (screenFR:byte(index) * 65536) + (screenFG:byte(index) * 256) + screenFB:byte(index)
|
||||
local bg = (screenBR:byte(index) * 65536) + (screenBG:byte(index) * 256) + screenBB:byte(index)
|
||||
return unicode.sub(screenText, index, index), fg, bg
|
||||
end
|
||||
--
|
||||
-- Directly exposed to userspace
|
||||
local function setSize(w, h, first)
|
||||
w = math.min(math.max(math.floor(w), 1), maxW)
|
||||
h = math.min(math.max(math.floor(h), 1), maxH)
|
||||
screenW, screenH = w, h
|
||||
screenText = (" "):rep(w * h)
|
||||
screenFR = ("\xFF"):rep(w * h)
|
||||
screenFG = screenFR
|
||||
screenFB = screenFG
|
||||
screenBR = ("\x00"):rep(w * h)
|
||||
screenBG = screenBR
|
||||
screenBB = screenBG
|
||||
if not first then emitResize(w, h) end
|
||||
window.setSize(w, h)
|
||||
end
|
||||
local function rectOOB(x, y, w, h)
|
||||
if x < 1 or x > screenW - (w - 1) then return true end
|
||||
if y < 1 or y > screenH - (h - 1) then return true end
|
||||
end
|
||||
local function redrawLine(x, y, w)
|
||||
x, y, w = math.floor(x), math.floor(y), math.floor(w)
|
||||
w = math.min(w, screenW - (x - 1))
|
||||
if w < 1 then return end
|
||||
if x < 1 or x > screenW then return end
|
||||
if y < 1 or y > screenH then return end
|
||||
local index = x + ((y - 1) * screenW)
|
||||
local currentSegmentI
|
||||
local currentSegment
|
||||
local currentSegmentR2
|
||||
local function flushSegment()
|
||||
if not currentSegment then return end
|
||||
local tx = unicode.undoSafeTextFormat(currentSegment)
|
||||
local fg = (currentSegmentR2:byte(1) * 65536) + (currentSegmentR2:byte(2) * 256) + currentSegmentR2:byte(3)
|
||||
local bg = (currentSegmentR2:byte(4) * 65536) + (currentSegmentR2:byte(5) * 256) + currentSegmentR2:byte(6)
|
||||
-- Span format is bg, fg, not fg, bg
|
||||
window.span(x + currentSegmentI - 1, y, tx, bg, fg)
|
||||
currentSegment = nil
|
||||
currentSegmentI = nil
|
||||
currentSegmentR2 = nil
|
||||
end
|
||||
for i = 1, w do
|
||||
local idx = index + i - 1
|
||||
local p = unicode.sub(screenText, idx, idx)
|
||||
local s =
|
||||
screenFR:sub(idx, idx) .. screenFG:sub(idx, idx) .. screenFB:sub(idx, idx) ..
|
||||
screenBR:sub(idx, idx) .. screenBG:sub(idx, idx) .. screenBB:sub(idx, idx)
|
||||
if currentSegmentR2 ~= s then
|
||||
flushSegment()
|
||||
currentSegmentI = i
|
||||
currentSegmentR2 = s
|
||||
currentSegment = p
|
||||
else
|
||||
currentSegment = currentSegment .. p
|
||||
end
|
||||
end
|
||||
flushSegment()
|
||||
end
|
||||
local function queueRedraw(x, y, w, h)
|
||||
for i = 1, h do
|
||||
queueLine(y + i - 1)
|
||||
end
|
||||
end
|
||||
setSize(maxW, maxH, true)
|
||||
local fgRGB, bgRGB = 0xFFFFFF, 0
|
||||
local videoInterfaceChipset = {
|
||||
getSize = function ()
|
||||
return 1, 1
|
||||
end,
|
||||
maxResolution = function ()
|
||||
return maxW, maxH
|
||||
end,
|
||||
getResolution = function ()
|
||||
return screenW, screenH
|
||||
end,
|
||||
setResolution = setSize,
|
||||
maxDepth = function ()
|
||||
return 8
|
||||
end,
|
||||
getDepth = function ()
|
||||
return 8
|
||||
end,
|
||||
setDepth = function (d)
|
||||
end,
|
||||
get = getCh,
|
||||
set = function (x, y, str, v)
|
||||
x, y = math.floor(x), math.floor(y)
|
||||
if v then
|
||||
for i = 1, unicode.len(str) do
|
||||
put(x, y + i - 1, unicode.sub(str, i, i), fgRGB, bgRGB)
|
||||
end
|
||||
return true
|
||||
end
|
||||
local chw = put(x, y, str, fgRGB, bgRGB)
|
||||
if chw then
|
||||
queueLine(y)
|
||||
else
|
||||
return false, "Out of bounds."
|
||||
end
|
||||
return true
|
||||
end,
|
||||
copy = function (x, y, w, h, ox, oy)
|
||||
x, y, w, h, ox, oy = math.floor(x), math.floor(y), math.floor(w), math.floor(h), math.floor(ox), math.floor(oy)
|
||||
if rectOOB(x, y, w, h) then return false, "out of bounds. s" end
|
||||
if rectOOB(x + ox, y + oy, w, h) then return false, "out of bounds. t" end
|
||||
local collation = {}
|
||||
for iy = 1, h do
|
||||
collation[iy] = {}
|
||||
for ix = 1, w do
|
||||
collation[iy][ix] = {getCh(ix + x - 1, iy + y - 1)}
|
||||
end
|
||||
end
|
||||
for iy = 1, h do
|
||||
for ix = 1, w do
|
||||
local cc = collation[iy][ix]
|
||||
if ix + unicode.charWidth(cc[1]) - 1 <= w then
|
||||
put(ix + ox + x - 1, iy + oy + y - 1, cc[1], cc[2], cc[3])
|
||||
end
|
||||
end
|
||||
end
|
||||
queueRedraw(x + ox, y + oy, w, h)
|
||||
return true
|
||||
end,
|
||||
fill = function (x, y, w, h, str)
|
||||
x, y, w, h = math.floor(x), math.floor(y), math.floor(w), math.floor(h)
|
||||
if rectOOB(x, y, w, h) then return false, "out of bounds" end
|
||||
str = unicode.sub(str, 1, 1)
|
||||
str = str:rep(math.floor(w / unicode.charWidth(str)))
|
||||
for i = 1, h do
|
||||
put(x, y + i - 1, str, fgRGB, bgRGB)
|
||||
end
|
||||
queueRedraw(x, y, w, h)
|
||||
return true
|
||||
end,
|
||||
setForeground = function (rgb)
|
||||
fgRGB = rgb
|
||||
end,
|
||||
setBackground = function (rgb)
|
||||
bgRGB = rgb
|
||||
end,
|
||||
}
|
||||
-- Various interfaces
|
||||
local int = {
|
||||
-- Internal interface
|
||||
line = function (y)
|
||||
redrawLine(1, y, screenW)
|
||||
end,
|
||||
precise = false
|
||||
}
|
||||
return function ()
|
||||
return videoInterfaceChipset, false
|
||||
end, int, {
|
||||
type = "screen",
|
||||
isOn = function ()
|
||||
return true
|
||||
end,
|
||||
turnOn = function ()
|
||||
return true
|
||||
end,
|
||||
turnOff = function ()
|
||||
return true
|
||||
end,
|
||||
getAspectRatio = function ()
|
||||
return 1, 1
|
||||
end,
|
||||
getKeyboards = function ()
|
||||
local kbs = {}
|
||||
for k, v in ipairs(keyboards) do
|
||||
kbs[k] = v
|
||||
end
|
||||
return kbs
|
||||
end,
|
||||
setPrecise = function (p)
|
||||
int.precise = p
|
||||
end,
|
||||
isPrecise = function ()
|
||||
return int.precise
|
||||
end,
|
||||
setTouchModeInverted = function (p)
|
||||
return false
|
||||
end,
|
||||
isTouchModeInverted = function ()
|
||||
return false
|
||||
end,
|
||||
}
|
||||
end
|
||||
}
|
Loading…
Reference in New Issue
Block a user