Advertisement
PaymentOption

CC Multi-Program-Shell Installer (CCJam)

Jul 28th, 2013
175
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 36.64 KB | None | 0 0
  1. local unpackDirectoryPath = "/"
  2. local packedDirectory     = {["Multi-Program-Shell"]={["Program"]="--[[\
  3.    Program         Trystan Cannon\
  4.                    26 July 2013\
  5.                    \
  6.    Contains the Program class which is used to construct\
  7.    program objects  for use in the main script. Each program\
  8.    object has a redirectable buffer so that programs can be\
  9.    switched between without losing the state of the screen between\
  10.    each switch.\
  11. ]]\
  12. \
  13. -- Variables ----------------------------------------------------\
  14. local programMetatable = { __index = getfenv (1) }\
  15. -- Variables ----------------------------------------------------\
  16. \
  17. \
  18. \
  19. -- Returns the metatable for every program.\
  20. function getMetatable()\
  21.    return programMetatable\
  22. end\
  23. \
  24. -- Resumes the program with the given event data and updates the terminal\
  25. -- using the buffer for the program.\
  26. function resume (program, ...)\
  27.    term.redirect (program.redirectBuffer.redirectTable)\
  28.    local returnData = { coroutine.resume (program.programCoroutine, ...) }\
  29.    term.restore()\
  30.    \
  31.    return returnData\
  32. end\
  33. \
  34. -- Constructs and returns a new Program object.\
  35. -- Returns the data yielded by the coroutine after the first yield.\
  36. -- Basically, if the program returns without yielding (no os.pullEventRaw), then\
  37. -- the data returned by the program after being executed is returned by this constructor.\
  38. function new (programPath, environment, ...)\
  39.    local this = {\
  40.        programPath = programPath,\
  41.        environment = environment or {},\
  42.        \
  43.        programFunction  = nil,\
  44.        programCoroutine = nil,\
  45.        redirectBuffer   = RedirectBuffer.new (term.native.getSize()),\
  46.        \
  47.        programArguments = { ... }\
  48.    }\
  49.    setmetatable (this, programMetatable)\
  50.    \
  51.    -- Load the file for the program at the path specified, set\
  52.    -- the environment for aforementioned function, and create a coroutine\
  53.    -- for the function.\
  54.    this.programFunction = loadfile (this.programPath)\
  55.    setmetatable (this.environment, { __index = _G })\
  56.    setfenv (this.programFunction, this.environment)\
  57.    \
  58.    this.programCoroutine = coroutine.create (this.programFunction)\
  59.    \
  60.    -- Initialize the coroutine, but make sure that the buffer isn't visible.\
  61.    this.redirectBuffer.isVisible = false\
  62.    term.redirect (this.redirectBuffer.redirectTable)\
  63.    local initializationYieldData = { coroutine.resume (this.programCoroutine, unpack (this.programArguments)) }\
  64.    term.restore()\
  65.    this.redirectBuffer.isVisible = true\
  66.    \
  67.    return this\
  68. end",["RunningProgramsInterface"]="--[[\
  69.    Running Programs Interface          Trystan Cannon\
  70.                                        26 July 2013\
  71.                                        \
  72.    This script is used by the Main script for the multi\
  73.    program shell in order to switch between programs which are\
  74.    being executed currently. Also, the ability to start new\
  75.    programs is also available to users.\
  76.    \
  77.    This script, when loaded as a function, returns one of the following\
  78.    values:\
  79.       * -1, programPath, arguments  = The user chose to start a new program. The path for\
  80.                                       aforementioned program is also returned.\
  81.       * n where n >= 1  = The user chose to switch to a program currently running.\
  82.                           The index in the program stack for the chosen program is returned.\
  83. ]]\
  84. \
  85. -- Make sure that we have a program stack to view and make sure that the objects\
  86. -- in the stack are valid programs.\
  87. if not (programStack and getmetatable (programStack[#programStack]) == Program.getMetatable()) then\
  88.    print (\"The running programs interface must be executed by the Multi-Program-Shell.\")\
  89.    return\
  90. end\
  91. \
  92. -- Constants ----------------------------------------------------\
  93. local RUN_BUTTON_TOGGLE                = 'r' -- The character that has to be pressed for the run button to be displayed.\
  94. local SCREEN_WIDTH, SCREEN_HEIGHT      = term.native.getSize() -- The static dimensions of the screen.\
  95. local MOUSE_WHEEL_UP, MOUSE_WHEEL_DOWN = -1, 1 -- The directions of the mouse wheel returned by os.pullEvent.\
  96. -- Constants ----------------------------------------------------\
  97. \
  98. \
  99. \
  100. -- Variables ----------------------------------------------------\
  101. 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.\
  102. local isRunning           = true -- Whether or not the running programs interface is running (the user hasn't made a selection).\
  103. local selectionIndex      = #programStack -- The index of the program currently selected in the program stack for switching.\
  104. \
  105. local programStackTextColor = colors.black\
  106. local programStackBackColor = colors.white\
  107. \
  108. local programStackDrawX = 10 -- The x position that the program stack will be drawn at.\
  109. local programStackDrawY = 5 -- The y position that the program stack will be drawn at.\
  110. \
  111. local programStackDrawWidth  = SCREEN_WIDTH  - programStackDrawX -- The maximum width of each program's name when being drawn on the screen.\
  112. local programStackDrawHeight = SCREEN_HEIGHT - (programStackDrawY + 4) -- The maximum height which the program stack can be drawn to.\
  113. local scroll                 = (selectionIndex - programStackDrawHeight > 0) and selectionIndex - programStackDrawHeight or 0\
  114.                                -- ^The number of entries to scroll the program stack when drawn.\
  115. \
  116. local runButtonLabel = \"|[R]un Program|\"\
  117. local runButtonX     = SCREEN_WIDTH  - runButtonLabel:len()\
  118. local runButtonY     = SCREEN_HEIGHT - 1\
  119. \
  120. local runButtonTextColor = term.native.isColor() and colors.gray or colors.black\
  121. local runButtonBackColor = term.native.isColor() and colors.lightGray or colors.white\
  122. -- Variables ----------------------------------------------------\
  123. \
  124. \
  125. \
  126. -- Clears the screen with a given color (or black by default) and resets\
  127. -- the cursor to the upper left corner.\
  128. local function clearScreen (textColor, backColor)\
  129.    term.native.setTextColor (textColor or colors.white)\
  130.    term.native.setBackgroundColor (backColor or colors.black)\
  131.    \
  132.    term.native.clear()\
  133.    term.native.setCursorPos (1, 1)\
  134. end\
  135. \
  136. -- Writes the given text to the center of the screen on the given line.\
  137. local function writeCentered (text, lineNumber)\
  138.    term.native.setCursorPos (SCREEN_WIDTH / 2 - text:len() / 2, lineNumber)\
  139.    term.native.write (text)\
  140. end\
  141. \
  142. -- Draws a header for the running programs interface.\
  143. local function drawHeader (headerText, headerY)\
  144.    -- Handle any new line characters in the header\
  145.    local linesInHeader = {}\
  146.    for line in headerText:gmatch (\"[^\\n]+\") do\
  147.        table.insert (linesInHeader, line)\
  148.    end\
  149.    \
  150.    -- Write each of the lines in the header to the center of the screen on a separate\
  151.    -- line starting at the headerY provided.\
  152.    for lineIndex = 1, #linesInHeader do\
  153.        writeCentered (linesInHeader[lineIndex], lineIndex + headerY - 1)\
  154.    end\
  155. end\
  156. \
  157. -- Draws the program stack at the given coordinates with each entry\
  158. -- on a separate line for 'height' lines. Each entry starts at 'x' and\
  159. -- goes until 'width' or the length of the entry if it is shorter than\
  160. -- 'width'.\
  161. local function drawProgramStack (x, y, width, height, scroll)\
  162.    local lineNumber = y\
  163.    \
  164.    for programIndex = scroll + 1, scroll + height do\
  165.        if programStack[programIndex] then\
  166.            local programName = (programIndex .. \" \" .. fs.getName (programStack[programIndex].programPath)):sub (1, width)\
  167.            if programIndex == selectionIndex then\
  168.                programName = programName .. \" <-\"\
  169.            end\
  170.            \
  171.            term.native.setCursorPos (x, lineNumber)\
  172.            term.native.write (programName)\
  173.            \
  174.            lineNumber = lineNumber + 1\
  175.        end\
  176.    end\
  177.    \
  178.    -- Display to the user whether or not that there are more programs running\
  179.    -- but aren't visible with the current scroll.\
  180.    if selectionIndex < #programStack then\
  181.        term.native.setCursorPos (width, y + height - 1)\
  182.        term.native.write (\"More...\")\
  183.    end\
  184. end\
  185. \
  186. -- Draws a button at the given position.\
  187. local function drawButton (label, x, y, textColor, backColor)\
  188.    term.native.setTextColor (textColor)\
  189.    term.native.setBackgroundColor (backColor)\
  190.    \
  191.    term.native.setCursorPos (x, y)\
  192.    term.native.write (label)\
  193.    \
  194.    term.native.setTextColor (programStackTextColor)\
  195.    term.native.setBackgroundColor (programStackBackColor)\
  196. end\
  197. \
  198. -- Handles a click on the run button. The user is prompted to enter a path\
  199. -- to a program to run.\
  200. local function handleRunButtonClick()\
  201.    local prompt = \"Enter a program path and arguments:\"\
  202.    term.native.setCursorPos (3, runButtonY - 1)\
  203.    term.native.write (prompt)\
  204.    \
  205.    term.native.setTextColor (runButtonTextColor)\
  206.    term.native.setBackgroundColor (runButtonBackColor)\
  207.    term.native.setCursorPos (2, runButtonY)\
  208.    term.native.clearLine()\
  209.    \
  210.    -- Get the path and arguments for the program.\
  211.    local programAndArguments = read()\
  212.    \
  213.    -- Parse the string returned by spaces with the first argument being the path for the program.\
  214.    newProgramArguments = {}\
  215.    \
  216.    for argument in programAndArguments:gmatch (\"[^%s]+\") do\
  217.        table.insert (newProgramArguments, argument)\
  218.    end\
  219.    \
  220.    -- Close the program and return the arguments entered if the program at the given path\
  221.    -- is valid.\
  222.    if fs.exists (newProgramArguments[1]) and not fs.isDir (newProgramArguments[1]) then\
  223.        isRunning      = false\
  224.        selectionIndex = -1\
  225.    -- Reset the program arguments since the path for the program is invalid.\
  226.    else\
  227.        term.native.setTextColor (colors.red)\
  228.        term.native.setBackgroundColor (programStackBackColor)\
  229.        \
  230.        writeCentered (\"No such program.\", runButtonY - 2)\
  231.        term.native.setTextColor (programStackTextColor)\
  232.        sleep (1)\
  233.        \
  234.        newProgramArguments = nil\
  235.    end\
  236. end\
  237. \
  238. -- Handles events which will affect the state of the screen (mouse wheel scrolls, clicks, and key presses).\
  239. local function handleEvent (eventData)\
  240.    -- If the user wishes to terminate the program, then we should just return\
  241.    -- with the program selection being that which the user started with (program at the top of the stack).\
  242.    if eventData[1] == \"terminate\" then\
  243.        selectionIndex = #programStack\
  244.        isRunning      = false\
  245.    elseif eventData[1] == \"mouse_click\" then\
  246.        -- A click on the run button will allow the user to enter input which is similar to the shell command line, but the full\
  247.        -- path of the program must be specified.\
  248.        if eventData[3] >= runButtonX and eventData[3] <= runButtonX + runButtonLabel:len() and eventData[4] == runButtonY then\
  249.            handleRunButtonClick()\
  250.        -- If the user clicked somewhere on the list of programs which can be switched to, then we should switch the selection index\
  251.        -- to the program that they clicked on.\
  252.        elseif eventData[3] >= programStackDrawX and eventData[3] <= programStackDrawX + programStackDrawWidth and\
  253.               eventData[4] >= programStackDrawY and eventData[4] <= programStackDrawY + math.min (programStackDrawHeight, #programStack) then\
  254.               \
  255.            -- Make sure the click was on a valid program and not just space in the area which is allocated for drawing the programs\
  256.            -- in the stack.\
  257.            if programStack [scroll + eventData[4] - programStackDrawY] then\
  258.                selectionIndex = scroll + eventData[4] - programStackDrawY\
  259.                isRunning      = false\
  260.            end\
  261.        end\
  262.    -- If the user pressed the toggle for the run button, then allow them execute a program with arguments.\
  263.    elseif eventData[1] == \"char\" and eventData[2] == RUN_BUTTON_TOGGLE then\
  264.        handleRunButtonClick()\
  265.    -- If the user spun the mouse-whell, then scroll the selection up or down depending on the direction of the spin.\
  266.    elseif eventData[1] == \"mouse_scroll\" then\
  267.        if eventData[2] == MOUSE_WHEEL_UP and selectionIndex > 1 then\
  268.            selectionIndex = selectionIndex - 1\
  269.        elseif selectionIndex < #programStack then\
  270.            selectionIndex = selectionIndex + 1\
  271.        end\
  272.    -- If the user pressed enter, then return with the selection that the user was on when\
  273.    -- they pressed the button.\
  274.    elseif eventData[1] == \"key\" then\
  275.        if eventData[2] == keys[\"return\"] then\
  276.            isRunning = false\
  277.        -- Move the selection up or down if up or down was pressed and there are still selections\
  278.        -- above or below the current one.\
  279.        elseif eventData[2] == keys[\"up\"] and selectionIndex > 1 then\
  280.            selectionIndex = selectionIndex - 1\
  281.        elseif eventData[2] == keys[\"down\"] and selectionIndex < #programStack then\
  282.            selectionIndex = selectionIndex + 1\
  283.        end\
  284.    end\
  285.    \
  286.    -- Update the scroll of the program stack.\
  287.    scroll = (selectionIndex - programStackDrawHeight > 0) and selectionIndex - programStackDrawHeight or 0\
  288. end\
  289. \
  290. -- Update the screen and handle any events which may relate to the screen.\
  291. while isRunning do\
  292.    clearScreen (programStackTextColor, programStackBackColor)\
  293.    \
  294.    drawButton (runButtonLabel, runButtonX, runButtonY, runButtonTextColor, runButtonBackColor)\
  295.    drawHeader (\"Running Programs\", 2)\
  296.    drawProgramStack (programStackDrawX, programStackDrawY, programStackDrawWidth, programStackDrawHeight, scroll)\
  297.    \
  298.    handleEvent ({ os.pullEventRaw() })\
  299. end\
  300. \
  301. -- Return whatever index for the selection made by the user. See the header of this script\
  302. -- for information on how those work.\
  303. return selectionIndex, newProgramArguments",["RedirectBuffer"]="--[[\
  304.    Redirect Buffer         Trystan Cannon\
  305.                            26 July 2013\
  306.                            \
  307.    Contains the Redirect Buffer class whose objects\
  308.    are used to buffer the screen when programs are redirected\
  309.    to while being run concurrently.\
  310.    \
  311.    When programs switch, the state of the terminal needs to be\
  312.    maintained so that the screen isn't black, the cursor knows where it\
  313.    should be, and the current colors of the screen are remembered.\
  314.    \
  315.    The buffer is stored in three tables: text lines, text color lines, and\
  316.    background colors. The colors are serialized into hex values (0-f) because\
  317.    there are only sixteen colors.\
  318. ]]\
  319. \
  320. -- Constants ----------------------------------------------------\
  321. local HEX_COLOR_LOOKUP = {}\
  322. for _, color in pairs (colors) do\
  323.    if type (color) == \"number\" then\
  324.        -- Format the log base 2 of the color as a one digit hex number (0-f).\
  325.        -- 0 being white and f being black.\
  326.        HEX_COLOR_LOOKUP[color]                   = string.format (\"%x\", math.floor (math.log (color) / math.log (2)))\
  327.        HEX_COLOR_LOOKUP[HEX_COLOR_LOOKUP[color]] = color\
  328.    end\
  329. end\
  330. -- Constants ----------------------------------------------------\
  331. \
  332. \
  333. \
  334. -- Variables ----------------------------------------------------\
  335. local redirectBufferMetatable = { __index = getfenv (1) }\
  336. -- Variables ----------------------------------------------------\
  337. \
  338. \
  339. \
  340. -- Returns the metatable for every redirect buffer.\
  341. function getMetatable()\
  342.    return redirectBufferMetatable\
  343. end\
  344. \
  345. -- Scrolls the table once in the direction provided. References are swapped, so tables of tables won't be copies.\
  346. -- The 'clearedElement' parameter is the value which is placed in the lines which are scrolled.\
  347. -- The direction is a boolean: true for up, false for down.\
  348. -- NOTE: Indexes must be numerical and in bounds by one in the direction specified.\
  349. local function scrollTableOnce (_table, clearedElement, startIndex, directionIsUp)\
  350.    if directionIsUP then\
  351.        _table[startIndex]     = _table[startIndex + 1]\
  352.        _table[startIndex + 1] = clearedElement\
  353.    else\
  354.        _table[startIndex]     = _table[startIndex - 1]\
  355.        _table[startIndex - 1] = clearedElement\
  356.    end\
  357. end\
  358. \
  359. -- Returns a table of the dimensions of the buffer filled with the given character.\
  360. local function clearBase (buffer, character)\
  361.    local clearedTable = {}\
  362.    \
  363.    for line = 1, buffer.height do\
  364.        clearedTable[line] = \"\"\
  365.        \
  366.        for cell = 1, buffer.width do\
  367.            clearedTable[line] = clearedTable[line] .. character\
  368.        end\
  369.    end\
  370.    \
  371.    return clearedTable\
  372. end\
  373. \
  374. -- Initializes the buffer by creating a redirect table for it and filling\
  375. -- the buffer with text and color data.\
  376. local function initialize (buffer)\
  377.    -- Create a table of redirectable functions so that the terminal API can be redirected\
  378.    -- to the buffer.\
  379.    local redirectTable = {}\
  380.    \
  381.    function redirectTable.getSize()\
  382.        return buffer.width, buffer.height\
  383.    end\
  384.    \
  385.    \
  386.    -- START: Cursor redirect functions.\
  387.    function redirectTable.getCursorPos()\
  388.        return buffer.cursorX, buffer.cursorY\
  389.    end\
  390.    function redirectTable.setCursorPos (x, y)\
  391.        -- NOTE: We might have to handle NaN here... Oh well.\
  392.        buffer.cursorX = math.floor (x)\
  393.        buffer.cursorY = math.floor (y)\
  394.        \
  395.        if buffer.isVisible then\
  396.            return term.native.setCursorPos (buffer.cursorX, buffer.cursorY)\
  397.        end\
  398.    end\
  399.    function redirectTable.setCursorBlink (shouldCursorBlink)\
  400.        buffer.shouldCursorBlink = shouldCursorBlink\
  401.        \
  402.        if buffer.isVisible then\
  403.            return term.native.setCursorBlink (shouldCursorBlink)\
  404.        end\
  405.    end\
  406.    -- END: Cursor redirect functions.\
  407.    \
  408.    \
  409.    -- START: Color redirect functions.\
  410.    function redirectTable.isColor()\
  411.        return term.native.isColour()\
  412.    end\
  413.    function redirectTable.setTextColor (color)\
  414.        buffer.currentTextColor = color\
  415.        return term.native.setTextColour (color)\
  416.    end\
  417.    function redirectTable.setBackgroundColor (color)\
  418.        buffer.currentBackColor = color\
  419.        return term.native.setBackgroundColour (color)\
  420.    end\
  421.    redirectTable.isColour            = redirectTable.isColor\
  422.    redirectTable.setTextColour       = redirectTable.setTextColor\
  423.    redirectTable.setBackgroundColour = redirectTable.setBackgroundColor\
  424.    -- END: Color redirect functions.\
  425.    \
  426.    \
  427.    -- START: Write redirect functions.\
  428.    function redirectTable.write (text)\
  429.        -- Use the native terminal API to write the text to the screen normally and capture\
  430.        -- whatever is returned by that call if the buffer is visible.\
  431.        local nativeTerminalWriteReturnData = nil\
  432.        if buffer.isVisible then\
  433.            term.native.setCursorPos (buffer.cursorX, buffer.cursorY)\
  434.            nativeTerminalWriteReturnData = term.native.write (text)\
  435.        end\
  436.        \
  437.        -- Convert any tabs to spaces and replace any other control characters (escape sequences)\
  438.        -- to question marks. We need to keep track of the original text in order to update the cursor\
  439.        -- properly.\
  440.        local originalText = text\
  441.              text         = tostring (text):gsub (\"\\t\", \" \")\
  442.              text         = text:gsub (\"%c\", \"?\")\
  443.        \
  444.        -- Make sure that the cursor is in bounds of the buffer in order to write to it.\
  445.        -- Return if the cursor is outside of the buffer. However, we should move the cursor\
  446.        -- as if the text was written.\
  447.        if buffer.cursorX > buffer.width or buffer.cursorY < 1 or buffer.cursorY > buffer.height then\
  448.            buffer.cursorX = buffer.cursorX + text:len()\
  449.            return\
  450.        -- If the cursor is outside to the left of the buffer, then we should truncate the text to see\
  451.        -- if the cursor will end up inside the buffer when the text is written.\
  452.        elseif buffer.cursorX < 1 then\
  453.            -- NOTE: We should add 2 to the position of the substring start point to compensate for the position\
  454.            -- starting at 1 on the terminal instead of 0 for writing.\
  455.            -- NOTE: Keep track of the original text length in case the text won't end up fitting on screen but we\
  456.            -- still need to update the cursor.\
  457.            local textLength = text:len()\
  458.            text             = text:sub (math.abs (buffer.cursorX) + 2)\
  459.            \
  460.            -- If the cursor won't end up inside the buffer boundaries, then we shouldn't write\
  461.            -- the text to the buffer, but we should still update the cursor.\
  462.            if text:len() == 0 then\
  463.                buffer.cursorX = buffer.cursorX + textLength\
  464.                return\
  465.            end\
  466.            \
  467.            -- Set the cursor position to 1 if the text will end up fitting so that the truncated text looks\
  468.            -- like it has come in from off the screen when we write it to the buffer.\
  469.            buffer.cursorX = 1\
  470.        end\
  471.        -- 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\
  472.        -- inside of the buffer.\
  473.        if buffer.cursorX + text:len() > buffer.width then\
  474.            text = text:sub (1, buffer.width - buffer.cursorX + 1)\
  475.        end\
  476.        \
  477.        -- Append the text to the text lines and append the proper serialized colors to the text color lines and the background color lines.\
  478.        buffer.text[buffer.cursorY]       = buffer.text[buffer.cursorY]:sub (1, buffer.cursorX - 1) .. text .. buffer.text[buffer.cursorY]:sub (buffer.cursorX + text:len())\
  479.        buffer.textColors[buffer.cursorY] = buffer.textColors[buffer.cursorY]:sub (1, buffer.cursorX - 1) .. (HEX_COLOR_LOOKUP[buffer.currentTextColor]):rep (text:len()) ..\
  480.                                            buffer.textColors[buffer.cursorY]:sub (buffer.cursorX + text:len())\
  481.        buffer.backColors[buffer.cursorY] = buffer.backColors[buffer.cursorY]:sub (1, buffer.cursorX - 1) .. (HEX_COLOR_LOOKUP[buffer.currentBackColor]):rep (text:len()) ..\
  482.                                            buffer.backColors[buffer.cursorY]:sub (buffer.cursorX + text:len())\
  483.        \
  484.        -- Update the cursor and return what was returned by the native terminal write call at the start of this function.\
  485.        buffer.cursorX = buffer.cursorX + originalText:len()\
  486.        return nativeTerminalWriteReturnData\
  487.    end\
  488.    function redirectTable.scroll (timesToScroll)\
  489.        -- Only scroll the buffer if the number of times to scroll isn't zero.\
  490.        if timesToScroll ~= 0 then\
  491.            -- If the size of the buffer is 1, then we can just clear the only line in the buffer.\
  492.            if timesToScroll == 1 then\
  493.                redirectTable.clear()\
  494.                return\
  495.            end\
  496.            \
  497.            for timesScrolled = 1, timesToScroll do\
  498.                -- Scroll the buffer up if the value is positive.\
  499.                if timesToScroll > 0 then\
  500.                    for lineNumber = 1, buffer.height do\
  501.                        scrollTableOnce (buffer.text, (\" \"):rep (buffer.width), lineNumber, true)\
  502.                        scrollTableOnce (buffer.textColors, HEX_COLOR_LOOKUP[buffer.currentTextColor]:rep (buffer.width), lineNumber, true)\
  503.                        scrollTableOnce (buffer.backColors, HEX_COLOR_LOOKUP[buffer.currentBackColor]:rep (buffer.width), lineNumber, true)\
  504.                    end\
  505.                -- Scroll the buffer down if the value is negative.\
  506.                else\
  507.                    for lineNumber = buffer.height, 2 do\
  508.                        scrollTableOnce (buffer.text, (\" \"):rep (buffer.width), lineNumber, false)\
  509.                        scrollTableOnce (buffer.textColors, HEX_COLOR_LOOKUP[buffer.currentTextColor]:rep (buffer.width), lineNumber, false)\
  510.                        scrollTableOnce (buffer.backColors, HEX_COLOR_LOOKUP[buffer.currentBackColor]:rep (buffer.width), lineNumber, false)\
  511.                    end\
  512.                end\
  513.            end\
  514.        end\
  515.        \
  516.        -- Scroll the native terminal and return whatever is returned by that call only if the buffer is visible.\
  517.        if buffer.isVisible then\
  518.            return term.native.scroll (timesToScroll)\
  519.        end\
  520.    end\
  521.    -- END: Write redirect functions.\
  522.    \
  523.    \
  524.    -- START: Clear redirect functions.\
  525.    function redirectTable.clear()\
  526.        buffer.text       = clearBase (buffer, \" \")\
  527.        buffer.textColors = clearBase (buffer, HEX_COLOR_LOOKUP[buffer.currentTextColor])\
  528.        buffer.backColors = clearBase (buffer, HEX_COLOR_LOOKUP[buffer.currentBackColor])\
  529.       \
  530.       -- Only clear the screen if the buffer is visible.\
  531.        if buffer.isVisible then\
  532.            return term.native.clear()\
  533.        end\
  534.    end\
  535.    function redirectTable.clearLine()\
  536.        buffer.text[buffer.cursorY]       = (\" \"):rep (buffer.width)\
  537.        buffer.textColors[buffer.cursorY] = HEX_COLOR_LOOKUP[buffer.currentTextColor]:rep (buffer.width)\
  538.        buffer.backColors[buffer.cursorY] = HEX_COLOR_LOOKUP[buffer.currentBackColor]:rep (buffer.width)\
  539.        \
  540.        if buffer.isVisible then\
  541.            return term.native.clearLine()\
  542.        end\
  543.    end\
  544.    -- END: Clear redirect functions.\
  545.    \
  546.    -- Initialize the text and colors for the buffer by calling clear.\
  547.    buffer.isVisible = false\
  548.    redirectTable.clear()\
  549.    buffer.isVisible = true\
  550.    \
  551.    -- Set the redirect table for the buffer to the redirectable table we just created.\
  552.    buffer.redirectTable = redirectTable\
  553. end\
  554. \
  555. -- Renders the buffer to the screen using the native terminal API.\
  556. function render (buffer)\
  557.    -- Only render the buffer if it is visible.\
  558.    if buffer.isVisible then\
  559.        -- Disable the cursor blink so, in the case that rendering is slow, we don't\
  560.        -- see the cursor flying around everywhere.\
  561.        term.native.setCursorBlink (false)\
  562.        \
  563.        for lineNumber = 1, buffer.height do\
  564.            for cell = 1, buffer.width do\
  565.                term.native.setCursorPos (cell, lineNumber)\
  566.                \
  567.                term.native.setTextColor (HEX_COLOR_LOOKUP[buffer.textColors[lineNumber]:sub (cell, cell)])\
  568.                term.native.setBackgroundColor (HEX_COLOR_LOOKUP[buffer.backColors[lineNumber]:sub (cell, cell)])\
  569.                term.native.write (buffer.text[lineNumber]:sub (cell, cell))\
  570.            end\
  571.        end\
  572.        \
  573.        -- Reset the cursor's blink state to that of the buffer because we turned it off\
  574.        -- for rendering. However, the cursor should only blink if the buffer is visible.\
  575.        term.native.setCursorBlink (buffer.shouldCursorBlink)\
  576.        term.native.setCursorPos (buffer.cursorX, buffer.cursorY)\
  577.    end\
  578. end\
  579. \
  580. -- Constructs and returns a new RedirectBuffer object. Check the design description\
  581. -- at the top of the file to see how this thing works.\
  582. function new (width, height)\
  583.    local this = {\
  584.        text       = {},\
  585.        textColors = {},\
  586.        backColors = {},\
  587.        \
  588.        cursorX = 1,\
  589.        cursorY = 1,\
  590.        width   = width,\
  591.        height  = height,\
  592.        \
  593.        currentTextColor = colors.white,\
  594.        currentBackColor = colors.black,\
  595.        \
  596.        shouldCursorBlink = false,\
  597.        redirectTable     = {},\
  598.        \
  599.        isVisible = true\
  600.    }\
  601.    \
  602.    setmetatable (this, redirectBufferMetatable)\
  603.    -- Initialize the buffer.\
  604.    initialize (this)\
  605.    \
  606.    return this\
  607. end",["Main"]="--[[\
  608.    Multi-Program-Shell         Trystan Cannon\
  609.                                26 July 2013\
  610.                                \
  611.        The multi program shell gives the user the ability\
  612.    to execute multiple programs at the same time. The user\
  613.    can switch between each program being executed at the\
  614.    same time by using a graphical interface which is toggled\
  615.    visible by pressing some bound key.\
  616. ]]\
  617. \
  618. -- Check to see if the multi-program-shell is already running. If it is, then don't allow another\
  619. -- instance to be spawned.\
  620. if _G.multiProgram then\
  621.    printError (\"Cannot start another instance of the multi-program-shell.\")\
  622.    return\
  623. end\
  624. \
  625. -- Make sure that the necessary APIs for the program are available.\
  626. if fs.exists (shell.dir() .. \"/RedirectBuffer\") then\
  627.    os.loadAPI (shell.dir() .. \"/RedirectBuffer\")\
  628. else\
  629.    printError (\"The Redirect Buffer API is missing from \" .. shell.dir() .. '.')\
  630.    return\
  631. end\
  632. if fs.exists (shell.dir() .. \"/Program\") then\
  633.    os.loadAPI (shell.dir() .. \"/Program\")\
  634. else\
  635.    printError (\"The Program API is missing from \" .. shell.dir() .. '.')\
  636.    return\
  637. end\
  638. \
  639. -- Constants ----------------------------------------------------\
  640. local RUNNING_PROGRAMS_INTERFACE_PATH       = shell.dir() .. \"/RunningProgramsInterface\"\
  641. local RUNNING_PROGRAMS_INTERFACE_TOGGLE_KEY = keys[\"f10\"] -- The key which is pressed to bring up or close the running programs interface.\
  642. -- Constants ----------------------------------------------------\
  643. \
  644. \
  645. \
  646. -- Variables ----------------------------------------------------\
  647. local multiProgram = getfenv (1) -- The table which will be used to store non-local functions in this file so that programs being executed\
  648.                                             -- can run other programs using the multiProgramShell.\
  649. local isViewingRunningPrograms = false -- Whether or not the user is viewing the running programs interface.\
  650. local programStack             = {} -- All of the program objects which are being executed concurrently.\
  651. -- Variables ----------------------------------------------------\
  652. \
  653. \
  654. \
  655. -- Runs a program by adding it to the top of the program stack.\
  656. function run (path, ...)\
  657.    -- Make sure that the program is valid and isn't another copy of the multi-program shell.\
  658.    if fs.exists (path) and not fs.isDir (path) then\
  659.        -- Create a program object for the program that is going to be run.\
  660.        -- After creating said program object, render the buffer for the program object.\
  661.        -- Also, add the multiProgramAPI to the global table so that the running program can\
  662.        -- run programs using the multi program shell.\
  663.        _G.multiProgram = { run = multiProgram.run }\
  664.        \
  665.        programStack[#programStack + 1] = Program.new (path, { shell = shell }, ...)\
  666.        programStack[#programStack].redirectBuffer:render()\
  667.    end\
  668. end\
  669. \
  670. -- Cleans up the program stack by removing all dead programs.\
  671. -- Returns true or false if there are still running programs.\
  672. local function cleanProgramStack()\
  673.    -- Keep track of the original size of the program stack before we remove any programs.\
  674.    local originalProgramStackSize = #programStack\
  675.    \
  676.    -- Record all of the indexes of dead programs in the stack.\
  677.    local deadProgramIndexes = {}\
  678.    for deadProgramIndex, program in pairs (programStack) do\
  679.        if coroutine.status (program.programCoroutine) == \"dead\" then\
  680.            table.insert (deadProgramIndexes, deadProgramIndex)\
  681.        end\
  682.    end\
  683.    \
  684.    -- Remove all of the indexes of the dead programs in the stack.\
  685.    for _, deadProgramIndex in pairs (deadProgramIndexes) do\
  686.        table.remove (programStack, deadProgramIndex)\
  687.    end\
  688.    \
  689.    -- If the size of the program stack has changed, then render the program at the\
  690.    -- top of the stack.\
  691.    if originalProgramStackSize ~= #programStack and #programStack > 0 then\
  692.        programStack[#programStack].redirectBuffer:render()\
  693.    end\
  694.    \
  695.    return #programStack > 0\
  696. end\
  697. \
  698. -- Executes the running program interface and returns the value returned by the running programs\
  699. -- interface script. See the program header of the running programs interface to learn about\
  700. -- these return values.\
  701. local function executeRunningProgramsInterface()\
  702.    -- Disable the cursor blink.\
  703.    term.native.setCursorBlink (false)\
  704.    \
  705.    -- Load the running programs interface script into a function who has access to the shell and program stack.\
  706.    local runningProgramsInterfaceEnvironment = setmetatable ({ shell = shell, programStack = programStack }, { __index = _G })\
  707.    local runningProgramsInterfaceFunction    = loadfile (RUNNING_PROGRAMS_INTERFACE_PATH)\
  708.    setfenv (runningProgramsInterfaceFunction, runningProgramsInterfaceEnvironment)\
  709.    \
  710.    local programSelection, programPathAndArguments = runningProgramsInterfaceFunction()\
  711.    \
  712.    -- Check to make sure the program path provided is valid.\
  713.    -- If it is, then add it to the stack with arguments passed back.\
  714.    if programPathAndArguments and fs.exists (programPathAndArguments[1]) and not fs.isDir (programPathAndArguments[1]) then\
  715.        local path =table.remove (programPathAndArguments, 1)\
  716.        run (path, unpack (programPathAndArguments))\
  717.    end\
  718.    -- Switch the program at the top of the stack with the one selected.\
  719.    if programSelection and programSelection ~= -1 then\
  720.        local currentProgram = programStack[#programStack]\
  721.        \
  722.        programStack[#programStack]    = programStack[programSelection]\
  723.        programStack[programSelection] = currentProgram\
  724.    end\
  725.    \
  726.    -- Reset the cursor blink to what it should be.\
  727.    programStack[#programStack].redirectBuffer:render()\
  728. end\
  729. \
  730. -- Set the directory back to the root and start up an instance of the shell.\
  731. shell.setDir (\"\")\
  732. run (\"rom/programs/shell\")\
  733. \
  734. -- Execute programs until there are no more programs in the program stack.\
  735. while cleanProgramStack() do\
  736.    local eventData = { os.pullEventRaw() }\
  737.    \
  738.    -- If the event was termination, then we should kill the program at the top of the stack.\
  739.    if eventData[1] == \"terminate\" then\
  740.        -- Remove the program at the top of the stack and alert the user\
  741.        -- that the program they were viewing has been terminated.\
  742.        programStack[#programStack] = nil\
  743.        \
  744.        printError (\"Terminated.\")\
  745.        term.native.setCursorBlink (false)\
  746.        sleep (0.3)\
  747.        \
  748.        -- Clean up the program stack and render the next program at the top\
  749.        -- if there are any more running programs.\
  750.        if cleanProgramStack() then\
  751.            programStack[#programStack].redirectBuffer:render()\
  752.            programStack[#programStack]:resume (\"key\", -1)\
  753.        end\
  754.    -- If the event was any other event besides termination, then pass on the event to the program\
  755.    -- at the top of the stack.\
  756.    else\
  757.        -- Launch the running programs interface if they keybind for the running programs interface\
  758.        -- was pressed.\
  759.        -- NOTE: We need to prompt the user if they want the keybind event to be sent to the currently running\
  760.        -- program.\
  761.        if eventData[1] == \"key\" and eventData[2] == RUNNING_PROGRAMS_INTERFACE_TOGGLE_KEY then\
  762.            executeRunningProgramsInterface()\
  763.        end\
  764.        \
  765.        programStack[#programStack]:resume (unpack (eventData))\
  766.    end\
  767. end\
  768. \
  769. -- Remove the multi program API and prompt the user that the multi-program-shell has ended.\
  770. _G.multiProgram = nil\
  771. print (\"All programs have died. This is the original shell launched at startup.\")",},}
  772.  
  773. if fs.exists (unpackDirectoryPath .. "/Multi-Program-Shell/") then
  774.     printError ("The directory to be unpacked already exists on this computer.")
  775.     return
  776. end
  777.  
  778. local function createDirectoryFromUnpackedDirectory (unpackedDirectoryTable, currentPath)
  779.     currentPath = currentPath or unpackDirectoryPath
  780.    
  781.     if unpackedDirectoryTable then
  782.         if not fs.isDir (currentPath) then
  783.             fs.makeDir (currentPath)
  784.         end
  785.        
  786.         for name, contents in pairs (unpackedDirectoryTable) do
  787.             if type (contents) == "table" then
  788.                 createDirectoryFromUnpackedDirectory (contents, currentPath .. '/' .. name)
  789.             else
  790.                 local fileHandle = fs.open (currentPath .. '/' .. name, 'w')
  791.                
  792.                 if fileHandle then
  793.                     fileHandle.write (contents)
  794.                     fileHandle.close()
  795.                 else
  796.                     printError ("There was an error unpacking a file to " .. currentPath .. '/' .. name .. '.')
  797.                     sleep (1)
  798.                    
  799.                     os.reboot()
  800.                 end
  801.             end
  802.         end
  803.     else
  804.         printError ("There was an error unpacking the packed directory.")
  805.     end
  806. end
  807.  
  808. createDirectoryFromUnpackedDirectory (packedDirectory, unpackDirectoryPath)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement