View difference between Paste ID: EFYpUTmy and X5Fysdi4
SHOW: | | - or go back to the newest paste.
1-
local args = { ... }
1+
--I DID NOT MAKE THIS PROGRAM IM MAKING SLIGHT EDITS TO MY NEEDS
2
local tArgs = { ... }
3
4
local connections = {}
5
6
local nshAPI = {
7
	connList = connections
8
}
9-
local bufferDirs = {"/","/LyqydOS/","/usr/apis/","/disk/"}
9+
10
nshAPI.getRemoteID = function()
11-
if not framebuffer then
11+
12-
	for i = 1, #bufferDirs do
12+
13-
		if fs.exists(bufferDirs[i].."framebuffer") and os.loadAPI(bufferDirs[i].."framebuffer") then
13+
		if cInfo.thread == coroutine.running() then
14
			if cNum == "localShell" then
15
				--if we are a client running on the server, return the remote server ID.
16
				if nshAPI.serverNum then
17
					return nshAPI.serverNum
18-
if not framebuffer then
18+
19-
	print("Couldn't find framebuffer API, using fallback")
19+
20
				end
21
			end
22-
local function rawSend(id, msg)
22+
23-
	if term.current then
23+
24-
		return rednet.send(id, msg, "tror")
24+
25
	--client running without local server, return remote server ID.
26
	if nshAPI.serverNum then return nshAPI.serverNum end
27
	return nil
28
end
29
30-
local function rawRecv(id, timeout)
30+
31-
	if type(timeout) == "number" then timeout = os.startTimer(timeout) end
31+
32
	if id then
33
		return rednet.send(id, msg)
34-
		if event[1] == "rednet_message" and (id == nil and true or event[2] == id) and (not term.current and true or event[4] == "tror") then
34+
35
	return nil
36
end
37
38
nshAPI.receive = function(timeout)
39
	if type(timeout) == number then timeout = os.startTimer(timeout) end
40
	while true do
41
		event = {os.pullEvent()}
42
		if event[1] == "rednet_message" and event[2] == nshAPI.getRemoteID() then
43
			return event[3]
44
		elseif event[1] == "timer" and event[2] == timeout then
45
			return nil
46-
		if cInfo and type(cInfo) == "table" and cInfo.thread == coroutine.running() then
46+
47
	end
48
end
49
50
nshAPI.getClientCapabilities = function()
51
	if nshAPI.clientCapabilities then return nshAPI.clientCapabilities end
52
	nshAPI.send("SP:;clientCapabilities")
53
	return nshAPI.receive(1)
54
end
55
56
nshAPI.getRemoteConnections = function()
57
	local remotes = {}
58
	for cNum, cInfo in pairs(nshAPI.connList) do
59
		table.insert(remotes, cNum)
60
		if cInfo.outbound then
61
			table.insert(remotes, cInfo.outbound)
62
		end
63
	end
64
	return remotes
65
end
66-
		return rawSend(id, msg)
66+
67
local packetConversion = {
68
	query = "SQ",
69
	response = "SR",
70
	data = "SP",
71
	close = "SC",
72-
	return rawRecv(nshAPI.getRemoteID(), timeout)
72+
73
	fileSend = "FS",
74
	fileResponse = "FR",
75
	fileHeader = "FH",
76
	fileData = "FD",
77
	fileEnd = "FE",
78
	textWrite = "TW",
79
	textCursorPos = "TC",
80
	textGetCursorPos = "TG",
81
	textGetSize = "TD",
82
	textInfo = "TI",
83
	textClear = "TE",
84
	textClearLine = "TL",
85
	textScroll = "TS",
86
	textBlink = "TB",
87
	textColor = "TF",
88
	textBackground = "TK",
89
	textIsColor = "TA",
90
	event = "EV",
91
	SQ = "query",
92-
nshAPI.packFile = function(path)
92+
93-
	local data = {}
93+
94-
	local count = 0
94+
95-
	local handle = io.open(path, "rb")
95+
96-
	if handle then
96+
97-
		local byte = handle:read()
97+
98-
		repeat
98+
99-
			data[#data + 1] = byte
99+
100-
			count = count + 1
100+
101-
			if count % 1000 == 0 then
101+
102-
				os.queueEvent("yield")
102+
103-
				os.pullEvent("yield")
103+
104
	TD = "textGetSize",
105-
			byte = handle:read()
105+
106-
		until not byte
106+
107-
		handle:close()
107+
108
	TS = "textScroll",
109
	TB = "textBlink",
110
	TF = "textColor",
111-
	local outputTable = {}
111+
112-
	for i = 1, #data, 3 do
112+
113-
		local num1, num2, num3 = data[i], data[i + 1] or 0, data[i + 2] or 0
113+
114-
		table.insert(outputTable, string.char(bit.band(bit.brshift(num1, 2), 63)))
114+
115-
		table.insert(outputTable, string.char(bit.bor(bit.band(bit.blshift(num1, 4), 48), bit.band(bit.brshift(num2, 4), 15))))
115+
116-
		table.insert(outputTable, string.char(bit.bor(bit.band(bit.blshift(num2, 2), 60), bit.band(bit.brshift(num3, 6), 3))))
116+
117-
		table.insert(outputTable, string.char(bit.band(num3, 63)))
117+
118
	for _, side in ipairs(rs.getSides()) do
119-
	--mark non-data (invalid) bytes
119+
120-
	if #data % 3 == 1 then
120+
121-
		outputTable[#outputTable] = "="
121+
122-
		outputTable[#outputTable - 1] = "="
122+
123-
	elseif #data % 3 == 2 then
123+
124-
		outputTable[#outputTable] = "="
124+
125
	return modemFound
126-
	return table.concat(outputTable, "")
126+
127
128
local function send(id, type, message)
129-
nshAPI.unpackAndSaveFile = function(path, data)
129+
	return rednet.send(id, packetConversion[type]..":;"..message)
130-
	local outputTable = {}
130+
131-
	for i=1, #data, 4 do
131+
132-
		local char1, char2, char3, char4 = string.byte(string.sub(data, i, i)), string.byte(string.sub(data, i + 1, i + 1)), string.byte(string.sub(data, i + 2, i + 2)), string.byte(string.sub(data, i + 3, i + 3))
132+
133-
		table.insert(outputTable, bit.band(bit.bor(bit.blshift(char1, 2), bit.brshift(char2, 4)), 255))
133+
134-
		table.insert(outputTable, bit.band(bit.bor(bit.blshift(char2, 4), bit.brshift(char3, 2)), 255))
134+
135-
		table.insert(outputTable, bit.band(bit.bor(bit.blshift(char3, 6), char4), 255))
135+
136
	if time then listenTimeOut = os.startTimer(time) end
137-
	--clean invalid bytes if marked
137+
138-
	if string.sub(data, #data, #data) == "=" then
138+
139-
		table.remove(outputTable)
139+
140-
		if string.sub(data, #data - 1, #data - 1) == "=" then
140+
141-
			table.remove(outputTable)
141+
142
			sender, message = p1, p2
143
			if id == sender and message then
144-
	local handle = io.open(path, "wb")
144+
145-
	if handle then
145+
146-
		for i = 1, #outputTable do
146+
147-
			handle:write(outputTable[i])
147+
148-
			if i % 10 == 0 then
148+
149-
				os.startTimer(0.1)
149+
150-
				os.pullEvent("timer")
150+
151
end
152
153-
		handle:close()
153+
154
	if not pType then return false end
155
	if pType == "textWrite" and value then
156
		term.write(value)
157
	elseif pType == "textClear" then
158
		term.clear()
159
	elseif pType == "textClearLine" then
160
		term.clearLine()
161
	elseif pType == "textGetCursorPos" then
162
		local x, y = term.getCursorPos()
163
		send(conn, "textInfo", math.floor(x)..","..math.floor(y))
164
	elseif pType == "textCursorPos" then
165
		local x, y = string.match(value, "(%d+),(%d+)")
166
		term.setCursorPos(tonumber(x), tonumber(y))
167
	elseif pType == "textBlink" then
168
		if value == "true" then
169
			term.setCursorBlink(true)
170
		else
171
			term.setCursorBlink(false)
172
		end
173
	elseif pType == "textGetSize" then
174
		x, y = term.getSize()
175
		send(conn, "textInfo", x..","..y)
176
	elseif pType == "textScroll" and value then
177
		term.scroll(tonumber(value))
178
	elseif pType == "textIsColor" then
179
		send(conn, "textInfo", tostring(term.isColor()))
180-
	textTable = "TT",
180+
181
		value = tonumber(value)
182
		if (value == 1 or value == 32768) or term.isColor() then
183
			term.setTextColor(value)
184
		end
185
	elseif pType == "textBackground" and value then
186
		value = tonumber(value)
187
		if (value == 1 or value == 32768) or term.isColor() then
188
			term.setBackgroundColor(value)
189
		end
190
	end
191
	return
192
end
193
194
local function textRedirect (id)
195
	local textTable = {}
196
	textTable.id = id
197
	textTable.write = function(text)
198
		return send(textTable.id, "textWrite", text)
199
	end
200
	textTable.clear = function()
201
		return send(textTable.id, "textClear", "nil")
202
	end
203
	textTable.clearLine = function()
204-
	TT = "textTable",
204+
205
	end
206
	textTable.getCursorPos = function()
207
		send(textTable.id, "textGetCursorPos", "nil")
208
		local pType, message = awaitResponse(textTable.id, 2)
209
		if pType and pType == "textInfo" then
210
			local x, y = string.match(message, "(%d+),(%d+)")
211
			return tonumber(x), tonumber(y)
212
		end
213
	end
214
	textTable.setCursorPos = function(x, y)
215
		return send(textTable.id, "textCursorPos", math.floor(x)..","..math.floor(y))
216
	end
217
	textTable.setCursorBlink = function(b)
218
		if b then
219
			return send(textTable.id, "textBlink", "true")
220-
local function send(id, pType, message)
220+
221-
	if pType and message then
221+
222-
		return rawSend(id, packetConversion[pType]..":;"..message)
222+
223
	end
224
	textTable.getSize = function()
225
		send(textTable.id, "textGetSize", "nil")
226
		local pType, message = awaitResponse(textTable.id, 2)
227
		if pType and pType == "textInfo" then
228
			local x, y = string.match(message, "(%d+),(%d+)")
229
			return tonumber(x), tonumber(y)
230
		end
231
	end
232
	textTable.scroll = function(lines)
233
		return send(textTable.id, "textScroll", lines)
234
	end
235
	textTable.isColor = function()
236
		send(textTable.id, "textIsColor", "nil")
237
		local pType, message = awaitResponse(textTable.id, 2)
238
		if pType and pType == "textInfo" then
239
			if message == "true" then
240
				return true
241
			end
242
		end
243
		return false
244
	end
245
	textTable.isColour = textTable.isColor
246
	textTable.setTextColor = function(color)
247
		return send(textTable.id, "textColor", tostring(color))
248
	end
249
	textTable.setTextColour = textTable.setTextColor
250
	textTable.setBackgroundColor = function(color)
251
		return send(textTable.id, "textBackground", tostring(color))
252
	end
253
	textTable.setBackgroundColour = textTable.setBackgroundColor
254
	return textTable
255
end
256
257
local eventFilter = {
258
	key = true,
259-
		local x, y = string.match(value, "(%-?%d+),(%-?%d+)")
259+
260
	mouse_click = true,
261
	mouse_drag = true,
262
	mouse_scroll = true,
263
}
264
265
local function newSession()
266
	local path = "/shell"
267
	if #tArgs >= 2 and shell.resolveProgram(tArgs[2]) then path = shell.resolveProgram(tArgs[2]) end
268
	local sessionThread = coroutine.create(function() shell.run(path) end)
269
	return sessionThread
270
end
271
272
if #tArgs >= 1 and tArgs[1] == "host" then
273
	_G.nsh = nshAPI
274
	_G.nsh = nshAPI
275
	if not openModem() then return end
276
	local connInfo = {}
277
	connInfo.target = term.native
278
	local path = "/shell"
279
	if #tArgs >= 3 and shell.resolveProgram(tArgs[3]) then path = shell.resolveProgram(tArgs[3]) end
280
	connInfo.thread = coroutine.create(function() shell.run(path) end)
281
	connections.localShell = connInfo
282
	term.clear()
283
	term.setCursorPos(1,1)
284-
	elseif pType == "textTable" then
284+
285-
		local linesTable = textutils.unserialize(value)
285+
286-
		for i=1, linesTable.sizeY do
286+
287-
			term.setCursorPos(1,i)
287+
288-
			local lineEnd = false
288+
289-
			local offset = 1
289+
290-
			while not lineEnd do
290+
291-
				local textColorString = string.match(string.sub(linesTable.textColor[i], offset), string.sub(linesTable.textColor[i], offset, offset).."*")
291+
292-
				local backColorString = string.match(string.sub(linesTable.backColor[i], offset), string.sub(linesTable.backColor[i], offset, offset).."*")
292+
293-
				term.setTextColor(2 ^ tonumber(string.sub(textColorString, 1, 1), 16))
293+
294-
				term.setBackgroundColor(2 ^ tonumber(string.sub(backColorString, 1, 1), 16))
294+
295-
				term.write(string.sub(linesTable.text[i], offset, offset + math.min(#textColorString, #backColorString) - 1))
295+
296-
				offset = offset + math.min(#textColorString, #backColorString)
296+
297-
				if offset > linesTable.sizeX then lineEnd = true end
297+
298
							eventTable = textutils.unserialize(message)
299
						else
300-
		term.setCursorPos(linesTable.cursorX, linesTable.cursorY)
300+
301-
		term.setCursorBlink(linesTable.cursorBlink)
301+
302
						end
303
						if not connections[conn].filter or eventTable[1] == connections[conn].filter then
304
							connections[conn].filter = nil
305
							term.redirect(connections[conn].target)
306-
local function textRedirect(id)
306+
							passback = {coroutine.resume(connections[conn].thread, unpack(eventTable))}
307
							if passback[1] and passback[2] then
308
								connections[conn].filter = passback[2]
309
							end
310
							if coroutine.status(connections[conn].thread) == "dead" then
311
								send(conn, "close", "disconnect")
312
								table.remove(connections, conn)
313
							end
314
							term.restore()
315
						end
316
					elseif packetType == "query" then
317
						--reset connection
318
						connections[conn].status = "open"
319
						connections[conn].target = textRedirect(conn)
320
						connections[conn].thread = newSession()
321
						send(conn, "response", "OK")
322-
			local x, y = string.match(message, "(%-?%d+),(%-?%d+)")
322+
						term.redirect(connections[conn].target)
323
						coroutine.resume(connections[conn].thread)
324
						term.restore()
325
					elseif packetType == "close" then
326
						table.remove(connections, conn)
327
						send(conn, "close", "disconnect")
328
						--close connection
329
					else
330
						--we got a packet, have an open connection, but despite it being in the conversion table, don't handle it ourselves. Send it onward.
331
						if not connections[conn].filter or eventTable[1] == connections[conn].filter then
332
							connections[conn].filter = nil
333
							term.redirect(connections[conn].target)
334
							passback = {coroutine.resume(connections[conn].thread, unpack(event))}
335
							if passback[2] then
336
								connections[conn].filter = passback[2]
337
							end
338
							if coroutine.status(connections[conn].thread) == "dead" then
339
								send(conn, "close", "disconnect")
340
								table.remove(connections, conn)
341
							end
342
							term.restore()
343
						end
344
					end
345
				elseif packetType ~= "query" then
346
					--usually, we would send a disconnect here, but this prevents one from hosting nsh and connecting to other computers.  Pass these to all shells as well.
347
					for cNum, cInfo in pairs(connections) do
348
						if not cInfo.filter or event[1] == cInfo.filter then
349
							cInfo.filter = nil
350
							term.redirect(cInfo.target)
351
							passback = {coroutine.resume(cInfo.thread, unpack(event))}
352
							if passback[2] then
353
								cInfo.filter = passback[2]
354
							end
355
							term.restore()
356
						end
357
					end
358
				else
359
					--open new connection
360
					local connInfo = {}
361
					connInfo.status = "open"
362
					connInfo.target = textRedirect(conn)
363
					connInfo.thread = newSession()
364
					send(conn, "response", "OK")
365
					connections[conn] = connInfo
366
					term.redirect(connInfo.target)
367
					coroutine.resume(connInfo.thread)
368
					term.restore()
369-
local function getServerID(server)
369+
370-
	if tonumber(server) then
370+
371-
		return tonumber(server)
371+
372-
	elseif term.current then
372+
373-
		return rednet.lookup("tror", args[1])
373+
					if not cInfo.filter or event[1] == cInfo.filter then
374
						cInfo.filter = nil
375
						term.redirect(cInfo.target)
376
						passback = {coroutine.resume(cInfo.thread, unpack(event))}
377-
local function resumeThread(conn, event)
377+
						if passback[2] then
378-
	local cInfo = connections[conn]
378+
							cInfo.filter = passback[2]
379-
	if connections[conn] and (not connections[conn].filter or event[1] == connections[conn].filter) then
379+
380-
		connections[conn].filter = nil
380+
						term.restore()
381-
		local _oldTerm = term.redirect(connections[conn].target)
381+
382-
		local passback = {coroutine.resume(connections[conn].thread, unpack(event))}
382+
383-
		if passback[1] and passback[2] then
383+
384-
			connections[conn].filter = passback[2]
384+
385
			--user interaction.
386-
		if coroutine.status(connections[conn].thread) == "dead" then
386+
387-
			send(conn, "close", "disconnect")
387+
388-
			connections[conn] = false
388+
389
					if cNum ~= "localShell" then
390-
		if _oldTerm then
390+
391-
			term.redirect(_oldTerm)
391+
392
				end
393-
			term.restore()
393+
394
			end
395-
		if connections[conn] and conn ~= "localShell" and framebuffer and connections[conn].target.changed then
395+
396-
			send(conn, "textTable", textutils.serialize(connections[conn].target.buffer))
396+
			_G.nsh = nil
397-
			connections[conn].target.changed = false
397+
398
		else
399
			--dispatch all other events to all shells
400
			for cNum, cInfo in pairs(connections) do
401
				if not cInfo.filter or event[1] == cInfo.filter then
402
					cInfo.filter = nil
403
					term.redirect(cInfo.target)
404
					passback = {coroutine.resume(cInfo.thread, unpack(event))}
405
					if passback[2] then
406
						cInfo.filter = passback[2]
407
					end
408
					term.restore()
409
				end
410-
local function newSession(conn, x, y, color)
410+
411-
	local session = {}
411+
412-
	local path = "/rom/programs/shell"
412+
413-
	if #args >= 2 and shell.resolveProgram(args[2]) then path = shell.resolveProgram(args[2]) end
413+
414-
	session.thread = coroutine.create(function() shell.run(path) end)
414+
elseif #tArgs == 1 and nsh and nsh.getRemoteID() then
415-
	if framebuffer then
415+
416-
		local target = {}
416+
417-
		local _target = framebuffer.new(x, y, color)
417+
418-
		for k, v in pairs(_target) do
418+
419-
			if type(k) == "string" and type(v) == "function" then
419+
420-
				target[k] = function(...)
420+
421-
					target.changed = true
421+
422-
					return _target[k](...)
422+
423
	end
424
	local fileTransferState = nil
425-
				target[k] = _target[k]
425+
426
	local serverNum = tonumber(tArgs[1])
427
	send(serverNum, "query", "connect")
428-
		session.target = target
428+
429
	if pType ~= "response" then
430-
		session.target = textRedirect(conn)
430+
431
		return
432-
	session.status = "open"
432+
433-
	_oldTerm = term.redirect(session.target)
433+
434-
	coroutine.resume(session.thread)
434+
435-
	if _oldTerm then
435+
436-
		term.redirect(_oldTerm)
436+
437
	local clientID = nsh.getRemoteID()
438-
		term.restore()
438+
	local serverID = tonumber(tArgs[1])
439
	while true do
440-
	if framebuffer then
440+
441-
		send(conn, "textTable", textutils.serialize(session.target.buffer))
441+
442-
		session.target.changed = false
442+
443
				if event[2] == serverID and string.sub(event[3], 1, 2) == "SC" then break end
444-
	return session
444+
445
			end
446
		elseif eventFilter[event[1]] then
447-
if #args >= 1 and args[1] == "host" then
447+
448
		end
449
	end
450-
	if term.current then
450+
451-
		if args[4] then
451+
452-
			rednet.host("tror", args[4])
452+
453-
		elseif os.getComputerLabel() then
453+
454-
			rednet.host("tror", os.getComputerLabel())
454+
455
elseif #tArgs == 1 then --either no server running or we are the local shell on the server.
456-
			print("No label or hostname provided!")
456+
	local serverNum = tonumber(tArgs[1])
457
	if nsh then
458
		local conns = nsh.getRemoteConnections()
459
		for i = 1, #conns do
460
			if conns[i] == serverNum then
461-
	connInfo.target = term.current and term.current() or term.native
461+
462-
	local path = "/rom/programs/shell"
462+
463-
	if #args >= 3 and shell.resolveProgram(args[3]) then path = shell.resolveProgram(args[3]) end
463+
464
		end
465
	end
466
	local fileTransferState = nil
467
	local fileData = nil
468
	if not openModem() then return end
469
	send(serverNum, "query", "connect")
470
	local pType, message = awaitResponse(serverNum, 2)
471
	if pType ~= "response" then
472
		print("Connection failed.")
473-
			if type(event[3]) == "string" and packetConversion[string.sub(event[3], 1, 2)] then
473+
474
	else
475
		if nsh then nshAPI = nsh end
476
		if nshAPI.connList and nshAPI.connList.localShell then nshAPI.connList.localShell.outbound = serverNum end
477
		nshAPI.serverNum = serverNum
478
		nshAPI.clientCapabilities = "-fileTransfer-extensions-"
479
		term.clear()
480
		term.setCursorPos(1,1)
481
	end
482
483
	while true do
484
		event = {os.pullEventRaw()}
485
		if event[1] == "rednet_message" and event[2] == serverNum then
486
			if packetConversion[string.sub(event[3], 1, 2)] then
487-
						resumeThread(conn, eventTable)
487+
488
				message = string.match(event[3], ";(.*)")
489-
						local connType, color, x, y = string.match(message, "(%a+):(%a+);(%d+),(%d+)")
489+
490-
						if connType == "connect" or (connType == "resume" and (not framebuffer)) then
490+
491-
							--reset connection
491+
492-
							send(conn, "response", "OK")
492+
493-
							connections[conn] = newSession(conn, tonumber(x), tonumber(y), color == "true")
493+
494-
						elseif connType == "resume" and connections[conn] and tonumber(x) == connections[conn].target.buffer.sizeX and tonumber(y) == connections[conn].target.buffer.sizeY then
494+
495-
							--restore connection
495+
496-
							send(conn, "response", "OK")
496+
497-
							send(conn, "textTable", textutils.serialize(connections[conn].target.buffer))
497+
					if fs.exists(message) then
498
						send(serverNum, "fileHeader", message)
499
						local file = io.open(message, "r")
500-
						connections[conn] = nil
500+
						if file then
501
							send(serverNum, "fileData", file:read("*a"))
502
							file:close()
503
						end
504
					else
505-
						resumeThread(conn, event)
505+
506
					end
507
					send(serverNum, "fileEnd", "end")
508
				elseif packetType == "fileSend" then
509
					--receive a file from the server, but don't overwrite existing files.
510-
						resumeThread(cNum, event)
510+
					if not fs.exists(message) then
511
						fileTransferState = "receive_wait:"..message
512
						send(serverNum, "fileResponse", "ok")
513
						fileData = ""
514
					else
515-
					local color, x, y = string.match(message, "connect:(%a+);(%d+),(%d+)")
515+
516-
					local connInfo = newSession(conn, tonumber(x), tonumber(y), color == "true")
516+
517
				elseif packetType == "fileHeader" then
518
					if message == "fileNotFound" then
519
						fileTransferState = nil
520
					end
521
				elseif packetType == "fileData" then
522-
					resumeThread(cNum, event)
522+
523
						fileData = fileData..message
524
					end
525
				elseif packetType == "fileEnd" then
526
					if fileTransferState and string.match(fileTransferState, "(.-):") == "receive_wait" then
527
						local file = io.open(string.match(fileTransferState, ":(.*)"), "w")
528
						if file then
529
							file:write(fileData)
530
							file:close()
531
						end
532
						fileTransferState = nil
533
					end
534
				elseif packetType == "close" then
535
					if term.isColor() then
536
						term.setBackgroundColor(colors.black)
537
						term.setTextColor(colors.white)
538
					end
539-
				resumeThread(cNum, event)
539+
540
					term.setCursorPos(1, 1)
541
					print("Connection closed by server.")
542
					nshAPI.serverNum = nil
543
					if nshAPI.connList and nshAPI.connList.localShell then nshAPI.connList.localShell.outbound = nil end
544-
elseif #args <= 2 and nsh and nsh.getRemoteID() then
544+
545
				end
546
			end
547
		elseif event[1] == "mouse_click" or event[1] == "mouse_drag" or event[1] == "mouse_scroll" or event[1] == "key" or event[1] == "char" then
548
			--pack up event
549
			send(serverNum, "event", textutils.serialize(event))
550
		elseif event[1] == "terminate" then
551
			nshAPI.serverNum = nil
552
			if nshAPI.localShell then nshAPI.localShell.outbound = nil end
553
			return
554
		end
555
	end
556-
	local serverNum = getServerID(args[1])
556+
557-
	if not serverNum then
557+
	print("Usage: nsh <serverID>")
558-
		print("Server Not Found")
558+
	print("       nsh host [remote [local]]")
559
end