OC-KittenOS/code/apps/sys-glacier.lua

342 lines
9.1 KiB
Lua

-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
-- 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 shutdownFin = neo.requireAccess("k.computer", "shutting down").shutdown
local primary = neo.requireAccess("c.filesystem", "settings I/O").primary
local gpus = neo.requireAccess("c.gpu", "screen control").list
local screens = neo.requireAccess("c.screen", "screen control").list
neo.requireAccess("s.h.component_added", "HW management")
neo.requireAccess("s.h.component_removed", "HW management")
-- 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",
["sys-everest.launcher"] = "app-launcher",
["run.sys-icecap"] = "yes",
-- scr.w/h/d/t.<uuid>
}
local function loadSettings()
pcall(function ()
local fw = require("sys-filewrap").create
local se = require("serial").deserialize
local st = fw(primary, "data/sys-glacier/sysconf.lua", false)
local cfg = st.read("*a")
st.close()
st = nil
fw = nil
cfg = se(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").create
local se = require("serial").serialize
primary.makeDirectory("data/sys-glacier")
local st = fw(primary, "data/sys-glacier/sysconf.lua", true)
st.write(se(settings))
st.close()
end
-- [i] = screenProxy
local monitorPool = {}
-- [screenAddr] = {gpu, claimedLoseCallback}
local monitorClaims = {}
-- [gpuAddr] = monitorAddr
local currentGPUBinding = {}
-- [gpuAddr] = userCount
local currentGPUUsers = {}
-- Thanks to Skye for this design!
local keyboardMonCacheK, keyboardMonCacheV = nil
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 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
v("set_setting", name, val)
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
-- Settings API
local mBase = {
getSetting = function (name)
neo.ensureType(name, "string")
return settings[name]
end,
listSettings = function ()
local s = {}
for k, v in pairs(settings) do
table.insert(s, k)
end
return s
end,
delSetting = function (name)
neo.ensureType(name, "string")
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)
neo.ensureType(name, "string")
neo.ensureType(val, "string")
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,
shutdown = function (reboot)
neo.ensureType(reboot, "boolean")
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(shutdownMode)
end
end)
end
if counter == 0 then
shutdownFin(shutdownMode)
end
-- donkonit will shutdown when the timer is hit.
end
}
donkonitSPProvider(function (pkg, pid, sendSig)
targs[pid] = sendSig
local n = {
registerForShutdownEvent = function ()
targsSD[pid] = sendSig
end,
registerSavingThrow = function (st)
neo.ensureType(st, "function")
targsST[pid] = st
end
}
return setmetatable(n, {
__index = mBase,
__metatable = 0
})
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)
if keyboardMonCacheK == kb then
return keyboardMonCacheV
end
for v in screens() do
for _, v2 in ipairs(v.getKeyboards()) do
if v2 == kb then
keyboardMonCacheK, keyboardMonCacheV = kb, v.address
return v.address
end
end
end
end,
getClaimable = function ()
local c = {}
-- do we have gpu?
if not gpus()() then return c end
for _, v in ipairs(monitorPool) do
table.insert(c, v.address)
end
return c
end,
claim = function (...) -- see sys-gpualloc
return require("sys-gpualloc")(
gpus, screens,
getMonitorSettings, settings, sRattle, saveSettings,
announceFreeMonitor, pid, claimed, sendSig,
monitorClaims, monitorPool, currentGPUUsers, currentGPUBinding,
...)
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 = {}
keyboardMonCacheK, keyboardMonCacheV = nil, nil
for k, v in pairs(monitorClaims) do
v[2](true)
end
monitorClaims = {}
for m in screens() do
table.insert(monitorPool, m)
if gpus()() then
announceFreeMonitor(m.address)
end
end
end
rescanDevs()
-- Save any settings made during the above (or just the language)
pcall(saveSettings)
-- --
glacierDCProvider(function (pkg, pid, sendSig)
targsDC[pid] = sendSig
local function sWrap(f)
return function (s, ...)
return f("pub." .. s, ...)
end
end
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 = sWrap(mBase.getSetting),
delSetting = sWrap(mBase.delSetting),
setSetting = sWrap(mBase.setSetting)
}
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" or s[1] == "h.component_removed" then
-- Anything important?
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