· 7 years ago · Jan 28, 2019, 02:48 AM
1{
2 [ "README.md" ] = "# Opus OS for computercraft\
3\
4## Features\
5* Multitasking OS - run programs in separate tabs\
6* Telnet (wireless remote shell)\
7* VNC (wireless screen sharing)\
8* UI API\
9* Turtle API (includes true pathfinding based on the ASTAR algorithm)\
10* Remote file system access (you can access the file system of any computer in wireless range)\
11* File manager\
12* Lua REPL with GUI\
13* Run scripts on single or groups of computers (GUI)\
14* Turtle follow (with GPS) and turtle come to you (without GPS)\
15\
16## Install\
17```\
18pastebin run uzghlbnc\
19reboot\
20```",
21 [ "startup.lua" ] = "local colors = _G.colors\
22local os = _G.os\
23local settings = _G.settings\
24local term = _G.term\
25\
26local bootOptions = {\
27 { prompt = os.version() },\
28 { prompt = 'Opus' , args = { '/sys/os/opus/sys/boot/opus.boot' } },\
29 { prompt = 'Opus Shell' , args = { '/sys/os/opus/sys/boot/opus.boot', 'sys/apps/shell' } },\
30}\
31local bootOption = 2\
32if settings then\
33 settings.load('.settings')\
34 bootOption = tonumber(settings.get('opus.boot_option') or 2) or 2\
35end\
36\
37local function startupMenu()\
38 while true do\
39 term.clear()\
40 term.setCursorPos(1, 1)\
41 print('Select startup mode')\
42 print()\
43 for k,option in pairs(bootOptions) do\
44 print(k .. ' : ' .. option.prompt)\
45 end\
46 print('')\
47 term.write('> ')\
48 local ch = tonumber(_G.read())\
49 if ch and bootOptions[ch] then\
50 return ch\
51 end\
52 end\
53end\
54\
55local function splash()\
56 local w, h = term.current().getSize()\
57\
58 term.setTextColor(colors.white)\
59 if not term.isColor() then\
60 local str = 'Opus OS'\
61 term.setCursorPos((w - #str) / 2, h / 2)\
62 term.write(str)\
63 else\
64 term.setBackgroundColor(colors.black)\
65 term.clear()\
66 local opus = {\
67 'fffff00',\
68 'ffff07000',\
69 'ff00770b00 4444',\
70 'ff077777444444444',\
71 'f07777744444444444',\
72 'f0000777444444444',\
73 '070000111744444',\
74 '777770000',\
75 '7777000000',\
76 '70700000000',\
77 '077000000000',\
78 }\
79 for k,line in ipairs(opus) do\
80 term.setCursorPos((w - 18) / 2, k + (h - #opus) / 2)\
81 term.blit(string.rep(' ', #line), string.rep('a', #line), line)\
82 end\
83 end\
84\
85 local str = 'Press any key for menu'\
86 term.setCursorPos((w - #str) / 2, h)\
87 term.write(str)\
88end\
89\
90term.clear()\
91splash()\
92\
93local timerId = os.startTimer(1.5)\
94while true do\
95 local e, id = os.pullEvent()\
96 if e == 'timer' and id == timerId then\
97 break\
98 end\
99 if e == 'char' then\
100 bootOption = startupMenu()\
101 if settings then\
102 settings.set('opus.boot_option', bootOption)\
103 settings.save('.settings')\
104 end\
105 break\
106 end\
107end\
108\
109term.clear()\
110term.setCursorPos(1, 1)\
111if bootOptions[bootOption].args then\
112 os.run(_G.getfenv(1), table.unpack(bootOptions[bootOption].args))\
113else\
114 print(bootOptions[bootOption].prompt)\
115end",
116 usr = {
117 apps = {},
118 config = {
119 multishell = "{\
120 standard = {\
121 tabBarBackgroundColor = 128,\
122 focusBackgroundColor = 128,\
123 errorColor = 32768,\
124 focusTextColor = 1,\
125 tabBarTextColor = 256,\
126 backgroundColor = 128,\
127 textColor = 256,\
128 },\
129 color = {\
130 tabBarBackgroundColor = 128,\
131 focusBackgroundColor = 128,\
132 errorColor = 16384,\
133 focusTextColor = 1,\
134 tabBarTextColor = 256,\
135 backgroundColor = 128,\
136 textColor = 256,\
137 },\
138}",
139 shellprompt = "{\
140 standard = {\
141 promptTextColor = 128,\
142 commandTextColor = 256,\
143 directoryTextColor = 128,\
144 promptBackgroundColor = 32768,\
145 directoryColor = 128,\
146 directoryBackgroundColor = 32768,\
147 textColor = 1,\
148 },\
149 displayDirectory = true,\
150 color = {\
151 promptTextColor = 2048,\
152 commandTextColor = 16,\
153 directoryTextColor = 2,\
154 promptBackgroundColor = 32768,\
155 directoryColor = 8192,\
156 directoryBackgroundColor = 32768,\
157 textColor = 1,\
158 },\
159}",
160 network = "{}",
161 gps = "{}",
162 packages = "{\
163 [ 'core' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/core/.package',\
164 [ 'builder' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/builder/.package',\
165 [ 'farms' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/farms/.package',\
166-- [ 'forestry' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/forestry/.package',\
167-- [ 'glasses' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/glasses/.package',\
168 [ 'milo' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/milo/.package',\
169 [ 'miners' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/miners/.package',\
170-- [ 'neural' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/neural/.package',\
171-- [ 'pickup' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/pickup/.package',\
172-- [ 'recipeBook' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/recipeBook/.package',\
173 [ 'storage' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/storage/.package',\
174}",
175 os = "{}",
176 Overview = "{\
177 Recent = {\
178 \"01c933b2a36ad8ed2d54089cb2903039046c1216\",\
179 \"53ebc572b4a44802ba114729f07bdaaf5409a9d7\",\
180 \"2a4d562b1d9a9c90bdede6fac8ce4f7402462b86\",\
181 \"bc0792d8dc81e8aa30b987246a5ce97c40cd6833\",\
182 \"bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d\",\
183 \"b0832074630eb731d7fbe8074de48a90cd9bb220\",\
184 },\
185 currentCategory = \"Apps\",\
186}",
187 shell = "{\
188 path = \"/sys/os/opus/usr/apps:sys/os/opus/sys/apps:.:/rom/programs:/rom/programs/advanced:/rom/programs/rednet:/rom/programs/fun:/rom/programs/fun/advanced:/rom/programs/http\",\
189 lua_path = \"sys/os/opus/sys/apis:/sys/os/opus/usr/apis\",\
190 aliases = {\
191 rm = \"delete\",\
192 dir = \"list\",\
193 clr = \"clear\",\
194 sh = \"shell\",\
195 ls = \"list\",\
196 background = \"bg\",\
197 foreground = \"fg\",\
198 cp = \"copy\",\
199 mv = \"move\",\
200 rs = \"redstone\",\
201 },\
202}",
203 },
204 autorun = {},
205 [ ".lua_history" ] = "print(\"hello\")\
206print(\"hello\"\
207print(\"hello\")",
208 },
209 [ "LICENSE.md" ] = "MIT License\
210\
211Copyright (c) 2016-2017 kepler155c\
212\
213Permission is hereby granted, free of charge, to any person obtaining a copy\
214of this software and associated documentation files (the \"Software\"), to deal\
215in the Software without restriction, including without limitation the rights\
216to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\
217copies of the Software, and to permit persons to whom the Software is\
218furnished to do so, subject to the following conditions:\
219\
220The above copyright notice and this permission notice shall be included in all\
221copies or substantial portions of the Software.\
222\
223THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\
224IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\
225FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\
226AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\
227LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\
228OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\
229SOFTWARE.",
230 [ ".gitignore" ] = "/ignore",
231 sys = {
232 network = {
233 [ "redserver.lua" ] = "local Event = require('event')\
234local Util = require('util')\
235\
236local fs = _G.fs\
237local modem = _G.device.wireless_modem\
238local os = _G.os\
239\
240local computerId = os.getComputerID()\
241\
242modem.open(80)\
243\
244-- https://github.com/golgote/neturl/blob/master/lib/net/url.lua\
245local function parseQuery(str)\
246 local sep = '&'\
247\
248 local values = {}\
249 for key,val in str:gmatch(string.format('([^%q=]+)(=*[^%q=]*)', sep, sep)) do\
250 --local key = decode(key)\
251 local keys = {}\
252 key = key:gsub('%[([^%]]*)%]', function(v)\
253 -- extract keys between balanced brackets\
254 if string.find(v, \"^-?%d+$\") then\
255 v = tonumber(v)\
256 --else\
257 --v = decode(v)\
258 end\
259 table.insert(keys, v)\
260 return \"=\"\
261 end)\
262 key = key:gsub('=+.*$', \"\")\
263 key = key:gsub('%s', \"_\") -- remove spaces in parameter name\
264 val = val:gsub('^=+', \"\")\
265\
266 if not values[key] then\
267 values[key] = {}\
268 end\
269 if #keys > 0 and type(values[key]) ~= 'table' then\
270 values[key] = {}\
271 elseif #keys == 0 and type(values[key]) == 'table' then\
272 values[key] = val --decode(val)\
273 end\
274\
275 local t = values[key]\
276 for i,k in ipairs(keys) do\
277 if type(t) ~= 'table' then\
278 t = {}\
279 end\
280 if k == \"\" then\
281 k = #t+1\
282 end\
283 if not t[k] then\
284 t[k] = {}\
285 end\
286 if i == #keys then\
287 t[k] = val --decode(val)\
288 end\
289 t = t[k]\
290 end\
291 end\
292 return values\
293end\
294\
295local function getListing(path, recursive)\
296 local list = { }\
297 local function listing(p)\
298 for _, f in pairs(fs.listEx(p)) do\
299 local abs = fs.combine(p, f.name)\
300 table.insert(list, {\
301 isDir = f.isDir,\
302 path = string.sub(abs, #path + 1),\
303 size = f.size,\
304 })\
305 if recursive and f.isDir then\
306 listing(abs)\
307 end\
308 end\
309 end\
310 listing(path)\
311 return list\
312end\
313\
314Event.on('modem_message', function(_, _, dport, dhost, request)\
315 if dport == 80 and dhost == computerId and type(request) == 'table' then\
316 if request.method == 'GET' then\
317 local query\
318 if not request.path or type(request.path) ~= 'string' then\
319 return\
320 end\
321 local path = request.path:gsub('%?(.*)', function(v)\
322 query = parseQuery(v)\
323 return ''\
324 end)\
325 if fs.isDir(path) then\
326 -- TODO: more validation\
327 modem.transmit(request.replyPort, request.replyAddress, {\
328 statusCode = 200,\
329 contentType = 'table/directory',\
330 data = getListing(path, query and query.recursive == 'true'),\
331 })\
332 elseif fs.exists(path) then\
333 modem.transmit(request.replyPort, request.replyAddress, {\
334 statusCode = 200,\
335 contentType = 'table/file',\
336 data = Util.readFile(path),\
337 })\
338 else\
339 modem.transmit(request.replyPort, request.replyAddress, {\
340 statusCode = 404,\
341 })\
342 end\
343 end\
344 end\
345end)",
346 [ "peripheral.lua" ] = "--[[\
347 Allow sharing of local peripherals.\
348]]--\
349\
350local Event = require('event')\
351local Peripheral = require('peripheral')\
352local Socket = require('socket')\
353\
354Event.addRoutine(function()\
355 print('peripheral: listening on port 189')\
356 while true do\
357 local socket = Socket.server(189)\
358\
359 print('peripheral: connection from ' .. socket.dhost)\
360\
361 Event.addRoutine(function()\
362 local uri = socket:read(2)\
363 if uri then\
364 local peripheral = Peripheral.lookup(uri)\
365\
366-- only 1 proxy of this device can happen at one time\
367-- need to prevent multiple shares\
368 if not peripheral then\
369 print('peripheral: invalid peripheral ' .. uri)\
370 socket:write('Invalid peripheral: ' .. uri)\
371 else\
372 print('peripheral: proxing ' .. uri)\
373 local proxy = {\
374 methods = { }\
375 }\
376\
377 if peripheral.blit then\
378 --peripheral = Util.shallowCopy(peripheral)\
379 peripheral.fastBlit = function(data)\
380 for _,v in ipairs(data) do\
381 peripheral[v.fn](unpack(v.args))\
382 end\
383 end\
384 end\
385\
386 for k,v in pairs(peripheral) do\
387 if type(v) == 'function' then\
388 table.insert(proxy.methods, k)\
389 else\
390 proxy[k] = v\
391 end\
392 end\
393\
394 socket:write(proxy)\
395\
396 if proxy.type == 'monitor' then\
397 peripheral.eventChannel = function(...)\
398 socket:write({\
399 fn = 'event',\
400 data = { ... }\
401 })\
402 end\
403 end\
404\
405 while true do\
406 local data = socket:read()\
407 if not data then\
408 print('peripheral: lost connection from ' .. socket.dhost)\
409 break\
410 end\
411 if not _G.device[peripheral.name] then\
412 print('periperal: detached')\
413 socket:close()\
414 break\
415 end\
416 if peripheral[data.fn] then\
417 -- need to trigger an error on the other end\
418 -- local s, m = pcall()\
419 socket:write({ peripheral[data.fn](table.unpack(data.args)) })\
420 else\
421 socket:write({ false, \"Invalid function: \" .. data.fn })\
422 end\
423 end\
424\
425 peripheral.eventChannel = nil\
426 peripheral.fastBlit = nil\
427 end\
428 end\
429 end)\
430 end\
431end)",
432 [ "trust.lua" ] = "local Crypto = require('crypto')\
433local Event = require('event')\
434local Security = require('security')\
435local Socket = require('socket')\
436local Util = require('util')\
437\
438Event.addRoutine(function()\
439\
440 print('trust: listening on port 19')\
441 while true do\
442 local socket = Socket.server(19)\
443\
444 print('trust: connection from ' .. socket.dhost)\
445\
446 local data = socket:read(2)\
447 if data then\
448 local password = Security.getPassword()\
449 if not password then\
450 socket:write({ msg = 'No password has been set' })\
451 else\
452 data = Crypto.decrypt(data, password)\
453 if data and data.pk and data.dh == socket.dhost then\
454 local trustList = Util.readTable('/sys/os/opus/usr/.known_hosts') or { }\
455 trustList[data.dh] = data.pk\
456 Util.writeTable('/sys/os/opus/usr/.known_hosts', trustList)\
457\
458 socket:write({ success = true, msg = 'Trust accepted' })\
459 else\
460 socket:write({ msg = 'Invalid password' })\
461 end\
462 end\
463 end\
464 socket:close()\
465 end\
466end)",
467 [ "vnc.lua" ] = "local Event = require('event')\
468local Socket = require('socket')\
469local Util = require('util')\
470\
471local os = _G.os\
472local terminal = _G.device.terminal\
473\
474local function vncHost(socket)\
475 local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write',\
476 'setTextColor', 'setTextColour', 'setBackgroundColor',\
477 'setBackgroundColour', 'scroll', 'setCursorBlink', }\
478\
479 local oldTerm = Util.shallowCopy(terminal)\
480\
481 for _,k in pairs(methods) do\
482 terminal[k] = function(...)\
483 if not socket.queue then\
484 socket.queue = { }\
485 Event.onTimeout(0, function()\
486 socket:write(socket.queue)\
487 socket.queue = nil\
488 end)\
489 end\
490 table.insert(socket.queue, {\
491 f = k,\
492 args = { ... },\
493 })\
494 oldTerm[k](...)\
495 end\
496 end\
497\
498 while true do\
499 local data = socket:read()\
500 if not data then\
501 print('vnc: closing connection to ' .. socket.dhost)\
502 break\
503 end\
504\
505 if data.type == 'shellRemote' then\
506 os.queueEvent(table.unpack(data.event))\
507 elseif data.type == 'termInfo' then\
508 terminal.getSize = function()\
509 return data.width, data.height\
510 end\
511 os.queueEvent('term_resize')\
512 end\
513 end\
514\
515 for k,v in pairs(oldTerm) do\
516 terminal[k] = v\
517 end\
518 os.queueEvent('term_resize')\
519end\
520\
521Event.addRoutine(function()\
522\
523 print('vnc: listening on port 5900')\
524\
525 while true do\
526 local socket = Socket.server(5900)\
527\
528 print('vnc: connection from ' .. socket.dhost)\
529\
530 -- no new process - only 1 connection allowed\
531 -- due to term size issues\
532 vncHost(socket)\
533 socket:close()\
534 end\
535end)",
536 [ "snmp.lua" ] = "local Event = require('event')\
537local GPS = require('gps')\
538local Socket = require('socket')\
539local Util = require('util')\
540\
541local device = _G.device\
542local kernel = _G.kernel\
543local network = _G.network\
544local os = _G.os\
545local turtle = _G.turtle\
546\
547-- move this into gps api\
548local gpsRequested\
549local gpsLastPoint\
550local gpsLastRequestTime\
551\
552local function snmpConnection(socket)\
553 while true do\
554 local msg = socket:read()\
555 if not msg then\
556 break\
557 end\
558\
559 if msg.type == 'reboot' then\
560 os.reboot()\
561\
562 elseif msg.type == 'shutdown' then\
563 os.shutdown()\
564\
565 elseif msg.type == 'ping' then\
566 socket:write('pong')\
567\
568 elseif msg.type == 'script' then\
569 local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G })\
570 local fn, err = load(msg.args, 'script', nil, env)\
571 if fn then\
572 kernel.run({\
573 fn = fn,\
574 env = env,\
575 title = 'script',\
576 })\
577 else\
578 _G.printError(err)\
579 end\
580\
581 elseif msg.type == 'scriptEx' then\
582 local s, m = pcall(function()\
583 local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G })\
584 local fn, m = load(msg.args, 'script', nil, env)\
585 if not fn then\
586 error(m)\
587 end\
588 return { fn() }\
589 end)\
590 if s then\
591 socket:write(m)\
592 else\
593 socket:write({ s, m })\
594 end\
595\
596 elseif msg.type == 'gps' then\
597 if gpsRequested then\
598 repeat\
599 os.sleep(0)\
600 until not gpsRequested\
601 end\
602\
603 if gpsLastPoint and os.clock() - gpsLastRequestTime < .5 then\
604 socket:write(gpsLastPoint)\
605 else\
606\
607 gpsRequested = true\
608 local pt = GPS.getPoint(2)\
609 if pt then\
610 socket:write(pt)\
611 else\
612 print('snmp: Unable to get GPS point')\
613 end\
614 gpsRequested = false\
615 gpsLastPoint = pt\
616 if pt then\
617 gpsLastRequestTime = os.clock()\
618 end\
619 end\
620\
621 elseif msg.type == 'info' then\
622 local info = {\
623 id = os.getComputerID(),\
624 label = os.getComputerLabel(),\
625 uptime = math.floor(os.clock()),\
626 }\
627 if turtle then\
628 info.fuel = turtle.getFuelLevel()\
629 info.status = turtle.getStatus()\
630 end\
631 socket:write(info)\
632 end\
633 end\
634end\
635\
636Event.addRoutine(function()\
637 print('snmp: listening on port 161')\
638\
639 while true do\
640 local socket = Socket.server(161)\
641\
642 Event.addRoutine(function()\
643 print('snmp: connection from ' .. socket.dhost)\
644 snmpConnection(socket)\
645 print('snmp: closing connection to ' .. socket.dhost)\
646 end)\
647 end\
648end)\
649\
650device.wireless_modem.open(999)\
651print('discovery: listening on port 999')\
652\
653Event.on('modem_message', function(_, _, sport, id, info, distance)\
654 if sport == 999 and tonumber(id) and type(info) == 'table' then\
655 if not network[id] then\
656 network[id] = { }\
657 end\
658 Util.merge(network[id], info)\
659 network[id].distance = distance\
660 network[id].timestamp = os.clock()\
661\
662 if not network[id].active then\
663 network[id].active = true\
664 os.queueEvent('network_attach', network[id])\
665 end\
666 end\
667end)\
668\
669local info = {\
670 id = os.getComputerID()\
671}\
672local infoTimer = os.clock()\
673\
674local function sendInfo()\
675 if os.clock() - infoTimer >= 1 then -- don't flood\
676 infoTimer = os.clock()\
677 info.label = os.getComputerLabel()\
678 info.uptime = math.floor(os.clock())\
679 if turtle then\
680 info.fuel = turtle.getFuelLevel()\
681 info.status = turtle.getStatus()\
682 info.point = turtle.point\
683 info.inventory = turtle.getInventory()\
684 info.slotIndex = turtle.getSelectedSlot()\
685 end\
686 if device.neuralInterface then\
687 info.status = device.neuralInterface.status\
688 pcall(function()\
689 if not info.status and device.neuralInterface.getMetaOwner then\
690 info.status = 'health: ' ..\
691 math.floor(device.neuralInterface.getMetaOwner().health /\
692 device.neuralInterface.getMetaOwner().maxHealth * 100)\
693 end\
694 end)\
695 end\
696 device.wireless_modem.transmit(999, os.getComputerID(), info)\
697 end\
698end\
699\
700-- every 10 seconds, send out this computer's info\
701Event.onInterval(10, function()\
702 sendInfo()\
703 for _,c in pairs(_G.network) do\
704 local elapsed = os.clock()-c.timestamp\
705 if c.active and elapsed > 15 then\
706 c.active = false\
707 os.queueEvent('network_detach', c)\
708 end\
709 end\
710end)\
711\
712Event.on('turtle_response', function()\
713 if turtle.getStatus() ~= info.status or\
714 turtle.fuel ~= info.fuel then\
715 sendInfo()\
716 end\
717end)",
718 [ "proxy.lua" ] = "local Event = require('event')\
719local Socket = require('socket')\
720\
721Event.addRoutine(function()\
722 while true do\
723 print('proxy: listening on port 188')\
724 local socket = Socket.server(188)\
725\
726 print('proxy: connection from ' .. socket.dhost)\
727\
728 Event.addRoutine(function()\
729 local api = socket:read(2)\
730 if api then\
731 local proxy = _G[api]\
732\
733 if not proxy then\
734 print('proxy: invalid API')\
735 return\
736 end\
737\
738 local methods = { }\
739 for k,v in pairs(proxy) do\
740 if type(v) == 'function' then\
741 table.insert(methods, k)\
742 end\
743 end\
744 socket:write(methods)\
745\
746 while true do\
747 local data = socket:read()\
748 if not data then\
749 print('proxy: lost connection from ' .. socket.dhost)\
750 break\
751 end\
752 socket:write({ proxy[data.fn](table.unpack(data.args)) })\
753 end\
754 end\
755 end)\
756 end\
757end)",
758 [ "telnet.lua" ] = "local Event = require('event')\
759local Socket = require('socket')\
760local Util = require('util')\
761\
762local kernel = _G.kernel\
763local term = _G.term\
764local window = _G.window\
765\
766local function telnetHost(socket)\
767 local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit',\
768 'setTextColor', 'setTextColour', 'setBackgroundColor',\
769 'setBackgroundColour', 'scroll', 'setCursorBlink', }\
770\
771 local termInfo = socket:read(5)\
772 if not termInfo then\
773 _G.printError('read failed')\
774 return\
775 end\
776\
777 local win = window.create(_G.device.terminal, 1, 1, termInfo.width, termInfo.height, false)\
778 win.setCursorPos(table.unpack(termInfo.pos))\
779\
780 for _,k in pairs(methods) do\
781 local fn = win[k]\
782 win[k] = function(...)\
783\
784 if not socket.queue then\
785 socket.queue = { }\
786 Event.onTimeout(0, function()\
787 socket:write(socket.queue)\
788 socket.queue = nil\
789 end)\
790 end\
791\
792 table.insert(socket.queue, {\
793 f = k,\
794 args = { ... },\
795 })\
796 fn(...)\
797 end\
798 end\
799\
800 local shellThread = kernel.run({\
801 terminal = win,\
802 window = win,\
803 title = 'Telnet client',\
804 hidden = true,\
805 co = coroutine.create(function()\
806 Util.run(_ENV, 'sys/os/opus/sys/apps/shell', table.unpack(termInfo.program))\
807 if socket.queue then\
808 socket:write(socket.queue)\
809 end\
810 socket:close()\
811 end)\
812 })\
813\
814 Event.addRoutine(function()\
815 while true do\
816 local data = socket:read()\
817 if not data then\
818 shellThread:resume('terminate')\
819 break\
820 end\
821 local previousTerm = term.current()\
822 shellThread:resume(table.unpack(data))\
823 term.redirect(previousTerm)\
824 end\
825 end)\
826end\
827\
828Event.addRoutine(function()\
829 print('telnet: listening on port 23')\
830 while true do\
831 local socket = Socket.server(23)\
832\
833 print('telnet: connection from ' .. socket.dhost)\
834\
835 Event.addRoutine(function()\
836 telnetHost(socket)\
837 end)\
838 end\
839end)",
840 [ "transport.lua" ] = "--[[\
841 Low level socket protocol implementation.\
842\
843 * sequencing\
844 * background read buffering\
845]]--\
846\
847local Event = require('event')\
848\
849local os = _G.os\
850\
851local computerId = os.getComputerID()\
852local transport = {\
853 timers = { },\
854 sockets = { },\
855 UID = 0,\
856}\
857_G.transport = transport\
858\
859function transport.open(socket)\
860 transport.UID = transport.UID + 1\
861\
862 transport.sockets[socket.sport] = socket\
863 socket.activityTimer = os.clock()\
864 socket.uid = transport.UID\
865end\
866\
867function transport.read(socket)\
868 local data = table.remove(socket.messages, 1)\
869 if data then\
870 return unpack(data)\
871 end\
872end\
873\
874function transport.write(socket, data)\
875 --_debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))\
876 socket.transmit(socket.dport, socket.dhost, data)\
877\
878 --local timerId = os.startTimer(3)\
879\
880 --transport.timers[timerId] = socket\
881 --socket.timers[socket.wseq] = timerId\
882\
883 socket.wseq = socket.wseq + 1\
884end\
885\
886function transport.ping(socket)\
887 --_debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))\
888 if os.clock() - socket.activityTimer > 10 then\
889 socket.activityTimer = os.clock()\
890 socket.transmit(socket.dport, socket.dhost, {\
891 type = 'PING',\
892 seq = -1,\
893 })\
894\
895 local timerId = os.startTimer(5)\
896 transport.timers[timerId] = socket\
897 socket.timers[-1] = timerId\
898 end\
899end\
900\
901function transport.close(socket)\
902 transport.sockets[socket.sport] = nil\
903end\
904\
905Event.on('timer', function(_, timerId)\
906 local socket = transport.timers[timerId]\
907\
908 if socket and socket.connected then\
909 print('transport timeout - closing socket ' .. socket.sport)\
910 socket:close()\
911 transport.timers[timerId] = nil\
912 end\
913end)\
914\
915Event.on('modem_message', function(_, _, dport, dhost, msg, distance)\
916 if dhost == computerId and type(msg) == 'table' then\
917 local socket = transport.sockets[dport]\
918 if socket and socket.connected then\
919\
920 --if msg.type then _debug('<< ' .. Util.tostring(msg)) end\
921 if socket.co and coroutine.status(socket.co) == 'dead' then\
922 _G._debug('socket coroutine dead')\
923 socket:close()\
924\
925 elseif msg.type == 'DISC' then\
926 -- received disconnect from other end\
927 if socket.connected then\
928 os.queueEvent('transport_' .. socket.uid)\
929 end\
930 socket.connected = false\
931 socket:close()\
932\
933 elseif msg.type == 'ACK' then\
934 local ackTimerId = socket.timers[msg.seq]\
935 if ackTimerId then\
936 os.cancelTimer(ackTimerId)\
937 socket.timers[msg.seq] = nil\
938 socket.activityTimer = os.clock()\
939 transport.timers[ackTimerId] = nil\
940 end\
941\
942 elseif msg.type == 'PING' then\
943 socket.activityTimer = os.clock()\
944 socket.transmit(socket.dport, socket.dhost, {\
945 type = 'ACK',\
946 seq = msg.seq,\
947 })\
948\
949 elseif msg.type == 'DATA' and msg.data then\
950 socket.activityTimer = os.clock()\
951 if msg.seq ~= socket.rseq then\
952 print('transport seq error - closing socket ' .. socket.sport)\
953 _debug(msg.data)\
954 _debug('current ' .. socket.rseq)\
955 _debug('expected ' .. msg.seq)\
956-- socket:close()\
957-- os.queueEvent('transport_' .. socket.uid)\
958 else\
959 socket.rseq = socket.rseq + 1\
960 table.insert(socket.messages, { msg.data, distance })\
961\
962 -- use resume instead ??\
963 if not socket.messages[2] then -- table size is 1\
964 os.queueEvent('transport_' .. socket.uid)\
965 end\
966\
967 --_debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq }))\
968 --socket.transmit(socket.dport, socket.dhost, {\
969 -- type = 'ACK',\
970 -- seq = msg.seq,\
971 --})\
972 end\
973 end\
974 end\
975 end\
976end)",
977 [ "samba.lua" ] = "local Event = require('event')\
978local Socket = require('socket')\
979\
980local fs = _G.fs\
981\
982local fileUid = 0\
983local fileHandles = { }\
984\
985local function remoteOpen(fn, fl)\
986 local fh = fs.open(fn, fl)\
987 if fh then\
988 local methods = { 'close', 'write', 'writeLine', 'flush', 'read', 'readLine', 'readAll', }\
989 fileUid = fileUid + 1\
990 fileHandles[fileUid] = fh\
991\
992 local vfh = {\
993 methods = { },\
994 fileUid = fileUid,\
995 }\
996\
997 for _,m in ipairs(methods) do\
998 if fh[m] then\
999 table.insert(vfh.methods, m)\
1000 end\
1001 end\
1002 return vfh\
1003 end\
1004end\
1005\
1006local function remoteFileOperation(fileId, op, ...)\
1007 local fh = fileHandles[fileId]\
1008 if fh then\
1009 return fh[op](...)\
1010 end\
1011end\
1012\
1013local function sambaConnection(socket)\
1014 while true do\
1015 local msg = socket:read()\
1016 if not msg then\
1017 break\
1018 end\
1019 local fn = fs[msg.fn]\
1020 if msg.fn == 'open' then\
1021 fn = remoteOpen\
1022 elseif msg.fn == 'fileOp' then\
1023 fn = remoteFileOperation\
1024 end\
1025 local ret\
1026 local s, m = pcall(function()\
1027 ret = fn(unpack(msg.args))\
1028 end)\
1029 if not s and m then\
1030 _G.printError('samba: ' .. m)\
1031 end\
1032 socket:write({ response = ret })\
1033 end\
1034\
1035 print('samba: Connection closed')\
1036end\
1037\
1038Event.addRoutine(function()\
1039 print('samba: listening on port 139')\
1040\
1041 while true do\
1042 local socket = Socket.server(139)\
1043\
1044 Event.addRoutine(function()\
1045 print('samba: connection from ' .. socket.dhost)\
1046 sambaConnection(socket)\
1047 print('samba: closing connection to ' .. socket.dhost)\
1048 end)\
1049 end\
1050end)\
1051\
1052Event.on('network_attach', function(_, computer)\
1053 fs.mount(fs.combine('network', computer.label), 'netfs', computer.id)\
1054end)\
1055\
1056Event.on('network_detach', function(_, computer)\
1057 print('samba: detaching ' .. computer.label)\
1058 fs.unmount(fs.combine('network', computer.label))\
1059end)",
1060 },
1061 apps = {
1062 [ "Help.lua" ] = "_G.requireInjector(_ENV)\
1063\
1064local UI = require('ui')\
1065local Util = require('util')\
1066\
1067local colors = _G.colors\
1068local help = _G.help\
1069\
1070UI:configure('Help', ...)\
1071\
1072local topics = { }\
1073for _,topic in pairs(help.topics()) do\
1074 if help.lookup(topic) then\
1075 table.insert(topics, { name = topic })\
1076 end\
1077end\
1078\
1079local page = UI.Page {\
1080 labelText = UI.Text {\
1081 x = 3, y = 2,\
1082 value = 'Search',\
1083 },\
1084 filter = UI.TextEntry {\
1085 x = 10, y = 2, ex = -3,\
1086 limit = 32,\
1087 },\
1088 grid = UI.ScrollingGrid {\
1089 y = 4,\
1090 values = topics,\
1091 columns = {\
1092 { heading = 'Topic', key = 'name' },\
1093 },\
1094 sortColumn = 'name',\
1095 },\
1096 accelerators = {\
1097 q = 'quit',\
1098 enter = 'grid_select',\
1099 },\
1100}\
1101\
1102local topicPage = UI.Page {\
1103 backgroundColor = colors.black,\
1104 titleBar = UI.TitleBar {\
1105 title = 'text',\
1106 previousPage = true,\
1107 },\
1108 helpText = UI.TextArea {\
1109 backgroundColor = colors.black,\
1110 x = 2, ex = -1, y = 3, ey = -2,\
1111 },\
1112 accelerators = {\
1113 q = 'back',\
1114 backspace = 'back',\
1115 },\
1116}\
1117\
1118function topicPage:eventHandler(event)\
1119 if event.type == 'back' then\
1120 UI:setPreviousPage()\
1121 end\
1122 return UI.Page.eventHandler(self, event)\
1123end\
1124\
1125function page:eventHandler(event)\
1126 if event.type == 'quit' then\
1127 UI:exitPullEvents()\
1128\
1129 elseif event.type == 'grid_select' then\
1130 if self.grid:getSelected() then\
1131 local name = self.grid:getSelected().name\
1132 local f = help.lookup(name)\
1133\
1134 topicPage.titleBar.title = name\
1135 topicPage.helpText:setText(Util.readFile(f))\
1136\
1137 UI:setPage(topicPage)\
1138 end\
1139\
1140 elseif event.type == 'text_change' then\
1141 if #event.text == 0 then\
1142 self.grid.values = topics\
1143 else\
1144 self.grid.values = { }\
1145 for _,f in pairs(topics) do\
1146 if string.find(f.name, event.text) then\
1147 table.insert(self.grid.values, f)\
1148 end\
1149 end\
1150 end\
1151 self.grid:update()\
1152 self.grid:setIndex(1)\
1153 self.grid:draw()\
1154 else\
1155 return UI.Page.eventHandler(self, event)\
1156 end\
1157end\
1158\
1159UI:setPage(page)\
1160UI:pullEvents()",
1161 [ "trust.lua" ] = "_G.requireInjector(_ENV)\
1162\
1163local Crypto = require('crypto')\
1164local Security = require('security')\
1165local SHA1 = require('sha1')\
1166local Socket = require('socket')\
1167local Terminal = require('terminal')\
1168\
1169local os = _G.os\
1170\
1171local remoteId\
1172local args = { ... }\
1173\
1174if #args == 1 then\
1175 remoteId = tonumber(args[1])\
1176else\
1177 print('Enter host ID')\
1178 remoteId = tonumber(_G.read())\
1179end\
1180\
1181if not remoteId then\
1182 error('Syntax: trust <host ID>')\
1183end\
1184\
1185local password = Terminal.readPassword('Enter password: ')\
1186\
1187if not password then\
1188 error('Invalid password')\
1189end\
1190\
1191print('connecting...')\
1192local socket, msg = Socket.connect(remoteId, 19)\
1193\
1194if not socket then\
1195 error(msg)\
1196end\
1197\
1198local publicKey = Security.getPublicKey()\
1199\
1200socket:write(Crypto.encrypt({ pk = publicKey, dh = os.getComputerID() }, SHA1.sha1(password)))\
1201\
1202local data = socket:read(2)\
1203socket:close()\
1204\
1205if data and data.success then\
1206 print(data.msg)\
1207elseif data then\
1208 error(data.msg)\
1209else\
1210 error('No response')\
1211end",
1212 [ "PackageManager.lua" ] = "_G.requireInjector(_ENV)\
1213\
1214local Ansi = require('ansi')\
1215local Packages = require('packages')\
1216local UI = require('ui')\
1217\
1218local colors = _G.colors\
1219local shell = _ENV.shell\
1220local term = _G.term\
1221\
1222UI:configure('PackageManager', ...)\
1223\
1224local page = UI.Page {\
1225 grid = UI.ScrollingGrid {\
1226 y = 2, ey = 7, x = 2, ex = -6,\
1227 values = { },\
1228 columns = {\
1229 { heading = 'Package', key = 'name' },\
1230 },\
1231 sortColumn = 'name',\
1232 autospace = true,\
1233 help = 'Select a package',\
1234 },\
1235 add = UI.Button {\
1236 x = -4, y = 4,\
1237 text = '+',\
1238 event = 'action',\
1239 help = 'Install or update',\
1240 },\
1241 remove = UI.Button {\
1242 x = -4, y = 6,\
1243 text = '-',\
1244 event = 'action',\
1245 operation = 'uninstall',\
1246 operationText = 'Remove',\
1247 help = 'Remove',\
1248 },\
1249 description = UI.TextArea {\
1250 x = 2, y = 9, ey = -2,\
1251 --backgroundColor = colors.white,\
1252 },\
1253 statusBar = UI.StatusBar { },\
1254 action = UI.SlideOut {\
1255 backgroundColor = colors.cyan,\
1256 titleBar = UI.TitleBar {\
1257 event = 'hide-action',\
1258 },\
1259 button = UI.Button {\
1260 ex = -4, y = 4, width = 7,\
1261 text = 'Begin', event = 'begin',\
1262 },\
1263 output = UI.Embedded {\
1264 y = 6, ey = -2, x = 2, ex = -2,\
1265 },\
1266 statusBar = UI.StatusBar {\
1267 backgroundColor = colors.cyan,\
1268 },\
1269 },\
1270}\
1271\
1272function page.grid:getRowTextColor(row, selected)\
1273 if row.installed then\
1274 return colors.yellow\
1275 end\
1276 return UI.Grid.getRowTextColor(self, row, selected)\
1277end\
1278\
1279function page.action:show()\
1280 UI.SlideOut.show(self)\
1281 self.output:draw()\
1282 self.output.win.redraw()\
1283end\
1284\
1285function page:run(operation, name)\
1286 local oterm = term.redirect(self.action.output.win)\
1287 self.action.output:clear()\
1288 local cmd = string.format('package %s %s', operation, name)\
1289 --for _ = 1, 3 do\
1290 -- print(cmd .. '\\n')\
1291 -- os.sleep(1)\
1292 --end\
1293 term.setCursorPos(1, 1)\
1294 term.clear()\
1295 term.setTextColor(colors.yellow)\
1296 print(cmd .. '\\n')\
1297 term.setTextColor(colors.white)\
1298 shell.run(cmd)\
1299 term.redirect(oterm)\
1300 self.action.output:draw()\
1301end\
1302\
1303function page:updateSelection(selected)\
1304 self.add.operation = selected.installed and 'update' or 'install'\
1305 self.add.operationText = selected.installed and 'Update' or 'Install'\
1306end\
1307\
1308function page:eventHandler(event)\
1309 if event.type == 'focus_change' then\
1310 self.statusBar:setStatus(event.focused.help)\
1311\
1312 elseif event.type == 'grid_focus_row' then\
1313 local manifest = event.selected.manifest\
1314\
1315 self.description.value = string.format('%s%s\\n\\n%s%s',\
1316 Ansi.yellow, manifest.title,\
1317 Ansi.white, manifest.description)\
1318 self.description:draw()\
1319 self:updateSelection(event.selected)\
1320\
1321 elseif event.type == 'action' then\
1322 local selected = self.grid:getSelected()\
1323 if selected then\
1324 self.operation = event.button.operation\
1325 self.action.button.text = event.button.operationText\
1326 self.action.titleBar.title = selected.manifest.title\
1327 self.action.button.text = 'Begin'\
1328 self.action.button.event = 'begin'\
1329 self.action:show()\
1330 end\
1331\
1332 elseif event.type == 'hide-action' then\
1333 self.action:hide()\
1334\
1335 elseif event.type == 'begin' then\
1336 local selected = self.grid:getSelected()\
1337 self:run(self.operation, selected.name)\
1338 selected.installed = Packages:isInstalled(selected.name)\
1339\
1340 self:updateSelection(selected)\
1341 self.action.button.text = 'Done'\
1342 self.action.button.event = 'hide-action'\
1343 self.action.button:draw()\
1344\
1345 elseif event.type == 'quit' then\
1346 UI:exitPullEvents()\
1347 end\
1348 UI.Page.eventHandler(self, event)\
1349end\
1350\
1351for k in pairs(Packages:list()) do\
1352 local manifest = Packages:getManifest(k)\
1353 if not manifest then\
1354 manifest = {\
1355 invalid = true,\
1356 description = 'Unable to download manifest',\
1357 title = '',\
1358 }\
1359 end\
1360 table.insert(page.grid.values, {\
1361 installed = not not Packages:isInstalled(k),\
1362 name = k,\
1363 manifest = manifest,\
1364 })\
1365end\
1366page.grid:update()\
1367\
1368UI:setPage(page)\
1369UI:pullEvents()",
1370 [ "Files.lua" ] = "_G.requireInjector(_ENV)\
1371\
1372local Config = require('config')\
1373local Event = require('event')\
1374local UI = require('ui')\
1375local Util = require('util')\
1376\
1377local colors = _G.colors\
1378local fs = _G.fs\
1379local multishell = _ENV.multishell\
1380local os = _G.os\
1381local shell = _ENV.shell\
1382\
1383UI:configure('Files', ...)\
1384\
1385local config = {\
1386 showHidden = false,\
1387 showDirSizes = false,\
1388}\
1389\
1390Config.load('Files', config)\
1391\
1392local copied = { }\
1393local marked = { }\
1394local directories = { }\
1395local cutMode = false\
1396\
1397local function formatSize(size)\
1398 if size >= 1000000 then\
1399 return string.format('%dM', math.floor(size/1000000, 2))\
1400 elseif size >= 1000 then\
1401 return string.format('%dK', math.floor(size/1000, 2))\
1402 end\
1403 return size\
1404end\
1405\
1406local Browser = UI.Page {\
1407 menuBar = UI.MenuBar {\
1408 buttons = {\
1409 { text = '^-', event = 'updir' },\
1410 { text = 'File', dropdown = {\
1411 { text = 'Run', event = 'run' },\
1412 { text = 'Edit e', event = 'edit' },\
1413 { text = 'Shell s', event = 'shell' },\
1414 UI.MenuBar.spacer,\
1415 { text = 'Quit q', event = 'quit' },\
1416 } },\
1417 { text = 'Edit', dropdown = {\
1418 { text = 'Cut ^x', event = 'cut' },\
1419 { text = 'Copy ^c', event = 'copy' },\
1420 { text = 'Copy path ', event = 'copy_path' },\
1421 { text = 'Paste ^v', event = 'paste' },\
1422 UI.MenuBar.spacer,\
1423 { text = 'Mark m', event = 'mark' },\
1424 { text = 'Unmark all u', event = 'unmark' },\
1425 UI.MenuBar.spacer,\
1426 { text = 'Delete del', event = 'delete' },\
1427 } },\
1428 { text = 'View', dropdown = {\
1429 { text = 'Refresh r', event = 'refresh' },\
1430 { text = 'Hidden ^h', event = 'toggle_hidden' },\
1431 { text = 'Dir Size ^s', event = 'toggle_dirSize' },\
1432 } },\
1433 },\
1434 },\
1435 grid = UI.ScrollingGrid {\
1436 columns = {\
1437 { heading = 'Name', key = 'name' },\
1438 { key = 'flags', width = 2 },\
1439 { heading = 'Size', key = 'fsize', width = 5 },\
1440 },\
1441 sortColumn = 'name',\
1442 y = 2, ey = -2,\
1443 },\
1444 statusBar = UI.StatusBar {\
1445 columns = {\
1446 { key = 'status' },\
1447 { key = 'totalSize', width = 6 },\
1448 },\
1449 },\
1450 accelerators = {\
1451 q = 'quit',\
1452 e = 'edit',\
1453 s = 'shell',\
1454 r = 'refresh',\
1455 space = 'mark',\
1456 backspace = 'updir',\
1457 m = 'move',\
1458 u = 'unmark',\
1459 d = 'delete',\
1460 delete = 'delete',\
1461 [ 'control-h' ] = 'toggle_hidden',\
1462 [ 'control-s' ] = 'toggle_dirSize',\
1463 [ 'control-x' ] = 'cut',\
1464 [ 'control-c' ] = 'copy',\
1465 paste = 'paste',\
1466 },\
1467}\
1468\
1469function Browser:enable()\
1470 UI.Page.enable(self)\
1471 self:setFocus(self.grid)\
1472end\
1473\
1474function Browser.menuBar:getActive(menuItem)\
1475 local file = Browser.grid:getSelected()\
1476 if file then\
1477 if menuItem.event == 'edit' or menuItem.event == 'run' then\
1478 return not file.isDir\
1479 end\
1480 end\
1481 return true\
1482end\
1483\
1484function Browser.grid:sortCompare(a, b)\
1485 if self.sortColumn == 'fsize' then\
1486 return a.size < b.size\
1487 elseif self.sortColumn == 'flags' then\
1488 return a.flags < b.flags\
1489 end\
1490 if a.isDir == b.isDir then\
1491 return a.name:lower() < b.name:lower()\
1492 end\
1493 return a.isDir\
1494end\
1495\
1496function Browser.grid:getRowTextColor(file)\
1497 if file.marked then\
1498 return colors.green\
1499 end\
1500 if file.isDir then\
1501 return colors.cyan\
1502 end\
1503 if file.isReadOnly then\
1504 return colors.pink\
1505 end\
1506 return colors.white\
1507end\
1508\
1509function Browser.grid:eventHandler(event)\
1510 if event.type == 'copy' then -- let copy be handled by parent\
1511 return false\
1512 end\
1513 return UI.ScrollingGrid.eventHandler(self, event)\
1514end\
1515\
1516function Browser.statusBar:draw()\
1517 if self.parent.dir then\
1518 local info = '#:' .. Util.size(self.parent.dir.files)\
1519 local numMarked = Util.size(marked)\
1520 if numMarked > 0 then\
1521 info = info .. ' M:' .. numMarked\
1522 end\
1523 self:setValue('info', info)\
1524 self:setValue('totalSize', formatSize(self.parent.dir.totalSize))\
1525 UI.StatusBar.draw(self)\
1526 end\
1527end\
1528\
1529function Browser:setStatus(status, ...)\
1530 self.statusBar:timedStatus(string.format(status, ...))\
1531end\
1532\
1533function Browser:unmarkAll()\
1534 for _,m in pairs(marked) do\
1535 m.marked = false\
1536 end\
1537 Util.clear(marked)\
1538end\
1539\
1540function Browser:getDirectory(directory)\
1541 local s, dir = pcall(function()\
1542\
1543 local dir = directories[directory]\
1544 if not dir then\
1545 dir = {\
1546 name = directory,\
1547 size = 0,\
1548 files = { },\
1549 totalSize = 0,\
1550 index = 1\
1551 }\
1552 directories[directory] = dir\
1553 end\
1554\
1555 self:updateDirectory(dir)\
1556\
1557 return dir\
1558 end)\
1559\
1560 return s, dir\
1561end\
1562\
1563function Browser:updateDirectory(dir)\
1564\
1565 dir.size = 0\
1566 dir.totalSize = 0\
1567 Util.clear(dir.files)\
1568\
1569 local files = fs.listEx(dir.name)\
1570 if files then\
1571 dir.size = #files\
1572 for _, file in pairs(files) do\
1573 file.fullName = fs.combine(dir.name, file.name)\
1574 file.flags = ''\
1575 if not file.isDir then\
1576 dir.totalSize = dir.totalSize + file.size\
1577 file.fsize = formatSize(file.size)\
1578 else\
1579 if config.showDirSizes then\
1580 file.size = fs.getSize(file.fullName, true)\
1581\
1582 dir.totalSize = dir.totalSize + file.size\
1583 file.fsize = formatSize(file.size)\
1584 end\
1585 file.flags = 'D'\
1586 end\
1587 if file.isReadOnly then\
1588 file.flags = file.flags .. 'R'\
1589 end\
1590 if config.showHidden or file.name:sub(1, 1) ~= '.' then\
1591 dir.files[file.fullName] = file\
1592 end\
1593 end\
1594 end\
1595-- self.grid:update()\
1596-- self.grid:setIndex(dir.index)\
1597 self.grid:setValues(dir.files)\
1598end\
1599\
1600function Browser:setDir(dirName, noStatus)\
1601 self:unmarkAll()\
1602\
1603 if self.dir then\
1604 self.dir.index = self.grid:getIndex()\
1605 end\
1606 local DIR = fs.combine('', dirName)\
1607 shell.setDir(DIR)\
1608 local s, dir = self:getDirectory(DIR)\
1609 if s then\
1610 self.dir = dir\
1611 elseif noStatus then\
1612 error(dir)\
1613 else\
1614 self:setStatus(dir)\
1615 self:setDir('', true)\
1616 return\
1617 end\
1618\
1619 if not noStatus then\
1620 self.statusBar:setValue('status', '/' .. self.dir.name)\
1621 self.statusBar:draw()\
1622 end\
1623 self.grid:setIndex(self.dir.index)\
1624end\
1625\
1626function Browser:run(...)\
1627 if multishell then\
1628 local tabId = shell.openTab(...)\
1629 multishell.setFocus(tabId)\
1630 else\
1631 shell.run(...)\
1632 Event.terminate = false\
1633 self:draw()\
1634 end\
1635end\
1636\
1637function Browser:hasMarked()\
1638 if Util.size(marked) == 0 then\
1639 local file = self.grid:getSelected()\
1640 if file then\
1641 file.marked = true\
1642 marked[file.fullName] = file\
1643 self.grid:draw()\
1644 end\
1645 end\
1646 return Util.size(marked) > 0\
1647end\
1648\
1649function Browser:eventHandler(event)\
1650 local file = self.grid:getSelected()\
1651\
1652 if event.type == 'quit' then\
1653 Event.exitPullEvents()\
1654\
1655 elseif event.type == 'edit' and file then\
1656 self:run('edit', file.name)\
1657\
1658 elseif event.type == 'shell' then\
1659 self:run('sys/os/opus/sys/apps/shell')\
1660\
1661 elseif event.type == 'refresh' then\
1662 self:updateDirectory(self.dir)\
1663 self.grid:draw()\
1664 self:setStatus('Refreshed')\
1665\
1666 elseif event.type == 'toggle_hidden' then\
1667 config.showHidden = not config.showHidden\
1668 Config.update('Files', config)\
1669\
1670 self:updateDirectory(self.dir)\
1671 self.grid:draw()\
1672 if not config.showHidden then\
1673 self:setStatus('Hiding hidden')\
1674 else\
1675 self:setStatus('Displaying hidden')\
1676 end\
1677\
1678 elseif event.type == 'toggle_dirSize' then\
1679 config.showDirSizes = not config.showDirSizes\
1680 Config.update('Files', config)\
1681\
1682 self:updateDirectory(self.dir)\
1683 self.grid:draw()\
1684 if config.showDirSizes then\
1685 self:setStatus('Displaying dir sizes')\
1686 end\
1687\
1688 elseif event.type == 'mark' and file then\
1689 file.marked = not file.marked\
1690 if file.marked then\
1691 marked[file.fullName] = file\
1692 else\
1693 marked[file.fullName] = nil\
1694 end\
1695 self.grid:draw()\
1696 self.statusBar:draw()\
1697\
1698 elseif event.type == 'unmark' then\
1699 self:unmarkAll()\
1700 self.grid:draw()\
1701 self:setStatus('Marked files cleared')\
1702\
1703 elseif event.type == 'grid_select' or event.type == 'run' then\
1704 if file then\
1705 if file.isDir then\
1706 self:setDir(file.fullName)\
1707 else\
1708 self:run(file.name)\
1709 end\
1710 end\
1711\
1712 elseif event.type == 'updir' then\
1713 local dir = (self.dir.name:match(\"(.*/)\"))\
1714 self:setDir(dir or '/')\
1715\
1716 elseif event.type == 'delete' then\
1717 if self:hasMarked() then\
1718 local width = self.statusBar:getColumnWidth('status')\
1719 self.statusBar:setColumnWidth('status', UI.term.width)\
1720 self.statusBar:setValue('status', 'Delete marked? (y/n)')\
1721 self.statusBar:draw()\
1722 self.statusBar:sync()\
1723 local _, ch = os.pullEvent('char')\
1724 if ch == 'y' or ch == 'Y' then\
1725 for _,m in pairs(marked) do\
1726 pcall(function()\
1727 fs.delete(m.fullName)\
1728 end)\
1729 end\
1730 end\
1731 marked = { }\
1732 self.statusBar:setColumnWidth('status', width)\
1733 self.statusBar:setValue('status', '/' .. self.dir.name)\
1734 self:updateDirectory(self.dir)\
1735\
1736 self.statusBar:draw()\
1737 self.grid:draw()\
1738 self:setFocus(self.grid)\
1739 end\
1740\
1741 elseif event.type == 'copy' or event.type == 'cut' then\
1742 if self:hasMarked() then\
1743 cutMode = event.type == 'cut'\
1744 Util.clear(copied)\
1745 Util.merge(copied, marked)\
1746 --self:unmarkAll()\
1747 self.grid:draw()\
1748 self:setStatus('Copied %d file(s)', Util.size(copied))\
1749 end\
1750\
1751 elseif event.type == 'copy_path' then\
1752 if file then\
1753 os.queueEvent('clipboard_copy', file.fullName)\
1754 end\
1755\
1756 elseif event.type == 'paste' then\
1757 for _,m in pairs(copied) do\
1758 local s, m = pcall(function()\
1759 if cutMode then\
1760 fs.move(m.fullName, fs.combine(self.dir.name, m.name))\
1761 else\
1762 fs.copy(m.fullName, fs.combine(self.dir.name, m.name))\
1763 end\
1764 end)\
1765 end\
1766 self:updateDirectory(self.dir)\
1767 self.grid:draw()\
1768 self:setStatus('Pasted ' .. Util.size(copied) .. ' file(s)')\
1769\
1770 else\
1771 return UI.Page.eventHandler(self, event)\
1772 end\
1773 self:setFocus(self.grid)\
1774 return true\
1775end\
1776\
1777--[[-- Startup logic --]]--\
1778local args = { ... }\
1779\
1780Browser:setDir(args[1] or shell.dir())\
1781\
1782UI:setPage(Browser)\
1783\
1784Event.pullEvents()\
1785UI.term:reset()",
1786 [ "Lua.lua" ] = "_G.requireInjector(_ENV)\
1787\
1788local History = require('history')\
1789local UI = require('ui')\
1790local Util = require('util')\
1791\
1792local colors = _G.colors\
1793local os = _G.os\
1794local textutils = _G.textutils\
1795local term = _G.term\
1796\
1797local _exit\
1798\
1799local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G })\
1800sandboxEnv.exit = function() _exit = true end\
1801sandboxEnv._echo = function( ... ) return { ... } end\
1802_G.requireInjector(sandboxEnv)\
1803\
1804UI:configure('Lua', ...)\
1805\
1806local command = ''\
1807local history = History.load('/sys/os/opus/usr/.lua_history', 25)\
1808\
1809local page = UI.Page {\
1810 menuBar = UI.MenuBar {\
1811 buttons = {\
1812 { text = 'Local', event = 'local' },\
1813 { text = 'Global', event = 'global' },\
1814 { text = 'Device', event = 'device', name = 'Device' },\
1815 },\
1816 },\
1817 prompt = UI.TextEntry {\
1818 y = 2,\
1819 shadowText = 'enter command',\
1820 limit = 256,\
1821 accelerators = {\
1822 enter = 'command_enter',\
1823 up = 'history_back',\
1824 down = 'history_forward',\
1825 mouse_rightclick = 'clear_prompt',\
1826 [ 'control-space' ] = 'autocomplete',\
1827 },\
1828 },\
1829 grid = UI.ScrollingGrid {\
1830 y = 3, ey = -2,\
1831 columns = {\
1832 { heading = 'Key', key = 'name' },\
1833 { heading = 'Value', key = 'value' },\
1834 },\
1835 sortColumn = 'name',\
1836 autospace = true,\
1837 },\
1838 titleBar = UI.TitleBar {\
1839 title = 'Output',\
1840 y = -1,\
1841 event = 'show_output',\
1842 closeInd = '^'\
1843 },\
1844 output = UI.Embedded {\
1845 y = -6,\
1846 backgroundColor = colors.gray,\
1847 },\
1848}\
1849\
1850function page:setPrompt(value, focus)\
1851 self.prompt:setValue(value)\
1852 self.prompt.scroll = 0\
1853 self.prompt:setPosition(#value)\
1854 self.prompt:updateScroll()\
1855\
1856 if value:sub(-1) == ')' then\
1857 self.prompt:setPosition(#value - 1)\
1858 end\
1859\
1860 self.prompt:draw()\
1861 if focus then\
1862 page:setFocus(self.prompt)\
1863 end\
1864end\
1865\
1866function page:enable()\
1867 self:setFocus(self.prompt)\
1868 UI.Page.enable(self)\
1869 self.output:disable()\
1870end\
1871\
1872local function autocomplete(env, oLine, x)\
1873 local sLine = oLine:sub(1, x)\
1874 local nStartPos = sLine:find(\"[a-zA-Z0-9_%.]+$\")\
1875 if nStartPos then\
1876 sLine = sLine:sub(nStartPos)\
1877 end\
1878\
1879 if #sLine > 0 then\
1880 local results = textutils.complete(sLine, env)\
1881\
1882 if #results == 1 then\
1883 return Util.insertString(oLine, results[1], x + 1)\
1884\
1885 elseif #results > 1 then\
1886 local prefix = results[1]\
1887 for n = 1, #results do\
1888 local result = results[n]\
1889 while #prefix > 0 do\
1890 if result:find(prefix, 1, true) == 1 then\
1891 break\
1892 end\
1893 prefix = prefix:sub(1, #prefix - 1)\
1894 end\
1895 end\
1896 if #prefix > 0 then\
1897 return Util.insertString(oLine, prefix, x + 1)\
1898 end\
1899 end\
1900 end\
1901 return oLine\
1902end\
1903\
1904function page:eventHandler(event)\
1905 if event.type == 'global' then\
1906 self:setPrompt('_G', true)\
1907 self:executeStatement('_G')\
1908 command = nil\
1909\
1910 elseif event.type == 'local' then\
1911 self:setPrompt('_ENV', true)\
1912 self:executeStatement('_ENV')\
1913 command = nil\
1914\
1915 elseif event.type == 'hide_output' then\
1916 self.output:disable()\
1917\
1918 self.titleBar.oy = -1\
1919 self.titleBar.event = 'show_output'\
1920 self.titleBar.closeInd = '^'\
1921 self.titleBar:resize()\
1922\
1923 self.grid.ey = -2\
1924 self.grid:resize()\
1925\
1926 self:draw()\
1927\
1928 elseif event.type == 'show_output' then\
1929 self.output:enable()\
1930\
1931 self.titleBar.oy = -7\
1932 self.titleBar.event = 'hide_output'\
1933 self.titleBar.closeInd = 'v'\
1934 self.titleBar:resize()\
1935\
1936 self.grid.ey = -8\
1937 self.grid:resize()\
1938\
1939 self:draw()\
1940\
1941 elseif event.type == 'autocomplete' then\
1942 local sz = #self.prompt.value\
1943 local pos = self.prompt.pos\
1944 self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.pos))\
1945 self.prompt:setPosition(pos + #self.prompt.value - sz)\
1946 self.prompt:updateCursor()\
1947\
1948 elseif event.type == 'device' then\
1949 self:setPrompt('device', true)\
1950 self:executeStatement('device')\
1951\
1952 elseif event.type == 'history_back' then\
1953 local value = history:back()\
1954 if value then\
1955 self:setPrompt(value)\
1956 end\
1957\
1958 elseif event.type == 'history_forward' then\
1959 self:setPrompt(history:forward() or '')\
1960\
1961 elseif event.type == 'clear_prompt' then\
1962 self:setPrompt('')\
1963 history:reset()\
1964\
1965 elseif event.type == 'command_enter' then\
1966 local s = tostring(self.prompt.value)\
1967\
1968 if #s > 0 then\
1969 history:add(s)\
1970 history:back()\
1971 self:executeStatement(s)\
1972 else\
1973 local t = { }\
1974 for k = #history.entries, 1, -1 do\
1975 table.insert(t, {\
1976 name = #t + 1,\
1977 value = history.entries[k],\
1978 isHistory = true,\
1979 pos = k,\
1980 })\
1981 end\
1982 history:reset()\
1983 command = nil\
1984 self.grid:setValues(t)\
1985 self.grid:setIndex(1)\
1986 self.grid:adjustWidth()\
1987 self:draw()\
1988 end\
1989 return true\
1990\
1991 else\
1992 return UI.Page.eventHandler(self, event)\
1993 end\
1994 return true\
1995end\
1996\
1997function page:setResult(result)\
1998 local t = { }\
1999\
2000 local oterm = term.redirect(self.output.win)\
2001 Util.print(result)\
2002 term.redirect(oterm)\
2003\
2004 local function safeValue(v)\
2005 if type(v) == 'string' or type(v) == 'number' then\
2006 return v\
2007 end\
2008 return tostring(v)\
2009 end\
2010\
2011 if type(result) == 'table' then\
2012 for k,v in pairs(result) do\
2013 local entry = {\
2014 name = safeValue(k),\
2015 rawName = k,\
2016 value = safeValue(v),\
2017 rawValue = v,\
2018 }\
2019 if type(v) == 'table' then\
2020 if Util.size(v) == 0 then\
2021 entry.value = 'table: (empty)'\
2022 else\
2023 entry.value = tostring(v)\
2024 end\
2025 end\
2026 table.insert(t, entry)\
2027 end\
2028 else\
2029 table.insert(t, {\
2030 name = type(result),\
2031 value = tostring(result),\
2032 rawValue = result,\
2033 })\
2034 end\
2035 self.grid:setValues(t)\
2036 self.grid:setIndex(1)\
2037 self.grid:adjustWidth()\
2038 self:draw()\
2039end\
2040\
2041function page.grid:eventHandler(event)\
2042 local entry = self:getSelected()\
2043\
2044 local function commandAppend()\
2045 if entry.isHistory then\
2046 --history.setPosition(entry.pos)\
2047 return entry.value\
2048 end\
2049 if type(entry.rawValue) == 'function' then\
2050 if command then\
2051 return command .. '.' .. entry.name .. '()'\
2052 end\
2053 return entry.name .. '()'\
2054 end\
2055 if command then\
2056 if type(entry.rawName) == 'number' then\
2057 return command .. '[' .. entry.name .. ']'\
2058 end\
2059 if entry.name:match(\"%W\") or\
2060 entry.name:sub(1, 1):match(\"%d\") then\
2061 return command .. \"['\" .. tostring(entry.name) .. \"']\"\
2062 end\
2063 return command .. '.' .. entry.name\
2064 end\
2065 return entry.name\
2066 end\
2067\
2068 if event.type == 'grid_focus_row' then\
2069 if self.focused then\
2070 page:setPrompt(commandAppend())\
2071 end\
2072 elseif event.type == 'grid_select' then\
2073 page:setPrompt(commandAppend(), true)\
2074 page:executeStatement(commandAppend())\
2075\
2076 elseif event.type == 'copy' then\
2077 if entry then\
2078 os.queueEvent('clipboard_copy', entry.rawValue)\
2079 end\
2080 else\
2081 return UI.ScrollingGrid.eventHandler(self, event)\
2082 end\
2083 return true\
2084end\
2085\
2086function page:rawExecute(s)\
2087 local fn, m\
2088\
2089 fn = load('return (' ..s.. ')', 'lua', nil, sandboxEnv)\
2090\
2091 if fn then\
2092 fn = load('return {' ..s.. '}', 'lua', nil, sandboxEnv)\
2093 end\
2094\
2095 if fn then\
2096 fn, m = pcall(fn)\
2097 if #m == 1 then\
2098 m = m[1]\
2099 end\
2100 return fn, m\
2101 end\
2102\
2103 fn, m = load(s, 'lua', nil, sandboxEnv)\
2104 if fn then\
2105 fn, m = pcall(fn)\
2106 end\
2107\
2108 return fn, m\
2109end\
2110\
2111function page:executeStatement(statement)\
2112 command = statement\
2113\
2114 local s, m\
2115 local oterm = term.redirect(self.output.win)\
2116 pcall(function()\
2117 s, m = self:rawExecute(command)\
2118 end)\
2119 if not s then\
2120 _G.printError(m)\
2121 end\
2122 term.redirect(oterm)\
2123\
2124 if s and m then\
2125 self:setResult(m)\
2126 else\
2127 self.grid:setValues({ })\
2128 self.grid:draw()\
2129 if m and not self.output.enabled then\
2130 self:emit({ type = 'show_output' })\
2131 end\
2132 end\
2133\
2134 if _exit then\
2135 UI:exitPullEvents()\
2136 end\
2137end\
2138\
2139local args = { ... }\
2140if args[1] then\
2141 command = 'args[1]'\
2142 sandboxEnv.args = args\
2143 page:setResult(args[1])\
2144end\
2145\
2146UI:setPage(page)\
2147UI:pullEvents()",
2148 [ "password.lua" ] = "_G.requireInjector(_ENV)\
2149\
2150local Security = require('security')\
2151local SHA1 = require('sha1')\
2152local Terminal = require('terminal')\
2153\
2154local password = Terminal.readPassword('Enter new password: ')\
2155\
2156if password then\
2157 Security.updatePassword(SHA1.sha1(password))\
2158 print('Password updated')\
2159end",
2160 [ "System.lua" ] = "_G.requireInjector(_ENV)\
2161\
2162local Config = require('config')\
2163local Security = require('security')\
2164local SHA1 = require('sha1')\
2165local UI = require('ui')\
2166local Util = require('util')\
2167\
2168local fs = _G.fs\
2169local os = _G.os\
2170local settings = _G.settings\
2171local shell = _ENV.shell\
2172local turtle = _G.turtle\
2173\
2174UI:configure('System', ...)\
2175\
2176local env = {\
2177 path = shell.path(),\
2178 aliases = shell.aliases(),\
2179 lua_path = _ENV.LUA_PATH,\
2180}\
2181Config.load('shell', env)\
2182\
2183local systemPage = UI.Page {\
2184 tabs = UI.Tabs {\
2185 pathTab = UI.Window {\
2186 tabTitle = 'Path',\
2187 entry = UI.TextEntry {\
2188 x = 2, y = 2, ex = -2,\
2189 limit = 256,\
2190 value = shell.path(),\
2191 shadowText = 'enter system path',\
2192 accelerators = {\
2193 enter = 'update_path',\
2194 },\
2195 },\
2196 grid = UI.Grid {\
2197 y = 4,\
2198 disableHeader = true,\
2199 columns = { { key = 'value' } },\
2200 autospace = true,\
2201 },\
2202 },\
2203\
2204 aliasTab = UI.Window {\
2205 tabTitle = 'Alias',\
2206 alias = UI.TextEntry {\
2207 x = 2, y = 2, ex = -2,\
2208 limit = 32,\
2209 shadowText = 'Alias',\
2210 },\
2211 path = UI.TextEntry {\
2212 y = 3, x = 2, ex = -2,\
2213 limit = 256,\
2214 shadowText = 'Program path',\
2215 accelerators = {\
2216 enter = 'new_alias',\
2217 },\
2218 },\
2219 grid = UI.Grid {\
2220 y = 5,\
2221 sortColumn = 'alias',\
2222 columns = {\
2223 { heading = 'Alias', key = 'alias' },\
2224 { heading = 'Program', key = 'path' },\
2225 },\
2226 accelerators = {\
2227 delete = 'delete_alias',\
2228 },\
2229 },\
2230 },\
2231\
2232 passwordTab = UI.Window {\
2233 tabTitle = 'Password',\
2234 oldPass = UI.TextEntry {\
2235 x = 2, y = 2, ex = -2,\
2236 limit = 32,\
2237 mask = true,\
2238 shadowText = 'old password',\
2239 inactive = not Security.getPassword(),\
2240 },\
2241 newPass = UI.TextEntry {\
2242 y = 3, x = 2, ex = -2,\
2243 limit = 32,\
2244 mask = true,\
2245 shadowText = 'new password',\
2246 accelerators = {\
2247 enter = 'new_password',\
2248 },\
2249 },\
2250 button = UI.Button {\
2251 x = 2, y = 5,\
2252 text = 'Update',\
2253 event = 'update_password',\
2254 },\
2255 info = UI.TextArea {\
2256 x = 2, ex = -2,\
2257 y = 7,\
2258 inactive = true,\
2259 value = 'Add a password to enable other computers to connect to this one.',\
2260 }\
2261 },\
2262\
2263 infoTab = UI.Window {\
2264 tabTitle = 'Info',\
2265 labelText = UI.Text {\
2266 x = 3, y = 2,\
2267 value = 'Label'\
2268 },\
2269 label = UI.TextEntry {\
2270 x = 9, y = 2, ex = -4,\
2271 limit = 32,\
2272 value = os.getComputerLabel(),\
2273 accelerators = {\
2274 enter = 'update_label',\
2275 },\
2276 },\
2277 grid = UI.ScrollingGrid {\
2278 y = 3,\
2279 values = {\
2280 { name = '', value = '' },\
2281 { name = 'CC version', value = Util.getVersion() },\
2282 { name = 'Lua version', value = _VERSION },\
2283 { name = 'MC version', value = Util.getMinecraftVersion() },\
2284 { name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },\
2285 { name = 'Computer ID', value = tostring(os.getComputerID()) },\
2286 { name = 'Day', value = tostring(os.day()) },\
2287 },\
2288 inactive = true,\
2289 columns = {\
2290 { key = 'name', width = 12 },\
2291 { key = 'value' },\
2292 },\
2293 },\
2294 },\
2295 },\
2296 notification = UI.Notification(),\
2297 accelerators = {\
2298 q = 'quit',\
2299 },\
2300}\
2301\
2302if turtle then\
2303 pcall(function()\
2304 local Home = require('turtle.home')\
2305-- TODO: dont rely on turtle.home\
2306 local values = { }\
2307 Config.load('gps', values.home and { values.home } or { })\
2308\
2309 systemPage.tabs:add({\
2310 gpsTab = UI.Window {\
2311 tabTitle = 'GPS',\
2312 labelText = UI.Text {\
2313 x = 3, y = 2,\
2314 value = 'On restart, return to this location'\
2315 },\
2316 grid = UI.Grid {\
2317 x = 3, ex = -3, y = 4,\
2318 height = 2,\
2319 values = values,\
2320 inactive = true,\
2321 columns = {\
2322 { heading = 'x', key = 'x' },\
2323 { heading = 'y', key = 'y' },\
2324 { heading = 'z', key = 'z' },\
2325 },\
2326 },\
2327 button1 = UI.Button {\
2328 x = 3, y = 7,\
2329 text = 'Set home',\
2330 event = 'gps_set',\
2331 },\
2332 button2 = UI.Button {\
2333 ex = -3, y = 7, width = 7,\
2334 text = 'Clear',\
2335 event = 'gps_clear',\
2336 },\
2337 },\
2338 })\
2339 function systemPage.tabs.gpsTab:eventHandler(event)\
2340 if event.type == 'gps_set' then\
2341 systemPage.notification:info('Determining location', 10)\
2342 systemPage:sync()\
2343 if Home.set() then\
2344 Config.load('gps', values)\
2345 self.grid:setValues(values.home and { values.home } or { })\
2346 self.grid:draw()\
2347 systemPage.notification:success('Location set')\
2348 else\
2349 systemPage.notification:error('Unable to determine location')\
2350 end\
2351 return true\
2352 elseif event.type == 'gps_clear' then\
2353 fs.delete('/sys/os/opus/usr/config/gps')\
2354 self.grid:setValues({ })\
2355 self.grid:draw()\
2356 return true\
2357 end\
2358 end\
2359 end)\
2360end\
2361\
2362if settings then\
2363 local values = { }\
2364 for _,v in pairs(settings.getNames()) do\
2365 local value = settings.get(v)\
2366 if not value then\
2367 value = false\
2368 end\
2369 table.insert(values, {\
2370 name = v,\
2371 value = value,\
2372 })\
2373 end\
2374\
2375 systemPage.tabs:add({\
2376 settingsTab = UI.Window {\
2377 tabTitle = 'Settings',\
2378 grid = UI.Grid {\
2379 y = 1,\
2380 values = values,\
2381 autospace = true,\
2382 sortColumn = 'name',\
2383 columns = {\
2384 { heading = 'Setting', key = 'name' },\
2385 { heading = 'Value', key = 'value' },\
2386 },\
2387 },\
2388 }\
2389 })\
2390 function systemPage.tabs.settingsTab:eventHandler(event)\
2391 if event.type == 'grid_select' then\
2392 if not event.selected.value or type(event.selected.value) == 'boolean' then\
2393 event.selected.value = not event.selected.value\
2394 end\
2395 settings.set(event.selected.name, event.selected.value)\
2396 settings.save('.settings')\
2397 self.grid:draw()\
2398 return true\
2399 end\
2400 end\
2401end\
2402\
2403function systemPage.tabs.pathTab.grid:draw()\
2404 self.values = { }\
2405 for _,v in ipairs(Util.split(env.path, '(.-):')) do\
2406 table.insert(self.values, { value = v })\
2407 end\
2408 self:update()\
2409 UI.Grid.draw(self)\
2410end\
2411\
2412function systemPage.tabs.pathTab:eventHandler(event)\
2413 if event.type == 'update_path' then\
2414 env.path = self.entry.value\
2415 self.grid:setIndex(self.grid:getIndex())\
2416 self.grid:draw()\
2417 Config.update('shell', env)\
2418 systemPage.notification:success('reboot to take effect')\
2419 return true\
2420 end\
2421end\
2422\
2423function systemPage.tabs.aliasTab.grid:draw()\
2424 self.values = { }\
2425 for k,v in pairs(env.aliases) do\
2426 table.insert(self.values, { alias = k, path = v })\
2427 end\
2428 self:update()\
2429 UI.Grid.draw(self)\
2430end\
2431\
2432function systemPage.tabs.aliasTab:eventHandler(event)\
2433 if event.type == 'delete_alias' then\
2434 env.aliases[self.grid:getSelected().alias] = nil\
2435 self.grid:setIndex(self.grid:getIndex())\
2436 self.grid:draw()\
2437 Config.update('shell', env)\
2438 systemPage.notification:success('reboot to take effect')\
2439 return true\
2440\
2441 elseif event.type == 'new_alias' then\
2442 env.aliases[self.alias.value] = self.path.value\
2443 self.alias:reset()\
2444 self.path:reset()\
2445 self:draw()\
2446 self:setFocus(self.alias)\
2447 Config.update('shell', env)\
2448 systemPage.notification:success('reboot to take effect')\
2449 return true\
2450 end\
2451end\
2452\
2453function systemPage.tabs.passwordTab:eventHandler(event)\
2454 if event.type == 'update_password' then\
2455 if #self.newPass.value == 0 then\
2456 systemPage.notification:error('Invalid password')\
2457 elseif Security.getPassword() and not Security.verifyPassword(SHA1.sha1(self.oldPass.value)) then\
2458 systemPage.notification:error('Passwords do not match')\
2459 else\
2460 Security.updatePassword(SHA1.sha1(self.newPass.value))\
2461 self.oldPass.inactive = false\
2462 systemPage.notification:success('Password updated')\
2463 end\
2464\
2465 return true\
2466 end\
2467end\
2468\
2469function systemPage.tabs.infoTab:eventHandler(event)\
2470 if event.type == 'update_label' then\
2471 os.setComputerLabel(self.label.value)\
2472 systemPage.notification:success('Label updated')\
2473 return true\
2474 end\
2475end\
2476\
2477function systemPage:eventHandler(event)\
2478 if event.type == 'quit' then\
2479 UI:exitPullEvents()\
2480 elseif event.type == 'tab_activate' then\
2481 event.activated:focusFirst()\
2482 else\
2483 return UI.Page.eventHandler(self, event)\
2484 end\
2485 return true\
2486end\
2487\
2488UI:setPage(systemPage)\
2489UI:pullEvents()",
2490 [ "netdaemon.lua" ] = "_G.requireInjector(_ENV)\
2491\
2492local Event = require('event')\
2493local Util = require('util')\
2494\
2495local device = _G.device\
2496local fs = _G.fs\
2497local network = _G.network\
2498local os = _G.os\
2499local printError = _G.printError\
2500\
2501if not device.wireless_modem then\
2502 return\
2503end\
2504\
2505print('Net daemon starting')\
2506\
2507for _,file in pairs(fs.list('sys/os/opus/sys/network')) do\
2508 local fn, msg = Util.run(_ENV, 'sys/os/opus/sys/network/' .. file)\
2509 if not fn then\
2510 printError(msg)\
2511 end\
2512end\
2513\
2514Event.on('device_detach', function()\
2515 if not device.wireless_modem then\
2516 Event.exitPullEvents()\
2517 end\
2518end)\
2519\
2520print('Net daemon started')\
2521os.queueEvent('network_up')\
2522Event.pullEvents()\
2523\
2524for _,c in pairs(network) do\
2525 c.active = false\
2526 os.queueEvent('network_detach', c)\
2527end\
2528os.queueEvent('network_down')\
2529Event.pullEvent('network_down')\
2530\
2531Util.clear(network)\
2532\
2533print('Net daemon stopped')",
2534 [ "mount.lua" ] = "local args = { ... }\
2535\
2536local target = table.remove(args, 1)\
2537target = shell.resolve(target)\
2538\
2539fs.mount(target, unpack(args))",
2540 [ "Network.lua" ] = "_G.requireInjector(_ENV)\
2541\
2542local Config = require('config')\
2543local Event = require('event')\
2544local Socket = require('socket')\
2545local UI = require('ui')\
2546local Util = require('util')\
2547\
2548local colors = _G.colors\
2549local device = _G.device\
2550local multishell = _ENV.multishell\
2551local network = _G.network\
2552local os = _G.os\
2553local shell = _ENV.shell\
2554\
2555UI:configure('Network', ...)\
2556\
2557local gridColumns = {\
2558 { heading = 'Label', key = 'label' },\
2559 { heading = 'Dist', key = 'distance' },\
2560 { heading = 'Status', key = 'status' },\
2561}\
2562\
2563local trusted = Util.readTable('/sys/os/opus/usr/.known_hosts')\
2564local config = Config.load('network', { })\
2565\
2566if UI.term.width >= 30 then\
2567 table.insert(gridColumns, { heading = 'Fuel', key = 'fuel', width = 5 })\
2568 table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' })\
2569end\
2570\
2571local page = UI.Page {\
2572 menuBar = UI.MenuBar {\
2573 buttons = {\
2574 { text = 'Connect', dropdown = {\
2575 { text = 'Telnet t', event = 'telnet' },\
2576 { text = 'VNC v', event = 'vnc' },\
2577 UI.MenuBar.spacer,\
2578 { text = 'Reboot r', event = 'reboot' },\
2579 } },\
2580 --{ text = 'Chat', event = 'chat' },\
2581 { text = 'Trust', dropdown = {\
2582 { text = 'Establish', event = 'trust' },\
2583 { text = 'Remove', event = 'untrust' },\
2584 } },\
2585 { text = 'Help', event = 'help', noCheck = true },\
2586 {\
2587 text = '\\206',\
2588 x = -3,\
2589 dropdown = {\
2590 { text = 'Show all', event = 'show_all', noCheck = true },\
2591 UI.MenuBar.spacer,\
2592 { text = 'Show trusted', event = 'show_trusted', noCheck = true },\
2593 },\
2594 },\
2595 },\
2596 },\
2597 grid = UI.ScrollingGrid {\
2598 y = 2,\
2599 values = network,\
2600 columns = gridColumns,\
2601 sortColumn = 'label',\
2602 autospace = true,\
2603 },\
2604 notification = UI.Notification { },\
2605 accelerators = {\
2606 t = 'telnet',\
2607 v = 'vnc',\
2608 r = 'reboot',\
2609 q = 'quit',\
2610 c = 'clear',\
2611 },\
2612}\
2613\
2614local function sendCommand(host, command)\
2615 if not device.wireless_modem then\
2616 page.notification:error('Wireless modem not present')\
2617 return\
2618 end\
2619\
2620 page.notification:info('Connecting')\
2621 page:sync()\
2622\
2623 local socket = Socket.connect(host, 161)\
2624 if socket then\
2625 socket:write({ type = command })\
2626 socket:close()\
2627 page.notification:success('Command sent')\
2628 else\
2629 page.notification:error('Failed to connect')\
2630 end\
2631end\
2632\
2633function page:eventHandler(event)\
2634 local t = self.grid:getSelected()\
2635 if t then\
2636 if event.type == 'telnet' then\
2637 multishell.openTab({\
2638 path = 'sys/os/opus/sys/apps/telnet.lua',\
2639 focused = true,\
2640 args = { t.id },\
2641 title = t.label,\
2642 })\
2643 elseif event.type == 'vnc' then\
2644 multishell.openTab({\
2645 path = 'sys/os/opus/sys/apps/vnc.lua',\
2646 focused = true,\
2647 args = { t.id },\
2648 title = t.label,\
2649 })\
2650 elseif event.type == 'clear' then\
2651 Util.clear(network)\
2652 page.grid:update()\
2653 page.grid:draw()\
2654\
2655 elseif event.type == 'trust' then\
2656 shell.openForegroundTab('trust ' .. t.id)\
2657\
2658 elseif event.type == 'untrust' then\
2659 local trustList = Util.readTable('/sys/os/opus/usr/.known_hosts') or { }\
2660 trustList[t.id] = nil\
2661 Util.writeTable('/sys/os/opus/usr/.known_hosts', trustList)\
2662\
2663 elseif event.type == 'chat' then\
2664 multishell.openTab({\
2665 path = 'sys/os/opus/sys/apps/shell',\
2666 args = { 'chat join opusChat-' .. t.id .. ' guest-' .. os.getComputerID() },\
2667 title = 'Chatroom',\
2668 focused = true,\
2669 })\
2670 elseif event.type == 'reboot' then\
2671 sendCommand(t.id, 'reboot')\
2672\
2673 elseif event.type == 'shutdown' then\
2674 sendCommand(t.id, 'shutdown')\
2675 end\
2676 end\
2677 if event.type == 'help' then\
2678 UI:setPage(UI.Dialog {\
2679 title = 'Network Help',\
2680 height = 10,\
2681 backgroundColor = colors.white,\
2682 text = UI.TextArea {\
2683 x = 2, y = 2,\
2684 backgroundColor = colors.white,\
2685 value = [[\
2686In order to connect to another computer:\
2687\
2688 1. The target computer must have a password set (run 'password' from the shell prompt).\
2689 2. From this computer, click trust and enter the password for that computer.\
2690\
2691This only needs to be done once.\
2692 ]],\
2693 },\
2694 accelerators = {\
2695 q = 'cancel',\
2696 }\
2697 })\
2698\
2699 elseif event.type == 'show_all' then\
2700 config.showTrusted = false\
2701 self.grid:setValues(network)\
2702 Config.update('network', config)\
2703\
2704 elseif event.type == 'show_trusted' then\
2705 config.showTrusted = true\
2706 Config.update('network', config)\
2707\
2708 elseif event.type == 'quit' then\
2709 Event.exitPullEvents()\
2710 end\
2711 UI.Page.eventHandler(self, event)\
2712end\
2713\
2714function page.menuBar:getActive(menuItem)\
2715 local t = page.grid:getSelected()\
2716 if menuItem.event == 'untrust' then\
2717 local trustList = Util.readTable('/sys/os/opus/usr/.known_hosts') or { }\
2718 return t and trustList[t.id]\
2719 end\
2720 return menuItem.noCheck or not not t\
2721end\
2722\
2723function page.grid:getRowTextColor(row, selected)\
2724 if not row.active then\
2725 return colors.orange\
2726 end\
2727 return UI.Grid.getRowTextColor(self, row, selected)\
2728end\
2729\
2730function page.grid:getDisplayValues(row)\
2731 row = Util.shallowCopy(row)\
2732 if row.uptime then\
2733 if row.uptime < 60 then\
2734 row.uptime = string.format(\"%ds\", math.floor(row.uptime))\
2735 else\
2736 row.uptime = string.format(\"%sm\", math.floor(row.uptime/6)/10)\
2737 end\
2738 end\
2739 if row.fuel then\
2740 row.fuel = Util.toBytes(row.fuel)\
2741 end\
2742 if row.distance then\
2743 row.distance = Util.round(row.distance, 1)\
2744 end\
2745 return row\
2746end\
2747\
2748Event.onInterval(1, function()\
2749 local t = { }\
2750 if config.showTrusted then\
2751 for k,v in pairs(network) do\
2752 if trusted[k] then\
2753 t[k] = v\
2754 end\
2755 end\
2756 page.grid:setValues(t)\
2757 else\
2758 page.grid:update()\
2759 end\
2760 page.grid:draw()\
2761 page:sync()\
2762end)\
2763\
2764Event.on('device_attach', function(_, deviceName)\
2765 if deviceName == 'wireless_modem' then\
2766 page.notification:success('Modem connected')\
2767 page:sync()\
2768 end\
2769end)\
2770\
2771Event.on('device_detach', function(_, deviceName)\
2772 if deviceName == 'wireless_modem' then\
2773 page.notification:error('Wireless modem not attached')\
2774 page:sync()\
2775 end\
2776end)\
2777\
2778if not device.wireless_modem then\
2779 page.notification:error('Wireless modem not attached')\
2780end\
2781\
2782UI:setPage(page)\
2783UI:pullEvents()",
2784 [ "Tasks.lua" ] = "_G.requireInjector(_ENV)\
2785\
2786local Event = require('event')\
2787local UI = require('ui')\
2788local Util = require('util')\
2789\
2790local kernel = _G.kernel\
2791local multishell = _ENV.multishell\
2792\
2793UI:configure('Tasks', ...)\
2794\
2795local page = UI.Page {\
2796 menuBar = UI.MenuBar {\
2797 buttons = {\
2798 { text = 'Activate', event = 'activate' },\
2799 { text = 'Terminate', event = 'terminate' },\
2800 },\
2801 },\
2802 grid = UI.ScrollingGrid {\
2803 y = 2,\
2804 columns = {\
2805 { heading = 'ID', key = 'uid', width = 3 },\
2806 { heading = 'Title', key = 'title' },\
2807 { heading = 'Status', key = 'status' },\
2808 { heading = 'Time', key = 'timestamp' },\
2809 },\
2810 values = kernel.routines,\
2811 sortColumn = 'uid',\
2812 autospace = true,\
2813 },\
2814 accelerators = {\
2815 q = 'quit',\
2816 space = 'activate',\
2817 t = 'terminate',\
2818 },\
2819}\
2820\
2821function page:eventHandler(event)\
2822 local t = self.grid:getSelected()\
2823 if t then\
2824 if event.type == 'activate' or event.type == 'grid_select' then\
2825 multishell.setFocus(t.uid)\
2826 elseif event.type == 'terminate' then\
2827 multishell.terminate(t.uid)\
2828 end\
2829 end\
2830 if event.type == 'quit' then\
2831 Event.exitPullEvents()\
2832 end\
2833 UI.Page.eventHandler(self, event)\
2834end\
2835\
2836function page.grid:getDisplayValues(row)\
2837 row = Util.shallowCopy(row)\
2838 local elapsed = os.clock()-row.timestamp\
2839 if elapsed < 60 then\
2840 row.timestamp = string.format(\"%ds\", math.floor(elapsed))\
2841 else\
2842 row.timestamp = string.format(\"%sm\", math.floor(elapsed/6)/10)\
2843 end\
2844 row.status = row.isDead and 'error' or coroutine.status(row.co)\
2845 return row\
2846end\
2847\
2848Event.onInterval(1, function()\
2849 page.grid:update()\
2850 page.grid:draw()\
2851 page:sync()\
2852end)\
2853\
2854UI:setPage(page)\
2855UI:pullEvents()",
2856 [ "telnet.lua" ] = "_G.requireInjector(_ENV)\
2857\
2858local Event = require('event')\
2859local Socket = require('socket')\
2860local Terminal = require('terminal')\
2861local Util = require('util')\
2862\
2863local multishell = _ENV.multishell\
2864local os = _G.os\
2865local read = _G.read\
2866local term = _G.term\
2867\
2868local args = { ... }\
2869\
2870local remoteId = tonumber(table.remove(args, 1) or '')\
2871if not remoteId then\
2872 print('Enter host ID')\
2873 remoteId = tonumber(read())\
2874end\
2875\
2876if not remoteId then\
2877 error('Syntax: telnet ID [PROGRAM] [ARGS]')\
2878end\
2879\
2880if multishell then\
2881 multishell.setTitle(multishell.getCurrent(), 'Telnet ' .. remoteId)\
2882end\
2883\
2884local socket, msg = Socket.connect(remoteId, 23)\
2885\
2886if not socket then\
2887 error(msg)\
2888end\
2889\
2890local ct = Util.shallowCopy(term.current())\
2891if not ct.isColor() then\
2892 Terminal.toGrayscale(ct)\
2893end\
2894\
2895local w, h = ct.getSize()\
2896socket:write({\
2897 width = w,\
2898 height = h,\
2899 isColor = ct.isColor(),\
2900 program = args,\
2901 pos = { ct.getCursorPos() },\
2902})\
2903\
2904Event.addRoutine(function()\
2905 while true do\
2906 local data = socket:read()\
2907 if not data then\
2908 break\
2909 end\
2910 for _,v in ipairs(data) do\
2911 ct[v.f](table.unpack(v.args))\
2912 end\
2913 end\
2914end)\
2915\
2916--ct.clear()\
2917--ct.setCursorPos(1, 1)\
2918\
2919local filter = Util.transpose {\
2920 'char', 'paste', 'key', 'key_up', 'terminate',\
2921 'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up',\
2922}\
2923\
2924while true do\
2925 local e = { os.pullEventRaw() }\
2926 local event = e[1]\
2927\
2928 if filter[event] then\
2929 socket:write(e)\
2930 else\
2931 Event.processEvent(e)\
2932 end\
2933\
2934 if not socket.connected then\
2935-- print()\
2936-- print('Connection lost')\
2937-- print('Press enter to exit')\
2938-- pcall(read)\
2939 break\
2940 end\
2941end",
2942 [ "package.lua" ] = "_G.requireInjector(_ENV)\
2943\
2944local Git = require('git')\
2945local Packages = require('packages')\
2946local Util = require('util')\
2947\
2948local fs = _G.fs\
2949local term = _G.term\
2950\
2951local args = { ... }\
2952local action = table.remove(args, 1)\
2953\
2954local function Syntax(msg)\
2955 _G.printError(msg)\
2956 print('\\nSyntax: Package list | install [name] ... | update [name] | uninstall [name]')\
2957 error(0)\
2958end\
2959\
2960local function progress(max)\
2961 -- modified from: https://pastebin.com/W5ZkVYSi (apemanzilla)\
2962 local _, y = term.getCursorPos()\
2963 local wide, _ = term.getSize()\
2964 term.setCursorPos(1, y)\
2965 term.write(\"[\")\
2966 term.setCursorPos(wide - 6, y)\
2967 term.write(\"]\")\
2968 local done = 0\
2969 return function()\
2970 done = done + 1\
2971 local value = done / max\
2972 term.setCursorPos(2,y)\
2973 term.write((\"=\"):rep(math.floor(value * (wide - 8))))\
2974 local percent = math.floor(value * 100) .. \"%\"\
2975 term.setCursorPos(wide - percent:len(),y)\
2976 term.write(percent)\
2977 end\
2978end\
2979\
2980local function install(name)\
2981 local manifest = Packages:getManifest(name) or error('Invalid package')\
2982 local packageDir = fs.combine('packages', name)\
2983 local method = args[2] or 'local'\
2984 if method == 'remote' then\
2985 Util.writeTable(packageDir .. '/.install', {\
2986 mount = string.format('%s gitfs %s', packageDir, manifest.repository),\
2987 })\
2988 Util.writeTable(fs.combine(packageDir, '.package'), manifest)\
2989 else\
2990 local list = Git.list(manifest.repository)\
2991 local showProgress = progress(Util.size(list))\
2992 for path, entry in pairs(list) do\
2993 Util.download(entry.url, fs.combine(packageDir, path))\
2994 showProgress()\
2995 end\
2996 end\
2997 return\
2998end\
2999\
3000if action == 'list' then\
3001 for k in pairs(Packages:list()) do\
3002 Util.print('[%s] %s', Packages:isInstalled(k) and 'x' or ' ', k)\
3003 end\
3004 return\
3005end\
3006\
3007if action == 'install' then\
3008 local name = args[1] or Syntax('Invalid package')\
3009 if Packages:isInstalled(name) then\
3010 error('Package is already installed')\
3011 end\
3012 install(name)\
3013 print('installation complete')\
3014 return\
3015end\
3016\
3017if action == 'update' then\
3018 local name = args[1] or Syntax('Invalid package')\
3019 if not Packages:isInstalled(name) then\
3020 error('Package is not installed')\
3021 end\
3022 install(name)\
3023 print('update complete')\
3024 return\
3025end\
3026\
3027if action == 'uninstall' then\
3028 local name = args[1] or Syntax('Invalid package')\
3029 if not Packages:isInstalled(name) then\
3030 error('Package is not installed')\
3031 end\
3032 local packageDir = fs.combine('packages', name)\
3033 fs.delete(packageDir)\
3034 print('removed: ' .. packageDir)\
3035 return\
3036end\
3037\
3038Syntax('Invalid command')",
3039 [ "vnc.lua" ] = "_G.requireInjector(_ENV)\
3040\
3041local Event = require('event')\
3042local Socket = require('socket')\
3043local Terminal = require('terminal')\
3044local Util = require('util')\
3045\
3046local colors = _G.colors\
3047local multishell = _ENV.multishell\
3048local os = _G.os\
3049local term = _G.term\
3050\
3051local remoteId\
3052local args = { ... }\
3053if #args == 1 then\
3054 remoteId = tonumber(args[1])\
3055else\
3056 print('Enter host ID')\
3057 remoteId = tonumber(_G.read())\
3058end\
3059\
3060if not remoteId then\
3061 error('Syntax: vnc <host ID>')\
3062end\
3063\
3064if multishell then\
3065 multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId)\
3066end\
3067\
3068local function connect()\
3069 local socket, msg = Socket.connect(remoteId, 5900)\
3070\
3071 if not socket then\
3072 return false, msg\
3073 end\
3074\
3075 local function writeTermInfo()\
3076 local w, h = term.getSize()\
3077 socket:write({\
3078 type = 'termInfo',\
3079 width = w,\
3080 height = h,\
3081 isColor = term.isColor(),\
3082 })\
3083 end\
3084\
3085 writeTermInfo()\
3086\
3087 local ct = Util.shallowCopy(term.current())\
3088\
3089 if not ct.isColor() then\
3090 Terminal.toGrayscale(ct)\
3091 end\
3092\
3093 ct.clear()\
3094 ct.setCursorPos(1, 1)\
3095\
3096 Event.addRoutine(function()\
3097 while true do\
3098 local data = socket:read()\
3099 if not data then\
3100 break\
3101 end\
3102 for _,v in ipairs(data) do\
3103 ct[v.f](unpack(v.args))\
3104 end\
3105 end\
3106 end)\
3107\
3108 local filter = Util.transpose({\
3109 'char', 'paste', 'key', 'key_up',\
3110 'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up',\
3111 })\
3112\
3113 while true do\
3114 local e = Event.pullEvent()\
3115 local event = e[1]\
3116\
3117 if not socket.connected then\
3118 break\
3119 end\
3120\
3121 if filter[event] then\
3122 socket:write({\
3123 type = 'shellRemote',\
3124 event = e,\
3125 })\
3126 elseif event == 'term_resize' then\
3127 writeTermInfo()\
3128 elseif event == 'terminate' then\
3129 socket:close()\
3130 ct.setBackgroundColor(colors.black)\
3131 ct.clear()\
3132 ct.setCursorPos(1, 1)\
3133 return true\
3134 end\
3135 end\
3136 return false, \"Connection Lost\"\
3137end\
3138\
3139while true do\
3140 term.clear()\
3141 term.setCursorPos(1, 1)\
3142\
3143 print('connecting...')\
3144 local s, m = connect()\
3145 if s then\
3146 break\
3147 end\
3148\
3149 term.setBackgroundColor(colors.black)\
3150 term.setTextColor(colors.white)\
3151 term.clear()\
3152 term.setCursorPos(1, 1)\
3153 print(m)\
3154 print('\\nPress any key to exit')\
3155 print('\\nRetrying in ... ')\
3156 local x, y = term.getCursorPos()\
3157 for i = 5, 1, -1 do\
3158 local timerId = os.startTimer(1)\
3159 term.setCursorPos(x, y)\
3160 term.write(i)\
3161 repeat\
3162 local e, id = os.pullEvent()\
3163 if e == 'char' or e == 'key' then\
3164 return\
3165 end\
3166 until e == 'timer' and id == timerId\
3167 end\
3168end",
3169 [ "Overview.lua" ] = "_G.requireInjector(_ENV)\
3170\
3171local class = require('class')\
3172local Config = require('config')\
3173local Event = require('event')\
3174local FileUI = require('ui.fileui')\
3175local NFT = require('nft')\
3176local Packages = require('packages')\
3177local SHA1 = require('sha1')\
3178local Tween = require('ui.tween')\
3179local UI = require('ui')\
3180local Util = require('util')\
3181\
3182local colors = _G.colors\
3183local fs = _G.fs\
3184local pocket = _G.pocket\
3185local shell = _ENV.shell\
3186local term = _G.term\
3187local turtle = _G.turtle\
3188\
3189if not _ENV.multishell then\
3190 error('multishell is required')\
3191end\
3192\
3193local REGISTRY_DIR = '/sys/os/opus/usr/.registry'\
3194\
3195UI:configure('Overview', ...)\
3196\
3197local config = {\
3198 Recent = { },\
3199 currentCategory = 'Apps',\
3200}\
3201Config.load('Overview', config)\
3202\
3203local applications = { }\
3204local extSupport = Util.getVersion() >= 1.76\
3205\
3206local function loadApplications()\
3207 local requirements = {\
3208 turtle = not not turtle,\
3209 advancedTurtle = turtle and term.isColor(),\
3210 advanced = term.isColor(),\
3211 pocket = not not pocket,\
3212 advancedPocket = pocket and term.isColor(),\
3213 advancedComputer = not turtle and not pocket and term.isColor(),\
3214 }\
3215\
3216 applications = Util.readTable('sys/os/opus/sys/etc/app.db')\
3217\
3218 for dir in pairs(Packages:installed()) do\
3219 local path = fs.combine('packages/' .. dir, 'etc/apps')\
3220 if fs.exists(path) then\
3221 local dbs = fs.list(path)\
3222 for _, db in pairs(dbs) do\
3223 local apps = Util.readTable(fs.combine(path, db)) or { }\
3224 Util.merge(applications, apps)\
3225 end\
3226 end\
3227 end\
3228\
3229 if fs.exists(REGISTRY_DIR) then\
3230 local files = fs.list(REGISTRY_DIR)\
3231 for _,file in pairs(files) do\
3232 local app = Util.readTable(fs.combine(REGISTRY_DIR, file))\
3233 if app and app.key then\
3234 app.filename = fs.combine(REGISTRY_DIR, file)\
3235 applications[app.key] = app\
3236 end\
3237 end\
3238 end\
3239\
3240 Util.each(applications, function(v, k) v.key = k end)\
3241 applications = Util.filter(applications, function(a)\
3242 if a.disabled then\
3243 return false\
3244 end\
3245\
3246 if a.requires then\
3247 return requirements[a.requires]\
3248 end\
3249\
3250 return true -- Util.startsWith(a.run, 'http') or shell.resolveProgram(a.run)\
3251 end)\
3252end\
3253\
3254loadApplications()\
3255\
3256local defaultIcon = NFT.parse(\"\\03180\\031711\\03180\\\
3257\\031800\\03171\\03180\\\
3258\\03171\\031800\\03171\")\
3259\
3260local sx, sy = term.current().getSize()\
3261local maxRecent = math.ceil(sx * sy / 62)\
3262\
3263local function elipse(s, len)\
3264 if #s > len then\
3265 s = s:sub(1, len - 2) .. '..'\
3266 end\
3267 return s\
3268end\
3269\
3270local buttons = { }\
3271local categories = { }\
3272for _,f in pairs(applications) do\
3273 if not categories[f.category] then\
3274 categories[f.category] = true\
3275 table.insert(buttons, { text = f.category })\
3276 end\
3277end\
3278table.sort(buttons, function(a, b) return a.text < b.text end)\
3279table.insert(buttons, 1, { text = 'Recent' })\
3280table.insert(buttons, { text = '+', event = 'new' })\
3281\
3282local function parseIcon(iconText)\
3283 local icon\
3284\
3285 local s, m = pcall(function()\
3286 icon = NFT.parse(iconText)\
3287 if icon then\
3288 if icon.height > 3 or icon.width > 8 then\
3289 error('Must be an NFT image - 3 rows, 8 cols max')\
3290 end\
3291 end\
3292 return icon\
3293 end)\
3294\
3295 if s then\
3296 return icon\
3297 end\
3298\
3299 return s, m\
3300end\
3301\
3302UI.VerticalTabBar = class(UI.TabBar)\
3303function UI.VerticalTabBar:setParent()\
3304 self.x = 1\
3305 self.width = 8\
3306 self.height = nil\
3307 self.ey = -1\
3308 UI.TabBar.setParent(self)\
3309 for k,c in pairs(self.children) do\
3310 c.x = 1\
3311 c.y = k + 1\
3312 c.ox, c.oy = c.x, c.y\
3313 c.ow = 8\
3314 c.width = 8\
3315 end\
3316end\
3317\
3318local cx = 9\
3319local cy = 1\
3320if sx < 30 then\
3321 UI.VerticalTabBar = UI.TabBar\
3322 cx = 1\
3323 cy = 2\
3324end\
3325\
3326local page = UI.Page {\
3327 tabBar = UI.VerticalTabBar {\
3328 buttons = buttons,\
3329 },\
3330 container = UI.Viewport {\
3331 x = cx,\
3332 y = cy,\
3333 },\
3334 notification = UI.Notification(),\
3335 accelerators = {\
3336 r = 'refresh',\
3337 e = 'edit',\
3338 f = 'files',\
3339 s = 'shell',\
3340 l = 'lua',\
3341 [ 'control-n' ] = 'new',\
3342 delete = 'delete',\
3343 },\
3344}\
3345\
3346if extSupport then\
3347 page.container.backgroundColor = colors.black\
3348end\
3349\
3350UI.Icon = class(UI.Window)\
3351UI.Icon.defaults = {\
3352 UIElement = 'Icon',\
3353 width = 14,\
3354 height = 4,\
3355}\
3356function UI.Icon:eventHandler(event)\
3357 if event.type == 'mouse_click' then\
3358 self:setFocus(self.button)\
3359 return true\
3360 elseif event.type == 'mouse_doubleclick' then\
3361 self:emit({ type = self.button.event, button = self.button })\
3362 elseif event.type == 'mouse_rightclick' then\
3363 self:setFocus(self.button)\
3364 self:emit({ type = 'edit', button = self.button })\
3365 end\
3366 return UI.Window.eventHandler(self, event)\
3367end\
3368\
3369function page.container:setCategory(categoryName, animate)\
3370 -- reset the viewport window\
3371 self.children = { }\
3372 self.offy = 0\
3373\
3374 local function filter(it, f)\
3375 local ot = { }\
3376 for _,v in pairs(it) do\
3377 if f(v) then\
3378 table.insert(ot, v)\
3379 end\
3380 end\
3381 return ot\
3382 end\
3383\
3384 local filtered\
3385\
3386 if categoryName == 'Recent' then\
3387 filtered = { }\
3388\
3389 for _,v in ipairs(config.Recent) do\
3390 local app = Util.find(applications, 'key', v)\
3391 if app then -- and fs.exists(app.run) then\
3392 table.insert(filtered, app)\
3393 end\
3394 end\
3395\
3396 else\
3397 filtered = filter(applications, function(a)\
3398 return a.category == categoryName -- and fs.exists(a.run)\
3399 end)\
3400 table.sort(filtered, function(a, b) return a.title < b.title end)\
3401 end\
3402\
3403 for _,program in ipairs(filtered) do\
3404\
3405 local icon\
3406 if extSupport and program.iconExt then\
3407 icon = parseIcon(program.iconExt)\
3408 end\
3409 if not icon and program.icon then\
3410 icon = parseIcon(program.icon)\
3411 end\
3412 if not icon then\
3413 icon = defaultIcon\
3414 end\
3415\
3416 local title = elipse(program.title, 8)\
3417\
3418 local width = math.max(icon.width + 2, #title + 2)\
3419 table.insert(self.children, UI.Icon({\
3420 width = width,\
3421 image = UI.NftImage({\
3422 x = math.floor((width - icon.width) / 2) + 1,\
3423 image = icon,\
3424 width = 5,\
3425 height = 3,\
3426 }),\
3427 button = UI.Button({\
3428 x = math.floor((width - #title - 2) / 2) + 1,\
3429 y = 4,\
3430 text = title,\
3431 backgroundColor = self.backgroundColor,\
3432 backgroundFocusColor = colors.gray,\
3433 textColor = colors.white,\
3434 textFocusColor = colors.white,\
3435 width = #title + 2,\
3436 event = 'button',\
3437 app = program,\
3438 }),\
3439 }))\
3440 end\
3441\
3442 local gutter = 2\
3443 if UI.term.width <= 26 then\
3444 gutter = 1\
3445 end\
3446 local col, row = gutter, 2\
3447 local count = #self.children\
3448\
3449 local r = math.random(1, 5)\
3450 -- reposition all children\
3451 for k,child in ipairs(self.children) do\
3452 if r == 1 then\
3453 child.x = math.random(1, self.width)\
3454 child.y = math.random(1, self.height)\
3455 elseif r == 2 then\
3456 child.x = self.width\
3457 child.y = self.height\
3458 elseif r == 3 then\
3459 child.x = math.floor(self.width / 2)\
3460 child.y = math.floor(self.height / 2)\
3461 elseif r == 4 then\
3462 child.x = self.width - col\
3463 child.y = row\
3464 elseif r == 5 then\
3465 child.x = col\
3466 child.y = row\
3467 if k == #self.children then\
3468 child.x = self.width\
3469 child.y = self.height\
3470 end\
3471 end\
3472 child.tween = Tween.new(6, child, { x = col, y = row }, 'linear')\
3473\
3474 if not animate then\
3475 child.x = col\
3476 child.y = row\
3477 end\
3478\
3479 if k < count then\
3480 col = col + child.width\
3481 if col + self.children[k + 1].width + gutter - 2 > self.width then\
3482 col = gutter\
3483 row = row + 5\
3484 end\
3485 end\
3486 end\
3487\
3488 self:initChildren()\
3489 if animate then -- need to fix transitions under layers\
3490 local function transition(args)\
3491 local i = 1\
3492 return function(device)\
3493 self:clear()\
3494 for _,child in pairs(self.children) do\
3495 child.tween:update(1)\
3496 child.x = math.floor(child.x)\
3497 child.y = math.floor(child.y)\
3498 child:draw()\
3499 end\
3500 args.canvas:blit(device, args, args)\
3501 i = i + 1\
3502 return i < 7\
3503 end\
3504 end\
3505 self:addTransition(transition)\
3506 end\
3507end\
3508\
3509function page:refresh()\
3510 local pos = self.container.offy\
3511 self:focusFirst(self)\
3512 self.container:setCategory(config.currentCategory)\
3513 self.container:setScrollPosition(pos)\
3514end\
3515\
3516function page:resize()\
3517 UI.Page.resize(self)\
3518 self:refresh()\
3519end\
3520\
3521function page:eventHandler(event)\
3522 if event.type == 'tab_select' then\
3523 self.container:setCategory(event.button.text, true)\
3524 self.container:draw()\
3525\
3526 config.currentCategory = event.button.text\
3527 Config.update('Overview', config)\
3528\
3529 elseif event.type == 'button' then\
3530 for k,v in ipairs(config.Recent) do\
3531 if v == event.button.app.key then\
3532 table.remove(config.Recent, k)\
3533 break\
3534 end\
3535 end\
3536 table.insert(config.Recent, 1, event.button.app.key)\
3537 if #config.Recent > maxRecent then\
3538 table.remove(config.Recent, maxRecent + 1)\
3539 end\
3540 Config.update('Overview', config)\
3541 shell.switchTab(shell.openTab(event.button.app.run))\
3542\
3543 elseif event.type == 'shell' then\
3544 shell.switchTab(shell.openTab('sys/os/opus/sys/apps/shell'))\
3545\
3546 elseif event.type == 'lua' then\
3547 shell.switchTab(shell.openTab('sys/os/opus/sys/apps/Lua.lua'))\
3548\
3549 elseif event.type == 'files' then\
3550 shell.switchTab(shell.openTab('sys/os/opus/sys/apps/Files.lua'))\
3551\
3552 elseif event.type == 'focus_change' then\
3553 if event.focused.parent.UIElement == 'Icon' then\
3554 event.focused.parent:scrollIntoView()\
3555 end\
3556\
3557 elseif event.type == 'refresh' then -- remove this after fixing notification\
3558 loadApplications()\
3559 self:refresh()\
3560 self:draw()\
3561 self.notification:success('Refreshed')\
3562\
3563 elseif event.type == 'delete' then\
3564 local focused = page:getFocused()\
3565 if focused.app then\
3566 focused.app.disabled = true\
3567 local filename = focused.app.filename or fs.combine(REGISTRY_DIR, focused.app.key)\
3568 Util.writeTable(filename, focused.app)\
3569 loadApplications()\
3570 page:refresh()\
3571 page:draw()\
3572 self.notification:success('Removed')\
3573 end\
3574\
3575 elseif event.type == 'new' then\
3576 local category = 'Apps'\
3577 if config.currentCategory ~= 'Recent' then\
3578 category = config.currentCategory or 'Apps'\
3579 end\
3580 UI:setPage('editor', { category = category })\
3581\
3582 elseif event.type == 'edit' then\
3583 local focused = page:getFocused()\
3584 if focused.app then\
3585 UI:setPage('editor', focused.app)\
3586 end\
3587\
3588 else\
3589 UI.Page.eventHandler(self, event)\
3590 end\
3591 return true\
3592end\
3593\
3594local formWidth = math.max(UI.term.width - 8, 26)\
3595\
3596local editor = UI.Dialog {\
3597 height = 11,\
3598 width = formWidth,\
3599 title = 'Edit Application',\
3600 form = UI.Form {\
3601 y = 2,\
3602 height = 9,\
3603 title = UI.TextEntry {\
3604 formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title',\
3605 required = true,\
3606 },\
3607 run = UI.TextEntry {\
3608 formLabel = 'Run', formKey = 'run', limit = 100, help = 'Full path to application',\
3609 required = true,\
3610 },\
3611 category = UI.TextEntry {\
3612 formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application',\
3613 required = true,\
3614 },\
3615 loadIcon = UI.Button {\
3616 x = 11, y = 6,\
3617 text = 'Icon', event = 'loadIcon', help = 'Select icon'\
3618 },\
3619 image = UI.NftImage {\
3620 y = 6, x = 2, height = 3, width = 8,\
3621 },\
3622 },\
3623 statusBar = UI.StatusBar(),\
3624 iconFile = '',\
3625}\
3626\
3627function editor:enable(app)\
3628 if app then\
3629 self.form:setValues(app)\
3630\
3631 local icon\
3632 if extSupport and app.iconExt then\
3633 icon = parseIcon(app.iconExt)\
3634 end\
3635 if not icon and app.icon then\
3636 icon = parseIcon(app.icon)\
3637 end\
3638 self.form.image:setImage(icon)\
3639 end\
3640 UI.Dialog.enable(self)\
3641 self:focusFirst()\
3642end\
3643\
3644function editor.form.image:draw()\
3645 self:clear()\
3646 UI.NftImage.draw(self)\
3647end\
3648\
3649function editor:updateApplications(app)\
3650 if not app.key then\
3651 app.key = SHA1.sha1(app.title)\
3652 end\
3653 local filename = app.filename or fs.combine(REGISTRY_DIR, app.key)\
3654 Util.writeTable(filename, app)\
3655 loadApplications()\
3656end\
3657\
3658function editor:eventHandler(event)\
3659 if event.type == 'form_cancel' or event.type == 'cancel' then\
3660 UI:setPreviousPage()\
3661\
3662 elseif event.type == 'focus_change' then\
3663 self.statusBar:setStatus(event.focused.help or '')\
3664 self.statusBar:draw()\
3665\
3666 elseif event.type == 'loadIcon' then\
3667 local fileui = FileUI({\
3668 x = self.x,\
3669 y = self.y,\
3670 z = 2,\
3671 width = self.width,\
3672 height = self.height,\
3673 })\
3674 UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName)\
3675 if fileName then\
3676 self.iconFile = fileName\
3677 local s, m = pcall(function()\
3678 local iconLines = Util.readFile(fileName)\
3679 if not iconLines then\
3680 error('Must be an NFT image - 3 rows, 8 cols max')\
3681 end\
3682 local icon, m = parseIcon(iconLines)\
3683 if not icon then\
3684 error(m)\
3685 end\
3686 if extSupport then\
3687 self.form.values.iconExt = iconLines\
3688 else\
3689 self.form.values.icon = iconLines\
3690 end\
3691 self.form.image:setImage(icon)\
3692 self.form.image:draw()\
3693 end)\
3694 if not s and m then\
3695 local msg = m:gsub('.*: (.*)', '%1')\
3696 page.notification:error(msg)\
3697 end\
3698 end\
3699 end)\
3700\
3701 elseif event.type == 'form_invalid' then\
3702 page.notification:error(event.message)\
3703\
3704 elseif event.type == 'form_complete' then\
3705 local values = self.form.values\
3706 UI:setPreviousPage()\
3707 self:updateApplications(values)\
3708 page:refresh()\
3709 page:draw()\
3710 else\
3711 return UI.Dialog.eventHandler(self, event)\
3712 end\
3713 return true\
3714end\
3715\
3716UI:setPages({\
3717 editor = editor,\
3718 main = page,\
3719})\
3720\
3721Event.on('os_register_app', function()\
3722 loadApplications()\
3723 page:refresh()\
3724 page:draw()\
3725 page:sync()\
3726end)\
3727\
3728page.tabBar:selectTab(config.currentCategory or 'Apps')\
3729page.container:setCategory(config.currentCategory or 'Apps')\
3730UI:setPage(page)\
3731\
3732UI:pullEvents()",
3733 [ "Installer.lua" ] = "local colors = _G.colors\
3734local fs = _G.fs\
3735local http = _G.http\
3736local install = _ENV.install\
3737local os = _G.os\
3738\
3739local injector\
3740if not install.testing then\
3741 _G.OPUS_BRANCH = 'master-1.8'\
3742 local url ='https://raw.githubusercontent.com/kepler155c/opus/master-1.8/sys/apis/injector.lua'\
3743 injector = load(http.get(url).readAll(), 'injector.lua', nil, _ENV)()\
3744else\
3745 injector = _G.requireInjector\
3746end\
3747\
3748injector(_ENV)\
3749\
3750if not install.testing then\
3751 if package then\
3752 for _ = 1, 4 do\
3753 table.remove(package.loaders, 1)\
3754 end\
3755 end\
3756end\
3757\
3758local Git = require('git')\
3759local UI = require('ui')\
3760local Util = require('util')\
3761\
3762local currentFile = ''\
3763local currentProgress = 0\
3764local cancelEvent\
3765\
3766local args = { ... }\
3767local steps = install.steps[args[1] or 'install']\
3768\
3769if not steps then\
3770 error('Invalid install type')\
3771end\
3772\
3773local mode = steps[#steps]\
3774\
3775if UI.term.width < 32 then\
3776 cancelEvent = 'quit'\
3777end\
3778\
3779local page = UI.Page {\
3780 backgroundColor = colors.cyan,\
3781 titleBar = UI.TitleBar {\
3782 event = cancelEvent,\
3783 },\
3784 wizard = UI.Wizard {\
3785 y = 2, ey = -2,\
3786 },\
3787 notification = UI.Notification(),\
3788 accelerators = {\
3789 q = 'quit',\
3790 },\
3791}\
3792\
3793local pages = {\
3794 splash = UI.Viewport { },\
3795 review = UI.Viewport { },\
3796 license = UI.Viewport {\
3797 backgroundColor = colors.black,\
3798 },\
3799 branch = UI.Window {\
3800 grid = UI.ScrollingGrid {\
3801 ey = -3,\
3802 columns = {\
3803 { heading = 'Branch', key = 'branch' },\
3804 { heading = 'Description', key = 'description' },\
3805 },\
3806 values = install.branches,\
3807 autospace = true,\
3808 },\
3809 },\
3810 files = UI.Window {\
3811 grid = UI.ScrollingGrid {\
3812 ey = -3,\
3813 columns = {\
3814 { heading = 'Files', key = 'file' },\
3815 },\
3816 sortColumn = 'file',\
3817 },\
3818 },\
3819 install = UI.Window {\
3820 progressBar = UI.ProgressBar {\
3821 y = -1,\
3822 },\
3823 },\
3824 uninstall = UI.Window {\
3825 progressBar = UI.ProgressBar {\
3826 y = -1,\
3827 },\
3828 },\
3829}\
3830\
3831local function getFileList()\
3832 if install.gitRepo then\
3833 local gitFiles = Git.list(string.format('%s/%s', install.gitRepo, install.gitBranch or 'master'))\
3834 install.files = { }\
3835 install.diskspace = 0\
3836 for path, entry in pairs(gitFiles) do\
3837 install.files[path] = entry.url\
3838 install.diskspace = install.diskspace + entry.size\
3839 end\
3840 end\
3841\
3842 if not install.files or Util.empty(install.files) then\
3843 error('File list is missing or empty')\
3844 end\
3845end\
3846\
3847--[[ Splash ]]--\
3848function pages.splash:enable()\
3849 page.titleBar.title = 'Installer v1.0'\
3850 UI.Viewport.enable(self)\
3851end\
3852\
3853function pages.splash:draw()\
3854 self:clear()\
3855 self:setCursorPos(1, 1)\
3856 self:print(\
3857 string.format('%s v%s\\n', install.title, install.version), nil, colors.yellow)\
3858 self:print(\
3859 string.format('By: %s\\n\\n%s\\n', install.author, install.description))\
3860\
3861 self.ymax = self.cursorY\
3862end\
3863\
3864--[[ License ]]--\
3865function pages.license:enable()\
3866 page.titleBar.title = 'License Review'\
3867 page.wizard.nextButton.text = 'Accept'\
3868 UI.Viewport.enable(self)\
3869end\
3870\
3871function pages.license:draw()\
3872 self:clear()\
3873 self:setCursorPos(1, 1)\
3874 self:print(\
3875 string.format('Copyright (c) %s %s\\n\\n', install.copyrightYear,\
3876 install.copyrightHolders),\
3877 nil, colors.yellow)\
3878 self:print(install.license)\
3879\
3880 self.ymax = self.cursorY + 1\
3881end\
3882\
3883--[[ Review ]]--\
3884function pages.review:enable()\
3885 if mode == 'uninstall' then\
3886 page.nextButton.text = 'Remove'\
3887 page.titleBar.title = 'Remove Installed Files'\
3888 else\
3889 page.wizard.nextButton.text = 'Begin'\
3890 page.titleBar.title = 'Download and Install'\
3891 end\
3892 UI.Viewport.enable(self)\
3893end\
3894\
3895function pages.review:draw()\
3896 self:clear()\
3897 self:setCursorPos(1, 1)\
3898\
3899 local text = 'Ready to begin installation.\\n\\nProceeding will download and install the files to the hard drive.'\
3900 if mode == 'uninstall' then\
3901 text = 'Ready to begin.\\n\\nProceeding will remove the files previously installed.'\
3902 end\
3903 self:print(text)\
3904\
3905 self.ymax = self.cursorY + 1\
3906end\
3907\
3908--[[ Files ]]--\
3909function pages.files:enable()\
3910 page.titleBar.title = 'Review Files'\
3911 self.grid.values = { }\
3912 for k,v in pairs(install.files) do\
3913 table.insert(self.grid.values, { file = k, code = v })\
3914 end\
3915 self.grid:update()\
3916 UI.Window.enable(self)\
3917end\
3918\
3919function pages.files:draw()\
3920 self:clear()\
3921\
3922 local function formatSize(size)\
3923 if size >= 1000000 then\
3924 return string.format('%dM', math.floor(size/1000000, 2))\
3925 elseif size >= 1000 then\
3926 return string.format('%dK', math.floor(size/1000, 2))\
3927 end\
3928 return size\
3929 end\
3930\
3931 if install.diskspace then\
3932\
3933 local bg = self.backgroundColor\
3934\
3935 local diskFree = fs.getFreeSpace('/')\
3936 if install.diskspace > diskFree then\
3937 bg = colors.red\
3938 end\
3939\
3940 local text = string.format('Space Required: %s, Free: %s',\
3941 formatSize(install.diskspace), formatSize(diskFree))\
3942\
3943 if #text > self.width then\
3944 text = string.format('Space: %s Free: %s',\
3945 formatSize(install.diskspace), formatSize(diskFree))\
3946 end\
3947\
3948 self:write(1, self.height, Util.widthify(text, self.width), bg)\
3949 end\
3950 self.grid:draw()\
3951end\
3952\
3953--[[\
3954function pages.files:view(url)\
3955 local s, m = pcall(function()\
3956 page.notification:info('Downloading')\
3957 page:sync()\
3958 Util.download(url, '/.source')\
3959 end)\
3960 page.notification:disable()\
3961 if s then\
3962 shell.run('edit /.source')\
3963 fs.delete('/.source')\
3964 page:draw()\
3965 page.notification:cancel()\
3966 else\
3967 page.notification:error(m:gsub('.*: (.*)', '%1'))\
3968 end\
3969end\
3970\
3971function pages.files:eventHandler(event)\
3972 if event.type == 'grid_select' then\
3973 self:view(event.selected.code)\
3974 return true\
3975 end\
3976end\
3977--]]\
3978\
3979local function drawCommon(self)\
3980 if currentFile then\
3981 self:write(1, 3, 'File:')\
3982 self:write(1, 4, Util.widthify(currentFile, self.width))\
3983 else\
3984 self:write(1, 3, 'Finished')\
3985 end\
3986 if self.failed then\
3987 self:write(1, 5, Util.widthify(self.failed, self.width), colors.red)\
3988 end\
3989 self:write(1, self.height - 1, 'Progress')\
3990\
3991 self.progressBar.value = currentProgress\
3992 self.progressBar:draw()\
3993 self:sync()\
3994end\
3995\
3996--[[ Branch ]]--\
3997function pages.branch:enable()\
3998 page.titleBar.title = 'Select Branch'\
3999 UI.Window.enable(self)\
4000end\
4001\
4002function pages.branch:eventHandler(event)\
4003 -- user is navigating to next view (not previous)\
4004 if event.type == 'enable_view' and event.next then\
4005 install.gitBranch = self.grid:getSelected().branch\
4006 getFileList()\
4007 end\
4008end\
4009\
4010--[[ Install ]]--\
4011function pages.install:enable()\
4012 page.wizard.cancelButton:disable()\
4013 page.wizard.previousButton:disable()\
4014 page.wizard.nextButton:disable()\
4015\
4016 page.titleBar.title = 'Installing...'\
4017 page.titleBar.event = nil\
4018\
4019 UI.Window.enable(self)\
4020\
4021 page:draw()\
4022 page:sync()\
4023\
4024 local i = 0\
4025 local numFiles = Util.size(install.files)\
4026 for filename,url in pairs(install.files) do\
4027 currentFile = filename\
4028 currentProgress = i / numFiles * 100\
4029 self:draw(self)\
4030 self:sync()\
4031 local s, m = pcall(function()\
4032 Util.download(url, fs.combine(install.directory or '', filename))\
4033 end)\
4034 if not s then\
4035 self.failed = m:gsub('.*: (.*)', '%1')\
4036 break\
4037 end\
4038 i = i + 1\
4039 end\
4040\
4041 if not self.failed then\
4042 currentProgress = 100\
4043 currentFile = nil\
4044\
4045 if install.postInstall then\
4046 local s, m = pcall(function() install.postInstall(page, UI) end)\
4047 if not s then\
4048 self.failed = m:gsub('.*: (.*)', '%1')\
4049 end\
4050 end\
4051 end\
4052\
4053 page.wizard.nextButton.text = 'Exit'\
4054 page.wizard.nextButton.event = 'quit'\
4055 if not self.failed and install.rebootAfter then\
4056 page.wizard.nextButton.text = 'Reboot'\
4057 page.wizard.nextButton.event = 'reboot'\
4058 end\
4059\
4060 page.wizard.nextButton:enable()\
4061 page:draw()\
4062 page:sync()\
4063\
4064 if not self.failed and Util.key(args, 'automatic') then\
4065 if install.rebootAfter then\
4066 os.reboot()\
4067 else\
4068 UI:exitPullEvents()\
4069 end\
4070 end\
4071end\
4072\
4073function pages.install:draw()\
4074 self:clear()\
4075 local text = 'The files are being installed'\
4076 if #text > self.width then\
4077 text = 'Installing files'\
4078 end\
4079 self:write(1, 1, text, nil, colors.yellow)\
4080\
4081 drawCommon(self)\
4082end\
4083\
4084--[[ Uninstall ]]--\
4085function pages.uninstall:enable()\
4086 page.wizard.cancelButton:disable()\
4087 page.wizard.previousButton:disable()\
4088 page.wizard.nextButton:disable()\
4089\
4090 page.titleBar.title = 'Uninstalling...'\
4091 page.titleBar.event = nil\
4092\
4093 page:draw()\
4094 page:sync()\
4095\
4096 UI.Window.enable(self)\
4097\
4098 local function pruneDir(dir)\
4099 if #dir > 0 then\
4100 if fs.exists(dir) then\
4101 local files = fs.list(dir)\
4102 if #files == 0 then\
4103 fs.delete(dir)\
4104 pruneDir(fs.getDir(dir))\
4105 end\
4106 end\
4107 end\
4108 end\
4109\
4110 local i = 0\
4111 local numFiles = Util.size(install.files)\
4112 for k in pairs(install.files) do\
4113 currentFile = k\
4114 currentProgress = i / numFiles * 100\
4115 self:draw()\
4116 self:sync()\
4117 fs.delete(k)\
4118 pruneDir(fs.getDir(k))\
4119 i = i + 1\
4120 end\
4121\
4122 currentProgress = 100\
4123 currentFile = nil\
4124\
4125 page.wizard.nextButton.text = 'Exit'\
4126 page.wizard.nextButton.event = 'quit'\
4127 page.wizard.nextButton:enable()\
4128\
4129 page:draw()\
4130 page:sync()\
4131end\
4132\
4133function pages.uninstall:draw()\
4134 self:clear()\
4135 self:write(1, 1, 'Uninstalling files', nil, colors.yellow)\
4136 drawCommon(self)\
4137end\
4138\
4139function page:eventHandler(event)\
4140 if event.type == 'cancel' then\
4141 UI:exitPullEvents()\
4142\
4143 elseif event.type == 'reboot' then\
4144 os.reboot()\
4145\
4146 elseif event.type == 'quit' then\
4147 UI:exitPullEvents()\
4148\
4149 else\
4150 return UI.Page.eventHandler(self, event)\
4151 end\
4152 return true\
4153end\
4154\
4155function page:enable()\
4156 UI.Page.enable(self)\
4157 self:setFocus(page.wizard.nextButton)\
4158 if UI.term.width < 32 then\
4159 page.wizard.cancelButton:disable()\
4160 page.wizard.previousButton.x = 2\
4161 end\
4162end\
4163\
4164getFileList()\
4165\
4166local wizardPages = { }\
4167for k,v in ipairs(steps) do\
4168 if not pages[v] then\
4169 error('Invalid step: ' .. v)\
4170 end\
4171 wizardPages[k] = pages[v]\
4172 wizardPages[k].index = k\
4173 wizardPages[k].x = 2\
4174 wizardPages[k].y = 2\
4175 wizardPages[k].ey = -3\
4176 wizardPages[k].ex = -2\
4177end\
4178page.wizard:add(wizardPages)\
4179\
4180if Util.key(steps, 'install') and install.preInstall then\
4181 install.preInstall(page, UI)\
4182end\
4183\
4184UI:setPage(page)\
4185local s, m = pcall(function() UI:pullEvents() end)\
4186if not s then\
4187 UI.term:reset()\
4188 _G.printError(m)\
4189end",
4190 shell = "local parentShell = _ENV.shell\
4191\
4192_ENV.shell = {}\
4193\
4194local fs = _G.fs\
4195local shell = _ENV.shell\
4196\
4197local sandboxEnv = setmetatable({}, {__index = _G})\
4198for k, v in pairs(_ENV) do\
4199 sandboxEnv[k] = v\
4200end\
4201sandboxEnv.shell = shell\
4202\
4203_G.requireInjector(_ENV)\
4204\
4205local Util = require('util')\
4206\
4207local DIR = (parentShell and parentShell.dir()) or \"\"\
4208local PATH = (parentShell and parentShell.path()) or \".:/rom/programs\"\
4209local tAliases = (parentShell and parentShell.aliases()) or {}\
4210local tCompletionInfo = (parentShell and parentShell.getCompletionInfo()) or {}\
4211\
4212local bExit = false\
4213local tProgramStack = {}\
4214\
4215local function tokenise(...)\
4216 local sLine = table.concat({...}, \" \")\
4217 local tWords = {}\
4218 local bQuoted = false\
4219 for match in string.gmatch(sLine .. \"\\\"\", \"(.-)\\\"\") do\
4220 if bQuoted then\
4221 table.insert(tWords, match)\
4222 else\
4223 for m in string.gmatch(match, \"[^ \\t]+\") do\
4224 table.insert(tWords, m)\
4225 end\
4226 end\
4227 bQuoted = not bQuoted\
4228 end\
4229 \
4230 return tWords\
4231end\
4232\
4233local function run(env, ...)\
4234 local args = tokenise(...)\
4235 local command = table.remove(args, 1) or error('No such program')\
4236 local isUrl = not not command:match(\"^(https?:)\")\
4237 \
4238 local path, loadFn\
4239 print(command)\
4240 if not string.find(command, \"sys/os/opus/sys\") then\
4241 command = command:gsub(\"sys\", \"sys/os/opus/sys\")\
4242 end\
4243 if isUrl then\
4244 path = command\
4245 loadFn = Util.loadUrl\
4246 else\
4247 path = shell.resolveProgram(command) or error('No such program')\
4248 loadFn = loadfile\
4249 end\
4250 \
4251 local fn, err = loadFn(path, env)\
4252 if not fn then\
4253 error(err)\
4254 end\
4255 \
4256 if _ENV.multishell then\
4257 _ENV.multishell.setTitle(_ENV.multishell.getCurrent(), fs.getName(path):match('([^%.]+)'))\
4258 end\
4259 \
4260 if isUrl then\
4261 tProgramStack[#tProgramStack + 1] = path:match(\"^https?://([^/:]+:?[0-9]*/?.*)$\")\
4262 else\
4263 tProgramStack[#tProgramStack + 1] = path\
4264 end\
4265 \
4266 local r = {fn(table.unpack(args))}\
4267 \
4268 tProgramStack[#tProgramStack] = nil\
4269 \
4270 return table.unpack(r)\
4271end\
4272\
4273-- Install shell API\
4274function shell.run(...)\
4275 local oldTitle\
4276 \
4277 if _ENV.multishell then\
4278 oldTitle = _ENV.multishell.getTitle(_ENV.multishell.getCurrent())\
4279 end\
4280 \
4281 local env = setmetatable(Util.shallowCopy(sandboxEnv), {__index = _G})\
4282 local r = {pcall(run, env, ...)}\
4283 \
4284 if _ENV.multishell then\
4285 _ENV.multishell.setTitle(_ENV.multishell.getCurrent(), oldTitle or 'shell')\
4286 end\
4287 \
4288 return table.unpack(r)\
4289end\
4290\
4291function shell.exit()\
4292 bExit = true\
4293end\
4294\
4295function shell.dir() return DIR end\
4296function shell.setDir(d)DIR = d end\
4297function shell.path() return PATH end\
4298function shell.setPath(p)PATH = p end\
4299\
4300function shell.resolve(_sPath)\
4301 local sStartChar = string.sub(_sPath, 1, 1)\
4302 if sStartChar == \"/\" or sStartChar == \"\\\\\" then\
4303 return fs.combine(\"\", _sPath)\
4304 else\
4305 return fs.combine(DIR, _sPath)\
4306 end\
4307end\
4308\
4309function shell.resolveProgram(_sCommand)\
4310 if tAliases[_sCommand] ~= nil then\
4311 _sCommand = tAliases[_sCommand]\
4312 end\
4313 \
4314 if _sCommand:match(\"^(https?:)\") then\
4315 return _sCommand\
4316 end\
4317 \
4318 local path = shell.resolve(_sCommand)\
4319 if fs.exists(path) and not fs.isDir(path) then\
4320 return path\
4321 end\
4322 if fs.exists(path .. '.lua') then\
4323 return path .. '.lua'\
4324 end\
4325 \
4326 -- If the path is a global path, use it directly\
4327 local sStartChar = string.sub(_sCommand, 1, 1)\
4328 if sStartChar == \"/\" or sStartChar == \"\\\\\" then\
4329 local sPath = fs.combine(\"\", _sCommand)\
4330 if fs.exists(sPath) and not fs.isDir(sPath) then\
4331 return sPath\
4332 end\
4333 return nil\
4334 end\
4335 \
4336 -- Otherwise, look on the path variable\
4337 for sPath in string.gmatch(PATH or '', \"[^:]+\") do\
4338 sPath = fs.combine(sPath, _sCommand)\
4339 if fs.exists(sPath) and not fs.isDir(sPath) then\
4340 return sPath\
4341 end\
4342 if fs.exists(sPath .. '.lua') then\
4343 return sPath .. '.lua'\
4344 end\
4345 end\
4346 -- Not found\
4347 return nil\
4348end\
4349\
4350function shell.programs(_bIncludeHidden)\
4351 local tItems = {}\
4352 \
4353 -- Add programs from the path\
4354 for sPath in string.gmatch(PATH, \"[^:]+\") do\
4355 sPath = shell.resolve(sPath)\
4356 if fs.isDir(sPath) then\
4357 local tList = fs.list(sPath)\
4358 for _, sFile in pairs(tList) do\
4359 if not fs.isDir(fs.combine(sPath, sFile)) and\
4360 (_bIncludeHidden or string.sub(sFile, 1, 1) ~= \".\") then\
4361 tItems[sFile] = true\
4362 end\
4363 end\
4364 end\
4365 end\
4366 \
4367 -- Sort and return\
4368 local tItemList = {}\
4369 for sItem in pairs(tItems) do\
4370 table.insert(tItemList, sItem)\
4371 end\
4372 table.sort(tItemList)\
4373 return tItemList\
4374end\
4375\
4376local function completeProgram(sLine)\
4377 if #sLine > 0 and string.sub(sLine, 1, 1) == \"/\" then\
4378 -- Add programs from the root\
4379 return fs.complete(sLine, \"\", true, false)\
4380 else\
4381 local tResults = {}\
4382 local tSeen = {}\
4383 \
4384 -- Add aliases\
4385 for sAlias in pairs(tAliases) do\
4386 if #sAlias > #sLine and string.sub(sAlias, 1, #sLine) == sLine then\
4387 local sResult = string.sub(sAlias, #sLine + 1)\
4388 if not tSeen[sResult] then\
4389 table.insert(tResults, sResult)\
4390 tSeen[sResult] = true\
4391 end\
4392 end\
4393 end\
4394 \
4395 -- Add programs from the path\
4396 local tPrograms = shell.programs()\
4397 for n = 1, #tPrograms do\
4398 local sProgram = tPrograms[n]\
4399 if #sProgram > #sLine and string.sub(sProgram, 1, #sLine) == sLine then\
4400 local sResult = string.sub(sProgram, #sLine + 1)\
4401 if not tSeen[sResult] then\
4402 table.insert(tResults, sResult)\
4403 tSeen[sResult] = true\
4404 end\
4405 end\
4406 end\
4407 \
4408 -- Sort and return\
4409 table.sort(tResults)\
4410 return tResults\
4411 end\
4412end\
4413\
4414local function completeProgramArgument(sProgram, nArgument, sPart, tPreviousParts)\
4415 local tInfo = tCompletionInfo[sProgram]\
4416 if tInfo then\
4417 return tInfo.fnComplete(shell, nArgument, sPart, tPreviousParts)\
4418 end\
4419 return nil\
4420end\
4421\
4422function shell.complete(sLine)\
4423 if #sLine > 0 then\
4424 local tWords = tokenise(sLine)\
4425 local nIndex = #tWords\
4426 if string.sub(sLine, #sLine, #sLine) == \" \" then\
4427 nIndex = nIndex + 1\
4428 end\
4429 if nIndex == 1 then\
4430 local sBit = tWords[1] or \"\"\
4431 local sPath = shell.resolveProgram(sBit)\
4432 if tCompletionInfo[sPath] then\
4433 return {\" \"}\
4434 else\
4435 local tResults = completeProgram(sBit)\
4436 for n = 1, #tResults do\
4437 local sResult = tResults[n]\
4438 local cPath = shell.resolveProgram(sBit .. sResult)\
4439 if tCompletionInfo[cPath] then\
4440 tResults[n] = sResult .. \" \"\
4441 end\
4442 end\
4443 return tResults\
4444 end\
4445 \
4446 elseif nIndex > 1 then\
4447 local sPath = shell.resolveProgram(tWords[1])\
4448 local sPart = tWords[nIndex] or \"\"\
4449 local tPreviousParts = tWords\
4450 tPreviousParts[nIndex] = nil\
4451 return completeProgramArgument(sPath, nIndex - 1, sPart, tPreviousParts)\
4452 end\
4453 end\
4454end\
4455\
4456function shell.completeProgram(sProgram)\
4457 return completeProgram(sProgram)\
4458end\
4459\
4460function shell.setCompletionFunction(sProgram, fnComplete)\
4461 tCompletionInfo[sProgram] = {fnComplete = fnComplete}\
4462end\
4463\
4464function shell.getCompletionInfo()\
4465 return tCompletionInfo\
4466end\
4467\
4468function shell.getRunningProgram()\
4469 return tProgramStack[#tProgramStack]\
4470end\
4471\
4472function shell.setEnv(name, value)\
4473 _ENV[name] = value\
4474 sandboxEnv[name] = value\
4475end\
4476\
4477function shell.getEnv()\
4478 return sandboxEnv\
4479end\
4480\
4481function shell.setAlias(_sCommand, _sProgram)\
4482 tAliases[_sCommand] = _sProgram\
4483end\
4484\
4485function shell.clearAlias(_sCommand)\
4486 tAliases[_sCommand] = nil\
4487end\
4488\
4489function shell.aliases()\
4490 local tCopy = {}\
4491 for sAlias, sCommand in pairs(tAliases) do\
4492 tCopy[sAlias] = sCommand\
4493 end\
4494 return tCopy\
4495end\
4496\
4497function shell.newTab(tabInfo, ...)\
4498 local args = tokenise(...)\
4499 local path = table.remove(args, 1)\
4500 path = shell.resolveProgram(path)\
4501 \
4502 if path then\
4503 tabInfo.path = path\
4504 tabInfo.env = Util.shallowCopy(sandboxEnv)\
4505 tabInfo.args = args\
4506 tabInfo.title = fs.getName(path):match('([^%.]+)')\
4507 \
4508 if path ~= 'sys/os/opus/sys/apps/shell' then\
4509 table.insert(tabInfo.args, 1, tabInfo.path)\
4510 tabInfo.path = 'sys/os/opus/sys/apps/shell'\
4511 end\
4512 return _ENV.multishell.openTab(tabInfo)\
4513 end\
4514 return nil, 'No such program'\
4515end\
4516\
4517function shell.openTab(...)\
4518 -- needs to use multishell.launch .. so we can run with stock multishell\
4519 local tWords = tokenise(...)\
4520 local sCommand = tWords[1]\
4521 if sCommand then\
4522 local sPath = shell.resolveProgram(sCommand)\
4523 if sPath == \"sys/os/opus/sys/apps/shell\" then\
4524 return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), sPath, table.unpack(tWords, 2))\
4525 else\
4526 return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), \"sys/os/opus/sys/apps/shell\", sCommand, table.unpack(tWords, 2))\
4527 end\
4528 end\
4529end\
4530\
4531function shell.openForegroundTab(...)\
4532 return shell.newTab({focused = true}, ...)\
4533end\
4534\
4535function shell.openHiddenTab(...)\
4536 return shell.newTab({hidden = true}, ...)\
4537end\
4538\
4539function shell.switchTab(tabId)\
4540 _ENV.multishell.setFocus(tabId)\
4541end\
4542\
4543local tArgs = {...}\
4544if #tArgs > 0 then\
4545 local env = setmetatable(Util.shallowCopy(sandboxEnv), {__index = _G})\
4546 return run(env, ...)\
4547end\
4548\
4549local Config = require('config')\
4550local Entry = require('entry')\
4551local History = require('history')\
4552local Input = require('input')\
4553local Terminal = require('terminal')\
4554\
4555local colors = _G.colors\
4556local os = _G.os\
4557local term = _G.term\
4558local textutils = _G.textutils\
4559\
4560local terminal = term.current()\
4561--Terminal.scrollable(terminal, 100)\
4562terminal.noAutoScroll = true\
4563\
4564local config = {\
4565 standard = {\
4566 textColor = colors.white,\
4567 commandTextColor = colors.lightGray,\
4568 directoryTextColor = colors.gray,\
4569 directoryBackgroundColor = colors.black,\
4570 promptTextColor = colors.gray,\
4571 promptBackgroundColor = colors.black,\
4572 directoryColor = colors.gray,\
4573 },\
4574 color = {\
4575 textColor = colors.white,\
4576 commandTextColor = colors.yellow,\
4577 directoryTextColor = colors.orange,\
4578 directoryBackgroundColor = colors.black,\
4579 promptTextColor = colors.blue,\
4580 promptBackgroundColor = colors.black,\
4581 directoryColor = colors.green,\
4582 },\
4583 displayDirectory = true,\
4584}\
4585\
4586Config.load('shellprompt', config)\
4587\
4588local _colors = config.standard\
4589if term.isColor() then\
4590 _colors = config.color\
4591end\
4592\
4593local function autocompleteArgument(program, words)\
4594 local word = ''\
4595 if #words > 1 then\
4596 word = words[#words]\
4597 end\
4598 \
4599 local tInfo = tCompletionInfo[program]\
4600 return tInfo.fnComplete(shell, #words - 1, word, words)\
4601end\
4602\
4603local function autocompleteAnything(line, words)\
4604 local results = shell.complete(line)\
4605 \
4606 if results and #results == 0 and #words == 1 then\
4607 results = nil\
4608 end\
4609 if not results then\
4610 results = fs.complete(words[#words] or '', shell.dir(), true, false)\
4611 end\
4612 \
4613 return results\
4614end\
4615\
4616local function autocomplete(line)\
4617 local words = {}\
4618 for word in line:gmatch(\"%S+\") do\
4619 table.insert(words, word)\
4620 end\
4621 if line:match(' $') then\
4622 table.insert(words, '')\
4623 end\
4624 if #words == 0 then\
4625 words = {''}\
4626 end\
4627 \
4628 local results\
4629 \
4630 local program = shell.resolveProgram(words[1])\
4631 if tCompletionInfo[program] then\
4632 results = autocompleteArgument(program, words) or {}\
4633 else\
4634 results = autocompleteAnything(line, words) or {}\
4635 end\
4636 \
4637 Util.filterInplace(results, function(f)\
4638 return not Util.key(results, f .. '/')\
4639 end)\
4640 local w = words[#words] or ''\
4641 for k, arg in pairs(results) do\
4642 results[k] = w .. arg\
4643 end\
4644 \
4645 if #results == 1 then\
4646 words[#words] = results[1]\
4647 return table.concat(words, ' ')\
4648 elseif #results > 1 then\
4649 \
4650 local function someComplete()\
4651 -- ugly (complete as much as possible)\
4652 local word = words[#words] or ''\
4653 local i = #word + 1\
4654 while true do\
4655 local ch\
4656 for _, f in ipairs(results) do\
4657 if #f < i then\
4658 words[#words] = string.sub(f, 1, i - 1)\
4659 return table.concat(words, ' ')\
4660 end\
4661 if not ch then\
4662 ch = string.sub(f, i, i)\
4663 elseif string.sub(f, i, i) ~= ch then\
4664 if i == #word + 1 then\
4665 return\
4666 end\
4667 words[#words] = string.sub(f, 1, i - 1)\
4668 return table.concat(words, ' ')\
4669 end\
4670 end\
4671 i = i + 1\
4672 end\
4673 end\
4674 \
4675 local t = someComplete()\
4676 if t then\
4677 return t\
4678 end\
4679 \
4680 print()\
4681 \
4682 local word = words[#words] or ''\
4683 local prefix = word:match(\"(.*/)\") or ''\
4684 if #prefix > 0 then\
4685 for _, f in ipairs(results) do\
4686 if f:match(\"^\" .. prefix) ~= prefix then\
4687 prefix = ''\
4688 break\
4689 end\
4690 end\
4691 end\
4692 \
4693 local tDirs, tFiles = {}, {}\
4694 for _, f in ipairs(results) do\
4695 if fs.isDir(shell.resolve(f)) then\
4696 f = f:gsub(prefix, '', 1)\
4697 table.insert(tDirs, f)\
4698 else\
4699 f = f:gsub(prefix, '', 1)\
4700 table.insert(tFiles, f)\
4701 end\
4702 end\
4703 table.sort(tDirs)\
4704 table.sort(tFiles)\
4705 \
4706 if #tDirs > 0 and #tDirs < #tFiles then\
4707 local tw = term.getSize()\
4708 local nMaxLen = tw / 8\
4709 for _, sItem in pairs(results) do\
4710 nMaxLen = math.max(string.len(sItem) + 1, nMaxLen)\
4711 end\
4712 local w = term.getSize()\
4713 local nCols = math.floor(w / nMaxLen)\
4714 if #tDirs < nCols then\
4715 for _ = #tDirs + 1, nCols do\
4716 table.insert(tDirs, '')\
4717 end\
4718 end\
4719 end\
4720 \
4721 if #tDirs > 0 then\
4722 textutils.tabulate(_colors.directoryColor, tDirs, colors.white, tFiles)\
4723 else\
4724 textutils.tabulate(colors.white, tFiles)\
4725 end\
4726 \
4727 term.setTextColour(_colors.promptTextColor)\
4728 term.setBackgroundColor(_colors.promptBackgroundColor)\
4729 term.write(\"$ \")\
4730 \
4731 term.setTextColour(_colors.commandTextColor)\
4732 term.setBackgroundColor(colors.black)\
4733 return line\
4734 end\
4735end\
4736\
4737local function shellRead(history)\
4738 local lastLen = 0\
4739 local entry = Entry({\
4740 width = term.getSize() - 3\
4741 })\
4742 \
4743 history:reset()\
4744 term.setCursorBlink(true)\
4745 \
4746 local function redraw()\
4747 local _, cy = term.getCursorPos()\
4748 term.setCursorPos(3, cy)\
4749 local filler = #entry.value < lastLen\
4750 and string.rep(' ', lastLen - #entry.value)\
4751 or ''\
4752 local str = string.sub(entry.value, entry.scroll + 1)\
4753 term.write(string.sub(str, 1, entry.width) .. filler)\
4754 term.setCursorPos(3 + entry.pos - entry.scroll, cy)\
4755 lastLen = #entry.value\
4756 end\
4757 \
4758 while true do\
4759 local event, p1, p2, p3 = os.pullEventRaw()\
4760 \
4761 local ie = Input:translate(event, p1, p2, p3)\
4762 if ie then\
4763 if ie.code == 'scroll_up' then\
4764 --terminal.scrollUp()\
4765 elseif ie.code == 'scroll_down' then\
4766 --terminal.scrollDown()\
4767 elseif ie.code == 'terminate' then\
4768 bExit = true\
4769 break\
4770 \
4771 elseif ie.code == 'enter' then\
4772 break\
4773 \
4774 elseif ie.code == 'up' or ie.code == 'down' then\
4775 if ie.code == 'up' then\
4776 entry.value = history:back() or ''\
4777 else\
4778 entry.value = history:forward() or ''\
4779 end\
4780 entry.pos = string.len(entry.value)\
4781 entry.scroll = 0\
4782 entry:updateScroll()\
4783 redraw()\
4784 \
4785 elseif ie.code == 'tab' then\
4786 if entry.pos == #entry.value then\
4787 local cline = autocomplete(entry.value)\
4788 if cline then\
4789 entry.value = cline\
4790 entry.pos = #entry.value\
4791 entry:updateScroll()\
4792 redraw()\
4793 end\
4794 end\
4795 \
4796 elseif entry:process(ie) then\
4797 redraw()\
4798 end\
4799 \
4800 elseif event == \"term_resize\" then\
4801 entry.width = term.getSize() - 3\
4802 redraw()\
4803 end\
4804 end\
4805 \
4806 --local _, cy = term.getCursorPos()\
4807 --term.setCursorPos( w + 1, cy )\
4808 print()\
4809 term.setCursorBlink(false)\
4810 return entry.value\
4811end\
4812\
4813local history = History.load('/sys/os/opus/usr/.shell_history', 25)\
4814\
4815while not bExit do\
4816 if config.displayDirectory then\
4817 term.setTextColour(_colors.directoryTextColor)\
4818 term.setBackgroundColor(_colors.directoryBackgroundColor)\
4819 print('==' .. os.getComputerLabel() .. ':/' .. DIR)\
4820 end\
4821 term.setTextColour(_colors.promptTextColor)\
4822 term.setBackgroundColor(_colors.promptBackgroundColor)\
4823 term.write(\"$ \")\
4824 term.setTextColour(_colors.commandTextColor)\
4825 term.setBackgroundColor(colors.black)\
4826 local sLine = shellRead(history)\
4827 if bExit then -- terminated\
4828 break\
4829 end\
4830 sLine = Util.trim(sLine)\
4831 if #sLine > 0 and sLine ~= 'exit' then\
4832 history:add(sLine)\
4833 end\
4834 term.setTextColour(_colors.textColor)\
4835 if #sLine > 0 then\
4836 local result, err = shell.run(sLine)\
4837 if not result and err then\
4838 _G.printError(err)\
4839 end\
4840 end\
4841end",
4842 },
4843 etc = {
4844 [ "app.db" ] = "{\
4845 [ \"0a999012ffb87b3edac99adbdfc498b12831a1e2\" ] = {\
4846 title = \"Packages\",\
4847 category = \"System\",\
4848 run = \"PackageManager.lua\",\
4849 iconExt = \"\\030c\\0317\\151\\131\\131\\131\\0307\\031c\\148\\\
4850\\030c\\0317\\151\\131\\0310\\143\\0317\\131\\0307\\031c\\148\\\
4851\\0307\\031c\\138\\030f\\0317\\151\\131\\131\\131\",\
4852 },\
4853 [ \"53ebc572b4a44802ba114729f07bdaaf5409a9d7\" ] = {\
4854 title = \"Network\",\
4855 category = \"Apps\",\
4856 icon = \"\\0304 \\030 \\\
4857\\030f \\0304 \\0307 \\030 \\031 \\031f)\\\
4858\\030f \\0304 \\0307 \\030 \\031f)\",\
4859 iconExt = \"\\030 \\031f \\0305\\031f\\140\\030f\\0315\\137\\144\\\
4860\\030 \\031f\\030f\\0314\\131\\131\\0304\\031f\\148\\030 \\0305\\155\\150\\149\\\
4861\\030 \\031f\\030f\\0310\\147\\0300\\031f\\141\\0304\\149\\0307\\0318\\149\\030 \",\
4862 run = \"Network.lua\",\
4863 },\
4864 c7116629a6a855cb774d9c7c8ad822fd83c71fb5 = {\
4865 title = \"Reboot\",\
4866 category = \"System\",\
4867 icon = \"\\0304\\031f \\030f\\0310o..\\0304\\031f \\\
4868\\0304\\031f \\030f\\0310.o.\\0304\\031f \\\
4869\\0304\\031f - \",\
4870 iconExt = \"\\0307\\031f\\135\\0300\\0317\\159\\0307\\0310\\144\\031f\\139\\\
4871\\0300\\0317\\131\\0307\\0310\\147\\0300\\0317\\156\\131\\\
4872\\130\\143\\143\\129\",\
4873 run = \"rom/programs/reboot\",\
4874 },\
4875 fb91e24fa52d8d2b32937bf04d843f730319a902 = {\
4876 title = \"Update\",\
4877 category = \"System\",\
4878 icon = \"\\0301\\03171\\03180\\030 \\031 \\\
4879\\0301\\03181\\030 \\031 \\\
4880\\0301\\03170\\03180\\03171\\0307\\031f>\",\
4881 iconExt = \"\\031f\\128\\0313\\152\\131\\131\\132\\031f\\128\\\
4882\\0313\\139\\159\\129\\0303\\031f\\159\\129\\139\\\
4883\\031f\\128\\0313\\136\\0303\\031f\\143\\143\\030f\\0313\\134\\031f\\128\",\
4884 run = \"http://pastebin.com/raw/UzGHLbNC\",\
4885 },\
4886 c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = {\
4887 title = \"Help\",\
4888 category = \"Apps\",\
4889 icon = \" \\031f?\\031 \\\
4890\\031f?\\031 \\\
4891 \\031f?\",\
4892 iconExt = \"\\0300\\031f\\129\\030f\\0310\\131\\0300\\031f\\148\\030f\\0310\\148\\\
4893\\030 \\031 \\0300\\031f\\131\\030f\\0310\\142\\129\\\
4894\\030 \\031 \\0300\\031f\\131\\030f\\128\",\
4895 run = \"Help.lua\",\
4896 },\
4897 b0832074630eb731d7fbe8074de48a90cd9bb220 = {\
4898 title = \"Lua\",\
4899 category = \"Apps\",\
4900 icon = \"\\030f \\\
4901\\030f\\0310lua>\\031 \\\
4902\\030f \",\
4903 iconExt = \"\\0300\\031f\\151\\030f\\128\\0300\\159\\159\\159\\030f\\0310\\144\\0304\\031f\\159\\030f\\128\\\
4904\\0300\\031f\\149\\030f\\128\\0300\\149\\149\\151\\145\\030f\\128\\0314\\153\\\
4905\\130\\131\\130\\131\\130\\131\\0314\\130\\031f\\128\",\
4906 run = \"sys/apps/Lua.lua\",\
4907 },\
4908 bc0792d8dc81e8aa30b987246a5ce97c40cd6833 = {\
4909 title = \"System\",\
4910 category = \"System\",\
4911 icon = \" \\0307\\031f| \\\
4912\\0307\\031f---o\\030 \\031 \\\
4913 \\0307\\031f| \",\
4914 iconExt = \"\\0318\\138\\0308\\031f\\130\\0318\\128\\031f\\129\\030f\\0318\\133\\\
4915\\0318\\143\\0308\\128\\0317\\143\\0318\\128\\030f\\143\\\
4916\\0318\\138\\135\\143\\139\\133\",\
4917 run = \"System.lua\",\
4918 },\
4919 [ \"2a4d562b1d9a9c90bdede6fac8ce4f7402462b86\" ] = {\
4920 title = \"Tasks\",\
4921 category = \"System\",\
4922 icon = \"\\030f\\031f \\0315/\\\
4923\\030f\\031f \\0315/\\\\/ \\\
4924\\030f\\0315/\\031f \",\
4925 iconExt = \"\\031f\\128\\128\\0305\\159\\030f\\128\\0305\\159\\030f\\0315\\134\\031f\\128\\\
4926\\031f\\128\\0315\\152\\129\\137\\0305\\031f\\158\\139\\030f\\0317 \\\
4927\\0315\\134\\031f\\128\\128\\128\\128\\0305\\154\\030f\\0317 \",\
4928 run = \"Tasks.lua\",\
4929 },\
4930 [ \"a0365977708b7387ee9ce2c13e5820e6e11732cb\" ] = {\
4931 title = \"Pain\",\
4932 category = \"Apps\",\
4933 icon = \"\\030 \\031f\\0307\\031f\\159\\030 \\159\\030 \\\
4934\\030 \\031f\\0308\\031f\\135\\0307\\0318\\144\\140\\030f\\0317\\159\\143\\031c\\139\\0302\\135\\030f\\0312\\157\\\
4935\\030 \\031f\\030f\\0318\\143\\133\\0312\\136\\0302\\031f\\159\\159\\143\\131\\030f\\0312\\132\",\
4936 run = \"http://pastebin.com/raw/wJQ7jav0\",\
4937 },\
4938 [ \"48d6857f6b2869d031f463b13aa34df47e18c548\" ] = {\
4939 title = \"Breakout\",\
4940 category = \"Games\",\
4941 icon = \"\\0301\\031f \\0309 \\030c \\030b \\030e \\030c \\0306 \\\
4942\\030 \\031f \\\
4943\\030 \\031f \\0300 \\0310 \",\
4944 iconExt = \"\\030 \\031f\\030f\\0319\\144\\030d\\031f\\159\\030b\\159\\030f\\0311\\144\\031b\\144\\030c\\031f\\159\\030f\\0311\\144\\\
4945\\030 \\031f\\030f\\0311\\130\\031b\\129\\0319\\130\\031e\\130\\0310\\144\\031d\\129\\0316\\129\\\
4946\\030 \\031f\\030f\\0310\\136\\140\\140\\030 \",\
4947 run = \"https://gist.github.com/LDDestroier/c7528d95bc0103545c2a/raw\",\
4948 },\
4949 [ \"53a5d150062b1e03206b9e15854b81060e3c7552\" ] = {\
4950 title = \"Minesweeper\",\
4951 category = \"Games\",\
4952 icon = \"\\030f\\031f \\03131\\0308\\031f \\030f\\031d2\\\
4953\\030f\\031f \\031d2\\03131\\0308\\031f \\030f\\03131\\\
4954\\030f\\03131\\0308\\031f \\030f\\03131\\031e3\",\
4955 run = \"https://pastebin.com/raw/nsKrHTbN\",\
4956 },\
4957 [ \"01c933b2a36ad8ed2d54089cb2903039046c1216\" ] = {\
4958 title = \"Enchat\",\
4959 icon = \"\\030e\\031f\\151\\030f\\031e\\156\\0311\\140\\0314\\140\\0315\\140\\031d\\140\\031b\\140\\031a\\132\\\
4960\\030f\\0314\\128\\030e\\031f\\132\\030f\\031e\\132\\0318nchat\\\
4961\\030f\\031e\\138\\141\\0311\\140\\0314\\140\\0315\\132\\0317v\\03183\\031a\\132\",\
4962 category = \"Apps\",\
4963 run = \"https://raw.githubusercontent.com/LDDestroier/enchat/master/enchat3.lua\",\
4964 },\
4965 [ \"6ce6c512ea433a7fc5c8841628e7696cd0ff7f2b\" ] = {\
4966 title = \"Files\",\
4967 category = \"Apps\",\
4968 icon = \"\\0300\\0317==\\031 \\0307 \\\
4969\\0300\\0317====\\\
4970\\0300\\0317====\",\
4971 iconExt = \"\\030 \\031f\\0300\\031f\\136\\140\\132\\0308\\130\\030f\\0318\\144\\\
4972\\030 \\031f\\030f\\0310\\157\\0300\\031f\\147\\030f\\0310\\142\\143\\149\\\
4973\\030 \\031f\\0300\\031f\\136\\140\\132\\140\\030f\\0310\\149\",\
4974 run = \"Files.lua\",\
4975 },\
4976 [ \"7fddb7d8d1d60b1eeefa9af01082e0811d4b484d\" ] = {\
4977 title = \"Shutdown\",\
4978 category = \"System\",\
4979 icon = \"\\0304\\031f \\\
4980\\0304\\031f \\030f\\0310zz\\031 \\\
4981\\0304\\031f \\030f \",\
4982 iconExt = \"\\030e\\031f\\135\\030f\\031e\\148\\030e\\128\\031f\\151\\139\\\
4983\\030e\\031e\\128\\030f\\031f\\128\\031e\\143\\031f\\128\\030e\\031e\\128\\\
4984\\031e\\139\\030e\\031f\\130\\131\\129\\030f\\031e\\135\",\
4985 run = \"/rom/programs/shutdown\",\
4986 },\
4987 bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d = {\
4988 title = \"Shell\",\
4989 category = \"Apps\",\
4990 icon = \"\\0304 \\030 \\\
4991\\0304 \\030f\\0314> \\0310_\\031 \\\
4992\\0304 \\030f \\030 \",\
4993 iconExt = \"\\030f\\0314\\151\\131\\131\\131\\131\\\
4994\\030f\\0314\\149\\030f\\0314> \\0310_ \\\
4995\\030f\\0314\\149\\030f \",\
4996 run = \"shell\",\
4997 },\
4998 b77aad5fb24921ef76ac8f3e500ed93fddae8f2a = {\
4999 title = \"Redirection\",\
5000 category = \"Games\",\
5001 icon = \"\\0307 \\0308 \\0307 \\\
5002\\0308\\031b> \\030b\\0310>\\0308\\0318 \\\
5003\\0307 \",\
5004 run = \"rom/programs/fun/advanced/redirection\",\
5005 requires = 'advanced',\
5006 },\
5007 f39d173d91c22348565c20283b89d4d1cabd3b7e = {\
5008 title = \"Falling\",\
5009 category = \"Games\",\
5010 icon = \"\\030f \\0302 \\\
5011\\0309 \\0302 \\0301 \\\
5012\\030e \\0309 \\0301 \",\
5013 run = \"rom/programs/pocket/falling\",\
5014 requires = 'advancedPocket',\
5015 },\
5016 db56e2e1db9f7accfc37f2b132d27505c66ba521 = {\
5017 title = \"Adventure\",\
5018 category = \"Games\",\
5019 icon = \"\\030f\\0310You \\031 \\\
5020\\030f\\0310Ther\\030 \\031 \\\
5021\\030f\\0314?\\031f \\031 \\030 \",\
5022 run = \"rom/programs/fun/adventure\",\
5023 },\
5024 [ \"76b849f460640bc789c433894382fb5acbac42a2\" ] = {\
5025 title = \"Worm\",\
5026 category = \"Games\",\
5027 icon = \"\\030d \\030 \\030e \\030 \\\
5028\\030d \\030 \\\
5029\\030d \",\
5030 iconExt = \"\\030 \\031f\\0305\\031f\\151\\030f\\0315\\135\\131\\0305\\031f\\146\\\
5031\\030 \\031f\\030f\\0315\\130\\141\\0305\\031f\\139\\030f\\0315\\130\\\
5032\\030 \\031f\\0305\\031f\\146\\143\\030f\\0315\\158\\031e\\130\",\
5033 run = \"/rom/programs/fun/worm\",\
5034 },\
5035 [ \"9f46ca3ef617166776ef6014a58d4e66859caa62\" ] = {\
5036 title = \"DJ\",\
5037 category = \"Games\",\
5038 icon = \" \\030f \\\
5039\\030f \\0307 \\\
5040\\030f \\0307 \\0300 \",\
5041 iconExt = \"\\031f\\128\\0307\\143\\131\\131\\131\\131\\143\\030f\\128\\\
5042\\0307\\031f\\129\\0317\\128\\0319\\136\\0309\\031b\\136\\132\\0307\\0319\\132\\0317\\128\\031f\\130\\\
5043\\0317\\130\\143\\0307\\128\\128\\128\\128\\030f\\143\\129\",\
5044 run = \"/rom/programs/fun/dj\",\
5045 },\
5046 [ \"76b849f460640bc789c433894382fb5acbac42a2\" ] = {\
5047 title = \"Tron\",\
5048 category = \"Games\",\
5049 iconExt = \"\\030 \\031f\\030b\\031f\\143\\030f\\128\\128\\030b\\143\\143\\143\\030f\\128\\128\\\
5050\\030 \\031f\\0309\\031b\\140\\030b\\031f\\151\\030f\\031b\\131\\0307\\148\\0317\\128\\030b\\151\\030f\\031b\\131\\148\\\
5051\\030 \\031f\\030f\\031b\\131\\031f\\128\\031b\\131\\0317\\131\\031f\\128\\0317\\131\\031b\\131\\031f\\128\",\
5052 run = \"https://raw.githubusercontent.com/LDDestroier/CC/master/tron.lua\",\
5053 },\
5054}",
5055 [ "ext.theme" ] = "{\
5056 ScrollBar = {\
5057 lineChar = '|',\
5058 sliderChar = '\\127',\
5059 upArrowChar = '\\30',\
5060 downArrowChar = '\\31',\
5061 },\
5062 Button = {\
5063 --focusIndicator = '\\183',\
5064 },\
5065 Checkbox = {\
5066 checkedIndicator = '\\4',\
5067 leftMarker = '\\124',\
5068 rightMarker = '\\124',\
5069 },\
5070 Chooser = {\
5071 leftIndicator = '\\17',\
5072 rightIndicator = '\\16',\
5073 },\
5074 Grid = {\
5075 focusIndicator = '\\183',\
5076 inverseSortIndicator = '\\24',\
5077 },\
5078 TitleBar = {\
5079 frameChar = '\\140',\
5080 closeInd = '\\215',\
5081 },\
5082}",
5083 },
5084 [ "kernel.lua" ] = "_G.requireInjector(_ENV)\
5085\
5086local Terminal = require('terminal')\
5087local Util = require('util')\
5088\
5089_G.kernel = {\
5090 UID = 0,\
5091 hooks = { },\
5092 routines = { },\
5093}\
5094\
5095local fs = _G.fs\
5096local kernel = _G.kernel\
5097local os = _G.os\
5098local shell = _ENV.shell\
5099local term = _G.term\
5100local window = _G.window\
5101\
5102local w, h = term.getSize()\
5103kernel.terminal = term.current()\
5104kernel.window = window.create(kernel.terminal, 1, 1, w, h, false)\
5105\
5106Terminal.scrollable(kernel.window)\
5107\
5108local focusedRoutineEvents = Util.transpose {\
5109 'char', 'key', 'key_up',\
5110 'mouse_click', 'mouse_drag', 'mouse_scroll', 'mouse_up',\
5111 'paste', 'terminate',\
5112}\
5113\
5114_G._debug = function(pattern, ...)\
5115 local oldTerm = term.redirect(kernel.window)\
5116 Util.print(pattern, ...)\
5117 term.redirect(oldTerm)\
5118end\
5119\
5120if not _G.debug then -- don't clobber lua debugger\
5121 function _G.debug(...)\
5122 _G._debug(...)\
5123 end\
5124end\
5125\
5126-- any function that runs in a kernel hook does not run in\
5127-- a separate coroutine or have a window. an error in a hook\
5128-- function will crash the system.\
5129function kernel.hook(event, fn)\
5130 if type(event) == 'table' then\
5131 for _,v in pairs(event) do\
5132 kernel.hook(v, fn)\
5133 end\
5134 else\
5135 if not kernel.hooks[event] then\
5136 kernel.hooks[event] = { }\
5137 end\
5138 table.insert(kernel.hooks[event], fn)\
5139 end\
5140end\
5141\
5142-- you can only unhook from within the function that hooked\
5143function kernel.unhook(event, fn)\
5144 local eventHooks = kernel.hooks[event]\
5145 if eventHooks then\
5146 Util.removeByValue(eventHooks, fn)\
5147 if #eventHooks == 0 then\
5148 kernel.hooks[event] = nil\
5149 end\
5150 end\
5151end\
5152\
5153local Routine = { }\
5154\
5155function Routine:resume(event, ...)\
5156 if not self.co or coroutine.status(self.co) == 'dead' then\
5157 return\
5158 end\
5159\
5160 if not self.filter or self.filter == event or event == \"terminate\" then\
5161 local previousTerm = term.redirect(self.terminal)\
5162\
5163 local previous = kernel.running\
5164 kernel.running = self -- stupid shell set title\
5165 local ok, result = coroutine.resume(self.co, event, ...)\
5166 kernel.running = previous\
5167\
5168 if ok then\
5169 self.filter = result\
5170 else\
5171 _G.printError(result)\
5172 end\
5173\
5174 self.terminal = term.current()\
5175 term.redirect(previousTerm)\
5176\
5177 if not ok and self.haltOnError then\
5178 error(result)\
5179 end\
5180 if coroutine.status(self.co) == 'dead' then\
5181 Util.removeByValue(kernel.routines, self)\
5182 if #kernel.routines > 0 then\
5183 os.queueEvent('kernel_focus', kernel.routines[1].uid)\
5184 end\
5185 if self.haltOnExit then\
5186 kernel.halt()\
5187 end\
5188 end\
5189 return ok, result\
5190 end\
5191end\
5192\
5193function kernel.getFocused()\
5194 return kernel.routines[1]\
5195end\
5196\
5197function kernel.getCurrent()\
5198 return kernel.running\
5199end\
5200\
5201function kernel.newRoutine(args)\
5202 kernel.UID = kernel.UID + 1\
5203\
5204 local routine = setmetatable({\
5205 uid = kernel.UID,\
5206 timestamp = os.clock(),\
5207 terminal = kernel.window,\
5208 window = kernel.window,\
5209 }, { __index = Routine })\
5210\
5211 Util.merge(routine, args)\
5212 routine.env = args.env or Util.shallowCopy(shell.getEnv())\
5213\
5214 return routine\
5215end\
5216\
5217function kernel.launch(routine)\
5218 routine.co = routine.co or coroutine.create(function()\
5219 local result, err\
5220\
5221 if routine.fn then\
5222 result, err = Util.runFunction(routine.env, routine.fn, table.unpack(routine.args or { } ))\
5223 elseif routine.path then\
5224 result, err = Util.run(routine.env, routine.path, table.unpack(routine.args or { } ))\
5225 else\
5226 err = 'kernel: invalid routine'\
5227 end\
5228\
5229 if not result and err ~= 'Terminated' then\
5230 error(err or 'Error occurred', 2)\
5231 end\
5232 end)\
5233\
5234 table.insert(kernel.routines, routine)\
5235\
5236 local s, m = routine:resume()\
5237\
5238 return not s and s or routine.uid, m\
5239end\
5240\
5241function kernel.run(args)\
5242 local routine = kernel.newRoutine(args)\
5243 kernel.launch(routine)\
5244 return routine\
5245end\
5246\
5247function kernel.raise(uid)\
5248 local routine = Util.find(kernel.routines, 'uid', uid)\
5249\
5250 if routine then\
5251 local previous = kernel.routines[1]\
5252 if routine ~= previous then\
5253 Util.removeByValue(kernel.routines, routine)\
5254 table.insert(kernel.routines, 1, routine)\
5255 end\
5256 os.queueEvent('kernel_focus', routine.uid, previous and previous.uid)\
5257 return true\
5258 end\
5259 return false\
5260end\
5261\
5262function kernel.lower(uid)\
5263 local routine = Util.find(kernel.routines, 'uid', uid)\
5264\
5265 if routine and #kernel.routines > 1 then\
5266 if routine == kernel.routines[1] then\
5267 local nextRoutine = kernel.routines[2]\
5268 if nextRoutine then\
5269 kernel.raise(nextRoutine.uid)\
5270 end\
5271 end\
5272\
5273 Util.removeByValue(kernel.routines, routine)\
5274 table.insert(kernel.routines, routine)\
5275 return true\
5276 end\
5277 return false\
5278end\
5279\
5280function kernel.find(uid)\
5281 return Util.find(kernel.routines, 'uid', uid)\
5282end\
5283\
5284function kernel.halt()\
5285 os.queueEvent('kernel_halt')\
5286end\
5287\
5288function kernel.event(event, eventData)\
5289 local stopPropagation\
5290\
5291 local eventHooks = kernel.hooks[event]\
5292 if eventHooks then\
5293 for i = #eventHooks, 1, -1 do\
5294 stopPropagation = eventHooks[i](event, eventData)\
5295 if stopPropagation then\
5296 break\
5297 end\
5298 end\
5299 end\
5300\
5301 if not stopPropagation then\
5302 if focusedRoutineEvents[event] then\
5303 local active = kernel.routines[1]\
5304 if active then\
5305 active:resume(event, table.unpack(eventData))\
5306 end\
5307 else\
5308 -- Passthrough to all processes\
5309 for _,routine in pairs(Util.shallowCopy(kernel.routines)) do\
5310 routine:resume(event, table.unpack(eventData))\
5311 end\
5312 end\
5313 end\
5314end\
5315\
5316function kernel.start()\
5317 local s, m = pcall(function()\
5318 repeat\
5319 local eventData = { os.pullEventRaw() }\
5320 local event = table.remove(eventData, 1)\
5321 kernel.event(event, eventData)\
5322 until event == 'kernel_halt'\
5323 end)\
5324\
5325 if not s then\
5326 kernel.window.setVisible(true)\
5327 term.redirect(kernel.window)\
5328 print('\\nCrash detected\\n')\
5329 _G.printError(m)\
5330 end\
5331 term.redirect(kernel.terminal)\
5332end\
5333\
5334local function init(...)\
5335 local args = { ... }\
5336\
5337 local runLevel = #args > 0 and 6 or 7\
5338\
5339 print('Starting Opus OS')\
5340 local dir = 'sys/os/opus/sys/extensions'\
5341 local files = fs.list(dir)\
5342 table.sort(files)\
5343 for _,file in ipairs(files) do\
5344 local level = file:match('(%d).%S+.lua') or 99\
5345 if tonumber(level) <= runLevel then\
5346 local s, m = shell.run(fs.combine(dir, file))\
5347 if not s then\
5348 error(m)\
5349 end\
5350 os.sleep(0)\
5351 end\
5352 end\
5353\
5354 os.queueEvent('kernel_ready')\
5355\
5356 if args[1] then\
5357 kernel.hook('kernel_ready', function()\
5358 local s, m = kernel.run({\
5359 title = args[1],\
5360 path = 'sys/os/opus/sys/apps/shell',\
5361 args = args,\
5362 haltOnExit = true,\
5363 haltOnError = true,\
5364 terminal = kernel.terminal,\
5365 })\
5366 if s then\
5367 kernel.raise(s.uid)\
5368 else\
5369 error(m)\
5370 end\
5371 end)\
5372 end\
5373end\
5374\
5375kernel.run({\
5376 fn = init,\
5377 title = 'init',\
5378 haltOnError = true,\
5379 args = { ... },\
5380})\
5381\
5382kernel.start()",
5383 autorun = {
5384 [ "gpshost.lua" ] = "if _G.device.wireless_modem then\
5385\
5386 _G.requireInjector(_ENV)\
5387 local Config = require('config')\
5388\
5389 local kernel = _G.kernel\
5390\
5391 local config = { }\
5392 Config.load('gps', config)\
5393\
5394 if config.host and type(config.host) == 'table' then\
5395 kernel.run({\
5396 title = 'GPS Daemon',\
5397 hidden = true,\
5398 path = '/rom/programs/gps',\
5399 args = { 'host', config.host.x, config.host.y, config.host.z },\
5400 })\
5401 end\
5402end",
5403 [ "log.lua" ] = "_G.requireInjector(_ENV)\
5404\
5405--[[\
5406 Adds a task and the control-d hotkey to view the kernel log.\
5407--]]\
5408\
5409local kernel = _G.kernel\
5410local keyboard = _G.device.keyboard\
5411local multishell = _ENV.multishell\
5412local os = _G.os\
5413local term = _G.term\
5414\
5415local function systemLog()\
5416 local routine = kernel.getCurrent()\
5417\
5418 local w, h = kernel.window.getSize()\
5419 kernel.window.reposition(1, 2, w, h - 1)\
5420\
5421 routine.terminal = kernel.window\
5422 routine.window = kernel.window\
5423 term.redirect(kernel.window)\
5424\
5425 kernel.hook('mouse_scroll', function(_, eventData)\
5426 local dir, y = eventData[1], eventData[3]\
5427\
5428 if y > 1 then\
5429 local currentTab = kernel.getFocused()\
5430 if currentTab.terminal.scrollUp and not currentTab.terminal.noAutoScroll then\
5431 if dir == -1 then\
5432 currentTab.terminal.scrollUp()\
5433 else\
5434 currentTab.terminal.scrollDown()\
5435 end\
5436 end\
5437 end\
5438 end)\
5439\
5440 keyboard.addHotkey('control-d', function()\
5441 local current = kernel.getFocused()\
5442 if current.uid ~= routine.uid then\
5443 kernel.raise(routine.uid)\
5444 elseif kernel.routines[2] then\
5445 kernel.raise(kernel.routines[2].uid)\
5446 end\
5447 end)\
5448\
5449 os.pullEventRaw('terminate')\
5450 keyboard.removeHotkey('control-d')\
5451end\
5452\
5453multishell.openTab({\
5454 title = 'System Log',\
5455 fn = systemLog,\
5456 hidden = true,\
5457})",
5458 [ "hotkeys.lua" ] = "_G.requireInjector(_ENV)\
5459\
5460local Util = require('util')\
5461\
5462local kernel = _G.kernel\
5463local keyboard = _G.device.keyboard\
5464local multishell = _ENV.multishell\
5465\
5466-- overview\
5467keyboard.addHotkey('control-o', function()\
5468 for _,tab in pairs(multishell.getTabs()) do\
5469 if tab.isOverview then\
5470 multishell.setFocus(tab.uid)\
5471 end\
5472 end\
5473end)\
5474\
5475-- restart tab\
5476keyboard.addHotkey('control-backspace', function()\
5477 local uid = multishell.getFocus()\
5478 local tab = kernel.find(uid)\
5479 if not tab.isOverview then\
5480 multishell.terminate(uid)\
5481 tab = Util.shallowCopy(tab)\
5482 tab.isDead = false\
5483 tab.focused = true\
5484 multishell.openTab(tab)\
5485 end\
5486end)\
5487\
5488-- next tab\
5489keyboard.addHotkey('control-tab', function()\
5490 local tabs = multishell.getTabs()\
5491 local visibleTabs = { }\
5492 local currentTabId = multishell.getFocus()\
5493\
5494 local function compareTab(a, b)\
5495 return a.uid < b.uid\
5496 end\
5497 for _,tab in Util.spairs(tabs, compareTab) do\
5498 if not tab.hidden then\
5499 table.insert(visibleTabs, tab)\
5500 end\
5501 end\
5502\
5503 for k,tab in ipairs(visibleTabs) do\
5504 if tab.uid == currentTabId then\
5505 if k < #visibleTabs then\
5506 multishell.setFocus(visibleTabs[k + 1].uid)\
5507 return\
5508 end\
5509 end\
5510 end\
5511 if #visibleTabs > 0 then\
5512 multishell.setFocus(visibleTabs[1].uid)\
5513 end\
5514end)",
5515 [ "gps.lua" ] = "local modem = _G.device.wireless_modem\
5516local turtle = _G.turtle\
5517\
5518if turtle and modem then\
5519 local s, m = turtle.run(function()\
5520\
5521 _G.requireInjector(_ENV)\
5522\
5523 local Config = require('config')\
5524 local config = {\
5525 destructive = false,\
5526 }\
5527 Config.load('gps', config)\
5528\
5529 if config.home then\
5530\
5531 local s = turtle.enableGPS(2)\
5532 if not s then\
5533 s = turtle.enableGPS(2)\
5534 end\
5535 if not s and config.destructive then\
5536 turtle.setPolicy('turtleSafe')\
5537 s = turtle.enableGPS(2)\
5538 end\
5539\
5540 if not s then\
5541 error('Unable to get GPS position')\
5542 end\
5543\
5544 if config.destructive then\
5545 turtle.setPolicy('turtleSafe')\
5546 end\
5547\
5548 if not turtle.pathfind(config.home) then\
5549 error('Failed to return home')\
5550 end\
5551 end\
5552 end)\
5553\
5554 if not s and m then\
5555 error(m)\
5556 end\
5557end",
5558 [ "clipboard.lua" ] = "_G.requireInjector(_ENV)\
5559\
5560local Util = require('util')\
5561\
5562local kernel = _G.kernel\
5563local keyboard = _G.device.keyboard\
5564local os = _G.os\
5565local textutils = _G.textutils\
5566\
5567local data\
5568\
5569kernel.hook('clipboard_copy', function(_, args)\
5570 data = args[1]\
5571end)\
5572\
5573keyboard.addHotkey('shift-paste', function()\
5574 if type(data) == 'table' then\
5575 local s, m = pcall(textutils.serialize, data)\
5576 data = (s and m) or Util.tostring(data)\
5577 end\
5578 -- replace the event paste data with our internal data\
5579 -- args[1] = Util.tostring(data or '')\
5580 if data then\
5581 os.queueEvent('paste', data)\
5582 end\
5583end)",
5584 },
5585 apis = {
5586 [ "packages.lua" ] = "local Util = require('util')\
5587\
5588local fs = _G.fs\
5589local textutils = _G.textutils\
5590\
5591local PACKAGE_DIR = 'packages'\
5592\
5593local Packages = { }\
5594\
5595function Packages:installed()\
5596 self.cache = { }\
5597\
5598 if fs.exists(PACKAGE_DIR) then\
5599 for _, dir in pairs(fs.list(PACKAGE_DIR)) do\
5600 local path = fs.combine(fs.combine(PACKAGE_DIR, dir), '.package')\
5601 self.cache[dir] = Util.readTable(path)\
5602 end\
5603 end\
5604\
5605 return self.cache\
5606end\
5607\
5608function Packages:list()\
5609 if self.packageList then\
5610 return self.packageList\
5611 end\
5612 self.packageList = Util.readTable('/sys/os/opus/usr/config/packages') or { }\
5613\
5614 return self.packageList\
5615end\
5616\
5617function Packages:isInstalled(package)\
5618 return self:installed()[package]\
5619end\
5620\
5621function Packages:getManifest(package)\
5622 local fname = 'packages/' .. package .. '/.package'\
5623 if fs.exists(fname) then\
5624 local c = Util.readTable(fname)\
5625 if c then\
5626 c.repository = c.repository:gsub('{{OPUS_BRANCH}}', _G.OPUS_BRANCH)\
5627 return c\
5628 end\
5629 end\
5630 local list = self:list()\
5631 local url = list and list[package]\
5632\
5633 if url then\
5634 local c = Util.httpGet(url)\
5635 if c then\
5636 c = textutils.unserialize(c)\
5637 if c then\
5638 c.repository = c.repository:gsub('{{OPUS_BRANCH}}', _G.OPUS_BRANCH)\
5639 return c\
5640 end\
5641 end\
5642 end\
5643end\
5644\
5645return Packages",
5646 [ "ansi.lua" ] = "local Ansi = setmetatable({ }, {\
5647 __call = function(_, ...)\
5648 local str = '\\027['\
5649 for k,v in ipairs({ ...}) do\
5650 if k == 1 then\
5651 str = str .. v\
5652 else\
5653 str = str .. ';' .. v\
5654 end\
5655 end\
5656 return str .. 'm'\
5657 end\
5658})\
5659\
5660Ansi.codes = {\
5661 reset = 0,\
5662 white = 1,\
5663 orange = 2,\
5664 magenta = 3,\
5665 lightBlue = 4,\
5666 yellow = 5,\
5667 lime = 6,\
5668 pink = 7,\
5669 gray = 8,\
5670 lightGray = 9,\
5671 cyan = 10,\
5672 purple = 11,\
5673 blue = 12,\
5674 brown = 13,\
5675 green = 14,\
5676 red = 15,\
5677 black = 16,\
5678 onwhite = 21,\
5679 onorange = 22,\
5680 onmagenta = 23,\
5681 onlightBlue = 24,\
5682 onyellow = 25,\
5683 onlime = 26,\
5684 onpink = 27,\
5685 ongray = 28,\
5686 onlightGray = 29,\
5687 oncyan = 30,\
5688 onpurple = 31,\
5689 onblue = 32,\
5690 onbrown = 33,\
5691 ongreen = 34,\
5692 onred = 35,\
5693 onblack = 36,\
5694}\
5695\
5696for k,v in pairs(Ansi.codes) do\
5697 Ansi[k] = Ansi(v)\
5698end\
5699\
5700return Ansi",
5701 fs = {
5702 [ "redfs.lua" ] = "--[[\
5703 Mount a readonly file system from another computer across rednet. The\
5704 target computer must be running OpusOS or redserver. Dissimlar to samba\
5705 in that a snapshot of the target is taken upon mounting - making this\
5706 faster.\
5707\
5708 Useful for mounting a non-changing directory tree.\
5709\
5710 Syntax:\
5711 rttp://<id>/directory/subdir\
5712\
5713 Examples:\
5714 rttp://12//sys/os/opus/usr/etc\
5715 rttp://8/usr\
5716]]--\
5717\
5718local rttp = require('rttp')\
5719\
5720local fs = _G.fs\
5721\
5722local redfs = { }\
5723\
5724local function getListing(uri)\
5725 local success, response = rttp.get(uri .. '?recursive=true')\
5726\
5727 if not success then\
5728 error(response)\
5729 end\
5730\
5731 if response.statusCode ~= 200 then\
5732 error('Received response ' .. response.statusCode)\
5733 end\
5734\
5735 local list = { }\
5736 for _,v in pairs(response.data) do\
5737 if not v.isDir then\
5738 list[v.path] = {\
5739 url = uri .. '/' .. v.path,\
5740 size = v.size,\
5741 }\
5742 end\
5743 end\
5744\
5745 return list\
5746end\
5747\
5748function redfs.mount(dir, uri)\
5749 if not uri then\
5750 error('redfs syntax: uri')\
5751 end\
5752\
5753 local list = getListing(uri)\
5754 for path, entry in pairs(list) do\
5755 if not fs.exists(fs.combine(dir, path)) then\
5756 local node = fs.mount(fs.combine(dir, path), 'urlfs', entry.url)\
5757 node.size = entry.size\
5758 end\
5759 end\
5760end\
5761\
5762return redfs",
5763 [ "gitfs.lua" ] = "local git = require('git')\
5764\
5765local fs = _G.fs\
5766\
5767local gitfs = { }\
5768\
5769function gitfs.mount(dir, repo)\
5770 if not repo then\
5771 error('gitfs syntax: repo')\
5772 end\
5773\
5774 local list = git.list(repo)\
5775 for path, entry in pairs(list) do\
5776 if not fs.exists(fs.combine(dir, path)) then\
5777 local node = fs.mount(fs.combine(dir, path), 'urlfs', entry.url)\
5778 node.size = entry.size\
5779 end\
5780 end\
5781end\
5782\
5783return gitfs",
5784 [ "urlfs.lua" ] = "local rttp = require('rttp')\
5785local Util = require('util')\
5786\
5787local fs = _G.fs\
5788\
5789local urlfs = { }\
5790\
5791function urlfs.mount(_, url)\
5792 if not url then\
5793 error('URL is required')\
5794 end\
5795 return {\
5796 url = url,\
5797 }\
5798end\
5799\
5800function urlfs.delete(_, dir)\
5801 fs.unmount(dir)\
5802end\
5803\
5804function urlfs.exists()\
5805 return true\
5806end\
5807\
5808function urlfs.getSize(node)\
5809 return node.size or 0\
5810end\
5811\
5812function urlfs.isReadOnly()\
5813 return true\
5814end\
5815\
5816function urlfs.isDir()\
5817 return false\
5818end\
5819\
5820function urlfs.getDrive()\
5821 return 'url'\
5822end\
5823\
5824function urlfs.open(node, fn, fl)\
5825\
5826 if fl == 'w' or fl == 'wb' then\
5827 fs.delete(fn)\
5828 return fs.open(fn, fl)\
5829 end\
5830\
5831 if fl ~= 'r' and fl ~= 'rb' then\
5832 error('Unsupported mode')\
5833 end\
5834\
5835 local c = node.cache\
5836 if not c then\
5837 if node.url:match(\"^(rttps?:)\") then\
5838 local s, response = rttp.get(node.url)\
5839 c = s and response.statusCode == 200 and response.data\
5840 else\
5841 c = Util.httpGet(node.url)\
5842 end\
5843 if c then\
5844 node.cache = c\
5845 node.size = #c\
5846 end\
5847 end\
5848\
5849 if not c then\
5850 return\
5851 end\
5852\
5853 local ctr = 0\
5854 local lines\
5855\
5856 if fl == 'r' then\
5857 return {\
5858 readLine = function()\
5859 if not lines then\
5860 lines = Util.split(c)\
5861 end\
5862 ctr = ctr + 1\
5863 return lines[ctr]\
5864 end,\
5865 readAll = function()\
5866 return c\
5867 end,\
5868 close = function()\
5869 lines = nil\
5870 end,\
5871 }\
5872 end\
5873 return {\
5874 read = function()\
5875 ctr = ctr + 1\
5876 return c:sub(ctr, ctr):byte()\
5877 end,\
5878 close = function()\
5879 ctr = 0\
5880 end,\
5881 }\
5882end\
5883\
5884return urlfs",
5885 [ "ramfs.lua" ] = "local Util = require('util')\
5886\
5887local fs = _G.fs\
5888\
5889local ramfs = { }\
5890\
5891function ramfs.mount(_, nodeType)\
5892 if nodeType == 'directory' then\
5893 return {\
5894 nodes = { },\
5895 size = 0,\
5896 }\
5897 elseif nodeType == 'file' then\
5898 return {\
5899 size = 0,\
5900 }\
5901 end\
5902 error('ramfs syntax: [directory, file]')\
5903end\
5904\
5905function ramfs.delete(node, dir)\
5906 if node.mountPoint == dir then\
5907 fs.unmount(node.mountPoint)\
5908 end\
5909end\
5910\
5911function ramfs.exists(node, fn)\
5912 return node.mountPoint == fn\
5913end\
5914\
5915function ramfs.getSize(node)\
5916 return node.size\
5917end\
5918\
5919function ramfs.isReadOnly()\
5920 return false\
5921end\
5922\
5923function ramfs.makeDir(_, dir)\
5924 fs.mount(dir, 'ramfs', 'directory')\
5925end\
5926\
5927function ramfs.isDir(node)\
5928 return not not node.nodes\
5929end\
5930\
5931function ramfs.getDrive()\
5932 return 'ram'\
5933end\
5934\
5935function ramfs.list(node, dir)\
5936 if node.nodes and node.mountPoint == dir then\
5937 local files = { }\
5938 for k in pairs(node.nodes) do\
5939 table.insert(files, k)\
5940 end\
5941 return files\
5942 end\
5943 error('Not a directory')\
5944end\
5945\
5946function ramfs.open(node, fn, fl)\
5947\
5948 if fl ~= 'r' and fl ~= 'w' and fl ~= 'rb' and fl ~= 'wb' then\
5949 error('Unsupported mode')\
5950 end\
5951\
5952 if fl == 'r' then\
5953 if node.mountPoint ~= fn then\
5954 return\
5955 end\
5956\
5957 local ctr = 0\
5958 local lines\
5959 return {\
5960 readLine = function()\
5961 if not lines then\
5962 lines = Util.split(node.contents)\
5963 end\
5964 ctr = ctr + 1\
5965 return lines[ctr]\
5966 end,\
5967 readAll = function()\
5968 return node.contents\
5969 end,\
5970 close = function()\
5971 lines = nil\
5972 end,\
5973 }\
5974 elseif fl == 'w' then\
5975 node = fs.mount(fn, 'ramfs', 'file')\
5976\
5977 local c = ''\
5978 return {\
5979 write = function(str)\
5980 c = c .. str\
5981 end,\
5982 writeLine = function(str)\
5983 c = c .. str .. '\\n'\
5984 end,\
5985 flush = function()\
5986 node.contents = c\
5987 node.size = #c\
5988 end,\
5989 close = function()\
5990 node.contents = c\
5991 node.size = #c\
5992 c = nil\
5993 end,\
5994 }\
5995 elseif fl == 'rb' then\
5996 if node.mountPoint ~= fn or not node.contents then\
5997 return\
5998 end\
5999\
6000 local ctr = 0\
6001 return {\
6002 read = function()\
6003 ctr = ctr + 1\
6004 return node.contents[ctr]\
6005 end,\
6006 close = function()\
6007 end,\
6008 }\
6009\
6010 elseif fl == 'wb' then\
6011 node = fs.mount(fn, 'ramfs', 'file')\
6012\
6013 local c = { }\
6014 return {\
6015 write = function(b)\
6016 table.insert(c, b)\
6017 end,\
6018 flush = function()\
6019 node.contents = c\
6020 node.size = #c\
6021 end,\
6022 close = function()\
6023 node.contents = c\
6024 node.size = #c\
6025 c = nil\
6026 end,\
6027 }\
6028 end\
6029end\
6030\
6031return ramfs",
6032 [ "netfs.lua" ] = "local Socket = require('socket')\
6033local synchronized = require('sync').sync\
6034\
6035local fs = _G.fs\
6036\
6037local netfs = { }\
6038\
6039local function remoteCommand(node, msg)\
6040\
6041 for _ = 1, 2 do\
6042 if not node.socket then\
6043 node.socket = Socket.connect(node.id, 139)\
6044 end\
6045\
6046 if not node.socket then\
6047 error('netfs: Unable to establish connection to ' .. node.id)\
6048 fs.unmount(node.mountPoint)\
6049 return\
6050 end\
6051\
6052 local ret\
6053 synchronized(node.socket, function()\
6054 node.socket:write(msg)\
6055 ret = node.socket:read(1)\
6056 end)\
6057\
6058 if ret then\
6059 return ret.response\
6060 end\
6061 node.socket:close()\
6062 node.socket = nil\
6063 end\
6064 error('netfs: Connection failed', 2)\
6065end\
6066\
6067local methods = { 'delete', 'exists', 'getFreeSpace', 'makeDir', 'list', 'listEx' }\
6068\
6069local function resolveDir(dir, node)\
6070 dir = dir:gsub(node.mountPoint, '', 1)\
6071 return fs.combine(node.directory, dir)\
6072end\
6073\
6074for _,m in pairs(methods) do\
6075 netfs[m] = function(node, dir)\
6076 dir = resolveDir(dir, node)\
6077\
6078 return remoteCommand(node, {\
6079 fn = m,\
6080 args = { dir },\
6081 })\
6082 end\
6083end\
6084\
6085function netfs.mount(_, id, directory)\
6086 if not id or not tonumber(id) then\
6087 error('ramfs syntax: computerId [directory]')\
6088 end\
6089 return {\
6090 id = tonumber(id),\
6091 nodes = { },\
6092 directory = directory or '',\
6093 }\
6094end\
6095\
6096function netfs.getDrive()\
6097 return 'net'\
6098end\
6099\
6100function netfs.complete(node, partial, dir, includeFiles, includeSlash)\
6101 dir = resolveDir(dir, node)\
6102\
6103 return remoteCommand(node, {\
6104 fn = 'complete',\
6105 args = { partial, dir, includeFiles, includeSlash },\
6106 })\
6107end\
6108\
6109function netfs.copy(node, s, t)\
6110 s = resolveDir(s, node)\
6111 t = resolveDir(t, node)\
6112\
6113 return remoteCommand(node, {\
6114 fn = 'copy',\
6115 args = { s, t },\
6116 })\
6117end\
6118\
6119function netfs.isDir(node, dir)\
6120 if dir == node.mountPoint and node.directory == '' then\
6121 return true\
6122 end\
6123 return remoteCommand(node, {\
6124 fn = 'isDir',\
6125 args = { resolveDir(dir, node) },\
6126 })\
6127end\
6128\
6129function netfs.isReadOnly(node, dir)\
6130 if dir == node.mountPoint and node.directory == '' then\
6131 return false\
6132 end\
6133 return remoteCommand(node, {\
6134 fn = 'isReadOnly',\
6135 args = { resolveDir(dir, node) },\
6136 })\
6137end\
6138\
6139function netfs.getSize(node, dir)\
6140 if dir == node.mountPoint and node.directory == '' then\
6141 return 0\
6142 end\
6143 return remoteCommand(node, {\
6144 fn = 'getSize',\
6145 args = { resolveDir(dir, node) },\
6146 })\
6147end\
6148\
6149function netfs.find(node, spec)\
6150 spec = resolveDir(spec, node)\
6151 local list = remoteCommand(node, {\
6152 fn = 'find',\
6153 args = { spec },\
6154 })\
6155\
6156 for k,f in ipairs(list) do\
6157 list[k] = fs.combine(node.mountPoint, f)\
6158 end\
6159\
6160 return list\
6161end\
6162\
6163function netfs.move(node, s, t)\
6164 s = resolveDir(s, node)\
6165 t = resolveDir(t, node)\
6166\
6167 return remoteCommand(node, {\
6168 fn = 'move',\
6169 args = { s, t },\
6170 })\
6171end\
6172\
6173function netfs.open(node, fn, fl)\
6174 fn = resolveDir(fn, node)\
6175\
6176 local vfh = remoteCommand(node, {\
6177 fn = 'open',\
6178 args = { fn, fl },\
6179 })\
6180\
6181 if vfh then\
6182 vfh.node = node\
6183 for _,m in ipairs(vfh.methods) do\
6184 vfh[m] = function(...)\
6185 return remoteCommand(node, {\
6186 fn = 'fileOp',\
6187 args = { vfh.fileUid, m, ... },\
6188 })\
6189 end\
6190 end\
6191 end\
6192\
6193 return vfh\
6194end\
6195\
6196return netfs",
6197 [ "linkfs.lua" ] = "local fs = _G.fs\
6198\
6199local linkfs = { }\
6200\
6201local methods = { 'exists', 'getFreeSpace', 'getSize',\
6202 'isDir', 'isReadOnly', 'list', 'listEx', 'makeDir', 'open', 'getDrive' }\
6203\
6204for _,m in pairs(methods) do\
6205 linkfs[m] = function(node, dir, ...)\
6206 dir = dir:gsub(node.mountPoint, node.source, 1)\
6207 return fs[m](dir, ...)\
6208 end\
6209end\
6210\
6211function linkfs.mount(_, source)\
6212 if not source then\
6213 error('Source is required')\
6214 end\
6215 source = fs.combine(source, '')\
6216 if fs.isDir(source) then\
6217 return {\
6218 source = source,\
6219 nodes = { },\
6220 }\
6221 end\
6222 return {\
6223 source = source\
6224 }\
6225end\
6226\
6227function linkfs.copy(node, s, t)\
6228 s = s:gsub(node.mountPoint, node.source, 1)\
6229 t = t:gsub(node.mountPoint, node.source, 1)\
6230 return fs.copy(s, t)\
6231end\
6232\
6233function linkfs.delete(node, dir)\
6234 if dir == node.mountPoint then\
6235 fs.unmount(node.mountPoint)\
6236 else\
6237 dir = dir:gsub(node.mountPoint, node.source, 1)\
6238 return fs.delete(dir)\
6239 end\
6240end\
6241\
6242function linkfs.find(node, spec)\
6243 spec = spec:gsub(node.mountPoint, node.source, 1)\
6244\
6245 local list = fs.find(spec)\
6246 for k,f in ipairs(list) do\
6247 list[k] = f:gsub(node.source, node.mountPoint, 1)\
6248 end\
6249\
6250 return list\
6251end\
6252\
6253function linkfs.move(node, s, t)\
6254 s = s:gsub(node.mountPoint, node.source, 1)\
6255 t = t:gsub(node.mountPoint, node.source, 1)\
6256 return fs.move(s, t)\
6257end\
6258\
6259return linkfs",
6260 },
6261 jumper = {
6262 core = {
6263 [ "utils.lua" ] = "-- Various utilities for Jumper top-level modules\
6264\
6265if (...) then\
6266\
6267 -- Dependencies\
6268 local _PATH = (...):gsub('%.utils$','')\
6269 local Path = require (_PATH .. '.path')\
6270\
6271 -- Local references\
6272 local pairs = pairs\
6273 local t_insert = table.insert\
6274\
6275 -- Raw array items count\
6276 local function arraySize(t)\
6277 local count = 0\
6278 for _ in pairs(t) do\
6279 count = count+1\
6280 end\
6281 return count\
6282 end\
6283\
6284 -- Extract a path from a given start/end position\
6285 local function traceBackPath(finder, node, startNode)\
6286 local path = Path:new()\
6287 path._grid = finder._grid\
6288 while true do\
6289 if node._parent then\
6290 t_insert(path._nodes,1,node)\
6291 node = node._parent\
6292 else\
6293 t_insert(path._nodes,1,startNode)\
6294 return path\
6295 end\
6296 end\
6297 end\
6298\
6299 -- Lookup for value in a table\
6300 local indexOf = function(t,v)\
6301 for i = 1,#t do\
6302 if t[i] == v then return i end\
6303 end\
6304 return nil\
6305 end\
6306\
6307 -- Is i out of range\
6308 local function outOfRange(i,low,up)\
6309 return (i< low or i > up)\
6310 end\
6311\
6312 return {\
6313 arraySize = arraySize,\
6314 indexOf = indexOf,\
6315 outOfRange = outOfRange,\
6316 traceBackPath = traceBackPath\
6317 }\
6318\
6319end",
6320 [ "node.lua" ] = "--- The Node class.\
6321-- The `node` represents a cell (or a tile) on a collision map. Basically, for each single cell (tile)\
6322-- in the collision map passed-in upon initialization, a `node` object will be generated\
6323-- and then cached within the `grid`.\
6324--\
6325-- In the following implementation, nodes can be compared using the `<` operator. The comparison is\
6326-- made with regards of their `f` cost. From a given node being examined, the `pathfinder` will expand the search\
6327-- to the next neighbouring node having the lowest `f` cost. See `core.bheap` for more details.\
6328--\
6329if (...) then\
6330\
6331 local Node = {}\
6332 Node.__index = Node\
6333\
6334 function Node:new(x,y,z)\
6335 return setmetatable({x = x, y = y, z = z }, Node)\
6336 end\
6337\
6338 -- Enables the use of operator '<' to compare nodes.\
6339 -- Will be used to sort a collection of nodes in a binary heap on the basis of their F-cost\
6340 function Node.__lt(A,B) return (A._f < B._f) end\
6341\
6342 function Node:getX() return self.x end\
6343 function Node:getY() return self.y end\
6344 function Node:getZ() return self.z end\
6345\
6346 --- Clears temporary cached attributes of a `node`.\
6347 -- Deletes the attributes cached within a given node after a pathfinding call.\
6348 -- This function is internally used by the search algorithms, so you should not use it explicitely.\
6349 function Node:reset()\
6350 self._g, self._h, self._f = nil, nil, nil\
6351 self._opened, self._closed, self._parent = nil, nil, nil\
6352 return self\
6353 end\
6354\
6355 return setmetatable(Node,\
6356 {__call = function(_,...)\
6357 return Node:new(...)\
6358 end}\
6359 )\
6360end",
6361 [ "bheap.lua" ] = "--- A light implementation of Binary heaps data structure.\
6362-- While running a search, some search algorithms (Astar, Dijkstra, Jump Point Search) have to maintains\
6363-- a list of nodes called __open list__. Retrieve from this list the lowest cost node can be quite slow,\
6364-- as it normally requires to skim through the full set of nodes stored in this list. This becomes a real\
6365-- problem especially when dozens of nodes are being processed (on large maps).\
6366--\
6367-- The current module implements a <a href=\"http://www.policyalmanac.org/games/binaryHeaps.htm\">binary heap</a>\
6368-- data structure, from which the search algorithm will instantiate an open list, and cache the nodes being\
6369-- examined during a search. As such, retrieving the lower-cost node is faster and globally makes the search end\
6370-- up quickly.\
6371--\
6372-- This module is internally used by the library on purpose.\
6373-- It should normally not be used explicitely, yet it remains fully accessible.\
6374--\
6375\
6376--[[\
6377 Notes:\
6378 This lighter implementation of binary heaps, based on :\
6379 https://github.com/Yonaba/Binary-Heaps\
6380--]]\
6381\
6382if (...) then\
6383\
6384 -- Dependency\
6385 local Utils = require((...):gsub('%.bheap$','.utils'))\
6386\
6387 -- Local reference\
6388 local floor = math.floor\
6389\
6390 -- Default comparison function\
6391 local function f_min(a,b) return a < b end\
6392\
6393 -- Percolates up\
6394 local function percolate_up(heap, index)\
6395 if index == 1 then return end\
6396 local pIndex\
6397 if index <= 1 then return end\
6398 if index%2 == 0 then\
6399 pIndex = index/2\
6400 else pIndex = (index-1)/2\
6401 end\
6402 if not heap._sort(heap._heap[pIndex], heap._heap[index]) then\
6403 heap._heap[pIndex], heap._heap[index] =\
6404 heap._heap[index], heap._heap[pIndex]\
6405 percolate_up(heap, pIndex)\
6406 end\
6407 end\
6408\
6409 -- Percolates down\
6410 local function percolate_down(heap,index)\
6411 local lfIndex,rtIndex,minIndex\
6412 lfIndex = 2*index\
6413 rtIndex = lfIndex + 1\
6414 if rtIndex > heap._size then\
6415 if lfIndex > heap._size then return\
6416 else minIndex = lfIndex end\
6417 else\
6418 if heap._sort(heap._heap[lfIndex],heap._heap[rtIndex]) then\
6419 minIndex = lfIndex\
6420 else\
6421 minIndex = rtIndex\
6422 end\
6423 end\
6424 if not heap._sort(heap._heap[index],heap._heap[minIndex]) then\
6425 heap._heap[index],heap._heap[minIndex] = heap._heap[minIndex],heap._heap[index]\
6426 percolate_down(heap,minIndex)\
6427 end\
6428 end\
6429\
6430 -- Produces a new heap\
6431 local function newHeap(template,comp)\
6432 return setmetatable({_heap = {},\
6433 _sort = comp or f_min, _size = 0},\
6434 template)\
6435 end\
6436\
6437\
6438 --- The `heap` class.<br/>\
6439 -- This class is callable.\
6440 -- _Therefore,_ <code>heap(...)</code> _is used to instantiate new heaps_.\
6441 -- @type heap\
6442 local heap = setmetatable({},\
6443 {__call = function(self,...)\
6444 return newHeap(self,...)\
6445 end})\
6446 heap.__index = heap\
6447\
6448 --- Checks if a `heap` is empty\
6449 -- @class function\
6450 -- @treturn bool __true__ of no item is queued in the heap, __false__ otherwise\
6451 -- @usage\
6452 -- if myHeap:empty() then\
6453 -- print('Heap is empty!')\
6454 -- end\
6455 function heap:empty()\
6456 return (self._size==0)\
6457 end\
6458\
6459 --- Clears the `heap` (removes all items queued in the heap)\
6460 -- @class function\
6461 -- @treturn heap self (the calling `heap` itself, can be chained)\
6462 -- @usage myHeap:clear()\
6463 function heap:clear()\
6464 self._heap = {}\
6465 self._size = 0\
6466 self._sort = self._sort or f_min\
6467 return self\
6468 end\
6469\
6470 --- Adds a new item in the `heap`\
6471 -- @class function\
6472 -- @tparam value item a new value to be queued in the heap\
6473 -- @treturn heap self (the calling `heap` itself, can be chained)\
6474 -- @usage\
6475 -- myHeap:push(1)\
6476 -- -- or, with chaining\
6477 -- myHeap:push(1):push(2):push(4)\
6478 function heap:push(item)\
6479 if item then\
6480 self._size = self._size + 1\
6481 self._heap[self._size] = item\
6482 percolate_up(self, self._size)\
6483 end\
6484 return self\
6485 end\
6486\
6487 --- Pops from the `heap`.\
6488 -- Removes and returns the lowest cost item (with respect to the comparison function being used) from the `heap`.\
6489 -- @class function\
6490 -- @treturn value a value previously pushed into the heap\
6491 -- @usage\
6492 -- while not myHeap:empty() do\
6493 -- local lowestValue = myHeap:pop()\
6494 -- ...\
6495 -- end\
6496 function heap:pop()\
6497 local root\
6498 if self._size > 0 then\
6499 root = self._heap[1]\
6500 self._heap[1] = self._heap[self._size]\
6501 self._heap[self._size] = nil\
6502 self._size = self._size-1\
6503 if self._size>1 then\
6504 percolate_down(self, 1)\
6505 end\
6506 end\
6507 return root\
6508 end\
6509\
6510 --- Restores the `heap` property.\
6511 -- Reorders the `heap` with respect to the comparison function being used.\
6512 -- When given argument __item__ (a value existing in the `heap`), will sort from that very item in the `heap`.\
6513 -- Otherwise, the whole `heap` will be cheacked.\
6514 -- @class function\
6515 -- @tparam[opt] value item the modified value\
6516 -- @treturn heap self (the calling `heap` itself, can be chained)\
6517 -- @usage myHeap:heapify()\
6518 function heap:heapify(item)\
6519 if self._size == 0 then return end\
6520 if item then\
6521 local i = Utils.indexOf(self._heap,item)\
6522 if i then\
6523 percolate_down(self, i)\
6524 percolate_up(self, i)\
6525 end\
6526 return\
6527 end\
6528 for i = floor(self._size/2),1,-1 do\
6529 percolate_down(self,i)\
6530 end\
6531 return self\
6532 end\
6533\
6534 return heap\
6535end",
6536 [ "path.lua" ] = "--- The Path class.\
6537-- The `path` class is a structure which represents a path (ordered set of nodes) from a start location to a goal.\
6538-- An instance from this class would be a result of a request addressed to `Pathfinder:getPath`.\
6539--\
6540-- This module is internally used by the library on purpose.\
6541-- It should normally not be used explicitely, yet it remains fully accessible.\
6542--\
6543\
6544if (...) then\
6545\
6546 local t_remove = table.remove\
6547\
6548 local Path = {}\
6549 Path.__index = Path\
6550\
6551 function Path:new()\
6552 return setmetatable({_nodes = {}}, Path)\
6553 end\
6554\
6555 --- Iterates on each single `node` along a `path`. At each step of iteration,\
6556 -- returns the `node` plus a count value. Aliased as @{Path:nodes}\
6557 -- @usage\
6558 -- for node, count in p:iter() do\
6559 -- ...\
6560 -- end\
6561 function Path:nodes()\
6562 local i = 1\
6563 return function()\
6564 if self._nodes[i] then\
6565 i = i+1\
6566 return self._nodes[i-1],i-1\
6567 end\
6568 end\
6569 end\
6570\
6571 --- `Path` compression modifier. Given a `path`, eliminates useless nodes to return a lighter `path`\
6572 -- consisting of straight moves. Does the opposite of @{Path:fill}\
6573 -- @class function\
6574 -- @treturn path self (the calling `path` itself, can be chained)\
6575 -- @see Path:fill\
6576 -- @usage p:filter()\
6577 function Path:filter()\
6578 local i = 2\
6579 local xi,yi,zi,dx,dy,dz, olddx, olddy, olddz\
6580 xi,yi,zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z\
6581 dx, dy,dz = xi - self._nodes[i-1].x, yi-self._nodes[i-1].y, zi-self._nodes[i-1].z\
6582 while true do\
6583 olddx, olddy, olddz = dx, dy, dz\
6584 if self._nodes[i+1] then\
6585 i = i+1\
6586 xi, yi, zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z\
6587 dx, dy, dz = xi - self._nodes[i-1].x, yi - self._nodes[i-1].y, zi - self._nodes[i-1].z\
6588 if olddx == dx and olddy == dy and olddz == dz then\
6589 t_remove(self._nodes, i-1)\
6590 i = i - 1\
6591 end\
6592 else break end\
6593 end\
6594 return self\
6595 end\
6596\
6597 return setmetatable(Path,\
6598 {__call = function(_,...)\
6599 return Path:new(...)\
6600 end\
6601 })\
6602end",
6603 },
6604 [ "pathfinder.lua" ] = "--[[\
6605 The following License applies to all files within the jumper directory.\
6606\
6607 Note that this is only a partial copy of the full jumper code base. Also,\
6608 the code was modified to support 3D maps.\
6609--]]\
6610\
6611--[[\
6612This work is under MIT-LICENSE\
6613Copyright (c) 2012-2013 Roland Yonaba.\
6614\
6615-- https://opensource.org/licenses/MIT\
6616\
6617--]]\
6618\
6619local _VERSION = \"\"\
6620local _RELEASEDATE = \"\"\
6621\
6622if (...) then\
6623\
6624 -- Dependencies\
6625 local _PATH = (...):gsub('%.pathfinder$','')\
6626 local Utils = require (_PATH .. '.core.utils')\
6627\
6628 -- Internalization\
6629 local pairs = pairs\
6630 local assert = assert\
6631 local setmetatable = setmetatable\
6632\
6633 --- Finders (search algorithms implemented). Refers to the search algorithms actually implemented in Jumper.\
6634 -- <li>[A*](http://en.wikipedia.org/wiki/A*_search_algorithm)</li>\
6635 local Finders = {\
6636 ['ASTAR'] = require (_PATH .. '.search.astar'),\
6637 }\
6638\
6639 -- Will keep track of all nodes expanded during the search\
6640 -- to easily reset their properties for the next pathfinding call\
6641 local toClear = {}\
6642\
6643 -- Performs a traceback from the goal node to the start node\
6644 -- Only happens when the path was found\
6645\
6646 local Pathfinder = {}\
6647 Pathfinder.__index = Pathfinder\
6648\
6649 function Pathfinder:new(heuristic)\
6650 local newPathfinder = {}\
6651 setmetatable(newPathfinder, Pathfinder)\
6652 self._finder = Finders.ASTAR\
6653 self._heuristic = heuristic\
6654 return newPathfinder\
6655 end\
6656\
6657 function Pathfinder:setGrid(grid)\
6658 self._grid = grid\
6659 return self\
6660 end\
6661\
6662 --- Calculates a `path`. Returns the `path` from start to end location\
6663 -- Both locations must exist on the collision map. The starting location can be unwalkable.\
6664 -- @treturn path a path (array of nodes) when found, otherwise nil\
6665 -- @usage local path = myFinder:getPath(1,1,5,5)\
6666 function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh)\
6667 self:reset()\
6668 local startNode = self._grid:getNodeAt(startX, startY, startZ)\
6669 local endNode = self._grid:getNodeAt(endX, endY, endZ)\
6670 if not startNode or not endNode then\
6671 return nil\
6672 end\
6673\
6674 startNode.heading = ih\
6675 endNode.heading = oh\
6676\
6677 assert(startNode, ('Invalid location [%d, %d, %d]'):format(startX, startY, startZ))\
6678 assert(endNode and self._grid:isWalkableAt(endX, endY, endZ),\
6679 ('Invalid or unreachable location [%d, %d, %d]'):format(endX, endY, endZ))\
6680 local _endNode = self._finder(self, startNode, endNode, toClear)\
6681 if _endNode then\
6682 return Utils.traceBackPath(self, _endNode, startNode)\
6683 end\
6684 return nil\
6685 end\
6686\
6687 --- Resets the `pathfinder`. This function is called internally between\
6688 -- successive pathfinding calls, so you should not\
6689 -- use it explicitely, unless under specific circumstances.\
6690 -- @class function\
6691 -- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)\
6692 -- @usage local path, len = myFinder:getPath(1,1,5,5)\
6693 function Pathfinder:reset()\
6694 for node in pairs(toClear) do node:reset() end\
6695 toClear = {}\
6696 return self\
6697 end\
6698\
6699 -- Returns Pathfinder class\
6700 Pathfinder._VERSION = _VERSION\
6701 Pathfinder._RELEASEDATE = _RELEASEDATE\
6702 return setmetatable(Pathfinder,{\
6703 __call = function(self,...)\
6704 return self:new(...)\
6705 end\
6706 })\
6707end",
6708 search = {
6709 [ "astar.lua" ] = "-- Astar algorithm\
6710-- This actual implementation of A-star is based on\
6711-- [Nash A. & al. pseudocode](http://aigamedev.com/open/tutorials/theta-star-any-angle-paths/)\
6712\
6713if (...) then\
6714\
6715 -- Internalization\
6716 local huge = math.huge\
6717\
6718 -- Dependancies\
6719 local _PATH = (...):match('(.+)%.search.astar$')\
6720 local Heap = require (_PATH.. '.core.bheap')\
6721\
6722 -- Updates G-cost\
6723 local function computeCost(node, neighbour, heuristic)\
6724 local mCost, heading = heuristic(neighbour, node) -- Heuristics.EUCLIDIAN(neighbour, node)\
6725\
6726 if node._g + mCost < neighbour._g then\
6727 neighbour._parent = node\
6728 neighbour._g = node._g + mCost\
6729 neighbour.heading = heading\
6730 end\
6731 end\
6732\
6733 -- Updates vertex node-neighbour\
6734 local function updateVertex(openList, node, neighbour, endNode, heuristic)\
6735 local oldG = neighbour._g\
6736 computeCost(node, neighbour, heuristic)\
6737 if neighbour._g < oldG then\
6738 if neighbour._opened then neighbour._opened = false end\
6739 neighbour._h = heuristic(endNode, neighbour)\
6740 neighbour._f = neighbour._g + neighbour._h\
6741 openList:push(neighbour)\
6742 neighbour._opened = true\
6743 end\
6744 end\
6745\
6746 -- Calculates a path.\
6747 -- Returns the path from location `<startX, startY>` to location `<endX, endY>`.\
6748 return function (finder, startNode, endNode, toClear)\
6749 local openList = Heap()\
6750 startNode._g = 0\
6751 startNode._h = finder._heuristic(endNode, startNode)\
6752 startNode._f = startNode._g + startNode._h\
6753 openList:push(startNode)\
6754 toClear[startNode] = true\
6755 startNode._opened = true\
6756\
6757 while not openList:empty() do\
6758 local node = openList:pop()\
6759 node._closed = true\
6760 if node == endNode then return node end\
6761 local neighbours = finder._grid:getNeighbours(node)\
6762 for i = 1,#neighbours do\
6763 local neighbour = neighbours[i]\
6764 if not neighbour._closed then\
6765 toClear[neighbour] = true\
6766 if not neighbour._opened then\
6767 neighbour._g = huge\
6768 neighbour._parent = nil\
6769 end\
6770 updateVertex(openList, node, neighbour, endNode, finder._heuristic)\
6771 end\
6772 end\
6773\
6774 --[[\
6775 printf('x:%d y:%d z:%d g:%d', node.x, node.y, node.z, node._g)\
6776 for i = 1,#neighbours do\
6777 local n = neighbours[i]\
6778 printf('x:%d y:%d z:%d f:%f g:%f h:%d', n.x, n.y, n.z, n._f, n._g, n.heading or -1)\
6779 end\
6780 --]]\
6781\
6782 end\
6783 return nil\
6784 end\
6785end",
6786 },
6787 [ "grid.lua" ] = "--- The Grid class.\
6788-- Implementation of the `grid` class.\
6789-- The `grid` is a implicit graph which represents the 2D\
6790-- world map layout on which the `pathfinder` object will run.\
6791-- During a search, the `pathfinder` object needs to save some critical values.\
6792-- These values are cached within each `node`\
6793-- object, and the whole set of nodes are tight inside the `grid` object itself.\
6794\
6795if (...) then\
6796\
6797 -- Dependencies\
6798 local _PATH = (...):gsub('%.grid$','')\
6799\
6800 -- Local references\
6801 local Utils = require (_PATH .. '.core.utils')\
6802 local Node = require (_PATH .. '.core.node')\
6803\
6804 -- Local references\
6805 local setmetatable = setmetatable\
6806\
6807 -- Offsets for straights moves\
6808 local straightOffsets = {\
6809 {x = 1, y = 0, z = 0} --[[W]], {x = -1, y = 0, z = 0}, --[[E]]\
6810 {x = 0, y = 1, z = 0} --[[S]], {x = 0, y = -1, z = 0}, --[[N]]\
6811 {x = 0, y = 0, z = 1} --[[U]], {x = 0, y = -0, z = -1}, --[[D]]\
6812 }\
6813\
6814 local Grid = {}\
6815 Grid.__index = Grid\
6816\
6817 function Grid:new(dim)\
6818 local newGrid = { }\
6819 newGrid._min_x, newGrid._max_x = dim.x, dim.ex\
6820 newGrid._min_y, newGrid._max_y = dim.y, dim.ey\
6821 newGrid._min_z, newGrid._max_z = dim.z, dim.ez\
6822 newGrid._nodes = { }\
6823 newGrid._width = (newGrid._max_x-newGrid._min_x)+1\
6824 newGrid._height = (newGrid._max_y-newGrid._min_y)+1\
6825 newGrid._length = (newGrid._max_z-newGrid._min_z)+1\
6826 return setmetatable(newGrid,Grid)\
6827 end\
6828\
6829 function Grid:isWalkableAt(x, y, z)\
6830 local node = self:getNodeAt(x,y,z)\
6831 return node and node.walkable ~= 1\
6832 end\
6833\
6834 function Grid:getWidth()\
6835 return self._width\
6836 end\
6837\
6838 function Grid:getHeight()\
6839 return self._height\
6840 end\
6841\
6842 function Grid:getNodes()\
6843 return self._nodes\
6844 end\
6845\
6846 function Grid:getBounds()\
6847 return self._min_x, self._min_y, self._min_z, self._max_x, self._max_y, self._max_z\
6848 end\
6849\
6850 --- Returns neighbours. The returned value is an array of __walkable__ nodes neighbouring a given `node`.\
6851 -- @treturn {node,...} an array of nodes neighbouring a given node\
6852 function Grid:getNeighbours(node)\
6853 local neighbours = {}\
6854 for i = 1,#straightOffsets do\
6855 local n = self:getNodeAt(\
6856 node.x + straightOffsets[i].x,\
6857 node.y + straightOffsets[i].y,\
6858 node.z + straightOffsets[i].z\
6859 )\
6860 if n and self:isWalkableAt(n.x, n.y, n.z) then\
6861 neighbours[#neighbours+1] = n\
6862 end\
6863 end\
6864\
6865 return neighbours\
6866 end\
6867\
6868 function Grid:getNodeAt(x,y,z)\
6869 if not x or not y or not z then return end\
6870 if Utils.outOfRange(x,self._min_x,self._max_x) then return end\
6871 if Utils.outOfRange(y,self._min_y,self._max_y) then return end\
6872 if Utils.outOfRange(z,self._min_z,self._max_z) then return end\
6873\
6874 -- inefficient\
6875 if not self._nodes[y] then self._nodes[y] = {} end\
6876 if not self._nodes[y][x] then self._nodes[y][x] = {} end\
6877 if not self._nodes[y][x][z] then self._nodes[y][x][z] = Node:new(x,y,z) end\
6878 return self._nodes[y][x][z]\
6879 end\
6880\
6881 return setmetatable(Grid,{\
6882 __call = function(self,...)\
6883 return self:new(...)\
6884 end\
6885 })\
6886\
6887end",
6888 },
6889 [ "class.lua" ] = "-- From http://lua-users.org/wiki/SimpleLuaClasses\
6890-- (with some modifications)\
6891\
6892-- class.lua\
6893-- Compatible with Lua 5.1 (not 5.0).\
6894return function(base)\
6895 local c = { } -- a new class instance\
6896 if type(base) == 'table' then\
6897 -- our new class is a shallow copy of the base class!\
6898 for i,v in pairs(base) do\
6899 c[i] = v\
6900 end\
6901 c._base = base\
6902 end\
6903 -- the class will be the metatable for all its objects,\
6904 -- and they will look up their methods in it.\
6905 c.__index = c\
6906\
6907 -- expose a constructor which can be called by <classname>(<args>)\
6908 setmetatable(c, {\
6909 __call = function(class_tbl, ...)\
6910 local obj = { }\
6911 setmetatable(obj,c)\
6912 if class_tbl.init then\
6913 class_tbl.init(obj, ...)\
6914 else\
6915 -- make sure that any stuff from the base class is initialized!\
6916 if base and base.init then\
6917 base.init(obj, ...)\
6918 end\
6919 end\
6920 return obj\
6921 end\
6922 })\
6923\
6924 c.is_a =\
6925 function(self, klass)\
6926 local m = getmetatable(self)\
6927 while m do\
6928 if m == klass then return true end\
6929 m = m._base\
6930 end\
6931 return false\
6932 end\
6933 return c\
6934end",
6935 [ "util.lua" ] = "local Util = { }\
6936\
6937local fs = _G.fs\
6938local http = _G.http\
6939local os = _G.os\
6940local term = _G.term\
6941local textutils = _G.textutils\
6942\
6943function Util.tryTimed(timeout, f, ...)\
6944 local c = os.clock()\
6945 repeat\
6946 local ret = f(...)\
6947 if ret then\
6948 return ret\
6949 end\
6950 until os.clock()-c >= timeout\
6951end\
6952\
6953function Util.tryTimes(attempts, f, ...)\
6954 local result\
6955 for _ = 1, attempts do\
6956 result = { f(...) }\
6957 if result[1] then\
6958 return unpack(result)\
6959 end\
6960 end\
6961 return unpack(result)\
6962end\
6963\
6964function Util.throttle(fn)\
6965 local ts = os.clock()\
6966 local timeout = .095\
6967 return function(...)\
6968 local nts = os.clock()\
6969 if nts > ts + timeout then\
6970 os.sleep(0)\
6971 ts = os.clock()\
6972 if fn then\
6973 fn(...)\
6974 end\
6975 end\
6976 end\
6977end\
6978\
6979function Util.tostring(pattern, ...)\
6980\
6981 local function serialize(tbl, width)\
6982 local str = '{\\n'\
6983 for k, v in pairs(tbl) do\
6984 local value\
6985 if type(v) == 'table' then\
6986 value = string.format('table: %d', Util.size(v))\
6987 else\
6988 value = tostring(v)\
6989 end\
6990 str = str .. string.format(' %s: %s\\n', k, value)\
6991 end\
6992 --if #str < width then\
6993 --str = str:gsub('\\n', '') .. ' }'\
6994 --else\
6995 str = str .. '}'\
6996 --end\
6997 return str\
6998 end\
6999\
7000 if type(pattern) == 'string' then\
7001 return string.format(pattern, ...)\
7002 elseif type(pattern) == 'table' then\
7003 return serialize(pattern, term.current().getSize())\
7004 end\
7005 return tostring(pattern)\
7006end\
7007\
7008function Util.print(pattern, ...)\
7009 print(Util.tostring(pattern, ...))\
7010end\
7011\
7012function Util.getVersion()\
7013 local version\
7014\
7015 if _G._CC_VERSION then\
7016 version = tonumber(_G._CC_VERSION:match('[%d]+%.?[%d][%d]'))\
7017 end\
7018 if not version and _G._HOST then\
7019 version = tonumber(_G._HOST:match('[%d]+%.?[%d][%d]'))\
7020 end\
7021\
7022 return version or 1.7\
7023end\
7024\
7025function Util.getMinecraftVersion()\
7026 local mcVersion = _G._MC_VERSION or 'unknown'\
7027 if _G._HOST then\
7028 local version = _G._HOST:match('%S+ %S+ %((%S.+)%)')\
7029 if version then\
7030 mcVersion = version:match('Minecraft (%S+)') or version\
7031 end\
7032 end\
7033 return mcVersion\
7034end\
7035\
7036function Util.checkMinecraftVersion(minVersion)\
7037 local version = Util.getMinecraftVersion()\
7038 local function convert(v)\
7039 local m1, m2, m3 = v:match('(%d)%.(%d)%.?(%d?)')\
7040 return tonumber(m1) * 10000 + tonumber(m2) * 100 + (tonumber(m3) or 0)\
7041 end\
7042\
7043 return convert(version) >= convert(tostring(minVersion))\
7044end\
7045\
7046-- http://lua-users.org/wiki/SimpleRound\
7047function Util.round(num, idp)\
7048 local mult = 10^(idp or 0)\
7049 return math.floor(num * mult + 0.5) / mult\
7050end\
7051\
7052function Util.random(max, min)\
7053 min = min or 0\
7054 return math.random(0, max-min) + min\
7055end\
7056\
7057--[[ Table functions ]] --\
7058function Util.clear(t)\
7059 local keys = Util.keys(t)\
7060 for _,k in pairs(keys) do\
7061 t[k] = nil\
7062 end\
7063end\
7064\
7065function Util.empty(t)\
7066 return not next(t)\
7067end\
7068\
7069function Util.key(t, value)\
7070 for k,v in pairs(t) do\
7071 if v == value then\
7072 return k\
7073 end\
7074 end\
7075end\
7076\
7077function Util.keys(t)\
7078 local keys = { }\
7079 for k in pairs(t) do\
7080 keys[#keys+1] = k\
7081 end\
7082 return keys\
7083end\
7084\
7085function Util.merge(obj, args)\
7086 if args then\
7087 for k,v in pairs(args) do\
7088 obj[k] = v\
7089 end\
7090 end\
7091 return obj\
7092end\
7093\
7094function Util.deepMerge(obj, args)\
7095 if args then\
7096 for k,v in pairs(args) do\
7097 if type(v) == 'table' then\
7098 if not obj[k] then\
7099 obj[k] = { }\
7100 end\
7101 Util.deepMerge(obj[k], v)\
7102 else\
7103 obj[k] = v\
7104 end\
7105 end\
7106 end\
7107end\
7108\
7109-- remove table entries if passed function returns false\
7110function Util.prune(t, fn)\
7111 for _,k in pairs(Util.keys(t)) do\
7112 local v = t[k]\
7113 if type(v) == 'table' then\
7114 t[k] = Util.prune(v, fn)\
7115 end\
7116 if not fn(t[k]) then\
7117 t[k] = nil\
7118 end\
7119 end\
7120 return t\
7121end\
7122\
7123function Util.transpose(t)\
7124 local tt = { }\
7125 for k,v in pairs(t) do\
7126 tt[v] = k\
7127 end\
7128 return tt\
7129end\
7130\
7131function Util.contains(t, value)\
7132 for k,v in pairs(t) do\
7133 if v == value then\
7134 return k\
7135 end\
7136 end\
7137end\
7138\
7139function Util.find(t, name, value)\
7140 for k,v in pairs(t) do\
7141 if v[name] == value then\
7142 return v, k\
7143 end\
7144 end\
7145end\
7146\
7147function Util.findAll(t, name, value)\
7148 local rt = { }\
7149 for _,v in pairs(t) do\
7150 if v[name] == value then\
7151 table.insert(rt, v)\
7152 end\
7153 end\
7154 return rt\
7155end\
7156\
7157function Util.shallowCopy(t)\
7158 if not t then error('Util.shallowCopy: invalid table', 2) end\
7159 local t2 = { }\
7160 for k,v in pairs(t) do\
7161 t2[k] = v\
7162 end\
7163 return t2\
7164end\
7165\
7166function Util.deepCopy(t)\
7167 if type(t) ~= 'table' then\
7168 return t\
7169 end\
7170 --local mt = getmetatable(t)\
7171 local res = {}\
7172 for k,v in pairs(t) do\
7173 if type(v) == 'table' then\
7174 v = Util.deepCopy(v)\
7175 end\
7176 res[k] = v\
7177 end\
7178 --setmetatable(res,mt)\
7179 return res\
7180end\
7181\
7182-- http://snippets.luacode.org/?p=snippets/Filter_a_table_in-place_119\
7183function Util.filterInplace(t, predicate)\
7184 local j = 1\
7185\
7186 for i = 1,#t do\
7187 local v = t[i]\
7188 if predicate(v) then\
7189 t[j] = v\
7190 j = j + 1\
7191 end\
7192 end\
7193\
7194 while t[j] ~= nil do\
7195 t[j] = nil\
7196 j = j + 1\
7197 end\
7198\
7199 return t\
7200end\
7201\
7202function Util.filter(it, f)\
7203 local ot = { }\
7204 for k,v in pairs(it) do\
7205 if f(v) then\
7206 ot[k] = v\
7207 end\
7208 end\
7209 return ot\
7210end\
7211\
7212function Util.size(list)\
7213 if type(list) == 'table' then\
7214 local length = 0\
7215 for _ in pairs(list) do\
7216 length = length + 1\
7217 end\
7218 return length\
7219 end\
7220 return 0\
7221end\
7222\
7223function Util.removeByValue(t, e)\
7224 for k,v in pairs(t) do\
7225 if v == e then\
7226 table.remove(t, k)\
7227 break\
7228 end\
7229 end\
7230end\
7231\
7232function Util.each(list, func)\
7233 for index, value in pairs(list) do\
7234 func(value, index, list)\
7235 end\
7236end\
7237\
7238function Util.rpairs(t)\
7239 local tkeys = Util.keys(t)\
7240 local i = #tkeys\
7241 return function()\
7242 local key = tkeys[i]\
7243 local k,v = key, t[key]\
7244 i = i - 1\
7245 if v then\
7246 return k, v\
7247 end\
7248 end\
7249end\
7250\
7251-- http://stackoverflow.com/questions/15706270/sort-a-table-in-lua\
7252function Util.spairs(t, order)\
7253 local keys = Util.keys(t)\
7254\
7255 -- if order function given, sort by it by passing the table and keys a, b,\
7256 -- otherwise just sort the keys\
7257 if order then\
7258 table.sort(keys, function(a,b) return order(t[a], t[b]) end)\
7259 else\
7260 table.sort(keys)\
7261 end\
7262\
7263 -- return the iterator function\
7264 local i = 0\
7265 return function()\
7266 i = i + 1\
7267 if keys[i] then\
7268 return keys[i], t[keys[i]]\
7269 end\
7270 end\
7271end\
7272\
7273function Util.first(t, order)\
7274 local keys = Util.keys(t)\
7275 if order then\
7276 table.sort(keys, function(a,b) return order(t[a], t[b]) end)\
7277 else\
7278 table.sort(keys)\
7279 end\
7280 return keys[1], t[keys[1]]\
7281end\
7282\
7283--[[ File functions ]]--\
7284function Util.readFile(fname)\
7285 local f = fs.open(fname, \"r\")\
7286 if f then\
7287 local t = f.readAll()\
7288 f.close()\
7289 return t\
7290 end\
7291end\
7292\
7293function Util.writeFile(fname, data)\
7294 if not fname or not data then error('Util.writeFile: invalid parameters', 2) end\
7295 local file = io.open(fname, \"w\")\
7296 if not file then\
7297 error('Unable to open ' .. fname, 2)\
7298 end\
7299 file:write(data)\
7300 file:close()\
7301end\
7302\
7303function Util.readLines(fname)\
7304 local file = fs.open(fname, \"r\")\
7305 if file then\
7306 local t = {}\
7307 local line = file.readLine()\
7308 while line do\
7309 table.insert(t, line)\
7310 line = file.readLine()\
7311 end\
7312 file.close()\
7313 return t\
7314 end\
7315end\
7316\
7317function Util.writeLines(fname, lines)\
7318 local file = fs.open(fname, 'w')\
7319 if file then\
7320 for _,line in ipairs(lines) do\
7321 file.writeLine(line)\
7322 end\
7323 file.close()\
7324 return true\
7325 end\
7326end\
7327\
7328function Util.readTable(fname)\
7329 local t = Util.readFile(fname)\
7330 if t then\
7331 return textutils.unserialize(t)\
7332 end\
7333end\
7334\
7335function Util.writeTable(fname, data)\
7336 Util.writeFile(fname, textutils.serialize(data))\
7337end\
7338\
7339function Util.loadTable(fname)\
7340 local fc = Util.readFile(fname)\
7341 if not fc then\
7342 return false, 'Unable to read file'\
7343 end\
7344 local s, m = loadstring('return ' .. fc, fname)\
7345 if s then\
7346 s, m = pcall(s)\
7347 if s then\
7348 return m\
7349 end\
7350 end\
7351 return s, m\
7352end\
7353\
7354--[[ loading and running functions ]] --\
7355function Util.httpGet(url, headers)\
7356 local h, msg = http.get(url, headers)\
7357 if h then\
7358 local contents = h.readAll()\
7359 h.close()\
7360 return contents\
7361 end\
7362 return h, msg\
7363end\
7364\
7365function Util.download(url, filename)\
7366 local contents, msg = Util.httpGet(url)\
7367 if not contents then\
7368 error(string.format('Failed to download %s\\n%s', url, msg), 2)\
7369 end\
7370\
7371 if filename then\
7372 Util.writeFile(filename, contents)\
7373 end\
7374 return contents\
7375end\
7376\
7377function Util.loadUrl(url, env) -- loadfile equivalent\
7378 local c, msg = Util.httpGet(url)\
7379 if not c then\
7380 return c, msg\
7381 end\
7382 return load(c, url, nil, env)\
7383end\
7384\
7385function Util.runUrl(env, url, ...) -- os.run equivalent\
7386 setmetatable(env, { __index = _G })\
7387 local fn, m = Util.loadUrl(url, env)\
7388 if fn then\
7389 return pcall(fn, ...)\
7390 end\
7391 return fn, m\
7392end\
7393\
7394function Util.run(env, path, ...)\
7395 if type(env) ~= 'table' then error('Util.run: env must be a table', 2) end\
7396 setmetatable(env, { __index = _G })\
7397 local fn, m = loadfile(path, env)\
7398 if fn then\
7399 return pcall(fn, ...)\
7400 end\
7401 return fn, m\
7402end\
7403\
7404function Util.runFunction(env, fn, ...)\
7405 setfenv(fn, env)\
7406 setmetatable(env, { __index = _G })\
7407 return pcall(fn, ...)\
7408end\
7409\
7410--[[ String functions ]] --\
7411function Util.toBytes(n)\
7412 if not tonumber(n) then error('Util.toBytes: n must be a number', 2) end\
7413 if n >= 1000000 or n <= -1000000 then\
7414 return string.format('%sM', math.floor(n/1000000 * 10) / 10)\
7415 elseif n >= 10000 or n <= -10000 then\
7416 return string.format('%sK', math.floor(n/1000))\
7417 elseif n >= 1000 or n <= -1000 then\
7418 return string.format('%sK', math.floor(n/1000 * 10) / 10)\
7419 end\
7420 return tostring(n)\
7421end\
7422\
7423function Util.insertString(str, istr, pos)\
7424 return str:sub(1, pos - 1) .. istr .. str:sub(pos)\
7425end\
7426\
7427function Util.split(str, pattern)\
7428 if not str then error('Util.split: Invalid parameters', 2) end\
7429 pattern = pattern or \"(.-)\\n\"\
7430 local t = {}\
7431 local function helper(line) table.insert(t, line) return \"\" end\
7432 helper((str:gsub(pattern, helper)))\
7433 return t\
7434end\
7435\
7436function Util.matches(str, pattern)\
7437 pattern = pattern or '%S+'\
7438 local t = { }\
7439 for s in str:gmatch(pattern) do\
7440 table.insert(t, s)\
7441 end\
7442 return t\
7443end\
7444\
7445function Util.startsWith(s, match)\
7446 return string.sub(s, 1, #match) == match\
7447end\
7448\
7449function Util.widthify(s, len)\
7450 s = s or ''\
7451 local slen = #s\
7452 if slen < len then\
7453 s = s .. string.rep(' ', len - #s)\
7454 elseif slen > len then\
7455 s = s:sub(1, len)\
7456 end\
7457 return s\
7458end\
7459\
7460-- http://snippets.luacode.org/?p=snippets/trim_whitespace_from_string_76\
7461function Util.trim(s)\
7462 return s:find'^%s*$' and '' or s:match'^%s*(.*%S)'\
7463end\
7464\
7465-- trim whitespace from left end of string\
7466function Util.triml(s)\
7467 return s:match'^%s*(.*)'\
7468end\
7469\
7470-- trim whitespace from right end of string\
7471function Util.trimr(s)\
7472 return s:find'^%s*$' and '' or s:match'^(.*%S)'\
7473end\
7474-- end http://snippets.luacode.org/?p=snippets/trim_whitespace_from_string_76\
7475\
7476-- word wrapping based on:\
7477-- https://www.rosettacode.org/wiki/Word_wrap#Lua and\
7478-- http://lua-users.org/wiki/StringRecipes\
7479local function paragraphwrap(text, linewidth, res)\
7480 linewidth = linewidth or 75\
7481 local spaceleft = linewidth\
7482 local line = { }\
7483\
7484 for word in text:gmatch(\"%S+\") do\
7485 local len = #word + 1\
7486\
7487 --if colorMode then\
7488 -- word:gsub('()@([@%d])', function(pos, c) len = len - 2 end)\
7489 --end\
7490\
7491 if len > spaceleft then\
7492 table.insert(res, table.concat(line, ' '))\
7493 line = { word }\
7494 spaceleft = linewidth - len - 1\
7495 else\
7496 table.insert(line, word)\
7497 spaceleft = spaceleft - len\
7498 end\
7499 end\
7500\
7501 table.insert(res, table.concat(line, ' '))\
7502 return table.concat(res, '\\n')\
7503end\
7504-- end word wrapping\
7505\
7506function Util.wordWrap(str, limit)\
7507 local longLines = Util.split(str)\
7508 local lines = { }\
7509\
7510 for _,line in ipairs(longLines) do\
7511 paragraphwrap(line, limit, lines)\
7512 end\
7513\
7514 return lines\
7515end\
7516\
7517function Util.args(arg)\
7518 local options, args = { }, { }\
7519\
7520 local k = 1\
7521 while k <= #arg do\
7522 local v = arg[k]\
7523 if string.sub(v, 1, 1) == '-' then\
7524 local opt = string.sub(v, 2)\
7525 options[opt] = arg[k + 1]\
7526 k = k + 1\
7527 else\
7528 table.insert(args, v)\
7529 end\
7530 k = k + 1\
7531 end\
7532 return options, args\
7533end\
7534\
7535-- http://lua-users.org/wiki/AlternativeGetOpt\
7536local function getopt( arg, options )\
7537 local tab = {}\
7538 for k, v in ipairs(arg) do\
7539 if type(v) == 'string' then\
7540 if string.sub( v, 1, 2) == \"--\" then\
7541 local x = string.find( v, \"=\", 1, true )\
7542 if x then tab[ string.sub( v, 3, x-1 ) ] = string.sub( v, x+1 )\
7543 else tab[ string.sub( v, 3 ) ] = true\
7544 end\
7545 elseif string.sub( v, 1, 1 ) == \"-\" then\
7546 local y = 2\
7547 local l = string.len(v)\
7548 local jopt\
7549 while ( y <= l ) do\
7550 jopt = string.sub( v, y, y )\
7551 if string.find( options, jopt, 1, true ) then\
7552 if y < l then\
7553 tab[ jopt ] = string.sub( v, y+1 )\
7554 y = l\
7555 else\
7556 tab[ jopt ] = arg[ k + 1 ]\
7557 end\
7558 else\
7559 tab[ jopt ] = true\
7560 end\
7561 y = y + 1\
7562 end\
7563 end\
7564 end\
7565 end\
7566 return tab\
7567end\
7568\
7569function Util.showOptions(options)\
7570 print('Arguments: ')\
7571 for _, v in pairs(options) do\
7572 print(string.format('-%s %s', v.arg, v.desc))\
7573 end\
7574end\
7575\
7576function Util.getOptions(options, args, ignoreInvalid)\
7577 local argLetters = ''\
7578 for _,o in pairs(options) do\
7579 if o.type ~= 'flag' then\
7580 argLetters = argLetters .. o.arg\
7581 end\
7582 end\
7583 local rawOptions = getopt(args, argLetters)\
7584\
7585 for k,ro in pairs(rawOptions) do\
7586 local found = false\
7587 for _,o in pairs(options) do\
7588 if o.arg == k then\
7589 found = true\
7590 if o.type == 'number' then\
7591 o.value = tonumber(ro)\
7592 elseif o.type == 'help' then\
7593 Util.showOptions(options)\
7594 return false\
7595 else\
7596 o.value = ro\
7597 end\
7598 end\
7599 end\
7600 if not found and not ignoreInvalid then\
7601 print('Invalid argument')\
7602 Util.showOptions(options)\
7603 return false\
7604 end\
7605 end\
7606 return true, Util.size(rawOptions)\
7607end\
7608\
7609return Util",
7610 [ "entry.lua" ] = "local class = require('class')\
7611\
7612local os = _G.os\
7613\
7614local Entry = class()\
7615\
7616function Entry:init(args)\
7617 self.pos = 0\
7618 self.scroll = 0\
7619 self.value = ''\
7620 self.width = args.width\
7621 self.limit = 1024\
7622end\
7623\
7624function Entry:reset()\
7625 self.pos = 0\
7626 self.scroll = 0\
7627 self.value = ''\
7628end\
7629\
7630local function nextWord(line, cx)\
7631 local result = { line:find(\"(%w+)\", cx) }\
7632 if #result > 1 and result[2] > cx then\
7633 return result[2] + 1\
7634 elseif #result > 0 and result[1] == cx then\
7635 result = { line:find(\"(%w+)\", result[2] + 1) }\
7636 if #result > 0 then\
7637 return result[1]\
7638 end\
7639 end\
7640end\
7641\
7642function Entry:updateScroll()\
7643 if self.pos - self.scroll > self.width then\
7644 self.scroll = self.pos - (self.width)\
7645 elseif self.pos < self.scroll then\
7646 self.scroll = self.pos\
7647 end\
7648end\
7649\
7650function Entry:process(ie)\
7651 local updated = false\
7652\
7653 if ie.code == 'left' then\
7654 if self.pos > 0 then\
7655 self.pos = math.max(self.pos - 1, 0)\
7656 updated = true\
7657 end\
7658\
7659 elseif ie.code == 'right' then\
7660 local input = tostring(self.value)\
7661 if self.pos < #input then\
7662 self.pos = math.min(self.pos + 1, #input)\
7663 updated = true\
7664 end\
7665\
7666 elseif ie.code == 'home' then\
7667 if self.pos ~= 0 then\
7668 self.pos = 0\
7669 updated = true\
7670 end\
7671\
7672 elseif ie.code == 'end' then\
7673 if self.pos ~= #tostring(self.value) then\
7674 self.pos = #tostring(self.value)\
7675 updated = true\
7676 end\
7677\
7678 elseif ie.code == 'backspace' then\
7679 if self.pos > 0 then\
7680 local input = tostring(self.value)\
7681 self.value = input:sub(1, self.pos - 1) .. input:sub(self.pos + 1)\
7682 self.pos = self.pos - 1\
7683 updated = true\
7684 end\
7685\
7686 elseif ie.code == 'control-right' then\
7687 local nx = nextWord(self.value, self.pos + 1)\
7688 if nx then\
7689 self.pos = math.min(nx - 1, #self.value)\
7690 elseif self.pos < #self.value then\
7691 self.pos = #self.value\
7692 end\
7693 updated = true\
7694\
7695 elseif ie.code == 'control-left' then\
7696 if self.pos ~= 0 then\
7697 local lx = 1\
7698 while true do\
7699 local nx = nextWord(self.value, lx)\
7700 if not nx or nx >= self.pos then\
7701 break\
7702 end\
7703 lx = nx\
7704 end\
7705 if not lx then\
7706 self.pos = 0\
7707 else\
7708 self.pos = lx - 1\
7709 end\
7710 updated = true\
7711 end\
7712\
7713 elseif ie.code == 'delete' then\
7714 local input = tostring(self.value)\
7715 if self.pos < #input then\
7716 self.value = input:sub(1, self.pos) .. input:sub(self.pos + 2)\
7717 self.update = true\
7718 updated = true\
7719 end\
7720\
7721 elseif ie.code == 'char' then\
7722 local input = tostring(self.value)\
7723 if #input < self.limit then\
7724 self.value = input:sub(1, self.pos) .. ie.ch .. input:sub(self.pos + 1)\
7725 self.pos = self.pos + 1\
7726 self.update = true\
7727 updated = true\
7728 end\
7729\
7730 elseif ie.code == 'copy' then\
7731 os.queueEvent('clipboard_copy', self.value)\
7732\
7733 elseif ie.code == 'paste' then\
7734 local input = tostring(self.value)\
7735 if #input + #ie.text > self.limit then\
7736 ie.text = ie.text:sub(1, self.limit-#input)\
7737 end\
7738 self.value = input:sub(1, self.pos) .. ie.text .. input:sub(self.pos + 1)\
7739 self.pos = self.pos + #ie.text\
7740 updated = true\
7741\
7742 elseif ie.code == 'mouse_click' then\
7743 -- need starting x passed in instead of hardcoding 3\
7744 self.pos = math.min(ie.x - 3 + self.scroll, #self.value)\
7745 updated = true\
7746\
7747 elseif ie.code == 'mouse_rightclick' then\
7748 local input = tostring(self.value)\
7749 if #input > 0 then\
7750 self:reset()\
7751 updated = true\
7752 end\
7753 end\
7754\
7755 self:updateScroll()\
7756\
7757 return updated\
7758end\
7759\
7760return Entry",
7761 [ "ui.lua" ] = "local Canvas = require('ui.canvas')\
7762local class = require('class')\
7763local Event = require('event')\
7764local Input = require('input')\
7765local Peripheral = require('peripheral')\
7766local Sound = require('sound')\
7767local Transition = require('ui.transition')\
7768local Util = require('util')\
7769\
7770local _rep = string.rep\
7771local _sub = string.sub\
7772local colors = _G.colors\
7773local device = _G.device\
7774local fs = _G.fs\
7775local os = _G.os\
7776local term = _G.term\
7777local window = _G.window\
7778\
7779--[[\
7780 Using the shorthand window definition, elements are created from\
7781 the bottom up. Once reaching the top, setParent is called top down.\
7782\
7783 On :init(), elements do not know the parent or can calculate sizing.\
7784]]\
7785\
7786local function safeValue(v)\
7787 local t = type(v)\
7788 if t == 'string' or t == 'number' then\
7789 return v\
7790 end\
7791 return tostring(v)\
7792end\
7793\
7794-- need to add offsets to this test\
7795local function getPosition(element)\
7796 local x, y = 1, 1\
7797 repeat\
7798 x = element.x + x - 1\
7799 y = element.y + y - 1\
7800 element = element.parent\
7801 until not element\
7802 return x, y\
7803end\
7804\
7805--[[-- Top Level Manager --]]--\
7806local Manager = class()\
7807function Manager:init()\
7808 local function keyFunction(event, code, held)\
7809 local ie = Input:translate(event, code, held)\
7810\
7811 if ie and self.currentPage then\
7812 local target = self.currentPage.focused or self.currentPage\
7813 self:inputEvent(target,\
7814 { type = 'key', key = ie.code == 'char' and ie.ch or ie.code, element = target })\
7815 self.currentPage:sync()\
7816 end\
7817 end\
7818\
7819 local handlers = {\
7820 char = keyFunction,\
7821 key_up = keyFunction,\
7822 key = keyFunction,\
7823\
7824 term_resize = function(_, side)\
7825 if self.currentPage then\
7826 -- the parent doesn't have any children set...\
7827 -- that's why we have to resize both the parent and the current page\
7828 -- kinda makes sense\
7829 if self.currentPage.parent.device.side == side then\
7830 self.currentPage.parent:resize()\
7831\
7832 self.currentPage:resize()\
7833 self.currentPage:draw()\
7834 self.currentPage:sync()\
7835 end\
7836 end\
7837 end,\
7838\
7839 mouse_scroll = function(_, direction, x, y)\
7840 if self.currentPage then\
7841 local event = self.currentPage:pointToChild(x, y)\
7842 local directions = {\
7843 [ -1 ] = 'up',\
7844 [ 1 ] = 'down'\
7845 }\
7846 -- revisit - should send out scroll_up and scroll_down events\
7847 -- let the element convert them to up / down\
7848 self:inputEvent(event.element,\
7849 { type = 'key', key = directions[direction] })\
7850 self.currentPage:sync()\
7851 end\
7852 end,\
7853\
7854 -- this should be moved to the device !\
7855 monitor_touch = function(_, side, x, y)\
7856 Input:translate('mouse_click', 1, x, y)\
7857 local ie = Input:translate('mouse_up', 1, x, y)\
7858 if self.currentPage then\
7859 if self.currentPage.parent.device.side == side then\
7860 self:click(ie.code, 1, x, y)\
7861 end\
7862 end\
7863 end,\
7864\
7865 mouse_click = function(_, button, x, y)\
7866 Input:translate('mouse_click', button, x, y)\
7867\
7868 if self.currentPage then\
7869 if not self.currentPage.parent.device.side then\
7870 local event = self.currentPage:pointToChild(x, y)\
7871 if event.element.focus and not event.element.inactive then\
7872 self.currentPage:setFocus(event.element)\
7873 self.currentPage:sync()\
7874 end\
7875 end\
7876 end\
7877 end,\
7878\
7879 mouse_up = function(_, button, x, y)\
7880 local ie = Input:translate('mouse_up', button, x, y)\
7881\
7882 if ie.code == 'control-shift-mouse_click' then -- hack\
7883 local event = self.currentPage:pointToChild(x, y)\
7884 _ENV.multishell.openTab({\
7885 path = 'sys/os/opus/sys/apps/Lua.lua',\
7886 args = { event.element },\
7887 focused = true })\
7888\
7889 elseif ie and self.currentPage then\
7890 --if not self.currentPage.parent.device.side then\
7891 self:click(ie.code, button, x, y)\
7892 --end\
7893 end\
7894 end,\
7895\
7896 mouse_drag = function(_, button, x, y)\
7897 local ie = Input:translate('mouse_drag', button, x, y)\
7898 if ie and self.currentPage then\
7899 local event = self.currentPage:pointToChild(x, y)\
7900 event.type = ie.code\
7901 self:inputEvent(event.element, event)\
7902 self.currentPage:sync()\
7903 end\
7904 end,\
7905\
7906 paste = function(_, text)\
7907 Input:translate('paste')\
7908 self:emitEvent({ type = 'paste', text = text })\
7909 self.currentPage:sync()\
7910 end,\
7911 }\
7912\
7913 -- use 1 handler to single thread all events\
7914 Event.on({\
7915 'char', 'key_up', 'key', 'term_resize',\
7916 'mouse_scroll', 'monitor_touch', 'mouse_click',\
7917 'mouse_up', 'mouse_drag', 'paste' },\
7918 function(event, ...)\
7919 handlers[event](event, ...)\
7920 end)\
7921end\
7922\
7923function Manager:configure(appName, ...)\
7924 local options = {\
7925 device = { arg = 'd', type = 'string',\
7926 desc = 'Device type' },\
7927 textScale = { arg = 't', type = 'number',\
7928 desc = 'Text scale' },\
7929 }\
7930 local defaults = Util.loadTable('/sys/os/opus/usr/config/' .. appName) or { }\
7931 if not defaults.device then\
7932 defaults.device = { }\
7933 end\
7934\
7935 Util.getOptions(options, { ... }, true)\
7936 local optionValues = {\
7937 name = options.device.value,\
7938 textScale = options.textScale.value,\
7939 }\
7940\
7941 Util.merge(defaults.device, optionValues)\
7942\
7943 if defaults.device.name then\
7944\
7945 local dev\
7946\
7947 if defaults.device.name == 'terminal' then\
7948 dev = term.current()\
7949 else\
7950 dev = Peripheral.lookup(defaults.device.name) --- device[defaults.device.name]\
7951 end\
7952\
7953 if not dev then\
7954 error('Invalid display device')\
7955 end\
7956 self:setDefaultDevice(self.Device({\
7957 device = dev,\
7958 textScale = defaults.device.textScale,\
7959 }))\
7960 end\
7961\
7962 if defaults.theme then\
7963 for k,v in pairs(defaults.theme) do\
7964 if self[k] and self[k].defaults then\
7965 Util.merge(self[k].defaults, v)\
7966 end\
7967 end\
7968 end\
7969end\
7970\
7971function Manager:disableEffects()\
7972 self.defaultDevice.effectsEnabled = false\
7973end\
7974\
7975function Manager:loadTheme(filename)\
7976 if fs.exists(filename) then\
7977 local theme, err = Util.loadTable(filename)\
7978 if not theme then\
7979 error(err)\
7980 end\
7981 for k,v in pairs(theme) do\
7982 if self[k] and self[k].defaults then\
7983 Util.merge(self[k].defaults, v)\
7984 end\
7985 end\
7986 end\
7987end\
7988\
7989function Manager:emitEvent(event)\
7990 if self.currentPage and self.currentPage.focused then\
7991 return self.currentPage.focused:emit(event)\
7992 end\
7993end\
7994\
7995function Manager:inputEvent(parent, event)\
7996 while parent do\
7997 if parent.accelerators then\
7998 local acc = parent.accelerators[event.key]\
7999 if acc then\
8000 if parent:emit({ type = acc, element = parent }) then\
8001 return true\
8002 end\
8003 end\
8004 end\
8005 if parent.eventHandler then\
8006 if parent:eventHandler(event) then\
8007 return true\
8008 end\
8009 end\
8010 parent = parent.parent\
8011 end\
8012end\
8013\
8014function Manager:click(code, button, x, y)\
8015 if self.currentPage then\
8016\
8017 local target = self.currentPage\
8018\
8019 -- need to add offsets into this check\
8020 --[[\
8021 if x < target.x or y < target.y or\
8022 x > target.x + target.width - 1 or\
8023 y > target.y + target.height - 1 then\
8024 target:emit({ type = 'mouse_out' })\
8025\
8026 target = self.currentPage\
8027 end\
8028 --]]\
8029\
8030 local clickEvent = target:pointToChild(x, y)\
8031\
8032 if code == 'mouse_doubleclick' then\
8033 if self.doubleClickElement ~= clickEvent.element then\
8034 return\
8035 end\
8036 else\
8037 self.doubleClickElement = clickEvent.element\
8038 end\
8039\
8040 clickEvent.button = button\
8041 clickEvent.type = code\
8042 clickEvent.key = code\
8043\
8044 if clickEvent.element.focus then\
8045 self.currentPage:setFocus(clickEvent.element)\
8046 end\
8047 if not self:inputEvent(clickEvent.element, clickEvent) then\
8048 --[[\
8049 if button == 3 then\
8050 -- if the double-click was not captured\
8051 -- send through a single-click\
8052 clickEvent.button = 1\
8053 clickEvent.type = events[1]\
8054 clickEvent.key = events[1]\
8055 self:inputEvent(clickEvent.element, clickEvent)\
8056 end\
8057 ]]\
8058 end\
8059\
8060 self.currentPage:sync()\
8061 end\
8062end\
8063\
8064function Manager:setDefaultDevice(dev)\
8065 self.defaultDevice = dev\
8066 self.term = dev\
8067end\
8068\
8069function Manager:addPage(name, page)\
8070 if not self.pages then\
8071 self.pages = { }\
8072 end\
8073 self.pages[name] = page\
8074end\
8075\
8076function Manager:setPages(pages)\
8077 self.pages = pages\
8078end\
8079\
8080function Manager:getPage(pageName)\
8081 local page = self.pages[pageName]\
8082\
8083 if not page then\
8084 error('UI:getPage: Invalid page: ' .. tostring(pageName), 2)\
8085 end\
8086\
8087 return page\
8088end\
8089\
8090function Manager:setPage(pageOrName, ...)\
8091 local page = pageOrName\
8092\
8093 if type(pageOrName) == 'string' then\
8094 page = self.pages[pageOrName] or error('Invalid page: ' .. pageOrName)\
8095 end\
8096\
8097 if page == self.currentPage then\
8098 page:draw()\
8099 else\
8100 local needSync\
8101 if self.currentPage then\
8102 if self.currentPage.focused then\
8103 self.currentPage.focused.focused = false\
8104 self.currentPage.focused:focus()\
8105 end\
8106 self.currentPage:disable()\
8107 page.previousPage = self.currentPage\
8108 else\
8109 needSync = true\
8110 end\
8111 self.currentPage = page\
8112 self.currentPage:clear(page.backgroundColor)\
8113 page:enable(...)\
8114 page:draw()\
8115 if self.currentPage.focused then\
8116 self.currentPage.focused.focused = true\
8117 self.currentPage.focused:focus()\
8118 end\
8119 if needSync then\
8120 page:sync() -- first time a page has been set\
8121 end\
8122 end\
8123end\
8124\
8125function Manager:getCurrentPage()\
8126 return self.currentPage\
8127end\
8128\
8129function Manager:setPreviousPage()\
8130 if self.currentPage.previousPage then\
8131 local previousPage = self.currentPage.previousPage.previousPage\
8132 self:setPage(self.currentPage.previousPage)\
8133 self.currentPage.previousPage = previousPage\
8134 end\
8135end\
8136\
8137function Manager:getDefaults(element, args)\
8138 local defaults = Util.deepCopy(element.defaults)\
8139 if args then\
8140 Manager:mergeProperties(defaults, args)\
8141 end\
8142 return defaults\
8143end\
8144\
8145function Manager:mergeProperties(obj, args)\
8146 if args then\
8147 for k,v in pairs(args) do\
8148 if k == 'accelerators' then\
8149 if obj.accelerators then\
8150 Util.merge(obj.accelerators, args.accelerators)\
8151 else\
8152 obj[k] = v\
8153 end\
8154 else\
8155 obj[k] = v\
8156 end\
8157 end\
8158 end\
8159end\
8160\
8161function Manager:pullEvents(...)\
8162 Event.pullEvents(...)\
8163 self.term:reset()\
8164end\
8165\
8166function Manager:exitPullEvents()\
8167 Event.exitPullEvents()\
8168end\
8169\
8170local UI = Manager()\
8171\
8172--[[-- Basic drawable area --]]--\
8173UI.Window = class()\
8174UI.Window.uid = 1\
8175UI.Window.defaults = {\
8176 UIElement = 'Window',\
8177 x = 1,\
8178 y = 1,\
8179 -- z = 0, -- eventually...\
8180 offx = 0,\
8181 offy = 0,\
8182 cursorX = 1,\
8183 cursorY = 1,\
8184}\
8185function UI.Window:init(args)\
8186 -- merge defaults for all subclasses\
8187 local defaults = args\
8188 local m = getmetatable(self) -- get the class for this instance\
8189 repeat\
8190 defaults = UI:getDefaults(m, defaults)\
8191 m = m._base\
8192 until not m\
8193 UI:mergeProperties(self, defaults)\
8194\
8195 -- each element has a unique ID\
8196 self.uid = UI.Window.uid\
8197 UI.Window.uid = UI.Window.uid + 1\
8198\
8199 -- at this time, the object has all the properties set\
8200\
8201 -- postInit is a special constructor. the element does not need to implement\
8202 -- the method. But we need to guarantee that each subclass which has this\
8203 -- method is called.\
8204 m = self\
8205 local lpi\
8206 repeat\
8207 if m.postInit and m.postInit ~= lpi then\
8208 m.postInit(self)\
8209 lpi = m.postInit\
8210 end\
8211 m = m._base\
8212 until not m\
8213end\
8214\
8215function UI.Window:postInit()\
8216 if self.parent then\
8217 -- this will cascade down the whole tree of elements starting at the\
8218 -- top level window (which has a device as a parent)\
8219 self:setParent()\
8220 end\
8221end\
8222\
8223function UI.Window:initChildren()\
8224 local children = self.children\
8225\
8226 -- insert any UI elements created using the shorthand\
8227 -- window definition into the children array\
8228 for k,child in pairs(self) do\
8229 if k ~= 'parent' then -- reserved\
8230 if type(child) == 'table' and child.UIElement and not child.parent then\
8231 if not children then\
8232 children = { }\
8233 end\
8234 table.insert(children, child)\
8235 end\
8236 end\
8237 end\
8238 if children then\
8239 for _,child in pairs(children) do\
8240 if not child.parent then\
8241 child.parent = self\
8242 child:setParent()\
8243 -- child:reposition() -- maybe\
8244 if self.enabled then\
8245 child:enable()\
8246 end\
8247 end\
8248 end\
8249 self.children = children\
8250 end\
8251end\
8252\
8253local function setSize(self)\
8254 if self.x < 0 then\
8255 self.x = self.parent.width + self.x + 1\
8256 end\
8257 if self.y < 0 then\
8258 self.y = self.parent.height + self.y + 1\
8259 end\
8260\
8261 if self.ex then\
8262 local ex = self.ex\
8263 if self.ex <= 1 then\
8264 ex = self.parent.width + self.ex + 1\
8265 end\
8266 if self.width then\
8267 self.x = ex - self.width + 1\
8268 else\
8269 self.width = ex - self.x + 1\
8270 end\
8271 end\
8272 if self.ey then\
8273 local ey = self.ey\
8274 if self.ey <= 1 then\
8275 ey = self.parent.height + self.ey + 1\
8276 end\
8277 if self.height then\
8278 self.y = ey - self.height + 1\
8279 else\
8280 self.height = ey - self.y + 1\
8281 end\
8282 end\
8283\
8284 if not self.width then\
8285 self.width = self.parent.width - self.x + 1\
8286 end\
8287 if not self.height then\
8288 self.height = self.parent.height - self.y + 1\
8289 end\
8290end\
8291\
8292-- bad name... should be called something like postInit\
8293-- normally used to determine sizes since the parent is\
8294-- only known at this point\
8295function UI.Window:setParent()\
8296 self.oh, self.ow = self.height, self.width\
8297 self.ox, self.oy = self.x, self.y\
8298\
8299 setSize(self)\
8300\
8301 self:initChildren()\
8302end\
8303\
8304function UI.Window:resize()\
8305 self.height, self.width = self.oh, self.ow\
8306 self.x, self.y = self.ox, self.oy\
8307\
8308 setSize(self)\
8309\
8310 if self.children then\
8311 for _,child in ipairs(self.children) do\
8312 child:resize()\
8313 end\
8314 end\
8315end\
8316\
8317function UI.Window:add(children)\
8318 UI:mergeProperties(self, children)\
8319 self:initChildren()\
8320end\
8321\
8322function UI.Window:getCursorPos()\
8323 return self.cursorX, self.cursorY\
8324end\
8325\
8326function UI.Window:setCursorPos(x, y)\
8327 self.cursorX = x\
8328 self.cursorY = y\
8329 self.parent:setCursorPos(self.x + x - 1, self.y + y - 1)\
8330end\
8331\
8332function UI.Window:setCursorBlink(blink)\
8333 self.parent:setCursorBlink(blink)\
8334end\
8335\
8336function UI.Window:draw()\
8337 self:clear(self.backgroundColor)\
8338 if self.children then\
8339 for _,child in pairs(self.children) do\
8340 if child.enabled then\
8341 child:draw()\
8342 end\
8343 end\
8344 end\
8345end\
8346\
8347function UI.Window:sync()\
8348 if self.parent then\
8349 self.parent:sync()\
8350 end\
8351end\
8352\
8353function UI.Window:enable()\
8354 self.enabled = true\
8355 if self.children then\
8356 for _,child in pairs(self.children) do\
8357 child:enable()\
8358 end\
8359 end\
8360end\
8361\
8362function UI.Window:disable()\
8363 self.enabled = false\
8364 if self.children then\
8365 for _,child in pairs(self.children) do\
8366 child:disable()\
8367 end\
8368 end\
8369end\
8370\
8371function UI.Window:setTextScale(textScale)\
8372 self.textScale = textScale\
8373 self.parent:setTextScale(textScale)\
8374end\
8375\
8376function UI.Window:clear(bg, fg)\
8377 if self.canvas then\
8378 self.canvas:clear(bg or self.backgroundColor, fg or self.textColor)\
8379 else\
8380 self:clearArea(1 + self.offx, 1 + self.offy, self.width, self.height, bg)\
8381 end\
8382end\
8383\
8384function UI.Window:clearLine(y, bg)\
8385 self:write(1, y, _rep(' ', self.width), bg)\
8386end\
8387\
8388function UI.Window:clearArea(x, y, width, height, bg)\
8389 if width > 0 then\
8390 local filler = _rep(' ', width)\
8391 for i = 0, height - 1 do\
8392 self:write(x, y + i, filler, bg)\
8393 end\
8394 end\
8395end\
8396\
8397function UI.Window:write(x, y, text, bg, tc)\
8398 bg = bg or self.backgroundColor\
8399 tc = tc or self.textColor\
8400 x = x - self.offx\
8401 y = y - self.offy\
8402 if y <= self.height and y > 0 then\
8403 if self.canvas then\
8404 self.canvas:write(x, y, text, bg, tc)\
8405 else\
8406 self.parent:write(\
8407 self.x + x - 1, self.y + y - 1, tostring(text), bg, tc)\
8408 end\
8409 end\
8410end\
8411\
8412function UI.Window:centeredWrite(y, text, bg, fg)\
8413 if #text >= self.width then\
8414 self:write(1, y, text, bg, fg)\
8415 else\
8416 local space = math.floor((self.width-#text) / 2)\
8417 local filler = _rep(' ', space + 1)\
8418 local str = _sub(filler, 1, space) .. text\
8419 str = str .. _sub(filler, self.width - #str + 1)\
8420 self:write(1, y, str, bg, fg)\
8421 end\
8422end\
8423\
8424function UI.Window:print(text, bg, fg)\
8425 local marginLeft = self.marginLeft or 0\
8426 local marginRight = self.marginRight or 0\
8427 local width = self.width - marginLeft - marginRight\
8428\
8429 local function nextWord(line, cx)\
8430 local result = { line:find(\"(%w+)\", cx) }\
8431 if #result > 1 and result[2] > cx then\
8432 return _sub(line, cx, result[2] + 1)\
8433 elseif #result > 0 and result[1] == cx then\
8434 result = { line:find(\"(%w+)\", result[2]) }\
8435 if #result > 0 then\
8436 return _sub(line, cx, result[1] + 1)\
8437 end\
8438 end\
8439 if cx <= #line then\
8440 return _sub(line, cx, #line)\
8441 end\
8442 end\
8443\
8444 local function pieces(f, bg, fg)\
8445 local pos = 1\
8446 local t = { }\
8447 while true do\
8448 local s = string.find(f, '\\027', pos, true)\
8449 if not s then\
8450 break\
8451 end\
8452 if pos < s then\
8453 table.insert(t, _sub(f, pos, s - 1))\
8454 end\
8455 local seq = _sub(f, s)\
8456 seq = seq:match(\"\\027%[([%d;]+)m\")\
8457 local e = { }\
8458 for color in string.gmatch(seq, \"%d+\") do\
8459 color = tonumber(color)\
8460 if color == 0 then\
8461 e.fg = fg\
8462 e.bg = bg\
8463 elseif color > 20 then\
8464 e.bg = 2 ^ (color - 21)\
8465 else\
8466 e.fg = 2 ^ (color - 1)\
8467 end\
8468 end\
8469 table.insert(t, e)\
8470 pos = s + #seq + 3\
8471 end\
8472 if pos <= #f then\
8473 table.insert(t, _sub(f, pos))\
8474 end\
8475 return t\
8476 end\
8477\
8478 local lines = Util.split(text)\
8479 for k,line in pairs(lines) do\
8480 local fragments = pieces(line, bg, fg)\
8481 for _, fragment in ipairs(fragments) do\
8482 local lx = 1\
8483 if type(fragment) == 'table' then -- ansi sequence\
8484 fg = fragment.fg\
8485 bg = fragment.bg\
8486 else\
8487 while true do\
8488 local word = nextWord(fragment, lx)\
8489 if not word then\
8490 break\
8491 end\
8492 local w = word\
8493 if self.cursorX + #word > width then\
8494 self.cursorX = marginLeft + 1\
8495 self.cursorY = self.cursorY + 1\
8496 w = word:gsub('^ ', '')\
8497 end\
8498 self:write(self.cursorX, self.cursorY, w, bg, fg)\
8499 self.cursorX = self.cursorX + #w\
8500 lx = lx + #word\
8501 end\
8502 end\
8503 end\
8504 if lines[k + 1] then\
8505 self.cursorX = marginLeft + 1\
8506 self.cursorY = self.cursorY + 1\
8507 end\
8508 end\
8509\
8510 return self.cursorX, self.cursorY\
8511end\
8512\
8513function UI.Window:setFocus(focus)\
8514 if self.parent then\
8515 self.parent:setFocus(focus)\
8516 end\
8517end\
8518\
8519function UI.Window:capture(child)\
8520 if self.parent then\
8521 self.parent:capture(child)\
8522 end\
8523end\
8524\
8525function UI.Window:release(child)\
8526 if self.parent then\
8527 self.parent:release(child)\
8528 end\
8529end\
8530\
8531function UI.Window:pointToChild(x, y)\
8532 x = x + self.offx - self.x + 1\
8533 y = y + self.offy - self.y + 1\
8534 if self.children then\
8535 for _,child in pairs(self.children) do\
8536 if child.enabled and not child.inactive and\
8537 x >= child.x and x < child.x + child.width and\
8538 y >= child.y and y < child.y + child.height then\
8539 local c = child:pointToChild(x, y)\
8540 if c then\
8541 return c\
8542 end\
8543 end\
8544 end\
8545 end\
8546 return {\
8547 element = self,\
8548 x = x,\
8549 y = y\
8550 }\
8551end\
8552\
8553function UI.Window:getFocusables()\
8554 local focusable = { }\
8555\
8556 local function focusSort(a, b)\
8557 if a.y == b.y then\
8558 return a.x < b.x\
8559 end\
8560 return a.y < b.y\
8561 end\
8562\
8563 local function getFocusable(parent, x, y)\
8564 for _,child in Util.spairs(parent.children, focusSort) do\
8565 if child.enabled and child.focus and not child.inactive then\
8566 table.insert(focusable, child)\
8567 end\
8568 if child.children then\
8569 getFocusable(child, child.x + x, child.y + y)\
8570 end\
8571 end\
8572 end\
8573\
8574 if self.children then\
8575 getFocusable(self, self.x, self.y)\
8576 end\
8577\
8578 return focusable\
8579end\
8580\
8581function UI.Window:focusFirst()\
8582 local focusables = self:getFocusables()\
8583 local focused = focusables[1]\
8584 if focused then\
8585 self:setFocus(focused)\
8586 end\
8587end\
8588\
8589function UI.Window:refocus()\
8590 local el = self\
8591 while el do\
8592 local focusables = el:getFocusables()\
8593 if focusables[1] then\
8594 self:setFocus(focusables[1])\
8595 break\
8596 end\
8597 el = el.parent\
8598 end\
8599end\
8600\
8601function UI.Window:scrollIntoView()\
8602 local parent = self.parent\
8603\
8604 if self.x <= parent.offx then\
8605 parent.offx = math.max(0, self.x - 1)\
8606 parent:draw()\
8607 elseif self.x + self.width > parent.width + parent.offx then\
8608 parent.offx = self.x + self.width - parent.width - 1\
8609 parent:draw()\
8610 end\
8611\
8612 if self.y <= parent.offy then\
8613 parent.offy = math.max(0, self.y - 1)\
8614 parent:draw()\
8615 elseif self.y + self.height > parent.height + parent.offy then\
8616 parent.offy = self.y + self.height - parent.height - 1\
8617 parent:draw()\
8618 end\
8619end\
8620\
8621function UI.Window:getCanvas()\
8622 local el = self\
8623 repeat\
8624 if el.canvas then\
8625 return el.canvas\
8626 end\
8627 el = el.parent\
8628 until not el\
8629end\
8630\
8631function UI.Window:addLayer(bg, fg)\
8632 local canvas = self:getCanvas()\
8633 canvas = canvas:addLayer(self, bg, fg)\
8634 canvas:clear(bg or self.backgroundColor, fg or self.textColor)\
8635 return canvas\
8636end\
8637\
8638function UI.Window:addTransition(effect, args)\
8639 if self.parent then\
8640 args = args or { }\
8641 if not args.x then -- not good\
8642 args.x, args.y = getPosition(self)\
8643 args.width = self.width\
8644 args.height = self.height\
8645 end\
8646\
8647 args.canvas = args.canvas or self.canvas\
8648 self.parent:addTransition(effect, args)\
8649 end\
8650end\
8651\
8652function UI.Window:emit(event)\
8653 local parent = self\
8654 while parent do\
8655 if parent.eventHandler then\
8656 if parent:eventHandler(event) then\
8657 return true\
8658 end\
8659 end\
8660 parent = parent.parent\
8661 end\
8662end\
8663\
8664function UI.Window:find(uid)\
8665 if self.children then\
8666 return Util.find(self.children, 'uid', uid)\
8667 end\
8668end\
8669\
8670function UI.Window:eventHandler(event)\
8671 return false\
8672end\
8673\
8674--[[-- Terminal for computer / advanced computer / monitor --]]--\
8675UI.Device = class(UI.Window)\
8676UI.Device.defaults = {\
8677 UIElement = 'Device',\
8678 backgroundColor = colors.black,\
8679 textColor = colors.white,\
8680 textScale = 1,\
8681 effectsEnabled = true,\
8682}\
8683function UI.Device:postInit()\
8684 self.device = self.device or term.current()\
8685\
8686 if self.deviceType then\
8687 self.device = device[self.deviceType]\
8688 end\
8689\
8690 if not self.device.setTextScale then\
8691 self.device.setTextScale = function() end\
8692 end\
8693\
8694 self.device.setTextScale(self.textScale)\
8695 self.width, self.height = self.device.getSize()\
8696\
8697 self.isColor = self.device.isColor()\
8698\
8699 self.canvas = Canvas({\
8700 x = 1, y = 1, width = self.width, height = self.height,\
8701 isColor = self.isColor,\
8702 })\
8703 self.canvas:clear(self.backgroundColor, self.textColor)\
8704end\
8705\
8706function UI.Device:resize()\
8707 self.device.setTextScale(self.textScale)\
8708 self.width, self.height = self.device.getSize()\
8709 self.lines = { }\
8710 self.canvas:resize(self.width, self.height)\
8711 self.canvas:clear(self.backgroundColor, self.textColor)\
8712end\
8713\
8714function UI.Device:setCursorPos(x, y)\
8715 self.cursorX = x\
8716 self.cursorY = y\
8717end\
8718\
8719function UI.Device:getCursorBlink()\
8720 return self.cursorBlink\
8721end\
8722\
8723function UI.Device:setCursorBlink(blink)\
8724 self.cursorBlink = blink\
8725 self.device.setCursorBlink(blink)\
8726end\
8727\
8728function UI.Device:setTextScale(textScale)\
8729 self.textScale = textScale\
8730 self.device.setTextScale(self.textScale)\
8731end\
8732\
8733function UI.Device:reset()\
8734 self.device.setBackgroundColor(colors.black)\
8735 self.device.setTextColor(colors.white)\
8736 self.device.clear()\
8737 self.device.setCursorPos(1, 1)\
8738end\
8739\
8740function UI.Device:addTransition(effect, args)\
8741 if not self.transitions then\
8742 self.transitions = { }\
8743 end\
8744\
8745 args = args or { }\
8746 args.ex = args.x + args.width - 1\
8747 args.ey = args.y + args.height - 1\
8748 args.canvas = args.canvas or self.canvas\
8749\
8750 if type(effect) == 'string' then\
8751 effect = Transition[effect]\
8752 if not effect then\
8753 error('Invalid transition')\
8754 end\
8755 end\
8756\
8757 table.insert(self.transitions, { update = effect(args), args = args })\
8758end\
8759\
8760function UI.Device:runTransitions(transitions, canvas)\
8761 for _,t in ipairs(transitions) do\
8762 canvas:punch(t.args) -- punch out the effect areas\
8763 end\
8764 canvas:blitClipped(self.device) -- and blit the remainder\
8765 canvas:reset()\
8766\
8767 while true do\
8768 for _,k in ipairs(Util.keys(transitions)) do\
8769 local transition = transitions[k]\
8770 if not transition.update(self.device) then\
8771 transitions[k] = nil\
8772 end\
8773 end\
8774 if Util.empty(transitions) then\
8775 break\
8776 end\
8777 os.sleep(0)\
8778 end\
8779end\
8780\
8781function UI.Device:sync()\
8782 local transitions\
8783 if self.transitions and self.effectsEnabled then\
8784 transitions = self.transitions\
8785 self.transitions = nil\
8786 end\
8787\
8788 if self:getCursorBlink() then\
8789 self.device.setCursorBlink(false)\
8790 end\
8791\
8792 if transitions then\
8793 self:runTransitions(transitions, self.canvas)\
8794 else\
8795 self.canvas:render(self.device)\
8796 end\
8797\
8798 if self:getCursorBlink() then\
8799 self.device.setCursorPos(self.cursorX, self.cursorY)\
8800 self.device.setCursorBlink(true)\
8801 end\
8802end\
8803\
8804--[[-- StringBuffer --]]--\
8805-- justs optimizes string concatenations\
8806UI.StringBuffer = class()\
8807function UI.StringBuffer:init(bufSize)\
8808 self.bufSize = bufSize\
8809 self.buffer = {}\
8810end\
8811\
8812function UI.StringBuffer:insert(s, width)\
8813 local len = #tostring(s or '')\
8814 if len > width then\
8815 s = _sub(s, 1, width)\
8816 end\
8817 table.insert(self.buffer, s)\
8818 if len < width then\
8819 table.insert(self.buffer, _rep(' ', width - len))\
8820 end\
8821end\
8822\
8823function UI.StringBuffer:insertRight(s, width)\
8824 local len = #tostring(s or '')\
8825 if len > width then\
8826 s = _sub(s, 1, width)\
8827 end\
8828 if len < width then\
8829 table.insert(self.buffer, _rep(' ', width - len))\
8830 end\
8831 table.insert(self.buffer, s)\
8832end\
8833\
8834function UI.StringBuffer:get(sep)\
8835 return Util.widthify(table.concat(self.buffer, sep or ''), self.bufSize)\
8836end\
8837\
8838function UI.StringBuffer:clear()\
8839 self.buffer = { }\
8840end\
8841\
8842-- For manipulating text in a fixed width string\
8843local SB = { }\
8844function SB:new(width)\
8845 return setmetatable({\
8846 width = width,\
8847 buf = _rep(' ', width)\
8848 }, { __index = SB })\
8849end\
8850function SB:insert(x, str, width)\
8851 if x < 1 then\
8852 x = self.width + x + 1\
8853 end\
8854 width = width or #str\
8855 if x + width - 1 > self.width then\
8856 width = self.width - x\
8857 end\
8858 if width > 0 then\
8859 self.buf = _sub(self.buf, 1, x - 1) .. _sub(str, 1, width) .. _sub(self.buf, x + width)\
8860 end\
8861end\
8862function SB:fill(x, ch, width)\
8863 width = width or self.width - x + 1\
8864 self:insert(x, _rep(ch, width))\
8865end\
8866function SB:center(str)\
8867 self:insert(math.max(1, math.ceil((self.width - #str + 1) / 2)), str)\
8868end\
8869function SB:get()\
8870 return self.buf\
8871end\
8872\
8873--[[-- Page (focus manager) --]]--\
8874UI.Page = class(UI.Window)\
8875UI.Page.defaults = {\
8876 UIElement = 'Page',\
8877 accelerators = {\
8878 down = 'focus_next',\
8879 enter = 'focus_next',\
8880 tab = 'focus_next',\
8881 ['shift-tab' ] = 'focus_prev',\
8882 up = 'focus_prev',\
8883 },\
8884 backgroundColor = colors.cyan,\
8885 textColor = colors.white,\
8886}\
8887function UI.Page:postInit()\
8888 self.parent = self.parent or UI.defaultDevice\
8889 self.__target = self\
8890end\
8891\
8892function UI.Page:setParent()\
8893 UI.Window.setParent(self)\
8894 if self.z then\
8895 self.canvas = self:addLayer(self.backgroundColor, self.textColor)\
8896 self.canvas:clear(self.backgroundColor, self.textColor)\
8897 else\
8898 self.canvas = self.parent.canvas\
8899 end\
8900end\
8901\
8902function UI.Page:enable()\
8903 self.canvas.visible = true\
8904 UI.Window.enable(self)\
8905\
8906 if not self.focused or not self.focused.enabled then\
8907 self:focusFirst()\
8908 end\
8909end\
8910\
8911function UI.Page:disable()\
8912 if self.z then\
8913 self.canvas.visible = false\
8914 end\
8915end\
8916\
8917function UI.Page:capture(child)\
8918 self.__target = child\
8919end\
8920\
8921function UI.Page:release(child)\
8922 if self.__target == child then\
8923 self.__target = self\
8924 end\
8925end\
8926\
8927function UI.Page:pointToChild(x, y)\
8928 if self.__target == self then\
8929 return UI.Window.pointToChild(self, x, y)\
8930 end\
8931 x = x + self.offx - self.x + 1\
8932 y = y + self.offy - self.y + 1\
8933 return self.__target:pointToChild(x, y)\
8934end\
8935\
8936function UI.Page:getFocusables()\
8937 if self.__target == self or self.__target.pageType ~= 'modal' then\
8938 return UI.Window.getFocusables(self)\
8939 end\
8940 return self.__target:getFocusables()\
8941end\
8942\
8943function UI.Page:getFocused()\
8944 return self.focused\
8945end\
8946\
8947function UI.Page:focusPrevious()\
8948 local function getPreviousFocus(focused)\
8949 local focusables = self:getFocusables()\
8950 local k = Util.contains(focusables, focused)\
8951 if k then\
8952 if k > 1 then\
8953 return focusables[k - 1]\
8954 end\
8955 return focusables[#focusables]\
8956 end\
8957 end\
8958\
8959 local focused = getPreviousFocus(self.focused)\
8960 if focused then\
8961 self:setFocus(focused)\
8962 end\
8963end\
8964\
8965function UI.Page:focusNext()\
8966 local function getNextFocus(focused)\
8967 local focusables = self:getFocusables()\
8968 local k = Util.contains(focusables, focused)\
8969 if k then\
8970 if k < #focusables then\
8971 return focusables[k + 1]\
8972 end\
8973 return focusables[1]\
8974 end\
8975 end\
8976\
8977 local focused = getNextFocus(self.focused)\
8978 if focused then\
8979 self:setFocus(focused)\
8980 end\
8981end\
8982\
8983function UI.Page:setFocus(child)\
8984 if not child or not child.focus then\
8985 return\
8986 end\
8987\
8988 if self.focused and self.focused ~= child then\
8989 self.focused.focused = false\
8990 self.focused:focus()\
8991 self.focused:emit({ type = 'focus_lost', focused = child })\
8992 end\
8993\
8994 self.focused = child\
8995 if not child.focused then\
8996 child.focused = true\
8997 child:emit({ type = 'focus_change', focused = child })\
8998 --self:emit({ type = 'focus_change', focused = child })\
8999 end\
9000\
9001 child:focus()\
9002end\
9003\
9004function UI.Page:eventHandler(event)\
9005 if self.focused then\
9006 if event.type == 'focus_next' then\
9007 self:focusNext()\
9008 return true\
9009 elseif event.type == 'focus_prev' then\
9010 self:focusPrevious()\
9011 return true\
9012 end\
9013 end\
9014end\
9015\
9016--[[-- Grid --]]--\
9017UI.Grid = class(UI.Window)\
9018UI.Grid.defaults = {\
9019 UIElement = 'Grid',\
9020 index = 1,\
9021 inverseSort = false,\
9022 disableHeader = false,\
9023 marginRight = 0,\
9024 textColor = colors.white,\
9025 textSelectedColor = colors.white,\
9026 backgroundColor = colors.black,\
9027 backgroundSelectedColor = colors.gray,\
9028 headerBackgroundColor = colors.cyan,\
9029 headerTextColor = colors.white,\
9030 headerSortColor = colors.yellow,\
9031 unfocusedTextSelectedColor = colors.white,\
9032 unfocusedBackgroundSelectedColor = colors.gray,\
9033 focusIndicator = '>',\
9034 sortIndicator = ' ',\
9035 inverseSortIndicator = '^',\
9036 values = { },\
9037 columns = { },\
9038 accelerators = {\
9039 enter = 'key_enter',\
9040 [ 'control-c' ] = 'copy',\
9041 down = 'scroll_down',\
9042 up = 'scroll_up',\
9043 home = 'scroll_top',\
9044 [ 'end' ] = 'scroll_bottom',\
9045 pageUp = 'scroll_pageUp',\
9046 [ 'control-b' ] = 'scroll_pageUp',\
9047 pageDown = 'scroll_pageDown',\
9048 [ 'control-f' ] = 'scroll_pageDown',\
9049 },\
9050}\
9051function UI.Grid:setParent()\
9052 UI.Window.setParent(self)\
9053\
9054 for _,c in pairs(self.columns) do\
9055 c.cw = c.width\
9056 if not c.heading then\
9057 c.heading = ''\
9058 end\
9059 end\
9060\
9061 self:update()\
9062\
9063 if not self.pageSize then\
9064 if self.disableHeader then\
9065 self.pageSize = self.height\
9066 else\
9067 self.pageSize = self.height - 1\
9068 end\
9069 end\
9070end\
9071\
9072function UI.Grid:resize()\
9073 UI.Window.resize(self)\
9074\
9075 if self.disableHeader then\
9076 self.pageSize = self.height\
9077 else\
9078 self.pageSize = self.height - 1\
9079 end\
9080 self:adjustWidth()\
9081end\
9082\
9083function UI.Grid:adjustWidth()\
9084 local t = { } -- cols without width\
9085 local w = self.width - #self.columns - 1 - self.marginRight -- width remaining\
9086\
9087 for _,c in pairs(self.columns) do\
9088 if c.width then\
9089 c.cw = c.width\
9090 w = w - c.cw\
9091 else\
9092 table.insert(t, c)\
9093 end\
9094 end\
9095\
9096 if #t == 0 then\
9097 return\
9098 end\
9099\
9100 if #t == 1 then\
9101 t[1].cw = #(t[1].heading or '')\
9102 t[1].cw = math.max(t[1].cw, w)\
9103 return\
9104 end\
9105\
9106 if not self.autospace then\
9107 for k,c in ipairs(t) do\
9108 c.cw = math.floor(w / (#t - k + 1))\
9109 w = w - c.cw\
9110 end\
9111\
9112 else\
9113 for _,c in ipairs(t) do\
9114 c.cw = #(c.heading or '')\
9115 w = w - c.cw\
9116 end\
9117 -- adjust the size to the length of the value\
9118 for key,row in pairs(self.values) do\
9119 if w <= 0 then\
9120 break\
9121 end\
9122 row = self:getDisplayValues(row, key)\
9123 for _,col in pairs(t) do\
9124 local value = row[col.key]\
9125 if value then\
9126 value = tostring(value)\
9127 if #value > col.cw then\
9128 w = w + col.cw\
9129 col.cw = math.min(#value, w)\
9130 w = w - col.cw\
9131 if w <= 0 then\
9132 break\
9133 end\
9134 end\
9135 end\
9136 end\
9137 end\
9138\
9139 -- last column does not get padding (right alignment)\
9140 if not self.columns[#self.columns].width then\
9141 Util.removeByValue(t, self.columns[#self.columns])\
9142 end\
9143\
9144 -- got some extra room - add some padding\
9145 if w > 0 then\
9146 for k,c in ipairs(t) do\
9147 local padding = math.floor(w / (#t - k + 1))\
9148 c.cw = c.cw + padding\
9149 w = w - padding\
9150 end\
9151 end\
9152 end\
9153end\
9154\
9155function UI.Grid:setPageSize(pageSize)\
9156 self.pageSize = pageSize\
9157end\
9158\
9159function UI.Grid:getValues()\
9160 return self.values\
9161end\
9162\
9163function UI.Grid:setValues(t)\
9164 self.values = t\
9165 self:update()\
9166end\
9167\
9168function UI.Grid:setInverseSort(inverseSort)\
9169 self.inverseSort = inverseSort\
9170 self:update()\
9171 self:setIndex(self.index)\
9172end\
9173\
9174function UI.Grid:setSortColumn(column)\
9175 self.sortColumn = column\
9176end\
9177\
9178function UI.Grid:getDisplayValues(row, key)\
9179 return row\
9180end\
9181\
9182function UI.Grid:getSelected()\
9183 if self.sorted then\
9184 return self.values[self.sorted[self.index]], self.sorted[self.index]\
9185 end\
9186end\
9187\
9188function UI.Grid:setSelected(name, value)\
9189 if self.sorted then\
9190 for k,v in pairs(self.sorted) do\
9191 if self.values[v][name] == value then\
9192 self:setIndex(k)\
9193 return\
9194 end\
9195 end\
9196 end\
9197 self:setIndex(1)\
9198end\
9199\
9200function UI.Grid:focus()\
9201 self:drawRows()\
9202end\
9203\
9204function UI.Grid:draw()\
9205 if not self.disableHeader then\
9206 self:drawHeadings()\
9207 end\
9208\
9209 if self.index <= 0 then\
9210 self:setIndex(1)\
9211 elseif self.index > #self.sorted then\
9212 self:setIndex(#self.sorted)\
9213 end\
9214 self:drawRows()\
9215end\
9216\
9217-- Something about the displayed table has changed\
9218-- resort the table\
9219function UI.Grid:update()\
9220 local function sort(a, b)\
9221 if not a[self.sortColumn] then\
9222 return false\
9223 elseif not b[self.sortColumn] then\
9224 return true\
9225 end\
9226 return self:sortCompare(a, b)\
9227 end\
9228\
9229 local function inverseSort(a, b)\
9230 return not sort(a, b)\
9231 end\
9232\
9233 local order\
9234 if self.sortColumn then\
9235 order = sort\
9236 if self.inverseSort then\
9237 order = inverseSort\
9238 end\
9239 end\
9240\
9241 self.sorted = Util.keys(self.values)\
9242 if order then\
9243 table.sort(self.sorted, function(a,b)\
9244 return order(self.values[a], self.values[b])\
9245 end)\
9246 end\
9247\
9248 self:adjustWidth()\
9249end\
9250\
9251function UI.Grid:drawHeadings()\
9252 local x = 1\
9253 for _,col in ipairs(self.columns) do\
9254 local ind = ' '\
9255 if col.key == self.sortColumn then\
9256 if self.inverseSort then\
9257 ind = self.inverseSortIndicator\
9258 else\
9259 ind = self.sortIndicator\
9260 end\
9261 end\
9262 self:write(x,\
9263 1,\
9264 Util.widthify(ind .. col.heading, col.cw + 1),\
9265 self.headerBackgroundColor,\
9266 col.key == self.sortColumn and self.headerSortColor or self.headerTextColor)\
9267 x = x + col.cw + 1\
9268 end\
9269end\
9270\
9271function UI.Grid:sortCompare(a, b)\
9272 a = safeValue(a[self.sortColumn])\
9273 b = safeValue(b[self.sortColumn])\
9274 if type(a) == type(b) then\
9275 return a < b\
9276 end\
9277 return tostring(a) < tostring(b)\
9278end\
9279\
9280function UI.Grid:drawRows()\
9281 local y = 1\
9282 local startRow = math.max(1, self:getStartRow())\
9283 local sb = UI.StringBuffer(self.width)\
9284\
9285 if not self.disableHeader then\
9286 y = y + 1\
9287 end\
9288\
9289 local lastRow = math.min(startRow + self.pageSize - 1, #self.sorted)\
9290 for index = startRow, lastRow do\
9291\
9292 local sindex = self.sorted[index]\
9293 local rawRow = self.values[sindex]\
9294 local key = sindex\
9295 local row = self:getDisplayValues(rawRow, key)\
9296\
9297 sb:clear()\
9298\
9299 local ind = ' '\
9300 if self.focused and index == self.index and not self.inactive then\
9301 ind = self.focusIndicator\
9302 end\
9303\
9304 for _,col in pairs(self.columns) do\
9305 if col.justify == 'right' then\
9306 sb:insertRight(ind .. safeValue(row[col.key] or ''), col.cw + 1)\
9307 else\
9308 sb:insert(ind .. safeValue(row[col.key] or ''), col.cw + 1)\
9309 end\
9310 ind = ' '\
9311 end\
9312\
9313 local selected = index == self.index and not self.inactive\
9314\
9315 self:write(1, y, sb:get(),\
9316 self:getRowBackgroundColor(rawRow, selected),\
9317 self:getRowTextColor(rawRow, selected))\
9318\
9319 y = y + 1\
9320 end\
9321\
9322 if y <= self.height then\
9323 self:clearArea(1, y, self.width, self.height - y + 1)\
9324 end\
9325end\
9326\
9327function UI.Grid:getRowTextColor(row, selected)\
9328 if selected then\
9329 if self.focused then\
9330 return self.textSelectedColor\
9331 end\
9332 return self.unfocusedTextSelectedColor\
9333 end\
9334 return self.textColor\
9335end\
9336\
9337function UI.Grid:getRowBackgroundColor(row, selected)\
9338 if selected then\
9339 if self.focused then\
9340 return self.backgroundSelectedColor\
9341 end\
9342 return self.unfocusedBackgroundSelectedColor\
9343 end\
9344 return self.backgroundColor\
9345end\
9346\
9347function UI.Grid:getIndex()\
9348 return self.index\
9349end\
9350\
9351function UI.Grid:setIndex(index)\
9352 index = math.max(1, index)\
9353 self.index = math.min(index, #self.sorted)\
9354\
9355 local selected = self:getSelected()\
9356 if selected ~= self.selected then\
9357 self:drawRows()\
9358 self.selected = selected\
9359 if selected then\
9360 self:emit({ type = 'grid_focus_row', selected = selected, element = self })\
9361 end\
9362 end\
9363end\
9364\
9365function UI.Grid:getStartRow()\
9366 return math.floor((self.index - 1) / self.pageSize) * self.pageSize + 1\
9367end\
9368\
9369function UI.Grid:getPage()\
9370 return math.floor(self.index / self.pageSize) + 1\
9371end\
9372\
9373function UI.Grid:getPageCount()\
9374 local tableSize = Util.size(self.values)\
9375 local pc = math.floor(tableSize / self.pageSize)\
9376 if tableSize % self.pageSize > 0 then\
9377 pc = pc + 1\
9378 end\
9379 return pc\
9380end\
9381\
9382function UI.Grid:nextPage()\
9383 self:setPage(self:getPage() + 1)\
9384end\
9385\
9386function UI.Grid:previousPage()\
9387 self:setPage(self:getPage() - 1)\
9388end\
9389\
9390function UI.Grid:setPage(pageNo)\
9391 -- 1 based paging\
9392 self:setIndex((pageNo-1) * self.pageSize + 1)\
9393end\
9394\
9395function UI.Grid:eventHandler(event)\
9396 if event.type == 'mouse_click' or\
9397 event.type == 'mouse_rightclick' or\
9398 event.type == 'mouse_doubleclick' then\
9399 if not self.disableHeader then\
9400 if event.y == 1 then\
9401 local col = 2\
9402 for _,c in ipairs(self.columns) do\
9403 if event.x < col + c.cw then\
9404 self:emit({\
9405 type = 'grid_sort',\
9406 sortColumn = c.key,\
9407 inverseSort = self.sortColumn == c.key and not self.inverseSort,\
9408 element = self,\
9409 })\
9410 break\
9411 end\
9412 col = col + c.cw + 1\
9413 end\
9414 return true\
9415 end\
9416 end\
9417 local row = self:getStartRow() + event.y - 1\
9418 if not self.disableHeader then\
9419 row = row - 1\
9420 end\
9421 if row > 0 and row <= Util.size(self.values) then\
9422 self:setIndex(row)\
9423 if event.type == 'mouse_doubleclick' then\
9424 self:emit({ type = 'key_enter' })\
9425 elseif event.type == 'mouse_rightclick' then\
9426 self:emit({ type = 'grid_select_right', selected = self.selected, element = self })\
9427 end\
9428 return true\
9429 end\
9430 return false\
9431\
9432 elseif event.type == 'grid_sort' then\
9433 self.sortColumn = event.sortColumn\
9434 self:setInverseSort(event.inverseSort)\
9435 self:draw()\
9436 elseif event.type == 'scroll_down' then\
9437 self:setIndex(self.index + 1)\
9438 elseif event.type == 'scroll_up' then\
9439 self:setIndex(self.index - 1)\
9440 elseif event.type == 'scroll_top' then\
9441 self:setIndex(1)\
9442 elseif event.type == 'scroll_bottom' then\
9443 self:setIndex(Util.size(self.values))\
9444 elseif event.type == 'scroll_pageUp' then\
9445 self:setIndex(self.index - self.pageSize)\
9446 elseif event.type == 'scroll_pageDown' then\
9447 self:setIndex(self.index + self.pageSize)\
9448 elseif event.type == 'key_enter' then\
9449 if self.selected then\
9450 self:emit({ type = 'grid_select', selected = self.selected, element = self })\
9451 end\
9452 elseif event.type == 'copy' then\
9453 if self.selected then\
9454 os.queueEvent('clipboard_copy', self.selected)\
9455 end\
9456 else\
9457 return false\
9458 end\
9459 return true\
9460end\
9461\
9462--[[-- ScrollingGrid --]]--\
9463UI.ScrollingGrid = class(UI.Grid)\
9464UI.ScrollingGrid.defaults = {\
9465 UIElement = 'ScrollingGrid',\
9466 scrollOffset = 0,\
9467 marginRight = 1,\
9468}\
9469function UI.ScrollingGrid:postInit()\
9470 self.scrollBar = UI.ScrollBar()\
9471end\
9472\
9473function UI.ScrollingGrid:drawRows()\
9474 UI.Grid.drawRows(self)\
9475 self.scrollBar:draw()\
9476end\
9477\
9478function UI.ScrollingGrid:getViewArea()\
9479 local y = 1\
9480 if not self.disableHeader then\
9481 y = 2\
9482 end\
9483 return {\
9484 static = true, -- the container doesn't scroll\
9485 y = y, -- scrollbar Y\
9486 height = self.pageSize, -- viewable height\
9487 totalHeight = Util.size(self.values), -- total height\
9488 offsetY = self.scrollOffset, -- scroll offset\
9489 }\
9490end\
9491\
9492function UI.ScrollingGrid:getStartRow()\
9493 local ts = Util.size(self.values)\
9494 if ts < self.pageSize then\
9495 self.scrollOffset = 0\
9496 end\
9497 return self.scrollOffset + 1\
9498end\
9499\
9500function UI.ScrollingGrid:setIndex(index)\
9501 if index < self.scrollOffset + 1 then\
9502 self.scrollOffset = index - 1\
9503 elseif index - self.scrollOffset > self.pageSize then\
9504 self.scrollOffset = index - self.pageSize\
9505 end\
9506\
9507 if self.scrollOffset < 0 then\
9508 self.scrollOffset = 0\
9509 else\
9510 local ts = Util.size(self.values)\
9511 if self.pageSize + self.scrollOffset + 1 > ts then\
9512 self.scrollOffset = math.max(0, ts - self.pageSize)\
9513 end\
9514 end\
9515 UI.Grid.setIndex(self, index)\
9516end\
9517\
9518--[[-- Menu --]]--\
9519UI.Menu = class(UI.Grid)\
9520UI.Menu.defaults = {\
9521 UIElement = 'Menu',\
9522 disableHeader = true,\
9523 columns = { { heading = 'Prompt', key = 'prompt', width = 20 } },\
9524}\
9525function UI.Menu:postInit()\
9526 self.values = self.menuItems\
9527 self.pageSize = #self.menuItems\
9528end\
9529\
9530function UI.Menu:setParent()\
9531 UI.Grid.setParent(self)\
9532 self.itemWidth = 1\
9533 for _,v in pairs(self.values) do\
9534 if #v.prompt > self.itemWidth then\
9535 self.itemWidth = #v.prompt\
9536 end\
9537 end\
9538 self.columns[1].width = self.itemWidth\
9539\
9540 if self.centered then\
9541 self:center()\
9542 else\
9543 self.width = self.itemWidth + 2\
9544 end\
9545end\
9546\
9547function UI.Menu:center()\
9548 self.x = (self.width - self.itemWidth + 2) / 2\
9549 self.width = self.itemWidth + 2\
9550end\
9551\
9552function UI.Menu:eventHandler(event)\
9553 if event.type == 'key' then\
9554 if event.key == 'enter' then\
9555 local selected = self.menuItems[self.index]\
9556 self:emit({\
9557 type = selected.event or 'menu_select',\
9558 selected = selected\
9559 })\
9560 return true\
9561 end\
9562 elseif event.type == 'mouse_click' then\
9563 if event.y <= #self.menuItems then\
9564 UI.Grid.setIndex(self, event.y)\
9565 local selected = self.menuItems[self.index]\
9566 self:emit({\
9567 type = selected.event or 'menu_select',\
9568 selected = selected\
9569 })\
9570 return true\
9571 end\
9572 end\
9573 return UI.Grid.eventHandler(self, event)\
9574end\
9575\
9576--[[-- Viewport --]]--\
9577UI.Viewport = class(UI.Window)\
9578UI.Viewport.defaults = {\
9579 UIElement = 'Viewport',\
9580 backgroundColor = colors.cyan,\
9581 accelerators = {\
9582 down = 'scroll_down',\
9583 up = 'scroll_up',\
9584 home = 'scroll_top',\
9585 [ 'end' ] = 'scroll_bottom',\
9586 pageUp = 'scroll_pageUp',\
9587 [ 'control-b' ] = 'scroll_pageUp',\
9588 pageDown = 'scroll_pageDown',\
9589 [ 'control-f' ] = 'scroll_pageDown',\
9590 },\
9591}\
9592function UI.Viewport:setScrollPosition(offset)\
9593 local oldOffset = self.offy\
9594 self.offy = math.max(offset, 0)\
9595 local max = self.ymax or self.height\
9596 if self.children then\
9597 for _, child in ipairs(self.children) do\
9598 if child ~= self.scrollBar then -- hack !\
9599 max = math.max(child.y + child.height - 1, max)\
9600 end\
9601 end\
9602 end\
9603 self.offy = math.min(self.offy, math.max(max, self.height) - self.height)\
9604 if self.offy ~= oldOffset then\
9605 self:draw()\
9606 end\
9607end\
9608\
9609function UI.Viewport:reset()\
9610 self.offy = 0\
9611end\
9612\
9613function UI.Viewport:getViewArea()\
9614 return {\
9615 y = (self.offy or 0) + 1,\
9616 height = self.height,\
9617 totalHeight = self.ymax,\
9618 offsetY = self.offy or 0,\
9619 }\
9620end\
9621\
9622function UI.Viewport:eventHandler(event)\
9623 if event.type == 'scroll_down' then\
9624 self:setScrollPosition(self.offy + 1)\
9625 elseif event.type == 'scroll_up' then\
9626 self:setScrollPosition(self.offy - 1)\
9627 elseif event.type == 'scroll_top' then\
9628 self:setScrollPosition(0)\
9629 elseif event.type == 'scroll_bottom' then\
9630 self:setScrollPosition(10000000)\
9631 elseif event.type == 'scroll_pageUp' then\
9632 self:setScrollPosition(self.offy - self.height)\
9633 elseif event.type == 'scroll_pageDown' then\
9634 self:setScrollPosition(self.offy + self.height)\
9635 else\
9636 return false\
9637 end\
9638 return true\
9639end\
9640\
9641--[[-- TitleBar --]]--\
9642UI.TitleBar = class(UI.Window)\
9643UI.TitleBar.defaults = {\
9644 UIElement = 'TitleBar',\
9645 height = 1,\
9646 textColor = colors.white,\
9647 backgroundColor = colors.cyan,\
9648 title = '',\
9649 frameChar = '-',\
9650 closeInd = '*',\
9651}\
9652function UI.TitleBar:draw()\
9653 local sb = SB:new(self.width)\
9654 sb:fill(2, self.frameChar, sb.width - 3)\
9655 sb:center(string.format(' %s ', self.title))\
9656 if self.previousPage or self.event then\
9657 sb:insert(-1, self.closeInd)\
9658 else\
9659 sb:insert(-2, self.frameChar)\
9660 end\
9661 self:write(1, 1, sb:get())\
9662end\
9663\
9664function UI.TitleBar:eventHandler(event)\
9665 if event.type == 'mouse_click' then\
9666 if (self.previousPage or self.event) and event.x == self.width then\
9667 if self.event then\
9668 self:emit({ type = self.event, element = self })\
9669 elseif type(self.previousPage) == 'string' or\
9670 type(self.previousPage) == 'table' then\
9671 UI:setPage(self.previousPage)\
9672 else\
9673 UI:setPreviousPage()\
9674 end\
9675 return true\
9676 end\
9677 end\
9678end\
9679\
9680--[[-- Button --]]--\
9681UI.Button = class(UI.Window)\
9682UI.Button.defaults = {\
9683 UIElement = 'Button',\
9684 text = 'button',\
9685 backgroundColor = colors.lightGray,\
9686 backgroundFocusColor = colors.gray,\
9687 textFocusColor = colors.white,\
9688 textInactiveColor = colors.gray,\
9689 textColor = colors.black,\
9690 centered = true,\
9691 height = 1,\
9692 focusIndicator = ' ',\
9693 event = 'button_press',\
9694 accelerators = {\
9695 space = 'button_activate',\
9696 enter = 'button_activate',\
9697 mouse_click = 'button_activate',\
9698 }\
9699}\
9700function UI.Button:setParent()\
9701 if not self.width and not self.ex then\
9702 self.width = #self.text + 2\
9703 end\
9704 UI.Window.setParent(self)\
9705end\
9706\
9707function UI.Button:draw()\
9708 local fg = self.textColor\
9709 local bg = self.backgroundColor\
9710 local ind = ' '\
9711 if self.focused then\
9712 bg = self.backgroundFocusColor\
9713 fg = self.textFocusColor\
9714 ind = self.focusIndicator\
9715 elseif self.inactive then\
9716 fg = self.textInactiveColor\
9717 end\
9718 local text = ind .. self.text .. ' '\
9719 if self.centered then\
9720 self:clear(bg)\
9721 self:centeredWrite(1 + math.floor(self.height / 2), text, bg, fg)\
9722 else\
9723 self:write(1, 1, Util.widthify(text, self.width), bg, fg)\
9724 end\
9725end\
9726\
9727function UI.Button:focus()\
9728 if self.focused then\
9729 self:scrollIntoView()\
9730 end\
9731 self:draw()\
9732end\
9733\
9734function UI.Button:eventHandler(event)\
9735 if event.type == 'button_activate' then\
9736 self:emit({ type = self.event, button = self })\
9737 return true\
9738 end\
9739 return false\
9740end\
9741\
9742--[[-- MenuItem --]]--\
9743UI.MenuItem = class(UI.Button)\
9744UI.MenuItem.defaults = {\
9745 UIElement = 'MenuItem',\
9746 textColor = colors.black,\
9747 backgroundColor = colors.lightGray,\
9748 textFocusColor = colors.white,\
9749 backgroundFocusColor = colors.lightGray,\
9750}\
9751\
9752--[[-- MenuBar --]]--\
9753UI.MenuBar = class(UI.Window)\
9754UI.MenuBar.defaults = {\
9755 UIElement = 'MenuBar',\
9756 buttons = { },\
9757 height = 1,\
9758 backgroundColor = colors.lightGray,\
9759 textColor = colors.black,\
9760 spacing = 2,\
9761 lastx = 1,\
9762 showBackButton = false,\
9763 buttonClass = 'MenuItem',\
9764}\
9765UI.MenuBar.spacer = { spacer = true, text = 'spacer', inactive = true }\
9766\
9767function UI.MenuBar:postInit()\
9768 self:addButtons(self.buttons)\
9769end\
9770\
9771function UI.MenuBar:addButtons(buttons)\
9772 if not self.children then\
9773 self.children = { }\
9774 end\
9775\
9776 for _,button in pairs(buttons) do\
9777 if button.UIElement then\
9778 table.insert(self.children, button)\
9779 else\
9780 local buttonProperties = {\
9781 x = self.lastx,\
9782 width = #button.text + self.spacing,\
9783 centered = false,\
9784 }\
9785 self.lastx = self.lastx + buttonProperties.width\
9786 UI:mergeProperties(buttonProperties, button)\
9787\
9788 button = UI[self.buttonClass](buttonProperties)\
9789 if button.name then\
9790 self[button.name] = button\
9791 else\
9792 table.insert(self.children, button)\
9793 end\
9794\
9795 if button.dropdown then\
9796 button.dropmenu = UI.DropMenu { buttons = button.dropdown }\
9797 end\
9798 end\
9799 end\
9800 if self.parent then\
9801 self:initChildren()\
9802 end\
9803end\
9804\
9805function UI.MenuBar:getActive(menuItem)\
9806 return not menuItem.inactive\
9807end\
9808\
9809function UI.MenuBar:eventHandler(event)\
9810 if event.type == 'button_press' and event.button.dropmenu then\
9811 if event.button.dropmenu.enabled then\
9812 event.button.dropmenu:hide()\
9813 return true\
9814 else\
9815 local x, y = getPosition(event.button)\
9816 if x + event.button.dropmenu.width > self.width then\
9817 x = self.width - event.button.dropmenu.width + 1\
9818 end\
9819 for _,c in pairs(event.button.dropmenu.children) do\
9820 if not c.spacer then\
9821 c.inactive = not self:getActive(c)\
9822 end\
9823 end\
9824 event.button.dropmenu:show(x, y + 1)\
9825 end\
9826 return true\
9827 end\
9828end\
9829\
9830--[[-- DropMenuItem --]]--\
9831UI.DropMenuItem = class(UI.Button)\
9832UI.DropMenuItem.defaults = {\
9833 UIElement = 'DropMenuItem',\
9834 textColor = colors.black,\
9835 backgroundColor = colors.white,\
9836 textFocusColor = colors.white,\
9837 textInactiveColor = colors.lightGray,\
9838 backgroundFocusColor = colors.lightGray,\
9839}\
9840function UI.DropMenuItem:eventHandler(event)\
9841 if event.type == 'button_activate' then\
9842 self.parent:hide()\
9843 end\
9844 return UI.Button.eventHandler(self, event)\
9845end\
9846\
9847--[[-- DropMenu --]]--\
9848UI.DropMenu = class(UI.MenuBar)\
9849UI.DropMenu.defaults = {\
9850 UIElement = 'DropMenu',\
9851 backgroundColor = colors.white,\
9852 buttonClass = 'DropMenuItem',\
9853}\
9854function UI.DropMenu:setParent()\
9855 UI.MenuBar.setParent(self)\
9856\
9857 local maxWidth = 1\
9858 for y,child in ipairs(self.children) do\
9859 child.x = 1\
9860 child.y = y\
9861 if #(child.text or '') > maxWidth then\
9862 maxWidth = #child.text\
9863 end\
9864 end\
9865 for _,child in ipairs(self.children) do\
9866 child.width = maxWidth + 2\
9867 if child.spacer then\
9868 child.text = string.rep('-', child.width - 2)\
9869 end\
9870 end\
9871\
9872 self.height = #self.children + 1\
9873 self.width = maxWidth + 2\
9874 self.ow = self.width\
9875\
9876 self.canvas = self:addLayer()\
9877end\
9878\
9879function UI.DropMenu:enable()\
9880 self.enabled = false\
9881end\
9882\
9883function UI.DropMenu:show(x, y)\
9884 self.x, self.y = x, y\
9885 self.canvas:move(x, y)\
9886 self.canvas:setVisible(true)\
9887\
9888 self.enabled = true\
9889 for _,child in pairs(self.children) do\
9890 child:enable()\
9891 end\
9892\
9893 self:draw()\
9894 self:capture(self)\
9895 self:focusFirst()\
9896end\
9897\
9898function UI.DropMenu:hide()\
9899 self:disable()\
9900 self.canvas:setVisible(false)\
9901 self:release(self)\
9902end\
9903\
9904function UI.DropMenu:eventHandler(event)\
9905 if event.type == 'focus_lost' and self.enabled then\
9906 if not Util.contains(self.children, event.focused) then\
9907 self:hide()\
9908 end\
9909 elseif event.type == 'mouse_out' and self.enabled then\
9910 self:hide()\
9911 self:refocus()\
9912 else\
9913 return UI.MenuBar.eventHandler(self, event)\
9914 end\
9915 return true\
9916end\
9917\
9918--[[-- TabBarMenuItem --]]--\
9919UI.TabBarMenuItem = class(UI.Button)\
9920UI.TabBarMenuItem.defaults = {\
9921 UIElement = 'TabBarMenuItem',\
9922 event = 'tab_select',\
9923 textColor = colors.black,\
9924 selectedBackgroundColor = colors.cyan,\
9925 unselectedBackgroundColor = colors.lightGray,\
9926 backgroundColor = colors.lightGray,\
9927}\
9928function UI.TabBarMenuItem:draw()\
9929 if self.selected then\
9930 self.backgroundColor = self.selectedBackgroundColor\
9931 self.backgroundFocusColor = self.selectedBackgroundColor\
9932 else\
9933 self.backgroundColor = self.unselectedBackgroundColor\
9934 self.backgroundFocusColor = self.unselectedBackgroundColor\
9935 end\
9936 UI.Button.draw(self)\
9937end\
9938\
9939--[[-- TabBar --]]--\
9940UI.TabBar = class(UI.MenuBar)\
9941UI.TabBar.defaults = {\
9942 UIElement = 'TabBar',\
9943 buttonClass = 'TabBarMenuItem',\
9944 selectedBackgroundColor = colors.cyan,\
9945}\
9946function UI.TabBar:enable()\
9947 UI.MenuBar.enable(self)\
9948 if not Util.find(self.children, 'selected', true) then\
9949 local menuItem = self:getFocusables()[1]\
9950 if menuItem then\
9951 menuItem.selected = true\
9952 end\
9953 end\
9954end\
9955\
9956function UI.TabBar:eventHandler(event)\
9957 if event.type == 'tab_select' then\
9958 local selected, si = Util.find(self:getFocusables(), 'uid', event.button.uid)\
9959 local previous, pi = Util.find(self:getFocusables(), 'selected', true)\
9960\
9961 if si ~= pi then\
9962 selected.selected = true\
9963 previous.selected = false\
9964 self:emit({ type = 'tab_change', current = si, last = pi, tab = selected })\
9965 end\
9966 UI.MenuBar.draw(self)\
9967 end\
9968 return UI.MenuBar.eventHandler(self, event)\
9969end\
9970\
9971function UI.TabBar:selectTab(text)\
9972 local menuItem = Util.find(self.children, 'text', text)\
9973 if menuItem then\
9974 menuItem.selected = true\
9975 end\
9976end\
9977\
9978--[[-- Tabs --]]--\
9979UI.Tabs = class(UI.Window)\
9980UI.Tabs.defaults = {\
9981 UIElement = 'Tabs',\
9982}\
9983function UI.Tabs:postInit()\
9984 self:add(self)\
9985end\
9986\
9987function UI.Tabs:add(children)\
9988 local buttons = { }\
9989 for _,child in pairs(children) do\
9990 if type(child) == 'table' and child.UIElement and child.tabTitle then\
9991 child.y = 2\
9992 table.insert(buttons, {\
9993 text = child.tabTitle,\
9994 event = 'tab_select',\
9995 tabUid = child.uid,\
9996 })\
9997 end\
9998 end\
9999\
10000 if not self.tabBar then\
10001 self.tabBar = UI.TabBar({\
10002 buttons = buttons,\
10003 })\
10004 else\
10005 self.tabBar:addButtons(buttons)\
10006 end\
10007\
10008 if self.parent then\
10009 return UI.Window.add(self, children)\
10010 end\
10011end\
10012\
10013function UI.Tabs:enable()\
10014 self.enabled = true\
10015 self.tabBar:enable()\
10016\
10017 local menuItem = Util.find(self.tabBar.children, 'selected', true)\
10018\
10019 for _,child in pairs(self.children) do\
10020 if child.uid == menuItem.tabUid then\
10021 child:enable()\
10022 self:emit({ type = 'tab_activate', activated = child })\
10023 elseif child.tabTitle then\
10024 child:disable()\
10025 end\
10026 end\
10027end\
10028\
10029function UI.Tabs:eventHandler(event)\
10030 if event.type == 'tab_change' then\
10031 local tab = self:find(event.tab.tabUid)\
10032 if event.current > event.last then\
10033 tab:addTransition('slideLeft')\
10034 else\
10035 tab:addTransition('slideRight')\
10036 end\
10037\
10038 for _,child in pairs(self.children) do\
10039 if child.uid == event.tab.tabUid then\
10040 child:enable()\
10041 elseif child.tabTitle then\
10042 child:disable()\
10043 end\
10044 end\
10045 self:emit({ type = 'tab_activate', activated = tab })\
10046 tab:draw()\
10047 end\
10048end\
10049\
10050--[[-- Wizard --]]--\
10051UI.Wizard = class(UI.Window)\
10052UI.Wizard.defaults = {\
10053 UIElement = 'Wizard',\
10054 pages = { },\
10055}\
10056function UI.Wizard:postInit()\
10057 self.cancelButton = UI.Button {\
10058 x = 2, y = -1,\
10059 text = 'Cancel',\
10060 event = 'cancel',\
10061 }\
10062 self.previousButton = UI.Button {\
10063 x = -18, y = -1,\
10064 text = '< Back',\
10065 event = 'previousView',\
10066 }\
10067 self.nextButton = UI.Button {\
10068 x = -9, y = -1,\
10069 text = 'Next >',\
10070 event = 'nextView',\
10071 }\
10072\
10073 Util.merge(self, self.pages)\
10074 for _, child in pairs(self.pages) do\
10075 child.ey = -2\
10076 end\
10077end\
10078\
10079function UI.Wizard:add(pages)\
10080 Util.merge(self.pages, pages)\
10081 Util.merge(self, pages)\
10082\
10083 for _, child in pairs(self.pages) do\
10084 child.ey = child.ey or -2\
10085 end\
10086\
10087 if self.parent then\
10088 self:initChildren()\
10089 end\
10090end\
10091\
10092function UI.Wizard:getPage(index)\
10093 return Util.find(self.pages, 'index', index)\
10094end\
10095\
10096function UI.Wizard:enable(...)\
10097 self.enabled = true\
10098 self.index = 1\
10099 local initial = self:getPage(1)\
10100 for _,child in pairs(self.children) do\
10101 if child == initial or not child.index then\
10102 child:enable(...)\
10103 else\
10104 child:disable()\
10105 end\
10106 end\
10107 self:emit({ type = 'enable_view', next = initial })\
10108end\
10109\
10110function UI.Wizard:isViewValid()\
10111 local currentView = self:getPage(self.index)\
10112 return not currentView.validate and true or currentView:validate()\
10113end\
10114\
10115function UI.Wizard:eventHandler(event)\
10116 if event.type == 'nextView' then\
10117 local currentView = self:getPage(self.index)\
10118 if self:isViewValid() then\
10119 self.index = self.index + 1\
10120 local nextView = self:getPage(self.index)\
10121 currentView:emit({ type = 'enable_view', next = nextView, current = currentView })\
10122 end\
10123\
10124 elseif event.type == 'previousView' then\
10125 local currentView = self:getPage(self.index)\
10126 local nextView = self:getPage(self.index - 1)\
10127 if nextView then\
10128 self.index = self.index - 1\
10129 currentView:emit({ type = 'enable_view', prev = nextView, current = currentView })\
10130 end\
10131 return true\
10132\
10133 elseif event.type == 'wizard_complete' then\
10134 if self:isViewValid() then\
10135 self:emit({ type = 'accept' })\
10136 end\
10137\
10138 elseif event.type == 'enable_view' then\
10139 if event.current then\
10140 if event.next then\
10141 self:addTransition('slideLeft')\
10142 elseif event.prev then\
10143 self:addTransition('slideRight')\
10144 end\
10145 event.current:disable()\
10146 end\
10147\
10148 local current = event.next or event.prev\
10149 if not current then error('property \"index\" is required on wizard pages') end\
10150\
10151 if self:getPage(self.index - 1) then\
10152 self.previousButton:enable()\
10153 else\
10154 self.previousButton:disable()\
10155 end\
10156\
10157 if self:getPage(self.index + 1) then\
10158 self.nextButton.text = 'Next >'\
10159 self.nextButton.event = 'nextView'\
10160 else\
10161 self.nextButton.text = 'Accept'\
10162 self.nextButton.event = 'wizard_complete'\
10163 end\
10164 -- a new current view\
10165 current:enable()\
10166 self:draw()\
10167 end\
10168end\
10169\
10170--[[-- SlideOut --]]--\
10171UI.SlideOut = class(UI.Window)\
10172UI.SlideOut.defaults = {\
10173 UIElement = 'SlideOut',\
10174 pageType = 'modal',\
10175}\
10176function UI.SlideOut:setParent()\
10177 UI.Window.setParent(self)\
10178 self.canvas = self:addLayer()\
10179end\
10180\
10181function UI.SlideOut:enable()\
10182 self.enabled = false\
10183end\
10184\
10185function UI.SlideOut:show(...)\
10186 self:addTransition('expandUp')\
10187 self.canvas:setVisible(true)\
10188 self.enabled = true\
10189 for _,child in pairs(self.children) do\
10190 child:enable(...)\
10191 end\
10192 self:draw()\
10193 self:capture(self)\
10194 self:focusFirst()\
10195end\
10196\
10197function UI.SlideOut:disable()\
10198 self.canvas:setVisible(false)\
10199 self.enabled = false\
10200 if self.children then\
10201 for _,child in pairs(self.children) do\
10202 child:disable()\
10203 end\
10204 end\
10205end\
10206\
10207function UI.SlideOut:hide()\
10208 self:disable()\
10209 self:release(self)\
10210 self:refocus()\
10211end\
10212\
10213function UI.SlideOut:eventHandler(event)\
10214 if event.type == 'slide_show' then\
10215 self:show()\
10216 return true\
10217\
10218 elseif event.type == 'slide_hide' then\
10219 self:hide()\
10220 return true\
10221 end\
10222end\
10223\
10224--[[-- Embedded --]]--\
10225UI.Embedded = class(UI.Window)\
10226UI.Embedded.defaults = {\
10227 UIElement = 'Embedded',\
10228 backgroundColor = colors.black,\
10229 textColor = colors.white,\
10230 accelerators = {\
10231 up = 'scroll_up',\
10232 down = 'scroll_down',\
10233 }\
10234}\
10235\
10236function UI.Embedded:setParent()\
10237 UI.Window.setParent(self)\
10238 self.win = window.create(UI.term.device, 1, 1, self.width, self.height, false)\
10239 Canvas.scrollingWindow(self.win, self.x, self.y)\
10240 self.win.setParent(UI.term.device)\
10241 self.win.setMaxScroll(100)\
10242\
10243 local canvas = self:getCanvas()\
10244 self.win.canvas.parent = canvas\
10245 table.insert(canvas.layers, self.win.canvas)\
10246 self.canvas = self.win.canvas\
10247\
10248 self.win.setCursorPos(1, 1)\
10249 self.win.setBackgroundColor(self.backgroundColor)\
10250 self.win.setTextColor(self.textColor)\
10251 self.win.clear()\
10252end\
10253\
10254function UI.Embedded:draw()\
10255 self.canvas:dirty()\
10256end\
10257\
10258function UI.Embedded:enable()\
10259 self.canvas:setVisible(true)\
10260 UI.Window.enable(self)\
10261end\
10262\
10263function UI.Embedded:disable()\
10264 self.canvas:setVisible(false)\
10265 UI.Window.disable(self)\
10266end\
10267\
10268function UI.Embedded:eventHandler(event)\
10269 if event.type == 'scroll_up' then\
10270 self.win.scrollUp()\
10271 return true\
10272 elseif event.type == 'scroll_down' then\
10273 self.win.scrollDown()\
10274 return true\
10275 end\
10276end\
10277\
10278function UI.Embedded:focus()\
10279 -- allow scrolling\
10280end\
10281\
10282--[[-- Notification --]]--\
10283UI.Notification = class(UI.Window)\
10284UI.Notification.defaults = {\
10285 UIElement = 'Notification',\
10286 backgroundColor = colors.gray,\
10287 height = 3,\
10288}\
10289function UI.Notification:draw()\
10290end\
10291\
10292function UI.Notification:enable()\
10293 self.enabled = false\
10294end\
10295\
10296function UI.Notification:error(value, timeout)\
10297 self.backgroundColor = colors.red\
10298 Sound.play('entity.villager.no', .5)\
10299 self:display(value, timeout)\
10300end\
10301\
10302function UI.Notification:info(value, timeout)\
10303 self.backgroundColor = colors.gray\
10304 self:display(value, timeout)\
10305end\
10306\
10307function UI.Notification:success(value, timeout)\
10308 self.backgroundColor = colors.green\
10309 self:display(value, timeout)\
10310end\
10311\
10312function UI.Notification:cancel()\
10313 if self.canvas then\
10314 Event.cancelNamedTimer('notificationTimer')\
10315 self.enabled = false\
10316 self.canvas:removeLayer()\
10317 self.canvas = nil\
10318 end\
10319end\
10320\
10321function UI.Notification:display(value, timeout)\
10322 self.enabled = true\
10323 local lines = Util.wordWrap(value, self.width - 2)\
10324 self.height = #lines + 1\
10325 self.y = self.parent.height - self.height + 1\
10326 if self.canvas then\
10327 self.canvas:removeLayer()\
10328 end\
10329\
10330 self.canvas = self:addLayer(self.backgroundColor, self.textColor)\
10331 self:addTransition('expandUp', { ticks = self.height })\
10332 self.canvas:setVisible(true)\
10333 self:clear()\
10334 for k,v in pairs(lines) do\
10335 self:write(2, k, v)\
10336 end\
10337\
10338 Event.addNamedTimer('notificationTimer', timeout or 3, false, function()\
10339 self:cancel()\
10340 self:sync()\
10341 end)\
10342end\
10343\
10344--[[-- Throttle --]]--\
10345UI.Throttle = class(UI.Window)\
10346UI.Throttle.defaults = {\
10347 UIElement = 'Throttle',\
10348 backgroundColor = colors.gray,\
10349 bordercolor = colors.cyan,\
10350 height = 4,\
10351 width = 10,\
10352 timeout = .075,\
10353 ctr = 0,\
10354 image = {\
10355 ' //) (O )~@ &~&-( ?Q ',\
10356 ' //) (O )- @ \\\\-( ?) && ',\
10357 ' //) (O ), @ \\\\-(?) && ',\
10358 ' //) (O ). @ \\\\-d ) (@ '\
10359 }\
10360}\
10361function UI.Throttle:setParent()\
10362 self.x = math.ceil((self.parent.width - self.width) / 2)\
10363 self.y = math.ceil((self.parent.height - self.height) / 2)\
10364 UI.Window.setParent(self)\
10365end\
10366\
10367function UI.Throttle:enable()\
10368 self.c = os.clock()\
10369 self.enabled = false\
10370end\
10371\
10372function UI.Throttle:disable()\
10373 if self.canvas then\
10374 self.enabled = false\
10375 self.canvas:removeLayer()\
10376 self.canvas = nil\
10377 self.ctr = 0\
10378 end\
10379end\
10380\
10381function UI.Throttle:update()\
10382 local cc = os.clock()\
10383 if cc > self.c + self.timeout then\
10384 os.sleep(0)\
10385 self.c = os.clock()\
10386 self.enabled = true\
10387 if not self.canvas then\
10388 self.canvas = self:addLayer(self.backgroundColor, self.borderColor)\
10389 self.canvas:setVisible(true)\
10390 self:clear(self.borderColor)\
10391 end\
10392 local image = self.image[self.ctr + 1]\
10393 local width = self.width - 2\
10394 for i = 0, #self.image do\
10395 self:write(2, i + 1, image:sub(width * i + 1, width * i + width),\
10396 self.backgroundColor, self.textColor)\
10397 end\
10398\
10399 self.ctr = (self.ctr + 1) % #self.image\
10400\
10401 self:sync()\
10402 end\
10403end\
10404\
10405--[[-- StatusBar --]]--\
10406UI.StatusBar = class(UI.Window)\
10407UI.StatusBar.defaults = {\
10408 UIElement = 'StatusBar',\
10409 backgroundColor = colors.lightGray,\
10410 textColor = colors.gray,\
10411 height = 1,\
10412 ey = -1,\
10413}\
10414function UI.StatusBar:adjustWidth()\
10415 -- Can only have 1 adjustable width\
10416 if self.columns then\
10417 local w = self.width - #self.columns - 1\
10418 for _,c in pairs(self.columns) do\
10419 if c.width then\
10420 c.cw = c.width -- computed width\
10421 w = w - c.width\
10422 end\
10423 end\
10424 for _,c in pairs(self.columns) do\
10425 if not c.width then\
10426 c.cw = w\
10427 end\
10428 end\
10429 end\
10430end\
10431\
10432function UI.StatusBar:resize()\
10433 UI.Window.resize(self)\
10434 self:adjustWidth()\
10435end\
10436\
10437function UI.StatusBar:setParent()\
10438 UI.Window.setParent(self)\
10439 self:adjustWidth()\
10440end\
10441\
10442function UI.StatusBar:setStatus(status)\
10443 if self.values ~= status then\
10444 self.values = status\
10445 self:draw()\
10446 end\
10447end\
10448\
10449function UI.StatusBar:setValue(name, value)\
10450 if not self.values then\
10451 self.values = { }\
10452 end\
10453 self.values[name] = value\
10454end\
10455\
10456function UI.StatusBar:getValue(name)\
10457 if self.values then\
10458 return self.values[name]\
10459 end\
10460end\
10461\
10462function UI.StatusBar:timedStatus(status, timeout)\
10463 timeout = timeout or 3\
10464 self:write(2, 1, Util.widthify(status, self.width-2), self.backgroundColor)\
10465 Event.addNamedTimer('statusTimer', timeout, false, function()\
10466 if self.parent.enabled then\
10467 self:draw()\
10468 self:sync()\
10469 end\
10470 end)\
10471end\
10472\
10473function UI.StatusBar:getColumnWidth(name)\
10474 local c = Util.find(self.columns, 'key', name)\
10475 return c and c.cw\
10476end\
10477\
10478function UI.StatusBar:setColumnWidth(name, width)\
10479 local c = Util.find(self.columns, 'key', name)\
10480 if c then\
10481 c.cw = width\
10482 end\
10483end\
10484\
10485function UI.StatusBar:draw()\
10486 if not self.values then\
10487 self:clear()\
10488 elseif type(self.values) == 'string' then\
10489 self:write(1, 1, Util.widthify(' ' .. self.values, self.width))\
10490 else\
10491 local s = ''\
10492 for _,c in ipairs(self.columns) do\
10493 s = s .. ' ' .. Util.widthify(tostring(self.values[c.key] or ''), c.cw)\
10494 end\
10495 self:write(1, 1, Util.widthify(s, self.width))\
10496 end\
10497end\
10498\
10499--[[-- ProgressBar --]]--\
10500UI.ProgressBar = class(UI.Window)\
10501UI.ProgressBar.defaults = {\
10502 UIElement = 'ProgressBar',\
10503 progressColor = colors.lime,\
10504 backgroundColor = colors.gray,\
10505 height = 1,\
10506 value = 0,\
10507}\
10508function UI.ProgressBar:draw()\
10509 self:clear()\
10510 local width = math.ceil(self.value / 100 * self.width)\
10511 self:clearArea(1, 1, width, self.height, self.progressColor)\
10512end\
10513\
10514--[[-- VerticalMeter --]]--\
10515UI.VerticalMeter = class(UI.Window)\
10516UI.VerticalMeter.defaults = {\
10517 UIElement = 'VerticalMeter',\
10518 backgroundColor = colors.gray,\
10519 meterColor = colors.lime,\
10520 width = 1,\
10521 value = 0,\
10522}\
10523function UI.VerticalMeter:draw()\
10524 local height = self.height - math.ceil(self.value / 100 * self.height)\
10525 self:clear()\
10526 self:clearArea(1, height + 1, self.width, self.height, self.meterColor)\
10527end\
10528\
10529--[[-- TextEntry --]]--\
10530UI.TextEntry = class(UI.Window)\
10531UI.TextEntry.defaults = {\
10532 UIElement = 'TextEntry',\
10533 value = '',\
10534 shadowText = '',\
10535 focused = false,\
10536 textColor = colors.white,\
10537 shadowTextColor = colors.gray,\
10538 backgroundColor = colors.black, -- colors.lightGray,\
10539 backgroundFocusColor = colors.black, --lightGray,\
10540 height = 1,\
10541 limit = 6,\
10542 pos = 0,\
10543 accelerators = {\
10544 [ 'control-c' ] = 'copy',\
10545 }\
10546}\
10547function UI.TextEntry:postInit()\
10548 self.value = tostring(self.value)\
10549end\
10550\
10551function UI.TextEntry:setValue(value)\
10552 self.value = value\
10553end\
10554\
10555function UI.TextEntry:setPosition(pos)\
10556 self.pos = pos\
10557end\
10558\
10559function UI.TextEntry:updateScroll()\
10560 if not self.scroll then\
10561 self.scroll = 0\
10562 end\
10563\
10564 if not self.pos then\
10565 self.pos = #tostring(self.value)\
10566 self.scroll = 0\
10567 elseif self.pos > #tostring(self.value) then\
10568 self.pos = #tostring(self.value)\
10569 self.scroll = 0\
10570 end\
10571\
10572 if self.pos - self.scroll > self.width - 2 then\
10573 self.scroll = self.pos - (self.width - 2)\
10574 elseif self.pos < self.scroll then\
10575 self.scroll = self.pos\
10576 end\
10577end\
10578\
10579function UI.TextEntry:draw()\
10580 local bg = self.backgroundColor\
10581 local tc = self.textColor\
10582 if self.focused then\
10583 bg = self.backgroundFocusColor\
10584 end\
10585\
10586 self:updateScroll()\
10587 local text = tostring(self.value)\
10588 if #text > 0 then\
10589 if self.scroll and self.scroll > 0 then\
10590 text = text:sub(1 + self.scroll)\
10591 end\
10592 if self.mask then\
10593 text = _rep('*', #text)\
10594 end\
10595 else\
10596 tc = self.shadowTextColor\
10597 text = self.shadowText\
10598 end\
10599\
10600 self:write(1, 1, ' ' .. Util.widthify(text, self.width - 2) .. ' ', bg, tc)\
10601 if self.focused then\
10602 self:setCursorPos(self.pos-self.scroll+2, 1)\
10603 end\
10604end\
10605\
10606function UI.TextEntry:reset()\
10607 self.pos = 0\
10608 self.value = ''\
10609 self:draw()\
10610 self:updateCursor()\
10611end\
10612\
10613function UI.TextEntry:updateCursor()\
10614 self:updateScroll()\
10615 self:setCursorPos(self.pos-self.scroll+2, 1)\
10616end\
10617\
10618function UI.TextEntry:focus()\
10619 self:draw()\
10620 if self.focused then\
10621 self:setCursorBlink(true)\
10622 else\
10623 self:setCursorBlink(false)\
10624 end\
10625end\
10626\
10627--[[\
10628 A few lines below from theoriginalbit\
10629 http://www.computercraft.info/forums2/index.php?/topic/16070-read-and-limit-length-of-the-input-field/\
10630--]]\
10631function UI.TextEntry:eventHandler(event)\
10632 if event.type == 'key' then\
10633 local ch = event.key\
10634 if ch == 'left' then\
10635 if self.pos > 0 then\
10636 self.pos = math.max(self.pos-1, 0)\
10637 self:draw()\
10638 end\
10639 elseif ch == 'right' then\
10640 local input = tostring(self.value)\
10641 if self.pos < #input then\
10642 self.pos = math.min(self.pos+1, #input)\
10643 self:draw()\
10644 end\
10645 elseif ch == 'home' then\
10646 self.pos = 0\
10647 self:draw()\
10648 elseif ch == 'end' then\
10649 self.pos = #tostring(self.value)\
10650 self:draw()\
10651 elseif ch == 'backspace' then\
10652 if self.pos > 0 then\
10653 local input = tostring(self.value)\
10654 self.value = input:sub(1, self.pos-1) .. input:sub(self.pos+1)\
10655 self.pos = self.pos - 1\
10656 self:draw()\
10657 self:emit({ type = 'text_change', text = self.value, element = self })\
10658 end\
10659 elseif ch == 'delete' then\
10660 local input = tostring(self.value)\
10661 if self.pos < #input then\
10662 self.value = input:sub(1, self.pos) .. input:sub(self.pos+2)\
10663 self:draw()\
10664 self:emit({ type = 'text_change', text = self.value, element = self })\
10665 end\
10666 elseif #ch == 1 then\
10667 local input = tostring(self.value)\
10668 if #input < self.limit then\
10669 self.value = input:sub(1, self.pos) .. ch .. input:sub(self.pos+1)\
10670 self.pos = self.pos + 1\
10671 self:draw()\
10672 self:emit({ type = 'text_change', text = self.value, element = self })\
10673 end\
10674 else\
10675 return false\
10676 end\
10677 return true\
10678\
10679 elseif event.type == 'copy' then\
10680 os.queueEvent('clipboard_copy', self.value)\
10681\
10682 elseif event.type == 'paste' then\
10683 local input = tostring(self.value)\
10684 local text = event.text\
10685 if #input + #text > self.limit then\
10686 text = text:sub(1, self.limit-#input)\
10687 end\
10688 self.value = input:sub(1, self.pos) .. text .. input:sub(self.pos+1)\
10689 self.pos = self.pos + #text\
10690 self:draw()\
10691 self:updateCursor()\
10692 self:emit({ type = 'text_change', text = self.value, element = self })\
10693 return true\
10694\
10695 elseif event.type == 'mouse_click' then\
10696 if self.focused and event.x > 1 then\
10697 self.pos = event.x + self.scroll - 2\
10698 self:updateCursor()\
10699 return true\
10700 end\
10701 elseif event.type == 'mouse_rightclick' then\
10702 local input = tostring(self.value)\
10703 if #input > 0 then\
10704 self:reset()\
10705 self:emit({ type = 'text_change', text = self.value, element = self })\
10706 end\
10707 end\
10708\
10709 return false\
10710end\
10711\
10712--[[-- Chooser --]]--\
10713UI.Chooser = class(UI.Window)\
10714UI.Chooser.defaults = {\
10715 UIElement = 'Chooser',\
10716 choices = { },\
10717 nochoice = 'Select',\
10718 backgroundFocusColor = colors.lightGray,\
10719 textInactiveColor = colors.gray,\
10720 leftIndicator = '<',\
10721 rightIndicator = '>',\
10722 height = 1,\
10723}\
10724function UI.Chooser:setParent()\
10725 if not self.width and not self.ex then\
10726 self.width = 1\
10727 for _,v in pairs(self.choices) do\
10728 if #v.name > self.width then\
10729 self.width = #v.name\
10730 end\
10731 end\
10732 self.width = self.width + 4\
10733 end\
10734 UI.Window.setParent(self)\
10735end\
10736\
10737function UI.Chooser:draw()\
10738 local bg = self.backgroundColor\
10739 if self.focused then\
10740 bg = self.backgroundFocusColor\
10741 end\
10742 local fg = self.inactive and self.textInactiveColor or self.textColor\
10743 local choice = Util.find(self.choices, 'value', self.value)\
10744 local value = self.nochoice\
10745 if choice then\
10746 value = choice.name\
10747 end\
10748 self:write(1, 1, self.leftIndicator, self.backgroundColor, colors.black)\
10749 self:write(2, 1, ' ' .. Util.widthify(tostring(value), self.width-4) .. ' ', bg, fg)\
10750 self:write(self.width, 1, self.rightIndicator, self.backgroundColor, colors.black)\
10751end\
10752\
10753function UI.Chooser:focus()\
10754 self:draw()\
10755end\
10756\
10757function UI.Chooser:eventHandler(event)\
10758 if event.type == 'key' then\
10759 if event.key == 'right' or event.key == 'space' then\
10760 local _,k = Util.find(self.choices, 'value', self.value)\
10761 local choice\
10762 if k and k < #self.choices then\
10763 choice = self.choices[k+1]\
10764 else\
10765 choice = self.choices[1]\
10766 end\
10767 self.value = choice.value\
10768 self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })\
10769 self:draw()\
10770 return true\
10771 elseif event.key == 'left' then\
10772 local _,k = Util.find(self.choices, 'value', self.value)\
10773 local choice\
10774 if k and k > 1 then\
10775 choice = self.choices[k-1]\
10776 else\
10777 choice = self.choices[#self.choices]\
10778 end\
10779 self.value = choice.value\
10780 self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })\
10781 self:draw()\
10782 return true\
10783 end\
10784 elseif event.type == 'mouse_click' then\
10785 if event.x == 1 then\
10786 self:emit({ type = 'key', key = 'left' })\
10787 return true\
10788 elseif event.x == self.width then\
10789 self:emit({ type = 'key', key = 'right' })\
10790 return true\
10791 end\
10792 end\
10793end\
10794\
10795--[[-- Chooser --]]--\
10796UI.Checkbox = class(UI.Window)\
10797UI.Checkbox.defaults = {\
10798 UIElement = 'Checkbox',\
10799 nochoice = 'Select',\
10800 checkedIndicator = 'X',\
10801 leftMarker = '[',\
10802 rightMarker = ']',\
10803 value = false,\
10804 textColor = colors.white,\
10805 backgroundColor = colors.black,\
10806 backgroundFocusColor = colors.lightGray,\
10807 height = 1,\
10808 width = 3,\
10809 accelerators = {\
10810 space = 'checkbox_toggle',\
10811 mouse_click = 'checkbox_toggle',\
10812 }\
10813}\
10814function UI.Checkbox:draw()\
10815 local bg = self.backgroundColor\
10816 if self.focused then\
10817 bg = self.backgroundFocusColor\
10818 end\
10819 if type(self.value) == 'string' then\
10820 self.value = nil -- TODO: fix form\
10821 end\
10822 local text = string.format('[%s]', not self.value and ' ' or self.checkedIndicator)\
10823 self:write(1, 1, text, bg)\
10824 self:write(1, 1, self.leftMarker, self.backgroundColor, self.textColor)\
10825 self:write(2, 1, not self.value and ' ' or self.checkedIndicator, bg)\
10826 self:write(3, 1, self.rightMarker, self.backgroundColor, self.textColor)\
10827end\
10828\
10829function UI.Checkbox:focus()\
10830 self:draw()\
10831end\
10832\
10833function UI.Checkbox:reset()\
10834 self.value = false\
10835end\
10836\
10837function UI.Checkbox:eventHandler(event)\
10838 if event.type == 'checkbox_toggle' then\
10839 self.value = not self.value\
10840 self:emit({ type = 'checkbox_change', checked = self.value, element = self })\
10841 self:draw()\
10842 return true\
10843 end\
10844end\
10845\
10846--[[-- Text --]]--\
10847UI.Text = class(UI.Window)\
10848UI.Text.defaults = {\
10849 UIElement = 'Text',\
10850 value = '',\
10851 height = 1,\
10852}\
10853function UI.Text:setParent()\
10854 if not self.width and not self.ex then\
10855 self.width = #tostring(self.value)\
10856 end\
10857 UI.Window.setParent(self)\
10858end\
10859\
10860function UI.Text:draw()\
10861 self:write(1, 1, Util.widthify(self.value or '', self.width), self.backgroundColor)\
10862end\
10863\
10864--[[-- ScrollBar --]]--\
10865UI.ScrollBar = class(UI.Window)\
10866UI.ScrollBar.defaults = {\
10867 UIElement = 'ScrollBar',\
10868 lineChar = '|',\
10869 sliderChar = '#',\
10870 upArrowChar = '^',\
10871 downArrowChar = 'v',\
10872 scrollbarColor = colors.lightGray,\
10873 value = '',\
10874 width = 1,\
10875 x = -1,\
10876 ey = -1,\
10877}\
10878function UI.ScrollBar:draw()\
10879 local parent = self.parent\
10880 local view = parent:getViewArea()\
10881\
10882 if view.totalHeight > view.height then\
10883 local maxScroll = view.totalHeight - view.height\
10884 local percent = view.offsetY / maxScroll\
10885 local sliderSize = math.max(1, Util.round(view.height / view.totalHeight * (view.height - 2)))\
10886 local x = self.width\
10887\
10888 local row = view.y\
10889 if not view.static then -- does the container scroll ?\
10890 self.y = row -- if so, move the scrollbar onscreen\
10891 row = 1\
10892 end\
10893\
10894 for i = 1, view.height - 2 do\
10895 self:write(x, row + i, self.lineChar, nil, self.scrollbarColor)\
10896 end\
10897\
10898 local y = Util.round((view.height - 2 - sliderSize) * percent)\
10899 for i = 1, sliderSize do\
10900 self:write(x, row + y + i, self.sliderChar, nil, self.scrollbarColor)\
10901 end\
10902\
10903 local color = self.scrollbarColor\
10904 if view.offsetY > 0 then\
10905 color = colors.white\
10906 end\
10907 self:write(x, row, self.upArrowChar, nil, color)\
10908\
10909 color = self.scrollbarColor\
10910 if view.offsetY + view.height < view.totalHeight then\
10911 color = colors.white\
10912 end\
10913 self:write(x, row + view.height - 1, self.downArrowChar, nil, color)\
10914 end\
10915end\
10916\
10917function UI.ScrollBar:eventHandler(event)\
10918 if event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then\
10919 if event.x == 1 then\
10920 local view = self.parent:getViewArea()\
10921 if view.totalHeight > view.height then\
10922 if event.y == view.y then\
10923 self:emit({ type = 'scroll_up'})\
10924 elseif event.y == self.height then\
10925 self:emit({ type = 'scroll_down'})\
10926 -- else\
10927 -- ... percentage ...\
10928 end\
10929 end\
10930 return true\
10931 end\
10932 end\
10933end\
10934\
10935--[[-- TextArea --]]--\
10936UI.TextArea = class(UI.Viewport)\
10937UI.TextArea.defaults = {\
10938 UIElement = 'TextArea',\
10939 marginRight = 2,\
10940 value = '',\
10941}\
10942function UI.TextArea:postInit()\
10943 self.scrollBar = UI.ScrollBar()\
10944end\
10945\
10946function UI.TextArea:setText(text)\
10947 self.offy = 0\
10948 self.ymax = nil\
10949 self.value = text\
10950 self:draw()\
10951end\
10952\
10953function UI.TextArea:focus()\
10954 -- allow keyboard scrolling\
10955end\
10956\
10957function UI.TextArea:draw()\
10958 self:clear()\
10959-- self:setCursorPos(1, 1)\
10960 self.cursorX, self.cursorY = 1, 1\
10961 self:print(self.value)\
10962 self.ymax = self.cursorY + 1\
10963\
10964 for _,child in pairs(self.children) do\
10965 if child.enabled then\
10966 child:draw()\
10967 end\
10968 end\
10969end\
10970\
10971--[[-- Form --]]--\
10972UI.Form = class(UI.Window)\
10973UI.Form.defaults = {\
10974 UIElement = 'Form',\
10975 values = { },\
10976 margin = 2,\
10977 event = 'form_complete',\
10978}\
10979function UI.Form:postInit()\
10980 self:createForm()\
10981end\
10982\
10983function UI.Form:reset()\
10984 for _,child in pairs(self.children) do\
10985 if child.reset then\
10986 child:reset()\
10987 end\
10988 end\
10989end\
10990\
10991function UI.Form:setValues(values)\
10992 self:reset()\
10993 self.values = values\
10994 for _,child in pairs(self.children) do\
10995 if child.formKey then\
10996 -- this should be child:setValue(self.values[child.formKey])\
10997 -- so chooser can set default choice if null\
10998 -- null should be valid as well\
10999 child.value = self.values[child.formKey] or ''\
11000 end\
11001 end\
11002end\
11003\
11004function UI.Form:createForm()\
11005 self.children = self.children or { }\
11006\
11007 if not self.labelWidth then\
11008 self.labelWidth = 1\
11009 for _, child in pairs(self) do\
11010 if type(child) == 'table' and child.UIElement then\
11011 if child.formLabel then\
11012 self.labelWidth = math.max(self.labelWidth, #child.formLabel + 2)\
11013 end\
11014 end\
11015 end\
11016 end\
11017\
11018 local y = self.margin\
11019 for _, child in pairs(self) do\
11020 if type(child) == 'table' and child.UIElement then\
11021 if child.formKey then\
11022 child.value = self.values[child.formKey] or ''\
11023 end\
11024 if child.formLabel then\
11025 child.x = self.labelWidth + self.margin - 1\
11026 child.y = y\
11027 if not child.width and not child.ex then\
11028 child.ex = -self.margin\
11029 end\
11030\
11031 table.insert(self.children, UI.Text {\
11032 x = self.margin,\
11033 y = y,\
11034 textColor = colors.black,\
11035 width = #child.formLabel,\
11036 value = child.formLabel,\
11037 })\
11038 end\
11039 if child.formKey or child.formLabel then\
11040 y = y + 1\
11041 end\
11042 end\
11043 end\
11044\
11045 if not self.manualControls then\
11046 table.insert(self.children, UI.Button {\
11047 y = -self.margin, x = -12 - self.margin,\
11048 text = 'Ok',\
11049 event = 'form_ok',\
11050 })\
11051 table.insert(self.children, UI.Button {\
11052 y = -self.margin, x = -7 - self.margin,\
11053 text = 'Cancel',\
11054 event = 'form_cancel',\
11055 })\
11056 end\
11057end\
11058\
11059function UI.Form:validateField(field)\
11060 if field.required then\
11061 if not field.value or #tostring(field.value) == 0 then\
11062 return false, 'Field is required'\
11063 end\
11064 end\
11065 if field.validate == 'numeric' then\
11066 if #tostring(field.value) > 0 then\
11067 if not tonumber(field.value) then\
11068 return false, 'Invalid number'\
11069 end\
11070 end\
11071 end\
11072 return true\
11073end\
11074\
11075function UI.Form:save()\
11076 for _,child in pairs(self.children) do\
11077 if child.formKey then\
11078 local s, m = self:validateField(child)\
11079 if not s then\
11080 self:setFocus(child)\
11081 self:emit({ type = 'form_invalid', message = m, field = child })\
11082 return false\
11083 end\
11084 end\
11085 end\
11086 for _,child in pairs(self.children) do\
11087 if child.formKey then\
11088 if (child.pruneEmpty and type(child.value) == 'string' and #child.value == 0) or\
11089 (child.pruneEmpty and type(child.value) == 'boolean' and not child.value) then\
11090 self.values[child.formKey] = nil\
11091 elseif child.validate == 'numeric' then\
11092 self.values[child.formKey] = tonumber(child.value)\
11093 else\
11094 self.values[child.formKey] = child.value\
11095 end\
11096 end\
11097 end\
11098\
11099 return true\
11100end\
11101\
11102function UI.Form:eventHandler(event)\
11103 if event.type == 'form_ok' then\
11104 if not self:save() then\
11105 return false\
11106 end\
11107 self:emit({ type = self.event, UIElement = self })\
11108 else\
11109 return UI.Window.eventHandler(self, event)\
11110 end\
11111 return true\
11112end\
11113\
11114--[[-- Dialog --]]--\
11115UI.Dialog = class(UI.Page)\
11116UI.Dialog.defaults = {\
11117 UIElement = 'Dialog',\
11118 x = 7,\
11119 y = 4,\
11120 z = 2,\
11121 height = 7,\
11122 textColor = colors.black,\
11123 backgroundColor = colors.white,\
11124}\
11125function UI.Dialog:postInit()\
11126 self.titleBar = UI.TitleBar({ previousPage = true, title = self.title })\
11127end\
11128\
11129function UI.Dialog:setParent()\
11130 if not self.width then\
11131 self.width = self.parent.width - 11\
11132 end\
11133 if self.width > self.parent.width then\
11134 self.width = self.parent.width\
11135 end\
11136 self.x = math.floor((self.parent.width - self.width) / 2) + 1\
11137 self.y = math.floor((self.parent.height - self.height) / 2) + 1\
11138 UI.Page.setParent(self)\
11139end\
11140\
11141function UI.Dialog:disable()\
11142 self.previousPage.canvas.palette = self.oldPalette\
11143 UI.Page.disable(self)\
11144end\
11145\
11146function UI.Dialog:enable(...)\
11147 self.oldPalette = self.previousPage.canvas.palette\
11148 self.previousPage.canvas:applyPalette(Canvas.darkPalette)\
11149 self:addTransition('grow')\
11150 UI.Page.enable(self, ...)\
11151end\
11152\
11153function UI.Dialog:eventHandler(event)\
11154 if event.type == 'cancel' then\
11155 UI:setPreviousPage()\
11156 end\
11157 return UI.Page.eventHandler(self, event)\
11158end\
11159\
11160--[[-- Image --]]--\
11161UI.Image = class(UI.Window)\
11162UI.Image.defaults = {\
11163 UIElement = 'Image',\
11164 event = 'button_press',\
11165}\
11166function UI.Image:setParent()\
11167 if self.image then\
11168 self.height = #self.image\
11169 end\
11170 if self.image and not self.width then\
11171 self.width = #self.image[1]\
11172 end\
11173 UI.Window.setParent(self)\
11174end\
11175\
11176function UI.Image:draw()\
11177 self:clear()\
11178 if self.image then\
11179 for y = 1, #self.image do\
11180 local line = self.image[y]\
11181 for x = 1, #line do\
11182 local ch = line[x]\
11183 if type(ch) == 'number' then\
11184 if ch > 0 then\
11185 self:write(x, y, ' ', ch)\
11186 end\
11187 else\
11188 self:write(x, y, ch)\
11189 end\
11190 end\
11191 end\
11192 end\
11193end\
11194\
11195function UI.Image:setImage(image)\
11196 self.image = image\
11197end\
11198\
11199--[[-- NftImage --]]--\
11200UI.NftImage = class(UI.Window)\
11201UI.NftImage.defaults = {\
11202 UIElement = 'NftImage',\
11203 event = 'button_press',\
11204}\
11205function UI.NftImage:setParent()\
11206 if self.image then\
11207 self.height = self.image.height\
11208 end\
11209 if self.image and not self.width then\
11210 self.width = self.image.width\
11211 end\
11212 UI.Window.setParent(self)\
11213end\
11214\
11215function UI.NftImage:draw()\
11216 if self.image then\
11217 for y = 1, self.image.height do\
11218 for x = 1, #self.image.text[y] do\
11219 self:write(x, y, self.image.text[y][x], self.image.bg[y][x], self.image.fg[y][x])\
11220 end\
11221 end\
11222 else\
11223 self:clear()\
11224 end\
11225end\
11226\
11227function UI.NftImage:setImage(image)\
11228 self.image = image\
11229end\
11230\
11231UI:loadTheme('/sys/os/opus/usr/config/ui.theme')\
11232if Util.getVersion() >= 1.76 then\
11233 UI:loadTheme('sys/os/opus/sys/etc/ext.theme')\
11234end\
11235\
11236UI:setDefaultDevice(UI.Device({ device = term.current() }))\
11237\
11238return UI",
11239 [ "socket.lua" ] = "local Crypto = require('crypto')\
11240local Logger = require('logger')\
11241local Security = require('security')\
11242local Util = require('util')\
11243\
11244local device = _G.device\
11245local os = _G.os\
11246\
11247local socketClass = { }\
11248\
11249function socketClass:read(timeout)\
11250 local data, distance = _G.transport.read(self)\
11251 if data then\
11252 return data, distance\
11253 end\
11254\
11255 if not self.connected then\
11256 Logger.log('socket', 'read: No connection')\
11257 return\
11258 end\
11259\
11260 local timerId = os.startTimer(timeout or 5)\
11261\
11262 while true do\
11263 local e, id = os.pullEvent()\
11264\
11265 if e == 'transport_' .. self.uid then\
11266 data, distance = _G.transport.read(self)\
11267 if data then\
11268 os.cancelTimer(timerId)\
11269 return data, distance\
11270 end\
11271 if not self.connected then\
11272 break\
11273 end\
11274\
11275 elseif e == 'timer' and id == timerId then\
11276 if timeout or not self.connected then\
11277 break\
11278 end\
11279 timerId = os.startTimer(5)\
11280 self:ping()\
11281 end\
11282 end\
11283end\
11284\
11285function socketClass:write(data)\
11286 if self.connected then\
11287 _G.transport.write(self, {\
11288 type = 'DATA',\
11289 seq = self.wseq,\
11290 data = data,\
11291 })\
11292 return true\
11293 end\
11294end\
11295\
11296function socketClass:ping()\
11297 if self.connected then\
11298 _G.transport.ping(self)\
11299 return true\
11300 end\
11301end\
11302\
11303function socketClass:close()\
11304 if self.connected then\
11305 Logger.log('socket', 'closing socket ' .. self.sport)\
11306 self.transmit(self.dport, self.dhost, {\
11307 type = 'DISC',\
11308 })\
11309 self.connected = false\
11310 end\
11311 device.wireless_modem.close(self.sport)\
11312 _G.transport.close(self)\
11313end\
11314\
11315local Socket = { }\
11316\
11317local function loopback(port, sport, msg)\
11318 os.queueEvent('modem_message', 'loopback', port, sport, msg, 0)\
11319end\
11320\
11321local function newSocket(isLoopback)\
11322 for _ = 16384, 32767 do\
11323 local i = math.random(16384, 32767)\
11324 if not device.wireless_modem.isOpen(i) then\
11325 local socket = {\
11326 shost = os.getComputerID(),\
11327 sport = i,\
11328 transmit = device.wireless_modem.transmit,\
11329 wseq = math.random(100, 100000),\
11330 rseq = math.random(100, 100000),\
11331 timers = { },\
11332 messages = { },\
11333 }\
11334 setmetatable(socket, { __index = socketClass })\
11335\
11336 device.wireless_modem.open(socket.sport)\
11337\
11338 if isLoopback then\
11339 socket.transmit = loopback\
11340 end\
11341 return socket\
11342 end\
11343 end\
11344 error('No ports available')\
11345end\
11346\
11347function Socket.connect(host, port)\
11348 if not device.wireless_modem then\
11349 return false, 'Wireless modem not found'\
11350 end\
11351\
11352 local socket = newSocket(host == os.getComputerID())\
11353 socket.dhost = tonumber(host)\
11354 Logger.log('socket', 'connecting to ' .. port)\
11355\
11356 socket.transmit(port, socket.sport, {\
11357 type = 'OPEN',\
11358 shost = socket.shost,\
11359 dhost = socket.dhost,\
11360 t = Crypto.encrypt({ ts = os.time(), seq = socket.seq }, Security.getPublicKey()),\
11361 rseq = socket.wseq,\
11362 wseq = socket.rseq,\
11363 })\
11364\
11365 local timerId = os.startTimer(3)\
11366 repeat\
11367 local e, id, sport, dport, msg = os.pullEvent()\
11368 if e == 'modem_message' and\
11369 sport == socket.sport and\
11370 type(msg) == 'table' and\
11371 msg.dhost == socket.shost then\
11372\
11373 os.cancelTimer(timerId)\
11374\
11375 if msg.type == 'CONN' then\
11376\
11377 socket.dport = dport\
11378 socket.connected = true\
11379 Logger.log('socket', 'connection established to %d %d->%d',\
11380 host, socket.sport, socket.dport)\
11381\
11382 _G.transport.open(socket)\
11383\
11384 return socket\
11385 elseif msg.type == 'REJE' then\
11386 return false, 'Password not set on target or not trusted'\
11387 end\
11388 end\
11389 until e == 'timer' and id == timerId\
11390\
11391 socket:close()\
11392\
11393 return false, 'Connection timed out'\
11394end\
11395\
11396local function trusted(msg, port)\
11397 if port == 19 or msg.shost == os.getComputerID() then\
11398 -- no auth for trust server or loopback\
11399 return true\
11400 end\
11401\
11402 if not Security.hasPassword() then\
11403 -- no password has been set on this computer\
11404 --return true\
11405 end\
11406\
11407 local trustList = Util.readTable('/sys/os/opus/usr/.known_hosts') or { }\
11408 local pubKey = trustList[msg.shost]\
11409\
11410 if pubKey then\
11411 local data = Crypto.decrypt(msg.t or '', pubKey)\
11412\
11413 --local sharedKey = modexp(pubKey, exchange.secretKey, public.primeMod)\
11414 return data.ts and tonumber(data.ts) and math.abs(os.time() - data.ts) < 24\
11415 end\
11416end\
11417\
11418function Socket.server(port)\
11419 device.wireless_modem.open(port)\
11420 Logger.log('socket', 'Waiting for connections on port ' .. port)\
11421\
11422 while true do\
11423 local _, _, sport, dport, msg = os.pullEvent('modem_message')\
11424\
11425 if sport == port and\
11426 msg and\
11427 type(msg) == 'table' and\
11428 msg.dhost == os.getComputerID() and\
11429 msg.type == 'OPEN' then\
11430\
11431 local socket = newSocket(msg.shost == os.getComputerID())\
11432 socket.dport = dport\
11433 socket.dhost = msg.shost\
11434 socket.wseq = msg.wseq\
11435 socket.rseq = msg.rseq\
11436\
11437 if trusted(msg, port) then\
11438 socket.connected = true\
11439 socket.transmit(socket.dport, socket.sport, {\
11440 type = 'CONN',\
11441 dhost = socket.dhost,\
11442 shost = socket.shost,\
11443 })\
11444 Logger.log('socket', 'Connection established %d->%d', socket.sport, socket.dport)\
11445\
11446 _G.transport.open(socket)\
11447 return socket\
11448 end\
11449\
11450 socket.transmit(socket.dport, socket.sport, {\
11451 type = 'REJE',\
11452 dhost = socket.dhost,\
11453 shost = socket.shost,\
11454 })\
11455 socket:close()\
11456 end\
11457 end\
11458end\
11459\
11460return Socket",
11461 ui = {
11462 [ "region.lua" ] = "--\
11463-- tek.lib.region\
11464-- Written by Timm S. Mueller <tmueller at schulze-mueller.de>\
11465--\
11466-- Copyright 2008 - 2016 by the authors and contributors:\
11467--\
11468-- * Timm S. Muller <tmueller at schulze-mueller.de>\
11469-- * Franciska Schulze <fschulze at schulze-mueller.de>\
11470-- * Tobias Schwinger <tschwinger at isonews2.com>\
11471--\
11472-- https://opensource.org/licenses/MIT\
11473--\
11474-- Some comments have been removed to reduce file size, see:\
11475-- https://github.com/technosaurus/tekui/blob/master/etc/region.lua\
11476-- for the full source\
11477\
11478local insert = table.insert\
11479local ipairs = ipairs\
11480local max = math.max\
11481local min = math.min\
11482local setmetatable = setmetatable\
11483local unpack = unpack or table.unpack\
11484\
11485local Region = { }\
11486Region._VERSION = \"Region 11.3\"\
11487\
11488Region.__index = Region\
11489\
11490-- x0, y0, x1, y1 = Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4):\
11491-- Returns the coordinates of a rectangle where a rectangle specified by\
11492-- the coordinates s1, s2, s3, s4 overlaps with the rectangle specified\
11493-- by the coordinates d1, d2, d3, d4. The return value is '''nil''' if\
11494-- the rectangles do not overlap.\
11495function Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4)\
11496 if s3 >= d1 and s1 <= d3 and s4 >= d2 and s2 <= d4 then\
11497 return max(s1, d1), max(s2, d2), min(s3, d3), min(s4, d4)\
11498 end\
11499end\
11500\
11501-- insertrect: insert rect to table, merging with an existing one if possible\
11502local function insertrect(d, s1, s2, s3, s4)\
11503 for i = 1, min(4, #d) do\
11504 local a = d[i]\
11505 local a1, a2, a3, a4 = a[1], a[2], a[3], a[4]\
11506 if a2 == s2 and a4 == s4 then\
11507 if a3 + 1 == s1 then\
11508 a[3] = s3\
11509 return\
11510 elseif a1 == s3 + 1 then\
11511 a[1] = s1\
11512 return\
11513 end\
11514 elseif a1 == s1 and a3 == s3 then\
11515 if a4 + 1 == s2 then\
11516 a[4] = s4\
11517 return\
11518 elseif a2 == s4 + 1 then\
11519 a[2] = s2\
11520 return\
11521 end\
11522 end\
11523 end\
11524 insert(d, 1, { s1, s2, s3, s4 })\
11525end\
11526\
11527-- cutrect: cut rect d into table of new rects, using rect s as a punch\
11528local function cutrect(d1, d2, d3, d4, s1, s2, s3, s4)\
11529 if not Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4) then\
11530 return { { d1, d2, d3, d4 } }\
11531 end\
11532 local r = { }\
11533 if d1 < s1 then\
11534 insertrect(r, d1, d2, s1 - 1, d4)\
11535 d1 = s1\
11536 end\
11537 if d2 < s2 then\
11538 insertrect(r, d1, d2, d3, s2 - 1)\
11539 d2 = s2\
11540 end\
11541 if d3 > s3 then\
11542 insertrect(r, s3 + 1, d2, d3, d4)\
11543 d3 = s3\
11544 end\
11545 if d4 > s4 then\
11546 insertrect(r, d1, s4 + 1, d3, d4)\
11547 end\
11548 return r\
11549end\
11550\
11551-- cutregion: cut region d, using s as a punch\
11552local function cutregion(d, s1, s2, s3, s4)\
11553 local r = { }\
11554 for _, dr in ipairs(d) do\
11555 local d1, d2, d3, d4 = dr[1], dr[2], dr[3], dr[4]\
11556 for _, t in ipairs(cutrect(d1, d2, d3, d4, s1, s2, s3, s4)) do\
11557 insertrect(r, t[1], t[2], t[3], t[4])\
11558 end\
11559 end\
11560 return r\
11561end\
11562\
11563-- region = Region.new(r1, r2, r3, r4): Creates a new region from the given\
11564-- coordinates.\
11565function Region.new(r1, r2, r3, r4)\
11566 if r1 then\
11567 return setmetatable({ region = { { r1, r2, r3, r4 } } }, Region)\
11568 end\
11569 return setmetatable({ region = { } }, Region)\
11570end\
11571\
11572-- self = region:setRect(r1, r2, r3, r4): Resets an existing region\
11573-- to the specified rectangle.\
11574function Region:setRect(r1, r2, r3, r4)\
11575 self.region = { { r1, r2, r3, r4 } }\
11576 return self\
11577end\
11578\
11579-- region:orRect(r1, r2, r3, r4): Logical ''or''s a rectangle to a region\
11580function Region:orRect(s1, s2, s3, s4)\
11581 self.region = cutregion(self.region, s1, s2, s3, s4)\
11582 insertrect(self.region, s1, s2, s3, s4)\
11583end\
11584\
11585-- region:orRegion(region): Logical ''or''s another region to a region\
11586function Region:orRegion(s)\
11587 for _, r in ipairs(s) do\
11588 self:orRect(r[1], r[2], r[3], r[4])\
11589 end\
11590end\
11591\
11592-- region:andRect(r1, r2, r3, r4): Logical ''and''s a rectange to a region\
11593function Region:andRect(s1, s2, s3, s4)\
11594 local r = { }\
11595 for _, d in ipairs(self.region) do\
11596 local t1, t2, t3, t4 =\
11597 Region.intersect(d[1], d[2], d[3], d[4], s1, s2, s3, s4)\
11598 if t1 then\
11599 insertrect(r, t1, t2, t3, t4)\
11600 end\
11601 end\
11602 self.region = r\
11603end\
11604\
11605-- region:xorRect(r1, r2, r3, r4): Logical ''xor''s a rectange to a region\
11606function Region:xorRect(s1, s2, s3, s4)\
11607 local r1 = { }\
11608 local r2 = { { s1, s2, s3, s4 } }\
11609 for _, d in ipairs(self.region) do\
11610 local d1, d2, d3, d4 = d[1], d[2], d[3], d[4]\
11611 for _, t in ipairs(cutrect(d1, d2, d3, d4, s1, s2, s3, s4)) do\
11612 insertrect(r1, t[1], t[2], t[3], t[4])\
11613 end\
11614 r2 = cutregion(r2, d1, d2, d3, d4)\
11615 end\
11616 self.region = r1\
11617 self:orRegion(r2)\
11618end\
11619\
11620-- self = region:subRect(r1, r2, r3, r4): Subtracts a rectangle from a region\
11621function Region:subRect(s1, s2, s3, s4)\
11622 local r1 = { }\
11623 for _, d in ipairs(self.region) do\
11624 local d1, d2, d3, d4 = d[1], d[2], d[3], d[4]\
11625 for _, t in ipairs(cutrect(d1, d2, d3, d4, s1, s2, s3, s4)) do\
11626 insertrect(r1, t[1], t[2], t[3], t[4])\
11627 end\
11628 end\
11629 self.region = r1\
11630 return self\
11631end\
11632\
11633-- region:getRect - gets an iterator on the rectangles in a region [internal]\
11634function Region:getRects()\
11635 local index = 0\
11636 return function(object)\
11637 index = index + 1\
11638 if object[index] then\
11639 return unpack(object[index])\
11640 end\
11641 end, self.region\
11642end\
11643\
11644-- success = region:checkIntersect(x0, y0, x1, y1): Returns a boolean\
11645-- indicating whether a rectangle specified by its coordinates overlaps\
11646-- with a region.\
11647function Region:checkIntersect(s1, s2, s3, s4)\
11648 for _, d in ipairs(self.region) do\
11649 if Region.intersect(d[1], d[2], d[3], d[4], s1, s2, s3, s4) then\
11650 return true\
11651 end\
11652 end\
11653 return false\
11654end\
11655\
11656-- region:subRegion(region2): Subtracts {{region2}} from {{region}}.\
11657function Region:subRegion(region)\
11658 if region then\
11659 for r1, r2, r3, r4 in region:getRects() do\
11660 self:subRect(r1, r2, r3, r4)\
11661 end\
11662 end\
11663end\
11664\
11665-- region:andRegion(r): Logically ''and''s a region to a region\
11666function Region:andRegion(s)\
11667 local r = { }\
11668 for _, s in ipairs(s.region) do\
11669 for _, d in ipairs(self.region) do\
11670 local t1, t2, t3, t4 =\
11671 Region.intersect(d[1], d[2], d[3], d[4],\
11672 s[1], s[2], s[3], s[4])\
11673 if t1 then\
11674 insertrect(r, t1, t2, t3, t4)\
11675 end\
11676 end\
11677 end\
11678 self.region = r\
11679end\
11680\
11681-- region:forEach(func, obj, ...): For each rectangle in a region, calls the\
11682-- specified function according the following scheme:\
11683-- func(obj, x0, y0, x1, y1, ...)\
11684-- Extra arguments are passed through to the function.\
11685function Region:forEach(func, obj, ...)\
11686 for x0, y0, x1, y1 in self:getRects() do\
11687 func(obj, x0, y0, x1, y1, ...) \
11688 end\
11689end\
11690\
11691-- region:shift(dx, dy): Shifts a region by delta x and y.\
11692function Region:shift(dx, dy)\
11693 for _, r in ipairs(self.region) do\
11694 r[1] = r[1] + dx\
11695 r[2] = r[2] + dy\
11696 r[3] = r[3] + dx\
11697 r[4] = r[4] + dy\
11698 end\
11699end\
11700\
11701-- region:isEmpty(): Returns '''true''' if a region is empty.\
11702function Region:isEmpty()\
11703 return #self.region == 0\
11704end\
11705\
11706-- minx, miny, maxx, maxy = region:get(): Get region's min/max extents\
11707function Region:get()\
11708 if #self.region > 0 then\
11709 local minx = 1000000 -- ui.HUGE\
11710 local miny = 1000000\
11711 local maxx = 0\
11712 local maxy = 0\
11713 for _, r in ipairs(self.region) do\
11714 minx = min(minx, r[1])\
11715 miny = min(miny, r[2])\
11716 maxx = max(maxx, r[3])\
11717 maxy = max(maxy, r[4])\
11718 end\
11719 return minx, miny, maxx, maxy\
11720 end\
11721end\
11722\
11723return Region",
11724 [ "transition.lua" ] = "local Tween = require('ui.tween')\
11725\
11726local Transition = { }\
11727\
11728function Transition.slideLeft(args)\
11729 local ticks = args.ticks or 6\
11730 local easing = args.easing or 'outQuint'\
11731 local pos = { x = args.ex }\
11732 local tween = Tween.new(ticks, pos, { x = args.x }, easing)\
11733 local lastScreen = args.canvas:copy()\
11734\
11735 return function(device)\
11736 local finished = tween:update(1)\
11737 local x = math.floor(pos.x)\
11738 lastScreen:dirty()\
11739 lastScreen:blit(device, {\
11740 x = args.ex - x + args.x,\
11741 y = args.y,\
11742 ex = args.ex,\
11743 ey = args.ey },\
11744 { x = args.x, y = args.y })\
11745 args.canvas:blit(device, {\
11746 x = args.x,\
11747 y = args.y,\
11748 ex = args.ex - x + args.x,\
11749 ey = args.ey },\
11750 { x = x, y = args.y })\
11751 return not finished\
11752 end\
11753end\
11754\
11755function Transition.slideRight(args)\
11756 local ticks = args.ticks or 6\
11757 local easing = args.easing or'outQuint'\
11758 local pos = { x = args.x }\
11759 local tween = Tween.new(ticks, pos, { x = args.ex }, easing)\
11760 local lastScreen = args.canvas:copy()\
11761\
11762 return function(device)\
11763 local finished = tween:update(1)\
11764 local x = math.floor(pos.x)\
11765 lastScreen:dirty()\
11766 lastScreen:blit(device, {\
11767 x = args.x,\
11768 y = args.y,\
11769 ex = args.ex - x + args.x,\
11770 ey = args.ey },\
11771 { x = x, y = args.y })\
11772 args.canvas:blit(device, {\
11773 x = args.ex - x + args.x,\
11774 y = args.y,\
11775 ex = args.ex,\
11776 ey = args.ey },\
11777 { x = args.x, y = args.y })\
11778 return not finished\
11779 end\
11780end\
11781\
11782function Transition.expandUp(args)\
11783 local ticks = args.ticks or 3\
11784 local easing = args.easing or 'linear'\
11785 local pos = { y = args.ey + 1 }\
11786 local tween = Tween.new(ticks, pos, { y = args.y }, easing)\
11787\
11788 return function(device)\
11789 local finished = tween:update(1)\
11790 args.canvas:blit(device, nil, { x = args.x, y = math.floor(pos.y) })\
11791 return not finished\
11792 end\
11793end\
11794\
11795function Transition.grow(args)\
11796 local ticks = args.ticks or 3\
11797 local easing = args.easing or 'linear'\
11798 local tween = Tween.new(ticks,\
11799 { x = args.width / 2 - 1, y = args.height / 2 - 1, w = 1, h = 1 },\
11800 { x = 1, y = 1, w = args.width, h = args.height }, easing)\
11801\
11802 return function(device)\
11803 local finished = tween:update(1)\
11804 local subj = tween.subject\
11805 local rect = { x = math.floor(subj.x), y = math.floor(subj.y) }\
11806 rect.ex = math.floor(rect.x + subj.w - 1)\
11807 rect.ey = math.floor(rect.y + subj.h - 1)\
11808 args.canvas:blit(device, rect, { x = args.x + rect.x - 1, y = args.y + rect.y - 1})\
11809 return not finished\
11810 end\
11811end\
11812\
11813return Transition",
11814 [ "tween.lua" ] = "local tween = {\
11815 _VERSION = 'tween 2.1.1',\
11816 _DESCRIPTION = 'tweening for lua',\
11817 _URL = 'https://github.com/kikito/tween.lua',\
11818 _LICENSE = [[\
11819 MIT LICENSE\
11820\
11821 Copyright (c) 2014 Enrique GarcÃa Cota, Yuichi Tateno, Emmanuel Oga\
11822\
11823 Licence details: https://opensource.org/licenses/MIT\
11824 ]]\
11825}\
11826\
11827-- easing\
11828\
11829-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits.\
11830-- For all easing functions:\
11831-- t = time == how much time has to pass for the tweening to complete\
11832-- b = begin == starting property value\
11833-- c = change == ending - beginning\
11834-- d = duration == running time. How much time has passed *right now*\
11835\
11836local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin\
11837\
11838-- linear\
11839local function linear(t, b, c, d) return c * t / d + b end\
11840\
11841-- quad\
11842local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end\
11843local function outQuad(t, b, c, d)\
11844 t = t / d\
11845 return -c * t * (t - 2) + b\
11846end\
11847local function inOutQuad(t, b, c, d)\
11848 t = t / d * 2\
11849 if t < 1 then return c / 2 * pow(t, 2) + b end\
11850 return -c / 2 * ((t - 1) * (t - 3) - 1) + b\
11851end\
11852local function outInQuad(t, b, c, d)\
11853 if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end\
11854 return inQuad((t * 2) - d, b + c / 2, c / 2, d)\
11855end\
11856\
11857-- cubic\
11858local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end\
11859local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end\
11860local function inOutCubic(t, b, c, d)\
11861 t = t / d * 2\
11862 if t < 1 then return c / 2 * t * t * t + b end\
11863 t = t - 2\
11864 return c / 2 * (t * t * t + 2) + b\
11865end\
11866local function outInCubic(t, b, c, d)\
11867 if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end\
11868 return inCubic((t * 2) - d, b + c / 2, c / 2, d)\
11869end\
11870\
11871-- quart\
11872local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end\
11873local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end\
11874local function inOutQuart(t, b, c, d)\
11875 t = t / d * 2\
11876 if t < 1 then return c / 2 * pow(t, 4) + b end\
11877 return -c / 2 * (pow(t - 2, 4) - 2) + b\
11878end\
11879local function outInQuart(t, b, c, d)\
11880 if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end\
11881 return inQuart((t * 2) - d, b + c / 2, c / 2, d)\
11882end\
11883\
11884-- quint\
11885local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end\
11886local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end\
11887local function inOutQuint(t, b, c, d)\
11888 t = t / d * 2\
11889 if t < 1 then return c / 2 * pow(t, 5) + b end\
11890 return c / 2 * (pow(t - 2, 5) + 2) + b\
11891end\
11892local function outInQuint(t, b, c, d)\
11893 if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end\
11894 return inQuint((t * 2) - d, b + c / 2, c / 2, d)\
11895end\
11896\
11897-- sine\
11898local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end\
11899local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end\
11900local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end\
11901local function outInSine(t, b, c, d)\
11902 if t < d / 2 then return outSine(t * 2, b, c / 2, d) end\
11903 return inSine((t * 2) -d, b + c / 2, c / 2, d)\
11904end\
11905\
11906-- expo\
11907local function inExpo(t, b, c, d)\
11908 if t == 0 then return b end\
11909 return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001\
11910end\
11911local function outExpo(t, b, c, d)\
11912 if t == d then return b + c end\
11913 return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b\
11914end\
11915local function inOutExpo(t, b, c, d)\
11916 if t == 0 then return b end\
11917 if t == d then return b + c end\
11918 t = t / d * 2\
11919 if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end\
11920 return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b\
11921end\
11922local function outInExpo(t, b, c, d)\
11923 if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end\
11924 return inExpo((t * 2) - d, b + c / 2, c / 2, d)\
11925end\
11926\
11927-- circ\
11928local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end\
11929local function outCirc(t, b, c, d) return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end\
11930local function inOutCirc(t, b, c, d)\
11931 t = t / d * 2\
11932 if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end\
11933 t = t - 2\
11934 return c / 2 * (sqrt(1 - t * t) + 1) + b\
11935end\
11936local function outInCirc(t, b, c, d)\
11937 if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end\
11938 return inCirc((t * 2) - d, b + c / 2, c / 2, d)\
11939end\
11940\
11941-- elastic\
11942local function calculatePAS(p,a,c,d)\
11943 p, a = p or d * 0.3, a or 0\
11944 if a < abs(c) then return p, c, p / 4 end -- p, a, s\
11945 return p, a, p / (2 * pi) * asin(c/a) -- p,a,s\
11946end\
11947local function inElastic(t, b, c, d, a, p)\
11948 local s\
11949 if t == 0 then return b end\
11950 t = t / d\
11951 if t == 1 then return b + c end\
11952 p,a,s = calculatePAS(p,a,c,d)\
11953 t = t - 1\
11954 return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b\
11955end\
11956local function outElastic(t, b, c, d, a, p)\
11957 local s\
11958 if t == 0 then return b end\
11959 t = t / d\
11960 if t == 1 then return b + c end\
11961 p,a,s = calculatePAS(p,a,c,d)\
11962 return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b\
11963end\
11964local function inOutElastic(t, b, c, d, a, p)\
11965 local s\
11966 if t == 0 then return b end\
11967 t = t / d * 2\
11968 if t == 2 then return b + c end\
11969 p,a,s = calculatePAS(p,a,c,d)\
11970 t = t - 1\
11971 if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end\
11972 return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b\
11973end\
11974local function outInElastic(t, b, c, d, a, p)\
11975 if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end\
11976 return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)\
11977end\
11978\
11979-- back\
11980local function inBack(t, b, c, d, s)\
11981 s = s or 1.70158\
11982 t = t / d\
11983 return c * t * t * ((s + 1) * t - s) + b\
11984end\
11985local function outBack(t, b, c, d, s)\
11986 s = s or 1.70158\
11987 t = t / d - 1\
11988 return c * (t * t * ((s + 1) * t + s) + 1) + b\
11989end\
11990local function inOutBack(t, b, c, d, s)\
11991 s = (s or 1.70158) * 1.525\
11992 t = t / d * 2\
11993 if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end\
11994 t = t - 2\
11995 return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b\
11996end\
11997local function outInBack(t, b, c, d, s)\
11998 if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end\
11999 return inBack((t * 2) - d, b + c / 2, c / 2, d, s)\
12000end\
12001\
12002-- bounce\
12003local function outBounce(t, b, c, d)\
12004 t = t / d\
12005 if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end\
12006 if t < 2 / 2.75 then\
12007 t = t - (1.5 / 2.75)\
12008 return c * (7.5625 * t * t + 0.75) + b\
12009 elseif t < 2.5 / 2.75 then\
12010 t = t - (2.25 / 2.75)\
12011 return c * (7.5625 * t * t + 0.9375) + b\
12012 end\
12013 t = t - (2.625 / 2.75)\
12014 return c * (7.5625 * t * t + 0.984375) + b\
12015end\
12016local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end\
12017local function inOutBounce(t, b, c, d)\
12018 if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end\
12019 return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b\
12020end\
12021local function outInBounce(t, b, c, d)\
12022 if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end\
12023 return inBounce((t * 2) - d, b + c / 2, c / 2, d)\
12024end\
12025\
12026tween.easing = {\
12027 linear = linear,\
12028 inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad,\
12029 inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic,\
12030 inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart,\
12031 inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint,\
12032 inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine,\
12033 inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo,\
12034 inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc,\
12035 inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic,\
12036 inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack,\
12037 inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce\
12038}\
12039\
12040\
12041\
12042-- private stuff\
12043\
12044local function copyTables(destination, keysTable, valuesTable)\
12045 valuesTable = valuesTable or keysTable\
12046 local mt = getmetatable(keysTable)\
12047 if mt and getmetatable(destination) == nil then\
12048 setmetatable(destination, mt)\
12049 end\
12050 for k,v in pairs(keysTable) do\
12051 if type(v) == 'table' then\
12052 destination[k] = copyTables({}, v, valuesTable[k])\
12053 else\
12054 destination[k] = valuesTable[k]\
12055 end\
12056 end\
12057 return destination\
12058end\
12059\
12060local function checkSubjectAndTargetRecursively(subject, target, path)\
12061 path = path or {}\
12062 local targetType, newPath\
12063 for k,targetValue in pairs(target) do\
12064 targetType, newPath = type(targetValue), copyTables({}, path)\
12065 table.insert(newPath, tostring(k))\
12066 if targetType == 'number' then\
12067 assert(type(subject[k]) == 'number', \"Parameter '\" .. table.concat(newPath,'/') .. \"' is missing from subject or isn't a number\")\
12068 elseif targetType == 'table' then\
12069 checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)\
12070 else\
12071 assert(targetType == 'number', \"Parameter '\" .. table.concat(newPath,'/') .. \"' must be a number or table of numbers\")\
12072 end\
12073 end\
12074end\
12075\
12076local function checkNewParams(duration, subject, target, easing)\
12077 assert(type(duration) == 'number' and duration > 0, \"duration must be a positive number. Was \" .. tostring(duration))\
12078 local tsubject = type(subject)\
12079 assert(tsubject == 'table' or tsubject == 'userdata', \"subject must be a table or userdata. Was \" .. tostring(subject))\
12080 assert(type(target)== 'table', \"target must be a table. Was \" .. tostring(target))\
12081 assert(type(easing)=='function', \"easing must be a function. Was \" .. tostring(easing))\
12082 checkSubjectAndTargetRecursively(subject, target)\
12083end\
12084\
12085local function getEasingFunction(easing)\
12086 easing = easing or \"linear\"\
12087 if type(easing) == 'string' then\
12088 local name = easing\
12089 easing = tween.easing[name]\
12090 if type(easing) ~= 'function' then\
12091 error(\"The easing function name '\" .. name .. \"' is invalid\")\
12092 end\
12093 end\
12094 return easing\
12095end\
12096\
12097local function performEasingOnSubject(subject, target, initial, clock, duration, easing)\
12098 local t,b,c,d\
12099 for k,v in pairs(target) do\
12100 if type(v) == 'table' then\
12101 performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)\
12102 else\
12103 t,b,c,d = clock, initial[k], v - initial[k], duration\
12104 subject[k] = easing(t,b,c,d)\
12105 end\
12106 end\
12107end\
12108\
12109-- Tween methods\
12110\
12111local Tween = {}\
12112local Tween_mt = {__index = Tween}\
12113\
12114function Tween:set(clock)\
12115 assert(type(clock) == 'number', \"clock must be a positive number or 0\")\
12116\
12117 self.initial = self.initial or copyTables({}, self.target, self.subject)\
12118 self.clock = clock\
12119\
12120 if self.clock <= 0 then\
12121\
12122 self.clock = 0\
12123 copyTables(self.subject, self.initial)\
12124\
12125 elseif self.clock >= self.duration then -- the tween has expired\
12126\
12127 self.clock = self.duration\
12128 copyTables(self.subject, self.target)\
12129\
12130 else\
12131\
12132 performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing)\
12133\
12134 end\
12135\
12136 return self.clock >= self.duration\
12137end\
12138\
12139function Tween:reset()\
12140 return self:set(0)\
12141end\
12142\
12143function Tween:update(dt)\
12144 assert(type(dt) == 'number', \"dt must be a number\")\
12145 return self:set(self.clock + dt)\
12146end\
12147\
12148\
12149-- Public interface\
12150\
12151function tween.new(duration, subject, target, easing)\
12152 easing = getEasingFunction(easing)\
12153 checkNewParams(duration, subject, target, easing)\
12154 return setmetatable({\
12155 duration = duration,\
12156 subject = subject,\
12157 target = target,\
12158 easing = easing,\
12159 clock = 0\
12160 }, Tween_mt)\
12161end\
12162\
12163return tween",
12164 [ "fileui.lua" ] = "local UI = require('ui')\
12165local Util = require('util')\
12166\
12167local colors = _G.colors\
12168local fs = _G.fs\
12169\
12170return function(args)\
12171\
12172 local columns = {\
12173 { heading = 'Name', key = 'name' },\
12174 }\
12175\
12176 if UI.term.width > 28 then\
12177 table.insert(columns,\
12178 { heading = 'Size', key = 'size', width = 5 }\
12179 )\
12180 end\
12181\
12182 args = args or { }\
12183\
12184 local selectFile = UI.Dialog {\
12185 x = args.x or 3,\
12186 y = args.y or 2,\
12187 z = args.z or 2,\
12188-- rex = args.rex or -3,\
12189-- rey = args.rey or -3,\
12190 height = args.height,\
12191 width = args.width,\
12192 title = 'Select File',\
12193 grid = UI.ScrollingGrid {\
12194 x = 2,\
12195 y = 2,\
12196 ex = -2,\
12197 ey = -4,\
12198 path = '',\
12199 sortColumn = 'name',\
12200 columns = columns,\
12201 },\
12202 path = UI.TextEntry {\
12203 x = 2,\
12204 y = -2,\
12205 ex = -11,\
12206 limit = 256,\
12207 accelerators = {\
12208 enter = 'path_enter',\
12209 }\
12210 },\
12211 cancel = UI.Button {\
12212 text = 'Cancel',\
12213 x = -9,\
12214 y = -2,\
12215 event = 'cancel',\
12216 },\
12217 }\
12218\
12219 function selectFile:enable(path, fn)\
12220 self:setPath(path)\
12221 self.fn = fn\
12222 UI.Dialog.enable(self)\
12223 end\
12224\
12225 function selectFile:setPath(path)\
12226 self.grid.dir = path\
12227 while not fs.isDir(self.grid.dir) do\
12228 self.grid.dir = fs.getDir(self.grid.dir)\
12229 end\
12230\
12231 self.path.value = self.grid.dir\
12232 end\
12233\
12234 function selectFile.grid:draw()\
12235 local files = fs.listEx(self.dir)\
12236 if #self.dir > 0 then\
12237 table.insert(files, {\
12238 name = '..',\
12239 isDir = true,\
12240 })\
12241 end\
12242 self:setValues(files)\
12243 self:setIndex(1)\
12244 UI.Grid.draw(self)\
12245 end\
12246\
12247 function selectFile.grid:getDisplayValues(row)\
12248 if row.size then\
12249 row = Util.shallowCopy(row)\
12250 row.size = Util.toBytes(row.size)\
12251 end\
12252 return row\
12253 end\
12254\
12255 function selectFile.grid:getRowTextColor(file)\
12256 if file.isDir then\
12257 return colors.cyan\
12258 end\
12259 if file.isReadOnly then\
12260 return colors.pink\
12261 end\
12262 return colors.white\
12263 end\
12264\
12265 function selectFile.grid:sortCompare(a, b)\
12266 if self.sortColumn == 'size' then\
12267 return a.size < b.size\
12268 end\
12269 if a.isDir == b.isDir then\
12270 return a.name:lower() < b.name:lower()\
12271 end\
12272 return a.isDir\
12273 end\
12274\
12275 function selectFile:eventHandler(event)\
12276\
12277 if event.type == 'grid_select' then\
12278 self.grid.dir = fs.combine(self.grid.dir, event.selected.name)\
12279 self.path.value = self.grid.dir\
12280 if event.selected.isDir then\
12281 self.grid:draw()\
12282 self.path:draw()\
12283 else\
12284 UI:setPreviousPage()\
12285 self.fn(self.path.value)\
12286 end\
12287\
12288 elseif event.type == 'path_enter' then\
12289 if fs.isDir(self.path.value) then\
12290 self:setPath(self.path.value)\
12291 self.grid:draw()\
12292 self.path:draw()\
12293 else\
12294 UI:setPreviousPage()\
12295 self.fn(self.path.value)\
12296 end\
12297\
12298 elseif event.type == 'cancel' then\
12299 UI:setPreviousPage()\
12300 self.fn()\
12301 else\
12302 return UI.Dialog.eventHandler(self, event)\
12303 end\
12304 return true\
12305 end\
12306\
12307 return selectFile\
12308end",
12309 [ "glasses.lua" ] = "local class = require('class')\
12310local UI = require('ui')\
12311local Event = require('event')\
12312local Peripheral = require('peripheral')\
12313\
12314--[[-- Glasses device --]]--\
12315local Glasses = class()\
12316function Glasses:init(args)\
12317\
12318 local defaults = {\
12319 backgroundColor = colors.black,\
12320 textColor = colors.white,\
12321 textScale = .5,\
12322 backgroundOpacity = .5,\
12323 multiplier = 2.6665,\
12324-- multiplier = 2.333,\
12325 }\
12326 defaults.width, defaults.height = term.getSize()\
12327\
12328 UI:setProperties(defaults, args)\
12329 UI:setProperties(self, defaults)\
12330\
12331 self.bridge = Peripheral.get({\
12332 type = 'openperipheral_bridge',\
12333 method = 'addBox',\
12334 })\
12335 self.bridge.clear()\
12336\
12337 self.setBackgroundColor = function(...) end\
12338 self.setTextColor = function(...) end\
12339\
12340 self.t = { }\
12341 for i = 1, self.height do\
12342 self.t[i] = {\
12343 text = string.rep(' ', self.width+1),\
12344 --text = self.bridge.addText(0, 40+i*4, string.rep(' ', self.width+1), 0xffffff),\
12345 bg = { },\
12346 textFields = { },\
12347 }\
12348 end\
12349end\
12350\
12351function Glasses:setBackgroundBox(boxes, ax, bx, y, bgColor)\
12352 local colors = {\
12353 [ colors.black ] = 0x000000,\
12354 [ colors.brown ] = 0x7F664C,\
12355 [ colors.blue ] = 0x253192,\
12356 [ colors.red ] = 0xFF0000,\
12357 [ colors.gray ] = 0x272727,\
12358 [ colors.lime ] = 0x426A0D,\
12359 [ colors.green ] = 0x2D5628,\
12360 [ colors.white ] = 0xFFFFFF\
12361 }\
12362\
12363 local function overlap(box, ax, bx)\
12364 if bx < box.ax or ax > box.bx then\
12365 return false\
12366 end\
12367 return true\
12368 end\
12369\
12370 for _,box in pairs(boxes) do\
12371 if overlap(box, ax, bx) then \
12372 if box.bgColor == bgColor then\
12373 ax = math.min(ax, box.ax)\
12374 bx = math.max(bx, box.bx)\
12375 box.ax = box.bx + 1\
12376 elseif ax == box.ax then\
12377 box.ax = bx + 1\
12378 elseif ax > box.ax then\
12379 if bx < box.bx then\
12380 table.insert(boxes, { -- split\
12381 ax = bx + 1,\
12382 bx = box.bx,\
12383 bgColor = box.bgColor\
12384 })\
12385 box.bx = ax - 1\
12386 break\
12387 else\
12388 box.ax = box.bx + 1\
12389 end\
12390 elseif ax < box.ax then\
12391 if bx > box.bx then\
12392 box.ax = box.bx + 1 -- delete\
12393 else\
12394 box.ax = bx + 1\
12395 end\
12396 end\
12397 end\
12398 end\
12399 if bgColor ~= colors.black then\
12400 table.insert(boxes, {\
12401 ax = ax,\
12402 bx = bx,\
12403 bgColor = bgColor\
12404 })\
12405 end\
12406\
12407 local deleted\
12408 repeat\
12409 deleted = false\
12410 for k,box in pairs(boxes) do\
12411 if box.ax > box.bx then\
12412 if box.box then\
12413 box.box.delete()\
12414 end\
12415 table.remove(boxes, k)\
12416 deleted = true\
12417 break\
12418 end\
12419 if not box.box then\
12420 box.box = self.bridge.addBox(\
12421 math.floor(self.x + (box.ax - 1) * self.multiplier),\
12422 self.y + y * 4,\
12423 math.ceil((box.bx - box.ax + 1) * self.multiplier),\
12424 4,\
12425 colors[bgColor],\
12426 self.backgroundOpacity)\
12427 else\
12428 box.box.setX(self.x + math.floor((box.ax - 1) * self.multiplier))\
12429 box.box.setWidth(math.ceil((box.bx - box.ax + 1) * self.multiplier))\
12430 end\
12431 end\
12432 until not deleted\
12433end\
12434\
12435function Glasses:write(x, y, text, bg)\
12436\
12437 if x < 1 then\
12438 error(' less ', 6)\
12439 end\
12440 if y <= #self.t then\
12441 local line = self.t[y]\
12442 local str = line.text\
12443 str = str:sub(1, x-1) .. text .. str:sub(x + #text)\
12444 self.t[y].text = str\
12445\
12446 for _,tf in pairs(line.textFields) do\
12447 tf.delete()\
12448 end\
12449 line.textFields = { }\
12450\
12451 local function split(st)\
12452 local words = { }\
12453 local offset = 0\
12454 while true do\
12455 local b,e,w = st:find('(%S+)')\
12456 if not b then\
12457 break\
12458 end\
12459 table.insert(words, {\
12460 offset = b + offset - 1,\
12461 text = w,\
12462 })\
12463 offset = offset + e\
12464 st = st:sub(e + 1)\
12465 end\
12466 return words\
12467 end\
12468\
12469 local words = split(str)\
12470 for _,word in pairs(words) do\
12471 local tf = self.bridge.addText(self.x + word.offset * self.multiplier,\
12472 self.y+y*4, '', 0xffffff)\
12473 tf.setScale(self.textScale)\
12474 tf.setZ(1)\
12475 tf.setText(word.text)\
12476 table.insert(line.textFields, tf)\
12477 end\
12478\
12479 self:setBackgroundBox(line.bg, x, x + #text - 1, y, bg)\
12480 end\
12481end\
12482\
12483function Glasses:clear(bg)\
12484 for _,line in pairs(self.t) do\
12485 for _,tf in pairs(line.textFields) do\
12486 tf.delete()\
12487 end\
12488 line.textFields = { }\
12489 line.text = string.rep(' ', self.width+1)\
12490-- self.t[i].text.setText('')\
12491 end\
12492end\
12493\
12494function Glasses:reset()\
12495 self:clear()\
12496 self.bridge.clear()\
12497 self.bridge.sync()\
12498end\
12499\
12500function Glasses:sync()\
12501 self.bridge.sync()\
12502end\
12503\
12504return Glasses",
12505 [ "canvas.lua" ] = "local class = require('class')\
12506local Region = require('ui.region')\
12507local Util = require('util')\
12508\
12509local _rep = string.rep\
12510local _sub = string.sub\
12511local _gsub = string.gsub\
12512local colors = _G.colors\
12513\
12514local Canvas = class()\
12515\
12516Canvas.colorPalette = { }\
12517Canvas.darkPalette = { }\
12518Canvas.grayscalePalette = { }\
12519\
12520for n = 1, 16 do\
12521 Canvas.colorPalette[2 ^ (n - 1)] = _sub(\"0123456789abcdef\", n, n)\
12522 Canvas.grayscalePalette[2 ^ (n - 1)] = _sub(\"088888878877787f\", n, n)\
12523 Canvas.darkPalette[2 ^ (n - 1)] = _sub(\"8777777f77fff77f\", n, n)\
12524end\
12525\
12526function Canvas:init(args)\
12527 self.x = 1\
12528 self.y = 1\
12529 self.layers = { }\
12530\
12531 Util.merge(self, args)\
12532\
12533 self.ex = self.x + self.width - 1\
12534 self.ey = self.y + self.height - 1\
12535\
12536 if not self.palette then\
12537 if self.isColor then\
12538 self.palette = Canvas.colorPalette\
12539 else\
12540 self.palette = Canvas.grayscalePalette\
12541 end\
12542 end\
12543\
12544 self.lines = { }\
12545 for i = 1, self.height do\
12546 self.lines[i] = { }\
12547 end\
12548end\
12549\
12550function Canvas:move(x, y)\
12551 self.x, self.y = x, y\
12552 self.ex = self.x + self.width - 1\
12553 self.ey = self.y + self.height - 1\
12554end\
12555\
12556function Canvas:resize(w, h)\
12557 for i = self.height, h do\
12558 self.lines[i] = { }\
12559 end\
12560\
12561 while #self.lines > h do\
12562 table.remove(self.lines, #self.lines)\
12563 end\
12564\
12565 if w ~= self.width then\
12566 for i = 1, self.height do\
12567 self.lines[i] = { dirty = true }\
12568 end\
12569 end\
12570\
12571 self.ex = self.x + w - 1\
12572 self.ey = self.y + h - 1\
12573 self.width = w\
12574 self.height = h\
12575end\
12576\
12577function Canvas:copy()\
12578 local b = Canvas({\
12579 x = self.x,\
12580 y = self.y,\
12581 width = self.width,\
12582 height = self.height,\
12583 isColor = self.isColor,\
12584 })\
12585 for i = 1, self.height do\
12586 b.lines[i].text = self.lines[i].text\
12587 b.lines[i].fg = self.lines[i].fg\
12588 b.lines[i].bg = self.lines[i].bg\
12589 end\
12590 return b\
12591end\
12592\
12593function Canvas:addLayer(layer)\
12594 local canvas = Canvas({\
12595 x = layer.x,\
12596 y = layer.y,\
12597 width = layer.width,\
12598 height = layer.height,\
12599 isColor = self.isColor,\
12600 })\
12601 canvas.parent = self\
12602 table.insert(self.layers, canvas)\
12603 return canvas\
12604end\
12605\
12606function Canvas:removeLayer()\
12607 for k, layer in pairs(self.parent.layers) do\
12608 if layer == self then\
12609 self:setVisible(false)\
12610 table.remove(self.parent.layers, k)\
12611 break\
12612 end\
12613 end\
12614end\
12615\
12616function Canvas:setVisible(visible)\
12617 self.visible = visible\
12618 if not visible then\
12619 self.parent:dirty()\
12620 -- set parent's lines to dirty for each line in self\
12621 end\
12622end\
12623\
12624function Canvas:write(x, y, text, bg, fg)\
12625 if bg then\
12626 bg = _rep(self.palette[bg], #text)\
12627 end\
12628 if fg then\
12629 fg = _rep(self.palette[fg], #text)\
12630 end\
12631 self:writeBlit(x, y, text, bg, fg)\
12632end\
12633\
12634function Canvas:writeBlit(x, y, text, bg, fg)\
12635 if y > 0 and y <= #self.lines and x <= self.width then\
12636 local width = #text\
12637\
12638 -- fix ffs\
12639 if x < 1 then\
12640 text = _sub(text, 2 - x)\
12641 if bg then\
12642 bg = _sub(bg, 2 - x)\
12643 end\
12644 if fg then\
12645 fg = _sub(fg, 2 - x)\
12646 end\
12647 width = width + x - 1\
12648 x = 1\
12649 end\
12650\
12651 if x + width - 1 > self.width then\
12652 text = _sub(text, 1, self.width - x + 1)\
12653 if bg then\
12654 bg = _sub(bg, 1, self.width - x + 1)\
12655 end\
12656 if fg then\
12657 fg = _sub(fg, 1, self.width - x + 1)\
12658 end\
12659 width = #text\
12660 end\
12661\
12662 if width > 0 then\
12663\
12664 local function replace(sstr, pos, rstr, width)\
12665 if pos == 1 and width == self.width then\
12666 return rstr\
12667 elseif pos == 1 then\
12668 return rstr .. _sub(sstr, pos+width)\
12669 elseif pos + width > self.width then\
12670 return _sub(sstr, 1, pos-1) .. rstr\
12671 end\
12672 return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width)\
12673 end\
12674\
12675 local line = self.lines[y]\
12676 line.dirty = true\
12677 line.text = replace(line.text, x, text, width)\
12678 if fg then\
12679 line.fg = replace(line.fg, x, fg, width)\
12680 end\
12681 if bg then\
12682 line.bg = replace(line.bg, x, bg, width)\
12683 end\
12684 end\
12685 end\
12686end\
12687\
12688function Canvas:writeLine(y, text, fg, bg)\
12689 self.lines[y].dirty = true\
12690 self.lines[y].text = text\
12691 self.lines[y].fg = fg\
12692 self.lines[y].bg = bg\
12693end\
12694\
12695function Canvas:reset()\
12696 self.regions = nil\
12697end\
12698\
12699function Canvas:clear(bg, fg)\
12700 local text = _rep(' ', self.width)\
12701 fg = _rep(self.palette[fg or colors.white], self.width)\
12702 bg = _rep(self.palette[bg or colors.black], self.width)\
12703 for i = 1, self.height do\
12704 self:writeLine(i, text, fg, bg)\
12705 end\
12706end\
12707\
12708function Canvas:punch(rect)\
12709 if not self.regions then\
12710 self.regions = Region.new(self.x, self.y, self.ex, self.ey)\
12711 end\
12712 self.regions:subRect(rect.x, rect.y, rect.ex, rect.ey)\
12713end\
12714\
12715function Canvas:blitClipped(device)\
12716 for _,region in ipairs(self.regions.region) do\
12717 self:blit(device,\
12718 { x = region[1] - self.x + 1,\
12719 y = region[2] - self.y + 1,\
12720 ex = region[3]- self.x + 1,\
12721 ey = region[4] - self.y + 1 },\
12722 { x = region[1], y = region[2] })\
12723 end\
12724end\
12725\
12726function Canvas:redraw(device)\
12727 self:reset()\
12728 if #self.layers > 0 then\
12729 for _,layer in pairs(self.layers) do\
12730 self:punch(layer)\
12731 end\
12732 self:blitClipped(device)\
12733 else\
12734 self:blit(device)\
12735 end\
12736 self:clean()\
12737end\
12738\
12739function Canvas:isDirty()\
12740 for _, line in pairs(self.lines) do\
12741 if line.dirty then\
12742 return true\
12743 end\
12744 end\
12745end\
12746\
12747function Canvas:dirty()\
12748 for _, line in pairs(self.lines) do\
12749 line.dirty = true\
12750 end\
12751end\
12752\
12753function Canvas:clean()\
12754 for _, line in pairs(self.lines) do\
12755 line.dirty = false\
12756 end\
12757end\
12758\
12759function Canvas:render(device, layers) --- redrawAll ?\
12760 layers = layers or self.layers\
12761 if #layers > 0 then\
12762 self.regions = Region.new(self.x, self.y, self.ex, self.ey)\
12763 local l = Util.shallowCopy(layers)\
12764 for _, canvas in ipairs(layers) do\
12765 table.remove(l, 1)\
12766 if canvas.visible then\
12767 self:punch(canvas)\
12768 canvas:render(device, l)\
12769 end\
12770 end\
12771 self:blitClipped(device)\
12772 self:reset()\
12773 else\
12774 self:blit(device)\
12775 end\
12776 self:clean()\
12777end\
12778\
12779function Canvas:blit(device, src, tgt)\
12780 src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 }\
12781 tgt = tgt or self\
12782\
12783 for i = 0, src.ey - src.y do\
12784 local line = self.lines[src.y + i]\
12785 if line and line.dirty then\
12786 local t, fg, bg = line.text, line.fg, line.bg\
12787 if src.x > 1 or src.ex < self.ex then\
12788 t = _sub(t, src.x, src.ex)\
12789 fg = _sub(fg, src.x, src.ex)\
12790 bg = _sub(bg, src.x, src.ex)\
12791 end\
12792 --if tgt.y + i > self.ey then -- wrong place to do clipping ??\
12793 -- break\
12794 --end\
12795 device.setCursorPos(tgt.x, tgt.y + i)\
12796 device.blit(t, fg, bg)\
12797 end\
12798 end\
12799end\
12800\
12801function Canvas:applyPalette(palette)\
12802\
12803 local lookup = { }\
12804 for n = 1, 16 do\
12805 lookup[self.palette[2 ^ (n - 1)]] = palette[2 ^ (n - 1)]\
12806 end\
12807\
12808 for _, l in pairs(self.lines) do\
12809 l.fg = _gsub(l.fg, '%w', lookup)\
12810 l.bg = _gsub(l.bg, '%w', lookup)\
12811 l.dirty = true\
12812 end\
12813\
12814 self.palette = palette\
12815end\
12816\
12817function Canvas.convertWindow(win, parent, wx, wy)\
12818 local w, h = win.getSize()\
12819\
12820 win.canvas = Canvas({\
12821 x = wx,\
12822 y = wy,\
12823 width = w,\
12824 height = h,\
12825 isColor = win.isColor(),\
12826 })\
12827\
12828 function win.clear()\
12829 win.canvas:clear(win.getBackgroundColor(), win.getTextColor())\
12830 end\
12831\
12832 function win.clearLine()\
12833 local _, y = win.getCursorPos()\
12834 win.canvas:write(1,\
12835 y,\
12836 _rep(' ', win.canvas.width),\
12837 win.getBackgroundColor(),\
12838 win.getTextColor())\
12839 end\
12840\
12841 function win.write(str)\
12842 local x, y = win.getCursorPos()\
12843 win.canvas:write(x,\
12844 y,\
12845 str,\
12846 win.getBackgroundColor(),\
12847 win.getTextColor())\
12848 win.setCursorPos(x + #str, y)\
12849 end\
12850\
12851 function win.blit(text, fg, bg)\
12852 local x, y = win.getCursorPos()\
12853 win.canvas:writeBlit(x, y, text, bg, fg)\
12854 end\
12855\
12856 function win.redraw()\
12857 win.canvas:redraw(parent)\
12858 end\
12859\
12860 function win.scroll(n)\
12861 table.insert(win.canvas.lines, table.remove(win.canvas.lines, 1))\
12862 win.canvas.lines[#win.canvas.lines].text = _rep(' ', win.canvas.width)\
12863 win.canvas:dirty()\
12864 end\
12865\
12866 function win.reposition(x, y, width, height)\
12867 win.canvas.x, win.canvas.y = x, y\
12868 win.canvas:resize(width or win.canvas.width, height or win.canvas.height)\
12869 end\
12870\
12871 win.clear()\
12872end\
12873\
12874function Canvas.scrollingWindow(win, wx, wy)\
12875 local w, h = win.getSize()\
12876 local scrollPos = 0\
12877 local maxScroll = h\
12878\
12879 -- canvas lines are are a sliding window within the local lines table\
12880 local lines = { }\
12881\
12882 local parent\
12883 local canvas = Canvas({\
12884 x = wx,\
12885 y = wy,\
12886 width = w,\
12887 height = h,\
12888 isColor = win.isColor(),\
12889 })\
12890 win.canvas = canvas\
12891\
12892 local function scrollTo(p, forceRedraw)\
12893 local ms = #lines - canvas.height -- max scroll\
12894 p = math.min(math.max(p, 0), ms) -- normalize\
12895\
12896 if p ~= scrollPos or forceRedraw then\
12897 scrollPos = p\
12898 for i = 1, canvas.height do\
12899 canvas.lines[i] = lines[i + scrollPos]\
12900 end\
12901 canvas:dirty()\
12902 end\
12903 end\
12904\
12905 function win.blit(text, fg, bg)\
12906 local x, y = win.getCursorPos()\
12907 win.canvas:writeBlit(x, y, text, bg, fg)\
12908 win.redraw()\
12909 end\
12910\
12911 function win.clear()\
12912 lines = { }\
12913 for i = 1, canvas.height do\
12914 lines[i] = canvas.lines[i]\
12915 end\
12916 scrollPos = 0\
12917 canvas:clear(win.getBackgroundColor(), win.getTextColor())\
12918 win.redraw()\
12919 end\
12920\
12921 function win.clearLine()\
12922 local _, y = win.getCursorPos()\
12923\
12924 scrollTo(#lines - canvas.height)\
12925 win.canvas:write(1,\
12926 y,\
12927 _rep(' ', win.canvas.width),\
12928 win.getBackgroundColor(),\
12929 win.getTextColor())\
12930 win.redraw()\
12931 end\
12932\
12933 function win.redraw()\
12934 if parent and canvas.visible then\
12935 local x, y = win.getCursorPos()\
12936 for i = 1, canvas.height do\
12937 local line = canvas.lines[i]\
12938 if line and line.dirty then\
12939 parent.setCursorPos(canvas.x, canvas.y + i - 1)\
12940 parent.blit(line.text, line.fg, line.bg)\
12941 line.dirty = false\
12942 end\
12943 end\
12944 win.setCursorPos(x, y)\
12945 end\
12946 end\
12947\
12948 -- doesn't support negative scrolling...\
12949 function win.scroll(n)\
12950 for _ = 1, n do\
12951 lines[#lines + 1] = {\
12952 text = _rep(' ', canvas.width),\
12953 fg = _rep(canvas.palette[win.getTextColor()], canvas.width),\
12954 bg = _rep(canvas.palette[win.getBackgroundColor()], canvas.width),\
12955 }\
12956 end\
12957\
12958 while #lines > maxScroll do\
12959 table.remove(lines, 1)\
12960 end\
12961\
12962 scrollTo(maxScroll, true)\
12963 win.redraw()\
12964 end\
12965\
12966 function win.scrollDown()\
12967 scrollTo(scrollPos + 1)\
12968 win.redraw()\
12969 end\
12970\
12971 function win.scrollUp()\
12972 scrollTo(scrollPos - 1)\
12973 win.redraw()\
12974 end\
12975\
12976 function win.setMaxScroll(ms)\
12977 maxScroll = ms\
12978 end\
12979\
12980 function win.setParent(p)\
12981 parent = p\
12982 end\
12983\
12984 function win.write(str)\
12985 str = tostring(str) or ''\
12986\
12987 local x, y = win.getCursorPos()\
12988 scrollTo(#lines - canvas.height)\
12989 win.blit(str,\
12990 _rep(canvas.palette[win.getTextColor()], #str),\
12991 _rep(canvas.palette[win.getBackgroundColor()], #str))\
12992 win.setCursorPos(x + #str, y)\
12993 end\
12994\
12995 function win.reposition(x, y, width, height)\
12996 win.canvas.x, win.canvas.y = x, y\
12997 win.canvas:resize(width or win.canvas.width, height or win.canvas.height)\
12998 end\
12999\
13000 win.clear()\
13001end\
13002return Canvas",
13003 },
13004 [ "point.lua" ] = "local Util = require('util')\
13005\
13006local Point = { }\
13007\
13008Point.directions = {\
13009 [ 0 ] = { xd = 1, zd = 0, yd = 0, heading = 0, direction = 'east' },\
13010 [ 1 ] = { xd = 0, zd = 1, yd = 0, heading = 1, direction = 'south' },\
13011 [ 2 ] = { xd = -1, zd = 0, yd = 0, heading = 2, direction = 'west' },\
13012 [ 3 ] = { xd = 0, zd = -1, yd = 0, heading = 3, direction = 'north' },\
13013 [ 4 ] = { xd = 0, zd = 0, yd = 1, heading = 4, direction = 'up' },\
13014 [ 5 ] = { xd = 0, zd = 0, yd = -1, heading = 5, direction = 'down' },\
13015}\
13016\
13017Point.facings = {\
13018 [ 0 ] = Point.directions[0],\
13019 [ 1 ] = Point.directions[1],\
13020 [ 2 ] = Point.directions[2],\
13021 [ 3 ] = Point.directions[3],\
13022 east = Point.directions[0],\
13023 south = Point.directions[1],\
13024 west = Point.directions[2],\
13025 north = Point.directions[3],\
13026}\
13027\
13028Point.headings = {\
13029 [ 0 ] = Point.directions[0],\
13030 [ 1 ] = Point.directions[1],\
13031 [ 2 ] = Point.directions[2],\
13032 [ 3 ] = Point.directions[3],\
13033 [ 4 ] = Point.directions[4],\
13034 [ 5 ] = Point.directions[5],\
13035 east = Point.directions[0],\
13036 south = Point.directions[1],\
13037 west = Point.directions[2],\
13038 north = Point.directions[3],\
13039 up = Point.directions[4],\
13040 down = Point.directions[5],\
13041}\
13042\
13043Point.EAST = 0\
13044Point.SOUTH = 1\
13045Point.WEST = 2\
13046Point.NORTH = 3\
13047Point.UP = 4\
13048Point.DOWN = 5\
13049\
13050function Point.copy(pt)\
13051 return { x = pt.x, y = pt.y, z = pt.z }\
13052end\
13053\
13054function Point.round(pt)\
13055 pt.x = Util.round(pt.x)\
13056 pt.y = Util.round(pt.y)\
13057 pt.z = Util.round(pt.z)\
13058 return pt\
13059end\
13060\
13061function Point.same(pta, ptb)\
13062 return pta.x == ptb.x and\
13063 pta.y == ptb.y and\
13064 pta.z == ptb.z\
13065end\
13066\
13067function Point.above(pt)\
13068 return { x = pt.x, y = pt.y + 1, z = pt.z, heading = pt.heading }\
13069end\
13070\
13071function Point.below(pt)\
13072 return { x = pt.x, y = pt.y - 1, z = pt.z, heading = pt.heading }\
13073end\
13074\
13075function Point.subtract(a, b)\
13076 a.x = a.x - b.x\
13077 a.y = a.y - b.y\
13078 a.z = a.z - b.z\
13079end\
13080\
13081-- Euclidian distance\
13082function Point.distance(a, b)\
13083 return math.sqrt(\
13084 math.pow(a.x - b.x, 2) +\
13085 math.pow(a.y - b.y, 2) +\
13086 math.pow(a.z - b.z, 2))\
13087end\
13088\
13089-- turtle distance (manhattan)\
13090function Point.turtleDistance(a, b)\
13091 if a.y and b.y then\
13092 return math.abs(a.x - b.x) +\
13093 math.abs(a.y - b.y) +\
13094 math.abs(a.z - b.z)\
13095 else\
13096 return math.abs(a.x - b.x) +\
13097 math.abs(a.z - b.z)\
13098 end\
13099end\
13100\
13101function Point.calculateTurns(ih, oh)\
13102 if ih == oh then\
13103 return 0\
13104 end\
13105 if (ih % 2) == (oh % 2) then\
13106 return 2\
13107 end\
13108 return 1\
13109end\
13110\
13111function Point.calculateHeading(pta, ptb)\
13112 local heading\
13113 local xd, zd = pta.x - ptb.x, pta.z - ptb.z\
13114\
13115 if (pta.heading % 2) == 0 and zd ~= 0 then\
13116 heading = zd < 0 and 1 or 3\
13117 elseif (pta.heading % 2) == 1 and xd ~= 0 then\
13118 heading = xd < 0 and 0 or 2\
13119 elseif pta.heading == 0 and xd > 0 then\
13120 heading = 2\
13121 elseif pta.heading == 2 and xd < 0 then\
13122 heading = 0\
13123 elseif pta.heading == 1 and zd > 0 then\
13124 heading = 3\
13125 elseif pta.heading == 3 and zd < 0 then\
13126 heading = 1\
13127 end\
13128\
13129 return heading or pta.heading\
13130end\
13131\
13132-- Calculate distance to location including turns\
13133-- also returns the resulting heading\
13134function Point.calculateMoves(pta, ptb, distance)\
13135 local heading = pta.heading\
13136 local moves = distance or Point.turtleDistance(pta, ptb)\
13137 if (pta.heading % 2) == 0 and pta.z ~= ptb.z then\
13138 moves = moves + 1\
13139 if ptb.heading and (ptb.heading % 2 == 1) then\
13140 heading = ptb.heading\
13141 elseif ptb.z > pta.z then\
13142 heading = 1\
13143 else\
13144 heading = 3\
13145 end\
13146 elseif (pta.heading % 2) == 1 and pta.x ~= ptb.x then\
13147 moves = moves + 1\
13148 if ptb.heading and (ptb.heading % 2 == 0) then\
13149 heading = ptb.heading\
13150 elseif ptb.x > pta.x then\
13151 heading = 0\
13152 else\
13153 heading = 2\
13154 end\
13155 end\
13156\
13157 if not ptb.heading then\
13158 return moves, heading, moves\
13159 end\
13160\
13161 -- calc turns as slightly less than moves\
13162 local weighted = moves\
13163 if heading ~= ptb.heading then\
13164 local turns = Point.calculateTurns(heading, ptb.heading)\
13165 moves = moves + turns\
13166 local wturns = { [0] = 0, [1] = .9, [2] = 1.9 }\
13167 weighted = weighted + wturns[turns]\
13168 heading = ptb.heading\
13169 end\
13170\
13171 return moves, heading, weighted\
13172end\
13173\
13174-- given a set of points, find the one taking the least moves\
13175function Point.closest(reference, pts)\
13176 if #pts == 1 then\
13177 return pts[1]\
13178 end\
13179\
13180 local lm, lpt = math.huge\
13181 for _,pt in pairs(pts) do\
13182 local distance = Point.turtleDistance(reference, pt)\
13183 if distance < lm then\
13184 local _, _, m = Point.calculateMoves(reference, pt, distance)\
13185 if m < lm then\
13186 lpt = pt\
13187 lm = m\
13188 end\
13189 end\
13190 end\
13191 return lpt\
13192end\
13193\
13194function Point.eachClosest(spt, ipts, fn)\
13195 local pts = Util.shallowCopy(ipts)\
13196 while #pts > 0 do\
13197 local pt = Point.closest(spt, pts)\
13198 local r = fn(pt)\
13199 if r then\
13200 return r\
13201 end\
13202 Util.removeByValue(pts, pt)\
13203 end\
13204end\
13205\
13206function Point.adjacentPoints(pt)\
13207 local pts = { }\
13208\
13209 for i = 0, 5 do\
13210 local hi = Point.headings[i]\
13211 table.insert(pts, { x = pt.x + hi.xd, y = pt.y + hi.yd, z = pt.z + hi.zd })\
13212 end\
13213\
13214 return pts\
13215end\
13216\
13217-- get the point nearest A that is in the direction of B\
13218function Point.nearestTo(pta, ptb)\
13219 local heading\
13220\
13221 if pta.x < ptb.x then\
13222 heading = 0\
13223 elseif pta.z < ptb.z then\
13224 heading = 1\
13225 elseif pta.x > ptb.x then\
13226 heading = 2\
13227 elseif pta.z > ptb.z then\
13228 heading = 3\
13229 elseif pta.y < ptb.y then\
13230 heading = 4\
13231 elseif pta.y > ptb.y then\
13232 heading = 5\
13233 end\
13234\
13235 if heading then\
13236 return {\
13237 x = pta.x + Point.headings[heading].xd,\
13238 y = pta.y + Point.headings[heading].yd,\
13239 z = pta.z + Point.headings[heading].zd,\
13240 }\
13241 end\
13242\
13243 return pta -- error ?\
13244end\
13245\
13246function Point.rotate(pt, facing)\
13247 local x, z = pt.x, pt.z\
13248 if facing == 1 then\
13249 pt.x = z\
13250 pt.z = -x\
13251 elseif facing == 2 then\
13252 pt.x = -x\
13253 pt.z = -z\
13254 elseif facing == 3 then\
13255 pt.x = -z\
13256 pt.z = x\
13257 end\
13258end\
13259\
13260function Point.makeBox(pt1, pt2)\
13261 return {\
13262 x = pt1.x,\
13263 y = pt1.y,\
13264 z = pt1.z,\
13265 ex = pt2.x,\
13266 ey = pt2.y,\
13267 ez = pt2.z,\
13268 }\
13269end\
13270\
13271-- expand box to include point\
13272function Point.expandBox(box, pt)\
13273 if pt.x < box.x then\
13274 box.x = pt.x\
13275 elseif pt.x > box.ex then\
13276 box.ex = pt.x\
13277 end\
13278 if pt.y < box.y then\
13279 box.y = pt.y\
13280 elseif pt.y > box.ey then\
13281 box.ey = pt.y\
13282 end\
13283 if pt.z < box.z then\
13284 box.z = pt.z\
13285 elseif pt.z > box.ez then\
13286 box.ez = pt.z\
13287 end\
13288end\
13289\
13290function Point.normalizeBox(box)\
13291 return {\
13292 x = math.min(box.x, box.ex),\
13293 y = math.min(box.y, box.ey),\
13294 z = math.min(box.z, box.ez),\
13295 ex = math.max(box.x, box.ex),\
13296 ey = math.max(box.y, box.ey),\
13297 ez = math.max(box.z, box.ez),\
13298 }\
13299end\
13300\
13301function Point.inBox(pt, box)\
13302 return pt.x >= box.x and\
13303 pt.y >= box.y and\
13304 pt.z >= box.z and\
13305 pt.x <= box.ex and\
13306 pt.y <= box.ey and\
13307 pt.z <= box.ez\
13308end\
13309\
13310function Point.closestPointInBox(pt, box)\
13311 local cpt = {\
13312 x = math.abs(pt.x - box.x) < math.abs(pt.x - box.ex) and box.x or box.ex,\
13313 y = math.abs(pt.y - box.y) < math.abs(pt.y - box.ey) and box.y or box.ey,\
13314 z = math.abs(pt.z - box.z) < math.abs(pt.z - box.ez) and box.z or box.ez,\
13315 }\
13316 cpt.x = pt.x > box.x and pt.x < box.ex and pt.x or cpt.x\
13317 cpt.y = pt.y > box.y and pt.y < box.ey and pt.y or cpt.y\
13318 cpt.z = pt.z > box.z and pt.z < box.ez and pt.z or cpt.z\
13319\
13320 return cpt\
13321end\
13322\
13323return Point",
13324 [ "injector.lua" ] = "local PASTEBIN_URL = 'http://pastebin.com/raw'\
13325local GIT_URL = 'https://raw.githubusercontent.com'\
13326local DEFAULT_PATH = 'sys/os/opus/sys/apis'\
13327local DEFAULT_BRANCH = _ENV.OPUS_BRANCH or _G.OPUS_BRANCH or 'master'\
13328local DEFAULT_UPATH = GIT_URL .. '/kepler155c/opus/' .. DEFAULT_BRANCH .. '/sys/apis'\
13329\
13330local fs = _G.fs\
13331local http = _G.http\
13332local os = _G.os\
13333\
13334if not http._patched then\
13335 -- fix broken http get\
13336 local syncLocks = { }\
13337\
13338 local function sync(obj, fn)\
13339 local key = tostring(obj)\
13340 if syncLocks[key] then\
13341 local cos = tostring(coroutine.running())\
13342 table.insert(syncLocks[key], cos)\
13343 repeat\
13344 local _, co = os.pullEvent('sync_lock')\
13345 until co == cos\
13346 else\
13347 syncLocks[key] = { }\
13348 end\
13349 fn()\
13350 local co = table.remove(syncLocks[key], 1)\
13351 if co then\
13352 os.queueEvent('sync_lock', co)\
13353 else\
13354 syncLocks[key] = nil\
13355 end\
13356 end\
13357\
13358 -- todo -- completely replace http.get with function that\
13359 -- checks for success on permanent redirects (minecraft 1.75 bug)\
13360\
13361 http._patched = http.get\
13362 function http.get(url, headers)\
13363 local s, m\
13364 sync(url, function()\
13365 s, m = http._patched(url, headers)\
13366 end)\
13367 return s, m\
13368 end\
13369end\
13370\
13371local function loadUrl(url)\
13372 local c\
13373 local h = http.get(url)\
13374 if h then\
13375 c = h.readAll()\
13376 h.close()\
13377 end\
13378 if c and #c > 0 then\
13379 return c\
13380 end\
13381end\
13382\
13383-- Add require and package to the environment\
13384local function requireWrapper(env)\
13385\
13386 local function standardSearcher(modname)\
13387 if env.package.loaded[modname] then\
13388 return function()\
13389 return env.package.loaded[modname]\
13390 end\
13391 end\
13392 end\
13393\
13394 local function shellSearcher(modname)\
13395 local fname = modname:gsub('%.', '/') .. '.lua'\
13396\
13397 if env.shell and type(env.shell.dir) == 'function' then\
13398 local path = env.shell.resolve(fname)\
13399 if fs.exists(path) and not fs.isDir(path) then\
13400 return loadfile(path, env)\
13401 end\
13402 end\
13403 end\
13404\
13405 local function pathSearcher(modname)\
13406 local fname = modname:gsub('%.', '/') .. '.lua'\
13407\
13408 for dir in string.gmatch(env.package.path, \"[^:]+\") do\
13409 local path = fs.combine(dir, fname)\
13410 if fs.exists(path) and not fs.isDir(path) then\
13411 return loadfile(path, env)\
13412 end\
13413 end\
13414 end\
13415\
13416 -- require('BniCQPVf')\
13417 local function pastebinSearcher(modname)\
13418 if #modname == 8 and not modname:match('%W') then\
13419 local url = PASTEBIN_URL .. '/' .. modname\
13420 local c = loadUrl(url)\
13421 if c then\
13422 return load(c, modname, nil, env)\
13423 end\
13424 end\
13425 end\
13426\
13427 -- require('kepler155c.opus.master.sys.apis.util')\
13428 local function gitSearcher(modname)\
13429 local fname = modname:gsub('%.', '/') .. '.lua'\
13430 local _, count = fname:gsub(\"/\", \"\")\
13431 if count >= 3 then\
13432 local url = GIT_URL .. '/' .. fname\
13433 local c = loadUrl(url)\
13434 if c then\
13435 return load(c, modname, nil, env)\
13436 end\
13437 end\
13438 end\
13439\
13440 local function urlSearcher(modname)\
13441 local fname = modname:gsub('%.', '/') .. '.lua'\
13442\
13443 if fname:sub(1, 1) ~= '/' then\
13444 for entry in string.gmatch(env.package.upath, \"[^;]+\") do\
13445 local url = entry .. '/' .. fname\
13446 local c = loadUrl(url)\
13447 if c then\
13448 return load(c, modname, nil, env)\
13449 end\
13450 end\
13451 end\
13452 end\
13453\
13454 -- place package and require function into env\
13455 env.package = {\
13456 path = env.LUA_PATH or _G.LUA_PATH or DEFAULT_PATH,\
13457 upath = env.LUA_UPATH or _G.LUA_UPATH or DEFAULT_UPATH,\
13458 config = '/\\n:\\n?\\n!\\n-',\
13459 loaded = {\
13460 math = math,\
13461 string = string,\
13462 table = table,\
13463 io = io,\
13464 os = os,\
13465 },\
13466 loaders = {\
13467 standardSearcher,\
13468 shellSearcher,\
13469 pathSearcher,\
13470 pastebinSearcher,\
13471 gitSearcher,\
13472 urlSearcher,\
13473 }\
13474 }\
13475\
13476 function env.require(modname)\
13477 for _,searcher in ipairs(env.package.loaders) do\
13478 local fn, msg = searcher(modname)\
13479 if fn then\
13480 local module, msg2 = fn(modname, env)\
13481 if not module then\
13482 error(msg2 or (modname .. ' module returned nil'), 2)\
13483 end\
13484 env.package.loaded[modname] = module\
13485 return module\
13486 end\
13487 if msg then\
13488 error(msg, 2)\
13489 end\
13490 end\
13491 error('Unable to find module ' .. modname)\
13492 end\
13493\
13494 return env.require -- backwards compatible\
13495end\
13496\
13497return function(env)\
13498 env = env or getfenv(2)\
13499 --setfenv(requireWrapper, env)\
13500 return requireWrapper(env)\
13501end",
13502 [ "gps.lua" ] = "local GPS = { }\
13503\
13504local device = _G.device\
13505local gps = _G.gps\
13506local turtle = _G.turtle\
13507\
13508function GPS.locate(timeout, debug)\
13509 local pt = { }\
13510 timeout = timeout or 10\
13511 pt.x, pt.y, pt.z = gps.locate(timeout, debug)\
13512 if pt.x then\
13513 return pt\
13514 end\
13515end\
13516\
13517function GPS.isAvailable()\
13518 return device.wireless_modem and GPS.locate()\
13519end\
13520\
13521function GPS.getPoint(timeout, debug)\
13522 local pt = GPS.locate(timeout, debug)\
13523 if not pt then\
13524 return\
13525 end\
13526\
13527 pt.x = math.floor(pt.x)\
13528 pt.y = math.floor(pt.y)\
13529 pt.z = math.floor(pt.z)\
13530\
13531 if _G.pocket then\
13532 pt.y = pt.y - 1\
13533 end\
13534\
13535 return pt\
13536end\
13537\
13538function GPS.getHeading(timeout)\
13539\
13540 if not turtle then\
13541 return\
13542 end\
13543\
13544 local apt = GPS.locate(timeout)\
13545 if not apt then\
13546 return\
13547 end\
13548\
13549 local heading = turtle.point.heading\
13550\
13551 while not turtle.forward() do\
13552 turtle.turnRight()\
13553 if turtle.getHeading() == heading then\
13554 _G.printError('GPS.getPoint: Unable to move forward')\
13555 return\
13556 end\
13557 end\
13558\
13559 local bpt = GPS.locate()\
13560 if not bpt then\
13561 return\
13562 end\
13563\
13564 if apt.x < bpt.x then\
13565 return 0\
13566 elseif apt.z < bpt.z then\
13567 return 1\
13568 elseif apt.x > bpt.x then\
13569 return 2\
13570 end\
13571 return 3\
13572end\
13573\
13574function GPS.getPointAndHeading(timeout)\
13575 local heading = GPS.getHeading(timeout)\
13576 if heading then\
13577 local pt = GPS.getPoint()\
13578 if pt then\
13579 pt.heading = heading\
13580 end\
13581 return pt\
13582 end\
13583end\
13584\
13585-- from stock gps API\
13586local function trilaterate(A, B, C)\
13587 local a2b = B.position - A.position\
13588 local a2c = C.position - A.position\
13589\
13590 if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then\
13591 return\
13592 end\
13593\
13594 local d = a2b:length()\
13595 local ex = a2b:normalize( )\
13596 local i = ex:dot( a2c )\
13597 local ey = (a2c - (ex * i)):normalize()\
13598 local j = ey:dot( a2c )\
13599 local ez = ex:cross( ey )\
13600\
13601 local r1 = A.distance\
13602 local r2 = B.distance\
13603 local r3 = C.distance\
13604\
13605 local x = (r1*r1 - r2*r2 + d*d) / (2*d)\
13606 local y = (r1*r1 - r3*r3 - x*x + (x-i)*(x-i) + j*j) / (2*j)\
13607\
13608 local result = A.position + (ex * x) + (ey * y)\
13609\
13610 local zSquared = r1*r1 - x*x - y*y\
13611 if zSquared > 0 then\
13612 local z = math.sqrt( zSquared )\
13613 local result1 = result + (ez * z)\
13614 local result2 = result - (ez * z)\
13615\
13616 local rounded1, rounded2 = result1:round(), result2:round()\
13617 if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then\
13618 return rounded1, rounded2\
13619 else\
13620 return rounded1\
13621 end\
13622 end\
13623 return result:round()\
13624end\
13625\
13626local function narrow( p1, p2, fix )\
13627 local dist1 = math.abs( (p1 - fix.position):length() - fix.distance )\
13628 local dist2 = math.abs( (p2 - fix.position):length() - fix.distance )\
13629\
13630 if math.abs(dist1 - dist2) < 0.05 then\
13631 return p1, p2\
13632 elseif dist1 < dist2 then\
13633 return p1:round()\
13634 else\
13635 return p2:round()\
13636 end\
13637end\
13638-- end stock gps api\
13639\
13640function GPS.trilaterate(tFixes)\
13641 local pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[3])\
13642\
13643 if pos2 then\
13644 pos1, pos2 = narrow(pos1, pos2, tFixes[4])\
13645 end\
13646\
13647 if pos1 and pos2 then\
13648 print(\"Ambiguous position\")\
13649 print(\"Could be \"..pos1.x..\",\"..pos1.y..\",\"..pos1.z..\" or \"..pos2.x..\",\"..pos2.y..\",\"..pos2.z )\
13650 return\
13651 end\
13652\
13653 return pos1\
13654end\
13655\
13656return GPS",
13657 [ "git.lua" ] = "local json = require('json')\
13658local Util = require('util')\
13659\
13660-- Limit queries to once per minecraft day\
13661-- TODO: will not work if time is stopped\
13662\
13663local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1'\
13664local FILE_URL = 'https://raw.githubusercontent.com/%s/%s/%s/%s'\
13665local git = { }\
13666\
13667if _G._GIT_API_KEY then\
13668 TREE_URL = TREE_URL .. '&access_token=' .. _G._GIT_API_KEY\
13669end\
13670\
13671local fs = _G.fs\
13672local os = _G.os\
13673\
13674if not _G.GIT then\
13675 _G.GIT = { }\
13676end\
13677\
13678function git.list(repository)\
13679 local t = Util.split(repository, '(.-)/')\
13680\
13681 local user = table.remove(t, 1)\
13682 local repo = table.remove(t, 1)\
13683 local branch = table.remove(t, 1) or 'master'\
13684 local path\
13685\
13686 if not Util.empty(t) then\
13687 path = table.concat(t, '/') .. '/'\
13688 end\
13689\
13690 local cacheKey = table.concat({ user, repo, branch }, '-')\
13691 local fname = fs.combine('.git', cacheKey)\
13692\
13693 local function getContents()\
13694 if fs.exists(fname) then\
13695 local contents = Util.readTable(fname)\
13696 if contents and contents.data == os.day() then\
13697 return contents.data\
13698 end\
13699 fs.delete(fname)\
13700 end\
13701 local dataUrl = string.format(TREE_URL, user, repo, branch)\
13702 local contents = Util.download(dataUrl)\
13703 if contents then\
13704 return json.decode(contents)\
13705 end\
13706 end\
13707\
13708 local data = getContents() or error('Invalid repository')\
13709\
13710 if data.message and data.message:find(\"API rate limit exceeded\") then\
13711 error(\"Out of API calls, try again later\")\
13712 end\
13713\
13714 if data.message and data.message == \"Not found\" then\
13715 error(\"Invalid repository\")\
13716 end\
13717\
13718 if not fs.exists(fname) then\
13719 Util.writeTable('.git/' .. cacheKey, { day = os.day(), data = data })\
13720 end\
13721\
13722 local list = { }\
13723 for _,v in pairs(data.tree) do\
13724 if v.type == \"blob\" then\
13725 v.path = v.path:gsub(\"%s\",\"%%20\")\
13726 if not path then\
13727 list[v.path] = {\
13728 url = string.format(FILE_URL, user, repo, branch, v.path),\
13729 size = v.size,\
13730 }\
13731 elseif Util.startsWith(v.path, path) then\
13732 local p = string.sub(v.path, #path)\
13733 list[p] = {\
13734 url = string.format(FILE_URL, user, repo, branch, path .. p),\
13735 size = v.size,\
13736 }\
13737 end\
13738 end\
13739 end\
13740\
13741 return list\
13742end\
13743\
13744return git",
13745 [ "sync.lua" ] = "local Sync = {\
13746 syncLocks = { }\
13747}\
13748\
13749local os = _G.os\
13750\
13751function Sync.sync(obj, fn)\
13752 local key = tostring(obj)\
13753 if Sync.syncLocks[key] then\
13754 local cos = tostring(coroutine.running())\
13755 table.insert(Sync.syncLocks[key], cos)\
13756 repeat\
13757 local _, co = os.pullEvent('sync_lock')\
13758 until co == cos\
13759 else\
13760 Sync.syncLocks[key] = { }\
13761 end\
13762 local s, m = pcall(fn)\
13763 local co = table.remove(Sync.syncLocks[key], 1)\
13764 if co then\
13765 os.queueEvent('sync_lock', co)\
13766 else\
13767 Sync.syncLocks[key] = nil\
13768 end\
13769 if not s then\
13770 error(m)\
13771 end\
13772end\
13773\
13774function Sync.lock(obj)\
13775 local key = tostring(obj)\
13776 if Sync.syncLocks[key] then\
13777 local cos = tostring(coroutine.running())\
13778 table.insert(Sync.syncLocks[key], cos)\
13779 repeat\
13780 local _, co = os.pullEvent('sync_lock')\
13781 until co == cos\
13782 else\
13783 Sync.syncLocks[key] = { }\
13784 end\
13785end\
13786\
13787function Sync.release(obj)\
13788 local key = tostring(obj)\
13789 if not Sync.syncLocks[key] then\
13790 error('Sync.release: Lock was not obtained', 2)\
13791 end\
13792 local co = table.remove(Sync.syncLocks[key], 1)\
13793 if co then\
13794 os.queueEvent('sync_lock', co)\
13795 else\
13796 Sync.syncLocks[key] = nil\
13797 end\
13798end\
13799\
13800function Sync.isLocked(obj)\
13801 local key = tostring(obj)\
13802 return not not Sync.syncLocks[key]\
13803end\
13804\
13805return Sync",
13806 [ "history.lua" ] = "local Util = require('util')\
13807\
13808local History = { }\
13809local History_mt = { __index = History }\
13810\
13811function History.load(filename, limit)\
13812\
13813 local self = setmetatable({\
13814 limit = limit,\
13815 filename = filename,\
13816 }, History_mt)\
13817\
13818 self.entries = Util.readLines(filename) or { }\
13819 self.pos = #self.entries + 1\
13820\
13821 return self\
13822end\
13823\
13824function History:add(line)\
13825 if line ~= self.entries[#self.entries] then\
13826 table.insert(self.entries, line)\
13827 if self.limit then\
13828 while #self.entries > self.limit do\
13829 table.remove(self.entries, 1)\
13830 end\
13831 end\
13832 Util.writeLines(self.filename, self.entries)\
13833 self.pos = #self.entries + 1\
13834 end\
13835end\
13836\
13837function History:reset()\
13838 self.pos = #self.entries + 1\
13839end\
13840\
13841function History:back()\
13842 if self.pos > 1 then\
13843 self.pos = self.pos - 1\
13844 return self.entries[self.pos]\
13845 end\
13846end\
13847\
13848function History:forward()\
13849 if self.pos <= #self.entries then\
13850 self.pos = self.pos + 1\
13851 return self.entries[self.pos]\
13852 end\
13853end\
13854\
13855return History",
13856 [ "peripheral.lua" ] = "local Event = require('event')\
13857local Socket = require('socket')\
13858local Util = require('util')\
13859\
13860local os = _G.os\
13861\
13862local Peripheral = Util.shallowCopy(_G.peripheral)\
13863\
13864function Peripheral.getList()\
13865 if _G.device then\
13866 return _G.device\
13867 end\
13868\
13869 local deviceList = { }\
13870 for _,side in pairs(Peripheral.getNames()) do\
13871 Peripheral.addDevice(deviceList, side)\
13872 end\
13873\
13874 return deviceList\
13875end\
13876\
13877function Peripheral.addDevice(deviceList, side)\
13878 local name = side\
13879 local ptype = Peripheral.getType(side)\
13880\
13881 if not ptype then\
13882 return\
13883 end\
13884\
13885 if ptype == 'modem' then\
13886 if not Peripheral.call(name, 'isWireless') then\
13887-- ptype = 'wireless_modem'\
13888-- else\
13889 ptype = 'wired_modem'\
13890 end\
13891 end\
13892\
13893 local sides = {\
13894 front = true,\
13895 back = true,\
13896 top = true,\
13897 bottom = true,\
13898 left = true,\
13899 right = true\
13900 }\
13901\
13902 if sides[name] then\
13903 local i = 1\
13904 local uniqueName = ptype\
13905 while deviceList[uniqueName] do\
13906 uniqueName = ptype .. '_' .. i\
13907 i = i + 1\
13908 end\
13909 name = uniqueName\
13910 end\
13911\
13912 -- this can randomly fail\
13913 if not deviceList[name] then\
13914 pcall(function()\
13915 deviceList[name] = Peripheral.wrap(side)\
13916 end)\
13917\
13918 if deviceList[name] then\
13919 Util.merge(deviceList[name], {\
13920 name = name,\
13921 type = ptype,\
13922 side = side,\
13923 })\
13924 end\
13925 end\
13926\
13927 return deviceList[name]\
13928end\
13929\
13930function Peripheral.getBySide(side)\
13931 return Util.find(Peripheral.getList(), 'side', side)\
13932end\
13933\
13934function Peripheral.getByType(typeName)\
13935 return Util.find(Peripheral.getList(), 'type', typeName)\
13936end\
13937\
13938function Peripheral.getByMethod(method)\
13939 for _,p in pairs(Peripheral.getList()) do\
13940 if p[method] then\
13941 return p\
13942 end\
13943 end\
13944end\
13945\
13946-- match any of the passed arguments\
13947function Peripheral.get(args)\
13948\
13949 if type(args) == 'string' then\
13950 args = { type = args }\
13951 end\
13952\
13953 if args.name then\
13954 return _G.device[args.name]\
13955 end\
13956\
13957 if args.type then\
13958 local p = Peripheral.getByType(args.type)\
13959 if p then\
13960 return p\
13961 end\
13962 end\
13963\
13964 if args.method then\
13965 local p = Peripheral.getByMethod(args.method)\
13966 if p then\
13967 return p\
13968 end\
13969 end\
13970\
13971 if args.side then\
13972 local p = Peripheral.getBySide(args.side)\
13973 if p then\
13974 return p\
13975 end\
13976 end\
13977end\
13978\
13979local function getProxy(pi)\
13980 local socket, msg = Socket.connect(pi.host, 189)\
13981\
13982 if not socket then\
13983 error(\"Timed out attaching peripheral: \" .. pi.uri .. '\\n' .. msg)\
13984 end\
13985\
13986 -- write the uri of the periperal we are requesting...\
13987 -- ie. type/monitor\
13988 socket:write(pi.path)\
13989 local proxy = socket:read(3)\
13990\
13991 if not proxy then\
13992 error(\"Timed out attaching peripheral: \" .. pi.uri)\
13993 end\
13994\
13995 if type(proxy) == 'string' then\
13996 error(proxy)\
13997 end\
13998\
13999 local methods = proxy.methods\
14000 proxy.methods = nil\
14001\
14002 for _,method in pairs(methods) do\
14003 proxy[method] = function(...)\
14004 socket:write({ fn = method, args = { ... } })\
14005 local resp = socket:read()\
14006 if not resp then\
14007 error(\"Timed out communicating with peripheral: \" .. pi.uri)\
14008 end\
14009 return table.unpack(resp)\
14010 end\
14011 end\
14012\
14013 if proxy.blit then\
14014 local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit',\
14015 'setTextColor', 'setTextColour', 'setBackgroundColor',\
14016 'setBackgroundColour', 'scroll', 'setCursorBlink', }\
14017 local queue = nil\
14018\
14019 for _,method in pairs(methods) do\
14020 proxy[method] = function(...)\
14021 if not queue then\
14022 queue = { }\
14023 Event.onTimeout(0, function()\
14024 if not socket:write({ fn = 'fastBlit', args = { queue } }) then\
14025 error(\"Timed out communicating with peripheral: \" .. pi.uri)\
14026 end\
14027 queue = nil\
14028 socket:read()\
14029 end)\
14030 end\
14031 if not socket.connected then\
14032 error(\"Timed out communicating with peripheral: \" .. pi.uri)\
14033 end\
14034\
14035 table.insert(queue, {\
14036 fn = method,\
14037 args = { ... },\
14038 })\
14039 end\
14040 end\
14041 end\
14042\
14043 if proxy.type == 'monitor' then\
14044 Event.addRoutine(function()\
14045 while true do\
14046 local data = socket:read()\
14047 if not data then\
14048 break\
14049 end\
14050 if data.fn and data.fn == 'event' then\
14051 os.queueEvent(table.unpack(data.data))\
14052 end\
14053 end\
14054 end)\
14055 end\
14056\
14057 return proxy\
14058end\
14059\
14060--[[\
14061 Parse a uri into it's components\
14062\
14063 Examples:\
14064 monitor = { name = 'monitor' }\
14065 side/top = { side = 'top' }\
14066 method/list = { method = 'list' }\
14067 12://name/monitor = { host = 12, name = 'monitor' }\
14068]]--\
14069local function parse(uri)\
14070 local pi = Util.split(uri:gsub('^%d*://', ''), '(.-)/')\
14071\
14072 if #pi == 1 then\
14073 pi = {\
14074 'name',\
14075 pi[1],\
14076 }\
14077 end\
14078\
14079 return {\
14080 host = uri:match('^(%d*)%:'), -- 12\
14081 uri = uri, -- 12://name/monitor\
14082 path = uri:gsub('^%d*://', ''), -- name/monitor\
14083 [ pi[1] ] = pi[2], -- name = 'monitor'\
14084 }\
14085end\
14086\
14087function Peripheral.lookup(uri)\
14088 local pi = parse(uri)\
14089\
14090 if pi.host and _G.device.wireless_modem then\
14091 return getProxy(pi)\
14092 end\
14093\
14094 return Peripheral.get(pi)\
14095end\
14096\
14097return Peripheral",
14098 [ "sha1.lua" ] = "local sha1 = {\
14099 _VERSION = \"sha.lua 0.5.0\",\
14100 _URL = \"https://github.com/kikito/sha.lua\",\
14101 _DESCRIPTION = [[\
14102 SHA-1 secure hash computation, and HMAC-SHA1 signature computation in Lua (5.1)\
14103 Based on code originally by Jeffrey Friedl (http://regex.info/blog/lua/sha1)\
14104 And modified by Eike Decker - (http://cube3d.de/uploads/Main/sha1.txt)\
14105 ]],\
14106 _LICENSE = [[\
14107 MIT LICENSE\
14108\
14109 Copyright (c) 2013 Enrique Garcia Cota + Eike Decker + Jeffrey Friedl\
14110\
14111 https://opensource.org/licenses/MIT\
14112 ]]\
14113}\
14114\
14115-----------------------------------------------------------------------------------\
14116\
14117-- loading this file (takes a while but grants a boost of factor 13)\
14118local PRELOAD_CACHE = false\
14119\
14120local BLOCK_SIZE = 64 -- 512 bits\
14121\
14122-- local storing of global functions (minor speedup)\
14123local floor,modf = math.floor,math.modf\
14124local char,format,rep = string.char,string.format,string.rep\
14125\
14126-- merge 4 bytes to an 32 bit word\
14127local function bytes_to_w32(a,b,c,d) return a*0x1000000+b*0x10000+c*0x100+d end\
14128-- split a 32 bit word into four 8 bit numbers\
14129local function w32_to_bytes(i)\
14130 return floor(i/0x1000000)%0x100,floor(i/0x10000)%0x100,floor(i/0x100)%0x100,i%0x100\
14131end\
14132\
14133-- shift the bits of a 32 bit word. Don't use negative values for \"bits\"\
14134local function w32_rot(bits,a)\
14135 local b2 = 2^(32-bits)\
14136 local a,b = modf(a/b2)\
14137 return a+b*b2*(2^(bits))\
14138end\
14139\
14140-- caching function for functions that accept 2 arguments, both of values between\
14141-- 0 and 255. The function to be cached is passed, all values are calculated\
14142-- during loading and a function is returned that returns the cached values (only)\
14143local function cache2arg(fn)\
14144 if not PRELOAD_CACHE then return fn end\
14145 local lut = {}\
14146 for i=0,0xffff do\
14147 local a,b = floor(i/0x100),i%0x100\
14148 lut[i] = fn(a,b)\
14149 end\
14150 return function(a,b)\
14151 return lut[a*0x100+b]\
14152 end\
14153end\
14154\
14155-- splits an 8-bit number into 8 bits, returning all 8 bits as booleans\
14156local function byte_to_bits(b)\
14157 local b = function(n)\
14158 local b = floor(b/n)\
14159 return b%2==1\
14160 end\
14161 return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128)\
14162end\
14163\
14164-- builds an 8bit number from 8 booleans\
14165local function bits_to_byte(a,b,c,d,e,f,g,h)\
14166 local function n(b,x) return b and x or 0 end\
14167 return n(a,1)+n(b,2)+n(c,4)+n(d,8)+n(e,16)+n(f,32)+n(g,64)+n(h,128)\
14168end\
14169\
14170-- bitwise \"and\" function for 2 8bit number\
14171local band = cache2arg (function(a,b)\
14172 local A,B,C,D,E,F,G,H = byte_to_bits(b)\
14173 local a,b,c,d,e,f,g,h = byte_to_bits(a)\
14174 return bits_to_byte(\
14175 A and a, B and b, C and c, D and d,\
14176 E and e, F and f, G and g, H and h)\
14177end)\
14178\
14179-- bitwise \"or\" function for 2 8bit numbers\
14180local bor = cache2arg(function(a,b)\
14181 local A,B,C,D,E,F,G,H = byte_to_bits(b)\
14182 local a,b,c,d,e,f,g,h = byte_to_bits(a)\
14183 return bits_to_byte(\
14184 A or a, B or b, C or c, D or d,\
14185 E or e, F or f, G or g, H or h)\
14186end)\
14187\
14188-- bitwise \"xor\" function for 2 8bit numbers\
14189local bxor = cache2arg(function(a,b)\
14190 local A,B,C,D,E,F,G,H = byte_to_bits(b)\
14191 local a,b,c,d,e,f,g,h = byte_to_bits(a)\
14192 return bits_to_byte(\
14193 A ~= a, B ~= b, C ~= c, D ~= d,\
14194 E ~= e, F ~= f, G ~= g, H ~= h)\
14195end)\
14196\
14197-- bitwise complement for one 8bit number\
14198local function bnot(x)\
14199 return 255-(x % 256)\
14200end\
14201\
14202-- creates a function to combine to 32bit numbers using an 8bit combination function\
14203local function w32_comb(fn)\
14204 return function(a,b)\
14205 local aa,ab,ac,ad = w32_to_bytes(a)\
14206 local ba,bb,bc,bd = w32_to_bytes(b)\
14207 return bytes_to_w32(fn(aa,ba),fn(ab,bb),fn(ac,bc),fn(ad,bd))\
14208 end\
14209end\
14210\
14211-- create functions for and, xor and or, all for 2 32bit numbers\
14212local w32_and = w32_comb(band)\
14213local w32_xor = w32_comb(bxor)\
14214local w32_or = w32_comb(bor)\
14215\
14216-- xor function that may receive a variable number of arguments\
14217local function w32_xor_n(a,...)\
14218 local aa,ab,ac,ad = w32_to_bytes(a)\
14219 for i=1,select('#',...) do\
14220 local ba,bb,bc,bd = w32_to_bytes(select(i,...))\
14221 aa,ab,ac,ad = bxor(aa,ba),bxor(ab,bb),bxor(ac,bc),bxor(ad,bd)\
14222 end\
14223 return bytes_to_w32(aa,ab,ac,ad)\
14224end\
14225\
14226-- combining 3 32bit numbers through binary \"or\" operation\
14227local function w32_or3(a,b,c)\
14228 local aa,ab,ac,ad = w32_to_bytes(a)\
14229 local ba,bb,bc,bd = w32_to_bytes(b)\
14230 local ca,cb,cc,cd = w32_to_bytes(c)\
14231 return bytes_to_w32(\
14232 bor(aa,bor(ba,ca)), bor(ab,bor(bb,cb)), bor(ac,bor(bc,cc)), bor(ad,bor(bd,cd))\
14233 )\
14234end\
14235\
14236-- binary complement for 32bit numbers\
14237local function w32_not(a)\
14238 return 4294967295-(a % 4294967296)\
14239end\
14240\
14241-- adding 2 32bit numbers, cutting off the remainder on 33th bit\
14242local function w32_add(a,b) return (a+b) % 4294967296 end\
14243\
14244-- adding n 32bit numbers, cutting off the remainder (again)\
14245local function w32_add_n(a,...)\
14246 for i=1,select('#',...) do\
14247 a = (a+select(i,...)) % 4294967296\
14248 end\
14249 return a\
14250end\
14251-- converting the number to a hexadecimal string\
14252local function w32_to_hexstring(w) return format(\"%08x\",w) end\
14253\
14254local function hex_to_binary(hex)\
14255 return hex:gsub('..', function(hexval)\
14256 return string.char(tonumber(hexval, 16))\
14257 end)\
14258end\
14259\
14260-- building the lookuptables ahead of time (instead of littering the source code\
14261-- with precalculated values)\
14262local xor_with_0x5c = {}\
14263local xor_with_0x36 = {}\
14264for i=0,0xff do\
14265 xor_with_0x5c[char(i)] = char(bxor(i,0x5c))\
14266 xor_with_0x36[char(i)] = char(bxor(i,0x36))\
14267end\
14268\
14269-----------------------------------------------------------------------------\
14270\
14271-- calculating the SHA1 for some text\
14272function sha1.sha1(msg)\
14273 local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0\
14274 local msg_len_in_bits = #msg * 8\
14275\
14276 local first_append = char(0x80) -- append a '1' bit plus seven '0' bits\
14277\
14278 local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length\
14279 local current_mod = non_zero_message_bytes % 64\
14280 local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or \"\"\
14281\
14282 -- now to append the length as a 64-bit number.\
14283 local B1, R1 = modf(msg_len_in_bits / 0x01000000)\
14284 local B2, R2 = modf( 0x01000000 * R1 / 0x00010000)\
14285 local B3, R3 = modf( 0x00010000 * R2 / 0x00000100)\
14286 local B4 = 0x00000100 * R3\
14287\
14288 local L64 = char( 0) .. char( 0) .. char( 0) .. char( 0) -- high 32 bits\
14289 .. char(B1) .. char(B2) .. char(B3) .. char(B4) -- low 32 bits\
14290\
14291 msg = msg .. first_append .. second_append .. L64\
14292\
14293 assert(#msg % 64 == 0)\
14294\
14295 local chunks = #msg / 64\
14296\
14297 local W = { }\
14298 local start, A, B, C, D, E, f, K, TEMP\
14299 local chunk = 0\
14300\
14301 while chunk < chunks do\
14302 --\
14303 -- break chunk up into W[0] through W[15]\
14304 --\
14305 start,chunk = chunk * 64 + 1,chunk + 1\
14306\
14307 for t = 0, 15 do\
14308 W[t] = bytes_to_w32(msg:byte(start, start + 3))\
14309 start = start + 4\
14310 end\
14311\
14312 --\
14313 -- build W[16] through W[79]\
14314 --\
14315 for t = 16, 79 do\
14316 -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16).\
14317 W[t] = w32_rot(1, w32_xor_n(W[t-3], W[t-8], W[t-14], W[t-16]))\
14318 end\
14319\
14320 A,B,C,D,E = H0,H1,H2,H3,H4\
14321\
14322 for t = 0, 79 do\
14323 if t <= 19 then\
14324 -- (B AND C) OR ((NOT B) AND D)\
14325 f = w32_or(w32_and(B, C), w32_and(w32_not(B), D))\
14326 K = 0x5A827999\
14327 elseif t <= 39 then\
14328 -- B XOR C XOR D\
14329 f = w32_xor_n(B, C, D)\
14330 K = 0x6ED9EBA1\
14331 elseif t <= 59 then\
14332 -- (B AND C) OR (B AND D) OR (C AND D\
14333 f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D))\
14334 K = 0x8F1BBCDC\
14335 else\
14336 -- B XOR C XOR D\
14337 f = w32_xor_n(B, C, D)\
14338 K = 0xCA62C1D6\
14339 end\
14340\
14341 -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt;\
14342 A,B,C,D,E = w32_add_n(w32_rot(5, A), f, E, W[t], K),\
14343 A, w32_rot(30, B), C, D\
14344 end\
14345 -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E.\
14346 H0,H1,H2,H3,H4 = w32_add(H0, A),w32_add(H1, B),w32_add(H2, C),w32_add(H3, D),w32_add(H4, E)\
14347 end\
14348 local f = w32_to_hexstring\
14349 return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4)\
14350end\
14351\
14352\
14353function sha1.binary(msg)\
14354 return hex_to_binary(sha1.sha1(msg))\
14355end\
14356\
14357function sha1.hmac(key, text)\
14358 assert(type(key) == 'string', \"key passed to sha1.hmac should be a string\")\
14359 assert(type(text) == 'string', \"text passed to sha1.hmac should be a string\")\
14360\
14361 if #key > BLOCK_SIZE then\
14362 key = sha1.binary(key)\
14363 end\
14364\
14365 local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), BLOCK_SIZE - #key)\
14366 local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), BLOCK_SIZE - #key)\
14367\
14368 return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text))\
14369end\
14370\
14371function sha1.hmac_binary(key, text)\
14372 return hex_to_binary(sha1.hmac(key, text))\
14373end\
14374\
14375setmetatable(sha1, {__call = function(_,msg) return sha1.sha1(msg) end })\
14376\
14377return sha1",
14378 [ "terminal.lua" ] = "local colors = _G.colors\
14379local term = _G.term\
14380local _gsub = string.gsub\
14381local _rep = string.rep\
14382local _sub = string.sub\
14383\
14384local Terminal = { }\
14385\
14386-- add scrolling functions to a window\
14387function Terminal.scrollable(win, maxScroll)\
14388 local lines = { }\
14389 local scrollPos = 0\
14390 local oblit, oreposition = win.blit, win.reposition\
14391\
14392 local palette = { }\
14393 for n = 1, 16 do\
14394 palette[2 ^ (n - 1)] = _sub(\"0123456789abcdef\", n, n)\
14395 end\
14396\
14397 maxScroll = maxScroll or 100\
14398\
14399 -- should only do if window is visible...\
14400 local function redraw()\
14401 local _, h = win.getSize()\
14402 local x, y = win.getCursorPos()\
14403 for i = 1, h do\
14404 local line = lines[i + scrollPos]\
14405 if line and line.dirty then\
14406 win.setCursorPos(1, i)\
14407 oblit(line.text, line.fg, line.bg)\
14408 line.dirty = false\
14409 end\
14410 end\
14411 win.setCursorPos(x, y)\
14412 end\
14413\
14414 local function scrollTo(p, forceRedraw)\
14415 local _, h = win.getSize()\
14416 local ms = #lines - h -- max scroll\
14417 p = math.min(math.max(p, 0), ms) -- normalize\
14418\
14419 if p ~= scrollPos or forceRedraw then\
14420 scrollPos = p\
14421 for _, line in pairs(lines) do\
14422 line.dirty = true\
14423 end\
14424 end\
14425 end\
14426\
14427 function win.write(text)\
14428 local _, h = win.getSize()\
14429\
14430 text = tostring(text) or ''\
14431 scrollTo(#lines - h)\
14432 win.blit(text,\
14433 _rep(palette[win.getTextColor()], #text),\
14434 _rep(palette[win.getBackgroundColor()], #text))\
14435 local x, y = win.getCursorPos()\
14436 win.setCursorPos(x + #text, y)\
14437 end\
14438\
14439 function win.clearLine()\
14440 local w, h = win.getSize()\
14441 local _, y = win.getCursorPos()\
14442\
14443 scrollTo(#lines - h)\
14444 lines[y + scrollPos] = {\
14445 text = _rep(' ', w),\
14446 fg = _rep(palette[win.getTextColor()], w),\
14447 bg = _rep(palette[win.getBackgroundColor()], w),\
14448 dirty = true,\
14449 }\
14450 redraw()\
14451 end\
14452\
14453 function win.blit(text, fg, bg)\
14454 local x, y = win.getCursorPos()\
14455 local w, h = win.getSize()\
14456\
14457 if y > 0 and y <= h and x <= w then\
14458 local width = #text\
14459\
14460 -- fix ffs\
14461 if x < 1 then\
14462 text = _sub(text, 2 - x)\
14463 if bg then\
14464 bg = _sub(bg, 2 - x)\
14465 end\
14466 if bg then\
14467 fg = _sub(fg, 2 - x)\
14468 end\
14469 width = width + x - 1\
14470 x = 1\
14471 end\
14472\
14473 if x + width - 1 > w then\
14474 text = _sub(text, 1, w - x + 1)\
14475 if bg then\
14476 bg = _sub(bg, 1, w - x + 1)\
14477 end\
14478 if bg then\
14479 fg = _sub(fg, 1, w - x + 1)\
14480 end\
14481 width = #text\
14482 end\
14483\
14484 if width > 0 then\
14485 local function replace(sstr, pos, rstr)\
14486 if pos == 1 and width == w then\
14487 return rstr\
14488 elseif pos == 1 then\
14489 return rstr .. _sub(sstr, pos+width)\
14490 elseif pos + width > w then\
14491 return _sub(sstr, 1, pos-1) .. rstr\
14492 end\
14493 return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width)\
14494 end\
14495\
14496 local line = lines[y + scrollPos]\
14497 line.dirty = true\
14498 line.text = replace(line.text, x, text, width)\
14499 if fg then\
14500 line.fg = replace(line.fg, x, fg, width)\
14501 end\
14502 if bg then\
14503 line.bg = replace(line.bg, x, bg, width)\
14504 end\
14505 end\
14506 end\
14507 redraw()\
14508 end\
14509\
14510 function win.clear()\
14511 local w, h = win.getSize()\
14512\
14513 local text = _rep(' ', w)\
14514 local fg = _rep(palette[win.getTextColor()], w)\
14515 local bg = _rep(palette[win.getBackgroundColor()], w)\
14516 lines = { }\
14517 for y = 1, h do\
14518 lines[y] = {\
14519 dirty = true,\
14520 text = text,\
14521 fg = fg,\
14522 bg = bg,\
14523 }\
14524 end\
14525 scrollPos = 0\
14526 redraw()\
14527 end\
14528\
14529 -- doesn't support negative scrolling...\
14530 function win.scroll(n)\
14531 local w = win.getSize()\
14532\
14533 for _ = 1, n do\
14534 lines[#lines + 1] = {\
14535 text = _rep(' ', w),\
14536 fg = _rep(palette[win.getTextColor()], w),\
14537 bg = _rep(palette[win.getBackgroundColor()], w),\
14538 }\
14539 end\
14540\
14541 while #lines > maxScroll do\
14542 table.remove(lines, 1)\
14543 end\
14544\
14545 scrollTo(maxScroll, true)\
14546 redraw()\
14547 end\
14548\
14549 function win.scrollUp()\
14550 scrollTo(scrollPos - 1)\
14551 redraw()\
14552 end\
14553\
14554 function win.scrollDown()\
14555 scrollTo(scrollPos + 1)\
14556 redraw()\
14557 end\
14558\
14559 function win.reposition(x, y, nw, nh)\
14560 local w, h = win.getSize()\
14561 local D = (nh or h) - h\
14562\
14563 if D > 0 then\
14564 for _ = 1, D do\
14565 lines[#lines + 1] = {\
14566 text = _rep(' ', w),\
14567 fg = _rep(palette[win.getTextColor()], w),\
14568 bg = _rep(palette[win.getBackgroundColor()], w),\
14569 }\
14570 end\
14571 elseif D < 0 then\
14572 for _ = D, -1 do\
14573 lines[#lines] = nil\
14574 end\
14575 end\
14576 return oreposition(x, y, nw, nh)\
14577 end\
14578\
14579 win.clear()\
14580end\
14581\
14582-- get windows contents\
14583function Terminal.getContents(win, parent)\
14584 local oblit, oscp = parent.blit, parent.setCursorPos\
14585 local lines = { }\
14586\
14587 parent.blit = function(text, fg, bg)\
14588 lines[#lines + 1] = {\
14589 text = text,\
14590 fg = fg,\
14591 bg = bg,\
14592 }\
14593 end\
14594 parent.setCursorPos = function() end\
14595\
14596 win.setVisible(true)\
14597 win.redraw()\
14598\
14599 parent.blit = oblit\
14600 parent.setCursorPos = oscp\
14601\
14602 return lines\
14603end\
14604\
14605function Terminal.toGrayscale(ct)\
14606 local scolors = {\
14607 [ colors.white ] = colors.white,\
14608 [ colors.orange ] = colors.lightGray,\
14609 [ colors.magenta ] = colors.lightGray,\
14610 [ colors.lightBlue ] = colors.lightGray,\
14611 [ colors.yellow ] = colors.lightGray,\
14612 [ colors.lime ] = colors.lightGray,\
14613 [ colors.pink ] = colors.lightGray,\
14614 [ colors.gray ] = colors.gray,\
14615 [ colors.lightGray ] = colors.lightGray,\
14616 [ colors.cyan ] = colors.lightGray,\
14617 [ colors.purple ] = colors.gray,\
14618 [ colors.blue ] = colors.gray,\
14619 [ colors.brown ] = colors.gray,\
14620 [ colors.green ] = colors.lightGray,\
14621 [ colors.red ] = colors.gray,\
14622 [ colors.black ] = colors.black,\
14623 }\
14624\
14625 local methods = { 'setBackgroundColor', 'setBackgroundColour',\
14626 'setTextColor', 'setTextColour' }\
14627 for _,v in pairs(methods) do\
14628 local fn = ct[v]\
14629 ct[v] = function(c)\
14630 fn(scolors[c])\
14631 end\
14632 end\
14633\
14634 local bcolors = {\
14635 [ '1' ] = '8',\
14636 [ '2' ] = '8',\
14637 [ '3' ] = '8',\
14638 [ '4' ] = '8',\
14639 [ '5' ] = '8',\
14640 [ '6' ] = '8',\
14641 [ '9' ] = '8',\
14642 [ 'a' ] = '7',\
14643 [ 'b' ] = '7',\
14644 [ 'c' ] = '7',\
14645 [ 'd' ] = '8',\
14646 [ 'e' ] = '7',\
14647 }\
14648\
14649 local function translate(s)\
14650 if s then\
14651 s = _gsub(s, \"%w\", bcolors)\
14652 end\
14653 return s\
14654 end\
14655\
14656 local fn = ct.blit\
14657 ct.blit = function(text, fg, bg)\
14658 fn(text, translate(fg), translate(bg))\
14659 end\
14660end\
14661\
14662function Terminal.getNullTerm(ct)\
14663 local nt = Terminal.copy(ct)\
14664\
14665 local methods = { 'blit', 'clear', 'clearLine', 'scroll',\
14666 'setCursorBlink', 'setCursorPos', 'write' }\
14667 for _,v in pairs(methods) do\
14668 nt[v] = function() end\
14669 end\
14670\
14671 return nt\
14672end\
14673\
14674function Terminal.copy(it, ot)\
14675 ot = ot or { }\
14676 for k,v in pairs(it) do\
14677 if type(v) == 'function' then\
14678 ot[k] = v\
14679 end\
14680 end\
14681 return ot\
14682end\
14683\
14684function Terminal.mirror(ct, dt)\
14685 for k,f in pairs(ct) do\
14686 ct[k] = function(...)\
14687 local ret = { f(...) }\
14688 if dt[k] then\
14689 dt[k](...)\
14690 end\
14691 return table.unpack(ret)\
14692 end\
14693 end\
14694end\
14695\
14696function Terminal.readPassword(prompt)\
14697 if prompt then\
14698 term.write(prompt)\
14699 end\
14700 local fn = term.current().write\
14701 term.current().write = function() end\
14702 local s\
14703 pcall(function() s = _G.read(prompt) end)\
14704 term.current().write = fn\
14705\
14706 if s == '' then\
14707 return\
14708 end\
14709 return s\
14710end\
14711\
14712return Terminal",
14713 [ "sound.lua" ] = "local peripheral = _G.peripheral\
14714\
14715local Sound = {\
14716 _volume = 1,\
14717}\
14718\
14719function Sound.play(sound, vol)\
14720 local speaker = peripheral.find('speaker')\
14721 if speaker then\
14722 speaker.playSound('minecraft:' .. sound, vol or Sound._volume)\
14723 end\
14724end\
14725\
14726function Sound.setVolume(volume)\
14727 Sound._volume = math.max(0, math.min(volume, 1))\
14728end\
14729\
14730return Sound",
14731 [ "security.lua" ] = "local Config = require('config')\
14732\
14733local config = { }\
14734\
14735local Security = { }\
14736\
14737function Security.verifyPassword(password)\
14738 Config.load('os', config)\
14739 return config.password and password == config.password\
14740end\
14741\
14742function Security.hasPassword()\
14743 return not not config.password\
14744end\
14745\
14746function Security.getSecretKey()\
14747 Config.load('os', config)\
14748 if not config.secretKey then\
14749 config.secretKey = math.random(100000, 999999)\
14750 Config.update('os', config)\
14751 end\
14752 return config.secretKey\
14753end\
14754\
14755function Security.getPublicKey()\
14756\
14757 local exchange = {\
14758 base = 11,\
14759 primeMod = 625210769\
14760 }\
14761\
14762 local function modexp(base, exponent, modulo)\
14763 local remainder = base\
14764\
14765 for _ = 1, exponent-1 do\
14766 remainder = remainder * remainder\
14767 if remainder >= modulo then\
14768 remainder = remainder % modulo\
14769 end\
14770 end\
14771\
14772 return remainder\
14773 end\
14774\
14775 local secretKey = Security.getSecretKey()\
14776 return modexp(exchange.base, secretKey, exchange.primeMod)\
14777end\
14778\
14779function Security.updatePassword(password)\
14780 Config.load('os', config)\
14781 config.password = password\
14782 Config.update('os', config)\
14783end\
14784\
14785function Security.getPassword()\
14786 Config.load('os', config)\
14787 return config.password\
14788end\
14789\
14790return Security",
14791 [ "rttp.lua" ] = "local device = _G.device\
14792local os = _G.os\
14793\
14794local rttp = { }\
14795local computerId = os.getComputerID()\
14796\
14797local function parse(url, default)\
14798 -- initialize default parameters\
14799 local parsed = {}\
14800 local authority\
14801\
14802 for i,v in pairs(default or parsed) do parsed[i] = v end\
14803 -- remove whitespace\
14804 -- url = string.gsub(url, \"%s\", \"\")\
14805 -- Decode unreserved characters\
14806 url = string.gsub(url, \"%%(%x%x)\", function(hex)\
14807 local char = string.char(tonumber(hex, 16))\
14808 if string.match(char, \"[a-zA-Z0-9._~-]\") then\
14809 return char\
14810 end\
14811 -- Hex encodings that are not unreserved must be preserved.\
14812 return nil\
14813 end)\
14814 -- get fragment\
14815 url = string.gsub(url, \"#(.*)$\", function(f)\
14816 parsed.fragment = f\
14817 return \"\"\
14818 end)\
14819 -- get scheme. Lower-case according to RFC 3986 section 3.1.\
14820 url = string.gsub(url, \"^(%w[%w.+-]*):\",\
14821 function(s) parsed.scheme = string.lower(s); return \"\" end)\
14822 -- get authority\
14823 url = string.gsub(url, \"^//([^/]*)\", function(n)\
14824 authority = n\
14825 return \"\"\
14826 end)\
14827 -- get query stringing\
14828 url = string.gsub(url, \"%?(.*)\", function(q)\
14829 parsed.query = q\
14830 return \"\"\
14831 end)\
14832 -- get params\
14833 url = string.gsub(url, \"%;(.*)\", function(p)\
14834 parsed.params = p\
14835 return \"\"\
14836 end)\
14837\
14838 -- path is whatever was left\
14839 parsed.path = url\
14840\
14841 -- Represents host:port, port = nil if not used.\
14842 if authority then\
14843 authority = string.gsub(authority, \":(%d+)$\",\
14844 function(p) parsed.port = tonumber(p); return \"\" end)\
14845 if authority ~= \"\" then\
14846 parsed.host = authority\
14847 end\
14848 end\
14849 return parsed\
14850end\
14851\
14852function rttp.get(url)\
14853 local modem = device.wireless_modem or error('Modem not found')\
14854 local parsed = parse(url, { port = 80 })\
14855\
14856 parsed.host = tonumber(parsed.host) or error('Invalid url')\
14857\
14858 for i = 16384, 32767 do\
14859 if not modem.isOpen(i) then\
14860 modem.open(i)\
14861 local path = parsed.query and parsed.path .. '?' .. parsed.query or parsed.path\
14862\
14863 modem.transmit(parsed.port, parsed.host, {\
14864 method = 'GET',\
14865 replyAddress = computerId,\
14866 replyPort = i,\
14867 path = path,\
14868 })\
14869 local timerId = os.startTimer(3)\
14870 repeat\
14871 local event, id, dport, dhost, response = os.pullEvent()\
14872 if event == 'modem_message' and\
14873 dport == i and\
14874 dhost == computerId and\
14875 type(response) == 'table' then\
14876 modem.close(i)\
14877 return true, response\
14878 end\
14879 until event == 'timer' and id == timerId\
14880 return false, 'timeout'\
14881 end\
14882 end\
14883end\
14884\
14885return rttp",
14886 turtle = {
14887 [ "pathfind.lua" ] = "local Grid = require('jumper.grid')\
14888local Pathfinder = require('jumper.pathfinder')\
14889local Point = require('point')\
14890local Util = require('util')\
14891\
14892local turtle = _G.turtle\
14893\
14894local function addBlock(grid, b, dim)\
14895 if Point.inBox(b, dim) then\
14896 local node = grid:getNodeAt(b.x, b.y, b.z)\
14897 if node then\
14898 node.walkable = 1\
14899 end\
14900 end\
14901end\
14902\
14903-- map shrinks/grows depending upon blocks encountered\
14904-- the map will encompass any blocks encountered, the turtle position, and the destination\
14905local function mapDimensions(dest, blocks, boundingBox, dests)\
14906 local box = Point.makeBox(turtle.point, turtle.point)\
14907\
14908 Point.expandBox(box, dest)\
14909\
14910 for _,d in pairs(dests) do\
14911 Point.expandBox(box, d)\
14912 end\
14913\
14914 for _,b in pairs(blocks) do\
14915 Point.expandBox(box, b)\
14916 end\
14917\
14918 -- expand one block out in all directions\
14919 if boundingBox then\
14920 box.x = math.max(box.x - 1, boundingBox.x)\
14921 box.z = math.max(box.z - 1, boundingBox.z)\
14922 box.y = math.max(box.y - 1, boundingBox.y)\
14923 box.ex = math.min(box.ex + 1, boundingBox.ex)\
14924 box.ez = math.min(box.ez + 1, boundingBox.ez)\
14925 box.ey = math.min(box.ey + 1, boundingBox.ey)\
14926 else\
14927 box.x = box.x - 1\
14928 box.z = box.z - 1\
14929 box.y = box.y - 1\
14930 box.ex = box.ex + 1\
14931 box.ez = box.ez + 1\
14932 box.ey = box.ey + 1\
14933 end\
14934\
14935 return box\
14936end\
14937\
14938local function nodeToPoint(node)\
14939 return { x = node.x, y = node.y, z = node.z, heading = node.heading }\
14940end\
14941\
14942local function heuristic(n, node)\
14943 return Point.calculateMoves(node, n)\
14944-- { x = node.x, y = node.y, z = node.z, heading = node.heading },\
14945-- { x = n.x, y = n.y, z = n.z, heading = n.heading })\
14946end\
14947\
14948local function dimsAreEqual(d1, d2)\
14949 return d1.ex == d2.ex and\
14950 d1.ey == d2.ey and\
14951 d1.ez == d2.ez and\
14952 d1.x == d2.x and\
14953 d1.y == d2.y and\
14954 d1.z == d2.z\
14955end\
14956\
14957-- turtle sensor returns blocks in relation to the world - not turtle orientation\
14958-- so cannot figure out block location unless we know our orientation in the world\
14959-- really kinda dumb since it returns the coordinates as offsets of our location\
14960-- instead of true coordinates\
14961local function addSensorBlocks(blocks, sblocks)\
14962 for _,b in pairs(sblocks) do\
14963 if b.type ~= 'AIR' then\
14964 local pt = { x = turtle.point.x, y = turtle.point.y + b.y, z = turtle.point.z }\
14965 pt.x = pt.x - b.x\
14966 pt.z = pt.z - b.z -- this will only work if we were originally facing west\
14967 local found = false\
14968 for _,ob in pairs(blocks) do\
14969 if pt.x == ob.x and pt.y == ob.y and pt.z == ob.z then\
14970 found = true\
14971 break\
14972 end\
14973 end\
14974 if not found then\
14975 table.insert(blocks, pt)\
14976 end\
14977 end\
14978 end\
14979end\
14980\
14981local function selectDestination(pts, box, grid)\
14982 while #pts > 0 do\
14983 local pt = Point.closest(turtle.point, pts)\
14984 if box and not Point.inBox(pt, box) then\
14985 Util.removeByValue(pts, pt)\
14986 else\
14987 if grid:isWalkableAt(pt.x, pt.y, pt.z) then\
14988 return pt\
14989 end\
14990 Util.removeByValue(pts, pt)\
14991 end\
14992 end\
14993end\
14994\
14995local function pathTo(dest, options)\
14996 local blocks = options.blocks or turtle.getState().blocks or { }\
14997 local dests = options.dest or { dest } -- support alternative destinations\
14998 local box = options.box or turtle.getState().box\
14999 local lastDim\
15000 local grid\
15001\
15002 if box then\
15003 box = Point.normalizeBox(box)\
15004 end\
15005\
15006 -- Creates a pathfinder object\
15007 local finder = Pathfinder(heuristic)\
15008\
15009 while turtle.point.x ~= dest.x or turtle.point.z ~= dest.z or turtle.point.y ~= dest.y do\
15010\
15011 -- map expands as we encounter obstacles\
15012 local dim = mapDimensions(dest, blocks, box, dests)\
15013\
15014 -- reuse map if possible\
15015 if not lastDim or not dimsAreEqual(dim, lastDim) then\
15016 -- Creates a grid object\
15017 grid = Grid(dim)\
15018 finder:setGrid(grid)\
15019\
15020 lastDim = dim\
15021 end\
15022 for _,b in pairs(blocks) do\
15023 addBlock(grid, b, dim)\
15024 end\
15025\
15026 dest = selectDestination(dests, box, grid)\
15027 if not dest then\
15028 return false, 'failed to reach destination'\
15029 end\
15030 if turtle.point.x == dest.x and turtle.point.z == dest.z and turtle.point.y == dest.y then\
15031 break\
15032 end\
15033\
15034 -- Define start and goal locations coordinates\
15035 local startPt = turtle.point\
15036\
15037 -- Calculates the path, and its length\
15038 local path = finder:getPath(\
15039 startPt.x, startPt.y, startPt.z, turtle.point.heading,\
15040 dest.x, dest.y, dest.z, dest.heading)\
15041\
15042 if not path then\
15043 Util.removeByValue(dests, dest)\
15044 else\
15045 path:filter()\
15046\
15047 for node in path:nodes() do\
15048 local pt = nodeToPoint(node)\
15049\
15050 if turtle.isAborted() then\
15051 return false, 'aborted'\
15052 end\
15053\
15054--if this is the next to last node\
15055--and we are traveling up or down, then the\
15056--heading for this node should be the heading of the last node\
15057--or, maybe..\
15058--if last node is up or down (or either?)\
15059\
15060 -- use single turn method so the turtle doesn't turn around\
15061 -- when encountering obstacles\
15062 -- if not turtle.gotoSingleTurn(pt.x, pt.y, pt.z, pt.heading) then\
15063 if not turtle.goto(pt) then\
15064 local bpt = Point.nearestTo(turtle.point, pt)\
15065\
15066 table.insert(blocks, bpt)\
15067 -- really need to check if the block we ran into was a turtle.\
15068 -- if so, this block should be temporary (1-2 secs)\
15069\
15070 --local side = turtle.getSide(turtle.point, pt)\
15071 --if turtle.isTurtleAtSide(side) then\
15072 -- pt.timestamp = os.clock() + ?\
15073 --end\
15074 -- if dim has not changed, then need to update grid with\
15075 -- walkable = nil (after time has elapsed)\
15076\
15077 --if device.turtlesensorenvironment then\
15078 -- addSensorBlocks(blocks, device.turtlesensorenvironment.sonicScan())\
15079 --end\
15080 break\
15081 end\
15082 end\
15083 end\
15084 end\
15085\
15086 if dest.heading then\
15087 turtle.setHeading(dest.heading)\
15088 end\
15089 return dest\
15090end\
15091\
15092return {\
15093 pathfind = function(dest, options)\
15094 options = options or { }\
15095 --if not options.blocks and turtle.gotoPoint(dest) then\
15096 -- return dest\
15097 --end\
15098 return pathTo(dest, options)\
15099 end,\
15100\
15101 -- set a global bounding box\
15102 -- box can be overridden by passing box in pathfind options\
15103 setBox = function(box)\
15104 turtle.getState().box = box\
15105 end,\
15106\
15107 setBlocks = function(blocks)\
15108 turtle.getState().blocks = blocks\
15109 end,\
15110\
15111 addBlock = function(block)\
15112 if turtle.getState().blocks then\
15113 table.insert(turtle.getState().blocks, block)\
15114 end\
15115 end,\
15116\
15117 reset = function()\
15118 turtle.getState().box = nil\
15119 turtle.getState().blocks = nil\
15120 end,\
15121}",
15122 },
15123 [ "event.lua" ] = "local os = _G.os\
15124local table = _G.table\
15125\
15126local Event = {\
15127 uid = 1, -- unique id for handlers\
15128 routines = { }, -- coroutines\
15129 types = { }, -- event handlers\
15130 timers = { }, -- named timers\
15131 terminate = false,\
15132 free = { },\
15133}\
15134\
15135-- Use a pool of coroutines for event handlers\
15136local function createCoroutine(h)\
15137 local co = table.remove(Event.free)\
15138 if not co then\
15139 co = coroutine.create(function(_, ...)\
15140 local args = { ... }\
15141 while true do\
15142 h.fn(table.unpack(args))\
15143 h.co = nil\
15144 table.insert(Event.free, co)\
15145 args = { coroutine.yield() }\
15146 h = table.remove(args, 1)\
15147 h.co = co\
15148 end\
15149 end)\
15150 end\
15151 h.primeCo = true -- TODO: fix...\
15152 return co\
15153end\
15154\
15155local Routine = { }\
15156\
15157function Routine:isDead()\
15158 if not self.co then\
15159 return true\
15160 end\
15161 return coroutine.status(self.co) == 'dead'\
15162end\
15163\
15164function Routine:terminate()\
15165 if self.co then\
15166 self:resume('terminate')\
15167 end\
15168end\
15169\
15170function Routine:resume(event, ...)\
15171 if not self.co then\
15172 error('Cannot resume a dead routine')\
15173 end\
15174\
15175 if not self.filter or self.filter == event or event == \"terminate\" then\
15176 local s, m\
15177 if self.primeCo then\
15178 -- Only need self passed when using a coroutine from the pool\
15179 s, m = coroutine.resume(self.co, self, event, ...)\
15180 self.primeCo = nil\
15181 else\
15182 s, m = coroutine.resume(self.co, event, ...)\
15183 end\
15184 if self:isDead() then\
15185 self.co = nil\
15186 self.filter = nil\
15187 Event.routines[self.uid] = nil\
15188 else\
15189 self.filter = m\
15190 end\
15191\
15192 if not s and event ~= 'terminate' then\
15193 error('\\n' .. (m or 'Error processing event'))\
15194 end\
15195\
15196 return s, m\
15197 end\
15198\
15199 return true, self.filter\
15200end\
15201\
15202local function nextUID()\
15203 Event.uid = Event.uid + 1\
15204 return Event.uid - 1\
15205end\
15206\
15207function Event.on(events, fn)\
15208 events = type(events) == 'table' and events or { events }\
15209\
15210 local handler = setmetatable({\
15211 uid = nextUID(),\
15212 event = events,\
15213 fn = fn,\
15214 }, { __index = Routine })\
15215\
15216 for _,event in pairs(events) do\
15217 local handlers = Event.types[event]\
15218 if not handlers then\
15219 handlers = { }\
15220 Event.types[event] = handlers\
15221 end\
15222\
15223 handlers[handler.uid] = handler\
15224 end\
15225\
15226 return handler\
15227end\
15228\
15229function Event.off(h)\
15230 if h and h.event then\
15231 for _,event in pairs(h.event) do\
15232 local handler = Event.types[event][h.uid]\
15233 if handler then\
15234 handler:terminate()\
15235 end\
15236 Event.types[event][h.uid] = nil\
15237 end\
15238 elseif h and h.co then\
15239 h:terminate()\
15240 end\
15241end\
15242\
15243local function addTimer(interval, recurring, fn)\
15244 local timerId = os.startTimer(interval)\
15245 local handler\
15246\
15247 handler = Event.on('timer', function(t, id)\
15248 if timerId == id then\
15249 fn(t, id)\
15250 if recurring then\
15251 timerId = os.startTimer(interval)\
15252 else\
15253 Event.off(handler)\
15254 end\
15255 end\
15256 end)\
15257\
15258 return handler\
15259end\
15260\
15261function Event.onInterval(interval, fn)\
15262 return Event.addRoutine(function()\
15263 while true do\
15264 os.sleep(interval)\
15265 fn()\
15266 end\
15267 end)\
15268end\
15269\
15270function Event.onTimeout(timeout, fn)\
15271 return addTimer(timeout, false, fn)\
15272end\
15273\
15274function Event.addNamedTimer(name, interval, recurring, fn)\
15275 Event.cancelNamedTimer(name)\
15276 Event.timers[name] = addTimer(interval, recurring, fn)\
15277end\
15278\
15279function Event.cancelNamedTimer(name)\
15280 local timer = Event.timers[name]\
15281 if timer then\
15282 Event.off(timer)\
15283 end\
15284end\
15285\
15286function Event.waitForEvent(event, timeout)\
15287 local timerId = os.startTimer(timeout)\
15288 repeat\
15289 local e = { os.pullEvent() }\
15290 if e[1] == event then\
15291 return table.unpack(e)\
15292 end\
15293 until e[1] == 'timer' and e[2] == timerId\
15294end\
15295\
15296-- Set a handler for the terminate event. Within the function, return\
15297-- true or false to indicate whether the event should be propagated to\
15298-- all sub-threads\
15299function Event.onTerminate(fn)\
15300 Event.termFn = fn\
15301end\
15302\
15303function Event.termFn()\
15304 Event.terminate = true\
15305 return true -- propagate\
15306end\
15307\
15308function Event.addRoutine(fn)\
15309 local r = setmetatable({\
15310 co = coroutine.create(fn),\
15311 uid = nextUID()\
15312 }, { __index = Routine })\
15313\
15314 Event.routines[r.uid] = r\
15315 r:resume()\
15316\
15317 return r\
15318end\
15319\
15320function Event.pullEvents(...)\
15321 for _, fn in ipairs({ ... }) do\
15322 Event.addRoutine(fn)\
15323 end\
15324\
15325 repeat\
15326 Event.pullEvent()\
15327 until Event.terminate\
15328\
15329 Event.terminate = false\
15330end\
15331\
15332function Event.exitPullEvents()\
15333 Event.terminate = true\
15334 os.sleep(0)\
15335end\
15336\
15337local function processHandlers(event)\
15338 local handlers = Event.types[event]\
15339 if handlers then\
15340 for _,h in pairs(handlers) do\
15341 if not h.co then\
15342 -- callbacks are single threaded (only 1 co per handler)\
15343 h.co = createCoroutine(h)\
15344 Event.routines[h.uid] = h\
15345 end\
15346 end\
15347 end\
15348end\
15349\
15350local function tokeys(t)\
15351 local keys = { }\
15352 for k in pairs(t) do\
15353 keys[#keys+1] = k\
15354 end\
15355 return keys\
15356end\
15357\
15358local function processRoutines(...)\
15359 local keys = tokeys(Event.routines)\
15360 for _,key in ipairs(keys) do\
15361 local r = Event.routines[key]\
15362 if r then\
15363 r:resume(...)\
15364 end\
15365 end\
15366end\
15367\
15368function Event.processEvent(e)\
15369 processHandlers(e[1])\
15370 processRoutines(table.unpack(e))\
15371end\
15372\
15373function Event.pullEvent(eventType)\
15374 while true do\
15375 local e = { os.pullEventRaw() }\
15376 local propagate = true -- don't like this...\
15377\
15378 if e[1] == 'terminate' then\
15379 propagate = Event.termFn()\
15380 end\
15381\
15382 if propagate then\
15383 processHandlers(e[1])\
15384 processRoutines(table.unpack(e))\
15385 end\
15386\
15387 if Event.terminate then\
15388 return { 'terminate' }\
15389 end\
15390\
15391 if not eventType or e[1] == eventType then\
15392 return e\
15393 end\
15394 end\
15395end\
15396\
15397return Event",
15398 [ "nft.lua" ] = "local Util = require('util')\
15399\
15400local NFT = { }\
15401\
15402-- largely copied from http://www.computercraft.info/forums2/index.php?/topic/5029-145-npaintpro/\
15403\
15404local tColourLookup = { }\
15405for n = 1, 16 do\
15406 tColourLookup[string.byte(\"0123456789abcdef\", n, n)] = 2 ^ (n - 1)\
15407end\
15408\
15409local function getColourOf(hex)\
15410 return tColourLookup[hex:byte()]\
15411end\
15412\
15413function NFT.parse(imageText)\
15414 local image = {\
15415 fg = { },\
15416 bg = { },\
15417 text = { },\
15418 }\
15419\
15420 local num = 1\
15421 local lines = Util.split(imageText)\
15422 while #lines[#lines] == 0 do\
15423 table.remove(lines, #lines)\
15424 end\
15425\
15426 for _,sLine in ipairs(lines) do\
15427 table.insert(image.fg, { })\
15428 table.insert(image.bg, { })\
15429 table.insert(image.text, { })\
15430\
15431 --As we're no longer 1-1, we keep track of what index to write to\
15432 local writeIndex = 1\
15433 --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour\
15434 local bgNext, fgNext = false, false\
15435 --The current background and foreground colours\
15436 local currBG, currFG = nil,nil\
15437 for i = 1, #sLine do\
15438 local nextChar = string.sub(sLine, i, i)\
15439 if nextChar:byte() == 30 then\
15440 bgNext = true\
15441 elseif nextChar:byte() == 31 then\
15442 fgNext = true\
15443 elseif bgNext then\
15444 currBG = getColourOf(nextChar)\
15445 bgNext = false\
15446 elseif fgNext then\
15447 currFG = getColourOf(nextChar)\
15448 fgNext = false\
15449 else\
15450 if nextChar ~= \" \" and currFG == nil then\
15451 currFG = _G.colors.white\
15452 end\
15453 image.bg[num][writeIndex] = currBG\
15454 image.fg[num][writeIndex] = currFG\
15455 image.text[num][writeIndex] = nextChar\
15456 writeIndex = writeIndex + 1\
15457 end\
15458 end\
15459 image.height = num\
15460 if not image.width or writeIndex - 1 > image.width then\
15461 image.width = writeIndex - 1\
15462 end\
15463 num = num+1\
15464 end\
15465 return image\
15466end\
15467\
15468function NFT.load(path)\
15469\
15470 local imageText = Util.readFile(path)\
15471 if not imageText then\
15472 error('Unable to read image file')\
15473 end\
15474 return NFT.parse(imageText)\
15475end\
15476\
15477return NFT",
15478 [ "input.lua" ] = "local Util = require('util')\
15479\
15480local keyboard = _G.device and _G.device.keyboard\
15481local keys = _G.keys\
15482local os = _G.os\
15483\
15484local modifiers = Util.transpose {\
15485 keys.leftCtrl, keys.rightCtrl,\
15486 keys.leftShift, keys.rightShift,\
15487 keys.leftAlt, keys.rightAlt,\
15488}\
15489\
15490local input = {\
15491 state = { },\
15492}\
15493\
15494if not keyboard then\
15495 keyboard = { state = input.state }\
15496end\
15497\
15498function input:modifierPressed()\
15499 return keyboard.state[keys.leftCtrl] or\
15500 keyboard.state[keys.rightCtrl] or\
15501 keyboard.state[keys.leftAlt] or\
15502 keyboard.state[keys.rightAlt]\
15503end\
15504\
15505function input:toCode(ch, code)\
15506 local result = { }\
15507\
15508 if not ch and code == 1 then\
15509 ch = 'escape'\
15510 end\
15511\
15512 if keyboard.state[keys.leftCtrl] or keyboard.state[keys.rightCtrl] or\
15513 code == keys.leftCtrl or code == keys.rightCtrl then\
15514 table.insert(result, 'control')\
15515 end\
15516\
15517 -- the key-up event for alt keys is not generated if the minecraft\
15518 -- window loses focus\
15519 --\
15520 -- if keyboard.state[keys.leftAlt] or keyboard.state[keys.rightAlt] or\
15521 -- code == keys.leftAlt or code == keys.rightAlt then\
15522 -- table.insert(result, 'alt')\
15523 -- end\
15524\
15525 if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] or\
15526 code == keys.leftShift or code == keys.rightShift then\
15527 if code and modifiers[code] then\
15528 table.insert(result, 'shift')\
15529 elseif #ch == 1 then\
15530 table.insert(result, ch:upper())\
15531 else\
15532 table.insert(result, 'shift')\
15533 table.insert(result, ch)\
15534 end\
15535 elseif not code or not modifiers[code] then\
15536 table.insert(result, ch)\
15537 end\
15538\
15539 return table.concat(result, '-')\
15540end\
15541\
15542function input:reset()\
15543 self.state = { }\
15544 self.fired = nil\
15545\
15546 self.timer = nil\
15547 self.mch = nil\
15548 self.mfired = nil\
15549end\
15550\
15551function input:translate(event, code, p1, p2)\
15552 if event == 'key' then\
15553 if p1 then -- key is held down\
15554 if not modifiers[code] then\
15555 self.fired = true\
15556 return { code = input:toCode(keys.getName(code), code) }\
15557 end\
15558 else\
15559 self.state[code] = true\
15560 if self:modifierPressed() and not modifiers[code] or code == 57 then\
15561 self.fired = true\
15562 return { code = input:toCode(keys.getName(code), code) }\
15563 else\
15564 self.fired = false\
15565 end\
15566 end\
15567\
15568 elseif event == 'char' then\
15569 if not self:modifierPressed() then\
15570 self.fired = true\
15571 return { code = event, ch = input:toCode(code) }\
15572 end\
15573\
15574 elseif event == 'key_up' then\
15575 if not self.fired then\
15576 if self.state[code] then\
15577 self.fired = true\
15578 local ch = input:toCode(keys.getName(code), code)\
15579 self.state[code] = nil\
15580 return { code = ch }\
15581 end\
15582 end\
15583 self.state[code] = nil\
15584\
15585 elseif event == 'paste' then\
15586 self.fired = true\
15587 if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] then\
15588 return { code = 'shift-paste', text = code }\
15589 else\
15590 return { code = 'paste', text = code }\
15591 end\
15592\
15593 elseif event == 'mouse_click' then\
15594 local buttons = { 'mouse_click', 'mouse_rightclick' }\
15595 self.mch = buttons[code]\
15596 self.mfired = nil\
15597\
15598 elseif event == 'mouse_drag' then\
15599 self.mfired = true\
15600 self.fired = true\
15601 return {\
15602 code = input:toCode('mouse_drag', 255),\
15603 button = code,\
15604 x = p1,\
15605 y = p2,\
15606 }\
15607\
15608 elseif event == 'mouse_up' then\
15609 if not self.mfired then\
15610 local clock = os.clock()\
15611 if self.timer and\
15612 p1 == self.x and p2 == self.y and\
15613 (clock - self.timer < .5) then\
15614\
15615 self.mch = 'mouse_doubleclick'\
15616 self.timer = nil\
15617 else\
15618 self.timer = os.clock()\
15619 self.x = p1\
15620 self.y = p2\
15621 end\
15622 self.mfired = input:toCode(self.mch, 255)\
15623 else\
15624 self.mch = 'mouse_up'\
15625 self.mfired = input:toCode(self.mch, 255)\
15626 end\
15627 self.fired = true\
15628 return {\
15629 code = self.mfired,\
15630 button = code,\
15631 x = p1,\
15632 y = p2,\
15633 }\
15634\
15635 elseif event == \"mouse_scroll\" then\
15636 local directions = {\
15637 [ -1 ] = 'scroll_up',\
15638 [ 1 ] = 'scroll_down'\
15639 }\
15640 self.fired = true\
15641 return {\
15642 code = input:toCode(directions[code], 255),\
15643 x = p1,\
15644 y = p2,\
15645 }\
15646\
15647 elseif event == 'terminate' then\
15648 return { code = 'terminate' }\
15649 end\
15650end\
15651\
15652function input:test()\
15653 while true do\
15654 local ch = self:translate(os.pullEvent())\
15655 if ch then\
15656 Util.print(ch)\
15657 end\
15658 end\
15659end\
15660\
15661return input",
15662 [ "logger.lua" ] = "local Logger = {\
15663 fn = function() end,\
15664 filteredEvents = { },\
15665}\
15666\
15667function Logger.setLogger(fn)\
15668 Logger.fn = fn\
15669end\
15670\
15671function Logger.disable()\
15672 Logger.setLogger(function() end)\
15673end\
15674\
15675function Logger.setDaemonLogging()\
15676 Logger.setLogger(function (text)\
15677 os.queueEvent('log', { text = text })\
15678 end)\
15679end\
15680\
15681function Logger.setMonitorLogging()\
15682 local debugMon = device.monitor\
15683\
15684 if not debugMon then\
15685 debugMon.setTextScale(.5)\
15686 debugMon.clear()\
15687 debugMon.setCursorPos(1, 1)\
15688 Logger.setLogger(function(text)\
15689 debugMon.write(text)\
15690 debugMon.scroll(-1)\
15691 debugMon.setCursorPos(1, 1)\
15692 end)\
15693 end\
15694end\
15695\
15696function Logger.setScreenLogging()\
15697 Logger.setLogger(function(text)\
15698 local x, y = term.getCursorPos()\
15699 if x ~= 1 then\
15700 local sx, sy = term.getSize()\
15701 term.setCursorPos(1, sy)\
15702 --term.scroll(1)\
15703 end\
15704 print(text)\
15705 end)\
15706end\
15707\
15708function Logger.setWirelessLogging()\
15709 if device.wireless_modem then\
15710 Logger.filter('modem_message')\
15711 Logger.filter('modem_receive')\
15712 Logger.filter('rednet_message')\
15713 Logger.setLogger(function(text)\
15714 device.wireless_modem.transmit(59998, os.getComputerID(), {\
15715 type = 'log', contents = text\
15716 })\
15717 end)\
15718 Logger.debug('Logging enabled')\
15719 return true\
15720 end\
15721end\
15722\
15723function Logger.setFileLogging(fileName)\
15724 fs.delete(fileName)\
15725 Logger.setLogger(function (text)\
15726 local logFile\
15727\
15728 local mode = 'w'\
15729 if fs.exists(fileName) then\
15730 mode = 'a'\
15731 end\
15732 local file = io.open(fileName, mode)\
15733 if file then\
15734 file:write(text)\
15735 file:write('\\n')\
15736 file:close()\
15737 end\
15738 end)\
15739end\
15740\
15741function Logger.log(category, value, ...)\
15742 if Logger.filteredEvents[category] then\
15743 return\
15744 end\
15745\
15746 if type(value) == 'table' then\
15747 local str\
15748 for k,v in pairs(value) do\
15749 if not str then\
15750 str = '{ '\
15751 else\
15752 str = str .. ', '\
15753 end\
15754 str = str .. k .. '=' .. tostring(v)\
15755 end\
15756 if str then\
15757 value = str .. ' }'\
15758 else\
15759 value = '{ }'\
15760 end\
15761 elseif type(value) == 'string' then\
15762 local args = { ... }\
15763 if #args > 0 then\
15764 value = string.format(value, unpack(args))\
15765 end\
15766 else\
15767 value = tostring(value)\
15768 end\
15769 Logger.fn(category .. ': ' .. value)\
15770end\
15771\
15772function Logger.debug(value, ...)\
15773 Logger.log('debug', value, ...)\
15774end\
15775\
15776function Logger.logNestedTable(t, indent)\
15777 for _,v in ipairs(t) do\
15778 if type(v) == 'table' then\
15779 log('table')\
15780 logNestedTable(v) --, indent+1)\
15781 else\
15782 log(v)\
15783 end\
15784 end\
15785end\
15786\
15787function Logger.filter( ...)\
15788 local events = { ... }\
15789 for _,event in pairs(events) do\
15790 Logger.filteredEvents[event] = true\
15791 end\
15792end\
15793\
15794return Logger",
15795 [ "config.lua" ] = "local Util = require('util')\
15796\
15797local fs = _G.fs\
15798local shell = _ENV.shell\
15799\
15800local Config = { }\
15801\
15802function Config.load(fname, data)\
15803 local filename = '/sys/os/opus/usr/config/' .. fname\
15804\
15805 if not fs.exists('/sys/os/opus/usr/config') then\
15806 fs.makeDir('/sys/os/opus/usr/config')\
15807 end\
15808\
15809 if not fs.exists(filename) then\
15810 Util.writeTable(filename, data)\
15811 else\
15812 local contents = Util.readTable(filename) or\
15813 error('Configuration file is corrupt:' .. filename)\
15814\
15815 Util.merge(data, contents)\
15816 end\
15817\
15818 return data\
15819end\
15820\
15821function Config.loadWithCheck(fname, data)\
15822 local filename = '/sys/os/opus/usr/config/' .. fname\
15823\
15824 if not fs.exists(filename) then\
15825 Config.load(fname, data)\
15826 print()\
15827 print('The configuration file has been created.')\
15828 print('The file name is: ' .. filename)\
15829 print()\
15830 _G.printError('Press enter to configure')\
15831 _G.read()\
15832 shell.run('edit ' .. filename)\
15833 end\
15834\
15835 return Config.load(fname, data)\
15836end\
15837\
15838function Config.update(fname, data)\
15839 local filename = '/sys/os/opus/usr/config/' .. fname\
15840 Util.writeTable(filename, data)\
15841end\
15842\
15843return Config",
15844 [ "json.lua" ] = "-- credit ElvishJerricco\
15845-- http://pastebin.com/raw.php?i=4nRg9CHU\
15846\
15847local json = { }\
15848\
15849------------------------------------------------------------------ utils\
15850local controls = {[\"\\n\"]=\"\\\\n\", [\"\\r\"]=\"\\\\r\", [\"\\t\"]=\"\\\\t\", [\"\\b\"]=\"\\\\b\", [\"\\f\"]=\"\\\\f\", [\"\\\"\"]=\"\\\\\\\"\", [\"\\\\\"]=\"\\\\\\\\\"}\
15851\
15852local function isArray(t)\
15853 local max = 0\
15854 for k,v in pairs(t) do\
15855 if type(k) ~= \"number\" then\
15856 return false\
15857 elseif k > max then\
15858 max = k\
15859 end\
15860 end\
15861 return max == #t\
15862end\
15863\
15864local whites = {['\\n']=true; ['\\r']=true; ['\\t']=true; [' ']=true; [',']=true; [':']=true}\
15865local function removeWhite(str)\
15866 while whites[str:sub(1, 1)] do\
15867 str = str:sub(2)\
15868 end\
15869 return str\
15870end\
15871\
15872------------------------------------------------------------------ encoding\
15873\
15874local function encodeCommon(val, pretty, tabLevel, tTracking)\
15875 local str = \"\"\
15876\
15877 -- Tabbing util\
15878 local function tab(s)\
15879 str = str .. (\"\\t\"):rep(tabLevel) .. s\
15880 end\
15881\
15882 local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc)\
15883 str = str .. bracket\
15884 if pretty then\
15885 str = str .. \"\\n\"\
15886 tabLevel = tabLevel + 1\
15887 end\
15888 for k,v in iterator(val) do\
15889 tab(\"\")\
15890 loopFunc(k,v)\
15891 str = str .. \",\"\
15892 if pretty then str = str .. \"\\n\" end\
15893 end\
15894 if pretty then\
15895 tabLevel = tabLevel - 1\
15896 end\
15897 if str:sub(-2) == \",\\n\" then\
15898 str = str:sub(1, -3) .. \"\\n\"\
15899 elseif str:sub(-1) == \",\" then\
15900 str = str:sub(1, -2)\
15901 end\
15902 tab(closeBracket)\
15903 end\
15904\
15905 -- Table encoding\
15906 if type(val) == \"table\" then\
15907 assert(not tTracking[val], \"Cannot encode a table holding itself recursively\")\
15908 tTracking[val] = true\
15909 if isArray(val) then\
15910 arrEncoding(val, \"[\", \"]\", ipairs, function(k,v)\
15911 str = str .. encodeCommon(v, pretty, tabLevel, tTracking)\
15912 end)\
15913 else\
15914 arrEncoding(val, \"{\", \"}\", pairs, function(k,v)\
15915 assert(type(k) == \"string\", \"JSON object keys must be strings\", 2)\
15916 str = str .. encodeCommon(k, pretty, tabLevel, tTracking)\
15917 str = str .. (pretty and \": \" or \":\") .. encodeCommon(v, pretty, tabLevel, tTracking)\
15918 end)\
15919 end\
15920 -- String encoding\
15921 elseif type(val) == \"string\" then\
15922 str = '\"' .. val:gsub(\"[%c\\\"\\\\]\", controls) .. '\"'\
15923 -- Number encoding\
15924 elseif type(val) == \"number\" or type(val) == \"boolean\" then\
15925 str = tostring(val)\
15926 else\
15927 error(\"JSON only supports arrays, objects, numbers, booleans, and strings\", 2)\
15928 end\
15929 return str\
15930end\
15931\
15932function json.encode(val)\
15933 return encodeCommon(val, false, 0, {})\
15934end\
15935\
15936function json.encodePretty(val)\
15937 return encodeCommon(val, true, 0, {})\
15938end\
15939\
15940function json.encodeToFile(path, val)\
15941 local file = io.open(path, \"w\")\
15942 assert(file, \"Unable to open file\")\
15943 file:write(json.encodePretty(val))\
15944 file:close()\
15945end\
15946\
15947------------------------------------------------------------------ decoding\
15948\
15949local decodeControls = {}\
15950for k,v in pairs(controls) do\
15951 decodeControls[v] = k\
15952end\
15953\
15954local function parseBoolean(str)\
15955 if str:sub(1, 4) == \"true\" then\
15956 return true, removeWhite(str:sub(5))\
15957 else\
15958 return false, removeWhite(str:sub(6))\
15959 end\
15960end\
15961\
15962local function parseNull(str)\
15963 return nil, removeWhite(str:sub(5))\
15964end\
15965\
15966local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true}\
15967local function parseNumber(str)\
15968 local i = 1\
15969 while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do\
15970 i = i + 1\
15971 end\
15972 local val = tonumber(str:sub(1, i - 1))\
15973 str = removeWhite(str:sub(i))\
15974 return val, str\
15975end\
15976\
15977local function parseString(str)\
15978 str = str:sub(2)\
15979 local s = \"\"\
15980 while str:sub(1,1) ~= \"\\\"\" do\
15981 local next = str:sub(1,1)\
15982 str = str:sub(2)\
15983 assert(next ~= \"\\n\", \"Unclosed string\")\
15984\
15985 if next == \"\\\\\" then\
15986 local escape = str:sub(1,1)\
15987 str = str:sub(2)\
15988\
15989 next = assert(decodeControls[next..escape], \"Invalid escape character\")\
15990 end\
15991\
15992 s = s .. next\
15993 end\
15994 return s, removeWhite(str:sub(2))\
15995end\
15996\
15997function json.parseArray(str)\
15998 str = removeWhite(str:sub(2))\
15999\
16000 local val = {}\
16001 local i = 1\
16002 while str:sub(1, 1) ~= \"]\" do\
16003 local v\
16004 v, str = json.parseValue(str)\
16005 val[i] = v\
16006 i = i + 1\
16007 str = removeWhite(str)\
16008 end\
16009 str = removeWhite(str:sub(2))\
16010 return val, str\
16011end\
16012\
16013function json.parseValue(str)\
16014 local fchar = str:sub(1, 1)\
16015 if fchar == \"{\" then\
16016 return json.parseObject(str)\
16017 elseif fchar == \"[\" then\
16018 return json.parseArray(str)\
16019 elseif tonumber(fchar) ~= nil or numChars[fchar] then\
16020 return parseNumber(str)\
16021 elseif str:sub(1, 4) == \"true\" or str:sub(1, 5) == \"false\" then\
16022 return parseBoolean(str)\
16023 elseif fchar == \"\\\"\" then\
16024 return parseString(str)\
16025 elseif str:sub(1, 4) == \"null\" then\
16026 return parseNull(str)\
16027 end\
16028end\
16029\
16030function json.parseMember(str)\
16031 local k, val\
16032 k, str = json.parseValue(str)\
16033 val, str = json.parseValue(str)\
16034 return k, val, str\
16035end\
16036\
16037function json.parseObject(str)\
16038 str = removeWhite(str:sub(2))\
16039\
16040 local val = {}\
16041 while str:sub(1, 1) ~= \"}\" do\
16042 local k, v\
16043 k, v, str = json.parseMember(str)\
16044 val[k] = v\
16045 str = removeWhite(str)\
16046 end\
16047 str = removeWhite(str:sub(2))\
16048 return val, str\
16049end\
16050\
16051function json.decode(str)\
16052 str = removeWhite(str)\
16053 return json.parseValue(str)\
16054end\
16055\
16056function json.decodeFromFile(path)\
16057 local file = assert(fs.open(path, \"r\"))\
16058 local decoded = json.decode(file.readAll())\
16059 file.close()\
16060 return decoded\
16061end\
16062\
16063return json",
16064 [ "crypto.lua" ] = "-- https://github.com/PixelToast/ComputerCraft/blob/master/apis/enc\
16065\
16066local Crypto = { }\
16067\
16068local function serialize(t) \
16069 local sType = type(t)\
16070 if sType == \"table\" then\
16071 local lstcnt=0\
16072 for k,v in pairs(t) do\
16073 lstcnt = lstcnt + 1\
16074 end\
16075 local result = \"{\"\
16076 local aset=1\
16077 for k,v in pairs(t) do\
16078 if k==aset then\
16079 result = result..serialize(v)..\",\"\
16080 aset=aset+1\
16081 else\
16082 result = result..(\"[\"..serialize(k)..\"]=\"..serialize(v)..\",\")\
16083 end\
16084 end\
16085 result = result..\"}\"\
16086 return result\
16087 elseif sType == \"string\" then\
16088 return string.format(\"%q\",t)\
16089 elseif sType == \"number\" or sType == \"boolean\" or sType == \"nil\" then\
16090 return tostring(t)\
16091 elseif sType == \"function\" then\
16092 local status,data=pcall(string.dump,t)\
16093 if status then\
16094 data2=\"\"\
16095 for char in string.gmatch(data,\".\") do\
16096 data2=data2..zfill(string.byte(char))\
16097 end\
16098 return 'f(\"'..data2..'\")'\
16099 else\
16100 error(\"Invalid function: \"..data)\
16101 end\
16102 else\
16103 error(\"Could not serialize type \"..sType..\".\")\
16104 end\
16105end\
16106\
16107local function unserialize( s )\
16108 local func, e = loadstring( \"return \"..s, \"serialize\" )\
16109 if not func then\
16110 return s,e\
16111 else\
16112 setfenv( func, {\
16113 f=function(S)\
16114 return loadstring(splitnum(S))\
16115 end,\
16116 })\
16117 return func()\
16118 end\
16119end\
16120\
16121local function splitnum(S)\
16122 local Out=\"\"\
16123 for l1=1,#S,2 do\
16124 local l2=(#S-l1)+1\
16125 local function sure(N,n)\
16126 if (l2-n)<1 then N=\"0\" end\
16127 return N\
16128 end\
16129 local CNum=tonumber(\"0x\"..sure(string.sub(S,l2-1,l2-1),1) .. sure(string.sub(S,l2,l2),0))\
16130 Out=string.char(CNum)..Out\
16131 end\
16132 return Out\
16133end\
16134\
16135local function zfill(N)\
16136 N=string.format(\"%X\",N)\
16137 Zs=\"\"\
16138 if #N==1 then\
16139 Zs=\"0\"\
16140 end\
16141 return Zs..N\
16142end\
16143\
16144local function wrap(N)\
16145 return N-(math.floor(N/256)*256)\
16146end\
16147\
16148local function checksum(S)\
16149 local sum=0\
16150 for char in string.gmatch(S,\".\") do\
16151 math.randomseed(string.byte(char)+sum)\
16152 sum=sum+math.random(0,9999)\
16153 end\
16154 math.randomseed(sum)\
16155 return sum\
16156end\
16157\
16158local function genkey(len,psw)\
16159 checksum(psw)\
16160 local key={}\
16161 local tKeys={}\
16162 for l1=1,len do\
16163 local num=math.random(1,len)\
16164 while tKeys[num] do\
16165 num=math.random(1,len)\
16166 end\
16167 tKeys[num]=true\
16168 key[l1]={num,math.random(0,255)}\
16169 end\
16170 return key\
16171end\
16172\
16173function Crypto.encrypt(data,psw)\
16174 data=serialize(data)\
16175 local chs=checksum(data)\
16176 local key=genkey(#data,psw)\
16177 local out={}\
16178 local cnt=1\
16179 for char in string.gmatch(data,\".\") do\
16180 table.insert(out,key[cnt][1],zfill(wrap(string.byte(char)+key[cnt][2])),chars)\
16181 cnt=cnt+1\
16182 end\
16183 return string.sub(serialize({chs,table.concat(out)}),2,-3)\
16184end\
16185\
16186function Crypto.decrypt(data,psw)\
16187 local oData=data\
16188 data=unserialize(\"{\"..data..\"}\")\
16189 if type(data)~=\"table\" then\
16190 return oData\
16191 end\
16192 local chs=data[1]\
16193 data=data[2]\
16194 local key=genkey((#data)/2,psw)\
16195 local sKey={}\
16196 for k,v in pairs(key) do\
16197 sKey[v[1]]={k,v[2]}\
16198 end\
16199 local str=splitnum(data)\
16200 local cnt=1\
16201 local out={}\
16202 for char in string.gmatch(str,\".\") do\
16203 table.insert(out,sKey[cnt][1],string.char(wrap(string.byte(char)-sKey[cnt][2])))\
16204 cnt=cnt+1\
16205 end\
16206 out=table.concat(out)\
16207 if checksum(out or \"\")==chs then\
16208 return unserialize(out)\
16209 end\
16210 return oData,out,chs\
16211end\
16212\
16213return Crypto",
16214 },
16215 extensions = {
16216 [ "7.multishell.lua" ] = "_G.requireInjector(_ENV)\
16217\
16218local Config = require('config')\
16219local Packages = require('packages')\
16220local Util = require('util')\
16221\
16222local colors = _G.colors\
16223local fs = _G.fs\
16224local kernel = _G.kernel\
16225local keys = _G.keys\
16226local os = _G.os\
16227local printError = _G.printError\
16228local shell = _ENV.shell\
16229local term = _G.term\
16230local window = _G.window\
16231\
16232local parentTerm = _G.device.terminal\
16233local w,h = parentTerm.getSize()\
16234local overviewId\
16235local tabsDirty = false\
16236local closeInd = Util.getVersion() >= 1.76 and '\\215' or '*'\
16237local multishell = { }\
16238\
16239shell.setEnv('multishell', multishell)\
16240\
16241multishell.term = parentTerm --deprecated\
16242\
16243local config = {\
16244 standard = {\
16245 textColor = colors.lightGray,\
16246 tabBarTextColor = colors.lightGray,\
16247 focusTextColor = colors.white,\
16248 backgroundColor = colors.gray,\
16249 tabBarBackgroundColor = colors.gray,\
16250 focusBackgroundColor = colors.gray,\
16251 errorColor = colors.black,\
16252 },\
16253 color = {\
16254 textColor = colors.lightGray,\
16255 tabBarTextColor = colors.lightGray,\
16256 focusTextColor = colors.white,\
16257 backgroundColor = colors.gray,\
16258 tabBarBackgroundColor = colors.gray,\
16259 focusBackgroundColor = colors.gray,\
16260 errorColor = colors.red,\
16261 },\
16262}\
16263Config.load('multishell', config)\
16264\
16265local _colors = parentTerm.isColor() and config.color or config.standard\
16266\
16267local function redrawMenu()\
16268 if not tabsDirty then\
16269 os.queueEvent('multishell_redraw')\
16270 tabsDirty = true\
16271 end\
16272end\
16273\
16274function multishell.getFocus()\
16275 local currentTab = kernel.getFocused()\
16276 return currentTab.uid\
16277end\
16278\
16279function multishell.setFocus(tabId)\
16280 return kernel.raise(tabId)\
16281end\
16282\
16283function multishell.getTitle(tabId)\
16284 local tab = kernel.find(tabId)\
16285 return tab and tab.title\
16286end\
16287\
16288function multishell.setTitle(tabId, title)\
16289 local tab = kernel.find(tabId)\
16290 if tab then\
16291 tab.title = title\
16292 redrawMenu()\
16293 end\
16294end\
16295\
16296function multishell.getCurrent()\
16297 local runningTab = kernel.getCurrent()\
16298 return runningTab and runningTab.uid\
16299end\
16300\
16301function multishell.getTab(tabId)\
16302 return kernel.find(tabId)\
16303end\
16304\
16305function multishell.terminate(tabId)\
16306 os.queueEvent('multishell_terminate', tabId)\
16307end\
16308\
16309function multishell.getTabs()\
16310 return kernel.routines\
16311end\
16312\
16313function multishell.launch( tProgramEnv, sProgramPath, ... )\
16314 -- backwards compatibility\
16315 return multishell.openTab({\
16316 env = tProgramEnv,\
16317 path = sProgramPath,\
16318 args = { ... },\
16319 })\
16320end\
16321\
16322function multishell.openTab(tab)\
16323 if not tab.title and tab.path then\
16324 tab.title = fs.getName(tab.path):match('([^%.]+)')\
16325 end\
16326 tab.title = tab.title or 'untitled'\
16327 tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false)\
16328 tab.terminal = tab.terminal or tab.window\
16329\
16330 local routine = kernel.newRoutine(tab)\
16331\
16332 routine.co = coroutine.create(function()\
16333 local result, err\
16334\
16335 if tab.fn then\
16336 result, err = Util.runFunction(routine.env, tab.fn, table.unpack(tab.args or { } ))\
16337 elseif tab.path then\
16338 result, err = Util.run(routine.env, tab.path, table.unpack(tab.args or { } ))\
16339 else\
16340 err = 'multishell: invalid tab'\
16341 end\
16342\
16343 if not result and err and err ~= 'Terminated' then\
16344 if err then\
16345 printError(tostring(err))\
16346 end\
16347 print('\\nPress enter to close')\
16348 routine.isDead = true\
16349 routine.hidden = false\
16350 redrawMenu()\
16351 while true do\
16352 local e, code = os.pullEventRaw('key')\
16353 if e == 'terminate' or e == 'key' and code == keys.enter then\
16354 break\
16355 end\
16356 end\
16357 end\
16358 end)\
16359\
16360 kernel.launch(routine)\
16361\
16362 if tab.focused then\
16363 multishell.setFocus(routine.uid)\
16364 else\
16365 redrawMenu()\
16366 end\
16367 return routine.uid\
16368end\
16369\
16370function multishell.hideTab(tabId)\
16371 local tab = kernel.find(tabId)\
16372 if tab then\
16373 tab.hidden = true\
16374 kernel.lower(tab.uid)\
16375 redrawMenu()\
16376 end\
16377end\
16378\
16379function multishell.unhideTab(tabId)\
16380 local tab = kernel.find(tabId)\
16381 if tab then\
16382 tab.hidden = false\
16383 redrawMenu()\
16384 end\
16385end\
16386\
16387function multishell.getCount()\
16388 return #kernel.routines\
16389end\
16390\
16391kernel.hook('kernel_focus', function(_, eventData)\
16392 local previous = eventData[2]\
16393 if previous then\
16394 local routine = kernel.find(previous)\
16395 if routine and routine.window then\
16396 routine.window.setVisible(false)\
16397 if routine.hidden then\
16398 kernel.lower(previous)\
16399 end\
16400 end\
16401 end\
16402\
16403 local focused = kernel.find(eventData[1])\
16404 if focused and focused.window then\
16405 focused.window.setVisible(true)\
16406 end\
16407\
16408 redrawMenu()\
16409end)\
16410\
16411kernel.hook('multishell_terminate', function(_, eventData)\
16412 local tab = kernel.find(eventData[1])\
16413 if tab and not tab.isOverview then\
16414 if coroutine.status(tab.co) ~= 'dead' then\
16415 tab:resume(\"terminate\")\
16416 end\
16417 end\
16418 return true\
16419end)\
16420\
16421kernel.hook('terminate', function()\
16422 return kernel.getFocused().isOverview\
16423end)\
16424\
16425kernel.hook('multishell_redraw', function()\
16426 tabsDirty = false\
16427\
16428 local function write(x, text, bg, fg)\
16429 parentTerm.setBackgroundColor(bg)\
16430 parentTerm.setTextColor(fg)\
16431 parentTerm.setCursorPos(x, 1)\
16432 parentTerm.write(text)\
16433 end\
16434\
16435 local bg = _colors.tabBarBackgroundColor\
16436 parentTerm.setBackgroundColor(bg)\
16437 parentTerm.setCursorPos(1, 1)\
16438 parentTerm.clearLine()\
16439\
16440 local currentTab = kernel.getFocused()\
16441\
16442 for _,tab in pairs(kernel.routines) do\
16443 if tab.hidden and tab ~= currentTab then\
16444 tab.width = 0\
16445 else\
16446 tab.width = #tab.title + 1\
16447 end\
16448 end\
16449\
16450 local function width()\
16451 local tw = 0\
16452 Util.each(kernel.routines, function(t) tw = tw + t.width end)\
16453 return tw\
16454 end\
16455\
16456 while width() > w - 3 do\
16457 local tab = select(2,\
16458 Util.spairs(kernel.routines, function(a, b) return a.width > b.width end)())\
16459 tab.width = tab.width - 1\
16460 end\
16461\
16462 local function compareTab(a, b)\
16463 if a.hidden then return false end\
16464 return b.hidden or a.uid < b.uid\
16465 end\
16466\
16467 local tabX = 0\
16468 for _,tab in Util.spairs(kernel.routines, compareTab) do\
16469 if tab.width > 0 then\
16470 tab.sx = tabX + 1\
16471 tab.ex = tabX + tab.width\
16472 tabX = tabX + tab.width\
16473 if tab ~= currentTab then\
16474 local textColor = tab.isDead and _colors.errorColor or _colors.textColor\
16475 write(tab.sx, tab.title:sub(1, tab.width - 1),\
16476 _colors.backgroundColor, textColor)\
16477 end\
16478 end\
16479 end\
16480\
16481 if currentTab then\
16482 write(currentTab.sx - 1,\
16483 ' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ',\
16484 _colors.focusBackgroundColor, _colors.focusTextColor)\
16485 if not currentTab.isOverview then\
16486 write(w, closeInd, _colors.backgroundColor, _colors.focusTextColor)\
16487 end\
16488 end\
16489\
16490 if currentTab and currentTab.window then\
16491 currentTab.window.restoreCursor()\
16492 end\
16493\
16494 return true\
16495end)\
16496\
16497kernel.hook('term_resize', function(_, eventData)\
16498 if not eventData[1] then --- TEST\
16499 w,h = parentTerm.getSize()\
16500\
16501 local windowHeight = h-1\
16502\
16503 for _,key in pairs(Util.keys(kernel.routines)) do\
16504 local tab = kernel.routines[key]\
16505 local x,y = tab.window.getCursorPos()\
16506 if y > windowHeight then\
16507 tab.window.scroll(y - windowHeight)\
16508 tab.window.setCursorPos(x, windowHeight)\
16509 end\
16510 tab.window.reposition(1, 2, w, windowHeight)\
16511 end\
16512\
16513 redrawMenu()\
16514 end\
16515end)\
16516\
16517kernel.hook('mouse_click', function(_, eventData)\
16518 local x, y = eventData[2], eventData[3]\
16519\
16520 if y == 1 then\
16521 if x == 1 then\
16522 multishell.setFocus(overviewId)\
16523 elseif x == w then\
16524 local currentTab = kernel.getFocused()\
16525 if currentTab then\
16526 multishell.terminate(currentTab.uid)\
16527 end\
16528 else\
16529 for _,tab in pairs(kernel.routines) do\
16530 if not tab.hidden and tab.sx then\
16531 if x >= tab.sx and x <= tab.ex then\
16532 multishell.setFocus(tab.uid)\
16533 break\
16534 end\
16535 end\
16536 end\
16537 end\
16538 return true\
16539 end\
16540 eventData[3] = eventData[3] - 1\
16541end)\
16542\
16543kernel.hook({ 'mouse_up', 'mouse_drag' }, function(_, eventData)\
16544 eventData[3] = eventData[3] - 1\
16545end)\
16546\
16547kernel.hook('mouse_scroll', function(_, eventData)\
16548 if eventData[3] == 1 then\
16549 return true\
16550 end\
16551 eventData[3] = eventData[3] - 1\
16552end)\
16553\
16554local function startup()\
16555 local success = true\
16556\
16557 local function runDir(directory, open)\
16558 if not fs.exists(directory) then\
16559 return true\
16560 end\
16561\
16562 local files = fs.list(directory)\
16563 table.sort(files)\
16564\
16565 for _,file in ipairs(files) do\
16566 os.sleep(0)\
16567 local result, err = open(directory .. '/' .. file)\
16568\
16569 if result then\
16570 if term.isColor() then\
16571 term.setTextColor(colors.green)\
16572 end\
16573 term.write('[PASS] ')\
16574 term.setTextColor(colors.white)\
16575 term.write(fs.combine(directory, file))\
16576 print()\
16577 else\
16578 if term.isColor() then\
16579 term.setTextColor(colors.red)\
16580 end\
16581 term.write('[FAIL] ')\
16582 term.setTextColor(colors.white)\
16583 term.write(fs.combine(directory, file))\
16584 if err then\
16585 _G.printError('\\n' .. err)\
16586 end\
16587 print()\
16588 success = false\
16589 end\
16590 end\
16591 end\
16592\
16593 runDir('sys/os/opus/sys/autorun', shell.run)\
16594 for name in pairs(Packages:installed()) do\
16595 local packageDir = 'packages/' .. name .. '/autorun'\
16596 runDir(packageDir, shell.run)\
16597 end\
16598 runDir('/sys/os/opus/usr/autorun', shell.run)\
16599\
16600 if not success then\
16601 multishell.setFocus(multishell.getCurrent())\
16602 printError('\\nA startup program has errored')\
16603 os.pullEvent('terminate')\
16604 end\
16605end\
16606\
16607kernel.hook('kernel_ready', function()\
16608 overviewId = multishell.openTab({\
16609 path = 'sys/os/opus/sys/apps/Overview.lua',\
16610 isOverview = true,\
16611 focused = true,\
16612 title = '+',\
16613 })\
16614\
16615 multishell.openTab({\
16616 fn = startup,\
16617 title = 'Autorun',\
16618 })\
16619end)",
16620 [ "6.tl3.lua" ] = "if not _G.turtle then\
16621 return\
16622end\
16623\
16624_G.requireInjector(_ENV)\
16625\
16626local Pathing = require('turtle.pathfind')\
16627local GPS = require('gps')\
16628local Point = require('point')\
16629local synchronized = require('sync').sync\
16630local Util = require('util')\
16631\
16632local os = _G.os\
16633local peripheral = _G.peripheral\
16634local turtle = _G.turtle\
16635\
16636local function noop() end\
16637local headings = Point.headings\
16638local state = { }\
16639\
16640turtle.pathfind = Pathing.pathfind\
16641turtle.point = { x = 0, y = 0, z = 0, heading = 0 }\
16642\
16643function turtle.getPoint() return turtle.point end\
16644function turtle.getState() return state end\
16645function turtle.isAborted() return state.abort end\
16646function turtle.getStatus() return state.status end\
16647function turtle.setStatus(s) state.status = s end\
16648\
16649local function _defaultMove(action)\
16650 while not action.move() do\
16651 if not state.digPolicy(action) and not state.attackPolicy(action) then\
16652 return false\
16653 end\
16654 end\
16655 return true\
16656end\
16657\
16658function turtle.setPoint(pt, isGPS)\
16659 turtle.point.x = pt.x\
16660 turtle.point.y = pt.y\
16661 turtle.point.z = pt.z\
16662 if pt.heading then\
16663 turtle.point.heading = pt.heading\
16664 end\
16665 turtle.point.gps = isGPS\
16666 return true\
16667end\
16668\
16669function turtle.resetState()\
16670 state.abort = false\
16671 state.status = 'idle'\
16672 state.attackPolicy = noop\
16673 state.digPolicy = noop\
16674 state.movePolicy = _defaultMove\
16675 state.moveCallback = noop\
16676 Pathing.reset()\
16677 return true\
16678end\
16679\
16680function turtle.reset()\
16681 turtle.point.x = 0\
16682 turtle.point.y = 0\
16683 turtle.point.z = 0\
16684 turtle.point.heading = 0 -- should be facing\
16685 turtle.point.gps = false\
16686\
16687 turtle.resetState()\
16688 return true\
16689end\
16690\
16691turtle.reset()\
16692\
16693local actions = {\
16694 up = {\
16695 detect = turtle.native.detectUp,\
16696 dig = turtle.native.digUp,\
16697 move = turtle.native.up,\
16698 attack = turtle.native.attackUp,\
16699 place = turtle.native.placeUp,\
16700 drop = turtle.native.dropUp,\
16701 suck = turtle.native.suckUp,\
16702 compare = turtle.native.compareUp,\
16703 inspect = turtle.native.inspectUp,\
16704 side = 'top'\
16705 },\
16706 down = {\
16707 detect = turtle.native.detectDown,\
16708 dig = turtle.native.digDown,\
16709 move = turtle.native.down,\
16710 attack = turtle.native.attackDown,\
16711 place = turtle.native.placeDown,\
16712 drop = turtle.native.dropDown,\
16713 suck = turtle.native.suckDown,\
16714 compare = turtle.native.compareDown,\
16715 inspect = turtle.native.inspectDown,\
16716 side = 'bottom'\
16717 },\
16718 forward = {\
16719 detect = turtle.native.detect,\
16720 dig = turtle.native.dig,\
16721 move = turtle.native.forward,\
16722 attack = turtle.native.attack,\
16723 place = turtle.native.place,\
16724 drop = turtle.native.drop,\
16725 suck = turtle.native.suck,\
16726 compare = turtle.native.compare,\
16727 inspect = turtle.native.inspect,\
16728 side = 'front'\
16729 },\
16730 back = {\
16731 detect = noop,\
16732 dig = noop,\
16733 move = turtle.native.back,\
16734 attack = noop,\
16735 place = noop,\
16736 suck = noop,\
16737 compare = noop,\
16738 side = 'back'\
16739 },\
16740}\
16741\
16742function turtle.getAction(direction)\
16743 return actions[direction]\
16744end\
16745\
16746function turtle.getHeadingInfo(heading)\
16747 heading = heading or turtle.point.heading\
16748 return headings[heading]\
16749end\
16750\
16751-- hackish way to support unlimited fuel\
16752if type(turtle.getFuelLevel()) ~= 'number' then\
16753 function turtle.getFuelLevel()\
16754 return 10000000\
16755 end\
16756end\
16757\
16758-- [[ Basic turtle actions ]] --\
16759local function inventoryAction(fn, name, qty)\
16760 local slots = turtle.getFilledSlots()\
16761 local s\
16762 for _,slot in pairs(slots) do\
16763 if slot.key == name or slot.name == name then\
16764 turtle.native.select(slot.index)\
16765 if not qty then\
16766 s = fn()\
16767 else\
16768 s = fn(math.min(qty, slot.count))\
16769 qty = qty - slot.count\
16770 if qty < 0 then\
16771 break\
16772 end\
16773 end\
16774 end\
16775 end\
16776 if not s then\
16777 return false, 'No items found'\
16778 end\
16779 return s\
16780end\
16781\
16782-- [[ Attack ]] --\
16783local function _attack(action)\
16784 if action.attack() then\
16785 repeat until not action.attack()\
16786 return true\
16787 end\
16788 return false\
16789end\
16790\
16791turtle.attackPolicies = {\
16792 none = noop,\
16793\
16794 attack = function(action)\
16795 return _attack(action)\
16796 end,\
16797}\
16798\
16799function turtle.attack() return _attack(actions.forward) end\
16800function turtle.attackUp() return _attack(actions.up) end\
16801function turtle.attackDown() return _attack(actions.down) end\
16802\
16803function turtle.setAttackPolicy(policy) state.attackPolicy = policy end\
16804\
16805-- [[ Place ]] --\
16806local function _place(action, indexOrId)\
16807\
16808 local slot\
16809\
16810 if indexOrId then\
16811 slot = turtle.getSlot(indexOrId)\
16812 if not slot then\
16813 return false, 'No items to place'\
16814 end\
16815 end\
16816\
16817 if slot and slot.qty == 0 then\
16818 return false, 'No items to place'\
16819 end\
16820\
16821 return Util.tryTimes(3, function()\
16822 if slot then\
16823 turtle.select(slot.index)\
16824 end\
16825 local result = { action.place() }\
16826 if result[1] then\
16827 return true\
16828 end\
16829 if not state.digPolicy(action) then\
16830 state.attackPolicy(action)\
16831 end\
16832 return unpack(result)\
16833 end)\
16834end\
16835\
16836function turtle.place(slot) return _place(actions.forward, slot) end\
16837function turtle.placeUp(slot) return _place(actions.up, slot) end\
16838function turtle.placeDown(slot) return _place(actions.down, slot) end\
16839\
16840local function _drop(action, qtyOrName, qty)\
16841 if not qtyOrName or type(qtyOrName) == 'number' then\
16842 return action.drop(qtyOrName or 64)\
16843 end\
16844 return inventoryAction(action.drop, qtyOrName, qty)\
16845end\
16846\
16847function turtle.drop(count, slot) return _drop(actions.forward, count, slot) end\
16848function turtle.dropUp(count, slot) return _drop(actions.up, count, slot) end\
16849function turtle.dropDown(count, slot) return _drop(actions.down, count, slot) end\
16850\
16851function turtle.refuel(qtyOrName, qty)\
16852 if not qtyOrName or type(qtyOrName) == 'number' then\
16853 return turtle.native.refuel(qtyOrName or 64)\
16854 end\
16855 return inventoryAction(turtle.native.refuel, qtyOrName, qty or 64)\
16856end\
16857\
16858function turtle.isTurtleAtSide(side)\
16859 local sideType = peripheral.getType(side)\
16860 return sideType and sideType == 'turtle'\
16861end\
16862\
16863turtle.digPolicies = {\
16864 none = noop,\
16865\
16866 dig = function(action)\
16867 return action.dig()\
16868 end,\
16869\
16870 turtleSafe = function(action)\
16871 if action.side == 'back' then\
16872 return false\
16873 end\
16874 if not turtle.isTurtleAtSide(action.side) then\
16875 return action.dig()\
16876 end\
16877 return Util.tryTimes(6, function()\
16878-- if not turtle.isTurtleAtSide(action.side) then\
16879-- return true --action.dig()\
16880-- end\
16881 os.sleep(.25)\
16882 if not action.detect() then\
16883 return true\
16884 end\
16885 end)\
16886 end,\
16887\
16888 digAndDrop = function(action)\
16889 if action.detect() then\
16890 local slots = turtle.getInventory()\
16891 if action.dig() then\
16892 turtle.reconcileInventory(slots)\
16893 return true\
16894 end\
16895 end\
16896 return false\
16897 end\
16898}\
16899\
16900turtle.movePolicies = {\
16901 none = noop,\
16902 default = _defaultMove,\
16903 assured = function(action)\
16904 if not _defaultMove(action) then\
16905 if action.side == 'back' then\
16906 return false\
16907 end\
16908 local oldStatus = state.status\
16909 print('assured move: stuck')\
16910 state.status = 'stuck'\
16911 repeat\
16912 os.sleep(1)\
16913 until _defaultMove(action)\
16914 state.status = oldStatus\
16915 end\
16916 return true\
16917 end,\
16918}\
16919\
16920turtle.policies = {\
16921 none = { dig = turtle.digPolicies.none, attack = turtle.attackPolicies.none },\
16922 digOnly = { dig = turtle.digPolicies.dig, attack = turtle.attackPolicies.none },\
16923 attackOnly = { dig = turtle.digPolicies.none, attack = turtle.attackPolicies.attack },\
16924 digAttack = { dig = turtle.digPolicies.dig, attack = turtle.attackPolicies.attack },\
16925 turtleSafe = { dig = turtle.digPolicies.turtleSafe, attack = turtle.attackPolicies.attack },\
16926\
16927 attack = { attack = turtle.attackPolicies.attack },\
16928\
16929 defaultMove = { move = turtle.movePolicies.default },\
16930 assuredMove = { move = turtle.movePolicies.assured },\
16931}\
16932\
16933function turtle.setPolicy(...)\
16934 local args = { ... }\
16935 for _, policy in pairs(args) do\
16936 if type(policy) == 'string' then\
16937 policy = turtle.policies[policy]\
16938 end\
16939 if not policy then\
16940 error('Invalid policy')\
16941 -- return false, 'Invalid policy'\
16942 end\
16943 if policy.dig then\
16944 state.digPolicy = policy.dig\
16945 end\
16946 if policy.attack then\
16947 state.attackPolicy = policy.attack\
16948 end\
16949 if policy.move then\
16950 state.movePolicy = policy.move\
16951 end\
16952 end\
16953 return true\
16954end\
16955\
16956function turtle.setDigPolicy(policy) state.digPolicy = policy end\
16957function turtle.setMoveCallback(cb) state.moveCallback = cb end\
16958function turtle.clearMoveCallback() state.moveCallback = noop end\
16959function turtle.getMoveCallback() return state.moveCallback end\
16960\
16961-- [[ Heading ]] --\
16962function turtle.getHeading()\
16963 return turtle.point.heading\
16964end\
16965\
16966function turtle.turnRight()\
16967 turtle.setHeading((turtle.point.heading + 1) % 4)\
16968 return turtle.point\
16969end\
16970\
16971function turtle.turnLeft()\
16972 turtle.setHeading((turtle.point.heading - 1) % 4)\
16973 return turtle.point\
16974end\
16975\
16976function turtle.turnAround()\
16977 turtle.setHeading((turtle.point.heading + 2) % 4)\
16978 return turtle.point\
16979end\
16980\
16981function turtle.setHeading(heading)\
16982 if not heading then\
16983 return false, 'Invalid heading'\
16984 end\
16985\
16986 local fi = Point.facings[heading]\
16987 if not fi then\
16988 return false, 'Invalid heading'\
16989 end\
16990\
16991 heading = fi.heading % 4\
16992 if heading ~= turtle.point.heading then\
16993 while heading < turtle.point.heading do\
16994 heading = heading + 4\
16995 end\
16996 if heading - turtle.point.heading == 3 then\
16997 turtle.native.turnLeft()\
16998 turtle.point.heading = (turtle.point.heading - 1) % 4\
16999 state.moveCallback('turn', turtle.point)\
17000 else\
17001 local turns = heading - turtle.point.heading\
17002 while turns > 0 do\
17003 turns = turns - 1\
17004 turtle.native.turnRight()\
17005 turtle.point.heading = (turtle.point.heading + 1) % 4\
17006 state.moveCallback('turn', turtle.point)\
17007 end\
17008 end\
17009 end\
17010\
17011 return turtle.point\
17012end\
17013\
17014function turtle.headTowardsX(dx)\
17015 if turtle.point.x ~= dx then\
17016 if turtle.point.x > dx then\
17017 turtle.setHeading(2)\
17018 else\
17019 turtle.setHeading(0)\
17020 end\
17021 end\
17022end\
17023\
17024function turtle.headTowardsZ(dz)\
17025 if turtle.point.z ~= dz then\
17026 if turtle.point.z > dz then\
17027 turtle.setHeading(3)\
17028 else\
17029 turtle.setHeading(1)\
17030 end\
17031 end\
17032end\
17033\
17034function turtle.headTowards(pt)\
17035 local xd = math.abs(turtle.point.x - pt.x)\
17036 local zd = math.abs(turtle.point.z - pt.z)\
17037 if xd > zd then\
17038 turtle.headTowardsX(pt.x)\
17039 else\
17040 turtle.headTowardsZ(pt.z)\
17041 end\
17042end\
17043\
17044-- [[ move ]] --\
17045function turtle.up()\
17046 if state.movePolicy(actions.up) then\
17047 turtle.point.y = turtle.point.y + 1\
17048 state.moveCallback('up', turtle.point)\
17049 return true, turtle.point\
17050 end\
17051end\
17052\
17053function turtle.down()\
17054 if state.movePolicy(actions.down) then\
17055 turtle.point.y = turtle.point.y - 1\
17056 state.moveCallback('down', turtle.point)\
17057 return true, turtle.point\
17058 end\
17059end\
17060\
17061function turtle.forward()\
17062 if state.movePolicy(actions.forward) then\
17063 turtle.point.x = turtle.point.x + headings[turtle.point.heading].xd\
17064 turtle.point.z = turtle.point.z + headings[turtle.point.heading].zd\
17065 state.moveCallback('forward', turtle.point)\
17066 return true, turtle.point\
17067 end\
17068end\
17069\
17070function turtle.back()\
17071 if state.movePolicy(actions.back) then\
17072 turtle.point.x = turtle.point.x - headings[turtle.point.heading].xd\
17073 turtle.point.z = turtle.point.z - headings[turtle.point.heading].zd\
17074 state.moveCallback('back', turtle.point)\
17075 return true, turtle.point\
17076 end\
17077end\
17078\
17079local function moveTowardsX(dx)\
17080 if not tonumber(dx) then error('moveTowardsX: Invalid arguments') end\
17081 local direction = dx - turtle.point.x\
17082 local move\
17083\
17084 if direction == 0 then\
17085 return true\
17086 end\
17087\
17088 if direction > 0 and turtle.point.heading == 0 or\
17089 direction < 0 and turtle.point.heading == 2 then\
17090 move = turtle.forward\
17091 else\
17092 move = turtle.back\
17093 end\
17094\
17095 repeat\
17096 if not move() then\
17097 return false\
17098 end\
17099 until turtle.point.x == dx\
17100 return true\
17101end\
17102\
17103local function moveTowardsZ(dz)\
17104 local direction = dz - turtle.point.z\
17105 local move\
17106\
17107 if direction == 0 then\
17108 return true\
17109 end\
17110\
17111 if direction > 0 and turtle.point.heading == 1 or\
17112 direction < 0 and turtle.point.heading == 3 then\
17113 move = turtle.forward\
17114 else\
17115 move = turtle.back\
17116 end\
17117\
17118 repeat\
17119 if not move() then\
17120 return false\
17121 end\
17122 until turtle.point.z == dz\
17123 return true\
17124end\
17125\
17126-- [[ go ]] --\
17127-- 1 turn goto (going backwards if possible)\
17128function turtle.gotoSingleTurn(dx, dy, dz, dh)\
17129 dx = dx or turtle.point.x\
17130 dy = dy or turtle.point.y\
17131 dz = dz or turtle.point.z\
17132\
17133 local function gx()\
17134 if turtle.point.x ~= dx then\
17135 moveTowardsX(dx)\
17136 end\
17137 if turtle.point.z ~= dz then\
17138 if dh and dh % 2 == 1 then\
17139 turtle.setHeading(dh)\
17140 else\
17141 turtle.headTowardsZ(dz)\
17142 end\
17143 end\
17144 end\
17145\
17146 local function gz()\
17147 if turtle.point.z ~= dz then\
17148 moveTowardsZ(dz)\
17149 end\
17150 if turtle.point.x ~= dx then\
17151 if dh and dh % 2 == 0 then\
17152 turtle.setHeading(dh)\
17153 else\
17154 turtle.headTowardsX(dx)\
17155 end\
17156 end\
17157 end\
17158\
17159 repeat\
17160 local x, z\
17161 local y = turtle.point.y\
17162\
17163 repeat\
17164 x, z = turtle.point.x, turtle.point.z\
17165\
17166 if turtle.point.heading % 2 == 0 then\
17167 gx()\
17168 gz()\
17169 else\
17170 gz()\
17171 gx()\
17172 end\
17173 until x == turtle.point.x and z == turtle.point.z\
17174\
17175 if turtle.point.y ~= dy then\
17176 turtle.gotoY(dy)\
17177 end\
17178\
17179 if turtle.point.x == dx and turtle.point.z == dz and turtle.point.y == dy then\
17180 return true\
17181 end\
17182\
17183 until x == turtle.point.x and z == turtle.point.z and y == turtle.point.y\
17184\
17185 return false\
17186end\
17187\
17188local function gotoEx(dx, dy, dz)\
17189 -- determine the heading to ensure the least amount of turns\
17190 -- first check is 1 turn needed - remaining require 2 turns\
17191 if turtle.point.heading == 0 and turtle.point.x <= dx or\
17192 turtle.point.heading == 2 and turtle.point.x >= dx or\
17193 turtle.point.heading == 1 and turtle.point.z <= dz or\
17194 turtle.point.heading == 3 and turtle.point.z >= dz then\
17195 -- maintain current heading\
17196 -- nop\
17197 elseif dz > turtle.point.z and turtle.point.heading == 0 or\
17198 dz < turtle.point.z and turtle.point.heading == 2 or\
17199 dx < turtle.point.x and turtle.point.heading == 1 or\
17200 dx > turtle.point.x and turtle.point.heading == 3 then\
17201 turtle.turnRight()\
17202 else\
17203 turtle.turnLeft()\
17204 end\
17205\
17206 if (turtle.point.heading % 2) == 1 then\
17207 if not turtle.gotoZ(dz) then return false end\
17208 if not turtle.gotoX(dx) then return false end\
17209 else\
17210 if not turtle.gotoX(dx) then return false end\
17211 if not turtle.gotoZ(dz) then return false end\
17212 end\
17213\
17214 if dy then\
17215 if not turtle.gotoY(dy) then return false end\
17216 end\
17217\
17218 return true\
17219end\
17220\
17221-- fallback goto - will turn around if was previously moving backwards\
17222local function gotoMultiTurn(dx, dy, dz)\
17223 if gotoEx(dx, dy, dz) then\
17224 return true\
17225 end\
17226\
17227 local moved\
17228 repeat\
17229 local x, y, z = turtle.point.x, turtle.point.y, turtle.point.z\
17230\
17231 -- try going the other way\
17232 if (turtle.point.heading % 2) == 1 then\
17233 turtle.headTowardsX(dx)\
17234 else\
17235 turtle.headTowardsZ(dz)\
17236 end\
17237\
17238 if gotoEx(dx, dy, dz) then\
17239 return true\
17240 end\
17241\
17242 if dy then\
17243 turtle.gotoY(dy)\
17244 end\
17245\
17246 moved = x ~= turtle.point.x or y ~= turtle.point.y or z ~= turtle.point.z\
17247 until not moved\
17248\
17249 return false\
17250end\
17251\
17252-- go backwards - turning around if necessary to fight mobs / break blocks\
17253function turtle.goback()\
17254 local hi = headings[turtle.point.heading]\
17255 return turtle._goto({\
17256 x = turtle.point.x - hi.xd,\
17257 y = turtle.point.y,\
17258 z = turtle.point.z - hi.zd,\
17259 heading = turtle.point.heading,\
17260 })\
17261end\
17262\
17263function turtle.gotoYfirst(pt)\
17264 if turtle._gotoY(pt.y) then\
17265 if turtle._goto(pt) then\
17266 turtle.setHeading(pt.heading)\
17267 return true\
17268 end\
17269 end\
17270end\
17271\
17272function turtle.gotoYlast(pt)\
17273 if turtle._goto({ x = pt.x, z = pt.z, heading = pt.heading }) then\
17274 if turtle.gotoY(pt.y) then\
17275 turtle.setHeading(pt.heading)\
17276 return true\
17277 end\
17278 end\
17279end\
17280\
17281function turtle._goto(pt)\
17282 local dx, dy, dz, dh = pt.x, pt.y, pt.z, pt.heading\
17283 if not turtle.gotoSingleTurn(dx, dy, dz, dh) then\
17284 if not gotoMultiTurn(dx, dy, dz) then\
17285 return false, 'Failed to reach location'\
17286 end\
17287 end\
17288 turtle.setHeading(dh)\
17289 return pt\
17290end\
17291\
17292-- avoid lint errors\
17293turtle['goto'] = turtle._goto\
17294\
17295function turtle.gotoX(dx)\
17296 turtle.headTowardsX(dx)\
17297\
17298 while turtle.point.x ~= dx do\
17299 if not turtle.forward() then\
17300 return false\
17301 end\
17302 end\
17303 return true\
17304end\
17305\
17306function turtle.gotoZ(dz)\
17307 turtle.headTowardsZ(dz)\
17308\
17309 while turtle.point.z ~= dz do\
17310 if not turtle.forward() then\
17311 return false\
17312 end\
17313 end\
17314 return true\
17315end\
17316\
17317function turtle.gotoY(dy)\
17318 while turtle.point.y > dy do\
17319 if not turtle.down() then\
17320 return false\
17321 end\
17322 end\
17323\
17324 while turtle.point.y < dy do\
17325 if not turtle.up() then\
17326 return false\
17327 end\
17328 end\
17329 return true\
17330end\
17331\
17332-- [[ Slot management ]] --\
17333function turtle.getSlot(indexOrId, slots)\
17334 if type(indexOrId) == 'string' then\
17335 slots = slots or turtle.getInventory()\
17336 local _,c = string.gsub(indexOrId, ':', '')\
17337 if c == 2 then -- combined id and dmg .. ie. minecraft:coal:0\
17338 return Util.find(slots, 'iddmg', indexOrId)\
17339 end\
17340 return Util.find(slots, 'id', indexOrId)\
17341 end\
17342\
17343 local detail = turtle.getItemDetail(indexOrId)\
17344 if detail then\
17345 return {\
17346 name = detail.name,\
17347 damage = detail.damage,\
17348 count = detail.count,\
17349 key = detail.name .. ':' .. detail.damage,\
17350\
17351 index = indexOrId,\
17352\
17353 -- deprecate\
17354 qty = detail.count,\
17355 dmg = detail.damage,\
17356 id = detail.name,\
17357 iddmg = detail.name .. ':' .. detail.damage,\
17358 }\
17359 end\
17360\
17361 -- inconsistent return value\
17362 -- null is returned if indexOrId is a string and no item is present\
17363 return {\
17364 qty = 0, -- deprecate\
17365 count = 0,\
17366 index = indexOrId,\
17367 }\
17368end\
17369\
17370function turtle.select(indexOrId)\
17371 if type(indexOrId) == 'number' then\
17372 return turtle.native.select(indexOrId)\
17373 end\
17374\
17375 local s = turtle.getSlot(indexOrId)\
17376 if s then\
17377 turtle.native.select(s.index)\
17378 return s\
17379 end\
17380\
17381 return false, 'Inventory does not contain item'\
17382end\
17383\
17384function turtle.getInventory(slots)\
17385 slots = slots or { }\
17386 for i = 1, 16 do\
17387 slots[i] = turtle.getSlot(i)\
17388 end\
17389 return slots\
17390end\
17391\
17392function turtle.getSummedInventory()\
17393 local slots = turtle.getFilledSlots()\
17394 local t = { }\
17395 for _,slot in pairs(slots) do\
17396 local entry = t[slot.iddmg]\
17397 if not entry then\
17398 entry = {\
17399 count = 0,\
17400 damage = slot.damage,\
17401 name = slot.name,\
17402 key = slot.key,\
17403\
17404 -- deprecate\
17405 qty = 0,\
17406 dmg = slot.dmg,\
17407 id = slot.id,\
17408 iddmg = slot.iddmg,\
17409 }\
17410 t[slot.iddmg] = entry\
17411 end\
17412 entry.qty = entry.qty + slot.qty\
17413 entry.count = entry.qty\
17414 end\
17415 return t\
17416end\
17417\
17418function turtle.has(item, count)\
17419 if item:match('.*:%d') then\
17420 local slot = turtle.getSummedInventory()[item]\
17421 return slot and slot.count >= (count or 1)\
17422 end\
17423 local slot = turtle.getSlot(item)\
17424 return slot and slot.count > 0\
17425end\
17426\
17427function turtle.getFilledSlots(startSlot)\
17428 startSlot = startSlot or 1\
17429\
17430 local slots = { }\
17431 for i = startSlot, 16 do\
17432 local count = turtle.getItemCount(i)\
17433 if count > 0 then\
17434 slots[i] = turtle.getSlot(i)\
17435 end\
17436 end\
17437 return slots\
17438end\
17439\
17440function turtle.eachFilledSlot(fn)\
17441 local slots = turtle.getFilledSlots()\
17442 for _,slot in pairs(slots) do\
17443 fn(slot)\
17444 end\
17445end\
17446\
17447function turtle.emptyInventory(dropAction)\
17448 dropAction = dropAction or turtle.native.drop\
17449 turtle.eachFilledSlot(function(slot)\
17450 turtle.select(slot.index)\
17451 dropAction()\
17452 end)\
17453 turtle.select(1)\
17454end\
17455\
17456function turtle.reconcileInventory(slots, dropAction)\
17457 dropAction = dropAction or turtle.native.drop\
17458 for _,s in pairs(slots) do\
17459 local qty = turtle.getItemCount(s.index)\
17460 if qty > s.qty then\
17461 turtle.select(s.index)\
17462 dropAction(qty-s.qty, s)\
17463 end\
17464 end\
17465end\
17466\
17467function turtle.selectSlotWithItems(startSlot)\
17468 startSlot = startSlot or 1\
17469 for i = startSlot, 16 do\
17470 if turtle.getItemCount(i) > 0 then\
17471 turtle.select(i)\
17472 return i\
17473 end\
17474 end\
17475end\
17476\
17477function turtle.selectSlotWithQuantity(qty, startSlot)\
17478 startSlot = startSlot or 1\
17479\
17480 for i = startSlot, 16 do\
17481 if turtle.getItemCount(i) == qty then\
17482 turtle.select(i)\
17483 return i\
17484 end\
17485 end\
17486end\
17487\
17488function turtle.selectOpenSlot(startSlot)\
17489 return turtle.selectSlotWithQuantity(0, startSlot)\
17490end\
17491\
17492function turtle.condense()\
17493 local slots = turtle.getInventory()\
17494\
17495 for i = 16, 1, -1 do\
17496 if slots[i].count > 0 then\
17497 for j = 1, i - 1 do\
17498 if slots[j].count == 0 or slots[i].key == slots[j].key then\
17499 turtle.select(i)\
17500 turtle.transferTo(j, 64)\
17501 local transferred = slots[i].qty - turtle.getItemCount(i)\
17502 slots[j].count = slots[j].count + transferred\
17503 slots[i].count = slots[i].count - transferred\
17504 slots[j].key = slots[i].key\
17505 if slots[i].count == 0 then\
17506 break\
17507 end\
17508 end\
17509 end\
17510 end\
17511 end\
17512 return true\
17513end\
17514\
17515function turtle.getItemCount(idOrName)\
17516 if type(idOrName) == 'number' then\
17517 return turtle.native.getItemCount(idOrName)\
17518 end\
17519 local slots = turtle.getFilledSlots()\
17520 local count = 0\
17521 for _,slot in pairs(slots) do\
17522 if slot.iddmg == idOrName or slot.name == idOrName then\
17523 count = count + slot.qty\
17524 end\
17525 end\
17526 return count\
17527end\
17528\
17529function turtle.equip(side, item)\
17530 if item then\
17531 if not turtle.select(item) then\
17532 return false, 'Unable to equip ' .. item\
17533 end\
17534 end\
17535\
17536 if side == 'left' then\
17537 return turtle.equipLeft()\
17538 end\
17539 return turtle.equipRight()\
17540end\
17541\
17542function turtle.isEquipped(item)\
17543 if peripheral.getType('left') == item then\
17544 return 'left'\
17545 elseif peripheral.getType('right') == item then\
17546 return 'right'\
17547 end\
17548end\
17549\
17550-- [[ ]] --\
17551function turtle.run(fn, ...)\
17552 local args = { ... }\
17553 local s, m\
17554\
17555 if type(fn) == 'string' then\
17556 fn = turtle[fn]\
17557 end\
17558\
17559 synchronized(turtle, function()\
17560 turtle.resetState()\
17561 s, m = pcall(function() fn(unpack(args)) end)\
17562 turtle.resetState()\
17563 if not s and m then\
17564 _G.printError(m)\
17565 end\
17566 end)\
17567\
17568 return s, m\
17569end\
17570\
17571function turtle.abort(abort)\
17572 state.abort = abort\
17573 if abort then\
17574 os.queueEvent('turtle_abort')\
17575 end\
17576end\
17577\
17578-- [[ Pathing ]] --\
17579function turtle.setPersistent(isPersistent)\
17580 if isPersistent then\
17581 Pathing.setBlocks({ })\
17582 else\
17583 Pathing.setBlocks()\
17584 end\
17585end\
17586\
17587function turtle.setPathingBox(box)\
17588 Pathing.setBox(box)\
17589end\
17590\
17591function turtle.addWorldBlock(pt)\
17592 Pathing.addBlock(pt)\
17593end\
17594\
17595local movementStrategy = turtle.pathfind\
17596\
17597function turtle.setMovementStrategy(strategy)\
17598 if strategy == 'pathing' then\
17599 movementStrategy = turtle.pathfind\
17600 elseif strategy == 'goto' then\
17601 movementStrategy = turtle._goto\
17602 else\
17603 error('Invalid movement strategy')\
17604 end\
17605end\
17606\
17607function turtle.faceAgainst(pt, options) -- 4 sided\
17608 options = options or { }\
17609 options.dest = { }\
17610\
17611 for i = 0, 3 do\
17612 local hi = Point.facings[i]\
17613 table.insert(options.dest, {\
17614 x = pt.x + hi.xd,\
17615 z = pt.z + hi.zd,\
17616 y = pt.y + hi.yd,\
17617 heading = (hi.heading + 2) % 4,\
17618 })\
17619 end\
17620\
17621 return movementStrategy(Point.closest(turtle.point, options.dest), options)\
17622end\
17623\
17624-- move against this point\
17625-- if the point does not contain a heading, then the turtle\
17626-- will face the block (if on same plane)\
17627-- if above or below, the heading is undetermined unless specified\
17628function turtle.moveAgainst(pt, options) -- 6 sided\
17629 options = options or { }\
17630 options.dest = { }\
17631\
17632 for i = 0, 5 do\
17633 local hi = turtle.getHeadingInfo(i)\
17634 local heading, direction\
17635 if i < 4 then\
17636 heading = (hi.heading + 2) % 4\
17637 direction = 'forward'\
17638 elseif i == 4 then\
17639 direction = 'down'\
17640 elseif i == 5 then\
17641 direction = 'up'\
17642 end\
17643\
17644 table.insert(options.dest, {\
17645 x = pt.x + hi.xd,\
17646 z = pt.z + hi.zd,\
17647 y = pt.y + hi.yd,\
17648 direction = direction,\
17649 heading = pt.heading or heading,\
17650 })\
17651 end\
17652\
17653 return movementStrategy(Point.closest(turtle.point, options.dest), options)\
17654end\
17655\
17656local actionsAt = {\
17657 detect = {\
17658 up = turtle.detectUp,\
17659 down = turtle.detectDown,\
17660 forward = turtle.detect,\
17661 },\
17662 dig = {\
17663 up = turtle.digUp,\
17664 down = turtle.digDown,\
17665 forward = turtle.dig,\
17666 },\
17667 move = {\
17668 up = turtle.moveUp,\
17669 down = turtle.moveDown,\
17670 forward = turtle.move,\
17671 },\
17672 attack = {\
17673 up = turtle.attackUp,\
17674 down = turtle.attackDown,\
17675 forward = turtle.attack,\
17676 },\
17677 place = {\
17678 up = turtle.placeUp,\
17679 down = turtle.placeDown,\
17680 forward = turtle.place,\
17681 },\
17682 drop = {\
17683 up = turtle.dropUp,\
17684 down = turtle.dropDown,\
17685 forward = turtle.drop,\
17686 },\
17687 suck = {\
17688 up = turtle.suckUp,\
17689 down = turtle.suckDown,\
17690 forward = turtle.suck,\
17691 },\
17692 compare = {\
17693 up = turtle.compareUp,\
17694 down = turtle.compareDown,\
17695 forward = turtle.compare,\
17696 },\
17697 inspect = {\
17698 up = turtle.inspectUp,\
17699 down = turtle.inspectDown,\
17700 forward = turtle.inspect,\
17701 },\
17702}\
17703\
17704-- pt = { x,y,z,heading,direction }\
17705-- direction should only be up or down if provided\
17706-- heading can be provided to tell which way to face during action\
17707-- ex: place a block at the point from above facing east\
17708local function _actionAt(action, pt, ...)\
17709 if not pt.heading and not pt.direction then\
17710 local msg\
17711 pt, msg = turtle.moveAgainst(pt)\
17712 if pt then\
17713 return action[pt.direction](...)\
17714 end\
17715 return pt, msg\
17716 end\
17717\
17718 local reversed =\
17719 { [0] = 2, [1] = 3, [2] = 0, [3] = 1, [4] = 5, [5] = 4, }\
17720 local dir = reversed[headings[pt.direction or pt.heading].heading]\
17721 local apt = { x = pt.x + headings[dir].xd,\
17722 y = pt.y + headings[dir].yd,\
17723 z = pt.z + headings[dir].zd, }\
17724 local direction\
17725\
17726 -- ex: place a block at this point, from above, facing east\
17727 if dir < 4 then\
17728 apt.heading = (dir + 2) % 4\
17729 direction = 'forward'\
17730 elseif dir == 4 then\
17731 apt.heading = pt.heading\
17732 direction = 'down'\
17733 elseif dir == 5 then\
17734 apt.heading = pt.heading\
17735 direction = 'up'\
17736 end\
17737\
17738 if movementStrategy(apt) then\
17739 return action[direction](...)\
17740 end\
17741end\
17742\
17743local function _actionDownAt(action, pt, ...)\
17744 pt = Util.shallowCopy(pt)\
17745 pt.direction = Point.DOWN\
17746 return _actionAt(action, pt, ...)\
17747end\
17748\
17749local function _actionUpAt(action, pt, ...)\
17750 pt = Util.shallowCopy(pt)\
17751 pt.direction = Point.UP\
17752 return _actionAt(action, pt, ...)\
17753end\
17754\
17755local function _actionForwardAt(action, pt, ...)\
17756 if turtle.faceAgainst(pt) then\
17757 return action.forward(...)\
17758 end\
17759end\
17760\
17761function turtle.detectAt(pt) return _actionAt(actionsAt.detect, pt) end\
17762function turtle.detectDownAt(pt) return _actionDownAt(actionsAt.detect, pt) end\
17763function turtle.detectForwardAt(pt) return _actionForwardAt(actionsAt.detect, pt) end\
17764function turtle.detectUpAt(pt) return _actionUpAt(actionsAt.detect, pt) end\
17765\
17766function turtle.digAt(pt) return _actionAt(actionsAt.dig, pt) end\
17767function turtle.digDownAt(pt) return _actionDownAt(actionsAt.dig, pt) end\
17768function turtle.digForwardAt(pt) return _actionForwardAt(actionsAt.dig, pt) end\
17769function turtle.digUpAt(pt) return _actionUpAt(actionsAt.dig, pt) end\
17770\
17771function turtle.attackAt(pt) return _actionAt(actionsAt.attack, pt) end\
17772function turtle.attackDownAt(pt) return _actionDownAt(actionsAt.attack, pt) end\
17773function turtle.attackForwardAt(pt) return _actionForwardAt(actionsAt.attack, pt) end\
17774function turtle.attackUpAt(pt) return _actionUpAt(actionsAt.attack, pt) end\
17775\
17776function turtle.placeAt(pt, arg, dir) return _actionAt(actionsAt.place, pt, arg, dir) end\
17777function turtle.placeDownAt(pt, arg) return _actionDownAt(actionsAt.place, pt, arg) end\
17778function turtle.placeForwardAt(pt, arg) return _actionForwardAt(actionsAt.place, pt, arg) end\
17779function turtle.placeUpAt(pt, arg) return _actionUpAt(actionsAt.place, pt, arg) end\
17780\
17781function turtle.dropAt(pt, ...) return _actionAt(actionsAt.drop, pt, ...) end\
17782function turtle.dropDownAt(pt, ...) return _actionDownAt(actionsAt.drop, pt, ...) end\
17783function turtle.dropForwardAt(pt, ...) return _actionForwardAt(actionsAt.drop, pt, ...) end\
17784function turtle.dropUpAt(pt, ...) return _actionUpAt(actionsAt.drop, pt, ...) end\
17785\
17786function turtle.suckAt(pt, qty) return _actionAt(actionsAt.suck, pt, qty or 64) end\
17787function turtle.suckDownAt(pt, qty) return _actionDownAt(actionsAt.suck, pt, qty or 64) end\
17788function turtle.suckForwardAt(pt, qty) return _actionForwardAt(actionsAt.suck, pt, qty or 64) end\
17789function turtle.suckUpAt(pt, qty) return _actionUpAt(actionsAt.suck, pt, qty or 64) end\
17790\
17791function turtle.compareAt(pt) return _actionAt(actionsAt.compare, pt) end\
17792function turtle.compareDownAt(pt) return _actionDownAt(actionsAt.compare, pt) end\
17793function turtle.compareForwardAt(pt) return _actionForwardAt(actionsAt.compare, pt) end\
17794function turtle.compareUpAt(pt) return _actionUpAt(actionsAt.compare, pt) end\
17795\
17796function turtle.inspectAt(pt) return _actionAt(actionsAt.inspect, pt) end\
17797function turtle.inspectDownAt(pt) return _actionDownAt(actionsAt.inspect, pt) end\
17798function turtle.inspectForwardAt(pt) return _actionForwardAt(actionsAt.inspect, pt) end\
17799function turtle.inspectUpAt(pt) return _actionUpAt(actionsAt.inspect, pt) end\
17800\
17801-- [[ GPS ]] --\
17802function turtle.enableGPS(timeout)\
17803 local pt = GPS.getPointAndHeading(timeout)\
17804 if pt then\
17805 turtle.setPoint(pt, true)\
17806 return turtle.point\
17807 end\
17808end\
17809\
17810function turtle.addFeatures(...)\
17811 for _,feature in pairs({ ... }) do\
17812 require('turtle.' .. feature)\
17813 end\
17814end",
17815 [ "4.label.lua" ] = "local os = _G.os\
17816\
17817-- Default label\
17818if not os.getComputerLabel() then\
17819 local id = os.getComputerID()\
17820 if _G.turtle then\
17821 os.setComputerLabel('turtle_' .. id)\
17822 elseif _G.pocket then\
17823 os.setComputerLabel('pocket_' .. id)\
17824 elseif _G.commands then\
17825 os.setComputerLabel('command_' .. id)\
17826 else\
17827 os.setComputerLabel('computer_' .. id)\
17828 end\
17829end",
17830 [ "1.device.lua" ] = "_G.requireInjector(_ENV)\
17831\
17832local Peripheral = require('peripheral')\
17833\
17834_G.device = Peripheral.getList()\
17835\
17836_G.device.terminal = _G.kernel.terminal\
17837_G.device.terminal.side = 'terminal'\
17838_G.device.terminal.type = 'terminal'\
17839_G.device.terminal.name = 'terminal'\
17840\
17841_G.device.keyboard = {\
17842 side = 'keyboard',\
17843 type = 'keyboard',\
17844 name = 'keyboard',\
17845 hotkeys = { },\
17846 state = { },\
17847}\
17848\
17849_G.device.mouse = {\
17850 side = 'mouse',\
17851 type = 'mouse',\
17852 name = 'mouse',\
17853 state = { },\
17854}\
17855\
17856local Input = require('input')\
17857local Util = require('util')\
17858\
17859local device = _G.device\
17860local kernel = _G.kernel\
17861local keyboard = _G.device.keyboard\
17862local mouse = _G.device.mouse\
17863local os = _G.os\
17864\
17865local drivers = { }\
17866\
17867kernel.hook('peripheral', function(_, eventData)\
17868 local side = eventData[1]\
17869 if side then\
17870 local dev = Peripheral.addDevice(device, side)\
17871 if dev then\
17872 if drivers[dev.type] then\
17873 local e = drivers[dev.type](dev)\
17874 if type(e) == 'table' then\
17875 for _, v in pairs(e) do\
17876 os.queueEvent('device_attach', v.name)\
17877 end\
17878 elseif e then\
17879 os.queueEvent('device_attach', e.name)\
17880 end\
17881 end\
17882\
17883 os.queueEvent('device_attach', dev.name, dev)\
17884 end\
17885 end\
17886end)\
17887\
17888kernel.hook('peripheral_detach', function(_, eventData)\
17889 local side = eventData[1]\
17890 if side then\
17891 local dev = Util.find(device, 'side', side)\
17892 if dev then\
17893 os.queueEvent('device_detach', dev.name, dev)\
17894 if dev._children then\
17895 for _,v in pairs(dev._children) do\
17896 os.queueEvent('peripheral_detach', v.name)\
17897 end\
17898 end\
17899 device[dev.name] = nil\
17900 end\
17901 end\
17902end)\
17903\
17904kernel.hook({ 'key', 'key_up', 'char', 'paste' }, function(event, eventData)\
17905 local code = eventData[1]\
17906\
17907 -- maintain global keyboard state\
17908 if event == 'key' then\
17909 keyboard.state[code] = true\
17910 elseif event == 'key_up' then\
17911 if not keyboard.state[code] then\
17912 return true -- ensure key ups are only generated if a key down was sent\
17913 end\
17914 keyboard.state[code] = nil\
17915 end\
17916\
17917 -- and fire hotkeys\
17918 local hotkey = Input:translate(event, eventData[1], eventData[2])\
17919\
17920 if hotkey and keyboard.hotkeys[hotkey.code] then\
17921 keyboard.hotkeys[hotkey.code](event, eventData)\
17922 end\
17923end)\
17924\
17925kernel.hook({ 'mouse_click', 'mouse_up', 'mouse_drag' }, function(event, eventData)\
17926 local button = eventData[1]\
17927 if event == 'mouse_click' then\
17928 mouse.state[button] = true\
17929 else\
17930 if not mouse.state[button] then\
17931 return true -- ensure mouse ups are only generated if a mouse down was sent\
17932 end\
17933 if event == 'mouse_up' then\
17934 mouse.state[button] = nil\
17935 end\
17936 end\
17937end)\
17938\
17939kernel.hook('kernel_focus', function()\
17940 Util.clear(keyboard.state)\
17941 Util.clear(mouse.state)\
17942end)\
17943\
17944function keyboard.addHotkey(code, fn)\
17945 keyboard.hotkeys[code] = fn\
17946end\
17947\
17948function keyboard.removeHotkey(code)\
17949 keyboard.hotkeys[code] = nil\
17950end\
17951\
17952kernel.hook('monitor_touch', function(event, eventData)\
17953 local monitor = Peripheral.getBySide(eventData[1])\
17954 if monitor and monitor.eventChannel then\
17955 monitor.eventChannel(event, table.unpack(eventData))\
17956 return true -- stop propagation\
17957 end\
17958end)\
17959\
17960local function createDevice(name, devType, method, manipulator)\
17961 local dev = {\
17962 name = name,\
17963 side = name,\
17964 type = devType,\
17965 }\
17966 local methods = {\
17967 'drop', 'getDocs', 'getItem', 'getItemMeta', 'getTransferLocations',\
17968 'list', 'pullItems', 'pushItems', 'size', 'suck',\
17969 }\
17970 if manipulator[method] then\
17971 for _,k in pairs(methods) do\
17972 dev[k] = function(...)\
17973 return manipulator[method]()[k](...)\
17974 end\
17975 end\
17976 if not manipulator._children then\
17977 manipulator._children = { dev }\
17978 else\
17979 table.insert(manipulator._children, dev)\
17980 end\
17981 device[name] = dev\
17982 end\
17983end\
17984\
17985drivers['manipulator'] = function(dev)\
17986 if dev.getName then\
17987 local name\
17988 pcall(function()\
17989 name = dev.getName()\
17990 end)\
17991 if name then\
17992 if dev.getInventory then\
17993 createDevice(name .. ':inventory', 'inventory', 'getInventory', dev)\
17994 end\
17995 if dev.getEquipment then\
17996 createDevice(name .. ':equipment', 'equipment', 'getEquipment', dev)\
17997 end\
17998 if dev.getEnder then\
17999 createDevice(name .. ':enderChest', 'enderChest', 'getEnder', dev)\
18000 end\
18001\
18002 return dev._children\
18003 end\
18004 end\
18005end\
18006\
18007-- initialize drivers\
18008for _,v in pairs(device) do\
18009 if drivers[v.type] then\
18010 local s, m = pcall(drivers[v.type], v)\
18011 if not s and m then\
18012 _G.printError(m)\
18013 end\
18014 end\
18015end",
18016 [ "4.user.lua" ] = "_G.requireInjector(_ENV)\
18017\
18018local Util = require('util')\
18019\
18020local fs = _G.fs\
18021local shell = _ENV.shell\
18022\
18023if not fs.exists('/sys/os/opus/usr/apps') then\
18024 fs.makeDir('/sys/os/opus/usr/apps')\
18025end\
18026if not fs.exists('/sys/os/opus/usr/autorun') then\
18027 fs.makeDir('/sys/os/opus/usr/autorun')\
18028end\
18029--if not fs.exists('/sys/os/opus/usr/config/fstab') then\
18030-- Util.writeFile('/sys/os/opus/usr/config/fstab',\
18031-- 'usr gitfs kepler155c/opus-apps/' .. _G.OPUS_BRANCH)\
18032--end\
18033\
18034if not fs.exists('/sys/os/opus/usr/config/shell') then\
18035 Util.writeTable('/sys/os/opus/usr/config/shell', {\
18036 aliases = shell.aliases(),\
18037 path = '/sys/os/opus/usr/apps:sys/os/opus/sys/apps:' .. shell.path(),\
18038 lua_path = 'sys/os/opus/sys/apis:/sys/os/opus/usr/apis',\
18039 })\
18040end\
18041\
18042if not fs.exists('/sys/os/opus/usr/config/packages') then\
18043 local packages = {\
18044 [ 'develop-1.8' ] = 'https://pastebin.com/raw/WhEiNGZE',\
18045 [ 'master-1.8' ] = 'https://pastebin.com/raw/pexZpAxt',\
18046 }\
18047\
18048 if packages[_G.OPUS_BRANCH] then\
18049 Util.download(packages[_G.OPUS_BRANCH], '/sys/os/opus/usr/config/packages')\
18050 end\
18051end\
18052\
18053local config = Util.readTable('/sys/os/opus/usr/config/shell')\
18054if config.aliases then\
18055 for k in pairs(shell.aliases()) do\
18056 shell.clearAlias(k)\
18057 end\
18058 for k,v in pairs(config.aliases) do\
18059 shell.setAlias(k, v)\
18060 end\
18061end\
18062shell.setPath(config.path)\
18063_G.LUA_PATH = config.lua_path\
18064\
18065fs.loadTab('/sys/os/opus/usr/config/fstab')",
18066 [ "5.network.lua" ] = "_G.requireInjector(_ENV)\
18067\
18068local Config = require('config')\
18069\
18070local device = _G.device\
18071local kernel = _G.kernel\
18072local os = _G.os\
18073\
18074_G.network = { }\
18075\
18076local function startNetwork()\
18077 kernel.run({\
18078 title = 'Net daemon',\
18079 path = 'sys/os/opus/sys/apps/netdaemon.lua',\
18080 hidden = true,\
18081 })\
18082end\
18083\
18084local function setModem(dev)\
18085 if not device.wireless_modem and dev.isWireless() then\
18086 local config = Config.load('os', { })\
18087 if not config.wirelessModem or dev.name == config.wirelessModem then\
18088 device.wireless_modem = dev\
18089 os.queueEvent('device_attach', 'wireless_modem')\
18090 return dev\
18091 end\
18092 end\
18093end\
18094\
18095-- create a psuedo-device named 'wireleess_modem'\
18096kernel.hook('device_attach', function(_, eventData)\
18097 local dev = device[eventData[1]]\
18098 if dev and dev.type == 'modem' then\
18099 if setModem(dev) then\
18100 startNetwork()\
18101 end\
18102 end\
18103end)\
18104\
18105kernel.hook('device_detach', function(_, eventData)\
18106 if device.wireless_modem and eventData[1] == device.wireless_modem.name then\
18107 device['wireless_modem'] = nil\
18108 os.queueEvent('device_detach', 'wireless_modem')\
18109 end\
18110end)\
18111\
18112for _,dev in pairs(device) do\
18113 if dev.type == 'modem' then\
18114 if setModem(dev) then\
18115 break\
18116 end\
18117 end\
18118end\
18119\
18120if device.wireless_modem then\
18121 print('waiting for network...')\
18122 startNetwork()\
18123 os.pullEvent('network_up')\
18124end",
18125 [ "6.packages.lua" ] = "_G.requireInjector(_ENV)\
18126\
18127local Packages = require('packages')\
18128local Util = require('util')\
18129\
18130local shell = _ENV.shell\
18131local fs = _G.fs\
18132\
18133local appPaths = Util.split(shell.path(), '(.-):')\
18134local luaPaths = Util.split(_G.LUA_PATH, '(.-):')\
18135\
18136local function addPath(t, e)\
18137 local function hasEntry()\
18138 for _,v in ipairs(t) do\
18139 if v == e then\
18140 return true\
18141 end\
18142 end\
18143 end\
18144 if not hasEntry() then\
18145 table.insert(t, 1, e)\
18146 end\
18147end\
18148\
18149-- dependency graph\
18150-- https://github.com/mpeterv/depgraph/blob/master/src/depgraph/init.lua\
18151\
18152for name in pairs(Packages:installed()) do\
18153 local packageDir = fs.combine('packages', name)\
18154 if fs.exists(fs.combine(packageDir, '.install')) then\
18155 local install = Util.readTable(fs.combine(packageDir, '.install'))\
18156 if install and install.mount then\
18157 fs.mount(table.unpack(Util.matches(install.mount)))\
18158 end\
18159 end\
18160\
18161 addPath(appPaths, packageDir)\
18162 local apiPath = fs.combine(fs.combine('packages', name), 'apis')\
18163 if fs.exists(apiPath) then\
18164 addPath(luaPaths, apiPath)\
18165 end\
18166end\
18167\
18168shell.setPath(table.concat(appPaths, ':'))\
18169_G.LUA_PATH = table.concat(luaPaths, ':')",
18170 [ "2.vfs.lua" ] = "if fs.native then\
18171 return\
18172end\
18173\
18174_G.requireInjector(_ENV)\
18175local Util = require('util')\
18176\
18177local fs = _G.fs\
18178\
18179fs.native = Util.shallowCopy(fs)\
18180\
18181local fstypes = { }\
18182local nativefs = { }\
18183\
18184for k,fn in pairs(fs) do\
18185 if type(fn) == 'function' then\
18186 nativefs[k] = function(node, ...)\
18187 return fn(...)\
18188 end\
18189 end\
18190end\
18191\
18192function nativefs.list(node, dir)\
18193\
18194 local files\
18195 if fs.native.isDir(dir) then\
18196 files = fs.native.list(dir)\
18197 end\
18198\
18199 local function inList(l, e)\
18200 for _,v in ipairs(l) do\
18201 if v == e then\
18202 return true\
18203 end\
18204 end\
18205 end\
18206\
18207 if dir == node.mountPoint and node.nodes then\
18208 files = files or { }\
18209 for k in pairs(node.nodes) do\
18210 if not inList(files, k) then\
18211 table.insert(files, k)\
18212 end\
18213 end\
18214 end\
18215\
18216 if not files then\
18217 error('Not a directory', 2)\
18218 end\
18219\
18220 return files\
18221end\
18222\
18223function nativefs.getSize(node, dir, recursive)\
18224 if recursive and fs.native.isDir(dir) then\
18225 local function sum(dir)\
18226 local total = 0\
18227 local files = fs.native.list(dir)\
18228 for _,f in ipairs(files) do\
18229 local fullName = fs.combine(dir, f)\
18230 if fs.native.isDir(fullName) then\
18231 total = total + sum(fullName)\
18232 else\
18233 total = total + fs.native.getSize(fullName)\
18234 end\
18235 end\
18236 return total\
18237 end\
18238 return sum(dir)\
18239 end\
18240 if node.mountPoint == dir and node.nodes then\
18241 return 0\
18242 end\
18243 return fs.native.getSize(dir)\
18244end\
18245\
18246function nativefs.isDir(node, dir)\
18247 if node.mountPoint == dir then\
18248 return not not node.nodes\
18249 end\
18250 return fs.native.isDir(dir)\
18251end\
18252\
18253function nativefs.exists(node, dir)\
18254 if node.mountPoint == dir then\
18255 return true\
18256 end\
18257 return fs.native.exists(dir)\
18258end\
18259\
18260function nativefs.delete(node, dir)\
18261 if node.mountPoint == dir then\
18262 fs.unmount(dir)\
18263 else\
18264 fs.native.delete(dir)\
18265 end\
18266end\
18267\
18268fstypes.nativefs = nativefs\
18269fs.nodes = {\
18270 fs = nativefs,\
18271 mountPoint = '',\
18272 fstype = 'nativefs',\
18273 nodes = { },\
18274}\
18275\
18276local function splitpath(path)\
18277 local parts = { }\
18278 for match in string.gmatch(path, \"[^/]+\") do\
18279 table.insert(parts, match)\
18280 end\
18281 return parts\
18282end\
18283\
18284local function getNode(dir)\
18285 local cd = fs.combine(dir, '')\
18286 local parts = splitpath(cd)\
18287 local node = fs.nodes\
18288\
18289 for _,d in ipairs(parts) do\
18290 if node.nodes and node.nodes[d] then\
18291 node = node.nodes[d]\
18292 else\
18293 break\
18294 end\
18295 end\
18296\
18297 return node\
18298end\
18299\
18300local methods = { 'delete', 'getFreeSpace', 'exists', 'isDir', 'getSize',\
18301 'isReadOnly', 'makeDir', 'getDrive', 'list', 'open' }\
18302\
18303for _,m in pairs(methods) do\
18304 fs[m] = function(dir, ...)\
18305 dir = fs.combine(dir or '', '')\
18306 local node = getNode(dir)\
18307 return node.fs[m](node, dir, ...)\
18308 end\
18309end\
18310\
18311function fs.complete(partial, dir, includeFiles, includeSlash)\
18312 dir = fs.combine(dir, '')\
18313 local node = getNode(dir)\
18314 if node.fs.complete then\
18315 return node.fs.complete(node, partial, dir, includeFiles, includeSlash)\
18316 end\
18317 return fs.native.complete(partial, dir, includeFiles, includeSlash)\
18318end\
18319\
18320function fs.listEx(dir)\
18321 dir = fs.combine(dir, '')\
18322 local node = getNode(dir)\
18323 if node.fs.listEx then\
18324 return node.fs.listEx(node, dir)\
18325 end\
18326\
18327 local t = { }\
18328 local files = node.fs.list(node, dir)\
18329\
18330 pcall(function()\
18331 for _,f in ipairs(files) do\
18332 local fullName = fs.combine(dir, f)\
18333 local file = {\
18334 name = f,\
18335 isDir = fs.isDir(fullName),\
18336 isReadOnly = fs.isReadOnly(fullName),\
18337 }\
18338 if not file.isDir then\
18339 file.size = fs.getSize(fullName)\
18340 end\
18341 table.insert(t, file)\
18342 end\
18343 end)\
18344 return t\
18345end\
18346\
18347function fs.copy(s, t)\
18348 local sp = getNode(s)\
18349 local tp = getNode(t)\
18350 if sp == tp and sp.fs.copy then\
18351 return sp.fs.copy(sp, s, t)\
18352 end\
18353\
18354 if fs.exists(t) then\
18355 error('File exists')\
18356 end\
18357\
18358 if fs.isDir(s) then\
18359 fs.makeDir(t)\
18360 local list = fs.list(s)\
18361 for _,f in ipairs(list) do\
18362 fs.copy(fs.combine(s, f), fs.combine(t, f))\
18363 end\
18364\
18365 else\
18366 local sf = Util.readFile(s)\
18367 if not sf then\
18368 error('No such file')\
18369 end\
18370\
18371 Util.writeFile(t, sf)\
18372 end\
18373end\
18374\
18375function fs.find(spec) -- not optimized\
18376-- local node = getNode(spec)\
18377-- local files = node.fs.find(node, spec)\
18378 local files = { }\
18379 -- method from https://github.com/N70/deltaOS/blob/dev/vfs\
18380 local function recurse_spec(results, path, spec)\
18381 local segment = spec:match('([^/]*)'):gsub('/', '')\
18382 local pattern = '^' .. segment:gsub(\"[%.%[%]%(%)%%%+%-%?%^%$]\",\"%%%1\"):gsub(\"%z\",\"%%z\"):gsub(\"%*\",\"[^/]-\") .. '$'\
18383 if fs.isDir(path) then\
18384 for _, file in ipairs(fs.list(path)) do\
18385 if file:match(pattern) then\
18386 local f = fs.combine(path, file)\
18387 if spec == segment then\
18388 table.insert(results, f)\
18389 end\
18390 if fs.isDir(f) then\
18391 recurse_spec(results, f, spec:sub(#segment + 2))\
18392 end\
18393 end\
18394 end\
18395 end\
18396 end\
18397 recurse_spec(files, '', spec)\
18398 table.sort(files)\
18399\
18400 return files\
18401end\
18402\
18403function fs.move(s, t)\
18404 local sp = getNode(s)\
18405 local tp = getNode(t)\
18406 if sp == tp and sp.fs.move then\
18407 return sp.fs.move(sp, s, t)\
18408 end\
18409 fs.copy(s, t)\
18410 fs.delete(s)\
18411end\
18412\
18413local function getfstype(fstype)\
18414 local vfs = fstypes[fstype]\
18415 if not vfs then\
18416 vfs = require('fs.' .. fstype)\
18417 fs.registerType(fstype, vfs)\
18418 end\
18419 return vfs\
18420end\
18421\
18422function fs.mount(path, fstype, ...)\
18423\
18424 local vfs = getfstype(fstype)\
18425 if not vfs then\
18426 error('Invalid file system type')\
18427 end\
18428 local node = vfs.mount(path, ...)\
18429 if node then\
18430 local parts = splitpath(path)\
18431 local targetName = table.remove(parts, #parts)\
18432\
18433 local tp = fs.nodes\
18434 for _,d in ipairs(parts) do\
18435 if not tp.nodes then\
18436 tp.nodes = { }\
18437 end\
18438 if not tp.nodes[d] then\
18439 tp.nodes[d] = Util.shallowCopy(tp)\
18440 tp.nodes[d].nodes = { }\
18441 tp.nodes[d].mountPoint = fs.combine(tp.mountPoint, d)\
18442 end\
18443 tp = tp.nodes[d]\
18444 end\
18445\
18446 node.fs = vfs\
18447 node.fstype = fstype\
18448 if not targetName then\
18449 node.mountPoint = ''\
18450 fs.nodes = node\
18451 else\
18452 node.mountPoint = fs.combine(tp.mountPoint, targetName)\
18453 tp.nodes[targetName] = node\
18454 end\
18455 end\
18456 return node\
18457end\
18458\
18459function fs.loadTab(path)\
18460 local mounts = Util.readFile(path)\
18461 if mounts then\
18462 for _,l in ipairs(Util.split(mounts)) do\
18463 if l:sub(1, 1) ~= '#' then\
18464 local s, m = pcall(function()\
18465 fs.mount(table.unpack(Util.matches(l)))\
18466 end)\
18467 if not s then\
18468 _G.printError('Mount failed')\
18469 _G.printError(l)\
18470 _G.printError(m)\
18471 end\
18472 end\
18473 end\
18474 end\
18475end\
18476\
18477local function getNodeByParts(parts)\
18478 local node = fs.nodes\
18479\
18480 for _,d in ipairs(parts) do\
18481 if not node.nodes[d] then\
18482 return\
18483 end\
18484 node = node.nodes[d]\
18485 end\
18486 return node\
18487end\
18488\
18489function fs.unmount(path)\
18490 local parts = splitpath(path)\
18491 local targetName = table.remove(parts, #parts)\
18492\
18493 local node = getNodeByParts(parts)\
18494\
18495 if node and node.nodes[targetName] then\
18496 node.nodes[targetName] = nil\
18497 end\
18498end\
18499\
18500function fs.registerType(name, fs)\
18501 fstypes[name] = fs\
18502end\
18503\
18504function fs.getTypes()\
18505 return fstypes\
18506end\
18507\
18508function fs.restore()\
18509 local native = fs.native\
18510 Util.clear(fs)\
18511 Util.merge(fs, native)\
18512end",
18513 },
18514 boot = {
18515 [ "tlco.boot" ] = "local pullEvent = os.pullEventRaw\
18516local shutdown = os.shutdown\
18517\
18518os.pullEventRaw = function()\
18519 error('')\
18520end\
18521\
18522os.shutdown = function()\
18523 os.pullEventRaw = pullEvent\
18524 os.shutdown = shutdown\
18525\
18526 os.run(getfenv(1), 'sys/os/opus/sys/boot/opus.boot')\
18527end\
18528\
18529os.queueEvent('modem_message')",
18530 [ "opus.boot" ] = "-- Loads the Opus environment regardless if the file system is local or not\
18531local fs = _G.fs\
18532local http = _G.http\
18533\
18534_G.OPUS_BRANCH = 'master-1.8'\
18535local GIT_REPO = 'kepler155c/opus/' .. _G.OPUS_BRANCH\
18536local BASE = 'https://raw.githubusercontent.com/' .. GIT_REPO\
18537\
18538local sandboxEnv = setmetatable({ }, { __index = _G })\
18539for k,v in pairs(_ENV) do\
18540 sandboxEnv[k] = v\
18541end\
18542\
18543_G._debug = function() end\
18544\
18545local function makeEnv()\
18546 local env = setmetatable({ }, { __index = _G })\
18547 for k,v in pairs(sandboxEnv) do\
18548 env[k] = v\
18549 end\
18550 return env\
18551end\
18552\
18553local function run(file, ...)\
18554 local s, m = loadfile(file, makeEnv())\
18555 if s then\
18556 return s(...)\
18557 end\
18558 error('Error loading ' .. file .. '\\n' .. m)\
18559end\
18560\
18561local function runUrl(file, ...)\
18562 local url = BASE .. '/' .. file\
18563\
18564 local u = http.get(url)\
18565 if u then\
18566 local fn = load(u.readAll(), url, nil, makeEnv())\
18567 u.close()\
18568 if fn then\
18569 return fn(...)\
18570 end\
18571 end\
18572 error('Failed to download ' .. url)\
18573end\
18574\
18575-- Install require shim\
18576if fs.exists('sys/os/opus/sys/apis/injector.lua') then\
18577 _G.requireInjector = run('sys/os/opus/sys/apis/injector.lua')\
18578else\
18579 -- not local, run the file system directly from git\
18580 _G.requireInjector = runUrl('sys/os/opus/sys/apis/injector.lua')\
18581 runUrl('sys/os/opus/sys/extensions/2.vfs.lua')\
18582\
18583 -- install file system\
18584 fs.mount('', 'gitfs', GIT_REPO)\
18585end\
18586\
18587local s, m = pcall(run, 'sys/os/opus/sys/apps/shell', 'sys/os/opus/sys/kernel.lua', ...)\
18588\
18589if not s then\
18590 print('\\nError loading Opus OS\\n')\
18591 _G.printError(m .. '\\n')\
18592end\
18593\
18594if fs.restore then\
18595 fs.restore()\
18596end",
18597 },
18598 },
18599}