feat(installer): 重写安装程序为图形化界面

添加图形用户界面,改进用户体验
- 实现窗口化界面和进度条显示
- 添加目录选择对话框
- 优化状态显示和进度更新
- 支持鼠标操作和中文显示
This commit is contained in:
2025-08-31 21:47:31 +08:00
parent 39b22cc477
commit 19c7e1fa5e

View File

@@ -1,8 +1,14 @@
-- LeonOS installer
-- LeonOS GUI Installer
local DEFAULT_ROM_DIR = "/rc"
print("Start loading LeonOS installer...")
print("[Installer] Loading module 1")
local json, tu
local w, h
local mainWindow, progressBar, statusText
-- 确保中文字符正常显示
term.setEncoding("utf-8")
-- 下载函数
local function dl(f)
local hand, err = http.get(f, nil, true)
if not hand then
@@ -14,159 +20,260 @@ local function dl(f)
return data
end
print("[Installer] Loading done.")
print("[Installer] Loading module 2")
-- set up package.loaded for LeonOS libs
package.loaded.rc = {
expect = require("cc.expect").expect,
write = write, sleep = sleep
}
print("[Installer] Loading done.")
print("[Installer] Loading module 3")
package.loaded.term = term
package.loaded.colors = colors
_G.require = require
print("[Installer] Loading done.")
print("[Installer] Loading module 4")
function term.at(x, y)
term.setCursorPos(x, y)
return term
end
print("[Installer] Loading done.")
print("[Installer] Loading module 5")
-- 加载GitHub上的文件
local function ghload(f, c)
return assert(load(dl("https://gh.catmak.name/https://raw.githubusercontent.com/"..f),
"="..(c or f), "t", _G))()
end
print("[Installer] Loading done.")
print("[Installer] Loading module 6")
local json = ghload("rxi/json.lua/master/json.lua", "ghload(json)")
package.loaded["rc.json"] = json
print("[Installer] Loading module 7")
-- 加载LeonOS的文件
local function rcload(f)
return ghload(
"Leonmmcoset/LeonOS/refs/heads/main/data/computercraft/lua/rom/"..f, f)
end
print("[Installer] Loading done.")
print("[Installer] Loading module 8")
-- get LeonOS's textutils with its extra utilities
local tu = rcload("apis/textutils.lua")
local function progress(y, a, b)
local progress = a/b
-- 初始化函数
local function init()
-- 加载必要的库
updateStatus("加载必要的库...")
json = ghload("rxi/json.lua/master/json.lua", "ghload(json)")
tu = rcload("apis/textutils.lua")
updateStatus("库加载完成")
local w = term.getSize()
local bar = (" "):rep(math.ceil((w-2) * progress))
term.at(1, y)
tu.coloredPrint(colors.yellow, "[", {fg=colors.white, bg=colors.white}, bar,
{fg=colors.white, bg=colors.black}, (" "):rep((w-2)-#bar),
colors.yellow, "]")
end
print("[Installer] Loading done.")
term.at(1,1).clear()
tu.coloredPrint(colors.yellow,
"LeonOS Installer\n=======================")
print("You are going to install LeonOS to your computer.")
print("This will overwrite any existing files in the installation directory.")
tu.coloredPrint(colors.yellow, "Are you sure? (y/n)")
local confirm = read()
if confirm ~= "y" then
print("Installation cancelled.")
return
end
local ROM_DIR
tu.coloredPrint("Enter installation directory ", colors.yellow, "[",
colors.lightBlue, DEFAULT_ROM_DIR, colors.yellow, "]")
tu.coloredWrite(colors.yellow, "$ installer >>>")
ROM_DIR = read()
if #ROM_DIR == 0 then ROM_DIR = DEFAULT_ROM_DIR end
ROM_DIR = "/"..shell.resolve(ROM_DIR)
settings.set("LeonOS.rom_dir", ROM_DIR)
settings.save()
tu.coloredPrint(colors.white, "Installing LeonOS to ", colors.lightBlue,
ROM_DIR, colors.white)
local function bullet(t)
tu.coloredWrite(colors.red, "- ", colors.white, t)
end
local function ok()
tu.coloredPrint(colors.green, "OK", colors.white)
end
bullet("Getting repository tree...")
local repodata = dl("https://api.github.com/repos/Leonmmcoset/LeonOS/git/trees/main?recursive=1")
repodata = json.decode(repodata)
ok()
bullet("Filtering files...")
local look = "data/computercraft/lua/"
local to_dl = {}
for _, v in pairs(repodata.tree) do
if v.path and v.path:sub(1,#look) == look then
v.path = v.path:sub(#look+1)
v.real_path = v.path:gsub("^/?rom", ROM_DIR)
to_dl[#to_dl+1] = v
-- 设置term.at函数
function term.at(x, y)
term.setCursorPos(x, y)
return term
end
end
ok()
bullet("Creating directories...")
for i=#to_dl, 1, -1 do
local v = to_dl[i]
if v.type == "tree" then
fs.makeDir(fs.combine(v.real_path))
table.remove(to_dl, i)
-- 获取屏幕大小
w, h = term.getSize()
-- 创建主窗口
mainWindow = window.create(term.native(), 1, 1, w, h)
mainWindow.setVisible(true)
-- 绘制标题
mainWindow.setTextColor(colors.yellow)
mainWindow.setBackgroundColor(colors.black)
mainWindow.at(2, 2).write("===== LeonOS 安装程序 ======")
-- 创建状态文本区域
statusText = window.create(mainWindow, 2, 4, w-2, 10)
statusText.setTextColor(colors.white)
statusText.setBackgroundColor(colors.black)
statusText.clear()
statusText.at(1, 1).write("欢迎使用LeonOS安装程序")
statusText.at(1, 2).write("该程序将安装LeonOS到您的计算机")
statusText.at(1, 3).write("安装过程会覆盖目标目录中的现有文件")
-- 创建进度条
progressBar = window.create(mainWindow, 2, h-4, w-2, 3)
progressBar.setTextColor(colors.white)
progressBar.setBackgroundColor(colors.gray)
progressBar.clear()
progressBar.at(1, 1).write("进度: ")
-- 创建按钮
local buttonWidth = 15
local yesButton = window.create(mainWindow, w/2 - buttonWidth - 2, h-8, buttonWidth, 3)
yesButton.setTextColor(colors.white)
yesButton.setBackgroundColor(colors.green)
yesButton.at(3, 2).write("确定")
local noButton = window.create(mainWindow, w/2 + 2, h-8, buttonWidth, 3)
noButton.setTextColor(colors.white)
noButton.setBackgroundColor(colors.red)
noButton.at(3, 2).write("取消")
return yesButton, noButton
end
-- 更新进度条
local function updateProgress(current, total)
progressBar.clear()
local percentage = math.floor((current / total) * 100)
local barWidth = w - 10
local filledWidth = math.floor((current / total) * barWidth)
progressBar.at(1, 1).write("进度: " .. percentage .. "%")
progressBar.at(1, 2).setBackgroundColor(colors.gray)
progressBar.at(1, 2).write(string.rep(" ", barWidth))
progressBar.at(1, 2).setBackgroundColor(colors.green)
progressBar.at(1, 2).write(string.rep(" ", filledWidth))
end
-- 更新状态文本
local function updateStatus(text)
statusText.clear()
local lines = {}
for line in text:gmatch("[^
]+") do
table.insert(lines, line)
end
end
ok()
bullet("Downloading files...")
local okx, oky = term.getCursorPos()
io.write("\n")
local _, pby = term.getCursorPos()
local parallels = {}
local done = 0
for i=1, #to_dl, 1 do
local v = to_dl[i]
if v.type == "blob" then
parallels[#parallels+1] = function()
local data = dl("https://gh.catmak.name/https://raw.githubusercontent.com/Leonmmcoset/LeonOS/refs/heads/main/data/computercraft/lua/"..v.path)
assert(io.open(v.real_path, "w")):write(data):close()
done = done + 1
progress(pby, done, #to_dl)
for i, line in ipairs(lines) do
if i <= statusText.getSize() then
statusText.at(1, i).write(line)
end
end
end
parallel.waitForAll(table.unpack(parallels))
-- 目录选择对话框
local function selectDirDialog(defaultDir)
local dialogWindow = window.create(term.native(), w/4, h/4, w/2, 8)
dialogWindow.setTextColor(colors.white)
dialogWindow.setBackgroundColor(colors.black)
dialogWindow.clear()
dialogWindow.at(2, 2).write("选择安装目录")
dialogWindow.at(2, 4).write("目录: ")
dialogWindow.at(9, 4).write(defaultDir)
term.at(1, pby).write((" "):rep((term.getSize())))
term.at(okx, oky)
ok()
local confirmButton = window.create(dialogWindow, dialogWindow.getSize() - 15, 6, 10, 2)
confirmButton.setTextColor(colors.white)
confirmButton.setBackgroundColor(colors.green)
confirmButton.at(2, 1).write("确认")
assert(io.open(
fs.exists("/startup.lua") and "/unbios-rc.lua" or "/startup.lua", "w"))
:write(dl(
"https://gh.catmak.name/https://raw.githubusercontent.com/Leonmmcoset/LeonOS/refs/heads/main/unbios.lua"
)):close()
local dirInput = defaultDir
local editing = true
tu.coloredPrint(colors.yellow, "Your computer will restart in 3 seconds.")
local _, y = term.getCursorPos()
while editing do
local event = table.pack(os.pullEvent())
if event[1] == "mouse_click" then
local x, y = event[3], event[4]
-- 检查是否点击确认按钮
if x >= dialogWindow.getX() + dialogWindow.getSize() - 15 and
x <= dialogWindow.getX() + dialogWindow.getSize() - 5 and
y >= dialogWindow.getY() + 6 and
y <= dialogWindow.getY() + 7 then
editing = false
end
elseif event[1] == "key" then
if event[2] == keys.enter then
editing = false
elseif event[2] == keys.backspace then
dirInput = dirInput:sub(1, -2)
end
elseif event[1] == "char" then
dirInput = dirInput .. event[2]
end
for i=1, 3, 1 do
progress(y, i, 5)
os.sleep(1)
dialogWindow.at(9, 4).write(string.rep(" ", 30))
dialogWindow.at(9, 4).write(dirInput)
end
dialogWindow.setVisible(false)
return dirInput
end
os.reboot()
-- 主安装函数
local function install(romDir)
updateStatus("正在获取仓库文件列表...\n请稍候...")
-- 获取仓库文件列表
local repodata = dl("https://api.github.com/repos/Leonmmcoset/LeonOS/git/trees/main?recursive=1")
repodata = json.decode(repodata)
updateProgress(1, 5)
updateStatus("正在筛选文件...\n请稍候...")
-- 筛选文件
local look = "data/computercraft/lua/"
local to_dl = {}
for _, v in pairs(repodata.tree) do
if v.path and v.path:sub(1, #look) == look then
v.path = v.path:sub(#look + 1)
v.real_path = v.path:gsub("^/?rom", romDir)
to_dl[#to_dl + 1] = v
end
end
updateProgress(2, 5)
updateStatus("正在创建目录...\n请稍候...")
-- 创建目录
for i = #to_dl, 1, -1 do
local v = to_dl[i]
if v.type == "tree" then
fs.makeDir(fs.combine(v.real_path))
table.remove(to_dl, i)
end
end
updateProgress(3, 5)
updateStatus("正在下载文件...\n这可能需要一些时间...")
-- 下载文件
local done = 0
local parallels = {}
for i = 1, #to_dl, 1 do
local v = to_dl[i]
if v.type == "blob" then
parallels[#parallels + 1] = function()
local data = dl("https://gh.catmak.name/https://raw.githubusercontent.com/Leonmmcoset/LeonOS/refs/heads/main/data/computercraft/lua/" .. v.path)
assert(io.open(v.real_path, "w")):write(data):close()
done = done + 1
updateProgress(3 + (done / #to_dl), 5)
end
end
end
parallel.waitForAll(table.unpack(parallels))
updateProgress(4, 5)
updateStatus("正在设置启动文件...\n请稍候...")
-- 设置启动文件
assert(io.open(
fs.exists("/startup.lua") and "/unbios-rc.lua" or "/startup.lua", "w"))
:write(dl(
"https://gh.catmak.name/https://raw.githubusercontent.com/Leonmmcoset/LeonOS/refs/heads/main/unbios.lua"
)):close()
updateProgress(5, 5)
updateStatus("安装完成!\n计算机将在3秒后重启...")
-- 重启倒计时
for i = 3, 1, -1 do
statusText.at(1, 3).write("计算机将在 " .. i .. " 秒后重启...")
os.sleep(1)
end
os.reboot()
end
-- 主函数
local function main()
term.clear()
local yesButton, noButton = init()
while true do
local event = table.pack(os.pullEvent())
if event[1] == "mouse_click" then
local x, y = event[3], event[4]
-- 检查是否点击"确定"按钮
if x >= yesButton.getX() and x <= yesButton.getX() + yesButton.getSize() and
y >= yesButton.getY() and y <= yesButton.getY() + 2 then
mainWindow.setVisible(false)
local romDir = selectDirDialog(DEFAULT_ROM_DIR)
if #romDir == 0 then romDir = DEFAULT_ROM_DIR end
romDir = "/" .. shell.resolve(romDir)
settings.set("LeonOS.rom_dir", romDir)
settings.save()
mainWindow.setVisible(true)
updateStatus("开始安装LeonOS到目录: " .. romDir .. "\n请稍候...")
install(romDir)
break
end
-- 检查是否点击"取消"按钮
if x >= noButton.getX() and x <= noButton.getX() + noButton.getSize() and
y >= noButton.getY() and y <= noButton.getY() + 2 then
mainWindow.setVisible(false)
term.clear()
print("安装已取消")
break
end
end
end
end
-- 启动程序
main()