Posted 10 October 2015 - 08:41 PM
I'm posting two APIs here, as one is just a smaller part of the TRoR API which I thought some people may find useful as a stand alone API. TRoR is short for Term Redirect over Rednet. This isn't a new concept, and it has been done before. I built this because I couldn't find any TRoR implementations which did exactly what I needed, and because I have way too much free time. This TRoR implementation includes a basic packet system for transferring serialized information along with sender and recipient details over rednet. This also allows you to filter incoming packets very easily. The API allows you to send the term commands (such as "clear", "setColor", etc.) to another computer, and have that computer receive and execute those commands. This effectively causes the receiving computer to display the same information written to the screen of the sending computer after the API is activated. The API also includes the ability to transfer events ("mouse_click", "key", "terminate", etc) from one computer to the next. You may provide functions to this portion of the API which will modify the events going out or coming in (including coming in from the sender and the local computer).
Download:
packet API: http://pastebin.com/8CfjPgCu or run "pastebin get 8CfjPgCu packet" on a CC computer
tror API: http://pastebin.com/kjKrUP41 or run "pastebin get kjKrUP41 tror" on a CC computer
Note: The tror API has the packet API built in, you do not need to download them together.
This API isn't as polished as I would like it to be, but I don't have any more time to work on it. I may eventually get around to polishing it more, but not right now. If you find any bugs, please provide me with details on how to reproduce the error, and I'll get around to fixing it eventually.
Download:
packet API: http://pastebin.com/8CfjPgCu or run "pastebin get 8CfjPgCu packet" on a CC computer
tror API: http://pastebin.com/kjKrUP41 or run "pastebin get kjKrUP41 tror" on a CC computer
Note: The tror API has the packet API built in, you do not need to download them together.
Function headers and documentation for the packet API
--[[PACKET API: This API allows information to be easily transferred between computers. Packets are formed in a similar fashion to real network packets. Each packet
contains the type of the program sending it ("server", "client", "browser", etc.), the sender's computer name and ID, the recipient's (destination's) computer name and
ID, the type of the data being sent, and the data itself. The data may be anything except a function or a table containing a function. Packets are serialized as strings
and sent over rednet. The recipient ID is used as the sending channel: pastebin:8CfjPgCu]]
--Utility for quickly yielding. This is very important for the queue functions as they are intended to run parallel to other functions
function yield()
--[[Utility for checking if a variable is of a required type. If the type matches, true is returned. Otherwise false may be returned, or a function may be provided to
take come custom action such as raising an error or returning an error message. Passing "standard_error" as the function will cause an error to be raised with a common
error message. The level argument is the stack level above this function call which is responsible for the error. This defaults to the level above the caller]]
function checkType(mVar, sType, func, nLevel)
--UnProtected CALL at a specified Level. Re-raises any error raised by the given function at the specified level. This makes sure our functions don't get blamed for a
--user's mistake
local function upcallL(nLevel, func, ...)
--Utility for quickly clearing a table, used to empty the queues after they've been processed in multi-packet mode
function clearTable(tTbl)
--Copies a table (in case you didn't know)
function copyTable(tTbl)
--Utility for creating packet tables
function asPacketData(senderType, senderName, senderID, recipientName, recipientID, dataType, data, tOther)
--Utility for creating packet tables to be passed to send as send does not need to know the sender name or ID
function asPacketDataS(senderType, recipientName, recipientID, dataType, data, tOther)
--[[Compares two packets, a sample and a real packet, to check for things like who the sender was, and who they were sending to. This is intended to be used to check if
this computer was the intended recipient. If all values in the sample packet match the same values in the real packet, all values in the sample packets are functions
which take the same value of the sample packet as an argument and calling these functions return true, or any combination of the two, this function will return true.
Otherwise, it will return false. Any values in the packet which are not in the sample packet will be ignored]]
function checkPacket(tSamplePacket, tPacket)
--Formats informations about who's sending, who they're sending to, and what they're sending and returns it as a string which can be sent over rednet
function asPacket(...)
--Formats a string as a table of data, handling any error caused by the process and returning them as an errorMessage packet
function asData(sPacket)
--Sends a packet on the channel of the given recipient recipient ID
function send(sSenderType, sRecipientName, nRecipientID, sDataType, mData)
--[[Receives a packet and converts it into data. If a timeout is specified, and no data is received in that amount of time, an empty or "blank" packet will be returned
If a sample packet is specified, this function will not return until it receives a packet which passes the check against the specified sample packet. If specified, this
function may return blank packets, even if they do not match the sample packet. This defaults to false]]
function receive(nTimeout, tSamplePacket)
--Receives a packet, or multi-packet, and places it in the queue
function queueReceiver(tQueue, tSamplePacket)
--Runs parallel to a function which should fill the given queue with information to send. This information should NOT be formatted as a packet. As information is
--received, it will be formatted as a packet, or as a multi-packet if the third function argument is true
function queueSender(tQueue, tTemplatePacket, bMultiPacketMode)
--Runs the given function parallel to the queueReceiver. The function is called every time a new piece of data is received and is passed that piece of data as an argument
function packetListener(func, tSamplePacket)
--Runs the given function parallel to the queueSender. The queue is passed to the function as an argument, anything placed in the queue will be sent over rednet and deleted
function packetSender(func, tTemplatePacket, bMultiPacketMode)
Function headers and documentation for the tror API (including the packet API)
--[[PACKET API: This API allows information to be easily transferred between computers. Packets are formed in a similar fashion to real network packets. Each packet
contains the type of the program sending it ("server", "client", "browser", etc.), the sender's computer name and ID, the recipient's (destination's) computer name and
ID, the type of the data being sent, and the data itself. The data may be anything except a function or a table containing a function. Packets are serialized as strings
and sent over rednet. The recipient ID is used as the sending channel: pastebin:8CfjPgCu]]
--Utility for quickly yielding. This is very important for the queue functions as they are intended to run parallel to other functions
function yield()
--[[Utility for checking if a variable is of a required type. If the type matches, true is returned. Otherwise false may be returned, or a function may be provided to
take come custom action such as raising an error or returning an error message. Passing "standard_error" as the function will cause an error to be raised with a common
error message. The level argument is the stack level above this function call which is responsible for the error. This defaults to the level above the caller]]
function checkType(mVar, sType, func, nLevel)
--UnProtected CALL at a specified Level. Re-raises any error raised by the given function at the specified level. This makes sure our functions don't get blamed for a
--user's mistake
local function upcallL(nLevel, func, ...)
--Utility for quickly clearing a table, used to empty the queues after they've been processed in multi-packet mode
function clearTable(tTbl)
--Copies a table (in case you didn't know)
function copyTable(tTbl)
--Utility for creating packet tables
function asPacketData(senderType, senderName, senderID, recipientName, recipientID, dataType, data, tOther)
--Utility for creating packet tables to be passed to send as send does not need to know the sender name or ID
function asPacketDataS(senderType, recipientName, recipientID, dataType, data, tOther)
--[[Compares two packets, a sample and a real packet, to check for things like who the sender was, and who they were sending to. This is intended to be used to check if
this computer was the intended recipient. If all values in the sample packet match the same values in the real packet, all values in the sample packets are functions
which take the same value of the sample packet as an argument and calling these functions return true, or any combination of the two, this function will return true.
Otherwise, it will return false. Any values in the packet which are not in the sample packet will be ignored]]
function checkPacket(tSamplePacket, tPacket)
--Formats informations about who's sending, who they're sending to, and what they're sending and returns it as a string which can be sent over rednet
function asPacket(...)
--Formats a string as a table of data, handling any error caused by the process and returning them as an errorMessage packet
function asData(sPacket)
--Sends a packet on the channel of the given recipient recipient ID
function send(sSenderType, sRecipientName, nRecipientID, sDataType, mData)
--[[Receives a packet and converts it into data. If a timeout is specified, and no data is received in that amount of time, an empty or "blank" packet will be returned
If a sample packet is specified, this function will not return until it receives a packet which passes the check against the specified sample packet. If specified, this
function may return blank packets, even if they do not match the sample packet. This defaults to false]]
function receive(nTimeout, tSamplePacket)
--Receives a packet, or multi-packet, and places it in the queue
function queueReceiver(tQueue, tSamplePacket)
--Runs parallel to a function which should fill the given queue with information to send. This information should NOT be formatted as a packet. As information is
--received, it will be formatted as a packet, or as a multi-packet if the third function argument is true
function queueSender(tQueue, tTemplatePacket, bMultiPacketMode)
--Runs the given function parallel to the queueReceiver. The function is called every time a new piece of data is received and is passed that piece of data as an argument
function packetListener(func, tSamplePacket)
--Runs the given function parallel to the queueSender. The queue is passed to the function as an argument, anything placed in the queue will be sent over rednet and deleted
function packetSender(func, tTemplatePacket, bMultiPacketMode)
--END PACKET API
--[[TRoR API: This API allows a program to share screen output with other computers, or receive it from another computer. It allows events to be transferred between
computers, and it allows them to be modified, both before and after being sent, and just before they are pulled. It allows the shell to be escaped and a function to be
run in the top level coroutine. This is because the multishell will strip flags off of mouse click events. Three built in functions will run the shell, the multishell,
and a modified version of the multishll which doesn't strip mouse click events: pastebin:kjKrUP41]]
--Kills the current running shell, allowing the given function to run in the top level coroutine
--TLCO: http://www.computercraft.info/forums2/index.php?/topic/22078-extremely-short-no-multishell-tlco-kills-multishell-runs-normal-shell-as-the-top-level-coroutine/
function killShell(func, ...)
--Designed to be passed to killShell as the func argument. Runs the regular shell. This is one of the two functions which may be run to fix mouse click modification
function runShell(...)
--Designed to be passed to killShell as the func argument. Runs the multishell. This program will break mouse click modification
function runMultishell(...)
--Runs a modified version of the multishell which doesn't strip the flags off of mouse click events.
function clickSafeMultishell()
--Designed to be passed to killShell as the func argument. Runs the multishell in the top level coroutine. This is one of the two functions which may be run to fix mouse
--click modification
function runClickSafeMultishell(...)
--Receives terminal commands from a remote computer and draws them on this computers screen
function receiveScreen(tSamplePacket)
--Sends terminal commands over rednet to be drawn by a remote computer
function sendScreen(func, tTemplatePacket, bMultiPacketMode)
--Receives events and queues them. A table containing functions which receive a table containing an event and return a table containing a modified version may be
--provided. These may return the event as is, modify it as needed, or return nil to cancel the event
function receiveEvents(func, tSamplePacket, tEventModifiers)
--Sends events to a remote computer. A table containing functions which receive a table containing an event and return a table containing a modified version may be
--provided. These may return the event as is, modify it as needed, or return nil to cancel the event
function sendEvents(tTemplatePacket, tEventModifiers, bMultiPacketMode)
--This function returns nil on any even which hasn't been modified, but just returns the event otherwise. The return result will always be nil, which will make the event
--it processes void. This may be used as an event modifier
function eventVoider(tEvent)
--When called, this creates a function which swaps out the name of the event for whatever the specified new name is. The return value may be used as an event modifier
function createEventSwapper(sNewName, ...)
Example remote control computer
--Reload the API (or load it if this is the first time)
os.unloadAPI("tror")
os.loadAPI("tror")
--Open a rednet modem on the top, change this to whatever side you have your modem on
rednet.open("top")
--This should be on the computer
parallel.waitForAny(
--This client implementation makes the computer running it act as a "remote control" for another computer. This computer can send input and receive output from
--another computer, but it doesn't actually run anything locally
function()
--Regular events are voided on the other side unless the contain flags marking them as having already been modified. We'll turn the events we send into "remote_"
--events. When they get to the other computer, they will be turned back into regular events with a flag that prevents them from being modified.
local tEventModifiers = {
["terminate"]=tror.createEventSwapper("remote_terminate"),
["key"]=tror.createEventSwapper("remote_key"),
["char"]=tror.createEventSwapper("remote_char"),
["key_up"]=tror.createEventSwapper("remote_key_up"),
["mouse_click"]=tror.createEventSwapper("remote_mouse_click"),
["mouse_up"]=tror.createEventSwapper("remote_mouse_up"),
["mouse_scroll"]=tror.createEventSwapper("remote_mouse_scroll"),
["mouse_drag"]=tror.createEventSwapper("remote_mouse_drag")
}
--This makes it so any event not listed above will be ignored. This makes the modifier list also double as a whitelist
setmetatable(tEventModifiers, {__index = function() return tror.eventVoider end})
--Send events to any computer listening for a sender whose type is "test1"
tror.sendEvents(tror.asPacketDataS(
"test1",--Sender type
"all",--Recipient name
rednet.CHANNEL_BROADCAST--Recipient ID
--No data type or data
),
tEventModifiers, true)
end,
function()
--Receive term commands from any computer sending as the type "test2"
tror.receiveScreen(tror.asPacketData(
"test2"--Sender type
--No sender name, ID, recipient name, ID, data type, or data
))
end
)
Example remotely controlled computer
--Reload the API (or load it if this is the first time)
os.unloadAPI("tror")
os.loadAPI("tror")
--Open a rednet modem on the top, change this to whatever side you have your modem on
rednet.open("top")
--When the new shell starts, we want it to just rerun us
local sProgram = shell.getRunningProgram()
if multishell then
--Just run the regular shell
tror.killShell(tror.runShell, sProgram)
end
--This allows another computer to fire input events on this computer, but prevents this computer from firing any. This will also send term commands back to the
--controlling computer. This effectively makes this a remote control computer where the remote controller has complete control of all input
parallel.waitForAny(
function()
--Pass nil as the function. This means the events will just be queued as they come in, which is what we want
tror.receiveEvents(nil,
tror.asPacketData(
--Receive events from any computer sending as the type "test1"
"test1"--Sender type
--No sender name, ID, recipient name, ID, data type, or data
),
{
["remote_terminate"]=tror.createEventSwapper("terminate", "EventModified"),--Remote events will be converted to regular events, they are allowed to be pulled this way
["remote_key"]=tror.createEventSwapper("key", "EventModified"),
["remote_char"]=tror.createEventSwapper("char", "EventModified"),
["remote_key_up"]=tror.createEventSwapper("key_up", "EventModified"),
["remote_mouse_click"]=tror.createEventSwapper("mouse_click", "EventModified"),
["remote_mouse_up"]=tror.createEventSwapper("mouse_up", "EventModified"),
["remote_mouse_scroll"]=tror.createEventSwapper("mouse_scroll", "EventModified"),
["remote_mouse_drag"]=tror.createEventSwapper("mouse_drag", "EventModified"),
["terminate"]=tror.eventVoider,--Any local events are not allowed to be pulled, they are voided to prevent the user from controlling the computer
["key"]=tror.eventVoider,
["char"]=tror.eventVoider,
["key_up"]=tror.eventVoider,
["mouse_click"]=tror.eventVoider,
["mouse_up"]=tror.eventVoider,
["mouse_scroll"]=tror.eventVoider,
["mouse_drag"]=tror.eventVoider
}
)
end,
function()
--Send term commands to any computer listening for a sender whose type is "test2"
tror.sendScreen(function()
--If we can, run the click safe multishell now. We don't do this earlier, as if this is implemented before the screen share, only the shell tab this
--program is running in will be sent
if term.isColor() then
tror.clickSafeMultishell()
else--If we can't, just run the regular shell
shell.run("shell")
end
end,
tror.asPacketDataS(
"test2",--Sender type
"all",--Recipient name
rednet.CHANNEL_BROADCAST--Recipient ID
--No data type or data
), true
)
end
)
--If the reset functions are still present, they haven't been run yet, so run them now
if os.resetOsTable then
os.resetOsTable()
end
if term.resetTerm then
term.resetTerm()
end
--If we can, run the regular multishell
if term.isColor() then
tror.killShell(tror.runMultishell)
else--If we can't, just run the regular shell
tror.killShell(tror.runShell)
end
This API isn't as polished as I would like it to be, but I don't have any more time to work on it. I may eventually get around to polishing it more, but not right now. If you find any bugs, please provide me with details on how to reproduce the error, and I'll get around to fixing it eventually.