mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-11-30 14:18:06 +11:00
821 lines
22 KiB
Lua
821 lines
22 KiB
Lua
-- 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 Glacier.
|
|
-- "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. The user logs in
|
|
-- 2. Bristol starts up Everest, and frees the primary monitor
|
|
-- 3. The primary monitor is claimed by Everest and becomes monitor 1
|
|
-- 4. After a small time, Bristol dies, unclaiming all monitors
|
|
-- 5. Everest claims the new monitors, and the desktop session begins
|
|
-- 6. Everest shuts down for some reason,
|
|
-- sys-init gets started UNLESS endSession(false) was used
|
|
|
|
local everestProvider = neo.requireAccess("r.neo.pub.window", "registering npw")
|
|
local everestSessionProvider = neo.requireAccess("r.neo.sys.session", "registering nsse")
|
|
|
|
-- 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.requireAccess("x.neo.sys.screens", "access to screens")
|
|
|
|
neo.requestAccess("s.h.clipboard")
|
|
neo.requestAccess("s.h.touch")
|
|
neo.requestAccess("s.h.drag")
|
|
neo.requestAccess("s.h.drop")
|
|
neo.requestAccess("s.h.scroll")
|
|
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 until monitors show up
|
|
monitors[0] = {nil, nil, 160, 50}
|
|
|
|
-- {monitor, x, y, w, h, callback}
|
|
-- callback events are:
|
|
-- key ka kc down
|
|
-- line y
|
|
local surfaces = {}
|
|
|
|
-- Last Interact Monitor
|
|
local lIM = 1
|
|
|
|
-- Stops the main loop
|
|
local shuttingDown = false
|
|
|
|
-- Also used for settings.
|
|
local savingThrow = neo.requestAccess("x.neo.sys.manage")
|
|
|
|
local function suggestAppsStop()
|
|
for k, v in ipairs(surfaces) do
|
|
for i = 1, 4 do
|
|
v[6]("close")
|
|
end
|
|
end
|
|
end
|
|
|
|
local function dying()
|
|
local primary = (monitors[1] or {})[2] or ""
|
|
for _, v in ipairs(monitors) do
|
|
pcall(screens.disclaim, v[2])
|
|
end
|
|
monitors = {}
|
|
neo.executeAsync("sys-init", primary)
|
|
-- 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.
|
|
-- Also note, should savingThrow fail, neo.dead is now a thing.
|
|
for _, v in ipairs(surfaces) do
|
|
pcall(v[6], "line", 1)
|
|
pcall(v[6], "line", 2)
|
|
end
|
|
surfaces = {}
|
|
end
|
|
if savingThrow then
|
|
savingThrow.registerForShutdownEvent()
|
|
savingThrow.registerSavingThrow(dying)
|
|
end
|
|
|
|
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
|
|
|
|
-- Always use the first if the GPU has been rebound
|
|
local function monitorResetBF(m)
|
|
m[5] = -1
|
|
m[6] = -1
|
|
end
|
|
local function monitorGPUColours(m, cb, bg, fg)
|
|
local nbg = m[5]
|
|
local nfg = m[6]
|
|
if nbg ~= bg then
|
|
pcall(cb.setBackground, bg)
|
|
m[5] = bg
|
|
end
|
|
if nfg ~= fg then
|
|
pcall(cb.setForeground, fg)
|
|
m[6] = fg
|
|
end
|
|
end
|
|
|
|
-- Status line at top of screen
|
|
local statusLine = nil
|
|
|
|
local function doBackgroundLine(m, mg, bdx, bdy, bdl)
|
|
if statusLine and (bdy == 1) then
|
|
-- Status bar
|
|
monitorGPUColours(m, mg, 0x000000, 0xFFFFFF)
|
|
local str = unicode.sub(statusLine, bdx, bdx + bdl - 1)
|
|
local strl = unicode.len(str)
|
|
pcall(mg.set, bdx, bdy, unicode.undoSafeTextFormat(str))
|
|
pcall(mg.fill, bdx + strl, bdy, bdl - strl, 1, " ")
|
|
else
|
|
monitorGPUColours(m, mg, 0x000040, 0)
|
|
pcall(mg.fill, bdx, bdy, bdl, 1, " ")
|
|
end
|
|
end
|
|
|
|
local function updateRegion(monitorId, x, y, w, h, surfaceSpanCache)
|
|
if not renderingAllowed() then return end
|
|
local m = monitors[monitorId]
|
|
local mg, rb = m[1]()
|
|
if not mg then return end
|
|
if rb then
|
|
monitorResetBF(m)
|
|
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
|
|
-- WCHAX: 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
|
|
-- Background must occur first due to wide char weirdness
|
|
if backgroundMarkStart then
|
|
local bdx, bdy, bdl = backgroundMarkStart + x - 1, span + y - 1, sx - backgroundMarkStart
|
|
doBackgroundLine(m, mg, bdx, bdy, bdl)
|
|
backgroundMarkStart = nil
|
|
end
|
|
if not surfaceSpanCache[monitorId .. "_" .. t .. "_" .. ty] then
|
|
surfaceSpanCache[monitorId .. "_" .. t .. "_" .. ty] = true
|
|
surfaces[t][6]("line", ty)
|
|
end
|
|
elseif not backgroundMarkStart then
|
|
backgroundMarkStart = sx
|
|
end
|
|
end
|
|
if backgroundMarkStart then
|
|
doBackgroundLine(monitors[monitorId], mg, backgroundMarkStart + x - 1, span + y - 1, (w - backgroundMarkStart) + 1)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function updateStatus()
|
|
statusLine = "Λ-¶: menu (launch 'control' to logout)"
|
|
if surfaces[1] then
|
|
if #monitors > 1 then
|
|
-- 123456789X123456789X123456789X123456789X123456789X
|
|
statusLine = "Λ-+: move, Λ-Z: switch, Λ-X: swMonitor, Λ-C: close"
|
|
else
|
|
statusLine = "Λ-+: move, Λ-Z: switch, Λ-C: close"
|
|
end
|
|
end
|
|
statusLine = unicode.safeTextFormat(statusLine)
|
|
for k, v in ipairs(monitors) do
|
|
updateRegion(k, 1, 1, v[3], 1, {})
|
|
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.max(1, math.min(monitors[monitor][4] - (h - 1), y))
|
|
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
|
|
local k = 1
|
|
while k <= #monitors do
|
|
local v = monitors[k]
|
|
local mon, rb = v[1]()
|
|
if rb then
|
|
monitorResetBF(v)
|
|
end
|
|
if mon then
|
|
-- This *can* return null if something went wonky. Let's detect that
|
|
v[3], v[4] = mon.getResolution()
|
|
if not v[3] then
|
|
neo.emergency("everest: monitor went AWOL and nobody told me u.u")
|
|
table.remove(monitors, k)
|
|
v = nil
|
|
end
|
|
end
|
|
if v then
|
|
updateRegion(k, 1, 1, v[3], v[4], {})
|
|
k = k + 1
|
|
end
|
|
end
|
|
updateStatus()
|
|
end
|
|
|
|
-- NOTE: If the M, X, Y, W and H are the same, this function ignores you, unless you put , true on the end.
|
|
local function moveSurface(surface, m, x, y, w, h, force)
|
|
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 and ow == w and oh == h then
|
|
if ox == x and oy == y and not force then
|
|
return
|
|
end
|
|
-- note: this doesn't always work due to WC support, and due to resize-to-repaint
|
|
if renderingAllowed() and not force then
|
|
local cb, b = monitors[m][1]()
|
|
if b then
|
|
monitorResetBF(monitors[m])
|
|
end
|
|
if cb then
|
|
cb.copy(ox, oy, w, h, x - ox, y - oy)
|
|
if surface == surfaces[1] then
|
|
local cacheControl = {}
|
|
for i = 1, h do
|
|
cacheControl[om .. "_1_" .. i] = true
|
|
end
|
|
updateRegion(om, ox, oy, ow, oh, cacheControl)
|
|
return
|
|
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, rb = m[1]()
|
|
if not cb then return end
|
|
if rb then
|
|
monitorResetBF(m)
|
|
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)
|
|
-- rely on undoSafeTextFormat for this transform now
|
|
monitorGPUColours(m, cb, bg, fg)
|
|
pcall(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 basePalT2 = {
|
|
-- on T2 we provide 'system colours' by default
|
|
0x000000, 0x0080FF, 0x000040, 0xFFFFFF,
|
|
-- stuff above cannot be altered by user applications, to prevent graphical glitches.
|
|
-- Provide some shades of grey to try and prevent accidental chroma.
|
|
0x182828, 0x404040, 0x686868, 0x909090,
|
|
0xB8B8B8, 0xE0E0E0, 0x800080, 0xFF0000,
|
|
0x808000, 0x00FF00, 0x008080, 0x0000FF
|
|
}
|
|
local basePalT3 = {
|
|
-- on T3 we provide the Tier 3 pal.
|
|
0x0F0F0F, 0x1E1E1E, 0x2D2D2D, 0x3C3C3C,
|
|
0x4B4B4B, 0x5A5A5A, 0x696969, 0x787878,
|
|
0x878787, 0x969696, 0xA5A5A5, 0xB4B4B4,
|
|
0xC3C3C3, 0xD2D2D2, 0xE1E1E1, 0xF0F0F0
|
|
}
|
|
|
|
local function setSurfacePalette(surf, pal)
|
|
if neo.dead then return 0 end
|
|
local m = monitors[surf[1]]
|
|
if not m then return 0 end
|
|
local cb, rb = m[1]()
|
|
if not cb then return 0 end
|
|
local dok, depth = pcall(cb.getDepth)
|
|
if not dok then depth = 4 end
|
|
if rb then
|
|
monitorResetBF(m)
|
|
end
|
|
local ko = -1
|
|
local unlocked = false
|
|
if not rawequal(pal, nil) then
|
|
neo.ensureType(pal, "table")
|
|
if depth < 8 then
|
|
ko = 3 -- start overriding at indexes 4+
|
|
end
|
|
elseif depth < 8 then
|
|
pal = basePalT2
|
|
unlocked = true
|
|
else
|
|
pal = basePalT3
|
|
unlocked = true
|
|
end
|
|
for k, v in ipairs(pal) do
|
|
-- prevent graphical glitches for
|
|
-- critical system colours on T3
|
|
local av = v % 0x1000000
|
|
if unlocked or (av ~= 0xFFFFFF and
|
|
av ~= 0x000000 and
|
|
av ~= 0x0080FF and
|
|
av ~= 0x000040) then
|
|
local ok = pcall(cb.setPaletteColor, k + ko, v)
|
|
if not ok then return k - 1 end
|
|
end
|
|
end
|
|
return #pal
|
|
end
|
|
|
|
local function changeFocus(oldSurface, optcache)
|
|
local ns1 = surfaces[1]
|
|
optcache = optcache or {}
|
|
if ns1 ~= oldSurface then
|
|
if oldSurface then
|
|
setSurfacePalette(oldSurface, nil)
|
|
oldSurface[6]("focus", false)
|
|
end
|
|
if ns1 then
|
|
setSurfacePalette(ns1, nil)
|
|
ns1[6]("focus", true)
|
|
end
|
|
updateStatus()
|
|
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 neo.dead then error("everest died") end
|
|
if shuttingDown or waitingShutdownCallback then error("system shutting down") 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 surf = {math.min(#monitors, math.max(1, lIM)), 1, 2, w, h}
|
|
if h >= monitors[surf[1]][4] then
|
|
surf[3] = 1
|
|
end
|
|
local focusState = false
|
|
local llid = lid
|
|
lid = lid + 1
|
|
local specialDragHandler
|
|
surf[6] = function (ev, a, b, c, d, e)
|
|
-- Must forward surface events
|
|
if ev == "focus" then
|
|
focusState = a
|
|
end
|
|
if ev == "touch" then
|
|
specialDragHandler = nil
|
|
if math.floor(b) == 1 then
|
|
if e == 1 then
|
|
sendSig(llid, "close")
|
|
return
|
|
end
|
|
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
|
|
ofsSurface(surf, ofsX, ofsY)
|
|
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 == "scroll" or ev == "drop" then
|
|
specialDragHandler = nil
|
|
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
|
|
a = a - 1
|
|
end
|
|
sendSig(llid, ev, a, b, c, d, e)
|
|
end
|
|
local osrf = surfaces[1]
|
|
table.insert(surfaces, 1, surf)
|
|
surfaceOwners[surf] = pid
|
|
changeFocus(osrf)
|
|
return {
|
|
id = llid,
|
|
setSize = function (w, h)
|
|
if neo.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, true)
|
|
return w, (h - 1)
|
|
end,
|
|
getDepth = function ()
|
|
if neo.dead then return 1 end
|
|
local m = monitors[surf[1]]
|
|
if not m then return 1 end
|
|
local cb, rb = m[1]()
|
|
if not cb then return 1 end
|
|
if rb then
|
|
monitorResetBF(m)
|
|
end
|
|
return cb.getDepth()
|
|
end,
|
|
span = function (x, y, text, bg, fg)
|
|
if neo.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,
|
|
recommendPalette = function (pal)
|
|
if not focusState then return 0 end
|
|
return setSurfacePalette(surf, pal)
|
|
end,
|
|
close = function ()
|
|
if neo.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)
|
|
-- used later on for lost monitor, too
|
|
local function disclaimMonitor(mon)
|
|
neo.ensureType(mon, "string")
|
|
screens.disclaim(mon)
|
|
for k, v in ipairs(monitors) do
|
|
if v[2] == mon then
|
|
table.remove(monitors, k)
|
|
reconcileAll()
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
everestSessionProvider(function (pkg, pid, sendSig)
|
|
return {
|
|
endSession = function (gotoBristol)
|
|
neo.ensureType(gotoBristol, "boolean")
|
|
shuttingDown = true
|
|
if gotoBristol then
|
|
suggestAppsStop()
|
|
dying()
|
|
end
|
|
end,
|
|
getMonitors = function ()
|
|
local details = {}
|
|
for k, v in ipairs(monitors) do
|
|
details[k] = v[2]
|
|
end
|
|
return details
|
|
end,
|
|
disclaimMonitor = disclaimMonitor
|
|
}
|
|
end)
|
|
-- THE EVEREST SESSION API ENDS
|
|
|
|
local function startLauncher()
|
|
if not waitingShutdownCallback then
|
|
local lApp = "app-launcher"
|
|
if savingThrow then
|
|
lApp = savingThrow.getSetting("sys-everest.launcher") or lApp
|
|
end
|
|
if lApp then
|
|
neo.executeAsync(lApp)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 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(ku, ka, kc, down)
|
|
local ku = screens.getMonitorByKeyboard(ku)
|
|
if not ku then return end
|
|
for k, v in ipairs(monitors) do
|
|
if v[2] == mu then
|
|
lIM = k
|
|
end
|
|
end
|
|
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 then
|
|
startLauncher()
|
|
end
|
|
return
|
|
end
|
|
end
|
|
if focus then
|
|
if kc ~= 56 then
|
|
lIM = focus[1]
|
|
end
|
|
focus[6]("key", ka, kc, down)
|
|
end
|
|
end
|
|
|
|
-- take all displays!
|
|
local function performClaim(s3)
|
|
local gpu, _ = screens.claim(s3)
|
|
local gpucb = gpu and (gpu())
|
|
if gpucb then
|
|
local w, h = gpucb.getResolution()
|
|
table.insert(monitors, {gpu, s3, w, h, -1, -1})
|
|
setSurfacePalette({#monitors}, nil)
|
|
-- 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
|
|
|
|
for _, v in ipairs(screens.getClaimable()) do
|
|
performClaim(v)
|
|
end
|
|
|
|
while not shuttingDown do
|
|
local s = {coroutine.yield()}
|
|
if renderingAllowed() then
|
|
if s[1] == "h.key_down" then
|
|
key(s[2], s[3], s[4], true)
|
|
end
|
|
if s[1] == "h.key_up" then
|
|
key(s[2], 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
|
|
lIM = k
|
|
local x, y = math.ceil(s[3]), math.ceil(s[4])
|
|
local ix, iy = s[3] - math.floor(x), s[4] - math.floor(y)
|
|
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, ix, iy, s[5])
|
|
else
|
|
if s[5] == 1 then startLauncher() end
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if s[1] == "h.drag" or s[1] == "h.drop" or s[1] == "h.scroll" 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.ceil(s[3]) - focus[2]) + 1, (math.ceil(s[4]) - focus[3]) + 1
|
|
local ix, iy = s[3] - math.floor(s[3]), s[4] - math.floor(s[4])
|
|
-- Ok, so let's see...
|
|
focus[6](s[1]:sub(3), x, y, ix, iy, s[5])
|
|
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))
|
|
if os1 == surf then
|
|
os1 = nil
|
|
end
|
|
updateRegion(surf[1], surf[2], surf[3], surf[4], surf[5], {})
|
|
end
|
|
checkWSC()
|
|
if os1 then
|
|
changeFocus(os1)
|
|
else
|
|
changeFocus(surfaces[1])
|
|
end
|
|
end
|
|
if s[1] == "x.neo.sys.screens" then
|
|
if s[2] == "available" then
|
|
performClaim(s[3])
|
|
end
|
|
if s[2] == "lost" then
|
|
disclaimMonitor(s[3])
|
|
end
|
|
end
|
|
if s[1] == "x.neo.sys.manage" then
|
|
if s[2] == "shutdown" then
|
|
waitingShutdownCallback = s[4]
|
|
suggestAppsStop()
|
|
checkWSC()
|
|
end
|
|
end
|
|
end
|