mirror of
https://github.com/kepler155c/opus
synced 2025-10-18 17:27:39 +00:00
major directory reorganize
This commit is contained in:
605
sys/apps/multishell
Normal file
605
sys/apps/multishell
Normal file
@@ -0,0 +1,605 @@
|
||||
-- Default label
|
||||
if not os.getComputerLabel() then
|
||||
local id = os.getComputerID()
|
||||
if turtle then
|
||||
os.setComputerLabel('turtle_' .. id)
|
||||
elseif pocket then
|
||||
os.setComputerLabel('pocket_' .. id)
|
||||
else
|
||||
os.setComputerLabel('computer_' .. id)
|
||||
end
|
||||
end
|
||||
|
||||
multishell.term = term.current()
|
||||
|
||||
local defaultEnv = Util.shallowCopy(getfenv(1))
|
||||
|
||||
require = requireInjector(getfenv(1))
|
||||
local Config = require('config')
|
||||
|
||||
-- Begin multishell
|
||||
local parentTerm = term.current()
|
||||
local w,h = parentTerm.getSize()
|
||||
local tabs = {}
|
||||
local currentTab
|
||||
local _tabId = 0
|
||||
local overviewTab
|
||||
local runningTab
|
||||
local tabsDirty = false
|
||||
|
||||
local config = {
|
||||
standard = {
|
||||
focusTextColor = colors.lightGray,
|
||||
focusBackgroundColor = colors.gray,
|
||||
textColor = colors.black,
|
||||
backgroundColor = colors.lightGray,
|
||||
tabBarTextColor = colors.black,
|
||||
tabBarBackgroundColor = colors.lightGray,
|
||||
},
|
||||
color = {
|
||||
focusTextColor = colors.white,
|
||||
focusBackgroundColor = colors.brown,
|
||||
textColor = colors.gray,
|
||||
backgroundColor = colors.brown,
|
||||
tabBarTextColor = colors.lightGray,
|
||||
tabBarBackgroundColor = colors.brown,
|
||||
},
|
||||
-- path = '.:/apps:' .. shell.path():sub(3),
|
||||
path = 'usr/apps:sys/apps:' .. shell.path(),
|
||||
}
|
||||
|
||||
Config.load('multishell', config)
|
||||
|
||||
shell.setPath(config.path)
|
||||
|
||||
if config.aliases then
|
||||
for k in pairs(shell.aliases()) do
|
||||
shell.clearAlias(k)
|
||||
end
|
||||
for k,v in pairs(config.aliases) do
|
||||
shell.setAlias(k, v)
|
||||
end
|
||||
end
|
||||
|
||||
local _colors = config.standard
|
||||
if parentTerm.isColor() then
|
||||
_colors = config.color
|
||||
end
|
||||
|
||||
local function redrawMenu()
|
||||
if not tabsDirty then
|
||||
os.queueEvent('multishell', 'draw')
|
||||
tabsDirty = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw menu
|
||||
local function draw()
|
||||
tabsDirty = false
|
||||
|
||||
parentTerm.setBackgroundColor( _colors.tabBarBackgroundColor )
|
||||
if currentTab and currentTab.isOverview then
|
||||
parentTerm.setTextColor( _colors.focusTextColor )
|
||||
else
|
||||
parentTerm.setTextColor( _colors.tabBarTextColor )
|
||||
end
|
||||
parentTerm.setCursorPos( 1, 1 )
|
||||
parentTerm.clearLine()
|
||||
parentTerm.write('+')
|
||||
|
||||
local tabX = 2
|
||||
local function compareTab(a, b)
|
||||
return a.tabId < b.tabId
|
||||
end
|
||||
for _,tab in Util.spairs(tabs, compareTab) do
|
||||
|
||||
if tab.hidden and tab ~= currentTab or tab.isOverview then
|
||||
tab.sx = nil
|
||||
tab.ex = nil
|
||||
else
|
||||
tab.sx = tabX + 1
|
||||
tab.ex = tabX + #tab.title
|
||||
tabX = tabX + #tab.title + 1
|
||||
end
|
||||
end
|
||||
for _,tab in Util.spairs(tabs) do
|
||||
if tab.sx then
|
||||
if tab == currentTab then
|
||||
parentTerm.setTextColor(_colors.focusTextColor)
|
||||
parentTerm.setBackgroundColor(_colors.focusBackgroundColor)
|
||||
else
|
||||
parentTerm.setTextColor(_colors.textColor)
|
||||
parentTerm.setBackgroundColor(_colors.backgroundColor)
|
||||
end
|
||||
parentTerm.setCursorPos(tab.sx, 1)
|
||||
parentTerm.write(tab.title)
|
||||
end
|
||||
end
|
||||
if currentTab and not currentTab.isOverview then
|
||||
parentTerm.setTextColor(_colors.textColor)
|
||||
parentTerm.setBackgroundColor(_colors.backgroundColor)
|
||||
parentTerm.setCursorPos( w, 1 )
|
||||
parentTerm.write('*')
|
||||
end
|
||||
|
||||
if currentTab then
|
||||
currentTab.window.restoreCursor()
|
||||
end
|
||||
end
|
||||
|
||||
local function selectTab( tab )
|
||||
if not tab then
|
||||
for _,ftab in pairs(tabs) do
|
||||
if not ftab.hidden then
|
||||
tab = ftab
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not tab then
|
||||
tab = overviewTab
|
||||
end
|
||||
|
||||
if currentTab and currentTab ~= tab then
|
||||
currentTab.window.setVisible(false)
|
||||
if tab and not currentTab.hidden then
|
||||
tab.previousTabId = currentTab.tabId
|
||||
end
|
||||
end
|
||||
|
||||
if tab then
|
||||
currentTab = tab
|
||||
tab.window.setVisible(true)
|
||||
end
|
||||
end
|
||||
|
||||
local function resumeTab(tab, event, eventData)
|
||||
if not tab or coroutine.status(tab.co) == 'dead' then
|
||||
return
|
||||
end
|
||||
|
||||
if not tab.filter or tab.filter == event or event == "terminate" then
|
||||
eventData = eventData or { }
|
||||
term.redirect(tab.terminal)
|
||||
local previousTab = runningTab
|
||||
runningTab = tab
|
||||
local ok, result = coroutine.resume(tab.co, event, unpack(eventData))
|
||||
tab.terminal = term.current()
|
||||
if ok then
|
||||
tab.filter = result
|
||||
else
|
||||
printError(result)
|
||||
end
|
||||
|
||||
runningTab = previousTab
|
||||
|
||||
return ok, result
|
||||
end
|
||||
end
|
||||
|
||||
local function nextTabId()
|
||||
_tabId = _tabId + 1
|
||||
return _tabId
|
||||
end
|
||||
|
||||
local function launchProcess(tab)
|
||||
|
||||
tab.tabId = nextTabId()
|
||||
tab.timestamp = os.clock()
|
||||
tab.window = window.create(parentTerm, 1, 2, w, h - 1, false)
|
||||
tab.terminal = tab.window
|
||||
tab.env = Util.shallowCopy(tab.env or defaultEnv)
|
||||
|
||||
tab.co = coroutine.create(function()
|
||||
|
||||
local result, err
|
||||
|
||||
if tab.fn then
|
||||
result, err = Util.runFunction(tab.env, tab.fn, table.unpack(tab.args or { } ))
|
||||
elseif tab.path then
|
||||
result, err = os.run(tab.env, tab.path, table.unpack(tab.args or { } ))
|
||||
else
|
||||
err = 'multishell: invalid tab'
|
||||
end
|
||||
|
||||
if not result and err ~= 'Terminated' then
|
||||
if err then
|
||||
printError(tostring(err))
|
||||
end
|
||||
printError('Press enter to exit')
|
||||
tab.isDead = true
|
||||
while true do
|
||||
local e, code = os.pullEventRaw('key')
|
||||
if e == 'terminate' or e == 'key' and code == keys.enter then
|
||||
if tab.isOverview then
|
||||
os.queueEvent('multishell', 'terminate')
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
tabs[tab.tabId] = nil
|
||||
if tab == currentTab then
|
||||
local previousTab
|
||||
if tab.previousTabId then
|
||||
previousTab = tabs[tab.previousTabId]
|
||||
end
|
||||
selectTab(previousTab)
|
||||
end
|
||||
redrawMenu()
|
||||
end)
|
||||
|
||||
tabs[tab.tabId] = tab
|
||||
|
||||
resumeTab(tab)
|
||||
|
||||
return tab
|
||||
end
|
||||
|
||||
local function resizeWindows()
|
||||
local windowY = 2
|
||||
local windowHeight = h-1
|
||||
|
||||
local keys = Util.keys(tabs)
|
||||
for _,key in pairs(keys) do
|
||||
local tab = tabs[key]
|
||||
local x,y = tab.window.getCursorPos()
|
||||
if y > windowHeight then
|
||||
tab.window.scroll( y - windowHeight )
|
||||
tab.window.setCursorPos( x, windowHeight )
|
||||
end
|
||||
tab.window.reposition( 1, windowY, w, windowHeight )
|
||||
end
|
||||
|
||||
-- Pass term_resize to all processes
|
||||
local keys = Util.keys(tabs)
|
||||
for _,key in pairs(keys) do
|
||||
resumeTab(tabs[key], "term_resize")
|
||||
end
|
||||
end
|
||||
|
||||
local control
|
||||
local hotkeys = { }
|
||||
|
||||
local function processKeyEvent(event, code)
|
||||
if event == 'key_up' then
|
||||
if code == keys.leftCtrl or code == keys.rightCtrl then
|
||||
control = false
|
||||
end
|
||||
elseif event == 'char' then
|
||||
control = false
|
||||
elseif event == 'key' then
|
||||
if code == keys.leftCtrl or code == keys.rightCtrl then
|
||||
control = true
|
||||
elseif control then
|
||||
local hotkey = hotkeys[code]
|
||||
control = false
|
||||
if hotkey then
|
||||
hotkey()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.addHotkey(code, fn)
|
||||
hotkeys[code] = fn
|
||||
end
|
||||
|
||||
function multishell.removeHotkey(code)
|
||||
hotkeys[code] = nil
|
||||
end
|
||||
|
||||
function multishell.getFocus()
|
||||
return currentTab.tabId
|
||||
end
|
||||
|
||||
function multishell.setFocus(tabId)
|
||||
local tab = tabs[tabId]
|
||||
if tab then
|
||||
selectTab(tab)
|
||||
redrawMenu()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function multishell.getTitle(tabId)
|
||||
local tab = tabs[tabId]
|
||||
if tab then
|
||||
return tab.title
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.setTitle(tabId, sTitle)
|
||||
local tab = tabs[tabId]
|
||||
if tab then
|
||||
tab.title = sTitle or ''
|
||||
redrawMenu()
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.getCurrent()
|
||||
if runningTab then
|
||||
return runningTab.tabId
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.getTab(tabId)
|
||||
return tabs[tabId]
|
||||
end
|
||||
|
||||
function multishell.terminate(tabId)
|
||||
local tab = tabs[tabId]
|
||||
if tab and not tab.isOverview then
|
||||
if coroutine.status(tab.co) ~= 'dead' then
|
||||
--os.queueEvent('multishell', 'terminate', tab)
|
||||
resumeTab(tab, "terminate")
|
||||
else
|
||||
tabs[tabId] = nil
|
||||
if tab == currentTab then
|
||||
local previousTab
|
||||
if tab.previousTabId then
|
||||
previousTab = tabs[tab.previousTabId]
|
||||
end
|
||||
selectTab(previousTab)
|
||||
end
|
||||
redrawMenu()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.getTabs()
|
||||
return tabs
|
||||
end
|
||||
|
||||
function multishell.launch( tProgramEnv, sProgramPath, ... )
|
||||
-- backwards compatibility
|
||||
return multishell.openTab({
|
||||
env = tProgramEnv,
|
||||
path = sProgramPath,
|
||||
args = { ... },
|
||||
})
|
||||
end
|
||||
|
||||
function multishell.openTab(tab)
|
||||
|
||||
if not tab.title and tab.path then
|
||||
tab.title = fs.getName(tab.path)
|
||||
end
|
||||
|
||||
tab.title = tab.title or 'untitled'
|
||||
|
||||
local previousTerm = term.current()
|
||||
launchProcess(tab)
|
||||
term.redirect(previousTerm)
|
||||
|
||||
if tab.hidden then
|
||||
if coroutine.status(tab.co) == 'dead' or tab.isDead then
|
||||
tab.hidden = false
|
||||
end
|
||||
elseif tab.focused then
|
||||
multishell.setFocus(tab.tabId)
|
||||
else
|
||||
redrawMenu()
|
||||
end
|
||||
return tab.tabId
|
||||
end
|
||||
|
||||
function multishell.hideTab(tabId)
|
||||
local tab = tabs[tabId]
|
||||
if tab then
|
||||
tab.hidden = true
|
||||
redrawMenu()
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.unhideTab(tabId)
|
||||
local tab = tabs[tabId]
|
||||
if tab then
|
||||
tab.hidden = false
|
||||
redrawMenu()
|
||||
end
|
||||
end
|
||||
|
||||
function multishell.getCount()
|
||||
local count
|
||||
for _,tab in pairs(tabs) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
-- control-o - overview
|
||||
multishell.addHotkey(24, function()
|
||||
multishell.setFocus(overviewTab.tabId)
|
||||
end)
|
||||
|
||||
-- control-backspace
|
||||
multishell.addHotkey(14, function()
|
||||
local tabId = multishell.getFocus()
|
||||
local tab = tabs[tabId]
|
||||
if not tab.isOverview then
|
||||
os.queueEvent('multishell', 'terminateTab', tabId)
|
||||
tab = Util.shallowCopy(tab)
|
||||
tab.isDead = false
|
||||
tab.focused = true
|
||||
multishell.openTab(tab)
|
||||
end
|
||||
end)
|
||||
|
||||
-- control-tab - next tab
|
||||
multishell.addHotkey(15, function()
|
||||
local function compareTab(a, b)
|
||||
return a.tabId < b.tabId
|
||||
end
|
||||
local visibleTabs = { }
|
||||
for _,tab in Util.spairs(tabs, compareTab) do
|
||||
if not tab.hidden then
|
||||
table.insert(visibleTabs, tab)
|
||||
end
|
||||
end
|
||||
for k,tab in ipairs(visibleTabs) do
|
||||
if tab.tabId == currentTab.tabId then
|
||||
if k < #visibleTabs then
|
||||
multishell.setFocus(visibleTabs[k + 1].tabId)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
if #visibleTabs > 0 then
|
||||
multishell.setFocus(visibleTabs[1].tabId)
|
||||
end
|
||||
end)
|
||||
|
||||
local function startup()
|
||||
local hasError
|
||||
|
||||
local function runDir(directory, desc, open)
|
||||
if not fs.exists(directory) then
|
||||
return
|
||||
end
|
||||
|
||||
local files = fs.list(directory)
|
||||
table.sort(files)
|
||||
|
||||
for _,file in ipairs(files) do
|
||||
print(desc .. file)
|
||||
os.sleep(0)
|
||||
local result, err = open(directory .. '/' .. file)
|
||||
if not result then
|
||||
printError(err)
|
||||
hasError = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
runDir('sys/extensions', '[ ext ] ', shell.run)
|
||||
|
||||
local overviewId = multishell.openTab({
|
||||
path = 'sys/apps/Overview.lua',
|
||||
focused = true,
|
||||
hidden = true,
|
||||
isOverview = true,
|
||||
})
|
||||
overviewTab = tabs[overviewId]
|
||||
|
||||
runDir('sys/services', '[ svc ] ', shell.openHiddenTab)
|
||||
runDir('sys/autorun', '[ aut ] ', shell.run)
|
||||
runDir('usr/autorun', '[ aut ] ', shell.run)
|
||||
|
||||
if hasError then
|
||||
error('An autorun program has errored')
|
||||
end
|
||||
end
|
||||
|
||||
-- Begin
|
||||
parentTerm.clear()
|
||||
|
||||
multishell.openTab({
|
||||
focused = true,
|
||||
fn = startup,
|
||||
env = defaultEnv,
|
||||
title = 'Autorun',
|
||||
})
|
||||
|
||||
if not overviewTab or coroutine.status(overviewTab.co) == 'dead' then
|
||||
--error('Overview aborted')
|
||||
end
|
||||
|
||||
if not currentTab then
|
||||
multishell.setFocus(overviewTab.tabId)
|
||||
end
|
||||
|
||||
draw()
|
||||
|
||||
while true do
|
||||
|
||||
-- Get the event
|
||||
local tEventData = { os.pullEventRaw() }
|
||||
local sEvent = table.remove(tEventData, 1)
|
||||
|
||||
if sEvent == 'key_up' then
|
||||
processKeyEvent(sEvent, tEventData[1])
|
||||
end
|
||||
|
||||
if sEvent == "term_resize" then
|
||||
-- Resize event
|
||||
w,h = parentTerm.getSize()
|
||||
resizeWindows()
|
||||
redrawMenu()
|
||||
|
||||
elseif sEvent == 'multishell' then
|
||||
local action = tEventData[1]
|
||||
|
||||
if action == 'terminate' then
|
||||
break
|
||||
elseif action == 'terminateTab' then
|
||||
multishell.terminate(tEventData[2])
|
||||
elseif action == 'draw' then
|
||||
draw()
|
||||
end
|
||||
|
||||
elseif sEvent == "char" or
|
||||
sEvent == "key" or
|
||||
sEvent == "paste" or
|
||||
sEvent == "terminate" then
|
||||
|
||||
processKeyEvent(sEvent, tEventData[1])
|
||||
|
||||
-- Keyboard event - Passthrough to current process
|
||||
resumeTab(currentTab, sEvent, tEventData)
|
||||
|
||||
elseif sEvent == "mouse_click" then
|
||||
local button, x, y = tEventData[1], tEventData[2], tEventData[3]
|
||||
if y == 1 and os.locked then
|
||||
-- ignore
|
||||
elseif y == 1 then
|
||||
-- Switch process
|
||||
local w, h = parentTerm.getSize()
|
||||
if x == 1 then
|
||||
multishell.setFocus(overviewTab.tabId)
|
||||
elseif x == w then
|
||||
if currentTab then
|
||||
multishell.terminate(currentTab.tabId)
|
||||
end
|
||||
else
|
||||
for _,tab in pairs(tabs) do
|
||||
if not tab.hidden and tab.sx then
|
||||
if x >= tab.sx and x <= tab.ex then
|
||||
multishell.setFocus(tab.tabId)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif currentTab then
|
||||
-- Passthrough to current process
|
||||
resumeTab(currentTab, sEvent, { button, x, y-1 })
|
||||
end
|
||||
|
||||
elseif sEvent == "mouse_drag" or sEvent == "mouse_scroll" then
|
||||
-- Other mouse event
|
||||
local p1, x, y = tEventData[1], tEventData[2], tEventData[3]
|
||||
if currentTab and (y ~= 1) then
|
||||
if currentTab.terminal.scrollUp then
|
||||
if p1 == -1 then
|
||||
currentTab.terminal.scrollUp()
|
||||
else
|
||||
currentTab.terminal.scrollDown()
|
||||
end
|
||||
else
|
||||
-- Passthrough to current process
|
||||
resumeTab(currentTab, sEvent, { p1, x, y-1 })
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
-- Other event
|
||||
-- Passthrough to all processes
|
||||
local keys = Util.keys(tabs)
|
||||
for _,key in pairs(keys) do
|
||||
resumeTab(tabs[key], sEvent, tEventData)
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user