Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local unpackDirectoryPath = "/"
- local packedDirectory = {["Multi-Program-Shell"]={["Program"]="--[[\
- Program Trystan Cannon\
- 26 July 2013\
- \
- Contains the Program class which is used to construct\
- program objects for use in the main script. Each program\
- object has a redirectable buffer so that programs can be\
- switched between without losing the state of the screen between\
- each switch.\
- ]]\
- \
- -- Variables ----------------------------------------------------\
- local programMetatable = { __index = getfenv (1) }\
- -- Variables ----------------------------------------------------\
- \
- \
- \
- -- Returns the metatable for every program.\
- function getMetatable()\
- return programMetatable\
- end\
- \
- -- Resumes the program with the given event data and updates the terminal\
- -- using the buffer for the program.\
- function resume (program, ...)\
- term.redirect (program.redirectBuffer.redirectTable)\
- local returnData = { coroutine.resume (program.programCoroutine, ...) }\
- term.restore()\
- \
- return returnData\
- end\
- \
- -- Constructs and returns a new Program object.\
- -- Returns the data yielded by the coroutine after the first yield.\
- -- Basically, if the program returns without yielding (no os.pullEventRaw), then\
- -- the data returned by the program after being executed is returned by this constructor.\
- function new (programPath, environment, ...)\
- local this = {\
- programPath = programPath,\
- environment = environment or {},\
- \
- programFunction = nil,\
- programCoroutine = nil,\
- redirectBuffer = RedirectBuffer.new (term.native.getSize()),\
- \
- programArguments = { ... }\
- }\
- setmetatable (this, programMetatable)\
- \
- -- Load the file for the program at the path specified, set\
- -- the environment for aforementioned function, and create a coroutine\
- -- for the function.\
- this.programFunction = loadfile (this.programPath)\
- setmetatable (this.environment, { __index = _G })\
- setfenv (this.programFunction, this.environment)\
- \
- this.programCoroutine = coroutine.create (this.programFunction)\
- \
- -- Initialize the coroutine, but make sure that the buffer isn't visible.\
- this.redirectBuffer.isVisible = false\
- term.redirect (this.redirectBuffer.redirectTable)\
- local initializationYieldData = { coroutine.resume (this.programCoroutine, unpack (this.programArguments)) }\
- term.restore()\
- this.redirectBuffer.isVisible = true\
- \
- return this\
- end",["RunningProgramsInterface"]="--[[\
- Running Programs Interface Trystan Cannon\
- 26 July 2013\
- \
- This script is used by the Main script for the multi\
- program shell in order to switch between programs which are\
- being executed currently. Also, the ability to start new\
- programs is also available to users.\
- \
- This script, when loaded as a function, returns one of the following\
- values:\
- * -1, programPath, arguments = The user chose to start a new program. The path for\
- aforementioned program is also returned.\
- * n where n >= 1 = The user chose to switch to a program currently running.\
- The index in the program stack for the chosen program is returned.\
- ]]\
- \
- -- Make sure that we have a program stack to view and make sure that the objects\
- -- in the stack are valid programs.\
- if not (programStack and getmetatable (programStack[#programStack]) == Program.getMetatable()) then\
- print (\"The running programs interface must be executed by the Multi-Program-Shell.\")\
- return\
- end\
- \
- -- Constants ----------------------------------------------------\
- local RUN_BUTTON_TOGGLE = 'r' -- The character that has to be pressed for the run button to be displayed.\
- local SCREEN_WIDTH, SCREEN_HEIGHT = term.native.getSize() -- The static dimensions of the screen.\
- local MOUSE_WHEEL_UP, MOUSE_WHEEL_DOWN = -1, 1 -- The directions of the mouse wheel returned by os.pullEvent.\
- -- Constants ----------------------------------------------------\
- \
- \
- \
- -- Variables ----------------------------------------------------\
- local newProgramArguments = nil -- The arguments given to a new program if the user wants to run another program instead of selection one from the stack.\
- local isRunning = true -- Whether or not the running programs interface is running (the user hasn't made a selection).\
- local selectionIndex = #programStack -- The index of the program currently selected in the program stack for switching.\
- \
- local programStackTextColor = colors.black\
- local programStackBackColor = colors.white\
- \
- local programStackDrawX = 10 -- The x position that the program stack will be drawn at.\
- local programStackDrawY = 5 -- The y position that the program stack will be drawn at.\
- \
- local programStackDrawWidth = SCREEN_WIDTH - programStackDrawX -- The maximum width of each program's name when being drawn on the screen.\
- local programStackDrawHeight = SCREEN_HEIGHT - (programStackDrawY + 4) -- The maximum height which the program stack can be drawn to.\
- local scroll = (selectionIndex - programStackDrawHeight > 0) and selectionIndex - programStackDrawHeight or 0\
- -- ^The number of entries to scroll the program stack when drawn.\
- \
- local runButtonLabel = \"|[R]un Program|\"\
- local runButtonX = SCREEN_WIDTH - runButtonLabel:len()\
- local runButtonY = SCREEN_HEIGHT - 1\
- \
- local runButtonTextColor = term.native.isColor() and colors.gray or colors.black\
- local runButtonBackColor = term.native.isColor() and colors.lightGray or colors.white\
- -- Variables ----------------------------------------------------\
- \
- \
- \
- -- Clears the screen with a given color (or black by default) and resets\
- -- the cursor to the upper left corner.\
- local function clearScreen (textColor, backColor)\
- term.native.setTextColor (textColor or colors.white)\
- term.native.setBackgroundColor (backColor or colors.black)\
- \
- term.native.clear()\
- term.native.setCursorPos (1, 1)\
- end\
- \
- -- Writes the given text to the center of the screen on the given line.\
- local function writeCentered (text, lineNumber)\
- term.native.setCursorPos (SCREEN_WIDTH / 2 - text:len() / 2, lineNumber)\
- term.native.write (text)\
- end\
- \
- -- Draws a header for the running programs interface.\
- local function drawHeader (headerText, headerY)\
- -- Handle any new line characters in the header\
- local linesInHeader = {}\
- for line in headerText:gmatch (\"[^\\n]+\") do\
- table.insert (linesInHeader, line)\
- end\
- \
- -- Write each of the lines in the header to the center of the screen on a separate\
- -- line starting at the headerY provided.\
- for lineIndex = 1, #linesInHeader do\
- writeCentered (linesInHeader[lineIndex], lineIndex + headerY - 1)\
- end\
- end\
- \
- -- Draws the program stack at the given coordinates with each entry\
- -- on a separate line for 'height' lines. Each entry starts at 'x' and\
- -- goes until 'width' or the length of the entry if it is shorter than\
- -- 'width'.\
- local function drawProgramStack (x, y, width, height, scroll)\
- local lineNumber = y\
- \
- for programIndex = scroll + 1, scroll + height do\
- if programStack[programIndex] then\
- local programName = (programIndex .. \" \" .. fs.getName (programStack[programIndex].programPath)):sub (1, width)\
- if programIndex == selectionIndex then\
- programName = programName .. \" <-\"\
- end\
- \
- term.native.setCursorPos (x, lineNumber)\
- term.native.write (programName)\
- \
- lineNumber = lineNumber + 1\
- end\
- end\
- \
- -- Display to the user whether or not that there are more programs running\
- -- but aren't visible with the current scroll.\
- if selectionIndex < #programStack then\
- term.native.setCursorPos (width, y + height - 1)\
- term.native.write (\"More...\")\
- end\
- end\
- \
- -- Draws a button at the given position.\
- local function drawButton (label, x, y, textColor, backColor)\
- term.native.setTextColor (textColor)\
- term.native.setBackgroundColor (backColor)\
- \
- term.native.setCursorPos (x, y)\
- term.native.write (label)\
- \
- term.native.setTextColor (programStackTextColor)\
- term.native.setBackgroundColor (programStackBackColor)\
- end\
- \
- -- Handles a click on the run button. The user is prompted to enter a path\
- -- to a program to run.\
- local function handleRunButtonClick()\
- local prompt = \"Enter a program path and arguments:\"\
- term.native.setCursorPos (3, runButtonY - 1)\
- term.native.write (prompt)\
- \
- term.native.setTextColor (runButtonTextColor)\
- term.native.setBackgroundColor (runButtonBackColor)\
- term.native.setCursorPos (2, runButtonY)\
- term.native.clearLine()\
- \
- -- Get the path and arguments for the program.\
- local programAndArguments = read()\
- \
- -- Parse the string returned by spaces with the first argument being the path for the program.\
- newProgramArguments = {}\
- \
- for argument in programAndArguments:gmatch (\"[^%s]+\") do\
- table.insert (newProgramArguments, argument)\
- end\
- \
- -- Close the program and return the arguments entered if the program at the given path\
- -- is valid.\
- if fs.exists (newProgramArguments[1]) and not fs.isDir (newProgramArguments[1]) then\
- isRunning = false\
- selectionIndex = -1\
- -- Reset the program arguments since the path for the program is invalid.\
- else\
- term.native.setTextColor (colors.red)\
- term.native.setBackgroundColor (programStackBackColor)\
- \
- writeCentered (\"No such program.\", runButtonY - 2)\
- term.native.setTextColor (programStackTextColor)\
- sleep (1)\
- \
- newProgramArguments = nil\
- end\
- end\
- \
- -- Handles events which will affect the state of the screen (mouse wheel scrolls, clicks, and key presses).\
- local function handleEvent (eventData)\
- -- If the user wishes to terminate the program, then we should just return\
- -- with the program selection being that which the user started with (program at the top of the stack).\
- if eventData[1] == \"terminate\" then\
- selectionIndex = #programStack\
- isRunning = false\
- elseif eventData[1] == \"mouse_click\" then\
- -- A click on the run button will allow the user to enter input which is similar to the shell command line, but the full\
- -- path of the program must be specified.\
- if eventData[3] >= runButtonX and eventData[3] <= runButtonX + runButtonLabel:len() and eventData[4] == runButtonY then\
- handleRunButtonClick()\
- -- If the user clicked somewhere on the list of programs which can be switched to, then we should switch the selection index\
- -- to the program that they clicked on.\
- elseif eventData[3] >= programStackDrawX and eventData[3] <= programStackDrawX + programStackDrawWidth and\
- eventData[4] >= programStackDrawY and eventData[4] <= programStackDrawY + math.min (programStackDrawHeight, #programStack) then\
- \
- -- Make sure the click was on a valid program and not just space in the area which is allocated for drawing the programs\
- -- in the stack.\
- if programStack [scroll + eventData[4] - programStackDrawY] then\
- selectionIndex = scroll + eventData[4] - programStackDrawY\
- isRunning = false\
- end\
- end\
- -- If the user pressed the toggle for the run button, then allow them execute a program with arguments.\
- elseif eventData[1] == \"char\" and eventData[2] == RUN_BUTTON_TOGGLE then\
- handleRunButtonClick()\
- -- If the user spun the mouse-whell, then scroll the selection up or down depending on the direction of the spin.\
- elseif eventData[1] == \"mouse_scroll\" then\
- if eventData[2] == MOUSE_WHEEL_UP and selectionIndex > 1 then\
- selectionIndex = selectionIndex - 1\
- elseif selectionIndex < #programStack then\
- selectionIndex = selectionIndex + 1\
- end\
- -- If the user pressed enter, then return with the selection that the user was on when\
- -- they pressed the button.\
- elseif eventData[1] == \"key\" then\
- if eventData[2] == keys[\"return\"] then\
- isRunning = false\
- -- Move the selection up or down if up or down was pressed and there are still selections\
- -- above or below the current one.\
- elseif eventData[2] == keys[\"up\"] and selectionIndex > 1 then\
- selectionIndex = selectionIndex - 1\
- elseif eventData[2] == keys[\"down\"] and selectionIndex < #programStack then\
- selectionIndex = selectionIndex + 1\
- end\
- end\
- \
- -- Update the scroll of the program stack.\
- scroll = (selectionIndex - programStackDrawHeight > 0) and selectionIndex - programStackDrawHeight or 0\
- end\
- \
- -- Update the screen and handle any events which may relate to the screen.\
- while isRunning do\
- clearScreen (programStackTextColor, programStackBackColor)\
- \
- drawButton (runButtonLabel, runButtonX, runButtonY, runButtonTextColor, runButtonBackColor)\
- drawHeader (\"Running Programs\", 2)\
- drawProgramStack (programStackDrawX, programStackDrawY, programStackDrawWidth, programStackDrawHeight, scroll)\
- \
- handleEvent ({ os.pullEventRaw() })\
- end\
- \
- -- Return whatever index for the selection made by the user. See the header of this script\
- -- for information on how those work.\
- return selectionIndex, newProgramArguments",["RedirectBuffer"]="--[[\
- Redirect Buffer Trystan Cannon\
- 26 July 2013\
- \
- Contains the Redirect Buffer class whose objects\
- are used to buffer the screen when programs are redirected\
- to while being run concurrently.\
- \
- When programs switch, the state of the terminal needs to be\
- maintained so that the screen isn't black, the cursor knows where it\
- should be, and the current colors of the screen are remembered.\
- \
- The buffer is stored in three tables: text lines, text color lines, and\
- background colors. The colors are serialized into hex values (0-f) because\
- there are only sixteen colors.\
- ]]\
- \
- -- Constants ----------------------------------------------------\
- local HEX_COLOR_LOOKUP = {}\
- for _, color in pairs (colors) do\
- if type (color) == \"number\" then\
- -- Format the log base 2 of the color as a one digit hex number (0-f).\
- -- 0 being white and f being black.\
- HEX_COLOR_LOOKUP[color] = string.format (\"%x\", math.floor (math.log (color) / math.log (2)))\
- HEX_COLOR_LOOKUP[HEX_COLOR_LOOKUP[color]] = color\
- end\
- end\
- -- Constants ----------------------------------------------------\
- \
- \
- \
- -- Variables ----------------------------------------------------\
- local redirectBufferMetatable = { __index = getfenv (1) }\
- -- Variables ----------------------------------------------------\
- \
- \
- \
- -- Returns the metatable for every redirect buffer.\
- function getMetatable()\
- return redirectBufferMetatable\
- end\
- \
- -- Scrolls the table once in the direction provided. References are swapped, so tables of tables won't be copies.\
- -- The 'clearedElement' parameter is the value which is placed in the lines which are scrolled.\
- -- The direction is a boolean: true for up, false for down.\
- -- NOTE: Indexes must be numerical and in bounds by one in the direction specified.\
- local function scrollTableOnce (_table, clearedElement, startIndex, directionIsUp)\
- if directionIsUP then\
- _table[startIndex] = _table[startIndex + 1]\
- _table[startIndex + 1] = clearedElement\
- else\
- _table[startIndex] = _table[startIndex - 1]\
- _table[startIndex - 1] = clearedElement\
- end\
- end\
- \
- -- Returns a table of the dimensions of the buffer filled with the given character.\
- local function clearBase (buffer, character)\
- local clearedTable = {}\
- \
- for line = 1, buffer.height do\
- clearedTable[line] = \"\"\
- \
- for cell = 1, buffer.width do\
- clearedTable[line] = clearedTable[line] .. character\
- end\
- end\
- \
- return clearedTable\
- end\
- \
- -- Initializes the buffer by creating a redirect table for it and filling\
- -- the buffer with text and color data.\
- local function initialize (buffer)\
- -- Create a table of redirectable functions so that the terminal API can be redirected\
- -- to the buffer.\
- local redirectTable = {}\
- \
- function redirectTable.getSize()\
- return buffer.width, buffer.height\
- end\
- \
- \
- -- START: Cursor redirect functions.\
- function redirectTable.getCursorPos()\
- return buffer.cursorX, buffer.cursorY\
- end\
- function redirectTable.setCursorPos (x, y)\
- -- NOTE: We might have to handle NaN here... Oh well.\
- buffer.cursorX = math.floor (x)\
- buffer.cursorY = math.floor (y)\
- \
- if buffer.isVisible then\
- return term.native.setCursorPos (buffer.cursorX, buffer.cursorY)\
- end\
- end\
- function redirectTable.setCursorBlink (shouldCursorBlink)\
- buffer.shouldCursorBlink = shouldCursorBlink\
- \
- if buffer.isVisible then\
- return term.native.setCursorBlink (shouldCursorBlink)\
- end\
- end\
- -- END: Cursor redirect functions.\
- \
- \
- -- START: Color redirect functions.\
- function redirectTable.isColor()\
- return term.native.isColour()\
- end\
- function redirectTable.setTextColor (color)\
- buffer.currentTextColor = color\
- return term.native.setTextColour (color)\
- end\
- function redirectTable.setBackgroundColor (color)\
- buffer.currentBackColor = color\
- return term.native.setBackgroundColour (color)\
- end\
- redirectTable.isColour = redirectTable.isColor\
- redirectTable.setTextColour = redirectTable.setTextColor\
- redirectTable.setBackgroundColour = redirectTable.setBackgroundColor\
- -- END: Color redirect functions.\
- \
- \
- -- START: Write redirect functions.\
- function redirectTable.write (text)\
- -- Use the native terminal API to write the text to the screen normally and capture\
- -- whatever is returned by that call if the buffer is visible.\
- local nativeTerminalWriteReturnData = nil\
- if buffer.isVisible then\
- term.native.setCursorPos (buffer.cursorX, buffer.cursorY)\
- nativeTerminalWriteReturnData = term.native.write (text)\
- end\
- \
- -- Convert any tabs to spaces and replace any other control characters (escape sequences)\
- -- to question marks. We need to keep track of the original text in order to update the cursor\
- -- properly.\
- local originalText = text\
- text = tostring (text):gsub (\"\\t\", \" \")\
- text = text:gsub (\"%c\", \"?\")\
- \
- -- Make sure that the cursor is in bounds of the buffer in order to write to it.\
- -- Return if the cursor is outside of the buffer. However, we should move the cursor\
- -- as if the text was written.\
- if buffer.cursorX > buffer.width or buffer.cursorY < 1 or buffer.cursorY > buffer.height then\
- buffer.cursorX = buffer.cursorX + text:len()\
- return\
- -- If the cursor is outside to the left of the buffer, then we should truncate the text to see\
- -- if the cursor will end up inside the buffer when the text is written.\
- elseif buffer.cursorX < 1 then\
- -- NOTE: We should add 2 to the position of the substring start point to compensate for the position\
- -- starting at 1 on the terminal instead of 0 for writing.\
- -- NOTE: Keep track of the original text length in case the text won't end up fitting on screen but we\
- -- still need to update the cursor.\
- local textLength = text:len()\
- text = text:sub (math.abs (buffer.cursorX) + 2)\
- \
- -- If the cursor won't end up inside the buffer boundaries, then we shouldn't write\
- -- the text to the buffer, but we should still update the cursor.\
- if text:len() == 0 then\
- buffer.cursorX = buffer.cursorX + textLength\
- return\
- end\
- \
- -- Set the cursor position to 1 if the text will end up fitting so that the truncated text looks\
- -- like it has come in from off the screen when we write it to the buffer.\
- buffer.cursorX = 1\
- end\
- -- If the cursor starts in bounds of the buffer but will end up outside of it when the text is written, just truncate the text to fit\
- -- inside of the buffer.\
- if buffer.cursorX + text:len() > buffer.width then\
- text = text:sub (1, buffer.width - buffer.cursorX + 1)\
- end\
- \
- -- Append the text to the text lines and append the proper serialized colors to the text color lines and the background color lines.\
- buffer.text[buffer.cursorY] = buffer.text[buffer.cursorY]:sub (1, buffer.cursorX - 1) .. text .. buffer.text[buffer.cursorY]:sub (buffer.cursorX + text:len())\
- buffer.textColors[buffer.cursorY] = buffer.textColors[buffer.cursorY]:sub (1, buffer.cursorX - 1) .. (HEX_COLOR_LOOKUP[buffer.currentTextColor]):rep (text:len()) ..\
- buffer.textColors[buffer.cursorY]:sub (buffer.cursorX + text:len())\
- buffer.backColors[buffer.cursorY] = buffer.backColors[buffer.cursorY]:sub (1, buffer.cursorX - 1) .. (HEX_COLOR_LOOKUP[buffer.currentBackColor]):rep (text:len()) ..\
- buffer.backColors[buffer.cursorY]:sub (buffer.cursorX + text:len())\
- \
- -- Update the cursor and return what was returned by the native terminal write call at the start of this function.\
- buffer.cursorX = buffer.cursorX + originalText:len()\
- return nativeTerminalWriteReturnData\
- end\
- function redirectTable.scroll (timesToScroll)\
- -- Only scroll the buffer if the number of times to scroll isn't zero.\
- if timesToScroll ~= 0 then\
- -- If the size of the buffer is 1, then we can just clear the only line in the buffer.\
- if timesToScroll == 1 then\
- redirectTable.clear()\
- return\
- end\
- \
- for timesScrolled = 1, timesToScroll do\
- -- Scroll the buffer up if the value is positive.\
- if timesToScroll > 0 then\
- for lineNumber = 1, buffer.height do\
- scrollTableOnce (buffer.text, (\" \"):rep (buffer.width), lineNumber, true)\
- scrollTableOnce (buffer.textColors, HEX_COLOR_LOOKUP[buffer.currentTextColor]:rep (buffer.width), lineNumber, true)\
- scrollTableOnce (buffer.backColors, HEX_COLOR_LOOKUP[buffer.currentBackColor]:rep (buffer.width), lineNumber, true)\
- end\
- -- Scroll the buffer down if the value is negative.\
- else\
- for lineNumber = buffer.height, 2 do\
- scrollTableOnce (buffer.text, (\" \"):rep (buffer.width), lineNumber, false)\
- scrollTableOnce (buffer.textColors, HEX_COLOR_LOOKUP[buffer.currentTextColor]:rep (buffer.width), lineNumber, false)\
- scrollTableOnce (buffer.backColors, HEX_COLOR_LOOKUP[buffer.currentBackColor]:rep (buffer.width), lineNumber, false)\
- end\
- end\
- end\
- end\
- \
- -- Scroll the native terminal and return whatever is returned by that call only if the buffer is visible.\
- if buffer.isVisible then\
- return term.native.scroll (timesToScroll)\
- end\
- end\
- -- END: Write redirect functions.\
- \
- \
- -- START: Clear redirect functions.\
- function redirectTable.clear()\
- buffer.text = clearBase (buffer, \" \")\
- buffer.textColors = clearBase (buffer, HEX_COLOR_LOOKUP[buffer.currentTextColor])\
- buffer.backColors = clearBase (buffer, HEX_COLOR_LOOKUP[buffer.currentBackColor])\
- \
- -- Only clear the screen if the buffer is visible.\
- if buffer.isVisible then\
- return term.native.clear()\
- end\
- end\
- function redirectTable.clearLine()\
- buffer.text[buffer.cursorY] = (\" \"):rep (buffer.width)\
- buffer.textColors[buffer.cursorY] = HEX_COLOR_LOOKUP[buffer.currentTextColor]:rep (buffer.width)\
- buffer.backColors[buffer.cursorY] = HEX_COLOR_LOOKUP[buffer.currentBackColor]:rep (buffer.width)\
- \
- if buffer.isVisible then\
- return term.native.clearLine()\
- end\
- end\
- -- END: Clear redirect functions.\
- \
- -- Initialize the text and colors for the buffer by calling clear.\
- buffer.isVisible = false\
- redirectTable.clear()\
- buffer.isVisible = true\
- \
- -- Set the redirect table for the buffer to the redirectable table we just created.\
- buffer.redirectTable = redirectTable\
- end\
- \
- -- Renders the buffer to the screen using the native terminal API.\
- function render (buffer)\
- -- Only render the buffer if it is visible.\
- if buffer.isVisible then\
- -- Disable the cursor blink so, in the case that rendering is slow, we don't\
- -- see the cursor flying around everywhere.\
- term.native.setCursorBlink (false)\
- \
- for lineNumber = 1, buffer.height do\
- for cell = 1, buffer.width do\
- term.native.setCursorPos (cell, lineNumber)\
- \
- term.native.setTextColor (HEX_COLOR_LOOKUP[buffer.textColors[lineNumber]:sub (cell, cell)])\
- term.native.setBackgroundColor (HEX_COLOR_LOOKUP[buffer.backColors[lineNumber]:sub (cell, cell)])\
- term.native.write (buffer.text[lineNumber]:sub (cell, cell))\
- end\
- end\
- \
- -- Reset the cursor's blink state to that of the buffer because we turned it off\
- -- for rendering. However, the cursor should only blink if the buffer is visible.\
- term.native.setCursorBlink (buffer.shouldCursorBlink)\
- term.native.setCursorPos (buffer.cursorX, buffer.cursorY)\
- end\
- end\
- \
- -- Constructs and returns a new RedirectBuffer object. Check the design description\
- -- at the top of the file to see how this thing works.\
- function new (width, height)\
- local this = {\
- text = {},\
- textColors = {},\
- backColors = {},\
- \
- cursorX = 1,\
- cursorY = 1,\
- width = width,\
- height = height,\
- \
- currentTextColor = colors.white,\
- currentBackColor = colors.black,\
- \
- shouldCursorBlink = false,\
- redirectTable = {},\
- \
- isVisible = true\
- }\
- \
- setmetatable (this, redirectBufferMetatable)\
- -- Initialize the buffer.\
- initialize (this)\
- \
- return this\
- end",["Main"]="--[[\
- Multi-Program-Shell Trystan Cannon\
- 26 July 2013\
- \
- The multi program shell gives the user the ability\
- to execute multiple programs at the same time. The user\
- can switch between each program being executed at the\
- same time by using a graphical interface which is toggled\
- visible by pressing some bound key.\
- ]]\
- \
- -- Check to see if the multi-program-shell is already running. If it is, then don't allow another\
- -- instance to be spawned.\
- if _G.multiProgram then\
- printError (\"Cannot start another instance of the multi-program-shell.\")\
- return\
- end\
- \
- -- Make sure that the necessary APIs for the program are available.\
- if fs.exists (shell.dir() .. \"/RedirectBuffer\") then\
- os.loadAPI (shell.dir() .. \"/RedirectBuffer\")\
- else\
- printError (\"The Redirect Buffer API is missing from \" .. shell.dir() .. '.')\
- return\
- end\
- if fs.exists (shell.dir() .. \"/Program\") then\
- os.loadAPI (shell.dir() .. \"/Program\")\
- else\
- printError (\"The Program API is missing from \" .. shell.dir() .. '.')\
- return\
- end\
- \
- -- Constants ----------------------------------------------------\
- local RUNNING_PROGRAMS_INTERFACE_PATH = shell.dir() .. \"/RunningProgramsInterface\"\
- local RUNNING_PROGRAMS_INTERFACE_TOGGLE_KEY = keys[\"f10\"] -- The key which is pressed to bring up or close the running programs interface.\
- -- Constants ----------------------------------------------------\
- \
- \
- \
- -- Variables ----------------------------------------------------\
- local multiProgram = getfenv (1) -- The table which will be used to store non-local functions in this file so that programs being executed\
- -- can run other programs using the multiProgramShell.\
- local isViewingRunningPrograms = false -- Whether or not the user is viewing the running programs interface.\
- local programStack = {} -- All of the program objects which are being executed concurrently.\
- -- Variables ----------------------------------------------------\
- \
- \
- \
- -- Runs a program by adding it to the top of the program stack.\
- function run (path, ...)\
- -- Make sure that the program is valid and isn't another copy of the multi-program shell.\
- if fs.exists (path) and not fs.isDir (path) then\
- -- Create a program object for the program that is going to be run.\
- -- After creating said program object, render the buffer for the program object.\
- -- Also, add the multiProgramAPI to the global table so that the running program can\
- -- run programs using the multi program shell.\
- _G.multiProgram = { run = multiProgram.run }\
- \
- programStack[#programStack + 1] = Program.new (path, { shell = shell }, ...)\
- programStack[#programStack].redirectBuffer:render()\
- end\
- end\
- \
- -- Cleans up the program stack by removing all dead programs.\
- -- Returns true or false if there are still running programs.\
- local function cleanProgramStack()\
- -- Keep track of the original size of the program stack before we remove any programs.\
- local originalProgramStackSize = #programStack\
- \
- -- Record all of the indexes of dead programs in the stack.\
- local deadProgramIndexes = {}\
- for deadProgramIndex, program in pairs (programStack) do\
- if coroutine.status (program.programCoroutine) == \"dead\" then\
- table.insert (deadProgramIndexes, deadProgramIndex)\
- end\
- end\
- \
- -- Remove all of the indexes of the dead programs in the stack.\
- for _, deadProgramIndex in pairs (deadProgramIndexes) do\
- table.remove (programStack, deadProgramIndex)\
- end\
- \
- -- If the size of the program stack has changed, then render the program at the\
- -- top of the stack.\
- if originalProgramStackSize ~= #programStack and #programStack > 0 then\
- programStack[#programStack].redirectBuffer:render()\
- end\
- \
- return #programStack > 0\
- end\
- \
- -- Executes the running program interface and returns the value returned by the running programs\
- -- interface script. See the program header of the running programs interface to learn about\
- -- these return values.\
- local function executeRunningProgramsInterface()\
- -- Disable the cursor blink.\
- term.native.setCursorBlink (false)\
- \
- -- Load the running programs interface script into a function who has access to the shell and program stack.\
- local runningProgramsInterfaceEnvironment = setmetatable ({ shell = shell, programStack = programStack }, { __index = _G })\
- local runningProgramsInterfaceFunction = loadfile (RUNNING_PROGRAMS_INTERFACE_PATH)\
- setfenv (runningProgramsInterfaceFunction, runningProgramsInterfaceEnvironment)\
- \
- local programSelection, programPathAndArguments = runningProgramsInterfaceFunction()\
- \
- -- Check to make sure the program path provided is valid.\
- -- If it is, then add it to the stack with arguments passed back.\
- if programPathAndArguments and fs.exists (programPathAndArguments[1]) and not fs.isDir (programPathAndArguments[1]) then\
- local path =table.remove (programPathAndArguments, 1)\
- run (path, unpack (programPathAndArguments))\
- end\
- -- Switch the program at the top of the stack with the one selected.\
- if programSelection and programSelection ~= -1 then\
- local currentProgram = programStack[#programStack]\
- \
- programStack[#programStack] = programStack[programSelection]\
- programStack[programSelection] = currentProgram\
- end\
- \
- -- Reset the cursor blink to what it should be.\
- programStack[#programStack].redirectBuffer:render()\
- end\
- \
- -- Set the directory back to the root and start up an instance of the shell.\
- shell.setDir (\"\")\
- run (\"rom/programs/shell\")\
- \
- -- Execute programs until there are no more programs in the program stack.\
- while cleanProgramStack() do\
- local eventData = { os.pullEventRaw() }\
- \
- -- If the event was termination, then we should kill the program at the top of the stack.\
- if eventData[1] == \"terminate\" then\
- -- Remove the program at the top of the stack and alert the user\
- -- that the program they were viewing has been terminated.\
- programStack[#programStack] = nil\
- \
- printError (\"Terminated.\")\
- term.native.setCursorBlink (false)\
- sleep (0.3)\
- \
- -- Clean up the program stack and render the next program at the top\
- -- if there are any more running programs.\
- if cleanProgramStack() then\
- programStack[#programStack].redirectBuffer:render()\
- programStack[#programStack]:resume (\"key\", -1)\
- end\
- -- If the event was any other event besides termination, then pass on the event to the program\
- -- at the top of the stack.\
- else\
- -- Launch the running programs interface if they keybind for the running programs interface\
- -- was pressed.\
- -- NOTE: We need to prompt the user if they want the keybind event to be sent to the currently running\
- -- program.\
- if eventData[1] == \"key\" and eventData[2] == RUNNING_PROGRAMS_INTERFACE_TOGGLE_KEY then\
- executeRunningProgramsInterface()\
- end\
- \
- programStack[#programStack]:resume (unpack (eventData))\
- end\
- end\
- \
- -- Remove the multi program API and prompt the user that the multi-program-shell has ended.\
- _G.multiProgram = nil\
- print (\"All programs have died. This is the original shell launched at startup.\")",},}
- if fs.exists (unpackDirectoryPath .. "/Multi-Program-Shell/") then
- printError ("The directory to be unpacked already exists on this computer.")
- return
- end
- local function createDirectoryFromUnpackedDirectory (unpackedDirectoryTable, currentPath)
- currentPath = currentPath or unpackDirectoryPath
- if unpackedDirectoryTable then
- if not fs.isDir (currentPath) then
- fs.makeDir (currentPath)
- end
- for name, contents in pairs (unpackedDirectoryTable) do
- if type (contents) == "table" then
- createDirectoryFromUnpackedDirectory (contents, currentPath .. '/' .. name)
- else
- local fileHandle = fs.open (currentPath .. '/' .. name, 'w')
- if fileHandle then
- fileHandle.write (contents)
- fileHandle.close()
- else
- printError ("There was an error unpacking a file to " .. currentPath .. '/' .. name .. '.')
- sleep (1)
- os.reboot()
- end
- end
- end
- else
- printError ("There was an error unpacking the packed directory.")
- end
- end
- createDirectoryFromUnpackedDirectory (packedDirectory, unpackDirectoryPath)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement