This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
GopherAtl's profile picture

Multishell - 10 shells at once without gui

Started by GopherAtl, 07 February 2013 - 07:45 AM
GopherAtl #1
Posted 07 February 2013 - 08:45 AM
Multishell lets you run up to 10 shell sessions at once, switching between them by pressing ctrl+1 through ctrl+0. By default it runs instances of the standard CraftOS shell, but you can specify any program as a parameter when running multishell and switch between 10 instances of that instead.

There are many multitasking "OS" options out there, but all seem to be at least to some extent gui-based, whether the gui is an elaborate windowing system or a simple task bar. These are great, but using them tends to limit available screen area for the current program, block certain keystrokes, or both, and I found myself wanting a way to just switch easily between multiple full-screen programs.

The source can be seen on pastebin here http://pastebin.com/e4Uf8M3p

This uses my goroutine API to handle each session as coroutines, my redirect api to create and switch between buffered redirects for each session, and my ctrlkeys api to generate the ctrl+# input events it uses to switch sessions. To save you the headache of grabbing all of those if you don't already have all of those installed, I've written a simple install program which will grab all three apis and the multishell program itself from pastebin for you. You can get more info on these APIs in my api thread, here.

The installer can be downloaded and run from pastebin:
http://pastebin.com/Pn9wbtJv

just enter the following at a craftOS shell to download and install it:

>pastebin get Pn9wbtJv setupmsh
>setupmsh 

You can then delete setupmsh if you want. Just type "multishell" to launch multishell; you'll be presented with a new shell session, bound to ctrl-1. Ctrl-2 will start and switch to a new session; same with ctrl-3 through ctrl-0. exit from any session and you will be returned to another session; close the last session and multishell will start a new shell 1.

I'll be adding more features in the future, possibly making it more screen-like.
Cranium #2
Posted 07 February 2013 - 09:12 AM
Wow, sounds really cool!
rhyleymaster #3
Posted 07 February 2013 - 05:20 PM
Screenshots please?
GopherAtl #4
Posted 07 February 2013 - 06:15 PM
uhm. As I said, there's no gui. You switch by pressing ctrl+#. What kind of screenshots do you want exactly? O_o

I suppose I could do a video, but there's no possible way to show this with screenshots, it would just look like screenshots of a normal CraftOS shell.
Grim Reaper #5
Posted 07 February 2013 - 07:34 PM
This is pretty cool!

I actually made something similar to this after seeing it in hopes that I could simplify it without the use of an external API.
Here's my attempt:

Spoiler


--[[
    Multishell  Trystan Cannon
                6 February 2013

    This is a clone of the multi-shell program
    posted in the programs section of ComputerCraft
    forums on the day of this file's creation.
]]--

--========================================================
-- Variables:

local PROGRAM_PATH  = "rom/programs/shell" -- The program to run an instance of.
local NUM_INSTANCES = 10 -- The number of instances to run of PROGRAM_PATH.

local instanceStack   = {} -- The stack/table of instance objects. instaceStack[n] = { thread = coroutine, terminal = table }
local currentInstance = 1  -- The index of the current instance object in the instance stack.
--========================================================



--========================================================
-- Terminal functions:

-- Creates and returns a new copy of the term.native function for redirection.
local function createTerminal()
    local terminal = {
        buffer = {}
    }

    for index, item in pairs(term.native) do
        terminal[index] = function( ... )
            terminal.buffer[#terminal.buffer + 1] = {functionName = index, parameters = { ... }}
            return item( ... )
        end
    end

    terminal.flushBuffer = function()
            for index, item in pairs(terminal.buffer) do
                term.native[item.functionName](unpack(item.parameters))
            end
    end
    terminal.emptyBuffer = function()
        terminal.buffer = {}
    end
    terminal.clear = function()
        terminal.emptyBuffer()
        term.native.clear()
    end

    return terminal
end
--========================================================


--========================================================
-- Instance functions:

-- Creates a new instance object using the global PROGRAM_PATH as the target program.
local function createInstance()
    -- Create the new instance object by making a new thread for the program using coroutine.create() and a new terminal
    -- using createTerminal().
    local thread   = coroutine.create(function()
        while true do
            local eventData   = { os.pullEvent() }
            local hasFinished = coroutine.resume(os.run({}, PROGRAM_PATH), unpack( eventData ))

            if hasFinished then
                return "Finished."
            end
        end
    end)

    local instance = { thread = thread, terminal = createTerminal() }

    return instance
end

-- Fills the instance stack with desired instances of the desired program.
local function initInstanceStack()
    for instanceNum = 1, NUM_INSTANCES do
        instanceStack[instanceNum] = createInstance()
    end
end
--========================================================



--========================================================
-- Main entry point:

initInstanceStack()
term.redirect(instanceStack[currentInstance].terminal)

while true do
    local eventData = { os.pullEvent() }

    -- Handle switching instances before anything else.
    if eventData[1] == "key" and (eventData[2] == keys["leftCtrl"] or eventData[2] == keys["rightCtrl"]) then
        -- Start a timer to make sure we don't discard an event to an instance.
        local keyTimer = os.startTimer(0.8)
        local hotKeyEvent, instanceNumber = os.pullEvent()

        -- Swap instances if that is what the user wants to do.
        if hotKeyEvent == "key" and instanceNumber - 1 >= 1 and instanceNumber - 1 <= 10 then
            currentInstance = instanceNumber - 1
            -- Update the current instance.
            term.redirect(instanceStack[currentInstance].terminal)
            term.native.clear()
            term.native.setCursorPos(1, 1)
            instanceStack[currentInstance].terminal.flushBuffer()

            -- Nullify the event for this iteration.
            eventData = {}
        end
    end

    -- Clean up any dead instances.
    for index, instance in pairs(instanceStack) do
        if coroutine.status(instance.thread) == "dead" then
            instanceStack[index] = nil
        end
    end

    -- Update the current thread with this event.
    coroutine.resume(instanceStack[currentInstance].thread, unpack( eventData ))
end

-- If the loop breaks, then restore the terminal.
term.redirect(term.native)
--========================================================
Pastebin.

I managed to crunch it down from 159 lines to 130, even though a lot of my code is filled with comments :P/>. I also managed to do this without an API that wasn't already built into Lua or ComputerCraft.

However, I think your version probably has a lot more ability to control itself. In fact, my program doesn't check if the program is already running, haha.
tesla1889 #6
Posted 07 February 2013 - 08:21 PM
fantastic job to both of you!
AnthonyD98™ #7
Posted 07 February 2013 - 09:51 PM
This looks really cool :)/>
GopherAtl #8
Posted 08 February 2013 - 03:50 AM
Grim: That is quite a bit more compact, but it does have quite a few issues and limitations. The way you handle ctrl-key events can eat events, and only the active shell is updated, the rest are paused until resumed. The approach of queueing all terminal commands and rerunning them to switch terminals also breaks down if a program goes very long without clearing the screen; you'd start to get some pretty serious flickering after a while when you switch between them. And yours does not handle programs that call term.redirect themselves. Also "exit" exits the parent shell, closing them all and shutting down, and ctrl+t terminates the whole thing and drops back to calling shell. All that adds up to making it a lot less usable. But still, for all that, it's impressive what you accomplished in relatively few lines of code!

My goal was to make something I could actually live with all the time. I know it would be more sharable if it didn't have api dependencies, but I always have my apis on every new computer/turtle I set up, so for me at least, the dependencies are a non-issue.
Grim Reaper #9
Posted 08 February 2013 - 12:27 PM
Grim: That is quite a bit more compact, but it does have quite a few issues and limitations. The way you handle ctrl-key events can eat events, and only the active shell is updated, the rest are paused until resumed. The approach of queueing all terminal commands and rerunning them to switch terminals also breaks down if a program goes very long without clearing the screen; you'd start to get some pretty serious flickering after a while when you switch between them. And yours does not handle programs that call term.redirect themselves. Also "exit" exits the parent shell, closing them all and shutting down, and ctrl+t terminates the whole thing and drops back to calling shell. All that adds up to making it a lot less usable. But still, for all that, it's impressive what you accomplished in relatively few lines of code!

My goal was to make something I could actually live with all the time. I know it would be more sharable if it didn't have api dependencies, but I always have my apis on every new computer/turtle I set up, so for me at least, the dependencies are a non-issue.

I completely understand what you mean. My design was intended to just "work". Plus, 45 minutes of work at 22:00 isn't the best time to write something innovative :P/>. I would definitely prefer your program over mine if I were to use something like this. Most of the programs I write are just to prove to myself that I could do it, at least.

The control switching is very slow and the stacking of terminal calls in a buffer does become very resource intensive when it comes to using programs that have a lot of terminal calls. However, I'm pretty sure I've presented a possible to solution to this problem by clearing the buffer every time that a program calls term.clear(). You're right in saying that it doesn't handle programs which call term.redirect(), but this could probably be implemented without too much trouble. As for only updating the current shell, I thought that was the intention when I designed my program :P/>. Also, 'exit' and termination weren't really considered when I wrote this, so you're right there too.

Again, I agree with you on saying that your program is much better in terms of design. Program size, when it comes to ComputerCraft Lua scripts, doesn't really matter unless we're talking thousands upon thousands of lines in difference.

Good job. +1
GopherAtl #10
Posted 08 February 2013 - 12:36 PM
clearing the buffer when you clear the screen was an excellent idea, but it doesn't help with programs like edit, which never call term.clear() and redraw the entire screen repeately. In practice, this is one of the most frequent use cases for me - switching between edit and a shell or lua prompt while fiddling with a file.

All the issues I mentioned could be fixed in your version. The result would probably be less lines in total than mine (meaning my program plus apis combined), but using my apis gives other features not directly related to the multishell concept as well. And as I said, I use the required APIs anyway, so if they're going to be loaded, multishell might as well use them rather than duplicate a subset of their functionality!
Lyqyd #11
Posted 06 July 2013 - 12:17 AM
The install script for this downloads redirect under the name "buffer", and the installation fails to load APIs if it's downloaded into a subfolder. Additionally, os.loadAPI seems to not like loading without leading slashes, or at least full paths. And then, it just hangs. I was trying to help someone get it up and running tonight, and these were the issues we ran into. FYI. :)/>
GopherAtl #12
Posted 06 July 2013 - 08:55 AM
…seriously? And it took how long for anyone to notice, or at least, to speak up about it?

Will fix as soon as I have a chance, on a phone on the back of a boat in the Bahamas atm, may try to fix from my iPad a bit later…
Zudo #13
Posted 06 July 2013 - 12:33 PM
Looks good!
Lyqyd #14
Posted 06 July 2013 - 07:41 PM
Heh, I was trying to get it running myself last night. I had thought you were using it on the previous server. I ended up having to set the guy up with LyqydOS, but he was really looking for something like multishell.
GopherAtl #15
Posted 06 July 2013 - 09:15 PM
I didn't get around to fixing this today, but maybe tomorrow, or worst case I'll flying back home early next week - tho porting biolock. T 1.6 might take priority at that point….
Brodur #16
Posted 07 July 2013 - 08:21 AM
Yes, a fix for this would be much appreciated, I am trying to run a "server" computer that hosts a printer and an IRC, currently it can only do one or the other.
GopherAtl #17
Posted 17 July 2013 - 12:58 AM
Yes, a fix for this would be much appreciated, I am trying to run a "server" computer that hosts a printer and an IRC, currently it can only do one or the other.


got back from bahamas and completely forgot about this until lyqyd reminded me on irc.

All is fixed now!
Brodur #18
Posted 17 July 2013 - 12:19 PM
Much appreciated man.
GopherAtl #19
Posted 18 July 2013 - 12:10 AM
no problem! I'm a bit embarassed it was posted bugged for so long… lemme know if you have any issues!
UMayBleed #20
Posted 18 July 2013 - 04:14 PM
Great work, I could so use this.
jesusthekiller #21
Posted 19 July 2013 - 06:26 AM
As always, good job!
Thib0704 #22
Posted 21 July 2013 - 05:04 PM
This program is amazing !
gicon #23
Posted 14 October 2013 - 08:24 PM
I so love you right now.. I went through SOOOOOO many os's trying to find a multitasker that would accomplish what I wanted to do. And absolutely nothing worked like this. I can now run several open peripheral redirects, monitor redirects, and all from a single computer supporting over 40 users. You sir.. are my hero.
Dragon53535 #24
Posted 15 October 2013 - 06:48 PM
Question: Are you able to have a program establish other programs in the other shells?
gicon #25
Posted 16 October 2013 - 11:27 AM
a program establish other programs? You got me there mate.

are you asking if the computers have become sentient?
Dragon53535 #26
Posted 16 October 2013 - 02:01 PM
Possibly, though what i was wondering was if you could have a program startup other programs inside the other shells.
gicon #27
Posted 21 October 2013 - 09:30 AM
ah, you'd like a startup script for each shell, I do concur, that would be handy.
Dragon53535 #28
Posted 21 October 2013 - 09:45 AM
basically yes, and it would be handy for automation.