357 lines
11 KiB
Lua
357 lines
11 KiB
Lua
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
|
|
|
-- Setup process switching
|
|
local parentTerm = term.current()
|
|
local w, h = parentTerm.getSize()
|
|
|
|
local tProcesses = {}
|
|
local nCurrentProcess = nil
|
|
local nRunningProcess = nil
|
|
local bShowMenu = false
|
|
local bWindowsResized = false
|
|
local nScrollPos = 1
|
|
local bScrollRight = false
|
|
|
|
local function selectProcess( n )
|
|
if nCurrentProcess ~= n then
|
|
if nCurrentProcess then
|
|
local tOldProcess = tProcesses[ nCurrentProcess ]
|
|
tOldProcess.window.setVisible( false )
|
|
end
|
|
nCurrentProcess = n
|
|
if nCurrentProcess then
|
|
local tNewProcess = tProcesses[ nCurrentProcess ]
|
|
tNewProcess.window.setVisible( true )
|
|
tNewProcess.bInteracted = true
|
|
end
|
|
end
|
|
end
|
|
|
|
local function setProcessTitle( n, sTitle )
|
|
tProcesses[ n ].sTitle = sTitle
|
|
end
|
|
|
|
local function resumeProcess( nProcess, sEvent, ... )
|
|
local tProcess = tProcesses[ nProcess ]
|
|
local sFilter = tProcess.sFilter
|
|
if sFilter == nil or sFilter == sEvent or sEvent == "terminate" then
|
|
local nPreviousProcess = nRunningProcess
|
|
nRunningProcess = nProcess
|
|
term.redirect( tProcess.terminal )
|
|
local ok, result = coroutine.resume( tProcess.co, sEvent, ... )
|
|
tProcess.terminal = term.current()
|
|
if ok then
|
|
tProcess.sFilter = result
|
|
else
|
|
printError( result )
|
|
end
|
|
nRunningProcess = nPreviousProcess
|
|
end
|
|
end
|
|
|
|
local function launchProcess( bFocus, tProgramEnv, sProgramPath, ... )
|
|
local tProgramArgs = table.pack( ... )
|
|
local nProcess = #tProcesses + 1
|
|
local tProcess = {}
|
|
tProcess.sTitle = fs.getName( sProgramPath )
|
|
if bShowMenu then
|
|
tProcess.window = window.create( parentTerm, 1, 2, w, h - 1, false )
|
|
else
|
|
tProcess.window = window.create( parentTerm, 1, 1, w, h, false )
|
|
end
|
|
tProcess.co = coroutine.create( function()
|
|
os.run( tProgramEnv, sProgramPath, table.unpack( tProgramArgs, 1, tProgramArgs.n ) )
|
|
if not tProcess.bInteracted then
|
|
term.setCursorBlink( false )
|
|
print( "Press any key to continue" )
|
|
os.pullEvent( "char" )
|
|
end
|
|
end )
|
|
tProcess.sFilter = nil
|
|
tProcess.terminal = tProcess.window
|
|
tProcess.bInteracted = false
|
|
tProcesses[ nProcess ] = tProcess
|
|
if bFocus then
|
|
selectProcess( nProcess )
|
|
end
|
|
resumeProcess( nProcess )
|
|
return nProcess
|
|
end
|
|
|
|
local function cullProcess( nProcess )
|
|
local tProcess = tProcesses[ nProcess ]
|
|
if coroutine.status( tProcess.co ) == "dead" then
|
|
if nCurrentProcess == nProcess then
|
|
selectProcess( nil )
|
|
end
|
|
table.remove( tProcesses, nProcess )
|
|
if nCurrentProcess == nil then
|
|
if nProcess > 1 then
|
|
selectProcess( nProcess - 1 )
|
|
elseif #tProcesses > 0 then
|
|
selectProcess( 1 )
|
|
end
|
|
end
|
|
if nScrollPos ~= 1 then
|
|
nScrollPos = nScrollPos - 1
|
|
end
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function cullProcesses()
|
|
local culled = false
|
|
for n = #tProcesses, 1, -1 do
|
|
culled = culled or cullProcess( n )
|
|
end
|
|
return culled
|
|
end
|
|
|
|
-- Setup the main menu
|
|
local menuMainTextColor, menuMainBgColor, menuOtherTextColor, menuOtherBgColor
|
|
if parentTerm.isColor() then
|
|
menuMainTextColor, menuMainBgColor = colors.yellow, colors.black
|
|
menuOtherTextColor, menuOtherBgColor = colors.black, colors.gray
|
|
else
|
|
menuMainTextColor, menuMainBgColor = colors.white, colors.black
|
|
menuOtherTextColor, menuOtherBgColor = colors.black, colors.gray
|
|
end
|
|
|
|
local function redrawMenu()
|
|
if bShowMenu then
|
|
-- Draw menu
|
|
parentTerm.setCursorPos( 1, 1 )
|
|
parentTerm.setBackgroundColor( menuOtherBgColor )
|
|
parentTerm.clearLine()
|
|
local nCharCount = 0
|
|
local nSize = parentTerm.getSize()
|
|
if nScrollPos ~= 1 then
|
|
parentTerm.setTextColor( menuOtherTextColor )
|
|
parentTerm.setBackgroundColor( menuOtherBgColor )
|
|
parentTerm.write( "<" )
|
|
nCharCount = 1
|
|
end
|
|
for n = nScrollPos, #tProcesses do
|
|
if n == nCurrentProcess then
|
|
parentTerm.setTextColor( menuMainTextColor )
|
|
parentTerm.setBackgroundColor( menuMainBgColor )
|
|
else
|
|
parentTerm.setTextColor( menuOtherTextColor )
|
|
parentTerm.setBackgroundColor( menuOtherBgColor )
|
|
end
|
|
parentTerm.write( " " .. tProcesses[n].sTitle .. " " )
|
|
nCharCount = nCharCount + #tProcesses[n].sTitle + 2
|
|
end
|
|
if nCharCount > nSize then
|
|
parentTerm.setTextColor( menuOtherTextColor )
|
|
parentTerm.setBackgroundColor( menuOtherBgColor )
|
|
parentTerm.setCursorPos( nSize, 1 )
|
|
parentTerm.write( ">" )
|
|
bScrollRight = true
|
|
else
|
|
bScrollRight = false
|
|
end
|
|
|
|
-- Put the cursor back where it should be
|
|
local tProcess = tProcesses[ nCurrentProcess ]
|
|
if tProcess then
|
|
tProcess.window.restoreCursor()
|
|
end
|
|
end
|
|
end
|
|
|
|
local function resizeWindows()
|
|
local windowY, windowHeight
|
|
if bShowMenu then
|
|
windowY = 2
|
|
windowHeight = h - 1
|
|
else
|
|
windowY = 1
|
|
windowHeight = h
|
|
end
|
|
for n = 1, #tProcesses do
|
|
local tProcess = tProcesses[n]
|
|
local x, y = tProcess.window.getCursorPos()
|
|
if y > windowHeight then
|
|
tProcess.window.scroll( y - windowHeight )
|
|
tProcess.window.setCursorPos( x, windowHeight )
|
|
end
|
|
tProcess.window.reposition( 1, windowY, w, windowHeight )
|
|
end
|
|
bWindowsResized = true
|
|
end
|
|
|
|
local function setMenuVisible( bVis )
|
|
if bShowMenu ~= bVis then
|
|
bShowMenu = bVis
|
|
resizeWindows()
|
|
redrawMenu()
|
|
end
|
|
end
|
|
|
|
local multishell = {}
|
|
|
|
function multishell.getFocus()
|
|
return nCurrentProcess
|
|
end
|
|
|
|
function multishell.setFocus( n )
|
|
expect(1, n, "number")
|
|
if n >= 1 and n <= #tProcesses then
|
|
selectProcess( n )
|
|
redrawMenu()
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function multishell.getTitle( n )
|
|
expect(1, n, "number")
|
|
if n >= 1 and n <= #tProcesses then
|
|
return tProcesses[n].sTitle
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function multishell.setTitle( n, sTitle )
|
|
expect(1, n, "number")
|
|
expect(2, sTitle, "string")
|
|
if n >= 1 and n <= #tProcesses then
|
|
setProcessTitle( n, sTitle )
|
|
redrawMenu()
|
|
end
|
|
end
|
|
|
|
function multishell.getCurrent()
|
|
return nRunningProcess
|
|
end
|
|
|
|
function multishell.launch( tProgramEnv, sProgramPath, ... )
|
|
expect(1, tProgramEnv, "table")
|
|
expect(2, sProgramPath, "string")
|
|
local previousTerm = term.current()
|
|
setMenuVisible( #tProcesses + 1 >= 2 )
|
|
local nResult = launchProcess( false, tProgramEnv, sProgramPath, ... )
|
|
redrawMenu()
|
|
term.redirect( previousTerm )
|
|
return nResult
|
|
end
|
|
|
|
function multishell.getCount()
|
|
return #tProcesses
|
|
end
|
|
|
|
-- Begin
|
|
parentTerm.clear()
|
|
setMenuVisible( false )
|
|
launchProcess( true, {
|
|
["shell"] = shell,
|
|
["multishell"] = multishell,
|
|
}, "/rom/programs/shell.lua" )
|
|
|
|
-- Run processes
|
|
while #tProcesses > 0 do
|
|
-- Get the event
|
|
local tEventData = table.pack( os.pullEventRaw() )
|
|
local sEvent = tEventData[1]
|
|
if sEvent == "term_resize" then
|
|
-- Resize event
|
|
w, h = parentTerm.getSize()
|
|
resizeWindows()
|
|
redrawMenu()
|
|
|
|
elseif sEvent == "char" or sEvent == "key" or sEvent == "key_up" or sEvent == "paste" or sEvent == "terminate" then
|
|
-- Keyboard event
|
|
-- Passthrough to current process
|
|
resumeProcess( nCurrentProcess, table.unpack( tEventData, 1, tEventData.n ) )
|
|
if cullProcess( nCurrentProcess ) then
|
|
setMenuVisible( #tProcesses >= 2 )
|
|
redrawMenu()
|
|
end
|
|
|
|
elseif sEvent == "mouse_click" then
|
|
-- Click event
|
|
local button, x, y = tEventData[2], tEventData[3], tEventData[4]
|
|
if bShowMenu and y == 1 then
|
|
-- Switch process
|
|
if x == 1 and nScrollPos ~= 1 then
|
|
nScrollPos = nScrollPos - 1
|
|
redrawMenu()
|
|
elseif bScrollRight and x == term.getSize() then
|
|
nScrollPos = nScrollPos + 1
|
|
redrawMenu()
|
|
else
|
|
local tabStart = 1
|
|
if nScrollPos ~= 1 then
|
|
tabStart = 2
|
|
end
|
|
for n = nScrollPos, #tProcesses do
|
|
local tabEnd = tabStart + #tProcesses[n].sTitle + 1
|
|
if x >= tabStart and x <= tabEnd then
|
|
selectProcess( n )
|
|
redrawMenu()
|
|
break
|
|
end
|
|
tabStart = tabEnd + 1
|
|
end
|
|
end
|
|
else
|
|
-- Passthrough to current process
|
|
resumeProcess( nCurrentProcess, sEvent, button, x, bShowMenu and y - 1 or y )
|
|
if cullProcess( nCurrentProcess ) then
|
|
setMenuVisible( #tProcesses >= 2 )
|
|
redrawMenu()
|
|
end
|
|
end
|
|
|
|
elseif sEvent == "mouse_drag" or sEvent == "mouse_up" or sEvent == "mouse_scroll" then
|
|
-- Other mouse event
|
|
local p1, x, y = tEventData[2], tEventData[3], tEventData[4]
|
|
if bShowMenu and sEvent == "mouse_scroll" and y == 1 then
|
|
if p1 == -1 and nScrollPos ~= 1 then
|
|
nScrollPos = nScrollPos - 1
|
|
redrawMenu()
|
|
elseif bScrollRight and p1 == 1 then
|
|
nScrollPos = nScrollPos + 1
|
|
redrawMenu()
|
|
end
|
|
elseif not (bShowMenu and y == 1) then
|
|
-- Passthrough to current process
|
|
resumeProcess( nCurrentProcess, sEvent, p1, x, bShowMenu and y - 1 or y )
|
|
if cullProcess( nCurrentProcess ) then
|
|
setMenuVisible( #tProcesses >= 2 )
|
|
redrawMenu()
|
|
end
|
|
end
|
|
|
|
else
|
|
-- Other event
|
|
-- Passthrough to all processes
|
|
local nLimit = #tProcesses -- Storing this ensures any new things spawned don't get the event
|
|
for n = 1, nLimit do
|
|
resumeProcess( n, table.unpack( tEventData, 1, tEventData.n ) )
|
|
end
|
|
if cullProcesses() then
|
|
setMenuVisible( #tProcesses >= 2 )
|
|
redrawMenu()
|
|
end
|
|
end
|
|
|
|
if bWindowsResized then
|
|
-- Pass term_resize to all processes
|
|
local nLimit = #tProcesses -- Storing this ensures any new things spawned don't get the event
|
|
for n = 1, nLimit do
|
|
resumeProcess( n, "term_resize" )
|
|
end
|
|
bWindowsResized = false
|
|
if cullProcesses() then
|
|
setMenuVisible( #tProcesses >= 2 )
|
|
redrawMenu()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Shutdown
|
|
term.redirect( parentTerm )
|