mirror of
https://github.com/Ale32bit/Capy64.git
synced 2025-12-14 18:15:44 +00:00
Compare commits
14 commits
v1.1.1-bet
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2445175248 | |||
| c19fb78b1f | |||
| 5d0b6e864a | |||
| b2c3158d72 | |||
| 1ba7185939 | |||
| e0afdf177c | |||
| 993b7f43f3 | |||
| 74c9e25ad6 | |||
| f5d6bbfaae | |||
| 2bf4b17577 | |||
| 990b30b7bb | |||
| 3326d0e73d | |||
| f7d4b728f5 | |||
| bf24c6e989 |
47 changed files with 1786 additions and 161 deletions
7
Capy64/Assets/Lua/CapyOS/home/.shrc
Normal file
7
Capy64/Assets/Lua/CapyOS/home/.shrc
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
alias ll "ls -al"
|
||||
alias la "ls -a"
|
||||
alias rmdir "rm -r"
|
||||
alias reboot "shutdown -r"
|
||||
|
||||
# Comment or remove the line below to disable the MOTD
|
||||
motd
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
local version = "0.0.3"
|
||||
local version = "0.1.1"
|
||||
local systemDirectory = "/sys"
|
||||
|
||||
print("Starting CapyOS")
|
||||
|
|
|
|||
28
Capy64/Assets/Lua/CapyOS/sys/bin/alias.lua
Normal file
28
Capy64/Assets/Lua/CapyOS/sys/bin/alias.lua
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
local argparser = require("argparser")
|
||||
|
||||
local args, options = argparser.parse(...)
|
||||
|
||||
|
||||
if options.l or options.list then
|
||||
for alias, value in pairs(shell.aliases) do
|
||||
print(string.format("%s = \"%s\"", alias, value))
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local alias = args[1]
|
||||
|
||||
if not alias or options.h or options.help then
|
||||
print("Usage: alias [option...] <alias> [command]")
|
||||
print("Options:")
|
||||
print(" -l --list: List aliases")
|
||||
return false
|
||||
end
|
||||
|
||||
local command = table.pack(select(2, ...))
|
||||
if #command == 0 then
|
||||
shell.aliases[alias] = nil
|
||||
return
|
||||
end
|
||||
|
||||
shell.aliases[alias] = table.concat(command, " ")
|
||||
4
Capy64/Assets/Lua/CapyOS/sys/bin/bg.lua
Normal file
4
Capy64/Assets/Lua/CapyOS/sys/bin/bg.lua
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
local scheduler = require("scheduler")
|
||||
scheduler.spawn(function()
|
||||
shell.run(arg.string)
|
||||
end)
|
||||
9
Capy64/Assets/Lua/CapyOS/sys/bin/cat.lua
Normal file
9
Capy64/Assets/Lua/CapyOS/sys/bin/cat.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local fs = require("fs")
|
||||
|
||||
local args = {...}
|
||||
|
||||
local path = shell.resolve(args[1])
|
||||
|
||||
local f<close> = fs.open(path, "r")
|
||||
print(f:read("a"))
|
||||
f:close()
|
||||
4
Capy64/Assets/Lua/CapyOS/sys/bin/echo.lua
Normal file
4
Capy64/Assets/Lua/CapyOS/sys/bin/echo.lua
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
local argparser = require("argparser")
|
||||
|
||||
local args, options = argparser.parse(...)
|
||||
print(table.concat(args, " "))
|
||||
|
|
@ -1 +1 @@
|
|||
shell.exit()
|
||||
shell.exit()
|
||||
40
Capy64/Assets/Lua/CapyOS/sys/bin/fun/donuts.lua
Normal file
40
Capy64/Assets/Lua/CapyOS/sys/bin/fun/donuts.lua
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
local gpu = require("gpu")
|
||||
local event = require("event")
|
||||
local donuts = {}
|
||||
local limit = 100
|
||||
|
||||
local w, h = gpu.getSize()
|
||||
local function insert()
|
||||
local donut = {
|
||||
x = math.random(-20, w + 20),
|
||||
y = math.random(-20, h + 20),
|
||||
d = math.random() * math.pi*2,
|
||||
dir = math.random(0, 1),
|
||||
c = math.random(0xffffff),
|
||||
life = math.random(100, 1000),
|
||||
}
|
||||
table.insert(donuts, donut)
|
||||
end
|
||||
|
||||
while true do
|
||||
if #donuts < limit then
|
||||
insert()
|
||||
end
|
||||
gpu.clear(0)
|
||||
for k, donut in ipairs(donuts) do
|
||||
if donut.life <= 0 then
|
||||
table.remove(donuts, k)
|
||||
end
|
||||
local doReverse = math.random(0, 1000) > 950
|
||||
donut.x = donut.x + math.cos(donut.d) * 4
|
||||
donut.y = donut.y + math.sin(donut.d) * 4
|
||||
donut.d = donut.d + (donut.dir == 1 and 0.05 or -0.05)
|
||||
gpu.drawCircle(donut.x, donut.y, 20, donut.c, 10)
|
||||
if doReverse then
|
||||
donut.dir = donut.dir == 1 and 0 or 1
|
||||
end
|
||||
donut.life = donut.life - 1
|
||||
end
|
||||
event.push("donuts")
|
||||
event.pull("donuts")
|
||||
end
|
||||
|
|
@ -38,15 +38,19 @@ local function iter(cr, ci)
|
|||
end
|
||||
|
||||
local function draw()
|
||||
local buffer <close> = gpu.newBuffer()
|
||||
local size = w * h
|
||||
local canvas = { string.unpack(("B"):rep(size), ("\0"):rep(size)) }
|
||||
canvas[#canvas] = nil
|
||||
|
||||
|
||||
for y = 0, h - 1 do
|
||||
for x = 0, w - 1 do
|
||||
local _, _, i = iter((x - cx + dx * pscale) * px, (y - cy + dy * pscale) * px)
|
||||
buffer[y * w + x] = colorUnit * (iterations - i)
|
||||
canvas[y * w + x] = colorUnit * (iterations - i)
|
||||
end
|
||||
end
|
||||
|
||||
local buffer <close> = gpu.bufferFrom(canvas, w, h)
|
||||
gpu.setBuffer(buffer)
|
||||
end
|
||||
|
||||
|
|
|
|||
209
Capy64/Assets/Lua/CapyOS/sys/bin/fun/paint.lua
Normal file
209
Capy64/Assets/Lua/CapyOS/sys/bin/fun/paint.lua
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
local event = require("event")
|
||||
local gpu = require("gpu")
|
||||
local colors = require("colors")
|
||||
local term = require("term")
|
||||
local timer = require("timer")
|
||||
|
||||
local w, h = gpu.getSize()
|
||||
local tw, th = term.getSize()
|
||||
|
||||
local selectedColor = 1
|
||||
local thickness = 4
|
||||
|
||||
local canvasW, canvasH = term.toRealPos(tw - 1, th + 1)
|
||||
canvasW = canvasW - 3
|
||||
local size = canvasW * canvasH
|
||||
local canvas = {string.unpack(("B"):rep(size), ("\0"):rep(size))}
|
||||
canvas[#canvas] = nil
|
||||
|
||||
local function drawCircle(buffer, x, y, radius, color)
|
||||
radius = math.max(0, radius)
|
||||
if radius == 0 then
|
||||
buffer[x + buffer.width * y] = color
|
||||
return
|
||||
end
|
||||
local width = buffer.width
|
||||
local height = buffer.height
|
||||
|
||||
local index = function(x, y)
|
||||
return y * width + x
|
||||
end
|
||||
|
||||
local isValid = function(x, y)
|
||||
return x >= 0 and x < width and y >= 0 and y < height
|
||||
end
|
||||
|
||||
local setPixel = function(x, y, color)
|
||||
if isValid(x, y) then
|
||||
buffer[index(x, y)] = color
|
||||
end
|
||||
end
|
||||
|
||||
local drawFilledCirclePoints = function(cx, cy, x, y)
|
||||
for dx = -x, x do
|
||||
for dy = -y, y do
|
||||
setPixel(cx + dx, cy + dy, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local drawCircleBresenham = function(cx, cy, radius)
|
||||
local x = 0
|
||||
local y = radius
|
||||
local d = 3 - 2 * radius
|
||||
drawFilledCirclePoints(cx, cy, x, y)
|
||||
while y >= x do
|
||||
x = x + 1
|
||||
if d > 0 then
|
||||
y = y - 1
|
||||
d = d + 4 * (x - y) + 10
|
||||
else
|
||||
d = d + 4 * x + 6
|
||||
end
|
||||
drawFilledCirclePoints(cx, cy, x, y)
|
||||
end
|
||||
end
|
||||
|
||||
drawCircleBresenham(x, y, radius)
|
||||
end
|
||||
|
||||
local function drawLine(buffer, x0, y0, x1, y1, color, thickness)
|
||||
local width = canvasW
|
||||
local height = canvasH
|
||||
|
||||
local index = function(x, y)
|
||||
return y * width + x
|
||||
end
|
||||
|
||||
local isValid = function(x, y)
|
||||
return x >= 0 and x < width and y >= 0 and y < height
|
||||
end
|
||||
|
||||
local setPixel = function(x, y)
|
||||
if isValid(x, y) then
|
||||
buffer[index(x, y)] = color
|
||||
end
|
||||
end
|
||||
|
||||
local drawLineBresenham = function()
|
||||
local i = 0
|
||||
local dx = math.abs(x1 - x0)
|
||||
local dy = math.abs(y1 - y0)
|
||||
local sx = x0 < x1 and 1 or -1
|
||||
local sy = y0 < y1 and 1 or -1
|
||||
local err = dx - dy
|
||||
|
||||
local majorAxis = dx > dy
|
||||
|
||||
while x0 ~= x1 or y0 ~= y1 do
|
||||
for i = 0, thickness - 1 do
|
||||
if majorAxis then
|
||||
setPixel(x0, y0 + i)
|
||||
else
|
||||
setPixel(x0 + i, y0)
|
||||
end
|
||||
end
|
||||
|
||||
local err2 = 2 * err
|
||||
if err2 > -dy then
|
||||
err = err - dy
|
||||
x0 = x0 + sx
|
||||
end
|
||||
if err2 < dx then
|
||||
err = err + dx
|
||||
y0 = y0 + sy
|
||||
end
|
||||
|
||||
if i % 1024 == 0 then
|
||||
--event.push("paint")
|
||||
--event.pull("paint")
|
||||
--timer.sleep(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
drawLineBresenham()
|
||||
end
|
||||
|
||||
local function drawUI()
|
||||
term.setBackground(0)
|
||||
term.clear()
|
||||
for y = 1, 16 do
|
||||
term.setPos(tw - 1, y)
|
||||
term.setBackground(0)
|
||||
term.setForeground(colors[y])
|
||||
if selectedColor == y then
|
||||
term.setBackground(colors[y])
|
||||
term.write(" ")
|
||||
else
|
||||
term.write("##")
|
||||
end
|
||||
end
|
||||
term.setPos(tw - 1, 17)
|
||||
|
||||
if selectedColor == 0 then
|
||||
term.setBackground(colors.white)
|
||||
term.setForeground(0)
|
||||
else
|
||||
term.setBackground(0)
|
||||
term.setForeground(colors.white)
|
||||
end
|
||||
term.write("XX")
|
||||
|
||||
term.setPos(tw - 1, 18)
|
||||
term.setBackground(colors.black)
|
||||
term.setForeground(colors.white)
|
||||
term.write(thickness)
|
||||
|
||||
gpu.drawLine(canvasW + 1, 0, canvasW, canvasH, colors.gray, 2)
|
||||
|
||||
local b<close> = gpu.bufferFrom(canvas, canvasW, canvasH)
|
||||
gpu.drawBuffer(b, 0, 0, {
|
||||
source = {
|
||||
0, 0, canvasW, canvasH
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
local function contains(arr, val)
|
||||
for i, v in ipairs(arr) do
|
||||
if v == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local oldX, oldY
|
||||
while true do
|
||||
drawUI()
|
||||
|
||||
local ev, b, x, y = event.pull("mouse_down", "mouse_up", "mouse_move", "mouse_scroll")
|
||||
local tx, ty = term.fromRealPos(x, y)
|
||||
if ev == "mouse_up" then
|
||||
if x >= canvasW then
|
||||
if ty <= 16 then
|
||||
selectedColor = ty
|
||||
elseif ty == 17 then
|
||||
selectedColor = 0
|
||||
end
|
||||
end
|
||||
oldX, oldY = nil, nil
|
||||
elseif ev == "mouse_down" or (ev == "mouse_move" and contains(b, 1)) then
|
||||
if x < canvasW and y < canvasH then
|
||||
--canvas[x + y * canvasW] = colors[selectedColor] or 0
|
||||
--drawCircle(canvas, x, y, thickness - 2, colors[selectedColor])
|
||||
|
||||
drawLine(canvas, x, y, oldX or x, oldY or y, colors[selectedColor] or 0, thickness)
|
||||
--gpu.drawLine(x, y, oldX or x, oldY or y, colors[selectedColor] or 0)
|
||||
--canvas = gpu.getBuffer()
|
||||
|
||||
oldX, oldY = x, y
|
||||
end
|
||||
elseif ev == "mouse_scroll" then
|
||||
local x, y, b = b, x, y
|
||||
local tx, ty = term.fromRealPos(x, y)
|
||||
if x >= canvasW and ty == 18 then
|
||||
thickness = math.min(99, math.max(0, thickness - b))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -11,7 +11,13 @@ local function slowPrint(text, delay)
|
|||
print()
|
||||
end
|
||||
|
||||
local args = {...}
|
||||
local text = "Hello, World!"
|
||||
if #args > 0 then
|
||||
text = table.concat(args, " ")
|
||||
end
|
||||
|
||||
local color = colors[math.random(1, #colors)]
|
||||
|
||||
term.setForeground(color)
|
||||
slowPrint("Hello, World!", 0.05)
|
||||
slowPrint(text, 0.05)
|
||||
|
|
|
|||
11
Capy64/Assets/Lua/CapyOS/sys/bin/help.lua
Normal file
11
Capy64/Assets/Lua/CapyOS/sys/bin/help.lua
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
local fs = require("fs")
|
||||
local helpPath = "/sys/share/help"
|
||||
|
||||
local topicName = arg[1] or "index"
|
||||
|
||||
if not fs.exists(fs.combine(helpPath, topicName)) then
|
||||
print(string.format("Topic \"%s\" not found.", topicName))
|
||||
return false
|
||||
end
|
||||
|
||||
shell.run("/sys/bin/less.lua", fs.combine(helpPath, topicName))
|
||||
78
Capy64/Assets/Lua/CapyOS/sys/bin/less.lua
Normal file
78
Capy64/Assets/Lua/CapyOS/sys/bin/less.lua
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
local term = require("term")
|
||||
local keys = require("keys")
|
||||
local event = require("event")
|
||||
local fs = require("fs")
|
||||
local timer = require("timer")
|
||||
local colors = require("colors")
|
||||
|
||||
local filename = shell.resolve(arg[1])
|
||||
|
||||
local f<close> = fs.open(filename, "r")
|
||||
local lines = {}
|
||||
local lineMax = 0
|
||||
for line in f:lines() do
|
||||
table.insert(lines, line)
|
||||
lineMax = math.max(lineMax, #line)
|
||||
end
|
||||
f:close()
|
||||
|
||||
|
||||
local width, height = term.getSize()
|
||||
height = height - 1
|
||||
local posx, posy = 0, 0
|
||||
|
||||
local function redraw()
|
||||
term.clear()
|
||||
term.setForeground(colors.white)
|
||||
for i = 1, height do
|
||||
if i + posy > #lines then
|
||||
break
|
||||
end
|
||||
term.setPos(-posx + 1, i)
|
||||
term.write(lines[i + posy])
|
||||
end
|
||||
|
||||
term.setForeground(colors.yellow)
|
||||
term.setPos(1, height + 1)
|
||||
term.write("Use arrow keys to move or press Q to exit.")
|
||||
end
|
||||
|
||||
while true do
|
||||
redraw()
|
||||
|
||||
local _, key = event.pull("key_down")
|
||||
|
||||
if key == keys.enter or key == keys.down then
|
||||
posy = posy + 1
|
||||
elseif key == keys.up then
|
||||
posy = posy - 1
|
||||
elseif key == keys.right then
|
||||
posx = posx + 1
|
||||
elseif key == keys.left then
|
||||
posx = posx - 1
|
||||
elseif key == keys.q or key == keys.escape then
|
||||
-- Clear event queue
|
||||
timer.sleep(0)
|
||||
term.clear()
|
||||
term.setPos(1, 1)
|
||||
break
|
||||
end
|
||||
|
||||
|
||||
|
||||
if posy > #lines - height then
|
||||
posy = #lines - height
|
||||
end
|
||||
|
||||
if posy < 0 then
|
||||
posy = 0
|
||||
end
|
||||
|
||||
if posx + width > lineMax + 1 then
|
||||
posx = lineMax - width + 1
|
||||
end
|
||||
|
||||
if posx < 0 then
|
||||
posx = 0
|
||||
end
|
||||
end
|
||||
|
|
@ -1,25 +1,88 @@
|
|||
local args = { ... }
|
||||
local fs = require("fs")
|
||||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
local dir = shell.getDir()
|
||||
local argparser = require("argparser")
|
||||
|
||||
if args[1] then
|
||||
dir = shell.resolve(args[1])
|
||||
local theme = {
|
||||
directory = colors.lightBlue,
|
||||
file = colors.white,
|
||||
lua = colors.yellow,
|
||||
}
|
||||
|
||||
local function humanizeBytes(n)
|
||||
local prefixes = {
|
||||
[0] = "",
|
||||
"k",
|
||||
"M",
|
||||
"G",
|
||||
"T",
|
||||
}
|
||||
local block = 1024
|
||||
local prefixIndex = 0
|
||||
|
||||
while n >= block do
|
||||
n = n / 1024
|
||||
prefixIndex = prefixIndex + 1
|
||||
end
|
||||
|
||||
return string.format("%.0f%s", n, prefixes[prefixIndex])
|
||||
end
|
||||
|
||||
if not fs.isDir(dir) then
|
||||
error("No such directory: " .. dir, 0)
|
||||
local args, options = argparser.parse(...)
|
||||
|
||||
if options.h or options.help then
|
||||
print("Usage: ls [option...] [path]")
|
||||
print("List files (current directory by default)")
|
||||
print("Options:")
|
||||
print(" -a: Include hidden files")
|
||||
print(" -l: Use long listing format")
|
||||
return
|
||||
end
|
||||
local path = shell.getDir()
|
||||
|
||||
if args[1] then
|
||||
path = shell.resolve(args[1])
|
||||
end
|
||||
|
||||
if not fs.isDir(path) then
|
||||
error("No such directory: " .. path, 0)
|
||||
return false
|
||||
end
|
||||
|
||||
local files = fs.list(dir)
|
||||
for k, v in ipairs(files) do
|
||||
if fs.isDir(fs.combine(dir, v)) then
|
||||
term.setForeground(colors.lightBlue)
|
||||
print(v .. "/")
|
||||
else
|
||||
term.setForeground(colors.white)
|
||||
print(v)
|
||||
local entries = fs.list(path)
|
||||
|
||||
if options.l then
|
||||
print(string.format("total %d", #entries))
|
||||
end
|
||||
local printed = 0
|
||||
for i, entry in ipairs(entries) do
|
||||
if entry:sub(1, 1) ~= "." or options.a then
|
||||
printed = printed + 1
|
||||
local attributes = fs.attributes(fs.combine(path, entry))
|
||||
local size = humanizeBytes(attributes.size)
|
||||
local date = os.date("%x %H:%m", attributes.modified // 1000)
|
||||
|
||||
local entryType
|
||||
if attributes.isDirectory then
|
||||
entryType = "directory"
|
||||
else
|
||||
entryType = "file"
|
||||
if string.match(entry, "%.lua$") then
|
||||
entryType = "lua"
|
||||
end
|
||||
end
|
||||
|
||||
if options.l then
|
||||
term.setForeground(colors.white)
|
||||
term.write(string.format("%s %5s %s ", attributes.isDirectory and "d" or "-", size, date))
|
||||
end
|
||||
term.setForeground(theme[entryType])
|
||||
io.write(entry)
|
||||
|
||||
io.write(options.l and "\n" or "\t")
|
||||
end
|
||||
end
|
||||
|
||||
if not options.l and printed > 0 then
|
||||
print()
|
||||
end
|
||||
|
|
@ -1,49 +1,16 @@
|
|||
local term = require("term")
|
||||
local io = require("io")
|
||||
local colors = require("colors")
|
||||
local colours = colors
|
||||
local argparser = require("argparser")
|
||||
local tableutils = require("tableutils")
|
||||
|
||||
local tArgs = { ... }
|
||||
if #tArgs > 0 then
|
||||
print("This is an interactive Lua prompt.")
|
||||
print("To run a lua program, just type its name.")
|
||||
return
|
||||
end
|
||||
|
||||
--local pretty = require "cc.pretty"
|
||||
|
||||
local bRunning = true
|
||||
local tCommandHistory = {}
|
||||
local tEnv = {
|
||||
["exit"] = setmetatable({}, {
|
||||
__tostring = function() return "Call exit() to exit." end,
|
||||
__call = function() bRunning = false end,
|
||||
}),
|
||||
}
|
||||
setmetatable(tEnv, { __index = _ENV })
|
||||
|
||||
for k, v in pairs(package.loaded) do
|
||||
tEnv[k] = v
|
||||
end
|
||||
|
||||
term.setForeground(colours.yellow)
|
||||
print(_VERSION .. " interactive prompt")
|
||||
print("Call exit() to exit.")
|
||||
term.setForeground(colours.white)
|
||||
|
||||
while bRunning do
|
||||
term.setForeground(colours.yellow)
|
||||
io.write("> ")
|
||||
term.setForeground(colours.white)
|
||||
|
||||
local s = io.read(nil, tCommandHistory)
|
||||
if s:match("%S") and tCommandHistory[#tCommandHistory] ~= s then
|
||||
table.insert(tCommandHistory, s)
|
||||
end
|
||||
local args, options = argparser.parse(...)
|
||||
|
||||
local function evaluate(str, env, chunkname)
|
||||
chunkname = chunkname or "=lua"
|
||||
local nForcePrint = 0
|
||||
local func, e = load(s, "=lua", "t", tEnv)
|
||||
local func2 = load("return " .. s, "=lua", "t", tEnv)
|
||||
local func, e = load(str, chunkname, "t", env)
|
||||
local func2 = load("return " .. str, chunkname, "t", env)
|
||||
if not func then
|
||||
if func2 then
|
||||
func = func2
|
||||
|
|
@ -62,14 +29,70 @@ while bRunning do
|
|||
local n = 1
|
||||
while n < tResults.n or n <= nForcePrint do
|
||||
local value = tResults[n + 1]
|
||||
print(tostring(value))
|
||||
print(tableutils.pretty(value))
|
||||
n = n + 1
|
||||
end
|
||||
else
|
||||
io.stderr.print(tResults[2])
|
||||
return false
|
||||
end
|
||||
else
|
||||
io.stderr.print(e)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function createEnvironment()
|
||||
return setmetatable({}, { __index = _ENV })
|
||||
end
|
||||
|
||||
local function loadPackages(env)
|
||||
for k, v in pairs(package.loaded) do
|
||||
env[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
if options.e then
|
||||
local env = createEnvironment()
|
||||
loadPackages(env)
|
||||
return evaluate(table.concat(args, " "), env)
|
||||
end
|
||||
|
||||
if #args > 0 then
|
||||
print("This is an interactive Lua prompt.")
|
||||
print("To run a lua program, just type its name.")
|
||||
return
|
||||
end
|
||||
|
||||
--local pretty = require "cc.pretty"
|
||||
|
||||
local bRunning = true
|
||||
local tCommandHistory = {}
|
||||
|
||||
|
||||
local tEnv = createEnvironment()
|
||||
tEnv.exit = setmetatable({}, {
|
||||
__tostring = function() return "Call exit() to exit." end,
|
||||
__call = function() bRunning = false end,
|
||||
})
|
||||
loadPackages(tEnv)
|
||||
|
||||
term.setForeground(colors.yellow)
|
||||
print(_VERSION .. " interactive prompt")
|
||||
print("Call exit() to exit.")
|
||||
term.setForeground(colors.white)
|
||||
|
||||
while bRunning do
|
||||
term.setForeground(colors.yellow)
|
||||
io.write("> ")
|
||||
term.setForeground(colors.white)
|
||||
|
||||
local s = io.read(nil, tCommandHistory)
|
||||
if s:match("%S") and tCommandHistory[#tCommandHistory] ~= s then
|
||||
table.insert(tCommandHistory, s)
|
||||
end
|
||||
|
||||
evaluate(s, tEnv)
|
||||
|
||||
end
|
||||
|
|
|
|||
20
Capy64/Assets/Lua/CapyOS/sys/bin/motd.lua
Normal file
20
Capy64/Assets/Lua/CapyOS/sys/bin/motd.lua
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
local fs = require("fs")
|
||||
|
||||
local date = os.date("*t")
|
||||
|
||||
if date.month == 4 and date.day == 28 then
|
||||
print("Ed Balls")
|
||||
return
|
||||
end
|
||||
|
||||
local motdList = {}
|
||||
|
||||
local f<close> = fs.open("/sys/share/motd.txt", "r")
|
||||
for line in f:lines() do
|
||||
table.insert(motdList, line)
|
||||
end
|
||||
f:close()
|
||||
|
||||
local motdIndex = math.random(1, #motdList)
|
||||
|
||||
print(motdList[motdIndex])
|
||||
16
Capy64/Assets/Lua/CapyOS/sys/bin/mv.lua
Normal file
16
Capy64/Assets/Lua/CapyOS/sys/bin/mv.lua
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
local fs = require("fs")
|
||||
local argparser = require("argparser")
|
||||
|
||||
local args, options = argparser.parse(...)
|
||||
|
||||
if not args[1] or not args[2] or options.h or options.help then
|
||||
print("Usage: mv [option...] <source> <target>")
|
||||
print("Options:")
|
||||
print(" -h --help: Display help")
|
||||
return
|
||||
end
|
||||
|
||||
local source = shell.resolve(args[1])
|
||||
local destination = shell.resolve(args[2])
|
||||
|
||||
fs.move(source, destination)
|
||||
10
Capy64/Assets/Lua/CapyOS/sys/bin/programs.lua
Normal file
10
Capy64/Assets/Lua/CapyOS/sys/bin/programs.lua
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
local fs = require("fs")
|
||||
local programs = fs.list("/sys/bin", function(name, attr)
|
||||
return not attr.isDirectory
|
||||
end)
|
||||
|
||||
for i, v in ipairs(programs) do
|
||||
programs[i] = string.gsub(v, "%.lua$", "")
|
||||
end
|
||||
|
||||
print(table.concat(programs, " "))
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
local timer = require("timer")
|
||||
local machine = require("machine")
|
||||
|
||||
print("Goodbye!")
|
||||
|
||||
timer.sleep(1)
|
||||
|
||||
machine.reboot()
|
||||
|
|
@ -1,10 +1,16 @@
|
|||
local fs = require("fs")
|
||||
local argparser = require("argparser")
|
||||
|
||||
local args = { ... }
|
||||
if #args == 0 then
|
||||
print("Usage: rm <file>")
|
||||
local args, options = argparser.parse(...)
|
||||
|
||||
if not args[1] or options.h or options.help then
|
||||
print("Usage: rm [option...] <path>")
|
||||
print("Options:")
|
||||
print(" -r --recursive: Delete non-empty directories")
|
||||
print(" -h --help: Display help")
|
||||
return
|
||||
end
|
||||
|
||||
local file = shell.resolve(args[1])
|
||||
fs.delete(file, true)
|
||||
|
||||
fs.delete(file, options.recursive or options.r)
|
||||
|
|
|
|||
|
|
@ -2,40 +2,35 @@ local term = require("term")
|
|||
local colors = require("colors")
|
||||
local fs = require("fs")
|
||||
local machine = require("machine")
|
||||
local argparser = require("argparser")
|
||||
local scheduler = require("scheduler")
|
||||
local createPackageEnvironment = require("shell.package")
|
||||
|
||||
local useScheduler = false
|
||||
local exit = false
|
||||
local parentShell = shell
|
||||
local isStartupShell = parentShell == nil
|
||||
local shell = {}
|
||||
|
||||
shell.path = "./?;./?.lua;/bin/?.lua;/sys/bin/?.lua"
|
||||
shell.homePath = "/home"
|
||||
shell.path = parentShell and parentShell.path or "./?;./?.lua;/bin/?.lua;/sys/bin/?.lua"
|
||||
shell.homePath = parentShell and parentShell.home or "/home"
|
||||
shell.aliases = parentShell and parentShell.aliases or {}
|
||||
|
||||
local currentDir = shell.homePath
|
||||
local currentDir = parentShell and parentShell.getDir() or shell.homePath
|
||||
|
||||
local function buildEnvironment(path, args)
|
||||
local function buildEnvironment(command, filepath, args, argf)
|
||||
local arg = { table.unpack(args, 2) }
|
||||
arg[0] = path
|
||||
|
||||
arg[0] = command
|
||||
arg.string = argf
|
||||
|
||||
local envPackage = createPackageEnvironment(filepath)
|
||||
envPackage.loaded.scheduler = scheduler
|
||||
|
||||
return setmetatable({
|
||||
shell = shell,
|
||||
arg = arg
|
||||
}, { __index = _G })
|
||||
end
|
||||
|
||||
local function tokenise(...)
|
||||
local sLine = table.concat({ ... }, " ")
|
||||
local tWords = {}
|
||||
local bQuoted = false
|
||||
for match in string.gmatch(sLine .. "\"", "(.-)\"") do
|
||||
if bQuoted then
|
||||
table.insert(tWords, match)
|
||||
else
|
||||
for m in string.gmatch(match, "[^ \t]+") do
|
||||
table.insert(tWords, m)
|
||||
end
|
||||
end
|
||||
bQuoted = not bQuoted
|
||||
end
|
||||
return tWords
|
||||
arg = arg,
|
||||
scheduler = scheduler,
|
||||
}, { __index = envPackage.loaded._G })
|
||||
end
|
||||
|
||||
function shell.getDir()
|
||||
|
|
@ -72,16 +67,24 @@ function shell.resolveProgram(path)
|
|||
end
|
||||
|
||||
function shell.run(...)
|
||||
local args = tokenise(...)
|
||||
local args = argparser.tokenize(...)
|
||||
local argf = table.concat({...}, " ")
|
||||
local command = args[1]
|
||||
|
||||
argf = argf:sub(#command + 2)
|
||||
|
||||
local path = shell.resolveProgram(command)
|
||||
|
||||
if not path then
|
||||
io.stderr.print("Command not found: " .. command)
|
||||
return false
|
||||
if shell.aliases[command] then
|
||||
return shell.run(shell.aliases[command], select(2, table.unpack(args)))
|
||||
else
|
||||
io.stderr.print("Command not found: " .. command)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local env = buildEnvironment(command, args)
|
||||
local env = buildEnvironment(command, path, args, argf)
|
||||
|
||||
local func, err = loadfile(path, "t", env)
|
||||
|
||||
|
|
@ -90,7 +93,21 @@ function shell.run(...)
|
|||
return false
|
||||
end
|
||||
|
||||
local ok, err = pcall(func, table.unpack(args, 2))
|
||||
local ok, err
|
||||
local function run()
|
||||
ok, err = pcall(func, table.unpack(args, 2))
|
||||
end
|
||||
|
||||
if useScheduler then
|
||||
local programTask, yielded = scheduler.spawn(run)
|
||||
|
||||
if yielded then
|
||||
coroutine.yield("scheduler_task_end")
|
||||
end
|
||||
else
|
||||
run()
|
||||
end
|
||||
|
||||
if not ok then
|
||||
io.stderr.print(err)
|
||||
return false
|
||||
|
|
@ -107,6 +124,21 @@ if not fs.exists(shell.homePath) then
|
|||
fs.makeDir(shell.homePath)
|
||||
end
|
||||
|
||||
term.setForeground(colors.white)
|
||||
term.setBackground(colors.black)
|
||||
|
||||
if isStartupShell then
|
||||
if fs.exists(fs.combine(shell.homePath, ".shrc")) then
|
||||
local f <close> = fs.open(fs.combine(shell.homePath, ".shrc"), "r")
|
||||
for line in f:lines() do
|
||||
if line:match("%S") and not line:match("^%s-#") then
|
||||
shell.run(line)
|
||||
end
|
||||
end
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
|
||||
local history = {}
|
||||
local lastExecSuccess = true
|
||||
while not exit do
|
||||
|
|
|
|||
|
|
@ -1,8 +1,33 @@
|
|||
local timer = require("timer")
|
||||
local machine = require("machine")
|
||||
local scheduler = require("scheduler")
|
||||
local argparser = require("argparser")
|
||||
|
||||
print("Goodbye!")
|
||||
local args, options = argparser.parse(...)
|
||||
|
||||
timer.sleep(1)
|
||||
if options.h or options.help then
|
||||
print("Usage: shutdown [option...]")
|
||||
print("Shutdown or restart Capy64.")
|
||||
print("Options:")
|
||||
print(" -s --shutdown: Shutdown and exit Capy64. (default)")
|
||||
print(" -r --reboot: Restart Capy64.")
|
||||
print(" -t --time: Time to wait in seconds. (\"now\" is 0 seconds, default)")
|
||||
return
|
||||
end
|
||||
|
||||
machine.shutdown()
|
||||
local time = 0
|
||||
if options.t or options.time then
|
||||
time = options.t or options.time
|
||||
end
|
||||
if time == "now" then
|
||||
time = 0
|
||||
else
|
||||
time = tonumber(time)
|
||||
if not time then
|
||||
error("Invalid time option: " .. (options.t or options.time), 0)
|
||||
end
|
||||
end
|
||||
|
||||
scheduler.ipc(1, "power", {
|
||||
reboot = options.r or options.reboot,
|
||||
time = time,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ if not http.checkURL(args[1]) then
|
|||
error("Invalid URL", 0)
|
||||
end
|
||||
|
||||
print("Connecting...")
|
||||
|
||||
local response, err = http.get(args[1], nil, {
|
||||
binary = true,
|
||||
})
|
||||
|
|
@ -27,8 +29,8 @@ if not response then
|
|||
end
|
||||
|
||||
local file <close> = fs.open(outputPath, "wb")
|
||||
file:write(response:readAll())
|
||||
file:write(response.content:read("a"))
|
||||
file:close()
|
||||
response:close()
|
||||
response.content:close()
|
||||
|
||||
print("File written to " .. outputPath)
|
||||
print("Downloaded to " .. outputPath)
|
||||
|
|
|
|||
28
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/02_fs.lua
Normal file
28
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/02_fs.lua
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
local fs = require("fs")
|
||||
local expect = require("expect").expect
|
||||
|
||||
local fsList = fs.list
|
||||
|
||||
function fs.list(path, filter)
|
||||
expect(1, path, "string")
|
||||
expect(2, filter, "nil", "function")
|
||||
|
||||
if not fs.isDir(path) then
|
||||
error("directory not found", 2)
|
||||
end
|
||||
|
||||
local list = fsList(path)
|
||||
if not filter then
|
||||
return list
|
||||
end
|
||||
|
||||
local filteredList = {}
|
||||
for i = 1, #list do
|
||||
local attributes = fs.attributes(fs.combine(path, list[i]))
|
||||
if filter(list[i], attributes) then
|
||||
table.insert(filteredList, list[i])
|
||||
end
|
||||
end
|
||||
|
||||
return filteredList
|
||||
end
|
||||
47
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/50_os_manager.lua
Normal file
47
Capy64/Assets/Lua/CapyOS/sys/boot/autorun/50_os_manager.lua
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
local machine = require("machine")
|
||||
local scheduler = require("scheduler")
|
||||
|
||||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
term.setForeground(0x59c9ff)
|
||||
term.setBackground(colors.black)
|
||||
term.clear()
|
||||
term.setPos(1, 1)
|
||||
|
||||
term.write(os.version())
|
||||
term.setPos(1, 2)
|
||||
|
||||
local function spawnShell()
|
||||
return scheduler.spawn(loadfile("/sys/bin/shell.lua"))
|
||||
end
|
||||
|
||||
local function main()
|
||||
local shellTask = spawnShell()
|
||||
while true do
|
||||
local ev = {coroutine.yield()}
|
||||
if ev[1] == "ipc_message" then
|
||||
local sender = ev[2]
|
||||
local call = ev[3]
|
||||
if call == "power" then
|
||||
local options = ev[4]
|
||||
--todo: handle time and cancels
|
||||
if options.reboot then
|
||||
machine.reboot()
|
||||
else
|
||||
machine.shutdown()
|
||||
end
|
||||
end
|
||||
elseif ev[1] == "scheduler_task_end" then
|
||||
if ev[2].pid == shellTask.pid then
|
||||
if not ev[3] then
|
||||
io.stderr.print(ev[4])
|
||||
end
|
||||
shellTask = spawnShell()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scheduler.spawn(main)
|
||||
|
||||
scheduler.init()
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("machine").shutdown()
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
local term = require("term")
|
||||
local colors = require("colors")
|
||||
local machine = require("machine")
|
||||
|
||||
term.setForeground(0x59c9ff)
|
||||
term.setBackground(colors.black)
|
||||
term.clear()
|
||||
term.setPos(1, 1)
|
||||
|
||||
term.write(os.version())
|
||||
term.setPos(1, 2)
|
||||
|
||||
dofile("/sys/bin/shell.lua")
|
||||
|
||||
machine.shutdown()
|
||||
91
Capy64/Assets/Lua/CapyOS/sys/lib/argparser.lua
Normal file
91
Capy64/Assets/Lua/CapyOS/sys/lib/argparser.lua
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
local argparser = {}
|
||||
|
||||
function argparser.tokenize(...)
|
||||
local input = table.concat(table.pack(...), " ")
|
||||
local tokens = {}
|
||||
|
||||
-- surely there must be a better way
|
||||
local quoted = false
|
||||
local escaped = false
|
||||
local current = ""
|
||||
for i = 1, #input do
|
||||
local char = input:sub(i, i)
|
||||
if escaped then
|
||||
escaped = false
|
||||
current = current .. char
|
||||
else
|
||||
if char == "\\" then
|
||||
escaped = true
|
||||
elseif char == "\"" then
|
||||
if quoted then
|
||||
-- close quote
|
||||
table.insert(tokens, current)
|
||||
current = ""
|
||||
end
|
||||
quoted = not quoted
|
||||
elseif char == " " and not quoted then
|
||||
if #current > 0 then
|
||||
table.insert(tokens, current)
|
||||
end
|
||||
current = ""
|
||||
else
|
||||
current = current .. char
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if current ~= "" then
|
||||
table.insert(tokens, current)
|
||||
end
|
||||
|
||||
return tokens
|
||||
end
|
||||
|
||||
function argparser.parse(...)
|
||||
local tokens = { ... }
|
||||
local args = {}
|
||||
local options = {}
|
||||
local ignoreOptions = false
|
||||
|
||||
for i = 1, #tokens do
|
||||
local token = tokens[i]
|
||||
if not ignoreOptions then
|
||||
if token == "--" then
|
||||
ignoreOptions = true
|
||||
elseif token:sub(1, 2) == "--" then
|
||||
local opt, value = token:match("%-%-(.+)=(.+)")
|
||||
if not opt then
|
||||
opt = token:sub(3)
|
||||
if opt:sub(-1) == "=" then
|
||||
-- next token is value
|
||||
value = tokens[i + 1]
|
||||
opt = opt:sub(1, -2)
|
||||
options[opt] = value
|
||||
i = i + 1
|
||||
else
|
||||
options[opt] = true
|
||||
end
|
||||
else
|
||||
options[opt] = value
|
||||
end
|
||||
elseif token:sub(1, 1) == "-" then
|
||||
local opts = token:sub(2)
|
||||
for j = 1, #opts do
|
||||
options[opts:sub(j, j)] = true
|
||||
end
|
||||
else
|
||||
if #token > 0 then
|
||||
table.insert(args, token)
|
||||
end
|
||||
end
|
||||
else
|
||||
if #token > 0 then
|
||||
table.insert(args, token)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return args, options
|
||||
end
|
||||
|
||||
return argparser
|
||||
|
|
@ -6,7 +6,347 @@ local machine = require("machine")
|
|||
|
||||
local io = {}
|
||||
|
||||
function io.write(text)
|
||||
function io.write(sText)
|
||||
sText = tostring(sText)
|
||||
|
||||
local w, h = term.getSize()
|
||||
local x, y = term.getPos()
|
||||
|
||||
local nLinesPrinted = 0
|
||||
local function newLine()
|
||||
if y + 1 <= h then
|
||||
term.setPos(1, y + 1)
|
||||
else
|
||||
term.setPos(1, h)
|
||||
term.scroll(1)
|
||||
end
|
||||
x, y = term.getPos()
|
||||
nLinesPrinted = nLinesPrinted + 1
|
||||
end
|
||||
|
||||
-- Print the line with proper word wrapping
|
||||
sText = tostring(sText)
|
||||
while #sText > 0 do
|
||||
local whitespace = string.match(sText, "^[ \t]+")
|
||||
if whitespace then
|
||||
-- Print whitespace
|
||||
term.write(whitespace)
|
||||
x, y = term.getPos()
|
||||
sText = string.sub(sText, #whitespace + 1)
|
||||
end
|
||||
|
||||
local newline = string.match(sText, "^\n")
|
||||
if newline then
|
||||
-- Print newlines
|
||||
newLine()
|
||||
sText = string.sub(sText, 2)
|
||||
end
|
||||
|
||||
local text = string.match(sText, "^[^ \t\n]+")
|
||||
if text then
|
||||
sText = string.sub(sText, #text + 1)
|
||||
if #text > w then
|
||||
-- Print a multiline word
|
||||
while #text > 0 do
|
||||
if x > w then
|
||||
newLine()
|
||||
end
|
||||
term.write(text)
|
||||
text = string.sub(text, w - x + 2)
|
||||
x, y = term.getPos()
|
||||
end
|
||||
else
|
||||
-- Print a word normally
|
||||
if x + #text - 1 > w then
|
||||
newLine()
|
||||
end
|
||||
term.write(text)
|
||||
x, y = term.getPos()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nLinesPrinted
|
||||
end
|
||||
|
||||
function io.read(_sReplaceChar, _tHistory, _fnComplete, _sDefault)
|
||||
expect(1, _sReplaceChar, "string", "nil")
|
||||
expect(2, _tHistory, "table", "nil")
|
||||
expect(3, _fnComplete, "function", "nil")
|
||||
expect(4, _sDefault, "string", "nil")
|
||||
|
||||
term.setBlink(true)
|
||||
|
||||
local sLine
|
||||
if type(_sDefault) == "string" then
|
||||
sLine = _sDefault
|
||||
else
|
||||
sLine = ""
|
||||
end
|
||||
local nHistoryPos
|
||||
local nPos, nScroll = #sLine, 0
|
||||
if _sReplaceChar then
|
||||
_sReplaceChar = string.sub(_sReplaceChar, 1, 1)
|
||||
end
|
||||
|
||||
local tCompletions
|
||||
local nCompletion
|
||||
local function recomplete()
|
||||
if _fnComplete and nPos == #sLine then
|
||||
tCompletions = _fnComplete(sLine)
|
||||
if tCompletions and #tCompletions > 0 then
|
||||
nCompletion = 1
|
||||
else
|
||||
nCompletion = nil
|
||||
end
|
||||
else
|
||||
tCompletions = nil
|
||||
nCompletion = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function uncomplete()
|
||||
tCompletions = nil
|
||||
nCompletion = nil
|
||||
end
|
||||
|
||||
local w = term.getSize()
|
||||
local sx = term.getPos()
|
||||
|
||||
local function redraw(_bClear)
|
||||
local cursor_pos = nPos - nScroll
|
||||
if sx + cursor_pos >= w then
|
||||
-- We've moved beyond the RHS, ensure we're on the edge.
|
||||
nScroll = sx + nPos - w
|
||||
elseif cursor_pos < 0 then
|
||||
-- We've moved beyond the LHS, ensure we're on the edge.
|
||||
nScroll = nPos
|
||||
end
|
||||
|
||||
local _, cy = term.getPos()
|
||||
term.setPos(sx, cy)
|
||||
local sReplace = _bClear and " " or _sReplaceChar
|
||||
if sReplace then
|
||||
term.write(string.rep(sReplace, math.max(#sLine - nScroll, 0)))
|
||||
else
|
||||
term.write(string.sub(sLine, nScroll + 1))
|
||||
end
|
||||
|
||||
if nCompletion then
|
||||
local sCompletion = tCompletions[nCompletion]
|
||||
local oldText, oldBg
|
||||
if not _bClear then
|
||||
oldText = term.getTextColor()
|
||||
oldBg = term.getBackgroundColor()
|
||||
term.setTextColor(colors.white)
|
||||
term.setBackgroundColor(colors.gray)
|
||||
end
|
||||
if sReplace then
|
||||
term.write(string.rep(sReplace, #sCompletion))
|
||||
else
|
||||
term.write(sCompletion)
|
||||
end
|
||||
if not _bClear then
|
||||
term.setTextColor(oldText)
|
||||
term.setBackgroundColor(oldBg)
|
||||
end
|
||||
end
|
||||
|
||||
term.setPos(sx + nPos - nScroll, cy)
|
||||
end
|
||||
|
||||
local function clear()
|
||||
redraw(true)
|
||||
end
|
||||
|
||||
recomplete()
|
||||
redraw()
|
||||
|
||||
local function acceptCompletion()
|
||||
if nCompletion then
|
||||
-- Clear
|
||||
clear()
|
||||
|
||||
-- Find the common prefix of all the other suggestions which start with the same letter as the current one
|
||||
local sCompletion = tCompletions[nCompletion]
|
||||
sLine = sLine .. sCompletion
|
||||
nPos = #sLine
|
||||
|
||||
-- Redraw
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
end
|
||||
while true do
|
||||
local sEvent, param, param1, param2 = event.pull()
|
||||
if sEvent == "char" then
|
||||
-- Typed key
|
||||
clear()
|
||||
sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1)
|
||||
nPos = nPos + 1
|
||||
recomplete()
|
||||
redraw()
|
||||
|
||||
elseif sEvent == "paste" then
|
||||
-- Pasted text
|
||||
clear()
|
||||
sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1)
|
||||
nPos = nPos + #param
|
||||
recomplete()
|
||||
redraw()
|
||||
|
||||
elseif sEvent == "key_down" then
|
||||
if param == keys.enter or param == keys.numPadEnter then
|
||||
-- Enter/Numpad Enter
|
||||
if nCompletion then
|
||||
clear()
|
||||
uncomplete()
|
||||
redraw()
|
||||
end
|
||||
break
|
||||
|
||||
elseif param == keys.left then
|
||||
-- Left
|
||||
if nPos > 0 then
|
||||
clear()
|
||||
nPos = nPos - 1
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
|
||||
elseif param == keys.right then
|
||||
-- Right
|
||||
if nPos < #sLine then
|
||||
-- Move right
|
||||
clear()
|
||||
nPos = nPos + 1
|
||||
recomplete()
|
||||
redraw()
|
||||
else
|
||||
-- Accept autocomplete
|
||||
acceptCompletion()
|
||||
end
|
||||
|
||||
elseif param == keys.up or param == keys.down then
|
||||
-- Up or down
|
||||
if nCompletion then
|
||||
-- Cycle completions
|
||||
clear()
|
||||
if param == keys.up then
|
||||
nCompletion = nCompletion - 1
|
||||
if nCompletion < 1 then
|
||||
nCompletion = #tCompletions
|
||||
end
|
||||
elseif param == keys.down then
|
||||
nCompletion = nCompletion + 1
|
||||
if nCompletion > #tCompletions then
|
||||
nCompletion = 1
|
||||
end
|
||||
end
|
||||
redraw()
|
||||
|
||||
elseif _tHistory then
|
||||
-- Cycle history
|
||||
clear()
|
||||
if param == keys.up then
|
||||
-- Up
|
||||
if nHistoryPos == nil then
|
||||
if #_tHistory > 0 then
|
||||
nHistoryPos = #_tHistory
|
||||
end
|
||||
elseif nHistoryPos > 1 then
|
||||
nHistoryPos = nHistoryPos - 1
|
||||
end
|
||||
else
|
||||
-- Down
|
||||
if nHistoryPos == #_tHistory then
|
||||
nHistoryPos = nil
|
||||
elseif nHistoryPos ~= nil then
|
||||
nHistoryPos = nHistoryPos + 1
|
||||
end
|
||||
end
|
||||
if nHistoryPos then
|
||||
sLine = _tHistory[nHistoryPos]
|
||||
nPos, nScroll = #sLine, 0
|
||||
else
|
||||
sLine = ""
|
||||
nPos, nScroll = 0, 0
|
||||
end
|
||||
uncomplete()
|
||||
redraw()
|
||||
|
||||
end
|
||||
|
||||
elseif param == keys.back then
|
||||
-- Backspace
|
||||
if nPos > 0 then
|
||||
clear()
|
||||
sLine = string.sub(sLine, 1, nPos - 1) .. string.sub(sLine, nPos + 1)
|
||||
nPos = nPos - 1
|
||||
if nScroll > 0 then nScroll = nScroll - 1 end
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
|
||||
elseif param == keys.home then
|
||||
-- Home
|
||||
if nPos > 0 then
|
||||
clear()
|
||||
nPos = 0
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
|
||||
elseif param == keys.delete then
|
||||
-- Delete
|
||||
if nPos < #sLine then
|
||||
clear()
|
||||
sLine = string.sub(sLine, 1, nPos) .. string.sub(sLine, nPos + 2)
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
|
||||
elseif param == keys["end"] then
|
||||
-- End
|
||||
if nPos < #sLine then
|
||||
clear()
|
||||
nPos = #sLine
|
||||
recomplete()
|
||||
redraw()
|
||||
end
|
||||
|
||||
elseif param == keys.tab then
|
||||
-- Tab (accept autocomplete)
|
||||
acceptCompletion()
|
||||
|
||||
end
|
||||
|
||||
elseif sEvent == "mouse_down" or sEvent == "mouse_drag" and param == 1 then
|
||||
local _, cy = term.getPos()
|
||||
if param1 >= sx and param1 <= w and param2 == cy then
|
||||
-- Ensure we don't scroll beyond the current line
|
||||
nPos = math.min(math.max(nScroll + param1 - sx, 0), #sLine)
|
||||
redraw()
|
||||
end
|
||||
|
||||
elseif sEvent == "term_resize" then
|
||||
-- Terminal resized
|
||||
w = term.getSize()
|
||||
redraw()
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
local _, cy = term.getPos()
|
||||
term.setBlink(false)
|
||||
term.setPos(w + 1, cy)
|
||||
print()
|
||||
|
||||
return sLine
|
||||
end
|
||||
|
||||
|
||||
--[[function io.write(text)
|
||||
text = tostring(text)
|
||||
|
||||
local lines = 0
|
||||
|
|
@ -28,7 +368,7 @@ function io.write(text)
|
|||
local chunk = text:sub(1, nl)
|
||||
text = text:sub(#chunk + 1)
|
||||
|
||||
local has_nl = chunk:sub( -1) == "\n"
|
||||
local has_nl = chunk:sub(-1) == "\n"
|
||||
if has_nl then chunk = chunk:sub(1, -2) end
|
||||
|
||||
local cx, cy = term.getPos()
|
||||
|
|
@ -83,7 +423,7 @@ function io.read(replace, history, complete, default)
|
|||
|
||||
local function clearCompletion()
|
||||
if completions[comp_id] then
|
||||
write((" "):rep(#completions[comp_id]))
|
||||
io.write((" "):rep(#completions[comp_id]))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -142,7 +482,7 @@ function io.read(replace, history, complete, default)
|
|||
elseif cursor_pos == #buffer then
|
||||
buffer = par1 .. buffer
|
||||
else
|
||||
buffer = buffer:sub(0, -cursor_pos - 1) .. par1 .. buffer:sub( -cursor_pos)
|
||||
buffer = buffer:sub(0, -cursor_pos - 1) .. par1 .. buffer:sub(-cursor_pos)
|
||||
end
|
||||
elseif evt == "key_down" then
|
||||
if par1 == keys.back and #buffer > 0 then
|
||||
|
|
@ -151,7 +491,7 @@ function io.read(replace, history, complete, default)
|
|||
buffer = buffer:sub(1, -2)
|
||||
clearCompletion()
|
||||
elseif cursor_pos < #buffer then
|
||||
buffer = buffer:sub(0, -cursor_pos - 2) .. buffer:sub( -cursor_pos)
|
||||
buffer = buffer:sub(0, -cursor_pos - 2) .. buffer:sub(-cursor_pos)
|
||||
end
|
||||
elseif par1 == keys.delete and cursor_pos > 0 then
|
||||
dirty = true
|
||||
|
|
@ -161,7 +501,7 @@ function io.read(replace, history, complete, default)
|
|||
elseif cursor_pos == 1 then
|
||||
buffer = buffer:sub(1, -2)
|
||||
else
|
||||
buffer = buffer:sub(0, -cursor_pos - 1) .. buffer:sub( -cursor_pos + 1)
|
||||
buffer = buffer:sub(0, -cursor_pos - 1) .. buffer:sub(-cursor_pos + 1)
|
||||
end
|
||||
cursor_pos = cursor_pos - 1
|
||||
elseif par1 == keys.up then
|
||||
|
|
@ -243,7 +583,7 @@ function io.read(replace, history, complete, default)
|
|||
buffer = text .. buffer
|
||||
else
|
||||
buffer = buffer:sub(0, -cursor_pos - 1) .. text ..
|
||||
buffer:sub( -cursor_pos + (#text - 1))
|
||||
buffer:sub(-cursor_pos + (#text - 1))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -255,6 +595,7 @@ function io.read(replace, history, complete, default)
|
|||
|
||||
return buffer
|
||||
end
|
||||
]]
|
||||
|
||||
io.stderr = {}
|
||||
|
||||
|
|
|
|||
3
Capy64/Assets/Lua/CapyOS/sys/lib/lib.lua
Normal file
3
Capy64/Assets/Lua/CapyOS/sys/lib/lib.lua
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
local x = math.random()
|
||||
|
||||
return x
|
||||
182
Capy64/Assets/Lua/CapyOS/sys/lib/scheduler.lua
Normal file
182
Capy64/Assets/Lua/CapyOS/sys/lib/scheduler.lua
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
local expect = require("expect").expect
|
||||
local tableutils = require("tableutils")
|
||||
local event = require("event")
|
||||
|
||||
local scheduler = {}
|
||||
|
||||
local function contains(array, value)
|
||||
for k, v in pairs(array) do
|
||||
if v == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local tasks = {}
|
||||
local processes = 0
|
||||
|
||||
local Task = {}
|
||||
local TaskMeta = {
|
||||
__index = Task,
|
||||
__name = "OS_TASK",
|
||||
__tostring = function(self)
|
||||
return string.format("OS_TASK[%s]: %d", self.source or "", self.pid or 0)
|
||||
end,
|
||||
}
|
||||
local function newTask()
|
||||
local task = {}
|
||||
return setmetatable(task, TaskMeta)
|
||||
end
|
||||
|
||||
function Task:queue(eventName, ...)
|
||||
expect(1, eventName, "string")
|
||||
event.push("scheduler", self.pid, eventName, ...)
|
||||
end
|
||||
|
||||
local function findParent()
|
||||
local i = 3
|
||||
|
||||
while true do
|
||||
local info = debug.getinfo(i)
|
||||
if not info then
|
||||
break
|
||||
end
|
||||
|
||||
for pid, task in pairs(tasks) do
|
||||
if task.uuid == tostring(info.func) then
|
||||
return task
|
||||
end
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function cascadeKill(pid, err)
|
||||
local task = tasks[pid]
|
||||
if not task then
|
||||
return
|
||||
end
|
||||
for i, cpid in ipairs(task.children) do
|
||||
cascadeKill(cpid, err)
|
||||
end
|
||||
if task.parent then
|
||||
local parent = tasks[task.parent]
|
||||
if parent then
|
||||
local index = tableutils.find(parent.children, task.pid)
|
||||
table.remove(parent.children, index)
|
||||
parent:queue("scheduler_task_end", task, err == nil, err)
|
||||
end
|
||||
else
|
||||
if err then
|
||||
error(err, 0)
|
||||
end
|
||||
end
|
||||
if task then
|
||||
task.killed = true
|
||||
coroutine.close(task.thread)
|
||||
tasks[pid] = nil
|
||||
processes = processes - 1
|
||||
end
|
||||
end
|
||||
|
||||
local function resumeTask(task, yieldPars)
|
||||
local pars = table.pack(coroutine.resume(task.thread, table.unpack(yieldPars)))
|
||||
if pars[1] then
|
||||
task.filters = table.pack(table.unpack(pars, 2))
|
||||
return coroutine.status(task.thread) ~= "dead"
|
||||
else
|
||||
cascadeKill(task.pid, pars[2])
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function scheduler.spawn(func, options)
|
||||
expect(1, func, "function")
|
||||
expect(2, options, "nil", "table")
|
||||
|
||||
options = options or {}
|
||||
options.args = options.args or {}
|
||||
|
||||
local source = debug.getinfo(2)
|
||||
|
||||
local task = newTask()
|
||||
local pid = #tasks + 1
|
||||
task.pid = pid
|
||||
task.options = options
|
||||
task.source = source.source
|
||||
task.uuid = tostring(func)
|
||||
task.thread = coroutine.create(func)
|
||||
local parent = findParent()
|
||||
if parent then
|
||||
task.parent = parent.pid
|
||||
table.insert(parent.children, pid)
|
||||
end
|
||||
task.filters = {}
|
||||
task.children = {}
|
||||
task.eventQueue = {}
|
||||
task.skip = true
|
||||
|
||||
tasks[pid] = task
|
||||
|
||||
processes = processes + 1
|
||||
|
||||
return task, resumeTask(task, task.options.args)
|
||||
end
|
||||
|
||||
function scheduler.kill(pid)
|
||||
expect(1, pid, "number")
|
||||
cascadeKill(pid)
|
||||
end
|
||||
|
||||
function scheduler.ipc(pid, ...)
|
||||
expect(1, pid, "number")
|
||||
if not tasks[pid] then
|
||||
error("process by pid " .. pid .. " does not exist.", 2)
|
||||
end
|
||||
|
||||
local sender = findParent()
|
||||
tasks[pid]:queue("ipc_message", sender, ...)
|
||||
end
|
||||
|
||||
local running = false
|
||||
function scheduler.init()
|
||||
if running then
|
||||
error("scheduler already running", 2)
|
||||
end
|
||||
running = true
|
||||
|
||||
local ev = { n = 0 }
|
||||
while processes > 0 do
|
||||
for pid, task in pairs(tasks) do
|
||||
local yieldPars = ev
|
||||
if ev[1] == "scheduler" and ev[2] == pid then
|
||||
yieldPars = table.pack(table.unpack(ev, 3))
|
||||
end
|
||||
if yieldPars[1] ~= "scheduler" and not task.filters or #task.filters == 0 or contains(task.filters, yieldPars[1]) or yieldPars[1] == "interrupt" then
|
||||
if task.skip then
|
||||
task.skip = false
|
||||
else
|
||||
resumeTask(task, yieldPars)
|
||||
end
|
||||
end
|
||||
|
||||
if coroutine.status(task.thread) == "dead" then
|
||||
cascadeKill(pid)
|
||||
end
|
||||
end
|
||||
|
||||
if processes <= 0 then
|
||||
break
|
||||
end
|
||||
|
||||
ev = table.pack(coroutine.yield())
|
||||
end
|
||||
|
||||
running = false
|
||||
end
|
||||
|
||||
return scheduler
|
||||
112
Capy64/Assets/Lua/CapyOS/sys/lib/shell/package.lua
Normal file
112
Capy64/Assets/Lua/CapyOS/sys/lib/shell/package.lua
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
local expect = require("expect").expect
|
||||
local fs = require("fs")
|
||||
local nativePackage = package
|
||||
|
||||
local function copyTable(source, target)
|
||||
target = target or {}
|
||||
|
||||
for k, v in pairs(source) do
|
||||
target[k] = v
|
||||
end
|
||||
|
||||
return target
|
||||
end
|
||||
|
||||
local hostPackages = copyTable(nativePackage._host)
|
||||
|
||||
local function createPreloadSearcher(envPackage)
|
||||
return function(name)
|
||||
if not envPackage.preload[name] then
|
||||
return string.format("no field package.preload['%s']", name)
|
||||
end
|
||||
return envPackage.preload[name], ":preload:"
|
||||
end
|
||||
end
|
||||
|
||||
local function createLoaderSearcher(envPackage)
|
||||
return function(name)
|
||||
local path, err = envPackage.searchpath(name, envPackage.path)
|
||||
|
||||
if not path then
|
||||
return err
|
||||
end
|
||||
|
||||
local func, err = loadfile(path)
|
||||
if not func then
|
||||
return string.format("error loading module '%s' from file '%s':\t%s", name, path, err)
|
||||
end
|
||||
|
||||
return func, path
|
||||
end
|
||||
end
|
||||
|
||||
local function createEnvironment(filePath)
|
||||
local envPackage = {
|
||||
cpath = nativePackage.cpath,
|
||||
searchpath = nativePackage.searchpath,
|
||||
config = nativePackage.config,
|
||||
searchers = {},
|
||||
loaded = {},
|
||||
preload = {},
|
||||
}
|
||||
|
||||
local dirName = fs.getDir(filePath)
|
||||
--envPackage.path = string.format("%s/?.lua;%s/?/init.lua;", dirName, dirName) .. nativePackage.path
|
||||
envPackage.path = nativePackage.path
|
||||
|
||||
envPackage.searchers[1] = createPreloadSearcher(envPackage)
|
||||
envPackage.searchers[2] = createLoaderSearcher(envPackage)
|
||||
|
||||
local function envRequire(modname)
|
||||
expect(1, modname, "string", "number")
|
||||
modname = tostring(modname)
|
||||
|
||||
if envPackage.loaded[modname] then
|
||||
return envPackage.loaded[modname]
|
||||
end
|
||||
|
||||
local errorOutput = ""
|
||||
local libFunction, libPath
|
||||
for i = 1, #envPackage.searchers do
|
||||
local par, path = envPackage.searchers[i](modname)
|
||||
if type(par) == "function" then
|
||||
libFunction, libPath = par, path
|
||||
break
|
||||
else
|
||||
errorOutput = errorOutput .. "\n\t" .. par
|
||||
end
|
||||
end
|
||||
|
||||
if not libFunction then
|
||||
error(string.format("module '%s' not found:%s", modname, errorOutput), 2)
|
||||
end
|
||||
|
||||
local ok, par = pcall(libFunction)
|
||||
if not ok then
|
||||
error(par, 0)
|
||||
end
|
||||
|
||||
if par == nil then
|
||||
envPackage.loaded[modname] = true
|
||||
return true
|
||||
end
|
||||
|
||||
envPackage.loaded[modname] = par
|
||||
|
||||
return par, libPath
|
||||
end
|
||||
|
||||
copyTable(hostPackages, envPackage.loaded)
|
||||
envPackage.loaded.package = envPackage
|
||||
|
||||
local env_G = copyTable(envPackage.loaded._G or _G)
|
||||
envPackage.loaded._G = env_G
|
||||
env_G._G = env_G
|
||||
|
||||
envPackage.loaded._G.package = envPackage
|
||||
envPackage.loaded._G.require = envRequire
|
||||
|
||||
return envPackage, envRequire
|
||||
end
|
||||
|
||||
return createEnvironment
|
||||
87
Capy64/Assets/Lua/CapyOS/sys/lib/tableutils.lua
Normal file
87
Capy64/Assets/Lua/CapyOS/sys/lib/tableutils.lua
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
local expect = require("expect").expect
|
||||
local tableutils = {}
|
||||
|
||||
local function serialize(data, circular)
|
||||
expect(1, data, "string", "table", "number", "boolean", "nil")
|
||||
if type(data) == "table" then
|
||||
if not circular then
|
||||
circular = {}
|
||||
end
|
||||
local output = "{"
|
||||
for k, v in pairs(data) do
|
||||
if type(v) == "table" then
|
||||
local name = tostring(v)
|
||||
if circular[name] then
|
||||
error("circular reference in table", 2)
|
||||
end
|
||||
circular[name] = true
|
||||
end
|
||||
output = output .. string.format("[%q] = %s,", k, serialize(v, circular))
|
||||
end
|
||||
output = output .. "}"
|
||||
return output
|
||||
else
|
||||
return string.format("%q", data)
|
||||
end
|
||||
end
|
||||
|
||||
function tableutils.serialize(data)
|
||||
expect(1, data, "string", "table", "number", "boolean", "nil")
|
||||
return serialize(data)
|
||||
end
|
||||
|
||||
function tableutils.deserialize(data)
|
||||
local func, err = load("return " .. data, "=tableutils", "t", {})
|
||||
if not func then
|
||||
error(err, 2)
|
||||
end
|
||||
return func()
|
||||
end
|
||||
|
||||
local function prettyvalue(value)
|
||||
if type(value) == "table" or type(value) == "function" or type(value) == "thread" or type(value) == "userdata" or type(value) == "number" then
|
||||
return tostring(value)
|
||||
else
|
||||
return string.format("%q", value)
|
||||
end
|
||||
end
|
||||
|
||||
function tableutils.pretty(data)
|
||||
if type(data) == "table" then
|
||||
local output = "{"
|
||||
|
||||
local index = 0
|
||||
for k, v in pairs(data) do
|
||||
local value = prettyvalue(v)
|
||||
|
||||
if type(k) == "number" and k - 1 == index then
|
||||
index = index + 1
|
||||
output = output .. string.format("\n %s,", value)
|
||||
elseif type(k) == "string" and k:match("^[%a_][%w_]*$") then
|
||||
output = output .. string.format("\n %s = %s,", k, value)
|
||||
else
|
||||
output = output .. string.format("\n [%s] = %s,", prettyvalue(k), value)
|
||||
end
|
||||
end
|
||||
if output == "{" then
|
||||
return "{}"
|
||||
end
|
||||
output = output .. "\n}"
|
||||
|
||||
return output
|
||||
else
|
||||
return prettyvalue(data)
|
||||
end
|
||||
end
|
||||
|
||||
function tableutils.find(tbl, element)
|
||||
for i = 1, #tbl do
|
||||
if tbl[i] == element then
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
return tableutils
|
||||
2
Capy64/Assets/Lua/CapyOS/sys/share/help/index
Normal file
2
Capy64/Assets/Lua/CapyOS/sys/share/help/index
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Welcome to CapyOS!
|
||||
Run "programs" to get a list of available programs.
|
||||
12
Capy64/Assets/Lua/CapyOS/sys/share/help/license
Normal file
12
Capy64/Assets/Lua/CapyOS/sys/share/help/license
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
CapyOS and Capy64 are licensed under the Apache 2.0 Public License.
|
||||
https://capy64.alexdevs.me/
|
||||
https://github.com/Ale32bit/Capy64
|
||||
|
||||
Some CapyOS components include code from the CC Recrafted project.
|
||||
https://github.com/Ocawesome101/recrafted
|
||||
|
||||
Some CapyOS components may include code from CC: Tweaked.
|
||||
https://github.com/CC-Tweaked/CC-Tweaked
|
||||
|
||||
json.lua by rxi is licensed under the MIT License.
|
||||
https://github.com/rxi/json.lua
|
||||
3
Capy64/Assets/Lua/CapyOS/sys/share/motd.txt
Normal file
3
Capy64/Assets/Lua/CapyOS/sys/share/motd.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
You can get started with Lua by following this manual: https://www.lua.org/manual/
|
||||
Learn more about Capy64 by following the documentation: https://capy64.alexdevs.me/
|
||||
Found a bug or would like to suggest a feature? https://github.com/Ale32bit/Capy64/issues
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
-- This file is part of Capy64 - https://github.com/Capy64/Capy64
|
||||
-- This file is part of Capy64 - https://github.com/Capy64/Capy64
|
||||
-- Copyright 2023 Alessandro "AlexDevs" Proto
|
||||
--
|
||||
-- Licensed under the Apache License, Version 2.0 (the "License").
|
||||
|
|
@ -24,6 +24,9 @@ local event = require("event")
|
|||
local bootSleep = 2
|
||||
local bg = 0x0
|
||||
local fg = 0xffffff
|
||||
local setupbg = 0x0608a6
|
||||
local setupfg = 0xffffff
|
||||
local accent = 0xffea00
|
||||
|
||||
term.setForeground(fg)
|
||||
term.setBackground(bg)
|
||||
|
|
@ -97,11 +100,60 @@ local function promptKey()
|
|||
event.pull("key_down")
|
||||
end
|
||||
|
||||
local function alert(...)
|
||||
local args = {...}
|
||||
table.insert(args, "[ OK ]")
|
||||
local lines = {}
|
||||
local width = 0
|
||||
local padding = 1
|
||||
for k, v in ipairs(args) do
|
||||
lines[k] = tostring(v)
|
||||
width = math.max(width, #tostring(v))
|
||||
end
|
||||
|
||||
lines[#lines] = nil
|
||||
|
||||
local okPad = string.rep(" ", (width - #args[#args]) // 2)
|
||||
local okLine = string.format("%s%s", okPad, args[#args])
|
||||
|
||||
local w, h = term.getSize()
|
||||
local cx, cy = w//2, h//2
|
||||
local dx, dy = cx - (width + padding * 2) // 2, cy - #lines//2 - 1
|
||||
|
||||
local pad = string.rep(" ", padding)
|
||||
local emptyLine = string.format("\u{258C}%s%s%s\u{2590}", pad, string.rep(" ", width), pad)
|
||||
|
||||
term.setPos(dx, dy)
|
||||
print("\u{259B}" .. string.rep("\u{2580}", width + padding * 2) .. "\u{259C}")
|
||||
for k, v in ipairs(lines) do
|
||||
term.setPos(dx, dy + k)
|
||||
local space = string.rep(" ", width - #v)
|
||||
print(string.format("\u{258C}%s%s%s%s\u{2590}", pad, v, space, pad))
|
||||
end
|
||||
term.setPos(dx, dy + #lines + 1)
|
||||
print(emptyLine)
|
||||
term.setPos(dx, dy + #lines + 2)
|
||||
local space = string.rep(" ", width - #okLine)
|
||||
term.write(string.format("\u{258C}%s", pad))
|
||||
term.setForeground(accent)
|
||||
term.write(okLine)
|
||||
term.setForeground(setupfg)
|
||||
print(string.format("%s%s\u{2590}", space, pad))
|
||||
term.setPos(dx, dy + #lines + 3)
|
||||
print("\u{2599}" .. string.rep("\u{2584}", width + padding * 2) .. "\u{259F}")
|
||||
|
||||
local _, key, keyname
|
||||
repeat
|
||||
_, key, keyname = event.pull("key_down")
|
||||
until keyname == "enter" or keyname == "escape"
|
||||
end
|
||||
|
||||
local function installDefaultOS()
|
||||
if fs.exists("/sys") then
|
||||
fs.delete("/sys", true)
|
||||
end
|
||||
installOS()
|
||||
alert("Default OS installed!")
|
||||
end
|
||||
|
||||
term.setBlink(false)
|
||||
|
|
@ -116,6 +168,7 @@ local function setupScreen()
|
|||
"Install default OS",
|
||||
installDefaultOS,
|
||||
},
|
||||
{},
|
||||
{
|
||||
"Exit setup",
|
||||
exit,
|
||||
|
|
@ -127,26 +180,40 @@ local function setupScreen()
|
|||
}
|
||||
|
||||
local selection = 1
|
||||
local function redraw()
|
||||
term.setForeground(fg)
|
||||
term.setBackground(bg)
|
||||
local function redraw(noDrawSel)
|
||||
local w, h = term.getSize()
|
||||
term.setForeground(setupfg)
|
||||
term.setBackground(setupbg)
|
||||
term.clear()
|
||||
term.setPos(1,2)
|
||||
writeCenter("Capy64 Setup")
|
||||
|
||||
term.setPos(1,3)
|
||||
|
||||
term.setForeground(accent)
|
||||
|
||||
for k, v in ipairs(options) do
|
||||
local _, y = term.getPos()
|
||||
term.setPos(1, y + 1)
|
||||
term.clearLine()
|
||||
|
||||
if selection == k then
|
||||
writeCenter("[ " .. v[1] .. " ]")
|
||||
else
|
||||
writeCenter(v[1])
|
||||
if #v > 0 then
|
||||
if selection == k and not noDrawSel then
|
||||
writeCenter("[ " .. v[1] .. " ]")
|
||||
else
|
||||
writeCenter(v[1])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
term.setForeground(setupfg)
|
||||
|
||||
term.setPos(1, h - 2)
|
||||
term.write("\u{2190}\u{2191}\u{2192}\u{2193}: Move")
|
||||
term.setPos(1, h - 1)
|
||||
term.write("Escape: Exit")
|
||||
term.setPos(1, h)
|
||||
term.write("Enter: Select")
|
||||
end
|
||||
|
||||
while true do
|
||||
|
|
@ -154,9 +221,16 @@ local function setupScreen()
|
|||
local ev = { coroutine.yield("key_down") }
|
||||
if ev[3] == "up" then
|
||||
selection = selection - 1
|
||||
if options[selection] and #options[selection] == 0 then
|
||||
selection = selection - 1
|
||||
end
|
||||
elseif ev[3] == "down" then
|
||||
selection = selection + 1
|
||||
if options[selection] and #options[selection] == 0 then
|
||||
selection = selection + 1
|
||||
end
|
||||
elseif ev[3] == "enter" then
|
||||
redraw(true)
|
||||
options[selection][2]()
|
||||
elseif ev[3] == "escape" then
|
||||
exit()
|
||||
|
|
@ -177,7 +251,7 @@ local function bootScreen()
|
|||
term.setPos(1,2)
|
||||
writeCenter("Capy64")
|
||||
term.setPos(1,4)
|
||||
writeCenter("Powered by Capybaras")
|
||||
writeCenter("(c) 2023 AlexDevs")
|
||||
|
||||
term.setPos(1, h - 1)
|
||||
writeCenter("Press F2 to open setup")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"EngineMode": 0,
|
||||
"SafeMode": false,
|
||||
"Window": {
|
||||
"Scale": 2
|
||||
},
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public enum EngineMode
|
|||
|
||||
public class Capy64 : Game
|
||||
{
|
||||
public const string Version = "1.1.0-beta";
|
||||
public const string Version = "1.1.2-beta";
|
||||
|
||||
public static class DefaultParameters
|
||||
{
|
||||
|
|
@ -112,7 +112,6 @@ public class Capy64 : Game
|
|||
private readonly InputManager _inputManager;
|
||||
private RenderTarget2D renderTarget;
|
||||
private readonly GraphicsDeviceManager _graphics;
|
||||
private IServiceProvider _serviceProvider;
|
||||
private ulong _totalTicks = 0;
|
||||
private int tickrate = 0;
|
||||
private int everyTick => 60 / tickrate;
|
||||
|
|
@ -122,7 +121,7 @@ public class Capy64 : Game
|
|||
Instance = this;
|
||||
|
||||
_graphics = new GraphicsDeviceManager(this);
|
||||
Content.RootDirectory = "Content";
|
||||
//Content.RootDirectory = "Content";
|
||||
IsMouseVisible = true;
|
||||
|
||||
EventEmitter = new();
|
||||
|
|
@ -131,11 +130,6 @@ public class Capy64 : Game
|
|||
Drawing = new();
|
||||
}
|
||||
|
||||
public void ConfigureServices(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void SetEngineMode(EngineMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
|
|
@ -265,7 +259,9 @@ public class Capy64 : Game
|
|||
Audio = new Audio();
|
||||
|
||||
NativePlugins = GetNativePlugins();
|
||||
Plugins = PluginLoader.LoadAllPlugins("plugins", _serviceProvider);
|
||||
var safeMode = Configuration.GetValue("SafeMode", false);
|
||||
if (!safeMode)
|
||||
Plugins = PluginLoader.LoadAllPlugins(Path.Combine(AppDataPath, "plugins"));
|
||||
|
||||
EventEmitter.RaiseInit();
|
||||
|
||||
|
|
@ -284,7 +280,6 @@ public class Capy64 : Game
|
|||
foreach (var type in types)
|
||||
{
|
||||
var instance = (IComponent)Activator.CreateInstance(type, this);
|
||||
//var instance = (IComponent)ActivatorUtilities.CreateInstance(_serviceProvider, type)!;
|
||||
plugins.Add(instance);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RollForward>Major</RollForward>
|
||||
<PublishReadyToRun>false</PublishReadyToRun>
|
||||
<TieredCompilation>false</TieredCompilation>
|
||||
|
|
@ -37,13 +37,13 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||
<PackageReference Include="FontStashSharp.MonoGame" Version="1.2.8" />
|
||||
<PackageReference Include="KeraLua" Version="1.3.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageReference Include="FontStashSharp.MonoGame" Version="1.3.6" />
|
||||
<PackageReference Include="KeraLua" Version="1.4.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||
<PackageReference Include="MonoGame.Extended.Graphics" Version="3.8.0" />
|
||||
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
|
||||
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" />
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ internal class PluginLoader
|
|||
return loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(path)));
|
||||
}
|
||||
|
||||
public static List<IComponent> LoadAllPlugins(string pluginsPath, IServiceProvider provider)
|
||||
public static List<IComponent> LoadAllPlugins(string pluginsPath)
|
||||
{
|
||||
if (!Directory.Exists(pluginsPath))
|
||||
Directory.CreateDirectory(pluginsPath);
|
||||
|
|
|
|||
|
|
@ -134,6 +134,9 @@ public class FileSystemLib : IComponent
|
|||
// Get drive root (C:\ for Windows, / for *nix)
|
||||
var rootPath = Path.GetFullPath(Path.GetPathRoot("/") ?? "/");
|
||||
|
||||
var invalidPathChars = Path.GetInvalidPathChars();
|
||||
path = invalidPathChars.Aggregate(path, (current, invalidChar) => current.Replace(invalidChar, ' '));
|
||||
|
||||
// Join path to rootPath and resolves to absolute path
|
||||
// Relative paths are resolved here (es. ../ and ./)
|
||||
var absolutePath = Path.GetFullPath(path, rootPath);
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ internal class RuntimeManager : IComponent
|
|||
luaState = new LuaState();
|
||||
_game.LuaRuntime = luaState;
|
||||
luaState.Init();
|
||||
CopyHostLibraries();
|
||||
|
||||
emitter = new(_game.EventEmitter, luaState);
|
||||
|
||||
|
|
@ -162,11 +163,31 @@ internal class RuntimeManager : IComponent
|
|||
}
|
||||
}
|
||||
|
||||
private void CopyHostLibraries()
|
||||
{
|
||||
// table that will contain host libraries
|
||||
luaState.Thread.NewTable();
|
||||
|
||||
luaState.Thread.GetGlobal("package");
|
||||
luaState.Thread.GetField(-1, "loaded");
|
||||
|
||||
luaState.Thread.PushNil();
|
||||
while (luaState.Thread.Next(-2))
|
||||
{
|
||||
var libname = luaState.Thread.ToString(-2);
|
||||
luaState.Thread.SetField(1, libname);
|
||||
}
|
||||
|
||||
luaState.Thread.Rotate(1, -1);
|
||||
luaState.Thread.SetField(1, "_host");
|
||||
luaState.Thread.SetTop(0);
|
||||
}
|
||||
|
||||
private void LoadFirmware()
|
||||
{
|
||||
var firmwareContent = File.ReadAllText(Path.Combine(Capy64.AssetsPath, "Lua/firmware.lua"));
|
||||
var errored = luaState.Thread.DoString(firmwareContent);
|
||||
if(errored)
|
||||
if (errored)
|
||||
{
|
||||
throw new LuaException(luaState.Thread.ToString(-1));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
|
|
|
|||
BIN
Resources/256.png
Normal file
BIN
Resources/256.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
11
Resources/Capy64.desktop
Normal file
11
Resources/Capy64.desktop
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
|
||||
Name=Capy64
|
||||
Comment=Lua Fantasy Computer
|
||||
Categories=Game;Emulator;
|
||||
|
||||
Icon=me.alexdevs.Capy64
|
||||
Exec=Capy64
|
||||
Terminal=false
|
||||
37
Resources/appdata.xml
Normal file
37
Resources/appdata.xml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>me.alexdevs.Capy64</id>
|
||||
<launchable type="desktop-id">me.alexdevs.Capy64.desktop</launchable>
|
||||
|
||||
<name>Capy64</name>
|
||||
<summary>Lua Fantasy Computer</summary>
|
||||
<developer_name>AlexDevs</developer_name>
|
||||
<url>https://capy64.alexdevs.me/</url>
|
||||
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<project_license>Apache-2.0</project_license>
|
||||
|
||||
<content_rating type="oars-1.1" />
|
||||
|
||||
<supports>
|
||||
<control>pointing</control>
|
||||
<control>keyboard</control>
|
||||
<control>gamepad</control>
|
||||
</supports>
|
||||
|
||||
<description>
|
||||
<p>
|
||||
Capy64 is a fantasy console that runs sandbox Lua 5.4.
|
||||
</p>
|
||||
<p>
|
||||
Create anything, from Hello Worlds to games!
|
||||
</p>
|
||||
</description>
|
||||
|
||||
<releases>
|
||||
<release version="1.1.2" date="2023-07-24" type="beta">
|
||||
<url>https://github.com/Ale32bit/Capy64/releases/tag/v1.1.2-beta</url>
|
||||
</release>
|
||||
</releases>
|
||||
|
||||
</component>
|
||||
Loading…
Add table
Reference in a new issue