mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-11-26 20:38:06 +11:00
dcd7154ec2
This license change should only increase the amount of stuff people can do in countries where the public domain is not a valid concept. If this license somehow restricts you as opposed to the previous one, please file an issue.
794 lines
22 KiB
Lua
794 lines
22 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-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, title}
|
|
-- 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 draggingWindowX, draggingWindowY
|
|
|
|
local function suggestAppsStop()
|
|
for k, v in ipairs(surfaces) do
|
|
for i = 1, 4 do
|
|
v[6](v[8], "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], v[8], "line", 1)
|
|
pcall(v[6], v[8], "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.set, bdx + strl, bdy, (" "):rep(bdl - strl))
|
|
else
|
|
monitorGPUColours(m, mg, 0x000040, 0)
|
|
pcall(mg.set, bdx, bdy, (" "):rep(bdl))
|
|
end
|
|
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 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
|
|
if ty == 1 then
|
|
local lw = surfaces[t][4]
|
|
local bg = 0x0080FF
|
|
local fg = 0x000000
|
|
local tx = "-"
|
|
if t == 1 then
|
|
bg = 0x000000
|
|
fg = 0x0080FF
|
|
tx = "+"
|
|
end
|
|
local vtitle = surfaces[t][7]
|
|
local vto = unicode.len(vtitle)
|
|
if vto < lw then
|
|
vtitle = vtitle .. (tx):rep(lw - vto)
|
|
else
|
|
vtitle = unicode.sub(vtitle, 1, lw)
|
|
end
|
|
handleSpan(surfaces[t], 1, 1, vtitle, bg, fg)
|
|
else
|
|
surfaces[t][6](surfaces[t][8], "line", ty - 1)
|
|
end
|
|
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(x, 1), monitors[monitor][3] - (w - 1))
|
|
y = math.max(2 - h, math.min(monitors[monitor][4], 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
|
|
local monH = monitors[m][4]
|
|
local cutTop = math.max(0, 1 - math.min(oy, y))
|
|
local cutBottom = math.max(0, (math.max(oy + oh, y + h) - 1) - monH)
|
|
pcall(cb.copy, ox, oy + cutTop, w, h - (cutTop + cutBottom), x - ox, y - oy)
|
|
if surface == surfaces[1] then
|
|
local cacheControl = {}
|
|
for i = 1 + cutTop, h - cutBottom 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 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](oldSurface[8], "focus", false)
|
|
end
|
|
if ns1 then
|
|
setSurfacePalette(ns1, nil)
|
|
ns1[6](ns1[8], "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 llid = lid
|
|
lid = lid + 1
|
|
local surf = {math.min(#monitors, math.max(1, lIM)), 1, 2, w, h, sendSig, title, llid}
|
|
if h >= monitors[surf[1]][4] then
|
|
surf[3] = 1
|
|
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
|
|
checkArg(3, text, "string")
|
|
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 surfaces[1] ~= surf 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)
|
|
checkArg(1, 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)
|
|
checkArg(1, 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, lin = screens.getMonitorByKeyboard(ku)
|
|
for k, v in ipairs(monitors) do
|
|
if ku and v[2] == ku then
|
|
lin = k
|
|
break
|
|
end
|
|
end
|
|
if not lin then return end
|
|
lIM = lin
|
|
|
|
local focus = surfaces[1]
|
|
if kc == 29 then
|
|
isCtrDown = down
|
|
elseif kc == 56 then
|
|
isAltDown = down
|
|
end
|
|
if isAltDown and ka == 122 then
|
|
if focus and down then
|
|
local n = table.remove(surfaces, 1)
|
|
table.insert(surfaces, n)
|
|
changeFocus(n)
|
|
end
|
|
elseif isAltDown and kc == 200 then
|
|
if focus and down then ofsSurface(focus, 0, -1) end
|
|
elseif isAltDown and kc == 208 then
|
|
if focus and down then ofsSurface(focus, 0, 1) end
|
|
elseif isAltDown and kc == 203 then
|
|
if focus and down then ofsSurface(focus, -1, 0) end
|
|
elseif isAltDown and kc == 205 then
|
|
if focus and down then ofsSurface(focus, 1, 0) end
|
|
elseif isAltDown and ka == 120 then
|
|
if focus and down then ofsMSurface(focus, 1) end
|
|
elseif isAltDown and ka == 97 then
|
|
if not down then
|
|
isAltDown = false
|
|
end
|
|
elseif isAltDown and (ka == 3 or ka == 99) then
|
|
if down then
|
|
if isCtrDown then
|
|
error("User-authorized Everest crash.")
|
|
elseif focus then
|
|
focus[6](focus[8], "close")
|
|
end
|
|
end
|
|
elseif isAltDown and ka == 13 then
|
|
if down then
|
|
startLauncher()
|
|
end
|
|
elseif focus then
|
|
if kc ~= 56 then
|
|
lIM = focus[1]
|
|
end
|
|
focus[6](focus[8], "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](surfaces[1][8], "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)
|
|
draggingWindowX, draggingWindowY = nil
|
|
if ly == 1 then
|
|
if s[5] == 1 then
|
|
ns[6](ns[8], "close")
|
|
else
|
|
draggingWindowX, draggingWindowY = lx, ly
|
|
end
|
|
else
|
|
ns[6](ns[8], "touch", lx, ly - 1, ix, iy, s[5])
|
|
end
|
|
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])
|
|
if s[1] == "h.drag" and draggingWindowX then
|
|
local ofsX, ofsY = x - draggingWindowX, y - draggingWindowY
|
|
if ofsX ~= 0 or ofsY ~= 0 then
|
|
ofsSurface(focus, ofsX, ofsY)
|
|
end
|
|
else
|
|
draggingWindowX, draggingWindowY = nil
|
|
-- Ok, so let's see...
|
|
focus[6](focus[8], s[1]:sub(3), x, y - 1, ix, iy, s[5])
|
|
end
|
|
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()
|
|
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
|