mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-11-27 12:58:05 +11:00
429 lines
12 KiB
Lua
429 lines
12 KiB
Lua
-- This is released into the public domain.
|
|
-- No warranty is provided, implied or otherwise.
|
|
|
|
-- s-donkonit : config, shutdown, screens
|
|
|
|
local donkonitSPProvider = neo.requireAccess("r.neo.sys.manage", "creating NEO core APIs") -- Restrict to s-
|
|
-- Doesn't matter what calls this service, because there's a mutex here.
|
|
local donkonitRDProvider = neo.requireAccess("r.neo.sys.screens", "creating NEO core APIs")
|
|
local glacierDCProvider = neo.requireAccess("r.neo.pub.globals", "creating NEO core APIs")
|
|
|
|
local computer = neo.requireAccess("k.computer", "shutting down")
|
|
local fs = neo.requireAccess("c.filesystem", "settings I/O")
|
|
local gpus = neo.requireAccess("c.gpu", "screen control")
|
|
local screens = neo.requireAccess("c.screen", "screen control")
|
|
neo.requireAccess("s.h.component_added", "HW management")
|
|
neo.requireAccess("s.h.component_removed", "HW management")
|
|
neo.requireAccess("s.h.key_down", "Keymap guesswork")
|
|
|
|
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 targsDC = {} -- displaycontrol 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 = "",
|
|
["pub.clipboard"] = "",
|
|
["sys-init.shell"] = "sys-everest",
|
|
["run.sys-icecap"] = "yes",
|
|
-- scr.w/h/d/t.<uuid>
|
|
}
|
|
|
|
local function loadSettings()
|
|
pcall(function ()
|
|
local fw = require("sys-filewrap")
|
|
local se = require("serial")
|
|
local st = fw(fs.primary, "data/sys-glacier/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-glacier")
|
|
local st = fw(fs.primary, "data/sys-glacier/sysconf.lua", true)
|
|
st.write(se.serialize(settings))
|
|
st.close()
|
|
end
|
|
|
|
-- [i] = screenProxy
|
|
local monitorPool = {}
|
|
-- [screenAddr] = {gpu, claimedLoseCallback}
|
|
local monitorClaims = {}
|
|
-- [gpuAddr] = monitorAddr
|
|
local currentGPUBinding = {}
|
|
-- [gpuAddr] = userCount
|
|
local currentGPUUsers = {}
|
|
|
|
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, false)
|
|
currentGPUBinding[v.address] = nil
|
|
local w, h = v.maxResolution()
|
|
local d = v.maxDepth() * w * h
|
|
if d > bestD then
|
|
bestG = v
|
|
bestD = d
|
|
bestU = currentGPUUsers[v.address] or 0
|
|
elseif d == bestD then
|
|
if (currentGPUUsers[v.address] or 0) < bestU then
|
|
bestG = v
|
|
bestD = d
|
|
bestU = currentGPUUsers[v.address] or 0
|
|
end
|
|
end
|
|
end
|
|
return bestG
|
|
end
|
|
|
|
local function sRattle(name, val)
|
|
for _, v in pairs(targs) do
|
|
v("set_setting", name, val)
|
|
end
|
|
if name:sub(1, 4) == "scr." or name:sub(1, 4) == "pub." then
|
|
for k, v in pairs(targsDC) do
|
|
if not targs[k] then
|
|
v("set_setting", name, val)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Settings integration w/ monitors
|
|
local function getMonitorSettings(a)
|
|
local w = tonumber(settings["scr.w." .. a]) or 80
|
|
local h = tonumber(settings["scr.h." .. a]) or 25
|
|
local d = tonumber(settings["scr.d." .. a]) or 8
|
|
local t = ((settings["scr.t." .. a] == "yes") and "yes") or "no"
|
|
w, h, d = math.floor(w), math.floor(h), math.floor(d)
|
|
return w, h, d, t
|
|
end
|
|
local function setupMonitor(gpu, monitor)
|
|
monitor.setPrecise(true)
|
|
monitor.turnOn()
|
|
gpu.bind(monitor.address, false)
|
|
currentGPUBinding[gpu.address] = monitor.address
|
|
local maxW, maxH = gpu.maxResolution()
|
|
local maxD = gpu.maxDepth()
|
|
local w, h, d, t = getMonitorSettings(monitor.address)
|
|
w, h, d = math.min(w, maxW), math.min(h, maxH), math.min(d, maxD)
|
|
if monitor.setTouchModeInverted then
|
|
monitor.setTouchModeInverted(t == "yes")
|
|
else
|
|
t = "no"
|
|
end
|
|
settings["scr.w." .. monitor.address] = tostring(w)
|
|
settings["scr.h." .. monitor.address] = tostring(h)
|
|
settings["scr.d." .. monitor.address] = tostring(d)
|
|
settings["scr.t." .. monitor.address] = t
|
|
sRattle("scr.w." .. monitor.address, tostring(w))
|
|
sRattle("scr.h." .. monitor.address, tostring(h))
|
|
sRattle("scr.d." .. monitor.address, tostring(d))
|
|
sRattle("scr.t." .. monitor.address, t)
|
|
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,
|
|
-- NOTE: REPLICATED IN GB
|
|
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" or name == "pub.clipboard" then val = "" end
|
|
settings[name] = val
|
|
sRattle(name, val)
|
|
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
|
|
-- NOTE: Either a monitor is under application control,
|
|
-- or it's not under any control.
|
|
-- Monitor settings are applied on the transition to control.
|
|
sRattle(name, val)
|
|
pcall(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
|
|
v(false)
|
|
end
|
|
end}
|
|
return {
|
|
getMonitorByKeyboard = function (kb)
|
|
for v in screens.list() do
|
|
for _, v2 in ipairs(v.getKeyboards()) do
|
|
if v2 == kb then
|
|
return v.address
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
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
|
|
currentGPUBinding[gpu] = address
|
|
currentGPUUsers[gpu] = (currentGPUUsers[gpu] or 0) + 1
|
|
local disclaimer = function (wasDevLoss)
|
|
-- we lost it
|
|
monitorClaims[address] = nil
|
|
claimed[address] = nil
|
|
if not wasDevLoss then
|
|
currentGPUUsers[gpu] = currentGPUUsers[gpu] - 1
|
|
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 didBind = false
|
|
if currentGPUBinding[gpu] ~= address then
|
|
v.bind(address, false)
|
|
didBind = true
|
|
end
|
|
currentGPUBinding[gpu] = address
|
|
return v, didBind
|
|
end
|
|
end
|
|
end, v
|
|
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 = {}
|
|
currentGPUBinding = {}
|
|
currentGPUUsers = {}
|
|
local hasGPU = gpus.list()()
|
|
for k, v in pairs(monitorClaims) do
|
|
v[2](true)
|
|
end
|
|
monitorClaims = {}
|
|
for m in screens.list() do
|
|
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()
|
|
-- --
|
|
|
|
glacierDCProvider(function (pkg, pid, sendSig)
|
|
targsDC[pid] = sendSig
|
|
return {
|
|
getKnownMonitors = function ()
|
|
local tbl = {}
|
|
-- yes, this should work fine so long as GMS is the *last* one #luaquirks
|
|
for k, v in ipairs(monitorPool) do
|
|
tbl[k] = {v.address, false, getMonitorSettings(v.address)}
|
|
end
|
|
for k, v in pairs(monitorClaims) do
|
|
table.insert(tbl, {k, true, getMonitorSettings(k)})
|
|
end
|
|
return tbl
|
|
end,
|
|
changeMonitorSetup = function (ma, w, h, d, t)
|
|
neo.ensureType(ma, "string")
|
|
neo.ensureType(w, "number")
|
|
neo.ensureType(h, "number")
|
|
neo.ensureType(d, "number")
|
|
neo.ensureType(t, "string")
|
|
w = math.floor(w)
|
|
h = math.floor(h)
|
|
d = math.floor(d)
|
|
if t ~= "yes" then t = "no" end
|
|
if w < 1 then error("Invalid width") end
|
|
if h < 1 then error("Invalid height") end
|
|
if d < 1 then error("Invalid depth") end
|
|
w, h, d = tostring(w), tostring(h), tostring(d)
|
|
settings["scr.w." .. ma] = w
|
|
settings["scr.h." .. ma] = h
|
|
settings["scr.d." .. ma] = d
|
|
settings["scr.t." .. ma] = t
|
|
sRattle("scr.w." .. ma, w)
|
|
sRattle("scr.h." .. ma, h)
|
|
sRattle("scr.d." .. ma, d)
|
|
sRattle("scr.t." .. ma, t)
|
|
pcall(saveSettings)
|
|
end,
|
|
forceRescan = rescanDevs,
|
|
-- NOTE: "pub." prefixed version of functions in sys.manage
|
|
getSetting = function (name)
|
|
if type(name) ~= "string" then error("Setting name must be string") end
|
|
return settings["pub." .. name]
|
|
end,
|
|
delSetting = function (name)
|
|
if type(name) ~= "string" then error("Setting name must be string") end
|
|
local val = nil
|
|
if name == "clipboard" then val = "" end
|
|
settings["pub." .. name] = val
|
|
sRattle("pub." .. name, val)
|
|
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["pub." .. name] = val
|
|
sRattle("pub." .. name, val)
|
|
pcall(saveSettings)
|
|
end
|
|
}
|
|
end)
|
|
|
|
-- main loop
|
|
|
|
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
|
|
-- Before doing anything, is it worth it?
|
|
if s[3] == "gpu" or s[3] == "screen" then
|
|
rescanDevs()
|
|
end
|
|
end
|
|
if s[1] == "h.component_removed" then
|
|
if s[3] == "gpu" or s[3] == "screen" then
|
|
rescanDevs()
|
|
end
|
|
end
|
|
if s[1] == "k.procdie" then
|
|
targs[s[3]] = nil
|
|
targsDC[s[3]] = nil
|
|
targsSD[s[3]] = nil
|
|
if targsST[s[3]] then
|
|
if s[4] then
|
|
coroutine.resume(coroutine.create(targsST[s[3]]))
|
|
end
|
|
end
|
|
targsST[s[3]] = nil
|
|
if targsRD[s[3]] then
|
|
targsRD[s[3]][2]()
|
|
end
|
|
end
|
|
end
|