Added paste event.

Added proper shell
Added shell commands
This commit is contained in:
Alessandro Proto 2023-01-15 16:44:42 +01:00
parent 6b26e8f66f
commit ad2166831b
19 changed files with 389 additions and 23 deletions

View file

@ -0,0 +1,17 @@
local args = {...}
local fs = require("fs")
local dir = args[1]
if #args == 0 then
dir = shell.homePath
end
dir = fs.combine(shell.getDir(), dir)
if not fs.exists(dir) or not fs.getAttributes(dir).isDirectory then
error("No such directory: " .. dir, 0)
return false
end
shell.setDir(dir)

View file

@ -0,0 +1,5 @@
local term = require("term")
local colors = require("colors")
term.setBackground(colors.black)
term.clear()
term.setPos(1, 1)

View file

@ -0,0 +1 @@
shell.exit()

View file

@ -0,0 +1,32 @@
local args = { ... }
local fs = require("fs")
local term = require("term")
local colors = require("colors")
local dir = shell.getDir()
if args[1] then
dir = fs.combine(shell.getDir(), args[1])
end
if not fs.exists(dir) then
error("No such directory: " .. dir, 0)
return false
end
local attr = fs.getAttributes(dir)
if not attr.isDirectory then
error("No such directory: " .. dir, 0)
return false
end
local files = fs.list(dir)
for k,v in ipairs(files) do
local attr = fs.getAttributes(fs.combine(dir, v))
if attr.isDirectory then
term.setForeground(colors.lightBlue)
print(v .. "/")
else
term.setForeground(colors.white)
print(v)
end
end

View file

@ -30,13 +30,13 @@ for k, v in pairs(package.loaded) do
end end
term.setForeground(colours.yellow) term.setForeground(colours.yellow)
print("Interactive Lua prompt.") print(_VERSION .. " interactive prompt")
print("Call exit() to exit.") print("Call exit() to exit.")
term.setForeground(colours.white) term.setForeground(colours.white)
while bRunning do while bRunning do
term.setForeground( colours.yellow ) term.setForeground( colours.yellow )
write("lua> ") write("> ")
term.setForeground( colours.white ) term.setForeground( colours.white )
local s = io.read(nil, tCommandHistory) local s = io.read(nil, tCommandHistory)

View file

@ -0,0 +1,14 @@
local fs = require("fs")
local args = {...}
if #args == 0 then
print("Usage: mkdir <directory>")
return
end
local dir = fs.combine(shell.getDir(), args[1])
if fs.exists(dir) then
error("Path already exists", 0)
end
fs.makeDir(dir)

View file

@ -0,0 +1 @@
print(shell.getDir())

View file

@ -0,0 +1,7 @@
local timer = require("timer")
print("Goodbye!")
timer.sleep(1000)
os.shutdown(true)

View file

@ -0,0 +1,10 @@
local fs = require("fs")
local args = {...}
if #args == 0 then
print("Usage: rm <file>")
return
end
local file = fs.combine(shell.getDir(), args[1])
fs.delete(file, true)

View file

@ -1,32 +1,131 @@
local term = require("term") local term = require("term")
local colors = require("colors") local colors = require("colors")
local event = require("event")
local io = require("io") local io = require("io")
local fs = require("fs")
local exit = false
local shell = {} local shell = {}
shell.path = "/bin/?.lua" shell.path = "./?.lua;./?;/bin/?.lua"
shell.homePath = "/home"
local currentDir = shell.homePath
local function buildEnvironment()
return setmetatable({
shell = shell,
}, { __index = _G })
end
local function printError(...)
local cfg = {term.getForeground()}
local cbg = {term.getBackground()}
term.setForeground(colors.red)
term.setBackground(colors.black)
print(...)
term.setForeground(table.unpack(cfg))
term.setBackground(table.unpack(cbg))
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
end
function shell.getDir() function shell.getDir()
return currentDir
end end
function shell.setDir(path) function shell.setDir(path)
currentDir = path
end end
function shell.resolve(path) function shell.resolve(path)
if path:sub(1, 1) == "/" then
return path
end
for seg in shell.path:gmatch("[^;]+") do
local resolved = seg:gsub("%?", path)
if fs.exists(resolved) then
return resolved
end
end
end end
function shell.run(path, args) function shell.run(...)
local args = tokenise(...)
local command = args[1]
local path = shell.resolve(command)
if not path then
printError("Command not found: " .. command)
return false
end
local env = buildEnvironment()
local func, err = loadfile(path, "t", env)
if not func then
printError(err)
return false
end
local ok, err = pcall(func, table.unpack(args, 2))
if not ok then
printError(err)
return false
end
return true
end end
function shell.exit()
exit = true
end
while true do local history = {}
term.setForeground(colors.yellow) local lastExecSuccess = true
write("$ ") while not exit do
term.setForeground(colors.white) term.setForeground(colors.white)
local line = io.read() write(":")
end term.setForeground(colors.lightBlue)
local currentDir = shell.getDir()
if currentDir == shell.homePath then
write("~")
else
write(currentDir)
end
if lastExecSuccess then
term.setForeground(colors.yellow)
else
term.setForeground(colors.red)
end
write("$ ")
term.setForeground(colors.white)
local line = io.read(nil, history)
if line:match("%S") and history[#history] ~= line then
table.insert(history, line)
end
if line:match("%S") then
lastExecSuccess = shell.run(line)
end
end

View file

@ -0,0 +1,7 @@
local timer = require("timer")
print("Goodbye!")
timer.sleep(1000)
os.shutdown(false)

View file

@ -0,0 +1,32 @@
local http = require("http")
local fs = require("fs")
local args = {...}
if not http then
error("HTTP is not enabled", 0)
end
if #args == 0 then
print("Usage: wget <url> [outputPath]")
return false
end
local outputName = args[2] or fs.getName(args[1])
local outputPath = fs.combine(shell.getDir(), outputName)
if not http.checkURL(args[1]) then
error("Invalid URL", 0)
end
local response, err = http.get(args[1])
if not response then
error(err, 0)
end
local file = fs.open(outputPath, "w")
file:write(response:readAll())
file:close()
response:close()
print("File written to " .. outputPath)

View file

@ -6,8 +6,24 @@ term.setSize(51, 19)
term.setForeground(0x59c9ff) term.setForeground(0x59c9ff)
term.setBackground(colors.black) term.setBackground(colors.black)
term.clear() term.clear()
term.setPos(1,1) term.setPos(1, 1)
print(os.version()) print(os.version())
dofile("/bin/lua.lua") local func, err = loadfile("/bin/shell.lua")
if func then
while true do
local ok, err = pcall(func)
if not ok then
print(err)
end
end
else
print(err)
end
print("Press any key to continue...")
coroutine.yield("key_down")
os.shutdown(false)

View file

@ -1,4 +1,5 @@
using Capy64.Eventing; using Capy64.Core;
using Capy64.Eventing;
using Capy64.Eventing.Events; using Capy64.Eventing.Events;
using Capy64.LuaRuntime; using Capy64.LuaRuntime;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
@ -109,6 +110,20 @@ internal class RuntimeInputEvents
BypassFilter = true, BypassFilter = true,
}); });
} }
else if (e.Key == Keys.V)
{
if (SDL.HasClipboardText())
{
var text = SDL.GetClipboardText();
_runtime.PushEvent(new LuaEvent()
{
Name = "paste",
Parameters = new[] {
text,
},
});
}
}
} }
} }

21
Capy64/Core/SDL.cs Normal file
View file

@ -0,0 +1,21 @@
using Capy64.Extensions.Bindings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Capy64.Core;
public class SDL
{
public static string GetClipboardText()
{
return SDL2.UTF8_ToManaged(SDL2.Native_SDL_GetClipboardText(), true);
}
public static bool HasClipboardText()
{
return SDL2.SDL_HasClipboardText() == 1;
}
}

View file

@ -9,9 +9,83 @@ public partial class SDL2
[LibraryImport(SDL)] [LibraryImport(SDL)]
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
public static partial void SDL_MaximizeWindow(IntPtr window); internal static partial void SDL_MaximizeWindow(IntPtr window);
[LibraryImport(SDL)] [LibraryImport(SDL)]
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
public static partial uint SDL_GetWindowFlags(IntPtr window); internal static partial uint SDL_GetWindowFlags(IntPtr window);
[LibraryImport(SDL, EntryPoint = "SDL_GetClipboardText")]
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
internal static partial IntPtr Native_SDL_GetClipboardText();
/// <summary>
/// </summary>
/// <returns>0 is false; 1 is true</returns>
[LibraryImport(SDL)]
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
internal static partial int SDL_HasClipboardText();
[LibraryImport(SDL)]
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
internal static partial void SDL_free(IntPtr memblock);
/// <summary>
/// https://github.com/flibitijibibo/SDL2-CS/blob/master/src/SDL2.cs#L128
/// </summary>
/// <param name="s"></param>
/// <param name="freePtr"></param>
/// <returns></returns>
public static unsafe string UTF8_ToManaged(IntPtr s, bool freePtr = false)
{
if (s == IntPtr.Zero)
{
return null;
}
/* We get to do strlen ourselves! */
byte* ptr = (byte*)s;
while (*ptr != 0)
{
ptr++;
}
/* TODO: This #ifdef is only here because the equivalent
* .NET 2.0 constructor appears to be less efficient?
* Here's the pretty version, maybe steal this instead:
*
string result = new string(
(sbyte*) s, // Also, why sbyte???
0,
(int) (ptr - (byte*) s),
System.Text.Encoding.UTF8
);
* See the CoreCLR source for more info.
* -flibit
*/
#if NETSTANDARD2_0
/* Modern C# lets you just send the byte*, nice! */
string result = System.Text.Encoding.UTF8.GetString(
(byte*) s,
(int) (ptr - (byte*) s)
);
#else
/* Old C# requires an extra memcpy, bleh! */
int len = (int)(ptr - (byte*)s);
if (len == 0)
{
return string.Empty;
}
char* chars = stackalloc char[len];
int strLen = System.Text.Encoding.UTF8.GetChars((byte*)s, len, chars, len);
string result = new string(chars, 0, strLen);
#endif
/* Some SDL functions will malloc, we have to free! */
if (freePtr)
{
SDL_free(s);
}
return result;
}
} }

View file

@ -117,7 +117,16 @@ public class FileSystem : IPlugin
var absolutePath = Path.GetFullPath(path, rootPath); var absolutePath = Path.GetFullPath(path, rootPath);
// Trim root from path // Trim root from path
return absolutePath.Remove(0, rootPath.Length); string localPath;
try
{
localPath = absolutePath.Remove(0, rootPath.Length);
}
catch
{
localPath = absolutePath;
}
return Path.Join("/", localPath);
} }
public static string Resolve(string path) public static string Resolve(string path)
@ -210,7 +219,7 @@ public class FileSystem : IPlugin
parts.Add(pathPart); parts.Add(pathPart);
} }
var result = Path.Join(parts.ToArray()); var result = Path.Combine(parts.ToArray());
if (string.IsNullOrEmpty(result)) if (string.IsNullOrEmpty(result))
{ {
L.PushString(""); L.PushString("");

View file

@ -16,6 +16,7 @@ public class HTTP : IPlugin
private static HttpClient _client; private static HttpClient _client;
private static long RequestId; private static long RequestId;
private readonly IConfiguration _configuration;
private readonly LuaRegister[] HttpLib = new LuaRegister[] private readonly LuaRegister[] HttpLib = new LuaRegister[]
{ {
new() new()
@ -36,11 +37,13 @@ public class HTTP : IPlugin
RequestId = 0; RequestId = 0;
_client = new(); _client = new();
_client.DefaultRequestHeaders.Add("User-Agent", $"Capy64/{Capy64.Version}"); _client.DefaultRequestHeaders.Add("User-Agent", $"Capy64/{Capy64.Version}");
_configuration = configuration;
} }
public void LuaInit(Lua L) public void LuaInit(Lua L)
{ {
L.RequireF("http", Open, false); if (_configuration.GetValue<bool>("HTTP:Enable"))
L.RequireF("http", Open, false);
} }
private int Open(IntPtr state) private int Open(IntPtr state)

View file

@ -120,10 +120,13 @@ public class Runtime
{ {
if (Disposing) if (Disposing)
return false; return false;
filters = new string[pars]; if (status == LuaStatus.Yield)
for (int i = 0; i < pars; i++)
{ {
filters[i] = Thread.OptString(i + 1, null); filters = new string[pars];
for (int i = 0; i < pars; i++)
{
filters[i] = Thread.OptString(i + 1, null);
}
} }
Thread.Pop(pars); Thread.Pop(pars);
return status == LuaStatus.Yield; return status == LuaStatus.Yield;