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" local DEFAULT_ROM_DIR = "/rc"
print("Start loading LeonOS installer...") local json, tu
print("[Installer] Loading module 1") local w, h
local mainWindow, progressBar, statusText
-- 确保中文字符正常显示
term.setEncoding("utf-8")
-- 下载函数
local function dl(f) local function dl(f)
local hand, err = http.get(f, nil, true) local hand, err = http.get(f, nil, true)
if not hand then if not hand then
@@ -14,159 +20,260 @@ local function dl(f)
return data return data
end end
print("[Installer] Loading done.")
print("[Installer] Loading module 2") -- 加载GitHub上的文件
-- 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")
local function ghload(f, c) local function ghload(f, c)
return assert(load(dl("https://gh.catmak.name/https://raw.githubusercontent.com/"..f), return assert(load(dl("https://gh.catmak.name/https://raw.githubusercontent.com/"..f),
"="..(c or f), "t", _G))() "="..(c or f), "t", _G))()
end end
print("[Installer] Loading done.")
print("[Installer] Loading module 6") -- 加载LeonOS的文件
local json = ghload("rxi/json.lua/master/json.lua", "ghload(json)")
package.loaded["rc.json"] = json
print("[Installer] Loading module 7")
local function rcload(f) local function rcload(f)
return ghload( return ghload(
"Leonmmcoset/LeonOS/refs/heads/main/data/computercraft/lua/rom/"..f, f) "Leonmmcoset/LeonOS/refs/heads/main/data/computercraft/lua/rom/"..f, f)
end 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() -- 设置term.at函数
local bar = (" "):rep(math.ceil((w-2) * progress)) function term.at(x, y)
term.at(1, y) term.setCursorPos(x, y)
tu.coloredPrint(colors.yellow, "[", {fg=colors.white, bg=colors.white}, bar, return term
{fg=colors.white, bg=colors.black}, (" "):rep((w-2)-#bar), end
colors.yellow, "]")
end -- 获取屏幕大小
print("[Installer] Loading done.") w, h = term.getSize()
term.at(1,1).clear()
tu.coloredPrint(colors.yellow, -- 创建主窗口
"LeonOS Installer\n=======================") mainWindow = window.create(term.native(), 1, 1, w, h)
print("You are going to install LeonOS to your computer.") mainWindow.setVisible(true)
print("This will overwrite any existing files in the installation directory.")
tu.coloredPrint(colors.yellow, "Are you sure? (y/n)") -- 绘制标题
local confirm = read() mainWindow.setTextColor(colors.yellow)
if confirm ~= "y" then mainWindow.setBackgroundColor(colors.black)
print("Installation cancelled.") mainWindow.at(2, 2).write("===== LeonOS 安装程序 ======")
return
-- 创建状态文本区域
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 end
local ROM_DIR -- 更新进度条
tu.coloredPrint("Enter installation directory ", colors.yellow, "[", local function updateProgress(current, total)
colors.lightBlue, DEFAULT_ROM_DIR, colors.yellow, "]") progressBar.clear()
tu.coloredWrite(colors.yellow, "$ installer >>>") local percentage = math.floor((current / total) * 100)
local barWidth = w - 10
local filledWidth = math.floor((current / total) * barWidth)
ROM_DIR = read() progressBar.at(1, 1).write("进度: " .. percentage .. "%")
if #ROM_DIR == 0 then ROM_DIR = DEFAULT_ROM_DIR end progressBar.at(1, 2).setBackgroundColor(colors.gray)
progressBar.at(1, 2).write(string.rep(" ", barWidth))
ROM_DIR = "/"..shell.resolve(ROM_DIR) progressBar.at(1, 2).setBackgroundColor(colors.green)
progressBar.at(1, 2).write(string.rep(" ", filledWidth))
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 end
local function ok() -- 更新状态文本
tu.coloredPrint(colors.green, "OK", colors.white) local function updateStatus(text)
end statusText.clear()
local lines = {}
bullet("Getting repository tree...") for line in text:gmatch("[^
]+") do
local repodata = dl("https://api.github.com/repos/Leonmmcoset/LeonOS/git/trees/main?recursive=1") table.insert(lines, line)
end
repodata = json.decode(repodata) for i, line in ipairs(lines) do
if i <= statusText.getSize() then
ok() statusText.at(1, i).write(line)
end
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
end end
end end
ok()
bullet("Creating directories...") -- 目录选择对话框
for i=#to_dl, 1, -1 do 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)
local confirmButton = window.create(dialogWindow, dialogWindow.getSize() - 15, 6, 10, 2)
confirmButton.setTextColor(colors.white)
confirmButton.setBackgroundColor(colors.green)
confirmButton.at(2, 1).write("确认")
local dirInput = defaultDir
local editing = true
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
dialogWindow.at(9, 4).write(string.rep(" ", 30))
dialogWindow.at(9, 4).write(dirInput)
end
dialogWindow.setVisible(false)
return dirInput
end
-- 主安装函数
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] local v = to_dl[i]
if v.type == "tree" then if v.type == "tree" then
fs.makeDir(fs.combine(v.real_path)) fs.makeDir(fs.combine(v.real_path))
table.remove(to_dl, i) table.remove(to_dl, i)
end end
end end
ok() updateProgress(3, 5)
bullet("Downloading files...") updateStatus("正在下载文件...\n这可能需要一些时间...")
local okx, oky = term.getCursorPos() -- 下载文件
io.write("\n") local done = 0
local _, pby = term.getCursorPos() local parallels = {}
local parallels = {} for i = 1, #to_dl, 1 do
local done = 0
for i=1, #to_dl, 1 do
local v = to_dl[i] local v = to_dl[i]
if v.type == "blob" then if v.type == "blob" then
parallels[#parallels+1] = function() 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) 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() assert(io.open(v.real_path, "w")):write(data):close()
done = done + 1 done = done + 1
progress(pby, done, #to_dl) updateProgress(3 + (done / #to_dl), 5)
end
end end
end end
end
parallel.waitForAll(table.unpack(parallels)) parallel.waitForAll(table.unpack(parallels))
updateProgress(4, 5)
term.at(1, pby).write((" "):rep((term.getSize()))) updateStatus("正在设置启动文件...\n请稍候...")
term.at(okx, oky) -- 设置启动文件
ok() assert(io.open(
assert(io.open(
fs.exists("/startup.lua") and "/unbios-rc.lua" or "/startup.lua", "w")) fs.exists("/startup.lua") and "/unbios-rc.lua" or "/startup.lua", "w"))
:write(dl( :write(dl(
"https://gh.catmak.name/https://raw.githubusercontent.com/Leonmmcoset/LeonOS/refs/heads/main/unbios.lua" "https://gh.catmak.name/https://raw.githubusercontent.com/Leonmmcoset/LeonOS/refs/heads/main/unbios.lua"
)):close() )):close()
updateProgress(5, 5)
tu.coloredPrint(colors.yellow, "Your computer will restart in 3 seconds.") updateStatus("安装完成!\n计算机将在3秒后重启...")
local _, y = term.getCursorPos() -- 重启倒计时
for i = 3, 1, -1 do
for i=1, 3, 1 do statusText.at(1, 3).write("计算机将在 " .. i .. " 秒后重启...")
progress(y, i, 5)
os.sleep(1) os.sleep(1)
end
os.reboot()
end end
os.reboot() -- 主函数
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()