mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-12-25 02:18:07 +11:00
With SYSTEM HEROES making continuing feasible, FIRST COMMIT
This commit is contained in:
commit
6474b9356b
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# leaving in preSH.tar.gz for anyone who's interested
|
||||
# in how NOT to do compression
|
||||
code.tar
|
||||
inst.lua
|
||||
com2/code.tar.bd
|
||||
|
79
README.md
Normal file
79
README.md
Normal file
@ -0,0 +1,79 @@
|
||||
# KittenOS NEO
|
||||
### efficient. multi-tasking. clean. security-oriented.
|
||||
|
||||
# WARNING! STATUS: UNSTABLE!
|
||||
|
||||
The first commit is after I got the installer working again after the new compression system (BDIVIDE).
|
||||
|
||||
The older compression systems (which are not compatible with `heroes.lua`) are kept in preSH.tar.gz in case you want to see how NOT to do things.
|
||||
|
||||
## Description
|
||||
|
||||
KittenOS NEO is an OpenComputers operating system designed for Tier 1 hardware.
|
||||
|
||||
This means, among other things, it has an operating overhead limit of 192KiB real-world (on 32-bit).
|
||||
|
||||
Unlike the original KittenOS (now in the "legacy" branch), it is also designed with some attempt at cleanliness.
|
||||
|
||||
## User Guide
|
||||
|
||||
It is recommended that you take out your OpenComputers CPU, and shift-right-click until it says "Architecture: Lua 5.3", if possible.
|
||||
|
||||
Then simply download the installer from inst.lua here, rename it to "init.lua" and put it on a blank disk.
|
||||
|
||||
Finally, remove all other disks and reboot.
|
||||
|
||||
KittenOS NEO will install itself.
|
||||
|
||||
(This does not account for custom EEPROMs.)
|
||||
|
||||
## Policy regarding updates
|
||||
|
||||
KittenOS NEO's installer, including the full KittenOS NEO base operating system, is 65536 bytes or below.
|
||||
|
||||
As the installer must be loaded in full into RAM, this is not negotiable.
|
||||
|
||||
If it can't be kept this way with the current compressor, then a better compressor will have to be made.
|
||||
|
||||
All kernel or security-critical `sys-` process bugs will cause an installer update.
|
||||
|
||||
Other bugs will merely result in an updated copy in the repository.
|
||||
|
||||
This copy will be copied to installer code if and only if another condition requires the installer code be updated.
|
||||
|
||||
The code in the `code/` folder is the code meant for the installer.
|
||||
|
||||
Non-installer code is in the `repository/`, (WORKING ON THIS) and thus accessible via CLAW.
|
||||
|
||||
(NOTE: HTTPS is not used for this due to OC/OCEmu issues.)
|
||||
|
||||
Requests for additional features in system APIs will NOT cause an installer update.
|
||||
|
||||
## Building
|
||||
|
||||
The tools are meant for internal use, so are thus designed to run on some generic Unix.
|
||||
|
||||
Firstly, you can create a "raw installer" (uncompressed) with `mkucinst.lua`.
|
||||
|
||||
This executes `tar -cf code.tar code`, which you will need to do in any case - the installer contains a compressed TAR.
|
||||
|
||||
Secondly, for a compressed installer, after creating the TAR, `symsear-st1.sh`, `symsear-st2.sh`, and `symsear-st4.sh` (st3 is executed by st2) are used.
|
||||
|
||||
## Kernel Architecture
|
||||
|
||||
KittenOS NEO is an idea of what a Lua-based efficient microkernel would look like.
|
||||
|
||||
Scheduling is based entirely around uptime and timers,
|
||||
which cause something to be executed at a given uptime.
|
||||
|
||||
## Installer Architecture
|
||||
|
||||
The installer is split into a generic TAR extractor frontend `insthead.lua` and a replacable compression backend (written in by relevant tools - in current versions, `heroes.lua` is where it starts).
|
||||
|
||||
There was more details on this but the details changed.
|
||||
|
||||
## License
|
||||
|
||||
This is released into the public domain.
|
||||
No warranty is provided, implied or otherwise.
|
||||
|
338
code/apps/app-claw.lua
Normal file
338
code/apps/app-claw.lua
Normal file
@ -0,0 +1,338 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- app-claw: Package manager.
|
||||
|
||||
-- HTTP-only because OCEmu SUCKS
|
||||
local source = "http://20kdc.duckdns.org/neo/"
|
||||
|
||||
local primaryINet = neo.requestAccess("c.internet")
|
||||
if primaryINet then primaryINet = primaryINet.list()() end
|
||||
|
||||
local function pkgExists(pkg, isApp)
|
||||
local b = {}
|
||||
if isApp then
|
||||
b = neo.listApps()
|
||||
else
|
||||
b = neo.listLibs()
|
||||
end
|
||||
for _, v in ipairs(b) do
|
||||
if v == pkg then return true end
|
||||
end
|
||||
end
|
||||
|
||||
-- global app variables
|
||||
-- elements:
|
||||
-- {
|
||||
-- description,
|
||||
-- dlURL/nil,
|
||||
-- localPath,
|
||||
-- isApp,
|
||||
-- }
|
||||
local packages = {}
|
||||
local packageList = {}
|
||||
local libList = {} -- removed after scan
|
||||
local windows = 1
|
||||
local searchTx = ""
|
||||
local primaryWindowRegen
|
||||
--
|
||||
|
||||
local event = require("event")(neo)
|
||||
local neoux, err = require("neoux")
|
||||
if not neoux then error(err) end
|
||||
neoux = neoux(event, neo)
|
||||
|
||||
local function download(url)
|
||||
if not primaryINet then return nil, "no internet" end
|
||||
local req, err = primaryINet.request(url)
|
||||
if not req then
|
||||
return nil, tostring(err)
|
||||
end
|
||||
local ok, err = req.finishConnect()
|
||||
if not req.finishConnect() then
|
||||
req.close()
|
||||
return nil, tostring(err)
|
||||
end
|
||||
local dt = ""
|
||||
while true do
|
||||
local n, n2 = req.read()
|
||||
if not n then
|
||||
req.close()
|
||||
if n2 then
|
||||
return nil, n2
|
||||
else
|
||||
break
|
||||
end
|
||||
else
|
||||
if n == "" then
|
||||
-- slightly dangerous, but what can we do?
|
||||
event.sleepTo(os.uptime() + 0.05)
|
||||
end
|
||||
dt = dt .. n
|
||||
end
|
||||
end
|
||||
req.close()
|
||||
return dt
|
||||
end
|
||||
|
||||
local function readLocal()
|
||||
-- Read apps & libs
|
||||
local function isLua(v)
|
||||
if v:sub(#v - 3) == ".lua" then
|
||||
return v:sub(1, #v - 4)
|
||||
end
|
||||
end
|
||||
for _, pkg in ipairs(neo.listApps()) do
|
||||
table.insert(packageList, pkg)
|
||||
packages[pkg] = {
|
||||
"A pre-installed or self-written process.",
|
||||
nil,
|
||||
"apps/" .. pkg .. ".lua",
|
||||
true
|
||||
}
|
||||
end
|
||||
for _, pkg in ipairs(neo.listLibs()) do
|
||||
table.insert(libList, pkg)
|
||||
packages[pkg] = {
|
||||
"A pre-installed or self-written library.",
|
||||
nil,
|
||||
"libs/" .. pkg .. ".lua",
|
||||
false
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function getEntry(name, isApp)
|
||||
if packages[name] then
|
||||
if packages[name][4] ~= isApp then
|
||||
return
|
||||
end
|
||||
else
|
||||
local path = "libs/" .. name .. ".lua"
|
||||
if isApp then
|
||||
path = "apps/" .. name .. ".lua"
|
||||
end
|
||||
packages[name] = {"An unknown entry, lost to time.", nil, path, isApp}
|
||||
if isApp then
|
||||
table.insert(packageList, name)
|
||||
else
|
||||
table.insert(libList, name)
|
||||
end
|
||||
end
|
||||
return packages[name]
|
||||
end
|
||||
|
||||
local function readWeb()
|
||||
local listData, err = download(source .. "list")
|
||||
if not listData then
|
||||
neoux.startDialog("Couldn't get web index: " .. err, "web", true)
|
||||
return
|
||||
end
|
||||
--neoux.startDialog(listData, "web", false)
|
||||
listData = (listData .. "\n"):gmatch(".-\n")
|
||||
local function listDataStrip()
|
||||
local l = listData()
|
||||
if not l then
|
||||
return
|
||||
end
|
||||
l = l:sub(1, #l - 1)
|
||||
return l
|
||||
end
|
||||
while true do
|
||||
local l = listDataStrip()
|
||||
if not l then return end
|
||||
if l == "end" then return end
|
||||
local ent = getEntry(l:sub(5), l:sub(1, 4) == "app ")
|
||||
if ent then
|
||||
ent[1] = listDataStrip() or "PARSE ERROR"
|
||||
ent[2] = source .. l:sub(5) .. ".lua"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
readLocal()
|
||||
if source then
|
||||
readWeb()
|
||||
end
|
||||
|
||||
table.sort(packageList)
|
||||
table.sort(libList)
|
||||
for _, v in ipairs(libList) do
|
||||
table.insert(packageList, v)
|
||||
end
|
||||
libList = nil
|
||||
|
||||
local function startPackageWindow(pkg)
|
||||
windows = windows + 1
|
||||
local downloading = false
|
||||
local desc = packages[pkg][1]
|
||||
local isApp = packages[pkg][4]
|
||||
local isSysSvc = isApp and (pkg:sub(1, 4) == "sys-")
|
||||
local isSvc = isSysSvc or (isApp and (pkg:sub(1, 4) == "svc-"))
|
||||
local settings
|
||||
if isSvc then
|
||||
settings = neo.requestAccess("x.neo.sys.manage")
|
||||
end
|
||||
local function update(w)
|
||||
if downloading then return end
|
||||
downloading = true
|
||||
local fd = download(packages[pkg][2])
|
||||
local msg = "Success!"
|
||||
if fd then
|
||||
local primaryDisk = neo.requestAccess("c.filesystem").primary
|
||||
local f = primaryDisk.open(packages[pkg][3], "wb")
|
||||
primaryDisk.write(f, fd)
|
||||
primaryDisk.close(f)
|
||||
else
|
||||
msg = "Couldn't download."
|
||||
end
|
||||
w.close()
|
||||
windows = windows - 1
|
||||
primaryWindowRegen()
|
||||
-- Another event loop so the program won't exit too early.
|
||||
neoux.startDialog(msg, pkg, true)
|
||||
downloading = false
|
||||
end
|
||||
|
||||
local elems = {
|
||||
neoux.tcrawview(1, 1, neoux.fmtText(unicode.safeTextFormat(desc), 30)),
|
||||
}
|
||||
-- {txt, run}
|
||||
local buttonbar = {}
|
||||
if pkgExists(pkg, isApp) then
|
||||
if isApp then
|
||||
if not isSvc then
|
||||
table.insert(buttonbar, {"Start", function (w)
|
||||
neo.executeAsync(pkg)
|
||||
w.close()
|
||||
windows = windows - 1
|
||||
end})
|
||||
else
|
||||
if not isSysSvc then
|
||||
table.insert(buttonbar, {"Start", function (w)
|
||||
neo.executeAsync(pkg)
|
||||
w.close()
|
||||
windows = windows - 1
|
||||
end})
|
||||
end
|
||||
if settings.getSetting("run." .. pkg) == "yes" then
|
||||
table.insert(buttonbar, {"Disable", function (w)
|
||||
settings.setSetting("run." .. pkg, "no")
|
||||
w.close()
|
||||
windows = windows - 1
|
||||
end})
|
||||
else
|
||||
table.insert(buttonbar, {"Enable", function (w)
|
||||
settings.setSetting("run." .. pkg, "yes")
|
||||
w.close()
|
||||
windows = windows - 1
|
||||
end})
|
||||
end
|
||||
end
|
||||
end
|
||||
if packages[pkg][2] then
|
||||
table.insert(buttonbar, {"Update", update})
|
||||
end
|
||||
table.insert(buttonbar, {"Delete", function (w)
|
||||
local primaryDisk = neo.requestAccess("c.filesystem").primary
|
||||
primaryDisk.remove(packages[pkg][3])
|
||||
w.close()
|
||||
windows = windows - 1
|
||||
primaryWindowRegen()
|
||||
end})
|
||||
else
|
||||
if packages[pkg][2] then
|
||||
table.insert(buttonbar, {"Install", update})
|
||||
end
|
||||
end
|
||||
local x = 1
|
||||
for _, v in ipairs(buttonbar) do
|
||||
local b = neoux.tcbutton(x, 10, v[1], v[2])
|
||||
x = x + (#v[1]) + 2
|
||||
table.insert(elems, b)
|
||||
end
|
||||
neoux.create(30, 10, pkg, neoux.tcwindow(30, 10, elems, function (w)
|
||||
w.close()
|
||||
windows = windows - 1
|
||||
end, 0xFFFFFF, 0))
|
||||
end
|
||||
|
||||
local genwin, primaryWindow
|
||||
local primaryWindowPage = 1
|
||||
local primaryWindowList = packageList
|
||||
|
||||
function primaryWindowRegen()
|
||||
primaryWindow.reset(20, 8, genwin(primaryWindowPage, primaryWindowList))
|
||||
end
|
||||
|
||||
genwin = function (page, efList)
|
||||
local pages = math.ceil(#efList / 6)
|
||||
local elems = {
|
||||
neoux.tcbutton(18, 1, "+", function (w)
|
||||
if page < pages then
|
||||
primaryWindowPage = page + 1
|
||||
primaryWindowRegen()
|
||||
end
|
||||
end),
|
||||
neoux.tcrawview(4, 1, {neoux.pad(page .. " / " .. pages, 14, true, true)}),
|
||||
neoux.tcbutton(1, 1, "-", function (w)
|
||||
if page > 1 then
|
||||
primaryWindowPage = page - 1
|
||||
primaryWindowRegen()
|
||||
end
|
||||
end)
|
||||
}
|
||||
local base = (page - 1) * 6
|
||||
for i = 1, 6 do
|
||||
local ent = efList[base + i]
|
||||
if ent then
|
||||
local enttx = ent
|
||||
if packages[ent][4] then
|
||||
enttx = "A " .. enttx
|
||||
else
|
||||
enttx = "L " .. enttx
|
||||
end
|
||||
if pkgExists(ent, packages[ent][4]) then
|
||||
if packages[ent][2] then
|
||||
enttx = "I" .. enttx
|
||||
else
|
||||
enttx = "i" .. enttx
|
||||
end
|
||||
else
|
||||
enttx = " " .. enttx
|
||||
end
|
||||
table.insert(elems, neoux.tcbutton(1, i + 1, unicode.safeTextFormat(enttx), function (w)
|
||||
-- Start a dialog
|
||||
startPackageWindow(ent)
|
||||
end))
|
||||
end
|
||||
end
|
||||
table.insert(elems, neoux.tcfield(1, 8, 11, function (s)
|
||||
if s then searchTx = s end
|
||||
return searchTx
|
||||
end))
|
||||
table.insert(elems, neoux.tcbutton(12, 8, "Search!", function (w)
|
||||
local n = {}
|
||||
for _, v in ipairs(packageList) do
|
||||
for i = 1, #v do
|
||||
if v:sub(i, i + #searchTx - 1) == searchTx then
|
||||
table.insert(n, v)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
primaryWindowPage = 1
|
||||
primaryWindowList = n
|
||||
primaryWindowRegen()
|
||||
end))
|
||||
return neoux.tcwindow(20, 8, elems, function (w)
|
||||
w.close()
|
||||
windows = windows - 1
|
||||
end, 0xFFFFFF, 0)
|
||||
end
|
||||
|
||||
primaryWindow = neoux.create(20, 8, "claw", genwin(1, packageList))
|
||||
|
||||
while windows > 0 do
|
||||
event.pull()
|
||||
end
|
13
code/apps/app-fm.lua
Normal file
13
code/apps/app-fm.lua
Normal file
@ -0,0 +1,13 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- app-fm: dummy app to start FM
|
||||
neo.requestAccess("x.neo.pub.base").showFileDialogAsync(nil)
|
||||
while true do
|
||||
local x = {coroutine.yield()}
|
||||
if x[1] == "x.neo.pub.base" then
|
||||
if x[2] == "filedialog" then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
41
code/apps/app-launcher.lua
Normal file
41
code/apps/app-launcher.lua
Normal file
@ -0,0 +1,41 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- app-launcher: The launcher
|
||||
local event = require("event")(neo)
|
||||
local neoux, err = require("neoux")
|
||||
if not neoux then error(err) end -- This app is basically neoux's testcase
|
||||
neoux = neoux(event, neo)
|
||||
|
||||
local running = true
|
||||
|
||||
local buttons = {}
|
||||
local xlen = 0
|
||||
local appNames = neo.listApps()
|
||||
for k, v in ipairs(appNames) do
|
||||
if v:sub(1, 4) == "app-" then
|
||||
local vl = unicode.len(v)
|
||||
if xlen < vl then
|
||||
xlen = vl
|
||||
end
|
||||
table.insert(buttons, neoux.tcbutton(1, #buttons + 1, v, function (w)
|
||||
-- Button pressed.
|
||||
local pid, err = neo.executeAsync(v)
|
||||
if not pid then
|
||||
neoux.startDialog(tostring(err), "launchErr")
|
||||
else
|
||||
w.close()
|
||||
running = false
|
||||
end
|
||||
end))
|
||||
end
|
||||
end
|
||||
|
||||
neoux.create(xlen + 2, #buttons, nil, neoux.tcwindow(xlen + 2, #buttons, buttons, function (w)
|
||||
w.close()
|
||||
running = false
|
||||
end, 0xFFFFFF, 0))
|
||||
|
||||
while running do
|
||||
event.pull()
|
||||
end
|
47
code/apps/app-pass.lua
Normal file
47
code/apps/app-pass.lua
Normal file
@ -0,0 +1,47 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- app-pass: The password setter
|
||||
local settings = neo.requestAccess("x.neo.sys.manage")
|
||||
if not settings then error("no management") return end
|
||||
|
||||
local event = require("event")(neo)
|
||||
local neoux, err = require("neoux")
|
||||
if not neoux then error(err) end
|
||||
neoux = neoux(event, neo)
|
||||
|
||||
local running = true
|
||||
|
||||
local pw = settings.getSetting("password")
|
||||
neoux.create(20, 2, nil, neoux.tcwindow(20, 2, {
|
||||
neoux.tcfield(1, 1, 12, function (set)
|
||||
if not set then
|
||||
return pw
|
||||
end
|
||||
pw = set
|
||||
end),
|
||||
neoux.tcbutton(13, 1, "set PW", function (w)
|
||||
settings.setSetting("password", pw)
|
||||
w.close()
|
||||
running = false
|
||||
end),
|
||||
neoux.tcbutton(1, 2, "log out", function (w)
|
||||
w.close()
|
||||
running = false
|
||||
local session = neo.requestAccess("x.neo.sys.session")
|
||||
if not session then return end
|
||||
session.endSession(true)
|
||||
end),
|
||||
neoux.tcbutton(11, 2, "shutdown", function (w)
|
||||
w.close()
|
||||
running = false
|
||||
settings.shutdown(false)
|
||||
end)
|
||||
}, function (w)
|
||||
w.close()
|
||||
running = false
|
||||
end, 0xFFFFFF, 0))
|
||||
|
||||
while running do
|
||||
event.pull()
|
||||
end
|
134
code/apps/app-taskmgr.lua
Normal file
134
code/apps/app-taskmgr.lua
Normal file
@ -0,0 +1,134 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- app-taskmgr: Task manager
|
||||
-- a-hello : simple test program for Everest.
|
||||
|
||||
local everest = neo.requestAccess("x.neo.pub.window")
|
||||
if not everest then error("no everest") return end
|
||||
|
||||
local kill = neo.requestAccess("k.kill")
|
||||
|
||||
local sW, sH = 20, 8
|
||||
local headlines = 2
|
||||
|
||||
local window = everest(sW, sH)
|
||||
if not window then error("no window") end
|
||||
|
||||
local lastIdleTimeTime = os.uptime()
|
||||
local lastIdleTime = neo.totalIdleTime()
|
||||
local cpuPercent = 100
|
||||
|
||||
local lastCPUTimeRecord = {}
|
||||
local cpuCause = "(none)"
|
||||
|
||||
local camY = 1
|
||||
-- elements have {pid, text}
|
||||
local consistentProcList = {}
|
||||
|
||||
local function drawLine(n)
|
||||
local red = false
|
||||
local idx = (camY + n) - (headlines + 1)
|
||||
local stat = ("~"):rep(sW)
|
||||
if n == 1 then
|
||||
-- x.everest redraw. Since this window only has one line...
|
||||
local usage = math.floor((os.totalMemory() - os.freeMemory()) / 1024)
|
||||
stat = usage .. "/" .. math.floor(os.totalMemory() / 1024) .. "K, CPU " .. cpuPercent .. "%"
|
||||
red = true
|
||||
elseif n == 2 then
|
||||
stat = "MAX:" .. cpuCause
|
||||
red = true
|
||||
elseif consistentProcList[idx] then
|
||||
if idx == camY then
|
||||
stat = ">" .. consistentProcList[idx][2]
|
||||
else
|
||||
stat = " " .. consistentProcList[idx][2]
|
||||
end
|
||||
end
|
||||
stat = unicode.safeTextFormat(stat)
|
||||
while unicode.len(stat) < sW do stat = stat .. " " end
|
||||
if red then
|
||||
window.span(1, n, unicode.sub(stat, 1, sW), 0xFFFFFF, 0)
|
||||
else
|
||||
window.span(1, n, unicode.sub(stat, 1, sW), 0x000000, 0xFFFFFF)
|
||||
end
|
||||
end
|
||||
local function updateConsistentProcList(pt, lp)
|
||||
local tbl = {}
|
||||
local tbl2 = {}
|
||||
local tbl3 = {}
|
||||
for _, v in ipairs(pt) do
|
||||
table.insert(tbl, v[1])
|
||||
tbl2[v[1]] = v[2] .. "/" .. v[1] .. " " .. tostring(lp[v[1]]) .. "%"
|
||||
end
|
||||
table.sort(tbl)
|
||||
for k, v in ipairs(tbl) do
|
||||
tbl3[k] = {v, tbl2[v]}
|
||||
end
|
||||
consistentProcList = tbl3
|
||||
end
|
||||
local p = os.uptime()
|
||||
neo.scheduleTimer(p)
|
||||
while true do
|
||||
local n = {coroutine.yield()}
|
||||
if n[1] == "x.neo.pub.window" then
|
||||
if n[3] == "line" then
|
||||
drawLine(n[4])
|
||||
end
|
||||
if n[3] == "close" then
|
||||
return
|
||||
end
|
||||
if n[3] == "key" then
|
||||
if n[6] then
|
||||
if n[4] == 8 then
|
||||
if consistentProcList[camY] then
|
||||
kill(consistentProcList[camY][1])
|
||||
end
|
||||
end
|
||||
if n[5] == 200 then
|
||||
camY = camY - 1
|
||||
if camY < 1 then camY = 1 end
|
||||
for i = (headlines + 1), sH do drawLine(i) end
|
||||
end
|
||||
if n[5] == 208 then
|
||||
camY = camY + 1
|
||||
for i = (headlines + 1), sH do drawLine(i) end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if n[1] == "k.timer" then
|
||||
local now = os.uptime()
|
||||
local nowIT = neo.totalIdleTime()
|
||||
local tD = now - lastIdleTimeTime
|
||||
local iD = nowIT - lastIdleTime
|
||||
cpuPercent = math.ceil(((tD - iD) / tD) * 100)
|
||||
lastIdleTimeTime = now
|
||||
lastIdleTime = nowIT
|
||||
|
||||
local newRecord = {}
|
||||
cpuCause = "(none)"
|
||||
local causeUsage = 0
|
||||
local pt = neo.listProcs()
|
||||
local localPercent = {}
|
||||
for _, v in ipairs(pt) do
|
||||
-- pkg, pid, cpuTime
|
||||
local baseline = 0
|
||||
if lastCPUTimeRecord[v[1]] then
|
||||
baseline = lastCPUTimeRecord[v[1]]
|
||||
end
|
||||
local val = v[3] - baseline
|
||||
localPercent[v[1]] = math.ceil(100 * (val / tD))
|
||||
if causeUsage < val then
|
||||
cpuCause = v[2] .. "/" .. v[1]
|
||||
causeUsage = val
|
||||
end
|
||||
newRecord[v[1]] = v[3]
|
||||
end
|
||||
lastCPUTimeRecord = newRecord
|
||||
updateConsistentProcList(pt, localPercent)
|
||||
for i = 1, sH do drawLine(i) end
|
||||
p = p + 1
|
||||
neo.scheduleTimer(p)
|
||||
end
|
||||
end
|
423
code/apps/app-textedit.lua
Normal file
423
code/apps/app-textedit.lua
Normal file
@ -0,0 +1,423 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- 'neolithic': Text Editor
|
||||
-- This was textedit (femto) from KittenOS brought kicking and screaming into the NEO world
|
||||
-- It also has fixes for bugs involving wide text, and runs faster due to the chars -> lines change.
|
||||
|
||||
-- needs translation??? figure out how to do translation in the new system NICELY
|
||||
local lines = {
|
||||
"Neolithic: Text Editor",
|
||||
"^A, ^S, ^Q: Load, Save, New",
|
||||
"^C, ^V, ^D: Copy, Paste, Delete Line",
|
||||
-- These two are meant to replace similar functionality in GNU Nano
|
||||
-- (which I consider the best console text editor out there - Neolithic is an *imitation* and a poor one at that),
|
||||
-- except fixing a UI flaw by instead making J responsible for resetting the append flag,
|
||||
-- so the user can more or less arbitrarily mash together lines
|
||||
"^J: Reset 'append' flag for Cut Lines",
|
||||
"^K: Cut Line(s)",
|
||||
"^<arrows>: Resize Win",
|
||||
"'^' is Control.",
|
||||
"Wide text can be pasted in,",
|
||||
" using your 'actual' clipboard.",
|
||||
"For example, this.",
|
||||
}
|
||||
|
||||
-- If replicating Nano's clipboard :
|
||||
-- Nano starts off in a "replace" mode,
|
||||
-- and then after an action occurs switches to "append" until *any cursor action is performed*.
|
||||
-- The way I have things setup is that you perform J then K(repeat) *instead*, which means you have to explicitly say "destroy current clipboard".
|
||||
|
||||
local event = require("event")(neo)
|
||||
local clipboard = neo.requestAccess("x.neo.pub.clip.text")
|
||||
if not clipboard then
|
||||
local clipboardData = ""
|
||||
clipboard = {
|
||||
copy = function (text) clipboardData = text end,
|
||||
paste = function () return clipboardData end
|
||||
}
|
||||
end
|
||||
|
||||
local cursorX = 1
|
||||
local cursorY = math.ceil(#lines / 2)
|
||||
local cFlash = true
|
||||
local ctrlFlag = false
|
||||
local dialogLock = false
|
||||
local appendFlag = false
|
||||
local sW, sH = 37, #lines + 2
|
||||
local window = neo.requestAccess("x.neo.pub.window")(sW, sH)
|
||||
local flush
|
||||
|
||||
local function splitCur()
|
||||
local s = lines[cursorY]
|
||||
local st = unicode.sub(s, 1, cursorX - 1)
|
||||
local en = unicode.sub(s, cursorX)
|
||||
return st, en
|
||||
end
|
||||
|
||||
local function clampCursorX()
|
||||
local s = lines[cursorY]
|
||||
if unicode.len(s) < (cursorX - 1) then
|
||||
cursorX = unicode.len(s) + 1
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function fileDialog(writing, callback)
|
||||
local tag = neo.requestAccess("x.neo.pub.base").showFileDialogAsync(writing)
|
||||
local f
|
||||
function f(_, evt, tag2, res)
|
||||
if evt == "filedialog" then
|
||||
if tag == tag2 then
|
||||
callback(res)
|
||||
event.ignore(f)
|
||||
end
|
||||
end
|
||||
end
|
||||
event.listen("x.neo.pub.base", f)
|
||||
end
|
||||
|
||||
-- Save/Load
|
||||
local function startSave()
|
||||
dialogLock = true
|
||||
fileDialog(true, function (res)
|
||||
dialogLock = false
|
||||
if res then
|
||||
for k, v in ipairs(lines) do
|
||||
if k ~= 1 then
|
||||
res.write("\n" .. v)
|
||||
else
|
||||
res.write(v)
|
||||
end
|
||||
end
|
||||
res.close()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function startLoad()
|
||||
dialogLock = true
|
||||
fileDialog(false, function (res)
|
||||
dialogLock = false
|
||||
if res then
|
||||
lines = {}
|
||||
local lb = ""
|
||||
while true do
|
||||
local l = res.read(64)
|
||||
if not l then
|
||||
table.insert(lines, lb)
|
||||
cursorX = 1
|
||||
cursorY = 1
|
||||
res.close()
|
||||
flush()
|
||||
return
|
||||
end
|
||||
local lp = l:find("\n")
|
||||
while lp do
|
||||
lb = lb .. l:sub(1, lp - 1)
|
||||
table.insert(lines, lb)
|
||||
lb = ""
|
||||
l = l:sub(lp + 1)
|
||||
lp = l:find("\n")
|
||||
end
|
||||
lb = lb .. l
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function getline(y)
|
||||
-- do rY first since unw
|
||||
-- only requires that
|
||||
-- horizontal stuff be
|
||||
-- messed with...
|
||||
-- ...thankfully
|
||||
local rY = (y + cursorY) - math.ceil(sH / 2)
|
||||
|
||||
-- rX is difficult!
|
||||
local rX = 1
|
||||
local Xthold = math.floor(sW / 2)
|
||||
local _, cursorXP = unicode.safeTextFormat(lines[cursorY], cursorX)
|
||||
if cursorXP > Xthold then
|
||||
rX = rX + (cursorXP - Xthold)
|
||||
end
|
||||
local line = lines[rY]
|
||||
if not line then
|
||||
return ("¬"):rep(sW)
|
||||
end
|
||||
line = unicode.safeTextFormat(line)
|
||||
-- <alter RX here by 1 if needed>
|
||||
local tl = unicode.sub(line, rX, rX + sW - 1)
|
||||
cursorXP = (cursorXP - rX) + 1
|
||||
if cFlash then
|
||||
if rY == cursorY then
|
||||
if cursorXP >= 1 then
|
||||
if cursorXP <= sW then
|
||||
local start = unicode.sub(tl, 1, cursorXP - 1)
|
||||
local endx = unicode.sub(tl, cursorXP + 1)
|
||||
tl = start .. "_" .. endx
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
while unicode.len(tl) < sW do
|
||||
tl = tl .. " "
|
||||
end
|
||||
return tl
|
||||
end
|
||||
local function delLine()
|
||||
local contents = lines[cursorY]
|
||||
if cursorY == #lines then
|
||||
if cursorY == 1 then
|
||||
lines[1] = ""
|
||||
else
|
||||
cursorY = cursorY - 1
|
||||
lines[#lines] = nil
|
||||
end
|
||||
else
|
||||
table.remove(lines, cursorY)
|
||||
end
|
||||
return contents
|
||||
end
|
||||
-- add a single character
|
||||
local function putLetter(ch)
|
||||
if ch == "\r" then
|
||||
local a, b = splitCur()
|
||||
lines[cursorY] = a
|
||||
table.insert(lines, cursorY + 1, b)
|
||||
cursorY = cursorY + 1
|
||||
cursorX = 1
|
||||
return
|
||||
end
|
||||
local a, b = splitCur()
|
||||
a = a .. ch
|
||||
lines[cursorY] = a .. b
|
||||
cursorX = unicode.len(a) + 1
|
||||
end
|
||||
local function ev_key(ka, kc, down)
|
||||
if dialogLock then
|
||||
return false
|
||||
end
|
||||
if kc == 29 then
|
||||
ctrlFlag = down
|
||||
return false
|
||||
end
|
||||
if ctrlFlag then
|
||||
if not down then return false end
|
||||
if kc == 200 then -- Up
|
||||
sH = sH - 1
|
||||
if sH == 0 then
|
||||
sH = 1
|
||||
end
|
||||
sW, sH = window.setSize(sW, sH)
|
||||
end
|
||||
if kc == 208 then -- Down
|
||||
sH = sH + 1
|
||||
sW, sH = window.setSize(sW, sH)
|
||||
end
|
||||
if kc == 203 then -- Left
|
||||
sW = sW - 1
|
||||
if sW == 0 then
|
||||
sW = 1
|
||||
end
|
||||
sW, sH = window.setSize(sW, sH)
|
||||
end
|
||||
if kc == 205 then -- Right
|
||||
sW = sW + 1
|
||||
sW, sH = window.setSize(sW, sH)
|
||||
end
|
||||
if kc == 31 then -- S
|
||||
startSave()
|
||||
end
|
||||
if kc == 30 then -- A
|
||||
startLoad()
|
||||
end
|
||||
if kc == 16 then -- Q
|
||||
lines = {""}
|
||||
cursorX = 1
|
||||
cursorY = 1
|
||||
return true
|
||||
end
|
||||
if kc == 32 then -- D
|
||||
delLine()
|
||||
return true
|
||||
end
|
||||
if kc == 46 then -- C
|
||||
clipboard.copy(lines[cursorY])
|
||||
end
|
||||
if kc == 36 then -- J
|
||||
appendFlag = false
|
||||
end
|
||||
if kc == 37 then -- K
|
||||
if appendFlag then
|
||||
local base = clipboard.paste()
|
||||
clipboard.copy(base .. "\n" .. delLine())
|
||||
else
|
||||
clipboard.copy(delLine())
|
||||
end
|
||||
appendFlag = true
|
||||
return true
|
||||
end
|
||||
if kc == 47 then -- V
|
||||
local tx = clipboard.paste()
|
||||
local txi = tx:find("\n")
|
||||
local nt = {}
|
||||
while txi do
|
||||
table.insert(nt, 1, tx:sub(1, txi - 1))
|
||||
tx = tx:sub(txi + 1)
|
||||
txi = tx:find("\n")
|
||||
end
|
||||
table.insert(lines, cursorY, tx)
|
||||
for _, v in ipairs(nt) do
|
||||
table.insert(lines, cursorY, v)
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
-- action keys
|
||||
if not down then
|
||||
return false
|
||||
end
|
||||
if kc == 200 or kc == 201 then -- Go up one - go up page
|
||||
local moveAmount = 1
|
||||
if kc == 201 then
|
||||
moveAmount = math.floor(sH / 2)
|
||||
end
|
||||
cursorY = cursorY - moveAmount
|
||||
if cursorY < 1 then
|
||||
cursorY = 1
|
||||
end
|
||||
clampCursorX()
|
||||
return true
|
||||
end
|
||||
if kc == 208 or kc == 209 then -- Go down one - go down page
|
||||
local moveAmount = 1
|
||||
if kc == 209 then
|
||||
moveAmount = math.floor(sH / 2)
|
||||
end
|
||||
cursorY = cursorY + moveAmount
|
||||
if cursorY > #lines then
|
||||
cursorY = #lines
|
||||
end
|
||||
clampCursorX()
|
||||
return true
|
||||
end
|
||||
if kc == 203 then
|
||||
if cursorX > 1 then
|
||||
cursorX = cursorX - 1
|
||||
else
|
||||
if cursorY > 1 then
|
||||
cursorY = cursorY - 1
|
||||
cursorX = unicode.len(lines[cursorY]) + 1
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
if kc == 205 then
|
||||
cursorX = cursorX + 1
|
||||
if clampCursorX() then
|
||||
if cursorY < #lines then
|
||||
cursorY = cursorY + 1
|
||||
cursorX = 1
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
if kc == 199 then
|
||||
cursorX = 1
|
||||
return true
|
||||
end
|
||||
if kc == 207 then
|
||||
cursorX = unicode.len(lines[cursorY]) + 1
|
||||
return true
|
||||
end
|
||||
if ka == 8 then
|
||||
if cursorX == 1 then
|
||||
if cursorY == 1 then
|
||||
return false
|
||||
end
|
||||
local l = table.remove(lines, cursorY)
|
||||
cursorY = cursorY - 1
|
||||
cursorX = unicode.len(lines[cursorY]) + 1
|
||||
lines[cursorY] = lines[cursorY] .. l
|
||||
else
|
||||
local a, b = splitCur()
|
||||
a = unicode.sub(a, 1, unicode.len(a) - 1)
|
||||
lines[cursorY] = a.. b
|
||||
cursorX = cursorX - 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
if ka ~= 0 then
|
||||
putLetter(unicode.char(ka))
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function ev_clipboard(t)
|
||||
for i = 1, unicode.len(t) do
|
||||
local c = unicode.sub(t, i, i)
|
||||
if c ~= "\r" then
|
||||
if c == "\n" then
|
||||
c = "\r"
|
||||
end
|
||||
putLetter(c)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
flush = function ()
|
||||
for i = 1, sH do
|
||||
window.span(1, i, getline(i), 0xFFFFFF, 0)
|
||||
end
|
||||
end
|
||||
local flash
|
||||
flash = function ()
|
||||
cFlash = not cFlash
|
||||
-- reverse:
|
||||
--local rY = (y + cursorY) - math.ceil(sH / 2)
|
||||
local csY = math.ceil(sH / 2)
|
||||
window.span(1, csY, getline(csY), 0xFFFFFF, 0)
|
||||
event.runAt(os.uptime() + 0.5, flash)
|
||||
end
|
||||
event.runAt(os.uptime() + 0.5, flash)
|
||||
|
||||
while true do
|
||||
local e = {event.pull()}
|
||||
if e[1] == "x.neo.pub.window" then
|
||||
if e[2] == window.id then
|
||||
if e[3] == "touch" then
|
||||
-- reverse:
|
||||
--local rY = (y + cursorY) - math.ceil(sH / 2)
|
||||
local csY = math.ceil(sH / 2)
|
||||
local nY = math.max(1, math.min(#lines, (math.floor(e[5]) - csY) + cursorY))
|
||||
cursorY = nY
|
||||
clampCursorX()
|
||||
flush()
|
||||
end
|
||||
if e[3] == "key" then
|
||||
if ev_key(e[4], e[5], e[6]) then
|
||||
flush()
|
||||
end
|
||||
end
|
||||
if e[3] == "line" then
|
||||
window.span(1, e[4], getline(e[4]), 0xFFFFFF, 0)
|
||||
end
|
||||
if e[3] == "focus" then
|
||||
ctrlFlag = false
|
||||
end
|
||||
if e[3] == "close" then
|
||||
return
|
||||
end
|
||||
if e[3] == "clipboard" then
|
||||
ev_clipboard(e[4])
|
||||
flush()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
295
code/apps/sys-donkonit.lua
Normal file
295
code/apps/sys-donkonit.lua
Normal file
@ -0,0 +1,295 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- s-donkonit : config, shutdown, screens
|
||||
|
||||
-- Doesn't matter what calls this service, because there's a mutex here.
|
||||
local donkonitSPProvider = neo.requestAccess("r.neo.sys.manage") -- Restrict to s-
|
||||
if not donkonitSPProvider then return end
|
||||
local donkonitRDProvider = neo.requestAccess("r.neo.sys.screens")
|
||||
if not donkonitRDProvider then return end
|
||||
|
||||
local computer = neo.requestAccess("k.computer")
|
||||
local fs = neo.requestAccess("c.filesystem")
|
||||
local gpus = neo.requestAccess("c.gpu")
|
||||
local screens = neo.requestAccess("c.screen")
|
||||
neo.requestAccess("s.h.component_added")
|
||||
neo.requestAccess("s.h.component_removed")
|
||||
|
||||
local function shutdownFin(reboot)
|
||||
-- any final actions donkonit needs to take here
|
||||
computer.shutdown(reboot)
|
||||
end
|
||||
|
||||
-- keys are pids
|
||||
local targs = {} -- settings notify targs
|
||||
local targsSD = {} -- shutdown notify targs
|
||||
local targsST = {} -- saving throws
|
||||
|
||||
local targsRD = {} -- pid->{sendSig,dead}
|
||||
|
||||
-- screen address -> {gpu, monitor}
|
||||
local monitorMap = {}
|
||||
|
||||
local shuttingDown = false
|
||||
local shutdownMode = false
|
||||
|
||||
-- needs improvements
|
||||
local settings = {
|
||||
-- The list of settings is here:
|
||||
-- password
|
||||
password = "",
|
||||
["run.sys-everest"] = "yes",
|
||||
["run.sys-icecap"] = "yes",
|
||||
-- scr.w/h/d.<uuid>
|
||||
}
|
||||
|
||||
local function loadSettings()
|
||||
pcall(function ()
|
||||
local fw = require("sys-filewrap")
|
||||
local se = require("serial")
|
||||
local st = fw(fs.primary, "data/sys-donkonit/sysconf.lua", false)
|
||||
local cfg = st.read("*a")
|
||||
st.close()
|
||||
st = nil
|
||||
fw = nil
|
||||
cfg = se.deserialize(cfg)
|
||||
for k, v in pairs(cfg) do
|
||||
if type(k) == "string" then
|
||||
if type(v) == "string" then
|
||||
settings[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
local function saveSettings()
|
||||
local fw = require("sys-filewrap")
|
||||
local se = require("serial")
|
||||
fs.primary.makeDirectory("data/sys-donkonit")
|
||||
local st = fw(fs.primary, "data/sys-donkonit/sysconf.lua", true)
|
||||
st.write(se.serialize(settings))
|
||||
st.close()
|
||||
end
|
||||
|
||||
-- Monitor management stuff
|
||||
local monitorPool = {}
|
||||
-- {gpu, claimedLoseCallback}
|
||||
local monitorClaims = {}
|
||||
|
||||
local function announceFreeMonitor(address, except)
|
||||
for k, v in pairs(targsRD) do
|
||||
if k ~= except then
|
||||
v[1]("available", address)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function getGPU(monitor)
|
||||
local bestG
|
||||
local bestD = 0
|
||||
for v in gpus.list() do
|
||||
v.bind(monitor.address)
|
||||
local d = v.maxDepth()
|
||||
if d > bestD then
|
||||
bestG = v
|
||||
bestD = d
|
||||
end
|
||||
end
|
||||
return bestG
|
||||
end
|
||||
|
||||
local function setupMonitor(gpu, monitor)
|
||||
local maxW, maxH = gpu.maxResolution()
|
||||
local maxD = gpu.maxDepth()
|
||||
local w = tonumber(settings["scr.w." .. monitor.address]) or 80
|
||||
local h = tonumber(settings["scr.h." .. monitor.address]) or 25
|
||||
local d = tonumber(settings["scr.d." .. monitor.address]) or 8
|
||||
w, h, d = math.floor(w), math.floor(h), math.floor(d)
|
||||
w, h, d = math.min(w, maxW), math.min(h, maxH), math.min(d, maxD)
|
||||
settings["scr.w." .. monitor.address] = tostring(w)
|
||||
settings["scr.h." .. monitor.address] = tostring(h)
|
||||
settings["scr.d." .. monitor.address] = tostring(d)
|
||||
gpu.setResolution(w, h)
|
||||
gpu.setDepth(d)
|
||||
pcall(saveSettings)
|
||||
end
|
||||
|
||||
donkonitSPProvider(function (pkg, pid, sendSig)
|
||||
targs[pid] = sendSig
|
||||
return {
|
||||
listSettings = function ()
|
||||
local s = {}
|
||||
for k, v in pairs(settings) do
|
||||
table.insert(s, k)
|
||||
end
|
||||
return s
|
||||
end,
|
||||
getSetting = function (name)
|
||||
if type(name) ~= "string" then error("Setting name must be string") end
|
||||
return settings[name]
|
||||
end,
|
||||
delSetting = function (name)
|
||||
if type(name) ~= "string" then error("Setting name must be string") end
|
||||
local val = nil
|
||||
if name == "password" then val = "" end
|
||||
settings[name] = val
|
||||
for _, v in pairs(targs) do
|
||||
v("set_setting", name, val)
|
||||
end
|
||||
pcall(saveSettings)
|
||||
end,
|
||||
setSetting = function (name, val)
|
||||
if type(name) ~= "string" then error("Setting name must be string") end
|
||||
if type(val) ~= "string" then error("Setting value must be string") end
|
||||
settings[name] = val
|
||||
for _, v in pairs(targs) do
|
||||
v("set_setting", name, val)
|
||||
end
|
||||
pcall(saveSettings)
|
||||
--saveSettings()
|
||||
end,
|
||||
registerForShutdownEvent = function ()
|
||||
targsSD[pid] = sendSig
|
||||
end,
|
||||
registerSavingThrow = function (st)
|
||||
if type(st) ~= "function" then error("Saving throw function must be a function") end
|
||||
targsST[pid] = st
|
||||
end,
|
||||
shutdown = function (reboot)
|
||||
if type(reboot) ~= "boolean" then error("Shutdown parameter must be a boolean (reboot)") end
|
||||
if shuttingDown then return end
|
||||
shuttingDown = true
|
||||
shutdownMode = reboot
|
||||
local counter = 0
|
||||
neo.scheduleTimer(os.uptime() + 5) -- in case the upcoming code fails in some way
|
||||
for f, v in pairs(targsSD) do
|
||||
counter = counter + 1
|
||||
v("shutdown", reboot, function ()
|
||||
counter = counter - 1
|
||||
if counter == 0 then
|
||||
shutdownFin(reboot)
|
||||
end
|
||||
end)
|
||||
end
|
||||
if counter == 0 then
|
||||
shutdownFin(reboot)
|
||||
end
|
||||
-- donkonit will shutdown when the timer is hit.
|
||||
end
|
||||
}
|
||||
end)
|
||||
|
||||
donkonitRDProvider(function (pkg, pid, sendSig)
|
||||
local claimed = {}
|
||||
targsRD[pid] = {sendSig, function ()
|
||||
for k, v in pairs(claimed) do
|
||||
-- Nothing to really do here
|
||||
monitorClaims[k] = nil
|
||||
announceFreeMonitor(k, pid)
|
||||
end
|
||||
end}
|
||||
return {
|
||||
getClaimable = function ()
|
||||
local c = {}
|
||||
-- do we have gpu?
|
||||
if not gpus.list()() then return c end
|
||||
for _, v in ipairs(monitorPool) do
|
||||
table.insert(c, v.address)
|
||||
end
|
||||
return c
|
||||
end,
|
||||
claim = function (address)
|
||||
if type(address) ~= "string" then error("Address must be string.") end
|
||||
for k, v in ipairs(monitorPool) do
|
||||
if v.address == address then
|
||||
local gpu = getGPU(v)
|
||||
if gpu then
|
||||
setupMonitor(gpu, v)
|
||||
gpu = gpu.address
|
||||
local disclaimer = function (wasDevLoss)
|
||||
-- we lost it
|
||||
monitorClaims[address] = nil
|
||||
claimed[address] = nil
|
||||
if not wasDevLoss then
|
||||
table.insert(monitorPool, v)
|
||||
announceFreeMonitor(address, pid)
|
||||
else
|
||||
sendSig("lost", address)
|
||||
end
|
||||
end
|
||||
claimed[address] = disclaimer
|
||||
monitorClaims[address] = {gpu, disclaimer}
|
||||
table.remove(monitorPool, k)
|
||||
return function ()
|
||||
for v in gpus.list() do
|
||||
if v.address == gpu then
|
||||
local _, v2 = v.bind(address)
|
||||
if not v2 then
|
||||
return v
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
disclaim = function (address)
|
||||
if not address then error("Cannot disclaim nothing.") end
|
||||
if claimed[address] then
|
||||
claimed[address](false)
|
||||
end
|
||||
end
|
||||
}
|
||||
end)
|
||||
|
||||
-- -- The actual initialization
|
||||
loadSettings()
|
||||
local function rescanDevs()
|
||||
monitorPool = {}
|
||||
local hasGPU = gpus.list()()
|
||||
for m in screens.list() do
|
||||
if monitorClaims[m.address] then
|
||||
monitorClaims[m.address][2](true)
|
||||
end
|
||||
table.insert(monitorPool, m)
|
||||
if hasGPU then
|
||||
announceFreeMonitor(m.address)
|
||||
end
|
||||
end
|
||||
end
|
||||
rescanDevs()
|
||||
|
||||
-- Save any settings made during the above (or just the language)
|
||||
saveSettings()
|
||||
-- --
|
||||
|
||||
while true do
|
||||
local s = {coroutine.yield()}
|
||||
if s[1] == "k.timer" then
|
||||
-- always shutdown
|
||||
shutdownFin(shutdownMode)
|
||||
end
|
||||
if s[1] == "h.component_added" then
|
||||
rescanDevs()
|
||||
end
|
||||
if s[1] == "h.component_removed" then
|
||||
rescanDevs()
|
||||
end
|
||||
if s[1] == "k.procdie" then
|
||||
targs[s[3]] = nil
|
||||
targsSD[s[3]] = nil
|
||||
if targsST[s[3]] then
|
||||
if s[4] then
|
||||
pcall(targsST[s[3]])
|
||||
end
|
||||
end
|
||||
targsST[s[3]] = nil
|
||||
if targsRD[s[3]] then
|
||||
targsRD[s[3]][2]()
|
||||
end
|
||||
end
|
||||
end
|
639
code/apps/sys-everest.lua
Normal file
639
code/apps/sys-everest.lua
Normal file
@ -0,0 +1,639 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- s-everest
|
||||
|
||||
-- Terminology:
|
||||
-- "monitor": Either the Null Virtual Monitor[0] (a safetynet),
|
||||
-- or an actual GPU/Screen pair managed by donkonit.
|
||||
-- "surface": Everest system-level drawing primitive
|
||||
-- "window" : Everest user-level wrapper around a surface providing a reliable window frame, movement, etc.
|
||||
-- "line" : A Wx1 area across a surface.
|
||||
-- "span" : A ?x1 area across a surface with text and a single fg/bg colour.
|
||||
-- This has less user calls as opposed to the old KittenOS system, which had a high CPU usage.
|
||||
-- Another thing to note is that Everest still uses callbacks, both for efficiency and library convenience,
|
||||
-- though with automatically closing windows on process death.
|
||||
|
||||
-- How Bristol talks to this is:
|
||||
-- 1. Bristol starts up Everest. Everest does not claim new monitors by default.
|
||||
-- 2. Bristol claims all available monitors to blank out the display
|
||||
-- 3. The user logs in
|
||||
-- 4. Bristol runs "startSession", enabling claiming of free monitors, and then promptly dies.
|
||||
-- 5. Everest claims the new monitors, and the desktop session begins
|
||||
-- 6. Everest dies/respawns, or endSession is called - in both cases,
|
||||
-- Everest is now essentially back at the state in 1.
|
||||
-- 7. Either this is Bristol, so go to 2,
|
||||
-- or this is a screensaver host, and has a saving-throw to start Bristol if it dies unexpectedly.
|
||||
-- In any case, this eventually returns to 2 or 4.
|
||||
|
||||
local everestProvider = neo.requestAccess("r.neo.pub.window")
|
||||
if not everestProvider then return end
|
||||
|
||||
local everestSessionProvider = neo.requestAccess("r.neo.sys.session")
|
||||
if not everestSessionProvider then return end
|
||||
|
||||
-- Got mutexes. Now setup saving throw and shutdown callback
|
||||
-- Something to note is that Donkonit is the safety net on this,
|
||||
-- as it auto-releases the monitors.
|
||||
local screens = neo.requestAccess("x.neo.sys.screens")
|
||||
if not screens then
|
||||
error("Donkonit is required to run Everest")
|
||||
end
|
||||
|
||||
neo.requestAccess("s.h.clipboard")
|
||||
neo.requestAccess("s.h.touch")
|
||||
neo.requestAccess("s.h.drag")
|
||||
neo.requestAccess("s.h.key_up")
|
||||
neo.requestAccess("s.h.key_down")
|
||||
|
||||
-- {gpu, screenAddr, w, h, bg, fg}
|
||||
local monitors = {}
|
||||
|
||||
-- NULL VIRTUAL MONITOR!
|
||||
-- This is where we stuff processes while Bristol isn't online
|
||||
monitors[0] = {nil, nil, 80, 25}
|
||||
|
||||
-- {monitor, x, y, w, h, callback}
|
||||
-- callback events are:
|
||||
-- key ka kc down
|
||||
-- line y
|
||||
local surfaces = {}
|
||||
|
||||
-- Dead process's switch to clean up resources by crashing processes which "just don't get it"
|
||||
local dead = false
|
||||
|
||||
local savingThrow = neo.requestAccess("x.neo.sys.manage")
|
||||
if savingThrow then
|
||||
savingThrow.registerForShutdownEvent()
|
||||
savingThrow.registerSavingThrow(function ()
|
||||
if #monitors > 0 then
|
||||
neo.executeAsync("sys-init", monitors[1][2])
|
||||
end
|
||||
neo.executeAsync("sys-everest")
|
||||
-- In this case the surfaces are leaked and hold references here. They have to be removed manually.
|
||||
-- Do this via a "primary event" (k.deregistration) and "deathtrap events"
|
||||
-- If a process evades the deathtrap then it clearly has reason to stay alive regardless of Everest status.
|
||||
dead = true
|
||||
monitors = {}
|
||||
for _, v in ipairs(surfaces) do
|
||||
pcall(v[6], "line", 1)
|
||||
pcall(v[6], "line", 2)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Grab all available monitors when they become available
|
||||
local inSession = false
|
||||
|
||||
local function renderingAllowed()
|
||||
-- This is a safety feature to prevent implosion due to missing monitors.
|
||||
return #monitors > 0
|
||||
end
|
||||
|
||||
local function surfaceAt(monitor, x, y)
|
||||
for k, v in ipairs(surfaces) do
|
||||
if v[1] == monitor then
|
||||
if x >= v[2] then
|
||||
if y >= v[3] then
|
||||
if x < (v[2] + v[4]) then
|
||||
if y < (v[3] + v[5]) then
|
||||
return k, (x - v[2]) + 1, (y - v[3]) + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function monitorGPUColours(m, cb, bg, fg)
|
||||
local nbg = m[5]
|
||||
local nfg = m[6]
|
||||
if nbg ~= bg then
|
||||
cb.setBackground(bg)
|
||||
m[5] = bg
|
||||
end
|
||||
if nfg ~= fg then
|
||||
cb.setForeground(fg)
|
||||
m[6] = fg
|
||||
end
|
||||
end
|
||||
|
||||
local function updateRegion(monitorId, x, y, w, h, surfaceSpanCache)
|
||||
if not renderingAllowed() then return end
|
||||
local m = monitors[monitorId]
|
||||
local mg = m[1]()
|
||||
if not mg then return end
|
||||
-- The input region is the one that makes SENSE.
|
||||
-- Considering WC handling, that's not an option.
|
||||
-- WCHAX: start
|
||||
if x > 1 then
|
||||
x = x - 1
|
||||
w = w + 1
|
||||
end
|
||||
-- this, in combination with 'forcefully blank out last column of window during render',
|
||||
-- cleans up littering
|
||||
w = w + 1
|
||||
-- end
|
||||
|
||||
for span = 1, h do
|
||||
local backgroundMarkStart = nil
|
||||
for sx = 1, w do
|
||||
local t, tx, ty = surfaceAt(monitorId, sx + x - 1, span + y - 1)
|
||||
if t then
|
||||
-- It has to be in this order to get rid of wide char weirdness
|
||||
if backgroundMarkStart then
|
||||
monitorGPUColours(m, mg, 0x000020, 0)
|
||||
mg.fill(backgroundMarkStart + x - 1, span + y - 1, sx - backgroundMarkStart, 1, " ")
|
||||
backgroundMarkStart = nil
|
||||
end
|
||||
if not surfaceSpanCache[t .. "_" .. ty] then
|
||||
surfaceSpanCache[t .. "_" .. ty] = true
|
||||
surfaces[t][6]("line", ty)
|
||||
end
|
||||
elseif not backgroundMarkStart then
|
||||
backgroundMarkStart = sx
|
||||
end
|
||||
end
|
||||
if backgroundMarkStart then
|
||||
local m = monitors[monitorId]
|
||||
monitorGPUColours(m, mg, 0x000020, 0)
|
||||
mg.fill(backgroundMarkStart + x - 1, span + y - 1, (w - backgroundMarkStart) + 1, 1, " ")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function ensureOnscreen(monitor, x, y, w, h)
|
||||
if monitor <= 0 then monitor = #monitors end
|
||||
if monitor >= (#monitors + 1) then monitor = 1 end
|
||||
-- Failing anything else, revert to monitor 0
|
||||
if #monitors == 0 then monitor = 0 end
|
||||
x = math.min(math.max(1, x), monitors[monitor][3] - (w - 1))
|
||||
y = math.min(math.max(1, y), monitors[monitor][4] - (h - 1))
|
||||
return monitor, x, y
|
||||
end
|
||||
|
||||
-- This is the "a state change occurred" function, only for use when needed
|
||||
local function reconcileAll()
|
||||
for k, v in ipairs(surfaces) do
|
||||
-- About to update whole screen anyway so avoid the wait.
|
||||
v[1], v[2], v[3] = ensureOnscreen(v[1], v[2], v[3], v[4], v[5])
|
||||
end
|
||||
for k, v in ipairs(monitors) do
|
||||
local mon = v[1]()
|
||||
if mon then
|
||||
v[3], v[4] = mon.getResolution()
|
||||
end
|
||||
v[5] = -1
|
||||
v[6] = -1
|
||||
updateRegion(k, 1, 1, v[3], v[4], {})
|
||||
end
|
||||
end
|
||||
|
||||
local function moveSurface(surface, m, x, y, w, h)
|
||||
local om, ox, oy, ow, oh = table.unpack(surface, 1, 5)
|
||||
m = m or om
|
||||
x = x or ox
|
||||
y = y or oy
|
||||
w = w or ow
|
||||
h = h or oh
|
||||
surface[1], surface[2], surface[3], surface[4], surface[5] = m, x, y, w, h
|
||||
local cache = {}
|
||||
if om == m then
|
||||
if ow == w then
|
||||
if oh == h then
|
||||
-- Cheat - perform a GPU copy
|
||||
-- this increases "apparent" performance while we're inevitably waiting for the app to catch up,
|
||||
-- CANNOT glitch since we're going to draw over this later,
|
||||
-- and will usually work since the user can only move focused surfaces
|
||||
if renderingAllowed() then
|
||||
local cb = monitors[m][1]()
|
||||
if cb then
|
||||
cb.copy(ox, oy, w, h, x - ox, y - oy)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
updateRegion(om, ox, oy, ow, oh, cache)
|
||||
updateRegion(m, x, y, w, h, cache)
|
||||
end
|
||||
-- Returns offset from where we expected to be to where we are.
|
||||
local function ofsSurface(focus, dx, dy)
|
||||
local exX, exY = focus[2] + dx, focus[3] + dy
|
||||
local m, x, y = ensureOnscreen(focus[1], exX, exY, focus[4], focus[5])
|
||||
moveSurface(focus, nil, x, y)
|
||||
return focus[2] - exX, focus[3] - exY
|
||||
end
|
||||
local function ofsMSurface(focus, dm)
|
||||
local m, x, y = ensureOnscreen(focus[1] + dm, focus[2], focus[3], focus[4], focus[5])
|
||||
moveSurface(focus, m, x, y)
|
||||
end
|
||||
|
||||
local function handleSpan(target, x, y, text, bg, fg)
|
||||
if not renderingAllowed() then return end
|
||||
local m = monitors[target[1]]
|
||||
local cb = m[1]()
|
||||
if not cb then return end
|
||||
-- It is assumed basic type checks were handled earlier.
|
||||
if y < 1 then return end
|
||||
if y > target[5] then return end
|
||||
if x < 1 then return end
|
||||
-- Note the use of unicode.len here.
|
||||
-- It's assumed that if the app is using Unicode text, then it used safeTextFormat earlier.
|
||||
-- This works for a consistent safety check.
|
||||
local w = unicode.len(text)
|
||||
if ((x + w) - 1) > target[4] then return end
|
||||
-- Checks complete, now commence screen cropping...
|
||||
local worldY = ((y + target[3]) - 1)
|
||||
if worldY < 1 then return end
|
||||
if worldY > monitors[target[1]][4] then return end
|
||||
-- The actual draw loop
|
||||
local buildingSegmentWX = nil
|
||||
local buildingSegmentWY = nil
|
||||
local buildingSegment = nil
|
||||
local buildingSegmentE = nil
|
||||
local function submitSegment()
|
||||
if buildingSegment then
|
||||
base = unicode.sub(text, buildingSegment, buildingSegmentE - 1)
|
||||
local ext = unicode.sub(text, buildingSegmentE, buildingSegmentE)
|
||||
if unicode.charWidth(ext) == 1 then
|
||||
base = base .. ext
|
||||
else
|
||||
-- While the GPU may or may not be able to display "half a character",
|
||||
-- getting it to do so reliably is another matter.
|
||||
-- In my experience it always leads to drawing errors much worse than if the code was left alone.
|
||||
-- If your language uses wide chars and you are affected by a window's positioning...
|
||||
-- ... may I ask how, exactly, you intend me to fix it?
|
||||
-- My current theory is that for cases where the segment is >= 2 chars (so we have scratchpad),
|
||||
-- the GPU might be tricked via a copy.
|
||||
-- Then the rest of the draw can proceed as normal,
|
||||
-- with the offending char removed.
|
||||
base = base .. " "
|
||||
end
|
||||
monitorGPUColours(m, cb, bg, fg)
|
||||
cb.set(buildingSegmentWX, buildingSegmentWY, unicode.undoSafeTextFormat(base))
|
||||
buildingSegment = nil
|
||||
end
|
||||
end
|
||||
for i = 1, w do
|
||||
local rWX = (i - 1) + (x - 1) + target[2]
|
||||
local rWY = (y - 1) + target[3]
|
||||
local s = surfaceAt(target[1], rWX, rWY)
|
||||
local ok = false
|
||||
if s then
|
||||
ok = surfaces[s] == target
|
||||
end
|
||||
if ok then
|
||||
if not buildingSegment then
|
||||
buildingSegmentWX = rWX
|
||||
buildingSegmentWY = rWY
|
||||
buildingSegment = i
|
||||
end
|
||||
buildingSegmentE = i
|
||||
else
|
||||
submitSegment()
|
||||
end
|
||||
end
|
||||
submitSegment()
|
||||
end
|
||||
|
||||
local function changeFocus(oldSurface, optcache)
|
||||
local ns1 = surfaces[1]
|
||||
optcache = optcache or {}
|
||||
if ns1 ~= oldSurface then
|
||||
if oldSurface then
|
||||
oldSurface[6]("focus", false)
|
||||
end
|
||||
if ns1 then
|
||||
ns1[6]("focus", true)
|
||||
end
|
||||
if oldSurface then
|
||||
updateRegion(oldSurface[1], oldSurface[2], oldSurface[3], oldSurface[4], oldSurface[5], optcache)
|
||||
end
|
||||
if ns1 then
|
||||
updateRegion(ns1[1], ns1[2], ns1[3], ns1[4], ns1[5], optcache)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- THE EVEREST USER API BEGINS
|
||||
local surfaceOwners = {}
|
||||
|
||||
-- Not relevant here really, but has to be up here because it closes the window
|
||||
local waitingShutdownCallback = nil
|
||||
local function checkWSC()
|
||||
if waitingShutdownCallback then
|
||||
if #surfaces == 0 then
|
||||
waitingShutdownCallback()
|
||||
waitingShutdownCallback = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
everestProvider(function (pkg, pid, sendSig)
|
||||
local base = pkg .. "/" .. pid
|
||||
local lid = 0
|
||||
return function (w, h, title)
|
||||
if dead then error("everest died") end
|
||||
w = math.floor(math.max(w, 8))
|
||||
h = math.floor(math.max(h, 1)) + 1
|
||||
if type(title) ~= "string" then
|
||||
title = base
|
||||
else
|
||||
title = base .. ":" .. title
|
||||
end
|
||||
local m = 0
|
||||
if renderingAllowed() then m = 1 end
|
||||
local surf = {m, 1, 1, w, h}
|
||||
local focusState = false
|
||||
local llid = lid
|
||||
lid = lid + 1
|
||||
local specialDragHandler
|
||||
surf[6] = function (ev, a, b, c)
|
||||
-- Must forward surface events
|
||||
if ev == "focus" then
|
||||
focusState = a
|
||||
end
|
||||
if ev == "touch" then
|
||||
specialDragHandler = nil
|
||||
if math.floor(b) == 1 then
|
||||
specialDragHandler = function (x, y)
|
||||
local ofsX, ofsY = math.floor(x) - math.floor(a), math.floor(y) - math.floor(b)
|
||||
if (ofsX == 0) and (ofsY == 0) then return end
|
||||
local pX, pY = ofsSurface(surf, ofsX, ofsY)
|
||||
--a = a + pX
|
||||
--b = b + pY
|
||||
end
|
||||
return
|
||||
end
|
||||
b = b - 1
|
||||
end
|
||||
if ev == "drag" then
|
||||
if specialDragHandler then
|
||||
specialDragHandler(a, b)
|
||||
return
|
||||
end
|
||||
b = b - 1
|
||||
end
|
||||
if ev == "line" then
|
||||
if a == 1 then
|
||||
local lw = surf[4]
|
||||
local bg = 0x0080FF
|
||||
local fg = 0x000000
|
||||
local tx = "-"
|
||||
if focusState then
|
||||
bg = 0x000000
|
||||
fg = 0x0080FF
|
||||
tx = "+"
|
||||
end
|
||||
local vtitle = title
|
||||
local vto = unicode.len(vtitle)
|
||||
if vto < lw then
|
||||
vtitle = vtitle .. (tx):rep(lw - vto)
|
||||
else
|
||||
vtitle = unicode.sub(vtitle, 1, lw)
|
||||
end
|
||||
handleSpan(surf, 1, 1, vtitle, bg, fg)
|
||||
return
|
||||
end
|
||||
-- WCHAX : Wide-char-cleanup has to be done left-to-right, so this handles the important part of that.
|
||||
handleSpan(surf, surf[4], a, " ", 0, 0)
|
||||
a = a - 1
|
||||
end
|
||||
sendSig(llid, ev, a, b, c)
|
||||
end
|
||||
local osrf = surfaces[1]
|
||||
table.insert(surfaces, 1, surf)
|
||||
surfaceOwners[surf] = pid
|
||||
changeFocus(osrf)
|
||||
return {
|
||||
id = llid,
|
||||
setSize = function (w, h)
|
||||
if dead then return end
|
||||
w = math.floor(math.max(w, 8))
|
||||
h = math.floor(math.max(h, 1)) + 1
|
||||
local _, x, y = ensureOnscreen(surf[1], surf[2], surf[3], w, h)
|
||||
moveSurface(surf, nil, x, y, w, h)
|
||||
return w, (h - 1)
|
||||
end,
|
||||
span = function (x, y, text, bg, fg)
|
||||
if dead then error("everest died") end
|
||||
if type(x) ~= "number" then error("X must be number.") end
|
||||
if type(y) ~= "number" then error("Y must be number.") end
|
||||
if type(bg) ~= "number" then error("Background must be number.") end
|
||||
if type(fg) ~= "number" then error("Foreground must be number.") end
|
||||
if type(text) ~= "string" then error("Text must be string.") end
|
||||
x, y, bg, fg = math.floor(x), math.floor(y), math.floor(bg), math.floor(fg)
|
||||
if y == 0 then return end
|
||||
handleSpan(surf, x, y + 1, text, bg, fg)
|
||||
end,
|
||||
close = function ()
|
||||
if dead then return end
|
||||
local os1 = surfaces[1]
|
||||
surfaceOwners[surf] = nil
|
||||
for k, v in ipairs(surfaces) do
|
||||
if v == surf then
|
||||
table.remove(surfaces, k)
|
||||
local cache = {}
|
||||
checkWSC()
|
||||
changeFocus(os1, cache)
|
||||
-- focus up to date, deal with any remains
|
||||
updateRegion(surf[1], surf[2], surf[3], surf[4], surf[5], cache)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
end)
|
||||
-- THE EVEREST USER API ENDS (now for the session API, which just does boring stuff)
|
||||
everestSessionProvider(function (pkg, pid, sendSig)
|
||||
return {
|
||||
startSession = function ()
|
||||
inSession = true
|
||||
end,
|
||||
endSession = function (startBristol)
|
||||
if not inSession then return end
|
||||
local m = nil
|
||||
if monitors[1] then
|
||||
m = monitors[1][2]
|
||||
end
|
||||
inSession = false
|
||||
for k = 1, #monitors do
|
||||
screens.disclaim(monitors[k][2])
|
||||
monitors[k] = nil
|
||||
end
|
||||
if startBristol then
|
||||
neo.executeAsync("sys-init", m)
|
||||
end
|
||||
reconcileAll()
|
||||
if not startBristol then
|
||||
return m
|
||||
end
|
||||
end
|
||||
}
|
||||
end)
|
||||
-- THE EVEREST SESSION API ENDS
|
||||
|
||||
-- WM shortcuts are:
|
||||
-- Alt-Z: Switch surface
|
||||
-- Alt-Enter: Launcher
|
||||
-- Alt-Up/Down/Left/Right: Move surface
|
||||
local isAltDown = false
|
||||
local isCtrDown = false
|
||||
local function key(ka, kc, down)
|
||||
local focus = surfaces[1]
|
||||
if kc == 29 then isCtrDown = down end
|
||||
if kc == 56 then isAltDown = down end
|
||||
if isAltDown then
|
||||
if ka == 120 then
|
||||
if focus and down then ofsMSurface(focus, 1) end return
|
||||
end
|
||||
if kc == 200 then
|
||||
if focus and down then ofsSurface(focus, 0, -1) end return
|
||||
end
|
||||
if kc == 208 then
|
||||
if focus and down then ofsSurface(focus, 0, 1) end return
|
||||
end
|
||||
if kc == 203 then
|
||||
if focus and down then ofsSurface(focus, -1, 0) end return
|
||||
end
|
||||
if kc == 205 then
|
||||
if focus and down then ofsSurface(focus, 1, 0) end return
|
||||
end
|
||||
if ka == 122 then
|
||||
if focus and down then
|
||||
local n = table.remove(surfaces, 1)
|
||||
table.insert(surfaces, n)
|
||||
changeFocus(n)
|
||||
end return
|
||||
end
|
||||
if ka == 3 then
|
||||
-- Ctrl-Alt-C (!?!?!!)
|
||||
if isCtrDown then
|
||||
error("User-authorized Everest crash.")
|
||||
end
|
||||
end
|
||||
if ka == 99 then
|
||||
if down then
|
||||
if isCtrDown then
|
||||
error("User-authorized Everest crash.")
|
||||
else
|
||||
if focus then
|
||||
focus[6]("close")
|
||||
end
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
if ka == 13 then
|
||||
if down and (not waitingShutdownCallback) then neo.executeAsync("app-launcher") end return
|
||||
end
|
||||
end
|
||||
if focus then
|
||||
focus[6]("key", ka, kc, down)
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
local s = {coroutine.yield()}
|
||||
if renderingAllowed() then
|
||||
if s[1] == "h.key_down" then
|
||||
key(s[3], s[4], true)
|
||||
end
|
||||
if s[1] == "h.key_up" then
|
||||
key(s[3], s[4], false)
|
||||
end
|
||||
if s[1] == "h.clipboard" then
|
||||
if surfaces[1] then
|
||||
surfaces[1][6]("clipboard", s[3])
|
||||
end
|
||||
end
|
||||
-- next on my list: high-res coordinates
|
||||
if s[1] == "h.touch" then
|
||||
for k, v in ipairs(monitors) do
|
||||
if v[2] == s[2] then
|
||||
local x, y = math.floor(s[3]), math.floor(s[4])
|
||||
local sid, lx, ly = surfaceAt(k, x, y)
|
||||
if sid then
|
||||
local os = surfaces[1]
|
||||
local ns = table.remove(surfaces, sid)
|
||||
table.insert(surfaces, 1, ns)
|
||||
changeFocus(os)
|
||||
ns[6]("touch", lx, ly)
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if s[1] == "h.drag" then
|
||||
-- Pass to focus surface, even if out of bounds
|
||||
local focus = surfaces[1]
|
||||
if focus then
|
||||
for k, v in ipairs(monitors) do
|
||||
if v[2] == s[2] then
|
||||
if k == focus[1] then
|
||||
local x, y = (math.floor(s[3]) - focus[2]) + 1, (math.floor(s[4]) - focus[3]) + 1
|
||||
focus[6]("drag", x, y)
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
isCtrDown = false
|
||||
isAltDown = false
|
||||
end
|
||||
if s[1] == "k.procdie" then
|
||||
local os1 = surfaces[1]
|
||||
-- Note this is in order (that's important)
|
||||
local tags = {}
|
||||
for k, v in ipairs(surfaces) do
|
||||
if surfaceOwners[v] == s[3] then
|
||||
table.insert(tags, k)
|
||||
surfaceOwners[v] = nil
|
||||
end
|
||||
end
|
||||
for k, v in ipairs(tags) do
|
||||
local surf = table.remove(surfaces, v - (k - 1))
|
||||
updateRegion(surf[1], surf[2], surf[3], surf[4], surf[5], {})
|
||||
end
|
||||
checkWSC()
|
||||
changeFocus(os1)
|
||||
end
|
||||
if s[1] == "x.neo.sys.screens" then
|
||||
if s[2] == "available" then
|
||||
if inSession then
|
||||
local gpu = screens.claim(s[3])
|
||||
local gpucb = gpu and (gpu())
|
||||
if gpucb then
|
||||
local w, h = gpucb.getResolution()
|
||||
table.insert(monitors, {gpu, s[3], w, h, -1, -1})
|
||||
-- This is required to ensure windows are moved off of the null monitor.
|
||||
-- Luckily, there's an obvious sign if they aren't - everest will promptly crash.
|
||||
reconcileAll()
|
||||
end
|
||||
end
|
||||
end
|
||||
if s[2] == "lost" then
|
||||
for k, v in ipairs(monitors) do
|
||||
if v[2] == s[3] then
|
||||
table.remove(monitors, k)
|
||||
reconcileAll()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if s[1] == "x.neo.sys.manage" then
|
||||
if s[2] == "shutdown" then
|
||||
waitingShutdownCallback = s[4]
|
||||
for k, v in ipairs(surfaces) do
|
||||
v[6]("close")
|
||||
end
|
||||
checkWSC()
|
||||
end
|
||||
end
|
||||
end
|
137
code/apps/sys-icecap.lua
Normal file
137
code/apps/sys-icecap.lua
Normal file
@ -0,0 +1,137 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- s-icecap : Responsible for x.neo.pub API, crash dialogs, and security policy that isn't "sys- has ALL access, anything else has none"
|
||||
-- In general, this is what userspace will be interacting with in some way or another to get stuff done
|
||||
|
||||
local event = require("event")(neo)
|
||||
local neoux, err = require("neoux")
|
||||
if not neoux then error(err) end -- This app is basically neoux's testcase
|
||||
neoux = neoux(event, neo)
|
||||
|
||||
local donkonit = neo.requestAccess("x.neo.sys.manage")
|
||||
if not donkonit then error("needs donkonit for sysconf access") end
|
||||
local scr = neo.requestAccess("s.k.securityrequest")
|
||||
if not scr then error("no security request access") end
|
||||
|
||||
local fs = neo.requestAccess("c.filesystem")
|
||||
|
||||
local donkonitDFProvider = neo.requestAccess("r.neo.pub.base")
|
||||
if not donkonitDFProvider then return end
|
||||
|
||||
local targsDH = {} -- data disposal
|
||||
|
||||
donkonitDFProvider(function (pkg, pid, sendSig)
|
||||
local prefixNS = "data/" .. pkg
|
||||
local prefixWS = "data/" .. pkg .. "/"
|
||||
fs.primary.makeDirectory(prefixNS)
|
||||
local openHandles = {}
|
||||
targsDH[pid] = function ()
|
||||
for k, v in pairs(openHandles) do
|
||||
v()
|
||||
end
|
||||
end
|
||||
return {
|
||||
showFileDialogAsync = function (forWrite)
|
||||
-- Not hooked into the event API, so can't safely interfere
|
||||
-- Thus, this is async and uses a return event.
|
||||
local tag = {}
|
||||
event.runAt(0, function ()
|
||||
-- sys-filedialog is yet another "library to control memory usage".
|
||||
local closer = require("sys-filedialog")(event, neoux, function (res) openHandles[tag] = nil sendSig("filedialog", tag, res) end, fs, pkg, forWrite)
|
||||
openHandles[tag] = closer
|
||||
end)
|
||||
return tag
|
||||
end,
|
||||
-- Paths must begin with / implicitly
|
||||
list = function (path)
|
||||
if type(path) ~= "string" then error("Expected path to be string") end
|
||||
path = prefixNS .. path
|
||||
neo.ensurePath(path, prefixWS)
|
||||
if path:sub(#path, #path) ~= "/" then error("Expected / at end") end
|
||||
return fs.primary.list(path:sub(1, #path - 1))
|
||||
end,
|
||||
makeDirectory = function (path)
|
||||
if type(path) ~= "string" then error("Expected path to be string") end
|
||||
path = prefixNS .. path
|
||||
neo.ensurePath(path, prefixWS)
|
||||
if path:sub(#path, #path) == "/" then error("Expected no / at end") end
|
||||
return fs.primary.makeDirectory(path)
|
||||
end,
|
||||
rename = function (path1, path2)
|
||||
if type(path1) ~= "string" then error("Expected path to be string") end
|
||||
if type(path2) ~= "string" then error("Expected path to be string") end
|
||||
path1 = prefixNS .. path1
|
||||
path2 = prefixNS .. path2
|
||||
neo.ensurePath(path1, prefixWS)
|
||||
neo.ensurePath(path2, prefixWS)
|
||||
if path:sub(#path1, #path1) == "/" then error("Expected no / at end") end
|
||||
if path:sub(#path2, #path2) == "/" then error("Expected no / at end") end
|
||||
return fs.primary.rename(path1, path2)
|
||||
end,
|
||||
open = function (path, mode)
|
||||
if type(path) ~= "string" then error("Expected path to be string") end
|
||||
if type(mode) ~= "boolean" then error("Expected mode to be boolean (writing)") end
|
||||
path = prefixNS .. path
|
||||
neo.ensurePath(path, prefixWS)
|
||||
if path:sub(#path, #path) == "/" then error("Expected no / at end") end
|
||||
local fw, closer = require("sys-filewrap")(fs.primary, path, mode)
|
||||
local oc = fw.close
|
||||
fw.close = function ()
|
||||
oc()
|
||||
openHandles[fw] = nil
|
||||
end
|
||||
openHandles[fw] = closer
|
||||
return fw
|
||||
end,
|
||||
remove = function (path)
|
||||
if type(path) ~= "string" then error("Expected path to be string") end
|
||||
path = prefixNS .. path
|
||||
neo.ensurePath(path, prefixWS)
|
||||
if path:sub(#path, #path) == "/" then error("Expected no / at end") end
|
||||
return fs.primary.remove(path)
|
||||
end,
|
||||
stat = function (path)
|
||||
if type(path) ~= "string" then error("Expected path to be string") end
|
||||
path = prefixNS .. path
|
||||
neo.ensurePath(path, prefixWS)
|
||||
if path:sub(#path, #path) == "/" then error("Expected no / at end") end
|
||||
if not fs.primary.exists(path) then return nil end
|
||||
return {
|
||||
fs.primary.isDirectory(path),
|
||||
fs.primary.size(path),
|
||||
fs.primary.lastModified(path)
|
||||
}
|
||||
end,
|
||||
-- getLabel/setLabel have nothing to do with this
|
||||
spaceUsed = fs.primary.spaceUsed,
|
||||
spaceTotal = fs.primary.spaceTotal,
|
||||
isReadOnly = fs.primary.isReadOnly
|
||||
}
|
||||
end)
|
||||
|
||||
event.listen("k.securityrequest", function (evt, pkg, pid, perm, rsp)
|
||||
local secpol, err = require("sys-secpolicy")
|
||||
if not secpol then
|
||||
rsp(false)
|
||||
error("Bad security policy: " .. err)
|
||||
end
|
||||
secpol(neoux, donkonit, pkg, pid, perm, rsp)
|
||||
end)
|
||||
|
||||
event.listen("k.procdie", function (evt, pkg, pid, reason)
|
||||
if targsDH[pid] then
|
||||
targsDH[pid]()
|
||||
end
|
||||
targsDH[pid] = nil
|
||||
if reason then
|
||||
-- Process death logging in console (for lifecycle dbg)
|
||||
-- neo.emergency(n[2])
|
||||
-- neo.emergency(n[4])
|
||||
neoux.startDialog(string.format("%s/%i died:\n%s", pkg, pid, reason), "error")
|
||||
end
|
||||
end)
|
||||
|
||||
while true do
|
||||
event.pull()
|
||||
end
|
417
code/apps/sys-init.lua
Normal file
417
code/apps/sys-init.lua
Normal file
@ -0,0 +1,417 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- s-bristol splash screen, login agent
|
||||
-- Named to allude to Plymouth (splash screen used in some linux distros)
|
||||
|
||||
local callerPkg, callerPid, callerScr = ...
|
||||
|
||||
local gpu, screen = nil, nil
|
||||
local shutdownEmergency = neo.requestAccess("k.computer").shutdown
|
||||
neo.requestAccess("s.h.key_down")
|
||||
|
||||
-- donkonit hasn't started yet - this will be dealt with later
|
||||
local function _(tx)
|
||||
return tx
|
||||
end
|
||||
|
||||
local scrW, scrH
|
||||
local warnings = {
|
||||
"",
|
||||
"",
|
||||
""
|
||||
}
|
||||
|
||||
-- Attempts to call upon nsm for a safe shutdown
|
||||
local function shutdown(reboot)
|
||||
local donkonit = neo.requestAccess("x.neo.sys.manage")
|
||||
if donkonit then
|
||||
donkonit.shutdown(reboot)
|
||||
while true do
|
||||
coroutine.yield()
|
||||
end
|
||||
else
|
||||
shutdownEmergency(reboot)
|
||||
end
|
||||
end
|
||||
|
||||
local function basicDraw()
|
||||
scrW, scrH = gpu.getResolution()
|
||||
gpu.fill(1, 1, scrW, scrH, " ")
|
||||
gpu.set(2, 2, _("KittenOS NEO"))
|
||||
end
|
||||
|
||||
local function advDraw()
|
||||
basicDraw()
|
||||
local usage = math.floor((os.totalMemory() - os.freeMemory()) / 1024)
|
||||
gpu.set(2, 3, _("RAM Usage: ") .. usage .. "K / " .. math.floor(os.totalMemory() / 1024) .. "K")
|
||||
for i = 1, #warnings do
|
||||
gpu.set(2, 6 + i, warnings[i])
|
||||
end
|
||||
end
|
||||
|
||||
-- Callback setup by finalPrompt to disclaim the main monitor first
|
||||
local performDisclaim = nil
|
||||
|
||||
local function retrieveNssMonitor(nss)
|
||||
gpu = nil
|
||||
local subpool = {}
|
||||
while not gpu do
|
||||
if performDisclaim then
|
||||
performDisclaim()
|
||||
end
|
||||
-- nss available - this means the monitor pool is now ready.
|
||||
-- If no monitors are available, shut down now.
|
||||
-- donkonit monitor pool output is smaller than, but similar to, Everest monitor data:
|
||||
-- {gpu, screenAddr}
|
||||
local pool = nss.getClaimable()
|
||||
while not pool[1] do
|
||||
coroutine.yield() -- wait for presumably a donkonit notification
|
||||
pool = nss.getClaimable()
|
||||
end
|
||||
subpool = {}
|
||||
-- Specifies which element to elevate to top priority
|
||||
local optimalSwap = nil
|
||||
if screen then
|
||||
for k, v in ipairs(pool) do
|
||||
if v == screen then
|
||||
optimalSwap = k
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if optimalSwap then
|
||||
local swapA = pool[optimalSwap]
|
||||
pool[optimalSwap] = pool[1]
|
||||
pool[1] = swapA
|
||||
end
|
||||
for _, v in ipairs(pool) do
|
||||
local gpu = nss.claim(v)
|
||||
if gpu then
|
||||
local gcb = gpu()
|
||||
if gcb then
|
||||
table.insert(subpool, {gpu, v})
|
||||
gcb.setBackground(0x000020)
|
||||
local w, h = gcb.getResolution()
|
||||
gcb.fill(1, 1, w, h, " ")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not subpool[1] then error(_("Unable to claim any monitor.")) end
|
||||
gpu = subpool[1][1]() -- BAD
|
||||
screen = subpool[1][2]
|
||||
end
|
||||
-- done with search
|
||||
scrW, scrH = gpu.getResolution()
|
||||
gpu.setBackground(0xFFFFFF)
|
||||
gpu.setForeground(0x000000)
|
||||
gpu.fill(1, 1, scrW, scrH, " ")
|
||||
performDisclaim = function ()
|
||||
for _, v in ipairs(subpool) do
|
||||
nss.disclaim(v[2])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function sleep(t)
|
||||
neo.scheduleTimer(os.uptime() + t)
|
||||
while true do
|
||||
local ev = {coroutine.yield()}
|
||||
if ev[1] == "k.timer" then
|
||||
break
|
||||
end
|
||||
if ev[1] == "x.neo.sys.screens" then
|
||||
-- This implies we have and can use nss, but check anyway
|
||||
local nss = neo.requestAccess("x.neo.sys.screens")
|
||||
if nss then
|
||||
retrieveNssMonitor(nss)
|
||||
gpu.setBackground(0xFFFFFF)
|
||||
gpu.setForeground(0x000000)
|
||||
basicDraw()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function finalPrompt()
|
||||
local nss = neo.requestAccess("x.neo.sys.screens")
|
||||
if nss then
|
||||
retrieveNssMonitor(nss)
|
||||
end
|
||||
-- This is nsm's final chance to make itself available and thus allow the password to be set
|
||||
local nsm = neo.requestAccess("x.neo.sys.manage")
|
||||
local waiting = true
|
||||
local safeModeActive = false
|
||||
local password = ""
|
||||
if nsm then
|
||||
password = nsm.getSetting("password")
|
||||
end
|
||||
warnings[1] = _("TAB to change option,")
|
||||
warnings[2] = _("ENTER to select...")
|
||||
-- The actual main prompt loop
|
||||
while waiting do
|
||||
advDraw()
|
||||
local entry = ""
|
||||
local entry2 = ""
|
||||
local active = true
|
||||
local shButton = _("<Shutdown>")
|
||||
local rbButton = _("<Reboot>")
|
||||
local smButton = _("<Safe Mode>")
|
||||
local pw = {function ()
|
||||
return _("Password: ") .. entry2
|
||||
end, function (key)
|
||||
if key >= 32 then
|
||||
entry = entry .. unicode.char(key)
|
||||
entry2 = entry2 .. "*"
|
||||
end
|
||||
if key == 13 then
|
||||
if entry == password then
|
||||
waiting = false
|
||||
else
|
||||
advDraw()
|
||||
sleep(1)
|
||||
end
|
||||
active = false
|
||||
end
|
||||
end, 2, 5, scrW - 2}
|
||||
if password == "" then
|
||||
pw = {function ()
|
||||
return "Log in..."
|
||||
end, function (key)
|
||||
if key == 13 then
|
||||
waiting = false
|
||||
active = false
|
||||
end
|
||||
end, 2, 5, scrW - 2}
|
||||
end
|
||||
local controls = {
|
||||
{function ()
|
||||
return shButton
|
||||
end, function (key)
|
||||
if key == 13 then
|
||||
basicDraw()
|
||||
gpu.set(2, 4, "Shutting down...")
|
||||
shutdown(false)
|
||||
end
|
||||
end, 2, scrH - 1, unicode.len(shButton)},
|
||||
{function ()
|
||||
return rbButton
|
||||
end, function (key)
|
||||
if key == 13 then
|
||||
basicDraw()
|
||||
gpu.set(2, 4, "Rebooting...")
|
||||
shutdown(true)
|
||||
end
|
||||
end, 3 + unicode.len(shButton), scrH - 1, unicode.len(rbButton)},
|
||||
{function ()
|
||||
return smButton
|
||||
end, function (key)
|
||||
if key == 13 then
|
||||
basicDraw()
|
||||
gpu.set(2, 4, "Login to activate Safe Mode.")
|
||||
sleep(1)
|
||||
safeModeActive = true
|
||||
advDraw()
|
||||
end
|
||||
end, 4 + unicode.len(shButton) + unicode.len(rbButton), scrH - 1, unicode.len(smButton)},
|
||||
pw,
|
||||
}
|
||||
local control = #controls
|
||||
while active do
|
||||
for k, v in ipairs(controls) do
|
||||
if k == control then
|
||||
gpu.setBackground(0x000000)
|
||||
gpu.setForeground(0xFFFFFF)
|
||||
else
|
||||
gpu.setBackground(0xFFFFFF)
|
||||
gpu.setForeground(0x000000)
|
||||
end
|
||||
gpu.fill(v[3], v[4], v[5], 1, " ")
|
||||
gpu.set(v[3], v[4], v[1]())
|
||||
end
|
||||
-- Reset to normal
|
||||
gpu.setBackground(0xFFFFFF)
|
||||
gpu.setForeground(0x000000)
|
||||
-- event handling...
|
||||
local sig = {coroutine.yield()}
|
||||
if sig[1] == "x.neo.sys.screens" then
|
||||
-- We need to reinit screens no matter what.
|
||||
retrieveNssMonitor(nss)
|
||||
end
|
||||
if sig[1] == "h.key_down" then
|
||||
if sig[4] == 15 then
|
||||
-- this makes sense in context
|
||||
control = control % (#controls)
|
||||
control = control + 1
|
||||
else
|
||||
controls[control][2](sig[3])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
advDraw()
|
||||
return safeModeActive
|
||||
end
|
||||
local function postPrompt()
|
||||
-- Begin to finish login, or fail
|
||||
local everests = neo.requestAccess("x.neo.sys.session")
|
||||
if everests then
|
||||
local s, e = pcall(everests.startSession)
|
||||
if not s then
|
||||
table.insert(warnings, "Everest failed to create a session")
|
||||
table.insert(warnings, tostring(e))
|
||||
else
|
||||
warnings = {"Transferring to Everest..."}
|
||||
advDraw()
|
||||
if performDisclaim then
|
||||
performDisclaim()
|
||||
-- Give Everest time (this isn't perceptible, and is really just a safety measure)
|
||||
sleep(1)
|
||||
end
|
||||
return
|
||||
end
|
||||
else
|
||||
table.insert(warnings, "Couldn't communicate with Everest...")
|
||||
end
|
||||
advDraw()
|
||||
sleep(1)
|
||||
shutdown(true)
|
||||
end
|
||||
|
||||
local function initializeSystem()
|
||||
-- System has just booted, bristol is in charge
|
||||
-- Firstly, initialize hardware to something sensible since we don't know scrcfg
|
||||
gpu = neo.requestAccess("c.gpu").list()()
|
||||
if gpu then
|
||||
screen = neo.requestAccess("c.screen").list()()
|
||||
if not screen then gpu = nil end
|
||||
end
|
||||
if gpu then
|
||||
screen = screen.address
|
||||
gpu.bind(screen, true)
|
||||
gpu.setDepth(gpu.maxDepth())
|
||||
local gW, gH = gpu.maxResolution()
|
||||
gW, gH = math.min(80, gW), math.min(25, gH)
|
||||
gpu.setResolution(gW, gH)
|
||||
gpu.setForeground(0x000000)
|
||||
end
|
||||
local w = 1
|
||||
local steps = {
|
||||
"sys-donkonit", -- (Donkonit : Config, Screen, Power)
|
||||
-- Let that start, and system GC
|
||||
"WAIT",
|
||||
"WAIT",
|
||||
"WAIT",
|
||||
"WAIT",
|
||||
-- Start services
|
||||
"INJECT",
|
||||
-- extra GC time
|
||||
"WAIT",
|
||||
"WAIT",
|
||||
"WAIT",
|
||||
"WAIT",
|
||||
"WAIT",
|
||||
"WAIT",
|
||||
"WAIT"
|
||||
}
|
||||
local stepCount = #steps
|
||||
|
||||
neo.scheduleTimer(os.uptime())
|
||||
while true do
|
||||
local ev = {coroutine.yield()}
|
||||
if ev[1] == "k.procnew" then
|
||||
table.insert(warnings, ev[2] .. "/" .. ev[3] .. " UP")
|
||||
end
|
||||
if ev[1] == "k.procdie" then
|
||||
table.insert(warnings, ev[2] .. "/" .. ev[3] .. " DOWN")
|
||||
table.insert(warnings, tostring(ev[4]))
|
||||
end
|
||||
if ev[1] == "k.timer" then
|
||||
if gpu then
|
||||
gpu.setForeground(0x000000)
|
||||
if w < stepCount then
|
||||
local n = math.floor((w / stepCount) * 255)
|
||||
gpu.setBackground((n * 0x10000) + (n * 0x100) + n)
|
||||
else
|
||||
gpu.setBackground(0xFFFFFF)
|
||||
end
|
||||
basicDraw()
|
||||
end
|
||||
if steps[w] then
|
||||
if steps[w] == "INJECT" then
|
||||
local donkonit = neo.requestAccess("x.neo.sys.manage")
|
||||
if not donkonit then
|
||||
table.insert(warnings, "Settings not available for INJECT.")
|
||||
else
|
||||
local nextstepsA = {}
|
||||
local nextstepsB = {}
|
||||
for _, v in ipairs(neo.listApps()) do
|
||||
if donkonit.getSetting("run." .. v) == "yes" then
|
||||
if v:sub(1, 4) == "sys-" then
|
||||
table.insert(nextstepsA, v)
|
||||
else
|
||||
table.insert(nextstepsB, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, v in ipairs(nextstepsB) do
|
||||
table.insert(steps, w + 1, v)
|
||||
end
|
||||
for _, v in ipairs(nextstepsA) do
|
||||
table.insert(steps, w + 1, v)
|
||||
end
|
||||
end
|
||||
elseif steps[w] == "WAIT" then
|
||||
else
|
||||
local v, err = neo.executeAsync(steps[w])
|
||||
if not v then
|
||||
table.insert(warnings, steps[w] .. " STF")
|
||||
table.insert(warnings, err)
|
||||
end
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
w = w + 1
|
||||
neo.scheduleTimer(os.uptime() + 0.049)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Actual sequence
|
||||
|
||||
if callerPkg ~= nil then
|
||||
-- Everest can call into this to force a login screen
|
||||
-- In this case it locks Everest, then starts Bristol.
|
||||
--
|
||||
if callerPkg ~= "sys-everest" then
|
||||
return
|
||||
end
|
||||
screen = callerScr
|
||||
-- Skip to "System initialized" (Everest either logged off, or died & used a Saving Throw to restart)
|
||||
else
|
||||
initializeSystem()
|
||||
end
|
||||
-- System initialized
|
||||
if finalPrompt() then
|
||||
-- Safe Mode
|
||||
basicDraw()
|
||||
local donkonit = neo.requestAccess("x.neo.sys.manage")
|
||||
if donkonit then
|
||||
gpu.set(2, 4, "Rebooting for Safe Mode...")
|
||||
for _, v in ipairs(donkonit.listSettings()) do
|
||||
if v ~= "password" then
|
||||
donkonit.delSetting(v)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- assume sysconf.lua did something very bad
|
||||
gpu.set(2, 4, "No Donkonit. Wiping configuration completely.")
|
||||
local fs = neo.requestAccess("c.filesystem")
|
||||
fs.primary.remove("/data/sys-donkonit/sysconf.lua")
|
||||
end
|
||||
-- Do not give anything a chance to alter the new configuration
|
||||
shutdownEmergency(true)
|
||||
return
|
||||
end
|
||||
postPrompt()
|
667
code/init.lua
Normal file
667
code/init.lua
Normal file
@ -0,0 +1,667 @@
|
||||
-- KittenOS N.E.O Kernel: "Tell Mettaton I said hi."
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- NOTE: If it's not "local", it's because k.root ought to access it.
|
||||
-- List of things that apply:
|
||||
-- primaryDisk, timers, libraries, processes, accesses, wrapMeta,
|
||||
-- distEvent, baseProcEnv, loadLibraryInner, retrieveAccess,
|
||||
-- start
|
||||
|
||||
-- Debugging option, turns process errors into actual errors (!)
|
||||
local criticalFailure = false
|
||||
-- In case of OpenComputers configuration abnormality
|
||||
local readBufSize = 2048
|
||||
|
||||
-- A function used for logging, usable by programs.
|
||||
-- Comment this out if you don't want programs to have
|
||||
-- access to ocemu's logger.
|
||||
local emergencyFunction
|
||||
local ocemu = component.list("ocemu", true)()
|
||||
if ocemu then
|
||||
ocemu = component.proxy(ocemu)
|
||||
emergencyFunction = ocemu.log
|
||||
end
|
||||
|
||||
primaryDisk = component.proxy(computer.getBootAddress())
|
||||
|
||||
timers = {}
|
||||
|
||||
libraries = {}
|
||||
setmetatable(libraries, {
|
||||
__mode = "v"
|
||||
})
|
||||
|
||||
-- proc.co = coroutine.create(appfunc)
|
||||
-- proc.pkg = "pkg"
|
||||
-- proc.access = {["perm"] = true, ...}
|
||||
-- proc.denied = {["perm"] = true, ...}
|
||||
-- proc.deathcbs = {function(), ...}
|
||||
-- very slightly adjusted total CPU time
|
||||
-- proc.cpuUsage
|
||||
processes = {}
|
||||
-- Maps registration-accesses to function(pkg, pid)
|
||||
accesses = {}
|
||||
local lastPID = 0
|
||||
|
||||
-- keys: <any>
|
||||
-- sr.waiting: keys are PIDs, values are just "true"
|
||||
-- sr.service: function()
|
||||
-- sr.result: boolean
|
||||
local outstandingSR = {}
|
||||
|
||||
-- Kernel global "idle time" counter, useful for accurate performance data
|
||||
local idleTime = 0
|
||||
|
||||
-- This function is critical to wide text support.
|
||||
function unicode.safeTextFormat(s, ptr)
|
||||
local res = ""
|
||||
if not ptr then ptr = 1 end
|
||||
local aptr = 1
|
||||
for i = 1, unicode.len(s) do
|
||||
local ch = unicode.sub(s, i, i)
|
||||
local ex = unicode.charWidth(ch)
|
||||
if i < ptr then
|
||||
aptr = aptr + ex
|
||||
end
|
||||
for j = 2, ex do
|
||||
ch = ch .. " "
|
||||
end
|
||||
res = res .. ch
|
||||
end
|
||||
return res, aptr
|
||||
end
|
||||
|
||||
-- The issue with the above function, of course, is that in practice the GPU is a weird mess.
|
||||
-- So this undoes the above transformation for feeding to gpu.set.
|
||||
-- (In practice if safeTextFormat supports RTL, and that's a big "if", then this will not undo that.
|
||||
-- The point is that this converts it into gpu.set format.)
|
||||
function unicode.undoSafeTextFormat(s)
|
||||
local res = ""
|
||||
local ignoreNext = false
|
||||
for i = 1, unicode.len(s) do
|
||||
if not ignoreNext then
|
||||
local ch = unicode.sub(s, i, i)
|
||||
ignoreNext = unicode.charWidth(ch) ~= 1
|
||||
res = res .. ch
|
||||
else
|
||||
ignoreNext = false
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function loadfile(s, e)
|
||||
local h = primaryDisk.open(s)
|
||||
if h then
|
||||
local ch = ""
|
||||
local c = primaryDisk.read(h, readBufSize)
|
||||
while c do
|
||||
ch = ch .. c
|
||||
c = primaryDisk.read(h, readBufSize)
|
||||
end
|
||||
primaryDisk.close(h)
|
||||
return load(ch, "=" .. s, "t", e)
|
||||
end
|
||||
return nil, "File Unreadable"
|
||||
end
|
||||
|
||||
local wrapMeta = nil
|
||||
local uniqueNEOProtectionObject = {}
|
||||
function wrapMeta(t)
|
||||
if type(t) == "table" then
|
||||
local t2 = {}
|
||||
setmetatable(t2, {
|
||||
__index = function (a, k) return wrapMeta(t[k]) end,
|
||||
__newindex = function (a, k, v) end,
|
||||
__pairs = function (a)
|
||||
return function (x, key)
|
||||
local k, v = next(t, k)
|
||||
if k then return k, wrapMeta(v) end
|
||||
end, {}, nil
|
||||
end,
|
||||
__ipairs = function (a)
|
||||
return function (x, key)
|
||||
key = key + 1
|
||||
if t[key] then
|
||||
return key, wrapMeta(t[key])
|
||||
end
|
||||
end, {}, 0
|
||||
end,
|
||||
__metatable = uniqueNEOProtectionObject
|
||||
-- Don't protect this table - it'll make things worse
|
||||
})
|
||||
return t2
|
||||
else
|
||||
return t
|
||||
end
|
||||
end
|
||||
|
||||
local function ensureType(a, t)
|
||||
if type(a) ~= t then error("Invalid parameter, expected a " .. t) end
|
||||
if t == "table" then
|
||||
if getmetatable(a) then error("Invalid parameter, has metatable") end
|
||||
end
|
||||
end
|
||||
|
||||
local function ensurePathComponent(s)
|
||||
if not s:match("^[a-zA-Z0-9_%-%+%,%#%~%@%'%;%[%]%(%)%&%%%$%! %=%{%}%^]+") then error("chars disallowed") end
|
||||
if s == "." then error("single dot disallowed") end
|
||||
if s == ".." then error("double dot disallowed") end
|
||||
end
|
||||
|
||||
local function ensurePath(s, r)
|
||||
-- Filter filename for anything "worrying". Note / is allowed, see further filters
|
||||
if not s:match("^[a-zA-Z0-9_%-%+%,%#%~%@%'%;%[%]%(%)%&%%%$%! %=%{%}%^%/]+") then error("chars disallowed") end
|
||||
if s:sub(1, r:len()) ~= r then error("base disallowed") end
|
||||
if s:match("//") then error("// disallowed") end
|
||||
if s:match("^%.%./") then error("../ disallowed") end
|
||||
if s:match("/%.%./") then error("/../ disallowed") end
|
||||
if s:match("/%.%.$") then error("/.. disallowed") end
|
||||
if s:match("^%./") then error("./ disallowed") end
|
||||
if s:match("/%./") then error("/./ disallowed") end
|
||||
if s:match("/%.$") then error("/. disallowed") end
|
||||
end
|
||||
|
||||
local wrapMath = wrapMeta(math)
|
||||
local wrapTable = wrapMeta(table)
|
||||
local wrapString = wrapMeta(string)
|
||||
local wrapUnicode = wrapMeta(unicode)
|
||||
local wrapCoroutine = wrapMeta(coroutine)
|
||||
local wrapOs = wrapMeta({
|
||||
totalMemory = computer.totalMemory, freeMemory = computer.freeMemory,
|
||||
energy = computer.energy, maxEnergy = computer.maxEnergy,
|
||||
clock = os.clock, date = os.date, difftime = os.difftime,
|
||||
time = os.time, uptime = computer.uptime
|
||||
})
|
||||
|
||||
local distEvent = nil
|
||||
|
||||
-- Use with extreme care.
|
||||
-- (A process killing itself will actually survive until the next yield... before any of the death events have run.)
|
||||
local function termProc(pid, reason)
|
||||
if processes[pid] then
|
||||
-- Immediately prepare for GC, it's possible this is out of memory.
|
||||
-- If out of memory, then to reduce risk of memory leak by error, memory needs to be freed ASAP.
|
||||
-- Start by getting rid of all process data.
|
||||
local dcbs = processes[pid].deathcbs
|
||||
local pkg = processes[pid].pkg
|
||||
local usage = processes[pid].cpuUsage
|
||||
processes[pid] = nil
|
||||
-- This gets rid of a few more bits of data.
|
||||
for _, v in ipairs(dcbs) do
|
||||
v()
|
||||
end
|
||||
-- This finishes off that.
|
||||
dcbs = nil
|
||||
if reason and criticalFailure then
|
||||
error(tostring(reason)) -- This is a debugging aid to give development work an easy-to-get-at outlet. Icecap is for most cases
|
||||
end
|
||||
if reason and emergencyFunction then
|
||||
emergencyFunction("d1 " .. pkg .. "/" .. pid)
|
||||
emergencyFunction("d2 " .. reason)
|
||||
end
|
||||
-- And this is why it's important, because this generates timers.
|
||||
-- The important targets of these timers will delete even more data.
|
||||
distEvent(nil, "k.procdie", pkg, pid, reason, usage)
|
||||
end
|
||||
end
|
||||
|
||||
local function execEvent(k, ...)
|
||||
if processes[k] then
|
||||
local v = processes[k]
|
||||
local timerA = computer.uptime()
|
||||
local r, reason = coroutine.resume(v.co, ...)
|
||||
-- Mostly reliable accounting
|
||||
v.cpuUsage = v.cpuUsage + (computer.uptime() - timerA)
|
||||
local dead = not r
|
||||
local hasReason = dead
|
||||
if not dead then
|
||||
if coroutine.status(v.co) == "dead" then
|
||||
dead = true
|
||||
end
|
||||
end
|
||||
if dead then
|
||||
if hasReason then
|
||||
reason = tostring(reason)
|
||||
else
|
||||
reason = nil
|
||||
end
|
||||
termProc(k, reason)
|
||||
return hasReason
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function distEvent(pid, s, ...)
|
||||
local ev = {...}
|
||||
if pid then
|
||||
local v = processes[pid]
|
||||
if not v then
|
||||
return
|
||||
end
|
||||
if (not v.access["s." .. s]) or v.access["k.root"] then
|
||||
return
|
||||
end
|
||||
-- Schedule a timer for "now"
|
||||
table.insert(timers, {computer.uptime(), function ()
|
||||
return execEvent(pid, s, table.unpack(ev))
|
||||
end})
|
||||
return
|
||||
end
|
||||
for k, v in pairs(processes) do
|
||||
distEvent(k, s, ...)
|
||||
end
|
||||
end
|
||||
|
||||
local loadLibraryInner = nil
|
||||
|
||||
function baseProcEnv()
|
||||
return {math = wrapMath,
|
||||
table = wrapTable,
|
||||
string = wrapString,
|
||||
unicode = wrapUnicode,
|
||||
coroutine = wrapCoroutine,
|
||||
os = wrapOs,
|
||||
-- Note raw-methods are gone - these can interfere with the metatable safeties.
|
||||
require = loadLibraryInner,
|
||||
assert = assert, ipairs = ipairs,
|
||||
load = load, next = next,
|
||||
pairs = pairs, pcall = pcall,
|
||||
xpcall = xpcall, select = select,
|
||||
type = type, error = error,
|
||||
tonumber = tonumber, tostring = tostring,
|
||||
setmetatable = setmetatable, getmetatable = function (n)
|
||||
local mt = getmetatable(n)
|
||||
if mt == uniqueNEOProtectionObject then return "NEO-Protected Object" end
|
||||
return mt
|
||||
end,
|
||||
rawset = function (t, i, v)
|
||||
local mt = getmetatable(n)
|
||||
if mt == uniqueNEOProtectionObject then error("NEO-Protected Object") end
|
||||
return rawset(t, i, v)
|
||||
end, rawget = rawget, rawlen = rawlen, rawequal = rawequal,
|
||||
neo = {
|
||||
emergency = emergencyFunction,
|
||||
readBufSize = readBufSize,
|
||||
wrapMeta = wrapMeta,
|
||||
listProcs = function ()
|
||||
local n = {}
|
||||
for k, v in pairs(processes) do
|
||||
table.insert(n, {k, v.pkg, v.cpuUsage})
|
||||
end
|
||||
return n
|
||||
end,
|
||||
listApps = function ()
|
||||
local n = primaryDisk.list("apps/")
|
||||
local n2 = {}
|
||||
for k, v in ipairs(n) do
|
||||
if v:sub(#v - 3) == ".lua" then
|
||||
table.insert(n2, v:sub(1, #v - 4))
|
||||
end
|
||||
end
|
||||
return n2
|
||||
end,
|
||||
listLibs = function ()
|
||||
local n = primaryDisk.list("libs/")
|
||||
local n2 = {}
|
||||
for k, v in ipairs(n) do
|
||||
if v:sub(#v - 3) == ".lua" then
|
||||
table.insert(n2, v:sub(1, #v - 4))
|
||||
end
|
||||
end
|
||||
return n2
|
||||
end,
|
||||
totalIdleTime = function () return idleTime end,
|
||||
ensurePath = ensurePath,
|
||||
ensurePathComponent = ensurePathComponent,
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
function loadLibraryInner(library)
|
||||
ensureType(library, "string")
|
||||
library = "libs/" .. library .. ".lua"
|
||||
ensurePath(library, "libs/")
|
||||
if libraries[library] then return libraries[library] end
|
||||
local l, r = loadfile(library, baseProcEnv())
|
||||
if l then
|
||||
local ok, al = pcall(l)
|
||||
if ok then
|
||||
libraries[library] = al
|
||||
return al
|
||||
else
|
||||
return nil, al
|
||||
end
|
||||
end
|
||||
return nil, r
|
||||
end
|
||||
|
||||
function retrieveAccess(perm, pkg, pid)
|
||||
-- Return the access lib and the death callback.
|
||||
|
||||
-- Access categories are sorted into:
|
||||
-- "c.<hw>": Component
|
||||
-- "s.<event>": Signal receiver (with responsibilities for Security Request watchers)
|
||||
-- "s.k.<...>": Kernel stuff
|
||||
-- "s.k.securityrequest": !!! HAS SIDE EFFECTS !!!
|
||||
-- "s.h.<...>": Incoming HW messages
|
||||
-- "s.x.<endpoint>": This access is actually useless on it's own - it is given by x.<endpoint>
|
||||
|
||||
-- "k.<x>": Kernel
|
||||
-- "k.root": _ENV (holy grail)
|
||||
-- "k.computer": computer
|
||||
|
||||
-- "r.<endpoint>": Registration Of Service...
|
||||
-- "x.<endpoint>": Access Of Service (handled by r. & accesses table)
|
||||
if accesses[perm] then
|
||||
return accesses[perm](pkg, pid)
|
||||
end
|
||||
|
||||
if perm == "k.root" then
|
||||
return _ENV
|
||||
end
|
||||
if perm == "k.computer" then
|
||||
return wrapMeta(computer)
|
||||
end
|
||||
if perm == "k.kill" then
|
||||
return function(npid)
|
||||
ensureType(npid, "number")
|
||||
termProc(npid, "Killed by " .. pkg .. "/" .. pid)
|
||||
end
|
||||
end
|
||||
if perm:sub(1, 2) == "s." then
|
||||
-- This is more of a "return success". Signal access is determined by the access/denied maps.
|
||||
return true
|
||||
end
|
||||
if perm:sub(1, 2) == "c." then
|
||||
-- Allows for simple "Control any of these connected to the system" APIs,
|
||||
-- for things the OS shouldn't be poking it's nose in.
|
||||
local primary = nil
|
||||
local temporary = nil
|
||||
local t = perm:sub(3)
|
||||
if t == "filesystem" then
|
||||
primary = primaryDisk
|
||||
temporary = component.proxy(computer.tmpAddress())
|
||||
end
|
||||
return {
|
||||
list = function ()
|
||||
local i = component.list(t, true)
|
||||
return function ()
|
||||
local ii = i()
|
||||
if not ii then return nil end
|
||||
return component.proxy(ii)
|
||||
end
|
||||
end,
|
||||
primary = primary,
|
||||
temporary = temporary
|
||||
}
|
||||
end
|
||||
if perm:sub(1, 2) == "r." then
|
||||
local uid = "x" .. perm:sub(2)
|
||||
local sid = "s.x" .. perm:sub(2)
|
||||
if accesses[uid] then return nil end
|
||||
accesses[uid] = function (pkg, pid)
|
||||
return nil
|
||||
end
|
||||
return function (f)
|
||||
-- Registration function
|
||||
ensureType(f, "function")
|
||||
local accessObjectCache = {}
|
||||
accesses[uid] = function(pkg, pid)
|
||||
-- Basically, a per registration per process cache.
|
||||
-- This is a consistent yet flexible behavior.
|
||||
if accessObjectCache[pid] then
|
||||
return accessObjectCache[pid]
|
||||
end
|
||||
processes[pid].access[sid] = true
|
||||
local ok, a = pcall(f, pkg, pid, function (...)
|
||||
distEvent(pid, uid, ...)
|
||||
end)
|
||||
if ok then
|
||||
accessObjectCache[pid] = a
|
||||
return a, function ()
|
||||
accessObjectCache[pid] = nil
|
||||
end
|
||||
end
|
||||
-- returns nil and fails
|
||||
end
|
||||
-- Announce registration
|
||||
distEvent(nil, "k.registration", uid)
|
||||
end, function ()
|
||||
-- Registration becomes null (access is held but other processes cannot retrieve object)
|
||||
if accesses[uid] then
|
||||
distEvent(nil, "k.deregistration", uid)
|
||||
end
|
||||
accesses[uid] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local start = nil
|
||||
|
||||
function start(pkg, ...)
|
||||
ensureType(pkg, "string")
|
||||
ensurePathComponent(pkg .. ".lua")
|
||||
local args = {...}
|
||||
local proc = {}
|
||||
local pid = lastPID
|
||||
lastPID = lastPID + 1
|
||||
|
||||
local function startFromUser(ipkg, ...)
|
||||
-- VERY specific injunction here:
|
||||
-- non "sys-" apps NEVER start "sys-" apps
|
||||
-- This is part of the "default security policy" below:
|
||||
-- sys- has all access
|
||||
-- anything else has none
|
||||
if ipkg:sub(1, 4) == "sys-" then
|
||||
if pkg:sub(1, 4) ~= "sys-" then
|
||||
return nil, "non-sys app trying to start sys app"
|
||||
end
|
||||
end
|
||||
return start(ipkg, pkg, pid, ...)
|
||||
end
|
||||
|
||||
local function osExecuteCore(handler, ...)
|
||||
local pid, err = startFromUser(...)
|
||||
while pid do
|
||||
local sig = {coroutine.yield()}
|
||||
if sig[1] == "k.procdie" then
|
||||
if sig[3] == pid then
|
||||
return 0, sig[4]
|
||||
end
|
||||
end
|
||||
end
|
||||
return -1, err
|
||||
end
|
||||
|
||||
local requestAccessAsync = function (perm)
|
||||
ensureType(perm, "string")
|
||||
-- Safety-checked, prepare security event.
|
||||
local req = {}
|
||||
req.waiting = {}
|
||||
req.service = function ()
|
||||
if processes[pid] then
|
||||
local n = nil
|
||||
local n2 = nil
|
||||
if req.result then
|
||||
proc.access[perm] = true
|
||||
n, n2 = retrieveAccess(perm, pkg, pid)
|
||||
if n2 then
|
||||
table.insert(processes[pid].deathcbs, n2)
|
||||
end
|
||||
else
|
||||
proc.denied[perm] = true
|
||||
end
|
||||
distEvent(pid, "k.securityresponse", perm, n)
|
||||
end
|
||||
end
|
||||
req.result = (not proc.denied[perm]) or proc.access["k.root"]
|
||||
if proc.access["k.root"] or proc.access[perm] or proc.denied[perm] then
|
||||
-- Use cached result to prevent possible unintentional security service spam
|
||||
req.service()
|
||||
return
|
||||
end
|
||||
-- Anything with s.k.securityrequest access has the response function and thus the vote,
|
||||
-- but can't vote on itself for obvious reasons. Kernel judge is a fallback.
|
||||
local shouldKernelJudge = true
|
||||
for k, v in pairs(processes) do
|
||||
if v.access["s.k.securityrequest"] then
|
||||
shouldKernelJudge = false
|
||||
if k ~= pid then
|
||||
req.waiting[k] = true
|
||||
distEvent(k, "k.securityrequest", pkg, pid, perm, function (r)
|
||||
ensureType(r, "boolean")
|
||||
if not r then
|
||||
req.result = false
|
||||
end
|
||||
req.waiting[k] = nil
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
if shouldKernelJudge then
|
||||
-- Rather restrictive, but an important safety measure
|
||||
req.result = pkg:sub(1, 4) == "sys-"
|
||||
req.service()
|
||||
else
|
||||
table.insert(outstandingSR, req)
|
||||
end
|
||||
end
|
||||
local env = baseProcEnv()
|
||||
env.neo.pid = pid
|
||||
env.neo.executeAsync = startFromUser
|
||||
env.neo.execute = function (...)
|
||||
return osExecuteCore(function () end, ...)
|
||||
end
|
||||
env.neo.executeExt = osExecuteCore
|
||||
env.neo.requestAccessAsync = requestAccessAsync
|
||||
env.neo.requestAccess = function (perm, handler)
|
||||
requestAccessAsync(perm)
|
||||
if not handler then handler = function() end end
|
||||
while true do
|
||||
local n = {coroutine.yield()}
|
||||
if n[1] == "k.securityresponse" then
|
||||
-- Security response - if it involves the permission, then take it
|
||||
if n[2] == perm then return n[3] end
|
||||
end
|
||||
handler(table.unpack(n))
|
||||
end
|
||||
end
|
||||
env.neo.scheduleTimer = function (time)
|
||||
ensureType(time, "number")
|
||||
local tag = {}
|
||||
table.insert(timers, {time, function(ofs)
|
||||
return execEvent(pid, "k.timer", tag, time, ofs)
|
||||
end})
|
||||
return tag
|
||||
end
|
||||
|
||||
local appfunc, r = loadfile("apps/" .. pkg .. ".lua", env)
|
||||
if not appfunc then
|
||||
return nil, r
|
||||
end
|
||||
proc.co = coroutine.create(appfunc)
|
||||
proc.pkg = pkg
|
||||
proc.access = {
|
||||
-- These permissions are the "critical set".
|
||||
["s.k.securityresponse"] = true,
|
||||
["s.k.timer"] = true,
|
||||
["s.k.procnew"] = true,
|
||||
["s.k.procdie"] = true,
|
||||
-- Used when a registration is updated, in particular, as this signifies "readiness"
|
||||
["s.k.registration"] = true,
|
||||
}
|
||||
proc.denied = {}
|
||||
proc.deathcbs = {}
|
||||
proc.cpuUsage = 0
|
||||
-- Note the target process doesn't get the procnew (it's executed after it's creation)
|
||||
pcall(distEvent, nil, "k.procnew", pkg, pid)
|
||||
processes[pid] = proc
|
||||
-- For processes waiting on others, this at least tries to guarantee some safety.
|
||||
if criticalFailure then
|
||||
execEvent(pid, ...)
|
||||
else
|
||||
if not pcall(execEvent, pid, ...) then
|
||||
return nil, "neocore"
|
||||
end
|
||||
end
|
||||
return pid
|
||||
end
|
||||
|
||||
-- Main Scheduling Loop --
|
||||
|
||||
local function processSRs()
|
||||
local didAnything = false
|
||||
for k, v in pairs(outstandingSR) do
|
||||
-- Outstanding security request handler.
|
||||
local actualWaitingCount = 0
|
||||
for k2, _ in pairs(v.waiting) do
|
||||
if not processes[k2] then
|
||||
v.waiting[k2] = nil
|
||||
v.result = false
|
||||
else
|
||||
actualWaitingCount = actualWaitingCount + 1
|
||||
end
|
||||
end
|
||||
if actualWaitingCount == 0 then
|
||||
-- Service the SR
|
||||
outstandingSR[k].service()
|
||||
outstandingSR[k] = nil
|
||||
didAnything = true
|
||||
end
|
||||
end
|
||||
return didAnything
|
||||
end
|
||||
|
||||
-- The actual loop & initialization
|
||||
|
||||
if not start("sys-init") then error("Could not start sys-init") end
|
||||
while true do
|
||||
local tmr = nil
|
||||
for i = 1, 16 do
|
||||
tmr = nil
|
||||
local now = computer.uptime()
|
||||
local breaking = false -- Used when a process dies - in this case it's assumed OC just did something drastic
|
||||
local didAnything = false
|
||||
local k = 1
|
||||
while timers[k] do
|
||||
local v = timers[k]
|
||||
if v[1] <= now then
|
||||
if v[2](now - v[1]) then
|
||||
breaking = true
|
||||
tmr = 0.05
|
||||
break
|
||||
end
|
||||
didAnything = true
|
||||
v = nil
|
||||
else
|
||||
if not tmr then
|
||||
tmr = v[1]
|
||||
else
|
||||
tmr = math.min(tmr, v[1])
|
||||
end
|
||||
end
|
||||
if v then
|
||||
k = k + 1
|
||||
else
|
||||
table.remove(timers, k)
|
||||
end
|
||||
end
|
||||
if breaking then break end
|
||||
didAnything = didAnything or processSRs()
|
||||
-- If the system didn't make any progress, then we're waiting for a signal (this includes timers)
|
||||
if not didAnything then break end
|
||||
end
|
||||
now = computer.uptime() -- the above probably took a while
|
||||
local dist = nil
|
||||
if tmr then
|
||||
dist = tmr - now
|
||||
if dist < 0.05 then dist = 0.05 end
|
||||
end
|
||||
local signal = {computer.pullSignal(dist)}
|
||||
idleTime = idleTime + (computer.uptime() - now)
|
||||
if signal[1] then
|
||||
distEvent(nil, "h." .. signal[1], select(2, table.unpack(signal)))
|
||||
end
|
||||
end
|
104
code/libs/event.lua
Normal file
104
code/libs/event.lua
Normal file
@ -0,0 +1,104 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- event: Implements pull/listen semantics in a consistent way for a given process.
|
||||
-- This is similar in API to OpenOS's event framework, but is per-process.
|
||||
-- To make this work, a function is shared.
|
||||
-- This function needs access to the caller's NEO in order to ensure that NEO system functions are covered.
|
||||
-- This can do less checks than usual as it only affects the caller.
|
||||
|
||||
return function (neo)
|
||||
local listeners = {}
|
||||
local translations = {}
|
||||
local timers = {}
|
||||
local oldRA = neo.requestAccess
|
||||
local function doPush(tp, tag, ...)
|
||||
if tp == "k.timer" then
|
||||
local f = timers[tag]
|
||||
timers[tag] = nil
|
||||
if f then
|
||||
f(...)
|
||||
end
|
||||
end
|
||||
local listeners2 = {}
|
||||
for k, _ in pairs(listeners) do
|
||||
table.insert(listeners2, k)
|
||||
end
|
||||
for _, v in ipairs(listeners2) do
|
||||
v(tp, tag, ...)
|
||||
end
|
||||
end
|
||||
neo.requestAccess = function (perm, handler)
|
||||
return oldRA(perm, function (...)
|
||||
doPush(...)
|
||||
if handler then
|
||||
handler(...)
|
||||
end
|
||||
end)
|
||||
end
|
||||
local function doPull()
|
||||
local ev = {coroutine.yield()}
|
||||
doPush(table.unpack(ev))
|
||||
return ev
|
||||
end
|
||||
return {
|
||||
listen = function (p1, p2)
|
||||
if type(p2) == "function" then
|
||||
local t = function (...)
|
||||
local evn = ...
|
||||
if evn == p1 then
|
||||
p2(...)
|
||||
end
|
||||
end
|
||||
translations[p2] = t
|
||||
listeners[t] = true
|
||||
else
|
||||
listeners[p1] = true
|
||||
end
|
||||
end,
|
||||
ignore = function (func)
|
||||
if translations[func] then
|
||||
listeners[translations[func]] = nil
|
||||
translations[func] = nil
|
||||
end
|
||||
listeners[func] = nil
|
||||
end,
|
||||
-- Arguments are filtering.
|
||||
-- For example, to filter for timers with a given tag, use pull("k.timer", tag)
|
||||
-- Note the explicit discouragement of timeout-pulls. Use runAt or the complex form of sleep.
|
||||
pull = function (...)
|
||||
local filter = {...}
|
||||
while true do
|
||||
local ev = doPull()
|
||||
local err = false
|
||||
for i = 1, #filter do
|
||||
err = err or (filter[i] ~= ev[i])
|
||||
end
|
||||
if not err then return table.unpack(ev) end
|
||||
end
|
||||
end,
|
||||
-- Run a function at a specified uptime.
|
||||
runAt = function (time, func)
|
||||
timers[neo.scheduleTimer(time)] = func
|
||||
end,
|
||||
-- Sleeps until a time (unless time is nil, in which case sleeps forever), but can be woken up before then.
|
||||
-- This allows using an async API as a synchronous one with optional time-to-failure.
|
||||
sleepTo = function (time, wakeUpPoll)
|
||||
local timerHit = false
|
||||
local oWUP = wakeUpPoll
|
||||
wakeUpPoll = function ()
|
||||
if oWUP then
|
||||
return timerHit or (oWUP())
|
||||
end
|
||||
return timerHit
|
||||
end
|
||||
if time then
|
||||
timers[neo.scheduleTimer(time)] = function () timerHit = true end
|
||||
end
|
||||
while true do
|
||||
local ev = doPull()
|
||||
if wakeUpPoll() then return end
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
406
code/libs/neoux.lua
Normal file
406
code/libs/neoux.lua
Normal file
@ -0,0 +1,406 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- neoux: Implements utilities on top of Everest & event:
|
||||
-- Everest crash protection
|
||||
return function (event, neo)
|
||||
-- this is why neo access is 'needed'
|
||||
local function retrieveIcecap()
|
||||
return neo.requestAccess("x.neo.pub.base")
|
||||
end
|
||||
local function retrieveEverest()
|
||||
return neo.requestAccess("x.neo.pub.window")
|
||||
end
|
||||
-- id -> {lclEv, w, h, title, callback}
|
||||
local windows = {}
|
||||
local lclEvToW = {}
|
||||
retrieveEverest()
|
||||
local function everestDied()
|
||||
for _, v in pairs(windows) do
|
||||
v[1] = nil
|
||||
end
|
||||
lclEvToW = {}
|
||||
end
|
||||
local function pushWindowToEverest(k)
|
||||
local everest = retrieveEverest()
|
||||
if not everest then
|
||||
everestDied()
|
||||
return
|
||||
end
|
||||
local v = windows[k]
|
||||
local r, res = pcall(everest, v[2], v[3], v[4])
|
||||
if not r then
|
||||
everestDied()
|
||||
return
|
||||
else
|
||||
-- res is the window!
|
||||
lclEvToW[res.id] = k
|
||||
windows[k][1] = res
|
||||
end
|
||||
end
|
||||
event.listen("k.registration", function (_, xe)
|
||||
if #windows > 0 then
|
||||
if xe == "x.neo.pub.window" then
|
||||
for k, v in pairs(windows) do
|
||||
pushWindowToEverest(k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
event.listen("k.deregistration", function (_, xe)
|
||||
if xe == "x.neo.pub.window" then
|
||||
everestDied()
|
||||
end
|
||||
end)
|
||||
event.listen("x.neo.pub.window", function (_, window, tp, ...)
|
||||
if lclEvToW[window] then
|
||||
windows[lclEvToW[window]][5](tp, ...)
|
||||
end
|
||||
end)
|
||||
local neoux = {}
|
||||
neoux.fileDialog = function (forWrite, callback)
|
||||
local sync = false
|
||||
local rtt = nil
|
||||
if not callback then
|
||||
sync = true
|
||||
callback = function (rt)
|
||||
sync = false
|
||||
rtt = rt
|
||||
end
|
||||
end
|
||||
local tag = retrieveIcecap().showFileDialogAsync(forWrite)
|
||||
local f
|
||||
f = function (_, fd, tg, re)
|
||||
if fd == "filedialog" then
|
||||
if tg == tag then
|
||||
callback(re)
|
||||
event.ignore(f)
|
||||
end
|
||||
end
|
||||
end
|
||||
event.listen("x.neo.pub.base", f)
|
||||
while sync do
|
||||
event.pull()
|
||||
end
|
||||
return rtt
|
||||
end
|
||||
-- Creates a wrapper around a window.
|
||||
neoux.create = function (w, h, title, callback)
|
||||
local window = {}
|
||||
local windowCore = {nil, w, h, title, function (...) callback(window, ...) end}
|
||||
local k = #windows + 1
|
||||
table.insert(windows, windowCore)
|
||||
pushWindowToEverest(k)
|
||||
window.reset = function (w, h, cb)
|
||||
callback = cb
|
||||
if mw or nh then
|
||||
windowCore[2] = nw
|
||||
windowCore[3] = nh
|
||||
end
|
||||
if windowCore[1] then
|
||||
windowCore[1].setSize(windowCore[2], windowCore[3])
|
||||
end
|
||||
end
|
||||
window.getSize = function ()
|
||||
return windowCore[2], windowCore[3]
|
||||
end
|
||||
window.setSize = function (w, h)
|
||||
windowCore[2] = w
|
||||
windowCore[3] = h
|
||||
if windowCore[1] then
|
||||
windowCore[1].setSize(w, h)
|
||||
end
|
||||
end
|
||||
window.span = function (x, y, text, bg, fg)
|
||||
if windowCore[1] then
|
||||
pcall(windowCore[1].span, x, y, text, bg, fg)
|
||||
end
|
||||
end
|
||||
window.close = function ()
|
||||
if windowCore[1] then
|
||||
windowCore[1].close()
|
||||
lclEvToW[windowCore[1].id] = nil
|
||||
windowCore[1] = nil
|
||||
end
|
||||
windows[k] = nil
|
||||
end
|
||||
return window
|
||||
end
|
||||
-- Padding function
|
||||
neoux.pad = function (t, len, centre, cut)
|
||||
local l = unicode.len(t)
|
||||
local add = len - l
|
||||
if add > 0 then
|
||||
if centre then
|
||||
t = (" "):rep(math.floor(add / 2)) .. t .. (" "):rep(math.ceil(add / 2))
|
||||
else
|
||||
t = t .. (" "):rep(add)
|
||||
end
|
||||
end
|
||||
if cut then
|
||||
t = unicode.sub(t, 1, len)
|
||||
end
|
||||
return t
|
||||
end
|
||||
-- Text dialog formatting function.
|
||||
-- Assumes you've run unicode.safeTextFormat if need be
|
||||
neoux.fmtText = function (text, w)
|
||||
local nl = text:find("\n")
|
||||
if nl then
|
||||
local base = text:sub(1, nl - 1)
|
||||
local ext = text:sub(nl + 1)
|
||||
local baseT = neoux.fmtText(base, w)
|
||||
local extT = neoux.fmtText(ext, w)
|
||||
for _, v in ipairs(extT) do
|
||||
table.insert(baseT, v)
|
||||
end
|
||||
return baseT
|
||||
end
|
||||
if unicode.len(text) > w then
|
||||
local lastSpace
|
||||
for i = 1, w do
|
||||
if unicode.sub(text, i, i) == " " then
|
||||
-- Check this isn't an inserted space (unicode safe text format)
|
||||
local ok = true
|
||||
if i > 1 then
|
||||
if unicode.charWidth(unicode.sub(text, i - 1, i - 1)) ~= 1 then
|
||||
ok = false
|
||||
end
|
||||
end
|
||||
if ok then
|
||||
lastSpace = i
|
||||
end
|
||||
end
|
||||
end
|
||||
local baseText, extText
|
||||
if not lastSpace then
|
||||
-- Break at a 1-earlier boundary
|
||||
local wEffect = w
|
||||
if unicode.charWidth(unicode.sub(text, w, w)) ~= 1 then
|
||||
-- Guaranteed to be safe, so
|
||||
wEffect = wEffect - 1
|
||||
end
|
||||
baseText = unicode.sub(text, 1, wEffect)
|
||||
extText = unicode.sub(text, wEffect + 1)
|
||||
else
|
||||
baseText = unicode.sub(text, 1, lastSpace - 1)
|
||||
extText = unicode.sub(text, lastSpace + 1)
|
||||
end
|
||||
local full = neoux.fmtText(extText, w)
|
||||
table.insert(full, 1, neoux.pad(baseText, w))
|
||||
return full
|
||||
end
|
||||
return {neoux.pad(text, w)}
|
||||
end
|
||||
-- UI FRAMEWORK --
|
||||
neoux.tcwindow = function (w, h, controls, closing, bg, fg)
|
||||
local selIndex = #controls
|
||||
if #controls == 0 then
|
||||
selIndex = 1
|
||||
end
|
||||
local function rotateSelIndex()
|
||||
local original = selIndex
|
||||
while true do
|
||||
selIndex = selIndex + 1
|
||||
if not controls[selIndex] then
|
||||
selIndex = 1
|
||||
end
|
||||
if controls[selIndex] then
|
||||
if controls[selIndex].selectable then
|
||||
return
|
||||
end
|
||||
end
|
||||
if selIndex == original then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
rotateSelIndex()
|
||||
|
||||
local function doLine(window, a)
|
||||
window.span(1, a, (" "):rep(w), bg, fg)
|
||||
for k, v in ipairs(controls) do
|
||||
if a >= v.y then
|
||||
if a < (v.y + v.h) then
|
||||
v.line(window, v.x, a, (a - v.y) + 1, bg, fg, selIndex == k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local function doZone(window, control, cache)
|
||||
for i = 1, control.h do
|
||||
local l = i + control.y - 1
|
||||
if (not cache) or (not cache[l]) then
|
||||
doLine(window, l)
|
||||
if cache then cache[l] = true end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return function (window, ev, a, b, c)
|
||||
if ev == "touch" then
|
||||
local found = nil
|
||||
for k, v in ipairs(controls) do
|
||||
if v.selectable then
|
||||
if a >= v.x then
|
||||
if a < (v.x + v.w) then
|
||||
if b >= v.y then
|
||||
if b < (v.y + v.h) then
|
||||
found = k
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if found then
|
||||
local c1 = controls[selIndex]
|
||||
selIndex = found
|
||||
local c2 = controls[selIndex]
|
||||
local cache = {}
|
||||
if c1 then doZone(window, c1, cache) end
|
||||
if c2 then
|
||||
doZone(window, c2, cache)
|
||||
if c2.touch then
|
||||
c2.touch(window, function () doZone(window, c2) end, (a - c2.x) + 1, (b - c2.y) + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if ev == "drag" then
|
||||
if controls[selIndex] then
|
||||
if controls[selIndex].drag then
|
||||
controls[selIndex].drag(window, function () doZone(window, controls[selIndex]) end, (a - controls[selIndex].x) + 1, (b - controls[selIndex].y) + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
if ev == "key" then
|
||||
if a == 9 then
|
||||
if c then
|
||||
local c1 = controls[selIndex]
|
||||
rotateSelIndex()
|
||||
local c2 = controls[selIndex]
|
||||
local cache = {}
|
||||
if c1 then doZone(window, c1, cache) end
|
||||
if c2 then doZone(window, c2, cache) end
|
||||
end
|
||||
else
|
||||
if controls[selIndex] then
|
||||
if controls[selIndex].key then
|
||||
controls[selIndex].key(window, function () doZone(window, controls[selIndex]) end, a, b, c)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if ev == "line" then
|
||||
doLine(window, a)
|
||||
end
|
||||
if ev == "close" then
|
||||
closing(window)
|
||||
end
|
||||
end, doZone
|
||||
end
|
||||
neoux.tcrawview = function (x, y, lines)
|
||||
return {
|
||||
x = x,
|
||||
y = y,
|
||||
w = unicode.len(lines[1]),
|
||||
h = #lines,
|
||||
selectable = false,
|
||||
line = function (window, x, y, lined, bg, fg, selected)
|
||||
-- Can't be selected normally so ignore that flag
|
||||
window.span(x, y, lines[lined], bg, fg)
|
||||
end
|
||||
}
|
||||
end
|
||||
neoux.tchdivider = function (x, y, w)
|
||||
return neoux.tcrawview(x, y, {("-"):rep(w)})
|
||||
end
|
||||
neoux.tcvdivider = function (x, y, h)
|
||||
local n = {}
|
||||
for i = 1, h do
|
||||
n[i] = "|"
|
||||
end
|
||||
return neoux.tcrawview(x, y, n)
|
||||
end
|
||||
neoux.tcbutton = function (x, y, text, callback)
|
||||
text = "<" .. text .. ">"
|
||||
return {
|
||||
x = x,
|
||||
y = y,
|
||||
w = unicode.len(text),
|
||||
h = 1,
|
||||
selectable = true,
|
||||
key = function (window, update, a, c, d)
|
||||
if d then
|
||||
if a == 13 or a == 32 then
|
||||
callback(window)
|
||||
end
|
||||
end
|
||||
end,
|
||||
touch = function (window, update, x, y)
|
||||
callback(window)
|
||||
end,
|
||||
line = function (window, x, y, lind, bg, fg, selected)
|
||||
local fg1 = fg
|
||||
if selected then
|
||||
fg = bg
|
||||
bg = fg1
|
||||
end
|
||||
window.span(x, y, text, bg, fg)
|
||||
end
|
||||
}
|
||||
end
|
||||
-- Note: w should be at least 2 - this is similar to buttons.
|
||||
neoux.tcfield = function (x, y, w, textprop)
|
||||
return {
|
||||
x = x,
|
||||
y = y,
|
||||
w = w,
|
||||
h = 1,
|
||||
selectable = true,
|
||||
key = function (window, update, a, c, d)
|
||||
if d then
|
||||
if a == 13 then
|
||||
elseif a == 8 then
|
||||
local str = textprop()
|
||||
textprop(unicode.sub(str, 1, unicode.len(str) - 1))
|
||||
update()
|
||||
elseif a ~= 0 then
|
||||
textprop(textprop() .. unicode.char(a))
|
||||
update()
|
||||
end
|
||||
end
|
||||
end,
|
||||
line = function (window, x, y, lind, bg, fg, selected)
|
||||
local fg1 = fg
|
||||
if selected then
|
||||
fg = bg
|
||||
bg = fg1
|
||||
end
|
||||
local text = unicode.safeTextFormat(textprop())
|
||||
local txl = unicode.len(text)
|
||||
local start = math.max(1, (txl - (w - 2)) + 1)
|
||||
text = "[" .. neoux.pad(unicode.sub(text, start, start + (w - 3)), w - 2, false, true) .. "]"
|
||||
window.span(x, y, text, bg, fg)
|
||||
end
|
||||
}
|
||||
end
|
||||
neoux.startDialog = function (fmt, title, wait)
|
||||
fmt = neoux.fmtText(unicode.safeTextFormat(fmt), 20)
|
||||
neoux.create(20, #fmt, title, function (window, ev, a, b, c)
|
||||
if ev == "line" then
|
||||
window.span(1, a, fmt[a], 0xFFFFFF, 0)
|
||||
end
|
||||
if ev == "close" then
|
||||
window.close()
|
||||
wait = nil
|
||||
end
|
||||
end)
|
||||
while wait do
|
||||
event.pull()
|
||||
end
|
||||
end
|
||||
return neoux
|
||||
end
|
28
code/libs/serial.lua
Normal file
28
code/libs/serial.lua
Normal file
@ -0,0 +1,28 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
local doSerialize = nil
|
||||
function doSerialize(s)
|
||||
if type(s) == "table" then
|
||||
local str = "{\n"
|
||||
for k, v in pairs(s) do
|
||||
str = str .. "[" .. doSerialize(k) .. "]=" .. doSerialize(v) .. ",\n"
|
||||
end
|
||||
return str .. "}"
|
||||
end
|
||||
if type(s) == "string" then
|
||||
return string.format("%q", s)
|
||||
end
|
||||
if type(s) == "number" then
|
||||
return tostring(s)
|
||||
end
|
||||
error("Cannot serialize " .. type(s))
|
||||
end
|
||||
return neo.wrapMeta({
|
||||
serialize = function (x) return "return " .. doSerialize(x) end,
|
||||
deserialize = function (s)
|
||||
local r1, r2 = pcall(function() return load(s, "=serial", "t", {})() end)
|
||||
if r1 then
|
||||
return r2
|
||||
end
|
||||
end
|
||||
})
|
159
code/libs/sys-filedialog.lua
Normal file
159
code/libs/sys-filedialog.lua
Normal file
@ -0,0 +1,159 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- just don't bother with proper indent here
|
||||
return function (event, neoux, retFunc, fs, pkg, mode)
|
||||
|
||||
local class = "manage"
|
||||
if mode ~= nil then
|
||||
if mode then
|
||||
class = "save"
|
||||
else
|
||||
class = "load"
|
||||
end
|
||||
end
|
||||
|
||||
local prepareNode
|
||||
|
||||
local ccb = nil
|
||||
local function cb(...)
|
||||
local res, e = pcall(ccb, ...)
|
||||
if not res then
|
||||
prepareNode({
|
||||
name = "F.M. Error",
|
||||
list = function ()
|
||||
local l = {}
|
||||
for k, v in ipairs(neoux.fmtText(unicode.safeTextFormat(e), 25)) do
|
||||
l[k] = {v, function () return true end}
|
||||
end
|
||||
return l
|
||||
end,
|
||||
unknownAvailable = false,
|
||||
selectUnknown = function (text) end
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local function prepareNodeI(node)
|
||||
local l = node.list()
|
||||
local w, h = 30, 8
|
||||
-- Local State
|
||||
-- Selection. Having this equal to #l + 1 means typing area ('unknown')
|
||||
local selection = 1
|
||||
local unknownTx = ""
|
||||
--
|
||||
local function format(a)
|
||||
if a <= 1 then
|
||||
return true, true, node.name
|
||||
end
|
||||
local camY = math.max(1, selection - 4)
|
||||
local idx = a + camY - 2
|
||||
if node.unknownAvailable then
|
||||
if idx == #l + 1 then
|
||||
return selection == #l + 1, false, ":" .. unknownTx
|
||||
end
|
||||
end
|
||||
if l[idx] then
|
||||
return selection == idx, false, l[idx][1]
|
||||
end
|
||||
return true, true, "~~~"
|
||||
end
|
||||
local function updateLine(wnd, a)
|
||||
local colA, colB = 0xFFFFFF, 0
|
||||
local sel, cen, text = format(a)
|
||||
if sel then
|
||||
colB, colA = 0xFFFFFF, 0
|
||||
end
|
||||
wnd.span(1, a, neoux.pad(unicode.safeTextFormat(text), w, cen, true), colA, colB)
|
||||
end
|
||||
local function flush(wnd)
|
||||
for i = 1, h do
|
||||
updateLine(wnd, i)
|
||||
end
|
||||
end
|
||||
local function key(wnd, ka, kc, down)
|
||||
if not down then return end
|
||||
if (ka == 9) or (kc == 208) then
|
||||
local lo = selection
|
||||
selection = selection + 1
|
||||
local max = #l
|
||||
if node.unknownAvailable then
|
||||
max = max + 1
|
||||
end
|
||||
if selection > max then
|
||||
selection = 1
|
||||
end
|
||||
flush(wnd)
|
||||
return
|
||||
end
|
||||
if kc == 200 then
|
||||
local lo = selection
|
||||
selection = selection - 1
|
||||
local max = #l
|
||||
if node.unknownAvailable then
|
||||
max = max + 1
|
||||
end
|
||||
if selection == 0 then
|
||||
selection = max
|
||||
end
|
||||
flush(wnd)
|
||||
return
|
||||
end
|
||||
if ka == 13 then
|
||||
local aResult, res
|
||||
if selection ~= #l + 1 then
|
||||
aResult, res = l[selection][2]()
|
||||
else
|
||||
aResult, res = node.selectUnknown(unknownTx)
|
||||
end
|
||||
if aResult then
|
||||
retFunc(res)
|
||||
wnd.close()
|
||||
else
|
||||
prepareNode(res)
|
||||
end
|
||||
return
|
||||
end
|
||||
if selection == #l + 1 then
|
||||
if ka == 8 then
|
||||
unknownTx = unicode.sub(unknownTx, 1, unicode.len(unknownTx) - 1)
|
||||
flush(wnd)
|
||||
return
|
||||
end
|
||||
if ka ~= 0 then
|
||||
unknownTx = unknownTx .. unicode.char(ka)
|
||||
flush(wnd)
|
||||
end
|
||||
end
|
||||
end
|
||||
return w, h, function (wnd, evt, a, b, c)
|
||||
if evt == "key" then
|
||||
key(wnd, a, b, c)
|
||||
end
|
||||
if evt == "line" then
|
||||
updateLine(wnd, a)
|
||||
end
|
||||
if evt == "close" then
|
||||
retFunc(nil)
|
||||
wnd.close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local text = class .. " " .. pkg
|
||||
local window = neoux.create(25, 10, text, cb)
|
||||
|
||||
function prepareNode(node)
|
||||
local w, h, c = prepareNodeI(node)
|
||||
ccb = c
|
||||
window.setSize(w, h)
|
||||
end
|
||||
|
||||
prepareNode(require("sys-filevfs")(fs, mode))
|
||||
return function ()
|
||||
retFunc()
|
||||
window.close()
|
||||
end
|
||||
|
||||
-- end bad indent
|
||||
end
|
181
code/libs/sys-filevfs.lua
Normal file
181
code/libs/sys-filevfs.lua
Normal file
@ -0,0 +1,181 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- Used by filedialog to provide a sane relative environment.
|
||||
-- Essentially, the filedialog is just a 'thin' UI wrapper over this.
|
||||
-- Returns the root node.
|
||||
|
||||
local function dialog(name, parent)
|
||||
return {
|
||||
name = name,
|
||||
list = function () return {{"Back", function () return nil, parent end}} end,
|
||||
unknownAvailable = false,
|
||||
selectUnknown = function (text) end
|
||||
}
|
||||
end
|
||||
|
||||
local getFsNode, getRoot
|
||||
local setupCopyNode
|
||||
function setupCopyNode(parent, myRoot, op, complete)
|
||||
local function handleResult(aRes, res)
|
||||
if aRes then
|
||||
return complete(res)
|
||||
else
|
||||
return nil, setupCopyNode(parent, res, op, complete)
|
||||
end
|
||||
end
|
||||
return {
|
||||
name = "(" .. op .. ") " .. myRoot.name,
|
||||
list = function ()
|
||||
local l = {}
|
||||
table.insert(l, {"Cancel Operation: " .. op, function ()
|
||||
return false, parent
|
||||
end})
|
||||
for _, v in ipairs(myRoot.list()) do
|
||||
table.insert(l, {v[1], function ()
|
||||
return handleResult(v[2]())
|
||||
end})
|
||||
end
|
||||
return l
|
||||
end,
|
||||
unknownAvailable = myRoot.unknownAvailable,
|
||||
selectUnknown = function (tx)
|
||||
return handleResult(myRoot.selectUnknown(tx))
|
||||
end
|
||||
}
|
||||
end
|
||||
local function setupCopyVirtualEnvironment(fs, parent, fwrap)
|
||||
if not fwrap then
|
||||
return false, dialog("Could not open source", parent)
|
||||
end
|
||||
local myRoot = getRoot(fs, true)
|
||||
-- Setup wrapping node
|
||||
return setupCopyNode(parent, myRoot, "Copy", function (fwrap2)
|
||||
if not fwrap2 then
|
||||
return false, dialog("Could not open dest.", parent)
|
||||
end
|
||||
local data = fwrap.read(neo.readBufSize)
|
||||
while data do
|
||||
fwrap2.write(data)
|
||||
data = fwrap.read(neo.readBufSize)
|
||||
end
|
||||
fwrap.close()
|
||||
fwrap2.close()
|
||||
return false, dialog("Completed copy.", parent)
|
||||
end)
|
||||
end
|
||||
getFsNode = function (fs, parent, fsc, path, mode)
|
||||
local va = fsc.address:sub(1, 4)
|
||||
if path:sub(#path, #path) == "/" then
|
||||
local t
|
||||
local confirmedDel = false
|
||||
t = {
|
||||
name = "DIR : " .. va .. path,
|
||||
list = function ()
|
||||
local n = {}
|
||||
n[1] = {"..", function ()
|
||||
return nil, parent
|
||||
end}
|
||||
for k, v in ipairs(fsc.list(path)) do
|
||||
local nm = "[F] " .. v
|
||||
local fp = path .. v
|
||||
if fsc.isDirectory(fp) then
|
||||
nm = "[D] " .. v
|
||||
end
|
||||
n[k + 1] = {nm, function () return nil, getFsNode(fs, t, fsc, fp, mode) end}
|
||||
end
|
||||
local delText = "Delete"
|
||||
if confirmedDel then
|
||||
delText = "Delete <ARMED>"
|
||||
end
|
||||
n[#n + 1] = {delText, function ()
|
||||
if not confirmedDel then
|
||||
confirmedDel = true
|
||||
return nil, t
|
||||
end
|
||||
fsc.remove(path)
|
||||
return nil, dialog("Done.", parent)
|
||||
end}
|
||||
n[#n + 1] = {"Mk. Directory", function ()
|
||||
return nil, {
|
||||
name = "MKDIR...",
|
||||
list = function () return {} end,
|
||||
unknownAvailable = true,
|
||||
selectUnknown = function (text)
|
||||
fsc.makeDirectory(path .. text)
|
||||
return nil, dialog("Done!", t)
|
||||
end
|
||||
}
|
||||
end}
|
||||
return n
|
||||
end,
|
||||
unknownAvailable = mode ~= nil,
|
||||
selectUnknown = function (text)
|
||||
return true, require("sys-filewrap")(fsc, path .. text, mode)
|
||||
end
|
||||
}
|
||||
return t
|
||||
end
|
||||
return {
|
||||
name = "FILE: " .. va .. path,
|
||||
list = function ()
|
||||
local n = {}
|
||||
table.insert(n, {"Back", function ()
|
||||
return nil, parent
|
||||
end})
|
||||
if mode ~= nil then
|
||||
table.insert(n, {"Open", function ()
|
||||
return true, require("sys-filewrap")(fsc, path, mode)
|
||||
end})
|
||||
end
|
||||
table.insert(n, {"Copy", function ()
|
||||
return nil, setupCopyVirtualEnvironment(fs, parent, require("sys-filewrap")(fsc, path, false))
|
||||
end})
|
||||
table.insert(n, {"Delete", function ()
|
||||
fsc.remove(path)
|
||||
return nil, dialog("Done.", parent)
|
||||
end})
|
||||
return n
|
||||
end,
|
||||
unknownAvailable = false,
|
||||
selectUnknown = function (text) end
|
||||
}
|
||||
end
|
||||
function getRoot(fs, mode)
|
||||
local t
|
||||
t = {
|
||||
name = "DRVS:",
|
||||
list = function ()
|
||||
local l = {}
|
||||
for fsi in fs.list() do
|
||||
local id = fsi.getLabel()
|
||||
if not id then
|
||||
id = " Disk"
|
||||
else
|
||||
id = ":" .. id
|
||||
end
|
||||
if fsi == fs.primary then
|
||||
id = "NEO" .. id
|
||||
elseif fsi == fs.temporary then
|
||||
id = "RAM" .. id
|
||||
end
|
||||
local used, total = fsi.spaceUsed(), fsi.spaceTotal()
|
||||
local amount = string.format("%02i", math.ceil((used / total) * 100))
|
||||
local mb = math.floor(total / (1024 * 1024))
|
||||
if fsi.isReadOnly() then
|
||||
id = amount .. "% RO " .. mb .. "M " .. id
|
||||
else
|
||||
id = amount .. "% RW " .. mb .. "M " .. id
|
||||
end
|
||||
table.insert(l, {fsi.address:sub(1, 4) .. ": " .. id, function ()
|
||||
return nil, getFsNode(fs, t, fsi, "/", mode)
|
||||
end})
|
||||
end
|
||||
return l
|
||||
end,
|
||||
unknownAvailable = false,
|
||||
selectUnknown = function (text) end
|
||||
}
|
||||
return t
|
||||
end
|
||||
return getRoot
|
42
code/libs/sys-filewrap.lua
Normal file
42
code/libs/sys-filewrap.lua
Normal file
@ -0,0 +1,42 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
return function(dev, file, mode)
|
||||
local n = "rb"
|
||||
if mode then n = "wb" end
|
||||
local handle = dev.open(file, n)
|
||||
local open = true
|
||||
local function closer()
|
||||
if not open then return end
|
||||
open = false
|
||||
pcall(function()
|
||||
dev.close(handle)
|
||||
end)
|
||||
end
|
||||
if not mode then
|
||||
return {
|
||||
close = closer,
|
||||
read = function (len)
|
||||
if len == "*a" then
|
||||
local ch = ""
|
||||
local c = dev.read(handle, neo.readBufSize)
|
||||
while c do
|
||||
ch = ch .. c
|
||||
c = dev.read(handle, neo.readBufSize)
|
||||
end
|
||||
return ch
|
||||
end
|
||||
if type(len) ~= "number" then error("Length of read must be number or '*a'") end
|
||||
return dev.read(handle, len)
|
||||
end
|
||||
}, closer
|
||||
else
|
||||
return {
|
||||
close = closer,
|
||||
write = function (txt)
|
||||
if type(txt) ~= "string" then error("Write data must be string-bytearray") end
|
||||
return dev.write(handle, txt)
|
||||
end
|
||||
}, closer
|
||||
end
|
||||
end
|
90
code/libs/sys-secpolicy.lua
Normal file
90
code/libs/sys-secpolicy.lua
Normal file
@ -0,0 +1,90 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- CRITICAL FILE!
|
||||
-- This file defines how your KittenOS NEO system responds to access requests.
|
||||
-- Modification, renaming or deletion can disable security features.
|
||||
-- Usually, a change that breaks the ability for the file to do it's job will cause the "failsafe" to activate,
|
||||
-- and for the system to become unable to run user applications.
|
||||
-- However - I would not like to test this in a situation where said user applications were in any way untrusted,
|
||||
-- for example, if you downloaded them from the Internet, or in particular if someone forwarded them over Discord.
|
||||
-- IRC is usually pretty safe, but no guarantees.
|
||||
|
||||
-- Returns "allow", "deny", or "ask".
|
||||
local actualPolicy = function (pkg, pid, perm)
|
||||
-- System stuff is allowed.
|
||||
if pkg:sub(1, 4) == "sys-" then
|
||||
return "allow"
|
||||
end
|
||||
-- <The following is for apps & services>
|
||||
-- x.neo.pub (aka Icecap) is open to all
|
||||
if perm:sub(1, 10) == "x.neo.pub." then
|
||||
return "allow"
|
||||
end
|
||||
-- This is to ensure the prefix naming scheme is FOLLOWED!
|
||||
-- sys- : System, part of KittenOS NEO and thus tries to present a "unified fragmented interface" in 'neo'
|
||||
-- app- : Application - these can have ad-hoc relationships. It is EXPECTED these have a GUI
|
||||
-- svc- : Service - Same as Application but with no expectation of desktop usability
|
||||
-- Libraries "have no rights" as they are essentially loadable blobs of Lua code.
|
||||
-- They have access via the calling program, and have a subset of the NEO Kernel API
|
||||
local pfx = nil
|
||||
if pkg:sub(1, 4) == "app-" then pfx = "app" end
|
||||
if pkg:sub(1, 4) == "svc-" then pfx = "svc" end
|
||||
if pfx then
|
||||
-- Apps can register with their own name
|
||||
if perm == "r." .. pfx .. "." .. pkg:sub(5) then
|
||||
return "allow"
|
||||
end
|
||||
end
|
||||
-- Userlevel has no other registration rights
|
||||
if perm:sub(1, 2) == "r." then
|
||||
return "deny"
|
||||
end
|
||||
-- app/svc stuff is world-accessible
|
||||
if perm:sub(1, 6) == "x.app." then
|
||||
return "allow"
|
||||
end
|
||||
if perm:sub(1, 6) == "x.svc." then
|
||||
return "allow"
|
||||
end
|
||||
-- For hardware access, ASK!
|
||||
return "ask"
|
||||
end
|
||||
|
||||
return function (neoux, settings, pkg, pid, perm, rsp)
|
||||
local res = actualPolicy(pkg, pid, perm)
|
||||
if res == "ask" then
|
||||
res = settings.getSetting("perm|" .. pkg .. "|" .. perm) or "ask"
|
||||
end
|
||||
if res == "ask" then
|
||||
local fmt = neoux.fmtText(unicode.safeTextFormat(string.format("%s/%i wants:\n%s\nAllow this?", pkg, pid, perm)), 20)
|
||||
|
||||
local always = "Always"
|
||||
local yes = "Yes"
|
||||
local no = "No"
|
||||
local totalW = (#yes) + (#always) + (#no) + 8
|
||||
neoux.create(20, #fmt + 2, "security", neoux.tcwindow(20, #fmt + 3, {
|
||||
neoux.tcbutton(1, #fmt + 2, no, function (w)
|
||||
rsp(false)
|
||||
w.close()
|
||||
end),
|
||||
neoux.tcbutton(totalW - ((#yes) + 1), #fmt + 2, yes, function (w)
|
||||
rsp(true)
|
||||
w.close()
|
||||
end),
|
||||
neoux.tcbutton((#yes) + 3, #fmt + 2, always, function (w)
|
||||
settings.setSetting("perm|" .. pkg .. "|" .. perm, "allow")
|
||||
rsp(true)
|
||||
w.close()
|
||||
end),
|
||||
neoux.tchdivider(1, #fmt + 1, 21),
|
||||
neoux.tcrawview(1, 1, fmt),
|
||||
}, function (w)
|
||||
rsp(false)
|
||||
w.close()
|
||||
end, 0xFFFFFF, 0))
|
||||
else
|
||||
rsp(res == "allow")
|
||||
end
|
||||
end
|
||||
|
58
com2/bdivide.lua
Normal file
58
com2/bdivide.lua
Normal file
@ -0,0 +1,58 @@
|
||||
-- BDIVIDE
|
||||
-- format:
|
||||
-- 0-127 for constants
|
||||
-- <block + 128>, <(len - 3) * 2, + lowest bit is upper bit of position>, <position - 1>
|
||||
|
||||
io.write("\x00") -- initiation character
|
||||
|
||||
local blockCache = {}
|
||||
local window = 0
|
||||
local blockUse = 128
|
||||
for i = 128, 128 + blockUse - 1 do
|
||||
blockCache[i] = ("\x00"):rep(512)
|
||||
end
|
||||
|
||||
local function runBlock(blk)
|
||||
-- firstly, get current block index
|
||||
local blockIndex = window + 128
|
||||
window = (window + 1) % blockUse
|
||||
blockCache[blockIndex] = ""
|
||||
-- ok, now work on the problem
|
||||
local i = 1
|
||||
while i <= #blk do
|
||||
local bestData = blk:sub(i, i)
|
||||
local bestRes = bestData
|
||||
local bestScore = 1
|
||||
for bid = 128, 128 + blockUse - 1 do
|
||||
for lm = 0, 127 do
|
||||
local al = lm + 3
|
||||
local pfx = blk:sub(i, i + al - 1)
|
||||
if #pfx ~= al then
|
||||
break
|
||||
end
|
||||
local p = blockCache[bid]:find(pfx, 1, true)
|
||||
if not p then
|
||||
break
|
||||
end
|
||||
local score = al / 3
|
||||
if score > bestScore then
|
||||
bestData = string.char(bid) .. string.char((lm * 2) + math.floor((p - 1) / 256)) .. string.char((p - 1) % 256)
|
||||
bestRes = pfx
|
||||
bestScore = score
|
||||
end
|
||||
end
|
||||
end
|
||||
-- ok, encode!
|
||||
io.write(bestData)
|
||||
blockCache[blockIndex] = blockCache[blockIndex] .. bestRes
|
||||
i = i + #bestRes
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
local blkd = io.read(512)
|
||||
runBlock(blkd)
|
||||
if #blkd < 512 then
|
||||
return
|
||||
end
|
||||
end
|
64
com2/bundiv.lua
Normal file
64
com2/bundiv.lua
Normal file
@ -0,0 +1,64 @@
|
||||
local sector = io.write -- XX
|
||||
-- BUNDIVIDE reference implementation for integration XX
|
||||
local Cs,Cbu,Cb,Cw,Cp,Ci,CP,CB,CD={},128,"",128,""
|
||||
CP=function(d,b,i)
|
||||
i=1
|
||||
while i<=#d do
|
||||
b=d:byte(i)
|
||||
i=i+1
|
||||
if b==127 then
|
||||
b=d:byte(i)
|
||||
i=i+1
|
||||
if b==127 then
|
||||
b=d:byte(i)+254
|
||||
i=i+1
|
||||
else
|
||||
b=b+127
|
||||
end
|
||||
end
|
||||
Cp=Cp..string.char(b)
|
||||
if #Cp==512 then
|
||||
sector(Cp)
|
||||
Cp=""
|
||||
end
|
||||
end
|
||||
end
|
||||
for i=128,127+Cbu do Cs[i]=("\x00"):rep(512) end
|
||||
Cs[Cw]=""
|
||||
CB=function(d,i,d2,x,y)
|
||||
i=1
|
||||
while i<=#d-2 do
|
||||
b=d:byte(i)
|
||||
d2=d:sub(i,i)
|
||||
i=i+1
|
||||
if not Ci then
|
||||
if b==0then
|
||||
Ci=1
|
||||
end
|
||||
else
|
||||
if b>=128then
|
||||
x=d:byte(i)i=i+1
|
||||
y=(d:byte(i)+((x%2)*256))i=i+1
|
||||
d2=Cs[b]:sub(y+1,y+3+math.floor(x/2))
|
||||
end
|
||||
Cs[Cw]=Cs[Cw]..d2
|
||||
if #Cs[Cw]>=512then
|
||||
CP(Cs[Cw])
|
||||
Cw=((Cw-127)%Cbu)+128
|
||||
Cs[Cw]=""
|
||||
end
|
||||
end
|
||||
end
|
||||
return i
|
||||
end
|
||||
CD=function(d)
|
||||
Cb=Cb..d
|
||||
Cb=Cb:sub(CB(Cb))
|
||||
end
|
||||
CD(io.read("*a")) -- XX
|
||||
--D.remove("init-bdivide.lua")--
|
||||
--D.rename("init.lua","init-bdivide.lua")--
|
||||
--local Ch=D.open("init-bdivide.lua","rb")--
|
||||
--dieCB=function()D.close(Ch)D.remove("init-bdivide.lua")end--
|
||||
--while true do local t=D.read(Ch, 64)if not t then break end CD(t)end--
|
||||
CD("\x00\x00")CP(Cs[Cw])
|
17
com2/preproc.lua
Normal file
17
com2/preproc.lua
Normal file
@ -0,0 +1,17 @@
|
||||
-- PREPROC: preprocess input
|
||||
while true do
|
||||
local c = io.read(1)
|
||||
if not c then return end
|
||||
local bc = c:byte()
|
||||
if bc < 127 then
|
||||
io.write(c)
|
||||
else
|
||||
if bc <= 253 then
|
||||
-- 127(0) through 253(126)
|
||||
io.write("\x7F" .. string.char(bc - 127))
|
||||
else
|
||||
-- 254(0) through 255 (1)
|
||||
io.write("\x7F\x7F" .. string.char(bc - 254))
|
||||
end
|
||||
end
|
||||
end
|
32
heroes.lua
Normal file
32
heroes.lua
Normal file
@ -0,0 +1,32 @@
|
||||
-- SYSTEM HEROES.
|
||||
-- Nabbed Sonic Heroes lyrics follow:
|
||||
-- "What comes up, must come down... "
|
||||
-- "Yet, my feet don't touch the ground..."
|
||||
|
||||
-- arg is the size of the code.tar file
|
||||
local arg = ...
|
||||
os.execute("lua com2/preproc.lua < code.tar | lua com2/bdivide.lua > com2/code.tar.bd")
|
||||
os.execute("cat insthead.lua")
|
||||
print("sC=" .. math.ceil(tonumber(arg) / 512))
|
||||
local src = io.open("com2/bundiv.lua", "r")
|
||||
while true do
|
||||
local line = src:read()
|
||||
if not line then
|
||||
src:close()
|
||||
io.write("--[[")
|
||||
io.flush()
|
||||
os.execute("cat com2/code.tar.bd")
|
||||
io.write("]]")
|
||||
return
|
||||
end
|
||||
local endix = line:sub(#line-1,#line)
|
||||
if endix ~= "XX" then
|
||||
if endix == "--" then
|
||||
-- included
|
||||
print(line:sub(3,#line-2))
|
||||
else
|
||||
print(line)
|
||||
end
|
||||
end
|
||||
-- XX means ignored.
|
||||
end
|
103
insthead.lua
Normal file
103
insthead.lua
Normal file
@ -0,0 +1,103 @@
|
||||
-- KOSNEO inst.
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
local C, O, G, D = component, computer
|
||||
local sa = C.list("screen", true)()
|
||||
if sa then
|
||||
G = C.list("gpu", true)()
|
||||
if G then
|
||||
G = C.proxy(G)
|
||||
G.bind(sa)
|
||||
G.setForeground(0xFFFFFF)
|
||||
G.setBackground(0x000000)
|
||||
G.setResolution(50, 5)
|
||||
G.setDepth(1)
|
||||
G.fill(1, 1, 50, 5, " ")
|
||||
G.setBackground(0xFFFFFF)
|
||||
G.setForeground(0x000000)
|
||||
G.fill(1, 2, 50, 1, " ")
|
||||
G.set(2, 2, "KittenOS NEO Installer")
|
||||
end
|
||||
end
|
||||
|
||||
D = C.proxy(O.getBootAddress())
|
||||
|
||||
local tF = nil
|
||||
local tFN = "Starting..."
|
||||
local tFSR = 0
|
||||
local tW = 0
|
||||
|
||||
local convoct
|
||||
convoct = function (oct)
|
||||
local v = oct:byte(#oct) - 0x30
|
||||
if #oct > 1 then
|
||||
return (convoct(oct:sub(1, #oct - 1)) * 8) + v
|
||||
end
|
||||
return v
|
||||
end
|
||||
local function tA(s)
|
||||
if tW > 0 then
|
||||
tW = tW - 1
|
||||
return
|
||||
end
|
||||
if tF then
|
||||
local ta = math.min(512, tFSR)
|
||||
D.write(tF, s:sub(1, ta))
|
||||
tFSR = tFSR - ta
|
||||
if tFSR == 0 then
|
||||
D.close(tF)
|
||||
tF = nil
|
||||
end
|
||||
else
|
||||
tFN = s:sub(1, 100):gsub("\x00", "")
|
||||
local sz = convoct(s:sub(125, 135))
|
||||
if tFN:sub(1, 5) ~= "code/" then
|
||||
tW = math.ceil(sz / 512)
|
||||
else
|
||||
tFN = tFN:sub(6)
|
||||
if tFN == "" then
|
||||
return
|
||||
end
|
||||
if tFN:sub(#tFN) == "/" then
|
||||
tW = math.ceil(sz / 512)
|
||||
D.makeDirectory(tFN)
|
||||
else
|
||||
tF = D.open(tFN, "wb")
|
||||
tFSR = sz
|
||||
if tFSR == 0 then
|
||||
D.close(tF)
|
||||
tF = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local dieCB = function () end
|
||||
|
||||
local sN = 0
|
||||
local sC = 0
|
||||
|
||||
local function sector(n)
|
||||
tA(n)
|
||||
sN = sN + 1
|
||||
if G then
|
||||
local a = sN / sC
|
||||
G.fill(1, 2, 50, 1, " ")
|
||||
G.set(2, 2, "KittenOS NEO Installer : " .. tFN)
|
||||
G.setForeground(0xFFFFFF)
|
||||
G.setBackground(0x000000)
|
||||
G.fill(2, 4, 48, 1, " ")
|
||||
G.setBackground(0xFFFFFF)
|
||||
G.setForeground(0x000000)
|
||||
G.fill(2, 4, math.ceil(48 * a), 1, " ")
|
||||
end
|
||||
if sN % 8 == 0 then
|
||||
O.pullSignal(0.05)
|
||||
end
|
||||
if sN == sC then
|
||||
dieCB()
|
||||
O.shutdown(true)
|
||||
end
|
||||
end
|
24
mkucinst.lua
Normal file
24
mkucinst.lua
Normal file
@ -0,0 +1,24 @@
|
||||
os.execute("tar -cf code.tar code")
|
||||
os.execute("cat insthead.lua > inst.lua")
|
||||
local f = io.open("inst.lua", "ab")
|
||||
|
||||
local df = io.open("code.tar", "rb")
|
||||
local sc = 0
|
||||
while true do
|
||||
local d = df:read(512)
|
||||
if not d then break end
|
||||
sc = sc + 1
|
||||
end
|
||||
df:close()
|
||||
local df = io.open("code.tar", "rb")
|
||||
f:write("sC = " .. sc .. "\n")
|
||||
while true do
|
||||
local d = df:read(512)
|
||||
if d then
|
||||
f:write(string.format("sector(%q)", d))
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
df:close()
|
||||
f:close()
|
BIN
preSH.tar.gz
Normal file
BIN
preSH.tar.gz
Normal file
Binary file not shown.
48
repository/app-eeprog.lua
Normal file
48
repository/app-eeprog.lua
Normal file
@ -0,0 +1,48 @@
|
||||
-- This is released into the public domain.
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- app-eeprom: Tiny EEPROM flasher
|
||||
-- Example of a tiny app a user could write relatively quickly if they have NEO system knowledge
|
||||
-- Note the high amount of synchronous routines used here.
|
||||
-- For a tiny app like this, it's fine, and KittenOS NEO makes sure it won't interfere.
|
||||
-- (Plus, this isn't a library, so that's not a concern)
|
||||
-- Really, in KittenOS NEO, the only program you break is your own
|
||||
|
||||
local event = require("event")(neo)
|
||||
local neoux = require("neoux")(event, neo)
|
||||
|
||||
local eeprom = neo.requestAccess("c.eeprom")
|
||||
if eeprom then
|
||||
eeprom = eeprom.list()()
|
||||
end
|
||||
if not eeprom then
|
||||
error("No EEPROM access")
|
||||
end
|
||||
|
||||
neoux.startDialog("NOTE: If this program is used improperly, it can require EEPROM replacement.\nOnly use trusted EEPROMs.", "eeprom-flash", true)
|
||||
|
||||
local fd = neoux.fileDialog(false)
|
||||
if not fd then return end
|
||||
local eepromCode = fd.read("*a")
|
||||
fd.close()
|
||||
eeprom.set(eepromCode)
|
||||
|
||||
neoux.startDialog("The flash was successful - the next dialog can change the label.", "eeprom-flash", true)
|
||||
|
||||
-- text dialog
|
||||
local done = false
|
||||
neoux.create(20, 1, "label", neoux.tcwindow(20, 1, {
|
||||
neoux.tcfield(1, 1, 20, function (nv)
|
||||
if not nv then
|
||||
return eeprom.getLabel()
|
||||
end
|
||||
eeprom.setLabel(nv)
|
||||
end)
|
||||
}, function (w)
|
||||
w.close()
|
||||
done = true
|
||||
end, 0xFFFFFF, 0))
|
||||
|
||||
while not done do
|
||||
event.pull()
|
||||
end
|
3
repository/list
Normal file
3
repository/list
Normal file
@ -0,0 +1,3 @@
|
||||
app app-eeprog
|
||||
This flashes EEPROMs, an example of a package that does not come by default with KittenOS NEO.
|
||||
end
|
Loading…
Reference in New Issue
Block a user