1
0
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:
20kdc 2018-05-29 23:50:11 +01:00
parent d5405685dd
commit 7fa7441794
8 changed files with 1410 additions and 17 deletions

View 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

View File

@ -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 = {

View 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

View 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)

View File

@ -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

View 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

View 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

View 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
}